gsd-pi 2.78.1-dev.e9d88a536 → 2.78.1-dev.eccf86e27

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 (212) hide show
  1. package/README.md +5 -7
  2. package/dist/help-text.js +1 -1
  3. package/dist/resource-loader.js +6 -1
  4. package/dist/resources/.managed-resources-content-hash +1 -1
  5. package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
  6. package/dist/resources/extensions/gsd/auto/loop.js +235 -36
  7. package/dist/resources/extensions/gsd/auto/phases.js +14 -7
  8. package/dist/resources/extensions/gsd/auto/session.js +36 -0
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
  11. package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
  12. package/dist/resources/extensions/gsd/auto.js +139 -49
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
  16. package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
  17. package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
  18. package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
  19. package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
  20. package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
  21. package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
  22. package/dist/resources/extensions/gsd/db-writer.js +96 -16
  23. package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
  24. package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  25. package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
  26. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
  27. package/dist/resources/extensions/gsd/doctor.js +12 -2
  28. package/dist/resources/extensions/gsd/gsd-db.js +355 -3
  29. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  30. package/dist/resources/extensions/gsd/guided-flow.js +116 -26
  31. package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
  32. package/dist/resources/extensions/gsd/metrics.js +287 -1
  33. package/dist/resources/extensions/gsd/paths.js +79 -8
  34. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  35. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  36. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  37. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  38. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  39. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  40. package/dist/resources/extensions/gsd/state.js +21 -6
  41. package/dist/resources/extensions/gsd/templates/project.md +10 -0
  42. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  43. package/dist/resources/extensions/gsd/workspace.js +59 -0
  44. package/dist/resources/extensions/gsd/worktree-resolver.js +79 -2
  45. package/dist/resources/extensions/gsd/write-intercept.js +3 -3
  46. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  47. package/dist/web/standalone/.next/BUILD_ID +1 -1
  48. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  49. package/dist/web/standalone/.next/build-manifest.json +2 -2
  50. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  51. package/dist/web/standalone/.next/required-server-files.json +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.html +1 -1
  69. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  76. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  78. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  79. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  80. package/dist/web/standalone/server.js +1 -1
  81. package/package.json +1 -1
  82. package/packages/mcp-server/README.md +2 -11
  83. package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
  84. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  85. package/packages/mcp-server/dist/remote-questions.js +28 -0
  86. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  87. package/packages/mcp-server/dist/server.d.ts +28 -0
  88. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  89. package/packages/mcp-server/dist/server.js +94 -4
  90. package/packages/mcp-server/dist/server.js.map +1 -1
  91. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  92. package/packages/mcp-server/src/mcp-server.test.ts +226 -0
  93. package/packages/mcp-server/src/remote-questions.test.ts +103 -0
  94. package/packages/mcp-server/src/remote-questions.ts +35 -0
  95. package/packages/mcp-server/src/server.ts +129 -6
  96. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  97. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  98. package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
  99. package/src/resources/extensions/gsd/auto/loop.ts +263 -41
  100. package/src/resources/extensions/gsd/auto/phases.ts +15 -7
  101. package/src/resources/extensions/gsd/auto/session.ts +40 -0
  102. package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
  103. package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
  104. package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
  105. package/src/resources/extensions/gsd/auto.ts +166 -43
  106. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
  107. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
  108. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
  109. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
  110. package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
  111. package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
  112. package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
  113. package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
  114. package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
  115. package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
  116. package/src/resources/extensions/gsd/db-writer.ts +113 -17
  117. package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
  118. package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  119. package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
  120. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
  121. package/src/resources/extensions/gsd/doctor.ts +10 -2
  122. package/src/resources/extensions/gsd/gsd-db.ts +354 -3
  123. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  124. package/src/resources/extensions/gsd/guided-flow.ts +152 -26
  125. package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
  126. package/src/resources/extensions/gsd/metrics.ts +321 -1
  127. package/src/resources/extensions/gsd/paths.ts +67 -8
  128. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  129. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  130. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  131. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  132. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  133. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  134. package/src/resources/extensions/gsd/state.ts +44 -6
  135. package/src/resources/extensions/gsd/templates/project.md +10 -0
  136. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
  137. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
  138. package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
  139. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
  140. package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
  141. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
  142. package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
  143. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
  144. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
  145. package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
  146. package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
  147. package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
  148. package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
  149. package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
  150. package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
  151. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
  152. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
  153. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
  154. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
  155. package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
  156. package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
  157. package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
  158. package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
  159. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
  160. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
  161. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
  162. package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
  163. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
  164. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
  165. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
  166. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
  167. package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
  168. package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
  169. package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
  170. package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
  171. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
  172. package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
  173. package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
  174. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
  175. package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
  176. package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
  177. package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
  178. package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
  179. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
  180. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
  181. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
  182. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
  183. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
  184. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
  185. package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
  186. package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
  187. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
  188. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
  189. package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
  190. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -0
  191. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
  192. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +98 -0
  193. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
  194. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
  195. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
  196. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
  197. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
  198. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  199. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
  200. package/src/resources/extensions/gsd/tests/workspace.test.ts +196 -0
  201. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
  202. package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
  203. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
  204. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  205. package/src/resources/extensions/gsd/workspace.ts +95 -0
  206. package/src/resources/extensions/gsd/worktree-resolver.ts +78 -2
  207. package/src/resources/extensions/gsd/write-intercept.ts +3 -3
  208. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
  209. package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
  210. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
  211. /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
  212. /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
