gsd-pi 2.78.1-dev.b6a389b66 → 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 (155) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/gsd/auto/phases.js +7 -2
  3. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +3 -2
  5. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
  6. package/dist/resources/extensions/gsd/auto-worktree.js +185 -40
  7. package/dist/resources/extensions/gsd/auto.js +62 -1
  8. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
  9. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
  10. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
  11. package/dist/resources/extensions/gsd/db-writer.js +96 -16
  12. package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
  13. package/dist/resources/extensions/gsd/gsd-db.js +194 -0
  14. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  15. package/dist/resources/extensions/gsd/guided-flow.js +117 -25
  16. package/dist/resources/extensions/gsd/metrics.js +287 -1
  17. package/dist/resources/extensions/gsd/paths.js +79 -8
  18. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  19. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  20. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  21. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  22. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  23. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  24. package/dist/resources/extensions/gsd/templates/project.md +10 -0
  25. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  26. package/dist/resources/extensions/gsd/workspace.js +59 -0
  27. package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
  28. package/dist/resources/extensions/gsd/write-intercept.js +3 -3
  29. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  30. package/dist/web/standalone/.next/BUILD_ID +1 -1
  31. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  32. package/dist/web/standalone/.next/build-manifest.json +2 -2
  33. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  34. package/dist/web/standalone/.next/required-server-files.json +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.html +1 -1
  52. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  59. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  61. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  62. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  63. package/dist/web/standalone/server.js +1 -1
  64. package/package.json +1 -1
  65. package/packages/mcp-server/README.md +2 -11
  66. package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
  67. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  68. package/packages/mcp-server/dist/remote-questions.js +28 -0
  69. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  70. package/packages/mcp-server/dist/server.d.ts +28 -0
  71. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  72. package/packages/mcp-server/dist/server.js +94 -4
  73. package/packages/mcp-server/dist/server.js.map +1 -1
  74. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  75. package/packages/mcp-server/src/mcp-server.test.ts +226 -0
  76. package/packages/mcp-server/src/remote-questions.test.ts +103 -0
  77. package/packages/mcp-server/src/remote-questions.ts +35 -0
  78. package/packages/mcp-server/src/server.ts +129 -6
  79. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  80. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  81. package/src/resources/extensions/gsd/auto/phases.ts +8 -2
  82. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  83. package/src/resources/extensions/gsd/auto-dispatch.ts +10 -2
  84. package/src/resources/extensions/gsd/auto-post-unit.ts +8 -1
  85. package/src/resources/extensions/gsd/auto-worktree.ts +225 -47
  86. package/src/resources/extensions/gsd/auto.ts +79 -1
  87. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
  88. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
  89. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
  90. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
  91. package/src/resources/extensions/gsd/db-writer.ts +113 -17
  92. package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
  93. package/src/resources/extensions/gsd/gsd-db.ts +184 -0
  94. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  95. package/src/resources/extensions/gsd/guided-flow.ts +154 -25
  96. package/src/resources/extensions/gsd/metrics.ts +321 -1
  97. package/src/resources/extensions/gsd/paths.ts +67 -8
  98. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  99. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  100. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  101. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  102. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  103. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  104. package/src/resources/extensions/gsd/templates/project.md +10 -0
  105. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
  106. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
  107. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
  108. package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
  109. package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
  110. package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
  111. package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
  112. package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
  113. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
  114. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
  115. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
  116. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
  117. package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
  118. package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
  119. package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
  120. package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
  121. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
  122. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
  123. package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
  124. package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
  125. package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
  126. package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
  127. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
  128. package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
  129. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
  130. package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
  131. package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
  132. package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
  133. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
  134. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
  135. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +74 -0
  136. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
  137. package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
  138. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
  139. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
  140. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
  141. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
  142. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
  143. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
  144. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  145. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
  146. package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
  147. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
  148. package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
  149. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
  150. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  151. package/src/resources/extensions/gsd/workspace.ts +95 -0
  152. package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
  153. package/src/resources/extensions/gsd/write-intercept.ts +3 -3
  154. /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
  155. /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- cc8431019a2c8873
