gsd-pi 2.82.0-dev.c22380fc3 → 2.82.0-dev.dfbc5f58f

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 (287) hide show
  1. package/README.md +4 -3
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/GSD-WORKFLOW.md +10 -1
  4. package/dist/resources/extensions/claude-code-cli/partial-builder.js +2 -1
  5. package/dist/resources/extensions/cmux/index.js +5 -0
  6. package/dist/resources/extensions/gsd/auto/infra-errors.js +9 -3
  7. package/dist/resources/extensions/gsd/auto/loop.js +5 -5
  8. package/dist/resources/extensions/gsd/auto/orchestrator.js +11 -0
  9. package/dist/resources/extensions/gsd/auto/phases.js +8 -1
  10. package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +12 -0
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +13 -6
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +2 -0
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +233 -127
  14. package/dist/resources/extensions/gsd/auto-prompts.js +2 -2
  15. package/dist/resources/extensions/gsd/auto-recovery.js +31 -1
  16. package/dist/resources/extensions/gsd/auto-start.js +85 -12
  17. package/dist/resources/extensions/gsd/auto-verification.js +28 -22
  18. package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
  19. package/dist/resources/extensions/gsd/auto.js +30 -3
  20. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +4 -1
  21. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +9 -8
  22. package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +21 -9
  23. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -2
  24. package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
  25. package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
  26. package/dist/resources/extensions/gsd/commands/handlers/core.js +37 -0
  27. package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
  28. package/dist/resources/extensions/gsd/crash-recovery.js +31 -5
  29. package/dist/resources/extensions/gsd/db/unit-dispatches.js +3 -2
  30. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -2
  31. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +28 -11
  32. package/dist/resources/extensions/gsd/doctor.js +2 -28
  33. package/dist/resources/extensions/gsd/export-html.js +27 -425
  34. package/dist/resources/extensions/gsd/git-service.js +39 -1
  35. package/dist/resources/extensions/gsd/gsd-db.js +1 -0
  36. package/dist/resources/extensions/gsd/guided-flow.js +6 -0
  37. package/dist/resources/extensions/gsd/migrate/parsers.js +10 -0
  38. package/dist/resources/extensions/gsd/milestone-actions.js +11 -4
  39. package/dist/resources/extensions/gsd/native-git-bridge.js +48 -12
  40. package/dist/resources/extensions/gsd/post-execution-checks.js +73 -2
  41. package/dist/resources/extensions/gsd/pre-execution-checks.js +28 -1
  42. package/dist/resources/extensions/gsd/prompt-loader.js +1 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  44. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  45. package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +6 -1
  46. package/dist/resources/extensions/gsd/status-guards.js +4 -0
  47. package/dist/resources/extensions/gsd/templates/plan.md +8 -5
  48. package/dist/resources/extensions/gsd/templates/task-plan.md +4 -2
  49. package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -8
  50. package/dist/resources/extensions/gsd/tools/complete-slice.js +6 -8
  51. package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -1
  52. package/dist/resources/extensions/gsd/tools/plan-slice.js +89 -14
  53. package/dist/resources/extensions/gsd/unit-context-manifest.js +32 -10
  54. package/dist/resources/extensions/gsd/validation.js +23 -1
  55. package/dist/resources/extensions/gsd/verification-gate.js +68 -7
  56. package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
  57. package/dist/resources/extensions/gsd/workflow-projections.js +6 -8
  58. package/dist/resources/extensions/gsd/worktree-lifecycle.js +33 -8
  59. package/dist/resources/extensions/shared/html-shell.js +388 -0
  60. package/dist/resources/extensions/subagent/index.js +448 -78
  61. package/dist/resources/extensions/subagent/launch.js +77 -0
  62. package/dist/resources/extensions/subagent/run-store.js +148 -0
  63. package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
  64. package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
  65. package/dist/resources/extensions/visual-brief/index.js +5 -0
  66. package/dist/resources/extensions/visual-brief/page-contract.js +124 -0
  67. package/dist/resources/extensions/visual-brief/prompts.js +140 -0
  68. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  69. package/dist/web/standalone/.next/BUILD_ID +1 -1
  70. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  71. package/dist/web/standalone/.next/build-manifest.json +3 -3
  72. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  73. package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
  74. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  84. package/dist/web/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -7
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -5
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
  94. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/index.html +1 -1
  96. package/dist/web/standalone/.next/server/app/index.rsc +4 -7
  97. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -7
  99. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -5
  101. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -5
  102. package/dist/web/standalone/.next/server/app/page.js +2 -2
  103. package/dist/web/standalone/.next/server/app/page.js.nft.json +1 -1
  104. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  106. package/dist/web/standalone/.next/server/chunks/4266.js +2 -0
  107. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  111. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  112. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  113. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  114. package/dist/web/standalone/.next/static/chunks/2973.33f26573894b6153.js +2 -0
  115. package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
  116. package/dist/web/standalone/.next/static/chunks/app/layout-8c10ec293ae0f1d5.js +1 -0
  117. package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-9a4db269f9ed63ad.js} +1 -1
  118. package/dist/web/standalone/.next/static/css/746ee28c929d1880.css +1 -0
  119. package/package.json +4 -4
  120. package/packages/mcp-server/src/workflow-tools.test.ts +1 -1
  121. package/packages/native/tsconfig.json +2 -1
  122. package/packages/native/tsconfig.tsbuildinfo +1 -1
  123. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  124. package/packages/pi-ai/dist/providers/openai-codex-responses.js +82 -1
  125. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  126. package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts +2 -0
  127. package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts.map +1 -0
  128. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +52 -0
  129. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -0
  130. package/packages/pi-ai/dist/providers/simple-options.d.ts +2 -4
  131. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/providers/simple-options.js +5 -6
  133. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  134. package/packages/pi-ai/dist/providers/simple-options.test.d.ts +2 -0
  135. package/packages/pi-ai/dist/providers/simple-options.test.d.ts.map +1 -0
  136. package/packages/pi-ai/dist/providers/simple-options.test.js +50 -0
  137. package/packages/pi-ai/dist/providers/simple-options.test.js.map +1 -0
  138. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +63 -0
  139. package/packages/pi-ai/src/providers/openai-codex-responses.ts +91 -1
  140. package/packages/pi-ai/src/providers/simple-options.test.ts +60 -0
  141. package/packages/pi-ai/src/providers/simple-options.ts +5 -6
  142. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  143. package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts +2 -0
  144. package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts.map +1 -0
  145. package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js +66 -0
  146. package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js.map +1 -0
  147. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  148. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  149. package/packages/pi-coding-agent/src/core/agent-session-thinking-level.test.ts +79 -0
  150. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  151. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  152. package/src/resources/GSD-WORKFLOW.md +10 -1
  153. package/src/resources/extensions/claude-code-cli/partial-builder.ts +2 -1
  154. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +19 -2
  155. package/src/resources/extensions/cmux/index.ts +6 -0
  156. package/src/resources/extensions/gsd/auto/contracts.ts +14 -6
  157. package/src/resources/extensions/gsd/auto/infra-errors.ts +9 -3
  158. package/src/resources/extensions/gsd/auto/loop.ts +8 -5
  159. package/src/resources/extensions/gsd/auto/orchestrator.ts +11 -0
  160. package/src/resources/extensions/gsd/auto/phases.ts +7 -1
  161. package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +13 -0
  162. package/src/resources/extensions/gsd/auto-dispatch.ts +14 -6
  163. package/src/resources/extensions/gsd/auto-model-selection.ts +2 -1
  164. package/src/resources/extensions/gsd/auto-post-unit.ts +266 -139
  165. package/src/resources/extensions/gsd/auto-prompts.ts +2 -2
  166. package/src/resources/extensions/gsd/auto-recovery.ts +29 -0
  167. package/src/resources/extensions/gsd/auto-start.ts +92 -9
  168. package/src/resources/extensions/gsd/auto-verification.ts +36 -34
  169. package/src/resources/extensions/gsd/auto-worktree.ts +119 -1
  170. package/src/resources/extensions/gsd/auto.ts +32 -3
  171. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +6 -1
  172. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +9 -8
  173. package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +19 -7
  174. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +19 -3
  175. package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
  176. package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
  177. package/src/resources/extensions/gsd/commands/handlers/core.ts +40 -0
  178. package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
  179. package/src/resources/extensions/gsd/crash-recovery.ts +30 -4
  180. package/src/resources/extensions/gsd/db/unit-dispatches.ts +4 -3
  181. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -2
  182. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +25 -13
  183. package/src/resources/extensions/gsd/doctor.ts +2 -27
  184. package/src/resources/extensions/gsd/export-html.ts +27 -427
  185. package/src/resources/extensions/gsd/git-service.ts +45 -1
  186. package/src/resources/extensions/gsd/gsd-db.ts +3 -0
  187. package/src/resources/extensions/gsd/guided-flow.ts +6 -0
  188. package/src/resources/extensions/gsd/migrate/parsers.ts +11 -0
  189. package/src/resources/extensions/gsd/milestone-actions.ts +10 -4
  190. package/src/resources/extensions/gsd/native-git-bridge.ts +54 -12
  191. package/src/resources/extensions/gsd/post-execution-checks.ts +87 -2
  192. package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -1
  193. package/src/resources/extensions/gsd/prompt-loader.ts +1 -1
  194. package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  195. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  196. package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +8 -1
  197. package/src/resources/extensions/gsd/status-guards.ts +5 -0
  198. package/src/resources/extensions/gsd/templates/plan.md +8 -5
  199. package/src/resources/extensions/gsd/templates/task-plan.md +4 -2
  200. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +116 -0
  201. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +54 -0
  202. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +80 -1
  203. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -6
  204. package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +12 -1
  205. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +15 -1
  206. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +1 -0
  207. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +69 -1
  208. package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
  209. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
  210. package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
  211. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +4 -1
  212. package/src/resources/extensions/gsd/tests/complete-task.test.ts +3 -1
  213. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -2
  214. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +59 -2
  215. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +39 -0
  216. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  217. package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
  218. package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +8 -0
  219. package/src/resources/extensions/gsd/tests/guided-flow.test.ts +21 -0
  220. package/src/resources/extensions/gsd/tests/hook-model-resolution.test.ts +5 -0
  221. package/src/resources/extensions/gsd/tests/infra-error.test.ts +2 -2
  222. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +9 -0
  223. package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +20 -0
  224. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +103 -1
  225. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +6 -1
  226. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +24 -1
  227. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +63 -2
  228. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +121 -1
  229. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +55 -1
  230. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +26 -0
  231. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +225 -1
  232. package/src/resources/extensions/gsd/tests/plan-task.test.ts +17 -0
  233. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +79 -1
  234. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +86 -0
  235. package/src/resources/extensions/gsd/tests/post-unit-git-failure.test.ts +1 -1
  236. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +53 -0
  237. package/src/resources/extensions/gsd/tests/prompt-loader.test.ts +23 -0
  238. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +46 -2
  239. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +10 -0
  240. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +31 -1
  241. package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +64 -1
  242. package/src/resources/extensions/gsd/tests/summary-render-parity.test.ts +7 -3
  243. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +86 -7
  244. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +110 -1
  245. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
  246. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +1 -1
  247. package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +21 -1
  248. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +1 -1
  249. package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +39 -0
  250. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +64 -12
  251. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +54 -0
  252. package/src/resources/extensions/gsd/tools/complete-milestone.ts +8 -10
  253. package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -8
  254. package/src/resources/extensions/gsd/tools/plan-milestone.ts +5 -1
  255. package/src/resources/extensions/gsd/tools/plan-slice.ts +98 -12
  256. package/src/resources/extensions/gsd/types.ts +1 -1
  257. package/src/resources/extensions/gsd/unit-context-manifest.ts +47 -11
  258. package/src/resources/extensions/gsd/validation.ts +23 -1
  259. package/src/resources/extensions/gsd/verification-gate.ts +78 -6
  260. package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
  261. package/src/resources/extensions/gsd/workflow-projections.ts +6 -8
  262. package/src/resources/extensions/gsd/worktree-lifecycle.ts +41 -8
  263. package/src/resources/extensions/shared/html-shell.ts +412 -0
  264. package/src/resources/extensions/subagent/index.ts +567 -103
  265. package/src/resources/extensions/subagent/launch.ts +131 -0
  266. package/src/resources/extensions/subagent/run-store.ts +218 -0
  267. package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
  268. package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
  269. package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
  270. package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
  271. package/src/resources/extensions/visual-brief/index.ts +8 -0
  272. package/src/resources/extensions/visual-brief/page-contract.ts +136 -0
  273. package/src/resources/extensions/visual-brief/prompts.ts +183 -0
  274. package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +212 -0
  275. package/dist/web/standalone/.next/server/chunks/5822.js +0 -2
  276. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
  277. package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +0 -1
  278. package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
  279. package/dist/web/standalone/.next/static/css/de70bee13400563f.css +0 -1
  280. package/dist/web/standalone/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  281. package/dist/web/standalone/.next/static/media/747892c23ea88013-s.woff2 +0 -0
  282. package/dist/web/standalone/.next/static/media/8d697b304b401681-s.woff2 +0 -0
  283. package/dist/web/standalone/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  284. package/dist/web/standalone/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
  285. package/dist/web/standalone/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  286. /package/dist/web/standalone/.next/static/{Wop3A7KRGyR06H3rla_1- → q0WYuDVbHeFFYbdd-fei2}/_buildManifest.js +0 -0
  287. /package/dist/web/standalone/.next/static/{Wop3A7KRGyR06H3rla_1- → q0WYuDVbHeFFYbdd-fei2}/_ssgManifest.js +0 -0
