@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
@@ -186,6 +186,58 @@ test("uok gitops turn action commit creates commit with unit trailer", () => {
186
186
  }
187
187
  });
188
188
 
189
+ test("uok gitops turn action commit defaults to child repositories in parent mode", () => {
190
+ const root = mkdtempSync(join(tmpdir(), "gsd-uok-gitops-parent-commit-"));
191
+ try {
192
+ initRepo(root);
193
+ mkdirSync(join(root, ".gsd"), { recursive: true });
194
+ mkdirSync(join(root, "frontend"), { recursive: true });
195
+ mkdirSync(join(root, "backend"), { recursive: true });
196
+ initRepo(join(root, "frontend"));
197
+ initRepo(join(root, "backend"));
198
+ writeFileSync(join(root, ".gitignore"), "frontend/\nbackend/\n", "utf-8");
199
+ run("git add .gitignore", root);
200
+ run('git commit -m "chore: ignore nested repos"', root);
201
+ writeFileSync(join(root, ".gsd", "PREFERENCES.md"), `---
202
+ version: 1
203
+ workspace:
204
+ mode: parent
205
+ repositories:
206
+ frontend:
207
+ path: frontend
208
+ backend:
209
+ path: backend
210
+ ---
211
+ `, "utf-8");
212
+ run("git add .gsd/PREFERENCES.md", root);
213
+ run('git commit -m "chore: configure parent workspace repos"', root);
214
+
215
+ const rootHeadBefore = run("git rev-parse HEAD", root);
216
+ writeFileSync(join(root, "frontend", "README.md"), "# frontend dirty\n", "utf-8");
217
+ writeFileSync(join(root, "backend", "README.md"), "# backend dirty\n", "utf-8");
218
+
219
+ const result = runTurnGitAction({
220
+ basePath: root,
221
+ action: "commit",
222
+ unitType: "execute-task",
223
+ unitId: "M001/S01/T-parent",
224
+ });
225
+
226
+ assert.equal(result.status, "ok");
227
+ assert.equal(result.commitMessages?.project, undefined);
228
+ assert.equal(typeof result.commitMessages?.frontend, "string");
229
+ assert.equal(typeof result.commitMessages?.backend, "string");
230
+ assert.equal(result.commitMessage, result.commitMessages?.frontend);
231
+ assert.equal(run("git rev-parse HEAD", root), rootHeadBefore);
232
+ assert.equal(run("git status --porcelain", join(root, "frontend")), "");
233
+ assert.equal(run("git status --porcelain", join(root, "backend")), "");
234
+ assert.ok(run("git log -1 --pretty=%B", join(root, "frontend")).includes("GSD-Unit: M001/S01/T-parent"));
235
+ assert.ok(run("git log -1 --pretty=%B", join(root, "backend")).includes("GSD-Unit: M001/S01/T-parent"));
236
+ } finally {
237
+ rmSync(root, { recursive: true, force: true });
238
+ }
239
+ });
240
+
189
241
  test("uok gitops turn action commit reuses the collected dirty status", () => {
190
242
  const repo = makeRepo();
191
243
  const wrapperDir = mkdtempSync(join(tmpdir(), "gsd-uok-git-wrapper-"));
@@ -23,7 +23,7 @@ import {
23
23
 
24
24
  function makeProjectDir(label = "gsd-vsp-"): string {
25
25
  const dir = realpathSync(mkdtempSync(join(tmpdir(), label)));
26
- mkdirSync(join(dir, ".gsd", "milestones", "M001"), { recursive: true });
26
+ mkdirSync(join(dir, ".gsd", "phases", "01-m001"), { recursive: true });
27
27
  return dir;
28
28
  }
29
29
 
@@ -236,4 +236,56 @@ describe("validator-scope-parity: scope uses canonical projectRoot not worktree
236
236
  "verifyExpectedArtifactForScope should return false when artifact is absent",
237
237
  );
238
238
  });
239
+
240
+ test("verifyExpectedArtifactForScope rejects zero-slice scoped plan-milestone roadmaps", () => {
241
+ const ws = createWorkspace(base);
242
+ const scope = scopeMilestone(ws, "M001");
243
+
244
+ writeFileSync(scope.roadmapFile(), [
245
+ "# M001: Placeholder",
246
+ "",
247
+ "## Slices",
248
+ "",
249
+ "_TBD_",
250
+ "",
251
+ ].join("\n"));
252
+
253
+ const ready = verifyExpectedArtifactForScope(scope, "plan-milestone", "M001");
254
+ assert.equal(
255
+ ready,
256
+ false,
257
+ "scoped plan-milestone verification must parse roadmap content, not only check existence",
258
+ );
259
+ });
260
+
261
+ test("verifyExpectedArtifactForScope verifies the scoped plan-milestone roadmap path", () => {
262
+ const ws = createWorkspace(base);
263
+ const realScope = scopeMilestone(ws, "M001");
264
+ const scopedRoadmap = join(base, "scoped-M001-ROADMAP.md");
265
+ const scope = {
266
+ ...realScope,
267
+ roadmapFile: () => scopedRoadmap,
268
+ };
269
+
270
+ writeFileSync(scopedRoadmap, [
271
+ "# M001: Scoped roadmap",
272
+ "",
273
+ "## Slices",
274
+ "",
275
+ "- [ ] **S01: First slice** `risk:low` `depends:[]`",
276
+ " > After this: a real slice exists.",
277
+ "",
278
+ ].join("\n"));
279
+
280
+ assert.equal(
281
+ resolveExpectedArtifactPathForScope(scope, "plan-milestone", "M001"),
282
+ scopedRoadmap,
283
+ "scoped plan-milestone artifact path must come from scope.roadmapFile()",
284
+ );
285
+ assert.equal(
286
+ verifyExpectedArtifactForScope(scope, "plan-milestone", "M001"),
287
+ true,
288
+ "scoped plan-milestone verification must read the roadmap from scope.roadmapFile()",
289
+ );
290
+ });
239
291
  });
