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.
Files changed (562) hide show
  1. package/LICENSE +17 -5
  2. package/dist/packages/agent-harness-core/src/driver.d.ts +27 -1
  3. package/dist/packages/agent-harness-core/src/driver.js +46 -0
  4. package/dist/packages/agent-harness-core/src/event-filter.js +11 -0
  5. package/dist/packages/agent-harness-core/src/event-redact.js +7 -0
  6. package/dist/packages/agent-harness-core/src/event-tee.d.ts +64 -0
  7. package/dist/packages/agent-harness-core/src/event-tee.js +104 -0
  8. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +25 -0
  9. package/dist/packages/agent-harness-core/src/mcp-server.js +169 -21
  10. package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
  11. package/dist/packages/agent-harness-core/src/protocol.d.ts +90 -4
  12. package/dist/packages/agent-harness-core/src/protocol.js +15 -0
  13. package/dist/packages/agent-harness-core/src/visual-quality.d.ts +58 -0
  14. package/dist/packages/agent-harness-core/src/visual-quality.js +141 -0
  15. package/dist/packages/agent-harness-opentui/src/agent-mode.d.ts +6 -0
  16. package/dist/packages/agent-harness-opentui/src/agent-mode.js +14 -1
  17. package/dist/packages/agent-harness-opentui/src/input-bridge.d.ts +2 -10
  18. package/dist/packages/agent-harness-opentui/src/input-bridge.js +103 -16
  19. package/dist/packages/agent-harness-opentui/src/install.d.ts +8 -0
  20. package/dist/packages/agent-harness-opentui/src/install.js +10 -0
  21. package/dist/packages/agent-harness-opentui/src/semantic.js +12 -10
  22. package/dist/packages/agent-harness-opentui/src/visual-capture.d.ts +56 -0
  23. package/dist/packages/agent-harness-opentui/src/visual-capture.js +103 -0
  24. package/dist/src/agent-harness/mock-model.d.ts +38 -0
  25. package/dist/src/agent-harness/mock-model.js +69 -3
  26. package/dist/src/agent-harness/test-spawn.js +31 -0
  27. package/dist/src/chat/chat-keychain.d.ts +7 -12
  28. package/dist/src/chat/chat-keychain.js +19 -86
  29. package/dist/src/cli/config/screen-providers.js +1 -1
  30. package/dist/src/cli/cost-forensics.d.ts +10 -0
  31. package/dist/src/cli/cost-forensics.js +18 -3
  32. package/dist/src/cli/keys-bundle.d.ts +1 -1
  33. package/dist/src/cli/keys-bundle.js +1 -1
  34. package/dist/src/cli/keys.d.ts +10 -47
  35. package/dist/src/cli/keys.js +31 -399
  36. package/dist/src/council/clarifier.d.ts +31 -3
  37. package/dist/src/council/clarifier.js +220 -32
  38. package/dist/src/council/context.js +49 -15
  39. package/dist/src/council/debate-checkpoint.d.ts +129 -0
  40. package/dist/src/council/debate-checkpoint.js +176 -0
  41. package/dist/src/council/debate-planner.js +54 -5
  42. package/dist/src/council/debate-summary.d.ts +25 -0
  43. package/dist/src/council/debate-summary.js +85 -0
  44. package/dist/src/council/debate.d.ts +169 -2
  45. package/dist/src/council/debate.js +1265 -135
  46. package/dist/src/council/index.d.ts +108 -1
  47. package/dist/src/council/index.js +670 -197
  48. package/dist/src/council/leader.d.ts +26 -0
  49. package/dist/src/council/leader.js +150 -9
  50. package/dist/src/council/llm.d.ts +94 -0
  51. package/dist/src/council/llm.js +348 -55
  52. package/dist/src/council/panel-select.d.ts +30 -0
  53. package/dist/src/council/panel-select.js +82 -0
  54. package/dist/src/council/planner.js +40 -0
  55. package/dist/src/council/preflight.d.ts +17 -0
  56. package/dist/src/council/preflight.js +50 -2
  57. package/dist/src/council/prompts.d.ts +39 -4
  58. package/dist/src/council/prompts.js +256 -69
  59. package/dist/src/council/stance-recall.d.ts +42 -0
  60. package/dist/src/council/stance-recall.js +57 -0
  61. package/dist/src/council/strip-think.d.ts +17 -0
  62. package/dist/src/council/strip-think.js +33 -0
  63. package/dist/src/council/types.d.ts +138 -0
  64. package/dist/src/ee/artifact-cache.d.ts +16 -0
  65. package/dist/src/ee/artifact-cache.js +32 -0
  66. package/dist/src/ee/auth.d.ts +20 -0
  67. package/dist/src/ee/auth.js +54 -2
  68. package/dist/src/ee/bridge.d.ts +10 -0
  69. package/dist/src/ee/bridge.js +58 -0
  70. package/dist/src/ee/client.js +109 -21
  71. package/dist/src/ee/ee-onboarding.js +6 -26
  72. package/dist/src/ee/export-transcripts.d.ts +1 -0
  73. package/dist/src/ee/export-transcripts.js +8 -10
  74. package/dist/src/ee/extract-session.js +29 -0
  75. package/dist/src/ee/extract-style.d.ts +58 -0
  76. package/dist/src/ee/extract-style.js +270 -0
  77. package/dist/src/ee/recall-ledger.d.ts +9 -0
  78. package/dist/src/ee/recall-ledger.js +3 -0
  79. package/dist/src/ee/scope.d.ts +1 -0
  80. package/dist/src/ee/scope.js +26 -1
  81. package/dist/src/ee/search.d.ts +7 -0
  82. package/dist/src/ee/search.js +24 -0
  83. package/dist/src/ee/transcript-emit.js +2 -0
  84. package/dist/src/ee/types.d.ts +22 -0
  85. package/dist/src/ee/who-am-i-brain.d.ts +35 -0
  86. package/dist/src/ee/who-am-i-brain.js +220 -0
  87. package/dist/src/ee/who-am-i.d.ts +10 -3
  88. package/dist/src/ee/who-am-i.js +12 -0
  89. package/dist/src/ee/workflow-event.d.ts +48 -0
  90. package/dist/src/ee/workflow-event.js +81 -0
  91. package/dist/src/flow/compaction/compress.d.ts +3 -3
  92. package/dist/src/flow/compaction/compress.js +58 -8
  93. package/dist/src/flow/compaction/extract.d.ts +4 -7
  94. package/dist/src/flow/compaction/extract.js +50 -10
  95. package/dist/src/flow/compaction/index.d.ts +14 -1
  96. package/dist/src/flow/compaction/index.js +96 -3
  97. package/dist/src/flow/compaction/input-guard.d.ts +24 -0
  98. package/dist/src/flow/compaction/input-guard.js +43 -0
  99. package/dist/src/flow/compaction/progress.d.ts +35 -0
  100. package/dist/src/flow/compaction/progress.js +35 -0
  101. package/dist/src/flow/fold-planning.d.ts +36 -0
  102. package/dist/src/flow/fold-planning.js +83 -0
  103. package/dist/src/flow/hierarchy.d.ts +146 -0
  104. package/dist/src/flow/hierarchy.js +427 -0
  105. package/dist/src/flow/index.d.ts +1 -0
  106. package/dist/src/flow/index.js +2 -0
  107. package/dist/src/flow/run-artifacts.d.ts +102 -0
  108. package/dist/src/flow/run-artifacts.js +208 -0
  109. package/dist/src/generated/version.d.ts +1 -1
  110. package/dist/src/generated/version.js +1 -1
  111. package/dist/src/gsd/assessment-schema.d.ts +44 -0
  112. package/dist/src/gsd/assessment-schema.js +134 -0
  113. package/dist/src/gsd/capability-registry.d.ts +45 -0
  114. package/dist/src/gsd/capability-registry.js +337 -0
  115. package/dist/src/gsd/complexity-assessor.d.ts +39 -0
  116. package/dist/src/gsd/complexity-assessor.js +152 -0
  117. package/dist/src/gsd/config-bridge.d.ts +7 -0
  118. package/dist/src/gsd/config-bridge.js +114 -0
  119. package/dist/src/gsd/config-loader.d.ts +27 -0
  120. package/dist/src/gsd/config-loader.js +50 -0
  121. package/dist/src/gsd/council-context.d.ts +44 -0
  122. package/dist/src/gsd/council-context.js +114 -0
  123. package/dist/src/gsd/ee-closure.d.ts +28 -0
  124. package/dist/src/gsd/ee-closure.js +49 -0
  125. package/dist/src/gsd/flags.d.ts +66 -0
  126. package/dist/src/gsd/flags.js +102 -0
  127. package/dist/src/gsd/gsd-dispatch.d.ts +58 -0
  128. package/dist/src/gsd/gsd-dispatch.js +131 -0
  129. package/dist/src/gsd/gsd-runtime.d.ts +22 -0
  130. package/dist/src/gsd/gsd-runtime.js +37 -0
  131. package/dist/src/gsd/host-adapter.d.ts +11 -0
  132. package/dist/src/gsd/host-adapter.js +29 -0
  133. package/dist/src/gsd/index.d.ts +24 -1
  134. package/dist/src/gsd/index.js +27 -0
  135. package/dist/src/gsd/loop-host-contract.d.ts +21 -0
  136. package/dist/src/gsd/loop-host-contract.js +39 -0
  137. package/dist/src/gsd/loop-host.d.ts +69 -0
  138. package/dist/src/gsd/loop-host.js +245 -0
  139. package/dist/src/gsd/loop-resolver.d.ts +36 -0
  140. package/dist/src/gsd/loop-resolver.js +79 -0
  141. package/dist/src/gsd/model-tier.d.ts +13 -0
  142. package/dist/src/gsd/model-tier.js +45 -0
  143. package/dist/src/gsd/mutation-gate.d.ts +16 -0
  144. package/dist/src/gsd/mutation-gate.js +41 -0
  145. package/dist/src/gsd/native-roadmap.d.ts +89 -0
  146. package/dist/src/gsd/native-roadmap.js +343 -0
  147. package/dist/src/gsd/native-state.d.ts +47 -0
  148. package/dist/src/gsd/native-state.js +220 -0
  149. package/dist/src/gsd/paths.d.ts +23 -0
  150. package/dist/src/gsd/paths.js +66 -0
  151. package/dist/src/gsd/phase-dag.d.ts +12 -0
  152. package/dist/src/gsd/phase-dag.js +94 -0
  153. package/dist/src/gsd/phase-sync.d.ts +42 -0
  154. package/dist/src/gsd/phase-sync.js +321 -0
  155. package/dist/src/gsd/pil-gate-context.d.ts +13 -0
  156. package/dist/src/gsd/pil-gate-context.js +64 -0
  157. package/dist/src/gsd/pil-gate-critic.d.ts +19 -0
  158. package/dist/src/gsd/pil-gate-critic.js +74 -0
  159. package/dist/src/gsd/plan-council-prompts.d.ts +25 -0
  160. package/dist/src/gsd/plan-council-prompts.js +79 -0
  161. package/dist/src/gsd/plan-council.d.ts +44 -0
  162. package/dist/src/gsd/plan-council.js +283 -0
  163. package/dist/src/gsd/plan-gate-vocabulary.d.ts +40 -0
  164. package/dist/src/gsd/plan-gate-vocabulary.js +64 -0
  165. package/dist/src/gsd/product-workspace.d.ts +13 -0
  166. package/dist/src/gsd/product-workspace.js +124 -0
  167. package/dist/src/gsd/ship-bridge.d.ts +25 -0
  168. package/dist/src/gsd/ship-bridge.js +65 -0
  169. package/dist/src/gsd/state-document.d.ts +40 -0
  170. package/dist/src/gsd/state-document.js +163 -0
  171. package/dist/src/gsd/verdict-schema.d.ts +39 -0
  172. package/dist/src/gsd/verdict-schema.js +144 -0
  173. package/dist/src/gsd/verify-context.d.ts +22 -0
  174. package/dist/src/gsd/verify-context.js +27 -0
  175. package/dist/src/gsd/verify-council-prompts.d.ts +19 -0
  176. package/dist/src/gsd/verify-council-prompts.js +85 -0
  177. package/dist/src/gsd/verify-council.d.ts +25 -0
  178. package/dist/src/gsd/verify-council.js +119 -0
  179. package/dist/src/gsd/verify-gate-vocabulary.d.ts +25 -0
  180. package/dist/src/gsd/verify-gate-vocabulary.js +46 -0
  181. package/dist/src/gsd/workflow-engine.d.ts +60 -0
  182. package/dist/src/gsd/workflow-engine.js +207 -0
  183. package/dist/src/gsd/workflow-tools.d.ts +13 -0
  184. package/dist/src/gsd/workflow-tools.js +277 -0
  185. package/dist/src/headless/council-answers.js +4 -0
  186. package/dist/src/hooks/index.js +1 -1
  187. package/dist/src/index.js +172 -270
  188. package/dist/src/lsp/builtins.js +3 -1
  189. package/dist/src/lsp/manager.d.ts +5 -1
  190. package/dist/src/lsp/manager.js +249 -3
  191. package/dist/src/lsp/npm-cache.d.ts +11 -1
  192. package/dist/src/lsp/npm-cache.js +17 -1
  193. package/dist/src/lsp/runtime.d.ts +6 -1
  194. package/dist/src/lsp/runtime.js +17 -1
  195. package/dist/src/lsp/types.d.ts +83 -1
  196. package/dist/src/lsp/types.js +10 -0
  197. package/dist/src/maintain/pr-builder.js +23 -13
  198. package/dist/src/mcp/auto-setup.js +57 -32
  199. package/dist/src/mcp/client-pool.js +44 -16
  200. package/dist/src/mcp/lsp-tools.d.ts +5 -1
  201. package/dist/src/mcp/lsp-tools.js +93 -2
  202. package/dist/src/mcp/mcp-keychain.d.ts +3 -5
  203. package/dist/src/mcp/mcp-keychain.js +9 -49
  204. package/dist/src/mcp/research-onboarding.js +8 -7
  205. package/dist/src/mcp/runtime.js +34 -2
  206. package/dist/src/mcp/setup-guide-text.d.ts +1 -1
  207. package/dist/src/mcp/setup-guide-text.js +22 -2
  208. package/dist/src/mcp/tools-server.d.ts +10 -0
  209. package/dist/src/mcp/tools-server.js +10 -2
  210. package/dist/src/models/catalog-client.d.ts +87 -0
  211. package/dist/src/models/catalog-client.js +105 -38
  212. package/dist/src/models/catalog.json +528 -265
  213. package/dist/src/models/registry.d.ts +22 -7
  214. package/dist/src/models/registry.js +73 -10
  215. package/dist/src/ops/doctor.js +1 -1
  216. package/dist/src/orchestrator/ask-user.d.ts +61 -0
  217. package/dist/src/orchestrator/ask-user.js +65 -0
  218. package/dist/src/orchestrator/auto-commit.js +1 -1
  219. package/dist/src/orchestrator/batch-turn-runner.js +2 -2
  220. package/dist/src/orchestrator/cache-prefix.d.ts +67 -0
  221. package/dist/src/orchestrator/cache-prefix.js +83 -0
  222. package/dist/src/orchestrator/compact-request.d.ts +32 -0
  223. package/dist/src/orchestrator/compact-request.js +41 -0
  224. package/dist/src/orchestrator/compaction.d.ts +12 -3
  225. package/dist/src/orchestrator/compaction.js +35 -15
  226. package/dist/src/orchestrator/council-manager.d.ts +12 -3
  227. package/dist/src/orchestrator/council-manager.js +74 -32
  228. package/dist/src/orchestrator/council-request.d.ts +49 -0
  229. package/dist/src/orchestrator/council-request.js +62 -0
  230. package/dist/src/orchestrator/cross-turn-dedup.d.ts +6 -0
  231. package/dist/src/orchestrator/cross-turn-dedup.js +22 -1
  232. package/dist/src/orchestrator/error-utils.d.ts +29 -0
  233. package/dist/src/orchestrator/error-utils.js +132 -24
  234. package/dist/src/orchestrator/grounding-check.js +39 -1
  235. package/dist/src/orchestrator/interactive-pause.d.ts +26 -0
  236. package/dist/src/orchestrator/interactive-pause.js +36 -0
  237. package/dist/src/orchestrator/message-processor.d.ts +4 -0
  238. package/dist/src/orchestrator/message-processor.js +268 -41
  239. package/dist/src/orchestrator/orchestrator.d.ts +64 -3
  240. package/dist/src/orchestrator/orchestrator.js +823 -120
  241. package/dist/src/orchestrator/preprocessor.js +3 -3
  242. package/dist/src/orchestrator/proactive-compact-detector.d.ts +26 -0
  243. package/dist/src/orchestrator/proactive-compact-detector.js +36 -0
  244. package/dist/src/orchestrator/prompts.js +17 -17
  245. package/dist/src/orchestrator/reactive-delegation.d.ts +39 -0
  246. package/dist/src/orchestrator/reactive-delegation.js +59 -0
  247. package/dist/src/orchestrator/retry-classifier.d.ts +2 -1
  248. package/dist/src/orchestrator/retry-classifier.js +46 -2
  249. package/dist/src/orchestrator/safety-askcard.d.ts +1 -1
  250. package/dist/src/orchestrator/safety-askcard.js +5 -2
  251. package/dist/src/orchestrator/safety-intercept.d.ts +50 -0
  252. package/dist/src/orchestrator/safety-intercept.js +62 -0
  253. package/dist/src/orchestrator/scope-reminder.js +1 -1
  254. package/dist/src/orchestrator/session-experience.d.ts +2 -1
  255. package/dist/src/orchestrator/session-experience.js +2 -1
  256. package/dist/src/orchestrator/should-run-gate.d.ts +5 -0
  257. package/dist/src/orchestrator/should-run-gate.js +18 -0
  258. package/dist/src/orchestrator/stall-watchdog.d.ts +31 -3
  259. package/dist/src/orchestrator/stall-watchdog.js +65 -10
  260. package/dist/src/orchestrator/stream-runner.d.ts +13 -3
  261. package/dist/src/orchestrator/stream-runner.js +115 -49
  262. package/dist/src/orchestrator/sub-agent-cap.d.ts +13 -1
  263. package/dist/src/orchestrator/sub-agent-cap.js +16 -1
  264. package/dist/src/orchestrator/sub-agent-model-tier.d.ts +13 -1
  265. package/dist/src/orchestrator/sub-agent-model-tier.js +16 -2
  266. package/dist/src/orchestrator/subagent-compactor.d.ts +53 -1
  267. package/dist/src/orchestrator/subagent-compactor.js +126 -15
  268. package/dist/src/orchestrator/tool-engine.d.ts +41 -0
  269. package/dist/src/orchestrator/tool-engine.js +846 -66
  270. package/dist/src/orchestrator/tool-limit-auto-recover.d.ts +22 -0
  271. package/dist/src/orchestrator/tool-limit-auto-recover.js +30 -0
  272. package/dist/src/orchestrator/turn-runner-deps.d.ts +19 -0
  273. package/dist/src/orchestrator/turn-watchdog.d.ts +44 -0
  274. package/dist/src/orchestrator/turn-watchdog.js +84 -0
  275. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  276. package/dist/src/pil/agent-operating-contract.js +6 -4
  277. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  278. package/dist/src/pil/cheap-model-playbook.js +5 -1
  279. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  280. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  281. package/dist/src/pil/discovery-types.d.ts +1 -0
  282. package/dist/src/pil/discovery.d.ts +1 -1
  283. package/dist/src/pil/discovery.js +18 -13
  284. package/dist/src/pil/layer1-intent.d.ts +18 -6
  285. package/dist/src/pil/layer1-intent.js +66 -757
  286. package/dist/src/pil/layer15-context-scan.js +15 -1
  287. package/dist/src/pil/layer1_5-complexity-size.d.ts +7 -0
  288. package/dist/src/pil/layer1_5-complexity-size.js +31 -5
  289. package/dist/src/pil/layer3-ee-injection.js +23 -8
  290. package/dist/src/pil/layer4-gsd.js +69 -16
  291. package/dist/src/pil/layer5-context.js +7 -3
  292. package/dist/src/pil/layer6-output.d.ts +23 -0
  293. package/dist/src/pil/layer6-output.js +5 -1
  294. package/dist/src/pil/llm-classify.d.ts +111 -5
  295. package/dist/src/pil/llm-classify.js +421 -189
  296. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  297. package/dist/src/pil/native-capabilities-workbook.js +8 -0
  298. package/dist/src/pil/pipeline.js +36 -2
  299. package/dist/src/pil/repo-grounding-probe.d.ts +15 -0
  300. package/dist/src/pil/repo-grounding-probe.js +136 -0
  301. package/dist/src/pil/repo-structure-hints.d.ts +7 -0
  302. package/dist/src/pil/repo-structure-hints.js +45 -0
  303. package/dist/src/pil/response-tools.js +5 -3
  304. package/dist/src/pil/schema.d.ts +1 -0
  305. package/dist/src/pil/schema.js +2 -0
  306. package/dist/src/pil/types.d.ts +18 -0
  307. package/dist/src/playbook/directives.d.ts +4 -0
  308. package/dist/src/playbook/directives.js +17 -5
  309. package/dist/src/product-loop/artifact-io.js +4 -0
  310. package/dist/src/product-loop/backlog-builder.d.ts +14 -1
  311. package/dist/src/product-loop/backlog-builder.js +30 -6
  312. package/dist/src/product-loop/criteria-seed.d.ts +51 -0
  313. package/dist/src/product-loop/criteria-seed.js +200 -0
  314. package/dist/src/product-loop/discovery-context-format.js +3 -1
  315. package/dist/src/product-loop/discovery-ecosystem.js +4 -1
  316. package/dist/src/product-loop/discovery-interview.d.ts +9 -0
  317. package/dist/src/product-loop/discovery-interview.js +60 -12
  318. package/dist/src/product-loop/discovery-recommender.js +2 -1
  319. package/dist/src/product-loop/discovery-schema.js +19 -2
  320. package/dist/src/product-loop/discovery-triage.d.ts +23 -0
  321. package/dist/src/product-loop/discovery-triage.js +109 -0
  322. package/dist/src/product-loop/gather.js +150 -2
  323. package/dist/src/product-loop/ideal-trace.d.ts +7 -0
  324. package/dist/src/product-loop/ideal-trace.js +64 -0
  325. package/dist/src/product-loop/index.d.ts +13 -1
  326. package/dist/src/product-loop/index.js +340 -52
  327. package/dist/src/product-loop/loop-driver.d.ts +7 -0
  328. package/dist/src/product-loop/loop-driver.js +330 -106
  329. package/dist/src/product-loop/phase-plan.d.ts +21 -0
  330. package/dist/src/product-loop/phase-plan.js +81 -6
  331. package/dist/src/product-loop/phase-rituals.d.ts +3 -0
  332. package/dist/src/product-loop/phase-rituals.js +8 -3
  333. package/dist/src/product-loop/phase-runner.js +39 -12
  334. package/dist/src/product-loop/plan-adherence-review.d.ts +26 -0
  335. package/dist/src/product-loop/plan-adherence-review.js +144 -0
  336. package/dist/src/product-loop/sprint-runner.d.ts +173 -0
  337. package/dist/src/product-loop/sprint-runner.js +863 -19
  338. package/dist/src/product-loop/types.d.ts +61 -5
  339. package/dist/src/providers/adapter.d.ts +1 -1
  340. package/dist/src/providers/adapter.js +3 -4
  341. package/dist/src/providers/anthropic.d.ts +9 -8
  342. package/dist/src/providers/anthropic.js +13 -47
  343. package/dist/src/providers/auth/browser-flow.d.ts +1 -1
  344. package/dist/src/providers/auth/browser-flow.js +1 -1
  345. package/dist/src/providers/auth/grok-oauth.d.ts +1 -0
  346. package/dist/src/providers/auth/grok-oauth.js +30 -5
  347. package/dist/src/providers/auth/openai-oauth.d.ts +1 -0
  348. package/dist/src/providers/auth/openai-oauth.js +15 -1
  349. package/dist/src/providers/auth/registry.js +0 -34
  350. package/dist/src/providers/auth/token-store.d.ts +9 -9
  351. package/dist/src/providers/auth/token-store.js +8 -67
  352. package/dist/src/providers/auth/types.d.ts +9 -1
  353. package/dist/src/providers/auth/types.js +1 -1
  354. package/dist/src/providers/capabilities.d.ts +24 -5
  355. package/dist/src/providers/capabilities.js +42 -24
  356. package/dist/src/providers/endpoints.d.ts +2 -2
  357. package/dist/src/providers/endpoints.js +11 -10
  358. package/dist/src/providers/env-store.d.ts +17 -0
  359. package/dist/src/providers/env-store.js +228 -0
  360. package/dist/src/providers/keychain.d.ts +22 -18
  361. package/dist/src/providers/keychain.js +127 -140
  362. package/dist/src/providers/mcp-vision-bridge.js +56 -146
  363. package/dist/src/providers/openai-compatible.js +8 -1
  364. package/dist/src/providers/pricing.d.ts +2 -2
  365. package/dist/src/providers/pricing.js +3 -13
  366. package/dist/src/providers/runtime.d.ts +43 -3
  367. package/dist/src/providers/runtime.js +88 -14
  368. package/dist/src/providers/strategies/base.strategy.d.ts +16 -0
  369. package/dist/src/providers/strategies/base.strategy.js +24 -1
  370. package/dist/src/providers/strategies/{siliconflow.strategy.d.ts → opencode-go.strategy.d.ts} +3 -3
  371. package/dist/src/providers/strategies/opencode-go.strategy.js +83 -0
  372. package/dist/src/providers/strategies/registry.js +4 -4
  373. package/dist/src/providers/strategies/thinking-mode.d.ts +109 -0
  374. package/dist/src/providers/strategies/thinking-mode.js +288 -1
  375. package/dist/src/providers/strategies/xai.strategy.js +27 -0
  376. package/dist/src/providers/strategies/zai.strategy.d.ts +14 -0
  377. package/dist/src/providers/strategies/zai.strategy.js +44 -0
  378. package/dist/src/providers/types.d.ts +5 -6
  379. package/dist/src/providers/types.js +2 -2
  380. package/dist/src/providers/vision-backend.d.ts +47 -0
  381. package/dist/src/providers/vision-backend.js +258 -0
  382. package/dist/src/providers/vision-proxy.d.ts +22 -9
  383. package/dist/src/providers/vision-proxy.js +63 -132
  384. package/dist/src/providers/warm.d.ts +65 -0
  385. package/dist/src/providers/warm.js +145 -0
  386. package/dist/src/providers/wire-debug.js +95 -0
  387. package/dist/src/router/decide.d.ts +13 -0
  388. package/dist/src/router/decide.js +138 -36
  389. package/dist/src/router/peak-hour.d.ts +38 -0
  390. package/dist/src/router/peak-hour.js +107 -0
  391. package/dist/src/router/step-router.js +3 -2
  392. package/dist/src/router/warm.js +4 -5
  393. package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
  394. package/dist/src/scaffold/continuation-prompt.js +26 -0
  395. package/dist/src/scaffold/point-to-existing.d.ts +21 -0
  396. package/dist/src/scaffold/point-to-existing.js +25 -0
  397. package/dist/src/self-qa/agentic-loop.js +6 -5
  398. package/dist/src/{ui/state → state}/active-run.d.ts +19 -0
  399. package/dist/src/{ui/state → state}/active-run.js +21 -0
  400. package/dist/src/{ui/status-bar/store.d.ts → state/status-bar-store.d.ts} +4 -1
  401. package/dist/src/{ui/status-bar/store.js → state/status-bar-store.js} +12 -9
  402. package/dist/src/state/turn-trace.d.ts +43 -0
  403. package/dist/src/state/turn-trace.js +32 -0
  404. package/dist/src/storage/db.js +2 -1
  405. package/dist/src/storage/index.d.ts +1 -1
  406. package/dist/src/storage/index.js +1 -1
  407. package/dist/src/storage/interaction-log.d.ts +1 -1
  408. package/dist/src/storage/migrations.js +71 -1
  409. package/dist/src/storage/sessions.d.ts +28 -10
  410. package/dist/src/storage/sessions.js +78 -21
  411. package/dist/src/storage/transcript-view.js +1 -1
  412. package/dist/src/storage/transcript.d.ts +51 -0
  413. package/dist/src/storage/transcript.js +340 -15
  414. package/dist/src/tools/file.d.ts +15 -0
  415. package/dist/src/tools/file.js +32 -0
  416. package/dist/src/tools/git-safety.d.ts +19 -0
  417. package/dist/src/tools/git-safety.js +168 -0
  418. package/dist/src/tools/native-tools.d.ts +1 -1
  419. package/dist/src/tools/native-tools.js +81 -1
  420. package/dist/src/tools/registry.d.ts +20 -0
  421. package/dist/src/tools/registry.js +576 -23
  422. package/dist/src/tools/research.d.ts +29 -0
  423. package/dist/src/tools/research.js +233 -0
  424. package/dist/src/types/index.d.ts +147 -4
  425. package/dist/src/ui/app.js +0 -0
  426. package/dist/src/ui/cards/product-status-card.js +1 -1
  427. package/dist/src/ui/components/agent-rail-activities.d.ts +26 -0
  428. package/dist/src/ui/components/agent-rail-activities.js +47 -0
  429. package/dist/src/ui/components/bubble-body-guard.d.ts +27 -0
  430. package/dist/src/ui/components/bubble-body-guard.js +50 -0
  431. package/dist/src/ui/components/compact-progress-card.d.ts +24 -0
  432. package/dist/src/ui/components/compact-progress-card.js +42 -0
  433. package/dist/src/ui/components/context-rail.d.ts +26 -0
  434. package/dist/src/ui/components/context-rail.js +33 -0
  435. package/dist/src/ui/components/council-conclusion-card.d.ts +73 -0
  436. package/dist/src/ui/components/council-conclusion-card.js +420 -0
  437. package/dist/src/ui/components/council-debate-pill.d.ts +36 -0
  438. package/dist/src/ui/components/council-debate-pill.js +34 -0
  439. package/dist/src/ui/components/council-info-card.js +2 -2
  440. package/dist/src/ui/components/council-leader-bubble.d.ts +10 -3
  441. package/dist/src/ui/components/council-leader-bubble.js +21 -11
  442. package/dist/src/ui/components/council-message-bubble.d.ts +15 -2
  443. package/dist/src/ui/components/council-message-bubble.js +16 -15
  444. package/dist/src/ui/components/council-phase-timeline.d.ts +9 -1
  445. package/dist/src/ui/components/council-phase-timeline.js +66 -17
  446. package/dist/src/ui/components/council-placeholder-bubble.d.ts +16 -9
  447. package/dist/src/ui/components/council-placeholder-bubble.js +32 -29
  448. package/dist/src/ui/components/council-question-card.js +13 -12
  449. package/dist/src/ui/components/council-rail-rounds.d.ts +26 -0
  450. package/dist/src/ui/components/council-rail-rounds.js +57 -0
  451. package/dist/src/ui/components/council-round-group.d.ts +38 -0
  452. package/dist/src/ui/components/council-round-group.js +88 -0
  453. package/dist/src/ui/components/council-status-list.d.ts +3 -1
  454. package/dist/src/ui/components/council-status-list.js +36 -24
  455. package/dist/src/ui/components/council-synthesis-banner.d.ts +7 -2
  456. package/dist/src/ui/components/council-synthesis-banner.js +20 -5
  457. package/dist/src/ui/components/halt-recovery-card.js +9 -5
  458. package/dist/src/ui/components/jump-to-latest-pill.d.ts +11 -0
  459. package/dist/src/ui/components/jump-to-latest-pill.js +14 -0
  460. package/dist/src/ui/components/message-view.d.ts +15 -0
  461. package/dist/src/ui/components/message-view.js +50 -1
  462. package/dist/src/ui/components/prompt-box.js +18 -16
  463. package/dist/src/ui/components/session-tree-card.d.ts +14 -0
  464. package/dist/src/ui/components/session-tree-card.js +46 -0
  465. package/dist/src/ui/components/slash-inline-menu.d.ts +12 -0
  466. package/dist/src/ui/components/slash-inline-menu.js +26 -5
  467. package/dist/src/ui/components/task-list-panel.d.ts +14 -1
  468. package/dist/src/ui/components/task-list-panel.js +22 -2
  469. package/dist/src/ui/components/tool-group.d.ts +15 -3
  470. package/dist/src/ui/components/tool-group.js +69 -11
  471. package/dist/src/ui/containers/modals-layer.d.ts +4 -2
  472. package/dist/src/ui/containers/modals-layer.js +2 -2
  473. package/dist/src/ui/council-harness-event.d.ts +57 -0
  474. package/dist/src/ui/council-harness-event.js +46 -0
  475. package/dist/src/ui/heartbeat-debug.d.ts +29 -0
  476. package/dist/src/ui/heartbeat-debug.js +45 -0
  477. package/dist/src/ui/hooks/use-session-picker.d.ts +3 -3
  478. package/dist/src/ui/mcp-modal.js +2 -4
  479. package/dist/src/ui/modals/api-key-modal.js +1 -1
  480. package/dist/src/ui/modals/connect-modal.js +4 -3
  481. package/dist/src/ui/modals/model-picker-modal.d.ts +8 -18
  482. package/dist/src/ui/modals/model-picker-modal.js +8 -10
  483. package/dist/src/ui/modals/session-picker-modal.d.ts +2 -2
  484. package/dist/src/ui/modals/session-picker-modal.js +3 -5
  485. package/dist/src/ui/picker-providers.d.ts +1 -1
  486. package/dist/src/ui/picker-providers.js +1 -1
  487. package/dist/src/ui/primitives/index.d.ts +1 -0
  488. package/dist/src/ui/primitives/index.js +2 -0
  489. package/dist/src/ui/primitives/semantic-primitives.d.ts +76 -0
  490. package/dist/src/ui/primitives/semantic-primitives.js +81 -0
  491. package/dist/src/ui/slash/compact.js +5 -7
  492. package/dist/src/ui/slash/cost.js +1 -1
  493. package/dist/src/ui/slash/council.js +19 -1
  494. package/dist/src/ui/slash/debug.d.ts +3 -31
  495. package/dist/src/ui/slash/debug.js +9 -20
  496. package/dist/src/ui/slash/ee.js +81 -0
  497. package/dist/src/ui/slash/ideal.d.ts +6 -2
  498. package/dist/src/ui/slash/ideal.js +97 -7
  499. package/dist/src/ui/slash/menu-items.d.ts +7 -0
  500. package/dist/src/ui/slash/menu-items.js +23 -20
  501. package/dist/src/ui/slash/registry.d.ts +2 -0
  502. package/dist/src/ui/slash/registry.js +4 -0
  503. package/dist/src/ui/status-bar/cache-hit.d.ts +6 -0
  504. package/dist/src/ui/status-bar/cache-hit.js +9 -0
  505. package/dist/src/ui/status-bar/index.d.ts +1 -1
  506. package/dist/src/ui/status-bar/index.js +7 -3
  507. package/dist/src/ui/status-bar/usd-meter.d.ts +5 -4
  508. package/dist/src/ui/status-bar/usd-meter.js +6 -4
  509. package/dist/src/ui/theme.d.ts +1 -0
  510. package/dist/src/ui/theme.js +2 -0
  511. package/dist/src/ui/types.d.ts +7 -0
  512. package/dist/src/ui/use-app-logic.js +0 -0
  513. package/dist/src/ui/utils/agent-activities.d.ts +39 -0
  514. package/dist/src/ui/utils/agent-activities.js +96 -0
  515. package/dist/src/ui/utils/format.d.ts +14 -0
  516. package/dist/src/ui/utils/format.js +23 -3
  517. package/dist/src/ui/utils/group-tool-entries.d.ts +26 -0
  518. package/dist/src/ui/utils/group-tool-entries.js +111 -0
  519. package/dist/src/ui/utils/tool-summary.d.ts +21 -0
  520. package/dist/src/ui/utils/tool-summary.js +91 -0
  521. package/dist/src/usage/downgrade.js +2 -2
  522. package/dist/src/usage/product-ledger.js +2 -2
  523. package/dist/src/utils/event-loop-monitor.d.ts +85 -0
  524. package/dist/src/utils/event-loop-monitor.js +107 -0
  525. package/dist/src/utils/install-manager.js +2 -1
  526. package/dist/src/utils/llm-deadline.d.ts +14 -0
  527. package/dist/src/utils/llm-deadline.js +19 -0
  528. package/dist/src/utils/logger.js +2 -2
  529. package/dist/src/utils/loop-profiler.d.ts +102 -0
  530. package/dist/src/utils/loop-profiler.js +202 -0
  531. package/dist/src/utils/permission-mode.js +5 -3
  532. package/dist/src/utils/redactor.js +1 -1
  533. package/dist/src/utils/settings.d.ts +180 -5
  534. package/dist/src/utils/settings.js +271 -31
  535. package/dist/src/utils/side-question.d.ts +1 -2
  536. package/dist/src/utils/side-question.js +2 -2
  537. package/dist/src/utils/visible-retry.d.ts +11 -0
  538. package/dist/src/utils/visible-retry.js +10 -1
  539. package/dist/src/verify/entrypoint.d.ts +1 -1
  540. package/dist/src/verify/entrypoint.js +52 -17
  541. package/dist/src/verify/orchestrator.d.ts +1 -1
  542. package/dist/src/verify/orchestrator.js +20 -3
  543. package/dist/src/verify/recipes.d.ts +13 -0
  544. package/dist/src/verify/recipes.js +15 -0
  545. package/package.json +134 -132
  546. package/dist/src/cli/bw-vault.d.ts +0 -55
  547. package/dist/src/cli/bw-vault.js +0 -133
  548. package/dist/src/mcp/ee-tools.d.ts +0 -46
  549. package/dist/src/mcp/ee-tools.js +0 -193
  550. package/dist/src/providers/auth/gcloud.d.ts +0 -28
  551. package/dist/src/providers/auth/gcloud.js +0 -102
  552. package/dist/src/providers/auth/gemini-oauth.d.ts +0 -82
  553. package/dist/src/providers/auth/gemini-oauth.js +0 -472
  554. package/dist/src/providers/gemini.d.ts +0 -11
  555. package/dist/src/providers/gemini.js +0 -45
  556. package/dist/src/providers/siliconflow-sse-repair.d.ts +0 -58
  557. package/dist/src/providers/siliconflow-sse-repair.js +0 -177
  558. package/dist/src/providers/strategies/google.strategy.d.ts +0 -22
  559. package/dist/src/providers/strategies/google.strategy.js +0 -174
  560. package/dist/src/providers/strategies/siliconflow.strategy.js +0 -29
  561. package/dist/src/ui/containers/chat-feed.d.ts +0 -40
  562. 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
- const verifyRecipe = await verifyAgent.detectVerifyRecipe(verifyAgent.getSandboxSettings());
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
- if (cb3.halt) {
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
- const planGen = runCouncil(councilTopic, sessionModelId, [], ctx.runId, productLlm, ctx.respondToQuestion, ctx.respondToPreflight, ctx.processMessageFn ?? noopProcess, { skipClarification: true, cwd, runDir });
220
- let planSynthesis = "";
221
- while (true) {
222
- const step = await planGen.next();
223
- if (step.done) {
224
- planSynthesis = step.value ?? "";
225
- break;
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
- yield step.value;
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
- implPrompt = prependDecisionsLock(planSynthesis, lockContent);
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
- const implGen = ctx.processMessageFn(implPrompt);
285
- for await (const chunk of implGen) {
286
- yield chunk;
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
- const verifyResult = await runVerifyOrchestration(verifyAgent);
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", `Sprint: ${sprintN} | Stage: ${iter.stage} | Score: ${verdict.score.toFixed(2)} | Verify: ${verifyVerdict}`);
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`,