gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216

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 (135) hide show
  1. package/dist/bundled-resource-path.d.ts +8 -0
  2. package/dist/bundled-resource-path.js +14 -0
  3. package/dist/headless-query.js +6 -6
  4. package/dist/resources/extensions/gsd/auto/session.js +27 -32
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
  8. package/dist/resources/extensions/gsd/auto-loop.js +956 -0
  9. package/dist/resources/extensions/gsd/auto-observability.js +4 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
  11. package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
  12. package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
  13. package/dist/resources/extensions/gsd/auto-start.js +330 -309
  14. package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
  16. package/dist/resources/extensions/gsd/auto-timers.js +3 -4
  17. package/dist/resources/extensions/gsd/auto-verification.js +35 -73
  18. package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
  19. package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
  20. package/dist/resources/extensions/gsd/auto.js +283 -1013
  21. package/dist/resources/extensions/gsd/captures.js +10 -4
  22. package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  24. package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
  25. package/dist/resources/extensions/gsd/git-service.js +1 -1
  26. package/dist/resources/extensions/gsd/gsd-db.js +296 -151
  27. package/dist/resources/extensions/gsd/index.js +92 -228
  28. package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
  29. package/dist/resources/extensions/gsd/progress-score.js +61 -156
  30. package/dist/resources/extensions/gsd/quick.js +98 -122
  31. package/dist/resources/extensions/gsd/session-lock.js +13 -0
  32. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  33. package/dist/resources/extensions/gsd/undo.js +43 -48
  34. package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
  35. package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
  36. package/dist/resources/extensions/gsd/verification-gate.js +6 -35
  37. package/dist/resources/extensions/gsd/worktree-command.js +30 -24
  38. package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
  39. package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
  40. package/dist/resources/extensions/gsd/worktree.js +7 -44
  41. package/dist/tool-bootstrap.js +59 -11
  42. package/dist/worktree-cli.js +7 -7
  43. package/package.json +1 -1
  44. package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
  45. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/models.generated.js +735 -2588
  47. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  48. package/packages/pi-ai/src/models.generated.ts +1039 -2892
  49. package/packages/pi-coding-agent/package.json +1 -1
  50. package/pkg/package.json +1 -1
  51. package/src/resources/extensions/gsd/auto/session.ts +47 -30
  52. package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
  53. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
  54. package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
  55. package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
  56. package/src/resources/extensions/gsd/auto-observability.ts +4 -2
  57. package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
  58. package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
  59. package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
  60. package/src/resources/extensions/gsd/auto-start.ts +440 -354
  61. package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
  62. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
  63. package/src/resources/extensions/gsd/auto-timers.ts +3 -4
  64. package/src/resources/extensions/gsd/auto-verification.ts +76 -90
  65. package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
  66. package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
  67. package/src/resources/extensions/gsd/auto.ts +515 -1199
  68. package/src/resources/extensions/gsd/captures.ts +10 -4
  69. package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
  70. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  71. package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
  72. package/src/resources/extensions/gsd/git-service.ts +8 -1
  73. package/src/resources/extensions/gsd/gitignore.ts +4 -2
  74. package/src/resources/extensions/gsd/gsd-db.ts +375 -180
  75. package/src/resources/extensions/gsd/index.ts +104 -263
  76. package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
  77. package/src/resources/extensions/gsd/progress-score.ts +65 -200
  78. package/src/resources/extensions/gsd/quick.ts +121 -125
  79. package/src/resources/extensions/gsd/session-lock.ts +11 -0
  80. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  81. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
  82. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
  83. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  84. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
  85. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
  86. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
  87. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
  88. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
  89. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
  90. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  91. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
  92. package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
  93. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
  94. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
  95. package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
  96. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
  97. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
  98. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
  99. package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
  100. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
  101. package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
  102. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
  103. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
  104. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  105. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  106. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
  107. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
  108. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
  109. package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
  110. package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
  111. package/src/resources/extensions/gsd/types.ts +90 -81
  112. package/src/resources/extensions/gsd/undo.ts +42 -46
  113. package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
  114. package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
  115. package/src/resources/extensions/gsd/verification-gate.ts +6 -39
  116. package/src/resources/extensions/gsd/worktree-command.ts +36 -24
  117. package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
  118. package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
  119. package/src/resources/extensions/gsd/worktree.ts +7 -44
  120. package/dist/resources/extensions/gsd/auto-constants.js +0 -5
  121. package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
  122. package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
  123. package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
  124. package/src/resources/extensions/gsd/auto-constants.ts +0 -6
  125. package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
  126. package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
  127. package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
  128. package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
  129. package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
  130. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
  131. package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
  132. package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
  133. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
  134. package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
  135. package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
@@ -6,24 +6,40 @@
6
6
  * manages create, enter, detect, and teardown for auto-mode worktrees.
7
7
  */
8
8
 
9
- import { existsSync, readFileSync, realpathSync, unlinkSync, statSync, rmSync, readdirSync, cpSync, mkdirSync, lstatSync as lstatSyncFn } from "node:fs";
10
- import { isAbsolute, join, sep } from "node:path";
9
+ import {
10
+ existsSync,
11
+ cpSync,
12
+ readFileSync,
13
+ readdirSync,
14
+ mkdirSync,
15
+ realpathSync,
16
+ unlinkSync,
17
+ lstatSync as lstatSyncFn,
18
+ } from "node:fs";
19
+ import { isAbsolute, join } from "node:path";
11
20
  import { GSDError, GSD_IO_ERROR, GSD_GIT_ERROR } from "./errors.js";
