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

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 (203) hide show
  1. package/README.md +8 -5
  2. package/dist/headless-recover.d.ts +23 -0
  3. package/dist/headless-recover.js +93 -0
  4. package/dist/headless.js +9 -0
  5. package/dist/help-text.js +1 -0
  6. package/dist/resources/.managed-resources-content-hash +1 -1
  7. package/dist/resources/extensions/browser-tools/tools/intent.js +8 -1
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +4 -56
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -27
  10. package/dist/resources/extensions/gsd/auto-start.js +1 -8
  11. package/dist/resources/extensions/gsd/auto-worktree.js +59 -176
  12. package/dist/resources/extensions/gsd/auto.js +24 -6
  13. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
  14. package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
  15. package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
  16. package/dist/resources/extensions/gsd/commands-logs.js +2 -2
  17. package/dist/resources/extensions/gsd/commands-scan.js +2 -2
  18. package/dist/resources/extensions/gsd/commands-ship.js +2 -2
  19. package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
  20. package/dist/resources/extensions/gsd/db-writer.js +16 -85
  21. package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
  22. package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
  23. package/dist/resources/extensions/gsd/gsd-db.js +74 -8
  24. package/dist/resources/extensions/gsd/guided-flow.js +31 -8
  25. package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
  26. package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
  27. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
  28. package/dist/resources/extensions/gsd/paths.js +35 -1
  29. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  30. package/dist/resources/extensions/gsd/queue-order.js +6 -1
  31. package/dist/resources/extensions/gsd/rethink.js +2 -2
  32. package/dist/resources/extensions/gsd/state.js +91 -372
  33. package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
  34. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
  35. package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
  36. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
  37. package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
  38. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
  39. package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
  40. package/dist/resources/extensions/gsd/worktree-command.js +4 -3
  41. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  42. package/dist/web/standalone/.next/BUILD_ID +1 -1
  43. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  44. package/dist/web/standalone/.next/build-manifest.json +2 -2
  45. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  46. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  63. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  64. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  65. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  66. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  67. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  68. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  69. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  70. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  71. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  72. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  73. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  74. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  75. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  76. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  77. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  78. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  79. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  80. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  81. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  82. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  83. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  84. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/index.html +1 -1
  95. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  102. package/dist/web/standalone/.next/server/chunks/6336.js +1 -0
  103. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  104. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  106. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  107. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  108. package/package.json +1 -1
  109. package/packages/mcp-server/dist/workflow-tools.d.ts +6 -0
  110. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  111. package/packages/mcp-server/dist/workflow-tools.js +56 -2
  112. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  113. package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
  114. package/packages/mcp-server/src/workflow-tools.ts +61 -2
  115. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  116. package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
  117. package/src/resources/extensions/gsd/auto-dispatch.ts +4 -60
  118. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -26
  119. package/src/resources/extensions/gsd/auto-start.ts +1 -8
  120. package/src/resources/extensions/gsd/auto-worktree.ts +61 -204
  121. package/src/resources/extensions/gsd/auto.ts +23 -6
  122. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
  123. package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
  124. package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
  125. package/src/resources/extensions/gsd/commands-logs.ts +2 -2
  126. package/src/resources/extensions/gsd/commands-scan.ts +2 -2
  127. package/src/resources/extensions/gsd/commands-ship.ts +2 -2
  128. package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
  129. package/src/resources/extensions/gsd/db-writer.ts +16 -83
  130. package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
  131. package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
  132. package/src/resources/extensions/gsd/gsd-db.ts +85 -8
  133. package/src/resources/extensions/gsd/guided-flow.ts +35 -8
  134. package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
  135. package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
  136. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
  137. package/src/resources/extensions/gsd/paths.ts +55 -1
  138. package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  139. package/src/resources/extensions/gsd/queue-order.ts +6 -1
  140. package/src/resources/extensions/gsd/rethink.ts +2 -2
  141. package/src/resources/extensions/gsd/state.ts +91 -389
  142. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
  143. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
  144. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
  145. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
  146. package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
  147. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
  148. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
  149. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  150. package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
  151. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
  152. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
  153. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
  154. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
  155. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
  156. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
  157. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
  158. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
  159. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
  160. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
  161. package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
  162. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
  163. package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
  164. package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
  165. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
  166. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
  167. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
  168. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
  169. package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
  170. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
  171. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
  172. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
  173. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
  174. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
  175. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
  176. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
  177. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
  178. package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
  179. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
  180. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  181. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
  182. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
  183. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
  184. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
  185. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
  186. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
  187. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
  188. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
  189. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +24 -1
  190. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
  191. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
  192. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
  193. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
  194. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
  195. package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
  196. package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
  197. package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
  198. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
  199. package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
  200. package/src/resources/extensions/gsd/worktree-command.ts +4 -3
  201. package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
  202. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → oZGTPvJBQX_IDKKnuV8Bt}/_buildManifest.js +0 -0
  203. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → oZGTPvJBQX_IDKKnuV8Bt}/_ssgManifest.js +0 -0
