muonroi-cli 1.7.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +122 -122
  3. package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
  4. package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
  5. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
  6. package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
  7. package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
  8. package/dist/packages/agent-harness-core/src/selector.js +2 -2
  9. package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
  10. package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
  11. package/dist/packages/agent-harness-opentui/src/install.js +1 -1
  12. package/dist/src/__test-stubs__/vitest-setup.js +4 -0
  13. package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
  14. package/dist/src/agent-harness/mock-model.d.ts +1 -1
  15. package/dist/src/agent-harness/mock-model.js +32 -3
  16. package/dist/src/agent-harness/test-spawn.js +3 -1
  17. package/dist/src/chat/chat-keychain.js +9 -1
  18. package/dist/src/cli/cost-forensics.js +12 -12
  19. package/dist/src/cli/keys.d.ts +2 -2
  20. package/dist/src/cli/keys.js +107 -28
  21. package/dist/src/cli/usage-report.js +2 -2
  22. package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
  23. package/dist/src/council/__tests__/abort-threading.test.js +193 -0
  24. package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
  25. package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
  26. package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
  27. package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
  28. package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
  29. package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
  30. package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
  31. package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
  32. package/dist/src/council/__tests__/research-tools.test.js +4 -0
  33. package/dist/src/council/__tests__/round-tools.test.js +6 -0
  34. package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
  35. package/dist/src/council/clarifier-question-cap.test.js +73 -0
  36. package/dist/src/council/clarifier.d.ts +29 -1
  37. package/dist/src/council/clarifier.js +64 -1
  38. package/dist/src/council/debate-planner.d.ts +2 -1
  39. package/dist/src/council/debate-planner.js +5 -3
  40. package/dist/src/council/debate.d.ts +9 -0
  41. package/dist/src/council/debate.js +88 -8
  42. package/dist/src/council/index.d.ts +46 -1
  43. package/dist/src/council/index.js +125 -19
  44. package/dist/src/council/llm.js +41 -13
  45. package/dist/src/council/prompts.js +56 -21
  46. package/dist/src/council/types.d.ts +14 -1
  47. package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
  48. package/dist/src/ee/bridge.d.ts +3 -1
  49. package/dist/src/ee/bridge.js +9 -3
  50. package/dist/src/ee/bridge.test.js +1 -2
  51. package/dist/src/ee/client.js +15 -2
  52. package/dist/src/ee/recall-ledger.d.ts +20 -0
  53. package/dist/src/ee/recall-ledger.js +40 -1
  54. package/dist/src/ee/search.d.ts +25 -0
  55. package/dist/src/ee/search.js +73 -0
  56. package/dist/src/ee/types.d.ts +15 -1
  57. package/dist/src/ee/who-am-i.d.ts +41 -0
  58. package/dist/src/ee/who-am-i.js +143 -0
  59. package/dist/src/ee/who-am-i.test.d.ts +1 -0
  60. package/dist/src/ee/who-am-i.test.js +101 -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 +143 -104
  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 +2 -2
  76. package/dist/src/lsp/manager.test.js +2 -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/client-pool.d.ts +1 -1
  86. package/dist/src/mcp/client-pool.js +25 -9
  87. package/dist/src/mcp/ee-tools.d.ts +10 -0
  88. package/dist/src/mcp/ee-tools.js +34 -0
  89. package/dist/src/mcp/oauth-callback.js +2 -2
  90. package/dist/src/mcp/parse-headers.test.js +14 -14
  91. package/dist/src/mcp/self-verify-jobs.js +1 -3
  92. package/dist/src/mcp/setup-guide-text.js +74 -74
  93. package/dist/src/mcp/smoke.test.js +43 -43
  94. package/dist/src/models/catalog-gemini.test.js +12 -10
  95. package/dist/src/models/catalog.json +106 -38
  96. package/dist/src/ops/doctor.js +7 -7
  97. package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
  98. package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
  99. package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +2 -0
  100. package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
  101. package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
  102. package/dist/src/orchestrator/__tests__/message-processor.test.js +1 -0
  103. package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
  104. package/dist/src/orchestrator/agent.test.js +5 -2
  105. package/dist/src/orchestrator/auto-commit.d.ts +110 -0
  106. package/dist/src/orchestrator/auto-commit.js +364 -0
  107. package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
  108. package/dist/src/orchestrator/batch-turn-runner.js +2 -1
  109. package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
  110. package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
  111. package/dist/src/orchestrator/compaction.d.ts +22 -0
  112. package/dist/src/orchestrator/compaction.js +42 -0
  113. package/dist/src/orchestrator/delegations.d.ts +1 -0
  114. package/dist/src/orchestrator/delegations.js +41 -0
  115. package/dist/src/orchestrator/error-utils.js +27 -2
  116. package/dist/src/orchestrator/message-processor.d.ts +21 -5
  117. package/dist/src/orchestrator/message-processor.js +219 -2573
  118. package/dist/src/orchestrator/orchestrator.d.ts +8 -2
  119. package/dist/src/orchestrator/orchestrator.js +187 -34
  120. package/dist/src/orchestrator/preprocessor.d.ts +13 -0
  121. package/dist/src/orchestrator/preprocessor.js +145 -0
  122. package/dist/src/orchestrator/prompts.d.ts +8 -0
  123. package/dist/src/orchestrator/prompts.js +76 -60
  124. package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
  125. package/dist/src/orchestrator/safety-askcard.js +74 -0
  126. package/dist/src/orchestrator/sandbox.test.js +8 -5
  127. package/dist/src/orchestrator/scope-ceiling.js +1 -1
  128. package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
  129. package/dist/src/orchestrator/stall-watchdog.js +23 -0
  130. package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
  131. package/dist/src/orchestrator/stream-runner.js +52 -30
  132. package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
  133. package/dist/src/orchestrator/subagent-compactor.d.ts +14 -0
  134. package/dist/src/orchestrator/subagent-compactor.js +83 -10
  135. package/dist/src/orchestrator/subagent-compactor.spec.js +132 -0
  136. package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
  137. package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
  138. package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
  139. package/dist/src/orchestrator/tool-args-repair.js +1 -1
  140. package/dist/src/orchestrator/tool-engine.d.ts +151 -0
  141. package/dist/src/orchestrator/tool-engine.js +3079 -0
  142. package/dist/src/orchestrator/turn-runner-deps.d.ts +1 -0
  143. package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
  144. package/dist/src/pil/__tests__/discovery.test.js +89 -145
  145. package/dist/src/pil/__tests__/dual-run.test.js +2 -0
  146. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
  147. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  148. package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
  149. package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
  150. package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
  151. package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
  152. package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
  153. package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
  154. package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
  155. package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
  156. package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
  157. package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
  158. package/dist/src/pil/__tests__/pipeline.test.js +11 -8
  159. package/dist/src/pil/__tests__/response-tools.test.js +52 -0
  160. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  161. package/dist/src/pil/agent-operating-contract.js +5 -3
  162. package/dist/src/pil/agent-operating-contract.test.js +2 -2
  163. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  164. package/dist/src/pil/cheap-model-playbook.js +1 -1
  165. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  166. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  167. package/dist/src/pil/config.d.ts +20 -0
  168. package/dist/src/pil/config.js +35 -0
  169. package/dist/src/pil/discovery-types.d.ts +32 -9
  170. package/dist/src/pil/discovery.d.ts +3 -3
  171. package/dist/src/pil/discovery.js +138 -244
  172. package/dist/src/pil/index.d.ts +1 -1
  173. package/dist/src/pil/index.js +1 -1
  174. package/dist/src/pil/layer1-intent.d.ts +9 -1
  175. package/dist/src/pil/layer1-intent.js +99 -44
  176. package/dist/src/pil/layer1-intent.test.js +112 -2
  177. package/dist/src/pil/layer15-context-scan.js +1 -1
  178. package/dist/src/pil/layer16-clarity.d.ts +21 -25
  179. package/dist/src/pil/layer16-clarity.js +52 -88
  180. package/dist/src/pil/layer18-acceptance.d.ts +13 -3
  181. package/dist/src/pil/layer18-acceptance.js +1 -75
  182. package/dist/src/pil/layer2-personality.js +7 -8
  183. package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
  184. package/dist/src/pil/layer2_5-ponytail.js +41 -0
  185. package/dist/src/pil/layer3-ee-injection.js +180 -14
  186. package/dist/src/pil/layer4-gsd.js +22 -8
  187. package/dist/src/pil/layer5-context.d.ts +1 -0
  188. package/dist/src/pil/layer5-context.js +26 -11
  189. package/dist/src/pil/layer6-output.js +23 -5
  190. package/dist/src/pil/llm-classify.js +3 -3
  191. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  192. package/dist/src/pil/native-capabilities-workbook.js +4 -3
  193. package/dist/src/pil/pipeline.js +15 -1
  194. package/dist/src/pil/response-tools.d.ts +16 -0
  195. package/dist/src/pil/response-tools.js +41 -0
  196. package/dist/src/pil/schema.d.ts +8 -0
  197. package/dist/src/pil/schema.js +24 -3
  198. package/dist/src/pil/task-tier-map.js +7 -7
  199. package/dist/src/pil/types.d.ts +9 -3
  200. package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
  201. package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
  202. package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
  203. package/dist/src/product-loop/discovery-interview.d.ts +20 -0
  204. package/dist/src/product-loop/discovery-interview.js +121 -7
  205. package/dist/src/product-loop/done-gate.js +3 -3
  206. package/dist/src/product-loop/gather.js +19 -5
  207. package/dist/src/product-loop/loop-driver.js +20 -20
  208. package/dist/src/product-loop/progress-snapshot.js +4 -4
  209. package/dist/src/product-loop/sprint-self-verify.js +2 -2
  210. package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
  211. package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
  212. package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
  213. package/dist/src/providers/auth/gcloud.d.ts +28 -0
  214. package/dist/src/providers/auth/gcloud.js +102 -0
  215. package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
  216. package/dist/src/providers/auth/gemini-oauth.js +292 -105
  217. package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
  218. package/dist/src/providers/auth/grok-oauth.js +1 -1
  219. package/dist/src/providers/auth/registry.js +28 -6
  220. package/dist/src/providers/gemini.d.ts +1 -1
  221. package/dist/src/providers/gemini.js +10 -3
  222. package/dist/src/providers/keychain.d.ts +2 -2
  223. package/dist/src/providers/keychain.js +20 -2
  224. package/dist/src/providers/mcp-vision-bridge.js +48 -48
  225. package/dist/src/providers/openai.js +5 -1
  226. package/dist/src/providers/runtime.d.ts +1 -1
  227. package/dist/src/providers/runtime.js +11 -2
  228. package/dist/src/providers/siliconflow-sse-repair.js +1 -1
  229. package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
  230. package/dist/src/providers/strategies/google.strategy.js +159 -9
  231. package/dist/src/providers/strategies/thinking-mode.js +19 -6
  232. package/dist/src/providers/types.d.ts +1 -1
  233. package/dist/src/reporter/index.js +1 -1
  234. package/dist/src/router/decide.test.js +40 -8
  235. package/dist/src/router/step-router.d.ts +20 -0
  236. package/dist/src/router/step-router.js +73 -0
  237. package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
  238. package/dist/src/scaffold/bb-quality-gate.js +5 -5
  239. package/dist/src/scaffold/continuation-prompt.js +60 -60
  240. package/dist/src/scaffold/init-new.js +453 -453
  241. package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
  242. package/dist/src/self-qa/agentic-loop.js +19 -19
  243. package/dist/src/self-qa/orchestrator.js +5 -5
  244. package/dist/src/self-qa/scenario-planner.js +1 -1
  245. package/dist/src/self-qa/spec-emitter.js +4 -4
  246. package/dist/src/storage/__tests__/migrations.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/interaction-log.js +5 -5
  250. package/dist/src/storage/migrations.js +122 -122
  251. package/dist/src/storage/session-experience-store.js +4 -4
  252. package/dist/src/storage/sessions.d.ts +6 -0
  253. package/dist/src/storage/sessions.js +49 -42
  254. package/dist/src/storage/transcript-view.js +7 -1
  255. package/dist/src/storage/transcript.d.ts +1 -0
  256. package/dist/src/storage/transcript.js +55 -0
  257. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  258. package/dist/src/storage/usage.js +14 -14
  259. package/dist/src/storage/workspaces.js +12 -12
  260. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  261. package/dist/src/tools/bash.d.ts +36 -23
  262. package/dist/src/tools/bash.js +59 -301
  263. package/dist/src/tools/bash.test.js +2 -257
  264. package/dist/src/tools/file-tracker.d.ts +5 -0
  265. package/dist/src/tools/file-tracker.js +14 -0
  266. package/dist/src/tools/file.js +2 -0
  267. package/dist/src/tools/git-safety.d.ts +38 -1
  268. package/dist/src/tools/git-safety.js +94 -4
  269. package/dist/src/tools/git-safety.test.js +45 -1
  270. package/dist/src/tools/native-tools.d.ts +1 -1
  271. package/dist/src/tools/native-tools.js +56 -0
  272. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  273. package/dist/src/tools/registry-git-safety.test.js +17 -6
  274. package/dist/src/tools/registry.d.ts +5 -0
  275. package/dist/src/tools/registry.js +185 -11
  276. package/dist/src/types/index.d.ts +7 -1
  277. package/dist/src/ui/app.d.ts +2 -1
  278. package/dist/src/ui/app.js +0 -0
  279. package/dist/src/ui/components/council-question-card.js +1 -0
  280. package/dist/src/ui/components/message-view.js +37 -3
  281. package/dist/src/ui/components/structured-response-view.js +7 -0
  282. package/dist/src/ui/components/task-list-panel.js +3 -12
  283. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  284. package/dist/src/ui/containers/chat-feed.js +66 -0
  285. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  286. package/dist/src/ui/containers/modals-layer.js +18 -0
  287. package/dist/src/ui/modals/session-picker-modal.js +14 -9
  288. package/dist/src/ui/modals/update-modal.js +2 -1
  289. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  290. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  291. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  292. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  293. package/dist/src/ui/slash/council-inspect.js +4 -4
  294. package/dist/src/ui/slash/export.js +16 -1
  295. package/dist/src/ui/slash/menu-items.js +1 -0
  296. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  297. package/dist/src/ui/slash/ponytail.js +19 -0
  298. package/dist/src/ui/status-bar/store.js +0 -1
  299. package/dist/src/ui/status-bar/store.test.js +2 -2
  300. package/dist/src/ui/types.d.ts +7 -0
  301. package/dist/src/ui/use-app-logic.d.ts +26 -0
  302. package/dist/src/ui/use-app-logic.js +0 -0
  303. package/dist/src/ui/utils/relaunch.d.ts +17 -0
  304. package/dist/src/ui/utils/relaunch.js +28 -4
  305. package/dist/src/ui/utils/relaunch.test.js +50 -0
  306. package/dist/src/ui/utils/text.d.ts +11 -0
  307. package/dist/src/ui/utils/text.js +21 -1
  308. package/dist/src/ui/utils/text.test.js +25 -1
  309. package/dist/src/ui/utils/tools.js +3 -1
  310. package/dist/src/usage/cost-log.js +2 -2
  311. package/dist/src/usage/estimator.d.ts +14 -0
  312. package/dist/src/usage/estimator.js +22 -0
  313. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  314. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  315. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  316. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  317. package/dist/src/utils/clipboard-image.js +23 -23
  318. package/dist/src/utils/llm-deadline.d.ts +9 -1
  319. package/dist/src/utils/llm-deadline.js +31 -2
  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 +27 -31
  324. package/dist/src/utils/settings.js +100 -109
  325. package/dist/src/utils/side-question.js +2 -2
  326. package/dist/src/utils/skills.js +3 -3
  327. package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
  328. package/package.json +1 -1
