gsd-pi 2.78.1-dev.b0759e59b → 2.78.1-dev.d8826a445

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 (300) hide show
  1. package/README.md +8 -5
  2. package/dist/headless-recover.d.ts +23 -0
  3. package/dist/headless-recover.js +93 -0
  4. package/dist/headless.js +9 -0
  5. package/dist/help-text.js +1 -0
  6. package/dist/resources/.managed-resources-content-hash +1 -1
  7. package/dist/resources/extensions/browser-tools/tools/intent.js +8 -1
  8. package/dist/resources/extensions/gsd/auto/phases.js +7 -2
  9. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +7 -58
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +14 -28
  12. package/dist/resources/extensions/gsd/auto-start.js +1 -8
  13. package/dist/resources/extensions/gsd/auto-worktree.js +244 -216
  14. package/dist/resources/extensions/gsd/auto.js +86 -7
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
  16. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
  18. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
  19. package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
  20. package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
  21. package/dist/resources/extensions/gsd/commands-logs.js +2 -2
  22. package/dist/resources/extensions/gsd/commands-scan.js +2 -2
  23. package/dist/resources/extensions/gsd/commands-ship.js +2 -2
  24. package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
  25. package/dist/resources/extensions/gsd/db-writer.js +106 -95
  26. package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
  27. package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
  28. package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
  29. package/dist/resources/extensions/gsd/gsd-db.js +268 -8
  30. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  31. package/dist/resources/extensions/gsd/guided-flow.js +141 -32
  32. package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
  33. package/dist/resources/extensions/gsd/metrics.js +287 -1
  34. package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
  35. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
  36. package/dist/resources/extensions/gsd/paths.js +114 -9
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  39. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  40. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  41. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  42. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  44. package/dist/resources/extensions/gsd/queue-order.js +6 -1
  45. package/dist/resources/extensions/gsd/rethink.js +2 -2
  46. package/dist/resources/extensions/gsd/state.js +91 -372
  47. package/dist/resources/extensions/gsd/templates/project.md +10 -0
  48. package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
  49. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
  50. package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
  51. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
  52. package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
  53. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
  54. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  55. package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
  56. package/dist/resources/extensions/gsd/workspace.js +59 -0
  57. package/dist/resources/extensions/gsd/worktree-command.js +4 -3
  58. package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
  59. package/dist/resources/extensions/gsd/write-intercept.js +3 -3
  60. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  61. package/dist/web/standalone/.next/BUILD_ID +1 -1
  62. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  63. package/dist/web/standalone/.next/build-manifest.json +2 -2
  64. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  65. package/dist/web/standalone/.next/required-server-files.json +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  83. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  84. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  101. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  108. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  112. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/index.html +1 -1
  115. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  122. package/dist/web/standalone/.next/server/chunks/6336.js +1 -0
  123. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  124. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  126. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  127. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  128. package/dist/web/standalone/server.js +1 -1
  129. package/package.json +1 -1
  130. package/packages/mcp-server/README.md +2 -11
  131. package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
  132. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  133. package/packages/mcp-server/dist/remote-questions.js +28 -0
  134. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  135. package/packages/mcp-server/dist/server.d.ts +28 -0
  136. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  137. package/packages/mcp-server/dist/server.js +94 -4
  138. package/packages/mcp-server/dist/server.js.map +1 -1
  139. package/packages/mcp-server/dist/workflow-tools.d.ts +6 -0
  140. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  141. package/packages/mcp-server/dist/workflow-tools.js +56 -2
  142. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  143. package/packages/mcp-server/src/mcp-server.test.ts +226 -0
  144. package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
  145. package/packages/mcp-server/src/remote-questions.test.ts +103 -0
  146. package/packages/mcp-server/src/remote-questions.ts +35 -0
  147. package/packages/mcp-server/src/server.ts +129 -6
  148. package/packages/mcp-server/src/workflow-tools.ts +62 -3
  149. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  150. package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
  151. package/src/resources/extensions/gsd/auto/phases.ts +8 -2
  152. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  153. package/src/resources/extensions/gsd/auto-dispatch.ts +14 -62
  154. package/src/resources/extensions/gsd/auto-post-unit.ts +15 -27
  155. package/src/resources/extensions/gsd/auto-start.ts +1 -8
  156. package/src/resources/extensions/gsd/auto-worktree.ts +286 -251
  157. package/src/resources/extensions/gsd/auto.ts +102 -7
  158. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
  159. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
  160. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
  161. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
  162. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
  163. package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
  164. package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
  165. package/src/resources/extensions/gsd/commands-logs.ts +2 -2
  166. package/src/resources/extensions/gsd/commands-scan.ts +2 -2
  167. package/src/resources/extensions/gsd/commands-ship.ts +2 -2
  168. package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
  169. package/src/resources/extensions/gsd/db-writer.ts +123 -94
  170. package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
  171. package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
  172. package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
  173. package/src/resources/extensions/gsd/gsd-db.ts +269 -8
  174. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  175. package/src/resources/extensions/gsd/guided-flow.ts +181 -32
  176. package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
  177. package/src/resources/extensions/gsd/metrics.ts +321 -1
  178. package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
  179. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
  180. package/src/resources/extensions/gsd/paths.ts +122 -9
  181. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  182. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  183. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  184. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  185. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  186. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  188. package/src/resources/extensions/gsd/queue-order.ts +6 -1
  189. package/src/resources/extensions/gsd/rethink.ts +2 -2
  190. package/src/resources/extensions/gsd/state.ts +91 -389
  191. package/src/resources/extensions/gsd/templates/project.md +10 -0
  192. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
  193. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
  194. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
  195. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
  196. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
  197. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
  198. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
  199. package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
  200. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
  201. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
  202. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  203. package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
  204. package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
  205. package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
  206. package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
  207. package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
  208. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
  209. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
  210. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
  211. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
  212. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
  213. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
  214. package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
  215. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
  216. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
  217. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
  218. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
  219. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
  220. package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
  221. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
  222. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
  223. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
  224. package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
  225. package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
  226. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
  227. package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
  228. package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
  229. package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
  230. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
  231. package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
  232. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
  233. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
  234. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
  235. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
  236. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
  237. package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
  238. package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
  239. package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
  240. package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
  241. package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
  242. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
  243. package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
  244. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
  245. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
  246. package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
  247. package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
  248. package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
  249. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
  250. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
  251. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
  252. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
  253. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
  254. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
  255. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
  256. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
  257. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
  258. package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
  259. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
  260. package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
  261. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
  262. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  263. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
  264. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
  265. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
  266. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
  267. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
  268. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
  269. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
  270. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
  271. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
  272. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
  273. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
  274. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
  275. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
  276. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
  277. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +26 -3
  278. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
  279. package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
  280. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
  281. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
  282. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
  283. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
  284. package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
  285. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
  287. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
  288. package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
  289. package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
  290. package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
  291. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
  292. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  293. package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
  294. package/src/resources/extensions/gsd/workspace.ts +95 -0
  295. package/src/resources/extensions/gsd/worktree-command.ts +4 -3
  296. package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
  297. package/src/resources/extensions/gsd/write-intercept.ts +3 -3
  298. package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
  299. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
  300. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
