@opengsd/gsd-pi 1.1.1-dev.74e8dd1 → 1.1.1-dev.75048e7

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 (329) hide show
  1. package/dist/cli.js +3 -2
  2. package/dist/help-text.js +10 -6
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +495 -0
  5. package/dist/resources/extensions/browser-tools/engine/selection.js +16 -0
  6. package/dist/resources/extensions/browser-tools/extension-manifest.json +2 -2
  7. package/dist/resources/extensions/browser-tools/index.js +57 -9
  8. package/dist/resources/extensions/browser-tools/package.json +5 -1
  9. package/dist/resources/extensions/gsd/auto/orchestrator.js +0 -1
  10. package/dist/resources/extensions/gsd/auto-dashboard.js +77 -13
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
  12. package/dist/resources/extensions/gsd/auto-post-unit.js +21 -3
  13. package/dist/resources/extensions/gsd/auto-prompts.js +63 -22
  14. package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
  15. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  16. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  17. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
  18. package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
  19. package/dist/resources/extensions/gsd/auto.js +9 -2
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +20 -14
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +28 -13
  22. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
  23. package/dist/resources/extensions/gsd/browser-evidence.js +29 -2
  24. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
  25. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -2
  26. package/dist/resources/extensions/gsd/commands-handlers.js +76 -11
  27. package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -1
  28. package/dist/resources/extensions/gsd/dashboard-overlay.js +21 -7
  29. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  30. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +2 -2
  31. package/dist/resources/extensions/gsd/escalation.js +4 -4
  32. package/dist/resources/extensions/gsd/forensics.js +74 -2
  33. package/dist/resources/extensions/gsd/gsd-db.js +5 -2
  34. package/dist/resources/extensions/gsd/guided-flow.js +118 -175
  35. package/dist/resources/extensions/gsd/mcp-project-config.js +9 -76
  36. package/dist/resources/extensions/gsd/memory-store.js +4 -1
  37. package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
  38. package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
  39. package/dist/resources/extensions/gsd/post-unit-hooks.js +9 -0
  40. package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
  41. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  42. package/dist/resources/extensions/gsd/prompts/forensics.md +61 -1
  43. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  44. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  45. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  47. package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -21
  48. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  49. package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
  50. package/dist/resources/extensions/gsd/rule-registry.js +428 -52
  51. package/dist/resources/extensions/gsd/state.js +2 -2
  52. package/dist/resources/extensions/gsd/templates/plan.md +3 -1
  53. package/dist/resources/extensions/gsd/tool-contract.js +5 -0
  54. package/dist/resources/extensions/gsd/tool-presentation-plan.js +30 -7
  55. package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -1
  56. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -1
  57. package/dist/resources/extensions/gsd/tools/validate-milestone.js +46 -16
  58. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +132 -18
  59. package/dist/resources/extensions/gsd/unit-tool-contracts.js +169 -0
  60. package/dist/resources/extensions/gsd/verdict-parser.js +59 -15
  61. package/dist/resources/extensions/gsd/verification-gate.js +72 -1
  62. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -75
  63. package/dist/resources/extensions/shared/gsd-browser-cli.js +145 -0
  64. package/dist/rtk.d.ts +7 -1
  65. package/dist/rtk.js +27 -11
  66. package/dist/update-check.d.ts +15 -1
  67. package/dist/update-check.js +87 -12
  68. package/dist/update-cmd.d.ts +1 -0
  69. package/dist/update-cmd.js +53 -2
  70. package/dist/web/standalone/.next/BUILD_ID +1 -1
  71. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  72. package/dist/web/standalone/.next/build-manifest.json +2 -2
  73. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  74. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/index.html +1 -1
  92. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  99. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  100. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  102. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  103. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  104. package/package.json +4 -2
  105. package/packages/cloud-mcp-gateway/package.json +2 -2
  106. package/packages/contracts/package.json +1 -1
  107. package/packages/daemon/package.json +4 -4
  108. package/packages/gsd-agent-core/dist/agent-session.d.ts +9 -0
  109. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  110. package/packages/gsd-agent-core/dist/agent-session.js +32 -0
  111. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  112. package/packages/gsd-agent-core/dist/index.d.ts +1 -0
  113. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  114. package/packages/gsd-agent-core/dist/index.js +1 -0
  115. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  116. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts +2 -0
  117. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts.map +1 -1
  118. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js +8 -2
  119. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js.map +1 -1
  120. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +7 -0
  121. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  122. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  123. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  124. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +69 -1
  125. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  126. package/packages/gsd-agent-core/dist/turn-latency.d.ts +47 -0
  127. package/packages/gsd-agent-core/dist/turn-latency.d.ts.map +1 -0
  128. package/packages/gsd-agent-core/dist/turn-latency.js +123 -0
  129. package/packages/gsd-agent-core/dist/turn-latency.js.map +1 -0
  130. package/packages/gsd-agent-core/package.json +6 -6
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +21 -0
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +1 -0
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +213 -0
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +1 -0
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.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 +20 -0
  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/controllers/input-controller.d.ts.map +1 -1
  142. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +7 -1
  143. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  144. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.d.ts.map +1 -1
  145. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js +6 -0
  146. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js.map +1 -1
  147. package/packages/gsd-agent-modes/package.json +7 -7
  148. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  149. package/packages/mcp-server/dist/remote-questions.js +23 -9
  150. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  151. package/packages/mcp-server/dist/workflow-tools.js +2 -2
  152. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  153. package/packages/mcp-server/package.json +3 -3
  154. package/packages/native/package.json +1 -1
  155. package/packages/pi-agent-core/dist/agent-loop.js +42 -3
  156. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  157. package/packages/pi-agent-core/dist/agent.d.ts +5 -1
  158. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  159. package/packages/pi-agent-core/dist/agent.js +2 -0
  160. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  161. package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  162. package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
  163. package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  164. package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
  165. package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  166. package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
  167. package/packages/pi-agent-core/dist/types.d.ts +6 -1
  168. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  169. package/packages/pi-agent-core/dist/types.js.map +1 -1
  170. package/packages/pi-agent-core/package.json +1 -1
  171. package/packages/pi-ai/dist/api-registry.d.ts +2 -0
  172. package/packages/pi-ai/dist/api-registry.d.ts.map +1 -1
  173. package/packages/pi-ai/dist/api-registry.js +23 -0
  174. package/packages/pi-ai/dist/api-registry.js.map +1 -1
  175. package/packages/pi-ai/dist/models.generated.d.ts +74 -23
  176. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  177. package/packages/pi-ai/dist/models.generated.js +82 -31
  178. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  179. package/packages/pi-ai/dist/stream.js +6 -6
  180. package/packages/pi-ai/dist/stream.js.map +1 -1
  181. package/packages/pi-ai/package.json +1 -1
  182. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
  183. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -2
  187. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
  189. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
  192. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  193. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
  194. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
  196. package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
  199. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  200. package/packages/pi-coding-agent/package.json +7 -7
  201. package/packages/pi-tui/package.json +1 -1
  202. package/packages/rpc-client/package.json +2 -2
  203. package/pkg/package.json +1 -1
  204. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +579 -0
  205. package/src/resources/extensions/browser-tools/engine/selection.ts +19 -0
  206. package/src/resources/extensions/browser-tools/extension-manifest.json +2 -2
  207. package/src/resources/extensions/browser-tools/index.ts +60 -9
  208. package/src/resources/extensions/browser-tools/package.json +5 -1
  209. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +35 -0
  210. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +33 -0
  211. package/src/resources/extensions/gsd/auto/orchestrator.ts +0 -1
  212. package/src/resources/extensions/gsd/auto-dashboard.ts +82 -14
  213. package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
  214. package/src/resources/extensions/gsd/auto-post-unit.ts +28 -2
  215. package/src/resources/extensions/gsd/auto-prompts.ts +97 -15
  216. package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
  217. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
  218. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  219. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
  220. package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
  221. package/src/resources/extensions/gsd/auto.ts +12 -2
  222. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +20 -14
  223. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +32 -13
  224. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
  225. package/src/resources/extensions/gsd/browser-evidence.ts +26 -2
  226. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
  227. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -2
  228. package/src/resources/extensions/gsd/commands-handlers.ts +76 -11
  229. package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -1
  230. package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -7
  231. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  232. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +2 -2
  233. package/src/resources/extensions/gsd/escalation.ts +4 -4
  234. package/src/resources/extensions/gsd/forensics.ts +99 -5
  235. package/src/resources/extensions/gsd/gsd-db.ts +5 -2
  236. package/src/resources/extensions/gsd/guided-flow.ts +214 -216
  237. package/src/resources/extensions/gsd/mcp-project-config.ts +13 -78
  238. package/src/resources/extensions/gsd/memory-store.ts +4 -1
  239. package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
  240. package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
  241. package/src/resources/extensions/gsd/post-unit-hooks.ts +14 -1
  242. package/src/resources/extensions/gsd/preferences-validation.ts +36 -0
  243. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  244. package/src/resources/extensions/gsd/prompts/forensics.md +61 -1
  245. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  246. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  247. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  248. package/src/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  249. package/src/resources/extensions/gsd/prompts/run-uat.md +25 -21
  250. package/src/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  251. package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
  252. package/src/resources/extensions/gsd/rule-registry.ts +558 -58
  253. package/src/resources/extensions/gsd/rule-types.ts +2 -0
  254. package/src/resources/extensions/gsd/state.ts +2 -2
  255. package/src/resources/extensions/gsd/templates/plan.md +3 -1
  256. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +105 -4
  257. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +37 -0
  258. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
  259. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
  260. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
  261. package/src/resources/extensions/gsd/tests/browser-evidence.test.ts +142 -0
  262. package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
  263. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
  264. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
  265. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +30 -0
  266. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
  267. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +45 -0
  268. package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +53 -0
  269. package/src/resources/extensions/gsd/tests/discuss-milestone-structured-questions.test.ts +31 -0
  270. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
  271. package/src/resources/extensions/gsd/tests/doctor-runtime-checks.test.ts +27 -0
  272. package/src/resources/extensions/gsd/tests/escalation.test.ts +16 -27
  273. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +20 -0
  274. package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +3 -0
  275. package/src/resources/extensions/gsd/tests/forensics-tool-scope.test.ts +69 -0
  276. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
  277. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +40 -1
  278. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +86 -0
  279. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
  280. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
  281. package/src/resources/extensions/gsd/tests/guided-flow.test.ts +12 -9
  282. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +4 -4
  283. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
  284. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
  285. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +72 -10
  286. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +32 -0
  287. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +2 -0
  288. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +39 -8
  289. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
  290. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
  291. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
  292. package/src/resources/extensions/gsd/tests/new-milestone-discuss-routing.test.ts +3 -3
  293. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +9 -0
  294. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +157 -0
  295. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +179 -0
  296. package/src/resources/extensions/gsd/tests/preferences.test.ts +29 -0
  297. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +73 -1
  298. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +14 -0
  299. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +7 -8
  300. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
  301. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +75 -0
  302. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
  303. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +36 -0
  304. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +100 -0
  305. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +139 -0
  306. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
  307. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
  308. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +7 -1
  309. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +6 -3
  310. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +133 -0
  311. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +51 -0
  312. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +410 -0
  313. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
  314. package/src/resources/extensions/gsd/tool-contract.ts +6 -0
  315. package/src/resources/extensions/gsd/tool-presentation-plan.ts +63 -7
  316. package/src/resources/extensions/gsd/tools/complete-slice.ts +14 -1
  317. package/src/resources/extensions/gsd/tools/complete-task.ts +20 -2
  318. package/src/resources/extensions/gsd/tools/validate-milestone.ts +46 -15
  319. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +163 -20
  320. package/src/resources/extensions/gsd/types.ts +69 -5
  321. package/src/resources/extensions/gsd/unit-tool-contracts.ts +186 -0
  322. package/src/resources/extensions/gsd/verdict-parser.ts +54 -13
  323. package/src/resources/extensions/gsd/verification-gate.ts +87 -1
  324. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -75
  325. package/src/resources/extensions/shared/gsd-browser-cli.ts +172 -0
  326. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
  327. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
  328. /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → h4TGni4xJzlZjGkxaT6uU}/_buildManifest.js +0 -0
  329. /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → h4TGni4xJzlZjGkxaT6uU}/_ssgManifest.js +0 -0
