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,5 +1,5 @@
1
1
  // GSD Extension — State Derivation
2
- // DB-primary state derivation with filesystem fallback for unmigrated projects.
2
+ // DB-authoritative runtime derivation with explicit legacy filesystem fallback.
3
3
  // Pure TypeScript, zero Pi dependencies.
4
4
 
5
5
  import type {
@@ -40,11 +40,10 @@ import { isClosedStatus, isDeferredStatus } from './status-guards.js';
40
40
  import { nativeBatchParseGsdFiles, type BatchParsedFile } from './native-parser-bridge.js';
41
41
 
42
42
  import { join, resolve } from 'path';
43
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
43
+ import { existsSync, readdirSync } from 'node:fs';
44
44
  import { debugCount, debugTime } from './debug-logger.js';
45
- import { logWarning, logError } from './workflow-logger.js';
45
+ import { logWarning } from './workflow-logger.js';
46
46
  import { extractVerdict } from './verdict-parser.js';
47
- import { loadEffectiveGSDPreferences } from './preferences.js';
48
47
  import { detectPendingEscalation } from './escalation.js';
49
48
  import { isTerminalMilestoneSummaryContent } from './milestone-summary-classifier.js';
50
49
 
@@ -57,13 +56,9 @@ import {
57
56
  getSliceTasks,
58
57
  getReplanHistory,
59
58
  getSlice,
60
- insertMilestone,
61
- insertSlice,
62
- insertTask,
63
- updateSliceStatus,
64
- updateTaskStatus,
59
+ getRequirementCounts,
60
+ getLatestAssessmentByScope,
65
61
  getPendingGateCountForTurn,
66
- autoHealSketchFlags,
67
62
  type MilestoneRow,
68
63
  type SliceRow,
69
64
  type TaskRow,
@@ -223,9 +218,16 @@ export function invalidateStateCache(): void {
223
218
  * Returns the ID of the first incomplete milestone, or null if all are complete.
224
219
  */
225
220
  export async function getActiveMilestoneId(basePath: string): Promise<string | null> {
226
- // Parallel worker isolation
227
- const milestoneLock = process.env.GSD_MILESTONE_LOCK;
221
+ // Parallel worker isolation. Normal DB state derivation remains DB-only;
222
+ // lock env vars are execution routing for explicit worker processes.
223
+ const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
228
224
  if (milestoneLock) {
225
+ if (isDbAvailable()) {
226
+ const locked = getAllMilestones().find(m => m.id === milestoneLock);
227
+ if (!locked || isClosedStatus(locked.status) || locked.status === "parked") return null;
228
+ return locked.id;
229
+ }
230
+
229
231
  const milestoneIds = findMilestoneIds(basePath);
230
232
  if (!milestoneIds.includes(milestoneLock)) return null;
231
233
  const lockedParked = resolveMilestoneFile(basePath, milestoneLock, "PARKED");
@@ -237,14 +239,7 @@ export async function getActiveMilestoneId(basePath: string): Promise<string | n
237
239
  if (isDbAvailable()) {
238
240
  const allMilestones = getAllMilestones();
239
241
  if (allMilestones.length > 0) {
240
- // Respect queue-order.json so /gsd queue reordering is honored (#2556).
241
- // Without this, the DB path uses lexicographic sort while the dispatch
242
- // guard uses queue order — causing a deadlock.
243
- const customOrder = loadQueueOrder(basePath);
244
- const sortedIds = sortByQueueOrder(allMilestones.map(m => m.id), customOrder);
245
- const byId = new Map(allMilestones.map(m => [m.id, m]));
246
- for (const id of sortedIds) {
247
- const m = byId.get(id)!;
242
+ for (const m of allMilestones) {
248
243
  if (isClosedStatus(m.status) || m.status === "parked") continue;
249
244
  return m.id;
250
245
  }
@@ -276,12 +271,12 @@ export async function getActiveMilestoneId(basePath: string): Promise<string | n
276
271
  }
277
272
 
278
273
  /**
279
- * Reconstruct GSD state from DB (primary) or filesystem (fallback).
274
+ * Reconstruct GSD state from the authoritative DB.
280
275
  * STATE.md is a rendered cache of this output.
281
276
  *
282
277
  * When DB is available, queries milestone/slice/task tables directly.
283
- * Falls back to filesystem parsing for unmigrated projects or when DB
284
- * has zero milestones (e.g. first run before migration).
278
+ * Legacy filesystem parsing is available only through an explicit opt-in for
279
+ * tests/recovery flows; runtime must not silently infer state from markdown.
285
280
  */
286
281
  export async function deriveState(basePath: string): Promise<GSDState> {
287
282
  // Return cached result if within the TTL window for the same basePath
@@ -296,45 +291,36 @@ export async function deriveState(basePath: string): Promise<GSDState> {
296
291
  const stopTimer = debugTime("derive-state-impl");
297
292
  let result: GSDState;
298
293
 
299
- // Dual-path: try DB-backed derivation first when hierarchy tables are populated
294
+ // DB-backed derivation is authoritative whenever the DB is open.
295
+ // Markdown fallback is explicit-only; runtime degrade must not infer state
296
+ // from ROADMAP.md, PLAN.md, SUMMARY.md, REQUIREMENTS.md, or flag files.
300
297
  if (isDbAvailable()) {
301
- let dbMilestones = getAllMilestones();
302
-
303
- // Disk→DB reconciliation when DB is empty but disk has milestones (#2631).
304
- // deriveStateFromDb() does its own reconciliation, but deriveState() skips
305
- // it entirely when the DB is empty. Sync here so the DB path is used when
306
- // disk milestones exist but haven't been migrated yet.
307
- if (dbMilestones.length === 0) {
308
- const diskIds = findMilestoneIds(basePath);
309
- let synced = false;
310
- for (const diskId of diskIds) {
311
- if (!isGhostMilestone(basePath, diskId)) {
312
- insertMilestone(diskMilestoneInsert(basePath, diskId));
313
- synced = true;
314
- }
315
- }
316
- if (synced) dbMilestones = getAllMilestones();
317
- }
318
-
319
- if (dbMilestones.length > 0) {
320
- const stopDbTimer = debugTime("derive-state-db");
321
- result = await deriveStateFromDb(basePath);
322
- stopDbTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
323
- _telemetry.dbDeriveCount++;
324
- } else {
325
- // DB open but no milestones on disk either — use filesystem path
326
- result = await _deriveStateImpl(basePath);
327
- _telemetry.markdownDeriveCount++;
328
- }
329
- } else {
330
- // Only warn when DB initialization was attempted and failed — not when
331
- // the DB simply hasn't been opened yet (e.g. during before_agent_start
332
- // context injection which runs before any tool invocation opens the DB).
298
+ const stopDbTimer = debugTime("derive-state-db");
299
+ result = await deriveStateFromDb(basePath);
300
+ stopDbTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
301
+ _telemetry.dbDeriveCount++;
302
+ } else if (process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK === "1") {
333
303
  if (wasDbOpenAttempted()) {
334
- logWarning("state", "DB unavailable — using filesystem state derivation (degraded mode)");
304
+ logWarning("state", "DB unavailable — using explicit legacy filesystem state derivation");
335
305
  }
336
306
  result = await _deriveStateImpl(basePath);
337
307
  _telemetry.markdownDeriveCount++;
308
+ } else {
309
+ if (wasDbOpenAttempted()) {
310
+ logWarning("state", "DB unavailable — refusing implicit markdown state derivation");
311
+ }
312
+ result = {
313
+ activeMilestone: null,
314
+ activeSlice: null,
315
+ activeTask: null,
316
+ phase: "pre-planning",
317
+ recentDecisions: [],
318
+ blockers: ["DB unavailable — runtime markdown state derivation is disabled"],
319
+ nextAction: "Open or create the canonical GSD database before deriving workflow state.",
320
+ registry: [],
321
+ requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
322
+ progress: { milestones: { done: 0, total: 0 } },
323
+ };
338
324
  }
339
325
 
340
326
  stopTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
@@ -369,115 +355,11 @@ function extractContextTitle(content: string | null, fallback: string): string {
369
355
  // Alias kept for backward compatibility within this file.
370
356
  const isStatusDone = isClosedStatus;
371
357
 
372
- function loadSync(path: string | null): string | null {
373
- if (!path) return null;
374
- try {
375
- return readFileSync(path, "utf-8");
376
- } catch {
377
- return null;
378
- }
379
- }
380
-
381
- function diskMilestoneInsert(basePath: string, mid: string): {
382
- id: string;
383
- title?: string;
384
- status: string;
385
- depends_on: string[];
386
- } {
387
- const contextContent = loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT"));
388
- const draftContent = !contextContent ? loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT")) : null;
389
- const roadmapContent = loadSync(resolveMilestoneFile(basePath, mid, "ROADMAP"));
390
- const summaryContent = loadSync(resolveMilestoneFile(basePath, mid, "SUMMARY"));
391
- const roadmap = roadmapContent ? parseRoadmap(roadmapContent) : null;
392
- const summary = summaryContent ? parseSummary(summaryContent) : null;
393
- const summaryTerminal = summaryContent != null && isTerminalMilestoneSummaryContent(summaryContent);
394
- const parked = resolveMilestoneFile(basePath, mid, "PARKED") !== null;
395
-
396
- return {
397
- id: mid,
398
- title: roadmap
399
- ? stripMilestonePrefix(roadmap.title)
400
- : (contextContent || draftContent)
401
- ? extractContextTitle(contextContent || draftContent, mid)
402
- : (summary?.title || mid),
403
- status: parked ? "parked" : summaryTerminal ? "complete" : "active",
404
- depends_on: parseContextDependsOn(contextContent ?? draftContent),
405
- };
406
- }
407
-
408
358
  /**
409
359
  * Derive GSD state from the milestones/slices/tasks DB tables.
410
- * Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
411
- * are still checked on the filesystem since they aren't in DB tables.
412
- * Requirements also stay file-based via parseRequirementCounts().
413
- *
414
- * Must produce field-identical GSDState to _deriveStateImpl() for the same project.
360
+ * Markdown files are projections only in this path; they are never imported,
361
+ * reconciled, or used as completion signals.
415
362
  */
416
- function reconcileDiskToDb(basePath: string): MilestoneRow[] {
417
- let allMilestones = getAllMilestones();
418
- const dbIdSet = new Set(allMilestones.map(m => m.id));
419
- const diskIds = findMilestoneIds(basePath);
420
- let synced = false;
421
- for (const diskId of diskIds) {
422
- if (!dbIdSet.has(diskId) && !isGhostMilestone(basePath, diskId)) {
423
- insertMilestone(diskMilestoneInsert(basePath, diskId));
424
- synced = true;
425
- }
426
- }
427
- if (synced) allMilestones = getAllMilestones();
428
-
429
- for (const mid of diskIds) {
430
- if (isGhostMilestone(basePath, mid)) continue;
431
- const roadmapPath = resolveMilestoneFile(basePath, mid, "ROADMAP");
432
- if (!roadmapPath) continue;
433
-
434
- const dbSlices = getMilestoneSlices(mid);
435
- const dbSliceIds = new Set(dbSlices.map(s => s.id));
436
-
437
- let roadmapContent: string;
438
- try {
439
- roadmapContent = readFileSync(roadmapPath, "utf-8");
440
- } catch (err) {
441
- logWarning("state", "reconcileDiskToDb: roadmap read failed, skipping milestone", {
442
- mid,
443
- error: (err as Error).message,
444
- });
445
- continue;
446
- }
447
-
448
- const parsed = parseRoadmap(roadmapContent);
449
- for (const s of parsed.slices) {
450
- if (dbSliceIds.has(s.id)) continue;
451
- const summaryPath = resolveSliceFile(basePath, mid, s.id, "SUMMARY");
452
- const sliceStatus = (s.done || summaryPath) ? "complete" : "pending";
453
- insertSlice({
454
- id: s.id, milestoneId: mid, title: s.title,
455
- status: sliceStatus, risk: s.risk,
456
- depends: s.depends, demo: s.demo,
457
- });
458
- }
459
-
460
- // Reconcile stale *existing* slice rows (#3599): a slice row may exist in
461
- // the DB with status "pending" even though disk artifacts (SUMMARY) prove
462
- // completion — the same class of desync that task-level reconciliation
463
- // (further below) already handles. Without this, the dependency resolver
464
- // builds doneSliceIds from stale DB rows and downstream slices stay blocked
465
- // forever with "No slice eligible".
466
- for (const dbSlice of dbSlices) {
467
- if (isStatusDone(dbSlice.status)) continue;
468
- const summaryPath = resolveSliceFile(basePath, mid, dbSlice.id, "SUMMARY");
469
- if (summaryPath) {
470
- try {
471
- updateSliceStatus(mid, dbSlice.id, "complete");
472
- logWarning("reconcile", `slice ${mid}/${dbSlice.id} status reconciled from "${dbSlice.status}" to "complete" (#3599)`, { mid, sid: dbSlice.id });
473
- } catch (e) {
474
- logError("reconcile", `failed to update slice ${dbSlice.id}`, { sid: dbSlice.id, error: (e as Error).message });
475
- }
476
- }
477
- }
478
- }
479
- return allMilestones;
480
- }
481
363
 
482
364
  function buildCompletenessSet(basePath: string, milestones: MilestoneRow[]) {
483
365
  const completeMilestoneIds = new Set<string>();
@@ -488,8 +370,7 @@ function buildCompletenessSet(basePath: string, milestones: MilestoneRow[]) {
488
370
  // (crashed complete-milestone turn, partial merge, manual edit) must not
489
371
  // flip derived state to complete and cascade into a false auto-merge (#4179).
490
372
  for (const m of milestones) {
491
- const parkedFile = resolveMilestoneFile(basePath, m.id, "PARKED");
492
- if (parkedFile || m.status === 'parked') {
373
+ if (m.status === 'parked') {
493
374
  parkedMilestoneIds.add(m.id);
494
375
  continue;
495
376
  }
@@ -521,39 +402,19 @@ async function buildRegistryAndFindActive(
521
402
  }
522
403
 
523
404
  const slices = getMilestoneSlices(m.id);
524
- if (slices.length === 0 && !isStatusDone(m.status) && m.status !== 'queued') {
525
- if (isGhostMilestone(basePath, m.id)) continue;
526
- }
527
405
 
528
406
  // DB-authoritative completeness (#4179): only trust completeMilestoneIds,
529
407
  // which is itself derived from DB status. SUMMARY-file presence alone must
530
- // not imply completion. The summary file may still be consulted below as a
531
- // title source for legitimately-complete milestones whose DB row has no title.
408
+ // not imply completion.
532
409
  if (completeMilestoneIds.has(m.id)) {
533
- let title = stripMilestonePrefix(m.title) || m.id;
534
- if (!m.title) {
535
- const summaryFile = resolveMilestoneFile(basePath, m.id, "SUMMARY");
536
- if (summaryFile) {
537
- const summaryContent = await loadFile(summaryFile);
538
- if (summaryContent) {
539
- title = parseSummary(summaryContent).title || m.id;
540
- }
541
- }
542
- }
410
+ const title = stripMilestonePrefix(m.title) || m.id;
543
411
  registry.push({ id: m.id, title, status: 'complete' });
544
412
  continue;
545
413
  }
546
414
 
547
415
  const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
548
416
 
549
- let title = stripMilestonePrefix(m.title) || m.id;
550
- if (title === m.id) {
551
- const contextFile = resolveMilestoneFile(basePath, m.id, "CONTEXT");
552
- const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
553
- const contextContent = contextFile ? await loadFile(contextFile) : null;
554
- const draftContent = draftFile && !contextContent ? await loadFile(draftFile) : null;
555
- title = extractContextTitle(contextContent || draftContent, m.id);
556
- }
417
+ const title = stripMilestonePrefix(m.title) || m.id;
557
418
 
558
419
  if (!activeMilestoneFound) {
559
420
  const deps = m.depends_on;
@@ -565,41 +426,22 @@ async function buildRegistryAndFindActive(
565
426
  }
566
427
 
567
428
  if (m.status === 'queued' && slices.length === 0) {
568
- const contextFile = resolveMilestoneFile(basePath, m.id, "CONTEXT");
569
- const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
570
- if (!contextFile && !draftFile) {
571
- if (!firstDeferredQueuedShell) {
572
- firstDeferredQueuedShell = { id: m.id, title, deps };
573
- }
574
- registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
575
- continue;
429
+ if (!firstDeferredQueuedShell) {
430
+ firstDeferredQueuedShell = { id: m.id, title, deps };
576
431
  }
432
+ registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
433
+ continue;
577
434
  }
578
435
 
579
436
  if (allSlicesDone) {
580
- const validationFile = resolveMilestoneFile(basePath, m.id, "VALIDATION");
581
- const validationContent = validationFile ? await loadFile(validationFile) : null;
582
- const validationTerminal = validationContent ? isValidationTerminal(validationContent) : false;
583
-
584
- // DB-authoritative (#4179): completeness is already decided by
585
- // completeMilestoneIds above. If we reached this branch, the DB says
586
- // the milestone is NOT complete — so any SUMMARY file on disk is an
587
- // orphan (crashed complete-milestone, partial merge, manual edit) and
588
- // must not short-circuit this path. When validation is terminal, fall
589
- // through to the default active-push below so `complete-milestone` can
590
- // re-run idempotently.
591
- if (!validationTerminal) {
592
- activeMilestone = { id: m.id, title };
593
- activeMilestoneSlices = slices;
594
- activeMilestoneFound = true;
595
- registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
596
- continue;
597
- }
437
+ activeMilestone = { id: m.id, title };
438
+ activeMilestoneSlices = slices;
439
+ activeMilestoneFound = true;
440
+ registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
441
+ continue;
598
442
  }
599
443
 
600
- const contextFile = resolveMilestoneFile(basePath, m.id, "CONTEXT");
601
- const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
602
- if (!contextFile && draftFile) activeMilestoneHasDraft = true;
444
+ if (m.status === 'needs-discussion') activeMilestoneHasDraft = true;
603
445
 
604
446
  activeMilestone = { id: m.id, title };
605
447
  activeMilestoneSlices = slices;
@@ -695,10 +537,9 @@ async function handleAllSlicesDone(
695
537
  milestoneProgress: { done: number, total: number },
696
538
  sliceProgress: { done: number, total: number }
697
539
  ): Promise<GSDState> {
698
- const validationFile = resolveMilestoneFile(basePath, activeMilestone.id, "VALIDATION");
699
- const validationContent = validationFile ? await loadFile(validationFile) : null;
700
- const validationTerminal = validationContent ? isValidationTerminal(validationContent) : false;
701
- const verdict = validationContent ? extractVerdict(validationContent) : undefined;
540
+ const validation = getLatestAssessmentByScope(activeMilestone.id, "milestone-validation");
541
+ const verdict = typeof validation?.status === "string" ? validation.status : undefined;
542
+ const validationTerminal = verdict != null && verdict !== "";
702
543
 
703
544
  if (!validationTerminal) {
704
545
  return {
@@ -744,7 +585,7 @@ function resolveSliceDependencies(activeMilestoneSlices: SliceRow[]): { activeSl
744
585
  activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id)
745
586
  );
746
587
 
747
- const sliceLock = process.env.GSD_SLICE_LOCK;
588
+ const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
748
589
  if (sliceLock) {
749
590
  const lockedSlice = activeMilestoneSlices.find(s => s.id === sliceLock);
750
591
  if (lockedSlice) {
@@ -766,117 +607,27 @@ function resolveSliceDependencies(activeMilestoneSlices: SliceRow[]): { activeSl
766
607
  return { activeSlice: null, activeSliceRow: null };
767
608
  }
768
609
 
769
- async function reconcileSliceTasks(
770
- basePath: string,
771
- milestoneId: string,
772
- sliceId: string,
773
- planFile: string
774
- ): Promise<TaskRow[]> {
775
- let tasks = getSliceTasks(milestoneId, sliceId);
776
-
777
- // #3600/#4974: import missing plan-file tasks even when the DB already has
778
- // a partial task set. Existing DB task statuses stay authoritative.
779
- if (planFile) {
780
- try {
781
- const planContent = await loadFile(planFile);
782
- if (planContent) {
783
- const diskPlan = parsePlan(planContent);
784
- if (diskPlan.tasks.length > 0) {
785
- const dbTaskIds = new Set(tasks.map(t => t.id));
786
- let inserted = 0;
787
- for (let i = 0; i < diskPlan.tasks.length; i++) {
788
- const t = diskPlan.tasks[i];
789
- if (dbTaskIds.has(t.id)) continue;
790
- try {
791
- insertTask({
792
- id: t.id,
793
- sliceId,
794
- milestoneId,
795
- title: t.title,
796
- status: t.done ? 'complete' : 'pending',
797
- sequence: i + 1,
798
- });
799
- inserted++;
800
- } catch (insertErr) {
801
- logWarning("reconcile", `failed to insert task ${t.id} from plan file: ${insertErr instanceof Error ? insertErr.message : String(insertErr)}`);
802
- }
803
- }
804
- if (inserted > 0) {
805
- tasks = getSliceTasks(milestoneId, sliceId);
806
- logWarning("reconcile", `imported ${inserted} missing task(s) from plan file for ${milestoneId}/${sliceId}`, { mid: milestoneId, sid: sliceId });
807
- }
808
- }
809
- }
810
- } catch (err) {
811
- logError("reconcile", `plan-file task import failed for ${milestoneId}/${sliceId}: ${err instanceof Error ? err.message : String(err)}`);
812
- }
813
- }
814
-
815
- let reconciled = false;
816
- for (const t of tasks) {
817
- if (isStatusDone(t.status)) continue;
818
- const summaryPath = resolveTaskFile(basePath, milestoneId, sliceId, t.id, "SUMMARY");
819
- if (summaryPath && existsSync(summaryPath)) {
820
- try {
821
- updateTaskStatus(milestoneId, sliceId, t.id, "complete", new Date().toISOString());
822
- logWarning("reconcile", `task ${milestoneId}/${sliceId}/${t.id} status reconciled from "${t.status}" to "complete" (#2514)`, { mid: milestoneId, sid: sliceId, tid: t.id });
823
- reconciled = true;
824
- } catch (e) {
825
- logError("reconcile", `failed to update task ${t.id}`, { tid: t.id, error: (e as Error).message });
826
- }
827
- }
828
- }
829
- if (reconciled) {
830
- tasks = getSliceTasks(milestoneId, sliceId);
831
- }
832
- return tasks;
833
- }
834
-
835
610
  async function detectBlockers(basePath: string, milestoneId: string, sliceId: string, tasks: TaskRow[]): Promise<string | null> {
836
611
  const completedTasks = tasks.filter(t => isStatusDone(t.status));
837
612
  for (const ct of completedTasks) {
838
613
  if (ct.blocker_discovered) {
839
614
  return ct.id;
840
615
  }
841
- const summaryFile = resolveTaskFile(basePath, milestoneId, sliceId, ct.id, "SUMMARY");
842
- if (!summaryFile) continue;
843
- const summaryContent = await loadFile(summaryFile);
844
- if (!summaryContent) continue;
845
- const summary = parseSummary(summaryContent);
846
- if (summary.frontmatter.blocker_discovered) {
847
- return ct.id;
848
- }
849
616
  }
850
617
  return null;
851
618
  }
852
619
 
853
620
  function checkReplanTrigger(basePath: string, milestoneId: string, sliceId: string): boolean {
854
621
  const sliceRow = getSlice(milestoneId, sliceId);
855
- const dbTriggered = !!sliceRow?.replan_triggered_at;
856
- const diskTriggered = !dbTriggered &&
857
- !!resolveSliceFile(basePath, milestoneId, sliceId, "REPLAN-TRIGGER");
858
- return dbTriggered || diskTriggered;
859
- }
860
-
861
- async function checkInterruptedWork(basePath: string, milestoneId: string, sliceId: string): Promise<boolean> {
862
- const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
863
- const continueFile = sDir ? resolveSliceFile(basePath, milestoneId, sliceId, "CONTINUE") : null;
864
- return !!(continueFile && await loadFile(continueFile)) ||
865
- !!(sDir && await loadFile(join(sDir, "continue.md")));
622
+ return !!sliceRow?.replan_triggered_at;
866
623
  }
867
624
 
868
625
  export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
869
- const requirements = parseRequirementCounts(await loadFile(resolveGsdRootFile(basePath, "REQUIREMENTS")));
870
-
871
- let allMilestones = reconcileDiskToDb(basePath);
626
+ const requirements = getRequirementCounts();
872
627
 
873
- const customOrder = loadQueueOrder(basePath);
874
- const sortedIds = sortByQueueOrder(allMilestones.map(m => m.id), customOrder);
875
- const byId = new Map(allMilestones.map(m => [m.id, m]));
876
- allMilestones.length = 0;
877
- for (const id of sortedIds) allMilestones.push(byId.get(id)!);
628
+ const allMilestones = getAllMilestones();
878
629
 
879
- const milestoneLock = process.env.GSD_MILESTONE_LOCK;
630
+ const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
880
631
  const milestones = milestoneLock
881
632
  ? allMilestones.filter(m => m.id === milestoneLock)
882
633
  : allMilestones;
@@ -905,28 +656,16 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
905
656
  return handleNoActiveMilestone(registry, requirements, milestoneProgress);
906
657
  }
907
658
 
908
- const hasRoadmap = resolveMilestoneFile(basePath, activeMilestone.id, "ROADMAP") !== null;
909
-
910
659
  if (activeMilestoneSlices.length === 0) {
911
- if (!hasRoadmap) {
912
- const phase = activeMilestoneHasDraft ? 'needs-discussion' as const : 'pre-planning' as const;
913
- const nextAction = activeMilestoneHasDraft
914
- ? `Discuss draft context for milestone ${activeMilestone.id}.`
915
- : `Plan milestone ${activeMilestone.id}.`;
916
- return {
917
- activeMilestone, activeSlice: null, activeTask: null,
918
- phase, recentDecisions: [], blockers: [],
919
- nextAction, registry, requirements,
920
- progress: { milestones: milestoneProgress },
921
- };
922
- }
923
-
660
+ const phase = activeMilestoneHasDraft ? 'needs-discussion' as const : 'pre-planning' as const;
661
+ const nextAction = activeMilestoneHasDraft
662
+ ? `Discuss draft context for milestone ${activeMilestone.id}.`
663
+ : `Plan milestone ${activeMilestone.id}.`;
924
664
  return {
925
665
  activeMilestone, activeSlice: null, activeTask: null,
926
- phase: 'pre-planning', recentDecisions: [], blockers: [],
927
- nextAction: `Milestone ${activeMilestone.id} has a roadmap but no slices defined. Add slices to the roadmap.`,
928
- registry, requirements,
929
- progress: { milestones: milestoneProgress, slices: { done: 0, total: 0 } },
666
+ phase, recentDecisions: [], blockers: [],
667
+ nextAction, registry, requirements,
668
+ progress: { milestones: milestoneProgress },
930
669
  };
931
670
  }
932
671
 
@@ -940,21 +679,14 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
940
679
  return handleAllSlicesDone(basePath, activeMilestone, registry, requirements, milestoneProgress, sliceProgress);
941
680
  }
942
681
 
943
- // ADR-011 auto-heal: if a slice has a PLAN on disk but is still flagged is_sketch=1
944
- // (e.g. a crash between plan-slice write and the sketch flip), reconcile before
945
- // running phase derivation so the flag doesn't misroute state.
946
- autoHealSketchFlags(activeMilestone.id, (sid) =>
947
- !!resolveSliceFile(basePath, activeMilestone.id, sid, "PLAN"),
948
- );
949
- // Re-read slices after auto-heal so downstream reads see fresh is_sketch values.
950
- const healedSlices = getMilestoneSlices(activeMilestone.id);
951
- const activeSliceContext = resolveSliceDependencies(healedSlices);
682
+ const activeSliceContext = resolveSliceDependencies(activeMilestoneSlices);
952
683
  if (!activeSliceContext.activeSlice) {
953
684
  // If locked slice wasn't found, it returns null but logs warning, we need to return 'blocked'
954
- if (process.env.GSD_SLICE_LOCK) {
685
+ const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
686
+ if (sliceLock) {
955
687
  return {
956
688
  activeMilestone, activeSlice: null, activeTask: null,
957
- phase: 'blocked', recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${process.env.GSD_SLICE_LOCK} not found in active milestone slices`],
689
+ phase: 'blocked', recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${sliceLock} not found in active milestone slices`],
958
690
  nextAction: 'Slice lock references a non-existent slice — check orchestrator dispatch.',
959
691
  registry, requirements,
960
692
  progress: { milestones: milestoneProgress, slices: sliceProgress },
@@ -970,32 +702,20 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
970
702
  }
971
703
  const { activeSlice, activeSliceRow } = activeSliceContext;
972
704
 
973
- const planFile = resolveSliceFile(basePath, activeMilestone.id, activeSlice.id, "PLAN");
974
- if (!planFile) {
975
- // ADR-011: sketch slices with progressive_planning enabled enter the
976
- // `refining` phase a refine-slice unit expands the sketch into a full plan
977
- // before execution. When the flag is off, sketches are indistinguishable
978
- // from a missing plan and fall through to the normal `planning` phase.
979
- const progressive = loadEffectiveGSDPreferences()?.preferences?.phases?.progressive_planning === true;
980
- if (progressive && activeSliceRow?.is_sketch === 1) {
981
- return {
982
- activeMilestone, activeSlice, activeTask: null,
983
- phase: 'refining', recentDecisions: [], blockers: [],
984
- nextAction: `Refine sketch slice ${activeSlice.id} (${activeSlice.title}) using prior slice context.`,
985
- registry, requirements,
986
- progress: { milestones: milestoneProgress, slices: sliceProgress },
987
- };
988
- }
705
+ // ADR-011: DB slice metadata is authoritative for sketch refinement.
706
+ // PLAN.md and preference flags are projections/configuration and are
707
+ // deliberately not used to infer whether the slice itself is a sketch.
708
+ if (activeSliceRow?.is_sketch === 1) {
989
709
  return {
990
710
  activeMilestone, activeSlice, activeTask: null,
991
- phase: 'planning', recentDecisions: [], blockers: [],
992
- nextAction: `Plan slice ${activeSlice.id} (${activeSlice.title}).`,
711
+ phase: 'refining', recentDecisions: [], blockers: [],
712
+ nextAction: `Refine sketch slice ${activeSlice.id} (${activeSlice.title}) using prior slice context.`,
993
713
  registry, requirements,
994
714
  progress: { milestones: milestoneProgress, slices: sliceProgress },
995
715
  };
996
716
  }
997
717
 
998
- const tasks = await reconcileSliceTasks(basePath, activeMilestone.id, activeSlice.id, planFile);
718
+ const tasks = getSliceTasks(activeMilestone.id, activeSlice.id);
999
719
 
1000
720
  const taskProgress = {
1001
721
  done: tasks.filter(t => isStatusDone(t.status)).length,
@@ -1018,7 +738,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
1018
738
  return {
1019
739
  activeMilestone, activeSlice, activeTask: null,
1020
740
  phase: 'planning', recentDecisions: [], blockers: [],
1021
- nextAction: `Slice ${activeSlice.id} has a plan file but no tasks. Add tasks to the plan.`,
741
+ nextAction: `Slice ${activeSlice.id} has no DB tasks. Plan slice tasks before execution.`,
1022
742
  registry, requirements,
1023
743
  progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
1024
744
  };
@@ -1026,20 +746,6 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
1026
746
 
1027
747
  const activeTask: ActiveRef = { id: activeTaskRow.id, title: activeTaskRow.title };
1028
748
 
1029
- const tasksDir = resolveTasksDir(basePath, activeMilestone.id, activeSlice.id);
1030
- if (tasksDir && existsSync(tasksDir) && tasks.length > 0) {
1031
- const allFiles = readdirSync(tasksDir).filter(f => f.endsWith(".md"));
1032
- if (allFiles.length === 0) {
1033
- return {
1034
- activeMilestone, activeSlice, activeTask: null,
1035
- phase: 'planning', recentDecisions: [], blockers: [],
1036
- nextAction: `Task plan files missing for ${activeSlice.id}. Run plan-slice to generate task plans.`,
1037
- registry, requirements,
1038
- progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
1039
- };
1040
- }
1041
- }
1042
-
1043
749
  // ── Quality gate evaluation check ──────────────────────────────────
1044
750
  // Pause before execution only when gates owned by the `gate-evaluate`
1045
751
  // turn (Q3/Q4) are still pending. Q8 is also `scope:"slice"` but is
@@ -1119,14 +825,10 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
1119
825
  }
1120
826
  }
1121
827
 
1122
- const hasInterrupted = await checkInterruptedWork(basePath, activeMilestone.id, activeSlice.id);
1123
-
1124
828
  return {
1125
829
  activeMilestone, activeSlice, activeTask,
1126
830
  phase: 'executing', recentDecisions: [], blockers: [],
1127
- nextAction: hasInterrupted
1128
- ? `Resume interrupted work on ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}. Read continue.md first.`
1129
- : `Execute ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}.`,
831
+ nextAction: `Execute ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}.`,
1130
832
  registry, requirements,
1131
833
  progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
1132
834
  };
@@ -1142,12 +844,12 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
1142
844
  const milestoneIds = sortByQueueOrder(diskIds, customOrder);
1143
845
 
1144
846
  // ── Parallel worker isolation ──────────────────────────────────────────
1145
- // When GSD_MILESTONE_LOCK is set, this process is a parallel worker
847
+ // When GSD_PARALLEL_WORKER and GSD_MILESTONE_LOCK are set, this process is a parallel worker
1146
848
  // scoped to a single milestone. Filter the milestone list so this worker
1147
849
  // only sees its assigned milestone (all others are treated as if they
1148
850
  // don't exist). This gives each worker complete isolation without
1149
851
  // modifying any other state derivation logic.
1150
- const milestoneLock = process.env.GSD_MILESTONE_LOCK;
852
+ const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
1151
853
  if (milestoneLock && milestoneIds.includes(milestoneLock)) {
1152
854
  milestoneIds.length = 0;
1153
855
  milestoneIds.push(milestoneLock);
@@ -1610,8 +1312,8 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
1610
1312
  let activeSlice: ActiveRef | null = null;
1611
1313
 
1612
1314
  // ── Slice-level parallel worker isolation ─────────────────────────────
1613
- // When GSD_SLICE_LOCK is set, override activeSlice to only the locked slice.
1614
- const sliceLockLegacy = process.env.GSD_SLICE_LOCK;
1315
+ // When GSD_PARALLEL_WORKER and GSD_SLICE_LOCK are set, override activeSlice to only the locked slice.
1316
+ const sliceLockLegacy = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
1615
1317
  if (sliceLockLegacy) {
1616
1318
  const lockedSlice = activeRoadmap.slices.find(s => s.id === sliceLockLegacy);
1617
1319
  if (lockedSlice) {