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
@@ -0,0 +1,221 @@
1
+ // GSD-2 + db-writer root-artifact path guard: regression tests for M1 fix
2
+
3
+ import { describe, test, beforeEach, afterEach } from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import {
6
+ mkdtempSync,
7
+ mkdirSync,
8
+ existsSync,
9
+ readFileSync,
10
+ realpathSync,
11
+ rmSync,
12
+ } from "node:fs";
13
+ import { join, resolve } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+
16
+ import { createWorkspace, scopeMilestone } from "../workspace.ts";
17
+ import {
18
+ saveArtifactToDb,
19
+ saveArtifactToDbByScope,
20
+ saveArtifactToDbForWorkspace,
21
+ } from "../db-writer.ts";
22
+ import { openDatabase, closeDatabase } from "../gsd-db.ts";
23
+
24
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
25
+
26
+ function makeProjectDir(): string {
27
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-dbwriter-root-")));
28
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
29
+ return dir;
30
+ }
31
+
32
+ // ─── Suite 1: saveArtifactToDb with undefined milestone_id writes to .gsd/ root, not milestones/ ──
33
+
34
+ describe("saveArtifactToDb: root artifact (no milestone_id) routes to workspace .gsd root", () => {
35
+ let tmp: string;
36
+
37
+ beforeEach(() => {
38
+ tmp = makeProjectDir();
39
+ openDatabase(join(tmp, ".gsd", "gsd.db"));
40
+ });
41
+
42
+ afterEach(() => {
43
+ closeDatabase();
44
+ rmSync(tmp, { recursive: true, force: true });
45
+ });
46
+
47
+ test("opts.milestone_id = undefined writes artifact at .gsd/REQUIREMENTS.md, not inside milestones/", async () => {
48
+ const content = "# Requirements\n\nTest root artifact.\n";
49
+ const opts = {
50
+ path: "REQUIREMENTS.md",
51
+ artifact_type: "REQUIREMENTS_DRAFT",
52
+ content,
53
+ milestone_id: undefined,
54
+ };
55
+
56
+ await saveArtifactToDb(opts, tmp);
57
+
58
+ const ws = createWorkspace(tmp);
59
+ const expectedPath = resolve(ws.contract.projectGsd, "REQUIREMENTS.md");
60
+
61
+ assert.ok(existsSync(expectedPath), "root artifact written at .gsd/REQUIREMENTS.md");
62
+ assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
63
+
64
+ // Must NOT be inside milestones/ — the latent trap being fixed
65
+ const wrongPath = resolve(ws.contract.projectGsd, "milestones", "", "REQUIREMENTS.md");
66
+ assert.ok(!existsSync(wrongPath), "artifact NOT written inside milestones/");
67
+ });
68
+
69
+ test("opts.milestone_id = null writes artifact at .gsd/ root", async () => {
70
+ const content = "# Project\n\nRoot project doc.\n";
71
+ const opts = {
72
+ path: "PROJECT.md",
73
+ artifact_type: "PROJECT",
74
+ content,
75
+ milestone_id: undefined,
76
+ };
77
+
78
+ await saveArtifactToDb(opts, tmp);
79
+
80
+ const ws = createWorkspace(tmp);
81
+ const expectedPath = resolve(ws.contract.projectGsd, "PROJECT.md");
82
+
83
+ assert.ok(existsSync(expectedPath), "PROJECT.md written at .gsd/PROJECT.md");
84
+ assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
85
+ });
86
+
87
+ test("path resolves via workspace.contract.projectGsd, not a hand-rolled join", async () => {
88
+ const content = "# Knowledge\n";
89
+ const opts = {
90
+ path: "KNOWLEDGE.md",
91
+ artifact_type: "KNOWLEDGE",
92
+ content,
93
+ milestone_id: undefined,
94
+ };
95
+
96
+ await saveArtifactToDb(opts, tmp);
97
+
98
+ const ws = createWorkspace(tmp);
99
+ // The canonical path must equal contract.projectGsd + '/KNOWLEDGE.md'
100
+ const canonicalPath = join(ws.contract.projectGsd, "KNOWLEDGE.md");
101
+ assert.ok(existsSync(canonicalPath), "file at contract.projectGsd/KNOWLEDGE.md");
102
+ assert.equal(
103
+ canonicalPath,
104
+ join(ws.projectRoot, ".gsd", "KNOWLEDGE.md"),
105
+ "contract.projectGsd-based path equals projectRoot/.gsd/KNOWLEDGE.md",
106
+ );
107
+ });
108
+ });
109
+
110
+ // ─── Suite 2: saveArtifactToDb with a real milestone_id still works (no regression) ──
111
+
112
+ describe("saveArtifactToDb: milestone_id present routes to milestones/ (no regression)", () => {
113
+ let tmp: string;
114
+
115
+ beforeEach(() => {
116
+ tmp = makeProjectDir();
117
+ openDatabase(join(tmp, ".gsd", "gsd.db"));
118
+ });
119
+
120
+ afterEach(() => {
121
+ closeDatabase();
122
+ rmSync(tmp, { recursive: true, force: true });
123
+ });
124
+
125
+ test("milestone_id = 'M001' writes to .gsd/milestones/M001/...", async () => {
126
+ const relPath = "milestones/M001/M001-CONTEXT.md";
127
+ const content = "# M001 Context\n";
128
+ const opts = {
129
+ path: relPath,
130
+ artifact_type: "CONTEXT",
131
+ content,
132
+ milestone_id: "M001",
133
+ };
134
+
135
+ await saveArtifactToDb(opts, tmp);
136
+
137
+ const ws = createWorkspace(tmp);
138
+ const expectedPath = resolve(ws.contract.projectGsd, relPath);
139
+
140
+ assert.ok(existsSync(expectedPath), "milestone artifact written at correct path");
141
+ assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
142
+ });
143
+ });
144
+
145
+ // ─── Suite 3: saveArtifactToDbByScope with empty milestoneId throws a clear error ──
146
+
147
+ describe("saveArtifactToDbByScope: empty milestoneId throws defensive error", () => {
148
+ let tmp: string;
149
+
150
+ beforeEach(() => {
151
+ tmp = makeProjectDir();
152
+ openDatabase(join(tmp, ".gsd", "gsd.db"));
153
+ });
154
+
155
+ afterEach(() => {
156
+ closeDatabase();
157
+ rmSync(tmp, { recursive: true, force: true });
158
+ });
159
+
160
+ test("scope with empty milestoneId throws GSDError mentioning saveArtifactToDbForWorkspace", async () => {
161
+ const ws = createWorkspace(tmp);
162
+ const emptyScope = scopeMilestone(ws, "");
163
+ const opts = {
164
+ path: "REQUIREMENTS.md",
165
+ artifact_type: "REQUIREMENTS_DRAFT",
166
+ content: "# req\n",
167
+ };
168
+
169
+ await assert.rejects(
170
+ () => saveArtifactToDbByScope(emptyScope, opts),
171
+ (err: unknown) => {
172
+ assert.ok(err instanceof Error, "thrown value is an Error");
173
+ assert.ok(
174
+ err.message.includes("milestoneId is empty"),
175
+ `error message should mention 'milestoneId is empty', got: ${err.message}`,
176
+ );
177
+ assert.ok(
178
+ err.message.includes("saveArtifactToDbForWorkspace"),
179
+ `error message should mention 'saveArtifactToDbForWorkspace', got: ${err.message}`,
180
+ );
181
+ return true;
182
+ },
183
+ );
184
+ });
185
+ });
186
+
187
+ // ─── Suite 4: saveArtifactToDbForWorkspace writes at contract.projectGsd, not milestones/ ──
188
+
189
+ describe("saveArtifactToDbForWorkspace: writes directly to .gsd root via workspace contract", () => {
190
+ let tmp: string;
191
+
192
+ beforeEach(() => {
193
+ tmp = makeProjectDir();
194
+ openDatabase(join(tmp, ".gsd", "gsd.db"));
195
+ });
196
+
197
+ afterEach(() => {
198
+ closeDatabase();
199
+ rmSync(tmp, { recursive: true, force: true });
200
+ });
201
+
202
+ test("root artifact lands at contract.projectGsd/path, not milestones/", async () => {
203
+ const ws = createWorkspace(tmp);
204
+ const content = "# Requirements\n";
205
+ const opts = {
206
+ path: "REQUIREMENTS.md",
207
+ artifact_type: "REQUIREMENTS_DRAFT",
208
+ content,
209
+ };
210
+
211
+ await saveArtifactToDbForWorkspace(ws, opts);
212
+
213
+ const expectedPath = resolve(ws.contract.projectGsd, "REQUIREMENTS.md");
214
+ assert.ok(existsSync(expectedPath), "artifact written at contract.projectGsd/REQUIREMENTS.md");
215
+ assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
216
+
217
+ // Must not have landed inside milestones/
218
+ const milestonePath = join(ws.contract.projectGsd, "milestones", "", "REQUIREMENTS.md");
219
+ assert.ok(!existsSync(milestonePath), "artifact NOT inside milestones/empty-string/");
220
+ });
221
+ });
@@ -0,0 +1,230 @@
1
+ // GSD-2 + db-writer saveArtifactToDbByScope: workspace-contract path routing tests
2
+
3
+ import { describe, test, beforeEach, afterEach } from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import {
6
+ mkdtempSync,
7
+ mkdirSync,
8
+ existsSync,
9
+ readFileSync,
10
+ realpathSync,
11
+ rmSync,
12
+ } from "node:fs";
13
+ import { join, resolve } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+
16
+ import { createWorkspace, scopeMilestone } from "../workspace.ts";
17
+ import { saveArtifactToDb, saveArtifactToDbByScope } from "../db-writer.ts";
18
+ import { openDatabase, closeDatabase } from "../gsd-db.ts";
19
+
20
+ // ─── Helpers ────────────────────────────────────────────────────────────────
21
+
22
+ function makeProjectDir(): string {
23
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-dbwriter-scope-")));
24
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
25
+ return dir;
26
+ }
27
+
28
+ // ─── Suite 1: scope variant writes to the same canonical path as legacy ──────
29
+
30
+ describe("saveArtifactToDbByScope: path parity with legacy saveArtifactToDb", () => {
31
+ let tmp1: string;
32
+ let tmp2: string;
33
+
34
+ beforeEach(() => {
35
+ tmp1 = makeProjectDir();
36
+ tmp2 = makeProjectDir();
37
+ });
38
+
39
+ afterEach(() => {
40
+ closeDatabase();
41
+ rmSync(tmp1, { recursive: true, force: true });
42
+ rmSync(tmp2, { recursive: true, force: true });
43
+ });
44
+
45
+ test("scope variant writes artifact to same canonical path as legacy variant", async () => {
46
+ const relPath = "milestones/M001/slices/S01/tasks/T01-SUMMARY.md";
47
+ const content = "# T01 Summary\n\nTest content.\n";
48
+ const opts = {
49
+ path: relPath,
50
+ artifact_type: "SUMMARY",
51
+ content,
52
+ milestone_id: "M001",
53
+ slice_id: "S01",
54
+ task_id: "T01",
55
+ };
56
+
57
+ // Legacy path: basePath + '.gsd' join
58
+ const legacyExpectedPath = resolve(tmp1, ".gsd", relPath);
59
+
60
+ // Scope path: contract.projectGsd
61
+ const ws = createWorkspace(tmp2);
62
+ const scope = scopeMilestone(ws, "M001");
63
+ const scopeExpectedPath = resolve(ws.contract.projectGsd, relPath);
64
+
65
+ // Both should resolve to the same relative structure
66
+ // (though under different temp dirs — so we compare structure, not absolute path)
67
+ assert.equal(
68
+ scopeExpectedPath,
69
+ resolve(ws.contract.projectGsd, relPath),
70
+ "scope path must be contract.projectGsd + relPath",
71
+ );
72
+ assert.equal(
73
+ legacyExpectedPath,
74
+ resolve(tmp1, ".gsd", relPath),
75
+ "legacy path must be basePath/.gsd + relPath",
76
+ );
77
+
78
+ // Open DB for tmp1 and write via legacy
79
+ const dbPath1 = join(tmp1, ".gsd", "gsd.db");
80
+ openDatabase(dbPath1);
81
+ await saveArtifactToDb(opts, tmp1);
82
+ closeDatabase();
83
+
84
+ // Open DB for tmp2 and write via scope variant
85
+ const dbPath2 = join(tmp2, ".gsd", "gsd.db");
86
+ openDatabase(dbPath2);
87
+ await saveArtifactToDbByScope(scope, opts);
88
+ closeDatabase();
89
+
90
+ // Both should have written to the correct location under their respective .gsd dirs
91
+ assert.ok(existsSync(legacyExpectedPath), "legacy: artifact written at basePath/.gsd/relPath");
92
+ assert.ok(existsSync(scopeExpectedPath), "scope: artifact written at contract.projectGsd/relPath");
93
+
94
+ // Content must match
95
+ assert.equal(readFileSync(legacyExpectedPath, "utf-8"), content, "legacy: content matches");
96
+ assert.equal(readFileSync(scopeExpectedPath, "utf-8"), content, "scope: content matches");
97
+ });
98
+ });
99
+
100
+ // ─── Suite 2: scope variant uses contract.projectGsd, not a basePath join ────
101
+
102
+ describe("saveArtifactToDbByScope: uses contract.projectGsd, not hand-rolled basePath join", () => {
103
+ let tmp: string;
104
+
105
+ beforeEach(() => {
106
+ tmp = makeProjectDir();
107
+ });
108
+
109
+ afterEach(() => {
110
+ closeDatabase();
111
+ rmSync(tmp, { recursive: true, force: true });
112
+ });
113
+
114
+ test("scope.workspace.contract.projectGsd is used as the .gsd root, not basePath/.gsd", async () => {
115
+ const ws = createWorkspace(tmp);
116
+ const scope = scopeMilestone(ws, "M001");
117
+
118
+ // The contract.projectGsd must equal the canonical join(projectRoot, '.gsd')
119
+ assert.equal(
120
+ ws.contract.projectGsd,
121
+ join(ws.projectRoot, ".gsd"),
122
+ "contract.projectGsd must equal join(projectRoot, '.gsd')",
123
+ );
124
+
125
+ // It must NOT be a hand-rolled resolution from an arbitrary basePath string
126
+ // (i.e., contract.projectGsd routes through the workspace contract)
127
+ assert.ok(
128
+ ws.contract.projectGsd.startsWith(ws.projectRoot),
129
+ "contract.projectGsd must be rooted at projectRoot",
130
+ );
131
+
132
+ const relPath = "milestones/M001/M001-CONTEXT.md";
133
+ const content = "# M001 Context\n";
134
+ const opts = {
135
+ path: relPath,
136
+ artifact_type: "CONTEXT",
137
+ content,
138
+ milestone_id: "M001",
139
+ };
140
+
141
+ openDatabase(join(tmp, ".gsd", "gsd.db"));
142
+ await saveArtifactToDbByScope(scope, opts);
143
+
144
+ // File must be at contract.projectGsd/relPath
145
+ const expectedPath = resolve(ws.contract.projectGsd, relPath);
146
+ assert.ok(existsSync(expectedPath), "artifact written at contract.projectGsd/relPath");
147
+ assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
148
+
149
+ // And must NOT be at some other location
150
+ const handRolledPath = resolve(tmp, ".gsd", relPath);
151
+ // Both should be the same path in project mode (they should agree)
152
+ assert.equal(
153
+ expectedPath,
154
+ handRolledPath,
155
+ "in project mode, contract.projectGsd resolves same as basePath/.gsd",
156
+ );
157
+ });
158
+ });
159
+
160
+ // ─── Suite 3: worktree-mode scope routes to project root's .gsd/ ─────────────
161
+
162
+ describe("saveArtifactToDbByScope: worktree scope writes to project root .gsd/", () => {
163
+ let tmp: string;
164
+
165
+ beforeEach(() => {
166
+ tmp = realpathSync(mkdtempSync(join(tmpdir(), "gsd-dbwriter-wt-scope-")));
167
+ // Create project .gsd directory
168
+ mkdirSync(join(tmp, ".gsd"), { recursive: true });
169
+ });
170
+
171
+ afterEach(() => {
172
+ closeDatabase();
173
+ rmSync(tmp, { recursive: true, force: true });
174
+ });
175
+
176
+ test("worktree-mode scope: contract.projectGsd resolves to project root's .gsd/, not worktree .gsd/", async () => {
177
+ // Construct a worktree path inside the project's .gsd/worktrees/<MID>
178
+ const worktreePath = join(tmp, ".gsd", "worktrees", "M001");
179
+ mkdirSync(join(worktreePath, ".gsd"), { recursive: true });
180
+
181
+ const projectWs = createWorkspace(tmp);
182
+ const worktreeWs = createWorkspace(worktreePath);
183
+
184
+ // Both should share the same projectRoot (worktree-root resolution)
185
+ assert.equal(
186
+ worktreeWs.projectRoot,
187
+ projectWs.projectRoot,
188
+ "worktree workspace must have same projectRoot as project workspace",
189
+ );
190
+
191
+ // contract.projectGsd for the worktree workspace must point to the PROJECT root's .gsd/
192
+ assert.equal(
193
+ worktreeWs.contract.projectGsd,
194
+ join(projectWs.projectRoot, ".gsd"),
195
+ "worktree contract.projectGsd must equal project root's .gsd/",
196
+ );
197
+
198
+ // Must NOT be the worktree-local .gsd/
199
+ assert.notEqual(
200
+ worktreeWs.contract.projectGsd,
201
+ join(worktreePath, ".gsd"),
202
+ "worktree contract.projectGsd must NOT be the worktree-local .gsd/",
203
+ );
204
+
205
+ // Write via the worktree-mode scope
206
+ const scope = scopeMilestone(worktreeWs, "M001");
207
+ const relPath = "milestones/M001/M001-CONTEXT.md";
208
+ const content = "# M001 Context from worktree scope\n";
209
+ const opts = {
210
+ path: relPath,
211
+ artifact_type: "CONTEXT",
212
+ content,
213
+ milestone_id: "M001",
214
+ };
215
+
216
+ openDatabase(join(tmp, ".gsd", "gsd.db"));
217
+ await saveArtifactToDbByScope(scope, opts);
218
+
219
+ // File must land in the PROJECT root's .gsd/, not in the worktree's .gsd/
220
+ const projectPath = resolve(projectWs.contract.projectGsd, relPath);
221
+ const worktreeLocalPath = resolve(worktreePath, ".gsd", relPath);
222
+
223
+ assert.ok(existsSync(projectPath), "artifact written to project root's .gsd/");
224
+ assert.ok(
225
+ !existsSync(worktreeLocalPath),
226
+ "artifact must NOT be written to worktree-local .gsd/",
227
+ );
228
+ assert.equal(readFileSync(projectPath, "utf-8"), content, "content at project root matches");
229
+ });
230
+ });
@@ -481,7 +481,7 @@ describe('db-writer', () => {
481
481
  }
482
482
  });
