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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/dist/cli.js +3 -2
  2. package/dist/help-text.js +10 -6
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +495 -0
  5. package/dist/resources/extensions/browser-tools/engine/selection.js +16 -0
  6. package/dist/resources/extensions/browser-tools/extension-manifest.json +2 -2
  7. package/dist/resources/extensions/browser-tools/index.js +57 -9
  8. package/dist/resources/extensions/browser-tools/package.json +5 -1
  9. package/dist/resources/extensions/gsd/auto/orchestrator.js +0 -1
  10. package/dist/resources/extensions/gsd/auto-dashboard.js +77 -13
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +5 -0
  12. package/dist/resources/extensions/gsd/auto-post-unit.js +21 -3
  13. package/dist/resources/extensions/gsd/auto-prompts.js +59 -22
  14. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  15. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  16. package/dist/resources/extensions/gsd/auto.js +9 -2
  17. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -4
  18. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -5
  19. package/dist/resources/extensions/gsd/browser-evidence.js +29 -2
  20. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -2
  21. package/dist/resources/extensions/gsd/commands-handlers.js +76 -11
  22. package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -1
  23. package/dist/resources/extensions/gsd/dashboard-overlay.js +21 -7
  24. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  25. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +2 -2
  26. package/dist/resources/extensions/gsd/escalation.js +4 -4
  27. package/dist/resources/extensions/gsd/forensics.js +74 -2
  28. package/dist/resources/extensions/gsd/gsd-db.js +5 -2
  29. package/dist/resources/extensions/gsd/guided-flow.js +29 -68
  30. package/dist/resources/extensions/gsd/mcp-project-config.js +9 -76
  31. package/dist/resources/extensions/gsd/memory-store.js +4 -1
  32. package/dist/resources/extensions/gsd/post-unit-hooks.js +9 -0
  33. package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
  34. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  35. package/dist/resources/extensions/gsd/prompts/forensics.md +61 -1
  36. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  37. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  38. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  40. package/dist/resources/extensions/gsd/prompts/run-uat.md +40 -22
  41. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  42. package/dist/resources/extensions/gsd/rule-registry.js +428 -52
  43. package/dist/resources/extensions/gsd/state.js +2 -2
  44. package/dist/resources/extensions/gsd/templates/plan.md +3 -1
  45. package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -1
  46. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -1
  47. package/dist/resources/extensions/gsd/tools/validate-milestone.js +46 -16
  48. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +51 -14
  49. package/dist/resources/extensions/gsd/verdict-parser.js +59 -15
  50. package/dist/resources/extensions/gsd/verification-gate.js +72 -1
  51. package/dist/resources/extensions/shared/gsd-browser-cli.js +145 -0
  52. package/dist/rtk.d.ts +7 -1
  53. package/dist/rtk.js +27 -11
  54. package/dist/update-check.d.ts +15 -1
  55. package/dist/update-check.js +87 -12
  56. package/dist/update-cmd.d.ts +1 -0
  57. package/dist/update-cmd.js +53 -2
  58. package/dist/web/standalone/.next/BUILD_ID +1 -1
  59. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  60. package/dist/web/standalone/.next/build-manifest.json +2 -2
  61. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  62. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/index.html +1 -1
  80. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  87. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  88. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  90. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  91. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  92. package/package.json +4 -2
  93. package/packages/cloud-mcp-gateway/package.json +2 -2
  94. package/packages/contracts/package.json +1 -1
  95. package/packages/daemon/package.json +4 -4
  96. package/packages/gsd-agent-core/dist/agent-session.d.ts +9 -0
  97. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  98. package/packages/gsd-agent-core/dist/agent-session.js +32 -0
  99. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  100. package/packages/gsd-agent-core/dist/index.d.ts +1 -0
  101. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  102. package/packages/gsd-agent-core/dist/index.js +1 -0
  103. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  104. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts +2 -0
  105. package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts.map +1 -1
  106. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js +8 -2
  107. package/packages/gsd-agent-core/dist/session/agent-session-compaction.js.map +1 -1
  108. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +7 -0
  109. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  110. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  111. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  112. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +69 -1
  113. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  114. package/packages/gsd-agent-core/dist/turn-latency.d.ts +47 -0
  115. package/packages/gsd-agent-core/dist/turn-latency.d.ts.map +1 -0
  116. package/packages/gsd-agent-core/dist/turn-latency.js +123 -0
  117. package/packages/gsd-agent-core/dist/turn-latency.js.map +1 -0
  118. package/packages/gsd-agent-core/package.json +6 -6
  119. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +21 -0
  120. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +1 -0
  121. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +213 -0
  122. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +1 -0
  123. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  124. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +20 -0
  125. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  126. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  127. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +7 -1
  128. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  129. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.d.ts.map +1 -1
  130. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js +6 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js.map +1 -1
  132. package/packages/gsd-agent-modes/package.json +7 -7
  133. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  134. package/packages/mcp-server/dist/remote-questions.js +23 -9
  135. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  136. package/packages/mcp-server/dist/workflow-tools.js +2 -2
  137. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  138. package/packages/mcp-server/package.json +3 -3
  139. package/packages/native/package.json +1 -1
  140. package/packages/pi-agent-core/dist/agent-loop.js +38 -0
  141. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  142. package/packages/pi-agent-core/dist/agent.d.ts +5 -1
  143. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  144. package/packages/pi-agent-core/dist/agent.js +2 -0
  145. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  146. package/packages/pi-agent-core/dist/types.d.ts +3 -0
  147. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  148. package/packages/pi-agent-core/dist/types.js.map +1 -1
  149. package/packages/pi-agent-core/package.json +1 -1
  150. package/packages/pi-ai/dist/api-registry.d.ts +2 -0
  151. package/packages/pi-ai/dist/api-registry.d.ts.map +1 -1
  152. package/packages/pi-ai/dist/api-registry.js +23 -0
  153. package/packages/pi-ai/dist/api-registry.js.map +1 -1
  154. package/packages/pi-ai/dist/models.generated.d.ts +68 -0
  155. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  156. package/packages/pi-ai/dist/models.generated.js +72 -4
  157. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  158. package/packages/pi-ai/dist/stream.js +6 -6
  159. package/packages/pi-ai/dist/stream.js.map +1 -1
  160. package/packages/pi-ai/package.json +1 -1
  161. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -2
  163. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  164. package/packages/pi-coding-agent/package.json +7 -7
  165. package/packages/pi-tui/package.json +1 -1
  166. package/packages/rpc-client/package.json +2 -2
  167. package/pkg/package.json +1 -1
  168. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +579 -0
  169. package/src/resources/extensions/browser-tools/engine/selection.ts +19 -0
  170. package/src/resources/extensions/browser-tools/extension-manifest.json +2 -2
  171. package/src/resources/extensions/browser-tools/index.ts +60 -9
  172. package/src/resources/extensions/browser-tools/package.json +5 -1
  173. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +35 -0
  174. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +33 -0
  175. package/src/resources/extensions/gsd/auto/orchestrator.ts +0 -1
  176. package/src/resources/extensions/gsd/auto-dashboard.ts +82 -14
  177. package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
  178. package/src/resources/extensions/gsd/auto-post-unit.ts +28 -2
  179. package/src/resources/extensions/gsd/auto-prompts.ts +93 -15
  180. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
  181. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  182. package/src/resources/extensions/gsd/auto.ts +12 -2
  183. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -4
  184. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -5
  185. package/src/resources/extensions/gsd/browser-evidence.ts +26 -2
  186. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -2
  187. package/src/resources/extensions/gsd/commands-handlers.ts +76 -11
  188. package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -1
  189. package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -7
  190. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -0
  191. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +2 -2
  192. package/src/resources/extensions/gsd/escalation.ts +4 -4
  193. package/src/resources/extensions/gsd/forensics.ts +99 -5
  194. package/src/resources/extensions/gsd/gsd-db.ts +5 -2
  195. package/src/resources/extensions/gsd/guided-flow.ts +90 -82
  196. package/src/resources/extensions/gsd/mcp-project-config.ts +13 -78
  197. package/src/resources/extensions/gsd/memory-store.ts +4 -1
  198. package/src/resources/extensions/gsd/post-unit-hooks.ts +14 -1
  199. package/src/resources/extensions/gsd/preferences-validation.ts +36 -0
  200. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  201. package/src/resources/extensions/gsd/prompts/forensics.md +61 -1
  202. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
  203. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
  204. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  205. package/src/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
  206. package/src/resources/extensions/gsd/prompts/run-uat.md +40 -22
  207. package/src/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
  208. package/src/resources/extensions/gsd/rule-registry.ts +558 -58
  209. package/src/resources/extensions/gsd/rule-types.ts +2 -0
  210. package/src/resources/extensions/gsd/state.ts +2 -2
  211. package/src/resources/extensions/gsd/templates/plan.md +3 -1
  212. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +105 -4
  213. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +37 -0
  214. package/src/resources/extensions/gsd/tests/browser-evidence.test.ts +142 -0
  215. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +30 -0
  216. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
  217. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +45 -0
  218. package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +53 -0
  219. package/src/resources/extensions/gsd/tests/discuss-milestone-structured-questions.test.ts +31 -0
  220. package/src/resources/extensions/gsd/tests/doctor-runtime-checks.test.ts +27 -0
  221. package/src/resources/extensions/gsd/tests/escalation.test.ts +16 -27
  222. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +20 -0
  223. package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +3 -0
  224. package/src/resources/extensions/gsd/tests/forensics-tool-scope.test.ts +69 -0
  225. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +40 -1
  226. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +86 -0
  227. package/src/resources/extensions/gsd/tests/guided-flow.test.ts +12 -9
  228. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +4 -4
  229. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +66 -10
  230. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +32 -0
  231. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +2 -0
  232. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +39 -8
  233. package/src/resources/extensions/gsd/tests/new-milestone-discuss-routing.test.ts +3 -3
  234. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +9 -0
  235. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +157 -0
  236. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +179 -0
  237. package/src/resources/extensions/gsd/tests/preferences.test.ts +29 -0
  238. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +43 -1
  239. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +14 -0
  240. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +7 -8
  241. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +75 -0
  242. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +100 -0
  243. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +139 -0
  244. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
  245. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +7 -1
  246. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +6 -3
  247. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +133 -0
  248. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +51 -0
  249. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +130 -0
  250. package/src/resources/extensions/gsd/tools/complete-slice.ts +14 -1
  251. package/src/resources/extensions/gsd/tools/complete-task.ts +20 -2
  252. package/src/resources/extensions/gsd/tools/validate-milestone.ts +46 -15
  253. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +63 -15
  254. package/src/resources/extensions/gsd/types.ts +69 -5
  255. package/src/resources/extensions/gsd/verdict-parser.ts +54 -13
  256. package/src/resources/extensions/gsd/verification-gate.ts +87 -1
  257. package/src/resources/extensions/shared/gsd-browser-cli.ts +172 -0
  258. /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → jBtwT9v1u2lUA3UEOy_ZH}/_buildManifest.js +0 -0
  259. /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → jBtwT9v1u2lUA3UEOy_ZH}/_ssgManifest.js +0 -0
