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

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 (259) 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 +5 -0
  12. package/dist/resources/extensions/gsd/auto-post-unit.js +21 -3
  13. package/dist/resources/extensions/gsd/auto-prompts.js +59 -22
  14. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  15. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  16. package/dist/resources/extensions/gsd/auto.js +9 -2
  17. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -4
  18. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -5
  19. package/dist/resources/extensions/gsd/browser-evidence.js +29 -2
  20. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -2
  21. package/dist/resources/extensions/gsd/commands-handlers.js +76 -11
  22. package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -1
  23. package/dist/resources/extensions/gsd/dashboard-overlay.js +21 -7
  24. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  25. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +2 -2
  26. package/dist/resources/extensions/gsd/escalation.js +4 -4
  27. package/dist/resources/extensions/gsd/forensics.js +74 -2
  28. package/dist/resources/extensions/gsd/gsd-db.js +5 -2
  29. package/dist/resources/extensions/gsd/guided-flow.js +29 -68
  30. package/dist/resources/extensions/gsd/mcp-project-config.js +9 -76
  31. package/dist/resources/extensions/gsd/memory-store.js +4 -1
  32. package/dist/resources/extensions/gsd/post-unit-hooks.js +9 -0
  33. package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
  34. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  35. package/dist/resources/extensions/gsd/prompts/forensics.md +61 -1
  36. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  37. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  38. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  40. package/dist/resources/extensions/gsd/prompts/run-uat.md +40 -22
  41. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  42. package/dist/resources/extensions/gsd/rule-registry.js +428 -52
  43. package/dist/resources/extensions/gsd/state.js +2 -2
  44. package/dist/resources/extensions/gsd/templates/plan.md +3 -1
  45. package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -1
  46. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -1
  47. package/dist/resources/extensions/gsd/tools/validate-milestone.js +46 -16
  48. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +51 -14
  49. package/dist/resources/extensions/gsd/verdict-parser.js +59 -15
  50. package/dist/resources/extensions/gsd/verification-gate.js +72 -1
  51. package/dist/resources/extensions/shared/gsd-browser-cli.js +145 -0
  52. package/dist/rtk.d.ts +7 -1
  53. package/dist/rtk.js +27 -11
  54. package/dist/update-check.d.ts +15 -1
  55. package/dist/update-check.js +87 -12
  56. package/dist/update-cmd.d.ts +1 -0
  57. package/dist/update-cmd.js +53 -2
  58. package/dist/web/standalone/.next/BUILD_ID +1 -1
  59. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  60. package/dist/web/standalone/.next/build-manifest.json +2 -2
  61. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  62. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/index.html +1 -1
  80. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  87. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  88. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  90. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  91. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  92. package/package.json +4 -2
  93. package/packages/cloud-mcp-gateway/package.json +2 -2
  94. package/packages/contracts/package.json +1 -1
  95. package/packages/daemon/package.json +4 -4
  96. package/packages/gsd-agent-core/dist/agent-session.d.ts +9 -0
  97. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  98. package/packages/gsd-agent-core/dist/agent-session.js +32 -0
  99. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  100. package/packages/gsd-agent-core/dist/index.d.ts +1 -0
  101. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  102. package/packages/gsd-agent-core/dist/index.js +1 -0
  103. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  104. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts +2 -0
  105. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts.map +1 -1
  106. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js +8 -2
  107. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js.map +1 -1
  108. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +7 -0
  109. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  110. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  111. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  112. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +69 -1
  113. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  114. package/packages/gsd-agent-core/dist/turn-latency.d.ts +47 -0
  115. package/packages/gsd-agent-core/dist/turn-latency.d.ts.map +1 -0
  116. package/packages/gsd-agent-core/dist/turn-latency.js +123 -0
  117. package/packages/gsd-agent-core/dist/turn-latency.js.map +1 -0
  118. package/packages/gsd-agent-core/package.json +6 -6
  119. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +21 -0
  120. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +1 -0
  121. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +213 -0
  122. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +1 -0
  123. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  124. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +20 -0
  125. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  126. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  127. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +7 -1
  128. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  129. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.d.ts.map +1 -1
  130. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js +6 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js.map +1 -1
  132. package/packages/gsd-agent-modes/package.json +7 -7
  133. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  134. package/packages/mcp-server/dist/remote-questions.js +23 -9
  135. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  136. package/packages/mcp-server/dist/workflow-tools.js +2 -2
  137. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  138. package/packages/mcp-server/package.json +3 -3
  139. package/packages/native/package.json +1 -1
  140. package/packages/pi-agent-core/dist/agent-loop.js +38 -0
  141. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  142. package/packages/pi-agent-core/dist/agent.d.ts +5 -1
  143. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  144. package/packages/pi-agent-core/dist/agent.js +2 -0
  145. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  146. package/packages/pi-agent-core/dist/types.d.ts +3 -0
  147. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  148. package/packages/pi-agent-core/dist/types.js.map +1 -1
  149. package/packages/pi-agent-core/package.json +1 -1
  150. package/packages/pi-ai/dist/api-registry.d.ts +2 -0
  151. package/packages/pi-ai/dist/api-registry.d.ts.map +1 -1
  152. package/packages/pi-ai/dist/api-registry.js +23 -0
  153. package/packages/pi-ai/dist/api-registry.js.map +1 -1
  154. package/packages/pi-ai/dist/models.generated.d.ts +68 -0
  155. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  156. package/packages/pi-ai/dist/models.generated.js +72 -4
  157. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  158. package/packages/pi-ai/dist/stream.js +6 -6
  159. package/packages/pi-ai/dist/stream.js.map +1 -1
  160. package/packages/pi-ai/package.json +1 -1
  161. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -2
  163. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  164. package/packages/pi-coding-agent/package.json +7 -7
  165. package/packages/pi-tui/package.json +1 -1
  166. package/packages/rpc-client/package.json +2 -2
  167. package/pkg/package.json +1 -1
  168. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +579 -0
  169. package/src/resources/extensions/browser-tools/engine/selection.ts +19 -0
  170. package/src/resources/extensions/browser-tools/extension-manifest.json +2 -2
  171. package/src/resources/extensions/browser-tools/index.ts +60 -9
  172. package/src/resources/extensions/browser-tools/package.json +5 -1
  173. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +35 -0
  174. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +33 -0
  175. package/src/resources/extensions/gsd/auto/orchestrator.ts +0 -1
  176. package/src/resources/extensions/gsd/auto-dashboard.ts +82 -14
  177. package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
  178. package/src/resources/extensions/gsd/auto-post-unit.ts +28 -2
  179. package/src/resources/extensions/gsd/auto-prompts.ts +93 -15
  180. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
  181. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  182. package/src/resources/extensions/gsd/auto.ts +12 -2
  183. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -4
  184. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -5
  185. package/src/resources/extensions/gsd/browser-evidence.ts +26 -2
  186. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -2
  187. package/src/resources/extensions/gsd/commands-handlers.ts +76 -11
  188. package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -1
  189. package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -7
  190. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  191. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +2 -2
  192. package/src/resources/extensions/gsd/escalation.ts +4 -4
  193. package/src/resources/extensions/gsd/forensics.ts +99 -5
  194. package/src/resources/extensions/gsd/gsd-db.ts +5 -2
  195. package/src/resources/extensions/gsd/guided-flow.ts +90 -82
  196. package/src/resources/extensions/gsd/mcp-project-config.ts +13 -78
  197. package/src/resources/extensions/gsd/memory-store.ts +4 -1
  198. package/src/resources/extensions/gsd/post-unit-hooks.ts +14 -1
  199. package/src/resources/extensions/gsd/preferences-validation.ts +36 -0
  200. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  201. package/src/resources/extensions/gsd/prompts/forensics.md +61 -1
  202. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  203. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  204. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  205. package/src/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  206. package/src/resources/extensions/gsd/prompts/run-uat.md +40 -22
  207. package/src/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  208. package/src/resources/extensions/gsd/rule-registry.ts +558 -58
  209. package/src/resources/extensions/gsd/rule-types.ts +2 -0
  210. package/src/resources/extensions/gsd/state.ts +2 -2
  211. package/src/resources/extensions/gsd/templates/plan.md +3 -1
  212. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +105 -4
  213. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +37 -0
  214. package/src/resources/extensions/gsd/tests/browser-evidence.test.ts +142 -0
  215. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +30 -0
  216. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
  217. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +45 -0
  218. package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +53 -0
  219. package/src/resources/extensions/gsd/tests/discuss-milestone-structured-questions.test.ts +31 -0
  220. package/src/resources/extensions/gsd/tests/doctor-runtime-checks.test.ts +27 -0
  221. package/src/resources/extensions/gsd/tests/escalation.test.ts +16 -27
  222. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +20 -0
  223. package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +3 -0
  224. package/src/resources/extensions/gsd/tests/forensics-tool-scope.test.ts +69 -0
  225. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +40 -1
  226. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +86 -0
  227. package/src/resources/extensions/gsd/tests/guided-flow.test.ts +12 -9
  228. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +4 -4
  229. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +66 -10
  230. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +32 -0
  231. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +2 -0
  232. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +39 -8
  233. package/src/resources/extensions/gsd/tests/new-milestone-discuss-routing.test.ts +3 -3
  234. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +9 -0
  235. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +157 -0
  236. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +179 -0
  237. package/src/resources/extensions/gsd/tests/preferences.test.ts +29 -0
  238. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +43 -1
  239. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +14 -0
  240. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +7 -8
  241. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +75 -0
  242. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +100 -0
  243. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +139 -0
  244. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
  245. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +7 -1
  246. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +6 -3
  247. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +133 -0
  248. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +51 -0
  249. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +130 -0
  250. package/src/resources/extensions/gsd/tools/complete-slice.ts +14 -1
  251. package/src/resources/extensions/gsd/tools/complete-task.ts +20 -2
  252. package/src/resources/extensions/gsd/tools/validate-milestone.ts +46 -15
  253. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +63 -15
  254. package/src/resources/extensions/gsd/types.ts +69 -5
  255. package/src/resources/extensions/gsd/verdict-parser.ts +54 -13
  256. package/src/resources/extensions/gsd/verification-gate.ts +87 -1
  257. package/src/resources/extensions/shared/gsd-browser-cli.ts +172 -0
  258. /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → jBtwT9v1u2lUA3UEOy_ZH}/_buildManifest.js +0 -0
  259. /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → jBtwT9v1u2lUA3UEOy_ZH}/_ssgManifest.js +0 -0
