package strategy import ( "io" "strings" "git.mleku.dev/mleku/dendrite/pkg/axiom" ) // Enzyme decomposes The Art of War into multi-level strategy elements. type Enzyme struct{} // CanDigest returns true for text containing Art of War markers. func (Enzyme) CanDigest(sample []byte) bool { s := string(sample) return strings.Contains(s, "Sun Tz") || strings.Contains(s, "Art of War") } // Digest reads The Art of War and emits strategy elements at four levels. func (Enzyme) Digest(r io.Reader) <-chan axiom.Element { ch := make(chan axiom.Element, 256) go func() { defer close(ch) data, err := io.ReadAll(r) if err != nil { return } chapters := parseTextIntoChapters(string(data)) for _, chap := range chapters { // Chapter-level element. ch <- StrategyElement{ Content: chap.Title + ": " + summarize(chap.Text, 200), Ch: chap.Number, Cat: chap.Category, Lvl: LevelChapter, } // Principle-level: paragraphs. for _, para := range splitParagraphs(chap.Text) { if len(para) < 20 { continue } ch <- StrategyElement{ Content: para, Ch: chap.Number, Cat: chap.Category, Lvl: LevelPrinciple, } } // Maxim-level: extracted key sentences. for _, maxim := range chap.Maxims { ch <- StrategyElement{ Content: maxim.Text, Ch: chap.Number, Cat: chap.Category, Lvl: LevelMaxim, } } // Tactic-level: short imperative phrases. for _, tactic := range extractTactics(chap.Text) { ch <- StrategyElement{ Content: tactic, Ch: chap.Number, Cat: chap.Category, Lvl: LevelTactic, } } } }() return ch } // DigestEmbedded digests the built-in Art of War text. func DigestEmbedded() <-chan axiom.Element { return (Enzyme{}).Digest(strings.NewReader(artOfWarText)) } // parseTextIntoChapters splits arbitrary Art of War text into chapters. // Uses the same markers as the embedded text. func parseTextIntoChapters(text string) []Chapter { lines := strings.Split(text, "\n") type boundary struct { line int idx int } var bounds []boundary for i, line := range lines { for j, def := range chapterDefs { if strings.HasPrefix(line, def.marker) { bounds = append(bounds, boundary{i, j}) } } } result := make([]Chapter, 0, len(bounds)) for bi, b := range bounds { end := len(lines) if bi+1 < len(bounds) { end = bounds[bi+1].line } var textLines []string for _, l := range lines[b.line+1 : end] { textLines = append(textLines, l) } text := strings.TrimSpace(strings.Join(textLines, "\n")) def := chapterDefs[b.idx] result = append(result, Chapter{ Number: b.idx + 1, Title: def.title, Category: def.category, Text: text, Maxims: extractMaxims(text, b.idx+1), }) } return result } // splitParagraphs splits text on blank lines. func splitParagraphs(text string) []string { raw := strings.Split(text, "\n\n") var result []string for _, p := range raw { p = strings.TrimSpace(p) if p != "" { result = append(result, p) } } return result } // summarize returns the first n bytes of text, ending at a word boundary. func summarize(text string, n int) string { if len(text) <= n { return text } cut := strings.LastIndex(text[:n], " ") if cut < 0 { cut = n } return text[:cut] + "..." } // extractTactics identifies short imperative phrases from chapter text. func extractTactics(text string) []string { sentences := splitSentences(text) var tactics []string for _, s := range sentences { s = strings.TrimSpace(s) words := strings.Fields(s) // Tactics are short, punchy directives. if len(words) >= 3 && len(words) <= 15 && isTactical(s) { tactics = append(tactics, s) } } return tactics } // isTactical checks if a short sentence is a tactical directive. func isTactical(s string) bool { lower := strings.ToLower(s) tactical := []string{ "attack", "defend", "retreat", "advance", "hold", "strike", "feign", "entice", "crush", "separate", "surround", "divide", "wait", "avoid", "pursue", "do not", "never", "always", "beware", "appear", "conceal", "hide", "reveal", } for _, kw := range tactical { if strings.Contains(lower, kw) { return true } } return false }