muonroi-cli 1.8.4 → 1.8.5

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 (458) 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-tee.d.ts +48 -0
  5. package/dist/packages/agent-harness-core/src/event-tee.js +77 -0
  6. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +11 -0
  7. package/dist/packages/agent-harness-core/src/mcp-server.js +87 -15
  8. package/dist/packages/agent-harness-core/src/protocol.d.ts +66 -2
  9. package/dist/packages/agent-harness-core/src/protocol.js +15 -0
  10. package/dist/packages/agent-harness-core/src/visual-quality.d.ts +58 -0
  11. package/dist/packages/agent-harness-core/src/visual-quality.js +141 -0
  12. package/dist/packages/agent-harness-opentui/src/agent-mode.d.ts +6 -0
  13. package/dist/packages/agent-harness-opentui/src/agent-mode.js +14 -1
  14. package/dist/packages/agent-harness-opentui/src/input-bridge.d.ts +2 -10
  15. package/dist/packages/agent-harness-opentui/src/input-bridge.js +103 -16
  16. package/dist/packages/agent-harness-opentui/src/install.d.ts +8 -0
  17. package/dist/packages/agent-harness-opentui/src/install.js +10 -0
  18. package/dist/packages/agent-harness-opentui/src/semantic.js +12 -10
  19. package/dist/packages/agent-harness-opentui/src/visual-capture.d.ts +56 -0
  20. package/dist/packages/agent-harness-opentui/src/visual-capture.js +103 -0
  21. package/dist/src/agent-harness/mock-model.d.ts +28 -0
  22. package/dist/src/agent-harness/mock-model.js +63 -1
  23. package/dist/src/agent-harness/test-spawn.js +31 -0
  24. package/dist/src/cli/config/screen-providers.js +1 -1
  25. package/dist/src/cli/cost-forensics.d.ts +10 -0
  26. package/dist/src/cli/cost-forensics.js +18 -3
  27. package/dist/src/cli/keys-bundle.d.ts +1 -1
  28. package/dist/src/cli/keys-bundle.js +1 -1
  29. package/dist/src/cli/keys.d.ts +2 -2
  30. package/dist/src/cli/keys.js +19 -81
  31. package/dist/src/council/clarifier.d.ts +28 -2
  32. package/dist/src/council/clarifier.js +81 -15
  33. package/dist/src/council/context.js +49 -15
  34. package/dist/src/council/debate-checkpoint.d.ts +129 -0
  35. package/dist/src/council/debate-checkpoint.js +176 -0
  36. package/dist/src/council/debate-planner.js +51 -3
  37. package/dist/src/council/debate-summary.d.ts +25 -0
  38. package/dist/src/council/debate-summary.js +85 -0
  39. package/dist/src/council/debate.d.ts +169 -2
  40. package/dist/src/council/debate.js +1210 -134
  41. package/dist/src/council/index.d.ts +85 -1
  42. package/dist/src/council/index.js +634 -196
  43. package/dist/src/council/leader.d.ts +26 -0
  44. package/dist/src/council/leader.js +150 -9
  45. package/dist/src/council/llm.d.ts +32 -0
  46. package/dist/src/council/llm.js +231 -38
  47. package/dist/src/council/panel-select.d.ts +30 -0
  48. package/dist/src/council/panel-select.js +72 -0
  49. package/dist/src/council/planner.js +23 -0
  50. package/dist/src/council/preflight.d.ts +7 -0
  51. package/dist/src/council/preflight.js +14 -2
  52. package/dist/src/council/prompts.d.ts +30 -3
  53. package/dist/src/council/prompts.js +234 -64
  54. package/dist/src/council/stance-recall.d.ts +42 -0
  55. package/dist/src/council/stance-recall.js +57 -0
  56. package/dist/src/council/strip-think.d.ts +17 -0
  57. package/dist/src/council/strip-think.js +33 -0
  58. package/dist/src/council/types.d.ts +128 -0
  59. package/dist/src/ee/artifact-cache.d.ts +16 -0
  60. package/dist/src/ee/artifact-cache.js +32 -0
  61. package/dist/src/ee/auth.d.ts +1 -0
  62. package/dist/src/ee/auth.js +15 -2
  63. package/dist/src/ee/bridge.d.ts +10 -0
  64. package/dist/src/ee/bridge.js +58 -0
  65. package/dist/src/ee/client.js +81 -18
  66. package/dist/src/ee/export-transcripts.d.ts +1 -0
  67. package/dist/src/ee/export-transcripts.js +8 -10
  68. package/dist/src/ee/extract-session.js +29 -0
  69. package/dist/src/ee/extract-style.d.ts +58 -0
  70. package/dist/src/ee/extract-style.js +270 -0
  71. package/dist/src/ee/recall-ledger.d.ts +9 -0
  72. package/dist/src/ee/recall-ledger.js +3 -0
  73. package/dist/src/ee/scope.d.ts +1 -0
  74. package/dist/src/ee/scope.js +26 -1
  75. package/dist/src/ee/search.d.ts +7 -0
  76. package/dist/src/ee/search.js +24 -0
  77. package/dist/src/ee/transcript-emit.js +2 -0
  78. package/dist/src/ee/types.d.ts +22 -0
  79. package/dist/src/ee/who-am-i-brain.d.ts +35 -0
  80. package/dist/src/ee/who-am-i-brain.js +220 -0
  81. package/dist/src/ee/who-am-i.d.ts +10 -3
  82. package/dist/src/ee/who-am-i.js +12 -0
  83. package/dist/src/ee/workflow-event.d.ts +48 -0
  84. package/dist/src/ee/workflow-event.js +81 -0
  85. package/dist/src/flow/compaction/compress.d.ts +3 -3
  86. package/dist/src/flow/compaction/compress.js +45 -8
  87. package/dist/src/flow/compaction/extract.d.ts +4 -7
  88. package/dist/src/flow/compaction/extract.js +50 -10
  89. package/dist/src/flow/compaction/index.d.ts +13 -1
  90. package/dist/src/flow/compaction/index.js +70 -3
  91. package/dist/src/flow/compaction/input-guard.d.ts +24 -0
  92. package/dist/src/flow/compaction/input-guard.js +43 -0
  93. package/dist/src/flow/fold-planning.d.ts +36 -0
  94. package/dist/src/flow/fold-planning.js +83 -0
  95. package/dist/src/flow/hierarchy.d.ts +146 -0
  96. package/dist/src/flow/hierarchy.js +427 -0
  97. package/dist/src/flow/index.d.ts +1 -0
  98. package/dist/src/flow/index.js +2 -0
  99. package/dist/src/flow/run-artifacts.d.ts +102 -0
  100. package/dist/src/flow/run-artifacts.js +208 -0
  101. package/dist/src/generated/version.d.ts +1 -1
  102. package/dist/src/generated/version.js +1 -1
  103. package/dist/src/gsd/assessment-schema.d.ts +44 -0
  104. package/dist/src/gsd/assessment-schema.js +134 -0
  105. package/dist/src/gsd/capability-registry.d.ts +45 -0
  106. package/dist/src/gsd/capability-registry.js +337 -0
  107. package/dist/src/gsd/complexity-assessor.d.ts +39 -0
  108. package/dist/src/gsd/complexity-assessor.js +152 -0
  109. package/dist/src/gsd/config-bridge.d.ts +7 -0
  110. package/dist/src/gsd/config-bridge.js +114 -0
  111. package/dist/src/gsd/config-loader.d.ts +27 -0
  112. package/dist/src/gsd/config-loader.js +50 -0
  113. package/dist/src/gsd/council-context.d.ts +44 -0
  114. package/dist/src/gsd/council-context.js +114 -0
  115. package/dist/src/gsd/ee-closure.d.ts +28 -0
  116. package/dist/src/gsd/ee-closure.js +49 -0
  117. package/dist/src/gsd/flags.d.ts +55 -0
  118. package/dist/src/gsd/flags.js +83 -0
  119. package/dist/src/gsd/gsd-dispatch.d.ts +58 -0
  120. package/dist/src/gsd/gsd-dispatch.js +131 -0
  121. package/dist/src/gsd/gsd-runtime.d.ts +22 -0
  122. package/dist/src/gsd/gsd-runtime.js +37 -0
  123. package/dist/src/gsd/host-adapter.d.ts +11 -0
  124. package/dist/src/gsd/host-adapter.js +29 -0
  125. package/dist/src/gsd/index.d.ts +24 -1
  126. package/dist/src/gsd/index.js +27 -0
  127. package/dist/src/gsd/loop-host-contract.d.ts +21 -0
  128. package/dist/src/gsd/loop-host-contract.js +39 -0
  129. package/dist/src/gsd/loop-host.d.ts +69 -0
  130. package/dist/src/gsd/loop-host.js +245 -0
  131. package/dist/src/gsd/loop-resolver.d.ts +36 -0
  132. package/dist/src/gsd/loop-resolver.js +79 -0
  133. package/dist/src/gsd/model-tier.d.ts +13 -0
  134. package/dist/src/gsd/model-tier.js +45 -0
  135. package/dist/src/gsd/mutation-gate.d.ts +16 -0
  136. package/dist/src/gsd/mutation-gate.js +41 -0
  137. package/dist/src/gsd/native-roadmap.d.ts +89 -0
  138. package/dist/src/gsd/native-roadmap.js +343 -0
  139. package/dist/src/gsd/native-state.d.ts +47 -0
  140. package/dist/src/gsd/native-state.js +220 -0
  141. package/dist/src/gsd/paths.d.ts +23 -0
  142. package/dist/src/gsd/paths.js +66 -0
  143. package/dist/src/gsd/phase-dag.d.ts +12 -0
  144. package/dist/src/gsd/phase-dag.js +94 -0
  145. package/dist/src/gsd/phase-sync.d.ts +42 -0
  146. package/dist/src/gsd/phase-sync.js +321 -0
  147. package/dist/src/gsd/pil-gate-context.d.ts +13 -0
  148. package/dist/src/gsd/pil-gate-context.js +64 -0
  149. package/dist/src/gsd/pil-gate-critic.d.ts +19 -0
  150. package/dist/src/gsd/pil-gate-critic.js +74 -0
  151. package/dist/src/gsd/plan-council-prompts.d.ts +25 -0
  152. package/dist/src/gsd/plan-council-prompts.js +79 -0
  153. package/dist/src/gsd/plan-council.d.ts +44 -0
  154. package/dist/src/gsd/plan-council.js +251 -0
  155. package/dist/src/gsd/plan-gate-vocabulary.d.ts +40 -0
  156. package/dist/src/gsd/plan-gate-vocabulary.js +64 -0
  157. package/dist/src/gsd/product-workspace.d.ts +13 -0
  158. package/dist/src/gsd/product-workspace.js +124 -0
  159. package/dist/src/gsd/ship-bridge.d.ts +25 -0
  160. package/dist/src/gsd/ship-bridge.js +65 -0
  161. package/dist/src/gsd/state-document.d.ts +40 -0
  162. package/dist/src/gsd/state-document.js +163 -0
  163. package/dist/src/gsd/verdict-schema.d.ts +39 -0
  164. package/dist/src/gsd/verdict-schema.js +144 -0
  165. package/dist/src/gsd/verify-context.d.ts +22 -0
  166. package/dist/src/gsd/verify-context.js +27 -0
  167. package/dist/src/gsd/verify-council-prompts.d.ts +19 -0
  168. package/dist/src/gsd/verify-council-prompts.js +85 -0
  169. package/dist/src/gsd/verify-council.d.ts +25 -0
  170. package/dist/src/gsd/verify-council.js +119 -0
  171. package/dist/src/gsd/verify-gate-vocabulary.d.ts +25 -0
  172. package/dist/src/gsd/verify-gate-vocabulary.js +46 -0
  173. package/dist/src/gsd/workflow-engine.d.ts +60 -0
  174. package/dist/src/gsd/workflow-engine.js +207 -0
  175. package/dist/src/gsd/workflow-tools.d.ts +13 -0
  176. package/dist/src/gsd/workflow-tools.js +277 -0
  177. package/dist/src/hooks/index.js +1 -1
  178. package/dist/src/index.js +44 -11
  179. package/dist/src/maintain/pr-builder.js +23 -13
  180. package/dist/src/mcp/auto-setup.js +57 -32
  181. package/dist/src/mcp/client-pool.js +1 -1
  182. package/dist/src/mcp/ee-tools.js +1 -0
  183. package/dist/src/mcp/research-onboarding.js +8 -7
  184. package/dist/src/mcp/runtime.js +34 -2
  185. package/dist/src/models/catalog-client.d.ts +87 -0
  186. package/dist/src/models/catalog-client.js +105 -38
  187. package/dist/src/models/catalog.json +528 -265
  188. package/dist/src/models/registry.d.ts +22 -7
  189. package/dist/src/models/registry.js +73 -10
  190. package/dist/src/ops/doctor.js +1 -1
  191. package/dist/src/orchestrator/auto-commit.js +1 -1
  192. package/dist/src/orchestrator/batch-turn-runner.js +2 -2
  193. package/dist/src/orchestrator/cache-prefix.d.ts +67 -0
  194. package/dist/src/orchestrator/cache-prefix.js +83 -0
  195. package/dist/src/orchestrator/compact-request.d.ts +32 -0
  196. package/dist/src/orchestrator/compact-request.js +41 -0
  197. package/dist/src/orchestrator/compaction.d.ts +10 -0
  198. package/dist/src/orchestrator/compaction.js +27 -7
  199. package/dist/src/orchestrator/council-manager.d.ts +12 -3
  200. package/dist/src/orchestrator/council-manager.js +65 -24
  201. package/dist/src/orchestrator/cross-turn-dedup.d.ts +6 -0
  202. package/dist/src/orchestrator/cross-turn-dedup.js +22 -1
  203. package/dist/src/orchestrator/error-utils.d.ts +29 -0
  204. package/dist/src/orchestrator/error-utils.js +132 -24
  205. package/dist/src/orchestrator/grounding-check.js +39 -1
  206. package/dist/src/orchestrator/message-processor.js +242 -33
  207. package/dist/src/orchestrator/orchestrator.d.ts +39 -3
  208. package/dist/src/orchestrator/orchestrator.js +651 -102
  209. package/dist/src/orchestrator/preprocessor.js +1 -1
  210. package/dist/src/orchestrator/proactive-compact-detector.d.ts +26 -0
  211. package/dist/src/orchestrator/proactive-compact-detector.js +36 -0
  212. package/dist/src/orchestrator/prompts.js +17 -17
  213. package/dist/src/orchestrator/reactive-delegation.d.ts +39 -0
  214. package/dist/src/orchestrator/reactive-delegation.js +59 -0
  215. package/dist/src/orchestrator/retry-classifier.d.ts +2 -1
  216. package/dist/src/orchestrator/retry-classifier.js +46 -2
  217. package/dist/src/orchestrator/safety-intercept.d.ts +45 -0
  218. package/dist/src/orchestrator/safety-intercept.js +55 -0
  219. package/dist/src/orchestrator/scope-reminder.js +1 -1
  220. package/dist/src/orchestrator/session-experience.d.ts +2 -1
  221. package/dist/src/orchestrator/session-experience.js +2 -1
  222. package/dist/src/orchestrator/should-run-gate.d.ts +5 -0
  223. package/dist/src/orchestrator/should-run-gate.js +18 -0
  224. package/dist/src/orchestrator/stall-watchdog.d.ts +24 -3
  225. package/dist/src/orchestrator/stall-watchdog.js +47 -13
  226. package/dist/src/orchestrator/stream-runner.js +62 -29
  227. package/dist/src/orchestrator/sub-agent-cap.d.ts +13 -1
  228. package/dist/src/orchestrator/sub-agent-cap.js +16 -1
  229. package/dist/src/orchestrator/sub-agent-model-tier.d.ts +13 -1
  230. package/dist/src/orchestrator/sub-agent-model-tier.js +16 -2
  231. package/dist/src/orchestrator/subagent-compactor.d.ts +53 -1
  232. package/dist/src/orchestrator/subagent-compactor.js +126 -15
  233. package/dist/src/orchestrator/tool-engine.d.ts +22 -0
  234. package/dist/src/orchestrator/tool-engine.js +620 -56
  235. package/dist/src/orchestrator/tool-limit-auto-recover.d.ts +22 -0
  236. package/dist/src/orchestrator/tool-limit-auto-recover.js +30 -0
  237. package/dist/src/orchestrator/turn-runner-deps.d.ts +19 -0
  238. package/dist/src/orchestrator/turn-watchdog.d.ts +37 -0
  239. package/dist/src/orchestrator/turn-watchdog.js +55 -0
  240. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  241. package/dist/src/pil/agent-operating-contract.js +1 -1
  242. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  243. package/dist/src/pil/cheap-model-playbook.js +5 -1
  244. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  245. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  246. package/dist/src/pil/discovery-types.d.ts +1 -0
  247. package/dist/src/pil/discovery.js +16 -11
  248. package/dist/src/pil/layer1-intent.d.ts +18 -6
  249. package/dist/src/pil/layer1-intent.js +66 -757
  250. package/dist/src/pil/layer15-context-scan.js +15 -1
  251. package/dist/src/pil/layer3-ee-injection.js +23 -8
  252. package/dist/src/pil/layer4-gsd.js +69 -16
  253. package/dist/src/pil/layer5-context.js +7 -3
  254. package/dist/src/pil/layer6-output.d.ts +23 -0
  255. package/dist/src/pil/layer6-output.js +5 -1
  256. package/dist/src/pil/llm-classify.d.ts +33 -2
  257. package/dist/src/pil/llm-classify.js +123 -131
  258. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  259. package/dist/src/pil/native-capabilities-workbook.js +1 -0
  260. package/dist/src/pil/pipeline.js +34 -2
  261. package/dist/src/pil/response-tools.js +5 -3
  262. package/dist/src/pil/schema.d.ts +1 -0
  263. package/dist/src/pil/schema.js +2 -0
  264. package/dist/src/pil/types.d.ts +18 -0
  265. package/dist/src/playbook/directives.d.ts +4 -0
  266. package/dist/src/playbook/directives.js +17 -5
  267. package/dist/src/product-loop/backlog-builder.d.ts +14 -1
  268. package/dist/src/product-loop/backlog-builder.js +30 -6
  269. package/dist/src/product-loop/discovery-context-format.js +3 -1
  270. package/dist/src/product-loop/discovery-ecosystem.js +4 -1
  271. package/dist/src/product-loop/discovery-interview.js +32 -3
  272. package/dist/src/product-loop/discovery-schema.js +5 -1
  273. package/dist/src/product-loop/ideal-trace.d.ts +7 -0
  274. package/dist/src/product-loop/ideal-trace.js +64 -0
  275. package/dist/src/product-loop/index.d.ts +13 -1
  276. package/dist/src/product-loop/index.js +333 -52
  277. package/dist/src/product-loop/loop-driver.d.ts +7 -0
  278. package/dist/src/product-loop/loop-driver.js +310 -99
  279. package/dist/src/product-loop/phase-plan.d.ts +5 -0
  280. package/dist/src/product-loop/phase-plan.js +39 -2
  281. package/dist/src/product-loop/phase-runner.js +9 -1
  282. package/dist/src/product-loop/sprint-runner.d.ts +111 -0
  283. package/dist/src/product-loop/sprint-runner.js +559 -16
  284. package/dist/src/product-loop/types.d.ts +36 -5
  285. package/dist/src/providers/adapter.d.ts +1 -1
  286. package/dist/src/providers/adapter.js +3 -4
  287. package/dist/src/providers/auth/browser-flow.d.ts +1 -1
  288. package/dist/src/providers/auth/browser-flow.js +1 -1
  289. package/dist/src/providers/auth/openai-oauth.js +1 -1
  290. package/dist/src/providers/auth/registry.js +0 -34
  291. package/dist/src/providers/auth/token-store.js +4 -1
  292. package/dist/src/providers/auth/types.d.ts +1 -1
  293. package/dist/src/providers/auth/types.js +1 -1
  294. package/dist/src/providers/capabilities.d.ts +24 -5
  295. package/dist/src/providers/capabilities.js +42 -24
  296. package/dist/src/providers/endpoints.d.ts +2 -2
  297. package/dist/src/providers/endpoints.js +11 -10
  298. package/dist/src/providers/keychain.d.ts +1 -1
  299. package/dist/src/providers/keychain.js +7 -9
  300. package/dist/src/providers/mcp-vision-bridge.js +56 -146
  301. package/dist/src/providers/openai-compatible.js +8 -1
  302. package/dist/src/providers/pricing.d.ts +2 -2
  303. package/dist/src/providers/pricing.js +3 -13
  304. package/dist/src/providers/runtime.d.ts +27 -2
  305. package/dist/src/providers/runtime.js +78 -15
  306. package/dist/src/providers/strategies/base.strategy.d.ts +16 -0
  307. package/dist/src/providers/strategies/base.strategy.js +24 -1
  308. package/dist/src/providers/strategies/{siliconflow.strategy.d.ts → opencode-go.strategy.d.ts} +3 -3
  309. package/dist/src/providers/strategies/opencode-go.strategy.js +83 -0
  310. package/dist/src/providers/strategies/registry.js +4 -4
  311. package/dist/src/providers/strategies/thinking-mode.d.ts +109 -0
  312. package/dist/src/providers/strategies/thinking-mode.js +280 -1
  313. package/dist/src/providers/strategies/zai.strategy.d.ts +14 -0
  314. package/dist/src/providers/strategies/zai.strategy.js +44 -0
  315. package/dist/src/providers/types.d.ts +5 -6
  316. package/dist/src/providers/types.js +2 -2
  317. package/dist/src/providers/vision-backend.d.ts +47 -0
  318. package/dist/src/providers/vision-backend.js +258 -0
  319. package/dist/src/providers/vision-proxy.d.ts +22 -9
  320. package/dist/src/providers/vision-proxy.js +63 -132
  321. package/dist/src/providers/wire-debug.js +95 -0
  322. package/dist/src/router/decide.d.ts +13 -0
  323. package/dist/src/router/decide.js +138 -36
  324. package/dist/src/router/peak-hour.d.ts +38 -0
  325. package/dist/src/router/peak-hour.js +107 -0
  326. package/dist/src/router/step-router.js +3 -2
  327. package/dist/src/router/warm.js +4 -5
  328. package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
  329. package/dist/src/scaffold/continuation-prompt.js +26 -0
  330. package/dist/src/scaffold/point-to-existing.d.ts +21 -0
  331. package/dist/src/scaffold/point-to-existing.js +25 -0
  332. package/dist/src/self-qa/agentic-loop.js +3 -3
  333. package/dist/src/{ui/state → state}/active-run.d.ts +19 -0
  334. package/dist/src/{ui/state → state}/active-run.js +21 -0
  335. package/dist/src/{ui/status-bar/store.d.ts → state/status-bar-store.d.ts} +4 -1
  336. package/dist/src/{ui/status-bar/store.js → state/status-bar-store.js} +12 -9
  337. package/dist/src/state/turn-trace.d.ts +43 -0
  338. package/dist/src/state/turn-trace.js +32 -0
  339. package/dist/src/storage/db.js +2 -1
  340. package/dist/src/storage/index.d.ts +1 -1
  341. package/dist/src/storage/index.js +1 -1
  342. package/dist/src/storage/interaction-log.d.ts +1 -1
  343. package/dist/src/storage/migrations.js +71 -1
  344. package/dist/src/storage/sessions.d.ts +28 -10
  345. package/dist/src/storage/sessions.js +78 -21
  346. package/dist/src/storage/transcript-view.js +1 -1
  347. package/dist/src/storage/transcript.d.ts +51 -0
  348. package/dist/src/storage/transcript.js +284 -13
  349. package/dist/src/tools/file.d.ts +15 -0
  350. package/dist/src/tools/file.js +32 -0
  351. package/dist/src/tools/native-tools.js +5 -0
  352. package/dist/src/tools/registry.d.ts +3 -0
  353. package/dist/src/tools/registry.js +460 -22
  354. package/dist/src/tools/research.d.ts +29 -0
  355. package/dist/src/tools/research.js +233 -0
  356. package/dist/src/types/index.d.ts +118 -3
  357. package/dist/src/ui/app.js +0 -0
  358. package/dist/src/ui/cards/product-status-card.js +1 -1
  359. package/dist/src/ui/components/bubble-body-guard.d.ts +27 -0
  360. package/dist/src/ui/components/bubble-body-guard.js +50 -0
  361. package/dist/src/ui/components/context-rail.d.ts +26 -0
  362. package/dist/src/ui/components/context-rail.js +33 -0
  363. package/dist/src/ui/components/council-conclusion-card.d.ts +73 -0
  364. package/dist/src/ui/components/council-conclusion-card.js +420 -0
  365. package/dist/src/ui/components/council-debate-pill.d.ts +36 -0
  366. package/dist/src/ui/components/council-debate-pill.js +34 -0
  367. package/dist/src/ui/components/council-info-card.js +2 -2
  368. package/dist/src/ui/components/council-leader-bubble.d.ts +10 -3
  369. package/dist/src/ui/components/council-leader-bubble.js +21 -11
  370. package/dist/src/ui/components/council-message-bubble.d.ts +15 -2
  371. package/dist/src/ui/components/council-message-bubble.js +16 -15
  372. package/dist/src/ui/components/council-phase-timeline.d.ts +9 -1
  373. package/dist/src/ui/components/council-phase-timeline.js +49 -15
  374. package/dist/src/ui/components/council-placeholder-bubble.d.ts +16 -9
  375. package/dist/src/ui/components/council-placeholder-bubble.js +32 -29
  376. package/dist/src/ui/components/council-question-card.js +12 -12
  377. package/dist/src/ui/components/council-rail-rounds.d.ts +26 -0
  378. package/dist/src/ui/components/council-rail-rounds.js +57 -0
  379. package/dist/src/ui/components/council-round-group.d.ts +38 -0
  380. package/dist/src/ui/components/council-round-group.js +88 -0
  381. package/dist/src/ui/components/council-status-list.d.ts +3 -1
  382. package/dist/src/ui/components/council-status-list.js +36 -24
  383. package/dist/src/ui/components/council-synthesis-banner.d.ts +7 -2
  384. package/dist/src/ui/components/council-synthesis-banner.js +20 -5
  385. package/dist/src/ui/components/halt-recovery-card.js +9 -5
  386. package/dist/src/ui/components/jump-to-latest-pill.d.ts +11 -0
  387. package/dist/src/ui/components/jump-to-latest-pill.js +14 -0
  388. package/dist/src/ui/components/prompt-box.js +18 -16
  389. package/dist/src/ui/components/session-tree-card.d.ts +14 -0
  390. package/dist/src/ui/components/session-tree-card.js +46 -0
  391. package/dist/src/ui/components/slash-inline-menu.d.ts +12 -0
  392. package/dist/src/ui/components/slash-inline-menu.js +26 -5
  393. package/dist/src/ui/components/task-list-panel.d.ts +14 -1
  394. package/dist/src/ui/components/task-list-panel.js +22 -2
  395. package/dist/src/ui/containers/modals-layer.d.ts +2 -1
  396. package/dist/src/ui/hooks/use-session-picker.d.ts +3 -3
  397. package/dist/src/ui/mcp-modal.js +2 -4
  398. package/dist/src/ui/modals/api-key-modal.js +1 -1
  399. package/dist/src/ui/modals/connect-modal.js +4 -3
  400. package/dist/src/ui/modals/session-picker-modal.d.ts +2 -2
  401. package/dist/src/ui/modals/session-picker-modal.js +3 -5
  402. package/dist/src/ui/picker-providers.d.ts +1 -1
  403. package/dist/src/ui/picker-providers.js +1 -1
  404. package/dist/src/ui/primitives/index.d.ts +1 -0
  405. package/dist/src/ui/primitives/index.js +2 -0
  406. package/dist/src/ui/primitives/semantic-primitives.d.ts +76 -0
  407. package/dist/src/ui/primitives/semantic-primitives.js +81 -0
  408. package/dist/src/ui/slash/compact.js +5 -7
  409. package/dist/src/ui/slash/cost.js +1 -1
  410. package/dist/src/ui/slash/council.js +19 -1
  411. package/dist/src/ui/slash/debug.d.ts +3 -31
  412. package/dist/src/ui/slash/debug.js +9 -20
  413. package/dist/src/ui/slash/ideal.d.ts +6 -2
  414. package/dist/src/ui/slash/ideal.js +97 -7
  415. package/dist/src/ui/slash/menu-items.d.ts +7 -0
  416. package/dist/src/ui/slash/menu-items.js +12 -18
  417. package/dist/src/ui/slash/registry.d.ts +2 -0
  418. package/dist/src/ui/slash/registry.js +4 -0
  419. package/dist/src/ui/status-bar/cache-hit.d.ts +6 -0
  420. package/dist/src/ui/status-bar/cache-hit.js +9 -0
  421. package/dist/src/ui/status-bar/index.d.ts +1 -1
  422. package/dist/src/ui/status-bar/index.js +7 -3
  423. package/dist/src/ui/status-bar/usd-meter.d.ts +5 -4
  424. package/dist/src/ui/status-bar/usd-meter.js +6 -4
  425. package/dist/src/ui/theme.d.ts +1 -0
  426. package/dist/src/ui/theme.js +2 -0
  427. package/dist/src/ui/types.d.ts +7 -0
  428. package/dist/src/ui/use-app-logic.js +0 -0
  429. package/dist/src/ui/utils/format.d.ts +14 -0
  430. package/dist/src/ui/utils/format.js +23 -3
  431. package/dist/src/usage/downgrade.js +2 -2
  432. package/dist/src/usage/product-ledger.js +2 -2
  433. package/dist/src/utils/install-manager.js +2 -1
  434. package/dist/src/utils/logger.js +2 -2
  435. package/dist/src/utils/permission-mode.js +5 -3
  436. package/dist/src/utils/redactor.js +1 -1
  437. package/dist/src/utils/settings.d.ts +153 -5
  438. package/dist/src/utils/settings.js +233 -29
  439. package/dist/src/utils/visible-retry.d.ts +11 -0
  440. package/dist/src/utils/visible-retry.js +10 -1
  441. package/dist/src/verify/entrypoint.d.ts +1 -1
  442. package/dist/src/verify/entrypoint.js +1 -1
  443. package/dist/src/verify/recipes.d.ts +13 -0
  444. package/dist/src/verify/recipes.js +15 -0
  445. package/package.json +135 -132
  446. package/dist/src/providers/auth/gcloud.d.ts +0 -28
  447. package/dist/src/providers/auth/gcloud.js +0 -102
  448. package/dist/src/providers/auth/gemini-oauth.d.ts +0 -82
  449. package/dist/src/providers/auth/gemini-oauth.js +0 -472
  450. package/dist/src/providers/gemini.d.ts +0 -11
  451. package/dist/src/providers/gemini.js +0 -45
  452. package/dist/src/providers/siliconflow-sse-repair.d.ts +0 -58
  453. package/dist/src/providers/siliconflow-sse-repair.js +0 -177
  454. package/dist/src/providers/strategies/google.strategy.d.ts +0 -22
  455. package/dist/src/providers/strategies/google.strategy.js +0 -174
  456. package/dist/src/providers/strategies/siliconflow.strategy.js +0 -29
  457. package/dist/src/ui/containers/chat-feed.d.ts +0 -40
  458. package/dist/src/ui/containers/chat-feed.js +0 -66
