gsd-pi 2.80.0-dev.2be7caf18 → 2.80.0-dev.3065f7bfa

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 (268) hide show
  1. package/README.md +1 -0
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/phases.js +178 -108
  4. package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
  5. package/dist/resources/extensions/gsd/auto/run-unit.js +20 -30
  6. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  7. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -4
  10. package/dist/resources/extensions/gsd/auto-prompts.js +90 -15
  11. package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
  12. package/dist/resources/extensions/gsd/auto-start.js +197 -6
  13. package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
  14. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
  15. package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
  16. package/dist/resources/extensions/gsd/auto.js +79 -24
  17. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +42 -2
  18. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +49 -36
  19. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +15 -5
  20. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +9 -3
  21. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +7 -1
  22. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +9 -3
  23. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +8 -2
  24. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +238 -28
  25. package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
  26. package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
  27. package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
  28. package/dist/resources/extensions/gsd/context-budget.js +37 -2
  29. package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
  30. package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
  31. package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
  32. package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
  33. package/dist/resources/extensions/gsd/git-service.js +36 -4
  34. package/dist/resources/extensions/gsd/gsd-db.js +46 -13
  35. package/dist/resources/extensions/gsd/guided-flow.js +66 -32
  36. package/dist/resources/extensions/gsd/memory-store.js +69 -12
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +32 -8
  38. package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
  39. package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
  40. package/dist/resources/extensions/gsd/pre-execution-checks.js +22 -0
  41. package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
  42. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  43. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
  45. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  47. package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
  48. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
  49. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  50. package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
  51. package/dist/resources/extensions/gsd/worktree-resolver.js +68 -21
  52. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  53. package/dist/web/standalone/.next/BUILD_ID +1 -1
  54. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  55. package/dist/web/standalone/.next/build-manifest.json +2 -2
  56. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  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.html +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.html +1 -1
  74. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  81. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  83. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  84. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  85. package/package.json +3 -3
  86. package/packages/mcp-server/src/workflow-tools.test.ts +9 -2
  87. package/packages/native/tsconfig.tsbuildinfo +1 -1
  88. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  89. package/packages/pi-agent-core/dist/agent-loop.js +4 -1
  90. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  91. package/packages/pi-agent-core/dist/agent.d.ts +5 -0
  92. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  93. package/packages/pi-agent-core/dist/agent.js +2 -0
  94. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  95. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  96. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  97. package/packages/pi-agent-core/dist/index.js +2 -0
  98. package/packages/pi-agent-core/dist/index.js.map +1 -1
  99. package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
  100. package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
  101. package/packages/pi-agent-core/dist/token-audit.js +221 -0
  102. package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
  103. package/packages/pi-agent-core/dist/types.d.ts +9 -0
  104. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  105. package/packages/pi-agent-core/dist/types.js.map +1 -1
  106. package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
  107. package/packages/pi-agent-core/src/agent-loop.ts +4 -1
  108. package/packages/pi-agent-core/src/agent.ts +8 -0
  109. package/packages/pi-agent-core/src/index.ts +2 -0
  110. package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
  111. package/packages/pi-agent-core/src/token-audit.ts +287 -0
  112. package/packages/pi-agent-core/src/types.ts +14 -0
  113. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  114. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
  115. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +18 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  119. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/agent-session.js +41 -7
  121. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
  124. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  126. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -6
  128. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +3 -3
  130. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +32 -1
  132. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +2 -0
  135. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
  137. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
  138. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
  139. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
  140. package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
  141. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/sdk.js +74 -2
  143. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
  145. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
  147. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
  149. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  150. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
  151. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +25 -0
  152. package/packages/pi-coding-agent/src/core/agent-session.ts +45 -7
  153. package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
  154. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +3 -3
  155. package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -5
  156. package/packages/pi-coding-agent/src/core/extensions/types.ts +35 -1
  157. package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
  158. package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
  159. package/packages/pi-coding-agent/src/core/sdk.ts +85 -3
  160. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
  161. package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
  162. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  163. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
  164. package/src/resources/extensions/gsd/auto/phases.ts +251 -172
  165. package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
  166. package/src/resources/extensions/gsd/auto/run-unit.ts +25 -30
  167. package/src/resources/extensions/gsd/auto/session.ts +8 -0
  168. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
  169. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -0
  170. package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
  171. package/src/resources/extensions/gsd/auto-prompts.ts +95 -14
  172. package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
  173. package/src/resources/extensions/gsd/auto-start.ts +230 -9
  174. package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
  175. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
  176. package/src/resources/extensions/gsd/auto-worktree.ts +123 -0
  177. package/src/resources/extensions/gsd/auto.ts +87 -19
  178. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +49 -1
  179. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +50 -36
  180. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -5
  181. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +10 -3
  182. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +8 -1
  183. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +10 -3
  184. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +9 -2
  185. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +283 -28
  186. package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
  187. package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
  188. package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
  189. package/src/resources/extensions/gsd/context-budget.ts +44 -2
  190. package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
  191. package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
  192. package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
  193. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
  194. package/src/resources/extensions/gsd/git-service.ts +46 -8
  195. package/src/resources/extensions/gsd/gsd-db.ts +50 -13
  196. package/src/resources/extensions/gsd/guided-flow.ts +84 -39
  197. package/src/resources/extensions/gsd/memory-store.ts +77 -12
  198. package/src/resources/extensions/gsd/native-git-bridge.ts +39 -6
  199. package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
  200. package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
  201. package/src/resources/extensions/gsd/pre-execution-checks.ts +23 -0
  202. package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
  203. package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  204. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  205. package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
  206. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  207. package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  208. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +2 -2
  209. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +417 -10
  210. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
  211. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
  212. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +168 -6
  213. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
  214. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +3 -1
  215. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
  216. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
  217. package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
  218. package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
  219. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
  220. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
  221. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
  222. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
  223. package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +132 -0
  224. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
  225. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +6 -3
  226. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
  227. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
  228. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
  229. package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
  230. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -0
  231. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +242 -0
  232. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
  233. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -0
  234. package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +133 -0
  235. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
  236. package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
  237. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +45 -5
  238. package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
  239. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
  240. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +3 -3
  241. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +38 -17
  242. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
  243. package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
  244. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +106 -0
  245. package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
  246. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
  247. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +38 -0
  248. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  249. package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
  250. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
  251. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +291 -0
  252. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
  253. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  254. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -4
  255. package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
  256. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
  257. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +1 -0
  258. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
  259. package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
  260. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +167 -4
  261. package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
  262. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
  263. package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
  264. package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
  265. package/src/resources/extensions/gsd/worktree-resolver.ts +85 -19
  266. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
  267. /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_buildManifest.js +0 -0
  268. /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_ssgManifest.js +0 -0
