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.
- package/dist/headless-ui.js +12 -2
- package/dist/headless.js +29 -13
- package/dist/resources/extensions/gsd/auto/infra-errors.js +1 -0
- package/dist/resources/extensions/gsd/auto/phases.js +11 -11
- package/dist/resources/extensions/gsd/auto/resolve.js +2 -2
- package/dist/resources/extensions/gsd/auto/run-unit.js +2 -2
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +8 -10
- package/dist/resources/extensions/gsd/auto-dashboard.js +6 -3
- package/dist/resources/extensions/gsd/auto-dispatch.js +33 -21
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -24
- package/dist/resources/extensions/gsd/auto-prompts.js +102 -21
- package/dist/resources/extensions/gsd/auto-recovery.js +62 -184
- package/dist/resources/extensions/gsd/auto-start.js +4 -31
- package/dist/resources/extensions/gsd/auto-timers.js +2 -2
- package/dist/resources/extensions/gsd/auto-verification.js +4 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +257 -113
- package/dist/resources/extensions/gsd/auto.js +7 -5
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +89 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -1
- package/dist/resources/extensions/gsd/branch-patterns.js +13 -0
- package/dist/resources/extensions/gsd/doctor-checks.js +5 -1234
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +168 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +28 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +405 -0
- package/dist/resources/extensions/gsd/doctor-global-checks.js +74 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +600 -0
- package/dist/resources/extensions/gsd/doctor.js +9 -1
- package/dist/resources/extensions/gsd/extension-manifest.json +1 -1
- package/dist/resources/extensions/gsd/git-service.js +9 -10
- package/dist/resources/extensions/gsd/gsd-db.js +124 -1
- package/dist/resources/extensions/gsd/guided-flow-queue.js +10 -11
- package/dist/resources/extensions/gsd/markdown-renderer.js +33 -5
- package/dist/resources/extensions/gsd/preferences-types.js +2 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +9 -8
- package/dist/resources/extensions/gsd/prompts/execute-task.md +16 -13
- package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +8 -3
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/dist/resources/extensions/gsd/repo-identity.js +29 -0
- package/dist/resources/extensions/gsd/roadmap-slices.js +2 -2
- package/dist/resources/extensions/gsd/session-forensics.js +6 -11
- package/dist/resources/extensions/gsd/session-lock.js +67 -56
- package/dist/resources/extensions/gsd/state.js +34 -7
- package/dist/resources/extensions/gsd/templates/milestone-summary.md +8 -0
- package/dist/resources/extensions/gsd/templates/plan.md +16 -0
- package/dist/resources/extensions/gsd/templates/roadmap.md +13 -0
- package/dist/resources/extensions/gsd/templates/slice-summary.md +9 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +24 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -1
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -3
- package/dist/resources/extensions/gsd/verdict-parser.js +84 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +24 -0
- package/dist/resources/extensions/gsd/worktree.js +3 -2
- package/dist/resources/extensions/remote-questions/config.js +3 -5
- package/dist/resources/extensions/search-the-web/native-search.js +8 -3
- package/dist/resources/extensions/search-the-web/tool-search.js +19 -2
- package/dist/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
- package/dist/web/standalone/.next/server/chunks/229.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/4024.7c75ac378de0f2b5.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-0a4cd455ec4197d2.js → webpack-2473ce2c3879fff4.js} +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -1
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +4 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +39 -10
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +39 -8
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/blob-store.js +8 -3
- package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.js +9 -2
- package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -32
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/jsonl.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js +5 -0
- package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +0 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/blob-store.ts +6 -3
- package/packages/pi-coding-agent/src/core/discovery-cache.ts +9 -2
- package/packages/pi-coding-agent/src/core/retry-handler.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +7 -32
- package/packages/pi-coding-agent/src/modes/rpc/jsonl.ts +6 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +0 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/infra-errors.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +10 -11
- package/src/resources/extensions/gsd/auto/resolve.ts +3 -3
- package/src/resources/extensions/gsd/auto/run-unit.ts +2 -2
- package/src/resources/extensions/gsd/auto/session.ts +5 -0
- package/src/resources/extensions/gsd/auto/types.ts +13 -0
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +19 -21
- package/src/resources/extensions/gsd/auto-dashboard.ts +5 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +39 -21
- package/src/resources/extensions/gsd/auto-loop.ts +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +18 -28
- package/src/resources/extensions/gsd/auto-prompts.ts +113 -19
- package/src/resources/extensions/gsd/auto-recovery.ts +65 -199
- package/src/resources/extensions/gsd/auto-start.ts +7 -27
- package/src/resources/extensions/gsd/auto-timers.ts +2 -2
- package/src/resources/extensions/gsd/auto-verification.ts +4 -7
- package/src/resources/extensions/gsd/auto-worktree.ts +305 -108
- package/src/resources/extensions/gsd/auto.ts +11 -10
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +93 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
- package/src/resources/extensions/gsd/branch-patterns.ts +16 -0
- package/src/resources/extensions/gsd/doctor-checks.ts +5 -1291
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +182 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +30 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +415 -0
- package/src/resources/extensions/gsd/doctor-global-checks.ts +84 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +626 -0
- package/src/resources/extensions/gsd/doctor.ts +9 -1
- package/src/resources/extensions/gsd/extension-manifest.json +1 -1
- package/src/resources/extensions/gsd/git-service.ts +7 -15
- package/src/resources/extensions/gsd/gsd-db.ts +150 -2
- package/src/resources/extensions/gsd/guided-flow-queue.ts +11 -12
- package/src/resources/extensions/gsd/markdown-renderer.ts +37 -4
- package/src/resources/extensions/gsd/preferences-types.ts +5 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +37 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +9 -8
- package/src/resources/extensions/gsd/prompts/execute-task.md +16 -13
- package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +8 -3
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/repo-identity.ts +28 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +2 -2
- package/src/resources/extensions/gsd/session-forensics.ts +6 -11
- package/src/resources/extensions/gsd/session-lock.ts +92 -64
- package/src/resources/extensions/gsd/state.ts +38 -5
- package/src/resources/extensions/gsd/templates/milestone-summary.md +8 -0
- package/src/resources/extensions/gsd/templates/plan.md +16 -0
- package/src/resources/extensions/gsd/templates/roadmap.md +13 -0
- package/src/resources/extensions/gsd/templates/slice-summary.md +9 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +24 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +1 -81
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +9 -12
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +115 -1
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +65 -1
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +347 -0
- package/src/resources/extensions/gsd/tests/queue-completed-milestone-perf.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +20 -16
- package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +223 -0
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +44 -4
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +0 -16
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +204 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +16 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +3 -3
- package/src/resources/extensions/gsd/types.ts +30 -0
- package/src/resources/extensions/gsd/verdict-parser.ts +95 -0
- package/src/resources/extensions/gsd/verification-gate.ts +0 -2
- package/src/resources/extensions/gsd/worktree-resolver.ts +31 -0
- package/src/resources/extensions/gsd/worktree.ts +3 -2
- package/src/resources/extensions/remote-questions/config.ts +3 -5
- package/src/resources/extensions/search-the-web/native-search.ts +8 -3
- package/src/resources/extensions/search-the-web/tool-search.ts +22 -2
- package/src/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +0 -191
- package/dist/resources/extensions/gsd/resource-version.js +0 -97
- package/dist/web/standalone/.next/static/chunks/4024.11ca5c01938e5948.js +0 -9
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -234
- package/src/resources/extensions/gsd/resource-version.ts +0 -101
- /package/dist/web/standalone/.next/static/{PTL5V00OW8q4-092tUQKx → vNN0h0emdEi8l_npi8poE}/_buildManifest.js +0 -0
- /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'],
|
|
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,
|
|
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,
|
|
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
|
|
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'],
|
|
328
|
+
assert.deepStrictEqual(version?.['v'], 12, 'schema version should be 12');
|
|
329
329
|
|
|
330
330
|
closeDatabase();
|
|
331
331
|
});
|