gsd-pi 2.82.0-dev.3709f22a5 → 2.82.0-dev.4285182e8
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/README.md +2 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +14 -1
- package/dist/resources/extensions/gsd/auto/phases.js +53 -29
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +3 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +13 -19
- package/dist/resources/extensions/gsd/auto-post-unit.js +13 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
- package/dist/resources/extensions/gsd/auto-start.js +3 -3
- package/dist/resources/extensions/gsd/auto-verification.js +17 -4
- package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
- package/dist/resources/extensions/gsd/auto.js +14 -8
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +7 -2
- package/dist/resources/extensions/gsd/commands-verdict.js +139 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
- package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
- package/dist/resources/extensions/gsd/forensics.js +3 -3
- package/dist/resources/extensions/gsd/git-service.js +6 -2
- package/dist/resources/extensions/gsd/gsd-db.js +20 -6
- package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
- package/dist/resources/extensions/gsd/guided-flow.js +8 -5
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -8
- package/dist/resources/extensions/gsd/paths.js +4 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
- package/dist/resources/extensions/gsd/state.js +3 -3
- package/dist/resources/extensions/gsd/status-guards.js +7 -0
- package/dist/resources/extensions/gsd/templates/plan.md +1 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -5
- package/dist/resources/extensions/gsd/workflow-mcp.js +17 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +1 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.js +3 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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/api/browse-directories/route.js +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 +11 -11
- 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 +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/8359.65b24fac92188a6b.js +10 -0
- package/dist/web/standalone/.next/static/chunks/9441.ff70bb53f6835771.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9a4db269f9ed63ad.js → webpack-855d616060cb6e59.js} +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -3
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +71 -97
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +25 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- 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 +24 -10
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +53 -3
- package/packages/pi-coding-agent/src/core/sdk.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -102
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +30 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +29 -10
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
- package/packages/pi-tui/dist/terminal.d.ts +2 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +12 -0
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
- package/packages/pi-tui/src/terminal.ts +11 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +60 -36
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +5 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +13 -19
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
- package/src/resources/extensions/gsd/auto-start.ts +2 -3
- package/src/resources/extensions/gsd/auto-verification.ts +22 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
- package/src/resources/extensions/gsd/auto.ts +13 -8
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +8 -3
- package/src/resources/extensions/gsd/commands-verdict.ts +202 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
- package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
- package/src/resources/extensions/gsd/forensics.ts +3 -3
- package/src/resources/extensions/gsd/git-service.ts +6 -3
- package/src/resources/extensions/gsd/gsd-db.ts +18 -6
- package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +8 -5
- package/src/resources/extensions/gsd/markdown-renderer.ts +10 -8
- package/src/resources/extensions/gsd/paths.ts +5 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
- package/src/resources/extensions/gsd/state.ts +3 -3
- package/src/resources/extensions/gsd/status-guards.ts +8 -0
- package/src/resources/extensions/gsd/templates/plan.md +1 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +139 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -4
- package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +1 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +5 -1
- package/dist/web/standalone/.next/static/chunks/8359.7eb3bb8f8ecf4c01.js +0 -10
- package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +0 -1
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for /gsd verdict — manual override of milestone validation verdict.
|
|
3
|
+
*
|
|
4
|
+
* Covers parseValidationFile section extraction and handleVerdict end-to-end:
|
|
5
|
+
* pass override, needs-remediation override with rationale, missing rationale
|
|
6
|
+
* rejection, active-milestone fallback, missing VALIDATION rejection.
|
|
7
|
+
*
|
|
8
|
+
* Also asserts the three paused-state messages reference /gsd verdict so the
|
|
9
|
+
* user has a discoverable recovery path.
|
|
10
|
+
*/
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
17
|
+
|
|
18
|
+
import { handleVerdict, parseValidationFile } from "../commands-verdict.ts";
|
|
19
|
+
import { openDatabase, closeDatabase, _getAdapter } from "../gsd-db.ts";
|
|
20
|
+
import { invalidateStateCache } from "../state.ts";
|
|
21
|
+
|
|
22
|
+
interface NotifyCall {
|
|
23
|
+
message: string;
|
|
24
|
+
kind: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeMockCtx(): { ctx: any; calls: NotifyCall[] } {
|
|
28
|
+
const calls: NotifyCall[] = [];
|
|
29
|
+
const ctx = {
|
|
30
|
+
ui: {
|
|
31
|
+
notify: (message: string, kind: string) => {
|
|
32
|
+
calls.push({ message, kind });
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
return { ctx, calls };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function makeBase(): string {
|
|
40
|
+
const base = mkdtempSync(join(tmpdir(), `gsd-verdict-${randomUUID()}-`));
|
|
41
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
42
|
+
return base;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function cleanup(base: string): void {
|
|
46
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* swallow */ }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function openTestDb(base: string): void {
|
|
50
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function seedMilestone(milestoneId: string, title: string, status = "active"): void {
|
|
54
|
+
const db = _getAdapter();
|
|
55
|
+
if (!db) throw new Error("DB not open");
|
|
56
|
+
db.prepare(
|
|
57
|
+
"INSERT OR REPLACE INTO milestones (id, title, status, created_at) VALUES (?, ?, ?, ?)",
|
|
58
|
+
).run(milestoneId, title, status, new Date().toISOString());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function seedSlice(milestoneId: string, sliceId: string, status: string): void {
|
|
62
|
+
const db = _getAdapter();
|
|
63
|
+
if (!db) throw new Error("DB not open");
|
|
64
|
+
db.prepare(
|
|
65
|
+
"INSERT OR REPLACE INTO slices (milestone_id, id, title, status, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
66
|
+
).run(milestoneId, sliceId, `Slice ${sliceId}`, status, new Date().toISOString());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function writeValidation(base: string, milestoneId: string, verdict: string, round = 0): string {
|
|
70
|
+
const milestoneDir = join(base, ".gsd", "milestones", milestoneId);
|
|
71
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
72
|
+
const path = join(milestoneDir, `${milestoneId}-VALIDATION.md`);
|
|
73
|
+
const md = [
|
|
74
|
+
"---",
|
|
75
|
+
`verdict: ${verdict}`,
|
|
76
|
+
`remediation_round: ${round}`,
|
|
77
|
+
"---",
|
|
78
|
+
"",
|
|
79
|
+
`# Milestone Validation: ${milestoneId}`,
|
|
80
|
+
"",
|
|
81
|
+
"## Success Criteria Checklist",
|
|
82
|
+
"- [x] Criterion A met",
|
|
83
|
+
"- [x] Criterion B met",
|
|
84
|
+
"",
|
|
85
|
+
"## Slice Delivery Audit",
|
|
86
|
+
"| Slice | Result |",
|
|
87
|
+
"| --- | --- |",
|
|
88
|
+
"| S01 | delivered |",
|
|
89
|
+
"",
|
|
90
|
+
"## Cross-Slice Integration",
|
|
91
|
+
"No cross-slice mismatches detected.",
|
|
92
|
+
"",
|
|
93
|
+
"## Requirement Coverage",
|
|
94
|
+
"All requirements covered.",
|
|
95
|
+
"",
|
|
96
|
+
"## Verdict Rationale",
|
|
97
|
+
`Initial verdict was ${verdict}.`,
|
|
98
|
+
"",
|
|
99
|
+
].join("\n");
|
|
100
|
+
writeFileSync(path, md);
|
|
101
|
+
return path;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── parseValidationFile ────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
test("parseValidationFile extracts all standard sections and frontmatter", () => {
|
|
107
|
+
const md = [
|
|
108
|
+
"---",
|
|
109
|
+
"verdict: needs-attention",
|
|
110
|
+
"remediation_round: 2",
|
|
111
|
+
"---",
|
|
112
|
+
"",
|
|
113
|
+
"# Milestone Validation: M001",
|
|
114
|
+
"",
|
|
115
|
+
"## Success Criteria Checklist",
|
|
116
|
+
"- [x] First",
|
|
117
|
+
"- [ ] Second",
|
|
118
|
+
"",
|
|
119
|
+
"## Slice Delivery Audit",
|
|
120
|
+
"| Slice | Delivered |",
|
|
121
|
+
"",
|
|
122
|
+
"## Cross-Slice Integration",
|
|
123
|
+
"Boundary intact.",
|
|
124
|
+
"",
|
|
125
|
+
"## Requirement Coverage",
|
|
126
|
+
"R001 covered.",
|
|
127
|
+
"",
|
|
128
|
+
"## Verification Class Compliance",
|
|
129
|
+
"Operational: MET",
|
|
130
|
+
"",
|
|
131
|
+
"## Verdict Rationale",
|
|
132
|
+
"Acceptance proof incomplete.",
|
|
133
|
+
"",
|
|
134
|
+
"## Remediation Plan",
|
|
135
|
+
"Address acceptance proof.",
|
|
136
|
+
"",
|
|
137
|
+
].join("\n");
|
|
138
|
+
|
|
139
|
+
const parsed = parseValidationFile(md);
|
|
140
|
+
|
|
141
|
+
assert.equal(parsed.verdict, "needs-attention");
|
|
142
|
+
assert.equal(parsed.remediationRound, 2);
|
|
143
|
+
assert.match(parsed.successCriteriaChecklist, /First/);
|
|
144
|
+
assert.match(parsed.successCriteriaChecklist, /Second/);
|
|
145
|
+
assert.match(parsed.sliceDeliveryAudit, /Delivered/);
|
|
146
|
+
assert.match(parsed.crossSliceIntegration, /Boundary intact/);
|
|
147
|
+
assert.match(parsed.requirementCoverage, /R001 covered/);
|
|
148
|
+
assert.match(parsed.verificationClasses ?? "", /Operational: MET/);
|
|
149
|
+
assert.match(parsed.verdictRationale, /Acceptance proof incomplete/);
|
|
150
|
+
assert.match(parsed.remediationPlan ?? "", /Address acceptance proof/);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("parseValidationFile omits optional sections when absent", () => {
|
|
154
|
+
const md = [
|
|
155
|
+
"---",
|
|
156
|
+
"verdict: pass",
|
|
157
|
+
"remediation_round: 0",
|
|
158
|
+
"---",
|
|
159
|
+
"",
|
|
160
|
+
"## Success Criteria Checklist",
|
|
161
|
+
"- [x] All met",
|
|
162
|
+
"",
|
|
163
|
+
"## Slice Delivery Audit",
|
|
164
|
+
"Done.",
|
|
165
|
+
"",
|
|
166
|
+
"## Cross-Slice Integration",
|
|
167
|
+
"Clean.",
|
|
168
|
+
"",
|
|
169
|
+
"## Requirement Coverage",
|
|
170
|
+
"Complete.",
|
|
171
|
+
"",
|
|
172
|
+
"## Verdict Rationale",
|
|
173
|
+
"All criteria met.",
|
|
174
|
+
].join("\n");
|
|
175
|
+
|
|
176
|
+
const parsed = parseValidationFile(md);
|
|
177
|
+
|
|
178
|
+
assert.equal(parsed.verdict, "pass");
|
|
179
|
+
assert.equal(parsed.remediationRound, 0);
|
|
180
|
+
assert.equal(parsed.verificationClasses, undefined);
|
|
181
|
+
assert.equal(parsed.remediationPlan, undefined);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// ─── handleVerdict — argument validation ────────────────────────────────
|
|
185
|
+
|
|
186
|
+
test("handleVerdict rejects missing verdict", async () => {
|
|
187
|
+
const { ctx, calls } = makeMockCtx();
|
|
188
|
+
await handleVerdict("", ctx, "/tmp/unused");
|
|
189
|
+
assert.equal(calls.length, 1);
|
|
190
|
+
assert.match(calls[0].message, /Usage: \/gsd verdict/);
|
|
191
|
+
assert.equal(calls[0].kind, "warning");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("handleVerdict rejects invalid verdict", async () => {
|
|
195
|
+
const { ctx, calls } = makeMockCtx();
|
|
196
|
+
await handleVerdict("yolo", ctx, "/tmp/unused");
|
|
197
|
+
assert.equal(calls.length, 1);
|
|
198
|
+
assert.match(calls[0].message, /Invalid verdict "yolo"/);
|
|
199
|
+
assert.equal(calls[0].kind, "warning");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("handleVerdict rejects needs-remediation without --rationale", async () => {
|
|
203
|
+
const base = makeBase();
|
|
204
|
+
try {
|
|
205
|
+
openTestDb(base);
|
|
206
|
+
seedMilestone("M001", "Test Milestone");
|
|
207
|
+
seedSlice("M001", "S01", "complete");
|
|
208
|
+
writeValidation(base, "M001", "pass");
|
|
209
|
+
|
|
210
|
+
const { ctx, calls } = makeMockCtx();
|
|
211
|
+
await handleVerdict("needs-remediation --milestone M001", ctx, base);
|
|
212
|
+
|
|
213
|
+
assert.ok(
|
|
214
|
+
calls.some((c) => /--rationale is required/.test(c.message)),
|
|
215
|
+
`expected rationale-required warning, got: ${JSON.stringify(calls)}`,
|
|
216
|
+
);
|
|
217
|
+
} finally {
|
|
218
|
+
closeDatabase();
|
|
219
|
+
invalidateStateCache();
|
|
220
|
+
cleanup(base);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("handleVerdict rejects when VALIDATION file is missing", async () => {
|
|
225
|
+
const base = makeBase();
|
|
226
|
+
try {
|
|
227
|
+
openTestDb(base);
|
|
228
|
+
seedMilestone("M001", "Test Milestone");
|
|
229
|
+
seedSlice("M001", "S01", "complete");
|
|
230
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
231
|
+
|
|
232
|
+
const { ctx, calls } = makeMockCtx();
|
|
233
|
+
await handleVerdict("pass --milestone M001", ctx, base);
|
|
234
|
+
|
|
235
|
+
assert.ok(
|
|
236
|
+
calls.some((c) => /No VALIDATION file found/.test(c.message)),
|
|
237
|
+
`expected missing-VALIDATION warning, got: ${JSON.stringify(calls)}`,
|
|
238
|
+
);
|
|
239
|
+
} finally {
|
|
240
|
+
closeDatabase();
|
|
241
|
+
invalidateStateCache();
|
|
242
|
+
cleanup(base);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// ─── handleVerdict — pass override flow ─────────────────────────────────
|
|
247
|
+
|
|
248
|
+
test("handleVerdict pass override flips verdict and preserves sections", async () => {
|
|
249
|
+
const base = makeBase();
|
|
250
|
+
try {
|
|
251
|
+
openTestDb(base);
|
|
252
|
+
seedMilestone("M001", "Test Milestone");
|
|
253
|
+
seedSlice("M001", "S01", "complete");
|
|
254
|
+
const validationPath = writeValidation(base, "M001", "needs-attention");
|
|
255
|
+
|
|
256
|
+
const { ctx, calls } = makeMockCtx();
|
|
257
|
+
await handleVerdict("pass --milestone M001", ctx, base);
|
|
258
|
+
|
|
259
|
+
const rewritten = readFileSync(validationPath, "utf-8");
|
|
260
|
+
assert.match(rewritten, /^verdict: pass$/m, "verdict should flip to pass");
|
|
261
|
+
assert.match(rewritten, /Criterion A met/, "success criteria preserved");
|
|
262
|
+
assert.match(rewritten, /S01 \| delivered/, "slice audit preserved");
|
|
263
|
+
assert.match(rewritten, /Manually overridden via \/gsd verdict/, "default rationale applied");
|
|
264
|
+
|
|
265
|
+
assert.ok(
|
|
266
|
+
calls.some((c) => c.kind === "success" && /needs-attention.*->.*pass/.test(c.message)),
|
|
267
|
+
`expected success notification, got: ${JSON.stringify(calls)}`,
|
|
268
|
+
);
|
|
269
|
+
} finally {
|
|
270
|
+
closeDatabase();
|
|
271
|
+
invalidateStateCache();
|
|
272
|
+
cleanup(base);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("handleVerdict needs-remediation override with --rationale rewrites verdict", async () => {
|
|
277
|
+
const base = makeBase();
|
|
278
|
+
try {
|
|
279
|
+
openTestDb(base);
|
|
280
|
+
seedMilestone("M001", "Test Milestone");
|
|
281
|
+
seedSlice("M001", "S01", "complete");
|
|
282
|
+
const validationPath = writeValidation(base, "M001", "pass");
|
|
283
|
+
|
|
284
|
+
const { ctx, calls } = makeMockCtx();
|
|
285
|
+
await handleVerdict(
|
|
286
|
+
'needs-remediation --milestone M001 --rationale "found missing slice"',
|
|
287
|
+
ctx,
|
|
288
|
+
base,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const rewritten = readFileSync(validationPath, "utf-8");
|
|
292
|
+
assert.match(rewritten, /^verdict: needs-remediation$/m);
|
|
293
|
+
assert.match(rewritten, /found missing slice/);
|
|
294
|
+
|
|
295
|
+
assert.ok(
|
|
296
|
+
calls.some((c) => /gsd_reassess_roadmap/.test(c.message)),
|
|
297
|
+
"needs-remediation override should suggest gsd_reassess_roadmap follow-up",
|
|
298
|
+
);
|
|
299
|
+
} finally {
|
|
300
|
+
closeDatabase();
|
|
301
|
+
invalidateStateCache();
|
|
302
|
+
cleanup(base);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("handleVerdict resolves active milestone when --milestone omitted", async () => {
|
|
307
|
+
const base = makeBase();
|
|
308
|
+
try {
|
|
309
|
+
openTestDb(base);
|
|
310
|
+
seedMilestone("M042", "Active Milestone");
|
|
311
|
+
seedSlice("M042", "S01", "complete");
|
|
312
|
+
const validationPath = writeValidation(base, "M042", "needs-attention");
|
|
313
|
+
|
|
314
|
+
const { ctx, calls } = makeMockCtx();
|
|
315
|
+
invalidateStateCache();
|
|
316
|
+
await handleVerdict("pass", ctx, base);
|
|
317
|
+
|
|
318
|
+
const rewritten = readFileSync(validationPath, "utf-8");
|
|
319
|
+
assert.match(rewritten, /^verdict: pass$/m);
|
|
320
|
+
assert.ok(
|
|
321
|
+
calls.some((c) => c.kind === "success" && /M042/.test(c.message)),
|
|
322
|
+
`expected success notification naming M042, got: ${JSON.stringify(calls)}`,
|
|
323
|
+
);
|
|
324
|
+
} finally {
|
|
325
|
+
closeDatabase();
|
|
326
|
+
invalidateStateCache();
|
|
327
|
+
cleanup(base);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// ─── Pause messages reference /gsd verdict ─────────────────────────────
|
|
332
|
+
|
|
333
|
+
test("auto-dispatch needs-attention pause message references /gsd verdict", async () => {
|
|
334
|
+
const { DISPATCH_RULES } = await import("../auto-dispatch.ts");
|
|
335
|
+
const rule = DISPATCH_RULES.find((r) => r.name === "completing-milestone → complete-milestone");
|
|
336
|
+
assert.ok(rule, "completing-milestone rule should exist");
|
|
337
|
+
|
|
338
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-verdict-paused-"));
|
|
339
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
340
|
+
try {
|
|
341
|
+
writeFileSync(
|
|
342
|
+
join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md"),
|
|
343
|
+
"---\nverdict: needs-attention\nremediation_round: 0\n---\n\n# Validation\nNeeds work.\n",
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
const result = await rule!.match({
|
|
347
|
+
mid: "M001",
|
|
348
|
+
midTitle: "Test",
|
|
349
|
+
basePath: base,
|
|
350
|
+
state: { phase: "completing-milestone" } as any,
|
|
351
|
+
prefs: {} as any,
|
|
352
|
+
session: undefined,
|
|
353
|
+
} as any);
|
|
354
|
+
|
|
355
|
+
assert.ok(result !== null);
|
|
356
|
+
assert.equal(result!.action, "stop");
|
|
357
|
+
if (result!.action === "stop") {
|
|
358
|
+
assert.match(result!.reason, /\/gsd verdict/);
|
|
359
|
+
}
|
|
360
|
+
} finally {
|
|
361
|
+
cleanup(base);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("state.ts needs-remediation blocker messages reference /gsd verdict", async () => {
|
|
366
|
+
// We don't need to invoke deriveState — just assert the substring is in the
|
|
367
|
+
// source. The blocker strings are constructed inline and shipped to the user
|
|
368
|
+
// verbatim, so a static check is sufficient and avoids fragile DB setup.
|
|
369
|
+
const stateSource = readFileSync(
|
|
370
|
+
new URL("../state.ts", import.meta.url).pathname,
|
|
371
|
+
"utf-8",
|
|
372
|
+
);
|
|
373
|
+
const occurrences = stateSource.match(/`\/gsd verdict /g) ?? [];
|
|
374
|
+
assert.ok(
|
|
375
|
+
occurrences.length >= 2,
|
|
376
|
+
`expected at least 2 references to /gsd verdict in state.ts blockers, found ${occurrences.length}`,
|
|
377
|
+
);
|
|
378
|
+
});
|
|
@@ -225,6 +225,25 @@ test("clearLock removes the session_file row for the active worker", (t) => {
|
|
|
225
225
|
"session_file row deleted by clearLock");
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
+
test("clearLock marks stale worker crashed when no current-process worker matches", (t) => {
|
|
229
|
+
const base = makeBase();
|
|
230
|
+
t.after(() => cleanup(base));
|
|
231
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
232
|
+
const projectRoot = normalizeRealPath(base);
|
|
233
|
+
const workerId = registerAutoWorker({ projectRootRealpath: projectRoot });
|
|
234
|
+
|
|
235
|
+
setRuntimeKv("worker", workerId, "session_file", "/tmp/stale-session.jsonl");
|
|
236
|
+
setWorkerPid(workerId, 99999);
|
|
237
|
+
expireWorker(workerId);
|
|
238
|
+
assert.ok(readCrashLock(base), "stale worker is detected before clearLock");
|
|
239
|
+
|
|
240
|
+
clearLock(base);
|
|
241
|
+
|
|
242
|
+
assert.equal(getAutoWorker(workerId)?.status, "crashed");
|
|
243
|
+
assert.equal(getRuntimeKv("worker", workerId, "session_file"), null);
|
|
244
|
+
assert.equal(readCrashLock(base), null);
|
|
245
|
+
});
|
|
246
|
+
|
|
228
247
|
test("clearStaleWorkerLock crashes stale worker and cancels latest active dispatch", (t) => {
|
|
229
248
|
const base = makeBase();
|
|
230
249
|
t.after(() => cleanup(base));
|
|
@@ -264,3 +283,27 @@ test("clearStaleWorkerLock crashes stale worker and cancels latest active dispat
|
|
|
264
283
|
assert.equal(getRuntimeKv("worker", workerId, "session_file"), null);
|
|
265
284
|
assert.equal(readCrashLock(base), null);
|
|
266
285
|
});
|
|
286
|
+
|
|
287
|
+
test("clearLock marks stale worker crashed and releases held milestone lease", (t) => {
|
|
288
|
+
const base = makeBase();
|
|
289
|
+
t.after(() => cleanup(base));
|
|
290
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
291
|
+
insertMilestone({ id: "M001", title: "T", status: "active" });
|
|
292
|
+
const projectRoot = normalizeRealPath(base);
|
|
293
|
+
const workerId = registerAutoWorker({ projectRootRealpath: projectRoot });
|
|
294
|
+
const lease = claimMilestoneLease(workerId, "M001");
|
|
295
|
+
assert.equal(lease.ok, true);
|
|
296
|
+
if (!lease.ok) return;
|
|
297
|
+
|
|
298
|
+
setWorkerPid(workerId, 99999);
|
|
299
|
+
expireWorker(workerId);
|
|
300
|
+
assert.ok(readCrashLock(base), "stale worker is detected before clearLock");
|
|
301
|
+
|
|
302
|
+
clearLock(base);
|
|
303
|
+
|
|
304
|
+
assert.equal(getAutoWorker(workerId)?.status, "crashed");
|
|
305
|
+
const leaseRow = _getAdapter()!.prepare(
|
|
306
|
+
`SELECT status FROM milestone_leases WHERE milestone_id = :m`,
|
|
307
|
+
).get({ ":m": "M001" }) as { status: string } | undefined;
|
|
308
|
+
assert.equal(leaseRow?.status, "released");
|
|
309
|
+
});
|
|
@@ -10,6 +10,7 @@ import assert from "node:assert/strict";
|
|
|
10
10
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
13
14
|
|
|
14
15
|
import { DISPATCH_RULES, type DispatchContext } from "../auto-dispatch.ts";
|
|
15
16
|
import { closeDatabase, insertMilestone, openDatabase } from "../gsd-db.ts";
|
|
@@ -23,6 +24,14 @@ function makeBase(): string {
|
|
|
23
24
|
return base;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
function initGitRepo(base: string): void {
|
|
28
|
+
execFileSync("git", ["init"], { cwd: base, stdio: "ignore" });
|
|
29
|
+
execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
|
|
30
|
+
execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
|
|
31
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
32
|
+
execFileSync("git", ["commit", "-m", "initial"], { cwd: base, stdio: "ignore" });
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
function buildDispatchCtx(basePath: string): DispatchContext {
|
|
27
36
|
return {
|
|
28
37
|
basePath,
|
|
@@ -76,6 +85,24 @@ describe("completing-milestone dispatch guard (#4324)", () => {
|
|
|
76
85
|
assert.equal(result?.unitType, "complete-milestone");
|
|
77
86
|
assert.equal(result?.unitId, "M001");
|
|
78
87
|
});
|
|
88
|
+
|
|
89
|
+
test("dispatches complete-milestone when only .gsd/ files exist in git history (#5097)", async () => {
|
|
90
|
+
base = makeBase();
|
|
91
|
+
rmSync(join(base, "implementation.txt"), { force: true });
|
|
92
|
+
initGitRepo(base);
|
|
93
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\n");
|
|
94
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
95
|
+
execFileSync("git", ["commit", "-m", "chore: planning artifacts only"], { cwd: base, stdio: "ignore" });
|
|
96
|
+
|
|
97
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
98
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
|
|
99
|
+
|
|
100
|
+
const result = await rule.match(buildDispatchCtx(base));
|
|
101
|
+
|
|
102
|
+
assert.equal(result?.action, "dispatch");
|
|
103
|
+
assert.equal(result?.unitType, "complete-milestone");
|
|
104
|
+
assert.equal(result?.unitId, "M001");
|
|
105
|
+
});
|
|
79
106
|
});
|
|
80
107
|
|
|
81
108
|
describe("complete phase dispatch guard (#5683)", () => {
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
checkpointDatabase,
|
|
38
38
|
refreshOpenDatabaseFromDisk,
|
|
39
39
|
tryCreateMemoriesFts,
|
|
40
|
+
_isLikelyWslDrvFsPathForTest,
|
|
40
41
|
} from '../gsd-db.ts';
|
|
41
42
|
import { _resetLogs, peekLogs, setStderrLoggingEnabled } from '../workflow-logger.ts';
|
|
42
43
|
|
|
@@ -345,6 +346,16 @@ describe('gsd-db', () => {
|
|
|
345
346
|
});
|
|
346
347
|
});
|
|
347
348
|
|
|
349
|
+
test('gsd-db: detects WSL DrvFs mount paths for conservative pragmas', () => {
|
|
350
|
+
withPlatform('linux', () => {
|
|
351
|
+
assert.equal(_isLikelyWslDrvFsPathForTest('/mnt/d/code/project/.gsd/gsd.db'), true);
|
|
352
|
+
assert.equal(_isLikelyWslDrvFsPathForTest('/tmp/gsd.db'), false);
|
|
353
|
+
});
|
|
354
|
+
withPlatform('darwin', () => {
|
|
355
|
+
assert.equal(_isLikelyWslDrvFsPathForTest('/mnt/d/code/project/.gsd/gsd.db'), false);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
348
359
|
test('gsd-db: transaction rollback on error', () => {
|
|
349
360
|
openDatabase(':memory:');
|
|
350
361
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* gsdRoot() must return the canonical project .gsd directory when basePath
|
|
5
5
|
* is inside a .gsd/worktrees/<name>/ structure. Worktree-local .gsd folders
|
|
6
|
-
* are
|
|
6
|
+
* are projection roots; runtime/control state stays DB-authoritative at the
|
|
7
7
|
* project .gsd.
|
|
8
8
|
*
|
|
9
9
|
* The bug: when a git worktree lives at /project/.gsd/worktrees/M008/,
|
|
@@ -21,7 +21,7 @@ import { mkdtempSync, realpathSync } from "node:fs";
|
|
|
21
21
|
import { tmpdir } from "node:os";
|
|
22
22
|
import { spawnSync } from "node:child_process";
|
|
23
23
|
|
|
24
|
-
import { gsdRoot, resolveGsdPathContract, _clearGsdRootCache } from "../paths.ts";
|
|
24
|
+
import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, _clearGsdRootCache } from "../paths.ts";
|
|
25
25
|
|
|
26
26
|
describe("gsdRoot() worktree detection (#2594)", () => {
|
|
27
27
|
let projectRoot: string;
|
|
@@ -76,6 +76,7 @@ describe("gsdRoot() worktree detection (#2594)", () => {
|
|
|
76
76
|
`Expected canonical project .gsd (${projectGsd}), got ${result}.`,
|
|
77
77
|
);
|
|
78
78
|
assert.equal(resolveGsdPathContract(worktreeBase).worktreeGsd, worktreeGsd);
|
|
79
|
+
assert.equal(gsdProjectionRoot(worktreeBase), worktreeGsd);
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
test("returns project .gsd when worktree .gsd does not exist yet", () => {
|
|
@@ -89,6 +90,7 @@ describe("gsdRoot() worktree detection (#2594)", () => {
|
|
|
89
90
|
projectGsd,
|
|
90
91
|
`Expected canonical project .gsd (${projectGsd}), got ${result}.`,
|
|
91
92
|
);
|
|
93
|
+
assert.equal(gsdProjectionRoot(worktreeBase), join(worktreeBase, ".gsd"));
|
|
92
94
|
});
|
|
93
95
|
|
|
94
96
|
test("returns project .gsd when basePath is a real git worktree inside .gsd/worktrees/", () => {
|
|
@@ -116,6 +118,7 @@ describe("gsdRoot() worktree detection (#2594)", () => {
|
|
|
116
118
|
projectGsd,
|
|
117
119
|
`Expected canonical project .gsd (${projectGsd}), got ${gsdResult}`,
|
|
118
120
|
);
|
|
121
|
+
assert.equal(gsdProjectionRoot(worktreeBase), join(worktreeBase, ".gsd"));
|
|
119
122
|
|
|
120
123
|
// Cleanup worktree
|
|
121
124
|
spawnSync("git", ["worktree", "remove", "--force", worktreeBase], {
|
|
@@ -1168,6 +1168,15 @@ describe('git-service', async () => {
|
|
|
1168
1168
|
rmSync(repo, { recursive: true, force: true });
|
|
1169
1169
|
});
|
|
1170
1170
|
|
|
1171
|
+
test('Integration branch: rejects milestone branches', () => {
|
|
1172
|
+
const repo = initBranchTestRepo();
|
|
1173
|
+
|
|
1174
|
+
writeIntegrationBranch(repo, "M001", "milestone/M001");
|
|
1175
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "milestone branches are not recorded as integration branch");
|
|
1176
|
+
|
|
1177
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1171
1180
|
// ─── writeIntegrationBranch: still records legitimate branches ────────
|
|
1172
1181
|
|
|
1173
1182
|
test('Integration branch: records non-ephemeral gsd branches', () => {
|
|
@@ -22,6 +22,7 @@ import type { IterationContext, LoopState, PreDispatchData, IterationData } from
|
|
|
22
22
|
import type { SessionLockStatus } from "../session-lock.js";
|
|
23
23
|
import { runDispatch, runUnitPhase, runPreDispatch, runFinalize } from "../auto/phases.js";
|
|
24
24
|
import { readUnitRuntimeRecord } from "../unit-runtime.js";
|
|
25
|
+
import { ModelPolicyDispatchBlockedError } from "../auto-model-selection.js";
|
|
25
26
|
import {
|
|
26
27
|
closeDatabase,
|
|
27
28
|
insertMilestone,
|
|
@@ -160,6 +161,8 @@ function makeIC(
|
|
|
160
161
|
pi: {
|
|
161
162
|
sendMessage: () => {},
|
|
162
163
|
setModel: async () => true,
|
|
164
|
+
getThinkingLevel: () => "off",
|
|
165
|
+
setThinkingLevel: () => {},
|
|
163
166
|
} as any,
|
|
164
167
|
s: makeSession(),
|
|
165
168
|
deps,
|
|
@@ -868,6 +871,49 @@ test("runUnitPhase increments unitDispatchCount for repeated artifact-missing re
|
|
|
868
871
|
assert.equal(ic.s.unitDispatchCount.get("execute-task/M001/S01/T01"), 2);
|
|
869
872
|
});
|
|
870
873
|
|
|
874
|
+
test("runUnitPhase pre-dispatch model validation failures do not emit unit-start or dispatch runtime state", async (t) => {
|
|
875
|
+
const capture = createEventCapture();
|
|
876
|
+
const base = mkdtempSync(join(tmpdir(), `gsd-pre-dispatch-block-${randomUUID()}`));
|
|
877
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
878
|
+
|
|
879
|
+
const deps = makeMockDeps(capture, {
|
|
880
|
+
selectAndApplyModel: async () => {
|
|
881
|
+
throw new ModelPolicyDispatchBlockedError("execute-task", "M001/S01/T01", []);
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
const ic = makeIC(deps, {
|
|
885
|
+
s: {
|
|
886
|
+
...makeSession(),
|
|
887
|
+
basePath: base,
|
|
888
|
+
} as any,
|
|
889
|
+
});
|
|
890
|
+
const iterData: IterationData = {
|
|
891
|
+
unitType: "execute-task",
|
|
892
|
+
unitId: "M001/S01/T01",
|
|
893
|
+
prompt: "do stuff",
|
|
894
|
+
finalPrompt: "do stuff",
|
|
895
|
+
pauseAfterUatDispatch: false,
|
|
896
|
+
state: { phase: "executing", activeMilestone: { id: "M001" }, activeSlice: { id: "S01" }, registry: [], blockers: [] } as any,
|
|
897
|
+
mid: "M001",
|
|
898
|
+
midTitle: "Test",
|
|
899
|
+
isRetry: false,
|
|
900
|
+
previousTier: undefined,
|
|
901
|
+
};
|
|
902
|
+
const loopState: LoopState = { recentUnits: [{ key: "execute-task/M001/S01/T01" }], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
|
|
903
|
+
|
|
904
|
+
await assert.rejects(() => runUnitPhase(ic, iterData, loopState), ModelPolicyDispatchBlockedError);
|
|
905
|
+
await assert.rejects(() => runUnitPhase(ic, iterData, loopState), ModelPolicyDispatchBlockedError);
|
|
906
|
+
|
|
907
|
+
const startEvents = capture.events.filter(e => e.eventType === "unit-start");
|
|
908
|
+
assert.equal(startEvents.length, 0, "pre-dispatch validation failures must not emit unit-start");
|
|
909
|
+
assert.equal(ic.s.unitDispatchCount.get("execute-task/M001/S01/T01") ?? 0, 0, "dispatch count must not increment on pre-dispatch validation failure");
|
|
910
|
+
assert.equal(
|
|
911
|
+
readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01"),
|
|
912
|
+
null,
|
|
913
|
+
"pre-dispatch validation failures must not persist a dispatched runtime record",
|
|
914
|
+
);
|
|
915
|
+
});
|
|
916
|
+
|
|
871
917
|
test("all events from a mock iteration have monotonically increasing seq and same flowId", async () => {
|
|
872
918
|
const capture = createEventCapture();
|
|
873
919
|
const { resolveAgentEnd, _resetPendingResolve } = await import("../auto/resolve.js");
|