carthagent 0.0.0-stage → 1.0.7

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 (379) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -2
  3. package/bin/ctg.mjs +736 -0
  4. package/docs/budgets.md +72 -0
  5. package/docs/compaction.md +34 -0
  6. package/docs/context-efficiency-plan.md +24 -0
  7. package/docs/context-efficiency-review.md +27 -0
  8. package/docs/context-efficiency.md +49 -0
  9. package/docs/deepseek-jev-benchmark-plan.md +70 -0
  10. package/docs/deepseek-jev-benchmark-repair-plan.md +20 -0
  11. package/docs/deepseek-jev-benchmark-restart.md +9 -0
  12. package/docs/deepseek-jev-benchmark-results.md +77 -0
  13. package/docs/deepseek-jev-benchmark-running.md +60 -0
  14. package/docs/deepseek-jev-benchmark-v3-results.md +41 -0
  15. package/docs/deepseek-jev-benchmark-v4-protocol.md +15 -0
  16. package/docs/deepseek-jev-benchmark-v4-results.md +119 -0
  17. package/docs/deepseek-jev-http400-diagnosis.md +45 -0
  18. package/docs/delivery-review-evidence.md +53 -0
  19. package/docs/delivery-timeout-repair-plan.md +51 -0
  20. package/docs/evaluation-readiness.md +31 -0
  21. package/docs/evaluation.md +70 -0
  22. package/docs/extending.md +354 -0
  23. package/docs/glm-pilot-execution.md +9 -0
  24. package/docs/glm-pilot-plan.md +15 -0
  25. package/docs/glm-pilot-results.md +71 -0
  26. package/docs/graph-context-optimization.md +15 -0
  27. package/docs/jev-repository-pilot-plan.md +25 -0
  28. package/docs/jev-repository-pilot-protocol.md +39 -0
  29. package/docs/jev-repository-pilot-restart.md +7 -0
  30. package/docs/jev-repository-pilot-results.md +79 -0
  31. package/docs/jev.md +32 -0
  32. package/docs/model-registry.md +25 -0
  33. package/docs/native-compatibility.md +117 -0
  34. package/docs/opencode-archive.md +9 -0
  35. package/docs/opencode-migration.md +115 -0
  36. package/docs/opencode-runtime.md +94 -0
  37. package/docs/opencode-workspace.md +31 -0
  38. package/docs/owned-runtime-workflow-registration.md +17 -0
  39. package/docs/plan-advisor-evaluation.md +38 -0
  40. package/docs/planning-startup-fix-plan.md +23 -0
  41. package/docs/planning-startup-fix-review.md +24 -0
  42. package/docs/planning.md +118 -0
  43. package/docs/production-merge-review.md +23 -0
  44. package/docs/quality.md +35 -0
  45. package/docs/repository-benchmark-design.md +111 -0
  46. package/docs/review-assurance.md +9 -0
  47. package/docs/review-experiment-results.md +59 -0
  48. package/docs/tested-planning-roadmap.md +58 -0
  49. package/docs/tested-planning.md +58 -0
  50. package/docs/tui-pickers.md +9 -0
  51. package/docs/webdriver.md +83 -0
  52. package/docs/workflow-modes.md +26 -0
  53. package/docs/workflow.d2 +17 -0
  54. package/docs/workflow.svg +149 -0
  55. package/docs/workspace-coordination.md +9 -0
  56. package/evaluate.mjs +249 -0
  57. package/extensions/codegraph.ts +36 -0
  58. package/extensions/delivery.ts +819 -0
  59. package/extensions/experiential.ts +21 -0
  60. package/extensions/subscriptions.ts +21 -0
  61. package/guidance/profiles/authenticated-web.json +24 -0
  62. package/guidance/profiles/data-visualization.json +25 -0
  63. package/guidance/profiles/database-change.json +24 -0
  64. package/guidance/profiles/external-api.json +24 -0
  65. package/guidance/profiles/transactional-data.json +25 -0
  66. package/guidance/profiles/web-application.json +26 -0
  67. package/lib/acceptance-review.mjs +4 -0
  68. package/lib/browser-bin.mjs +160 -0
  69. package/lib/browser-errors.mjs +54 -0
  70. package/lib/browser.mjs +219 -0
  71. package/lib/budget.mjs +37 -0
  72. package/lib/cdp.mjs +221 -0
  73. package/lib/cloud/client.mjs +275 -0
  74. package/lib/cloud/control-plane.mjs +350 -0
  75. package/lib/cloud/gateway.mjs +380 -0
  76. package/lib/cloud/http.mjs +135 -0
  77. package/lib/cloud/ledger.mjs +215 -0
  78. package/lib/codegraph-query.mjs +60 -0
  79. package/lib/codegraph.mjs +378 -0
  80. package/lib/context-archive.mjs +18 -0
  81. package/lib/delivery-context.mjs +132 -0
  82. package/lib/delivery-guidance.mjs +9 -0
  83. package/lib/delivery-review.mjs +94 -0
  84. package/lib/delivery.mjs +610 -0
  85. package/lib/engine.mjs +137 -0
  86. package/lib/guidance.mjs +71 -0
  87. package/lib/jev-advisor.mjs +47 -0
  88. package/lib/opencode/archive.mjs +122 -0
  89. package/lib/opencode/compatibility.mjs +19 -0
  90. package/lib/opencode/credentials.mjs +205 -0
  91. package/lib/opencode/evaluation-budget-plugin/index.ts +147 -0
  92. package/lib/opencode/evaluation.mjs +242 -0
  93. package/lib/opencode/file-lock.mjs +86 -0
  94. package/lib/opencode/headless.mjs +200 -0
  95. package/lib/opencode/review.mjs +213 -0
  96. package/lib/opencode/runtime.mjs +246 -0
  97. package/lib/opencode/service.mjs +249 -0
  98. package/lib/opencode/session-options.mjs +60 -0
  99. package/lib/opencode/workspace.mjs +133 -0
  100. package/lib/openrouter-jev.mjs +37 -0
  101. package/lib/outcome-contract.mjs +21 -0
  102. package/lib/plan-advisor.mjs +38 -0
  103. package/lib/planning-access.mjs +7 -0
  104. package/lib/planning-advice.mjs +28 -0
  105. package/lib/planning-discovery.mjs +14 -0
  106. package/lib/planning-guide.mjs +34 -0
  107. package/lib/planning-view.mjs +57 -0
  108. package/lib/planning.mjs +155 -0
  109. package/lib/probe-diagnostics.mjs +13 -0
  110. package/lib/providers/experiential.mjs +292 -0
  111. package/lib/providers/model-catalog.json +700 -0
  112. package/lib/providers/model-catalog.mjs +7 -0
  113. package/lib/providers/names.mjs +13 -0
  114. package/lib/providers/subscription-models.mjs +483 -0
  115. package/lib/reports.mjs +80 -0
  116. package/lib/review-state.mjs +49 -0
  117. package/lib/review.mjs +114 -0
  118. package/lib/scenarios.mjs +99 -0
  119. package/lib/ship-auth.mjs +44 -0
  120. package/lib/ship-jev.mjs +59 -0
  121. package/lib/ship-telemetry.mjs +183 -0
  122. package/lib/tui/ansi.mjs +255 -0
  123. package/lib/tui/app.mjs +2445 -0
  124. package/lib/tui/auth.mjs +249 -0
  125. package/lib/tui/carthage.mjs +109 -0
  126. package/lib/tui/clipboard.mjs +122 -0
  127. package/lib/tui/d2.mjs +431 -0
  128. package/lib/tui/feed.mjs +467 -0
  129. package/lib/tui/framing.mjs +80 -0
  130. package/lib/tui/glyphs.mjs +172 -0
  131. package/lib/tui/hash-worker.mjs +11 -0
  132. package/lib/tui/mock-engine.mjs +222 -0
  133. package/lib/tui/models.mjs +73 -0
  134. package/lib/tui/picker.mjs +18 -0
  135. package/lib/tui/theme.mjs +663 -0
  136. package/lib/tui/title.mjs +7 -0
  137. package/lib/update-channel.mjs +62 -0
  138. package/lib/update.mjs +230 -0
  139. package/lib/webdriver.mjs +82 -0
  140. package/lib/workflow.mjs +88 -0
  141. package/lib/workspace.mjs +39 -0
  142. package/metadata.json +8 -0
  143. package/package.json +94 -4
  144. package/plugins/carthagent/index.ts +3 -0
  145. package/plugins/carthagent/package.json +28 -0
  146. package/plugins/carthagent/src/archive-rpc.ts +46 -0
  147. package/plugins/carthagent/src/bootstrap.ts +38 -0
  148. package/plugins/carthagent/src/index.ts +62 -0
  149. package/plugins/carthagent/src/pull-request-rpc.ts +54 -0
  150. package/plugins/carthagent/src/rpc.ts +76 -0
  151. package/plugins/carthagent/src/server/archive-rpc.ts +327 -0
  152. package/plugins/carthagent/src/server/budgets.ts +21 -0
  153. package/plugins/carthagent/src/server/codegraph.ts +46 -0
  154. package/plugins/carthagent/src/server/delivery.ts +488 -0
  155. package/plugins/carthagent/src/server/device-auth.ts +91 -0
  156. package/plugins/carthagent/src/server/native-delivery.ts +26 -0
  157. package/plugins/carthagent/src/server/orchestration.ts +56 -0
  158. package/plugins/carthagent/src/server/policy.ts +24 -0
  159. package/plugins/carthagent/src/server/providers.ts +424 -0
  160. package/plugins/carthagent/src/server/pull-request-rpc.ts +387 -0
  161. package/plugins/carthagent/src/server/session-controls.ts +82 -0
  162. package/plugins/carthagent/src/server/telemetry.ts +92 -0
  163. package/plugins/carthagent/src/server/workspace-rpc.ts +349 -0
  164. package/plugins/carthagent/src/session-rpc.ts +31 -0
  165. package/plugins/carthagent/src/testing.ts +85 -0
  166. package/plugins/carthagent/src/workspace-rpc.ts +133 -0
  167. package/plugins/carthagent/test-plugin/index.ts +4 -0
  168. package/plugins/carthagent/test-plugin/package.json +1 -0
  169. package/plugins/carthagent/tsconfig.json +19 -0
  170. package/plugins/carthagent/tui.ts +1 -0
  171. package/prompts/delivery.md +7 -0
  172. package/public/index.html +983 -0
  173. package/scenarios/cli/grade_cli.py +69 -0
  174. package/scenarios/cli/scenario.json +21 -0
  175. package/scenarios/game/baseline/minecraft.py +193 -0
  176. package/scenarios/game/context.md +1 -0
  177. package/scenarios/game/grade_minecraft_clone.py +603 -0
  178. package/scenarios/game/requirements.txt +2 -0
  179. package/scenarios/game/scenario.json +33 -0
  180. package/server.mjs +341 -0
  181. package/skills/game-development/SKILL.md +36 -0
  182. package/skills/game-development/assets/ursina_starter.py +98 -0
  183. package/skills/game-development/scripts/verify_ursina.py +80 -0
  184. package/skills/software-delivery/SKILL.md +50 -0
  185. package/skills/typescript-delivery/SKILL.md +69 -0
  186. package/tests/acceptance-review.test.mjs +13 -0
  187. package/tests/auth.test.mjs +234 -0
  188. package/tests/bounded-summary.test.mjs +68 -0
  189. package/tests/browser-bin.test.mjs +91 -0
  190. package/tests/browser.test.mjs +96 -0
  191. package/tests/budget-engine.test.mjs +110 -0
  192. package/tests/budget.test.mjs +23 -0
  193. package/tests/cli.test.mjs +245 -0
  194. package/tests/clipboard.test.mjs +79 -0
  195. package/tests/cloud-client.test.mjs +129 -0
  196. package/tests/cloud-control-plane.test.mjs +144 -0
  197. package/tests/cloud-gateway.test.mjs +147 -0
  198. package/tests/cloud-http.test.mjs +102 -0
  199. package/tests/cloud-ledger.test.mjs +75 -0
  200. package/tests/codegraph-query.test.mjs +66 -0
  201. package/tests/codegraph.test.mjs +142 -0
  202. package/tests/delivery-context.test.mjs +66 -0
  203. package/tests/delivery-review.test.mjs +139 -0
  204. package/tests/delivery.test.mjs +343 -0
  205. package/tests/engine-api.mjs +24 -0
  206. package/tests/experiential-extension.test.mjs +37 -0
  207. package/tests/experiential.test.mjs +300 -0
  208. package/tests/extension.test.mjs +921 -0
  209. package/tests/fixtures/archive-plugin/index.ts +10 -0
  210. package/tests/guidance.test.mjs +78 -0
  211. package/tests/model-registry.test.mjs +68 -0
  212. package/tests/models.test.mjs +70 -0
  213. package/tests/openai-idempotency.test.mjs +99 -0
  214. package/tests/opencode-archive-rpc.test.mjs +82 -0
  215. package/tests/opencode-delivery.test.mjs +231 -0
  216. package/tests/opencode-providers.test.mjs +414 -0
  217. package/tests/opencode-runtime.test.mjs +455 -0
  218. package/tests/opencode-session-controls.test.mjs +115 -0
  219. package/tests/opencode-session-options.test.mjs +26 -0
  220. package/tests/opencode-workspace.test.mjs +339 -0
  221. package/tests/outcome-contract.test.mjs +17 -0
  222. package/tests/picker.test.mjs +34 -0
  223. package/tests/plan-advisor.test.mjs +88 -0
  224. package/tests/plan-review-loop.test.mjs +53 -0
  225. package/tests/planning-advice.test.mjs +27 -0
  226. package/tests/planning-contract.test.mjs +65 -0
  227. package/tests/planning-startup.test.mjs +38 -0
  228. package/tests/planning-view.test.mjs +33 -0
  229. package/tests/planning.test.mjs +223 -0
  230. package/tests/probe-diagnostics.test.mjs +21 -0
  231. package/tests/pull-request-rpc.test.mjs +366 -0
  232. package/tests/review-state.test.mjs +32 -0
  233. package/tests/review.test.mjs +127 -0
  234. package/tests/server.test.mjs +252 -0
  235. package/tests/ship-jev.test.mjs +77 -0
  236. package/tests/ship-telemetry.test.mjs +333 -0
  237. package/tests/standard-workflow.test.mjs +28 -0
  238. package/tests/subscription-models.test.mjs +416 -0
  239. package/tests/subscriptions-extension.test.mjs +27 -0
  240. package/tests/terminal-title.test.mjs +13 -0
  241. package/tests/test_grade_minecraft_clone.py +199 -0
  242. package/tests/test_starter.py +78 -0
  243. package/tests/theme.test.mjs +175 -0
  244. package/tests/tui-copy-paste.test.mjs +54 -0
  245. package/tests/tui.test.mjs +808 -0
  246. package/tests/update-channels.test.mjs +129 -0
  247. package/tests/update.test.mjs +175 -0
  248. package/tests/webdriver.test.mjs +31 -0
  249. package/tests/workflow.test.mjs +47 -0
  250. package/tests/workspace-rpc.test.mjs +236 -0
  251. package/tests/workspace.test.mjs +24 -0
  252. package/tools/benchmark/advisor.test.mjs +7 -0
  253. package/tools/benchmark/advisor.ts +27 -0
  254. package/tools/benchmark/campaign.mjs +45 -0
  255. package/tools/benchmark/diagnose-request.mjs +40 -0
  256. package/tools/benchmark/eval-common.mjs +32 -0
  257. package/tools/benchmark/eval-ledger.mjs +22 -0
  258. package/tools/benchmark/eval-shop.mjs +26 -0
  259. package/tools/benchmark/eval-voxel.mjs +20 -0
  260. package/tools/benchmark/evaluate.mjs +24 -0
  261. package/tools/benchmark/evaluators.test.mjs +13 -0
  262. package/tools/benchmark/fixtures/shop/server.mjs +18 -0
  263. package/tools/benchmark/fixtures/shop/ui.html +8 -0
  264. package/tools/benchmark/freeze.mjs +30 -0
  265. package/tools/benchmark/gateway.mjs +91 -0
  266. package/tools/benchmark/gateway.test.mjs +38 -0
  267. package/tools/benchmark/generator.mjs +11 -0
  268. package/tools/benchmark/generator.test.mjs +26 -0
  269. package/tools/benchmark/guard.test.mjs +133 -0
  270. package/tools/benchmark/guard.ts +136 -0
  271. package/tools/benchmark/native-plugin/index.ts +24 -0
  272. package/tools/benchmark/planning-startup.test.mjs +47 -0
  273. package/tools/benchmark/preflight-live.mjs +31 -0
  274. package/tools/benchmark/preflight.mjs +22 -0
  275. package/tools/benchmark/replay-diagnostic.mjs +23 -0
  276. package/tools/benchmark/runner.mjs +141 -0
  277. package/tools/benchmark/runner.test.mjs +93 -0
  278. package/tools/benchmark/runtime.mjs +23 -0
  279. package/tools/benchmark/selftest.mjs +47 -0
  280. package/tools/benchmark/smoke-jev.mjs +5 -0
  281. package/tools/benchmark/tasks.mjs +20 -0
  282. package/tools/benchmark/transport.mjs +65 -0
  283. package/tools/benchmark/transport.test.mjs +44 -0
  284. package/tools/benchmark/upstream-error.mjs +18 -0
  285. package/tools/benchmark/upstream-error.test.mjs +10 -0
  286. package/tools/browser-check.mjs +109 -0
  287. package/tools/check-syntax.mjs +27 -0
  288. package/tools/evaluate-plan-advisor.mjs +20 -0
  289. package/tools/native-fixture.mjs +194 -0
  290. package/tools/native-pty-smoke.py +108 -0
  291. package/tools/native-report-plugin/index.ts +50 -0
  292. package/tools/native-report-plugin/package.json +7 -0
  293. package/tools/patch-compaction.mjs +24 -0
  294. package/tools/patch-model-catalog.mjs +12 -0
  295. package/tools/patch-print-shutdown.mjs +39 -0
  296. package/tools/repo-pilot/evaluator.cjs +32 -0
  297. package/tools/repo-pilot/reference.mjs +43 -0
  298. package/tools/repo-pilot/runner.mjs +85 -0
  299. package/tools/repo-pilot/sandbox-entry.mjs +12 -0
  300. package/tools/repo-pilot/tasks.mjs +20 -0
  301. package/tools/repo-pilot/type-evaluator.cjs +6 -0
  302. package/tools/test-native-controls.mjs +84 -0
  303. package/tools/test-native-delivery-host.mjs +307 -0
  304. package/tools/test-native-evaluation.mjs +46 -0
  305. package/tools/test-native-host.mjs +385 -0
  306. package/tools/test-native-outcomes.mjs +73 -0
  307. package/tools/test-native-package.mjs +70 -0
  308. package/tools/test-native-pty.mjs +20 -0
  309. package/tools/test-native-report.mjs +88 -0
  310. package/tools/test-native-swe-host.mjs +132 -0
  311. package/tools/test-native-swe.mjs +122 -0
  312. package/tools/test-picker-pty.py +45 -0
  313. package/tools/test-python.mjs +29 -0
  314. package/tools/test-webdriver.mjs +54 -0
  315. package/tools/verify-release-version.mjs +37 -0
  316. package/tools/webdriver-check.mjs +52 -0
  317. package/vendor/agent/bounded-summary.mjs +88 -0
  318. package/vendor/agent/chunks/anthropic-messages-VWZZOSJQ.js +6 -0
  319. package/vendor/agent/chunks/anthropic.js +75 -0
  320. package/vendor/agent/chunks/azure-openai-responses-BJLAM6CD.js +2 -0
  321. package/vendor/agent/chunks/bedrock-converse-stream.js +74 -0
  322. package/vendor/agent/chunks/chord-context.js +96 -0
  323. package/vendor/agent/chunks/chunk-4L3WN2XY.js +2 -0
  324. package/vendor/agent/chunks/chunk-6CYCI3EJ.js +12 -0
  325. package/vendor/agent/chunks/chunk-6R7UI4IY.js +2 -0
  326. package/vendor/agent/chunks/chunk-7ZC4UXSL.js +2 -0
  327. package/vendor/agent/chunks/chunk-AXIIZGTV.js +2 -0
  328. package/vendor/agent/chunks/chunk-CLNPYIDP.js +42 -0
  329. package/vendor/agent/chunks/chunk-DRQDKNXA.js +2 -0
  330. package/vendor/agent/chunks/chunk-GMWCTUPB.js +2 -0
  331. package/vendor/agent/chunks/chunk-IDDQWTHI.js +2 -0
  332. package/vendor/agent/chunks/chunk-JVUZSMYM.js +1548 -0
  333. package/vendor/agent/chunks/chunk-L2ZPNLYD.js +2 -0
  334. package/vendor/agent/chunks/chunk-NUHFSC37.js +25 -0
  335. package/vendor/agent/chunks/chunk-OUXLLA64.js +11 -0
  336. package/vendor/agent/chunks/chunk-PGXDEOVV.js +2 -0
  337. package/vendor/agent/chunks/chunk-PJACZCIR.js +2 -0
  338. package/vendor/agent/chunks/chunk-PRRNXB7S.js +2 -0
  339. package/vendor/agent/chunks/chunk-TEPMHNKQ.js +2 -0
  340. package/vendor/agent/chunks/chunk-TRDNDS6A.js +4 -0
  341. package/vendor/agent/chunks/chunk-U6ZMQKGD.js +50 -0
  342. package/vendor/agent/chunks/chunk-UAQELI3K.js +2 -0
  343. package/vendor/agent/chunks/chunk-USJZMAIR.js +2 -0
  344. package/vendor/agent/chunks/chunk-XDUWOHPL.js +6 -0
  345. package/vendor/agent/chunks/chunk-XNGRGP62.js +2 -0
  346. package/vendor/agent/chunks/github-copilot.js +2 -0
  347. package/vendor/agent/chunks/google-generative-ai-Q2K7FBDI.js +2 -0
  348. package/vendor/agent/chunks/google-vertex-7UJBF4S7.js +2 -0
  349. package/vendor/agent/chunks/https-proxy-agent-2VXB7436.js +13 -0
  350. package/vendor/agent/chunks/image-resize-worker.js +2 -0
  351. package/vendor/agent/chunks/kimi-coding.js +2 -0
  352. package/vendor/agent/chunks/lib-HGQMNYVT.js +9 -0
  353. package/vendor/agent/chunks/mistral-conversations-4M6BZBX6.js +5 -0
  354. package/vendor/agent/chunks/multipart-parser-SQ6GDJL7.js +3 -0
  355. package/vendor/agent/chunks/node-KG362ECQ.js +16 -0
  356. package/vendor/agent/chunks/node-TWVVAGPO.js +2 -0
  357. package/vendor/agent/chunks/openai-codex-responses-R6VYZTVY.js +10 -0
  358. package/vendor/agent/chunks/openai-codex.js +75 -0
  359. package/vendor/agent/chunks/openai-completions-EKZT2IH2.js +7 -0
  360. package/vendor/agent/chunks/openai-responses-TFDINO6W.js +2 -0
  361. package/vendor/agent/chunks/openrouter-images-N4XVVFSD.js +2 -0
  362. package/vendor/agent/chunks/openrouter.js +75 -0
  363. package/vendor/agent/chunks/pi-messages-PML4SPVJ.js +8 -0
  364. package/vendor/agent/chunks/radius.js +75 -0
  365. package/vendor/agent/chunks/src-UK77SYZE.js +4 -0
  366. package/vendor/agent/chunks/xai.js +2 -0
  367. package/vendor/agent/cli.js +3 -0
  368. package/vendor/agent/dist/modes/interactive/theme/dark.json +91 -0
  369. package/vendor/agent/dist/modes/interactive/theme/light.json +90 -0
  370. package/vendor/agent/dist/modes/interactive/theme/theme-schema.json +356 -0
  371. package/vendor/agent/index.js +2 -0
  372. package/vendor/agent/modes/interactive/theme/dark.json +91 -0
  373. package/vendor/agent/modes/interactive/theme/light.json +90 -0
  374. package/vendor/agent/modes/interactive/theme/theme-schema.json +356 -0
  375. package/vendor/agent/package.json +14 -0
  376. package/vendor/agent/rpc-entry.js +3 -0
  377. package/vendor/agent/theme/dark.json +91 -0
  378. package/vendor/agent/theme/light.json +90 -0
  379. package/vendor/agent/theme/theme-schema.json +356 -0
