gsd-pi 2.49.0-dev.de3d9f6 → 2.50.0-dev.9476db8

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 +15 -15
  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 +15 -15
  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/{ceckLbAMjhzHaQ3RPtJnT → MkE9kzqUGny3-cSE0GNnm}/_buildManifest.js +0 -0
  249. /package/dist/web/standalone/.next/static/{ceckLbAMjhzHaQ3RPtJnT → MkE9kzqUGny3-cSE0GNnm}/_ssgManifest.js +0 -0
@@ -6,8 +6,10 @@ import {
6
6
  insertTask,
7
7
  upsertSlicePlanning,
8
8
  upsertTaskPlanning,
9
+ insertGateRow,
9
10
  _getAdapter,
10
11
  } from "../gsd-db.js";
12
+ import type { GateId } from "../types.js";
11
13
  import { invalidateStateCache } from "../state.js";
12
14
  import { renderPlanFromDb } from "../markdown-renderer.js";
13
15
  import { renderAllProjections } from "../workflow-projections.js";
@@ -190,6 +192,20 @@ export async function handlePlanSlice(
190
192
  fullPlanMd: task.fullPlanMd,
191
193
  });
192
194
  }
195
+
196
+ // Seed quality gate rows inside the transaction — all-or-nothing with
197
+ // the plan data so a crash can't leave orphaned gates without tasks.
198
+ const sliceGates: GateId[] = ["Q3", "Q4"];
199
+ for (const gid of sliceGates) {
200
+ insertGateRow({ milestoneId: params.milestoneId, sliceId: params.sliceId, gateId: gid, scope: "slice" });
201
+ }
202
+ const taskGates: GateId[] = ["Q5", "Q6", "Q7"];
203
+ for (const task of params.tasks) {
204
+ for (const gid of taskGates) {
205
+ insertGateRow({ milestoneId: params.milestoneId, sliceId: params.sliceId, gateId: gid, scope: "task", taskId: task.taskId });
206
+ }
207
+ }
208
+ insertGateRow({ milestoneId: params.milestoneId, sliceId: params.sliceId, gateId: "Q8", scope: "slice" });
193
209
  });