21
+ import {
22
+ copyWorktreeDb,
23
+ reconcileWorktreeDb,
24
+ isDbAvailable,
25
+ } from "./gsd-db.js";
26
+ import { atomicWriteSync } from "./atomic-write.js";
12
27
  import { execSync, execFileSync } from "node:child_process";
28
+ import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
29
+ import { gsdRoot } from "./paths.js";
13
30
  import {
14
31
  createWorktree,
15
32
  removeWorktree,
16
33
  worktreePath,
17
34
  } from "./worktree-manager.js";
18
- import { detectWorktreeName, resolveGitHeadPath, nudgeGitBranchCache } from "./worktree.js";
19
- import { ensureGsdSymlink } from "./repo-identity.js";
20
35
  import {
21
- MergeConflictError,
22
- readIntegrationBranch,
23
- } from "./git-service.js";
36
+ detectWorktreeName,
37
+ resolveGitHeadPath,
38
+ nudgeGitBranchCache,
39
+ } from "./worktree.js";
40
+ import { MergeConflictError, readIntegrationBranch } from "./git-service.js";
24
41
  import { parseRoadmap } from "./files.js";
25
42
  import { loadEffectiveGSDPreferences } from "./preferences.js";
26
- import { gsdRoot } from "./paths.js";
27
43
  import {
28
44
  nativeGetCurrentBranch,
29
45
  nativeWorkingTreeStatus,
@@ -38,13 +54,28 @@ import {
38
54
  nativeBranchDelete,
39
55
  nativeBranchExists,
40
56
  } from "./native-git-bridge.js";
41
- import { getErrorMessage } from "./error-utils.js";
42
57
 
43
58
  // ─── Module State ──────────────────────────────────────────────────────────
44
59
 
45
60
  /** Original project root before chdir into auto-worktree. */
46
61
  let originalBase: string | null = null;
47
62
 
63
+ function clearProjectRootStateFiles(basePath: string, milestoneId: string): void {
64
+ const gsdDir = gsdRoot(basePath);
65
+ const transientFiles = [
66
+ join(gsdDir, "STATE.md"),
67
+ join(gsdDir, "auto.lock"),
68
+ join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
69
+ ];
70
+
71
+ for (const file of transientFiles) {
72
+ try {
73
+ unlinkSync(file);
74
+ } catch {
75
+ /* non-fatal — file may not exist */
76
+ }
77
+ }
78
+ }
48
79
  // ─── Worktree ↔ Main Repo Sync (#1311) ──────────────────────────────────────
49
80
 
50
81
  /**
@@ -61,7 +92,10 @@ let originalBase: string | null = null;
61
92
  * Only adds missing content — never overwrites existing files in the worktree
62
93
  * (the worktree's execution state is authoritative for in-progress work).
63
94
  */
64
- export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: string): { synced: string[] } {
95
+ export function syncGsdStateToWorktree(
96
+ mainBasePath: string,
97
+ worktreePath_: string,
98
+ ): { synced: string[] } {
65
99
  const mainGsd = gsdRoot(mainBasePath);
66
100
  const wtGsd = gsdRoot(worktreePath_);
67
101
  const synced: string[] = [];
@@ -78,7 +112,13 @@ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: stri
78
112
  if (!existsSync(mainGsd) || !existsSync(wtGsd)) return { synced };
79
113
 
80
114
  // Sync root-level .gsd/ files (DECISIONS, REQUIREMENTS, PROJECT, KNOWLEDGE)
81
- const rootFiles = ["DECISIONS.md", "REQUIREMENTS.md", "PROJECT.md", "KNOWLEDGE.md", "OVERRIDES.md"];
115
+ const rootFiles = [
116
+ "DECISIONS.md",
117
+ "REQUIREMENTS.md",
118
+ "PROJECT.md",
119
+ "KNOWLEDGE.md",
120
+ "OVERRIDES.md",
121
+ ];
82
122
  for (const f of rootFiles) {
83
123
  const src = join(mainGsd, f);
84
124
  const dst = join(wtGsd, f);
@@ -86,7 +126,9 @@ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: stri
86
126
  try {
87
127
  cpSync(src, dst);
88
128
  synced.push(f);
89
- } catch { /* non-fatal */ }
129
+ } catch {
130
+ /* non-fatal */
131
+ }
90
132
  }
91
133
  }
92
134
 
@@ -96,9 +138,11 @@ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: stri
96
138
  if (existsSync(mainMilestonesDir)) {
97
139
  try {
98
140
  mkdirSync(wtMilestonesDir, { recursive: true });
99
- const mainMilestones = readdirSync(mainMilestonesDir, { withFileTypes: true })
100
- .filter(d => d.isDirectory() && /^M\d{3}/.test(d.name))
101
- .map(d => d.name);
141
+ const mainMilestones = readdirSync(mainMilestonesDir, {
142
+ withFileTypes: true,
143
+ })
144
+ .filter((d) => d.isDirectory() && /^M\d{3}/.test(d.name))
145
+ .map((d) => d.name);
102
146
 
103
147
  for (const mid of mainMilestones) {
104
148
  const srcDir = join(mainMilestonesDir, mid);
@@ -109,12 +153,16 @@ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: stri
109
153
  try {
110
154
  cpSync(srcDir, dstDir, { recursive: true });
111
155
  synced.push(`milestones/${mid}/`);
112
- } catch { /* non-fatal */ }
156
+ } catch {
157
+ /* non-fatal */
158
+ }
113
159
  } else {
114
160
  // Milestone directory exists but may be missing files (stale snapshot).
115
161
  // Sync individual top-level milestone files (CONTEXT, ROADMAP, RESEARCH, etc.)
116
162
  try {
117
- const srcFiles = readdirSync(srcDir).filter(f => f.endsWith(".md") || f.endsWith(".json"));
163
+ const srcFiles = readdirSync(srcDir).filter(
164
+ (f) => f.endsWith(".md") || f.endsWith(".json"),
165
+ );
118
166
  for (const f of srcFiles) {
119
167
  const srcFile = join(srcDir, f);
120
168
  const dstFile = join(dstDir, f);
@@ -125,7 +173,9 @@ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: stri
125
173
  cpSync(srcFile, dstFile);
126
174
  synced.push(`milestones/${mid}/${f}`);
127
175
  }
128
- } catch { /* non-fatal */ }
176
+ } catch {
177
+ /* non-fatal */
178
+ }
129
179
  }
