@opengsd/gsd-pi 1.2.0-dev.84c56d87 → 1.2.0-dev.9ad8ae33

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 (460) hide show
  1. package/dist/headless-events.js +7 -5
  2. package/dist/mcp-server.js +2 -1
  3. package/dist/resource-loader.d.ts +10 -5
  4. package/dist/resource-loader.js +121 -6
  5. package/dist/resources/.managed-resources-content-hash +1 -1
  6. package/dist/resources/GSD-WORKFLOW.md +5 -4
  7. package/dist/resources/extensions/async-jobs/async-bash-tool.js +30 -64
  8. package/dist/resources/extensions/async-jobs/await-tool.js +80 -12
  9. package/dist/resources/extensions/async-jobs/index.js +65 -0
  10. package/dist/resources/extensions/async-jobs/job-manager.js +12 -1
  11. package/dist/resources/extensions/bg-shell/bg-shell-command.js +6 -6
  12. package/dist/resources/extensions/bg-shell/bg-shell-tool.js +10 -7
  13. package/dist/resources/extensions/bg-shell/overlay.js +9 -6
  14. package/dist/resources/extensions/bg-shell/process-manager.js +54 -25
  15. package/dist/resources/extensions/bg-shell/readiness-detector.js +11 -0
  16. package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +17 -2
  17. package/dist/resources/extensions/gsd/auto/detect-stuck.js +33 -13
  18. package/dist/resources/extensions/gsd/auto/dispatch-history.js +105 -0
  19. package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
  20. package/dist/resources/extensions/gsd/auto/loop.js +4 -1
  21. package/dist/resources/extensions/gsd/auto/orchestrator.js +89 -54
  22. package/dist/resources/extensions/gsd/auto/phases.js +49 -6
  23. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  24. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -34
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +50 -58
  26. package/dist/resources/extensions/gsd/auto-model-selection.js +36 -13
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +30 -12
  28. package/dist/resources/extensions/gsd/auto-prompts.js +78 -19
  29. package/dist/resources/extensions/gsd/auto-start.js +35 -15
  30. package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
  31. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +5 -4
  32. package/dist/resources/extensions/gsd/auto-verification.js +23 -30
  33. package/dist/resources/extensions/gsd/auto-worktree.js +14 -1
  34. package/dist/resources/extensions/gsd/auto.js +37 -1
  35. package/dist/resources/extensions/gsd/blocked-models.js +28 -0
  36. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
  37. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +23 -6
  38. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
  39. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +145 -50
  40. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +302 -80
  41. package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +83 -0
  42. package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
  43. package/dist/resources/extensions/gsd/commands/context.js +16 -2
  44. package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
  45. package/dist/resources/extensions/gsd/consent-question.js +353 -0
  46. package/dist/resources/extensions/gsd/consent-verdict.js +63 -0
  47. package/dist/resources/extensions/gsd/constants.js +0 -2
  48. package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
  49. package/dist/resources/extensions/gsd/db/queries.js +26 -0
  50. package/dist/resources/extensions/gsd/db-writer.js +8 -17
  51. package/dist/resources/extensions/gsd/dispatch-guard.js +10 -35
  52. package/dist/resources/extensions/gsd/doctor-engine-checks.js +5 -5
  53. package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
  54. package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
  55. package/dist/resources/extensions/gsd/exec-sandbox.js +30 -10
  56. package/dist/resources/extensions/gsd/files.js +33 -19
  57. package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
  58. package/dist/resources/extensions/gsd/gsd-db.js +2 -1
  59. package/dist/resources/extensions/gsd/guidance.js +60 -0
  60. package/dist/resources/extensions/gsd/guided-flow.js +6 -3
  61. package/dist/resources/extensions/gsd/markdown-renderer.js +10 -0
  62. package/dist/resources/extensions/gsd/milestone-closeout.js +85 -24
  63. package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
  64. package/dist/resources/extensions/gsd/milestone-reopen-events.js +3 -5
  65. package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
  66. package/dist/resources/extensions/gsd/preferences-models.js +2 -2
  67. package/dist/resources/extensions/gsd/projection-flush.js +7 -0
  68. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  69. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  72. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
  73. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  74. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  75. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  76. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  77. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  78. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  79. package/dist/resources/extensions/gsd/prompts/run-uat.md +7 -5
  80. package/dist/resources/extensions/gsd/prompts/system.md +5 -2
  81. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  82. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  83. package/dist/resources/extensions/gsd/reactive-graph.js +8 -1
  84. package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
  85. package/dist/resources/extensions/gsd/safety/destructive-confirmation.js +108 -0
  86. package/dist/resources/extensions/gsd/session-lock.js +1 -1
  87. package/dist/resources/extensions/gsd/state.js +5 -0
  88. package/dist/resources/extensions/gsd/tool-contract.js +14 -3
  89. package/dist/resources/extensions/gsd/tool-presentation-plan.js +4 -4
  90. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  91. package/dist/resources/extensions/gsd/tools/complete-slice.js +22 -12
  92. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  93. package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -0
  94. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
  95. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
  96. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
  97. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  98. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
  99. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
  100. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
  101. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
  102. package/dist/resources/extensions/gsd/uat-policy.js +40 -15
  103. package/dist/resources/extensions/gsd/unit-context-composer.js +65 -0
  104. package/dist/resources/extensions/gsd/verdict-parser.js +1 -1
  105. package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
  106. package/dist/resources/extensions/gsd/workflow-event-ledger.js +91 -0
  107. package/dist/resources/extensions/gsd/workflow-event-vocabulary.js +46 -0
  108. package/dist/resources/extensions/gsd/workflow-events.js +6 -18
  109. package/dist/resources/extensions/gsd/workflow-reconcile.js +21 -56
  110. package/dist/resources/extensions/gsd/worktree-lifecycle.js +3 -2
  111. package/dist/resources/extensions/gsd/worktree-manager.js +7 -1
  112. package/dist/resources/extensions/gsd/worktree.js +8 -1
  113. package/dist/resources/extensions/shared/gsd-browser-cli.js +45 -3
  114. package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
  115. package/dist/resources/shared/package-manager-detection.js +1 -1
  116. package/dist/resources/shared/package.json +3 -0
  117. package/dist/resources/skills/create-skill/SKILL.md +3 -0
  118. package/dist/resources/skills/create-skill/references/skill-structure.md +1 -0
  119. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  120. package/dist/update-check.d.ts +2 -0
  121. package/dist/update-check.js +24 -1
  122. package/dist/update-cmd.js +20 -3
  123. package/dist/web/standalone/.next/BUILD_ID +1 -1
  124. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  125. package/dist/web/standalone/.next/build-manifest.json +3 -3
  126. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  127. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  128. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  129. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  137. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/index.html +1 -1
  147. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  148. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  149. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  154. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  155. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  158. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  159. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  160. package/dist/web/standalone/.next/static/chunks/{796.cf859a427a2cb2ac.js → 796.e0bdc932325d7e03.js} +1 -1
  161. package/dist/web/standalone/.next/static/chunks/{webpack-fbea77b5f9953368.js → webpack-f0285ce91d4ec9ef.js} +1 -1
  162. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  163. package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
  164. package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
  165. package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
  166. package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
  167. package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
  168. package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
  169. package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
  170. package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
  171. package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
  172. package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
  173. package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
  174. package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
  175. package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
  176. package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
  177. package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
  178. package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
  179. package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
  180. package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
  181. package/dist/web/standalone/node_modules/postcss/package.json +48 -48
  182. package/package.json +2 -2
  183. package/packages/cloud-mcp-gateway/package.json +2 -2
  184. package/packages/contracts/dist/rpc.d.ts +1 -0
  185. package/packages/contracts/dist/rpc.d.ts.map +1 -1
  186. package/packages/contracts/dist/rpc.js.map +1 -1
  187. package/packages/contracts/package.json +1 -1
  188. package/packages/daemon/package.json +4 -4
  189. package/packages/gsd-agent-core/package.json +5 -5
  190. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +5 -0
  191. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  192. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
  193. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  194. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  195. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +7 -0
  196. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  197. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  198. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +8 -1
  199. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  200. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  201. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +11 -1
  202. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  203. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  204. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +4 -4
  205. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  206. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  207. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +3 -1
  208. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  209. package/packages/gsd-agent-modes/package.json +7 -7
  210. package/packages/mcp-server/dist/cli.js +10 -5
  211. package/packages/mcp-server/dist/cli.js.map +1 -1
  212. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
  213. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
  214. package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
  215. package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
  216. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  217. package/packages/mcp-server/dist/server.js +4 -0
  218. package/packages/mcp-server/dist/server.js.map +1 -1
  219. package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
  220. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  221. package/packages/mcp-server/dist/workflow-tools.js +99 -38
  222. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  223. package/packages/mcp-server/package.json +5 -4
  224. package/packages/native/package.json +1 -1
  225. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts +1 -0
  226. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  227. package/packages/pi-agent-core/dist/harness/env/nodejs.js +34 -3
  228. package/packages/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  229. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  230. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  231. package/packages/pi-agent-core/dist/index.js +3 -0
  232. package/packages/pi-agent-core/dist/index.js.map +1 -1
  233. package/packages/pi-agent-core/package.json +1 -1
  234. package/packages/pi-ai/README.md +1 -0
  235. package/packages/pi-ai/dist/image-models.generated.d.ts +2 -2
  236. package/packages/pi-ai/dist/image-models.generated.js +6 -6
  237. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  238. package/packages/pi-ai/dist/index.d.ts +2 -0
  239. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  240. package/packages/pi-ai/dist/index.js +2 -0
  241. package/packages/pi-ai/dist/index.js.map +1 -1
  242. package/packages/pi-ai/dist/models.generated.d.ts +419 -221
  243. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  244. package/packages/pi-ai/dist/models.generated.js +460 -261
  245. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  246. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  247. package/packages/pi-ai/dist/providers/anthropic.js +12 -7
  248. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  249. package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
  250. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  251. package/packages/pi-ai/dist/providers/google-shared.js +12 -3
  252. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  253. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  254. package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
  255. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  256. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
  257. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
  258. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
  259. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
  260. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  261. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
  262. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  263. package/packages/pi-ai/package.json +3 -2
  264. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +2 -2
  265. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/core/auth-storage.js +19 -13
  267. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/core/provider-readiness.d.ts.map +1 -1
  269. package/packages/pi-coding-agent/dist/core/provider-readiness.js +13 -6
  270. package/packages/pi-coding-agent/dist/core/provider-readiness.js.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +11 -0
  272. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  273. package/packages/pi-coding-agent/dist/core/tools/bash.js +53 -11
  274. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  275. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  276. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  277. package/packages/pi-coding-agent/dist/index.js +1 -1
  278. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/utils/shell.d.ts +28 -2
  280. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  281. package/packages/pi-coding-agent/dist/utils/shell.js +56 -10
  282. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  283. package/packages/pi-coding-agent/package.json +7 -7
  284. package/packages/pi-tui/package.json +2 -2
  285. package/packages/rpc-client/package.json +2 -2
  286. package/pkg/package.json +1 -1
  287. package/src/resources/GSD-WORKFLOW.md +5 -4
  288. package/src/resources/extensions/async-jobs/async-bash-cancel.test.ts +360 -0
  289. package/src/resources/extensions/async-jobs/async-bash-tool.ts +33 -56
  290. package/src/resources/extensions/async-jobs/await-tool.test.ts +139 -0
  291. package/src/resources/extensions/async-jobs/await-tool.ts +82 -12
  292. package/src/resources/extensions/async-jobs/index.ts +79 -0
  293. package/src/resources/extensions/async-jobs/job-manager.ts +21 -1
  294. package/src/resources/extensions/bg-shell/bg-shell-command.ts +6 -6
  295. package/src/resources/extensions/bg-shell/bg-shell-tool.ts +10 -6
  296. package/src/resources/extensions/bg-shell/overlay.ts +9 -5
  297. package/src/resources/extensions/bg-shell/process-manager.ts +50 -25
  298. package/src/resources/extensions/bg-shell/readiness-detector.ts +12 -0
  299. package/src/resources/extensions/bg-shell/tests/lifecycle-and-utilities.test.ts +48 -1
  300. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +40 -1
  301. package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +21 -3
  302. package/src/resources/extensions/gsd/auto/detect-stuck.ts +32 -9
  303. package/src/resources/extensions/gsd/auto/dispatch-history.ts +152 -0
  304. package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
  305. package/src/resources/extensions/gsd/auto/loop.ts +4 -1
  306. package/src/resources/extensions/gsd/auto/orchestrator.ts +98 -56
  307. package/src/resources/extensions/gsd/auto/phases.ts +65 -26
  308. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  309. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +18 -48
  310. package/src/resources/extensions/gsd/auto-dispatch.ts +48 -61
  311. package/src/resources/extensions/gsd/auto-model-selection.ts +41 -12
  312. package/src/resources/extensions/gsd/auto-post-unit.ts +33 -12
  313. package/src/resources/extensions/gsd/auto-prompts.ts +115 -35
  314. package/src/resources/extensions/gsd/auto-start.ts +36 -18
  315. package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
  316. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +4 -4
  317. package/src/resources/extensions/gsd/auto-verification.ts +26 -28
  318. package/src/resources/extensions/gsd/auto-worktree.ts +14 -1
  319. package/src/resources/extensions/gsd/auto.ts +44 -1
  320. package/src/resources/extensions/gsd/blocked-models.ts +49 -0
  321. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
  322. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +23 -6
  323. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
  324. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +163 -55
  325. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +350 -86
  326. package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +108 -0
  327. package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
  328. package/src/resources/extensions/gsd/commands/context.ts +16 -2
  329. package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
  330. package/src/resources/extensions/gsd/consent-question.ts +431 -0
  331. package/src/resources/extensions/gsd/consent-verdict.ts +86 -0
  332. package/src/resources/extensions/gsd/constants.ts +0 -3
  333. package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
  334. package/src/resources/extensions/gsd/db/queries.ts +37 -0
  335. package/src/resources/extensions/gsd/db-writer.ts +11 -19
  336. package/src/resources/extensions/gsd/dispatch-guard.ts +8 -31
  337. package/src/resources/extensions/gsd/doctor-engine-checks.ts +5 -4
  338. package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
  339. package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
  340. package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
  341. package/src/resources/extensions/gsd/files.ts +33 -12
  342. package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
  343. package/src/resources/extensions/gsd/gsd-db.ts +4 -3
  344. package/src/resources/extensions/gsd/guidance.ts +78 -0
  345. package/src/resources/extensions/gsd/guided-flow.ts +21 -26
  346. package/src/resources/extensions/gsd/markdown-renderer.ts +11 -0
  347. package/src/resources/extensions/gsd/milestone-closeout.ts +109 -24
  348. package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
  349. package/src/resources/extensions/gsd/milestone-reopen-events.ts +3 -6
  350. package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
  351. package/src/resources/extensions/gsd/preferences-models.ts +2 -1
  352. package/src/resources/extensions/gsd/projection-flush.ts +20 -0
  353. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  354. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  355. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  356. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  357. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
  358. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  359. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  360. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  361. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  362. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  363. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  364. package/src/resources/extensions/gsd/prompts/run-uat.md +7 -5
  365. package/src/resources/extensions/gsd/prompts/system.md +5 -2
  366. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  367. package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  368. package/src/resources/extensions/gsd/reactive-graph.ts +11 -1
  369. package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
  370. package/src/resources/extensions/gsd/safety/destructive-confirmation.ts +134 -0
  371. package/src/resources/extensions/gsd/session-lock.ts +1 -1
  372. package/src/resources/extensions/gsd/state.ts +5 -0
  373. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +97 -1
  374. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +198 -26
  375. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
  376. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +236 -0
  377. package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
  378. package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +144 -0
  379. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
  380. package/src/resources/extensions/gsd/tests/consent-question.test.ts +351 -0
  381. package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +67 -0
  382. package/src/resources/extensions/gsd/tests/db-writer.test.ts +15 -4
  383. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +10 -10
  384. package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
  385. package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +12 -2
  386. package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +273 -0
  387. package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
  388. package/src/resources/extensions/gsd/tests/dynamic-bash-no-cap.test.ts +132 -0
  389. package/src/resources/extensions/gsd/tests/engine-hook-contract.test.ts +148 -0
  390. package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +193 -0
  391. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +29 -1
  392. package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
  393. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +27 -0
  394. package/src/resources/extensions/gsd/tests/guidance.test.ts +23 -0
  395. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
  396. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +199 -0
  397. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
  398. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
  399. package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +32 -1
  400. package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
  401. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
  402. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +138 -0
  403. package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
  404. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
  405. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +124 -6
  406. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
  407. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
  408. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +17 -0
  409. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +41 -0
  410. package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
  411. package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
  412. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +88 -0
  413. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +44 -0
  414. package/src/resources/extensions/gsd/tests/uok-audit-unified.test.ts +8 -0
  415. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
  416. package/src/resources/extensions/gsd/tests/workflow-events.test.ts +19 -0
  417. package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +20 -0
  418. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
  419. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +22 -0
  420. package/src/resources/extensions/gsd/tests/worktree.test.ts +18 -0
  421. package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +358 -0
  422. package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -1
  423. package/src/resources/extensions/gsd/tool-contract.ts +38 -3
  424. package/src/resources/extensions/gsd/tool-presentation-plan.ts +4 -4
  425. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  426. package/src/resources/extensions/gsd/tools/complete-slice.ts +22 -12
  427. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
  428. package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -0
  429. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
  430. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
  431. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
  432. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  433. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
  434. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
  435. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
  436. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
  437. package/src/resources/extensions/gsd/uat-policy.ts +60 -15
  438. package/src/resources/extensions/gsd/unit-context-composer.ts +99 -0
  439. package/src/resources/extensions/gsd/verdict-parser.ts +1 -1
  440. package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
  441. package/src/resources/extensions/gsd/workflow-event-ledger.ts +131 -0
  442. package/src/resources/extensions/gsd/workflow-event-vocabulary.ts +59 -0
  443. package/src/resources/extensions/gsd/workflow-events.ts +12 -20
  444. package/src/resources/extensions/gsd/workflow-reconcile.ts +29 -62
  445. package/src/resources/extensions/gsd/worktree-lifecycle.ts +3 -8
  446. package/src/resources/extensions/gsd/worktree-manager.ts +6 -1
  447. package/src/resources/extensions/gsd/worktree.ts +7 -1
  448. package/src/resources/extensions/shared/gsd-browser-cli.ts +54 -3
  449. package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
  450. package/src/resources/shared/package-manager-detection.ts +1 -1
  451. package/src/resources/shared/package.json +3 -0
  452. package/src/resources/skills/create-skill/SKILL.md +3 -0
  453. package/src/resources/skills/create-skill/references/skill-structure.md +1 -0
  454. package/dist/resources/extensions/gsd/user-input-boundary.js +0 -218
  455. package/dist/resources/skills/gsd-browser/SKILL.md +0 -41
  456. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -173
  457. package/src/resources/extensions/gsd/user-input-boundary.ts +0 -216
  458. package/src/resources/skills/gsd-browser/SKILL.md +0 -41
  459. /package/dist/web/standalone/.next/static/{AOpDeK_gJHU8OZjRo31gQ → FBNo5cT_chy7YNoAQsU3o}/_buildManifest.js +0 -0
  460. /package/dist/web/standalone/.next/static/{AOpDeK_gJHU8OZjRo31gQ → FBNo5cT_chy7YNoAQsU3o}/_ssgManifest.js +0 -0
