@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
@@ -11,6 +11,7 @@ import { fileURLToPath } from 'node:url';
11
11
  import { extractUatType } from '../../files.ts';
12
12
  import { resolveSliceFile } from '../../paths.ts';
13
13
  import { buildRunUatPrompt, checkNeedsRunUat } from '../../auto-prompts.ts';
14
+ import { buildRunUatResultPresentation, RUN_UAT_TOOL_PRESENTATION_PLAN_ID } from '../../tool-presentation-plan.ts';
14
15
 
15
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
17
  const worktreePromptsDir = join(__dirname, '../..', 'prompts');
@@ -20,6 +21,8 @@ function loadPromptFromWorktree(name: string, vars: Record<string, string> = {})
20
21
  let content = readFileSync(path, 'utf-8');
21
22
  const effectiveVars = {
22
23
  skillActivation: 'If no installed skill clearly matches this unit, skip explicit skill activation and continue with the required workflow.',
24
+ canonicalPresentation: JSON.stringify(buildRunUatResultPresentation(), null, 2),
25
+ toolPresentationPlanId: RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
23
26
  ...vars,
24
27
  };
25
28
  for (const [key, value] of Object.entries(effectiveVars)) {
@@ -72,6 +75,38 @@ function makeBrowserObservableUatContent(mode = 'artifact-driven'): string {
72
75
  ].join('\n');
73
76
  }
74
77
 
