@opengsd/gsd-pi 1.1.1-dev.a5a2de8 → 1.1.1-dev.b2556262

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 (325) hide show
  1. package/dist/headless-recover.js +56 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
  4. package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
  5. package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
  6. package/dist/resources/extensions/browser-tools/index.js +68 -24
  7. package/dist/resources/extensions/browser-tools/state.js +12 -0
  8. package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
  9. package/dist/resources/extensions/browser-tools/utils.js +3 -3
  10. package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
  11. package/dist/resources/extensions/gsd/auto/loop.js +4 -2
  12. package/dist/resources/extensions/gsd/auto/phases.js +87 -12
  13. package/dist/resources/extensions/gsd/auto/session.js +22 -1
  14. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +81 -13
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +154 -9
  17. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
  18. package/dist/resources/extensions/gsd/auto-prompts.js +26 -21
  19. package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
  20. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  21. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  22. package/dist/resources/extensions/gsd/auto-timers.js +24 -10
  23. package/dist/resources/extensions/gsd/auto.js +40 -15
  24. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +192 -77
  26. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  27. package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
  28. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
  29. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
  30. package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
  31. package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
  33. package/dist/resources/extensions/gsd/config-overlay.js +1 -0
  34. package/dist/resources/extensions/gsd/context-masker.js +129 -5
  35. package/dist/resources/extensions/gsd/db-writer.js +35 -0
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  37. package/dist/resources/extensions/gsd/gsd-db.js +480 -172
  38. package/dist/resources/extensions/gsd/guided-flow.js +4 -1
  39. package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
  40. package/dist/resources/extensions/gsd/md-importer.js +38 -3
  41. package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
  42. package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
  43. package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
  44. package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
  45. package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
  46. package/dist/resources/extensions/gsd/preferences-models.js +111 -43
  47. package/dist/resources/extensions/gsd/preferences-types.js +13 -0
  48. package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
  49. package/dist/resources/extensions/gsd/preferences.js +4 -1
  50. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  51. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  52. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -2
  55. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  56. package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
  57. package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
  58. package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
  59. package/dist/resources/extensions/gsd/source-observations.js +306 -0
  60. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
  61. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
  62. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
  63. package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
  64. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
  65. package/dist/resources/extensions/gsd/state.js +7 -3
  66. package/dist/resources/extensions/gsd/tool-contract.js +15 -1
  67. package/dist/resources/extensions/gsd/tool-presentation-plan.js +24 -2
  68. package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -0
  69. package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
  70. package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
  71. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +62 -406
  72. package/dist/resources/extensions/gsd/uat-policy.js +130 -0
  73. package/dist/resources/extensions/gsd/uat-run.js +414 -0
  74. package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
  75. package/dist/resources/extensions/gsd/unit-tool-contracts.js +38 -14
  76. package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
  77. package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
  78. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -3
  79. package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
  80. package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
  81. package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
  82. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
  83. package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
  84. package/dist/resources/extensions/subagent/agents.js +1 -0
  85. package/dist/resources/extensions/subagent/index.js +27 -12
  86. package/dist/resources/extensions/subagent/launch.js +7 -2
  87. package/dist/web/standalone/.next/BUILD_ID +1 -1
  88. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  89. package/dist/web/standalone/.next/build-manifest.json +2 -2
  90. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  91. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.html +1 -1
  108. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  115. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  116. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  118. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  119. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  120. package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
  121. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  122. package/package.json +4 -4
  123. package/packages/cloud-mcp-gateway/package.json +2 -2
  124. package/packages/contracts/package.json +1 -1
  125. package/packages/daemon/package.json +4 -4
  126. package/packages/gsd-agent-core/package.json +5 -5
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  138. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
  140. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  141. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  142. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
  143. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  144. package/packages/gsd-agent-modes/package.json +7 -7
  145. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  146. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  147. package/packages/mcp-server/package.json +3 -3
  148. package/packages/native/dist/native.js +22 -0
  149. package/packages/native/package.json +1 -1
  150. package/packages/pi-agent-core/package.json +1 -1
  151. package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
  152. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  153. package/packages/pi-ai/dist/image-models.generated.js +30 -0
  154. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  155. package/packages/pi-ai/dist/models.generated.d.ts +174 -29
  156. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/models.generated.js +178 -54
  158. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  159. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  160. package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
  161. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  162. package/packages/pi-ai/package.json +1 -1
  163. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  164. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  165. package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
  166. package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
  167. package/packages/pi-coding-agent/package.json +7 -7
  168. package/packages/pi-tui/dist/utils.d.ts +11 -0
  169. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  170. package/packages/pi-tui/dist/utils.js +119 -6
  171. package/packages/pi-tui/dist/utils.js.map +1 -1
  172. package/packages/pi-tui/package.json +2 -1
  173. package/packages/rpc-client/package.json +2 -2
  174. package/pkg/dist/theme/themes.js +1 -1
  175. package/pkg/dist/theme/themes.js.map +1 -1
  176. package/pkg/package.json +1 -1
  177. package/scripts/install/handoff.js +16 -3
  178. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
  179. package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
  180. package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
  181. package/src/resources/extensions/browser-tools/index.ts +75 -27
  182. package/src/resources/extensions/browser-tools/state.ts +13 -0
  183. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
  184. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
  185. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
  186. package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
  187. package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
  188. package/src/resources/extensions/browser-tools/utils.ts +3 -3
  189. package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
  190. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  191. package/src/resources/extensions/gsd/auto/loop.ts +4 -2
  192. package/src/resources/extensions/gsd/auto/phases.ts +89 -15
  193. package/src/resources/extensions/gsd/auto/session.ts +24 -1
  194. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
  195. package/src/resources/extensions/gsd/auto-dispatch.ts +117 -12
  196. package/src/resources/extensions/gsd/auto-model-selection.ts +190 -12
  197. package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
  198. package/src/resources/extensions/gsd/auto-prompts.ts +25 -22
  199. package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
  200. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  201. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  202. package/src/resources/extensions/gsd/auto-timers.ts +25 -9
  203. package/src/resources/extensions/gsd/auto.ts +41 -14
  204. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  205. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +250 -78
  206. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  207. package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
  208. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
  209. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
  210. package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
  211. package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
  212. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
  213. package/src/resources/extensions/gsd/config-overlay.ts +1 -0
  214. package/src/resources/extensions/gsd/context-masker.ts +152 -5
  215. package/src/resources/extensions/gsd/db-writer.ts +38 -0
  216. package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  217. package/src/resources/extensions/gsd/gsd-db.ts +564 -186
  218. package/src/resources/extensions/gsd/guided-flow.ts +4 -1
  219. package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
  220. package/src/resources/extensions/gsd/md-importer.ts +49 -2
  221. package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
  222. package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
  223. package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
  224. package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
  225. package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
  226. package/src/resources/extensions/gsd/preferences-models.ts +113 -43
  227. package/src/resources/extensions/gsd/preferences-types.ts +47 -0
  228. package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
  229. package/src/resources/extensions/gsd/preferences.ts +5 -0
  230. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  231. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  232. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  233. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  234. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -2
  235. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  236. package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
  237. package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
  238. package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
  239. package/src/resources/extensions/gsd/source-observations.ts +402 -0
  240. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
  241. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
  242. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
  243. package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
  244. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
  245. package/src/resources/extensions/gsd/state.ts +7 -4
  246. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +114 -0
  247. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
  248. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
  249. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
  250. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
  251. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
  252. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
  253. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
  254. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
  255. package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
  256. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
  257. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
  258. package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
  259. package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
  260. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +17 -2
  261. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
  262. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
  263. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
  264. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
  265. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
  266. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +4 -1
  267. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
  268. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -0
  269. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
  270. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
  271. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
  272. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
  273. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
  274. package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
  275. package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
  276. package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
  277. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
  278. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +133 -0
  279. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
  280. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
  281. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
  282. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +28 -0
  283. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
  284. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
  285. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
  286. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
  287. package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
  288. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
  289. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
  290. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
  291. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
  292. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
  293. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
  294. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
  295. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
  296. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +260 -5
  297. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
  298. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
  299. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
  300. package/src/resources/extensions/gsd/tool-contract.ts +29 -1
  301. package/src/resources/extensions/gsd/tool-presentation-plan.ts +41 -6
  302. package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -0
  303. package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
  304. package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
  305. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +71 -489
  306. package/src/resources/extensions/gsd/types.ts +1 -0
  307. package/src/resources/extensions/gsd/uat-policy.ts +191 -0
  308. package/src/resources/extensions/gsd/uat-run.ts +550 -0
  309. package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
  310. package/src/resources/extensions/gsd/unit-tool-contracts.ts +38 -14
  311. package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
  312. package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
  313. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -3
  314. package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
  315. package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
  316. package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
  317. package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
  318. package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
  319. package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
  320. package/src/resources/extensions/subagent/agents.ts +4 -0
  321. package/src/resources/extensions/subagent/index.ts +28 -3
  322. package/src/resources/extensions/subagent/launch.ts +8 -0
  323. package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
  324. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
  325. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
