gsd-pi 2.48.0-dev.ced2eca → 2.49.0-dev.9e177e9

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 (249) hide show
  1. package/dist/headless-ui.js +12 -2
  2. package/dist/headless.js +29 -13
  3. package/dist/resources/extensions/gsd/auto/infra-errors.js +1 -0
  4. package/dist/resources/extensions/gsd/auto/phases.js +11 -11
  5. package/dist/resources/extensions/gsd/auto/resolve.js +2 -2
  6. package/dist/resources/extensions/gsd/auto/run-unit.js +2 -2
  7. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  8. package/dist/resources/extensions/gsd/auto-artifact-paths.js +8 -10
  9. package/dist/resources/extensions/gsd/auto-dashboard.js +6 -3
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +33 -21
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -24
  12. package/dist/resources/extensions/gsd/auto-prompts.js +102 -21
  13. package/dist/resources/extensions/gsd/auto-recovery.js +62 -184
  14. package/dist/resources/extensions/gsd/auto-start.js +4 -31
  15. package/dist/resources/extensions/gsd/auto-timers.js +2 -2
  16. package/dist/resources/extensions/gsd/auto-verification.js +4 -7
  17. package/dist/resources/extensions/gsd/auto-worktree.js +257 -113
  18. package/dist/resources/extensions/gsd/auto.js +7 -5
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +89 -0
  20. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -1
  21. package/dist/resources/extensions/gsd/branch-patterns.js +13 -0
  22. package/dist/resources/extensions/gsd/doctor-checks.js +5 -1234
  23. package/dist/resources/extensions/gsd/doctor-engine-checks.js +168 -0
  24. package/dist/resources/extensions/gsd/doctor-environment.js +28 -7
  25. package/dist/resources/extensions/gsd/doctor-git-checks.js +405 -0
  26. package/dist/resources/extensions/gsd/doctor-global-checks.js +74 -0
  27. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +600 -0
  28. package/dist/resources/extensions/gsd/doctor.js +9 -1
  29. package/dist/resources/extensions/gsd/extension-manifest.json +1 -1
  30. package/dist/resources/extensions/gsd/git-service.js +9 -10
  31. package/dist/resources/extensions/gsd/gsd-db.js +124 -1
  32. package/dist/resources/extensions/gsd/guided-flow-queue.js +10 -11
  33. package/dist/resources/extensions/gsd/markdown-renderer.js +33 -5
  34. package/dist/resources/extensions/gsd/preferences-types.js +2 -1
  35. package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
  36. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +9 -8
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +16 -13
  39. package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
  40. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
  41. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  43. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/plan-slice.md +8 -3
  47. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
  48. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  49. package/dist/resources/extensions/gsd/repo-identity.js +29 -0
  50. package/dist/resources/extensions/gsd/roadmap-slices.js +2 -2
  51. package/dist/resources/extensions/gsd/session-forensics.js +6 -11
  52. package/dist/resources/extensions/gsd/session-lock.js +67 -56
  53. package/dist/resources/extensions/gsd/state.js +34 -7
  54. package/dist/resources/extensions/gsd/templates/milestone-summary.md +8 -0
  55. package/dist/resources/extensions/gsd/templates/plan.md +16 -0
  56. package/dist/resources/extensions/gsd/templates/roadmap.md +13 -0
  57. package/dist/resources/extensions/gsd/templates/slice-summary.md +9 -0
  58. package/dist/resources/extensions/gsd/templates/task-plan.md +24 -0
  59. package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -1
  60. package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -3
  61. package/dist/resources/extensions/gsd/verdict-parser.js +84 -0
  62. package/dist/resources/extensions/gsd/worktree-resolver.js +24 -0
  63. package/dist/resources/extensions/gsd/worktree.js +3 -2
  64. package/dist/resources/extensions/remote-questions/config.js +3 -5
  65. package/dist/resources/extensions/search-the-web/native-search.js +8 -3
  66. package/dist/resources/extensions/search-the-web/tool-search.js +19 -2
  67. package/dist/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
  68. package/dist/web/standalone/.next/BUILD_ID +1 -1
  69. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  70. package/dist/web/standalone/.next/build-manifest.json +3 -3
  71. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  72. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  73. package/dist/web/standalone/.next/required-server-files.json +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.html +1 -1
  91. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  98. package/dist/web/standalone/.next/server/chunks/229.js +2 -2
  99. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  102. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  103. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  104. package/dist/web/standalone/.next/static/chunks/4024.7c75ac378de0f2b5.js +9 -0
  105. package/dist/web/standalone/.next/static/chunks/{webpack-0a4cd455ec4197d2.js → webpack-2473ce2c3879fff4.js} +1 -1
  106. package/dist/web/standalone/server.js +1 -1
  107. package/package.json +1 -1
  108. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  109. package/packages/pi-agent-core/dist/agent-loop.js +4 -1
  110. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  111. package/packages/pi-agent-core/src/agent-loop.ts +4 -1
  112. package/packages/pi-ai/dist/providers/openai-codex-responses.js +39 -10
  113. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  114. package/packages/pi-ai/src/providers/openai-codex-responses.ts +39 -8
  115. package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/blob-store.js +8 -3
  117. package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/discovery-cache.js +9 -2
  120. package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/retry-handler.js +1 -1
  122. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -32
  125. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/rpc/jsonl.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js +5 -0
  128. package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +0 -1
  131. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
  133. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  134. package/packages/pi-coding-agent/package.json +1 -1
  135. package/packages/pi-coding-agent/src/core/blob-store.ts +6 -3
  136. package/packages/pi-coding-agent/src/core/discovery-cache.ts +9 -2
  137. package/packages/pi-coding-agent/src/core/retry-handler.ts +1 -1
  138. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +7 -32
  139. package/packages/pi-coding-agent/src/modes/rpc/jsonl.ts +6 -0
  140. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +0 -2
  141. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -2
  142. package/pkg/package.json +1 -1
  143. package/src/resources/extensions/gsd/auto/infra-errors.ts +1 -0
  144. package/src/resources/extensions/gsd/auto/phases.ts +10 -11
  145. package/src/resources/extensions/gsd/auto/resolve.ts +3 -3
  146. package/src/resources/extensions/gsd/auto/run-unit.ts +2 -2
  147. package/src/resources/extensions/gsd/auto/session.ts +5 -0
  148. package/src/resources/extensions/gsd/auto/types.ts +13 -0
  149. package/src/resources/extensions/gsd/auto-artifact-paths.ts +19 -21
  150. package/src/resources/extensions/gsd/auto-dashboard.ts +5 -2
  151. package/src/resources/extensions/gsd/auto-dispatch.ts +39 -21
  152. package/src/resources/extensions/gsd/auto-loop.ts +1 -1
  153. package/src/resources/extensions/gsd/auto-post-unit.ts +18 -28
  154. package/src/resources/extensions/gsd/auto-prompts.ts +113 -19
  155. package/src/resources/extensions/gsd/auto-recovery.ts +65 -199
  156. package/src/resources/extensions/gsd/auto-start.ts +7 -27
  157. package/src/resources/extensions/gsd/auto-timers.ts +2 -2
  158. package/src/resources/extensions/gsd/auto-verification.ts +4 -7
  159. package/src/resources/extensions/gsd/auto-worktree.ts +305 -108
  160. package/src/resources/extensions/gsd/auto.ts +11 -10
  161. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +93 -0
  162. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
  163. package/src/resources/extensions/gsd/branch-patterns.ts +16 -0
  164. package/src/resources/extensions/gsd/doctor-checks.ts +5 -1291
  165. package/src/resources/extensions/gsd/doctor-engine-checks.ts +182 -0
  166. package/src/resources/extensions/gsd/doctor-environment.ts +30 -7
  167. package/src/resources/extensions/gsd/doctor-git-checks.ts +415 -0
  168. package/src/resources/extensions/gsd/doctor-global-checks.ts +84 -0
  169. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +626 -0
  170. package/src/resources/extensions/gsd/doctor.ts +9 -1
  171. package/src/resources/extensions/gsd/extension-manifest.json +1 -1
  172. package/src/resources/extensions/gsd/git-service.ts +7 -15
  173. package/src/resources/extensions/gsd/gsd-db.ts +150 -2
  174. package/src/resources/extensions/gsd/guided-flow-queue.ts +11 -12
  175. package/src/resources/extensions/gsd/markdown-renderer.ts +37 -4
  176. package/src/resources/extensions/gsd/preferences-types.ts +5 -1
  177. package/src/resources/extensions/gsd/preferences-validation.ts +37 -0
  178. package/src/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
  179. package/src/resources/extensions/gsd/prompts/complete-slice.md +9 -8
  180. package/src/resources/extensions/gsd/prompts/execute-task.md +16 -13
  181. package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
  182. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
  183. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  184. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  185. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  186. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  188. package/src/resources/extensions/gsd/prompts/plan-slice.md +8 -3
  189. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
  190. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  191. package/src/resources/extensions/gsd/repo-identity.ts +28 -0
  192. package/src/resources/extensions/gsd/roadmap-slices.ts +2 -2
  193. package/src/resources/extensions/gsd/session-forensics.ts +6 -11
  194. package/src/resources/extensions/gsd/session-lock.ts +92 -64
  195. package/src/resources/extensions/gsd/state.ts +38 -5
  196. package/src/resources/extensions/gsd/templates/milestone-summary.md +8 -0
  197. package/src/resources/extensions/gsd/templates/plan.md +16 -0
  198. package/src/resources/extensions/gsd/templates/roadmap.md +13 -0
  199. package/src/resources/extensions/gsd/templates/slice-summary.md +9 -0
  200. package/src/resources/extensions/gsd/templates/task-plan.md +24 -0
  201. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +2 -2
  202. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +35 -0
  203. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +1 -81
  204. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  205. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  206. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +9 -12
  207. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +115 -1
  208. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +65 -1
  209. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +50 -0
  210. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +189 -0
  211. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +156 -0
  212. package/src/resources/extensions/gsd/tests/git-service.test.ts +49 -0
  213. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  214. package/src/resources/extensions/gsd/tests/infra-error.test.ts +12 -2
  215. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +39 -0
  216. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  217. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  218. package/src/resources/extensions/gsd/tests/quality-gates.test.ts +347 -0
  219. package/src/resources/extensions/gsd/tests/queue-completed-milestone-perf.test.ts +155 -0
  220. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +2 -1
  221. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +32 -0
  222. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +26 -0
  223. package/src/resources/extensions/gsd/tests/run-uat.test.ts +20 -16
  224. package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +223 -0
  225. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +44 -4
  226. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -1
  227. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +2 -1
  228. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +0 -16
  229. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +67 -0
  230. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +1 -1
  231. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +204 -0
  232. package/src/resources/extensions/gsd/tools/plan-slice.ts +16 -0
  233. package/src/resources/extensions/gsd/tools/validate-milestone.ts +3 -3
  234. package/src/resources/extensions/gsd/types.ts +30 -0
  235. package/src/resources/extensions/gsd/verdict-parser.ts +95 -0
  236. package/src/resources/extensions/gsd/verification-gate.ts +0 -2
  237. package/src/resources/extensions/gsd/worktree-resolver.ts +31 -0
  238. package/src/resources/extensions/gsd/worktree.ts +3 -2
  239. package/src/resources/extensions/remote-questions/config.ts +3 -5
  240. package/src/resources/extensions/search-the-web/native-search.ts +8 -3
  241. package/src/resources/extensions/search-the-web/tool-search.ts +22 -2
  242. package/src/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
  243. package/dist/resources/extensions/gsd/auto-worktree-sync.js +0 -191
  244. package/dist/resources/extensions/gsd/resource-version.js +0 -97
  245. package/dist/web/standalone/.next/static/chunks/4024.11ca5c01938e5948.js +0 -9
  246. package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -234
  247. package/src/resources/extensions/gsd/resource-version.ts +0 -101
  248. /package/dist/web/standalone/.next/static/{PTL5V00OW8q4-092tUQKx → vNN0h0emdEi8l_npi8poE}/_buildManifest.js +0 -0
  249. /package/dist/web/standalone/.next/static/{PTL5V00OW8q4-092tUQKx → vNN0h0emdEi8l_npi8poE}/_ssgManifest.js +0 -0