@@ -298,6 +298,106 @@ test("session_start installs the welcome screen as the TUI header", async (t) =>
298
298
  assert.deepEqual(header.render(123), ["welcome 9.9.9-test none 123"]);
299
299
  });
300
300
 
301
+ test("session hooks preserve closeout-boundary UI during completion reroot", async (t) => {
302
+ const dir = join(
303
+ tmpdir(),
304
+ `gsd-closeout-session-hooks-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
305
+ );
306
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
307
+ mkdirSync(join(dir, "dist"), { recursive: true });
308
+ const tempGsdHome = join(dir, "home");
309
+ mkdirSync(tempGsdHome, { recursive: true });
310
+ writeFileSync(
311
+ join(dir, "dist", "welcome-screen.js"),
312
+ "export function buildWelcomeScreenLines() { return ['welcome header']; }\n",
313
+ "utf-8",
314
+ );
315
+
316
+ const originalCwd = process.cwd();
317
+ const originalGsdHome = process.env.GSD_HOME;
318
+ const originalGsdPkgRoot = process.env.GSD_PKG_ROOT;
319
+ process.chdir(dir);
320
+ process.env.GSD_HOME = tempGsdHome;
321
+ process.env.GSD_PKG_ROOT = dir;
322
+ autoSession.reset();
323
+ autoSession.completionStopInProgress = true;
324
+ t.after(() => {
325
+ autoSession.reset();
326
+ process.chdir(originalCwd);
327
+ if (originalGsdHome === undefined) delete process.env.GSD_HOME;
328
+ else process.env.GSD_HOME = originalGsdHome;
329
+ if (originalGsdPkgRoot === undefined) delete process.env.GSD_PKG_ROOT;
330
+ else process.env.GSD_PKG_ROOT = originalGsdPkgRoot;
331
+ try { rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort */ }
332
+ });
333
+
334
+ const handlers = new Map<string, (event: unknown, ctx: any) => Promise<void> | void>();
335
+ const pi = {
336
+ on(event: string, handler: (event: unknown, ctx: any) => Promise<void> | void) {
337
+ handlers.set(event, handler);
338
+ },
339
+ } as any;
340
+
341
+ registerHooks(pi, []);
342
+
343
+ const sessionStart = handlers.get("session_start");
344
+ const sessionSwitch = handlers.get("session_switch");
345
+ assert.ok(sessionStart, "session_start handler must be registered");
346
+ assert.ok(sessionSwitch, "session_switch handler must be registered");
347
+
348
+ const widgetCalls: Array<{ key: string; value: unknown }> = [];
349
+ let headerInstallCount = 0;
350
+ const ctx = {
351
+ hasUI: true,
352
+ ui: {
353
+ notify: () => {},
354
+ setStatus: () => {},
355
+ setFooter: () => {},
356
+ setHeader: () => {
357
+ headerInstallCount++;
358
+ },
359
+ setWorkingMessage: () => {},
360
+ onTerminalInput: () => () => {},
361
+ setWidget: (key: string, value: unknown) => {
362
+ widgetCalls.push({ key, value });
363
+ },
364
+ },
365
+ sessionManager: { getSessionId: () => null },
366
+ model: null,
367
+ setCompactionThresholdOverride: () => {},
368
+ modelRegistry: {
369
+ setDisabledModelProviders: () => {},
370
+ getProviderAuthMode: () => undefined,
371
+ isProviderRequestReady: () => false,
372
+ },
373
+ };
374
+
375
+ await sessionStart!({}, ctx);
376
+ assert.equal(
377
+ headerInstallCount,
378
+ 0,
379
+ "completion reroot session_start must not reinstall the welcome/project-console header",
380
+ );
381
+ assert.ok(
382
+ widgetCalls.some((call) => call.key === "gsd-health" && call.value === undefined),
383
+ "completion reroot session_start should hide the ambient health widget",
384
+ );
385
+
386
+ widgetCalls.length = 0;
387
+ await sessionSwitch!({ reason: "reroot" }, ctx);
388
+ assert.deepEqual(
389
+ widgetCalls
390
+ .filter((call) => call.key === "gsd-progress" || call.key === "gsd-outcome")
391
+ .map((call) => [call.key, call.value]),
392
+ [],
393
+ "completion reroot session_switch must not clear the preserved closeout surface",
394
+ );
395
+ assert.ok(
396
+ widgetCalls.some((call) => call.key === "gsd-health" && call.value === undefined),
397
+ "completion reroot session_switch should keep the ambient health widget hidden",
398
+ );
399
+ });
400
+
301
401
  test("session_start and session_switch apply disabled model provider policy from current preferences", async (t) => {
302
402
  const dir = join(
303
403
  tmpdir(),
@@ -1352,6 +1352,145 @@ test("ADR-017: orphan task completion artifact fails closed", async (t) => {
1352
1352
  assert.match(result.blockers.join("\n"), /Artifact\/DB status drift/);
1353
1353
  });
1354
1354
 
1355
+ test("ADR-017 (#414): failure-path summary artifact blocker matches auto.ts filter phrase", async (t) => {
1356
+ // When gsd_summary_save writes a SUMMARY artifact row for a task that never
1357
+ // called gsd_task_complete, the task stays pending and the artifact DB row
1358
+ // produces an artifact-db-status-divergence blocker. The auto.ts dispatch
1359
+ // wrapper must be able to filter this class of blocker to allow re-dispatch.
1360
+ // If this test fails, update the filter strings in auto.ts to match.
1361
+ const base = mkdtempSync(join(tmpdir(), "gsd-failure-path-summary-drift-"));
1362
+ t.after(() => cleanup(base));
1363
+
1364
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S04"), { recursive: true });
1365
+ openDatabase(join(base, ".gsd", "gsd.db"));
1366
+ insertMilestone({ id: "M001", title: "Milestone", status: "active" });
1367
+ insertSlice({ id: "S04", milestoneId: "M001", title: "Slice", status: "pending" });
1368
+ insertTask({ id: "T01", sliceId: "S04", milestoneId: "M001", title: "Task", status: "pending" });
1369
+ insertArtifact({
1370
+ path: join(base, ".gsd", "milestones", "M001", "slices", "S04", "tasks", "T01", "T01-SUMMARY.md"),
1371
+ artifact_type: "SUMMARY",
1372
+ milestone_id: "M001",
1373
+ slice_id: "S04",
1374
+ task_id: "T01",
1375
+ full_content: "# T01 Failure Summary\n",
1376
+ });
1377
+
1378
+ const result = await reconcileBeforeDispatch(base, {
1379
+ invalidateStateCache: () => {},
1380
+ deriveState: async () => makeState({ activeMilestone: { id: "M001", title: "Milestone" } }),
1381
+ });
1382
+
1383
+ assert.equal(result.ok, true);
1384
+ assert.ok(result.blockers.length > 0, "blocker must be produced for pending-task SUMMARY drift");
1385
+ const blocker = result.blockers.join("\n");
1386
+ assert.match(
1387
+ blocker,
1388
+ /has SUMMARY artifact while DB status is/,
1389
+ "blocker phrase must match the filter in auto.ts reconcileBeforeDispatch wrapper",
1390
+ );
1391
+ });
1392
+
1393
+ test("ADR-017 (#414): no-db-tasks summary artifact blocker matches auto.ts filter phrase", async (t) => {
1394
+ // When a slice has SUMMARY artifacts in the DB but no DB tasks, the auto.ts
1395
+ // filter must be able to recognise this as a failure-path case and skip it.
1396
+ const base = mkdtempSync(join(tmpdir(), "gsd-no-db-tasks-summary-drift-"));
1397
+ t.after(() => cleanup(base));
1398
+
1399
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S04"), { recursive: true });
1400
+ openDatabase(join(base, ".gsd", "gsd.db"));
1401
+ insertMilestone({ id: "M001", title: "Milestone", status: "active" });
1402
+ insertSlice({ id: "S04", milestoneId: "M001", title: "Slice", status: "pending" });
1403
+ // No tasks inserted — slice has SUMMARY artifacts for a task that no longer exists.
1404
+ insertArtifact({
1405
+ path: join(base, ".gsd", "milestones", "M001", "slices", "S04", "tasks", "T01", "T01-SUMMARY.md"),
1406
+ artifact_type: "SUMMARY",
1407
+ milestone_id: "M001",
1408
+ slice_id: "S04",
1409
+ task_id: "T01",
1410
+ full_content: "# T01 Failure Summary\n",
1411
+ });
1412
+
1413
+ const result = await reconcileBeforeDispatch(base, {
1414
+ invalidateStateCache: () => {},
1415
+ deriveState: async () => makeState({ activeMilestone: { id: "M001", title: "Milestone" } }),
1416
+ });
1417
+
1418
+ assert.equal(result.ok, true);
1419
+ assert.ok(result.blockers.length > 0, "blocker must be produced for no-db-tasks SUMMARY drift");
1420
+ const blocker = result.blockers.join("\n");
1421
+ assert.match(
1422
+ blocker,
1423
+ /has task SUMMARY artifacts but no DB tasks/,
1424
+ "blocker phrase must match the filter in auto.ts reconcileBeforeDispatch wrapper",
1425
+ );
1426
+ });
1427
+
1428
+ test("ADR-017 (#414): task-level on-disk summary blocker matches auto.ts filter phrase", async (t) => {
1429
+ // When gsd_summary_save writes a SUMMARY file to disk for a task that never
1430
+ // called gsd_task_complete, but the artifact DB row was not yet written (or
1431
+ // the process crashed before insertion), reconciliation emits
1432
+ // "has SUMMARY on disk while DB status is". The auto.ts filter must match
1433
+ // this phrase so re-dispatch is not blocked.
1434
+ const base = mkdtempSync(join(tmpdir(), "gsd-task-disk-summary-drift-"));
1435
+ t.after(() => cleanup(base));
1436
+
1437
+ const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S04", "tasks");
1438
+ mkdirSync(tasksDir, { recursive: true });
1439
+ writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Failure Summary\n");
1440
+
1441
+ openDatabase(join(base, ".gsd", "gsd.db"));
1442
+ insertMilestone({ id: "M001", title: "Milestone", status: "active" });
1443
+ insertSlice({ id: "S04", milestoneId: "M001", title: "Slice", status: "pending" });
1444
+ insertTask({ id: "T01", sliceId: "S04", milestoneId: "M001", title: "Task", status: "pending" });
1445
+ // No artifact row inserted — SUMMARY exists only on disk.
1446
+
1447
+ const result = await reconcileBeforeDispatch(base, {
1448
+ invalidateStateCache: () => {},
1449
+ deriveState: async () => makeState({ activeMilestone: { id: "M001", title: "Milestone" } }),
1450
+ });
1451
+
1452
+ assert.equal(result.ok, true);
1453
+ assert.ok(result.blockers.length > 0, "blocker must be produced for on-disk task SUMMARY drift");
1454
+ const blocker = result.blockers.join("\n");
1455
+ assert.match(
1456
+ blocker,
1457
+ /has SUMMARY on disk while DB status is/,
1458
+ "blocker phrase must match the filter in auto.ts reconcileBeforeDispatch wrapper",
1459
+ );
1460
+ });
1461
+
1462
+ test("ADR-017 (#414): slice-level on-disk summary blocker matches auto.ts filter phrase", async (t) => {
1463
+ // When a SUMMARY file exists on disk for a slice that is still pending
1464
+ // (no gsd_task_complete for the slice), reconciliation emits
1465
+ // "has SUMMARY on disk while DB status is". The auto.ts filter must match
1466
+ // this phrase so re-dispatch is not blocked.
1467
+ const base = mkdtempSync(join(tmpdir(), "gsd-slice-disk-summary-drift-"));
1468
+ t.after(() => cleanup(base));
1469
+
1470
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S04");
1471
+ mkdirSync(sliceDir, { recursive: true });
1472
+ writeFileSync(join(sliceDir, "S04-SUMMARY.md"), "# S04 Failure Summary\n");
1473
+
1474
+ openDatabase(join(base, ".gsd", "gsd.db"));
1475
+ insertMilestone({ id: "M001", title: "Milestone", status: "active" });
1476
+ insertSlice({ id: "S04", milestoneId: "M001", title: "Slice", status: "pending" });
1477
+ // No artifact row inserted — SUMMARY exists only on disk.
1478
+
1479
+ const result = await reconcileBeforeDispatch(base, {
1480
+ invalidateStateCache: () => {},
1481
+ deriveState: async () => makeState({ activeMilestone: { id: "M001", title: "Milestone" } }),
1482
+ });
1483
+
1484
+ assert.equal(result.ok, true);
1485
+ assert.ok(result.blockers.length > 0, "blocker must be produced for on-disk slice SUMMARY drift");
1486
+ const blocker = result.blockers.join("\n");
1487
+ assert.match(
1488
+ blocker,
1489
+ /has SUMMARY on disk while DB status is/,
1490
+ "blocker phrase must match the filter in auto.ts reconcileBeforeDispatch wrapper",
1491
+ );
1492
+ });
1493
+
1355
1494
  test("ADR-017: completed milestone dispatch history blocks accidental re-planning", async (t) => {
1356
1495
  const base = mkdtempSync(join(tmpdir(), "gsd-completed-reopened-drift-"));
1357
1496
  t.after(() => cleanup(base));
@@ -101,7 +101,7 @@ test("buildMinimalAutoGsdToolSet keeps unit-specific completion tools without al
101
101
  assert.ok(!result.includes("gsd_complete_slice"));
102
102
  });
103
103
 
104
- test("buildMinimalAutoGsdToolSet scopes run-uat to UAT-specific tools", () => {
104
+ test("buildMinimalAutoGsdToolSet scopes run-uat to UAT-specific and read-only tools", () => {
105
105
  const active = ["ask_user_questions", "bash", "read", "edit", "write", "gsd_summary_save"];
106
106
  const registered = [
107
107
  ...active,
@@ -123,11 +123,11 @@ test("buildMinimalAutoGsdToolSet scopes run-uat to UAT-specific tools", () => {
123
123
  assert.ok(result.includes("gsd_resume"));
124
124
  assert.ok(result.includes("gsd_milestone_status"));
125
125
  assert.ok(result.includes("gsd_journal_query"));
126
+ assert.ok(result.includes("read"));
126
127
  assert.ok(result.includes("browser_navigate"), "run-uat needs browser_navigate");
127
128
  assert.ok(result.includes("browser_click"), "run-uat needs browser_click");
128
129
  assert.ok(!result.includes("ToolSearch"));
129
130
  assert.ok(!result.includes("bash"));
130
- assert.ok(!result.includes("read"));
131
131
  assert.ok(!result.includes("edit"));
132
132
  assert.ok(!result.includes("write"));
133
133
  assert.ok(!result.includes("gsd_exec"));
@@ -230,9 +230,9 @@ test("buildMinimalAutoGsdToolSet preserves compatible browser add-ons for run-ua
230
230
  assert.ok(result.includes("gsd_uat_exec"));
231
231
  assert.ok(result.includes("gsd_uat_result_save"));
232
232
  assert.ok(result.includes("subagent"));
233
+ assert.ok(result.includes("read"));
233
234
  assert.ok(!result.includes("ToolSearch"));
234
235
  assert.ok(!result.includes("bash"));
235
- assert.ok(!result.includes("read"));
236
236
  assert.ok(!result.includes("edit"));
237
237
  assert.ok(!result.includes("write"));
238
238
  assert.ok(!result.includes("gsd_exec"));
@@ -281,12 +281,12 @@ test("buildMinimalAutoGsdToolSet honors provider-compatible registered tools for
281
281
 
282
282
  assert.ok(result.includes("gsd_uat_exec"));
283
283
  assert.ok(result.includes("gsd_uat_result_save"));
284
+ assert.ok(result.includes("read"));
284
285
  assert.ok(result.includes("browser_navigate"));
285
286
  assert.ok(result.includes("browser_click"));
286
287
  assert.ok(!result.includes("browser_screenshot"), "provider-filtered screenshot tool must stay filtered");
287
288
  assert.ok(!result.includes("ToolSearch"));
288
289
  assert.ok(!result.includes("bash"));
289
- assert.ok(!result.includes("read"));
290
290
  assert.ok(!result.includes("gsd_exec"));
291
291
  assert.ok(!result.includes("gsd_summary_save"));
292
292
  assert.ok(!result.includes("gsd_save_gate_result"));
@@ -102,6 +102,25 @@ describe("#2883: isToolInvocationError classification", () => {
102
102
  );
103
103
  });
104
104
 
105
+ test("detects MCP -32602 Input validation error (wire format)", () => {
106
+ assert.equal(
107
+ isToolInvocationError("MCP error -32602: Input validation error: Invalid arguments for tool gsd_slice_complete [path: verification, expected string, invalid_type]"),
108
+ true,
109
+ );
110
+ });
111
+
112
+ test("detects standalone 'Input validation error' prefix", () => {
113
+ assert.equal(isToolInvocationError("Input validation error: expected string"), true);
114
+ });
115
+
116
+ test("detects 'Invalid arguments for tool' prefix", () => {
117
+ assert.equal(isToolInvocationError("Invalid arguments for tool gsd_slice_complete"), true);
118
+ });
119
+
120
+ test("detects 'No such tool available' error", () => {
121
+ assert.equal(isToolInvocationError("No such tool available: mcp__gsd-workflow__memory_query"), true);
122
+ });
123
+
105
124
  test("detects ESM export-link errors", () => {
106
125
  assert.equal(
107
126
  isToolInvocationError("The requested module '../paths.js' does not provide an export named 'gsdProjectionRoot'"),
@@ -101,13 +101,19 @@ test("gsd_slice_complete — enrichment arrays are optional", () => {
101
101
  "sliceTitle",
102
102
  "oneLiner",
103
103
  "narrative",
104
- "verification",
105
104
  "uatContent",
106
105
  ];
107
106
  for (const field of coreRequired) {
108
107
  assert.ok(required.has(field), `core field "${field}" must be required`);
109
108
  }
110
109
 
110
+ // verification is intentionally optional — models that omit it avoid -32602;
111
+ // the summary records verification as passed without detail in that case.
112
+ assert.ok(
113
+ !required.has("verification"),
114
+ "verification must be optional — omitting it avoids -32602; summary records verification as passed without detail",
115
+ );
116
+
111
117
  // Enrichment/metadata arrays MUST be optional
112
118
  const enrichmentFields = [
113
119
  "keyFiles",
@@ -11,11 +11,14 @@ const prompt = readFileSync(promptPath, "utf-8");
11
11
 
12
12
  test("validate-milestone reviewer C requires canonical verification class names", () => {
13
13
  assert.match(prompt, /\*\*Reviewer C[\s\S]*Verification Classes/i);
14
- assert.match(prompt, /exact class names [`']?Contract[`']?, [`']?Integration[`']?, [`']?Operational[`']?, and [`']?UAT[`']?/i);
14
+ assert.match(prompt, /must be exactly `Contract`, `Integration`, `Operational`, or `UAT`/i);
15
+ assert.match(prompt, /Preserve every planned non-empty class row/i);
16
+ assert.match(prompt, /first cell of each row must be exactly `Contract`, `Integration`, `Operational`, or `UAT`/i);
15
17
  assert.match(prompt, /If no verification classes were planned, say that explicitly/i);
16
18
  });
