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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/gsd/auto/orchestrator.js +0 -1
  3. package/dist/resources/extensions/gsd/auto/phases.js +4 -3
  4. package/dist/resources/extensions/gsd/auto-dashboard.js +92 -17
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +5 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +132 -8
  7. package/dist/resources/extensions/gsd/auto-prompts.js +68 -22
  8. package/dist/resources/extensions/gsd/auto-start.js +41 -12
  9. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
  10. package/dist/resources/extensions/gsd/auto.js +12 -5
  11. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +82 -3
  12. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
  13. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
  14. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
  15. package/dist/resources/extensions/gsd/browser-evidence.js +29 -2
  16. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
  17. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
  18. package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
  19. package/dist/resources/extensions/gsd/config-overlay.js +2 -1
  20. package/dist/resources/extensions/gsd/dashboard-overlay.js +21 -7
  21. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  22. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +2 -2
  23. package/dist/resources/extensions/gsd/error-classifier.js +2 -1
  24. package/dist/resources/extensions/gsd/escalation.js +4 -4
  25. package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
  26. package/dist/resources/extensions/gsd/forensics.js +74 -2
  27. package/dist/resources/extensions/gsd/gsd-db.js +5 -2
  28. package/dist/resources/extensions/gsd/guided-flow.js +29 -68
  29. package/dist/resources/extensions/gsd/memory-store.js +4 -1
  30. package/dist/resources/extensions/gsd/post-unit-hooks.js +9 -0
  31. package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
  32. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  33. package/dist/resources/extensions/gsd/prompts/forensics.md +61 -1
  34. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  35. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  36. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  38. package/dist/resources/extensions/gsd/prompts/run-uat.md +48 -24
  39. package/dist/resources/extensions/gsd/prompts/system.md +3 -1
  40. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  41. package/dist/resources/extensions/gsd/rule-registry.js +428 -52
  42. package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
  43. package/dist/resources/extensions/gsd/skill-activation.js +20 -3
  44. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
  45. package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
  46. package/dist/resources/extensions/gsd/state.js +3 -3
  47. package/dist/resources/extensions/gsd/templates/plan.md +3 -1
  48. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -1
  49. package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
  50. package/dist/resources/extensions/gsd/tools/validate-milestone.js +46 -16
  51. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +403 -3
  52. package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
  53. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
  54. package/dist/resources/extensions/gsd/verdict-parser.js +59 -15
  55. package/dist/resources/extensions/gsd/verification-gate.js +72 -1
  56. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +1 -1
  57. package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
  58. package/dist/rtk.d.ts +7 -1
  59. package/dist/rtk.js +27 -11
  60. package/dist/web/standalone/.next/BUILD_ID +1 -1
  61. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  62. package/dist/web/standalone/.next/build-manifest.json +2 -2
  63. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  64. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.html +1 -1
  81. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  88. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  89. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  91. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  92. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  93. package/package.json +3 -2
  94. package/packages/cloud-mcp-gateway/package.json +2 -2
  95. package/packages/contracts/dist/workflow.d.ts +14 -0
  96. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  97. package/packages/contracts/dist/workflow.js +16 -0
  98. package/packages/contracts/dist/workflow.js.map +1 -1
  99. package/packages/contracts/package.json +1 -1
  100. package/packages/daemon/package.json +4 -4
  101. package/packages/gsd-agent-core/dist/agent-session.d.ts +9 -0
  102. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  103. package/packages/gsd-agent-core/dist/agent-session.js +32 -0
  104. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  105. package/packages/gsd-agent-core/dist/index.d.ts +1 -0
  106. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  107. package/packages/gsd-agent-core/dist/index.js +1 -0
  108. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  109. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts +2 -0
  110. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts.map +1 -1
  111. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js +8 -2
  112. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js.map +1 -1
  113. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +7 -0
  114. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  115. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  116. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  117. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +69 -1
  118. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  119. package/packages/gsd-agent-core/dist/turn-latency.d.ts +47 -0
  120. package/packages/gsd-agent-core/dist/turn-latency.d.ts.map +1 -0
  121. package/packages/gsd-agent-core/dist/turn-latency.js +123 -0
  122. package/packages/gsd-agent-core/dist/turn-latency.js.map +1 -0
  123. package/packages/gsd-agent-core/package.json +6 -6
  124. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +21 -0
  125. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +1 -0
  126. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +213 -0
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +1 -0
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  132. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
  133. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  134. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +89 -31
  135. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  137. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +7 -1
  138. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.d.ts.map +1 -1
  140. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js +6 -0
  141. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js.map +1 -1
  142. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
  143. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  144. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
  145. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  146. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  147. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
  148. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  149. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
  150. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
  151. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
  152. package/packages/gsd-agent-modes/package.json +7 -7
  153. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  154. package/packages/mcp-server/dist/remote-questions.js +23 -9
  155. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  156. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  157. package/packages/mcp-server/dist/workflow-tools.js +84 -2
  158. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  159. package/packages/mcp-server/package.json +3 -3
  160. package/packages/native/package.json +1 -1
  161. package/packages/pi-agent-core/dist/agent-loop.js +38 -0
  162. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  163. package/packages/pi-agent-core/dist/agent.d.ts +5 -1
  164. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  165. package/packages/pi-agent-core/dist/agent.js +2 -0
  166. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  167. package/packages/pi-agent-core/dist/types.d.ts +3 -0
  168. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  169. package/packages/pi-agent-core/dist/types.js.map +1 -1
  170. package/packages/pi-agent-core/package.json +1 -1
  171. package/packages/pi-ai/dist/api-registry.d.ts +2 -0
  172. package/packages/pi-ai/dist/api-registry.d.ts.map +1 -1
  173. package/packages/pi-ai/dist/api-registry.js +23 -0
  174. package/packages/pi-ai/dist/api-registry.js.map +1 -1
  175. package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
  176. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  177. package/packages/pi-ai/dist/image-models.generated.js +15 -0
  178. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  179. package/packages/pi-ai/dist/models.generated.d.ts +86 -18
  180. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  181. package/packages/pi-ai/dist/models.generated.js +108 -40
  182. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  183. package/packages/pi-ai/dist/stream.js +6 -6
  184. package/packages/pi-ai/dist/stream.js.map +1 -1
  185. package/packages/pi-ai/package.json +1 -1
  186. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -2
  188. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  190. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  192. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  193. package/packages/pi-coding-agent/package.json +7 -7
  194. package/packages/pi-tui/dist/terminal.d.ts +1 -0
  195. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  196. package/packages/pi-tui/dist/terminal.js +8 -4
  197. package/packages/pi-tui/dist/terminal.js.map +1 -1
  198. package/packages/pi-tui/package.json +1 -1
  199. package/packages/rpc-client/package.json +2 -2
  200. package/pkg/package.json +1 -1
  201. package/src/resources/extensions/gsd/auto/orchestrator.ts +0 -1
  202. package/src/resources/extensions/gsd/auto/phases.ts +5 -3
  203. package/src/resources/extensions/gsd/auto-dashboard.ts +98 -18
  204. package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
  205. package/src/resources/extensions/gsd/auto-post-unit.ts +164 -7
  206. package/src/resources/extensions/gsd/auto-prompts.ts +102 -15
  207. package/src/resources/extensions/gsd/auto-start.ts +54 -14
  208. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
  209. package/src/resources/extensions/gsd/auto.ts +15 -4
  210. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +89 -3
  211. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
  212. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
  213. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
  214. package/src/resources/extensions/gsd/browser-evidence.ts +26 -2
  215. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
  216. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
  217. package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
  218. package/src/resources/extensions/gsd/config-overlay.ts +3 -1
  219. package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -7
  220. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  221. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +2 -2
  222. package/src/resources/extensions/gsd/error-classifier.ts +2 -1
  223. package/src/resources/extensions/gsd/escalation.ts +4 -4
  224. package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
  225. package/src/resources/extensions/gsd/forensics.ts +99 -5
  226. package/src/resources/extensions/gsd/gsd-db.ts +5 -2
  227. package/src/resources/extensions/gsd/guided-flow.ts +90 -82
  228. package/src/resources/extensions/gsd/memory-store.ts +4 -1
  229. package/src/resources/extensions/gsd/post-unit-hooks.ts +14 -1
  230. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  231. package/src/resources/extensions/gsd/preferences-validation.ts +36 -0
  232. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  233. package/src/resources/extensions/gsd/prompts/forensics.md +61 -1
  234. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  235. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  236. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  237. package/src/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  238. package/src/resources/extensions/gsd/prompts/run-uat.md +48 -24
  239. package/src/resources/extensions/gsd/prompts/system.md +3 -1
  240. package/src/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  241. package/src/resources/extensions/gsd/rule-registry.ts +558 -58
  242. package/src/resources/extensions/gsd/rule-types.ts +2 -0
  243. package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
  244. package/src/resources/extensions/gsd/skill-activation.ts +20 -2
  245. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
  246. package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
  247. package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
  248. package/src/resources/extensions/gsd/state.ts +3 -3
  249. package/src/resources/extensions/gsd/templates/plan.md +3 -1
  250. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +156 -4
  251. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +37 -0
  252. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +16 -3
  253. package/src/resources/extensions/gsd/tests/browser-evidence.test.ts +142 -0
  254. package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
  255. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
  256. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +30 -0
  257. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +45 -0
  258. package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +53 -0
  259. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
  260. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +8 -0
  261. package/src/resources/extensions/gsd/tests/discuss-milestone-structured-questions.test.ts +31 -0
  262. package/src/resources/extensions/gsd/tests/doctor-runtime-checks.test.ts +27 -0
  263. package/src/resources/extensions/gsd/tests/escalation.test.ts +16 -27
  264. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
  265. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
  266. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +20 -0
  267. package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +3 -0
  268. package/src/resources/extensions/gsd/tests/forensics-tool-scope.test.ts +69 -0
  269. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +40 -1
  270. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +86 -0
  271. package/src/resources/extensions/gsd/tests/guided-flow.test.ts +12 -9
  272. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +4 -4
  273. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +66 -10
  274. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +39 -8
  275. package/src/resources/extensions/gsd/tests/new-milestone-discuss-routing.test.ts +3 -3
  276. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
  277. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +9 -0
  278. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +157 -0
  279. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +179 -0
  280. package/src/resources/extensions/gsd/tests/preferences.test.ts +29 -0
  281. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +53 -1
  282. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +14 -0
  283. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
  284. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +7 -8
  285. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
  286. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
  287. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
  288. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +75 -0
  289. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
  290. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +191 -0
  291. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
  292. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
  293. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
  294. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
  295. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +6 -3
  296. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +133 -0
  297. package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
  298. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +51 -0
  299. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +2 -2
  300. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +213 -0
  301. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
  302. package/src/resources/extensions/gsd/tools/complete-task.ts +20 -2
  303. package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
  304. package/src/resources/extensions/gsd/tools/validate-milestone.ts +46 -15
  305. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +489 -3
  306. package/src/resources/extensions/gsd/types.ts +67 -4
  307. package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
  308. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
  309. package/src/resources/extensions/gsd/verdict-parser.ts +54 -13
  310. package/src/resources/extensions/gsd/verification-gate.ts +87 -1
  311. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +1 -1
  312. package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
  313. /package/dist/web/standalone/.next/static/{xACmObbrDjwLriepRgaa9 → IDKjyRHLIaumjgonPcYiX}/_buildManifest.js +0 -0
  314. /package/dist/web/standalone/.next/static/{xACmObbrDjwLriepRgaa9 → IDKjyRHLIaumjgonPcYiX}/_ssgManifest.js +0 -0