@@ -65,6 +65,7 @@ import { snapshotSkills } from "./skill-discovery.js";
65
65
  import { isDbAvailable, getMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
66
66
  import { isClosedStatus } from "./status-guards.js";
67
67
  import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.js";
68
+ import { auditOrphanedPreflightStashes } from "./orphan-stash-audit.js";
68
69
 
69
70
  import {
70
71
  debugLog,
@@ -332,6 +333,165 @@ export function auditOrphanedMilestoneBranches(
332
333
  return { recovered, warnings };
333
334
  }
334
335
 
336
+ /**
337
+ * Pure decision function for picking which orphan milestone the auto-loop
338
+ * should resume the merge transition for. Extracted so it can be unit-tested
339
+ * without spinning up a git repo or a SQLite DB.
340
+ *
341
+ * Returns the lexicographically-greatest milestone id (e.g. "M002" beats
342
+ * "M001") whose branch is unmerged AND has commits ahead of main AND whose
343
+ * status is `complete`. Lex-ordering matches the project's M00x convention,
344
+ * which is the most-recently-completed milestone in practice.
345
+ * `isComplete` errors propagate; `commitsAhead` errors are treated as 0.
346
+ */
347
+ export function _selectResumableMilestone(
348
+ branchNames: readonly string[],
349
+ mergedBranches: ReadonlySet<string>,
350
+ isComplete: (milestoneId: string) => boolean,
351
+ commitsAhead: (branch: string) => number,
352
+ ): string | null {
353
+ const candidates: string[] = [];
354
+ for (const branch of branchNames) {
355
+ if (!branch.startsWith("milestone/")) continue;
356
+ const milestoneId = branch.slice("milestone/".length);
357
+ if (mergedBranches.has(branch)) continue;
358
+ if (!isComplete(milestoneId)) continue;
359
+ let ahead = 0;
360
+ try {
361
+ ahead = commitsAhead(branch);
362
+ } catch {
363
+ continue;
364
+ }
365
+ if (ahead <= 0) continue;
366
+ candidates.push(milestoneId);
367
+ }
368
+ if (candidates.length === 0) return null;
369
+ candidates.sort();
370
+ return candidates[candidates.length - 1];
371
+ }
372
+
373
+ /**
374
+ * Find the most-recent completed milestone whose branch still has unmerged
375
+ * commits ahead of the integration branch. Used by `bootstrapAutoSession`
376
+ * to seed `s.currentMilestoneId` so the auto-loop's transition guard at
377
+ * `phases.ts:730` fires on the first iteration after a process restart —
378
+ * without this, the in-memory-only `s.currentMilestoneId` is `null` after
379
+ * restart, the guard short-circuits, and the orphaned milestone branch
380
+ * never gets merged into main (#5538-followup).
381
+ *
382
+ * Returns null when isolation is `none`, the DB is unavailable, or no
383
+ * orphan candidate exists. All git failures degrade silently — startup
384
+ * must never block on this defensive lookup.
385
+ */
386
+ export function findUnmergedCompletedMilestone(
387
+ basePath: string,
388
+ isolationMode: "worktree" | "branch" | "none",
389
+ ): string | null {
390
+ if (isolationMode === "none") return null;
391
+ if (!isDbAvailable()) return null;
392
+
393
+ let milestoneBranches: string[];
394
+ try {
395
+ milestoneBranches = nativeBranchList(basePath, "milestone/*");
396
+ } catch {
397
+ return null;
398
+ }
399
+ if (milestoneBranches.length === 0) return null;
400
+
401
+ let mainBranch: string;
402
+ try {
403
+ mainBranch = nativeDetectMainBranch(basePath);
404
+ } catch {
405
+ mainBranch = "main";
406
+ }
407
+
408
+ let mergedBranches: Set<string>;
409
+ try {
410
+ mergedBranches = new Set(
411
+ nativeBranchListMerged(basePath, mainBranch, "milestone/*"),
412
+ );
413
+ } catch {
414
+ mergedBranches = new Set();
415
+ }
416
+
417
+ return _selectResumableMilestone(
418
+ milestoneBranches,
419
+ mergedBranches,
420
+ (milestoneId) => {
421
+ const row = getMilestone(milestoneId);
422
+ return !!row && row.status === "complete";
423
+ },
424
+ (branch) => nativeCommitCountBetween(basePath, mainBranch, branch),
425
+ );
426
+ }
427
+
428
+ /**
429
+ * Run `mergeAndExit` for a milestone whose worktree/branch finalization
430
+ * never completed in a prior session — the active-milestone in phase
431
+ * `complete` with a survivor `milestone/<id>` branch still around.
432
+ *
433
+ * Wraps the call in try/catch so a thrown error from `_mergeBranchMode`
434
+ * (made fail-loud in commit 68ef58a3c) is converted into a user-facing
435
+ * error notify instead of an unhandled exception that propagates through
436
+ * `bootstrapAutoSession` to the slash-command caller's `.catch` block.
437
+ *
438
+ * Returns `{ merged: true }` on success; `{ merged: false, error }` on
439
+ * throw — caller decides whether to abort bootstrap.
440
+ */
441
+ export function _finalizeSurvivorBranch(
442
+ resolver: WorktreeResolver,
443
+ milestoneId: string,
444
+ ui: { notify: (msg: string, level?: "info" | "warning" | "error" | "success") => void },
445
+ ): { merged: boolean; error?: unknown } {
446
+ ui.notify(
447
+ `Milestone ${milestoneId} is complete but branch/worktree was not finalized. Running merge now.`,
448
+ "info",
449
+ );
450
+ try {
451
+ resolver.mergeAndExit(milestoneId, { notify: ui.notify.bind(ui) });
452
+ return { merged: true };
453
+ } catch (err) {
454
+ const msg = err instanceof Error ? err.message : String(err);
455
+ ui.notify(
456
+ `Survivor-branch finalization for ${milestoneId} failed: ${msg}. Resolve manually and re-run /gsd auto.`,
457
+ "error",
458
+ );
459
+ return { merged: false, error: err };
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Merge a milestone whose DB row is `complete` but whose branch is still
465
+ * unmerged into the integration branch. Called from `bootstrapAutoSession`
466
+ * for orphans surfaced by `findUnmergedCompletedMilestone`.
467
+ *
468
+ * Notifies the user before and after, swallowing errors so a transient git
469
+ * failure never blocks bootstrap. Returns `{ merged: true }` when the
470
+ * underlying `mergeAndExit` completes; `{ merged: false, error }` on throw.
471
+ *
472
+ * Extracted to keep `bootstrapAutoSession` testable: the merge call and the
473
+ * notify shape are exercised against a mock resolver in
474
+ * `tests/orphan-merge-bootstrap.test.ts`.
475
+ */
476
+ export function _mergeOrphanCompletedMilestone(
477
+ resolver: WorktreeResolver,
478
+ orphanId: string,
479
+ ui: { notify: (msg: string, level?: "info" | "warning" | "error" | "success") => void },
480
+ ): { merged: boolean; error?: unknown } {
481
+ ui.notify(`Detected unmerged completed milestone ${orphanId}. Merging now.`, "info");
482
+ try {
483
+ resolver.mergeAndExit(orphanId, { notify: ui.notify.bind(ui) });
484
+ return { merged: true };
485
+ } catch (err) {
486
+ const msg = err instanceof Error ? err.message : String(err);
487
+ ui.notify(
488
+ `Could not merge orphan milestone ${orphanId}: ${msg}. Resolve manually and re-run /gsd auto.`,
489
+ "warning",
490
+ );
491
+ return { merged: false, error: err };
492
+ }
493
+ }
494
+
335
495
  export async function bootstrapAutoSession(
336
496
  s: AutoSession,
337
497
  ctx: ExtensionCommandContext,
@@ -577,6 +737,39 @@ export async function bootstrapAutoSession(
577
737
  logWarning("bootstrap", `orphaned milestone branch audit failed: ${err instanceof Error ? err.message : String(err)}`);
578
738
  }
579
739
 
740
+ // ── Orphaned preflight-stash audit (#5538-followup) ──
741
+ // Reapplies pre-merge stashes whose milestone is now complete but whose
742
+ // postflight pop was skipped by an interrupted merge in a prior session.
743
+ // Uses `git stash apply` (not pop) so the entry remains as a backup.
744
+ try {
745
+ if (isDbAvailable()) {
746
+ const stashAudit = auditOrphanedPreflightStashes(base, (milestoneId) => {
747
+ const row = getMilestone(milestoneId);
748
+ return !!row && isClosedStatus(row.status);
749
+ });
750
+ for (const entry of stashAudit.applied) {
751
+ ctx.ui.notify(
752
+ `Orphan audit: applied preflight stash ${entry.stashRef} for completed milestone ${entry.milestoneId}. The stash entry is preserved as a backup.`,
753
+ "info",
754
+ );
755
+ }
756
+ for (const msg of stashAudit.warnings) {
757
+ ctx.ui.notify(`Orphan audit: ${msg}`, "warning");
758
+ }
759
+ if (stashAudit.applied.length > 0) {
760
+ debugLog("orphan-stash-audit", {
761
+ applied: stashAudit.applied,
762
+ warnings: stashAudit.warnings,
763
+ });
764
+ }
765
+ }
766
+ } catch (err) {
767
+ logWarning(
768
+ "bootstrap",
769
+ `orphaned preflight-stash audit failed: ${err instanceof Error ? err.message : String(err)}`,
770
+ );
771
+ }
772
+
580
773
  let state = await deriveState(base);
581
774
 
582
775
  // Stale worktree state recovery (#654)
@@ -649,20 +842,48 @@ export async function bootstrapAutoSession(
649
842
  // hasSurvivorBranch after a successful promotion.
650
843
  if (decideSurvivorAction(hasSurvivorBranch, state.phase) === "finalize") {
651
844
  const mid = state.activeMilestone!.id;
652
- ctx.ui.notify(
653
- `Milestone ${mid} is complete but branch/worktree was not finalized. Running merge now.`,
654
- "info",
655
- );
656
- const resolver = buildResolver();
657
- resolver.mergeAndExit(mid, {
658
- notify: ctx.ui.notify.bind(ctx.ui),
659
- });
845
+ // Commit 68ef58a3c made `_mergeBranchMode` throw on wrong-branch
846
+ // instead of returning false silently. Wrap the call so the throw is
847
+ // converted into an error notify + clean bootstrap abort, not an
848
+ // unhandled exception propagating to the slash-command caller (#5549
849
+ // post-merge audit, R2).
850
+ const finalize = _finalizeSurvivorBranch(buildResolver(), mid, ctx.ui);
851
+ if (!finalize.merged) {
852
+ return releaseLockAndReturn();
853
+ }
660
854
  invalidateAllCaches();
661
855
  state = await deriveState(base);
662
856
  // Clear survivor flag — finalization is done
663
857
  hasSurvivorBranch = false;
664
858
  }
665
859
 
860
+ // ── Orphan-completed-milestone merge (#5538-followup) ──
861
+ // A process killed between `complete-milestone` (DB flip + SUMMARY write)
862
+ // and the loop's transition-guard merge strands the milestone branch
863
+ // forever: `s.currentMilestoneId` is in-memory only, so on the next
864
+ // bootstrap the guard at phases.ts:730 sees `mid === s.currentMilestoneId`
865
+ // and short-circuits.
866
+ //
867
+ // The earlier attempt at this fix seeded `s.currentMilestoneId` to the
868
+ // orphan id pre-state-derivation, but the unconditional assignment at
869
+ // line 948 (`s.currentMilestoneId = state.activeMilestone?.id ?? null`)
870
+ // immediately overwrote the seed. Active-merge is the more durable fix:
871
+ // call `mergeAndExit` directly during bootstrap, then re-derive state so
872
+ // the loop's normal flow continues without an in-memory hint.
873
+ //
874
+ // Mirrors the survivor-finalize block above. Failures degrade to a
875
+ // warning notify so a transient git error doesn't block bootstrap.
876
+ {
877
+ const orphan = findUnmergedCompletedMilestone(base, getIsolationMode(base));
878
+ if (orphan && orphan !== state.activeMilestone?.id) {
879
+ const result = _mergeOrphanCompletedMilestone(buildResolver(), orphan, ctx.ui);
880
+ if (result.merged) {
881
+ invalidateAllCaches();
882
+ state = await deriveState(base);
883
+ }
884
+ }
885
+ }
886
+
666
887
  const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
667
888
  const { shouldRunDeepProjectSetup } = await import("./auto-dispatch.js");
668
889
  const deepProjectStagePending = shouldRunDeepProjectSetup(
@@ -790,7 +1011,7 @@ export async function bootstrapAutoSession(
790
1011
  s.resourceVersionOnStart = readResourceVersion();
791
1012
  s.pendingQuickTasks = [];
792
1013
  s.currentUnit = null;
793
- s.currentMilestoneId = deepProjectStagePending ? null : state.activeMilestone?.id ?? null;
1014
+ s.currentMilestoneId ??= deepProjectStagePending ? null : state.activeMilestone?.id ?? null;
794
1015
  s.originalModelId = startModelSnapshot?.id ?? ctx.model?.id ?? null;
795
1016
  s.originalModelProvider = startModelSnapshot?.provider ?? ctx.model?.provider ?? null;
796
1017
  s.originalThinkingLevel = startThinkingSnapshot ?? null;
@@ -32,6 +32,7 @@ let _currentSigtermHandler: (() => void) | null = null;
32
32
  export function registerSigtermHandler(
33
33
  currentBasePath: string,
34
34
  previousHandler: (() => void) | null,
35
+ onSignalCleanup?: () => void,
35
36
  ): () => void {
36
37
  // Remove the explicitly-passed previous handler
37
38
  if (previousHandler) {
@@ -43,6 +44,12 @@ export function registerSigtermHandler(
43
44
  for (const sig of CLEANUP_SIGNALS) process.off(sig, _currentSigtermHandler);
44
45
  }
45
46
  const handler = () => {
47
+ try {
48
+ onSignalCleanup?.();
49
+ } catch {
50
+ void 0;
51
+ // Signal cleanup is best-effort; lock cleanup and process exit still run.
52
+ }
46
53
  clearLock(currentBasePath);
47
54
  releaseSessionLock(currentBasePath);
48
55
  process.exit(0);
@@ -71,14 +71,14 @@ export async function recoverTimedOutUnit(
71
71
  recovery: status,
72
72
  });
73
73
 
74
- const durableComplete = status.summaryExists && status.taskChecked && status.nextActionAdvanced;
74
+ const durableComplete = status.dbComplete || (status.summaryExists && status.taskChecked && status.nextActionAdvanced);
75
75
  if (durableComplete) {
76
76
  writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
77
77
  phase: "finalized",
78
78
  recovery: status,
79
79
  });
80
80
  ctx.ui.notify(
81
- `${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed on disk. Continuing auto-mode. (attempt ${attemptNumber})`,
81
+ `${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed. Continuing auto-mode. (attempt ${attemptNumber})`,
82
82
  "info",
83
83
  );
84
84
  unitRecoveryCount.delete(recoveryKey);
@@ -78,6 +78,7 @@ import {
78
78
  nativeUpdateRef,
79
79
  nativeIsAncestor,
80
80
  nativeMergeAbort,
81
+ nativeWorktreeList,
81
82
  } from "./native-git-bridge.js";
82
83
  import { gsdHome } from "./gsd-home.js";
83
84
  import { type MilestoneScope, type GsdWorkspace, createWorkspace } from "./workspace.js";
@@ -1180,6 +1181,122 @@ export function enterBranchModeForMilestone(
1180
1181
  * (both formerly here) became dead.
1181
1182
  */
1182
1183
 
1184
+ /**
1185
+ * True when `branch` is checked out in any worktree listed by
1186
+ * `git worktree list --porcelain`. Used to gate ref updates that would
1187
+ * otherwise leave a concurrent worktree's HEAD inconsistent with its
1188
+ * index/working tree (Codex peer-review of #5538-followup).
1189
+ *
1190
+ * Best-effort: a `nativeWorktreeList` failure returns true so we err on
1191
+ * the side of NOT moving the ref. Better to skip a fast-forward than to
1192
+ * silently corrupt another worktree.
1193
+ */
1194
+ export function _isBranchCheckedOutElsewhere(
1195
+ basePath: string,
1196
+ branch: string,
1197
+ ): boolean {
1198
+ try {
1199
+ const entries = nativeWorktreeList(basePath);
1200
+ return entries.some((entry) => entry.branch === branch);
1201
+ } catch {
1202
+ return true;
1203
+ }
1204
+ }
1205
+
1206
+ /**
1207
+ * Resolve the integration branch using the same 3-tier fallback as the
1208
+ * fresh-create path: META.json → git.main_branch preference → detected
1209
+ * main branch. Returns null when no usable target exists.
1210
+ */
1211
+ function _resolveIntegrationBranchForReuse(
1212
+ basePath: string,
1213
+ milestoneId: string,
1214
+ ): string | null {
1215
+ const fromMeta = readIntegrationBranch(basePath, milestoneId);
1216
+ if (fromMeta) return fromMeta;
1217
+
1218
+ const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
1219
+ const fromPref = gitPrefs?.main_branch &&
1220
+ typeof gitPrefs.main_branch === "string" &&
1221
+ gitPrefs.main_branch.length > 0 &&
1222
+ nativeBranchExists(basePath, gitPrefs.main_branch)
1223
+ ? gitPrefs.main_branch
1224
+ : null;
1225
+ if (fromPref) return fromPref;
1226
+
1227
+ try {
1228
+ return nativeDetectMainBranch(basePath);
1229
+ } catch {
1230
+ return null;
1231
+ }
1232
+ }
1233
+
1234
+ /**
1235
+ * When reusing an existing milestone branch, fast-forward it onto the
1236
+ * integration branch when that's safe (branch is a strict ancestor of
1237
+ * integration — no commits would be lost). Skips when the branch has its
1238
+ * own commits ahead of integration, when the integration branch can't be
1239
+ * resolved, or when any git operation fails — the merge gate at milestone
1240
+ * completion will surface real divergence as a conflict.
1241
+ *
1242
+ * The previous behavior re-attached the worktree to whatever stale tip
1243
+ * the branch held, which caused new milestone work to fork from a base
1244
+ * missing prior milestones' merges (#5538-followup).
1245
+ */
1246
+ export function fastForwardReusedMilestoneBranchIfSafe(
1247
+ basePath: string,
1248
+ milestoneId: string,
1249
+ branch: string,
1250
+ ): void {
1251
+ try {
1252
+ const integrationBranch = _resolveIntegrationBranchForReuse(basePath, milestoneId);
1253
+ if (!integrationBranch || integrationBranch === branch) return;
1254
+ if (!nativeBranchExists(basePath, integrationBranch)) return;
1255
+
1256
+ // Pure fast-forward only: branch must be a strict ancestor of integration.
1257
+ // If the branch has its own commits ahead, leave it alone.
1258
+ if (!nativeIsAncestor(basePath, branch, integrationBranch)) {
1259
+ debugLog("createAutoWorktree", {
1260
+ phase: "skip-ff-branch-not-ancestor",
1261
+ milestoneId,
1262
+ branch,
1263
+ integration: integrationBranch,
1264
+ });
1265
+ return;
1266
+ }
1267
+
1268
+ // Codex peer-review: `nativeUpdateRef` succeeds even when the branch is
1269
+ // currently checked out in another worktree, leaving that worktree's HEAD
1270
+ // inconsistent with its index/work tree. Skip the fast-forward if any
1271
+ // listed worktree has this branch checked out — the merge gate at
1272
+ // milestone-completion will surface stale-base divergence as a conflict
1273
+ // instead of silently corrupting the other worktree's state.
1274
+ if (_isBranchCheckedOutElsewhere(basePath, branch)) {
1275
+ debugLog("createAutoWorktree", {
1276
+ phase: "skip-ff-branch-checked-out-elsewhere",
1277
+ milestoneId,
1278
+ branch,
1279
+ });
1280
+ return;
1281
+ }
1282
+
1283
+ nativeUpdateRef(basePath, `refs/heads/${branch}`, integrationBranch);
1284
+ debugLog("createAutoWorktree", {
1285
+ phase: "fast-forward-reused-branch",
1286
+ milestoneId,
1287
+ branch,
1288
+ integration: integrationBranch,
1289
+ });
1290
+ } catch (err) {
1291
+ debugLog("createAutoWorktree", {
1292
+ phase: "fast-forward-reused-branch-failed",
1293
+ milestoneId,
1294
+ branch,
1295
+ error: err instanceof Error ? err.message : String(err),
1296
+ });
1297
+ }
1298
+ }
1299
+
1183
1300
  export function createAutoWorktree(
1184
1301
  basePath: string,
1185
1302
  milestoneId: string,
@@ -1206,6 +1323,12 @@ export function createAutoWorktree(
1206
1323
 
1207
1324
  let info: { name: string; path: string; branch: string; exists: boolean };
1208
1325
  if (branchExists) {
1326
+ // #5538-followup: fast-forward the reused branch onto the integration
1327
+ // branch when safe so the next milestone forks from up-to-date code.
1328
+ // Without this, a milestone that was created before another milestone
1329
+ // merged into main would carry a stale base into its worktree.
1330
+ fastForwardReusedMilestoneBranchIfSafe(basePath, milestoneId, branch);
1331
+
1209
1332
  // Re-attach worktree to the existing milestone branch (preserving commits)
1210
1333
  info = createWorktree(basePath, milestoneId, {
1211
1334
  branch,
@@ -59,6 +59,7 @@ import {
59
59
  isLockProcessAlive,
60
60
  formatCrashInfo,
61
61
  emitCrashRecoveredUnitEnd,
62
+ emitOpenUnitEndForUnit,
62
63
  } from "./crash-recovery.js";
63
64
  import {
64
65
  acquireSessionLock,
@@ -150,6 +151,7 @@ import {
150
151
  resolveProjectRoot,
151
152
  } from "./worktree.js";
152
153
  import { GitServiceImpl } from "./git-service.js";
154
+ import { nativeCheckoutBranch } from "./native-git-bridge.js";
153
155
  import { getPriorSliceCompletionBlocker } from "./dispatch-guard.js";
154
156
  import {
155
157
  createAutoWorktree,
@@ -200,6 +202,8 @@ import {
200
202
  detectWorkingTreeActivity,
201
203
  } from "./auto-supervisor.js";
202
204
  import { isDbAvailable, getMilestone } from "./gsd-db.js";
205
+ import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
206
+ import { writeUnitRuntimeRecord } from "./unit-runtime.js";
203
207
  import { countPendingCaptures } from "./captures.js";
204
208
  import { CMUX_CHANNELS, type CmuxLogLevel } from "../shared/cmux-events.js";
205
209
  import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
@@ -242,6 +246,20 @@ import {
242
246
  type WorktreeResolverDeps,
243
247
  } from "./worktree-resolver.js";
244
248
  import { reorderForCaching } from "./prompt-ordering.js";
249
+ import { initTokenCounter } from "./token-counter.js";
250
+
251
+ // Warm the tiktoken encoder at extension startup so context-budget computations
252
+ // can use accurate token counts via countTokensSync without paying the load
253
+ // cost mid-prompt-build. Fire-and-forget — failure falls back to the
254
+ // provider-aware char-ratio estimator already used by getCharsPerToken().
255
+ // Catch rejections explicitly: an unhandled rejection at module-import time
256
+ // can destabilize startup before the engine logger is configured.
257
+ void initTokenCounter().catch((err) => {
258
+ logWarning(
259
+ "engine",
260
+ `token counter warm-up failed: ${err instanceof Error ? err.message : String(err)}`,
261
+ );
262
+ });
245
263
 
246
264
  // ─── Session State ─────────────────────────────────────────────────────────
247
265
 
@@ -505,9 +523,54 @@ export {
505
523
  getBudgetEnforcementAction,
506
524
  } from "./auto-budget.js";
507
525
 
526
+ function closeOutSignalInterruptedUnit(currentBasePath: string): void {
527
+ const currentUnit = s.currentUnit;
528
+ if (!currentUnit) return;
529
+
530
+ const reason = "Auto-mode process received a termination signal";
531
+ const errorContext: ErrorContext = {
532
+ message: reason,
533
+ category: "aborted",
534
+ isTransient: false,
535
+ };
536
+ const basePath = s.basePath || currentBasePath;
537
+
538
+ try {
539
+ emitOpenUnitEndForUnit(basePath, currentUnit.type, currentUnit.id, "cancelled", errorContext);
540
+ } catch (err) {
541
+ logWarning("engine", `signal unit-end cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
542
+ }
543
+
544
+ try {
545
+ writeUnitRuntimeRecord(basePath, currentUnit.type, currentUnit.id, currentUnit.startedAt, {
546
+ phase: "crashed",
547
+ lastProgressAt: Date.now(),
548
+ lastProgressKind: "signal",
549
+ });
550
+ } catch (err) {
551
+ logWarning("engine", `signal runtime cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
552
+ }
553
+
554
+ try {
555
+ if (s.workerId) markLatestActiveForWorkerCanceled(s.workerId, "signal-exit");
556
+ } catch (err) {
557
+ logWarning("engine", `signal dispatch cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
558
+ }
559
+
560
+ try {
561
+ resolveAgentEndCancelled(errorContext);
562
+ } catch (err) {
563
+ logWarning("engine", `signal resolve cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
564
+ }
565
+ }
566
+
508
567
  /** Wrapper: register SIGTERM handler and store reference. */
509
568
  function registerSigtermHandler(currentBasePath: string): void {
510
- s.sigtermHandler = _registerSigtermHandler(currentBasePath, s.sigtermHandler);
569
+ s.sigtermHandler = _registerSigtermHandler(
570
+ currentBasePath,
571
+ s.sigtermHandler,
572
+ () => closeOutSignalInterruptedUnit(currentBasePath),
573
+ );
511
574
  }
512
575
 
513
576
  /** Wrapper: deregister SIGTERM handler and clear reference. */
@@ -972,6 +1035,8 @@ export async function stopAuto(
972
1035
  if (s.workerId) {
973
1036
  markWorkerStopping(s.workerId);
974
1037
  }
1038
+ s.workerId = null;
1039
+ s.milestoneLeaseToken = null;
975
1040
  } catch (e) {
976
1041
  debugLog("stop-cleanup-coordination", { error: e instanceof Error ? e.message : String(e) });
977
1042
  }
@@ -1109,6 +1174,21 @@ export async function stopAuto(
1109
1174
  debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
1110
1175
  }
1111
1176
 
1177
+ // Re-root the active command session/tool runtime after worktree teardown.
1178
+ // mergeAndExit restores process.cwd(), but AgentSession has already captured
1179
+ // its own cwd for tools and system prompt; refresh it before returning to the
1180
+ // user so follow-up commands do not target a removed milestone worktree.
1181
+ if (s.originalBasePath && ctx && s.cmdCtx) {
1182
+ try {
1183
+ const result = await s.cmdCtx.newSession({ workspaceRoot: s.basePath });
1184
+ if (result.cancelled) {
1185
+ logWarning("engine", "post-stop session re-root was cancelled", { file: "auto.ts", basePath: s.basePath });
1186
+ }
1187
+ } catch (err) {
1188
+ logWarning("engine", `post-stop session re-root failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts", basePath: s.basePath });
1189
+ }
1190
+ }
1191
+
1112
1192
  // ── Step 8: Ledger notification ──
1113
1193
  try {
1114
1194
  const ledger = getLedger();
@@ -1372,6 +1452,7 @@ function buildResolverDeps(): WorktreeResolverDeps {
1372
1452
  getAutoWorktreePath,
1373
1453
  autoCommitCurrentBranch,
1374
1454
  getCurrentBranch,
1455
+ checkoutBranch: nativeCheckoutBranch,
1375
1456
  autoWorktreeBranch,
1376
1457
  resolveMilestoneFile,
1377
1458
  readFileSync: (path: string, encoding: string) =>
@@ -1975,6 +2056,10 @@ export async function startAuto(
1975
2056
  : new URL("../../../resource-loader.js", import.meta.url).href;
1976
2057
  const { initResources } = await import(resourceLoaderPath);
1977
2058
  initResources(agentDir);
2059
+ // initResources() uses synchronous fs APIs, so the prompt-template cache
2060
+ // can be primed immediately — no need for the legacy 1s setTimeout deferral.
2061
+ const { primeCache } = await import("./prompt-loader.js");
2062
+ primeCache();
1978
2063
  // Open the project DB before rebuild/derive so resume uses DB-backed
1979
2064
  // state instead of falling back to stale markdown parsing (#2940).
1980
2065
  await openProjectDbIfPresent(s.basePath);
@@ -2233,24 +2318,7 @@ export async function dispatchHookUnit(
2233
2318
  startedAt: hookStartedAt,
2234
2319
  };
2235
2320
 
2236
- // Ensure cwd matches basePath BEFORE newSession() captures it (#1389).
2237
- // newSession() snapshots process.cwd() during construction; chdir-ing
2238
- // afterward leaves the session rooted to whatever cwd was when the call
2239
- // was made. Must be synchronous — no awaits between chdir and newSession.
2240
- try { if (process.cwd() !== s.basePath) process.chdir(s.basePath); } catch (err) {
2241
- const msg = `Failed to chdir before hook newSession (basePath: ${s.basePath}): ${err instanceof Error ? err.message : String(err)}`;
2242
- logWarning("engine", msg, { file: "auto.ts", basePath: s.basePath, error: err instanceof Error ? err.message : String(err) });
2243
- ctx.ui.notify(`${msg}. Cancelling hook dispatch to avoid running in the wrong directory.`, "error");
2244
- if (wasActive) {
2245
- s.basePath = previousBasePath;
2246
- s.currentUnit = previousCurrentUnit;
2247
- } else {
2248
- s.reset();
2249
- }
2250
- return false;
2251
- }
2252
-
2253
- const result = await s.cmdCtx!.newSession();
2321
+ const result = await s.cmdCtx!.newSession({ workspaceRoot: s.basePath });
2254
2322
  if (result.cancelled) {
2255
2323
  await stopAuto(ctx, pi);
2256
2324
  return false;