@@ -9,7 +9,6 @@ import { existsSync, cpSync, readFileSync, readdirSync, mkdirSync, realpathSync,
9
9
  import { isAbsolute, join, sep as pathSep } from "node:path";
10
10
  import { GSDError, GSD_IO_ERROR, GSD_GIT_ERROR } from "./errors.js";
11
11
  import { reconcileWorktreeDb, isDbAvailable, getMilestone, getMilestoneSlices, closeDatabase, openDatabase, getDbPath, } from "./gsd-db.js";
12
- import { atomicWriteSync } from "./atomic-write.js";
13
12
  import { execFileSync } from "node:child_process";
14
13
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
15
14
  import { gsdRoot, resolveGsdPathContract } from "./paths.js";
@@ -23,6 +22,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
23
22
  import { MILESTONE_ID_RE } from "./milestone-ids.js";
24
23
  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
24
  import { gsdHome } from "./gsd-home.js";
25
+ import { createWorkspace } from "./workspace.js";
26
26
  const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
27
27
  const LEGACY_PROJECT_PREFERENCES_FILE = "preferences.md";
28
28
  const LEGACY_DEEP_SETUP_RUNTIME_UNIT_FILES = new Set([
@@ -195,13 +195,22 @@ function forceOverwriteAssessmentsWithVerdict(srcMilestoneDir, dstMilestoneDir)
195
195
  }
196
196
  }
197
197
  // ─── Module State ──────────────────────────────────────────────────────────
198
- /** Original project root before chdir into auto-worktree. */
199
- let originalBase = null;
198
+ /** Active workspace registry replaces the legacy `originalBase` singleton. */
199
+ let activeWorkspace = null;
200
+ function setActiveWorkspace(ws) {
201
+ activeWorkspace = ws;
202
+ }
203
+ function getActiveWorkspace() {
204
+ return activeWorkspace;
205
+ }
200
206
  function clearProjectRootStateFiles(basePath, milestoneId) {
201
207
  const gsdDir = gsdRoot(basePath);
208
+ // Phase C pt 2: auto.lock removed from this list — the file is gone
209
+ // (migrated to the workers + unit_dispatches + runtime_kv tables). The
210
+ // remaining transient files (STATE.md, {MID}-META.json) are still
211
+ // worth removing on teardown.
202
212
  const transientFiles = [
203
213
  join(gsdDir, "STATE.md"),
204
- join(gsdDir, "auto.lock"),
205
214
  join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
206
215
  ];
207
216
  for (const file of transientFiles) {
@@ -275,6 +284,33 @@ export const isSafeToAutoResolve = (filePath) => filePath.startsWith(".gsd/") ||
275
284
  * gsd.db in the worktree so it rebuilds from fresh disk state (#853).
276
285
  * Non-fatal — sync failure should never block dispatch.
277
286
  */
287
+ /**
288
+ * Scope-typed variant of syncProjectRootToWorktree.
289
+ *
290
+ * Takes an explicit (rootScope, worktreeScope) pair where rootScope is the
291
+ * project root and worktreeScope is the auto-worktree. Direction is encoded
292
+ * in argument order. Asserts both scopes belong to the same workspace identity
293
+ * to prevent silent mismatch bugs.
294
+ */
295
+ export function syncProjectRootToWorktreeByScope(rootScope, worktreeScope) {
296
+ if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
297
+ throw new Error(`syncProjectRootToWorktreeByScope: scope identity mismatch — ` +
298
+ `rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
299
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
300
+ }
301
+ if (rootScope.milestoneId !== worktreeScope.milestoneId) {
302
+ throw new Error(`syncProjectRootToWorktreeByScope: milestoneId mismatch — ` +
303
+ `rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`);
304
+ }
305
+ const projectRoot = rootScope.workspace.projectRoot;
306
+ const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
307
+ const milestoneId = rootScope.milestoneId;
308
+ syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId);
309
+ }
310
+ /**
311
+ * @deprecated Use syncProjectRootToWorktreeByScope instead.
312
+ * TODO(C-future): remove once all callers migrated.
313
+ */
278
314
  export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId) {
279
315
  if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
280
316
  return;
@@ -343,12 +379,36 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
343
379
  logWarning("worktree", `worktree DB cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
344
380
  }
345
381
  }
382
+ /**
383
+ * Scope-typed variant of syncStateToProjectRoot.
384
+ *
385
+ * Takes an explicit (worktreeScope, rootScope) pair. Direction is encoded in
386
+ * argument order (worktree → root). Asserts both scopes belong to the same
387
+ * workspace identity to prevent silent mismatch bugs.
388
+ */
389
+ export function syncStateToProjectRootByScope(worktreeScope, rootScope) {
390
+ if (worktreeScope.workspace.identityKey !== rootScope.workspace.identityKey) {
391
+ throw new Error(`syncStateToProjectRootByScope: scope identity mismatch — ` +
392
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}" ` +
393
+ `rootScope.identityKey="${rootScope.workspace.identityKey}"`);
394
+ }
395
+ if (worktreeScope.milestoneId !== rootScope.milestoneId) {
396
+ throw new Error(`syncStateToProjectRootByScope: milestoneId mismatch — ` +
397
+ `worktreeScope.milestoneId="${worktreeScope.milestoneId}" rootScope.milestoneId="${rootScope.milestoneId}"`);
398
+ }
399
+ const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
400
+ const projectRoot = rootScope.workspace.projectRoot;
401
+ const milestoneId = worktreeScope.milestoneId;
402
+ syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId);
403
+ }
346
404
  /**
347
405
  * Sync worktree diagnostics from worktree to project root.
348
406
  * Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
349
407
  * DB/project-root state remains authoritative; markdown projections are not
350
408
  * copied from the worktree back to the project root.
351
409
  * Non-fatal — sync failure should never block dispatch.
410
+ * @deprecated Use syncStateToProjectRootByScope instead.
411
+ * TODO(C-future): remove once all callers migrated.
352
412
  */
353
413
  export function syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId) {
354
414
  if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
@@ -520,6 +580,24 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
520
580
  return cleaned;
521
581
  }
522
582
  // ─── Worktree ↔ Main Repo Sync (#1311) ──────────────────────────────────────
583
+ /**
584
+ * Scope-typed variant of syncGsdStateToWorktree.
585
+ *
586
+ * Takes an explicit (rootScope, worktreeScope) pair. Note: milestoneId is not
587
+ * used by syncGsdStateToWorktree — this variant only requires workspace
588
+ * identity. Asserts both scopes belong to the same workspace identity to
589
+ * prevent silent mismatch bugs.
590
+ */
591
+ export function syncGsdStateToWorktreeByScope(rootScope, worktreeScope) {
592
+ if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
593
+ throw new Error(`syncGsdStateToWorktreeByScope: scope identity mismatch — ` +
594
+ `rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
595
+ `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
596
+ }
597
+ const mainBasePath = rootScope.workspace.projectRoot;
598
+ const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
599
+ return syncGsdStateToWorktree(mainBasePath, worktreePath_);
600
+ }
523
601
  /**
524
602
  * Sync .gsd/ state from the main repo into the worktree.
525
603
  *
@@ -534,6 +612,8 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
534
612
  * Only adds missing content — never overwrites existing files in the worktree.
535
613
  * Worktree files are compatibility projections; DB/project root remains
536
614
  * authoritative for runtime state.
615
+ * @deprecated Use syncGsdStateToWorktreeByScope instead.
616
+ * TODO(C-future): remove once all callers migrated.
537
617
  */
538
618
  export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
539
619
  const contract = resolveGsdPathContract(worktreePath_, mainBasePath);
@@ -890,94 +970,13 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
890
970
  * Forward-merge plan checkbox state from the project root into a freshly
891
971
  * re-attached worktree (#778).
892
972
  *
893
- * When auto-mode stops via crash (not graceful stop), the milestone branch
894
- * HEAD may be behind the filesystem state at the project root because
895
- * syncStateToProjectRoot() runs after every task completion but the final
896
- * git commit may not have happened before the crash. On restart the worktree
897
- * is re-attached to the branch HEAD, which has [ ] for the crashed task,
898
- * causing verifyExpectedArtifact() to fail and triggering an infinite
899
- * dispatch/skip loop.
900
- *
901
- * Fix: after re-attaching, read every *.md plan file in the milestone
902
- * directory at the project root and apply any [x] checkbox states that are
903
- * ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
904
- *
905
- * This is forward-only compatibility for legacy projection copies. The DB
906
- * remains authoritative; this never downgrades checked boxes in a local
907
- * worktree projection.
973
+ * Phase C: deleted. Writers in workflow-projections.ts, triage-resolution.ts,
974
+ * rule-registry.ts, and auto-post-unit.ts now route through
975
+ * s.canonicalProjectRoot, so non-symlinked worktrees no longer need a local
976
+ * .gsd/ projection the project-root .gsd/ is the only authoritative source
977
+ * for both reads and writes. copyPlanningArtifacts and reconcilePlanCheckboxes
978
+ * (both formerly here) became dead.
908
979
  */
909
- function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
910
- const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
911
- const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
912
- if (!existsSync(srcMilestone) || !existsSync(dstMilestone))
913
- return;
914
- // Walk all markdown files in the milestone directory (plans, summaries, etc.)
915
- function walkMd(dir) {
916
- const results = [];
917
- try {
918
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
919
- const full = join(dir, entry.name);
920
- if (entry.isDirectory()) {
921
- results.push(...walkMd(full));
922
- }
923
- else if (entry.isFile() && entry.name.endsWith(".md")) {
924
- results.push(full);
925
- }
926
- }
927
- }
928
- catch (err) {
929
- /* non-fatal */
930
- logWarning("worktree", `walkMd directory read failed: ${err instanceof Error ? err.message : String(err)}`);
931
- }
932
- return results;
933
- }
934
- for (const srcFile of walkMd(srcMilestone)) {
935
- const rel = srcFile.slice(srcMilestone.length);
936
- const dstFile = dstMilestone + rel;
937
- if (!existsSync(dstFile))
938
- continue; // only reconcile existing files
939
- let srcContent;
940
- let dstContent;
941
- try {
942
- srcContent = readFileSync(srcFile, "utf-8");
943
- dstContent = readFileSync(dstFile, "utf-8");
944
- }
945
- catch (e) {
946
- logWarning("worktree", `reconcilePlanCheckboxes read failed: ${e.message}`);
947
- continue;
948
- }
949
- if (srcContent === dstContent)
950
- continue;
951
- // Extract all checked task IDs from the source (project root)
952
- // Pattern: - [x] **T<id>: or - [x] **S<id>: (case-insensitive x)
953
- const checkedRe = /^- \[[xX]\] \*\*([TS]\d+):/gm;
954
- const srcChecked = new Set();
955
- for (const m of srcContent.matchAll(checkedRe))
956
- srcChecked.add(m[1]);
957
- if (srcChecked.size === 0)
958
- continue;
959
- // Forward-apply: replace [ ] → [x] for any IDs that are checked in src
960
- let updated = dstContent;
961
- let changed = false;
962
- for (const id of srcChecked) {
963
- const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
964
- const uncheckedRe = new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm");
965
- if (uncheckedRe.test(updated)) {
966
- updated = updated.replace(new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm"), "$1[x]$2");
967
- changed = true;
968
- }
969
- }
970
- if (changed) {
971
- try {
972
- atomicWriteSync(dstFile, updated, "utf-8");
973
- }
974
- catch (err) {
975
- /* non-fatal */
976
- logWarning("worktree", `plan checkbox reconcile write failed: ${err instanceof Error ? err.message : String(err)}`);
977
- }
978
- }
979
- }
980
- }
981
980
  export function createAutoWorktree(basePath, milestoneId) {
982
981
  basePath = resolveWorktreeProjectRoot(basePath);
983
982
  // Check if repo has commits — git worktree requires a valid HEAD
@@ -1023,33 +1022,15 @@ export function createAutoWorktree(basePath, milestoneId) {
1023
1022
  startPoint,
1024
1023
  });
1025
1024
  }
