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
@@ -12,7 +12,7 @@ import { reconcileWorktreeDb, isDbAvailable, getMilestone, getMilestoneSlices, c
12
12
  import { atomicWriteSync } from "./atomic-write.js";
13
13
  import { execFileSync } from "node:child_process";
14
14
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
15
- import { gsdRoot } from "./paths.js";
15
+ import { gsdRoot, resolveGsdPathContract } from "./paths.js";
16
16
  import { createWorktree, removeWorktree, resolveGitDir, worktreePath, isInsideWorktreesDir, } from "./worktree-manager.js";
17
17
  import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
18
18
  import { isGsdWorktreePath, normalizeWorktreePathForCompare, resolveWorktreeProjectRoot, } from "./worktree-root.js";
@@ -23,6 +23,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
23
23
  import { MILESTONE_ID_RE } from "./milestone-ids.js";
24
24
  import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchForceReset, nativeBranchExists, nativeDiffNumstat, nativeUpdateRef, nativeIsAncestor, nativeMergeAbort, } from "./native-git-bridge.js";
25
25
  import { gsdHome } from "./gsd-home.js";
26
+ import { createWorkspace } from "./workspace.js";
26
27
  const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
27
28
  const LEGACY_PROJECT_PREFERENCES_FILE = "preferences.md";