@@ -1,11 +1,18 @@
1
1
  import * as path from "node:path";
2
2
  import { runDebate } from "../council/debate.js";
3
+ import { deleteDebateInputs, readDebateCheckpoint, readDebateInputs, writeDebateInputs, } from "../council/debate-checkpoint.js";
4
+ import { resolveDebateSummary } from "../council/debate-summary.js";
3
5
  import { resolveLeaderModelDetailed, resolveParticipants } from "../council/leader.js";
4
6
  import { phaseStart } from "../council/phase-events.js";
5
7
  import { runPreflight } from "../council/preflight.js";
8
+ import { makeStanceRecall } from "../council/stance-recall.js";
6
9
  import { fetchBBContext, inferBBFromPrompt, renderBBContextBlock } from "../ee/bb-retrieval.js";
10
+ import { getDefaultEEClient } from "../ee/intercept.js";
11
+ import { fireAndForgetWorkflowEvent } from "../ee/workflow-event.js";
7
12
  import { readArtifact, writeArtifact } from "../flow/artifact-io.js";
8
- import { logInteraction, recordUsageEvent } from "../storage/index.js";
13
+ import { ensureRunScoped } from "../flow/hierarchy.js";
14
+ import { renderResumeDigest, writeContextDoc, writeResearchDoc } from "../flow/run-artifacts.js";
15
+ import { logInteraction } from "../storage/index.js";
9
16
  import { isCouncilMultiProviderPreferred } from "../utils/settings.js";
