gsd-pi 2.28.0-dev.7da7967 → 2.28.0-dev.853dfc5
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/resource-loader.js +2 -26
- package/dist/resources/extensions/gsd/auto-verification.ts +7 -27
- package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
- package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +7 -136
- package/dist/resources/extensions/gsd/types.ts +0 -1
- package/dist/resources/extensions/gsd/verification-evidence.ts +0 -2
- package/dist/resources/extensions/gsd/verification-gate.ts +2 -13
- package/package.json +1 -1
- package/packages/pi-coding-agent/scripts/copy-assets.cjs +8 -39
- package/src/resources/extensions/gsd/auto-verification.ts +7 -27
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -136
- package/src/resources/extensions/gsd/types.ts +0 -1
- package/src/resources/extensions/gsd/verification-evidence.ts +0 -2
- package/src/resources/extensions/gsd/verification-gate.ts +2 -13
package/dist/resource-loader.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DefaultResourceLoader } from '@gsd/pi-coding-agent';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { chmodSync,
|
|
3
|
+
import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { compareSemver } from './update-check.js';
|
|
@@ -111,34 +111,10 @@ function syncResourceDir(srcDir, destDir) {
|
|
|
111
111
|
rmSync(target, { recursive: true, force: true });
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
|
|
115
|
-
cpSync(srcDir, destDir, { recursive: true, force: true });
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
// Fallback for Windows paths with non-ASCII characters where cpSync
|
|
119
|
-
// fails with the \\?\ extended-length prefix (#1178).
|
|
120
|
-
copyDirRecursive(srcDir, destDir);
|
|
121
|
-
}
|
|
114
|
+
cpSync(srcDir, destDir, { recursive: true, force: true });
|
|
122
115
|
makeTreeWritable(destDir);
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
|
-
/**
|
|
126
|
-
* Recursive directory copy using copyFileSync — workaround for cpSync failures
|
|
127
|
-
* on Windows paths containing non-ASCII characters (#1178).
|
|
128
|
-
*/
|
|
129
|
-
function copyDirRecursive(src, dest) {
|
|
130
|
-
mkdirSync(dest, { recursive: true });
|
|
131
|
-
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
132
|
-
const srcPath = join(src, entry.name);
|
|
133
|
-
const destPath = join(dest, entry.name);
|
|
134
|
-
if (entry.isDirectory()) {
|
|
135
|
-
copyDirRecursive(srcPath, destPath);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
copyFileSync(srcPath, destPath);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
118
|
/**
|
|
143
119
|
* Syncs all bundled resources to agentDir (~/.gsd/agent/) on every launch.
|
|
144
120
|
*
|
|
@@ -105,39 +105,19 @@ export async function runPostUnitVerification(
|
|
|
105
105
|
const completionKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
|
|
106
106
|
|
|
107
107
|
if (result.checks.length > 0) {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const blockingPassCount = blockingChecks.filter(c => c.exitCode === 0).length;
|
|
111
|
-
const advisoryFailCount = advisoryChecks.filter(c => c.exitCode !== 0).length;
|
|
112
|
-
|
|
108
|
+
const passCount = result.checks.filter(c => c.exitCode === 0).length;
|
|
109
|
+
const total = result.checks.length;
|
|
113
110
|
if (result.passed) {
|
|
114
|
-
|
|
115
|
-
? `Verification gate: ${blockingPassCount}/${blockingChecks.length} blocking checks passed`
|
|
116
|
-
: `Verification gate: passed (no blocking checks)`;
|
|
117
|
-
if (advisoryFailCount > 0) {
|
|
118
|
-
msg += ` (${advisoryFailCount} advisory warning${advisoryFailCount > 1 ? "s" : ""})`;
|
|
119
|
-
}
|
|
120
|
-
ctx.ui.notify(msg);
|
|
121
|
-
// Log advisory warnings to stderr for visibility
|
|
122
|
-
if (advisoryFailCount > 0) {
|
|
123
|
-
const advisoryFailures = advisoryChecks.filter(c => c.exitCode !== 0);
|
|
124
|
-
process.stderr.write(`verification-gate: ${advisoryFailCount} advisory (non-blocking) failure(s)\n`);
|
|
125
|
-
for (const f of advisoryFailures) {
|
|
126
|
-
process.stderr.write(` [advisory] ${f.command} exited ${f.exitCode}\n`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
111
|
+
ctx.ui.notify(`Verification gate: ${passCount}/${total} checks passed`);
|
|
129
112
|
} else {
|
|
130
|
-
const
|
|
131
|
-
const failNames =
|
|
113
|
+
const failures = result.checks.filter(c => c.exitCode !== 0);
|
|
114
|
+
const failNames = failures.map(f => f.command).join(", ");
|
|
132
115
|
ctx.ui.notify(`Verification gate: FAILED — ${failNames}`);
|
|
133
|
-
process.stderr.write(`verification-gate: ${
|
|
134
|
-
for (const f of
|
|
116
|
+
process.stderr.write(`verification-gate: ${total - passCount}/${total} checks failed\n`);
|
|
117
|
+
for (const f of failures) {
|
|
135
118
|
process.stderr.write(` ${f.command} exited ${f.exitCode}\n`);
|
|
136
119
|
if (f.stderr) process.stderr.write(` stderr: ${f.stderr.slice(0, 500)}\n`);
|
|
137
120
|
}
|
|
138
|
-
if (advisoryFailCount > 0) {
|
|
139
|
-
process.stderr.write(`verification-gate: ${advisoryFailCount} additional advisory (non-blocking) failure(s)\n`);
|
|
140
|
-
}
|
|
141
121
|
}
|
|
142
122
|
}
|
|
143
123
|
|
|
@@ -58,7 +58,6 @@ test("verification-evidence: writeVerificationJSON writes correct JSON shape", (
|
|
|
58
58
|
stdout: "all good",
|
|
59
59
|
stderr: "",
|
|
60
60
|
durationMs: 2340,
|
|
61
|
-
blocking: true,
|
|
62
61
|
},
|
|
63
62
|
],
|
|
64
63
|
});
|
|
@@ -106,9 +105,9 @@ test("verification-evidence: writeVerificationJSON maps exitCode to verdict corr
|
|
|
106
105
|
const result = makeResult({
|
|
107
106
|
passed: false,
|
|
108
107
|
checks: [
|
|
109
|
-
{ command: "lint", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
110
|
-
{ command: "test", exitCode: 1, stdout: "", stderr: "fail", durationMs: 200
|
|
111
|
-
{ command: "audit", exitCode: 2, stdout: "", stderr: "err", durationMs: 300
|
|
108
|
+
{ command: "lint", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
109
|
+
{ command: "test", exitCode: 1, stdout: "", stderr: "fail", durationMs: 200 },
|
|
110
|
+
{ command: "audit", exitCode: 2, stdout: "", stderr: "err", durationMs: 300 },
|
|
112
111
|
],
|
|
113
112
|
});
|
|
114
113
|
|
|
@@ -134,7 +133,6 @@ test("verification-evidence: writeVerificationJSON excludes stdout/stderr from o
|
|
|
134
133
|
stdout: "hello\n",
|
|
135
134
|
stderr: "some warning",
|
|
136
135
|
durationMs: 50,
|
|
137
|
-
blocking: true,
|
|
138
136
|
},
|
|
139
137
|
],
|
|
140
138
|
});
|
|
@@ -183,8 +181,8 @@ test("verification-evidence: writeVerificationJSON uses optional unitId when pro
|
|
|
183
181
|
test("verification-evidence: formatEvidenceTable returns markdown table with correct columns", () => {
|
|
184
182
|
const result = makeResult({
|
|
185
183
|
checks: [
|
|
186
|
-
{ command: "npm run typecheck", exitCode: 0, stdout: "", stderr: "", durationMs: 2340
|
|
187
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "err", durationMs: 1100
|
|
184
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "", stderr: "", durationMs: 2340 },
|
|
185
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "err", durationMs: 1100 },
|
|
188
186
|
],
|
|
189
187
|
});
|
|
190
188
|
|
|
@@ -216,9 +214,9 @@ test("verification-evidence: formatEvidenceTable returns no-checks message for e
|
|
|
216
214
|
test("verification-evidence: formatEvidenceTable formats duration as seconds with 1 decimal", () => {
|
|
217
215
|
const result = makeResult({
|
|
218
216
|
checks: [
|
|
219
|
-
{ command: "fast", exitCode: 0, stdout: "", stderr: "", durationMs: 150
|
|
220
|
-
{ command: "slow", exitCode: 0, stdout: "", stderr: "", durationMs: 2340
|
|
221
|
-
{ command: "zero", exitCode: 0, stdout: "", stderr: "", durationMs: 0
|
|
217
|
+
{ command: "fast", exitCode: 0, stdout: "", stderr: "", durationMs: 150 },
|
|
218
|
+
{ command: "slow", exitCode: 0, stdout: "", stderr: "", durationMs: 2340 },
|
|
219
|
+
{ command: "zero", exitCode: 0, stdout: "", stderr: "", durationMs: 0 },
|
|
222
220
|
],
|
|
223
221
|
});
|
|
224
222
|
|
|
@@ -232,8 +230,8 @@ test("verification-evidence: formatEvidenceTable uses ✅/❌ emoji for pass/fai
|
|
|
232
230
|
const result = makeResult({
|
|
233
231
|
passed: false,
|
|
234
232
|
checks: [
|
|
235
|
-
{ command: "pass-cmd", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
236
|
-
{ command: "fail-cmd", exitCode: 1, stdout: "", stderr: "", durationMs: 200
|
|
233
|
+
{ command: "pass-cmd", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
234
|
+
{ command: "fail-cmd", exitCode: 1, stdout: "", stderr: "", durationMs: 200 },
|
|
237
235
|
],
|
|
238
236
|
});
|
|
239
237
|
|
|
@@ -337,8 +335,8 @@ test("verification-evidence: integration — VerificationResult → JSON → tab
|
|
|
337
335
|
const result = makeResult({
|
|
338
336
|
passed: false,
|
|
339
337
|
checks: [
|
|
340
|
-
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500
|
|
341
|
-
{ command: "npm run test:unit", exitCode: 1, stdout: "", stderr: "1 failed", durationMs: 3200
|
|
338
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500 },
|
|
339
|
+
{ command: "npm run test:unit", exitCode: 1, stdout: "", stderr: "1 failed", durationMs: 3200 },
|
|
342
340
|
],
|
|
343
341
|
discoverySource: "package-json",
|
|
344
342
|
});
|
|
@@ -392,7 +390,7 @@ test("verification-evidence: writeVerificationJSON with retryAttempt and maxRetr
|
|
|
392
390
|
const result = makeResult({
|
|
393
391
|
passed: false,
|
|
394
392
|
checks: [
|
|
395
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error", durationMs: 300
|
|
393
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error", durationMs: 300 },
|
|
396
394
|
],
|
|
397
395
|
});
|
|
398
396
|
|
|
@@ -417,7 +415,7 @@ test("verification-evidence: writeVerificationJSON without retry params omits re
|
|
|
417
415
|
const result = makeResult({
|
|
418
416
|
passed: true,
|
|
419
417
|
checks: [
|
|
420
|
-
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100
|
|
418
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
421
419
|
],
|
|
422
420
|
});
|
|
423
421
|
|
|
@@ -443,7 +441,7 @@ test("verification-evidence: writeVerificationJSON includes runtimeErrors when p
|
|
|
443
441
|
const result = makeResult({
|
|
444
442
|
passed: false,
|
|
445
443
|
checks: [
|
|
446
|
-
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100
|
|
444
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
447
445
|
],
|
|
448
446
|
runtimeErrors: [
|
|
449
447
|
{ source: "bg-shell", severity: "crash", message: "Server crashed", blocking: true },
|
|
@@ -475,7 +473,7 @@ test("verification-evidence: writeVerificationJSON omits runtimeErrors when abse
|
|
|
475
473
|
const result = makeResult({
|
|
476
474
|
passed: true,
|
|
477
475
|
checks: [
|
|
478
|
-
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50
|
|
476
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50 },
|
|
479
477
|
],
|
|
480
478
|
});
|
|
481
479
|
|
|
@@ -514,7 +512,7 @@ test("verification-evidence: formatEvidenceTable appends runtime errors section"
|
|
|
514
512
|
const result = makeResult({
|
|
515
513
|
passed: false,
|
|
516
514
|
checks: [
|
|
517
|
-
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
515
|
+
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
518
516
|
],
|
|
519
517
|
runtimeErrors: [
|
|
520
518
|
{ source: "bg-shell", severity: "crash", message: "Server crashed with SIGKILL", blocking: true },
|
|
@@ -539,7 +537,7 @@ test("verification-evidence: formatEvidenceTable omits runtime errors section wh
|
|
|
539
537
|
const result = makeResult({
|
|
540
538
|
passed: true,
|
|
541
539
|
checks: [
|
|
542
|
-
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200
|
|
540
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200 },
|
|
543
541
|
],
|
|
544
542
|
});
|
|
545
543
|
|
|
@@ -554,7 +552,7 @@ test("verification-evidence: formatEvidenceTable truncates runtime error message
|
|
|
554
552
|
const result = makeResult({
|
|
555
553
|
passed: false,
|
|
556
554
|
checks: [
|
|
557
|
-
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
555
|
+
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
558
556
|
],
|
|
559
557
|
runtimeErrors: [
|
|
560
558
|
{ source: "bg-shell", severity: "error", message: longMessage, blocking: false },
|
|
@@ -600,7 +598,7 @@ test("verification-evidence: writeVerificationJSON includes auditWarnings when p
|
|
|
600
598
|
const result = makeResult({
|
|
601
599
|
passed: true,
|
|
602
600
|
checks: [
|
|
603
|
-
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100
|
|
601
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
604
602
|
],
|
|
605
603
|
auditWarnings: SAMPLE_AUDIT_WARNINGS,
|
|
606
604
|
});
|
|
@@ -629,7 +627,7 @@ test("verification-evidence: writeVerificationJSON omits auditWarnings when abse
|
|
|
629
627
|
const result = makeResult({
|
|
630
628
|
passed: true,
|
|
631
629
|
checks: [
|
|
632
|
-
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50
|
|
630
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50 },
|
|
633
631
|
],
|
|
634
632
|
});
|
|
635
633
|
|
|
@@ -668,7 +666,7 @@ test("verification-evidence: formatEvidenceTable appends audit warnings section"
|
|
|
668
666
|
const result = makeResult({
|
|
669
667
|
passed: true,
|
|
670
668
|
checks: [
|
|
671
|
-
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
669
|
+
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
672
670
|
],
|
|
673
671
|
auditWarnings: SAMPLE_AUDIT_WARNINGS,
|
|
674
672
|
});
|
|
@@ -691,7 +689,7 @@ test("verification-evidence: formatEvidenceTable omits audit warnings section wh
|
|
|
691
689
|
const result = makeResult({
|
|
692
690
|
passed: true,
|
|
693
691
|
checks: [
|
|
694
|
-
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200
|
|
692
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200 },
|
|
695
693
|
],
|
|
696
694
|
});
|
|
697
695
|
|
|
@@ -707,7 +705,7 @@ test("verification-evidence: integration — VerificationResult with auditWarnin
|
|
|
707
705
|
const result = makeResult({
|
|
708
706
|
passed: true,
|
|
709
707
|
checks: [
|
|
710
|
-
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500
|
|
708
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500 },
|
|
711
709
|
],
|
|
712
710
|
auditWarnings: [
|
|
713
711
|
{
|
|
@@ -581,7 +581,7 @@ test("formatFailureContext: formats a single failure with command, exit code, st
|
|
|
581
581
|
const result: import("../types.ts").VerificationResult = {
|
|
582
582
|
passed: false,
|
|
583
583
|
checks: [
|
|
584
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error: unused var", durationMs: 500
|
|
584
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error: unused var", durationMs: 500 },
|
|
585
585
|
],
|
|
586
586
|
discoverySource: "preference",
|
|
587
587
|
timestamp: Date.now(),
|
|
@@ -598,9 +598,9 @@ test("formatFailureContext: formats multiple failures", () => {
|
|
|
598
598
|
const result: import("../types.ts").VerificationResult = {
|
|
599
599
|
passed: false,
|
|
600
600
|
checks: [
|
|
601
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint error", durationMs: 100
|
|
602
|
-
{ command: "npm run test", exitCode: 2, stdout: "", stderr: "test failure", durationMs: 200
|
|
603
|
-
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 50
|
|
601
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint error", durationMs: 100 },
|
|
602
|
+
{ command: "npm run test", exitCode: 2, stdout: "", stderr: "test failure", durationMs: 200 },
|
|
603
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 50 },
|
|
604
604
|
],
|
|
605
605
|
discoverySource: "preference",
|
|
606
606
|
timestamp: Date.now(),
|
|
@@ -619,7 +619,7 @@ test("formatFailureContext: truncates stderr longer than 2000 chars", () => {
|
|
|
619
619
|
const result: import("../types.ts").VerificationResult = {
|
|
620
620
|
passed: false,
|
|
621
621
|
checks: [
|
|
622
|
-
{ command: "big-err", exitCode: 1, stdout: "", stderr: longStderr, durationMs: 100
|
|
622
|
+
{ command: "big-err", exitCode: 1, stdout: "", stderr: longStderr, durationMs: 100 },
|
|
623
623
|
],
|
|
624
624
|
discoverySource: "preference",
|
|
625
625
|
timestamp: Date.now(),
|
|
@@ -634,8 +634,8 @@ test("formatFailureContext: returns empty string when all checks pass", () => {
|
|
|
634
634
|
const result: import("../types.ts").VerificationResult = {
|
|
635
635
|
passed: true,
|
|
636
636
|
checks: [
|
|
637
|
-
{ command: "npm run lint", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100
|
|
638
|
-
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 200
|
|
637
|
+
{ command: "npm run lint", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
638
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 200 },
|
|
639
639
|
],
|
|
640
640
|
discoverySource: "preference",
|
|
641
641
|
timestamp: Date.now(),
|
|
@@ -663,7 +663,6 @@ test("formatFailureContext: caps total output at 10,000 chars", () => {
|
|
|
663
663
|
stdout: "",
|
|
664
664
|
stderr: "e".repeat(1000), // 1000 chars each, 20 * ~1050 (with formatting) > 10,000
|
|
665
665
|
durationMs: 100,
|
|
666
|
-
blocking: true,
|
|
667
666
|
});
|
|
668
667
|
}
|
|
669
668
|
const result: import("../types.ts").VerificationResult = {
|
|
@@ -1078,131 +1077,3 @@ test("dependency-audit: subdirectory package.json does not trigger audit", () =>
|
|
|
1078
1077
|
assert.equal(npmAuditCalled, false, "subdirectory dependency files should not trigger audit");
|
|
1079
1078
|
assert.deepStrictEqual(result, []);
|
|
1080
1079
|
});
|
|
1081
|
-
|
|
1082
|
-
// ─── Non-Blocking Discovery Tests ────────────────────────────────────────────
|
|
1083
|
-
|
|
1084
|
-
test("non-blocking: package-json discovered commands failing → result.passed is still true", () => {
|
|
1085
|
-
const tmp = makeTempDir("vg-nb-pkg-fail");
|
|
1086
|
-
try {
|
|
1087
|
-
writeFileSync(
|
|
1088
|
-
join(tmp, "package.json"),
|
|
1089
|
-
JSON.stringify({ scripts: { lint: "eslint .", test: "vitest" } }),
|
|
1090
|
-
);
|
|
1091
|
-
// These commands will fail because eslint/vitest don't exist in the temp dir
|
|
1092
|
-
const result = runVerificationGate({
|
|
1093
|
-
basePath: tmp,
|
|
1094
|
-
unitId: "T01",
|
|
1095
|
-
cwd: tmp,
|
|
1096
|
-
// No preference commands — discovery falls through to package.json
|
|
1097
|
-
});
|
|
1098
|
-
assert.equal(result.discoverySource, "package-json");
|
|
1099
|
-
assert.ok(result.checks.length > 0, "should have discovered package.json checks");
|
|
1100
|
-
assert.equal(result.passed, true, "package-json failures should not block the gate");
|
|
1101
|
-
for (const check of result.checks) {
|
|
1102
|
-
assert.equal(check.blocking, false, "package-json checks should be non-blocking");
|
|
1103
|
-
}
|
|
1104
|
-
} finally {
|
|
1105
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
|
-
|
|
1109
|
-
test("non-blocking: preference commands failing → result.passed is false", () => {
|
|
1110
|
-
const tmp = makeTempDir("vg-nb-pref-fail");
|
|
1111
|
-
try {
|
|
1112
|
-
const result = runVerificationGate({
|
|
1113
|
-
basePath: tmp,
|
|
1114
|
-
unitId: "T01",
|
|
1115
|
-
cwd: tmp,
|
|
1116
|
-
preferenceCommands: ["sh -c 'exit 1'"],
|
|
1117
|
-
});
|
|
1118
|
-
assert.equal(result.discoverySource, "preference");
|
|
1119
|
-
assert.equal(result.passed, false, "preference failures should block the gate");
|
|
1120
|
-
assert.equal(result.checks[0].blocking, true, "preference checks should be blocking");
|
|
1121
|
-
} finally {
|
|
1122
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1123
|
-
}
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
test("non-blocking: task-plan commands failing → result.passed is false", () => {
|
|
1127
|
-
const tmp = makeTempDir("vg-nb-tp-fail");
|
|
1128
|
-
try {
|
|
1129
|
-
const result = runVerificationGate({
|
|
1130
|
-
basePath: tmp,
|
|
1131
|
-
unitId: "T01",
|
|
1132
|
-
cwd: tmp,
|
|
1133
|
-
taskPlanVerify: "sh -c 'exit 1'",
|
|
1134
|
-
});
|
|
1135
|
-
assert.equal(result.discoverySource, "task-plan");
|
|
1136
|
-
assert.equal(result.passed, false, "task-plan failures should block the gate");
|
|
1137
|
-
assert.equal(result.checks[0].blocking, true, "task-plan checks should be blocking");
|
|
1138
|
-
} finally {
|
|
1139
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1140
|
-
}
|
|
1141
|
-
});
|
|
1142
|
-
|
|
1143
|
-
test("non-blocking: blocking field is set correctly based on discovery source", () => {
|
|
1144
|
-
const tmp = makeTempDir("vg-nb-field");
|
|
1145
|
-
try {
|
|
1146
|
-
// preference → blocking
|
|
1147
|
-
const prefResult = runVerificationGate({
|
|
1148
|
-
basePath: tmp,
|
|
1149
|
-
unitId: "T01",
|
|
1150
|
-
cwd: tmp,
|
|
1151
|
-
preferenceCommands: ["echo ok"],
|
|
1152
|
-
});
|
|
1153
|
-
assert.equal(prefResult.checks[0].blocking, true);
|
|
1154
|
-
|
|
1155
|
-
// task-plan → blocking
|
|
1156
|
-
const tpResult = runVerificationGate({
|
|
1157
|
-
basePath: tmp,
|
|
1158
|
-
unitId: "T01",
|
|
1159
|
-
cwd: tmp,
|
|
1160
|
-
taskPlanVerify: "echo ok",
|
|
1161
|
-
});
|
|
1162
|
-
assert.equal(tpResult.checks[0].blocking, true);
|
|
1163
|
-
|
|
1164
|
-
// package-json → non-blocking
|
|
1165
|
-
writeFileSync(
|
|
1166
|
-
join(tmp, "package.json"),
|
|
1167
|
-
JSON.stringify({ scripts: { test: "echo ok" } }),
|
|
1168
|
-
);
|
|
1169
|
-
const pkgResult = runVerificationGate({
|
|
1170
|
-
basePath: tmp,
|
|
1171
|
-
unitId: "T01",
|
|
1172
|
-
cwd: tmp,
|
|
1173
|
-
});
|
|
1174
|
-
assert.equal(pkgResult.checks[0].blocking, false);
|
|
1175
|
-
} finally {
|
|
1176
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1177
|
-
}
|
|
1178
|
-
});
|
|
1179
|
-
|
|
1180
|
-
test("non-blocking: formatFailureContext only includes blocking failures", () => {
|
|
1181
|
-
const result: import("../types.ts").VerificationResult = {
|
|
1182
|
-
passed: true,
|
|
1183
|
-
checks: [
|
|
1184
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint warning", durationMs: 100, blocking: false },
|
|
1185
|
-
{ command: "npm run test", exitCode: 1, stdout: "", stderr: "test error", durationMs: 200, blocking: true },
|
|
1186
|
-
{ command: "npm run typecheck", exitCode: 1, stdout: "", stderr: "type error", durationMs: 50, blocking: false },
|
|
1187
|
-
],
|
|
1188
|
-
discoverySource: "preference",
|
|
1189
|
-
timestamp: Date.now(),
|
|
1190
|
-
};
|
|
1191
|
-
const output = formatFailureContext(result);
|
|
1192
|
-
assert.ok(output.includes("`npm run test`"), "should include blocking failure");
|
|
1193
|
-
assert.ok(!output.includes("npm run lint"), "should not include non-blocking failure");
|
|
1194
|
-
assert.ok(!output.includes("npm run typecheck"), "should not include non-blocking failure");
|
|
1195
|
-
});
|
|
1196
|
-
|
|
1197
|
-
test("non-blocking: formatFailureContext returns empty when only non-blocking failures exist", () => {
|
|
1198
|
-
const result: import("../types.ts").VerificationResult = {
|
|
1199
|
-
passed: true,
|
|
1200
|
-
checks: [
|
|
1201
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint warning", durationMs: 100, blocking: false },
|
|
1202
|
-
{ command: "npm run test", exitCode: 1, stdout: "", stderr: "test warning", durationMs: 200, blocking: false },
|
|
1203
|
-
],
|
|
1204
|
-
discoverySource: "package-json",
|
|
1205
|
-
timestamp: Date.now(),
|
|
1206
|
-
};
|
|
1207
|
-
assert.equal(formatFailureContext(result), "", "should return empty when only non-blocking failures");
|
|
1208
|
-
});
|
|
@@ -55,7 +55,6 @@ export interface VerificationCheck {
|
|
|
55
55
|
stdout: string;
|
|
56
56
|
stderr: string;
|
|
57
57
|
durationMs: number;
|
|
58
|
-
blocking: boolean; // true for preference/task-plan sources, false for package-json (advisory only)
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
/** A runtime error captured from bg-shell processes or browser console */
|
|
@@ -20,7 +20,6 @@ export interface EvidenceCheckJSON {
|
|
|
20
20
|
exitCode: number;
|
|
21
21
|
durationMs: number;
|
|
22
22
|
verdict: "pass" | "fail";
|
|
23
|
-
blocking: boolean;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
export interface RuntimeErrorJSON {
|
|
@@ -81,7 +80,6 @@ export function writeVerificationJSON(
|
|
|
81
80
|
exitCode: check.exitCode,
|
|
82
81
|
durationMs: check.durationMs,
|
|
83
82
|
verdict: check.exitCode === 0 ? "pass" : "fail",
|
|
84
|
-
blocking: check.blocking,
|
|
85
83
|
})),
|
|
86
84
|
...(retryAttempt !== undefined ? { retryAttempt } : {}),
|
|
87
85
|
...(maxRetries !== undefined ? { maxRetries } : {}),
|
|
@@ -112,9 +112,7 @@ const MAX_FAILURE_CONTEXT_CHARS = 10_000;
|
|
|
112
112
|
* Returns an empty string when all checks pass or the checks array is empty.
|
|
113
113
|
*/
|
|
114
114
|
export function formatFailureContext(result: VerificationResult): string {
|
|
115
|
-
|
|
116
|
-
// should not be injected into retry prompts to avoid noise pollution.
|
|
117
|
-
const failures = result.checks.filter((c) => c.exitCode !== 0 && c.blocking);
|
|
115
|
+
const failures = result.checks.filter((c) => c.exitCode !== 0);
|
|
118
116
|
if (failures.length === 0) return "";
|
|
119
117
|
|
|
120
118
|
const blocks: string[] = [];
|
|
@@ -258,10 +256,6 @@ export function runVerificationGate(options: RunVerificationGateOptions): Verifi
|
|
|
258
256
|
};
|
|
259
257
|
}
|
|
260
258
|
|
|
261
|
-
// Commands from preference and task-plan sources are blocking;
|
|
262
|
-
// package-json discovered commands are advisory (non-blocking).
|
|
263
|
-
const blocking = source === "preference" || source === "task-plan";
|
|
264
|
-
|
|
265
259
|
const checks: VerificationCheck[] = [];
|
|
266
260
|
|
|
267
261
|
for (const command of commands) {
|
|
@@ -297,16 +291,11 @@ export function runVerificationGate(options: RunVerificationGateOptions): Verifi
|
|
|
297
291
|
stdout: truncate(result.stdout, MAX_OUTPUT_BYTES),
|
|
298
292
|
stderr,
|
|
299
293
|
durationMs,
|
|
300
|
-
blocking,
|
|
301
294
|
});
|
|
302
295
|
}
|
|
303
296
|
|
|
304
|
-
// Gate passes if all blocking checks pass (non-blocking failures are advisory)
|
|
305
|
-
const blockingChecks = checks.filter(c => c.blocking);
|
|
306
|
-
const passed = blockingChecks.length === 0 || blockingChecks.every(c => c.exitCode === 0);
|
|
307
|
-
|
|
308
297
|
return {
|
|
309
|
-
passed,
|
|
298
|
+
passed: checks.every(c => c.exitCode === 0),
|
|
310
299
|
checks,
|
|
311
300
|
discoverySource: source,
|
|
312
301
|
timestamp,
|
package/package.json
CHANGED
|
@@ -1,55 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const { mkdirSync, cpSync
|
|
3
|
-
const { join } = require('path');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Recursive directory copy using copyFileSync — workaround for cpSync failures
|
|
7
|
-
* on Windows paths containing non-ASCII characters (#1178).
|
|
8
|
-
*/
|
|
9
|
-
function safeCpSync(src, dest, options) {
|
|
10
|
-
try {
|
|
11
|
-
cpSync(src, dest, options);
|
|
12
|
-
} catch {
|
|
13
|
-
if (options && options.recursive) {
|
|
14
|
-
copyDirRecursive(src, dest, options && options.filter);
|
|
15
|
-
} else {
|
|
16
|
-
copyFileSync(src, dest);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function copyDirRecursive(src, dest, filter) {
|
|
22
|
-
mkdirSync(dest, { recursive: true });
|
|
23
|
-
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
24
|
-
const srcPath = join(src, entry.name);
|
|
25
|
-
const destPath = join(dest, entry.name);
|
|
26
|
-
if (filter && !filter(srcPath)) continue;
|
|
27
|
-
if (entry.isDirectory()) {
|
|
28
|
-
copyDirRecursive(srcPath, destPath, filter);
|
|
29
|
-
} else {
|
|
30
|
-
copyFileSync(srcPath, destPath);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
2
|
+
const { mkdirSync, cpSync } = require('fs');
|
|
34
3
|
|
|
35
4
|
// Theme assets
|
|
36
5
|
mkdirSync('dist/modes/interactive/theme', { recursive: true });
|
|
37
|
-
|
|
6
|
+
cpSync('src/modes/interactive/theme', 'dist/modes/interactive/theme', {
|
|
38
7
|
recursive: true,
|
|
39
8
|
filter: (s) => !s.endsWith('.ts'),
|
|
40
9
|
});
|
|
41
10
|
|
|
42
11
|
// Export HTML templates and vendor files
|
|
43
12
|
mkdirSync('dist/core/export-html/vendor', { recursive: true });
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
13
|
+
cpSync('src/core/export-html/template.html', 'dist/core/export-html/template.html');
|
|
14
|
+
cpSync('src/core/export-html/template.css', 'dist/core/export-html/template.css');
|
|
15
|
+
cpSync('src/core/export-html/template.js', 'dist/core/export-html/template.js');
|
|
16
|
+
cpSync('src/core/export-html/vendor', 'dist/core/export-html/vendor', {
|
|
48
17
|
recursive: true,
|
|
49
18
|
filter: (s) => !s.endsWith('.ts'),
|
|
50
19
|
});
|
|
51
20
|
|
|
52
21
|
// LSP defaults
|
|
53
22
|
mkdirSync('dist/core/lsp', { recursive: true });
|
|
54
|
-
|
|
55
|
-
|
|
23
|
+
cpSync('src/core/lsp/defaults.json', 'dist/core/lsp/defaults.json');
|
|
24
|
+
cpSync('src/core/lsp/lsp.md', 'dist/core/lsp/lsp.md');
|
|
@@ -105,39 +105,19 @@ export async function runPostUnitVerification(
|
|
|
105
105
|
const completionKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
|
|
106
106
|
|
|
107
107
|
if (result.checks.length > 0) {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const blockingPassCount = blockingChecks.filter(c => c.exitCode === 0).length;
|
|
111
|
-
const advisoryFailCount = advisoryChecks.filter(c => c.exitCode !== 0).length;
|
|
112
|
-
|
|
108
|
+
const passCount = result.checks.filter(c => c.exitCode === 0).length;
|
|
109
|
+
const total = result.checks.length;
|
|
113
110
|
if (result.passed) {
|
|
114
|
-
|
|
115
|
-
? `Verification gate: ${blockingPassCount}/${blockingChecks.length} blocking checks passed`
|
|
116
|
-
: `Verification gate: passed (no blocking checks)`;
|
|
117
|
-
if (advisoryFailCount > 0) {
|
|
118
|
-
msg += ` (${advisoryFailCount} advisory warning${advisoryFailCount > 1 ? "s" : ""})`;
|
|
119
|
-
}
|
|
120
|
-
ctx.ui.notify(msg);
|
|
121
|
-
// Log advisory warnings to stderr for visibility
|
|
122
|
-
if (advisoryFailCount > 0) {
|
|
123
|
-
const advisoryFailures = advisoryChecks.filter(c => c.exitCode !== 0);
|
|
124
|
-
process.stderr.write(`verification-gate: ${advisoryFailCount} advisory (non-blocking) failure(s)\n`);
|
|
125
|
-
for (const f of advisoryFailures) {
|
|
126
|
-
process.stderr.write(` [advisory] ${f.command} exited ${f.exitCode}\n`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
111
|
+
ctx.ui.notify(`Verification gate: ${passCount}/${total} checks passed`);
|
|
129
112
|
} else {
|
|
130
|
-
const
|
|
131
|
-
const failNames =
|
|
113
|
+
const failures = result.checks.filter(c => c.exitCode !== 0);
|
|
114
|
+
const failNames = failures.map(f => f.command).join(", ");
|
|
132
115
|
ctx.ui.notify(`Verification gate: FAILED — ${failNames}`);
|
|
133
|
-
process.stderr.write(`verification-gate: ${
|
|
134
|
-
for (const f of
|
|
116
|
+
process.stderr.write(`verification-gate: ${total - passCount}/${total} checks failed\n`);
|
|
117
|
+
for (const f of failures) {
|
|
135
118
|
process.stderr.write(` ${f.command} exited ${f.exitCode}\n`);
|
|
136
119
|
if (f.stderr) process.stderr.write(` stderr: ${f.stderr.slice(0, 500)}\n`);
|
|
137
120
|
}
|
|
138
|
-
if (advisoryFailCount > 0) {
|
|
139
|
-
process.stderr.write(`verification-gate: ${advisoryFailCount} additional advisory (non-blocking) failure(s)\n`);
|
|
140
|
-
}
|
|
141
121
|
}
|
|
142
122
|
}
|
|
143
123
|
|
|
@@ -58,7 +58,6 @@ test("verification-evidence: writeVerificationJSON writes correct JSON shape", (
|
|
|
58
58
|
stdout: "all good",
|
|
59
59
|
stderr: "",
|
|
60
60
|
durationMs: 2340,
|
|
61
|
-
blocking: true,
|
|
62
61
|
},
|
|
63
62
|
],
|
|
64
63
|
});
|
|
@@ -106,9 +105,9 @@ test("verification-evidence: writeVerificationJSON maps exitCode to verdict corr
|
|
|
106
105
|
const result = makeResult({
|
|
107
106
|
passed: false,
|
|
108
107
|
checks: [
|
|
109
|
-
{ command: "lint", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
110
|
-
{ command: "test", exitCode: 1, stdout: "", stderr: "fail", durationMs: 200
|
|
111
|
-
{ command: "audit", exitCode: 2, stdout: "", stderr: "err", durationMs: 300
|
|
108
|
+
{ command: "lint", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
109
|
+
{ command: "test", exitCode: 1, stdout: "", stderr: "fail", durationMs: 200 },
|
|
110
|
+
{ command: "audit", exitCode: 2, stdout: "", stderr: "err", durationMs: 300 },
|
|
112
111
|
],
|
|
113
112
|
});
|
|
114
113
|
|
|
@@ -134,7 +133,6 @@ test("verification-evidence: writeVerificationJSON excludes stdout/stderr from o
|
|
|
134
133
|
stdout: "hello\n",
|
|
135
134
|
stderr: "some warning",
|
|
136
135
|
durationMs: 50,
|
|
137
|
-
blocking: true,
|
|
138
136
|
},
|
|
139
137
|
],
|
|
140
138
|
});
|
|
@@ -183,8 +181,8 @@ test("verification-evidence: writeVerificationJSON uses optional unitId when pro
|
|
|
183
181
|
test("verification-evidence: formatEvidenceTable returns markdown table with correct columns", () => {
|
|
184
182
|
const result = makeResult({
|
|
185
183
|
checks: [
|
|
186
|
-
{ command: "npm run typecheck", exitCode: 0, stdout: "", stderr: "", durationMs: 2340
|
|
187
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "err", durationMs: 1100
|
|
184
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "", stderr: "", durationMs: 2340 },
|
|
185
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "err", durationMs: 1100 },
|
|
188
186
|
],
|
|
189
187
|
});
|
|
190
188
|
|
|
@@ -216,9 +214,9 @@ test("verification-evidence: formatEvidenceTable returns no-checks message for e
|
|
|
216
214
|
test("verification-evidence: formatEvidenceTable formats duration as seconds with 1 decimal", () => {
|
|
217
215
|
const result = makeResult({
|
|
218
216
|
checks: [
|
|
219
|
-
{ command: "fast", exitCode: 0, stdout: "", stderr: "", durationMs: 150
|
|
220
|
-
{ command: "slow", exitCode: 0, stdout: "", stderr: "", durationMs: 2340
|
|
221
|
-
{ command: "zero", exitCode: 0, stdout: "", stderr: "", durationMs: 0
|
|
217
|
+
{ command: "fast", exitCode: 0, stdout: "", stderr: "", durationMs: 150 },
|
|
218
|
+
{ command: "slow", exitCode: 0, stdout: "", stderr: "", durationMs: 2340 },
|
|
219
|
+
{ command: "zero", exitCode: 0, stdout: "", stderr: "", durationMs: 0 },
|
|
222
220
|
],
|
|
223
221
|
});
|
|
224
222
|
|
|
@@ -232,8 +230,8 @@ test("verification-evidence: formatEvidenceTable uses ✅/❌ emoji for pass/fai
|
|
|
232
230
|
const result = makeResult({
|
|
233
231
|
passed: false,
|
|
234
232
|
checks: [
|
|
235
|
-
{ command: "pass-cmd", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
236
|
-
{ command: "fail-cmd", exitCode: 1, stdout: "", stderr: "", durationMs: 200
|
|
233
|
+
{ command: "pass-cmd", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
234
|
+
{ command: "fail-cmd", exitCode: 1, stdout: "", stderr: "", durationMs: 200 },
|
|
237
235
|
],
|
|
238
236
|
});
|
|
239
237
|
|
|
@@ -337,8 +335,8 @@ test("verification-evidence: integration — VerificationResult → JSON → tab
|
|
|
337
335
|
const result = makeResult({
|
|
338
336
|
passed: false,
|
|
339
337
|
checks: [
|
|
340
|
-
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500
|
|
341
|
-
{ command: "npm run test:unit", exitCode: 1, stdout: "", stderr: "1 failed", durationMs: 3200
|
|
338
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500 },
|
|
339
|
+
{ command: "npm run test:unit", exitCode: 1, stdout: "", stderr: "1 failed", durationMs: 3200 },
|
|
342
340
|
],
|
|
343
341
|
discoverySource: "package-json",
|
|
344
342
|
});
|
|
@@ -392,7 +390,7 @@ test("verification-evidence: writeVerificationJSON with retryAttempt and maxRetr
|
|
|
392
390
|
const result = makeResult({
|
|
393
391
|
passed: false,
|
|
394
392
|
checks: [
|
|
395
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error", durationMs: 300
|
|
393
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error", durationMs: 300 },
|
|
396
394
|
],
|
|
397
395
|
});
|
|
398
396
|
|
|
@@ -417,7 +415,7 @@ test("verification-evidence: writeVerificationJSON without retry params omits re
|
|
|
417
415
|
const result = makeResult({
|
|
418
416
|
passed: true,
|
|
419
417
|
checks: [
|
|
420
|
-
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100
|
|
418
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
421
419
|
],
|
|
422
420
|
});
|
|
423
421
|
|
|
@@ -443,7 +441,7 @@ test("verification-evidence: writeVerificationJSON includes runtimeErrors when p
|
|
|
443
441
|
const result = makeResult({
|
|
444
442
|
passed: false,
|
|
445
443
|
checks: [
|
|
446
|
-
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100
|
|
444
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
447
445
|
],
|
|
448
446
|
runtimeErrors: [
|
|
449
447
|
{ source: "bg-shell", severity: "crash", message: "Server crashed", blocking: true },
|
|
@@ -475,7 +473,7 @@ test("verification-evidence: writeVerificationJSON omits runtimeErrors when abse
|
|
|
475
473
|
const result = makeResult({
|
|
476
474
|
passed: true,
|
|
477
475
|
checks: [
|
|
478
|
-
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50
|
|
476
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50 },
|
|
479
477
|
],
|
|
480
478
|
});
|
|
481
479
|
|
|
@@ -514,7 +512,7 @@ test("verification-evidence: formatEvidenceTable appends runtime errors section"
|
|
|
514
512
|
const result = makeResult({
|
|
515
513
|
passed: false,
|
|
516
514
|
checks: [
|
|
517
|
-
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
515
|
+
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
518
516
|
],
|
|
519
517
|
runtimeErrors: [
|
|
520
518
|
{ source: "bg-shell", severity: "crash", message: "Server crashed with SIGKILL", blocking: true },
|
|
@@ -539,7 +537,7 @@ test("verification-evidence: formatEvidenceTable omits runtime errors section wh
|
|
|
539
537
|
const result = makeResult({
|
|
540
538
|
passed: true,
|
|
541
539
|
checks: [
|
|
542
|
-
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200
|
|
540
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200 },
|
|
543
541
|
],
|
|
544
542
|
});
|
|
545
543
|
|
|
@@ -554,7 +552,7 @@ test("verification-evidence: formatEvidenceTable truncates runtime error message
|
|
|
554
552
|
const result = makeResult({
|
|
555
553
|
passed: false,
|
|
556
554
|
checks: [
|
|
557
|
-
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
555
|
+
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
558
556
|
],
|
|
559
557
|
runtimeErrors: [
|
|
560
558
|
{ source: "bg-shell", severity: "error", message: longMessage, blocking: false },
|
|
@@ -600,7 +598,7 @@ test("verification-evidence: writeVerificationJSON includes auditWarnings when p
|
|
|
600
598
|
const result = makeResult({
|
|
601
599
|
passed: true,
|
|
602
600
|
checks: [
|
|
603
|
-
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100
|
|
601
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
604
602
|
],
|
|
605
603
|
auditWarnings: SAMPLE_AUDIT_WARNINGS,
|
|
606
604
|
});
|
|
@@ -629,7 +627,7 @@ test("verification-evidence: writeVerificationJSON omits auditWarnings when abse
|
|
|
629
627
|
const result = makeResult({
|
|
630
628
|
passed: true,
|
|
631
629
|
checks: [
|
|
632
|
-
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50
|
|
630
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50 },
|
|
633
631
|
],
|
|
634
632
|
});
|
|
635
633
|
|
|
@@ -668,7 +666,7 @@ test("verification-evidence: formatEvidenceTable appends audit warnings section"
|
|
|
668
666
|
const result = makeResult({
|
|
669
667
|
passed: true,
|
|
670
668
|
checks: [
|
|
671
|
-
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100
|
|
669
|
+
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
672
670
|
],
|
|
673
671
|
auditWarnings: SAMPLE_AUDIT_WARNINGS,
|
|
674
672
|
});
|
|
@@ -691,7 +689,7 @@ test("verification-evidence: formatEvidenceTable omits audit warnings section wh
|
|
|
691
689
|
const result = makeResult({
|
|
692
690
|
passed: true,
|
|
693
691
|
checks: [
|
|
694
|
-
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200
|
|
692
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200 },
|
|
695
693
|
],
|
|
696
694
|
});
|
|
697
695
|
|
|
@@ -707,7 +705,7 @@ test("verification-evidence: integration — VerificationResult with auditWarnin
|
|
|
707
705
|
const result = makeResult({
|
|
708
706
|
passed: true,
|
|
709
707
|
checks: [
|
|
710
|
-
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500
|
|
708
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500 },
|
|
711
709
|
],
|
|
712
710
|
auditWarnings: [
|
|
713
711
|
{
|
|
@@ -581,7 +581,7 @@ test("formatFailureContext: formats a single failure with command, exit code, st
|
|
|
581
581
|
const result: import("../types.ts").VerificationResult = {
|
|
582
582
|
passed: false,
|
|
583
583
|
checks: [
|
|
584
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error: unused var", durationMs: 500
|
|
584
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error: unused var", durationMs: 500 },
|
|
585
585
|
],
|
|
586
586
|
discoverySource: "preference",
|
|
587
587
|
timestamp: Date.now(),
|
|
@@ -598,9 +598,9 @@ test("formatFailureContext: formats multiple failures", () => {
|
|
|
598
598
|
const result: import("../types.ts").VerificationResult = {
|
|
599
599
|
passed: false,
|
|
600
600
|
checks: [
|
|
601
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint error", durationMs: 100
|
|
602
|
-
{ command: "npm run test", exitCode: 2, stdout: "", stderr: "test failure", durationMs: 200
|
|
603
|
-
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 50
|
|
601
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint error", durationMs: 100 },
|
|
602
|
+
{ command: "npm run test", exitCode: 2, stdout: "", stderr: "test failure", durationMs: 200 },
|
|
603
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 50 },
|
|
604
604
|
],
|
|
605
605
|
discoverySource: "preference",
|
|
606
606
|
timestamp: Date.now(),
|
|
@@ -619,7 +619,7 @@ test("formatFailureContext: truncates stderr longer than 2000 chars", () => {
|
|
|
619
619
|
const result: import("../types.ts").VerificationResult = {
|
|
620
620
|
passed: false,
|
|
621
621
|
checks: [
|
|
622
|
-
{ command: "big-err", exitCode: 1, stdout: "", stderr: longStderr, durationMs: 100
|
|
622
|
+
{ command: "big-err", exitCode: 1, stdout: "", stderr: longStderr, durationMs: 100 },
|
|
623
623
|
],
|
|
624
624
|
discoverySource: "preference",
|
|
625
625
|
timestamp: Date.now(),
|
|
@@ -634,8 +634,8 @@ test("formatFailureContext: returns empty string when all checks pass", () => {
|
|
|
634
634
|
const result: import("../types.ts").VerificationResult = {
|
|
635
635
|
passed: true,
|
|
636
636
|
checks: [
|
|
637
|
-
{ command: "npm run lint", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100
|
|
638
|
-
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 200
|
|
637
|
+
{ command: "npm run lint", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
638
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 200 },
|
|
639
639
|
],
|
|
640
640
|
discoverySource: "preference",
|
|
641
641
|
timestamp: Date.now(),
|
|
@@ -663,7 +663,6 @@ test("formatFailureContext: caps total output at 10,000 chars", () => {
|
|
|
663
663
|
stdout: "",
|
|
664
664
|
stderr: "e".repeat(1000), // 1000 chars each, 20 * ~1050 (with formatting) > 10,000
|
|
665
665
|
durationMs: 100,
|
|
666
|
-
blocking: true,
|
|
667
666
|
});
|
|
668
667
|
}
|
|
669
668
|
const result: import("../types.ts").VerificationResult = {
|
|
@@ -1078,131 +1077,3 @@ test("dependency-audit: subdirectory package.json does not trigger audit", () =>
|
|
|
1078
1077
|
assert.equal(npmAuditCalled, false, "subdirectory dependency files should not trigger audit");
|
|
1079
1078
|
assert.deepStrictEqual(result, []);
|
|
1080
1079
|
});
|
|
1081
|
-
|
|
1082
|
-
// ─── Non-Blocking Discovery Tests ────────────────────────────────────────────
|
|
1083
|
-
|
|
1084
|
-
test("non-blocking: package-json discovered commands failing → result.passed is still true", () => {
|
|
1085
|
-
const tmp = makeTempDir("vg-nb-pkg-fail");
|
|
1086
|
-
try {
|
|
1087
|
-
writeFileSync(
|
|
1088
|
-
join(tmp, "package.json"),
|
|
1089
|
-
JSON.stringify({ scripts: { lint: "eslint .", test: "vitest" } }),
|
|
1090
|
-
);
|
|
1091
|
-
// These commands will fail because eslint/vitest don't exist in the temp dir
|
|
1092
|
-
const result = runVerificationGate({
|
|
1093
|
-
basePath: tmp,
|
|
1094
|
-
unitId: "T01",
|
|
1095
|
-
cwd: tmp,
|
|
1096
|
-
// No preference commands — discovery falls through to package.json
|
|
1097
|
-
});
|
|
1098
|
-
assert.equal(result.discoverySource, "package-json");
|
|
1099
|
-
assert.ok(result.checks.length > 0, "should have discovered package.json checks");
|
|
1100
|
-
assert.equal(result.passed, true, "package-json failures should not block the gate");
|
|
1101
|
-
for (const check of result.checks) {
|
|
1102
|
-
assert.equal(check.blocking, false, "package-json checks should be non-blocking");
|
|
1103
|
-
}
|
|
1104
|
-
} finally {
|
|
1105
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
|
-
|
|
1109
|
-
test("non-blocking: preference commands failing → result.passed is false", () => {
|
|
1110
|
-
const tmp = makeTempDir("vg-nb-pref-fail");
|
|
1111
|
-
try {
|
|
1112
|
-
const result = runVerificationGate({
|
|
1113
|
-
basePath: tmp,
|
|
1114
|
-
unitId: "T01",
|
|
1115
|
-
cwd: tmp,
|
|
1116
|
-
preferenceCommands: ["sh -c 'exit 1'"],
|
|
1117
|
-
});
|
|
1118
|
-
assert.equal(result.discoverySource, "preference");
|
|
1119
|
-
assert.equal(result.passed, false, "preference failures should block the gate");
|
|
1120
|
-
assert.equal(result.checks[0].blocking, true, "preference checks should be blocking");
|
|
1121
|
-
} finally {
|
|
1122
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1123
|
-
}
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
test("non-blocking: task-plan commands failing → result.passed is false", () => {
|
|
1127
|
-
const tmp = makeTempDir("vg-nb-tp-fail");
|
|
1128
|
-
try {
|
|
1129
|
-
const result = runVerificationGate({
|
|
1130
|
-
basePath: tmp,
|
|
1131
|
-
unitId: "T01",
|
|
1132
|
-
cwd: tmp,
|
|
1133
|
-
taskPlanVerify: "sh -c 'exit 1'",
|
|
1134
|
-
});
|
|
1135
|
-
assert.equal(result.discoverySource, "task-plan");
|
|
1136
|
-
assert.equal(result.passed, false, "task-plan failures should block the gate");
|
|
1137
|
-
assert.equal(result.checks[0].blocking, true, "task-plan checks should be blocking");
|
|
1138
|
-
} finally {
|
|
1139
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1140
|
-
}
|
|
1141
|
-
});
|
|
1142
|
-
|
|
1143
|
-
test("non-blocking: blocking field is set correctly based on discovery source", () => {
|
|
1144
|
-
const tmp = makeTempDir("vg-nb-field");
|
|
1145
|
-
try {
|
|
1146
|
-
// preference → blocking
|
|
1147
|
-
const prefResult = runVerificationGate({
|
|
1148
|
-
basePath: tmp,
|
|
1149
|
-
unitId: "T01",
|
|
1150
|
-
cwd: tmp,
|
|
1151
|
-
preferenceCommands: ["echo ok"],
|
|
1152
|
-
});
|
|
1153
|
-
assert.equal(prefResult.checks[0].blocking, true);
|
|
1154
|
-
|
|
1155
|
-
// task-plan → blocking
|
|
1156
|
-
const tpResult = runVerificationGate({
|
|
1157
|
-
basePath: tmp,
|
|
1158
|
-
unitId: "T01",
|
|
1159
|
-
cwd: tmp,
|
|
1160
|
-
taskPlanVerify: "echo ok",
|
|
1161
|
-
});
|
|
1162
|
-
assert.equal(tpResult.checks[0].blocking, true);
|
|
1163
|
-
|
|
1164
|
-
// package-json → non-blocking
|
|
1165
|
-
writeFileSync(
|
|
1166
|
-
join(tmp, "package.json"),
|
|
1167
|
-
JSON.stringify({ scripts: { test: "echo ok" } }),
|
|
1168
|
-
);
|
|
1169
|
-
const pkgResult = runVerificationGate({
|
|
1170
|
-
basePath: tmp,
|
|
1171
|
-
unitId: "T01",
|
|
1172
|
-
cwd: tmp,
|
|
1173
|
-
});
|
|
1174
|
-
assert.equal(pkgResult.checks[0].blocking, false);
|
|
1175
|
-
} finally {
|
|
1176
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1177
|
-
}
|
|
1178
|
-
});
|
|
1179
|
-
|
|
1180
|
-
test("non-blocking: formatFailureContext only includes blocking failures", () => {
|
|
1181
|
-
const result: import("../types.ts").VerificationResult = {
|
|
1182
|
-
passed: true,
|
|
1183
|
-
checks: [
|
|
1184
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint warning", durationMs: 100, blocking: false },
|
|
1185
|
-
{ command: "npm run test", exitCode: 1, stdout: "", stderr: "test error", durationMs: 200, blocking: true },
|
|
1186
|
-
{ command: "npm run typecheck", exitCode: 1, stdout: "", stderr: "type error", durationMs: 50, blocking: false },
|
|
1187
|
-
],
|
|
1188
|
-
discoverySource: "preference",
|
|
1189
|
-
timestamp: Date.now(),
|
|
1190
|
-
};
|
|
1191
|
-
const output = formatFailureContext(result);
|
|
1192
|
-
assert.ok(output.includes("`npm run test`"), "should include blocking failure");
|
|
1193
|
-
assert.ok(!output.includes("npm run lint"), "should not include non-blocking failure");
|
|
1194
|
-
assert.ok(!output.includes("npm run typecheck"), "should not include non-blocking failure");
|
|
1195
|
-
});
|
|
1196
|
-
|
|
1197
|
-
test("non-blocking: formatFailureContext returns empty when only non-blocking failures exist", () => {
|
|
1198
|
-
const result: import("../types.ts").VerificationResult = {
|
|
1199
|
-
passed: true,
|
|
1200
|
-
checks: [
|
|
1201
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint warning", durationMs: 100, blocking: false },
|
|
1202
|
-
{ command: "npm run test", exitCode: 1, stdout: "", stderr: "test warning", durationMs: 200, blocking: false },
|
|
1203
|
-
],
|
|
1204
|
-
discoverySource: "package-json",
|
|
1205
|
-
timestamp: Date.now(),
|
|
1206
|
-
};
|
|
1207
|
-
assert.equal(formatFailureContext(result), "", "should return empty when only non-blocking failures");
|
|
1208
|
-
});
|
|
@@ -55,7 +55,6 @@ export interface VerificationCheck {
|
|
|
55
55
|
stdout: string;
|
|
56
56
|
stderr: string;
|
|
57
57
|
durationMs: number;
|
|
58
|
-
blocking: boolean; // true for preference/task-plan sources, false for package-json (advisory only)
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
/** A runtime error captured from bg-shell processes or browser console */
|
|
@@ -20,7 +20,6 @@ export interface EvidenceCheckJSON {
|
|
|
20
20
|
exitCode: number;
|
|
21
21
|
durationMs: number;
|
|
22
22
|
verdict: "pass" | "fail";
|
|
23
|
-
blocking: boolean;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
export interface RuntimeErrorJSON {
|
|
@@ -81,7 +80,6 @@ export function writeVerificationJSON(
|
|
|
81
80
|
exitCode: check.exitCode,
|
|
82
81
|
durationMs: check.durationMs,
|
|
83
82
|
verdict: check.exitCode === 0 ? "pass" : "fail",
|
|
84
|
-
blocking: check.blocking,
|
|
85
83
|
})),
|
|
86
84
|
...(retryAttempt !== undefined ? { retryAttempt } : {}),
|
|
87
85
|
...(maxRetries !== undefined ? { maxRetries } : {}),
|
|
@@ -112,9 +112,7 @@ const MAX_FAILURE_CONTEXT_CHARS = 10_000;
|
|
|
112
112
|
* Returns an empty string when all checks pass or the checks array is empty.
|
|
113
113
|
*/
|
|
114
114
|
export function formatFailureContext(result: VerificationResult): string {
|
|
115
|
-
|
|
116
|
-
// should not be injected into retry prompts to avoid noise pollution.
|
|
117
|
-
const failures = result.checks.filter((c) => c.exitCode !== 0 && c.blocking);
|
|
115
|
+
const failures = result.checks.filter((c) => c.exitCode !== 0);
|
|
118
116
|
if (failures.length === 0) return "";
|
|
119
117
|
|
|
120
118
|
const blocks: string[] = [];
|
|
@@ -258,10 +256,6 @@ export function runVerificationGate(options: RunVerificationGateOptions): Verifi
|
|
|
258
256
|
};
|
|
259
257
|
}
|
|
260
258
|
|
|
261
|
-
// Commands from preference and task-plan sources are blocking;
|
|
262
|
-
// package-json discovered commands are advisory (non-blocking).
|
|
263
|
-
const blocking = source === "preference" || source === "task-plan";
|
|
264
|
-
|
|
265
259
|
const checks: VerificationCheck[] = [];
|
|
266
260
|
|
|
267
261
|
for (const command of commands) {
|
|
@@ -297,16 +291,11 @@ export function runVerificationGate(options: RunVerificationGateOptions): Verifi
|
|
|
297
291
|
stdout: truncate(result.stdout, MAX_OUTPUT_BYTES),
|
|
298
292
|
stderr,
|
|
299
293
|
durationMs,
|
|
300
|
-
blocking,
|
|
301
294
|
});
|
|
302
295
|
}
|
|
303
296
|
|
|
304
|
-
// Gate passes if all blocking checks pass (non-blocking failures are advisory)
|
|
305
|
-
const blockingChecks = checks.filter(c => c.blocking);
|
|
306
|
-
const passed = blockingChecks.length === 0 || blockingChecks.every(c => c.exitCode === 0);
|
|
307
|
-
|
|
308
297
|
return {
|
|
309
|
-
passed,
|
|
298
|
+
passed: checks.every(c => c.exitCode === 0),
|
|
310
299
|
checks,
|
|
311
300
|
discoverySource: source,
|
|
312
301
|
timestamp,
|