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
@@ -12,7 +12,7 @@ import { reconcileWorktreeDb, isDbAvailable, getMilestone, getMilestoneSlices, c
12
12
  import { atomicWriteSync } from "./atomic-write.js";
13
13
  import { execFileSync } from "node:child_process";
14
14
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
15
- import { gsdRoot } from "./paths.js";
15
+ import { gsdRoot, resolveGsdPathContract } from "./paths.js";
16
16
  import { createWorktree, removeWorktree, resolveGitDir, worktreePath, isInsideWorktreesDir, } from "./worktree-manager.js";
17
17
  import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
18
18
  import { isGsdWorktreePath, normalizeWorktreePathForCompare, resolveWorktreeProjectRoot, } from "./worktree-root.js";
@@ -34,9 +34,8 @@ const LEGACY_DEEP_SETUP_RUNTIME_UNIT_FILES = new Set([
34
34
  ]);
35
35
  // ─── Shared Constants & Helpers ─────────────────────────────────────────────
36
36
  /**
37
- * Root-level .gsd/ state files synced between worktree and project root.
38
- * Single source of truth used by syncGsdStateToWorktree, syncWorktreeStateBack,
39
- * and the dispatch-level sync functions.
37
+ * Root-level .gsd/ projections copied from project root into worktrees for
38
+ * compatibility. Project root remains the canonical state/projection root.
40
39
  */
41
40
  const ROOT_STATE_FILES = [
42
41
  "DECISIONS.md",
@@ -53,6 +52,10 @@ const ROOT_STATE_FILES = [
53
52
  // Back-sync (worktree → main) must NEVER overwrite the project root's copy
54
53
  // because the project root is authoritative for preferences (#2684).
55
54
  ];
55
+ const ROOT_DIAGNOSTIC_FILES = [
56
+ "completed-units.json",
57
+ "metrics.json",
58
+ ];
56
59
  /**
57
60
  * Pop a stash entry by tracking the unique marker embedded in its message so
58
61
  * concurrent stash operations against the same project root cannot cause us to
@@ -137,7 +140,7 @@ const VERDICT_RE = /verdict:\s*[\w-]+/i;
137
140
  * destination when the source copy contains a `verdict:` field.
138
141
  *
139
142
  * This is the targeted fix for the UAT stuck-loop (#2821): the main
140
- * safeCopyRecursive uses force:false to protect worktree-authoritative
143
+ * safeCopyRecursive uses force:false to protect worktree-local projection
141
144
  * files (#1886), but ASSESSMENT files written by run-uat must be
142
145
  * forward-synced when the project root has a verdict. Without this,
143
146
  * the worktree retains a stale FAIL or missing ASSESSMENT and
@@ -212,9 +215,9 @@ function clearProjectRootStateFiles(basePath, milestoneId) {
212
215
  }
213
216
  }
214
217
  }
215
- // Clean up entire synced milestone directory and runtime/units.
216
- // syncStateToProjectRoot() copies these into the project root during
217
- // execution. If they remain as untracked files when we attempt
218
+ // Clean up legacy synced milestone directories and runtime/units.
219
+ // Older versions copied these into the project root during execution.
220
+ // If they remain as untracked files when we attempt
218
221
  // `git merge --squash`, git rejects the merge with "local changes would
219
222
  // be overwritten", causing silent data loss (#1738).
220
223
  const syncedDirs = [
@@ -277,8 +280,9 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
277
280
  return;
278
281
  if (!milestoneId)
279
282
  return;
280
- const prGsd = join(projectRoot, ".gsd");
281
- const wtGsd = join(worktreePath_, ".gsd");
283
+ const contract = resolveGsdPathContract(worktreePath_, projectRoot);
284
+ const prGsd = contract.projectGsd;
285
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath_, ".gsd");
282
286
  // When .gsd is a symlink to the same external directory in both locations,
283
287
  // cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
284
288
  // Compare realpaths and skip when they resolve to the same physical path (#2184).
@@ -286,12 +290,12 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
286
290
  return;
287
291
  // Copy milestone directory from project root to worktree — additive only.
288
292
  // force:false prevents cpSync from overwriting existing worktree files.
289
- // Without this, worktree-authoritative files (e.g. VALIDATION.md written
293
+ // Without this, worktree-local files (e.g. VALIDATION.md written
290
294
  // by validate-milestone) get clobbered by stale project root copies,
291
295
  // causing an infinite re-validation loop (#1886).
292
296
  safeCopyRecursive(join(prGsd, "milestones", milestoneId), join(wtGsd, "milestones", milestoneId), { force: false });
293
297
  // Force-sync ASSESSMENT files that have a verdict from project root (#2821).
294
- // The additive-only copy above preserves worktree-authoritative files, but
298
+ // The additive-only copy above preserves worktree-local files, but
295
299
  // ASSESSMENT files are special: after run-uat writes a verdict and post-unit
296
300
  // syncs it to the project root, the worktree may retain a stale copy (e.g.
297
301
  // verdict:fail while the project root has verdict:pass from a retry). On
@@ -303,11 +307,9 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
303
307
  // Project root is authoritative for completion state after crash recovery;
304
308
  // without this, the worktree re-dispatches already-completed units (#1886).
305
309
  safeCopy(join(prGsd, "completed-units.json"), join(wtGsd, "completed-units.json"), { force: true });
306
- // Delete worktree gsd.db ONLY if it is empty (0 bytes).
307
- // An empty DB is stale/corrupt and should be rebuilt (#853).
308
- // A non-empty DB was populated by gsd-migrate on respawn and must be
309
- // preserved — deleting it truncates the file to 0 bytes when
310
- // openDatabase re-creates it, causing "no such table" failures (#2815).
310
+ // Delete a legacy worktree-local gsd.db ONLY if it is empty (0 bytes).
311
+ // Runtime opens contract.projectDb; this cleanup only removes corrupt
312
+ // pre-upgrade local DB projections.
311
313
  try {
312
314
  const wtDb = join(wtGsd, "gsd.db");
313
315
  let deleteSidecars = false;
@@ -342,9 +344,10 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
342
344
  }
343
345
  }
344
346
  /**
345
- * Sync dispatch-critical .gsd/ state files from worktree to project root.
347
+ * Sync worktree diagnostics from worktree to project root.
346
348
  * Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
347
- * Copies: STATE.md + active milestone directory (roadmap, slice plans, task summaries).
349
+ * DB/project-root state remains authoritative; markdown projections are not
350
+ * copied from the worktree back to the project root.
348
351
  * Non-fatal — sync failure should never block dispatch.
349
352
  */
350
353
  export function syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId) {
@@ -352,23 +355,22 @@ export function syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId)
352
355
  return;
353
356
  if (!milestoneId)
354
357
  return;
355
- const wtGsd = join(worktreePath_, ".gsd");
356
- const prGsd = join(projectRoot, ".gsd");
358
+ const contract = resolveGsdPathContract(worktreePath_, projectRoot);
359
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath_, ".gsd");
360
+ const prGsd = contract.projectGsd;
357
361
  // When .gsd is a symlink to the same external directory in both locations,