130
180
  }
131
181
 
@@ -136,12 +186,16 @@ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: stri
136
186
  try {
137
187
  cpSync(srcSlicesDir, dstSlicesDir, { recursive: true });
138
188
  synced.push(`milestones/${mid}/slices/`);
139
- } catch { /* non-fatal */ }
189
+ } catch {
190
+ /* non-fatal */
191
+ }
140
192
  } else if (existsSync(srcSlicesDir) && existsSync(dstSlicesDir)) {
141
193
  // Both exist — sync missing slice directories
142
- const srcSlices = readdirSync(srcSlicesDir, { withFileTypes: true })
143
- .filter(d => d.isDirectory())
144
- .map(d => d.name);
194
+ const srcSlices = readdirSync(srcSlicesDir, {
195
+ withFileTypes: true,
196
+ })
197
+ .filter((d) => d.isDirectory())
198
+ .map((d) => d.name);
145
199
  for (const sid of srcSlices) {
146
200
  const srcSlice = join(srcSlicesDir, sid);
147
201
  const dstSlice = join(dstSlicesDir, sid);
@@ -149,14 +203,20 @@ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: stri
149
203
  try {
150
204
  cpSync(srcSlice, dstSlice, { recursive: true });
151
205
  synced.push(`milestones/${mid}/slices/${sid}/`);
152
- } catch { /* non-fatal */ }
206
+ } catch {
207
+ /* non-fatal */
208
+ }
153
209
  }
154
210
  }
155
211
  }
156
- } catch { /* non-fatal */ }
212
+ } catch {
213
+ /* non-fatal */
214
+ }
157
215
  }
158
216
  }
159
- } catch { /* non-fatal */ }
217
+ } catch {
218
+ /* non-fatal */
219
+ }
160
220
  }
161
221
 
162
222
  return { synced };
@@ -170,7 +230,11 @@ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: stri
170
230
  * Only syncs .gsd/milestones/ content — root-level files (DECISIONS, REQUIREMENTS, etc.)
171
231
  * are handled by the merge itself.
172
232
  */
173
- export function syncWorktreeStateBack(mainBasePath: string, worktreePath: string, milestoneId: string): { synced: string[] } {
233
+ export function syncWorktreeStateBack(
234
+ mainBasePath: string,
235
+ worktreePath: string,
236
+ milestoneId: string,
237
+ ): { synced: string[] } {
174
238
  const mainGsd = gsdRoot(mainBasePath);
175
239
  const wtGsd = gsdRoot(worktreePath);
176
240
  const synced: string[] = [];
@@ -199,40 +263,53 @@ export function syncWorktreeStateBack(mainBasePath: string, worktreePath: string
199
263
  try {
200
264
  cpSync(src, dst, { force: true });
201
265
  synced.push(`milestones/${milestoneId}/${entry.name}`);
202
- } catch { /* non-fatal */ }
266
+ } catch {
267
+ /* non-fatal */
268
+ }
203
269
  }
204
270
  }
205
- } catch { /* non-fatal */ }
271
+ } catch {
272
+ /* non-fatal */
273
+ }
206
274
 
207
275
  // Sync slice-level files (summaries, UATs)
208
276
  const wtSlicesDir = join(wtMilestoneDir, "slices");
209
277
  const mainSlicesDir = join(mainMilestoneDir, "slices");
210
278
  if (existsSync(wtSlicesDir)) {
211
279
  try {
212
- for (const sliceEntry of readdirSync(wtSlicesDir, { withFileTypes: true })) {
280
+ for (const sliceEntry of readdirSync(wtSlicesDir, {
281
+ withFileTypes: true,
282
+ })) {
213
283
  if (!sliceEntry.isDirectory()) continue;
214
284
  const sid = sliceEntry.name;
215
285
  const wtSliceDir = join(wtSlicesDir, sid);
216
286
  const mainSliceDir = join(mainSlicesDir, sid);
217
287
  mkdirSync(mainSliceDir, { recursive: true });
218
288
 
219
- for (const fileEntry of readdirSync(wtSliceDir, { withFileTypes: true })) {
289
+ for (const fileEntry of readdirSync(wtSliceDir, {
290
+ withFileTypes: true,
291
+ })) {
220
292
  if (fileEntry.isFile() && fileEntry.name.endsWith(".md")) {
221
293
  const src = join(wtSliceDir, fileEntry.name);
222
294
  const dst = join(mainSliceDir, fileEntry.name);
223
295
  try {
224
296
  cpSync(src, dst, { force: true });
225
- synced.push(`milestones/${milestoneId}/slices/${sid}/${fileEntry.name}`);
226
- } catch { /* non-fatal */ }
297
+ synced.push(
298
+ `milestones/${milestoneId}/slices/${sid}/${fileEntry.name}`,
299
+ );
300
+ } catch {
301
+ /* non-fatal */
302
+ }
227
303
  }
228
304
  }
229
305
  }
230
- } catch { /* non-fatal */ }
306
+ } catch {
307
+ /* non-fatal */
308
+ }
231
309
  }
