@opengsd/gsd-pi 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/README.md +11 -11
  2. package/dist/onboarding.js +11 -0
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/GSD-WORKFLOW.md +20 -25
  5. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
  6. package/dist/resources/extensions/cursor-cli/index.js +16 -0
  7. package/dist/resources/extensions/cursor-cli/models.js +63 -0
  8. package/dist/resources/extensions/cursor-cli/package.json +11 -0
  9. package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
  10. package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
  11. package/dist/resources/extensions/gsd/activity-log.js +4 -1
  12. package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
  13. package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
  14. package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
  15. package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
  16. package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
  17. package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
  18. package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
  19. package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
  20. package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
  21. package/dist/resources/extensions/gsd/auto-start.js +4 -3
  22. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
  23. package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
  24. package/dist/resources/extensions/gsd/auto.js +23 -3
  25. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
  27. package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
  28. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
  29. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
  30. package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
  31. package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
  33. package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
  34. package/dist/resources/extensions/gsd/db/queries.js +39 -0
  35. package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  37. package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
  38. package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
  39. package/dist/resources/extensions/gsd/doctor.js +93 -3
  40. package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
  41. package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
  42. package/dist/resources/extensions/gsd/git-service.js +14 -18
  43. package/dist/resources/extensions/gsd/gsd-db.js +52 -0
  44. package/dist/resources/extensions/gsd/guidance.js +1 -0
  45. package/dist/resources/extensions/gsd/guided-flow.js +40 -2
  46. package/dist/resources/extensions/gsd/key-manager.js +1 -0
  47. package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
  48. package/dist/resources/extensions/gsd/migrate-external.js +24 -7
  49. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
  50. package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
  51. package/dist/resources/extensions/gsd/preferences-models.js +6 -4
  52. package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
  53. package/dist/resources/extensions/gsd/preferences.js +29 -2
  54. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  55. package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
  56. package/dist/resources/extensions/gsd/repo-identity.js +16 -0
  57. package/dist/resources/extensions/gsd/repository-registry.js +6 -1
  58. package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
  59. package/dist/resources/extensions/gsd/safe-fs.js +23 -2
  60. package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  61. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  62. package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
  63. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
  64. package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
  65. package/dist/resources/extensions/gsd/unit-registry.js +27 -2
  66. package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
  67. package/dist/resources/extensions/gsd/workspace.js +26 -8
  68. package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
  69. package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
  70. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  73. package/dist/web/standalone/.next/build-manifest.json +2 -2
  74. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/index.html +1 -1
  92. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  99. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  100. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  102. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  103. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  104. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  105. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  106. package/dist/wizard.d.ts +8 -0
  107. package/dist/wizard.js +8 -1
  108. package/integrations/hermes/CONTEXT.md +47 -0
  109. package/integrations/hermes/README.md +21 -0
  110. package/integrations/hermes/__init__.py +31 -0
  111. package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
  112. package/integrations/hermes/docs/setup.md +4 -0
  113. package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
  114. package/integrations/hermes/open_gsd_hermes/config.py +2 -0
  115. package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
  116. package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
  117. package/integrations/hermes/plugin.yaml +4 -0
  118. package/integrations/hermes/tests/test_config.py +6 -0
  119. package/integrations/hermes/tests/test_directory_loader.py +37 -0
  120. package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
  121. package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
  122. package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
  123. package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
  124. package/integrations/hermes/tests/test_milestone_client.py +447 -0
  125. package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
  126. package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
  127. package/package.json +8 -8
  128. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
  129. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
  130. package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
  131. package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
  132. package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
  133. package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
  134. package/packages/cloud-mcp-gateway/package.json +2 -2
  135. package/packages/contracts/package.json +1 -1
  136. package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
  137. package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
  138. package/packages/daemon/dist/cloud-runtime.js +25 -1
  139. package/packages/daemon/dist/cloud-runtime.js.map +1 -1
  140. package/packages/daemon/dist/cloud-runtime.test.js +51 -0
  141. package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
  142. package/packages/daemon/dist/message-batcher.d.ts +7 -0
  143. package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
  144. package/packages/daemon/dist/message-batcher.js +13 -0
  145. package/packages/daemon/dist/message-batcher.js.map +1 -1
  146. package/packages/daemon/dist/message-batcher.test.js +48 -0
  147. package/packages/daemon/dist/message-batcher.test.js.map +1 -1
  148. package/packages/daemon/dist/session-manager.js +1 -1
  149. package/packages/daemon/dist/session-manager.js.map +1 -1
  150. package/packages/daemon/package.json +5 -5
  151. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
  152. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
  153. package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
  154. package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
  155. package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
  156. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
  157. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
  158. package/packages/gsd-agent-core/package.json +5 -5
  159. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
  160. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
  161. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
  162. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
  163. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
  164. package/packages/gsd-agent-modes/package.json +7 -7
  165. package/packages/mcp-server/dist/server.d.ts +18 -5
  166. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  167. package/packages/mcp-server/dist/server.js +46 -5
  168. package/packages/mcp-server/dist/server.js.map +1 -1
  169. package/packages/mcp-server/dist/session-manager.js +1 -1
  170. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  171. package/packages/mcp-server/package.json +4 -4
  172. package/packages/native/package.json +1 -1
  173. package/packages/pi-agent-core/package.json +1 -1
  174. package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  175. package/packages/pi-ai/dist/env-api-keys.js +3 -0
  176. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  177. package/packages/pi-ai/dist/types.d.ts +2 -2
  178. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  179. package/packages/pi-ai/dist/types.js.map +1 -1
  180. package/packages/pi-ai/package.json +1 -1
  181. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  183. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
  185. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
  189. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/dist/rpc-client.js +1 -1
  193. package/packages/rpc-client/dist/rpc-client.js.map +1 -1
  194. package/packages/rpc-client/package.json +2 -2
  195. package/pkg/package.json +1 -1
  196. package/scripts/link-workspace-packages.cjs +14 -4
  197. package/src/resources/GSD-WORKFLOW.md +20 -25
  198. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
  199. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
  200. package/src/resources/extensions/cursor-cli/index.ts +18 -0
  201. package/src/resources/extensions/cursor-cli/models.ts +65 -0
  202. package/src/resources/extensions/cursor-cli/package.json +11 -0
  203. package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
  204. package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
  205. package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
  206. package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
  207. package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
  208. package/src/resources/extensions/gsd/activity-log.ts +4 -1
  209. package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
  210. package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
  211. package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
  212. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  213. package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
  214. package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
  215. package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
  216. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
  217. package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
  218. package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
  219. package/src/resources/extensions/gsd/auto-start.ts +4 -3
  220. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
  221. package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
  222. package/src/resources/extensions/gsd/auto.ts +22 -3
  223. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
  224. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
  225. package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
  226. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
  227. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
  228. package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
  229. package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
  230. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
  231. package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
  232. package/src/resources/extensions/gsd/db/queries.ts +40 -0
  233. package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
  234. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  235. package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
  236. package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
  237. package/src/resources/extensions/gsd/doctor-types.ts +5 -1
  238. package/src/resources/extensions/gsd/doctor.ts +95 -3
  239. package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
  240. package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
  241. package/src/resources/extensions/gsd/git-service.ts +14 -17
  242. package/src/resources/extensions/gsd/gsd-db.ts +72 -0
  243. package/src/resources/extensions/gsd/guidance.ts +2 -0
  244. package/src/resources/extensions/gsd/guided-flow.ts +45 -2
  245. package/src/resources/extensions/gsd/key-manager.ts +1 -0
  246. package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
  247. package/src/resources/extensions/gsd/migrate-external.ts +23 -6
  248. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
  249. package/src/resources/extensions/gsd/model-router.ts +4 -5
  250. package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
  251. package/src/resources/extensions/gsd/preferences-models.ts +6 -3
  252. package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
  253. package/src/resources/extensions/gsd/preferences.ts +38 -4
  254. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  255. package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
  256. package/src/resources/extensions/gsd/repo-identity.ts +17 -0
  257. package/src/resources/extensions/gsd/repository-registry.ts +7 -1
  258. package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
  259. package/src/resources/extensions/gsd/safe-fs.ts +22 -2
  260. package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  261. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  262. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
  263. package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
  264. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
  265. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
  266. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  267. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
  268. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
  269. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
  270. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
  271. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
  272. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
  273. package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
  274. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
  275. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
  276. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
  277. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
  278. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
  279. package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
  280. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
  281. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
  282. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
  283. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
  284. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  285. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
  286. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
  287. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
  288. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
  289. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
  291. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
  292. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
  293. package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
  294. package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
  295. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
  296. package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
  297. package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
  298. package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
  299. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
  300. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
  301. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
  302. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
  303. package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
  304. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
  305. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
  306. package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
  307. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
  308. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
  309. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
  310. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
  311. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  312. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
  313. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
  314. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
  315. package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
  316. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
  317. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
  318. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
  319. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
  320. package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
  321. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
  322. package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
  323. package/src/resources/extensions/gsd/unit-registry.ts +34 -2
  324. package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
  325. package/src/resources/extensions/gsd/workspace.ts +42 -8
  326. package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
  327. package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
  328. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
  329. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