358
362
  // cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
359
363
  // Compare realpaths and skip when they resolve to the same physical path (#2184).
360
364
  if (isSamePath(wtGsd, prGsd))
361
365
  return;
362
- // 1. STATE.md the quick-glance status used by initial deriveState()
363
- safeCopy(join(wtGsd, "STATE.md"), join(prGsd, "STATE.md"), { force: true });
364
- // 2. Milestone directory — ROADMAP, slice PLANs, task summaries
365
- // Copy the entire milestone .gsd subtree so deriveState reads current checkboxes
366
- safeCopyRecursive(join(wtGsd, "milestones", milestoneId), join(prGsd, "milestones", milestoneId), { force: true });
367
- // 3. metrics.json — session cost/token tracking (#2313).
366
+ // metrics.jsonsession cost/token tracking (#2313).
368
367
  // Without this, metrics accumulated in the worktree are invisible from the
369
368
  // project root and never appear in the dashboard or skill-health reports.
370
369
  safeCopy(join(wtGsd, "metrics.json"), join(prGsd, "metrics.json"), { force: true });
371
- // 4. Runtime records unit dispatch state used by selfHealRuntimeRecords().
370
+ // completed-units.jsonruntime completion diagnostics used to avoid
371
+ // re-dispatching work already completed in an isolated worktree.
372
+ safeCopy(join(wtGsd, "completed-units.json"), join(prGsd, "completed-units.json"), { force: true });
373
+ // Runtime records — unit dispatch diagnostics used by selfHealRuntimeRecords().
372
374
  // Without this, a crash during a unit leaves the runtime record only in the
373
375
  // worktree. If the next session resolves basePath before worktree re-entry,
374
376
  // selfHeal can't find or clear the stale record (#769).
@@ -529,12 +531,14 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
529
531
  * missing milestones, CONTEXT, ROADMAP, DECISIONS, REQUIREMENTS, and