232
310
 
233
311
  return { synced };
234
312
  }
235
-
236
313
  // ─── Worktree Post-Create Hook (#597) ────────────────────────────────────────
237
314
 
238
315
  /**
@@ -243,7 +320,11 @@ export function syncWorktreeStateBack(mainBasePath: string, worktreePath: string
243
320
  * Reads the hook path from git.worktree_post_create in preferences.
244
321
  * Pass hookPath directly to bypass preference loading (useful for testing).
245
322
  */
246
- export function runWorktreePostCreateHook(sourceDir: string, worktreeDir: string, hookPath?: string): string | null {
323
+ export function runWorktreePostCreateHook(
324
+ sourceDir: string,
325
+ worktreeDir: string,
326
+ hookPath?: string,
327
+ ): string | null {
247
328
  if (hookPath === undefined) {
248
329
  const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
249
330
  hookPath = prefs?.worktree_post_create;
@@ -270,7 +351,7 @@ export function runWorktreePostCreateHook(sourceDir: string, worktreeDir: string
270
351
  });
271
352
  return null;
272
353
  } catch (err) {
273
- const msg = getErrorMessage(err);
354
+ const msg = err instanceof Error ? err.message : String(err);
274
355
  return `Worktree post-create hook failed: ${msg}`;
275
356
  }
276
357
  }
