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

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 +17 -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 +7 -7
  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 +7 -7
  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 -6
  176. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  177. package/packages/pi-ai/dist/models.generated.js +78 -10
  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 +69 -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 +61 -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 +351 -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 +38 -8
  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 → 9y3LeeR2uGr2yRj9RjY3D}/_buildManifest.js +0 -0
  329. /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → 9y3LeeR2uGr2yRj9RjY3D}/_ssgManifest.js +0 -0
@@ -10,6 +10,9 @@ import { resolvePostUnitHooks, resolvePreDispatchHooks } from "./preferences.js"
10
10
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { parseUnitId } from "./unit-id.js";
13
+ import { queryJournal } from "./journal.js";
14
+ import { readUnitRuntimeRecord } from "./unit-runtime.js";
15
+ import { extractFrontmatterVerdict } from "./verdict-parser.js";
13
16
  // ─── Artifact Path Resolution ──────────────────────────────────────────────
14
17
  export function resolveHookArtifactPath(basePath, unitId, artifactName) {
15
18
  const { milestone, slice, task } = parseUnitId(unitId);
@@ -38,6 +41,28 @@ export function convertDispatchRules(rules) {
38
41
  }
39
42
  // ─── RuleRegistry ─────────────────────────────────────────────────────────
40
43
  const HOOK_STATE_FILE = "hook-state.json";
44
+ const FAILED_HOOK_RUNTIME_PHASES = new Set([
45
+ "timeout",
46
+ "finalize-timeout",
47
+ "crashed",
48
+ "paused",
49
+ ]);
50
+ const HOOK_OUTCOME_VERDICTS = new Set([
51
+ "pass",
52
+ "advisory",
53
+ "needs-rework",
54
+ "needs-remediation",
55
+ "needs-attention",
56
+ ]);
57
+ function isBlockingHook(config) {
58
+ return config?.criticality === "blocking";
59
+ }
60
+ function hookMaxCycles(config) {
61
+ return config.max_cycles ?? 1;
62
+ }
63
+ function hookCycleKey(config, trigger) {
64
+ return `${config.name}/${trigger.triggerUnitType}/${trigger.triggerUnitId}`;
65
+ }
41
66
  export class RuleRegistry {
42
67
  /** Static dispatch rules provided at construction time. */
43
68
  dispatchRules;
@@ -47,6 +72,8 @@ export class RuleRegistry {
47
72
  cycleCounts = new Map();
48
73
  retryPending = false;
49
74
  retryTrigger = null;
75
+ hookFailure = null;
76
+ gateBlockPending = null;
50
77
  constructor(dispatchRules) {
51
78
  this.dispatchRules = dispatchRules;
52
79
  }
@@ -71,6 +98,7 @@ export class RuleRegistry {
71
98
  artifact: hook.artifact,
72
99
  retry_on: hook.retry_on,
73
100
  max_cycles: hook.max_cycles,
101
+ criticality: hook.criticality,
74
102
  },
75
103
  });
76
104
  }