530
532
  * PROJECT files from the main repo's .gsd/ into the worktree's .gsd/.
531
533
  *
532
- * Only adds missing content — never overwrites existing files in the worktree
533
- * (the worktree's execution state is authoritative for in-progress work).
534
+ * Only adds missing content — never overwrites existing files in the worktree.
535
+ * Worktree files are compatibility projections; DB/project root remains
536
+ * authoritative for runtime state.
534
537
  */
535
538
  export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
536
- const mainGsd = gsdRoot(mainBasePath);
537
- const wtGsd = gsdRoot(worktreePath_);
539
+ const contract = resolveGsdPathContract(worktreePath_, mainBasePath);
540
+ const mainGsd = contract.projectGsd;
541
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath_, ".gsd");
538
542
  const synced = [];
539
543
  // If both resolve to the same directory (symlink), no sync needed
540
544
  if (isSamePath(mainGsd, wtGsd))
@@ -678,28 +682,22 @@ export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
678
682
  return { synced };
679
683
  }
680
684
  /**
681
- * Sync milestone artifacts from worktree back to the main external state directory.
682
- * Called before milestone merge to ensure completion artifacts (SUMMARY, VALIDATION,
683
- * updated ROADMAP) are visible from the project root (#1412).
685
+ * Sync compatibility artifacts from worktree back to the main external state
686
+ * directory. Canonical workflow state lives in the project DB; worktree .gsd
687
+ * content is legacy projection/diagnostic data only.
684
688
  *
685
689
  * Syncs:
686
- * 1. Root-level .gsd/ files (REQUIREMENTS, PROJECT, DECISIONS, KNOWLEDGE,
687
- * OVERRIDES) the worktree's versions overwrite main's because the
688
- * worktree is the authoritative execution context.
689
- * 2. ALL milestone directories found in the worktree — not just the
690
- * current milestoneId. The complete-milestone unit may create artifacts
691
- * for the *next* milestone (CONTEXT, ROADMAP, new requirements) which
692
- * must survive worktree teardown.
690
+ * 1. Legacy worktree DBs are reconciled into the canonical project DB.
691
+ * 2. Runtime diagnostic files may be copied for operator visibility.
693
692
  *
694
- * History: Originally only synced milestones/<milestoneId>/ and assumed
695
- * root-level files would be carried by the squash merge. In practice,
696
- * .gsd/ files are often untracked (gitignored or never committed), so the
697
- * squash merge carries nothing. This caused next-milestone artifacts and
698
- * updated REQUIREMENTS/PROJECT to be silently lost on teardown.
693
+ * Markdown milestone directories are projections and are not copied from
694
+ * worktrees into the project root. Current workflow state must arrive through
695
+ * the shared project DB or the pre-upgrade DB reconciliation path above.
699
696
  */
700
697
  export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
701
- const mainGsd = gsdRoot(mainBasePath);
702
- const wtGsd = gsdRoot(worktreePath);
698
+ const contract = resolveGsdPathContract(worktreePath, mainBasePath);
699
+ const mainGsd = contract.projectGsd;
700
+ const wtGsd = contract.worktreeGsd ?? join(worktreePath, ".gsd");
703
701
  const synced = [];
704
702
  // If both resolve to the same directory (symlink), no sync needed
705
703
  if (isSamePath(mainGsd, wtGsd))
@@ -712,7 +710,7 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
712
710
  // files. This handles in-flight worktrees that were created before the
713
711
  // upgrade to shared WAL mode.
714
712
  const wtLocalDb = join(wtGsd, "gsd.db");
715
- const mainDb = join(mainGsd, "gsd.db");
713
+ const mainDb = contract.projectDb;
716
714
  if (existsSync(wtLocalDb) && existsSync(mainDb)) {
717
715
  try {
718
716
  reconcileWorktreeDb(mainDb, wtLocalDb);
@@ -723,13 +721,10 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
723
721
  logError("worktree", `DB reconciliation failed: ${err instanceof Error ? err.message : String(err)}`);
724
722
  }
725
723
  }
726
- // ── 1. Sync root-level .gsd/ files back ──────────────────────────────
727
- // The worktree is authoritative — complete-milestone updates REQUIREMENTS,
728
- // PROJECT, etc. These must overwrite main's copies so they survive teardown.
729
- // Also includes QUEUE.md, completed-units.json, and metrics.json which are
730
- // written during milestone closeout and lost on teardown without explicit sync
731
- // (#1787, #2313).
732
- for (const f of ROOT_STATE_FILES) {
724
+ // ── 1. Sync root-level diagnostic files back ─────────────────────────
725
+ // Markdown/JSON state projections remain project-root/DB authoritative.
726
+ // These diagnostic files are copied for observability only.
727
+ for (const f of ROOT_DIAGNOSTIC_FILES) {
733
728
  const src = join(wtGsd, f);
734
729
  const dst = join(mainGsd, f);
735
730
  if (existsSync(src)) {
@@ -743,103 +738,8 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
743
738
  }
744
739
  }
745
740
  }