@@ -291,7 +372,110 @@ export function autoWorktreeBranch(milestoneId: string): string {
291
372
  * to prevent split-brain.
292
373
  */
293
374
 
294
- export function createAutoWorktree(basePath: string, milestoneId: string): string {
375
+ /**
376
+ * Forward-merge plan checkbox state from the project root into a freshly
377
+ * re-attached worktree (#778).
378
+ *
379
+ * When auto-mode stops via crash (not graceful stop), the milestone branch
380
+ * HEAD may be behind the filesystem state at the project root because
381
+ * syncStateToProjectRoot() runs after every task completion but the final
382
+ * git commit may not have happened before the crash. On restart the worktree
383
+ * is re-attached to the branch HEAD, which has [ ] for the crashed task,
384
+ * causing verifyExpectedArtifact() to fail and triggering an infinite
385
+ * dispatch/skip loop.
386
+ *
387
+ * Fix: after re-attaching, read every *.md plan file in the milestone
388
+ * directory at the project root and apply any [x] checkbox states that are
389
+ * ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
390
+ *
391
+ * This is safe because syncStateToProjectRoot() is the authoritative source
392
+ * of post-task state at the project root — it writes the same [x] the LLM
393
+ * produced, then the auto-commit follows. If the commit never happened, the
394
+ * filesystem copy is still valid and correct.
395
+ */
396
+ function reconcilePlanCheckboxes(
397
+ projectRoot: string,
398
+ wtPath: string,
399
+ milestoneId: string,
400
+ ): void {
401
+ const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
402
+ const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
403
+ if (!existsSync(srcMilestone) || !existsSync(dstMilestone)) return;
404
+
405
+ // Walk all markdown files in the milestone directory (plans, summaries, etc.)
406
+ function walkMd(dir: string): string[] {
407
+ const results: string[] = [];
408
+ try {
409
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
410
+ const full = join(dir, entry.name);
411
+ if (entry.isDirectory()) {
412
+ results.push(...walkMd(full));
413
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
414
+ results.push(full);
415
+ }
416
+ }
417
+ } catch {
418
+ /* non-fatal */
419
+ }
420
+ return results;
421
+ }
422
+
423
+ for (const srcFile of walkMd(srcMilestone)) {
424
+ const rel = srcFile.slice(srcMilestone.length);
425
+ const dstFile = dstMilestone + rel;
426
+ if (!existsSync(dstFile)) continue; // only reconcile existing files
427
+
428
+ let srcContent: string;
429
+ let dstContent: string;
430
+ try {
431
+ srcContent = readFileSync(srcFile, "utf-8");
432
+ dstContent = readFileSync(dstFile, "utf-8");
433
+ } catch {
434
+ continue;
435
+ }
436
+
437
+ if (srcContent === dstContent) continue;
438
+
439
+ // Extract all checked task IDs from the source (project root)
440
+ // Pattern: - [x] **T<id>: or - [x] **S<id>: (case-insensitive x)
441
+ const checkedRe = /^- \[[xX]\] \*\*([TS]\d+):/gm;
442
+ const srcChecked = new Set<string>();
443
+ for (const m of srcContent.matchAll(checkedRe)) srcChecked.add(m[1]);
444
+
445
+ if (srcChecked.size === 0) continue;
446
+
447
+ // Forward-apply: replace [ ] → [x] for any IDs that are checked in src
448
+ let updated = dstContent;
449
+ let changed = false;
450
+ for (const id of srcChecked) {
451
+ const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
452
+ const uncheckedRe = new RegExp(
453
+ `^(- )\\[ \\]( \\*\\*${escapedId}:)`,
454
+ "gm",
455
+ );
456
+ if (uncheckedRe.test(updated)) {
457
+ updated = updated.replace(
458
+ new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm"),
459
+ "$1[x]$2",
460
+ );
461
+ changed = true;
462
+ }
463
+ }
464
+
465
+ if (changed) {
466
+ try {
467
+ atomicWriteSync(dstFile, updated, "utf-8");
468
+ } catch {
469
+ /* non-fatal */
470
+ }
471
+ }
472
+ }
473
+ }
474
+
475
+ export function createAutoWorktree(
476
+ basePath: string,
477
+ milestoneId: string,
478
+ ): string {
295
479
  const branch = autoWorktreeBranch(milestoneId);
296
480
 
297
481
  // Check if the milestone branch already exists — it survives auto-mode
@@ -303,21 +487,46 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
303
487
  let info: { name: string; path: string; branch: string; exists: boolean };
304
488
  if (branchExists) {
305
489
  // Re-attach worktree to the existing milestone branch (preserving commits)
306
- info = createWorktree(basePath, milestoneId, { branch, reuseExistingBranch: true });
490
+ info = createWorktree(basePath, milestoneId, {
491
+ branch,
492
+ reuseExistingBranch: true,
493
+ });
307
494
  } else {
308
495
  // Fresh start — create branch from integration branch
309
- const integrationBranch = readIntegrationBranch(basePath, milestoneId) ?? undefined;
310
- info = createWorktree(basePath, milestoneId, { branch, startPoint: integrationBranch });
496
+ const integrationBranch =
497
+ readIntegrationBranch(basePath, milestoneId) ?? undefined;
498
+ info = createWorktree(basePath, milestoneId, {
499
+ branch,
500
+ startPoint: integrationBranch,
501
+ });
311
502
  }
312
503
 
313
- // Ensure worktree shares external state via symlink
314
- ensureGsdSymlink(info.path);
315
-
316
- // Sync .gsd/ state from main repo into the worktree (#1311).
317
- // Even with the symlink, the worktree may have stale git-tracked files
318
- // if .gsd/ is not gitignored. And on fresh create, the milestone files
319
- // created on main since the branch point won't be in the worktree.
320
- syncGsdStateToWorktree(basePath, info.path);
504
+ // Copy .gsd/ planning artifacts from the source repo into the new worktree.
505
+ // Worktrees are fresh git checkouts — untracked files don't carry over.
506
+ // Planning artifacts may be untracked if the project's .gitignore had a
507
+ // blanket .gsd/ rule (pre-v2.14.0). Without this copy, auto-mode loops
508
+ // on plan-slice because the plan file doesn't exist in the worktree.
509
+ //
510
+ // IMPORTANT: Skip when re-attaching to an existing branch (#759).
511
+ // The branch checkout already has committed artifacts with correct state
512
+ // (e.g. [x] for completed slices). Copying from the project root would
513
+ // overwrite them with stale data ([ ] checkboxes) because the root is
514
+ // not always fully synced.
515
+ if (!branchExists) {
516
+ copyPlanningArtifacts(basePath, info.path);
517
+ } else {
518
+ // Re-attaching to an existing branch: forward-merge any plan checkpoint
519
+ // state from the project root into the worktree (#778).
520
+ //
521
+ // If auto-mode stopped via crash, the milestone branch HEAD may lag behind
522
+ // the project root filesystem because syncStateToProjectRoot() ran after
523
+ // task completion but the auto-commit never fired. On restart the worktree
524
+ // is re-created from the branch HEAD (which has [ ] for the crashed task),
525
+ // causing verifyExpectedArtifact() to return false → stale-key eviction →
526
+ // infinite dispatch/skip loop. Reconciling here ensures the worktree sees
527
+ // the same [x] state that syncStateToProjectRoot() wrote to the root.
528
+ reconcilePlanCheckboxes(basePath, info.path, milestoneId);
529
+ }
321
530
 
322
531
  // Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
323
532
  const hookError = runWorktreePostCreateHook(basePath, info.path);
@@ -336,7 +545,7 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
336
545
  // Don't store originalBase -- caller can retry or clean up.
337
546
  throw new GSDError(
338
547
  GSD_IO_ERROR,
339
- `Auto-worktree created at ${info.path} but chdir failed: ${getErrorMessage(err)}`,
548
+ `Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`,
340
549
  );
341
550
  }
342
551
 
@@ -344,6 +553,49 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
344
553
  return info.path;
345
554
  }
346
555
 
