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
@@ -20,7 +20,7 @@ import {
20
20
  insertSlice,
21
21
  insertTask,
22
22
  } from '../gsd-db.ts';
23
- import { migrateHierarchyToDb } from '../md-importer.ts';
23
+ import { migrateFromMarkdown, migrateHierarchyToDb } from '../md-importer.ts';
24
24
  import type { GSDState } from '../types.ts';
25
25
 
26
26
  // ─── Fixture Helpers ───────────────────────────────────────────────────────
@@ -479,12 +479,13 @@ skills_used: []
479
479
 
480
480
  // Step 2: Migrate markdown to DB
481
481
  openDatabase(':memory:');
482
- const counts = migrateHierarchyToDb(base);
482
+ const counts = migrateFromMarkdown(base);
483
483
 
484
484
  // Verify migration populated correctly
485
- assert.ok(counts.milestones >= 1, 'G-roundtrip: migrated milestones');
486
- assert.ok(counts.slices >= 2, 'G-roundtrip: migrated slices');
487
- assert.ok(counts.tasks >= 3, 'G-roundtrip: migrated tasks');
485
+ assert.ok(counts.hierarchy.milestones >= 1, 'G-roundtrip: migrated milestones');
486
+ assert.ok(counts.hierarchy.slices >= 2, 'G-roundtrip: migrated slices');
487
+ assert.ok(counts.hierarchy.tasks >= 3, 'G-roundtrip: migrated tasks');
488
+ assert.equal(counts.requirements, 3, 'G-roundtrip: migrated requirements');
488
489
 
489
490
  // Step 3: Get DB-backed state
490
491
  invalidateStateCache();
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * derive-state-db-disk-reconcile.test.ts — #2416
3
3
  *
4
- * After migration to DB-backed state, milestones that exist on disk
5
- * (in .gsd/milestones/) but were never imported into the DB become
6
- * invisible to deriveStateFromDb(). This test verifies that
7
- * deriveStateFromDb reconciles disk milestones with DB milestones.
4
+ * DB-authoritative state: milestones that exist only as markdown projections
5
+ * are not imported by deriveStateFromDb(). Explicit migration/import is the
6
+ * only markdown-to-DB path.
8
7
  */
9
8
 
10
9
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
@@ -17,7 +16,6 @@ import {
17
16
  closeDatabase,
18
17
  insertMilestone,
19
18
  insertSlice,
20
- insertTask,
21
19
  } from "../gsd-db.ts";
22
20
  import { createTestContext } from "./test-helpers.ts";
23
21
 
@@ -58,7 +56,7 @@ const ROADMAP_CONTENT = `# M002: Disk-Only Milestone
58
56
  `;
59
57
 