@@ -181,7 +181,7 @@ function openRawDb(path: string): unknown {
181
181
  return new Database(path);
182
182
  }
183
183
 
184
- export const SCHEMA_VERSION = 22;
184
+ export const SCHEMA_VERSION = 23;
185
185
 
186
186
  function indexExists(db: DbAdapter, name: string): boolean {
187
187
  return !!db.prepare(
@@ -354,7 +354,8 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
354
354
  verification_uat TEXT NOT NULL DEFAULT '',
355
355
  definition_of_done TEXT NOT NULL DEFAULT '[]',
356
356
  requirement_coverage TEXT NOT NULL DEFAULT '',
357
- boundary_map_markdown TEXT NOT NULL DEFAULT ''
357
+ boundary_map_markdown TEXT NOT NULL DEFAULT '',
358
+ sequence INTEGER DEFAULT 0
358
359
  )
359
360
  `);
360
361
 
@@ -1233,6 +1234,17 @@ function migrateSchema(db: DbAdapter): void {
1233
1234
  });
1234
1235
  }
1235
1236
 
1237
+ if (currentVersion < 23) {
1238
+ // v23: milestone queue ordering moves into the canonical DB. The
1239
+ // historical QUEUE-ORDER.json file remains a projection, but runtime
1240
+ // derivation must not read it as authoritative state.
1241
+ ensureColumn(db, "milestones", "sequence", "ALTER TABLE milestones ADD COLUMN sequence INTEGER DEFAULT 0");
1242
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1243
+ ":version": 23,
1244
+ ":applied_at": new Date().toISOString(),
1245
+ });
1246
+ }
1247
+
1236
1248
  db.exec("COMMIT");
1237
1249
  } catch (err) {
1238
1250
  db.exec("ROLLBACK");
@@ -1602,6 +1614,34 @@ export function getActiveRequirements(): Requirement[] {
1602
1614
  }));
1603
1615
  }
1604
1616
 
1617
+ export function getRequirementCounts(): {
1618
+ active: number;
1619
+ validated: number;
1620
+ deferred: number;
1621
+ outOfScope: number;
1622
+ blocked: number;
1623
+ total: number;
1624
+ } {
1625
+ if (!currentDb) {
1626
+ return { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 };
1627
+ }
1628
+ const rows = currentDb
1629
+ .prepare("SELECT lower(status) as status, COUNT(*) as count FROM requirements GROUP BY lower(status)")
1630
+ .all();
1631
+ const counts = { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 };
1632
+ for (const row of rows) {
1633
+ const status = String(row["status"] ?? "");
1634
+ const count = Number(row["count"] ?? 0);
1635
+ counts.total += count;
1636
+ if (status === "active") counts.active += count;
1637
+ else if (status === "validated") counts.validated += count;
1638
+ else if (status === "deferred") counts.deferred += count;
1639
+ else if (status === "out-of-scope" || status === "out_of_scope") counts.outOfScope += count;
1640
+ else if (status === "blocked") counts.blocked += count;
1641
+ }
1642
+ return counts;
1643
+ }
1644
+
1605
1645
  export function getDbOwnerPid(): number {
1606
1646
  return currentPid;
1607
1647
  }
@@ -2469,6 +2509,7 @@ export interface MilestoneRow {
2469
2509
  definition_of_done: string[];
2470
2510
  requirement_coverage: string;
2471
2511
  boundary_map_markdown: string;
2512
+ sequence: number;
2472
2513
  }
2473
2514
 
2474
2515
  function rowToMilestone(row: Record<string, unknown>): MilestoneRow {
@@ -2490,6 +2531,7 @@ function rowToMilestone(row: Record<string, unknown>): MilestoneRow {
2490
2531
  definition_of_done: JSON.parse((row["definition_of_done"] as string) || "[]"),
2491
2532
  requirement_coverage: (row["requirement_coverage"] as string) ?? "",
2492
2533
  boundary_map_markdown: (row["boundary_map_markdown"] as string) ?? "",
2534
+ sequence: Number(row["sequence"] ?? 0),
2493
2535
  };
2494
2536
  }
2495
2537
 
@@ -2517,7 +2559,9 @@ function rowToArtifact(row: Record<string, unknown>): ArtifactRow {
2517
2559
 
2518
2560
  export function getAllMilestones(): MilestoneRow[] {
2519
2561
  if (!currentDb) return [];
2520
- const rows = currentDb.prepare("SELECT * FROM milestones ORDER BY id").all();
2562
+ const rows = currentDb.prepare(
2563
+ "SELECT * FROM milestones ORDER BY CASE WHEN sequence > 0 THEN 0 ELSE 1 END, sequence, id",
2564
+ ).all();
2521
2565
  return rows.map(rowToMilestone);
2522
2566
  }
2523
2567
 
@@ -2528,6 +2572,22 @@ export function getMilestone(id: string): MilestoneRow | null {
2528
2572
  return rowToMilestone(row);
2529
2573
  }
2530
2574
 
2575
+ export function setMilestoneQueueOrder(order: string[]): void {
2576
+ if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2577
+ currentDb.exec("BEGIN IMMEDIATE");
2578
+ try {
2579
+ currentDb.prepare("UPDATE milestones SET sequence = 0").run();
2580
+ const stmt = currentDb.prepare("UPDATE milestones SET sequence = :sequence WHERE id = :id");
2581
+ order.forEach((id, index) => {
2582
+ stmt.run({ ":id": id, ":sequence": index + 1 });
2583
+ });
2584
+ currentDb.exec("COMMIT");
2585
+ } catch (err) {
2586
+ currentDb.exec("ROLLBACK");
2587
+ throw err;
2588
+ }
2589
+ }
2590
+
2531
2591
  /**
2532
2592
  * Update a milestone's status in the database.
2533
2593
  * Used by park/unpark to keep the DB in sync with the filesystem marker.
@@ -2723,6 +2783,8 @@ export function reconcileWorktreeDb(
2723
2783
  // fall through to the main DB's existing value (not a literal default)
2724
2784
  // so reconcile never silently clears state the main tree has recorded.
2725
2785
  const hasDecisionSource = wtInfo.some((col) => col["name"] === "source");
2786
+ const wtMilestoneInfo = adapter.prepare("PRAGMA wt.table_info('milestones')").all();
2787
+ const hasMilestoneSequence = wtMilestoneInfo.some((col) => col["name"] === "sequence");
2726
2788
  const wtSliceInfo = adapter.prepare("PRAGMA wt.table_info('slices')").all();
2727
2789
  const hasIsSketch = wtSliceInfo.some((col) => col["name"] === "is_sketch");
2728
2790
  const hasSketchScope = wtSliceInfo.some((col) => col["name"] === "sketch_scope");
@@ -2796,7 +2858,7 @@ export function reconcileWorktreeDb(
2796
2858
  id, title, status, depends_on, created_at, completed_at,
2797
2859
  vision, success_criteria, key_risks, proof_strategy,
2798
2860
  verification_contract, verification_integration, verification_operational, verification_uat,
2799
- definition_of_done, requirement_coverage, boundary_map_markdown
2861
+ definition_of_done, requirement_coverage, boundary_map_markdown, sequence
2800
2862
  )
2801
2863
  SELECT w.id, w.title,
2802
2864
  CASE
@@ -2814,7 +2876,8 @@ export function reconcileWorktreeDb(
2814
2876
  END,
2815
2877
  w.vision, w.success_criteria, w.key_risks, w.proof_strategy,
2816
2878
  w.verification_contract, w.verification_integration, w.verification_operational, w.verification_uat,
2817
- w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown
2879
+ w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown,
2880
+ ${hasMilestoneSequence ? "COALESCE(w.sequence, 0)" : "COALESCE(m.sequence, 0)"}
2818
2881
  FROM wt.milestones w
2819
2882
  LEFT JOIN milestones m ON m.id = w.id
2820
2883
  `).run());