@@ -0,0 +1,65 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { validateDesign, validatePlanning, inspectPlanning, recordPlanReview, approvePlanning, planningStatus, startImplementation } from '../lib/planning.mjs';
4
+
5
+ export function designedState() {
6
+ return { runId: 'run', revision: 1, plan: {
7
+ goal: 'Tested design', verification: 'required', artifacts: ['.'], steps: ['Implement'],
8
+ acceptance: [{ requirement: 'Correct result', checks: ['unit'] }],
9
+ checks: [{ id: 'unit', kind: 'test', argv: ['node', '--version'], timeoutSeconds: 10 }],
10
+ design: { components: [{ id: 'policy', responsibility: 'Compute result', excludes: ['Persistence'], layer: 'core', dependencies: [], externalDependencies: [], requirements: ['Correct result'] }], ports: [], risks: [], scenarios: [
11
+ { id: 'valid', requirement: 'Correct result', kind: 'positive', path: ['policy'], input: 'valid input', expected: 'correct value', assertion: 'value equals expected', checks: ['unit'] },
12
+ { id: 'invalid', requirement: 'Correct result', kind: 'failure', path: ['policy'], input: 'invalid input', expected: 'typed error', assertion: 'throws specified error', checks: ['unit'] },
13
+ ] },
14
+ } };
15
+ }
16
+ export function reviewed(state, hash = 'source') {
17
+ validatePlanning(state, hash);
18
+ const capture = inspectPlanning(state, hash);
19
+ recordPlanReview(state, hash, { captureId: capture.id, challenges: ['Who owns failure behavior? Policy owns typed errors, not persistence.'], walkthroughs: state.plan.design.scenarios.map(s => ({ scenario: s.id, trace: `${s.input} -> policy -> ${s.expected}`, assertion: s.assertion })), findings: [], limitations: ['Model-authored review does not establish implementation correctness.'] });
20
+ approvePlanning(state, hash);
21
+ return state;
22
+ }
23
+
24
+ test('design validation requires ownership, negative scenarios and explicit DI contracts', () => {
25
+ const state = designedState();
26
+ assert.deepEqual(validateDesign(state.plan), []);
27
+ state.plan.design.scenarios.pop();
28
+ assert.ok(validateDesign(state.plan).some(f => f.code === 'coverage'));
29
+ state.plan.design.components[0].externalDependencies = ['http'];
30
+ assert.ok(validateDesign(state.plan).some(f => f.code === 'injection'));
31
+ state.plan.design.risks.push({ id: 'unknown', description: 'Unknown API', status: 'open', blocking: true });
32
+ assert.ok(validateDesign(state.plan).some(f => f.code === 'risk-open'));
33
+ });
34
+ test('approval cannot bypass validation, scenarios, blocking findings or fresh identity', () => {
35
+ const state = designedState();
36
+ assert.throws(() => approvePlanning(state, 'source'), /validation/);
37
+ validatePlanning(state, 'source');
38
+ const capture = inspectPlanning(state, 'source');
39
+ assert.throws(() => recordPlanReview(state, 'source', { captureId: capture.id, challenges: ['challenge'], findings: [], walkthroughs: [], limitations: [] }), /every scenario/);
40
+ reviewed(state);
41
+ assert.equal(planningStatus(state, 'source').locked, false);
42
+ assert.equal(planningStatus(state, 'changed').locked, true);
43
+ state.revision++;
44
+ assert.equal(planningStatus(state, 'source').locked, true);
45
+ });
46
+ test('planned implementation edits do not deadlock approval; design changes relock', () => {
47
+ const state = reviewed(designedState());
48
+ startImplementation(state, 'source');
49
+ assert.equal(planningStatus(state, 'edited-source').locked, false);
50
+ state.plan.goal = 'Changed scope';
51
+ assert.equal(planningStatus(state, 'edited-source').locked, true);
52
+ assert.throws(() => startImplementation(state, 'edited-source'), /locked/);
53
+ });
54
+ test('informational plans and forged receipt fields cannot grant mutation', () => {
55
+ const state = reviewed(designedState());
56
+ state.plan.verification = 'none';
57
+ assert.throws(() => approvePlanning(state, 'source'), /Informational/);
58
+ assert.equal(planningStatus(state, 'source').locked, true);
59
+ });
60
+ test('malformed designs fail diagnostically and component cycles are blocked', () => {
61
+ for (const design of [null, [], {}, { components: [null], ports: [], scenarios: [], risks: [] }]) assert.ok(validateDesign({ ...designedState().plan, design }).length);
62
+ const state = designedState();
63
+ state.plan.design.components[0].dependencies = ['policy'];
64
+ assert.ok(validateDesign(state.plan).some(f => f.code === 'cycle'));
65
+ });
@@ -0,0 +1,38 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { planningGuide, planningNext } from '../lib/planning-guide.mjs';
4
+ import { validateDesign } from '../lib/planning.mjs';
5
+ import { validatePlan } from '../lib/delivery.mjs';
6
+ import { newDiscovery, restoreDiscovery, discoveryEvent, discoveryCheckpoint } from '../lib/planning-discovery.mjs';
7
+ import { createJevAdvisor } from '../lib/jev-advisor.mjs';
8
+ import { createOpenRouterJev } from '../lib/openrouter-jev.mjs';
9
+
10
+ test('on-demand guide passes structural validators; reference errors remain blocking', () => {
11
+ const guide=planningGuide();assert.equal(guide.authority,'none');assert.ok(JSON.stringify(guide).length<6500);
12
+ const plan=validatePlan(guide.example,process.cwd());assert.deepEqual(validateDesign(plan),[]);
13
+ const bad=structuredClone(plan);bad.design.scenarios[0].path=['unknown'];bad.design.scenarios[0].checks=['missing'];bad.design.scenarios[0].requirement='rewritten';
14
+ const findings=validateDesign(bad);assert.ok(findings.some(f=>f.message.includes('unknown')&&f.message.includes('headers')));assert.ok(findings.some(f=>f.message.includes('missing')&&f.message.includes('headers')));assert.ok(findings.some(f=>f.message.includes('rewritten')));assert.ok(findings.every(f=>f.severity==='blocking'));
15
+ assert.deepEqual(planningNext({plan}, {findings}).references.requirements,plan.acceptance.map(a=>a.requirement));
16
+ const malformed={...plan,design:{components:'wrong',ports:{},scenarios:[],risks:[]}};
17
+ assert.deepEqual(planningNext({plan:malformed},{findings:validateDesign(malformed)}).references.components,[]);
18
+ });
19
+ test('checkpoint thresholds/restoration are bounded advice, never permission',()=>{
20
+ let state=newDiscovery(true);for(let i=0;i<7;i++)state=discoveryEvent(state,'read');assert.equal(discoveryCheckpoint(state),undefined);
21
+ state=discoveryEvent(state,'read');assert.match(discoveryCheckpoint(state),/first concrete plan/);
22
+ for(let i=0;i<8;i++)state=discoveryEvent(state,'read');assert.match(discoveryCheckpoint(state),/exact unresolved question/);
23
+ let blocked=newDiscovery(true);blocked=discoveryEvent(discoveryEvent(blocked,'blocked'),'blocked');assert.match(discoveryCheckpoint(blocked),/2 blocked/);
24
+ assert.equal(discoveryCheckpoint(newDiscovery()),undefined);assert.deepEqual(discoveryEvent(newDiscovery(),'read'),newDiscovery());
25
+ assert.deepEqual(restoreDiscovery({version:1,active:true,reads:Infinity,blocked:-1,approval:'bogus'}),newDiscovery(true));
26
+ assert.equal(restoreDiscovery({version:1,active:true,reads:1000000}).reads,100000);
27
+ });
28
+ test('both Jev adapters disclose check definitions, outputs and assumptions without mutation',async()=>{
29
+ const plan={...planningGuide().example,outputs:['README.md']};const before=structuredClone(plan);
30
+ for(const [factory,model] of [[createJevAdvisor,'jev-1.13.0'],[createOpenRouterJev,'typesafe/jev-1.13']]){
31
+ let body;
32
+ const advisor=factory({apiKey:'dummy',fetchImpl:async(_url,options)=>{body=JSON.parse(options.body);return Response.json({model,answers:Object.fromEntries(['srp','di','tests'].map(k=>[k,{type:'noul',noul:.1}]))});}});
33
+ await advisor.classify(plan);
34
+ for(const key of ['checks','outputs','assumptions','acceptance'])assert.deepEqual(body.state[key],plan[key]);
35
+ assert.match(body.questions.tests.instructions,/commands are declarations/);assert.deepEqual(plan,before);
36
+ await assert.rejects(advisor.classify({...plan,checks:[{argv:['x'.repeat(65000)]}]}),/64KB/);
37
+ }
38
+ });
@@ -0,0 +1,33 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { planningView, planningViewLines } from '../lib/planning-view.mjs';
4
+ import { approveFixture, fixtureDesign } from './helpers/tested-design.mjs';
5
+ import { evidenceIdentity, fingerprint } from '../lib/delivery.mjs';
6
+ import { mkdtempSync, rmSync } from 'node:fs';
7
+ import { tmpdir } from 'node:os';
8
+ import { join } from 'node:path';
9
+
10
+ test('shared views distinguish locked, proposed, observed, failed and stale receipts', t => {
11
+ const cwd = mkdtempSync(join(tmpdir(), 'planning-view-'));
12
+ t.after(() => rmSync(cwd, { recursive: true, force: true }));
13
+ const state = { runId: 'run', revision: 1, status: 'implementing', evidence: {}, plan: { goal: 'View', steps: ['Work'], acceptance: [{ requirement: 'Works', checks: ['unit'] }], checks: [{ id: 'unit', kind: 'test', argv: ['node', '--version'], timeoutSeconds: 10 }] } };
14
+ state.plan.design = fixtureDesign(state.plan);
15
+ let view = planningView(state, fingerprint(cwd, ['.']));
16
+ assert.ok(view.gate.locked);
17
+ assert.equal(view.current.length, 0);
18
+ assert.equal(view.workflow.nodes.find(n => n.id === 'step0').status, 'blocked');
19
+ approveFixture(state, cwd);
20
+ const hash = fingerprint(cwd, ['.']);
21
+ state.stepStatus = { step0: 'active' };
22
+ view = planningView(state, hash);
23
+ assert.equal(view.current[0].id, 'step0');
24
+ assert.match(planningViewLines(view, 'architecture').join('\n'), /Production behavior/);
25
+ const check = state.plan.checks[0];
26
+ state.evidence.unit = { ...evidenceIdentity(state, check), executedRevision: 1, fingerprint: hash, passed: false, code: 1 };
27
+ assert.equal(planningView(state, hash).evidence[0].status, 'fail');
28
+ state.evidence.unit.passed = true;
29
+ assert.equal(planningView(state, hash).evidence[0].status, 'done');
30
+ assert.equal(planningView(state, 'changed').evidence[0].status, 'stale');
31
+ assert.match(planningViewLines(planningView(state, 'changed'), 'evidence').join('\n'), /stale/);
32
+ assert.equal(planningView(null, hash).workflow, null);
33
+ });
@@ -0,0 +1,223 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import {
7
+ validatePlan, revisePlan, fingerprint, evidenceIdentity, pendingChecks,
8
+ updateStepStatus, completionIssues, coverageReport, planD2, looksInformational, pendingRequirements,
9
+ } from '../lib/delivery.mjs';
10
+
11
+ function fixture(t) {
12
+ const cwd = mkdtempSync(join(tmpdir(), 'planning monorepo café '));
13
+ t.after(() => rmSync(cwd, { recursive: true, force: true }));
14
+ for (const name of ['api', 'client', 'shared']) {
15
+ mkdirSync(join(cwd, 'packages', name), { recursive: true });
16
+ writeFileSync(join(cwd, 'packages', name, 'index.js'), 'export const version = 1;');
17
+ }
18
+ const checks = ['unit', 'integration'].map(id => ({ id, kind: 'test', argv: ['node', '--test', `${id}.test.mjs`], timeoutSeconds: 60 }));
19
+ const plan = validatePlan({
20
+ goal: 'Migrate a public API while preserving existing clients', assumptions: ['Old clients must remain supported'],
21
+ artifacts: ['packages'],
22
+ steps: [
23
+ { id: 'contract', title: 'Capture the public contract', checks: ['unit'] },
24
+ { id: 'api', title: 'Implement compatible API', dependsOn: ['contract'] },
25
+ { id: 'client', title: 'Migrate client', dependsOn: ['contract'] },
26
+ { id: 'integration', title: 'Exercise both clients', dependsOn: ['api', 'client'], checks: ['integration'] },
27
+ ],
28
+ checks, acceptance: [{ requirement: 'Old and new clients both work', checks: ['unit', 'integration'] }],
29
+ }, cwd);
30
+ const state = { runId: 'task-a', revision: 1, plan, evidence: {}, stepStatus: {}, status: 'implementing' };
31
+ const hash = fingerprint(cwd, ['.']);
32
+ for (const check of checks) state.evidence[check.id] = { ...evidenceIdentity(state, check), passed: true, fingerprint: hash, logPath: `${check.id}.log` };
33
+ const revise = (patch, reason = 'Discovered an additional compatibility constraint') => revisePlan(state, patch, { cwd, hash: fingerprint(cwd, ['.']), reason });
34
+ return { cwd, state, hash, revise };
35
+ }
36
+
37
+ test('cross-package API migration: dependencies gate work and tests gate completion', t => {
38
+ const { state, cwd, hash } = fixture(t);
39
+ assert.throws(() => updateStepStatus(state, 'api', 'active'), /dependencies: contract/);
40
+ assert.throws(() => updateStepStatus(state, 'contract', 'done', 'stale'), /fresh passing checks/);
41
+ updateStepStatus(state, 'contract', 'done', hash);
42
+ updateStepStatus(state, 'api', 'active');
43
+ updateStepStatus(state, 'client', 'active'); // independent branches are allowed
44
+ updateStepStatus(state, 'api', 'done');
45
+ assert.throws(() => updateStepStatus(state, 'integration', 'done', hash), /dependencies: client/);
46
+ assert.deepEqual(completionIssues(state, cwd, hash).incompleteSteps, ['client', 'integration']);
47
+ updateStepStatus(state, 'client', 'done');
48
+ updateStepStatus(state, 'integration', 'done', hash);
49
+ assert.equal(completionIssues(state, cwd, hash).review.length, 1);
50
+ state.reviewEvidence = { runId: state.runId, revision: state.revision, fingerprint: hash };
51
+ assert.ok(Object.values(completionIssues(state, cwd, hash)).every(items => items.length === 0));
52
+ state.status = 'verified'; state.handoff = { review: 'old review' };
53
+ updateStepStatus(state, 'contract', 'pending');
54
+ assert.deepEqual(state.stepStatus, { step0: 'pending', step1: 'pending', step2: 'pending', step3: 'pending' });
55
+ assert.equal(state.status, 'implementing');
56
+ assert.equal(state.handoff, undefined);
57
+ });
58
+
59
+ test('rolling-wave refinement retains fresh evidence and stable progress through reorder', t => {
60
+ const { state, cwd, revise, hash } = fixture(t);
61
+ updateStepStatus(state, 'contract', 'done', hash);
62
+ updateStepStatus(state, 'api', 'done');
63
+ const old = structuredClone(state);
64
+ const steps = [state.plan.steps[2], state.plan.steps[0], state.plan.steps[1], state.plan.steps[3]];
65
+ const next = revise({ steps, assumptions: [...state.plan.assumptions, 'Client requires a compatibility adapter'] });
66
+ assert.equal(next.runId, state.runId);
67
+ assert.equal(next.revision, 2);
68
+ assert.deepEqual(pendingChecks(next, hash), []);
69
+ assert.equal(next.evidence.unit.originRevision, 1);
70
+ const legacy = revisePlan(next, { steps: ['Continue legacy labels'] }, { cwd, hash, reason: 'Change display shape' });
71
+ assert.equal(legacy.reviewRequired, true, 'switching labels cannot discard a structured task review obligation');
72
+ assert.equal(next.evidence.unit.logPath, state.evidence.unit.logPath);
73
+ assert.equal(next.stepStatus.step1, 'done');
74
+ assert.equal(next.stepStatus.step2, 'done');
75
+ assert.deepEqual(state, old, 'revision is pure, preserving the old report snapshot');
76
+ assert.deepEqual(next.revisions[0].plan, old.plan);
77
+ next.revisions[0].plan.goal = 'changed copy';
78
+ assert.equal(state.plan.goal, old.plan.goal);
79
+ });
80
+
81
+ test('source and shared config changes never become fresh via a revision', t => {
82
+ const { state, cwd, revise, hash } = fixture(t);
83
+ writeFileSync(join(cwd, 'package-lock.json'), '{"newDependency":true}');
84
+ const next = revise({ assumptions: ['Dependency resolution changed'] });
85
+ assert.deepEqual(pendingChecks(next, fingerprint(cwd, ['.'])), ['unit', 'integration']);
86
+ assert.equal(next.evidence.unit.revision, 1, 'stale evidence is retained without rebinding');
87
+ assert.deepEqual(pendingChecks(next, hash), ['unit', 'integration'], 'even reverting source needs an explicit fresh rebind or rerun');
88
+ assert.equal(state.evidence.unit.passed, true);
89
+ });
90
+
91
+ test('discovered migration risk resets changed step and downstream progress, not unrelated checks', t => {
92
+ const { state, revise, hash } = fixture(t);
93
+ for (const id of ['contract', 'api', 'client', 'integration']) updateStepStatus(state, id, 'done', hash);
94
+ const steps = structuredClone(state.plan.steps);
95
+ steps[0].title = 'Capture contract including rollback and retry behavior';
96
+ const next = revise({ steps });
97
+ assert.deepEqual(next.stepStatus, {});
98
+ assert.deepEqual(pendingChecks(next, hash), []);
99
+ const changedChecks = structuredClone(state.plan.checks);
100
+ changedChecks[0].argv.push('--test-name-pattern=rollback');
101
+ const changed = revise({ checks: changedChecks });
102
+ assert.deepEqual(pendingChecks(changed, hash), ['unit']);
103
+ assert.deepEqual(changed.stepStatus, {}, 'dependent completed steps must be reconsidered');
104
+ });
105
+
106
+ test('failed evidence cannot be washed away through multiple revisions or blocked resumption', t => {
107
+ const { state, cwd, hash, revise } = fixture(t);
108
+ state.evidence.unit.passed = false;
109
+ const next = revise({ checks: state.plan.checks.map(c => ({ ...c, timeoutSeconds: 90 })) });
110
+ assert.deepEqual(next.evidence, {});
111
+ assert.equal(next.verificationStarted, true);
112
+ next.status = 'blocked';
113
+ assert.throws(() => revisePlan(next, { verification: 'none', checks: [], acceptance: [], steps: ['Answer'] }, { cwd, hash, reason: 'Try downgrade' }), /Cannot downgrade/);
114
+ assert.throws(() => revisePlan(next, { acceptance: [{ requirement: 'Only compilation matters now', checks: ['unit'] }] }, { cwd, hash, reason: 'Try weaker scope' }), /remove acceptance/);
115
+ assert.equal(next.revisions[0].evidence.unit.passed, false, 'original failure remains auditable');
116
+ });
117
+
118
+ test('failed, stale and forged evidence is never rebound to a new revision', t => {
119
+ const { state, revise, hash } = fixture(t);
120
+ for (const mutation of [{ passed: false }, { runId: 'other' }, { revision: 99 }, { checkDigest: 'forged' }, { fingerprint: 'old' }]) {
121
+ const original = state.evidence.unit;
122
+ state.evidence.unit = { ...original, ...mutation };
123
+ const next = revise({ goal: state.plan.goal });
124
+ assert.deepEqual(pendingChecks(next, hash), ['unit']);
125
+ state.evidence.unit = original;
126
+ }
127
+ });
128
+
129
+ test('legacy step progress follows unique labels and resets ambiguous duplicates', t => {
130
+ const { state, revise } = fixture(t);
131
+ state.plan.steps = ['Inspect', 'Build', 'Review']; state.stepStatus = { step0: 'done', step1: 'active' };
132
+ const next = revise({ steps: ['New investigation', 'Inspect', 'Build', 'Review'] });
133
+ assert.deepEqual(next.stepStatus, { step1: 'done', step2: 'active' });
134
+ const ambiguous = revise({ steps: ['Inspect', 'Inspect', 'Review'] });
135
+ assert.deepEqual(ambiguous.stepStatus, {});
136
+ });
137
+
138
+ test('plan validation rejects malformed steps, missing references and dependency cycles', t => {
139
+ const { state, cwd } = fixture(t);
140
+ const base = state.plan;
141
+ for (const steps of [
142
+ [], [null], [{}], [' '], [{ id: 'bad id', title: 'X' }],
143
+ [{ id: 'x', title: 'X' }, { id: 'x', title: 'Y' }],
144
+ [{ id: 'x', title: 'X', dependsOn: ['missing'] }],
145
+ [{ id: 'x', title: 'X', dependsOn: ['x'] }],
146
+ [{ id: 'x', title: 'X', dependsOn: ['y'] }, { id: 'y', title: 'Y', dependsOn: ['x'] }],
147
+ [{ id: 'x', title: 'X', dependsOn: 'y' }],
148
+ [{ id: 'x', title: 'X', checks: ['missing'] }],
149
+ ]) assert.throws(() => validatePlan({ ...base, steps }, cwd));
150
+ for (const patch of [{ checks: [null] }, { checks: [{ ...base.checks[0], argv: 'node' }] }, { acceptance: [null] }, { artifacts: '.' }]) {
151
+ assert.throws(() => validatePlan({ ...base, ...patch }, cwd));
152
+ }
153
+ assert.throws(() => validatePlan({ ...base, acceptance: [...base.acceptance, ...base.acceptance] }, cwd), /must be unique/);
154
+ assert.throws(() => revisePlan(state, {}, { cwd, reason: 'x' }), /nonempty/);
155
+ assert.throws(() => revisePlan(state, { evidence: {} }, { cwd, reason: 'x' }), /Unknown/);
156
+ assert.throws(() => revisePlan(state, { goal: 'x' }, { cwd, reason: ' ' }), /reason/);
157
+ });
158
+
159
+ test('D2 reflects branching dependencies rather than inventing a linear sequence', t => {
160
+ const { state } = fixture(t);
161
+ const graph = planD2(state.plan);
162
+ assert.match(graph, /step0 -> step1/);
163
+ assert.match(graph, /step0 -> step2/);
164
+ assert.match(graph, /step1 -> step3/);
165
+ assert.match(graph, /step2 -> step3/);
166
+ assert.match(graph, /step3 -> verify/);
167
+ assert.doesNotMatch(graph, /step1 -> step2/);
168
+ assert.doesNotMatch(graph, /\[object Object\]/);
169
+ });
170
+
171
+ test('coverage requires every mapped check, not just the easiest passing one', t => {
172
+ const { state, hash } = fixture(t);
173
+ assert.equal(coverageReport(state.plan, state.evidence, hash, state)[0].covered, true);
174
+ state.evidence.integration.passed = false;
175
+ const coverage = coverageReport(state.plan, state.evidence, hash, state)[0];
176
+ assert.equal(coverage.fresh, 1);
177
+ assert.equal(coverage.failing, 1);
178
+ assert.equal(coverage.covered, false);
179
+ });
180
+
181
+ test('declared reports must be files and unfinished work prevents completion', t => {
182
+ const { state, cwd, hash } = fixture(t);
183
+ state.plan.outputs = ['artifacts/migration-report.json'];
184
+ mkdirSync(join(cwd, 'artifacts', 'migration-report.json'), { recursive: true });
185
+ assert.deepEqual(completionIssues(state, cwd, hash).missingOutputs, state.plan.outputs);
186
+ rmSync(join(cwd, 'artifacts', 'migration-report.json'), { recursive: true });
187
+ writeFileSync(join(cwd, 'artifacts', 'migration-report.json'), '{}');
188
+ assert.deepEqual(completionIssues(state, cwd, hash).missingOutputs, []);
189
+ assert.equal(completionIssues(state, cwd, hash).incompleteSteps.length, 4);
190
+ });
191
+
192
+ test('polite implementation requests are not informational receipts', () => {
193
+ for (const prompt of ['Could you fix this?', 'Can you refactor the billing package?', 'Please migrate the schema?', 'Could you identify and fix flaws in the planning system?']) {
194
+ assert.equal(looksInformational(prompt), false, prompt);
195
+ }
196
+ assert.equal(looksInformational('Why is the billing package failing?'), true);
197
+ });
198
+
199
+ test('new and remapped requirements need executions after their introduction, not rebound history', t => {
200
+ const { state, cwd, hash, revise } = fixture(t);
201
+ const acceptance = [...state.plan.acceptance, { requirement: 'Safe cancellation', checks: ['unit'] }];
202
+ const next = revise({ acceptance });
203
+ assert.deepEqual(pendingChecks(next, hash), []);
204
+ assert.deepEqual(pendingRequirements(next, hash), ['Safe cancellation']);
205
+ assert.equal(coverageReport(next.plan, next.evidence, hash, next)[1].covered, false);
206
+ const again = revisePlan(next, { assumptions: ['New discovery'] }, { cwd, hash, reason: 'Refine' });
207
+ assert.deepEqual(pendingRequirements(again, hash), ['Safe cancellation']);
208
+ again.evidence.unit = { ...evidenceIdentity(again, again.plan.checks[0]), passed: true, fingerprint: hash, executedRevision: again.revision };
209
+ assert.deepEqual(pendingRequirements(again, hash), []);
210
+ const remapped = revisePlan(again, { acceptance: [acceptance[0], { ...acceptance[1], checks: ['integration'] }] }, { cwd, hash, reason: 'Use integration assertions' });
211
+ assert.deepEqual(pendingRequirements(remapped, hash), ['Safe cancellation']);
212
+ });
213
+
214
+ test('regression capture is an intentionally red milestone, never a passing final gate', t => {
215
+ const { state, cwd, hash } = fixture(t);
216
+ state.plan.steps = [{ id: 'red', title: 'Capture failing behavior', kind: 'regression', checks: [] }];
217
+ validatePlan(state.plan, cwd);
218
+ state.evidence.unit.passed = false;
219
+ updateStepStatus(state, 'red', 'done', hash);
220
+ assert.deepEqual(completionIssues(state, cwd, hash).pendingChecks, ['unit']);
221
+ assert.throws(() => validatePlan({ ...state.plan, steps: [{ ...state.plan.steps[0], checks: ['unit'] }] }, cwd), /must not require green/);
222
+ assert.throws(() => validatePlan({ ...state.plan, steps: [{ ...state.plan.steps[0], kind: 'anything' }] }, cwd), /Step kind/);
223
+ });
@@ -0,0 +1,21 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { probeDiagnostics } from '../lib/probe-diagnostics.mjs';
4
+ test('repeated module failures get bounded actionable advice without hiding results', () => {
5
+ const event = { toolName: 'bash', isError: false, content: [{ type: 'text', text: 'ERR_MODULE_NOT_FOUND: tsx\nexit code: 1' }] };
6
+ const before = structuredClone(event);
7
+ let result = probeDiagnostics(undefined, event); assert.equal(result.message, undefined);
8
+ result = probeDiagnostics(result.state, event); assert.match(result.message, /ONE changed hypothesis/);
9
+ assert.match(result.message, /original exit code/); assert.match(result.message, /no execution or approval authority/);
10
+ result = probeDiagnostics(result.state, event); assert.equal(result.message, undefined);
11
+ result = probeDiagnostics(result.state, event); assert.ok(result.message);
12
+ result = probeDiagnostics(result.state, event); assert.equal(result.message, undefined);
13
+ assert.deepEqual(event, before);
14
+ assert.deepEqual(probeDiagnostics(result.state, { toolName: 'bash', content: [{ type: 'text', text: 'tests passed' }] }).state, { failures: 0, family: null });
15
+ });
16
+ test('ordinary reads preserve diagnostic state, check failures count, unrelated failures restart', () => {
17
+ const prior = { failures: 1, family: 'module-loader' };
18
+ assert.equal(probeDiagnostics(prior, { toolName: 'read' }).state, prior);
19
+ const check = probeDiagnostics(prior, { toolName: 'delivery_check', isError: true, content: [{ type: 'text', text: '{"passed":false,"outputTail":"assertion failed"}' }] });
20
+ assert.equal(check.state.family, 'execution'); assert.equal(check.state.failures, 1);
21
+ });