1026
- // Copy .gsd/ planning artifacts from the source repo into the new worktree.
1027
- // Worktrees are fresh git checkouts untracked files don't carry over.
1028
- // Planning artifacts may be untracked if the project's .gitignore had a
1029
- // blanket .gsd/ rule (pre-v2.14.0). Without this copy, auto-mode loops
1030
- // on plan-slice because the plan file doesn't exist in the worktree.
1031
- //
1032
- // IMPORTANT: Skip when re-attaching to an existing branch (#759).
1033
- // The branch checkout already has committed artifacts with correct state
1034
- // (e.g. [x] for completed slices). Copying from the project root would
1035
- // overwrite them with stale data ([ ] checkboxes) because the root is
1036
- // not always fully synced.
1037
- if (!branchExists) {
1038
- copyPlanningArtifacts(basePath, info.path);
1039
- }
1040
- else {
1041
- // Re-attaching to an existing branch: forward-merge any plan checkpoint
1042
- // state from the project root into the worktree (#778).
1043
- //
1044
- // If auto-mode stopped via crash, the milestone branch HEAD may lag behind
1045
- // the project root filesystem because syncStateToProjectRoot() ran after
1046
- // task completion but the auto-commit never fired. On restart the worktree
1047
- // is re-created from the branch HEAD (which has [ ] for the crashed task),
1048
- // causing verifyExpectedArtifact() to return false → stale-key eviction →
1049
- // infinite dispatch/skip loop. Reconciling here ensures the worktree sees
1050
- // the same [x] state that syncStateToProjectRoot() wrote to the root.
1051
- reconcilePlanCheckboxes(basePath, info.path, milestoneId);
1052
- }
1025
+ // Phase C: copyPlanningArtifacts and reconcilePlanCheckboxes were
1026
+ // deleted. Both addressed the same problem (worktree-local .gsd/
1027
+ // projection lagging behind project-root state) by maintaining a stale
1028
+ // copy. Now that auto-mode writers in workflow-projections.ts,
1029
+ // triage-resolution.ts, rule-registry.ts, and auto-post-unit.ts route
1030
+ // through s.canonicalProjectRoot, the worktree never needs a local
1031
+ // .gsd/ both reads and writes converge on the project-root .gsd/.
1032
+ // The original concerns (#759, #778) no longer apply because there is
1033
+ // no second copy to drift.
1053
1034
  // Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
