@xdevops/issue-auto-finish 1.0.87 → 1.0.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AIRunnerRegistry-II3WWSFN.js → AIRunnerRegistry-CFDNWSXC.js} +6 -3
- package/dist/{LockNote-Z2CLDZNN.js → LockNote-W2JNVMW7.js} +3 -3
- package/dist/PtyRunner-NYASBTRP.js +33 -0
- package/dist/SdkRunner-U2OTOMZU.js +9 -0
- package/dist/ai-runner/AIRunner.d.ts +19 -1
- package/dist/ai-runner/AIRunner.d.ts.map +1 -1
- package/dist/ai-runner/AIRunnerRegistry.d.ts +8 -0
- package/dist/ai-runner/AIRunnerRegistry.d.ts.map +1 -1
- package/dist/ai-runner/PlanFileResolver.d.ts +53 -0
- package/dist/ai-runner/PlanFileResolver.d.ts.map +1 -0
- package/dist/ai-runner/PtyRunner.d.ts +45 -4
- package/dist/ai-runner/PtyRunner.d.ts.map +1 -1
- package/dist/ai-runner/SdkRunner.d.ts +22 -0
- package/dist/ai-runner/SdkRunner.d.ts.map +1 -0
- package/dist/ai-runner/index.d.ts +5 -2
- package/dist/ai-runner/index.d.ts.map +1 -1
- package/dist/ai-runner/sdk/ClaudeCodeSDK.d.ts +37 -0
- package/dist/ai-runner/sdk/ClaudeCodeSDK.d.ts.map +1 -0
- package/dist/ai-runner/sdk/Stream.d.ts +22 -0
- package/dist/ai-runner/sdk/Stream.d.ts.map +1 -0
- package/dist/ai-runner/sdk/types.d.ts +146 -0
- package/dist/ai-runner/sdk/types.d.ts.map +1 -0
- package/dist/{ai-runner-HLA44WI6.js → ai-runner-TOHVJJ76.js} +14 -5
- package/dist/{analyze-ZIXNC5GN.js → analyze-DBH4K3J7.js} +8 -6
- package/dist/{analyze-ZIXNC5GN.js.map → analyze-DBH4K3J7.js.map} +1 -1
- package/dist/{braindump-56WAY2RD.js → braindump-RYI4BGMG.js} +11 -9
- package/dist/{braindump-56WAY2RD.js.map → braindump-RYI4BGMG.js.map} +1 -1
- package/dist/{chunk-AVGZH64A.js → chunk-2RWGZPNF.js} +4 -1
- package/dist/chunk-2RWGZPNF.js.map +1 -0
- package/dist/chunk-4XMYOXGZ.js +1153 -0
- package/dist/chunk-4XMYOXGZ.js.map +1 -0
- package/dist/{chunk-UBQLXQ7I.js → chunk-5JBADEKR.js} +7 -7
- package/dist/{chunk-M5C2WILQ.js → chunk-5M5SB6ZA.js} +7 -5
- package/dist/{chunk-M5C2WILQ.js.map → chunk-5M5SB6ZA.js.map} +1 -1
- package/dist/{chunk-HDFNMVRQ.js → chunk-DVNAH2GV.js} +2 -2
- package/dist/{chunk-GXFG4JU6.js → chunk-EU4XFZ2T.js} +2 -2
- package/dist/{chunk-NZHKAPU6.js → chunk-FJTZKAJA.js} +9 -3
- package/dist/chunk-FJTZKAJA.js.map +1 -0
- package/dist/chunk-G7QI5WDI.js +14 -0
- package/dist/chunk-G7QI5WDI.js.map +1 -0
- package/dist/{chunk-2YQHKXLL.js → chunk-GPZX4DSY.js} +22 -6
- package/dist/chunk-GPZX4DSY.js.map +1 -0
- package/dist/{chunk-IP3QTP5A.js → chunk-IWSMQXBL.js} +189 -48
- package/dist/chunk-IWSMQXBL.js.map +1 -0
- package/dist/{chunk-O3WEV5W3.js → chunk-JMACM7AJ.js} +47 -9
- package/dist/chunk-JMACM7AJ.js.map +1 -0
- package/dist/chunk-MSL7ROVK.js +1 -0
- package/dist/{chunk-YCYVNRLF.js → chunk-OBGEEGQ3.js} +61 -19
- package/dist/chunk-OBGEEGQ3.js.map +1 -0
- package/dist/chunk-R32Q3RGK.js +666 -0
- package/dist/chunk-R32Q3RGK.js.map +1 -0
- package/dist/{chunk-SAMTXC4A.js → chunk-TFEPHOVE.js} +12 -17
- package/dist/chunk-TFEPHOVE.js.map +1 -0
- package/dist/{chunk-QZZGIZWC.js → chunk-XSX3PGQW.js} +63 -20
- package/dist/chunk-XSX3PGQW.js.map +1 -0
- package/dist/{chunk-2MESXJEZ.js → chunk-YNRKPQLS.js} +3 -3
- package/dist/cli/setup/PreflightChecker.d.ts +1 -0
- package/dist/cli/setup/PreflightChecker.d.ts.map +1 -1
- package/dist/cli/setup/env-metadata.d.ts.map +1 -1
- package/dist/cli.js +10 -9
- package/dist/cli.js.map +1 -1
- package/dist/{config-WTRSZLOC.js → config-23TBYFP5.js} +5 -4
- package/dist/config-schema.d.ts +6 -0
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/{doctor-37JNBGDN.js → doctor-ZG3DO7J5.js} +3 -3
- package/dist/errors/AIExecutionError.d.ts +3 -0
- package/dist/errors/AIExecutionError.d.ts.map +1 -1
- package/dist/{errors-S3BWYA4I.js → errors-J3ZRP66W.js} +2 -2
- package/dist/events/EventBus.d.ts +1 -1
- package/dist/events/EventBus.d.ts.map +1 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -14
- package/dist/{init-QQDXGTPB.js → init-37DLQ5AJ.js} +9 -8
- package/dist/{init-QQDXGTPB.js.map → init-37DLQ5AJ.js.map} +1 -1
- package/dist/lib.js +10 -8
- package/dist/lib.js.map +1 -1
- package/dist/orchestrator/PendingDialogStore.d.ts +12 -0
- package/dist/orchestrator/PendingDialogStore.d.ts.map +1 -0
- package/dist/orchestrator/steps/FailureHandler.d.ts.map +1 -1
- package/dist/orchestrator/steps/PhaseLoopStep.d.ts.map +1 -1
- package/dist/persistence/PlanPersistence.d.ts +5 -0
- package/dist/persistence/PlanPersistence.d.ts.map +1 -1
- package/dist/persistence/TodolistExtractor.d.ts +31 -0
- package/dist/persistence/TodolistExtractor.d.ts.map +1 -0
- package/dist/phases/BasePhase.d.ts.map +1 -1
- package/dist/phases/PhaseOutcome.d.ts +2 -0
- package/dist/phases/PhaseOutcome.d.ts.map +1 -1
- package/dist/phases/PlanPhase.d.ts.map +1 -1
- package/dist/prompts/templates.d.ts +2 -2
- package/dist/prompts/templates.d.ts.map +1 -1
- package/dist/{restart-BMILTP5X.js → restart-C7QBXT44.js} +9 -8
- package/dist/{restart-BMILTP5X.js.map → restart-C7QBXT44.js.map} +1 -1
- package/dist/run.js +16 -14
- package/dist/run.js.map +1 -1
- package/dist/start-66JO56AW.js +16 -0
- package/dist/start-66JO56AW.js.map +1 -0
- package/dist/tracker/IssueTracker.d.ts +6 -0
- package/dist/tracker/IssueTracker.d.ts.map +1 -1
- package/dist/web/routes/api.d.ts.map +1 -1
- package/package.json +5 -1
- package/src/web/frontend/dist/assets/index-DJzC2saL.css +1 -0
- package/src/web/frontend/dist/assets/{index-D_oTMuJU.js → index-Mnu8M3ww.js} +57 -57
- package/src/web/frontend/dist/index.html +2 -2
- package/dist/PtyRunner-6UGI5STW.js +0 -22
- package/dist/chunk-2YQHKXLL.js.map +0 -1
- package/dist/chunk-AVGZH64A.js.map +0 -1
- package/dist/chunk-IP3QTP5A.js.map +0 -1
- package/dist/chunk-NZHKAPU6.js.map +0 -1
- package/dist/chunk-O3WEV5W3.js.map +0 -1
- package/dist/chunk-QZZGIZWC.js.map +0 -1
- package/dist/chunk-SAMTXC4A.js.map +0 -1
- package/dist/chunk-U237JSLB.js +0 -1
- package/dist/chunk-U6GWFTKA.js +0 -657
- package/dist/chunk-U6GWFTKA.js.map +0 -1
- package/dist/chunk-YCYVNRLF.js.map +0 -1
- package/dist/start-6QRW6IJI.js +0 -15
- package/src/web/frontend/dist/assets/index-COYziOhv.css +0 -1
- /package/dist/{AIRunnerRegistry-II3WWSFN.js.map → AIRunnerRegistry-CFDNWSXC.js.map} +0 -0
- /package/dist/{LockNote-Z2CLDZNN.js.map → LockNote-W2JNVMW7.js.map} +0 -0
- /package/dist/{PtyRunner-6UGI5STW.js.map → PtyRunner-NYASBTRP.js.map} +0 -0
- /package/dist/{ai-runner-HLA44WI6.js.map → SdkRunner-U2OTOMZU.js.map} +0 -0
- /package/dist/{chunk-U237JSLB.js.map → ai-runner-TOHVJJ76.js.map} +0 -0
- /package/dist/{chunk-UBQLXQ7I.js.map → chunk-5JBADEKR.js.map} +0 -0
- /package/dist/{chunk-HDFNMVRQ.js.map → chunk-DVNAH2GV.js.map} +0 -0
- /package/dist/{chunk-GXFG4JU6.js.map → chunk-EU4XFZ2T.js.map} +0 -0
- /package/dist/{config-WTRSZLOC.js.map → chunk-MSL7ROVK.js.map} +0 -0
- /package/dist/{chunk-2MESXJEZ.js.map → chunk-YNRKPQLS.js.map} +0 -0
- /package/dist/{errors-S3BWYA4I.js.map → config-23TBYFP5.js.map} +0 -0
- /package/dist/{doctor-37JNBGDN.js.map → doctor-ZG3DO7J5.js.map} +0 -0
- /package/dist/{start-6QRW6IJI.js.map → errors-J3ZRP66W.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ai-runner/PtyRunner.ts","../src/ai-runner/PlanFileResolver.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { AIRunner, RunOptions, RunResult, StreamEvent, DialogOption } from './AIRunner.js';\nimport type { PtyProfile } from './AIRunnerRegistry.js';\nimport { getPtyProfile, getRegistryEntry } from './AIRunnerRegistry.js';\nimport { getRunnerCapabilities } from './AIRunnerRegistry.js';\nimport { resolveModelForRunner } from './ModelMapping.js';\nimport { PlanFileResolver } from './PlanFileResolver.js';\nimport type { TerminalManager } from '../terminal/TerminalManager.js';\nimport { isShuttingDown } from '../shutdown/ShutdownSignal.js';\nimport { logger as rootLogger } from '../logger.js';\n\nconst logger = rootLogger.child('PtyRunner');\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[?><=]*[0-9;]*[a-zA-Z~]|\\x1b\\][^\\x07]*\\x07|\\x1b\\(B/g;\n\nexport function stripAnsi(str: string): string {\n return str.replace(ANSI_RE, '');\n}\n\n/** Best-effort extraction of issue IID from a worktree path like `.../issue-123/...` */\nfunction extractIidFromPath(workDir: string): number | undefined {\n const match = workDir.match(/issue-(\\d+)/);\n return match ? parseInt(match[1], 10) : undefined;\n}\n\n/** Claude Code / Codebuddy shows a prompt when idle and waiting for user input.\n * - Claude Code: ❯ (U+276F) — unique character, safe to match broadly\n * - Codebuddy: > — appears between separator lines, above ⏵⏵ status bar\n */\nexport function isIdlePrompt(stripped: string): boolean {\n const lines = stripped.split('\\n').filter((l) => l.trim());\n if (lines.length === 0) return false;\n const last = lines[lines.length - 1].trim();\n // 1. Prompt character on the last non-empty line\n if (/^[❯>$%]\\s*$/.test(last) || /[❯>]\\s*$/.test(last)) return true;\n // 2. Claude Code TUI: ❯ may appear mid-line due to cursor-positioned rendering\n // (status bar appended below, cursor coordinate artifacts like \"42\" after ❯).\n // Three-step heuristic: standalone ❯ + not followed by a sentence = idle.\n if (lines.some((l) => {\n const t = l.trim();\n // ❯ must be standalone (preceded by whitespace or at start of string)\n if (!/(?:^|\\s)❯/.test(t)) return false;\n // ❯ at line end → definitely idle\n if (/❯\\s*$/.test(t)) return true;\n // ❯ followed by 3+ letter word → input echo / queued message, not idle\n return !/❯\\s+[A-Za-z]{3,}/.test(t);\n })) return true;\n // 3. Codebuddy TUI: > prompt appears above the ⏵⏵ status bar, so it's NOT\n // the last line. Use ⏵ as a Codebuddy-specific guard to distinguish from\n // random > in markdown/agent output.\n if (lines.some((l) => /^>/.test(l.trim())) && /⏵/.test(stripped)) return true;\n // 4. cursor-agent TUI: → placeholder in input box + footer with / commands,\n // and NO \"ctrl+c to stop\" (which indicates generation in progress).\n if (\n /→/.test(stripped) &&\n /\\/\\s*commands/.test(stripped) &&\n !/ctrl\\+c\\s*to\\s*stop/i.test(stripped)\n ) {\n return true;\n }\n return false;\n}\n\n// ---- Known TUI noise patterns ------------------------------------------------\n\nconst SPINNER_CHARS = '✶✻✽✢✾◆❖●◐◑◒◓○⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⠂⠒⠐⠈⠠⠤·*…⋯⬡⬢';\nconst SPINNER_RE = new RegExp(`^[${SPINNER_CHARS}\\\\s]+$`);\nconst SPINNER_FRAGMENT_RE = new RegExp(`^[${SPINNER_CHARS}][a-zA-Z]{0,3}$`);\nconst STATUS_BAR_RE = /bypass permissions|shift\\+tab|ctrl\\+[a-z].*(?:to |edit)|to interrupt|to cycle|⏵.*(?:bypass|permission)/;\nconst THINKING_RE = /^[^\\w]*\\w[\\w-]*…\\s*$/;\nconst SEPARATOR_RE = /^[─━═-]{10,}$/;\nconst EFFORT_RE = /^[◐◑◒◓]\\s+(?:low|medium|high)\\s+·\\s+\\/effort$/;\n// Claude Code status bar: spinner + elapsed time + token count, e.g. \"✶80s · ↓ 14.2k tokens)\"\nconst CLAUDE_STATUS_BAR_RE = new RegExp(`^[${SPINNER_CHARS}]\\\\s*\\\\d+[smh]`);\n// Cursor positioning artifacts: bare 1-3 digit numbers left after ANSI stripping\nconst CURSOR_ARTIFACT_RE = /^\\d{1,3}$/;\n\n// ---- cursor-agent TUI noise patterns ----------------------------------------\nconst CURSOR_GENERATING_RE = /^[⬡⬢]\\s*Generating/;\nconst CURSOR_FOOTER_RE = /^\\/\\s*commands\\s*·\\s*@\\s*files\\s*·\\s*!\\s*shell$/;\nconst CURSOR_STATUS_RE = /^▶︎?\\s*Auto-run/;\nconst CURSOR_MODEL_RE = /^(?:Opus|Claude|GPT|Gemini|当前使用的模型)\\s*/;\nconst BOX_DRAWING_RE = /^[┌┐└┘│─┬┴├┤┼╭╮╰╯═║╔╗╚╝╠╣╦╩╬\\s]*$/;\n// Claude Code queued message indicator — NOT an idle prompt\nconst QUEUED_MSG_RE = /Press\\s*up\\s*to\\s*edit\\s*queued\\s*messages/;\n\n// ---- Claude Code stop hook / session-end patterns ---------------------------\n// After completing a task, Claude Code runs a \"stop hook\" and then displays a\n// summary + banner. These outputs must be classified as noise to prevent them\n// from resetting `lastOutputTime` and blocking the idle debounce.\n\n// Stop hook thinking line: \"✽Fiddle-faddling… (running stop hook · 9m39s · ↓ 6.8k tokens)\"\n// Extends THINKING_RE to allow a trailing parenthetical (stats / hook info).\nconst STOP_HOOK_RE = /^[^\\w]*\\w[\\w-]*…\\s*\\(.*\\)\\s*$/;\n\n// Session summary: \"✻Worked for 9m39s\" / \"✻Cogitated for 3m37s\" / \"✻工作了 5m12s\"\n// Claude Code rotates verbs (Worked, Cogitated, Pondered, Reasoned …).\n// Match any `<spinner><Word> for <duration>` or Chinese `工作了`.\n// The `m` flag lets `^` match at the start of each line inside multi-line PTY chunks.\nconst WORKED_SUMMARY_RE = new RegExp(`^[${SPINNER_CHARS}]\\\\s*(?:\\\\w+\\\\s+for|工作了)\\\\s+\\\\d+[smh]`, 'm');\n\n// Claude Code banner after stop hook — uses Block Element characters (▐▛▜▌▝▘█)\n// that are distinct from traditional Box Drawing characters (┌┐└┘│─).\n// Pure block-element lines (logo art) and lines starting with block elements\n// followed by product info (e.g. \"▐▛███▜▌ Claude Code v2.1.78\").\nconst CLAUDE_BANNER_RE = /^[▐▛▜▌▝▘█\\s]+$/;\nconst CLAUDE_BANNER_INFO_RE = /^[▐▛▜▌▝▘█]+\\s+(?:Claude|Opus|Gemini|GPT|Sonnet|Haiku)/;\n\n/** Workspace trust dialog — TUI strips spaces, so use \\s* between words.\n * Claude Code: \"trust this folder\" / \"I trust this\"\n * Codebuddy: \"Do you trust the files in this folder?\"\n */\nexport const TRUST_DIALOG_RE = /trust\\s*(?:the\\s*files\\s*in\\s*)?this\\s*(?:folder|workspace)|I\\s*trust\\s*this/i;\n\n/** Permission / confirmation dialog — Claude Code asks \"Do you want to proceed?\"\n * when running potentially dangerous commands in plan mode.\n * The dialog shows numbered options with \"Yes\" pre-selected (❯ 1. Yes).\n * Auto-confirm by sending Enter to select the highlighted option. */\nexport const PERMISSION_DIALOG_RE =\n /Do\\s*you\\s*want\\s*to\\s*proceed\\s*\\?|(?:❯|>)\\s*\\d+\\.\\s*Yes\\b|Allow\\s+(?:this|the)\\s+(?:action|command)\\s*\\?/i;\n\n/** Plan confirmation dialog — Claude Code shows this when the plan is finalized\n * and ready to execute. This is the definitive signal that plan mode is done.\n * Matches: \"Claude has written up a plan and is ready to execute\"\n * or \"Yes, and bypass permissions\" (unique to this dialog). */\nexport const PLAN_CONFIRM_RE =\n /written\\s*up\\s*a\\s*plan|ready\\s*to\\s*execute.*(?:Would|proceed)|Yes,?\\s*and\\s*bypass\\s*permissions/i;\n\n// ---- Interactive dialog detection (brainstorming interview, etc.) ----------\n\nconst NUMBERED_OPTION_RE = /(?:❯|>)?\\s*(\\d+)\\.\\s*(.+)/;\n\n/** Navigation hint shown at the bottom of Claude Code interactive selection menus.\n * Presence of this line is a strong positive signal for an interactive dialog. */\nexport const INTERACTIVE_NAV_HINT_RE =\n /Enter\\s*to\\s*select|↑.*↓.*navigate|Esc\\s*to\\s*cancel/i;\n\n/** Claude Code checkbox header (e.g. \"☐ 操作确认\") — a strong TUI dialog signal. */\nconst CHECKBOX_HEADER_RE = /☐/;\n\n/** ❯ prefix on an option line — Claude Code highlights the selected option with ❯. */\nconst HIGHLIGHTED_OPTION_RE = /❯\\s*\\d+\\./;\n\nexport type DialogConfidence = 'high' | 'low';\n\n/**\n * Parse a TUI frame containing a numbered-choice dialog. Returns the question\n * text and option list, or null if the frame doesn't contain a valid dialog.\n *\n * When a navigation hint (\"Enter to select · ↑/↓ to navigate\") is present,\n * the minimum option threshold is lowered to 1 (ANSI stripping may corrupt\n * some option lines). Otherwise, at least 2 numbered options are required.\n */\nexport function parseInteractiveDialog(stripped: string): { question: string; options: DialogOption[] } | null {\n const lines = stripped.split(/[\\r\\n]+/).map(l => l.trim()).filter(Boolean);\n const options: DialogOption[] = [];\n const questionParts: string[] = [];\n let optionsStarted = false;\n\n for (const line of lines) {\n if (isTuiNoise(line)) continue;\n if (INTERACTIVE_NAV_HINT_RE.test(line)) continue;\n const m = NUMBERED_OPTION_RE.exec(line);\n if (m) {\n const label = m[2].trim();\n // Guard against decimal numbers (e.g. \"3.14\") — label must be >= 2 chars\n // and not purely numeric.\n if (label.length >= 2 && !/^\\d+$/.test(label)) {\n // Handle merged lines: ANSI cursor-position stripping can merge the\n // question text and the first option onto a single line. Extract\n // the prefix before the match as question text.\n if (m.index > 0 && !optionsStarted) {\n const prefix = line.slice(0, m.index)\n .replace(/[─━═╌☐]+/g, ' ')\n .trim();\n if (prefix.length > 0) questionParts.push(prefix);\n }\n optionsStarted = true;\n options.push({ index: parseInt(m[1], 10), label });\n }\n } else if (!optionsStarted) {\n if (!/^[❯>$%]\\s*$/.test(line) && !/^[─━═╌]{4,}$/.test(line)) {\n questionParts.push(line);\n }\n }\n }\n\n const minRequired = INTERACTIVE_NAV_HINT_RE.test(stripped) ? 1 : 2;\n if (options.length < minRequired) return null;\n return { question: questionParts.join(' ').trim(), options };\n}\n\n/**\n * Assess the confidence that a frame is a genuine interactive dialog vs. an\n * agent's numbered markdown list. High-confidence signals include TUI-specific\n * chrome that only appears in real dialogs (nav hint, ❯ highlight, ☐ header).\n */\nexport function getDialogConfidence(stripped: string): DialogConfidence {\n if (INTERACTIVE_NAV_HINT_RE.test(stripped)) return 'high';\n if (HIGHLIGHTED_OPTION_RE.test(stripped)) return 'high';\n if (CHECKBOX_HEADER_RE.test(stripped)) return 'high';\n return 'low';\n}\n\n/**\n * Returns true when the frame contains an interactive numbered-choice dialog\n * that is NOT already handled by PLAN_CONFIRM_RE or PERMISSION_DIALOG_RE.\n *\n * Two detection paths:\n * 1. Standard: parseInteractiveDialog finds >= 2 numbered options.\n * 2. Nav-hint fallback: the frame contains a navigation hint line\n * (\"Enter to select · ↑/↓ to navigate\") AND at least 1 numbered option.\n * This handles cases where ANSI stripping corrupts some option lines.\n */\nexport function isInteractiveDialog(stripped: string): boolean {\n if (PLAN_CONFIRM_RE.test(stripped)) return false;\n if (PERMISSION_DIALOG_RE.test(stripped)) return false;\n if (parseInteractiveDialog(stripped) !== null) return true;\n // Fallback: nav hint + at least 1 numbered option\n if (INTERACTIVE_NAV_HINT_RE.test(stripped)) {\n const lines = stripped.split(/[\\r\\n]+/).map(l => l.trim()).filter(Boolean);\n return lines.some(l => !isTuiNoise(l) && NUMBERED_OPTION_RE.test(l));\n }\n return false;\n}\n\n/**\n * Check if a PTY data frame contains active work content alongside the idle\n * prompt. Claude Code TUI co-renders the ❯ prompt with tool results, file\n * listings, and thinking indicators in a single data frame. When both are\n * present, the frame is a \"mixed frame\" — the agent is still active.\n *\n * Returns true when the frame has meaningful non-noise, non-prompt content.\n */\nexport function containsActiveWork(stripped: string): boolean {\n const lines = stripped.split(/[\\r\\n]+/).filter(l => l.trim());\n let inTipBlock = false;\n\n return lines.some(line => {\n const t = line.trim();\n if (t.length === 0) return false;\n\n if (/Tip:/i.test(t)) {\n inTipBlock = true;\n return false;\n }\n if (inTipBlock) return false;\n\n if (/ctrl\\+o to expand/i.test(t)) return false;\n if (/^⎿/.test(t)) return false;\n if (/^[❯>$%]\\s*$/.test(t) || /[❯>]\\s*$/.test(t)) return false;\n // Table data rows (e.g. │pnpmlint│✅通过│) — completed report output, not active work\n if (/^[│║┃]/.test(t) && /[│║┃]$/.test(t)) return false;\n if (isTuiNoise(t)) return false;\n return true;\n });\n}\n\n/** True when the line is Claude Code TUI chrome (spinners, status bar, etc.) */\nexport function isTuiNoise(line: string): boolean {\n const t = line.replace(/\\r/g, '').trim();\n if (t.length === 0) return true;\n if (SPINNER_RE.test(t)) return true;\n if (t.length <= 4 && SPINNER_FRAGMENT_RE.test(t)) return true;\n if (STATUS_BAR_RE.test(t)) return true;\n if (THINKING_RE.test(t)) return true;\n if (SEPARATOR_RE.test(t)) return true;\n if (EFFORT_RE.test(t)) return true;\n if (CLAUDE_STATUS_BAR_RE.test(t)) return true;\n if (QUEUED_MSG_RE.test(t)) return true;\n if (CURSOR_ARTIFACT_RE.test(t)) return true;\n // cursor-agent patterns\n if (CURSOR_GENERATING_RE.test(t)) return true;\n if (CURSOR_FOOTER_RE.test(t)) return true;\n if (CURSOR_STATUS_RE.test(t)) return true;\n if (CURSOR_MODEL_RE.test(t)) return true;\n if (BOX_DRAWING_RE.test(t) && t.length > 1) return true;\n // Claude Code stop hook / session-end patterns\n if (STOP_HOOK_RE.test(t)) return true;\n if (WORKED_SUMMARY_RE.test(t)) return true;\n if (CLAUDE_BANNER_RE.test(t) && t.length > 1) return true;\n if (CLAUDE_BANNER_INFO_RE.test(t)) return true;\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Completion detection result\n// ---------------------------------------------------------------------------\n\ninterface CompletionResult {\n output: string;\n timedOut: boolean;\n timeoutType?: 'wall-clock' | 'idle';\n /** Whether the agent was actively producing output when timeout fired */\n wasActiveAtTimeout?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Session tracking\n// ---------------------------------------------------------------------------\n\ninterface PtySessionInfo {\n sessionId: string;\n agentMode: string;\n /** Current interactive mode (e.g. 'bypass', 'plan', 'accept-edits'). Tracked\n * so that mode-cycle keys are only sent when the mode actually needs to change. */\n currentMode: string;\n /** The mode this session was launched with via CLI args (undefined = agent default) */\n startedWithMode?: string;\n}\n\n// ---------------------------------------------------------------------------\n// PtyRunner\n// ---------------------------------------------------------------------------\n\n/**\n * AIRunner implementation that drives persistent interactive AI agent\n * terminal sessions (PTY) per issue.\n *\n * Supports multiple agent types (claude-internal, codebuddy, cursor-agent).\n * Each phase can use a different agent; sessions are reused when the agent\n * doesn't change between consecutive phases, and recreated when it does.\n */\nexport class PtyRunner implements AIRunner {\n /** workDir → active session info (sessionId + current agent type) */\n private sessions = new Map<string, PtySessionInfo>();\n\n /** Sessions that were forcefully killed (via killByWorkDir/killAll). Checked by\n * detectCompletion's onExit handler to report failure instead of success. */\n private killedSessions = new Set<string>();\n\n constructor(\n private readonly nvmNodeVersion: string,\n private readonly terminalManager: TerminalManager,\n private readonly defaultAgentMode: string,\n private readonly phaseAgentMap: Record<string, string>,\n private readonly globalModel?: string,\n private readonly idleDetectMs: number = 30_000,\n ) {}\n\n // ---- AIRunner interface ---------------------------------------------------\n\n async run(options: RunOptions): Promise<RunResult> {\n if (isShuttingDown()) {\n logger.warn('PtyRunner skipped — service is shutting down');\n return { success: false, output: 'Service shutting down', exitCode: null };\n }\n\n const { prompt, workDir, timeoutMs, onStreamEvent, phaseName } = options;\n const agentMode = this.resolveAgentForPhase(phaseName);\n const startMode = options.mode; // 'plan' | undefined currently; extensible for future modes\n const continueSession = options.continueSession ?? false;\n\n logger.info('PtyRunner.run()', { workDir, timeoutMs, phaseName, agentMode, continueSession });\n\n // 1. Ensure persistent PTY session (creates or reuses based on agent match)\n const { sessionId, isNew } = this.ensureSession(workDir, agentMode, startMode);\n\n // 2. Wait for Claude Code to be ready before sending commands.\n // New sessions need time to initialize (banner, config loading, etc.).\n if (isNew) {\n logger.info('Waiting for AI agent prompt (new session)', { sessionId, phaseName });\n // Custom agents (e.g. claude-internal) may show banners/notices before\n // entering interactive mode. Allow up to 5 min for first-time startup.\n await this.waitForPrompt(sessionId, 300_000);\n } else if (continueSession) {\n // Continue-session mode: the agent may still be working from a previous\n // timeout. Use a longer wait (30s) to detect if it has settled to idle.\n logger.info('Waiting for AI agent prompt (continue-session)', { sessionId, phaseName });\n await this.waitForPrompt(sessionId, 30_000);\n } else {\n // Reused session may have been interrupted (Ctrl+C) by retryFromPhase.\n // Brief wait for the agent to settle back to idle prompt.\n logger.info('Waiting for AI agent prompt (reused session)', { sessionId, phaseName });\n await this.waitForPrompt(sessionId, 10_000);\n }\n\n // 2.5 Native plan mode: two-phase execution\n // Phase 1: switch to plan mode → execute plan prompt (no artifact gate)\n // Phase 2: switch to bypass → commit plan to disk file (with artifact gate)\n if (startMode === 'plan' && this.shouldUseNativePlan(agentMode)) {\n return this.runNativePlanMode(sessionId, isNew, options, agentMode, workDir);\n }\n\n // Continue-session path: skip /clear and new prompt, just wait for the\n // agent to finish whatever it was doing before the timeout.\n if (continueSession && !isNew) {\n logger.info('Continue-session mode: attaching detectCompletion (no /clear)', { sessionId });\n const result = await this.detectCompletion(sessionId, options, onStreamEvent);\n logger.info('PtyRunner continue-session completed', {\n workDir, agentMode, phaseName,\n timedOut: result.timedOut,\n outputLength: result.output.length,\n });\n return this.buildRunResult(result, sessionId);\n }\n\n // 2.5 Toggle interactive mode if needed (Shift+Tab fallback for reused sessions)\n await this.ensurePlanMode(sessionId, agentMode, startMode === 'plan', workDir);\n\n // 3. Send /clear to reset context.\n // /clear only resets conversation history — the ❯ prompt stays on screen\n // and is NOT re-emitted by the PTY (TUI sends delta updates only).\n // A brief delay is sufficient for the command to process.\n await this.writeCommand(sessionId, '/clear', agentMode);\n await new Promise(resolve => setTimeout(resolve, 2_000));\n\n // 4. Write prompt to file\n const promptFile = this.writePromptFile(workDir, prompt);\n\n // 5. Send instruction to read the prompt file\n const instruction = `Please read and follow all instructions in ${promptFile}`;\n await this.writeCommand(sessionId, instruction, agentMode);\n\n // 6. Wait for completion\n const result = await this.detectCompletion(sessionId, options, onStreamEvent);\n\n logger.info('PtyRunner phase completed', {\n workDir,\n agentMode,\n phaseName,\n timedOut: result.timedOut,\n outputLength: result.output.length,\n });\n\n return this.buildRunResult(result, sessionId);\n }\n\n killAll(): void {\n for (const [, info] of this.sessions) {\n this.killedSessions.add(info.sessionId);\n this.terminalManager.destroy(info.sessionId);\n }\n this.sessions.clear();\n logger.info('PtyRunner: all managed sessions destroyed');\n }\n\n killByWorkDir(targetWorkDir: string): number {\n const info = this.sessions.get(targetWorkDir);\n if (!info) return 0;\n this.killedSessions.add(info.sessionId);\n this.terminalManager.destroy(info.sessionId);\n this.sessions.delete(targetWorkDir);\n return 1;\n }\n\n interruptByWorkDir(targetWorkDir: string): boolean {\n const info = this.sessions.get(targetWorkDir);\n if (!info) return false;\n\n const session = this.terminalManager.get(info.sessionId);\n if (!session) {\n // Session died externally — clean up tracking\n this.sessions.delete(targetWorkDir);\n return false;\n }\n\n // Send Ctrl+C to interrupt any running task without destroying the session\n this.terminalManager.write(info.sessionId, '\\x03');\n logger.info('Interrupted PTY session for retry', {\n workDir: targetWorkDir,\n sessionId: info.sessionId,\n });\n return true;\n }\n\n // ---- Agent resolution ----------------------------------------------------\n\n /** Get the Enter key sequence for the given agent mode */\n private getEnterKey(agentMode: string): string {\n const profile = getPtyProfile(agentMode);\n return profile?.enterKey ?? '\\r';\n }\n\n /**\n * Write a command to the PTY and press Enter.\n *\n * Some agents (codebuddy) use input coalescing in their TUI: when text and\n * Enter arrive in the same PTY write, the Enter is treated as a newline\n * character (paste) instead of triggering message submission. For these\n * agents (PtyProfile.separateEnter=true), text and Enter are sent in\n * separate writes with a 150ms gap so the Enter key is recognized as\n * a standalone key press.\n */\n private async writeCommand(sessionId: string, text: string, agentMode: string): Promise<void> {\n const enterKey = this.getEnterKey(agentMode);\n const profile = getPtyProfile(agentMode);\n\n if (profile?.separateEnter) {\n this.terminalManager.write(sessionId, text);\n await new Promise(resolve => setTimeout(resolve, 150));\n this.terminalManager.write(sessionId, enterKey);\n } else {\n this.terminalManager.write(sessionId, text + enterKey);\n }\n }\n\n /** Resolve agent mode for a given phase (fallback to default) */\n private resolveAgentForPhase(phaseName?: string): string {\n if (phaseName && this.phaseAgentMap[phaseName]) {\n return this.phaseAgentMap[phaseName];\n }\n return this.defaultAgentMode;\n }\n\n /** Look up PtyProfile from registry + resolve agent-specific binary and model */\n private resolveProfileAndModel(agentMode: string): {\n profile: PtyProfile;\n model: string | undefined;\n binary: string;\n } {\n const profile = getPtyProfile(agentMode);\n if (!profile) {\n throw new Error(\n `Agent \"${agentMode}\" has no PtyProfile — not supported in PTY mode. ` +\n `Compatible agents: claude-internal, codebuddy, cursor-agent`,\n );\n }\n const entry = getRegistryEntry(agentMode);\n const binary = (entry && process.env[entry.binaryEnvKey]) || entry?.defaultBinary || agentMode;\n const model = this.globalModel\n ? resolveModelForRunner(agentMode, this.globalModel)\n : undefined;\n return { profile, model, binary };\n }\n\n // ---- Session management ---------------------------------------------------\n\n private ensureSession(workDir: string, agentMode: string, startMode?: string): { sessionId: string; isNew: boolean } {\n const existing = this.sessions.get(workDir);\n\n // Agent switched → destroy old session\n if (existing && existing.agentMode !== agentMode) {\n logger.info('Agent switched, destroying old PTY session', {\n workDir,\n oldAgent: existing.agentMode,\n newAgent: agentMode,\n sessionId: existing.sessionId,\n });\n this.terminalManager.destroy(existing.sessionId);\n this.sessions.delete(workDir);\n }\n\n // Same agent → reuse if alive\n if (existing && existing.agentMode === agentMode) {\n if (this.terminalManager.get(existing.sessionId)) {\n logger.info('Reusing existing PTY session (same agent)', {\n workDir,\n agentMode,\n sessionId: existing.sessionId,\n });\n return { sessionId: existing.sessionId, isNew: false };\n }\n // Session died externally, clean up tracking\n this.sessions.delete(workDir);\n }\n\n // Check for orphaned sessions (e.g. from previous run or WebSocket)\n // Cannot safely reuse — we don't know what agent spawned it\n const orphan = this.terminalManager.findByWorkDir(workDir);\n if (orphan) {\n logger.info('Destroying orphaned PTY session on workDir', {\n workDir,\n sessionId: orphan.id,\n });\n this.terminalManager.destroy(orphan.id);\n }\n\n // Create new managed session with the resolved agent's binary and args\n const { profile, model, binary } = this.resolveProfileAndModel(agentMode);\n const args = profile.buildPtyArgs({ model, startMode });\n const issueIid = extractIidFromPath(workDir);\n\n const info = this.terminalManager.create({\n workDir,\n issueIid,\n command: binary,\n args,\n managed: true,\n });\n\n this.sessions.set(workDir, {\n sessionId: info.id,\n agentMode,\n // Always set initial mode to defaultModeName — CLI args always use bypass\n // (startMode is applied at runtime via Shift+Tab in ensurePlanMode).\n currentMode: profile.defaultModeName ?? 'bypass',\n startedWithMode: startMode,\n });\n logger.info('Created new PTY session', {\n workDir,\n agentMode,\n binary,\n args,\n sessionId: info.id,\n pid: info.pid,\n issueIid,\n });\n return { sessionId: info.id, isNew: true };\n }\n\n // ---- Prompt delivery ------------------------------------------------------\n\n /**\n * Wait for the AI agent to show its idle prompt (❯ or >), indicating\n * it is ready to accept input. Used before sending /clear and instructions\n * to prevent commands from arriving before the agent is initialized.\n */\n private waitForPrompt(sessionId: string, timeoutMs: number = 60_000): Promise<void> {\n return new Promise<void>((resolve) => {\n let promptSeen = false;\n let trustDialogHandled = false;\n let stabilityTimer: ReturnType<typeof setTimeout> | undefined;\n // After a prompt is first detected, wait this long with no new output\n // to confirm the agent is truly idle (not still initialising — e.g.\n // MCP auth, IDE extension install can arrive after the ❯ prompt renders).\n const STABILITY_MS = 3_000;\n\n // Secondary: TUI status bar (e.g. \"bypass permissions on (shift+tab to cycle)\")\n // is a strong signal that the input area is fully rendered. The ❯ prompt\n // exists in the same data frame but isIdlePrompt may fail on full-screen\n // renders where ❯ is embedded among other TUI content.\n const TUI_READY_RE = /bypass\\s*permissions|shift\\+?\\s*tab\\s*to\\s*cycle/i;\n let tuiReady = false;\n\n // Fallback: Claude Code TUI may render the ❯ prompt via cursor-positioning\n // sequences that don't produce a discrete onData event. When the banner has\n // been shown and the PTY goes silent, treat the agent as ready.\n let bannerSeen = false;\n let silenceTimer: ReturnType<typeof setTimeout> | undefined;\n const SILENCE_READY_MS = 8_000;\n\n const timer = setTimeout(() => {\n if (stabilityTimer) clearTimeout(stabilityTimer);\n if (silenceTimer) clearTimeout(silenceTimer);\n subscription.dispose();\n logger.warn('Timed out waiting for AI agent prompt', { sessionId, timeoutMs });\n // Resolve instead of reject — best-effort, let the run proceed\n resolve();\n }, timeoutMs);\n\n const done = (reason: string) => {\n clearTimeout(timer);\n if (stabilityTimer) clearTimeout(stabilityTimer);\n if (silenceTimer) clearTimeout(silenceTimer);\n subscription.dispose();\n logger.info('AI agent prompt detected', { sessionId, reason });\n resolve();\n };\n\n const resetSilenceTimer = () => {\n if (!bannerSeen) return;\n if (silenceTimer) clearTimeout(silenceTimer);\n silenceTimer = setTimeout(() => {\n if (promptSeen) return; // Primary detection already succeeded\n logger.info('Banner shown and PTY silent — treating agent as ready', { sessionId });\n done('silence-after-banner');\n }, SILENCE_READY_MS);\n };\n\n const subscription = this.terminalManager.onData(sessionId, (data: string) => {\n const stripped = stripAnsi(data);\n\n // Classify TUI noise (consistent with detectCompletion)\n const nonEmptyLines = stripped.split('\\n').filter((l) => l.trim());\n const isNoise = nonEmptyLines.length === 0 || nonEmptyLines.every((l) => isTuiNoise(l));\n\n // Detect Claude Code banner to enable silence-based readiness fallback.\n if (!bannerSeen && /Claude\\s*Code/i.test(stripped)) {\n bannerSeen = true;\n }\n\n // Reset silence timer on every data event (noise or not) — we want to\n // detect true silence (no PTY output at all), not filtered silence.\n resetSilenceTimer();\n\n // Auto-confirm workspace trust dialog in new worktree directories.\n // TUI cursor positioning removes spaces after ANSI stripping, so use\n // \\s* between words to match both \"trust this folder\" and \"trustthisfolder\".\n if (!trustDialogHandled && TRUST_DIALOG_RE.test(stripped)) {\n trustDialogHandled = true;\n logger.info('Trust dialog detected, auto-confirming', { sessionId });\n setTimeout(() => this.terminalManager.write(sessionId, '\\r'), 500);\n }\n\n // Auto-approve permission/confirmation dialogs that appear during\n // startup or between phases (e.g. leftover prompt from a previous task).\n if (PERMISSION_DIALOG_RE.test(stripped)) {\n logger.info('Permission dialog detected in waitForPrompt, auto-confirming', { sessionId });\n setTimeout(() => this.terminalManager.write(sessionId, '\\r'), 500);\n }\n\n if (isIdlePrompt(stripped)) {\n promptSeen = true;\n }\n\n if (!tuiReady && TUI_READY_RE.test(stripped)) {\n tuiReady = true;\n }\n\n // Once the agent is considered ready (idle prompt detected OR TUI status\n // bar rendered), (re)start a stability timer.\n // CRITICAL: TUI noise (spinners, status bar ~100ms updates) must NOT\n // reset the timer — otherwise it can never complete. Only substantive\n // output (e.g. MCP auth dialog, extension install notice) resets it.\n if (promptSeen || tuiReady) {\n if (stabilityTimer && isNoise) {\n // Already waiting — noise-only frame, let the timer continue\n } else {\n if (stabilityTimer) clearTimeout(stabilityTimer);\n const reason = promptSeen ? 'idle-prompt-stable' : 'status-bar-ready';\n stabilityTimer = setTimeout(() => done(reason), STABILITY_MS);\n }\n }\n });\n });\n }\n\n // ---- Interactive mode switching --------------------------------------------\n\n /**\n * Switch the PTY session to (or away from) plan mode by pressing the mode\n * cycle key (Shift+Tab) until the target mode is detected in the output.\n *\n * This is a no-op when the agent has no modeCycleKey configured, or the\n * session is already in the desired mode.\n */\n private async ensurePlanMode(\n sessionId: string,\n agentMode: string,\n wantPlan: boolean,\n workDir: string,\n ): Promise<void> {\n const profile = getPtyProfile(agentMode);\n if (!profile?.modeCycleKey || !profile.detectMode || !profile.planModeName) return;\n\n const session = this.sessions.get(workDir);\n if (!session) return;\n\n const targetMode = wantPlan ? profile.planModeName : (profile.defaultModeName ?? 'bypass');\n if (session.currentMode === targetMode) {\n logger.info('PTY already in target mode', { sessionId, targetMode });\n return;\n }\n\n const MAX_ATTEMPTS = 5;\n for (let i = 0; i < MAX_ATTEMPTS; i++) {\n // Start collecting BEFORE sending the key — the mode indicator is emitted\n // within milliseconds of the key press and would be missed if we waited.\n const outputPromise = this.collectRecentOutput(sessionId, 3_000);\n this.terminalManager.write(sessionId, profile.modeCycleKey);\n const recentOutput = await outputPromise;\n\n const detected = profile.detectMode(recentOutput);\n\n if (detected) {\n session.currentMode = detected;\n if (detected === targetMode) {\n logger.info('PTY mode switched', {\n sessionId, agentMode, targetMode, attempts: i + 1,\n });\n return;\n }\n }\n }\n\n logger.warn('Failed to switch PTY mode after max attempts', {\n sessionId, agentMode, targetMode, current: session.currentMode,\n });\n }\n\n /**\n * Briefly subscribe to PTY output and collect all data emitted during a\n * window. Used by ensurePlanMode to read the mode indicator after pressing\n * the cycle key.\n */\n private collectRecentOutput(sessionId: string, durationMs: number): Promise<string> {\n return new Promise(resolve => {\n const chunks: string[] = [];\n const subscription = this.terminalManager.onData(sessionId, (data: string) => {\n chunks.push(stripAnsi(data));\n });\n setTimeout(() => {\n subscription.dispose();\n resolve(chunks.join(''));\n }, durationMs);\n });\n }\n\n // ---- Native plan mode (two-phase execution) --------------------------------\n\n /**\n * Whether the agent supports the two-phase native plan strategy:\n * bypass start → Shift+Tab to plan → execute → Shift+Tab to bypass → commit file.\n *\n * Conditions: runner does NOT natively handle plan mode on its own (nativePlanMode=false)\n * AND the PTY profile supports interactive mode switching (modeCycleKey + planModeName).\n */\n private shouldUseNativePlan(agentMode: string): boolean {\n const caps = getRunnerCapabilities(agentMode);\n const profile = getPtyProfile(agentMode);\n return !caps?.nativePlanMode && !!profile?.modeCycleKey && !!profile?.planModeName;\n }\n\n /**\n * Two-phase plan execution:\n * Phase 1 — Switch to plan mode, send plan prompt, wait for idle (no artifact gate).\n * Phase 2 — Deterministic copy: resolve plan file from CLI-native storage\n * (~/.claude/plans/) and copy it to the expected artifact path.\n *\n * Phase 2 was previously prompt-driven (\"ask agent to write file\"), which was\n * unreliable because /clear wiped the agent's context. Now it uses PlanFileResolver\n * to find the plan file by snapshot diffing + content validation.\n */\n private async runNativePlanMode(\n sessionId: string,\n isNew: boolean,\n options: RunOptions,\n agentMode: string,\n workDir: string,\n ): Promise<RunResult> {\n const continueSession = options.continueSession && !isNew;\n\n // ── Phase 2 prep: snapshot plan directory before Phase 1 starts ──\n const resolver = PlanFileResolver.forRunner(agentMode);\n resolver.takeBeforeSnapshot();\n\n const issueIid = extractIidFromPath(workDir);\n const contentHint = issueIid\n ? PlanFileResolver.buildContentHint(issueIid)\n : undefined;\n\n // ── Phase 1: native plan mode ──\n if (continueSession) {\n logger.info('Native plan mode: continue-session (no /clear, no prompt)', { sessionId });\n } else {\n logger.info('Native plan mode: switching to plan', { sessionId, agentMode });\n await this.ensurePlanMode(sessionId, agentMode, true, workDir);\n\n if (!isNew) {\n await this.writeCommand(sessionId, '/clear', agentMode);\n await new Promise(resolve => setTimeout(resolve, 2_000));\n }\n\n const promptFile = this.writePromptFile(workDir, options.prompt);\n await this.writeCommand(\n sessionId,\n `Please read and follow all instructions in ${promptFile}`,\n agentMode,\n );\n }\n\n // Primary signal: plan confirmation dialog (\"Claude has written up a plan\n // and is ready to execute\"). Secondary: artifact file check as debounce fallback.\n const planArtifactCheck = () => resolver.hasNewOrModifiedFiles();\n\n const planResult = await this.detectCompletion(sessionId, {\n ...options,\n completionSignal: PLAN_CONFIRM_RE,\n artifactCheck: planArtifactCheck,\n }, options.onStreamEvent, continueSession);\n\n if (planResult.timedOut) {\n logger.warn('Native plan mode: plan phase timed out', {\n sessionId, wasActive: planResult.wasActiveAtTimeout,\n });\n return this.buildRunResult(planResult, sessionId);\n }\n\n // ── Phase 2: deterministic plan file copy ──\n logger.info('Native plan mode: resolving plan file from CLI storage', { sessionId });\n\n const resolved = resolver.resolve(contentHint);\n\n if (!resolved) {\n logger.error('Native plan mode: no plan file found in CLI storage', { sessionId, workDir });\n return {\n success: false,\n output: planResult.output,\n errorMessage: 'Plan 阶段完成但未在 CLI 计划目录中找到计划文件',\n sessionId,\n exitCode: null,\n };\n }\n\n // Copy resolved plan file to all expected artifact paths\n const artifactPaths = options.artifactPaths ?? [];\n if (artifactPaths.length > 0) {\n for (const targetPath of artifactPaths) {\n const targetDir = path.dirname(targetPath);\n if (!fs.existsSync(targetDir)) {\n fs.mkdirSync(targetDir, { recursive: true });\n }\n fs.writeFileSync(targetPath, resolved.content, 'utf-8');\n logger.info('Plan file copied to artifact path', {\n source: path.basename(resolved.sourcePath),\n target: targetPath,\n size: resolved.content.length,\n });\n }\n } else {\n logger.warn('Native plan mode: no artifactPaths specified, plan file not copied', {\n sessionId,\n resolvedFile: resolved.sourcePath,\n });\n }\n\n logger.info('Native plan mode completed (deterministic copy)', {\n sessionId,\n planSource: path.basename(resolved.sourcePath),\n artifactsCopied: artifactPaths.length,\n });\n\n return this.buildRunResult(planResult, sessionId);\n }\n\n /** Map detectCompletion result to RunResult. */\n private buildRunResult(\n detectResult: CompletionResult,\n sessionId: string,\n ): RunResult {\n return {\n success: !detectResult.timedOut,\n output: detectResult.output,\n errorMessage: detectResult.timedOut\n ? (detectResult.timeoutType === 'idle' ? 'AI 长时间无响应,已超时终止' : '执行超时')\n : undefined,\n sessionId,\n exitCode: detectResult.timedOut ? null : 0,\n timeoutType: detectResult.timeoutType,\n wasActiveAtTimeout: detectResult.wasActiveAtTimeout,\n };\n }\n\n private writePromptFile(workDir: string, prompt: string): string {\n // Write to .claude-plan/.phase-prompt.md (leading dot = not treated as artifact)\n const dir = path.join(workDir, '.claude-plan');\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n const relPath = '.claude-plan/.phase-prompt.md';\n const absPath = path.join(workDir, relPath);\n fs.writeFileSync(absPath, prompt, 'utf-8');\n return relPath;\n }\n\n // ---- Completion detection -------------------------------------------------\n\n private detectCompletion(\n sessionId: string,\n options: RunOptions,\n onStreamEvent?: (event: StreamEvent) => void,\n skipEchoDetection?: boolean,\n ): Promise<CompletionResult> {\n return new Promise<CompletionResult>((resolve) => {\n const outputLines: string[] = [];\n let lastOutputTime = Date.now();\n let hasSubstantiveOutput = false;\n let echoConsumed = skipEchoDetection ?? false;\n let resolved = false;\n let debounceTimer: ReturnType<typeof setTimeout> | undefined;\n const DIALOG_BUFFER_MAX = 40;\n const dialogBuffer: string[] = [];\n let dialogHandled = false;\n // Low-confidence dialog quiesce: wait for silence before forwarding.\n const DIALOG_QUIESCE_MS = 2_500;\n let dialogQuiesceTimer: ReturnType<typeof setTimeout> | undefined;\n let pendingDialogParsed: { question: string; options: DialogOption[] } | null = null;\n // Primary completion signal: isIdlePrompt() detects Claude Code's ❯ prompt.\n // The idle check below is a safety-net for truly stuck sessions — uses the\n // AI-level idle timeout (default 20 min) instead of the short PTY_IDLE_DETECT_MS.\n const idleTimeoutMs = options.idleTimeoutMs ?? 600_000;\n const timeoutMs = options.timeoutMs;\n\n // Progressive wall-clock timeout: if the agent is still actively producing\n // output when the wall-clock timer fires, extend the deadline instead of\n // immediately failing. Prevents the timeout→retry→/clear→restart death loop.\n const GRACE_WINDOW_MS = options.timeoutGraceMs ?? 60_000;\n const EXTENSION_MS = options.timeoutExtensionMs ?? 600_000;\n const MAX_EXTENSIONS = options.timeoutMaxExtensions ?? 3;\n let extensions = 0;\n\n const finish = (result: CompletionResult) => {\n if (resolved) return;\n resolved = true;\n cleanup();\n resolve(result);\n };\n\n // Wall-clock timeout with progressive extension\n const scheduleWallTimer = (delayMs: number): ReturnType<typeof setTimeout> => {\n return setTimeout(() => {\n const recentMs = Date.now() - lastOutputTime;\n const isActive = hasSubstantiveOutput && recentMs < GRACE_WINDOW_MS;\n if (isActive && extensions < MAX_EXTENSIONS) {\n extensions++;\n logger.info('Wall-clock timeout extended (agent still active)', {\n sessionId, extensions, maxExtensions: MAX_EXTENSIONS,\n lastOutputAgoMs: recentMs,\n });\n wallTimer = scheduleWallTimer(EXTENSION_MS);\n return;\n }\n finish({\n output: outputLines.join(''),\n timedOut: true,\n timeoutType: 'wall-clock',\n wasActiveAtTimeout: isActive,\n });\n }, delayMs);\n };\n let wallTimer = scheduleWallTimer(timeoutMs);\n\n // Idle safety-net: if no output for a long time, treat as idle timeout (failure).\n // The primary \"task done\" signal is isIdlePrompt() below, not this check.\n const idleCheck = setInterval(() => {\n if (!hasSubstantiveOutput) return;\n if (Date.now() - lastOutputTime >= idleTimeoutMs) {\n finish({\n output: outputLines.join(''),\n timedOut: true,\n timeoutType: 'idle',\n wasActiveAtTimeout: false,\n });\n }\n }, 5_000);\n\n // Subscribe to PTY output\n const subscription = this.terminalManager.onData(sessionId, (data: string) => {\n if (resolved) return;\n\n const stripped = stripAnsi(data);\n\n // Classify TUI noise (spinners, status bar, separators, etc.)\n const nonEmptyLines = stripped.split('\\n').filter((l) => l.trim());\n const isNoise = nonEmptyLines.length === 0 || nonEmptyLines.every((l) => isTuiNoise(l));\n const isIdle = isIdlePrompt(stripped);\n\n // Detect \"mixed frames\": the TUI co-renders ❯ with active work content\n // (tool results, file listings, thinking indicators) in a single data\n // chunk. These are NOT truly idle — the agent is still working.\n const isMixedFrame = isIdle && containsActiveWork(stripped);\n\n // Update last-activity timestamp for real content.\n // - Pure noise (spinners) → no update (would prevent debounce)\n // - Pure idle (❯ only) → no update (debounce needs to expire)\n // - Mixed frame (❯ + work content) → DO update (agent is still active)\n // - Normal content → update\n const hasRealContent = !isNoise && stripped.trim().length > 0;\n if (hasRealContent && (!isIdle || isMixedFrame)) {\n lastOutputTime = Date.now();\n // Cancel low-confidence dialog quiesce — new output means the agent\n // is still working and the detected numbered list was not a real dialog.\n if (dialogQuiesceTimer) {\n clearTimeout(dialogQuiesceTimer);\n dialogQuiesceTimer = undefined;\n pendingDialogParsed = null;\n logger.info('Dialog quiesce cancelled — new substantive output arrived', { sessionId });\n }\n }\n\n // Skip the echo of our instruction\n if (!echoConsumed && stripped.includes('.phase-prompt.md')) {\n echoConsumed = true;\n return;\n }\n\n // Queued message indicator — the agent has pending input but is still\n // processing. This is NOT an idle state and must not trigger completion.\n if (QUEUED_MSG_RE.test(stripped)) {\n return;\n }\n\n // Definitive completion signal (e.g. plan confirmation dialog).\n // Checked BEFORE PERMISSION_DIALOG_RE — the plan confirm dialog\n // contains \"❯ 1. Yes\" which would otherwise trigger auto-confirm\n // and start code execution prematurely.\n if (options.completionSignal && hasSubstantiveOutput && options.completionSignal.test(stripped)) {\n logger.info('Completion signal detected', { sessionId });\n finish({ output: outputLines.join(''), timedOut: false });\n return;\n }\n\n // Claude Code session-end summary (e.g. \"✻Worked for 9m39s\" / \"✻Cogitated for 3m37s\").\n // This is a strong built-in completion signal — the agent has finished\n // and Claude Code is displaying its session summary before returning\n // to idle. Finish immediately instead of waiting for the debounce,\n // which can be blocked by subsequent stop-hook / banner output.\n //\n // Gate on artifactCheck: the broadened regex may also match mid-session\n // thinking summaries (e.g. after the first tool call). The artifact\n // gate prevents premature completion when the phase output file has\n // not been written yet.\n if (hasSubstantiveOutput && WORKED_SUMMARY_RE.test(stripped)) {\n const artifactReady = options.artifactCheck ? options.artifactCheck() : true;\n if (artifactReady) {\n logger.info('Session-end summary detected, finishing', { sessionId });\n finish({ output: outputLines.join(''), timedOut: false });\n return;\n }\n logger.info('Session summary detected but artifacts not ready, continuing', { sessionId });\n }\n\n // Auto-approve permission/confirmation dialogs (plan mode safety prompts).\n // Must be checked BEFORE isIdlePrompt — the ❯ in \"❯ 1. Yes\" would\n // otherwise trigger idle detection and start a premature debounce.\n if (PERMISSION_DIALOG_RE.test(stripped)) {\n logger.info('Permission dialog detected, auto-confirming', { sessionId });\n setTimeout(() => {\n if (!resolved) this.terminalManager.write(sessionId, '\\r');\n }, 500);\n return; // Don't count dialog frames as substantive output or idle prompts\n }\n\n // Interactive dialog (brainstorming interview, etc.) — forward to\n // frontend for user decision via onInputRequired callback.\n // Uses confidence scoring to avoid false positives from agent numbered lists.\n if (options.onInputRequired && !dialogHandled && isInteractiveDialog(stripped)) {\n const parsed = parseInteractiveDialog(stripped);\n if (parsed) {\n const confidence = getDialogConfidence(stripped);\n if (confidence === 'high') {\n dialogHandled = true;\n dialogBuffer.length = 0;\n if (dialogQuiesceTimer) { clearTimeout(dialogQuiesceTimer); dialogQuiesceTimer = undefined; }\n logger.info('Interactive dialog detected (high confidence), forwarding to handler', {\n sessionId, question: parsed.question.slice(0, 80),\n optionCount: parsed.options.length,\n });\n options.onInputRequired({\n type: 'interactive-dialog',\n content: parsed.question,\n options: parsed.options,\n }).then((response) => {\n dialogHandled = false;\n if (!resolved && response) {\n this.terminalManager.write(sessionId, response + '\\r');\n }\n }).catch((err) => {\n logger.warn('onInputRequired callback failed for interactive dialog', {\n error: (err as Error).message,\n });\n dialogHandled = false;\n });\n return;\n }\n // Low confidence: start quiesce timer — only forward if no new\n // substantive output arrives within the window.\n if (!dialogQuiesceTimer) {\n pendingDialogParsed = parsed;\n logger.info('Interactive dialog detected (low confidence), starting quiesce', {\n sessionId, question: parsed.question.slice(0, 80),\n optionCount: parsed.options.length,\n });\n dialogQuiesceTimer = setTimeout(() => {\n dialogQuiesceTimer = undefined;\n if (resolved || dialogHandled || !pendingDialogParsed) return;\n dialogHandled = true;\n dialogBuffer.length = 0;\n const dp = pendingDialogParsed;\n pendingDialogParsed = null;\n logger.info('Dialog quiesce elapsed — forwarding low-confidence dialog', {\n sessionId, question: dp.question.slice(0, 80),\n });\n options.onInputRequired!({\n type: 'interactive-dialog',\n content: dp.question,\n options: dp.options,\n }).then((response) => {\n dialogHandled = false;\n if (!resolved && response) {\n this.terminalManager.write(sessionId, response + '\\r');\n }\n }).catch((err) => {\n logger.warn('onInputRequired callback failed (quiesced dialog)', {\n error: (err as Error).message,\n });\n dialogHandled = false;\n });\n }, DIALOG_QUIESCE_MS);\n }\n return;\n }\n }\n\n // Substantive output must be non-noise (spinners don't count)\n if (echoConsumed && !hasSubstantiveOutput && !isNoise && stripped.trim().length > 0) {\n hasSubstantiveOutput = true;\n }\n\n outputLines.push(stripped);\n\n // Emit stream events for the dashboard (filter TUI noise)\n if (onStreamEvent) {\n for (const line of stripped.split('\\n').filter((l) => l.trim())) {\n if (!isTuiNoise(line)) {\n onStreamEvent({\n type: 'raw',\n content: line,\n timestamp: new Date().toISOString(),\n });\n }\n }\n }\n\n // Accumulate non-noise lines for cross-frame dialog detection.\n // The per-frame check above may miss dialogs split across PTY chunks.\n if (!dialogHandled && !isNoise) {\n for (const line of stripped.split('\\n').filter((l) => l.trim())) {\n if (!isTuiNoise(line)) {\n dialogBuffer.push(line);\n if (dialogBuffer.length > DIALOG_BUFFER_MAX) dialogBuffer.shift();\n }\n }\n }\n\n // Check for Claude Code idle prompt\n if (hasSubstantiveOutput && isIdlePrompt(stripped)) {\n // Mixed frame: ❯ co-rendered with active work (tool results, etc.)\n // This is NOT a genuine idle state — skip debounce entirely.\n if (isMixedFrame) {\n // Agent is still working — do not start debounce\n } else if (options.completionSignal) {\n // completionSignal is configured — idle prompt is NOT a valid\n // completion path. Only the signal itself (e.g. PLAN_CONFIRM_RE)\n // can trigger successful finish. Wall-clock and idle timeouts\n // still act as failure safety nets.\n logger.debug('Idle prompt ignored (waiting for completionSignal)', { sessionId });\n } else if (debounceTimer && isNoise) {\n // CRITICAL: If a debounce is already pending and this is a pure-noise\n // idle frame (TUI refresh with ❯), skip entirely. Otherwise the TUI's\n // continuous idle-prompt stream (every ~100ms) would clear+reschedule\n // the timer infinitely, preventing it from ever completing.\n } else {\n // Cross-frame dialog detection: the per-frame check may have missed\n // a dialog whose options arrived in separate PTY data chunks. Before\n // starting the idle debounce, check the accumulated buffer.\n if (options.onInputRequired && !dialogHandled && dialogBuffer.length >= 2) {\n const combined = dialogBuffer.join('\\n');\n if (isInteractiveDialog(combined)) {\n const parsed = parseInteractiveDialog(combined);\n if (parsed) {\n const bufConfidence = getDialogConfidence(combined);\n if (bufConfidence === 'high') {\n dialogHandled = true;\n dialogBuffer.length = 0;\n if (dialogQuiesceTimer) { clearTimeout(dialogQuiesceTimer); dialogQuiesceTimer = undefined; }\n logger.info('Interactive dialog detected via accumulated buffer (high confidence)', {\n sessionId, question: parsed.question.slice(0, 80),\n optionCount: parsed.options.length,\n });\n options.onInputRequired({\n type: 'interactive-dialog',\n content: parsed.question,\n options: parsed.options,\n }).then((response) => {\n dialogHandled = false;\n if (!resolved && response) {\n this.terminalManager.write(sessionId, response + '\\r');\n }\n }).catch((err) => {\n logger.warn('onInputRequired callback failed (accumulated)', {\n error: (err as Error).message,\n });\n dialogHandled = false;\n });\n return;\n }\n // Low confidence via buffer: since we're already at an idle\n // prompt, this is likely genuine — but still use quiesce to\n // prevent false positives from accumulated numbered output.\n if (!dialogQuiesceTimer) {\n pendingDialogParsed = parsed;\n logger.info('Dialog detected via buffer (low confidence), starting quiesce', {\n sessionId, question: parsed.question.slice(0, 80),\n });\n dialogQuiesceTimer = setTimeout(() => {\n dialogQuiesceTimer = undefined;\n if (resolved || dialogHandled || !pendingDialogParsed) return;\n dialogHandled = true;\n dialogBuffer.length = 0;\n const dp = pendingDialogParsed;\n pendingDialogParsed = null;\n logger.info('Buffer dialog quiesce elapsed — forwarding', { sessionId });\n options.onInputRequired!({\n type: 'interactive-dialog',\n content: dp.question,\n options: dp.options,\n }).then((response) => {\n dialogHandled = false;\n if (!resolved && response) {\n this.terminalManager.write(sessionId, response + '\\r');\n }\n }).catch((err) => {\n logger.warn('onInputRequired callback failed (buffer quiesced)', {\n error: (err as Error).message,\n });\n dialogHandled = false;\n });\n }, DIALOG_QUIESCE_MS);\n }\n }\n }\n }\n\n // Real idle prompt (first one, or after real content interrupted debounce).\n // Start or restart the debounce timer.\n if (debounceTimer) clearTimeout(debounceTimer);\n const scheduleDebounce = () => {\n debounceTimer = setTimeout(() => {\n if (resolved) return;\n const recentActivityMs = Date.now() - lastOutputTime;\n if (recentActivityMs < 5_000) {\n // Recent activity — retry rather than giving up\n scheduleDebounce();\n return;\n }\n\n // Artifact hard-gate: if artifacts are expected but not ready, keep waiting\n const artifactReady = options.artifactCheck ? options.artifactCheck() : true;\n if (!artifactReady) {\n logger.info('Idle prompt detected but artifacts not ready, continuing to wait', {\n sessionId,\n });\n scheduleDebounce();\n return;\n }\n\n finish({\n output: outputLines.join(''),\n timedOut: false,\n timeoutType: undefined,\n });\n }, 5_000);\n };\n scheduleDebounce();\n }\n }\n });\n\n // PTY exit handler — Claude process crashed, was killed, or exited\n // after a stop hook. Always clean up the session mapping to prevent\n // ensureSession from trying to reuse a dead PTY.\n this.terminalManager.onExit(sessionId, (exitCode: number) => {\n const wasKilled = this.killedSessions.delete(sessionId);\n\n // Always remove the dead session from tracking\n for (const [wd, info] of this.sessions) {\n if (info.sessionId === sessionId) {\n this.sessions.delete(wd);\n break;\n }\n }\n\n if (!resolved) {\n logger.warn('PTY process exited during phase', { sessionId, exitCode, wasKilled });\n finish({\n output: outputLines.join(''),\n timedOut: wasKilled,\n timeoutType: wasKilled ? 'wall-clock' : undefined,\n });\n } else {\n // detectCompletion already resolved (e.g. via idle debounce or\n // Worked-for signal). The PTY exited afterwards (stop hook exit).\n // Session mapping already cleaned above — no further action needed.\n logger.info('PTY exited after phase completion (post stop-hook)', { sessionId, exitCode });\n }\n });\n\n const cleanup = () => {\n clearTimeout(wallTimer);\n clearInterval(idleCheck);\n if (debounceTimer) clearTimeout(debounceTimer);\n if (dialogQuiesceTimer) clearTimeout(dialogQuiesceTimer);\n subscription.dispose();\n };\n });\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { logger as rootLogger } from '../logger.js';\n\nconst logger = rootLogger.child('PlanFileResolver');\n\nexport interface ResolvedPlanFile {\n /** Absolute path to the plan file in the CLI plans directory */\n sourcePath: string;\n /** File content */\n content: string;\n}\n\n/** Known CLI plan directory paths by runner mode */\nconst PLAN_DIRS: Record<string, string> = {\n 'claude-internal': path.join(os.homedir(), '.claude-internal', 'plans'),\n 'codebuddy': path.join(os.homedir(), '.codebuddy', 'plans'),\n};\n\n/**\n * Resolves the correct plan file from CLI-native plan storage (e.g. ~/.claude/plans/)\n * after a plan-mode execution. Uses snapshot diffing (before/after file lists) with\n * optional content-based validation to handle concurrent plan generation.\n */\nexport class PlanFileResolver {\n private readonly plansDir: string;\n private beforeFiles: Map<string, number> = new Map();\n\n constructor(plansDir?: string) {\n this.plansDir = plansDir ?? path.join(os.homedir(), '.claude-internal', 'plans');\n }\n\n /** Create a resolver for a specific runner mode (e.g. 'claude-internal', 'codebuddy'). */\n static forRunner(agentMode: string): PlanFileResolver {\n const dir = PLAN_DIRS[agentMode];\n return new PlanFileResolver(dir);\n }\n\n /** Take a snapshot of existing plan files before the plan phase starts. */\n takeBeforeSnapshot(): void {\n this.beforeFiles = this.listFiles();\n logger.info('Plan file snapshot taken', {\n dir: this.plansDir,\n fileCount: this.beforeFiles.size,\n });\n }\n\n /**\n * Check if any new or modified plan files exist since the before-snapshot.\n * Used as an artifact gate by detectCompletion to prevent premature\n * completion when the agent is still exploring/thinking.\n */\n hasNewOrModifiedFiles(): boolean {\n const afterFiles = this.listFiles();\n for (const [filePath, mtime] of afterFiles) {\n const beforeMtime = this.beforeFiles.get(filePath);\n if (beforeMtime === undefined || mtime > beforeMtime) return true;\n }\n return false;\n }\n\n /**\n * After plan phase completes, find the newly created plan file.\n *\n * Strategy:\n * 1. Diff against beforeSnapshot to find new files\n * 2. Sort candidates by mtime (newest first)\n * 3. If contentHint provided, prefer the file whose content matches\n * 4. Return the best candidate\n *\n * @param contentHint - Optional string (e.g. issue IID or title) to validate content\n */\n resolve(contentHint?: string): ResolvedPlanFile | null {\n if (!fs.existsSync(this.plansDir)) {\n logger.warn('Plans directory does not exist', { dir: this.plansDir });\n return null;\n }\n\n const afterFiles = this.listFiles();\n const candidates = this.findNewFiles(afterFiles);\n\n if (candidates.length === 0) {\n logger.warn('No new plan files found after plan phase', {\n dir: this.plansDir,\n beforeCount: this.beforeFiles.size,\n afterCount: afterFiles.size,\n });\n return this.fallbackByMtime(afterFiles, contentHint);\n }\n\n if (candidates.length === 1) {\n return this.readCandidate(candidates[0].path, candidates[0].mtime);\n }\n\n // Multiple new files — try content matching first\n if (contentHint) {\n const matched = this.matchByContent(candidates, contentHint);\n if (matched) return matched;\n }\n\n // Fall back to most recent\n candidates.sort((a, b) => b.mtime - a.mtime);\n logger.info('Multiple new plan files found, using most recent', {\n count: candidates.length,\n selected: path.basename(candidates[0].path),\n });\n return this.readCandidate(candidates[0].path, candidates[0].mtime);\n }\n\n /**\n * Build a content hint string from issue metadata for content-based matching.\n */\n static buildContentHint(issueIid: number, issueTitle?: string): string {\n const parts = [`#${issueIid}`, `${issueIid}`];\n if (issueTitle) parts.push(issueTitle);\n return parts.join('|');\n }\n\n private listFiles(): Map<string, number> {\n const result = new Map<string, number>();\n if (!fs.existsSync(this.plansDir)) return result;\n\n try {\n const entries = fs.readdirSync(this.plansDir);\n for (const entry of entries) {\n if (!entry.endsWith('.md')) continue;\n const fullPath = path.join(this.plansDir, entry);\n try {\n const stat = fs.statSync(fullPath);\n if (stat.isFile()) {\n result.set(fullPath, stat.mtimeMs);\n }\n } catch {\n // skip unreadable files\n }\n }\n } catch (err) {\n logger.warn('Failed to list plans directory', { dir: this.plansDir, err });\n }\n return result;\n }\n\n private findNewFiles(afterFiles: Map<string, number>): Array<{ path: string; mtime: number }> {\n const candidates: Array<{ path: string; mtime: number }> = [];\n for (const [filePath, mtime] of afterFiles) {\n if (!this.beforeFiles.has(filePath)) {\n candidates.push({ path: filePath, mtime });\n }\n }\n return candidates;\n }\n\n /**\n * Fallback: if no new files found (rare case — plan might have overwritten\n * an existing file), find the most recently modified file.\n */\n private fallbackByMtime(\n afterFiles: Map<string, number>,\n contentHint?: string,\n ): ResolvedPlanFile | null {\n if (afterFiles.size === 0) return null;\n\n const sorted = [...afterFiles.entries()].sort((a, b) => b[1] - a[1]);\n\n if (contentHint) {\n for (const [filePath] of sorted.slice(0, 5)) {\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n if (this.contentMatches(content, contentHint)) {\n logger.info('Fallback: found plan file by content match', {\n file: path.basename(filePath),\n });\n return { sourcePath: filePath, content };\n }\n } catch {\n continue;\n }\n }\n }\n\n const [newestPath, newestMtime] = sorted[0];\n logger.info('Fallback: using most recently modified plan file', {\n file: path.basename(newestPath),\n ageMs: Date.now() - newestMtime,\n });\n return this.readCandidate(newestPath, newestMtime);\n }\n\n private matchByContent(\n candidates: Array<{ path: string; mtime: number }>,\n contentHint: string,\n ): ResolvedPlanFile | null {\n for (const candidate of candidates) {\n try {\n const content = fs.readFileSync(candidate.path, 'utf-8');\n if (this.contentMatches(content, contentHint)) {\n logger.info('Plan file matched by content', {\n file: path.basename(candidate.path),\n hint: contentHint.slice(0, 50),\n });\n return { sourcePath: candidate.path, content };\n }\n } catch {\n continue;\n }\n }\n return null;\n }\n\n private contentMatches(content: string, hint: string): boolean {\n const parts = hint.split('|');\n return parts.some(part => content.includes(part));\n }\n\n private readCandidate(filePath: string, mtime: number): ResolvedPlanFile | null {\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n logger.info('Resolved plan file', {\n file: path.basename(filePath),\n size: content.length,\n ageMs: Date.now() - mtime,\n });\n return { sourcePath: filePath, content };\n } catch (err) {\n logger.error('Failed to read plan file', { filePath, err });\n return null;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,OAAOA,SAAQ;AACf,OAAOC,WAAU;;;ACDjB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AAGf,IAAMC,UAAS,OAAW,MAAM,kBAAkB;AAUlD,IAAM,YAAoC;AAAA,EACxC,mBAAmB,KAAK,KAAK,GAAG,QAAQ,GAAG,oBAAoB,OAAO;AAAA,EACtE,aAAa,KAAK,KAAK,GAAG,QAAQ,GAAG,cAAc,OAAO;AAC5D;AAOO,IAAM,mBAAN,MAAM,kBAAiB;AAAA,EACX;AAAA,EACT,cAAmC,oBAAI,IAAI;AAAA,EAEnD,YAAY,UAAmB;AAC7B,SAAK,WAAW,YAAY,KAAK,KAAK,GAAG,QAAQ,GAAG,oBAAoB,OAAO;AAAA,EACjF;AAAA;AAAA,EAGA,OAAO,UAAU,WAAqC;AACpD,UAAM,MAAM,UAAU,SAAS;AAC/B,WAAO,IAAI,kBAAiB,GAAG;AAAA,EACjC;AAAA;AAAA,EAGA,qBAA2B;AACzB,SAAK,cAAc,KAAK,UAAU;AAClC,IAAAA,QAAO,KAAK,4BAA4B;AAAA,MACtC,KAAK,KAAK;AAAA,MACV,WAAW,KAAK,YAAY;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,wBAAiC;AAC/B,UAAM,aAAa,KAAK,UAAU;AAClC,eAAW,CAAC,UAAU,KAAK,KAAK,YAAY;AAC1C,YAAM,cAAc,KAAK,YAAY,IAAI,QAAQ;AACjD,UAAI,gBAAgB,UAAa,QAAQ,YAAa,QAAO;AAAA,IAC/D;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,QAAQ,aAA+C;AACrD,QAAI,CAAC,GAAG,WAAW,KAAK,QAAQ,GAAG;AACjC,MAAAA,QAAO,KAAK,kCAAkC,EAAE,KAAK,KAAK,SAAS,CAAC;AACpE,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,KAAK,UAAU;AAClC,UAAM,aAAa,KAAK,aAAa,UAAU;AAE/C,QAAI,WAAW,WAAW,GAAG;AAC3B,MAAAA,QAAO,KAAK,4CAA4C;AAAA,QACtD,KAAK,KAAK;AAAA,QACV,aAAa,KAAK,YAAY;AAAA,QAC9B,YAAY,WAAW;AAAA,MACzB,CAAC;AACD,aAAO,KAAK,gBAAgB,YAAY,WAAW;AAAA,IACrD;AAEA,QAAI,WAAW,WAAW,GAAG;AAC3B,aAAO,KAAK,cAAc,WAAW,CAAC,EAAE,MAAM,WAAW,CAAC,EAAE,KAAK;AAAA,IACnE;AAGA,QAAI,aAAa;AACf,YAAM,UAAU,KAAK,eAAe,YAAY,WAAW;AAC3D,UAAI,QAAS,QAAO;AAAA,IACtB;AAGA,eAAW,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC3C,IAAAA,QAAO,KAAK,oDAAoD;AAAA,MAC9D,OAAO,WAAW;AAAA,MAClB,UAAU,KAAK,SAAS,WAAW,CAAC,EAAE,IAAI;AAAA,IAC5C,CAAC;AACD,WAAO,KAAK,cAAc,WAAW,CAAC,EAAE,MAAM,WAAW,CAAC,EAAE,KAAK;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,iBAAiB,UAAkB,YAA6B;AACrE,UAAM,QAAQ,CAAC,IAAI,QAAQ,IAAI,GAAG,QAAQ,EAAE;AAC5C,QAAI,WAAY,OAAM,KAAK,UAAU;AACrC,WAAO,MAAM,KAAK,GAAG;AAAA,EACvB;AAAA,EAEQ,YAAiC;AACvC,UAAM,SAAS,oBAAI,IAAoB;AACvC,QAAI,CAAC,GAAG,WAAW,KAAK,QAAQ,EAAG,QAAO;AAE1C,QAAI;AACF,YAAM,UAAU,GAAG,YAAY,KAAK,QAAQ;AAC5C,iBAAW,SAAS,SAAS;AAC3B,YAAI,CAAC,MAAM,SAAS,KAAK,EAAG;AAC5B,cAAM,WAAW,KAAK,KAAK,KAAK,UAAU,KAAK;AAC/C,YAAI;AACF,gBAAM,OAAO,GAAG,SAAS,QAAQ;AACjC,cAAI,KAAK,OAAO,GAAG;AACjB,mBAAO,IAAI,UAAU,KAAK,OAAO;AAAA,UACnC;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,MAAAA,QAAO,KAAK,kCAAkC,EAAE,KAAK,KAAK,UAAU,IAAI,CAAC;AAAA,IAC3E;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,YAAyE;AAC5F,UAAM,aAAqD,CAAC;AAC5D,eAAW,CAAC,UAAU,KAAK,KAAK,YAAY;AAC1C,UAAI,CAAC,KAAK,YAAY,IAAI,QAAQ,GAAG;AACnC,mBAAW,KAAK,EAAE,MAAM,UAAU,MAAM,CAAC;AAAA,MAC3C;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,gBACN,YACA,aACyB;AACzB,QAAI,WAAW,SAAS,EAAG,QAAO;AAElC,UAAM,SAAS,CAAC,GAAG,WAAW,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAEnE,QAAI,aAAa;AACf,iBAAW,CAAC,QAAQ,KAAK,OAAO,MAAM,GAAG,CAAC,GAAG;AAC3C,YAAI;AACF,gBAAM,UAAU,GAAG,aAAa,UAAU,OAAO;AACjD,cAAI,KAAK,eAAe,SAAS,WAAW,GAAG;AAC7C,YAAAA,QAAO,KAAK,8CAA8C;AAAA,cACxD,MAAM,KAAK,SAAS,QAAQ;AAAA,YAC9B,CAAC;AACD,mBAAO,EAAE,YAAY,UAAU,QAAQ;AAAA,UACzC;AAAA,QACF,QAAQ;AACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,CAAC,YAAY,WAAW,IAAI,OAAO,CAAC;AAC1C,IAAAA,QAAO,KAAK,oDAAoD;AAAA,MAC9D,MAAM,KAAK,SAAS,UAAU;AAAA,MAC9B,OAAO,KAAK,IAAI,IAAI;AAAA,IACtB,CAAC;AACD,WAAO,KAAK,cAAc,YAAY,WAAW;AAAA,EACnD;AAAA,EAEQ,eACN,YACA,aACyB;AACzB,eAAW,aAAa,YAAY;AAClC,UAAI;AACF,cAAM,UAAU,GAAG,aAAa,UAAU,MAAM,OAAO;AACvD,YAAI,KAAK,eAAe,SAAS,WAAW,GAAG;AAC7C,UAAAA,QAAO,KAAK,gCAAgC;AAAA,YAC1C,MAAM,KAAK,SAAS,UAAU,IAAI;AAAA,YAClC,MAAM,YAAY,MAAM,GAAG,EAAE;AAAA,UAC/B,CAAC;AACD,iBAAO,EAAE,YAAY,UAAU,MAAM,QAAQ;AAAA,QAC/C;AAAA,MACF,QAAQ;AACN;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,SAAiB,MAAuB;AAC7D,UAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,WAAO,MAAM,KAAK,UAAQ,QAAQ,SAAS,IAAI,CAAC;AAAA,EAClD;AAAA,EAEQ,cAAc,UAAkB,OAAwC;AAC9E,QAAI;AACF,YAAM,UAAU,GAAG,aAAa,UAAU,OAAO;AACjD,MAAAA,QAAO,KAAK,sBAAsB;AAAA,QAChC,MAAM,KAAK,SAAS,QAAQ;AAAA,QAC5B,MAAM,QAAQ;AAAA,QACd,OAAO,KAAK,IAAI,IAAI;AAAA,MACtB,CAAC;AACD,aAAO,EAAE,YAAY,UAAU,QAAQ;AAAA,IACzC,SAAS,KAAK;AACZ,MAAAA,QAAO,MAAM,4BAA4B,EAAE,UAAU,IAAI,CAAC;AAC1D,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ADzNA,IAAMC,UAAS,OAAW,MAAM,WAAW;AAO3C,IAAM,UAAU;AAET,SAAS,UAAU,KAAqB;AAC7C,SAAO,IAAI,QAAQ,SAAS,EAAE;AAChC;AAGA,SAAS,mBAAmB,SAAqC;AAC/D,QAAM,QAAQ,QAAQ,MAAM,aAAa;AACzC,SAAO,QAAQ,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC1C;AAMO,SAAS,aAAa,UAA2B;AACtD,QAAM,QAAQ,SAAS,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;AACzD,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC,EAAE,KAAK;AAE1C,MAAI,cAAc,KAAK,IAAI,KAAK,WAAW,KAAK,IAAI,EAAG,QAAO;AAI9D,MAAI,MAAM,KAAK,CAAC,MAAM;AACpB,UAAM,IAAI,EAAE,KAAK;AAEjB,QAAI,CAAC,YAAY,KAAK,CAAC,EAAG,QAAO;AAEjC,QAAI,QAAQ,KAAK,CAAC,EAAG,QAAO;AAE5B,WAAO,CAAC,mBAAmB,KAAK,CAAC;AAAA,EACnC,CAAC,EAAG,QAAO;AAIX,MAAI,MAAM,KAAK,CAAC,MAAM,KAAK,KAAK,EAAE,KAAK,CAAC,CAAC,KAAK,IAAI,KAAK,QAAQ,EAAG,QAAO;AAGzE,MACE,IAAI,KAAK,QAAQ,KACjB,gBAAgB,KAAK,QAAQ,KAC7B,CAAC,uBAAuB,KAAK,QAAQ,GACrC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAIA,IAAM,gBAAgB;AACtB,IAAM,aAAa,IAAI,OAAO,KAAK,aAAa,QAAQ;AACxD,IAAM,sBAAsB,IAAI,OAAO,KAAK,aAAa,iBAAiB;AAC1E,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,eAAe;AACrB,IAAM,YAAY;AAElB,IAAM,uBAAuB,IAAI,OAAO,KAAK,aAAa,gBAAgB;AAE1E,IAAM,qBAAqB;AAG3B,IAAM,uBAAuB;AAC7B,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,iBAAiB;AAEvB,IAAM,gBAAgB;AAStB,IAAM,eAAe;AAMrB,IAAM,oBAAoB,IAAI,OAAO,KAAK,aAAa,wDAAyC,GAAG;AAMnG,IAAM,mBAAmB;AACzB,IAAM,wBAAwB;AAMvB,IAAM,kBAAkB;AAMxB,IAAM,uBACX;AAMK,IAAM,kBACX;AAIF,IAAM,qBAAqB;AAIpB,IAAM,0BACX;AAGF,IAAM,qBAAqB;AAG3B,IAAM,wBAAwB;AAYvB,SAAS,uBAAuB,UAAwE;AAC7G,QAAM,QAAQ,SAAS,MAAM,SAAS,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AACzE,QAAM,UAA0B,CAAC;AACjC,QAAM,gBAA0B,CAAC;AACjC,MAAI,iBAAiB;AAErB,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAW,IAAI,EAAG;AACtB,QAAI,wBAAwB,KAAK,IAAI,EAAG;AACxC,UAAM,IAAI,mBAAmB,KAAK,IAAI;AACtC,QAAI,GAAG;AACL,YAAM,QAAQ,EAAE,CAAC,EAAE,KAAK;AAGxB,UAAI,MAAM,UAAU,KAAK,CAAC,QAAQ,KAAK,KAAK,GAAG;AAI7C,YAAI,EAAE,QAAQ,KAAK,CAAC,gBAAgB;AAClC,gBAAM,SAAS,KAAK,MAAM,GAAG,EAAE,KAAK,EACjC,QAAQ,aAAa,GAAG,EACxB,KAAK;AACR,cAAI,OAAO,SAAS,EAAG,eAAc,KAAK,MAAM;AAAA,QAClD;AACA,yBAAiB;AACjB,gBAAQ,KAAK,EAAE,OAAO,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;AAAA,MACnD;AAAA,IACF,WAAW,CAAC,gBAAgB;AAC1B,UAAI,CAAC,cAAc,KAAK,IAAI,KAAK,CAAC,eAAe,KAAK,IAAI,GAAG;AAC3D,sBAAc,KAAK,IAAI;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,wBAAwB,KAAK,QAAQ,IAAI,IAAI;AACjE,MAAI,QAAQ,SAAS,YAAa,QAAO;AACzC,SAAO,EAAE,UAAU,cAAc,KAAK,GAAG,EAAE,KAAK,GAAG,QAAQ;AAC7D;AAOO,SAAS,oBAAoB,UAAoC;AACtE,MAAI,wBAAwB,KAAK,QAAQ,EAAG,QAAO;AACnD,MAAI,sBAAsB,KAAK,QAAQ,EAAG,QAAO;AACjD,MAAI,mBAAmB,KAAK,QAAQ,EAAG,QAAO;AAC9C,SAAO;AACT;AAYO,SAAS,oBAAoB,UAA2B;AAC7D,MAAI,gBAAgB,KAAK,QAAQ,EAAG,QAAO;AAC3C,MAAI,qBAAqB,KAAK,QAAQ,EAAG,QAAO;AAChD,MAAI,uBAAuB,QAAQ,MAAM,KAAM,QAAO;AAEtD,MAAI,wBAAwB,KAAK,QAAQ,GAAG;AAC1C,UAAM,QAAQ,SAAS,MAAM,SAAS,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AACzE,WAAO,MAAM,KAAK,OAAK,CAAC,WAAW,CAAC,KAAK,mBAAmB,KAAK,CAAC,CAAC;AAAA,EACrE;AACA,SAAO;AACT;AAUO,SAAS,mBAAmB,UAA2B;AAC5D,QAAM,QAAQ,SAAS,MAAM,SAAS,EAAE,OAAO,OAAK,EAAE,KAAK,CAAC;AAC5D,MAAI,aAAa;AAEjB,SAAO,MAAM,KAAK,UAAQ;AACxB,UAAM,IAAI,KAAK,KAAK;AACpB,QAAI,EAAE,WAAW,EAAG,QAAO;AAE3B,QAAI,QAAQ,KAAK,CAAC,GAAG;AACnB,mBAAa;AACb,aAAO;AAAA,IACT;AACA,QAAI,WAAY,QAAO;AAEvB,QAAI,qBAAqB,KAAK,CAAC,EAAG,QAAO;AACzC,QAAI,KAAK,KAAK,CAAC,EAAG,QAAO;AACzB,QAAI,cAAc,KAAK,CAAC,KAAK,WAAW,KAAK,CAAC,EAAG,QAAO;AAExD,QAAI,SAAS,KAAK,CAAC,KAAK,SAAS,KAAK,CAAC,EAAG,QAAO;AACjD,QAAI,WAAW,CAAC,EAAG,QAAO;AAC1B,WAAO;AAAA,EACT,CAAC;AACH;AAGO,SAAS,WAAW,MAAuB;AAChD,QAAM,IAAI,KAAK,QAAQ,OAAO,EAAE,EAAE,KAAK;AACvC,MAAI,EAAE,WAAW,EAAG,QAAO;AAC3B,MAAI,WAAW,KAAK,CAAC,EAAG,QAAO;AAC/B,MAAI,EAAE,UAAU,KAAK,oBAAoB,KAAK,CAAC,EAAG,QAAO;AACzD,MAAI,cAAc,KAAK,CAAC,EAAG,QAAO;AAClC,MAAI,YAAY,KAAK,CAAC,EAAG,QAAO;AAChC,MAAI,aAAa,KAAK,CAAC,EAAG,QAAO;AACjC,MAAI,UAAU,KAAK,CAAC,EAAG,QAAO;AAC9B,MAAI,qBAAqB,KAAK,CAAC,EAAG,QAAO;AACzC,MAAI,cAAc,KAAK,CAAC,EAAG,QAAO;AAClC,MAAI,mBAAmB,KAAK,CAAC,EAAG,QAAO;AAEvC,MAAI,qBAAqB,KAAK,CAAC,EAAG,QAAO;AACzC,MAAI,iBAAiB,KAAK,CAAC,EAAG,QAAO;AACrC,MAAI,iBAAiB,KAAK,CAAC,EAAG,QAAO;AACrC,MAAI,gBAAgB,KAAK,CAAC,EAAG,QAAO;AACpC,MAAI,eAAe,KAAK,CAAC,KAAK,EAAE,SAAS,EAAG,QAAO;AAEnD,MAAI,aAAa,KAAK,CAAC,EAAG,QAAO;AACjC,MAAI,kBAAkB,KAAK,CAAC,EAAG,QAAO;AACtC,MAAI,iBAAiB,KAAK,CAAC,KAAK,EAAE,SAAS,EAAG,QAAO;AACrD,MAAI,sBAAsB,KAAK,CAAC,EAAG,QAAO;AAC1C,SAAO;AACT;AAwCO,IAAM,YAAN,MAAoC;AAAA,EAQzC,YACmB,gBACA,iBACA,kBACA,eACA,aACA,eAAuB,KACxC;AANiB;AACA;AACA;AACA;AACA;AACA;AAAA,EAChB;AAAA;AAAA,EAbK,WAAW,oBAAI,IAA4B;AAAA;AAAA;AAAA,EAI3C,iBAAiB,oBAAI,IAAY;AAAA;AAAA,EAazC,MAAM,IAAI,SAAyC;AACjD,QAAI,eAAe,GAAG;AACpB,MAAAA,QAAO,KAAK,mDAA8C;AAC1D,aAAO,EAAE,SAAS,OAAO,QAAQ,yBAAyB,UAAU,KAAK;AAAA,IAC3E;AAEA,UAAM,EAAE,QAAQ,SAAS,WAAW,eAAe,UAAU,IAAI;AACjE,UAAM,YAAY,KAAK,qBAAqB,SAAS;AACrD,UAAM,YAAY,QAAQ;AAC1B,UAAM,kBAAkB,QAAQ,mBAAmB;AAEnD,IAAAA,QAAO,KAAK,mBAAmB,EAAE,SAAS,WAAW,WAAW,WAAW,gBAAgB,CAAC;AAG5F,UAAM,EAAE,WAAW,MAAM,IAAI,KAAK,cAAc,SAAS,WAAW,SAAS;AAI7E,QAAI,OAAO;AACT,MAAAA,QAAO,KAAK,6CAA6C,EAAE,WAAW,UAAU,CAAC;AAGjF,YAAM,KAAK,cAAc,WAAW,GAAO;AAAA,IAC7C,WAAW,iBAAiB;AAG1B,MAAAA,QAAO,KAAK,kDAAkD,EAAE,WAAW,UAAU,CAAC;AACtF,YAAM,KAAK,cAAc,WAAW,GAAM;AAAA,IAC5C,OAAO;AAGL,MAAAA,QAAO,KAAK,gDAAgD,EAAE,WAAW,UAAU,CAAC;AACpF,YAAM,KAAK,cAAc,WAAW,GAAM;AAAA,IAC5C;AAKA,QAAI,cAAc,UAAU,KAAK,oBAAoB,SAAS,GAAG;AAC/D,aAAO,KAAK,kBAAkB,WAAW,OAAO,SAAS,WAAW,OAAO;AAAA,IAC7E;AAIA,QAAI,mBAAmB,CAAC,OAAO;AAC7B,MAAAA,QAAO,KAAK,iEAAiE,EAAE,UAAU,CAAC;AAC1F,YAAMC,UAAS,MAAM,KAAK,iBAAiB,WAAW,SAAS,aAAa;AAC5E,MAAAD,QAAO,KAAK,wCAAwC;AAAA,QAClD;AAAA,QAAS;AAAA,QAAW;AAAA,QACpB,UAAUC,QAAO;AAAA,QACjB,cAAcA,QAAO,OAAO;AAAA,MAC9B,CAAC;AACD,aAAO,KAAK,eAAeA,SAAQ,SAAS;AAAA,IAC9C;AAGA,UAAM,KAAK,eAAe,WAAW,WAAW,cAAc,QAAQ,OAAO;AAM7E,UAAM,KAAK,aAAa,WAAW,UAAU,SAAS;AACtD,UAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAK,CAAC;AAGvD,UAAM,aAAa,KAAK,gBAAgB,SAAS,MAAM;AAGvD,UAAM,cAAc,8CAA8C,UAAU;AAC5E,UAAM,KAAK,aAAa,WAAW,aAAa,SAAS;AAGzD,UAAM,SAAS,MAAM,KAAK,iBAAiB,WAAW,SAAS,aAAa;AAE5E,IAAAD,QAAO,KAAK,6BAA6B;AAAA,MACvC;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,OAAO;AAAA,MACjB,cAAc,OAAO,OAAO;AAAA,IAC9B,CAAC;AAED,WAAO,KAAK,eAAe,QAAQ,SAAS;AAAA,EAC9C;AAAA,EAEA,UAAgB;AACd,eAAW,CAAC,EAAE,IAAI,KAAK,KAAK,UAAU;AACpC,WAAK,eAAe,IAAI,KAAK,SAAS;AACtC,WAAK,gBAAgB,QAAQ,KAAK,SAAS;AAAA,IAC7C;AACA,SAAK,SAAS,MAAM;AACpB,IAAAA,QAAO,KAAK,2CAA2C;AAAA,EACzD;AAAA,EAEA,cAAc,eAA+B;AAC3C,UAAM,OAAO,KAAK,SAAS,IAAI,aAAa;AAC5C,QAAI,CAAC,KAAM,QAAO;AAClB,SAAK,eAAe,IAAI,KAAK,SAAS;AACtC,SAAK,gBAAgB,QAAQ,KAAK,SAAS;AAC3C,SAAK,SAAS,OAAO,aAAa;AAClC,WAAO;AAAA,EACT;AAAA,EAEA,mBAAmB,eAAgC;AACjD,UAAM,OAAO,KAAK,SAAS,IAAI,aAAa;AAC5C,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,UAAU,KAAK,gBAAgB,IAAI,KAAK,SAAS;AACvD,QAAI,CAAC,SAAS;AAEZ,WAAK,SAAS,OAAO,aAAa;AAClC,aAAO;AAAA,IACT;AAGA,SAAK,gBAAgB,MAAM,KAAK,WAAW,GAAM;AACjD,IAAAA,QAAO,KAAK,qCAAqC;AAAA,MAC/C,SAAS;AAAA,MACT,WAAW,KAAK;AAAA,IAClB,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAKQ,YAAY,WAA2B;AAC7C,UAAM,UAAU,cAAc,SAAS;AACvC,WAAO,SAAS,YAAY;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,aAAa,WAAmB,MAAc,WAAkC;AAC5F,UAAM,WAAW,KAAK,YAAY,SAAS;AAC3C,UAAM,UAAU,cAAc,SAAS;AAEvC,QAAI,SAAS,eAAe;AAC1B,WAAK,gBAAgB,MAAM,WAAW,IAAI;AAC1C,YAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAG,CAAC;AACrD,WAAK,gBAAgB,MAAM,WAAW,QAAQ;AAAA,IAChD,OAAO;AACL,WAAK,gBAAgB,MAAM,WAAW,OAAO,QAAQ;AAAA,IACvD;AAAA,EACF;AAAA;AAAA,EAGQ,qBAAqB,WAA4B;AACvD,QAAI,aAAa,KAAK,cAAc,SAAS,GAAG;AAC9C,aAAO,KAAK,cAAc,SAAS;AAAA,IACrC;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGQ,uBAAuB,WAI7B;AACA,UAAM,UAAU,cAAc,SAAS;AACvC,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI;AAAA,QACR,UAAU,SAAS;AAAA,MAErB;AAAA,IACF;AACA,UAAM,QAAQ,iBAAiB,SAAS;AACxC,UAAM,SAAU,SAAS,QAAQ,IAAI,MAAM,YAAY,KAAM,OAAO,iBAAiB;AACrF,UAAM,QAAQ,KAAK,cACf,sBAAsB,WAAW,KAAK,WAAW,IACjD;AACJ,WAAO,EAAE,SAAS,OAAO,OAAO;AAAA,EAClC;AAAA;AAAA,EAIQ,cAAc,SAAiB,WAAmB,WAA2D;AACnH,UAAM,WAAW,KAAK,SAAS,IAAI,OAAO;AAG1C,QAAI,YAAY,SAAS,cAAc,WAAW;AAChD,MAAAA,QAAO,KAAK,8CAA8C;AAAA,QACxD;AAAA,QACA,UAAU,SAAS;AAAA,QACnB,UAAU;AAAA,QACV,WAAW,SAAS;AAAA,MACtB,CAAC;AACD,WAAK,gBAAgB,QAAQ,SAAS,SAAS;AAC/C,WAAK,SAAS,OAAO,OAAO;AAAA,IAC9B;AAGA,QAAI,YAAY,SAAS,cAAc,WAAW;AAChD,UAAI,KAAK,gBAAgB,IAAI,SAAS,SAAS,GAAG;AAChD,QAAAA,QAAO,KAAK,6CAA6C;AAAA,UACvD;AAAA,UACA;AAAA,UACA,WAAW,SAAS;AAAA,QACtB,CAAC;AACD,eAAO,EAAE,WAAW,SAAS,WAAW,OAAO,MAAM;AAAA,MACvD;AAEA,WAAK,SAAS,OAAO,OAAO;AAAA,IAC9B;AAIA,UAAM,SAAS,KAAK,gBAAgB,cAAc,OAAO;AACzD,QAAI,QAAQ;AACV,MAAAA,QAAO,KAAK,8CAA8C;AAAA,QACxD;AAAA,QACA,WAAW,OAAO;AAAA,MACpB,CAAC;AACD,WAAK,gBAAgB,QAAQ,OAAO,EAAE;AAAA,IACxC;AAGA,UAAM,EAAE,SAAS,OAAO,OAAO,IAAI,KAAK,uBAAuB,SAAS;AACxE,UAAM,OAAO,QAAQ,aAAa,EAAE,OAAO,UAAU,CAAC;AACtD,UAAM,WAAW,mBAAmB,OAAO;AAE3C,UAAM,OAAO,KAAK,gBAAgB,OAAO;AAAA,MACvC;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,SAAS;AAAA,IACX,CAAC;AAED,SAAK,SAAS,IAAI,SAAS;AAAA,MACzB,WAAW,KAAK;AAAA,MAChB;AAAA;AAAA;AAAA,MAGA,aAAa,QAAQ,mBAAmB;AAAA,MACxC,iBAAiB;AAAA,IACnB,CAAC;AACD,IAAAA,QAAO,KAAK,2BAA2B;AAAA,MACrC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,KAAK;AAAA,MAChB,KAAK,KAAK;AAAA,MACV;AAAA,IACF,CAAC;AACD,WAAO,EAAE,WAAW,KAAK,IAAI,OAAO,KAAK;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,cAAc,WAAmB,YAAoB,KAAuB;AAClF,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,UAAI,aAAa;AACjB,UAAI,qBAAqB;AACzB,UAAI;AAIJ,YAAM,eAAe;AAMrB,YAAM,eAAe;AACrB,UAAI,WAAW;AAKf,UAAI,aAAa;AACjB,UAAI;AACJ,YAAM,mBAAmB;AAEzB,YAAM,QAAQ,WAAW,MAAM;AAC7B,YAAI,eAAgB,cAAa,cAAc;AAC/C,YAAI,aAAc,cAAa,YAAY;AAC3C,qBAAa,QAAQ;AACrB,QAAAA,QAAO,KAAK,yCAAyC,EAAE,WAAW,UAAU,CAAC;AAE7E,gBAAQ;AAAA,MACV,GAAG,SAAS;AAEZ,YAAM,OAAO,CAAC,WAAmB;AAC/B,qBAAa,KAAK;AAClB,YAAI,eAAgB,cAAa,cAAc;AAC/C,YAAI,aAAc,cAAa,YAAY;AAC3C,qBAAa,QAAQ;AACrB,QAAAA,QAAO,KAAK,4BAA4B,EAAE,WAAW,OAAO,CAAC;AAC7D,gBAAQ;AAAA,MACV;AAEA,YAAM,oBAAoB,MAAM;AAC9B,YAAI,CAAC,WAAY;AACjB,YAAI,aAAc,cAAa,YAAY;AAC3C,uBAAe,WAAW,MAAM;AAC9B,cAAI,WAAY;AAChB,UAAAA,QAAO,KAAK,8DAAyD,EAAE,UAAU,CAAC;AAClF,eAAK,sBAAsB;AAAA,QAC7B,GAAG,gBAAgB;AAAA,MACrB;AAEA,YAAM,eAAe,KAAK,gBAAgB,OAAO,WAAW,CAAC,SAAiB;AAC5E,cAAM,WAAW,UAAU,IAAI;AAG/B,cAAM,gBAAgB,SAAS,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;AACjE,cAAM,UAAU,cAAc,WAAW,KAAK,cAAc,MAAM,CAAC,MAAM,WAAW,CAAC,CAAC;AAGtF,YAAI,CAAC,cAAc,iBAAiB,KAAK,QAAQ,GAAG;AAClD,uBAAa;AAAA,QACf;AAIA,0BAAkB;AAKlB,YAAI,CAAC,sBAAsB,gBAAgB,KAAK,QAAQ,GAAG;AACzD,+BAAqB;AACrB,UAAAA,QAAO,KAAK,0CAA0C,EAAE,UAAU,CAAC;AACnE,qBAAW,MAAM,KAAK,gBAAgB,MAAM,WAAW,IAAI,GAAG,GAAG;AAAA,QACnE;AAIA,YAAI,qBAAqB,KAAK,QAAQ,GAAG;AACvC,UAAAA,QAAO,KAAK,gEAAgE,EAAE,UAAU,CAAC;AACzF,qBAAW,MAAM,KAAK,gBAAgB,MAAM,WAAW,IAAI,GAAG,GAAG;AAAA,QACnE;AAEA,YAAI,aAAa,QAAQ,GAAG;AAC1B,uBAAa;AAAA,QACf;AAEA,YAAI,CAAC,YAAY,aAAa,KAAK,QAAQ,GAAG;AAC5C,qBAAW;AAAA,QACb;AAOA,YAAI,cAAc,UAAU;AAC1B,cAAI,kBAAkB,SAAS;AAAA,UAE/B,OAAO;AACL,gBAAI,eAAgB,cAAa,cAAc;AAC/C,kBAAM,SAAS,aAAa,uBAAuB;AACnD,6BAAiB,WAAW,MAAM,KAAK,MAAM,GAAG,YAAY;AAAA,UAC9D;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,eACZ,WACA,WACA,UACA,SACe;AACf,UAAM,UAAU,cAAc,SAAS;AACvC,QAAI,CAAC,SAAS,gBAAgB,CAAC,QAAQ,cAAc,CAAC,QAAQ,aAAc;AAE5E,UAAM,UAAU,KAAK,SAAS,IAAI,OAAO;AACzC,QAAI,CAAC,QAAS;AAEd,UAAM,aAAa,WAAW,QAAQ,eAAgB,QAAQ,mBAAmB;AACjF,QAAI,QAAQ,gBAAgB,YAAY;AACtC,MAAAA,QAAO,KAAK,8BAA8B,EAAE,WAAW,WAAW,CAAC;AACnE;AAAA,IACF;AAEA,UAAM,eAAe;AACrB,aAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AAGrC,YAAM,gBAAgB,KAAK,oBAAoB,WAAW,GAAK;AAC/D,WAAK,gBAAgB,MAAM,WAAW,QAAQ,YAAY;AAC1D,YAAM,eAAe,MAAM;AAE3B,YAAM,WAAW,QAAQ,WAAW,YAAY;AAEhD,UAAI,UAAU;AACZ,gBAAQ,cAAc;AACtB,YAAI,aAAa,YAAY;AAC3B,UAAAA,QAAO,KAAK,qBAAqB;AAAA,YAC/B;AAAA,YAAW;AAAA,YAAW;AAAA,YAAY,UAAU,IAAI;AAAA,UAClD,CAAC;AACD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,IAAAA,QAAO,KAAK,gDAAgD;AAAA,MAC1D;AAAA,MAAW;AAAA,MAAW;AAAA,MAAY,SAAS,QAAQ;AAAA,IACrD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,oBAAoB,WAAmB,YAAqC;AAClF,WAAO,IAAI,QAAQ,aAAW;AAC5B,YAAM,SAAmB,CAAC;AAC1B,YAAM,eAAe,KAAK,gBAAgB,OAAO,WAAW,CAAC,SAAiB;AAC5E,eAAO,KAAK,UAAU,IAAI,CAAC;AAAA,MAC7B,CAAC;AACD,iBAAW,MAAM;AACf,qBAAa,QAAQ;AACrB,gBAAQ,OAAO,KAAK,EAAE,CAAC;AAAA,MACzB,GAAG,UAAU;AAAA,IACf,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,oBAAoB,WAA4B;AACtD,UAAM,OAAO,sBAAsB,SAAS;AAC5C,UAAM,UAAU,cAAc,SAAS;AACvC,WAAO,CAAC,MAAM,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,CAAC,CAAC,SAAS;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,kBACZ,WACA,OACA,SACA,WACA,SACoB;AACpB,UAAM,kBAAkB,QAAQ,mBAAmB,CAAC;AAGpD,UAAM,WAAW,iBAAiB,UAAU,SAAS;AACrD,aAAS,mBAAmB;AAE5B,UAAM,WAAW,mBAAmB,OAAO;AAC3C,UAAM,cAAc,WAChB,iBAAiB,iBAAiB,QAAQ,IAC1C;AAGJ,QAAI,iBAAiB;AACnB,MAAAA,QAAO,KAAK,6DAA6D,EAAE,UAAU,CAAC;AAAA,IACxF,OAAO;AACL,MAAAA,QAAO,KAAK,uCAAuC,EAAE,WAAW,UAAU,CAAC;AAC3E,YAAM,KAAK,eAAe,WAAW,WAAW,MAAM,OAAO;AAE7D,UAAI,CAAC,OAAO;AACV,cAAM,KAAK,aAAa,WAAW,UAAU,SAAS;AACtD,cAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAK,CAAC;AAAA,MACzD;AAEA,YAAM,aAAa,KAAK,gBAAgB,SAAS,QAAQ,MAAM;AAC/D,YAAM,KAAK;AAAA,QACT;AAAA,QACA,8CAA8C,UAAU;AAAA,QACxD;AAAA,MACF;AAAA,IACF;AAIA,UAAM,oBAAoB,MAAM,SAAS,sBAAsB;AAE/D,UAAM,aAAa,MAAM,KAAK,iBAAiB,WAAW;AAAA,MACxD,GAAG;AAAA,MACH,kBAAkB;AAAA,MAClB,eAAe;AAAA,IACjB,GAAG,QAAQ,eAAe,eAAe;AAEzC,QAAI,WAAW,UAAU;AACvB,MAAAA,QAAO,KAAK,0CAA0C;AAAA,QACpD;AAAA,QAAW,WAAW,WAAW;AAAA,MACnC,CAAC;AACD,aAAO,KAAK,eAAe,YAAY,SAAS;AAAA,IAClD;AAGA,IAAAA,QAAO,KAAK,0DAA0D,EAAE,UAAU,CAAC;AAEnF,UAAM,WAAW,SAAS,QAAQ,WAAW;AAE7C,QAAI,CAAC,UAAU;AACb,MAAAA,QAAO,MAAM,uDAAuD,EAAE,WAAW,QAAQ,CAAC;AAC1F,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,WAAW;AAAA,QACnB,cAAc;AAAA,QACd;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,IACF;AAGA,UAAM,gBAAgB,QAAQ,iBAAiB,CAAC;AAChD,QAAI,cAAc,SAAS,GAAG;AAC5B,iBAAW,cAAc,eAAe;AACtC,cAAM,YAAYE,MAAK,QAAQ,UAAU;AACzC,YAAI,CAACC,IAAG,WAAW,SAAS,GAAG;AAC7B,UAAAA,IAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,QAC7C;AACA,QAAAA,IAAG,cAAc,YAAY,SAAS,SAAS,OAAO;AACtD,QAAAH,QAAO,KAAK,qCAAqC;AAAA,UAC/C,QAAQE,MAAK,SAAS,SAAS,UAAU;AAAA,UACzC,QAAQ;AAAA,UACR,MAAM,SAAS,QAAQ;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF,OAAO;AACL,MAAAF,QAAO,KAAK,sEAAsE;AAAA,QAChF;AAAA,QACA,cAAc,SAAS;AAAA,MACzB,CAAC;AAAA,IACH;AAEA,IAAAA,QAAO,KAAK,mDAAmD;AAAA,MAC7D;AAAA,MACA,YAAYE,MAAK,SAAS,SAAS,UAAU;AAAA,MAC7C,iBAAiB,cAAc;AAAA,IACjC,CAAC;AAED,WAAO,KAAK,eAAe,YAAY,SAAS;AAAA,EAClD;AAAA;AAAA,EAGQ,eACN,cACA,WACW;AACX,WAAO;AAAA,MACL,SAAS,CAAC,aAAa;AAAA,MACvB,QAAQ,aAAa;AAAA,MACrB,cAAc,aAAa,WACtB,aAAa,gBAAgB,SAAS,gFAAoB,6BAC3D;AAAA,MACJ;AAAA,MACA,UAAU,aAAa,WAAW,OAAO;AAAA,MACzC,aAAa,aAAa;AAAA,MAC1B,oBAAoB,aAAa;AAAA,IACnC;AAAA,EACF;AAAA,EAEQ,gBAAgB,SAAiB,QAAwB;AAE/D,UAAM,MAAMA,MAAK,KAAK,SAAS,cAAc;AAC7C,QAAI,CAACC,IAAG,WAAW,GAAG,GAAG;AACvB,MAAAA,IAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACvC;AACA,UAAM,UAAU;AAChB,UAAM,UAAUD,MAAK,KAAK,SAAS,OAAO;AAC1C,IAAAC,IAAG,cAAc,SAAS,QAAQ,OAAO;AACzC,WAAO;AAAA,EACT;AAAA;AAAA,EAIQ,iBACN,WACA,SACA,eACA,mBAC2B;AAC3B,WAAO,IAAI,QAA0B,CAAC,YAAY;AAChD,YAAM,cAAwB,CAAC;AAC/B,UAAI,iBAAiB,KAAK,IAAI;AAC9B,UAAI,uBAAuB;AAC3B,UAAI,eAAe,qBAAqB;AACxC,UAAI,WAAW;AACf,UAAI;AACJ,YAAM,oBAAoB;AAC1B,YAAM,eAAyB,CAAC;AAChC,UAAI,gBAAgB;AAEpB,YAAM,oBAAoB;AAC1B,UAAI;AACJ,UAAI,sBAA4E;AAIhF,YAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,YAAM,YAAY,QAAQ;AAK1B,YAAM,kBAAkB,QAAQ,kBAAkB;AAClD,YAAM,eAAe,QAAQ,sBAAsB;AACnD,YAAM,iBAAiB,QAAQ,wBAAwB;AACvD,UAAI,aAAa;AAEjB,YAAM,SAAS,CAAC,WAA6B;AAC3C,YAAI,SAAU;AACd,mBAAW;AACX,gBAAQ;AACR,gBAAQ,MAAM;AAAA,MAChB;AAGA,YAAM,oBAAoB,CAAC,YAAmD;AAC5E,eAAO,WAAW,MAAM;AACtB,gBAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,gBAAM,WAAW,wBAAwB,WAAW;AACpD,cAAI,YAAY,aAAa,gBAAgB;AAC3C;AACA,YAAAH,QAAO,KAAK,oDAAoD;AAAA,cAC9D;AAAA,cAAW;AAAA,cAAY,eAAe;AAAA,cACtC,iBAAiB;AAAA,YACnB,CAAC;AACD,wBAAY,kBAAkB,YAAY;AAC1C;AAAA,UACF;AACA,iBAAO;AAAA,YACL,QAAQ,YAAY,KAAK,EAAE;AAAA,YAC3B,UAAU;AAAA,YACV,aAAa;AAAA,YACb,oBAAoB;AAAA,UACtB,CAAC;AAAA,QACH,GAAG,OAAO;AAAA,MACZ;AACA,UAAI,YAAY,kBAAkB,SAAS;AAI3C,YAAM,YAAY,YAAY,MAAM;AAClC,YAAI,CAAC,qBAAsB;AAC3B,YAAI,KAAK,IAAI,IAAI,kBAAkB,eAAe;AAChD,iBAAO;AAAA,YACL,QAAQ,YAAY,KAAK,EAAE;AAAA,YAC3B,UAAU;AAAA,YACV,aAAa;AAAA,YACb,oBAAoB;AAAA,UACtB,CAAC;AAAA,QACH;AAAA,MACF,GAAG,GAAK;AAGR,YAAM,eAAe,KAAK,gBAAgB,OAAO,WAAW,CAAC,SAAiB;AAC5E,YAAI,SAAU;AAEd,cAAM,WAAW,UAAU,IAAI;AAG/B,cAAM,gBAAgB,SAAS,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;AACjE,cAAM,UAAU,cAAc,WAAW,KAAK,cAAc,MAAM,CAAC,MAAM,WAAW,CAAC,CAAC;AACtF,cAAM,SAAS,aAAa,QAAQ;AAKpC,cAAM,eAAe,UAAU,mBAAmB,QAAQ;AAO1D,cAAM,iBAAiB,CAAC,WAAW,SAAS,KAAK,EAAE,SAAS;AAC5D,YAAI,mBAAmB,CAAC,UAAU,eAAe;AAC/C,2BAAiB,KAAK,IAAI;AAG1B,cAAI,oBAAoB;AACtB,yBAAa,kBAAkB;AAC/B,iCAAqB;AACrB,kCAAsB;AACtB,YAAAA,QAAO,KAAK,kEAA6D,EAAE,UAAU,CAAC;AAAA,UACxF;AAAA,QACF;AAGA,YAAI,CAAC,gBAAgB,SAAS,SAAS,kBAAkB,GAAG;AAC1D,yBAAe;AACf;AAAA,QACF;AAIA,YAAI,cAAc,KAAK,QAAQ,GAAG;AAChC;AAAA,QACF;AAMA,YAAI,QAAQ,oBAAoB,wBAAwB,QAAQ,iBAAiB,KAAK,QAAQ,GAAG;AAC/F,UAAAA,QAAO,KAAK,8BAA8B,EAAE,UAAU,CAAC;AACvD,iBAAO,EAAE,QAAQ,YAAY,KAAK,EAAE,GAAG,UAAU,MAAM,CAAC;AACxD;AAAA,QACF;AAYA,YAAI,wBAAwB,kBAAkB,KAAK,QAAQ,GAAG;AAC5D,gBAAM,gBAAgB,QAAQ,gBAAgB,QAAQ,cAAc,IAAI;AACxE,cAAI,eAAe;AACjB,YAAAA,QAAO,KAAK,2CAA2C,EAAE,UAAU,CAAC;AACpE,mBAAO,EAAE,QAAQ,YAAY,KAAK,EAAE,GAAG,UAAU,MAAM,CAAC;AACxD;AAAA,UACF;AACA,UAAAA,QAAO,KAAK,gEAAgE,EAAE,UAAU,CAAC;AAAA,QAC3F;AAKA,YAAI,qBAAqB,KAAK,QAAQ,GAAG;AACvC,UAAAA,QAAO,KAAK,+CAA+C,EAAE,UAAU,CAAC;AACxE,qBAAW,MAAM;AACf,gBAAI,CAAC,SAAU,MAAK,gBAAgB,MAAM,WAAW,IAAI;AAAA,UAC3D,GAAG,GAAG;AACN;AAAA,QACF;AAKA,YAAI,QAAQ,mBAAmB,CAAC,iBAAiB,oBAAoB,QAAQ,GAAG;AAC9E,gBAAM,SAAS,uBAAuB,QAAQ;AAC9C,cAAI,QAAQ;AACV,kBAAM,aAAa,oBAAoB,QAAQ;AAC/C,gBAAI,eAAe,QAAQ;AACzB,8BAAgB;AAChB,2BAAa,SAAS;AACtB,kBAAI,oBAAoB;AAAE,6BAAa,kBAAkB;AAAG,qCAAqB;AAAA,cAAW;AAC5F,cAAAA,QAAO,KAAK,wEAAwE;AAAA,gBAClF;AAAA,gBAAW,UAAU,OAAO,SAAS,MAAM,GAAG,EAAE;AAAA,gBAChD,aAAa,OAAO,QAAQ;AAAA,cAC9B,CAAC;AACD,sBAAQ,gBAAgB;AAAA,gBACtB,MAAM;AAAA,gBACN,SAAS,OAAO;AAAA,gBAChB,SAAS,OAAO;AAAA,cAClB,CAAC,EAAE,KAAK,CAAC,aAAa;AACpB,gCAAgB;AAChB,oBAAI,CAAC,YAAY,UAAU;AACzB,uBAAK,gBAAgB,MAAM,WAAW,WAAW,IAAI;AAAA,gBACvD;AAAA,cACF,CAAC,EAAE,MAAM,CAAC,QAAQ;AAChB,gBAAAA,QAAO,KAAK,0DAA0D;AAAA,kBACpE,OAAQ,IAAc;AAAA,gBACxB,CAAC;AACD,gCAAgB;AAAA,cAClB,CAAC;AACD;AAAA,YACF;AAGA,gBAAI,CAAC,oBAAoB;AACvB,oCAAsB;AACtB,cAAAA,QAAO,KAAK,kEAAkE;AAAA,gBAC5E;AAAA,gBAAW,UAAU,OAAO,SAAS,MAAM,GAAG,EAAE;AAAA,gBAChD,aAAa,OAAO,QAAQ;AAAA,cAC9B,CAAC;AACD,mCAAqB,WAAW,MAAM;AACpC,qCAAqB;AACrB,oBAAI,YAAY,iBAAiB,CAAC,oBAAqB;AACvD,gCAAgB;AAChB,6BAAa,SAAS;AACtB,sBAAM,KAAK;AACX,sCAAsB;AACtB,gBAAAA,QAAO,KAAK,kEAA6D;AAAA,kBACvE;AAAA,kBAAW,UAAU,GAAG,SAAS,MAAM,GAAG,EAAE;AAAA,gBAC9C,CAAC;AACD,wBAAQ,gBAAiB;AAAA,kBACvB,MAAM;AAAA,kBACN,SAAS,GAAG;AAAA,kBACZ,SAAS,GAAG;AAAA,gBACd,CAAC,EAAE,KAAK,CAAC,aAAa;AACpB,kCAAgB;AAChB,sBAAI,CAAC,YAAY,UAAU;AACzB,yBAAK,gBAAgB,MAAM,WAAW,WAAW,IAAI;AAAA,kBACvD;AAAA,gBACF,CAAC,EAAE,MAAM,CAAC,QAAQ;AAChB,kBAAAA,QAAO,KAAK,qDAAqD;AAAA,oBAC/D,OAAQ,IAAc;AAAA,kBACxB,CAAC;AACD,kCAAgB;AAAA,gBAClB,CAAC;AAAA,cACH,GAAG,iBAAiB;AAAA,YACtB;AACA;AAAA,UACF;AAAA,QACF;AAGA,YAAI,gBAAgB,CAAC,wBAAwB,CAAC,WAAW,SAAS,KAAK,EAAE,SAAS,GAAG;AACnF,iCAAuB;AAAA,QACzB;AAEA,oBAAY,KAAK,QAAQ;AAGzB,YAAI,eAAe;AACjB,qBAAW,QAAQ,SAAS,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG;AAC/D,gBAAI,CAAC,WAAW,IAAI,GAAG;AACrB,4BAAc;AAAA,gBACZ,MAAM;AAAA,gBACN,SAAS;AAAA,gBACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,cACpC,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAIA,YAAI,CAAC,iBAAiB,CAAC,SAAS;AAC9B,qBAAW,QAAQ,SAAS,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG;AAC/D,gBAAI,CAAC,WAAW,IAAI,GAAG;AACrB,2BAAa,KAAK,IAAI;AACtB,kBAAI,aAAa,SAAS,kBAAmB,cAAa,MAAM;AAAA,YAClE;AAAA,UACF;AAAA,QACF;AAGA,YAAI,wBAAwB,aAAa,QAAQ,GAAG;AAGlD,cAAI,cAAc;AAAA,UAElB,WAAW,QAAQ,kBAAkB;AAKnC,YAAAA,QAAO,MAAM,sDAAsD,EAAE,UAAU,CAAC;AAAA,UAClF,WAAW,iBAAiB,SAAS;AAAA,UAKrC,OAAO;AAIL,gBAAI,QAAQ,mBAAmB,CAAC,iBAAiB,aAAa,UAAU,GAAG;AACzE,oBAAM,WAAW,aAAa,KAAK,IAAI;AACvC,kBAAI,oBAAoB,QAAQ,GAAG;AACjC,sBAAM,SAAS,uBAAuB,QAAQ;AAC9C,oBAAI,QAAQ;AACV,wBAAM,gBAAgB,oBAAoB,QAAQ;AAClD,sBAAI,kBAAkB,QAAQ;AAC5B,oCAAgB;AAChB,iCAAa,SAAS;AACtB,wBAAI,oBAAoB;AAAE,mCAAa,kBAAkB;AAAG,2CAAqB;AAAA,oBAAW;AAC5F,oBAAAA,QAAO,KAAK,wEAAwE;AAAA,sBAClF;AAAA,sBAAW,UAAU,OAAO,SAAS,MAAM,GAAG,EAAE;AAAA,sBAChD,aAAa,OAAO,QAAQ;AAAA,oBAC9B,CAAC;AACD,4BAAQ,gBAAgB;AAAA,sBACtB,MAAM;AAAA,sBACN,SAAS,OAAO;AAAA,sBAChB,SAAS,OAAO;AAAA,oBAClB,CAAC,EAAE,KAAK,CAAC,aAAa;AACpB,sCAAgB;AAChB,0BAAI,CAAC,YAAY,UAAU;AACzB,6BAAK,gBAAgB,MAAM,WAAW,WAAW,IAAI;AAAA,sBACvD;AAAA,oBACF,CAAC,EAAE,MAAM,CAAC,QAAQ;AAChB,sBAAAA,QAAO,KAAK,iDAAiD;AAAA,wBAC3D,OAAQ,IAAc;AAAA,sBACxB,CAAC;AACD,sCAAgB;AAAA,oBAClB,CAAC;AACD;AAAA,kBACF;AAIA,sBAAI,CAAC,oBAAoB;AACvB,0CAAsB;AACtB,oBAAAA,QAAO,KAAK,iEAAiE;AAAA,sBAC3E;AAAA,sBAAW,UAAU,OAAO,SAAS,MAAM,GAAG,EAAE;AAAA,oBAClD,CAAC;AACD,yCAAqB,WAAW,MAAM;AACpC,2CAAqB;AACrB,0BAAI,YAAY,iBAAiB,CAAC,oBAAqB;AACvD,sCAAgB;AAChB,mCAAa,SAAS;AACtB,4BAAM,KAAK;AACX,4CAAsB;AACtB,sBAAAA,QAAO,KAAK,mDAA8C,EAAE,UAAU,CAAC;AACvE,8BAAQ,gBAAiB;AAAA,wBACvB,MAAM;AAAA,wBACN,SAAS,GAAG;AAAA,wBACZ,SAAS,GAAG;AAAA,sBACd,CAAC,EAAE,KAAK,CAAC,aAAa;AACpB,wCAAgB;AAChB,4BAAI,CAAC,YAAY,UAAU;AACzB,+BAAK,gBAAgB,MAAM,WAAW,WAAW,IAAI;AAAA,wBACvD;AAAA,sBACF,CAAC,EAAE,MAAM,CAAC,QAAQ;AAChB,wBAAAA,QAAO,KAAK,qDAAqD;AAAA,0BAC/D,OAAQ,IAAc;AAAA,wBACxB,CAAC;AACD,wCAAgB;AAAA,sBAClB,CAAC;AAAA,oBACH,GAAG,iBAAiB;AAAA,kBACtB;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAIA,gBAAI,cAAe,cAAa,aAAa;AAC7C,kBAAM,mBAAmB,MAAM;AAC7B,8BAAgB,WAAW,MAAM;AAC/B,oBAAI,SAAU;AACd,sBAAM,mBAAmB,KAAK,IAAI,IAAI;AACtC,oBAAI,mBAAmB,KAAO;AAE5B,mCAAiB;AACjB;AAAA,gBACF;AAGA,sBAAM,gBAAgB,QAAQ,gBAAgB,QAAQ,cAAc,IAAI;AACxE,oBAAI,CAAC,eAAe;AAClB,kBAAAA,QAAO,KAAK,oEAAoE;AAAA,oBAC9E;AAAA,kBACF,CAAC;AACD,mCAAiB;AACjB;AAAA,gBACF;AAEA,uBAAO;AAAA,kBACL,QAAQ,YAAY,KAAK,EAAE;AAAA,kBAC3B,UAAU;AAAA,kBACV,aAAa;AAAA,gBACf,CAAC;AAAA,cACH,GAAG,GAAK;AAAA,YACV;AACA,6BAAiB;AAAA,UACnB;AAAA,QACF;AAAA,MACF,CAAC;AAKD,WAAK,gBAAgB,OAAO,WAAW,CAAC,aAAqB;AAC3D,cAAM,YAAY,KAAK,eAAe,OAAO,SAAS;AAGtD,mBAAW,CAAC,IAAI,IAAI,KAAK,KAAK,UAAU;AACtC,cAAI,KAAK,cAAc,WAAW;AAChC,iBAAK,SAAS,OAAO,EAAE;AACvB;AAAA,UACF;AAAA,QACF;AAEA,YAAI,CAAC,UAAU;AACb,UAAAA,QAAO,KAAK,mCAAmC,EAAE,WAAW,UAAU,UAAU,CAAC;AACjF,iBAAO;AAAA,YACL,QAAQ,YAAY,KAAK,EAAE;AAAA,YAC3B,UAAU;AAAA,YACV,aAAa,YAAY,eAAe;AAAA,UAC1C,CAAC;AAAA,QACH,OAAO;AAIL,UAAAA,QAAO,KAAK,sDAAsD,EAAE,WAAW,SAAS,CAAC;AAAA,QAC3F;AAAA,MACF,CAAC;AAED,YAAM,UAAU,MAAM;AACpB,qBAAa,SAAS;AACtB,sBAAc,SAAS;AACvB,YAAI,cAAe,cAAa,aAAa;AAC7C,YAAI,mBAAoB,cAAa,kBAAkB;AACvD,qBAAa,QAAQ;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":["fs","path","logger","logger","result","path","fs"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DependencyChecker
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DVNAH2GV.js";
|
|
4
4
|
import {
|
|
5
5
|
ConfigGenerator,
|
|
6
6
|
PreflightChecker
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-JMACM7AJ.js";
|
|
8
8
|
import {
|
|
9
9
|
getProjectKnowledge
|
|
10
10
|
} from "./chunk-ACVOOHAR.js";
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-B7TVVODN.js";
|
|
15
15
|
import {
|
|
16
16
|
resolveConfigFilePath
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-FJTZKAJA.js";
|
|
18
18
|
|
|
19
19
|
// src/web/routes/setup.ts
|
|
20
20
|
import express from "express";
|
|
@@ -172,7 +172,7 @@ var E2ESetupRunner = class {
|
|
|
172
172
|
const step = "reload";
|
|
173
173
|
yield { step, status: "running", message: "Reloading configuration..." };
|
|
174
174
|
try {
|
|
175
|
-
const { reloadConfig } = await import("./config-
|
|
175
|
+
const { reloadConfig } = await import("./config-23TBYFP5.js");
|
|
176
176
|
reloadConfig();
|
|
177
177
|
yield { step, status: "done", message: "Configuration reloaded" };
|
|
178
178
|
} catch (err) {
|
|
@@ -468,8 +468,8 @@ function createSetupRouter(deps = {}) {
|
|
|
468
468
|
sse.write({ step: "collected", message: "Static info collected" });
|
|
469
469
|
sse.write({ step: "analyzing", message: "Running AI analysis..." });
|
|
470
470
|
try {
|
|
471
|
-
const { loadConfig } = await import("./config-
|
|
472
|
-
const { createAIRunner } = await import("./ai-runner-
|
|
471
|
+
const { loadConfig } = await import("./config-23TBYFP5.js");
|
|
472
|
+
const { createAIRunner } = await import("./ai-runner-TOHVJJ76.js");
|
|
473
473
|
const config = loadConfig();
|
|
474
474
|
const runner = createAIRunner(config.ai);
|
|
475
475
|
const knowledge = await analyze({ workDir, aiRunner: runner });
|
|
@@ -549,4 +549,4 @@ function createSetupRouter(deps = {}) {
|
|
|
549
549
|
export {
|
|
550
550
|
createSetupRouter
|
|
551
551
|
};
|
|
552
|
-
//# sourceMappingURL=chunk-
|
|
552
|
+
//# sourceMappingURL=chunk-5JBADEKR.js.map
|
|
@@ -4,16 +4,18 @@ import {
|
|
|
4
4
|
GitOperations,
|
|
5
5
|
braindumpTaskToExecutableTask,
|
|
6
6
|
eventBus
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GPZX4DSY.js";
|
|
8
|
+
import {
|
|
9
|
+
createAIRunner
|
|
10
|
+
} from "./chunk-TFEPHOVE.js";
|
|
8
11
|
import {
|
|
9
|
-
createAIRunner,
|
|
10
12
|
isShuttingDown
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-G7QI5WDI.js";
|
|
12
14
|
import {
|
|
13
15
|
AIOutputParseError,
|
|
14
16
|
BatchNotFoundError,
|
|
15
17
|
TaskNotFoundError
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-2RWGZPNF.js";
|
|
17
19
|
import {
|
|
18
20
|
logger
|
|
19
21
|
} from "./chunk-GF2RRYHB.js";
|
|
@@ -657,4 +659,4 @@ export {
|
|
|
657
659
|
BraindumpOrchestrator,
|
|
658
660
|
BraindumpTracker
|
|
659
661
|
};
|
|
660
|
-
//# sourceMappingURL=chunk-
|
|
662
|
+
//# sourceMappingURL=chunk-5M5SB6ZA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/braindump/BraindumpOrchestrator.ts","../src/braindump/prompts/split-prompt.ts","../src/braindump/TaskSplitter.ts","../src/braindump/MergeQueue.ts","../src/braindump/prompts/task-prompt.ts","../src/braindump/BraindumpTracker.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport { Config, AIRunnerMode } from '../config.js';\nimport { GitOperations } from '../git/GitOperations.js';\nimport { AsyncMutex } from '../utils/AsyncMutex.js';\nimport { createAIRunner, type AIRunner, type StreamEvent } from '../ai-runner/index.js';\nimport { eventBus } from '../events/EventBus.js';\nimport { isShuttingDown } from '../shutdown/ShutdownSignal.js';\nimport { logger as rootLogger } from '../logger.js';\nimport {\n BatchStatus,\n TaskStatus,\n type BraindumpBatch,\n type BraindumpTask,\n type SplitResult,\n} from './BraindumpState.js';\nimport { BraindumpTracker } from './BraindumpTracker.js';\nimport { TaskSplitter } from './TaskSplitter.js';\nimport { MergeQueue } from './MergeQueue.js';\nimport { buildTaskPrompt } from './prompts/task-prompt.js';\n\nconst logger = rootLogger.child('BraindumpOrchestrator');\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport interface BatchCreateOptions {\n targetBranch?: string;\n maxConcurrent?: number;\n defaultAiRunnerMode?: AIRunnerMode;\n}\n\nexport class BraindumpOrchestrator {\n private config: Config;\n private mainGit: GitOperations;\n private mainGitMutex: AsyncMutex;\n private defaultAiRunner: AIRunner;\n private tracker: BraindumpTracker;\n private mergeQueue: MergeQueue;\n private activeBatches = new Set<string>();\n\n constructor(\n config: Config,\n mainGit: GitOperations,\n mainGitMutex: AsyncMutex,\n defaultAiRunner: AIRunner,\n tracker: BraindumpTracker,\n ) {\n this.config = config;\n this.mainGit = mainGit;\n this.mainGitMutex = mainGitMutex;\n this.defaultAiRunner = defaultAiRunner;\n this.tracker = tracker;\n\n this.mergeQueue = new MergeQueue({\n mainGit,\n mainGitMutex,\n aiRunner: defaultAiRunner,\n tracker,\n worktreeBaseDir: config.project.worktreeBaseDir || config.project.gitRootDir,\n projectSubDir: config.project.projectSubDir,\n phaseTimeoutMs: config.braindump.taskTimeoutMs,\n maxConflictAttempts: config.braindump.maxConflictAttempts,\n });\n }\n\n getTracker(): BraindumpTracker {\n return this.tracker;\n }\n\n /** Replace the default AI runner (used by config hot-reload) */\n setAIRunner(runner: AIRunner): void {\n this.defaultAiRunner = runner;\n logger.info('BraindumpOrchestrator AIRunner replaced via hot-reload');\n }\n\n // ── Batch lifecycle ──\n\n createBatch(rawInput: string, options?: BatchCreateOptions): BraindumpBatch {\n const id = crypto.randomUUID();\n const targetBranch = options?.targetBranch || this.config.project.baseBranch;\n const batchShort = id.slice(0, 8);\n\n const batch: BraindumpBatch = {\n id,\n rawInput,\n targetBranch,\n integrationBranch: `braindump/${batchShort}/integration`,\n status: BatchStatus.Draft,\n tasks: [],\n maxConcurrent: options?.maxConcurrent || this.config.braindump.maxConcurrent,\n defaultAiRunnerMode: options?.defaultAiRunnerMode || this.config.ai.mode,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n return this.tracker.createBatch(batch);\n }\n\n async splitBatch(\n batchId: string,\n onEvent?: (event: StreamEvent) => void,\n ): Promise<BraindumpBatch> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n this.tracker.updateBatchStatus(batchId, BatchStatus.Splitting);\n\n try {\n const splitter = new TaskSplitter(this.defaultAiRunner);\n const result: SplitResult = await splitter.split(\n batch.rawInput,\n this.config.project.workDir,\n this.config.braindump.splitTimeoutMs,\n onEvent,\n );\n\n const batchShort = batchId.slice(0, 8);\n const tasks: BraindumpTask[] = result.tasks.map((t, i) => ({\n id: crypto.randomUUID(),\n index: i,\n title: t.title,\n description: t.description,\n dependsOn: t.dependsOn.map(depIdx => {\n // Convert index-based dependencies to task IDs (we'll set them below)\n return String(depIdx);\n }),\n branchName: `braindump/${batchShort}/task-${i}`,\n status: TaskStatus.Pending,\n attempts: 0,\n }));\n\n // Resolve index-based dependsOn to actual task IDs\n for (const task of tasks) {\n task.dependsOn = task.dependsOn\n .map(depIdxStr => {\n const depIdx = parseInt(depIdxStr, 10);\n return tasks[depIdx]?.id;\n })\n .filter((id): id is string => !!id);\n\n // Mark tasks with dependencies as blocked\n if (task.dependsOn.length > 0) {\n task.status = TaskStatus.Blocked;\n }\n }\n\n this.tracker.updateBatch(batchId, {\n tasks,\n status: BatchStatus.WaitingConfirm,\n });\n\n eventBus.emitTyped('braindump:split:done', { batchId, taskCount: tasks.length });\n logger.info('Batch split completed', { batchId, taskCount: tasks.length });\n\n return this.tracker.getBatch(batchId)!;\n } catch (err) {\n const errorMsg = (err as Error).message;\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, errorMsg);\n throw err;\n }\n }\n\n confirmBatch(batchId: string, tasks?: BraindumpTask[]): void {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n if (batch.status !== BatchStatus.WaitingConfirm) {\n throw new Error(`Batch ${batchId} is not waiting for confirmation (status: ${batch.status})`);\n }\n\n if (tasks) {\n this.tracker.updateBatch(batchId, { tasks });\n }\n eventBus.emitTyped('braindump:confirmed', { batchId });\n }\n\n async executeBatch(batchId: string): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n if (this.activeBatches.has(batchId)) {\n throw new Error(`Batch ${batchId} is already being executed`);\n }\n\n this.activeBatches.add(batchId);\n this.tracker.updateBatchStatus(batchId, BatchStatus.Running);\n\n try {\n // 1. Create integration branch from target\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n const exists = await this.mainGit.branchExists(batch.integrationBranch);\n if (!exists) {\n await this.mainGit.createBranch(batch.integrationBranch, batch.targetBranch);\n await this.mainGit.push(batch.integrationBranch);\n // Switch back to original branch\n await this.mainGit.checkout(this.config.project.baseBranch);\n }\n });\n\n // 2. Scheduling loop\n const runningTasks = new Map<string, Promise<void>>();\n\n while (!isShuttingDown()) {\n const currentBatch = this.tracker.getBatch(batchId);\n if (!currentBatch || currentBatch.status === BatchStatus.Failed) break;\n\n // Check if all tasks are in terminal state\n const allTerminal = currentBatch.tasks.every(\n t => t.status === TaskStatus.Merged || t.status === TaskStatus.Failed,\n );\n if (allTerminal) break;\n\n // Unblock tasks whose dependencies are met\n this.unblockReadyTasks(batchId);\n\n // Get ready tasks (pending + not running)\n const ready = currentBatch.tasks.filter(\n t => t.status === TaskStatus.Pending && !runningTasks.has(t.id),\n );\n const available = currentBatch.maxConcurrent - runningTasks.size;\n\n // Start new tasks\n for (const task of ready.slice(0, available)) {\n const promise = this.executeTask(batchId, task).finally(() => {\n runningTasks.delete(task.id);\n });\n runningTasks.set(task.id, promise);\n }\n\n await sleep(2000);\n }\n\n // Wait for remaining running tasks\n if (runningTasks.size > 0) {\n await Promise.allSettled([...runningTasks.values()]);\n }\n\n // Determine final batch status\n const finalBatch = this.tracker.getBatch(batchId)!;\n const allMerged = finalBatch.tasks.every(t => t.status === TaskStatus.Merged);\n const anyFailed = finalBatch.tasks.some(t => t.status === TaskStatus.Failed);\n\n if (allMerged) {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Completed);\n eventBus.emitTyped('braindump:completed', { batchId });\n logger.info('Batch completed successfully', { batchId });\n } else if (anyFailed) {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, 'Some tasks failed');\n eventBus.emitTyped('braindump:failed', { batchId });\n logger.warn('Batch completed with failures', { batchId });\n }\n } catch (err) {\n const errorMsg = (err as Error).message;\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, errorMsg);\n eventBus.emitTyped('braindump:failed', { batchId, error: errorMsg });\n logger.error('Batch execution failed', { batchId, error: errorMsg });\n } finally {\n this.activeBatches.delete(batchId);\n }\n }\n\n async cancelBatch(batchId: string): Promise<void> {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, 'Cancelled by user');\n this.activeBatches.delete(batchId);\n }\n\n async retryFailed(batchId: string): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n for (const task of batch.tasks) {\n if (task.status === TaskStatus.Failed) {\n task.status = TaskStatus.Pending;\n task.lastError = undefined;\n }\n }\n this.tracker.updateBatch(batchId, { tasks: batch.tasks });\n await this.executeBatch(batchId);\n }\n\n recoverInterrupted(): number {\n return this.tracker.recoverInterrupted();\n }\n\n // ── Private ──\n\n private unblockReadyTasks(batchId: string): void {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) return;\n\n for (const task of batch.tasks) {\n if (task.status !== TaskStatus.Blocked) continue;\n\n const allDepsMet = task.dependsOn.every(depId => {\n const dep = batch.tasks.find(t => t.id === depId);\n return dep && dep.status === TaskStatus.Merged;\n });\n\n if (allDepsMet) {\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Pending);\n }\n }\n }\n\n private async executeTask(batchId: string, task: BraindumpTask): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) return;\n\n logger.info('Starting task execution', { batchId, taskId: task.id, title: task.title });\n\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Running, {\n startedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:started', { batchId, taskId: task.id, title: task.title });\n\n const batchShort = batchId.slice(0, 8);\n const worktreeBaseDir = this.config.project.worktreeBaseDir || this.config.project.gitRootDir;\n const taskWorktreeDir = `${worktreeBaseDir}/braindump-${batchShort}-task-${task.index}`;\n const taskWorkDir = this.config.project.projectSubDir\n ? `${taskWorktreeDir}/${this.config.project.projectSubDir}`\n : taskWorktreeDir;\n\n try {\n // 1. Create worktree from integration branch\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n const worktrees = await this.mainGit.worktreeList();\n if (!worktrees.includes(taskWorktreeDir)) {\n await this.mainGit.worktreeAdd(\n taskWorktreeDir,\n task.branchName,\n `origin/${batch.integrationBranch}`,\n );\n }\n });\n\n // 2. Run AI\n const aiRunner = this.getRunnerForTask(task);\n const prompt = buildTaskPrompt(task.title, task.description);\n\n await aiRunner.run({\n prompt,\n workDir: taskWorkDir,\n timeoutMs: this.config.braindump.taskTimeoutMs,\n idleTimeoutMs: this.config.ai.idleTimeoutMs,\n onStreamEvent: (event: StreamEvent) => {\n eventBus.emitTyped('agent:output', {\n batchId,\n taskId: task.id,\n phase: 'task-execute',\n event,\n });\n },\n });\n\n // 3. Commit changes\n const wtGit = new GitOperations(taskWorktreeDir);\n if (await wtGit.hasChanges()) {\n await wtGit.add(['.']);\n await wtGit.commit(`braindump: ${task.title}`);\n await wtGit.push(task.branchName);\n }\n\n // 4. Mark done\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Done, {\n completedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:completed', { batchId, taskId: task.id });\n logger.info('Task execution completed', { batchId, taskId: task.id });\n\n // 5. Enqueue for merge\n await this.mergeQueue.enqueue(batchId, {\n ...task,\n status: TaskStatus.Done,\n completedAt: new Date().toISOString(),\n });\n } catch (err) {\n const errorMsg = (err as Error).message;\n logger.error('Task execution failed', { batchId, taskId: task.id, error: errorMsg });\n this.tracker.markTaskFailed(batchId, task.id, errorMsg);\n }\n }\n\n private getRunnerForTask(task: BraindumpTask): AIRunner {\n const mode = task.aiRunnerMode || this.config.ai.mode;\n if (mode === this.config.ai.mode) return this.defaultAiRunner;\n return createAIRunner({\n mode,\n binary: this.config.ai.binary,\n phaseTimeoutMs: this.config.braindump.taskTimeoutMs,\n nvmNodeVersion: this.config.ai.nvmNodeVersion,\n model: this.config.ai.model,\n });\n }\n}\n","/**\n * Build the prompt for AI task splitting.\n * The AI should browse the codebase, analyze the user's input,\n * and output a structured JSON with independent tasks.\n */\nexport function buildSplitPrompt(rawInput: string): string {\n return `你是一个项目管理专家和代码架构师。用户输入了一段包含多个想法/特性/任务的文本内容。\n\n你的任务:\n1. 先浏览项目代码库,了解项目结构和技术栈\n2. 分析用户输入的内容,识别出所有独立的特性或任务\n3. 为每个任务生成清晰的标题、详细描述和依赖关系\n4. 标注哪些任务可以并行执行(没有依赖关系的任务)\n\n## 用户输入\n\n${rawInput}\n\n## 输出要求\n\n请严格按以下 JSON 格式输出(用 \\`\\`\\`json 包裹):\n\n\\`\\`\\`json\n{\n \"tasks\": [\n {\n \"title\": \"简洁的任务标题\",\n \"description\": \"详细的任务描述,包含:\\\\n1. 需要修改哪些文件\\\\n2. 具体要做什么改动\\\\n3. 验收标准\",\n \"dependsOn\": [],\n \"estimatedComplexity\": \"low\"\n },\n {\n \"title\": \"另一个任务\",\n \"description\": \"这个任务依赖第一个任务的完成\",\n \"dependsOn\": [0],\n \"estimatedComplexity\": \"medium\"\n }\n ]\n}\n\\`\\`\\`\n\n## 注意事项\n\n- \\`dependsOn\\` 中填写的是任务在数组中的索引(从 0 开始),表示本任务依赖哪些任务先完成\n- 如果两个任务修改不同文件且逻辑独立,它们之间不应有依赖关系(可并行)\n- 如果两个任务修改相同文件或存在逻辑先后顺序,应设置依赖关系\n- \\`estimatedComplexity\\` 可选值:low(简单改动)、medium(中等复杂度)、high(复杂重构)\n- 每个任务的描述要足够详细,让 AI Agent 能独立完成该任务\n- 任务粒度适中:不要太大(一个任务不应包含多个独立变更),也不要太小(一行代码的改动不需要单独一个任务)`;\n}\n","import type { AIRunner, RunResult, StreamEvent } from '../ai-runner/index.js';\nimport type { SplitResult } from './BraindumpState.js';\nimport { buildSplitPrompt } from './prompts/split-prompt.js';\nimport { AIOutputParseError } from '../errors/index.js';\nimport { logger as rootLogger } from '../logger.js';\n\nconst logger = rootLogger.child('TaskSplitter');\n\nexport class TaskSplitter {\n constructor(private aiRunner: AIRunner) {}\n\n async split(\n rawInput: string,\n workDir: string,\n timeoutMs: number,\n onEvent?: (event: StreamEvent) => void,\n ): Promise<SplitResult> {\n logger.info('Starting task split', { inputLength: rawInput.length });\n\n const prompt = buildSplitPrompt(rawInput);\n const result: RunResult = await this.aiRunner.run({\n prompt,\n workDir,\n timeoutMs,\n onStreamEvent: onEvent,\n });\n\n return this.parseSplitOutput(result.output);\n }\n\n private parseSplitOutput(output: string): SplitResult {\n // Extract JSON block from AI output (wrapped in ```json ... ```)\n const jsonMatch = output.match(/```json\\s*\\n([\\s\\S]*?)\\n\\s*```/);\n if (!jsonMatch) {\n // Try parsing the entire output as JSON\n try {\n return this.validateResult(JSON.parse(output));\n } catch {\n throw new AIOutputParseError(\n 'Failed to parse task split output: no JSON block found in AI response',\n output,\n );\n }\n }\n\n try {\n return this.validateResult(JSON.parse(jsonMatch[1]));\n } catch (err) {\n throw new AIOutputParseError(`Failed to parse task split JSON: ${(err as Error).message}`, jsonMatch[1]);\n }\n }\n\n private validateResult(raw: unknown): SplitResult {\n if (!raw || typeof raw !== 'object') {\n throw new AIOutputParseError('Split result is not an object');\n }\n\n const obj = raw as Record<string, unknown>;\n if (!Array.isArray(obj.tasks)) {\n throw new AIOutputParseError('Split result missing \"tasks\" array');\n }\n\n const tasks = obj.tasks.map((t: Record<string, unknown>, i: number) => ({\n title: String(t.title || `Task ${i + 1}`),\n description: String(t.description || ''),\n dependsOn: Array.isArray(t.dependsOn)\n ? t.dependsOn.map(Number).filter(n => !isNaN(n))\n : [],\n estimatedComplexity: (['low', 'medium', 'high'].includes(String(t.estimatedComplexity))\n ? String(t.estimatedComplexity)\n : 'medium') as 'low' | 'medium' | 'high',\n }));\n\n logger.info('Parsed split result', { taskCount: tasks.length });\n return { tasks };\n }\n}\n","import { GitOperations } from '../git/GitOperations.js';\nimport { ConflictResolver } from '../git/ConflictResolver.js';\nimport { AsyncMutex } from '../utils/AsyncMutex.js';\nimport type { AIRunner } from '../ai-runner/index.js';\nimport { eventBus } from '../events/EventBus.js';\nimport { logger as rootLogger } from '../logger.js';\nimport type { BraindumpTask } from './BraindumpState.js';\nimport { TaskStatus } from './BraindumpState.js';\nimport { BraindumpTracker } from './BraindumpTracker.js';\n\nconst logger = rootLogger.child('MergeQueue');\n\ninterface MergeRequest {\n batchId: string;\n task: BraindumpTask;\n resolve: () => void;\n reject: (err: Error) => void;\n}\n\n/**\n * Serial merge queue: tasks are merged one at a time into the integration branch.\n * This ensures each merge sees the latest integration state and minimizes conflicts.\n */\nexport class MergeQueue {\n private queue: MergeRequest[] = [];\n private processing = false;\n private mainGit: GitOperations;\n private mainGitMutex: AsyncMutex;\n private conflictResolver: ConflictResolver;\n private tracker: BraindumpTracker;\n private worktreeBaseDir: string;\n private projectSubDir: string;\n private phaseTimeoutMs: number;\n private maxConflictAttempts: number;\n\n constructor(opts: {\n mainGit: GitOperations;\n mainGitMutex: AsyncMutex;\n aiRunner: AIRunner;\n tracker: BraindumpTracker;\n worktreeBaseDir: string;\n projectSubDir: string;\n phaseTimeoutMs: number;\n maxConflictAttempts: number;\n }) {\n this.mainGit = opts.mainGit;\n this.mainGitMutex = opts.mainGitMutex;\n this.conflictResolver = new ConflictResolver(opts.aiRunner);\n this.tracker = opts.tracker;\n this.worktreeBaseDir = opts.worktreeBaseDir;\n this.projectSubDir = opts.projectSubDir;\n this.phaseTimeoutMs = opts.phaseTimeoutMs;\n this.maxConflictAttempts = opts.maxConflictAttempts;\n }\n\n /** Enqueue a completed task for merging. Returns a promise that resolves when merge is done. */\n enqueue(batchId: string, task: BraindumpTask): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.queue.push({ batchId, task, resolve, reject });\n this.processNext();\n });\n }\n\n get pendingCount(): number {\n return this.queue.length;\n }\n\n private async processNext(): Promise<void> {\n if (this.processing || this.queue.length === 0) return;\n this.processing = true;\n\n // Sort by task index to ensure deterministic merge order\n this.queue.sort((a, b) => a.task.index - b.task.index);\n const req = this.queue.shift()!;\n\n try {\n await this.mergeTask(req.batchId, req.task);\n req.resolve();\n } catch (err) {\n req.reject(err as Error);\n } finally {\n this.processing = false;\n // Process next item if any\n if (this.queue.length > 0) {\n this.processNext();\n }\n }\n }\n\n private async mergeTask(batchId: string, task: BraindumpTask): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n logger.info('Merging task into integration branch', {\n batchId, taskId: task.id, taskIndex: task.index, branch: task.branchName,\n });\n\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Merging);\n eventBus.emitTyped('braindump:task:merging', { batchId, taskId: task.id });\n\n const taskWorktreeDir = this.getTaskWorktreeDir(batchId, task.index);\n const taskWorkDir = this.projectSubDir\n ? `${taskWorktreeDir}/${this.projectSubDir}`\n : taskWorktreeDir;\n\n try {\n const wtGit = new GitOperations(taskWorktreeDir);\n\n // 1. Checkout the task branch\n await wtGit.checkout(task.branchName);\n\n // 2. Rebase onto integration branch (with AI conflict resolution)\n await this.conflictResolver.resolve({\n wtGit,\n targetRef: `origin/${batch.integrationBranch}`,\n workDir: taskWorkDir,\n branchName: task.branchName,\n contextId: task.id,\n phaseTimeoutMs: this.phaseTimeoutMs,\n maxAttempts: this.maxConflictAttempts,\n onEvent: (event) => {\n eventBus.emitTyped('agent:output', {\n batchId,\n taskId: task.id,\n phase: 'merge-conflict-resolve',\n event,\n });\n },\n });\n\n // 3. Force push the rebased task branch\n await wtGit.forcePush(task.branchName);\n\n // 4. Merge into integration branch (in main repo under mutex)\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n await this.mainGit.checkout(batch.integrationBranch);\n try {\n await this.mainGit.mergeFF(task.branchName);\n } catch {\n // If ff not possible, do a regular merge\n await this.mainGit.merge(task.branchName, `merge: braindump task #${task.index} - ${task.title}`);\n }\n await this.mainGit.push(batch.integrationBranch);\n });\n\n // 5. Update status\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Merged, {\n mergedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:merged', { batchId, taskId: task.id });\n\n logger.info('Task merged successfully', { batchId, taskId: task.id });\n } catch (err) {\n const errorMsg = (err as Error).message;\n logger.error('Task merge failed', { batchId, taskId: task.id, error: errorMsg });\n\n // Try to abort any in-progress rebase\n try {\n const wtGit = new GitOperations(taskWorktreeDir);\n if (await wtGit.isRebaseInProgress()) {\n await wtGit.rebaseAbort();\n }\n } catch { /* ignore */ }\n\n this.tracker.markTaskFailed(batchId, task.id, `Merge failed: ${errorMsg}`);\n throw err;\n }\n }\n\n private getTaskWorktreeDir(batchId: string, taskIndex: number): string {\n const batchShort = batchId.slice(0, 8);\n return `${this.worktreeBaseDir}/braindump-${batchShort}-task-${taskIndex}`;\n }\n}\n","/**\n * Build the prompt for executing a single braindump task.\n */\nexport function buildTaskPrompt(taskTitle: string, taskDescription: string): string {\n return `你需要完成以下任务:\n\n## 任务:${taskTitle}\n\n${taskDescription}\n\n## 要求\n\n1. 仔细阅读相关代码,理解现有架构\n2. 按照任务描述进行代码修改\n3. 确保修改不会破坏现有功能\n4. 遵循项目现有的代码风格和规范\n5. 完成后确保代码可以通过 lint 检查`;\n}\n","import { BatchStatus, TaskStatus, type BraindumpBatch, type BraindumpTask } from './BraindumpState.js';\nimport { BaseTracker } from '../tracker/BaseTracker.js';\nimport { type ExecutableTask, braindumpTaskToExecutableTask } from '../tracker/ExecutableTask.js';\nimport { BatchNotFoundError, TaskNotFoundError } from '../errors/index.js';\nimport { logger as rootLogger } from '../logger.js';\nimport { eventBus } from '../events/EventBus.js';\n\nconst logger = rootLogger.child('BraindumpTracker');\n\nexport class BraindumpTracker extends BaseTracker<BraindumpBatch> {\n constructor(dataDir: string) {\n super(dataDir, 'braindump-tracker.json', 'batches', 'braindump-tracker');\n }\n\n // ── CRUD ──\n\n getBatch(batchId: string): BraindumpBatch | undefined {\n return this.getByKey(batchId);\n }\n\n getAll(): BraindumpBatch[] {\n return this.getAllRecords();\n }\n\n createBatch(batch: BraindumpBatch): BraindumpBatch {\n this.setRecord(batch.id, batch);\n this.save();\n logger.info('Batch created', { batchId: batch.id });\n eventBus.emitTyped('braindump:created', { batchId: batch.id });\n return batch;\n }\n\n updateBatch(batchId: string, updates: Partial<BraindumpBatch>): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n Object.assign(batch, updates, { updatedAt: new Date().toISOString() });\n this.save();\n }\n\n deleteBatch(batchId: string): boolean {\n if (!this.deleteByKey(batchId)) return false;\n logger.info('Batch deleted', { batchId });\n return true;\n }\n\n // ── Status helpers ──\n\n updateBatchStatus(batchId: string, status: BatchStatus, error?: string): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n batch.status = status;\n batch.updatedAt = new Date().toISOString();\n if (error !== undefined) batch.lastError = error;\n if (status === BatchStatus.Completed) batch.completedAt = new Date().toISOString();\n this.save();\n }\n\n updateTaskStatus(batchId: string, taskId: string, status: TaskStatus, extra?: Partial<BraindumpTask>): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n const task = batch.tasks.find(t => t.id === taskId);\n if (!task) throw new TaskNotFoundError(taskId);\n task.status = status;\n if (extra) Object.assign(task, extra);\n batch.updatedAt = new Date().toISOString();\n this.save();\n }\n\n markTaskFailed(batchId: string, taskId: string, error: string): void {\n const batch = this.collection[batchId];\n if (!batch) return;\n const task = batch.tasks.find(t => t.id === taskId);\n if (!task) return;\n task.status = TaskStatus.Failed;\n task.lastError = error;\n task.attempts += 1;\n batch.updatedAt = new Date().toISOString();\n this.save();\n logger.warn('Task marked failed', { batchId, taskId, error, attempts: task.attempts });\n eventBus.emitTyped('braindump:task:failed', { batchId, taskId, error });\n }\n\n // ── Recovery ──\n\n /** Recover batches interrupted by service restart. Returns count of recovered batches. */\n recoverInterrupted(): number {\n const IN_PROGRESS_TASK_STATES = new Set([TaskStatus.Running, TaskStatus.Merging, TaskStatus.ConflictResolving]);\n let count = 0;\n\n for (const batch of this.getAllRecords()) {\n if (batch.status !== BatchStatus.Running && batch.status !== BatchStatus.Merging) continue;\n\n let recovered = false;\n for (const task of batch.tasks) {\n if (IN_PROGRESS_TASK_STATES.has(task.status)) {\n task.status = TaskStatus.Failed;\n task.lastError = 'Interrupted by service restart';\n task.attempts += 1;\n recovered = true;\n }\n }\n\n if (recovered) {\n // If any task is still pending/blocked/done, keep batch running for retry\n const hasRecoverable = batch.tasks.some(\n t => t.status === TaskStatus.Pending || t.status === TaskStatus.Blocked || t.status === TaskStatus.Done,\n );\n if (!hasRecoverable) {\n batch.status = BatchStatus.Failed;\n batch.lastError = 'Interrupted by service restart';\n }\n batch.updatedAt = new Date().toISOString();\n count++;\n }\n }\n\n if (count > 0) {\n this.save();\n logger.info('Recovered interrupted braindump batches', { count });\n }\n return count;\n }\n\n /** 将指定批次的所有任务投影为 ExecutableTask[] */\n toExecutableTasks(batchId: string): ExecutableTask[] {\n const batch = this.getByKey(batchId);\n if (!batch) return [];\n return batch.tasks.map((task) =>\n braindumpTaskToExecutableTask(task, batch),\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,YAAY;;;ACKZ,SAAS,iBAAiB,UAA0B;AACzD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUP,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCV;;;AC3CA,IAAMA,UAAS,OAAW,MAAM,cAAc;AAEvC,IAAM,eAAN,MAAmB;AAAA,EACxB,YAAoB,UAAoB;AAApB;AAAA,EAAqB;AAAA,EAEzC,MAAM,MACJ,UACA,SACA,WACA,SACsB;AACtB,IAAAA,QAAO,KAAK,uBAAuB,EAAE,aAAa,SAAS,OAAO,CAAC;AAEnE,UAAM,SAAS,iBAAiB,QAAQ;AACxC,UAAM,SAAoB,MAAM,KAAK,SAAS,IAAI;AAAA,MAChD;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe;AAAA,IACjB,CAAC;AAED,WAAO,KAAK,iBAAiB,OAAO,MAAM;AAAA,EAC5C;AAAA,EAEQ,iBAAiB,QAA6B;AAEpD,UAAM,YAAY,OAAO,MAAM,gCAAgC;AAC/D,QAAI,CAAC,WAAW;AAEd,UAAI;AACF,eAAO,KAAK,eAAe,KAAK,MAAM,MAAM,CAAC;AAAA,MAC/C,QAAQ;AACN,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACF,aAAO,KAAK,eAAe,KAAK,MAAM,UAAU,CAAC,CAAC,CAAC;AAAA,IACrD,SAAS,KAAK;AACZ,YAAM,IAAI,mBAAmB,oCAAqC,IAAc,OAAO,IAAI,UAAU,CAAC,CAAC;AAAA,IACzG;AAAA,EACF;AAAA,EAEQ,eAAe,KAA2B;AAChD,QAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,YAAM,IAAI,mBAAmB,+BAA+B;AAAA,IAC9D;AAEA,UAAM,MAAM;AACZ,QAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,GAAG;AAC7B,YAAM,IAAI,mBAAmB,oCAAoC;AAAA,IACnE;AAEA,UAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,GAA4B,OAAe;AAAA,MACtE,OAAO,OAAO,EAAE,SAAS,QAAQ,IAAI,CAAC,EAAE;AAAA,MACxC,aAAa,OAAO,EAAE,eAAe,EAAE;AAAA,MACvC,WAAW,MAAM,QAAQ,EAAE,SAAS,IAChC,EAAE,UAAU,IAAI,MAAM,EAAE,OAAO,OAAK,CAAC,MAAM,CAAC,CAAC,IAC7C,CAAC;AAAA,MACL,qBAAsB,CAAC,OAAO,UAAU,MAAM,EAAE,SAAS,OAAO,EAAE,mBAAmB,CAAC,IAClF,OAAO,EAAE,mBAAmB,IAC5B;AAAA,IACN,EAAE;AAEF,IAAAA,QAAO,KAAK,uBAAuB,EAAE,WAAW,MAAM,OAAO,CAAC;AAC9D,WAAO,EAAE,MAAM;AAAA,EACjB;AACF;;;AClEA,IAAMC,UAAS,OAAW,MAAM,YAAY;AAarC,IAAM,aAAN,MAAiB;AAAA,EACd,QAAwB,CAAC;AAAA,EACzB,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,MAST;AACD,SAAK,UAAU,KAAK;AACpB,SAAK,eAAe,KAAK;AACzB,SAAK,mBAAmB,IAAI,iBAAiB,KAAK,QAAQ;AAC1D,SAAK,UAAU,KAAK;AACpB,SAAK,kBAAkB,KAAK;AAC5B,SAAK,gBAAgB,KAAK;AAC1B,SAAK,iBAAiB,KAAK;AAC3B,SAAK,sBAAsB,KAAK;AAAA,EAClC;AAAA;AAAA,EAGA,QAAQ,SAAiB,MAAoC;AAC3D,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAK,MAAM,KAAK,EAAE,SAAS,MAAM,SAAS,OAAO,CAAC;AAClD,WAAK,YAAY;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAEA,IAAI,eAAuB;AACzB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,MAAc,cAA6B;AACzC,QAAI,KAAK,cAAc,KAAK,MAAM,WAAW,EAAG;AAChD,SAAK,aAAa;AAGlB,SAAK,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,QAAQ,EAAE,KAAK,KAAK;AACrD,UAAM,MAAM,KAAK,MAAM,MAAM;AAE7B,QAAI;AACF,YAAM,KAAK,UAAU,IAAI,SAAS,IAAI,IAAI;AAC1C,UAAI,QAAQ;AAAA,IACd,SAAS,KAAK;AACZ,UAAI,OAAO,GAAY;AAAA,IACzB,UAAE;AACA,WAAK,aAAa;AAElB,UAAI,KAAK,MAAM,SAAS,GAAG;AACzB,aAAK,YAAY;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,UAAU,SAAiB,MAAoC;AAC3E,UAAM,QAAQ,KAAK,QAAQ,SAAS,OAAO;AAC3C,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,SAAS,OAAO,YAAY;AAExD,IAAAA,QAAO,KAAK,wCAAwC;AAAA,MAClD;AAAA,MAAS,QAAQ,KAAK;AAAA,MAAI,WAAW,KAAK;AAAA,MAAO,QAAQ,KAAK;AAAA,IAChE,CAAC;AAED,SAAK,QAAQ,iBAAiB,SAAS,KAAK,2BAAsB;AAClE,aAAS,UAAU,0BAA0B,EAAE,SAAS,QAAQ,KAAK,GAAG,CAAC;AAEzE,UAAM,kBAAkB,KAAK,mBAAmB,SAAS,KAAK,KAAK;AACnE,UAAM,cAAc,KAAK,gBACrB,GAAG,eAAe,IAAI,KAAK,aAAa,KACxC;AAEJ,QAAI;AACF,YAAM,QAAQ,IAAI,cAAc,eAAe;AAG/C,YAAM,MAAM,SAAS,KAAK,UAAU;AAGpC,YAAM,KAAK,iBAAiB,QAAQ;AAAA,QAClC;AAAA,QACA,WAAW,UAAU,MAAM,iBAAiB;AAAA,QAC5C,SAAS;AAAA,QACT,YAAY,KAAK;AAAA,QACjB,WAAW,KAAK;AAAA,QAChB,gBAAgB,KAAK;AAAA,QACrB,aAAa,KAAK;AAAA,QAClB,SAAS,CAAC,UAAU;AAClB,mBAAS,UAAU,gBAAgB;AAAA,YACjC;AAAA,YACA,QAAQ,KAAK;AAAA,YACb,OAAO;AAAA,YACP;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAGD,YAAM,MAAM,UAAU,KAAK,UAAU;AAGrC,YAAM,KAAK,aAAa,aAAa,YAAY;AAC/C,cAAM,KAAK,QAAQ,MAAM;AACzB,cAAM,KAAK,QAAQ,SAAS,MAAM,iBAAiB;AACnD,YAAI;AACF,gBAAM,KAAK,QAAQ,QAAQ,KAAK,UAAU;AAAA,QAC5C,QAAQ;AAEN,gBAAM,KAAK,QAAQ,MAAM,KAAK,YAAY,0BAA0B,KAAK,KAAK,MAAM,KAAK,KAAK,EAAE;AAAA,QAClG;AACA,cAAM,KAAK,QAAQ,KAAK,MAAM,iBAAiB;AAAA,MACjD,CAAC;AAGD,WAAK,QAAQ,iBAAiB,SAAS,KAAK,2BAAuB;AAAA,QACjE,WAAU,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,CAAC;AACD,eAAS,UAAU,yBAAyB,EAAE,SAAS,QAAQ,KAAK,GAAG,CAAC;AAExE,MAAAA,QAAO,KAAK,4BAA4B,EAAE,SAAS,QAAQ,KAAK,GAAG,CAAC;AAAA,IACtE,SAAS,KAAK;AACZ,YAAM,WAAY,IAAc;AAChC,MAAAA,QAAO,MAAM,qBAAqB,EAAE,SAAS,QAAQ,KAAK,IAAI,OAAO,SAAS,CAAC;AAG/E,UAAI;AACF,cAAM,QAAQ,IAAI,cAAc,eAAe;AAC/C,YAAI,MAAM,MAAM,mBAAmB,GAAG;AACpC,gBAAM,MAAM,YAAY;AAAA,QAC1B;AAAA,MACF,QAAQ;AAAA,MAAe;AAEvB,WAAK,QAAQ,eAAe,SAAS,KAAK,IAAI,iBAAiB,QAAQ,EAAE;AACzE,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEQ,mBAAmB,SAAiB,WAA2B;AACrE,UAAM,aAAa,QAAQ,MAAM,GAAG,CAAC;AACrC,WAAO,GAAG,KAAK,eAAe,cAAc,UAAU,SAAS,SAAS;AAAA,EAC1E;AACF;;;AC3KO,SAAS,gBAAgB,WAAmB,iBAAiC;AAClF,SAAO;AAAA;AAAA,uBAED,SAAS;AAAA;AAAA,EAEf,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASjB;;;AJGA,IAAMC,UAAS,OAAW,MAAM,uBAAuB;AAEvD,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAQO,IAAM,wBAAN,MAA4B;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB,oBAAI,IAAY;AAAA,EAExC,YACE,QACA,SACA,cACA,iBACA,SACA;AACA,SAAK,SAAS;AACd,SAAK,UAAU;AACf,SAAK,eAAe;AACpB,SAAK,kBAAkB;AACvB,SAAK,UAAU;AAEf,SAAK,aAAa,IAAI,WAAW;AAAA,MAC/B;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA,iBAAiB,OAAO,QAAQ,mBAAmB,OAAO,QAAQ;AAAA,MAClE,eAAe,OAAO,QAAQ;AAAA,MAC9B,gBAAgB,OAAO,UAAU;AAAA,MACjC,qBAAqB,OAAO,UAAU;AAAA,IACxC,CAAC;AAAA,EACH;AAAA,EAEA,aAA+B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,YAAY,QAAwB;AAClC,SAAK,kBAAkB;AACvB,IAAAA,QAAO,KAAK,wDAAwD;AAAA,EACtE;AAAA;AAAA,EAIA,YAAY,UAAkB,SAA8C;AAC1E,UAAM,KAAK,OAAO,WAAW;AAC7B,UAAM,eAAe,SAAS,gBAAgB,KAAK,OAAO,QAAQ;AAClE,UAAM,aAAa,GAAG,MAAM,GAAG,CAAC;AAEhC,UAAM,QAAwB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA,mBAAmB,aAAa,UAAU;AAAA,MAC1C;AAAA,MACA,OAAO,CAAC;AAAA,MACR,eAAe,SAAS,iBAAiB,KAAK,OAAO,UAAU;AAAA,MAC/D,qBAAqB,SAAS,uBAAuB,KAAK,OAAO,GAAG;AAAA,MACpE,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAEA,WAAO,KAAK,QAAQ,YAAY,KAAK;AAAA,EACvC;AAAA,EAEA,MAAM,WACJ,SACA,SACyB;AACzB,UAAM,QAAQ,KAAK,QAAQ,SAAS,OAAO;AAC3C,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,SAAS,OAAO,YAAY;AAExD,SAAK,QAAQ,kBAAkB,oCAA8B;AAE7D,QAAI;AACF,YAAM,WAAW,IAAI,aAAa,KAAK,eAAe;AACtD,YAAM,SAAsB,MAAM,SAAS;AAAA,QACzC,MAAM;AAAA,QACN,KAAK,OAAO,QAAQ;AAAA,QACpB,KAAK,OAAO,UAAU;AAAA,QACtB;AAAA,MACF;AAEA,YAAM,aAAa,QAAQ,MAAM,GAAG,CAAC;AACrC,YAAM,QAAyB,OAAO,MAAM,IAAI,CAAC,GAAG,OAAO;AAAA,QACzD,IAAI,OAAO,WAAW;AAAA,QACtB,OAAO;AAAA,QACP,OAAO,EAAE;AAAA,QACT,aAAa,EAAE;AAAA,QACf,WAAW,EAAE,UAAU,IAAI,YAAU;AAEnC,iBAAO,OAAO,MAAM;AAAA,QACtB,CAAC;AAAA,QACD,YAAY,aAAa,UAAU,SAAS,CAAC;AAAA,QAC7C;AAAA,QACA,UAAU;AAAA,MACZ,EAAE;AAGF,iBAAW,QAAQ,OAAO;AACxB,aAAK,YAAY,KAAK,UACnB,IAAI,eAAa;AAChB,gBAAM,SAAS,SAAS,WAAW,EAAE;AACrC,iBAAO,MAAM,MAAM,GAAG;AAAA,QACxB,CAAC,EACA,OAAO,CAAC,OAAqB,CAAC,CAAC,EAAE;AAGpC,YAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,eAAK;AAAA,QACP;AAAA,MACF;AAEA,WAAK,QAAQ,YAAY,SAAS;AAAA,QAChC;AAAA,QACA;AAAA,MACF,CAAC;AAED,eAAS,UAAU,wBAAwB,EAAE,SAAS,WAAW,MAAM,OAAO,CAAC;AAC/E,MAAAA,QAAO,KAAK,yBAAyB,EAAE,SAAS,WAAW,MAAM,OAAO,CAAC;AAEzE,aAAO,KAAK,QAAQ,SAAS,OAAO;AAAA,IACtC,SAAS,KAAK;AACZ,YAAM,WAAY,IAAc;AAChC,WAAK,QAAQ,kBAAkB,gCAA6B,QAAQ;AACpE,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,aAAa,SAAiB,OAA+B;AAC3D,UAAM,QAAQ,KAAK,QAAQ,SAAS,OAAO;AAC3C,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,SAAS,OAAO,YAAY;AACxD,QAAI,MAAM,mDAAuC;AAC/C,YAAM,IAAI,MAAM,SAAS,OAAO,6CAA6C,MAAM,MAAM,GAAG;AAAA,IAC9F;AAEA,QAAI,OAAO;AACT,WAAK,QAAQ,YAAY,SAAS,EAAE,MAAM,CAAC;AAAA,IAC7C;AACA,aAAS,UAAU,uBAAuB,EAAE,QAAQ,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,aAAa,SAAgC;AACjD,UAAM,QAAQ,KAAK,QAAQ,SAAS,OAAO;AAC3C,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,SAAS,OAAO,YAAY;AACxD,QAAI,KAAK,cAAc,IAAI,OAAO,GAAG;AACnC,YAAM,IAAI,MAAM,SAAS,OAAO,4BAA4B;AAAA,IAC9D;AAEA,SAAK,cAAc,IAAI,OAAO;AAC9B,SAAK,QAAQ,kBAAkB,gCAA4B;AAE3D,QAAI;AAEF,YAAM,KAAK,aAAa,aAAa,YAAY;AAC/C,cAAM,KAAK,QAAQ,MAAM;AACzB,cAAM,SAAS,MAAM,KAAK,QAAQ,aAAa,MAAM,iBAAiB;AACtE,YAAI,CAAC,QAAQ;AACX,gBAAM,KAAK,QAAQ,aAAa,MAAM,mBAAmB,MAAM,YAAY;AAC3E,gBAAM,KAAK,QAAQ,KAAK,MAAM,iBAAiB;AAE/C,gBAAM,KAAK,QAAQ,SAAS,KAAK,OAAO,QAAQ,UAAU;AAAA,QAC5D;AAAA,MACF,CAAC;AAGD,YAAM,eAAe,oBAAI,IAA2B;AAEpD,aAAO,CAAC,eAAe,GAAG;AACxB,cAAM,eAAe,KAAK,QAAQ,SAAS,OAAO;AAClD,YAAI,CAAC,gBAAgB,aAAa,iCAA+B;AAGjE,cAAM,cAAc,aAAa,MAAM;AAAA,UACrC,OAAK,EAAE,oCAAgC,EAAE;AAAA,QAC3C;AACA,YAAI,YAAa;AAGjB,aAAK,kBAAkB,OAAO;AAG9B,cAAM,QAAQ,aAAa,MAAM;AAAA,UAC/B,OAAK,EAAE,sCAAiC,CAAC,aAAa,IAAI,EAAE,EAAE;AAAA,QAChE;AACA,cAAM,YAAY,aAAa,gBAAgB,aAAa;AAG5D,mBAAW,QAAQ,MAAM,MAAM,GAAG,SAAS,GAAG;AAC5C,gBAAM,UAAU,KAAK,YAAY,SAAS,IAAI,EAAE,QAAQ,MAAM;AAC5D,yBAAa,OAAO,KAAK,EAAE;AAAA,UAC7B,CAAC;AACD,uBAAa,IAAI,KAAK,IAAI,OAAO;AAAA,QACnC;AAEA,cAAM,MAAM,GAAI;AAAA,MAClB;AAGA,UAAI,aAAa,OAAO,GAAG;AACzB,cAAM,QAAQ,WAAW,CAAC,GAAG,aAAa,OAAO,CAAC,CAAC;AAAA,MACrD;AAGA,YAAM,aAAa,KAAK,QAAQ,SAAS,OAAO;AAChD,YAAM,YAAY,WAAW,MAAM,MAAM,OAAK,EAAE,gCAA4B;AAC5E,YAAM,YAAY,WAAW,MAAM,KAAK,OAAK,EAAE,gCAA4B;AAE3E,UAAI,WAAW;AACb,aAAK,QAAQ,kBAAkB,oCAA8B;AAC7D,iBAAS,UAAU,uBAAuB,EAAE,QAAQ,CAAC;AACrD,QAAAA,QAAO,KAAK,gCAAgC,EAAE,QAAQ,CAAC;AAAA,MACzD,WAAW,WAAW;AACpB,aAAK,QAAQ,kBAAkB,gCAA6B,mBAAmB;AAC/E,iBAAS,UAAU,oBAAoB,EAAE,QAAQ,CAAC;AAClD,QAAAA,QAAO,KAAK,iCAAiC,EAAE,QAAQ,CAAC;AAAA,MAC1D;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,WAAY,IAAc;AAChC,WAAK,QAAQ,kBAAkB,gCAA6B,QAAQ;AACpE,eAAS,UAAU,oBAAoB,EAAE,SAAS,OAAO,SAAS,CAAC;AACnE,MAAAA,QAAO,MAAM,0BAA0B,EAAE,SAAS,OAAO,SAAS,CAAC;AAAA,IACrE,UAAE;AACA,WAAK,cAAc,OAAO,OAAO;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,SAAgC;AAChD,SAAK,QAAQ,kBAAkB,gCAA6B,mBAAmB;AAC/E,SAAK,cAAc,OAAO,OAAO;AAAA,EACnC;AAAA,EAEA,MAAM,YAAY,SAAgC;AAChD,UAAM,QAAQ,KAAK,QAAQ,SAAS,OAAO;AAC3C,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,SAAS,OAAO,YAAY;AAExD,eAAW,QAAQ,MAAM,OAAO;AAC9B,UAAI,KAAK,kCAA8B;AACrC,aAAK;AACL,aAAK,YAAY;AAAA,MACnB;AAAA,IACF;AACA,SAAK,QAAQ,YAAY,SAAS,EAAE,OAAO,MAAM,MAAM,CAAC;AACxD,UAAM,KAAK,aAAa,OAAO;AAAA,EACjC;AAAA,EAEA,qBAA6B;AAC3B,WAAO,KAAK,QAAQ,mBAAmB;AAAA,EACzC;AAAA;AAAA,EAIQ,kBAAkB,SAAuB;AAC/C,UAAM,QAAQ,KAAK,QAAQ,SAAS,OAAO;AAC3C,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,MAAM,OAAO;AAC9B,UAAI,KAAK,mCAA+B;AAExC,YAAM,aAAa,KAAK,UAAU,MAAM,WAAS;AAC/C,cAAM,MAAM,MAAM,MAAM,KAAK,OAAK,EAAE,OAAO,KAAK;AAChD,eAAO,OAAO,IAAI;AAAA,MACpB,CAAC;AAED,UAAI,YAAY;AACd,aAAK,QAAQ,iBAAiB,SAAS,KAAK,2BAAsB;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,YAAY,SAAiB,MAAoC;AAC7E,UAAM,QAAQ,KAAK,QAAQ,SAAS,OAAO;AAC3C,QAAI,CAAC,MAAO;AAEZ,IAAAA,QAAO,KAAK,2BAA2B,EAAE,SAAS,QAAQ,KAAK,IAAI,OAAO,KAAK,MAAM,CAAC;AAEtF,SAAK,QAAQ,iBAAiB,SAAS,KAAK,6BAAwB;AAAA,MAClE,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,CAAC;AACD,aAAS,UAAU,0BAA0B,EAAE,SAAS,QAAQ,KAAK,IAAI,OAAO,KAAK,MAAM,CAAC;AAE5F,UAAM,aAAa,QAAQ,MAAM,GAAG,CAAC;AACrC,UAAM,kBAAkB,KAAK,OAAO,QAAQ,mBAAmB,KAAK,OAAO,QAAQ;AACnF,UAAM,kBAAkB,GAAG,eAAe,cAAc,UAAU,SAAS,KAAK,KAAK;AACrF,UAAM,cAAc,KAAK,OAAO,QAAQ,gBACpC,GAAG,eAAe,IAAI,KAAK,OAAO,QAAQ,aAAa,KACvD;AAEJ,QAAI;AAEF,YAAM,KAAK,aAAa,aAAa,YAAY;AAC/C,cAAM,KAAK,QAAQ,MAAM;AACzB,cAAM,YAAY,MAAM,KAAK,QAAQ,aAAa;AAClD,YAAI,CAAC,UAAU,SAAS,eAAe,GAAG;AACxC,gBAAM,KAAK,QAAQ;AAAA,YACjB;AAAA,YACA,KAAK;AAAA,YACL,UAAU,MAAM,iBAAiB;AAAA,UACnC;AAAA,QACF;AAAA,MACF,CAAC;AAGD,YAAM,WAAW,KAAK,iBAAiB,IAAI;AAC3C,YAAM,SAAS,gBAAgB,KAAK,OAAO,KAAK,WAAW;AAE3D,YAAM,SAAS,IAAI;AAAA,QACjB;AAAA,QACA,SAAS;AAAA,QACT,WAAW,KAAK,OAAO,UAAU;AAAA,QACjC,eAAe,KAAK,OAAO,GAAG;AAAA,QAC9B,eAAe,CAAC,UAAuB;AACrC,mBAAS,UAAU,gBAAgB;AAAA,YACjC;AAAA,YACA,QAAQ,KAAK;AAAA,YACb,OAAO;AAAA,YACP;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAGD,YAAM,QAAQ,IAAI,cAAc,eAAe;AAC/C,UAAI,MAAM,MAAM,WAAW,GAAG;AAC5B,cAAM,MAAM,IAAI,CAAC,GAAG,CAAC;AACrB,cAAM,MAAM,OAAO,cAAc,KAAK,KAAK,EAAE;AAC7C,cAAM,MAAM,KAAK,KAAK,UAAU;AAAA,MAClC;AAGA,WAAK,QAAQ,iBAAiB,SAAS,KAAK,uBAAqB;AAAA,QAC/D,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,MACtC,CAAC;AACD,eAAS,UAAU,4BAA4B,EAAE,SAAS,QAAQ,KAAK,GAAG,CAAC;AAC3E,MAAAA,QAAO,KAAK,4BAA4B,EAAE,SAAS,QAAQ,KAAK,GAAG,CAAC;AAGpE,YAAM,KAAK,WAAW,QAAQ,SAAS;AAAA,QACrC,GAAG;AAAA,QACH;AAAA,QACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,MACtC,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,WAAY,IAAc;AAChC,MAAAA,QAAO,MAAM,yBAAyB,EAAE,SAAS,QAAQ,KAAK,IAAI,OAAO,SAAS,CAAC;AACnF,WAAK,QAAQ,eAAe,SAAS,KAAK,IAAI,QAAQ;AAAA,IACxD;AAAA,EACF;AAAA,EAEQ,iBAAiB,MAA+B;AACtD,UAAM,OAAO,KAAK,gBAAgB,KAAK,OAAO,GAAG;AACjD,QAAI,SAAS,KAAK,OAAO,GAAG,KAAM,QAAO,KAAK;AAC9C,WAAO,eAAe;AAAA,MACpB;AAAA,MACA,QAAQ,KAAK,OAAO,GAAG;AAAA,MACvB,gBAAgB,KAAK,OAAO,UAAU;AAAA,MACtC,gBAAgB,KAAK,OAAO,GAAG;AAAA,MAC/B,OAAO,KAAK,OAAO,GAAG;AAAA,IACxB,CAAC;AAAA,EACH;AACF;;;AKnYA,IAAMC,UAAS,OAAW,MAAM,kBAAkB;AAE3C,IAAM,mBAAN,cAA+B,YAA4B;AAAA,EAChE,YAAY,SAAiB;AAC3B,UAAM,SAAS,0BAA0B,WAAW,mBAAmB;AAAA,EACzE;AAAA;AAAA,EAIA,SAAS,SAA6C;AACpD,WAAO,KAAK,SAAS,OAAO;AAAA,EAC9B;AAAA,EAEA,SAA2B;AACzB,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA,EAEA,YAAY,OAAuC;AACjD,SAAK,UAAU,MAAM,IAAI,KAAK;AAC9B,SAAK,KAAK;AACV,IAAAA,QAAO,KAAK,iBAAiB,EAAE,SAAS,MAAM,GAAG,CAAC;AAClD,aAAS,UAAU,qBAAqB,EAAE,SAAS,MAAM,GAAG,CAAC;AAC7D,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,SAAiB,SAAwC;AACnE,UAAM,QAAQ,KAAK,WAAW,OAAO;AACrC,QAAI,CAAC,MAAO,OAAM,IAAI,mBAAmB,OAAO;AAChD,WAAO,OAAO,OAAO,SAAS,EAAE,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AACrE,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,YAAY,SAA0B;AACpC,QAAI,CAAC,KAAK,YAAY,OAAO,EAAG,QAAO;AACvC,IAAAA,QAAO,KAAK,iBAAiB,EAAE,QAAQ,CAAC;AACxC,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,kBAAkB,SAAiB,QAAqB,OAAsB;AAC5E,UAAM,QAAQ,KAAK,WAAW,OAAO;AACrC,QAAI,CAAC,MAAO,OAAM,IAAI,mBAAmB,OAAO;AAChD,UAAM,SAAS;AACf,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,QAAI,UAAU,OAAW,OAAM,YAAY;AAC3C,QAAI,uCAAkC,OAAM,eAAc,oBAAI,KAAK,GAAE,YAAY;AACjF,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,iBAAiB,SAAiB,QAAgB,QAAoB,OAAsC;AAC1G,UAAM,QAAQ,KAAK,WAAW,OAAO;AACrC,QAAI,CAAC,MAAO,OAAM,IAAI,mBAAmB,OAAO;AAChD,UAAM,OAAO,MAAM,MAAM,KAAK,OAAK,EAAE,OAAO,MAAM;AAClD,QAAI,CAAC,KAAM,OAAM,IAAI,kBAAkB,MAAM;AAC7C,SAAK,SAAS;AACd,QAAI,MAAO,QAAO,OAAO,MAAM,KAAK;AACpC,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,eAAe,SAAiB,QAAgB,OAAqB;AACnE,UAAM,QAAQ,KAAK,WAAW,OAAO;AACrC,QAAI,CAAC,MAAO;AACZ,UAAM,OAAO,MAAM,MAAM,KAAK,OAAK,EAAE,OAAO,MAAM;AAClD,QAAI,CAAC,KAAM;AACX,SAAK;AACL,SAAK,YAAY;AACjB,SAAK,YAAY;AACjB,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,SAAK,KAAK;AACV,IAAAA,QAAO,KAAK,sBAAsB,EAAE,SAAS,QAAQ,OAAO,UAAU,KAAK,SAAS,CAAC;AACrF,aAAS,UAAU,yBAAyB,EAAE,SAAS,QAAQ,MAAM,CAAC;AAAA,EACxE;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC3B,UAAM,0BAA0B,oBAAI,IAAI,+FAAqE,CAAC;AAC9G,QAAI,QAAQ;AAEZ,eAAW,SAAS,KAAK,cAAc,GAAG;AACxC,UAAI,MAAM,sCAAkC,MAAM,mCAAgC;AAElF,UAAI,YAAY;AAChB,iBAAW,QAAQ,MAAM,OAAO;AAC9B,YAAI,wBAAwB,IAAI,KAAK,MAAM,GAAG;AAC5C,eAAK;AACL,eAAK,YAAY;AACjB,eAAK,YAAY;AACjB,sBAAY;AAAA,QACd;AAAA,MACF;AAEA,UAAI,WAAW;AAEb,cAAM,iBAAiB,MAAM,MAAM;AAAA,UACjC,OAAK,EAAE,sCAAiC,EAAE,sCAAiC,EAAE;AAAA,QAC/E;AACA,YAAI,CAAC,gBAAgB;AACnB,gBAAM;AACN,gBAAM,YAAY;AAAA,QACpB;AACA,cAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,QAAQ,GAAG;AACb,WAAK,KAAK;AACV,MAAAA,QAAO,KAAK,2CAA2C,EAAE,MAAM,CAAC;AAAA,IAClE;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,kBAAkB,SAAmC;AACnD,UAAM,QAAQ,KAAK,SAAS,OAAO;AACnC,QAAI,CAAC,MAAO,QAAO,CAAC;AACpB,WAAO,MAAM,MAAM;AAAA,MAAI,CAAC,SACtB,8BAA8B,MAAM,KAAK;AAAA,IAC3C;AAAA,EACF;AACF;","names":["logger","logger","logger","logger"]}
|
|
1
|
+
{"version":3,"sources":["../src/braindump/BraindumpOrchestrator.ts","../src/braindump/prompts/split-prompt.ts","../src/braindump/TaskSplitter.ts","../src/braindump/MergeQueue.ts","../src/braindump/prompts/task-prompt.ts","../src/braindump/BraindumpTracker.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport { Config, AIRunnerMode } from '../config.js';\nimport { GitOperations } from '../git/GitOperations.js';\nimport { AsyncMutex } from '../utils/AsyncMutex.js';\nimport { createAIRunner, type AIRunner, type StreamEvent } from '../ai-runner/index.js';\nimport { eventBus } from '../events/EventBus.js';\nimport { isShuttingDown } from '../shutdown/ShutdownSignal.js';\nimport { logger as rootLogger } from '../logger.js';\nimport {\n BatchStatus,\n TaskStatus,\n type BraindumpBatch,\n type BraindumpTask,\n type SplitResult,\n} from './BraindumpState.js';\nimport { BraindumpTracker } from './BraindumpTracker.js';\nimport { TaskSplitter } from './TaskSplitter.js';\nimport { MergeQueue } from './MergeQueue.js';\nimport { buildTaskPrompt } from './prompts/task-prompt.js';\n\nconst logger = rootLogger.child('BraindumpOrchestrator');\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport interface BatchCreateOptions {\n targetBranch?: string;\n maxConcurrent?: number;\n defaultAiRunnerMode?: AIRunnerMode;\n}\n\nexport class BraindumpOrchestrator {\n private config: Config;\n private mainGit: GitOperations;\n private mainGitMutex: AsyncMutex;\n private defaultAiRunner: AIRunner;\n private tracker: BraindumpTracker;\n private mergeQueue: MergeQueue;\n private activeBatches = new Set<string>();\n\n constructor(\n config: Config,\n mainGit: GitOperations,\n mainGitMutex: AsyncMutex,\n defaultAiRunner: AIRunner,\n tracker: BraindumpTracker,\n ) {\n this.config = config;\n this.mainGit = mainGit;\n this.mainGitMutex = mainGitMutex;\n this.defaultAiRunner = defaultAiRunner;\n this.tracker = tracker;\n\n this.mergeQueue = new MergeQueue({\n mainGit,\n mainGitMutex,\n aiRunner: defaultAiRunner,\n tracker,\n worktreeBaseDir: config.project.worktreeBaseDir || config.project.gitRootDir,\n projectSubDir: config.project.projectSubDir,\n phaseTimeoutMs: config.braindump.taskTimeoutMs,\n maxConflictAttempts: config.braindump.maxConflictAttempts,\n });\n }\n\n getTracker(): BraindumpTracker {\n return this.tracker;\n }\n\n /** Replace the default AI runner (used by config hot-reload) */\n setAIRunner(runner: AIRunner): void {\n this.defaultAiRunner = runner;\n logger.info('BraindumpOrchestrator AIRunner replaced via hot-reload');\n }\n\n // ── Batch lifecycle ──\n\n createBatch(rawInput: string, options?: BatchCreateOptions): BraindumpBatch {\n const id = crypto.randomUUID();\n const targetBranch = options?.targetBranch || this.config.project.baseBranch;\n const batchShort = id.slice(0, 8);\n\n const batch: BraindumpBatch = {\n id,\n rawInput,\n targetBranch,\n integrationBranch: `braindump/${batchShort}/integration`,\n status: BatchStatus.Draft,\n tasks: [],\n maxConcurrent: options?.maxConcurrent || this.config.braindump.maxConcurrent,\n defaultAiRunnerMode: options?.defaultAiRunnerMode || this.config.ai.mode,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n return this.tracker.createBatch(batch);\n }\n\n async splitBatch(\n batchId: string,\n onEvent?: (event: StreamEvent) => void,\n ): Promise<BraindumpBatch> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n this.tracker.updateBatchStatus(batchId, BatchStatus.Splitting);\n\n try {\n const splitter = new TaskSplitter(this.defaultAiRunner);\n const result: SplitResult = await splitter.split(\n batch.rawInput,\n this.config.project.workDir,\n this.config.braindump.splitTimeoutMs,\n onEvent,\n );\n\n const batchShort = batchId.slice(0, 8);\n const tasks: BraindumpTask[] = result.tasks.map((t, i) => ({\n id: crypto.randomUUID(),\n index: i,\n title: t.title,\n description: t.description,\n dependsOn: t.dependsOn.map(depIdx => {\n // Convert index-based dependencies to task IDs (we'll set them below)\n return String(depIdx);\n }),\n branchName: `braindump/${batchShort}/task-${i}`,\n status: TaskStatus.Pending,\n attempts: 0,\n }));\n\n // Resolve index-based dependsOn to actual task IDs\n for (const task of tasks) {\n task.dependsOn = task.dependsOn\n .map(depIdxStr => {\n const depIdx = parseInt(depIdxStr, 10);\n return tasks[depIdx]?.id;\n })\n .filter((id): id is string => !!id);\n\n // Mark tasks with dependencies as blocked\n if (task.dependsOn.length > 0) {\n task.status = TaskStatus.Blocked;\n }\n }\n\n this.tracker.updateBatch(batchId, {\n tasks,\n status: BatchStatus.WaitingConfirm,\n });\n\n eventBus.emitTyped('braindump:split:done', { batchId, taskCount: tasks.length });\n logger.info('Batch split completed', { batchId, taskCount: tasks.length });\n\n return this.tracker.getBatch(batchId)!;\n } catch (err) {\n const errorMsg = (err as Error).message;\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, errorMsg);\n throw err;\n }\n }\n\n confirmBatch(batchId: string, tasks?: BraindumpTask[]): void {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n if (batch.status !== BatchStatus.WaitingConfirm) {\n throw new Error(`Batch ${batchId} is not waiting for confirmation (status: ${batch.status})`);\n }\n\n if (tasks) {\n this.tracker.updateBatch(batchId, { tasks });\n }\n eventBus.emitTyped('braindump:confirmed', { batchId });\n }\n\n async executeBatch(batchId: string): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n if (this.activeBatches.has(batchId)) {\n throw new Error(`Batch ${batchId} is already being executed`);\n }\n\n this.activeBatches.add(batchId);\n this.tracker.updateBatchStatus(batchId, BatchStatus.Running);\n\n try {\n // 1. Create integration branch from target\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n const exists = await this.mainGit.branchExists(batch.integrationBranch);\n if (!exists) {\n await this.mainGit.createBranch(batch.integrationBranch, batch.targetBranch);\n await this.mainGit.push(batch.integrationBranch);\n // Switch back to original branch\n await this.mainGit.checkout(this.config.project.baseBranch);\n }\n });\n\n // 2. Scheduling loop\n const runningTasks = new Map<string, Promise<void>>();\n\n while (!isShuttingDown()) {\n const currentBatch = this.tracker.getBatch(batchId);\n if (!currentBatch || currentBatch.status === BatchStatus.Failed) break;\n\n // Check if all tasks are in terminal state\n const allTerminal = currentBatch.tasks.every(\n t => t.status === TaskStatus.Merged || t.status === TaskStatus.Failed,\n );\n if (allTerminal) break;\n\n // Unblock tasks whose dependencies are met\n this.unblockReadyTasks(batchId);\n\n // Get ready tasks (pending + not running)\n const ready = currentBatch.tasks.filter(\n t => t.status === TaskStatus.Pending && !runningTasks.has(t.id),\n );\n const available = currentBatch.maxConcurrent - runningTasks.size;\n\n // Start new tasks\n for (const task of ready.slice(0, available)) {\n const promise = this.executeTask(batchId, task).finally(() => {\n runningTasks.delete(task.id);\n });\n runningTasks.set(task.id, promise);\n }\n\n await sleep(2000);\n }\n\n // Wait for remaining running tasks\n if (runningTasks.size > 0) {\n await Promise.allSettled([...runningTasks.values()]);\n }\n\n // Determine final batch status\n const finalBatch = this.tracker.getBatch(batchId)!;\n const allMerged = finalBatch.tasks.every(t => t.status === TaskStatus.Merged);\n const anyFailed = finalBatch.tasks.some(t => t.status === TaskStatus.Failed);\n\n if (allMerged) {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Completed);\n eventBus.emitTyped('braindump:completed', { batchId });\n logger.info('Batch completed successfully', { batchId });\n } else if (anyFailed) {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, 'Some tasks failed');\n eventBus.emitTyped('braindump:failed', { batchId });\n logger.warn('Batch completed with failures', { batchId });\n }\n } catch (err) {\n const errorMsg = (err as Error).message;\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, errorMsg);\n eventBus.emitTyped('braindump:failed', { batchId, error: errorMsg });\n logger.error('Batch execution failed', { batchId, error: errorMsg });\n } finally {\n this.activeBatches.delete(batchId);\n }\n }\n\n async cancelBatch(batchId: string): Promise<void> {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, 'Cancelled by user');\n this.activeBatches.delete(batchId);\n }\n\n async retryFailed(batchId: string): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n for (const task of batch.tasks) {\n if (task.status === TaskStatus.Failed) {\n task.status = TaskStatus.Pending;\n task.lastError = undefined;\n }\n }\n this.tracker.updateBatch(batchId, { tasks: batch.tasks });\n await this.executeBatch(batchId);\n }\n\n recoverInterrupted(): number {\n return this.tracker.recoverInterrupted();\n }\n\n // ── Private ──\n\n private unblockReadyTasks(batchId: string): void {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) return;\n\n for (const task of batch.tasks) {\n if (task.status !== TaskStatus.Blocked) continue;\n\n const allDepsMet = task.dependsOn.every(depId => {\n const dep = batch.tasks.find(t => t.id === depId);\n return dep && dep.status === TaskStatus.Merged;\n });\n\n if (allDepsMet) {\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Pending);\n }\n }\n }\n\n private async executeTask(batchId: string, task: BraindumpTask): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) return;\n\n logger.info('Starting task execution', { batchId, taskId: task.id, title: task.title });\n\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Running, {\n startedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:started', { batchId, taskId: task.id, title: task.title });\n\n const batchShort = batchId.slice(0, 8);\n const worktreeBaseDir = this.config.project.worktreeBaseDir || this.config.project.gitRootDir;\n const taskWorktreeDir = `${worktreeBaseDir}/braindump-${batchShort}-task-${task.index}`;\n const taskWorkDir = this.config.project.projectSubDir\n ? `${taskWorktreeDir}/${this.config.project.projectSubDir}`\n : taskWorktreeDir;\n\n try {\n // 1. Create worktree from integration branch\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n const worktrees = await this.mainGit.worktreeList();\n if (!worktrees.includes(taskWorktreeDir)) {\n await this.mainGit.worktreeAdd(\n taskWorktreeDir,\n task.branchName,\n `origin/${batch.integrationBranch}`,\n );\n }\n });\n\n // 2. Run AI\n const aiRunner = this.getRunnerForTask(task);\n const prompt = buildTaskPrompt(task.title, task.description);\n\n await aiRunner.run({\n prompt,\n workDir: taskWorkDir,\n timeoutMs: this.config.braindump.taskTimeoutMs,\n idleTimeoutMs: this.config.ai.idleTimeoutMs,\n onStreamEvent: (event: StreamEvent) => {\n eventBus.emitTyped('agent:output', {\n batchId,\n taskId: task.id,\n phase: 'task-execute',\n event,\n });\n },\n });\n\n // 3. Commit changes\n const wtGit = new GitOperations(taskWorktreeDir);\n if (await wtGit.hasChanges()) {\n await wtGit.add(['.']);\n await wtGit.commit(`braindump: ${task.title}`);\n await wtGit.push(task.branchName);\n }\n\n // 4. Mark done\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Done, {\n completedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:completed', { batchId, taskId: task.id });\n logger.info('Task execution completed', { batchId, taskId: task.id });\n\n // 5. Enqueue for merge\n await this.mergeQueue.enqueue(batchId, {\n ...task,\n status: TaskStatus.Done,\n completedAt: new Date().toISOString(),\n });\n } catch (err) {\n const errorMsg = (err as Error).message;\n logger.error('Task execution failed', { batchId, taskId: task.id, error: errorMsg });\n this.tracker.markTaskFailed(batchId, task.id, errorMsg);\n }\n }\n\n private getRunnerForTask(task: BraindumpTask): AIRunner {\n const mode = task.aiRunnerMode || this.config.ai.mode;\n if (mode === this.config.ai.mode) return this.defaultAiRunner;\n return createAIRunner({\n mode,\n binary: this.config.ai.binary,\n phaseTimeoutMs: this.config.braindump.taskTimeoutMs,\n nvmNodeVersion: this.config.ai.nvmNodeVersion,\n model: this.config.ai.model,\n });\n }\n}\n","/**\n * Build the prompt for AI task splitting.\n * The AI should browse the codebase, analyze the user's input,\n * and output a structured JSON with independent tasks.\n */\nexport function buildSplitPrompt(rawInput: string): string {\n return `你是一个项目管理专家和代码架构师。用户输入了一段包含多个想法/特性/任务的文本内容。\n\n你的任务:\n1. 先浏览项目代码库,了解项目结构和技术栈\n2. 分析用户输入的内容,识别出所有独立的特性或任务\n3. 为每个任务生成清晰的标题、详细描述和依赖关系\n4. 标注哪些任务可以并行执行(没有依赖关系的任务)\n\n## 用户输入\n\n${rawInput}\n\n## 输出要求\n\n请严格按以下 JSON 格式输出(用 \\`\\`\\`json 包裹):\n\n\\`\\`\\`json\n{\n \"tasks\": [\n {\n \"title\": \"简洁的任务标题\",\n \"description\": \"详细的任务描述,包含:\\\\n1. 需要修改哪些文件\\\\n2. 具体要做什么改动\\\\n3. 验收标准\",\n \"dependsOn\": [],\n \"estimatedComplexity\": \"low\"\n },\n {\n \"title\": \"另一个任务\",\n \"description\": \"这个任务依赖第一个任务的完成\",\n \"dependsOn\": [0],\n \"estimatedComplexity\": \"medium\"\n }\n ]\n}\n\\`\\`\\`\n\n## 注意事项\n\n- \\`dependsOn\\` 中填写的是任务在数组中的索引(从 0 开始),表示本任务依赖哪些任务先完成\n- 如果两个任务修改不同文件且逻辑独立,它们之间不应有依赖关系(可并行)\n- 如果两个任务修改相同文件或存在逻辑先后顺序,应设置依赖关系\n- \\`estimatedComplexity\\` 可选值:low(简单改动)、medium(中等复杂度)、high(复杂重构)\n- 每个任务的描述要足够详细,让 AI Agent 能独立完成该任务\n- 任务粒度适中:不要太大(一个任务不应包含多个独立变更),也不要太小(一行代码的改动不需要单独一个任务)`;\n}\n","import type { AIRunner, RunResult, StreamEvent } from '../ai-runner/index.js';\nimport type { SplitResult } from './BraindumpState.js';\nimport { buildSplitPrompt } from './prompts/split-prompt.js';\nimport { AIOutputParseError } from '../errors/index.js';\nimport { logger as rootLogger } from '../logger.js';\n\nconst logger = rootLogger.child('TaskSplitter');\n\nexport class TaskSplitter {\n constructor(private aiRunner: AIRunner) {}\n\n async split(\n rawInput: string,\n workDir: string,\n timeoutMs: number,\n onEvent?: (event: StreamEvent) => void,\n ): Promise<SplitResult> {\n logger.info('Starting task split', { inputLength: rawInput.length });\n\n const prompt = buildSplitPrompt(rawInput);\n const result: RunResult = await this.aiRunner.run({\n prompt,\n workDir,\n timeoutMs,\n onStreamEvent: onEvent,\n });\n\n return this.parseSplitOutput(result.output);\n }\n\n private parseSplitOutput(output: string): SplitResult {\n // Extract JSON block from AI output (wrapped in ```json ... ```)\n const jsonMatch = output.match(/```json\\s*\\n([\\s\\S]*?)\\n\\s*```/);\n if (!jsonMatch) {\n // Try parsing the entire output as JSON\n try {\n return this.validateResult(JSON.parse(output));\n } catch {\n throw new AIOutputParseError(\n 'Failed to parse task split output: no JSON block found in AI response',\n output,\n );\n }\n }\n\n try {\n return this.validateResult(JSON.parse(jsonMatch[1]));\n } catch (err) {\n throw new AIOutputParseError(`Failed to parse task split JSON: ${(err as Error).message}`, jsonMatch[1]);\n }\n }\n\n private validateResult(raw: unknown): SplitResult {\n if (!raw || typeof raw !== 'object') {\n throw new AIOutputParseError('Split result is not an object');\n }\n\n const obj = raw as Record<string, unknown>;\n if (!Array.isArray(obj.tasks)) {\n throw new AIOutputParseError('Split result missing \"tasks\" array');\n }\n\n const tasks = obj.tasks.map((t: Record<string, unknown>, i: number) => ({\n title: String(t.title || `Task ${i + 1}`),\n description: String(t.description || ''),\n dependsOn: Array.isArray(t.dependsOn)\n ? t.dependsOn.map(Number).filter(n => !isNaN(n))\n : [],\n estimatedComplexity: (['low', 'medium', 'high'].includes(String(t.estimatedComplexity))\n ? String(t.estimatedComplexity)\n : 'medium') as 'low' | 'medium' | 'high',\n }));\n\n logger.info('Parsed split result', { taskCount: tasks.length });\n return { tasks };\n }\n}\n","import { GitOperations } from '../git/GitOperations.js';\nimport { ConflictResolver } from '../git/ConflictResolver.js';\nimport { AsyncMutex } from '../utils/AsyncMutex.js';\nimport type { AIRunner } from '../ai-runner/index.js';\nimport { eventBus } from '../events/EventBus.js';\nimport { logger as rootLogger } from '../logger.js';\nimport type { BraindumpTask } from './BraindumpState.js';\nimport { TaskStatus } from './BraindumpState.js';\nimport { BraindumpTracker } from './BraindumpTracker.js';\n\nconst logger = rootLogger.child('MergeQueue');\n\ninterface MergeRequest {\n batchId: string;\n task: BraindumpTask;\n resolve: () => void;\n reject: (err: Error) => void;\n}\n\n/**\n * Serial merge queue: tasks are merged one at a time into the integration branch.\n * This ensures each merge sees the latest integration state and minimizes conflicts.\n */\nexport class MergeQueue {\n private queue: MergeRequest[] = [];\n private processing = false;\n private mainGit: GitOperations;\n private mainGitMutex: AsyncMutex;\n private conflictResolver: ConflictResolver;\n private tracker: BraindumpTracker;\n private worktreeBaseDir: string;\n private projectSubDir: string;\n private phaseTimeoutMs: number;\n private maxConflictAttempts: number;\n\n constructor(opts: {\n mainGit: GitOperations;\n mainGitMutex: AsyncMutex;\n aiRunner: AIRunner;\n tracker: BraindumpTracker;\n worktreeBaseDir: string;\n projectSubDir: string;\n phaseTimeoutMs: number;\n maxConflictAttempts: number;\n }) {\n this.mainGit = opts.mainGit;\n this.mainGitMutex = opts.mainGitMutex;\n this.conflictResolver = new ConflictResolver(opts.aiRunner);\n this.tracker = opts.tracker;\n this.worktreeBaseDir = opts.worktreeBaseDir;\n this.projectSubDir = opts.projectSubDir;\n this.phaseTimeoutMs = opts.phaseTimeoutMs;\n this.maxConflictAttempts = opts.maxConflictAttempts;\n }\n\n /** Enqueue a completed task for merging. Returns a promise that resolves when merge is done. */\n enqueue(batchId: string, task: BraindumpTask): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.queue.push({ batchId, task, resolve, reject });\n this.processNext();\n });\n }\n\n get pendingCount(): number {\n return this.queue.length;\n }\n\n private async processNext(): Promise<void> {\n if (this.processing || this.queue.length === 0) return;\n this.processing = true;\n\n // Sort by task index to ensure deterministic merge order\n this.queue.sort((a, b) => a.task.index - b.task.index);\n const req = this.queue.shift()!;\n\n try {\n await this.mergeTask(req.batchId, req.task);\n req.resolve();\n } catch (err) {\n req.reject(err as Error);\n } finally {\n this.processing = false;\n // Process next item if any\n if (this.queue.length > 0) {\n this.processNext();\n }\n }\n }\n\n private async mergeTask(batchId: string, task: BraindumpTask): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n logger.info('Merging task into integration branch', {\n batchId, taskId: task.id, taskIndex: task.index, branch: task.branchName,\n });\n\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Merging);\n eventBus.emitTyped('braindump:task:merging', { batchId, taskId: task.id });\n\n const taskWorktreeDir = this.getTaskWorktreeDir(batchId, task.index);\n const taskWorkDir = this.projectSubDir\n ? `${taskWorktreeDir}/${this.projectSubDir}`\n : taskWorktreeDir;\n\n try {\n const wtGit = new GitOperations(taskWorktreeDir);\n\n // 1. Checkout the task branch\n await wtGit.checkout(task.branchName);\n\n // 2. Rebase onto integration branch (with AI conflict resolution)\n await this.conflictResolver.resolve({\n wtGit,\n targetRef: `origin/${batch.integrationBranch}`,\n workDir: taskWorkDir,\n branchName: task.branchName,\n contextId: task.id,\n phaseTimeoutMs: this.phaseTimeoutMs,\n maxAttempts: this.maxConflictAttempts,\n onEvent: (event) => {\n eventBus.emitTyped('agent:output', {\n batchId,\n taskId: task.id,\n phase: 'merge-conflict-resolve',\n event,\n });\n },\n });\n\n // 3. Force push the rebased task branch\n await wtGit.forcePush(task.branchName);\n\n // 4. Merge into integration branch (in main repo under mutex)\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n await this.mainGit.checkout(batch.integrationBranch);\n try {\n await this.mainGit.mergeFF(task.branchName);\n } catch {\n // If ff not possible, do a regular merge\n await this.mainGit.merge(task.branchName, `merge: braindump task #${task.index} - ${task.title}`);\n }\n await this.mainGit.push(batch.integrationBranch);\n });\n\n // 5. Update status\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Merged, {\n mergedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:merged', { batchId, taskId: task.id });\n\n logger.info('Task merged successfully', { batchId, taskId: task.id });\n } catch (err) {\n const errorMsg = (err as Error).message;\n logger.error('Task merge failed', { batchId, taskId: task.id, error: errorMsg });\n\n // Try to abort any in-progress rebase\n try {\n const wtGit = new GitOperations(taskWorktreeDir);\n if (await wtGit.isRebaseInProgress()) {\n await wtGit.rebaseAbort();\n }\n } catch { /* ignore */ }\n\n this.tracker.markTaskFailed(batchId, task.id, `Merge failed: ${errorMsg}`);\n throw err;\n }\n }\n\n private getTaskWorktreeDir(batchId: string, taskIndex: number): string {\n const batchShort = batchId.slice(0, 8);\n return `${this.worktreeBaseDir}/braindump-${batchShort}-task-${taskIndex}`;\n }\n}\n","/**\n * Build the prompt for executing a single braindump task.\n */\nexport function buildTaskPrompt(taskTitle: string, taskDescription: string): string {\n return `你需要完成以下任务:\n\n## 任务:${taskTitle}\n\n${taskDescription}\n\n## 要求\n\n1. 仔细阅读相关代码,理解现有架构\n2. 按照任务描述进行代码修改\n3. 确保修改不会破坏现有功能\n4. 遵循项目现有的代码风格和规范\n5. 完成后确保代码可以通过 lint 检查`;\n}\n","import { BatchStatus, TaskStatus, type BraindumpBatch, type BraindumpTask } from './BraindumpState.js';\nimport { BaseTracker } from '../tracker/BaseTracker.js';\nimport { type ExecutableTask, braindumpTaskToExecutableTask } from '../tracker/ExecutableTask.js';\nimport { BatchNotFoundError, TaskNotFoundError } from '../errors/index.js';\nimport { logger as rootLogger } from '../logger.js';\nimport { eventBus } from '../events/EventBus.js';\n\nconst logger = rootLogger.child('BraindumpTracker');\n\nexport class BraindumpTracker extends BaseTracker<BraindumpBatch> {\n constructor(dataDir: string) {\n super(dataDir, 'braindump-tracker.json', 'batches', 'braindump-tracker');\n }\n\n // ── CRUD ──\n\n getBatch(batchId: string): BraindumpBatch | undefined {\n return this.getByKey(batchId);\n }\n\n getAll(): BraindumpBatch[] {\n return this.getAllRecords();\n }\n\n createBatch(batch: BraindumpBatch): BraindumpBatch {\n this.setRecord(batch.id, batch);\n this.save();\n logger.info('Batch created', { batchId: batch.id });\n eventBus.emitTyped('braindump:created', { batchId: batch.id });\n return batch;\n }\n\n updateBatch(batchId: string, updates: Partial<BraindumpBatch>): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n Object.assign(batch, updates, { updatedAt: new Date().toISOString() });\n this.save();\n }\n\n deleteBatch(batchId: string): boolean {\n if (!this.deleteByKey(batchId)) return false;\n logger.info('Batch deleted', { batchId });\n return true;\n }\n\n // ── Status helpers ──\n\n updateBatchStatus(batchId: string, status: BatchStatus, error?: string): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n batch.status = status;\n batch.updatedAt = new Date().toISOString();\n if (error !== undefined) batch.lastError = error;\n if (status === BatchStatus.Completed) batch.completedAt = new Date().toISOString();\n this.save();\n }\n\n updateTaskStatus(batchId: string, taskId: string, status: TaskStatus, extra?: Partial<BraindumpTask>): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n const task = batch.tasks.find(t => t.id === taskId);\n if (!task) throw new TaskNotFoundError(taskId);\n task.status = status;\n if (extra) Object.assign(task, extra);\n batch.updatedAt = new Date().toISOString();\n this.save();\n }\n\n markTaskFailed(batchId: string, taskId: string, error: string): void {\n const batch = this.collection[batchId];\n if (!batch) return;\n const task = batch.tasks.find(t => t.id === taskId);\n if (!task) return;\n task.status = TaskStatus.Failed;\n task.lastError = error;\n task.attempts += 1;\n batch.updatedAt = new Date().toISOString();\n this.save();\n logger.warn('Task marked failed', { batchId, taskId, error, attempts: task.attempts });\n eventBus.emitTyped('braindump:task:failed', { batchId, taskId, error });\n }\n\n // ── Recovery ──\n\n /** Recover batches interrupted by service restart. Returns count of recovered batches. */\n recoverInterrupted(): number {\n const IN_PROGRESS_TASK_STATES = new Set([TaskStatus.Running, TaskStatus.Merging, TaskStatus.ConflictResolving]);\n let count = 0;\n\n for (const batch of this.getAllRecords()) {\n if (batch.status !== BatchStatus.Running && batch.status !== BatchStatus.Merging) continue;\n\n let recovered = false;\n for (const task of batch.tasks) {\n if (IN_PROGRESS_TASK_STATES.has(task.status)) {\n task.status = TaskStatus.Failed;\n task.lastError = 'Interrupted by service restart';\n task.attempts += 1;\n recovered = true;\n }\n }\n\n if (recovered) {\n // If any task is still pending/blocked/done, keep batch running for retry\n const hasRecoverable = batch.tasks.some(\n t => t.status === TaskStatus.Pending || t.status === TaskStatus.Blocked || t.status === TaskStatus.Done,\n );\n if (!hasRecoverable) {\n batch.status = BatchStatus.Failed;\n batch.lastError = 'Interrupted by service restart';\n }\n batch.updatedAt = new Date().toISOString();\n count++;\n }\n }\n\n if (count > 0) {\n this.save();\n logger.info('Recovered interrupted braindump batches', { count });\n }\n return count;\n }\n\n /** 将指定批次的所有任务投影为 ExecutableTask[] */\n toExecutableTasks(batchId: string): ExecutableTask[] {\n const batch = this.getByKey(batchId);\n if (!batch) return [];\n return batch.tasks.map((task) =>\n braindumpTaskToExecutableTask(task, batch),\n );\n }\n}\n"],"mappings":"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","names":["logger","logger","logger","logger"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
t
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OBGEEGQ3.js";
|
|
4
4
|
|
|
5
5
|
// src/cli/setup/DependencyChecker.ts
|
|
6
6
|
import { spawn } from "child_process";
|
|
@@ -407,4 +407,4 @@ async function* streamProcessLines(proc, timeoutMs) {
|
|
|
407
407
|
export {
|
|
408
408
|
DependencyChecker
|
|
409
409
|
};
|
|
410
|
-
//# sourceMappingURL=chunk-
|
|
410
|
+
//# sourceMappingURL=chunk-DVNAH2GV.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
t
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OBGEEGQ3.js";
|
|
4
4
|
|
|
5
5
|
// src/coordination/LockNote.ts
|
|
6
6
|
var LOCK_NOTE_REGEX = /<!-- iaf-lock:(\{.*?\}) -->/;
|
|
@@ -66,4 +66,4 @@ export {
|
|
|
66
66
|
findAllLockNotes,
|
|
67
67
|
isLockStale
|
|
68
68
|
};
|
|
69
|
-
//# sourceMappingURL=chunk-
|
|
69
|
+
//# sourceMappingURL=chunk-EU4XFZ2T.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
getBinaryEnvKey,
|
|
9
9
|
getDefaultBinary
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-TFEPHOVE.js";
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
13
13
|
import { config as loadDotenv } from "dotenv";
|
|
@@ -65,8 +65,11 @@ var envSchema = z.object({
|
|
|
65
65
|
CURSOR_BINARY: z.string().optional(),
|
|
66
66
|
CODEBUDDY_BINARY: z.string().optional(),
|
|
67
67
|
CODEBUDDY_ACP_AUTO_APPROVE: envBoolean("true"),
|
|
68
|
-
CLAUDE_PHASE_TIMEOUT_MS: envMs("
|
|
68
|
+
CLAUDE_PHASE_TIMEOUT_MS: envMs("2700000"),
|
|
69
69
|
AI_IDLE_TIMEOUT_MS: z.coerce.number().int().min(0, "AI_IDLE_TIMEOUT_MS must be >= 0 (0 to disable)").optional().default(12e5),
|
|
70
|
+
PHASE_TIMEOUT_GRACE_MS: envMs("60000"),
|
|
71
|
+
PHASE_TIMEOUT_EXTENSION_MS: envMs("600000"),
|
|
72
|
+
PHASE_TIMEOUT_MAX_EXTENSIONS: z.coerce.number().int().min(0).optional().default(3),
|
|
70
73
|
NVM_NODE_VERSION: z.string().optional().default("20"),
|
|
71
74
|
// --- Pipeline ---
|
|
72
75
|
PIPELINE_MODE: z.string().optional().default("auto"),
|
|
@@ -250,6 +253,9 @@ function transformEnvToConfig(env, dirname) {
|
|
|
250
253
|
binary: resolveAIBinary(aiMode, env),
|
|
251
254
|
phaseTimeoutMs: env.CLAUDE_PHASE_TIMEOUT_MS,
|
|
252
255
|
idleTimeoutMs: env.AI_IDLE_TIMEOUT_MS || void 0,
|
|
256
|
+
timeoutGraceMs: env.PHASE_TIMEOUT_GRACE_MS,
|
|
257
|
+
timeoutExtensionMs: env.PHASE_TIMEOUT_EXTENSION_MS,
|
|
258
|
+
timeoutMaxExtensions: env.PHASE_TIMEOUT_MAX_EXTENSIONS,
|
|
253
259
|
nvmNodeVersion: env.NVM_NODE_VERSION,
|
|
254
260
|
model: env.AI_MODEL,
|
|
255
261
|
codebuddyAcpAutoApprove: env.CODEBUDDY_ACP_AUTO_APPROVE
|
|
@@ -458,4 +464,4 @@ export {
|
|
|
458
464
|
resetDotenvCache,
|
|
459
465
|
reloadConfig
|
|
460
466
|
};
|
|
461
|
-
//# sourceMappingURL=chunk-
|
|
467
|
+
//# sourceMappingURL=chunk-FJTZKAJA.js.map
|