@@ -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
  }
@@ -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,
@@ -57,6 +58,17 @@ function cleanup(dir: string): void {
57
58
  }
58
59
  }
59
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
+
60
72
  // ─── unitVerb ─────────────────────────────────────────────────────────────
61
73
 
62
74
  test("unitVerb maps known unit types to verbs", () => {
@@ -662,6 +674,95 @@ test("updateProgressWidget full mode keeps footer-owned signals out of auto deck
662
674
  assert.doesNotMatch(rendered, /\$/, "footer owns session cost display");
663
675
  });
664
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
+
665
766
  test("updateProgressWidget shows provider-waiting state consistently for auto and next modes", (t) => {
666
767
  const dir = makeTempDir("auto-next-dashboard");
667
768
  mkdirSync(join(dir, ".gsd"), { recursive: true });
@@ -720,14 +821,14 @@ test("updateProgressWidget shows provider-waiting state consistently for auto an
720
821
  const autoRendered = renderDashboard(false);
721
822
  const nextRendered = renderDashboard(true);
722
823
 
723
- assert.match(autoRendered, /GSD\s+·\s+AUTO\s+·\s+running/);
724
- 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/);
725
826
  assert.doesNotMatch(autoRendered.split("\n")[1] ?? "", /completing M003\/S01/);
726
827
  assert.doesNotMatch(nextRendered.split("\n")[1] ?? "", /completing M003\/S01/);
727
828
  assert.doesNotMatch(autoRendered, /waiting on provider.*Waiting on provider/i);
728
829
  assert.doesNotMatch(nextRendered, /waiting on provider.*Waiting on provider/i);
729
- assert.match(autoRendered, /completing\s+M003\/S01/);
730
- assert.match(nextRendered, /completing\s+M003\/S01/);
830
+ assert.match(autoRendered, /PHASE\s+complete-slice/);
831
+ assert.match(nextRendered, /PHASE\s+complete-slice/);
731
832
  assert.doesNotMatch(autoRendered, /Working/);
732
833
  assert.doesNotMatch(nextRendered, /Working/);
733
834
  });
@@ -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);
@@ -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
+ });
@@ -12,6 +12,7 @@ import { tmpdir } from "node:os";
12
12
 
