@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
@@ -235,6 +235,62 @@ test("executeTaskComplete derives missing verification from evidence", async ()
235
235
  }
236
236
  });
237
237
 
238
+ test("executeTaskComplete surfaces escalation questions and metadata", async () => {
239
+ const base = makeTmpBase();
240
+ try {
241
+ openTestDb(base);
242
+ writeFileSync(join(base, ".gsd", "PREFERENCES.md"), [
243
+ "---",
244
+ "version: 1",
245
+ "phases:",
246
+ " mid_execution_escalation: true",
247
+ "---",
248
+ ].join("\n"));
249
+ const planDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
250
+ mkdirSync(planDir, { recursive: true });
251
+ writeFileSync(join(planDir, "S01-PLAN.md"), "# S01\n\n- [ ] **T01: Demo** `est:5m`\n");
252
+
253
+ const result = await inProjectDir(base, () => executeTaskComplete({
254
+ milestoneId: "M001",
255
+ sliceId: "S01",
256
+ taskId: "T01",
257
+ oneLiner: "Completed task",
258
+ narrative: "Did the work but found an ambiguity.",
259
+ verification: "npm test",
260
+ escalation: {
261
+ question: "Should the cache use write-through or write-back?",
262
+ options: [
263
+ { id: "A", label: "Write-through", tradeoffs: "Simpler reads; slower writes." },
264
+ { id: "B", label: "Write-back", tradeoffs: "Faster writes; more flush complexity." },
265
+ ],
266
+ recommendation: "A",
267
+ recommendationRationale: "Current usage favors correctness over write latency.",
268
+ continueWithDefault: true,
269
+ },
270
+ }, base));
271
+
272
+ assert.equal(result.details.operation, "complete_task");
273
+ assert.match(
274
+ String(result.content[0]?.text),
275
+ /Task completed with escalation decision required: Should the cache use write-through or write-back\?/,
276
+ );
277
+ assert.match(String(result.content[0]?.text), /Resolve with: \/gsd escalate resolve T01/);
278
+ assert.equal((result.details.escalation as { question?: string }).question, "Should the cache use write-through or write-back?");
279
+
280
+ const db = _getAdapter();
281
+ assert.ok(db, "DB should be open");
282
+ const row = db!.prepare(
283
+ "SELECT escalation_pending, escalation_awaiting_review, escalation_artifact_path FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?",
284
+ ).get("M001", "S01", "T01") as Record<string, unknown> | undefined;
285
+ assert.equal(row?.escalation_pending, 0);
286
+ assert.equal(row?.escalation_awaiting_review, 1);
287
+ assert.ok(String(row?.escalation_artifact_path ?? "").endsWith("T01-ESCALATION.json"));
288
+ } finally {
289
+ closeDatabase();
290
+ cleanup(base);
291
+ }
292
+ });
293
+
238
294
  test("executeTaskComplete returns a tool error when verification cannot be derived", async () => {
239
295
  const base = makeTmpBase();
240
296
  try {
@@ -587,6 +643,80 @@ test("executeUatResultSave accepts gsd_uat_exec evidence written in a milestone
587
643
  }
588
644
  });
589
645
 
646
+ test("executeUatResultSave rejects artifact-driven PASS with human follow-up checks", async () => {
647
+ const base = makeTmpBase();
648
+ const worktree = join(base, ".gsd", "worktrees", "M001");
649
+ const evidenceId = "uat-artifact-nonautomatable";
650
+ const worktreeExecDir = join(worktree, ".gsd", "exec");
651
+ try {
652
+ openTestDb(base);
653
+ seedMilestone("M001", "Milestone One");
654
+ seedSlice("M001", "S01", "complete");
655
+ mkdirSync(worktreeExecDir, { recursive: true });
656
+ writeFileSync(
657
+ join(worktreeExecDir, `${evidenceId}.meta.json`),
658
+ JSON.stringify({
659
+ id: evidenceId,
660
+ metadata: {
661
+ kind: "uat_exec",
662
+ milestoneId: "M001",
663
+ sliceId: "S01",
664
+ checkId: "UAT-01",
665
+ intent: "uat-artifact-check",
666
+ },
667
+ }),
668
+ "utf-8",
669
+ );
670
+
671
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
672
+ milestoneId: "M001",
673
+ sliceId: "S01",
674
+ uatType: "artifact-driven",
675
+ verdict: "PASS",
676
+ checks: [
677
+ {
678
+ id: "UAT-01",
679
+ description: "Static contract passes",
680
+ mode: "artifact",
681
+ result: "PASS",
682
+ evidence: [{ kind: "gsd_uat_exec", ref: evidenceId }],
683
+ notes: "Artifact check passed.",
684
+ },
685
+ {
686
+ id: "UAT-02",
687
+ description: "Browser polish is deferred to the next slice",
688
+ mode: "human-follow-up",
689
+ result: "NEEDS-HUMAN",
690
+ notes: "Out of scope for this artifact-driven UAT.",
691
+ nonAutomatable: true,
692
+ },
693
+ ],
694
+ presentation: {
695
+ surface: "mcp",
696
+ presentedTools: [
697
+ "gsd_uat_exec",
698
+ "gsd_uat_result_save",
699
+ "gsd_resume",
700
+ "gsd_milestone_status",
701
+ "gsd_journal_query",
702
+ ],
703
+ blockedTools: [
704
+ { name: "gsd_exec", reason: "forbidden during run-uat" },
705
+ { name: "gsd_summary_save", reason: "forbidden during run-uat" },
706
+ { name: "gsd_save_gate_result", reason: "forbidden during run-uat" },
707
+ ],
708
+ },
709
+ notes: "UAT passed; non-automatable browser polish is deferred.",
710
+ }, worktree));
711
+
712
+ assert.equal(result.isError, true);
713
+ assert.match(String(result.content[0]?.text), /artifact-driven UAT cannot PASS with human-only checks/);
714
+ } finally {
715
+ closeDatabase();
716
+ cleanup(base);
717
+ }
718
+ });
719
+
590
720
  test("executeSliceComplete coerces string enrichment entries and writes summary/UAT artifacts", async () => {
591
721
  const base = makeTmpBase();
592
722
  try {
@@ -217,7 +217,7 @@ ${params.narrative}
217
217
 
218
218
  ## Verification
219
219
 
220
- ${params.verification}
220
+ ${params.verification ?? ""}
221
221
 
222
222
  ## Requirements Advanced
223
223
 
@@ -440,6 +440,19 @@ export async function handleCompleteSlice(
440
440
  const parsed = parseRequirementSection(existingSummaryMd, "Requirements Invalidated or Re-scoped", "what");
441
441
  if (parsed.length > 0) effectiveParams.requirementsInvalidated = parsed as Array<{ id: string; what: string }>;
442
442
  }
443
+ if (effectiveParams.verification === undefined) {
444
+ const headingLine = "## Verification\n\n";
445
+ const start = existingSummaryMd.indexOf(headingLine);
446
+ if (start !== -1) {
447
+ const contentStart = start + headingLine.length;
448
+ const nextHeading = existingSummaryMd.indexOf("\n\n## ", contentStart);
449
+ const prior = nextHeading === -1
450
+ ? existingSummaryMd.slice(contentStart)
451
+ : existingSummaryMd.slice(contentStart, nextHeading);
452
+ const trimmed = prior.trim();
453
+ if (trimmed) effectiveParams.verification = trimmed;
454
+ }
455
+ }
443
456
  }
444
457
 
445
458
  // Render summary markdown
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { join } from "node:path";
14
14
 
15
- import type { CompleteTaskParams } from "../types.js";
15
+ import type { CompleteTaskParams, EscalationArtifact } from "../types.js";
16
16
  import { isClosedStatus } from "../status-guards.js";
17
17
  import {
18
18
  transaction,
@@ -48,6 +48,14 @@ export interface CompleteTaskResult {
48
48
  sliceId: string;
49
49
  milestoneId: string;
50
50
  summaryPath: string;
51
+ escalation?: {
52
+ artifactPath: string;
53
+ question: string;
54
+ options: EscalationArtifact["options"];
55
+ recommendation: string;
56
+ recommendationRationale: string;
57
+ continueWithDefault: boolean;
58
+ };
51
59
  /**
52
60
  * True when this call re-completed an already-closed task from a turn that
53
61
  * had been superseded by timeout recovery or cancellation. The underlying
@@ -407,9 +415,18 @@ export async function handleCompleteTask(
407
415
  // overwrite it; gate rows are UPSERT-keyed per task and will also be
408
416
  // overwritten. This restores the invariant that deriveState() sees a
409
417
  // consistent "task not done" view so the loop re-dispatches the task.
418
+ let escalationMetadata: CompleteTaskResult["escalation"] | undefined;
410
419
  if (validatedEscalationArtifact) {
411
420
  try {
412
- writeEscalationArtifact(artifactBasePath, validatedEscalationArtifact);
421
+ const escalationPath = writeEscalationArtifact(artifactBasePath, validatedEscalationArtifact);
422
+ escalationMetadata = {
423
+ artifactPath: escalationPath,
424
+ question: validatedEscalationArtifact.question,
425
+ options: validatedEscalationArtifact.options,
426
+ recommendation: validatedEscalationArtifact.recommendation,
427
+ recommendationRationale: validatedEscalationArtifact.recommendationRationale,
428
+ continueWithDefault: validatedEscalationArtifact.continueWithDefault,
429
+ };
413
430
  } catch (escalationErr) {
414
431
  const msg = `complete-task escalation write failed for ${params.milestoneId}/${params.sliceId}/${params.taskId}: ${(escalationErr as Error).message}`;
415
432
  logWarning("tool", msg);
@@ -477,6 +494,7 @@ export async function handleCompleteTask(
477
494
  sliceId: params.sliceId,
478
495
  milestoneId: params.milestoneId,
479
496
  summaryPath,
497
+ ...(escalationMetadata ? { escalation: escalationMetadata } : {}),
480
498
  ...(projectionStale ? { stale: true } : {}),
481
499
  };
482
500
  }
@@ -78,18 +78,10 @@ function getRequiredVerificationClasses(milestoneId: string): string[] {
78
78
  return required;
79
79
  }
80
80
 
81
- async function collectPersistedBrowserEvidence(basePath: string, milestoneId: string): Promise<string> {
82
- const chunks: string[] = [];
83
- for (const slice of getMilestoneSlices(milestoneId)) {
84
- const artifactPath = `milestones/${milestoneId}/slices/${slice.id}/${slice.id}-ASSESSMENT.md`;
85
- const artifact = getArtifact(artifactPath);
86
- if (artifact?.full_content) chunks.push(artifact.full_content);
87
-
88
- const assessmentPath = resolveSliceFile(basePath, milestoneId, slice.id, "ASSESSMENT");
89
- const assessmentContent = assessmentPath ? await loadFile(assessmentPath) : null;
90
- if (assessmentContent) chunks.push(assessmentContent);
91
- }
92
- return chunks.join("\n\n");
81
+ function hasRuntimeExecutableUatEvidenceText(text: string): boolean {
82
+ if (!/\buatType:\s*runtime-executable\b/i.test(text)) return false;
83
+ if (!/\bverdict:\s*PASS\b/i.test(text)) return false;
84
+ return /^\|\s*[^|\n]+\s*\|\s*runtime\s*\|\s*PASS\s*\|[^|\n]*\bgsd_uat_exec\b/mi.test(text);
93
85
  }
94
86
 
95
87
  async function browserEvidenceGateRequiresAttention(
@@ -114,7 +106,38 @@ async function browserEvidenceGateRequiresAttention(
114
106
  ]);
115
107
  if (!hasBrowserRequiredText(requirementText)) return false;
116
108
 
117
- const persistedEvidence = await collectPersistedBrowserEvidence(basePath, params.milestoneId);
109
+ // Collect per-slice evidence so the runtime bypass is checked independently
110
+ // for each slice. Concatenating all slices before checking would allow runtime
111
+ // evidence from one slice to cover another slice's browser requirements.
112
+ const sliceEvidencePairs: Array<{ sliceRequirementText: string; evidenceText: string }> = [];
113
+ for (const slice of slices) {
114
+ const chunks: string[] = [];
115
+ const artifactPath = `milestones/${params.milestoneId}/slices/${slice.id}/${slice.id}-ASSESSMENT.md`;
116
+ const artifact = getArtifact(artifactPath);
117
+ if (artifact?.full_content) chunks.push(artifact.full_content);
118
+ const assessmentPath = resolveSliceFile(basePath, params.milestoneId, slice.id, "ASSESSMENT");
119
+ const assessmentContent = assessmentPath ? await loadFile(assessmentPath) : null;
120
+ if (assessmentContent) chunks.push(assessmentContent);
121
+ sliceEvidencePairs.push({
122
+ sliceRequirementText: compactTextParts([slice.demo, slice.goal, slice.success_criteria]),
123
+ evidenceText: chunks.join("\n\n"),
124
+ });
125
+ }
126
+ const persistedEvidence = sliceEvidencePairs.map((s) => s.evidenceText).join("\n\n");
127
+
128
+ // Runtime bypass: each slice whose own requirement text has browser-observable
129
+ // criteria must have its own runtime-executable UAT evidence. When no individual
130
+ // slice has slice-level browser requirements (e.g., they come from milestone-level
131
+ // fields only), fall back to checking whether any slice has runtime evidence.
132
+ const browserRequiringSlices = sliceEvidencePairs.filter((s) =>
133
+ hasBrowserRequiredText(s.sliceRequirementText),
134
+ );
135
+ const runtimeBypasses =
136
+ browserRequiringSlices.length > 0
137
+ ? browserRequiringSlices.every((s) => hasRuntimeExecutableUatEvidenceText(s.evidenceText))
138
+ : sliceEvidencePairs.some((s) => hasRuntimeExecutableUatEvidenceText(s.evidenceText));
139
+ if (runtimeBypasses) return false;
140
+
118
141
  const validationEvidence = compactTextParts([
119
142
  params.successCriteriaChecklist,
120
143
  params.verificationClasses,
@@ -184,14 +207,22 @@ export async function handleValidateMilestone(
184
207
  const requiredClasses = getRequiredVerificationClasses(params.milestoneId);
185
208
  if (requiredClasses.length > 0) {
186
209
  const verificationClasses = params.verificationClasses ?? "";
187
- const missingClass = requiredClasses.find(
210
+ const missingClasses = requiredClasses.filter(
188
211
  (className) => !new RegExp(`\\b${className}\\b`, "i").test(verificationClasses),
189
212
  );
190
- if (missingClass) {
213
+ if (missingClasses.length === 1) {
214
+ const missingClass = missingClasses[0];
191
215
  return {
192
216
  error: `verificationClasses must include canonical row "${missingClass}" because this milestone planned ${missingClass.toLowerCase()} verification`,
193
217
  };
194
218
  }
219
+ if (missingClasses.length > 1) {
220
+ const quotedClasses = missingClasses.map((className) => `"${className}"`).join(", ");
221
+ const plannedClasses = missingClasses.map((className) => className.toLowerCase()).join(", ");
222
+ return {
223
+ error: `verificationClasses must include canonical rows ${quotedClasses} because this milestone planned ${plannedClasses} verification`,
224
+ };
225
+ }
195
226
  }
196
227
 
197
228
  const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, params.milestoneId);
@@ -24,7 +24,7 @@ import { isAbsolute, join, resolve } from "node:path";
24
24
  import type { CompleteMilestoneParams } from "./complete-milestone.js";
25
25
  import { handleCompleteMilestone } from "./complete-milestone.js";
26
26
  import { handleCompleteTask } from "./complete-task.js";
27
- import type { CompleteSliceParams } from "../types.js";
27
+ import type { CompleteSliceParams, EscalationOption } from "../types.js";
28
28
  import { handleCompleteSlice } from "./complete-slice.js";
29
29
  import type { PlanMilestoneParams } from "./plan-milestone.js";
30
30
  import { handlePlanMilestone } from "./plan-milestone.js";
@@ -347,6 +347,14 @@ type VerificationEvidenceInput =
347
347
  }
348
348
  | string;
349
349
 
350
+ interface TaskEscalationInput {
351
+ question: string;
352
+ options: EscalationOption[];
353
+ recommendation: string;
354
+ recommendationRationale: string;
355
+ continueWithDefault: boolean;
356
+ }
357
+
350
358
  export interface TaskCompleteParams {
351
359
  taskId: string;
352
360
  sliceId: string;
@@ -359,6 +367,7 @@ export interface TaskCompleteParams {
359
367
  keyFiles?: string[];
360
368
  keyDecisions?: string[];
361
369
  blockerDiscovered?: boolean;
370
+ escalation?: TaskEscalationInput;
362
371
  verificationEvidence?: VerificationEvidenceInput[];
363
372
  }
364
373
 
@@ -511,6 +520,28 @@ export async function executeTaskComplete(
511
520
  isError: true,
512
521
  };
513
522
  }
523
+ if (result.escalation) {
524
+ const recommended = result.escalation.options.find((option) => option.id === result.escalation?.recommendation);
525
+ const optionIds = result.escalation.options.map((option) => option.id).join("|");
526
+ return {
527
+ content: [{
528
+ type: "text",
529
+ text: [
530
+ `Task completed with escalation decision required: ${result.escalation.question}`,
531
+ `Recommendation: ${result.escalation.recommendation}${recommended ? ` (${recommended.label})` : ""} — ${result.escalation.recommendationRationale}`,
532
+ `Resolve with: /gsd escalate resolve ${result.taskId} <${optionIds}|accept|reject-blocker> [rationale...]`,
533
+ ].join("\n"),
534
+ }],
535
+ details: {
536
+ operation: "complete_task",
537
+ taskId: result.taskId,
538
+ sliceId: result.sliceId,
539
+ milestoneId: result.milestoneId,
540
+ summaryPath: result.summaryPath,
541
+ escalation: result.escalation,
542
+ },
543
+ };
544
+ }
514
545
  return {
515
546
  content: [{ type: "text", text: `Completed task ${result.taskId} (${result.sliceId}/${result.milestoneId})` }],
516
547
  details: {
@@ -1119,6 +1150,9 @@ function validateUatChecks(basePath: string, params: UatResultSaveParams): strin
1119
1150
  function validateUatMode(params: UatResultSaveParams): string | null {
1120
1151
  const modes = new Set(params.checks.map((check) => check.mode));
1121
1152
  const hasHuman = params.checks.some((check) => check.result === "NEEDS-HUMAN");
1153
+ if (params.uatType === "artifact-driven" && hasHuman && params.verdict === "PASS") {
1154
+ return "artifact-driven UAT cannot PASS with human-only checks";
1155
+ }
1122
1156
  if (
1123
1157
  hasHuman &&
1124
1158
  params.verdict === "PASS" &&
@@ -1136,12 +1170,13 @@ function validateUatMode(params: UatResultSaveParams): string | null {
1136
1170
  if (params.uatType === "live-runtime" && !modes.has("runtime") && !modes.has("browser")) {
1137
1171
  return "live-runtime UAT requires runtime or browser evidence";
1138
1172
  }
1139
- if (params.uatType === "artifact-driven" && hasHuman && params.verdict === "PASS") {
1140
- return "artifact-driven UAT cannot PASS with human-only checks";
1141
- }
1142
1173
  return null;
1143
1174
  }
1144
1175
 
1176
+ function quoteToolNames(toolNames: readonly string[]): string {
1177
+ return toolNames.map((toolName) => `"${toolName}"`).join(", ");
1178
+ }
1179
+
1145
1180
  function validateCanonicalPresentation(params: UatResultSaveParams): string | null {
1146
1181
  const aliasHints: Record<string, string> = {
1147
1182
  gsd_save_summary: "gsd_summary_save",
@@ -1149,10 +1184,11 @@ function validateCanonicalPresentation(params: UatResultSaveParams): string | nu
1149
1184
  gsd_complete_slice: "gsd_slice_complete",
1150
1185
  gsd_milestone_complete: "gsd_complete_milestone",
1151
1186
  };
1187
+ const errors: string[] = [];
1152
1188
  for (const toolName of params.presentation.presentedTools) {
1153
1189
  const baseName = parseMcpToolName(toolName)?.tool ?? toolName;
1154
1190
  const canonical = aliasHints[baseName];
1155
- if (canonical) return `presentation tool "${toolName}" uses an alias; use canonical "${canonical}"`;
1191
+ if (canonical) errors.push(`presentation tool "${toolName}" uses an alias; use canonical "${canonical}"`);
1156
1192
  }
1157
1193
 
1158
1194
  const presentedCanonical = new Set(
@@ -1160,10 +1196,13 @@ function validateCanonicalPresentation(params: UatResultSaveParams): string | nu
1160
1196
  canonicalWorkflowToolName(parseMcpToolName(toolName)?.tool ?? toolName)
1161
1197
  ),
1162
1198
  );
1163
- for (const requiredTool of RUN_UAT_WORKFLOW_TOOL_NAMES) {
1164
- if (!presentedCanonical.has(requiredTool)) {
1165
- return `presentation is missing required UAT tool "${requiredTool}"`;
1166
- }
1199
+ const missingRequiredTools = RUN_UAT_WORKFLOW_TOOL_NAMES.filter(
1200
+ (requiredTool) => !presentedCanonical.has(requiredTool),
1201
+ );
1202
+ if (missingRequiredTools.length === 1) {
1203
+ errors.push(`presentation is missing required UAT tool "${missingRequiredTools[0]}"`);
1204
+ } else if (missingRequiredTools.length > 1) {
1205
+ errors.push(`presentation is missing required UAT tools ${quoteToolNames(missingRequiredTools)}`);
1167
1206
  }
1168
1207
 
1169
1208
  const forbiddenCanonical = new Set(
@@ -1171,24 +1210,33 @@ function validateCanonicalPresentation(params: UatResultSaveParams): string | nu
1171
1210
  .filter((toolName) => !toolName.includes("*"))
1172
1211
  .map((toolName) => canonicalWorkflowToolName(parseMcpToolName(toolName)?.tool ?? toolName)),
1173
1212
  );
1213
+ const forbiddenPresentedTools: string[] = [];
1174
1214
  for (const toolName of params.presentation.presentedTools) {
1175
1215
  const canonical = canonicalWorkflowToolName(parseMcpToolName(toolName)?.tool ?? toolName);
1176
1216
  if (toolName === "mcp__gsd-workflow__*" || forbiddenCanonical.has(canonical)) {
1177
- return `presentation includes forbidden run-uat tool "${toolName}"`;
1217
+ forbiddenPresentedTools.push(toolName);
1178
1218
  }
1179
1219
  }
1220
+ if (forbiddenPresentedTools.length === 1) {
1221
+ errors.push(`presentation includes forbidden run-uat tool "${forbiddenPresentedTools[0]}"`);
1222
+ } else if (forbiddenPresentedTools.length > 1) {
1223
+ errors.push(`presentation includes forbidden run-uat tools ${quoteToolNames(forbiddenPresentedTools)}`);
1224
+ }
1180
1225
 
1181
1226
  const blockedCanonical = new Set(
1182
1227
  params.presentation.blockedTools.map((entry) =>
1183
1228
  canonicalWorkflowToolName(parseMcpToolName(entry.name)?.tool ?? entry.name)
1184
1229
  ),
1185
1230
  );
1186
- for (const blockedTool of ["gsd_exec", "gsd_summary_save", "gsd_save_gate_result"]) {
1187
- if (!blockedCanonical.has(blockedTool)) {
1188
- return `presentation must record "${blockedTool}" as blocked during run-uat`;
1189
- }
1231
+ const missingBlockedTools = ["gsd_exec", "gsd_summary_save", "gsd_save_gate_result"].filter(
1232
+ (blockedTool) => !blockedCanonical.has(blockedTool),
1233
+ );
1234
+ if (missingBlockedTools.length === 1) {
1235
+ errors.push(`presentation must record "${missingBlockedTools[0]}" as blocked during run-uat`);
1236
+ } else if (missingBlockedTools.length > 1) {
1237
+ errors.push(`presentation must record ${quoteToolNames(missingBlockedTools)} as blocked during run-uat`);
1190
1238
  }
1191
- return null;
1239
+ return errors.length > 0 ? errors.join("; ") : null;
1192
1240
  }
1193
1241
 
1194
1242
  function nextUatAttempt(basePath: string, milestoneId: string, sliceId: string): number {
@@ -270,6 +270,29 @@ export interface GSDActiveUnit {
270
270
 
271
271
  // ─── Post-Unit Hook Types ─────────────────────────────────────────────────
272
272
 
273
+ export type PostUnitHookCriticality = "advisory" | "blocking";
274
+
275
+ export type PostUnitHookOutcomeVerdict =
276
+ | "pass"
277
+ | "advisory"
278
+ | "needs-rework"
279
+ | "needs-remediation"
280
+ | "needs-attention";
281
+
282
+ export type PostUnitHookOnBlockAction =
283
+ | "retry-unit"
284
+ | "retry-task"
285
+ | "queue-task"
286
+ | "queue-slice"
287
+ | "pause";
288
+
289
+ export interface PostUnitHookOnBlockConfig {
290
+ /** Routing action for blocking hook findings. */
291
+ action: PostUnitHookOnBlockAction;
292
+ /** Optional artifact used by compatibility retry routing. */
293
+ artifact?: string;
294
+ }
295
+
273
296
  export interface PostUnitHookConfig {
274
297
  /** Unique hook identifier — used in idempotency keys and logging. */
275
298
  name: string;
@@ -283,8 +306,12 @@ export interface PostUnitHookConfig {
283
306
  model?: string;
284
307
  /** Expected output file name (relative to task/slice dir). Used for idempotency — skip if exists. */
285
308
  artifact?: string;
309
+ /** Whether the hook is advisory or blocks unit advancement. Default advisory. */
310
+ criticality?: PostUnitHookCriticality;
286
311
  /** If this file is produced instead of artifact, re-run the trigger unit then re-run hooks. */
287
312
  retry_on?: string;
313
+ /** Optional routing for blocking findings. */
314
+ on_block?: PostUnitHookOnBlockConfig;
288
315
  /** Agent definition file to use. */
289
316
  agent?: string;
290
317
  /** Set false to disable without removing config. Default true. */
@@ -317,6 +344,31 @@ export interface HookDispatchResult {
317
344
  unitId: string;
318
345
  }
319
346
 
347
+ export interface PostUnitGateBlock {
348
+ /** Blocking hook name. */
349
+ hookName: string;
350
+ /** The unit type that triggered the gate. */
351
+ triggerUnitType: string;
352
+ /** The unit ID that triggered the gate. */
353
+ triggerUnitId: string;
354
+ /** Gate artifact name, when configured. */
355
+ artifact?: string;
356
+ /** Absolute path to the gate artifact, when known. */
357
+ artifactPath?: string;
358
+ /** Parsed blocking verdict, when present. */
359
+ verdict?: PostUnitHookOutcomeVerdict | "failed";
360
+ /** Configured routing action that caused the pause. */
361
+ action: PostUnitHookOnBlockAction;
362
+ /** Human-readable pause reason. */
363
+ reason: string;
364
+ /** Current hook cycle count. */
365
+ cycle: number;
366
+ /** Configured max cycle count. */
367
+ maxCycles: number;
368
+ /** Optional compatibility retry artifact. */
369
+ retryArtifact?: string;
370
+ }
371
+
320
372
  // ─── Budget & Notification Types ──────────────────────────────────────────
321
373
 
322
374
  export type BudgetEnforcementMode = "warn" | "pause" | "halt";
@@ -384,10 +436,10 @@ export interface EscalationArtifact {
384
436
  /** Why the executor recommends that option (1-2 sentences). */
385
437
  recommendationRationale: string;
386
438
  /**
387
- * When true, the executor proceeds with the recommendation as the answer
388
- * and the loop continues. User's later choice becomes a carry-forward
389
- * override for the NEXT task. When false, auto-mode pauses until the
390
- * user resolves via `/gsd escalate resolve`.
439
+ * When true, the recommendation is recorded as the default path but the
440
+ * loop still pauses until the user explicitly resolves the escalation.
441
+ * When false, auto-mode also pauses until the user resolves via
442
+ * `/gsd escalate resolve`.
391
443
  */
392
444
  continueWithDefault: boolean;
393
445
  createdAt: string;
@@ -452,6 +504,15 @@ export interface PreDispatchResult {
452
504
  export interface PersistedHookState {
453
505
  /** Cycle counts keyed as "hookName/triggerUnitType/triggerUnitId". */
454
506
  cycleCounts: Record<string, number>;
507
+ /** In-flight hook, persisted so blocking gates cannot be skipped after resume. */
508
+ activeHook?: HookExecutionState | null;
509
+ /** Remaining hook queue by hook name and trigger unit. */
510
+ hookQueue?: Array<{
511
+ hookName: string;
512
+ triggerUnitType: string;
513
+ triggerUnitId: string;
514
+ forceRun?: boolean;
515
+ }>;
455
516
  /** Timestamp of last state save. */
456
517
  savedAt: string;
457
518
  }
@@ -465,6 +526,8 @@ export interface HookStatusEntry {
465
526
  enabled: boolean;
466
527
  /** What unit types it targets. */
467
528
  targets: string[];
529
+ /** Whether this post-unit hook is advisory or blocking. */
530
+ criticality?: PostUnitHookCriticality;
468
531
  /** Current cycle counts for active triggers. */
469
532
  activeCycles: Record<string, number>;
470
533
  }
@@ -644,7 +707,8 @@ export interface CompleteSliceParams {
644
707
  sliceTitle: string;
645
708
  oneLiner: string;
646
709
  narrative: string;
647
- verification: string;
710
+ /** @optional — if omitted, verification section is left blank in summary */
711
+ verification?: string;
648
712
  uatContent: string;
649
713
  /** @optional — defaults to [] when omitted by models with limited tool-calling */
650
714
  keyFiles?: string[];