1054
1035
  const hookError = runWorktreePostCreateHook(basePath, info.path);
1055
1036
  if (hookError) {
@@ -1059,61 +1040,23 @@ export function createAutoWorktree(basePath, milestoneId) {
1059
1040
  const previousCwd = process.cwd();
1060
1041
  try {
1061
1042
  process.chdir(info.path);
1062
- originalBase = basePath;
1043
+ setActiveWorkspace(createWorkspace(basePath));
1063
1044
  }
1064
1045
  catch (err) {
1065
1046
  // If chdir fails, the worktree was created but we couldn't enter it.
1066
- // Don't store originalBase -- caller can retry or clean up.
1047
+ // Don't set activeWorkspace -- caller can retry or clean up.
1067
1048
  throw new GSDError(GSD_IO_ERROR, `Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`);
1068
1049
  }
1069
1050
  nudgeGitBranchCache(previousCwd);
1070
1051
  return info.path;
1071
1052
  }
1072
- /**
1073
- * Copy .gsd/ planning artifacts from source repo to a new worktree.
1074
- * Copies milestones/, DECISIONS.md, REQUIREMENTS.md, PROJECT.md, QUEUE.md,
1075
- * STATE.md, KNOWLEDGE.md, and OVERRIDES.md.
1076
- * Skips runtime files (auto.lock, metrics.json, etc.) and the worktrees/ dir.
1077
- * Best-effort failures are non-fatal since auto-mode can recreate artifacts.
1078
- */
1079
- function copyPlanningArtifacts(srcBase, wtPath) {
1080
- const srcGsd = join(srcBase, ".gsd");
1081
- const dstGsd = join(wtPath, ".gsd");
1082
- if (!existsSync(srcGsd))
1083
- return;
1084
- if (isSamePath(srcGsd, dstGsd))
1085
- return;
1086
- // Copy milestones/ directory (planning files, roadmaps, plans, research)
1087
- safeCopyRecursive(join(srcGsd, "milestones"), join(dstGsd, "milestones"), {
1088
- force: true,
1089
- filter: (src) => !src.endsWith("-META.json"),
1090
- });
1091
- // Copy top-level planning files
1092
- for (const file of [
1093
- "DECISIONS.md",
1094
- "REQUIREMENTS.md",
1095
- "PROJECT.md",
1096
- "QUEUE.md",
1097
- "STATE.md",
1098
- "KNOWLEDGE.md",
1099
- "OVERRIDES.md",
1100
- "mcp.json",
1101
- ]) {
1102
- safeCopy(join(srcGsd, file), join(dstGsd, file), { force: true });
1103
- }
1104
- // Seed canonical PREFERENCES.md when available; fall back to legacy lowercase.
1105
- if (existsSync(join(srcGsd, PROJECT_PREFERENCES_FILE))) {
1106
- safeCopy(join(srcGsd, PROJECT_PREFERENCES_FILE), join(dstGsd, PROJECT_PREFERENCES_FILE), { force: true });
1107
- }
1108
- else if (existsSync(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE))) {
1109
- safeCopy(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE), join(dstGsd, LEGACY_PROJECT_PREFERENCES_FILE), { force: true });
1110
- }
1111
- // Shared WAL (R012): worktrees use the project root's DB directly.
1112
- // No longer copy gsd.db into the worktree — the DB path resolver in
1113
- // ensureDbOpen() detects the worktree location and opens the root DB.
1114
- // Compat note: reconcileWorktreeDb() in mergeMilestoneToMain handles
1115
- // worktrees that already have a local gsd.db from before this change.
1116
- }
1053
+ // Phase C: copyPlanningArtifacts removed. Planning artifacts now live
1054
+ // only at the project root .gsd/; auto-mode writers (workflow-projections,
1055
+ // triage-resolution, rule-registry, regenerateIfMissing,
1056
+ // resolveHookArtifactPath) all route through s.canonicalProjectRoot.
1057
+ // Worktrees are pure git checkouts they no longer maintain a parallel
1058
+ // .gsd/ projection. The gsd.db has always lived at the project root via
1059
+ // the shared-WAL R012 contract; that is unchanged.
1117
1060
  /**
1118
1061
  * Teardown an auto-worktree: chdir back to original base, then remove
1119
1062
  * the worktree and its branch.
@@ -1123,41 +1066,79 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
1123
1066
  const branch = autoWorktreeBranch(milestoneId);
1124
1067
  const { preserveBranch = false } = opts;
1125
1068
  const previousCwd = process.cwd();
1069
+ // Wrap the entire teardown body in a single try/finally so activeWorkspace
1070
+ // is ALWAYS cleared — even if process.chdir throws (e.g. originalBasePath
1071
+ // was deleted before teardown ran). Previously the finally only covered
1072
+ // removeWorktree, leaving the registry stale on a chdir failure (H3 fix).
1126
1073
  try {
1127
- process.chdir(originalBasePath);
1128
- originalBase = null;
1129
- }
1130
- catch (err) {
1131
- throw new GSDError(GSD_IO_ERROR, `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`);
1132
- }
1133
- nudgeGitBranchCache(previousCwd);
1134
- removeWorktree(originalBasePath, milestoneId, {
1135
- branch,
1136
- deleteBranch: !preserveBranch,
1137
- });
1138
- // Verify cleanup succeeded — warn if the worktree directory is still on disk.
1139
- // On Windows, bash-based cleanup can silently fail when paths contain
1140
- // backslashes (#1436), leaving ~1 GB+ orphaned directories.
1141
- const wtDir = worktreePath(originalBasePath, milestoneId);
1142
- if (existsSync(wtDir)) {
1143
- logWarning("reconcile", `Worktree directory still exists after teardown: ${wtDir}. ` +
1144
- `This is likely an orphaned directory consuming disk space. ` +
1145
- `Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`, { worktree: milestoneId });
1146
- // Attempt a direct filesystem removal as a fallback — but ONLY if the
1147
- // path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
1148
- if (isInsideWorktreesDir(originalBasePath, wtDir)) {
1074
+ try {
1075
+ process.chdir(originalBasePath);
1076
+ }
1077
+ catch (err) {
1078
+ throw new GSDError(GSD_IO_ERROR, `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`);
1079
+ }
1080
+ // Mirror cleanup steps from mergeMilestoneToMain abort path:
1081
+ // 1. Remove transient state files (STATE.md, auto.lock, {MID}-META.json).
1082
+ // Non-fatal — must not block teardown.
1083
+ try {
1084
+ clearProjectRootStateFiles(originalBasePath, milestoneId);
1085
+ }
1086
+ catch (err) {
1087
+ logWarning("worktree", `clearProjectRootStateFiles failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
1088
+ }
1089
+ // 2. Reconcile worktree-local gsd.db into project root DB if both exist.
1090
+ // Non-fatal handles legacy worktrees that have a local copy.
1091
+ if (isDbAvailable()) {
1149
1092
  try {
1150
- rmSync(wtDir, { recursive: true, force: true });
1093
+ const contract = resolveGsdPathContract(previousCwd, originalBasePath);
1094
+ const worktreeDbPath = join(contract.worktreeGsd ?? join(previousCwd, ".gsd"), "gsd.db");
1095
+ const mainDbPath = contract.projectDb;
1096
+ if (existsSync(worktreeDbPath) && !isSamePath(worktreeDbPath, mainDbPath)) {
1097
+ reconcileWorktreeDb(mainDbPath, worktreeDbPath);
1098
+ }
1151
1099
  }
1152
1100
  catch (err) {
1153
- // Non-fatal — the warning above tells the user how to clean up
1154
- logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
1101
+ /* non-fatal */
1102
+ logError("worktree", `DB reconciliation failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
1155
1103
  }
1156
1104
  }
1157
- else {
1158
- console.error(`[GSD] REFUSING fallback rmSync path is outside .gsd/worktrees/: ${wtDir}`);
1105
+ nudgeGitBranchCache(previousCwd);
1106
+ // 3. Remove the worktree. Errors propagate naturally the outer finally
1107
+ // ensures activeWorkspace is cleared regardless.
1108
+ removeWorktree(originalBasePath, milestoneId, {
1109
+ branch,
1110
+ deleteBranch: !preserveBranch,
1111
+ });
1112
+ // Verify cleanup succeeded — warn if the worktree directory is still on disk.
1113
+ // On Windows, bash-based cleanup can silently fail when paths contain
1114
+ // backslashes (#1436), leaving ~1 GB+ orphaned directories.
1115
+ const wtDir = worktreePath(originalBasePath, milestoneId);
1116
+ if (existsSync(wtDir)) {
1117
+ logWarning("reconcile", `Worktree directory still exists after teardown: ${wtDir}. ` +
1118
+ `This is likely an orphaned directory consuming disk space. ` +
1119
+ `Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`, { worktree: milestoneId });
1120
+ // Attempt a direct filesystem removal as a fallback — but ONLY if the
1121
+ // path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
1122
+ if (isInsideWorktreesDir(originalBasePath, wtDir)) {
1123
+ try {
1124
+ rmSync(wtDir, { recursive: true, force: true });
1125
+ }
1126
+ catch (err) {
1127
+ // Non-fatal — the warning above tells the user how to clean up
1128
+ logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
1129
+ }
1130
+ }
1131
+ else {
1132
+ console.error(`[GSD] REFUSING fallback rmSync — path is outside .gsd/worktrees/: ${wtDir}`);
1133
+ }
1159
1134
  }
1160
1135
  }
1136
+ finally {
1137
+ // Clear module state unconditionally — regardless of which step above
1138
+ // failed. A stale activeWorkspace causes getActiveAutoWorktreeContext()
1139
+ // to return wrong data for subsequent operations.
1140
+ setActiveWorkspace(null);
1141
+ }
1161
1142
  }
1162
1143
  /**
1163
1144
  * Detect if the process is currently inside an auto-worktree.
@@ -1168,8 +1149,9 @@ export function isInAutoWorktree(basePath) {
1168
1149
  const targetPath = isGsdWorktreePath(basePath) ? basePath : process.cwd();
1169
1150
  if (!isGsdWorktreePath(targetPath))
1170
1151
  return false;
1171
- const projectRoot = resolveWorktreeProjectRoot(basePath, originalBase);
1172
- const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, originalBase);
1152
+ const storedBase = getAutoWorktreeOriginalBase();
1153
+ const projectRoot = resolveWorktreeProjectRoot(basePath, storedBase);
1154
+ const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, storedBase);
1173
1155
  if (normalizeWorktreePathForCompare(projectRoot) !==
1174
1156
  normalizeWorktreePathForCompare(targetProjectRoot)) {
1175
1157
  return false;
@@ -1242,7 +1224,7 @@ export function enterAutoWorktree(basePath, milestoneId) {
1242
1224
  const previousCwd = process.cwd();
1243
1225
  try {
1244
1226
  process.chdir(p);
1245
- originalBase = basePath;
1227
+ setActiveWorkspace(createWorkspace(basePath));
1246
1228
  }
1247
1229
  catch (err) {
1248
1230
  throw new GSDError(GSD_IO_ERROR, `Failed to enter auto-worktree at ${p}: ${err instanceof Error ? err.message : String(err)}`);
@@ -1255,14 +1237,16 @@ export function enterAutoWorktree(basePath, milestoneId) {
1255
1237
  * Returns null if not currently in an auto-worktree.
1256
1238
  */
1257
1239
  export function getAutoWorktreeOriginalBase() {
1258
- return originalBase;
1240
+ return getActiveWorkspace()?.projectRoot ?? null;
1259
1241
  }
1260
1242
  export function _resetAutoWorktreeOriginalBaseForTests() {
1261
- originalBase = null;
1243
+ setActiveWorkspace(null);
1262
1244
  }
1263
1245
  export function getActiveAutoWorktreeContext() {
1264
- if (!originalBase)
1246
+ const ws = getActiveWorkspace();
1247
+ if (!ws)
1265
1248
  return null;
1249
+ const originalBase = ws.projectRoot;
1266
1250
  const cwd = process.cwd();
1267
1251
  if (!isGsdWorktreePath(cwd))
1268
1252
  return null;
@@ -1332,11 +1316,11 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1332
1316
  // integration branch captures dirty files from OTHER milestones under a
1333
1317
  // misleading commit message, contaminating the main branch (#2929).
1334
1318
  //
1335
- // When originalBase is null (branch mode, no worktree), autoCommitDirtyState
1319
+ // When activeWorkspace is null (branch mode, no worktree), autoCommitDirtyState
1336
1320
  // runs unconditionally — the caller is responsible for cwd placement.
1337
1321
  {
1338
1322
  let shouldAutoCommit = true;
1339
- if (originalBase !== null) {
1323
+ if (getActiveWorkspace() !== null) {
1340
1324
  try {
1341
1325
  const currentBranch = nativeGetCurrentBranch(worktreeCwd);
1342
1326
  shouldAutoCommit = currentBranch === milestoneBranch;
@@ -2020,7 +2004,7 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
2020
2004
  logWarning("worktree", `git branch-delete failed: ${err instanceof Error ? err.message : String(err)}`);
2021
2005
  }
2022
2006
  // 14. Clear module state
2023
- originalBase = null;
2007
+ setActiveWorkspace(null);
2024
2008
  nudgeGitBranchCache(previousCwd);
2025
2009
  // 15. Anchor cwd at the project root on success-return. Step 12 removed
2026
2010
  // the worktree dir; if cwd was inside it, every subsequent process.cwd()