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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/dist/cli.js +3 -2
  2. package/dist/help-text.js +10 -6
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +495 -0
  5. package/dist/resources/extensions/browser-tools/engine/selection.js +16 -0
  6. package/dist/resources/extensions/browser-tools/extension-manifest.json +2 -2
  7. package/dist/resources/extensions/browser-tools/index.js +57 -9
  8. package/dist/resources/extensions/browser-tools/package.json +5 -1
  9. package/dist/resources/extensions/gsd/auto/orchestrator.js +0 -1
  10. package/dist/resources/extensions/gsd/auto-dashboard.js +77 -13
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
  12. package/dist/resources/extensions/gsd/auto-post-unit.js +21 -3
  13. package/dist/resources/extensions/gsd/auto-prompts.js +63 -22
  14. package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
  15. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  16. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  17. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
  18. package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
  19. package/dist/resources/extensions/gsd/auto.js +9 -2
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +20 -14
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +28 -13
  22. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
  23. package/dist/resources/extensions/gsd/browser-evidence.js +29 -2
  24. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
  25. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -2
  26. package/dist/resources/extensions/gsd/commands-handlers.js +76 -11
  27. package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -1
  28. package/dist/resources/extensions/gsd/dashboard-overlay.js +21 -7
  29. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  30. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +2 -2
  31. package/dist/resources/extensions/gsd/escalation.js +4 -4
  32. package/dist/resources/extensions/gsd/forensics.js +74 -2
  33. package/dist/resources/extensions/gsd/gsd-db.js +5 -2
  34. package/dist/resources/extensions/gsd/guided-flow.js +118 -175
  35. package/dist/resources/extensions/gsd/mcp-project-config.js +9 -76
  36. package/dist/resources/extensions/gsd/memory-store.js +4 -1
  37. package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
  38. package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
  39. package/dist/resources/extensions/gsd/post-unit-hooks.js +9 -0
  40. package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
  41. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  42. package/dist/resources/extensions/gsd/prompts/forensics.md +61 -1
  43. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  44. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  45. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  47. package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -21
  48. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  49. package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
  50. package/dist/resources/extensions/gsd/rule-registry.js +428 -52
  51. package/dist/resources/extensions/gsd/state.js +2 -2
  52. package/dist/resources/extensions/gsd/templates/plan.md +3 -1
  53. package/dist/resources/extensions/gsd/tool-contract.js +5 -0
  54. package/dist/resources/extensions/gsd/tool-presentation-plan.js +30 -7
  55. package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -1
  56. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -1
  57. package/dist/resources/extensions/gsd/tools/validate-milestone.js +46 -16
  58. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +132 -18
  59. package/dist/resources/extensions/gsd/unit-tool-contracts.js +169 -0
  60. package/dist/resources/extensions/gsd/verdict-parser.js +59 -15
  61. package/dist/resources/extensions/gsd/verification-gate.js +72 -1
  62. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -75
  63. package/dist/resources/extensions/shared/gsd-browser-cli.js +145 -0
  64. package/dist/rtk.d.ts +7 -1
  65. package/dist/rtk.js +27 -11
  66. package/dist/update-check.d.ts +15 -1
  67. package/dist/update-check.js +87 -12
  68. package/dist/update-cmd.d.ts +1 -0
  69. package/dist/update-cmd.js +53 -2
  70. package/dist/web/standalone/.next/BUILD_ID +1 -1
  71. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  72. package/dist/web/standalone/.next/build-manifest.json +2 -2
  73. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  74. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/index.html +1 -1
  92. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  99. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  100. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  102. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  103. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  104. package/package.json +4 -2
  105. package/packages/cloud-mcp-gateway/package.json +2 -2
  106. package/packages/contracts/package.json +1 -1
  107. package/packages/daemon/package.json +4 -4
  108. package/packages/gsd-agent-core/dist/agent-session.d.ts +9 -0
  109. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  110. package/packages/gsd-agent-core/dist/agent-session.js +32 -0
  111. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  112. package/packages/gsd-agent-core/dist/index.d.ts +1 -0
  113. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  114. package/packages/gsd-agent-core/dist/index.js +1 -0
  115. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  116. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts +2 -0
  117. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts.map +1 -1
  118. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js +8 -2
  119. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js.map +1 -1
  120. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +7 -0
  121. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  122. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  123. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  124. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +69 -1
  125. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  126. package/packages/gsd-agent-core/dist/turn-latency.d.ts +47 -0
  127. package/packages/gsd-agent-core/dist/turn-latency.d.ts.map +1 -0
  128. package/packages/gsd-agent-core/dist/turn-latency.js +123 -0
  129. package/packages/gsd-agent-core/dist/turn-latency.js.map +1 -0
  130. package/packages/gsd-agent-core/package.json +6 -6
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +21 -0
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +1 -0
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +213 -0
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +1 -0
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  138. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +20 -0
  140. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  141. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  142. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +7 -1
  143. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  144. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.d.ts.map +1 -1
  145. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js +6 -0
  146. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js.map +1 -1
  147. package/packages/gsd-agent-modes/package.json +7 -7
  148. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  149. package/packages/mcp-server/dist/remote-questions.js +23 -9
  150. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  151. package/packages/mcp-server/dist/workflow-tools.js +2 -2
  152. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  153. package/packages/mcp-server/package.json +3 -3
  154. package/packages/native/package.json +1 -1
  155. package/packages/pi-agent-core/dist/agent-loop.js +42 -3
  156. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  157. package/packages/pi-agent-core/dist/agent.d.ts +5 -1
  158. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  159. package/packages/pi-agent-core/dist/agent.js +2 -0
  160. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  161. package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  162. package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
  163. package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  164. package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
  165. package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  166. package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
  167. package/packages/pi-agent-core/dist/types.d.ts +6 -1
  168. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  169. package/packages/pi-agent-core/dist/types.js.map +1 -1
  170. package/packages/pi-agent-core/package.json +1 -1
  171. package/packages/pi-ai/dist/api-registry.d.ts +2 -0
  172. package/packages/pi-ai/dist/api-registry.d.ts.map +1 -1
  173. package/packages/pi-ai/dist/api-registry.js +23 -0
  174. package/packages/pi-ai/dist/api-registry.js.map +1 -1
  175. package/packages/pi-ai/dist/models.generated.d.ts +74 -23
  176. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  177. package/packages/pi-ai/dist/models.generated.js +82 -31
  178. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  179. package/packages/pi-ai/dist/stream.js +6 -6
  180. package/packages/pi-ai/dist/stream.js.map +1 -1
  181. package/packages/pi-ai/package.json +1 -1
  182. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
  183. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -2
  187. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
  189. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
  192. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  193. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
  194. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
  196. package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
  199. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  200. package/packages/pi-coding-agent/package.json +7 -7
  201. package/packages/pi-tui/package.json +1 -1
  202. package/packages/rpc-client/package.json +2 -2
  203. package/pkg/package.json +1 -1
  204. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +579 -0
  205. package/src/resources/extensions/browser-tools/engine/selection.ts +19 -0
  206. package/src/resources/extensions/browser-tools/extension-manifest.json +2 -2
  207. package/src/resources/extensions/browser-tools/index.ts +60 -9
  208. package/src/resources/extensions/browser-tools/package.json +5 -1
  209. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +35 -0
  210. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +33 -0
  211. package/src/resources/extensions/gsd/auto/orchestrator.ts +0 -1
  212. package/src/resources/extensions/gsd/auto-dashboard.ts +82 -14
  213. package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
  214. package/src/resources/extensions/gsd/auto-post-unit.ts +28 -2
  215. package/src/resources/extensions/gsd/auto-prompts.ts +97 -15
  216. package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
  217. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
  218. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  219. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
  220. package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
  221. package/src/resources/extensions/gsd/auto.ts +12 -2
  222. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +20 -14
  223. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +32 -13
  224. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
  225. package/src/resources/extensions/gsd/browser-evidence.ts +26 -2
  226. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
  227. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -2
  228. package/src/resources/extensions/gsd/commands-handlers.ts +76 -11
  229. package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -1
  230. package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -7
  231. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  232. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +2 -2
  233. package/src/resources/extensions/gsd/escalation.ts +4 -4
  234. package/src/resources/extensions/gsd/forensics.ts +99 -5
  235. package/src/resources/extensions/gsd/gsd-db.ts +5 -2
  236. package/src/resources/extensions/gsd/guided-flow.ts +214 -216
  237. package/src/resources/extensions/gsd/mcp-project-config.ts +13 -78
  238. package/src/resources/extensions/gsd/memory-store.ts +4 -1
  239. package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
  240. package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
  241. package/src/resources/extensions/gsd/post-unit-hooks.ts +14 -1
  242. package/src/resources/extensions/gsd/preferences-validation.ts +36 -0
  243. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  244. package/src/resources/extensions/gsd/prompts/forensics.md +61 -1
  245. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  246. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  247. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  248. package/src/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  249. package/src/resources/extensions/gsd/prompts/run-uat.md +25 -21
  250. package/src/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  251. package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
  252. package/src/resources/extensions/gsd/rule-registry.ts +558 -58
  253. package/src/resources/extensions/gsd/rule-types.ts +2 -0
  254. package/src/resources/extensions/gsd/state.ts +2 -2
  255. package/src/resources/extensions/gsd/templates/plan.md +3 -1
  256. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +105 -4
  257. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +37 -0
  258. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
  259. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
  260. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
  261. package/src/resources/extensions/gsd/tests/browser-evidence.test.ts +142 -0
  262. package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
  263. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
  264. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
  265. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +30 -0
  266. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
  267. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +45 -0
  268. package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +53 -0
  269. package/src/resources/extensions/gsd/tests/discuss-milestone-structured-questions.test.ts +31 -0
  270. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
  271. package/src/resources/extensions/gsd/tests/doctor-runtime-checks.test.ts +27 -0
  272. package/src/resources/extensions/gsd/tests/escalation.test.ts +16 -27
  273. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +20 -0
  274. package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +3 -0
  275. package/src/resources/extensions/gsd/tests/forensics-tool-scope.test.ts +69 -0
  276. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
  277. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +40 -1
  278. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +86 -0
  279. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
  280. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
  281. package/src/resources/extensions/gsd/tests/guided-flow.test.ts +12 -9
  282. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +4 -4
  283. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
  284. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
  285. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +72 -10
  286. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +32 -0
  287. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +2 -0
  288. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +39 -8
  289. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
  290. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
  291. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
  292. package/src/resources/extensions/gsd/tests/new-milestone-discuss-routing.test.ts +3 -3
  293. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +9 -0
  294. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +157 -0
  295. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +179 -0
  296. package/src/resources/extensions/gsd/tests/preferences.test.ts +29 -0
  297. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +73 -1
  298. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +14 -0
  299. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +7 -8
  300. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
  301. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +75 -0
  302. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
  303. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +36 -0
  304. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +100 -0
  305. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +139 -0
  306. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
  307. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
  308. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +7 -1
  309. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +6 -3
  310. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +133 -0
  311. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +51 -0
  312. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +410 -0
  313. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
  314. package/src/resources/extensions/gsd/tool-contract.ts +6 -0
  315. package/src/resources/extensions/gsd/tool-presentation-plan.ts +63 -7
  316. package/src/resources/extensions/gsd/tools/complete-slice.ts +14 -1
  317. package/src/resources/extensions/gsd/tools/complete-task.ts +20 -2
  318. package/src/resources/extensions/gsd/tools/validate-milestone.ts +46 -15
  319. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +163 -20
  320. package/src/resources/extensions/gsd/types.ts +69 -5
  321. package/src/resources/extensions/gsd/unit-tool-contracts.ts +186 -0
  322. package/src/resources/extensions/gsd/verdict-parser.ts +54 -13
  323. package/src/resources/extensions/gsd/verification-gate.ts +87 -1
  324. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -75
  325. package/src/resources/extensions/shared/gsd-browser-cli.ts +172 -0
  326. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
  327. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
  328. /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → h4TGni4xJzlZjGkxaT6uU}/_buildManifest.js +0 -0
  329. /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → h4TGni4xJzlZjGkxaT6uU}/_ssgManifest.js +0 -0