@@ -48,11 +48,17 @@ export function attachJsonlLineReader(stream: Readable, onLine: (line: string) =
48
48
  }
49
49
  };
50
50
 
51
+ const onError = (_err: Error) => {
52
+ // Stream errors are non-fatal for JSONL reading
53
+ };
54
+
51
55
  stream.on("data", onData);
52
56
  stream.on("end", onEnd);
57
+ stream.on("error", onError);
53
58
 
54
59
  return () => {
55
60
  stream.off("data", onData);
56
61
  stream.off("end", onEnd);
62
+ stream.off("error", onError);
57
63
  };
58
64
  }
@@ -488,8 +488,6 @@ export class RpcClient {
488
488
  const fullCommand = { ...command, id } as RpcCommand;
489
489
 
490
490
  return new Promise((resolve, reject) => {
491
- this.pendingRequests.set(id, { resolve, reject });
492
-
493
491
  const timeout = setTimeout(() => {
494
492
  this.pendingRequests.delete(id);
495
493
  reject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`));
@@ -710,8 +710,8 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
710
710
  }
711
711
 
712
712
  default: {
713
- const unknownCommand = command as { type: string };
714
- return error(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
713
+ const unknownCommand = command as { type: string; id?: string };
714
+ return error(unknownCommand.id, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
715
715
  }
716
716
  }
717
717
  };
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "2.48.0",
3
+ "version": "2.49.0",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"
@@ -18,6 +18,7 @@ export const INFRA_ERROR_CODES: ReadonlySet<string> = new Set([
18
18
  "EDQUOT", // disk quota exceeded
19
19
  "EMFILE", // too many open files (process)
20
20
  "ENFILE", // too many open files (system)
21
+ "EAGAIN", // resource temporarily unavailable (resource exhaustion)
21
22
  "ECONNREFUSED", // connection refused (offline / local server down)
22
23
  "ENOTFOUND", // DNS lookup failed (offline / no network)
23
24
  "ENETUNREACH", // network unreachable (offline / no route)
@@ -1039,17 +1039,16 @@ export async function runUnitPhase(
1039
1039
  );
1040
1040
 
1041
1041
  // Tag the most recent window entry with error info for stuck detection
1042
- if (unitResult.status === "error" || unitResult.status === "cancelled") {
1043
- const lastEntry = loopState.recentUnits[loopState.recentUnits.length - 1];
1044
- if (lastEntry) {
1042
+ const lastEntry = loopState.recentUnits[loopState.recentUnits.length - 1];
1043
+ if (lastEntry) {
1044
+ if (unitResult.errorContext) {
1045
+ lastEntry.error = `${unitResult.errorContext.category}:${unitResult.errorContext.message}`.slice(0, 200);
1046
+ } else if (unitResult.status === "error" || unitResult.status === "cancelled") {
1045
1047
  lastEntry.error = `${unitResult.status}:${unitType}/${unitId}`;
1046
- }
1047
- } else if (unitResult.event?.messages?.length) {
1048
- const lastMsg = unitResult.event.messages[unitResult.event.messages.length - 1];
1049
- const msgStr = typeof lastMsg === "string" ? lastMsg : JSON.stringify(lastMsg);
1050
- if (/error|fail|exception/i.test(msgStr)) {
1051
- const lastEntry = loopState.recentUnits[loopState.recentUnits.length - 1];
1052
- if (lastEntry) {
1048
+ } else if (unitResult.event?.messages?.length) {
1049
+ const lastMsg = unitResult.event.messages[unitResult.event.messages.length - 1];
1050
+ const msgStr = typeof lastMsg === "string" ? lastMsg : JSON.stringify(lastMsg);
1051
+ if (/error|fail|exception/i.test(msgStr)) {
1053
1052
  lastEntry.error = msgStr.slice(0, 200);
1054
1053
  }
1055
1054
  }
@@ -1122,7 +1121,7 @@ export async function runUnitPhase(
1122
1121
  s.unitRecoveryCount.delete(`${unitType}/${unitId}`);
1123
1122
  }
1124
1123
 
1125
- deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "unit-end", data: { unitType, unitId, status: unitResult.status, artifactVerified }, causedBy: { flowId: ic.flowId, seq: unitStartSeq } });
1124
+ deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "unit-end", data: { unitType, unitId, status: unitResult.status, artifactVerified, ...(unitResult.errorContext ? { errorContext: unitResult.errorContext } : {}) }, causedBy: { flowId: ic.flowId, seq: unitStartSeq } });
1126
1125
 
1127
1126
  return { action: "next", data: { unitStartedAt: s.currentUnit.startedAt } };
1128
1127
  }
@@ -8,7 +8,7 @@
8
8
  * Imports from: auto/types
9
9
  */
10
10
 
11
- import type { UnitResult, AgentEndEvent } from "./types.js";
11
+ import type { UnitResult, AgentEndEvent, ErrorContext } from "./types.js";
12
12
  import type { AutoSession } from "./session.js";
13
13
  import { debugLog } from "../debug-logger.js";
14
14
 
@@ -77,12 +77,12 @@ export function isSessionSwitchInFlight(): boolean {
77
77
  * blocks to ensure the autoLoop is never stuck awaiting a promise that
78
78
  * will never resolve. Safe to call when no resolver is pending (no-op).
79
79
  */
80
- export function resolveAgentEndCancelled(): void {
80
+ export function resolveAgentEndCancelled(errorContext?: ErrorContext): void {
81
81
  if (_currentResolve) {
82
82
  debugLog("resolveAgentEndCancelled", { status: "resolving-cancelled" });
83
83
  const r = _currentResolve;
84
84
  _currentResolve = null;
85
- r({ status: "cancelled" });
85
+ r({ status: "cancelled", ...(errorContext ? { errorContext } : {}) });
86
86
  }
87
87
  }
88
88
 
@@ -58,13 +58,13 @@ export async function runUnit(
58
58
  unitId,
59
59
  error: msg,
60
60
  });
61
- return { status: "cancelled" };
61
+ return { status: "cancelled", errorContext: { message: `Session creation failed: ${msg}`, category: "session-failed", isTransient: true } };
62
62
  }
63
63
  if (sessionTimeoutHandle) clearTimeout(sessionTimeoutHandle);
64
64
 
65
65
  if (sessionResult.cancelled) {
66
66
  debugLog("runUnit-session-timeout", { unitType, unitId });
67
- return { status: "cancelled" };
67
+ return { status: "cancelled", errorContext: { message: "Session creation timed out", category: "timeout", isTransient: true } };
68
68
  }
69
69
 
70
70
  if (!s.active) {
@@ -118,6 +118,10 @@ export class AutoSession {
118
118
  // ── Sidecar queue ─────────────────────────────────────────────────────
119
119
  sidecarQueue: SidecarItem[] = [];
120
120
 
121
+ // ── Isolation degradation ────────────────────────────────────────────
122
+ /** Set to true when worktree creation fails; prevents merge of nonexistent branch. */
123
+ isolationDegraded = false;
124
+
121
125
  // ── Dispatch circuit breakers ──────────────────────────────────────
122
126
  rewriteAttemptCount = 0;
123
127
 
@@ -200,6 +204,7 @@ export class AutoSession {
200
204
  this.pendingQuickTasks = [];
201
205
  this.sidecarQueue = [];
202
206
  this.rewriteAttemptCount = 0;
207
+ this.isolationDegraded = false;
203
208
 
204
209
  // Signal handler
205
210
  this.sigtermHandler = null;
@@ -47,12 +47,25 @@ export interface AgentEndEvent {
47
47
  messages: unknown[];
48
48
  }
49
49
 
50
+ /**
51
+ * Structured error context attached to a UnitResult when the unit ends
52
+ * due to an infrastructure or timeout error (not user-driven cancellation).
53
+ */
54
+ export interface ErrorContext {
55
+ message: string;
56
+ category: "provider" | "timeout" | "idle" | "network" | "aborted" | "session-failed" | "unknown";
57
+ stopReason?: string;
58
+ isTransient?: boolean;
59
+ retryAfterMs?: number;
60
+ }
61
+
50
62
  /**
51
63
  * Result of a single unit execution (one iteration of the loop).
52
64
  */
53
65
  export interface UnitResult {
54
66
  status: "completed" | "cancelled" | "error";
55
67
  event?: AgentEndEvent;
68
+ errorContext?: ErrorContext;
56
69
  }
57
70
 
58
71
  // ─── Phase pipeline types ────────────────────────────────────────────────────
@@ -13,6 +13,7 @@ import {
13
13
  buildSliceFileName,
14
14
  buildTaskFileName,
15
15
  } from "./paths.js";
16
+ import { parseUnitId } from "./unit-id.js";
16
17
  import { join } from "node:path";
17
18
 
18
19
  /**
@@ -23,9 +24,7 @@ export function resolveExpectedArtifactPath(
23
24
  unitId: string,
24
25
  base: string,
25
26
  ): string | null {
26
- const parts = unitId.split("/");
27
- const mid = parts[0]!;
28
- const sid = parts[1];
27
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
29
28
  switch (unitType) {
30
29
  case "discuss-milestone": {
31
30
  const dir = resolveMilestonePath(base, mid);
@@ -53,10 +52,9 @@ export function resolveExpectedArtifactPath(
53
52
  }
54
53
  case "run-uat": {
55
54
  const dir = resolveSlicePath(base, mid, sid!);
56
- return dir ? join(dir, buildSliceFileName(sid!, "UAT-RESULT")) : null;
55
+ return dir ? join(dir, buildSliceFileName(sid!, "UAT")) : null;
57
56
  }
58
57
  case "execute-task": {
59
- const tid = parts[2];
60
58
  const dir = resolveSlicePath(base, mid, sid!);
61
59
  return dir && tid
62
60
  ? join(dir, "tasks", buildTaskFileName(tid, "SUMMARY"))
@@ -80,6 +78,9 @@ export function resolveExpectedArtifactPath(
80
78
  }
81
79
  case "rewrite-docs":
82
80
  return null;
81
+ case "gate-evaluate":
82
+ // Gate evaluate writes to DB quality_gates table — verified via state derivation
83
+ return null;
83
84
  case "reactive-execute":
84
85
  // Reactive execute produces multiple task summaries — verified separately
85
86
  return null;
@@ -93,38 +94,35 @@ export function diagnoseExpectedArtifact(
93
94
  unitId: string,
94
95
  base: string,
95
96
  ): string | null {
96
- const parts = unitId.split("/");
97
- const mid = parts[0];
98
- const sid = parts[1];
97
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
99
98
  switch (unitType) {
100
99
  case "discuss-milestone":
101
- return `${relMilestoneFile(base, mid!, "CONTEXT")} (milestone context from discussion)`;
100
+ return `${relMilestoneFile(base, mid, "CONTEXT")} (milestone context from discussion)`;
102
101
  case "research-milestone":
103
- return `${relMilestoneFile(base, mid!, "RESEARCH")} (milestone research)`;
102
+ return `${relMilestoneFile(base, mid, "RESEARCH")} (milestone research)`;
104
103
  case "plan-milestone":
105
- return `${relMilestoneFile(base, mid!, "ROADMAP")} (milestone roadmap)`;
104
+ return `${relMilestoneFile(base, mid, "ROADMAP")} (milestone roadmap)`;
106
105
  case "research-slice":
107
- return `${relSliceFile(base, mid!, sid!, "RESEARCH")} (slice research)`;
106
+ return `${relSliceFile(base, mid, sid!, "RESEARCH")} (slice research)`;
108
107
  case "plan-slice":
109
- return `${relSliceFile(base, mid!, sid!, "PLAN")} (slice plan)`;
108
+ return `${relSliceFile(base, mid, sid!, "PLAN")} (slice plan)`;
110
109
  case "execute-task": {
111
- const tid = parts[2];
112
- return `Task ${tid} marked [x] in ${relSliceFile(base, mid!, sid!, "PLAN")} + summary written`;
110
+ return `Task ${tid} marked [x] in ${relSliceFile(base, mid, sid!, "PLAN")} + summary written`;
113
111
  }
114
112
  case "complete-slice":
115
- return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid!, "ROADMAP")} + summary + UAT written`;
113
+ return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid, "ROADMAP")} + summary + UAT written`;
116
114
  case "replan-slice":
117
- return `${relSliceFile(base, mid!, sid!, "REPLAN")} + updated ${relSliceFile(base, mid!, sid!, "PLAN")}`;
115
+ return `${relSliceFile(base, mid, sid!, "REPLAN")} + updated ${relSliceFile(base, mid, sid!, "PLAN")}`;
118
116
  case "rewrite-docs":
119
117
  return "Active overrides resolved in .gsd/OVERRIDES.md + plan documents updated";
120
118
  case "reassess-roadmap":
121
- return `${relSliceFile(base, mid!, sid!, "ASSESSMENT")} (roadmap reassessment)`;
119
+ return `${relSliceFile(base, mid, sid!, "ASSESSMENT")} (roadmap reassessment)`;
122
120
  case "run-uat":
123
- return `${relSliceFile(base, mid!, sid!, "UAT-RESULT")} (UAT result)`;
121
+ return `${relSliceFile(base, mid, sid!, "UAT")} (UAT result)`;
124
122
  case "validate-milestone":
125
- return `${relMilestoneFile(base, mid!, "VALIDATION")} (milestone validation report)`;
123
+ return `${relMilestoneFile(base, mid, "VALIDATION")} (milestone validation report)`;
126
124
  case "complete-milestone":
127
- return `${relMilestoneFile(base, mid!, "SUMMARY")} (milestone summary)`;
125
+ return `${relMilestoneFile(base, mid, "SUMMARY")} (milestone summary)`;
128
126
  default:
129
127
  return null;
130
128
  }
@@ -25,6 +25,7 @@ import { computeProgressScore } from "./progress-score.js";
25
25
  import { getActiveWorktreeName } from "./worktree-command.js";
26
26
  import { loadEffectiveGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
27
27
  import { resolveServiceTierIcon, getEffectiveServiceTier } from "./service-tier.js";
28
+ import { parseUnitId } from "./unit-id.js";
28
29
 
29
30
  // ─── UAT Slice Extraction ─────────────────────────────────────────────────────
30
31
 
@@ -33,8 +34,8 @@ import { resolveServiceTierIcon, getEffectiveServiceTier } from "./service-tier.
33
34
  * Returns null if the format doesn't match.
34
35
  */
35
36
  export function extractUatSliceId(unitId: string): string | null {
36
- const parts = unitId.split("/");
37
- if (parts.length >= 2 && parts[1]!.startsWith("S")) return parts[1]!;
37
+ const { slice } = parseUnitId(unitId);
38
+ if (slice?.startsWith("S")) return slice;
38
39
  return null;
39
40
  }
40
41
 
@@ -151,6 +152,8 @@ export function describeNextUnit(state: GSDState): { label: string; description:
151
152
  return { label: `Replan ${sid}: ${sTitle}`, description: "Blocker found — replan the slice." };
152
153
  case "completing-milestone":
153
154
  return { label: "Complete milestone", description: "Write milestone summary." };
155
+ case "evaluating-gates":
156
+ return { label: `Evaluate gates for ${sid}: ${sTitle}`, description: "Parallel quality gate assessment before execution." };
154
157
  default:
155
158
  return { label: "Continue", description: "Execute the next step." };
156
159
  }
@@ -13,7 +13,8 @@ import type { GSDState } from "./types.js";
13
13
  import type { GSDPreferences } from "./preferences.js";
14
14
  import type { UatType } from "./files.js";
15
15
  import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
16
- import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
16
+ import { isDbAvailable, getMilestoneSlices, getPendingGates, markAllGatesOmitted } from "./gsd-db.js";
17
+ import { extractVerdict, isAcceptableUatVerdict } from "./verdict-parser.js";
17
18
 
18
19
  import {
19
20
  resolveMilestoneFile,
@@ -43,6 +44,7 @@ import {
43
44
  buildReassessRoadmapPrompt,
44
45
  buildRewriteDocsPrompt,
45
46
  buildReactiveExecutePrompt,
47
+ buildGateEvaluatePrompt,
46
48
  checkNeedsReassessment,
47
49
  checkNeedsRunUat,
48
50
  } from "./auto-prompts.js";
@@ -184,30 +186,14 @@ export const DISPATCH_RULES: DispatchRule[] = [
184
186
  }
185
187
 
186
188
  for (const sliceId of completedSliceIds) {
187
- const resultFile = resolveSliceFile(basePath, mid, sliceId, "UAT-RESULT");
189
+ const resultFile = resolveSliceFile(basePath, mid, sliceId, "UAT");
188
190
  if (!resultFile) continue;
189
191
  const content = await loadFile(resultFile);
190
192
  if (!content) continue;
191
- const verdictMatch = content.match(/verdict:\s*([\w-]+)/i);
192
- const verdict = verdictMatch?.[1]?.toLowerCase();
193
-
194
- // Determine acceptable verdicts based on UAT type.
195
- // mixed / human-experience / live-runtime modes may legitimately
196
- // produce PARTIAL when all automatable checks pass but human-only
197
- // checks remain — this should not block progression.
198
- const acceptableVerdicts: string[] = ["pass", "passed"];
199
- const uatFile = resolveSliceFile(basePath, mid, sliceId, "UAT");
200
- if (uatFile) {
201
- const uatContent = await loadFile(uatFile);
202
- if (uatContent) {
203
- const uatType = extractUatType(uatContent);
204
- if (uatType === "mixed" || uatType === "human-experience" || uatType === "live-runtime") {
205
- acceptableVerdicts.push("partial");
206
- }
207
- }
208
- }
193
+ const verdict = extractVerdict(content);
194
+ const uatType = extractUatType(content);
209
195
 
210
- if (verdict && !acceptableVerdicts.includes(verdict)) {
196
+ if (verdict && !isAcceptableUatVerdict(verdict, uatType)) {
211
197
  return {
212
198
  action: "stop" as const,
213
199
  reason: `UAT verdict for ${sliceId} is "${verdict}" — blocking progression until resolved.\nReview the UAT result and update the verdict to PASS, or re-run /gsd auto after fixing.`,
@@ -348,6 +334,38 @@ export const DISPATCH_RULES: DispatchRule[] = [
348
334
  };
349
335
  },
350
336
  },
337
+ {
338
+ name: "evaluating-gates → gate-evaluate",
339
+ match: async ({ state, mid, midTitle, basePath, prefs }) => {
340
+ if (state.phase !== "evaluating-gates") return null;
341
+ if (!state.activeSlice) return missingSliceStop(mid, state.phase);
342
+ const sid = state.activeSlice.id;
343
+ const sTitle = state.activeSlice.title;
344
+
345
+ // Gate evaluation is opt-in via preferences
346
+ const gateConfig = prefs?.gate_evaluation;
347
+ if (!gateConfig?.enabled) {
348
+ markAllGatesOmitted(mid, sid);
349
+ return { action: "skip" };
350
+ }
351
+
352
+ const pending = getPendingGates(mid, sid, "slice");
353
+ if (pending.length === 0) return { action: "skip" };
354
+
355
+ return {
356
+ action: "dispatch",
357
+ unitType: "gate-evaluate",
358
+ unitId: `${mid}/${sid}/gates+${pending.map(g => g.gate_id).join(",")}`,
359
+ prompt: await buildGateEvaluatePrompt(
360
+ mid,
361
+ midTitle,
362
+ sid,
363
+ sTitle,
364
+ basePath,
365
+ ),
366
+ };
367
+ },
368
+ },
351
369
  {
352
370
  name: "replanning-slice → replan-slice",
353
371
  match: async ({ state, mid, midTitle, basePath }) => {
@@ -13,4 +13,4 @@ export { resolveAgentEnd, resolveAgentEndCancelled, isSessionSwitchInFlight, _re
13
13
  export { detectStuck } from "./auto/detect-stuck.js";
14
14
  export { runUnit } from "./auto/run-unit.js";
15
15
  export type { LoopDeps } from "./auto/loop-deps.js";
16
- export type { AgentEndEvent, UnitResult } from "./auto/types.js";
16
+ export type { AgentEndEvent, ErrorContext, UnitResult } from "./auto/types.js";
@@ -23,6 +23,7 @@ import {
23
23
  buildTaskFileName,
24
24
  } from "./paths.js";
25
25
  import { invalidateAllCaches } from "./cache.js";
26
+ import { parseUnitId } from "./unit-id.js";
26
27
  import { closeoutUnit, type CloseoutOptions } from "./auto-unit-closeout.js";
27
28
  import {
28
29
  autoCommitCurrentBranch,
@@ -33,7 +34,7 @@ import {
33
34
  resolveExpectedArtifactPath,
34
35
  } from "./auto-recovery.js";
35
36
  import { regenerateIfMissing } from "./workflow-projections.js";
36
- import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
37
+ import { syncStateToProjectRoot } from "./auto-worktree.js";
37
38
  import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
38
39
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
39
40
  import { consumeSignal } from "./session-status-io.js";
@@ -84,6 +85,15 @@ export interface RogueFileWrite {
84
85
  * in postUnitPostVerification() eventually ingests rogue files, but explicit
85
86
  * detection provides immediate diagnostics so operators know the prompt failed.
86
87
  */
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ function hasNonEmptyFields(row: Record<string, any> | null, fields: string[]): boolean {
90
+ if (!row) return false;
91
+ return fields.some(f => String(row[f] || "").trim().length > 0);
92
+ }
93
+
94
+ const MILESTONE_PLANNING_FIELDS = ["title", "vision", "requirement_coverage", "boundary_map_markdown"];
95
+ const SLICE_PLANNING_FIELDS = ["title", "demo", "risk", "depends"];
96
+
87
97
  export function detectRogueFileWrites(
88
98
  unitType: string,
89
99
  unitId: string,
@@ -91,11 +101,10 @@ export function detectRogueFileWrites(
91
101
  ): RogueFileWrite[] {
92
102
  if (!isDbAvailable()) return [];
93
103
 
94
- const parts = unitId.split("/");
104
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
95
105
  const rogues: RogueFileWrite[] = [];
96
106
 
97
107
  if (unitType === "execute-task") {
98
- const [mid, sid, tid] = parts;
99
108
  if (!mid || !sid || !tid) return [];
100
109
 
101
110
  const summaryPath = resolveTaskFile(basePath, mid, sid, tid, "SUMMARY");
@@ -106,7 +115,6 @@ export function detectRogueFileWrites(
106
115
  rogues.push({ path: summaryPath, unitType, unitId });
107
116
  }
108
117
  } else if (unitType === "complete-slice") {
109
- const [mid, sid] = parts;
110
118
  if (!mid || !sid) return [];
111
119
 
112
120
  const summaryPath = resolveSliceFile(basePath, mid, sid, "SUMMARY");
@@ -117,37 +125,25 @@ export function detectRogueFileWrites(
117
125
  rogues.push({ path: summaryPath, unitType, unitId });
118
126
  }
119
127
  } else if (unitType === "plan-milestone") {
120
- const [mid] = parts;
121
128
  if (!mid) return [];
122
129
 
123
130
  const roadmapPath = resolveMilestoneFile(basePath, mid, "ROADMAP");
124
131
  if (!roadmapPath || !existsSync(roadmapPath)) return [];
125
132
 
126
133
  const dbRow = getMilestone(mid);
127
- const hasPlanningState = !!dbRow && (
128
- String(dbRow.title || "").trim().length > 0 ||
129
- String(dbRow.vision || "").trim().length > 0 ||
130
- String(dbRow.requirement_coverage || "").trim().length > 0 ||
131
- String(dbRow.boundary_map_markdown || "").trim().length > 0
132
- );
134
+ const hasPlanningState = hasNonEmptyFields(dbRow, MILESTONE_PLANNING_FIELDS);
133
135
 
134
136
  if (!hasPlanningState) {
135
137
  rogues.push({ path: roadmapPath, unitType, unitId });
136
138
  }
137
139
  } else if (unitType === "plan-slice" || unitType === "replan-slice") {
138
- const [mid, sid] = parts;
139
140
  if (!mid || !sid) return [];
140
141
 
141
142
  const planPath = resolveSliceFile(basePath, mid, sid, "PLAN");
142
143
  if (!planPath || !existsSync(planPath)) return [];
143
144
 
144
145
  const dbRow = getSlice(mid, sid);
145
- const hasPlanningState = !!dbRow && (
146
- String(dbRow.title || "").trim().length > 0 ||
147
- String(dbRow.demo || "").trim().length > 0 ||
148
- String(dbRow.risk || "").trim().length > 0 ||
149
- String(dbRow.depends || "").trim().length > 0
150
- );
146
+ const hasPlanningState = hasNonEmptyFields(dbRow, SLICE_PLANNING_FIELDS);
151
147
 
152
148
  if (!hasPlanningState) {
153
149
  rogues.push({ path: planPath, unitType, unitId });
@@ -159,7 +155,6 @@ export function detectRogueFileWrites(
159
155
  rogues.push({ path: replanPath, unitType, unitId });
160
156
  }
161
157
  } else if (unitType === "reassess-roadmap") {
162
- const [mid, sid] = parts;
163
158
  if (!mid || !sid) return [];
164
159
 
165
160
  const assessPath = resolveSliceFile(basePath, mid, sid, "ASSESSMENT");
@@ -176,7 +171,6 @@ export function detectRogueFileWrites(
176
171
  }
177
172
  }
178
173
  } else if (unitType === "plan-task") {
179
- const [mid, sid, tid] = parts;
180
174
  if (!mid || !sid || !tid) return [];
181
175
 
182
176
  const taskPlanPath = resolveTaskFile(basePath, mid, sid, tid, "PLAN");
@@ -249,8 +243,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
249
243
  let taskContext: TaskCommitContext | undefined;
250
244
 
251
245
  if (s.currentUnit.type === "execute-task") {
252
- const parts = s.currentUnit.id.split("/");
253
- const [mid, sid, tid] = parts;
246
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
254
247
  if (mid && sid && tid) {
255
248
  const summaryPath = resolveTaskFile(s.basePath, mid, sid, tid, "SUMMARY");
256
249
  if (summaryPath) {
@@ -354,8 +347,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
354
347
  // Reactive state cleanup on slice completion
355
348
  if (s.currentUnit.type === "complete-slice") {
356
349
  try {
357
- const parts = s.currentUnit.id.split("/");
358
- const [mid, sid] = parts;
350
+ const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
359
351
  if (mid && sid) {
360
352
  const { clearReactiveState } = await import("./reactive-graph.js");
361
353
  clearReactiveState(s.basePath, mid, sid);
@@ -440,8 +432,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
440
432
  // from DB data before giving up (e.g. research-slice produces PLAN from engine).
441
433
  if (!triggerArtifactVerified) {
442
434
  try {
443
- const parts = s.currentUnit.id.split("/");
444
- const [mid, sid] = parts;
435
+ const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
445
436
  if (mid && sid) {
446
437
  const regenerated = regenerateIfMissing(s.basePath, mid, sid, "PLAN");
447
438
  if (regenerated) {
@@ -541,8 +532,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
541
532
 
542
533
  // ── State reset: undo the completion so deriveState re-derives the unit ──
543
534
  try {
544
- const parts = trigger.unitId.split("/");
545
- const [mid, sid, tid] = parts;
535
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(trigger.unitId);
546
536
 
547
537
  // 1. Reset task status in DB and re-render plan checkboxes
548
538
  if (mid && sid && tid) {