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
@@ -0,0 +1,189 @@
1
+ // Quality gate dispatch + state derivation tests
2
+ // Verifies the evaluating-gates phase and dispatch rule behavior.
3
+
4
+ import { describe, test, beforeEach, afterEach } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { tmpdir } from "node:os";
9
+
10
+ import {
11
+ openDatabase,
12
+ closeDatabase,
13
+ insertMilestone,
14
+ insertSlice,
15
+ insertTask,
16
+ upsertSlicePlanning,
17
+ upsertTaskPlanning,
18
+ insertGateRow,
19
+ saveGateResult,
20
+ markAllGatesOmitted,
21
+ getPendingSliceGateCount,
22
+ } from "../gsd-db.ts";
23
+ import { deriveState, invalidateStateCache } from "../state.ts";
24
+ import { renderPlanFromDb } from "../markdown-renderer.ts";
25
+ import { invalidateAllCaches } from "../cache.ts";
26
+
27
+ function setupTestProject(): { tmpDir: string; dbPath: string } {
28
+ const tmpDir = mkdtempSync(join(tmpdir(), "gate-dispatch-"));
29
+ const dbPath = join(tmpDir, ".gsd", "gsd.db");
30
+ mkdirSync(join(tmpDir, ".gsd"), { recursive: true });
31
+ openDatabase(dbPath);
32
+
33
+ // Create milestone
34
+ insertMilestone({
35
+ id: "M001",
36
+ title: "Test Milestone",
37
+ status: "active",
38
+ });
39
+
40
+ // Create slice
41
+ insertSlice({
42
+ milestoneId: "M001",
43
+ id: "S01",
44
+ title: "Test Slice",
45
+ status: "pending",
46
+ risk: "medium",
47
+ depends: [],
48
+ });
49
+
50
+ // Write roadmap file (required for deriveState)
51
+ const milestoneDir = join(tmpDir, ".gsd", "milestones", "M001");
52
+ mkdirSync(milestoneDir, { recursive: true });
53
+ writeFileSync(
54
+ join(milestoneDir, "M001-ROADMAP.md"),
55
+ [
56
+ "# M001: Test Milestone",
57
+ "",
58
+ "## Vision",
59
+ "Test milestone vision.",
60
+ "",
61
+ "## Success Criteria",
62
+ "- Test criteria",
63
+ "",
64
+ "## Delivery Sequence",
65
+ "- [ ] **S01: Test Slice** `risk:medium`",
66
+ " After this: test demo",
67
+ "",
68
+ ].join("\n"),
69
+ );
70
+
71
+ return { tmpDir, dbPath };
72
+ }
73
+
74
+ function planSlice(tmpDir: string) {
75
+ upsertSlicePlanning("M001", "S01", {
76
+ goal: "Test goal",
77
+ successCriteria: "Test criteria",
78
+ proofLevel: "contract",
79
+ integrationClosure: "",
80
+ observabilityImpact: "Run tests",
81
+ });
82
+ insertTask({
83
+ id: "T01",
84
+ sliceId: "S01",
85
+ milestoneId: "M001",
86
+ title: "Test Task",
87
+ status: "pending",
88
+ });
89
+ upsertTaskPlanning("M001", "S01", "T01", {
90
+ title: "Test Task",
91
+ description: "Implement test",
92
+ estimate: "1h",
93
+ files: ["src/test.ts"],
94
+ verify: "npm test",
95
+ inputs: [],
96
+ expectedOutput: ["src/test.ts"],
97
+ observabilityImpact: "",
98
+ fullPlanMd: "",
99
+ });
100
+ }
101
+
102
+ describe("evaluating-gates phase", () => {
103
+ let tmpDir: string;
104
+
105
+ beforeEach(() => {
106
+ const setup = setupTestProject();
107
+ tmpDir = setup.tmpDir;
108
+ });
109
+
110
+ afterEach(() => {
111
+ invalidateAllCaches();
112
+ invalidateStateCache();
113
+ closeDatabase();
114
+ rmSync(tmpDir, { recursive: true, force: true });
115
+ });
116
+
117
+ test("state returns evaluating-gates when slice gates are pending", async () => {
118
+ planSlice(tmpDir);
119
+ await renderPlanFromDb(tmpDir, "M001", "S01");
120
+
121
+ // Seed gates as pending
122
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
123
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
124
+
125
+ invalidateStateCache();
126
+ const state = await deriveState(tmpDir);
127
+ assert.equal(state.phase, "evaluating-gates");
128
+ assert.ok(state.nextAction.includes("quality gate"));
129
+ });
130
+
131
+ test("state returns executing when all gates are resolved", async () => {
132
+ planSlice(tmpDir);
133
+ await renderPlanFromDb(tmpDir, "M001", "S01");
134
+
135
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
136
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
137
+
138
+ saveGateResult({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", verdict: "pass", rationale: "OK", findings: "" });
139
+ saveGateResult({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", verdict: "omitted", rationale: "N/A", findings: "" });
140
+
141
+ invalidateStateCache();
142
+ const state = await deriveState(tmpDir);
143
+ assert.equal(state.phase, "executing");
144
+ });
145
+
146
+ test("state returns executing when no gates exist (backward compat)", async () => {
147
+ planSlice(tmpDir);
148
+ await renderPlanFromDb(tmpDir, "M001", "S01");
149
+
150
+ // No gates seeded at all
151
+ invalidateStateCache();
152
+ const state = await deriveState(tmpDir);
153
+ assert.equal(state.phase, "executing");
154
+ });
155
+
156
+ test("markAllGatesOmitted clears evaluating-gates phase", async () => {
157
+ planSlice(tmpDir);
158
+ await renderPlanFromDb(tmpDir, "M001", "S01");
159
+
160
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
161
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
162
+
163
+ invalidateStateCache();
164
+ assert.equal((await deriveState(tmpDir)).phase, "evaluating-gates");
165
+
166
+ markAllGatesOmitted("M001", "S01");
167
+ invalidateStateCache();
168
+ assert.equal((await deriveState(tmpDir)).phase, "executing");
169
+ });
170
+
171
+ test("task-scoped gates do not block evaluating-gates phase", async () => {
172
+ planSlice(tmpDir);
173
+ await renderPlanFromDb(tmpDir, "M001", "S01");
174
+
175
+ // Only task-scoped gates — no slice-scoped gates
176
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
177
+
178
+ invalidateStateCache();
179
+ const state = await deriveState(tmpDir);
180
+ // Should be executing, not evaluating-gates, because Q5 is task-scoped
181
+ assert.equal(state.phase, "executing");
182
+ });
183
+
184
+ test("getPendingSliceGateCount ignores task-scoped gates", () => {
185
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
186
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
187
+ assert.equal(getPendingSliceGateCount("M001", "S01"), 1);
188
+ });
189
+ });
@@ -0,0 +1,156 @@
1
+ // Quality gate DB storage tests
2
+ // Verifies CRUD operations on the quality_gates table.
3
+
4
+ import { describe, test, beforeEach, afterEach } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { mkdtempSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { tmpdir } from "node:os";
9
+
10
+ import {
11
+ openDatabase,
12
+ closeDatabase,
13
+ insertGateRow,
14
+ saveGateResult,
15
+ getPendingGates,
16
+ getGateResults,
17
+ markAllGatesOmitted,
18
+ getPendingSliceGateCount,
19
+ insertMilestone,
20
+ insertSlice,
21
+ } from "../gsd-db.ts";
22
+
23
+ describe("quality_gates CRUD", () => {
24
+ let tmpDir: string;
25
+ let dbPath: string;
26
+
27
+ beforeEach(() => {
28
+ tmpDir = mkdtempSync(join(tmpdir(), "gate-test-"));
29
+ dbPath = join(tmpDir, "gsd.db");
30
+ openDatabase(dbPath);
31
+ // Seed parent rows
32
+ insertMilestone({
33
+ id: "M001",
34
+ title: "Test Milestone",
35
+ status: "active",
36
+ });
37
+ insertSlice({
38
+ milestoneId: "M001",
39
+ id: "S01",
40
+ title: "Test Slice",
41
+ status: "pending",
42
+ risk: "medium",
43
+ depends: [],
44
+ });
45
+ });
46
+
47
+ afterEach(() => {
48
+ closeDatabase();
49
+ rmSync(tmpDir, { recursive: true, force: true });
50
+ });
51
+
52
+ test("insertGateRow creates a pending gate", () => {
53
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
54
+ const pending = getPendingGates("M001", "S01");
55
+ assert.equal(pending.length, 1);
56
+ assert.equal(pending[0].gate_id, "Q3");
57
+ assert.equal(pending[0].status, "pending");
58
+ assert.equal(pending[0].scope, "slice");
59
+ });
60
+
61
+ test("insertGateRow with INSERT OR IGNORE is idempotent", () => {
62
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
63
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
64
+ const all = getGateResults("M001", "S01");
65
+ assert.equal(all.length, 1);
66
+ });
67
+
68
+ test("saveGateResult updates status and verdict", () => {
69
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
70
+ saveGateResult({
71
+ milestoneId: "M001",
72
+ sliceId: "S01",
73
+ gateId: "Q3",
74
+ verdict: "pass",
75
+ rationale: "No auth surface",
76
+ findings: "This slice has no user-facing endpoints.",
77
+ });
78
+ const results = getGateResults("M001", "S01");
79
+ assert.equal(results.length, 1);
80
+ assert.equal(results[0].status, "complete");
81
+ assert.equal(results[0].verdict, "pass");
82
+ assert.equal(results[0].rationale, "No auth surface");
83
+ assert.ok(results[0].evaluated_at);
84
+ });
85
+
86
+ test("getPendingGates filters by scope", () => {
87
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
88
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
89
+
90
+ const sliceGates = getPendingGates("M001", "S01", "slice");
91
+ assert.equal(sliceGates.length, 1);
92
+ assert.equal(sliceGates[0].gate_id, "Q3");
93
+
94
+ const taskGates = getPendingGates("M001", "S01", "task");
95
+ assert.equal(taskGates.length, 1);
96
+ assert.equal(taskGates[0].gate_id, "Q5");
97
+ });
98
+
99
+ test("markAllGatesOmitted marks all pending gates as omitted", () => {
100
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
101
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
102
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
103
+
104
+ markAllGatesOmitted("M001", "S01");
105
+
106
+ const pending = getPendingGates("M001", "S01");
107
+ assert.equal(pending.length, 0);
108
+
109
+ const all = getGateResults("M001", "S01");
110
+ assert.equal(all.length, 3);
111
+ for (const g of all) {
112
+ assert.equal(g.status, "omitted");
113
+ assert.equal(g.verdict, "omitted");
114
+ }
115
+ });
116
+
117
+ test("getPendingSliceGateCount returns correct count", () => {
118
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
119
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
120
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
121
+
122
+ assert.equal(getPendingSliceGateCount("M001", "S01"), 2);
123
+
124
+ saveGateResult({
125
+ milestoneId: "M001", sliceId: "S01", gateId: "Q3",
126
+ verdict: "pass", rationale: "OK", findings: "",
127
+ });
128
+ assert.equal(getPendingSliceGateCount("M001", "S01"), 1);
129
+ });
130
+
131
+ test("task-scoped gates with different task_id are distinct", () => {
132
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
133
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T02" });
134
+
135
+ const all = getGateResults("M001", "S01", "task");
136
+ assert.equal(all.length, 2);
137
+ });
138
+
139
+ test("getGateResults returns empty for nonexistent slice", () => {
140
+ const results = getGateResults("M001", "S99");
141
+ assert.equal(results.length, 0);
142
+ });
143
+
144
+ test("saveGateResult with flag verdict preserves findings", () => {
145
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
146
+ saveGateResult({
147
+ milestoneId: "M001", sliceId: "S01", gateId: "Q4",
148
+ verdict: "flag", rationale: "Breaks R003",
149
+ findings: "## R003 Impact\n\n- Login flow must be re-tested\n- Session token format changed",
150
+ });
151
+ const results = getGateResults("M001", "S01", "slice");
152
+ const q4 = results.find(g => g.gate_id === "Q4")!;
153
+ assert.equal(q4.verdict, "flag");
154
+ assert.ok(q4.findings.includes("R003 Impact"));
155
+ });
156
+ });
@@ -868,6 +868,55 @@ describe('git-service', async () => {
868
868
  rmSync(repo, { recursive: true, force: true });
869
869
  });
870
870
 
871
+ // ─── writeIntegrationBranch: rejects workflow-template branches (#2498) ─
872
+
873
+ test('Integration branch: rejects workflow-template branches', () => {
874
+ const repo = initBranchTestRepo();
875
+
876
+ // All 8 registered workflow templates should be rejected
877
+ writeIntegrationBranch(repo, "M001", "gsd/hotfix/fix-login");
878
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "hotfix branch is not recorded");
879
+
880
+ writeIntegrationBranch(repo, "M001", "gsd/bugfix/null-pointer");
881
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "bugfix branch is not recorded");
882
+
883
+ writeIntegrationBranch(repo, "M001", "gsd/small-feature/add-button");
884
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "small-feature branch is not recorded");
885
+
886
+ writeIntegrationBranch(repo, "M001", "gsd/refactor/rename-module");
887
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "refactor branch is not recorded");
888
+
889
+ writeIntegrationBranch(repo, "M001", "gsd/spike/evaluate-lib");
890
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "spike branch is not recorded");
891
+
892
+ writeIntegrationBranch(repo, "M001", "gsd/security-audit/owasp-scan");
893
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "security-audit branch is not recorded");
894
+
895
+ writeIntegrationBranch(repo, "M001", "gsd/dep-upgrade/bump-react");
896
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "dep-upgrade branch is not recorded");
897
+
898
+ writeIntegrationBranch(repo, "M001", "gsd/full-project/new-app");
899
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "full-project branch is not recorded");
900
+
901
+ rmSync(repo, { recursive: true, force: true });
902
+ });
903
+
904
+ // ─── writeIntegrationBranch: still records legitimate branches ────────
905
+
906
+ test('Integration branch: records non-ephemeral gsd branches', () => {
907
+ const repo = initBranchTestRepo();
908
+
909
+ // A normal feature branch should still be recorded
910
+ writeIntegrationBranch(repo, "M001", "feature/new-thing");
911
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "feature/new-thing", "normal branches are recorded");
912
+
913
+ // The main branch should be recorded
914
+ writeIntegrationBranch(repo, "M002", "main");
915
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M002"), "main", "main branch is recorded");
916
+
917
+ rmSync(repo, { recursive: true, force: true });
918
+ });
919
+
871
920
  // ─── writeIntegrationBranch: rejects invalid branch names ─────────────