746
- // ── 2. Sync ALL milestone directories ────────────────────────────────
747
- // The complete-milestone unit may create next-milestone artifacts (e.g.
748
- // M007 setup while closing M006). We must sync every milestone directory
749
- // in the worktree, not just the current one.
750
- const wtMilestonesDir = join(wtGsd, "milestones");
751
- if (!existsSync(wtMilestonesDir))
752
- return { synced };
753
- try {
754
- const wtMilestones = readdirSync(wtMilestonesDir, { withFileTypes: true })
755
- .filter((d) => d.isDirectory())
756
- .map((d) => d.name);
757
- for (const mid of wtMilestones) {
758
- // Skip the current milestone being merged — its files are already in the
759
- // milestone branch and would conflict with the squash merge (#3641).
760
- if (mid === milestoneId)
761
- continue;
762
- syncMilestoneDir(wtGsd, mainGsd, mid, synced);
763
- }
764
- }
765
- catch (err) {
766
- /* non-fatal */
767
- logWarning("worktree", `milestone sync-back failed: ${err instanceof Error ? err.message : String(err)}`);
768
- }
769
741
  return { synced };
770
742
  }
771
- function syncCurrentMilestoneStateAfterMerge(mainBasePath, worktreePath, milestoneId) {
772
- const mainGsd = gsdRoot(mainBasePath);
773
- const wtGsd = gsdRoot(worktreePath);
774
- const synced = [];
775
- if (isSamePath(mainGsd, wtGsd))
776
- return { synced };
777
- if (!existsSync(wtGsd) || !existsSync(mainGsd))
778
- return { synced };
779
- syncMilestoneDir(wtGsd, mainGsd, milestoneId, synced);
780
- return { synced };
781
- }
782
- /**
783
- * Sync a single milestone directory from worktree to main.
784
- * Copies milestone-level .md files, slice-level files, and task summaries.
785
- */
786
- /** Copy matching files from srcDir to dstDir (non-fatal per file). */
787
- function syncDirFiles(srcDir, dstDir, filter, synced, prefix) {
788
- try {
789
- for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
790
- if (!entry.isFile() || !filter(entry.name))
791
- continue;
792
- try {
793
- cpSync(join(srcDir, entry.name), join(dstDir, entry.name), { force: true });
794
- synced.push(`${prefix}${entry.name}`);
795
- }
796
- catch (err) {
797
- /* non-fatal */
798
- logWarning("worktree", `file copy failed (${prefix}${entry.name}): ${err instanceof Error ? err.message : String(err)}`);
799
- }
800
- }
801
- }
802
- catch (err) {
803
- /* non-fatal — srcDir may not be readable */
804
- logWarning("worktree", `directory read failed: ${err instanceof Error ? err.message : String(err)}`);
805
- }
806
- }
807
- function syncMilestoneDir(wtGsd, mainGsd, mid, synced) {
808
- const wtMilestoneDir = join(wtGsd, "milestones", mid);
809
- const mainMilestoneDir = join(mainGsd, "milestones", mid);
810
- if (!existsSync(wtMilestoneDir))
811
- return;
812
- mkdirSync(mainMilestoneDir, { recursive: true });
813
- const isMd = (name) => name.endsWith(".md");
814
- // Sync milestone-level files (SUMMARY, VALIDATION, ROADMAP, CONTEXT)
815
- syncDirFiles(wtMilestoneDir, mainMilestoneDir, isMd, synced, `milestones/${mid}/`);
816
- // Sync slice-level files (summaries, UATs) and task summaries (#1678)
817
- const wtSlicesDir = join(wtMilestoneDir, "slices");
818
- const mainSlicesDir = join(mainMilestoneDir, "slices");
819
- if (!existsSync(wtSlicesDir))
820
- return;
821
- try {
822
- for (const sliceEntry of readdirSync(wtSlicesDir, { withFileTypes: true })) {
823
- if (!sliceEntry.isDirectory())
824
- continue;
825
- const sid = sliceEntry.name;
826
- const wtSliceDir = join(wtSlicesDir, sid);
827
- const mainSliceDir = join(mainSlicesDir, sid);
828
- mkdirSync(mainSliceDir, { recursive: true });
829
- syncDirFiles(wtSliceDir, mainSliceDir, isMd, synced, `milestones/${mid}/slices/${sid}/`);
830
- const wtTasksDir = join(wtSliceDir, "tasks");
831
- const mainTasksDir = join(mainSliceDir, "tasks");
832
- if (existsSync(wtTasksDir)) {
833
- mkdirSync(mainTasksDir, { recursive: true });
834
- syncDirFiles(wtTasksDir, mainTasksDir, isMd, synced, `milestones/${mid}/slices/${sid}/tasks/`);
835
- }
836
- }
837
- }
838
- catch (err) {
839
- /* non-fatal */
840
- logWarning("worktree", `milestone slice sync failed (${mid}): ${err instanceof Error ? err.message : String(err)}`);
841
- }
842
- }
843
743
  // ─── Worktree Post-Create Hook (#597) ────────────────────────────────────────
