gsd-pi 2.32.0 → 2.33.0-dev.69bff0f

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 (109) hide show
  1. package/README.md +22 -20
  2. package/dist/resource-loader.js +13 -3
  3. package/dist/resources/extensions/gsd/auto-dashboard.ts +3 -1
  4. package/dist/resources/extensions/gsd/auto-dispatch.ts +40 -12
  5. package/dist/resources/extensions/gsd/auto-idempotency.ts +3 -2
  6. package/dist/resources/extensions/gsd/auto-observability.ts +2 -4
  7. package/dist/resources/extensions/gsd/auto-post-unit.ts +5 -5
  8. package/dist/resources/extensions/gsd/auto-prompts.ts +46 -44
  9. package/dist/resources/extensions/gsd/auto-recovery.ts +8 -22
  10. package/dist/resources/extensions/gsd/auto-start.ts +8 -6
  11. package/dist/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
  12. package/dist/resources/extensions/gsd/auto-supervisor.ts +10 -5
  13. package/dist/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
  14. package/dist/resources/extensions/gsd/auto-timers.ts +3 -2
  15. package/dist/resources/extensions/gsd/auto-verification.ts +6 -6
  16. package/dist/resources/extensions/gsd/auto-worktree.ts +140 -5
  17. package/dist/resources/extensions/gsd/auto.ts +108 -182
  18. package/dist/resources/extensions/gsd/commands-inspect.ts +2 -1
  19. package/dist/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
  20. package/dist/resources/extensions/gsd/commands.ts +14 -2
  21. package/dist/resources/extensions/gsd/complexity-classifier.ts +5 -7
  22. package/dist/resources/extensions/gsd/crash-recovery.ts +15 -2
  23. package/dist/resources/extensions/gsd/dispatch-guard.ts +2 -1
  24. package/dist/resources/extensions/gsd/error-utils.ts +6 -0
  25. package/dist/resources/extensions/gsd/export.ts +2 -1
  26. package/dist/resources/extensions/gsd/git-service.ts +3 -2
  27. package/dist/resources/extensions/gsd/guided-flow.ts +3 -2
  28. package/dist/resources/extensions/gsd/index.ts +12 -5
  29. package/dist/resources/extensions/gsd/key-manager.ts +2 -1
  30. package/dist/resources/extensions/gsd/marketplace-discovery.ts +4 -3
  31. package/dist/resources/extensions/gsd/metrics.ts +3 -3
  32. package/dist/resources/extensions/gsd/migrate-external.ts +21 -4
  33. package/dist/resources/extensions/gsd/milestone-ids.ts +2 -1
  34. package/dist/resources/extensions/gsd/native-git-bridge.ts +2 -1
  35. package/dist/resources/extensions/gsd/parallel-merge.ts +2 -1
  36. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
  37. package/dist/resources/extensions/gsd/post-unit-hooks.ts +8 -9
  38. package/dist/resources/extensions/gsd/quick.ts +58 -3
  39. package/dist/resources/extensions/gsd/repo-identity.ts +22 -1
  40. package/dist/resources/extensions/gsd/session-lock.ts +86 -11
  41. package/dist/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +14 -11
  42. package/dist/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +691 -0
  43. package/dist/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +317 -0
  44. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  45. package/dist/resources/extensions/gsd/tests/loop-regression.test.ts +877 -0
  46. package/dist/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +358 -0
  47. package/dist/resources/extensions/gsd/tests/session-lock-regression.test.ts +216 -0
  48. package/dist/resources/extensions/gsd/tests/session-lock.test.ts +119 -0
  49. package/dist/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +206 -0
  50. package/dist/resources/extensions/gsd/undo.ts +5 -7
  51. package/dist/resources/extensions/gsd/unit-id.ts +14 -0
  52. package/dist/resources/extensions/gsd/unit-runtime.ts +2 -1
  53. package/dist/resources/extensions/gsd/worktree-command.ts +8 -7
  54. package/package.json +3 -2
  55. package/packages/pi-coding-agent/package.json +1 -1
  56. package/pkg/package.json +1 -1
  57. package/src/resources/extensions/gsd/auto-dashboard.ts +3 -1
  58. package/src/resources/extensions/gsd/auto-dispatch.ts +40 -12
  59. package/src/resources/extensions/gsd/auto-idempotency.ts +3 -2
  60. package/src/resources/extensions/gsd/auto-observability.ts +2 -4
  61. package/src/resources/extensions/gsd/auto-post-unit.ts +5 -5
  62. package/src/resources/extensions/gsd/auto-prompts.ts +46 -44
  63. package/src/resources/extensions/gsd/auto-recovery.ts +8 -22
  64. package/src/resources/extensions/gsd/auto-start.ts +8 -6
  65. package/src/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
  66. package/src/resources/extensions/gsd/auto-supervisor.ts +10 -5
  67. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
  68. package/src/resources/extensions/gsd/auto-timers.ts +3 -2
  69. package/src/resources/extensions/gsd/auto-verification.ts +6 -6
  70. package/src/resources/extensions/gsd/auto-worktree.ts +140 -5
  71. package/src/resources/extensions/gsd/auto.ts +108 -182
  72. package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
  73. package/src/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
  74. package/src/resources/extensions/gsd/commands.ts +14 -2
  75. package/src/resources/extensions/gsd/complexity-classifier.ts +5 -7
  76. package/src/resources/extensions/gsd/crash-recovery.ts +15 -2
  77. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
  78. package/src/resources/extensions/gsd/error-utils.ts +6 -0
  79. package/src/resources/extensions/gsd/export.ts +2 -1
  80. package/src/resources/extensions/gsd/git-service.ts +3 -2
  81. package/src/resources/extensions/gsd/guided-flow.ts +3 -2
  82. package/src/resources/extensions/gsd/index.ts +12 -5
  83. package/src/resources/extensions/gsd/key-manager.ts +2 -1
  84. package/src/resources/extensions/gsd/marketplace-discovery.ts +4 -3
  85. package/src/resources/extensions/gsd/metrics.ts +3 -3
  86. package/src/resources/extensions/gsd/migrate-external.ts +21 -4
  87. package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
  88. package/src/resources/extensions/gsd/native-git-bridge.ts +2 -1
  89. package/src/resources/extensions/gsd/parallel-merge.ts +2 -1
  90. package/src/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
  91. package/src/resources/extensions/gsd/post-unit-hooks.ts +8 -9
  92. package/src/resources/extensions/gsd/quick.ts +58 -3
  93. package/src/resources/extensions/gsd/repo-identity.ts +22 -1
  94. package/src/resources/extensions/gsd/session-lock.ts +86 -11
  95. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +14 -11
  96. package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +691 -0
  97. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +317 -0
  98. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  99. package/src/resources/extensions/gsd/tests/loop-regression.test.ts +877 -0
  100. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +358 -0
  101. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +216 -0
  102. package/src/resources/extensions/gsd/tests/session-lock.test.ts +119 -0
  103. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +206 -0
  104. package/src/resources/extensions/gsd/undo.ts +5 -7
  105. package/src/resources/extensions/gsd/unit-id.ts +14 -0
  106. package/src/resources/extensions/gsd/unit-runtime.ts +2 -1
  107. package/src/resources/extensions/gsd/worktree-command.ts +8 -7
  108. package/dist/resources/extensions/mcporter/extension-manifest.json +0 -12
  109. package/src/resources/extensions/mcporter/extension-manifest.json +0 -12
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Auto-mode Supervisor — SIGTERM handling and working-tree activity detection.
2
+ * Auto-mode Supervisor — signal handling and working-tree activity detection.
3
3
  *
