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
@@ -32,7 +32,7 @@ import {
32
32
  import { atomicWriteSync } from "./atomic-write.js";
33
33
  import { execFileSync } from "node:child_process";
34
34
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
35
- import { gsdRoot } from "./paths.js";
35
+ import { gsdRoot, resolveGsdPathContract } from "./paths.js";
36
36
  import {
37
37
  createWorktree,
38
38
  removeWorktree,
@@ -76,6 +76,7 @@ import {
76
76
  nativeMergeAbort,
77
77
  } from "./native-git-bridge.js";
78
78
  import { gsdHome } from "./gsd-home.js";
79
+ import { type MilestoneScope, type GsdWorkspace, createWorkspace } from "./workspace.js";
79
80
 
80
81
  const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
81
82
  const LEGACY_PROJECT_PREFERENCES_FILE = "preferences.md";
@@ -90,9 +91,8 @@ const LEGACY_DEEP_SETUP_RUNTIME_UNIT_FILES = new Set([
90
91
  // ─── Shared Constants & Helpers ─────────────────────────────────────────────
91
92
 
92
93
  /**
93
- * Root-level .gsd/ state files synced between worktree and project root.
94
- * Single source of truth used by syncGsdStateToWorktree, syncWorktreeStateBack,
95
- * and the dispatch-level sync functions.
94
+ * Root-level .gsd/ projections copied from project root into worktrees for
95
+ * compatibility. Project root remains the canonical state/projection root.
96
96
  */
97
97
  const ROOT_STATE_FILES = [
98
98
  "DECISIONS.md",
@@ -110,6 +110,11 @@ const ROOT_STATE_FILES = [
110
110
  // because the project root is authoritative for preferences (#2684).
111
111
  ] as const;
112
112
 
113
+ const ROOT_DIAGNOSTIC_FILES = [
114
+ "completed-units.json",
115
+ "metrics.json",
116
+ ] as const;
117
+
113
118
  /**
114
119
  * Pop a stash entry by tracking the unique marker embedded in its message so
115
120
  * concurrent stash operations against the same project root cannot cause us to
@@ -194,7 +199,7 @@ const VERDICT_RE = /verdict:\s*[\w-]+/i;
194
199
  * destination when the source copy contains a `verdict:` field.
195
200
  *
196
201
  * This is the targeted fix for the UAT stuck-loop (#2821): the main
197
- * safeCopyRecursive uses force:false to protect worktree-authoritative
202
+ * safeCopyRecursive uses force:false to protect worktree-local projection
198
203
  * files (#1886), but ASSESSMENT files written by run-uat must be
199
204
  * forward-synced when the project root has a verdict. Without this,
200
205
  * the worktree retains a stale FAIL or missing ASSESSMENT and
@@ -250,8 +255,16 @@ function forceOverwriteAssessmentsWithVerdict(
250
255
 
251
256
  // ─── Module State ──────────────────────────────────────────────────────────
252
257
 
253
- /** Original project root before chdir into auto-worktree. */
254
- let originalBase: string | null = null;
258
+ /** Active workspace registry replaces the legacy `originalBase` singleton. */
259
+ let activeWorkspace: GsdWorkspace | null = null;
260
+
261
+ function setActiveWorkspace(ws: GsdWorkspace | null): void {
262
+ activeWorkspace = ws;
263
+ }
264
+
265
+ function getActiveWorkspace(): GsdWorkspace | null {
266
+ return activeWorkspace;
267
+ }
255
268
 
256
269
  function clearProjectRootStateFiles(basePath: string, milestoneId: string): void {
257
270
  const gsdDir = gsdRoot(basePath);
@@ -272,9 +285,9 @@ function clearProjectRootStateFiles(basePath: string, milestoneId: string): void
272
285
  }
273
286
  }
274
287
 
275
- // Clean up entire synced milestone directory and runtime/units.
276
- // syncStateToProjectRoot() copies these into the project root during
277
- // execution. If they remain as untracked files when we attempt
288
+ // Clean up legacy synced milestone directories and runtime/units.
289
+ // Older versions copied these into the project root during execution.
290
+ // If they remain as untracked files when we attempt
278
291
  // `git merge --squash`, git rejects the merge with "local changes would
279
292
  // be overwritten", causing silent data loss (#1738).
280
293
  const syncedDirs = [
@@ -341,6 +354,41 @@ export const isSafeToAutoResolve = (filePath: string): boolean =>
341
354
  * gsd.db in the worktree so it rebuilds from fresh disk state (#853).
342
355
  * Non-fatal — sync failure should never block dispatch.
343
356
  */
357
+ /**
358
+ * Scope-typed variant of syncProjectRootToWorktree.
359
+ *
360
+ * Takes an explicit (rootScope, worktreeScope) pair where rootScope is the
361
+ * project root and worktreeScope is the auto-worktree. Direction is encoded
362
+ * in argument order. Asserts both scopes belong to the same workspace identity
363
+ * to prevent silent mismatch bugs.
364
+ */
365
+ export function syncProjectRootToWorktreeByScope(
366
+ rootScope: MilestoneScope,
367
+ worktreeScope: MilestoneScope,
368
+ ): void {
369
+ if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
370
+ throw new Error(
371
+ `syncProjectRootToWorktreeByScope: scope identity mismatch — ` +
372
+ `rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
373
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`,
374
+ );
375
+ }
376
+ if (rootScope.milestoneId !== worktreeScope.milestoneId) {
377
+ throw new Error(
378
+ `syncProjectRootToWorktreeByScope: milestoneId mismatch — ` +
379
+ `rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`,
380
+ );
381
+ }
382
+ const projectRoot = rootScope.workspace.projectRoot;
383
+ const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
384
+ const milestoneId = rootScope.milestoneId;
385
+ syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId);
386
+ }
387
+
388
+ /**
389
+ * @deprecated Use syncProjectRootToWorktreeByScope instead.
390
+ * TODO(C-future): remove once all callers migrated.
391
+ */
344
392
  export function syncProjectRootToWorktree(
345
393
  projectRoot: string,
346
394
  worktreePath_: string,
@@ -349,8 +397,9 @@ export function syncProjectRootToWorktree(
349
397
  if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot) return;
350
398
  if (!milestoneId) return;
351
399
 
352
- const prGsd = join(projectRoot, ".gsd");
353
- const wtGsd = join(worktreePath_, ".gsd");
400
+ const contract = resolveGsdPathContract(worktreePath_, projectRoot);
401
+ const prGsd = contract.projectGsd;
402
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath_, ".gsd");
354
403
 
355
404
  // When .gsd is a symlink to the same external directory in both locations,
356
405
  // cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
@@ -359,7 +408,7 @@ export function syncProjectRootToWorktree(
359
408
 
360
409
  // Copy milestone directory from project root to worktree — additive only.
361
410
  // force:false prevents cpSync from overwriting existing worktree files.
362
- // Without this, worktree-authoritative files (e.g. VALIDATION.md written
411
+ // Without this, worktree-local files (e.g. VALIDATION.md written
363
412
  // by validate-milestone) get clobbered by stale project root copies,
364
413
  // causing an infinite re-validation loop (#1886).
365
414
  safeCopyRecursive(
@@ -369,7 +418,7 @@ export function syncProjectRootToWorktree(
369
418
  );
370
419
 
371
420
  // Force-sync ASSESSMENT files that have a verdict from project root (#2821).
372
- // The additive-only copy above preserves worktree-authoritative files, but
421
+ // The additive-only copy above preserves worktree-local files, but
373
422
  // ASSESSMENT files are special: after run-uat writes a verdict and post-unit
374
423
  // syncs it to the project root, the worktree may retain a stale copy (e.g.
375
424
  // verdict:fail while the project root has verdict:pass from a retry). On
@@ -390,11 +439,9 @@ export function syncProjectRootToWorktree(
390
439
  { force: true },
391
440
  );
392
441
 
393
- // Delete worktree gsd.db ONLY if it is empty (0 bytes).
394
- // An empty DB is stale/corrupt and should be rebuilt (#853).
395
- // A non-empty DB was populated by gsd-migrate on respawn and must be
396
- // preserved — deleting it truncates the file to 0 bytes when
397
- // openDatabase re-creates it, causing "no such table" failures (#2815).
442
+ // Delete a legacy worktree-local gsd.db ONLY if it is empty (0 bytes).
443
+ // Runtime opens contract.projectDb; this cleanup only removes corrupt
444
+ // pre-upgrade local DB projections.
398
445
  try {
399
446
  const wtDb = join(wtGsd, "gsd.db");
400
447
  let deleteSidecars = false;
@@ -428,10 +475,43 @@ export function syncProjectRootToWorktree(
428
475
  }
429
476
 
430
477
  /**
431
- * Sync dispatch-critical .gsd/ state files from worktree to project root.
478
+ * Scope-typed variant of syncStateToProjectRoot.
479
+ *
480
+ * Takes an explicit (worktreeScope, rootScope) pair. Direction is encoded in
481
+ * argument order (worktree → root). Asserts both scopes belong to the same
482
+ * workspace identity to prevent silent mismatch bugs.
483
+ */
484
+ export function syncStateToProjectRootByScope(
485
+ worktreeScope: MilestoneScope,
486
+ rootScope: MilestoneScope,
487
+ ): void {
488
+ if (worktreeScope.workspace.identityKey !== rootScope.workspace.identityKey) {
489
+ throw new Error(
490
+ `syncStateToProjectRootByScope: scope identity mismatch — ` +
491
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}" ` +
492
+ `rootScope.identityKey="${rootScope.workspace.identityKey}"`,
493
+ );
494
+ }
495
+ if (worktreeScope.milestoneId !== rootScope.milestoneId) {
496
+ throw new Error(
497
+ `syncStateToProjectRootByScope: milestoneId mismatch — ` +
498
+ `worktreeScope.milestoneId="${worktreeScope.milestoneId}" rootScope.milestoneId="${rootScope.milestoneId}"`,
499
+ );
500
+ }
501
+ const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
502
+ const projectRoot = rootScope.workspace.projectRoot;
503
+ const milestoneId = worktreeScope.milestoneId;
504
+ syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId);
505
+ }
506
+
507
+ /**
508
+ * Sync worktree diagnostics from worktree to project root.
432
509
  * Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
433
- * Copies: STATE.md + active milestone directory (roadmap, slice plans, task summaries).
510
+ * DB/project-root state remains authoritative; markdown projections are not
511
+ * copied from the worktree back to the project root.
434
512
  * Non-fatal — sync failure should never block dispatch.
513
+ * @deprecated Use syncStateToProjectRootByScope instead.
514
+ * TODO(C-future): remove once all callers migrated.
435
515
  */
436
516
  export function syncStateToProjectRoot(
437
517
  worktreePath_: string,
@@ -441,31 +521,25 @@ export function syncStateToProjectRoot(
441
521
  if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot) return;
442
522
  if (!milestoneId) return;
443
523
 
444
- const wtGsd = join(worktreePath_, ".gsd");
445
- const prGsd = join(projectRoot, ".gsd");
524
+ const contract = resolveGsdPathContract(worktreePath_, projectRoot);
525
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath_, ".gsd");
526
+ const prGsd = contract.projectGsd;
446
527
 
447
528
  // When .gsd is a symlink to the same external directory in both locations,
448
529
  // cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
449
530
  // Compare realpaths and skip when they resolve to the same physical path (#2184).
450
531
  if (isSamePath(wtGsd, prGsd)) return;
451
532
 
452
- // 1. STATE.md the quick-glance status used by initial deriveState()
453
- safeCopy(join(wtGsd, "STATE.md"), join(prGsd, "STATE.md"), { force: true });
454
-
455
- // 2. Milestone directory — ROADMAP, slice PLANs, task summaries
456
- // Copy the entire milestone .gsd subtree so deriveState reads current checkboxes
457
- safeCopyRecursive(
458
- join(wtGsd, "milestones", milestoneId),
459
- join(prGsd, "milestones", milestoneId),
460
- { force: true },
461
- );
462
-
463
- // 3. metrics.json — session cost/token tracking (#2313).
533
+ // metrics.jsonsession cost/token tracking (#2313).
464
534
  // Without this, metrics accumulated in the worktree are invisible from the
465
535
  // project root and never appear in the dashboard or skill-health reports.
466
536
  safeCopy(join(wtGsd, "metrics.json"), join(prGsd, "metrics.json"), { force: true });
467
537
 
468
- // 4. Runtime records unit dispatch state used by selfHealRuntimeRecords().
538
+ // completed-units.jsonruntime completion diagnostics used to avoid
539
+ // re-dispatching work already completed in an isolated worktree.
540
+ safeCopy(join(wtGsd, "completed-units.json"), join(prGsd, "completed-units.json"), { force: true });
541
+
542
+ // Runtime records — unit dispatch diagnostics used by selfHealRuntimeRecords().
469
543
  // Without this, a crash during a unit leaves the runtime record only in the
470
544
  // worktree. If the next session resolves basePath before worktree re-entry,
471
545
  // selfHeal can't find or clear the stale record (#769).
@@ -627,6 +701,30 @@ export function cleanStaleRuntimeUnits(
627
701
 
628
702
  // ─── Worktree ↔ Main Repo Sync (#1311) ──────────────────────────────────────
629
703
 
704
+ /**
705
+ * Scope-typed variant of syncGsdStateToWorktree.
706
+ *
707
+ * Takes an explicit (rootScope, worktreeScope) pair. Note: milestoneId is not
708
+ * used by syncGsdStateToWorktree — this variant only requires workspace
709
+ * identity. Asserts both scopes belong to the same workspace identity to
710
+ * prevent silent mismatch bugs.
711
+ */
712
+ export function syncGsdStateToWorktreeByScope(
713
+ rootScope: MilestoneScope,
714
+ worktreeScope: MilestoneScope,
715
+ ): { synced: string[] } {
716
+ if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
717
+ throw new Error(
718
+ `syncGsdStateToWorktreeByScope: scope identity mismatch — ` +
719
+ `rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
720
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`,
721
+ );
722
+ }
723
+ const mainBasePath = rootScope.workspace.projectRoot;
724
+ const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
725
+ return syncGsdStateToWorktree(mainBasePath, worktreePath_);
726
+ }
727
+
630
728
  /**
631
729
  * Sync .gsd/ state from the main repo into the worktree.
632
730
  *
@@ -638,15 +736,19 @@ export function cleanStaleRuntimeUnits(
638
736
  * missing milestones, CONTEXT, ROADMAP, DECISIONS, REQUIREMENTS, and
639
737
  * PROJECT files from the main repo's .gsd/ into the worktree's .gsd/.
640
738
  *
641
- * Only adds missing content — never overwrites existing files in the worktree
642
- * (the worktree's execution state is authoritative for in-progress work).
739
+ * Only adds missing content — never overwrites existing files in the worktree.
740
+ * Worktree files are compatibility projections; DB/project root remains
741
+ * authoritative for runtime state.
742
+ * @deprecated Use syncGsdStateToWorktreeByScope instead.
743
+ * TODO(C-future): remove once all callers migrated.
643
744
  */
644
745
  export function syncGsdStateToWorktree(
645
746
  mainBasePath: string,
646
747
  worktreePath_: string,
647
748
  ): { synced: string[] } {
648
- const mainGsd = gsdRoot(mainBasePath);
649
- const wtGsd = gsdRoot(worktreePath_);
749
+ const contract = resolveGsdPathContract(worktreePath_, mainBasePath);
750
+ const mainGsd = contract.projectGsd;
751
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath_, ".gsd");
650
752
  const synced: string[] = [];
651
753
 
652
754
  // If both resolve to the same directory (symlink), no sync needed
@@ -790,32 +892,26 @@ export function syncGsdStateToWorktree(
790
892
  }
791
893
 
792
894
  /**
793
- * Sync milestone artifacts from worktree back to the main external state directory.
794
- * Called before milestone merge to ensure completion artifacts (SUMMARY, VALIDATION,
795
- * updated ROADMAP) are visible from the project root (#1412).
895
+ * Sync compatibility artifacts from worktree back to the main external state
896
+ * directory. Canonical workflow state lives in the project DB; worktree .gsd
897
+ * content is legacy projection/diagnostic data only.
796
898
  *
797
899
  * Syncs:
798
- * 1. Root-level .gsd/ files (REQUIREMENTS, PROJECT, DECISIONS, KNOWLEDGE,
799
- * OVERRIDES) the worktree's versions overwrite main's because the
800
- * worktree is the authoritative execution context.
801
- * 2. ALL milestone directories found in the worktree — not just the
802
- * current milestoneId. The complete-milestone unit may create artifacts
803
- * for the *next* milestone (CONTEXT, ROADMAP, new requirements) which
804
- * must survive worktree teardown.
900
+ * 1. Legacy worktree DBs are reconciled into the canonical project DB.
901
+ * 2. Runtime diagnostic files may be copied for operator visibility.
805
902
  *
806
- * History: Originally only synced milestones/<milestoneId>/ and assumed
807
- * root-level files would be carried by the squash merge. In practice,
808
- * .gsd/ files are often untracked (gitignored or never committed), so the
809
- * squash merge carries nothing. This caused next-milestone artifacts and
810
- * updated REQUIREMENTS/PROJECT to be silently lost on teardown.
903
+ * Markdown milestone directories are projections and are not copied from
904
+ * worktrees into the project root. Current workflow state must arrive through
905
+ * the shared project DB or the pre-upgrade DB reconciliation path above.
811
906
  */
812
907
  export function syncWorktreeStateBack(
813
908
  mainBasePath: string,
814
909
  worktreePath: string,
815
910
  milestoneId: string,
816
911
  ): { synced: string[] } {
817
- const mainGsd = gsdRoot(mainBasePath);
818
- const wtGsd = gsdRoot(worktreePath);
912
+ const contract = resolveGsdPathContract(worktreePath, mainBasePath);
913
+ const mainGsd = contract.projectGsd;
914
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath, ".gsd");
819
915
  const synced: string[] = [];
820
916
 
821
917
  // If both resolve to the same directory (symlink), no sync needed
@@ -829,7 +925,7 @@ export function syncWorktreeStateBack(
829
925
  // files. This handles in-flight worktrees that were created before the
830
926
  // upgrade to shared WAL mode.
831
927
  const wtLocalDb = join(wtGsd, "gsd.db");
832
- const mainDb = join(mainGsd, "gsd.db");
928
+ const mainDb = contract.projectDb;
833
929
  if (existsSync(wtLocalDb) && existsSync(mainDb)) {
834
930
  try {
835
931
  reconcileWorktreeDb(mainDb, wtLocalDb);
@@ -840,13 +936,10 @@ export function syncWorktreeStateBack(
840
936
  }
841
937
  }
842
938
 
843
- // ── 1. Sync root-level .gsd/ files back ──────────────────────────────
844
- // The worktree is authoritative — complete-milestone updates REQUIREMENTS,
845
- // PROJECT, etc. These must overwrite main's copies so they survive teardown.
846
- // Also includes QUEUE.md, completed-units.json, and metrics.json which are
847
- // written during milestone closeout and lost on teardown without explicit sync
848
- // (#1787, #2313).
849
- for (const f of ROOT_STATE_FILES) {
939
+ // ── 1. Sync root-level diagnostic files back ─────────────────────────
940
+ // Markdown/JSON state projections remain project-root/DB authoritative.
941
+ // These diagnostic files are copied for observability only.
942
+ for (const f of ROOT_DIAGNOSTIC_FILES) {
850
943
  const src = join(wtGsd, f);
851
944
  const dst = join(mainGsd, f);
852
945
  if (existsSync(src)) {
@@ -860,121 +953,8 @@ export function syncWorktreeStateBack(
860
953
  }
861
954
  }
862
955
 
863
- // ── 2. Sync ALL milestone directories ────────────────────────────────
864
- // The complete-milestone unit may create next-milestone artifacts (e.g.
865
- // M007 setup while closing M006). We must sync every milestone directory
866
- // in the worktree, not just the current one.
867
- const wtMilestonesDir = join(wtGsd, "milestones");
868
- if (!existsSync(wtMilestonesDir)) return { synced };
869
-
870
- try {
871
- const wtMilestones = readdirSync(wtMilestonesDir, { withFileTypes: true })
872
- .filter((d) => d.isDirectory())
873
- .map((d) => d.name);
874
-
875
- for (const mid of wtMilestones) {
876
- // Skip the current milestone being merged — its files are already in the
877
- // milestone branch and would conflict with the squash merge (#3641).
878
- if (mid === milestoneId) continue;
879
- syncMilestoneDir(wtGsd, mainGsd, mid, synced);
880
- }
881
- } catch (err) {
882
- /* non-fatal */
883
- logWarning("worktree", `milestone sync-back failed: ${err instanceof Error ? err.message : String(err)}`);
884
- }
885
-
886
956
  return { synced };
887
957
  }
888
-
889
- function syncCurrentMilestoneStateAfterMerge(
890
- mainBasePath: string,
891
- worktreePath: string,
892
- milestoneId: string,
893
- ): { synced: string[] } {
894
- const mainGsd = gsdRoot(mainBasePath);
895
- const wtGsd = gsdRoot(worktreePath);
896
- const synced: string[] = [];
897
-
898
- if (isSamePath(mainGsd, wtGsd)) return { synced };
899
- if (!existsSync(wtGsd) || !existsSync(mainGsd)) return { synced };
900
-
901
- syncMilestoneDir(wtGsd, mainGsd, milestoneId, synced);
902
- return { synced };
903
- }
904
-
905
- /**
906
- * Sync a single milestone directory from worktree to main.
907
- * Copies milestone-level .md files, slice-level files, and task summaries.
908
- */
909
- /** Copy matching files from srcDir to dstDir (non-fatal per file). */
910
- function syncDirFiles(
911
- srcDir: string,
912
- dstDir: string,
913
- filter: (name: string) => boolean,
914
- synced: string[],
915
- prefix: string,
916
- ): void {
917
- try {
918
- for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
919
- if (!entry.isFile() || !filter(entry.name)) continue;
920
- try {
921
- cpSync(join(srcDir, entry.name), join(dstDir, entry.name), { force: true });
922
- synced.push(`${prefix}${entry.name}`);
923
- } catch (err) {
924
- /* non-fatal */
925
- logWarning("worktree", `file copy failed (${prefix}${entry.name}): ${err instanceof Error ? err.message : String(err)}`);
926
- }
927
- }
928
- } catch (err) {
929
- /* non-fatal — srcDir may not be readable */
930
- logWarning("worktree", `directory read failed: ${err instanceof Error ? err.message : String(err)}`);
931
- }
932
- }
933
-
934
- function syncMilestoneDir(
935
- wtGsd: string,
936
- mainGsd: string,
937
- mid: string,
938
- synced: string[],
939
- ): void {
940
- const wtMilestoneDir = join(wtGsd, "milestones", mid);
941
- const mainMilestoneDir = join(mainGsd, "milestones", mid);
942
-
943
- if (!existsSync(wtMilestoneDir)) return;
944
- mkdirSync(mainMilestoneDir, { recursive: true });
945
-
946
- const isMd = (name: string): boolean => name.endsWith(".md");
947
-
948
- // Sync milestone-level files (SUMMARY, VALIDATION, ROADMAP, CONTEXT)
949
- syncDirFiles(wtMilestoneDir, mainMilestoneDir, isMd, synced, `milestones/${mid}/`);
950
-
951
- // Sync slice-level files (summaries, UATs) and task summaries (#1678)
952
- const wtSlicesDir = join(wtMilestoneDir, "slices");
953
- const mainSlicesDir = join(mainMilestoneDir, "slices");
954
- if (!existsSync(wtSlicesDir)) return;
955
-
956
- try {
957
- for (const sliceEntry of readdirSync(wtSlicesDir, { withFileTypes: true })) {
958
- if (!sliceEntry.isDirectory()) continue;
959
- const sid = sliceEntry.name;
960
- const wtSliceDir = join(wtSlicesDir, sid);
961
- const mainSliceDir = join(mainSlicesDir, sid);
962
- mkdirSync(mainSliceDir, { recursive: true });
963
-
964
- syncDirFiles(wtSliceDir, mainSliceDir, isMd, synced, `milestones/${mid}/slices/${sid}/`);
965
-
966
- const wtTasksDir = join(wtSliceDir, "tasks");
967
- const mainTasksDir = join(mainSliceDir, "tasks");
968
- if (existsSync(wtTasksDir)) {
969
- mkdirSync(mainTasksDir, { recursive: true });
970
- syncDirFiles(wtTasksDir, mainTasksDir, isMd, synced, `milestones/${mid}/slices/${sid}/tasks/`);
971
- }
972
- }
973
- } catch (err) {
974
- /* non-fatal */
975
- logWarning("worktree", `milestone slice sync failed (${mid}): ${err instanceof Error ? err.message : String(err)}`);
976
- }
977
- }
978
958
  // ─── Worktree Post-Create Hook (#597) ────────────────────────────────────────
979
959
 
980
960
  /**
@@ -1160,10 +1140,43 @@ export function enterBranchModeForMilestone(
1160
1140
  * directory at the project root and apply any [x] checkbox states that are
1161
1141
  * ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
1162
1142
  *
1163
- * This is safe because syncStateToProjectRoot() is the authoritative source
1164
- * of post-task state at the project root it writes the same [x] the LLM
1165
- * produced, then the auto-commit follows. If the commit never happened, the
1166
- * filesystem copy is still valid and correct.
1143
+ * This is forward-only compatibility for legacy projection copies. The DB
1144
+ * remains authoritative; this never downgrades checked boxes in a local
1145
+ * worktree projection.
1146
+ */
1147
+ /**
1148
+ * Scope-typed variant of reconcilePlanCheckboxes.
1149
+ *
1150
+ * Takes an explicit (rootScope, worktreeScope) pair. milestoneId is taken
1151
+ * from rootScope. Asserts both scopes belong to the same workspace identity
1152
+ * to prevent silent mismatch bugs.
1153
+ */
1154
+ export function reconcilePlanCheckboxesByScope(
1155
+ rootScope: MilestoneScope,
1156
+ worktreeScope: MilestoneScope,
1157
+ ): void {
1158
+ if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
1159
+ throw new Error(
1160
+ `reconcilePlanCheckboxesByScope: scope identity mismatch — ` +
1161
+ `rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
1162
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`,
1163
+ );
1164
+ }
1165
+ if (rootScope.milestoneId !== worktreeScope.milestoneId) {
1166
+ throw new Error(
1167
+ `reconcilePlanCheckboxesByScope: milestoneId mismatch — ` +
1168
+ `rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`,
1169
+ );
1170
+ }
1171
+ const projectRoot = rootScope.workspace.projectRoot;
1172
+ const wtPath = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
1173
+ const milestoneId = rootScope.milestoneId;
1174
+ reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId);
1175
+ }
1176
+
1177
+ /**
1178
+ * @deprecated Use reconcilePlanCheckboxesByScope instead.
1179
+ * TODO(C-future): remove once all callers migrated.
1167
1180
  */
1168
1181
  function reconcilePlanCheckboxes(
1169
1182
  projectRoot: string,
@@ -1341,10 +1354,10 @@ export function createAutoWorktree(
1341
1354
 
1342
1355
  try {
1343
1356
  process.chdir(info.path);
1344
- originalBase = basePath;
1357
+ setActiveWorkspace(createWorkspace(basePath));
1345
1358
  } catch (err) {
1346
1359
  // If chdir fails, the worktree was created but we couldn't enter it.
1347
- // Don't store originalBase -- caller can retry or clean up.
1360
+ // Don't set activeWorkspace -- caller can retry or clean up.
1348
1361
  throw new GSDError(
1349
1362
  GSD_IO_ERROR,
1350
1363
  `Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`,
@@ -1425,48 +1438,87 @@ export function teardownAutoWorktree(
1425
1438
  const { preserveBranch = false } = opts;
1426
1439
  const previousCwd = process.cwd();
1427
1440
 
1441
+ // Wrap the entire teardown body in a single try/finally so activeWorkspace
1442
+ // is ALWAYS cleared — even if process.chdir throws (e.g. originalBasePath
1443
+ // was deleted before teardown ran). Previously the finally only covered
1444
+ // removeWorktree, leaving the registry stale on a chdir failure (H3 fix).
1428
1445
  try {
1429
- process.chdir(originalBasePath);
1430
- originalBase = null;
1431
- } catch (err) {
1432
- throw new GSDError(
1433
- GSD_IO_ERROR,
1434
- `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`,
1435
- );
1436
- }
1446
+ try {
1447
+ process.chdir(originalBasePath);
1448
+ } catch (err) {
1449
+ throw new GSDError(
1450
+ GSD_IO_ERROR,
1451
+ `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`,
1452
+ );
1453
+ }
1437
1454
 
1438
- nudgeGitBranchCache(previousCwd);
1439
- removeWorktree(originalBasePath, milestoneId, {
1440
- branch,
1441
- deleteBranch: !preserveBranch,
1442
- });
1455
+ // Mirror cleanup steps from mergeMilestoneToMain abort path:
1443
1456
 
1444
- // Verify cleanup succeeded warn if the worktree directory is still on disk.
1445
- // On Windows, bash-based cleanup can silently fail when paths contain
1446
- // backslashes (#1436), leaving ~1 GB+ orphaned directories.
1447
- const wtDir = worktreePath(originalBasePath, milestoneId);
1448
- if (existsSync(wtDir)) {
1449
- logWarning(
1450
- "reconcile",
1451
- `Worktree directory still exists after teardown: ${wtDir}. ` +
1452
- `This is likely an orphaned directory consuming disk space. ` +
1453
- `Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`,
1454
- { worktree: milestoneId },
1455
- );
1456
- // Attempt a direct filesystem removal as a fallback — but ONLY if the
1457
- // path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
1458
- if (isInsideWorktreesDir(originalBasePath, wtDir)) {
1457
+ // 1. Remove transient state files (STATE.md, auto.lock, {MID}-META.json).
1458
+ // Non-fatal must not block teardown.
1459
+ try {
1460
+ clearProjectRootStateFiles(originalBasePath, milestoneId);
1461
+ } catch (err) {
1462
+ logWarning("worktree", `clearProjectRootStateFiles failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
1463
+ }
1464
+
1465
+ // 2. Reconcile worktree-local gsd.db into project root DB if both exist.
1466
+ // Non-fatal handles legacy worktrees that have a local copy.
1467
+ if (isDbAvailable()) {
1459
1468
  try {
1460
- rmSync(wtDir, { recursive: true, force: true });
1469
+ const contract = resolveGsdPathContract(previousCwd, originalBasePath);
1470
+ const worktreeDbPath = join(contract.worktreeGsd ?? join(previousCwd, ".gsd"), "gsd.db");
1471
+ const mainDbPath = contract.projectDb;
1472
+ if (existsSync(worktreeDbPath) && !isSamePath(worktreeDbPath, mainDbPath)) {
1473
+ reconcileWorktreeDb(mainDbPath, worktreeDbPath);
1474
+ }
1461
1475
  } catch (err) {
1462
- // Non-fatal — the warning above tells the user how to clean up
1463
- logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
1476
+ /* non-fatal */
1477
+ logError("worktree", `DB reconciliation failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
1464
1478
  }
1465
- } else {
1466
- console.error(
1467
- `[GSD] REFUSING fallback rmSync — path is outside .gsd/worktrees/: ${wtDir}`,
1479
+ }
1480
+
1481
+ nudgeGitBranchCache(previousCwd);
1482
+
1483
+ // 3. Remove the worktree. Errors propagate naturally — the outer finally
1484
+ // ensures activeWorkspace is cleared regardless.
1485
+ removeWorktree(originalBasePath, milestoneId, {
1486
+ branch,
1487
+ deleteBranch: !preserveBranch,
1488
+ });
1489
+
1490
+ // Verify cleanup succeeded — warn if the worktree directory is still on disk.
1491
+ // On Windows, bash-based cleanup can silently fail when paths contain
1492
+ // backslashes (#1436), leaving ~1 GB+ orphaned directories.
1493
+ const wtDir = worktreePath(originalBasePath, milestoneId);
1494
+ if (existsSync(wtDir)) {
1495
+ logWarning(
1496
+ "reconcile",
1497
+ `Worktree directory still exists after teardown: ${wtDir}. ` +
1498
+ `This is likely an orphaned directory consuming disk space. ` +
1499
+ `Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`,
1500
+ { worktree: milestoneId },
1468
1501
  );
1502
+ // Attempt a direct filesystem removal as a fallback — but ONLY if the
1503
+ // path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
1504
+ if (isInsideWorktreesDir(originalBasePath, wtDir)) {
1505
+ try {
1506
+ rmSync(wtDir, { recursive: true, force: true });
1507
+ } catch (err) {
1508
+ // Non-fatal — the warning above tells the user how to clean up
1509
+ logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
1510
+ }
1511
+ } else {
1512
+ console.error(
1513
+ `[GSD] REFUSING fallback rmSync — path is outside .gsd/worktrees/: ${wtDir}`,
1514
+ );
1515
+ }
1469
1516
  }
1517
+ } finally {
1518
+ // Clear module state unconditionally — regardless of which step above
1519
+ // failed. A stale activeWorkspace causes getActiveAutoWorktreeContext()
1520
+ // to return wrong data for subsequent operations.
1521
+ setActiveWorkspace(null);
1470
1522
  }
1471
1523
  }
1472
1524
 
@@ -1479,8 +1531,9 @@ export function isInAutoWorktree(basePath: string): boolean {
1479
1531
  const targetPath = isGsdWorktreePath(basePath) ? basePath : process.cwd();
1480
1532
  if (!isGsdWorktreePath(targetPath)) return false;
1481
1533
 
1482
- const projectRoot = resolveWorktreeProjectRoot(basePath, originalBase);
1483
- const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, originalBase);
1534
+ const storedBase = getAutoWorktreeOriginalBase();
1535
+ const projectRoot = resolveWorktreeProjectRoot(basePath, storedBase);
1536
+ const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, storedBase);
1484
1537
  if (
1485
1538
  normalizeWorktreePathForCompare(projectRoot) !==
1486
1539
  normalizeWorktreePathForCompare(targetProjectRoot)
@@ -1576,7 +1629,7 @@ export function enterAutoWorktree(
1576
1629
 
1577
1630
  try {
1578
1631
  process.chdir(p);
1579
- originalBase = basePath;
1632
+ setActiveWorkspace(createWorkspace(basePath));
1580
1633
  } catch (err) {
1581
1634
  throw new GSDError(
1582
1635
  GSD_IO_ERROR,
@@ -1593,11 +1646,11 @@ export function enterAutoWorktree(
1593
1646
  * Returns null if not currently in an auto-worktree.
1594
1647
  */
1595
1648
  export function getAutoWorktreeOriginalBase(): string | null {
1596
- return originalBase;
1649
+ return getActiveWorkspace()?.projectRoot ?? null;
1597
1650
  }
1598
1651
 
1599
1652
  export function _resetAutoWorktreeOriginalBaseForTests(): void {
1600
- originalBase = null;
1653
+ setActiveWorkspace(null);
1601
1654
  }
1602
1655
 
1603
1656
  export function getActiveAutoWorktreeContext(): {
@@ -1605,7 +1658,9 @@ export function getActiveAutoWorktreeContext(): {
1605
1658
  worktreeName: string;
1606
1659
  branch: string;
1607
1660
  } | null {
1608
- if (!originalBase) return null;
1661
+ const ws = getActiveWorkspace();
1662
+ if (!ws) return null;
1663
+ const originalBase = ws.projectRoot;
1609
1664
  const cwd = process.cwd();
1610
1665
  if (!isGsdWorktreePath(cwd)) return null;
1611
1666
  const cwdProjectRoot = resolveWorktreeProjectRoot(cwd, originalBase);
@@ -1686,11 +1741,11 @@ export function mergeMilestoneToMain(
1686
1741
  // integration branch captures dirty files from OTHER milestones under a
1687
1742
  // misleading commit message, contaminating the main branch (#2929).
1688
1743
  //
1689
- // When originalBase is null (branch mode, no worktree), autoCommitDirtyState
1744
+ // When activeWorkspace is null (branch mode, no worktree), autoCommitDirtyState
1690
1745
  // runs unconditionally — the caller is responsible for cwd placement.
1691
1746
  {
1692
1747
  let shouldAutoCommit = true;
1693
- if (originalBase !== null) {
1748
+ if (getActiveWorkspace() !== null) {
1694
1749
  try {
1695
1750
  const currentBranch = nativeGetCurrentBranch(worktreeCwd);
1696
1751
  shouldAutoCommit = currentBranch === milestoneBranch;
@@ -1710,9 +1765,10 @@ export function mergeMilestoneToMain(
1710
1765
  // database (#2823).
1711
1766
  if (isDbAvailable()) {
1712
1767
  try {
1713
- const worktreeDbPath = join(worktreeCwd, ".gsd", "gsd.db");
1714
- const mainDbPath = join(originalBasePath_, ".gsd", "gsd.db");
1715
- if (!isSamePath(worktreeDbPath, mainDbPath)) {
1768
+ const contract = resolveGsdPathContract(worktreeCwd, originalBasePath_);
1769
+ const worktreeDbPath = join(contract.worktreeGsd ?? join(worktreeCwd, ".gsd"), "gsd.db");
1770
+ const mainDbPath = contract.projectDb;
1771
+ if (existsSync(worktreeDbPath) && !isSamePath(worktreeDbPath, mainDbPath)) {
1716
1772
  reconcileWorktreeDb(mainDbPath, worktreeDbPath);
1717
1773
  }
1718
1774
  } catch (err) {
@@ -2248,27 +2304,6 @@ export function mergeMilestoneToMain(
2248
2304
  // 9a-iii. Restore sheltered queued milestone directories (#2505).
2249
2305
  restoreShelter();
2250
2306
 
2251
- // 9a-iv. Preserve current milestone artifacts that may be untracked in git.
2252
- // syncWorktreeStateBack intentionally skips the current milestone before the
2253
- // squash merge to avoid conflicting with the merge content. Once the squash
2254
- // commit is complete, copy those files back so summaries, validation, and
2255
- // task outputs survive worktree teardown in external/.gitignored .gsd setups.
2256
- try {
2257
- const { synced } = syncCurrentMilestoneStateAfterMerge(
2258
- originalBasePath_,
2259
- worktreeCwd,
2260
- milestoneId,
2261
- );
2262
- if (synced.length > 0) {
2263
- debugLog("mergeMilestoneToMain", {
2264
- phase: "current-milestone-sync-after-merge",
2265
- synced: synced.length,
2266
- });
2267
- }
2268
- } catch (err) {
2269
- logWarning("worktree", `current milestone sync after merge failed: ${err instanceof Error ? err.message : String(err)}`);
2270
- }
2271
-
2272
2307
  // 9b. Safety check (#1792): if nothing was committed, verify the milestone
2273
2308
  // work is already on the integration branch before allowing teardown.
2274
2309
  // Compare only non-.gsd/ paths — .gsd/ state files diverge normally and
@@ -2445,7 +2480,7 @@ export function mergeMilestoneToMain(
2445
2480
  }
2446
2481
 
2447
2482
  // 14. Clear module state
2448
- originalBase = null;
2483
+ setActiveWorkspace(null);
2449
2484
  nudgeGitBranchCache(previousCwd);
2450
2485
 
2451
2486
  // 15. Anchor cwd at the project root on success-return. Step 12 removed