@@ -4,7 +4,7 @@ import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
 
5
5
  import { minimatch } from "minimatch";
6
6
 
7
- import { shouldBlockAutoUnitToolCall } from "../auto-unit-tool-scope.js";
7
+ import { GSD_PHASE_SCOPE_DISPLAY_REASON, shouldBlockAutoUnitToolCall } from "../auto-unit-tool-scope.js";
8
8
  import { getIsolationMode } from "../preferences.js";
9
9
  import { compileSubagentPermissionContract, type ToolsPolicy } from "../unit-context-manifest.js";
10
10
  import { logWarning } from "../workflow-logger.js";
@@ -772,6 +772,20 @@ function blockReason(unitType: string, mode: string, what: string): string {
772
772
  ].join(" ");
773
773
  }
774
774
 
775
+ function planningBlock(unitType: string, mode: string, what: string): PlanningUnitBlockResult {
776
+ return {
777
+ block: true,
778
+ reason: blockReason(unitType, mode, what),
779
+ displayReason: GSD_PHASE_SCOPE_DISPLAY_REASON,
780
+ };
781
+ }
782
+
783
+ type PlanningUnitBlockResult = {
784
+ block: boolean;
785
+ reason?: string;
786
+ displayReason?: string;
787
+ };
788
+
775
789
  /**
776
790
  * Planning-unit tool-policy enforcement. Returns { block } per the policy
777
791
  * resolved from the active unit's manifest:
@@ -812,7 +826,7 @@ export function shouldBlockPlanningUnit(
812
826
  agentClasses?: readonly string[],
813
827
  toolInput?: unknown,
814
828
  unitId?: string,
815
- ): { block: boolean; reason?: string } {
829
+ ): PlanningUnitBlockResult {
816
830
  const tool = canonicalToolName(toolName);
817
831
  const autoScopeGuard = shouldBlockAutoUnitToolCall(unitType, toolName, toolInput, unitId);
818
832
  if (autoScopeGuard.block) return autoScopeGuard;
@@ -825,10 +839,10 @@ export function shouldBlockPlanningUnit(
825
839
  if (PLANNING_SAFE_TOOLS.has(tool)) return { block: false };
826
840
  if (tool.startsWith("gsd_")) return { block: false };
827
841
  if (PLANNING_WRITE_TOOLS.has(tool) || tool === "bash" || PLANNING_SUBAGENT_TOOLS.has(tool)) {
828
- return { block: true, reason: blockReason(unitType, policy.mode, `${tool} is not permitted (read-only)`) };
842
+ return planningBlock(unitType, policy.mode, `${tool} is not permitted (read-only)`);
829
843
  }
830
844
  // Unknown tool in read-only mode — block by default.
831
- return { block: true, reason: blockReason(unitType, policy.mode, `tool "${tool}" is not on the read-only allowlist`) };
845
+ return planningBlock(unitType, policy.mode, `tool "${tool}" is not on the read-only allowlist`);
832
846
  }
833
847
 
834
848
  // planning / planning-dispatch / docs / verification modes share the same surface for safe tools, bash, and subagent.
@@ -846,14 +860,11 @@ export function shouldBlockPlanningUnit(
846
860
  // instead of silently bypassing the gate.
847
861
  if (agentClasses === undefined) {
848
862
  warnMissingControlledDispatchAgentClasses(unitType, policy.mode, tool);
849
- return {
850
- block: true,
851
- reason: blockReason(
852
- unitType,
853
- policy.mode,
854
- `subagent dispatch blocked: stale caller did not supply agent identities for "${tool}"; update extractSubagentAgentClasses to handle this input shape`,
855
- ),
856
- };
863
+ return planningBlock(
864
+ unitType,
865
+ policy.mode,
866
+ `subagent dispatch blocked: stale caller did not supply agent identities for "${tool}"; update extractSubagentAgentClasses to handle this input shape`,
867
+ );
857
868
  }
858
869
  // agentClasses was explicitly provided but resolved to an empty list (for
859
870
  // example, a bare tool call with no agent field). Pass through; no agents
@@ -863,57 +874,45 @@ export function shouldBlockPlanningUnit(
863
874
  }
864
875
  const globallyDisallowed = requested.find(a => !isReadOnlySpecialist(a));
865
876
  if (globallyDisallowed) {
866
- return {
867
- block: true,
868
- reason: blockReason(
869
- unitType,
870
- policy.mode,
871
- `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`,
872
- ),
873
- };
877
+ return planningBlock(
878
+ unitType,
879
+ policy.mode,
880
+ `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`,
881
+ );
874
882
  }
875
883
  const disallowedByPolicy = requested.find(a => !allowed.has(a));
876
884
  if (disallowedByPolicy) {
877
- return {
878
- block: true,
879
- reason: blockReason(
880
- unitType,
881
- policy.mode,
882
- `subagent dispatch of "${disallowedByPolicy}" not permitted by ToolsPolicy.allowedSubagents; permitted agents for this unit: ${allowedSubagents.join(", ")}`,
883
- ),
884
- };
885
+ return planningBlock(
886
+ unitType,
887
+ policy.mode,
888
+ `subagent dispatch of "${disallowedByPolicy}" not permitted by ToolsPolicy.allowedSubagents; permitted agents for this unit: ${allowedSubagents.join(", ")}`,
889
+ );
885
890
  }
886
891
  return { block: false };
887
892
  }
888
- return { block: true, reason: blockReason(unitType, policy.mode, `subagent dispatch is not permitted in planning units`) };
893
+ return planningBlock(unitType, policy.mode, "subagent dispatch is not permitted in planning units");
889
894
  }
890
895
 
891
896
  if (tool === "bash") {
892
897
  if (policy.mode === "verification") {
893
898
  if (BASH_VERIFICATION_RE.test(pathOrCommand) || BASH_READ_ONLY_RE.test(pathOrCommand)) return { block: false };
894
- return {
895
- block: true,
896
- reason: blockReason(
897
- unitType,
898
- policy.mode,
899
- `bash is restricted to build/test verification commands (npm run build, npm test, etc.); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`,
900
- ),
901
- };
902
- }
903
- if (BASH_READ_ONLY_RE.test(pathOrCommand)) return { block: false };
904
- return {
905
- block: true,
906
- reason: blockReason(
899
+ return planningBlock(
907
900
  unitType,
908
901
  policy.mode,
909
- `bash is restricted to read-only commands (cat/grep/git log/etc); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`,
910
- ),
911
- };
902
+ `bash is restricted to build/test verification commands (npm run build, npm test, etc.); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`,
903
+ );
904
+ }
905
+ if (BASH_READ_ONLY_RE.test(pathOrCommand)) return { block: false };
906
+ return planningBlock(
907
+ unitType,
908
+ policy.mode,
909
+ `bash is restricted to read-only commands (cat/grep/git log/etc); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`,
910
+ );
912
911
  }
913
912
 
914
913
  if (PLANNING_WRITE_TOOLS.has(tool)) {
915
914
  if (!pathOrCommand) {
916
- return { block: true, reason: blockReason(unitType, policy.mode, `${tool} called with empty path`) };
915
+ return planningBlock(unitType, policy.mode, `${tool} called with empty path`);
917
916
  }
918
917
  const absPath = isAbsolute(pathOrCommand) ? pathOrCommand : resolve(basePath, pathOrCommand);
919
918
 
@@ -925,14 +924,11 @@ export function shouldBlockPlanningUnit(
925
924
  return { block: false };
926
925
  }
927
926
 
928
- return {
929
- block: true,
930
- reason: blockReason(
931
- unitType,
932
- policy.mode,
933
- `cannot ${tool} "${pathOrCommand}" — writes are restricted to .gsd/${policy.mode === "docs" ? " and " + policy.allowedPathGlobs.join(", ") : ""}`,
934
- ),
935
- };
927
+ return planningBlock(
928
+ unitType,
929
+ policy.mode,
930
+ `cannot ${tool} "${pathOrCommand}" — writes are restricted to .gsd/${policy.mode === "docs" ? " and " + policy.allowedPathGlobs.join(", ") : ""}`,
931
+ );
936
932
  }
937
933
 
938
934
  // Unknown tool name — pass through. Other layers (queue, pending-gate,
@@ -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 {
@@ -0,0 +1,137 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Shared DB-backed guard for milestone closeout finalization.
3
+
4
+ import {
5
+ getDbPath,
6
+ getLatestAssessmentByScope,
7
+ getMilestone,
8
+ getMilestoneSlices,
9
+ getPendingGates,
10
+ getSliceTasks,
11
+ isDbAvailable,
12
+ refreshOpenDatabaseFromDisk,
13
+ } from "./gsd-db.js";
14
+ import { isClosedStatus } from "./status-guards.js";
15
+
16
+ export const CLOSEOUT_CONSISTENCY_BLOCKED_REASON = "closeout-consistency-blocked";
17
+
18
+ export type CloseoutConsistencyFailureReason =
19
+ | "db-unavailable"
20
+ | "db-refresh-failed"
21
+ | "milestone-missing"
22
+ | "milestone-open"
23
+ | "validation-not-pass"
24
+ | "slice-missing"
25
+ | "slice-open"
26
+ | "task-open"
27
+ | "quality-gate-pending";
28
+
29
+ export type CloseoutConsistencyResult =
30
+ | { ok: true }
31
+ | {
32
+ ok: false;
33
+ reason: CloseoutConsistencyFailureReason;
34
+ recoveryReason: typeof CLOSEOUT_CONSISTENCY_BLOCKED_REASON;
35
+ message: string;
36
+ };
37
+
38
+ export interface CloseoutConsistencyOptions {
39
+ refreshFromDisk?: boolean;
40
+ }
41
+
42
+ function blocked(reason: CloseoutConsistencyFailureReason, message: string): CloseoutConsistencyResult {
43
+ return {
44
+ ok: false,
45
+ reason,
46
+ recoveryReason: CLOSEOUT_CONSISTENCY_BLOCKED_REASON,
47
+ message,
48
+ };
49
+ }
50
+
51
+ function isFileBackedDbPath(path: string | null): boolean {
52
+ return Boolean(path && path !== ":memory:");
53
+ }
54
+
55
+ export function checkCloseoutConsistencyGate(
56
+ milestoneId: string,
57
+ options: CloseoutConsistencyOptions = {},
58
+ ): CloseoutConsistencyResult {
59
+ if (!isDbAvailable()) {
60
+ return blocked(
61
+ "db-unavailable",
62
+ `Closeout consistency blocked for ${milestoneId}: canonical DB is unavailable.`,
63
+ );
64
+ }
65
+
66
+ if (options.refreshFromDisk && isFileBackedDbPath(getDbPath()) && !refreshOpenDatabaseFromDisk()) {
67
+ return blocked(
68
+ "db-refresh-failed",
69
+ `Closeout consistency blocked for ${milestoneId}: canonical DB refresh failed.`,
70
+ );
71
+ }
72
+
73
+ const milestone = getMilestone(milestoneId);
74
+ if (!milestone) {
75
+ return blocked(
76
+ "milestone-missing",
77
+ `Closeout consistency blocked for ${milestoneId}: milestone is missing from canonical DB.`,
78
+ );
79
+ }
80
+ if (!isClosedStatus(milestone.status)) {
81
+ return blocked(
82
+ "milestone-open",
83
+ `Closeout consistency blocked for ${milestoneId}: canonical DB milestone status is "${milestone.status}".`,
84
+ );
85
+ }
86
+
87
+ if (milestone.status !== "skipped") {
88
+ const validation = getLatestAssessmentByScope(milestoneId, "milestone-validation");
89
+ if (validation?.status !== "pass") {
90
+ return blocked(
91
+ "validation-not-pass",
92
+ `Closeout consistency blocked for ${milestoneId}: latest milestone validation is "${validation?.status ?? "absent"}".`,
93
+ );
94
+ }
95
+ }
96
+
97
+ const slices = getMilestoneSlices(milestoneId);
98
+ if (slices.length === 0 && milestone.status !== "skipped") {
99
+ return blocked(
100
+ "slice-missing",
101
+ `Closeout consistency blocked for ${milestoneId}: no slices exist in canonical DB.`,
102
+ );
103
+ }
104
+
105
+ for (const slice of slices) {
106
+ if (!isClosedStatus(slice.status)) {
107
+ return blocked(
108
+ "slice-open",
109
+ `Closeout consistency blocked for ${milestoneId}: slice ${slice.id} status is "${slice.status}".`,
110
+ );
111
+ }
112
+
113
+ for (const task of getSliceTasks(milestoneId, slice.id)) {
114
+ if (!isClosedStatus(task.status)) {
115
+ return blocked(
116
+ "task-open",
117
+ `Closeout consistency blocked for ${milestoneId}: task ${slice.id}/${task.id} status is "${task.status}".`,
118
+ );
119
+ }
120
+ }
121
+
122
+ const pendingGate = getPendingGates(milestoneId, slice.id)[0];
123
+ if (pendingGate) {
124
+ return blocked(
125
+ "quality-gate-pending",
126
+ `Closeout consistency blocked for ${milestoneId}: quality gate ${pendingGate.gate_id} is still pending for ${slice.id}.`,
127
+ );
128
+ }
129
+ }
130
+
131
+ return { ok: true };
132
+ }
133
+
134
+ export function formatCloseoutConsistencyBlock(result: CloseoutConsistencyResult): string {
135
+ if (result.ok) return "";
136
+ return `${result.message} Recovery reason: ${result.recoveryReason}. Resolve the canonical DB state and run /gsd auto to retry.`;
137
+ }
@@ -282,8 +282,8 @@ Examples:
282
282
  await handleInspect(ctx);
283
283
  return true;
284
284
  }
285
- if (trimmed === "update" || trimmed === "upgrade") {
286
- await handleUpdate(ctx);
285
+ if (trimmed === "update" || trimmed.startsWith("update ") || trimmed === "upgrade" || trimmed.startsWith("upgrade ")) {
286
+ await handleUpdate(ctx, trimmed.replace(/^(?:update|upgrade)\s*/, "").trim());
287
287
  return true;