10
17
  import { extractAssumptionsFromDebate, mergeAssumptions, renderLedgerSummary } from "./assumption-ledger.js";
11
18
  import { buildPriorContext } from "./cross-run-memory.js";
@@ -17,42 +24,13 @@ import { recordPhaseEnd, recordPhaseStart } from "./phase-budget.js";
17
24
  import { additionalPrefills, auditAsContextBlock, auditRepo } from "./repo-audit.js";
18
25
  import { SEED_DIMENSIONS } from "./seed-questions.js";
19
26
  import { deriveTasksFromSpec, writeTasks } from "./typed-artifacts.js";
20
- /**
21
- * Wraps a CouncilLLM so every debate/research/generate call records a
22
- * usage_events row with source="council". debate.ts callers don't pass an
23
- * onUsage callback today, so the only place we can intercept is here, by
24
- * injecting our recorder into each method invocation before forwarding.
25
- * The original onUsage (if any) is still called.
26
- */
27
- function wrapLLMForUsageTracking(llm, ctx) {
28
- const sid = ctx.sessionId ?? ctx.runId;
29
- const recorder = (modelId) => (usage) => {
30
- try {
31
- recordUsageEvent(sid, "council", modelId, {
32
- inputTokens: usage.inputTokens,
33
- outputTokens: usage.outputTokens,
34
- cacheReadTokens: usage.cachedInputTokens,
35
- });
36
- }
37
- catch {
38
- /* best-effort — never block the debate */
39
- }
40
- };
41
- return {
42
- generate: (modelId, system, prompt, maxTokens, onUsage, signal) => llm.generate(modelId, system, prompt, maxTokens, (u) => {
43
- recorder(modelId)(u);
44
- onUsage?.(u);
45
- }, signal),
46
- debate: (modelId, system, prompt, signal, persistTrace, options, onUsage) => llm.debate(modelId, system, prompt, signal, persistTrace, options, (u) => {
47
- recorder(modelId)(u);
48
- onUsage?.(u);
49
- }),
50
- research: (modelId, topic, conversationContext, signal, persistTrace, options, onUsage) => llm.research(modelId, topic, conversationContext, signal, persistTrace, options, (u) => {
51
- recorder(modelId)(u);
52
- onUsage?.(u);
53
- }),
54
- };
55
- }
27
+ // Council usage_events recording (source="council") now happens at the single
28
+ // source of truth inside createCouncilLLM (src/council/llm.ts recordCouncilUsage),
29
+ // so it covers EVERY council entry point the /council slash path, auto-council,
30
+ // and all /ideal phases (clarifier, research, generate, sprint) not just this
31
+ // driver's runDebate call site. The former loop-driver-local wrapper only wrapped
32
+ // the debate llm and double-counted nothing else; recording at the source removes
33
+ // both the leak and the risk of a double count here.
56
34
  /**
57
35
  * Best-effort interaction_logs writer for the loop-driver. Swallows failures
58
36
  * so a broken DB never blocks the FSM. `ctx.sessionId` falls back to runId
@@ -71,6 +49,21 @@ function logLoopEvent(ctx, subtype, data) {
71
49
  /* non-critical — audit trail only */