@@ -12,6 +12,7 @@ import {
12
12
  getArtifact,
13
13
  getAssessment,
14
14
  insertAssessment,
15
+ insertGateRow,
15
16
  insertMilestone,
16
17
  upsertRequirement,
17
18
  getAllMilestones,
@@ -21,6 +22,7 @@ import { claimMilestoneLease, getMilestoneLease } from "../db/milestone-leases.t
21
22
  import { deriveState, invalidateStateCache } from "../state.ts";
22
23
  import { autoSession } from "../auto-runtime-state.ts";
23
24
  import { normalizeRealPath } from "../paths.ts";
25
+ import { recordUnitHarnessAbort } from "../unit-runtime.ts";
24
26
  import { markApprovalGateVerified, markDepthVerified, clearDiscussionFlowState, loadWriteGateSnapshot, setPendingGate } from "../bootstrap/write-gate.ts";
25
27
  import {
26
28
  executeCompleteMilestone,
@@ -37,6 +39,11 @@ import {
37
39
  executeValidateMilestone,
38
40
  executeUatResultSave,
39
41
  } from "../tools/workflow-tool-executors.ts";
42
+ import {
43
+ initNotificationStore,
44
+ readNotifications,
45
+ _resetNotificationStore,
46
+ } from "../notification-store.ts";
40
47
 
41
48
  function makeTmpBase(): string {
42
49
  const base = join(tmpdir(), `gsd-workflow-executors-${randomUUID()}`);
@@ -910,6 +917,104 @@ test("executeUatResultSave accepts gsd_uat_exec evidence written in a milestone
910
917
  }
911
918
  });
912
919
 
920
+ test("executeUatResultSave leaves UAT pending after a harness-aborted turn", async () => {
921
+ const base = makeTmpBase();
922
+ const worktree = join(base, ".gsd", "worktrees", "M001");
923
+ const worktreeExecDir = join(worktree, ".gsd", "exec");
924
+ const evidenceId = "harness-aborted-uat-evidence";
925
+ const startedAt = Date.now();
926
+ try {
927
+ openTestDb(base);
928
+ seedMilestone("M001", "Milestone One");
929
+ seedSlice("M001", "S04", "complete");
930
+ mkdirSync(worktreeExecDir, { recursive: true });
931
+ writeFileSync(
932
+ join(worktreeExecDir, `${evidenceId}.meta.json`),
933
+ JSON.stringify({
934
+ id: evidenceId,
935
+ metadata: {
936
+ kind: "uat_exec",
937
+ milestoneId: "M001",
938
+ sliceId: "S04",
939
+ checkId: "UAT-01",
940
+ intent: "uat-runtime-check",
941
+ },
942
+ }),
943
+ "utf-8",
944
+ );
945
+ _getAdapter()!.prepare(
946
+ `INSERT INTO quality_gates (milestone_id, slice_id, gate_id, scope, task_id, status)
947
+ VALUES (?, ?, ?, ?, ?, ?)`,
948
+ ).run("M001", "S04", "UAT", "slice", "", "pending");
949
+ autoSession.reset();
950
+ autoSession.active = true;
951
+ autoSession.basePath = base;
952
+ autoSession.currentUnit = { type: "run-uat", id: "M001/S04", startedAt, workspaceRoot: worktree };
953
+ recordUnitHarnessAbort(base, "run-uat", "M001/S04", startedAt, {
954
+ kind: "tool-loop-guard",
955
+ reason: "Tool loop detected (repeated tool): browser_click called 7 times this turn.",
956
+ toolName: "browser_click",
957
+ count: 7,
958
+ });
959
+
960
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
961
+ milestoneId: "M001",
962
+ sliceId: "S04",
963
+ uatType: "runtime-executable",
964
+ verdict: "FAIL",
965
+ checks: [{
966
+ id: "UAT-01",
967
+ description: "Runtime behavior could not be fully evaluated before harness abort",
968
+ mode: "runtime",
969
+ result: "FAIL",
970
+ evidence: [{ kind: "gsd_uat_exec", ref: evidenceId }],
971
+ notes: "Partial failure should be retried because the harness aborted the turn.",
972
+ }],
973
+ presentation: {
974
+ surface: "mcp",
975
+ presentedTools: [
976
+ "gsd_uat_exec",
977
+ "gsd_uat_result_save",
978
+ "gsd_resume",
979
+ "gsd_milestone_status",
980
+ "gsd_journal_query",
981
+ ],
982
+ blockedTools: [
983
+ { name: "gsd_exec", reason: "forbidden during run-uat" },
984
+ { name: "gsd_summary_save", reason: "forbidden during run-uat" },
985
+ { name: "gsd_save_gate_result", reason: "forbidden during run-uat" },
986
+ ],
987
+ },
988
+ notes: "Partial UAT result after harness abort.",
989
+ }, worktree));
990
+
991
+ assert.equal(result.isError, true);
992
+ assert.equal(result.details.operation, "save_uat_result");
993
+ assert.equal(result.details.error, "harness_aborted_needs_retry");
994
+ assert.equal(result.details.retryable, true);
995
+ assert.equal(result.details.harnessAbortKind, "tool-loop-guard");
996
+ assert.equal(
997
+ existsSync(join(base, ".gsd", "uat", "M001", "S04", "attempt-1.json")),
998
+ false,
999
+ "harness-aborted UAT should not persist an attempt artifact",
1000
+ );
1001
+ assert.equal(
1002
+ existsSync(join(base, ".gsd", "phases", "01-m001", "01-04-ASSESSMENT.md")),
1003
+ false,
1004
+ "harness-aborted UAT should not write an ASSESSMENT verdict",
1005
+ );
1006
+ const row = _getAdapter()!.prepare(
1007
+ "SELECT status, verdict FROM quality_gates WHERE milestone_id = ? AND slice_id = ? AND gate_id = ?",
1008
+ ).get("M001", "S04", "UAT") as Record<string, unknown>;
1009
+ assert.equal(row.status, "pending");
1010
+ assert.equal(row.verdict, "");
1011
+ } finally {
1012
+ autoSession.reset();
1013
+ closeDatabase();
1014
+ cleanup(base);
1015
+ }
1016
+ });
1017
+
913
1018
  test("executeUatResultSave supplies canonical presentation and normalizes verdict casing", async () => {
914
1019
  const base = makeTmpBase();
915
1020
  const worktree = join(base, ".gsd", "worktrees", "M001");
@@ -1100,13 +1205,14 @@ test("executeUatResultSave merges canonical plan ID and read-only tools when pre
1100
1205
  }
1101
1206
  });