78
+ function makeDeferredBrowserUatContent(): string {
79
+ return [
80
+ '# UAT File',
81
+ '',
82
+ '## UAT Type',
83
+ '',
84
+ '- UAT mode: artifact-driven',
85
+ '- Why this mode is sufficient: Node interaction tests exercise the real app.js render/event/localStorage loop through a DOM harness. Live browser, keyboard, responsive, and visual-polish UAT remain intentionally deferred to S02.',
86
+ '',
87
+ '## Smoke Test',
88
+ '',
89
+ 'Run `node --test tests/s01-static-interactions.test.js` and confirm all tests pass.',
90
+ '',
91
+ '## Test Cases',
92
+ '',
93
+ '1. Click the todo row edit control in the DOM harness.',
94
+ '2. Save changed text and reload/recreate the app from persisted localStorage.',
95
+ '3. Expected: the stored record shape remains unchanged.',
96
+ '',
97
+ '## Not Proven By This UAT',
98
+ '',
99
+ '- Final visual polish of edit controls.',
100
+ '- Keyboard usability through a real browser.',
101
+ '- Browser console and local network cleanliness.',
102
+ '',
103
+ '## Notes for Tester',
104
+ '',
105
+ 'S02 should capture browser evidence for the full loop rather than changing this persisted model.',
106
+ '',
107
+ ].join('\n');
108
+ }
109
+
75
110
  describe('run-uat', () => {
76
111
  test('(a) artifact-driven', () => {
77
112
  assert.deepStrictEqual(
@@ -232,8 +267,8 @@ test('(k) run-uat prompt template', () => {
232
267
  `prompt contains detected dynamic uatType value "${uatType}" after substitution`,
233
268
  );
234
269
  assert.ok(
235
- promptResult?.includes(`uatType: ${uatType}`) ?? false,
236
- `prompt contains dynamic uatType frontmatter value "${uatType}" after substitution`,
270
+ promptResult?.includes(`uatType: "${uatType}"`) ?? false,
271
+ `prompt contains dynamic uatType field "${uatType}" after substitution`,
237
272
  );
238
273
  assert.ok(
239
274
  !/\{\{[^}]+\}\}/.test(promptResult ?? ''),
@@ -249,7 +284,7 @@ test('(k) run-uat prompt template', () => {
249
284
  );
250
285
  });
251
286
 
252
- test('(k2) run-uat prompt references gsd_summary_save, not direct write', () => {
287
+ test('(k2) run-uat prompt references gsd_uat_result_save, not direct write', () => {
253
288
  const promptResult = loadPromptFromWorktree('run-uat', {
254
289
  workingDirectory: '/tmp/test-project',
255
290
  milestoneId: 'M001',
@@ -261,17 +296,25 @@ test('(k2) run-uat prompt references gsd_summary_save, not direct write', () =>
261
296
  });
262
297
 
263
298
  assert.ok(
264
- promptResult.includes('gsd_summary_save'),
265
- 'run-uat prompt should reference gsd_summary_save tool',
299
+ promptResult.includes('gsd_uat_result_save'),
300
+ 'run-uat prompt should reference gsd_uat_result_save tool',
301
+ );
302
+ assert.ok(
303
+ promptResult.includes('presentedTools') && promptResult.includes('blockedTools'),
304
+ 'run-uat prompt should specify the tool presentation contract',
266
305
  );
267
306
  assert.ok(
268
- promptResult.includes('artifact_type: "ASSESSMENT"'),
269
- 'run-uat prompt should specify ASSESSMENT artifact type',
307
+ !promptResult.includes('Call `gsd_summary_save`'),
308
+ 'run-uat prompt should not instruct direct summary-save UAT persistence',
270
309
  );
271
310
  assert.ok(
272
311
  !promptResult.includes('MUST write'),
273
312
  'run-uat prompt should not instruct direct file write in footer',
274
313
  );
314
+ assert.ok(
315
+ !promptResult.includes('Call `gsd_summary_save` with `artifact_type: "ASSESSMENT"`'),
316
+ 'run-uat prompt should not instruct the legacy summary-save UAT path',
317
+ );
275
318
  });
276
319
 
277
320
  test('(l) dispatch preconditions via resolveSliceFile', () => {
@@ -482,8 +525,8 @@ test('(n) stale replay guard', async () => {
482
525
  });
483
526
 
484
527
  test('(q) verdict in ASSESSMENT file skips UAT dispatch (file-based path)', async () => {
485
- // Regression test for #2644: run-uat prompt writes the verdict to
486
- // S{sid}-ASSESSMENT.md (via gsd_summary_save artifact_type:"ASSESSMENT"),
528
+ // Regression test for #2644: run-uat writes the verdict to
529
+ // S{sid}-ASSESSMENT.md through the structured UAT save path,
487
530
  // but checkNeedsRunUat only checked S{sid}-UAT.md — causing a stuck loop.
488
531
  const base = createFixtureBase();
489
532
  try {
@@ -679,11 +722,30 @@ test('(u) run-uat prompt promotes artifact-driven browser specs to browser-execu
679
722
  const prompt = await buildRunUatPrompt('M001', 'S01', uatRel, uatContent, base);
680
723
 
681
724
  assert.match(prompt, /\*\*Detected UAT mode:\*\*\s*`browser-executable`/);
682
- assert.match(prompt, /uatType: browser-executable/);
725
+ assert.match(prompt, /uatType: "browser-executable"/);
683
726
  assert.match(prompt, /use gsd-browser tools/i);
727
+ assert.match(prompt, /"browser_navigate"/);
728
+ assert.match(prompt, /"browser_assert"/);
684
729
  } finally {
685
730
  cleanup(base);
686
731
  }
687
732
  });
688
733
 
734
+ test('(v) run-uat prompt keeps deferred browser work artifact-driven', async () => {
735
+ const base = createFixtureBase();
736
+ try {
737
+ const uatRel = '.gsd/milestones/M001/slices/S01/S01-UAT.md';
738
+ const uatContent = makeDeferredBrowserUatContent();
739
+ writeSliceFile(base, 'M001', 'S01', 'UAT', uatContent);
740
+
741
+ const prompt = await buildRunUatPrompt('M001', 'S01', uatRel, uatContent, base);
742
+
743
+ assert.match(prompt, /\*\*Detected UAT mode:\*\*\s*`artifact-driven`/);
744
+ assert.match(prompt, /uatType: "artifact-driven"/);
745
+ assert.doesNotMatch(prompt, /uatType: "browser-executable"/);
746
+ assert.doesNotMatch(prompt, /"browser_navigate"/);
747
+ } finally {
748
+ cleanup(base);
749
+ }
750
+ });
689
751
  });
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
7
7
  import {
8
+ buildProjectBrowserMcpServerConfig,
8
9
  ensureClaudeCodeMcpJsonServerEnabled,
9
10
  ensureProjectWorkflowMcpConfig,
10
11
  GSD_BROWSER_MCP_SERVER_NAME,
@@ -160,6 +161,37 @@ test("ensureProjectWorkflowMcpConfig can disable the default browser MCP server"
160
161
  }
161
162
  });
162
163
 
164
+ test("buildProjectBrowserMcpServerConfig prefers newer gsd-browser on PATH", () => {
165
+ const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-browser-"));
166
+
167
+ try {
168
+ const config = buildProjectBrowserMcpServerConfig(projectRoot, {
169
+ GSD_BROWSER_PATH_VERSION: "99.0.0",
170
+ });
171
+
172
+ assert.equal(config?.command, "gsd-browser");
173
+ assert.equal(config?.args?.[0], "mcp");
174
+ } finally {
175
+ rmSync(projectRoot, { recursive: true, force: true });
176
+ }
177
+ });
178
+
179
+ test("buildProjectBrowserMcpServerConfig keeps bundled browser when PATH version is older", () => {
180
+ const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-browser-"));
181
+
182
+ try {
183
+ const config = buildProjectBrowserMcpServerConfig(projectRoot, {
184
+ GSD_BROWSER_PATH_VERSION: "0.0.1",
185
+ });
186
+
187
+ assert.equal(config?.command, process.execPath);
188
+ assert.match(config?.args?.[0] ?? "", /@opengsd[\/\\]gsd-browser[\/\\]bin[\/\\]gsd-browser/);
189
+ assert.equal(config?.args?.[1], "mcp");
190
+ } finally {
191
+ rmSync(projectRoot, { recursive: true, force: true });
192
+ }
193
+ });
194
+
163
195
  test("ensureProjectWorkflowMcpConfig is idempotent when config is already current", () => {
164
196
  const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-init-"));
165
197
  mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
@@ -341,6 +341,8 @@ describe("formatMcpInitResult", () => {
341
341
  assert.match(result, /created project mcp config/i);
342
342
  assert.match(result, /\/tmp\/project\/\.mcp\.json/);
343
343
  assert.match(result, /mcp-capable clients/i);
344
+ assert.match(result, /workflow and gsd-browser MCP servers/i);
345
+ assert.match(result, /Pi Providers use the managed gsd-browser engine/i);
344
346
  assert.doesNotMatch(result, /claude code/i);
345
347
  });
346
348
 
@@ -11,6 +11,16 @@ import {
11
11
  import { createMemoryRelation, listRelationsFor } from '../memory-relations.ts';
12
12
  import { saveEmbedding, getEmbeddingForMemory } from '../memory-embeddings.ts';
13
13
 
14
+ function markProcessedUnits(count = 21): void {
15
+ const now = Date.now();
16
+ for (let i = 0; i < count; i++) {
17
+ markUnitProcessed(`unit/${i}`, `file-${i}`);
18
+ _getAdapter()!
19
+ .prepare('UPDATE memory_processed_units SET processed_at = :ts WHERE unit_key = :key')
20
+ .run({ ':ts': new Date(now + i * 1000).toISOString(), ':key': `unit/${i}` });
21
+ }
22
+ }
23
+
14
24
  // ═══════════════════════════════════════════════════════════════════════════
15
25
  // enforceMemoryCap — cascade cleanup of embeddings and relations
16
26
  // ═══════════════════════════════════════════════════════════════════════════
@@ -71,14 +81,7 @@ test('memory-decay: returns decayed memory IDs', () => {
71
81
  openDatabase(':memory:');
72
82
 
73
83
  // Insert processed units — decayStaleMemories needs at least N rows.
74
- const now = Date.now();
75
- for (let i = 0; i < 21; i++) {
76
- markUnitProcessed(`unit/${i}`, `file-${i}`);
77
- // small spacing to create deterministic ordering
78
- const row = _getAdapter()!
79
- .prepare('UPDATE memory_processed_units SET processed_at = :ts WHERE unit_key = :key');
80
- row.run({ ':ts': new Date(now + i * 1000).toISOString(), ':key': `unit/${i}` });
81
- }
84
+ markProcessedUnits();
82
85
 
83
86
  // Create memory with updated_at in the distant past
84
87
  createMemory({ category: 'pattern', content: 'stale entry', confidence: 0.9 });
@@ -98,6 +101,34 @@ test('memory-decay: returns decayed memory IDs', () => {
98
101
  closeDatabase();
99
102
  });
100
103
 
104
+ test('memory-decay: skips stale decision-sourced memories', () => {
105
+ openDatabase(':memory:');
106
+ markProcessedUnits();
107
+
108
+ createMemory({ category: 'pattern', content: 'stale working note', confidence: 0.9 });
109
+ createMemory({
110
+ category: 'architecture',
111
+ content: 'decision-backed architecture memory',
112
+ confidence: 0.85,
113
+ structuredFields: { sourceDecisionId: 'D001' },
114
+ });
115
+ _getAdapter()!
116
+ .prepare("UPDATE memories SET updated_at = '2000-01-01T00:00:00Z' WHERE id IN ('MEM001', 'MEM002')")
117
+ .run({});
118
+
119
+ const decayed = decayStaleMemories(20);
120
+ assert.ok(decayed.includes('MEM001'));
121
+ assert.ok(!decayed.includes('MEM002'));
122
+
123
+ const rows = _getAdapter()!
124
+ .prepare("SELECT id, confidence FROM memories WHERE id IN ('MEM001', 'MEM002') ORDER BY id")
125
+ .all() as Array<{ id: string; confidence: number }>;
126
+ assert.ok(rows.find((row) => row.id === 'MEM001')!.confidence < 0.9);
127
+ assert.equal(rows.find((row) => row.id === 'MEM002')!.confidence, 0.85);
128
+
129
+ closeDatabase();
130
+ });
131
+
101
132
  test('memory-decay: returns empty when there are fewer processed units than the threshold', () => {
102
133
  openDatabase(':memory:');
103
134
  createMemory({ category: 'pattern', content: 'fresh' });
@@ -0,0 +1,63 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Regression tests for DB-backed closeout consistency before merge.
3
+
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { existsSync, mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { tmpdir } from "node:os";
9
+ import { execFileSync } from "node:child_process";
10
+
11
+ import { mergeMilestoneToMain } from "../auto-worktree.ts";
12
+ import { closeDatabase, insertMilestone, openDatabase } from "../gsd-db.ts";
13
+
14
+ function git(args: string[], cwd: string): string {
15
+ return execFileSync("git", args, {
16
+ cwd,
17
+ stdio: ["ignore", "pipe", "pipe"],
18
+ encoding: "utf-8",
19
+ }).trim();
20
+ }
21
+
22
+ function createRepo(): string {
23
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "merge-closeout-gate-")));
24
+ git(["init"], dir);
25
+ git(["config", "user.email", "test@test.com"], dir);
26
+ git(["config", "user.name", "Test"], dir);
27
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
28
+ writeFileSync(join(dir, "README.md"), "# test\n");
29
+ git(["add", "."], dir);
30
+ git(["commit", "-m", "init"], dir);
31
+ git(["branch", "-M", "main"], dir);
32
+
33
+ git(["checkout", "-b", "milestone/M001"], dir);
34
+ writeFileSync(join(dir, "feature.ts"), "export const feature = true;\n");
35
+ git(["add", "feature.ts"], dir);
36
+ git(["commit", "-m", "feat: milestone work"], dir);
37
+ git(["checkout", "main"], dir);
38
+ return dir;
39
+ }
40
+
41
+ test("mergeMilestoneToMain blocks when project DB closeout is still open", () => {
42
+ const savedCwd = process.cwd();
43
+ const repo = createRepo();
44
+ try {
45
+ assert.equal(openDatabase(join(repo, ".gsd", "gsd.db")), true);
46
+ insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
47
+
48
+ const mainHeadBefore = git(["rev-parse", "main"], repo);
49
+ process.chdir(repo);
50
+
51
+ assert.throws(
52
+ () => mergeMilestoneToMain(repo, "M001", "# M001\n- [x] **S01: Done**\n"),
53
+ /closeout-consistency-blocked/,
54
+ );
55
+
56
+ assert.equal(git(["rev-parse", "main"], repo), mainHeadBefore);
57
+ assert.equal(git(["branch", "--show-current"], repo), "main");
58
+ } finally {
59
+ closeDatabase();
60
+ process.chdir(savedCwd);
61
+ if (existsSync(repo)) rmSync(repo, { recursive: true, force: true });
62
+ }
63
+ });
@@ -15,7 +15,7 @@ import { delimiter, join } from "node:path";
15
15
  import { execFileSync } from "node:child_process";
16
16
 
17
17
  import { mergeMilestoneToMain } from "../auto-worktree.ts";
18
- import { closeDatabase, openDatabase } from "../gsd-db.ts";
18
+ import { closeDatabase, insertAssessment, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
19
19
  import { GIT_NO_PROMPT_ENV } from "../git-constants.js";
20
20
  import { _clearGsdRootCache } from "../paths.ts";
21
21
  import { _resetServiceCache } from "../worktree.ts";
@@ -145,6 +145,15 @@ test("mergeMilestoneToMain keeps the Windows DB cycle closed through squash merg
145
145
 
146
146
  withPlatform("win32", () => {
147
147
  assert.equal(openDatabase(join(repo, ".gsd", "gsd.db")), true);
148
+ insertMilestone({ id: "M001", title: "Windows DB cycle", status: "complete" });
149
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Done Slice", status: "complete" });
150
+ insertAssessment({
151
+ path: "milestones/M001/M001-VALIDATION.md",
152
+ milestoneId: "M001",
153
+ status: "pass",
154
+ scope: "milestone-validation",
155
+ fullContent: "verdict: pass",
156
+ });
148
157
  assert.equal(existsSync(join(repo, ".gsd", "gsd.db-shm")), true);
149
158
 
150
159
  process.env.PATH = `${bin}${delimiter}${originalPath}`;
@@ -6,7 +6,7 @@ import assert from "node:assert/strict";
6
6
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import { tmpdir } from "node:os";
9
- import { openDatabase, insertMilestone, closeDatabase } from "../gsd-db.js";
9
+ import { openDatabase, insertAssessment, insertMilestone, insertSlice, closeDatabase } from "../gsd-db.js";
10
10
  import {
11
11
  isMilestoneCloseoutSettled,
12
12
  evaluateCompleteMilestoneDispatch,
@@ -39,6 +39,14 @@ test("isMilestoneCloseoutSettled requires DB closed and summary artifact", async
39
39
  mkdirSync(join(base, ".gsd"), { recursive: true });
40
40
  openDatabase(join(base, ".gsd", "gsd.db"));
41
41
  insertMilestone({ id: "M001", title: "Done", status: "complete" });
42
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Done Slice", status: "complete" });
43
+ insertAssessment({
44
+ path: "milestones/M001/M001-VALIDATION.md",
45
+ milestoneId: "M001",
46
+ status: "pass",
47
+ scope: "milestone-validation",
48
+ fullContent: "verdict: pass",
49
+ });
42
50
  const milestoneDir = join(base, ".gsd", "milestones", "M001");
43
51
  mkdirSync(milestoneDir, { recursive: true });
44
52
  writeFileSync(join(milestoneDir, "M001-SUMMARY.md"), "# Milestone Summary\n");
@@ -13,10 +13,10 @@ test("dispatchNewMilestoneDiscuss uses discuss.md only on greenfield projects",
13
13
 
14
14
  assert.match(fnBody, /findMilestoneIds\(basePath\)\.length === 0/);
15
15
  assert.match(fnBody, /prepareAndBuildDiscussPrompt/);
16
- assert.match(fnBody, /loadPrompt\("guided-discuss-milestone"/);
16
+ assert.match(fnBody, /buildDiscussMilestonePrompt/);
17
17
  assert.match(
18
18
  fnBody,
19
- /if \(isGreenfield\)[\s\S]*prepareAndBuildDiscussPrompt[\s\S]*loadPrompt\("guided-discuss-milestone"/,
19
+ /if \(isGreenfield\)[\s\S]*prepareAndBuildDiscussPrompt[\s\S]*buildDiscussMilestonePrompt/,
20
20
  "greenfield branch must precede guided-discuss-milestone branch",
21
21
  );
22
22
  });
@@ -24,7 +24,7 @@ test("dispatchNewMilestoneDiscuss uses discuss.md only on greenfield projects",
24
24
  test("dispatchNewMilestoneDiscuss uses milestone-specific preparation guidance", () => {
25
25
  const source = readFileSync(join(__dirname, "..", "guided-flow.ts"), "utf-8");
26
26
  const fnBody = extractSourceRegion(source, "async function dispatchNewMilestoneDiscuss(");
27
- assert.match(fnBody, /buildDiscussPreparationContext\(ctx, basePath, "milestone"\)/);
27
+ assert.match(fnBody, /buildDiscussPreparationContext\(ctx, basePath, "milestone", true\)/);
28
28
  });
29
29
 
30
30
  test("launchNextMilestoneDiscuss routes through dispatchNewMilestoneDiscuss for normal path", () => {
@@ -83,6 +83,15 @@ test("plan-slice prompt: compact planning gates survive template substitution",
83
83
  assert.ok(!result.includes("{{"));
84
84
  });
85
85
 
86
+ test("plan-slice prompt: absence checks use negated quiet searches", () => {
87
+ const result = loadPrompt("plan-slice", { ...BASE_VARS, commitInstruction: "Do not commit." });
88
+ assert.ok(result.includes("For absence checks"));
89
+ assert.ok(result.includes("`! grep -q 'pattern' file`"));
90
+ assert.ok(result.includes("`! rg -q 'pattern' file`"));
91
+ assert.ok(result.includes("do not use `grep -c` or `rg -c`"));
92
+ assert.ok(result.includes("count commands exit 1 when they find zero matches"));
93
+ });
94
+
86
95
  test("plan-slice prompt: footer references gsd_plan_slice tool, not direct write", () => {
87
96
  const result = loadPrompt("plan-slice", { ...BASE_VARS, commitInstruction: "Do not commit." });
88
97
  assert.ok(
@@ -11,6 +11,7 @@ import {
11
11
  resetHookState,
12
12
  isRetryPending,
13
13
  consumeRetryTrigger,
14
+ consumeGateBlock,
14
15
  resolveHookArtifactPath,
15
16
  runPreDispatchHooks,
16
17
  persistHookState,
@@ -20,6 +21,7 @@ import {
20
21
  formatHookStatus,
21
22
  triggerHookManually,
22
23
  } from "../post-unit-hooks.ts";
24
+ import { invalidateAllCaches } from "../cache.ts";
23
25
 
24
26
  // ─── Fixture Helpers ───────────────────────────────────────────────────────
25
27
 
@@ -29,6 +31,11 @@ function createFixtureBase(): string {
29
31
  return base;
30
32
  }
31
33
 
34
+ function writeHookPreferences(base: string, hookYaml: string): void {
35
+ writeFileSync(join(base, ".gsd", "PREFERENCES.md"), `---\npost_unit_hooks:\n${hookYaml}\n---\n`, "utf-8");
36
+ invalidateAllCaches();
37
+ }
38
+
32
39
  // ═══════════════════════════════════════════════════════════════════════════
33
40
  // Phase 1: Post-Unit Hook Tests
34
41
  // ═══════════════════════════════════════════════════════════════════════════
@@ -104,6 +111,156 @@ test('consumeRetryTrigger clears state', () => {
104
111
  assert.ok(!isRetryPending(), "no retry initially");
105
112
  });
106
113
 
114
+ test('Advisory hook keeps artifact idempotency without verdict frontmatter', () => {
115
+ resetHookState();
116
+ const base = createFixtureBase();
117
+ try {
118
+ writeHookPreferences(base, ` - name: docs-hint
119
+ after:
120
+ - execute-task
121
+ prompt: Review docs
122
+ artifact: DOCS-HINT.md
123
+ `);
124
+ writeFileSync(resolveHookArtifactPath(base, "M001/S01/T01", "DOCS-HINT.md"), "plain advisory note", "utf-8");
125
+
126
+ const result = checkPostUnitHooks("execute-task", "M001/S01/T01", base);
127
+ assert.deepStrictEqual(result, null, "existing advisory artifact remains idempotent");
128
+ assert.deepStrictEqual(consumeGateBlock(), null, "advisory hook does not create gate block");
129
+ } finally {
130
+ resetHookState();
131
+ invalidateAllCaches();
132
+ rmSync(base, { recursive: true, force: true });
133
+ }
134
+ });
135
+
136
+ test('Blocking hook skips only after passing frontmatter verdict', () => {
137
+ resetHookState();
138
+ const base = createFixtureBase();
139
+ try {
140
+ writeHookPreferences(base, ` - name: security-review
141
+ after:
142
+ - execute-task
143
+ prompt: Review security
144
+ artifact: SECURITY-REVIEW.md
145
+ criticality: blocking
146
+ `);
147
+ writeFileSync(
148
+ resolveHookArtifactPath(base, "M001/S01/T01", "SECURITY-REVIEW.md"),
149
+ "---\nverdict: pass\n---\n\nNo blocking findings.\n",
150
+ "utf-8",
151
+ );
152
+
153
+ const result = checkPostUnitHooks("execute-task", "M001/S01/T01", base);
154
+ assert.deepStrictEqual(result, null, "passing gate artifact is idempotent");
155
+ assert.deepStrictEqual(consumeGateBlock(), null, "passing gate does not block");
156
+ } finally {
157
+ resetHookState();
158
+ invalidateAllCaches();
159
+ rmSync(base, { recursive: true, force: true });
160
+ }
161
+ });
162
+
163
+ test('Blocking hook reruns invalid artifact once then blocks at cycle budget', () => {
164
+ resetHookState();
165
+ const base = createFixtureBase();
166
+ try {
167
+ writeHookPreferences(base, ` - name: security-review
168
+ after:
169
+ - execute-task
170
+ prompt: Review security
171
+ artifact: SECURITY-REVIEW.md
172
+ criticality: blocking
173
+ `);
174
+ writeFileSync(resolveHookArtifactPath(base, "M001/S01/T01", "SECURITY-REVIEW.md"), "partial output", "utf-8");
175
+
176
+ const dispatch = checkPostUnitHooks("execute-task", "M001/S01/T01", base);
177
+ assert.ok(dispatch, "invalid gate artifact dispatches the blocking hook");
178
+ assert.equal(dispatch.unitType, "hook/security-review");
179
+
180
+ const afterHook = checkPostUnitHooks("hook/security-review", "M001/S01/T01", base);
181
+ assert.deepStrictEqual(afterHook, null, "no further hook dispatch after max_cycles=1");
182
+ const block = consumeGateBlock();
183
+ assert.ok(block, "gate block is recorded");
184
+ assert.equal(block.hookName, "security-review");
185
+ assert.match(block.reason, /missing frontmatter verdict/);
186
+ } finally {
187
+ resetHookState();
188
+ invalidateAllCaches();
189
+ rmSync(base, { recursive: true, force: true });
190
+ }
191
+ });
192
+
193
+ test('Blocking hook restored from disk does not trust artifact without clean hook completion', () => {
194
+ resetHookState();
195
+ const base = createFixtureBase();
196
+ try {
197
+ writeHookPreferences(base, ` - name: security-review
198
+ after:
199
+ - execute-task
200
+ prompt: Review security
201
+ artifact: SECURITY-REVIEW.md
202
+ criticality: blocking
203
+ max_cycles: 2
204
+ `);
205
+ const firstDispatch = checkPostUnitHooks("execute-task", "M001/S01/T01", base);
206
+ assert.ok(firstDispatch, "gate dispatches first cycle");
207
+ persistHookState(base);
208
+
209
+ writeFileSync(
210
+ resolveHookArtifactPath(base, "M001/S01/T01", "SECURITY-REVIEW.md"),
211
+ "---\noutcome:\n verdict: pass\n---\n",
212
+ "utf-8",
213
+ );
214
+
215
+ resetHookState();
216
+ restoreHookState(base);
217
+
218
+ const resumed = checkPostUnitHooks("execute-task", "M001/S01/T01", base);
219
+ assert.ok(resumed, "persisted active gate reruns when clean hook completion was not observed");
220
+ assert.equal(resumed.unitType, "hook/security-review");
221
+ } finally {
222
+ resetHookState();
223
+ invalidateAllCaches();
224
+ rmSync(base, { recursive: true, force: true });
225
+ }
226
+ });
227
+
228
+ test('Blocking hook needs-rework verdict requests trigger unit retry', () => {
229
+ resetHookState();
230
+ const base = createFixtureBase();
231
+ try {
232
+ writeHookPreferences(base, ` - name: review-arbiter
233
+ after:
234
+ - execute-task
235
+ prompt: Review task
236
+ artifact: REVIEW-DEBATE.md
237
+ criticality: blocking
238
+ max_cycles: 2
239
+ on_block:
240
+ action: retry-unit
241
+ `);
242
+ const dispatch = checkPostUnitHooks("execute-task", "M001/S01/T01", base);
243
+ assert.ok(dispatch, "gate dispatches");
244
+ writeFileSync(
245
+ resolveHookArtifactPath(base, "M001/S01/T01", "REVIEW-DEBATE.md"),
246
+ "---\nverdict: needs-rework\n---\n\nRework required.\n",
247
+ "utf-8",
248
+ );
249
+
250
+ const afterHook = checkPostUnitHooks("hook/review-arbiter", "M001/S01/T01", base);
251
+ assert.deepStrictEqual(afterHook, null, "needs-rework routes via retry signal");
252
+ assert.ok(isRetryPending(), "retry is pending");
253
+ assert.deepStrictEqual(consumeRetryTrigger(), {
254
+ unitType: "execute-task",
255
+ unitId: "M001/S01/T01",
256
+ });
257
+ } finally {
258
+ resetHookState();
259
+ invalidateAllCaches();
260
+ rmSync(base, { recursive: true, force: true });
261
+ }
262
+ });
263
+
107
264
  // ─── Variable substitution in prompts ──────────────────────────────────────
108
265
  test('Variable substitution', () => {
109
266
  const base = "/project";