gsd-pi 2.80.0-dev.3065f7bfa → 2.80.0-dev.488219eb6

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 (376) hide show
  1. package/dist/claude-cli-check.d.ts +30 -0
  2. package/dist/claude-cli-check.js +18 -7
  3. package/dist/cli.js +0 -19
  4. package/dist/headless-query.d.ts +10 -0
  5. package/dist/headless-query.js +6 -4
  6. package/dist/loader-entrypoint.d.ts +8 -0
  7. package/dist/loader-entrypoint.js +27 -0
  8. package/dist/loader.js +2 -11
  9. package/dist/mcp-server.d.ts +1 -0
  10. package/dist/mcp-server.js +6 -3
  11. package/dist/resources/.managed-resources-content-hash +1 -1
  12. package/dist/resources/extensions/claude-code-cli/readiness.js +18 -7
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +29 -0
  14. package/dist/resources/extensions/github-sync/sync.js +4 -1
  15. package/dist/resources/extensions/gsd/auto/loop.js +71 -8
  16. package/dist/resources/extensions/gsd/auto/phases.js +32 -8
  17. package/dist/resources/extensions/gsd/auto/resolve.js +12 -0
  18. package/dist/resources/extensions/gsd/auto/run-unit.js +3 -1
  19. package/dist/resources/extensions/gsd/auto/workflow-dispatch-claim.js +33 -1
  20. package/dist/resources/extensions/gsd/auto/workflow-worker-heartbeat.js +9 -1
  21. package/dist/resources/extensions/gsd/auto-post-unit.js +81 -76
  22. package/dist/resources/extensions/gsd/auto-start.js +41 -6
  23. package/dist/resources/extensions/gsd/auto-worktree.js +98 -80
  24. package/dist/resources/extensions/gsd/auto.js +84 -29
  25. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +62 -13
  26. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +20 -26
  27. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +29 -12
  28. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +6 -9
  29. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +4 -7
  30. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +6 -10
  31. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +5 -9
  32. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +1 -1
  33. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +60 -26
  34. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +4 -0
  35. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -2
  36. package/dist/resources/extensions/gsd/commands-config.js +1 -1
  37. package/dist/resources/extensions/gsd/commands-eval-review.js +2 -2
  38. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -1
  39. package/dist/resources/extensions/gsd/db/unit-dispatches.js +53 -0
  40. package/dist/resources/extensions/gsd/git-service.js +37 -1
  41. package/dist/resources/extensions/gsd/guided-flow.js +14 -4
  42. package/dist/resources/extensions/gsd/init-wizard.js +4 -1
  43. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -4
  44. package/dist/resources/extensions/gsd/slice-cadence.js +45 -2
  45. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +15 -9
  46. package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -7
  47. package/dist/resources/extensions/gsd/tools/complete-task.js +1 -1
  48. package/dist/resources/extensions/gsd/worktree-resolver.js +22 -1
  49. package/dist/resources/skills/web-quality-audit/scripts/analyze.sh +0 -0
  50. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  51. package/dist/web/standalone/.next/BUILD_ID +1 -1
  52. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  53. package/dist/web/standalone/.next/build-manifest.json +3 -3
  54. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  55. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  56. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.html +1 -1
  75. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  76. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  77. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  78. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  83. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/middleware.js +3 -3
  86. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  87. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  88. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  89. package/dist/web/standalone/.next/static/chunks/8359.e059d86b255fce1c.js +10 -0
  90. package/dist/web/standalone/.next/static/chunks/app/{page-fab3ebb85b006001.js → page-752f1e2ebdaa3e45.js} +1 -1
  91. package/dist/web/standalone/.next/static/chunks/{webpack-0481f1221120a7c6.js → webpack-de742b64187e13fe.js} +1 -1
  92. package/dist/welcome-screen.d.ts +2 -0
  93. package/dist/welcome-screen.js +9 -7
  94. package/package.json +1 -1
  95. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +35 -13
  96. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  97. package/packages/pi-ai/dist/providers/anthropic-bearer-auth.test.js +21 -11
  98. package/packages/pi-ai/dist/providers/anthropic-bearer-auth.test.js.map +1 -1
  99. package/packages/pi-ai/dist/providers/anthropic.d.ts +7 -0
  100. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  101. package/packages/pi-ai/dist/providers/anthropic.js +9 -7
  102. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  103. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +23 -14
  104. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -1
  105. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +48 -21
  106. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -1
  107. package/packages/pi-ai/dist/utils/oauth/google-antigravity.test.js +22 -21
  108. package/packages/pi-ai/dist/utils/oauth/google-antigravity.test.js.map +1 -1
  109. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.test.js +22 -21
  110. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.test.js.map +1 -1
  111. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +39 -25
  112. package/packages/pi-ai/src/providers/anthropic-bearer-auth.test.ts +26 -22
  113. package/packages/pi-ai/src/providers/anthropic.ts +22 -9
  114. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +34 -21
  115. package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +56 -22
  116. package/packages/pi-ai/src/utils/oauth/google-antigravity.test.ts +24 -28
  117. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.test.ts +24 -28
  118. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  119. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
  120. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -2
  122. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +22 -56
  125. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +1 -0
  127. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/tools/bash.js +3 -1
  129. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts +2 -0
  131. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/tools/edit.js +12 -1
  133. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/tools/find.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/tools/find.js +14 -6
  137. package/packages/pi-coding-agent/dist/core/tools/find.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/tools/grep.d.ts +2 -0
  139. package/packages/pi-coding-agent/dist/core/tools/grep.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/tools/grep.js +12 -3
  141. package/packages/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/tools/hashline-read.d.ts +2 -0
  143. package/packages/pi-coding-agent/dist/core/tools/hashline-read.d.ts.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/tools/hashline-read.js +3 -1
  145. package/packages/pi-coding-agent/dist/core/tools/hashline-read.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -1
  147. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/core/tools/index.js +1 -0
  149. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/tools/ls.d.ts +2 -0
  151. package/packages/pi-coding-agent/dist/core/tools/ls.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/tools/ls.js +10 -3
  153. package/packages/pi-coding-agent/dist/core/tools/ls.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/tools/read.d.ts +2 -0
  155. package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/tools/read.js +3 -1
  157. package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.js +7 -62
  159. package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.js.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/tools/tool-target-metadata.test.d.ts +2 -0
  161. package/packages/pi-coding-agent/dist/core/tools/tool-target-metadata.test.d.ts.map +1 -0
  162. package/packages/pi-coding-agent/dist/core/tools/tool-target-metadata.test.js +115 -0
  163. package/packages/pi-coding-agent/dist/core/tools/tool-target-metadata.test.js.map +1 -0
  164. package/packages/pi-coding-agent/dist/core/tools/tool-target.d.ts +19 -0
  165. package/packages/pi-coding-agent/dist/core/tools/tool-target.d.ts.map +1 -0
  166. package/packages/pi-coding-agent/dist/core/tools/tool-target.js +20 -0
  167. package/packages/pi-coding-agent/dist/core/tools/tool-target.js.map +1 -0
  168. package/packages/pi-coding-agent/dist/core/tools/write.d.ts +4 -0
  169. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  170. package/packages/pi-coding-agent/dist/core/tools/write.js +9 -1
  171. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  172. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +89 -2
  173. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +25 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +5 -0
  177. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +153 -8
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +18 -1
  182. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +23 -5
  184. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
  186. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +19 -0
  188. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +23 -5
  190. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -1
  191. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
  192. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
  193. package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +22 -66
  194. package/packages/pi-coding-agent/src/core/tools/bash.ts +4 -1
  195. package/packages/pi-coding-agent/src/core/tools/edit.ts +13 -1
  196. package/packages/pi-coding-agent/src/core/tools/find.ts +15 -6
  197. package/packages/pi-coding-agent/src/core/tools/grep.ts +13 -3
  198. package/packages/pi-coding-agent/src/core/tools/hashline-read.ts +4 -1
  199. package/packages/pi-coding-agent/src/core/tools/index.ts +8 -0
  200. package/packages/pi-coding-agent/src/core/tools/ls.ts +11 -3
  201. package/packages/pi-coding-agent/src/core/tools/read.ts +4 -1
  202. package/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts +11 -72
  203. package/packages/pi-coding-agent/src/core/tools/tool-target-metadata.test.ts +127 -0
  204. package/packages/pi-coding-agent/src/core/tools/tool-target.ts +48 -0
  205. package/packages/pi-coding-agent/src/core/tools/write.ts +14 -2
  206. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +119 -2
  207. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +31 -1
  208. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +177 -8
  209. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +24 -5
  210. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +17 -1
  211. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +21 -0
  212. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +25 -8
  213. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  214. package/src/resources/extensions/claude-code-cli/readiness.ts +25 -7
  215. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +30 -0
  216. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +26 -0
  217. package/src/resources/extensions/github-sync/sync.ts +8 -1
  218. package/src/resources/extensions/github-sync/tests/sync-source.test.ts +6 -18
  219. package/src/resources/extensions/gsd/auto/loop.ts +84 -8
  220. package/src/resources/extensions/gsd/auto/phases.ts +43 -8
  221. package/src/resources/extensions/gsd/auto/resolve.ts +19 -0
  222. package/src/resources/extensions/gsd/auto/run-unit.ts +3 -0
  223. package/src/resources/extensions/gsd/auto/workflow-dispatch-claim.ts +63 -1
  224. package/src/resources/extensions/gsd/auto/workflow-worker-heartbeat.ts +14 -1
  225. package/src/resources/extensions/gsd/auto-post-unit.ts +93 -78
  226. package/src/resources/extensions/gsd/auto-start.ts +56 -6
  227. package/src/resources/extensions/gsd/auto-worktree.ts +118 -72
  228. package/src/resources/extensions/gsd/auto.ts +121 -29
  229. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +76 -12
  230. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +20 -26
  231. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +27 -12
  232. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +7 -9
  233. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +4 -7
  234. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +6 -10
  235. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +5 -9
  236. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +1 -1
  237. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +64 -26
  238. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +4 -0
  239. package/src/resources/extensions/gsd/commands/handlers/ops.ts +4 -2
  240. package/src/resources/extensions/gsd/commands-config.ts +1 -1
  241. package/src/resources/extensions/gsd/commands-eval-review.ts +2 -2
  242. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -1
  243. package/src/resources/extensions/gsd/db/unit-dispatches.ts +66 -0
  244. package/src/resources/extensions/gsd/git-service.ts +41 -2
  245. package/src/resources/extensions/gsd/guided-flow.ts +19 -4
  246. package/src/resources/extensions/gsd/init-wizard.ts +5 -1
  247. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -4
  248. package/src/resources/extensions/gsd/slice-cadence.ts +49 -2
  249. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +23 -9
  250. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +59 -89
  251. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +47 -172
  252. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +0 -35
  253. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +35 -9
  254. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +15 -0
  255. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +80 -59
  256. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +3 -47
  257. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +76 -18
  258. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +106 -0
  259. package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +54 -95
  260. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +67 -26
  261. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +32 -30
  262. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +32 -128
  263. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +20 -54
  264. package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +20 -30
  265. package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +17 -29
  266. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +138 -0
  267. package/src/resources/extensions/gsd/tests/auto-start-time-persistence.test.ts +21 -39
  268. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +15 -24
  269. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +44 -29
  270. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +39 -51
  271. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +1 -217
  272. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +15 -32
  273. package/src/resources/extensions/gsd/tests/browser-teardown.test.ts +0 -41
  274. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +34 -27
  275. package/src/resources/extensions/gsd/tests/cmux.test.ts +51 -53
  276. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +39 -61
  277. package/src/resources/extensions/gsd/tests/commands-config.test.ts +26 -19
  278. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  279. package/src/resources/extensions/gsd/tests/complete-task-normalize-lists.test.ts +29 -33
  280. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +45 -108
  281. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +35 -31
  282. package/src/resources/extensions/gsd/tests/cwd-fallback-hardening.test.ts +138 -0
  283. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +4 -68
  284. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +4 -9
  285. package/src/resources/extensions/gsd/tests/deferred-milestone-dir-4996.test.ts +14 -65
  286. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +44 -37
  287. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +58 -40
  288. package/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts +25 -15
  289. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +35 -17
  290. package/src/resources/extensions/gsd/tests/error-success-mask.test.ts +16 -21
  291. package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +15 -82
  292. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +2 -20
  293. package/src/resources/extensions/gsd/tests/frontmatter-parse-noise.test.ts +18 -26
  294. package/src/resources/extensions/gsd/tests/init-skip-git.test.ts +9 -12
  295. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +40 -0
  296. package/src/resources/extensions/gsd/tests/integration/commands-eval-review.integration.test.ts +4 -2
  297. package/src/resources/extensions/gsd/tests/integration/git-locale.test.ts +31 -20
  298. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +12 -0
  299. package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +0 -47
  300. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +60 -202
  301. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +13 -59
  302. package/src/resources/extensions/gsd/tests/lazy-pi-tui-import.test.ts +44 -6
  303. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +21 -35
  304. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +79 -98
  305. package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +70 -278
  306. package/src/resources/extensions/gsd/tests/needs-remediation-revalidation.test.ts +37 -30
  307. package/src/resources/extensions/gsd/tests/note-captures-executed.test.ts +32 -28
  308. package/src/resources/extensions/gsd/tests/phantom-ghost-detection.test.ts +24 -37
  309. package/src/resources/extensions/gsd/tests/phantom-milestone-default-queued.test.ts +9 -24
  310. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +113 -0
  311. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +36 -22
  312. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +36 -30
  313. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +74 -4
  314. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +20 -22
  315. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +130 -32
  316. package/src/resources/extensions/gsd/tests/project-root-cwd-crash.test.ts +18 -36
  317. package/src/resources/extensions/gsd/tests/projection-no-plan-overwrite.test.ts +35 -73
  318. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +76 -138
  319. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +70 -125
  320. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +59 -161
  321. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +33 -29
  322. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +22 -196
  323. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +23 -93
  324. package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +50 -79
  325. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +27 -13
  326. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +151 -251
  327. package/src/resources/extensions/gsd/tests/resource-loader-import-path.test.ts +41 -29
  328. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +58 -90
  329. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +32 -164
  330. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +57 -41
  331. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +14 -9
  332. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +77 -0
  333. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +61 -1
  334. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +44 -42
  335. package/src/resources/extensions/gsd/tests/skip-slice-state-rebuild.test.ts +56 -24
  336. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +51 -11
  337. package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +23 -0
  338. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +66 -50
  339. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +68 -107
  340. package/src/resources/extensions/gsd/tests/slice-sequence-insert.test.ts +115 -42
  341. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +21 -97
  342. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +25 -116
  343. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +21 -57
  344. package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +29 -76
  345. package/src/resources/extensions/gsd/tests/stale-lockfile-recovery.test.ts +33 -24
  346. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +39 -30
  347. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +33 -47
  348. package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +48 -46
  349. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +14 -102
  350. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +78 -232
  351. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +32 -35
  352. package/src/resources/extensions/gsd/tests/token-profile.test.ts +84 -309
  353. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +134 -341
  354. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +8 -25
  355. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +50 -1
  356. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +6 -101
  357. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +43 -36
  358. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -444
  359. package/src/resources/extensions/gsd/tests/workflow-dispatch-claim.test.ts +142 -0
  360. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +44 -189
  361. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -57
  362. package/src/resources/extensions/gsd/tests/workflow-worker-heartbeat.test.ts +32 -1
  363. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +21 -44
  364. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +27 -26
  365. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +14 -13
  366. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +20 -18
  367. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +36 -0
  368. package/src/resources/extensions/gsd/tests/zero-slice-roadmap-guided.test.ts +19 -13
  369. package/src/resources/extensions/gsd/tools/complete-milestone.ts +14 -15
  370. package/src/resources/extensions/gsd/tools/complete-task.ts +1 -1
  371. package/src/resources/extensions/gsd/worktree-resolver.ts +34 -1
  372. package/src/resources/skills/create-gsd-extension/templates/templates.test.ts +86 -40
  373. package/src/resources/skills/web-quality-audit/scripts/analyze.sh +0 -0
  374. package/dist/web/standalone/.next/static/chunks/8336.631939fb583761fa.js +0 -10
  375. /package/dist/web/standalone/.next/static/{O2WvnXROue6l7nAp_4dhC → G68KoLv5claox_Dyd4pZn}/_buildManifest.js +0 -0
  376. /package/dist/web/standalone/.next/static/{O2WvnXROue6l7nAp_4dhC → G68KoLv5claox_Dyd4pZn}/_ssgManifest.js +0 -0