4
4
  * Pure functions — no module-level globals or AutoContext dependency.
5
5
  */
@@ -8,10 +8,10 @@ import { clearLock } from "./crash-recovery.js";
8
8
  import { releaseSessionLock } from "./session-lock.js";
9
9
  import { nativeHasChanges } from "./native-git-bridge.js";
10
10
 
11
- // ─── SIGTERM Handling ─────────────────────────────────────────────────────────
11
+ // ─── Signal Handling ──────────────────────────────────────────────────────────
12
12
 
13
13
  /**
14
- * Register a SIGTERM handler that clears the lock file and exits cleanly.
14
+ * Register SIGTERM and SIGINT handlers that clear lock files and exit cleanly.
15
15
  * Captures the active base path at registration time so the handler
16
16
  * always references the correct path even if the module variable changes.
17
17
  * Removes any previously registered handler before installing the new one.
@@ -22,20 +22,25 @@ export function registerSigtermHandler(
22
22
  currentBasePath: string,
23
23
  previousHandler: (() => void) | null,
24
24
  ): () => void {
25
- if (previousHandler) process.off("SIGTERM", previousHandler);
25
+ if (previousHandler) {
26
+ process.off("SIGTERM", previousHandler);
27
+ process.off("SIGINT", previousHandler);
28
+ }
26
29
  const handler = () => {
27
30
  releaseSessionLock(currentBasePath);
28
31
  clearLock(currentBasePath);
29
32
  process.exit(0);
30
33
  };
31
34
  process.on("SIGTERM", handler);
35
+ process.on("SIGINT", handler);
32
36
  return handler;
33
37
  }
34
38
 
35
- /** Deregister the SIGTERM handler (called on stop/pause). */
39
+ /** Deregister signal handlers (called on stop/pause). */
36
40
  export function deregisterSigtermHandler(handler: (() => void) | null): void {
37
41
  if (handler) {
38
42
  process.off("SIGTERM", handler);
43
+ process.off("SIGINT", handler);
39
44
  }
40
45
  }