28
29
  const LEGACY_DEEP_SETUP_RUNTIME_UNIT_FILES = new Set([
@@ -34,9 +35,8 @@ const LEGACY_DEEP_SETUP_RUNTIME_UNIT_FILES = new Set([
34
35
  ]);
35
36
  // ─── Shared Constants & Helpers ─────────────────────────────────────────────
36
37
  /**
37
- * Root-level .gsd/ state files synced between worktree and project root.
38
- * Single source of truth used by syncGsdStateToWorktree, syncWorktreeStateBack,
39
- * and the dispatch-level sync functions.
38
+ * Root-level .gsd/ projections copied from project root into worktrees for
39
+ * compatibility. Project root remains the canonical state/projection root.
40
40
  */
41
41
  const ROOT_STATE_FILES = [
42
42
  "DECISIONS.md",
@@ -53,6 +53,10 @@ const ROOT_STATE_FILES = [
53
53
  // Back-sync (worktree → main) must NEVER overwrite the project root's copy
54
54
  // because the project root is authoritative for preferences (#2684).
55
55
  ];
56
+ const ROOT_DIAGNOSTIC_FILES = [
57
+ "completed-units.json",
58
+ "metrics.json",
59
+ ];
56
60
  /**
57
61
  * Pop a stash entry by tracking the unique marker embedded in its message so
58
62
  * concurrent stash operations against the same project root cannot cause us to
@@ -137,7 +141,7 @@ const VERDICT_RE = /verdict:\s*[\w-]+/i;
137
141
  * destination when the source copy contains a `verdict:` field.
138
142
  *
139
143
  * This is the targeted fix for the UAT stuck-loop (#2821): the main
140
- * safeCopyRecursive uses force:false to protect worktree-authoritative
144
+ * safeCopyRecursive uses force:false to protect worktree-local projection
141
145
  * files (#1886), but ASSESSMENT files written by run-uat must be
142
146
  * forward-synced when the project root has a verdict. Without this,
143
147
  * the worktree retains a stale FAIL or missing ASSESSMENT and
@@ -192,8 +196,14 @@ function forceOverwriteAssessmentsWithVerdict(srcMilestoneDir, dstMilestoneDir)
192
196
  }
193
197
  }
194
198
  // ─── Module State ──────────────────────────────────────────────────────────
195
- /** Original project root before chdir into auto-worktree. */
196
- let originalBase = null;
199
+ /** Active workspace registry replaces the legacy `originalBase` singleton. */
200
+ let activeWorkspace = null;
201
+ function setActiveWorkspace(ws) {
202
+ activeWorkspace = ws;
203
+ }
204
+ function getActiveWorkspace() {
205
+ return activeWorkspace;
206
+ }
197
207
  function clearProjectRootStateFiles(basePath, milestoneId) {
198
208
  const gsdDir = gsdRoot(basePath);
199
209
  const transientFiles = [
@@ -212,9 +222,9 @@ function clearProjectRootStateFiles(basePath, milestoneId) {
212
222
  }
213
223
  }
214
224
  }
215
- // Clean up entire synced milestone directory and runtime/units.
216
- // syncStateToProjectRoot() copies these into the project root during
217
- // execution. If they remain as untracked files when we attempt
225
+ // Clean up legacy synced milestone directories and runtime/units.
226
+ // Older versions copied these into the project root during execution.
227
+ // If they remain as untracked files when we attempt
218
228
  // `git merge --squash`, git rejects the merge with "local changes would
219
229
  // be overwritten", causing silent data loss (#1738).
220
230
  const syncedDirs = [
@@ -272,13 +282,41 @@ export const isSafeToAutoResolve = (filePath) => filePath.startsWith(".gsd/") ||
272
282
  * gsd.db in the worktree so it rebuilds from fresh disk state (#853).
273
283
  * Non-fatal — sync failure should never block dispatch.
274
284
  */
285
+ /**
286
+ * Scope-typed variant of syncProjectRootToWorktree.
287
+ *
288
+ * Takes an explicit (rootScope, worktreeScope) pair where rootScope is the
289
+ * project root and worktreeScope is the auto-worktree. Direction is encoded
290
+ * in argument order. Asserts both scopes belong to the same workspace identity
291
+ * to prevent silent mismatch bugs.
292
+ */
293
+ export function syncProjectRootToWorktreeByScope(rootScope, worktreeScope) {
294
+ if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
295
+ throw new Error(`syncProjectRootToWorktreeByScope: scope identity mismatch — ` +
296
+ `rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
297
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
298
+ }
299
+ if (rootScope.milestoneId !== worktreeScope.milestoneId) {
300
+ throw new Error(`syncProjectRootToWorktreeByScope: milestoneId mismatch — ` +
301
+ `rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`);
302
+ }
303
+ const projectRoot = rootScope.workspace.projectRoot;
304
+ const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
305
+ const milestoneId = rootScope.milestoneId;
306
+ syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId);
307
+ }
308
+ /**
309
+ * @deprecated Use syncProjectRootToWorktreeByScope instead.
310
+ * TODO(C-future): remove once all callers migrated.
311
+ */
275
312
  export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId) {
276
313
  if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
277
314
  return;
278
315
  if (!milestoneId)
279
316
  return;
280
- const prGsd = join(projectRoot, ".gsd");
281
- const wtGsd = join(worktreePath_, ".gsd");
317
+ const contract = resolveGsdPathContract(worktreePath_, projectRoot);
318
+ const prGsd = contract.projectGsd;
319
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath_, ".gsd");
282
320
  // When .gsd is a symlink to the same external directory in both locations,
283
321
  // cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
284
322
  // Compare realpaths and skip when they resolve to the same physical path (#2184).
@@ -286,12 +324,12 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
286
324
  return;
287
325
  // Copy milestone directory from project root to worktree — additive only.
288
326
  // force:false prevents cpSync from overwriting existing worktree files.
289
- // Without this, worktree-authoritative files (e.g. VALIDATION.md written
327
+ // Without this, worktree-local files (e.g. VALIDATION.md written
290
328
  // by validate-milestone) get clobbered by stale project root copies,
291
329
  // causing an infinite re-validation loop (#1886).
292
330
  safeCopyRecursive(join(prGsd, "milestones", milestoneId), join(wtGsd, "milestones", milestoneId), { force: false });
293
331
  // Force-sync ASSESSMENT files that have a verdict from project root (#2821).
294
- // The additive-only copy above preserves worktree-authoritative files, but
332
+ // The additive-only copy above preserves worktree-local files, but
295
333
  // ASSESSMENT files are special: after run-uat writes a verdict and post-unit
296
334
  // syncs it to the project root, the worktree may retain a stale copy (e.g.
297
335
  // verdict:fail while the project root has verdict:pass from a retry). On
@@ -303,11 +341,9 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
303
341
  // Project root is authoritative for completion state after crash recovery;
304
342
  // without this, the worktree re-dispatches already-completed units (#1886).
305
343
  safeCopy(join(prGsd, "completed-units.json"), join(wtGsd, "completed-units.json"), { force: true });
306
- // Delete worktree gsd.db ONLY if it is empty (0 bytes).
307
- // An empty DB is stale/corrupt and should be rebuilt (#853).
308
- // A non-empty DB was populated by gsd-migrate on respawn and must be
309
- // preserved — deleting it truncates the file to 0 bytes when
310
- // openDatabase re-creates it, causing "no such table" failures (#2815).
344
+ // Delete a legacy worktree-local gsd.db ONLY if it is empty (0 bytes).
345
+ // Runtime opens contract.projectDb; this cleanup only removes corrupt
346
+ // pre-upgrade local DB projections.
311
347
  try {
312
348
  const wtDb = join(wtGsd, "gsd.db");
313
349
  let deleteSidecars = false;
@@ -342,33 +378,57 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
342
378
  }
343
379
  }
344
380
  /**
345
- * Sync dispatch-critical .gsd/ state files from worktree to project root.
381
+ * Scope-typed variant of syncStateToProjectRoot.
382
+ *
383
+ * Takes an explicit (worktreeScope, rootScope) pair. Direction is encoded in
384
+ * argument order (worktree → root). Asserts both scopes belong to the same
385
+ * workspace identity to prevent silent mismatch bugs.
386
+ */
387
+ export function syncStateToProjectRootByScope(worktreeScope, rootScope) {
388
+ if (worktreeScope.workspace.identityKey !== rootScope.workspace.identityKey) {
389
+ throw new Error(`syncStateToProjectRootByScope: scope identity mismatch — ` +
390
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}" ` +
391
+ `rootScope.identityKey="${rootScope.workspace.identityKey}"`);
392
+ }
393
+ if (worktreeScope.milestoneId !== rootScope.milestoneId) {
394
+ throw new Error(`syncStateToProjectRootByScope: milestoneId mismatch — ` +
395
+ `worktreeScope.milestoneId="${worktreeScope.milestoneId}" rootScope.milestoneId="${rootScope.milestoneId}"`);
396
+ }
397
+ const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
398
+ const projectRoot = rootScope.workspace.projectRoot;
399
+ const milestoneId = worktreeScope.milestoneId;
400
+ syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId);
401
+ }
402
+ /**
403
+ * Sync worktree diagnostics from worktree to project root.
346
404
  * Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
347
- * Copies: STATE.md + active milestone directory (roadmap, slice plans, task summaries).
405
+ * DB/project-root state remains authoritative; markdown projections are not
406
+ * copied from the worktree back to the project root.
348
407
  * Non-fatal — sync failure should never block dispatch.
408
+ * @deprecated Use syncStateToProjectRootByScope instead.
409
+ * TODO(C-future): remove once all callers migrated.
349
410
  */
350
411
  export function syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId) {
351
412
  if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
352
413
  return;
353
414
  if (!milestoneId)
354
415
  return;
355
- const wtGsd = join(worktreePath_, ".gsd");
356
- const prGsd = join(projectRoot, ".gsd");
416
+ const contract = resolveGsdPathContract(worktreePath_, projectRoot);
417
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath_, ".gsd");
418
+ const prGsd = contract.projectGsd;
357
419
  // When .gsd is a symlink to the same external directory in both locations,
358
420
  // cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
359
421
  // Compare realpaths and skip when they resolve to the same physical path (#2184).
360
422
  if (isSamePath(wtGsd, prGsd))
361
423
  return;
362
- // 1. STATE.md the quick-glance status used by initial deriveState()
363
- safeCopy(join(wtGsd, "STATE.md"), join(prGsd, "STATE.md"), { force: true });
364
- // 2. Milestone directory — ROADMAP, slice PLANs, task summaries
365
- // Copy the entire milestone .gsd subtree so deriveState reads current checkboxes
366
- safeCopyRecursive(join(wtGsd, "milestones", milestoneId), join(prGsd, "milestones", milestoneId), { force: true });
367
- // 3. metrics.json — session cost/token tracking (#2313).
424
+ // metrics.jsonsession cost/token tracking (#2313).
368
425
  // Without this, metrics accumulated in the worktree are invisible from the
369
426
  // project root and never appear in the dashboard or skill-health reports.
370
427
  safeCopy(join(wtGsd, "metrics.json"), join(prGsd, "metrics.json"), { force: true });
371
- // 4. Runtime records unit dispatch state used by selfHealRuntimeRecords().
428
+ // completed-units.jsonruntime completion diagnostics used to avoid
429
+ // re-dispatching work already completed in an isolated worktree.
430
+ safeCopy(join(wtGsd, "completed-units.json"), join(prGsd, "completed-units.json"), { force: true });
431
+ // Runtime records — unit dispatch diagnostics used by selfHealRuntimeRecords().
372
432
  // Without this, a crash during a unit leaves the runtime record only in the
373
433
  // worktree. If the next session resolves basePath before worktree re-entry,
374
434
  // selfHeal can't find or clear the stale record (#769).
@@ -518,6 +578,24 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
518
578
  return cleaned;
519
579
  }
520
580
  // ─── Worktree ↔ Main Repo Sync (#1311) ──────────────────────────────────────
581
+ /**
582
+ * Scope-typed variant of syncGsdStateToWorktree.
583
+ *
584
+ * Takes an explicit (rootScope, worktreeScope) pair. Note: milestoneId is not
585
+ * used by syncGsdStateToWorktree — this variant only requires workspace
586
+ * identity. Asserts both scopes belong to the same workspace identity to
587
+ * prevent silent mismatch bugs.
588
+ */
589
+ export function syncGsdStateToWorktreeByScope(rootScope, worktreeScope) {
590
+ if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
591
+ throw new Error(`syncGsdStateToWorktreeByScope: scope identity mismatch — ` +
592
+ `rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
593
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
594
+ }
595
+ const mainBasePath = rootScope.workspace.projectRoot;
596
+ const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
597
+ return syncGsdStateToWorktree(mainBasePath, worktreePath_);
598
+ }
521
599
  /**
522
600
  * Sync .gsd/ state from the main repo into the worktree.
523
601
  *
@@ -529,12 +607,16 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
529
607
  * missing milestones, CONTEXT, ROADMAP, DECISIONS, REQUIREMENTS, and
530
608
  * PROJECT files from the main repo's .gsd/ into the worktree's .gsd/.
531
609
  *
532
- * Only adds missing content — never overwrites existing files in the worktree
533
- * (the worktree's execution state is authoritative for in-progress work).
610
+ * Only adds missing content — never overwrites existing files in the worktree.
611
+ * Worktree files are compatibility projections; DB/project root remains
612
+ * authoritative for runtime state.
613
+ * @deprecated Use syncGsdStateToWorktreeByScope instead.
614
+ * TODO(C-future): remove once all callers migrated.
534
615
  */
535
616
  export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
536
- const mainGsd = gsdRoot(mainBasePath);
537
- const wtGsd = gsdRoot(worktreePath_);
617
+ const contract = resolveGsdPathContract(worktreePath_, mainBasePath);
618
+ const mainGsd = contract.projectGsd;
619
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath_, ".gsd");
538
620
  const synced = [];
539
621
  // If both resolve to the same directory (symlink), no sync needed
540
622
  if (isSamePath(mainGsd, wtGsd))
@@ -678,28 +760,22 @@ export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
678
760
  return { synced };
679
761
  }
680
762
  /**
681
- * Sync milestone artifacts from worktree back to the main external state directory.
682
- * Called before milestone merge to ensure completion artifacts (SUMMARY, VALIDATION,
683
- * updated ROADMAP) are visible from the project root (#1412).
763
+ * Sync compatibility artifacts from worktree back to the main external state
764
+ * directory. Canonical workflow state lives in the project DB; worktree .gsd
765
+ * content is legacy projection/diagnostic data only.
684
766
  *
685
767
  * Syncs:
686
- * 1. Root-level .gsd/ files (REQUIREMENTS, PROJECT, DECISIONS, KNOWLEDGE,
687
- * OVERRIDES) the worktree's versions overwrite main's because the
688
- * worktree is the authoritative execution context.
689
- * 2. ALL milestone directories found in the worktree — not just the
690
- * current milestoneId. The complete-milestone unit may create artifacts
691
- * for the *next* milestone (CONTEXT, ROADMAP, new requirements) which
692
- * must survive worktree teardown.
768
+ * 1. Legacy worktree DBs are reconciled into the canonical project DB.
769
+ * 2. Runtime diagnostic files may be copied for operator visibility.
693
770
  *
694
- * History: Originally only synced milestones/<milestoneId>/ and assumed
695
- * root-level files would be carried by the squash merge. In practice,
696
- * .gsd/ files are often untracked (gitignored or never committed), so the
697
- * squash merge carries nothing. This caused next-milestone artifacts and
698
- * updated REQUIREMENTS/PROJECT to be silently lost on teardown.
771
+ * Markdown milestone directories are projections and are not copied from
772
+ * worktrees into the project root. Current workflow state must arrive through
773
+ * the shared project DB or the pre-upgrade DB reconciliation path above.
699
774
  */
700
775
  export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
701
- const mainGsd = gsdRoot(mainBasePath);
702
- const wtGsd = gsdRoot(worktreePath);
776
+ const contract = resolveGsdPathContract(worktreePath, mainBasePath);
777
+ const mainGsd = contract.projectGsd;
778
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath, ".gsd");
703
779
  const synced = [];
704
780
  // If both resolve to the same directory (symlink), no sync needed
705
781
  if (isSamePath(mainGsd, wtGsd))
@@ -712,7 +788,7 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
712
788
  // files. This handles in-flight worktrees that were created before the
713
789
  // upgrade to shared WAL mode.
714
790
  const wtLocalDb = join(wtGsd, "gsd.db");
715
- const mainDb = join(mainGsd, "gsd.db");
791
+ const mainDb = contract.projectDb;
716
792
  if (existsSync(wtLocalDb) && existsSync(mainDb)) {
717
793
  try {
718
794
  reconcileWorktreeDb(mainDb, wtLocalDb);
@@ -723,13 +799,10 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
723
799
  logError("worktree", `DB reconciliation failed: ${err instanceof Error ? err.message : String(err)}`);
724
800
  }
725
801
  }
726
- // ── 1. Sync root-level .gsd/ files back ──────────────────────────────
727
- // The worktree is authoritative — complete-milestone updates REQUIREMENTS,
728
- // PROJECT, etc. These must overwrite main's copies so they survive teardown.
729
- // Also includes QUEUE.md, completed-units.json, and metrics.json which are
730
- // written during milestone closeout and lost on teardown without explicit sync
731
- // (#1787, #2313).
732
- for (const f of ROOT_STATE_FILES) {
802
+ // ── 1. Sync root-level diagnostic files back ─────────────────────────
803
+ // Markdown/JSON state projections remain project-root/DB authoritative.
804
+ // These diagnostic files are copied for observability only.
805
+ for (const f of ROOT_DIAGNOSTIC_FILES) {
733
806
  const src = join(wtGsd, f);
734
807
  const dst = join(mainGsd, f);
735
808
  if (existsSync(src)) {
@@ -743,103 +816,8 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
743
816
  }
744
817
  }
745
818
  }
746
- // ── 2. Sync ALL milestone directories ────────────────────────────────
747
- // The complete-milestone unit may create next-milestone artifacts (e.g.
748
- // M007 setup while closing M006). We must sync every milestone directory
749
- // in the worktree, not just the current one.
750
- const wtMilestonesDir = join(wtGsd, "milestones");
751
- if (!existsSync(wtMilestonesDir))
752
- return { synced };
753
- try {
754
- const wtMilestones = readdirSync(wtMilestonesDir, { withFileTypes: true })
755
- .filter((d) => d.isDirectory())
756
- .map((d) => d.name);
757
- for (const mid of wtMilestones) {
758
- // Skip the current milestone being merged — its files are already in the
759
- // milestone branch and would conflict with the squash merge (#3641).
760
- if (mid === milestoneId)
761
- continue;
762
- syncMilestoneDir(wtGsd, mainGsd, mid, synced);
763
- }
764
- }
765
- catch (err) {
766
- /* non-fatal */
767
- logWarning("worktree", `milestone sync-back failed: ${err instanceof Error ? err.message : String(err)}`);
768
- }
769
819
  return { synced };
770
820
  }
771
- function syncCurrentMilestoneStateAfterMerge(mainBasePath, worktreePath, milestoneId) {
772
- const mainGsd = gsdRoot(mainBasePath);
773
- const wtGsd = gsdRoot(worktreePath);
774
- const synced = [];
775
- if (isSamePath(mainGsd, wtGsd))
776
- return { synced };
777
- if (!existsSync(wtGsd) || !existsSync(mainGsd))
778
- return { synced };
779
- syncMilestoneDir(wtGsd, mainGsd, milestoneId, synced);
780
- return { synced };
781
- }
782
- /**
783
- * Sync a single milestone directory from worktree to main.
784
- * Copies milestone-level .md files, slice-level files, and task summaries.
785
- */
786
- /** Copy matching files from srcDir to dstDir (non-fatal per file). */
787
- function syncDirFiles(srcDir, dstDir, filter, synced, prefix) {
788
- try {
789
- for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
790
- if (!entry.isFile() || !filter(entry.name))
791
- continue;
792
- try {
793
- cpSync(join(srcDir, entry.name), join(dstDir, entry.name), { force: true });
794
- synced.push(`${prefix}${entry.name}`);
795
- }
796
- catch (err) {
797
- /* non-fatal */
798
- logWarning("worktree", `file copy failed (${prefix}${entry.name}): ${err instanceof Error ? err.message : String(err)}`);
799
- }
800
- }
801
- }
802
- catch (err) {
803
- /* non-fatal — srcDir may not be readable */
804
- logWarning("worktree", `directory read failed: ${err instanceof Error ? err.message : String(err)}`);
805
- }
806
- }
807
- function syncMilestoneDir(wtGsd, mainGsd, mid, synced) {
808
- const wtMilestoneDir = join(wtGsd, "milestones", mid);
809
- const mainMilestoneDir = join(mainGsd, "milestones", mid);
810
- if (!existsSync(wtMilestoneDir))
811
- return;
812
- mkdirSync(mainMilestoneDir, { recursive: true });
813
- const isMd = (name) => name.endsWith(".md");
814
- // Sync milestone-level files (SUMMARY, VALIDATION, ROADMAP, CONTEXT)
815
- syncDirFiles(wtMilestoneDir, mainMilestoneDir, isMd, synced, `milestones/${mid}/`);
816
- // Sync slice-level files (summaries, UATs) and task summaries (#1678)
817
- const wtSlicesDir = join(wtMilestoneDir, "slices");
818
- const mainSlicesDir = join(mainMilestoneDir, "slices");
819
- if (!existsSync(wtSlicesDir))
820
- return;
821
- try {
822
- for (const sliceEntry of readdirSync(wtSlicesDir, { withFileTypes: true })) {
823
- if (!sliceEntry.isDirectory())
824
- continue;
825
- const sid = sliceEntry.name;
826
- const wtSliceDir = join(wtSlicesDir, sid);
827
- const mainSliceDir = join(mainSlicesDir, sid);
828
- mkdirSync(mainSliceDir, { recursive: true });
829
- syncDirFiles(wtSliceDir, mainSliceDir, isMd, synced, `milestones/${mid}/slices/${sid}/`);
830
- const wtTasksDir = join(wtSliceDir, "tasks");
831
- const mainTasksDir = join(mainSliceDir, "tasks");
832
- if (existsSync(wtTasksDir)) {
833
- mkdirSync(mainTasksDir, { recursive: true });
834
- syncDirFiles(wtTasksDir, mainTasksDir, isMd, synced, `milestones/${mid}/slices/${sid}/tasks/`);
835
- }
836
- }
837
- }
838
- catch (err) {
839
- /* non-fatal */
840
- logWarning("worktree", `milestone slice sync failed (${mid}): ${err instanceof Error ? err.message : String(err)}`);
841
- }
842
- }
843
821
  // ─── Worktree Post-Create Hook (#597) ────────────────────────────────────────
844
822
  /**
845
823
  * Run the user-configured post-create hook script after worktree creation.
@@ -1002,10 +980,35 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
1002
980
  * directory at the project root and apply any [x] checkbox states that are
1003
981
  * ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
1004
982
  *
1005
- * This is safe because syncStateToProjectRoot() is the authoritative source
1006
- * of post-task state at the project root it writes the same [x] the LLM
1007
- * produced, then the auto-commit follows. If the commit never happened, the
1008
- * filesystem copy is still valid and correct.
983
+ * This is forward-only compatibility for legacy projection copies. The DB
984
+ * remains authoritative; this never downgrades checked boxes in a local
985
+ * worktree projection.
986
+ */
987
+ /**
988
+ * Scope-typed variant of reconcilePlanCheckboxes.
989
+ *
990
+ * Takes an explicit (rootScope, worktreeScope) pair. milestoneId is taken
991
+ * from rootScope. Asserts both scopes belong to the same workspace identity
992
+ * to prevent silent mismatch bugs.
993
+ */
994
+ export function reconcilePlanCheckboxesByScope(rootScope, worktreeScope) {
995
+ if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
996
+ throw new Error(`reconcilePlanCheckboxesByScope: scope identity mismatch — ` +
997
+ `rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
998
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
999
+ }
1000
+ if (rootScope.milestoneId !== worktreeScope.milestoneId) {
1001
+ throw new Error(`reconcilePlanCheckboxesByScope: milestoneId mismatch — ` +
1002
+ `rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`);
1003
+ }
1004
+ const projectRoot = rootScope.workspace.projectRoot;
1005
+ const wtPath = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
1006
+ const milestoneId = rootScope.milestoneId;
1007
+ reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId);
1008
+ }
1009
+ /**
1010
+ * @deprecated Use reconcilePlanCheckboxesByScope instead.
1011
+ * TODO(C-future): remove once all callers migrated.
1009
1012
  */
