muonroi-cli 1.7.2 → 1.8.1

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 (326) hide show
  1. package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
  2. package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
  3. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
  4. package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
  5. package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
  6. package/dist/packages/agent-harness-core/src/selector.js +2 -2
  7. package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
  8. package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
  9. package/dist/packages/agent-harness-opentui/src/install.js +1 -1
  10. package/dist/src/__test-stubs__/vitest-setup.js +4 -0
  11. package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
  12. package/dist/src/agent-harness/mock-model.d.ts +1 -1
  13. package/dist/src/agent-harness/mock-model.js +143 -6
  14. package/dist/src/agent-harness/test-spawn.js +3 -1
  15. package/dist/src/chat/chat-keychain.js +9 -1
  16. package/dist/src/cli/cost-forensics.js +7 -4
  17. package/dist/src/cli/keys.d.ts +2 -2
  18. package/dist/src/cli/keys.js +107 -28
  19. package/dist/src/cli/usage-report.js +2 -2
  20. package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
  21. package/dist/src/council/__tests__/abort-threading.test.js +193 -0
  22. package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
  23. package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
  24. package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
  25. package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
  26. package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
  27. package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
  28. package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
  29. package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
  30. package/dist/src/council/__tests__/research-tools.test.js +4 -0
  31. package/dist/src/council/__tests__/round-tools.test.js +6 -0
  32. package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
  33. package/dist/src/council/clarifier-question-cap.test.js +73 -0
  34. package/dist/src/council/clarifier.d.ts +29 -1
  35. package/dist/src/council/clarifier.js +64 -1
  36. package/dist/src/council/debate-planner.d.ts +2 -1
  37. package/dist/src/council/debate-planner.js +5 -3
  38. package/dist/src/council/debate.d.ts +9 -0
  39. package/dist/src/council/debate.js +88 -8
  40. package/dist/src/council/index.d.ts +46 -1
  41. package/dist/src/council/index.js +125 -19
  42. package/dist/src/council/llm.js +41 -13
  43. package/dist/src/council/prompts.js +36 -1
  44. package/dist/src/council/types.d.ts +14 -1
  45. package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
  46. package/dist/src/ee/bridge.d.ts +3 -1
  47. package/dist/src/ee/bridge.js +9 -3
  48. package/dist/src/ee/bridge.test.js +1 -2
  49. package/dist/src/ee/client.js +15 -2
  50. package/dist/src/ee/recall-ledger.d.ts +20 -0
  51. package/dist/src/ee/recall-ledger.js +40 -1
  52. package/dist/src/ee/search.d.ts +25 -0
  53. package/dist/src/ee/search.js +73 -0
  54. package/dist/src/ee/types.d.ts +15 -1
  55. package/dist/src/ee/who-am-i.d.ts +41 -0
  56. package/dist/src/ee/who-am-i.js +143 -0
  57. package/dist/src/ee/who-am-i.test.d.ts +1 -0
  58. package/dist/src/ee/who-am-i.test.js +101 -0
  59. package/dist/src/flow/compaction/index.d.ts +1 -0
  60. package/dist/src/flow/compaction/index.js +4 -0
  61. package/dist/src/generated/version.d.ts +1 -1
  62. package/dist/src/generated/version.js +1 -1
  63. package/dist/src/headless/council-answers.js +1 -0
  64. package/dist/src/headless/output.d.ts +30 -1
  65. package/dist/src/headless/output.js +180 -0
  66. package/dist/src/headless/output.test.js +61 -1
  67. package/dist/src/hooks/index.js +48 -2
  68. package/dist/src/index.d.ts +2 -0
  69. package/dist/src/index.js +59 -62
  70. package/dist/src/lsp/client-normalize.test.d.ts +12 -0
  71. package/dist/src/lsp/client-normalize.test.js +34 -0
  72. package/dist/src/lsp/client.d.ts +1 -0
  73. package/dist/src/lsp/client.js +10 -2
  74. package/dist/src/lsp/manager.d.ts +1 -1
  75. package/dist/src/lsp/manager.js +13 -5
  76. package/dist/src/lsp/manager.test.js +41 -2
  77. package/dist/src/lsp/runtime.d.ts +1 -1
  78. package/dist/src/lsp/runtime.js +2 -2
  79. package/dist/src/maintain/__tests__/gh-create-pr.test.js +7 -3
  80. package/dist/src/maintain/__tests__/pr-builder.test.js +7 -3
  81. package/dist/src/mcp/__tests__/client-pool.spec.js +3 -3
  82. package/dist/src/mcp/__tests__/ee-tools.test.js +42 -14
  83. package/dist/src/mcp/__tests__/forensics-tools.test.js +3 -3
  84. package/dist/src/mcp/__tests__/lsp-tools.test.js +4 -4
  85. package/dist/src/mcp/auto-setup.js +0 -8
  86. package/dist/src/mcp/client-pool.d.ts +1 -1
  87. package/dist/src/mcp/client-pool.js +25 -9
  88. package/dist/src/mcp/ee-tools.d.ts +10 -0
  89. package/dist/src/mcp/ee-tools.js +34 -0
  90. package/dist/src/mcp/self-verify-jobs.d.ts +1 -0
  91. package/dist/src/mcp/self-verify-jobs.js +1 -3
  92. package/dist/src/mcp/self-verify-runner.js +1 -1
  93. package/dist/src/models/catalog-gemini.test.js +12 -10
  94. package/dist/src/models/catalog.json +106 -38
  95. package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
  96. package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
  97. package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +3 -0
  98. package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
  99. package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
  100. package/dist/src/orchestrator/__tests__/message-processor.test.js +2 -0
  101. package/dist/src/orchestrator/__tests__/prompts.test.d.ts +1 -0
  102. package/dist/src/orchestrator/__tests__/prompts.test.js +88 -0
  103. package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
  104. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.d.ts +1 -0
  105. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.js +507 -0
  106. package/dist/src/orchestrator/agent.test.js +5 -2
  107. package/dist/src/orchestrator/auto-commit.d.ts +110 -0
  108. package/dist/src/orchestrator/auto-commit.js +366 -0
  109. package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
  110. package/dist/src/orchestrator/batch-turn-runner.js +2 -1
  111. package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
  112. package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
  113. package/dist/src/orchestrator/compaction.d.ts +22 -0
  114. package/dist/src/orchestrator/compaction.js +42 -0
  115. package/dist/src/orchestrator/delegations.d.ts +1 -0
  116. package/dist/src/orchestrator/delegations.js +41 -0
  117. package/dist/src/orchestrator/error-utils.js +27 -2
  118. package/dist/src/orchestrator/message-processor.d.ts +23 -5
  119. package/dist/src/orchestrator/message-processor.js +226 -2573
  120. package/dist/src/orchestrator/orchestrator.d.ts +11 -2
  121. package/dist/src/orchestrator/orchestrator.js +530 -43
  122. package/dist/src/orchestrator/pending-calls.js +2 -1
  123. package/dist/src/orchestrator/preprocessor.d.ts +13 -0
  124. package/dist/src/orchestrator/preprocessor.js +146 -0
  125. package/dist/src/orchestrator/prompts.d.ts +16 -0
  126. package/dist/src/orchestrator/prompts.js +130 -67
  127. package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
  128. package/dist/src/orchestrator/safety-askcard.js +74 -0
  129. package/dist/src/orchestrator/sandbox.test.js +8 -5
  130. package/dist/src/orchestrator/scope-ceiling.js +1 -1
  131. package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
  132. package/dist/src/orchestrator/stall-watchdog.js +23 -0
  133. package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
  134. package/dist/src/orchestrator/stream-runner.js +52 -30
  135. package/dist/src/orchestrator/sub-agent-cap.d.ts +2 -0
  136. package/dist/src/orchestrator/sub-agent-cap.js +12 -4
  137. package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
  138. package/dist/src/orchestrator/subagent-compactor.d.ts +21 -0
  139. package/dist/src/orchestrator/subagent-compactor.js +148 -24
  140. package/dist/src/orchestrator/subagent-compactor.spec.js +249 -1
  141. package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
  142. package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
  143. package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
  144. package/dist/src/orchestrator/tool-args-repair.js +1 -1
  145. package/dist/src/orchestrator/tool-engine.d.ts +153 -0
  146. package/dist/src/orchestrator/tool-engine.js +3138 -0
  147. package/dist/src/orchestrator/turn-runner-deps.d.ts +3 -0
  148. package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
  149. package/dist/src/pil/__tests__/discovery.test.js +89 -145
  150. package/dist/src/pil/__tests__/dual-run.test.js +2 -0
  151. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
  152. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  153. package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
  154. package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
  155. package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
  156. package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
  157. package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
  158. package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
  159. package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
  160. package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
  161. package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
  162. package/dist/src/pil/__tests__/llm-classify.test.js +79 -1
  163. package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
  164. package/dist/src/pil/__tests__/pipeline.test.js +11 -8
  165. package/dist/src/pil/__tests__/response-tools.test.js +52 -0
  166. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  167. package/dist/src/pil/agent-operating-contract.js +5 -3
  168. package/dist/src/pil/agent-operating-contract.test.js +2 -2
  169. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  170. package/dist/src/pil/cheap-model-playbook.js +1 -1
  171. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  172. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  173. package/dist/src/pil/config.d.ts +20 -0
  174. package/dist/src/pil/config.js +35 -0
  175. package/dist/src/pil/discovery-types.d.ts +32 -9
  176. package/dist/src/pil/discovery.d.ts +3 -3
  177. package/dist/src/pil/discovery.js +138 -244
  178. package/dist/src/pil/index.d.ts +1 -1
  179. package/dist/src/pil/index.js +1 -1
  180. package/dist/src/pil/layer1-intent.d.ts +9 -1
  181. package/dist/src/pil/layer1-intent.js +62 -7
  182. package/dist/src/pil/layer1-intent.test.js +112 -2
  183. package/dist/src/pil/layer15-context-scan.js +1 -1
  184. package/dist/src/pil/layer16-clarity.d.ts +21 -25
  185. package/dist/src/pil/layer16-clarity.js +52 -88
  186. package/dist/src/pil/layer18-acceptance.d.ts +13 -3
  187. package/dist/src/pil/layer18-acceptance.js +1 -75
  188. package/dist/src/pil/layer2-personality.js +7 -8
  189. package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
  190. package/dist/src/pil/layer2_5-ponytail.js +41 -0
  191. package/dist/src/pil/layer3-ee-injection.js +180 -14
  192. package/dist/src/pil/layer4-gsd.js +22 -8
  193. package/dist/src/pil/layer5-context.d.ts +1 -0
  194. package/dist/src/pil/layer5-context.js +26 -11
  195. package/dist/src/pil/layer6-output.js +23 -5
  196. package/dist/src/pil/llm-classify.d.ts +11 -0
  197. package/dist/src/pil/llm-classify.js +196 -3
  198. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  199. package/dist/src/pil/native-capabilities-workbook.js +4 -3
  200. package/dist/src/pil/pipeline.js +15 -1
  201. package/dist/src/pil/response-tools.d.ts +16 -0
  202. package/dist/src/pil/response-tools.js +41 -0
  203. package/dist/src/pil/schema.d.ts +8 -0
  204. package/dist/src/pil/schema.js +24 -3
  205. package/dist/src/pil/task-tier-map.js +7 -7
  206. package/dist/src/pil/types.d.ts +9 -3
  207. package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
  208. package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
  209. package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
  210. package/dist/src/product-loop/discovery-interview.d.ts +20 -0
  211. package/dist/src/product-loop/discovery-interview.js +121 -7
  212. package/dist/src/product-loop/gather.js +19 -5
  213. package/dist/src/product-loop/loop-driver.js +2 -2
  214. package/dist/src/product-loop/sprint-self-verify.js +2 -2
  215. package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
  216. package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
  217. package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
  218. package/dist/src/providers/auth/gcloud.d.ts +28 -0
  219. package/dist/src/providers/auth/gcloud.js +102 -0
  220. package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
  221. package/dist/src/providers/auth/gemini-oauth.js +292 -105
  222. package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
  223. package/dist/src/providers/auth/grok-oauth.js +1 -1
  224. package/dist/src/providers/auth/registry.js +28 -6
  225. package/dist/src/providers/gemini.d.ts +1 -1
  226. package/dist/src/providers/gemini.js +10 -3
  227. package/dist/src/providers/keychain.d.ts +2 -2
  228. package/dist/src/providers/keychain.js +20 -2
  229. package/dist/src/providers/openai.js +5 -1
  230. package/dist/src/providers/runtime.d.ts +1 -1
  231. package/dist/src/providers/runtime.js +11 -2
  232. package/dist/src/providers/siliconflow-sse-repair.js +1 -1
  233. package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
  234. package/dist/src/providers/strategies/google.strategy.js +159 -9
  235. package/dist/src/providers/strategies/thinking-mode.js +19 -6
  236. package/dist/src/providers/types.d.ts +1 -1
  237. package/dist/src/router/decide.test.js +40 -8
  238. package/dist/src/router/step-router.d.ts +20 -0
  239. package/dist/src/router/step-router.js +73 -0
  240. package/dist/src/self-qa/agentic-loop.js +30 -21
  241. package/dist/src/self-qa/orchestrator.js +5 -5
  242. package/dist/src/self-qa/scenario-planner.js +1 -1
  243. package/dist/src/self-qa/spec-emitter.js +4 -4
  244. package/dist/src/storage/__tests__/hierarchical-session.test.d.ts +1 -0
  245. package/dist/src/storage/__tests__/hierarchical-session.test.js +161 -0
  246. package/dist/src/storage/__tests__/sweep-stale-pending.test.js +2 -2
  247. package/dist/src/storage/index.d.ts +1 -1
  248. package/dist/src/storage/index.js +1 -1
  249. package/dist/src/storage/migrations.js +13 -1
  250. package/dist/src/storage/transcript-response-entry.test.js +18 -5
  251. package/dist/src/storage/transcript-view.js +7 -1
  252. package/dist/src/storage/transcript.d.ts +3 -0
  253. package/dist/src/storage/transcript.js +254 -73
  254. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  255. package/dist/src/storage/usage.js +22 -5
  256. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  257. package/dist/src/tools/bash.d.ts +36 -23
  258. package/dist/src/tools/bash.js +59 -301
  259. package/dist/src/tools/bash.test.js +2 -257
  260. package/dist/src/tools/file-tracker.d.ts +5 -0
  261. package/dist/src/tools/file-tracker.js +14 -0
  262. package/dist/src/tools/file.js +2 -0
  263. package/dist/src/tools/git-safety.d.ts +38 -1
  264. package/dist/src/tools/git-safety.js +94 -4
  265. package/dist/src/tools/git-safety.test.js +45 -1
  266. package/dist/src/tools/native-tools.d.ts +1 -1
  267. package/dist/src/tools/native-tools.js +66 -1
  268. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  269. package/dist/src/tools/registry-git-safety.test.js +17 -6
  270. package/dist/src/tools/registry.d.ts +6 -0
  271. package/dist/src/tools/registry.js +215 -13
  272. package/dist/src/types/index.d.ts +8 -2
  273. package/dist/src/ui/app.d.ts +1 -0
  274. package/dist/src/ui/app.js +0 -0
  275. package/dist/src/ui/components/council-question-card.js +1 -0
  276. package/dist/src/ui/components/message-view.js +37 -3
  277. package/dist/src/ui/components/structured-response-view.js +7 -0
  278. package/dist/src/ui/components/task-list-panel.js +3 -12
  279. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  280. package/dist/src/ui/containers/chat-feed.js +66 -0
  281. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  282. package/dist/src/ui/containers/modals-layer.js +18 -0
  283. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  284. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  285. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  286. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  287. package/dist/src/ui/slash/expand.js +14 -1
  288. package/dist/src/ui/slash/export.js +16 -1
  289. package/dist/src/ui/slash/menu-items.js +1 -0
  290. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  291. package/dist/src/ui/slash/ponytail.js +19 -0
  292. package/dist/src/ui/status-bar/store.js +0 -1
  293. package/dist/src/ui/status-bar/store.test.js +2 -2
  294. package/dist/src/ui/use-app-logic.d.ts +26 -0
  295. package/dist/src/ui/use-app-logic.js +0 -0
  296. package/dist/src/ui/utils/relaunch.js +1 -1
  297. package/dist/src/ui/utils/text.d.ts +11 -0
  298. package/dist/src/ui/utils/text.js +21 -1
  299. package/dist/src/ui/utils/text.test.js +25 -1
  300. package/dist/src/ui/utils/tools.js +3 -1
  301. package/dist/src/usage/cost-log.js +2 -2
  302. package/dist/src/usage/estimator.d.ts +14 -0
  303. package/dist/src/usage/estimator.js +22 -0
  304. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  305. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  306. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  307. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  308. package/dist/src/utils/__tests__/llm-deadline.test.js +16 -4
  309. package/dist/src/utils/__tests__/logger.test.d.ts +1 -0
  310. package/dist/src/utils/__tests__/logger.test.js +115 -0
  311. package/dist/src/utils/__tests__/settings-agent-first.test.d.ts +1 -0
  312. package/dist/src/utils/__tests__/settings-agent-first.test.js +48 -0
  313. package/dist/src/utils/install-manager.d.ts +1 -0
  314. package/dist/src/utils/install-manager.js +76 -7
  315. package/dist/src/utils/install-manager.test.js +43 -1
  316. package/dist/src/utils/llm-deadline.d.ts +9 -1
  317. package/dist/src/utils/llm-deadline.js +31 -2
  318. package/dist/src/utils/logger.d.ts +28 -0
  319. package/dist/src/utils/logger.js +171 -0
  320. package/dist/src/utils/permission-mode.d.ts +33 -0
  321. package/dist/src/utils/permission-mode.js +164 -3
  322. package/dist/src/utils/permission-mode.test.js +92 -1
  323. package/dist/src/utils/settings.d.ts +36 -32
  324. package/dist/src/utils/settings.js +151 -123
  325. package/dist/src/utils/update-checker.test.js +5 -1
  326. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  import { promises as fs } from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