17
19
 
18
20
  test("validate-milestone prompt routes verification class analysis into verificationClasses", () => {
19
- assert.match(prompt, /pass it in `verificationClasses`/i);
20
- assert.match(prompt, /Extract the `Verification Classes` subsection from Reviewer C and pass it verbatim in `verificationClasses`/);
21
+ assert.match(prompt, /pass a complete canonical table in `verificationClasses`/i);
22
+ assert.match(prompt, /If Reviewer C omitted a planned class, reconstruct the missing row/i);
23
+ assert.match(prompt, /Do not call `gsd_validate_milestone` with a partial `verificationClasses` table/i);
21
24
  });
@@ -180,6 +180,35 @@ describe("handleValidateMilestone write ordering (#2725)", () => {
180
180
  assert.equal(row, undefined, "assessment row should not be written when verification classes are invalid");
181
181
  });
182
182
 
183
+ it("reports all missing planned verification class rows at once", async () => {
184
+ base = makeTmpBase();
185
+ const dbPath = join(base, ".gsd", "gsd.db");
186
+ openDatabase(dbPath);
187
+ insertMilestone({
188
+ id: "M001",
189
+ planning: {
190
+ verificationContract: "Contract command exits 0",
191
+ verificationOperational: "Process lifecycle proof",
192
+ verificationUat: "Browser-observable UAT proof",
193
+ },
194
+ });
195
+ insertSlice({ id: "S01", milestoneId: "M001" });
196
+
197
+ const result = await handleValidateMilestone(
198
+ { ...VALID_PARAMS, verificationClasses: "| Check | Result |\n| --- | --- |\n| Generic verification | PASS |" },
199
+ base,
200
+ );
201
+ assert.ok("error" in result, "expected validation to fail");
202
+ assert.match(result.error, /canonical rows "Contract", "Operational", "UAT"/);
203
+ assert.match(result.error, /planned contract, operational, uat verification/);
204
+
205
+ const adapter = _getAdapter()!;
206
+ const row = adapter.prepare(
207
+ `SELECT status FROM assessments WHERE milestone_id = 'M001' AND scope = 'milestone-validation'`,
208
+ ).get() as { status: string } | undefined;
209
+ assert.equal(row, undefined, "assessment row should not be written when verification classes are invalid");
210
+ });
211
+
183
212
  it("accepts verificationClasses when planned Operational class is present", async () => {
184
213
  base = makeTmpBase();
185
214
  const dbPath = join(base, ".gsd", "gsd.db");
@@ -406,6 +435,110 @@ describe("handleValidateMilestone write ordering (#2725)", () => {
406
435
  assert.equal(result.verdict, "pass");
407
436
  });
408
437
 
438
+ it("keeps pass when browser-like criteria are verified by runtime-executable UAT", async () => {
439
+ base = makeTmpBase();
440
+ const dbPath = join(base, ".gsd", "gsd.db");
441
+ openDatabase(dbPath);
442
+ insertMilestone({
443
+ id: "M001",
444
+ planning: {
445
+ successCriteria: [
446
+ "Clicking Mark All Complete sets all todos completed",
447
+ "Reload keeps completed state",
448
+ ],
449
+ verificationUat: "Run the Node.js DOM-state script against the static app source.",
450
+ },
451
+ });
452
+ insertSlice({
453
+ id: "S01",
454
+ milestoneId: "M001",
455
+ // Uses localhost so hasBrowserRequiredText returns true and the gate is
456
+ // actually triggered before the runtime evidence bypasses it.
457
+ demo: "Visit localhost:3000 to verify DOM state after clicking Mark All Complete.",
458
+ });
459
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
460
+ mkdirSync(sliceDir, { recursive: true });
461
+ writeFileSync(
462
+ join(sliceDir, "S01-ASSESSMENT.md"),
463
+ [
464
+ "---",
465
+ "sliceId: S01",
466
+ "uatType: runtime-executable",
467
+ "verdict: PASS",
468
+ "attempt: 1",
469
+ "---",
470
+ "# UAT Result - S01",
471
+ "",
472
+ "## Checks",
473
+ "",
474
+ "| Check | Mode | Result | Evidence | Notes |",
475
+ "|-------|------|--------|----------|-------|",
476
+ "| DOM-state script | runtime | PASS | gsd_uat_exec:.gsd/evidence/uat/M001/S01/dom-state.json | Runtime assertion verified completed state and reload persistence. |",
477
+ "",
478
+ ].join("\n"),
479
+ "utf-8",
480
+ );
481
+
482
+ const result = await handleValidateMilestone(
483
+ {
484
+ ...VALID_PARAMS,
485
+ verificationClasses:
486
+ `${VALID_PARAMS.verificationClasses}\n| UAT | Runtime executable UAT verified static-app behavior. |`,
487
+ },
488
+ base,
489
+ );
490
+
491
+ assert.ok(!("error" in result), `unexpected error: ${"error" in result ? result.error : ""}`);
492
+ assert.equal(result.verdict, "pass");
493
+ });
494
+
495
+ it("downgrades to needs-attention when only one of two browser-requiring slices has runtime evidence", async () => {
496
+ base = makeTmpBase();
497
+ const dbPath = join(base, ".gsd", "gsd.db");
498
+ openDatabase(dbPath);
499
+ insertMilestone({ id: "M001" });
500
+ insertSlice({
501
+ id: "S01",
502
+ milestoneId: "M001",
503
+ demo: "Visit localhost:3000 to verify DOM state.",
504
+ });
505
+ insertSlice({
506
+ id: "S02",
507
+ milestoneId: "M001",
508
+ demo: "Visit localhost:3000 to confirm persistence after reload.",
509
+ });
510
+ // S01 has runtime-executable evidence; S02 has none.
511
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01"), { recursive: true });
512
+ writeFileSync(
513
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-ASSESSMENT.md"),
514
+ [
515
+ "---",
516
+ "sliceId: S01",
517
+ "uatType: runtime-executable",
518
+ "verdict: PASS",
519
+ "---",
520
+ "| DOM check | runtime | PASS | gsd_uat_exec:.gsd/evidence/uat/M001/S01/dom.json | Verified. |",
521
+ "",
522
+ ].join("\n"),
523
+ "utf-8",
524
+ );
525
+
526
+ const result = await handleValidateMilestone(
527
+ {
528
+ ...VALID_PARAMS,
529
+ verificationClasses: `${VALID_PARAMS.verificationClasses}\n| UAT | S01 runtime verified; S02 still needs evidence. |`,
530
+ },
531
+ base,
532
+ );
533
+
534
+ assert.ok(!("error" in result), `unexpected error: ${"error" in result ? result.error : ""}`);
535
+ assert.equal(
536
+ result.verdict,
537
+ "needs-attention",
538
+ "S01 runtime evidence must not bypass the gate for S02 which has browser requirements but no evidence",
539
+ );
540
+ });
541
+
409
542
  it("ignores slice full_uat_md planning text for browser requirement detection", async () => {
410
543
  base = makeTmpBase();
411
544
  const dbPath = join(base, ".gsd", "gsd.db");
@@ -35,6 +35,20 @@ function makeTempDir(prefix: string): string {
35
35
  return dir;
36
36
  }
37
37
 
38
+ function withRtkDisabled<T>(callback: () => T): T {
39
+ const previous = process.env.GSD_RTK_DISABLED;
40
+ process.env.GSD_RTK_DISABLED = "1";
41
+ try {
42
+ return callback();
43
+ } finally {
44
+ if (previous === undefined) {
45
+ delete process.env.GSD_RTK_DISABLED;
46
+ } else {
47
+ process.env.GSD_RTK_DISABLED = previous;
48
+ }
49
+ }
50
+ }
51
+
38
52
  // ─── Discovery Tests ─────────────────────────────────────────────────────────
39
53
 
40
54
  describe("verification-gate: discovery", () => {
@@ -430,6 +444,38 @@ describe("verification-gate: execution", () => {
430
444
  assert.ok(result.checks[1].stderr.includes("err"));
431
445
  });
432
446
 
447
+ test("grep -c zero-match failure includes absence-check warning", () => {
448
+ writeFileSync(join(tmp, "sample.txt"), "present\n");
449
+
450
+ const result = withRtkDisabled(() => runVerificationGate({
451
+ cwd: tmp,
452
+ preferenceCommands: ["grep -c missing sample.txt"],
453
+ }));
454
+
455
+ assert.equal(result.passed, false);
456
+ assert.equal(result.checks.length, 1);
457
+ assert.equal(result.checks[0].exitCode, 1);
458
+ assert.equal(result.checks[0].stdout.trim(), "0");
459
+ assert.match(result.checks[0].stderr, /grep -c/);
460
+ assert.match(result.checks[0].stderr, /count=0/);
461
+ assert.match(result.checks[0].stderr, /! grep -q/);
462
+ });
463
+
464
+ test("grep -c matching count does not warn", () => {
465
+ writeFileSync(join(tmp, "sample.txt"), "present\n");
466
+
467
+ const result = withRtkDisabled(() => runVerificationGate({
468
+ cwd: tmp,
469
+ preferenceCommands: ["grep -c present sample.txt"],
470
+ }));
471
+
472
+ assert.equal(result.passed, true);
473
+ assert.equal(result.checks.length, 1);
474
+ assert.equal(result.checks[0].exitCode, 0);
475
+ assert.equal(result.checks[0].stdout.trim(), "1");
476
+ assert.equal(result.checks[0].stderr, "");
477
+ });
478
+
433
479
  test("no commands discovered → gate passes with 0 checks", () => {
434
480
  const result = runVerificationGate({
435
481
  cwd: tmp,
@@ -686,6 +732,11 @@ test("isLikelyCommand: bash negation with known command is accepted", () => {
686
732
  assert.equal(isLikelyCommand("! grep needle file.txt"), true);
687
733
  });
688
734
 
735
+ test("validateVerificationCommand accepts negated quiet absence checks", () => {
736
+ assert.equal(validateVerificationCommand("! grep -q needle file.txt").ok, true);
737
+ assert.equal(validateVerificationCommand("! rg -q needle file.txt").ok, true);
738
+ });
739
+
689
740
  test("validateVerificationCommand allows shell pipelines", () => {
690
741
  assert.deepEqual(validateVerificationCommand("python3 -m pytest tests/ -q --tb=short").ok, true);
691
742
  const result = validateVerificationCommand("python3 -m pytest tests/ -q --tb=short | tail -5");