556
+ /**
557
+ * Copy .gsd/ planning artifacts from source repo to a new worktree.
558
+ * Copies milestones/, DECISIONS.md, REQUIREMENTS.md, PROJECT.md, QUEUE.md,
559
+ * STATE.md, KNOWLEDGE.md, and OVERRIDES.md.
560
+ * Skips runtime files (auto.lock, metrics.json, etc.) and the worktrees/ dir.
561
+ * Best-effort — failures are non-fatal since auto-mode can recreate artifacts.
562
+ */
563
+ function copyPlanningArtifacts(srcBase: string, wtPath: string): void {
564
+ const srcGsd = join(srcBase, ".gsd");
565
+ const dstGsd = join(wtPath, ".gsd");
566
+ if (!existsSync(srcGsd)) return;
567
+
568
+ // Copy milestones/ directory (planning files, roadmaps, plans, research)
569
+ safeCopyRecursive(join(srcGsd, "milestones"), join(dstGsd, "milestones"), {
570
+ force: true,
571
+ filter: (src) => !src.endsWith("-META.json"),
572
+ });
573
+
574
+ // Copy top-level planning files
575
+ for (const file of [
576
+ "DECISIONS.md",
577
+ "REQUIREMENTS.md",
578
+ "PROJECT.md",
579
+ "QUEUE.md",
580
+ "STATE.md",
581
+ "KNOWLEDGE.md",
582
+ "OVERRIDES.md",
583
+ ]) {
584
+ safeCopy(join(srcGsd, file), join(dstGsd, file), { force: true });
585
+ }
586
+
587
+ // Copy gsd.db if present in source
588
+ const srcDb = join(srcGsd, "gsd.db");
589
+ const destDb = join(dstGsd, "gsd.db");
590
+ if (existsSync(srcDb)) {
591
+ try {
592
+ copyWorktreeDb(srcDb, destDb);
593
+ } catch {
594
+ /* non-fatal */
595
+ }
596
+ }
597
+ }
598
+
347
599
  /**
348
600
  * Teardown an auto-worktree: chdir back to original base, then remove
349
601
  * the worktree and its branch.
@@ -363,12 +615,15 @@ export function teardownAutoWorktree(
363
615
  } catch (err) {
364
616
  throw new GSDError(
365
617
  GSD_IO_ERROR,
366
- `Failed to chdir back to ${originalBasePath} during teardown: ${getErrorMessage(err)}`,
618
+ `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`,
367
619
  );
368
620
  }
369
621
 
370
622
  nudgeGitBranchCache(previousCwd);
371
- removeWorktree(originalBasePath, milestoneId, { branch, deleteBranch: !preserveBranch });
623
+ removeWorktree(originalBasePath, milestoneId, {
624
+ branch,
625
+ deleteBranch: !preserveBranch,
626
+ });
372
627
  }
373
628
 
374
629
  /**
@@ -376,36 +631,13 @@ export function teardownAutoWorktree(
376
631
  * Checks both module state and git branch prefix.
377
632
  */
378
633
  export function isInAutoWorktree(basePath: string): boolean {
634
+ if (!originalBase) return false;
379
635
  const cwd = process.cwd();
380
-
381
- // Primary check: use originalBase if available (fast path)
382
- if (originalBase) {
383
- const resolvedBase = existsSync(basePath) ? realpathSync(basePath) : basePath;
384
- const wtDir = join(gsdRoot(resolvedBase), "worktrees");
385
- if (!cwd.startsWith(wtDir)) return false;
386
- const branch = nativeGetCurrentBranch(cwd);
387
- return branch.startsWith("milestone/");
388
- }
389
-
390
- // Fallback: infer worktree status structurally when originalBase is null
391
- // (happens after session restart where module-level state is lost, #1120).
392
- // Check if cwd is inside a .gsd/worktrees/ directory and has a .git file
393
- // (worktree marker) pointing to the main repo.
394
- const worktreeMarker = join(cwd, ".git");
395
- if (!existsSync(worktreeMarker)) return false;
396
- try {
397
- const stat = statSync(worktreeMarker);
398
- if (stat.isDirectory()) return false; // Main repo has .git dir, not file
399
- // Worktrees have a .git file with "gitdir: ..." pointing to the main repo
400
- const gitContent = readFileSync(worktreeMarker, "utf-8").trim();
401
- if (!gitContent.startsWith("gitdir:")) return false;
402
- // Verify we're inside a GSD-managed worktree
403
- if (!detectWorktreeName(cwd)) return false;
404
- const branch = nativeGetCurrentBranch(cwd);
405
- return branch.startsWith("milestone/");
406
- } catch {
407
- return false;
408
- }
636
+ const resolvedBase = existsSync(basePath) ? realpathSync(basePath) : basePath;
637
+ const wtDir = join(resolvedBase, ".gsd", "worktrees");
638
+ if (!cwd.startsWith(wtDir)) return false;
639
+ const branch = nativeGetCurrentBranch(cwd);
640
+ return branch.startsWith("milestone/");
409
641
  }
410
642
 
411
643
  /**
@@ -416,7 +648,10 @@ export function isInAutoWorktree(basePath: string): boolean {
416
648
  * gitdir: pointer) rather than just a stray directory. This prevents
417
649
  * mis-detection of leftover directories as active worktrees (#695).
418
650
  */