288
288
  }
289
289
  if (trimmed === "fast" || trimmed.startsWith("fast ")) {
@@ -7,6 +7,8 @@
7
7
 
8
8
  import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
9
9
  import { existsSync, readFileSync, mkdirSync } from "node:fs";
10
+ import { execFileSync } from "node:child_process";
11
+ import { createRequire } from "node:module";
10
12
  import { join, resolve as resolvePath, sep } from "node:path";
11
13
  import { homedir } from "node:os";
12
14
  import { deriveState } from "./state.js";
@@ -37,7 +39,10 @@ import {
37
39
  scopeGsdWorkflowToolsForDispatch,
38
40
  } from "./bootstrap/register-hooks.js";
39
41
 
42
+ const GSD_PI_PACKAGE = "@opengsd/gsd-pi";
43
+ const GSD_BROWSER_PACKAGE = "@opengsd/gsd-browser";
40
44
  const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/@opengsd%2fgsd-pi/latest";
45
+ const BROWSER_UPDATE_REGISTRY_URL = "https://registry.npmjs.org/@opengsd%2fgsd-browser/latest";
41
46
  const UPDATE_FETCH_TIMEOUT_MS = 5000;
42
47
 
43
48
  // Detects a bun-installed gsd via `process.argv[1]`. Mirrors isBunInstall in
@@ -62,12 +67,12 @@ function resolveInstallCommand(pkg: string): string {
62
67
  return `npm install -g ${pkg}`;
63
68
  }
64
69
 
65
- async function fetchLatestVersionForCommand(): Promise<string | null> {
70
+ async function fetchLatestVersionForCommand(registryUrl: string = UPDATE_REGISTRY_URL): Promise<string | null> {
66
71
  const controller = new AbortController();
67
72
  const timeout = setTimeout(() => controller.abort(), UPDATE_FETCH_TIMEOUT_MS);
68
73
 
69
74
  try {
70
- const res = await fetch(UPDATE_REGISTRY_URL, { signal: controller.signal });
75
+ const res = await fetch(registryUrl, { signal: controller.signal });
71
76
  if (!res.ok) return null;
72
77
  const data = (await res.json()) as { version?: string };
73
78
  const latest = typeof data.version === "string" ? data.version.trim().replace(/^v/, "") : "";
@@ -79,6 +84,19 @@ async function fetchLatestVersionForCommand(): Promise<string | null> {
79
84
  }
80
85
  }
81
86
 
87
+ function resolveInstalledPackageVersionForCommand(packageName: string): string | null {
88
+ try {
89
+ const requireFromHere = createRequire(import.meta.url);
90
+ const packageJsonPath = requireFromHere.resolve(`${packageName}/package.json`);
91
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as { version?: unknown };
92
+ return typeof pkg.version === "string" && pkg.version.trim().length > 0
93
+ ? pkg.version.trim().replace(/^v/, "")
94
+ : null;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+
82
100
  export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
83
101
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(gsdHome(), "agent", "GSD-WORKFLOW.md");
84
102
  const workflow = readFileSync(workflowPath, "utf-8");
@@ -473,34 +491,81 @@ function compareSemverLocal(a: string, b: string): number {
473
491
  return 0
474
492
  }
475
493
 
476
- export async function handleUpdate(ctx: ExtensionCommandContext): Promise<void> {
494
+ function formatCommandVersion(version: string | null): string {
495
+ return version ? `v${version}` : "unknown";
496
+ }
497
+
498
+ function pickHigherVersionForCommand(a: string | null, b: string | null): string | null {
499
+ if (!a) return b;
500
+ if (!b) return a;
501
+ return compareSemverLocal(a, b) >= 0 ? a : b;
502
+ }
503
+
504
+ // Mirrors resolveGsdBrowserPathVersion in src/update-check.ts — duplicated because
505
+ // tsconfig.resources.json rootDir prevents importing from src/.
506
+ function resolveGsdBrowserPathVersionForCommand(env: NodeJS.ProcessEnv = process.env): string | null {
507
+ const explicit = env.GSD_BROWSER_PATH_VERSION?.trim();
508
+ if (explicit) return explicit.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
509
+ try {
510
+ const out = execFileSync("gsd-browser", ["--version"], {
511
+ encoding: "utf-8",
512
+ env,
513
+ stdio: ["ignore", "pipe", "ignore"],
514
+ timeout: 2000,
515
+ });
516
+ return out.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
517
+ } catch {
518
+ return null;
519
+ }
520
+ }
521
+
522
+ export async function handleUpdate(ctx: ExtensionCommandContext, args = ""): Promise<void> {
477
523
  const { execSync } = await import("node:child_process");
478
524
 
479
- const NPM_PACKAGE = "@opengsd/gsd-pi";
480
- const current = process.env.GSD_VERSION || "0.0.0";
525
+ const target = args.trim();
526
+ const browserUpdate = target === "browser" || target === "gsd-browser";
527
+ if (target && !browserUpdate) {
528
+ ctx.ui.notify("Usage: /gsd update [browser]", "warning");
529
+ return;
530
+ }
531
+
532
+ const NPM_PACKAGE = browserUpdate ? GSD_BROWSER_PACKAGE : GSD_PI_PACKAGE;
533
+ const registryUrl = browserUpdate ? BROWSER_UPDATE_REGISTRY_URL : UPDATE_REGISTRY_URL;
534
+ const bundledVersion = browserUpdate
535
+ ? resolveInstalledPackageVersionForCommand(GSD_BROWSER_PACKAGE)
536
+ : null;
537
+ const current = browserUpdate
538
+ ? pickHigherVersionForCommand(bundledVersion, resolveGsdBrowserPathVersionForCommand())
539
+ : process.env.GSD_VERSION || "0.0.0";
540
+ const label = browserUpdate ? "gsd-browser version" : "version";
481
541
 
482
- ctx.ui.notify(`Current version: v${current}\nChecking npm registry...`, "info");
542
+ ctx.ui.notify(`Current ${label}: ${formatCommandVersion(current)}\nChecking npm registry...`, "info");
483
543
 
484
- const latest = await fetchLatestVersionForCommand();
544
+ const latest = await fetchLatestVersionForCommand(registryUrl);
485
545
  if (!latest) {
486
546
  ctx.ui.notify("Failed to reach npm registry. Check your network connection.", "error");
487
547
  return;
488
548
  }
489
549
 
490
- if (compareSemverLocal(latest, current) <= 0) {
491
- ctx.ui.notify(`Already up to date (v${current}).`, "info");
550
+ if (current && compareSemverLocal(latest, current) <= 0) {
551
+ ctx.ui.notify(`Already up to date (${formatCommandVersion(current)}).`, "info");
492
552
  return;
493
553
  }
494
554
 
495
- ctx.ui.notify(`Updating: v${current} → v${latest}...`, "info");
555
+ ctx.ui.notify(`Updating: ${formatCommandVersion(current)} → v${latest}...`, "info");
496
556
 
497
557
  const installCmd = resolveInstallCommand(`${NPM_PACKAGE}@latest`);
498
558
  try {
499
559
  execSync(installCmd, {
500
560
  stdio: ["ignore", "pipe", "ignore"],
501
561
  });
562
+ const newPathVersion = browserUpdate ? resolveGsdBrowserPathVersionForCommand() : null;
563
+ const pathReady = !browserUpdate || (!!newPathVersion && compareSemverLocal(newPathVersion, latest) >= 0);
502
564
  ctx.ui.notify(
503
- `Updated to v${latest}. Restart your GSD session to use the new version.`,
565
+ browserUpdate
566
+ ? `Updated gsd-browser to v${latest}. Restart your GSD session to use the new browser automation version.` +
567
+ (pathReady ? "" : "\nNote: Ensure the npm global bin directory is on your PATH so MCP automation uses the updated binary.")
568
+ : `Updated to v${latest}. Restart your GSD session to use the new version.`,
504
569
  "info",
505
570
  );
506
571
  } catch {
@@ -72,7 +72,8 @@ export function formatMcpInitResult(
72
72
  `Project: ${targetPath}`,
73
73
  `Config: ${configPath}`,
74
74
  "",
75
- "MCP-capable clients can now load the GSD workflow MCP server from this folder.",
75
+ "MCP-capable clients can now load the GSD workflow and gsd-browser MCP servers from this folder.",
76
+ "Pi Providers use the managed gsd-browser engine directly; this project config is for External MCP Clients.",
76
77
  "Restart or reconnect any client that already has this project open.",
77
78
  ].join("\n");
78
79
  }
@@ -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
  });