gsd-pi 2.80.0-dev.fbe7c8c6f → 2.81.0-dev.3cddbbba2

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 (814) hide show
  1. package/README.md +47 -59
  2. package/dist/claude-cli-check.d.ts +30 -0
  3. package/dist/claude-cli-check.js +18 -7
  4. package/dist/cli.js +0 -19
  5. package/dist/headless-query.d.ts +10 -0
  6. package/dist/headless-query.js +6 -4
  7. package/dist/loader-entrypoint.d.ts +8 -0
  8. package/dist/loader-entrypoint.js +27 -0
  9. package/dist/loader.js +2 -11
  10. package/dist/mcp-server.d.ts +1 -0
  11. package/dist/mcp-server.js +6 -3
  12. package/dist/resources/.managed-resources-content-hash +1 -1
  13. package/dist/resources/extensions/claude-code-cli/readiness.js +18 -7
  14. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +40 -3
  15. package/dist/resources/extensions/github-sync/sync.js +4 -1
  16. package/dist/resources/extensions/gsd/auto/contracts.js +2 -0
  17. package/dist/resources/extensions/gsd/auto/loop.js +214 -17
  18. package/dist/resources/extensions/gsd/auto/orchestrator.js +48 -4
  19. package/dist/resources/extensions/gsd/auto/phases.js +372 -134
  20. package/dist/resources/extensions/gsd/auto/resolve.js +29 -0
  21. package/dist/resources/extensions/gsd/auto/run-unit.js +88 -33
  22. package/dist/resources/extensions/gsd/auto/session.js +18 -1
  23. package/dist/resources/extensions/gsd/auto/unit-runner-events.js +7 -0
  24. package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +43 -0
  25. package/dist/resources/extensions/gsd/auto/workflow-dispatch-claim.js +33 -1
  26. package/dist/resources/extensions/gsd/auto/workflow-worker-heartbeat.js +9 -1
  27. package/dist/resources/extensions/gsd/auto-dashboard.js +199 -177
  28. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
  29. package/dist/resources/extensions/gsd/auto-dispatch.js +30 -11
  30. package/dist/resources/extensions/gsd/auto-post-unit.js +119 -79
  31. package/dist/resources/extensions/gsd/auto-prompts.js +103 -16
  32. package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
  33. package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -0
  34. package/dist/resources/extensions/gsd/auto-start.js +251 -16
  35. package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
  36. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
  37. package/dist/resources/extensions/gsd/auto-unit-closeout.js +33 -5
  38. package/dist/resources/extensions/gsd/auto-verification.js +12 -6
  39. package/dist/resources/extensions/gsd/auto-worktree.js +237 -336
  40. package/dist/resources/extensions/gsd/auto.js +493 -129
  41. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +133 -12
  42. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +44 -37
  43. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +37 -10
  44. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +30 -20
  45. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +4 -1
  46. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +6 -4
  47. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +5 -3
  48. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +1 -1
  49. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +337 -55
  50. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +4 -8
  51. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +4 -0
  52. package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
  53. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +19 -2
  54. package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
  55. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +4 -10
  56. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -2
  57. package/dist/resources/extensions/gsd/commands-config.js +1 -1
  58. package/dist/resources/extensions/gsd/commands-eval-review.js +2 -2
  59. package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
  60. package/dist/resources/extensions/gsd/context-budget.js +37 -2
  61. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  62. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -1
  63. package/dist/resources/extensions/gsd/db/unit-dispatches.js +92 -0
  64. package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
  65. package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
  66. package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
  67. package/dist/resources/extensions/gsd/git-service.js +75 -6
  68. package/dist/resources/extensions/gsd/gsd-db.js +46 -13
  69. package/dist/resources/extensions/gsd/guided-flow.js +119 -42
  70. package/dist/resources/extensions/gsd/health-widget-core.js +1 -1
  71. package/dist/resources/extensions/gsd/health-widget.js +6 -9
  72. package/dist/resources/extensions/gsd/init-wizard.js +4 -1
  73. package/dist/resources/extensions/gsd/memory-store.js +69 -12
  74. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  75. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  76. package/dist/resources/extensions/gsd/native-git-bridge.js +46 -22
  77. package/dist/resources/extensions/gsd/notification-overlay.js +35 -40
  78. package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
  79. package/dist/resources/extensions/gsd/parallel-merge.js +53 -30
  80. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +25 -33
  81. package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
  82. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  83. package/dist/resources/extensions/gsd/pre-execution-checks.js +22 -0
  84. package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
  85. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +22 -17
  86. package/dist/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  87. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
  88. package/dist/resources/extensions/gsd/prompts/discuss.md +20 -2
  89. package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
  90. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  91. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  92. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  93. package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  94. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  95. package/dist/resources/extensions/gsd/quick.js +34 -2
  96. package/dist/resources/extensions/gsd/recovery-classification.js +94 -0
  97. package/dist/resources/extensions/gsd/slice-cadence.js +45 -2
  98. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +15 -9
  99. package/dist/resources/extensions/gsd/state-reconciliation.js +27 -0
  100. package/dist/resources/extensions/gsd/tool-contract.js +50 -0
  101. package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -7
  102. package/dist/resources/extensions/gsd/tools/complete-task.js +1 -1
  103. package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
  104. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
  105. package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
  106. package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
  107. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  108. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  109. package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
  110. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
  111. package/dist/resources/extensions/gsd/tui/render-kit.js +74 -0
  112. package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
  113. package/dist/resources/extensions/gsd/unit-runtime.js +22 -0
  114. package/dist/resources/extensions/gsd/watch/header-renderer.js +92 -69
  115. package/dist/resources/extensions/gsd/watch/splash-palette.js +10 -0
  116. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  117. package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
  118. package/dist/resources/extensions/gsd/worktree-lifecycle.js +1364 -0
  119. package/dist/resources/extensions/gsd/worktree-safety.js +119 -0
  120. package/dist/resources/extensions/gsd/worktree-state-projection.js +317 -0
  121. package/dist/resources/extensions/gsd/worktree-telemetry.js +3 -1
  122. package/dist/resources/skills/web-quality-audit/scripts/analyze.sh +0 -0
  123. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  124. package/dist/web/standalone/.next/BUILD_ID +1 -1
  125. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  126. package/dist/web/standalone/.next/build-manifest.json +3 -3
  127. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  128. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  129. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  131. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  140. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  145. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  146. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  147. package/dist/web/standalone/.next/server/app/index.html +1 -1
  148. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  149. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  150. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  151. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  154. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  156. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/middleware.js +3 -3
  159. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  160. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  161. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  162. package/dist/web/standalone/.next/static/chunks/8359.e059d86b255fce1c.js +10 -0
  163. package/dist/web/standalone/.next/static/chunks/app/{page-fab3ebb85b006001.js → page-752f1e2ebdaa3e45.js} +1 -1
  164. package/dist/web/standalone/.next/static/chunks/{webpack-0481f1221120a7c6.js → webpack-de742b64187e13fe.js} +1 -1
  165. package/dist/welcome-screen.d.ts +2 -7
  166. package/dist/welcome-screen.js +68 -75
  167. package/package.json +3 -3
  168. package/packages/daemon/package.json +2 -2
  169. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  170. package/packages/mcp-server/dist/workflow-tools.js +22 -17
  171. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  172. package/packages/mcp-server/package.json +2 -2
  173. package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
  174. package/packages/mcp-server/src/workflow-tools.ts +30 -16
  175. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  176. package/packages/native/package.json +1 -1
  177. package/packages/native/tsconfig.tsbuildinfo +1 -1
  178. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  179. package/packages/pi-agent-core/dist/agent-loop.js +4 -1
  180. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  181. package/packages/pi-agent-core/dist/agent.d.ts +9 -2
  182. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  183. package/packages/pi-agent-core/dist/agent.js +43 -11
  184. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  185. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  186. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  187. package/packages/pi-agent-core/dist/index.js +2 -0
  188. package/packages/pi-agent-core/dist/index.js.map +1 -1
  189. package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
  190. package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
  191. package/packages/pi-agent-core/dist/token-audit.js +221 -0
  192. package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
  193. package/packages/pi-agent-core/dist/types.d.ts +31 -0
  194. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  195. package/packages/pi-agent-core/dist/types.js.map +1 -1
  196. package/packages/pi-agent-core/package.json +1 -1
  197. package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
  198. package/packages/pi-agent-core/src/agent-loop.ts +4 -1
  199. package/packages/pi-agent-core/src/agent.ts +52 -11
  200. package/packages/pi-agent-core/src/index.ts +2 -0
  201. package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
  202. package/packages/pi-agent-core/src/token-audit.ts +287 -0
  203. package/packages/pi-agent-core/src/types.ts +26 -10
  204. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  205. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +35 -13
  206. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  207. package/packages/pi-ai/dist/providers/anthropic-bearer-auth.test.js +21 -11
  208. package/packages/pi-ai/dist/providers/anthropic-bearer-auth.test.js.map +1 -1
  209. package/packages/pi-ai/dist/providers/anthropic.d.ts +7 -0
  210. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  211. package/packages/pi-ai/dist/providers/anthropic.js +9 -7
  212. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  213. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +23 -14
  214. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -1
  215. package/packages/pi-ai/dist/types.d.ts +2 -0
  216. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  217. package/packages/pi-ai/dist/types.js.map +1 -1
  218. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +48 -21
  219. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -1
  220. package/packages/pi-ai/dist/utils/oauth/google-antigravity.test.js +22 -21
  221. package/packages/pi-ai/dist/utils/oauth/google-antigravity.test.js.map +1 -1
  222. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.test.js +22 -21
  223. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.test.js.map +1 -1
  224. package/packages/pi-ai/package.json +1 -1
  225. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +39 -25
  226. package/packages/pi-ai/src/providers/anthropic-bearer-auth.test.ts +26 -22
  227. package/packages/pi-ai/src/providers/anthropic.ts +22 -9
  228. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +34 -21
  229. package/packages/pi-ai/src/types.ts +3 -0
  230. package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +56 -22
  231. package/packages/pi-ai/src/utils/oauth/google-antigravity.test.ts +24 -28
  232. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.test.ts +24 -28
  233. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  234. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +36 -1
  235. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  236. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +30 -1
  237. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  238. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +21 -2
  239. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  240. package/packages/pi-coding-agent/dist/core/agent-session.js +94 -16
  241. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  242. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +6 -2
  243. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  244. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  245. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  246. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  247. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  248. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  249. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  250. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  251. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  252. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  253. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  254. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  255. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  256. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  257. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  258. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  259. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  260. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  261. package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
  262. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  263. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  264. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  265. package/packages/pi-coding-agent/dist/core/extensions/runner.js +6 -6
  266. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  267. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +5 -3
  268. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  269. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +60 -4
  270. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  272. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +2 -0
  273. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
  274. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
  275. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
  276. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
  277. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
  278. package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
  279. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  280. package/packages/pi-coding-agent/dist/core/sdk.js +81 -4
  281. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  282. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  283. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  284. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  285. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  286. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
  287. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
  288. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
  289. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  290. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
  291. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  292. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +22 -56
  293. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -1
  294. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +1 -0
  295. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  296. package/packages/pi-coding-agent/dist/core/tools/bash.js +3 -1
  297. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  298. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts +2 -0
  299. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  300. package/packages/pi-coding-agent/dist/core/tools/edit.js +12 -1
  301. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  302. package/packages/pi-coding-agent/dist/core/tools/find.d.ts +2 -0
  303. package/packages/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
  304. package/packages/pi-coding-agent/dist/core/tools/find.js +14 -6
  305. package/packages/pi-coding-agent/dist/core/tools/find.js.map +1 -1
  306. package/packages/pi-coding-agent/dist/core/tools/grep.d.ts +2 -0
  307. package/packages/pi-coding-agent/dist/core/tools/grep.d.ts.map +1 -1
  308. package/packages/pi-coding-agent/dist/core/tools/grep.js +12 -3
  309. package/packages/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
  310. package/packages/pi-coding-agent/dist/core/tools/hashline-read.d.ts +2 -0
  311. package/packages/pi-coding-agent/dist/core/tools/hashline-read.d.ts.map +1 -1
  312. package/packages/pi-coding-agent/dist/core/tools/hashline-read.js +3 -1
  313. package/packages/pi-coding-agent/dist/core/tools/hashline-read.js.map +1 -1
  314. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -1
  315. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  316. package/packages/pi-coding-agent/dist/core/tools/index.js +1 -0
  317. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  318. package/packages/pi-coding-agent/dist/core/tools/ls.d.ts +2 -0
  319. package/packages/pi-coding-agent/dist/core/tools/ls.d.ts.map +1 -1
  320. package/packages/pi-coding-agent/dist/core/tools/ls.js +10 -3
  321. package/packages/pi-coding-agent/dist/core/tools/ls.js.map +1 -1
  322. package/packages/pi-coding-agent/dist/core/tools/read.d.ts +2 -0
  323. package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  324. package/packages/pi-coding-agent/dist/core/tools/read.js +3 -1
  325. package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  326. package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.js +7 -62
  327. package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.js.map +1 -1
  328. package/packages/pi-coding-agent/dist/core/tools/tool-target-metadata.test.d.ts +2 -0
  329. package/packages/pi-coding-agent/dist/core/tools/tool-target-metadata.test.d.ts.map +1 -0
  330. package/packages/pi-coding-agent/dist/core/tools/tool-target-metadata.test.js +115 -0
  331. package/packages/pi-coding-agent/dist/core/tools/tool-target-metadata.test.js.map +1 -0
  332. package/packages/pi-coding-agent/dist/core/tools/tool-target.d.ts +19 -0
  333. package/packages/pi-coding-agent/dist/core/tools/tool-target.d.ts.map +1 -0
  334. package/packages/pi-coding-agent/dist/core/tools/tool-target.js +20 -0
  335. package/packages/pi-coding-agent/dist/core/tools/tool-target.js.map +1 -0
  336. package/packages/pi-coding-agent/dist/core/tools/write.d.ts +4 -0
  337. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  338. package/packages/pi-coding-agent/dist/core/tools/write.js +9 -1
  339. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  340. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts +2 -0
  341. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts.map +1 -0
  342. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js +47 -0
  343. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js.map +1 -0
  344. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +161 -7
  345. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  346. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts +2 -0
  347. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts.map +1 -0
  348. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js +40 -0
  349. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js.map +1 -0
  350. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +0 -1
  351. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -1
  352. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +30 -29
  353. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  354. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +10 -3
  355. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -1
  356. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  357. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +13 -13
  358. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  359. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -3
  360. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  361. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +58 -3
  362. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  363. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +2 -2
  364. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  365. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +12 -6
  366. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  367. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  368. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -41
  369. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  370. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +25 -1
  371. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  372. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +6 -1
  373. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  374. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +225 -73
  375. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  376. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts +35 -0
  377. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts.map +1 -0
  378. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js +152 -0
  379. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js.map +1 -0
  380. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts +16 -0
  381. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts.map +1 -0
  382. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js +73 -0
  383. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js.map +1 -0
  384. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +1 -1
  385. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  386. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +12 -8
  387. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  388. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  389. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +31 -6
  390. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  391. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +71 -0
  392. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  393. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
  394. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  395. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +25 -3
  396. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  397. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  398. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  399. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  400. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  401. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  402. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +105 -1
  403. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  404. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  405. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +27 -26
  406. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  407. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +9 -6
  408. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  409. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
  410. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  411. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  412. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  413. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +28 -0
  414. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  415. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  416. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  417. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  418. package/packages/pi-coding-agent/package.json +1 -1
  419. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +40 -1
  420. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +40 -1
  421. package/packages/pi-coding-agent/src/core/agent-session.ts +102 -16
  422. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +6 -2
  423. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  424. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  425. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  426. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  427. package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
  428. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +5 -3
  429. package/packages/pi-coding-agent/src/core/extensions/runner.ts +8 -5
  430. package/packages/pi-coding-agent/src/core/extensions/types.ts +63 -2
  431. package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
  432. package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
  433. package/packages/pi-coding-agent/src/core/sdk.ts +92 -4
  434. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  435. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
  436. package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
  437. package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +22 -66
  438. package/packages/pi-coding-agent/src/core/tools/bash.ts +4 -1
  439. package/packages/pi-coding-agent/src/core/tools/edit.ts +13 -1
  440. package/packages/pi-coding-agent/src/core/tools/find.ts +15 -6
  441. package/packages/pi-coding-agent/src/core/tools/grep.ts +13 -3
  442. package/packages/pi-coding-agent/src/core/tools/hashline-read.ts +4 -1
  443. package/packages/pi-coding-agent/src/core/tools/index.ts +8 -0
  444. package/packages/pi-coding-agent/src/core/tools/ls.ts +11 -3
  445. package/packages/pi-coding-agent/src/core/tools/read.ts +4 -1
  446. package/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts +11 -72
  447. package/packages/pi-coding-agent/src/core/tools/tool-target-metadata.test.ts +127 -0
  448. package/packages/pi-coding-agent/src/core/tools/tool-target.ts +48 -0
  449. package/packages/pi-coding-agent/src/core/tools/write.ts +14 -2
  450. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/assistant-message-design.test.ts +56 -0
  451. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +228 -7
  452. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/user-message-design.test.ts +48 -0
  453. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +10 -3
  454. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +43 -42
  455. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +14 -14
  456. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +64 -3
  457. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +13 -7
  458. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +15 -42
  459. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +31 -1
  460. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +247 -94
  461. package/packages/pi-coding-agent/src/modes/interactive/components/transcript-design.ts +196 -0
  462. package/packages/pi-coding-agent/src/modes/interactive/components/tui-style-kit.ts +94 -0
  463. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +14 -9
  464. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +75 -0
  465. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +39 -8
  466. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +27 -3
  467. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-highlight.test.ts +23 -0
  468. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +106 -1
  469. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +27 -26
  470. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +9 -6
  471. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +1 -1
  472. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +31 -0
  473. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  474. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  475. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +14 -1
  476. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  477. package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
  478. package/packages/pi-tui/dist/overlay-layout.js +9 -6
  479. package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
  480. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  481. package/packages/pi-tui/dist/tui.js +18 -8
  482. package/packages/pi-tui/dist/tui.js.map +1 -1
  483. package/packages/pi-tui/package.json +1 -1
  484. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +20 -1
  485. package/packages/pi-tui/src/overlay-layout.ts +10 -7
  486. package/packages/pi-tui/src/tui.ts +20 -8
  487. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  488. package/packages/rpc-client/README.md +7 -0
  489. package/packages/rpc-client/package.json +1 -1
  490. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  491. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  492. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  493. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  494. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  495. package/pkg/dist/modes/interactive/theme/theme.js +105 -1
  496. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  497. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  498. package/pkg/dist/modes/interactive/theme/themes.js +27 -26
  499. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  500. package/pkg/package.json +1 -1
  501. package/src/resources/extensions/claude-code-cli/readiness.ts +25 -7
  502. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +42 -3
  503. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +67 -0
  504. package/src/resources/extensions/github-sync/sync.ts +8 -1
  505. package/src/resources/extensions/github-sync/tests/sync-source.test.ts +6 -18
  506. package/src/resources/extensions/gsd/auto/contracts.ts +19 -2
  507. package/src/resources/extensions/gsd/auto/loop-deps.ts +19 -16
  508. package/src/resources/extensions/gsd/auto/loop.ts +247 -25
  509. package/src/resources/extensions/gsd/auto/orchestrator.ts +52 -4
  510. package/src/resources/extensions/gsd/auto/phases.ts +512 -202
  511. package/src/resources/extensions/gsd/auto/resolve.ts +42 -1
  512. package/src/resources/extensions/gsd/auto/run-unit.ts +97 -34
  513. package/src/resources/extensions/gsd/auto/session.ts +19 -1
  514. package/src/resources/extensions/gsd/auto/types.ts +3 -0
  515. package/src/resources/extensions/gsd/auto/unit-runner-events.ts +15 -0
  516. package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +82 -0
  517. package/src/resources/extensions/gsd/auto/workflow-dispatch-claim.ts +63 -1
  518. package/src/resources/extensions/gsd/auto/workflow-worker-heartbeat.ts +14 -1
  519. package/src/resources/extensions/gsd/auto-dashboard.ts +249 -182
  520. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
  521. package/src/resources/extensions/gsd/auto-dispatch.ts +31 -1
  522. package/src/resources/extensions/gsd/auto-post-unit.ts +131 -81
  523. package/src/resources/extensions/gsd/auto-prompts.ts +112 -15
  524. package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
  525. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  526. package/src/resources/extensions/gsd/auto-start.ts +321 -21
  527. package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
  528. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
  529. package/src/resources/extensions/gsd/auto-unit-closeout.ts +51 -0
  530. package/src/resources/extensions/gsd/auto-verification.ts +12 -6
  531. package/src/resources/extensions/gsd/auto-worktree.ts +267 -360
  532. package/src/resources/extensions/gsd/auto.ts +578 -126
  533. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +166 -12
  534. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +45 -37
  535. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +36 -10
  536. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +32 -19
  537. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +5 -1
  538. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +7 -4
  539. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +6 -3
  540. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +1 -1
  541. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +384 -55
  542. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +5 -8
  543. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +4 -0
  544. package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
  545. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +19 -2
  546. package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
  547. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +4 -10
  548. package/src/resources/extensions/gsd/commands/handlers/ops.ts +4 -2
  549. package/src/resources/extensions/gsd/commands-config.ts +1 -1
  550. package/src/resources/extensions/gsd/commands-eval-review.ts +2 -2
  551. package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
  552. package/src/resources/extensions/gsd/context-budget.ts +44 -2
  553. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  554. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -1
  555. package/src/resources/extensions/gsd/db/unit-dispatches.ts +107 -0
  556. package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
  557. package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
  558. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
  559. package/src/resources/extensions/gsd/git-service.ts +89 -10
  560. package/src/resources/extensions/gsd/gsd-db.ts +50 -13
  561. package/src/resources/extensions/gsd/guided-flow.ts +148 -49
  562. package/src/resources/extensions/gsd/health-widget-core.ts +1 -1
  563. package/src/resources/extensions/gsd/health-widget.ts +8 -9
  564. package/src/resources/extensions/gsd/init-wizard.ts +5 -1
  565. package/src/resources/extensions/gsd/journal.ts +2 -0
  566. package/src/resources/extensions/gsd/memory-store.ts +77 -12
  567. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  568. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  569. package/src/resources/extensions/gsd/native-git-bridge.ts +53 -19
  570. package/src/resources/extensions/gsd/notification-overlay.ts +50 -46
  571. package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
  572. package/src/resources/extensions/gsd/parallel-merge.ts +61 -34
  573. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +33 -35
  574. package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
  575. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  576. package/src/resources/extensions/gsd/pre-execution-checks.ts +23 -0
  577. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  578. package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
  579. package/src/resources/extensions/gsd/prompts/complete-milestone.md +22 -17
  580. package/src/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  581. package/src/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
  582. package/src/resources/extensions/gsd/prompts/discuss.md +20 -2
  583. package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
  584. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  585. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  586. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  587. package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  588. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  589. package/src/resources/extensions/gsd/quick.ts +37 -2
  590. package/src/resources/extensions/gsd/recovery-classification.ts +122 -0
  591. package/src/resources/extensions/gsd/slice-cadence.ts +49 -2
  592. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +23 -9
  593. package/src/resources/extensions/gsd/state-reconciliation.ts +57 -0
  594. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +59 -89
  595. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +47 -172
  596. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +0 -35
  597. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +134 -9
  598. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1125 -215
  599. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +80 -59
  600. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +119 -2
  601. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +3 -47
  602. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +363 -18
  603. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +175 -11
  604. package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +54 -95
  605. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +67 -26
  606. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
  607. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +32 -30
  608. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +16 -1
  609. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +32 -128
  610. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +20 -54
  611. package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +20 -30
  612. package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +17 -29
  613. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +160 -0
  614. package/src/resources/extensions/gsd/tests/auto-start-time-persistence.test.ts +21 -39
  615. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +15 -24
  616. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +44 -29
  617. package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +68 -0
  618. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +39 -51
  619. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +159 -213
  620. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +15 -32
  621. package/src/resources/extensions/gsd/tests/browser-teardown.test.ts +0 -41
  622. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
  623. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +34 -27
  624. package/src/resources/extensions/gsd/tests/cmux.test.ts +51 -53
  625. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +39 -61
  626. package/src/resources/extensions/gsd/tests/commands-config.test.ts +26 -19
  627. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
  628. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
  629. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  630. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
  631. package/src/resources/extensions/gsd/tests/complete-task-normalize-lists.test.ts +29 -33
  632. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +45 -108
  633. package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
  634. package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
  635. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +90 -31
  636. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  637. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +46 -11
  638. package/src/resources/extensions/gsd/tests/cwd-fallback-hardening.test.ts +138 -0
  639. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +4 -68
  640. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +24 -11
  641. package/src/resources/extensions/gsd/tests/deferred-milestone-dir-4996.test.ts +14 -65
  642. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +44 -37
  643. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +100 -38
  644. package/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts +25 -15
  645. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
  646. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +35 -17
  647. package/src/resources/extensions/gsd/tests/error-success-mask.test.ts +16 -21
  648. package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +15 -82
  649. package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
  650. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
  651. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
  652. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
  653. package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +151 -0
  654. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +2 -20
  655. package/src/resources/extensions/gsd/tests/frontmatter-parse-noise.test.ts +18 -26
  656. package/src/resources/extensions/gsd/tests/header-renderer.test.ts +40 -0
  657. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +10 -0
  658. package/src/resources/extensions/gsd/tests/health-widget.test.ts +14 -4
  659. package/src/resources/extensions/gsd/tests/init-skip-git.test.ts +9 -12
  660. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +40 -0
  661. package/src/resources/extensions/gsd/tests/integration/commands-eval-review.integration.test.ts +4 -2
  662. package/src/resources/extensions/gsd/tests/integration/git-locale.test.ts +31 -20
  663. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +92 -0
  664. package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +0 -47
  665. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +116 -24
  666. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +60 -202
  667. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +13 -56
  668. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +248 -11
  669. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
  670. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
  671. package/src/resources/extensions/gsd/tests/lazy-pi-tui-import.test.ts +44 -6
  672. package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
  673. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +21 -35
  674. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +77 -12
  675. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  676. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  677. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +267 -0
  678. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +88 -98
  679. package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +70 -278
  680. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
  681. package/src/resources/extensions/gsd/tests/needs-remediation-revalidation.test.ts +37 -30
  682. package/src/resources/extensions/gsd/tests/note-captures-executed.test.ts +32 -28
  683. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +78 -41
  684. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +44 -0
  685. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +12 -182
  686. package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +144 -0
  687. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
  688. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +38 -6
  689. package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
  690. package/src/resources/extensions/gsd/tests/phantom-ghost-detection.test.ts +24 -37
  691. package/src/resources/extensions/gsd/tests/phantom-milestone-default-queued.test.ts +9 -24
  692. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +95 -75
  693. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  694. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  695. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +2 -2
  696. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +36 -22
  697. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +36 -30
  698. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +45 -5
  699. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +74 -4
  700. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +20 -22
  701. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +130 -32
  702. package/src/resources/extensions/gsd/tests/project-root-cwd-crash.test.ts +18 -36
  703. package/src/resources/extensions/gsd/tests/projection-no-plan-overwrite.test.ts +35 -73
  704. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +76 -138
  705. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +24 -1
  706. package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
  707. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  708. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +70 -106
  709. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +59 -161
  710. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +33 -29
  711. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +22 -196
  712. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +23 -93
  713. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  714. package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +50 -79
  715. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +27 -13
  716. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +151 -251
  717. package/src/resources/extensions/gsd/tests/resource-loader-import-path.test.ts +41 -29
  718. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +58 -69
  719. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +36 -164
  720. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +57 -41
  721. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +91 -0
  722. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
  723. package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
  724. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +14 -9
  725. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +77 -0
  726. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +222 -0
  727. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +44 -42
  728. package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
  729. package/src/resources/extensions/gsd/tests/skip-slice-state-rebuild.test.ts +56 -24
  730. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +51 -11
  731. package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +23 -0
  732. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +66 -50
  733. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +68 -107
  734. package/src/resources/extensions/gsd/tests/slice-sequence-insert.test.ts +115 -42
  735. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +21 -77
  736. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +25 -116
  737. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +21 -57
  738. package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +29 -76
  739. package/src/resources/extensions/gsd/tests/stale-lockfile-recovery.test.ts +33 -24
  740. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +39 -30
  741. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
  742. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
  743. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +65 -56
  744. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +35 -40
  745. package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +48 -46
  746. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +14 -102
  747. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +78 -232
  748. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +32 -35
  749. package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
  750. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
  751. package/src/resources/extensions/gsd/tests/token-profile.test.ts +84 -309
  752. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +295 -0
  753. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +134 -341
  754. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +330 -0
  755. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +66 -0
  756. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
  757. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +8 -25
  758. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +80 -1
  759. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +37 -0
  760. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -99
  761. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +43 -36
  762. package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +83 -0
  763. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -444
  764. package/src/resources/extensions/gsd/tests/workflow-dispatch-claim.test.ts +142 -0
  765. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +44 -189
  766. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +8 -57
  767. package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
  768. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
  769. package/src/resources/extensions/gsd/tests/workflow-worker-heartbeat.test.ts +32 -1
  770. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +21 -44
  771. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +27 -26
  772. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +14 -13
  773. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +197 -78
  774. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +888 -0
  775. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +20 -18
  776. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
  777. package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
  778. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +327 -0
  779. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +120 -0
  780. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +59 -2
  781. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +18 -0
  782. package/src/resources/extensions/gsd/tests/write-gate.test.ts +40 -1
  783. package/src/resources/extensions/gsd/tests/zero-slice-roadmap-guided.test.ts +19 -13
  784. package/src/resources/extensions/gsd/tool-contract.ts +82 -0
  785. package/src/resources/extensions/gsd/tools/complete-milestone.ts +14 -15
  786. package/src/resources/extensions/gsd/tools/complete-task.ts +1 -1
  787. package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
  788. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
  789. package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
  790. package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
  791. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  792. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  793. package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
  794. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
  795. package/src/resources/extensions/gsd/tui/render-kit.ts +109 -0
  796. package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
  797. package/src/resources/extensions/gsd/unit-runtime.ts +25 -0
  798. package/src/resources/extensions/gsd/watch/header-renderer.ts +121 -79
  799. package/src/resources/extensions/gsd/watch/splash-palette.ts +11 -0
  800. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  801. package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
  802. package/src/resources/extensions/gsd/worktree-lifecycle.ts +1882 -0
  803. package/src/resources/extensions/gsd/worktree-safety.ts +282 -0
  804. package/src/resources/extensions/gsd/worktree-state-projection.ts +404 -0
  805. package/src/resources/extensions/gsd/worktree-telemetry.ts +7 -2
  806. package/src/resources/skills/create-gsd-extension/templates/templates.test.ts +86 -40
  807. package/src/resources/skills/web-quality-audit/scripts/analyze.sh +0 -0
  808. package/dist/resources/extensions/gsd/worktree-resolver.js +0 -733
  809. package/dist/web/standalone/.next/static/chunks/8336.631939fb583761fa.js +0 -10
  810. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +0 -434
  811. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +0 -1247
  812. package/src/resources/extensions/gsd/worktree-resolver.ts +0 -909
  813. /package/dist/web/standalone/.next/static/{yTuahMMuJzVnsov5PreWl → F5x9E6H9k_52fjqyql93y}/_buildManifest.js +0 -0
  814. /package/dist/web/standalone/.next/static/{yTuahMMuJzVnsov5PreWl → F5x9E6H9k_52fjqyql93y}/_ssgManifest.js +0 -0