60
58
  async function main(): Promise<void> {
61
- console.log("\n=== #2416: deriveStateFromDb reconciles disk milestones ===");
59
+ console.log("\n=== deriveStateFromDb does not reconcile disk milestones ===");
62
60
 
63
61
  // Set up: M001 in DB, M002 on disk only
64
62
  const base = createFixtureBase();
@@ -81,12 +79,9 @@ async function main(): Promise<void> {
81
79
  invalidateStateCache();
82
80
  const state = await deriveStateFromDb(base);
83
81
 
84
- // M002 should be visible in the registry
82
+ // M002 is disk-only and should not be visible in DB-backed state.
85
83
  const m002Entry = state.registry.find((m) => m.id === "M002");
86
- assertTrue(
87
- m002Entry !== undefined,
88
- "M002 (disk-only milestone) should appear in state.registry (#2416)",
89
- );
84
+ assertEq(m002Entry, undefined, "M002 disk-only milestone should not appear in DB-backed state");
90
85
 
91
86
  // M001 should still be in the registry
92
87
  const m001Entry = state.registry.find((m) => m.id === "M001");
@@ -95,24 +90,14 @@ async function main(): Promise<void> {
95
90
  "M001 (DB milestone) should still appear in state.registry",
96
91
  );
97
92
 
98
- // The active milestone should be M002 (since M001 is complete)
99
- assertTrue(
100
- state.activeMilestone !== null,
101
- "There should be an active milestone",
102
- );
103
- if (state.activeMilestone) {
104
- assertEq(
105
- state.activeMilestone.id,
106
- "M002",
107
- "Active milestone should be M002 (disk-only, not complete) (#2416)",
108
- );
109
- }
93
+ assertEq(state.activeMilestone, null, "No active milestone should be inferred from disk-only markdown");
94
+ assertEq(state.phase, "complete", "DB-only complete milestone drives complete state");
110
95
  } finally {
111
96
  closeDatabase();
112
97
  cleanup(base);
113
98
  }
114
99
 
115
- console.log("\n=== #4974: summary-only disk milestones keep parsed title ===");
100
+ console.log("\n=== summary-only disk milestones are not imported ===");
116
101
 
117
102
  {
118
103
  const summaryOnlyBase = createFixtureBase();
@@ -132,20 +117,7 @@ async function main(): Promise<void> {
132
117
  const state = await deriveStateFromDb(summaryOnlyBase);
133
118
  const m002Entry = state.registry.find((m) => m.id === "M002");
134
119
 
135
- assertTrue(
136
- m002Entry !== undefined,
137
- "M002 summary-only disk milestone should appear in state.registry (#4974)",
138
- );
139
- assertEq(
140
- m002Entry?.title,
141
- "Summary-Only Milestone",
142
- "M002 summary-only disk milestone should use parsed SUMMARY title (#4974)",
143
- );
144
- assertEq(
145
- m002Entry?.status,
146
- "complete",
147
- "M002 summary-only disk milestone should reconcile as complete (#4974)",
148
- );
120
+ assertEq(m002Entry, undefined, "M002 summary-only disk milestone should not appear without explicit import");
149
121
  } finally {
150
122
  closeDatabase();
151
123
  cleanup(summaryOnlyBase);
@@ -11,6 +11,8 @@ import {
11
11
  insertArtifact,
12
12
  isDbAvailable,
13
13
  insertMilestone,
14
+ insertRequirement,
15
+ insertAssessment,
14
16
  getAllMilestones,
15
17
  insertSlice,
16
18
  insertTask,
@@ -47,6 +49,23 @@ function insertArtifactRow(relativePath: string, content: string, opts?: {
47
49
  });
48
50
  }
49
51
 
52
+ function insertRequirementRow(id: string, status: string): void {
53
+ insertRequirement({
54
+ id,
55
+ class: 'functional',
56
+ status,
57
+ description: `${id} ${status}`,
58
+ why: 'test',
59
+ source: 'test',
60
+ primary_owner: '',
61
+ supporting_slices: '',
62
+ validation: '',
63
+ notes: '',
64
+ full_content: '',
65
+ superseded_by: null,
66
+ });
67
+ }
68
+
50
69
  function cleanup(base: string): void {
51
70
  rmSync(base, { recursive: true, force: true });
52
71
  }
@@ -114,9 +133,9 @@ describe('derive-state-db', async () => {
114
133
  writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
115
134
  writeFile(base, 'REQUIREMENTS.md', REQUIREMENTS_CONTENT);
116
135
 
117
- // Derive state from files only (no DB)
136
+ // Derive state from the explicit legacy file-only path (no DB)
118
137
  invalidateStateCache();
119
- const fileState = await deriveState(base);
138
+ const fileState = await _deriveStateImpl(base);
120
139
 
121
140
  // Now open DB, insert matching artifacts + milestone hierarchy
122
141
  openDatabase(':memory:');
@@ -128,6 +147,9 @@ describe('derive-state-db', async () => {
128
147
  insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second Slice', status: 'pending', risk: 'low', depends: ['S01'] });
129
148
  insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First Task', status: 'pending' });
130
149
  insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Done Task', status: 'complete' });
150
+ insertRequirementRow('R001', 'active');
151
+ insertRequirementRow('R002', 'active');
152
+ insertRequirementRow('R003', 'validated');
131
153
 
132
154
  insertArtifactRow('milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT, {
133
155
  artifact_type: 'roadmap',
@@ -174,10 +196,12 @@ describe('derive-state-db', async () => {
174
196
  }
175
197
  });
176
198
 
177
- // ─── Test 2: Fallback when DB unavailable ─────────────────────────────
178
- test('derive-state-db: fallback when DB unavailable', async () => {
199
+ // ─── Test 2: DB-unavailable runtime does not derive from markdown ──────
200
+ test('derive-state-db: DB-unavailable runtime does not derive from markdown by default', async () => {
179
201
  const base = createFixtureBase();
202
+ const prev = process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK;
180
203
  try {
204
+ process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK = '0';
181
205
  writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
182
206
  writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
183
207
  writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
@@ -188,17 +212,49 @@ describe('derive-state-db', async () => {
188
212
  invalidateStateCache();
189
213
  const state = await deriveState(base);
190
214
 
215
+ assert.deepStrictEqual(state.phase, 'pre-planning', 'runtime degrade: phase is pre-planning');
216
+ assert.deepStrictEqual(state.activeMilestone, null, 'runtime degrade: markdown milestone is not imported');
217
+ assert.deepStrictEqual(state.activeSlice, null, 'runtime degrade: markdown slice is not imported');
218
+ assert.deepStrictEqual(state.activeTask, null, 'runtime degrade: markdown task is not imported');
219
+ assert.ok(
220
+ state.blockers.some(b => b.includes('DB unavailable')),
221
+ 'runtime degrade: blocker explains unavailable DB',
222
+ );
223
+ } finally {
224
+ if (prev === undefined) delete process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK;
225
+ else process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK = prev;
226
+ cleanup(base);
227
+ }
228
+ });
229
+
230
+ test('derive-state-db: explicit legacy markdown fallback remains opt-in', async () => {
231
+ const base = createFixtureBase();
232
+ const prev = process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK;
233
+ try {
234
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
235
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
236
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
237
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
238
+
239
+ process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK = '1';
240
+
241
+ assert.ok(!isDbAvailable(), 'fallback: DB is not available');
242
+ invalidateStateCache();
243
+ const state = await deriveState(base);
244
+
191
245
  assert.deepStrictEqual(state.phase, 'executing', 'fallback: phase is executing');
192
246
  assert.deepStrictEqual(state.activeMilestone?.id, 'M001', 'fallback: activeMilestone is M001');
193
247
  assert.deepStrictEqual(state.activeSlice?.id, 'S01', 'fallback: activeSlice is S01');
194
248
  assert.deepStrictEqual(state.activeTask?.id, 'T01', 'fallback: activeTask is T01');
195
249
  } finally {
250
+ if (prev === undefined) delete process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK;
251
+ else process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK = prev;
196
252
  cleanup(base);
197
253
  }
198
254
  });
199
255
 
200
- // ─── Test 3: Empty DB falls back to file reads ────────────────────────
201
- test('derive-state-db: empty DB falls back to files', async () => {
256
+ // ─── Test 3: Empty DB remains authoritative ───────────────────────────
257
+ test('derive-state-db: empty DB does not import markdown milestones', async () => {
202
258
  const base = createFixtureBase();
203
259
  try {
204
260
  writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
@@ -206,21 +262,16 @@ describe('derive-state-db', async () => {
206
262
  writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
207
263
  writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
208
264
 
209
- // Open DB but insert nothing — empty tables.
210
- // With #2631 fix, deriveState will sync disk milestones into DB
211
- // and then take the DB path. The result should still reflect the
212
- // disk milestone correctly.
265
+ // Open DB but insert nothing — empty DB is authoritative at runtime.
213
266
  openDatabase(':memory:');
214
267
  assert.ok(isDbAvailable(), 'empty-db: DB is available');
215
268
 
216
269
  invalidateStateCache();
217
270
  const state = await deriveState(base);
218
271
 
219
- // Milestone should be detected (synced from disk)
220
- assert.deepStrictEqual(state.activeMilestone?.id, 'M001', 'empty-db: activeMilestone is M001');
221
- // The DB path without explicit slice/task rows may derive a different
222
- // phase than the filesystem path, but the milestone must be found.
223
- assert.ok(state.activeMilestone !== null, 'empty-db: activeMilestone is not null');
272
+ assert.deepStrictEqual(getAllMilestones().length, 0, 'empty-db: markdown milestones are not imported');
273
+ assert.deepStrictEqual(state.activeMilestone, null, 'empty-db: no active milestone from disk');
274
+ assert.deepStrictEqual(state.registry, [], 'empty-db: registry remains empty');
224
275
 
225
276
  closeDatabase();
226
277
  } finally {
@@ -229,8 +280,8 @@ describe('derive-state-db', async () => {
229
280
  }
230
281
  });
231
282
 
232
- // ─── Test 4: Partial DB content fills gaps from disk ──────────────────
233
- test('derive-state-db: partial DB fills gaps from disk', async () => {
283
+ // ─── Test 4: Partial DB content does not fill gaps from disk ──────────
284
+ test('derive-state-db: partial DB does not fill requirements from disk', async () => {
234
285
  const base = createFixtureBase();
235
286
  try {
236
287
  // Write all files to disk
@@ -254,15 +305,14 @@ describe('derive-state-db', async () => {
254
305
  invalidateStateCache();
255
306
  const state = await deriveState(base);
256
307
 
257
- // Should work: roadmap from DB, plan from disk fallback
308
+ // Should work from DB hierarchy, but requirements are DB-authoritative.
258
309
  assert.deepStrictEqual(state.phase, 'executing', 'partial-db: phase is executing');
259
310
  assert.deepStrictEqual(state.activeMilestone?.id, 'M001', 'partial-db: activeMilestone is M001');
260
311
  assert.deepStrictEqual(state.activeSlice?.id, 'S01', 'partial-db: activeSlice is S01');
261
312
  assert.deepStrictEqual(state.activeTask?.id, 'T01', 'partial-db: activeTask is T01');
262
- // Requirements loaded from disk fallback
263
- assert.deepStrictEqual(state.requirements?.active, 2, 'partial-db: requirements.active from disk');
264
- assert.deepStrictEqual(state.requirements?.validated, 1, 'partial-db: requirements.validated from disk');
265
- assert.deepStrictEqual(state.requirements?.total, 3, 'partial-db: requirements.total from disk');
313
+ assert.deepStrictEqual(state.requirements?.active, 0, 'partial-db: requirements.active not imported from disk');
314
+ assert.deepStrictEqual(state.requirements?.validated, 0, 'partial-db: requirements.validated not imported from disk');
315
+ assert.deepStrictEqual(state.requirements?.total, 0, 'partial-db: requirements.total not imported from disk');
266
316
 
267
317
  closeDatabase();
268
318
  } finally {
@@ -271,7 +321,7 @@ describe('derive-state-db', async () => {
271
321
  }
272
322
  });
273
323
 
274
- test('derive-state-db: partial task rows reconcile missing plan tasks before summarizing', async (t) => {
324
+ test('derive-state-db: partial task rows do not import missing plan tasks', async (t) => {
275
325
  const base = createFixtureBase();
276
326
  t.after(() => {
277
327
  closeDatabase();
@@ -306,15 +356,39 @@ describe('derive-state-db', async () => {
306
356
  const state = await deriveState(base);
307
357
 
308
358
  const dbTasks = getSliceTasks('M001', 'S01');
309
- assert.deepStrictEqual(dbTasks.length, 2, 'partial-task-db: missing T02 imported from plan');
359
+ assert.deepStrictEqual(dbTasks.length, 1, 'partial-task-db: missing T02 is not imported from plan');
310
360
  assert.deepStrictEqual(dbTasks.find(t => t.id === 'T01')?.status, 'complete', 'partial-task-db: existing complete T01 preserved');
311
- assert.deepStrictEqual(dbTasks.find(t => t.id === 'T02')?.status, 'pending', 'partial-task-db: missing T02 inserted pending');
312
- assert.deepStrictEqual(state.phase, 'executing', 'partial-task-db: phase remains executing, not summarizing');
313
- assert.deepStrictEqual(state.activeTask?.id, 'T02', 'partial-task-db: activeTask is missing plan task T02');
314
- assert.deepStrictEqual(state.progress?.tasks, { done: 1, total: 2 }, 'partial-task-db: task progress includes reconciled plan task');
361
+ assert.deepStrictEqual(dbTasks.find(t => t.id === 'T02'), undefined, 'partial-task-db: missing T02 absent from DB');
362
+ assert.deepStrictEqual(state.phase, 'summarizing', 'partial-task-db: phase follows DB tasks only');
363
+ assert.deepStrictEqual(state.activeTask, null, 'partial-task-db: no active task from disk-only plan row');
364
+ assert.deepStrictEqual(state.progress?.tasks, { done: 1, total: 1 }, 'partial-task-db: task progress is DB-only');
315
365
  });
316
366
 
317
- test('derive-state-db: empty DB disk import preserves milestone completion and dependencies', async (t) => {
367
+ test('derive-state-db: disk SUMMARY does not complete a pending DB task', async (t) => {
368
+ const base = createFixtureBase();
369
+ t.after(() => {
370
+ closeDatabase();
371
+ cleanup(base);
372
+ });
373
+
374
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
375
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
376
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T01-SUMMARY.md', '# T01 Summary\n\nManual disk edit.');
377
+
378
+ openDatabase(':memory:');
379
+ insertMilestone({ id: 'M001', title: 'Test Milestone', status: 'active' });
380
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First Slice', status: 'active', risk: 'low', depends: [] });
381
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First Task', status: 'pending' });
382
+
383
+ invalidateStateCache();
384
+ const state = await deriveStateFromDb(base);
385
+
386
+ assert.deepStrictEqual(getSliceTasks('M001', 'S01').find(t => t.id === 'T01')?.status, 'pending');
387
+ assert.deepStrictEqual(state.phase, 'executing');
388
+ assert.deepStrictEqual(state.activeTask?.id, 'T01');
389
+ });
390
+
391
+ test('derive-state-db: empty DB does not import milestone completion and dependencies', async (t) => {
318
392
  const base = createFixtureBase();
319
393
  t.after(() => {
320
394
  closeDatabase();
@@ -350,16 +424,13 @@ describe('derive-state-db', async () => {
350
424
  const state = await deriveState(base);
351
425
 
352
426
  const milestones = getAllMilestones();
353
- assert.deepStrictEqual(milestones.find(m => m.id === 'M001')?.status, 'complete', 'disk-import: terminal summary imports M001 as complete');
354
- assert.deepStrictEqual(milestones.find(m => m.id === 'M002')?.depends_on, ['M001'], 'disk-import: M002 depends_on imported from CONTEXT');
355
- assert.deepStrictEqual(milestones.find(m => m.id === 'M003')?.depends_on, ['M002'], 'disk-import: M003 depends_on imported from CONTEXT');
356
- assert.deepStrictEqual(state.activeMilestone?.id, 'M002', 'disk-import: M002 is active because M001 completion satisfies dependency');
357
- assert.deepStrictEqual(state.registry.find(e => e.id === 'M001')?.status, 'complete', 'disk-import: M001 registry status is complete');
358
- assert.deepStrictEqual(state.registry.find(e => e.id === 'M003')?.status, 'pending', 'disk-import: M003 stays pending on unmet M002 dependency');
427
+ assert.deepStrictEqual(milestones.length, 0, 'disk-import: markdown milestones are not imported');
428
+ assert.deepStrictEqual(state.activeMilestone, null, 'disk-import: no active milestone from markdown');
429
+ assert.deepStrictEqual(state.registry, [], 'disk-import: registry remains DB-only');
359
430
  });
360
431
 
361
- // ─── Test 5: Requirements counting from disk (DB no longer used for content) ─
362
- test('derive-state-db: requirements from disk content', async () => {
432
+ // ─── Test 5: Legacy requirements counting from disk ────────────────────
433
+ test('derive-state-db: explicit legacy derivation counts requirements from disk content', async () => {
363
434
  const base = createFixtureBase();
364
435
  try {
365
436
  // Write minimal milestone dir (needed for milestone discovery)
@@ -368,9 +439,9 @@ describe('derive-state-db', async () => {
368
439
  writeFile(base, 'REQUIREMENTS.md', REQUIREMENTS_CONTENT);
369
440
 
370
441
  invalidateStateCache();
371
- const state = await deriveState(base);
442
+ const state = await _deriveStateImpl(base);
372
443
 
373
- // Requirements should come from disk
444
+ // Explicit legacy derivation still reads requirements from disk.
374
445
  assert.deepStrictEqual(state.requirements?.active, 2, 'req-from-disk: requirements.active = 2');
375
446
  assert.deepStrictEqual(state.requirements?.validated, 1, 'req-from-disk: requirements.validated = 1');
376
447
  assert.deepStrictEqual(state.requirements?.total, 3, 'req-from-disk: requirements.total = 3');
@@ -805,6 +876,13 @@ describe('derive-state-db', async () => {
805
876
  openDatabase(':memory:');
806
877
  insertMilestone({ id: 'M001', title: 'Stuck Remediation', status: 'active' });
807
878
  insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Done Slice', status: 'complete', risk: 'low', depends: [] });
879
+ insertAssessment({
880
+ path: 'milestones/M001/M001-VALIDATION.md',
881
+ milestoneId: 'M001',
882
+ status: 'needs-remediation',
883
+ scope: 'milestone-validation',
884
+ fullContent: 'verdict: needs-remediation',
885
+ });
808
886
 
809
887
  invalidateStateCache();
810
888
  const dbState = await deriveStateFromDb(base);
@@ -846,6 +924,13 @@ describe('derive-state-db', async () => {
846
924
  openDatabase(':memory:');
847
925
  insertMilestone({ id: 'M001', title: 'Complete Test', status: 'active' });
848
926
  insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Done Slice', status: 'complete', risk: 'low', depends: [] });
927
+ insertAssessment({
928
+ path: 'milestones/M001/M001-VALIDATION.md',
929
+ milestoneId: 'M001',
930
+ status: 'pass',
931
+ scope: 'milestone-validation',
932
+ fullContent: 'verdict: pass',
933
+ });
849
934
 
850
935
  invalidateStateCache();
851
936
  const dbState = await deriveStateFromDb(base);
@@ -1098,8 +1183,8 @@ describe('derive-state-db', async () => {
1098
1183
  }
1099
1184
  });
1100
1185
 
1101
- // ─── Test 22: Needs-discussion — CONTEXT-DRAFT exists ─────────────────
1102
- test('derive-state-db: needs-discussion via DB', async () => {
1186
+ // ─── Test 22: Needs-discussion — DB status, not CONTEXT-DRAFT ─────────
1187
+ test('derive-state-db: needs-discussion via DB status', async () => {
1103
1188
  const base = createFixtureBase();
1104
1189
  try {
1105
1190
  writeFile(base, 'milestones/M001/M001-CONTEXT-DRAFT.md', '# M001: Draft\n\nDraft content.');
@@ -1108,7 +1193,7 @@ describe('derive-state-db', async () => {
1108
1193
  const fileState = await _deriveStateImpl(base);
1109
1194
 
1110
1195
  openDatabase(':memory:');
1111
- insertMilestone({ id: 'M001', title: 'Draft', status: 'active' });
1196
+ insertMilestone({ id: 'M001', title: 'Draft', status: 'needs-discussion' });
1112
1197
 
1113
1198
  invalidateStateCache();
1114
1199
  const dbState = await deriveStateFromDb(base);
@@ -1127,7 +1212,7 @@ describe('derive-state-db', async () => {
1127
1212
  test('derive-state-db: disk-only milestone auto-synced into DB (#2416)', async () => {
1128
1213
  const base = createFixtureBase();
1129
1214
  try {
1130
- // M001 is complete and exists in DB. M002 was queued on disk only — no DB row.
1215
+ // M001 is complete and exists in DB. M002 is disk-only — no DB row.
1131
1216
  writeFile(base, 'milestones/M001/M001-SUMMARY.md', '# M001 Summary\n\nDone.');
1132
1217
  writeFile(base, 'milestones/M002/M002-CONTEXT.md', '# M002: Queued\n\nQueued milestone.');
1133
1218
 
@@ -1138,16 +1223,12 @@ describe('derive-state-db', async () => {
1138
1223
  invalidateStateCache();
1139
1224
  const state = await deriveStateFromDb(base);
1140
1225
 
1141
- // Before the fix, M002 was invisible: getAllMilestones() returned only M001
1142
- // (complete) phase='complete' auto-mode stopped.
1143
- // After the fix, deriveStateFromDb reconciles disk dirs and inserts M002.
1144
- assert.deepStrictEqual(state.phase, 'pre-planning', 'disk-sync-2416: phase is pre-planning, not complete');
1145
- assert.deepStrictEqual(state.registry.length, 2, 'disk-sync-2416: both milestones visible in registry');
1226
+ assert.deepStrictEqual(state.phase, 'complete', 'disk-sync-2416: disk-only milestone is not imported');
1227
+ assert.deepStrictEqual(state.registry.length, 1, 'disk-sync-2416: only DB milestones visible in registry');
1146
1228
  assert.deepStrictEqual(state.registry[0]?.id, 'M001', 'disk-sync-2416: registry[0] is M001');
1147
1229
  assert.deepStrictEqual(state.registry[0]?.status, 'complete', 'disk-sync-2416: M001 is complete');
1148
- assert.deepStrictEqual(state.registry[1]?.id, 'M002', 'disk-sync-2416: registry[1] is M002');
1149
- assert.deepStrictEqual(state.registry[1]?.status, 'active', 'disk-sync-2416: M002 is active');
1150
- assert.deepStrictEqual(state.activeMilestone?.id, 'M002', 'disk-sync-2416: activeMilestone is M002');
1230
+ assert.deepStrictEqual(state.registry[1], undefined, 'disk-sync-2416: M002 remains absent without explicit import');
1231
+ assert.deepStrictEqual(state.activeMilestone, null, 'disk-sync-2416: no active milestone from disk-only row');
1151
1232
 
1152
1233
  closeDatabase();
1153
1234
  } finally {
@@ -1208,12 +1289,11 @@ describe('derive-state-db', async () => {
1208
1289
  invalidateStateCache();
1209
1290
  const dbState = await deriveStateFromDb(base);
1210
1291
 
1211
- // M002 should be reconciled from disk (not skipped as ghost) and become active
1292
+ // M002 is legitimate legacy disk state but is not authoritative without a DB row.
1212
1293
  const m002Entry = dbState.registry.find(e => e.id === 'M002');
1213
- assert.ok(m002Entry !== undefined, 'ghost-wt: M002 should be in registry');
1214
- assert.deepStrictEqual(dbState.activeMilestone?.id, 'M002', 'ghost-wt: M002 should be active');
1215
- // Should NOT be phase: complete
1216
- assert.notEqual(dbState.phase, 'complete', 'ghost-wt: phase should not be complete');
1294
+ assert.equal(m002Entry, undefined, 'ghost-wt: M002 should not be imported into registry');
1295
+ assert.deepStrictEqual(dbState.activeMilestone, null, 'ghost-wt: no active milestone from disk-only worktree');
1296
+ assert.equal(dbState.phase, 'complete', 'ghost-wt: DB-only M001 completion drives state');
1217
1297
 
1218
1298
  closeDatabase();
1219
1299
  } finally {
@@ -4,6 +4,9 @@ import { tmpdir } from 'node:os';
4
4
 
5
5
  import { deriveState } from '../state.js';
6
6
 
7
+ // This suite exercises the explicit legacy markdown derivation path.
8
+ process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK = '1';
9
+
7
10
  let passed = 0;
8
11
  let failed = 0;
9
12