1
+ 3cb2810818585c65
@@ -12,7 +12,7 @@ import { MAX_RECOVERY_CHARS, BUDGET_THRESHOLDS, MAX_FINALIZE_TIMEOUTS, } from ".
12
12
  import { detectStuck } from "./detect-stuck.js";
13
13
  import { runUnit } from "./run-unit.js";
14
14
  import { debugLog } from "../debug-logger.js";
15
- import { resolveWorktreeProjectRoot } from "../worktree-root.js";
15
+ import { resolveWorktreeProjectRoot, normalizeWorktreePathForCompare } from "../worktree-root.js";
16
16
  import { PROJECT_FILES, hasProjectFileInAncestor } from "../detection.js";
17
17
  import { MergeConflictError } from "../git-service.js";
18
18
  import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
@@ -37,6 +37,11 @@ import { parseUnitId } from "../unit-id.js";
37
37
  import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
38
38
  import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
39
39
  import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, supportsStructuredQuestions, } from "../workflow-mcp.js";
40
+ // ─── Path Comparison Helper ───────────────────────────────────────────────
41
+ /** Compare two paths for physical identity, tolerating trailing slashes and symlinks. */
42
+ function isSamePathLocal(a, b) {
43
+ return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
44
+ }
40
45
  // ─── Session timeout auto-resume state ────────────────────────────────────────
41
46
  let consecutiveSessionTimeouts = 0;
42
47
  const MAX_SESSION_TIMEOUT_AUTO_RESUMES = 3;
@@ -280,7 +285,7 @@ export async function runPreDispatch(ic, loopState) {
280
285
  }
281
286
  // Sync project root artifacts into worktree
282
287
  if (s.originalBasePath &&
283
- s.basePath !== s.originalBasePath &&
288
+ !isSamePathLocal(s.basePath, s.originalBasePath) &&
284
289
  s.currentMilestoneId) {
285
290
  deps.syncProjectRootToWorktree(s.originalBasePath, s.basePath, s.currentMilestoneId);
286
291
  }