@@ -1,6 +1,9 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Auto-loop execution, dispatch, recovery, and cancellation regression tests.
3
+
1
4
  import test, { mock } from "node:test";
2
5
  import assert from "node:assert/strict";
3
- import { mkdtempSync } from "node:fs";
6
+ import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
4
7
  import { tmpdir } from "node:os";
5
8
  import { join } from "node:path";
6
9
 
@@ -10,13 +13,21 @@ import {
10
13
  _resetPendingResolve,
11
14
  _hasPendingResolveForTest,
12
15
  _setActiveSession,
16
+ _setSessionSwitchInFlight,
17
+ _markSessionSwitchAbortGraceWindow,
18
+ _clearSessionSwitchAbortGraceWindow,
19
+ _consumePendingSwitchCancellation,
13
20
  isSessionSwitchInFlight,
21
+ isSessionSwitchAbortGraceActive,
14
22
  } from "../auto/resolve.js";
15
- import { runUnit } from "../auto/run-unit.js";
23
+ import { runUnit, shouldDeferUnitFailsafeTimeout } from "../auto/run-unit.js";
24
+ import { writeUnitRuntimeRecord, readUnitRuntimeRecord } from "../unit-runtime.js";
16
25
  import { autoLoop } from "../auto/loop.js";
26
+ import { runDispatch, runUnitPhase } from "../auto/phases.js";
17
27
  import { detectStuck } from "../auto/detect-stuck.js";
