@opengsd/gsd-pi 1.1.1-dev.3ea310e → 1.1.1-dev.595401e

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 (314) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/gsd/auto/orchestrator.js +0 -1
  3. package/dist/resources/extensions/gsd/auto/phases.js +4 -3
  4. package/dist/resources/extensions/gsd/auto-dashboard.js +92 -17
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +5 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +132 -8
  7. package/dist/resources/extensions/gsd/auto-prompts.js +68 -22
  8. package/dist/resources/extensions/gsd/auto-start.js +41 -12
  9. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
  10. package/dist/resources/extensions/gsd/auto.js +12 -5
  11. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +82 -3
  12. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
  13. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
  14. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
  15. package/dist/resources/extensions/gsd/browser-evidence.js +29 -2
  16. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
  17. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
  18. package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
  19. package/dist/resources/extensions/gsd/config-overlay.js +2 -1
  20. package/dist/resources/extensions/gsd/dashboard-overlay.js +21 -7
  21. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  22. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +2 -2
  23. package/dist/resources/extensions/gsd/error-classifier.js +2 -1
  24. package/dist/resources/extensions/gsd/escalation.js +4 -4
  25. package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
  26. package/dist/resources/extensions/gsd/forensics.js +74 -2
  27. package/dist/resources/extensions/gsd/gsd-db.js +5 -2
  28. package/dist/resources/extensions/gsd/guided-flow.js +29 -68
  29. package/dist/resources/extensions/gsd/memory-store.js +4 -1
  30. package/dist/resources/extensions/gsd/post-unit-hooks.js +9 -0
  31. package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
  32. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  33. package/dist/resources/extensions/gsd/prompts/forensics.md +61 -1
  34. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  35. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  36. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  38. package/dist/resources/extensions/gsd/prompts/run-uat.md +48 -24
  39. package/dist/resources/extensions/gsd/prompts/system.md +3 -1
  40. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  41. package/dist/resources/extensions/gsd/rule-registry.js +428 -52
  42. package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
  43. package/dist/resources/extensions/gsd/skill-activation.js +20 -3
  44. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
  45. package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
  46. package/dist/resources/extensions/gsd/state.js +3 -3
  47. package/dist/resources/extensions/gsd/templates/plan.md +3 -1
  48. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -1
  49. package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
  50. package/dist/resources/extensions/gsd/tools/validate-milestone.js +46 -16
  51. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +403 -3
  52. package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
  53. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
  54. package/dist/resources/extensions/gsd/verdict-parser.js +59 -15
  55. package/dist/resources/extensions/gsd/verification-gate.js +72 -1
  56. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +1 -1
  57. package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
  58. package/dist/rtk.d.ts +7 -1
  59. package/dist/rtk.js +27 -11
  60. package/dist/web/standalone/.next/BUILD_ID +1 -1
  61. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  62. package/dist/web/standalone/.next/build-manifest.json +2 -2
  63. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  64. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.html +1 -1
  81. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  88. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  89. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  91. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  92. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  93. package/package.json +3 -2
  94. package/packages/cloud-mcp-gateway/package.json +2 -2
  95. package/packages/contracts/dist/workflow.d.ts +14 -0
  96. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  97. package/packages/contracts/dist/workflow.js +16 -0
  98. package/packages/contracts/dist/workflow.js.map +1 -1
  99. package/packages/contracts/package.json +1 -1
  100. package/packages/daemon/package.json +4 -4
  101. package/packages/gsd-agent-core/dist/agent-session.d.ts +9 -0
  102. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  103. package/packages/gsd-agent-core/dist/agent-session.js +32 -0
  104. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  105. package/packages/gsd-agent-core/dist/index.d.ts +1 -0
  106. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  107. package/packages/gsd-agent-core/dist/index.js +1 -0
  108. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  109. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts +2 -0
  110. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts.map +1 -1
  111. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js +8 -2
  112. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js.map +1 -1
  113. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +7 -0
  114. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  115. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  116. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  117. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +69 -1
  118. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  119. package/packages/gsd-agent-core/dist/turn-latency.d.ts +47 -0
  120. package/packages/gsd-agent-core/dist/turn-latency.d.ts.map +1 -0
  121. package/packages/gsd-agent-core/dist/turn-latency.js +123 -0
  122. package/packages/gsd-agent-core/dist/turn-latency.js.map +1 -0
  123. package/packages/gsd-agent-core/package.json +6 -6
  124. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +21 -0
  125. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +1 -0
  126. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +213 -0
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +1 -0
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  132. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
  133. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  134. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +89 -31
  135. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  137. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +7 -1
  138. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.d.ts.map +1 -1
  140. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js +6 -0
  141. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js.map +1 -1
  142. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
  143. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  144. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
  145. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  146. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  147. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
  148. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  149. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
  150. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
  151. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
  152. package/packages/gsd-agent-modes/package.json +7 -7
  153. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  154. package/packages/mcp-server/dist/remote-questions.js +23 -9
  155. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  156. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  157. package/packages/mcp-server/dist/workflow-tools.js +84 -2
  158. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  159. package/packages/mcp-server/package.json +3 -3
  160. package/packages/native/package.json +1 -1
  161. package/packages/pi-agent-core/dist/agent-loop.js +38 -0
  162. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  163. package/packages/pi-agent-core/dist/agent.d.ts +5 -1
  164. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  165. package/packages/pi-agent-core/dist/agent.js +2 -0
  166. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  167. package/packages/pi-agent-core/dist/types.d.ts +3 -0
  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/image-models.generated.d.ts +15 -0
  176. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  177. package/packages/pi-ai/dist/image-models.generated.js +15 -0
  178. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  179. package/packages/pi-ai/dist/models.generated.d.ts +86 -18
  180. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  181. package/packages/pi-ai/dist/models.generated.js +108 -40
  182. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  183. package/packages/pi-ai/dist/stream.js +6 -6
  184. package/packages/pi-ai/dist/stream.js.map +1 -1
  185. package/packages/pi-ai/package.json +1 -1
  186. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -2
  188. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  190. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  192. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  193. package/packages/pi-coding-agent/package.json +7 -7
  194. package/packages/pi-tui/dist/terminal.d.ts +1 -0
  195. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  196. package/packages/pi-tui/dist/terminal.js +8 -4
  197. package/packages/pi-tui/dist/terminal.js.map +1 -1
  198. package/packages/pi-tui/package.json +1 -1
  199. package/packages/rpc-client/package.json +2 -2
  200. package/pkg/package.json +1 -1
  201. package/src/resources/extensions/gsd/auto/orchestrator.ts +0 -1
  202. package/src/resources/extensions/gsd/auto/phases.ts +5 -3
  203. package/src/resources/extensions/gsd/auto-dashboard.ts +98 -18
  204. package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
  205. package/src/resources/extensions/gsd/auto-post-unit.ts +164 -7
  206. package/src/resources/extensions/gsd/auto-prompts.ts +102 -15
  207. package/src/resources/extensions/gsd/auto-start.ts +54 -14
  208. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
  209. package/src/resources/extensions/gsd/auto.ts +15 -4
  210. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +89 -3
  211. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
  212. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
  213. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
  214. package/src/resources/extensions/gsd/browser-evidence.ts +26 -2
  215. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
  216. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
  217. package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
  218. package/src/resources/extensions/gsd/config-overlay.ts +3 -1
  219. package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -7
  220. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  221. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +2 -2
  222. package/src/resources/extensions/gsd/error-classifier.ts +2 -1
  223. package/src/resources/extensions/gsd/escalation.ts +4 -4
  224. package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
  225. package/src/resources/extensions/gsd/forensics.ts +99 -5
  226. package/src/resources/extensions/gsd/gsd-db.ts +5 -2
  227. package/src/resources/extensions/gsd/guided-flow.ts +90 -82
  228. package/src/resources/extensions/gsd/memory-store.ts +4 -1
  229. package/src/resources/extensions/gsd/post-unit-hooks.ts +14 -1
  230. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  231. package/src/resources/extensions/gsd/preferences-validation.ts +36 -0
  232. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  233. package/src/resources/extensions/gsd/prompts/forensics.md +61 -1
  234. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  235. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  236. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  237. package/src/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  238. package/src/resources/extensions/gsd/prompts/run-uat.md +48 -24
  239. package/src/resources/extensions/gsd/prompts/system.md +3 -1
  240. package/src/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  241. package/src/resources/extensions/gsd/rule-registry.ts +558 -58
  242. package/src/resources/extensions/gsd/rule-types.ts +2 -0
  243. package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
  244. package/src/resources/extensions/gsd/skill-activation.ts +20 -2
  245. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
  246. package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
  247. package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
  248. package/src/resources/extensions/gsd/state.ts +3 -3
  249. package/src/resources/extensions/gsd/templates/plan.md +3 -1
  250. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +156 -4
  251. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +37 -0
  252. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +16 -3
  253. package/src/resources/extensions/gsd/tests/browser-evidence.test.ts +142 -0
  254. package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
  255. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
  256. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +30 -0
  257. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +45 -0
  258. package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +53 -0
  259. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
  260. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +8 -0
  261. package/src/resources/extensions/gsd/tests/discuss-milestone-structured-questions.test.ts +31 -0
  262. package/src/resources/extensions/gsd/tests/doctor-runtime-checks.test.ts +27 -0
  263. package/src/resources/extensions/gsd/tests/escalation.test.ts +16 -27
  264. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
  265. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
  266. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +20 -0
  267. package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +3 -0
  268. package/src/resources/extensions/gsd/tests/forensics-tool-scope.test.ts +69 -0
  269. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +40 -1
  270. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +86 -0
  271. package/src/resources/extensions/gsd/tests/guided-flow.test.ts +12 -9
  272. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +4 -4
  273. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +66 -10
  274. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +39 -8
  275. package/src/resources/extensions/gsd/tests/new-milestone-discuss-routing.test.ts +3 -3
  276. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
  277. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +9 -0
  278. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +157 -0
  279. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +179 -0
  280. package/src/resources/extensions/gsd/tests/preferences.test.ts +29 -0
  281. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +53 -1
  282. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +14 -0
  283. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
  284. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +7 -8
  285. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
  286. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
  287. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
  288. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +75 -0
  289. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
  290. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +191 -0
  291. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
  292. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
  293. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
  294. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
  295. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +6 -3
  296. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +133 -0
  297. package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
  298. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +51 -0
  299. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +2 -2
  300. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +213 -0
  301. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
  302. package/src/resources/extensions/gsd/tools/complete-task.ts +20 -2
  303. package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
  304. package/src/resources/extensions/gsd/tools/validate-milestone.ts +46 -15
  305. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +489 -3
  306. package/src/resources/extensions/gsd/types.ts +67 -4
  307. package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
  308. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
  309. package/src/resources/extensions/gsd/verdict-parser.ts +54 -13
  310. package/src/resources/extensions/gsd/verification-gate.ts +87 -1
  311. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +1 -1
  312. package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
  313. /package/dist/web/standalone/.next/static/{xACmObbrDjwLriepRgaa9 → IDKjyRHLIaumjgonPcYiX}/_buildManifest.js +0 -0
  314. /package/dist/web/standalone/.next/static/{xACmObbrDjwLriepRgaa9 → IDKjyRHLIaumjgonPcYiX}/_ssgManifest.js +0 -0