- import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { iterateInterview } from "../discovery-interview.js";
7
7
  import { initDiscoveryState, readDiscoveryState } from "../discovery-persistence.js";
8
8
  async function mktmp() {
@@ -66,13 +66,25 @@ function makeRecommender(answers) {
66
66
  describe("discovery-interview", () => {
67
67
  let flowDir;
68
68
  const runId = "iv-run";
69
+ let prevAutofill;
69
70
  beforeEach(async () => {
71
+ // These tests exercise the per-question CARD flow (skip budget, FE-policy
72
+ // retry, leader/council dispatch). Disable G2-b auto-fill so the cards
73
+ // actually surface; the auto-fill path has its own test below.
74
+ prevAutofill = process.env.MUONROI_DISCOVERY_AUTOFILL;
75
+ process.env.MUONROI_DISCOVERY_AUTOFILL = "0";
70
76
  flowDir = await mktmp();
71
77
  await initDiscoveryState(flowDir, runId, {
72
78
  classification: "greenfield",
73
79
  prefillSource: { fromDetection: [], fromPrompt: [] },
74
80
  });
75
81
  });
82
+ afterEach(() => {
83
+ if (prevAutofill === undefined)
84
+ delete process.env.MUONROI_DISCOVERY_AUTOFILL;
85
+ else
86
+ process.env.MUONROI_DISCOVERY_AUTOFILL = prevAutofill;
87
+ });
76
88
  it("iterates all 10 questions with leader/council dispatch", async () => {
77
89
  const answers = {
78
90
  productType: "saas",
@@ -288,5 +300,95 @@ describe("discovery-interview", () => {
288
300
  const state = await readDiscoveryState(flowDir, runId);
289
301
  expect((state?.answers.frontendApproach).library).toBe("shadcn");
290
302
  });
303
+ // G2-b: a minimal/well-specified prompt auto-accepts the recommender primary
304
+ // for required questions — NO per-question cards — and surfaces ONE summary
305
+ // gate listing the assumptions (verified live: /ideal previously showed
306
+ // productType/targetPlatform/audience as 3 separate cards).
307
+ it("auto-fills required questions and surfaces ONE summary gate (minimal prompt)", async () => {
308
+ delete process.env.MUONROI_DISCOVERY_AUTOFILL; // auto-fill ON (default)
309
+ const answers = {
310
+ productType: "saas",
311
+ targetPlatform: ["cli"],
312
+ audience: { persona: "devs", scale: "1k-100k", geography: "SEA" },
313
+ backendArchitecture: "monolith",
314
+ backendStack: { language: "TS", framework: "Nest" },
315
+ dbStrategy: { mode: "greenfield", engine: "PG" },
316
+ };
317
+ const rec = makeRecommender(answers);
318
+ const promptedIds = [];
319
+ let gateAssumptions;
320
+ const userPrompt = async (args) => {
321
+ promptedIds.push(args.questionId);
322
+ if (args.questionId === "__user_gate__") {
323
+ gateAssumptions = args.assumptions;
324
+ return { action: "proceed" };
325
+ }
326
+ return { action: "accept" };
327
+ };
328
+ await iterateInterview({
329
+ flowDir,
330
+ runId,
331
+ idea: "build a small cli todo app",
332
+ capUsd: 50,
333
+ detection: FAKE_DETECTION,
334
+ userPrompt,
335
+ recommender: rec,
336
+ });
337
+ // No per-question card was shown — only the single summary gate.
338
+ expect(promptedIds.filter((id) => id !== "__user_gate__")).toEqual([]);
339
+ expect(promptedIds).toContain("__user_gate__");
340
+ // The gate carried the assumed required answers for one-shot review.
341
+ expect(gateAssumptions?.map((a) => a.id)).toEqual(expect.arrayContaining(["productType", "targetPlatform", "audience", "backendStack", "dbStrategy"]));
342
+ const state = await readDiscoveryState(flowDir, runId);
343
+ expect(state?.answers.productType).toBe("saas");
344
+ expect(state?.userGatePassed).toBe(true);
345
+ });
346
+ // G1 follow-up: from the summary gate the user can revise ONE auto-filled
347
+ // assumption in place (instead of abort+reprompt or AUTOFILL=0). The interview
348
+ // re-asks just that field, persists the new value, and re-shows the gate.
349
+ it("edit-field at the gate re-asks one assumption, saves the new value, then re-shows the gate", async () => {
350
+ delete process.env.MUONROI_DISCOVERY_AUTOFILL; // auto-fill ON
351
+ const answers = {
352
+ productType: "saas",
353
+ targetPlatform: ["cli"],
354
+ audience: { persona: "devs", scale: "1k-100k", geography: "SEA" },
355
+ backendArchitecture: "monolith",
356
+ backendStack: { language: "TS", framework: "Nest" },
357
+ dbStrategy: { mode: "greenfield", engine: "PG" },
358
+ };
359
+ const rec = makeRecommender(answers);
360
+ const gatesSeen = [];
361
+ let productTypeEditAsks = 0;
362
+ const userPrompt = async (args) => {
363
+ if (args.questionId === "__user_gate__") {
364
+ gatesSeen.push(args.assumptions);
365
+ // First gate → edit productType; after the edit, second gate → proceed.
366
+ return gatesSeen.length === 1 ? { action: "edit-field", fieldId: "productType" } : { action: "proceed" };
367
+ }
368
+ if (args.questionId === "productType" && !args.message) {
369
+ productTypeEditAsks += 1;
370
+ return { action: "override", value: "internal-tool", reason: "revised at gate" };
371
+ }
372
+ return { action: "accept" };
373
+ };
374
+ await iterateInterview({
375
+ flowDir,
376
+ runId,
377
+ idea: "build a small cli todo app",
378
+ capUsd: 50,
379
+ detection: FAKE_DETECTION,
380
+ userPrompt,
381
+ recommender: rec,
382
+ });
383
+ // Gate shown twice (edit, then proceed); productType re-asked exactly once.
384
+ expect(gatesSeen.length).toBe(2);
385
+ expect(productTypeEditAsks).toBe(1);
386
+ // The edited value is persisted (NOT the auto-filled "saas").
387
+ const state = await readDiscoveryState(flowDir, runId);
388
+ expect(state?.answers.productType).toBe("internal-tool");
389
+ expect(state?.userGatePassed).toBe(true);
390
+ // The re-shown gate reflects the edit in its assumption summary.
391
+ expect(gatesSeen[1]?.find((a) => a.id === "productType")?.value).toBe("internal-tool");
392
+ });
291
393
  });
292
394
  //# sourceMappingURL=discovery-interview.test.js.map
@@ -106,4 +106,38 @@ describe("buildGatherUserPrompt — selectable alternatives", () => {
106
106
  expect(await prompt2({ questionId: "x", recommendation: makeRec() })).toEqual({ action: "skip" });
107
107
  });
108
108
  });
109
+ describe("buildGatherUserPrompt — user-gate per-field edit (G1 follow-up)", () => {
110
+ const assumptions = [
111
+ { id: "productType", value: "internal-tool" },
112
+ { id: "targetPlatform", value: ["cli"] },
113
+ ];
114
+ it("offers proceed + one edit:<id> per assumption + ask-more + abort", async () => {
115
+ let captured;
116
+ const tuiAsk = vi.fn(async (_label, options) => {
117
+ captured = options;
118
+ return "proceed";
119
+ });
120
+ await buildGatherUserPrompt(tuiAsk)({ questionId: "__user_gate__", assumptions });
121
+ expect(captured).toEqual(["proceed", "edit: productType", "edit: targetPlatform", "ask-more", "abort"]);
122
+ });
123
+ it('selecting "edit: targetPlatform" returns {action:"edit-field", fieldId}', async () => {
124
+ const tuiAsk = vi.fn(async () => "edit: targetPlatform");
125
+ const result = await buildGatherUserPrompt(tuiAsk)({ questionId: "__user_gate__", assumptions });
126
+ expect(result).toEqual({ action: "edit-field", fieldId: "targetPlatform" });
127
+ });
128
+ it("no assumptions → no edit options (proceed/ask-more/abort only)", async () => {
129
+ let captured;
130
+ const tuiAsk = vi.fn(async (_label, options) => {
131
+ captured = options;
132
+ return "ask-more";
133
+ });
134
+ await buildGatherUserPrompt(tuiAsk)({ questionId: "__user_gate__" });
135
+ expect(captured).toEqual(["proceed", "ask-more", "abort"]);
136
+ });
137
+ it("proceed / abort / ask-more still map correctly with assumptions present", async () => {
138
+ expect(await buildGatherUserPrompt(vi.fn(async () => "proceed"))({ questionId: "__user_gate__", assumptions })).toEqual({ action: "proceed" });
139
+ expect(await buildGatherUserPrompt(vi.fn(async () => "abort"))({ questionId: "__user_gate__", assumptions })).toEqual({ action: "abort" });
140
+ expect(await buildGatherUserPrompt(vi.fn(async () => "ask-more"))({ questionId: "__user_gate__", assumptions })).toEqual({ action: "ask-more" });
141
+ });
142
+ });
109
143
  //# sourceMappingURL=gather-selectable-alts.test.js.map
@@ -8,22 +8,22 @@
8
8
  import { describe, expect, it } from "vitest";
9
9
  import { runSprintSelfVerify } from "../sprint-self-verify.js";
10
10
  describe("runSprintSelfVerify", () => {
11
- const ORIG_CI = process.env["CI"];
12
- const ORIG_NODE_ENV = process.env["NODE_ENV"];
11
+ const ORIG_CI = process.env.CI;
12
+ const ORIG_NODE_ENV = process.env.NODE_ENV;
13
13
  function clearEnv() {
14
- delete process.env["MUONROI_SPRINT_SELF_VERIFY"];
15
- delete process.env["CI"];
16
- delete process.env["NODE_ENV"];
14
+ delete process.env.MUONROI_SPRINT_SELF_VERIFY;
15
+ delete process.env.CI;
16
+ delete process.env.NODE_ENV;
17
17
  }
18
18
  function restoreEnv() {
19
19
  if (ORIG_CI === undefined)
20
- delete process.env["CI"];
20
+ delete process.env.CI;
21
21
  else
22
- process.env["CI"] = ORIG_CI;
22
+ process.env.CI = ORIG_CI;
23
23
  if (ORIG_NODE_ENV === undefined)
24
- delete process.env["NODE_ENV"];
24
+ delete process.env.NODE_ENV;
25
25
  else
26
- process.env["NODE_ENV"] = ORIG_NODE_ENV;
26
+ process.env.NODE_ENV = ORIG_NODE_ENV;
27
27
  }
28
28
  it("default ON when no env flag set — fires past the gate", async () => {
29
29
  clearEnv();
@@ -34,7 +34,7 @@ describe("runSprintSelfVerify", () => {
34
34
  });
35
35
  it("returns skipReason='disabled' when MUONROI_SPRINT_SELF_VERIFY=0", async () => {
36
36
  clearEnv();
37
- process.env["MUONROI_SPRINT_SELF_VERIFY"] = "0";
37
+ process.env.MUONROI_SPRINT_SELF_VERIFY = "0";
38
38
  const r = await runSprintSelfVerify({ repoRoot: process.cwd() });
39
39
  expect(r.ran).toBe(false);
40
40
  expect(r.skipReason).toBe("disabled");
@@ -42,7 +42,7 @@ describe("runSprintSelfVerify", () => {
42
42
  });
43
43
  it("returns skipReason='disabled' when CI=true", async () => {
44
44
  clearEnv();
45
- process.env["CI"] = "true";
45
+ process.env.CI = "true";
46
46
  const r = await runSprintSelfVerify({ repoRoot: process.cwd() });
47
47
  expect(r.ran).toBe(false);
48
48
  expect(r.skipReason).toBe("disabled");
@@ -60,7 +60,7 @@ describe("runSprintSelfVerify", () => {
60
60
  });
61
61
  it("forceEnable overrides CI=true", async () => {
62
62
  clearEnv();
63
- process.env["CI"] = "true";
63
+ process.env.CI = "true";
64
64
  const r = await runSprintSelfVerify({
65
65
  repoRoot: process.cwd(),
66
66
  baseRef: "HEAD",
@@ -14,6 +14,16 @@ export type UserPromptResult = {
14
14
  action: "proceed";
15
15
  } | {
16
16
  action: "ask-more";
17
+ }
18
+ /**
19
+ * G1 follow-up: from the `__user_gate__` summary card the user picked one
20
+ * auto-filled assumption to revise. The interview re-asks that single field
21
+ * (reusing the per-question card) and re-shows the gate — instead of forcing
22
+ * an abort-and-reprompt or MUONROI_DISCOVERY_AUTOFILL=0 to change one value.
23
+ */
24
+ | {
25
+ action: "edit-field";
26
+ fieldId: string;
17
27
  } | {
18
28
  action: "abort";
19
29
  };
@@ -22,6 +32,16 @@ export interface UserPromptArgs {
22
32
  recommendation?: RecommendOutput;
23
33
  prefilled?: any;
24
34
  message?: string;
35
+ /**
36
+ * G2-b: when the interview auto-filled required questions from the
37
+ * recommender (minimal/well-specified prompt), the `__user_gate__` card
38
+ * carries the assumed answers so it can render ONE summary ("I assumed X/Y/Z
39
+ * — proceed or adjust?") instead of N sequential per-question cards.
40
+ */
41
+ assumptions?: Array<{
42
+ id: string;
43
+ value: any;
44
+ }>;
25
45
  }
26
46
  export type UserPromptFn = (args: UserPromptArgs) => Promise<UserPromptResult>;
27
47
  export interface RecommenderLike {
@@ -21,6 +21,21 @@ export async function iterateInterview(opts) {
21
21
  // not web) are deferred unless the user explicitly re-runs with more context.
22
22
  const specificity = computePromptSpecificity(opts.idea);
23
23
  const skipOptionalForMinimal = specificity === "minimal";
24
+ // G2-b: for a minimal OR well-specified ("detailed") prompt the recommender's
25
+ // primary is high-confidence — minimal picks smallest-scope defaults, detailed
26
+ // respects the stated context. In both cases surfacing a card for EVERY
27
+ // required question (productType/targetPlatform/audience/…) is over-asking the
28
+ // user already complained about: they accept by reflex. Instead auto-accept
29
+ // the recommender primary for required questions and surface ONE summary card
30
+ // (the user gate) listing the assumptions so the user can proceed or adjust.
31
+ // "moderate" prompts keep the per-question cards (genuinely ambiguous). Escape
32
+ // hatch: MUONROI_DISCOVERY_AUTOFILL=0 restores per-question cards everywhere.
33
+ const autoFillRequired = (specificity === "minimal" || specificity === "detailed") && process.env.MUONROI_DISCOVERY_AUTOFILL !== "0";
34
+ const assumed = [];
35
+ // G1 follow-up: keep the recommendation behind each auto-filled assumption so
36
+ // the user-gate "edit: <field>" path can re-render the SAME per-question card
37
+ // (Recommended + alternatives) without a second recommender LLM call.
38
+ const assumedRec = new Map();
24
39
  // Build the repo brief ONCE per interview run for existing projects. The
25
40
  // brief replaces the Muonroi vendor preamble inside leader prompts so
26
41
  // rationales must cite real files, deps, and scripts. Skipped for greenfield —
@@ -74,12 +89,33 @@ export async function iterateInterview(opts) {
74
89
  else {
75
90
  recommendation = await opts.recommender.leaderRecommend(recInput);
76
91
  }
92
+ // G2-b: auto-accept the recommender primary for required questions on a
93
+ // minimal/well-specified prompt — no per-question card. Validated (+ FE
94
+ // policy) so a malformed recommendation falls back to the normal card flow.
95
+ // The assumed answers are surfaced together on the single user-gate card.
96
+ let autoAccepted = false;
97
+ if (autoFillRequired && effectivelyRequired && recommendation.primary?.value != null) {
98
+ const v = recommendation.primary.value;
99
+ const validation = validateAnswer(question.id, v);
100
+ const feLib = question.id === "frontendApproach" ? v?.library : undefined;
101
+ const fePolicyOk = !feLib || isFePolicyAccepted(feLib);
102
+ if (validation.ok && fePolicyOk) {
103
+ await recordRecommendation(flowDir, runId, question.id, toEntry(recommendation), recommendation.costUsd);
104
+ await saveDiscoveryAnswer(flowDir, runId, question.id, v);
105
+ assumed.push({ id: question.id, value: v });
106
+ assumedRec.set(question.id, recommendation);
107
+ autoAccepted = true;
108
+ // Fall through to the post-answer user-gate check below (skip the card).
109
+ }
110
+ }
77
111
  // Per-question skip-attempt counter for required questions.
78
112
  // Resets when the user provides a real answer (accept/override).
79
113
  let skipAttempts = 0;
80
114
  const MAX_SKIP_ATTEMPTS = 3;
81
115
  const _debugInterview = process.env.MUONROI_DEBUG_LEADER === "1";
82
- for (;;) {
116
+ // When auto-accepted above, the card loop body is skipped entirely (the
117
+ // for-condition is false on entry) and we go straight to the gate check.
118
+ for (; !autoAccepted;) {
83
119
  const _iterStart = Date.now();
84
120
  if (_debugInterview) {
85
121
  process.stderr.write(`[interview-timing] iter-start: ${JSON.stringify({ questionId: question.id, skipAttempts })}\n`);
@@ -176,14 +212,32 @@ export async function iterateInterview(opts) {
176
212
  if (refreshed &&
177
213
  allRequiredAnswered(refreshed.questionsAnswered, refreshedPlatforms) &&
178
214
  !refreshed.userGatePassed) {
179
- const gate = await opts.userPrompt({ questionId: "__user_gate__" });
180
- if (gate.action === "proceed") {
181
- await markUserGatePassed(flowDir, runId);
215
+ // Gate loop: "proceed" exits the whole interview, "abort" throws,
216
+ // "edit-field" re-asks one assumed answer then RE-shows the gate (so the
217
+ // user can revise several before proceeding), "ask-more" falls through to
218
+ // keep iterating optional questions.
219
+ let proceeded = false;
220
+ for (;;) {
221
+ const gate = await opts.userPrompt({
222
+ questionId: "__user_gate__",
223
+ assumptions: assumed.length > 0 ? assumed : undefined,
224
+ });
225
+ if (gate.action === "proceed") {
226
+ await markUserGatePassed(flowDir, runId);
227
+ proceeded = true;
228
+ break;
229
+ }
230
+ if (gate.action === "abort")
231
+ throw new Error("discovery aborted at user gate");
232
+ if (gate.action === "edit-field") {
233
+ await reAskAssumedField(opts, flowDir, runId, gate.fieldId, assumed, assumedRec.get(gate.fieldId));
234
+ continue; // re-render the gate with the updated assumption
235
+ }
236
+ // ask-more: stop gating, continue iterating optional questions
182
237
  break;
183
238
  }
184
- if (gate.action === "abort")
185
- throw new Error("discovery aborted at user gate");
186
- // ask-more: continue iterating optional questions
239
+ if (proceeded)
240
+ break;
187
241
  }
188
242
  }
189
243
  const finalState = await readDiscoveryState(flowDir, runId);
@@ -197,6 +251,66 @@ export async function iterateInterview(opts) {
197
251
  await markDone(flowDir, runId);
198
252
  return ctx;
199
253
  }
254
+ /**
255
+ * G1 follow-up: re-ask ONE auto-filled assumption from the user gate. Renders
256
+ * the same per-question card (Recommended + alternatives) as the normal flow by
257
+ * reusing the stored recommendation, validates (+ FE policy), persists the new
258
+ * answer, and updates the in-memory `assumed` list so the re-rendered gate shows
259
+ * the revised value. "skip" cancels the edit (keeps the current assumption);
260
+ * "abort" aborts the whole interview, consistent with the per-question card.
261
+ */
262
+ async function reAskAssumedField(opts, flowDir, runId, fieldId, assumed, recommendation) {
263
+ const question = DISCOVERY_QUESTIONS.find((q) => q.id === fieldId);
264
+ if (!question)
265
+ return; // unknown field id — ignore defensively, keep current value
266
+ for (;;) {
267
+ const ans = await opts.userPrompt({ questionId: fieldId, recommendation });
268
+ if (ans.action === "abort")
269
+ throw new Error("discovery aborted by user");
270
+ let chosenValue;
271
+ if (ans.action === "accept") {
272
+ if (recommendation?.primary?.value == null)
273
+ continue; // nothing to accept — re-prompt
274
+ chosenValue = recommendation.primary.value;
275
+ }
276
+ else if (ans.action === "override") {
277
+ chosenValue = ans.value;
278
+ }
279
+ else if (ans.action === "skip") {
280
+ return; // user backed out of editing this field — keep the existing assumption
281
+ }
282
+ else {
283
+ continue; // more-options / edit-field / proceed / ask-more are no-ops here
284
+ }
285
+ const validation = validateAnswer(fieldId, chosenValue);
286
+ if (!validation.ok) {
287
+ if (fieldId !== "frontendApproach") {
288
+ await opts.userPrompt({ questionId: fieldId, message: validation.reason ?? "invalid answer" });
289
+ }
290
+ continue;
291
+ }
292
+ if (fieldId === "frontendApproach") {
293
+ const lib = chosenValue?.library;
294
+ if (lib && !isFePolicyAccepted(lib)) {
295
+ await opts.userPrompt({
296
+ questionId: fieldId,
297
+ message: "FE policy: library must be shadcn, radix, headlessui, or none",
298
+ });
299
+ continue;
300
+ }
301
+ }
302
+ if (ans.action === "override" && recommendation) {
303
+ await appendUserOverride(flowDir, runId, fieldId, recommendation.primary.value, chosenValue, ans.reason);
304
+ }
305
+ await saveDiscoveryAnswer(flowDir, runId, fieldId, chosenValue);
306
+ const entry = assumed.find((a) => a.id === fieldId);
307
+ if (entry)
308
+ entry.value = chosenValue;
309
+ else
310
+ assumed.push({ id: fieldId, value: chosenValue });
311
+ return;
312
+ }
313
+ }
200
314
  function allRequiredAnswered(answered, platforms) {
201
315
  const baseRequired = REQUIRED_QUESTION_IDS.every((id) => answered.includes(id));
202
316
  if (!baseRequired)
@@ -93,15 +93,29 @@ function buildLiveTuiAsk(emit, respondToQuestion) {
93
93
  export function buildGatherUserPrompt(tuiAsk) {
94
94
  return async (args) => {
95
95
  if (args.questionId === "__user_gate__") {
96
- const choice = await tuiAsk("All required questions answered. Proceed to research or ask more?", [
97
- "proceed",
98
- "ask-more",
99
- "abort",
100
- ]);
96
+ // G2-b: when required answers were auto-filled from the recommender
97
+ // (minimal/well-specified prompt), this single card IS the summary — list
98
+ // the assumptions so the user reviews them in one place instead of N
99
+ // sequential per-question cards. "ask-more" lets them revisit/expand.
100
+ const hasAssumptions = !!args.assumptions && args.assumptions.length > 0;
101
+ const label = hasAssumptions
102
+ ? `Assumed from your prompt:\n${args
103
+ .assumptions.map((a) => ` • ${a.id} = ${JSON.stringify(a.value)}`)
104
+ .join("\n")}\n\nProceed, edit one of the assumptions, or ask more to adjust/expand?`
105
+ : "All required questions answered. Proceed to research or ask more?";
106
+ // G1 follow-up: one "edit: <field>" option per auto-filled assumption so a
107
+ // single wrong default can be revised in place — no abort+reprompt or
108
+ // MUONROI_DISCOVERY_AUTOFILL=0. The prefix must match the parse below.
109
+ const EDIT_PREFIX = "edit: ";
110
+ const editOpts = hasAssumptions ? args.assumptions.map((a) => `${EDIT_PREFIX}${a.id}`) : [];
111
+ const choice = await tuiAsk(label, ["proceed", ...editOpts, "ask-more", "abort"]);
101
112
  if (choice === "proceed")
102
113
  return { action: "proceed" };
103
114
  if (choice === "abort")
104
115
  return { action: "abort" };
116
+ if (choice.startsWith(EDIT_PREFIX)) {
117
+ return { action: "edit-field", fieldId: choice.slice(EDIT_PREFIX.length) };
118
+ }
105
119
  return { action: "ask-more" };
106
120
  }
107
121
  if (args.message) {
@@ -39,10 +39,10 @@ function wrapLLMForUsageTracking(llm, ctx) {
39
39
  }
40
40
  };
41
41
  return {
42
- generate: (modelId, system, prompt, maxTokens, onUsage) => llm.generate(modelId, system, prompt, maxTokens, (u) => {
42
+ generate: (modelId, system, prompt, maxTokens, onUsage, signal) => llm.generate(modelId, system, prompt, maxTokens, (u) => {
43
43
  recorder(modelId)(u);
44
44
  onUsage?.(u);
45
- }),
45
+ }, signal),
46
46
  debate: (modelId, system, prompt, signal, persistTrace, options, onUsage) => llm.debate(modelId, system, prompt, signal, persistTrace, options, (u) => {
47
47
  recorder(modelId)(u);
48
48
  onUsage?.(u);
@@ -31,10 +31,10 @@ const WATCH_DIRS = ["src/ui/", "src/self-qa/", "src/agent-harness/", "packages/a
31
31
  function isEnabled(forceEnable) {
32
32
  if (forceEnable === true)
33
33
  return true;
34
- const v = process.env["MUONROI_SPRINT_SELF_VERIFY"];
34
+ const v = process.env.MUONROI_SPRINT_SELF_VERIFY;
35
35
  if (v === "0" || (v && v.toLowerCase() === "false"))
36
36
  return false;
37
- if (process.env["CI"] === "true" || process.env["NODE_ENV"] === "ci")
37
+ if (process.env.CI === "true" || process.env.NODE_ENV === "ci")
38
38
  return false;
39
39
  // Default: ON. v === "1"/"true" still works; absence of env var also enables.
40
40
  return true;
@@ -179,6 +179,18 @@ describe("transformThinkingModeBody — backfill / disable (code 20015 fix)", ()
179
179
  expect(asst[0].reasoning_content).toBe("real thought"); // untouched
180
180
  expect(asst[1].reasoning_content).toBe(""); // backfilled
181
181
  });
182
+ it("A (default): backfills content: '' on a reasoning-only turn that lacks content and tool_calls", () => {
183
+ const body = {
184
+ messages: [
185
+ { role: "user", content: "go" },
186
+ { role: "assistant", content: null, reasoning_content: "only thought" },
187
+ ],
188
+ };
189
+ const out = transformThinkingModeBody(body);
190
+ const asst = out.messages.find((m) => m.role === "assistant");
191
+ expect(asst.reasoning_content).toBe("only thought");
192
+ expect(asst.content).toBe("");
193
+ });
182
194
  it("A (default): does not touch non-assistant messages", () => {
183
195
  const body = {
184
196
  messages: [
@@ -1,8 +1,4 @@
1
1
  /**
2
- * Tests for src/providers/auth/gemini-oauth.ts
3
- *
4
- * Login flow (callback server mocked), refresh (expiry pre-emption + mutex),
5
- * revoke, authHeaders, loadGeminiTokensWithRefresh.
6
- * No live network calls — all HTTP via mockFetch, no real browser opens.
2
+ * Tests for Agy Google OAuth (replaces legacy gemini public client)
7
3
  */
8
4
  export {};
@@ -1,9 +1,5 @@
1
1
  /**
2
- * Tests for src/providers/auth/gemini-oauth.ts
3
- *
4
- * Login flow (callback server mocked), refresh (expiry pre-emption + mutex),
5
- * revoke, authHeaders, loadGeminiTokensWithRefresh.
6
- * No live network calls — all HTTP via mockFetch, no real browser opens.
2
+ * Tests for Agy Google OAuth (replaces legacy gemini public client)
7
3
  */
8
4
  import { describe, expect, it, vi } from "vitest";
9
5
  import { GeminiOAuthProvider, loadGeminiTokensWithRefresh } from "../gemini-oauth.js";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * src/providers/auth/gcloud.ts
3
+ *
4
+ * Reads and refreshes tokens from Google Cloud SDK credentials
5
+ * (~/.config/gcloud/application_default_credentials.json).
6
+ *
7
+ * The gcloud ADC token uses GCP's official OAuth client with cloud-platform
8
+ * scopes, which IS accepted by generativelanguage.googleapis.com (unlike the
9
+ * Agy OAuth token whose scope is restricted to cloudcode-pa).
10
+ *
11
+ * Usage:
12
+ * 1. User runs `gcloud auth application-default login` once.
13
+ * 2. This file reads the resulting credentials file, extracts the token,
14
+ * and returns it as an OAuthTokens-compatible object.
15
+ * 3. The token auto-refreshes when expired (using the refresh_token inside
16
+ * the credentials file).
17
+ */
18
+ import type { OAuthTokens } from "./types.js";
19
+ /**
20
+ * Try to load a token from gcloud ADC.
21
+ * Returns null if gcloud is not installed or no credentials exist.
22
+ */
23
+ export declare function loadGcloudToken(): Promise<OAuthTokens | null>;
24
+ /**
25
+ * Obtain auth headers from a gcloud-derived OAuthTokens.
26
+ * Compatible with ProviderOAuth.authHeaders() signature.
27
+ */
28
+ export declare function gcloudAuthHeaders(tokens: OAuthTokens): Record<string, string>;