844
744
  /**
845
745
  * Run the user-configured post-create hook script after worktree creation.
@@ -1002,10 +902,9 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
1002
902
  * directory at the project root and apply any [x] checkbox states that are
1003
903
  * ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
1004
904
  *
1005
- * This is safe because syncStateToProjectRoot() is the authoritative source
1006
- * of post-task state at the project root it writes the same [x] the LLM
1007
- * produced, then the auto-commit follows. If the commit never happened, the
1008
- * filesystem copy is still valid and correct.
905
+ * This is forward-only compatibility for legacy projection copies. The DB
906
+ * remains authoritative; this never downgrades checked boxes in a local
907
+ * worktree projection.
1009
908
  */
1010
909
  function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
1011
910
  const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
@@ -1457,9 +1356,10 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1457
1356
  // database (#2823).
1458
1357
  if (isDbAvailable()) {
1459
1358
  try {
1460
- const worktreeDbPath = join(worktreeCwd, ".gsd", "gsd.db");
1461
- const mainDbPath = join(originalBasePath_, ".gsd", "gsd.db");
1462
- if (!isSamePath(worktreeDbPath, mainDbPath)) {
1359
+ const contract = resolveGsdPathContract(worktreeCwd, originalBasePath_);
1360
+ const worktreeDbPath = join(contract.worktreeGsd ?? join(worktreeCwd, ".gsd"), "gsd.db");
1361
+ const mainDbPath = contract.projectDb;
1362
+ if (existsSync(worktreeDbPath) && !isSamePath(worktreeDbPath, mainDbPath)) {
1463
1363
  reconcileWorktreeDb(mainDbPath, worktreeDbPath);
1464
1364
  }
1465
1365
  }
@@ -1964,23 +1864,6 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1964
1864
  }
1965
1865
  // 9a-iii. Restore sheltered queued milestone directories (#2505).
1966
1866
  restoreShelter();
1967
- // 9a-iv. Preserve current milestone artifacts that may be untracked in git.
1968
- // syncWorktreeStateBack intentionally skips the current milestone before the
1969
- // squash merge to avoid conflicting with the merge content. Once the squash
1970
- // commit is complete, copy those files back so summaries, validation, and
1971
- // task outputs survive worktree teardown in external/.gitignored .gsd setups.
1972
- try {
1973
- const { synced } = syncCurrentMilestoneStateAfterMerge(originalBasePath_, worktreeCwd, milestoneId);
1974
- if (synced.length > 0) {
1975
- debugLog("mergeMilestoneToMain", {
1976
- phase: "current-milestone-sync-after-merge",
1977
- synced: synced.length,
1978
- });
1979
- }
1980
- }
1981
- catch (err) {
1982
- logWarning("worktree", `current milestone sync after merge failed: ${err instanceof Error ? err.message : String(err)}`);
1983
- }
1984
1867
  // 9b. Safety check (#1792): if nothing was committed, verify the milestone
1985
1868
  // work is already on the integration branch before allowing teardown.
1986
1869
  // Compare only non-.gsd/ paths — .gsd/ state files diverge normally and
@@ -56,11 +56,13 @@ import { getErrorMessage } from "./error-utils.js";
56
56
  import { recoverFailedMigration } from "./migrate-external.js";
57
57
  import { initRegistry, convertDispatchRules } from "./rule-registry.js";
58
58
  import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
59
+ import { isClosedStatus } from "./status-guards.js";
59
60
  import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, } from "./auto-dashboard.js";
60
61
  import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