@@ -127,6 +127,61 @@ import { b } from './b';
127
127
  assert.equal(imports.length, 2);
128
128
  });
129
129
 
130
+ test("ignores import-looking string literals in test fixtures", () => {
131
+ const source = `
132
+ const rewritten = source.replace(
133
+ 'import { normalizeZagrebBusinessDeadline } from "./cutoff";',
134
+ 'const helper = true;'
135
+ );
136
+
137
+ import realThing from "./real-thing";
138
+ `;
139
+ const imports = extractRelativeImports(source);
140
+ assert.deepEqual(imports, [
141
+ { importPath: "./real-thing", lineNum: 7 },
142
+ ]);
143
+ });
144
+
145
+ test("ignores import-looking lines inside template literals", () => {
146
+ const source = [
147
+ "const fixture = `",
148
+ "import missingThing from './missing-thing';",
149
+ "`;",
150
+ "",
151
+ "import realThing from './real-thing';",
152
+ ].join("\n");
153
+ const imports = extractRelativeImports(source);
154
+ assert.deepEqual(imports, [
155
+ { importPath: "./real-thing", lineNum: 5 },
156
+ ]);
157
+ });
158
+
159
+ test("ignores require() inside string literals", () => {
160
+ const source = [
161
+ 'const fixture = "const x = require(\'./missing\');";',
162
+ "const otherFixture = 'const y = require(\"./also-missing\");';",
163
+ "const real = require('./real');",
164
+ ].join("\n");
165
+ const imports = extractRelativeImports(source);
166
+ assert.deepEqual(imports, [
167
+ { importPath: "./real", lineNum: 3 },
168
+ ]);
169
+ });
170
+
171
+ test("ignores require() inside template literals", () => {
172
+ const source = [
173
+ "const fixture = `",
174
+ "const x = require('./missing');",
175
+ "`;",
176
+ "",
177
+ "const real = require('./real');",
178
+ ].join("\n");
179
+ const imports = extractRelativeImports(source);
180
+ assert.deepEqual(imports, [
181
+ { importPath: "./real", lineNum: 5 },
182
+ ]);
183
+ });
184
+
130
185
  test("handles empty source", () => {
131
186
  const imports = extractRelativeImports("");
132
187
  assert.deepEqual(imports, []);
@@ -810,6 +865,37 @@ describe("runPostExecutionChecks", () => {
810
865
  }
811
866
  });
812
867
 
868
+ test("does not fail on import-looking strings in task key files", () => {
869
+ tempDir = join(tmpdir(), `post-exec-test-${Date.now()}`);
870
+ mkdirSync(tempDir, { recursive: true });
871
+ mkdirSync(join(tempDir, "tests"), { recursive: true });
872
+ writeFileSync(join(tempDir, "tests", "real-thing.ts"), "export default true;");
873
+ writeFileSync(
874
+ join(tempDir, "tests", "source-verifier.test.ts"),
875
+ `
876
+ const rewritten = source.replace(
877
+ 'import { normalizeZagrebBusinessDeadline } from "./cutoff";',
878
+ 'const helper = true;'
879
+ );
880
+
881
+ import realThing from "./real-thing";
882
+ assert.ok(realThing);
883
+ `
884
+ );
885
+
886
+ try {
887
+ const task = createTask({
888
+ id: "T03",
889
+ key_files: ["tests/source-verifier.test.ts"],
890
+ });
891
+ const result = runPostExecutionChecks(task, [], tempDir);
892
+ assert.equal(result.status, "pass");
893
+ assert.deepEqual(result.checks, []);
894
+ } finally {
895
+ rmSync(tempDir, { recursive: true, force: true });
896
+ }
897
+ });
898
+
813
899
  test("returns fail status when blocking failure exists", () => {
814
900
  tempDir = join(tmpdir(), `post-exec-test-${Date.now()}`);
815
901
  mkdirSync(tempDir, { recursive: true });
@@ -11,7 +11,7 @@ const source = readFileSync(
11
11
 
12
12
  test("postUnitPreVerification blocks on git action failure", () => {
13
13
  const failureBlock = extractSourceRegion(source, 'if (gitResult.status === "failed")');
14
- assert.ok(failureBlock.includes('ctx.ui.notify(failureMsg, "error")'));
14
+ assert.ok(failureBlock.includes('ctx.ui.notify(failureMsg, opts?.softFailure ? "warning" : "error")'));
15
15
  assert.ok(failureBlock.includes("await pauseAuto(ctx, pi)"));
16
16
  assert.ok(failureBlock.includes('return "dispatched"'));
17
17
  assert.ok(!failureBlock.includes("git-action-failed-nonblocking"));
@@ -9,6 +9,7 @@
9
9
  * 2. File path consistency — files exist vs prior expected_output
10
10
  * 3. Task ordering — detect impossible read-before-create
11
11
  * 4. Interface contracts — contradictory function signatures
12
+ * 5. Verify commands — reject unsafe or non-runnable task verification
12
13
  */
13
14
 
14
15
  import { describe, test, mock } from "node:test";
@@ -22,6 +23,7 @@ import {
22
23
  checkFilePathConsistency,
23
24
  checkTaskOrdering,
24
25
  checkInterfaceContracts,
26
+ checkVerificationCommands,
25
27
  runPreExecutionChecks,
26
28
  normalizeFilePath,
27
29
  type PreExecutionResult,
@@ -812,6 +814,33 @@ function process(a: number): number
812
814
  });
813
815
  });
814
816
 
817
+ describe("checkVerificationCommands", () => {
818
+ test("accepts pipe-free pytest Verify command", () => {
819
+ const results = checkVerificationCommands([
820
+ createTask({
821
+ id: "T01",
822
+ verify: "python3 -m pytest tests/ -q --tb=short",
823
+ }),
824
+ ]);
825
+
826
+ assert.deepEqual(results, []);
827
+ });
828
+
829
+ test("rejects piped pytest Verify command", () => {
830
+ const results = checkVerificationCommands([
831
+ createTask({
832
+ id: "T01",
833
+ verify: "python3 -m pytest tests/ -q --tb=short 2>&1 | tail -5",
834
+ }),
835
+ ]);
836
+
837
+ assert.equal(results.length, 1);
838
+ assert.equal(results[0]?.category, "tool");
839
+ assert.equal(results[0]?.blocking, true);
840
+ assert.match(results[0]?.message ?? "", /shell control syntax/);
841
+ });
842
+ });
843
+
815
844
  // ─── runPreExecutionChecks Integration Tests ─────────────────────────────────
816
845
 
817
846
  describe("runPreExecutionChecks", () => {
@@ -847,6 +876,30 @@ describe("runPreExecutionChecks", () => {
847
876
  }
848
877
  });
849
878
 
879
+ test("returns fail status for unsafe Verify command before execution", async () => {
880
+ tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
881
+ mkdirSync(tempDir, { recursive: true });
882
+
883
+ try {
884
+ const tasks = [
885
+ createTask({
886
+ id: "T01",
887
+ verify: "python3 -m pytest tests/ -q --tb=short 2>&1 | tail -5",
888
+ }),
889
+ ];
890
+
891
+ const result = await runPreExecutionChecks(tasks, tempDir);
892
+
893
+ assert.equal(result.status, "fail");
894
+ assert.equal(result.checks.length, 1);
895
+ assert.equal(result.checks[0]?.category, "tool");
896
+ assert.equal(result.checks[0]?.blocking, true);
897
+ assert.match(result.checks[0]?.message ?? "", /Unsafe or non-runnable Verify command/);
898
+ } finally {
899
+ rmSync(tempDir, { recursive: true, force: true });
900
+ }
901
+ });
902
+
850
903
  test("returns fail status when blocking failure exists", async () => {
851
904
  tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
852
905
  mkdirSync(tempDir, { recursive: true });
@@ -0,0 +1,23 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { loadPrompt } from "../prompt-loader.ts";
5
+
6
+ test("loadPrompt reports missing template variables with balanced braces", () => {
7
+ assert.throws(
8
+ () => loadPrompt("guided-discuss-milestone", {
9
+ milestoneId: "M001",
10
+ milestoneTitle: "Missing working directory",
11
+ structuredQuestionsAvailable: "false",
12
+ fastPathInstruction: "",
13
+ inlinedTemplates: "context template",
14
+ commitInstruction: "Do not commit during this test.",
15
+ }),
16
+ (error) => {
17
+ assert.ok(error instanceof Error);
18
+ assert.match(error.message, /template declares \{\{workingDirectory\}\} but no value was provided/);
19
+ assert.doesNotMatch(error.message, /\{\{workingDirectory\}\}\}/);
20
+ return true;
21
+ },
22
+ );
23
+ });
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Regression test for #2675: completing-milestone dispatch rule must
3
- * block completion when VALIDATION verdict is "needs-remediation".
2
+ * Regression tests for non-passing VALIDATION verdicts: completing-milestone
3
+ * dispatch must block completion when VALIDATION needs remediation or attention.
4
4
  *
5
5
  * Without this guard, needs-remediation + allSlicesDone causes a loop:
6
6
  * complete-milestone dispatched → agent refuses (correct) → no SUMMARY
@@ -66,6 +66,50 @@ test("completing-milestone blocks when VALIDATION verdict is needs-remediation (
66
66
  }
67
67
  });
68
68
 
69
+ test("completing-milestone blocks when VALIDATION verdict is needs-attention (#5747)", async () => {
70
+ const base = mkdtempSync(join(tmpdir(), "gsd-attention-"));
71
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
72
+
73
+ try {
74
+ writeFileSync(
75
+ join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md"),
76
+ [
77
+ "---",
78
+ "verdict: needs-attention",
79
+ "remediation_round: 0",
80
+ "---",
81
+ "",
82
+ "# Validation Report",
83
+ "",
84
+ "Acceptance proof is incomplete and needs human attention.",
85
+ ].join("\n"),
86
+ );
87
+
88
+ const ctx = {
89
+ mid: "M001",
90
+ midTitle: "Test Milestone",
91
+ basePath: base,
92
+ state: { phase: "completing-milestone" } as any,
93
+ prefs: {} as any,
94
+ session: undefined,
95
+ };
96
+
97
+ const result = await completingRule!.match(ctx);
98
+
99
+ assert.ok(result !== null, "rule should match");
100
+ assert.equal(result!.action, "stop", "should return stop action");
101
+ if (result!.action === "stop") {
102
+ assert.equal(result!.level, "warning", "should be warning level (pausable)");
103
+ assert.ok(
104
+ result!.reason.includes("needs-attention"),
105
+ "reason should mention needs-attention",
106
+ );
107
+ }
108
+ } finally {
109
+ rmSync(base, { recursive: true, force: true });
110
+ }
111
+ });
112
+
69
113
  test("completing-milestone proceeds normally when VALIDATION verdict is pass (#2675 guard)", async () => {
70
114
  const base = mkdtempSync(join(tmpdir(), "gsd-remediation-"));
71
115
  mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
@@ -5,6 +5,7 @@ import test from "node:test";
5
5
  import assert from "node:assert/strict";
6
6
 
7
7
  import {
8
+ _hasEmptyAgentEndContent,
8
9
  _handleSessionSwitchAgentEnd,
9
10
  isBareClaudeCodeStreamAbortPlaceholder,
10
11
  isClaudeCodeSessionSwitchAbortMessage,
@@ -188,6 +189,15 @@ test("empty-content aborted during session-switch is silently ignored", () => {
188
189
  assert.equal(cancelledWith, null);
189
190
  });
190
191
 
192
+ test("missing agent_end content is classified as empty abort content", () => {
193
+ // Providers may omit content entirely for a late aborted agent_end. That is
194
+ // equivalent to empty content and must not pause/cancel the next unit.
195
+ assert.equal(_hasEmptyAgentEndContent(undefined), true);
196
+ assert.equal(_hasEmptyAgentEndContent(null), true);
197
+ assert.equal(_hasEmptyAgentEndContent([]), true);
198
+ assert.equal(_hasEmptyAgentEndContent([{ type: "text", text: "partial" }]), false);
199
+ });
200
+
191
201
  test("completed assistant content with aborted stopReason during session-switch is ignored", () => {
192
202
  // newSession() can abort the just-finished provider stream while the last
193
203
  // assistant message still carries the completed unit summary. That is a
@@ -99,19 +99,49 @@ test("auto bootstrap validates blocked directories before touching .gsd migratio
99
99
 
100
100
  test("fresh start registers the auto worker before bootstrap enters worktree flow (#5405)", () => {
101
101
  const autoSrc = readGsdFile("auto.ts");
102
+ const autoStartSrc = readGsdFile("auto-start.ts");
102
103
  const startAutoIdx = autoSrc.indexOf("export async function startAuto(");
103
104
  const startAutoBody = autoSrc.slice(startAutoIdx);
105
+ const bootstrapIdx = autoStartSrc.indexOf("export async function bootstrapAutoSession(");
106
+ const bootstrapBody = autoStartSrc.slice(bootstrapIdx);
104
107
 
105
- const preBootstrapRegisterIdx = startAutoBody.indexOf("registerAutoWorkerForSession(s, base);");
106
108
  const bootstrapCallIdx = startAutoBody.indexOf("const ready = await bootstrapAutoSession(");
109
+ const preBootstrapBody = startAutoBody.slice(0, bootstrapCallIdx);
110
+ const preBootstrapRegisterIdx = preBootstrapBody.lastIndexOf("registerAutoWorkerForSession(s, base);");
111
+ const resumeSectionIdx = startAutoBody.indexOf("if (s.paused) {");
112
+ const freshStartSectionIdx = startAutoBody.indexOf("// ── Fresh start path — delegated to auto-start.ts ──");
113
+ const resumeBody = startAutoBody.slice(resumeSectionIdx, freshStartSectionIdx);
114
+ const resumeDbOpenIdx = resumeBody.indexOf("await openProjectDbIfPresent(base);");
115
+ const resumeRegisterIdx = resumeBody.indexOf("registerAutoWorkerForSession(s, base);");
116
+ const resumeEnterMilestoneIdx = resumeBody.indexOf("buildLifecycle().enterMilestone");
117
+ const dbOpenIdx = bootstrapBody.indexOf("await openProjectDbIfPresent(base);");
118
+ const bootstrapRegisterIdx = bootstrapBody.indexOf("registerAutoWorkerForSession(base);");
119
+ const enterMilestoneIdx = bootstrapBody.indexOf("buildLifecycle().enterMilestone");
107
120
 
108
121
  assert.ok(startAutoIdx > -1, "startAuto should exist");
109
122
  assert.ok(preBootstrapRegisterIdx > -1, "startAuto should register worker before bootstrap");
110
123
  assert.ok(bootstrapCallIdx > -1, "startAuto should call bootstrapAutoSession");
124
+ assert.ok(resumeSectionIdx > -1, "startAuto should have resume milestone entry flow");
125
+ assert.ok(freshStartSectionIdx > resumeSectionIdx, "resume assertions should be scoped before fresh start");
126
+ assert.ok(resumeDbOpenIdx > -1, "resume should open DB before milestone entry");
127
+ assert.ok(resumeRegisterIdx > -1, "resume should register worker before milestone entry");
128
+ assert.ok(resumeEnterMilestoneIdx > -1, "resume should enter milestones through lifecycle");
129
+ assert.ok(bootstrapIdx > -1, "bootstrapAutoSession should exist");
130
+ assert.ok(dbOpenIdx > -1, "bootstrap should open the project DB");
131
+ assert.ok(bootstrapRegisterIdx > -1, "bootstrap should register worker after DB open");
132
+ assert.ok(enterMilestoneIdx > -1, "bootstrap should enter milestones through lifecycle");
111
133
  assert.ok(
112
134
  preBootstrapRegisterIdx < bootstrapCallIdx,
113
135
  "worker registration must happen before bootstrap so enterMilestone can claim milestone leases on first entry",
114
136
  );
137
+ assert.ok(
138
+ dbOpenIdx < bootstrapRegisterIdx && bootstrapRegisterIdx < enterMilestoneIdx,
139
+ "bootstrap must open DB and register worker before first enterMilestone",
140
+ );
141
+ assert.ok(
142
+ resumeDbOpenIdx < resumeRegisterIdx && resumeRegisterIdx < resumeEnterMilestoneIdx,
143
+ "resume must open DB and register worker before first enterMilestone",
144
+ );
115
145
  });
116
146
 
117
147
  test("startAutoDetached reports failures asynchronously (#3733)", () => {
@@ -19,13 +19,17 @@ import {
19
19
  closeDatabase,
20
20
  insertMilestone,
21
21
  } from "../gsd-db.ts";
22
- import { registerAutoWorker } from "../db/auto-workers.ts";
22
+ import { registerAutoWorker, markWorkerCrashed } from "../db/auto-workers.ts";
23
23
  import { claimMilestoneLease } from "../db/milestone-leases.ts";
24
24
  import {
25
25
  recordDispatchClaim,
26
+ markFailed,
27
+ markCanceled,
26
28
  getRecentUnitKeysForWorker,
29
+ getRecentUnitKeysForProjectRoot,
27
30
  } from "../db/unit-dispatches.ts";
28
31
  import { setRuntimeKv, getRuntimeKv } from "../db/runtime-kv.ts";
32
+ import { detectStuck } from "../auto/detect-stuck.ts";
29
33
 
30
34
  function makeBase(): string {
31
35
  const base = mkdtempSync(join(tmpdir(), "gsd-stuck-state-db-"));
@@ -67,6 +71,65 @@ test("getRecentUnitKeysForWorker reconstructs the recentUnits sliding window", (
67
71
  assert.deepEqual(window.map(w => w.key), ["U1", "U2", "U3"]);
68
72
  });
69
73
 
74
+ test("getRecentUnitKeysForProjectRoot restores compound keys used by stuck detection", (t) => {
75
+ const base = makeBase();
76
+ t.after(() => cleanup(base));
77
+ openDatabase(join(base, ".gsd", "gsd.db"));
78
+ insertMilestone({ id: "M001", title: "T", status: "active" });
79
+ insertMilestone({ id: "M002", title: "Crashed", status: "active" });
80
+ const worker = registerAutoWorker({ projectRootRealpath: base });
81
+ const lease = claimMilestoneLease(worker, "M001");
82
+ assert.equal(lease.ok, true);
83
+ if (!lease.ok) return;
84
+
85
+ for (let i = 0; i < 2; i++) {
86
+ const claim = recordDispatchClaim({
87
+ traceId: `t${i}`,
88
+ workerId: worker,
89
+ milestoneLeaseToken: lease.token,
90
+ milestoneId: "M001",
91
+ sliceId: "S01",
92
+ unitType: "complete-slice",
93
+ unitId: "M001/S01",
94
+ });
95
+ assert.equal(claim.ok, true);
96
+ if (!claim.ok) return;
97
+ markCanceled(claim.dispatchId, "pause");
98
+ }
99
+
100
+ const crashedWorker = registerAutoWorker({ projectRootRealpath: base });
101
+ const crashedLease = claimMilestoneLease(crashedWorker, "M002");
102
+ assert.equal(crashedLease.ok, true);
103
+ if (!crashedLease.ok) return;
104
+
105
+ for (let i = 0; i < 3; i++) {
106
+ const claim = recordDispatchClaim({
107
+ traceId: `crashed-${i}`,
108
+ workerId: crashedWorker,
109
+ milestoneLeaseToken: crashedLease.token,
110
+ milestoneId: "M002",
111
+ sliceId: "S01",
112
+ taskId: "T01",
113
+ unitType: "execute-task",
114
+ unitId: "M002/S01/T01",
115
+ });
116
+ assert.equal(claim.ok, true);
117
+ if (!claim.ok) return;
118
+ markFailed(claim.dispatchId, { errorSummary: "worker crashed" });
119
+ }
120
+ markWorkerCrashed(crashedWorker);
121
+
122
+ const window = getRecentUnitKeysForProjectRoot(base, 3);
123
+ assert.deepEqual(window.map(w => w.key), [
124
+ "complete-slice/M001/S01",
125
+ "complete-slice/M001/S01",
126
+ ]);
127
+
128
+ const result = detectStuck([...window, { key: "complete-slice/M001/S01" }]);
129
+ assert.equal(result?.stuck, true);
130
+ assert.match(result?.reason ?? "", /3 consecutive times/);
131
+ });
132
+
70
133
  test("getRecentUnitKeysForWorker honors the limit parameter", (t) => {
71
134
  const base = makeBase();
72
135
  t.after(() => cleanup(base));
@@ -175,13 +175,17 @@ const verificationEvidence = [
175
175
  );
176
176
  }
177
177
 
178
- // Test 11: empty key_files renders YAML placeholder, not empty array
178
+ // Test 11: empty key_files renders an empty YAML list, not a sentinel path
179
179
  {
180
180
  const noFiles = { ...taskRow, key_files: [] };
181
181
  const output = renderSummaryContent(noFiles, SLICE_ID, MILESTONE_ID);
182
182
  assertTrue(
183
- output.includes("key_files:\n - (none)"),
184
- "empty key_files must render as YAML list with (none) placeholder",
183
+ output.includes("key_files: []"),
184
+ "empty key_files must render as an empty YAML list",
185
+ );
186
+ assertTrue(
187
+ !output.includes("key_files:\n - (none)"),
188
+ "empty key_files must not render (none) as a path-like list item",
185
189
  );
186
190
  }
187
191
 
@@ -7,13 +7,17 @@ import {
7
7
  ARTIFACT_KEYS,
8
8
  KNOWN_UNIT_TYPES,
9
9
  UNIT_MANIFESTS,
10
+ resolveSubagentPermissionContract,
10
11
  resolveManifest,
11
12
  type ArtifactKey,
12
13
  type ContextModePolicy,
13
14
  type SkillsPolicy,
14
15
  type UnitContextManifest,
15
16
  } from "../unit-context-manifest.ts";
16
- import { ALLOWED_PLANNING_DISPATCH_AGENTS } from "../bootstrap/write-gate.ts";
17
+ import {
18
+ ALLOWED_PLANNING_DISPATCH_AGENTS,
19
+ shouldBlockPlanningUnit,
20
+ } from "../bootstrap/write-gate.ts";
17
21
  import {
18
22
  getRequiredWorkflowToolsForAutoUnit,
19
23
  getRequiredWorkflowToolsForGuidedUnit,
@@ -216,7 +220,7 @@ test("#4934: every manifest declares a tools policy", () => {
216
220
  });
217
221
 
218
222
  test("#4934: tools.mode is one of the declared policies", () => {
219
- const validModes = new Set(["all", "read-only", "planning", "planning-dispatch", "docs"]);
223
+ const validModes = new Set(["all", "read-only", "planning", "planning-dispatch", "docs", "verification"]);
220
224
  for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
221
225
  const mode = (manifest as { tools: { mode: string } }).tools.mode;
222
226
  assert.ok(
@@ -226,27 +230,84 @@ test("#4934: tools.mode is one of the declared policies", () => {
226
230
  }
227
231
  });
228
232
 
229
- test('#4934: only execute-task and reactive-execute may use tools.mode "all" (full source-tree write access)', () => {
230
- const allowedAllUnits = new Set(["execute-task", "reactive-execute"]);
233
+ test('#4934: only execution units and complete-milestone may use tools.mode "all"', () => {
234
+ const allowedAllUnits = new Set(["execute-task", "reactive-execute", "complete-milestone"]);
231
235
  for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
232
236
  const mode = (manifest as { tools: { mode: string } }).tools.mode;
233
237
  if (mode === "all") {
234
238
  assert.ok(
235
239
  allowedAllUnits.has(unitType),
236
- `manifest "${unitType}" declares tools.mode = "all" but is not on the execute-track. ` +
237
- 'Only execute-task and reactive-execute should have full source write access; ' +
240
+ `manifest "${unitType}" declares tools.mode = "all" but is not explicitly allowed. ` +
241
+ 'Only execute-task, reactive-execute, and complete-milestone should have full source write access; ' +
238
242
  'planning/discuss/research units must use "planning" or "planning-dispatch" (or "docs" for rewrite-docs).',
239
243
  );
240
244
  }
241
245
  }
242
246
  });
243
247
 
248
+ test("#5453: complete-milestone uses all tools so bash verification is not planning-dispatch blocked", () => {
249
+ const manifest = UNIT_MANIFESTS["complete-milestone"];
250
+
251
+ assert.strictEqual(manifest.tools.mode, "all");
252
+ assert.deepEqual(resolveSubagentPermissionContract("complete-milestone"), {
253
+ allowed: true,
254
+ allowedSubagents: ["*"],
255
+ toolsMode: "all",
256
+ });
257
+ // Runtime gate-level regression: these verification commands were blocked
258
+ // under planning-dispatch in #5453; complete-milestone must bypass that gate.
259
+ for (const cmd of ["git diff --name-only HEAD~1", "git log -n1 --oneline"]) {
260
+ const result = shouldBlockPlanningUnit(
261
+ "bash",
262
+ cmd,
263
+ process.cwd(),
264
+ "complete-milestone",
265
+ manifest.tools,
266
+ );
267
+ assert.strictEqual(
268
+ result.block,
269
+ false,
270
+ `shouldBlockPlanningUnit must not block ${cmd} for complete-milestone: ${result.reason}`,
271
+ );
272
+ }
273
+ });
274
+
275
+ test("#5843: run-uat uses verification tools policy so build/test commands can run", () => {
276
+ const manifest = UNIT_MANIFESTS["run-uat"];
277
+
278
+ assert.strictEqual(manifest.tools.mode, "verification");
279
+
280
+ const buildResult = shouldBlockPlanningUnit(
281
+ "bash",
282
+ "npm run build 2>&1",
283
+ process.cwd(),
284
+ "run-uat",
285
+ manifest.tools,
286
+ );
287
+ assert.strictEqual(
288
+ buildResult.block,
289
+ false,
290
+ `run-uat must allow build verification commands: ${buildResult.reason}`,
291
+ );
292
+
293
+ const sourceWriteResult = shouldBlockPlanningUnit(
294
+ "edit",
295
+ "src/main.ts",
296
+ process.cwd(),
297
+ "run-uat",
298
+ manifest.tools,
299
+ );
300
+ assert.strictEqual(sourceWriteResult.block, true);
301
+ assert.match(sourceWriteResult.reason!, /tools-policy "verification"/);
302
+ });
303
+
244
304
  test('planning-dispatch mode is reserved for slice-level decomposition and completion units', () => {
245
305
  const allowedDispatchUnits = new Set([
246
306
  "plan-slice",
307
+ "research-slice",
247
308
  "refine-slice",
248
309
  "complete-slice",
249
- "complete-milestone",
310
+ "gate-evaluate",
250
311
  // Deep planning mode: research-project orchestrates 4 parallel research
251
312
  // subagents (stack/features/architecture/pitfalls). Subagent dispatch is
252
313
  // the unit's core mechanism — without it, the unit cannot do its job.
@@ -265,6 +326,24 @@ test('planning-dispatch mode is reserved for slice-level decomposition and compl
265
326
  }
266
327
  });
267
328
 
329
+ test('Unit Tool Contract exposes subagent dispatch permissions', () => {
330
+ assert.deepEqual(resolveSubagentPermissionContract("plan-slice"), {
331
+ allowed: true,
332
+ allowedSubagents: ["scout", "planner"],
333
+ toolsMode: "planning-dispatch",
334
+ });
335
+ assert.deepEqual(resolveSubagentPermissionContract("gate-evaluate"), {
336
+ allowed: true,
337
+ allowedSubagents: ["reviewer", "security", "tester"],
338
+ toolsMode: "planning-dispatch",
339
+ });
340
+ assert.deepEqual(resolveSubagentPermissionContract("discuss-milestone"), {
341
+ allowed: false,
342
+ allowedSubagents: [],
343
+ toolsMode: "planning",
344
+ });
345
+ });
346
+
268
347
  test('planning-dispatch manifests declare non-empty allowedSubagents lists', () => {
269
348
  for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
270
349
  if (manifest.tools.mode !== "planning-dispatch") continue;