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

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 (300) 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/phases.js +7 -2
  9. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +7 -58
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +14 -28
  12. package/dist/resources/extensions/gsd/auto-start.js +1 -8
  13. package/dist/resources/extensions/gsd/auto-worktree.js +244 -216
  14. package/dist/resources/extensions/gsd/auto.js +86 -7
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
  16. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
  18. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
  19. package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
  20. package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
  21. package/dist/resources/extensions/gsd/commands-logs.js +2 -2
  22. package/dist/resources/extensions/gsd/commands-scan.js +2 -2
  23. package/dist/resources/extensions/gsd/commands-ship.js +2 -2
  24. package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
  25. package/dist/resources/extensions/gsd/db-writer.js +106 -95
  26. package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
  27. package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
  28. package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
  29. package/dist/resources/extensions/gsd/gsd-db.js +268 -8
  30. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  31. package/dist/resources/extensions/gsd/guided-flow.js +141 -32
  32. package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
  33. package/dist/resources/extensions/gsd/metrics.js +287 -1
  34. package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
  35. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
  36. package/dist/resources/extensions/gsd/paths.js +114 -9
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  39. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  40. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  41. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  42. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  44. package/dist/resources/extensions/gsd/queue-order.js +6 -1
  45. package/dist/resources/extensions/gsd/rethink.js +2 -2
  46. package/dist/resources/extensions/gsd/state.js +91 -372
  47. package/dist/resources/extensions/gsd/templates/project.md +10 -0
  48. package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
  49. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
  50. package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
  51. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
  52. package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
  53. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
  54. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  55. package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
  56. package/dist/resources/extensions/gsd/workspace.js +59 -0
  57. package/dist/resources/extensions/gsd/worktree-command.js +4 -3
  58. package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
  59. package/dist/resources/extensions/gsd/write-intercept.js +3 -3
  60. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  61. package/dist/web/standalone/.next/BUILD_ID +1 -1
  62. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  63. package/dist/web/standalone/.next/build-manifest.json +2 -2
  64. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  65. package/dist/web/standalone/.next/required-server-files.json +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  83. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  84. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  101. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  108. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  112. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/index.html +1 -1
  115. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  122. package/dist/web/standalone/.next/server/chunks/6336.js +1 -0
  123. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  124. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  126. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  127. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  128. package/dist/web/standalone/server.js +1 -1
  129. package/package.json +1 -1
  130. package/packages/mcp-server/README.md +2 -11
  131. package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
  132. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  133. package/packages/mcp-server/dist/remote-questions.js +28 -0
  134. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  135. package/packages/mcp-server/dist/server.d.ts +28 -0
  136. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  137. package/packages/mcp-server/dist/server.js +94 -4
  138. package/packages/mcp-server/dist/server.js.map +1 -1
  139. package/packages/mcp-server/dist/workflow-tools.d.ts +6 -0
  140. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  141. package/packages/mcp-server/dist/workflow-tools.js +56 -2
  142. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  143. package/packages/mcp-server/src/mcp-server.test.ts +226 -0
  144. package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
  145. package/packages/mcp-server/src/remote-questions.test.ts +103 -0
  146. package/packages/mcp-server/src/remote-questions.ts +35 -0
  147. package/packages/mcp-server/src/server.ts +129 -6
  148. package/packages/mcp-server/src/workflow-tools.ts +62 -3
  149. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  150. package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
  151. package/src/resources/extensions/gsd/auto/phases.ts +8 -2
  152. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  153. package/src/resources/extensions/gsd/auto-dispatch.ts +14 -62
  154. package/src/resources/extensions/gsd/auto-post-unit.ts +15 -27
  155. package/src/resources/extensions/gsd/auto-start.ts +1 -8
  156. package/src/resources/extensions/gsd/auto-worktree.ts +286 -251
  157. package/src/resources/extensions/gsd/auto.ts +102 -7
  158. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
  159. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
  160. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
  161. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
  162. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
  163. package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
  164. package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
  165. package/src/resources/extensions/gsd/commands-logs.ts +2 -2
  166. package/src/resources/extensions/gsd/commands-scan.ts +2 -2
  167. package/src/resources/extensions/gsd/commands-ship.ts +2 -2
  168. package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
  169. package/src/resources/extensions/gsd/db-writer.ts +123 -94
  170. package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
  171. package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
  172. package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
  173. package/src/resources/extensions/gsd/gsd-db.ts +269 -8
  174. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  175. package/src/resources/extensions/gsd/guided-flow.ts +181 -32
  176. package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
  177. package/src/resources/extensions/gsd/metrics.ts +321 -1
  178. package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
  179. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
  180. package/src/resources/extensions/gsd/paths.ts +122 -9
  181. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  182. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  183. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  184. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  185. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  186. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  188. package/src/resources/extensions/gsd/queue-order.ts +6 -1
  189. package/src/resources/extensions/gsd/rethink.ts +2 -2
  190. package/src/resources/extensions/gsd/state.ts +91 -389
  191. package/src/resources/extensions/gsd/templates/project.md +10 -0
  192. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
  193. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
  194. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
  195. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
  196. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
  197. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
  198. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
  199. package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
  200. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
  201. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
  202. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  203. package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
  204. package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
  205. package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
  206. package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
  207. package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
  208. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
  209. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
  210. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
  211. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
  212. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
  213. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
  214. package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
  215. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
  216. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
  217. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
  218. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
  219. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
  220. package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
  221. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
  222. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
  223. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
  224. package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
  225. package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
  226. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
  227. package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
  228. package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
  229. package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
  230. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
  231. package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
  232. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
  233. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
  234. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
  235. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
  236. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
  237. package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
  238. package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
  239. package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
  240. package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
  241. package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
  242. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
  243. package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
  244. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
  245. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
  246. package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
  247. package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
  248. package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
  249. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
  250. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
  251. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
  252. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
  253. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
  254. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
  255. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
  256. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
  257. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
  258. package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
  259. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
  260. package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
  261. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
  262. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  263. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
  264. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
  265. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
  266. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
  267. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
  268. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
  269. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
  270. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
  271. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
  272. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
  273. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
  274. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
  275. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
  276. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
  277. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +26 -3
  278. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
  279. package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
  280. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
  281. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
  282. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
  283. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
  284. package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
  285. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
  287. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
  288. package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
  289. package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
  290. package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
  291. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
  292. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  293. package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
  294. package/src/resources/extensions/gsd/workspace.ts +95 -0
  295. package/src/resources/extensions/gsd/worktree-command.ts +4 -3
  296. package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
  297. package/src/resources/extensions/gsd/write-intercept.ts +3 -3
  298. package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
  299. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
  300. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
