{"version":3,"file":"Chapters.d.ts","sourceRoot":"","sources":["../../src/components/Chapters.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtH,MAAM,WAAW,IAAI;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CAChB;AA2BD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAM/C;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAmC/D;AAoBD,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,EAAE,CAExE;AAED,eAAO,MAAM,WAAW,4BAAoD,CAAC","sourcesContent":["/**\n * Chapters — auto-fold transcript turns by detected intent.\n *\n * The TUI keeps a flat history; chapters group consecutive turns that share\n * a detected intent (\"debug\", \"refactor\", \"test\", \"explain\", …) into a\n * single foldable unit. Detection is heuristic (keyword + tool-pattern) so\n * the UX stays predictable and predictable-feeling cheap.\n */\n\nexport type Intent = \"implement\" | \"debug\" | \"refactor\" | \"test\" | \"explain\" | \"review\" | \"plan\" | \"memory\" | \"other\";\n\nexport interface Turn {\n\tid: string;\n\trole: \"user\" | \"assistant\" | \"tool\";\n\ttext: string;\n\ttools?: string[];\n\ttimestamp: number;\n}\n\nexport interface Chapter {\n\tid: string;\n\tintent: Intent;\n\ttitle: string;\n\tturns: Turn[];\n\tstartedAt: number;\n\tendedAt: number;\n\tfolded: boolean;\n}\n\ninterface IntentRule {\n\tintent: Intent;\n\tkeywords: RegExp;\n\ttools?: RegExp;\n}\n\nconst RULES: IntentRule[] = [\n\t{\n\t\tintent: \"debug\",\n\t\tkeywords:\n\t\t\t/\\b(bug|crash|stack trace|null|undefined|reproduce|fail(ed|ing)?|exception|TypeError|RangeError|why is|why does|broken)\\b/i,\n\t},\n\t{ intent: \"test\", keywords: /\\b(test|spec|vitest|jest|node:test|coverage|assertion)s?\\b/i, tools: /Bash|Test/ },\n\t{ intent: \"refactor\", keywords: /\\b(refactor|rename|extract|inline|deduplicat|cleanup|tidy|simplif|reorganiz)/i },\n\t{\n\t\tintent: \"implement\",\n\t\tkeywords: /\\b(implement|add|create|build|wire|hook up|introduce|scaffold)\\b/i,\n\t\ttools: /Edit|Write/,\n\t},\n\t{ intent: \"explain\", keywords: /\\b(explain|what does|how does|walk me through|tell me about|what is)\\b/i },\n\t{ intent: \"review\", keywords: /\\b(review|critique|feedback|audit|lgtm|nit:)/i },\n\t{ intent: \"plan\", keywords: /\\b(plan|outline|strategy|approach|design|architecture|sketch)\\b/i },\n\t{ intent: \"memory\", keywords: /\\b(remember|memory|memorize|recall|forget)\\b/i },\n];\n\n/**\n * Classify a single turn into an intent. The first matching rule wins; ties\n * are broken by rule order, which mirrors how a user perceives priority\n * (test/debug/refactor before generic implement).\n */\nexport function detectIntent(turn: Turn): Intent {\n\tfor (const rule of RULES) {\n\t\tif (rule.keywords.test(turn.text)) return rule.intent;\n\t\tif (rule.tools && turn.tools && turn.tools.some((t) => rule.tools?.test(t))) return rule.intent;\n\t}\n\treturn \"other\";\n}\n\n/**\n * Group a turn stream into chapters. Consecutive same-intent turns merge;\n * tool-only turns inherit the previous user/assistant intent so they don't\n * fragment a chapter.\n */\nexport function groupTurnsIntoChapters(turns: Turn[]): Chapter[] {\n\tconst out: Chapter[] = [];\n\tlet current: Chapter | undefined;\n\tlet lastNonToolIntent: Intent | undefined;\n\n\tfor (const turn of turns) {\n\t\tlet intent: Intent;\n\t\tif (turn.role === \"tool\" && lastNonToolIntent) {\n\t\t\tintent = lastNonToolIntent;\n\t\t} else {\n\t\t\tintent = detectIntent(turn);\n\t\t\tif (turn.role !== \"tool\") lastNonToolIntent = intent;\n\t\t}\n\n\t\tif (current && current.intent === intent) {\n\t\t\tcurrent.turns.push(turn);\n\t\t\tcurrent.endedAt = turn.timestamp;\n\t\t} else {\n\t\t\tcurrent = {\n\t\t\t\tid: `ch-${out.length + 1}`,\n\t\t\t\tintent,\n\t\t\t\ttitle: titleForIntent(intent, turn),\n\t\t\t\tturns: [turn],\n\t\t\t\tstartedAt: turn.timestamp,\n\t\t\t\tendedAt: turn.timestamp,\n\t\t\t\t// Fold every chapter except the last by default; the renderer\n\t\t\t\t// flips the most recent open after grouping.\n\t\t\t\tfolded: true,\n\t\t\t};\n\t\t\tout.push(current);\n\t\t}\n\t}\n\n\tif (out.length > 0) out[out.length - 1].folded = false;\n\treturn out;\n}\n\nfunction titleForIntent(intent: Intent, firstTurn: Turn): string {\n\tconst summary = firstTurn.text.replace(/\\s+/g, \" \").trim().slice(0, 60);\n\tconst prefix = INTENT_LABELS[intent];\n\treturn summary.length > 0 ? `${prefix}: ${summary}` : prefix;\n}\n\nconst INTENT_LABELS: Record<Intent, string> = {\n\timplement: \"Implement\",\n\tdebug: \"Debug\",\n\trefactor: \"Refactor\",\n\ttest: \"Test\",\n\texplain: \"Explain\",\n\treview: \"Review\",\n\tplan: \"Plan\",\n\tmemory: \"Memory\",\n\tother: \"Notes\",\n};\n\n/** Toggle a chapter's folded state. Returns a new array (immutable). */\nexport function toggleChapter(chapters: Chapter[], id: string): Chapter[] {\n\treturn chapters.map((c) => (c.id === id ? { ...c, folded: !c.folded } : c));\n}\n\nexport const intentLabel = (intent: Intent): string => INTENT_LABELS[intent];\n"]}