@@ -3104,6 +3167,20 @@ export function getAssessment(path: string): Record<string, unknown> | null {
3104
3167
  return row ?? null;
3105
3168
  }
3106
3169
 
3170
+ export function getLatestAssessmentByScope(
3171
+ milestoneId: string,
3172
+ scope: string,
3173
+ ): Record<string, unknown> | null {
3174
+ if (!currentDb) return null;
3175
+ const row = currentDb.prepare(
3176
+ `SELECT * FROM assessments
3177
+ WHERE milestone_id = :mid AND scope = :scope
3178
+ ORDER BY created_at DESC
3179
+ LIMIT 1`,
3180
+ ).get({ ":mid": milestoneId, ":scope": scope });
3181
+ return row ?? null;
3182
+ }
3183
+
3107
3184
  // ─── Quality Gates ───────────────────────────────────────────────────────
3108
3185
 
3109
3186
  function rowToGate(row: Record<string, unknown>): GateRow {
@@ -3591,8 +3668,8 @@ export function restoreManifest(manifest: StateManifest): void {
3591
3668
  `INSERT INTO milestones (id, title, status, depends_on, created_at, completed_at,
3592
3669
  vision, success_criteria, key_risks, proof_strategy,
3593
3670
  verification_contract, verification_integration, verification_operational, verification_uat,
3594
- definition_of_done, requirement_coverage, boundary_map_markdown)
3595
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3671
+ definition_of_done, requirement_coverage, boundary_map_markdown, sequence)
3672
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3596
3673
  );
3597
3674
  for (const m of manifest.milestones) {
3598
3675
  msStmt.run(
@@ -3601,7 +3678,7 @@ export function restoreManifest(manifest: StateManifest): void {
3601
3678
  m.vision, JSON.stringify(m.success_criteria), JSON.stringify(m.key_risks),
3602
3679
  JSON.stringify(m.proof_strategy),
3603
3680
  m.verification_contract, m.verification_integration, m.verification_operational, m.verification_uat,
3604
- JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown,
3681
+ JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown, m.sequence ?? 0,
3605
3682
  );
3606
3683
  }
3607
3684
 
@@ -36,7 +36,7 @@ import { gsdHome } from "./gsd-home.js";
36
36
  import {
37
37
  gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath,
38
38
  resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile,
39
- relMilestoneFile, relSliceFile,
39
+ relMilestoneFile, relSliceFile, clearPathCache,
40
40
  } from "./paths.js";
41
41
  import { join } from "node:path";
42
42
  import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
@@ -588,12 +588,39 @@ export function maybeHandleReadyPhraseWithoutFiles(event: { messages: any[] }):
588
588
  const text = extractAssistantText(lastMsg);
589
589
  if (!READY_PHRASE_RE.test(text)) return false;
590
590
 
591
+ // Bust paths.ts cached dir listings before checking for fresh writes. The
592
+ // LLM's Write tool calls do not invalidate paths.ts caches, so a stale
593
+ // listing taken before the milestone dir or its CONTEXT/ROADMAP files
594
+ // existed would falsely report the artifacts as missing and trigger the
595
+ // 3-strike "ready without files" abort even though the writes succeeded.
596
+ clearPathCache();
597
+
591
598
  // Gate: artifacts must still be missing — if they exist, the happy path
592
599
  // already fired and we have nothing to do.
593
600
  const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
594
601
  const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
595
602
  if (contextFile || roadmapFile) return false;
596
603
 
604
+ // Diagnostic: when the cached resolver reports both files missing, also probe
605
+ // the canonical paths with uncached existsSync so we can tell whether the
606
+ // recovery is firing on real-missing files or a path-resolution miss
607
+ // (basePath/symlink mismatch, stale cache despite agent-end-recovery flush,
608
+ // legacy descriptor dir not matching, etc.).
609
+ try {
610
+ const mDir = resolveMilestonePath(basePath, milestoneId);
611
+ const canonicalCtx = mDir ? join(mDir, `${milestoneId}-CONTEXT.md`) : null;
612
+ const canonicalRoadmap = mDir ? join(mDir, `${milestoneId}-ROADMAP.md`) : null;
613
+ logWarning(
614
+ "guided",
615
+ `ready-phrase-reject diagnostic mid=${milestoneId} basePath=${basePath} ` +
616
+ `mDir=${mDir ?? "null"} ` +
617
+ `canonical-ctx=${canonicalCtx ?? "null"} ctx-exists=${canonicalCtx ? existsSync(canonicalCtx) : "n/a"} ` +
618
+ `canonical-roadmap=${canonicalRoadmap ?? "null"} roadmap-exists=${canonicalRoadmap ? existsSync(canonicalRoadmap) : "n/a"}`,
619
+ );
620
+ } catch (e) {
621
+ logWarning("guided", `ready-phrase-reject diagnostic failed: ${(e as Error).message}`);
622
+ }
623
+
597
624
  entry.readyRejectCount = (entry.readyRejectCount ?? 0) + 1;
598
625
 
599
626
  if (entry.readyRejectCount > MAX_READY_REJECTS) {
@@ -696,14 +723,14 @@ export function maybeHandleEmptyIntentTurn(
696
723
  // path, handled by maybeHandleReadyPhraseWithoutFiles.
697
724
  if (READY_PHRASE_RE.test(text)) return false;
698
725
 
699
- // Skip if the LLM is clearly handing back to the user. Last-line `?` is
700
- // the strongest signal, but discuss flows often end with a freeform
701
- // question followed by a closing remark ("…what should we build? I'll
702
- // pick one if you don't care."). Treat ANY non-empty line ending in `?`
703
- // as a question-asked signal false negatives here auto-reply to the
726
+ // Skip if the LLM is clearly handing back to the user. Discuss flows
727
+ // often pose a question and follow it with a conditional intent on the
728
+ // same line ("Did I capture that correctly? If so, I'll write the
729
+ // requirements."). A line-trailing `?` check misses these because the
730
+ // line ends in `.`. Match any sentence-terminating `?` (followed by
731
+ // whitespace or end-of-text) — false negatives here auto-reply to the
704
732
  // user, which is a much worse failure mode than a missed nudge.
705
- const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
706
- if (lines.some((l) => l.endsWith("?"))) return false;
733
+ if (/\?(?:\s|$)/.test(text)) return false;
707
734
 
708
735
  // Must contain a commit-intent phrase — this is the stall we care about.
709
736
  if (!COMMIT_INTENT_RE.test(text)) return false;
@@ -1,9 +1,8 @@
1
1
  // GSD Markdown Renderer — DB → Markdown file generation
2
2
  //
3
3
  // Transforms DB state into correct markdown files on disk.
4
- // Each render function reads from DB (with disk fallback),
5
- // patches content to match DB status, writes atomically to disk,
6
- // stores updated content in the artifacts table, and invalidates caches.
4
+ // Each render function reads from DB, writes a markdown projection to disk,
5
+ // stores generated content in the artifacts table, and invalidates caches.
7
6
  //
8
7
  // Critical invariant: rendered markdown must round-trip through
9
8
  // parseRoadmap(), parsePlan(), parseSummary() in files.ts.
@@ -85,58 +84,19 @@ function taskSummaryForSlicePlan(description: string): string {
85
84
  }
86
85
 
87
86
  /**
88
- * Load artifact content from DB first, falling back to reading from disk.
89
- * On disk fallback, stores the content in the artifacts table for future use.
90
- * Returns null if content is unavailable from both sources.
87
+ * Load artifact content from the DB. Markdown projections are not authoritative
88
+ * during runtime; when the artifact row is missing, callers regenerate from DB
89
+ * rows instead of patching disk fallback content and storing it back.
91
90
  */
92
91
  function loadArtifactContent(
93
92
  artifactPath: string,
94
- absPath: string | null,
95
- opts: {
96
- artifact_type: string;
97
- milestone_id: string;
98
- slice_id?: string;
99
- task_id?: string;
100
- },
101
93
  ): string | null {
102
- // Try DB first
103
94
  const artifact = getArtifact(artifactPath);
104
95
  if (artifact && artifact.full_content) {
105
96
  return artifact.full_content;
106
97
  }
107
98
 
108
- // Fall back to disk
109
- if (!absPath) {
110
- process.stderr.write(
111
- `markdown-renderer: artifact not found in DB or on disk: ${artifactPath}\n`,
112
- );
113
- return null;
114
- }
115
-
116
- let content: string;
117
- try {
118
- content = readFileSync(absPath, "utf-8");
119
- } catch {
120
- logWarning("renderer", `cannot read file from disk: ${absPath}`);
121
- return null;
122
- }
123
-
124
- // Store in DB for future use (graceful degradation path)
125
- try {
126
- insertArtifact({
127
- path: artifactPath,
128
- artifact_type: opts.artifact_type,
129
- milestone_id: opts.milestone_id,
130
- slice_id: opts.slice_id ?? null,
131
- task_id: opts.task_id ?? null,
132
- full_content: content,
133
- });
134
- } catch {
135
- // Non-fatal: we have the content, DB storage is best-effort
136
- logWarning("renderer", `failed to store disk fallback in DB: ${artifactPath}`);
137
- }
138
-
139
- return content;
99
+ return null;
140
100
  }
141
101
 
142
102
  /**
@@ -507,20 +467,15 @@ export async function renderRoadmapCheckboxes(
507
467
  const absPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
508
468
  const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
509
469
 
510
- // Load content from DB (with disk fallback)
470
+ // Load content from DB; regenerate from DB rows when the artifact is absent.
511
471
  let content: string | null = null;
512
472
  if (artifactPath) {
513
- content = loadArtifactContent(artifactPath, absPath, {
514
- artifact_type: "ROADMAP",
515
- milestone_id: milestoneId,
516
- });
473
+ content = loadArtifactContent(artifactPath);
517
474
  }
518
475
 
519
476
  if (!content) {
520
- process.stderr.write(
521
- `markdown-renderer: no roadmap content available for ${milestoneId}\n`,
522
- );
523
- return false;
477
+ await renderRoadmapFromDb(basePath, milestoneId);
478
+ return true;
524
479
  }
525
480
 
526
481
  // Apply checkbox patches for each slice
@@ -582,18 +537,12 @@ export async function renderPlanCheckboxes(
582
537
 
583
538
  let content: string | null = null;
584
539
  if (artifactPath) {
585
- content = loadArtifactContent(artifactPath, absPath, {
586
- artifact_type: "PLAN",
587
- milestone_id: milestoneId,
588
- slice_id: sliceId,
589
- });
540
+ content = loadArtifactContent(artifactPath);
590
541
  }
591
542
 
592
543
  if (!content) {
593
- process.stderr.write(
594
- `markdown-renderer: no plan content available for ${milestoneId}/${sliceId}\n`,
595
- );
596
- return false;
544
+ await renderPlanFromDb(basePath, milestoneId, sliceId);
545
+ return true;
597
546
  }
598
547
 
599
548
  // Apply checkbox patches for each task
@@ -9,7 +9,7 @@ import { existsSync, readdirSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { spawnSync } from "node:child_process";
11
11
  import { loadFile } from "./files.js";
12
- import { resolveMilestoneFile } from "./paths.js";
12
+ import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
13
13
  import { mergeMilestoneToMain } from "./auto-worktree.js";
14
14
  import { MergeConflictError } from "./git-service.js";
15
15
  import { removeSessionStatus } from "./session-status-io.js";
@@ -33,12 +33,13 @@ export type MergeOrder = "sequential" | "by-completion";
33
33
  // ─── Merge Queue ───────────────────────────────────────────────────────────
34
34
 
35
35
  /**
36
- * Check whether a milestone is complete by querying its worktree SQLite DB.
36
+ * Check whether a milestone is complete by querying the canonical project DB.
37
37
  * Uses a subprocess to avoid disrupting the global DB singleton.
38
- * Returns true when milestones.status = 'complete' in the worktree's gsd.db.
38
+ * Returns true when milestones.status = 'complete' in project gsd.db.
39
39
  */
40
- export function isMilestoneCompleteInWorktreeDb(basePath: string, mid: string): boolean {
41
- const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
40
+ export function isMilestoneCompleteInProjectDb(basePath: string, mid: string): boolean {
41
+ const workRoot = join(basePath, ".gsd", "worktrees", mid);
42
+ const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
42
43
  if (!existsSync(dbPath)) return false;
43
44
 
44
45
  try {
@@ -55,15 +56,15 @@ export function isMilestoneCompleteInWorktreeDb(basePath: string, mid: string):
55
56
  }
56
57
 
57
58
  /**
58
- * Discover milestone IDs with status='complete' in their worktree DB,
59
- * scanning .gsd/worktrees/<MID>/.gsd/gsd.db for each worktree directory.
59
+ * Discover milestone IDs with status='complete' in the canonical DB,
60
+ * using worktree directories only to enumerate active parallel workers.
60
61
  */
61
62
  function discoverDbCompletedMilestones(basePath: string): Set<string> {
62
63
  const completed = new Set<string>();
63
64
  const worktreeDir = join(basePath, ".gsd", "worktrees");
64
65
  try {
65
66
  for (const entry of readdirSync(worktreeDir)) {
66
- if (entry.startsWith("M") && isMilestoneCompleteInWorktreeDb(basePath, entry)) {
67
+ if (entry.startsWith("M") && isMilestoneCompleteInProjectDb(basePath, entry)) {
67
68
  completed.add(entry);
68
69
  }
69
70
  }
@@ -78,9 +79,9 @@ function discoverDbCompletedMilestones(basePath: string): Set<string> {
78
79
  * Sequential: merge in milestone ID order (M001 before M002).
79
80
  * By-completion: merge in the order milestones finished.
80
81
  *
81
- * When basePath is provided, also checks worktree SQLite DBs as the
82
- * source of truth workers with stale orchestrator state (e.g. "error")
83
- * are included if their worktree DB shows status='complete'.
82
+ * When basePath is provided, also checks the canonical project DB as the
83
+ * source of truth. Workers with stale orchestrator state (e.g. "error")
84
+ * are included if their project DB row shows status='complete'.
84
85
  * See: https://github.com/gsd-build/gsd-2/issues/2812
85
86
  */
86
87
  export function determineMergeOrder(
@@ -93,7 +94,7 @@ export function determineMergeOrder(
93
94
  workers.filter(w => w.state === "stopped").map(w => w.milestoneId),
94
95
  );
95
96
 
96
- // When basePath is available, also check worktree DBs for milestones
97
+ // When basePath is available, also check the project DB for milestones
97
98
  // whose orchestrator state is stale but are actually complete (#2812)
98
99
  const dbCompleted = basePath ? discoverDbCompletedMilestones(basePath) : new Set<string>();
99
100
 
@@ -109,7 +110,7 @@ export function determineMergeOrder(
109
110
  if (w) {
110
111
  allMergeable.push(w);
111
112
  } else {
112
- // Milestone discovered from worktree DB but not in workers list
113
+ // Milestone discovered from project DB but not in workers list
113
114
  allMergeable.push({
114
115
  milestoneId: mid,
115
116
  title: mid,
@@ -17,6 +17,7 @@ import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
17
17
 
18
18
  import { formatDuration, STATUS_GLYPH, STATUS_COLOR } from "../shared/mod.js";
19
19
  import { formattedShortcutPair } from "./shortcut-defs.js";
20
+ import { resolveGsdPathContract } from "./paths.js";
20
21
 
21
22
  // ─── Types ────────────────────────────────────────────────────────────────
22
23
 
@@ -126,7 +127,8 @@ function discoverWorkers(basePath: string): string[] {
126
127
  }
127
128
 
128
129
  function querySliceProgress(basePath: string, mid: string): SliceProgress[] {
129
- const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
130
+ const workRoot = join(basePath, ".gsd", "worktrees", mid);
131
+ const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
130
132
  if (!existsSync(dbPath)) return [];
131
133
 
132
134
  try {
@@ -166,7 +168,8 @@ function extractCostFromNdjson(basePath: string, mid: string): number {
166
168
  }
167
169
 
168
170
  function queryRecentCompletions(basePath: string, mid: string): string[] {
169
- const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
171
+ const workRoot = join(basePath, ".gsd", "worktrees", mid);
172
+ const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
170
173
  if (!existsSync(dbPath)) return [];
171
174
  try {
172
175
  const sql = `SELECT id, slice_id, one_liner FROM tasks WHERE milestone_id='${mid}' AND status='complete' AND completed_at IS NOT NULL ORDER BY completed_at DESC LIMIT 5`;
@@ -10,12 +10,13 @@
10
10
  */
11
11
 
12
12
  import { readdirSync, existsSync, realpathSync, Dirent } from "node:fs";
13
- import { join, dirname, normalize } from "node:path";
13
+ import { join, dirname, normalize, resolve } from "node:path";
14
14
  import { homedir } from "node:os";
15
15
  import { spawnSync } from "node:child_process";
16
16
  import { nativeScanGsdTree, type GsdTreeEntry } from "./native-parser-bridge.js";
17
17
  import { DIR_CACHE_MAX } from "./constants.js";
18
18
  import { gsdHome } from "./gsd-home.js";
19
+ import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "./worktree-root.js";
19
20
 
20
21
  // ─── Directory Listing Cache ──────────────────────────────────────────────────
21
22
 
@@ -286,6 +287,56 @@ const LEGACY_GSD_ROOT_FILES: Record<GSDRootFileKey, string> = {
286
287
 
287
288
  const gsdRootCache = new Map<string, string>();
288
289
 
290
+ export interface GsdPathContract {
291
+ /** Canonical repo/project root where authoritative state lives. */
292
+ projectRoot: string;
293
+ /** Current execution root, which may be an auto-worktree. */
294
+ workRoot: string;
295
+ /** Canonical authoritative .gsd directory. */
296
+ projectGsd: string;
297
+ /** Legacy worktree-local .gsd projection directory, when applicable. */
298
+ worktreeGsd: string | null;
299
+ /** Canonical authoritative SQLite DB path. */
300
+ projectDb: string;
301
+ /** True when workRoot is inside a GSD worktree layout. */
302
+ isWorktree: boolean;
303
+ }
304
+
305
+ export function resolveGsdPathContract(
306
+ workRoot: string,
307
+ originalProjectRoot?: string | null,
308
+ ): GsdPathContract {
309
+ const resolvedWorkRoot = resolve(workRoot || process.cwd());
310
+ const isWorktree = isGsdWorktreePath(resolvedWorkRoot);
311
+ if (isWorktree && !originalProjectRoot?.trim()) {
312
+ const externalMatch = /[/\\]\.gsd[/\\]projects[/\\][^/\\]+[/\\]worktrees(?:[/\\]|$)/.exec(resolvedWorkRoot);
313
+ if (externalMatch) {
314
+ const worktreesIdx = externalMatch[0].search(/[/\\]worktrees(?:[/\\]|$)/);
315
+ const projectGsd = resolvedWorkRoot.slice(0, externalMatch.index + worktreesIdx);
316
+ return {
317
+ projectRoot: dirname(dirname(projectGsd)),
318
+ workRoot: resolvedWorkRoot,
319
+ projectGsd,
320
+ worktreeGsd: join(resolvedWorkRoot, ".gsd"),
321
+ projectDb: join(projectGsd, "gsd.db"),
322
+ isWorktree,
323
+ };
324
+ }
325
+ }
326
+ const projectRoot = resolve(resolveWorktreeProjectRoot(resolvedWorkRoot, originalProjectRoot));
327
+ const projectGsd = join(projectRoot, ".gsd");
328
+ const worktreeGsd = isWorktree ? join(resolvedWorkRoot, ".gsd") : null;
329
+
330
+ return {
331
+ projectRoot,
332
+ workRoot: resolvedWorkRoot,
333
+ projectGsd,
334
+ worktreeGsd,
335
+ projectDb: join(projectGsd, "gsd.db"),
336
+ isWorktree,
337
+ };
338
+ }
339
+
289
340
  /** Exported for tests only — do not call in production code. */
290
341
  export function _clearGsdRootCache(): void {
291
342
  gsdRootCache.clear();
@@ -378,6 +429,9 @@ function isInsideGsdWorktree(p: string): boolean {
378
429
  }
379
430
 
380
431
  function probeGsdRoot(rawBasePath: string): string {
432
+ const contract = resolveGsdPathContract(rawBasePath);
433
+ if (contract.isWorktree) return contract.projectGsd;
434
+
381
435
  // 1. Fast path — check the input path directly
382
436
  const local = join(rawBasePath, ".gsd");
383
437
  if (existsSync(local)) return local;
@@ -10,6 +10,12 @@ All relevant context has been preloaded below — start working immediately with
10
10
 
11
11
  {{inlinedContext}}
12
12
 
13
+ ## Already Planned? Soft Brake
14
+
15
+ If `{{outputPath}}` already exists on disk with at least one slice line (e.g. `- [ ] **S01:`) AND `gsd_query` reports slice rows for this milestone, a prior `gsd_plan_milestone` call already persisted the plan. Do **not** re-call `gsd_plan_milestone` — its UPSERT would overwrite the existing plan with whatever you reconstruct, which is unsafe when you don't have the original decomposition reasoning. Skip directly to the ready phrase at the end of this prompt.
16
+
17
+ If only the file exists (no DB rows) or only DB rows exist (no file), the prior write was incomplete — proceed with planning as normal so the tool reconciles both.
18
+
13
19
  ## Your Role in the Pipeline
14
20
 
15
21
  You are the first deep look at this milestone. You have full tool access — explore the codebase, look up docs, investigate technology choices. Your job is to understand the landscape and then strategically decompose the work into demoable slices.
@@ -13,6 +13,7 @@ import { join } from "node:path";
13
13
  import { gsdRoot } from "./paths.js";
14
14
  import { milestoneIdSort } from "./milestone-ids.js";
15
15
  import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
16
+ import { isDbAvailable, setMilestoneQueueOrder } from "./gsd-db.js";
16
17
 
17
18
  // ─── Types ───────────────────────────────────────────────────────────────────
18
19
 
@@ -63,9 +64,13 @@ export function loadQueueOrder(basePath: string): string[] | null {
63
64
  }
64
65
 
65
66
  /**
66
- * Save a custom queue order to disk.
67
+ * Save a custom queue order. The DB sequence is canonical when a DB
68
+ * connection is open; QUEUE-ORDER.json remains a compatibility projection.
67
69
  */
68
70
  export function saveQueueOrder(basePath: string, order: string[]): void {
71
+ if (isDbAvailable()) {
72
+ setMilestoneQueueOrder(order);
73
+ }
69
74
  const data: QueueOrderFile = {
70
75
  order,
71
76
  updatedAt: new Date().toISOString(),
@@ -20,7 +20,7 @@ import { getMilestoneSlices, isDbAvailable } from "./gsd-db.js";
20
20
  import { buildExistingMilestonesContext } from "./guided-flow-queue.js";
21
21
  import { loadPrompt } from "./prompt-loader.js";
22
22
  import { isGsdGitignored } from "./gitignore.js";
23
- import { projectRoot } from "./commands/context.js";
23
+ import { currentDirectoryRoot } from "./commands/context.js";
24
24
 
25
25
  // ─── Entry Point ──────────────────────────────────────────────────────────────
26
26
 
@@ -34,7 +34,7 @@ export async function handleRethink(
34
34
  return;
35
35
  }
36
36
 
37
- const basePath = projectRoot();
37
+ const basePath = currentDirectoryRoot();
38
38
  const root = gsdRoot(basePath);
39
39
  if (!existsSync(root)) {
40
40
  ctx.ui.notify("No GSD project found. Run /gsd init first.", "warning");