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
@@ -6,8 +6,9 @@
6
6
  * utility.
7
7
  */
8
8
 
9
- import { loadFile, parseContinue, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
9
+ import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
10
10
  import type { Override, UatType } from "./files.js";
11
+ import { hasVerdict, getUatType } from "./verdict-parser.js";
11
12
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
12
13
  import {
13
14
  resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
@@ -23,6 +24,7 @@ import { getLoadedSkills, type Skill } from "@gsd/pi-coding-agent";
23
24
  import { join, basename } from "node:path";
24
25
  import { existsSync } from "node:fs";
25
26
  import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
27
+ import { getPendingGates } from "./gsd-db.js";
26
28
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
27
29
 
28
30
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
@@ -419,9 +421,17 @@ function resolvePreferredSkillNames(
419
421
  .map(skill => normalizeSkillReference(skill.name));
420
422
  }
421
423
 
424
+ /** Skill names must be lowercase alphanumeric with hyphens — reject anything else
425
+ * to prevent prompt injection via crafted directory names. */
426
+ const SAFE_SKILL_NAME = /^[a-z0-9][a-z0-9-]*$/;
427
+
422
428
  function formatSkillActivationBlock(skillNames: string[]): string {
423
- if (skillNames.length === 0) return "";
424
- const calls = skillNames.map(name => `Call Skill('${name}')`).join('. ');
429
+ const safe = skillNames.filter(name => SAFE_SKILL_NAME.test(name));
430
+ if (safe.length === 0) return "";
431
+ // Use explicit parameter syntax so LLMs pass { skill: "..." } instead of { name: "..." }.
432
+ // The function-call-like syntax `Skill('name')` led LLMs to infer a positional
433
+ // parameter name, causing tool validation failures — see #2224.
434
+ const calls = safe.map(name => `Call Skill({ skill: '${name}' })`).join('. ');
425
435
  return `<skill_activation>${calls}.</skill_activation>`;
426
436
  }
427
437
 
@@ -772,12 +782,9 @@ export async function checkNeedsRunUat(
772
782
  if (!uatFile) return null;
773
783
  const uatContent = await loadFile(uatFile);
774
784
  if (!uatContent) return null;
775
- const uatResultFile = resolveSliceFile(base, mid, sid, "UAT-RESULT");
776
- if (uatResultFile) {
777
- const hasResult = !!(await loadFile(uatResultFile));
778
- if (hasResult) return null;
779
- }
780
- const uatType = extractUatType(uatContent) ?? "artifact-driven";
785
+ // If the UAT file already contains a verdict, UAT has been run — skip
786
+ if (hasVerdict(uatContent)) return null;
787
+ const uatType = getUatType(uatContent);
781
788
  return { sliceId: sid, uatType };
782
789
  }
783
790
  }
@@ -799,12 +806,9 @@ export async function checkNeedsRunUat(
799
806
  if (!uatFileFb) return null;
800
807
  const uatContentFb = await loadFile(uatFileFb);
801
808
  if (!uatContentFb) return null;
802
- const uatResultFb = resolveSliceFile(base, mid, uatSid, "UAT-RESULT");
803
- if (uatResultFb) {
804
- const hasResultFb = !!(await loadFile(uatResultFb));
805
- if (hasResultFb) return null;
806
- }
807
- const uatTypeFb = extractUatType(uatContentFb) ?? "artifact-driven";
809
+ // If the UAT file already contains a verdict, UAT has been run — skip
810
+ if (hasVerdict(uatContentFb)) return null;
811
+ const uatTypeFb = getUatType(uatContentFb);
808
812
  return { sliceId: uatSid, uatType: uatTypeFb };
809
813
  }
810
814
 
@@ -1349,8 +1353,8 @@ export async function buildValidateMilestonePrompt(
1349
1353
  const summaryRel = relSliceFile(base, mid, sid, "SUMMARY");
1350
1354
  inlined.push(await inlineFile(summaryPath, summaryRel, `${sid} Summary`));
1351
1355
 
1352
- const uatPath = resolveSliceFile(base, mid, sid, "UAT-RESULT");
1353
- const uatRel = relSliceFile(base, mid, sid, "UAT-RESULT");
1356
+ const uatPath = resolveSliceFile(base, mid, sid, "UAT");
1357
+ const uatRel = relSliceFile(base, mid, sid, "UAT");
1354
1358
  const uatInline = await inlineFileOptional(uatPath, uatRel, `${sid} UAT Result`);
1355
1359
  if (uatInline) inlined.push(uatInline);
1356
1360
  }
@@ -1501,8 +1505,8 @@ export async function buildRunUatPrompt(
1501
1505
 
1502
1506
  const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1503
1507
 
1504
- const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
1505
- const uatType = extractUatType(uatContent) ?? "artifact-driven";
1508
+ const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT"));
1509
+ const uatType = getUatType(uatContent);
1506
1510
 
1507
1511
  return loadPrompt("run-uat", {
1508
1512
  workingDirectory: base,
@@ -1659,6 +1663,96 @@ export async function buildReactiveExecutePrompt(
1659
1663
  });
1660
1664
  }
1661
1665
 
1666
+ // ─── Gate Evaluation ──────────────────────────────────────────────────────
1667
+
1668
+ const GATE_QUESTIONS: Record<string, { question: string; guidance: string }> = {
1669
+ Q3: {
1670
+ question: "How can this be exploited?",
1671
+ guidance: [
1672
+ "Identify abuse scenarios: parameter tampering, replay attacks, privilege escalation.",
1673
+ "Map data exposure risks: PII, tokens, secrets accessible through this slice.",
1674
+ "Define input trust boundaries: untrusted user input reaching DB, API, or filesystem.",
1675
+ "If none apply, return verdict 'omitted' with rationale explaining why.",
1676
+ ].join("\n"),
1677
+ },
1678
+ Q4: {
1679
+ question: "What existing promises does this break?",
1680
+ guidance: [
1681
+ "List which existing requirements (R001, R003, etc.) are touched by this slice.",
1682
+ "Identify what must be re-tested after shipping.",
1683
+ "Flag decisions that should be revisited given the new scope.",
1684
+ "If no existing requirements are affected, return verdict 'omitted'.",
1685
+ ].join("\n"),
1686
+ },
1687
+ };
1688
+
1689
+ export async function buildGateEvaluatePrompt(
1690
+ mid: string, midTitle: string, sid: string, sTitle: string,
1691
+ base: string,
1692
+ ): Promise<string> {
1693
+ const pending = getPendingGates(mid, sid, "slice");
1694
+
1695
+ // Load the slice plan for context
1696
+ const planFile = resolveSliceFile(base, mid, sid, "PLAN");
1697
+ const planContent = planFile ? (await loadFile(planFile)) ?? "(plan file empty)" : "(plan file not found)";
1698
+
1699
+ // Build per-gate subagent prompts
1700
+ const subagentSections: string[] = [];
1701
+ const gateListLines: string[] = [];
1702
+
1703
+ for (const gate of pending) {
1704
+ const meta = GATE_QUESTIONS[gate.gate_id];
1705
+ if (!meta) continue;
1706
+
1707
+ gateListLines.push(`- **${gate.gate_id}**: ${meta.question}`);
1708
+
1709
+ const subPrompt = [
1710
+ `You are evaluating quality gate **${gate.gate_id}** for slice ${sid} (${sTitle}).`,
1711
+ "",
1712
+ `## Question: ${meta.question}`,
1713
+ "",
1714
+ meta.guidance,
1715
+ "",
1716
+ "## Slice Plan",
1717
+ "",
1718
+ planContent,
1719
+ "",
1720
+ "## Instructions",
1721
+ "",
1722
+ "Analyze the slice plan above and answer the gate question.",
1723
+ `Call the \`gsd_save_gate_result\` tool with:`,
1724
+ `- \`milestoneId\`: "${mid}"`,
1725
+ `- \`sliceId\`: "${sid}"`,
1726
+ `- \`gateId\`: "${gate.gate_id}"`,
1727
+ "- `verdict`: \"pass\" (no concerns), \"flag\" (concerns found), or \"omitted\" (not applicable)",
1728
+ "- `rationale`: one-sentence justification",
1729
+ "- `findings`: detailed markdown findings (or empty if omitted)",
1730
+ ].join("\n");
1731
+
1732
+ subagentSections.push([
1733
+ `### ${gate.gate_id}: ${meta.question}`,
1734
+ "",
1735
+ "Use this as the prompt for a `subagent` call:",
1736
+ "",
1737
+ "```",
1738
+ subPrompt,
1739
+ "```",
1740
+ ].join("\n"));
1741
+ }
1742
+
1743
+ return loadPrompt("gate-evaluate", {
1744
+ workingDirectory: base,
1745
+ milestoneId: mid,
1746
+ milestoneTitle: midTitle,
1747
+ sliceId: sid,
1748
+ sliceTitle: sTitle,
1749
+ slicePlanContent: planContent,
1750
+ gateCount: String(pending.length),
1751
+ gateList: gateListLines.join("\n"),
1752
+ subagentPrompts: subagentSections.join("\n\n---\n\n"),
1753
+ });
1754
+ }
1755
+
1662
1756
  export async function buildRewriteDocsPrompt(
1663
1757
  mid: string, midTitle: string,
1664
1758
  activeSlice: { id: string; title: string } | null,
@@ -10,7 +10,6 @@
10
10
  import type { ExtensionContext } from "@gsd/pi-coding-agent";
11
11
  import { parseUnitId } from "./unit-id.js";
12
12
  import { atomicWriteSync } from "./atomic-write.js";
13
- import { clearUnitRuntimeRecord } from "./unit-runtime.js";
14
13
  import { clearParseCache } from "./files.js";
15
14
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
16
15
  import { isDbAvailable, getTask, getSlice, getSliceTasks } from "./gsd-db.js";
@@ -24,18 +23,13 @@ import {
24
23
  nativeResetHard,
25
24
  } from "./native-git-bridge.js";
26
25
  import {
27
- resolveMilestonePath,
28
26
  resolveSlicePath,
29
27
  resolveSliceFile,
30
28
  resolveTasksDir,
31
29
  resolveTaskFiles,
32
30
  relMilestoneFile,
33
31
  relSliceFile,
34
- relSlicePath,
35
- relTaskFile,
36
- buildMilestoneFileName,
37
32
  buildSliceFileName,
38
- buildTaskFileName,
39
33
  resolveMilestoneFile,
40
34
  clearPathCache,
41
35
  resolveGsdRootFile,
@@ -49,81 +43,15 @@ import {
49
43
  } from "node:fs";
50
44
  import { execFileSync } from "node:child_process";
51
45
  import { dirname, join } from "node:path";
46
+ import {
47
+ resolveExpectedArtifactPath,
48
+ diagnoseExpectedArtifact,
49
+ } from "./auto-artifact-paths.js";
52
50
 
53
- // ─── Artifact Resolution & Verification ───────────────────────────────────────
51
+ // Re-export so existing consumers of auto-recovery.ts keep working.
52
+ export { resolveExpectedArtifactPath, diagnoseExpectedArtifact };
54
53
 
55
- /**
56
- * Resolve the expected artifact for a unit to an absolute path.
57
- */
58
- export function resolveExpectedArtifactPath(
59
- unitType: string,
60
- unitId: string,
61
- base: string,
62
- ): string | null {
63
- const parts = unitId.split("/");
64
- const mid = parts[0]!;
65
- const sid = parts[1];
66
- switch (unitType) {
67
- case "discuss-milestone": {
68
- const dir = resolveMilestonePath(base, mid);
69
- return dir ? join(dir, buildMilestoneFileName(mid, "CONTEXT")) : null;
70
- }
71
- case "research-milestone": {
72
- const dir = resolveMilestonePath(base, mid);
73
- return dir ? join(dir, buildMilestoneFileName(mid, "RESEARCH")) : null;
74
- }
75
- case "plan-milestone": {
76
- const dir = resolveMilestonePath(base, mid);
77
- return dir ? join(dir, buildMilestoneFileName(mid, "ROADMAP")) : null;
78
- }
79
- case "research-slice": {
80
- const dir = resolveSlicePath(base, mid, sid!);
81
- return dir ? join(dir, buildSliceFileName(sid!, "RESEARCH")) : null;
82
- }
83
- case "plan-slice": {
84
- const dir = resolveSlicePath(base, mid, sid!);
85
- return dir ? join(dir, buildSliceFileName(sid!, "PLAN")) : null;
86
- }
87
- case "reassess-roadmap": {
88
- const dir = resolveSlicePath(base, mid, sid!);
89
- return dir ? join(dir, buildSliceFileName(sid!, "ASSESSMENT")) : null;
90
- }
91
- case "run-uat": {
92
- const dir = resolveSlicePath(base, mid, sid!);
93
- return dir ? join(dir, buildSliceFileName(sid!, "UAT-RESULT")) : null;
94
- }
95
- case "execute-task": {
96
- const tid = parts[2];
97
- const dir = resolveSlicePath(base, mid, sid!);
98
- return dir && tid
99
- ? join(dir, "tasks", buildTaskFileName(tid, "SUMMARY"))
100
- : null;
101
- }
102
- case "complete-slice": {
103
- const dir = resolveSlicePath(base, mid, sid!);
104
- return dir ? join(dir, buildSliceFileName(sid!, "SUMMARY")) : null;
105
- }
106
- case "validate-milestone": {
107
- const dir = resolveMilestonePath(base, mid);
108
- return dir ? join(dir, buildMilestoneFileName(mid, "VALIDATION")) : null;
109
- }
110
- case "complete-milestone": {
111
- const dir = resolveMilestonePath(base, mid);
112
- return dir ? join(dir, buildMilestoneFileName(mid, "SUMMARY")) : null;
113
- }
114
- case "replan-slice": {
115
- const dir = resolveSlicePath(base, mid, sid!);
116
- return dir ? join(dir, buildSliceFileName(sid!, "REPLAN")) : null;
117
- }
118
- case "rewrite-docs":
119
- return null;
120
- case "reactive-execute":
121
- // Reactive execute produces multiple task summaries — verified separately
122
- return null;
123
- default:
124
- return null;
125
- }
126
- }
54
+ // ─── Artifact Resolution & Verification ───────────────────────────────────────
127
55
 
128
56
  /**
129
57
  * Check whether a milestone produced implementation artifacts (non-`.gsd/` files)
@@ -302,6 +230,35 @@ export function verifyExpectedArtifact(
302
230
  return true;
303
231
  }
304
232
 
233
+ // Gate-evaluate: verify that each dispatched gate has been resolved in the DB.
234
+ // The unitId encodes the batch: "{mid}/{sid}/gates+Q3,Q4"
235
+ if (unitType === "gate-evaluate") {
236
+ const parts = unitId.split("/");
237
+ const mid = parts[0];
238
+ const sid = parts[1];
239
+ const batchPart = parts[2]; // "gates+Q3,Q4"
240
+ if (!mid || !sid || !batchPart) return false;
241
+
242
+ const plusIdx = batchPart.indexOf("+");
243
+ if (plusIdx === -1) return true; // no specific gates encoded — pass
244
+
245
+ const gateIds = batchPart.slice(plusIdx + 1).split(",").filter(Boolean);
246
+ if (gateIds.length === 0) return true;
247
+
248
+ try {
249
+ const { getPendingGates: getPending } = require("./gsd-db.js");
250
+ const pending = getPending(mid, sid, "slice");
251
+ const pendingIds = new Set(pending.map((g: any) => g.gate_id));
252
+ // All dispatched gates must no longer be pending
253
+ for (const gid of gateIds) {
254
+ if (pendingIds.has(gid)) return false;
255
+ }
256
+ } catch {
257
+ // DB unavailable — treat as verified to avoid blocking
258
+ }
259
+ return true;
260
+ }
261
+
305
262
  const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
306
263
  // For unit types with no verifiable artifact (null path), the parent directory
307
264
  // is missing on disk — treat as stale completion state so the key gets evicted (#313).
@@ -471,50 +428,37 @@ export function writeBlockerPlaceholder(
471
428
  return diagnoseExpectedArtifact(unitType, unitId, base);
472
429
  }
473
430
 
474
- export function diagnoseExpectedArtifact(
475
- unitType: string,
476
- unitId: string,
477
- base: string,
478
- ): string | null {
479
- const parts = unitId.split("/");
480
- const mid = parts[0];
481
- const sid = parts[1];
482
- switch (unitType) {
483
- case "discuss-milestone":
484
- return `${relMilestoneFile(base, mid!, "CONTEXT")} (milestone context from discussion)`;
485
- case "research-milestone":
486
- return `${relMilestoneFile(base, mid!, "RESEARCH")} (milestone research)`;
487
- case "plan-milestone":
488
- return `${relMilestoneFile(base, mid!, "ROADMAP")} (milestone roadmap)`;
489
- case "research-slice":
490
- return `${relSliceFile(base, mid!, sid!, "RESEARCH")} (slice research)`;
491
- case "plan-slice":
492
- return `${relSliceFile(base, mid!, sid!, "PLAN")} (slice plan)`;
493
- case "execute-task": {
494
- const tid = parts[2];
495
- return `Task ${tid} marked [x] in ${relSliceFile(base, mid!, sid!, "PLAN")} + summary written`;
431
+ // ─── Merge State Reconciliation ───────────────────────────────────────────────
432
+
433
+ /**
434
+ * Best-effort abort of a pending merge/squash and hard-reset to HEAD.
435
+ * Handles both real merges (MERGE_HEAD) and squash merges (SQUASH_MSG).
436
+ */
437
+ function abortAndResetMerge(
438
+ basePath: string,
439
+ hasMergeHead: boolean,
440
+ squashMsgPath: string,
441
+ ): void {
442
+ if (hasMergeHead) {
443
+ try {
444
+ nativeMergeAbort(basePath);
445
+ } catch {
446
+ /* best-effort */
447
+ }
448
+ } else if (squashMsgPath) {
449
+ try {
450
+ unlinkSync(squashMsgPath);
451
+ } catch {
452
+ /* best-effort */
496
453
  }
497
- case "complete-slice":
498
- return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid!, "ROADMAP")} + summary + UAT written`;
499
- case "replan-slice":
500
- return `${relSliceFile(base, mid!, sid!, "REPLAN")} + updated ${relSliceFile(base, mid!, sid!, "PLAN")}`;
501
- case "rewrite-docs":
502
- return "Active overrides resolved in .gsd/OVERRIDES.md + plan documents updated";
503
- case "reassess-roadmap":
504
- return `${relSliceFile(base, mid!, sid!, "ASSESSMENT")} (roadmap reassessment)`;
505
- case "run-uat":
506
- return `${relSliceFile(base, mid!, sid!, "UAT-RESULT")} (UAT result)`;
507
- case "validate-milestone":
508
- return `${relMilestoneFile(base, mid!, "VALIDATION")} (milestone validation report)`;
509
- case "complete-milestone":
510
- return `${relMilestoneFile(base, mid!, "SUMMARY")} (milestone summary)`;
511
- default:
512
- return null;
454
+ }
455
+ try {
456
+ nativeResetHard(basePath);
457
+ } catch {
458
+ /* best-effort */
513
459
  }
514
460
  }
515
461
 
516
- // ─── Merge State Reconciliation ───────────────────────────────────────────────
517
-
518
462
  /**
519
463
  * Detect leftover merge state from a prior session and reconcile it.
520
464
  * If MERGE_HEAD or SQUASH_MSG exists, check whether conflicts are resolved.
@@ -571,24 +515,7 @@ export function reconcileMergeState(
571
515
  }
572
516
  }
573
517
  if (!resolved) {
574
- if (hasMergeHead) {
575
- try {
576
- nativeMergeAbort(basePath);
577
- } catch {
578
- /* best-effort */
579
- }
580
- } else if (hasSquashMsg) {
581
- try {
582
- unlinkSync(squashMsgPath);
583
- } catch {
584
- /* best-effort */
585
- }
586
- }
587
- try {
588
- nativeResetHard(basePath);
589
- } catch {
590
- /* best-effort */
591
- }
518
+ abortAndResetMerge(basePath, hasMergeHead, squashMsgPath);
592
519
  ctx.ui.notify(
593
520
  "Detected leftover merge state — auto-resolve failed, cleaned up. Re-deriving state.",
594
521
  "warning",
@@ -596,24 +523,7 @@ export function reconcileMergeState(
596
523
  }
597
524
  } else {
598
525
  // Code conflicts present — abort and reset
599
- if (hasMergeHead) {
600
- try {
601
- nativeMergeAbort(basePath);
602
- } catch {
603
- /* best-effort */
604
- }
605
- } else if (hasSquashMsg) {
606
- try {
607
- unlinkSync(squashMsgPath);
608
- } catch {
609
- /* best-effort */
610
- }
611
- }
612
- try {
613
- nativeResetHard(basePath);
614
- } catch {
615
- /* best-effort */
616
- }
526
+ abortAndResetMerge(basePath, hasMergeHead, squashMsgPath);
617
527
  ctx.ui.notify(
618
528
  "Detected leftover merge state with unresolved conflicts — cleaned up. Re-deriving state.",
619
529
  "warning",
@@ -623,50 +533,6 @@ export function reconcileMergeState(
623
533
  return true;
624
534
  }
625
535
 
626
- // ─── Self-Heal Runtime Records ────────────────────────────────────────────────
627
-
628
- /**
629
- * Self-heal: scan runtime records in .gsd/ and clear stale ones.
630
- * Clears dispatched records older than 1 hour (process crashed before
631
- * completing the unit). deriveState() handles re-derivation — no need
632
- * for completion key persistence here.
633
- */
634
- export async function selfHealRuntimeRecords(
635
- base: string,
636
- ctx: ExtensionContext,
637
- ): Promise<void> {
638
- try {
639
- const { listUnitRuntimeRecords } = await import("./unit-runtime.js");
640
- const records = listUnitRuntimeRecords(base);
641
- let healed = 0;
642
- const STALE_THRESHOLD_MS = 60 * 60 * 1000; // 1 hour
643
- const now = Date.now();
644
- for (const record of records) {
645
- const { unitType, unitId } = record;
646
-
647
- // Case 0 removed — roadmap checkbox auto-fix is no longer needed.
648
- // With DB-as-truth, stale checkboxes are fixed by repairStaleRenders().
649
-
650
- // Clear stale dispatched records (dispatched > 1h ago, process crashed)
651
- const age = now - (record.startedAt ?? 0);
652
- if (record.phase === "dispatched" && age > STALE_THRESHOLD_MS) {
653
- clearUnitRuntimeRecord(base, unitType, unitId);
654
- healed++;
655
- continue;
656
- }
657
- }
658
- if (healed > 0) {
659
- ctx.ui.notify(
660
- `Self-heal: cleared ${healed} stale runtime record(s).`,
661
- "info",
662
- );
663
- }
664
- } catch (e) {
665
- // Non-fatal — self-heal should never block auto-mode start
666
- void e;
667
- }
668
- }
669
-
670
536
  // ─── Loop Remediation ─────────────────────────────────────────────────────────
671
537
 
672
538
  /**
@@ -52,7 +52,7 @@ import {
52
52
  setActiveMilestoneId,
53
53
  } from "./worktree.js";
54
54
  import { getAutoWorktreePath, isInAutoWorktree } from "./auto-worktree.js";
55
- import { readResourceVersion } from "./auto-worktree-sync.js";
55
+ import { readResourceVersion, cleanStaleRuntimeUnits } from "./auto-worktree.js";
56
56
  import { initMetrics } from "./metrics.js";
57
57
  import { initRoutingHistory } from "./routing-history.js";
58
58
  import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
@@ -66,6 +66,7 @@ import {
66
66
  isDebugEnabled,
67
67
  getDebugLogPath,
68
68
  } from "./debug-logger.js";
69
+ import { parseUnitId } from "./unit-id.js";
69
70
  import type { AutoSession } from "./auto/session.js";
70
71
  import {
71
72
  existsSync,
@@ -200,7 +201,7 @@ export async function bootstrapAutoSession(
200
201
  );
201
202
  return releaseLockAndReturn();
202
203
  }
203
- const recoveredMid = crashLock.unitId.split("/")[0];
204
+ const recoveredMid = parseUnitId(crashLock.unitId).milestone;
204
205
  const milestoneAlreadyComplete = recoveredMid
205
206
  ? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
206
207
  : false;
@@ -258,31 +259,10 @@ export async function bootstrapAutoSession(
258
259
  invalidateAllCaches();
259
260
 
260
261
  // Clean stale runtime unit files for completed milestones (#887)
261
- try {
262
- const runtimeUnitsDir = join(gsdRoot(base), "runtime", "units");
263
- if (existsSync(runtimeUnitsDir)) {
264
- for (const file of readdirSync(runtimeUnitsDir)) {
265
- if (!file.endsWith(".json")) continue;
266
- const midMatch = file.match(/(M\d+(?:-[a-z0-9]{6})?)/);
267
- if (!midMatch) continue;
268
- const mid = midMatch[1];
269
- if (resolveMilestoneFile(base, mid, "SUMMARY")) {
270
- try {
271
- unlinkSync(join(runtimeUnitsDir, file));
272
- } catch (e) {
273
- debugLog("stale-unit-cleanup-failed", {
274
- file,
275
- error: e instanceof Error ? e.message : String(e),
276
- });
277
- }
278
- }
279
- }
280
- }
281
- } catch (e) {
282
- debugLog("stale-unit-dir-cleanup-failed", {
283
- error: e instanceof Error ? e.message : String(e),
284
- });
285
- }
262
+ cleanStaleRuntimeUnits(
263
+ gsdRoot(base),
264
+ (mid) => !!resolveMilestoneFile(base, mid, "SUMMARY"),
265
+ );
286
266
 
287
267
  let state = await deriveState(base);
288
268
 
@@ -192,7 +192,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
192
192
  const message = err instanceof Error ? err.message : String(err);
193
193
  console.error(`[idle-watchdog] Unhandled error: ${message}`);
194
194
  // Unblock any pending unit promise so the auto-loop is not orphaned.
195
- resolveAgentEndCancelled();
195
+ resolveAgentEndCancelled({ message: `Idle watchdog error: ${message}`, category: "idle", isTransient: true });
196
196
  try {
197
197
  ctx.ui.notify(`Idle watchdog error: ${message}`, "warning");
198
198
  } catch { /* best effort */ }
@@ -226,7 +226,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
226
226
  const message = err instanceof Error ? err.message : String(err);
227
227
  console.error(`[hard-timeout] Unhandled error: ${message}`);
228
228
  // Unblock any pending unit promise so the auto-loop is not orphaned.
229
- resolveAgentEndCancelled();
229
+ resolveAgentEndCancelled({ message: `Hard timeout error: ${message}`, category: "timeout", isTransient: true });
230
230
  try {
231
231
  ctx.ui.notify(`Hard timeout error: ${message}`, "warning");
232
232
  } catch { /* best effort */ }
@@ -12,6 +12,7 @@
12
12
 
13
13
  import type { ExtensionContext, ExtensionAPI } from "@gsd/pi-coding-agent";
14
14
  import { resolveSliceFile, resolveSlicePath } from "./paths.js";
15
+ import { parseUnitId } from "./unit-id.js";
15
16
  import { isDbAvailable, getTask } from "./gsd-db.js";
16
17
  import { loadEffectiveGSDPreferences } from "./preferences.js";
17
18
  import {
@@ -60,10 +61,9 @@ export async function runPostUnitVerification(
60
61
  const prefs = effectivePrefs?.preferences;
61
62
 
62
63
  // Read task plan verify field
63
- const parts = s.currentUnit.id.split("/");
64
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
64
65
  let taskPlanVerify: string | undefined;
65
- if (parts.length >= 3) {
66
- const [mid, sid, tid] = parts;
66
+ if (mid && sid && tid) {
67
67
  if (isDbAvailable()) {
68
68
  taskPlanVerify = getTask(mid, sid, tid)?.verify;
69
69
  }
@@ -71,8 +71,6 @@ export async function runPostUnitVerification(
71
71
  }
72
72
 
73
73
  const result = runVerificationGate({
74
- basePath: s.basePath,
75
- unitId: s.currentUnit.id,
76
74
  cwd: s.basePath,
77
75
  preferenceCommands: prefs?.verification_commands,
78
76
  taskPlanVerify,
@@ -141,9 +139,8 @@ export async function runPostUnitVerification(
141
139
 
142
140
  // Write verification evidence JSON
143
141
  const attempt = s.verificationRetryCount.get(s.currentUnit.id) ?? 0;
144
- if (parts.length >= 3) {
142
+ if (mid && sid && tid) {
145
143
  try {
146
- const [mid, sid, tid] = parts;
147
144
  const sDir = resolveSlicePath(s.basePath, mid, sid);
148
145
  if (sDir) {
149
146
  const tasksDir = join(sDir, "tasks");