@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
@@ -32,6 +32,8 @@ export interface RuleLifecycle {
32
32
  retry_on?: string;
33
33
  /** Max times this hook can fire for the same trigger unit. */
34
34
  max_cycles?: number;
35
+ /** Whether this hook is advisory or blocking. */
36
+ criticality?: PostUnitHookConfig["criticality"];
35
37
  /** Idempotency key pattern for this hook. */
36
38
  idempotency_key?: string;
37
39
  }
@@ -24,6 +24,9 @@ const DESTRUCTIVE_PATTERNS: readonly DestructivePattern[] = [
24
24
  { pattern: /\btruncate\s+table\b/i, label: "SQL truncate" },
25
25
  { pattern: /\bchmod\s+777\b/, label: "world-writable permissions" },
26
26
  { pattern: /\bcurl\s.*\|\s*(bash|sh|zsh)\b/, label: "pipe to shell" },
27
+ { pattern: /\bterra(form|grunt)\s+(apply|destroy)/i, label: "IaC apply/destroy" },
28
+ { pattern: /\baws\s+\w+\s+(delete|create|put|remove|terminate)\b/i, label: "AWS mutation" },
29
+ { pattern: /\bkubectl\s+(delete|apply)\b/i, label: "kubectl mutation" },
27
30
  ];
28
31
 
29
32
  // ─── Public API ─────────────────────────────────────────────────────────────
@@ -50,6 +50,16 @@ function tokenizeSkillContext(...parts: Array<string | null | undefined>): Set<s
50
50
  return tokens;
51
51
  }
52
52
 