483
483
 
484
- test('updateRequirementInDb — seeds from REQUIREMENTS.md when DB empty (#3346)', async () => {
484
+ test('updateRequirementInDb — ignores REQUIREMENTS.md projection when DB empty', async () => {
485
485
  const tmpDir = makeTmpDir();
486
486
  const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
487
487
  openDatabase(dbPath);
@@ -515,31 +515,28 @@ describe('db-writer', () => {
515
515
  ].join('\n');
516
516
  fs.writeFileSync(path.join(tmpDir, '.gsd', 'REQUIREMENTS.md'), reqContent);
517
517
 
518
- // DB is empty no requirements seeded. Update R005 to "validated".
519
- // Before #3346 fix: this would create a skeleton with empty fields.
520
- // After fix: this seeds all 3 requirements from REQUIREMENTS.md first.
518
+ // DB is empty. REQUIREMENTS.md is a projection and must not be imported
519
+ // implicitly by a runtime DB write.
521
520
  await updateRequirementInDb('R005', {
522
521
  status: 'validated',
523
522
  validation: 'S02 — auth flow verified',
524
523
  }, tmpDir);
525
524
 
526
- // R005 should have the update AND the original content from markdown
525
+ // R005 should have the requested update only; disk projection content is ignored.
527
526
  const r005 = getRequirementById('R005');
528
527
  assert.ok(r005, 'R005 should exist');
529
528
  assert.equal(r005!.status, 'validated', 'status should be updated');
530
529
  assert.equal(r005!.validation, 'S02 — auth flow verified', 'validation should be updated');
531
- assert.equal(r005!.class, 'functional', 'class should be preserved from REQUIREMENTS.md');
532
- assert.ok(r005!.description?.includes('authentication') || r005!.full_content?.includes('authentication'),
533
- 'original content should be preserved');
530
+ assert.equal(r005!.class, '', 'class should not be imported from REQUIREMENTS.md');
531
+ assert.ok(!r005!.description?.includes('authentication'), 'description should not be imported');
532
+ assert.ok(!r005!.full_content?.includes('authentication'), 'full content should not be imported');
534
533
 
535
- // R007 and R001 should also be seeded (not just the one being updated)
534
+ // Other requirements in the projection are not seeded.
536
535
  const r007 = getRequirementById('R007');
537
- assert.ok(r007, 'R007 should be seeded from REQUIREMENTS.md');
538
- assert.equal(r007!.status, 'active', 'R007 status should be active');
536
+ assert.equal(r007, null, 'R007 should not be imported from REQUIREMENTS.md');
539
537
 
540
538
  const r001 = getRequirementById('R001');
541
- assert.ok(r001, 'R001 should be seeded from REQUIREMENTS.md');
542
- assert.equal(r001!.status, 'validated', 'R001 status should be validated (from section heading)');
539
+ assert.equal(r001, null, 'R001 should not be imported from REQUIREMENTS.md');
543
540
  } finally {
544
541
  closeDatabase();
545
542
  cleanupDir(tmpDir);
@@ -663,15 +660,16 @@ describe('db-writer', () => {
663
660
  'disk file preserved — shrinkage guard prevented overwrite',
664
661
  );
665
662
 
666
- // DB should contain the full disk content, not the abbreviated content
663
+ // DB should keep the caller-provided content. The larger disk file is a
664
+ // stale projection, not runtime authority.
667
665
  const adapter = _getAdapter();
668
666
  const row = adapter!
669
667
  .prepare('SELECT full_content FROM artifacts WHERE path = ?')
670
668
  .get(relPath);
671
669
  assert.deepStrictEqual(
672
670
  row!['full_content'],
673
- fullContent,
674
- 'DB stores the richer disk content instead of abbreviated content',
671
+ abbreviatedContent,
672
+ 'DB stores caller-provided content instead of importing disk projection content',
675
673
  );
676
674
  } finally {
677
675
  closeDatabase();
@@ -0,0 +1,151 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ annotateBackgroundable,
5
+ getDelegationVerdict,
6
+ getVerdictByUnitType,
7
+ isBackgroundable,
8
+ listBackgroundableTools,
9
+ } from "../delegation-policy.js";
10
+
11
+ // Pin the GOOD set: changes here must come with explicit re-evaluation.
12
+ const EXPECTED_BACKGROUNDABLE = [
13
+ "gsd_execute",
14
+ "gsd_plan_slice",
15
+ "gsd_reassess_roadmap",
16
+ "gsd_validate_milestone",
17
+ ];
18
+
19
+ test("isBackgroundable returns true for the four GOOD-verdict tools", () => {
20
+ for (const name of EXPECTED_BACKGROUNDABLE) {
21
+ assert.equal(isBackgroundable(name), true, `${name} should be backgroundable`);
22
+ }
23
+ });
24
+
25
+ test("isBackgroundable returns false for RISKY-verdict tools", () => {
26
+ for (const name of ["gsd_doctor", "gsd_plan_milestone", "gsd_replan_slice"]) {
27
+ assert.equal(isBackgroundable(name), false, `${name} should not be backgroundable`);
28
+ }
29
+ });
30
+
31
+ test("isBackgroundable returns false for NO-verdict tools", () => {
32
+ assert.equal(isBackgroundable("gsd_plan_task"), false);
33
+ });
34
+
35
+ test("isBackgroundable defaults to false for unknown tools (default-deny)", () => {
36
+ assert.equal(isBackgroundable("gsd_nonexistent_tool"), false);
37
+ assert.equal(isBackgroundable(""), false);
38
+ });
39
+
40
+ test("listBackgroundableTools returns exactly the four GOOD tools, sorted", () => {
41
+ assert.deepEqual(listBackgroundableTools(), EXPECTED_BACKGROUNDABLE);
42
+ });
43
+
44
+ test("getDelegationVerdict resolves alias names to canonical entries", () => {
45
+ for (const [alias, canonical] of [
46
+ ["gsd_milestone_validate", "gsd_validate_milestone"],
47
+ ["gsd_roadmap_reassess", "gsd_reassess_roadmap"],
48
+ ["gsd_slice_replan", "gsd_replan_slice"],
49
+ ["gsd_task_plan", "gsd_plan_task"],
50
+ ] as const) {
51
+ const entry = getDelegationVerdict(alias);
52
+ assert.ok(entry, `alias ${alias} should resolve`);
53
+ assert.equal(entry.toolName, canonical, `${alias} should resolve to ${canonical}`);
54
+ }
55
+ });
56
+
57
+ test("plan_slice carries the slice-lock + await constraints", () => {
58
+ const entry = getDelegationVerdict("gsd_plan_slice");
59
+ assert.ok(entry);
60
+ assert.ok(entry.constraints && entry.constraints.length >= 3);
61
+ assert.ok(
62
+ entry.constraints!.some((c) => /lock the slice/i.test(c)),
63
+ "plan_slice must carry the slice-lock constraint",
64
+ );
65
+ assert.ok(
66
+ entry.constraints!.some((c) => /await background completion/i.test(c)),
67
+ "plan_slice must require await before downstream reads",
68
+ );
69
+ });
70
+
71
+ test("doctor carries fix-mode safety constraints", () => {
72
+ const entry = getDelegationVerdict("gsd_doctor");
73
+ assert.ok(entry);
74
+ assert.equal(entry.verdict, "risky");
75
+ assert.ok(
76
+ entry.constraints && entry.constraints.some((c) => /fix=false/.test(c)),
77
+ "doctor must restrict background runs to fix=false",
78
+ );
79
+ });
80
+
81
+ test("getVerdictByUnitType maps dispatcher unit types back to the policy", () => {
82
+ assert.equal(getVerdictByUnitType("plan-slice")?.toolName, "gsd_plan_slice");
83
+ assert.equal(getVerdictByUnitType("validate-milestone")?.toolName, "gsd_validate_milestone");
84
+ assert.equal(getVerdictByUnitType("reassess-roadmap")?.toolName, "gsd_reassess_roadmap");
85
+ assert.equal(getVerdictByUnitType("plan-milestone")?.toolName, "gsd_plan_milestone");
86
+ assert.equal(getVerdictByUnitType("replan-slice")?.toolName, "gsd_replan_slice");
87
+ assert.equal(getVerdictByUnitType("nonexistent-unit"), null);
88
+ });
89
+
90
+ test("every entry carries a non-empty rationale so the verdict is auditable", () => {
91
+ for (const name of [...EXPECTED_BACKGROUNDABLE, "gsd_doctor", "gsd_plan_milestone", "gsd_replan_slice", "gsd_plan_task"]) {
92
+ const entry = getDelegationVerdict(name);
93
+ assert.ok(entry, `${name} should be in the policy`);
94
+ assert.ok(entry.rationale.length > 20, `${name} rationale must be substantive`);
95
+ }
96
+ });
97
+
98
+ // ─── annotateBackgroundable contract pins ────────────────────────────────
99
+
100
+ test("annotateBackgroundable recomputes the verdict on every call (no internal cache)", () => {
101
+ // The annotator mutates in place. Repeated calls on the same object with
102
+ // different unit types must always reflect the latest unitType — never a
103
+ // stale cached value. This pins the contract documented in the JSDoc so a
104
+ // future "optimization" that adds memoization keyed on object identity
105
+ // breaks the suite instead of silently leaking a stale flag.
106
+ const action: { action: "dispatch"; unitType: string; backgroundable?: boolean } = {
107
+ action: "dispatch",
108
+ unitType: "plan-slice",
109
+ };
110
+ annotateBackgroundable(action);
111
+ assert.equal(action.backgroundable, true, "plan-slice should annotate true");
112
+
113
+ action.unitType = "plan-milestone";
114
+ annotateBackgroundable(action);
115
+ assert.equal(action.backgroundable, false, "plan-milestone (risky) should re-annotate false");
116
+
117
+ action.unitType = "validate-milestone";
118
+ annotateBackgroundable(action);
119
+ assert.equal(action.backgroundable, true, "validate-milestone should re-annotate true");
120
+
121
+ action.unitType = "complete-slice";
122
+ annotateBackgroundable(action);
123
+ assert.equal(action.backgroundable, false, "uncovered unit type should re-annotate false (default-deny)");
124
+ });
125
+
126
+ test("annotateBackgroundable passes stop/skip actions through unchanged", () => {
127
+ const stop = { action: "stop" as const, reason: "x", level: "info" as const };
128
+ const skip = { action: "skip" as const };
129
+ assert.equal(annotateBackgroundable(stop), stop);
130
+ assert.equal(annotateBackgroundable(skip), skip);
131
+ assert.equal((stop as Record<string, unknown>).backgroundable, undefined);
132
+ assert.equal((skip as Record<string, unknown>).backgroundable, undefined);
133
+ });
134
+
135
+ // ─── F4 latent gap pin: silent default-deny on unit types invoking GOOD tools ──
136
+
137
+ test("execute-task / reactive-execute / execute-task-simple intentionally default-deny despite gsd_execute being GOOD", () => {
138
+ // gsd_execute carries a GOOD verdict but no `unitType`, by design — the
139
+ // unit-level orchestrations wrap prompt and harness work whose safety is
140
+ // a separate analysis. Lifting these out of default-deny must be an
141
+ // explicit, audited change. This test pins the current behavior; if the
142
+ // policy entry gains a unitType mapping (or a unitTypes array), update
143
+ // both the entry and this test together.
144
+ for (const unitType of ["execute-task", "execute-task-simple", "reactive-execute"]) {
145
+ assert.equal(
146
+ getVerdictByUnitType(unitType),
147
+ null,
148
+ `${unitType} must remain unmapped until per-unit analysis is recorded`,
149
+ );
150
+ }
151
+ });