13
13
  import { buildSliceSummaryExcerpt, buildCompleteMilestonePrompt, buildValidateMilestonePrompt } from "../auto-prompts.ts";
14
14
  import { invalidateAllCaches } from "../cache.ts";
15
+ import { closeDatabase, insertMilestone, openDatabase } from "../gsd-db.ts";
15
16
 
16
17
  // ─── Fixture helpers ──────────────────────────────────────────────────────
17
18
 
@@ -364,3 +365,32 @@ test("validate-milestone prompt uses slice excerpts and on-demand paths instead
364
365
  "validate prompt must not inline full assessment traces",
365
366
  );
366
367
  });
368
+
369
+ test("validate-milestone prompt inlines planned verification classes as canonical rows", async (t) => {
370
+ const base = createBase();
371
+ t.after(() => {
372
+ try { closeDatabase(); } catch { /* ignore */ }
373
+ cleanup(base);
374
+ });
375
+ invalidateAllCaches();
376
+
377
+ openDatabase(join(base, ".gsd", "gsd.db"));
378
+ insertMilestone({
379
+ id: "M001",
380
+ planning: {
381
+ verificationContract: "Local command exits 0.",
382
+ verificationOperational: "No long-running child process remains.",
383
+ },
384
+ });
385
+ writeRoadmap(base, makeRoadmap());
386
+ writeSummary(base, "S01", makeFatSummary("S01"));
387
+ writeSummary(base, "S02", makeFatSummary("S02"));
388
+
389
+ const prompt = await buildValidateMilestonePrompt("M001", "Test Milestone", base);
390
+
391
+ assert.match(prompt, /### Verification Classes \(from planning\)/);
392
+ assert.match(prompt, /Every row in this table must appear in `verificationClasses`/);
393
+ assert.match(prompt, /\| Class \| Planned Check \|/);
394
+ assert.match(prompt, /\| Contract \| Local command exits 0\. \|/);
395
+ assert.match(prompt, /\| Operational \| No long-running child process remains\. \|/);
396
+ });
@@ -20,6 +20,7 @@ import {
20
20
  insertMilestone,
21
21
  insertSlice,
22
22
  insertTask,
23
+ setSliceSummaryMd,
23
24
  } from '../gsd-db.ts';
24
25
  import { handleCompleteSlice } from '../tools/complete-slice.ts';
25
26
  import type { CompleteSliceParams } from '../types.ts';
@@ -153,4 +154,45 @@ describe('complete-slice verification gate (#3580)', () => {
153
154
  );
154
155
  }
155
156
  });
