@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,56 @@
1
+ // redact-secrets.test.ts
2
+ // Verifies secret-shaped substrings are replaced while benign text and JSON
3
+ // structure are preserved.
4
+ //
5
+ // Fake secrets are assembled from fragments at runtime so external secret
6
+ // scanners (GitGuardian) and the repo secret-scan never see a literal
7
+ // secret-shaped string in this source file — the runtime value still matches
8
+ // the redaction patterns, which is what the test exercises.
9
+
10
+ import { test } from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { redactSecrets } from "../redact-secrets.js";
13
+
14
+ const PLACEHOLDER = "«redacted»";
15
+
16
+ // Fragment-built fakes (never a full secret literal in source).
17
+ const FAKE = {
18
+ github: "gh" + "p_" + "A".repeat(36), // GitHub PAT shape: ghp_ + 36 chars
19
+ aws: "AKIA" + "IOSFODNN7EXAMPL0", // AWS access key id shape: AKIA + 16 chars
20
+ anthropic: "sk-" + "ant-" + "x".repeat(28), // Anthropic key shape
21
+ bearer: "sk-" + "b".repeat(24), // generic sk- token
22
+ };
23
+
24
+ test("redacts a Bearer authorization token", () => {
25
+ const out = redactSecrets(`Authorization: Bearer ${FAKE.bearer}`);
26
+ assert.ok(out.includes("Authorization:"), "keeps the label context");
27
+ assert.ok(out.includes(PLACEHOLDER), "redacts the token");
28
+ assert.ok(!out.includes(FAKE.bearer), "raw token is gone");
29
+ });
30
+
31
+ test("redacts key=value style secrets", () => {
32
+ const anth = redactSecrets(`ANTHROPIC_API_KEY="${FAKE.anthropic}"`);
33
+ assert.ok(anth.includes(PLACEHOLDER) && !anth.includes(FAKE.anthropic));
34
+ const gh = redactSecrets(`"token": "${FAKE.github}"`);
35
+ assert.ok(gh.includes(PLACEHOLDER) && !gh.includes(FAKE.github));
36
+ });
37
+
38
+ test("redacts an AWS access key id", () => {
39
+ const out = redactSecrets(`aws key ${FAKE.aws} in output`);
40
+ assert.ok(out.includes(PLACEHOLDER) && !out.includes(FAKE.aws));
41
+ });
42
+
43
+ test("leaves benign text mentioning 'token' untouched", () => {
44
+ const line = "The token is passed to the next unit for processing.";
45
+ assert.equal(redactSecrets(line), line);
46
+ });
47
+
48
+ test("redacting a JSON string keeps it valid JSON", () => {
49
+ const original = JSON.stringify({ msg: "using key=SECRETVALUE here", auth: FAKE.github });
50
+ const redacted = redactSecrets(original);
51
+ // Must still parse.
52
+ const parsed = JSON.parse(redacted) as { msg: string; auth: string };
53
+ // The bare GitHub token is redacted; the non-keyword "key=..." stays (no false structure break).
54
+ assert.ok(!redacted.includes(FAKE.github));
55
+ assert.equal(parsed.auth, PLACEHOLDER);
56
+ });
@@ -0,0 +1,73 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Verifies session bootstrap fails closed when flat-phase migration cannot complete.
3
+
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { tmpdir } from "node:os";
9
+
10
+ import { registerHooks } from "../bootstrap/register-hooks.ts";
11
+ import { closeDatabase, insertMilestone, openDatabase } from "../gsd-db.ts";
12
+
13
+ type HookHandler = (event: unknown, ctx: any) => Promise<void> | void;
14
+
15
+ function createSessionStartHandler(): HookHandler {
16
+ const handlers = new Map<string, HookHandler>();
17
+ const pi = {
18
+ on(event: string, handler: HookHandler) {
19
+ handlers.set(event, handler);
20
+ },
21
+ };
22
+
23
+ registerHooks(pi as any, []);
24
+ const sessionStart = handlers.get("session_start");
25
+ assert.ok(sessionStart, "session_start handler should be registered");
26
+ return sessionStart;
27
+ }
28
+
29
+ function makeContext(basePath: string) {
30
+ return {
31
+ cwd: basePath,
32
+ hasUI: false,
33
+ ui: {
34
+ notify: () => {},
35
+ setStatus: () => {},
36
+ setWidget: () => {},
37
+ setWorkingMessage: () => {},
38
+ onTerminalInput: () => () => {},
39
+ },
40
+ model: null,
41
+ modelRegistry: {
42
+ setDisabledModelProviders: () => {},
43
+ },
44
+ sessionManager: {
45
+ getSessionId: () => null,
46
+ },
47
+ setCompactionThresholdOverride: () => {},
48
+ };
49
+ }
50
+
51
+ test("session_start rejects when required flat-phase migration fails", async (t) => {
52
+ const base = mkdtempSync(join(tmpdir(), "gsd-bootstrap-flat-migration-"));
53
+ t.after(() => {
54
+ closeDatabase();
55
+ rmSync(base, { recursive: true, force: true });
56
+ });
57
+
58
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
59
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"), "# M001: Foundation\n", "utf-8");
60
+ openDatabase(join(base, ".gsd", "gsd.db"));
61
+ insertMilestone({ id: "M001", title: "Foundation", status: "active" });
62
+ closeDatabase();
63
+
64
+ writeFileSync(join(base, ".gsd-backups"), "not a directory\n", "utf-8");
65
+
66
+ const sessionStart = createSessionStartHandler();
67
+
68
+ await assert.rejects(
69
+ () => Promise.resolve(sessionStart({}, makeContext(base))),
70
+ /flat-phase migration/,
71
+ );
72
+ assert.ok(existsSync(join(base, ".gsd", "milestones", "M001")), "legacy layout should remain for recovery");
73
+ });
@@ -1,14 +1,22 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
+ import { mkdirSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { randomUUID } from "node:crypto";
3
7
 
4
8
  import { autoSession } from "../auto-runtime-state.ts";
5
9
  import { registerHooks } from "../bootstrap/register-hooks.ts";
6
10
  import { resetToolCallLoopGuard } from "../bootstrap/tool-call-loop-guard.ts";
11
+ import { readUnitHarnessAbort } from "../unit-runtime.ts";
7
12
 
8
13
  type Handler = (event: any, ctx?: any) => Promise<any> | any;
9
14
 
10
15
  function makeHookHarness(): {
11
16
  emitToolCall: (toolName: string, input: Record<string, unknown>) => Promise<any>;
17
+ emitToolResult: (event: Record<string, unknown>) => Promise<void>;
18
+ emitToolExecutionEnd: (event: Record<string, unknown>) => Promise<void>;
19
+ emitAgentEnd: (event: Record<string, unknown>) => Promise<void>;
12
20
  } {
13
21
  const handlers = new Map<string, Handler[]>();
14
22
  const pi = {
@@ -33,9 +41,38 @@ function makeHookHarness(): {
33
41
  assert.ok(loopGuardHandler, "loop-guard tool_call handler should be registered");
34
42
  return loopGuardHandler({ toolCallId: `loop-${callId}`, toolName, input }, ctx);
35
43
  },
44
+ async emitToolResult(event: Record<string, unknown>): Promise<void> {
45
+ callId += 1;
46
+ for (const handler of handlers.get("tool_result") ?? []) {
47
+ await handler({
48
+ toolCallId: `result-${callId}`,
49
+ ...event,
50
+ }, ctx);
51
+ }
52
+ },
53
+ async emitToolExecutionEnd(event: Record<string, unknown>): Promise<void> {
54
+ callId += 1;
55
+ for (const handler of handlers.get("tool_execution_end") ?? []) {
56
+ await handler({
57
+ toolCallId: `exec-${callId}`,
58
+ ...event,
59
+ }, ctx);
60
+ }
61
+ },
62
+ async emitAgentEnd(event: Record<string, unknown>): Promise<void> {
63
+ for (const handler of handlers.get("agent_end") ?? []) {
64
+ await handler(event, ctx);
65
+ }
66
+ },
36
67
  };
37
68
  }
38
69
 
70
+ function makeRuntimeBase(): string {
71
+ const base = join(tmpdir(), `gsd-hook-runtime-${randomUUID()}`);
72
+ mkdirSync(join(base, ".gsd"), { recursive: true });
73
+ return base;
74
+ }
75
+
39
76
  test("register-hooks keeps loop-guard block reason interactive outside auto-mode", async (t) => {
40
77
  autoSession.reset();
41
78
  resetToolCallLoopGuard();
@@ -101,3 +138,241 @@ test("register-hooks rewrites loop-guard block reason for auto-mode per-tool blo
101
138
  assert.match(block.reason, /auto-mode recovery\/replan path/);
102
139
  assert.doesNotMatch(block.reason, /respond to the user in text/);
103
140
  });
141
+
142
+ test("register-hooks does not record normal product tool failures as harness aborts", async (t) => {
143
+ const base = makeRuntimeBase();
144
+ const startedAt = Date.now();
145
+ autoSession.reset();
146
+ resetToolCallLoopGuard();
147
+ autoSession.active = true;
148
+ autoSession.basePath = base;
149
+ autoSession.currentUnit = { type: "gate-evaluate", id: "M001/S01/gates+Q3", startedAt };
150
+ t.after(() => {
151
+ autoSession.reset();
152
+ resetToolCallLoopGuard();
153
+ rmSync(base, { recursive: true, force: true });
154
+ });
155
+
156
+ const { emitToolExecutionEnd } = makeHookHarness();
157
+ await emitToolExecutionEnd({
158
+ toolName: "mcp__gsd-workflow__browser_click",
159
+ isError: true,
160
+ result: {
161
+ content: [{ type: "text", text: "Element not found: #submit" }],
162
+ },
163
+ });
164
+
165
+ const abort = readUnitHarnessAbort(base, "gate-evaluate", "M001/S01/gates+Q3", startedAt);
166
+ assert.equal(abort, null);
167
+ });
168
+
169
+ test("register-hooks does not classify normal gsd_uat_exec nonzero exits as harness aborts", async (t) => {
170
+ const base = makeRuntimeBase();
171
+ const startedAt = Date.now();
172
+ autoSession.reset();
173
+ resetToolCallLoopGuard();
174
+ autoSession.active = true;
175
+ autoSession.basePath = base;
176
+ autoSession.currentUnit = { type: "run-uat", id: "M001/S01", startedAt };
177
+ t.after(() => {
178
+ autoSession.reset();
179
+ resetToolCallLoopGuard();
180
+ rmSync(base, { recursive: true, force: true });
181
+ });
182
+
183
+ const { emitToolExecutionEnd } = makeHookHarness();
184
+ await emitToolExecutionEnd({
185
+ toolName: "gsd_uat_exec",
186
+ isError: true,
187
+ result: {
188
+ content: [{ type: "text", text: "gsd_exec[exec-1] runtime=bash exit=1 duration=12ms" }],
189
+ details: {
190
+ operation: "gsd_uat_exec",
191
+ exit_code: 1,
192
+ aborted: false,
193
+ force_resolved: false,
194
+ },
195
+ },
196
+ });
197
+
198
+ const abort = readUnitHarnessAbort(base, "run-uat", "M001/S01", startedAt);
199
+ assert.equal(abort, null);
200
+ });
201
+
202
+ test("register-hooks records exec deadline timeouts as harness aborts", async () => {
203
+ for (const scenario of [
204
+ { toolName: "gsd_exec", unitType: "gate-evaluate", unitId: "M001/S01/gates+Q3" },
205
+ { toolName: "gsd_uat_exec", unitType: "run-uat", unitId: "M001/S01" },
206
+ ]) {
207
+ const base = makeRuntimeBase();
208
+ const startedAt = Date.now();
209
+ autoSession.reset();
210
+ resetToolCallLoopGuard();
211
+ autoSession.active = true;
212
+ autoSession.basePath = base;
213
+ autoSession.currentUnit = { type: scenario.unitType, id: scenario.unitId, startedAt };
214
+
215
+ try {
216
+ const { emitToolExecutionEnd } = makeHookHarness();
217
+ await emitToolExecutionEnd({
218
+ toolName: scenario.toolName,
219
+ isError: true,
220
+ result: {
221
+ content: [{ type: "text", text: "gsd_exec[exec-timeout] runtime=bash timeout duration=600000ms" }],
222
+ details: {
223
+ operation: scenario.toolName,
224
+ exit_code: null,
225
+ aborted: false,
226
+ force_resolved: false,
227
+ timed_out: true,
228
+ },
229
+ },
230
+ });
231
+
232
+ const abort = readUnitHarnessAbort(base, scenario.unitType, scenario.unitId, startedAt);
233
+ assert.equal(abort?.kind, "tool-error");
234
+ assert.equal(abort?.toolName, scenario.toolName);
235
+ assert.match(abort?.reason ?? "", /timeout/);
236
+ } finally {
237
+ autoSession.reset();
238
+ resetToolCallLoopGuard();
239
+ rmSync(base, { recursive: true, force: true });
240
+ }
241
+ }
242
+ });
243
+
244
+ test("register-hooks preserves retryable harness abort after later successful tools", async (t) => {
245
+ const base = makeRuntimeBase();
246
+ const startedAt = Date.now();
247
+ autoSession.reset();
248
+ resetToolCallLoopGuard();
249
+ autoSession.active = true;
250
+ autoSession.basePath = base;
251
+ autoSession.currentUnit = { type: "run-uat", id: "M001/S01", startedAt };
252
+ t.after(() => {
253
+ autoSession.reset();
254
+ resetToolCallLoopGuard();
255
+ rmSync(base, { recursive: true, force: true });
256
+ });
257
+
258
+ const { emitToolExecutionEnd, emitToolResult } = makeHookHarness();
259
+ await emitToolExecutionEnd({
260
+ toolName: "mcp__gsd-workflow__browser_click",
261
+ isError: true,
262
+ result: {
263
+ content: [{ type: "text", text: "No such tool available: browser_click" }],
264
+ },
265
+ });
266
+
267
+ const recorded = readUnitHarnessAbort(base, "run-uat", "M001/S01", startedAt);
268
+ assert.equal(recorded?.kind, "tool-error");
269
+
270
+ await emitToolResult({
271
+ toolName: "read",
272
+ isError: false,
273
+ input: { path: "README.md" },
274
+ result: {
275
+ content: [{ type: "text", text: "file contents" }],
276
+ },
277
+ });
278
+ assert.equal(
279
+ readUnitHarnessAbort(base, "run-uat", "M001/S01", startedAt)?.kind,
280
+ "tool-error",
281
+ "successful native tool_result must not clear the harness abort",
282
+ );
283
+
284
+ await emitToolExecutionEnd({
285
+ toolName: "read",
286
+ isError: false,
287
+ result: {
288
+ content: [{ type: "text", text: "file contents" }],
289
+ },
290
+ });
291
+ assert.equal(
292
+ readUnitHarnessAbort(base, "run-uat", "M001/S01", startedAt)?.kind,
293
+ "tool-error",
294
+ "successful universal tool_execution_end must not clear the harness abort",
295
+ );
296
+ });
297
+
298
+ test("register-hooks does not record save-tool validation errors as harness aborts", async (t) => {
299
+ const base = makeRuntimeBase();
300
+ const startedAt = Date.now();
301
+ autoSession.reset();
302
+ resetToolCallLoopGuard();
303
+ autoSession.active = true;
304
+ autoSession.basePath = base;
305
+ autoSession.currentUnit = { type: "run-uat", id: "M001/S01", startedAt };
306
+ t.after(() => {
307
+ autoSession.reset();
308
+ resetToolCallLoopGuard();
309
+ rmSync(base, { recursive: true, force: true });
310
+ });
311
+
312
+ const { emitToolExecutionEnd } = makeHookHarness();
313
+ await emitToolExecutionEnd({
314
+ toolName: "gsd_uat_result_save",
315
+ isError: true,
316
+ result: {
317
+ content: [{ type: "text", text: "Error: UAT Assessment requires at least one fresh gsd_uat_exec evidence reference" }],
318
+ details: { operation: "save_uat_result", error: "uat_missing_fresh_evidence" },
319
+ },
320
+ });
321
+
322
+ const abort = readUnitHarnessAbort(base, "run-uat", "M001/S01", startedAt);
323
+ assert.equal(abort, null);
324
+ });
325
+
326
+ test("register-hooks does not record pending approval hard-blocks as harness aborts", async (t) => {
327
+ const base = makeRuntimeBase();
328
+ const startedAt = Date.now();
329
+ autoSession.reset();
330
+ resetToolCallLoopGuard();
331
+ autoSession.active = true;
332
+ autoSession.basePath = base;
333
+ autoSession.currentUnit = { type: "gate-evaluate", id: "M001/S01/gates+Q3", startedAt };
334
+ t.after(() => {
335
+ autoSession.reset();
336
+ resetToolCallLoopGuard();
337
+ rmSync(base, { recursive: true, force: true });
338
+ });
339
+
340
+ const { emitToolExecutionEnd } = makeHookHarness();
341
+ await emitToolExecutionEnd({
342
+ toolName: "gsd_summary_save",
343
+ isError: true,
344
+ result: {
345
+ content: [{
346
+ type: "text",
347
+ text: 'HARD BLOCK: Discussion gate "depth:M001" has not been confirmed by the user.',
348
+ }],
349
+ },
350
+ });
351
+
352
+ const abort = readUnitHarnessAbort(base, "gate-evaluate", "M001/S01/gates+Q3", startedAt);
353
+ assert.equal(abort, null);
354
+ });
355
+
356
+ test("register-hooks only records agent_end turn aborts for aborted or error stop reasons", async (t) => {
357
+ const base = makeRuntimeBase();
358
+ const startedAt = Date.now();
359
+ autoSession.reset();
360
+ resetToolCallLoopGuard();
361
+ autoSession.active = true;
362
+ autoSession.basePath = base;
363
+ autoSession.currentUnit = { type: "gate-evaluate", id: "M001/S01/gates+Q3", startedAt };
364
+ t.after(() => {
365
+ autoSession.reset();
366
+ resetToolCallLoopGuard();
367
+ rmSync(base, { recursive: true, force: true });
368
+ });
369
+
370
+ const { emitAgentEnd } = makeHookHarness();
371
+ await emitAgentEnd({
372
+ abortOrigin: "auto-context",
373
+ messages: [{ stopReason: "stop" }],
374
+ });
375
+
376
+ const abort = readUnitHarnessAbort(base, "gate-evaluate", "M001/S01/gates+Q3", startedAt);
377
+ assert.equal(abort, null);
378
+ });
@@ -83,7 +83,7 @@ test("defaultRepositoryTargets returns [project] for a single-repo project regis
83
83
  assert.deepEqual(defaultRepositoryTargets(registry), ["project"]);
84
84
  });
85
85
 
86
- test("defaultRepositoryTargets returns [project] for a parent-mode registry", (t) => {
86
+ test("defaultRepositoryTargets returns declared child repos for a parent-mode registry", (t) => {
87
87
  const base = mkdtempSync(join(tmpdir(), "gsd-repo-registry-"));
88
88
  t.after(() => rmSync(base, { recursive: true, force: true }));
89
89
  mkdirSync(join(base, ".gsd"), { recursive: true });
@@ -98,7 +98,46 @@ test("defaultRepositoryTargets returns [project] for a parent-mode registry", (t
98
98
  },
99
99
  });
100
100
 
101
- assert.deepEqual(defaultRepositoryTargets(registry), ["project"]);
101
+ assert.deepEqual(defaultRepositoryTargets(registry), ["frontend"]);
102
+ });
103
+
104
+ test("defaultRepositoryTargets returns all child repos (declaration order) in parent mode", (t) => {
105
+ const base = mkdtempSync(join(tmpdir(), "gsd-repo-registry-"));
106
+ t.after(() => rmSync(base, { recursive: true, force: true }));
107
+ mkdirSync(join(base, ".gsd"), { recursive: true });
108
+ mkdirSync(join(base, "frontend"), { recursive: true });
109
+ mkdirSync(join(base, "backend"), { recursive: true });
110
+
111
+ const registry = createRepositoryRegistryFromPreferences(base, {
112
+ workspace: {
113
+ mode: "parent",
114
+ repositories: {
115
+ frontend: { path: "frontend" },
116
+ backend: { path: "backend" },
117
+ },
118
+ },
119
+ });
120
+
121
+ assert.deepEqual(defaultRepositoryTargets(registry), ["frontend", "backend"]);
122
+ });
123
+
124
+ test("defaultRepositoryTargets excludes the implicit project repo in parent mode", (t) => {
125
+ const base = mkdtempSync(join(tmpdir(), "gsd-repo-registry-"));
126
+ t.after(() => rmSync(base, { recursive: true, force: true }));
127
+ mkdirSync(join(base, ".gsd"), { recursive: true });
128
+ mkdirSync(join(base, "frontend"), { recursive: true });
129
+
130
+ const registry = createRepositoryRegistryFromPreferences(base, {
131
+ workspace: {
132
+ mode: "parent",
133
+ repositories: {
134
+ frontend: { path: "frontend" },
135
+ },
136
+ },
137
+ });
138
+
139
+ const targets = defaultRepositoryTargets(registry);
140
+ assert.ok(!targets.includes("project"), "implicit project repo must not be a default target in parent mode");
102
141
  });
103
142
 
104
143
  test("repository registry keeps project root anchored to .gsd project in monorepo subdirectory", (t) => {
@@ -16,6 +16,13 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
  import { tmpdir } from "node:os";
18
18
  import { DISPATCH_RULES, type DispatchContext } from "../auto-dispatch.ts";
19
+ import {
20
+ closeDatabase,
21
+ insertAssessment,
22
+ insertMilestone,
23
+ insertSlice,
24
+ openDatabase,
25
+ } from "../gsd-db.ts";
19
26
  import type { GSDState } from "../types.ts";
20
27
  import type { GSDPreferences } from "../preferences.ts";
21
28
 
@@ -87,6 +94,54 @@ describe("require_slice_discussion dispatch rule (#3454)", () => {
87
94
  }
88
95
  });
89
96
 
97
+ test("includes prior non-PASS UAT warning when the just-closed slice failed", async () => {
98
+ const basePath = makeBasePath("prior-fail");
99
+ const dbDir = mkdtempSync(join(tmpdir(), "gsd-req-slice-db-"));
100
+ const dbPath = join(dbDir, "test.db");
101
+ try {
102
+ openDatabase(dbPath);
103
+ mkdirSync(join(basePath, ".gsd", "milestones", "M001", "slices", "S02"), { recursive: true });
104
+
105
+ insertMilestone({ id: "M001", title: "Test milestone" });
106
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Completed slice", status: "complete", sequence: 1 });
107
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Next slice", status: "pending", sequence: 2 });
108
+
109
+ const assessmentPath = ".gsd/milestones/M001/slices/S01/S01-ASSESSMENT.md";
110
+ const assessmentBody = [
111
+ "---",
112
+ "verdict: fail",
113
+ "---",
114
+ "",
115
+ "# S01 UAT Assessment",
116
+ ].join("\n");
117
+ writeFileSync(join(basePath, assessmentPath), assessmentBody, "utf-8");
118
+ insertAssessment({
119
+ path: assessmentPath,
120
+ milestoneId: "M001",
121
+ sliceId: "S01",
122
+ status: "fail",
123
+ scope: "run-uat",
124
+ fullContent: assessmentBody,
125
+ });
126
+
127
+ const prefs = { phases: { require_slice_discussion: true } } as unknown as GSDPreferences;
128
+ const state = buildState({ activeSlice: { id: "S02", title: "Next slice" } });
129
+ const action = await findRule().match(buildCtx(basePath, prefs, state));
130
+
131
+ assert.ok(action, "rule must still pause for discussion");
132
+ assert.strictEqual(action!.action, "stop");
133
+ if (action!.action === "stop") {
134
+ assert.match(action!.reason, /S02 requires discussion/);
135
+ assert.match(action!.reason, /slice just closed \(S01\)/);
136
+ assert.match(action!.reason, /non-PASS UAT verdict \(FAIL\)/);
137
+ }
138
+ } finally {
139
+ closeDatabase();
140
+ rmSync(basePath, { recursive: true, force: true });
141
+ rmSync(dbDir, { recursive: true, force: true });
142
+ }
143
+ });
144
+
90
145
  // ─── Negative cases: rule falls through ───────────────────────────────
91
146
 
92
147
  test("falls through (null) when preference is disabled", async () => {
@@ -3,11 +3,11 @@
3
3
 
4
4
  import test from "node:test";
5
5
  import assert from "node:assert/strict";
6
- import { closeSync, ftruncateSync, openSync, statSync, writeFileSync } from "node:fs";
6
+ import { closeSync, ftruncateSync, openSync, statSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
 
9
- import { captureRootDirtySnapshot } from "../root-write-leak-guard.ts";
10
- import { cleanup, createFile, git, makeTempRepo } from "./test-utils.ts";
9
+ import { captureRootDirtySnapshot, detectRootWriteLeak } from "../root-write-leak-guard.ts";
10
+ import { cleanup, createFile, makeTempRepo } from "./test-utils.ts";
11
11
 
12
12
  test("captureRootDirtySnapshot ignores unignored GSD root runtime directories", () => {
13
13
  const base = makeTempRepo("gsd-root-dirty-runtime-");
@@ -34,11 +34,7 @@ test("captureRootDirtySnapshot does not read dirty files larger than Node's Buff
34
34
  const relPath = "large.bin";
35
35
  const absPath = join(base, relPath);
36
36
 
37
- writeFileSync(absPath, "tracked\n", "utf-8");
38
- git(base, "add", relPath);
39
- git(base, "commit", "-m", "track large fixture");
40
-
41
- const fd = openSync(absPath, "r+");
37
+ const fd = openSync(absPath, "w");
42
38
  try {
43
39
  ftruncateSync(fd, 2_200 * 1024 * 1024);
44
40
  } finally {
@@ -51,9 +47,35 @@ test("captureRootDirtySnapshot does not read dirty files larger than Node's Buff
51
47
  const snapshot = captureRootDirtySnapshot(base);
52
48
  const entry = snapshot.get(relPath);
53
49
 
54
- assert.equal(entry?.status, "M");
50
+ assert.equal(entry?.status, "??");
55
51
  assert.match(entry?.fingerprint ?? "", /^large:\d+:/);
56
52
  } finally {
57
53
  cleanup(base);
58
54
  }
59
55
  });
56
+
57
+ test("detectRootWriteLeak reports changed baseline untracked root files", () => {
58
+ const base = makeTempRepo("gsd-root-dirty-baseline-");
59
+ try {
60
+ const relPath = "notes/todo.md";
61
+ createFile(base, relPath, "before\n");
62
+ const before = captureRootDirtySnapshot(base);
63
+
64
+ createFile(base, relPath, "after\n");
65
+ const leak = detectRootWriteLeak({
66
+ rootPath: base,
67
+ worktreePath: join(base, ".gsd", "worktrees", "M001"),
68
+ unitType: "execute-task",
69
+ unitId: "M001/S01/T01",
70
+ before,
71
+ });
72
+
73
+ assert.ok(leak, "changed baseline untracked file should be reported as a leak");
74
+ assert.deepEqual(
75
+ leak.files.map((file) => file.path),
76
+ [relPath],
77
+ );
78
+ } finally {
79
+ cleanup(base);
80
+ }
81
+ });