53
+ function tokenizeUnitType(unitType: string | undefined): Set<string> {
54
+ const tokens = new Set<string>();
55
+ const value = unitType?.trim().toLowerCase();
56
+ if (!value) return tokens;
57
+ tokens.add(value);
58
+ tokens.add(value.replace(/[-_]+/g, " "));
59
+ tokens.add(value.replace(/[-_\s]+/g, ""));
60
+ return tokens;
61
+ }
62
+
53
63
  function skillMatchesContext(skill: Skill, contextTokens: Set<string>): boolean {
54
64
  const haystacks = [
55
65
  skill.name.toLowerCase(),
@@ -79,17 +89,25 @@ function ruleMatchesContext(when: string, contextTokens: Set<string>): boolean {
79
89
  );
80
90
  }
81
91
 
92
+ function ruleMatchesUnitType(when: string, unitType: string | undefined): boolean {
93
+ if (!unitType) return false;
94
+ const whenTokens = tokenizeSkillContext(when);
95
+ const unitTokens = tokenizeUnitType(unitType);
96
+ return [...unitTokens].some(token => whenTokens.has(token));
97
+ }
98
+
82
99
  function resolveSkillRuleMatches(
83
100
  prefs: GSDPreferences | undefined,
84
101
  contextTokens: Set<string>,
85
102
  base: string,
103
+ unitType?: string,
86
104
  ): { include: string[]; avoid: string[] } {
87
105
  if (!prefs?.skill_rules?.length) return { include: [], avoid: [] };
88
106
 
89
107
  const include: string[] = [];
90
108
  const avoid: string[] = [];
91
109
  for (const rule of prefs.skill_rules) {
92
- if (!ruleMatchesContext(rule.when, contextTokens)) continue;
110
+ if (!ruleMatchesContext(rule.when, contextTokens) && !ruleMatchesUnitType(rule.when, unitType)) continue;
93
111
  include.push(...resolvePreferenceSkillNames([...(rule.use ?? []), ...(rule.prefer ?? [])], base));
94
112
  avoid.push(...resolvePreferenceSkillNames(rule.avoid ?? [], base));
95
113
  }
@@ -196,7 +214,7 @@ export function buildSkillActivationBlock(params: {
196
214
  matched.add(name);
197
215
  }
198
216
 
199
- const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base);
217
+ const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base, params.unitType);
200
218
  for (const name of ruleMatches.include) matched.add(name);
201
219
  for (const name of ruleMatches.avoid) avoided.add(name);
202
220
 
@@ -9,6 +9,7 @@ import { existsSync, readFileSync } from "node:fs";
9
9
  import {
10
10
  getMilestone,
11
11
  getMilestoneSlices,
12
+ getSliceTasks,
12
13
  isDbAvailable,
13
14
  } from "../../gsd-db.js";
14
15
  import { renderRoadmapFromDb } from "../../markdown-renderer.js";
@@ -30,6 +31,19 @@ function arraysEqual(a: readonly string[], b: readonly string[]): boolean {
30
31
  return true;
31
32
  }
32
33
 
34
+ function getSlicesReadyForDivergenceCheck(
35
+ milestoneId: string,
36
+ dbSlices: ReturnType<typeof getMilestoneSlices>,
37
+ ): Set<string> {
38
+ const ready = new Set<string>();
39
+ for (const slice of dbSlices) {
40
+ if (isClosedStatus(slice.status) || getSliceTasks(milestoneId, slice.id).length > 0) {
41
+ ready.add(slice.id);
42
+ }
43
+ }
44
+ return ready;
45
+ }
46
+
33
47
  function milestoneHasDivergence(
34
48
  basePath: string,
35
49
  milestoneId: string,
@@ -46,6 +60,10 @@ function milestoneHasDivergence(
46
60
 
47
61
  const dbSlices = getMilestoneSlices(milestoneId);
48
62
  const dbSliceMap = new Map(dbSlices.map((s) => [s.id, s]));
63
+ const readySliceIds = getSlicesReadyForDivergenceCheck(milestoneId, dbSlices);
64
+ if (dbSlices.length > 0 && readySliceIds.size === 0) {
65
+ return false;
66
+ }
49
67
  const roadmapSliceIds = new Set<string>();
50
68
 
51
69
  for (let i = 0; i < roadmap.slices.length; i++) {
@@ -54,11 +72,13 @@ function milestoneHasDivergence(
54
72
  const expectedSequence = i + 1;
55
73
  const dbSlice = dbSliceMap.get(roadmapSlice.id);
56
74
  if (!dbSlice) return true; // Roadmap has a slice the DB doesn't.
75
+ if (!readySliceIds.has(dbSlice.id)) continue;
57
76
  if (dbSlice.sequence !== expectedSequence) return true;
58
77
  if (!arraysEqual(dbSlice.depends, roadmapSlice.depends)) return true;
59
78
  if (isClosedStatus(dbSlice.status) !== roadmapSlice.done) return true;
60
79
  }
61
80
  for (const dbSlice of dbSlices) {
81
+ if (!readySliceIds.has(dbSlice.id)) continue;
62
82
  if (!roadmapSliceIds.has(dbSlice.id)) return true;
63
83
  }
64
84
  return false;
@@ -6,6 +6,7 @@ import {
6
6
  deriveState as defaultDeriveState,
7
7
  invalidateStateCache as defaultInvalidate,
8
8
  } from "../state.js";
9
+ import { clearParseCache as defaultClearParseCache } from "../files.js";
9
10
  import type { GSDState } from "../types.js";
10
11
 
11
12
  import {
@@ -37,6 +38,7 @@ const MAX_PASSES = 2;
37
38
  const defaultDeps: ReconciliationDeps = {
38
39
  invalidateStateCache: defaultInvalidate,
39
40
  deriveState: defaultDeriveState,
41
+ clearParseCache: defaultClearParseCache,
40
42
  };
41
43
 
42
44
  /**
@@ -58,6 +60,7 @@ export async function reconcileBeforeDispatch(
58
60
  deps: ReconciliationDeps = defaultDeps,
59
61
  ): Promise<ReconciliationResult> {
60
62
  const registry = deps.registry ?? DRIFT_REGISTRY;
63
+ const clearParseCache = deps.clearParseCache ?? defaultClearParseCache;
61
64
  const repaired: DriftRecord[] = [];
62
65
 
63
66
  for (let pass = 0; pass < MAX_PASSES; pass++) {
@@ -103,6 +106,9 @@ export async function reconcileBeforeDispatch(
103
106
  }
104
107
  }
105
108
 
109
+ if (repairedThisPass) {
110
+ clearParseCache();
111
+ }
106
112
  if (blockers.length > 0) {
107
113
  let blockerState = stateSnapshot;
108
114
  if (repairedThisPass) {
@@ -101,6 +101,7 @@ export interface ReconciliationDeps {
101
101
  basePath: string,
102
102
  opts?: DeriveStateOptions,
103
103
  ) => Promise<GSDState>;
104
+ clearParseCache?: () => void;
104
105
  /**
105
106
  * Override of the drift handler catalog. Defaults to DRIFT_REGISTRY. Each
106
107
  * handler is parameterized over its own DriftRecord variant; the union of
@@ -83,7 +83,7 @@ function formatNeedsRemediationBlocker(milestoneId: string): string {
83
83
  return [
84
84
  `Milestone ${milestoneId} is blocked because milestone validation returned needs-remediation, but all slices are complete.`,
85
85
  `Fix options:`,
86
- `1. Add remediation slices with \`gsd_reassess_roadmap\`, then run \`/gsd auto\``,
86
+ `1. Run \`/gsd dispatch reassess\` to add remediation slices, then run \`/gsd auto\``,
87
87
  `2. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
88
88
  `3. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
89
89
  ].join("\n");
@@ -896,8 +896,8 @@ export async function deriveStateFromDb(
896
896
  }
897
897
 
898
898
  // ADR-011 Phase 2: pause-on-escalation takes precedence over dispatching the
899
- // next task. `awaiting_review` tasks (continueWithDefault=true) are NOT
900
- // surfaced here they let the loop continue.
899
+ // next task. `awaiting_review` tasks (continueWithDefault=true) still pause
900
+ // here so silence is never treated as consent.
901
901
  //
902
902
  // We do NOT gate this on `phases.mid_execution_escalation` — creation of
903
903
  // new escalations is gated at the write site (tools/complete-task.ts:315),
@@ -132,14 +132,16 @@
132
132
  Verify field rules:
133
133
  - MUST be a mechanically executable command: `npm test`, `grep -q "pattern" file`, `test -f path`
134
134
  - MUST NOT use shell pipes, redirects, semicolons, backticks, command substitution, output trimming, or grep regex alternation with `|`
135
+ - For absence checks, use `! grep -q "pattern" file` or `! rg -q "pattern" file`; do not use `grep -c` or `rg -c` to assert zero matches because count commands exit 1 when they find zero matches
135
136
  - MUST NOT use inline `node -e` assertions for verification; put assertions in a real test file and run it with `node --test` or a package test script
136
137
  - For content/document tasks: verify file existence, section count, YAML validity, or word count
137
138
  NOT exact phrasing, specific formulas, or "zero TBD" aspirational criteria
138
139
  - If no command can verify the output, write: "Manual review — file exists and is non-empty"
139
140
  - BAD: `python3 -m pytest tests/ -q --tb=short 2>&1 | tail -5`
141
+ - BAD: `grep -c "old_api" src/index.ts`
140
142
  - BAD: "Sections 3.1 and 3.2 exist with exact formulas. Zero TBD/TODO."
141
143
  - GOOD: `python3 -m pytest tests/ -q --tb=short`
142
- - GOOD: `node --test tests/verify-doc.test.js`, `grep -q "Required heading" doc.md`, `test -s doc.md`
144
+ - GOOD: `node --test tests/verify-doc.test.js`, `grep -q "Required heading" doc.md`, `! grep -q "old_api" src/index.ts`, `test -s doc.md`
143
145
 
144
146
  Integration closure rule:
145
147
  - At least one slice in any multi-boundary milestone should perform real composition/wiring, not just contract hardening
@@ -4,6 +4,7 @@ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { execFileSync } from "node:child_process";
7
+ import { visibleWidth } from "@gsd/pi-tui";
7
8
 
8
9
  import {
9
10
  unitVerb,
@@ -16,11 +17,13 @@ import {
16
17
  buildPhaseHandoffOutcome,
17
18
  updateProgressWidget,
18
19
  setAutoOutcomeWidget,
20
+ setAutoActiveStatus,
19
21
  setCompletionProgressWidget,
20
22
  getRoadmapSlicesSync,
21
23
  clearSliceProgressCache,
22
24
  getWidgetMode,
23
25
  cycleWidgetMode,
26
+ setWidgetMode,
24
27
  _resetWidgetModeForTests,
25
28
  _resetLastCommitCacheForTests,
26
29
  _refreshLastCommitForTests,
@@ -55,6 +58,17 @@ function cleanup(dir: string): void {
55
58
  }
56
59
  }
57
60
 
61
+ function assertLinesFit(lines: string[], width: number): void {
62
+ for (const line of lines) {
63
+ assert.ok(
64
+ visibleWidth(line) <= width,
65
+ `line exceeds width ${width}: ${visibleWidth(line)} "${line}"`,
66
+ );
67
+ }
68
+ }
69
+
70
+ type RenderableWidget = { render(width: number): string[]; invalidate(): void; dispose?: () => void };
71
+
58
72
  // ─── unitVerb ─────────────────────────────────────────────────────────────
59
73
 
60
74
  test("unitVerb maps known unit types to verbs", () => {
@@ -78,6 +92,26 @@ test("unitVerb handles hook types", () => {
78
92
  assert.equal(unitVerb("hook/"), "hook: ");
79
93
  });
80
94
 
95
+ test("setAutoActiveStatus clears stale outcome surfaces", () => {
96
+ const statusCalls: Array<[string, string]> = [];
97
+ const widgetCalls: Array<[string, unknown]> = [];
98
+
99
+ setAutoActiveStatus({
100
+ hasUI: true,
101
+ ui: {
102
+ setStatus: (key: string, value: string) => {
103
+ statusCalls.push([key, value]);
104
+ },
105
+ setWidget: (key: string, value: unknown) => {
106
+ widgetCalls.push([key, value]);
107
+ },
108
+ },
109
+ } as any, "next");
110
+
111
+ assert.deepEqual(statusCalls, [["gsd-auto", "next"]]);
112
+ assert.deepEqual(widgetCalls, [["gsd-outcome", undefined]]);
113
+ });
114
+
81
115
  // ─── unitPhaseLabel ───────────────────────────────────────────────────────
82
116
 
83
117
  test("unitPhaseLabel maps known types to labels", () => {
@@ -571,14 +605,21 @@ test("updateProgressWidget refreshes slice progress cache immediately", (t) => {
571
605
  test("updateProgressWidget full mode keeps footer-owned signals out of auto deck", (t) => {
572
606
  const dir = makeTempDir("command-deck");
573
607
  mkdirSync(join(dir, ".gsd"), { recursive: true });
608
+ const projectPrefsPath = join(dir, ".gsd", "preferences.md");
609
+ const globalPrefsPath = join(dir, ".gsd", "global-preferences.md");
610
+ writeFileSync(projectPrefsPath, "---\nversion: 1\n---\n", "utf-8");
574
611
  let widget: { render(width: number): string[]; dispose?: () => void } | null = null;
575
612
 
576
613
  t.after(() => {
577
614
  widget?.dispose?.();
615
+ _resetWidgetModeForTests();
578
616
  clearSliceProgressCache();
579
617
  cleanup(dir);
580
618
  });
581
619
 
620
+ _resetWidgetModeForTests();
621
+ setWidgetMode("full", projectPrefsPath, globalPrefsPath);
622
+
582
623
  updateProgressWidget(
583
624
  {
584
625
  hasUI: true,
@@ -633,6 +674,95 @@ test("updateProgressWidget full mode keeps footer-owned signals out of auto deck
633
674
  assert.doesNotMatch(rendered, /\$/, "footer owns session cost display");
634
675
  });
635
676
 
677
+ test("updateProgressWidget small mode renders the dense horizontal grid", (t) => {
678
+ const dir = makeTempDir("small-dense-grid");
679
+ const homeDir = makeTempDir("small-dense-grid-home");
680
+ const projectPrefsPath = join(dir, ".gsd", "preferences.md");
681
+ const globalPrefsPath = join(homeDir, ".gsd", "preferences.md");
682
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
683
+ mkdirSync(join(homeDir, ".gsd"), { recursive: true });
684
+ writeFileSync(projectPrefsPath, "---\nversion: 1\nwidget_mode: full\n---\n", "utf-8");
685
+ writeFileSync(globalPrefsPath, "---\nversion: 1\nwidget_mode: full\n---\n", "utf-8");
686
+
687
+ const holder: { widget?: RenderableWidget } = {};
688
+
689
+ t.after(() => {
690
+ holder.widget?.dispose?.();
691
+ closeDatabase();
692
+ clearSliceProgressCache();
693
+ _resetWidgetModeForTests();
694
+ cleanup(dir);
695
+ cleanup(homeDir);
696
+ });
697
+
698
+ openDatabase(join(dir, ".gsd", "gsd.db"));
699
+ insertMilestone({ id: "M004", title: "Budget Tracking", status: "active" });
700
+ insertSlice({ milestoneId: "M004", id: "S01", title: "Schema migration", status: "complete", sequence: 1 });
701
+ insertSlice({ milestoneId: "M004", id: "S02", title: "Expense add", status: "pending", sequence: 2 });
702
+ insertTask({ milestoneId: "M004", sliceId: "S01", id: "T01", title: "Add repeat column via idempotent ALTER TABLE", status: "complete" });
703
+ insertTask({ milestoneId: "M004", sliceId: "S01", id: "T02", title: "Backfill repeat metadata", status: "pending" });
704
+
705
+ _resetWidgetModeForTests();
706
+ setWidgetMode("small", projectPrefsPath, globalPrefsPath);
707
+
708
+ updateProgressWidget(
709
+ {
710
+ hasUI: true,
711
+ ui: {
712
+ setHeader() {},
713
+ setStatus() {},
714
+ setWidget(_key: string, factory: any) {
715
+ if (_key === "gsd-progress") {
716
+ holder.widget = factory(
717
+ { requestRender() {} },
718
+ { fg: (_color: string, text: string) => text, bold: (text: string) => text },
719
+ );
720
+ }
721
+ },
722
+ },
723
+ } as any,
724
+ "execute-task",
725
+ "M004/S01/T02",
726
+ {
727
+ phase: "executing",
728
+ activeMilestone: { id: "M004", title: "Budget Tracking" },
729
+ activeSlice: { id: "S01", title: "Schema migration" },
730
+ activeTask: { id: "T02", title: "Backfill repeat metadata" },
731
+ } as any,
732
+ {
733
+ getAutoStartTime: () => Date.now() - 18_000,
734
+ isStepMode: () => false,
735
+ getCmdCtx: () => null,
736
+ getBasePath: () => dir,
737
+ isVerbose: () => false,
738
+ isSessionSwitching: () => false,
739
+ getCurrentDispatchedModelId: () => null,
740
+ },
741
+ );
742
+
743
+ assert.ok(holder.widget, "progress widget should be installed");
744
+ const widget = holder.widget;
745
+ const lines = widget.render(120);
746
+ const rendered = lines.join("\n");
747
+
748
+ assert.equal(lines.length, 4, `small widget should render as rule + two dense rows + rule:\n${rendered}`);
749
+ assert.match(rendered, /STATUS\s+.*AUTO\s+running/);
750
+ assert.match(rendered, /UNIT\s+M004\/S01\/T02/);
751
+ assert.match(rendered, /SPEND/);
752
+ assert.match(rendered, /TIME/);
753
+ assert.match(rendered, /PHASE\s+execute-task/);
754
+ assert.match(rendered, /WORK\s+T02: Backfill repeat m/);
755
+ assert.match(rendered, /TASK\s+2\/2/);
756
+ assert.match(rendered, /SLICE.*1\/2/);
757
+ assert.doesNotMatch(rendered, /\/gsd next|\/gsd status/);
758
+ assert.doesNotMatch(rendered, /dashboard|esc pause/);
759
+
760
+ for (const width of [40, 80, 120]) {
761
+ widget.invalidate();
762
+ assertLinesFit(widget.render(width), width);
763
+ }
764
+ });
765
+
636
766
  test("updateProgressWidget shows provider-waiting state consistently for auto and next modes", (t) => {
637
767
  const dir = makeTempDir("auto-next-dashboard");
638
768
  mkdirSync(join(dir, ".gsd"), { recursive: true });
@@ -691,14 +821,14 @@ test("updateProgressWidget shows provider-waiting state consistently for auto an
691
821
  const autoRendered = renderDashboard(false);
692
822
  const nextRendered = renderDashboard(true);
693
823
 
694
- assert.match(autoRendered, /GSD\s+·\s+AUTO\s+·\s+running/);
695
- assert.match(nextRendered, /GSD\s+·\s+NEXT\s+·\s+running/);
824
+ assert.match(autoRendered, /STATUS\s+.*AUTO\s+running/);
825
+ assert.match(nextRendered, /STATUS\s+.*NEXT\s+running/);
696
826
  assert.doesNotMatch(autoRendered.split("\n")[1] ?? "", /completing M003\/S01/);
697
827
  assert.doesNotMatch(nextRendered.split("\n")[1] ?? "", /completing M003\/S01/);
698
828
  assert.doesNotMatch(autoRendered, /waiting on provider.*Waiting on provider/i);
699
829
  assert.doesNotMatch(nextRendered, /waiting on provider.*Waiting on provider/i);
700
- assert.match(autoRendered, /completing\s+M003\/S01/);
701
- assert.match(nextRendered, /completing\s+M003\/S01/);
830
+ assert.match(autoRendered, /PHASE\s+complete-slice/);
831
+ assert.match(nextRendered, /PHASE\s+complete-slice/);
702
832
  assert.doesNotMatch(autoRendered, /Working/);
703
833
  assert.doesNotMatch(nextRendered, /Working/);
704
834
  });
@@ -812,3 +942,25 @@ test("widget mode respects project preference precedence and persists there", (t
812
942
  assert.match(projectPrefs, /widget_mode:\s*min/);
813
943
  assert.match(globalPrefs, /widget_mode:\s*off/);
814
944
  });
945
+
946
+ test("widget mode defaults to small when preferences do not set it", (t) => {
947
+ const homeDir = makeTempDir("home-no-widget-pref");
948
+ const projectDir = makeTempDir("project-no-widget-pref");
949
+ const globalPrefsPath = join(homeDir, ".gsd", "preferences.md");
950
+ const projectPrefsPath = join(projectDir, ".gsd", "preferences.md");
951
+
952
+ mkdirSync(join(homeDir, ".gsd"), { recursive: true });
953
+ mkdirSync(join(projectDir, ".gsd"), { recursive: true });
954
+ writeFileSync(globalPrefsPath, "---\nversion: 1\n---\n", "utf-8");
955
+ writeFileSync(projectPrefsPath, "---\nversion: 1\n---\n", "utf-8");
956
+
957
+ t.after(() => {
958
+ cleanup(homeDir);
959
+ cleanup(projectDir);
960
+ _resetWidgetModeForTests();
961
+ });
962
+
963
+ _resetWidgetModeForTests();
964
+
965
+ assert.equal(getWidgetMode(projectPrefsPath, globalPrefsPath), "small");
966
+ });
@@ -574,6 +574,43 @@ test("completeActiveUnit allows a different next unit to advance", async () => {
574
574
  assert.deepEqual(second.unit, { unitType: "execute-task", unitId: "T02" });
575
575
  });
576
576
 
577
+ test("completeActiveUnit guard survives an intervening advance and blocks X→Y→X re-dispatch", async () => {
578
+ // Regression test for issue #415: lastFinalizedUnitKey was wiped on every advance(),
579
+ // allowing completed units to be re-dispatched after any interleaving unit (X→Y→X).
580
+ let nextTaskId = "T01";
581
+ const { deps } = makeDeps({
582
+ dispatch: {
583
+ async decideNextUnit() {
584
+ return { unitType: "execute-task", unitId: nextTaskId, reason: "ready", preconditions: [] };
585
+ },
586
+ },
587
+ });
588
+ const orchestrator = createAutoOrchestrator(deps);
589
+
590
+ // Step 1: advance X (T01)
591
+ const first = await orchestrator.advance();
592
+ assert.equal(first.kind, "advanced");
593
+ if (first.kind !== "advanced") throw new Error("expected first advance");
594
+
595
+ // Step 2: complete X (T01) — sets lastFinalizedUnitKey = 'execute-task:T01'
596
+ await orchestrator.completeActiveUnit(first.unit);
597
+
598
+ // Step 3: advance Y (T02) — must NOT clear lastFinalizedUnitKey
599
+ nextTaskId = "T02";
600
+ const second = await orchestrator.advance();
601
+ assert.equal(second.kind, "advanced");
602
+ if (second.kind !== "advanced") throw new Error("expected second advance (T02)");
603
+ assert.deepEqual(second.unit, { unitType: "execute-task", unitId: "T02" });
604
+
605
+ // Step 4: re-select X (T01) — must be blocked because T01 was finalized
606
+ nextTaskId = "T01";
607
+ const third = await orchestrator.advance();
608
+ assert.equal(third.kind, "blocked");
609
+ if (third.kind !== "blocked") throw new Error("expected X→Y→X re-dispatch to be blocked");
610
+ assert.equal(third.action, "stop");
611
+ assert.equal(third.reason, "state did not advance after finalized execute-task T01");
612
+ });
613
+
577
614
  test("retryActiveUnit clears in-flight idempotency without marking the unit finalized", async () => {
578
615
  const { deps, calls } = makeDeps();
579
616
  const orchestrator = createAutoOrchestrator(deps);
@@ -531,8 +531,13 @@ test("bootstrap honors explicit solo milestone lock when recovering stranded tar
531
531
  assert.equal(ready, true);
532
532
  assert.deepEqual(adoptCalls, [{ milestoneId: "M002", mode: "worktree" }]);
533
533
  assert.equal(s.currentMilestoneId, "M002");
534
- assert.match(messages, /Recovering stranded work for M002/);
534
+ assert.match(messages, /Resuming saved milestone work for M002/);
535
535
  assert.doesNotMatch(messages, /blocks auto-mode before M001/);
536
+ assert.doesNotMatch(messages, /Stranded work for in-progress milestone M002/);
537
+ assert.ok(
538
+ notifications.some((entry) => entry.level === "info" && entry.message.includes("Resuming saved milestone work for M002")),
539
+ "active recovery should be presented as an info-level resume",
540
+ );
536
541
  } finally {
537
542
  if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
538
543
  else process.env.GSD_MILESTONE_LOCK = previousLock;
@@ -624,7 +629,11 @@ test("bootstrap adopts stranded active branch even when isolation is none", asyn
624
629
  assert.equal(s.strandedRecoveryIsolationMode, "branch");
625
630
  assert.match(
626
631
  notifications.map((entry) => entry.message).join("\n"),
627
- /Recovering stranded work for M001/,
632
+ /Resuming saved milestone work for M001/,
633
+ );
634
+ assert.ok(
635
+ notifications.every((entry) => entry.level !== "warning" || !entry.message.includes("Stranded work for in-progress milestone M001")),
636
+ "adopting the active milestone should not emit a scary stranded-work warning",
628
637
  );
629
638
  } finally {
630
639
  try {
@@ -713,7 +722,11 @@ test("bootstrap adopts stranded active branch before deep project setup", async
713
722
  assert.equal(s.strandedRecoveryIsolationMode, "branch");
714
723
  assert.match(
715
724
  notifications.map((entry) => entry.message).join("\n"),
716
- /Recovering stranded work for M001/,
725
+ /Resuming saved milestone work for M001/,
726
+ );
727
+ assert.ok(
728
+ notifications.every((entry) => entry.level !== "warning" || !entry.message.includes("Stranded work for in-progress milestone M001")),
729
+ "adopting the active milestone should not emit a scary stranded-work warning",
717
730
  );
718
731
  } finally {
719
732
  try {
@@ -0,0 +1,142 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Unit tests for hasBrowserRequiredText heading-depth section guard.
3
+
4
+ import { describe, test } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+
7
+ import { hasBrowserRequiredText } from '../browser-evidence.ts';
8
+
9
+ describe('hasBrowserRequiredText', () => {
10
+ test('detects browser requirement in a plain test-cases section', () => {
11
+ const text = [
12
+ '## Test Cases',
13
+ '',
14
+ '1. Open index.html in a browser and navigate to /dashboard.',
15
+ '',
16
+ ].join('\n');
17
+ assert.ok(hasBrowserRequiredText(text), 'plain browser step should be detected');
18
+ });
19
+
20
+ test('ignores browser mention under a top-level non-requirement heading', () => {
21
+ const text = [
22
+ '## Not Proven',
23
+ '',
24
+ '- Keyboard usability through a real browser.',
25
+ '- Browser console cleanliness.',
26
+ '',
27
+ ].join('\n');
28
+ assert.ok(!hasBrowserRequiredText(text), 'browser mention under "Not Proven" should be ignored');
29
+ });
30
+
31
+ test('sub-heading inside a non-requirement section does not re-enable detection', () => {
32
+ // BUG (pre-fix): ### sub-heading under ## Not Proven resets inNonRequirementSection
33
+ // to false, causing subsequent lines to be detected as browser requirements.
34
+ const text = [
35
+ '## Not Proven By This UAT',
36
+ '',
37
+ '- No live browser session was used.',
38
+ '',
39
+ '### Visual Checks',
40
+ '',
41
+ '- Browser visual polish deferred to next slice.',
42
+ '- Keyboard interaction in a real browser is not proven here.',
43
+ '',
44
+ ].join('\n');
45
+ assert.ok(
46
+ !hasBrowserRequiredText(text),
47
+ 'sub-heading under a non-requirement section must not re-enable browser detection',
48
+ );
49
+ });
50
+
51
+ test('requirement-level heading after non-requirement section re-enables detection', () => {
52
+ const text = [
53
+ '## Not Proven',
54
+ '',
55
+ '- Browser polish deferred.',
56
+ '',
57
+ '## Test Cases',
58
+ '',
59
+ '1. Launch browser and open localhost.',
60
+ '',
61
+ ].join('\n');
62
+ assert.ok(
63
+ hasBrowserRequiredText(text),
64
+ 'browser step under "Test Cases" (same depth as "Not Proven") must still be detected',
65
+ );
66
+ });
67
+
68
+ test('deferred sub-heading inside a requirement section scopes exclusion to its own block', () => {
69
+ const text = [
70
+ '## Test Cases',
71
+ '',
72
+ '1. Open browser at localhost.',
73
+ '',
74
+ '### Deferred: keyboard check',
75
+ '',
76
+ '- Keyboard UAT deferred to next slice.',
77
+ '',
78
+ '### Step 2: Verify DOM',
79
+ '',
80
+ '1. Navigate to /dashboard in the browser.',
81
+ '',
82
+ ].join('\n');
83
+ assert.ok(
84
+ hasBrowserRequiredText(text),
85
+ 'browser step under "Step 2" sub-heading must be detected after a sibling "Deferred" sub-heading',
86
+ );
87
+ });
88
+
89
+ test('deferred sub-heading at same depth as test cases does not escape to parent', () => {
90
+ const text = [
91
+ '## Test Cases',
92
+ '',
93
+ '### Deferred: responsive layout',
94
+ '',
95
+ '- Responsive layout check is deferred to S02.',
96
+ '',
97
+ ].join('\n');
98
+ assert.ok(
99
+ !hasBrowserRequiredText(text),
100
+ 'content under a "Deferred" sub-heading should be excluded from detection',
101
+ );
102
+ });
103
+
104
+ test('detects browser requirement written only in a heading', () => {
105
+ // Regression: the line-by-line scan previously skip-continued past headings,
106
+ // missing browser obligations expressed only in heading text.
107
+ const text = '## Open browser session at localhost\n';
108
+ assert.ok(hasBrowserRequiredText(text), 'browser requirement in heading text must be detected');
109
+ });
110
+
111
+ test('heading that opens a non-requirement section is not itself detected as a requirement', () => {
112
+ const text = '## Not Proven\n\n- Some note.\n';
113
+ assert.ok(
114
+ !hasBrowserRequiredText(text),
115
+ 'a non-requirement section heading should not trigger browser detection',
116
+ );
117
+ });
118
+
119
+ test('returns false for empty text', () => {
120
+ assert.ok(!hasBrowserRequiredText(''), 'empty string returns false');
121
+ });
122
+
123
+ test('notes-for-tester heading with sub-headings stays non-requirement', () => {
124
+ const text = [
125
+ '## Notes for Tester',
126
+ '',
127
+ '### Browser Setup',
128
+ '',
129
+ '- Run this spec without a browser; a DOM harness is sufficient.',
130
+ '- Browser-based visual checks are deferred.',
131
+ '',
132
+ '### Follow-up Items',
133
+ '',
134
+ '- Track browser session evidence in S02.',
135
+ '',
136
+ ].join('\n');
137
+ assert.ok(
138
+ !hasBrowserRequiredText(text),
139
+ 'sub-headings under "Notes for Tester" should not re-enable browser detection',
140
+ );
141
+ });
142
+ });