@@ -1,3 +1,4 @@
1
+ // GSD-2 + metrics.ts: token & cost tracking for auto-mode units
1
2
  /**
2
3
  * GSD Metrics — Token & Cost Tracking
3
4
  *
@@ -14,6 +15,7 @@
14
15
  */
15
16
 
16
17
  import { join } from "node:path";
18
+ import { openSync, closeSync, unlinkSync, statSync, writeFileSync } from "node:fs";
17
19
  import type { ExtensionContext } from "@gsd/pi-coding-agent";
18
20
  import { gsdRoot } from "./paths.js";
19
21
  import { getAndClearSkills } from "./skill-telemetry.js";
@@ -21,6 +23,8 @@ import { loadJsonFile, loadJsonFileOrNull, saveJsonFile } from "./json-persisten
21
23
  import { parseUnitId } from "./unit-id.js";
22
24
  import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js";
23
25
  import { isUnifiedAuditEnabled } from "./uok/audit-toggle.js";
26
+ import type { MilestoneScope } from "./workspace.js";
27
+ import { logWarning } from "./workflow-logger.js";
24
28
 
25
29
  // Re-export from shared — import directly from format-utils to avoid pulling
26
30
  // in the full barrel (mod.js → ui.js → @gsd/pi-tui) which breaks when loaded