@@ -25,11 +25,20 @@ import { execFileSync } from "node:child_process";
25
25
  * we invoke `cmd /c <command> <args...>` explicitly. On POSIX we don't
26
26
  * need a shell at all.
27
27
  */
28
- function spawnClaude(command: string, args: string[], opts: { timeout: number; stdio: "pipe" }): Buffer {
29
- if (process.platform === "win32") {
30
- return execFileSync("cmd", ["/c", command, ...args], opts);
28
+ export function buildClaudeSpawnInvocation(
29
+ command: string,
30
+ args: string[],
31
+ platform: NodeJS.Platform = process.platform,
32
+ ): { command: string; args: string[] } {
33
+ if (platform === "win32") {
34
+ return { command: "cmd", args: ["/c", command, ...args] };
31
35
  }
32
- return execFileSync(command, args, opts);
36
+ return { command, args };
37
+ }
38
+
39
+ function spawnClaude(command: string, args: string[], opts: { timeout: number; stdio: "pipe" }): Buffer {
40
+ const invocation = buildClaudeSpawnInvocation(command, args);
41
+ return execFileSync(invocation.command, invocation.args, opts);
33
42
  }
34
43
 
35
44
  /**
@@ -38,7 +47,11 @@ function spawnClaude(command: string, args: string[], opts: { timeout: number; s
38
47
  * Keep the explicit win32 ternary selector for regression coverage (Issue #4424):
39
48
  * Node's execFileSync must target `claude.cmd` directly on Windows.
40
49
  */
41
- const CLAUDE_COMMAND = process.platform === "win32" ? "claude.cmd" : "claude";
50
+ export function getClaudeCommand(platform: NodeJS.Platform = process.platform): string {
51
+ return platform === "win32" ? "claude.cmd" : "claude";
52
+ }
53
+
54
+ const CLAUDE_COMMAND = getClaudeCommand();
42
55
 
43
56
  /**
44
57
  * Windows installs vary: some environments expose `claude.cmd` (npm shim),
@@ -46,7 +59,12 @@ const CLAUDE_COMMAND = process.platform === "win32" ? "claude.cmd" : "claude";
46
59
  * (for example Git Bash wrappers). Try all three to avoid false "not
47
60
  * installed" results in readiness checks.
48
61
  */
49
- const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude.exe", "claude"] : [CLAUDE_COMMAND];
62
+ export function getClaudeCommandCandidates(platform: NodeJS.Platform = process.platform): string[] {
63
+ const command = getClaudeCommand(platform);
64
+ return platform === "win32" ? [command, "claude.exe", "claude"] : [command];
65
+ }
66
+
67
+ const CLAUDE_COMMAND_CANDIDATES = getClaudeCommandCandidates();
50
68
 
51
69
  // Keep the version probe snappy — `claude --version` is a quick path.
52
70
  const VERSION_TIMEOUT_MS = 5_000;
@@ -97,7 +115,7 @@ function findWorkingCommand(): string | null {
97
115
  * back to a text heuristic. Note: the text heuristic only covers English
98
116
  * phrasing — the JSON path is the durable signal.
99
117
  */
100
- function parseAuthStatus(output: string): boolean | null {
118
+ export function parseAuthStatus(output: string): boolean | null {
101
119
  const trimmed = output.trim();
102
120
  if (!trimmed) return null;
103
121
 
@@ -418,6 +418,27 @@ function makeErrorMessage(model: string, errorMsg: string): AssistantMessage {
418
418
  };
419
419
  }
420
420
 
421
+ export function isClaudeCodeAbortErrorMessage(message: string | undefined | null): boolean {
422
+ if (!message) return false;
423
+ return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user)\b/i.test(message);
424
+ }
425
+
426
+ function isBareClaudeCodeAbortErrorMessage(message: string | undefined | null): boolean {
427
+ if (!message) return false;
428
+ const normalized = message.trim().replace(/\s+/g, " ").toLowerCase();
429
+ return normalized === "claude code process aborted by user"
430
+ || normalized === "request aborted by user"
431
+ || normalized === "process aborted by user";
432
+ }
433
+
434
+ export function resolveClaudeCodeAbortedMessageText(errorMsg: string, lastTextContent: string): string {
435
+ const trimmedError = errorMsg.trim();
436
+ if (trimmedError && !isBareClaudeCodeAbortErrorMessage(trimmedError)) {
437
+ return trimmedError;
438
+ }
439
+ return lastTextContent;
440
+ }
441
+
421
442
  /**
422
443
  * Generator exhaustion without a terminal result means the SDK stream was
423
444
  * interrupted mid-turn. Surface it as an error so downstream recovery logic
@@ -1840,6 +1861,15 @@ async function pumpSdkMessages(
1840
1861
  stream.push({ type: "error", reason: "error", error: fallback });
1841
1862
  } catch (err) {
1842
1863
  const errorMsg = err instanceof Error ? err.message : String(err);
1864
+ if (options?.signal?.aborted || isClaudeCodeAbortErrorMessage(errorMsg)) {
1865
+ const abortedText = resolveClaudeCodeAbortedMessageText(errorMsg, lastTextContent);
1866
+ stream.push({
1867
+ type: "error",
1868
+ reason: "aborted",
1869
+ error: makeAbortedMessage(modelId, abortedText),
1870
+ });
1871
+ return;
1872
+ }
1843
1873
  stream.push({
1844
1874
  type: "error",
1845
1875
  reason: "error",
@@ -5,6 +5,8 @@ import { join, resolve } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import {
7
7
  makeStreamExhaustedErrorMessage,
8
+ isClaudeCodeAbortErrorMessage,
9
+ resolveClaudeCodeAbortedMessageText,
8
10
  getResultErrorMessage,
9
11
  makeAbortedMessage,
10
12
  mergePendingToolCalls,
@@ -1212,6 +1214,12 @@ describe("stream-adapter — MCP elicitation bridge", () => {
1212
1214
  // ---------------------------------------------------------------------------
1213
1215
 
1214
1216
  describe("stream-adapter — abort classification (F2)", () => {
1217
+ test("recognizes Claude Code SDK abort exceptions", () => {
1218
+ assert.equal(isClaudeCodeAbortErrorMessage("Claude Code process aborted by user"), true);
1219
+ assert.equal(isClaudeCodeAbortErrorMessage("Request aborted by user"), true);
1220
+ assert.equal(isClaudeCodeAbortErrorMessage("rate limit exceeded"), false);
1221
+ });
1222
+
1215
1223
  test("makeAbortedMessage sets stopReason to 'aborted', not 'error'", () => {
1216
1224
  const message = makeAbortedMessage("claude-sonnet-4-6", "");
1217
1225
  assert.equal(message.stopReason, "aborted");
@@ -1229,6 +1237,24 @@ describe("stream-adapter — abort classification (F2)", () => {
1229
1237
  assert.notEqual(aborted.stopReason, exhausted.stopReason);
1230
1238
  assert.equal(exhausted.errorMessage, "stream_exhausted_without_result");
1231
1239
  });
1240
+
1241
+ test("abort catch preserves SDK diagnostic text instead of partial output", () => {
1242
+ const text = resolveClaudeCodeAbortedMessageText(
1243
+ "Request aborted by user\nAPI Error: 529 overloaded",
1244
+ "partial mid-stream text",
1245
+ );
1246
+
1247
+ assert.equal(text, "Request aborted by user\nAPI Error: 529 overloaded");
1248
+ });
1249
+
1250
+ test("abort catch falls back to partial output for bare abort markers", () => {
1251
+ const text = resolveClaudeCodeAbortedMessageText(
1252
+ "Request aborted by user",
1253
+ "partial mid-stream text",
1254
+ );
1255
+
1256
+ assert.equal(text, "partial mid-stream text");
1257
+ });
1232
1258
  });
1233
1259
 
1234
1260
  // ---------------------------------------------------------------------------
@@ -122,6 +122,13 @@ export async function runGitHubSync(
122
122
  }
123
123
  }
124
124
 
125
+ export function shouldCreateSlicePrForSyncEvent(
126
+ unitType: string,
127
+ config: Pick<GitHubSyncConfig, "slice_prs">,
128
+ ): boolean {
129
+ return unitType === "complete-slice" && config.slice_prs !== false;
130
+ }
131
+
125
132
  // ─── Per-Event Sync Functions ───────────────────────────────────────────────
126
133
 
127
134
  async function syncMilestonePlan(
@@ -406,7 +413,7 @@ async function syncSliceComplete(
406
413
  sliceRecord = getSliceRecord(mapping, mid, sid);
407
414
  }
408
415
  if (!sliceRecord || sliceRecord.state === "closed") return;
409
- if (!sliceRecord.prNumber && config.slice_prs !== false) {
416
+ if (!sliceRecord.prNumber && shouldCreateSlicePrForSyncEvent("complete-slice", config)) {
410
417
  await ensureSlicePullRequest(basePath, mapping, mid, sid);
411
418
  sliceRecord = getSliceRecord(mapping, mid, sid);
412
419
  if (!sliceRecord || !sliceRecord.prNumber) return;
@@ -1,26 +1,14 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { readFileSync } from "node:fs";
4
- import { join } from "node:path";
5
-
6
- const source = readFileSync(join(import.meta.dirname, "..", "sync.ts"), "utf8");
3
+ import { shouldCreateSlicePrForSyncEvent } from "../sync.ts";
7
4
 
8
5
  test("slice plan sync records issues without creating a draft PR", () => {
9
- const planStart = source.indexOf("async function syncSlicePlan");
10
- const prHelperStart = source.indexOf("async function ensureSlicePullRequest");
11
- assert.ok(planStart >= 0 && prHelperStart > planStart, "slice plan and PR helper functions are present");
12
-
13
- const planBody = source.slice(planStart, prHelperStart);
14
- assert.match(planBody, /prNumber:\s*0/);
15
- assert.doesNotMatch(planBody, /ghCreatePR\(/);
16
- assert.doesNotMatch(planBody, /ghPushBranch\(/);
6
+ assert.equal(shouldCreateSlicePrForSyncEvent("plan-slice", { slice_prs: true }), false);
7
+ assert.equal(shouldCreateSlicePrForSyncEvent("research-slice", { slice_prs: true }), false);
17
8
  });
18
9
 
19
10
  test("slice completion creates the PR only after slice work is complete", () => {
20
- const completeStart = source.indexOf("async function syncSliceComplete");
21
- const milestoneStart = source.indexOf("async function syncMilestoneComplete");
22
- assert.ok(completeStart >= 0 && milestoneStart > completeStart, "slice complete function is present");
23
-
24
- const completeBody = source.slice(completeStart, milestoneStart);
25
- assert.match(completeBody, /ensureSlicePullRequest\(basePath, mapping, mid, sid\)/);
11
+ assert.equal(shouldCreateSlicePrForSyncEvent("complete-slice", { slice_prs: true }), true);
12
+ assert.equal(shouldCreateSlicePrForSyncEvent("complete-slice", {}), true);
13
+ assert.equal(shouldCreateSlicePrForSyncEvent("complete-slice", { slice_prs: false }), false);
26
14
  });
@@ -38,7 +38,7 @@ import {
38
38
  getRecentForUnit as getRecentDispatchesForUnit,
39
39
  getRecentUnitKeysForProjectRoot,
40
40
  } from "../db/unit-dispatches.js";
41
- import { refreshMilestoneLease } from "../db/milestone-leases.js";
41
+ import { claimMilestoneLease, refreshMilestoneLease } from "../db/milestone-leases.js";
42
42
  import { heartbeatAutoWorker } from "../db/auto-workers.js";
43
43
  import { getRuntimeKv, setRuntimeKv } from "../db/runtime-kv.js";
44
44
  import { resolveUokFlags } from "../uok/flags.js";
@@ -70,7 +70,7 @@ import {
70
70
  } from "./workflow-dispatch-ledger.js";
71
71
  import { emitOpenUnitEndForUnit } from "../crash-recovery.js";
72
72
  import { writeUnitRuntimeRecord } from "../unit-runtime.js";
73
- import { openDispatchClaim } from "./workflow-dispatch-claim.js";
73
+ import { ensureDispatchLease, openDispatchClaim } from "./workflow-dispatch-claim.js";
74
74
  import { completeWorkflowIteration } from "./workflow-iteration-completion.js";
75
75
  import { createWorkflowJournalReporter } from "./workflow-journal-reporter.js";
76
76
  import { createWorkflowPhaseReporter } from "./workflow-phase-reporter.js";
@@ -165,6 +165,29 @@ function logDispatchClaimFailed(err: unknown): void {
165
165
  });
166
166
  }
167
167
 
168
+ function logDispatchLeaseRecovered(details: {
169
+ milestoneId: string;
170
+ workerId: string;
171
+ token: number;
172
+ recovered: boolean;
173
+ }): void {
174
+ debugLog("autoLoop", {
175
+ phase: details.recovered ? "dispatch-lease-recovered" : "dispatch-lease-acquired",
176
+ ...details,
177
+ });
178
+ }
179
+
180
+ function logDispatchLeaseRecoveryFailed(details: {
181
+ milestoneId?: string;
182
+ workerId?: string;
183
+ reason: string;
184
+ }): void {
185
+ debugLog("autoLoop", {
186
+ phase: "dispatch-lease-recovery-failed",
187
+ ...details,
188
+ });
189
+ }
190
+
168
191
  function logCustomVerifyRetryLoadFailure(err: unknown): void {
169
192
  debugLog("autoLoop", {
170
193
  phase: "load-custom-verify-retries-failed",
@@ -274,6 +297,10 @@ export async function autoLoop(
274
297
  phase: "heartbeat-failed",
275
298
  error: err instanceof Error ? err.message : String(err),
276
299
  }),
300
+ logLeaseRefreshMiss: details => debugLog("autoLoop", {
301
+ phase: "lease-refresh-missed",
302
+ ...details,
303
+ }),
277
304
  });
278
305
 
279
306
  // ── Journal: per-iteration flow grouping ──
@@ -703,25 +730,74 @@ export async function autoLoop(
703
730
 
704
731
  // Phase B: claim a unit_dispatches row before invoking the unit. The
705
732
  // partial unique index idx_unit_dispatches_active_per_unit prevents
706
- // a second worker from claiming the same unit concurrently. Returns
707
- // null when DB unavailable, no worker registered, or no active lease
708
- // those degraded paths fall through to the existing single-worker
709
- // semantics with no ledger entry, preserving back-compat.
710
- const dispatchClaim = openDispatchClaim(s, flowId, turnId, iterData, {
733
+ // a second worker from claiming the same unit concurrently. When this
734
+ // process has a worker identity, make the milestone lease explicit before
735
+ // claiming so a step-mode handoff cannot leave us running with a stale
736
+ // in-memory token and no backing lease row.
737
+ const leaseBeforeClaim = ensureDispatchLease(s, iterData.mid, {
738
+ claimMilestoneLease,
739
+ logLeaseRecovered: logDispatchLeaseRecovered,
740
+ logLeaseRecoveryFailed: logDispatchLeaseRecoveryFailed,
741
+ });
742
+ if (leaseBeforeClaim.kind === "blocked" || leaseBeforeClaim.kind === "failed") {
743
+ const msg = `Lost milestone lease for ${iterData.mid ?? "unknown"} before dispatching ${iterData.unitType} ${iterData.unitId}: ${leaseBeforeClaim.reason}`;
744
+ ctx.ui.notify(msg, "error");
745
+ finishTurn("stopped", "execution", msg);
746
+ await deps.stopAuto(ctx, pi, msg);
747
+ break;
748
+ }
749
+
750
+ let dispatchClaim = openDispatchClaim(s, flowId, turnId, iterData, {
711
751
  getRecentDispatchesForUnit,
712
752
  recordDispatchClaim,
713
753
  markDispatchRunning,
714
754
  logClaimRejected: logDispatchClaimRejected,
715
755
  logClaimFailed: logDispatchClaimFailed,
716
756
  });
717
- const dispatchDecision = decideDispatchClaim(
757
+ let dispatchDecision = decideDispatchClaim(
718
758
  dispatchClaim.kind === "opened"
719
759
  ? { kind: "opened", dispatchId: dispatchClaim.dispatchId }
720
760
  : dispatchClaim.kind === "skip"
721
761
  ? { kind: "skip", reason: dispatchClaim.reason }
722
762
  : { kind: "degraded" },
723
763
  );
764
+ if (dispatchDecision.action === "skip" && dispatchDecision.reason === "stale-lease") {
765
+ const leaseRecovery = ensureDispatchLease(s, iterData.mid, {
766
+ claimMilestoneLease,
767
+ logLeaseRecovered: logDispatchLeaseRecovered,
768
+ logLeaseRecoveryFailed: logDispatchLeaseRecoveryFailed,
769
+ }, { forceReclaim: true });
770
+ if (leaseRecovery.kind === "ready") {
771
+ dispatchClaim = openDispatchClaim(s, flowId, turnId, iterData, {
772
+ getRecentDispatchesForUnit,
773
+ recordDispatchClaim,
774
+ markDispatchRunning,
775
+ logClaimRejected: logDispatchClaimRejected,
776
+ logClaimFailed: logDispatchClaimFailed,
777
+ });
778
+ dispatchDecision = decideDispatchClaim(
779
+ dispatchClaim.kind === "opened"
780
+ ? { kind: "opened", dispatchId: dispatchClaim.dispatchId }
781
+ : dispatchClaim.kind === "skip"
782
+ ? { kind: "skip", reason: dispatchClaim.reason }
783
+ : { kind: "degraded" },
784
+ );
785
+ } else {
786
+ const msg = `Lost milestone lease for ${iterData.mid ?? "unknown"} while claiming ${iterData.unitType} ${iterData.unitId}: ${leaseRecovery.reason}`;
787
+ ctx.ui.notify(msg, "error");
788
+ finishTurn("stopped", "execution", msg);
789
+ await deps.stopAuto(ctx, pi, msg);
790
+ break;
791
+ }
792
+ }
724
793
  if (dispatchDecision.action === "skip") {
794
+ if (dispatchDecision.reason === "stale-lease") {
795
+ const msg = `Lost milestone lease for ${iterData.mid ?? "unknown"} while claiming ${iterData.unitType} ${iterData.unitId}; dispatch claim still failed after recovery.`;
796
+ ctx.ui.notify(msg, "error");
797
+ finishTurn("stopped", "execution", msg);
798
+ await deps.stopAuto(ctx, pi, msg);
799
+ break;
800
+ }
725
801
  finishTurn("skipped", "execution", dispatchDecision.reason);
726
802
  continue;
727
803
  }
@@ -1,3 +1,5 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Auto-loop pipeline phases, merge closeout, and finalize handling.
1
3
  /**
2
4
  * auto/phases.ts — Pipeline phases for the auto-loop.
3
5
  *
@@ -126,10 +128,23 @@ const PLAN_V2_GATE_PHASES: ReadonlySet<Phase> = new Set([
126
128
  "completing-milestone",
127
129
  ]);
128
130
 
129
- function shouldRunPlanV2Gate(phase: Phase): boolean {
131
+ export function shouldRunPlanV2Gate(phase: Phase): boolean {
130
132
  return PLAN_V2_GATE_PHASES.has(phase);
131
133
  }
132
134
 
135
+ export function _shouldProceedWithInvalidRepoClassificationForTest(
136
+ reason: string | undefined,
137
+ hasGit: boolean,
138
+ ): boolean {
139
+ return reason === "missing .git" && hasGit;
140
+ }
141
+
142
+ export function _resolveCurrentUnitStartedAtForTest(
143
+ currentUnit: { startedAt: number } | null | undefined,
144
+ ): number | undefined {
145
+ return currentUnit?.startedAt;
146
+ }
147
+
133
148
  /**
134
149
  * Generate and write an HTML milestone report snapshot.
135
150
  * Extracted from the milestone-transition block in autoLoop.
@@ -332,6 +347,21 @@ export async function _runMilestoneMergeWithStashRestore(
332
347
  return null;
333
348
  }
334
349
 
350
+ export async function _runMilestoneMergeOnceWithStashRestore(
351
+ ic: IterationContext,
352
+ milestoneId: string,
353
+ ): Promise<{ action: "break"; reason: string } | null> {
354
+ if (ic.s.milestoneMergedInPhases) {
355
+ debugLog("autoLoop", {
356
+ phase: "milestone-merge-skip",
357
+ reason: "already-merged-in-phases",
358
+ milestoneId,
359
+ });
360
+ return null;
361
+ }
362
+ return _runMilestoneMergeWithStashRestore(ic, milestoneId);
363
+ }
364
+
335
365
  async function emitCancelledUnitEnd(
336
366
  ic: IterationContext,
337
367
  unitType: string,
@@ -771,7 +801,7 @@ export async function runPreDispatch(
771
801
  // Worktree lifecycle on milestone transition — merge current, enter next.
772
802
  // #2909 / #5538-followup: preflight stash + always-on postflight pop.
773
803
  {
774
- const stop = await _runMilestoneMergeWithStashRestore(ic, s.currentMilestoneId!);
804
+ const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId!);
775
805
  if (stop) return stop;
776
806
  }
777
807
 
@@ -853,7 +883,7 @@ export async function runPreDispatch(
853
883
  // All milestones complete — merge milestone branch before stopping.
854
884
  if (s.currentMilestoneId) {
855
885
  // #2909 / #5538-followup: preflight stash + always-on postflight pop.
856
- const stop = await _runMilestoneMergeWithStashRestore(ic, s.currentMilestoneId);
886
+ const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
857
887
  if (stop) return stop;
858
888
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
859
889
  }
@@ -948,7 +978,7 @@ export async function runPreDispatch(
948
978
  // Milestone merge on complete (before closeout so branch state is clean).
949
979
  if (s.currentMilestoneId) {
950
980
  // #2909 / #5538-followup: preflight stash + always-on postflight pop.
951
- const stop = await _runMilestoneMergeWithStashRestore(ic, s.currentMilestoneId);
981
+ const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
952
982
  if (stop) return stop;
953
983
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
954
984
  }
@@ -1553,7 +1583,7 @@ export async function runUnitPhase(
1553
1583
  if (projectClassification.kind === "invalid-repo") {
1554
1584
  const msg = `Worktree health check failed: ${s.basePath} classified as invalid-repo (${projectClassification.reason}) — refusing to dispatch ${unitType} ${unitId}`;
1555
1585
  debugLog("runUnitPhase", { phase: "worktree-health-invalid-repo", basePath: s.basePath, classification: projectClassification });
1556
- if (projectClassification.reason === "missing .git" && hasGit) {
1586
+ if (_shouldProceedWithInvalidRepoClassificationForTest(projectClassification.reason, hasGit)) {
1557
1587
  ctx.ui.notify(
1558
1588
  `Warning: ${s.basePath} project classification could not confirm .git; assuming it has no project content yet — proceeding as greenfield project because worktree health reported .git present`,
1559
1589
  "warning",
@@ -2068,7 +2098,7 @@ export async function runUnitPhase(
2068
2098
  const currentLedger = deps.getLedger() as { units: Array<{ type: string; id: string; startedAt: number; toolCalls: number }> } | null;
2069
2099
  if (currentLedger?.units) {
2070
2100
  const lastUnit = [...currentLedger.units].reverse().find(
2071
- (u: { type: string; id: string; startedAt: number; toolCalls: number }) => u.type === unitType && u.id === unitId && u.startedAt === s.currentUnit?.startedAt,
2101
+ (u: { type: string; id: string; startedAt: number; toolCalls: number }) => u.type === unitType && u.id === unitId && u.startedAt === _resolveCurrentUnitStartedAtForTest(s.currentUnit),
2072
2102
  );
2073
2103
  if (lastUnit && lastUnit.toolCalls === 0) {
2074
2104
  if (USER_DRIVEN_DEEP_UNITS.has(unitType) && isAwaitingUserInput(s.lastUnitAgentEndMessages ?? undefined)) {
@@ -2090,7 +2120,7 @@ export async function runUnitPhase(
2090
2120
  );
2091
2121
  // Fall through to next iteration where dispatch will re-derive
2092
2122
  // and re-dispatch this unit.
2093
- return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt, requestDispatchedAt: unitResult.requestDispatchedAt } };
2123
+ return { action: "next", data: { unitStartedAt: _resolveCurrentUnitStartedAtForTest(s.currentUnit), requestDispatchedAt: unitResult.requestDispatchedAt } };
2094
2124
  }
2095
2125
  }
2096
2126
  }
@@ -2155,7 +2185,7 @@ export async function runUnitPhase(
2155
2185
  s.checkpointSha = null;
2156
2186
  }
2157
2187
 
2158
- return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt, requestDispatchedAt: unitResult.requestDispatchedAt } };
2188
+ return { action: "next", data: { unitStartedAt: _resolveCurrentUnitStartedAtForTest(s.currentUnit), requestDispatchedAt: unitResult.requestDispatchedAt } };
2159
2189
  }
2160
2190
 
2161
2191
  // ─── runFinalize ──────────────────────────────────────────────────────────────
@@ -2335,6 +2365,11 @@ export async function runFinalize(
2335
2365
  return { action: "break", reason: "step-wizard" };
2336
2366
  }
2337
2367
 
2368
+ if (preUnitSnapshot?.type === "complete-milestone" && s.currentMilestoneId) {
2369
+ const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
2370
+ if (stop) return stop;
2371
+ }
2372
+
2338
2373
  // Both pre and post verification completed without timeout — reset counter
2339
2374
  loopState.consecutiveFinalizeTimeouts = 0;
2340
2375
  if (preUnitSnapshot) {
@@ -23,6 +23,9 @@ import { bumpTurnGeneration } from "./turn-epoch.js";
23
23
  let _currentResolve: ((result: UnitResult) => void) | null = null;
24
24
  let _sessionSwitchInFlight = false;
25
25
  let _pendingSwitchCancellation: { errorContext?: ErrorContext } | null = null;
26
+ let _sessionSwitchAbortGraceUntil = 0;
27
+
28
+ const DEFAULT_SESSION_SWITCH_ABORT_GRACE_MS = 2_000;
26
29
 
27
30
  // ─── Setters (needed for cross-module mutation) ─────────────────────────────
28
31
 
@@ -34,6 +37,21 @@ export function _setSessionSwitchInFlight(v: boolean): void {
34
37
  _sessionSwitchInFlight = v;
35
38
  }
36
39
 
40
+ export function _markSessionSwitchAbortGraceWindow(durationMs = DEFAULT_SESSION_SWITCH_ABORT_GRACE_MS): void {
41
+ _sessionSwitchAbortGraceUntil = Math.max(
42
+ _sessionSwitchAbortGraceUntil,
43
+ Date.now() + durationMs,
44
+ );
45
+ }
46
+
47
+ export function _clearSessionSwitchAbortGraceWindow(): void {
48
+ _sessionSwitchAbortGraceUntil = 0;
49
+ }
50
+
51
+ export function isSessionSwitchAbortGraceActive(now = Date.now()): boolean {
52
+ return now < _sessionSwitchAbortGraceUntil;
53
+ }
54
+
37
55
  export function _clearCurrentResolve(): void {
38
56
  _currentResolve = null;
39
57
  }
@@ -142,6 +160,7 @@ export function _resetPendingResolve(): void {
142
160
  _currentResolve = null;
143
161
  _sessionSwitchInFlight = false;
144
162
  _pendingSwitchCancellation = null;
163
+ _sessionSwitchAbortGraceUntil = 0;
145
164
  }
146
165
 
147
166
  export function _hasPendingResolveForTest(): boolean {
@@ -14,6 +14,7 @@ import type { UnitResult } from "./types.js";
14
14
  import {
15
15
  _clearCurrentResolve,
16
16
  _consumePendingSwitchCancellation,
17
+ _markSessionSwitchAbortGraceWindow,
17
18
  _setCurrentResolve,
18
19
  _setSessionSwitchInFlight,
19
20
  } from "./resolve.js";
@@ -65,6 +66,7 @@ export async function runUnit(
65
66
  workspaceRoot: s.basePath,
66
67
  }).finally(() => {
67
68
  if (sessionSwitchGeneration === mySessionSwitchGeneration) {
69
+ _markSessionSwitchAbortGraceWindow();
68
70
  _setSessionSwitchInFlight(false);
69
71
  }
70
72
  });
@@ -128,6 +130,7 @@ export async function runUnit(
128
130
  // ── Create the agent_end promise (per-unit one-shot) ──
129
131
  // This happens after newSession completes so session-switch agent_end events
130
132
  // from the previous session cannot resolve the new unit.
133
+ _markSessionSwitchAbortGraceWindow();
131
134
  _setSessionSwitchInFlight(false);
132
135
  const unitPromise = new Promise<UnitResult>((resolve) => {
133
136
  _setCurrentResolve(resolve);
@@ -9,6 +9,16 @@ export type DispatchClaimOutcome =
9
9
  | { kind: "skip"; reason: "already-active" | "stale-lease"; existingId?: number; existingWorker?: string }
10
10
  | { kind: "degraded" };
11
11
 
12
+ export type DispatchLeaseOutcome =
13
+ | { kind: "ready"; token: number; recovered: boolean }
14
+ | { kind: "degraded"; reason: "missing-worker" | "missing-milestone" }
15
+ | { kind: "blocked"; reason: string }
16
+ | { kind: "failed"; reason: string };
17
+
18
+ type ClaimMilestoneLeaseResult =
19
+ | { ok: true; token: number; expiresAt: string }
20
+ | { ok: false; error: "held_by"; byWorker: string; expiresAt: string };
21
+
12
22
  interface RecentDispatch {
13
23
  attempt_n?: number | null;
14
24
  }
@@ -44,6 +54,58 @@ export interface OpenDispatchClaimDeps {
44
54
  logClaimFailed: (err: unknown) => void;
45
55
  }
46
56
 
57
+ export interface EnsureDispatchLeaseDeps {
58
+ claimMilestoneLease: (workerId: string, milestoneId: string) => ClaimMilestoneLeaseResult;
59
+ logLeaseRecovered: (details: {
60
+ milestoneId: string;
61
+ workerId: string;
62
+ token: number;
63
+ recovered: boolean;
64
+ }) => void;
65
+ logLeaseRecoveryFailed: (details: {
66
+ milestoneId?: string;
67
+ workerId?: string;
68
+ reason: string;
69
+ }) => void;
70
+ }
71
+
72
+ export function ensureDispatchLease(
73
+ s: AutoSession,
74
+ milestoneId: string | undefined,
75
+ deps: EnsureDispatchLeaseDeps,
76
+ opts: { forceReclaim?: boolean } = {},
77
+ ): DispatchLeaseOutcome {
78
+ if (!s.workerId) return { kind: "degraded", reason: "missing-worker" };
79
+ if (!milestoneId) return { kind: "degraded", reason: "missing-milestone" };
80
+ if (!opts.forceReclaim && typeof s.milestoneLeaseToken === "number") {
81
+ return { kind: "ready", token: s.milestoneLeaseToken, recovered: false };
82
+ }
83
+
84
+ s.milestoneLeaseToken = null;
85
+ try {
86
+ const claim = deps.claimMilestoneLease(s.workerId, milestoneId);
87
+ if (!claim.ok) {
88
+ const reason = `Milestone ${milestoneId} is held by worker ${claim.byWorker} until ${claim.expiresAt}.`;
89
+ deps.logLeaseRecoveryFailed({ milestoneId, workerId: s.workerId, reason });
90
+ return { kind: "blocked", reason };
91
+ }
92
+
93
+ s.currentMilestoneId = milestoneId;
94
+ s.milestoneLeaseToken = claim.token;
95
+ deps.logLeaseRecovered({
96
+ milestoneId,
97
+ workerId: s.workerId,
98
+ token: claim.token,
99
+ recovered: opts.forceReclaim === true,
100
+ });
101
+ return { kind: "ready", token: claim.token, recovered: opts.forceReclaim === true };
102
+ } catch (err) {
103
+ const reason = err instanceof Error ? err.message : String(err);
104
+ deps.logLeaseRecoveryFailed({ milestoneId, workerId: s.workerId, reason });
105
+ return { kind: "failed", reason };
106
+ }
107
+ }
108
+
47
109
  export function openDispatchClaim(
48
110
  s: AutoSession,
49
111
  flowId: string,
@@ -51,7 +113,7 @@ export function openDispatchClaim(
51
113
  iterData: IterationData,
52
114
  deps: OpenDispatchClaimDeps,
53
115
  ): DispatchClaimOutcome {
54
- if (!s.workerId || s.milestoneLeaseToken === null) return { kind: "degraded" };
116
+ if (!s.workerId || typeof s.milestoneLeaseToken !== "number") return { kind: "degraded" };
55
117
  const mid = iterData.mid;
56
118
  if (!mid) return { kind: "degraded" };
57
119
 
@@ -15,6 +15,11 @@ export interface MaintainWorkerHeartbeatDeps {
15
15
  fencingToken: number,
16
16
  ) => boolean;
17
17
  logHeartbeatFailure: (err: unknown) => void;
18
+ logLeaseRefreshMiss?: (details: {
19
+ workerId: string;
20
+ milestoneId: string;
21
+ fencingToken: number;
22
+ }) => void;
18
23
  }
19
24
 
20
25
  export function maintainWorkerHeartbeat(
@@ -26,11 +31,19 @@ export function maintainWorkerHeartbeat(
26
31
  try {
27
32
  deps.heartbeatAutoWorker(session.workerId);
28
33
  if (session.currentMilestoneId && session.milestoneLeaseToken) {
29
- deps.refreshMilestoneLease(
34
+ const refreshed = deps.refreshMilestoneLease(
30
35
  session.workerId,
31
36
  session.currentMilestoneId,
32
37
  session.milestoneLeaseToken,
33
38
  );
39
+ if (!refreshed) {
40
+ deps.logLeaseRefreshMiss?.({
41
+ workerId: session.workerId,
42
+ milestoneId: session.currentMilestoneId,
43
+ fencingToken: session.milestoneLeaseToken,
44
+ });
45
+ session.milestoneLeaseToken = null;
46
+ }
34
47
  }
35
48
  } catch (err) {
36
49
  deps.logHeartbeatFailure(err);