18
28
  import type { UnitResult, AgentEndEvent } from "../auto/types.js";
19
29
  import type { LoopDeps } from "../auto/loop-deps.js";
30
+ import { WorktreeStateProjection } from "../worktree-state-projection.js";
20
31
  import { ModelPolicyDispatchBlockedError } from "../auto-model-selection.js";
21
32
  import type { SessionLockStatus } from "../session-lock.js";
22
33
 
@@ -64,7 +75,7 @@ function makeMockSession(opts?: {
64
75
  verbose: false,
65
76
  basePath: process.cwd(),
66
77
  cmdCtx: {
67
- newSession: (options?: { abortSignal?: AbortSignal }) => {
78
+ newSession: (options?: { abortSignal?: AbortSignal; workspaceRoot?: string }) => {
68
79
  opts?.onNewSessionStart?.(session);
69
80
  if (opts?.newSessionThrows) {
70
81
  return Promise.reject(new Error(opts.newSessionThrows));
@@ -76,7 +87,7 @@ function makeMockSession(opts?: {
76
87
  setTimeout(() => {
77
88
  // Simulate AgentSession.newSession() checking abortSignal after
78
89
  // its internal async work (abort()) completes — this is where the
79
- // real code captures process.cwd() and rebuilds the tool runtime.
90
+ // real code selects a workspace root and rebuilds the tool runtime.
80
91
  // If the signal is aborted, the real code discards the session.
81
92
  opts?.onSignalCheck?.(options?.abortSignal?.aborted ?? false);
82
93
  opts?.onNewSessionSettle?.(session);
@@ -155,6 +166,109 @@ test("resolveAgentEnd resolves a pending runUnit promise", async () => {
155
166
  assert.deepEqual(result.event, event);
156
167
  });
157
168
 
169
+ test("runUnit failsafe defers cancellation while timeout recovery is making fresh progress", async () => {
170
+ _resetPendingResolve();
171
+ mock.timers.enable();
172
+ const originalCwd = process.cwd();
173
+
174
+ try {
175
+ mock.timers.setTime(10_000);
176
+ const ctx = makeMockCtx();
177
+ const pi = makeMockPi();
178
+ const s = makeMockSession();
179
+ s.basePath = mkdtempSync(join(tmpdir(), "gsd-rununit-recovery-"));
180
+ s.currentUnit = { type: "task", id: "T01", startedAt: 1234 };
181
+
182
+ const resultPromise = runUnit(ctx, pi, s, "task", "T01", "prompt");
183
+ await waitForMicrotasks(() => pi.calls.length === 1, "unit dispatch");
184
+
185
+ writeUnitRuntimeRecord(s.basePath, "task", "T01", 1234, {
186
+ phase: "recovered",
187
+ recoveryAttempts: 1,
188
+ lastProgressKind: "hard-recovery-retry",
189
+ lastProgressAt: Date.now(),
190
+ });
191
+ assert.equal(
192
+ shouldDeferUnitFailsafeTimeout(readUnitRuntimeRecord(s.basePath, "task", "T01"), {
193
+ nowMs: Date.now(),
194
+ currentUnitStartedAt: s.currentUnit.startedAt,
195
+ freshProgressMs: 30_000,
196
+ }),
197
+ true,
198
+ "fresh recovery runtime should defer the failsafe",
199
+ );
200
+
201
+ setTimeout(() => {
202
+ writeUnitRuntimeRecord(s.basePath, "task", "T01", 1234, {
203
+ phase: "recovered",
204
+ recoveryAttempts: 1,
205
+ lastProgressKind: "hard-recovery-retry",
206
+ lastProgressAt: Date.now(),
207
+ });
208
+ }, (30 * 60 * 1000) + 29_000);
209
+
210
+ mock.timers.tick((30 * 60 * 1000) + 31_000);
211
+ await Promise.resolve();
212
+
213
+ resolveAgentEnd(makeEvent());
214
+ const result = await resultPromise;
215
+ assert.equal(result.status, "completed");
216
+ } finally {
217
+ mock.timers.reset();
218
+ process.chdir(originalCwd);
219
+ }
220
+ });
221
+
222
+ test("shouldDeferUnitFailsafeTimeout rejects stale runtime progress", () => {
223
+ assert.equal(
224
+ shouldDeferUnitFailsafeTimeout({
225
+ version: 1,
226
+ unitType: "task",
227
+ unitId: "T01",
228
+ startedAt: 1234,
229
+ updatedAt: 1,
230
+ phase: "recovered",
231
+ wrapupWarningSent: false,
232
+ continueHereFired: false,
233
+ timeoutAt: 1,
234
+ lastProgressAt: 1,
235
+ progressCount: 1,
236
+ lastProgressKind: "hard-recovery-retry",
237
+ recoveryAttempts: 1,
238
+ }, {
239
+ nowMs: 120_000,
240
+ currentUnitStartedAt: 1234,
241
+ freshProgressMs: 30_000,
242
+ }),
243
+ false,
244
+ );
245
+ });
246
+
247
+ test("shouldDeferUnitFailsafeTimeout rejects future runtime progress", () => {
248
+ assert.equal(
249
+ shouldDeferUnitFailsafeTimeout({
250
+ version: 1,
251
+ unitType: "task",
252
+ unitId: "T01",
253
+ startedAt: 1234,
254
+ updatedAt: 1,
255
+ phase: "recovered",
256
+ wrapupWarningSent: false,
257
+ continueHereFired: false,
258
+ timeoutAt: 1,
259
+ lastProgressAt: 150_000,
260
+ progressCount: 1,
261
+ lastProgressKind: "hard-recovery-retry",
262
+ recoveryAttempts: 1,
263
+ }, {
264
+ nowMs: 120_000,
265
+ currentUnitStartedAt: 1234,
266
+ freshProgressMs: 30_000,
267
+ }),
268
+ false,
269
+ );
270
+ });
271
+
158
272
  test("resolveAgentEnd drops event when no promise is pending", () => {
159
273
  _resetPendingResolve();
160
274
 
@@ -206,6 +320,28 @@ test("runUnit returns cancelled when session creation fails", async () => {
206
320
  assert.equal(pi.calls.length, 0);
207
321
  });
208
322
 
323
+ test("runUnit clears queued switch cancellation when session creation fails", async () => {
324
+ _resetPendingResolve();
325
+
326
+ const ctx = makeMockCtx();
327
+ const pi = makeMockPi();
328
+ const s = makeMockSession({
329
+ newSessionThrows: "connection refused",
330
+ onNewSessionStart: () => {
331
+ resolveAgentEndCancelled({
332
+ message: "Claude Code process aborted by user",
333
+ category: "aborted",
334
+ isTransient: false,
335
+ });
336
+ },
337
+ });
338
+
339
+ const result = await runUnit(ctx, pi, s, "task", "T01", "prompt");
340
+
341
+ assert.equal(result.status, "cancelled");
342
+ assert.equal(_consumePendingSwitchCancellation(), null);
343
+ });
344
+
209
345
  test("runUnit returns cancelled when session creation times out", async () => {
210
346
  _resetPendingResolve();
211
347
 
@@ -221,6 +357,34 @@ test("runUnit returns cancelled when session creation times out", async () => {
221
357
  assert.equal(pi.calls.length, 0);
222
358
  });
223
359
 
360
+ test("runUnit consumes a cancellation queued during session switch before dispatch", async () => {
361
+ _resetPendingResolve();
362
+
363
+ const ctx = makeMockCtx();
364
+ const pi = makeMockPi();
365
+ let cancellationQueued = false;
366
+ const s = makeMockSession({
367
+ newSessionDelayMs: 10,
368
+ onNewSessionStart: () => {
369
+ setTimeout(() => {
370
+ cancellationQueued = !resolveAgentEndCancelled({
371
+ message: "Claude Code process aborted by user",
372
+ category: "aborted",
373
+ isTransient: false,
374
+ });
375
+ }, 0);
376
+ },
377
+ });
378
+
379
+ const result = await runUnit(ctx, pi, s, "plan-slice", "M009/S01", "prompt");
380
+
381
+ assert.equal(cancellationQueued, true);
382
+ assert.equal(result.status, "cancelled");
383
+ assert.equal(result.errorContext?.category, "aborted");
384
+ assert.equal(result.errorContext?.message, "Claude Code process aborted by user");
385
+ assert.equal(pi.calls.length, 0, "queued switch cancellation must prevent prompt dispatch");
386
+ });
387
+
224
388
  test("runUnit keeps the session-switch guard across a late newSession settlement", async () => {
225
389
  _resetPendingResolve();
226
390
  mock.timers.enable();
@@ -496,10 +660,9 @@ test("runUnit proceeds when isProviderRequestReady throws (defensive) (#4555)",
496
660
  assert.equal(pi.calls.length, 0);
497
661
  });
498
662
 
499
- test("late-resolving newSession() after timeout receives aborted signal so tool runtime is not configured with root cwd (#3731)", async () => {
500
- // When newSession() times out in runUnit(), auto-mode restores cwd to project
501
- // root. If newSession() later resolves, it must NOT use process.cwd() to
502
- // configure the tool runtime (which would give it root cwd, not worktree cwd).
663
+ test("late-resolving newSession() after timeout receives aborted signal so tool runtime is not configured with stale workspace root (#3731)", async () => {
664
+ // When newSession() times out in runUnit(), a late resolution must not
665
+ // configure the tool runtime against a stale workspace root.
503
666
  //
504
667
  // The fix: runUnit creates an AbortController, aborts it on timeout, and passes
505
668
  // the signal to newSession(). AgentSession.newSession() checks the signal after
@@ -514,8 +677,8 @@ test("late-resolving newSession() after timeout receives aborted signal so tool
514
677
 
515
678
  // newSession mock simulates AgentSession.newSession() behavior:
516
679
  // after an internal delay (representing await this.abort()), it checks the
517
- // abortSignal that's where the real code would capture process.cwd() and
518
- // call _buildRuntime. If aborted, the real code must discard the session.
680
+ // abortSignal before selecting the workspace root and calling _buildRuntime.
681
+ // If aborted, the real code must discard the session.
519
682
  const s = makeMockSession({
520
683
  newSessionDelayMs: 200_000, // longer than NEW_SESSION_TIMEOUT_MS (120s)
521
684
  onSignalCheck: (aborted) => {
@@ -548,7 +711,7 @@ test("late-resolving newSession() after timeout receives aborted signal so tool
548
711
  abortedWhenLateSessionSettled,
549
712
  true,
550
713
  "runUnit must pass an aborted AbortSignal to newSession() when it resolves after the session-creation timeout (#3731). " +
551
- "Without this, AgentSession.newSession() captures root process.cwd() and rebuilds the tool runtime with wrong cwd.",
714
+ "Without this, AgentSession.newSession() can rebuild the tool runtime with a stale workspace root.",
552
715
  );
553
716
  } finally {
554
717
  mock.timers.reset();
@@ -613,7 +776,6 @@ function makeMockDeps(
613
776
  preferences: { uok: { plan_v2: { enabled: false } } },
614
777
  }),
615
778
  preDispatchHealthGate: async () => ({ proceed: true, fixesApplied: [] }),
616
- syncProjectRootToWorktree: () => {},
617
779
  checkResourcesStale: () => null,
618
780
  validateSessionLock: () => ({ valid: true } as SessionLockStatus),
619
781
  updateSessionLock: () => {
@@ -627,7 +789,6 @@ function makeMockDeps(
627
789
  pruneQueueOrder: () => {},
628
790
  isInAutoWorktree: () => false,
629
791
  shouldUseWorktreeIsolation: () => false,
630
- mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
631
792
  teardownAutoWorktree: () => {},
632
793
  createAutoWorktree: () => "/tmp/wt",
633
794
  captureIntegrationBranch: () => {},
@@ -637,7 +798,11 @@ function makeMockDeps(
637
798
  resolveMilestoneFile: () => null,
638
799
  reconcileMergeState: () => "clean",
639
800
  preflightCleanRoot: () => ({ stashPushed: false, summary: "" }),
640
- postflightPopStash: () => {},
801
+ postflightPopStash: () => ({
802
+ restored: true,
803
+ needsManualRecovery: false,
804
+ message: "restored",
805
+ }),
641
806
  getLedger: () => null,
642
807
  getProjectTotals: () => ({ cost: 0 }),
643
808
  formatCost: (c: number) => `$${c.toFixed(2)}`,
@@ -673,21 +838,15 @@ function makeMockDeps(
673
838
  readFileSync: () => "",
674
839
  atomicWriteSync: () => {},
675
840
  GitServiceImpl: class {} as any,
676
- resolver: {
677
- get workPath() {
678
- return "/tmp/project";
679
- },
680
- get projectRoot() {
681
- return "/tmp/project";
682
- },
683
- get lockPath() {
684
- return "/tmp/project";
685
- },
686
- enterMilestone: () => {},
687
- exitMilestone: () => {},
688
- mergeAndExit: () => {},
689
- mergeAndEnterNext: () => {},
841
+ lifecycle: {
842
+ enterMilestone: () => ({ ok: true, mode: "worktree", path: "/tmp/project" }),
843
+ exitMilestone: (_mid: string, opts: { merge: boolean }) => ({
844
+ ok: true,
845
+ merged: opts.merge,
846
+ codeFilesChanged: false,
847
+ }),
690
848
  } as any,
849
+ worktreeProjection: new WorktreeStateProjection(),
691
850
  postUnitPreVerification: async () => {
692
851
  callLog.push("postUnitPreVerification");
693
852
  return "continue" as const;
@@ -732,6 +891,7 @@ function makeLoopSession(overrides?: Partial<Record<string, unknown>>) {
732
891
  lastBudgetAlertLevel: 0,
733
892
  pendingVerificationRetry: null,
734
893
  pendingCrashRecovery: null,
894
+ verificationRetryFailureHashes: new Map<string, string>(),
735
895
  pendingQuickTasks: [],
736
896
  sidecarQueue: [],
737
897
  autoModeStartModel: null,
@@ -805,6 +965,139 @@ test("autoLoop exits on terminal complete state", async (t) => {
805
965
  );
806
966
  });
807
967
 
968
+ test("autoLoop stops before success notification when postflight stash restore needs recovery", async () => {
969
+ _resetPendingResolve();
970
+
971
+ const notifications: Array<{ msg: string; level: string }> = [];
972
+ const ctx = makeMockCtx();
973
+ ctx.ui.setStatus = () => {};
974
+ ctx.ui.notify = (msg: string, level: string) => {
975
+ notifications.push({ msg, level });
976
+ };
977
+ const pi = makeMockPi();
978
+ const s = makeLoopSession();
979
+ let stopReason = "";
980
+
981
+ const deps = makeMockDeps({
982
+ deriveState: async () => {
983
+ deps.callLog.push("deriveState");
984
+ return {
985
+ phase: "complete",
986
+ activeMilestone: { id: "M001", title: "Test", status: "complete" },
987
+ activeSlice: null,
988
+ activeTask: null,
989
+ registry: [{ id: "M001", status: "complete" }],
990
+ blockers: [],
991
+ } as any;
992
+ },
993
+ preflightCleanRoot: () => ({
994
+ stashPushed: true,
995
+ stashMarker: "gsd-preflight-stash:M001:test",
996
+ summary: "stashed",
997
+ }),
998
+ postflightPopStash: () => ({
999
+ restored: false,
1000
+ needsManualRecovery: true,
1001
+ message: "git stash pop stash@{0} failed after merge of milestone M001",
1002
+ stashRef: "stash@{0}",
1003
+ }),
1004
+ sendDesktopNotification: () => {
1005
+ deps.callLog.push("sendDesktopNotification");
1006
+ },
1007
+ logCmuxEvent: () => {
1008
+ deps.callLog.push("logCmuxEvent");
1009
+ },
1010
+ stopAuto: async (_ctx, _pi, reason) => {
1011
+ deps.callLog.push("stopAuto");
1012
+ stopReason = reason ?? "";
1013
+ },
1014
+ });
1015
+
1016
+ await autoLoop(ctx, pi, s, deps);
1017
+
1018
+ assert.equal(stopReason, "Post-merge stash restore failed for milestone M001");
1019
+ assert.ok(
1020
+ notifications.some(
1021
+ (n) => n.level === "error" && n.msg.includes("Post-merge stash restore failed for milestone M001"),
1022
+ ),
1023
+ "failed postflight restore must be surfaced as an error",
1024
+ );
1025
+ assert.ok(
1026
+ !deps.callLog.includes("sendDesktopNotification"),
1027
+ "must not emit milestone success desktop notification after stash restore failure",
1028
+ );
1029
+ assert.ok(
1030
+ !deps.callLog.includes("logCmuxEvent"),
1031
+ "must not emit milestone success cmux event after stash restore failure",
1032
+ );
1033
+ });
1034
+
1035
+ test("autoLoop marks transition merge complete before postflight recovery stop", async () => {
1036
+ _resetPendingResolve();
1037
+
1038
+ const ctx = makeMockCtx();
1039
+ ctx.ui.setStatus = () => {};
1040
+ ctx.ui.notify = () => {};
1041
+ const pi = makeMockPi();
1042
+ const s = makeLoopSession();
1043
+ let mergeCalls = 0;
1044
+ let stopReason = "";
1045
+
1046
+ const deps = makeMockDeps({
1047
+ deriveState: async () => {
1048
+ deps.callLog.push("deriveState");
1049
+ return {
1050
+ phase: "executing",
1051
+ activeMilestone: { id: "M002", title: "Next", status: "active" },
1052
+ activeSlice: null,
1053
+ activeTask: null,
1054
+ registry: [
1055
+ { id: "M001", title: "Done", status: "complete" },
1056
+ { id: "M002", title: "Next", status: "active" },
1057
+ ],
1058
+ blockers: [],
1059
+ } as any;
1060
+ },
1061
+ preflightCleanRoot: () => ({
1062
+ stashPushed: true,
1063
+ stashMarker: "gsd-preflight-stash:M001:test",
1064
+ summary: "stashed",
1065
+ }),
1066
+ postflightPopStash: () => ({
1067
+ restored: false,
1068
+ needsManualRecovery: true,
1069
+ message: "git stash pop stash@{0} failed after merge of milestone M001",
1070
+ stashRef: "stash@{0}",
1071
+ }),
1072
+ lifecycle: {
1073
+ enterMilestone: () => {
1074
+ assert.fail("must not enter the next milestone after postflight recovery fails");
1075
+ },
1076
+ exitMilestone: (_mid: string, opts: { merge: boolean }) => {
1077
+ if (opts.merge) mergeCalls += 1;
1078
+ return { ok: true, merged: opts.merge, codeFilesChanged: false };
1079
+ },
1080
+ } as any,
1081
+ stopAuto: async (_ctx, _pi, reason) => {
1082
+ deps.callLog.push("stopAuto");
1083
+ stopReason = reason ?? "";
1084
+ if (!s.milestoneMergedInPhases) {
1085
+ deps.lifecycle.exitMilestone(
1086
+ "M001",
1087
+ { merge: true },
1088
+ { notify: ctx.ui.notify.bind(ctx.ui) },
1089
+ );
1090
+ }
1091
+ },
1092
+ });
1093
+
1094
+ await autoLoop(ctx, pi, s, deps);
1095
+
1096
+ assert.equal(stopReason, "Post-merge stash restore failed for milestone M001");
1097
+ assert.equal(s.milestoneMergedInPhases, true);
1098
+ assert.equal(mergeCalls, 1, "postflight recovery stop must not re-run an already completed transition merge");
1099
+ });
1100
+
808
1101
  test("autoLoop pauses when provider readiness cancels before dispatch", async () => {
809
1102
  _resetPendingResolve();
810
1103
 
@@ -966,7 +1259,7 @@ test("autoLoop dequeues sidecar item before session-lock break (mid-session, #53
966
1259
  deps.callLog.push("postUnitPostVerification");
967
1260
  s.sidecarQueue.push({
968
1261
  kind: "hook" as const,
969
- unitType: "hook/review",
1262
+ unitType: "run-uat",
970
1263
  unitId: "M001/S01/T01/review",
971
1264
  prompt: "review the code",
972
1265
  });
@@ -1108,6 +1401,107 @@ test("autoLoop calls deriveState → resolveDispatch → runUnit in sequence", a
1108
1401
  );
1109
1402
  });
1110
1403
 
1404
+ test("autoLoop journals post-unit finalize stop after completed unit", async () => {
1405
+ _resetPendingResolve();
1406
+
1407
+ const ctx = makeMockCtx();
1408
+ ctx.ui.setStatus = () => {};
1409
+ ctx.sessionManager = { getSessionFile: () => "/tmp/session.json" };
1410
+ const pi = makeMockPi();
1411
+ const s = makeLoopSession();
1412
+ const journalEvents: Array<{ eventType: string; data?: any }> = [];
1413
+
1414
+ const deps = makeMockDeps({
1415
+ postUnitPreVerification: async () => {
1416
+ deps.callLog.push("postUnitPreVerification");
1417
+ s.lastGitActionFailure = "commit failed";
1418
+ return "dispatched" as const;
1419
+ },
1420
+ emitJournalEvent: (entry: any) => {
1421
+ journalEvents.push(entry);
1422
+ },
1423
+ });
1424
+
1425
+ const loopPromise = autoLoop(ctx, pi, s, deps);
1426
+ await new Promise((r) => setTimeout(r, 50));
1427
+ resolveAgentEnd(makeEvent());
1428
+ await loopPromise;
1429
+
1430
+ assert.ok(
1431
+ deps.callLog.includes("postUnitPreVerification"),
1432
+ "completed units must enter post-unit pre-verification before stopping",
1433
+ );
1434
+ assert.ok(
1435
+ !deps.callLog.includes("runPostUnitVerification"),
1436
+ "git-closeout stop should not run later verification phases",
1437
+ );
1438
+
1439
+ const unitEndIndex = journalEvents.findIndex((e) => e.eventType === "unit-end");
1440
+ const finalizeStartIndex = journalEvents.findIndex((e) => e.eventType === "post-unit-finalize-start");
1441
+ const finalizeEndIndex = journalEvents.findIndex((e) => e.eventType === "post-unit-finalize-end");
1442
+ const iterationEndIndex = journalEvents.findIndex((e) => e.eventType === "iteration-end");
1443
+
1444
+ assert.ok(unitEndIndex >= 0, "unit-end should be journaled after agent completion");
1445
+ assert.ok(finalizeStartIndex > unitEndIndex, "post-unit finalize must start after unit-end");
1446
+ assert.ok(finalizeEndIndex > finalizeStartIndex, "post-unit finalize must journal its stop result");
1447
+ assert.ok(iterationEndIndex > finalizeEndIndex, "iteration-end must be emitted even when finalize stops");
1448
+
1449
+ assert.deepEqual(journalEvents[finalizeEndIndex]!.data, {
1450
+ iteration: 1,
1451
+ unitType: "execute-task",
1452
+ unitId: "M001/S01/T01",
1453
+ status: "stopped",
1454
+ action: "break",
1455
+ reason: "git-closeout-failure",
1456
+ });
1457
+ assert.deepEqual(journalEvents[iterationEndIndex]!.data, {
1458
+ iteration: 1,
1459
+ status: "stopped",
1460
+ reason: "git-closeout-failure",
1461
+ unitType: "execute-task",
1462
+ unitId: "M001/S01/T01",
1463
+ failureClass: "git",
1464
+ });
1465
+ });
1466
+
1467
+ test("autoLoop journals iteration-end when unit phase breaks after cancelled unit", async () => {
1468
+ _resetPendingResolve();
1469
+
1470
+ const ctx = makeMockCtx();
1471
+ ctx.ui.setStatus = () => {};
1472
+ ctx.sessionManager = { getSessionFile: () => "/tmp/session.json" };
1473
+ const pi = makeMockPi();
1474
+ const s = makeLoopSession();
1475
+ const journalEvents: Array<{ eventType: string; data?: any }> = [];
1476
+
1477
+ const deps = makeMockDeps({
1478
+ emitJournalEvent: (entry: any) => {
1479
+ journalEvents.push(entry);
1480
+ },
1481
+ });
1482
+
1483
+ const loopPromise = autoLoop(ctx, pi, s, deps);
1484
+ await new Promise((r) => setTimeout(r, 50));
1485
+ resolveAgentEndCancelled();
1486
+ await loopPromise;
1487
+
1488
+ const unitEndIndex = journalEvents.findIndex(
1489
+ (e) => e.eventType === "unit-end" && e.data?.status === "cancelled",
1490
+ );
1491
+ const iterationEndIndex = journalEvents.findIndex((e) => e.eventType === "iteration-end");
1492
+
1493
+ assert.ok(unitEndIndex >= 0, "cancelled unit should still emit unit-end");
1494
+ assert.ok(iterationEndIndex > unitEndIndex, "unit-phase break must close the iteration after unit-end");
1495
+ assert.deepEqual(journalEvents[iterationEndIndex]!.data, {
1496
+ iteration: 1,
1497
+ status: "stopped",
1498
+ reason: "unit-aborted",
1499
+ unitType: "execute-task",
1500
+ unitId: "M001/S01/T01",
1501
+ failureClass: "execution",
1502
+ });
1503
+ });
1504
+
1111
1505
  test("crash lock records session file from AFTER newSession, not before (#1710)", async (t) => {
1112
1506
  _resetPendingResolve();
1113
1507
 
@@ -1218,86 +1612,152 @@ test("crash lock records session file from AFTER newSession, not before (#1710)"
1218
1612
 
1219
1613
  test("autoLoop handles verification retry by continuing loop", async (t) => {
1220
1614
  _resetPendingResolve();
1615
+ mock.timers.enable({ apis: ["Date", "setTimeout"], now: 10_000 });
1221
1616
 
1222
- const ctx = makeMockCtx();
1223
- ctx.ui.setStatus = () => {};
1224
- ctx.sessionManager = { getSessionFile: () => "/tmp/session.json" };
1225
- const pi = makeMockPi();
1617
+ try {
1618
+ const ctx = makeMockCtx();
1619
+ ctx.ui.setStatus = () => {};
1620
+ ctx.sessionManager = { getSessionFile: () => "/tmp/session.json" };
1621
+ const pi = makeMockPi();
1226
1622
 
1227
- let verifyCallCount = 0;
1228
- let deriveCallCount = 0;
1229
- const s = makeLoopSession();
1623
+ let verifyCallCount = 0;
1624
+ let deriveCallCount = 0;
1625
+ const s = makeLoopSession();
1230
1626
 
1231
- // Pre-queued verification actions: each entry provides a side-effect + return value
1232
- type VerifyAction = { sideEffect?: () => void; response: "retry" | "continue" };
1233
- const verificationActions: VerifyAction[] = [
1234
- {
1235
- sideEffect: () => {
1236
- // Simulate retry — set pendingVerificationRetry on session
1237
- s.pendingVerificationRetry = {
1238
- unitId: "M001/S01/T01",
1239
- failureContext: "test failed: expected X got Y",
1240
- attempt: 1,
1241
- };
1627
+ // Pre-queued verification actions: each entry provides a side-effect + return value
1628
+ type VerifyAction = { sideEffect?: () => void; response: "retry" | "continue" };
1629
+ const verificationActions: VerifyAction[] = [
1630
+ {
1631
+ sideEffect: () => {
1632
+ // Simulate retry — set pendingVerificationRetry on session
1633
+ s.pendingVerificationRetry = {
1634
+ unitId: "M001/S01/T01",
1635
+ failureContext: "test failed: expected X got Y",
1636
+ attempt: 1,
1637
+ };
1638
+ },
1639
+ response: "retry",
1242
1640
  },
1243
- response: "retry",
1244
- },
1245
- { response: "continue" },
1246
- ];
1641
+ { response: "continue" },
1642
+ ];
1247
1643
 
1248
- const deps = makeMockDeps({
1249
- deriveState: async () => {
1250
- deriveCallCount++;
1251
- deps.callLog.push("deriveState");
1252
- return {
1253
- phase: "executing",
1254
- activeMilestone: { id: "M001", title: "Test", status: "active" },
1255
- activeSlice: { id: "S01", title: "Slice 1" },
1256
- activeTask: { id: "T01" },
1257
- registry: [{ id: "M001", status: "active" }],
1258
- blockers: [],
1259
- } as any;
1260
- },
1261
- runPostUnitVerification: async () => {
1262
- const action = verificationActions[verifyCallCount] ?? { response: "continue" as const };
1263
- verifyCallCount++;
1264
- deps.callLog.push("runPostUnitVerification");
1265
- action.sideEffect?.();
1266
- return action.response;
1267
- },
1268
- postUnitPostVerification: async () => {
1269
- deps.callLog.push("postUnitPostVerification");
1270
- // After the retry cycle completes, deactivate
1271
- s.active = false;
1272
- return "continue" as const;
1273
- },
1274
- });
1644
+ const deps = makeMockDeps({
1645
+ deriveState: async () => {
1646
+ deriveCallCount++;
1647
+ deps.callLog.push("deriveState");
1648
+ return {
1649
+ phase: "executing",
1650
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
1651
+ activeSlice: { id: "S01", title: "Slice 1" },
1652
+ activeTask: { id: "T01" },
1653
+ registry: [{ id: "M001", status: "active" }],
1654
+ blockers: [],
1655
+ } as any;
1656
+ },
1657
+ runPostUnitVerification: async () => {
1658
+ const action = verificationActions[verifyCallCount] ?? { response: "continue" as const };
1659
+ verifyCallCount++;
1660
+ deps.callLog.push("runPostUnitVerification");
1661
+ action.sideEffect?.();
1662
+ return action.response;
1663
+ },
1664
+ postUnitPostVerification: async () => {
1665
+ deps.callLog.push("postUnitPostVerification");
1666
+ // After the retry cycle completes, deactivate
1667
+ s.active = false;
1668
+ return "continue" as const;
1669
+ },
1670
+ });
1275
1671
 
1276
- const loopPromise = autoLoop(ctx, pi, s, deps);
1672
+ const loopPromise = autoLoop(ctx, pi, s, deps);
1277
1673
 
1278
- // First iteration: runUnit → verification returns "retry" → loop continues
1279
- await new Promise((r) => setTimeout(r, 50));
1280
- resolveAgentEnd(makeEvent()); // resolve first unit
1674
+ // First iteration: runUnit → verification returns "retry" → loop continues
1675
+ await waitForMicrotasks(() => pi.calls.length === 1, "first dispatch");
1676
+ resolveAgentEnd(makeEvent()); // resolve first unit
1281
1677
 
1282
- // Second iteration: runUnit → verification returns "continue"
1283
- await new Promise((r) => setTimeout(r, 50));
1284
- resolveAgentEnd(makeEvent()); // resolve retry unit
1678
+ await drainMicrotasks(100);
1679
+ mock.timers.tick(30_000);
1680
+ await waitForMicrotasks(() => pi.calls.length === 2, "retry dispatch");
1681
+ resolveAgentEnd(makeEvent()); // resolve retry unit
1285
1682
 
1286
- await loopPromise;
1683
+ await loopPromise;
1287
1684
 
1288
- // Verify deriveState was called twice (two iterations)
1289
- const deriveCount = deps.callLog.filter((c) => c === "deriveState").length;
1290
- assert.ok(
1291
- deriveCount >= 2,
1292
- `deriveState should be called at least 2 times (got ${deriveCount})`,
1293
- );
1685
+ // Verify deriveState was called twice (two iterations)
1686
+ const deriveCount = deps.callLog.filter((c) => c === "deriveState").length;
1687
+ assert.ok(
1688
+ deriveCount >= 2,
1689
+ `deriveState should be called at least 2 times (got ${deriveCount})`,
1690
+ );
1294
1691
 
1295
- // Verify verification was called twice
1296
- assert.equal(
1297
- verifyCallCount,
1298
- 2,
1299
- "verification should have been called twice (once retry, once pass)",
1300
- );
1692
+ // Verify verification was called twice
1693
+ assert.equal(
1694
+ verifyCallCount,
1695
+ 2,
1696
+ "verification should have been called twice (once retry, once pass)",
1697
+ );
1698
+ } finally {
1699
+ mock.timers.reset();
1700
+ }
1701
+ });
1702
+
1703
+ test("autoLoop pauses instead of redispatching identical verification failure context", async () => {
1704
+ _resetPendingResolve();
1705
+ mock.timers.enable({ apis: ["Date", "setTimeout"], now: 15_000 });
1706
+
1707
+ try {
1708
+ const ctx = makeMockCtx();
1709
+ ctx.ui.setStatus = () => {};
1710
+ ctx.ui.notify = () => {};
1711
+ ctx.sessionManager = { getSessionFile: () => "/tmp/session.json" };
1712
+ const pi = makeMockPi();
1713
+ const s = makeLoopSession();
1714
+ let verifyCallCount = 0;
1715
+ let pauseCallCount = 0;
1716
+
1717
+ const deps = makeMockDeps({
1718
+ deriveState: async () =>
1719
+ ({
1720
+ phase: "executing",
1721
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
1722
+ activeSlice: { id: "S01", title: "Slice 1" },
1723
+ activeTask: { id: "T01" },
1724
+ registry: [{ id: "M001", status: "active" }],
1725
+ blockers: [],
1726
+ }) as any,
1727
+ runPostUnitVerification: async () => {
1728
+ verifyCallCount++;
1729
+ deps.callLog.push("runPostUnitVerification");
1730
+ s.pendingVerificationRetry = {
1731
+ unitId: "M001/S01/T01",
1732
+ failureContext: "test failed: expected X got Y",
1733
+ attempt: verifyCallCount,
1734
+ };
1735
+ return "retry" as const;
1736
+ },
1737
+ pauseAuto: async () => {
1738
+ pauseCallCount++;
1739
+ s.active = false;
1740
+ },
1741
+ });
1742
+
1743
+ const loopPromise = autoLoop(ctx, pi, s, deps);
1744
+
1745
+ await waitForMicrotasks(() => pi.calls.length === 1, "first dispatch");
1746
+ resolveAgentEnd(makeEvent());
1747
+ await drainMicrotasks(100);
1748
+ mock.timers.tick(30_000);
1749
+
1750
+ await waitForMicrotasks(() => pi.calls.length === 2, "retry dispatch");
1751
+ resolveAgentEnd(makeEvent());
1752
+
1753
+ await loopPromise;
1754
+
1755
+ assert.equal(verifyCallCount, 2);
1756
+ assert.equal(pi.calls.length, 2, "duplicate failure should not be redispatched a third time");
1757
+ assert.equal(pauseCallCount, 1, "duplicate failure should pause auto-mode");
1758
+ } finally {
1759
+ mock.timers.reset();
1760
+ }
1301
1761
  });
1302
1762
 
1303
1763
  test("autoLoop handles dispatch stop action", async (t) => {
@@ -1453,7 +1913,7 @@ test("autoLoop drains sidecar queue after postUnitPostVerification enqueues item
1453
1913
  // First call (main unit): enqueue a sidecar item
1454
1914
  s.sidecarQueue.push({
1455
1915
  kind: "hook" as const,
1456
- unitType: "hook/review",
1916
+ unitType: "run-uat",
1457
1917
  unitId: "M001/S01/T01/review",
1458
1918
  prompt: "review the code",
1459
1919
  });
@@ -1475,11 +1935,17 @@ test("autoLoop drains sidecar queue after postUnitPostVerification enqueues item
1475
1935
  const loopPromise = autoLoop(ctx, pi, s, deps);
1476
1936
 
1477
1937
  // Wait for main unit's runUnit to be awaiting
1478
- await new Promise((r) => setTimeout(r, 50));
1938
+ for (let i = 0; !_hasPendingResolveForTest() && i < 100; i++) {
1939
+ await new Promise((r) => setTimeout(r, 5));
1940
+ }
1941
+ assert.equal(_hasPendingResolveForTest(), true, "main unit should be awaiting agent_end");
1479
1942
  resolveAgentEnd(makeEvent()); // resolve main unit
1480
1943
 
1481
1944
  // Wait for the sidecar unit's runUnit to be awaiting
1482
- await new Promise((r) => setTimeout(r, 50));
1945
+ for (let i = 0; !_hasPendingResolveForTest() && postVerCallCount < 2 && i < 100; i++) {
1946
+ await new Promise((r) => setTimeout(r, 5));
1947
+ }
1948
+ assert.equal(_hasPendingResolveForTest(), true, "sidecar unit should be awaiting agent_end");
1483
1949
  resolveAgentEnd(makeEvent()); // resolve sidecar unit
1484
1950
 
1485
1951
  await loopPromise;
@@ -1680,74 +2146,83 @@ test("stuck detection: window resets recovery when deriveState returns a differe
1680
2146
 
1681
2147
  test("stuck detection: does not push to window during verification retry", async () => {
1682
2148
  _resetPendingResolve();
2149
+ mock.timers.enable({ apis: ["Date", "setTimeout"], now: 20_000 });
1683
2150
 
1684
- const ctx = makeMockCtx();
1685
- ctx.ui.setStatus = () => {};
1686
- ctx.ui.notify = () => {};
1687
- const pi = makeMockPi();
1688
- const s = makeLoopSession();
2151
+ try {
2152
+ const ctx = makeMockCtx();
2153
+ ctx.ui.setStatus = () => {};
2154
+ ctx.ui.notify = () => {};
2155
+ const pi = makeMockPi();
2156
+ const s = makeLoopSession();
1689
2157
 
1690
- let verifyCallCount = 0;
1691
- let stopReason = "";
2158
+ let verifyCallCount = 0;
2159
+ let stopReason = "";
1692
2160
 
1693
- // Pre-queued responses: 3 retries then a continue (exit)
1694
- const verifyActions: Array<() => "retry" | "continue"> = [
1695
- () => { s.pendingVerificationRetry = { unitId: "M001/S01/T01", failureContext: "test failed", attempt: 1 }; return "retry"; },
1696
- () => { s.pendingVerificationRetry = { unitId: "M001/S01/T01", failureContext: "test failed", attempt: 2 }; return "retry"; },
1697
- () => { s.pendingVerificationRetry = { unitId: "M001/S01/T01", failureContext: "test failed", attempt: 3 }; return "retry"; },
1698
- () => { s.active = false; return "continue"; },
1699
- ];
2161
+ // Pre-queued responses: 3 retries then a continue (exit). Failure
2162
+ // contexts differ so this test exercises stuck-window behavior without
2163
+ // tripping duplicate-failure suppression.
2164
+ const verifyActions: Array<() => "retry" | "continue"> = [
2165
+ () => { s.pendingVerificationRetry = { unitId: "M001/S01/T01", failureContext: "test failed: 1", attempt: 1 }; return "retry"; },
2166
+ () => { s.pendingVerificationRetry = { unitId: "M001/S01/T01", failureContext: "test failed: 2", attempt: 2 }; return "retry"; },
2167
+ () => { s.pendingVerificationRetry = { unitId: "M001/S01/T01", failureContext: "test failed: 3", attempt: 3 }; return "retry"; },
2168
+ () => { s.active = false; return "continue"; },
2169
+ ];
1700
2170
 
1701
- const deps = makeMockDeps({
1702
- deriveState: async () =>
1703
- ({
1704
- phase: "executing",
1705
- activeMilestone: { id: "M001", title: "Test", status: "active" },
1706
- activeSlice: { id: "S01", title: "Slice 1" },
1707
- activeTask: { id: "T01" },
1708
- registry: [{ id: "M001", status: "active" }],
1709
- blockers: [],
1710
- }) as any,
1711
- resolveDispatch: async () => ({
1712
- action: "dispatch" as const,
1713
- unitType: "execute-task",
1714
- unitId: "M001/S01/T01",
1715
- prompt: "do the thing",
1716
- }),
1717
- runPostUnitVerification: async () => {
1718
- const action = verifyActions[verifyCallCount] ?? (() => { s.active = false; return "continue" as const; });
1719
- verifyCallCount++;
1720
- deps.callLog.push("runPostUnitVerification");
1721
- return action();
1722
- },
1723
- stopAuto: async (_ctx?: any, _pi?: any, reason?: string) => {
1724
- deps.callLog.push("stopAuto");
1725
- stopReason = reason ?? "";
1726
- s.active = false;
1727
- },
1728
- });
2171
+ const deps = makeMockDeps({
2172
+ deriveState: async () =>
2173
+ ({
2174
+ phase: "executing",
2175
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
2176
+ activeSlice: { id: "S01", title: "Slice 1" },
2177
+ activeTask: { id: "T01" },
2178
+ registry: [{ id: "M001", status: "active" }],
2179
+ blockers: [],
2180
+ }) as any,
2181
+ resolveDispatch: async () => ({
2182
+ action: "dispatch" as const,
2183
+ unitType: "execute-task",
2184
+ unitId: "M001/S01/T01",
2185
+ prompt: "do the thing",
2186
+ }),
2187
+ runPostUnitVerification: async () => {
2188
+ const action = verifyActions[verifyCallCount] ?? (() => { s.active = false; return "continue" as const; });
2189
+ verifyCallCount++;
2190
+ deps.callLog.push("runPostUnitVerification");
2191
+ return action();
2192
+ },
2193
+ stopAuto: async (_ctx?: any, _pi?: any, reason?: string) => {
2194
+ deps.callLog.push("stopAuto");
2195
+ stopReason = reason ?? "";
2196
+ s.active = false;
2197
+ },
2198
+ });
1729
2199
 
1730
- const loopPromise = autoLoop(ctx, pi, s, deps);
2200
+ const loopPromise = autoLoop(ctx, pi, s, deps);
1731
2201
 
1732
- // Resolve agent_end for 4 iterations (1 initial + 3 retries)
1733
- for (let i = 0; i < 4; i++) {
1734
- await new Promise((r) => setTimeout(r, 30));
1735
- resolveAgentEnd(makeEvent());
1736
- }
2202
+ // Resolve agent_end for 4 iterations (1 initial + 3 retries)
2203
+ for (let i = 1; i <= 4; i++) {
2204
+ await waitForMicrotasks(() => pi.calls.length === i, `dispatch ${i}`);
2205
+ resolveAgentEnd(makeEvent());
2206
+ await drainMicrotasks(100);
2207
+ mock.timers.tick(30_000);
2208
+ }
1737
2209
 
1738
- await loopPromise;
2210
+ await loopPromise;
1739
2211
 
1740
- // Even though same unit was derived 4 times, verification retries should
1741
- // not push to the sliding window, so stuck detection should not have fired
1742
- assert.ok(
1743
- !stopReason.includes("Stuck"),
1744
- `stuck detection should not fire during verification retries, got: ${stopReason}`,
1745
- );
1746
- assert.equal(
1747
- verifyCallCount,
1748
- 4,
1749
- "verification should have been called 4 times (1 initial + 3 retries)",
1750
- );
2212
+ // Even though same unit was derived 4 times, verification retries should
2213
+ // not push to the sliding window, so stuck detection should not have fired
2214
+ assert.ok(
2215
+ !stopReason.includes("Stuck"),
2216
+ `stuck detection should not fire during verification retries, got: ${stopReason}`,
2217
+ );
2218
+ assert.equal(
2219
+ verifyCallCount,
2220
+ 4,
2221
+ "verification should have been called 4 times (1 initial + 3 retries)",
2222
+ );
2223
+ } finally {
2224
+ mock.timers.reset();
2225
+ }
1751
2226
  });
1752
2227
 
1753
2228
  // ── detectStuck unit tests ────────────────────────────────────────────────────
@@ -1898,7 +2373,7 @@ test("autoLoop lifecycle: advances through research → plan → execute → ver
1898
2373
  { unitType: "research-slice", unitId: "M001/S01", prompt: "research" },
1899
2374
  { unitType: "plan-slice", unitId: "M001/S01", prompt: "plan" },
1900
2375
  { unitType: "execute-task", unitId: "M001/S01/T01", prompt: "execute" },
1901
- { unitType: "verify-slice", unitId: "M001/S01", prompt: "verify" },
2376
+ { unitType: "run-uat", unitId: "M001/S01", prompt: "verify" },
1902
2377
  { unitType: "complete-slice", unitId: "M001/S01", prompt: "complete" },
1903
2378
  ];
1904
2379
 
@@ -1968,8 +2443,8 @@ test("autoLoop lifecycle: advances through research → plan → execute → ver
1968
2443
  `should have dispatched execute-task, got: ${dispatchedUnitTypes.join(", ")}`,
1969
2444
  );
1970
2445
  assert.ok(
1971
- dispatchedUnitTypes.includes("verify-slice"),
1972
- `should have dispatched verify-slice, got: ${dispatchedUnitTypes.join(", ")}`,
2446
+ dispatchedUnitTypes.includes("run-uat"),
2447
+ `should have dispatched run-uat, got: ${dispatchedUnitTypes.join(", ")}`,
1973
2448
  );
1974
2449
  assert.ok(
1975
2450
  dispatchedUnitTypes.includes("complete-slice"),
@@ -2001,7 +2476,7 @@ test("autoLoop lifecycle: advances through research → plan → execute → ver
2001
2476
  "research-slice",
2002
2477
  "plan-slice",
2003
2478
  "execute-task",
2004
- "verify-slice",
2479
+ "run-uat",
2005
2480
  "complete-slice",
2006
2481
  ],
2007
2482
  "dispatched unit types should follow the full lifecycle sequence",
@@ -2073,6 +2548,108 @@ test("resolveAgentEndCancelled with errorContext passes it through to resolved p
2073
2548
  assert.equal(resolved.errorContext!.isTransient, true);
2074
2549
  });
2075
2550
 
2551
+ test("runUnitPhase pauses ghost completions before closeout and finalize side effects", async (t) => {
2552
+ _resetPendingResolve();
2553
+
2554
+ const basePath = mkdtempSync(join(tmpdir(), "gsd-ghost-completion-"));
2555
+ t.after(() => {
2556
+ _resetPendingResolve();
2557
+ rmSync(basePath, { recursive: true, force: true });
2558
+ });
2559
+
2560
+ let closeoutCalls = 0;
2561
+ let preVerificationCalls = 0;
2562
+ let postVerificationCalls = 0;
2563
+ const journalEvents: any[] = [];
2564
+ const deps = makeMockDeps({
2565
+ closeoutUnit: async () => {
2566
+ closeoutCalls++;
2567
+ },
2568
+ postUnitPreVerification: async () => {
2569
+ preVerificationCalls++;
2570
+ return "continue";
2571
+ },
2572
+ postUnitPostVerification: async () => {
2573
+ postVerificationCalls++;
2574
+ return "continue";
2575
+ },
2576
+ emitJournalEvent: (event: any) => {
2577
+ journalEvents.push(event);
2578
+ },
2579
+ });
2580
+ const ctx = {
2581
+ ...makeMockCtx(),
2582
+ ui: {
2583
+ notify: () => {},
2584
+ setStatus: () => {},
2585
+ setWorkingMessage: () => {},
2586
+ },
2587
+ sessionManager: {
2588
+ getEntries: () => [],
2589
+ },
2590
+ modelRegistry: {
2591
+ getProviderAuthMode: () => undefined,
2592
+ isProviderRequestReady: () => true,
2593
+ },
2594
+ } as any;
2595
+ const pi = {
2596
+ ...makeMockPi(),
2597
+ sendMessage: () => {
2598
+ queueMicrotask(() => resolveAgentEnd({ messages: [] }));
2599
+ },
2600
+ } as any;
2601
+ const s = makeLoopSession({
2602
+ basePath,
2603
+ canonicalProjectRoot: basePath,
2604
+ originalBasePath: basePath,
2605
+ });
2606
+ let seq = 0;
2607
+
2608
+ const result = await runUnitPhase(
2609
+ { ctx, pi, s, deps, prefs: undefined, iteration: 1, flowId: "flow-ghost", nextSeq: () => ++seq },
2610
+ {
2611
+ unitType: "execute-task",
2612
+ unitId: "M001/S01/T01",
2613
+ prompt: "do work",
2614
+ finalPrompt: "do work",
2615
+ pauseAfterUatDispatch: false,
2616
+ state: {
2617
+ phase: "executing",
2618
+ activeMilestone: { id: "M001", title: "Milestone" },
2619
+ activeSlice: { id: "S01", title: "Slice" },
2620
+ activeTask: { id: "T01", title: "Task" },
2621
+ registry: [{ id: "M001", title: "Milestone", status: "active" }],
2622
+ recentDecisions: [],
2623
+ blockers: [],
2624
+ nextAction: "",
2625
+ progress: { milestones: { done: 0, total: 1 } },
2626
+ requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
2627
+ } as any,
2628
+ mid: "M001",
2629
+ midTitle: "Milestone",
2630
+ isRetry: false,
2631
+ previousTier: undefined,
2632
+ },
2633
+ { recentUnits: [], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 },
2634
+ );
2635
+
2636
+ assert.equal(result.action, "break");
2637
+ assert.equal((result as any).reason, "ghost-completion");
2638
+ assert.equal(deps.callLog.includes("pauseAuto"), true);
2639
+ assert.equal(closeoutCalls, 0);
2640
+ assert.equal(preVerificationCalls, 0);
2641
+ assert.equal(postVerificationCalls, 0);
2642
+ assert.equal(s.currentUnit, null);
2643
+ assert.ok(
2644
+ journalEvents.some((event) =>
2645
+ event.eventType === "unit-end" &&
2646
+ event.data?.status === "cancelled" &&
2647
+ event.data?.errorContext?.message.includes("stale ghost completion")
2648
+ ),
2649
+ "ghost completion should emit a cancelled unit-end",
2650
+ );
2651
+ });
2652
+
2076
2653
  test("resolveAgentEndCancelled without args produces no errorContext field", async () => {
2077
2654
  _resetPendingResolve();
2078
2655
 
@@ -2089,64 +2666,110 @@ test("resolveAgentEndCancelled without args produces no errorContext field", asy
2089
2666
  assert.equal(resolved.errorContext, undefined, "errorContext must not be present when no args passed");
2090
2667
  });
2091
2668
 
2669
+ test("resolveAgentEndCancelled queues cancellation that arrives during session switch", () => {
2670
+ _resetPendingResolve();
2671
+
2672
+ _setSessionSwitchInFlight(true);
2673
+ const resolved = resolveAgentEndCancelled({
2674
+ message: "Claude Code process aborted by user",
2675
+ category: "aborted",
2676
+ isTransient: false,
2677
+ });
2678
+
2679
+ assert.equal(resolved, false);
2680
+ const pending = _consumePendingSwitchCancellation();
2681
+ assert.ok(pending?.errorContext, "queued cancellation should preserve errorContext");
2682
+ assert.equal(pending.errorContext.category, "aborted");
2683
+ assert.equal(pending.errorContext.message, "Claude Code process aborted by user");
2684
+ assert.equal(_consumePendingSwitchCancellation(), null);
2685
+ _resetPendingResolve();
2686
+ });
2687
+
2688
+ test("session-switch abort grace window is short-lived and resettable", () => {
2689
+ _resetPendingResolve();
2690
+
2691
+ _markSessionSwitchAbortGraceWindow(1_000);
2692
+
2693
+ assert.equal(isSessionSwitchAbortGraceActive(Date.now()), true);
2694
+ assert.equal(isSessionSwitchAbortGraceActive(Date.now() + 10_000), false);
2695
+
2696
+ _clearSessionSwitchAbortGraceWindow();
2697
+ assert.equal(isSessionSwitchAbortGraceActive(), false);
2698
+ });
2699
+
2092
2700
  // ─── #1571: artifact verification retry ──────────────────────────────────────
2093
2701
 
2094
2702
  test("autoLoop re-iterates when postUnitPreVerification returns retry (#1571)", async () => {
2095
2703
  _resetPendingResolve();
2704
+ mock.timers.enable({ apis: ["Date", "setTimeout"], now: 30_000 });
2096
2705
 
2097
- const ctx = makeMockCtx();
2098
- ctx.ui.setStatus = () => {};
2099
- const pi = makeMockPi();
2100
- const s = makeLoopSession();
2706
+ try {
2707
+ const ctx = makeMockCtx();
2708
+ ctx.ui.setStatus = () => {};
2709
+ const pi = makeMockPi();
2710
+ const s = makeLoopSession();
2101
2711
 
2102
- let preVerifyCallCount = 0;
2103
- // Pre-queued responses: first call returns "retry", second returns "continue"
2104
- const preVerifyResponses = ["retry", "continue"] as const;
2712
+ let preVerifyCallCount = 0;
2713
+ // Pre-queued responses: first call returns "retry", second returns "continue"
2714
+ const preVerifyResponses = ["retry", "continue"] as const;
2105
2715
 
2106
- const deps = makeMockDeps({
2107
- deriveState: async () => {
2108
- deps.callLog.push("deriveState");
2109
- return {
2110
- phase: "executing",
2111
- activeMilestone: { id: "M001", title: "Test", status: "active" },
2112
- activeSlice: { id: "S01", title: "Slice 1" },
2113
- activeTask: { id: "T01" },
2114
- registry: [{ id: "M001", status: "active" }],
2115
- blockers: [],
2116
- } as any;
2117
- },
2118
- postUnitPreVerification: async () => {
2119
- deps.callLog.push("postUnitPreVerification");
2120
- return preVerifyResponses[preVerifyCallCount++] ?? "continue";
2121
- },
2122
- postUnitPostVerification: async () => {
2123
- deps.callLog.push("postUnitPostVerification");
2124
- s.active = false;
2125
- return "continue" as const;
2126
- },
2127
- });
2716
+ const deps = makeMockDeps({
2717
+ deriveState: async () => {
2718
+ deps.callLog.push("deriveState");
2719
+ return {
2720
+ phase: "executing",
2721
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
2722
+ activeSlice: { id: "S01", title: "Slice 1" },
2723
+ activeTask: { id: "T01" },
2724
+ registry: [{ id: "M001", status: "active" }],
2725
+ blockers: [],
2726
+ } as any;
2727
+ },
2728
+ postUnitPreVerification: async () => {
2729
+ deps.callLog.push("postUnitPreVerification");
2730
+ const response = preVerifyResponses[preVerifyCallCount++] ?? "continue";
2731
+ if (response === "retry") {
2732
+ s.pendingVerificationRetry = {
2733
+ unitId: "M001/S01/T01",
2734
+ failureContext: "missing artifact",
2735
+ attempt: 1,
2736
+ };
2737
+ }
2738
+ return response;
2739
+ },
2740
+ postUnitPostVerification: async () => {
2741
+ deps.callLog.push("postUnitPostVerification");
2742
+ s.active = false;
2743
+ return "continue" as const;
2744
+ },
2745
+ });
2128
2746
 
2129
- const loopPromise = autoLoop(ctx, pi, s, deps);
2747
+ const loopPromise = autoLoop(ctx, pi, s, deps);
2130
2748
 
2131
- await new Promise((r) => setTimeout(r, 50));
2132
- resolveAgentEnd(makeEvent());
2749
+ await waitForMicrotasks(() => pi.calls.length === 1, "first dispatch");
2750
+ resolveAgentEnd(makeEvent());
2133
2751
 
2134
- await new Promise((r) => setTimeout(r, 50));
2135
- resolveAgentEnd(makeEvent());
2752
+ await drainMicrotasks(100);
2753
+ mock.timers.tick(30_000);
2754
+ await waitForMicrotasks(() => pi.calls.length === 2, "retry dispatch");
2755
+ resolveAgentEnd(makeEvent());
2136
2756
 
2137
- await loopPromise;
2757
+ await loopPromise;
2138
2758
 
2139
- assert.equal(preVerifyCallCount, 2, "preVerification should be called twice");
2759
+ assert.equal(preVerifyCallCount, 2, "preVerification should be called twice");
2140
2760
 
2141
- const postVerifyCalls = deps.callLog.filter(
2142
- (c: string) => c === "runPostUnitVerification",
2143
- );
2144
- const postPostVerifyCalls = deps.callLog.filter(
2145
- (c: string) => c === "postUnitPostVerification",
2146
- );
2761
+ const postVerifyCalls = deps.callLog.filter(
2762
+ (c: string) => c === "runPostUnitVerification",
2763
+ );
2764
+ const postPostVerifyCalls = deps.callLog.filter(
2765
+ (c: string) => c === "postUnitPostVerification",
2766
+ );
2147
2767
 
2148
- assert.equal(postVerifyCalls.length, 1, "runPostUnitVerification should only be called once");
2149
- assert.equal(postPostVerifyCalls.length, 1, "postUnitPostVerification should only be called once");
2768
+ assert.equal(postVerifyCalls.length, 1, "runPostUnitVerification should only be called once");
2769
+ assert.equal(postPostVerifyCalls.length, 1, "postUnitPostVerification should only be called once");
2770
+ } finally {
2771
+ mock.timers.reset();
2772
+ }
2150
2773
  });
2151
2774
 
2152
2775
  // ─── stopAuto unitPromise leak regression (#1799) ────────────────────────────
@@ -2465,7 +3088,7 @@ test("autoLoop rejects complete-slice with 0 tool calls as context-exhausted (#2
2465
3088
 
2466
3089
  // ─── Worktree health check (#1833) ────────────────────────────────────────
2467
3090
 
2468
- test("autoLoop stops when worktree has no .git for execute-task (#1833)", async () => {
3091
+ test("autoLoop stops when Worktree Safety finds no .git marker for execute-task (#1833)", async (t) => {
2469
3092
  _resetPendingResolve();
2470
3093
 
2471
3094
  const ctx = makeMockCtx();
@@ -2476,7 +3099,16 @@ test("autoLoop stops when worktree has no .git for execute-task (#1833)", async
2476
3099
  const notifications: string[] = [];
2477
3100
  ctx.ui.notify = (msg: string) => { notifications.push(msg); };
2478
3101
 
2479
- const s = makeLoopSession({ basePath: "/tmp/broken-worktree" });
3102
+ const projectRoot = mkdtempSync(join(tmpdir(), "gsd-wt-safety-loop-"));
3103
+ const worktreeRoot = join(projectRoot, ".gsd", "worktrees", "M001");
3104
+ mkdirSync(worktreeRoot, { recursive: true });
3105
+ t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
3106
+
3107
+ const s = makeLoopSession({
3108
+ basePath: worktreeRoot,
3109
+ originalBasePath: projectRoot,
3110
+ canonicalProjectRoot: projectRoot,
3111
+ });
2480
3112
 
2481
3113
  const deps = makeMockDeps({
2482
3114
  deriveState: async () => {
@@ -2490,8 +3122,7 @@ test("autoLoop stops when worktree has no .git for execute-task (#1833)", async
2490
3122
  blockers: [],
2491
3123
  } as any;
2492
3124
  },
2493
- // .git does not exist in the broken worktree
2494
- existsSync: (p: string) => !p.endsWith(".git"),
3125
+ getIsolationMode: () => "worktree",
2495
3126
  });
2496
3127
 
2497
3128
  await autoLoop(ctx, pi, s, deps);
@@ -2501,11 +3132,283 @@ test("autoLoop stops when worktree has no .git for execute-task (#1833)", async
2501
3132
  "should stop auto-mode when worktree is invalid",
2502
3133
  );
2503
3134
  const healthNotification = notifications.find(
2504
- (n) => n.includes("Worktree health check failed") && n.includes("no .git"),
3135
+ (n) => n.includes("Worktree Safety failed") && n.includes("worktree-git-marker-missing"),
2505
3136
  );
2506
3137
  assert.ok(
2507
3138
  healthNotification,
2508
- "should notify about missing .git in worktree",
3139
+ "should notify about missing worktree .git marker",
3140
+ );
3141
+ });
3142
+
3143
+ test("dispatch Worktree Safety wins before stuck detection for execute-task without .git", async (t) => {
3144
+ _resetPendingResolve();
3145
+
3146
+ const ctx = makeMockCtx();
3147
+ const pi = makeMockPi();
3148
+ const notifications: string[] = [];
3149
+ ctx.ui.notify = (msg: string) => { notifications.push(msg); };
3150
+
3151
+ const projectRoot = mkdtempSync(join(tmpdir(), "gsd-wt-safety-dispatch-"));
3152
+ const worktreeRoot = join(projectRoot, ".gsd", "worktrees", "M001");
3153
+ mkdirSync(worktreeRoot, { recursive: true });
3154
+ t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
3155
+
3156
+ const s = makeLoopSession({
3157
+ basePath: worktreeRoot,
3158
+ originalBasePath: projectRoot,
3159
+ canonicalProjectRoot: projectRoot,
3160
+ });
3161
+ const deps = makeMockDeps({
3162
+ getIsolationMode: () => "worktree",
3163
+ });
3164
+ const result = await runDispatch(
3165
+ {
3166
+ ctx,
3167
+ pi,
3168
+ s,
3169
+ deps,
3170
+ prefs: undefined,
3171
+ iteration: 1,
3172
+ flowId: "test-flow",
3173
+ nextSeq: () => 1,
3174
+ },
3175
+ {
3176
+ state: {
3177
+ phase: "executing",
3178
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
3179
+ activeSlice: { id: "S01", title: "Slice 1" },
3180
+ activeTask: { id: "T01" },
3181
+ registry: [{ id: "M001", status: "active" }],
3182
+ blockers: [],
3183
+ } as any,
3184
+ mid: "M001",
3185
+ midTitle: "Test",
3186
+ },
3187
+ {
3188
+ recentUnits: [
3189
+ { key: "execute-task/M001/S01/T01" },
3190
+ { key: "execute-task/M001/S01/T01" },
3191
+ ],
3192
+ stuckRecoveryAttempts: 1,
3193
+ consecutiveFinalizeTimeouts: 0,
3194
+ },
3195
+ );
3196
+
3197
+ assert.equal(result.action, "break");
3198
+ assert.equal(result.reason, "worktree-git-marker-missing");
3199
+ assert.ok(deps.callLog.includes("stopAuto"), "should stop through Worktree Safety");
3200
+ assert.ok(
3201
+ notifications.some((n) => n.includes("Worktree Safety failed") && n.includes("worktree-git-marker-missing")),
3202
+ "should notify about missing worktree .git marker",
3203
+ );
3204
+ assert.ok(
3205
+ !notifications.some((n) => n.includes("Stuck on execute-task")),
3206
+ "stuck-loop message must not mask the worktree health failure",
3207
+ );
3208
+ });
3209
+
3210
+ test("dispatch Worktree Safety stops unknown unit types with missing Tool Contract", async (t) => {
3211
+ _resetPendingResolve();
3212
+
3213
+ const ctx = makeMockCtx();
3214
+ const pi = makeMockPi();
3215
+ const notifications: string[] = [];
3216
+ ctx.ui.notify = (msg: string) => { notifications.push(msg); };
3217
+
3218
+ const projectRoot = mkdtempSync(join(tmpdir(), "gsd-wt-safety-missing-contract-"));
3219
+ const worktreeRoot = join(projectRoot, ".gsd", "worktrees", "M001");
3220
+ mkdirSync(worktreeRoot, { recursive: true });
3221
+ t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
3222
+
3223
+ const s = makeLoopSession({
3224
+ basePath: worktreeRoot,
3225
+ originalBasePath: projectRoot,
3226
+ canonicalProjectRoot: projectRoot,
3227
+ });
3228
+ const deps = makeMockDeps({
3229
+ getIsolationMode: () => "worktree",
3230
+ resolveDispatch: async () => {
3231
+ deps.callLog.push("resolveDispatch");
3232
+ return {
3233
+ action: "dispatch" as const,
3234
+ unitType: "new-source-writing-unit-without-manifest",
3235
+ unitId: "M001/S01/T01",
3236
+ prompt: "do the thing",
3237
+ };
3238
+ },
3239
+ });
3240
+
3241
+ const result = await runDispatch(
3242
+ {
3243
+ ctx,
3244
+ pi,
3245
+ s,
3246
+ deps,
3247
+ prefs: undefined,
3248
+ iteration: 1,
3249
+ flowId: "test-flow",
3250
+ nextSeq: () => 1,
3251
+ },
3252
+ {
3253
+ state: {
3254
+ phase: "executing",
3255
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
3256
+ activeSlice: { id: "S01", title: "Slice 1" },
3257
+ activeTask: { id: "T01" },
3258
+ registry: [{ id: "M001", status: "active" }],
3259
+ blockers: [],
3260
+ } as any,
3261
+ mid: "M001",
3262
+ midTitle: "Test",
3263
+ },
3264
+ {
3265
+ recentUnits: [],
3266
+ stuckRecoveryAttempts: 0,
3267
+ consecutiveFinalizeTimeouts: 0,
3268
+ },
3269
+ );
3270
+
3271
+ assert.equal(result.action, "break");
3272
+ assert.equal(result.reason, "missing-tool-contract");
3273
+ assert.ok(deps.callLog.includes("stopAuto"), "should stop when the Tool Contract is missing");
3274
+ assert.ok(
3275
+ notifications.some((n) => n.includes("missing Tool Contract for new-source-writing-unit-without-manifest")),
3276
+ "should notify with an actionable missing Tool Contract reason",
3277
+ );
3278
+ });
3279
+
3280
+ test("pre-dispatch skip resolves before dispatch health and stuck accounting", async () => {
3281
+ _resetPendingResolve();
3282
+
3283
+ const ctx = makeMockCtx();
3284
+ const pi = makeMockPi();
3285
+ const notifications: string[] = [];
3286
+ ctx.ui.notify = (msg: string) => { notifications.push(msg); };
3287
+
3288
+ const s = makeLoopSession({ basePath: "/tmp/broken-worktree" });
3289
+ const deps = makeMockDeps({
3290
+ existsSync: (p: string) => !p.endsWith(".git"),
3291
+ runPreDispatchHooks: () => ({ firedHooks: ["skip-execute"], action: "skip" }),
3292
+ });
3293
+ const loopState = {
3294
+ recentUnits: [
3295
+ { key: "execute-task/M001/S01/T01" },
3296
+ { key: "execute-task/M001/S01/T01" },
3297
+ ],
3298
+ stuckRecoveryAttempts: 1,
3299
+ consecutiveFinalizeTimeouts: 0,
3300
+ };
3301
+
3302
+ const result = await runDispatch(
3303
+ {
3304
+ ctx,
3305
+ pi,
3306
+ s,
3307
+ deps,
3308
+ prefs: undefined,
3309
+ iteration: 1,
3310
+ flowId: "test-flow",
3311
+ nextSeq: () => 1,
3312
+ },
3313
+ {
3314
+ state: {
3315
+ phase: "executing",
3316
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
3317
+ activeSlice: { id: "S01", title: "Slice 1" },
3318
+ activeTask: { id: "T01" },
3319
+ registry: [{ id: "M001", status: "active" }],
3320
+ blockers: [],
3321
+ } as any,
3322
+ mid: "M001",
3323
+ midTitle: "Test",
3324
+ },
3325
+ loopState,
3326
+ );
3327
+
3328
+ assert.equal(result.action, "continue");
3329
+ assert.ok(!deps.callLog.includes("stopAuto"), "skip hook should not stop on worktree health");
3330
+ assert.equal(loopState.recentUnits.length, 2, "skip hook should not update stuck accounting");
3331
+ assert.ok(
3332
+ notifications.some((n) => n.includes("Skipping execute-task M001/S01/T01")),
3333
+ "should notify about the skip hook",
3334
+ );
3335
+ assert.ok(
3336
+ !notifications.some((n) => n.includes("Worktree health check failed") || n.includes("Stuck on execute-task")),
3337
+ "health and stuck notifications must not run before skip hook resolution",
3338
+ );
3339
+ });
3340
+
3341
+ test("pre-dispatch replace resolves final unit before dispatch health and stuck accounting", async () => {
3342
+ _resetPendingResolve();
3343
+
3344
+ const ctx = makeMockCtx();
3345
+ const pi = makeMockPi();
3346
+ const notifications: string[] = [];
3347
+ ctx.ui.notify = (msg: string) => { notifications.push(msg); };
3348
+
3349
+ const s = makeLoopSession({ basePath: "/tmp/broken-worktree" });
3350
+ const deps = makeMockDeps({
3351
+ existsSync: (p: string) => !p.endsWith(".git"),
3352
+ runPreDispatchHooks: () => ({
3353
+ firedHooks: ["review"],
3354
+ action: "replace",
3355
+ unitType: "run-uat",
3356
+ prompt: "review before executing",
3357
+ model: "review-model",
3358
+ }),
3359
+ });
3360
+ const loopState = {
3361
+ recentUnits: [
3362
+ { key: "execute-task/M001/S01/T01" },
3363
+ { key: "execute-task/M001/S01/T01" },
3364
+ ],
3365
+ stuckRecoveryAttempts: 1,
3366
+ consecutiveFinalizeTimeouts: 0,
3367
+ };
3368
+
3369
+ const result = await runDispatch(
3370
+ {
3371
+ ctx,
3372
+ pi,
3373
+ s,
3374
+ deps,
3375
+ prefs: undefined,
3376
+ iteration: 1,
3377
+ flowId: "test-flow",
3378
+ nextSeq: () => 1,
3379
+ },
3380
+ {
3381
+ state: {
3382
+ phase: "executing",
3383
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
3384
+ activeSlice: { id: "S01", title: "Slice 1" },
3385
+ activeTask: { id: "T01" },
3386
+ registry: [{ id: "M001", status: "active" }],
3387
+ blockers: [],
3388
+ } as any,
3389
+ mid: "M001",
3390
+ midTitle: "Test",
3391
+ },
3392
+ loopState,
3393
+ );
3394
+
3395
+ assert.equal(result.action, "next");
3396
+ assert.equal(result.data?.unitType, "run-uat");
3397
+ assert.equal(result.data?.finalPrompt, "review before executing");
3398
+ assert.equal(result.data?.hookModelOverride, "review-model");
3399
+ assert.ok(!deps.callLog.includes("stopAuto"), "replace hook should not stop on execute-task health");
3400
+ assert.deepEqual(
3401
+ loopState.recentUnits.map((u) => u.key),
3402
+ [
3403
+ "execute-task/M001/S01/T01",
3404
+ "execute-task/M001/S01/T01",
3405
+ "run-uat/M001/S01/T01",
3406
+ ],
3407
+ "stuck accounting should record the final replaced unit",
3408
+ );
3409
+ assert.ok(
3410
+ !notifications.some((n) => n.includes("Worktree health check failed") || n.includes("Stuck on execute-task")),
3411
+ "health and stuck notifications must use the final replaced unit",
2509
3412
  );
2510
3413
  });
2511
3414
 
@@ -2769,6 +3672,13 @@ test("autoLoop classifies ModelPolicyDispatchBlockedError as blocked, not a retr
2769
3672
  );
2770
3673
  assert.ok(unitEnd, "should emit unit-end with status=blocked");
2771
3674
  assert.equal(unitEnd!.data.reason, "model-policy-dispatch-blocked");
3675
+ const unitEndIndex = journalEvents.findIndex(
3676
+ e => e.eventType === "unit-end" && e.data?.status === "blocked",
3677
+ );
3678
+ const iterationEndIndex = journalEvents.findIndex(
3679
+ e => e.eventType === "iteration-end" && e.data?.status === "blocked",
3680
+ );
3681
+ assert.ok(iterationEndIndex > unitEndIndex, "blocked policy iterations must close after unit-end");
2772
3682
 
2773
3683
  // Loop must pause for manual attention, NOT retry until 3-strike hard stop.
2774
3684
  assert.equal(pauseAutoCalls, 1, "should pause once on policy block");