gsd-pi 2.23.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +12 -3
  3. package/dist/headless.d.ts +4 -0
  4. package/dist/headless.js +118 -10
  5. package/dist/help-text.js +22 -7
  6. package/dist/models-resolver.d.ts +0 -11
  7. package/dist/models-resolver.js +0 -15
  8. package/dist/resource-loader.d.ts +0 -1
  9. package/dist/resource-loader.js +64 -18
  10. package/dist/resources/GSD-WORKFLOW.md +12 -9
  11. package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
  12. package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
  13. package/dist/resources/extensions/gsd/activity-log.ts +5 -3
  14. package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
  15. package/dist/resources/extensions/gsd/auto-prompts.ts +87 -0
  16. package/dist/resources/extensions/gsd/auto-recovery.ts +41 -2
  17. package/dist/resources/extensions/gsd/auto-worktree.ts +134 -4
  18. package/dist/resources/extensions/gsd/auto.ts +307 -77
  19. package/dist/resources/extensions/gsd/cache.ts +3 -1
  20. package/dist/resources/extensions/gsd/commands.ts +176 -10
  21. package/dist/resources/extensions/gsd/complexity.ts +1 -0
  22. package/dist/resources/extensions/gsd/dashboard-overlay.ts +38 -0
  23. package/dist/resources/extensions/gsd/doctor.ts +58 -11
  24. package/dist/resources/extensions/gsd/exit-command.ts +2 -2
  25. package/dist/resources/extensions/gsd/git-service.ts +74 -14
  26. package/dist/resources/extensions/gsd/gitignore.ts +1 -0
  27. package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
  28. package/dist/resources/extensions/gsd/guided-flow.ts +109 -12
  29. package/dist/resources/extensions/gsd/index.ts +48 -2
  30. package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
  31. package/dist/resources/extensions/gsd/memory-store.ts +441 -0
  32. package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
  33. package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  34. package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
  35. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  36. package/dist/resources/extensions/gsd/preferences.ts +65 -1
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  39. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -4
  40. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  41. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
  48. package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
  49. package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
  50. package/dist/resources/extensions/gsd/state.ts +72 -30
  51. package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  52. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  53. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  54. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
  55. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  56. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  57. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  58. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  59. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  60. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  62. package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  63. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  64. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  65. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  66. package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  67. package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  68. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  69. package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  70. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  71. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  72. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  73. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  74. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  75. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  76. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  77. package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  78. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  79. package/dist/resources/extensions/gsd/types.ts +15 -1
  80. package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
  81. package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  82. package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
  83. package/dist/resources/extensions/gsd/worktree.ts +9 -2
  84. package/dist/resources/extensions/search-the-web/native-search.ts +15 -5
  85. package/dist/resources/extensions/subagent/index.ts +5 -0
  86. package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
  87. package/dist/update-check.d.ts +9 -0
  88. package/dist/update-check.js +97 -0
  89. package/package.json +6 -1
  90. package/packages/pi-agent-core/dist/agent-loop.js +2 -0
  91. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  92. package/packages/pi-agent-core/src/agent-loop.ts +2 -0
  93. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  94. package/packages/pi-ai/dist/providers/anthropic.js +55 -7
  95. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  96. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  97. package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
  98. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  99. package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  100. package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
  101. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  102. package/packages/pi-ai/dist/providers/mistral.js +3 -0
  103. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
  106. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
  109. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  110. package/packages/pi-ai/dist/types.d.ts +23 -1
  111. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  112. package/packages/pi-ai/dist/types.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic.ts +59 -9
  114. package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
  115. package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
  116. package/packages/pi-ai/src/providers/mistral.ts +3 -0
  117. package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
  118. package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
  119. package/packages/pi-ai/src/types.ts +19 -1
  120. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  121. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  123. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +72 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  131. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  132. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  133. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
  134. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +84 -0
  135. package/scripts/postinstall.js +7 -109
  136. package/src/resources/GSD-WORKFLOW.md +12 -9
  137. package/src/resources/extensions/bg-shell/overlay.ts +18 -17
  138. package/src/resources/extensions/get-secrets-from-user.ts +5 -23
  139. package/src/resources/extensions/gsd/activity-log.ts +5 -3
  140. package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
  141. package/src/resources/extensions/gsd/auto-prompts.ts +87 -0
  142. package/src/resources/extensions/gsd/auto-recovery.ts +41 -2
  143. package/src/resources/extensions/gsd/auto-worktree.ts +134 -4
  144. package/src/resources/extensions/gsd/auto.ts +307 -77
  145. package/src/resources/extensions/gsd/cache.ts +3 -1
  146. package/src/resources/extensions/gsd/commands.ts +176 -10
  147. package/src/resources/extensions/gsd/complexity.ts +1 -0
  148. package/src/resources/extensions/gsd/dashboard-overlay.ts +38 -0
  149. package/src/resources/extensions/gsd/doctor.ts +58 -11
  150. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  151. package/src/resources/extensions/gsd/git-service.ts +74 -14
  152. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  153. package/src/resources/extensions/gsd/gsd-db.ts +78 -1
  154. package/src/resources/extensions/gsd/guided-flow.ts +109 -12
  155. package/src/resources/extensions/gsd/index.ts +48 -2
  156. package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
  157. package/src/resources/extensions/gsd/memory-store.ts +441 -0
  158. package/src/resources/extensions/gsd/migrate/command.ts +2 -2
  159. package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  160. package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
  161. package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  162. package/src/resources/extensions/gsd/preferences.ts +65 -1
  163. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  165. package/src/resources/extensions/gsd/prompts/discuss.md +4 -4
  166. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  168. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  169. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  170. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  171. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  172. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
  174. package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
  175. package/src/resources/extensions/gsd/session-status-io.ts +197 -0
  176. package/src/resources/extensions/gsd/state.ts +72 -30
  177. package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  178. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  179. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  180. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
  181. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  182. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  183. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  184. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  185. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  186. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  187. package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  188. package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  189. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  190. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  191. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  192. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  193. package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  194. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  195. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  196. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  197. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  198. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  199. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  200. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  201. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  202. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  203. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  204. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  205. package/src/resources/extensions/gsd/types.ts +15 -1
  206. package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
  207. package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  208. package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
  209. package/src/resources/extensions/gsd/worktree.ts +9 -2
  210. package/src/resources/extensions/search-the-web/native-search.ts +15 -5
  211. package/src/resources/extensions/subagent/index.ts +5 -0
  212. package/src/resources/extensions/subagent/worker-registry.ts +99 -0