1102
1207
 
1103
- test("executeUatResultSave surfaces the worktree validation path for NEEDS-HUMAN checks", async () => {
1208
+ test("executeUatResultSave surfaces the worktree validation path and notification for NEEDS-HUMAN checks", async () => {
1104
1209
  const base = makeTmpBase();
1105
1210
  const worktree = join(base, ".gsd", "worktrees", "M001");
1106
1211
  const worktreeExecDir = join(worktree, ".gsd", "exec");
1107
1212
  const evidenceId = "uat-human-validation-evidence";
1108
1213
  try {
1109
1214
  openTestDb(base);
1215
+ initNotificationStore(base);
1110
1216
  seedMilestone("M001", "Milestone One");
1111
1217
  seedSlice("M001", "S07", "complete");
1112
1218
  mkdirSync(worktreeExecDir, { recursive: true });
@@ -1166,7 +1272,17 @@ test("executeUatResultSave surfaces the worktree validation path for NEEDS-HUMAN
1166
1272
  assert.match(assessment, /## Manual Validation/);
1167
1273
  assert.ok(assessment.includes(worktree), "assessment should include the worktree checkout path");
1168
1274
  assert.match(assessment, /git worktree/);
1275
+
1276
+ const notifications = readNotifications(base, { kind: "uat-needs-human", scope: "M001/S07" });
1277
+ assert.equal(notifications.length, 1);
1278
+ assert.equal(notifications[0].severity, "warning");
1279
+ assert.equal(notifications[0].source, "notify");
1280
+ assert.equal(
1281
+ notifications[0].message,
1282
+ "UAT for M001/S07 has NEEDS-HUMAN checks awaiting human validation",
1283
+ );
1169
1284
  } finally {
1285
+ _resetNotificationStore();
1170
1286
  closeDatabase();
1171
1287
  cleanup(base);
1172
1288
  }
@@ -1757,6 +1873,97 @@ test("executeSaveGateResult validates inputs and persists verdicts", async () =>
1757
1873
  }
1758
1874
  });
1759
1875
 
1876
+ test("executeSaveGateResult leaves quality gates pending after a retryable tool error", async () => {
1877
+ const base = makeTmpBase();
1878
+ const startedAt = Date.now();
1879
+ try {
1880
+ openTestDb(base);
1881
+ seedMilestone("M005", "Milestone Five");
1882
+ seedSlice("M005", "S05", "pending");
1883
+ insertGateRow({ milestoneId: "M005", sliceId: "S05", gateId: "Q3", scope: "slice" });
1884
+ autoSession.reset();
1885
+ autoSession.active = true;
1886
+ autoSession.basePath = base;
1887
+ autoSession.currentUnit = { type: "plan-slice", id: "M005/S05", startedAt };
1888
+ recordUnitHarnessAbort(base, "plan-slice", "M005/S05", startedAt, {
1889
+ kind: "tool-error",
1890
+ reason: "Input validation error: selector is required",
1891
+ toolName: "browser_click",
1892
+ });
1893
+
1894
+ const result = await inProjectDir(base, () => executeSaveGateResult({
1895
+ milestoneId: "M005",
1896
+ sliceId: "S05",
1897
+ gateId: "Q3",
1898
+ verdict: "flag",
1899
+ rationale: "Partial gate failure after harness abort.",
1900
+ findings: "This should not be persisted as a product-quality finding.",
1901
+ }, base));
1902
+
1903
+ assert.equal(result.isError, true);
1904
+ assert.equal(result.details.operation, "save_gate_result");
1905
+ assert.equal(result.details.error, "harness_aborted_needs_retry");
1906
+ assert.equal(result.details.retryable, true);
1907
+ assert.equal(result.details.harnessAbortKind, "tool-error");
1908
+ const row = _getAdapter()!.prepare(
1909
+ "SELECT status, verdict, rationale, findings FROM quality_gates WHERE milestone_id = ? AND slice_id = ? AND gate_id = ? AND task_id = ''",
1910
+ ).get("M005", "S05", "Q3") as Record<string, unknown>;
1911
+ assert.equal(row.status, "pending");
1912
+ assert.equal(row.verdict, "");
1913
+ assert.equal(row.rationale, "");
1914
+ assert.equal(row.findings, "");
1915
+ } finally {
1916
+ autoSession.reset();
1917
+ closeDatabase();
1918
+ cleanup(base);
1919
+ }
1920
+ });
1921
+
1922
+ test("executeSaveGateResult leaves quality gates pending after a turn abort", async () => {
1923
+ const base = makeTmpBase();
1924
+ const startedAt = Date.now();
1925
+ try {
1926
+ openTestDb(base);
1927
+ seedMilestone("M005", "Milestone Five");
1928
+ seedSlice("M005", "S05", "pending");
1929
+ insertGateRow({ milestoneId: "M005", sliceId: "S05", gateId: "Q3", scope: "slice" });
1930
+ autoSession.reset();
1931
+ autoSession.active = true;
1932
+ autoSession.basePath = base;
1933
+ autoSession.currentUnit = { type: "plan-slice", id: "M005/S05", startedAt };
1934
+ recordUnitHarnessAbort(base, "plan-slice", "M005/S05", startedAt, {
1935
+ kind: "turn-abort",
1936
+ reason: "Agent turn aborted before the gate evaluation completed. origin=timeout stopReason=aborted",
1937
+ });
1938
+
1939
+ const result = await inProjectDir(base, () => executeSaveGateResult({
1940
+ milestoneId: "M005",
1941
+ sliceId: "S05",
1942
+ gateId: "Q3",
1943
+ verdict: "flag",
1944
+ rationale: "Partial gate failure after a turn abort.",
1945
+ findings: "This should not be persisted as a product-quality finding.",
1946
+ }, base));
1947
+
1948
+ assert.equal(result.isError, true);
1949
+ assert.equal(result.details.operation, "save_gate_result");
1950
+ assert.equal(result.details.error, "harness_aborted_needs_retry");
1951
+ assert.equal(result.details.retryable, true);
1952
+ assert.equal(result.details.harnessAbortKind, "turn-abort");
1953
+ const row = _getAdapter()!.prepare(
1954
+ "SELECT status, verdict, rationale, findings FROM quality_gates WHERE milestone_id = ? AND slice_id = ? AND gate_id = ? AND task_id = ''",
1955
+ ).get("M005", "S05", "Q3") as Record<string, unknown>;
1956
+ assert.equal(row.status, "pending");
1957
+ assert.equal(row.verdict, "");
1958
+ assert.equal(row.rationale, "");
1959
+ assert.equal(row.findings, "");
1960
+ } finally {
1961
+ autoSession.reset();
1962
+ closeDatabase();
1963
+ cleanup(base);
1964
+ }
1965
+ });
1966
+
1760
1967
  test("executeSaveGateResult fails when no canonical gate row is updated", async () => {
1761
1968
  const base = makeTmpBase();
1762
1969
  try {
@@ -8,6 +8,7 @@ import {
8
8
  rmSync,
9
9
  realpathSync,
10
10
  symlinkSync,
11
+ writeFileSync,
11
12
  } from "node:fs";
12
13
  import { tmpdir } from "node:os";
13
14
  import { join } from "node:path";
@@ -118,17 +119,17 @@ describe("scopeMilestone path methods", () => {
118
119
  rmSync(projectDir, { recursive: true, force: true });
119
120
  });
120
121
 
121
- test("produces correct paths for a known milestone ID", () => {
122
+ test("produces flat-phase paths for a known milestone ID", () => {
122
123
  const ws = createWorkspace(projectDir);
123
124
  const scope = scopeMilestone(ws, MID);
124
125
  const gsd = ws.contract.projectGsd;
125
126
 
126
127
  assert.equal(scope.milestoneId, MID);
127
- assert.equal(scope.contextFile(), join(gsd, "milestones", MID, `${MID}-CONTEXT.md`));
128
- assert.equal(scope.roadmapFile(), join(gsd, "milestones", MID, `${MID}-ROADMAP.md`));
128
+ assert.equal(scope.contextFile(), join(gsd, "phases", "01-m001", "01-CONTEXT.md"));
129
+ assert.equal(scope.roadmapFile(), join(gsd, "phases", "01-m001", "01-ROADMAP.md"));
129
130
  assert.equal(scope.stateFile(), join(gsd, "STATE.md"));
130
131
  assert.equal(scope.dbPath(), ws.contract.projectDb);
131
- assert.equal(scope.milestoneDir(), join(gsd, "milestones", MID));
132
+ assert.equal(scope.milestoneDir(), join(gsd, "phases", "01-m001"));
132
133
  assert.equal(scope.metaJson(), join(gsd, `${MID}-META.json`));
133
134
  });
134
135
 
@@ -144,6 +145,19 @@ describe("scopeMilestone path methods", () => {
144
145
  assert.equal(scope1.milestoneDir(), scope2.milestoneDir());
145
146
  assert.equal(scope1.metaJson(), scope2.metaJson());
146
147
  });
148
+
149
+ test("uses legacy milestone paths when legacy content exists", () => {
150
+ const legacyDir = join(projectDir, ".gsd", "milestones", MID);
151
+ mkdirSync(legacyDir, { recursive: true });
152
+ writeFileSync(join(legacyDir, `${MID}-CONTEXT.md`), "# Legacy context\n");
153
+
154
+ const ws = createWorkspace(projectDir);
155
+ const scope = scopeMilestone(ws, MID);
156
+
157
+ assert.equal(scope.contextFile(), join(legacyDir, `${MID}-CONTEXT.md`));
158
+ assert.equal(scope.roadmapFile(), join(legacyDir, `${MID}-ROADMAP.md`));
159
+ assert.equal(scope.milestoneDir(), legacyDir);
160
+ });
147
161
  });
148
162
 
149
163
  describe("createWorkspace: contract.projectGsd is realpath-canonicalized when basePath is a symlink", () => {
@@ -107,7 +107,7 @@ function makeDeps(
107
107
  // real `getAutoWorktreePath` / `createAutoWorktree` / etc. and the
108
108
  // success/existing/failure branches won't fire as expected.
109
109
  const deps: LegacyTestDeps = {
110
- mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
110
+ mergeMilestone: () => ({ pushed: false, codeFilesChanged: true }),
111
111
  worktreeProjection: new WorktreeStateProjection(),
112
112
  // ADR-016 phase 2 / C4 (#5627): GitServiceImpl constructor → factory.
113
113
  gitServiceFactory: () => ({}) as unknown as ReturnType<
@@ -283,7 +283,7 @@ describe("worktree journal events", () => {
283
283
 
284
284
  const s = makeSession({ basePath: wt, originalBasePath: tmp });
285
285
  const deps = makeDeps({
286
- mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
286
+ mergeMilestone: () => ({ pushed: false, codeFilesChanged: true }),
287
287
  });
288
288
  process.chdir(wt);
289
289
 
@@ -304,7 +304,7 @@ describe("worktree journal events", () => {
304
304
  const wt = setupMergeWorktree(tmp, "M001");
305
305
  const s = makeSession({ basePath: wt, originalBasePath: tmp });
306
306
  const deps = makeDeps({
307
- mergeMilestoneToMain: () => { throw new Error("conflict in main"); },
307
+ mergeMilestone: () => { throw new Error("conflict in main"); },
308
308
  });
309
309
  // Since #4380, mergeAndExit re-throws all errors after emitting the journal
310
310
  // event and restoring state. Lifecycle now wraps that throw in a typed
@@ -347,7 +347,7 @@ describe("worktree journal events", () => {
347
347
  const s = makeSession({ basePath: wt, originalBasePath: tmp });
348
348
  let attempt = 0;
349
349
  const deps = makeDeps({
350
- mergeMilestoneToMain: () => {
350
+ mergeMilestone: () => {
351
351
  attempt += 1;
352
352
  throw new MergeConflictError(
353
353
  attempt === 1 ? ["src/a.ts"] : ["src/b.ts", "src/c.ts"],
@@ -46,7 +46,7 @@ function makeDeps(
46
46
  ): LegacyTestDeps & { calls: CallLog[] } {
47
47
  const calls: CallLog[] = [];
48
48
  // ADR-016 phase 2 / C-track close-out: WorktreeLifecycleDeps is now a
49
- // 3-field bag (gitServiceFactory, worktreeProjection, mergeMilestoneToMain).
49
+ // 3-field bag (gitServiceFactory, worktreeProjection, mergeMilestone).
50
50
  // Tests still pass legacy override hooks via `LegacyTestDeps` — Lifecycle
51
51
  // ignores the extras structurally and reads them through the C1-healing
52
52
  // primitive-override pattern when stubs are needed.
@@ -73,7 +73,7 @@ function makeDeps(
73
73
  },
74
74
  autoWorktreeBranch: (mid: string) => `milestone/${mid}`,
75
75
  teardownAutoWorktree: () => {},
76
- mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
76
+ mergeMilestone: () => ({ pushed: false, codeFilesChanged: true }),
77
77
  ...overrides,
78
78
  };
79
79
  return deps;
@@ -23,7 +23,7 @@
23
23
  * - syncWorktreeStateBack skips non-standard milestone projection dir names
24
24
  */
25
25
 
26
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from 'node:fs';
26
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, statSync } from 'node:fs';
27
27
  import { join } from 'node:path';
28
28
  import { tmpdir } from 'node:os';
29
29
 
@@ -148,6 +148,36 @@ describe('worktree-sync-milestones', async () => {
148
148
  }
149
149
  }
150
150
 
151
+ // ─── 3c. artifact file/dir collisions preserve existing worktree files ─
152
+ console.log('\n=== 3c. artifact file/dir collisions preserve existing worktree files (#1157) ===');
153
+ {
154
+ const mainBase = createBase('main');
155
+ const wtBase = createBase('wt');
156
+
157
+ try {
158
+ const mainSliceDir = join(mainBase, '.gsd', 'milestones', 'M001', 'slices', 'S01');
159
+ mkdirSync(join(mainSliceDir, 'S01-SUMMARY.md'), { recursive: true });
160
+ mkdirSync(join(mainSliceDir, 'S01-ASSESSMENT.md'), { recursive: true });
161
+
162
+ const wtSliceDir = join(wtBase, '.gsd', 'milestones', 'M001', 'slices', 'S01');
163
+ mkdirSync(wtSliceDir, { recursive: true });
164
+ writeFileSync(join(wtSliceDir, 'S01-SUMMARY.md'), '# Valid summary\n');
165
+ writeFileSync(join(wtSliceDir, 'S01-ASSESSMENT.md'), '---\nverdict: pass\n---\n# Valid assessment\n');
166
+
167
+ syncProjectRootToWorktree(mainBase, wtBase, 'M001');
168
+
169
+ const summaryPath = join(wtSliceDir, 'S01-SUMMARY.md');
170
+ const assessmentPath = join(wtSliceDir, 'S01-ASSESSMENT.md');
171
+ assert.ok(statSync(summaryPath).isFile(), 'existing SUMMARY.md stays a file');
172
+ assert.ok(statSync(assessmentPath).isFile(), 'existing ASSESSMENT.md stays a file');
173
+ assert.equal(readFileSync(summaryPath, 'utf-8'), '# Valid summary\n');
174
+ assert.equal(readFileSync(assessmentPath, 'utf-8'), '---\nverdict: pass\n---\n# Valid assessment\n');
175
+ } finally {
176
+ cleanup(mainBase);
177
+ cleanup(wtBase);
178
+ }
179
+ }
180
+
151
181
  // ─── 4. No-op when paths are equal ────────────────────────────────────
152
182
  console.log('\n=== 4. no-op when paths are equal ===');
153
183
  {
@@ -20,6 +20,7 @@ import {
20
20
  existsSync,
21
21
  realpathSync,
22
22
  readFileSync,
23
+ readdirSync,
23
24
  } from "node:fs";
24
25
  import { join } from "node:path";
25
26
  import { tmpdir } from "node:os";
@@ -132,6 +133,37 @@ describe("worktree-teardown-safety", () => {
132
133
  );
133
134
  });
134
135
 
136
+ it("removeWorktree quarantines dirty worktree contents before forced cleanup", () => {
137
+ const tempDir = createTempRepo();
138
+ dirs.push(tempDir);
139
+
140
+ const wt = createWorktree(tempDir, "dirty-wt");
141
+ writeFileSync(join(wt.path, "generated-feature.ts"), "export const answer = 42;\n");
142
+ writeFileSync(join(wt.path, "README.md"), "# changed in worktree\n");
143
+
144
+ removeWorktree(tempDir, "dirty-wt", { deleteBranch: false });
145
+
146
+ assertTrue(!existsSync(wt.path), "original dirty worktree path removed after quarantine");
147
+ const quarantineRoot = join(tempDir, ".gsd", "quarantine", "worktrees");
148
+ const entries = readdirSync(quarantineRoot).filter((entry) => entry.startsWith("dirty-wt-"));
149
+ assertEq(entries.length, 1, "dirty worktree snapshot quarantined");
150
+ const quarantineDir = join(quarantineRoot, entries[0]!);
151
+ assertEq(
152
+ readFileSync(join(quarantineDir, "generated-feature.ts"), "utf-8"),
153
+ "export const answer = 42;\n",
154
+ "untracked source file survives in quarantine",
155
+ );
156
+ assertEq(
157
+ readFileSync(join(quarantineDir, "README.md"), "utf-8"),
158
+ "# changed in worktree\n",
159
+ "modified tracked file survives in quarantine",
160
+ );
161
+ assertTrue(
162
+ existsSync(join(quarantineDir, ".gsd-quarantine.json")),
163
+ "quarantine metadata written",
164
+ );
165
+ });
166
+
135
167
  it("worktreePath always returns paths under .gsd/worktrees/", () => {
136
168
  const tempDir = createTempRepo();
137
169
  dirs.push(tempDir);