61
62
  import { isDbAvailable, getMilestone } from "./gsd-db.js";
62
63
  import { countPendingCaptures } from "./captures.js";
63
64
  import { CMUX_CHANNELS } from "../shared/cmux-events.js";
65
+ import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
64
66
  function makeCmuxEmitters(pi) {
65
67
  return {
66
68
  syncCmuxSidebar: (preferences, state) => pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync", preferences, state }),
@@ -1154,15 +1156,31 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1154
1156
  || !!freshStartAssessment.lock);
1155
1157
  if (shouldResumePausedSession) {
1156
1158
  // Validate the milestone still exists and isn't already complete (#1664).
1159
+ // DB status is authoritative when available; SUMMARY.md is a legacy
1160
+ // fallback only for unmigrated/offline projects.
1157
1161
  const mDir = resolveMilestonePath(base, meta.milestoneId);
1158
- const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
1159
1162
  let summaryIsTerminal = false;
1160
- if (summaryFile) {
1161
- try {
1162
- summaryIsTerminal = classifyMilestoneSummaryContent(readFileSync(summaryFile, "utf-8")) !== "failure";
1163
+ let dbAvailable = isDbAvailable();
1164
+ let milestoneRow = dbAvailable ? getMilestone(meta.milestoneId) : null;
1165
+ if (!milestoneRow) {
1166
+ const opened = await ensureDbOpen(base);
1167
+ dbAvailable = opened || isDbAvailable();
1168
+ if (dbAvailable) {
1169
+ milestoneRow = getMilestone(meta.milestoneId);
1163
1170
  }
1164
- catch {
1165
- summaryIsTerminal = false;
1171
+ }
1172
+ if (dbAvailable) {
1173
+ summaryIsTerminal = !!milestoneRow && isClosedStatus(milestoneRow.status);
1174
+ }
1175
+ else {
1176
+ const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
1177
+ if (summaryFile) {
1178
+ try {
1179
+ summaryIsTerminal = classifyMilestoneSummaryContent(readFileSync(summaryFile, "utf-8")) !== "failure";
1180
+ }
1181
+ catch {
1182
+ summaryIsTerminal = false;
1183
+ }
1166
1184
  }
1167
1185
  }
1168
1186
  if (!mDir || summaryIsTerminal) {
@@ -1,8 +1,9 @@
1
1
  import { existsSync } from "node:fs";
2
- import { join, sep } from "node:path";
2
+ import { dirname } from "node:path";
3
3
  import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@gsd/pi-coding-agent";
4
4
  import { DEFAULT_BASH_TIMEOUT_SECS } from "../constants.js";
5
5
  import { setLogBasePath, logWarning } from "../workflow-logger.js";
6
+ import { resolveGsdPathContract } from "../paths.js";
6
7
  /**
7
8
  * Resolve the correct DB path for the current working directory.
8
9
  * If `basePath` is inside a `.gsd/worktrees/<MID>/` directory, returns
@@ -10,67 +11,15 @@ import { setLogBasePath, logWarning } from "../workflow-logger.js";
10
11
  * returns `<basePath>/.gsd/gsd.db`.
11
12
  */
12
13
  export function resolveProjectRootDbPath(basePath) {
13
- // Detect worktree: look for `.gsd/worktrees/` in the path segments.
14
- // A worktree path looks like: /project/root/.gsd/worktrees/M001/...
15
- // We need to resolve back to /project/root/.gsd/gsd.db
16
- const marker = `${sep}.gsd${sep}worktrees${sep}`;
17
- const idx = basePath.indexOf(marker);
18
- if (idx !== -1) {
19
- const projectRoot = basePath.slice(0, idx);
20
- return join(projectRoot, ".gsd", "gsd.db");
21
- }
22
- // Also handle forward-slash paths on all platforms
23
- const fwdMarker = "/.gsd/worktrees/";
24
- const fwdIdx = basePath.indexOf(fwdMarker);
25
- if (fwdIdx !== -1) {
26
- const projectRoot = basePath.slice(0, fwdIdx);
27
- return join(projectRoot, ".gsd", "gsd.db");
28
- }
29
- // External-state layout: ~/.gsd/projects/<hash>/worktrees/<MID>/...
30
- // Resolve to ~/.gsd/projects/<hash>/gsd.db (the canonical project DB) (#2952).
31
- // Must be checked before the generic symlink-resolved handler: both match
32
- // /.gsd/projects/<hash>/worktrees/ but require different resolution targets.
33
- const extRe = /[/\\]\.gsd[/\\]projects[/\\][a-f0-9]+[/\\]worktrees(?:[/\\]|$)/;
34
- const extMatch = extRe.exec(basePath);
35
- if (extMatch) {
36
- const matchStr = extMatch[0];
37
- // Find the "/worktrees" portion within the match and slice up to it
38
- const wtIdx = matchStr.search(/[/\\]worktrees(?:[/\\]|$)/);
39
- const projectStateRoot = basePath.slice(0, extMatch.index + wtIdx);
40
- return join(projectStateRoot, "gsd.db");
41
- }
42
- // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/M001/...
43
- // The project root is everything before /.gsd/projects/ (#2517)
44
- const symlinkMarker = `${sep}.gsd${sep}projects${sep}`;
45
- const symlinkIdx = basePath.indexOf(symlinkMarker);
46
- if (symlinkIdx !== -1) {
47
- const afterProjects = basePath.slice(symlinkIdx + symlinkMarker.length);
48
- // Expect: <hash>/worktrees/...
49
- const worktreeSeg = `${sep}worktrees${sep}`;
50
- if (afterProjects.includes(worktreeSeg)) {
51
- const projectRoot = basePath.slice(0, symlinkIdx);
52
- return join(projectRoot, ".gsd", "gsd.db");
53
- }
54
- }
55
- // Forward-slash variant for symlink-resolved layout
56
- const fwdSymlinkMarker = "/.gsd/projects/";
57
- const fwdSymlinkIdx = basePath.indexOf(fwdSymlinkMarker);
58
- if (fwdSymlinkIdx !== -1) {
59
- const afterProjects = basePath.slice(fwdSymlinkIdx + fwdSymlinkMarker.length);
60
- if (afterProjects.includes("/worktrees/")) {
61
- const projectRoot = basePath.slice(0, fwdSymlinkIdx);
62
- return join(projectRoot, ".gsd", "gsd.db");
63
- }
64
- }
65
- return join(basePath, ".gsd", "gsd.db");
14
+ return resolveGsdPathContract(basePath).projectDb;
66
15
  }
67
16
  export async function ensureDbOpen(basePath = process.cwd()) {
68
17
  try {
69
18
  const db = await import("../gsd-db.js");
70
- const dbPath = resolveProjectRootDbPath(basePath);
71
- const gsdDir = join(basePath, ".gsd");
72
- // Derive the project root from the DB path (strip .gsd/gsd.db)
73
- const projectRoot = join(dbPath, "..", "..");
19
+ const contract = resolveGsdPathContract(basePath);
20
+ const dbPath = contract.projectDb;
21
+ const gsdDir = contract.projectGsd;
22
+ const projectRoot = dirname(dirname(dbPath));
74
23
  // Open existing DB file (may be at project root for worktrees)
75
24
  if (existsSync(dbPath)) {
76
25
  const opened = db.openDatabase(dbPath);
@@ -78,26 +27,9 @@ export async function ensureDbOpen(basePath = process.cwd()) {
78
27
  setLogBasePath(projectRoot);
79
28
  return opened;
80
29
  }
81
- // No DB file — create + migrate from Markdown if .gsd/ has content
30
+ // No DB file — create an empty authoritative DB. Markdown migration is
31
+ // explicit-only; runtime startup must not import projections into state.
82
32
  if (existsSync(gsdDir)) {
83
- const hasDecisions = existsSync(join(gsdDir, "DECISIONS.md"));
84
- const hasRequirements = existsSync(join(gsdDir, "REQUIREMENTS.md"));
85
- const hasMilestones = existsSync(join(gsdDir, "milestones"));
86
- if (hasDecisions || hasRequirements || hasMilestones) {
87
- const opened = db.openDatabase(dbPath);
88
- if (opened) {
89
- setLogBasePath(projectRoot);
90
- try {
91
- const { migrateFromMarkdown } = await import("../md-importer.js");
92
- migrateFromMarkdown(basePath);
93
- }
94
- catch (err) {
95
- logWarning("bootstrap", `ensureDbOpen auto-migration failed: ${err.message}`);
96
- }
97
- }
98
- return opened;
99
- }
100
- // .gsd/ exists but has no Markdown content (fresh project) — create empty DB
101
33
  const opened = db.openDatabase(dbPath);
102
34
  if (opened)
103
35
  setLogBasePath(projectRoot);
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { generateCodebaseMap, updateCodebaseMap, writeCodebaseMap, getCodebaseMapStats, readCodebaseMap, } from "./codebase-generator.js";
8
8
  import { loadEffectiveGSDPreferences } from "./preferences.js";
9
- import { projectRoot } from "./commands/context.js";
9
+ import { currentDirectoryRoot } from "./commands/context.js";
10
10
  const USAGE = "Usage: /gsd codebase [generate|update|stats]\n\n" +
11
11
  " generate [--max-files N] [--collapse-threshold N] — Generate or regenerate CODEBASE.md\n" +
12
12
  " update [--max-files N] [--collapse-threshold N] — Refresh the CODEBASE.md cache immediately\n" +
@@ -20,7 +20,7 @@ const USAGE = "Usage: /gsd codebase [generate|update|stats]\n\n" +
20
20
  " max_files: 1000\n" +
21
21
  " collapse_threshold: 15";
22
22
  export async function handleCodebase(args, ctx, _pi) {
23
- const basePath = projectRoot();
23
+ const basePath = currentDirectoryRoot();
24
24
  const parts = args.trim().split(/\s+/);
25
25
  const sub = parts[0] ?? "";
26
26
  switch (sub) {
@@ -15,7 +15,7 @@ import { appendOverride, appendKnowledge } from "./files.js";
15
15
  import { formatDoctorIssuesForPrompt, formatDoctorReport, formatDoctorReportJson, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
16
16
  import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
17
17
  import { getAutoWorktreePath } from "./auto-worktree.js";
18
- import { projectRoot } from "./commands/context.js";
18
+ import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
19
19
  import { loadPrompt } from "./prompt-loader.js";
20
20
  const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/gsd-pi/latest";
21
21
  const UPDATE_FETCH_TIMEOUT_MS = 5000;
@@ -169,7 +169,7 @@ export async function handleCapture(args, ctx) {
169
169
  ctx.ui.notify('Usage: /gsd capture "your thought here"', "warning");
170
170
  return;
171
171
  }
172
- const basePath = projectRoot();
172
+ const basePath = currentDirectoryRoot();
173
173
  // Ensure .gsd/ exists — capture should work even without a milestone
174
174
  const gsdDir = gsdRoot(basePath);
175
175
  if (!existsSync(gsdDir)) {
@@ -220,7 +220,7 @@ export async function handleTriage(ctx, pi, basePath) {
220
220
  }, { triggerTurn: true });
221
221
  }
222
222
  export async function handleSteer(change, ctx, pi) {
223
- const basePath = projectRoot();
223
+ const basePath = currentDirectoryRoot();
224
224
  const state = await deriveState(basePath);
225
225
  const mid = state.activeMilestone?.id ?? "none";
226
226
  const sid = state.activeSlice?.id ?? "none";
@@ -284,7 +284,7 @@ export async function handleKnowledge(args, ctx) {
284
284
  return;
285
285
  }
286
286
  const type = typeArg;
287
- const basePath = projectRoot();
287
+ const basePath = currentDirectoryRoot();
288
288
  const state = await deriveState(basePath);
289
289
  const scope = state.activeMilestone?.id
290
290
  ? `${state.activeMilestone.id}${state.activeSlice ? `/${state.activeSlice.id}` : ""}`
@@ -310,7 +310,7 @@ Examples:
310
310
  return;
311
311
  }
312
312
  const [hookName, unitType, unitId] = parts;
313
- const basePath = projectRoot();
313
+ const basePath = currentDirectoryRoot();
314
314
  // Import the hook trigger function
315
315
  const { triggerHookManually, formatHookStatus, getHookStatus } = await import("./post-unit-hooks.js");
316
316
  const { dispatchHookUnit } = await import("./auto.js");
@@ -13,7 +13,7 @@ import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "nod
13
13
  import { join } from "node:path";
14
14
  import { gsdRoot } from "./paths.js";
15
15
  import { loadJsonFileOrNull } from "./json-persistence.js";
16
- import { projectRoot } from "./commands/context.js";
16
+ import { currentDirectoryRoot } from "./commands/context.js";
17
17
  // ─── Helpers ────────────────────────────────────────────────────────────────
18
18
  function activityDir(basePath) {
19
19
  return join(gsdRoot(basePath), "activity");
@@ -227,7 +227,7 @@ function summarizeDebugLog(filePath) {
227
227
  }
228
228
  // ─── Main Handler ───────────────────────────────────────────────────────────
229
229
  export async function handleLogs(args, ctx) {
230
- const basePath = projectRoot();
230
+ const basePath = currentDirectoryRoot();
231
231
  const parts = args.trim().split(/\s+/).filter(Boolean);
232
232
  const subCmd = parts[0] ?? "";
233
233
  // /gsd logs clear