@@ -8,6 +8,7 @@ import {
8
8
  buildCloseoutMenuActions,
9
9
  buildIdleMenuSummary,
10
10
  getPrimaryCloseoutRecommendation,
11
+ showMilestoneMergeCloseout,
11
12
  } from "../closeout-wizard.ts";
12
13
  import { buildGsdHomeModel } from "../gsd-command-home.ts";
13
14
  import type { GSDState } from "../types.ts";
@@ -119,3 +120,46 @@ test("/gsd home recommends merge milestone when closeout is pending", () => {
119
120
  assert.equal(merge?.recommended, true);
120
121
  assert.equal(merge?.enabled, true);
121
122
  });
123
+
124
+ test("milestone merge closeout clears stale timer controls and installs the closeout outcome", () => {
125
+ const statuses: Array<[string, string | undefined]> = [];
126
+ const widgets: Array<[string, unknown]> = [];
127
+
128
+ showMilestoneMergeCloseout({
129
+ hasUI: true,
130
+ ui: {
131
+ setStatus: (key: string, value: string | undefined) => {
132
+ statuses.push([key, value]);
133
+ },
134
+ setWidget: (key: string, value: unknown) => {
135
+ widgets.push([key, value]);
136
+ },
137
+ },
138
+ } as any, {
139
+ milestoneId: "M004",
140
+ branch: "milestone/M004",
141
+ integrationBranch: "main",
142
+ files: ["src/app.ts"],
143
+ dirtyOverlap: [],
144
+ });
145
+
146
+ assert.deepEqual(statuses, [
147
+ ["gsd-auto", undefined],
148
+ ["gsd-step", undefined],
149
+ ]);
150
+ assert.ok(
151
+ widgets.some(([key, value]) => key === "gsd-progress" && value === undefined),
152
+ "stale progress/timer widget should be cleared",
153
+ );
154
+ const outcome = widgets.find(([key]) => key === "gsd-outcome")?.[1];
155
+ assert.equal(typeof outcome, "function");
156
+
157
+ const component = (outcome as any)(
158
+ { requestRender() {} },
159
+ { fg: (_color: string, text: string) => text, bold: (text: string) => text },
160
+ );
161
+ const rendered = component.render(100).join("\n");
162
+ assert.match(rendered, /Milestone M004 merged/);
163
+ assert.match(rendered, /Review the closeout/);
164
+ assert.doesNotMatch(rendered, /\/gsd auto/);
165
+ });
@@ -40,6 +40,7 @@ function makeMockCtx(base: string): {
40
40
  return {
41
41
  ctx: {
42
42
  cwd: base,
43
+ hasUI: true,
43
44
  ui: {
44
45
  notify: (message: string, kind: string) => {
45
46
  calls.push({ message, kind });
@@ -217,7 +218,7 @@ test("dispatcher recovers a completed unmerged milestone through complete-milest
217
218
  const base = makeTempRepo("gsd-dispatch-unmerged-");
218
219
  try {
219
220
  seedRegisteredCompletedWorktree(base);
220
- const { ctx, calls } = makeMockCtx(base);
221
+ const { ctx, calls, widgets, statuses } = makeMockCtx(base);
221
222
  const { pi, messages } = makeMockPi();
222
223
 
223
224
  await handleGSDCommand("dispatch complete-milestone M008", ctx, pi);
@@ -231,6 +232,30 @@ test("dispatcher recovers a completed unmerged milestone through complete-milest
231
232
  calls.some((call) => call.message.includes("Milestone M008 merged to main")),
232
233
  "user sees merge recovery success",
233
234
  );
235
+ assert.ok(
236
+ calls.some((call) => call.message.includes("Closeout is complete")),
237
+ "merge recovery ends at a closeout-complete message",
238
+ );
239
+ assert.ok(
240
+ calls.every((call) => !call.message.includes("Run /gsd again when ready")),
241
+ "merge recovery must not send the user past the closeout endpoint",
242
+ );
243
+ assert.ok(
244
+ statuses.some(([key, value]) => key === "gsd-auto" && value === undefined),
245
+ "merge recovery clears stale auto status so the run timer stops",
246
+ );
247
+ assert.ok(
248
+ statuses.some(([key, value]) => key === "gsd-step" && value === undefined),
249
+ "merge recovery clears stale step status",
250
+ );
251
+ assert.ok(
252
+ widgets.some(([key, value]) => key === "gsd-progress" && value === undefined),
253
+ "merge recovery clears stale progress/timer controls",
254
+ );
255
+ assert.ok(
256
+ widgets.some(([key, value]) => key === "gsd-outcome" && typeof value === "function"),
257
+ "merge recovery installs a durable closeout outcome",
258
+ );
234
259
  assert.equal(readFileSync(join(base, "index.html"), "utf-8"), "<h1>M008</h1>\n");
235
260
  assert.equal(git(base, "branch", "--list", "milestone/M008"), "");
236
261
  } finally {
@@ -155,6 +155,124 @@ describe('complete-slice verification gate (#3580)', () => {
155
155
  }
156
156
  });
157
157
 
158
+ // ── Browser/web UAT classification gate (M001/S03 regression) ──────────
159
+ const BROWSER_UAT_BODY = [
160
+ '## UAT Type',
161
+ '- UAT mode: artifact-driven',
162
+ '',
163
+ '## Smoke Test',
164
+ '1. Open the page in a browser and perform add/edit/complete/delete once.',
165
+ ].join('\n');
166
+
167
+ test('rejects an artifact-driven UAT that drives a browser (open the page in a browser)', async () => {
168
+ const result = await handleCompleteSlice(
169
+ makeParams({ uatContent: BROWSER_UAT_BODY }),
170
+ basePath,
171
+ );
172
+ assert.ok('error' in result, 'expected handler to reject a browser UAT mislabeled artifact-driven');
173
+ assert.match((result as { error: string }).error, /requires browser verification/i);
174
+ });
175
+
176
+ test('allows a runtime-executable UAT that runs a browser test command (playwright)', async () => {
177
+ // Bugbot regression: runtime-executable legitimately drives a browser via a
178
+ // command captured by gsd_uat_exec — it must not be pushed to gsd-browser.
179
+ const body = [
180
+ '## UAT Type',
181
+ '- UAT mode: runtime-executable',
182
+ '',
183
+ '## Test Cases',
184
+ '1. Run `npx playwright test` and confirm a passing exit code; capture a screenshot artifact.',
185
+ '2. Hit http://localhost:3000/health and assert a 200 response.',
186
+ ].join('\n');
187
+ const result = await handleCompleteSlice(
188
+ makeParams({ uatContent: body }),
189
+ basePath,
190
+ );
191
+ if ('error' in result) {
192
+ assert.doesNotMatch(
193
+ result.error,
194
+ /artifact-driven|browser-capable|browser verification/i,
195
+ `runtime-executable command UATs must not be gated, got: ${result.error}`,
196
+ );
197
+ }
198
+ });
199
+
200
+ test('allows an artifact-driven UAT that only disclaims browser coverage (no false positive)', async () => {
201
+ // S01-style: genuinely artifact-driven persistence scaffolding that merely
202
+ // mentions "cross-browser" / "browser-level" in a Not-Proven disclaimer.
203
+ const body = [
204
+ '## UAT Type',
205
+ '- UAT mode: artifact-driven',
206
+ '',
207
+ '## Not Proven By This UAT',
208
+ '- Interactive browser-level CRUD and real cross-browser localStorage behavior.',
209
+ ].join('\n');
210
+ const result = await handleCompleteSlice(
211
+ makeParams({ uatContent: body }),
212
+ basePath,
213
+ );
214
+ if ('error' in result) {
215
+ assert.doesNotMatch(
216
+ result.error,
217
+ /requires browser verification/i,
218
+ `disclaimer-only mention must not trip the browser gate, got: ${result.error}`,
219
+ );
220
+ }
221
+ });
222
+
223
+ test('allows an artifact-driven UAT whose "navigate" step targets a file, not a browser', async () => {
224
+ // Bugbot regression: a bare "navigate to <file/API>" must not trip the gate
225
+ // just because it contains the word "navigate".
226
+ const body = [
227
+ '## UAT Type',
228
+ '- UAT mode: artifact-driven',
229
+ '',
230
+ '## Test Cases',
231
+ '1. Navigate to the generated report file and confirm the schema section exists.',
232
+ ].join('\n');
233
+ const result = await handleCompleteSlice(
234
+ makeParams({ uatContent: body }),
235
+ basePath,
236
+ );
237
+ if ('error' in result) {
238
+ assert.doesNotMatch(
239
+ result.error,
240
+ /requires browser verification/i,
241
+ `non-web "navigate" must not trip the browser gate, got: ${result.error}`,
242
+ );
243
+ }
244
+ });
245
+
246
+ test('allows a browser UAT when it is declared browser-executable', async () => {
247
+ const body = BROWSER_UAT_BODY.replace('artifact-driven', 'browser-executable');
248
+ const result = await handleCompleteSlice(
249
+ makeParams({ uatContent: body }),
250
+ basePath,
251
+ );
252
+ if ('error' in result) {
253
+ assert.doesNotMatch(
254
+ result.error,
255
+ /requires browser verification/i,
256
+ `browser-executable UAT must pass the browser gate, got: ${result.error}`,
257
+ );
258
+ }
259
+ });
260
+
261
+ test('allows a browser UAT when it is declared mixed (mixed receives browser tools)', async () => {
262
+ const body = BROWSER_UAT_BODY.replace('artifact-driven', 'mixed (artifact-driven + browser)');
263
+ const result = await handleCompleteSlice(
264
+ makeParams({ uatContent: body }),
265
+ basePath,
266
+ );
267
+ if ('error' in result) {
268
+ assert.doesNotMatch(
269
+ result.error,
270
+ /requires browser verification/i,
271
+ `mixed UAT must pass the browser gate, got: ${result.error}`,
272
+ );
273
+ }
274
+ });
275
+
158
276
  test('backfills prior verification narrative when verification is omitted on re-completion', async () => {
159
277
  // Seed full_summary_md with a prior verification narrative (simulates a
160
278
  // previous completion where the verification text was recorded).
@@ -0,0 +1,74 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { validateContent } from "../safety/content-validator.ts";
8
+
9
+ function makeTempFile(content: string): { base: string; path: string } {
10
+ const base = mkdtempSync(join(tmpdir(), "gsd-content-validator-"));
11
+ mkdirSync(base, { recursive: true });
12
+ const path = join(base, "artifact.md");
13
+ writeFileSync(path, content, "utf-8");
14
+ return { base, path };
15
+ }
16
+
17
+ test("validateContent marks empty milestone roadmaps as blocking", () => {
18
+ const { base, path } = makeTempFile([
19
+ "# M004: Empty roadmap",
20
+ "",
21
+ "## Slices",
22
+ "",
23
+ "_TBD_",
24
+ "",
25
+ ].join("\n"));
26
+
27
+ try {
28
+ const violations = validateContent("plan-milestone", path);
29
+ assert.deepEqual(violations, [{
30
+ severity: "error",
31
+ reason: "Milestone roadmap has 0 slice(s) — expected at least 1",
32
+ }]);
33
+ } finally {
34
+ rmSync(base, { recursive: true, force: true });
35
+ }
36
+ });
37
+
38
+ test("validateContent accepts checkbox milestone roadmap slices", () => {
39
+ const { base, path } = makeTempFile([
40
+ "# M004: Roadmap",
41
+ "",
42
+ "## Slices",
43
+ "",
44
+ "- [ ] **S01: Browser due dates** `risk:low` `depends:[]`",
45
+ " > After this: due dates are visible.",
46
+ "",
47
+ ].join("\n"));
48
+
49
+ try {
50
+ const violations = validateContent("plan-milestone", path);
51
+ assert.deepEqual(violations, []);
52
+ } finally {
53
+ rmSync(base, { recursive: true, force: true });
54
+ }
55
+ });
56
+
57
+ test("validateContent marks empty slice plans as blocking", () => {
58
+ const { base, path } = makeTempFile([
59
+ "# S01: Empty slice",
60
+ "",
61
+ "## Tasks",
62
+ "",
63
+ "_TBD_",
64
+ "",
65
+ ].join("\n"));
66
+
67
+ try {
68
+ const violations = validateContent("plan-slice", path);
69
+ assert.equal(violations[0]?.severity, "error");
70
+ assert.equal(violations[0]?.reason, "Slice plan has 0 task(s) — expected at least 1");
71
+ } finally {
72
+ rmSync(base, { recursive: true, force: true });
73
+ }
74
+ });
@@ -1,7 +1,12 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
 
4
- import { createObservationMask } from "../context-masker.js";
4
+ import {
5
+ createObservationMask,
6
+ createResponsesInputObservationMask,
7
+ truncateContextResultMessages,
8
+ truncateResponsesInputResultItems,
9
+ } from "../context-masker.js";
5
10
 
6
11
  // These helpers produce messages in the pi-ai LLM payload format
7
12
  // (post-convertToLlm, pre-provider), which is what before_provider_request sees.
@@ -120,3 +125,53 @@ test("masks toolResult by role, not by type field", () => {
120
125
  const result = mask(messages as any);
121
126
  assert.equal((result[1].content as any)[0].text, MASK_TEXT);
122
127
  });
128
+
129
+ test("truncates recent bash result user messages", () => {
130
+ const messages = [
131
+ userMsg("turn 1"),
132
+ bashResult("a".repeat(50)),
133
+ assistantMsg("response 1"),
134
+ ];
135
+ const result = truncateContextResultMessages(messages as any, 10);
136
+ const text = (result[1].content as any)[0].text;
137
+ assert.ok(text.length < (messages[1].content as any)[0].text.length);
138
+ assert.match(text, /…\[truncated\]/);
139
+ });
140
+
141
+ test("masks Responses API function outputs older than keepRecentTurns", () => {
142
+ const mask = createResponsesInputObservationMask(1);
143
+ const items = [
144
+ { role: "user", content: [{ type: "input_text", text: "turn 1" }] },
145
+ { type: "function_call_output", call_id: "call_1", output: "old output" },
146
+ { type: "message", role: "assistant", content: [{ type: "output_text", text: "response 1" }] },
147
+ { role: "user", content: [{ type: "input_text", text: "turn 2" }] },
148
+ ];
149
+ const result = mask(items as any);
150
+ assert.equal(result[1].output, MASK_TEXT);
151
+ });
152
+
153
+ test("masks Responses API bash result user items older than keepRecentTurns", () => {
154
+ const mask = createResponsesInputObservationMask(1);
155
+ const items = [
156
+ { role: "user", content: [{ type: "input_text", text: "turn 1" }] },
157
+ { role: "user", content: [{ type: "input_text", text: "Ran `npm test`\n```\nold output\n```" }] },
158
+ { type: "message", role: "assistant", content: [{ type: "output_text", text: "response 1" }] },
159
+ { role: "user", content: [{ type: "input_text", text: "turn 2" }] },
160
+ ];
161
+ const result = mask(items as any);
162
+ assert.equal((result[1].content as any)[0].text, MASK_TEXT);
163
+ });
164
+
165
+ test("truncates Responses API function outputs and recent bash result items", () => {
166
+ const items = [
167
+ { role: "user", content: [{ type: "input_text", text: "turn 1" }] },
168
+ { type: "function_call_output", call_id: "call_1", output: "b".repeat(50) },
169
+ { role: "user", content: [{ type: "input_text", text: "Ran `npm test`\n```\n" + "c".repeat(50) + "\n```" }] },
170
+ ];
171
+ const result = truncateResponsesInputResultItems(items as any, 12);
172
+
173
+ assert.match(result[1].output as string, /…\[truncated\]/);
174
+ assert.match((result[2].content as any)[0].text, /…\[truncated\]/);
175
+ assert.ok((result[1].output as string).length < (items[1].output as string).length);
176
+ assert.ok((result[2].content as any)[0].text.length < (items[2].content as any)[0].text.length);
177
+ });
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { describe, it, afterEach } from "node:test";
10
10
  import assert from "node:assert/strict";
11
- import { mkdtempSync, rmSync, existsSync } from "node:fs";
11
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  import { tmpdir } from "node:os";
14
14
 
@@ -18,7 +18,7 @@ import type { LoopDeps } from "../auto/loop-deps.js";
18
18
  import { WorktreeStateProjection } from "../worktree-state-projection.js";
19
19
  import type { SessionLockStatus } from "../session-lock.js";
20
20
  import { writeGraph, readGraph, type WorkflowGraph, type GraphStep } from "../graph.ts";
21
- import { writeFileSync } from "node:fs";
21
+ import { SourceObservationStore } from "../source-observations.js";
22
22
  import { stringify } from "yaml";
23
23
 
24
24
  // ─── Helpers ─────────────────────────────────────────────────────────────
@@ -115,6 +115,7 @@ function makeLoopSession(overrides?: Record<string, unknown>) {
115
115
  currentMilestoneId: null,
116
116
  currentUnit: null,
117
117
  currentUnitRouting: null,
118
+ sourceObservations: new SourceObservationStore(),
118
119
  completedUnits: [],
119
120
  resourceVersionOnStart: null,
120
121
  lastPromptCharCount: undefined,
@@ -129,6 +130,7 @@ function makeLoopSession(overrides?: Record<string, unknown>) {
129
130
  unitLifetimeDispatches: new Map<string, number>(),
130
131
  unitRecoveryCount: new Map<string, number>(),
131
132
  verificationRetryCount: new Map<string, number>(),
133
+ zeroToolRetryCount: new Map<string, number>(),
132
134
  gitService: null,
133
135
  autoStartTime: Date.now(),
134
136
  activeEngineId: null,
@@ -138,6 +140,19 @@ function makeLoopSession(overrides?: Record<string, unknown>) {
138
140
  newSession: () => Promise.resolve({ cancelled: false }),
139
141
  getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
140
142
  },
143
+ setCurrentUnit(this: any, unit: any) {
144
+ this.currentUnit = unit;
145
+ this.sourceObservations.beginUnit({
146
+ unitType: unit.type,
147
+ unitId: unit.id,
148
+ startedAt: unit.startedAt,
149
+ basePath: unit.workspaceRoot ?? this.basePath,
150
+ });
151
+ },
152
+ clearCurrentUnit(this: any) {
153
+ this.currentUnit = null;
154
+ this.sourceObservations.clear();
155
+ },
141
156
  clearTimers: () => {},
142
157
  lockBasePath: "/tmp/project",
143
158
  ...overrides,
@@ -216,6 +216,30 @@ test("dispatch-rule-coverage: planning with active slice and skip_research → p
216
216
  );
217
217
  });
218
218
 
219
+ test("dispatch-rule-coverage: planning boundary without planner handoff → research-slice", async (t) => {
220
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-planning-"));
221
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
222
+
223
+ writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
224
+ writeMilestoneFile(tmp, "M001", "ROADMAP", "# Roadmap\n");
225
+
226
+ const state = makeState({
227
+ phase: "planning",
228
+ activeSlice: { id: "S01", title: "First Slice" },
229
+ nextAction: "Plan slice S01 (First Slice).",
230
+ });
231
+ const match = await findFirstMatch(makeCtx(tmp, state));
232
+ assertMatch(
233
+ match,
234
+ {
235
+ ruleName: "planning (no research, not S01) → research-slice",
236
+ action: "dispatch",
237
+ unitType: "research-slice",
238
+ },
239
+ "planning boundary without planner handoff",
240
+ );
241
+ });
242
+
219
243
  test("dispatch-rule-coverage: executing with task plan present → execute-task", async (t) => {
220
244
  const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-exec-"));
221
245
  t.after(() => rmSync(tmp, { recursive: true, force: true }));
@@ -109,22 +109,12 @@ test("checkEngineHealth treats explicit reopen as authoritative when dispatch ti
109
109
  worker_id, host, pid, started_at, version, last_heartbeat_at, status, project_root_realpath
110
110
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
111
111
  ).run("worker-1", "localhost", 1, "2026-01-01T00:00:00.000Z", "test", "2026-01-01T00:00:00.000Z", "stopped", base);
112
- db.exec("PRAGMA writable_schema = ON");
113
112
  db.prepare(
114
- `UPDATE sqlite_schema
115
- SET sql = replace(sql, 'started_at TEXT NOT NULL', 'started_at TEXT')
116
- WHERE type = 'table' AND name = 'unit_dispatches'`,
117
- ).run();
118
- db.exec("PRAGMA writable_schema = OFF");
119
- closeDatabase();
120
- openDatabase(join(gsdDir, "gsd.db"));
121
- const reopenedDb = _getAdapter()!;
122
- reopenedDb.prepare(
123
113
  `INSERT INTO unit_dispatches (
124
114
  trace_id, worker_id, milestone_lease_token, milestone_id,
125
115
  unit_type, unit_id, status, attempt_n, started_at, ended_at
126
116
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
127
- ).run("trace-1", "worker-1", 1, "M001", "complete-milestone", "M001", "completed", 1, null, null);
117
+ ).run("trace-1", "worker-1", 1, "M001", "complete-milestone", "M001", "completed", 1, "", "");
128
118
  appendEvent(base, {
129
119
  cmd: "reopen-milestone",
130
120
  params: { milestoneId: "M001" },
@@ -19,10 +19,12 @@ import {
19
19
  saveGateResult,
20
20
  markAllGatesOmitted,
21
21
  getPendingSliceGateCount,
22
+ getGateResults,
22
23
  } from "../gsd-db.ts";
23
24
  import { deriveState, invalidateStateCache } from "../state.ts";
24
25
  import { renderPlanFromDb } from "../markdown-renderer.ts";
25
26
  import { invalidateAllCaches } from "../cache.ts";
27
+ import { DISPATCH_RULES } from "../auto-dispatch.ts";
26
28
 
27
29
  function setupTestProject(): { tmpDir: string; dbPath: string } {
28
30
  const tmpDir = mkdtempSync(join(tmpdir(), "gate-dispatch-"));
@@ -213,4 +215,66 @@ describe("evaluating-gates phase", () => {
213
215
  `pending Q8 must not stall evaluating-gates — got phase=${state.phase}`,
214
216
  );
215
217
  });
218
+
219
+ test("gate-evaluate dispatch id includes only gate-evaluate-owned gates", async () => {
220
+ planSlice(tmpDir);
221
+ await renderPlanFromDb(tmpDir, "M001", "S01");
222
+
223
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
224
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
225
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q8", scope: "slice" });
226
+
227
+ invalidateStateCache();
228
+ const state = await deriveState(tmpDir);
229
+ assert.equal(state.phase, "evaluating-gates");
230
+
231
+ const rule = DISPATCH_RULES.find((candidate) => candidate.name === "evaluating-gates → gate-evaluate");
232
+ assert.ok(rule, "gate-evaluate dispatch rule must exist");
233
+
234
+ const result = await rule.match({
235
+ basePath: tmpDir,
236
+ mid: "M001",
237
+ midTitle: "Test Milestone",
238
+ state,
239
+ prefs: { gate_evaluation: { enabled: true } },
240
+ });
241
+
242
+ assert.ok(result);
243
+ assert.equal(result.action, "dispatch");
244
+ if (result.action !== "dispatch") throw new Error("expected gate-evaluate dispatch");
245
+ assert.equal(result.unitId, "M001/S01/gates+Q3,Q4");
246
+ assert.doesNotMatch(result.prompt, /\*\*Q8\*\*/);
247
+ });
248
+
249
+ test("disabled gate evaluation only omits gate-evaluate-owned gates", async () => {
250
+ planSlice(tmpDir);
251
+ await renderPlanFromDb(tmpDir, "M001", "S01");
252
+
253
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
254
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
255
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q8", scope: "slice" });
256
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
257
+
258
+ invalidateStateCache();
259
+ const state = await deriveState(tmpDir);
260
+ assert.equal(state.phase, "evaluating-gates");
261
+
262
+ const rule = DISPATCH_RULES.find((candidate) => candidate.name === "evaluating-gates → gate-evaluate");
263
+ assert.ok(rule, "gate-evaluate dispatch rule must exist");
264
+
265
+ const result = await rule.match({
266
+ basePath: tmpDir,
267
+ mid: "M001",
268
+ midTitle: "Test Milestone",
269
+ state,
270
+ prefs: { gate_evaluation: { enabled: false } },
271
+ });
272
+
273
+ assert.equal(result?.action, "skip");
274
+ const byId = new Map(getGateResults("M001", "S01").map((gate) => [gate.gate_id, gate]));
275
+ assert.equal(byId.get("Q3")?.verdict, "omitted");
276
+ assert.equal(byId.get("Q4")?.verdict, "omitted");
277
+ assert.equal(byId.get("Q8")?.status, "pending");
278
+ assert.equal(byId.get("Q5")?.status, "pending");
279
+ });
216
280
  });
@@ -83,6 +83,21 @@ describe("quality_gates CRUD", () => {
83
83
  assert.ok(results[0].evaluated_at);
84
84
  });
85
85
 
86
+ test("saveGateResult throws when the gate row does not exist", () => {
87
+ assert.throws(
88
+ () => saveGateResult({
89
+ milestoneId: "M001",
90
+ sliceId: "S01",
91
+ gateId: "Q3",
92
+ verdict: "pass",
93
+ rationale: "No row exists.",
94
+ findings: "",
95
+ }),
96
+ /quality gate row not found/,
97
+ );
98
+ assert.equal(getGateResults("M001", "S01").length, 0);
99
+ });
100
+
86
101
  test("getPendingGates filters by scope", () => {
87
102
  insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
88
103
  insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
@@ -502,7 +502,9 @@ describe('gsd-recover', async () => {
502
502
  insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
503
503
 
504
504
  const { ctx, notes } = makeCtx();
505
- await handleRecover(ctx, base, '--confirm');
505
+ // M999 is in the DB but not in the markdown, so recover would delete it:
506
+ // a data-loss recover now requires explicit --allow-data-loss.
507
+ await handleRecover(ctx, base, '--confirm --allow-data-loss');
506
508
 
507
509
  assert.equal(getMilestone('M999'), null, 'confirmed recover clears old hierarchy rows');
508
510
  assert.ok(getMilestone('M001'), 'confirmed recover imports markdown hierarchy');
@@ -512,4 +514,63 @@ describe('gsd-recover', async () => {
512
514
  cleanup(base);
513
515
  }
514
516
  });
517
+
518
+ test('handleRecover refuses to delete DB rows markdown lacks without --allow-data-loss', async () => {
519
+ const base = createFixtureBase();
520
+ try {
521
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
522
+ openDatabase(':memory:');
523
+ insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
524
+
525
+ const { ctx, notes } = makeCtx();
526
+ await handleRecover(ctx, base, '--confirm');
527
+
528
+ // --confirm alone must NOT clear authoritative DB rows the markdown lacks.
529
+ assert.ok(getMilestone('M999'), 'data-loss recover is refused, DB row preserved');
530
+ assert.equal(getMilestone('M001'), null, 'markdown not imported on refusal');
531
+ assert.equal(notes.at(-1)?.kind, 'error');
532
+ } finally {
533
+ closeDatabase();
534
+ cleanup(base);
535
+ }
536
+ });
537
+
538
+ test('handleRecover interactive data-loss requires a second explicit acknowledgement', async () => {
539
+ const base = createFixtureBase();
540
+ try {
541
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
542
+ openDatabase(':memory:');
543
+ insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
544
+
545
+ // First confirm (proceed?) = yes; second confirm (delete rows?) = no.
546
+ let call = 0;
547
+ const { ctx, notes } = makeCtx(async () => { call += 1; return call === 1; });
548
+ await handleRecover(ctx, base, '');
549
+
550
+ assert.ok(getMilestone('M999'), 'declining the data-loss ack preserves DB rows');
551
+ assert.equal(getMilestone('M001'), null, 'markdown not imported when data-loss ack declined');
552
+ assert.match(notes.at(-1)?.message ?? '', /cancelled/);
553
+ } finally {
554
+ closeDatabase();
555
+ cleanup(base);
556
+ }
557
+ });
558
+
559
+ test('handleRecover interactive proceeds when the data-loss deletion is acknowledged', async () => {
560
+ const base = createFixtureBase();
561
+ try {
562
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
563
+ openDatabase(':memory:');
564
+ insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
565
+
566
+ const { ctx } = makeCtx(async () => true); // both confirms accepted
567
+ await handleRecover(ctx, base, '');
568
+
569
+ assert.equal(getMilestone('M999'), null, 'acknowledged data-loss recover clears old rows');
570
+ assert.ok(getMilestone('M001'), 'acknowledged data-loss recover imports markdown');
571
+ } finally {
572
+ closeDatabase();
573
+ cleanup(base);
574
+ }
575
+ });
515
576
  });