@@ -0,0 +1,167 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Regression tests for ScheduleWakeup tool behavior.
3
+
4
+ import test, { mock } from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import { autoSession } from "../auto-runtime-state.ts";
8
+ import {
9
+ registerScheduleWakeupTool,
10
+ _resetInteractiveWakeupsForTest,
11
+ } from "../bootstrap/schedule-wakeup-tool.ts";
12
+
13
+ test("ScheduleWakeup arms an interactive wakeup when auto-mode is inactive", async () => {
14
+ autoSession.reset();
15
+ _resetInteractiveWakeupsForTest();
16
+ mock.timers.enable({ apis: ["setTimeout"] });
17
+
18
+ const sent: Array<{ message: unknown; options: unknown }> = [];
19
+ let tool: any;
20
+ const pi = {
21
+ registerTool(registered: any) {
22
+ tool = registered;
23
+ },
24
+ sendMessage(message: unknown, options: unknown) {
25
+ sent.push({ message, options });
26
+ },
27
+ };
28
+
29
+ try {
30
+ registerScheduleWakeupTool(pi as any);
31
+ assert.ok(tool, "ScheduleWakeup tool should be registered");
32
+
33
+ const result = await tool.execute(
34
+ "call-1",
35
+ {
36
+ delaySeconds: 1,
37
+ prompt: "Check the external job and report back.",
38
+ reason: "poll external job",
39
+ },
40
+ undefined,
41
+ undefined,
42
+ { cwd: process.cwd() },
43
+ );
44
+
45
+ assert.equal(result.isError, undefined);
46
+ assert.match(result.content[0].text, /Wakeup scheduled for 1s/);
47
+ assert.equal(sent.length, 0, "wakeup should not dispatch synchronously");
48
+
49
+ mock.timers.tick(999);
50
+ assert.equal(sent.length, 0, "wakeup should wait for the configured delay");
51
+
52
+ mock.timers.tick(1);
53
+ assert.equal(sent.length, 1);
54
+ assert.deepEqual(sent[0].options, { triggerTurn: true });
55
+ assert.deepEqual(sent[0].message, {
56
+ customType: "gsd-schedule-wakeup",
57
+ content: "Check the external job and report back.",
58
+ display: true,
59
+ details: {
60
+ delaySeconds: 1,
61
+ reason: "poll external job",
62
+ },
63
+ });
64
+ } finally {
65
+ mock.timers.reset();
66
+ _resetInteractiveWakeupsForTest();
67
+ autoSession.reset();
68
+ }
69
+ });
70
+
71
+ test("ScheduleWakeup re-arm cancels the prior interactive timer instead of stacking", async () => {
72
+ autoSession.reset();
73
+ _resetInteractiveWakeupsForTest();
74
+ mock.timers.enable({ apis: ["setTimeout"] });
75
+
76
+ const sent: Array<{ message: any; options: unknown }> = [];
77
+ let tool: any;
78
+ const pi = {
79
+ registerTool(registered: any) {
80
+ tool = registered;
81
+ },
82
+ sendMessage(message: unknown, options: unknown) {
83
+ sent.push({ message, options } as { message: any; options: unknown });
84
+ },
85
+ };
86
+
87
+ try {
88
+ registerScheduleWakeupTool(pi as any);
89
+ assert.ok(tool, "ScheduleWakeup tool should be registered");
90
+
91
+ const ctx = { cwd: process.cwd() };
92
+ await tool.execute(
93
+ "call-1",
94
+ { delaySeconds: 5, prompt: "stale prompt", reason: "first arm" },
95
+ undefined,
96
+ undefined,
97
+ ctx,
98
+ );
99
+ await tool.execute(
100
+ "call-2",
101
+ { delaySeconds: 5, prompt: "fresh prompt", reason: "re-arm" },
102
+ undefined,
103
+ undefined,
104
+ ctx,
105
+ );
106
+
107
+ mock.timers.tick(5000);
108
+
109
+ assert.equal(sent.length, 1, "re-arming must not stack overlapping wakeups");
110
+ assert.equal(sent[0].message.content, "fresh prompt");
111
+ } finally {
112
+ mock.timers.reset();
113
+ _resetInteractiveWakeupsForTest();
114
+ autoSession.reset();
115
+ }
116
+ });
117
+
118
+ test("ScheduleWakeup keeps interactive wakeups isolated per session base path", async () => {
119
+ autoSession.reset();
120
+ _resetInteractiveWakeupsForTest();
121
+ mock.timers.enable({ apis: ["setTimeout"] });
122
+
123
+ const sent: Array<{ message: any; options: unknown }> = [];
124
+ let tool: any;
125
+ const pi = {
126
+ registerTool(registered: any) {
127
+ tool = registered;
128
+ },
129
+ sendMessage(message: unknown, options: unknown) {
130
+ sent.push({ message, options } as { message: any; options: unknown });
131
+ },
132
+ };
133
+
134
+ try {
135
+ registerScheduleWakeupTool(pi as any);
136
+ assert.ok(tool, "ScheduleWakeup tool should be registered");
137
+
138
+ // Two concurrent interactive sessions (different cwds) arm wakeups. One
139
+ // session re-arming must not cancel the other session's pending wakeup.
140
+ await tool.execute(
141
+ "call-a",
142
+ { delaySeconds: 5, prompt: "session A", reason: "poll A" },
143
+ undefined,
144
+ undefined,
145
+ { cwd: "/tmp" },
146
+ );
147
+ await tool.execute(
148
+ "call-b",
149
+ { delaySeconds: 5, prompt: "session B", reason: "poll B" },
150
+ undefined,
151
+ undefined,
152
+ { cwd: process.cwd() },
153
+ );
154
+
155
+ mock.timers.tick(5000);
156
+
157
+ assert.equal(sent.length, 2, "distinct sessions must each keep their wakeup");
158
+ assert.deepEqual(
159
+ sent.map((s) => s.message.content).sort(),
160
+ ["session A", "session B"],
161
+ );
162
+ } finally {
163
+ mock.timers.reset();
164
+ _resetInteractiveWakeupsForTest();
165
+ autoSession.reset();
166
+ }
167
+ });
@@ -18,6 +18,7 @@
18
18
  import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