872
921
 
873
922
  test('Integration branch: rejects invalid names', () => {
@@ -64,7 +64,7 @@ describe('gsd-db', () => {
64
64
  // Check schema_version table
65
65
  const adapter = _getAdapter()!;
66
66
  const version = adapter.prepare('SELECT MAX(version) as version FROM schema_version').get();
67
- assert.deepStrictEqual(version?.['version'], 11, 'schema version should be 11');
67
+ assert.deepStrictEqual(version?.['version'], 12, 'schema version should be 12');
68
68
 
69
69
  // Check tables exist by querying them
70
70
  const dRows = adapter.prepare('SELECT count(*) as cnt FROM decisions').get();
@@ -9,11 +9,11 @@ import { isInfrastructureError, INFRA_ERROR_CODES } from "../auto/infra-errors.j
9
9
  test("INFRA_ERROR_CODES contains the expected codes", () => {
10
10
  for (const code of [
11
11
  "ENOSPC", "ENOMEM", "EROFS", "EDQUOT", "EMFILE", "ENFILE",
12
- "ECONNREFUSED", "ENOTFOUND", "ENETUNREACH",
12
+ "EAGAIN", "ECONNREFUSED", "ENOTFOUND", "ENETUNREACH",
13
13
  ]) {
14
14
  assert.ok(INFRA_ERROR_CODES.has(code), `missing ${code}`);
15
15
  }
16
- assert.equal(INFRA_ERROR_CODES.size, 9, "unexpected extra codes");
16
+ assert.equal(INFRA_ERROR_CODES.size, 10, "unexpected extra codes");
17
17
  });
18
18
 
19
19
  // ── isInfrastructureError: code property detection ───────────────────────────
@@ -48,6 +48,16 @@ test("detects ENFILE via code property", () => {
48
48
  assert.equal(isInfrastructureError(err), "ENFILE");
49
49
  });
50
50
 
51
+ test("detects EAGAIN via code property", () => {
52
+ const err = Object.assign(new Error("resource temporarily unavailable"), { code: "EAGAIN" });
53
+ assert.equal(isInfrastructureError(err), "EAGAIN");
54
+ });
55
+
56
+ test("detects EAGAIN in error message fallback", () => {
57
+ const err = new Error("spawn failed: EAGAIN resource temporarily unavailable");
58
+ assert.equal(isInfrastructureError(err), "EAGAIN");
59
+ });
60
+
51
61
  test("detects ECONNREFUSED via code property", () => {
52
62
  const err = Object.assign(new Error("connect ECONNREFUSED 127.0.0.1:3000"), { code: "ECONNREFUSED" });
53
63
  assert.equal(isInfrastructureError(err), "ECONNREFUSED");
@@ -505,3 +505,42 @@ test("milestone-transition event is emitted when milestone changes", async () =>
505
505
  assert.equal((transitionEvents[0].data as any).to, "M002");
506
506
  assert.equal(transitionEvents[0].flowId, ic.flowId);
507
507
  });
508
+
509
+ test("unit-end event contains errorContext when unit is cancelled with structured error", async () => {
510
+ const capture = createEventCapture();
511
+ const { resolveAgentEndCancelled, _resetPendingResolve } = await import("../auto-loop.js");
512
+ _resetPendingResolve();
513
+
514
+ const deps = makeMockDeps(capture);
515
+ const ic = makeIC(deps);
516
+ const iterData: IterationData = {
517
+ unitType: "execute-task",
518
+ unitId: "M001/S01/T01",
519
+ prompt: "do stuff",
520
+ finalPrompt: "do stuff",
521
+ pauseAfterUatDispatch: false,
522
+ state: { phase: "executing", activeMilestone: { id: "M001" }, activeSlice: { id: "S01" }, registry: [], blockers: [] } as any,
523
+ mid: "M001",
524
+ midTitle: "Test",
525
+ isRetry: false,
526
+ previousTier: undefined,
527
+ };
528
+ const loopState: LoopState = { recentUnits: [{ key: "execute-task/M001/S01/T01" }], stuckRecoveryAttempts: 0 };
529
+
530
+ const unitPromise = runUnitPhase(ic, iterData, loopState);
531
+ await new Promise(r => setTimeout(r, 50));
532
+
533
+ // Resolve with errorContext (simulates a timeout cancel)
534
+ resolveAgentEndCancelled({ message: "Hard timeout error: exceeded limit", category: "timeout", isTransient: true });
535
+
536
+ const result = await unitPromise;
537
+ // Cancelled units break the loop before emitting unit-end
538
+ assert.equal(result.action, "break");
539
+ assert.equal((result as any).reason, "session-failed");
540
+
541
+ // Verify error classification used structured errorContext on the window entry
542
+ const entry = loopState.recentUnits[loopState.recentUnits.length - 1];
543
+ assert.ok(entry.error, "window entry must have error set");
544
+ assert.ok(entry.error!.startsWith("timeout:"), "error must start with category from errorContext");
545
+ assert.ok(entry.error!.includes("Hard timeout error"), "error must include the errorContext message");
546
+ });
@@ -363,7 +363,7 @@ test('md-importer: schema v1→v2 migration', () => {
363
363
  openDatabase(':memory:');
364
364
  const adapter = _getAdapter();
365
365
  const version = adapter?.prepare('SELECT MAX(version) as v FROM schema_version').get();
366
- assert.deepStrictEqual(version?.v, 11, 'new DB should be at schema version 11');
366
+ assert.deepStrictEqual(version?.v, 12, 'new DB should be at schema version 12');
367
367
 
368
368
  // Artifacts table should exist
369
369
  const tableCheck = adapter?.prepare("SELECT count(*) as c FROM sqlite_master WHERE type='table' AND name='artifacts'").get();
@@ -323,9 +323,9 @@ test('memory-store: schema includes memories table', () => {
323
323
  const viewCount = adapter.prepare('SELECT count(*) as cnt FROM active_memories').get();
324
324
  assert.deepStrictEqual(viewCount?.['cnt'], 0, 'active_memories view should exist');
325
325
 
326
- // Verify schema version is 11 (after state machine migration)
326
+ // Verify schema version is 12 (after quality gates table)
327
327
  const version = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
328
- assert.deepStrictEqual(version?.['v'], 11, 'schema version should be 11');
328
+ assert.deepStrictEqual(version?.['v'], 12, 'schema version should be 12');
329
329
 
330
330
  closeDatabase();
331
331
  });