@@ -117,7 +145,9 @@ export class RuleRegistry {
117
145
  evaluatePostUnit(completedUnitType, completedUnitId, basePath) {
118
146
  // If we just completed a hook unit, handle its result
119
147
  if (this.activeHook) {
120
- return this._handleHookCompletion(basePath);
148
+ const observedCleanExecution = completedUnitType === `hook/${this.activeHook.hookName}` &&
149
+ completedUnitId === this.activeHook.triggerUnitId;
150
+ return this._handleHookCompletion(basePath, observedCleanExecution);
121
151
  }
122
152
  // Don't trigger hooks for other hook units (prevent hook-on-hook chains)
123
153
  // Don't trigger hooks for triage units or quick-task units
@@ -141,75 +171,371 @@ export class RuleRegistry {
141
171
  _dequeueNextHook(basePath) {
142
172
  while (this.hookQueue.length > 0) {
143
173
  const entry = this.hookQueue.shift();
144
- const { config, triggerUnitType, triggerUnitId } = entry;
145
- // Check idempotency if artifact already exists, skip
146
- if (config.artifact) {
174
+ const { config, triggerUnitType, triggerUnitId, forceRun } = entry;
175
+ // Advisory hooks preserve existing idempotency: any configured artifact
176
+ // means the hook already ran. Blocking gates must verify outcome first.
177
+ if (config.artifact && !forceRun) {
147
178
  const artifactPath = resolveHookArtifactPath(basePath, triggerUnitId, config.artifact);
148
- if (existsSync(artifactPath))
149
- continue;
179
+ if (existsSync(artifactPath)) {
180
+ const completion = this._assessConfiguredHookCompletion(basePath, config.name, triggerUnitId);
181
+ if (completion.outcome === "failed") {
182
+ return this._handleFailedHookCompletion(basePath, {
183
+ hookName: config.name,
184
+ triggerUnitType,
185
+ triggerUnitId,
186
+ cycle: this.cycleCounts.get(hookCycleKey(config, { triggerUnitType, triggerUnitId })) ?? 0,
187
+ pendingRetry: false,
188
+ }, config, completion.reason);
189
+ }
190
+ if (!isBlockingHook(config))
191
+ continue;
192
+ const decision = this._handleExistingBlockingArtifact(config, { triggerUnitType, triggerUnitId }, basePath);
193
+ if (decision === "skip")
194
+ continue;
195
+ return decision;
196
+ }
197
+ }
198
+ const dispatch = this._startHook(config, triggerUnitType, triggerUnitId);
199
+ if (dispatch)
200
+ return dispatch;
201
+ if (isBlockingHook(config)) {
202
+ const cycleKey = hookCycleKey(config, { triggerUnitType, triggerUnitId });
203
+ const maxCycles = hookMaxCycles(config);
204
+ const currentCycle = this.cycleCounts.get(cycleKey) ?? 0;
205
+ if (currentCycle >= maxCycles) {
206
+ this._setGateBlock(config, { triggerUnitType, triggerUnitId }, {
207
+ action: "pause",
208
+ reason: `gate cycle budget exhausted before ${config.name} produced a passing outcome`,
209
+ cycle: currentCycle,
210
+ maxCycles,
211
+ });
212
+ return null;
213
+ }
150
214
  }
151
- // Check cycle limit
152
- const cycleKey = `${config.name}/${triggerUnitType}/${triggerUnitId}`;
153
- const currentCycle = (this.cycleCounts.get(cycleKey) ?? 0) + 1;
154
- const maxCycles = config.max_cycles ?? 1;
155
- if (currentCycle > maxCycles)
156
- continue;
157
- this.cycleCounts.set(cycleKey, currentCycle);
158
- this.activeHook = {
159
- hookName: config.name,
160
- triggerUnitType,
161
- triggerUnitId,
162
- cycle: currentCycle,
163
- pendingRetry: false,
164
- };
165
- // Build prompt with variable substitution
166
- const { milestone: mid, slice: sid, task: tid } = parseUnitId(triggerUnitId);
167
- let prompt = config.prompt
168
- .replace(/\{milestoneId\}/g, mid ?? "")
169
- .replace(/\{sliceId\}/g, sid ?? "")
170
- .replace(/\{taskId\}/g, tid ?? "");
171
- // Inject browser safety instruction
172
- prompt += "\n\n**Browser tool safety:** Do NOT use `browser_wait_for` with `condition: \"network_idle\"` — it hangs indefinitely when dev servers keep persistent connections (Vite HMR, WebSocket). Use `selector_visible`, `text_visible`, or `delay` instead.";
173
- return {
174
- hookName: config.name,
175
- prompt,
176
- model: config.model,
177
- unitType: `hook/${config.name}`,
178
- unitId: triggerUnitId,
179
- };
180
215
  }
181
216
  // No more hooks — clear active state
182
217
  this.activeHook = null;
183
218
  return null;
184
219
  }
185
- _handleHookCompletion(basePath) {
220
+ _handleHookCompletion(basePath, observedCleanExecution) {
186
221
  const hook = this.activeHook;
187
222
  const hooks = resolvePostUnitHooks(basePath);
188
223
  const config = hooks.find(h => h.name === hook.hookName);
224
+ if (!config) {
225
+ this.activeHook = null;
226
+ return this._dequeueNextHook(basePath);
227
+ }
228
+ const completion = this._assessHookCompletion(basePath, hook);
229
+ if (completion.outcome === "failed") {
230
+ return this._handleFailedHookCompletion(basePath, hook, config, completion.reason);
231
+ }
189
232
  // Check if retry was requested via retry_on artifact
190
- if (config?.retry_on) {
233
+ if (config.retry_on) {
191
234
  const retryArtifactPath = resolveHookArtifactPath(basePath, hook.triggerUnitId, config.retry_on);
192
235
  if (existsSync(retryArtifactPath)) {
193
- const cycleKey = `${config.name}/${hook.triggerUnitType}/${hook.triggerUnitId}`;
194
- const currentCycle = this.cycleCounts.get(cycleKey) ?? 1;
195
- const maxCycles = config.max_cycles ?? 1;
196
- if (currentCycle < maxCycles) {
236
+ if (this._requestTriggerRetry(config, hook, config.retry_on)) {
237
+ return null;
238
+ }
239
+ if (isBlockingHook(config)) {
240
+ this._setGateBlock(config, hook, {
241
+ action: "pause",
242
+ reason: `gate cycle budget exhausted after ${config.retry_on} requested rework`,
243
+ retryArtifact: config.retry_on,
244
+ });
197
245
  this.activeHook = null;
198
246
  this.hookQueue = [];
199
- this.retryPending = true;
200
- this.retryTrigger = {
201
- unitType: hook.triggerUnitType,
202
- unitId: hook.triggerUnitId,
203
- retryArtifact: config.retry_on,
204
- };
205
247
  return null;
206
248
  }
207
249
  }
208
250
  }
251
+ if (isBlockingHook(config)) {
252
+ return this._handleBlockingGateCompletion(config, hook, basePath, observedCleanExecution);
253
+ }
209
254
  // Hook completed normally — try next hook in queue
210
255
  this.activeHook = null;
211
256
  return this._dequeueNextHook(basePath);
212
257
  }
258
+ _startHook(config, triggerUnitType, triggerUnitId) {
259
+ const cycleKey = `${config.name}/${triggerUnitType}/${triggerUnitId}`;
260
+ const currentCycle = (this.cycleCounts.get(cycleKey) ?? 0) + 1;
261
+ const maxCycles = config.max_cycles ?? 1;
262
+ if (currentCycle > maxCycles)
263
+ return null;
264
+ this.cycleCounts.set(cycleKey, currentCycle);
265
+ this.activeHook = {
266
+ hookName: config.name,
267
+ triggerUnitType,
268
+ triggerUnitId,
269
+ cycle: currentCycle,
270
+ pendingRetry: false,
271
+ };
272
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(triggerUnitId);
273
+ let prompt = config.prompt
274
+ .replace(/\{milestoneId\}/g, mid ?? "")
275
+ .replace(/\{sliceId\}/g, sid ?? "")
276
+ .replace(/\{taskId\}/g, tid ?? "");
277
+ prompt += "\n\n**Browser tool safety:** Do NOT use `browser_wait_for` with `condition: \"network_idle\"` — it hangs indefinitely when dev servers keep persistent connections (Vite HMR, WebSocket). Use `selector_visible`, `text_visible`, or `delay` instead.";
278
+ return {
279
+ hookName: config.name,
280
+ prompt,
281
+ model: config.model,
282
+ unitType: `hook/${config.name}`,
283
+ unitId: triggerUnitId,
284
+ };
285
+ }
286
+ _assessHookCompletion(basePath, hook) {
287
+ return this._assessConfiguredHookCompletion(basePath, hook.hookName, hook.triggerUnitId);
288
+ }
289
+ _assessConfiguredHookCompletion(basePath, hookName, unitId) {
290
+ const unitType = `hook/${hookName}`;
291
+ const latestUnitEnd = this._latestHookUnitEnd(basePath, unitType, unitId);
292
+ if (latestUnitEnd) {
293
+ const data = latestUnitEnd.data ?? {};
294
+ const status = data.status;
295
+ const artifactVerified = data.artifactVerified;
296
+ if (status === "completed" && artifactVerified !== false) {
297
+ return { outcome: "success" };
298
+ }
299
+ return {
300
+ outcome: "failed",
301
+ reason: this._formatHookFailureReason(status, artifactVerified, data.errorContext),
302
+ };
303
+ }
304
+ const runtime = readUnitRuntimeRecord(basePath, unitType, unitId);
305
+ if (runtime && FAILED_HOOK_RUNTIME_PHASES.has(runtime.phase)) {
306
+ return { outcome: "failed", reason: `runtime phase ${runtime.phase}` };
307
+ }
308
+ return { outcome: "unknown" };
309
+ }
310
+ _latestHookUnitEnd(basePath, unitType, unitId) {
311
+ const unitEnds = queryJournal(basePath, { eventType: "unit-end", unitId })
312
+ .filter(entry => entry.data?.unitType === unitType);
313
+ return unitEnds[unitEnds.length - 1] ?? null;
314
+ }
315
+ _formatHookFailureReason(status, artifactVerified, errorContext) {
316
+ const parts = [`status ${typeof status === "string" ? status : "unknown"}`];
317
+ if (artifactVerified === false) {
318
+ parts.push("artifact not verified");
319
+ }
320
+ if (typeof errorContext === "object" && errorContext !== null && "message" in errorContext) {
321
+ const message = errorContext.message;
322
+ if (typeof message === "string" && message.length > 0) {
323
+ parts.push(message);
324
+ }
325
+ }
326
+ return parts.join("; ");
327
+ }
328
+ _handleFailedHookCompletion(basePath, hook, config, reason) {
329
+ if (config) {
330
+ const retry = this._startHook(config, hook.triggerUnitType, hook.triggerUnitId);
331
+ if (retry)
332
+ return retry;
333
+ }
334
+ this.hookFailure = {
335
+ hookName: hook.hookName,
336
+ unitType: `hook/${hook.hookName}`,
337
+ unitId: hook.triggerUnitId,
338
+ reason,
339
+ };
340
+ this.activeHook = null;
341
+ this.hookQueue = [];
342
+ this.persistState(basePath);
343
+ return null;
344
+ }
345
+ _handleExistingBlockingArtifact(config, trigger, basePath) {
346
+ const outcome = this._readGateOutcome(config, trigger, basePath);
347
+ switch (outcome.verdict) {
348
+ case "pass":
349
+ case "advisory":
350
+ return "skip";
351
+ case "needs-rework":
352
+ return this._routeNeedsRework(config, trigger, outcome);
353
+ case "needs-remediation":
354
+ case "needs-attention":
355
+ this._pauseForGate(config, trigger, outcome, `gate reported ${outcome.verdict}`);
356
+ return null;
357
+ case "failed":
358
+ case undefined:
359
+ return this._rerunGateOrBlock(config, trigger, basePath, {
360
+ reason: outcome.reason ?? `gate artifact reported verdict=${outcome.verdict}`,
361
+ outcome,
362
+ });
363
+ }
364
+ return this._rerunGateOrBlock(config, trigger, basePath, {
365
+ reason: `gate artifact reported unsupported verdict=${String(outcome.verdict)}`,
366
+ outcome,
367
+ });
368
+ }
369
+ _handleBlockingGateCompletion(config, hook, basePath, observedCleanExecution) {
370
+ if (!observedCleanExecution) {
371
+ return this._rerunGateOrBlock(config, hook, basePath, {
372
+ reason: `hook/${config.name} did not complete cleanly before the trigger unit resumed`,
373
+ });
374
+ }
375
+ const outcome = this._readGateOutcome(config, hook, basePath);
376
+ switch (outcome.verdict) {
377
+ case "pass":
378
+ case "advisory":
379
+ this.activeHook = null;
380
+ return this._dequeueNextHook(basePath);
381
+ case "needs-rework":
382
+ return this._routeNeedsRework(config, hook, outcome);
383
+ case "needs-remediation":
384
+ case "needs-attention":
385
+ return this._pauseForGate(config, hook, outcome, `gate reported ${outcome.verdict}`);
386
+ case "failed":
387
+ case undefined:
388
+ return this._rerunGateOrBlock(config, hook, basePath, {
389
+ reason: outcome.reason ?? `gate artifact reported verdict=${outcome.verdict}`,
390
+ outcome,
391
+ });
392
+ }
393
+ return this._rerunGateOrBlock(config, hook, basePath, {
394
+ reason: `gate artifact reported unsupported verdict=${String(outcome.verdict)}`,
395
+ outcome,
396
+ });
397
+ }
398
+ _routeNeedsRework(config, trigger, outcome) {
399
+ const action = config.on_block?.action ?? "retry-unit";
400
+ if (action === "retry-task" || action === "retry-unit") {
401
+ if (this._requestTriggerRetry(config, trigger, config.on_block?.artifact)) {
402
+ return null;
403
+ }
404
+ this._setGateBlock(config, trigger, {
405
+ action: "pause",
406
+ reason: "gate cycle budget exhausted after needs-rework",
407
+ outcome,
408
+ retryArtifact: config.on_block?.artifact,
409
+ });
410
+ this.activeHook = null;
411
+ this.hookQueue = [];
412
+ return null;
413
+ }
414
+ return this._pauseForGate(config, trigger, outcome, `gate reported needs-rework; configured on_block action is ${action}`);
415
+ }
416
+ _pauseForGate(config, trigger, outcome, reason) {
417
+ this._setGateBlock(config, trigger, {
418
+ action: config.on_block?.action ?? "pause",
419
+ reason,
420
+ outcome,
421
+ retryArtifact: config.on_block?.artifact,
422
+ });
423
+ this.activeHook = null;
424
+ this.hookQueue = [];
425
+ return null;
426
+ }
427
+ _requestTriggerRetry(config, hook, retryArtifact) {
428
+ const cycleKey = hookCycleKey(config, hook);
429
+ const currentCycle = this.cycleCounts.get(cycleKey) ?? 1;
430
+ const maxCycles = hookMaxCycles(config);
431
+ if (currentCycle >= maxCycles)
432
+ return false;
433
+ this.activeHook = null;
434
+ this.hookQueue = [];
435
+ this.retryPending = true;
436
+ this.retryTrigger = {
437
+ unitType: hook.triggerUnitType,
438
+ unitId: hook.triggerUnitId,
439
+ };
440
+ if (retryArtifact !== undefined) {
441
+ this.retryTrigger.retryArtifact = retryArtifact;
442
+ }
443
+ return true;
444
+ }
445
+ _rerunGateOrBlock(config, trigger, basePath, opts) {
446
+ const cycleKey = hookCycleKey(config, trigger);
447
+ const currentCycle = this.cycleCounts.get(cycleKey) ?? 0;
448
+ const maxCycles = hookMaxCycles(config);
449
+ if (currentCycle < maxCycles) {
450
+ this.activeHook = null;
451
+ this.hookQueue.unshift({
452
+ config,
453
+ triggerUnitType: trigger.triggerUnitType,
454
+ triggerUnitId: trigger.triggerUnitId,
455
+ forceRun: true,
456
+ });
457
+ return this._dequeueNextHook(basePath);
458
+ }
459
+ this._setGateBlock(config, trigger, {
460
+ action: "pause",
461
+ reason: `${opts.reason}; gate cycle budget exhausted`,
462
+ outcome: opts.outcome,
463
+ cycle: currentCycle,
464
+ maxCycles,
465
+ });
466
+ this.activeHook = null;
467
+ this.hookQueue = [];
468
+ return null;
469
+ }
470
+ _readGateOutcome(config, trigger, basePath) {
471
+ if (!config.artifact) {
472
+ return { reason: "blocking gate has no configured artifact" };
473
+ }
474
+ const artifactPath = resolveHookArtifactPath(basePath, trigger.triggerUnitId, config.artifact);
475
+ if (!existsSync(artifactPath)) {
476
+ return {
477
+ artifact: config.artifact,
478
+ artifactPath,
479
+ reason: `missing required gate artifact ${config.artifact}`,
480
+ };
481
+ }
482
+ let content = "";
483
+ try {
484
+ content = readFileSync(artifactPath, "utf-8");
485
+ }
486
+ catch (e) {
487
+ return {
488
+ artifact: config.artifact,
489
+ artifactPath,
490
+ reason: `could not read gate artifact ${config.artifact}: ${e.message}`,
491
+ };
492
+ }
493
+ const rawVerdict = extractFrontmatterVerdict(content);
494
+ if (!rawVerdict) {
495
+ return {
496
+ artifact: config.artifact,
497
+ artifactPath,
498
+ reason: `gate artifact ${config.artifact} is missing frontmatter verdict`,
499
+ };
500
+ }
501
+ if (rawVerdict === "failed") {
502
+ return {
503
+ artifact: config.artifact,
504
+ artifactPath,
505
+ verdict: "failed",
506
+ reason: `gate artifact ${config.artifact} reported verdict=failed`,
507
+ };
508
+ }
509
+ if (!HOOK_OUTCOME_VERDICTS.has(rawVerdict)) {
510
+ return {
511
+ artifact: config.artifact,
512
+ artifactPath,
513
+ reason: `gate artifact ${config.artifact} has unsupported verdict=${rawVerdict}`,
514
+ };
515
+ }
516
+ return {
517
+ artifact: config.artifact,
518
+ artifactPath,
519
+ verdict: rawVerdict,
520
+ };
521
+ }
522
+ _setGateBlock(config, trigger, opts) {
523
+ const cycleKey = hookCycleKey(config, trigger);
524
+ const cycle = opts.cycle ?? this.cycleCounts.get(cycleKey) ?? 0;
525
+ this.gateBlockPending = {
526
+ hookName: config.name,
527
+ triggerUnitType: trigger.triggerUnitType,
528
+ triggerUnitId: trigger.triggerUnitId,
529
+ artifact: opts.outcome?.artifact ?? config.artifact,
530
+ artifactPath: opts.outcome?.artifactPath,
531
+ verdict: opts.outcome?.verdict,
532
+ action: opts.action,
533
+ reason: opts.reason,
534
+ cycle,
535
+ maxCycles: opts.maxCycles ?? hookMaxCycles(config),
536
+ retryArtifact: opts.retryArtifact,
537
+ };
538
+ }
213
539
  // ── Pre-dispatch hook evaluation (sync, all-matching with compose) ──
214
540
  /**
215
541
  * Replicate exact semantics of runPreDispatchHooks from post-unit-hooks.ts:
@@ -275,6 +601,16 @@ export class RuleRegistry {
275
601
  isRetryPending() {
276
602
  return this.retryPending;
277
603
  }
604
+ consumeHookFailure() {
605
+ if (!this.hookFailure)
606
+ return null;
607
+ const failure = { ...this.hookFailure };
608
+ this.hookFailure = null;
609
+ return failure;
610
+ }
611
+ isGateBlockPending() {
612
+ return this.gateBlockPending !== null;
613
+ }
278
614
  /**
279
615
  * Returns the trigger unit info for a pending retry, or null.
280
616
  * Clears the retry state after reading.
@@ -287,22 +623,42 @@ export class RuleRegistry {
287
623
  this.retryTrigger = null;
288
624
  return trigger;
289
625
  }
290
- /** Clear all mutable state (activeHook, hookQueue, cycleCounts, retryPending, retryTrigger). */
626
+ /**
627
+ * Returns a pending post-unit gate block, or null.
628
+ * Clears the block state after reading.
629
+ */
630
+ consumeGateBlock() {
631
+ if (!this.gateBlockPending)
632
+ return null;
633
+ const block = { ...this.gateBlockPending };
634
+ this.gateBlockPending = null;
635
+ return block;
636
+ }
637
+ /** Clear all mutable hook lifecycle state. */
291
638
  resetState() {
292
639
  this.activeHook = null;
293
640
  this.hookQueue = [];
294
641
  this.cycleCounts.clear();
295
642
  this.retryPending = false;
296
643
  this.retryTrigger = null;
644
+ this.hookFailure = null;
645
+ this.gateBlockPending = null;
297
646
  }
298
647
  // ── Persistence ─────────────────────────────────────────────────────
299
648
  _hookStatePath(basePath) {
300
649
  return join(basePath, ".gsd", HOOK_STATE_FILE);
301
650
  }
302
- /** Persist current hook cycle counts to disk. */
651
+ /** Persist current hook state to disk. */
303
652
  persistState(basePath) {
304
653
  const state = {
305
654
  cycleCounts: Object.fromEntries(this.cycleCounts),
655
+ activeHook: this.activeHook ? { ...this.activeHook } : null,
656
+ hookQueue: this.hookQueue.map(entry => ({
657
+ hookName: entry.config.name,
658
+ triggerUnitType: entry.triggerUnitType,
659
+ triggerUnitId: entry.triggerUnitId,
660
+ forceRun: entry.forceRun,
661
+ })),
306
662
  savedAt: new Date().toISOString(),
307
663
  };
308
664
  try {
@@ -315,7 +671,7 @@ export class RuleRegistry {
315
671
  logWarning("registry", `failed to persist hook state: ${e.message}`);
316
672
  }
317
673
  }
318
- /** Restore hook cycle counts from disk after a crash/restart. */
674
+ /** Restore hook state from disk after a crash/restart. */
319
675
  restoreState(basePath) {
320
676
  try {
321
677
  const filePath = this._hookStatePath(basePath);
@@ -331,6 +687,24 @@ export class RuleRegistry {
331
687
  }
332
688
  }
333
689
  }
690
+ this.activeHook = state.activeHook && typeof state.activeHook === "object"
691
+ ? { ...state.activeHook }
692
+ : null;
693
+ this.hookQueue = [];
694
+ if (Array.isArray(state.hookQueue)) {
695
+ const hooks = resolvePostUnitHooks(basePath);
696
+ for (const entry of state.hookQueue) {
697
+ const config = hooks.find(h => h.name === entry.hookName);
698
+ if (config) {
699
+ this.hookQueue.push({
700
+ config,
701
+ triggerUnitType: entry.triggerUnitType,
702
+ triggerUnitId: entry.triggerUnitId,
703
+ forceRun: entry.forceRun,
704
+ });
705
+ }
706
+ }
707
+ }
334
708
  }
335
709
  catch (e) {
336
710
  logWarning("registry", `failed to restore hook state: ${e.message}`);
@@ -341,7 +715,7 @@ export class RuleRegistry {
341
715
  try {
342
716
  const filePath = this._hookStatePath(basePath);
343
717
  if (existsSync(filePath)) {
344
- writeFileSync(filePath, JSON.stringify({ cycleCounts: {}, savedAt: new Date().toISOString() }, null, 2), "utf-8");
718
+ writeFileSync(filePath, JSON.stringify({ cycleCounts: {}, activeHook: null, hookQueue: [], savedAt: new Date().toISOString() }, null, 2), "utf-8");
345
719
  }
346
720
  }
347
721
  catch (e) {
@@ -365,6 +739,7 @@ export class RuleRegistry {
365
739
  type: "post",
366
740
  enabled: hook.enabled !== false,
367
741
  targets: hook.after,
742
+ criticality: hook.criticality ?? "advisory",
368
743
  activeCycles,
369
744
  });
370
745
  }
@@ -436,9 +811,10 @@ export class RuleRegistry {
436
811
  lines.push("Post-Unit Hooks (run after unit completes):");
437
812
  for (const hook of postHooks) {
438
813
  const status = hook.enabled ? "enabled" : "disabled";
814
+ const criticality = hook.criticality ?? "advisory";
439
815
  const cycles = Object.keys(hook.activeCycles).length;
440
816
  const cycleInfo = cycles > 0 ? ` (${cycles} active cycle${cycles === 1 ? "" : "s"})` : "";
441
- lines.push(` ${hook.name} [${status}] → after: ${hook.targets.join(", ")}${cycleInfo}`);
817
+ lines.push(` ${hook.name} [${status}, ${criticality}] → after: ${hook.targets.join(", ")}${cycleInfo}`);
442
818
  }
443
819
  lines.push("");
444
820
  }
@@ -724,8 +724,8 @@ export async function deriveStateFromDb(basePath, artifactReadRoot = basePath) {
724
724
  }
725
725
  }
726
726
  // ADR-011 Phase 2: pause-on-escalation takes precedence over dispatching the
727
- // next task. `awaiting_review` tasks (continueWithDefault=true) are NOT
728
- // surfaced here they let the loop continue.
727
+ // next task. `awaiting_review` tasks (continueWithDefault=true) still pause
728
+ // here so silence is never treated as consent.
729
729
  //
730
730
  // We do NOT gate this on `phases.mid_execution_escalation` — creation of
731
731
  // new escalations is gated at the write site (tools/complete-task.ts:315),
@@ -132,14 +132,16 @@
132
132
  Verify field rules:
133
133
  - MUST be a mechanically executable command: `npm test`, `grep -q "pattern" file`, `test -f path`
134
134
  - MUST NOT use shell pipes, redirects, semicolons, backticks, command substitution, output trimming, or grep regex alternation with `|`
135
+ - For absence checks, use `! grep -q "pattern" file` or `! rg -q "pattern" file`; do not use `grep -c` or `rg -c` to assert zero matches because count commands exit 1 when they find zero matches
135
136
  - MUST NOT use inline `node -e` assertions for verification; put assertions in a real test file and run it with `node --test` or a package test script
136
137
  - For content/document tasks: verify file existence, section count, YAML validity, or word count
137
138
  NOT exact phrasing, specific formulas, or "zero TBD" aspirational criteria
138
139
  - If no command can verify the output, write: "Manual review — file exists and is non-empty"
139
140
  - BAD: `python3 -m pytest tests/ -q --tb=short 2>&1 | tail -5`
141
+ - BAD: `grep -c "old_api" src/index.ts`
140
142
  - BAD: "Sections 3.1 and 3.2 exist with exact formulas. Zero TBD/TODO."
141
143
  - GOOD: `python3 -m pytest tests/ -q --tb=short`
142
- - GOOD: `node --test tests/verify-doc.test.js`, `grep -q "Required heading" doc.md`, `test -s doc.md`
144
+ - GOOD: `node --test tests/verify-doc.test.js`, `grep -q "Required heading" doc.md`, `! grep -q "old_api" src/index.ts`, `test -s doc.md`
143
145
 
144
146
  Integration closure rule:
145
147
  - At least one slice in any multi-boundary milestone should perform real composition/wiring, not just contract hardening
@@ -2,8 +2,10 @@
2
2
  // File Purpose: ADR-015 Tool Contract module for Unit prompt, policy, and tool parity.
3
3
  import { resolveManifest, } from "./unit-context-manifest.js";
4
4
  import { getRequiredWorkflowToolsForAutoUnit } from "./workflow-mcp.js";
5
+ import { getUnitToolSurfaceContract } from "./unit-tool-contracts.js";
5
6
  export function compileUnitToolContract(unitType) {
6
7
  const manifest = resolveManifest(unitType);
8
+ const surfaceContract = getUnitToolSurfaceContract(unitType);
7
9
  if (!manifest) {
8
10
  return {
9
11
  ok: false,
@@ -12,6 +14,8 @@ export function compileUnitToolContract(unitType) {
12
14
  };
13
15
  }
14
16
  const requiredWorkflowTools = getRequiredWorkflowToolsForAutoUnit(unitType);
17
+ const forbiddenWorkflowTools = Object.entries(surfaceContract?.forbiddenGsdTools ?? {})
18
+ .map(([name, reason]) => ({ name, reason }));
15
19
  const closeoutTools = requiredWorkflowTools.filter((tool) => /^gsd_(?:task|slice|milestone|complete|validate|save|summary)/.test(tool));
16
20
  if (requiresCloseoutTool(unitType) && closeoutTools.length === 0) {
17
21
  return {
@@ -27,6 +31,7 @@ export function compileUnitToolContract(unitType) {
27
31
  contextMode: manifest.contextMode,
28
32
  toolsPolicy: manifest.tools,
29
33
  requiredWorkflowTools,
34
+ forbiddenWorkflowTools,
30
35
  promptObligations: [
31
36
  `context-mode:${manifest.contextMode}`,
32
37
  `tools-policy:${manifest.tools.mode}`,