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
@@ -56,11 +56,13 @@ import { getErrorMessage } from "./error-utils.js";
56
56
  import { recoverFailedMigration } from "./migrate-external.js";
57
57
  import { initRegistry, convertDispatchRules } from "./rule-registry.js";
58
58
  import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
59
+ import { isClosedStatus } from "./status-guards.js";
59
60
  import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, } from "./auto-dashboard.js";
60
61
  import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
61
62
  import { isDbAvailable, getMilestone } from "./gsd-db.js";
62
63
  import { countPendingCaptures } from "./captures.js";
63
64
  import { CMUX_CHANNELS } from "../shared/cmux-events.js";
65
+ import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
64
66
  function makeCmuxEmitters(pi) {
65
67
  return {
66
68
  syncCmuxSidebar: (preferences, state) => pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync", preferences, state }),
@@ -84,6 +86,7 @@ import { reorderForCaching } from "./prompt-ordering.js";
84
86
  export { STUB_RECOVERY_THRESHOLD, NEW_SESSION_TIMEOUT_MS, } from "./auto/session.js";
85
87
  import { autoSession as s } from "./auto-runtime-state.js";
86
88
  import { gsdHome } from "./gsd-home.js";
89
+ import { createWorkspace, scopeMilestone } from "./workspace.js";
87
90
  // ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
88
91
  // ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
89
92
  // This file must NOT declare module-level `let` or `var` variables for state.
@@ -145,6 +148,32 @@ function restoreMilestoneLockEnv() {
145
148
  s.hadMilestoneLockEnv = false;
146
149
  s.milestoneLockEnvCaptured = false;
147
150
  }
151
+ /**
152
+ * Rebuild s.scope from the current s.basePath / s.originalBasePath / s.currentMilestoneId.
153
+ *
154
+ * Pass the worktree path as rawPath when entering a worktree so createWorkspace
155
+ * can detect the worktree layout and set mode="worktree". When no worktree is
156
+ * active, rawPath should equal the project root.
157
+ *
158
+ * Clears s.scope when milestoneId is absent — scope is only meaningful when a
159
+ * milestone is active.
160
+ *
161
+ * TODO(C8): remove basePath/originalBasePath once all readers use s.scope.
162
+ */
163
+ function rebuildScope(rawPath, milestoneId) {
164
+ if (!milestoneId) {
165
+ s.scope = null;
166
+ return;
167
+ }
168
+ try {
169
+ const workspace = createWorkspace(rawPath);
170
+ s.scope = scopeMilestone(workspace, milestoneId);
171
+ }
172
+ catch {
173
+ // Non-fatal — scope is additive. Existing readers still use basePath.
174
+ s.scope = null;
175
+ }
176
+ }
148
177
  function normalizeSessionFilePath(raw) {
149
178
  if (typeof raw !== "string")
150
179
  return null;
@@ -282,6 +311,18 @@ export function isAutoActive() {
282
311
  export function _setAutoActiveForTest(active) {
283
312
  s.active = active;
284
313
  }
314
+ /**
315
+ * Test-only seam: emit the missing-worktree warning exactly as the resume path
316
+ * does. Allows unit tests to verify the warning is produced without
317
+ * bootstrapping the full auto-mode entry point. Do not use in production code.
318
+ */
319
+ export function _warnIfWorktreeMissingForTest(worktreePath, milestoneId) {
320
+ if (worktreePath && !existsSync(worktreePath)) {
321
+ logWarning("session", `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.`, { file: "auto.ts", milestoneId });
322
+ return true;
323
+ }
324
+ return false;
325
+ }
285
326
  export function isAutoPaused() {
286
327
  return s.paused;
287
328
  }
@@ -1154,15 +1195,31 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1154
1195
  || !!freshStartAssessment.lock);
1155
1196
  if (shouldResumePausedSession) {
1156
1197
  // Validate the milestone still exists and isn't already complete (#1664).
1198
+ // DB status is authoritative when available; SUMMARY.md is a legacy
1199
+ // fallback only for unmigrated/offline projects.
1157
1200
  const mDir = resolveMilestonePath(base, meta.milestoneId);
1158
- const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
1159
1201
  let summaryIsTerminal = false;
1160
- if (summaryFile) {
1161
- try {
1162
- summaryIsTerminal = classifyMilestoneSummaryContent(readFileSync(summaryFile, "utf-8")) !== "failure";
1202
+ let dbAvailable = isDbAvailable();
1203
+ let milestoneRow = dbAvailable ? getMilestone(meta.milestoneId) : null;
1204
+ if (!milestoneRow) {
1205
+ const opened = await ensureDbOpen(base);
1206
+ dbAvailable = opened || isDbAvailable();
1207
+ if (dbAvailable) {
1208
+ milestoneRow = getMilestone(meta.milestoneId);
1163
1209
  }
1164
- catch {
1165
- summaryIsTerminal = false;
1210
+ }
1211
+ if (dbAvailable) {
1212
+ summaryIsTerminal = !!milestoneRow && isClosedStatus(milestoneRow.status);
1213
+ }
1214
+ else {
1215
+ const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
1216
+ if (summaryFile) {
1217
+ try {
1218
+ summaryIsTerminal = classifyMilestoneSummaryContent(readFileSync(summaryFile, "utf-8")) !== "failure";
1219
+ }
1220
+ catch {
1221
+ summaryIsTerminal = false;
1222
+ }
1166
1223
  }
1167
1224
  }
1168
1225
  if (!mDir || summaryIsTerminal) {
@@ -1186,6 +1243,18 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1186
1243
  s.autoStartTime = meta.autoStartTime || Date.now();
1187
1244
  s.sessionMilestoneLock = meta.milestoneLock ?? null;
1188
1245
  s.paused = true;
1246
+ // Build scope from persisted state. Use worktreePath when present and
1247
+ // still on disk so mode is detected correctly; fall back to project root.
1248
+ {
1249
+ const persistedWorktreePath = meta.worktreePath ?? null;
1250
+ if (persistedWorktreePath && !existsSync(persistedWorktreePath)) {
1251
+ logWarning("session", `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.`, { file: "auto.ts", milestoneId: meta.milestoneId ?? "" });
1252
+ }
1253
+ const rawForScope = (persistedWorktreePath && existsSync(persistedWorktreePath))
1254
+ ? persistedWorktreePath
1255
+ : (s.originalBasePath || base);
1256
+ rebuildScope(rawForScope, s.currentMilestoneId);
1257
+ }
1189
1258
  try {
1190
1259
  unlinkSync(pausedPath);
1191
1260
  }
@@ -1267,10 +1336,15 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1267
1336
  // session (e.g. isolation mode changed, detectWorktreeName differs across
1268
1337
  // process restarts). We guard with existsSync so a stale or deleted
1269
1338
  // worktree directory safely falls back to the project root.
1270
- const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath;
1339
+ const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath ?? null;
1340
+ if (resumeWorktreePath && !existsSync(resumeWorktreePath)) {
1341
+ logWarning("session", `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.`, { file: "auto.ts", milestoneId: s.currentMilestoneId ?? "" });
1342
+ }
1271
1343
  if (resumeWorktreePath && existsSync(resumeWorktreePath)) {
1272
1344
  s.basePath = resumeWorktreePath;
1273
1345
  }
1346
+ // Rebuild scope now that s.basePath reflects the actual worktree (or project root).
1347
+ rebuildScope(s.basePath, s.currentMilestoneId);
1274
1348
  // Ensure the workflow-logger audit log is pinned to the project root
1275
1349
  // even when auto-mode is entered via a path that bypasses the
1276
1350
  // bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain
@@ -1297,6 +1371,8 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1297
1371
  buildResolver().enterMilestone(s.currentMilestoneId, {
1298
1372
  notify: ctx.ui.notify.bind(ctx.ui),
1299
1373
  });
1374
+ // s.basePath may have been updated to a worktree path by enterMilestone.
1375
+ rebuildScope(s.basePath, s.currentMilestoneId);
1300
1376
  }
1301
1377
  registerSigtermHandler(lockBase());
1302
1378
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
@@ -1375,6 +1451,9 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1375
1451
  const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
1376
1452
  if (!ready)
1377
1453
  return;
1454
+ // Build scope after bootstrap has populated s.basePath / s.originalBasePath /
1455
+ // s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
1456
+ rebuildScope(s.basePath, s.currentMilestoneId);
1378
1457
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1379
1458
  try {
1380
1459
  pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync", preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, state: await deriveState(s.basePath) });
@@ -86,7 +86,7 @@ export async function handleAgentEnd(pi, event, ctx) {
86
86
  logWarning("bootstrap", `checkDeepProjectSetupAfterTurn failed: ${message}`);
87
87
  }
88
88
  if (checkAutoStartAfterDiscuss()) {
89
- clearDiscussionFlowState();
89
+ clearDiscussionFlowState(resolveAgentEndBasePath() ?? process.cwd());
90
90
  return;
91
91
  }
92
92
  // #4573 — When the LLM emits "Milestone X ready." but the required files
@@ -1,8 +1,9 @@
1
1
  import { existsSync } from "node:fs";
2
- import { join, sep } from "node:path";
2
+ import { dirname } from "node:path";
3
3
  import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@gsd/pi-coding-agent";
4
4
  import { DEFAULT_BASH_TIMEOUT_SECS } from "../constants.js";
5
5
  import { setLogBasePath, logWarning } from "../workflow-logger.js";
6
+ import { resolveGsdPathContract } from "../paths.js";
6
7
  /**
7
8
  * Resolve the correct DB path for the current working directory.
8
9
  * If `basePath` is inside a `.gsd/worktrees/<MID>/` directory, returns
@@ -10,67 +11,15 @@ import { setLogBasePath, logWarning } from "../workflow-logger.js";
10
11
  * returns `<basePath>/.gsd/gsd.db`.
11
12
  */
12
13
  export function resolveProjectRootDbPath(basePath) {
13
- // Detect worktree: look for `.gsd/worktrees/` in the path segments.
14
- // A worktree path looks like: /project/root/.gsd/worktrees/M001/...
15
- // We need to resolve back to /project/root/.gsd/gsd.db
16
- const marker = `${sep}.gsd${sep}worktrees${sep}`;
17
- const idx = basePath.indexOf(marker);
18
- if (idx !== -1) {
19
- const projectRoot = basePath.slice(0, idx);
20
- return join(projectRoot, ".gsd", "gsd.db");
21
- }
22
- // Also handle forward-slash paths on all platforms
23
- const fwdMarker = "/.gsd/worktrees/";
24
- const fwdIdx = basePath.indexOf(fwdMarker);
25
- if (fwdIdx !== -1) {
26
- const projectRoot = basePath.slice(0, fwdIdx);
27
- return join(projectRoot, ".gsd", "gsd.db");
28
- }
29
- // External-state layout: ~/.gsd/projects/<hash>/worktrees/<MID>/...
30
- // Resolve to ~/.gsd/projects/<hash>/gsd.db (the canonical project DB) (#2952).
31
- // Must be checked before the generic symlink-resolved handler: both match
32
- // /.gsd/projects/<hash>/worktrees/ but require different resolution targets.
33
- const extRe = /[/\\]\.gsd[/\\]projects[/\\][a-f0-9]+[/\\]worktrees(?:[/\\]|$)/;
34
- const extMatch = extRe.exec(basePath);
35
- if (extMatch) {
36
- const matchStr = extMatch[0];
37
- // Find the "/worktrees" portion within the match and slice up to it
38
- const wtIdx = matchStr.search(/[/\\]worktrees(?:[/\\]|$)/);
39
- const projectStateRoot = basePath.slice(0, extMatch.index + wtIdx);
40
- return join(projectStateRoot, "gsd.db");
41
- }
42
- // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/M001/...
43
- // The project root is everything before /.gsd/projects/ (#2517)
44
- const symlinkMarker = `${sep}.gsd${sep}projects${sep}`;
45
- const symlinkIdx = basePath.indexOf(symlinkMarker);
46
- if (symlinkIdx !== -1) {
47
- const afterProjects = basePath.slice(symlinkIdx + symlinkMarker.length);
48
- // Expect: <hash>/worktrees/...
49
- const worktreeSeg = `${sep}worktrees${sep}`;
50
- if (afterProjects.includes(worktreeSeg)) {
51
- const projectRoot = basePath.slice(0, symlinkIdx);
52
- return join(projectRoot, ".gsd", "gsd.db");
53
- }
54
- }
55
- // Forward-slash variant for symlink-resolved layout
56
- const fwdSymlinkMarker = "/.gsd/projects/";
57
- const fwdSymlinkIdx = basePath.indexOf(fwdSymlinkMarker);
58
- if (fwdSymlinkIdx !== -1) {
59
- const afterProjects = basePath.slice(fwdSymlinkIdx + fwdSymlinkMarker.length);
60
- if (afterProjects.includes("/worktrees/")) {
61
- const projectRoot = basePath.slice(0, fwdSymlinkIdx);
62
- return join(projectRoot, ".gsd", "gsd.db");
63
- }
64
- }
65
- return join(basePath, ".gsd", "gsd.db");
14
+ return resolveGsdPathContract(basePath).projectDb;
66
15
  }
67
16
  export async function ensureDbOpen(basePath = process.cwd()) {
68
17
  try {
69
18
  const db = await import("../gsd-db.js");
70
- const dbPath = resolveProjectRootDbPath(basePath);
71
- const gsdDir = join(basePath, ".gsd");
72
- // Derive the project root from the DB path (strip .gsd/gsd.db)
73
- const projectRoot = join(dbPath, "..", "..");
19
+ const contract = resolveGsdPathContract(basePath);
20
+ const dbPath = contract.projectDb;
21
+ const gsdDir = contract.projectGsd;
22
+ const projectRoot = dirname(dirname(dbPath));
74
23
  // Open existing DB file (may be at project root for worktrees)
75
24
  if (existsSync(dbPath)) {
76
25
  const opened = db.openDatabase(dbPath);
@@ -78,26 +27,9 @@ export async function ensureDbOpen(basePath = process.cwd()) {
78
27
  setLogBasePath(projectRoot);
79
28
  return opened;
80
29
  }
81
- // No DB file — create + migrate from Markdown if .gsd/ has content
30
+ // No DB file — create an empty authoritative DB. Markdown migration is
31
+ // explicit-only; runtime startup must not import projections into state.
82
32
  if (existsSync(gsdDir)) {
83
- const hasDecisions = existsSync(join(gsdDir, "DECISIONS.md"));
84
- const hasRequirements = existsSync(join(gsdDir, "REQUIREMENTS.md"));
85
- const hasMilestones = existsSync(join(gsdDir, "milestones"));
86
- if (hasDecisions || hasRequirements || hasMilestones) {
87
- const opened = db.openDatabase(dbPath);
88
- if (opened) {
89
- setLogBasePath(projectRoot);
90
- try {
91
- const { migrateFromMarkdown } = await import("../md-importer.js");
92
- migrateFromMarkdown(basePath);
93
- }
94
- catch (err) {
95
- logWarning("bootstrap", `ensureDbOpen auto-migration failed: ${err.message}`);
96
- }
97
- }
98
- return opened;
99
- }
100
- // .gsd/ exists but has no Markdown content (fresh project) — create empty DB
101
33
  const opened = db.openDatabase(dbPath);
102
34
  if (opened)
103
35
  setLogBasePath(projectRoot);
@@ -60,7 +60,7 @@ export function registerHooks(pi, ecosystemHandlers) {
60
60
  const { initHealthWidget } = await import("../health-widget.js");
61
61
  initHealthWidget(ctx);
62
62
  }
63
- resetWriteGateState();
63
+ resetWriteGateState(process.cwd());
64
64
  resetToolCallLoopGuard();
65
65
  approvalQuestionAbortInFlight = false;
66
66
  await resetAskUserQuestionsTurnCache();
@@ -109,10 +109,10 @@ export function registerHooks(pi, ecosystemHandlers) {
109
109
  pi.on("session_switch", async (_event, ctx) => {
110
110
  initNotificationStore(process.cwd());
111
111
  installNotifyInterceptor(ctx);
112
- resetWriteGateState();
112
+ resetWriteGateState(process.cwd());
113
113
  resetToolCallLoopGuard();
114
114
  await resetAskUserQuestionsTurnCache();
115
- clearDiscussionFlowState();
115
+ clearDiscussionFlowState(process.cwd());
116
116
  await syncServiceTierStatus(ctx);
117
117
  await applyDisabledModelProviderPolicy(ctx);
118
118
  // Skip MCP auto-prep when running inside an auto-worktree. The worktree
@@ -137,13 +137,14 @@ export function registerHooks(pi, ecosystemHandlers) {
137
137
  // Wait for ecosystem loader to finish (no-op after first turn).
138
138
  const { getEcosystemReadyPromise } = await import("../ecosystem/loader.js");
139
139
  await getEcosystemReadyPromise();
140
+ const beforeAgentBasePath = process.cwd();
140
141
  const pendingApprovalGate = getPendingGate();
141
142
  if (pendingApprovalGate && isExplicitApprovalResponse(event.prompt, pendingApprovalGate)) {
142
- markApprovalGateVerified(pendingApprovalGate);
143
+ markApprovalGateVerified(pendingApprovalGate, beforeAgentBasePath);
143
144
  const milestoneId = extractDepthVerificationMilestoneId(pendingApprovalGate);
144
145
  if (milestoneId)
145
- markDepthVerified(milestoneId);
146
- clearPendingGate();
146
+ markDepthVerified(milestoneId, beforeAgentBasePath);
147
+ clearPendingGate(beforeAgentBasePath);
147
148
  }
148
149
  // GSD's own context injection (existing behavior — unchanged).
149
150
  const { buildBeforeAgentStartResult } = await import("./system-context.js");
@@ -318,7 +319,7 @@ export function registerHooks(pi, ecosystemHandlers) {
318
319
  return;
319
320
  const gateId = approvalGateIdForUnit(unitType, unitId);
320
321
  if (gateId)
321
- setPendingGate(gateId);
322
+ setPendingGate(gateId, process.cwd());
322
323
  approvalQuestionAbortInFlight = true;
323
324
  ctx.ui.notify(`${unitType}${unitId ? ` ${unitId}` : ""} is waiting for your approval - pausing before more tool calls run.`, "info");
324
325
  // The pending gate set above blocks subsequent non-read-only tool calls
@@ -360,7 +361,7 @@ export function registerHooks(pi, ecosystemHandlers) {
360
361
  const questions = event.input?.questions ?? [];
361
362
  const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
362
363
  if (typeof questionId === "string") {
363
- setPendingGate(questionId);
364
+ setPendingGate(questionId, discussionBasePath);
364
365
  }
365
366
  }
366
367
  // ── Discussion gate enforcement: block tool calls while gate is pending ──
@@ -501,7 +502,8 @@ export function registerHooks(pi, ecosystemHandlers) {
501
502
  const toolName = canonicalToolName(event.toolName);
502
503
  if (toolName !== "ask_user_questions")
503
504
  return;
504
- const milestoneId = await getDiscussionMilestoneIdFor(process.cwd());
505
+ const basePath = process.cwd();
506
+ const milestoneId = await getDiscussionMilestoneIdFor(basePath);
505
507
  const queueActive = isQueuePhaseActive();
506
508
  const details = event.details;
507
509
  // ── Discussion gate enforcement: handle gate question responses ──
@@ -533,11 +535,11 @@ export function registerHooks(pi, ecosystemHandlers) {
533
535
  if (pendingQuestion) {
534
536
  const answer = details.response?.answers?.[currentPendingGate];
535
537
  if (isDepthConfirmationAnswer(answer?.selected, pendingQuestion.options)) {
536
- markApprovalGateVerified(currentPendingGate);
538
+ markApprovalGateVerified(currentPendingGate, basePath);
537
539
  const milestoneIdFromGate = extractDepthVerificationMilestoneId(currentPendingGate);
538
540
  if (milestoneIdFromGate)
539
- markDepthVerified(milestoneIdFromGate);
540
- clearPendingGate();
541
+ markDepthVerified(milestoneIdFromGate, basePath);
542
+ clearPendingGate(basePath);
541
543
  }
542
544
  }
543
545
  }
@@ -553,9 +555,9 @@ export function registerHooks(pi, ecosystemHandlers) {
553
555
  if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
554
556
  if (currentPendingGate && question.id !== currentPendingGate)
555
557
  break;
556
- markApprovalGateVerified(question.id);
557
- markDepthVerified(inferredMilestoneId);
558
- clearPendingGate();
558
+ markApprovalGateVerified(question.id, basePath);
559
+ markDepthVerified(inferredMilestoneId, basePath);
560
+ clearPendingGate(basePath);
559
561
  }
560
562
  break;
561
563
  }
@@ -564,7 +566,6 @@ export function registerHooks(pi, ecosystemHandlers) {
564
566
  return;
565
567
  if (!milestoneId)
566
568
  return;
567
- const basePath = process.cwd();
568
569
  const milestoneDir = resolveMilestonePath(basePath, milestoneId);
569
570
  if (!milestoneDir)
570
571
  return;
@@ -55,19 +55,31 @@ const QUEUE_SAFE_TOOLS = new Set([
55
55
  * true / false — shell no-ops / test exit codes
56
56
  */
57
57
  const BASH_READ_ONLY_RE = /^\s*(cat|head|tail|less|more|wc|file|stat|du|df|which|type|echo|printf|ls|find|grep|rg|awk|sed\b(?!.*-i)|sort|uniq|diff|comm|tr|cut|tee\s+-a\s+\/dev\/null|git\s+(log|show|diff|status|branch|tag|remote|rev-parse|ls-files|blame|shortlog|describe|stash\s+list|config\s+--get|cat-file)|gh\s+(issue|pr|api|repo|release)\s+(view|list|diff|status|checks)|mkdir\s+-p\s+\.gsd|rtk\s|npm\s+run\s+(test|test:\w+|lint|lint:\w+|typecheck|type-check|type-check:\w+|check|verify|audit|outdated|format:check|ci|validate)\b|npm\s+(ls|list|info|view|show|outdated|audit|explain|doctor|ping|--version|-v)\b|npx\s|tsx\s|node\s+(--print|--version|-v\b)|python[23]?\s+(-c\s+'[^']*'|--version|-V\b|-m\s+(pip\s+show|pip\s+list|site))|pip[23]?\s+(show|list|freeze|check|index\s+versions)\b|jq\s|yq\s|curl\s+(-s\b|--silent\b)(?!\s+[^|>]*\s-[oO]\b)(?!\s+[^|>]*\s--output\b)[^|>]*$|openssl\s+(version|x509|s_client)|env\b|printenv\b|true\b|false\b)/;
58
- const verifiedDepthMilestones = new Set();
59
- const verifiedApprovalGates = new Set();
60
- let activeQueuePhase = false;
58
+ function createEmptyWriteGateState() {
59
+ return {
60
+ verifiedDepthMilestones: new Set(),
61
+ verifiedApprovalGates: new Set(),
62
+ activeQueuePhase: false,
63
+ pendingGateId: null,
64
+ };
65
+ }
66
+ const writeGateStatesByBasePath = new Map();
67
+ function writeGateStateKey(basePath) {
68
+ return resolve(basePath);
69
+ }
70
+ function getWriteGateState(basePath = process.cwd()) {
71
+ const key = writeGateStateKey(basePath);
72
+ let state = writeGateStatesByBasePath.get(key);
73
+ if (!state) {
74
+ state = createEmptyWriteGateState();
75
+ writeGateStatesByBasePath.set(key, state);
76
+ }
77
+ return state;
78
+ }
61
79
  /**
62
- * Discussion gate enforcement state.
63
- *
64
- * When ask_user_questions is called with a recognized gate question ID,
65
- * we track the pending gate. Until the gate is confirmed (user selects the
66
- * first/recommended option), all non-read-only tool calls are blocked.
67
- * This mechanically prevents the model from rationalizing past failed or
68
- * cancelled gate questions.
80
+ * Discussion gate enforcement state is scoped per basePath so multiple
81
+ * workspaces can coexist in the same process without sharing gate state.
69
82
  */
70
- let pendingGateId = null;
71
83
  /**
72
84
  * Recognized gate question ID patterns.
73
85
  * These appear in discuss.md (depth/requirements/roadmap).
@@ -99,24 +111,25 @@ function shouldPersistWriteGateSnapshot(env = process.env) {
99
111
  const v = env.GSD_PERSIST_WRITE_GATE_STATE;
100
112
  return v !== "0" && v !== "false";
101
113
  }
102
- function writeGateSnapshotPath(basePath = process.cwd()) {
114
+ function writeGateSnapshotPath(basePath) {
103
115
  return join(basePath, ".gsd", "runtime", "write-gate-state.json");
104
116
  }
105
- function currentWriteGateSnapshot() {
117
+ function currentWriteGateSnapshot(basePath = process.cwd()) {
118
+ const state = getWriteGateState(basePath);
106
119
  return {
107
- verifiedDepthMilestones: [...verifiedDepthMilestones].sort(),
108
- verifiedApprovalGates: [...verifiedApprovalGates].sort(),
109
- activeQueuePhase,
110
- pendingGateId,
120
+ verifiedDepthMilestones: [...state.verifiedDepthMilestones].sort(),
121
+ verifiedApprovalGates: [...state.verifiedApprovalGates].sort(),
122
+ activeQueuePhase: state.activeQueuePhase,
123
+ pendingGateId: state.pendingGateId,
111
124
  };
112
125
  }
113
- function persistWriteGateSnapshot(basePath = process.cwd()) {
126
+ function persistWriteGateSnapshot(basePath) {
114
127
  if (!shouldPersistWriteGateSnapshot())
115
128
  return;
116
129
  const path = writeGateSnapshotPath(basePath);
117
130
  mkdirSync(join(basePath, ".gsd", "runtime"), { recursive: true });
118
131
  const tempPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
119
- writeFileSync(tempPath, JSON.stringify(currentWriteGateSnapshot(), null, 2), "utf-8");
132
+ writeFileSync(tempPath, JSON.stringify(currentWriteGateSnapshot(basePath), null, 2), "utf-8");
120
133
  try {
121
134
  renameSync(tempPath, path);
122
135
  }
@@ -132,7 +145,7 @@ function persistWriteGateSnapshot(basePath = process.cwd()) {
132
145
  }
133
146
  }
134
147
  }
135
- function clearPersistedWriteGateSnapshot(basePath = process.cwd()) {
148
+ function clearPersistedWriteGateSnapshot(basePath) {
136
149
  if (!shouldPersistWriteGateSnapshot())
137
150
  return;
138
151
  const path = writeGateSnapshotPath(basePath);
@@ -164,7 +177,7 @@ const EMPTY_SNAPSHOT = {
164
177
  activeQueuePhase: false,
165
178
  pendingGateId: null,
166
179
  };
167
- export function loadWriteGateSnapshot(basePath = process.cwd()) {
180
+ export function loadWriteGateSnapshot(basePath) {
168
181
  const path = writeGateSnapshotPath(basePath);
169
182
  if (!existsSync(path)) {
170
183
  // When persist mode is active and the file has been deleted, treat it as a
@@ -172,61 +185,59 @@ export function loadWriteGateSnapshot(basePath = process.cwd()) {
172
185
  // In non-persist mode the file is never written, so fall back to in-memory.
173
186
  if (shouldPersistWriteGateSnapshot())
174
187
  return EMPTY_SNAPSHOT;
175
- return currentWriteGateSnapshot();
188
+ return currentWriteGateSnapshot(basePath);
176
189
  }
177
190
  try {
178
191
  return normalizeWriteGateSnapshot(JSON.parse(readFileSync(path, "utf-8")));
179
192
  }
180
193
  catch {
181
- return currentWriteGateSnapshot();
194
+ return currentWriteGateSnapshot(basePath);
182
195
  }
183
196
  }
184
- export function isDepthVerified() {
185
- return verifiedDepthMilestones.size > 0;
197
+ export function isDepthVerified(basePath = process.cwd()) {
198
+ return getWriteGateState(basePath).verifiedDepthMilestones.size > 0;
186
199
  }
187
200
  /**
188
201
  * Check whether a specific milestone has passed depth verification.
189
202
  */
190
- export function isMilestoneDepthVerified(milestoneId) {
203
+ export function isMilestoneDepthVerified(milestoneId, basePath = process.cwd()) {
191
204
  if (!milestoneId)
192
205
  return false;
193
- return verifiedDepthMilestones.has(milestoneId);
206
+ return getWriteGateState(basePath).verifiedDepthMilestones.has(milestoneId);
194
207
  }
195
208
  export function isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId) {
196
209
  if (!milestoneId)
197
210
  return false;
198
211
  return snapshot.verifiedDepthMilestones.includes(milestoneId);
199
212
  }
200
- export function isQueuePhaseActive() {
201
- return activeQueuePhase;
213
+ export function isQueuePhaseActive(basePath = process.cwd()) {
214
+ return getWriteGateState(basePath).activeQueuePhase;
202
215
  }
203
- export function setQueuePhaseActive(active) {
204
- activeQueuePhase = active;
205
- persistWriteGateSnapshot();
216
+ export function setQueuePhaseActive(active, basePath) {
217
+ getWriteGateState(basePath).activeQueuePhase = active;
218
+ persistWriteGateSnapshot(basePath);
206
219
  }
207
- export function resetWriteGateState() {
208
- verifiedDepthMilestones.clear();
209
- verifiedApprovalGates.clear();
210
- pendingGateId = null;
211
- persistWriteGateSnapshot();
220
+ export function resetWriteGateState(basePath) {
221
+ const state = getWriteGateState(basePath);
222
+ state.verifiedDepthMilestones.clear();
223
+ state.verifiedApprovalGates.clear();
224
+ state.pendingGateId = null;
225
+ persistWriteGateSnapshot(basePath);
212
226
  }
213
- export function clearDiscussionFlowState() {
214
- verifiedDepthMilestones.clear();
215
- verifiedApprovalGates.clear();
216
- activeQueuePhase = false;
217
- pendingGateId = null;
218
- clearPersistedWriteGateSnapshot();
227
+ export function clearDiscussionFlowState(basePath) {
228
+ writeGateStatesByBasePath.delete(writeGateStateKey(basePath));
229
+ clearPersistedWriteGateSnapshot(basePath);
219
230
  }
220
231
  export function markDepthVerified(milestoneId, basePath = process.cwd()) {
221
232
  if (!milestoneId)
222
233
  return;
223
- verifiedDepthMilestones.add(milestoneId);
234
+ getWriteGateState(basePath).verifiedDepthMilestones.add(milestoneId);
224
235
  persistWriteGateSnapshot(basePath);
225
236
  }
226
237
  export function markApprovalGateVerified(gateId, basePath = process.cwd()) {
227
238
  if (!gateId)
228
239
  return;
229
- verifiedApprovalGates.add(gateId);
240
+ getWriteGateState(basePath).verifiedApprovalGates.add(gateId);
230
241
  persistWriteGateSnapshot(basePath);
231
242
  }
232
243
  export function isApprovalGateVerifiedInSnapshot(snapshot, gateId) {
@@ -258,26 +269,27 @@ function extractContextMilestoneId(inputPath) {
258
269
  /**
259
270
  * Mark a gate as pending (called when ask_user_questions is invoked with a gate ID).
260
271
  */
261
- export function setPendingGate(gateId) {
262
- pendingGateId = gateId;
263
- verifiedApprovalGates.delete(gateId);
272
+ export function setPendingGate(gateId, basePath) {
273
+ const state = getWriteGateState(basePath);
274
+ state.pendingGateId = gateId;
275
+ state.verifiedApprovalGates.delete(gateId);
264
276
  const milestoneId = extractDepthVerificationMilestoneId(gateId);
265
277
  if (milestoneId)
266
- verifiedDepthMilestones.delete(milestoneId);
267
- persistWriteGateSnapshot();
278
+ state.verifiedDepthMilestones.delete(milestoneId);
279
+ persistWriteGateSnapshot(basePath);
268
280
  }
269
281
  /**
270
282
  * Clear the pending gate (called when the user confirms).
271
283
  */
272
- export function clearPendingGate() {
273
- pendingGateId = null;
274
- persistWriteGateSnapshot();
284
+ export function clearPendingGate(basePath) {
285
+ getWriteGateState(basePath).pendingGateId = null;
286
+ persistWriteGateSnapshot(basePath);
275
287
  }
276
288
  /**
277
289
  * Get the currently pending gate, if any.
278
290
  */
279
- export function getPendingGate() {
280
- return pendingGateId;
291
+ export function getPendingGate(basePath = process.cwd()) {
292
+ return getWriteGateState(basePath).pendingGateId;
281
293
  }
282
294
  /**
283
295
  * Check whether a tool call should be blocked because a discussion gate
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { generateCodebaseMap, updateCodebaseMap, writeCodebaseMap, getCodebaseMapStats, readCodebaseMap, } from "./codebase-generator.js";
8
8
  import { loadEffectiveGSDPreferences } from "./preferences.js";
9
- import { projectRoot } from "./commands/context.js";
9
+ import { currentDirectoryRoot } from "./commands/context.js";
10
10
  const USAGE = "Usage: /gsd codebase [generate|update|stats]\n\n" +
11
11
  " generate [--max-files N] [--collapse-threshold N] — Generate or regenerate CODEBASE.md\n" +
12
12
  " update [--max-files N] [--collapse-threshold N] — Refresh the CODEBASE.md cache immediately\n" +
@@ -20,7 +20,7 @@ const USAGE = "Usage: /gsd codebase [generate|update|stats]\n\n" +
20
20
  " max_files: 1000\n" +
21
21
  " collapse_threshold: 15";
22
22
  export async function handleCodebase(args, ctx, _pi) {
23
- const basePath = projectRoot();
23
+ const basePath = currentDirectoryRoot();
24
24
  const parts = args.trim().split(/\s+/);
25
25
  const sub = parts[0] ?? "";
26
26
  switch (sub) {