1010
1013
  function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
1011
1014
  const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
@@ -1160,11 +1163,11 @@ export function createAutoWorktree(basePath, milestoneId) {
1160
1163
  const previousCwd = process.cwd();
1161
1164
  try {
1162
1165
  process.chdir(info.path);
1163
- originalBase = basePath;
1166
+ setActiveWorkspace(createWorkspace(basePath));
1164
1167
  }
1165
1168
  catch (err) {
1166
1169
  // If chdir fails, the worktree was created but we couldn't enter it.
1167
- // Don't store originalBase -- caller can retry or clean up.
1170
+ // Don't set activeWorkspace -- caller can retry or clean up.
1168
1171
  throw new GSDError(GSD_IO_ERROR, `Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`);
1169
1172
  }
1170
1173
  nudgeGitBranchCache(previousCwd);
@@ -1224,41 +1227,79 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
1224
1227
  const branch = autoWorktreeBranch(milestoneId);
1225
1228
  const { preserveBranch = false } = opts;
1226
1229
  const previousCwd = process.cwd();
1230
+ // Wrap the entire teardown body in a single try/finally so activeWorkspace
1231
+ // is ALWAYS cleared — even if process.chdir throws (e.g. originalBasePath
1232
+ // was deleted before teardown ran). Previously the finally only covered
1233
+ // removeWorktree, leaving the registry stale on a chdir failure (H3 fix).
1227
1234
  try {
1228
- process.chdir(originalBasePath);
1229
- originalBase = null;
1230
- }
1231
- catch (err) {
1232
- throw new GSDError(GSD_IO_ERROR, `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`);
1233
- }
1234
- nudgeGitBranchCache(previousCwd);
1235
- removeWorktree(originalBasePath, milestoneId, {
1236
- branch,
1237
- deleteBranch: !preserveBranch,
1238
- });
1239
- // Verify cleanup succeeded — warn if the worktree directory is still on disk.
1240
- // On Windows, bash-based cleanup can silently fail when paths contain
1241
- // backslashes (#1436), leaving ~1 GB+ orphaned directories.
1242
- const wtDir = worktreePath(originalBasePath, milestoneId);
1243
- if (existsSync(wtDir)) {
1244
- logWarning("reconcile", `Worktree directory still exists after teardown: ${wtDir}. ` +
1245
- `This is likely an orphaned directory consuming disk space. ` +
1246
- `Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`, { worktree: milestoneId });
1247
- // Attempt a direct filesystem removal as a fallback — but ONLY if the
1248
- // path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
1249
- if (isInsideWorktreesDir(originalBasePath, wtDir)) {
1235
+ try {
1236
+ process.chdir(originalBasePath);
1237
+ }
1238
+ catch (err) {
1239
+ throw new GSDError(GSD_IO_ERROR, `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`);
1240
+ }
1241
+ // Mirror cleanup steps from mergeMilestoneToMain abort path:
1242
+ // 1. Remove transient state files (STATE.md, auto.lock, {MID}-META.json).
1243
+ // Non-fatal — must not block teardown.
1244
+ try {
1245
+ clearProjectRootStateFiles(originalBasePath, milestoneId);
1246
+ }
1247
+ catch (err) {
1248
+ logWarning("worktree", `clearProjectRootStateFiles failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
1249
+ }
1250
+ // 2. Reconcile worktree-local gsd.db into project root DB if both exist.
1251
+ // Non-fatal handles legacy worktrees that have a local copy.
1252
+ if (isDbAvailable()) {
1250
1253
  try {
1251
- rmSync(wtDir, { recursive: true, force: true });
1254
+ const contract = resolveGsdPathContract(previousCwd, originalBasePath);
1255
+ const worktreeDbPath = join(contract.worktreeGsd ?? join(previousCwd, ".gsd"), "gsd.db");
1256
+ const mainDbPath = contract.projectDb;
1257
+ if (existsSync(worktreeDbPath) && !isSamePath(worktreeDbPath, mainDbPath)) {
1258
+ reconcileWorktreeDb(mainDbPath, worktreeDbPath);
1259
+ }
1252
1260
  }
1253
1261
  catch (err) {
1254
- // Non-fatal — the warning above tells the user how to clean up
1255
- logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
1262
+ /* non-fatal */
1263
+ logError("worktree", `DB reconciliation failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
1256
1264
  }
1257
1265
  }
1258
- else {
1259
- console.error(`[GSD] REFUSING fallback rmSync path is outside .gsd/worktrees/: ${wtDir}`);
1266
+ nudgeGitBranchCache(previousCwd);
1267
+ // 3. Remove the worktree. Errors propagate naturally the outer finally
1268
+ // ensures activeWorkspace is cleared regardless.
1269
+ removeWorktree(originalBasePath, milestoneId, {
1270
+ branch,
1271
+ deleteBranch: !preserveBranch,
1272
+ });
1273
+ // Verify cleanup succeeded — warn if the worktree directory is still on disk.
1274
+ // On Windows, bash-based cleanup can silently fail when paths contain
1275
+ // backslashes (#1436), leaving ~1 GB+ orphaned directories.
1276
+ const wtDir = worktreePath(originalBasePath, milestoneId);
1277
+ if (existsSync(wtDir)) {
1278
+ logWarning("reconcile", `Worktree directory still exists after teardown: ${wtDir}. ` +
1279
+ `This is likely an orphaned directory consuming disk space. ` +
1280
+ `Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`, { worktree: milestoneId });
1281
+ // Attempt a direct filesystem removal as a fallback — but ONLY if the
1282
+ // path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
1283
+ if (isInsideWorktreesDir(originalBasePath, wtDir)) {
1284
+ try {
1285
+ rmSync(wtDir, { recursive: true, force: true });
1286
+ }
1287
+ catch (err) {
1288
+ // Non-fatal — the warning above tells the user how to clean up
1289
+ logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
1290
+ }
1291
+ }
1292
+ else {
1293
+ console.error(`[GSD] REFUSING fallback rmSync — path is outside .gsd/worktrees/: ${wtDir}`);
1294
+ }
1260
1295
  }
1261
1296
  }
1297
+ finally {
1298
+ // Clear module state unconditionally — regardless of which step above
1299
+ // failed. A stale activeWorkspace causes getActiveAutoWorktreeContext()
1300
+ // to return wrong data for subsequent operations.
1301
+ setActiveWorkspace(null);
1302
+ }
1262
1303
  }
1263
1304
  /**
1264
1305
  * Detect if the process is currently inside an auto-worktree.
@@ -1269,8 +1310,9 @@ export function isInAutoWorktree(basePath) {
1269
1310
  const targetPath = isGsdWorktreePath(basePath) ? basePath : process.cwd();
1270
1311
  if (!isGsdWorktreePath(targetPath))
1271
1312
  return false;
1272
- const projectRoot = resolveWorktreeProjectRoot(basePath, originalBase);
1273
- const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, originalBase);
1313
+ const storedBase = getAutoWorktreeOriginalBase();
1314
+ const projectRoot = resolveWorktreeProjectRoot(basePath, storedBase);
1315
+ const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, storedBase);
1274
1316
  if (normalizeWorktreePathForCompare(projectRoot) !==
1275
1317
  normalizeWorktreePathForCompare(targetProjectRoot)) {
1276
1318
  return false;
@@ -1343,7 +1385,7 @@ export function enterAutoWorktree(basePath, milestoneId) {
1343
1385
  const previousCwd = process.cwd();
1344
1386
  try {
1345
1387
  process.chdir(p);
1346
- originalBase = basePath;
1388
+ setActiveWorkspace(createWorkspace(basePath));
1347
1389
  }
1348
1390
  catch (err) {
1349
1391
  throw new GSDError(GSD_IO_ERROR, `Failed to enter auto-worktree at ${p}: ${err instanceof Error ? err.message : String(err)}`);
@@ -1356,14 +1398,16 @@ export function enterAutoWorktree(basePath, milestoneId) {
1356
1398
  * Returns null if not currently in an auto-worktree.
1357
1399
  */
1358
1400
  export function getAutoWorktreeOriginalBase() {
1359
- return originalBase;
1401
+ return getActiveWorkspace()?.projectRoot ?? null;
1360
1402
  }
1361
1403
  export function _resetAutoWorktreeOriginalBaseForTests() {
1362
- originalBase = null;
1404
+ setActiveWorkspace(null);
1363
1405
  }
1364
1406
  export function getActiveAutoWorktreeContext() {
1365
- if (!originalBase)
1407
+ const ws = getActiveWorkspace();
1408
+ if (!ws)
1366
1409
  return null;
1410
+ const originalBase = ws.projectRoot;
1367
1411
  const cwd = process.cwd();
1368
1412
  if (!isGsdWorktreePath(cwd))
1369
1413
  return null;
@@ -1433,11 +1477,11 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1433
1477
  // integration branch captures dirty files from OTHER milestones under a
1434
1478
  // misleading commit message, contaminating the main branch (#2929).
1435
1479
  //
1436
- // When originalBase is null (branch mode, no worktree), autoCommitDirtyState
1480
+ // When activeWorkspace is null (branch mode, no worktree), autoCommitDirtyState
1437
1481
  // runs unconditionally — the caller is responsible for cwd placement.
1438
1482
  {
1439
1483
  let shouldAutoCommit = true;
1440
- if (originalBase !== null) {
1484
+ if (getActiveWorkspace() !== null) {
1441
1485
  try {
1442
1486
  const currentBranch = nativeGetCurrentBranch(worktreeCwd);
1443
1487
  shouldAutoCommit = currentBranch === milestoneBranch;
@@ -1457,9 +1501,10 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1457
1501
  // database (#2823).
1458
1502
  if (isDbAvailable()) {
1459
1503
  try {
1460
- const worktreeDbPath = join(worktreeCwd, ".gsd", "gsd.db");
1461
- const mainDbPath = join(originalBasePath_, ".gsd", "gsd.db");
1462
- if (!isSamePath(worktreeDbPath, mainDbPath)) {
1504
+ const contract = resolveGsdPathContract(worktreeCwd, originalBasePath_);
1505
+ const worktreeDbPath = join(contract.worktreeGsd ?? join(worktreeCwd, ".gsd"), "gsd.db");
1506
+ const mainDbPath = contract.projectDb;
1507
+ if (existsSync(worktreeDbPath) && !isSamePath(worktreeDbPath, mainDbPath)) {
1463
1508
  reconcileWorktreeDb(mainDbPath, worktreeDbPath);
1464
1509
  }
1465
1510
  }
@@ -1964,23 +2009,6 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1964
2009
  }
1965
2010
  // 9a-iii. Restore sheltered queued milestone directories (#2505).
1966
2011
  restoreShelter();
1967
- // 9a-iv. Preserve current milestone artifacts that may be untracked in git.
1968
- // syncWorktreeStateBack intentionally skips the current milestone before the
1969
- // squash merge to avoid conflicting with the merge content. Once the squash
1970
- // commit is complete, copy those files back so summaries, validation, and
1971
- // task outputs survive worktree teardown in external/.gitignored .gsd setups.
1972
- try {
1973
- const { synced } = syncCurrentMilestoneStateAfterMerge(originalBasePath_, worktreeCwd, milestoneId);
1974
- if (synced.length > 0) {
1975
- debugLog("mergeMilestoneToMain", {
1976
- phase: "current-milestone-sync-after-merge",
1977
- synced: synced.length,
1978
- });
1979
- }
1980
- }
1981
- catch (err) {
1982
- logWarning("worktree", `current milestone sync after merge failed: ${err instanceof Error ? err.message : String(err)}`);
1983
- }
1984
2012
  // 9b. Safety check (#1792): if nothing was committed, verify the milestone
1985
2013
  // work is already on the integration branch before allowing teardown.
1986
2014
  // Compare only non-.gsd/ paths — .gsd/ state files diverge normally and
@@ -2137,7 +2165,7 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
2137
2165
  logWarning("worktree", `git branch-delete failed: ${err instanceof Error ? err.message : String(err)}`);
2138
2166
  }
2139
2167
  // 14. Clear module state
2140
- originalBase = null;
2168
+ setActiveWorkspace(null);
2141
2169
  nudgeGitBranchCache(previousCwd);
2142
2170
  // 15. Anchor cwd at the project root on success-return. Step 12 removed
2143
2171
  // the worktree dir; if cwd was inside it, every subsequent process.cwd()