72
50
  }
73
51
  }
52
+ /**
53
+ * Clamp a title for the milestone/phase index (F12): truncate at a word
54
+ * boundary when possible and strip trailing punctuation/whitespace so a raw
55
+ * slice can't leave a dangling "(" or a half word. Falls back to a hard slice
56
+ * for a single long token.
57
+ */
58
+ export function clampTitle(raw, max) {
59
+ const s = (raw ?? "").trim();
60
+ if (s.length <= max)
61
+ return s.replace(/[\s([{<"'.,;:–—-]+$/u, "").trim() || s;
62
+ const hard = s.slice(0, max);
63
+ const lastSpace = hard.lastIndexOf(" ");
64
+ const base = lastSpace >= Math.floor(max * 0.6) ? hard.slice(0, lastSpace) : hard;
65
+ return base.replace(/[\s([{<"'.,;:–—-]+$/u, "").trim() || hard.trim();
66
+ }
74
67
  function buildWorkspaceDiscoveryCard(d, a, priorRunCount) {
75
68
  const sections = [];
76
69
  if (!d.hasProject) {
@@ -157,6 +150,12 @@ export async function* runLoopDriver(ctx) {
157
150
  let state = "idle";
158
151
  let clarifiedSpec;
159
152
  let debateState;
153
+ // Resolved once the research debate completes (F9): runningSummary, or a
154
+ // faithful fallback synthesized from participant positions when the debate
155
+ // returned after openings without a running summary. Reused across the
156
+ // research artifacts AND the scoping synthesis prompt so neither loses the
157
+ // debate. Empty until the research phase runs.
158
+ let resolvedDebateSummary = "";
160
159
  let discovery;
161
160
  let audit;
162
161
  let productSpec;
@@ -168,6 +167,28 @@ export async function* runLoopDriver(ctx) {
168
167
  const leaderResolution = await resolveLeaderModelDetailed(ctx.sessionModelId);
169
168
  const leaderModelId = leaderResolution.modelId;
170
169
  const councilParticipants = await resolveParticipants(ctx.sessionModelId, isCouncilMultiProviderPreferred());
170
+ // C-v2 cross-session resume — a persisted debate checkpoint means the council
171
+ // debate was interrupted in a PRIOR session (the process died mid-round; the
172
+ // in-process C-v1 retry only covers same-session throws). Skip the already-
173
+ // completed + persisted discovery + interview and jump straight to the
174
+ // research/debate stage with the restored gather outputs; the `research` case
175
+ // reads the checkpoint and resumes the debate from its last completed round.
176
+ const priorCheckpoint = await readDebateCheckpoint(runDir);
177
+ if (priorCheckpoint) {
178
+ const inputs = await readDebateInputs(runDir);
179
+ if (inputs) {
180
+ clarifiedSpec = inputs.clarifiedSpec;
181
+ conversationContext = inputs.conversationContext;
182
+ state = "research";
183
+ yield {
184
+ type: "content",
185
+ content: `\n> Resuming an interrupted council debate (round ${priorCheckpoint.roundCount + 1}) — skipping discovery + interview.\n`,
186
+ };
187
+ }
188
+ else {
189
+ console.error(`[loop-driver] debate checkpoint present but debate-inputs.json missing (run ${ctx.runId}); running the council FSM fresh.`);
190
+ }
191
+ }
171
192
  while (true) {
172
193
  switch (state) {
173
194
  case "idle": {
@@ -218,6 +239,21 @@ export async function* runLoopDriver(ctx) {
218
239
  if (discoveryCard) {
219
240
  yield { type: "council_info_card", councilInfoCard: discoveryCard };
220
241
  }
242
+ // Part A — persist prior-run context as a first-class context.md. The
243
+ // digest is intentionally NOT re-injected into the live system prompt
244
+ // (PIL Layer 3 already does semantic injection), but writing it to disk
245
+ // makes it a reviewable, resumable surface — the run's inherited memory.
246
+ try {
247
+ const ctxParts = [`Prior runs on this workspace: ${prior.runs.length}`];
248
+ if (prior.digest?.trim())
249
+ ctxParts.push("", prior.digest.trim());
250
+ if (audit.hasProject)
251
+ ctxParts.push("", "## Repo audit", conversationContext);
252
+ await writeContextDoc(ctx.flowDir, ctx.runId, ctxParts.join("\n"));
253
+ }
254
+ catch {
255
+ /* non-critical — context.md is a review surface, never blocks the FSM */
256
+ }
221
257
  // Persist discovery evidence + repo audit so resume can replay.
222
258
  const stateMap = (await readArtifact(runDir, "state.md")) ?? { preamble: "", sections: new Map() };
223
259
  if (discovery.evidence.length > 0) {
@@ -266,7 +302,12 @@ export async function* runLoopDriver(ctx) {
266
302
  });
267
303
  // Write Resume Digest to state.md
268
304
  const stateMap = (await readArtifact(runDir, "state.md")) ?? { preamble: "", sections: new Map() };
269
- stateMap.sections.set("Resume Digest", "Stage: Gather - Defining product dimensions");
305
+ stateMap.sections.set("Resume Digest", renderResumeDigest({
306
+ stage: "gather",
307
+ lastCompleted: "discover",
308
+ nextAction: "Answer clarification questions to define product dimensions",
309
+ updatedAt: new Date().toISOString(),
310
+ }));
270
311
  await writeArtifact(runDir, "state.md", stateMap);
271
312
  // runGatherPhase is async (not a generator), but the discovery
272
313
  // interview emits askcard chunks via the io.emit callback. We collect
@@ -390,6 +431,9 @@ export async function* runLoopDriver(ctx) {
390
431
  if (!clarifiedSpec) {
391
432
  return { runId: ctx.runId, stage: "error", success: false, reason: "missing_spec_for_research" };
392
433
  }
434
+ // Part E — web-research confidence for this run, filled when the
435
+ // Researcher stance is (re)assigned to a web-capable model below.
436
+ let researchWebConfidence;
393
437
  const researchPhaseStartMs = Date.now();
394
438
  yield phaseStart({
395
439
  phaseId: "loop:research",
@@ -402,7 +446,12 @@ export async function* runLoopDriver(ctx) {
402
446
  phase: "research",
403
447
  });
404
448
  const stateMap = (await readArtifact(runDir, "state.md")) ?? { preamble: "", sections: new Map() };
405
- stateMap.sections.set("Resume Digest", "Stage: Research - Multi-expert debate");
449
+ stateMap.sections.set("Resume Digest", renderResumeDigest({
450
+ stage: "research",
451
+ lastCompleted: "gather",
452
+ nextAction: "Run / resume the multi-expert council debate",
453
+ updatedAt: new Date().toISOString(),
454
+ }));
406
455
  await writeArtifact(runDir, "state.md", stateMap);
407
456
  // inside research phase, before building councilTopic:
408
457
  const projectCtx = await readProjectContext(ctx.flowDir, ctx.runId);
@@ -456,6 +505,42 @@ export async function* runLoopDriver(ctx) {
456
505
  stance: s,
457
506
  };
458
507
  });
508
+ // Part E — the Researcher stance MUST prefer a model with NATIVE online
509
+ // web research (its own web_search/browsing), so the research phase gets
510
+ // real online facts rather than codebase-only reasoning. If a web-capable
511
+ // model is reachable this session, route the Researcher to it; otherwise
512
+ // record degraded confidence into research.md (the fallback add-in path
513
+ // — Tavily/MCP — is untrusted per the owner's Part E principle).
514
+ const { modelHasNativeWebResearch, getWebResearchModel } = await import("../models/registry.js");
515
+ const reachableIds = new Set([...councilParticipants.map((p) => p.model), leaderModelId].filter(Boolean));
516
+ const researcherIdx = participants.findIndex((p) => p.stance?.name === "Researcher");
517
+ if (researcherIdx >= 0) {
518
+ const current = participants[researcherIdx].model;
519
+ if (!modelHasNativeWebResearch(current)) {
520
+ // ONLY reroute to a web-native model that is actually REACHABLE this
521
+ // session. A model whose provider has no factory wired (e.g. grok/xai
522
+ // when only opencode-go is authed) would fail council participant
523
+ // creation ("no factory for model's provider") and wedge the debate —
524
+ // so if no reachable web-native model exists, degrade gracefully and
525
+ // KEEP the current reachable model rather than swap in a dead id.
526
+ const webModel = getWebResearchModel(reachableIds);
527
+ if (webModel) {
528
+ participants[researcherIdx].model = webModel.id;
529
+ researchWebConfidence = { confidence: "native", model: webModel.id };
530
+ }
531
+ else {
532
+ researchWebConfidence = { confidence: "degraded" };
533
+ logLoopEvent(ctx, "research_web_degraded", {
534
+ phase: "research",
535
+ reason: "no reachable native_web_research model",
536
+ researcherModel: current,
537
+ });
538
+ }
539
+ }
540
+ else {
541
+ researchWebConfidence = { confidence: "native", model: current };
542
+ }
543
+ }
459
544
  // CB-1 — BB-aware context injection. Runs before council debate fires so
460
545
  // the research stances have access to relevant BB recipes and rules.
461
546
  //
@@ -503,67 +588,124 @@ export async function* runLoopDriver(ctx) {
503
588
  /* graceful degrade — never block the research phase */
504
589
  }
505
590
  }
506
- const debateGen = runDebate(clarifiedSpec, {
507
- topic: ctx.idea,
591
+ // C-v2 persist the minimal gather outputs so a fresh session can
592
+ // re-enter the debate (loop-driver resume entry above) without re-running
593
+ // the interactive discovery + interview. Written once here at debate start,
594
+ // captures the final conversationContext (after project + BB context).
595
+ // Deleted after scoping writes the spec. Non-fatal on failure.
596
+ await writeDebateInputs(runDir, {
597
+ version: 1,
598
+ problemStatement: clarifiedSpec.problemStatement,
599
+ clarifiedSpec,
508
600
  conversationContext,
509
- leaderModelId,
510
- participants,
511
- }, wrapLLMForUsageTracking(ctx.llm, ctx));
512
- // Suppress raw debate content so the user is not confused by inter-role
513
- // monologue ("Researcher Architect ... Question back to you?") which
514
- // is NOT addressed to them. We still pass through phase/status events
515
- // so the UI keeps a live progress indicator. After the debate completes
516
- // we emit a single condensed summary.
517
- try {
518
- while (true) {
519
- const { value, done } = await debateGen.next();
520
- if (done) {
521
- debateState = value;
522
- break;
601
+ savedAt: new Date().toISOString(),
602
+ });
603
+ // C (mid-debate checkpoint) — runDebate snapshots its per-round state to
604
+ // `<runDir>/debate-checkpoint.json` after each completed round and deletes
605
+ // it on normal completion. If a prior attempt (this process, or a crashed
606
+ // earlier run of the SAME runId) left a checkpoint, seed it so the debate
607
+ // resumes from the last completed round instead of re-running rounds 1..N.
608
+ let resumeCheckpoint = (await readDebateCheckpoint(runDir)) ?? undefined;
609
+ // In-process resilience: a mid-debate throw (provider 5xx after the
610
+ // fallback budget, transient network) re-runs runDebate ONCE (default)
611
+ // from the freshly-written checkpoint, so a round-5 break loses one round,
612
+ // not five. Only retried when a checkpoint exists (≥1 round completed)
613
+ // a pre-round-1 failure has nothing to resume and rethrows immediately.
614
+ const maxDebateRetries = (() => {
615
+ const raw = process.env.MUONROI_DEBATE_RESUME_RETRIES;
616
+ const n = raw ? Number.parseInt(raw, 10) : Number.NaN;
617
+ return Number.isFinite(n) && n >= 0 ? n : 1;
618
+ })();
619
+ for (let attempt = 0;; attempt++) {
620
+ const debateGen = runDebate(clarifiedSpec, {
621
+ topic: ctx.idea,
622
+ conversationContext,
623
+ leaderModelId,
624
+ participants,
625
+ runId: ctx.sessionId ?? ctx.runId,
626
+ checkpointDir: runDir,
627
+ resumeCheckpoint,
628
+ // Item 3 — per-stance recall: each participant opens grounded in the
629
+ // stance-weighted slice of the brain its lens cares about. Bounded +
630
+ // failure-tolerant; unavailable EE leaves openings unchanged.
631
+ stanceRecall: makeStanceRecall(getDefaultEEClient(), {
632
+ cwd: ctx.flowDir,
633
+ sourceSession: ctx.sessionId ?? ctx.runId,
634
+ }),
635
+ }, ctx.llm);
636
+ // Suppress raw debate content so the user is not confused by inter-role
637
+ // monologue ("Researcher → Architect ... Question back to you?") which
638
+ // is NOT addressed to them. We still pass through phase/status events
639
+ // so the UI keeps a live progress indicator. After the debate completes
640
+ // we emit a single condensed summary.
641
+ try {
642
+ while (true) {
643
+ const { value, done } = await debateGen.next();
644
+ if (done) {
645
+ debateState = value;
646
+ break;
647
+ }
648
+ const chunk = value;
649
+ if (chunk.type === "content")
650
+ continue;
651
+ // Persist debate speaker turns to interaction_logs so forensics
652
+ // can replay the debate text without relying on TUI scrollback
653
+ // (which currently holds the only copy — messages/usage_events
654
+ // tables stay empty for the debate path).
655
+ if (chunk.type === "council_message" && chunk.councilMessage) {
656
+ const cm = chunk.councilMessage;
657
+ logLoopEvent(ctx, "council_message", {
658
+ phase: "research",
659
+ kind: cm.kind,
660
+ speakerRole: cm.speaker.role,
661
+ speakerModel: cm.speaker.model,
662
+ partnerRole: cm.partner?.role ?? null,
663
+ round: cm.round ?? null,
664
+ attempts: cm.attempts ?? 1,
665
+ failureReason: cm.failureReason ?? null,
666
+ toolCalls: cm.toolCalls?.map((tc) => tc.name) ?? [],
667
+ // Cap at 4000 chars so a single row stays well under SQLite
668
+ // text limits even for the most verbose speaker turn.
669
+ textExcerpt: cm.text.slice(0, 4000),
670
+ textLength: cm.text.length,
671
+ });
672
+ }
673
+ yield chunk;
523
674
  }
524
- const chunk = value;
525
- if (chunk.type === "content")
675
+ break; // debate completed — exit the retry loop.
676
+ }
677
+ catch (err) {
678
+ // The debate iterator hit an exception (e.g. provider 5xx after the
679
+ // retry budget). Persist an audit row so we have a forensics trail —
680
+ // without it the FSM unwinds silently and the session looks like
681
+ // "research = 0 word" in the DB.
682
+ const freshCp = (await readDebateCheckpoint(runDir)) ?? undefined;
683
+ logLoopEvent(ctx, "council_error", {
684
+ phase: "research",
685
+ stage: "debate",
686
+ error: err instanceof Error ? err.message : String(err),
687
+ roundCount: debateState?.roundCount ?? freshCp?.roundCount ?? 0,
688
+ participantCount: participants.length,
689
+ elapsedMs: Date.now() - researchPhaseStartMs,
690
+ });
691
+ // C — resume-from-checkpoint retry: only when a completed round was
692
+ // checkpointed and retries remain. Otherwise rethrow unchanged.
693
+ if (attempt < maxDebateRetries && freshCp && freshCp.roundCount >= 1) {
694
+ resumeCheckpoint = freshCp;
695
+ yield {
696
+ type: "content",
697
+ content: `\n> Debate interrupted (${err instanceof Error ? err.message : String(err)}); resuming from round ${freshCp.roundCount + 1}…\n`,
698
+ };
526
699
  continue;
527
- // Persist debate speaker turns to interaction_logs so forensics
528
- // can replay the debate text without relying on TUI scrollback
529
- // (which currently holds the only copy — messages/usage_events
530
- // tables stay empty for the debate path).
531
- if (chunk.type === "council_message" && chunk.councilMessage) {
532
- const cm = chunk.councilMessage;
533
- logLoopEvent(ctx, "council_message", {
534
- phase: "research",
535
- kind: cm.kind,
536
- speakerRole: cm.speaker.role,
537
- speakerModel: cm.speaker.model,
538
- partnerRole: cm.partner?.role ?? null,
539
- round: cm.round ?? null,
540
- attempts: cm.attempts ?? 1,
541
- failureReason: cm.failureReason ?? null,
542
- toolCalls: cm.toolCalls?.map((tc) => tc.name) ?? [],
543
- // Cap at 4000 chars so a single row stays well under SQLite
544
- // text limits even for the most verbose speaker turn.
545
- textExcerpt: cm.text.slice(0, 4000),
546
- textLength: cm.text.length,
547
- });
548
700
  }
549
- yield chunk;
701
+ throw err;
550
702
  }
551
703
  }
552
- catch (err) {
553
- // The debate iterator hit an exception (e.g. provider 5xx after the
554
- // retry budget). Persist an audit row before re-throwing so we have
555
- // a forensics trail — without it the FSM unwinds silently and the
556
- // session looks like "research = 0 word" in the DB.
557
- logLoopEvent(ctx, "council_error", {
558
- phase: "research",
559
- stage: "debate",
560
- error: err instanceof Error ? err.message : String(err),
561
- roundCount: debateState?.roundCount ?? 0,
562
- participantCount: participants.length,
563
- elapsedMs: Date.now() - researchPhaseStartMs,
564
- });
565
- throw err;
566
- }
704
+ // F9 — resolve the debate summary once (runningSummary or a fallback
705
+ // synthesized from participant positions) for reuse across the research
706
+ // artifacts + scoping synthesis.
707
+ if (debateState)
708
+ resolvedDebateSummary = resolveDebateSummary(debateState);
567
709
  // Forensics row mirrors the council/index.ts council_summary record
568
710
  // (which only fires for sprint planning via runCouncil). The /ideal
569
711
  // initial debate goes through runDebate here and was previously
@@ -592,18 +734,47 @@ export async function* runLoopDriver(ctx) {
592
734
  durationMs: Date.now() - researchPhaseStartMs,
593
735
  });
594
736
  }
595
- const summaryText = debateState?.runningSummary?.trim() || "(debate produced no summary — using empty research findings)";
737
+ const summaryText = resolvedDebateSummary || "(debate produced no summary — using empty research findings)";
596
738
  yield {
597
739
  type: "council_info_card",
598
740
  councilInfoCard: buildResearchSummaryCard(summaryText, debateState?.researchFindings),
599
741
  };
600
- // Append research summary to delegations.md
742
+ // Append research summary to delegations.md (kept for back-compat: the
743
+ // EE transcript extractor + legacy readers still read these sections).
744
+ // A debate that returns after openings (leader routes straight to the
745
+ // preflight gate) leaves runningSummary empty — synthesize a faithful
746
+ // fallback from the participants' positions so the canonical research
747
+ // artifacts never silently drop the whole debate.
601
748
  const delegationsMap = (await readArtifact(runDir, "delegations.md")) ?? { preamble: "", sections: new Map() };
602
- delegationsMap.sections.set("Research Summary", debateState.runningSummary);
749
+ delegationsMap.sections.set("Research Summary", resolvedDebateSummary);
603
750
  if (debateState.researchFindings) {
604
751
  delegationsMap.sections.set("Research Findings", debateState.researchFindings);
605
752
  }
606
753
  await writeArtifact(runDir, "delegations.md", delegationsMap);
754
+ // Part A — promote the debate output to a first-class research.md. This
755
+ // is the canonical, reviewable surface (`/ideal review`) and the home
756
+ // for the EE recall seed once per-turn grounding lands (deferred).
757
+ try {
758
+ await writeResearchDoc(ctx.flowDir, ctx.runId, {
759
+ summary: resolvedDebateSummary,
760
+ findings: debateState.researchFindings ?? undefined,
761
+ webResearch: researchWebConfidence,
762
+ });
763
+ // Part C — persist the debate as a workflow_debate experience so a
764
+ // future run on a similar topic can seed its stances with what this
765
+ // council concluded (gate-on-outcome: fired after the debate produced
766
+ // a summary, not per-turn — Kill #4/#5).
767
+ fireAndForgetWorkflowEvent({
768
+ kind: "council-debate",
769
+ phaseRef: `runs/${ctx.runId}#research`,
770
+ sessionId: ctx.sessionId ?? ctx.runId,
771
+ text: (debateState.runningSummary ?? "").slice(0, 2000) || `debate on: ${ctx.idea.slice(0, 200)}`,
772
+ payload: { topic: ctx.idea.slice(0, 200), roundCount: debateState.roundCount ?? 0 },
773
+ });
774
+ }
775
+ catch {
776
+ /* non-critical — research.md is a review surface, never blocks the FSM */
777
+ }
607
778
  // P6 - extract assumptions from the research debate so foundational
608
779
  // claims (perf budgets, SDK contracts, etc.) become trackable across
609
780
  // sprints rather than buried in stance prose. Silent skip on
@@ -667,7 +838,12 @@ export async function* runLoopDriver(ctx) {
667
838
  phase: "scoping",
668
839
  });
669
840
  const stateMap = (await readArtifact(runDir, "state.md")) ?? { preamble: "", sections: new Map() };
670
- stateMap.sections.set("Resume Digest", "Stage: Scoping - Synthesizing product roadmap");
841
+ stateMap.sections.set("Resume Digest", renderResumeDigest({
842
+ stage: "scoping",
843
+ lastCompleted: "research",
844
+ nextAction: "Synthesize the ProductSpec and confirm the roadmap at the preflight gate",
845
+ updatedAt: new Date().toISOString(),
846
+ }));
671
847
  await writeArtifact(runDir, "state.md", stateMap);
672
848
  // Visible progress indicator — without this the TUI sits silent for
673
849
  // 30-60s while ctx.llm.generate runs the synthesis call. Users read
@@ -687,7 +863,7 @@ export async function* runLoopDriver(ctx) {
687
863
  const synthesisPrompt = `Synthesize a ProductSpec JSON based on the following:
688
864
  Idea: ${ctx.idea}
689
865
  Clarified Spec: ${JSON.stringify(clarifiedSpec)}
690
- Debate Summary: ${debateState.runningSummary}
866
+ Debate Summary: ${resolvedDebateSummary || debateState.runningSummary}
691
867
  Research Findings: ${debateState.researchFindings ?? "N/A"}
692
868
 
693
869
  Output ONLY a JSON object matching this interface:
@@ -758,6 +934,41 @@ interface ProductSpec {
758
934
  const roadmapMap = (await readArtifact(runDir, "roadmap.md")) ?? { preamble: "", sections: new Map() };
759
935
  roadmapMap.sections.set("Product Specification", JSON.stringify(productSpec, null, 2));
760
936
  await writeArtifact(runDir, "roadmap.md", roadmapMap);
937
+ // Part C — the synthesized spec IS the council's decision. Persist it as
938
+ // a workflow_decision experience so future runs don't re-litigate a
939
+ // settled architectural/scoping choice.
940
+ fireAndForgetWorkflowEvent({
941
+ kind: "decision",
942
+ phaseRef: `runs/${ctx.runId}#scoping`,
943
+ sessionId: ctx.sessionId ?? ctx.runId,
944
+ text: `Scoped "${ctx.idea.slice(0, 120)}": ${(productSpec?.architecture ?? "").slice(0, 400)}`,
945
+ payload: {
946
+ persona: productSpec?.persona ?? null,
947
+ mvp: productSpec?.mvp ?? [],
948
+ sprintEstimate: productSpec?.sprintEstimate ?? null,
949
+ },
950
+ });
951
+ // C-v2 — debate + scoping are done and the spec is persisted, so the
952
+ // cross-session resume inputs are obsolete (the checkpoint was already
953
+ // deleted by runDebate on completion). Clean them up so a later
954
+ // `/ideal resume` of this run does not re-enter the debate FSM.
955
+ await deleteDebateInputs(runDir);
956
+ // Hierarchy index — place this run under a milestone (the product idea)
957
+ // and a phase (this scoped iteration). Idempotent across resume; purely
958
+ // an index over runs/, never rewrites run artifacts or ROADMAP phases.
959
+ try {
960
+ const mvpHead = Array.isArray(productSpec?.mvp) && productSpec.mvp.length > 0 ? productSpec.mvp[0] : "";
961
+ await ensureRunScoped(ctx.flowDir, {
962
+ runId: ctx.runId,
963
+ milestoneTitle: clampTitle(ctx.idea, 60),
964
+ milestoneGoal: (productSpec?.architecture ?? "").slice(0, 200),
965
+ phaseTitle: clampTitle(mvpHead || ctx.idea, 50),
966
+ phaseGoal: (productSpec?.persona ?? "").slice(0, 200),
967
+ }, new Date().toISOString());
968
+ }
969
+ catch {
970
+ /* non-critical — hierarchy is an index; failure must not block scoping */
971
+ }
761
972
  // P8 - derive tasks.json from the spec (canonical machine-readable
762
973
  // surface for downstream /execute consumption). MVP items get
763
974
  // sprint=1, phase2 gets sprint=2. Re-deriving the same spec is
@@ -3,6 +3,11 @@ import type { Migrator } from "./discovery-migrations.js";
3
3
  import type { LeaderLike } from "./discovery-prompt-parser.js";
4
4
  import type { PhasePlanArtifact, ProductRunManifest } from "./types.js";
5
5
  export declare const PHASE_PLANNER_SYSTEM: string;
6
+ /**
7
+ * Normalise `id` + `dependsOn` on every phase to the canonical string form.
8
+ * Mutates and returns the same plan object. Safe on already-normalised plans.
9
+ */
10
+ export declare function normalizePhasePlan(plan: PhasePlanArtifact): PhasePlanArtifact;
6
11
  export declare function parsePhasePlanJson(raw: string): PhasePlanArtifact;
7
12
  export declare function validatePhasePlan(plan: PhasePlanArtifact, spec: ClarifiedSpec): void;
8
13
  export declare function fallbackSinglePhase(spec: ClarifiedSpec, manifest: ProductRunManifest): PhasePlanArtifact;
@@ -6,12 +6,47 @@ import { withRateLimitBackoff } from "./discovery-recommender.js";
6
6
  export const PHASE_PLANNER_SYSTEM = "You decompose a product idea into 3–5 sequential phases. Output strict JSON only. " +
7
7
  "Each phase covers a subset of successCriteria verbatim from the input spec. " +
8
8
  "Union of all phases.successCriteria MUST equal the input successCriteria array (no drift, no omission).";
9
+ /**
10
+ * Coerce a phase id / dependsOn element to the canonical `phase-N` string form.
11
+ *
12
+ * The phase-planner prompt does not pin `id` to a string, so the LLM frequently
13
+ * emits bare numbers — e.g. `"id": 1, "dependsOn": [1]` instead of
14
+ * `"id": "phase-1", "dependsOn": ["phase-1"]`. Downstream consumers assume the
15
+ * declared `Phase.id: string` / `dependsOn: string[]` contract; the roadmap
16
+ * renderer (`buildRoadmapFromPhasePlan`) calls `dep.match(...)` and crashed with
17
+ * `dep.match is not a function` on a numeric id, aborting the whole /ideal run
18
+ * right after the sprint plan committed — before any implementation ran.
19
+ * Normalising here (the single parse choke-point) restores the contract for
20
+ * every consumer.
21
+ */
22
+ function normalizePhaseRef(ref) {
23
+ if (typeof ref === "number" && Number.isFinite(ref))
24
+ return `phase-${ref}`;
25
+ const s = String(ref).trim();
26
+ const bareNumber = s.match(/^(\d+)$/);
27
+ if (bareNumber)
28
+ return `phase-${bareNumber[1]}`;
29
+ return s;
30
+ }
31
+ /**
32
+ * Normalise `id` + `dependsOn` on every phase to the canonical string form.
33
+ * Mutates and returns the same plan object. Safe on already-normalised plans.
34
+ */
35
+ export function normalizePhasePlan(plan) {
36
+ if (Array.isArray(plan?.phases)) {
37
+ for (const phase of plan.phases) {
38
+ phase.id = normalizePhaseRef(phase.id);
39
+ phase.dependsOn = Array.isArray(phase.dependsOn) ? phase.dependsOn.map(normalizePhaseRef) : [];
40
+ }
41
+ }
42
+ return plan;
43
+ }
9
44
  export function parsePhasePlanJson(raw) {
10
45
  const stripped = raw
11
46
  .replace(/^```(?:json)?\s*/, "")
12
47
  .replace(/\s*```$/, "")
13
48
  .trim();
14
- return JSON.parse(stripped);
49
+ return normalizePhasePlan(JSON.parse(stripped));
15
50
  }
16
51
  export function validatePhasePlan(plan, spec) {
17
52
  if (plan.phases.length < 1 || plan.phases.length > 6) {
@@ -109,7 +144,9 @@ export async function readPhasePlan(flowDir, runId) {
109
144
  if (!raw)
110
145
  return null;
111
146
  try {
112
- return JSON.parse(raw);
147
+ // Normalise on read too: runs persisted before the parse-time normalisation
148
+ // landed (or hand-edited phases.md) can still carry numeric ids.
149
+ return normalizePhasePlan(JSON.parse(raw));
113
150
  }
114
151
  catch {
115
152
  return null;
@@ -1,5 +1,7 @@
1
1
  import * as path from "node:path";
2
2
  import { readArtifact, writeArtifact } from "../flow/artifact-io.js";
3
+ import { isGsdNativeEnabled } from "../gsd/flags.js";
4
+ import { orderPhasesForExecution, syncPhasePlanToRoadmap } from "../gsd/phase-dag.js";
3
5
  import { buildSprintContext, digestSprintIntoPhase, handoffPhaseToNext } from "./context-policy.js";
4
6
  import { formatProjectContextForPrompt } from "./discovery-context-format.js";
5
7
  import { backupCorruptPhases, generatePhasePlan, readPhasePlan, validatePhasePlan, writePhasePlan, } from "./phase-plan.js";
@@ -249,8 +251,14 @@ export async function* runPhases(args) {
249
251
  backoffDelays: args.backoffDelays,
250
252
  });
251
253
  await writePhasePlan(args.flowDir, args.runId, plan);
254
+ if (isGsdNativeEnabled() && args.projectCwd) {
255
+ syncPhasePlanToRoadmap(args.projectCwd, args.idea ?? args.clarifiedSpec.problemStatement, plan);
256
+ }
252
257
  }
253
- for (const phase of orderByDeps(plan.phases)) {
258
+ const orderedPhases = args.projectCwd
259
+ ? orderPhasesForExecution(args.projectCwd, plan.phases)
260
+ : orderByDeps(plan.phases);
261
+ for (const phase of orderedPhases) {
254
262
  const status = await readPhaseStatus(args.flowDir, args.runId, phase.id);
255
263
  if (status === "done" || status === "blocked")
256
264
  continue;