@@ -186,6 +186,7 @@ export function writeLock(basePath, unitType, unitId, sessionFile) {
186
186
  * stale session-file pointer.
187
187
  */
188
188
  export function clearLock(basePath) {
189
+ const legacyLock = readLegacyLock(basePath);
189
190
  clearLegacyLockFile(basePath);
190
191
  if (!isDbAvailable())
191
192
  return;
@@ -198,9 +199,13 @@ export function clearLock(basePath) {
198
199
  deleteRuntimeKv("worker", staleWorker.worker_id, SESSION_FILE_KV_KEY);
199
200
  return;
200
201
  }
201
- const lock = readLegacyLock(basePath);
202
- if (lock?.pid)
203
- markWorkerStoppingByPid(projectRoot, lock.pid);
202
+ if (legacyLock?.pid) {
203
+ markWorkerStoppingByPid(projectRoot, legacyLock.pid);
204
+ const workerByLegacyPid = getAllAutoWorkers().find((w) => w.pid === legacyLock.pid
205
+ && normalizeRealPath(w.project_root_realpath) === projectRoot);
206
+ if (workerByLegacyPid)
207
+ forceReleaseLeasesForWorker(workerByLegacyPid.worker_id);
208
+ }
204
209
  const worker = findActiveWorkerForCurrentProcess(projectRoot);
205
210
  if (worker)
206
211
  deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
@@ -5,6 +5,7 @@
5
5
  // Read-only callers (forensics, dashboard, doctor) depend on this seam, not on
6
6
  // the single-writer surface.
7
7
  import { getDbOrNull } from "./engine.js";
8
+ import { isClosedStatus } from "../status-guards.js";
8
9
  import { getGateIdsForTurn } from "../gate-registry.js";
9
10
  import { emptyTaskStatusCounts, rowToActiveTaskSummary, rowToIdStatusSummary, rowToTaskStatusCounts, rowsToStringColumn, } from "../db-lightweight-query-rows.js";
10
11
  import { rowToActiveDecision, rowToActiveRequirement, rowToDecision, rowToRequirement, rowsToRequirementCounts, } from "../db-decision-requirement-rows.js";
@@ -217,6 +218,31 @@ export function getMilestoneSlices(milestoneId) {
217
218
  const rows = getDbOrNull().prepare("SELECT * FROM slices WHERE milestone_id = :mid ORDER BY sequence, id").all({ ":mid": milestoneId });
218
219
  return rows.map(rowToSlice);
219
220
  }
221
+ /**
222
+ * Consolidated DB read for dispatch/gate/completion decisions (ADR-017).
223
+ * `done` uses the canonical closed-status predicate (`isClosedStatus`) — the
224
+ * same vocabulary the SQL terminal-status fragment derives from. Decision
225
+ * paths must consume this instead of parsing `.gsd/*.md` projections.
226
+ * Rows keep `getMilestoneSlices` ordering (sequence, then id).
227
+ */
228
+ export function getMilestoneSliceSummaries(milestoneId) {
229
+ return getMilestoneSlices(milestoneId).map((s) => ({
230
+ id: s.id,
231
+ title: s.title,
232
+ done: isClosedStatus(s.status),
233
+ depends: s.depends ?? [],
234
+ }));
235
+ }
236
+ /**
237
+ * Ids of slices closed per the canonical status vocabulary (ADR-017), in
238
+ * milestone order. Thin wrapper over `getMilestoneSliceSummaries` for the
239
+ * common "which slices are done?" decision-path read.
240
+ */
241
+ export function getClosedSliceIds(milestoneId) {
242
+ return getMilestoneSliceSummaries(milestoneId)
243
+ .filter((s) => s.done)
244
+ .map((s) => s.id);
245
+ }
220
246
  export function getArtifact(path) {
221
247
  if (!getDbOrNull())
222
248
  return null;
@@ -620,27 +620,18 @@ async function mirrorDecisionToMemory(id, normalizedFields) {
620
620
  /**
621
621
  * Extract a milestone/slice reference from a deferral decision.
622
622
  *
623
- * Detects deferrals by checking:
624
- * - scope contains "defer" (e.g., "deferral", "defer")
625
- * - choice or decision contains "defer" + an M###/S## pattern
623
+ * Detects deferrals when the slice reference is part of the deferral phrase.
626
624
  *
627
625
  * Returns { milestoneId, sliceId } if found, null otherwise.
628
626
  */
629
627
  export function extractDeferredSliceRef(fields) {
630
- const isDeferral = /\bdefer(?:ral|red|ring|s)?\b/i.test(fields.scope) ||
631
- /\bdefer(?:ral|red|ring|s)?\b/i.test(fields.choice) ||
632
- /\bdefer(?:ral|red|ring|s)?\b/i.test(fields.decision);
633
- if (!isDeferral)
634
- return null;
635
- // Look for M###/S## pattern in choice first, then decision
636
- const slicePattern = /\b(M\d{3,4})\/(S\d{2,3})\b/;
637
- const choiceMatch = fields.choice.match(slicePattern);
638
- if (choiceMatch) {
639
- return { milestoneId: choiceMatch[1], sliceId: choiceMatch[2] };
640
- }
641
- const decisionMatch = fields.decision.match(slicePattern);
642
- if (decisionMatch) {
643
- return { milestoneId: decisionMatch[1], sliceId: decisionMatch[2] };
628
+ const defersSlicePattern = /\bdefer(?:ral|red|ring|s)?\b\s+(?:(?:of|the)\s+)*(?:slice\s+)?\b(M\d{3,4})\/(S\d{2,3})\b/i;
629
+ const sliceIsDeferredPattern = /\b(M\d{3,4})\/(S\d{2,3})\b\s+(?:is|was|will be|should be|can be)\s+defer(?:red|ring)?\b/i;
630
+ for (const text of [fields.choice, fields.decision, fields.scope]) {
631
+ const match = text.match(defersSlicePattern) ?? text.match(sliceIsDeferredPattern);
632
+ if (match) {
633
+ return { milestoneId: match[1], sliceId: match[2] };
634
+ }
644
635
  }
645
636
  return null;
646
637
  }
@@ -2,9 +2,8 @@
2
2
  import { resolveMilestoneFile } from "./paths.js";
3
3
  import { findMilestoneIds } from "./guided-flow.js";
4
4
  import { parseUnitId } from "./unit-id.js";
5
- import { isDbAvailable, getMilestoneSlices, getMilestone } from "./gsd-db.js";
6
- import { parseRoadmap } from "./parsers-legacy.js";
7
- import { isClosedStatus, isSkippedForDispatch } from "./status-guards.js";
5
+ import { isDbAvailable, getMilestoneSliceSummaries, getMilestone } from "./gsd-db.js";
6
+ import { isSkippedForDispatch } from "./status-guards.js";
8
7
  import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.js";
9
8
  import { readFileSync } from "node:fs";
10
9
  const SLICE_DISPATCH_TYPES = new Set([
@@ -97,38 +96,14 @@ export function getPriorSliceCompletionBlocker(base, _mainBranch, unitType, unit
97
96
  continue;
98
97
  }
99
98
  }
100
- let slices = null;
101
- if (isDbAvailable()) {
102
- const rows = getMilestoneSlices(mid);
103
- if (rows.length > 0) {
104
- slices = rows.map((r) => ({
105
- id: r.id,
106
- done: isClosedStatus(r.status),
107
- depends: r.depends ?? [],
108
- }));
109
- }
110
- }
111
- if (!slices) {
112
- // File-based fallback: parse roadmap checkboxes
113
- const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
114
- if (!roadmapPath)
115
- continue;
116
- let roadmapContent;
117
- try {
118
- roadmapContent = readFileSync(roadmapPath, "utf-8");
119
- }
120
- catch {
121
- continue;
122
- }
123
- const parsed = parseRoadmap(roadmapContent);
124
- if (parsed.slices.length === 0)
125
- continue;
126
- slices = parsed.slices.map((s) => ({
127
- id: s.id,
128
- done: s.done,
129
- depends: s.depends ?? [],
130
- }));
131
- }
99
+ // DB-authoritative eligibility list (ADR-017) — markdown projections are
100
+ // never parsed for dispatch decisions. No DB / no rows → skip this
101
+ // milestone's check (unknown is not a blocker).
102
+ if (!isDbAvailable())
103
+ continue;
104
+ const slices = getMilestoneSliceSummaries(mid);
105
+ if (slices.length === 0)
106
+ continue;
132
107
  if (mid !== targetMid) {
133
108
  const incomplete = slices.find((slice) => !slice.done);
134
109
  if (incomplete) {
@@ -1,11 +1,11 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
- import { join } from "node:path";
3
2
  import { isDbAvailable, _getAdapter } from "./gsd-db.js";
4
3
  import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
5
4
  import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
6
5
  import { deriveState } from "./state.js";
6
+ import { workflowEventLogPath } from "./workflow-event-ledger.js";
7
7
  import { readEvents } from "./workflow-events.js";
8
- import { renderAllProjections } from "./workflow-projections.js";
8
+ import { flushWorkflowProjections } from "./projection-flush.js";
9
9
  export async function checkEngineHealth(basePath, issues, fixesApplied) {
10
10
  const dbPath = resolveGsdPathContract(basePath).projectDb;
11
11
  if (!isDbAvailable() && existsSync(dbPath)) {
@@ -228,7 +228,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
228
228
  // relative to the event log and re-render them.
229
229
  try {
230
230
  if (isDbAvailable()) {
231
- const eventLogPath = join(basePath, ".gsd", "event-log.jsonl");
231
+ const eventLogPath = workflowEventLogPath(basePath);
232
232
  const events = readEvents(eventLogPath);
233
233
  if (events.length > 0) {
234
234
  const lastEventTs = new Date(events[events.length - 1].ts).getTime();
@@ -239,7 +239,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
239
239
  const roadmapPath = resolveMilestoneFile(basePath, milestone.id, "ROADMAP");
240
240
  if (!roadmapPath || !existsSync(roadmapPath)) {
241
241
  try {
242
- await renderAllProjections(basePath, milestone.id);
242
+ await flushWorkflowProjections(basePath, { milestoneId: milestone.id });
243
243
  fixesApplied.push(`re-rendered missing projections for ${milestone.id}`);
244
244
  }
245
245
  catch {
@@ -250,7 +250,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
250
250
  const projectionMtime = statSync(roadmapPath).mtimeMs;
251
251
  if (lastEventTs > projectionMtime) {
252
252
  try {
253
- await renderAllProjections(basePath, milestone.id);
253
+ await flushWorkflowProjections(basePath, { milestoneId: milestone.id });
254
254
  fixesApplied.push(`re-rendered stale projections for ${milestone.id}`);
255
255
  }
256
256
  catch {
@@ -3,10 +3,9 @@ import { spawnSync } from "node:child_process";
3
3
  import { cpSync, existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
4
  import { dirname, join } from "node:path";
5
5
  import { loadFile } from "./files.js";
6
- import { parseRoadmap as parseLegacyRoadmap } from "./parsers-legacy.js";
7
- import { isDbAvailable, getMilestone } from "./gsd-db.js";
8
6
  import { resolveMilestoneFile } from "./paths.js";
9
- import { deriveState, isMilestoneComplete } from "./state.js";
7
+ import { isCompletedMilestoneTerminal } from "./milestone-closeout.js";
8
+ import { deriveState } from "./state.js";
10
9
  import { allWorktreesDirs, createWorktree, listWorktrees, resolveGitDir } from "./worktree-manager.js";
11
10
  import { abortAndReset } from "./git-self-heal.js";
12
11
  import { RUNTIME_EXCLUSION_PATHS, resolveMilestoneIntegrationBranch, writeIntegrationBranch } from "./git-service.js";
@@ -141,21 +140,6 @@ function getSnapshotDiffCheckFailure(basePath) {
141
140
  }
142
141
  return failures.length > 0 ? failures.join("\n") : null;
143
142
  }
144
- async function isCompletedMilestoneTerminal(basePath, milestoneId) {
145
- const summaryPath = resolveMilestoneFile(basePath, milestoneId, "SUMMARY");
146
- if (!summaryPath)
147
- return false;
148
- if (isDbAvailable()) {
149
- const milestone = getMilestone(milestoneId);
150
- return !!milestone && milestone.status === "complete";
151
- }
152
- const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
153
- const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
154
- if (!roadmapContent)
155
- return false;
156
- const roadmap = parseLegacyRoadmap(roadmapContent);
157
- return isMilestoneComplete(roadmap);
158
- }
159
143
  export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode = "none") {
160
144
  // Degrade gracefully if not a git repo
161
145
  if (!nativeIsRepo(basePath)) {
@@ -0,0 +1,70 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Typed contract for which tool lifecycle hooks fire under which
3
+ // engine, plus the single seam for tool-name normalization.
4
+ //
5
+ // ── Why this contract exists ────────────────────────────────────────────────
6
+ // External engines (claude-code-cli) pre-execute tools inside the vendor CLI
7
+ // and hand the agent loop a `toolCall.externalResult`. The loop short-circuits
8
+ // before `beforeToolCall`/`afterToolCall` ever run for those calls
9
+ // (packages/pi-agent-core/src/agent-loop.ts, prepareToolCall: the
10
+ // externalResult branch returns an "immediate" outcome before the
11
+ // config.beforeToolCall invocation, and "immediate" outcomes skip
12
+ // finalizeExecutedToolCall where config.afterToolCall lives).
13
+ // `beforeToolCall`/`afterToolCall` are exactly what the extension runner maps
14
+ // to the `tool_call`/`tool_result` extension events
15
+ // (packages/gsd-agent-core/src/session/agent-session-extensions.ts,
16
+ // installAgentToolHooks).
17
+ //
18
+ // `tool_execution_start` is emitted unconditionally before prepareToolCall
19
+ // and `tool_execution_end` after every finalized outcome — immediate or
20
+ // executed — so those two are the ONLY tool lifecycle hooks that fire for
21
+ // every tool call on every engine.
22
+ //
23
+ // Consequence: any enforcement attached only to `tool_call` (blocking) or
24
+ // `tool_result` (rewriting) is silently dead under external engines. Safety
25
+ // enforcement must ride `tool_execution_start`/`tool_execution_end`, or
26
+ // mirror across both with toolCallId dedup. See the per-registration contract
27
+ // comments in bootstrap/register-hooks.ts for how each registered hook
28
+ // honors (or knowingly violates) this contract.
29
+ import { stripMcpToolPrefix } from "./mcp-tool-name.js";
30
+ import { canonicalWorkflowSurfaceToolName } from "./workflow-tool-surface.js";
31
+ /**
32
+ * Tool lifecycle hooks that fire for EVERY tool call on EVERY engine,
33
+ * including external engines (claude-code-cli) that pre-execute tools.
34
+ * Attach safety-critical enforcement and evidence collection here.
35
+ */
36
+ export const UNIVERSAL_TOOL_HOOKS = ["tool_execution_start", "tool_execution_end"];
37
+ /**
38
+ * Tool lifecycle hooks that fire ONLY for natively executed tools. External
39
+ * engines pre-execute tools (externalResult), short-circuiting the agent
40
+ * loop's beforeToolCall/afterToolCall — so handlers on these events never run
41
+ * for those calls. Blocking guards attached only here are dead under external
42
+ * engines; they need a tool_execution_start mirror to be universal.
43
+ */
44
+ export const NATIVE_ONLY_TOOL_HOOKS = ["tool_call", "tool_result"];
45
+ // Non-tool lifecycle events (session_start, agent_end, message_update, ...)
46
+ // are intentionally NOT classified here: they are emitted by the session
47
+ // host / extension runner independent of tool execution, so the external
48
+ // engine short-circuit above does not apply to them. Only classify events
49
+ // whose engine behavior has been verified against the agent loop.
50
+ /**
51
+ * Canonical tool name: strips the `mcp__<server>__` prefix when present,
52
+ * nothing else. Use for identity checks against host/native tool names and
53
+ * for any guard that must NOT conflate workflow aliases with their canonical
54
+ * tools (e.g. write gates, evidence keys).
55
+ *
56
+ * Malformed MCP names (empty server or empty tool segment, e.g.
57
+ * `mcp____tool` or `mcp__server__`) are returned unchanged.
58
+ */
59
+ export function canonicalToolName(toolName) {
60
+ return stripMcpToolPrefix(toolName);
61
+ }
62
+ /**
63
+ * Workflow-aware canonical tool name: strips the MCP prefix AND resolves
64
+ * workflow tool aliases to their canonical contract names. Use whenever the
65
+ * name is compared against the workflow tool surface (scoping, presentation,
66
+ * dispatch) — plain {@link canonicalToolName} would miss alias spellings.
67
+ */
68
+ export function canonicalWorkflowToolName(toolName) {
69
+ return canonicalWorkflowSurfaceToolName(toolName);
70
+ }
@@ -11,7 +11,11 @@ import { spawn } from "node:child_process";
11
11
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
12
12
  import { randomUUID } from "node:crypto";
13
13
  import { resolve } from "node:path";
14
+ import { killProcessTree, SIGKILL_GRACE_MS, HARD_DEADLINE_MS } from "@gsd/pi-coding-agent";
14
15
  const ALWAYS_FORWARD_ENV = ["PATH", "HOME"];
16
+ // SIGKILL_GRACE_MS / HARD_DEADLINE_MS are imported from @gsd/pi-coding-agent
17
+ // (shell.ts) — the single source of truth for the graceful-kill timing ladder —
18
+ // so this sandbox can never drift from the canonical kill path it delegates to.
15
19
  export const EXEC_DEFAULTS = {
16
20
  clampTimeoutMs: 600_000,
17
21
  defaultTimeoutMs: 30_000,
@@ -120,6 +124,7 @@ export function runExecSandbox(request, opts) {
120
124
  exit_code: null,
121
125
  signal: null,
122
126
  timed_out: false,
127
+ force_resolved: false,
123
128
  duration_ms: duration,
124
129
  stdout_bytes: 0,
125
130
  stderr_bytes: Buffer.byteLength(`spawn error: ${message}\n`),
@@ -172,24 +177,38 @@ export function runExecSandbox(request, opts) {
172
177
  stderrTruncated = true;
173
178
  }
174
179
  });
180
+ const effectiveGraceMs = opts.kill_grace_ms ?? SIGKILL_GRACE_MS;
181
+ const effectiveForceResolveDelay = opts.force_resolve_delay_ms ?? (effectiveGraceMs + HARD_DEADLINE_MS);
175
182
  let timedOut = false;
183
+ let settled = false;
184
+ let forceResolveTimer;
176
185
  const timer = setTimeout(() => {
177
186
  timedOut = true;
178
- if (useProcessGroup && child.pid != null) {
179
- try {
180
- process.kill(-child.pid, "SIGKILL");
181
- }
182
- catch {
183
- child.kill("SIGKILL");
184
- }
187
+ // killProcessTree handles both platforms and kills the whole tree: on Unix
188
+ // it signals the process group (SIGTERM -> grace -> SIGKILL); on Windows it
189
+ // force-kills the tree via taskkill /F /T. Using child.kill("SIGTERM") here
190
+ // would only terminate the direct child on Windows, orphaning grandchildren.
191
+ if (child.pid != null) {
192
+ killProcessTree(child.pid, { graceMs: effectiveGraceMs });
185
193
  }
186
194
  else {
187
- child.kill("SIGKILL");
195
+ child.kill("SIGTERM");
188
196
  }
197
+ // Arm hard-deadline force-resolve in case child never closes (D-state).
198
+ // The "SIGKILL" here is a synthetic marker (the process may not have actually
199
+ // received it); force_resolved=true records that this was a deadline, not an exit.
200
+ forceResolveTimer = setTimeout(() => {
201
+ finalize(null, "SIGKILL", true);
202
+ }, effectiveForceResolveDelay);
203
+ forceResolveTimer.unref?.();
189
204
  }, timeoutMs);
190
205
  timer.unref?.();
191
- const finalize = (exitCode, signal) => {
206
+ const finalize = (exitCode, signal, forceResolved = false) => {
207
+ if (settled)
208
+ return;
209
+ settled = true;
192
210
  clearTimeout(timer);
211
+ clearTimeout(forceResolveTimer);
193
212
  const duration = Date.now() - started;
194
213
  const stdoutBuf = Buffer.concat(stdoutChunks);
195
214
  const stderrBuf = Buffer.concat(stderrChunks);
@@ -211,6 +230,7 @@ export function runExecSandbox(request, opts) {
211
230
  exit_code: exitCode,
212
231
  signal,
213
232
  timed_out: timedOut,
233
+ force_resolved: forceResolved,
214
234
  duration_ms: duration,
215
235
  stdout_bytes: stdoutBytes,
216
236
  stderr_bytes: stderrBytes,
@@ -254,6 +274,7 @@ function writeMeta(path, result, request, now) {
254
274
  exit_code: result.exit_code,
255
275
  signal: result.signal,
256
276
  timed_out: result.timed_out,
277
+ force_resolved: result.force_resolved,
257
278
  duration_ms: result.duration_ms,
258
279
  stdout_bytes: result.stdout_bytes,
259
280
  stderr_bytes: result.stderr_bytes,
@@ -261,7 +282,6 @@ function writeMeta(path, result, request, now) {
261
282
  stderr_truncated: result.stderr_truncated,
262
283
  stdout_path: result.stdout_path,
263
284
  stderr_path: result.stderr_path,
264
- ...(request.metadata ? { metadata: request.metadata } : {}),
265
285
  };
266
286
  writeFileSync(path, `${JSON.stringify(meta, null, 2)}\n`);
267
287
  }
@@ -576,38 +576,52 @@ export function parseTaskPlanIO(content) {
576
576
  outputFiles: extractPaths(outputSection),
577
577
  };
578
578
  }
579
+ /** Canonical list of recognised UAT types — uat-policy.ts re-exports this as UAT_TYPES. */
580
+ export const UAT_TYPE_KEYWORDS = [
581
+ 'artifact-driven',
582
+ 'browser-executable',
583
+ 'runtime-executable',
584
+ 'live-runtime',
585
+ 'mixed',
586
+ 'human-experience',
587
+ ];
588
+ /** Match a value against the recognised UAT type keywords (leading-keyword-only). */
589
+ function matchUatTypeKeyword(value) {
590
+ const normalized = value.trim().toLowerCase();
591
+ return UAT_TYPE_KEYWORDS.find(keyword => normalized.startsWith(keyword));
592
+ }
579
593
  /**
580
594
  * Extract the UAT type from a UAT file's raw content.
581
595
  *
582
596
  * UAT files have no YAML frontmatter - pass raw file content directly.
583
597
  * Classification is leading-keyword-only: e.g. `mixed (artifact-driven + live-runtime)` → `'mixed'`.
584
598
  *
599
+ * The canonical form is a `- UAT mode: <type>` bullet under `## UAT Type`
600
+ * (case-insensitive prefix, `**bold**` tolerated). When no such line exists,
601
+ * a line that itself starts with a recognised keyword — e.g. a bare
602
+ * `browser-executable` under the heading — is accepted, so agent-authored
603
+ * format drift does not silently fall back to artifact-driven.
604
+ *
585
605
  * Returns `undefined` when:
586
606
  * - the `## UAT Type` section is absent
587
- * - no `UAT mode:` bullet is found in the section
588
- * - the value does not start with a recognised keyword
607
+ * - a `UAT mode:` line exists but its value starts with no recognised keyword
608
+ * - no line in the section starts with `UAT mode:` or a recognised keyword
589
609
  */
590
610
  export function extractUatType(content) {
591
611
  const sectionText = extractSection(content, 'UAT Type');
592
612
  if (!sectionText)
593
613
  return undefined;
594
- const bullets = parseBullets(sectionText);
595
- const modeBullet = bullets.find(b => b.startsWith('UAT mode:'));
596
- if (!modeBullet)
597
- return undefined;
598
- const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
599
- if (rawValue.startsWith('artifact-driven'))
600
- return 'artifact-driven';
601
- if (rawValue.startsWith('browser-executable'))
602
- return 'browser-executable';
603
- if (rawValue.startsWith('runtime-executable'))
604
- return 'runtime-executable';
605
- if (rawValue.startsWith('live-runtime'))
606
- return 'live-runtime';
607
- if (rawValue.startsWith('human-experience'))
608
- return 'human-experience';
609
- if (rawValue.startsWith('mixed'))
610
- return 'mixed';
614
+ const lines = parseBullets(sectionText).map(line => line.replace(/\*\*/g, ''));
615
+ for (const line of lines) {
616
+ const declared = /^uat mode:\s*(.*)$/i.exec(line);
617
+ if (declared)
618
+ return matchUatTypeKeyword(declared[1]);
619
+ }
620
+ for (const line of lines) {
621
+ const matched = matchUatTypeKeyword(line);
622
+ if (matched)
623
+ return matched;
624
+ }
611
625
  return undefined;
612
626
  }
613
627
  /**
@@ -28,13 +28,17 @@ export function buildGsdHomeModel(state, closeout) {
28
28
  const workLabel = activeWorkLabel(state);
29
29
  const strandedQuick = closeout?.strandedQuick ?? null;
30
30
  const unmergedMilestone = closeout?.unmergedMilestones?.[0];
31
+ const idleResidueHint = closeout?.idleResidueHint ?? null;
32
+ const hasIdleResidue = Boolean(idleResidueHint);
31
33
  const nextReason = complete
32
34
  ? "all milestones are complete"
33
35
  : blocked
34
36
  ? "the active milestone is blocked"
35
- : !hasActiveWork
36
- ? "there is no active milestone"
37
- : "";
37
+ : hasIdleResidue
38
+ ? "milestone git residue needs recovery"
39
+ : !hasActiveWork
40
+ ? "there is no active milestone"
41
+ : "";
38
42
  const canAdvance = hasActiveWork && !blocked && !complete;
39
43
  const unmappedActive = complete ? countUnmappedActiveRequirements() : 0;
40
44
  const recommended = strandedQuick
@@ -43,11 +47,13 @@ export function buildGsdHomeModel(state, closeout) {
43
47
  ? "finish_milestone"
44
48
  : blocked
45
49
  ? "fix_recover"
46
- : canAdvance
47
- ? "continue_step"
48
- : complete && unmappedActive > 0
49
- ? "review_requirements_backlog"
50
- : "start_configure";
50
+ : hasIdleResidue
51
+ ? "fix_recover"
52
+ : canAdvance
53
+ ? "continue_step"
54
+ : complete && unmappedActive > 0
55
+ ? "review_requirements_backlog"
56
+ : "start_configure";
51
57
  const completionSummary = complete
52
58
  ? appendRequirementsBacklogToSummary(state, [
53
59
  `All milestones complete${state.lastCompletedMilestone ? ` after ${state.lastCompletedMilestone.id}: ${state.lastCompletedMilestone.title}` : ""}.`,
@@ -57,7 +63,9 @@ export function buildGsdHomeModel(state, closeout) {
57
63
  ? [`Quick task Q${strandedQuick.taskNum} finished on ${strandedQuick.quickBranch} but is not merged to ${strandedQuick.originalBranch}.`]
58
64
  : unmergedMilestone
59
65
  ? [`${unmergedMilestone.milestoneId} is complete but not merged into ${unmergedMilestone.integrationBranch}.`]
60
- : completionSummary;
66
+ : idleResidueHint
67
+ ? [idleResidueHint.message]
68
+ : completionSummary;
61
69
  return {
62
70
  title: "GSD — What now?",
63
71
  summary: [
@@ -130,10 +138,12 @@ export function buildGsdHomeModel(state, closeout) {
130
138
  label: "Fix or recover",
131
139
  description: blocked
132
140
  ? "Review the blocker and recovery commands for the active milestone."
133
- : disabled("This becomes active when closeout, validation, or state recovery is needed.", "no blocker is active"),
134
- enabled: blocked,
141
+ : hasIdleResidue
142
+ ? "Review stranded milestone worktrees/branches and run the suggested recovery command."
143
+ : disabled("This becomes active when closeout, validation, or state recovery is needed.", "no blocker is active"),
144
+ enabled: blocked || hasIdleResidue,
135
145
  recommended: recommended === "fix_recover",
136
- disabledReason: blocked ? undefined : "no blocker is active",
146
+ disabledReason: blocked || hasIdleResidue ? undefined : "no blocker is active",
137
147
  },
138
148
  {
139
149
  id: "start_configure",
@@ -169,7 +169,7 @@ export function insertArtifact(a) {
169
169
  export function insertMilestone(m) {
170
170
  if (!getDbOrNull())
171
171
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
172
- getDbOrNull().prepare(`INSERT OR IGNORE INTO milestones (
172
+ const result = getDbOrNull().prepare(`INSERT OR IGNORE INTO milestones (
173
173
  id, title, status, depends_on, created_at,
174
174
  vision, success_criteria, key_risks, proof_strategy,
175
175
  verification_contract, verification_integration, verification_operational, verification_uat,
@@ -199,6 +199,7 @@ export function insertMilestone(m) {
199
199
  ":requirement_coverage": m.planning?.requirementCoverage ?? "",
200
200
  ":boundary_map_markdown": m.planning?.boundaryMapMarkdown ?? "",
201
201
  });
202
+ return (result.changes ?? 0) > 0;
202
203
  }
203
204
  export function upsertMilestonePlanning(milestoneId, planning) {
204
205
  if (!getDbOrNull())
@@ -55,6 +55,66 @@ export function needsRemediationBlockerGuidance(milestoneId) {
55
55
  `3. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
56
56
  ].join("\n");
57
57
  }
58
+ // ─── Milestone closeout UAT sign-off blockers ───────────────────────────
59
+ // The first sentence is the blocker finding; the numbered steps are the
60
+ // resolution path. `verdict` is the recorded non-PASS verdict, or undefined
61
+ // when no verdict has been recorded at all.
62
+ export function uatSignoffBlockerGuidance(milestoneId, sliceId, verdict) {
63
+ const finding = verdict === undefined
64
+ ? `missing UAT PASS verdict for ${sliceId}`
65
+ : `UAT verdict for ${sliceId} is "${verdict}"`;
66
+ const sliceSpecificRerunStep = `If ${sliceId} is not the most recently completed slice, type a chat request to re-run UAT for ${sliceId}; run-uat will record the verdict through \`gsd_uat_result_save\`.`;
67
+ const steps = verdict === undefined
68
+ ? [
69
+ `1. Run UAT for the most recently completed slice to record a verdict: \`/gsd dispatch uat\``,
70
+ `2. ${sliceSpecificRerunStep}`,
71
+ `3. Review the UAT criteria and progress: \`/gsd status\``,
72
+ `4. After UAT records PASS, run \`/gsd auto\` to complete the milestone.`,
73
+ ]
74
+ : [
75
+ `1. Review the failing UAT findings: \`/gsd status\` (the ${sliceId} ASSESSMENT records what failed)`,
76
+ `2. Fix the issue, then re-run UAT for the most recently completed slice to record a fresh verdict: \`/gsd dispatch uat\``,
77
+ `3. ${sliceSpecificRerunStep}`,
78
+ `4. If the fix needs new implementation work, add remediation slices: \`/gsd dispatch reassess\``,
79
+ `5. After UAT records PASS, run \`/gsd auto\` to complete the milestone.`,
80
+ ];
81
+ return [
82
+ `Cannot complete milestone ${milestoneId}: ${finding}. Manual UAT sign-off (PASS) is required before milestone closure.`,
83
+ `Fix options:`,
84
+ ...steps,
85
+ ].join("\n");
86
+ }
87
+ // ─── Worktree isolation degradation ─────────────────────────────────────
88
+ // The first sentence of each notice is load-bearing for log matching and
89
+ // tests — keep it intact and append guidance after it.
90
+ function restoreIsolationHint(milestoneId) {
91
+ return `To restore isolation: close any processes using the old worktree, merge salvageable work with \`/gsd worktree merge ${milestoneId}\` or remove the stale worktree with \`/gsd worktree remove ${milestoneId}\`, then run \`/gsd doctor fix\`.`;
92
+ }
93
+ export function worktreeCreationFailedGuidance(milestoneId, error) {
94
+ return [
95
+ `Auto-worktree creation for ${milestoneId} failed: ${error}. Continuing in project root.`,
96
+ `Worktree isolation is degraded for this session.`,
97
+ restoreIsolationHint(milestoneId),
98
+ ].join("\n");
99
+ }
100
+ export function isolationDegradedFallbackGuidance(milestoneId) {
101
+ return [
102
+ `Worktree isolation is degraded. Fell back to branch milestone/${milestoneId}.`,
103
+ `Work continues safely on the milestone branch in the project root.`,
104
+ restoreIsolationHint(milestoneId),
105
+ ].join("\n");
106
+ }
107
+ /** Hard entry blockers from auto-start: bootstrap stopped, user must act. */
108
+ export function milestoneEntryBlockedGuidance(milestoneId, reason) {
109
+ const finding = reason === "creation-failed"
110
+ ? `worktree/branch creation failed. Isolation is degraded.`
111
+ : `isolation is degraded from a prior worktree failure.`;
112
+ return [
113
+ `Cannot enter milestone ${milestoneId}: ${finding}`,
114
+ restoreIsolationHint(milestoneId),
115
+ `Then run \`/gsd auto\` to retry.`,
116
+ ].join("\n");
117
+ }
58
118
  // ─── Crash recovery resume hints ────────────────────────────────────────
59
119
  /** Resume hint for an interrupted auto-mode unit, by unit class. */
60
120
  export function crashResumeHint(unitType, unitId) {