gsd-pi 2.26.0 → 2.27.0
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 +43 -6
- package/dist/cli.js +4 -2
- package/dist/headless.d.ts +3 -0
- package/dist/headless.js +136 -8
- package/dist/help-text.js +3 -0
- package/dist/loader.js +33 -4
- package/dist/resources/extensions/bg-shell/index.ts +19 -2
- package/dist/resources/extensions/bg-shell/process-manager.ts +45 -0
- package/dist/resources/extensions/bg-shell/types.ts +21 -1
- package/dist/resources/extensions/gsd/auto/session.ts +224 -0
- package/dist/resources/extensions/gsd/auto-budget.ts +32 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +63 -10
- package/dist/resources/extensions/gsd/auto-direct-dispatch.ts +229 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +23 -10
- package/dist/resources/extensions/gsd/auto-model-selection.ts +179 -0
- package/dist/resources/extensions/gsd/auto-observability.ts +74 -0
- package/dist/resources/extensions/gsd/auto-prompts.ts +0 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.ts +262 -0
- package/dist/resources/extensions/gsd/auto-tool-tracking.ts +54 -0
- package/dist/resources/extensions/gsd/auto-unit-closeout.ts +46 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +207 -0
- package/dist/resources/extensions/gsd/auto.ts +977 -1551
- package/dist/resources/extensions/gsd/commands.ts +3 -3
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +47 -72
- package/dist/resources/extensions/gsd/doctor-proactive.ts +9 -4
- package/dist/resources/extensions/gsd/export-html.ts +1001 -0
- package/dist/resources/extensions/gsd/export.ts +49 -1
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gitignore.ts +4 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +24 -5
- package/dist/resources/extensions/gsd/index.ts +54 -1
- package/dist/resources/extensions/gsd/native-git-bridge.ts +30 -2
- package/dist/resources/extensions/gsd/observability-validator.ts +21 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +231 -20
- package/dist/resources/extensions/gsd/preferences.ts +62 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -3
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/reports.ts +510 -0
- package/dist/resources/extensions/gsd/roadmap-slices.ts +1 -1
- package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +178 -0
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +54 -0
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +59 -0
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/multi-session.md +185 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/task-summary.md +9 -0
- package/dist/resources/extensions/gsd/tests/auto-dashboard.test.ts +13 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +81 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +5 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +10 -1
- package/dist/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +132 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +14 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/native-has-changes-cache.test.ts +61 -0
- package/dist/resources/extensions/gsd/tests/network-error-fallback.test.ts +51 -1
- package/dist/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +331 -0
- package/dist/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +298 -0
- package/dist/resources/extensions/gsd/tests/parallel-merge.test.ts +465 -0
- package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +39 -10
- package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +71 -0
- package/dist/resources/extensions/gsd/tests/replan-slice.test.ts +42 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +9 -9
- package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +743 -0
- package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +965 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +44 -10
- package/dist/resources/extensions/gsd/tests/worktree.test.ts +3 -1
- package/dist/resources/extensions/gsd/types.ts +38 -0
- package/dist/resources/extensions/gsd/verification-evidence.ts +183 -0
- package/dist/resources/extensions/gsd/verification-gate.ts +567 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +25 -3
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +31 -21
- package/dist/resources/extensions/gsd/visualizer-views.ts +15 -66
- package/dist/resources/extensions/search-the-web/tool-search.ts +26 -0
- package/dist/resources/extensions/shared/format-utils.ts +85 -0
- package/dist/resources/extensions/shared/tests/format-utils.test.ts +153 -0
- package/dist/resources/extensions/subagent/index.ts +46 -1
- package/dist/resources/extensions/subagent/isolation.ts +9 -6
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +7 -4
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-completions.ts +7 -4
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +7 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js +9 -2
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +8 -0
- package/packages/pi-coding-agent/src/core/lsp/config.ts +9 -2
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +1 -1
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/src/components/editor.ts +3 -1
- package/scripts/link-workspace-packages.cjs +22 -6
- package/src/resources/extensions/bg-shell/index.ts +19 -2
- package/src/resources/extensions/bg-shell/process-manager.ts +45 -0
- package/src/resources/extensions/bg-shell/types.ts +21 -1
- package/src/resources/extensions/gsd/auto/session.ts +224 -0
- package/src/resources/extensions/gsd/auto-budget.ts +32 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +63 -10
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +229 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +23 -10
- package/src/resources/extensions/gsd/auto-model-selection.ts +179 -0
- package/src/resources/extensions/gsd/auto-observability.ts +74 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +0 -1
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +262 -0
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +54 -0
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +46 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +207 -0
- package/src/resources/extensions/gsd/auto.ts +977 -1551
- package/src/resources/extensions/gsd/commands.ts +3 -3
- package/src/resources/extensions/gsd/dashboard-overlay.ts +47 -72
- package/src/resources/extensions/gsd/doctor-proactive.ts +9 -4
- package/src/resources/extensions/gsd/export-html.ts +1001 -0
- package/src/resources/extensions/gsd/export.ts +49 -1
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gitignore.ts +4 -1
- package/src/resources/extensions/gsd/guided-flow.ts +24 -5
- package/src/resources/extensions/gsd/index.ts +54 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +30 -2
- package/src/resources/extensions/gsd/observability-validator.ts +21 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +231 -20
- package/src/resources/extensions/gsd/preferences.ts +62 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +4 -3
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/reports.ts +510 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +1 -1
- package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +178 -0
- package/src/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +54 -0
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +59 -0
- package/src/resources/extensions/gsd/skills/gsd-headless/references/multi-session.md +185 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/task-summary.md +9 -0
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/native-has-changes-cache.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/network-error-fallback.test.ts +51 -1
- package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +298 -0
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +465 -0
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +39 -10
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +9 -9
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +743 -0
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +965 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +44 -10
- package/src/resources/extensions/gsd/tests/worktree.test.ts +3 -1
- package/src/resources/extensions/gsd/types.ts +38 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +183 -0
- package/src/resources/extensions/gsd/verification-gate.ts +567 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +25 -3
- package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -21
- package/src/resources/extensions/gsd/visualizer-views.ts +15 -66
- package/src/resources/extensions/search-the-web/tool-search.ts +26 -0
- package/src/resources/extensions/shared/format-utils.ts +85 -0
- package/src/resources/extensions/shared/tests/format-utils.test.ts +153 -0
- package/src/resources/extensions/subagent/index.ts +46 -1
- package/src/resources/extensions/subagent/isolation.ts +9 -6
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the verification evidence module — JSON persistence and markdown table formatting.
|
|
3
|
+
*
|
|
4
|
+
* Tests cover:
|
|
5
|
+
* 1. writeVerificationJSON writes correct JSON shape (schemaVersion, taskId, timestamp, passed, discoverySource, checks)
|
|
6
|
+
* 2. writeVerificationJSON creates directory if it doesn't exist
|
|
7
|
+
* 3. writeVerificationJSON maps exitCode to verdict correctly (0 = pass, non-zero = fail)
|
|
8
|
+
* 4. writeVerificationJSON excludes stdout/stderr from output
|
|
9
|
+
* 5. writeVerificationJSON handles empty checks array
|
|
10
|
+
* 6. writeVerificationJSON accepts optional unitId
|
|
11
|
+
* 7. formatEvidenceTable returns markdown table with correct columns for checks
|
|
12
|
+
* 8. formatEvidenceTable returns "no checks" message for empty checks
|
|
13
|
+
* 9. formatEvidenceTable formats duration as seconds with 1 decimal
|
|
14
|
+
* 10. formatEvidenceTable uses ✅/❌ emoji for pass/fail verdict
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import test from "node:test";
|
|
18
|
+
import assert from "node:assert/strict";
|
|
19
|
+
import { mkdirSync, readFileSync, rmSync, existsSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import {
|
|
23
|
+
writeVerificationJSON,
|
|
24
|
+
formatEvidenceTable,
|
|
25
|
+
} from "../verification-evidence.ts";
|
|
26
|
+
import type { VerificationResult } from "../types.ts";
|
|
27
|
+
|
|
28
|
+
function makeTempDir(prefix: string): string {
|
|
29
|
+
const dir = join(
|
|
30
|
+
tmpdir(),
|
|
31
|
+
`${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
32
|
+
);
|
|
33
|
+
mkdirSync(dir, { recursive: true });
|
|
34
|
+
return dir;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function makeResult(overrides?: Partial<VerificationResult>): VerificationResult {
|
|
38
|
+
return {
|
|
39
|
+
passed: true,
|
|
40
|
+
checks: [],
|
|
41
|
+
discoverySource: "package-json",
|
|
42
|
+
timestamp: 1710000000000,
|
|
43
|
+
...overrides,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── writeVerificationJSON Tests ─────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
test("verification-evidence: writeVerificationJSON writes correct JSON shape", () => {
|
|
50
|
+
const tmp = makeTempDir("ve-shape");
|
|
51
|
+
try {
|
|
52
|
+
const result = makeResult({
|
|
53
|
+
passed: true,
|
|
54
|
+
checks: [
|
|
55
|
+
{
|
|
56
|
+
command: "npm run typecheck",
|
|
57
|
+
exitCode: 0,
|
|
58
|
+
stdout: "all good",
|
|
59
|
+
stderr: "",
|
|
60
|
+
durationMs: 2340,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
writeVerificationJSON(result, tmp, "T03");
|
|
66
|
+
|
|
67
|
+
const filePath = join(tmp, "T03-VERIFY.json");
|
|
68
|
+
assert.ok(existsSync(filePath), "JSON file should exist");
|
|
69
|
+
|
|
70
|
+
const json = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
71
|
+
assert.equal(json.schemaVersion, 1);
|
|
72
|
+
assert.equal(json.taskId, "T03");
|
|
73
|
+
assert.equal(json.unitId, "T03"); // defaults to taskId when unitId not provided
|
|
74
|
+
assert.equal(json.timestamp, 1710000000000);
|
|
75
|
+
assert.equal(json.passed, true);
|
|
76
|
+
assert.equal(json.discoverySource, "package-json");
|
|
77
|
+
assert.equal(json.checks.length, 1);
|
|
78
|
+
assert.equal(json.checks[0].command, "npm run typecheck");
|
|
79
|
+
assert.equal(json.checks[0].exitCode, 0);
|
|
80
|
+
assert.equal(json.checks[0].durationMs, 2340);
|
|
81
|
+
assert.equal(json.checks[0].verdict, "pass");
|
|
82
|
+
} finally {
|
|
83
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("verification-evidence: writeVerificationJSON creates directory if it doesn't exist", () => {
|
|
88
|
+
const tmp = makeTempDir("ve-mkdir");
|
|
89
|
+
const nested = join(tmp, "deep", "nested", "tasks");
|
|
90
|
+
try {
|
|
91
|
+
assert.ok(!existsSync(nested), "directory should not exist yet");
|
|
92
|
+
|
|
93
|
+
writeVerificationJSON(makeResult(), nested, "T01");
|
|
94
|
+
|
|
95
|
+
assert.ok(existsSync(nested), "directory should be created");
|
|
96
|
+
assert.ok(existsSync(join(nested, "T01-VERIFY.json")), "JSON file should exist");
|
|
97
|
+
} finally {
|
|
98
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("verification-evidence: writeVerificationJSON maps exitCode to verdict correctly", () => {
|
|
103
|
+
const tmp = makeTempDir("ve-verdict");
|
|
104
|
+
try {
|
|
105
|
+
const result = makeResult({
|
|
106
|
+
passed: false,
|
|
107
|
+
checks: [
|
|
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 },
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
writeVerificationJSON(result, tmp, "T02");
|
|
115
|
+
|
|
116
|
+
const json = JSON.parse(readFileSync(join(tmp, "T02-VERIFY.json"), "utf-8"));
|
|
117
|
+
assert.equal(json.checks[0].verdict, "pass");
|
|
118
|
+
assert.equal(json.checks[1].verdict, "fail");
|
|
119
|
+
assert.equal(json.checks[2].verdict, "fail");
|
|
120
|
+
} finally {
|
|
121
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("verification-evidence: writeVerificationJSON excludes stdout/stderr from output", () => {
|
|
126
|
+
const tmp = makeTempDir("ve-no-stdio");
|
|
127
|
+
try {
|
|
128
|
+
const result = makeResult({
|
|
129
|
+
checks: [
|
|
130
|
+
{
|
|
131
|
+
command: "echo hello",
|
|
132
|
+
exitCode: 0,
|
|
133
|
+
stdout: "hello\n",
|
|
134
|
+
stderr: "some warning",
|
|
135
|
+
durationMs: 50,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
writeVerificationJSON(result, tmp, "T01");
|
|
141
|
+
|
|
142
|
+
const raw = readFileSync(join(tmp, "T01-VERIFY.json"), "utf-8");
|
|
143
|
+
assert.ok(!raw.includes('"stdout"'), "JSON should not contain stdout key");
|
|
144
|
+
assert.ok(!raw.includes('"stderr"'), "JSON should not contain stderr key");
|
|
145
|
+
assert.ok(!raw.includes("hello\\n"), "JSON should not contain stdout value");
|
|
146
|
+
assert.ok(!raw.includes("some warning"), "JSON should not contain stderr value");
|
|
147
|
+
} finally {
|
|
148
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("verification-evidence: writeVerificationJSON handles empty checks array", () => {
|
|
153
|
+
const tmp = makeTempDir("ve-empty");
|
|
154
|
+
try {
|
|
155
|
+
writeVerificationJSON(makeResult({ checks: [] }), tmp, "T01");
|
|
156
|
+
|
|
157
|
+
const json = JSON.parse(readFileSync(join(tmp, "T01-VERIFY.json"), "utf-8"));
|
|
158
|
+
assert.equal(json.schemaVersion, 1);
|
|
159
|
+
assert.equal(json.passed, true);
|
|
160
|
+
assert.deepStrictEqual(json.checks, []);
|
|
161
|
+
} finally {
|
|
162
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("verification-evidence: writeVerificationJSON uses optional unitId when provided", () => {
|
|
167
|
+
const tmp = makeTempDir("ve-unitid");
|
|
168
|
+
try {
|
|
169
|
+
writeVerificationJSON(makeResult(), tmp, "T03", "M001/S01/T03");
|
|
170
|
+
|
|
171
|
+
const json = JSON.parse(readFileSync(join(tmp, "T03-VERIFY.json"), "utf-8"));
|
|
172
|
+
assert.equal(json.taskId, "T03");
|
|
173
|
+
assert.equal(json.unitId, "M001/S01/T03");
|
|
174
|
+
} finally {
|
|
175
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ─── formatEvidenceTable Tests ───────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
test("verification-evidence: formatEvidenceTable returns markdown table with correct columns", () => {
|
|
182
|
+
const result = makeResult({
|
|
183
|
+
checks: [
|
|
184
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "", stderr: "", durationMs: 2340 },
|
|
185
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "err", durationMs: 1100 },
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const table = formatEvidenceTable(result);
|
|
190
|
+
const lines = table.split("\n");
|
|
191
|
+
|
|
192
|
+
// Header row
|
|
193
|
+
assert.ok(lines[0].includes("# |"), "header should have # column");
|
|
194
|
+
assert.ok(lines[0].includes("Command"), "header should have Command column");
|
|
195
|
+
assert.ok(lines[0].includes("Exit Code"), "header should have Exit Code column");
|
|
196
|
+
assert.ok(lines[0].includes("Verdict"), "header should have Verdict column");
|
|
197
|
+
assert.ok(lines[0].includes("Duration"), "header should have Duration column");
|
|
198
|
+
|
|
199
|
+
// Separator row
|
|
200
|
+
assert.ok(lines[1].includes("---|"), "should have separator row");
|
|
201
|
+
|
|
202
|
+
// Data rows
|
|
203
|
+
assert.equal(lines.length, 4, "header + separator + 2 data rows");
|
|
204
|
+
assert.ok(lines[2].includes("npm run typecheck"), "first row command");
|
|
205
|
+
assert.ok(lines[3].includes("npm run lint"), "second row command");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("verification-evidence: formatEvidenceTable returns no-checks message for empty checks", () => {
|
|
209
|
+
const result = makeResult({ checks: [] });
|
|
210
|
+
const output = formatEvidenceTable(result);
|
|
211
|
+
assert.equal(output, "_No verification checks discovered._");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("verification-evidence: formatEvidenceTable formats duration as seconds with 1 decimal", () => {
|
|
215
|
+
const result = makeResult({
|
|
216
|
+
checks: [
|
|
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 },
|
|
220
|
+
],
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const table = formatEvidenceTable(result);
|
|
224
|
+
assert.ok(table.includes("0.1s"), "150ms → 0.1s");
|
|
225
|
+
assert.ok(table.includes("2.3s"), "2340ms → 2.3s");
|
|
226
|
+
assert.ok(table.includes("0.0s"), "0ms → 0.0s");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("verification-evidence: formatEvidenceTable uses ✅/❌ emoji for pass/fail verdict", () => {
|
|
230
|
+
const result = makeResult({
|
|
231
|
+
passed: false,
|
|
232
|
+
checks: [
|
|
233
|
+
{ command: "pass-cmd", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
234
|
+
{ command: "fail-cmd", exitCode: 1, stdout: "", stderr: "", durationMs: 200 },
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const table = formatEvidenceTable(result);
|
|
239
|
+
assert.ok(table.includes("✅ pass"), "passing check should have ✅ pass");
|
|
240
|
+
assert.ok(table.includes("❌ fail"), "failing check should have ❌ fail");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// ─── Validator Rule Tests (T03) ──────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
import { validateTaskSummaryContent } from "../observability-validator.ts";
|
|
246
|
+
|
|
247
|
+
const MINIMAL_SUMMARY_WITH_EVIDENCE = `---
|
|
248
|
+
observability_surfaces:
|
|
249
|
+
- gate-output
|
|
250
|
+
---
|
|
251
|
+
# T03 Summary
|
|
252
|
+
|
|
253
|
+
## Diagnostics
|
|
254
|
+
Run \`npm test\` to verify.
|
|
255
|
+
|
|
256
|
+
## Verification Evidence
|
|
257
|
+
| # | Command | Exit Code | Verdict | Duration |
|
|
258
|
+
|---|---------|-----------|---------|----------|
|
|
259
|
+
| 1 | npm run typecheck | 0 | ✅ pass | 2.3s |
|
|
260
|
+
`;
|
|
261
|
+
|
|
262
|
+
const MINIMAL_SUMMARY_NO_EVIDENCE = `---
|
|
263
|
+
observability_surfaces:
|
|
264
|
+
- gate-output
|
|
265
|
+
---
|
|
266
|
+
# T03 Summary
|
|
267
|
+
|
|
268
|
+
## Diagnostics
|
|
269
|
+
Run \`npm test\` to verify.
|
|
270
|
+
`;
|
|
271
|
+
|
|
272
|
+
const MINIMAL_SUMMARY_PLACEHOLDER_EVIDENCE = `---
|
|
273
|
+
observability_surfaces:
|
|
274
|
+
- gate-output
|
|
275
|
+
---
|
|
276
|
+
# T03 Summary
|
|
277
|
+
|
|
278
|
+
## Diagnostics
|
|
279
|
+
Run \`npm test\` to verify.
|
|
280
|
+
|
|
281
|
+
## Verification Evidence
|
|
282
|
+
{{evidence_table}}
|
|
283
|
+
`;
|
|
284
|
+
|
|
285
|
+
const MINIMAL_SUMMARY_NO_CHECKS_EVIDENCE = `---
|
|
286
|
+
observability_surfaces:
|
|
287
|
+
- gate-output
|
|
288
|
+
---
|
|
289
|
+
# T03 Summary
|
|
290
|
+
|
|
291
|
+
## Diagnostics
|
|
292
|
+
Run \`npm test\` to verify.
|
|
293
|
+
|
|
294
|
+
## Verification Evidence
|
|
295
|
+
_No verification checks discovered._
|
|
296
|
+
`;
|
|
297
|
+
|
|
298
|
+
test("verification-evidence: validator accepts summary with real evidence table", () => {
|
|
299
|
+
const issues = validateTaskSummaryContent("T03-SUMMARY.md", MINIMAL_SUMMARY_WITH_EVIDENCE);
|
|
300
|
+
const evidenceIssues = issues.filter(
|
|
301
|
+
(i) => i.ruleId === "evidence_block_missing" || i.ruleId === "evidence_block_placeholder",
|
|
302
|
+
);
|
|
303
|
+
assert.equal(evidenceIssues.length, 0, "no evidence warnings for real table");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("verification-evidence: validator warns when evidence section is missing", () => {
|
|
307
|
+
const issues = validateTaskSummaryContent("T03-SUMMARY.md", MINIMAL_SUMMARY_NO_EVIDENCE);
|
|
308
|
+
const match = issues.find((i) => i.ruleId === "evidence_block_missing");
|
|
309
|
+
assert.ok(match, "should produce evidence_block_missing warning");
|
|
310
|
+
assert.equal(match!.severity, "warning");
|
|
311
|
+
assert.equal(match!.scope, "task-summary");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("verification-evidence: validator warns when evidence section has only placeholder text", () => {
|
|
315
|
+
const issues = validateTaskSummaryContent("T03-SUMMARY.md", MINIMAL_SUMMARY_PLACEHOLDER_EVIDENCE);
|
|
316
|
+
const match = issues.find((i) => i.ruleId === "evidence_block_placeholder");
|
|
317
|
+
assert.ok(match, "should produce evidence_block_placeholder warning");
|
|
318
|
+
assert.equal(match!.severity, "warning");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("verification-evidence: validator accepts 'no checks discovered' as valid content", () => {
|
|
322
|
+
const issues = validateTaskSummaryContent("T03-SUMMARY.md", MINIMAL_SUMMARY_NO_CHECKS_EVIDENCE);
|
|
323
|
+
const evidenceIssues = issues.filter(
|
|
324
|
+
(i) => i.ruleId === "evidence_block_missing" || i.ruleId === "evidence_block_placeholder",
|
|
325
|
+
);
|
|
326
|
+
assert.equal(evidenceIssues.length, 0, "no evidence warnings for 'no checks discovered'");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// ─── Integration Test: Full Chain (T03) ──────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
test("verification-evidence: integration — VerificationResult → JSON → table → validator accepts", () => {
|
|
332
|
+
const tmp = makeTempDir("ve-integration");
|
|
333
|
+
try {
|
|
334
|
+
// 1. Create a VerificationResult with 2 checks (1 pass, 1 fail)
|
|
335
|
+
const result = makeResult({
|
|
336
|
+
passed: false,
|
|
337
|
+
checks: [
|
|
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 },
|
|
340
|
+
],
|
|
341
|
+
discoverySource: "package-json",
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// 2. Write JSON to temp dir and read it back
|
|
345
|
+
writeVerificationJSON(result, tmp, "T03");
|
|
346
|
+
const jsonPath = join(tmp, "T03-VERIFY.json");
|
|
347
|
+
assert.ok(existsSync(jsonPath), "JSON file should exist");
|
|
348
|
+
|
|
349
|
+
const json = JSON.parse(readFileSync(jsonPath, "utf-8"));
|
|
350
|
+
assert.equal(json.schemaVersion, 1, "schemaVersion should be 1");
|
|
351
|
+
assert.equal(json.passed, false, "passed should be false");
|
|
352
|
+
assert.equal(json.checks.length, 2, "should have 2 checks");
|
|
353
|
+
assert.equal(json.checks[0].verdict, "pass", "first check should pass");
|
|
354
|
+
assert.equal(json.checks[1].verdict, "fail", "second check should fail");
|
|
355
|
+
|
|
356
|
+
// 3. Generate evidence table and embed in a mock summary
|
|
357
|
+
const table = formatEvidenceTable(result);
|
|
358
|
+
assert.ok(table.includes("npm run typecheck"), "table should contain first command");
|
|
359
|
+
assert.ok(table.includes("npm run test:unit"), "table should contain second command");
|
|
360
|
+
|
|
361
|
+
const fullSummary = `---
|
|
362
|
+
observability_surfaces:
|
|
363
|
+
- gate-output
|
|
364
|
+
---
|
|
365
|
+
# T03 Summary
|
|
366
|
+
|
|
367
|
+
## Diagnostics
|
|
368
|
+
Run \`npm test\` to verify.
|
|
369
|
+
|
|
370
|
+
## Verification Evidence
|
|
371
|
+
${table}
|
|
372
|
+
`;
|
|
373
|
+
|
|
374
|
+
// 4. Validate — no evidence warnings
|
|
375
|
+
const issues = validateTaskSummaryContent("T03-SUMMARY.md", fullSummary);
|
|
376
|
+
const evidenceIssues = issues.filter(
|
|
377
|
+
(i) => i.ruleId === "evidence_block_missing" || i.ruleId === "evidence_block_placeholder",
|
|
378
|
+
);
|
|
379
|
+
assert.equal(evidenceIssues.length, 0, "validator should accept real evidence from formatEvidenceTable");
|
|
380
|
+
} finally {
|
|
381
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// ─── Retry Evidence Field Tests (S03/T01) ─────────────────────────────────────
|
|
386
|
+
|
|
387
|
+
test("verification-evidence: writeVerificationJSON with retryAttempt and maxRetries includes them in output", () => {
|
|
388
|
+
const tmp = makeTempDir("ve-retry-fields");
|
|
389
|
+
try {
|
|
390
|
+
const result = makeResult({
|
|
391
|
+
passed: false,
|
|
392
|
+
checks: [
|
|
393
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error", durationMs: 300 },
|
|
394
|
+
],
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
writeVerificationJSON(result, tmp, "T01", "M001/S03/T01", 1, 2);
|
|
398
|
+
|
|
399
|
+
const json = JSON.parse(readFileSync(join(tmp, "T01-VERIFY.json"), "utf-8"));
|
|
400
|
+
assert.equal(json.retryAttempt, 1, "retryAttempt should be 1");
|
|
401
|
+
assert.equal(json.maxRetries, 2, "maxRetries should be 2");
|
|
402
|
+
// Other fields should still be correct
|
|
403
|
+
assert.equal(json.schemaVersion, 1);
|
|
404
|
+
assert.equal(json.taskId, "T01");
|
|
405
|
+
assert.equal(json.unitId, "M001/S03/T01");
|
|
406
|
+
assert.equal(json.passed, false);
|
|
407
|
+
} finally {
|
|
408
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("verification-evidence: writeVerificationJSON without retry params omits retryAttempt/maxRetries keys", () => {
|
|
413
|
+
const tmp = makeTempDir("ve-no-retry");
|
|
414
|
+
try {
|
|
415
|
+
const result = makeResult({
|
|
416
|
+
passed: true,
|
|
417
|
+
checks: [
|
|
418
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
419
|
+
],
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
writeVerificationJSON(result, tmp, "T02");
|
|
423
|
+
|
|
424
|
+
const raw = readFileSync(join(tmp, "T02-VERIFY.json"), "utf-8");
|
|
425
|
+
const json = JSON.parse(raw);
|
|
426
|
+
assert.ok(!("retryAttempt" in json), "retryAttempt key should not be present");
|
|
427
|
+
assert.ok(!("maxRetries" in json), "maxRetries key should not be present");
|
|
428
|
+
// Confirm the JSON string does not contain these keys at all
|
|
429
|
+
assert.ok(!raw.includes('"retryAttempt"'), "raw JSON should not contain retryAttempt");
|
|
430
|
+
assert.ok(!raw.includes('"maxRetries"'), "raw JSON should not contain maxRetries");
|
|
431
|
+
} finally {
|
|
432
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// ─── Runtime Error Evidence Tests (S04/T02) ──────────────────────────────────
|
|
437
|
+
|
|
438
|
+
test("verification-evidence: writeVerificationJSON includes runtimeErrors when present", () => {
|
|
439
|
+
const tmp = makeTempDir("ve-rt-present");
|
|
440
|
+
try {
|
|
441
|
+
const result = makeResult({
|
|
442
|
+
passed: false,
|
|
443
|
+
checks: [
|
|
444
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
445
|
+
],
|
|
446
|
+
runtimeErrors: [
|
|
447
|
+
{ source: "bg-shell", severity: "crash", message: "Server crashed", blocking: true },
|
|
448
|
+
{ source: "browser", severity: "error", message: "Uncaught TypeError", blocking: false },
|
|
449
|
+
],
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
writeVerificationJSON(result, tmp, "T01");
|
|
453
|
+
|
|
454
|
+
const json = JSON.parse(readFileSync(join(tmp, "T01-VERIFY.json"), "utf-8"));
|
|
455
|
+
assert.ok(Array.isArray(json.runtimeErrors), "runtimeErrors should be an array");
|
|
456
|
+
assert.equal(json.runtimeErrors.length, 2, "should have 2 runtime errors");
|
|
457
|
+
assert.equal(json.runtimeErrors[0].source, "bg-shell");
|
|
458
|
+
assert.equal(json.runtimeErrors[0].severity, "crash");
|
|
459
|
+
assert.equal(json.runtimeErrors[0].message, "Server crashed");
|
|
460
|
+
assert.equal(json.runtimeErrors[0].blocking, true);
|
|
461
|
+
assert.equal(json.runtimeErrors[1].source, "browser");
|
|
462
|
+
assert.equal(json.runtimeErrors[1].severity, "error");
|
|
463
|
+
assert.equal(json.runtimeErrors[1].message, "Uncaught TypeError");
|
|
464
|
+
assert.equal(json.runtimeErrors[1].blocking, false);
|
|
465
|
+
} finally {
|
|
466
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("verification-evidence: writeVerificationJSON omits runtimeErrors when absent", () => {
|
|
471
|
+
const tmp = makeTempDir("ve-rt-absent");
|
|
472
|
+
try {
|
|
473
|
+
const result = makeResult({
|
|
474
|
+
passed: true,
|
|
475
|
+
checks: [
|
|
476
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50 },
|
|
477
|
+
],
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
writeVerificationJSON(result, tmp, "T01");
|
|
481
|
+
|
|
482
|
+
const raw = readFileSync(join(tmp, "T01-VERIFY.json"), "utf-8");
|
|
483
|
+
assert.ok(!raw.includes('"runtimeErrors"'), "raw JSON should not contain runtimeErrors key");
|
|
484
|
+
const json = JSON.parse(raw);
|
|
485
|
+
assert.ok(!("runtimeErrors" in json), "runtimeErrors key should not be present in parsed JSON");
|
|
486
|
+
} finally {
|
|
487
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("verification-evidence: writeVerificationJSON omits runtimeErrors when empty array", () => {
|
|
492
|
+
const tmp = makeTempDir("ve-rt-empty");
|
|
493
|
+
try {
|
|
494
|
+
const result = makeResult({
|
|
495
|
+
passed: true,
|
|
496
|
+
checks: [],
|
|
497
|
+
runtimeErrors: [],
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
writeVerificationJSON(result, tmp, "T01");
|
|
501
|
+
|
|
502
|
+
const raw = readFileSync(join(tmp, "T01-VERIFY.json"), "utf-8");
|
|
503
|
+
assert.ok(!raw.includes('"runtimeErrors"'), "raw JSON should not contain runtimeErrors key when empty array");
|
|
504
|
+
const json = JSON.parse(raw);
|
|
505
|
+
assert.ok(!("runtimeErrors" in json), "runtimeErrors key should not be present for empty array");
|
|
506
|
+
} finally {
|
|
507
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test("verification-evidence: formatEvidenceTable appends runtime errors section", () => {
|
|
512
|
+
const result = makeResult({
|
|
513
|
+
passed: false,
|
|
514
|
+
checks: [
|
|
515
|
+
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
516
|
+
],
|
|
517
|
+
runtimeErrors: [
|
|
518
|
+
{ source: "bg-shell", severity: "crash", message: "Server crashed with SIGKILL", blocking: true },
|
|
519
|
+
{ source: "browser", severity: "warning", message: "Deprecated API usage", blocking: false },
|
|
520
|
+
],
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const table = formatEvidenceTable(result);
|
|
524
|
+
|
|
525
|
+
// Should contain runtime errors section
|
|
526
|
+
assert.ok(table.includes("**Runtime Errors**"), "should have Runtime Errors heading");
|
|
527
|
+
assert.ok(table.includes("| # | Source | Severity | Blocking | Message |"), "should have runtime errors column headers");
|
|
528
|
+
assert.ok(table.includes("bg-shell"), "should contain bg-shell source");
|
|
529
|
+
assert.ok(table.includes("crash"), "should contain crash severity");
|
|
530
|
+
assert.ok(table.includes("🚫 yes"), "blocking error should show 🚫 yes");
|
|
531
|
+
assert.ok(table.includes("ℹ️ no"), "non-blocking error should show ℹ️ no");
|
|
532
|
+
assert.ok(table.includes("Server crashed with SIGKILL"), "should contain error message");
|
|
533
|
+
assert.ok(table.includes("Deprecated API usage"), "should contain warning message");
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test("verification-evidence: formatEvidenceTable omits runtime errors section when none", () => {
|
|
537
|
+
const result = makeResult({
|
|
538
|
+
passed: true,
|
|
539
|
+
checks: [
|
|
540
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200 },
|
|
541
|
+
],
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const table = formatEvidenceTable(result);
|
|
545
|
+
|
|
546
|
+
assert.ok(!table.includes("Runtime Errors"), "should not contain Runtime Errors heading");
|
|
547
|
+
assert.ok(table.includes("npm run lint"), "should still contain the check table");
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("verification-evidence: formatEvidenceTable truncates runtime error message to 100 chars", () => {
|
|
551
|
+
const longMessage = "A".repeat(150);
|
|
552
|
+
const result = makeResult({
|
|
553
|
+
passed: false,
|
|
554
|
+
checks: [
|
|
555
|
+
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
556
|
+
],
|
|
557
|
+
runtimeErrors: [
|
|
558
|
+
{ source: "bg-shell", severity: "error", message: longMessage, blocking: false },
|
|
559
|
+
],
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
const table = formatEvidenceTable(result);
|
|
563
|
+
|
|
564
|
+
// The table should contain the truncated message (100 chars), not the full 150
|
|
565
|
+
assert.ok(table.includes("A".repeat(100)), "should contain 100 A's");
|
|
566
|
+
assert.ok(!table.includes("A".repeat(101)), "should not contain 101 A's (truncated)");
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// ─── Audit Warning Evidence Tests (S05/T02) ──────────────────────────────────
|
|
570
|
+
|
|
571
|
+
const SAMPLE_AUDIT_WARNINGS = [
|
|
572
|
+
{
|
|
573
|
+
name: "lodash",
|
|
574
|
+
severity: "critical" as const,
|
|
575
|
+
title: "Prototype Pollution",
|
|
576
|
+
url: "https://github.com/advisories/GHSA-1234",
|
|
577
|
+
fixAvailable: true,
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: "express",
|
|
581
|
+
severity: "high" as const,
|
|
582
|
+
title: "Open Redirect",
|
|
583
|
+
url: "https://github.com/advisories/GHSA-5678",
|
|
584
|
+
fixAvailable: false,
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: "minimist",
|
|
588
|
+
severity: "moderate" as const,
|
|
589
|
+
title: "Prototype Pollution",
|
|
590
|
+
url: "https://github.com/advisories/GHSA-9012",
|
|
591
|
+
fixAvailable: true,
|
|
592
|
+
},
|
|
593
|
+
];
|
|
594
|
+
|
|
595
|
+
test("verification-evidence: writeVerificationJSON includes auditWarnings when present", () => {
|
|
596
|
+
const tmp = makeTempDir("ve-audit-present");
|
|
597
|
+
try {
|
|
598
|
+
const result = makeResult({
|
|
599
|
+
passed: true,
|
|
600
|
+
checks: [
|
|
601
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
602
|
+
],
|
|
603
|
+
auditWarnings: SAMPLE_AUDIT_WARNINGS,
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
writeVerificationJSON(result, tmp, "T01");
|
|
607
|
+
|
|
608
|
+
const json = JSON.parse(readFileSync(join(tmp, "T01-VERIFY.json"), "utf-8"));
|
|
609
|
+
assert.ok(Array.isArray(json.auditWarnings), "auditWarnings should be an array");
|
|
610
|
+
assert.equal(json.auditWarnings.length, 3, "should have 3 audit warnings");
|
|
611
|
+
assert.equal(json.auditWarnings[0].name, "lodash");
|
|
612
|
+
assert.equal(json.auditWarnings[0].severity, "critical");
|
|
613
|
+
assert.equal(json.auditWarnings[0].title, "Prototype Pollution");
|
|
614
|
+
assert.equal(json.auditWarnings[0].url, "https://github.com/advisories/GHSA-1234");
|
|
615
|
+
assert.equal(json.auditWarnings[0].fixAvailable, true);
|
|
616
|
+
assert.equal(json.auditWarnings[1].name, "express");
|
|
617
|
+
assert.equal(json.auditWarnings[1].severity, "high");
|
|
618
|
+
assert.equal(json.auditWarnings[1].fixAvailable, false);
|
|
619
|
+
} finally {
|
|
620
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
test("verification-evidence: writeVerificationJSON omits auditWarnings when absent", () => {
|
|
625
|
+
const tmp = makeTempDir("ve-audit-absent");
|
|
626
|
+
try {
|
|
627
|
+
const result = makeResult({
|
|
628
|
+
passed: true,
|
|
629
|
+
checks: [
|
|
630
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 50 },
|
|
631
|
+
],
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
writeVerificationJSON(result, tmp, "T01");
|
|
635
|
+
|
|
636
|
+
const raw = readFileSync(join(tmp, "T01-VERIFY.json"), "utf-8");
|
|
637
|
+
assert.ok(!raw.includes('"auditWarnings"'), "raw JSON should not contain auditWarnings key");
|
|
638
|
+
const json = JSON.parse(raw);
|
|
639
|
+
assert.ok(!("auditWarnings" in json), "auditWarnings key should not be present in parsed JSON");
|
|
640
|
+
} finally {
|
|
641
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test("verification-evidence: writeVerificationJSON omits auditWarnings when empty array", () => {
|
|
646
|
+
const tmp = makeTempDir("ve-audit-empty");
|
|
647
|
+
try {
|
|
648
|
+
const result = makeResult({
|
|
649
|
+
passed: true,
|
|
650
|
+
checks: [],
|
|
651
|
+
auditWarnings: [],
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
writeVerificationJSON(result, tmp, "T01");
|
|
655
|
+
|
|
656
|
+
const raw = readFileSync(join(tmp, "T01-VERIFY.json"), "utf-8");
|
|
657
|
+
assert.ok(!raw.includes('"auditWarnings"'), "raw JSON should not contain auditWarnings key when empty array");
|
|
658
|
+
const json = JSON.parse(raw);
|
|
659
|
+
assert.ok(!("auditWarnings" in json), "auditWarnings key should not be present for empty array");
|
|
660
|
+
} finally {
|
|
661
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test("verification-evidence: formatEvidenceTable appends audit warnings section", () => {
|
|
666
|
+
const result = makeResult({
|
|
667
|
+
passed: true,
|
|
668
|
+
checks: [
|
|
669
|
+
{ command: "npm run test", exitCode: 0, stdout: "", stderr: "", durationMs: 100 },
|
|
670
|
+
],
|
|
671
|
+
auditWarnings: SAMPLE_AUDIT_WARNINGS,
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
const table = formatEvidenceTable(result);
|
|
675
|
+
|
|
676
|
+
assert.ok(table.includes("**Audit Warnings**"), "should have Audit Warnings heading");
|
|
677
|
+
assert.ok(table.includes("| # | Package | Severity | Title | Fix Available |"), "should have audit warnings column headers");
|
|
678
|
+
assert.ok(table.includes("lodash"), "should contain lodash package");
|
|
679
|
+
assert.ok(table.includes("🔴 critical"), "should show critical emoji");
|
|
680
|
+
assert.ok(table.includes("🟠 high"), "should show high emoji");
|
|
681
|
+
assert.ok(table.includes("🟡 moderate"), "should show moderate emoji");
|
|
682
|
+
assert.ok(table.includes("Prototype Pollution"), "should contain vulnerability title");
|
|
683
|
+
assert.ok(table.includes("Open Redirect"), "should contain vulnerability title");
|
|
684
|
+
assert.ok(table.includes("✅ yes"), "fixAvailable true should show ✅ yes");
|
|
685
|
+
assert.ok(table.includes("❌ no"), "fixAvailable false should show ❌ no");
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test("verification-evidence: formatEvidenceTable omits audit warnings section when none", () => {
|
|
689
|
+
const result = makeResult({
|
|
690
|
+
passed: true,
|
|
691
|
+
checks: [
|
|
692
|
+
{ command: "npm run lint", exitCode: 0, stdout: "", stderr: "", durationMs: 200 },
|
|
693
|
+
],
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
const table = formatEvidenceTable(result);
|
|
697
|
+
|
|
698
|
+
assert.ok(!table.includes("Audit Warnings"), "should not contain Audit Warnings heading");
|
|
699
|
+
assert.ok(table.includes("npm run lint"), "should still contain the check table");
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
test("verification-evidence: integration — VerificationResult with auditWarnings → JSON → table", () => {
|
|
703
|
+
const tmp = makeTempDir("ve-audit-integration");
|
|
704
|
+
try {
|
|
705
|
+
const result = makeResult({
|
|
706
|
+
passed: true,
|
|
707
|
+
checks: [
|
|
708
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 1500 },
|
|
709
|
+
],
|
|
710
|
+
auditWarnings: [
|
|
711
|
+
{
|
|
712
|
+
name: "got",
|
|
713
|
+
severity: "moderate" as const,
|
|
714
|
+
title: "Redirect bypass",
|
|
715
|
+
url: "https://github.com/advisories/GHSA-abcd",
|
|
716
|
+
fixAvailable: true,
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// 1. Write JSON and verify
|
|
722
|
+
writeVerificationJSON(result, tmp, "T05");
|
|
723
|
+
const json = JSON.parse(readFileSync(join(tmp, "T05-VERIFY.json"), "utf-8"));
|
|
724
|
+
assert.equal(json.auditWarnings.length, 1, "JSON should have 1 audit warning");
|
|
725
|
+
assert.equal(json.auditWarnings[0].name, "got");
|
|
726
|
+
assert.equal(json.auditWarnings[0].severity, "moderate");
|
|
727
|
+
assert.equal(json.auditWarnings[0].fixAvailable, true);
|
|
728
|
+
// passed should still be true — audit warnings are non-blocking
|
|
729
|
+
assert.equal(json.passed, true, "passed should remain true despite audit warnings");
|
|
730
|
+
|
|
731
|
+
// 2. Format table and verify
|
|
732
|
+
const table = formatEvidenceTable(result);
|
|
733
|
+
assert.ok(table.includes("**Audit Warnings**"), "table should have Audit Warnings section");
|
|
734
|
+
assert.ok(table.includes("got"), "table should contain package name");
|
|
735
|
+
assert.ok(table.includes("🟡 moderate"), "table should show moderate severity with emoji");
|
|
736
|
+
assert.ok(table.includes("Redirect bypass"), "table should contain vulnerability title");
|
|
737
|
+
assert.ok(table.includes("✅ yes"), "table should show fix available");
|
|
738
|
+
// Check table still has the main verification checks
|
|
739
|
+
assert.ok(table.includes("npm run typecheck"), "table should still have main check");
|
|
740
|
+
} finally {
|
|
741
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
742
|
+
}
|
|
743
|
+
});
|