@@ -11,7 +11,7 @@ import type { ExtensionContext } from "@gsd/pi-coding-agent";
11
11
  import {
12
12
  clearUnitRuntimeRecord,
13
13
  } from "./unit-runtime.js";
14
- import { clearParseCache } from "./files.js";
14
+ import { clearParseCache, parseRoadmap, parsePlan } from "./files.js";
15
15
  import {
16
16
  nativeConflictFiles,
17
17
  nativeCommit,
@@ -36,7 +36,6 @@ import {
36
36
  clearPathCache,
37
37
  resolveGsdRootFile,
38
38
  } from "./paths.js";
39
- import { parseRoadmap } from "./files.js";
40
39
  import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "node:fs";
41
40
  import { dirname, join } from "node:path";
42
41
 
@@ -83,6 +82,10 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
83
82
  const dir = resolveSlicePath(base, mid, sid!);
84
83
  return dir ? join(dir, buildSliceFileName(sid!, "SUMMARY")) : null;
85
84
  }
85
+ case "validate-milestone": {
86
+ const dir = resolveMilestonePath(base, mid);
87
+ return dir ? join(dir, buildMilestoneFileName(mid, "VALIDATION")) : null;
88
+ }
86
89
  case "complete-milestone": {
87
90
  const dir = resolveMilestonePath(base, mid);
88
91
  return dir ? join(dir, buildMilestoneFileName(mid, "SUMMARY")) : null;
@@ -157,6 +160,31 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
157
160
  }
158
161
  }
159
162
 
163
+ // plan-slice must also produce individual task plan files for every task listed
164
+ // in the slice plan. Without this check, a plan-slice that wrote S{sid}-PLAN.md
165
+ // but omitted T{tid}-PLAN.md files would be marked complete, causing execute-task
166
+ // to dispatch with a missing task plan (see issue #739).
167
+ if (unitType === "plan-slice") {
168
+ const parts = unitId.split("/");
169
+ const mid = parts[0];
170
+ const sid = parts[1];
171
+ if (mid && sid) {
172
+ try {
173
+ const planContent = readFileSync(absPath, "utf-8");
174
+ const plan = parsePlan(planContent);
175
+ const tasksDir = resolveTasksDir(base, mid, sid);
176
+ if (plan.tasks.length > 0 && tasksDir) {
177
+ for (const task of plan.tasks) {
178
+ const taskPlanFile = join(tasksDir, `${task.id}-PLAN.md`);
179
+ if (!existsSync(taskPlanFile)) return false;
180
+ }
181
+ }
182
+ } catch {
183
+ // Parse failure — don't block; slice plan may have non-standard format
184
+ }
185
+ }
186
+ }
187
+
160
188
  // complete-slice must also produce a UAT file AND mark the slice [x] in the roadmap.
161
189
  // Without the roadmap check, a crash after writing SUMMARY+UAT but before updating
162
190
  // the roadmap causes an infinite skip loop: the idempotency key says "done" but the
