muonroi-cli 1.8.4 → 1.9.0
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/LICENSE +17 -5
- package/dist/packages/agent-harness-core/src/driver.d.ts +27 -1
- package/dist/packages/agent-harness-core/src/driver.js +46 -0
- package/dist/packages/agent-harness-core/src/event-filter.js +11 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +7 -0
- package/dist/packages/agent-harness-core/src/event-tee.d.ts +64 -0
- package/dist/packages/agent-harness-core/src/event-tee.js +104 -0
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +25 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +169 -21
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/packages/agent-harness-core/src/protocol.d.ts +90 -4
- package/dist/packages/agent-harness-core/src/protocol.js +15 -0
- package/dist/packages/agent-harness-core/src/visual-quality.d.ts +58 -0
- package/dist/packages/agent-harness-core/src/visual-quality.js +141 -0
- package/dist/packages/agent-harness-opentui/src/agent-mode.d.ts +6 -0
- package/dist/packages/agent-harness-opentui/src/agent-mode.js +14 -1
- package/dist/packages/agent-harness-opentui/src/input-bridge.d.ts +2 -10
- package/dist/packages/agent-harness-opentui/src/input-bridge.js +103 -16
- package/dist/packages/agent-harness-opentui/src/install.d.ts +8 -0
- package/dist/packages/agent-harness-opentui/src/install.js +10 -0
- package/dist/packages/agent-harness-opentui/src/semantic.js +12 -10
- package/dist/packages/agent-harness-opentui/src/visual-capture.d.ts +56 -0
- package/dist/packages/agent-harness-opentui/src/visual-capture.js +103 -0
- package/dist/src/agent-harness/mock-model.d.ts +38 -0
- package/dist/src/agent-harness/mock-model.js +69 -3
- package/dist/src/agent-harness/test-spawn.js +31 -0
- package/dist/src/chat/chat-keychain.d.ts +7 -12
- package/dist/src/chat/chat-keychain.js +19 -86
- package/dist/src/cli/config/screen-providers.js +1 -1
- package/dist/src/cli/cost-forensics.d.ts +10 -0
- package/dist/src/cli/cost-forensics.js +18 -3
- package/dist/src/cli/keys-bundle.d.ts +1 -1
- package/dist/src/cli/keys-bundle.js +1 -1
- package/dist/src/cli/keys.d.ts +10 -47
- package/dist/src/cli/keys.js +31 -399
- package/dist/src/council/clarifier.d.ts +31 -3
- package/dist/src/council/clarifier.js +220 -32
- package/dist/src/council/context.js +49 -15
- package/dist/src/council/debate-checkpoint.d.ts +129 -0
- package/dist/src/council/debate-checkpoint.js +176 -0
- package/dist/src/council/debate-planner.js +54 -5
- package/dist/src/council/debate-summary.d.ts +25 -0
- package/dist/src/council/debate-summary.js +85 -0
- package/dist/src/council/debate.d.ts +169 -2
- package/dist/src/council/debate.js +1265 -135
- package/dist/src/council/index.d.ts +108 -1
- package/dist/src/council/index.js +670 -197
- package/dist/src/council/leader.d.ts +26 -0
- package/dist/src/council/leader.js +150 -9
- package/dist/src/council/llm.d.ts +94 -0
- package/dist/src/council/llm.js +348 -55
- package/dist/src/council/panel-select.d.ts +30 -0
- package/dist/src/council/panel-select.js +82 -0
- package/dist/src/council/planner.js +40 -0
- package/dist/src/council/preflight.d.ts +17 -0
- package/dist/src/council/preflight.js +50 -2
- package/dist/src/council/prompts.d.ts +39 -4
- package/dist/src/council/prompts.js +256 -69
- package/dist/src/council/stance-recall.d.ts +42 -0
- package/dist/src/council/stance-recall.js +57 -0
- package/dist/src/council/strip-think.d.ts +17 -0
- package/dist/src/council/strip-think.js +33 -0
- package/dist/src/council/types.d.ts +138 -0
- package/dist/src/ee/artifact-cache.d.ts +16 -0
- package/dist/src/ee/artifact-cache.js +32 -0
- package/dist/src/ee/auth.d.ts +20 -0
- package/dist/src/ee/auth.js +54 -2
- package/dist/src/ee/bridge.d.ts +10 -0
- package/dist/src/ee/bridge.js +58 -0
- package/dist/src/ee/client.js +109 -21
- package/dist/src/ee/ee-onboarding.js +6 -26
- package/dist/src/ee/export-transcripts.d.ts +1 -0
- package/dist/src/ee/export-transcripts.js +8 -10
- package/dist/src/ee/extract-session.js +29 -0
- package/dist/src/ee/extract-style.d.ts +58 -0
- package/dist/src/ee/extract-style.js +270 -0
- package/dist/src/ee/recall-ledger.d.ts +9 -0
- package/dist/src/ee/recall-ledger.js +3 -0
- package/dist/src/ee/scope.d.ts +1 -0
- package/dist/src/ee/scope.js +26 -1
- package/dist/src/ee/search.d.ts +7 -0
- package/dist/src/ee/search.js +24 -0
- package/dist/src/ee/transcript-emit.js +2 -0
- package/dist/src/ee/types.d.ts +22 -0
- package/dist/src/ee/who-am-i-brain.d.ts +35 -0
- package/dist/src/ee/who-am-i-brain.js +220 -0
- package/dist/src/ee/who-am-i.d.ts +10 -3
- package/dist/src/ee/who-am-i.js +12 -0
- package/dist/src/ee/workflow-event.d.ts +48 -0
- package/dist/src/ee/workflow-event.js +81 -0
- package/dist/src/flow/compaction/compress.d.ts +3 -3
- package/dist/src/flow/compaction/compress.js +58 -8
- package/dist/src/flow/compaction/extract.d.ts +4 -7
- package/dist/src/flow/compaction/extract.js +50 -10
- package/dist/src/flow/compaction/index.d.ts +14 -1
- package/dist/src/flow/compaction/index.js +96 -3
- package/dist/src/flow/compaction/input-guard.d.ts +24 -0
- package/dist/src/flow/compaction/input-guard.js +43 -0
- package/dist/src/flow/compaction/progress.d.ts +35 -0
- package/dist/src/flow/compaction/progress.js +35 -0
- package/dist/src/flow/fold-planning.d.ts +36 -0
- package/dist/src/flow/fold-planning.js +83 -0
- package/dist/src/flow/hierarchy.d.ts +146 -0
- package/dist/src/flow/hierarchy.js +427 -0
- package/dist/src/flow/index.d.ts +1 -0
- package/dist/src/flow/index.js +2 -0
- package/dist/src/flow/run-artifacts.d.ts +102 -0
- package/dist/src/flow/run-artifacts.js +208 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/gsd/assessment-schema.d.ts +44 -0
- package/dist/src/gsd/assessment-schema.js +134 -0
- package/dist/src/gsd/capability-registry.d.ts +45 -0
- package/dist/src/gsd/capability-registry.js +337 -0
- package/dist/src/gsd/complexity-assessor.d.ts +39 -0
- package/dist/src/gsd/complexity-assessor.js +152 -0
- package/dist/src/gsd/config-bridge.d.ts +7 -0
- package/dist/src/gsd/config-bridge.js +114 -0
- package/dist/src/gsd/config-loader.d.ts +27 -0
- package/dist/src/gsd/config-loader.js +50 -0
- package/dist/src/gsd/council-context.d.ts +44 -0
- package/dist/src/gsd/council-context.js +114 -0
- package/dist/src/gsd/ee-closure.d.ts +28 -0
- package/dist/src/gsd/ee-closure.js +49 -0
- package/dist/src/gsd/flags.d.ts +66 -0
- package/dist/src/gsd/flags.js +102 -0
- package/dist/src/gsd/gsd-dispatch.d.ts +58 -0
- package/dist/src/gsd/gsd-dispatch.js +131 -0
- package/dist/src/gsd/gsd-runtime.d.ts +22 -0
- package/dist/src/gsd/gsd-runtime.js +37 -0
- package/dist/src/gsd/host-adapter.d.ts +11 -0
- package/dist/src/gsd/host-adapter.js +29 -0
- package/dist/src/gsd/index.d.ts +24 -1
- package/dist/src/gsd/index.js +27 -0
- package/dist/src/gsd/loop-host-contract.d.ts +21 -0
- package/dist/src/gsd/loop-host-contract.js +39 -0
- package/dist/src/gsd/loop-host.d.ts +69 -0
- package/dist/src/gsd/loop-host.js +245 -0
- package/dist/src/gsd/loop-resolver.d.ts +36 -0
- package/dist/src/gsd/loop-resolver.js +79 -0
- package/dist/src/gsd/model-tier.d.ts +13 -0
- package/dist/src/gsd/model-tier.js +45 -0
- package/dist/src/gsd/mutation-gate.d.ts +16 -0
- package/dist/src/gsd/mutation-gate.js +41 -0
- package/dist/src/gsd/native-roadmap.d.ts +89 -0
- package/dist/src/gsd/native-roadmap.js +343 -0
- package/dist/src/gsd/native-state.d.ts +47 -0
- package/dist/src/gsd/native-state.js +220 -0
- package/dist/src/gsd/paths.d.ts +23 -0
- package/dist/src/gsd/paths.js +66 -0
- package/dist/src/gsd/phase-dag.d.ts +12 -0
- package/dist/src/gsd/phase-dag.js +94 -0
- package/dist/src/gsd/phase-sync.d.ts +42 -0
- package/dist/src/gsd/phase-sync.js +321 -0
- package/dist/src/gsd/pil-gate-context.d.ts +13 -0
- package/dist/src/gsd/pil-gate-context.js +64 -0
- package/dist/src/gsd/pil-gate-critic.d.ts +19 -0
- package/dist/src/gsd/pil-gate-critic.js +74 -0
- package/dist/src/gsd/plan-council-prompts.d.ts +25 -0
- package/dist/src/gsd/plan-council-prompts.js +79 -0
- package/dist/src/gsd/plan-council.d.ts +44 -0
- package/dist/src/gsd/plan-council.js +283 -0
- package/dist/src/gsd/plan-gate-vocabulary.d.ts +40 -0
- package/dist/src/gsd/plan-gate-vocabulary.js +64 -0
- package/dist/src/gsd/product-workspace.d.ts +13 -0
- package/dist/src/gsd/product-workspace.js +124 -0
- package/dist/src/gsd/ship-bridge.d.ts +25 -0
- package/dist/src/gsd/ship-bridge.js +65 -0
- package/dist/src/gsd/state-document.d.ts +40 -0
- package/dist/src/gsd/state-document.js +163 -0
- package/dist/src/gsd/verdict-schema.d.ts +39 -0
- package/dist/src/gsd/verdict-schema.js +144 -0
- package/dist/src/gsd/verify-context.d.ts +22 -0
- package/dist/src/gsd/verify-context.js +27 -0
- package/dist/src/gsd/verify-council-prompts.d.ts +19 -0
- package/dist/src/gsd/verify-council-prompts.js +85 -0
- package/dist/src/gsd/verify-council.d.ts +25 -0
- package/dist/src/gsd/verify-council.js +119 -0
- package/dist/src/gsd/verify-gate-vocabulary.d.ts +25 -0
- package/dist/src/gsd/verify-gate-vocabulary.js +46 -0
- package/dist/src/gsd/workflow-engine.d.ts +60 -0
- package/dist/src/gsd/workflow-engine.js +207 -0
- package/dist/src/gsd/workflow-tools.d.ts +13 -0
- package/dist/src/gsd/workflow-tools.js +277 -0
- package/dist/src/headless/council-answers.js +4 -0
- package/dist/src/hooks/index.js +1 -1
- package/dist/src/index.js +172 -270
- package/dist/src/lsp/builtins.js +3 -1
- package/dist/src/lsp/manager.d.ts +5 -1
- package/dist/src/lsp/manager.js +249 -3
- package/dist/src/lsp/npm-cache.d.ts +11 -1
- package/dist/src/lsp/npm-cache.js +17 -1
- package/dist/src/lsp/runtime.d.ts +6 -1
- package/dist/src/lsp/runtime.js +17 -1
- package/dist/src/lsp/types.d.ts +83 -1
- package/dist/src/lsp/types.js +10 -0
- package/dist/src/maintain/pr-builder.js +23 -13
- package/dist/src/mcp/auto-setup.js +57 -32
- package/dist/src/mcp/client-pool.js +44 -16
- package/dist/src/mcp/lsp-tools.d.ts +5 -1
- package/dist/src/mcp/lsp-tools.js +93 -2
- package/dist/src/mcp/mcp-keychain.d.ts +3 -5
- package/dist/src/mcp/mcp-keychain.js +9 -49
- package/dist/src/mcp/research-onboarding.js +8 -7
- package/dist/src/mcp/runtime.js +34 -2
- package/dist/src/mcp/setup-guide-text.d.ts +1 -1
- package/dist/src/mcp/setup-guide-text.js +22 -2
- package/dist/src/mcp/tools-server.d.ts +10 -0
- package/dist/src/mcp/tools-server.js +10 -2
- package/dist/src/models/catalog-client.d.ts +87 -0
- package/dist/src/models/catalog-client.js +105 -38
- package/dist/src/models/catalog.json +528 -265
- package/dist/src/models/registry.d.ts +22 -7
- package/dist/src/models/registry.js +73 -10
- package/dist/src/ops/doctor.js +1 -1
- package/dist/src/orchestrator/ask-user.d.ts +61 -0
- package/dist/src/orchestrator/ask-user.js +65 -0
- package/dist/src/orchestrator/auto-commit.js +1 -1
- package/dist/src/orchestrator/batch-turn-runner.js +2 -2
- package/dist/src/orchestrator/cache-prefix.d.ts +67 -0
- package/dist/src/orchestrator/cache-prefix.js +83 -0
- package/dist/src/orchestrator/compact-request.d.ts +32 -0
- package/dist/src/orchestrator/compact-request.js +41 -0
- package/dist/src/orchestrator/compaction.d.ts +12 -3
- package/dist/src/orchestrator/compaction.js +35 -15
- package/dist/src/orchestrator/council-manager.d.ts +12 -3
- package/dist/src/orchestrator/council-manager.js +74 -32
- package/dist/src/orchestrator/council-request.d.ts +49 -0
- package/dist/src/orchestrator/council-request.js +62 -0
- package/dist/src/orchestrator/cross-turn-dedup.d.ts +6 -0
- package/dist/src/orchestrator/cross-turn-dedup.js +22 -1
- package/dist/src/orchestrator/error-utils.d.ts +29 -0
- package/dist/src/orchestrator/error-utils.js +132 -24
- package/dist/src/orchestrator/grounding-check.js +39 -1
- package/dist/src/orchestrator/interactive-pause.d.ts +26 -0
- package/dist/src/orchestrator/interactive-pause.js +36 -0
- package/dist/src/orchestrator/message-processor.d.ts +4 -0
- package/dist/src/orchestrator/message-processor.js +268 -41
- package/dist/src/orchestrator/orchestrator.d.ts +64 -3
- package/dist/src/orchestrator/orchestrator.js +823 -120
- package/dist/src/orchestrator/preprocessor.js +3 -3
- package/dist/src/orchestrator/proactive-compact-detector.d.ts +26 -0
- package/dist/src/orchestrator/proactive-compact-detector.js +36 -0
- package/dist/src/orchestrator/prompts.js +17 -17
- package/dist/src/orchestrator/reactive-delegation.d.ts +39 -0
- package/dist/src/orchestrator/reactive-delegation.js +59 -0
- package/dist/src/orchestrator/retry-classifier.d.ts +2 -1
- package/dist/src/orchestrator/retry-classifier.js +46 -2
- package/dist/src/orchestrator/safety-askcard.d.ts +1 -1
- package/dist/src/orchestrator/safety-askcard.js +5 -2
- package/dist/src/orchestrator/safety-intercept.d.ts +50 -0
- package/dist/src/orchestrator/safety-intercept.js +62 -0
- package/dist/src/orchestrator/scope-reminder.js +1 -1
- package/dist/src/orchestrator/session-experience.d.ts +2 -1
- package/dist/src/orchestrator/session-experience.js +2 -1
- package/dist/src/orchestrator/should-run-gate.d.ts +5 -0
- package/dist/src/orchestrator/should-run-gate.js +18 -0
- package/dist/src/orchestrator/stall-watchdog.d.ts +31 -3
- package/dist/src/orchestrator/stall-watchdog.js +65 -10
- package/dist/src/orchestrator/stream-runner.d.ts +13 -3
- package/dist/src/orchestrator/stream-runner.js +115 -49
- package/dist/src/orchestrator/sub-agent-cap.d.ts +13 -1
- package/dist/src/orchestrator/sub-agent-cap.js +16 -1
- package/dist/src/orchestrator/sub-agent-model-tier.d.ts +13 -1
- package/dist/src/orchestrator/sub-agent-model-tier.js +16 -2
- package/dist/src/orchestrator/subagent-compactor.d.ts +53 -1
- package/dist/src/orchestrator/subagent-compactor.js +126 -15
- package/dist/src/orchestrator/tool-engine.d.ts +41 -0
- package/dist/src/orchestrator/tool-engine.js +846 -66
- package/dist/src/orchestrator/tool-limit-auto-recover.d.ts +22 -0
- package/dist/src/orchestrator/tool-limit-auto-recover.js +30 -0
- package/dist/src/orchestrator/turn-runner-deps.d.ts +19 -0
- package/dist/src/orchestrator/turn-watchdog.d.ts +44 -0
- package/dist/src/orchestrator/turn-watchdog.js +84 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +6 -4
- package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
- package/dist/src/pil/cheap-model-playbook.js +5 -1
- package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
- package/dist/src/pil/cheap-model-workbooks.js +1 -1
- package/dist/src/pil/discovery-types.d.ts +1 -0
- package/dist/src/pil/discovery.d.ts +1 -1
- package/dist/src/pil/discovery.js +18 -13
- package/dist/src/pil/layer1-intent.d.ts +18 -6
- package/dist/src/pil/layer1-intent.js +66 -757
- package/dist/src/pil/layer15-context-scan.js +15 -1
- package/dist/src/pil/layer1_5-complexity-size.d.ts +7 -0
- package/dist/src/pil/layer1_5-complexity-size.js +31 -5
- package/dist/src/pil/layer3-ee-injection.js +23 -8
- package/dist/src/pil/layer4-gsd.js +69 -16
- package/dist/src/pil/layer5-context.js +7 -3
- package/dist/src/pil/layer6-output.d.ts +23 -0
- package/dist/src/pil/layer6-output.js +5 -1
- package/dist/src/pil/llm-classify.d.ts +111 -5
- package/dist/src/pil/llm-classify.js +421 -189
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +8 -0
- package/dist/src/pil/pipeline.js +36 -2
- package/dist/src/pil/repo-grounding-probe.d.ts +15 -0
- package/dist/src/pil/repo-grounding-probe.js +136 -0
- package/dist/src/pil/repo-structure-hints.d.ts +7 -0
- package/dist/src/pil/repo-structure-hints.js +45 -0
- package/dist/src/pil/response-tools.js +5 -3
- package/dist/src/pil/schema.d.ts +1 -0
- package/dist/src/pil/schema.js +2 -0
- package/dist/src/pil/types.d.ts +18 -0
- package/dist/src/playbook/directives.d.ts +4 -0
- package/dist/src/playbook/directives.js +17 -5
- package/dist/src/product-loop/artifact-io.js +4 -0
- package/dist/src/product-loop/backlog-builder.d.ts +14 -1
- package/dist/src/product-loop/backlog-builder.js +30 -6
- package/dist/src/product-loop/criteria-seed.d.ts +51 -0
- package/dist/src/product-loop/criteria-seed.js +200 -0
- package/dist/src/product-loop/discovery-context-format.js +3 -1
- package/dist/src/product-loop/discovery-ecosystem.js +4 -1
- package/dist/src/product-loop/discovery-interview.d.ts +9 -0
- package/dist/src/product-loop/discovery-interview.js +60 -12
- package/dist/src/product-loop/discovery-recommender.js +2 -1
- package/dist/src/product-loop/discovery-schema.js +19 -2
- package/dist/src/product-loop/discovery-triage.d.ts +23 -0
- package/dist/src/product-loop/discovery-triage.js +109 -0
- package/dist/src/product-loop/gather.js +150 -2
- package/dist/src/product-loop/ideal-trace.d.ts +7 -0
- package/dist/src/product-loop/ideal-trace.js +64 -0
- package/dist/src/product-loop/index.d.ts +13 -1
- package/dist/src/product-loop/index.js +340 -52
- package/dist/src/product-loop/loop-driver.d.ts +7 -0
- package/dist/src/product-loop/loop-driver.js +330 -106
- package/dist/src/product-loop/phase-plan.d.ts +21 -0
- package/dist/src/product-loop/phase-plan.js +81 -6
- package/dist/src/product-loop/phase-rituals.d.ts +3 -0
- package/dist/src/product-loop/phase-rituals.js +8 -3
- package/dist/src/product-loop/phase-runner.js +39 -12
- package/dist/src/product-loop/plan-adherence-review.d.ts +26 -0
- package/dist/src/product-loop/plan-adherence-review.js +144 -0
- package/dist/src/product-loop/sprint-runner.d.ts +173 -0
- package/dist/src/product-loop/sprint-runner.js +863 -19
- package/dist/src/product-loop/types.d.ts +61 -5
- package/dist/src/providers/adapter.d.ts +1 -1
- package/dist/src/providers/adapter.js +3 -4
- package/dist/src/providers/anthropic.d.ts +9 -8
- package/dist/src/providers/anthropic.js +13 -47
- package/dist/src/providers/auth/browser-flow.d.ts +1 -1
- package/dist/src/providers/auth/browser-flow.js +1 -1
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -0
- package/dist/src/providers/auth/grok-oauth.js +30 -5
- package/dist/src/providers/auth/openai-oauth.d.ts +1 -0
- package/dist/src/providers/auth/openai-oauth.js +15 -1
- package/dist/src/providers/auth/registry.js +0 -34
- package/dist/src/providers/auth/token-store.d.ts +9 -9
- package/dist/src/providers/auth/token-store.js +8 -67
- package/dist/src/providers/auth/types.d.ts +9 -1
- package/dist/src/providers/auth/types.js +1 -1
- package/dist/src/providers/capabilities.d.ts +24 -5
- package/dist/src/providers/capabilities.js +42 -24
- package/dist/src/providers/endpoints.d.ts +2 -2
- package/dist/src/providers/endpoints.js +11 -10
- package/dist/src/providers/env-store.d.ts +17 -0
- package/dist/src/providers/env-store.js +228 -0
- package/dist/src/providers/keychain.d.ts +22 -18
- package/dist/src/providers/keychain.js +127 -140
- package/dist/src/providers/mcp-vision-bridge.js +56 -146
- package/dist/src/providers/openai-compatible.js +8 -1
- package/dist/src/providers/pricing.d.ts +2 -2
- package/dist/src/providers/pricing.js +3 -13
- package/dist/src/providers/runtime.d.ts +43 -3
- package/dist/src/providers/runtime.js +88 -14
- package/dist/src/providers/strategies/base.strategy.d.ts +16 -0
- package/dist/src/providers/strategies/base.strategy.js +24 -1
- package/dist/src/providers/strategies/{siliconflow.strategy.d.ts → opencode-go.strategy.d.ts} +3 -3
- package/dist/src/providers/strategies/opencode-go.strategy.js +83 -0
- package/dist/src/providers/strategies/registry.js +4 -4
- package/dist/src/providers/strategies/thinking-mode.d.ts +109 -0
- package/dist/src/providers/strategies/thinking-mode.js +288 -1
- package/dist/src/providers/strategies/xai.strategy.js +27 -0
- package/dist/src/providers/strategies/zai.strategy.d.ts +14 -0
- package/dist/src/providers/strategies/zai.strategy.js +44 -0
- package/dist/src/providers/types.d.ts +5 -6
- package/dist/src/providers/types.js +2 -2
- package/dist/src/providers/vision-backend.d.ts +47 -0
- package/dist/src/providers/vision-backend.js +258 -0
- package/dist/src/providers/vision-proxy.d.ts +22 -9
- package/dist/src/providers/vision-proxy.js +63 -132
- package/dist/src/providers/warm.d.ts +65 -0
- package/dist/src/providers/warm.js +145 -0
- package/dist/src/providers/wire-debug.js +95 -0
- package/dist/src/router/decide.d.ts +13 -0
- package/dist/src/router/decide.js +138 -36
- package/dist/src/router/peak-hour.d.ts +38 -0
- package/dist/src/router/peak-hour.js +107 -0
- package/dist/src/router/step-router.js +3 -2
- package/dist/src/router/warm.js +4 -5
- package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
- package/dist/src/scaffold/continuation-prompt.js +26 -0
- package/dist/src/scaffold/point-to-existing.d.ts +21 -0
- package/dist/src/scaffold/point-to-existing.js +25 -0
- package/dist/src/self-qa/agentic-loop.js +6 -5
- package/dist/src/{ui/state → state}/active-run.d.ts +19 -0
- package/dist/src/{ui/state → state}/active-run.js +21 -0
- package/dist/src/{ui/status-bar/store.d.ts → state/status-bar-store.d.ts} +4 -1
- package/dist/src/{ui/status-bar/store.js → state/status-bar-store.js} +12 -9
- package/dist/src/state/turn-trace.d.ts +43 -0
- package/dist/src/state/turn-trace.js +32 -0
- package/dist/src/storage/db.js +2 -1
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/interaction-log.d.ts +1 -1
- package/dist/src/storage/migrations.js +71 -1
- package/dist/src/storage/sessions.d.ts +28 -10
- package/dist/src/storage/sessions.js +78 -21
- package/dist/src/storage/transcript-view.js +1 -1
- package/dist/src/storage/transcript.d.ts +51 -0
- package/dist/src/storage/transcript.js +340 -15
- package/dist/src/tools/file.d.ts +15 -0
- package/dist/src/tools/file.js +32 -0
- package/dist/src/tools/git-safety.d.ts +19 -0
- package/dist/src/tools/git-safety.js +168 -0
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +81 -1
- package/dist/src/tools/registry.d.ts +20 -0
- package/dist/src/tools/registry.js +576 -23
- package/dist/src/tools/research.d.ts +29 -0
- package/dist/src/tools/research.js +233 -0
- package/dist/src/types/index.d.ts +147 -4
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/cards/product-status-card.js +1 -1
- package/dist/src/ui/components/agent-rail-activities.d.ts +26 -0
- package/dist/src/ui/components/agent-rail-activities.js +47 -0
- package/dist/src/ui/components/bubble-body-guard.d.ts +27 -0
- package/dist/src/ui/components/bubble-body-guard.js +50 -0
- package/dist/src/ui/components/compact-progress-card.d.ts +24 -0
- package/dist/src/ui/components/compact-progress-card.js +42 -0
- package/dist/src/ui/components/context-rail.d.ts +26 -0
- package/dist/src/ui/components/context-rail.js +33 -0
- package/dist/src/ui/components/council-conclusion-card.d.ts +73 -0
- package/dist/src/ui/components/council-conclusion-card.js +420 -0
- package/dist/src/ui/components/council-debate-pill.d.ts +36 -0
- package/dist/src/ui/components/council-debate-pill.js +34 -0
- package/dist/src/ui/components/council-info-card.js +2 -2
- package/dist/src/ui/components/council-leader-bubble.d.ts +10 -3
- package/dist/src/ui/components/council-leader-bubble.js +21 -11
- package/dist/src/ui/components/council-message-bubble.d.ts +15 -2
- package/dist/src/ui/components/council-message-bubble.js +16 -15
- package/dist/src/ui/components/council-phase-timeline.d.ts +9 -1
- package/dist/src/ui/components/council-phase-timeline.js +66 -17
- package/dist/src/ui/components/council-placeholder-bubble.d.ts +16 -9
- package/dist/src/ui/components/council-placeholder-bubble.js +32 -29
- package/dist/src/ui/components/council-question-card.js +13 -12
- package/dist/src/ui/components/council-rail-rounds.d.ts +26 -0
- package/dist/src/ui/components/council-rail-rounds.js +57 -0
- package/dist/src/ui/components/council-round-group.d.ts +38 -0
- package/dist/src/ui/components/council-round-group.js +88 -0
- package/dist/src/ui/components/council-status-list.d.ts +3 -1
- package/dist/src/ui/components/council-status-list.js +36 -24
- package/dist/src/ui/components/council-synthesis-banner.d.ts +7 -2
- package/dist/src/ui/components/council-synthesis-banner.js +20 -5
- package/dist/src/ui/components/halt-recovery-card.js +9 -5
- package/dist/src/ui/components/jump-to-latest-pill.d.ts +11 -0
- package/dist/src/ui/components/jump-to-latest-pill.js +14 -0
- package/dist/src/ui/components/message-view.d.ts +15 -0
- package/dist/src/ui/components/message-view.js +50 -1
- package/dist/src/ui/components/prompt-box.js +18 -16
- package/dist/src/ui/components/session-tree-card.d.ts +14 -0
- package/dist/src/ui/components/session-tree-card.js +46 -0
- package/dist/src/ui/components/slash-inline-menu.d.ts +12 -0
- package/dist/src/ui/components/slash-inline-menu.js +26 -5
- package/dist/src/ui/components/task-list-panel.d.ts +14 -1
- package/dist/src/ui/components/task-list-panel.js +22 -2
- package/dist/src/ui/components/tool-group.d.ts +15 -3
- package/dist/src/ui/components/tool-group.js +69 -11
- package/dist/src/ui/containers/modals-layer.d.ts +4 -2
- package/dist/src/ui/containers/modals-layer.js +2 -2
- package/dist/src/ui/council-harness-event.d.ts +57 -0
- package/dist/src/ui/council-harness-event.js +46 -0
- package/dist/src/ui/heartbeat-debug.d.ts +29 -0
- package/dist/src/ui/heartbeat-debug.js +45 -0
- package/dist/src/ui/hooks/use-session-picker.d.ts +3 -3
- package/dist/src/ui/mcp-modal.js +2 -4
- package/dist/src/ui/modals/api-key-modal.js +1 -1
- package/dist/src/ui/modals/connect-modal.js +4 -3
- package/dist/src/ui/modals/model-picker-modal.d.ts +8 -18
- package/dist/src/ui/modals/model-picker-modal.js +8 -10
- package/dist/src/ui/modals/session-picker-modal.d.ts +2 -2
- package/dist/src/ui/modals/session-picker-modal.js +3 -5
- package/dist/src/ui/picker-providers.d.ts +1 -1
- package/dist/src/ui/picker-providers.js +1 -1
- package/dist/src/ui/primitives/index.d.ts +1 -0
- package/dist/src/ui/primitives/index.js +2 -0
- package/dist/src/ui/primitives/semantic-primitives.d.ts +76 -0
- package/dist/src/ui/primitives/semantic-primitives.js +81 -0
- package/dist/src/ui/slash/compact.js +5 -7
- package/dist/src/ui/slash/cost.js +1 -1
- package/dist/src/ui/slash/council.js +19 -1
- package/dist/src/ui/slash/debug.d.ts +3 -31
- package/dist/src/ui/slash/debug.js +9 -20
- package/dist/src/ui/slash/ee.js +81 -0
- package/dist/src/ui/slash/ideal.d.ts +6 -2
- package/dist/src/ui/slash/ideal.js +97 -7
- package/dist/src/ui/slash/menu-items.d.ts +7 -0
- package/dist/src/ui/slash/menu-items.js +23 -20
- package/dist/src/ui/slash/registry.d.ts +2 -0
- package/dist/src/ui/slash/registry.js +4 -0
- package/dist/src/ui/status-bar/cache-hit.d.ts +6 -0
- package/dist/src/ui/status-bar/cache-hit.js +9 -0
- package/dist/src/ui/status-bar/index.d.ts +1 -1
- package/dist/src/ui/status-bar/index.js +7 -3
- package/dist/src/ui/status-bar/usd-meter.d.ts +5 -4
- package/dist/src/ui/status-bar/usd-meter.js +6 -4
- package/dist/src/ui/theme.d.ts +1 -0
- package/dist/src/ui/theme.js +2 -0
- package/dist/src/ui/types.d.ts +7 -0
- package/dist/src/ui/use-app-logic.js +0 -0
- package/dist/src/ui/utils/agent-activities.d.ts +39 -0
- package/dist/src/ui/utils/agent-activities.js +96 -0
- package/dist/src/ui/utils/format.d.ts +14 -0
- package/dist/src/ui/utils/format.js +23 -3
- package/dist/src/ui/utils/group-tool-entries.d.ts +26 -0
- package/dist/src/ui/utils/group-tool-entries.js +111 -0
- package/dist/src/ui/utils/tool-summary.d.ts +21 -0
- package/dist/src/ui/utils/tool-summary.js +91 -0
- package/dist/src/usage/downgrade.js +2 -2
- package/dist/src/usage/product-ledger.js +2 -2
- package/dist/src/utils/event-loop-monitor.d.ts +85 -0
- package/dist/src/utils/event-loop-monitor.js +107 -0
- package/dist/src/utils/install-manager.js +2 -1
- package/dist/src/utils/llm-deadline.d.ts +14 -0
- package/dist/src/utils/llm-deadline.js +19 -0
- package/dist/src/utils/logger.js +2 -2
- package/dist/src/utils/loop-profiler.d.ts +102 -0
- package/dist/src/utils/loop-profiler.js +202 -0
- package/dist/src/utils/permission-mode.js +5 -3
- package/dist/src/utils/redactor.js +1 -1
- package/dist/src/utils/settings.d.ts +180 -5
- package/dist/src/utils/settings.js +271 -31
- package/dist/src/utils/side-question.d.ts +1 -2
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/visible-retry.d.ts +11 -0
- package/dist/src/utils/visible-retry.js +10 -1
- package/dist/src/verify/entrypoint.d.ts +1 -1
- package/dist/src/verify/entrypoint.js +52 -17
- package/dist/src/verify/orchestrator.d.ts +1 -1
- package/dist/src/verify/orchestrator.js +20 -3
- package/dist/src/verify/recipes.d.ts +13 -0
- package/dist/src/verify/recipes.js +15 -0
- package/package.json +134 -132
- package/dist/src/cli/bw-vault.d.ts +0 -55
- package/dist/src/cli/bw-vault.js +0 -133
- package/dist/src/mcp/ee-tools.d.ts +0 -46
- package/dist/src/mcp/ee-tools.js +0 -193
- package/dist/src/providers/auth/gcloud.d.ts +0 -28
- package/dist/src/providers/auth/gcloud.js +0 -102
- package/dist/src/providers/auth/gemini-oauth.d.ts +0 -82
- package/dist/src/providers/auth/gemini-oauth.js +0 -472
- package/dist/src/providers/gemini.d.ts +0 -11
- package/dist/src/providers/gemini.js +0 -45
- package/dist/src/providers/siliconflow-sse-repair.d.ts +0 -58
- package/dist/src/providers/siliconflow-sse-repair.js +0 -177
- package/dist/src/providers/strategies/google.strategy.d.ts +0 -22
- package/dist/src/providers/strategies/google.strategy.js +0 -174
- package/dist/src/providers/strategies/siliconflow.strategy.js +0 -29
- package/dist/src/ui/containers/chat-feed.d.ts +0 -40
- package/dist/src/ui/containers/chat-feed.js +0 -66
|
@@ -21,38 +21,334 @@
|
|
|
21
21
|
* projection check, not a retroactive one.
|
|
22
22
|
* - CB-2 (oscillation) is checked AFTER this sprint's score is known.
|
|
23
23
|
*/
|
|
24
|
+
import { existsSync } from "node:fs";
|
|
25
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
24
26
|
import * as path from "node:path";
|
|
25
27
|
import { prependDecisionsLock, readDecisionsLock } from "../council/decisions-lock.js";
|
|
26
28
|
import { runCouncil } from "../council/index.js";
|
|
29
|
+
import { resolveLeaderModel } from "../council/leader.js";
|
|
27
30
|
import { phaseDone, phaseError, phaseStart } from "../council/phase-events.js";
|
|
31
|
+
import { fireAndForgetWorkflowEvent } from "../ee/workflow-event.js";
|
|
28
32
|
import { readArtifact, writeArtifact } from "../flow/artifact-io.js";
|
|
33
|
+
import { renderResumeDigest, writeSprintOutcome, writeSprintVerify } from "../flow/run-artifacts.js";
|
|
34
|
+
import { isContextRailEnabled } from "../gsd/flags.js";
|
|
35
|
+
import { SPRINT_EXECUTION_MARKER } from "../pil/layer6-output.js";
|
|
29
36
|
import { detectProviderForModel } from "../providers/runtime.js";
|
|
30
|
-
import { logUIInteraction } from "../storage/index.js";
|
|
37
|
+
import { logInteraction, logUIInteraction } from "../storage/index.js";
|
|
31
38
|
import { commitToProduct, release } from "../usage/ledger.js";
|
|
32
39
|
import { CapBreachError } from "../usage/types.js";
|
|
40
|
+
import { getIsolatedTaskDeadlineMs, withDeadlineRace } from "../utils/llm-deadline.js";
|
|
33
41
|
import { runVerifyOrchestration } from "../verify/orchestrator.js";
|
|
34
42
|
import { appendIteration, readCriteria } from "./artifact-io.js";
|
|
35
43
|
import { formatUnverifiedForSprintContext, readLedger } from "./assumption-ledger.js";
|
|
36
44
|
import { readBacklog } from "./backlog-store.js";
|
|
37
45
|
import { CB1_costProjection, CB2_oscillation, CB3_verifyBlank } from "./circuit-breakers.js";
|
|
38
46
|
import { reserveForProduct } from "./cost-scoper.js";
|
|
47
|
+
import { extractAcceptanceCriteria, judgeCriteriaAgainstVerify, planQualityIssues, seedCriteriaFromPlan, } from "./criteria-seed.js";
|
|
39
48
|
import { formatProjectContextForPrompt } from "./discovery-context-format.js";
|
|
40
49
|
import { readProjectContext } from "./discovery-persistence.js";
|
|
41
50
|
import { evaluateDoneGate } from "./done-gate.js";
|
|
42
51
|
import { buildContinueFeedback } from "./feedback-routing.js";
|
|
52
|
+
import { idealTrace } from "./ideal-trace.js";
|
|
43
53
|
import { postSprintBoundary } from "./phase-tracker-bridge.js";
|
|
54
|
+
import { runPlanAdherenceReview } from "./plan-adherence-review.js";
|
|
44
55
|
import { computeProgressSnapshot, renderSnapshotMarkdown } from "./progress-snapshot.js";
|
|
45
56
|
import { appendRoleMemory } from "./role-memory.js";
|
|
46
57
|
import { loadVerifyFailureSignatures, recordVerifyFailureAndMaybePush } from "./verify-failure-tracking.js";
|
|
47
|
-
import { parseVerifyResult } from "./verify-result.js";
|
|
58
|
+
import { parseVerifyResult, VERIFY_PASS_MARKER } from "./verify-result.js";
|
|
48
59
|
// P3.7: track one-shot CB-2 retry bonus per run (keyed by runId).
|
|
49
60
|
// The Map is module-scoped so multiple sprints within the same run share state
|
|
50
61
|
// without touching DriverContext / IterationState shapes.
|
|
51
62
|
const _cb2RetryUsed = new Map();
|
|
63
|
+
/** Watchdog ceiling for the verify stage (ms). Override with MUONROI_SPRINT_VERIFY_TIMEOUT_MS. */
|
|
64
|
+
function getVerifyWatchdogTimeoutMs() {
|
|
65
|
+
const raw = process.env.MUONROI_SPRINT_VERIFY_TIMEOUT_MS;
|
|
66
|
+
const n = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
67
|
+
if (Number.isFinite(n) && n > 0)
|
|
68
|
+
return n;
|
|
69
|
+
return 10 * 60 * 1000; // 10 min default
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Bound the verify stage with a watchdog timeout.
|
|
73
|
+
*
|
|
74
|
+
* `runVerifyOrchestration` can hang indefinitely with no visible signal:
|
|
75
|
+
* `prepareVerifyRun` → `ensureVerifyCheckpoint` spawns the `shuru` sandbox
|
|
76
|
+
* (`spawnWithProgress("shuru", …)`) which stalls on hosts where shuru is
|
|
77
|
+
* unavailable/misconfigured (e.g. Windows), and the verify sub-agent itself has
|
|
78
|
+
* no TTFB timeout. Because sprint-runner previously called it as a bare
|
|
79
|
+
* `await runVerifyOrchestration(agent)` with NO abortSignal and NO timeout, a
|
|
80
|
+
* single hung verify BRICKED the whole /ideal run silently — no error, no
|
|
81
|
+
* recovery card — observed live as a 30+ min dead stall right after
|
|
82
|
+
* "Committed: N sprints planned" (the impl turn finished, verify never returned).
|
|
83
|
+
*
|
|
84
|
+
* On timeout we abort the sub-agent, log with context (No-Silent-Catch), and
|
|
85
|
+
* return an ERROR ToolResult so the sprint loop treats it as a failed verify
|
|
86
|
+
* (Step 5 → verifyVerdict FAIL/ERROR → feedback-routing) instead of hanging
|
|
87
|
+
* forever. The hung sandbox op may leak in the background, but the run recovers
|
|
88
|
+
* and the failure is surfaced + resumable. `onProgress` is forwarded to console
|
|
89
|
+
* so a future hang is diagnosable (e.g. "Creating checkpoint: <name>").
|
|
90
|
+
*/
|
|
91
|
+
async function runVerifyWithWatchdog(verifyAgent, runId, sprintN) {
|
|
92
|
+
const timeoutMs = getVerifyWatchdogTimeoutMs();
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
let timer;
|
|
95
|
+
const onProgress = (detail) => {
|
|
96
|
+
if (process.env.MUONROI_DEBUG_VERIFY === "1")
|
|
97
|
+
console.error(`[verify:sprint-${sprintN}] ${detail}`);
|
|
98
|
+
};
|
|
99
|
+
const timeout = new Promise((resolve) => {
|
|
100
|
+
timer = setTimeout(() => {
|
|
101
|
+
controller.abort();
|
|
102
|
+
const msg = `verify stage exceeded ${Math.round(timeoutMs / 1000)}s watchdog and was aborted ` +
|
|
103
|
+
`(sprint ${sprintN}, run ${runId}) — likely a hung sandbox checkpoint (shuru) or a ` +
|
|
104
|
+
`verify sub-agent LLM call with no TTFB timeout`;
|
|
105
|
+
console.error(`[sprint-runner] ${msg}`);
|
|
106
|
+
resolve({ success: false, output: "", error: `verify-timeout: ${msg}` });
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
return await Promise.race([
|
|
111
|
+
runVerifyOrchestration(verifyAgent, { abortSignal: controller.signal, onProgress }),
|
|
112
|
+
timeout,
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
console.error(`[sprint-runner] verify stage threw (sprint ${sprintN}, run ${runId}): ${message}`);
|
|
118
|
+
return { success: false, output: "", error: `verify-error: ${message}` };
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
if (timer)
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
52
125
|
/** @internal Test-only: reset CB-2 retry state for a given runId. */
|
|
53
126
|
export function _resetCb2RetryUsed(runId) {
|
|
54
127
|
_cb2RetryUsed.delete(runId);
|
|
55
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Idle-chunk ceiling for the implementation stage (ms). Override with
|
|
131
|
+
* MUONROI_SPRINT_IMPL_IDLE_MS. This is a TIME-SINCE-LAST-CHUNK budget, not a
|
|
132
|
+
* total-turn cap — a legitimately long implementation streams progress the
|
|
133
|
+
* whole way, so it may run for many minutes, but it must never go completely
|
|
134
|
+
* silent (no chunk at all) for this long.
|
|
135
|
+
*/
|
|
136
|
+
export function getImplIdleTimeoutMs() {
|
|
137
|
+
const raw = process.env.MUONROI_SPRINT_IMPL_IDLE_MS;
|
|
138
|
+
const n = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
139
|
+
if (Number.isFinite(n) && n > 0)
|
|
140
|
+
return n;
|
|
141
|
+
return 4 * 60 * 1000; // 4 min of total silence → treat the impl turn as stalled
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Hard total-elapsed ceiling for the implementation stage (ms). Override with
|
|
145
|
+
* MUONROI_SPRINT_IMPL_TOTAL_MS. Unlike the idle budget this is armed once and is
|
|
146
|
+
* NOT reset by chunks, so it catches a hang that keeps the idle guard alive with
|
|
147
|
+
* heartbeat/status chunks. Generous by default so a legitimately large sprint is
|
|
148
|
+
* not cut short; a genuine hang still terminates within this ceiling.
|
|
149
|
+
*/
|
|
150
|
+
export function getImplTotalTimeoutMs() {
|
|
151
|
+
const raw = process.env.MUONROI_SPRINT_IMPL_TOTAL_MS;
|
|
152
|
+
const n = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
153
|
+
if (Number.isFinite(n) && n > 0)
|
|
154
|
+
return n;
|
|
155
|
+
return 15 * 60 * 1000; // 15 min hard ceiling on a single impl turn
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Whether the implement stage runs in an ISOLATED bounded sub-agent context
|
|
159
|
+
* (ctx.runIsolatedTask) instead of the shared top-level turn (processMessageFn).
|
|
160
|
+
* Default ON. Disable with MUONROI_SPRINT_ISOLATED_IMPL=0.
|
|
161
|
+
*
|
|
162
|
+
* The isolated path is the fix for the live ctx-overflow wedge: the flat
|
|
163
|
+
* processMessageFn turn inherited the full council-debate history (~5.9M tokens
|
|
164
|
+
* observed), started implementation already at ~94% context, then wedged after a
|
|
165
|
+
* mid-turn compaction. A fresh child context (getSubAgentBudgetChars cap +
|
|
166
|
+
* independent in-loop compaction) never inherits the debate, so it starts near
|
|
167
|
+
* empty and its clutter is absorbed as one compact ToolResult.
|
|
168
|
+
*/
|
|
169
|
+
export function getSprintIsolatedImplEnabled() {
|
|
170
|
+
return process.env.MUONROI_SPRINT_ISOLATED_IMPL !== "0";
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Pure decision: use the isolated sub-agent path for the implement stage?
|
|
174
|
+
* True only when the flag is on AND the driver actually provides the bridge
|
|
175
|
+
* (legacy/test drivers omit runIsolatedTask → fall back to processMessageFn).
|
|
176
|
+
* Extracted for unit testing without spinning up a full runSprint.
|
|
177
|
+
*/
|
|
178
|
+
export function shouldUseIsolatedImpl(hasBridge, enabled = getSprintIsolatedImplEnabled()) {
|
|
179
|
+
return enabled && hasBridge;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Imperative execution directive prepended to the sprint plan before it is
|
|
183
|
+
* handed to the orchestrator. The raw plan synthesis is a declarative design
|
|
184
|
+
* document; without this prefix the impl turn narrates it back instead of
|
|
185
|
+
* applying edits. Exported for test assertion. @internal
|
|
186
|
+
*/
|
|
187
|
+
export const IMPL_EXECUTION_DIRECTIVE = `${SPRINT_EXECUTION_MARKER}\n\n` +
|
|
188
|
+
"You are the sprint IMPLEMENTER. EXECUTE the sprint plan below as an implementation task. Make the " +
|
|
189
|
+
"actual code changes NOW using your file-edit tools — read the target files, then edit/write them to " +
|
|
190
|
+
"apply every action item. Do NOT merely restate, summarize, or re-plan the design; apply the edits to " +
|
|
191
|
+
"the repository. Run the plan's own verification commands where given. Before you finish, self-verify " +
|
|
192
|
+
"as a reviewer would: confirm every target file named in the plan actually exists on disk with the " +
|
|
193
|
+
"intended change — do not stop with action items unaddressed. Stop only when the action items are " +
|
|
194
|
+
"implemented.\n\n" +
|
|
195
|
+
"--- SPRINT PLAN TO IMPLEMENT ---\n\n";
|
|
196
|
+
/**
|
|
197
|
+
* Wrap the implementation `processMessageFn` stream with an idle-chunk watchdog.
|
|
198
|
+
*
|
|
199
|
+
* Root cause it addresses (observed live 2026-07-08, /ideal resume of the
|
|
200
|
+
* gsd-core migration): the implementation stage delegates to the host
|
|
201
|
+
* orchestrator turn via `ctx.processMessageFn(implPrompt)` and consumes it with
|
|
202
|
+
* `for await (const chunk of implGen)`. The orchestrator turn finished its final
|
|
203
|
+
* LLM response cleanly (finishReason "stop", text-only) but the generator then
|
|
204
|
+
* suspended post-finish and never completed — the `for await` blocked for 17+
|
|
205
|
+
* minutes with NO chunk, NO phaseDone, NO advance to Verify, NO error. Because
|
|
206
|
+
* the LLM STREAM had already finished, the orchestrator's mid-stream
|
|
207
|
+
* time-to-next-chunk stall-watchdog does not fire — the hang is on the JS side
|
|
208
|
+
* after the stream terminator.
|
|
209
|
+
*
|
|
210
|
+
* TWO complementary guards (a single idle guard was observed live to be
|
|
211
|
+
* defeated: the impl created 2 files then emitted only non-progress heartbeat
|
|
212
|
+
* chunks for 9+ min, resetting a per-chunk idle timer without ever completing):
|
|
213
|
+
* - `idleMs` — resets on every yielded chunk; catches a TOTALLY silent stall
|
|
214
|
+
* (the post-finish hang above, zero chunks) quickly.
|
|
215
|
+
* - `totalMs` — armed ONCE at entry, NOT reset by chunks; a hard ceiling that
|
|
216
|
+
* fires even when heartbeat/status chunks keep the idle guard alive while no
|
|
217
|
+
* real progress is made.
|
|
218
|
+
* Either firing throws so the caller's existing try/catch converts the wedge
|
|
219
|
+
* into a visible phaseError (the sprint then surfaces + can recover), exactly
|
|
220
|
+
* like `runVerifyWithWatchdog` does for the verify stage. The suspended
|
|
221
|
+
* orchestrator promise may leak in the background, but the run recovers.
|
|
222
|
+
*/
|
|
223
|
+
export async function* withImplIdleWatchdog(gen, idleMs, sprintN, totalMs = getImplTotalTimeoutMs()) {
|
|
224
|
+
const it = gen[Symbol.asyncIterator]();
|
|
225
|
+
let totalTimer;
|
|
226
|
+
const total = new Promise((_, reject) => {
|
|
227
|
+
totalTimer = setTimeout(() => {
|
|
228
|
+
reject(new Error(`implementation stage exceeded ${Math.round(totalMs / 1000)}s total watchdog and was ` +
|
|
229
|
+
`treated as stalled (sprint ${sprintN}) — the orchestrator turn never completed ` +
|
|
230
|
+
`(likely hung after its final response while emitting only heartbeat chunks)`));
|
|
231
|
+
}, totalMs);
|
|
232
|
+
});
|
|
233
|
+
try {
|
|
234
|
+
while (true) {
|
|
235
|
+
let idleTimer;
|
|
236
|
+
const idle = new Promise((_, reject) => {
|
|
237
|
+
idleTimer = setTimeout(() => {
|
|
238
|
+
reject(new Error(`implementation stage produced no output for ${Math.round(idleMs / 1000)}s and was ` +
|
|
239
|
+
`treated as stalled (sprint ${sprintN}) — the orchestrator turn hung post-finish ` +
|
|
240
|
+
`(finished its LLM response but the generator never completed)`));
|
|
241
|
+
}, idleMs);
|
|
242
|
+
});
|
|
243
|
+
let res;
|
|
244
|
+
try {
|
|
245
|
+
res = await Promise.race([it.next(), idle, total]);
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
if (idleTimer)
|
|
249
|
+
clearTimeout(idleTimer);
|
|
250
|
+
}
|
|
251
|
+
if (res.done)
|
|
252
|
+
return;
|
|
253
|
+
yield res.value;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
if (totalTimer)
|
|
258
|
+
clearTimeout(totalTimer);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Wall-clock deadline for the ISOLATED implementation path.
|
|
263
|
+
*
|
|
264
|
+
* The isolated path (`ctx.runIsolatedTask`) returns a single Promise, not a
|
|
265
|
+
* stream, so `withImplIdleWatchdog` (which guards the streamed non-isolated
|
|
266
|
+
* path) cannot wrap it. Its only protection was the sub-agent's INTERNAL
|
|
267
|
+
* per-chunk stall-watchdog — which does NOT fire once the sub-agent's LLM stream
|
|
268
|
+
* has finished but its orchestrator turn hangs on the JS side afterwards (the
|
|
269
|
+
* exact "wrote N files then went silent" wedge documented on
|
|
270
|
+
* `withImplIdleWatchdog`). Observed live 2026-07-12 (run mrhc43f0fb9b): the
|
|
271
|
+
* isolated impl wrote 2 files, emitted its final `llm-done`, then wedged for 30+
|
|
272
|
+
* min with zero events and an idle process — because this `await` had no outer
|
|
273
|
+
* ceiling.
|
|
274
|
+
*
|
|
275
|
+
* This races the isolated task against a hard total-elapsed deadline. On
|
|
276
|
+
* timeout it rejects so the caller's existing try/catch converts the wedge into
|
|
277
|
+
* a visible phaseError (the sprint surfaces + can recover), mirroring what
|
|
278
|
+
* `withImplIdleWatchdog` / `runVerifyWithWatchdog` do for the other stages. The
|
|
279
|
+
* suspended sub-agent promise may leak in the background, but the run recovers.
|
|
280
|
+
* `totalMs <= 0` disables the guard (returns the task unchanged).
|
|
281
|
+
*/
|
|
282
|
+
/**
|
|
283
|
+
* Extract why an isolated implementation task failed, from its ToolResult.
|
|
284
|
+
*
|
|
285
|
+
* `output` is checked because StreamRunner reports EVERY sub-agent failure
|
|
286
|
+
* there — "Task failed: …" (stream-runner.ts:1061), "[Cancelled]" (:982), a
|
|
287
|
+
* provider stall (:988), an unknown-agent message (:265) — and never assigns
|
|
288
|
+
* `error`; grep stream-runner.ts for `error:` and there are no hits. Reading
|
|
289
|
+
* `error` alone made `result.error?.trim()` permanently undefined, so every
|
|
290
|
+
* distinct failure collapsed into the contentless fallback and two /ideal runs
|
|
291
|
+
* halted 1s into implementation with the cause already erased.
|
|
292
|
+
*
|
|
293
|
+
* `error` still wins when a caller does populate it — ToolResult declares the
|
|
294
|
+
* field, so a future non-StreamRunner producer may be more specific.
|
|
295
|
+
*/
|
|
296
|
+
export function resolveImplFailureReason(result) {
|
|
297
|
+
return result.error?.trim() || result.output?.trim() || "isolated implementation task failed";
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Persist an implementation-stage failure to `interaction_logs`.
|
|
301
|
+
*
|
|
302
|
+
* The implementation stage is where /ideal either ships code or does not, so its
|
|
303
|
+
* exception is the single most valuable line in a post-mortem — yet run
|
|
304
|
+
* mrn9yfle9801 halted with only `halt_card_open {trigger:"loop_throw"}` on
|
|
305
|
+
* record and the message itself unrecoverable: stderr belongs to the TUI child
|
|
306
|
+
* (the harness never captures it) and the council path writes no `messages`
|
|
307
|
+
* rows. `elapsedMs` is what separates the two indistinguishable causes — an
|
|
308
|
+
* immediate `!result.success` from a `withIsolatedImplDeadline` watchdog trip.
|
|
309
|
+
*
|
|
310
|
+
* Never throws: a broken audit trail must not take down the sprint it is
|
|
311
|
+
* describing.
|
|
312
|
+
*/
|
|
313
|
+
export function logSprintImplError(ctx, info) {
|
|
314
|
+
try {
|
|
315
|
+
logInteraction(ctx.sessionId ?? ctx.runId, "council", {
|
|
316
|
+
eventSubtype: "sprint_impl_error",
|
|
317
|
+
...(info.implModelId ? { model: info.implModelId } : {}),
|
|
318
|
+
durationMs: info.elapsedMs,
|
|
319
|
+
data: {
|
|
320
|
+
runId: ctx.runId,
|
|
321
|
+
sprintN: info.sprintN,
|
|
322
|
+
isolated: info.isolated,
|
|
323
|
+
message: info.message.slice(0, 2000),
|
|
324
|
+
stack: info.stack,
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
console.error(`[sprint-runner] failed to persist implementation error (sprint ${info.sprintN}): ${err instanceof Error ? err.message : String(err)}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
export async function withIsolatedImplDeadline(task, totalMs, sprintN) {
|
|
333
|
+
if (!(Number.isFinite(totalMs) && totalMs > 0))
|
|
334
|
+
return task;
|
|
335
|
+
let timer;
|
|
336
|
+
const deadline = new Promise((_, reject) => {
|
|
337
|
+
timer = setTimeout(() => {
|
|
338
|
+
reject(new Error(`isolated implementation stage exceeded ${Math.round(totalMs / 1000)}s total watchdog and was ` +
|
|
339
|
+
`treated as stalled (sprint ${sprintN}) — the isolated sub-agent turn never completed ` +
|
|
340
|
+
`(hung on the JS side after its final response; the isolated path has no per-chunk stall guard)`));
|
|
341
|
+
}, totalMs);
|
|
342
|
+
timer.unref?.();
|
|
343
|
+
});
|
|
344
|
+
try {
|
|
345
|
+
return await Promise.race([task, deadline]);
|
|
346
|
+
}
|
|
347
|
+
finally {
|
|
348
|
+
if (timer)
|
|
349
|
+
clearTimeout(timer);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
56
352
|
export { computeFailureSignature, loadVerifyFailureSignatures, pushFailureToEE, recordVerifyFailureAndMaybePush, saveVerifyFailureSignatures, } from "./verify-failure-tracking.js";
|
|
57
353
|
/**
|
|
58
354
|
* Run a single sprint. Yields StreamChunk events for the UI and returns the
|
|
@@ -61,6 +357,104 @@ export { computeFailureSignature, loadVerifyFailureSignatures, pushFailureToEE,
|
|
|
61
357
|
* Throws on circuit-breaker halt — caller (loop driver) catches and writes
|
|
62
358
|
* the appropriate halt state to manifest/state.
|
|
63
359
|
*/
|
|
360
|
+
/** Path to the persisted per-sprint plan synthesis (Wave 2). @internal */
|
|
361
|
+
export function sprintPlanPath(runDir, sprintN) {
|
|
362
|
+
return path.join(runDir, `sprint-${sprintN}-plan.md`);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Wave 2: read a persisted sprint plan if present. Returns "" when absent or on
|
|
366
|
+
* read error (caller then runs the planning council). Never throws.
|
|
367
|
+
*
|
|
368
|
+
* The planning council is non-deterministic — re-running it on a resumed/retried
|
|
369
|
+
* sprint produces a different design AND a different target folder, which is why
|
|
370
|
+
* the impl turn was observed re-scaffolding in a new location each run. Reusing
|
|
371
|
+
* the persisted plan makes per-sprint planning idempotent so the same target
|
|
372
|
+
* files are continued across resume.
|
|
373
|
+
*/
|
|
374
|
+
export async function readPersistedSprintPlan(planPath) {
|
|
375
|
+
try {
|
|
376
|
+
if (!existsSync(planPath))
|
|
377
|
+
return "";
|
|
378
|
+
return (await readFile(planPath, "utf8")).trim();
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
console.error(`[sprint-runner] readPersistedSprintPlan failed for ${planPath}: ${err.message}`);
|
|
382
|
+
return "";
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/** Wave 2: persist a sprint plan synthesis for idempotent resume. Never throws. */
|
|
386
|
+
export async function persistSprintPlan(planPath, synthesis) {
|
|
387
|
+
if (!synthesis.trim())
|
|
388
|
+
return;
|
|
389
|
+
try {
|
|
390
|
+
await writeFile(planPath, synthesis, "utf8");
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
console.error(`[sprint-runner] persistSprintPlan failed for ${planPath}: ${err.message}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Extract repo-relative target file paths a sprint plan names (src/…, packages/…,
|
|
398
|
+
* tests/…). Deduped, capped. Used by Wave 3 (existing targets → continue) and 4A
|
|
399
|
+
* (missing targets → completeness re-check). Never throws.
|
|
400
|
+
*/
|
|
401
|
+
export function extractPlanTargetPaths(planSynthesis, cap = 40) {
|
|
402
|
+
try {
|
|
403
|
+
const tokens = new Set();
|
|
404
|
+
const re = /\b((?:src|packages|tests|scripts|lib|app|apps)\/[\w./@-]+\.[a-z]{1,5})\b/gi;
|
|
405
|
+
let m = re.exec(planSynthesis);
|
|
406
|
+
while (m !== null) {
|
|
407
|
+
tokens.add(m[1].replace(/\\/g, "/"));
|
|
408
|
+
if (tokens.size >= cap)
|
|
409
|
+
break;
|
|
410
|
+
m = re.exec(planSynthesis);
|
|
411
|
+
}
|
|
412
|
+
return [...tokens];
|
|
413
|
+
}
|
|
414
|
+
catch (err) {
|
|
415
|
+
console.error(`[sprint-runner] extractPlanTargetPaths failed: ${err.message}`);
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Wave 3: plan-named target file paths that ALREADY EXIST on disk, so the impl
|
|
421
|
+
* turn continues them rather than re-scaffolding in a new location. Empty on a
|
|
422
|
+
* greenfield sprint (files don't exist yet) → no injection.
|
|
423
|
+
*/
|
|
424
|
+
export async function detectExistingPlanTargets(planSynthesis, cwd, cap = 20) {
|
|
425
|
+
const existing = [];
|
|
426
|
+
for (const t of extractPlanTargetPaths(planSynthesis)) {
|
|
427
|
+
if (existsSync(path.resolve(cwd, t)))
|
|
428
|
+
existing.push(t);
|
|
429
|
+
if (existing.length >= cap)
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
return existing;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* 4A: plan-named target file paths that STILL DO NOT EXIST after the impl turn —
|
|
436
|
+
* i.e. action items the implementer left unaddressed. Drives the post-impl
|
|
437
|
+
* completeness re-check (spend an extra turn ONLY when there is proven-incomplete
|
|
438
|
+
* work, unlike an unconditional reviewer pass). Empty ⇒ every named target landed.
|
|
439
|
+
*/
|
|
440
|
+
export async function computeMissingPlanTargets(planSynthesis, cwd, cap = 20) {
|
|
441
|
+
const missing = [];
|
|
442
|
+
for (const t of extractPlanTargetPaths(planSynthesis)) {
|
|
443
|
+
if (!existsSync(path.resolve(cwd, t)))
|
|
444
|
+
missing.push(t);
|
|
445
|
+
if (missing.length >= cap)
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
return missing;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* 4A completeness re-check toggle. Default ON; disable with
|
|
452
|
+
* MUONROI_SPRINT_IMPL_RECHECK=0. When on, and the impl turn left plan-named
|
|
453
|
+
* target files missing, ONE focused follow-up turn is spent to finish them.
|
|
454
|
+
*/
|
|
455
|
+
export function getImplRecheckEnabled() {
|
|
456
|
+
return process.env.MUONROI_SPRINT_IMPL_RECHECK !== "0";
|
|
457
|
+
}
|
|
64
458
|
export async function* runSprint(args) {
|
|
65
459
|
const { sprintN, ctx, productSpec, roleAssignments, history, carryOver, phaseScope } = args;
|
|
66
460
|
const runDir = path.join(ctx.flowDir, "runs", ctx.runId);
|
|
@@ -76,9 +470,54 @@ export async function* runSprint(args) {
|
|
|
76
470
|
// ── Step 2: Detect verify recipe BEFORE the planner spends any token ──────
|
|
77
471
|
// CB-3 fires deterministically on sprint 1 if recipe is null or coverage === 0.
|
|
78
472
|
const verifyAgent = buildVerifyAgent(ctx, cwd);
|
|
79
|
-
|
|
473
|
+
// Wall-clock backstop: `detectVerifyRecipe` runs a `verify-detect` LLM
|
|
474
|
+
// sub-agent turn (orchestrator.detectVerifyRecipe → runTaskRequest). Like the
|
|
475
|
+
// impl/verify stages, that turn can finish its stream then wedge on the JS side
|
|
476
|
+
// afterward — and this call site had NO deadline, so a single hung verify-detect
|
|
477
|
+
// turn bricked the entire /ideal run silently, right after "Committed: N sprints
|
|
478
|
+
// planned" and BEFORE the "Sprint N — Planning" yield (observed live 2026-07-13:
|
|
479
|
+
// 8+ min frozen frame, no forward progress). Race it against the shared isolated-
|
|
480
|
+
// task deadline; a timeout falls through to `null` → CB-3 emits the actionable
|
|
481
|
+
// recovery card instead of hanging. (The bridge signature does not thread an
|
|
482
|
+
// abortSignal, so this caller-side race is the guarantee.)
|
|
483
|
+
let verifyRecipe;
|
|
484
|
+
try {
|
|
485
|
+
verifyRecipe = await withDeadlineRace(() => verifyAgent.detectVerifyRecipe(verifyAgent.getSandboxSettings()), getIsolatedTaskDeadlineMs(), `sprint-${sprintN}-detect-verify`);
|
|
486
|
+
}
|
|
487
|
+
catch (err) {
|
|
488
|
+
console.error(`[sprint-runner] detectVerifyRecipe timed out/failed (sprint ${sprintN}, run ${ctx.runId}): ${err instanceof Error ? err.message : String(err)}`);
|
|
489
|
+
verifyRecipe = null;
|
|
490
|
+
}
|
|
80
491
|
const cb3 = CB3_verifyBlank(sprintN, verifyRecipe);
|
|
81
|
-
|
|
492
|
+
// Greenfield build-first (Task #8): on a fresh greenfield /ideal run the first
|
|
493
|
+
// sprint has nothing to verify yet — detectVerifyRecipe legitimately returns
|
|
494
|
+
// null / zero-coverage because the code and tests do not exist until THIS sprint
|
|
495
|
+
// builds them. Halting here (CB-3) would trap every greenfield idea before a
|
|
496
|
+
// single line is written. So for sprint 1 of a greenfield run, bypass the halt
|
|
497
|
+
// and let the implement stage scaffold the first increment; the verify stage
|
|
498
|
+
// re-detects the recipe from the code it creates (Step 5 reads
|
|
499
|
+
// verifyResult.verifyRecipe, not this one). The halt is preserved for EXISTING
|
|
500
|
+
// projects, where a missing recipe is a real "I can't tell how to test this"
|
|
501
|
+
// signal that warrants the recovery card. Opt out with
|
|
502
|
+
// MUONROI_IDEAL_GREENFIELD_BUILD_FIRST=0.
|
|
503
|
+
let greenfieldBuildFirst = false;
|
|
504
|
+
if (cb3.halt && sprintN === 1 && process.env.MUONROI_IDEAL_GREENFIELD_BUILD_FIRST !== "0") {
|
|
505
|
+
try {
|
|
506
|
+
const pc = await readProjectContext(ctx.flowDir, ctx.runId);
|
|
507
|
+
greenfieldBuildFirst = pc?.detection?.classification === "greenfield";
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
greenfieldBuildFirst = false;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (greenfieldBuildFirst) {
|
|
514
|
+
yield {
|
|
515
|
+
type: "content",
|
|
516
|
+
content: `\n> Greenfield: no verify recipe exists yet (nothing is built). Proceeding to build the ` +
|
|
517
|
+
`first increment — it will be verified against the code and tests this sprint creates.\n`,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
if (cb3.halt && !greenfieldBuildFirst) {
|
|
82
521
|
// Yield a structured halt chunk so the TUI can render an actionable recovery
|
|
83
522
|
// card (Task 5.2). Do NOT throw — callers must discriminate on chunk.type.
|
|
84
523
|
const haltChunk = {
|
|
@@ -216,15 +655,82 @@ export async function* runSprint(args) {
|
|
|
216
655
|
const noopProcess = async function* () {
|
|
217
656
|
/* no host orchestrator wired during planning */
|
|
218
657
|
};
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
658
|
+
// Wave 2 (2026-07-08): reuse a persisted per-sprint plan if one exists, making
|
|
659
|
+
// per-sprint planning idempotent across resume/retry. Without this the
|
|
660
|
+
// non-deterministic planning council re-ran on every runSprint call and emitted
|
|
661
|
+
// a different design → a different target folder each time (run1 src/council/,
|
|
662
|
+
// run4 src/engine/), so the impl turn re-scaffolded instead of continuing.
|
|
663
|
+
const planPath = sprintPlanPath(runDir, sprintN);
|
|
664
|
+
let planSynthesis = await readPersistedSprintPlan(planPath);
|
|
665
|
+
if (planSynthesis) {
|
|
666
|
+
idealTrace("sprint.planCouncil.reused", { runId: ctx.runId, sprintN, planSynthesisLen: planSynthesis.length });
|
|
667
|
+
yield {
|
|
668
|
+
type: "content",
|
|
669
|
+
content: `\n> [sprint-plan] Reusing persisted plan for sprint ${sprintN} (${planSynthesis.length} chars) — re-planning skipped so the same target files are continued.\n`,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
idealTrace("sprint.planCouncil.before", { runId: ctx.runId, sprintN });
|
|
674
|
+
const planGen = runCouncil(councilTopic, sessionModelId, [], ctx.runId, productLlm, ctx.respondToQuestion, ctx.respondToPreflight, ctx.processMessageFn ?? noopProcess, {
|
|
675
|
+
skipClarification: true,
|
|
676
|
+
cwd,
|
|
677
|
+
runDir,
|
|
678
|
+
suppressInlineMeta: isContextRailEnabled(),
|
|
679
|
+
// The product plan + spec were already debated (CB-1) and approved at the
|
|
680
|
+
// `/ideal` preflight. Re-gating and re-researching each sprint's internal
|
|
681
|
+
// plan strands the loop before implementation is ever reached (the exact
|
|
682
|
+
// "debate great, never implements" symptom). Auto-approve the per-sprint
|
|
683
|
+
// plan and reuse CB-1 research; the post-sprint customer verdict still lets
|
|
684
|
+
// the user review each sprint's OUTPUT.
|
|
685
|
+
autoApprovePreflight: true,
|
|
686
|
+
skipResearch: true,
|
|
687
|
+
// Automated per-sprint planning: suppress the interactive post-debate menu
|
|
688
|
+
// (it stranded the sprint before implementation — blocker 4/5) and skip the
|
|
689
|
+
// session-scoped persistence that FK-fails on the product-run id. The plan
|
|
690
|
+
// is auto-locked and control returns here for the Implementation stage.
|
|
691
|
+
sprintPlanningMode: true,
|
|
692
|
+
});
|
|
693
|
+
while (true) {
|
|
694
|
+
const step = await planGen.next();
|
|
695
|
+
if (step.done) {
|
|
696
|
+
planSynthesis = step.value ?? "";
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
yield step.value;
|
|
700
|
+
}
|
|
701
|
+
idealTrace("sprint.planCouncil.after", { runId: ctx.runId, sprintN, planSynthesisLen: planSynthesis.length });
|
|
702
|
+
// Persist so a resumed/retried sprint reuses this exact plan (and target folder).
|
|
703
|
+
await persistSprintPlan(planPath, planSynthesis);
|
|
704
|
+
}
|
|
705
|
+
// Plan-fidelity fix: seed the plan's acceptance_criteria into the criteria store
|
|
706
|
+
// so the done-gate scores against REAL criteria (previously readCriteria returned
|
|
707
|
+
// [] → score always 0.00 → no gate on plan divergence). Idempotent + non-clobbering.
|
|
708
|
+
// Also run a NON-BLOCKING plan-quality check (per-sprint plans are auto-approved
|
|
709
|
+
// with no gate) and fold any issues into a corrective note for the impl prompt.
|
|
710
|
+
let planQualityNote = "";
|
|
711
|
+
try {
|
|
712
|
+
const planCriteria = extractAcceptanceCriteria(planSynthesis ?? "");
|
|
713
|
+
const seeded = await seedCriteriaFromPlan(ctx.flowDir, ctx.runId, planCriteria, sprintN);
|
|
714
|
+
if (seeded > 0) {
|
|
715
|
+
yield {
|
|
716
|
+
type: "content",
|
|
717
|
+
content: `\n> [criteria] Seeded ${seeded} acceptance criteria from the sprint plan (done-gate now scores against them).\n`,
|
|
718
|
+
};
|
|
226
719
|
}
|
|
227
|
-
|
|
720
|
+
const issues = planQualityIssues(planSynthesis ?? "", seeded);
|
|
721
|
+
if (issues.length > 0) {
|
|
722
|
+
planQualityNote =
|
|
723
|
+
`\n\n--- PLAN QUALITY WARNINGS (address these while implementing) ---\n` +
|
|
724
|
+
issues.map((i) => `- ${i}`).join("\n") +
|
|
725
|
+
`\nImplement to satisfy the phase goal and every acceptance criterion; do not stop at scaffolding.\n`;
|
|
726
|
+
yield {
|
|
727
|
+
type: "content",
|
|
728
|
+
content: `\n> [plan-check] ${issues.length} plan-quality warning(s): ${issues.join("; ")}\n`,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
/* non-critical — a missing criteria seed degrades to the prior empty-criteria behavior */
|
|
228
734
|
}
|
|
229
735
|
// P4-C: close the planning phase row before opening implementation.
|
|
230
736
|
yield phaseDone({
|
|
@@ -234,6 +740,7 @@ export async function* runSprint(args) {
|
|
|
234
740
|
startedAt: planStartedAt,
|
|
235
741
|
});
|
|
236
742
|
// ── Step 4: Implement stage — pipe plan through host process loop ─────────
|
|
743
|
+
idealTrace("sprint.implementation.enter", { runId: ctx.runId, sprintN, planSynthesisLen: planSynthesis.length });
|
|
237
744
|
yield { type: "content", content: `\n## Sprint ${sprintN} — Implementation\n` };
|
|
238
745
|
const implPhaseId = `sprint-${sprintN}-implementation`;
|
|
239
746
|
const implStartedAt = Date.now();
|
|
@@ -262,13 +769,28 @@ export async function* runSprint(args) {
|
|
|
262
769
|
subtype: "sprint_stage",
|
|
263
770
|
data: { sprintIndex: sprintN, stage: "implementation", runId: ctx.runId },
|
|
264
771
|
});
|
|
772
|
+
// Defect fix (2026-07-08): the raw plan synthesis is a DECLARATIVE design
|
|
773
|
+
// document ("## Agreed Architecture / Function Signatures / Acceptance
|
|
774
|
+
// Criteria"). Passed verbatim as the orchestrator message it reads as
|
|
775
|
+
// something to discuss, so the impl turn narrated the plan back as markdown
|
|
776
|
+
// (finishReason "stop", zero edits) instead of applying it — observed live on
|
|
777
|
+
// the gsd-core migration. Prepend an explicit execution directive (module-level
|
|
778
|
+
// IMPL_EXECUTION_DIRECTIVE) so the PIL classifier routes it to the
|
|
779
|
+
// implement/edit path, not the respond path.
|
|
265
780
|
// C2: Pre-impl gate — read decisions.lock.md and prepend to implementation prompt.
|
|
266
781
|
// When lock file is missing (greenfield / no council with runDir), pass-through unchanged.
|
|
267
|
-
let implPrompt = planSynthesis;
|
|
782
|
+
let implPrompt = planSynthesis.trim() ? IMPL_EXECUTION_DIRECTIVE + planSynthesis + planQualityNote : planSynthesis;
|
|
268
783
|
try {
|
|
269
784
|
const lockContent = await readDecisionsLock(runDir);
|
|
270
785
|
if (lockContent) {
|
|
271
|
-
|
|
786
|
+
// Prepend the lock to the DIRECTIVE-carrying implPrompt, NOT the bare
|
|
787
|
+
// planSynthesis. Passing planSynthesis here (the original 2026-07-08 C2
|
|
788
|
+
// gate bug) silently dropped IMPL_EXECUTION_DIRECTIVE + its
|
|
789
|
+
// SPRINT_EXECUTION_MARKER, so every council-backed sprint (a lock always
|
|
790
|
+
// exists once the council ran) reached the orchestrator as a bare design
|
|
791
|
+
// doc: the impl turn narrated the plan instead of executing it, classified
|
|
792
|
+
// taskType=null (4_096 output cap), then wedged on finishReason:"length".
|
|
793
|
+
implPrompt = prependDecisionsLock(implPrompt, lockContent);
|
|
272
794
|
yield {
|
|
273
795
|
type: "content",
|
|
274
796
|
content: "\n> [decisions.lock.md] Locked decisions prepended to implementation prompt.\n",
|
|
@@ -278,16 +800,85 @@ export async function* runSprint(args) {
|
|
|
278
800
|
catch {
|
|
279
801
|
/* fail-open — lock read failure must not block implementation */
|
|
280
802
|
}
|
|
803
|
+
// Wave 3 (2026-07-08): the impl turn was blind to files a prior sprint/run had
|
|
804
|
+
// already created, so it re-created them from scratch. Tell it which of the
|
|
805
|
+
// plan's OWN named target files already exist on disk so it reads + continues
|
|
806
|
+
// them instead of re-scaffolding. Empty on greenfield (nothing exists yet).
|
|
807
|
+
if (planSynthesis.trim()) {
|
|
808
|
+
const existingTargets = await detectExistingPlanTargets(planSynthesis, cwd);
|
|
809
|
+
if (existingTargets.length > 0) {
|
|
810
|
+
implPrompt = `${implPrompt}\n\n--- FILES ALREADY PRESENT ON DISK (prior-sprint work — READ and CONTINUE these; do NOT recreate them from scratch) ---\n${existingTargets
|
|
811
|
+
.map((f) => `- ${f}`)
|
|
812
|
+
.join("\n")}\n`;
|
|
813
|
+
yield {
|
|
814
|
+
type: "content",
|
|
815
|
+
content: `\n> [continuation] ${existingTargets.length} plan target file(s) already exist — instructed to continue, not recreate.\n`,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
}
|
|
281
819
|
let implError = null;
|
|
820
|
+
let implErrorStack;
|
|
282
821
|
if (ctx.processMessageFn && implPrompt.trim()) {
|
|
822
|
+
const useIsolated = shouldUseIsolatedImpl(!!ctx.runIsolatedTask);
|
|
283
823
|
try {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
824
|
+
if (useIsolated && ctx.runIsolatedTask) {
|
|
825
|
+
// ISOLATED path — run the sprint plan in a fresh, budget-capped child
|
|
826
|
+
// context that does NOT inherit the council-debate history. This is the
|
|
827
|
+
// fix for the ctx-overflow wedge: the sub-agent starts near-empty, has
|
|
828
|
+
// full tool access (edit/bash), compacts independently in-loop, and
|
|
829
|
+
// returns a compact ToolResult (its tool clutter is absorbed, not piped
|
|
830
|
+
// into the parent). No stream to watchdog — the sub-agent has its own
|
|
831
|
+
// stall + no-forward-progress guards (stall-watchdog.ts).
|
|
832
|
+
yield {
|
|
833
|
+
type: "content",
|
|
834
|
+
content: "\n> [isolated impl] Executing the sprint in a fresh sub-agent context " +
|
|
835
|
+
"(anti-overflow: does not inherit the debate history).\n",
|
|
836
|
+
};
|
|
837
|
+
// Plan-fidelity fix: allow the implementation turn to run on a stronger
|
|
838
|
+
// model than the cheap session tier (which failed to faithfully follow a
|
|
839
|
+
// rich plan). Opt-in via MUONROI_IDEAL_IMPL_MODEL; defaults to the session
|
|
840
|
+
// model so the cheap-model philosophy stays the default.
|
|
841
|
+
const implModelId = process.env.MUONROI_IDEAL_IMPL_MODEL?.trim() || ctx.sessionModelId;
|
|
842
|
+
if (implModelId !== ctx.sessionModelId) {
|
|
843
|
+
yield {
|
|
844
|
+
type: "content",
|
|
845
|
+
content: `\n> [impl-model] Running implementation on ${implModelId} (override of session model ${ctx.sessionModelId}).\n`,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
// Wall-clock deadline: the isolated path has no per-chunk stall guard,
|
|
849
|
+
// so a post-finish JS-side hang would wedge this await forever (observed
|
|
850
|
+
// live, run mrhc43f0fb9b). Racing the total-elapsed ceiling turns a wedge
|
|
851
|
+
// into a phaseError via the try/catch below. See withIsolatedImplDeadline.
|
|
852
|
+
const result = await withIsolatedImplDeadline(ctx.runIsolatedTask({
|
|
853
|
+
agent: "general",
|
|
854
|
+
description: `Sprint ${sprintN} implementation`,
|
|
855
|
+
prompt: implPrompt,
|
|
856
|
+
modelId: implModelId,
|
|
857
|
+
}), getImplTotalTimeoutMs(), sprintN);
|
|
858
|
+
if (!result.success) {
|
|
859
|
+
implError = resolveImplFailureReason(result);
|
|
860
|
+
}
|
|
861
|
+
else if (result.output?.trim()) {
|
|
862
|
+
yield { type: "content", content: `\n${result.output.trim()}\n` };
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
const implGen = ctx.processMessageFn(implPrompt);
|
|
867
|
+
// Guard the impl turn with an idle-chunk watchdog so a post-finish
|
|
868
|
+
// orchestrator hang surfaces as a phaseError instead of a silent wedge.
|
|
869
|
+
for await (const chunk of withImplIdleWatchdog(implGen, getImplIdleTimeoutMs(), sprintN)) {
|
|
870
|
+
yield chunk;
|
|
871
|
+
}
|
|
287
872
|
}
|
|
288
873
|
}
|
|
289
874
|
catch (e) {
|
|
290
875
|
implError = e instanceof Error ? e.message : String(e);
|
|
876
|
+
implErrorStack = e instanceof Error ? e.stack?.split("\n").slice(0, 4).join(" | ") : undefined;
|
|
877
|
+
// No-Silent-Catch: the finally below surfaces a phaseError chunk, but log
|
|
878
|
+
// here too so the hang/failure is diagnosable from stderr / MUONROI logs.
|
|
879
|
+
// Persisting happens at the single convergence point below — a thrown
|
|
880
|
+
// error and a `!result.success` return must not log differently.
|
|
881
|
+
console.error(`[sprint-runner] implementation stage failed (sprint ${sprintN}, run ${ctx.runId}): ${implError}`);
|
|
291
882
|
}
|
|
292
883
|
finally {
|
|
293
884
|
// A3 FIX: phaseDone for implementation MUST always fire, even when
|
|
@@ -326,8 +917,145 @@ export async function* runSprint(args) {
|
|
|
326
917
|
});
|
|
327
918
|
}
|
|
328
919
|
if (implError) {
|
|
920
|
+
// The ONE place both failure shapes converge. The catch above handles a
|
|
921
|
+
// thrown error; a `!result.success` return never reaches it and instead
|
|
922
|
+
// falls through to here — which is why persisting from inside the catch
|
|
923
|
+
// recorded nothing for the two runs that actually failed. This throw
|
|
924
|
+
// escapes to the UI's loop-level catch (use-app-logic.tsx), which renders
|
|
925
|
+
// the message but persists only {reason, trigger, sprintN} — no text. So
|
|
926
|
+
// this is the last point at which the reason still exists.
|
|
927
|
+
logSprintImplError(ctx, {
|
|
928
|
+
sprintN,
|
|
929
|
+
message: implError,
|
|
930
|
+
stack: implErrorStack,
|
|
931
|
+
implModelId: process.env.MUONROI_IDEAL_IMPL_MODEL?.trim() || ctx.sessionModelId,
|
|
932
|
+
elapsedMs: Date.now() - implStartedAt,
|
|
933
|
+
isolated: shouldUseIsolatedImpl(!!ctx.runIsolatedTask),
|
|
934
|
+
});
|
|
329
935
|
throw new Error(implError);
|
|
330
936
|
}
|
|
937
|
+
// ── Step 4b: 4A completeness re-check ─────────────────────────────────────
|
|
938
|
+
// The impl turn can "finish" (finishReason stop) with plan action items
|
|
939
|
+
// unaddressed — narrated but not applied. Rather than an unconditional
|
|
940
|
+
// (2-3x cost) reviewer pass, spend ONE focused follow-up turn ONLY when
|
|
941
|
+
// plan-named target files are provably still missing on disk. No missing
|
|
942
|
+
// targets ⇒ no extra turn (the resume/migration case where the targets already
|
|
943
|
+
// exist is a no-op). A re-check failure never fails the sprint — the primary
|
|
944
|
+
// impl already succeeded and verify/tests are the real gate.
|
|
945
|
+
if (ctx.processMessageFn && getImplRecheckEnabled() && planSynthesis.trim()) {
|
|
946
|
+
const missing = await computeMissingPlanTargets(planSynthesis, cwd);
|
|
947
|
+
if (missing.length > 0) {
|
|
948
|
+
idealTrace("sprint.implementation.recheck", { runId: ctx.runId, sprintN, missing: missing.length });
|
|
949
|
+
const recheckPhaseId = `sprint-${sprintN}-impl-recheck`;
|
|
950
|
+
const recheckStartedAt = Date.now();
|
|
951
|
+
yield phaseStart({
|
|
952
|
+
phaseId: recheckPhaseId,
|
|
953
|
+
kind: "sprint_stage",
|
|
954
|
+
label: `Sprint ${sprintN} — Completeness re-check`,
|
|
955
|
+
detail: `${missing.length} plan target(s) still missing — finishing`,
|
|
956
|
+
startedAt: recheckStartedAt,
|
|
957
|
+
});
|
|
958
|
+
const recheckPrompt = "The sprint plan named these target files but they DO NOT exist on disk yet — the sprint is NOT " +
|
|
959
|
+
"finished. Create/complete each one NOW using your file-edit tools. Do NOT explain or re-plan; " +
|
|
960
|
+
"make the edits.\n" +
|
|
961
|
+
missing.map((f) => `- ${f}`).join("\n") +
|
|
962
|
+
"\n";
|
|
963
|
+
let recheckErr = null;
|
|
964
|
+
try {
|
|
965
|
+
const recheckGen = ctx.processMessageFn(recheckPrompt);
|
|
966
|
+
for await (const chunk of withImplIdleWatchdog(recheckGen, getImplIdleTimeoutMs(), sprintN)) {
|
|
967
|
+
yield chunk;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
catch (e) {
|
|
971
|
+
recheckErr = e instanceof Error ? e.message : String(e);
|
|
972
|
+
console.error(`[sprint-runner] impl completeness re-check failed (sprint ${sprintN}, run ${ctx.runId}): ${recheckErr}`);
|
|
973
|
+
}
|
|
974
|
+
finally {
|
|
975
|
+
if (recheckErr) {
|
|
976
|
+
yield phaseError({
|
|
977
|
+
phaseId: recheckPhaseId,
|
|
978
|
+
kind: "sprint_stage",
|
|
979
|
+
label: `Sprint ${sprintN} — Completeness re-check`,
|
|
980
|
+
startedAt: recheckStartedAt,
|
|
981
|
+
errorMessage: recheckErr,
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
yield phaseDone({
|
|
986
|
+
phaseId: recheckPhaseId,
|
|
987
|
+
kind: "sprint_stage",
|
|
988
|
+
label: `Sprint ${sprintN} — Completeness re-check`,
|
|
989
|
+
startedAt: recheckStartedAt,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
const stillMissing = await computeMissingPlanTargets(planSynthesis, cwd);
|
|
994
|
+
idealTrace("sprint.implementation.recheck.after", {
|
|
995
|
+
runId: ctx.runId,
|
|
996
|
+
sprintN,
|
|
997
|
+
stillMissing: stillMissing.length,
|
|
998
|
+
});
|
|
999
|
+
if (stillMissing.length > 0) {
|
|
1000
|
+
yield {
|
|
1001
|
+
type: "content",
|
|
1002
|
+
content: `\n> [completeness] ${stillMissing.length} plan target(s) still missing after re-check — deferring to verify.\n`,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
// ── Step 4c: Plan-adherence review gate (strong reviewer → cheap fixer) ────
|
|
1008
|
+
// A high-tier reviewer checks the diff against the approved plan; deviations are
|
|
1009
|
+
// handed to a lower-tier fixer and re-reviewed (bounded). Opt out with
|
|
1010
|
+
// MUONROI_IDEAL_ADHERENCE_REVIEW=0. Never halts — verify + the criteria done-gate
|
|
1011
|
+
// remain the hard gates; this tightens plan fidelity before verification so a
|
|
1012
|
+
// cheap implementer's divergence is caught and corrected, not shipped.
|
|
1013
|
+
// Plan deviations that survive the bounded fixer rounds — carried into the next
|
|
1014
|
+
// sprint's focus (Step 9) so "chưa tuân thủ" work continues rather than being
|
|
1015
|
+
// silently dropped after the review.
|
|
1016
|
+
let residualPlanDeviations = [];
|
|
1017
|
+
if (ctx.runIsolatedTask && planSynthesis.trim() && process.env.MUONROI_IDEAL_ADHERENCE_REVIEW !== "0") {
|
|
1018
|
+
const adhPhaseId = `sprint-${sprintN}-adherence`;
|
|
1019
|
+
const adhStartedAt = Date.now();
|
|
1020
|
+
yield phaseStart({
|
|
1021
|
+
phaseId: adhPhaseId,
|
|
1022
|
+
kind: "sprint_stage",
|
|
1023
|
+
label: `Sprint ${sprintN} — Plan-adherence review`,
|
|
1024
|
+
startedAt: adhStartedAt,
|
|
1025
|
+
});
|
|
1026
|
+
try {
|
|
1027
|
+
const reviewModelId = process.env.MUONROI_IDEAL_REVIEW_MODEL?.trim() || resolveLeaderModel(ctx.sessionModelId);
|
|
1028
|
+
const verdict = yield* runPlanAdherenceReview({
|
|
1029
|
+
sprintN,
|
|
1030
|
+
planSynthesis,
|
|
1031
|
+
cwd,
|
|
1032
|
+
reviewModelId,
|
|
1033
|
+
fixModelId: ctx.sessionModelId,
|
|
1034
|
+
runIsolatedTask: ctx.runIsolatedTask,
|
|
1035
|
+
maxRounds: Number.parseInt(process.env.MUONROI_IDEAL_ADHERENCE_ROUNDS ?? "2", 10) || 2,
|
|
1036
|
+
});
|
|
1037
|
+
idealTrace("sprint.adherence.after", {
|
|
1038
|
+
runId: ctx.runId,
|
|
1039
|
+
sprintN,
|
|
1040
|
+
rounds: verdict.rounds,
|
|
1041
|
+
adherent: verdict.adherent,
|
|
1042
|
+
deviations: verdict.deviations.length,
|
|
1043
|
+
});
|
|
1044
|
+
if (!verdict.adherent)
|
|
1045
|
+
residualPlanDeviations = verdict.deviations;
|
|
1046
|
+
}
|
|
1047
|
+
catch (err) {
|
|
1048
|
+
console.error(`[sprint-runner] plan-adherence review failed (sprint ${sprintN}): ${err.message}`);
|
|
1049
|
+
}
|
|
1050
|
+
finally {
|
|
1051
|
+
yield phaseDone({
|
|
1052
|
+
phaseId: adhPhaseId,
|
|
1053
|
+
kind: "sprint_stage",
|
|
1054
|
+
label: `Sprint ${sprintN} — Plan-adherence review`,
|
|
1055
|
+
startedAt: adhStartedAt,
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
331
1059
|
// ── Step 5: Verify stage ──────────────────────────────────────────────────
|
|
332
1060
|
yield { type: "content", content: `\n## Sprint ${sprintN} — Verification\n` };
|
|
333
1061
|
const verifyPhaseId = `sprint-${sprintN}-verification`;
|
|
@@ -351,7 +1079,30 @@ export async function* runSprint(args) {
|
|
|
351
1079
|
subtype: "sprint_stage",
|
|
352
1080
|
data: { sprintIndex: sprintN, stage: "verification", runId: ctx.runId },
|
|
353
1081
|
});
|
|
354
|
-
|
|
1082
|
+
// A — "Skip verify" recovery option: the user chose to bypass a broken verify
|
|
1083
|
+
// stage (e.g. shuru sandbox unavailable on Windows that hangs the watchdog
|
|
1084
|
+
// every sprint). Treat verify as a PASS with an explicit synthetic output so
|
|
1085
|
+
// the done-gate is not blocked, and log loudly so the bypass is auditable.
|
|
1086
|
+
// The env var is set by the recovery-card handler and reset on the next fresh
|
|
1087
|
+
// `/ideal "<idea>"` start, so a new run re-enables verification.
|
|
1088
|
+
const skipVerify = process.env.MUONROI_SPRINT_SKIP_VERIFY === "1";
|
|
1089
|
+
let verifyResult;
|
|
1090
|
+
if (skipVerify) {
|
|
1091
|
+
console.error(`[sprint-runner] MUONROI_SPRINT_SKIP_VERIFY=1 — verify stage bypassed (sprint ${sprintN}, run ${ctx.runId})`);
|
|
1092
|
+
verifyResult = {
|
|
1093
|
+
success: true,
|
|
1094
|
+
// Include the canonical PASS marker so parseVerifyResult → PASS (the user
|
|
1095
|
+
// explicitly opted to treat verify as satisfied for this recovery).
|
|
1096
|
+
output: `${VERIFY_PASS_MARKER}\nverify skipped by user recovery choice (MUONROI_SPRINT_SKIP_VERIFY=1)`,
|
|
1097
|
+
};
|
|
1098
|
+
yield {
|
|
1099
|
+
type: "content",
|
|
1100
|
+
content: `\n> [skip-verify] Verify stage bypassed for sprint ${sprintN} (user recovery choice).\n`,
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
verifyResult = await runVerifyWithWatchdog(verifyAgent, ctx.runId, sprintN);
|
|
1105
|
+
}
|
|
355
1106
|
yield phaseDone({
|
|
356
1107
|
phaseId: verifyPhaseId,
|
|
357
1108
|
kind: "sprint_stage",
|
|
@@ -433,6 +1184,42 @@ export async function* runSprint(args) {
|
|
|
433
1184
|
subtype: "sprint_stage",
|
|
434
1185
|
data: { sprintIndex: sprintN, stage: "judgment", runId: ctx.runId },
|
|
435
1186
|
});
|
|
1187
|
+
// Plan-fidelity fix: judge the seeded acceptance criteria against what was
|
|
1188
|
+
// actually built (verify output + diff) BEFORE the done-gate reads them.
|
|
1189
|
+
// Without this the criteria stay "unmet" forever → score 0.00 and the gate can
|
|
1190
|
+
// never distinguish an on-plan sprint from a divergent one. Only upgrades on a
|
|
1191
|
+
// PASSing verify with concrete evidence (see judgeCriteriaAgainstVerify).
|
|
1192
|
+
try {
|
|
1193
|
+
const judgeModelId = roleAssignments.get("Reviewer")?.modelId ?? roleAssignments.get("PO")?.modelId ?? ctx.sessionModelId;
|
|
1194
|
+
let diffSummary = "";
|
|
1195
|
+
try {
|
|
1196
|
+
const { spawnSync } = await import("node:child_process");
|
|
1197
|
+
const stat = spawnSync("git", ["diff", "--stat", "HEAD"], { cwd, encoding: "utf8", timeout: 15000 });
|
|
1198
|
+
diffSummary = (stat.stdout ?? "").slice(0, 4000) || "(no diff detected)";
|
|
1199
|
+
}
|
|
1200
|
+
catch {
|
|
1201
|
+
diffSummary = "(diff unavailable)";
|
|
1202
|
+
}
|
|
1203
|
+
const verifyOutputForJudge = (verifyResult.error?.trim() ? verifyResult.error : (verifyResult.output ?? "")).trim();
|
|
1204
|
+
const { judged, total } = await judgeCriteriaAgainstVerify({
|
|
1205
|
+
flowDir: ctx.flowDir,
|
|
1206
|
+
runId: ctx.runId,
|
|
1207
|
+
llm: productLlm,
|
|
1208
|
+
modelId: judgeModelId,
|
|
1209
|
+
verifyVerdict,
|
|
1210
|
+
verifyOutput: verifyOutputForJudge,
|
|
1211
|
+
diffSummary,
|
|
1212
|
+
});
|
|
1213
|
+
if (total > 0) {
|
|
1214
|
+
yield {
|
|
1215
|
+
type: "content",
|
|
1216
|
+
content: `\n> [criteria] Judged ${judged}/${total} acceptance criteria as met/partial against verify+diff.\n`,
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
catch {
|
|
1221
|
+
/* non-critical — judging failure leaves criteria unmet (conservative) */
|
|
1222
|
+
}
|
|
436
1223
|
const currentCriteria = await readCriteria(ctx.flowDir, ctx.runId);
|
|
437
1224
|
// When a phaseScope is provided (subsystem E), evaluate the done-gate only
|
|
438
1225
|
// against criteria belonging to this phase. Full criteria are kept for
|
|
@@ -516,6 +1303,7 @@ export async function* runSprint(args) {
|
|
|
516
1303
|
criteriaMet: currentCriteria.filter((c) => c.status === "met").length,
|
|
517
1304
|
criteriaPartial: currentCriteria.filter((c) => c.status === "partial").length,
|
|
518
1305
|
criteriaUnmet: currentCriteria.filter((c) => c.status === "unmet").length,
|
|
1306
|
+
totalCriteria: currentCriteria.length,
|
|
519
1307
|
costUsd: 0, // Per-sprint cost is observed via the per-product ledger; field kept for compat.
|
|
520
1308
|
actualCost: 0,
|
|
521
1309
|
score: verdict.score,
|
|
@@ -524,8 +1312,39 @@ export async function* runSprint(args) {
|
|
|
524
1312
|
await appendIteration(ctx.flowDir, ctx.runId, iter);
|
|
525
1313
|
// Update Resume Digest in state.md so PIL Layer 5 + future resume can pick it up
|
|
526
1314
|
const stateMap = (await readArtifact(runDir, "state.md")) ?? { preamble: "", sections: new Map() };
|
|
527
|
-
stateMap.sections.set("Resume Digest",
|
|
1315
|
+
stateMap.sections.set("Resume Digest", renderResumeDigest({
|
|
1316
|
+
stage: `sprint-${sprintN}`,
|
|
1317
|
+
lastCompleted: `sprint-${sprintN} ${iter.stage}`,
|
|
1318
|
+
nextAction: verdict.pass
|
|
1319
|
+
? "Definition-of-Done met — advance to the next phase or ship"
|
|
1320
|
+
: `Retry sprint ${sprintN}: ${verdict.failedCondition ?? "continue toward Definition-of-Done"}`,
|
|
1321
|
+
sprintN,
|
|
1322
|
+
score: verdict.score,
|
|
1323
|
+
verify: verifyVerdict,
|
|
1324
|
+
updatedAt: new Date().toISOString(),
|
|
1325
|
+
}));
|
|
528
1326
|
await writeArtifact(runDir, "state.md", stateMap);
|
|
1327
|
+
// Part A — persist a first-class per-sprint outcome record + verify report so
|
|
1328
|
+
// `/ideal review` and cross-run memory render real sprint history (not just
|
|
1329
|
+
// the fire-and-forget EE boundary event, which leaves nothing on disk).
|
|
1330
|
+
try {
|
|
1331
|
+
await writeSprintOutcome(ctx.flowDir, ctx.runId, {
|
|
1332
|
+
sprintN,
|
|
1333
|
+
pass: verdict.pass,
|
|
1334
|
+
score: verdict.score,
|
|
1335
|
+
verify: verifyVerdict,
|
|
1336
|
+
failedCondition: verdict.failedCondition ?? undefined,
|
|
1337
|
+
criteriaMet: iter.criteriaMet,
|
|
1338
|
+
criteriaPartial: iter.criteriaPartial,
|
|
1339
|
+
criteriaUnmet: iter.criteriaUnmet,
|
|
1340
|
+
finishedAt: new Date().toISOString(),
|
|
1341
|
+
});
|
|
1342
|
+
const verifyReport = (verifyResult.error?.trim() ? verifyResult.error : (verifyResult.output ?? "")).trim() || "(no verify output)";
|
|
1343
|
+
await writeSprintVerify(ctx.flowDir, ctx.runId, sprintN, `# Sprint ${sprintN} verify — ${verifyVerdict} (score ${verdict.score.toFixed(2)})\n\n\`\`\`\n${verifyReport.slice(0, 8000)}\n\`\`\`\n`);
|
|
1344
|
+
}
|
|
1345
|
+
catch {
|
|
1346
|
+
/* non-critical — sprint artifacts are a review surface, never derail the loop */
|
|
1347
|
+
}
|
|
529
1348
|
// Emit ProgressSnapshot on sprint boundary so the user sees rolling progress.
|
|
530
1349
|
// Wrapped in try/catch — never crash sprint-runner because the snapshot failed.
|
|
531
1350
|
try {
|
|
@@ -557,9 +1376,34 @@ export async function* runSprint(args) {
|
|
|
557
1376
|
}).catch(() => {
|
|
558
1377
|
/* EE failures must not derail the loop */
|
|
559
1378
|
});
|
|
1379
|
+
// Part C — write-during-execution: persist this sprint's outcome as a NEW
|
|
1380
|
+
// workflow_sprint experience (not just reinforcement) so a later sprint in the
|
|
1381
|
+
// SAME run — or a future run — can recall "how this kind of sprint went".
|
|
1382
|
+
// gate-on-outcome (Kill #4): fired here, AFTER verify+judge produced a verdict.
|
|
1383
|
+
fireAndForgetWorkflowEvent({
|
|
1384
|
+
kind: "sprint-execution",
|
|
1385
|
+
phaseRef: `runs/${ctx.runId}#sprint-${sprintN}`,
|
|
1386
|
+
sessionId: ctx.runId,
|
|
1387
|
+
text: `Sprint ${sprintN} ${verdict.pass ? "passed" : "failed"} (score ${verdict.score.toFixed(2)}, verify ${verifyVerdict})${verdict.failedCondition ? ` — ${verdict.failedCondition}` : ""}`,
|
|
1388
|
+
payload: {
|
|
1389
|
+
sprintN,
|
|
1390
|
+
pass: verdict.pass,
|
|
1391
|
+
score: verdict.score,
|
|
1392
|
+
verify: verifyVerdict,
|
|
1393
|
+
failedCondition: verdict.failedCondition ?? null,
|
|
1394
|
+
},
|
|
1395
|
+
});
|
|
560
1396
|
// ── Step 9: If not done, surface continue-feedback to the user ───────────
|
|
561
1397
|
if (!verdict.pass) {
|
|
562
1398
|
const fb = buildContinueFeedback(verdict, verifyResult, currentCriteria);
|
|
1399
|
+
// Fold any residual plan deviations (surviving the adherence fixer) into the
|
|
1400
|
+
// carry-over focus so the next sprint continues the non-adherent/risky parts.
|
|
1401
|
+
const deviationNote = residualPlanDeviations.length > 0
|
|
1402
|
+
? `\n\nPlan deviations still open (address these next):\n${residualPlanDeviations
|
|
1403
|
+
.map((d) => `- ${d}`)
|
|
1404
|
+
.join("\n")}`
|
|
1405
|
+
: "";
|
|
1406
|
+
iter.nextFocus = `${fb.focus}${deviationNote}`;
|
|
563
1407
|
yield {
|
|
564
1408
|
type: "content",
|
|
565
1409
|
content: `\n> Sprint ${sprintN} did not satisfy Definition-of-Done (${verdict.failedCondition ?? "unknown"}). Next focus: ${fb.focus}\n`,
|