194
210
  } catch (err) {
195
211
  return { error: `db write failed: ${(err as Error).message}` };
@@ -14,6 +14,7 @@ import {
14
14
  import { resolveMilestonePath, clearPathCache } from "../paths.js";
15
15
  import { saveFile, clearParseCache } from "../files.js";
16
16
  import { invalidateStateCache } from "../state.js";
17
+ import { VALIDATION_VERDICTS, isValidMilestoneVerdict } from "../verdict-parser.js";
17
18
 
18
19
  export interface ValidateMilestoneParams {
19
20
  milestoneId: string;
@@ -71,9 +72,8 @@ export async function handleValidateMilestone(
71
72
  if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
72
73
  return { error: "milestoneId is required and must be a non-empty string" };
73
74
  }
74
- const validVerdicts = ["pass", "needs-attention", "needs-remediation"];
75
- if (!validVerdicts.includes(params.verdict)) {
76
- return { error: `verdict must be one of: ${validVerdicts.join(", ")}` };
75
+ if (!isValidMilestoneVerdict(params.verdict)) {
76
+ return { error: `verdict must be one of: ${VALIDATION_VERDICTS.join(", ")}` };
77
77
  }
78
78
 
79
79
  // ── Filesystem render ──────────────────────────────────────────────────
@@ -11,6 +11,7 @@ export type Phase =
11
11
  | "discussing"
12
12
  | "researching"
13
13
  | "planning"
14
+ | "evaluating-gates"
14
15
  | "executing"
15
16
  | "verifying"
16
17
  | "summarizing"
@@ -557,3 +558,32 @@ export interface CompleteSliceParams {
557
558
  /** Optional caller-provided reason this action was triggered */
558
559
  triggerReason?: string;
559
560
  }
561
+
562
+ // ─── Quality Gates ───────────────────────────────────────────────────────
563
+
564
+ export type GateId = "Q3" | "Q4" | "Q5" | "Q6" | "Q7" | "Q8";
565
+ export type GateScope = "slice" | "task";
566
+ export type GateStatus = "pending" | "complete" | "omitted";
567
+ export type GateVerdict = "pass" | "flag" | "omitted" | "";
568
+
569
+ export interface GateRow {
570
+ milestone_id: string;
571
+ slice_id: string;
572
+ gate_id: GateId;
573
+ scope: GateScope;
574
+ task_id: string;
575
+ status: GateStatus;
576
+ verdict: GateVerdict;
577
+ rationale: string;
578
+ findings: string;
579
+ evaluated_at: string | null;
580
+ }
581
+
582
+ /** Configuration for parallel quality gate evaluation during slice planning. */
583
+ export interface GateEvaluationConfig {
584
+ enabled: boolean;
585
+ /** Which slice-scoped gates to evaluate in parallel. Default: ['Q3', 'Q4']. */
586
+ slice_gates?: string[];
587
+ /** Whether to evaluate task-level gates (Q5/Q6/Q7) via reactive-execute. Default: true when enabled. */
588
+ task_gates?: boolean;
589
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Centralized verdict extraction, normalization, and schema validation.
3
+ *
4
+ * All verdict-related logic lives here so that normalization rules
5
+ * (e.g. `passed` → `pass`) are applied consistently across the codebase.
6
+ */
7
+
8
+ import { extractUatType } from "./files.js";
9
+ import type { UatType } from "./files.js";
10
+
11
+ // ── Verdict extraction ──────────────────────────────────────────────────
12
+
13
+ /**
14
+ * Extract and normalize the `verdict` value from YAML frontmatter.
15
+ *
16
+ * Normalization:
17
+ * - lowercased
18
+ * - `passed` → `pass`
19
+ *
20
+ * Returns `undefined` when frontmatter is absent or has no `verdict` field.
21
+ */
22
+ export function extractVerdict(content: string): string | undefined {
23
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
24
+ if (!fmMatch) return undefined;
25
+ const verdictMatch = fmMatch[1].match(/verdict:\s*([\w-]+)/i);
26
+ if (!verdictMatch) return undefined;
27
+ let v = verdictMatch[1].toLowerCase();
28
+ if (v === "passed") v = "pass";
29
+ return v;
30
+ }
31
+
32
+ /**
33
+ * Returns `true` when the content's frontmatter contains a `verdict` field.
34
+ */
35
+ export function hasVerdict(content: string): boolean {
36
+ return /verdict:\s*[\w-]+/i.test(content);
37
+ }
38
+
39
+ // ── UAT verdict schema ──────────────────────────────────────────────────
40
+
41
+ /**
42
+ * Base verdicts that are always acceptable for UAT results.
43
+ */
44
+ export const UAT_ACCEPTABLE_VERDICTS: readonly string[] = ["pass", "passed"];
45
+
46
+ /**
47
+ * UAT types whose results may legitimately produce a `partial` verdict
48
+ * when all automatable checks pass but human-only checks remain.
49
+ */
50
+ const PARTIAL_ELIGIBLE_UAT_TYPES: readonly UatType[] = [
51
+ "mixed",
52
+ "human-experience",
53
+ "live-runtime",
54
+ ];
55
+
56
+ /**
57
+ * Check whether a verdict is acceptable for a given UAT type.
58
+ *
59
+ * `pass` / `passed` are always acceptable. `partial` is acceptable only for
60
+ * UAT types that include non-automatable human checks.
61
+ */
62
+ export function isAcceptableUatVerdict(verdict: string, uatType: UatType | undefined): boolean {
63
+ if (UAT_ACCEPTABLE_VERDICTS.includes(verdict)) return true;
64
+ if (verdict === "partial" && uatType && (PARTIAL_ELIGIBLE_UAT_TYPES as readonly string[]).includes(uatType)) {
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+
70
+ // ── Milestone validation verdict schema ─────────────────────────────────
71
+
72
+ /**
73
+ * Valid verdicts for the `validate-milestone` tool.
74
+ */
75
+ export const VALIDATION_VERDICTS = ["pass", "needs-attention", "needs-remediation"] as const;
76
+ export type ValidationVerdict = (typeof VALIDATION_VERDICTS)[number];
77
+
78
+ /**
79
+ * Check whether a string is a valid milestone validation verdict.
80
+ */
81
+ export function isValidMilestoneVerdict(verdict: string): verdict is ValidationVerdict {
82
+ return (VALIDATION_VERDICTS as readonly string[]).includes(verdict);
83
+ }
84
+
85
+ // ── UAT type helper ─────────────────────────────────────────────────────
86
+
87
+ /**
88
+ * Extract the UAT type from content, defaulting to `"artifact-driven"`.
89
+ *
90
+ * The `"artifact-driven"` fallback is the original default used throughout
91
+ * the codebase when a UAT file lacks an explicit `## UAT Type` section.
92
+ */
93
+ export function getUatType(content: string): UatType {
94
+ return extractUatType(content) ?? "artifact-driven";
95
+ }
@@ -220,8 +220,6 @@ function sanitizeCommand(cmd: string): string | null {
220
220
  }
221
221
 
222
222
  export interface RunVerificationGateOptions {
223
- basePath: string;
224
- unitId: string;
225
223
  cwd: string;
226
224
  preferenceCommands?: string[];
227
225
  taskPlanVerify?: string;
@@ -150,6 +150,18 @@ export class WorktreeResolver {
150
150
  */
151
151
  enterMilestone(milestoneId: string, ctx: NotifyCtx): void {
152
152
  this.validateMilestoneId(milestoneId);
153
+
154
+ // If worktree creation failed earlier this session, skip all future attempts
155
+ if (this.s.isolationDegraded) {
156
+ debugLog("WorktreeResolver", {
157
+ action: "enterMilestone",
158
+ milestoneId,
159
+ skipped: true,
160
+ reason: "isolation-degraded",
161
+ });
162
+ return;
163
+ }
164
+
153
165
  if (!this.deps.shouldUseWorktreeIsolation()) {
154
166
  debugLog("WorktreeResolver", {
155
167
  action: "enterMilestone",
@@ -220,6 +232,9 @@ export class WorktreeResolver {
220
232
  `Auto-worktree creation for ${milestoneId} failed: ${msg}. Continuing in project root.`,
221
233
  "warning",
222
234
  );
235
+ // Degrade isolation for the rest of this session so mergeAndExit
236
+ // doesn't try to merge a nonexistent worktree branch (#2483)
237
+ this.s.isolationDegraded = true;
223
238
  // Do NOT update s.basePath — stay in project root
224
239
  }
225
240
  }
@@ -304,6 +319,22 @@ export class WorktreeResolver {
304
319
  */
305
320
  mergeAndExit(milestoneId: string, ctx: NotifyCtx): void {
306
321
  this.validateMilestoneId(milestoneId);
322
+
323
+ // If worktree creation failed earlier, skip merge — work is on current branch (#2483)
324
+ if (this.s.isolationDegraded) {
325
+ debugLog("WorktreeResolver", {
326
+ action: "mergeAndExit",
327
+ milestoneId,
328
+ skipped: true,
329
+ reason: "isolation-degraded",
330
+ });
331
+ ctx.notify(
332
+ `Skipping worktree merge for ${milestoneId} — isolation was degraded (worktree creation failed earlier). Work is on the current branch.`,
333
+ "info",
334
+ );
335
+ return;
336
+ }
337
+
307
338
  const mode = this.deps.getIsolationMode();
308
339
  debugLog("WorktreeResolver", {
309
340
  action: "mergeAndExit",
@@ -235,8 +235,9 @@ export function getSliceBranchName(milestoneId: string, sliceId: string, worktre
235
235
  return `gsd/${milestoneId}/${sliceId}`;
236
236
  }
237
237
 
238
- /** Regex that matches both plain and worktree-namespaced slice branches. */
239
- export const SLICE_BRANCH_RE = /^gsd\/(?:([a-zA-Z0-9_-]+)\/)?(M\d+(?:-[a-z0-9]{6})?)\/(S\d+)$/;
238
+ /** Re-export for backward compatibility canonical definition in branch-patterns.ts */
239
+ export { SLICE_BRANCH_RE } from "./branch-patterns.js";
240
+ import { SLICE_BRANCH_RE } from "./branch-patterns.js";
240
241
 
241
242
  /**
242
243
  * Parse a slice branch name into its components.
@@ -2,7 +2,7 @@
2
2
  * Remote Questions — configuration resolution and validation
3
3
  */
4
4
 
5
- import { join } from "node:path";
5
+ import { AuthStorage } from "@gsd/pi-coding-agent";
6
6
  import { loadEffectiveGSDPreferences, type RemoteQuestionsConfig } from "../gsd/preferences.js";
7
7
  import type { RemoteChannel } from "./types.js";
8
8
 
@@ -54,9 +54,7 @@ function hydrateRemoteTokensFromAuth(): void {
54
54
  if (needed.length === 0) return;
55
55
 
56
56
  try {
57
- const { AuthStorage } = require("@gsd/pi-coding-agent") as typeof import("@gsd/pi-coding-agent");
58
- const authPath = join(process.env.HOME ?? "~", ".gsd", "agent", "auth.json");
59
- const auth = AuthStorage.create(authPath);
57
+ const auth = AuthStorage.create();
60
58
 
61
59
  for (const [providerId, envVar] of needed) {
62
60
  try {
@@ -72,7 +70,7 @@ function hydrateRemoteTokensFromAuth(): void {
72
70
  }
73
71
  }
74
72
  } catch {
75
- // AuthStorage unavailable (unit tests, stripped build) — skip silently.
73
+ // AuthStorage unavailable or auth.json missing/unreadable — skip silently.
76
74
  }
77
75
  }
78
76
 
@@ -176,11 +176,15 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
176
176
  );
177
177
  payload.tools = tools;
178
178
 
179
- // ── Session-level search budget (#1309) ──────────────────────────────
179
+ // ── Session-level search budget (#1309, #compaction-safe) ─────────────
180
180
  // Count web_search_tool_result blocks in the conversation history to
181
181
  // determine how many native searches have already been used this session.
182
182
  // The Anthropic API's max_uses resets per request, so without this guard,
183
183
  // pause_turn → resubmit cycles allow unlimited total searches.
184
+ //
185
+ // Use the monotonic high-water mark: take the max of the history count
186
+ // and the running counter. This prevents budget resets when context
187
+ // compaction removes web_search_tool_result blocks from history.
184
188
  if (Array.isArray(messages)) {
185
189
  let historySearchCount = 0;
186
190
  for (const msg of messages) {
@@ -192,8 +196,9 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
192
196
  }
193
197
  }
194
198
  }
195
- // Sync counter from history (handles session restore / context replay)
196
- sessionSearchCount = historySearchCount;
199
+ // High-water mark: never decrease the counter, even if compaction
200
+ // removes web_search_tool_result blocks from the visible history.
201
+ sessionSearchCount = Math.max(sessionSearchCount, historySearchCount);
197
202
  }
198
203
 
199
204
  const remaining = Math.max(0, MAX_NATIVE_SEARCHES_PER_SESSION - sessionSearchCount);
@@ -106,14 +106,20 @@ searchCache.startPurgeInterval(60_000);
106
106
 
107
107
  // Consecutive duplicate search guard (#949)
108
108
  // Tracks recent query keys to detect and break search loops.
109
- const MAX_CONSECUTIVE_DUPES = 3;
109
+ const MAX_CONSECUTIVE_DUPES = 1;
110
110
  let lastSearchKey = "";
111
111
  let consecutiveDupeCount = 0;
112
112
 
113
- /** Reset session-scoped duplicate-search guard state. */
113
+ // Session-level total search budget (all queries, not just duplicates).
114
+ // Prevents unbounded search accumulation across varied queries.
115
+ const MAX_SEARCHES_PER_SESSION = 15;
116
+ let sessionTotalSearches = 0;
117
+
118
+ /** Reset session-scoped search guard state (both duplicate and budget). */
114
119
  export function resetSearchLoopGuardState(): void {
115
120
  lastSearchKey = "";
116
121
  consecutiveDupeCount = 0;
122
+ sessionTotalSearches = 0;
117
123
  }
118
124
 
119
125
  // Summarizer responses: max 50 entries, 15-minute TTL
@@ -357,6 +363,17 @@ export function registerSearchTool(pi: ExtensionAPI) {
357
363
  };
358
364
  }
359
365
 
366
+ // ------------------------------------------------------------------
367
+ // Session-level search budget
368
+ // ------------------------------------------------------------------
369
+ if (sessionTotalSearches >= MAX_SEARCHES_PER_SESSION) {
370
+ return {
371
+ content: [{ type: "text" as const, text: `⚠️ Search budget exhausted: ${sessionTotalSearches}/${MAX_SEARCHES_PER_SESSION} searches used this session. The information you need should already be in previous search results. Stop searching and use those results to proceed with your task.` }],
372
+ isError: true,
373
+ details: { errorKind: "budget_exhausted", error: `Session search budget exhausted (${MAX_SEARCHES_PER_SESSION})` } satisfies Partial<SearchDetails>,
374
+ };
375
+ }
376
+
360
377
  const count = params.count ?? 5;
361
378
  const wantSummary = params.summary ?? false;
362
379
 
@@ -410,6 +427,9 @@ export function registerSearchTool(pi: ExtensionAPI) {
410
427
  consecutiveDupeCount = 1;
411
428
  }
412
429
 
430
+ // Count every search that passes the guards toward the session budget.
431
+ sessionTotalSearches++;
432
+
413
433
  const cached = searchCache.get(cacheKey);
414
434
 
415
435
  if (cached) {
@@ -103,9 +103,12 @@ gh issue list -R gsd-build/gsd-2
103
103
  gh issue list -R gsd-build/gsd-2 --label "priority:p1" --state open
104
104
 
105
105
  # Create issue with labels and milestone
106
+ # NOTE: Do NOT use labels for issue classification (bug, feature, etc.)
107
+ # Use labels for metadata (priority, status, auto-generated) only.
108
+ # Issue classification uses GitHub Issue Types, set via GraphQL after creation.
106
109
  gh issue create -R gsd-build/gsd-2 \
107
110
  --title "feat: add feature X" \
108
- --label "priority:p1" --label "type:feature" \
111
+ --label "priority:p1" \
109
112
  --milestone "v1.0"
110
113
 
111
114
  # View issue
@@ -120,6 +123,24 @@ gh issue edit <number> -R gsd-build/gsd-2 \
120
123
  --remove-label "status:needs-grooming"
121
124
  ```
122
125
 
126
+ ### Issue Types (Classification)
127
+
128
+ `gh issue create` has no `--type` flag. Issue types (Bug, Feature Request, etc.) are set via GraphQL after creation:
129
+
130
+ ```bash
131
+ # Step 1: Create the issue (returns URL)
132
+ ISSUE_URL=$(gh issue create -R gsd-build/gsd-2 \
133
+ --title "..." --body "...")
134
+
135
+ # Step 2: Set the issue type via GraphQL
136
+ ISSUE_NUM=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$')
137
+ ISSUE_ID=$(gh api graphql -f query='{ repository(owner:"gsd-build",name:"gsd-2") { issue(number:'"$ISSUE_NUM"') { id } } }' --jq '.data.repository.issue.id')
138
+ TYPE_ID=$(gh api graphql -f query='{ repository(owner:"gsd-build",name:"gsd-2") { issueTypes(first:20) { nodes { id name } } } }' --jq '.data.repository.issueTypes.nodes[] | select(.name=="Bug") | .id')
139
+ gh api graphql -f query='mutation { updateIssue(input:{id:"'"$ISSUE_ID"'",issueTypeId:"'"$TYPE_ID"'"}) { issue { number } } }'
140
+ ```
141
+
142
+ Replace `"Bug"` with the appropriate type name (`"Feature Request"`, `"Task"`, etc.).
143
+
123
144
  ### Labels
124
145
 
125
146
  ```bash
@@ -1,191 +0,0 @@
1
- /**
2
- * Worktree ↔ project root state synchronization for auto-mode.
3
- *
4
- * When auto-mode runs inside a worktree, dispatch-critical state files
5
- * (.gsd/ metadata) diverge between the worktree (where work happens)
6
- * and the project root (where startAutoMode reads initial state on restart).
7
- * Without syncing, restarting auto-mode reads stale state from the project
8
- * root and re-dispatches already-completed units.
9
- *
10
- * Also contains resource staleness detection and stale worktree escape.
11
- */
12
- import { existsSync, readFileSync, unlinkSync, readdirSync, } from "node:fs";
13
- import { join, sep as pathSep } from "node:path";
14
- import { homedir } from "node:os";
15
- import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
16
- const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
17
- // ─── Project Root → Worktree Sync ─────────────────────────────────────────
18
- /**
19
- * Sync milestone artifacts from project root INTO worktree before deriveState.
20
- * Covers the case where the LLM wrote artifacts to the main repo filesystem
21
- * (e.g. via absolute paths) but the worktree has stale data. Also deletes
22
- * gsd.db in the worktree so it rebuilds from fresh disk state (#853).
23
- * Non-fatal — sync failure should never block dispatch.
24
- */
25
- export function syncProjectRootToWorktree(projectRoot, worktreePath, milestoneId) {
26
- if (!worktreePath || !projectRoot || worktreePath === projectRoot)
27
- return;
28
- if (!milestoneId)
29
- return;
30
- const prGsd = join(projectRoot, ".gsd");
31
- const wtGsd = join(worktreePath, ".gsd");
32
- // Copy milestone directory from project root to worktree if the project root
33
- // has newer artifacts (e.g. slices that don't exist in the worktree yet)
34
- safeCopyRecursive(join(prGsd, "milestones", milestoneId), join(wtGsd, "milestones", milestoneId));
35
- // Delete worktree gsd.db so it rebuilds from the freshly synced files.
36
- // Stale DB rows are the root cause of the infinite skip loop (#853).
37
- try {
38
- const wtDb = join(wtGsd, "gsd.db");
39
- if (existsSync(wtDb)) {
40
- unlinkSync(wtDb);
41
- }
42
- }
43
- catch {
44
- /* non-fatal */
45
- }
46
- }
47
- // ─── Worktree → Project Root Sync ─────────────────────────────────────────
48
- /**
49
- * Sync dispatch-critical .gsd/ state files from worktree to project root.
50
- * Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
51
- * Copies: STATE.md + active milestone directory (roadmap, slice plans, task summaries).
52
- * Non-fatal — sync failure should never block dispatch.
53
- */
54
- export function syncStateToProjectRoot(worktreePath, projectRoot, milestoneId) {
55
- if (!worktreePath || !projectRoot || worktreePath === projectRoot)
56
- return;
57
- if (!milestoneId)
58
- return;
59
- const wtGsd = join(worktreePath, ".gsd");
60
- const prGsd = join(projectRoot, ".gsd");
61
- // 1. STATE.md — the quick-glance status used by initial deriveState()
62
- safeCopy(join(wtGsd, "STATE.md"), join(prGsd, "STATE.md"), { force: true });
63
- // 2. Milestone directory — ROADMAP, slice PLANs, task summaries
64
- // Copy the entire milestone .gsd subtree so deriveState reads current checkboxes
65
- safeCopyRecursive(join(wtGsd, "milestones", milestoneId), join(prGsd, "milestones", milestoneId), { force: true });
66
- // 3. metrics.json — session cost/token tracking (#2313).
67
- // Without this, metrics accumulated in the worktree are invisible from the
68
- // project root and never appear in the dashboard or skill-health reports.
69
- safeCopy(join(wtGsd, "metrics.json"), join(prGsd, "metrics.json"), { force: true });
70
- // 4. Runtime records — unit dispatch state used by selfHealRuntimeRecords().
71
- // Without this, a crash during a unit leaves the runtime record only in the
72
- // worktree. If the next session resolves basePath before worktree re-entry,
73
- // selfHeal can't find or clear the stale record (#769).
74
- safeCopyRecursive(join(wtGsd, "runtime", "units"), join(prGsd, "runtime", "units"), { force: true });
75
- }
76
- // ─── Resource Staleness ───────────────────────────────────────────────────
77
- /**
78
- * Read the resource version (semver) from the managed-resources manifest.
79
- * Uses gsdVersion instead of syncedAt so that launching a second session
80
- * doesn't falsely trigger staleness (#804).
81
- */
82
- export function readResourceVersion() {
83
- const agentDir = process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
84
- const manifestPath = join(agentDir, "managed-resources.json");
85
- try {
86
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
87
- return typeof manifest?.gsdVersion === "string"
88
- ? manifest.gsdVersion
89
- : null;
90
- }
91
- catch {
92
- return null;
93
- }
94
- }
95
- /**
96
- * Check if managed resources have been updated since session start.
97
- * Returns a warning message if stale, null otherwise.
98
- */
99
- export function checkResourcesStale(versionOnStart) {
100
- if (versionOnStart === null)
101
- return null;
102
- const current = readResourceVersion();
103
- if (current === null)
104
- return null;
105
- if (current !== versionOnStart) {
106
- return "GSD resources were updated since this session started. Restart gsd to load the new code.";
107
- }
108
- return null;
109
- }
110
- // ─── Stale Worktree Escape ────────────────────────────────────────────────
111
- /**
112
- * Detect and escape a stale worktree cwd (#608).
113
- *
114
- * After milestone completion + merge, the worktree directory is removed but
115
- * the process cwd may still point inside `.gsd/worktrees/<MID>/`.
116
- * When a new session starts, `process.cwd()` is passed as `base` to startAuto
117
- * and all subsequent writes land in the wrong directory. This function detects
118
- * that scenario and chdir back to the project root.
119
- *
120
- * Returns the corrected base path.
121
- */
122
- export function escapeStaleWorktree(base) {
123
- // Direct layout: /.gsd/worktrees/
124
- const directMarker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
125
- let idx = base.indexOf(directMarker);
126
- if (idx === -1) {
127
- // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
128
- const symlinkRe = new RegExp(`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees\\${pathSep}`);
129
- const match = base.match(symlinkRe);
130
- if (!match || match.index === undefined)
131
- return base;
132
- idx = match.index;
133
- }
134
- // base is inside .gsd/worktrees/<something> — extract the project root
135
- const projectRoot = base.slice(0, idx);
136
- // Guard: If the candidate project root's .gsd IS the user-level ~/.gsd,
137
- // the string-slice heuristic matched the wrong /.gsd/ boundary. This happens
138
- // when .gsd is a symlink into ~/.gsd/projects/<hash> and process.cwd()
139
- // resolved through the symlink. Returning ~ would be catastrophic (#1676).
140
- const candidateGsd = join(projectRoot, ".gsd").replaceAll("\\", "/");
141
- const gsdHomePath = gsdHome.replaceAll("\\", "/");
142
- if (candidateGsd === gsdHomePath || candidateGsd.startsWith(gsdHomePath + "/")) {
143
- // Don't chdir to home — return base unchanged.
144
- // resolveProjectRoot() in worktree.ts has the full git-file-based recovery
145
- // and will be called by the caller (startAuto → projectRoot()).
146
- return base;
147
- }
148
- try {
149
- process.chdir(projectRoot);
150
- }
151
- catch {
152
- // If chdir fails, return the original — caller will handle errors downstream
153
- return base;
154
- }
155
- return projectRoot;
156
- }
157
- /**
158
- * Clean stale runtime unit files for completed milestones.
159
- *
160
- * After restart, stale runtime/units/*.json from prior milestones can
161
- * cause deriveState to resume the wrong milestone (#887). Removes files
162
- * for milestones that have a SUMMARY (fully complete).
163
- */
164
- export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
165
- const runtimeUnitsDir = join(gsdRootPath, "runtime", "units");
166
- if (!existsSync(runtimeUnitsDir))
167
- return 0;
168
- let cleaned = 0;
169
- try {
170
- for (const file of readdirSync(runtimeUnitsDir)) {
171
- if (!file.endsWith(".json"))
172
- continue;
173
- const midMatch = file.match(/(M\d+(?:-[a-z0-9]{6})?)/);
174
- if (!midMatch)
175
- continue;
176
- if (hasMilestoneSummary(midMatch[1])) {
177
- try {
178
- unlinkSync(join(runtimeUnitsDir, file));
179
- cleaned++;
180
- }
181
- catch {
182
- /* non-fatal */
183
- }
184
- }
185
- }
186
- }
187
- catch {
188
- /* non-fatal */
189
- }
190
- return cleaned;
191
- }