@@ -244,6 +272,8 @@ export function diagnoseExpectedArtifact(unitType: string, unitId: string, base:
244
272
  return `${relSliceFile(base, mid!, sid!, "ASSESSMENT")} (roadmap reassessment)`;
245
273
  case "run-uat":
246
274
  return `${relSliceFile(base, mid!, sid!, "UAT-RESULT")} (UAT result)`;
275
+ case "validate-milestone":
276
+ return `${relMilestoneFile(base, mid!, "VALIDATION")} (milestone validation report)`;
247
277
  case "complete-milestone":
248
278
  return `${relMilestoneFile(base, mid!, "SUMMARY")} (milestone summary)`;
249
279
  default:
@@ -537,6 +567,15 @@ export function buildLoopRemediationSteps(unitType: string, unitId: string, base
537
567
  ` 4. Resume auto-mode`,
538
568
  ].join("\n");
539
569
  }
570
+ case "validate-milestone": {
571
+ if (!mid) break;
572
+ const artifactRel = relMilestoneFile(base, mid, "VALIDATION");
573
+ return [
574
+ ` 1. Write ${artifactRel} with verdict: pass`,
575
+ ` 2. Run \`gsd doctor\``,
576
+ ` 3. Resume auto-mode`,
577
+ ].join("\n");
578
+ }
540
579
  default:
541
580
  break;
542
581
  }
@@ -6,7 +6,7 @@
6
6
  * manages create, enter, detect, and teardown for auto-mode worktrees.
7
7
  */
8
8
 
9
- import { existsSync, cpSync, readFileSync, realpathSync, utimesSync } from "node:fs";
9
+ import { existsSync, cpSync, readFileSync, writeFileSync, readdirSync, mkdirSync, realpathSync, utimesSync } from "node:fs";
10
10
  import { isAbsolute, join, resolve } from "node:path";
11
11
  import { copyWorktreeDb, reconcileWorktreeDb, isDbAvailable } from "./gsd-db.js";
12
12
  import { execSync, execFileSync } from "node:child_process";