@@ -175,6 +175,7 @@ import { getErrorMessage } from "./error-utils.js";
175
175
  import { recoverFailedMigration } from "./migrate-external.js";
176
176
  import { initRegistry, convertDispatchRules } from "./rule-registry.js";
177
177
  import { emitJournalEvent as _emitJournalEvent, type JournalEntry } from "./journal.js";
178
+ import { isClosedStatus } from "./status-guards.js";
178
179
  import {
179
180
  type AutoDashboardData,
180
181
  updateProgressWidget as _updateProgressWidget,
@@ -194,6 +195,7 @@ import {
194
195
  import { isDbAvailable, getMilestone } from "./gsd-db.js";
195
196
  import { countPendingCaptures } from "./captures.js";
196
197
  import { CMUX_CHANNELS, type CmuxLogLevel } from "../shared/cmux-events.js";
198
+ import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
197
199
 
198
200
  function makeCmuxEmitters(pi: ExtensionAPI) {
199
201
  return {
@@ -254,6 +256,7 @@ export type {
254
256
  } from "./auto/session.js";
255
257
  import { autoSession as s } from "./auto-runtime-state.js";
256
258
  import { gsdHome } from "./gsd-home.js";
259
+ import { createWorkspace, scopeMilestone } from "./workspace.js";
257
260
 
258
261
  // ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
259
262
  // ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
@@ -322,6 +325,32 @@ function restoreMilestoneLockEnv(): void {
322
325
  s.milestoneLockEnvCaptured = false;
323
326
  }
324
327
 
328
+ /**
329
+ * Rebuild s.scope from the current s.basePath / s.originalBasePath / s.currentMilestoneId.
330
+ *
331
+ * Pass the worktree path as rawPath when entering a worktree so createWorkspace
332
+ * can detect the worktree layout and set mode="worktree". When no worktree is
333
+ * active, rawPath should equal the project root.
334
+ *
335
+ * Clears s.scope when milestoneId is absent — scope is only meaningful when a
336
+ * milestone is active.
337
+ *
338
+ * TODO(C8): remove basePath/originalBasePath once all readers use s.scope.
339
+ */
340
+ function rebuildScope(rawPath: string, milestoneId: string | null): void {
341
+ if (!milestoneId) {
342
+ s.scope = null;
343
+ return;
344
+ }
345
+ try {
346
+ const workspace = createWorkspace(rawPath);
347
+ s.scope = scopeMilestone(workspace, milestoneId);
348
+ } catch {
349
+ // Non-fatal — scope is additive. Existing readers still use basePath.
350
+ s.scope = null;
351
+ }
352
+ }
353
+
325
354
  function normalizeSessionFilePath(raw: unknown): string | null {
326
355
  if (typeof raw !== "string") return null;
327
356
  const trimmed = raw.trim();
@@ -507,6 +536,26 @@ export function _setAutoActiveForTest(active: boolean): void {
507
536
  s.active = active;
508
537
  }
509
538
 
539
+ /**
540
+ * Test-only seam: emit the missing-worktree warning exactly as the resume path
541
+ * does. Allows unit tests to verify the warning is produced without
542
+ * bootstrapping the full auto-mode entry point. Do not use in production code.
543
+ */
544
+ export function _warnIfWorktreeMissingForTest(
545
+ worktreePath: string | null | undefined,
546
+ milestoneId: string,
547
+ ): boolean {
548
+ if (worktreePath && !existsSync(worktreePath)) {
549
+ logWarning(
550
+ "session",
551
+ `Worktree was expected at ${worktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`,
552
+ { file: "auto.ts", milestoneId },
553
+ );
554
+ return true;
555
+ }
556
+ return false;
557
+ }
558
+
510
559
  export function isAutoPaused(): boolean {
511
560
  return s.paused;
512
561
  }
@@ -1506,14 +1555,29 @@ export async function startAuto(
1506
1555
  );
1507
1556
  if (shouldResumePausedSession) {
1508
1557
  // Validate the milestone still exists and isn't already complete (#1664).
1558
+ // DB status is authoritative when available; SUMMARY.md is a legacy
1559
+ // fallback only for unmigrated/offline projects.
1509
1560
  const mDir = resolveMilestonePath(base, meta.milestoneId);
1510
- const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
1511
1561
  let summaryIsTerminal = false;
1512
- if (summaryFile) {
1513
- try {
1514
- summaryIsTerminal = classifyMilestoneSummaryContent(readFileSync(summaryFile, "utf-8")) !== "failure";
1515
- } catch {
1516
- summaryIsTerminal = false;
1562
+ let dbAvailable = isDbAvailable();
1563
+ let milestoneRow = dbAvailable ? getMilestone(meta.milestoneId) : null;
1564
+ if (!milestoneRow) {
1565
+ const opened = await ensureDbOpen(base);
1566
+ dbAvailable = opened || isDbAvailable();
1567
+ if (dbAvailable) {
1568
+ milestoneRow = getMilestone(meta.milestoneId);
1569
+ }
1570
+ }
1571
+ if (dbAvailable) {
1572
+ summaryIsTerminal = !!milestoneRow && isClosedStatus(milestoneRow.status);
1573
+ } else {
1574
+ const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
1575
+ if (summaryFile) {
1576
+ try {
1577
+ summaryIsTerminal = classifyMilestoneSummaryContent(readFileSync(summaryFile, "utf-8")) !== "failure";
1578
+ } catch {
1579
+ summaryIsTerminal = false;
1580
+ }
1517
1581
  }
1518
1582
  }
1519
1583
  if (!mDir || summaryIsTerminal) {
@@ -1536,6 +1600,22 @@ export async function startAuto(
1536
1600
  s.autoStartTime = meta.autoStartTime || Date.now();
1537
1601
  s.sessionMilestoneLock = meta.milestoneLock ?? null;
1538
1602
  s.paused = true;
1603
+ // Build scope from persisted state. Use worktreePath when present and
1604
+ // still on disk so mode is detected correctly; fall back to project root.
1605
+ {
1606
+ const persistedWorktreePath = meta.worktreePath ?? null;
1607
+ if (persistedWorktreePath && !existsSync(persistedWorktreePath)) {
1608
+ logWarning(
1609
+ "session",
1610
+ `Worktree was expected at ${persistedWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`,
1611
+ { file: "auto.ts", milestoneId: meta.milestoneId ?? "" },
1612
+ );
1613
+ }
1614
+ const rawForScope = (persistedWorktreePath && existsSync(persistedWorktreePath))
1615
+ ? persistedWorktreePath
1616
+ : (s.originalBasePath || base);
1617
+ rebuildScope(rawForScope, s.currentMilestoneId);
1618
+ }
1539
1619
  try { unlinkSync(pausedPath); } catch (e) {
1540
1620
  if ((e as NodeJS.ErrnoException).code !== "ENOENT") {
1541
1621
  logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
@@ -1620,10 +1700,19 @@ export async function startAuto(
1620
1700
  // session (e.g. isolation mode changed, detectWorktreeName differs across
1621
1701
  // process restarts). We guard with existsSync so a stale or deleted
1622
1702
  // worktree directory safely falls back to the project root.
1623
- const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath;
1703
+ const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath ?? null;
1704
+ if (resumeWorktreePath && !existsSync(resumeWorktreePath)) {
1705
+ logWarning(
1706
+ "session",
1707
+ `Worktree was expected at ${resumeWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`,
1708
+ { file: "auto.ts", milestoneId: s.currentMilestoneId ?? "" },
1709
+ );
1710
+ }
1624
1711
  if (resumeWorktreePath && existsSync(resumeWorktreePath)) {
1625
1712
  s.basePath = resumeWorktreePath;
1626
1713
  }
1714
+ // Rebuild scope now that s.basePath reflects the actual worktree (or project root).
1715
+ rebuildScope(s.basePath, s.currentMilestoneId);
1627
1716
  // Ensure the workflow-logger audit log is pinned to the project root
1628
1717
  // even when auto-mode is entered via a path that bypasses the
1629
1718
  // bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain
@@ -1652,6 +1741,8 @@ export async function startAuto(
1652
1741
  buildResolver().enterMilestone(s.currentMilestoneId, {
1653
1742
  notify: ctx.ui.notify.bind(ctx.ui),
1654
1743
  });
1744
+ // s.basePath may have been updated to a worktree path by enterMilestone.
1745
+ rebuildScope(s.basePath, s.currentMilestoneId);
1655
1746
  }
1656
1747
 
1657
1748
  registerSigtermHandler(lockBase());
@@ -1766,6 +1857,10 @@ export async function startAuto(
1766
1857
  );
1767
1858
  if (!ready) return;
1768
1859
 
1860
+ // Build scope after bootstrap has populated s.basePath / s.originalBasePath /
1861
+ // s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
1862
+ rebuildScope(s.basePath, s.currentMilestoneId);
1863
+
1769
1864
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1770
1865
  try {
1771
1866
  pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync" as const, preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, state: await deriveState(s.basePath) });
@@ -115,7 +115,7 @@ export async function handleAgentEnd(
115
115
  }
116
116
 
117
117
  if (checkAutoStartAfterDiscuss()) {
118
- clearDiscussionFlowState();
118
+ clearDiscussionFlowState(resolveAgentEndBasePath() ?? process.cwd());
119
119
  return;
120
120
  }
121
121
 
@@ -1,11 +1,12 @@
1
1
  import { existsSync } from "node:fs";
2
- import { join, sep } from "node:path";
2
+ import { dirname } from "node:path";
3
3
 
4
4
  import type { ExtensionAPI } from "@gsd/pi-coding-agent";
5
5
  import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@gsd/pi-coding-agent";
6
6
 
7
7
  import { DEFAULT_BASH_TIMEOUT_SECS } from "../constants.js";
8
8
  import { setLogBasePath, logWarning } from "../workflow-logger.js";
9
+ import { resolveGsdPathContract } from "../paths.js";
9
10
 
10
11
  /**
11
12
  * Resolve the correct DB path for the current working directory.
@@ -14,75 +15,16 @@ import { setLogBasePath, logWarning } from "../workflow-logger.js";
14
15
  * returns `<basePath>/.gsd/gsd.db`.
15
16
  */
16
17
  export function resolveProjectRootDbPath(basePath: string): string {
17
- // Detect worktree: look for `.gsd/worktrees/` in the path segments.
18
- // A worktree path looks like: /project/root/.gsd/worktrees/M001/...
19
- // We need to resolve back to /project/root/.gsd/gsd.db
20
- const marker = `${sep}.gsd${sep}worktrees${sep}`;
21
- const idx = basePath.indexOf(marker);
22
- if (idx !== -1) {
23
- const projectRoot = basePath.slice(0, idx);
24
- return join(projectRoot, ".gsd", "gsd.db");
25
- }
26
-
27
- // Also handle forward-slash paths on all platforms
28
- const fwdMarker = "/.gsd/worktrees/";
29
- const fwdIdx = basePath.indexOf(fwdMarker);
30
- if (fwdIdx !== -1) {
31
- const projectRoot = basePath.slice(0, fwdIdx);
32
- return join(projectRoot, ".gsd", "gsd.db");
33
- }
34
-
35
- // External-state layout: ~/.gsd/projects/<hash>/worktrees/<MID>/...
36
- // Resolve to ~/.gsd/projects/<hash>/gsd.db (the canonical project DB) (#2952).
37
- // Must be checked before the generic symlink-resolved handler: both match
38
- // /.gsd/projects/<hash>/worktrees/ but require different resolution targets.
39
- const extRe = /[/\\]\.gsd[/\\]projects[/\\][a-f0-9]+[/\\]worktrees(?:[/\\]|$)/;
40
- const extMatch = extRe.exec(basePath);
41
- if (extMatch) {
42
- const matchStr = extMatch[0];
43
- // Find the "/worktrees" portion within the match and slice up to it
44
- const wtIdx = matchStr.search(/[/\\]worktrees(?:[/\\]|$)/);
45
- const projectStateRoot = basePath.slice(0, extMatch.index + wtIdx);
46
- return join(projectStateRoot, "gsd.db");
47
- }
48
-
49
- // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/M001/...
50
- // The project root is everything before /.gsd/projects/ (#2517)
51
- const symlinkMarker = `${sep}.gsd${sep}projects${sep}`;
52
- const symlinkIdx = basePath.indexOf(symlinkMarker);
53
- if (symlinkIdx !== -1) {
54
- const afterProjects = basePath.slice(symlinkIdx + symlinkMarker.length);
55
- // Expect: <hash>/worktrees/...
56
- const worktreeSeg = `${sep}worktrees${sep}`;
57
- if (afterProjects.includes(worktreeSeg)) {
58
- const projectRoot = basePath.slice(0, symlinkIdx);
59
- return join(projectRoot, ".gsd", "gsd.db");
60
- }
61
- }
62
-
63
- // Forward-slash variant for symlink-resolved layout
64
- const fwdSymlinkMarker = "/.gsd/projects/";
65
- const fwdSymlinkIdx = basePath.indexOf(fwdSymlinkMarker);
66
- if (fwdSymlinkIdx !== -1) {
67
- const afterProjects = basePath.slice(fwdSymlinkIdx + fwdSymlinkMarker.length);
68
- if (afterProjects.includes("/worktrees/")) {
69
- const projectRoot = basePath.slice(0, fwdSymlinkIdx);
70
- return join(projectRoot, ".gsd", "gsd.db");
71
- }
72
- }
73
-
74
-
75
- return join(basePath, ".gsd", "gsd.db");
18
+ return resolveGsdPathContract(basePath).projectDb;
76
19
  }
77
20
 
78
21
  export async function ensureDbOpen(basePath: string = process.cwd()): Promise<boolean> {
79
22
  try {
80
23
  const db = await import("../gsd-db.js");
81
- const dbPath = resolveProjectRootDbPath(basePath);
82
- const gsdDir = join(basePath, ".gsd");
83
-
84
- // Derive the project root from the DB path (strip .gsd/gsd.db)
85
- const projectRoot = join(dbPath, "..", "..");
24
+ const contract = resolveGsdPathContract(basePath);
25
+ const dbPath = contract.projectDb;
26
+ const gsdDir = contract.projectGsd;
27
+ const projectRoot = dirname(dirname(dbPath));
86
28
 
87
29
  // Open existing DB file (may be at project root for worktrees)
88
30
  if (existsSync(dbPath)) {
@@ -91,26 +33,9 @@ export async function ensureDbOpen(basePath: string = process.cwd()): Promise<bo
91
33
  return opened;
92
34
  }
93
35
 
94
- // No DB file — create + migrate from Markdown if .gsd/ has content
36
+ // No DB file — create an empty authoritative DB. Markdown migration is
37
+ // explicit-only; runtime startup must not import projections into state.
95
38
  if (existsSync(gsdDir)) {
96
- const hasDecisions = existsSync(join(gsdDir, "DECISIONS.md"));
97
- const hasRequirements = existsSync(join(gsdDir, "REQUIREMENTS.md"));
98
- const hasMilestones = existsSync(join(gsdDir, "milestones"));
99
- if (hasDecisions || hasRequirements || hasMilestones) {
100
- const opened = db.openDatabase(dbPath);
101
- if (opened) {
102
- setLogBasePath(projectRoot);
103
- try {
104
- const { migrateFromMarkdown } = await import("../md-importer.js");
105
- migrateFromMarkdown(basePath);
106
- } catch (err) {
107
- logWarning("bootstrap", `ensureDbOpen auto-migration failed: ${(err as Error).message}`);
108
- }
109
- }
110
- return opened;
111
- }
112
-
113
- // .gsd/ exists but has no Markdown content (fresh project) — create empty DB
114
39
  const opened = db.openDatabase(dbPath);
115
40
  if (opened) setLogBasePath(projectRoot);
116
41
  return opened;
@@ -76,7 +76,7 @@ export function registerHooks(
76
76
  const { initHealthWidget } = await import("../health-widget.js");
77
77
  initHealthWidget(ctx);
78
78
  }
79
- resetWriteGateState();
79
+ resetWriteGateState(process.cwd());
80
80
  resetToolCallLoopGuard();
81
81
  approvalQuestionAbortInFlight = false;
82
82
  await resetAskUserQuestionsTurnCache();
@@ -126,10 +126,10 @@ export function registerHooks(
126
126
  pi.on("session_switch", async (_event, ctx) => {
127
127
  initNotificationStore(process.cwd());
128
128
  installNotifyInterceptor(ctx);
129
- resetWriteGateState();
129
+ resetWriteGateState(process.cwd());
130
130
  resetToolCallLoopGuard();
131
131
  await resetAskUserQuestionsTurnCache();
132
- clearDiscussionFlowState();
132
+ clearDiscussionFlowState(process.cwd());
133
133
  await syncServiceTierStatus(ctx);
134
134
  await applyDisabledModelProviderPolicy(ctx);
135
135
  // Skip MCP auto-prep when running inside an auto-worktree. The worktree
@@ -155,12 +155,13 @@ export function registerHooks(
155
155
  const { getEcosystemReadyPromise } = await import("../ecosystem/loader.js");
156
156
  await getEcosystemReadyPromise();
157
157
 
158
+ const beforeAgentBasePath = process.cwd();
158
159
  const pendingApprovalGate = getPendingGate();
159
160
  if (pendingApprovalGate && isExplicitApprovalResponse(event.prompt, pendingApprovalGate)) {
160
- markApprovalGateVerified(pendingApprovalGate);
161
+ markApprovalGateVerified(pendingApprovalGate, beforeAgentBasePath);
161
162
  const milestoneId = extractDepthVerificationMilestoneId(pendingApprovalGate);
162
- if (milestoneId) markDepthVerified(milestoneId);
163
- clearPendingGate();
163
+ if (milestoneId) markDepthVerified(milestoneId, beforeAgentBasePath);
164
+ clearPendingGate(beforeAgentBasePath);
164
165
  }
165
166
 
166
167
  // GSD's own context injection (existing behavior — unchanged).
@@ -346,7 +347,7 @@ export function registerHooks(
346
347
  if (!shouldPauseForUserApprovalQuestion(unitType, [event.message])) return;
347
348
 
348
349
  const gateId = approvalGateIdForUnit(unitType, unitId);
349
- if (gateId) setPendingGate(gateId);
350
+ if (gateId) setPendingGate(gateId, process.cwd());
350
351
 
351
352
  approvalQuestionAbortInFlight = true;
352
353
  ctx.ui.notify(
@@ -393,7 +394,7 @@ export function registerHooks(
393
394
  const questions: any[] = (event.input as any)?.questions ?? [];
394
395
  const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
395
396
  if (typeof questionId === "string") {
396
- setPendingGate(questionId);
397
+ setPendingGate(questionId, discussionBasePath);
397
398
  }
398
399
  }
399
400
 
@@ -555,7 +556,8 @@ export function registerHooks(
555
556
  }
556
557
  const toolName = canonicalToolName(event.toolName);
557
558
  if (toolName !== "ask_user_questions") return;
558
- const milestoneId = await getDiscussionMilestoneIdFor(process.cwd());
559
+ const basePath = process.cwd();
560
+ const milestoneId = await getDiscussionMilestoneIdFor(basePath);
559
561
  const queueActive = isQueuePhaseActive();
560
562
 
561
563
  const details = event.details as any;
@@ -588,10 +590,10 @@ export function registerHooks(
588
590
  if (pendingQuestion) {
589
591
  const answer = details.response?.answers?.[currentPendingGate];
590
592
  if (isDepthConfirmationAnswer(answer?.selected, pendingQuestion.options)) {
591
- markApprovalGateVerified(currentPendingGate);
593
+ markApprovalGateVerified(currentPendingGate, basePath);
592
594
  const milestoneIdFromGate = extractDepthVerificationMilestoneId(currentPendingGate);
593
- if (milestoneIdFromGate) markDepthVerified(milestoneIdFromGate);
594
- clearPendingGate();
595
+ if (milestoneIdFromGate) markDepthVerified(milestoneIdFromGate, basePath);
596
+ clearPendingGate(basePath);
595
597
  }
596
598
  }
597
599
  }
@@ -607,9 +609,9 @@ export function registerHooks(
607
609
  const inferredMilestoneId = extractDepthVerificationMilestoneId(question.id) ?? milestoneId;
608
610
  if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
609
611
  if (currentPendingGate && question.id !== currentPendingGate) break;
610
- markApprovalGateVerified(question.id);
611
- markDepthVerified(inferredMilestoneId);
612
- clearPendingGate();
612
+ markApprovalGateVerified(question.id, basePath);
613
+ markDepthVerified(inferredMilestoneId, basePath);
614
+ clearPendingGate(basePath);
613
615
  }
614
616
  break;
615
617
  }
@@ -617,8 +619,6 @@ export function registerHooks(
617
619
 
618
620
  if (!milestoneId && !queueActive) return;
619
621
  if (!milestoneId) return;
620
-
621
- const basePath = process.cwd();
622
622
  const milestoneDir = resolveMilestonePath(basePath, milestoneId);
623
623
  if (!milestoneDir) return;
624
624
 
@@ -0,0 +1,103 @@
1
+ // GSD-2 write-gate bootstrap — regression test for required basePath (commit A3)
2
+ //
3
+ // Verifies that persistWriteGateSnapshot / loadWriteGateSnapshot are pinned to
4
+ // the basePath argument and do not silently fall back to process.cwd(). The
5
+ // underlying bug: both functions defaulted `basePath = process.cwd()`, so a
6
+ // persist in cwd-A followed by a chdir to cwd-B and a load (which also
7
+ // defaulted to process.cwd(), now cwd-B) missed the persisted file entirely —
8
+ // the depth-verification state became invisible across cwd boundaries.
9
+
10
+ import { test, describe, before, after } from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { mkdtempSync, rmSync, existsSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+
16
+ import {
17
+ markDepthVerified,
18
+ loadWriteGateSnapshot,
19
+ clearDiscussionFlowState,
20
+ } from "../write-gate.js";
21
+
22
+ // ─── Helpers ────────────────────────────────────────────────────────────────
23
+
24
+ function makeTempDir(): string {
25
+ return mkdtempSync(join(tmpdir(), "wg-basepath-test-"));
26
+ }
27
+
28
+ // Save and restore process.cwd() across tests to avoid cross-test pollution.
29
+ let originalCwd: string;
30
+ before(() => {
31
+ originalCwd = process.cwd();
32
+ });
33
+ after(() => {
34
+ if (process.cwd() !== originalCwd) {
35
+ process.chdir(originalCwd);
36
+ }
37
+ });
38
+
39
+ // ─── Scenario: persist with basePath=A, chdir, load with basePath=A ─────────
40
+ //
41
+ // This is the exact failure mode from the bug: persist used process.cwd() and
42
+ // load used process.cwd(), and they resolved to different directories after a
43
+ // chdir. With the fix, both calls receive an explicit basePath so cwd changes
44
+ // have no effect.
45
+
46
+ describe("write-gate basePath regression", () => {
47
+ let baseDirA: string;
48
+ let baseDirB: string;
49
+
50
+ before(() => {
51
+ baseDirA = makeTempDir();
52
+ baseDirB = makeTempDir();
53
+ });
54
+
55
+ after(() => {
56
+ // Restore cwd before cleanup to avoid issues on Windows.
57
+ process.chdir(originalCwd);
58
+ rmSync(baseDirA, { recursive: true, force: true });
59
+ rmSync(baseDirB, { recursive: true, force: true });
60
+ });
61
+
62
+ test("snapshot persisted to basePath=A is readable after chdir to basePath=B", (t) => {
63
+ // Arrange: enable persistence (the default when env var is not set to "0"/"false").
64
+ const prev = process.env.GSD_PERSIST_WRITE_GATE_STATE;
65
+ t.after(() => {
66
+ if (prev === undefined) {
67
+ delete process.env.GSD_PERSIST_WRITE_GATE_STATE;
68
+ } else {
69
+ process.env.GSD_PERSIST_WRITE_GATE_STATE = prev;
70
+ }
71
+ });
72
+ process.env.GSD_PERSIST_WRITE_GATE_STATE = "1";
73
+
74
+ // Reset state and clear any stale snapshot files from both dirs.
75
+ clearDiscussionFlowState(baseDirA);
76
+ clearDiscussionFlowState(baseDirB);
77
+
78
+ // Act: persist a milestone as depth-verified into baseDirA.
79
+ markDepthVerified("M001", baseDirA);
80
+
81
+ // Confirm the snapshot file was written under baseDirA.
82
+ const snapshotPath = join(baseDirA, ".gsd", "runtime", "write-gate-state.json");
83
+ assert.ok(existsSync(snapshotPath), "snapshot file should exist under baseDirA");
84
+
85
+ // Simulate what happens when cwd changes to a different project root.
86
+ process.chdir(baseDirB);
87
+ assert.notEqual(process.cwd(), baseDirA, "cwd should differ from baseDirA after chdir");
88
+
89
+ // Load snapshot using the explicit baseDirA — must see the persisted state.
90
+ const snapshot = loadWriteGateSnapshot(baseDirA);
91
+ assert.ok(
92
+ snapshot.verifiedDepthMilestones.includes("M001"),
93
+ "loadWriteGateSnapshot(baseDirA) must return the persisted milestone despite cwd being baseDirB",
94
+ );
95
+
96
+ // Loading with baseDirB must NOT see the state from baseDirA.
97
+ const snapshotB = loadWriteGateSnapshot(baseDirB);
98
+ assert.ok(
99
+ !snapshotB.verifiedDepthMilestones.includes("M001"),
100
+ "loadWriteGateSnapshot(baseDirB) must not bleed state from baseDirA",
101
+ );
102
+ });
103
+ });