@@ -108,11 +112,17 @@ export function classifyUnitPhase(unitType: string): MetricsPhase {
108
112
  let ledger: MetricsLedger | null = null;
109
113
  let basePath: string = "";
110
114
 
115
+ // Per-workspace ledger map, keyed by workspace.identityKey.
116
+ // Populated by initMetricsByScope; independent of the module singleton.
117
+ const scopedLedgers = new Map<string, MetricsLedger>();
118
+
111
119
  // ─── Public API ───────────────────────────────────────────────────────────────
112
120
 
113
121
  /**
114
122
  * Initialize the metrics system for a given project.
115
123
  * Loads existing ledger from disk if present.
124
+ *
125
+ * @deprecated TODO(C-future): remove module singleton. Use initMetricsByScope instead.
116
126
  */
117
127
  export function initMetrics(base: string): void {
118
128
  basePath = base;
@@ -121,6 +131,8 @@ export function initMetrics(base: string): void {
121
131
 
122
132
  /**
123
133
  * Reset in-memory state. Called when auto-mode stops.
134
+ *
135
+ * @deprecated TODO(C-future): remove module singleton. Use resetMetricsByScope instead.
124
136
  */
125
137
  export function resetMetrics(): void {
126
138
  ledger = null;
@@ -130,6 +142,8 @@ export function resetMetrics(): void {
130
142
  /**
131
143
  * Snapshot usage metrics from the current session before it's wiped.
132
144
  * Scans session entries for AssistantMessage usage data.
145
+ *
146
+ * @deprecated TODO(C-future): remove module singleton. Use snapshotUnitMetricsByScope instead.
133
147
  */
134
148
  export function snapshotUnitMetrics(
135
149
  ctx: ExtensionContext,
@@ -272,6 +286,182 @@ export function getLedger(): MetricsLedger | null {
272
286
  return ledger;
273
287
  }
274
288
 
289
+ // ─── Scope-aware API (canonical) ─────────────────────────────────────────────
290
+
291
+ /**
292
+ * Initialize the metrics system for a given workspace scope.
293
+ * Loads existing ledger from disk into the per-scope ledger map.
294
+ * Does NOT touch the module-level singleton.
295
+ */
296
+ export function initMetricsByScope(scope: MilestoneScope): void {
297
+ const base = scope.workspace.projectRoot;
298
+ const loaded = loadLedger(base);
299
+ scopedLedgers.set(scope.workspace.identityKey, loaded);
300
+ }
301
+
302
+ /**
303
+ * Get the in-memory ledger for the given scope, or null if not initialized.
304
+ */
305
+ export function getLedgerByScope(scope: MilestoneScope): MetricsLedger | null {
306
+ return scopedLedgers.get(scope.workspace.identityKey) ?? null;
307
+ }
308
+
309
+ /**
310
+ * Reset scoped in-memory state for a workspace. Called when auto-mode stops.
311
+ */
312
+ export function resetMetricsByScope(scope: MilestoneScope): void {
313
+ scopedLedgers.delete(scope.workspace.identityKey);
314
+ }
315
+
316
+ /**
317
+ * Snapshot usage metrics using an explicit workspace scope.
318
+ *
319
+ * This is the canonical variant. It derives the metrics path from
320
+ * scope.workspace.projectRoot rather than the module singleton, so it
321
+ * remains correct across session resume and in multi-workspace processes.
322
+ *
323
+ * Preserves the atomic write-merge logic from saveLedger so concurrent
324
+ * workers cannot silently discard each other's entries.
325
+ *
326
+ * If initMetricsByScope has not been called, the ledger is loaded from
327
+ * disk on first call (lazy init).
328
+ */
329
+ export function snapshotUnitMetricsByScope(
330
+ scope: MilestoneScope,
331
+ ctx: ExtensionContext,
332
+ unitType: string,
333
+ unitId: string,
334
+ startedAt: number,
335
+ model: string,
336
+ opts?: {
337
+ tier?: string;
338
+ modelDowngraded?: boolean;
339
+ contextWindowTokens?: number;
340
+ truncationSections?: number;
341
+ continueHereFired?: boolean;
342
+ promptCharCount?: number;
343
+ baselineCharCount?: number;
344
+ autoSessionKey?: string;
345
+ traceId?: string;
346
+ turnId?: string;
347
+ causedBy?: string;
348
+ },
349
+ ): UnitMetrics | null {
350
+ const base = scope.workspace.projectRoot;
351
+ const key = scope.workspace.identityKey;
352
+
353
+ // Lazy init: load from disk if not yet in scoped map.
354
+ if (!scopedLedgers.has(key)) {
355
+ scopedLedgers.set(key, loadLedger(base));
356
+ }
357
+ const scopedLedger = scopedLedgers.get(key)!;
358
+
359
+ const entries = ctx.sessionManager.getEntries();
360
+ if (!entries || entries.length === 0) return null;
361
+
362
+ const tokens: TokenCounts = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
363
+ let cost = 0;
364
+ let toolCalls = 0;
365
+ let assistantMessages = 0;
366
+ let userMessages = 0;
367
+
368
+ for (const entry of entries) {
369
+ if (entry.type !== "message") continue;
370
+ const msg = (entry as any).message;
371
+ if (!msg) continue;
372
+
373
+ if (msg.role === "assistant") {
374
+ assistantMessages++;
375
+ if (msg.usage) {
376
+ tokens.input += msg.usage.input ?? 0;
377
+ tokens.output += msg.usage.output ?? 0;
378
+ tokens.cacheRead += msg.usage.cacheRead ?? 0;
379
+ tokens.cacheWrite += msg.usage.cacheWrite ?? 0;
380
+ tokens.total += msg.usage.totalTokens ?? 0;
381
+ if (msg.usage.cost != null) {
382
+ const c = msg.usage.cost;
383
+ cost += typeof c === "number" ? c : (c.total ?? 0);
384
+ }
385
+ }
386
+ if (msg.content && Array.isArray(msg.content)) {
387
+ for (const block of msg.content) {
388
+ if (block.type === "toolCall") toolCalls++;
389
+ }
390
+ }
391
+ } else if (msg.role === "user") {
392
+ userMessages++;
393
+ }
394
+ }
395
+
396
+ const unit: UnitMetrics = {
397
+ type: unitType,
398
+ id: unitId,
399
+ model,
400
+ startedAt,
401
+ finishedAt: Date.now(),
402
+ ...(opts?.autoSessionKey ? { autoSessionKey: opts.autoSessionKey } : {}),
403
+ tokens,
404
+ cost,
405
+ toolCalls,
406
+ assistantMessages,
407
+ userMessages,
408
+ apiRequests: assistantMessages,
409
+ ...(opts?.tier ? { tier: opts.tier } : {}),
410
+ ...(opts?.modelDowngraded !== undefined ? { modelDowngraded: opts.modelDowngraded } : {}),
411
+ ...(opts?.contextWindowTokens !== undefined ? { contextWindowTokens: opts.contextWindowTokens } : {}),
412
+ ...(opts?.truncationSections !== undefined ? { truncationSections: opts.truncationSections } : {}),
413
+ ...(opts?.continueHereFired !== undefined ? { continueHereFired: opts.continueHereFired } : {}),
414
+ ...(opts?.promptCharCount != null ? { promptCharCount: opts.promptCharCount } : {}),
415
+ ...(opts?.baselineCharCount != null ? { baselineCharCount: opts.baselineCharCount } : {}),
416
+ };
417
+
418
+ // Auto-capture skill telemetry (#599)
419
+ const skills = getAndClearSkills();
420
+ if (skills.length > 0) {
421
+ unit.skills = skills;
422
+ }
423
+
424
+ // Compute cache hit rate
425
+ if (tokens.cacheRead > 0 || tokens.input > 0) {
426
+ const totalInput = tokens.cacheRead + tokens.input;
427
+ unit.cacheHitRate = totalInput > 0 ? Math.round((tokens.cacheRead / totalInput) * 100) : 0;
428
+ }
429
+
430
+ // Idempotency guard: update in-place on duplicate, append otherwise.
431
+ const dupeIdx = scopedLedger.units.findIndex(
432
+ (u) => u.type === unit.type && u.id === unit.id && u.startedAt === unit.startedAt,
433
+ );
434
+ if (dupeIdx >= 0) {
435
+ scopedLedger.units[dupeIdx] = unit;
436
+ } else {
437
+ scopedLedger.units.push(unit);
438
+ }
439
+ saveLedger(base, scopedLedger);
440
+
441
+ if (isUnifiedAuditEnabled()) {
442
+ emitUokAuditEvent(
443
+ base,
444
+ buildAuditEnvelope({
445
+ traceId: opts?.traceId ?? `metrics:${unitType}:${unitId}`,
446
+ turnId: opts?.turnId,
447
+ causedBy: opts?.causedBy,
448
+ category: "metrics",
449
+ type: "unit-metrics-snapshot",
450
+ payload: {
451
+ unitType,
452
+ unitId,
453
+ model,
454
+ tokens: unit.tokens,
455
+ cost: unit.cost,
456
+ toolCalls: unit.toolCalls,
457
+ },
458
+ }),
459
+ );
460
+ }
461
+
462
+ return unit;
463
+ }
464
+
275
465
  // ─── Aggregation helpers ──────────────────────────────────────────────────────
276
466
 
277
467
  export interface PhaseAggregate {
@@ -593,6 +783,12 @@ export function pruneMetricsLedger(base: string, keepCount: number): number {
593
783
  if (ledger) {
594
784
  ledger.units = ledger.units.slice(-keepCount);
595
785
  }
786
+ // Invalidate all scoped ledger cache entries. Prune is rare; clearing the
787
+ // entire map is simpler than tracking which entry belongs to `base`. Without
788
+ // this, scopedLedgers entries for the pruned workspace hold a pre-prune
789
+ // MetricsLedger that snapshotUnitMetricsByScope would merge back in, causing
790
+ // pruned units to reappear in subsequent snapshots.
791
+ scopedLedgers.clear();
596
792
  return removed;
597
793
  }
598
794
 
@@ -635,6 +831,130 @@ function deduplicateUnits(units: UnitMetrics[]): UnitMetrics[] {
635
831
  return Array.from(map.values());
636
832
  }
637
833
 
834
+ // How long a lock file must be untouched (in ms) before it is considered
835
+ // orphaned from a crashed process. Set to 2× the acquire timeout.
836
+ export const STALE_LOCK_THRESHOLD_MS = 4000;
837
+
838
+ // Retry interval between lock acquire attempts (ms). Caps syscall rate at
839
+ // ~200 attempts over a 2s timeout instead of ~20,000 without any sleep.
840
+ // Exposed for tests.
841
+ export const LOCK_RETRY_INTERVAL_MS = 5;
842
+
843
+ // Sync sleep via Atomics.wait — true OS-level sleep, no CPU spin.
844
+ // Int32Array must reference a SharedArrayBuffer; we wait on index 0 which
845
+ // will never be woken by a Atomics.notify, so the wait always times out.
846
+ const _lockSleepBuf = new Int32Array(new SharedArrayBuffer(4));
847
+ function syncSleep(ms: number): void {
848
+ Atomics.wait(_lockSleepBuf, 0, 0, ms);
849
+ }
850
+
851
+ // Counts the number of sleepy retries (non-stale-evicting) made by acquireLock
852
+ // across all calls since the last reset. Exported for test instrumentation only.
853
+ let _lockSleepyRetries = 0;
854
+ export function getLockSleepyRetries(): number { return _lockSleepyRetries; }
855
+ export function resetLockSleepyRetries(): void { _lockSleepyRetries = 0; }
856
+
857
+ /**
858
+ * Acquire an exclusive .lock sentinel file via O_EXCL.
859
+ *
860
+ * Improvements over the original:
861
+ * - No busy spin: the inner `while (Date.now() < waitUntil) {}` spin that
862
+ * burned CPU doing nothing useful is removed. Each retry attempt now makes
863
+ * one `openSync` syscall and immediately re-checks the deadline, which is
864
+ * orders of magnitude cheaper than a tight spin loop.
865
+ * - Stale-lock detection: if the existing lock file's mtime is older than
866
+ * STALE_LOCK_THRESHOLD_MS, the lock is considered orphaned (e.g. the
867
+ * writing process crashed) and is forcibly removed before retrying.
868
+ * A warning is logged so operators can detect crash patterns.
869
+ * - PID stamp: on success, writes the acquiring process's PID and a
870
+ * timestamp into the lock file so external monitors can identify orphans.
871
+ * - Retry sleep: after each non-stale-evicting retry, sleeps
872
+ * LOCK_RETRY_INTERVAL_MS (5ms) via Atomics.wait so the process yields to
873
+ * the OS. This caps syscall rate at ~200–400/s under contention instead of
874
+ * the ~20,000/s that would result from a tight openSync loop.
875
+ * After a stale-lock eviction (lock already removed), no sleep is injected
876
+ * — we retry immediately to close the short race window.
877
+ *
878
+ * Returns true on success, false on timeout.
879
+ */
880
+ function acquireLock(lockPath: string, timeoutMs = 2000): boolean {
881
+ const deadline = Date.now() + timeoutMs;
882
+ while (Date.now() < deadline) {
883
+ try {
884
+ const fd = openSync(lockPath, "wx"); // O_WRONLY | O_CREAT | O_EXCL
885
+ closeSync(fd);
886
+ // Write PID stamp so external monitors can identify the lock owner.
887
+ try {
888
+ writeFileSync(lockPath, `${process.pid}\n${new Date().toISOString()}\n`, "utf-8");
889
+ } catch { /* non-fatal — stamp is diagnostic only */ }
890
+ return true;
891
+ } catch {
892
+ // Lock held by another process — check for staleness before retrying.
893
+ try {
894
+ const st = statSync(lockPath);
895
+ if (Date.now() - st.mtimeMs > STALE_LOCK_THRESHOLD_MS) {
896
+ logWarning(
897
+ "fs",
898
+ `stale metrics lock at ${lockPath} (age ${Date.now() - st.mtimeMs}ms); forcibly removing and retrying`,
899
+ );
900
+ try { unlinkSync(lockPath); } catch { /* already gone */ }
901
+ // Do NOT sleep after stale-lock eviction — retry the open
902
+ // immediately. The lock file was just removed; a short race window
903
+ // exists and sleeping here would unnecessarily delay recovery.
904
+ continue;
905
+ }
906
+ } catch { /* lock file disappeared between the failed open and stat — retry */ }
907
+ // Sleep between retries to yield to the OS and cap syscall rate.
908
+ // Uses Atomics.wait for a true blocking sleep (no CPU spin).
909
+ _lockSleepyRetries++;
910
+ syncSleep(LOCK_RETRY_INTERVAL_MS);
911
+ }
912
+ }
913
+ return false;
914
+ }
915
+
916
+ function releaseLock(lockPath: string): void {
917
+ try { unlinkSync(lockPath); } catch { /* ignore */ }
918
+ }
919
+
920
+ /**
921
+ * Save the ledger with cross-process merge semantics.
922
+ *
923
+ * Acquires a .lock sentinel file, reads the current on-disk ledger,
924
+ * merges worker units with existing peer units (worker's entry wins on
925
+ * type+id+startedAt conflict since it has the latest finishedAt),
926
+ * then writes atomically. This prevents parallel auto-mode workers from
927
+ * silently discarding each other's metrics entries.
928
+ *
929
+ * Falls back to a direct write (no merge) if the lock cannot be acquired
930
+ * within the timeout — better to potentially overwrite than to lose data
931
+ * entirely.
932
+ */
638
933
  function saveLedger(base: string, data: MetricsLedger): void {
639
- saveJsonFile(metricsPath(base), data);
934
+ const path = metricsPath(base);
935
+ const lockPath = `${path}.lock`;
936
+ const acquired = acquireLock(lockPath);
937
+ if (acquired) {
938
+ try {
939
+ // Read current on-disk state and merge with worker's in-memory units.
940
+ // Worker units take precedence on conflict (by finishedAt in deduplicateUnits).
941
+ const onDisk = loadJsonFileOrNull(path, isMetricsLedger);
942
+ if (onDisk && onDisk.units.length > 0) {
943
+ const merged = deduplicateUnits([...onDisk.units, ...data.units]);
944
+ saveJsonFile(path, { ...data, units: merged });
945
+ } else {
946
+ saveJsonFile(path, data);
947
+ }
948
+ } finally {
949
+ releaseLock(lockPath);
950
+ }
951
+ } else {
952
+ // Lock could not be acquired within the timeout. Fall back to a direct
953
+ // write (no cross-process merge) to avoid losing this worker's data
954
+ // entirely. A concurrent writer may overwrite us, but that is preferable
955
+ // to a torn write caused by two writers simultaneously executing the
956
+ // read-merge-write sequence without mutual exclusion.
957
+ logWarning("fs", "saveLedger: lock not acquired — falling back to direct write (no merge)");
958
+ saveJsonFile(path, data);
959
+ }
640
960
  }
@@ -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`;