@@ -832,7 +832,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
832
832
  recommendation: Type.String({ description: "Option id the executor recommends." }),
833
833
  recommendationRationale: Type.String({ description: "Why the recommendation — 1–2 sentences." }),
834
834
  continueWithDefault: Type.Boolean({
835
- description: "When true, loop continues (artifact logged for later review). When false, auto-mode pauses until the user resolves via /gsd escalate resolve.",
835
+ description: "When true, the recommendation is recorded as the default, but auto-mode still pauses until the user resolves via /gsd escalate resolve.",
836
836
  }),
837
837
  }, { description: "ADR-011 Phase 2: optional escalation payload. Only honored when phases.mid_execution_escalation is true." })),
838
838
  verificationEvidence: Type.Optional(Type.Array(
@@ -881,7 +881,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
881
881
  sliceTitle: Type.String({ description: "Title of the slice" }),
882
882
  oneLiner: Type.String({ description: "One-line summary of what the slice accomplished" }),
883
883
  narrative: Type.String({ description: "Detailed narrative of what happened across all tasks" }),
884
- verification: Type.String({ description: "What was verified across all tasks" }),
884
+ verification: Type.Optional(Type.String({ description: "What was verified across all tasks — if omitted, summary records verification as passed without detail." })),
885
885
  uatContent: Type.String({ description: "UAT test content (markdown body)" }),
886
886
  // ── Enrichment metadata (optional — defaults to empty) ────────────
887
887
  deviations: Type.Optional(Type.String({ description: "Deviations from the slice plan, or 'None.'" })),
@@ -1095,7 +1095,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
1095
1095
  promptGuidelines: [
1096
1096
  "Use gsd_validate_milestone when all slices are done and the milestone needs validation before completion.",
1097
1097
  "Parameters: milestoneId, verdict, remediationRound, successCriteriaChecklist, sliceDeliveryAudit, crossSliceIntegration, requirementCoverage, verificationClasses (optional), verdictRationale, remediationPlan (optional).",
1098
- "If verification classes were planned, verificationClasses must include canonical class rows using the exact class names Contract, Integration, Operational, and UAT when present in planning.",
1098
+ "If verification classes were planned, verificationClasses must be a complete canonical table with one row for every applicable planned class using the exact class names Contract, Integration, Operational, and UAT. Do not submit a partial table.",
1099
1099
  "Planned verification text marked as none/not required/not applicable/N/A (including suffixed variants such as 'not required - backend-only') is treated as not applicable and does not require a class row.",
1100
1100
  "If verdict is 'needs-remediation', also provide remediationPlan and use gsd_reassess_roadmap to add remediation slices to the roadmap.",
1101
1101
  "On success, returns validationPath where VALIDATION.md was written.",
@@ -1108,7 +1108,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
1108
1108
  sliceDeliveryAudit: Type.String({ description: "Markdown table auditing each slice's claimed vs delivered output" }),
1109
1109
  crossSliceIntegration: Type.String({ description: "Markdown describing any cross-slice boundary mismatches" }),
1110
1110
  requirementCoverage: Type.String({ description: "Markdown describing any unaddressed requirements" }),
1111
- verificationClasses: Type.Optional(Type.String({ description: "Markdown describing verification class compliance and gaps using canonical class names (Contract, Integration, Operational, UAT) for each applicable planned class" })),
1111
+ verificationClasses: Type.Optional(Type.String({ description: "Complete markdown table describing verification class compliance and gaps; include one canonical row for every applicable planned class (Contract, Integration, Operational, UAT)" })),
1112
1112
  verdictRationale: Type.String({ description: "Why this verdict was chosen" }),
1113
1113
  remediationPlan: Type.Optional(Type.String({ description: "Remediation plan (required if verdict is needs-remediation)" })),
1114
1114
  }),
@@ -17,7 +17,7 @@ import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer,
17
17
  import { resolveManifest } from "../unit-context-manifest.js";
18
18
  import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
19
19
  import { loadFile, saveFile, formatContinue } from "../files.js";
20
- import { clearToolInvocationError, getAutoRuntimeSnapshot, isAutoActive, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError } from "../auto-runtime-state.js";
20
+ import { clearToolInvocationError, getAutoRuntimeSnapshot, isAutoActive, isAutoCompletionStopInProgress, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError } from "../auto-runtime-state.js";
21
21
 
22
22
  import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
23
23
  import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
@@ -535,8 +535,9 @@ export function registerHooks(
535
535
 
536
536
  pi.on("session_start", async (_event, ctx) => {
537
537
  const basePath = contextBasePath(ctx);
538
+ const preserveCloseoutSurface = isAutoCompletionStopInProgress();
538
539
  initSessionNotifications(ctx);
539
- if (!isAutoActive()) {
540
+ if (!isAutoActive() && !preserveCloseoutSurface) {
540
541
  const { initHealthWidget } = await import("../health-widget.js");
541
542
  initHealthWidget(ctx);
542
543
  }
@@ -556,15 +557,18 @@ export function registerHooks(
556
557
  const prefs = loadEffectiveGSDPreferences(basePath);
557
558
  process.env.GSD_SHOW_TOKEN_COST = prefs?.preferences.show_token_cost ? "1" : "";
558
559
  } catch { /* non-fatal */ }
559
- await installWelcomeHeader(ctx);
560
+ if (!preserveCloseoutSurface) {
561
+ await installWelcomeHeader(ctx);
562
+ }
560
563
  await loadToolApiKeysForSession();
561
- if (isAutoActive()) {
564
+ if (isAutoActive() || preserveCloseoutSurface) {
562
565
  ctx.ui.setWidget("gsd-health", undefined);
563
566
  }
564
567
  });
565
568
 
566
569
  pi.on("session_switch", async (_event, ctx) => {
567
570
  const basePath = contextBasePath(ctx);
571
+ const preserveCloseoutSurface = isAutoCompletionStopInProgress();
568
572
  initSessionNotifications(ctx);
569
573
  resetWriteGateState(basePath);
570
574
  resetToolCallLoopGuard();
@@ -576,7 +580,7 @@ export function registerHooks(
576
580
  await applyCompactionThresholdOverride(ctx);
577
581
  await prepareWorkflowMcpForHookContext(ctx, basePath);
578
582
  await loadToolApiKeysForSession();
579
- if (!isAutoActive()) {
583
+ if (!isAutoActive() && !preserveCloseoutSurface) {
580
584
  ctx.ui.setWidget("gsd-progress", undefined);
581
585
  ctx.ui.setWidget("gsd-outcome", undefined);
582
586
  const { initHealthWidget } = await import("../health-widget.js");
@@ -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 {
@@ -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
  });
@@ -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;
@@ -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);
@@ -1488,7 +1488,7 @@ export function setTaskEscalationPending(
1488
1488
  ).run({ ":path": artifactPath, ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
1489
1489
  }
1490
1490
 
1491
- /** Set awaiting-review state (artifact exists but continueWithDefault=true, no pause). Mutually exclusive with pending. */
1491
+ /** Set awaiting-review state (artifact exists and requires explicit user review). Mutually exclusive with pending. */
1492
1492
  export function setTaskEscalationAwaitingReview(
1493
1493
  milestoneId: string, sliceId: string, taskId: string,
1494
1494
  artifactPath: string,
@@ -3233,7 +3233,10 @@ export function decayMemoriesBefore(cutoffTs: string, now: string): void {
3233
3233
  currentDb.prepare(
3234
3234
  `UPDATE memories
3235
3235
  SET confidence = MAX(0.1, confidence - 0.1), updated_at = :now
3236
- WHERE superseded_by IS NULL AND updated_at < :cutoff AND confidence > 0.1`,
3236
+ WHERE superseded_by IS NULL
3237
+ AND updated_at < :cutoff
3238
+ AND confidence > 0.1
3239
+ AND (structured_fields IS NULL OR structured_fields NOT LIKE '%"sourceDecisionId"%')`,
3237
3240
  ).run({ ":now": now, ":cutoff": cutoffTs });
3238
3241
  }
3239
3242