157
+
158
+ test('backfills prior verification narrative when verification is omitted on re-completion', async () => {
159
+ // Seed full_summary_md with a prior verification narrative (simulates a
160
+ // previous completion where the verification text was recorded).
161
+ const priorVerification = 'All 12 API integration tests pass — zero regressions detected.';
162
+ const priorSummary = [
163
+ '---',
164
+ 'verification_result: passed',
165
+ '---',
166
+ '',
167
+ '# S01: Test Slice',
168
+ '',
169
+ '## What Happened',
170
+ '',
171
+ 'narrative goes here',
172
+ '',
173
+ '## Verification',
174
+ '',
175
+ priorVerification,
176
+ '',
177
+ '## Requirements Advanced',
178
+ '',
179
+ ].join('\n');
180
+ setSliceSummaryMd('M001', 'S01', priorSummary, '');
181
+
182
+ // Complete slice without providing verification — handler must backfill
183
+ // from the existing summary rather than writing an empty section.
184
+ const result = await handleCompleteSlice(
185
+ makeParams({ verification: undefined }),
186
+ basePath,
187
+ );
188
+
189
+ assert.ok(!('error' in result), `expected success, got: ${'error' in result ? (result as { error: string }).error : ''}`);
190
+
191
+ const { summaryPath } = result as { summaryPath: string };
192
+ const written = fs.readFileSync(summaryPath, 'utf8');
193
+ assert.ok(
194
+ written.includes(priorVerification),
195
+ `prior verification narrative must be preserved when verification is omitted; got:\n${written}`,
196
+ );
197
+ });
156
198
  });