@@ -49,6 +49,7 @@ import { resolveProjectRootDbPath } from "./bootstrap/dynamic-tools.js";
49
49
  import { validateDirectory } from "./validate-directory.js";
50
50
  import { isCustomProvider, resolveDefaultSessionModel, resolveDynamicRoutingConfig, } from "./preferences-models.js";
51
51
  import { getSessionModelOverride } from "./session-model-override.js";
52
+ import { setAutoActiveStatus } from "./auto-dashboard.js";
52
53
  export function resolveIsolationNoneBranchCheckout(currentBranch, integrationBranch, isolationMode, isRepo) {
53
54
  if (!isRepo || isolationMode !== "none")
54
55
  return null;
@@ -164,6 +165,19 @@ export function resolveSurvivorRecoveryIsolationMode(isolationMode, phase) {
164
165
  function isBlockingStrandedWorkAction(action) {
165
166
  return action.kind === "in-progress-stranded-work" && action.blocksAuto;
166
167
  }
168
+ function strandedWorkEvidence(args) {
169
+ const evidence = [];
170
+ if (args.branch && args.commitsAhead > 0) {
171
+ evidence.push(`branch ${args.branch} has ${args.commitsAhead} commit(s) ahead of ${args.mainBranch}`);
172
+ }
173
+ if (args.dirtyWorktree) {
174
+ evidence.push("the worktree has uncommitted changes");
175
+ }
176
+ if (evidence.length === 0) {
177
+ evidence.push("physical git evidence exists");
178
+ }
179
+ return evidence;
180
+ }
167
181
  function detectWorktreeEvidence(basePath, milestoneId, hasChanges) {
168
182
  const wtDir = getWorktreeDir(basePath, milestoneId);
169
183
  const wtPath = getAutoWorktreePath(basePath, milestoneId);
@@ -183,16 +197,7 @@ function detectWorktreeEvidence(basePath, milestoneId, hasChanges) {
183
197
  };
184
198
  }
185
199
  function strandedWorkMessage(args) {
186
- const evidence = [];
187
- if (args.branch && args.commitsAhead > 0) {
188
- evidence.push(`branch ${args.branch} has ${args.commitsAhead} commit(s) ahead of ${args.mainBranch}`);
189
- }
190
- if (args.dirtyWorktree) {
191
- evidence.push("the worktree has uncommitted changes");
192
- }
193
- if (evidence.length === 0) {
194
- evidence.push("physical git evidence exists");
195
- }
200
+ const evidence = strandedWorkEvidence(args);
196
201
  const wtSuffix = args.worktreeDirExists
197
202
  ? ` Worktree directory at .gsd/worktrees/${args.milestoneId}/ holds live work.`
198
203
  : "";
@@ -203,6 +208,23 @@ function strandedWorkMessage(args) {
203
208
  wtSuffix +
204
209
  ` ${recovery} Park or discard explicitly if abandoning.`);
205
210
  }
211
+ function formatStrandedWorkRecoveryMessage(action) {
212
+ const recoveryMode = action.recoveryMode === "worktree"
213
+ ? "existing worktree"
214
+ : "milestone branch";
215
+ const evidence = strandedWorkEvidence({
216
+ branch: action.branch,
217
+ commitsAhead: action.commitsAhead ?? 0,
218
+ mainBranch: action.mainBranch ?? "main",
219
+ dirtyWorktree: action.dirtyWorktree ?? false,
220
+ });
221
+ const wtSuffix = action.worktreeDirExists
222
+ ? ` Worktree directory at .gsd/worktrees/${action.milestoneId}/ holds live work.`
223
+ : "";
224
+ return (`Resuming saved milestone work for ${action.milestoneId}: ${evidence.join("; ")}.` +
225
+ wtSuffix +
226
+ ` Adopting the ${recoveryMode} before dispatching new units. Park or discard explicitly if abandoning.`);
227
+ }
206
228
  function formatStrandedWorkBlockerMessage(action, activeMilestoneId) {
207
229
  const target = action.milestoneId;
208
230
  const mode = action.recoveryMode === "worktree" ? "existing worktree" : "milestone branch";
@@ -307,6 +329,7 @@ export function auditOrphanedMilestoneBranches(basePath, _isolationMode, gitDeps
307
329
  kind: "in-progress-stranded-work",
308
330
  milestoneId,
309
331
  branch,
332
+ mainBranch,
310
333
  commitsAhead,
311
334
  dirtyWorktree: worktreeEvidence.dirty,
312
335
  worktreeDirExists: worktreeEvidence.dirExists,
@@ -462,6 +485,7 @@ export function auditOrphanedMilestoneBranches(basePath, _isolationMode, gitDeps
462
485
  pushAction({
463
486
  kind: "in-progress-stranded-work",
464
487
  milestoneId: m.id,
488
+ mainBranch,
465
489
  commitsAhead: 0,
466
490
  dirtyWorktree: true,
467
491
  worktreeDirExists: worktreeEvidence.dirExists,
@@ -890,7 +914,12 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
890
914
  for (const msg of auditResult.recovered) {
891
915
  ctx.ui.notify(`Orphan audit: ${msg}`, "info");
892
916
  }
917
+ const deferredStrandedMessages = new Set(auditResult.actions
918
+ .filter(isBlockingStrandedWorkAction)
919
+ .map((action) => action.message));
893
920
  for (const msg of auditResult.warnings) {
921
+ if (deferredStrandedMessages.has(msg))
922
+ continue;
894
923
  const prefix = msg.startsWith("Stranded work") ? "" : "Orphan audit: ";
895
924
  ctx.ui.notify(`${prefix}${msg}`, "warning");
896
925
  }
@@ -958,7 +987,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
958
987
  return releaseLockAndReturn();
959
988
  }
960
989
  strandedRecoveryAction = blockingStrandedRecoveryAction;
961
- ctx.ui.notify(`Recovering stranded work for ${strandedRecoveryAction.milestoneId} before dispatching new units.`, "info");
990
+ ctx.ui.notify(formatStrandedWorkRecoveryMessage(strandedRecoveryAction), "info");
962
991
  }
963
992
  if (process.env.GSD_HEADLESS === "1" &&
964
993
  orphanAuditRecovered &&
@@ -1343,7 +1372,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
1343
1372
  if (resolveSkillDiscoveryMode(base) !== "off") {
1344
1373
  snapshotSkills();
1345
1374
  }
1346
- ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
1375
+ setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
1347
1376
  ctx.ui.setWidget("gsd-health", undefined);
1348
1377
  const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
1349
1378
  const pendingCount = (state.registry ?? []).filter((m) => m.status !== "complete" && m.status !== "parked").length;
@@ -1,4 +1,5 @@
1
1
  import { parseUnitId } from "./unit-id.js";
2
+ import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "./tool-presentation-plan.js";
2
3
  export const RUN_UAT_BROWSER_TOOL_NAMES = [
3
4
  "browser_navigate",
4
5
  "browser_click",
@@ -42,7 +43,7 @@ export const AUTO_UNIT_SCOPED_TOOLS = {
42
43
  "execute-task": ["gsd_task_complete", "gsd_decision_save"],
43
44
  "execute-task-simple": ["gsd_task_complete", "gsd_decision_save"],
44
45
  "reactive-execute": ["gsd_task_complete", "gsd_decision_save"],
45
- "run-uat": ["gsd_summary_save", ...RUN_UAT_BROWSER_TOOL_NAMES],
46
+ "run-uat": [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent", ...RUN_UAT_BROWSER_TOOL_NAMES],
46
47
  "gate-evaluate": ["gsd_save_gate_result"],
47
48
  "rewrite-docs": ["gsd_summary_save", "gsd_decision_save"],
48
49
  "workflow-preferences": ["gsd_summary_save"],
@@ -64,7 +64,7 @@ import { initRegistry, convertDispatchRules } from "./rule-registry.js";
64
64
  import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
65
65
  import { isClosedStatus } from "./status-guards.js";
66
66
  import { MILESTONE_ID_RE } from "./milestone-ids.js";
67
- import { updateProgressWidget as _updateProgressWidget, setCompletionProgressWidget, setAutoOutcomeWidget, updateSliceProgressCache, clearSliceProgressCache, unitVerb, } from "./auto-dashboard.js";
67
+ import { updateProgressWidget as _updateProgressWidget, setCompletionProgressWidget, setAutoOutcomeWidget, setAutoActiveStatus, updateSliceProgressCache, clearSliceProgressCache, unitVerb, } from "./auto-dashboard.js";
68
68
  import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
69
69
  import { isDbAvailable, getMilestone, getMilestoneSlices, getSlice, getTask, refreshOpenDatabaseFromDisk, } from "./gsd-db.js";
70
70
  import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
@@ -1774,10 +1774,17 @@ export function createWiredAutoOrchestrationModule(ctx, pi, dispatchBasePath, ru
1774
1774
  async reconcileBeforeDispatch() {
1775
1775
  const activeBasePath = getLiveDispatchBasePath();
1776
1776
  const result = await reconcileBeforeDispatch(activeBasePath);
1777
- if (result.blockers.length > 0) {
1777
+ // Failure-path summaries written by gsd_summary_save create
1778
+ // artifact-db-status-divergence blockers for tasks that are still
1779
+ // pending (gsd_task_complete never ran). These tasks can still be
1780
+ // dispatched and the drift self-heals once they complete successfully.
1781
+ const hardBlockers = result.blockers.filter((b) => !b.includes("has SUMMARY artifact while DB status is") &&
1782
+ !b.includes("has SUMMARY on disk while DB status is") &&
1783
+ !b.includes("has task SUMMARY artifacts but no DB tasks"));
1784
+ if (hardBlockers.length > 0) {
1778
1785
  return {
1779
1786
  ok: false,
1780
- reason: result.blockers[0],
1787
+ reason: hardBlockers[0],
1781
1788
  stateSnapshot: result.stateSnapshot,
1782
1789
  };
1783
1790
  }
@@ -2414,7 +2421,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
2414
2421
  const loopDeps = buildLoopDeps(pi);
2415
2422
  ensureOrchestrationModule(ctx, pi, s.basePath || base);
2416
2423
  registerSigtermHandler(lockBase());
2417
- ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
2424
+ setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
2418
2425
  ctx.ui.setWidget("gsd-health", undefined);
2419
2426
  ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
2420
2427
  restoreHookState(s.basePath);
@@ -2659,7 +2666,7 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
2659
2666
  resetHookState();
2660
2667
  await pauseAuto(ctx, pi);
2661
2668
  }, hookHardTimeoutMs);
2662
- ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
2669
+ setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
2663
2670
  ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
2664
2671
  debugLog("dispatchHookUnit", {
2665
2672
  phase: "send-message",
@@ -396,6 +396,85 @@ export function registerDbTools(pi) {
396
396
  };
397
397
  pi.registerTool(summarySaveTool);
398
398
  registerAlias(pi, summarySaveTool, "gsd_save_summary", "gsd_summary_save");
399
+ // ─── gsd_uat_result_save ─────────────────────────────────────────────────
400
+ const uatResultSaveExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
401
+ const { executeUatResultSave } = await loadWorkflowExecutors();
402
+ return executeUatResultSave(params, resolveWorkflowToolBasePath(_ctx, params));
403
+ };
404
+ const uatEvidenceRef = Type.Object({
405
+ kind: StringEnum(["gsd_uat_exec", "gsd_exec", "screenshot", "log", "url", "browser"], { description: "Evidence kind" }),
406
+ ref: Type.String({ description: "Evidence ID, approved .gsd path, or URL" }),
407
+ note: Type.Optional(Type.String({ description: "Short evidence note" })),
408
+ });
409
+ const uatCheck = Type.Object({
410
+ id: Type.String({ description: "Stable check ID from the UAT spec" }),
411
+ description: Type.String({ description: "Check description" }),
412
+ mode: StringEnum(["artifact", "runtime", "browser", "human-follow-up"], { description: "Evidence mode" }),
413
+ result: StringEnum(["PASS", "FAIL", "NEEDS-HUMAN"], { description: "Check result" }),
414
+ evidence: Type.Optional(Type.Array(uatEvidenceRef, { description: "Objective evidence references" })),
415
+ notes: Type.Optional(Type.String({ description: "Observed result, failure notes, or human instruction" })),
416
+ nonAutomatable: Type.Optional(Type.Boolean({ description: "True when the check is explicitly non-automatable" })),
417
+ });
418
+ const toolPresentationBlock = Type.Object({
419
+ surface: StringEnum(["provider-tools", "claude-code-sdk", "mcp", "hybrid"], { description: "Tool presentation surface" }),
420
+ model: Type.Optional(Type.Object({
421
+ provider: Type.Optional(Type.String()),
422
+ api: Type.Optional(Type.String()),
423
+ id: Type.Optional(Type.String()),
424
+ })),
425
+ presentedTools: Type.Array(Type.String(), { description: "Tool names actually presented to the model" }),
426
+ blockedTools: Type.Array(Type.Object({
427
+ name: Type.String(),
428
+ reason: Type.String(),
429
+ }), { description: "Tool names blocked from the model with reasons" }),
430
+ aliases: Type.Optional(Type.Array(Type.Object({
431
+ requested: Type.String(),
432
+ canonical: Type.String(),
433
+ }))),
434
+ fallbackToolsUsed: Type.Optional(Type.Array(Type.String())),
435
+ toolPresentationPlanId: Type.Optional(Type.String()),
436
+ notes: Type.Optional(Type.String()),
437
+ });
438
+ const uatResultSaveTool = {
439
+ name: "gsd_uat_result_save",
440
+ label: "Save UAT Result",
441
+ description: "Save a structured UAT result for a slice. Validates evidence, writes the ASSESSMENT artifact, " +
442
+ "records attempt history, and saves the aggregate UAT gate result.",
443
+ promptSnippet: "Save structured UAT checks, evidence, verdict, and tool-presentation proof",
444
+ promptGuidelines: [
445
+ "Call gsd_uat_result_save once after all UAT checks have been executed.",
446
+ "Every PASS or FAIL check must cite objective evidence, preferably a gsd_uat_exec evidence ID.",
447
+ "Include the presented and blocked tool set in presentation so tool timing is auditable.",
448
+ "Do not use raw gsd_summary_save as a substitute for UAT results.",
449
+ ],
450
+ parameters: Type.Object({
451
+ milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
452
+ sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
453
+ uatType: StringEnum(["artifact-driven", "browser-executable", "runtime-executable", "live-runtime", "mixed", "human-experience"], { description: "Declared UAT mode" }),
454
+ verdict: StringEnum(["PASS", "FAIL", "PARTIAL"], { description: "Overall UAT verdict" }),
455
+ checks: Type.Array(uatCheck, { description: "Structured check results" }),
456
+ presentation: toolPresentationBlock,
457
+ notes: Type.Optional(Type.String({ description: "Overall verdict rationale" })),
458
+ attempt: Type.Optional(Type.String({ description: "Attempt number or auto" })),
459
+ previousAttemptId: Type.Optional(Type.String({ description: "Prior attempt ID, when retrying" })),
460
+ }),
461
+ execute: uatResultSaveExecute,
462
+ renderCall(args, theme) {
463
+ let text = theme.fg("toolTitle", theme.bold("uat_result_save "));
464
+ text += theme.fg("accent", `${args.milestoneId ?? "?"}/${args.sliceId ?? "?"}`);
465
+ if (args.verdict)
466
+ text += theme.fg("dim", ` → ${args.verdict}`);
467
+ return new Text(text, 0, 0);
468
+ },
469
+ renderResult(result, _options, theme) {
470
+ const d = readDetails(result);
471
+ if (result.isError || d?.error) {
472
+ return new Text(theme.fg("error", formatToolErrorText(result, d)), 0, 0);
473
+ }
474
+ return new Text(theme.fg("success", `UAT ${d?.sliceId ?? ""}: ${d?.verdict ?? "saved"}`), 0, 0);
475
+ },
476
+ };
477
+ pi.registerTool(uatResultSaveTool);
399
478
  // ─── gsd_milestone_generate_id (formerly gsd_generate_milestone_id) ────
400
479
  const milestoneGenerateIdExecute = async (_toolCallId, _params, _signal, _onUpdate, _ctx) => {
401
480
  try {
@@ -709,7 +788,7 @@ export function registerDbTools(pi) {
709
788
  recommendation: Type.String({ description: "Option id the executor recommends." }),
710
789
  recommendationRationale: Type.String({ description: "Why the recommendation — 1–2 sentences." }),
711
790
  continueWithDefault: Type.Boolean({
712
- description: "When true, loop continues (artifact logged for later review). When false, auto-mode pauses until the user resolves via /gsd escalate resolve.",
791
+ description: "When true, the recommendation is recorded as the default, but auto-mode still pauses until the user resolves via /gsd escalate resolve.",
713
792
  }),
714
793
  }, { description: "ADR-011 Phase 2: optional escalation payload. Only honored when phases.mid_execution_escalation is true." })),
715
794
  verificationEvidence: Type.Optional(Type.Array(Type.Object({
@@ -931,7 +1010,7 @@ export function registerDbTools(pi) {
931
1010
  promptGuidelines: [
932
1011
  "Use gsd_validate_milestone when all slices are done and the milestone needs validation before completion.",
933
1012
  "Parameters: milestoneId, verdict, remediationRound, successCriteriaChecklist, sliceDeliveryAudit, crossSliceIntegration, requirementCoverage, verificationClasses (optional), verdictRationale, remediationPlan (optional).",
934
- "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.",
1013
+ "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.",
935
1014
  "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.",
936
1015
  "If verdict is 'needs-remediation', also provide remediationPlan and use gsd_reassess_roadmap to add remediation slices to the roadmap.",
937
1016
  "On success, returns validationPath where VALIDATION.md was written.",
@@ -944,7 +1023,7 @@ export function registerDbTools(pi) {
944
1023
  sliceDeliveryAudit: Type.String({ description: "Markdown table auditing each slice's claimed vs delivered output" }),
945
1024
  crossSliceIntegration: Type.String({ description: "Markdown describing any cross-slice boundary mismatches" }),
946
1025
  requirementCoverage: Type.String({ description: "Markdown describing any unaddressed requirements" }),
947
- 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" })),
1026
+ 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)" })),
948
1027
  verdictRationale: Type.String({ description: "Why this verdict was chosen" }),
949
1028
  remediationPlan: Type.Optional(Type.String({ description: "Remediation plan (required if verdict is needs-remediation)" })),
950
1029
  }),
@@ -20,6 +20,49 @@ async function loadContextModePreferences(baseDir) {
20
20
  }
21
21
  }
22
22
  export function registerExecTools(pi) {
23
+ pi.registerTool({
24
+ name: "gsd_uat_exec",
25
+ label: "UAT Exec",
26
+ description: "Run a UAT-scoped bash/node/python check with milestone/slice/check metadata. " +
27
+ "Uses the same capped .gsd/exec evidence store as gsd_exec, but rejects commands that mutate dependencies, git state, credentials, or destructive files.",
28
+ promptSnippet: "Run one UAT check and save typed evidence under .gsd/exec",
29
+ promptGuidelines: [
30
+ "Use gsd_uat_exec for each automated UAT check.",
31
+ "Every PASS/FAIL check saved by gsd_uat_result_save must reference objective evidence from this tool or another approved GSD evidence path.",
32
+ "Do not install packages, mutate git state, edit source files, or dump credentials during UAT.",
33
+ ],
34
+ parameters: Type.Object({
35
+ milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
36
+ sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
37
+ checkId: Type.String({ description: "Stable check ID from the UAT spec (e.g. UAT-01)" }),
38
+ intent: Type.String({
39
+ description: "UAT command intent. Use one canonical value: uat-artifact-check, uat-runtime-check, " +
40
+ "uat-browser-check, uat-service-start, or uat-log-inspection. Short aliases such as artifact, " +
41
+ "runtime, browser, service-start, and log-inspection are accepted.",
42
+ }),
43
+ runtime: Type.Optional(Type.String({
44
+ description: "Optional interpreter. Defaults to bash. Supported: bash, node, python; sh/shell, js/nodejs, and py/python3 aliases are accepted.",
45
+ })),
46
+ script: Type.Optional(Type.String({ description: "Script body. Keep output small (log the finding, not the data)." })),
47
+ command: Type.Optional(Type.String({ description: "Alias for script; defaults to bash when runtime is omitted." })),
48
+ cmd: Type.Optional(Type.String({ description: "Short alias for script." })),
49
+ code: Type.Optional(Type.String({ description: "Alias for script, useful for node/python snippets." })),
50
+ expected: Type.Optional(Type.String({ description: "Expected outcome for this UAT check." })),
51
+ timeout_ms: Type.Optional(Type.Number({
52
+ description: "Per-invocation timeout (ms). Capped at 600000. Default from preferences.",
53
+ minimum: 1_000,
54
+ maximum: 600_000,
55
+ })),
56
+ }),
57
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
58
+ const { executeUatExec } = await import("../tools/exec-tool.js");
59
+ const baseDir = resolveCtxCwd(_ctx);
60
+ return executeUatExec(params, {
61
+ baseDir,
62
+ preferences: await loadContextModePreferences(baseDir),
63
+ });
64
+ },
65
+ });
23
66
  pi.registerTool({
24
67
  name: "gsd_exec",
25
68
  label: "Exec (Sandboxed)",
@@ -28,7 +28,9 @@ import { approvalGateIdForUnit, isExplicitApprovalResponse, shouldPauseForUserAp
28
28
  import { applyUnitSkillVisibility, unitHasSkillManifest } from "../skill-scope.js";
29
29
  import { getGuidedUnitContext } from "../guided-unit-context.js";
30
30
  import { registerPlanMilestoneSchemaRecovery } from "./plan-milestone-schema-recovery.js";
31
- import { AUTO_UNIT_SCOPED_TOOLS, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
31
+ import { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
32
+ import { filterToolsForProvider } from "../model-router.js";
33
+ import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
32
34
  let approvalQuestionAbortInFlight = false;
33
35
  async function loadWelcomeScreenModule() {
34
36
  const candidates = [];
@@ -104,6 +106,7 @@ export const MINIMAL_GSD_TOOL_NAMES = [
104
106
  "gsd_resume",
105
107
  "gsd_milestone_status",
106
108
  "gsd_checkpoint_db",
109
+ "gsd_plan_milestone",
107
110
  "memory_query",
108
111
  "capture_thought",
109
112
  ];
@@ -187,6 +190,9 @@ export function buildMinimalGsdToolSet(activeToolNames) {
187
190
  return withPreservedShimTools([...new Set([...preserved, ...minimal])]);
188
191
  }
189
192
  export function buildMinimalAutoGsdToolSet(activeToolNames, unitType, registeredToolNames = activeToolNames) {
193
+ if (unitType === "run-uat") {
194
+ return buildRunUatGsdToolSet(activeToolNames, registeredToolNames);
195
+ }
190
196
  const unitTools = unitType ? AUTO_UNIT_SCOPED_TOOLS[unitType] ?? [] : [];
191
197
  const autoBaseTools = new Set(MINIMAL_AUTO_BASE_TOOL_NAMES);
192
198
  const availableBaseTools = registeredToolNames.filter((name) => autoBaseTools.has(name));
@@ -197,6 +203,10 @@ export function buildMinimalAutoGsdToolSet(activeToolNames, unitType, registered
197
203
  const scoped = resolveScopedToolNames([...activeToolNames, ...registeredToolNames], [...MINIMAL_GSD_TOOL_NAMES, ...unitTools]);
198
204
  return withPreservedShimTools([...new Set([...preserved, ...scoped])]);
199
205
  }
206
+ export function buildRunUatGsdToolSet(activeToolNames, registeredToolNames = activeToolNames) {
207
+ const scoped = resolveScopedToolNames([...activeToolNames, ...registeredToolNames], [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent", ...RUN_UAT_BROWSER_TOOL_NAMES]);
208
+ return [...new Set(scoped)];
209
+ }
200
210
  export function buildMinimalGsdWorkflowToolSet(activeToolNames, registeredToolNames = activeToolNames) {
201
211
  const autoBaseTools = new Set(MINIMAL_AUTO_BASE_TOOL_NAMES);
202
212
  const availableBaseTools = registeredToolNames.filter((name) => autoBaseTools.has(name));
@@ -836,10 +846,8 @@ export function registerHooks(pi, ecosystemHandlers) {
836
846
  if (result.block)
837
847
  return result;
838
848
  });
839
- // ── Safety harness: evidence collection + destructive command warnings ──
849
+ // ── Safety harness: evidence collection + destructive command blocking ──
840
850
  pi.on("tool_call", async (event, ctx) => {
841
- if (!isAutoActive())
842
- return;
843
851
  markToolStart(event.toolCallId, event.toolName);
844
852
  safetyRecordToolCall(event.toolCallId, event.toolName, event.input);
845
853
  // Persist immediately at dispatch so a mid-unit re-dispatch — which calls
@@ -854,14 +862,23 @@ export function registerHooks(pi, ecosystemHandlers) {
854
862
  saveEvidenceToDisk(callDash.basePath, cMid, cSid, cTid);
855
863
  }
856
864
  }
857
- // Destructive command classification (warn only, never block)
865
+ // Destructive command classification + hard gate in all modes.
858
866
  if (isToolCallEventType("bash", event)) {
859
867
  const classification = classifyCommand(event.input.command);
860
868
  if (classification.destructive) {
869
+ const reason = [
870
+ "HARD BLOCK: destructive Bash command requires explicit human confirmation.",
871
+ `Detected: ${classification.labels.join(", ")}`,
872
+ "Run this via ask_user_questions, wait for the user's response,",
873
+ "then issue the command only when confirmed in the current turn.",
874
+ ].join(" ");
861
875
  safetyLogWarning("safety", `destructive command: ${classification.labels.join(", ")}`, {
862
876
  command: String(event.input.command).slice(0, 200),
863
877
  });
864
- ctx.ui.notify(`Destructive command detected: ${classification.labels.join(", ")}`, "warning");
878
+ if (ctx) {
879
+ await maybePauseAutoForApprovalGate(ctx, pi, isAutoActive(), "Depth confirmation is waiting for your answer — pausing auto-mode.");
880
+ }
881
+ return { block: true, reason };
865
882
  }
866
883
  }
867
884
  });
@@ -1123,21 +1140,25 @@ export function registerHooks(pi, ecosystemHandlers) {
1123
1140
  const fullToolsRequested = isFullGsdToolSurfaceRequested();
1124
1141
  const dropAliases = !fullToolsRequested;
1125
1142
  const dropBrowser = !fullToolsRequested && !isBrowserToolSurfaceRequested();
1126
- const providerCompatible = compatible.filter((name) => !(dropAliases && isWorkflowAliasTool(name)) && !(dropBrowser && isBrowserTool(name)));
1143
+ const aliasFilteredCompatible = compatible.filter((name) => !(dropAliases && isWorkflowAliasTool(name)));
1144
+ const providerCompatible = aliasFilteredCompatible.filter((name) => !(dropBrowser && isBrowserTool(name)));
1127
1145
  const surfaceReduced = providerCompatible.length !== compatible.length;
1128
1146
  if (fullToolsRequested) {
1129
1147
  return surfaceReduced ? { toolNames: providerCompatible } : undefined;
1130
1148
  }
1131
1149
  const registeredToolNames = resolveRegisteredToolNames(pi, event.activeToolNames);
1150
+ const compatibleRegisteredToolNames = filterToolsForProvider(registeredToolNames, event.selectedModelApi, event.selectedModelProvider).compatible.filter((name) => !(dropAliases && isWorkflowAliasTool(name)));
1132
1151
  const guidedUnit = getGuidedUnitContext();
1133
- const requestScoped = buildRequestScopedGsdToolSet(providerCompatible, event.requestCustomMessages, registeredToolNames, guidedUnit?.unitType);
1152
+ const requestScoped = buildRequestScopedGsdToolSet(guidedUnit?.unitType === "run-uat" ? aliasFilteredCompatible : providerCompatible, event.requestCustomMessages, guidedUnit?.unitType === "run-uat" ? compatibleRegisteredToolNames : registeredToolNames, guidedUnit?.unitType);
1134
1153
  if (requestScoped) {
1135
1154
  return { toolNames: requestScoped };
1136
1155
  }
1137
1156
  const dash = getAutoRuntimeSnapshot();
1138
1157
  if (dash.active && dash.currentUnit) {
1139
1158
  return {
1140
- toolNames: buildMinimalAutoGsdToolSet(providerCompatible, dash.currentUnit.type, resolveRegisteredToolNames(pi, event.activeToolNames)),
1159
+ toolNames: buildMinimalAutoGsdToolSet(dash.currentUnit.type === "run-uat" ? aliasFilteredCompatible : providerCompatible, dash.currentUnit.type, dash.currentUnit.type === "run-uat"
1160
+ ? compatibleRegisteredToolNames
1161
+ : resolveRegisteredToolNames(pi, event.activeToolNames)),
1141
1162
  };
1142
1163
  }
1143
1164
  if (isGeneralGsdToolScopingRequested()) {
@@ -565,6 +565,7 @@ const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
565
565
  * manifests still declare per-unit subsets via ToolsPolicy.allowedSubagents.
566
566
  */
567
567
  const PLANNING_DISPATCH_AGENT_REGISTRY = {
568
+ mnemo: { readOnlySpecialist: true },
568
569
  scout: { readOnlySpecialist: true },
569
570
  planner: { readOnlySpecialist: true },
570
571
  reviewer: { readOnlySpecialist: true },
@@ -574,7 +575,7 @@ const PLANNING_DISPATCH_AGENT_REGISTRY = {
574
575
  export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set(Object.entries(PLANNING_DISPATCH_AGENT_REGISTRY)
575
576
  .filter(([, metadata]) => metadata.readOnlySpecialist)
576
577
  .map(([agentId]) => agentId));
577
- let warnedMissingPlanningDispatchAgentClasses = false;
578
+ let warnedMissingControlledDispatchAgentClasses = false;
578
579
  function isReadOnlySpecialist(agentId) {
579
580
  const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId];
580
581
  return metadata?.readOnlySpecialist === true;
@@ -582,12 +583,16 @@ function isReadOnlySpecialist(agentId) {
582
583
  function allowedPlanningDispatchAgentsList() {
583
584
  return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
584
585
  }
585
- function warnMissingPlanningDispatchAgentClasses(unitType, mode, toolName) {
586
- if (warnedMissingPlanningDispatchAgentClasses)
586
+ function allowsControlledSubagentDispatch(policy) {
587
+ return ((policy.mode === "planning-dispatch" || policy.mode === "verification") &&
588
+ Array.isArray(policy.allowedSubagents));
589
+ }
590
+ function warnMissingControlledDispatchAgentClasses(unitType, mode, toolName) {
591
+ if (warnedMissingControlledDispatchAgentClasses)
587
592
  return;
588
- warnedMissingPlanningDispatchAgentClasses = true;
593
+ warnedMissingControlledDispatchAgentClasses = true;
589
594
  // TODO(#5060): Remove this migration shim once all subagent/task callers are verified to forward agent identities.
590
- const message = `[write-gate] planning-dispatch: shouldBlockPlanningUnit called for tool "${toolName}" ` +
595
+ const message = `[write-gate] controlled-dispatch: shouldBlockPlanningUnit called for tool "${toolName}" ` +
591
596
  `on unit "${unitType}" without agentClasses - stale caller; blocking dispatch.`;
592
597
  console.warn(message);
593
598
  logWarning("intercept", message, {
@@ -653,8 +658,9 @@ function blockReason(unitType, mode, what) {
653
658
  * - "docs" → like "planning" but also allows writes to paths
654
659
  * matching `allowedPathGlobs` relative to basePath.
655
660
  * - "verification"
656
- * → allows Bash for project verification commands, but keeps
657
- * writes restricted to .gsd/ and blocks subagent dispatch.
661
+ * → allows Bash for project verification commands, keeps
662
+ * writes restricted to .gsd/, and permits subagent dispatch
663
+ * only when the manifest declares allowedSubagents.
658
664
  *
659
665
  * `pathOrCommand` is the file path for write/edit-shaped tools and the
660
666
  * shell command for bash. Other tools ignore this argument.
@@ -695,7 +701,7 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
695
701
  if (tool.startsWith("gsd_"))
696
702
  return { block: false };
697
703
  if (PLANNING_SUBAGENT_TOOLS.has(tool)) {
698
- if (policy.mode === "planning-dispatch") {
704
+ if (allowsControlledSubagentDispatch(policy)) {
699
705
  const requested = (agentClasses ?? []).map(a => a.trim()).filter(Boolean);
700
706
  const dispatchContract = compileSubagentPermissionContract(policy);
701
707
  const allowedSubagents = dispatchContract.allowedSubagents;
@@ -704,7 +710,7 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
704
710
  // agent identities yet. Block and warn so stale callers surface in telemetry
705
711
  // instead of silently bypassing the gate.
706
712
  if (agentClasses === undefined) {
707
- warnMissingPlanningDispatchAgentClasses(unitType, policy.mode, tool);
713
+ warnMissingControlledDispatchAgentClasses(unitType, policy.mode, tool);
708
714
  return {
709
715
  block: true,
710
716
  reason: blockReason(unitType, policy.mode, `subagent dispatch blocked: stale caller did not supply agent identities for "${tool}"; update extractSubagentAgentClasses to handle this input shape`),
@@ -720,7 +726,7 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
720
726
  if (globallyDisallowed) {
721
727
  return {
722
728
  block: true,
723
- reason: blockReason(unitType, policy.mode, `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from planning-dispatch units`),
729
+ reason: blockReason(unitType, policy.mode, `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`),
724
730
  };
725
731
  }
726
732
  const disallowedByPolicy = requested.find(a => !allowed.has(a));
@@ -1,17 +1,44 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: Shared browser-observable UAT requirement and evidence detection.
3
- 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;
3
+ 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;
4
4
  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;
5
5
  export const BROWSER_RUNTIME_RE = /\b(?:browser|playwright|chrome|camoufox|browser_(?:assert|batch|find|verify|snapshot_refs)|screenshot|snapshot|file:\/\/|localhost)\b/i;
6
6
  export const BROWSER_ACTION_RE = /\b(?:open(?:ed)?|navigate(?:d)?|click(?:ed)?|type(?:d)?|reload(?:ed)?|capture(?:d)?|screenshot|snapshot)\b/i;
7
7
  export const BROWSER_ASSERTION_RE = /\b(?:assert(?:ed|ion)?|observed|confirmed|verified|expected|visible|text|count|label|strikethrough|localstorage|screenshot|snapshot|passed)\b/i;
8
+ 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;
9
+ 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;
8
10
  export function compactTextParts(parts) {
9
11
  return parts.flatMap((part) => Array.isArray(part) ? part : [part])
10
12
  .filter((part) => typeof part === "string" && part.trim().length > 0)
11
13
  .join("\n");
12
14
  }
13
15
  export function hasBrowserRequiredText(text) {
14
- return BROWSER_REQUIREMENT_RE.test(text);
16
+ let inNonRequirementSection = false;
17
+ let nonRequirementDepth = 0;
18
+ for (const line of text.split(/\r?\n/)) {
19
+ const headingMatch = line.match(/^(#{1,6})\s+(.+?)\s*$/);
20
+ if (headingMatch) {
21
+ const depth = headingMatch[1].length;
22
+ const title = headingMatch[2] ?? "";
23
+ // Only update section context when at the same or higher level than the
24
+ // heading that opened the non-requirement zone. A sub-heading deeper than
25
+ // the opening heading must not escape or re-enter the zone on its own.
26
+ if (!inNonRequirementSection || depth <= nonRequirementDepth) {
27
+ inNonRequirementSection = NON_REQUIREMENT_BROWSER_HEADING_RE.test(title);
28
+ nonRequirementDepth = inNonRequirementSection ? depth : 0;
29
+ }
30
+ // Check the heading title itself — section state is already updated, so
31
+ // we correctly skip headings that opened a non-requirement zone.
32
+ if (!inNonRequirementSection && BROWSER_REQUIREMENT_RE.test(title))
33
+ return true;
34
+ continue;
35
+ }
36
+ if (inNonRequirementSection || NON_REQUIREMENT_BROWSER_LINE_RE.test(line))
37
+ continue;
38
+ if (BROWSER_REQUIREMENT_RE.test(line))
39
+ return true;
40
+ }
41
+ return false;
15
42
  }
16
43
  export function hasBrowserEvidenceText(text) {
17
44
  if (!text.trim())
@@ -69,7 +69,7 @@ export function showHelp(ctx, args = "") {
69
69
  " /gsd new-milestone Create milestone from headless context (used by gsd headless)",
70
70
  " /gsd new-project Bootstrap a new project (use --deep for staged project-level discovery)",
71
71
  " /gsd quick Execute a quick task without full planning overhead",
72
- " /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|uat|replan]",
72
+ " /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|validate|reassess|uat|replan]",
73
73
  " /gsd verdict <v> Override milestone validation verdict [pass|needs-attention|needs-remediation] [--milestone Mxxx] [--rationale \"...\"]",
74
74
  " /gsd parallel Parallel milestone orchestration [start|status|stop|pause|resume|merge|watch]",
75
75
  " /gsd workflow Custom workflow lifecycle [new|run|list|validate|pause|resume]",
@@ -11,6 +11,8 @@ import { fileURLToPath } from "node:url";
11
11
  import { getGlobalGSDPreferencesPath, getLegacyGlobalGSDPreferencesPath, getProjectGSDPreferencesPath, loadGlobalGSDPreferences, loadProjectGSDPreferences, loadEffectiveGSDPreferences, normalizePreferencesShape, resolveAllSkillReferences, } from "./preferences.js";
12
12
  import { loadFile, saveFile, splitFrontmatter, parseFrontmatterMap } from "./files.js";
13
13
  import { runClaudeImportFlow } from "./claude-import.js";
14
+ const DEFAULT_WIDGET_MODE = "small";
15
+ const WIDGET_MODE_OPTIONS = [DEFAULT_WIDGET_MODE, "full", "min", "off"];
14
16
  /** Extract body content after frontmatter closing delimiter, or null if none. */
15
17
  function extractBodyAfterFrontmatter(content) {
16
18
  const closingIdx = content.indexOf("\n---", content.indexOf("---"));
@@ -1509,7 +1511,7 @@ async function configureAdvanced(ctx, prefs) {
1509
1511
  else if (minRequestInterval !== undefined) {
1510
1512
  prefs.min_request_interval_ms = minRequestInterval;
1511
1513
  }
1512
- const widget = await promptEnum(ctx, "Auto-mode widget display", prefs.widget_mode, ["full", "small", "min", "off"], "full");
1514
+ const widget = await promptEnum(ctx, "Auto-mode widget display", prefs.widget_mode, WIDGET_MODE_OPTIONS, DEFAULT_WIDGET_MODE);
1513
1515
  if (widget !== undefined)
1514
1516
  prefs.widget_mode = widget;
1515
1517
  const experimental = prefs.experimental ?? {};
@@ -166,6 +166,6 @@ export async function handleVerdict(rawArgs, ctx, basePath) {
166
166
  ctx.ui.notify(`Milestone ${milestoneId} verdict: ${prevVerdict} -> ${effectiveVerdict} (${existingValidation.source})`, "success");
167
167
  }
168
168
  if (effectiveVerdict === "needs-remediation") {
169
- ctx.ui.notify("Follow up with gsd_reassess_roadmap to add remediation slices, then re-run /gsd auto.", "info");
169
+ ctx.ui.notify("Follow up with /gsd dispatch reassess to add remediation slices, then re-run /gsd auto.", "info");
170
170
  }
171
171
  }
@@ -9,6 +9,7 @@
9
9
  import { matchesKey, Key, truncateToWidth } from "@gsd/pi-tui";
10
10
  import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
11
11
  import { loadEffectiveGSDPreferences, loadGlobalGSDPreferences, loadProjectGSDPreferences, getGlobalGSDPreferencesPath, getProjectGSDPreferencesPath, resolveDynamicRoutingConfig, resolveEffectiveProfile, resolveModelWithFallbacksForUnit, resolveAutoSupervisorConfig, } from "./preferences.js";
12
+ const DEFAULT_WIDGET_MODE = "small";
12
13
  function collectConfigSections() {
13
14
  const sections = [];
14
15
  const globalPrefs = loadGlobalGSDPreferences();
@@ -157,7 +158,7 @@ function collectConfigSections() {
157
158
  toggleRows.push({ label: "search_provider", value: prefs.search_provider });
158
159
  if (prefs?.context_selection)
159
160
  toggleRows.push({ label: "context_selection", value: prefs.context_selection });
160
- if (prefs?.widget_mode && prefs.widget_mode !== "full")
161
+ if (prefs?.widget_mode && prefs.widget_mode !== DEFAULT_WIDGET_MODE)
161
162
  toggleRows.push({ label: "widget_mode", value: prefs.widget_mode });
162
163
  if (prefs?.experimental?.rtk)
163
164
  toggleRows.push({ label: "experimental.rtk", value: "on" });