@@ -35,6 +35,7 @@ export interface TurnRunnerDepsBase {
35
35
  runDelegation(request: TaskRequest, signal?: AbortSignal): Promise<ToolResult>;
36
36
  readDelegation(id: string): Promise<ToolResult>;
37
37
  listDelegations(): Promise<ToolResult>;
38
+ killDelegation(id: string): Promise<ToolResult>;
38
39
  appendCompletedTurn(userMessage: ModelMessage, assistantMessages: ModelMessage[]): void;
39
40
  discardAbortedTurn(userMessage: ModelMessage): void;
40
41
  recordUsage(usage: {
@@ -16,11 +16,20 @@ describe("discovery-types", () => {
16
16
  };
17
17
  expect(ctx.language).toBe("typescript");
18
18
  });
19
- it("ClarityDimension union covers all 3 values", () => {
20
- const dims = ["outcome", "scope", "constraint"];
21
- expect(dims).toHaveLength(3);
19
+ it("ModelCard has all required fields", () => {
20
+ const card = {
21
+ question: "What auth method?",
22
+ options: [
23
+ { label: "OAuth", kind: "choice", isCancel: false, isAdjust: false },
24
+ { label: "API keys", kind: "choice" },
25
+ { label: "Custom", kind: "freetext" },
26
+ ],
27
+ defaultIndex: 0,
28
+ };
29
+ expect(card.question).toBe("What auth method?");
30
+ expect(card.options).toHaveLength(3);
22
31
  });
23
- it("DiscoveryResult has all required fields", () => {
32
+ it("DiscoveryResult has all required fields including interviewTranscript", () => {
24
33
  const result = {
25
34
  raw: "fix auth",
26
35
  projectContext: {
@@ -47,8 +56,10 @@ describe("discovery-types", () => {
47
56
  domain: "typescript",
48
57
  outputStyle: "balanced",
49
58
  discoveryMs: 100,
59
+ interviewTranscript: [],
50
60
  };
51
61
  expect(result.accepted).toBe(true);
62
+ expect(result.interviewTranscript).toEqual([]);
52
63
  });
53
64
  });
54
65
  //# sourceMappingURL=discovery-types.test.js.map
@@ -7,15 +7,11 @@ import { clearDiscoveryCache } from "../discovery-cache.js";
7
7
  afterEach(() => clearDiscoveryCache());
8
8
  const mockHandler = {
9
9
  askQuestion: vi.fn().mockResolvedValue({ questionId: "q1", text: "Error disappears", kind: "choice" }),
10
- showAcceptance: vi.fn().mockResolvedValue("accept"),
11
10
  };
12
11
  describe("runDiscovery()", () => {
13
- it("proceeds without interview when the model proposes no questions", async () => {
14
- // Phase 2: the model is the sole ask-decider. An empty proposer result means
15
- // "no gray area" → no interview, no fabricated [Discovery] outcome.
12
+ it("proceeds without interview when the model proposes no cards", async () => {
16
13
  const handler = {
17
14
  askQuestion: vi.fn().mockResolvedValue({ questionId: "q1", text: "x", kind: "choice" }),
18
- showAcceptance: vi.fn().mockResolvedValue("accept"),
19
15
  };
20
16
  const proposer = vi.fn().mockResolvedValue([]);
21
17
  const result = await runDiscovery("fix TypeError in src/auth/login.ts:42", {
@@ -31,15 +27,11 @@ describe("runDiscovery()", () => {
31
27
  expect(result.accepted).toBe(true);
32
28
  expect(handler.askQuestion).not.toHaveBeenCalled();
33
29
  });
34
- it("does NOT interview (and never fabricates regex questions) when no proposer is wired", async () => {
35
- // Phase 2 fail-loud: an interactive turn missing a proposer logs and proceeds
36
- // WITHOUT an interview — it must never fall back to keyword-generated gaps.
30
+ it("does NOT interview when no proposer is wired", async () => {
37
31
  const handler = {
38
32
  askQuestion: vi.fn().mockResolvedValue({ questionId: "q1", text: "x", kind: "choice" }),
39
- showAcceptance: vi.fn().mockResolvedValue("accept"),
40
33
  };
41
- const result = await runDiscovery("fix auth", // vague — old regex gate would have asked a scope question
42
- {
34
+ const result = await runDiscovery("fix auth", {
43
35
  taskType: "debug",
44
36
  confidence: 0.6,
45
37
  complexity: "low",
@@ -51,16 +43,61 @@ describe("runDiscovery()", () => {
51
43
  expect(result.accepted).toBe(true);
52
44
  expect(handler.askQuestion).not.toHaveBeenCalled();
53
45
  });
54
- it("surfaces the model's reason + recommends in the interview askcard", async () => {
55
- const askQuestion = vi.fn().mockResolvedValue({ questionId: "q1", text: "OAuth", kind: "choice" });
56
- const handler = {
57
- askQuestion,
58
- showAcceptance: vi.fn().mockResolvedValue("accept"),
59
- };
60
- const proposer = vi
46
+ it("interviews user when the model proposes cards", async () => {
47
+ const askQuestion = vi.fn().mockResolvedValue({ questionId: "q1", text: "Error disappears", kind: "choice" });
48
+ const handler = { askQuestion };
49
+ const modelCards = [
50
+ {
51
+ question: "What outcome do you expect?",
52
+ options: [
53
+ { label: "Error disappears", kind: "choice" },
54
+ { label: "Not sure", kind: "freetext" },
55
+ ],
56
+ defaultIndex: 0,
57
+ },
58
+ ];
59
+ const proposer = vi.fn().mockResolvedValue(modelCards);
60
+ const result = await runDiscovery("fix auth", {
61
+ taskType: "debug",
62
+ confidence: 0.6,
63
+ complexity: "low",
64
+ domain: "typescript",
65
+ outputStyle: null,
66
+ intentKind: "task",
67
+ }, process.cwd(), handler, null, proposer);
68
+ expect(result.interviewed).toBe(true);
69
+ expect(result.accepted).toBe(true);
70
+ expect(handler.askQuestion).toHaveBeenCalled();
71
+ expect(result.interviewTranscript).toHaveLength(1);
72
+ expect(result.interviewTranscript[0].question).toBe("What outcome do you expect?");
73
+ expect(result.interviewTranscript[0].answer).toBe("Error disappears");
74
+ });
75
+ it("records multiple cards in the interview transcript", async () => {
76
+ const askQuestion = vi
61
77
  .fn()
62
- .mockResolvedValue(["Which auth method? [MODEL RECS: OAuth | API keys] [WHY: changes the whole token flow]"]);
63
- await runDiscovery("add authentication", {
78
+ .mockResolvedValueOnce({ questionId: "q1", text: "OAuth", kind: "choice" })
79
+ .mockResolvedValueOnce({ questionId: "q2", text: "src/auth/", kind: "choice" });
80
+ const handler = { askQuestion };
81
+ const modelCards = [
82
+ {
83
+ question: "Which auth method?",
84
+ options: [
85
+ { label: "OAuth", kind: "choice" },
86
+ { label: "API Keys", kind: "choice" },
87
+ ],
88
+ defaultIndex: 0,
89
+ },
90
+ {
91
+ question: "Which module?",
92
+ options: [
93
+ { label: "src/auth/", kind: "choice" },
94
+ { label: "src/user/", kind: "choice" },
95
+ ],
96
+ defaultIndex: 0,
97
+ },
98
+ ];
99
+ const proposer = vi.fn().mockResolvedValue(modelCards);
100
+ const result = await runDiscovery("add authentication", {
64
101
  taskType: "generate",
65
102
  confidence: 0.6,
66
103
  complexity: "low",
@@ -68,24 +105,45 @@ describe("runDiscovery()", () => {
68
105
  outputStyle: null,
69
106
  intentKind: "task",
70
107
  }, process.cwd(), handler, null, proposer);
71
- expect(askQuestion).toHaveBeenCalled();
72
- const card = askQuestion.mock.calls[0][0];
73
- // Model's WHY drives the askcard context; recommends drive the options.
74
- expect(card.context).toBe("changes the whole token flow");
75
- expect(card.question).toBe("Which auth method?");
76
- const labels = (card.options ?? []).map((o) => o.label);
77
- expect(labels).toContain("OAuth");
78
- expect(labels).toContain("API keys");
79
- expect(card.defaultIndex).toBe(0); // first recommend = recommended default
108
+ expect(result.interviewTranscript).toHaveLength(2);
109
+ expect(result.interviewTranscript[0].question).toBe("Which auth method?");
110
+ expect(result.interviewTranscript[0].answer).toBe("OAuth");
111
+ expect(result.interviewTranscript[1].question).toBe("Which module?");
112
+ expect(result.interviewTranscript[1].answer).toBe("src/auth/");
113
+ });
114
+ it("sets accepted=false when user picks a cancel option", async () => {
115
+ const askQuestion = vi
116
+ .fn()
117
+ .mockResolvedValueOnce({ questionId: "q1", text: "Cancel this request", kind: "choice" });
118
+ const handler = { askQuestion };
119
+ const modelCards = [
120
+ {
121
+ question: "What outcome?",
122
+ options: [
123
+ { label: "Proceed", kind: "choice" },
124
+ { label: "Cancel this request", kind: "choice", isCancel: true },
125
+ ],
126
+ defaultIndex: 0,
127
+ },
128
+ ];
129
+ const proposer = vi.fn().mockResolvedValue(modelCards);
130
+ const result = await runDiscovery("fix auth", {
131
+ taskType: "debug",
132
+ confidence: 0.6,
133
+ complexity: "low",
134
+ domain: "typescript",
135
+ outputStyle: null,
136
+ intentKind: "task",
137
+ }, process.cwd(), handler, null, proposer);
138
+ expect(result.accepted).toBe(false);
80
139
  });
81
140
  it("skips all discovery when the user explicitly says don't ask (EN + VI)", async () => {
82
141
  const handler = {
83
142
  askQuestion: vi.fn().mockResolvedValue({ questionId: "q1", text: "x", kind: "choice" }),
84
- showAcceptance: vi.fn().mockResolvedValue("accept"),
85
143
  };
86
144
  const l1 = {
87
145
  taskType: "analyze",
88
- confidence: 0.6, // low enough that discovery would normally interview
146
+ confidence: 0.6,
89
147
  complexity: "low",
90
148
  domain: null,
91
149
  outputStyle: null,
@@ -99,24 +157,6 @@ describe("runDiscovery()", () => {
99
157
  expect(viResult.accepted).toBe(true);
100
158
  expect(handler.askQuestion).not.toHaveBeenCalled();
101
159
  });
102
- it("interviews user when the model proposes a question", async () => {
103
- const handler = {
104
- askQuestion: vi.fn().mockResolvedValue({ questionId: "q1", text: "Error disappears", kind: "choice" }),
105
- showAcceptance: vi.fn().mockResolvedValue("accept"),
106
- };
107
- const proposer = vi.fn().mockResolvedValue(["What's the expected fix outcome? [MODEL RECS: Error disappears]"]);
108
- const result = await runDiscovery("fix auth", {
109
- taskType: "debug",
110
- confidence: 0.6,
111
- complexity: "low",
112
- domain: "typescript",
113
- outputStyle: null,
114
- intentKind: "task",
115
- }, process.cwd(), handler, null, proposer);
116
- expect(result.interviewed).toBe(true);
117
- expect(result.accepted).toBe(true);
118
- expect(handler.askQuestion).toHaveBeenCalled();
119
- });
120
160
  it("skips interview but still passes when handler is null (headless)", async () => {
121
161
  const result = await runDiscovery("fix auth", {
122
162
  taskType: "debug",
@@ -129,106 +169,10 @@ describe("runDiscovery()", () => {
129
169
  expect(result.interviewed).toBe(false);
130
170
  expect(result.accepted).toBe(true);
131
171
  });
132
- it("sets accepted=false when user cancels", async () => {
133
- const handler = {
134
- // First askQuestion = the model's interview question, second = acceptance card.
135
- askQuestion: vi
136
- .fn()
137
- .mockResolvedValueOnce({ questionId: "q1", text: "done", kind: "choice" })
138
- .mockResolvedValue({ questionId: "q-acc", text: "cancel", kind: "choice" }),
139
- showAcceptance: vi.fn().mockResolvedValue("cancel"),
140
- };
141
- const proposer = vi.fn().mockResolvedValue(["What's the expected outcome? [MODEL RECS: Error disappears]"]);
142
- const result = await runDiscovery("fix auth", {
143
- taskType: "debug",
144
- confidence: 0.6,
145
- complexity: "low",
146
- domain: "typescript",
147
- outputStyle: null,
148
- intentKind: "task",
149
- }, process.cwd(), handler, null, proposer);
150
- expect(result.accepted).toBe(false);
151
- });
152
- it("does not swallow the original request into a generic outcome for a general prompt (B2)", async () => {
153
- // B2 — the old generic outcome askcard collapsed intent to "general: Task
154
- // completed", discarding the user's prompt. With the model proposing no
155
- // questions, the outcome must derive from the raw text (no fabrication).
156
- const handler = {
157
- askQuestion: vi.fn().mockResolvedValue({ questionId: "q1", text: "Task completed", kind: "choice" }),
158
- showAcceptance: vi.fn().mockResolvedValue("accept"),
159
- };
160
- const proposer = vi.fn().mockResolvedValue([]);
161
- const result = await runDiscovery("make the dashboard feel less cluttered", {
162
- taskType: "general",
163
- confidence: 0.6,
164
- complexity: "low",
165
- domain: null,
166
- outputStyle: null,
167
- intentKind: "task",
168
- }, process.cwd(), handler, null, proposer);
169
- expect(result.intentStatement).not.toBe("general: Task completed");
170
- expect(result.outcome).not.toBe("Task completed");
171
- // The original request must survive into the resolved outcome.
172
- expect(result.outcome.toLowerCase()).toContain("dashboard");
173
- });
174
172
  it("skips discovery entirely for chitchat", async () => {
175
173
  const result = await runDiscovery("hi", { taskType: null, confidence: 0.5, complexity: "low", domain: null, outputStyle: null, intentKind: "chitchat" }, process.cwd(), mockHandler);
176
174
  expect(result.interviewed).toBe(false);
177
175
  expect(result.accepted).toBe(true);
178
176
  });
179
177
  });
180
- describe("runDiscovery() — outcome autofill override (path-leak vs legit slash)", () => {
181
- const analyzeL1 = {
182
- taskType: "analyze",
183
- confidence: 0.6,
184
- complexity: "low",
185
- domain: null,
186
- outputStyle: null,
187
- intentKind: "task",
188
- };
189
- // A handler that always picks `text` for both the interview answer and the
190
- // acceptance card (any non-"cancel"/"adjust" text accepts).
191
- const pickAnswer = (text) => ({
192
- askQuestion: vi.fn().mockResolvedValue({ questionId: "q1", text, kind: "choice" }),
193
- showAcceptance: vi.fn().mockResolvedValue("accept"),
194
- });
195
- it("preserves a user outcome answer containing '/' (does not clobber with the autofilled default)", async () => {
196
- // Regression: the override matched ANY '/' (bare `\/` regex alt +
197
- // `.includes("/")`), silently replacing a legit answer like
198
- // "support both REST/GraphQL endpoints" with the analyze default.
199
- const userAnswer = "support both REST/GraphQL endpoints";
200
- const proposer = vi
201
- .fn()
202
- .mockResolvedValue([
203
- "Which API surface should the analysis target? [MODEL RECS: support both REST/GraphQL endpoints | REST only]",
204
- ]);
205
- const result = await runDiscovery("review the API layer", analyzeL1, process.cwd(), pickAnswer(userAnswer), null, proposer);
206
- expect(result.outcome).toBe(userAnswer);
207
- expect(result.outcome).not.toBe("Detailed analysis with concrete improvement recommendations");
208
- });
209
- it("preserves another 'or'-style slash answer (validate input/output schemas)", async () => {
210
- const userAnswer = "validate input/output schemas";
211
- const proposer = vi.fn().mockResolvedValue(["What should the analysis verify?"]);
212
- const result = await runDiscovery("review the API layer", analyzeL1, process.cwd(), pickAnswer(userAnswer), null, proposer);
213
- expect(result.outcome).toBe(userAnswer);
214
- });
215
- it("still overwrites a genuinely path-leaked outcome with the autofilled default", async () => {
216
- // Guard against over-correction: a real filesystem-path leak (scope-option
217
- // shape "src/cli (cli)") must STILL be replaced by the inferred outcome.
218
- const proposer = vi.fn().mockResolvedValue(["What scope? [MODEL RECS: src/cli (cli)]"]);
219
- const result = await runDiscovery("review the API layer", analyzeL1, process.cwd(), pickAnswer("src/cli (cli)"), null, proposer);
220
- expect(result.outcome).toBe("Detailed analysis with concrete improvement recommendations");
221
- });
222
- it("treats the 'provide my own details' meta-option as no-answer, not a literal outcome", async () => {
223
- // The default meta-option ("I will provide my own details / constraints")
224
- // is a 'no specific answer' sentinel — it must not survive verbatim as the
225
- // outcome. With no inferred default available (generate), it falls back to
226
- // the raw-derived intent rather than the sentinel string.
227
- const sentinel = "I will provide my own details / constraints";
228
- const proposer = vi.fn().mockResolvedValue(["What outcome do you expect?"]);
229
- const result = await runDiscovery("build the user dashboard widget", { ...analyzeL1, taskType: "generate" }, process.cwd(), pickAnswer(sentinel), null, proposer);
230
- expect(result.outcome).not.toBe(sentinel);
231
- expect(result.outcome.toLowerCase()).toContain("dashboard");
232
- });
233
- });
234
178
  //# sourceMappingURL=discovery.test.js.map
@@ -12,6 +12,8 @@ vi.mock("../../ee/bridge.js", () => ({
12
12
  getEmbeddingRaw: vi.fn().mockResolvedValue(null),
13
13
  routeTask: vi.fn().mockResolvedValue(null),
14
14
  pilContext: vi.fn().mockResolvedValue(null),
15
+ getWhoAmIProfile: vi.fn(() => null),
16
+ outputStyleFromProfile: vi.fn(() => null),
15
17
  }));
16
18
  const FIXTURES = [
17
19
  "tại sao test fail?",
@@ -20,6 +20,9 @@ vi.mock("../../ee/bridge.js", () => ({
20
20
  vi.mock("../config.js", () => ({
21
21
  isUnifiedPilEnabled: mockIsUnifiedPilEnabled,
22
22
  isLlmFirstClassifyEnabled: mockIsLlmFirstClassifyEnabled,
23
+ // Pass-3 unified reads the client-side budget; whole-module mock replaces the
24
+ // real export, so it must be provided or the call site throws.
25
+ getUnifiedPilBudgetMs: vi.fn(() => 3500),
23
26
  }));
24
27
  import { layer1Intent } from "../layer1-intent.js";
25
28
  function makeCtx(raw) {
@@ -7,6 +7,9 @@ vi.mock("../config.js", () => ({
7
7
  // OFF here so these cascade tests exercise the regex passes (the model-first
8
8
  // gate is covered in src/pil/layer1-intent.test.ts).
9
9
  isLlmFirstClassifyEnabled: vi.fn(() => false),
10
+ // Pass-3 unified reads the client-side budget; whole-module mock replaces the
11
+ // real export, so it must be provided or the call site throws.
12
+ getUnifiedPilBudgetMs: vi.fn(() => 3500),
10
13
  }));
11
14
  vi.mock("../../ee/bridge.js", () => ({
12
15
  classifyViaBrain: vi.fn().mockResolvedValue(null),
@@ -1,9 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { buildInterviewQuestion, resolveGapsNonInteractive } from "../layer16-clarity.js";
3
- // Phase 2 (2026-06-16): detectClarityGaps + its keyword option-builders were
4
- // removed (the model now generates every clarification). The surviving helpers
5
- // — buildInterviewQuestion (render) and resolveGapsNonInteractive (headless
6
- // default-answer resolution) — are exercised here with model-shaped gaps.
2
+ import { modelCardToQuestion, resolveGapsNonInteractive } from "../layer16-clarity.js";
7
3
  const EMPTY_PROJECT = {
8
4
  language: "typescript",
9
5
  framework: null,
@@ -18,49 +14,56 @@ const EMPTY_PROJECT = {
18
14
  scannedAt: Date.now(),
19
15
  cwd: "/proj",
20
16
  };
21
- describe("buildInterviewQuestion()", () => {
22
- it("builds a CouncilQuestionData with pil-interview phase", () => {
23
- const gap = {
24
- dimension: "outcome",
25
- description: "no outcome",
26
- suggestedQuestion: "What outcome?",
27
- options: ["test passes", "no error"],
17
+ describe("modelCardToQuestion()", () => {
18
+ it("maps ModelCard to CouncilQuestionData with pil-interview phase", () => {
19
+ const card = {
20
+ question: "What outcome?",
21
+ context: "This changes how we debug",
22
+ options: [
23
+ { label: "Error disappears", kind: "choice" },
24
+ { label: "Custom fix", kind: "freetext" },
25
+ ],
28
26
  defaultIndex: 0,
29
27
  };
30
- const q = buildInterviewQuestion(gap, "q-1");
28
+ const q = modelCardToQuestion(card, "q-1");
31
29
  expect(q.phase).toBe("pil-interview");
32
30
  expect(q.questionId).toBe("q-1");
33
31
  expect(q.options).toBeDefined();
34
- expect(q.options.some((o) => o.kind === "freetext")).toBe(true);
32
+ expect(q.options).toHaveLength(2);
33
+ expect(q.options[0].isCancel).toBeUndefined();
34
+ expect(q.options[0].isAdjust).toBeUndefined();
35
35
  });
36
- it("surfaces the model's reason (gap.description) as the askcard context", () => {
37
- const gap = {
38
- dimension: "outcome",
39
- description: "answering this changes whether we add OAuth or just API keys",
40
- suggestedQuestion: "Which auth method?",
41
- options: ["OAuth", "API keys"],
36
+ it("preserves isCancel and isAdjust flags from model card options", () => {
37
+ const card = {
38
+ question: "Proceed?",
39
+ options: [
40
+ { label: "Looks good, go ahead", kind: "choice" },
41
+ { label: "Cancel this", kind: "choice", isCancel: true },
42
+ { label: "Let me clarify", kind: "choice", isAdjust: true },
43
+ ],
42
44
  defaultIndex: 0,
43
45
  };
44
- const q = buildInterviewQuestion(gap, "q-2");
45
- expect(q.context).toBe("answering this changes whether we add OAuth or just API keys");
46
+ const q = modelCardToQuestion(card, "q-2");
47
+ expect(q.options[1].isCancel).toBe(true);
48
+ expect(q.options[2].isAdjust).toBe(true);
46
49
  });
47
50
  });
48
51
  describe("resolveGapsNonInteractive()", () => {
49
- it("fills gaps with best-effort defaults from the model options + project context", () => {
50
- const gaps = [
52
+ it("fills gaps with default options from model cards", () => {
53
+ const cards = [
51
54
  {
52
- dimension: "outcome",
53
- description: "Model-generated clarification #1",
54
- suggestedQuestion: "What outcome do you expect?",
55
- options: ["Error resolved", "Other (type free answer)"],
55
+ question: "What outcome?",
56
+ options: [
57
+ { label: "Error resolved", kind: "choice" },
58
+ { label: "Not sure", kind: "freetext" },
59
+ ],
56
60
  defaultIndex: 0,
57
61
  },
58
62
  ];
59
- const resolved = resolveGapsNonInteractive(gaps, EMPTY_PROJECT, "fix auth");
60
- expect(resolved.outcome).toBe("Error resolved");
61
- expect(resolved.scope.length).toBeGreaterThan(0);
63
+ const resolved = resolveGapsNonInteractive(cards, EMPTY_PROJECT, "fix auth");
64
+ expect(resolved.outcome).toContain("Error resolved");
62
65
  });
63
- it("falls back to the raw-derived outcome when there is no outcome gap", () => {
66
+ it("falls back to raw-derived outcome when model cards are empty", () => {
64
67
  const resolved = resolveGapsNonInteractive([], EMPTY_PROJECT, "fix the login bug");
65
68
  expect(resolved.outcome).toBeTruthy();
66
69
  expect(resolved.scope.length).toBeGreaterThan(0);
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { buildAcceptanceCard, buildAcceptanceQuestion } from "../layer18-acceptance.js";
2
+ import { buildAcceptanceCard } from "../layer18-acceptance.js";
3
3
  describe("buildAcceptanceCard()", () => {
4
4
  it("builds card with intent, outcome, scope, and warnings", () => {
5
5
  const intent = { outcome: "error gone", scope: ["src/auth/jwt.ts"], constraints: [], gaps: [] };
@@ -15,81 +15,4 @@ describe("buildAcceptanceCard()", () => {
15
15
  expect(card.warnings).toEqual(["oauth.ts also handles tokens"]);
16
16
  });
17
17
  });
18
- describe("buildAcceptanceQuestion()", () => {
19
- it("builds a CouncilQuestionData with pil-acceptance phase", () => {
20
- const card = { intentStatement: "Fix auth", outcome: "done", scope: ["src/auth/"], warnings: [] };
21
- const q = buildAcceptanceQuestion(card, "acc-1");
22
- expect(q.phase).toBe("pil-acceptance");
23
- expect(q.questionId).toBe("acc-1");
24
- expect(q.options).toHaveLength(3);
25
- expect(q.options[0].label).toBe("Accept");
26
- expect(q.options[1].label).toBe("Adjust");
27
- expect(q.options[2].label).toBe("Cancel");
28
- expect(q.defaultIndex).toBe(0);
29
- });
30
- it("includes warnings in context when present", () => {
31
- const card = { intentStatement: "Fix auth", outcome: "done", scope: ["src/auth/"], warnings: ["risk: oauth.ts"] };
32
- const q = buildAcceptanceQuestion(card, "acc-2");
33
- expect(q.context).toContain("risk: oauth.ts");
34
- });
35
- // Default flips to "Adjust" only for BLOCKING warnings (e.g. infeasibility).
36
- it("defaults to Adjust (index 1) when warning is blocking", () => {
37
- const card = {
38
- intentStatement: "Fix auth",
39
- outcome: "done",
40
- scope: ["src/"],
41
- warnings: ["infeasible: file does not exist"],
42
- };
43
- const q = buildAcceptanceQuestion(card, "acc-3");
44
- expect(q.defaultIndex).toBe(1);
45
- });
46
- it("defaults to Accept (index 0) when no warnings", () => {
47
- const card = { intentStatement: "Fix auth", outcome: "done", scope: ["src/"], warnings: [] };
48
- const q = buildAcceptanceQuestion(card, "acc-4");
49
- expect(q.defaultIndex).toBe(0);
50
- });
51
- // Fix #7 — informational mismatch warning should NOT flip default to Adjust.
52
- // Evidence: session 1f29e238 ("Can you fix it?" follow-up) — the intent
53
- // mismatch detector emitted "Detected debug/bug-fix signals..." which is
54
- // informational; forcing Adjust looped PIL through another interview round.
55
- it("defaults to Accept when only warning is intent-mismatch (informational)", () => {
56
- const card = {
57
- intentStatement: "debug: Complete: Can you fix it?",
58
- outcome: "done",
59
- scope: ["src/"],
60
- warnings: ['Detected debug/bug-fix signals in your prompt ("fail") but intent reframed as "..."'],
61
- };
62
- const q = buildAcceptanceQuestion(card, "acc-5");
63
- expect(q.defaultIndex).toBe(0);
64
- });
65
- it("defaults to Adjust when mix of informational + blocking warnings", () => {
66
- const card = {
67
- intentStatement: "Fix auth",
68
- outcome: "done",
69
- scope: ["src/"],
70
- warnings: ["Detected debug signals — Verify before accepting", "infeasible: file does not exist"],
71
- };
72
- const q = buildAcceptanceQuestion(card, "acc-6");
73
- expect(q.defaultIndex).toBe(1);
74
- });
75
- });
76
- describe("buildAcceptanceCard() intent-mismatch detection — PIL-L6", () => {
77
- const baseFeas = { viable: true, warnings: [], adjustedScope: [] };
78
- it("flags warning when raw has debug signals but intent is non-debug", () => {
79
- const intent = { outcome: "Feature implemented", scope: ["project"], constraints: [], gaps: [] };
80
- const card = buildAcceptanceCard("generate: Feature implemented and working", intent, baseFeas, "ci action fail dùng gh check và fix cho t");
81
- expect(card.warnings.length).toBeGreaterThan(0);
82
- expect(card.warnings[0]).toMatch(/debug.*signals|Verify before accepting/i);
83
- });
84
- it("no mismatch when intent statement matches debug signal", () => {
85
- const intent = { outcome: "CI green", scope: ["project"], constraints: [], gaps: [] };
86
- const card = buildAcceptanceCard("debug: CI pipeline green", intent, baseFeas, "fix the ci fail");
87
- expect(card.warnings).toEqual([]);
88
- });
89
- it("no warning when raw has no debug signals", () => {
90
- const intent = { outcome: "Feature implemented", scope: ["project"], constraints: [], gaps: [] };
91
- const card = buildAcceptanceCard("generate: Feature implemented and working", intent, baseFeas, "add a new button to the dashboard");
92
- expect(card.warnings).toEqual([]);
93
- });
94
- });
95
18
  //# sourceMappingURL=layer18-acceptance.test.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { layer2_5Ponytail } from "../layer2_5-ponytail.js";
3
+ describe("layer2_5Ponytail", () => {
4
+ const originalEnv = process.env.MUONROI_PONYTAIL_DISABLE;
5
+ afterEach(() => {
6
+ if (originalEnv === undefined) {
7
+ delete process.env.MUONROI_PONYTAIL_DISABLE;
8
+ }
9
+ else {
10
+ process.env.MUONROI_PONYTAIL_DISABLE = originalEnv;
11
+ }
12
+ });
13
+ it("should apply ponytail instruction when enabled", async () => {
14
+ process.env.MUONROI_PONYTAIL_DISABLE = "0";
15
+ const ctx = {
16
+ raw: "test",
17
+ enriched: "test",
18
+ taskType: "general",
19
+ confidence: 1,
20
+ domain: null,
21
+ outputStyle: "balanced",
22
+ tokenBudget: 1000,
23
+ metrics: null,
24
+ layers: [],
25
+ };
26
+ const result = await layer2_5Ponytail(ctx);
27
+ expect(result.layers.length).toBe(1);
28
+ expect(result.layers[0].name).toBe("ponytail-mode");
29
+ expect(result.layers[0].applied).toBe(true);
30
+ expect(result.enriched).toContain("LAZY SENIOR / PONYTAIL MODE ACTIVE");
31
+ });
32
+ it("should skip ponytail instruction when disabled via env var", async () => {
33
+ process.env.MUONROI_PONYTAIL_DISABLE = "1";
34
+ const ctx = {
35
+ raw: "test",
36
+ enriched: "test",
37
+ taskType: "general",
38
+ confidence: 1,
39
+ domain: null,
40
+ outputStyle: "balanced",
41
+ tokenBudget: 1000,
42
+ metrics: null,
43
+ layers: [],
44
+ };
45
+ const result = await layer2_5Ponytail(ctx);
46
+ expect(result.layers.length).toBe(1);
47
+ expect(result.layers[0].name).toBe("ponytail-mode");
48
+ expect(result.layers[0].applied).toBe(false);
49
+ expect(result.layers[0].delta).toBe("skipped:disabled-by-config");
50
+ expect(result.enriched).toBe("test"); // Not enriched with ponytail
51
+ });
52
+ });
53
+ //# sourceMappingURL=layer2_5-ponytail.test.js.map