41
46
 
@@ -18,6 +18,7 @@ import {
18
18
  writeBlockerPlaceholder,
19
19
  } from "./auto-recovery.js";
20
20
  import { existsSync } from "node:fs";
21
+ import { parseUnitId } from "./unit-id.js";
21
22
 
22
23
  export interface RecoveryContext {
23
24
  basePath: string;
@@ -128,7 +129,7 @@ export async function recoverTimedOutUnit(
128
129
 
129
130
  // Retries exhausted — write missing durable artifacts and advance.
130
131
  const diagnostic = formatExecuteTaskRecoveryStatus(status);
131
- const [mid, sid, tid] = unitId.split("/");
132
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
132
133
  const skipped = mid && sid && tid
133
134
  ? skipExecuteTask(basePath, mid, sid, tid, status, reason, maxRecoveryAttempts)
134
135
  : false;
@@ -20,6 +20,7 @@ import { closeoutUnit, type CloseoutOptions } from "./auto-unit-closeout.js";
20
20
  import { saveActivityLog } from "./activity-log.js";
21
21
  import { recoverTimedOutUnit, type RecoveryContext } from "./auto-timeout-recovery.js";
22
22
  import type { AutoSession } from "./auto/session.js";
23
+ import { getErrorMessage } from "./error-utils.js";
23
24
 
24
25
  export interface SupervisionContext {
25
26
  s: AutoSession;
@@ -127,7 +128,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
127
128
  );
128
129
  await pauseAuto(ctx, pi);
129
130
  } catch (err) {
130
- const message = err instanceof Error ? err.message : String(err);
131
+ const message = getErrorMessage(err);
131
132
  console.error(`[idle-watchdog] Unhandled error: ${message}`);
132
133
  try {
133
134
  ctx.ui.notify(`Idle watchdog error: ${message}`, "warning");
@@ -159,7 +160,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
159
160
  );
160
161
  await pauseAuto(ctx, pi);
161
162
  } catch (err) {
162
- const message = err instanceof Error ? err.message : String(err);
163
+ const message = getErrorMessage(err);
163
164
  console.error(`[hard-timeout] Unhandled error: ${message}`);
164
165
  try {
165
166
  ctx.ui.notify(`Hard timeout error: ${message}`, "warning");
@@ -24,6 +24,8 @@ import { writeVerificationJSON } from "./verification-evidence.js";
24
24
  import { removePersistedKey } from "./auto-recovery.js";
25
25
  import type { AutoSession, PendingVerificationRetry } from "./auto/session.js";
26
26
  import { join } from "node:path";
27
+ import { getErrorMessage } from "./error-utils.js";
28
+ import { parseUnitId } from "./unit-id.js";
27
29
 
28
30
  export interface VerificationContext {
29
31
  s: AutoSession;
@@ -57,10 +59,9 @@ export async function runPostUnitVerification(
57
59
  const prefs = effectivePrefs?.preferences;
58
60
 
59
61
  // Read task plan verify field
60
- const parts = s.currentUnit.id.split("/");
62
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
61
63
  let taskPlanVerify: string | undefined;
62
- if (parts.length >= 3) {
63
- const [mid, sid, tid] = parts;
64
+ if (mid && sid && tid) {
64
65
  const planFile = resolveSliceFile(s.basePath, mid, sid, "PLAN");
65
66
  if (planFile) {
66
67
  const planContent = await loadFile(planFile);
@@ -152,9 +153,8 @@ export async function runPostUnitVerification(
152
153
 
153
154
  // Write verification evidence JSON
154
155
  const attempt = s.verificationRetryCount.get(s.currentUnit.id) ?? 0;
155
- if (parts.length >= 3) {
156
+ if (mid && sid && tid) {
156
157
  try {
157
- const [mid, sid, tid] = parts;
158
158
  const sDir = resolveSlicePath(s.basePath, mid, sid);
159
159
  if (sDir) {
160
160
  const tasksDir = join(sDir, "tasks");
@@ -204,7 +204,7 @@ export async function runPostUnitVerification(
204
204
  try {
205
205
  await dispatchNextUnit(ctx, pi);
206
206
  } catch (retryDispatchErr) {
207
- const msg = retryDispatchErr instanceof Error ? retryDispatchErr.message : String(retryDispatchErr);
207
+ const msg = getErrorMessage(retryDispatchErr);
208
208
  ctx.ui.notify(`Verification retry dispatch error: ${msg}`, "error");
209
209
  startDispatchGapWatchdog(ctx, pi);
210
210
  }
@@ -6,7 +6,7 @@
6
6
  * manages create, enter, detect, and teardown for auto-mode worktrees.
7
7
  */
8
8
 
9
- import { existsSync, readFileSync, realpathSync, unlinkSync, statSync, rmSync } from "node:fs";
9
+ import { existsSync, readFileSync, realpathSync, unlinkSync, statSync, rmSync, readdirSync, cpSync, lstatSync as lstatSyncFn } from "node:fs";
10
10
  import { isAbsolute, join, sep } from "node:path";
11
11
  import { GSDError, GSD_IO_ERROR, GSD_GIT_ERROR } from "./errors.js";
12
12
  import { execSync, execFileSync } from "node:child_process";
@@ -38,12 +38,129 @@ import {
38
38
  nativeBranchDelete,
39
39
  nativeBranchExists,
40
40
  } from "./native-git-bridge.js";
41
+ import { getErrorMessage } from "./error-utils.js";
41
42
 
42
43
  // ─── Module State ──────────────────────────────────────────────────────────
43
44
 
44
45
  /** Original project root before chdir into auto-worktree. */
45
46
  let originalBase: string | null = null;
46
47
 
48
+ // ─── Worktree ↔ Main Repo Sync (#1311) ──────────────────────────────────────
49
+
50
+ /**
51
+ * Sync .gsd/ state from the main repo into the worktree.
52
+ *
53
+ * When .gsd/ is a symlink to the external state directory, both the main
54
+ * repo and worktree share the same directory — no sync needed.
55
+ *
56
+ * When .gsd/ is a real directory (e.g., git-tracked or manage_gitignore:false),
57
+ * the worktree has its own copy that may be stale. This function copies
58
+ * missing milestones, CONTEXT, ROADMAP, DECISIONS, REQUIREMENTS, and
59
+ * PROJECT files from the main repo's .gsd/ into the worktree's .gsd/.
60
+ *
61
+ * Only adds missing content — never overwrites existing files in the worktree
62
+ * (the worktree's execution state is authoritative for in-progress work).
63
+ */
64
+ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: string): { synced: string[] } {
65
+ const mainGsd = gsdRoot(mainBasePath);
66
+ const wtGsd = gsdRoot(worktreePath_);
67
+ const synced: string[] = [];
68
+
69
+ // If both resolve to the same directory (symlink), no sync needed
70
+ try {
71
+ const mainResolved = realpathSync(mainGsd);
72
+ const wtResolved = realpathSync(wtGsd);
73
+ if (mainResolved === wtResolved) return { synced };
74
+ } catch {
75
+ // Can't resolve — proceed with sync as a safety measure
76
+ }
77
+
78
+ if (!existsSync(mainGsd) || !existsSync(wtGsd)) return { synced };
79
+
80
+ // Sync root-level .gsd/ files (DECISIONS, REQUIREMENTS, PROJECT, KNOWLEDGE)
81
+ const rootFiles = ["DECISIONS.md", "REQUIREMENTS.md", "PROJECT.md", "KNOWLEDGE.md", "OVERRIDES.md"];
82
+ for (const f of rootFiles) {
83
+ const src = join(mainGsd, f);
84
+ const dst = join(wtGsd, f);
85
+ if (existsSync(src) && !existsSync(dst)) {
86
+ try {
87
+ cpSync(src, dst);
88
+ synced.push(f);
89
+ } catch { /* non-fatal */ }
90
+ }
91
+ }
92
+
93
+ // Sync milestones: copy entire milestone directories that are missing
94
+ const mainMilestonesDir = join(mainGsd, "milestones");
95
+ const wtMilestonesDir = join(wtGsd, "milestones");
96
+ if (existsSync(mainMilestonesDir) && existsSync(wtMilestonesDir)) {
97
+ try {
98
+ const mainMilestones = readdirSync(mainMilestonesDir, { withFileTypes: true })
99
+ .filter(d => d.isDirectory() && /^M\d{3}/.test(d.name))
100
+ .map(d => d.name);
101
+
102
+ for (const mid of mainMilestones) {
103
+ const srcDir = join(mainMilestonesDir, mid);
104
+ const dstDir = join(wtMilestonesDir, mid);
105
+
106
+ if (!existsSync(dstDir)) {
107
+ // Entire milestone missing from worktree — copy it
108
+ try {
109
+ cpSync(srcDir, dstDir, { recursive: true });
110
+ synced.push(`milestones/${mid}/`);
111
+ } catch { /* non-fatal */ }
112
+ } else {
113
+ // Milestone directory exists but may be missing files (stale snapshot).
114
+ // Sync individual top-level milestone files (CONTEXT, ROADMAP, RESEARCH, etc.)
115
+ try {
116
+ const srcFiles = readdirSync(srcDir).filter(f => f.endsWith(".md") || f.endsWith(".json"));
117
+ for (const f of srcFiles) {
118
+ const srcFile = join(srcDir, f);
119
+ const dstFile = join(dstDir, f);
120
+ if (!existsSync(dstFile)) {
121
+ try {
122
+ const srcStat = lstatSyncFn(srcFile);
123
+ if (srcStat.isFile()) {
124
+ cpSync(srcFile, dstFile);
125
+ synced.push(`milestones/${mid}/${f}`);
126
+ }
127
+ } catch { /* non-fatal */ }
128
+ }
129
+ }
130
+
131
+ // Sync slices directory if it exists in main but not in worktree
132
+ const srcSlicesDir = join(srcDir, "slices");
133
+ const dstSlicesDir = join(dstDir, "slices");
134
+ if (existsSync(srcSlicesDir) && !existsSync(dstSlicesDir)) {
135
+ try {
136
+ cpSync(srcSlicesDir, dstSlicesDir, { recursive: true });
137
+ synced.push(`milestones/${mid}/slices/`);
138
+ } catch { /* non-fatal */ }
139
+ } else if (existsSync(srcSlicesDir) && existsSync(dstSlicesDir)) {
140
+ // Both exist — sync missing slice directories
141
+ const srcSlices = readdirSync(srcSlicesDir, { withFileTypes: true })
142
+ .filter(d => d.isDirectory())
143
+ .map(d => d.name);
144
+ for (const sid of srcSlices) {
145
+ const srcSlice = join(srcSlicesDir, sid);
146
+ const dstSlice = join(dstSlicesDir, sid);
147
+ if (!existsSync(dstSlice)) {
148
+ try {
149
+ cpSync(srcSlice, dstSlice, { recursive: true });
150
+ synced.push(`milestones/${mid}/slices/${sid}/`);
151
+ } catch { /* non-fatal */ }
152
+ }
153
+ }
154
+ }
155
+ } catch { /* non-fatal */ }
156
+ }
157
+ }
158
+ } catch { /* non-fatal */ }
159
+ }
160
+
161
+ return { synced };
162
+ }
163
+
47
164
  // ─── Worktree Post-Create Hook (#597) ────────────────────────────────────────
48
165
 
49
166
  /**
@@ -81,7 +198,7 @@ export function runWorktreePostCreateHook(sourceDir: string, worktreeDir: string
81
198
  });
82
199
  return null;
83
200
  } catch (err) {
84
- const msg = err instanceof Error ? err.message : String(err);
201
+ const msg = getErrorMessage(err);
85
202
  return `Worktree post-create hook failed: ${msg}`;
86
203
  }
87
204
  }
@@ -124,6 +241,12 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
124
241
  // Ensure worktree shares external state via symlink
125
242
  ensureGsdSymlink(info.path);
126
243
 
244
+ // Sync .gsd/ state from main repo into the worktree (#1311).
245
+ // Even with the symlink, the worktree may have stale git-tracked files
246
+ // if .gsd/ is not gitignored. And on fresh create, the milestone files
247
+ // created on main since the branch point won't be in the worktree.
248
+ syncGsdStateToWorktree(basePath, info.path);
249
+
127
250
  // Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
128
251
  const hookError = runWorktreePostCreateHook(basePath, info.path);
129
252
  if (hookError) {
@@ -141,7 +264,7 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
141
264
  // Don't store originalBase -- caller can retry or clean up.
142
265
  throw new GSDError(
143
266
  GSD_IO_ERROR,
144
- `Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`,
267
+ `Auto-worktree created at ${info.path} but chdir failed: ${getErrorMessage(err)}`,
145
268
  );
146
269
  }
147
270
 
@@ -168,7 +291,7 @@ export function teardownAutoWorktree(
168
291
  } catch (err) {
169
292
  throw new GSDError(
170
293
  GSD_IO_ERROR,
171
- `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`,
294
+ `Failed to chdir back to ${originalBasePath} during teardown: ${getErrorMessage(err)}`,
172
295
  );
173
296
  }
174
297
 
@@ -266,6 +389,18 @@ export function enterAutoWorktree(basePath: string, milestoneId: string): string
266
389
  throw new GSDError(GSD_IO_ERROR, `Auto-worktree path ${p} exists but .git is unreadable`);
267
390
  }
268
391
 
392
+ // Ensure worktree shares external state via symlink (#1311).
393
+ // On resume (enterAutoWorktree), the symlink may be missing if it was
394
+ // created before ensureGsdSymlink existed, or the .gsd/ directory may be
395
+ // a stale git-tracked copy instead of a symlink. Refreshing here ensures
396
+ // the worktree sees the same milestone state as the main repo.
397
+ ensureGsdSymlink(p);
398
+
399
+ // Sync .gsd/ state from main repo into worktree (#1311).
400
+ // Covers the case where .gsd/ is a real directory (not symlinked) and
401
+ // milestones were created on main after the worktree was last used.
402
+ syncGsdStateToWorktree(basePath, p);
403
+
269
404
  const previousCwd = process.cwd();
270
405
 
271
406
  try {
@@ -274,7 +409,7 @@ export function enterAutoWorktree(basePath: string, milestoneId: string): string
274
409
  } catch (err) {
275
410
  throw new GSDError(
276
411
  GSD_IO_ERROR,
277
- `Failed to enter auto-worktree at ${p}: ${err instanceof Error ? err.message : String(err)}`,
412
+ `Failed to enter auto-worktree at ${p}: ${getErrorMessage(err)}`,
278
413
  );
279
414
  }
280
415