419
- export function getAutoWorktreePath(basePath: string, milestoneId: string): string | null {
651
+ export function getAutoWorktreePath(
652
+ basePath: string,
653
+ milestoneId: string,
654
+ ): string | null {
420
655
  const p = worktreePath(basePath, milestoneId);
421
656
  if (!existsSync(p)) return null;
422
657
 
@@ -440,39 +675,42 @@ export function getAutoWorktreePath(basePath: string, milestoneId: string): stri
440
675
  *
441
676
  * Atomic: chdir + originalBase update in same try block.
442
677
  */
443
- export function enterAutoWorktree(basePath: string, milestoneId: string): string {
678
+ export function enterAutoWorktree(
679
+ basePath: string,
680
+ milestoneId: string,
681
+ ): string {
444
682
  const p = worktreePath(basePath, milestoneId);
445
683
  if (!existsSync(p)) {
446
- throw new GSDError(GSD_IO_ERROR, `Auto-worktree for ${milestoneId} does not exist at ${p}`);
684
+ throw new GSDError(
685
+ GSD_IO_ERROR,
686
+ `Auto-worktree for ${milestoneId} does not exist at ${p}`,
687
+ );
447
688
  }
448
689
 
449
690
  // Validate this is a real git worktree, not a stray directory (#695)
450
691
  const gitPath = join(p, ".git");
451
692
  if (!existsSync(gitPath)) {
452
- throw new GSDError(GSD_GIT_ERROR, `Auto-worktree path ${p} exists but is not a git worktree (no .git)`);
693
+ throw new GSDError(
694
+ GSD_GIT_ERROR,
695
+ `Auto-worktree path ${p} exists but is not a git worktree (no .git)`,
696
+ );
453
697
  }
454
698
  try {
455
699
  const content = readFileSync(gitPath, "utf8").trim();
456
700
  if (!content.startsWith("gitdir: ")) {
457
- throw new GSDError(GSD_GIT_ERROR, `Auto-worktree path ${p} has a .git but it is not a worktree gitdir pointer`);
701
+ throw new GSDError(
702
+ GSD_GIT_ERROR,
703
+ `Auto-worktree path ${p} has a .git but it is not a worktree gitdir pointer`,
704
+ );
458
705
  }
459
706
  } catch (err) {
460
707
  if (err instanceof Error && err.message.includes("worktree")) throw err;
461
- throw new GSDError(GSD_IO_ERROR, `Auto-worktree path ${p} exists but .git is unreadable`);
708
+ throw new GSDError(
709
+ GSD_IO_ERROR,
710
+ `Auto-worktree path ${p} exists but .git is unreadable`,
711
+ );
462
712
  }
463
713
 
464
- // Ensure worktree shares external state via symlink (#1311).
465
- // On resume (enterAutoWorktree), the symlink may be missing if it was
466
- // created before ensureGsdSymlink existed, or the .gsd/ directory may be
467
- // a stale git-tracked copy instead of a symlink. Refreshing here ensures
468
- // the worktree sees the same milestone state as the main repo.
469
- ensureGsdSymlink(p);
470
-
471
- // Sync .gsd/ state from main repo into worktree (#1311).
472
- // Covers the case where .gsd/ is a real directory (not symlinked) and
473
- // milestones were created on main after the worktree was last used.
474
- syncGsdStateToWorktree(basePath, p);
475
-
476
714
  const previousCwd = process.cwd();
477
715
 
478
716
  try {
@@ -481,7 +719,7 @@ export function enterAutoWorktree(basePath: string, milestoneId: string): string
481
719
  } catch (err) {
482
720
  throw new GSDError(
483
721
  GSD_IO_ERROR,
484
- `Failed to enter auto-worktree at ${p}: ${getErrorMessage(err)}`,
722
+ `Failed to enter auto-worktree at ${p}: ${err instanceof Error ? err.message : String(err)}`,
485
723
  );
486
724
  }
487
725
 
@@ -504,8 +742,10 @@ export function getActiveAutoWorktreeContext(): {
504
742
  } | null {
505
743
  if (!originalBase) return null;
506
744
  const cwd = process.cwd();
507
- const resolvedBase = existsSync(originalBase) ? realpathSync(originalBase) : originalBase;
508
- const wtDir = join(gsdRoot(resolvedBase), "worktrees");
745
+ const resolvedBase = existsSync(originalBase)
746
+ ? realpathSync(originalBase)
747
+ : originalBase;
748
+ const wtDir = join(resolvedBase, ".gsd", "worktrees");
509
749
  if (!cwd.startsWith(wtDir)) return null;
510
750
  const worktreeName = detectWorktreeName(cwd);
511
751
  if (!worktreeName) return null;
@@ -529,7 +769,10 @@ function autoCommitDirtyState(cwd: string): boolean {
529
769
  const status = nativeWorkingTreeStatus(cwd);
530
770
  if (!status) return false;
531
771
  nativeAddAll(cwd);
532
- const result = nativeCommit(cwd, "chore: auto-commit before milestone merge");
772
+ const result = nativeCommit(
773
+ cwd,
774
+ "chore: auto-commit before milestone merge",
775
+ );
533
776
  return result !== null;
534
777
  } catch {
535
778
  return false;
@@ -565,59 +808,53 @@ export function mergeMilestoneToMain(
565
808
  // 1. Auto-commit dirty state in worktree before leaving
566
809
  autoCommitDirtyState(worktreeCwd);
567
810
 
811
+ // Reconcile worktree DB into main DB before leaving worktree context
812
+ if (isDbAvailable()) {
813
+ try {
814
+ const worktreeDbPath = join(worktreeCwd, ".gsd", "gsd.db");
815
+ const mainDbPath = join(originalBasePath_, ".gsd", "gsd.db");
816
+ reconcileWorktreeDb(mainDbPath, worktreeDbPath);
817
+ } catch {
818
+ /* non-fatal */
819
+ }
820
+ }
821
+
568
822
  // 2. Parse roadmap for slice listing
569
823
  const roadmap = parseRoadmap(roadmapContent);
570
- const completedSlices = roadmap.slices.filter(s => s.done);
824
+ const completedSlices = roadmap.slices.filter((s) => s.done);
571
825
 
572
826
  // 3. chdir to original base
573
827
  const previousCwd = process.cwd();
574
828
  process.chdir(originalBasePath_);
575
829
 
576
- // 3a. Auto-commit any dirty state in the project root. Without this, the
577
- // squash merge can fail with "Your local changes would be overwritten" (#1127).
578
- autoCommitDirtyState(originalBasePath_);
579
-
580
- // 3b. Remove untracked .gsd/ runtime files that syncStateToProjectRoot copied.
581
- // Only clean specific runtime files — NEVER touch milestones/, decisions, or
582
- // other planning artifacts that represent user work (#1250).
583
- const runtimeFilesToClean = ["STATE.md", "completed-units.json", "auto.lock", "gsd.db"];
584
- for (const f of runtimeFilesToClean) {
585
- const p = join(originalBasePath_, ".gsd", f);
586
- try { if (existsSync(p)) unlinkSync(p); } catch { /* non-fatal */ }
587
- }
588
- try {
589
- const runtimeDir = join(originalBasePath_, ".gsd", "runtime");
590
- if (existsSync(runtimeDir)) rmSync(runtimeDir, { recursive: true, force: true });
591
- } catch { /* non-fatal */ }
592
-
593
830
  // 4. Resolve integration branch — prefer milestone metadata, fall back to preferences / "main"
594
831
  const prefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
595
- const integrationBranch = readIntegrationBranch(originalBasePath_, milestoneId);
832
+ const integrationBranch = readIntegrationBranch(
833
+ originalBasePath_,
834
+ milestoneId,
835
+ );
596
836
  const mainBranch = integrationBranch ?? prefs.main_branch ?? "main";
597
837
 
838
+ // Remove transient project-root state files before any branch or merge
839
+ // operation. Untracked milestone metadata can otherwise block squash merges.
840
+ clearProjectRootStateFiles(originalBasePath_, milestoneId);
841
+
598
842
  // 5. Checkout integration branch (skip if already current — avoids git error
599
843
  // when main is already checked out in the project-root worktree, #757)
600
844
  const currentBranchAtBase = nativeGetCurrentBranch(originalBasePath_);
601
845
  if (currentBranchAtBase !== mainBranch) {
602
- // Remove untracked .gsd/ state files that may conflict with the branch
603
- // being checked out. These are regenerated by doctor/rebuildState and
604
- // are not meaningful in the main working tree — the worktree had the
605
- // real state. Without this, `git checkout main` fails with
606
- // "Your local changes would be overwritten" (#827).
607
- const gsdStateFiles = ["STATE.md", "completed-units.json", "auto.lock"];
608
- for (const f of gsdStateFiles) {
609
- const p = join(gsdRoot(originalBasePath_), f);
610
- try { unlinkSync(p); } catch { /* non-fatal — file may not exist */ }
611
- }
612
846
  nativeCheckoutBranch(originalBasePath_, mainBranch);
613
847
  }
614
848
 
615
849
  // 6. Build rich commit message
616
- const milestoneTitle = roadmap.title.replace(/^M\d+:\s*/, "").trim() || milestoneId;
850
+ const milestoneTitle =
851
+ roadmap.title.replace(/^M\d+:\s*/, "").trim() || milestoneId;
617
852
  const subject = `feat(${milestoneId}): ${milestoneTitle}`;
618
853
  let body = "";
619
854
  if (completedSlices.length > 0) {
620
- const sliceLines = completedSlices.map(s => `- ${s.id}: ${s.title}`).join("\n");
855
+ const sliceLines = completedSlices
856
+ .map((s) => `- ${s.id}: ${s.title}`)
857
+ .join("\n");
621
858
  body = `\n\nCompleted slices:\n${sliceLines}\n\nBranch: ${milestoneBranch}`;
622
859
  }
623
860
  const commitMessage = subject + body;
@@ -627,17 +864,20 @@ export function mergeMilestoneToMain(
627
864
 
628
865
  if (!mergeResult.success) {
629
866
  // Check for conflicts — use merge result first, fall back to nativeConflictFiles
630
- const conflictedFiles = mergeResult.conflicts.length > 0
631
- ? mergeResult.conflicts
632
- : nativeConflictFiles(originalBasePath_);
867
+ const conflictedFiles =
868
+ mergeResult.conflicts.length > 0
869
+ ? mergeResult.conflicts
870
+ : nativeConflictFiles(originalBasePath_);
633
871
 
634
872
  if (conflictedFiles.length > 0) {
635
873
  // Separate .gsd/ state file conflicts from real code conflicts.
636
- // GSD state files (STATE.md, completed-units.json, auto.lock, etc.)
874
+ // GSD state files (STATE.md, auto.lock, etc.)
637
875
  // diverge between branches during normal operation — always prefer the
638
876
  // milestone branch version since it has the latest execution state.
639
- const gsdConflicts = conflictedFiles.filter(f => f.startsWith(".gsd/"));
640
- const codeConflicts = conflictedFiles.filter(f => !f.startsWith(".gsd/"));
877
+ const gsdConflicts = conflictedFiles.filter((f) => f.startsWith(".gsd/"));
878
+ const codeConflicts = conflictedFiles.filter(
879
+ (f) => !f.startsWith(".gsd/"),
880
+ );
641
881
 
642
882
  // Auto-resolve .gsd/ conflicts by accepting the milestone branch version
643
883
  if (gsdConflicts.length > 0) {
@@ -655,7 +895,12 @@ export function mergeMilestoneToMain(
655
895
 
656
896
  // If there are still non-.gsd conflicts, escalate
657
897
  if (codeConflicts.length > 0) {
658
- throw new MergeConflictError(codeConflicts, "squash", milestoneBranch, mainBranch);
898
+ throw new MergeConflictError(
899
+ codeConflicts,
900
+ "squash",
901
+ milestoneBranch,
902
+ mainBranch,
903
+ );
659
904
  }
660
905
  }
661
906
  // No conflicts detected — possibly "already up to date", fall through to commit
@@ -710,7 +955,10 @@ export function mergeMilestoneToMain(
710
955
 
711
956
  // 10. Remove worktree directory first (must happen before branch deletion)
712
957
  try {
713
- removeWorktree(originalBasePath_, milestoneId, { branch: null as unknown as string, deleteBranch: false });
958
+ removeWorktree(originalBasePath_, milestoneId, {
959
+ branch: null as unknown as string,
960
+ deleteBranch: false,
961
+ });
714
962
  } catch {
715
963
  // Best-effort -- worktree dir may already be gone
716
964
  }