19
19
  import { join } from "node:path";
20
20
  import { tmpdir } from "node:os";
21
+ import { verifyExpectedArtifact } from "../auto-recovery.ts";
21
22
  import { recoverTimedOutUnit, type RecoveryContext } from "../auto-timeout-recovery.ts";
22
23
  import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.ts";
23
24
  import { test } from 'node:test';
@@ -167,6 +168,54 @@ function makeRecordingCtx() {
167
168
  }
168
169
  }
169
170
 
171
+ // ═══ plan-slice verification accepts completed task summaries ═══════════════
172
+
173
+ {
174
+ console.log("\n=== plan-slice verification accepts completed task summaries ===");
175
+ const base = mkdtempSync(join(tmpdir(), "gsd-plan-slice-complete-summary-"));
176
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S05");
177
+ const tasksDir = join(sliceDir, "tasks");
178
+ mkdirSync(tasksDir, { recursive: true });
179
+
180
+ try {
181
+ openDatabase(join(base, ".gsd", "gsd.db"));
182
+ insertMilestone({ id: "M001", title: "Milestone", status: "active" });
183
+ insertSlice({ id: "S05", milestoneId: "M001", title: "Slice", status: "in_progress" });
184
+ insertTask({ id: "T01", milestoneId: "M001", sliceId: "S05", title: "Done 1", status: "complete" });
185
+ insertTask({ id: "T02", milestoneId: "M001", sliceId: "S05", title: "Done 2", status: "complete" });
186
+ insertTask({ id: "T03", milestoneId: "M001", sliceId: "S05", title: "Pending 1", status: "pending" });
187
+ insertTask({ id: "T04", milestoneId: "M001", sliceId: "S05", title: "Pending 2", status: "pending" });
188
+ writeFileSync(
189
+ join(sliceDir, "S05-PLAN.md"),
190
+ [
191
+ "# S05: Slice",
192
+ "",
193
+ "## Tasks",
194
+ "",
195
+ "- [x] **T01: Done 1** `est:10m`",
196
+ "- [x] **T02: Done 2** `est:10m`",
197
+ "- [ ] **T03: Pending 1** `est:10m`",
198
+ "- [ ] **T04: Pending 2** `est:10m`",
199
+ "",
200
+ ].join("\n"),
201
+ "utf-8",
202
+ );
203
+ writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n", "utf-8");
204
+ writeFileSync(join(tasksDir, "T02-SUMMARY.md"), "# T02 Summary\n", "utf-8");
205
+ writeFileSync(join(tasksDir, "T03-PLAN.md"), "# T03 Plan\n", "utf-8");
206
+ writeFileSync(join(tasksDir, "T04-PLAN.md"), "# T04 Plan\n", "utf-8");
207
+
208
+ assert.equal(
209
+ verifyExpectedArtifact("plan-slice", "M001/S05", base),
210
+ true,
211
+ "completed DB tasks should be accounted for by SUMMARY files",
212
+ );
213
+ } finally {
214
+ closeDatabase();
215
+ rmSync(base, { recursive: true, force: true });
216
+ }
217
+ }
218
+
170
219
  // ═══ #1855: valid RecoveryContext does not crash ═════════════════════════════