@@ -134,6 +134,112 @@ export function autoWorktreeBranch(milestoneId: string): string {
134
134
  * Atomic: chdir + originalBase update happen in the same try block
135
135
  * to prevent split-brain.
136
136
  */
137
+
138
+ /**
139
+ * Forward-merge plan checkbox state from the project root into a freshly
140
+ * re-attached worktree (#778).
141
+ *
142
+ * When auto-mode stops via crash (not graceful stop), the milestone branch
143
+ * HEAD may be behind the filesystem state at the project root because
144
+ * syncStateToProjectRoot() runs after every task completion but the final
145
+ * git commit may not have happened before the crash. On restart the worktree
146
+ * is re-attached to the branch HEAD, which has [ ] for the crashed task,
147
+ * causing verifyExpectedArtifact() to fail and triggering an infinite
148
+ * dispatch/skip loop.
149
+ *
150
+ * Fix: after re-attaching, read every *.md plan file in the milestone
151
+ * directory at the project root and apply any [x] checkbox states that are
152
+ * ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
153
+ *
154
+ * This is safe because syncStateToProjectRoot() is the authoritative source
155
+ * of post-task state at the project root — it writes the same [x] the LLM
156
+ * produced, then the auto-commit follows. If the commit never happened, the
157
+ * filesystem copy is still valid and correct.
158
+ */
159
+ function reconcilePlanCheckboxes(projectRoot: string, wtPath: string, milestoneId: string): void {
160
+ const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
161
+ const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
162
+ if (!existsSync(srcMilestone) || !existsSync(dstMilestone)) return;
163
+
164
+ // Walk all markdown files in the milestone directory (plans, summaries, etc.)
165
+ function walkMd(dir: string): string[] {
166
+ const results: string[] = [];
167
+ try {
168
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
169
+ const full = join(dir, entry.name);
170
+ if (entry.isDirectory()) {
171
+ results.push(...walkMd(full));
172
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
173
+ results.push(full);
174
+ }
175
+ }
176
+ } catch { /* non-fatal */ }
177
+ return results;
178
+ }
179
+
180
+ for (const srcFile of walkMd(srcMilestone)) {
181
+ const rel = srcFile.slice(srcMilestone.length);
182
+ const dstFile = dstMilestone + rel;
183
+ if (!existsSync(dstFile)) continue; // only reconcile existing files
184
+
185
+ let srcContent: string;
186
+ let dstContent: string;
187
+ try {
188
+ srcContent = readFileSync(srcFile, "utf-8");
189
+ dstContent = readFileSync(dstFile, "utf-8");
190
+ } catch { continue; }
191
+
192
+ if (srcContent === dstContent) continue;
193
+
194
+ // Extract all checked task IDs from the source (project root)
195
+ // Pattern: - [x] **T<id>: or - [x] **S<id>: (case-insensitive x)
196
+ const checkedRe = /^- \[[xX]\] \*\*([TS]\d+):/gm;
197
+ const srcChecked = new Set<string>();
198
+ for (const m of srcContent.matchAll(checkedRe)) srcChecked.add(m[1]);
199
+
200
+ if (srcChecked.size === 0) continue;
201
+
202
+ // Forward-apply: replace [ ] → [x] for any IDs that are checked in src
203
+ let updated = dstContent;
204
+ let changed = false;
205
+ for (const id of srcChecked) {
206
+ const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
207
+ const uncheckedRe = new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm");
208
+ if (uncheckedRe.test(updated)) {
209
+ updated = updated.replace(
210
+ new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm"),
211
+ "$1[x]$2",
212
+ );
213
+ changed = true;
214
+ }
215
+ }
216
+
217
+ if (changed) {
218
+ try {
219
+ writeFileSync(dstFile, updated, "utf-8");
220
+ } catch { /* non-fatal */ }
221
+ }
222
+ }
223
+
224
+ // Also forward-merge completed-units.json (set-union)
225
+ const srcKeys = join(projectRoot, ".gsd", "completed-units.json");
226
+ const dstKeys = join(wtPath, ".gsd", "completed-units.json");
227
+ if (existsSync(srcKeys)) {
228
+ try {
229
+ const src: string[] = JSON.parse(readFileSync(srcKeys, "utf-8"));
230
+ let dst: string[] = [];
231
+ if (existsSync(dstKeys)) {
232
+ try { dst = JSON.parse(readFileSync(dstKeys, "utf-8")); } catch { /* ignore corrupt */ }
233
+ }
234
+ const merged = [...new Set([...dst, ...src])];
235
+ if (merged.length > dst.length) {
236
+ mkdirSync(join(wtPath, ".gsd"), { recursive: true });
237
+ writeFileSync(dstKeys, JSON.stringify(merged), "utf-8");
238
+ }
239
+ } catch { /* non-fatal */ }
240
+ }
241
+ }
242
+
137
243
  export function createAutoWorktree(basePath: string, milestoneId: string): string {
138
244
  const branch = autoWorktreeBranch(milestoneId);
139
245
 
@@ -158,7 +264,27 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
158
264
  // Planning artifacts may be untracked if the project's .gitignore had a
159
265
  // blanket .gsd/ rule (pre-v2.14.0). Without this copy, auto-mode loops
160
266
  // on plan-slice because the plan file doesn't exist in the worktree.
161
- copyPlanningArtifacts(basePath, info.path);
267
+ //
268
+ // IMPORTANT: Skip when re-attaching to an existing branch (#759).
269
+ // The branch checkout already has committed artifacts with correct state
270
+ // (e.g. [x] for completed slices). Copying from the project root would
271
+ // overwrite them with stale data ([ ] checkboxes) because the root is
272
+ // not always fully synced.
273
+ if (!branchExists) {
274
+ copyPlanningArtifacts(basePath, info.path);
275
+ } else {
276
+ // Re-attaching to an existing branch: forward-merge any plan checkpoint
277
+ // state from the project root into the worktree (#778).
278
+ //
279
+ // If auto-mode stopped via crash, the milestone branch HEAD may lag behind
280
+ // the project root filesystem because syncStateToProjectRoot() ran after
281
+ // task completion but the auto-commit never fired. On restart the worktree
282
+ // is re-created from the branch HEAD (which has [ ] for the crashed task),
283
+ // causing verifyExpectedArtifact() to return false → stale-key eviction →
284
+ // infinite dispatch/skip loop. Reconciling here ensures the worktree sees
285
+ // the same [x] state that syncStateToProjectRoot() wrote to the root.
286
+ reconcilePlanCheckboxes(basePath, info.path, milestoneId);
287
+ }
162
288
 
163
289
  // Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
164
290
  const hookError = runWorktreePostCreateHook(basePath, info.path);
@@ -429,8 +555,12 @@ export function mergeMilestoneToMain(
429
555
  const integrationBranch = readIntegrationBranch(originalBasePath_, milestoneId);
430
556
  const mainBranch = integrationBranch ?? prefs.main_branch ?? "main";
431
557
 
432
- // 5. Checkout integration branch
433
- nativeCheckoutBranch(originalBasePath_, mainBranch);
558
+ // 5. Checkout integration branch (skip if already current — avoids git error
559
+ // when main is already checked out in the project-root worktree, #757)
560
+ const currentBranchAtBase = nativeGetCurrentBranch(originalBasePath_);
561
+ if (currentBranchAtBase !== mainBranch) {
562
+ nativeCheckoutBranch(originalBasePath_, mainBranch);
563
+ }
434
564
 
435
565
  // 6. Build rich commit message
436
566
  const milestoneTitle = roadmap.title.replace(/^M\d+:\s*/, "").trim() || milestoneId;