@@ -32,6 +32,8 @@ export class AutoSession {
32
32
  // ── Paths ────────────────────────────────────────────────────────────────
33
33
  basePath = "";
34
34
  originalBasePath = "";
35
+ // TODO(C8): remove basePath/originalBasePath once all readers use s.scope
36
+ scope = null;
35
37
  previousProjectRootEnv = null;
36
38
  hadProjectRootEnv = false;
37
39
  projectRootEnvCaptured = false;
@@ -173,6 +175,7 @@ export class AutoSession {
173
175
  // Paths
174
176
  this.basePath = "";
175
177
  this.originalBasePath = "";
178
+ this.scope = null;
176
179
  this.previousProjectRootEnv = null;
177
180
  this.hadProjectRootEnv = false;
178
181
  this.projectRootEnvCaptured = false;
@@ -31,6 +31,7 @@ import { ensureWorkflowPreferencesCaptured } from "./planning-depth.js";
31
31
  import { MILESTONE_ID_RE } from "./milestone-ids.js";
32
32
  import { PROJECT_RESEARCH_INFLIGHT_MARKER, } from "./project-research-policy.js";
33
33
  import { isWorkflowPrefsCaptured, resolveDeepProjectSetupState, } from "./deep-project-setup-policy.js";
34
+ import { annotateBackgroundable } from "./delegation-policy.js";
34
35
  let reassessmentChecker = checkNeedsReassessment;
35
36
  let researchProjectPromptBuilder = buildResearchProjectPrompt;
36
37
  function shouldBypassMilestoneDepthGateInAuto(prefs) {
@@ -1152,7 +1153,7 @@ export async function resolveDispatch(ctx) {
1152
1153
  // Delegate to registry when available
1153
1154
  try {
1154
1155
  const registry = getRegistry();
1155
- return await registry.evaluateDispatch(ctx);
1156
+ return annotateBackgroundable(await registry.evaluateDispatch(ctx));
1156
1157
  }
1157
1158
  catch (err) {
1158
1159
  // Registry not initialized — fall back to inline loop
@@ -1163,7 +1164,7 @@ export async function resolveDispatch(ctx) {
1163
1164
  if (result) {
1164
1165
  if (result.action !== "skip")
1165
1166
  result.matchedRule = rule.name;
1166
- return result;
1167
+ return annotateBackgroundable(result);
1167
1168
  }
1168
1169
  }
1169
1170
  // No rule matched — unhandled phase.
@@ -24,6 +24,7 @@ import { runTurnGitAction, } from "./git-service.js";
24
24
  import { verifyExpectedArtifact, resolveExpectedArtifactPath, writeBlockerPlaceholder, diagnoseExpectedArtifact, } from "./auto-recovery.js";
25
25
  import { regenerateIfMissing } from "./workflow-projections.js";
26
26
  import { syncStateToProjectRoot } from "./auto-worktree.js";
27
+ import { normalizeWorktreePathForCompare } from "./worktree-root.js";
27
28
  import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
28
29
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
29
30
  import { consumeSignal } from "./session-status-io.js";
@@ -51,6 +52,11 @@ import { detectAbandonMilestone } from "./abandon-detect.js";
51
52
  import { isDeterministicPolicyError } from "./auto-tool-tracking.js";
52
53
  import { clearProjectResearchInflightMarker, finalizeProjectResearchTimeout, } from "./project-research-policy.js";
53
54
  import { validateArtifact } from "./schemas/validate.js";
55
+ // ─── Path Comparison Helper ───────────────────────────────────────────────
56
+ /** Compare two paths for physical identity, tolerating trailing slashes and symlinks. */
57
+ function isSamePathLocal(a, b) {
58
+ return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
59
+ }
54
60
  /** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
55
61
  const MAX_VERIFICATION_RETRIES = 3;
56
62
  /** Keep failure toasts short while still showing concrete examples. */
@@ -510,7 +516,7 @@ export async function postUnitPreVerification(pctx, opts) {
510
516
  await rebuildState(s.basePath);
511
517
  });
512
518
  // Sync worktree state back to project root (skipped for lightweight sidecars)
513
- if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
519
+ if (!opts?.skipWorktreeSync && s.originalBasePath && !isSamePathLocal(s.originalBasePath, s.basePath)) {
514
520
  await runSafely("postUnit", "worktree-sync", () => {
515
521
  syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
516
522
  });
@@ -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([
@@ -195,8 +196,14 @@ function forceOverwriteAssessmentsWithVerdict(srcMilestoneDir, dstMilestoneDir)
195
196
  }
196
197
  }
197
198
  // ─── Module State ──────────────────────────────────────────────────────────
198
- /** Original project root before chdir into auto-worktree. */
199
- 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
+ }
200
207
  function clearProjectRootStateFiles(basePath, milestoneId) {
201
208
  const gsdDir = gsdRoot(basePath);
202
209
  const transientFiles = [
@@ -275,6 +282,33 @@ export const isSafeToAutoResolve = (filePath) => filePath.startsWith(".gsd/") ||
275
282
  * gsd.db in the worktree so it rebuilds from fresh disk state (#853).
276
283
  * Non-fatal — sync failure should never block dispatch.
277
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
+ */
278
312
  export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId) {
279
313
  if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
280
314
  return;
@@ -343,12 +377,36 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
343
377
  logWarning("worktree", `worktree DB cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
344
378
  }
345
379
  }
380
+ /**
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
+ }
346
402
  /**
347
403
  * Sync worktree diagnostics from worktree to project root.
348
404
  * Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
349
405
  * DB/project-root state remains authoritative; markdown projections are not
350
406
  * copied from the worktree back to the project root.
351
407
  * Non-fatal — sync failure should never block dispatch.
408
+ * @deprecated Use syncStateToProjectRootByScope instead.
409
+ * TODO(C-future): remove once all callers migrated.
352
410
  */
353
411
  export function syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId) {
354
412
  if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
@@ -520,6 +578,24 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
520
578
  return cleaned;
521
579
  }
522
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
+ }
523
599
  /**
524
600
  * Sync .gsd/ state from the main repo into the worktree.
525
601
  *
@@ -534,6 +610,8 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
534
610
  * Only adds missing content — never overwrites existing files in the worktree.
535
611
  * Worktree files are compatibility projections; DB/project root remains
536
612
  * authoritative for runtime state.
613
+ * @deprecated Use syncGsdStateToWorktreeByScope instead.
614
+ * TODO(C-future): remove once all callers migrated.
537
615
  */
538
616
  export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
539
617
  const contract = resolveGsdPathContract(worktreePath_, mainBasePath);
@@ -906,6 +984,32 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
906
984
  * remains authoritative; this never downgrades checked boxes in a local
907
985
  * worktree projection.
908
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.
1012
+ */
909
1013
  function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
910
1014
  const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
911
1015
  const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
@@ -1059,11 +1163,11 @@ export function createAutoWorktree(basePath, milestoneId) {
1059
1163
  const previousCwd = process.cwd();
1060
1164
  try {
1061
1165
  process.chdir(info.path);
1062
- originalBase = basePath;
1166
+ setActiveWorkspace(createWorkspace(basePath));
1063
1167
  }
1064
1168
  catch (err) {
1065
1169
  // If chdir fails, the worktree was created but we couldn't enter it.
1066
- // Don't store originalBase -- caller can retry or clean up.
1170
+ // Don't set activeWorkspace -- caller can retry or clean up.
1067
1171
  throw new GSDError(GSD_IO_ERROR, `Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`);
1068
1172
  }
1069
1173
  nudgeGitBranchCache(previousCwd);
@@ -1123,41 +1227,79 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
1123
1227
  const branch = autoWorktreeBranch(milestoneId);
1124
1228
  const { preserveBranch = false } = opts;
1125
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).
1126
1234
  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)) {
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()) {
1149
1253
  try {
1150
- 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
+ }
1151
1260
  }
1152
1261
  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)}`);
1262
+ /* non-fatal */
1263
+ logError("worktree", `DB reconciliation failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
1155
1264
  }
1156
1265
  }
1157
- else {
1158
- 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
+ }
1159
1295
  }
1160
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
+ }
1161
1303
  }
1162
1304
  /**
1163
1305
  * Detect if the process is currently inside an auto-worktree.
@@ -1168,8 +1310,9 @@ export function isInAutoWorktree(basePath) {
1168
1310
  const targetPath = isGsdWorktreePath(basePath) ? basePath : process.cwd();
1169
1311
  if (!isGsdWorktreePath(targetPath))
1170
1312
  return false;
1171
- const projectRoot = resolveWorktreeProjectRoot(basePath, originalBase);
1172
- const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, originalBase);
1313
+ const storedBase = getAutoWorktreeOriginalBase();
1314
+ const projectRoot = resolveWorktreeProjectRoot(basePath, storedBase);
1315
+ const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, storedBase);
1173
1316
  if (normalizeWorktreePathForCompare(projectRoot) !==
1174
1317
  normalizeWorktreePathForCompare(targetProjectRoot)) {
1175
1318
  return false;
@@ -1242,7 +1385,7 @@ export function enterAutoWorktree(basePath, milestoneId) {
1242
1385
  const previousCwd = process.cwd();
1243
1386
  try {
1244
1387
  process.chdir(p);
1245
- originalBase = basePath;
1388
+ setActiveWorkspace(createWorkspace(basePath));
1246
1389
  }
1247
1390
  catch (err) {
1248
1391
  throw new GSDError(GSD_IO_ERROR, `Failed to enter auto-worktree at ${p}: ${err instanceof Error ? err.message : String(err)}`);
@@ -1255,14 +1398,16 @@ export function enterAutoWorktree(basePath, milestoneId) {
1255
1398
  * Returns null if not currently in an auto-worktree.
1256
1399
  */
1257
1400
  export function getAutoWorktreeOriginalBase() {
1258
- return originalBase;
1401
+ return getActiveWorkspace()?.projectRoot ?? null;
1259
1402
  }
1260
1403
  export function _resetAutoWorktreeOriginalBaseForTests() {
1261
- originalBase = null;
1404
+ setActiveWorkspace(null);
1262
1405
  }
1263
1406
  export function getActiveAutoWorktreeContext() {
1264
- if (!originalBase)
1407
+ const ws = getActiveWorkspace();
1408
+ if (!ws)
1265
1409
  return null;
1410
+ const originalBase = ws.projectRoot;
1266
1411
  const cwd = process.cwd();
1267
1412
  if (!isGsdWorktreePath(cwd))
1268
1413
  return null;
@@ -1332,11 +1477,11 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1332
1477
  // integration branch captures dirty files from OTHER milestones under a
1333
1478
  // misleading commit message, contaminating the main branch (#2929).
1334
1479
  //
1335
- // When originalBase is null (branch mode, no worktree), autoCommitDirtyState
1480
+ // When activeWorkspace is null (branch mode, no worktree), autoCommitDirtyState
1336
1481
  // runs unconditionally — the caller is responsible for cwd placement.
1337
1482
  {
1338
1483
  let shouldAutoCommit = true;
1339
- if (originalBase !== null) {
1484
+ if (getActiveWorkspace() !== null) {
1340
1485
  try {
1341
1486
  const currentBranch = nativeGetCurrentBranch(worktreeCwd);
1342
1487
  shouldAutoCommit = currentBranch === milestoneBranch;
@@ -2020,7 +2165,7 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
2020
2165
  logWarning("worktree", `git branch-delete failed: ${err instanceof Error ? err.message : String(err)}`);
2021
2166
  }
2022
2167
  // 14. Clear module state
2023
- originalBase = null;
2168
+ setActiveWorkspace(null);
2024
2169
  nudgeGitBranchCache(previousCwd);
2025
2170
  // 15. Anchor cwd at the project root on success-return. Step 12 removed
2026
2171
  // the worktree dir; if cwd was inside it, every subsequent process.cwd()
@@ -86,6 +86,7 @@ import { reorderForCaching } from "./prompt-ordering.js";
86
86
  export { STUB_RECOVERY_THRESHOLD, NEW_SESSION_TIMEOUT_MS, } from "./auto/session.js";
87
87
  import { autoSession as s } from "./auto-runtime-state.js";
88
88
  import { gsdHome } from "./gsd-home.js";
89
+ import { createWorkspace, scopeMilestone } from "./workspace.js";
89
90
  // ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
90
91
  // ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
91
92
  // This file must NOT declare module-level `let` or `var` variables for state.
@@ -147,6 +148,32 @@ function restoreMilestoneLockEnv() {
147
148
  s.hadMilestoneLockEnv = false;
148
149
  s.milestoneLockEnvCaptured = false;
149
150
  }
151
+ /**
152
+ * Rebuild s.scope from the current s.basePath / s.originalBasePath / s.currentMilestoneId.
153
+ *
154
+ * Pass the worktree path as rawPath when entering a worktree so createWorkspace
155
+ * can detect the worktree layout and set mode="worktree". When no worktree is
156
+ * active, rawPath should equal the project root.
157
+ *
158
+ * Clears s.scope when milestoneId is absent — scope is only meaningful when a
159
+ * milestone is active.
160
+ *
161
+ * TODO(C8): remove basePath/originalBasePath once all readers use s.scope.
162
+ */
163
+ function rebuildScope(rawPath, milestoneId) {
164
+ if (!milestoneId) {
165
+ s.scope = null;
166
+ return;
167
+ }
168
+ try {
169
+ const workspace = createWorkspace(rawPath);
170
+ s.scope = scopeMilestone(workspace, milestoneId);
171
+ }
172
+ catch {
173
+ // Non-fatal — scope is additive. Existing readers still use basePath.
174
+ s.scope = null;
175
+ }
176
+ }
150
177
  function normalizeSessionFilePath(raw) {
151
178
  if (typeof raw !== "string")
152
179
  return null;
@@ -284,6 +311,18 @@ export function isAutoActive() {
284
311
  export function _setAutoActiveForTest(active) {
285
312
  s.active = active;
286
313
  }
314
+ /**
315
+ * Test-only seam: emit the missing-worktree warning exactly as the resume path
316
+ * does. Allows unit tests to verify the warning is produced without
317
+ * bootstrapping the full auto-mode entry point. Do not use in production code.
318
+ */
319
+ export function _warnIfWorktreeMissingForTest(worktreePath, milestoneId) {
320
+ if (worktreePath && !existsSync(worktreePath)) {
321
+ logWarning("session", `Worktree was expected at ${worktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId });
322
+ return true;
323
+ }
324
+ return false;
325
+ }
287
326
  export function isAutoPaused() {
288
327
  return s.paused;
289
328
  }
@@ -1204,6 +1243,18 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1204
1243
  s.autoStartTime = meta.autoStartTime || Date.now();
1205
1244
  s.sessionMilestoneLock = meta.milestoneLock ?? null;
1206
1245
  s.paused = true;
1246
+ // Build scope from persisted state. Use worktreePath when present and
1247
+ // still on disk so mode is detected correctly; fall back to project root.
1248
+ {
1249
+ const persistedWorktreePath = meta.worktreePath ?? null;
1250
+ if (persistedWorktreePath && !existsSync(persistedWorktreePath)) {
1251
+ logWarning("session", `Worktree was expected at ${persistedWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: meta.milestoneId ?? "" });
1252
+ }
1253
+ const rawForScope = (persistedWorktreePath && existsSync(persistedWorktreePath))
1254
+ ? persistedWorktreePath
1255
+ : (s.originalBasePath || base);
1256
+ rebuildScope(rawForScope, s.currentMilestoneId);
1257
+ }
1207
1258
  try {
1208
1259
  unlinkSync(pausedPath);
1209
1260
  }
@@ -1285,10 +1336,15 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1285
1336
  // session (e.g. isolation mode changed, detectWorktreeName differs across
1286
1337
  // process restarts). We guard with existsSync so a stale or deleted
1287
1338
  // worktree directory safely falls back to the project root.
1288
- const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath;
1339
+ const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath ?? null;
1340
+ if (resumeWorktreePath && !existsSync(resumeWorktreePath)) {
1341
+ logWarning("session", `Worktree was expected at ${resumeWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: s.currentMilestoneId ?? "" });
1342
+ }
1289
1343
  if (resumeWorktreePath && existsSync(resumeWorktreePath)) {
1290
1344
  s.basePath = resumeWorktreePath;
1291
1345
  }
1346
+ // Rebuild scope now that s.basePath reflects the actual worktree (or project root).
1347
+ rebuildScope(s.basePath, s.currentMilestoneId);
1292
1348
  // Ensure the workflow-logger audit log is pinned to the project root
1293
1349
  // even when auto-mode is entered via a path that bypasses the
1294
1350
  // bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain
@@ -1315,6 +1371,8 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1315
1371
  buildResolver().enterMilestone(s.currentMilestoneId, {
1316
1372
  notify: ctx.ui.notify.bind(ctx.ui),
1317
1373
  });
1374
+ // s.basePath may have been updated to a worktree path by enterMilestone.
1375
+ rebuildScope(s.basePath, s.currentMilestoneId);
1318
1376
  }
1319
1377
  registerSigtermHandler(lockBase());
1320
1378
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
@@ -1393,6 +1451,9 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1393
1451
  const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
1394
1452
  if (!ready)
1395
1453
  return;
1454
+ // Build scope after bootstrap has populated s.basePath / s.originalBasePath /
1455
+ // s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
1456
+ rebuildScope(s.basePath, s.currentMilestoneId);
1396
1457
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1397
1458
  try {
1398
1459
  pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync", preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, state: await deriveState(s.basePath) });
@@ -86,7 +86,7 @@ export async function handleAgentEnd(pi, event, ctx) {
86
86
  logWarning("bootstrap", `checkDeepProjectSetupAfterTurn failed: ${message}`);
87
87
  }
88
88
  if (checkAutoStartAfterDiscuss()) {
89
- clearDiscussionFlowState();
89
+ clearDiscussionFlowState(resolveAgentEndBasePath() ?? process.cwd());
90
90
  return;
91
91
  }
92
92
  // #4573 — When the LLM emits "Milestone X ready." but the required files