@@ -6,6 +6,7 @@ import test from "node:test";
6
6
  import assert from "node:assert/strict";
7
7
 
8
8
  import { GSDDashboardOverlay } from "../dashboard-overlay.ts";
9
+ import type { UnitMetrics } from "../metrics.ts";
9
10
  import { assertFullOuterBorder } from "./tui-border-assertions.ts";
10
11
 
11
12
  const fakeTheme = {
@@ -23,3 +24,47 @@ test("GSDDashboardOverlay renders inside the shared full border", (t) => {
23
24
  assert.ok(lines.some((line) => line.startsWith("│")), "body rows should have side borders");
24
25
  assert.match(lines.at(-1) ?? "", /^╰─+╯$/);
25
26
  });
27
+
28
+ test("GSDDashboardOverlay reuses metrics aggregations until the unit count changes", (t) => {
29
+ const overlay = new GSDDashboardOverlay({ requestRender() {} }, fakeTheme as any, () => {});
30
+ t.after(() => overlay.dispose());
31
+
32
+ const firstUnits = [makeUnit("M001/S001/T001", 0.25)];
33
+ const firstMetrics = (overlay as any).ensureMetricsCache(firstUnits);
34
+
35
+ overlay.invalidate();
36
+
37
+ const sameCountUnits = [makeUnit("M001/S001/T002", 0.5)];
38
+ const sameCountMetrics = (overlay as any).ensureMetricsCache(sameCountUnits);
39
+ assert.equal(sameCountMetrics, firstMetrics, "same unit count should reuse cached metrics");
40
+ assert.equal(sameCountMetrics.totals.cost, 0.25);
41
+
42
+ const increasedCountMetrics = (overlay as any).ensureMetricsCache([
43
+ ...sameCountUnits,
44
+ makeUnit("M001/S001/T003", 0.75),
45
+ ]);
46
+ assert.notEqual(increasedCountMetrics, firstMetrics, "changed unit count should recompute metrics");
47
+ assert.equal(increasedCountMetrics.totals.units, 2);
48
+ assert.equal(increasedCountMetrics.totals.cost, 1.25);
49
+ });
50
+
51
+ function makeUnit(id: string, cost: number): UnitMetrics {
52
+ return {
53
+ type: "execute-task",
54
+ id,
55
+ model: "claude-sonnet-4.5",
56
+ startedAt: 1000,
57
+ finishedAt: 2000,
58
+ tokens: {
59
+ input: 100,
60
+ output: 50,
61
+ cacheRead: 25,
62
+ cacheWrite: 10,
63
+ total: 185,
64
+ },
65
+ cost,
66
+ toolCalls: 1,
67
+ assistantMessages: 1,
68
+ userMessages: 1,
69
+ };
70
+ }
@@ -229,6 +229,24 @@ function makeIsolatedBaseWithCleanup(t: TestContext): string {
229
229
  return base;
230
230
  }
231
231
 
232
+ function setGsdHeadless(t: TestContext): void {
233
+ const previous = process.env.GSD_HEADLESS;
234
+ process.env.GSD_HEADLESS = "1";
235
+ t.after(() => {
236
+ if (previous === undefined) delete process.env.GSD_HEADLESS;
237
+ else process.env.GSD_HEADLESS = previous;
238
+ });
239
+ }
240
+
241
+ function unsetGsdHeadless(t: TestContext): void {
242
+ const previous = process.env.GSD_HEADLESS;
243
+ delete process.env.GSD_HEADLESS;
244
+ t.after(() => {
245
+ if (previous === undefined) delete process.env.GSD_HEADLESS;
246
+ else process.env.GSD_HEADLESS = previous;
247
+ });
248
+ }
249
+
232
250
  function writeValidProject(base: string): void {
233
251
  writeFileSync(join(base, ".gsd", "PROJECT.md"), VALID_PROJECT_MD);
234
252
  }
@@ -364,16 +382,33 @@ test("Deep mode: discuss-project does NOT dispatch when planning_depth is 'light
364
382
  test("Deep mode: discuss-project DOES dispatch when planning_depth is 'deep' and PROJECT.md missing", async (t) => {
365
383
  const base = makeIsolatedBaseWithCleanup(t);
366
384
 
385
+ unsetGsdHeadless(t);
386
+
367
387
  const prefs = { planning_depth: "deep" } as GSDPreferences;
368
388
  const result = await rule(PROJECT_RULE_NAME).match(makeCtx(base, prefs));
369
389
  assert.ok(result && result.action === "dispatch", "deep mode + missing PROJECT.md must dispatch");
370
390
  if (result.action === "dispatch") {
371
391
  assert.strictEqual(result.unitType, "discuss-project");
372
392
  assert.strictEqual(result.unitId, "PROJECT");
393
+ assert.strictEqual(result.pauseAfterDispatch, true);
373
394
  assert.ok(result.prompt.length > 0, "prompt must be non-empty");
374
395
  }
375
396
  });
376
397
 
398
+ test("Deep mode: discuss-project does not pause when GSD_HEADLESS is set", async (t) => {
399
+ const base = makeIsolatedBaseWithCleanup(t);
400
+
401
+ setGsdHeadless(t);
402
+
403
+ const prefs = { planning_depth: "deep" } as GSDPreferences;
404
+ const result = await rule(PROJECT_RULE_NAME).match(makeCtx(base, prefs));
405
+ assert.ok(result && result.action === "dispatch", "deep mode + missing PROJECT.md must dispatch");
406
+ if (result.action === "dispatch") {
407
+ assert.strictEqual(result.unitType, "discuss-project");
408
+ assert.strictEqual(result.pauseAfterDispatch, false);
409
+ }
410
+ });
411
+
377
412
  test("Deep mode: discuss-project does NOT dispatch when PROJECT.md already exists and is valid", async (t) => {
378
413
  const base = makeIsolatedBaseWithCleanup(t);
379
414
 
@@ -432,6 +467,8 @@ test("Deep mode: discuss-requirements does NOT dispatch when PROJECT.md missing
432
467
  test("Deep mode: discuss-requirements DOES dispatch when PROJECT.md exists and REQUIREMENTS.md missing", async (t) => {
433
468
  const base = makeIsolatedBaseWithCleanup(t);
434
469
 
470
+ unsetGsdHeadless(t);
471
+
435
472
  writeValidProject(base);
436
473
  const prefs = { planning_depth: "deep" } as GSDPreferences;
437
474
  const result = await rule(REQUIREMENTS_RULE_NAME).match(makeCtx(base, prefs));
@@ -439,6 +476,22 @@ test("Deep mode: discuss-requirements DOES dispatch when PROJECT.md exists and R
439
476
  if (result.action === "dispatch") {
440
477
  assert.strictEqual(result.unitType, "discuss-requirements");
441
478
  assert.strictEqual(result.unitId, "REQUIREMENTS");
479
+ assert.strictEqual(result.pauseAfterDispatch, true);
480
+ }
481
+ });
482
+
483
+ test("Deep mode: discuss-requirements does not pause when GSD_HEADLESS is set", async (t) => {
484
+ const base = makeIsolatedBaseWithCleanup(t);
485
+
486
+ setGsdHeadless(t);
487
+
488
+ writeValidProject(base);
489
+ const prefs = { planning_depth: "deep" } as GSDPreferences;
490
+ const result = await rule(REQUIREMENTS_RULE_NAME).match(makeCtx(base, prefs));
491
+ assert.ok(result && result.action === "dispatch", "deep mode + PROJECT.md present + REQUIREMENTS.md missing must dispatch");
492
+ if (result.action === "dispatch") {
493
+ assert.strictEqual(result.unitType, "discuss-requirements");
494
+ assert.strictEqual(result.pauseAfterDispatch, false);
442
495
  }
443
496
  });
444
497