@@ -36,7 +36,9 @@ import { resolveSkillManifest } from "../skill-manifest.js";
36
36
  import { applyUnitSkillVisibility, unitHasSkillManifest } from "../skill-scope.js";
37
37
  import { getGuidedUnitContext } from "../guided-unit-context.js";
38
38
  import { registerPlanMilestoneSchemaRecovery } from "./plan-milestone-schema-recovery.js";
39
- import { AUTO_UNIT_SCOPED_TOOLS, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
39
+ import { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
40
+ import { filterToolsForProvider } from "../model-router.js";
41
+ import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
40
42
 
41
43
  let approvalQuestionAbortInFlight = false;
42
44
 
@@ -123,6 +125,7 @@ export const MINIMAL_GSD_TOOL_NAMES = [
123
125
  "gsd_resume",
124
126
  "gsd_milestone_status",
125
127
  "gsd_checkpoint_db",
128
+ "gsd_plan_milestone",
126
129
  "memory_query",
127
130
  "capture_thought",
128
131
  ] as const;
@@ -226,6 +229,9 @@ export function buildMinimalAutoGsdToolSet(
226
229
  unitType: string | undefined,
227
230
  registeredToolNames: readonly string[] = activeToolNames,
228
231
  ): string[] {
232
+ if (unitType === "run-uat") {
233
+ return buildRunUatGsdToolSet(activeToolNames, registeredToolNames);
234
+ }
229
235
  const unitTools = unitType ? AUTO_UNIT_SCOPED_TOOLS[unitType] ?? [] : [];
230
236
  const autoBaseTools = new Set<string>(MINIMAL_AUTO_BASE_TOOL_NAMES);
231
237
  const availableBaseTools = registeredToolNames.filter((name) => autoBaseTools.has(name));
@@ -240,6 +246,17 @@ export function buildMinimalAutoGsdToolSet(
240
246
  return withPreservedShimTools([...new Set([...preserved, ...scoped])]);
241
247
  }
242
248
 
249
+ export function buildRunUatGsdToolSet(
250
+ activeToolNames: readonly string[],
251
+ registeredToolNames: readonly string[] = activeToolNames,
252
+ ): string[] {
253
+ const scoped = resolveScopedToolNames(
254
+ [...activeToolNames, ...registeredToolNames],
255
+ [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent", ...RUN_UAT_BROWSER_TOOL_NAMES],
256
+ );
257
+ return [...new Set(scoped)];
258
+ }
259
+
243
260
  export function buildMinimalGsdWorkflowToolSet(
244
261
  activeToolNames: readonly string[],
245
262
  registeredToolNames: readonly string[] = activeToolNames,
@@ -1022,9 +1039,8 @@ export function registerHooks(
1022
1039
  if (result.block) return result;
1023
1040
  });
1024
1041
 
1025
- // ── Safety harness: evidence collection + destructive command warnings ──
1042
+ // ── Safety harness: evidence collection + destructive command blocking ──
1026
1043
  pi.on("tool_call", async (event, ctx) => {
1027
- if (!isAutoActive()) return;
1028
1044
  markToolStart(event.toolCallId, event.toolName);
1029
1045
  safetyRecordToolCall(event.toolCallId, event.toolName, event.input as Record<string, unknown>);
1030
1046
 
@@ -1041,17 +1057,28 @@ export function registerHooks(
1041
1057
  }
1042
1058
  }
1043
1059
 
1044
- // Destructive command classification (warn only, never block)
1060
+ // Destructive command classification + hard gate in all modes.
1045
1061
  if (isToolCallEventType("bash", event)) {
1046
1062
  const classification = classifyCommand(event.input.command);
1047
1063
  if (classification.destructive) {
1064
+ const reason = [
1065
+ "HARD BLOCK: destructive Bash command requires explicit human confirmation.",
1066
+ `Detected: ${classification.labels.join(", ")}`,
1067
+ "Run this via ask_user_questions, wait for the user's response,",
1068
+ "then issue the command only when confirmed in the current turn.",
1069
+ ].join(" ");
1048
1070
  safetyLogWarning("safety", `destructive command: ${classification.labels.join(", ")}`, {
1049
1071
  command: String(event.input.command).slice(0, 200),
1050
1072
  });
1051
- ctx.ui.notify(
1052
- `Destructive command detected: ${classification.labels.join(", ")}`,
1053
- "warning",
1054
- );
1073
+ if (ctx) {
1074
+ await maybePauseAutoForApprovalGate(
1075
+ ctx,
1076
+ pi,
1077
+ isAutoActive(),
1078
+ "Depth confirmation is waiting for your answer — pausing auto-mode.",
1079
+ );
1080
+ }
1081
+ return { block: true, reason };
1055
1082
  }
1056
1083
  }
1057
1084
  });
@@ -1320,19 +1347,27 @@ export function registerHooks(
1320
1347
  const fullToolsRequested = isFullGsdToolSurfaceRequested();
1321
1348
  const dropAliases = !fullToolsRequested;
1322
1349
  const dropBrowser = !fullToolsRequested && !isBrowserToolSurfaceRequested();
1323
- const providerCompatible = compatible.filter(
1324
- (name) => !(dropAliases && isWorkflowAliasTool(name)) && !(dropBrowser && isBrowserTool(name)),
1350
+ const aliasFilteredCompatible = compatible.filter(
1351
+ (name) => !(dropAliases && isWorkflowAliasTool(name)),
1352
+ );
1353
+ const providerCompatible = aliasFilteredCompatible.filter(
1354
+ (name) => !(dropBrowser && isBrowserTool(name)),
1325
1355
  );
1326
1356
  const surfaceReduced = providerCompatible.length !== compatible.length;
1327
1357
  if (fullToolsRequested) {
1328
1358
  return surfaceReduced ? { toolNames: providerCompatible } : undefined;
1329
1359
  }
1330
1360
  const registeredToolNames = resolveRegisteredToolNames(pi, event.activeToolNames);
1361
+ const compatibleRegisteredToolNames = filterToolsForProvider(
1362
+ registeredToolNames,
1363
+ event.selectedModelApi,
1364
+ event.selectedModelProvider,
1365
+ ).compatible.filter((name) => !(dropAliases && isWorkflowAliasTool(name)));
1331
1366
  const guidedUnit = getGuidedUnitContext();
1332
1367
  const requestScoped = buildRequestScopedGsdToolSet(
1333
- providerCompatible,
1368
+ guidedUnit?.unitType === "run-uat" ? aliasFilteredCompatible : providerCompatible,
1334
1369
  event.requestCustomMessages,
1335
- registeredToolNames,
1370
+ guidedUnit?.unitType === "run-uat" ? compatibleRegisteredToolNames : registeredToolNames,
1336
1371
  guidedUnit?.unitType,
1337
1372
  );
1338
1373
  if (requestScoped) {
@@ -1342,9 +1377,11 @@ export function registerHooks(
1342
1377
  if (dash.active && dash.currentUnit) {
1343
1378
  return {
1344
1379
  toolNames: buildMinimalAutoGsdToolSet(
1345
- providerCompatible,
1380
+ dash.currentUnit.type === "run-uat" ? aliasFilteredCompatible : providerCompatible,
1346
1381
  dash.currentUnit.type,
1347
- resolveRegisteredToolNames(pi, event.activeToolNames),
1382
+ dash.currentUnit.type === "run-uat"
1383
+ ? compatibleRegisteredToolNames
1384
+ : resolveRegisteredToolNames(pi, event.activeToolNames),
1348
1385
  ),
1349
1386
  };
1350
1387
  }
@@ -679,6 +679,7 @@ const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
679
679
  * manifests still declare per-unit subsets via ToolsPolicy.allowedSubagents.
680
680
  */
681
681
  const PLANNING_DISPATCH_AGENT_REGISTRY = {
682
+ mnemo: { readOnlySpecialist: true },
682
683
  scout: { readOnlySpecialist: true },
683
684
  planner: { readOnlySpecialist: true },
684
685
  reviewer: { readOnlySpecialist: true },
@@ -692,7 +693,7 @@ export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set<string>(
692
693
  .map(([agentId]) => agentId),
693
694
  );
694
695
 
695
- let warnedMissingPlanningDispatchAgentClasses = false;
696
+ let warnedMissingControlledDispatchAgentClasses = false;
696
697
 
697
698
  function isReadOnlySpecialist(agentId: string): boolean {
698
699
  const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId as keyof typeof PLANNING_DISPATCH_AGENT_REGISTRY];
@@ -703,11 +704,20 @@ function allowedPlanningDispatchAgentsList(): string {
703
704
  return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
704
705
  }
705
706
 
706
- function warnMissingPlanningDispatchAgentClasses(unitType: string, mode: string, toolName: string): void {
707
- if (warnedMissingPlanningDispatchAgentClasses) return;
708
- warnedMissingPlanningDispatchAgentClasses = true;
707
+ function allowsControlledSubagentDispatch(
708
+ policy: ToolsPolicy,
709
+ ): policy is ToolsPolicy & { readonly allowedSubagents: readonly string[] } {
710
+ return (
711
+ (policy.mode === "planning-dispatch" || policy.mode === "verification") &&
712
+ Array.isArray((policy as { readonly allowedSubagents?: unknown }).allowedSubagents)
713
+ );
714
+ }
715
+
716
+ function warnMissingControlledDispatchAgentClasses(unitType: string, mode: string, toolName: string): void {
717
+ if (warnedMissingControlledDispatchAgentClasses) return;
718
+ warnedMissingControlledDispatchAgentClasses = true;
709
719
  // TODO(#5060): Remove this migration shim once all subagent/task callers are verified to forward agent identities.
710
- const message = `[write-gate] planning-dispatch: shouldBlockPlanningUnit called for tool "${toolName}" ` +
720
+ const message = `[write-gate] controlled-dispatch: shouldBlockPlanningUnit called for tool "${toolName}" ` +
711
721
  `on unit "${unitType}" without agentClasses - stale caller; blocking dispatch.`;
712
722
  console.warn(message);
713
723
  logWarning("intercept", message, {
@@ -777,8 +787,9 @@ function blockReason(unitType: string, mode: string, what: string): string {
777
787
  * - "docs" → like "planning" but also allows writes to paths
778
788
  * matching `allowedPathGlobs` relative to basePath.
779
789
  * - "verification"
780
- * → allows Bash for project verification commands, but keeps
781
- * writes restricted to .gsd/ and blocks subagent dispatch.
790
+ * → allows Bash for project verification commands, keeps
791
+ * writes restricted to .gsd/, and permits subagent dispatch
792
+ * only when the manifest declares allowedSubagents.
782
793
  *
783
794
  * `pathOrCommand` is the file path for write/edit-shaped tools and the
784
795
  * shell command for bash. Other tools ignore this argument.
@@ -825,7 +836,7 @@ export function shouldBlockPlanningUnit(
825
836
  if (tool.startsWith("gsd_")) return { block: false };
826
837
 
827
838
  if (PLANNING_SUBAGENT_TOOLS.has(tool)) {
828
- if (policy.mode === "planning-dispatch") {
839
+ if (allowsControlledSubagentDispatch(policy)) {
829
840
  const requested = (agentClasses ?? []).map(a => a.trim()).filter(Boolean);
830
841
  const dispatchContract = compileSubagentPermissionContract(policy);
831
842
  const allowedSubagents = dispatchContract.allowedSubagents;
@@ -834,7 +845,7 @@ export function shouldBlockPlanningUnit(
834
845
  // agent identities yet. Block and warn so stale callers surface in telemetry
835
846
  // instead of silently bypassing the gate.
836
847
  if (agentClasses === undefined) {
837
- warnMissingPlanningDispatchAgentClasses(unitType, policy.mode, tool);
848
+ warnMissingControlledDispatchAgentClasses(unitType, policy.mode, tool);
838
849
  return {
839
850
  block: true,
840
851
  reason: blockReason(
@@ -857,7 +868,7 @@ export function shouldBlockPlanningUnit(
857
868
  reason: blockReason(
858
869
  unitType,
859
870
  policy.mode,
860
- `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from planning-dispatch units`,
871
+ `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`,
861
872
  ),
862
873
  };
863
874
  }
@@ -1,11 +1,13 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: Shared browser-observable UAT requirement and evidence detection.
3
3
 
4
- export const BROWSER_REQUIREMENT_RE = /\b(?:browser|file:\/\/|localhost|dom|localstorage|click(?:ing|ed)?|button|screenshot|snapshot|reload(?:ed)?|page refresh|user-visible|strikethrough|search box)\b/i;
4
+ export const BROWSER_REQUIREMENT_RE = /\b(?:file:\/\/|localhost|playwright|chrome|screenshot|snapshot|browser_(?:assert|batch|find|verify|snapshot_refs))\b|\b(?:open|launch|navigate|load|visit|serve|start)\b.{0,80}\b(?:browser|page|localhost|file:\/\/)\b|\bbrowser\s+(?:check|session|test|uat|tool|automation|interaction|flow)\b/i;
5
5
  export const NO_BROWSER_EVIDENCE_RE = /\b(?:no|without|not|wasn'?t|isn'?t)\s+(?:automated\s+)?(?:live\s+)?browser(?:\s+(?:session|test|uat))?|\bno\s+automated\s+browser\b|\bnot\s+conducted\b/i;
6
6
  export const BROWSER_RUNTIME_RE = /\b(?:browser|playwright|chrome|camoufox|browser_(?:assert|batch|find|verify|snapshot_refs)|screenshot|snapshot|file:\/\/|localhost)\b/i;
7
7
  export const BROWSER_ACTION_RE = /\b(?:open(?:ed)?|navigate(?:d)?|click(?:ed)?|type(?:d)?|reload(?:ed)?|capture(?:d)?|screenshot|snapshot)\b/i;
8
8
  export const BROWSER_ASSERTION_RE = /\b(?:assert(?:ed|ion)?|observed|confirmed|verified|expected|visible|text|count|label|strikethrough|localstorage|screenshot|snapshot|passed)\b/i;
9
+ const NON_REQUIREMENT_BROWSER_HEADING_RE = /^(?:not\s+proven|not\s+covered|out\s+of\s+scope|deferred|follow-?ups?|known\s+limitations|notes\s+for\s+tester)\b/i;
10
+ const NON_REQUIREMENT_BROWSER_LINE_RE = /\b(?:deferred|not\s+proven|not\s+covered|out\s+of\s+scope|future\s+slice|follow-?up|no\s+(?:live\s+)?browser|without\s+(?:a\s+)?browser|not\s+(?:a\s+)?browser)\b/i;
9
11
 
10
12
  export function compactTextParts(parts: Array<string | string[] | null | undefined>): string {
11
13
  return parts.flatMap((part) => Array.isArray(part) ? part : [part])
@@ -14,7 +16,29 @@ export function compactTextParts(parts: Array<string | string[] | null | undefin
14
16
  }
15
17
 
16
18
  export function hasBrowserRequiredText(text: string): boolean {
17
- return BROWSER_REQUIREMENT_RE.test(text);
19
+ let inNonRequirementSection = false;
20
+ let nonRequirementDepth = 0;
21
+ for (const line of text.split(/\r?\n/)) {
22
+ const headingMatch = line.match(/^(#{1,6})\s+(.+?)\s*$/);
23
+ if (headingMatch) {
24
+ const depth = headingMatch[1]!.length;
25
+ const title = headingMatch[2] ?? "";
26
+ // Only update section context when at the same or higher level than the
27
+ // heading that opened the non-requirement zone. A sub-heading deeper than
28
+ // the opening heading must not escape or re-enter the zone on its own.
29
+ if (!inNonRequirementSection || depth <= nonRequirementDepth) {
30
+ inNonRequirementSection = NON_REQUIREMENT_BROWSER_HEADING_RE.test(title);
31
+ nonRequirementDepth = inNonRequirementSection ? depth : 0;
32
+ }
33
+ // Check the heading title itself — section state is already updated, so
34
+ // we correctly skip headings that opened a non-requirement zone.
35
+ if (!inNonRequirementSection && BROWSER_REQUIREMENT_RE.test(title)) return true;
36
+ continue;
37
+ }
38
+ if (inNonRequirementSection || NON_REQUIREMENT_BROWSER_LINE_RE.test(line)) continue;
39
+ if (BROWSER_REQUIREMENT_RE.test(line)) return true;
40
+ }
41
+ return false;
18
42
  }
19
43
 
20
44
  export function hasBrowserEvidenceText(text: string): boolean {
@@ -75,7 +75,7 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
75
75
  " /gsd new-milestone Create milestone from headless context (used by gsd headless)",
76
76
  " /gsd new-project Bootstrap a new project (use --deep for staged project-level discovery)",
77
77
  " /gsd quick Execute a quick task without full planning overhead",
78
- " /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|uat|replan]",
78
+ " /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|validate|reassess|uat|replan]",
79
79
  " /gsd verdict <v> Override milestone validation verdict [pass|needs-attention|needs-remediation] [--milestone Mxxx] [--rationale \"...\"]",
80
80
  " /gsd parallel Parallel milestone orchestration [start|status|stop|pause|resume|merge|watch]",
81
81
  " /gsd workflow Custom workflow lifecycle [new|run|list|validate|pause|resume]",
@@ -23,6 +23,9 @@ import {
23
23
  import { loadFile, saveFile, splitFrontmatter, parseFrontmatterMap } from "./files.js";
24
24
  import { runClaudeImportFlow } from "./claude-import.js";
25
25
 
26
+ const DEFAULT_WIDGET_MODE = "small";
27
+ const WIDGET_MODE_OPTIONS = [DEFAULT_WIDGET_MODE, "full", "min", "off"] as const;
28
+
26
29
  /** Extract body content after frontmatter closing delimiter, or null if none. */
27
30
  function extractBodyAfterFrontmatter(content: string): string | null {
28
31
  const closingIdx = content.indexOf("\n---", content.indexOf("---"));
@@ -1558,7 +1561,7 @@ async function configureAdvanced(ctx: ExtensionCommandContext, prefs: Record<str
1558
1561
  prefs.min_request_interval_ms = minRequestInterval;
1559
1562
  }
1560
1563
 
1561
- const widget = await promptEnum(ctx, "Auto-mode widget display", prefs.widget_mode, ["full", "small", "min", "off"], "full");
1564
+ const widget = await promptEnum(ctx, "Auto-mode widget display", prefs.widget_mode, WIDGET_MODE_OPTIONS, DEFAULT_WIDGET_MODE);
1562
1565
  if (widget !== undefined) prefs.widget_mode = widget;
1563
1566
 
1564
1567
  const experimental = (prefs.experimental as Record<string, unknown> | undefined) ?? {};
@@ -238,7 +238,7 @@ export async function handleVerdict(
238
238
 
239
239
  if (effectiveVerdict === "needs-remediation") {
240
240
  ctx.ui.notify(
241
- "Follow up with gsd_reassess_roadmap to add remediation slices, then re-run /gsd auto.",
241
+ "Follow up with /gsd dispatch reassess to add remediation slices, then re-run /gsd auto.",
242
242
  "info",
243
243
  );
244
244
  }
@@ -23,6 +23,8 @@ import {
23
23
  resolveAutoSupervisorConfig,
24
24
  } from "./preferences.js";
25
25
 
26
+ const DEFAULT_WIDGET_MODE = "small";
27
+
26
28
  // ─── Data Collection ──────────────────────────────────────────────────────
27
29
 
28
30
  interface ConfigSection {
@@ -160,7 +162,7 @@ function collectConfigSections(): ConfigSection[] {
160
162
  if (prefs?.service_tier) toggleRows.push({ label: "service_tier", value: prefs.service_tier });
161
163
  if (prefs?.search_provider && prefs.search_provider !== "auto") toggleRows.push({ label: "search_provider", value: prefs.search_provider });
162
164
  if (prefs?.context_selection) toggleRows.push({ label: "context_selection", value: prefs.context_selection });
163
- if (prefs?.widget_mode && prefs.widget_mode !== "full") toggleRows.push({ label: "widget_mode", value: prefs.widget_mode });
165
+ if (prefs?.widget_mode && prefs.widget_mode !== DEFAULT_WIDGET_MODE) toggleRows.push({ label: "widget_mode", value: prefs.widget_mode });
164
166
  if (prefs?.experimental?.rtk) toggleRows.push({ label: "experimental.rtk", value: "on" });
165
167
  if (toggleRows.length > 0) sections.push({ title: "Toggles", rows: toggleRows });
166
168
 
@@ -71,6 +71,14 @@ export class GSDDashboardOverlay {
71
71
  private refreshInFlight: Promise<void> | null = null;
72
72
  private disposed = false;
73
73
  private resizeHandler: (() => void) | null = null;
74
+ private cachedMetrics: {
75
+ totals: ReturnType<typeof getProjectTotals>;
76
+ promptStats: ReturnType<typeof getPromptSizeStats>;
77
+ phases: ReturnType<typeof aggregateByPhase>;
78
+ slices: ReturnType<typeof aggregateBySlice>;
79
+ models: ReturnType<typeof aggregateByModel>;
80
+ } | null = null;
81
+ private lastSeenUnitCount = -1;
74
82
 
75
83
  constructor(
76
84
  tui: { requestRender: () => void },
@@ -123,7 +131,8 @@ export class GSDDashboardOverlay {
123
131
  this.dashData = getAutoDashboardData();
124
132
  const nextIdentity = this.computeDashboardIdentity(this.dashData);
125
133
 
126
- if (initial || nextIdentity !== this.loadedDashboardIdentity) {
134
+ const identityChanged = initial || nextIdentity !== this.loadedDashboardIdentity;
135
+ if (identityChanged) {
127
136
  const loaded = await this.loadData();
128
137
  if (this.disposed) return;
129
138
  if (loaded) {
@@ -135,7 +144,9 @@ export class GSDDashboardOverlay {
135
144
  this.loading = false;
136
145
  }
137
146
 
138
- this.invalidate();
147
+ if (identityChanged) {
148
+ this.invalidate();
149
+ }
139
150
  this.tui.requestRender();
140
151
  }
141
152
 
@@ -459,7 +470,7 @@ export class GSDDashboardOverlay {
459
470
 
460
471
  const ledger = getLedger();
461
472
  if (ledger && ledger.units.length > 0) {
462
- const totals = getProjectTotals(ledger.units);
473
+ const { totals, promptStats, phases, slices, models } = this.ensureMetricsCache(ledger.units);
463
474
 
464
475
  lines.push(blank());
465
476
  lines.push(hr());
@@ -496,7 +507,6 @@ export class GSDDashboardOverlay {
496
507
  lines.push(row(budgetParts.join(` ${th.fg("dim", "·")} `)));
497
508
  }
498
509
 
499
- const promptStats = getPromptSizeStats(ledger.units);
500
510
  if (promptStats) {
501
511
  const promptParts = [
502
512
  `${th.fg("dim", "avg prompt:")} ${th.fg("text", formatCharCount(promptStats.averagePromptChars))}`,
@@ -508,7 +518,6 @@ export class GSDDashboardOverlay {
508
518
  lines.push(row(promptParts.join(` ${th.fg("dim", "·")} `)));
509
519
  }
510
520
 
511
- const phases = aggregateByPhase(ledger.units);
512
521
  if (phases.length > 0) {
513
522
  lines.push(blank());
514
523
  lines.push(row(th.fg("dim", "By Phase")));
@@ -520,7 +529,6 @@ export class GSDDashboardOverlay {
520
529
  }
521
530
  }
522
531
 
523
- const slices = aggregateBySlice(ledger.units);
524
532
  if (slices.length > 0) {
525
533
  lines.push(blank());
526
534
  lines.push(row(th.fg("dim", "By Slice")));
@@ -551,7 +559,6 @@ export class GSDDashboardOverlay {
551
559
  }
552
560
  }
553
561
 
554
- const models = aggregateByModel(ledger.units);
555
562
  if (models.length >= 1) {
556
563
  lines.push(blank());
557
564
  lines.push(row(th.fg("dim", "By Model")));
@@ -625,6 +632,20 @@ export class GSDDashboardOverlay {
625
632
  return `${th.fg("dim", labelText)}${" ".repeat(gap)}${bar}${" ".repeat(gap)}${th.fg("dim", rightText)}`;
626
633
  }
627
634
 
635
+ private ensureMetricsCache(units: UnitMetrics[]) {
636
+ if (!this.cachedMetrics || units.length !== this.lastSeenUnitCount) {
637
+ this.cachedMetrics = {
638
+ totals: getProjectTotals(units),
639
+ promptStats: getPromptSizeStats(units),
640
+ phases: aggregateByPhase(units),
641
+ slices: aggregateBySlice(units),
642
+ models: aggregateByModel(units),
643
+ };
644
+ this.lastSeenUnitCount = units.length;
645
+ }
646
+ return this.cachedMetrics!;
647
+ }
648
+
628
649
  invalidate(): void {
629
650
  this.cachedWidth = undefined;
630
651
  this.cachedLines = undefined;
@@ -305,10 +305,18 @@ This config sets a parent workspace with two child repositories. The implicit `p
305
305
  - `max_cycles`: number — max times this hook fires per trigger (default: 1, max: 10).
306
306
  - `model`: string — optional model override.
307
307
  - `artifact`: string — expected output file name (relative to task/slice dir). Hook is skipped if file already exists (idempotent).
308
+ - `criticality`: `"advisory"` or `"blocking"` — advisory preserves current best-effort behavior; blocking requires clean hook completion plus a valid outcome verdict before auto-mode advances. Default: `"advisory"`.
308
309
  - `retry_on`: string — if this file is produced instead of the artifact, re-run the trigger unit then re-run hooks.
310
+ - `on_block`: object — optional routing for blocking findings:
311
+ - `action`: `"retry-unit"`, `"retry-task"`, `"queue-task"`, `"queue-slice"`, or `"pause"`.
312
+ - `artifact`: string — optional compatibility artifact for retry routing.
309
313
  - `agent`: string — agent definition file to use for hook execution.
310
314
  - `enabled`: boolean — toggle without removing (default: `true`).
311
315
 
316
+ Blocking hook artifacts must begin with YAML frontmatter containing either `verdict` or `outcome.verdict`.
317
+ Supported verdicts are `pass`, `advisory`, `needs-rework`, `needs-remediation`, and `needs-attention`.
318
+ `pass` and `advisory` continue; `needs-rework` retries the trigger unit when routed with `retry-unit`/`retry-task`; `needs-remediation` and `needs-attention` pause with recovery guidance.
319
+
312
320
  - `pre_dispatch_hooks`: array — hooks that fire before a unit is dispatched. Each entry has:
313
321
  - `name`: string — unique hook identifier.
314
322
  - `before`: string[] — unit types to intercept.
@@ -269,14 +269,14 @@ export async function checkRuntimeHealth(
269
269
  } catch {
270
270
  count = MAX_UAT_ATTEMPTS + 1;
271
271
  }
272
- if (count <= MAX_UAT_ATTEMPTS) continue;
272
+ if (count < MAX_UAT_ATTEMPTS) continue;
273
273
 
274
274
  issues.push({
275
275
  severity: "warning",
276
276
  code: "uat_retry_exhausted",
277
277
  scope: "slice",
278
278
  unitId: `${mid}/${sid}`,
279
- message: `run-uat for ${mid}/${sid} exhausted ${count - 1} retry attempt(s) without an ASSESSMENT verdict. Reset the retry counter after fixing the underlying UAT/tool issue, then rerun /gsd auto.`,
279
+ message: `run-uat for ${mid}/${sid} exhausted ${count} attempt(s) without an ASSESSMENT verdict. Reset the retry counter after fixing the underlying UAT/tool issue, then rerun /gsd auto.`,
280
280
  file: `.gsd/runtime/${fileName}`,
281
281
  fixable: true,
282
282
  });
@@ -47,9 +47,10 @@ export function resetRetryState(state: RetryState): void {
47
47
  const PERMANENT_RE = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|billing|quota exceeded|account/i;
48
48
  // Include provider-specific quota-window phrasing like:
49
49
  // - "You've hit your limit"
50
+ // - "You've reached your limit"
50
51
  // - "usage limit" / "quota reached"
51
52
  // - "out of extra usage"
52
- const RATE_LIMIT_RE = /rate.?limit|too many requests|429|hit your limit|usage limit|out of extra usage|quota (?:reached|hit)|limit.*resets?/i;
53
+ const RATE_LIMIT_RE = /rate.?limit|too many requests|429|(?:hit|reached) your (?:\w+ )?limit|(?:usage|session|weekly|daily|monthly|quota) limit|out of extra usage|quota (?:reached|hit)|limit.*resets?/i;
53
54
  // OpenRouter affordability-style quota errors should be treated as transient
54
55
  // so core retry logic can lower maxTokens and continue in-session.
55
56
  const AFFORDABILITY_RE = /requires more credits|can only afford|insufficient credits|not enough credits|fewer max_tokens/i;
@@ -159,13 +159,13 @@ export function readEscalationArtifact(path: string): EscalationArtifact | null
159
159
  // ─── Detection ────────────────────────────────────────────────────────────
160
160
 
161
161
  /**
162
- * Returns the task id of the first task with an un-resolved pause-escalation
163
- * (escalation_pending=1, not yet respondedAt). awaiting_review slices are NOT
164
- * returned they don't pause the loop.
162
+ * Returns the task id of the first task with an unresolved escalation.
163
+ * `continueWithDefault=true` artifacts keep the awaiting_review flag for
164
+ * compatibility, but still pause dispatch until the user explicitly responds.
165
165
  */
166
166
  export function detectPendingEscalation(tasks: TaskRow[], basePath: string): string | null {
167
167
  for (const t of tasks) {
168
- if (t.escalation_pending !== 1) continue;
168
+ if (t.escalation_pending !== 1 && t.escalation_awaiting_review !== 1) continue;
169
169
  if (!t.escalation_artifact_path) continue;
170
170
  const art = readEscalationArtifact(t.escalation_artifact_path);
171
171
  if (art && !art.respondedAt) return t.id;
@@ -20,6 +20,8 @@ export interface ExecSandboxRequest {
20
20
  script: string;
21
21
  /** Optional purpose/label recorded in meta.json. */
22
22
  purpose?: string;
23
+ /** Optional structured metadata recorded in meta.json. */
24
+ metadata?: Record<string, unknown>;
23
25
  /** Per-invocation timeout in ms. Clamped to `clamp_timeout_ms`. */
24
26
  timeout_ms?: number;
25
27
  }
@@ -315,6 +317,7 @@ function writeMeta(
315
317
  id: result.id,
316
318
  runtime: result.runtime,
317
319
  purpose: request.purpose ?? null,
320
+ ...(request.metadata ? { metadata: request.metadata } : {}),
318
321
  script_chars: request.script.length,
319
322
  started_at: now.toISOString(),
320
323
  finished_at: new Date(now.getTime() + result.duration_ms).toISOString(),
@@ -328,6 +331,7 @@ function writeMeta(
328
331
  stderr_truncated: result.stderr_truncated,
329
332
  stdout_path: result.stdout_path,
330
333
  stderr_path: result.stderr_path,
334
+ ...(request.metadata ? { metadata: request.metadata } : {}),
331
335
  };
332
336
  writeFileSync(path, `${JSON.stringify(meta, null, 2)}\n`);
333
337
  }
@@ -123,6 +123,91 @@ interface ForensicReport {
123
123
  worktreeTelemetry: WorktreeTelemetrySummary | null;
124
124
  }
125
125
 
126
+ // ─── Filing Tool Scope ───────────────────────────────────────────────────────
127
+
128
+ const FORENSICS_FILING_TOOLS = ["bash", "write"] as const;
129
+
130
+ type ForensicsFilingTool = typeof FORENSICS_FILING_TOOLS[number];
131
+
132
+ export interface ForensicsToolScope {
133
+ savedTools: string[];
134
+ activeToolsForTurn: string[];
135
+ availableFilingTools: ForensicsFilingTool[];
136
+ missingFilingTools: ForensicsFilingTool[];
137
+ toolsChanged: boolean;
138
+ }
139
+
140
+ function uniqueAppend(base: readonly string[], additions: readonly string[]): string[] {
141
+ const seen = new Set(base);
142
+ const next = [...base];
143
+ for (const name of additions) {
144
+ if (seen.has(name)) continue;
145
+ seen.add(name);
146
+ next.push(name);
147
+ }
148
+ return next;
149
+ }
150
+
151
+ function sameOrderedTools(a: readonly string[], b: readonly string[]): boolean {
152
+ return a.length === b.length && a.every((name, index) => name === b[index]);
153
+ }
154
+
155
+ function getRegisteredToolNames(
156
+ pi: Pick<ExtensionAPI, "getActiveTools"> & Partial<Pick<ExtensionAPI, "getAllTools">>,
157
+ fallback: readonly string[],
158
+ ): string[] {
159
+ if (typeof pi.getAllTools === "function") {
160
+ return pi.getAllTools().map((tool) => tool.name);
161
+ }
162
+ return [...fallback];
163
+ }
164
+
165
+ export function createForensicsToolScope(
166
+ pi: Pick<ExtensionAPI, "getActiveTools"> & Partial<Pick<ExtensionAPI, "getAllTools">>,
167
+ ): ForensicsToolScope {
168
+ const savedTools = [...pi.getActiveTools()];
169
+ const registeredTools = new Set([...getRegisteredToolNames(pi, savedTools), ...savedTools]);
170
+ const availableFilingTools = FORENSICS_FILING_TOOLS.filter((name) => registeredTools.has(name));
171
+ const missingFilingTools = FORENSICS_FILING_TOOLS.filter((name) => !registeredTools.has(name));
172
+ const activeToolsForTurn = uniqueAppend(savedTools, availableFilingTools);
173
+
174
+ return {
175
+ savedTools,
176
+ activeToolsForTurn,
177
+ availableFilingTools,
178
+ missingFilingTools,
179
+ toolsChanged: !sameOrderedTools(savedTools, activeToolsForTurn),
180
+ };
181
+ }
182
+
183
+ export function applyForensicsToolScope(pi: Pick<ExtensionAPI, "setActiveTools">, scope: ForensicsToolScope): void {
184
+ if (scope.toolsChanged) pi.setActiveTools(scope.activeToolsForTurn);
185
+ }
186
+
187
+ export function restoreForensicsToolScope(pi: Pick<ExtensionAPI, "setActiveTools">, scope: ForensicsToolScope): void {
188
+ if (scope.toolsChanged) pi.setActiveTools(scope.savedTools);
189
+ }
190
+
191
+ export function buildForensicsToolingSection(scope: ForensicsToolScope): string {
192
+ const available = new Set(scope.availableFilingTools);
193
+ const requested = scope.availableFilingTools.length
194
+ ? scope.availableFilingTools.map((name) => `\`${name}\``).join(", ")
195
+ : "none";
196
+ const statusFor = (name: ForensicsFilingTool) => available.has(name)
197
+ ? `- \`${name}\`: available for this queued forensics turn`
198
+ : `- \`${name}\`: unavailable in this host session`;
199
+
200
+ return `
201
+ ## Filing Tool Availability
202
+
203
+ For this queued forensic turn, the extension requested the registered filing tools: ${requested}.
204
+
205
+ ${FORENSICS_FILING_TOOLS.map(statusFor).join("\n")}
206
+
207
+ If \`bash\` is available, use the GitHub duplicate-check and issue-creation protocols below. If \`bash\` is unavailable, do not attempt duplicate-check or issue-creation tool calls with another tool; provide the paste-once shell script fallback instead.
208
+ `;
209
+ }
210
+
126
211
  // ─── Duplicate Detection ──────────────────────────────────────────────────────
127
212
 
128
213
  const DEDUP_PROMPT_SECTION = `
@@ -134,6 +219,8 @@ Before reading GSD source code or performing deep analysis, you MUST search for
134
219
 
135
220
  Use keywords from the user's problem description and the anomaly summaries in the forensic report above.
136
221
 
222
+ If \`bash\` is unavailable in the Filing Tool Availability section, do not attempt live duplicate-search tool calls. Say the live duplicate search must be run by the user, continue the source investigation, and include the duplicate-search commands in the paste-once fallback when issue filing is accepted.
223
+
137
224
  1. **Search closed issues** for similar keywords:
138
225
  \`\`\`
139
226
  gh issue list --repo open-gsd/gsd-pi --state closed --search "<keywords from root cause>" --limit 20
@@ -265,21 +352,28 @@ export async function handleForensics(
265
352
  }
266
353
 
267
354
  const forensicData = formatReportForPrompt(report);
355
+ const toolScope = createForensicsToolScope(pi);
268
356
  const content = loadPrompt("forensics", {
269
357
  problemDescription,
270
358
  forensicData,
271
359
  gsdSourceDir,
272
360
  dedupSection,
361
+ toolingSection: buildForensicsToolingSection(toolScope),
273
362
  });
274
363
 
275
364
  ctx.ui.notify(`Forensic report saved: ${relative(basePath, savedPath)}`, "info");
276
365
  ctx.ui.setStatus("gsd-forensics", "running");
277
366
 
278
- pi.sendMessage(
279
- { customType: "gsd-forensics", content, display: false },
280
- { triggerTurn: true },
281
- );
282
- ctx.ui.setStatus("gsd-forensics", undefined);
367
+ try {
368
+ applyForensicsToolScope(pi, toolScope);
369
+ await pi.sendMessage(
370
+ { customType: "gsd-forensics", content, display: false },
371
+ { triggerTurn: true },
372
+ );
373
+ } finally {
374
+ restoreForensicsToolScope(pi, toolScope);
375
+ ctx.ui.setStatus("gsd-forensics", undefined);
376
+ }
283
377
 
284
378
  // Persist forensics context so follow-up turns can re-inject it (#2941)
285
379
  writeForensicsMarker(basePath, savedPath, content);