@vibecheckai/cli 3.2.2 → 3.2.4
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/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -1,350 +1,350 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Schema Validation Utilities for vibecheck v2
|
|
3
|
-
*
|
|
4
|
-
* Validates artifacts against JSON schemas (Draft 2020-12).
|
|
5
|
-
* Used for CI validation and ensuring deterministic output.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
"use strict";
|
|
9
|
-
|
|
10
|
-
const fs = require("fs");
|
|
11
|
-
const path = require("path");
|
|
12
|
-
const crypto = require("crypto");
|
|
13
|
-
|
|
14
|
-
const SCHEMA_VERSION = "2.0.0";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Load a schema from the schemas directory
|
|
18
|
-
*/
|
|
19
|
-
function loadSchema(schemaName, schemasDir) {
|
|
20
|
-
const schemaPath = path.join(schemasDir, `${schemaName}.schema.json`);
|
|
21
|
-
if (!fs.existsSync(schemaPath)) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
return JSON.parse(fs.readFileSync(schemaPath, "utf8"));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Generate a stable fingerprint hash
|
|
29
|
-
*/
|
|
30
|
-
function generateFingerprint(data) {
|
|
31
|
-
const str = typeof data === "string" ? data : JSON.stringify(data);
|
|
32
|
-
return `sha256:${crypto.createHash("sha256").update(str).digest("hex")}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Generate a short fingerprint (12 chars)
|
|
37
|
-
*/
|
|
38
|
-
function shortFingerprint(data) {
|
|
39
|
-
const full = generateFingerprint(data);
|
|
40
|
-
return full.slice(0, 19); // sha256: + 12 chars
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Create a v2-compliant Evidence object
|
|
45
|
-
*/
|
|
46
|
-
function createEvidence({
|
|
47
|
-
kind,
|
|
48
|
-
reason,
|
|
49
|
-
file = null,
|
|
50
|
-
lines = null,
|
|
51
|
-
snippet = null,
|
|
52
|
-
url = null,
|
|
53
|
-
httpStatus = null,
|
|
54
|
-
artifactPath = null,
|
|
55
|
-
requestId = null,
|
|
56
|
-
traceTimeRangeMs = null,
|
|
57
|
-
}) {
|
|
58
|
-
const id = `E_${crypto.randomBytes(6).toString("hex").toUpperCase()}`;
|
|
59
|
-
|
|
60
|
-
const evidence = {
|
|
61
|
-
id,
|
|
62
|
-
kind,
|
|
63
|
-
reason,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
if (file) evidence.file = file;
|
|
67
|
-
if (lines) evidence.lines = lines;
|
|
68
|
-
if (snippet) evidence.snippetHash = generateFingerprint(snippet);
|
|
69
|
-
if (url) evidence.url = url;
|
|
70
|
-
if (httpStatus) evidence.httpStatus = httpStatus;
|
|
71
|
-
if (artifactPath) evidence.artifactPath = artifactPath;
|
|
72
|
-
if (requestId) evidence.requestId = requestId;
|
|
73
|
-
if (traceTimeRangeMs) evidence.traceTimeRangeMs = traceTimeRangeMs;
|
|
74
|
-
|
|
75
|
-
return evidence;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Create a v2-compliant Finding object
|
|
80
|
-
*/
|
|
81
|
-
function createFindingV2({
|
|
82
|
-
detectorId,
|
|
83
|
-
severity,
|
|
84
|
-
category,
|
|
85
|
-
scope,
|
|
86
|
-
title,
|
|
87
|
-
why,
|
|
88
|
-
confidence = "medium",
|
|
89
|
-
evidence = [],
|
|
90
|
-
fixHints = [],
|
|
91
|
-
related = [],
|
|
92
|
-
repro = null,
|
|
93
|
-
}) {
|
|
94
|
-
// Generate stable fingerprint from detector + primary evidence
|
|
95
|
-
const fingerprintData = [
|
|
96
|
-
detectorId,
|
|
97
|
-
evidence[0]?.file,
|
|
98
|
-
evidence[0]?.lines,
|
|
99
|
-
title,
|
|
100
|
-
].filter(Boolean).join("|");
|
|
101
|
-
|
|
102
|
-
const fingerprint = generateFingerprint(fingerprintData);
|
|
103
|
-
const id = `F_${detectorId}_${fingerprint.slice(7, 19).toUpperCase()}`;
|
|
104
|
-
|
|
105
|
-
const finding = {
|
|
106
|
-
id,
|
|
107
|
-
fingerprint,
|
|
108
|
-
severity,
|
|
109
|
-
category,
|
|
110
|
-
scope,
|
|
111
|
-
title,
|
|
112
|
-
why,
|
|
113
|
-
confidence,
|
|
114
|
-
evidence,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
if (fixHints.length > 0) finding.fixHints = fixHints.slice(0, 8);
|
|
118
|
-
if (related.length > 0) finding.related = related.slice(0, 12);
|
|
119
|
-
if (repro) finding.repro = repro;
|
|
120
|
-
|
|
121
|
-
return finding;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Validate a finding against the v2 schema
|
|
126
|
-
*/
|
|
127
|
-
function validateFindingV2(finding) {
|
|
128
|
-
const errors = [];
|
|
129
|
-
|
|
130
|
-
// Required fields
|
|
131
|
-
if (!finding.id || !/^F_[A-Z0-9_]+$/.test(finding.id)) {
|
|
132
|
-
errors.push(`Invalid id: ${finding.id} (must match F_[A-Z0-9_]+)`);
|
|
133
|
-
}
|
|
134
|
-
if (!finding.fingerprint || !/^(sha1|sha256):[a-f0-9]{40,64}$/.test(finding.fingerprint)) {
|
|
135
|
-
errors.push(`Invalid fingerprint: ${finding.fingerprint}`);
|
|
136
|
-
}
|
|
137
|
-
if (!["BLOCK", "WARN", "INFO"].includes(finding.severity)) {
|
|
138
|
-
errors.push(`Invalid severity: ${finding.severity}`);
|
|
139
|
-
}
|
|
140
|
-
if (!["DeadUI", "Routes", "AuthCoverage", "Env", "Billing", "Truth", "Entitlements", "Quality", "Drift"].includes(finding.category)) {
|
|
141
|
-
errors.push(`Invalid category: ${finding.category}`);
|
|
142
|
-
}
|
|
143
|
-
if (!["client", "server", "runtime", "contracts"].includes(finding.scope)) {
|
|
144
|
-
errors.push(`Invalid scope: ${finding.scope}`);
|
|
145
|
-
}
|
|
146
|
-
if (!finding.title || finding.title.length < 5) {
|
|
147
|
-
errors.push(`Title too short: ${finding.title}`);
|
|
148
|
-
}
|
|
149
|
-
if (!finding.why || finding.why.length < 10) {
|
|
150
|
-
errors.push(`Why too short: ${finding.why}`);
|
|
151
|
-
}
|
|
152
|
-
if (!["low", "medium", "high"].includes(finding.confidence)) {
|
|
153
|
-
errors.push(`Invalid confidence: ${finding.confidence}`);
|
|
154
|
-
}
|
|
155
|
-
if (!Array.isArray(finding.evidence) || finding.evidence.length < 1) {
|
|
156
|
-
errors.push("Evidence must have at least 1 item");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
valid: errors.length === 0,
|
|
161
|
-
errors,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Validate an evidence object against the v2 schema
|
|
167
|
-
*/
|
|
168
|
-
function validateEvidenceV2(evidence) {
|
|
169
|
-
const errors = [];
|
|
170
|
-
|
|
171
|
-
if (!evidence.id || !/^E_[A-Z0-9_]+$/.test(evidence.id)) {
|
|
172
|
-
errors.push(`Invalid id: ${evidence.id}`);
|
|
173
|
-
}
|
|
174
|
-
if (!["file", "url", "screenshot", "trace", "request", "console", "diff", "hash"].includes(evidence.kind)) {
|
|
175
|
-
errors.push(`Invalid kind: ${evidence.kind}`);
|
|
176
|
-
}
|
|
177
|
-
if (!evidence.reason || evidence.reason.length < 3) {
|
|
178
|
-
errors.push(`Reason too short: ${evidence.reason}`);
|
|
179
|
-
}
|
|
180
|
-
if (evidence.lines && !/^[0-9]+-[0-9]+$/.test(evidence.lines)) {
|
|
181
|
-
errors.push(`Invalid lines format: ${evidence.lines}`);
|
|
182
|
-
}
|
|
183
|
-
if (evidence.snippetHash && !/^(sha1|sha256):[a-f0-9]{40,64}$/.test(evidence.snippetHash)) {
|
|
184
|
-
errors.push(`Invalid snippetHash: ${evidence.snippetHash}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
valid: errors.length === 0,
|
|
189
|
-
errors,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Create a v2-compliant ship report
|
|
195
|
-
*/
|
|
196
|
-
function createShipReportV2({
|
|
197
|
-
projectPath,
|
|
198
|
-
findings,
|
|
199
|
-
truthpackPath,
|
|
200
|
-
truthpackHash,
|
|
201
|
-
realityPath = null,
|
|
202
|
-
realityHash = null,
|
|
203
|
-
contractsPaths = [],
|
|
204
|
-
policy = {},
|
|
205
|
-
proofGraph = null,
|
|
206
|
-
}) {
|
|
207
|
-
const blocks = findings.filter(f => f.severity === "BLOCK").length;
|
|
208
|
-
const warns = findings.filter(f => f.severity === "WARN").length;
|
|
209
|
-
const infos = findings.filter(f => f.severity === "INFO").length;
|
|
210
|
-
|
|
211
|
-
const verdict = blocks > 0 ? "BLOCK" : warns > 0 ? "WARN" : "SHIP";
|
|
212
|
-
const exitCode = verdict === "SHIP" ? 0 : verdict === "WARN" ? 1 : 2;
|
|
213
|
-
|
|
214
|
-
// Top findings (blockers first, then warns)
|
|
215
|
-
const top = findings
|
|
216
|
-
.filter(f => f.severity === "BLOCK" || f.severity === "WARN")
|
|
217
|
-
.sort((a, b) => (a.severity === "BLOCK" ? -1 : 1))
|
|
218
|
-
.slice(0, 5)
|
|
219
|
-
.map(f => f.id);
|
|
220
|
-
|
|
221
|
-
const report = {
|
|
222
|
-
schemaVersion: SCHEMA_VERSION,
|
|
223
|
-
generatedAt: new Date().toISOString(),
|
|
224
|
-
vibecheckVersion: getVersion(),
|
|
225
|
-
projectFingerprint: generateProjectFingerprint(projectPath),
|
|
226
|
-
|
|
227
|
-
verdict,
|
|
228
|
-
exitCode,
|
|
229
|
-
|
|
230
|
-
policy: {
|
|
231
|
-
failOnWarn: policy.failOnWarn || false,
|
|
232
|
-
realityFreshnessMinutes: policy.realityFreshnessMinutes || 60,
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
inputs: {
|
|
236
|
-
truthpackPath,
|
|
237
|
-
truthpackHash,
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
summary: {
|
|
241
|
-
counts: { BLOCK: blocks, WARN: warns, INFO: infos },
|
|
242
|
-
top,
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
findings,
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
if (realityPath) {
|
|
249
|
-
report.inputs.realityPath = realityPath;
|
|
250
|
-
report.inputs.realityHash = realityHash;
|
|
251
|
-
}
|
|
252
|
-
if (contractsPaths.length > 0) {
|
|
253
|
-
report.inputs.contractsPaths = contractsPaths;
|
|
254
|
-
}
|
|
255
|
-
if (proofGraph) {
|
|
256
|
-
report.proofGraph = proofGraph;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return report;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Generate project fingerprint
|
|
264
|
-
*/
|
|
265
|
-
function generateProjectFingerprint(projectPath) {
|
|
266
|
-
const keyFiles = ["package.json", "pnpm-lock.yaml", "package-lock.json", "yarn.lock"];
|
|
267
|
-
const hashes = [];
|
|
268
|
-
|
|
269
|
-
for (const file of keyFiles) {
|
|
270
|
-
const filePath = path.join(projectPath, file);
|
|
271
|
-
if (fs.existsSync(filePath)) {
|
|
272
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
273
|
-
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
274
|
-
hashes.push(`${file}:${hash}`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Git commit if available
|
|
279
|
-
try {
|
|
280
|
-
const { execSync } = require("child_process");
|
|
281
|
-
const commit = execSync("git rev-parse HEAD", { cwd: projectPath, encoding: "utf8" }).trim();
|
|
282
|
-
hashes.push(`git:${commit.slice(0, 8)}`);
|
|
283
|
-
} catch {}
|
|
284
|
-
|
|
285
|
-
return generateFingerprint(hashes.join("|"));
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Get vibecheck version
|
|
290
|
-
*/
|
|
291
|
-
function getVersion() {
|
|
292
|
-
try {
|
|
293
|
-
const pkgPath = path.join(__dirname, "..", "..", "..", "package.json");
|
|
294
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
295
|
-
return pkg.version || "0.0.0";
|
|
296
|
-
} catch {
|
|
297
|
-
return "0.0.0";
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Validate ship report against v2 schema
|
|
303
|
-
*/
|
|
304
|
-
function validateShipReportV2(report) {
|
|
305
|
-
const errors = [];
|
|
306
|
-
|
|
307
|
-
if (report.schemaVersion !== SCHEMA_VERSION) {
|
|
308
|
-
errors.push(`Invalid schemaVersion: ${report.schemaVersion}`);
|
|
309
|
-
}
|
|
310
|
-
if (!["SHIP", "WARN", "BLOCK"].includes(report.verdict)) {
|
|
311
|
-
errors.push(`Invalid verdict: ${report.verdict}`);
|
|
312
|
-
}
|
|
313
|
-
if (![0, 1, 2].includes(report.exitCode)) {
|
|
314
|
-
errors.push(`Invalid exitCode: ${report.exitCode}`);
|
|
315
|
-
}
|
|
316
|
-
if (!report.inputs?.truthpackPath) {
|
|
317
|
-
errors.push("Missing inputs.truthpackPath");
|
|
318
|
-
}
|
|
319
|
-
if (!report.summary?.counts) {
|
|
320
|
-
errors.push("Missing summary.counts");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Validate each finding
|
|
324
|
-
for (const finding of report.findings || []) {
|
|
325
|
-
const result = validateFindingV2(finding);
|
|
326
|
-
if (!result.valid) {
|
|
327
|
-
errors.push(`Finding ${finding.id}: ${result.errors.join(", ")}`);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return {
|
|
332
|
-
valid: errors.length === 0,
|
|
333
|
-
errors,
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
module.exports = {
|
|
338
|
-
SCHEMA_VERSION,
|
|
339
|
-
loadSchema,
|
|
340
|
-
generateFingerprint,
|
|
341
|
-
shortFingerprint,
|
|
342
|
-
createEvidence,
|
|
343
|
-
createFindingV2,
|
|
344
|
-
validateFindingV2,
|
|
345
|
-
validateEvidenceV2,
|
|
346
|
-
createShipReportV2,
|
|
347
|
-
generateProjectFingerprint,
|
|
348
|
-
validateShipReportV2,
|
|
349
|
-
getVersion,
|
|
350
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Schema Validation Utilities for vibecheck v2
|
|
3
|
+
*
|
|
4
|
+
* Validates artifacts against JSON schemas (Draft 2020-12).
|
|
5
|
+
* Used for CI validation and ensuring deterministic output.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const crypto = require("crypto");
|
|
13
|
+
|
|
14
|
+
const SCHEMA_VERSION = "2.0.0";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load a schema from the schemas directory
|
|
18
|
+
*/
|
|
19
|
+
function loadSchema(schemaName, schemasDir) {
|
|
20
|
+
const schemaPath = path.join(schemasDir, `${schemaName}.schema.json`);
|
|
21
|
+
if (!fs.existsSync(schemaPath)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return JSON.parse(fs.readFileSync(schemaPath, "utf8"));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate a stable fingerprint hash
|
|
29
|
+
*/
|
|
30
|
+
function generateFingerprint(data) {
|
|
31
|
+
const str = typeof data === "string" ? data : JSON.stringify(data);
|
|
32
|
+
return `sha256:${crypto.createHash("sha256").update(str).digest("hex")}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate a short fingerprint (12 chars)
|
|
37
|
+
*/
|
|
38
|
+
function shortFingerprint(data) {
|
|
39
|
+
const full = generateFingerprint(data);
|
|
40
|
+
return full.slice(0, 19); // sha256: + 12 chars
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a v2-compliant Evidence object
|
|
45
|
+
*/
|
|
46
|
+
function createEvidence({
|
|
47
|
+
kind,
|
|
48
|
+
reason,
|
|
49
|
+
file = null,
|
|
50
|
+
lines = null,
|
|
51
|
+
snippet = null,
|
|
52
|
+
url = null,
|
|
53
|
+
httpStatus = null,
|
|
54
|
+
artifactPath = null,
|
|
55
|
+
requestId = null,
|
|
56
|
+
traceTimeRangeMs = null,
|
|
57
|
+
}) {
|
|
58
|
+
const id = `E_${crypto.randomBytes(6).toString("hex").toUpperCase()}`;
|
|
59
|
+
|
|
60
|
+
const evidence = {
|
|
61
|
+
id,
|
|
62
|
+
kind,
|
|
63
|
+
reason,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (file) evidence.file = file;
|
|
67
|
+
if (lines) evidence.lines = lines;
|
|
68
|
+
if (snippet) evidence.snippetHash = generateFingerprint(snippet);
|
|
69
|
+
if (url) evidence.url = url;
|
|
70
|
+
if (httpStatus) evidence.httpStatus = httpStatus;
|
|
71
|
+
if (artifactPath) evidence.artifactPath = artifactPath;
|
|
72
|
+
if (requestId) evidence.requestId = requestId;
|
|
73
|
+
if (traceTimeRangeMs) evidence.traceTimeRangeMs = traceTimeRangeMs;
|
|
74
|
+
|
|
75
|
+
return evidence;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a v2-compliant Finding object
|
|
80
|
+
*/
|
|
81
|
+
function createFindingV2({
|
|
82
|
+
detectorId,
|
|
83
|
+
severity,
|
|
84
|
+
category,
|
|
85
|
+
scope,
|
|
86
|
+
title,
|
|
87
|
+
why,
|
|
88
|
+
confidence = "medium",
|
|
89
|
+
evidence = [],
|
|
90
|
+
fixHints = [],
|
|
91
|
+
related = [],
|
|
92
|
+
repro = null,
|
|
93
|
+
}) {
|
|
94
|
+
// Generate stable fingerprint from detector + primary evidence
|
|
95
|
+
const fingerprintData = [
|
|
96
|
+
detectorId,
|
|
97
|
+
evidence[0]?.file,
|
|
98
|
+
evidence[0]?.lines,
|
|
99
|
+
title,
|
|
100
|
+
].filter(Boolean).join("|");
|
|
101
|
+
|
|
102
|
+
const fingerprint = generateFingerprint(fingerprintData);
|
|
103
|
+
const id = `F_${detectorId}_${fingerprint.slice(7, 19).toUpperCase()}`;
|
|
104
|
+
|
|
105
|
+
const finding = {
|
|
106
|
+
id,
|
|
107
|
+
fingerprint,
|
|
108
|
+
severity,
|
|
109
|
+
category,
|
|
110
|
+
scope,
|
|
111
|
+
title,
|
|
112
|
+
why,
|
|
113
|
+
confidence,
|
|
114
|
+
evidence,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (fixHints.length > 0) finding.fixHints = fixHints.slice(0, 8);
|
|
118
|
+
if (related.length > 0) finding.related = related.slice(0, 12);
|
|
119
|
+
if (repro) finding.repro = repro;
|
|
120
|
+
|
|
121
|
+
return finding;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Validate a finding against the v2 schema
|
|
126
|
+
*/
|
|
127
|
+
function validateFindingV2(finding) {
|
|
128
|
+
const errors = [];
|
|
129
|
+
|
|
130
|
+
// Required fields
|
|
131
|
+
if (!finding.id || !/^F_[A-Z0-9_]+$/.test(finding.id)) {
|
|
132
|
+
errors.push(`Invalid id: ${finding.id} (must match F_[A-Z0-9_]+)`);
|
|
133
|
+
}
|
|
134
|
+
if (!finding.fingerprint || !/^(sha1|sha256):[a-f0-9]{40,64}$/.test(finding.fingerprint)) {
|
|
135
|
+
errors.push(`Invalid fingerprint: ${finding.fingerprint}`);
|
|
136
|
+
}
|
|
137
|
+
if (!["BLOCK", "WARN", "INFO"].includes(finding.severity)) {
|
|
138
|
+
errors.push(`Invalid severity: ${finding.severity}`);
|
|
139
|
+
}
|
|
140
|
+
if (!["DeadUI", "Routes", "AuthCoverage", "Env", "Billing", "Truth", "Entitlements", "Quality", "Drift"].includes(finding.category)) {
|
|
141
|
+
errors.push(`Invalid category: ${finding.category}`);
|
|
142
|
+
}
|
|
143
|
+
if (!["client", "server", "runtime", "contracts"].includes(finding.scope)) {
|
|
144
|
+
errors.push(`Invalid scope: ${finding.scope}`);
|
|
145
|
+
}
|
|
146
|
+
if (!finding.title || finding.title.length < 5) {
|
|
147
|
+
errors.push(`Title too short: ${finding.title}`);
|
|
148
|
+
}
|
|
149
|
+
if (!finding.why || finding.why.length < 10) {
|
|
150
|
+
errors.push(`Why too short: ${finding.why}`);
|
|
151
|
+
}
|
|
152
|
+
if (!["low", "medium", "high"].includes(finding.confidence)) {
|
|
153
|
+
errors.push(`Invalid confidence: ${finding.confidence}`);
|
|
154
|
+
}
|
|
155
|
+
if (!Array.isArray(finding.evidence) || finding.evidence.length < 1) {
|
|
156
|
+
errors.push("Evidence must have at least 1 item");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
valid: errors.length === 0,
|
|
161
|
+
errors,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Validate an evidence object against the v2 schema
|
|
167
|
+
*/
|
|
168
|
+
function validateEvidenceV2(evidence) {
|
|
169
|
+
const errors = [];
|
|
170
|
+
|
|
171
|
+
if (!evidence.id || !/^E_[A-Z0-9_]+$/.test(evidence.id)) {
|
|
172
|
+
errors.push(`Invalid id: ${evidence.id}`);
|
|
173
|
+
}
|
|
174
|
+
if (!["file", "url", "screenshot", "trace", "request", "console", "diff", "hash"].includes(evidence.kind)) {
|
|
175
|
+
errors.push(`Invalid kind: ${evidence.kind}`);
|
|
176
|
+
}
|
|
177
|
+
if (!evidence.reason || evidence.reason.length < 3) {
|
|
178
|
+
errors.push(`Reason too short: ${evidence.reason}`);
|
|
179
|
+
}
|
|
180
|
+
if (evidence.lines && !/^[0-9]+-[0-9]+$/.test(evidence.lines)) {
|
|
181
|
+
errors.push(`Invalid lines format: ${evidence.lines}`);
|
|
182
|
+
}
|
|
183
|
+
if (evidence.snippetHash && !/^(sha1|sha256):[a-f0-9]{40,64}$/.test(evidence.snippetHash)) {
|
|
184
|
+
errors.push(`Invalid snippetHash: ${evidence.snippetHash}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
valid: errors.length === 0,
|
|
189
|
+
errors,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Create a v2-compliant ship report
|
|
195
|
+
*/
|
|
196
|
+
function createShipReportV2({
|
|
197
|
+
projectPath,
|
|
198
|
+
findings,
|
|
199
|
+
truthpackPath,
|
|
200
|
+
truthpackHash,
|
|
201
|
+
realityPath = null,
|
|
202
|
+
realityHash = null,
|
|
203
|
+
contractsPaths = [],
|
|
204
|
+
policy = {},
|
|
205
|
+
proofGraph = null,
|
|
206
|
+
}) {
|
|
207
|
+
const blocks = findings.filter(f => f.severity === "BLOCK").length;
|
|
208
|
+
const warns = findings.filter(f => f.severity === "WARN").length;
|
|
209
|
+
const infos = findings.filter(f => f.severity === "INFO").length;
|
|
210
|
+
|
|
211
|
+
const verdict = blocks > 0 ? "BLOCK" : warns > 0 ? "WARN" : "SHIP";
|
|
212
|
+
const exitCode = verdict === "SHIP" ? 0 : verdict === "WARN" ? 1 : 2;
|
|
213
|
+
|
|
214
|
+
// Top findings (blockers first, then warns)
|
|
215
|
+
const top = findings
|
|
216
|
+
.filter(f => f.severity === "BLOCK" || f.severity === "WARN")
|
|
217
|
+
.sort((a, b) => (a.severity === "BLOCK" ? -1 : 1))
|
|
218
|
+
.slice(0, 5)
|
|
219
|
+
.map(f => f.id);
|
|
220
|
+
|
|
221
|
+
const report = {
|
|
222
|
+
schemaVersion: SCHEMA_VERSION,
|
|
223
|
+
generatedAt: new Date().toISOString(),
|
|
224
|
+
vibecheckVersion: getVersion(),
|
|
225
|
+
projectFingerprint: generateProjectFingerprint(projectPath),
|
|
226
|
+
|
|
227
|
+
verdict,
|
|
228
|
+
exitCode,
|
|
229
|
+
|
|
230
|
+
policy: {
|
|
231
|
+
failOnWarn: policy.failOnWarn || false,
|
|
232
|
+
realityFreshnessMinutes: policy.realityFreshnessMinutes || 60,
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
inputs: {
|
|
236
|
+
truthpackPath,
|
|
237
|
+
truthpackHash,
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
summary: {
|
|
241
|
+
counts: { BLOCK: blocks, WARN: warns, INFO: infos },
|
|
242
|
+
top,
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
findings,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
if (realityPath) {
|
|
249
|
+
report.inputs.realityPath = realityPath;
|
|
250
|
+
report.inputs.realityHash = realityHash;
|
|
251
|
+
}
|
|
252
|
+
if (contractsPaths.length > 0) {
|
|
253
|
+
report.inputs.contractsPaths = contractsPaths;
|
|
254
|
+
}
|
|
255
|
+
if (proofGraph) {
|
|
256
|
+
report.proofGraph = proofGraph;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return report;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Generate project fingerprint
|
|
264
|
+
*/
|
|
265
|
+
function generateProjectFingerprint(projectPath) {
|
|
266
|
+
const keyFiles = ["package.json", "pnpm-lock.yaml", "package-lock.json", "yarn.lock"];
|
|
267
|
+
const hashes = [];
|
|
268
|
+
|
|
269
|
+
for (const file of keyFiles) {
|
|
270
|
+
const filePath = path.join(projectPath, file);
|
|
271
|
+
if (fs.existsSync(filePath)) {
|
|
272
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
273
|
+
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
274
|
+
hashes.push(`${file}:${hash}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Git commit if available
|
|
279
|
+
try {
|
|
280
|
+
const { execSync } = require("child_process");
|
|
281
|
+
const commit = execSync("git rev-parse HEAD", { cwd: projectPath, encoding: "utf8" }).trim();
|
|
282
|
+
hashes.push(`git:${commit.slice(0, 8)}`);
|
|
283
|
+
} catch {}
|
|
284
|
+
|
|
285
|
+
return generateFingerprint(hashes.join("|"));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get vibecheck version
|
|
290
|
+
*/
|
|
291
|
+
function getVersion() {
|
|
292
|
+
try {
|
|
293
|
+
const pkgPath = path.join(__dirname, "..", "..", "..", "package.json");
|
|
294
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
295
|
+
return pkg.version || "0.0.0";
|
|
296
|
+
} catch {
|
|
297
|
+
return "0.0.0";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Validate ship report against v2 schema
|
|
303
|
+
*/
|
|
304
|
+
function validateShipReportV2(report) {
|
|
305
|
+
const errors = [];
|
|
306
|
+
|
|
307
|
+
if (report.schemaVersion !== SCHEMA_VERSION) {
|
|
308
|
+
errors.push(`Invalid schemaVersion: ${report.schemaVersion}`);
|
|
309
|
+
}
|
|
310
|
+
if (!["SHIP", "WARN", "BLOCK"].includes(report.verdict)) {
|
|
311
|
+
errors.push(`Invalid verdict: ${report.verdict}`);
|
|
312
|
+
}
|
|
313
|
+
if (![0, 1, 2].includes(report.exitCode)) {
|
|
314
|
+
errors.push(`Invalid exitCode: ${report.exitCode}`);
|
|
315
|
+
}
|
|
316
|
+
if (!report.inputs?.truthpackPath) {
|
|
317
|
+
errors.push("Missing inputs.truthpackPath");
|
|
318
|
+
}
|
|
319
|
+
if (!report.summary?.counts) {
|
|
320
|
+
errors.push("Missing summary.counts");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Validate each finding
|
|
324
|
+
for (const finding of report.findings || []) {
|
|
325
|
+
const result = validateFindingV2(finding);
|
|
326
|
+
if (!result.valid) {
|
|
327
|
+
errors.push(`Finding ${finding.id}: ${result.errors.join(", ")}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
valid: errors.length === 0,
|
|
333
|
+
errors,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
module.exports = {
|
|
338
|
+
SCHEMA_VERSION,
|
|
339
|
+
loadSchema,
|
|
340
|
+
generateFingerprint,
|
|
341
|
+
shortFingerprint,
|
|
342
|
+
createEvidence,
|
|
343
|
+
createFindingV2,
|
|
344
|
+
validateFindingV2,
|
|
345
|
+
validateEvidenceV2,
|
|
346
|
+
createShipReportV2,
|
|
347
|
+
generateProjectFingerprint,
|
|
348
|
+
validateShipReportV2,
|
|
349
|
+
getVersion,
|
|
350
|
+
};
|