171
220
 
172
221
  {
@@ -312,7 +312,7 @@ describe("#2945 Bug 3: mergeAndExit must teardown worktree after successful merg
312
312
  } as unknown as AutoSession;
313
313
 
314
314
  const deps = {
315
- mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
315
+ mergeMilestone: () => ({ pushed: false, codeFilesChanged: true }),
316
316
  worktreeProjection: new WorktreeStateProjection(),
317
317
  gitServiceFactory: () => ({}) as unknown as ReturnType<
318
318
  import("../worktree-lifecycle.js").WorktreeLifecycleDeps["gitServiceFactory"]
@@ -0,0 +1,94 @@
1
+ /**
2
+ * status-per-repo.test.ts — `/gsd status` per-repository git-health block (#818).
3
+ *
4
+ * Covers the parent-workspace addition to formatTextStatus:
5
+ * - parent mode with child repos → "Repositories:" section listing each
6
+ * - dirty vs clean markers
7
+ * - single-repo (project-mode) project → no Repositories section
8
+ */
9
+
10
+ import test from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+ import { execFileSync } from "node:child_process";
16
+
17
+ import { formatTextStatus } from "../commands/handlers/core.ts";
18
+ import type { GSDState } from "../types.ts";
19
+
20
+ function gitInit(cwd: string): void {
21
+ execFileSync("git", ["init"], { cwd, stdio: "ignore" });
22
+ }
23
+
24
+ function makeBaseState(): GSDState {
25
+ return {
26
+ activeMilestone: null,
27
+ activeSlice: null,
28
+ activeTask: null,
29
+ phase: "planning",
30
+ recentDecisions: [],
31
+ blockers: [],
32
+ nextAction: "None",
33
+ registry: [],
34
+ };
35
+ }
36
+
37
+ function writeParentPrefs(base: string, repos: Record<string, { path: string; role?: string }>): void {
38
+ const repoLines = Object.entries(repos)
39
+ .map(([id, cfg]) => ` ${id}:\n path: ${cfg.path}${cfg.role ? `\n role: ${cfg.role}` : ""}`)
40
+ .join("\n");
41
+ writeFileSync(
42
+ join(base, ".gsd", "PREFERENCES.md"),
43
+ `---\nversion: 1\nworkspace:\n mode: parent\n repositories:\n${repoLines}\n---\n`,
44
+ "utf-8",
45
+ );
46
+ }
47
+
48
+ test("formatTextStatus shows a Repositories section for a clean parent workspace", () => {
49
+ const base = mkdtempSync(join(tmpdir(), "gsd-status-repo-clean-"));
50
+ try {
51
+ mkdirSync(join(base, ".gsd"), { recursive: true });
52
+ gitInit(base);
53
+ mkdirSync(join(base, "frontend"), { recursive: true });
54
+ gitInit(join(base, "frontend"));
55
+ writeParentPrefs(base, { frontend: { path: "frontend", role: "web UI" } });
56
+
57
+ const out = formatTextStatus(makeBaseState(), base);
58
+ assert.match(out, /Repositories:/);
59
+ assert.match(out, /✓ frontend — web UI \(clean\)/);
60
+ } finally {
61
+ rmSync(base, { recursive: true, force: true });
62
+ }
63
+ });
64
+
65
+ test("formatTextStatus marks a dirty child repo with ✗", () => {
66
+ const base = mkdtempSync(join(tmpdir(), "gsd-status-repo-dirty-"));
67
+ try {
68
+ mkdirSync(join(base, ".gsd"), { recursive: true });
69
+ gitInit(base);
70
+ mkdirSync(join(base, "backend"), { recursive: true });
71
+ gitInit(join(base, "backend"));
72
+ writeParentPrefs(base, { backend: { path: "backend" } });
73
+ // Make the backend repo dirty.
74
+ writeFileSync(join(base, "backend", "README.md"), "# dirty\n", "utf-8");
75
+
76
+ const out = formatTextStatus(makeBaseState(), base);
77
+ assert.match(out, /✗ backend \(uncommitted changes\)/);
78
+ } finally {
79
+ rmSync(base, { recursive: true, force: true });
80
+ }
81
+ });
82
+
83
+ test("formatTextStatus omits the Repositories section for single-repo projects", () => {
84
+ const base = mkdtempSync(join(tmpdir(), "gsd-status-repo-single-"));
85
+ try {
86
+ mkdirSync(join(base, ".gsd"), { recursive: true });
87
+ gitInit(base);
88
+ // No workspace config → project mode (default).
89
+ const out = formatTextStatus(makeBaseState(), base);
90
+ assert.doesNotMatch(out, /Repositories:/);
91
+ } finally {
92
+ rmSync(base, { recursive: true, force: true });
93
+ }
94
+ });
@@ -552,6 +552,41 @@ test("discuss-milestone dispatch keeps required headless milestone tools after t
552
552
  assert.ok(!activeTools.includes("gsd_complete_milestone"));
553
553
  });
554
554
 
555
+ test("#1157: validate-milestone dispatch surface removes generic write and bash tools", () => {
556
+ const result = buildMinimalAutoGsdToolSet([
557
+ "ask_user_questions",
558
+ "bash",
559
+ "bg_shell",
560
+ "edit",
561
+ "find",
562
+ "glob",
563
+ "grep",
564
+ "ls",
565
+ "read",
566
+ "subagent",
567
+ "write",
568
+ "gsd_exec",
569
+ "gsd_exec_search",
570
+ "gsd_resume",
571
+ "gsd_milestone_status",
572
+ "gsd_validate_milestone",
573
+ "gsd_reassess_roadmap",
574
+ ], "validate-milestone");
575
+
576
+ assert.ok(result.includes("read"));
577
+ assert.ok(result.includes("find"));
578
+ assert.ok(result.includes("subagent"));
579
+ assert.ok(result.includes("gsd_validate_milestone"));
580
+ assert.ok(result.includes("gsd_milestone_status"));
581
+ assert.ok(result.includes("gsd_exec"));
582
+ assert.ok(result.includes("gsd_reassess_roadmap"));
583
+ assert.ok(!result.includes("bash"));
584
+ assert.ok(!result.includes("bg_shell"));
585
+ assert.ok(!result.includes("write"));
586
+ assert.ok(!result.includes("edit"));
587
+ assert.ok(!result.includes("ask_user_questions"));
588
+ });
589
+
555
590
  test("scopeGsdWorkflowToolsForDispatch applies and restores per-unit skill visibility", () => {
556
591
  const calls: Array<{ kind: "tools" | "skills"; value: string[] | undefined }> = [];
557
592
  let activeTools = [
@@ -385,3 +385,66 @@ console.log('\n── Loop guard: block reasons tell model to respond in text
385
385
  }
386
386
 
387
387
  // ═══════════════════════════════════════════════════════════════════════════
388
+ // Distinct-arg browser-automation calls are a legitimate UAT, not a loop (#1120)
389
+ // ═══════════════════════════════════════════════════════════════════════════
390
+
391
+ console.log('\n── Loop guard: distinct-arg browser automation calls are not capped by per-tool count (#1120) ──');
392
+
393
+ {
394
+ // A browser-backed UAT legitimately calls one browser verb many times with
395
+ // DIFFERENT arguments (read a validation message, count staged edits, inspect
396
+ // a control card, …). Guard 1's identical-signature streak never trips because
397
+ // the args vary; Guard 2's arg-independent per-tool cap must NOT block it.
398
+ for (const toolName of ['browser_evaluate', 'browser_act', 'browser_find']) {
399
+ resetToolCallLoopGuard();
400
+ for (let i = 1; i <= 12; i++) {
401
+ const result = checkToolCallLoop(toolName, { script: `step ${i}` });
402
+ assert.ok(result.block === false, `distinct ${toolName} call ${i} should be allowed`);
403
+ }
404
+ }
405
+
406
+ // MCP-prefixed browser tools canonicalize to `browser_*` and are exempt too.
407
+ resetToolCallLoopGuard();
408
+ for (let i = 1; i <= 12; i++) {
409
+ const result = checkToolCallLoop('mcp__agent-browser__browser_evaluate', { script: `step ${i}` });
410
+ assert.ok(result.block === false, `distinct MCP-prefixed browser_evaluate call ${i} should be allowed`);
411
+ }
412
+ }
413
+
414
+ // ═══════════════════════════════════════════════════════════════════════════
415
+ // Identical-arg browser loops are STILL caught by Guard 1 (#1120)
416
+ // ═══════════════════════════════════════════════════════════════════════════
417
+
418
+ console.log('\n── Loop guard: identical-arg browser loops are still caught by Guard 1 (#1120) ──');
419
+
420
+ {
421
+ resetToolCallLoopGuard();
422
+ for (let i = 1; i <= 4; i++) {
423
+ const result = checkToolCallLoop('browser_evaluate', { script: 'document.title' });
424
+ assert.ok(result.block === false, `identical browser_evaluate call ${i} should be allowed`);
425
+ }
426
+ const blocked = checkToolCallLoop('browser_evaluate', { script: 'document.title' });
427
+ assert.ok(blocked.block === true, '5th identical browser_evaluate call should be blocked by Guard 1');
428
+ assert.ok(blocked.reason?.includes('identical args'), 'identical browser loops should be caught by Guard 1, not Guard 2');
429
+ }
430
+
431
+ // ═══════════════════════════════════════════════════════════════════════════
432
+ // The browser exemption must NOT widen to other non-exempt tools (#1120)
433
+ // ═══════════════════════════════════════════════════════════════════════════
434
+
435
+ console.log('\n── Loop guard: browser exemption does not widen to other tools (#1120) ──');
436
+
437
+ {
438
+ // Negative invariant: an arbitrary non-exempt, non-browser tool called past
439
+ // the default cap with varied args must still be blocked by Guard 2.
440
+ resetToolCallLoopGuard();
441
+ for (let i = 1; i <= 6; i++) {
442
+ const result = checkToolCallLoop('some_workflow_tool', { arg: `v${i}` });
443
+ assert.ok(result.block === false, `non-exempt tool call ${i} should be allowed`);
444
+ }
445
+ const blocked = checkToolCallLoop('some_workflow_tool', { arg: 'v7' });
446
+ assert.ok(blocked.block === true, '7th varied-arg non-exempt call must still trip Guard 2');
447
+ assert.ok(blocked.reason?.includes('repeated tool'), 'non-exempt overflow should be caught by Guard 2');
448
+ }
449
+
450
+ // ═══════════════════════════════════════════════════════════════════════════
@@ -268,7 +268,7 @@ test("#4934: every manifest declares a tools policy", () => {
268
268
  });
269
269
 
270
270
  test("#4934: tools.mode is one of the declared policies", () => {
271
- const validModes = new Set(["all", "read-only", "planning", "planning-dispatch", "docs", "verification"]);
271
+ const validModes = new Set(["all", "read-only", "planning", "planning-dispatch", "docs", "verification", "workflow-only"]);
272
272
  for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
273
273
  const mode = (manifest as { tools: { mode: string } }).tools.mode;
274
274
  assert.ok(
@@ -327,23 +327,71 @@ test("#5453: complete-milestone uses all tools so bash verification is not plann
327
327
  }
328
328
  });
329
329
 
330
- test("#5731: validate-milestone and complete-slice use all tools so closeout verification is not planning-dispatch blocked", () => {
331
- for (const unitType of ["validate-milestone", "complete-slice"] as const) {
332
- const manifest = UNIT_MANIFESTS[unitType];
333
- assert.strictEqual(manifest.tools.mode, "all");
334
- const result = shouldBlockPlanningUnit(
335
- "bash",
336
- "go test -short -count=1 ./...",
337
- process.cwd(),
338
- unitType,
339
- manifest.tools,
340
- );
341
- assert.strictEqual(
342
- result.block,
343
- false,
344
- `${unitType} must allow verification bash commands: ${result.reason}`,
345
- );
346
- }
330
+ test("#5731: complete-slice uses all tools so closeout verification is not planning-dispatch blocked", () => {
331
+ const manifest = UNIT_MANIFESTS["complete-slice"];
332
+ assert.strictEqual(manifest.tools.mode, "all");
333
+ const result = shouldBlockPlanningUnit(
334
+ "bash",
335
+ "go test -short -count=1 ./...",
336
+ process.cwd(),
337
+ "complete-slice",
338
+ manifest.tools,
339
+ );
340
+ assert.strictEqual(
341
+ result.block,
342
+ false,
343
+ `complete-slice must allow verification bash commands: ${result.reason}`,
344
+ );
345
+ });
346
+
347
+ test("#1157: validate-milestone uses workflow-only tools and blocks manual artifact writes", () => {
348
+ const manifest = UNIT_MANIFESTS["validate-milestone"];
349
+ assert.strictEqual(manifest.tools.mode, "workflow-only");
350
+ assert.deepEqual(resolveSubagentPermissionContract("validate-milestone"), {
351
+ allowed: true,
352
+ allowedSubagents: ["reviewer", "security", "tester"],
353
+ toolsMode: "workflow-only",
354
+ });
355
+
356
+ const validationWrite = shouldBlockPlanningUnit(
357
+ "write",
358
+ ".gsd/milestones/M001/M001-VALIDATION.md",
359
+ process.cwd(),
360
+ "validate-milestone",
361
+ manifest.tools,
362
+ );
363
+ assert.strictEqual(validationWrite.block, true);
364
+ assert.match(validationWrite.reason!, /workflow-only/);
365
+ assert.match(validationWrite.reason!, /direct artifact writes are disabled/);
366
+
367
+ const bash = shouldBlockPlanningUnit(
368
+ "bash",
369
+ "npm test",
370
+ process.cwd(),
371
+ "validate-milestone",
372
+ manifest.tools,
373
+ );
374
+ assert.strictEqual(bash.block, true);
375
+ assert.match(bash.reason!, /bash is not permitted/);
376
+
377
+ const validateTool = shouldBlockPlanningUnit(
378
+ "gsd_validate_milestone",
379
+ "",
380
+ process.cwd(),
381
+ "validate-milestone",
382
+ manifest.tools,
383
+ );
384
+ assert.strictEqual(validateTool.block, false);
385
+
386
+ const reviewer = shouldBlockPlanningUnit(
387
+ "subagent",
388
+ "",
389
+ process.cwd(),
390
+ "validate-milestone",
391
+ manifest.tools,
392
+ ["reviewer"],
393
+ );
394
+ assert.strictEqual(reviewer.block, false, reviewer.reason);
347
395
  });
348
396
 
349
397
  test("#5843: run-uat uses verification tools policy so build/test commands can run", () => {
@@ -7,6 +7,8 @@
7
7
 
8
8
  import test from "node:test";
9
9
  import assert from "node:assert/strict";
10
+ import { existsSync } from "node:fs";
11
+ import { join } from "node:path";
10
12
 
11
13
  import {
12
14
  KNOWN_UNIT_TYPES,
@@ -15,6 +17,7 @@ import {
15
17
  SECTION_CLOSE_GATE_UNIT_TYPES,
16
18
  getUnitDescriptor,
17
19
  getUnitPhaseChain,
20
+ getUnitPromptTemplate,
18
21
  } from "../unit-registry.ts";
19
22
  import {
20
23
  AUTO_UNIT_SCOPED_TOOLS,
@@ -96,6 +99,37 @@ const EXPECTED_PHASE_CHAINS: Record<string, string[] | undefined> = {
96
99
  "no-such-unit": undefined,
97
100
  };
98
101
 
102
+ const EXPECTED_DIRECT_PROMPT_TEMPLATES: Record<string, string> = {
103
+ "research-milestone": "research-milestone",
104
+ "plan-milestone": "plan-milestone",
105
+ "validate-milestone": "validate-milestone",
106
+ "complete-milestone": "complete-milestone",
107
+ "research-slice": "research-slice",
108
+ "plan-slice": "plan-slice",
109
+ "refine-slice": "refine-slice",
110
+ "replan-slice": "replan-slice",
111
+ "complete-slice": "complete-slice",
112
+ "reassess-roadmap": "reassess-roadmap",
113
+ "execute-task": "execute-task",
114
+ "reactive-execute": "reactive-execute",
115
+ "run-uat": "run-uat",
116
+ "gate-evaluate": "gate-evaluate",
117
+ "rewrite-docs": "rewrite-docs",
118
+ "workflow-preferences": "guided-workflow-preferences",
119
+ "discuss-project": "guided-discuss-project",
120
+ "discuss-requirements": "guided-discuss-requirements",
121
+ "research-decision": "guided-research-decision",
122
+ "research-project": "guided-research-project",
123
+ };
124
+
125
+ const EXPECTED_UNDECLARED_PROMPT_TEMPLATE_TYPES = [
126
+ "discuss-milestone",
127
+ "discuss-slice",
128
+ "execute-task-simple",
129
+ "triage-captures",
130
+ "quick-task",
131
+ ];
132
+
99
133
  // ─── Derived-view parity ──────────────────────────────────────────────────
100
134
 
101
135
  test("KNOWN_UNIT_TYPES derives exactly the pre-registry list, in order", () => {
@@ -134,6 +168,28 @@ test("phaseChainForUnit matches the pre-registry switch for every known input",
134
168
  }
135
169
  });
136
170
 
171
+ test("direct prompt-template associations live on the Unit Registry", () => {
172
+ for (const [unitType, promptTemplate] of Object.entries(EXPECTED_DIRECT_PROMPT_TEMPLATES)) {
173
+ assert.equal(
174
+ getUnitPromptTemplate(unitType),
175
+ promptTemplate,
176
+ `prompt template for ${unitType}`,
177
+ );
178
+ assert.ok(
179
+ existsSync(join(process.cwd(), "src/resources/extensions/gsd/prompts", `${promptTemplate}.md`)),
180
+ `prompt template file must exist for ${unitType}: ${promptTemplate}`,
181
+ );
182
+ }
183
+
184
+ for (const unitType of EXPECTED_UNDECLARED_PROMPT_TEMPLATE_TYPES) {
185
+ assert.equal(
186
+ getUnitPromptTemplate(unitType),
187
+ undefined,
188
+ `${unitType} prompt association is conditional or not verified yet`,
189
+ );
190
+ }
191
+ });
192
+
137
193
  test("AUTO_UNIT_SCOPED_TOOLS mirrors each contract's allowed tools", () => {
138
194
  for (const [unitType, contract] of Object.entries(UNIT_TOOL_CONTRACTS)) {
139
195
  assert.deepEqual(AUTO_UNIT_SCOPED_TOOLS[unitType], contract.allowedGsdTools);
@@ -6,7 +6,9 @@ import {
6
6
  formatExecuteTaskRecoveryStatus,
7
7
  inspectExecuteTaskDurability,
8
8
  isInFlightRuntimePhase,
9
+ readUnitHarnessAbort,
9
10
  readUnitRuntimeRecord,
11
+ recordUnitHarnessAbort,
10
12
  writeUnitRuntimeRecord,
11
13
  } from "../unit-runtime.ts";
12
14
  import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.ts";
@@ -41,6 +43,34 @@ console.log("\n=== runtime record write/read/update ===");
41
43
  assert.deepStrictEqual(loaded!.phase, "wrapup-warning-sent", "updated phase readable");
42
44
  }
43
45
 
46
+ console.log("\n=== runtime harness abort preservation and explicit clear ===");
47
+ {
48
+ const startedAt = 3000;
49
+ recordUnitHarnessAbort(base, "gate-evaluate", "M100/S02/gates+Q3", startedAt, {
50
+ kind: "tool-error",
51
+ reason: "Tool execution failed before the unit could complete its gate evaluation.",
52
+ toolName: "browser_click",
53
+ });
54
+
55
+ const preserved = writeUnitRuntimeRecord(base, "gate-evaluate", "M100/S02/gates+Q3", startedAt, {
56
+ phase: "wrapup-warning-sent",
57
+ });
58
+ assert.equal(preserved.harnessAbort?.kind, "tool-error", "ordinary same-run updates preserve harness abort");
59
+
60
+ const cleared = writeUnitRuntimeRecord(base, "gate-evaluate", "M100/S02/gates+Q3", startedAt, {
61
+ phase: "recovered",
62
+ harnessAbort: undefined,
63
+ });
64
+ assert.equal(cleared.harnessAbort, undefined, "explicit clear removes stale harness abort");
65
+ assert.equal(
66
+ readUnitHarnessAbort(base, "gate-evaluate", "M100/S02/gates+Q3", startedAt),
67
+ null,
68
+ "cleared harness abort no longer blocks result saves",
69
+ );
70
+
71
+ clearUnitRuntimeRecord(base, "gate-evaluate", "M100/S02/gates+Q3");
72
+ }
73
+
44
74
  console.log("\n=== execute-task durability inspection ===");
45
75
  {
46
76
  let status = await inspectExecuteTaskDurability(base, "M100/S02/T09");