@vibecheckai/cli 3.2.4 → 3.2.6
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/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/api-client.js +269 -0
- 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/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/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/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.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/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/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/runAgent.d.ts +5 -0
- package/bin/runners/runFirewall.d.ts +5 -0
- package/bin/runners/runFirewallHook.d.ts +5 -0
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runScan.js +82 -0
- package/bin/runners/runTruth.d.ts +5 -0
- package/bin/vibecheck.js +45 -20
- package/mcp-server/index.js +85 -0
- package/mcp-server/lib/api-client.js +269 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/tier-auth.js +173 -113
- package/mcp-server/tools/index.js +72 -72
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
- package/package.json +1 -1
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
// bin/runners/lib/share-pack.js
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const { redactObject } = require("./redact");
|
|
5
|
-
|
|
6
|
-
function ensureDir(p) {
|
|
7
|
-
fs.mkdirSync(p, { recursive: true });
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function listMissionDirs(repoRoot) {
|
|
11
|
-
const base = path.join(repoRoot, ".vibecheck", "missions");
|
|
12
|
-
if (!fs.existsSync(base)) return [];
|
|
13
|
-
const dirs = fs.readdirSync(base)
|
|
14
|
-
.map(name => ({ name, abs: path.join(base, name) }))
|
|
15
|
-
.filter(x => fs.existsSync(x.abs) && fs.statSync(x.abs).isDirectory())
|
|
16
|
-
.sort((a,b) => (a.name < b.name ? 1 : -1));
|
|
17
|
-
return dirs;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function findLatestMissionDir(repoRoot) {
|
|
21
|
-
const dirs = listMissionDirs(repoRoot);
|
|
22
|
-
return dirs.length ? dirs[0].abs : null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function parseStepFiles(dirAbs) {
|
|
26
|
-
const files = fs.readdirSync(dirAbs).sort();
|
|
27
|
-
const steps = {};
|
|
28
|
-
|
|
29
|
-
for (const f of files) {
|
|
30
|
-
const m = f.match(/^step_(\d+)_([^_]+)_([a-z_]+)\.(json|txt)$/);
|
|
31
|
-
if (!m) continue;
|
|
32
|
-
|
|
33
|
-
const stepNo = Number(m[1]);
|
|
34
|
-
const missionId = m[2];
|
|
35
|
-
const kind = m[3];
|
|
36
|
-
const abs = path.join(dirAbs, f);
|
|
37
|
-
|
|
38
|
-
steps[stepNo] = steps[stepNo] || { stepNo, missionId, files: {} };
|
|
39
|
-
steps[stepNo].files[kind] = abs;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return Object.values(steps).sort((a,b) => a.stepNo - b.stepNo);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function countSeverities(findings) {
|
|
46
|
-
const out = { BLOCK: 0, WARN: 0, INFO: 0 };
|
|
47
|
-
for (const f of findings || []) {
|
|
48
|
-
if (f.severity === "BLOCK") out.BLOCK++;
|
|
49
|
-
else if (f.severity === "WARN") out.WARN++;
|
|
50
|
-
else out.INFO++;
|
|
51
|
-
}
|
|
52
|
-
return out;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function extractProgressTimeline(steps) {
|
|
56
|
-
const timeline = [];
|
|
57
|
-
for (const s of steps) {
|
|
58
|
-
const beforePath = s.files["before_ship"];
|
|
59
|
-
const afterPath = s.files["after_ship"];
|
|
60
|
-
if (!beforePath || !afterPath) continue;
|
|
61
|
-
|
|
62
|
-
const before = JSON.parse(fs.readFileSync(beforePath, "utf8"));
|
|
63
|
-
const after = JSON.parse(fs.readFileSync(afterPath, "utf8"));
|
|
64
|
-
|
|
65
|
-
timeline.push({
|
|
66
|
-
step: s.stepNo,
|
|
67
|
-
missionId: s.missionId,
|
|
68
|
-
before: {
|
|
69
|
-
verdict: before?.meta?.verdict,
|
|
70
|
-
counts: countSeverities(before?.findings)
|
|
71
|
-
},
|
|
72
|
-
after: {
|
|
73
|
-
verdict: after?.meta?.verdict,
|
|
74
|
-
counts: countSeverities(after?.findings)
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
return timeline;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function safeFindingDigest(findings, limit = 20) {
|
|
82
|
-
return (findings || []).slice(0, limit).map(f => ({
|
|
83
|
-
id: f.id,
|
|
84
|
-
severity: f.severity,
|
|
85
|
-
category: f.category,
|
|
86
|
-
title: f.title,
|
|
87
|
-
confidence: f.confidence,
|
|
88
|
-
evidence: (f.evidence || []).slice(0, 2).map(ev => ({
|
|
89
|
-
file: ev.file,
|
|
90
|
-
lines: ev.lines,
|
|
91
|
-
snippetHash: ev.snippetHash,
|
|
92
|
-
reason: ev.reason
|
|
93
|
-
})),
|
|
94
|
-
fixHints: (f.fixHints || []).slice(0, 4)
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function buildMarkdown({ header, missions, timeline, startFindings, endFindings }) {
|
|
99
|
-
const startCounts = countSeverities(startFindings);
|
|
100
|
-
const endCounts = countSeverities(endFindings);
|
|
101
|
-
|
|
102
|
-
const lines = [];
|
|
103
|
-
lines.push(`# vibecheck Mission Pack`);
|
|
104
|
-
lines.push(``);
|
|
105
|
-
lines.push(`**Generated:** ${header.generatedAt}`);
|
|
106
|
-
lines.push(`**Repo:** ${header.repoName || "(local)"}`);
|
|
107
|
-
lines.push(`**Start Verdict:** ${header.startVerdict} — **End Verdict:** ${header.endVerdict}`);
|
|
108
|
-
lines.push(``);
|
|
109
|
-
lines.push(`## Summary`);
|
|
110
|
-
lines.push(`- Start: BLOCK=${startCounts.BLOCK}, WARN=${startCounts.WARN}`);
|
|
111
|
-
lines.push(`- End: BLOCK=${endCounts.BLOCK}, WARN=${endCounts.WARN}`);
|
|
112
|
-
lines.push(`- Missions planned: ${missions.length}`);
|
|
113
|
-
lines.push(``);
|
|
114
|
-
|
|
115
|
-
lines.push(`## Verdict Timeline`);
|
|
116
|
-
lines.push(`| Step | Mission | Before | After |`);
|
|
117
|
-
lines.push(`|---:|---|---|---|`);
|
|
118
|
-
for (const t of timeline) {
|
|
119
|
-
const b = `${t.before.verdict} (B${t.before.counts.BLOCK}/W${t.before.counts.WARN})`;
|
|
120
|
-
const a = `${t.after.verdict} (B${t.after.counts.BLOCK}/W${t.after.counts.WARN})`;
|
|
121
|
-
lines.push(`| ${t.step} | ${t.missionId} | ${b} | ${a} |`);
|
|
122
|
-
}
|
|
123
|
-
lines.push(``);
|
|
124
|
-
|
|
125
|
-
lines.push(`## Key Findings (Start)`);
|
|
126
|
-
for (const f of safeFindingDigest(startFindings, 12)) {
|
|
127
|
-
lines.push(`- **${f.severity}** ${f.id} — ${f.title}`);
|
|
128
|
-
if (f.evidence?.[0]) {
|
|
129
|
-
const ev = f.evidence[0];
|
|
130
|
-
lines.push(` - Evidence: \`${ev.file}:${ev.lines}\` (${ev.reason})`);
|
|
131
|
-
lines.push(` - SnipHash: \`${ev.snippetHash}\``);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
lines.push(``);
|
|
135
|
-
|
|
136
|
-
lines.push(`## Remaining Findings (End)`);
|
|
137
|
-
const endDigest = safeFindingDigest(endFindings, 12);
|
|
138
|
-
if (!endDigest.length) {
|
|
139
|
-
lines.push(`✅ None.`);
|
|
140
|
-
} else {
|
|
141
|
-
for (const f of endDigest) {
|
|
142
|
-
lines.push(`- **${f.severity}** ${f.id} — ${f.title}`);
|
|
143
|
-
if (f.evidence?.[0]) {
|
|
144
|
-
const ev = f.evidence[0];
|
|
145
|
-
lines.push(` - Evidence: \`${ev.file}:${ev.lines}\` (${ev.reason})`);
|
|
146
|
-
lines.push(` - SnipHash: \`${ev.snippetHash}\``);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
lines.push(``);
|
|
151
|
-
|
|
152
|
-
lines.push(`## Notes`);
|
|
153
|
-
lines.push(`This bundle is sanitized: no secrets, no raw code snippets, only hashes + file/line pointers for verification.`);
|
|
154
|
-
lines.push(``);
|
|
155
|
-
|
|
156
|
-
return lines.join("\n");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function buildPrComment(md) {
|
|
160
|
-
const max = 9000;
|
|
161
|
-
if (md.length <= max) return md;
|
|
162
|
-
return md.slice(0, max) + "\n\n…(truncated)";
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function buildSharePack({ repoRoot, missionDirAbs, outputDirAbs }) {
|
|
166
|
-
const root = repoRoot || process.cwd();
|
|
167
|
-
const missionDir = missionDirAbs || findLatestMissionDir(root);
|
|
168
|
-
if (!missionDir) throw new Error("No mission packs found in .vibecheck/missions");
|
|
169
|
-
|
|
170
|
-
const outputDir = outputDirAbs || path.join(missionDir, "share");
|
|
171
|
-
ensureDir(outputDir);
|
|
172
|
-
|
|
173
|
-
const missionsPath = path.join(missionDir, "missions.json");
|
|
174
|
-
const missionsObj = fs.existsSync(missionsPath)
|
|
175
|
-
? JSON.parse(fs.readFileSync(missionsPath, "utf8"))
|
|
176
|
-
: { missions: [] };
|
|
177
|
-
|
|
178
|
-
const steps = parseStepFiles(missionDir);
|
|
179
|
-
const timeline = extractProgressTimeline(steps);
|
|
180
|
-
|
|
181
|
-
let startReport = null;
|
|
182
|
-
let endReport = null;
|
|
183
|
-
|
|
184
|
-
const firstStep = steps.find(s => s.files["before_ship"]);
|
|
185
|
-
const lastStep = [...steps].reverse().find(s => s.files["after_ship"]);
|
|
186
|
-
|
|
187
|
-
if (firstStep) startReport = JSON.parse(fs.readFileSync(firstStep.files["before_ship"], "utf8"));
|
|
188
|
-
if (lastStep) endReport = JSON.parse(fs.readFileSync(lastStep.files["after_ship"], "utf8"));
|
|
189
|
-
|
|
190
|
-
const fallbackLastShip = path.join(root, ".vibecheck", "last_ship.json");
|
|
191
|
-
if (!startReport && fs.existsSync(fallbackLastShip)) startReport = JSON.parse(fs.readFileSync(fallbackLastShip, "utf8"));
|
|
192
|
-
if (!endReport && fs.existsSync(fallbackLastShip)) endReport = JSON.parse(fs.readFileSync(fallbackLastShip, "utf8"));
|
|
193
|
-
|
|
194
|
-
const header = {
|
|
195
|
-
generatedAt: new Date().toISOString(),
|
|
196
|
-
repoName: path.basename(root),
|
|
197
|
-
missionPack: path.relative(root, missionDir).replace(/\\/g, "/"),
|
|
198
|
-
startVerdict: startReport?.meta?.verdict || "unknown",
|
|
199
|
-
endVerdict: endReport?.meta?.verdict || "unknown"
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const shareJson = redactObject({
|
|
203
|
-
header,
|
|
204
|
-
missions: missionsObj.missions || [],
|
|
205
|
-
timeline,
|
|
206
|
-
startFindings: safeFindingDigest(startReport?.findings || [], 25),
|
|
207
|
-
endFindings: safeFindingDigest(endReport?.findings || [], 25)
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const shareJsonPath = path.join(outputDir, "share.json");
|
|
211
|
-
fs.writeFileSync(shareJsonPath, JSON.stringify(shareJson, null, 2), "utf8");
|
|
212
|
-
|
|
213
|
-
const md = buildMarkdown({
|
|
214
|
-
header,
|
|
215
|
-
missions: missionsObj.missions || [],
|
|
216
|
-
timeline,
|
|
217
|
-
startFindings: startReport?.findings || [],
|
|
218
|
-
endFindings: endReport?.findings || []
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
const mdPath = path.join(outputDir, "share.md");
|
|
222
|
-
fs.writeFileSync(mdPath, md, "utf8");
|
|
223
|
-
|
|
224
|
-
const prBody = buildPrComment(md);
|
|
225
|
-
const prPath = path.join(outputDir, "pr_comment.md");
|
|
226
|
-
fs.writeFileSync(prPath, prBody, "utf8");
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
missionDir,
|
|
230
|
-
outputDir,
|
|
231
|
-
files: {
|
|
232
|
-
shareJson: shareJsonPath,
|
|
233
|
-
shareMd: mdPath,
|
|
234
|
-
prComment: prPath
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
module.exports = { buildSharePack, findLatestMissionDir };
|
|
1
|
+
// bin/runners/lib/share-pack.js
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { redactObject } = require("./redact");
|
|
5
|
+
|
|
6
|
+
function ensureDir(p) {
|
|
7
|
+
fs.mkdirSync(p, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function listMissionDirs(repoRoot) {
|
|
11
|
+
const base = path.join(repoRoot, ".vibecheck", "missions");
|
|
12
|
+
if (!fs.existsSync(base)) return [];
|
|
13
|
+
const dirs = fs.readdirSync(base)
|
|
14
|
+
.map(name => ({ name, abs: path.join(base, name) }))
|
|
15
|
+
.filter(x => fs.existsSync(x.abs) && fs.statSync(x.abs).isDirectory())
|
|
16
|
+
.sort((a,b) => (a.name < b.name ? 1 : -1));
|
|
17
|
+
return dirs;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function findLatestMissionDir(repoRoot) {
|
|
21
|
+
const dirs = listMissionDirs(repoRoot);
|
|
22
|
+
return dirs.length ? dirs[0].abs : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseStepFiles(dirAbs) {
|
|
26
|
+
const files = fs.readdirSync(dirAbs).sort();
|
|
27
|
+
const steps = {};
|
|
28
|
+
|
|
29
|
+
for (const f of files) {
|
|
30
|
+
const m = f.match(/^step_(\d+)_([^_]+)_([a-z_]+)\.(json|txt)$/);
|
|
31
|
+
if (!m) continue;
|
|
32
|
+
|
|
33
|
+
const stepNo = Number(m[1]);
|
|
34
|
+
const missionId = m[2];
|
|
35
|
+
const kind = m[3];
|
|
36
|
+
const abs = path.join(dirAbs, f);
|
|
37
|
+
|
|
38
|
+
steps[stepNo] = steps[stepNo] || { stepNo, missionId, files: {} };
|
|
39
|
+
steps[stepNo].files[kind] = abs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return Object.values(steps).sort((a,b) => a.stepNo - b.stepNo);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function countSeverities(findings) {
|
|
46
|
+
const out = { BLOCK: 0, WARN: 0, INFO: 0 };
|
|
47
|
+
for (const f of findings || []) {
|
|
48
|
+
if (f.severity === "BLOCK") out.BLOCK++;
|
|
49
|
+
else if (f.severity === "WARN") out.WARN++;
|
|
50
|
+
else out.INFO++;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function extractProgressTimeline(steps) {
|
|
56
|
+
const timeline = [];
|
|
57
|
+
for (const s of steps) {
|
|
58
|
+
const beforePath = s.files["before_ship"];
|
|
59
|
+
const afterPath = s.files["after_ship"];
|
|
60
|
+
if (!beforePath || !afterPath) continue;
|
|
61
|
+
|
|
62
|
+
const before = JSON.parse(fs.readFileSync(beforePath, "utf8"));
|
|
63
|
+
const after = JSON.parse(fs.readFileSync(afterPath, "utf8"));
|
|
64
|
+
|
|
65
|
+
timeline.push({
|
|
66
|
+
step: s.stepNo,
|
|
67
|
+
missionId: s.missionId,
|
|
68
|
+
before: {
|
|
69
|
+
verdict: before?.meta?.verdict,
|
|
70
|
+
counts: countSeverities(before?.findings)
|
|
71
|
+
},
|
|
72
|
+
after: {
|
|
73
|
+
verdict: after?.meta?.verdict,
|
|
74
|
+
counts: countSeverities(after?.findings)
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return timeline;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function safeFindingDigest(findings, limit = 20) {
|
|
82
|
+
return (findings || []).slice(0, limit).map(f => ({
|
|
83
|
+
id: f.id,
|
|
84
|
+
severity: f.severity,
|
|
85
|
+
category: f.category,
|
|
86
|
+
title: f.title,
|
|
87
|
+
confidence: f.confidence,
|
|
88
|
+
evidence: (f.evidence || []).slice(0, 2).map(ev => ({
|
|
89
|
+
file: ev.file,
|
|
90
|
+
lines: ev.lines,
|
|
91
|
+
snippetHash: ev.snippetHash,
|
|
92
|
+
reason: ev.reason
|
|
93
|
+
})),
|
|
94
|
+
fixHints: (f.fixHints || []).slice(0, 4)
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function buildMarkdown({ header, missions, timeline, startFindings, endFindings }) {
|
|
99
|
+
const startCounts = countSeverities(startFindings);
|
|
100
|
+
const endCounts = countSeverities(endFindings);
|
|
101
|
+
|
|
102
|
+
const lines = [];
|
|
103
|
+
lines.push(`# vibecheck Mission Pack`);
|
|
104
|
+
lines.push(``);
|
|
105
|
+
lines.push(`**Generated:** ${header.generatedAt}`);
|
|
106
|
+
lines.push(`**Repo:** ${header.repoName || "(local)"}`);
|
|
107
|
+
lines.push(`**Start Verdict:** ${header.startVerdict} — **End Verdict:** ${header.endVerdict}`);
|
|
108
|
+
lines.push(``);
|
|
109
|
+
lines.push(`## Summary`);
|
|
110
|
+
lines.push(`- Start: BLOCK=${startCounts.BLOCK}, WARN=${startCounts.WARN}`);
|
|
111
|
+
lines.push(`- End: BLOCK=${endCounts.BLOCK}, WARN=${endCounts.WARN}`);
|
|
112
|
+
lines.push(`- Missions planned: ${missions.length}`);
|
|
113
|
+
lines.push(``);
|
|
114
|
+
|
|
115
|
+
lines.push(`## Verdict Timeline`);
|
|
116
|
+
lines.push(`| Step | Mission | Before | After |`);
|
|
117
|
+
lines.push(`|---:|---|---|---|`);
|
|
118
|
+
for (const t of timeline) {
|
|
119
|
+
const b = `${t.before.verdict} (B${t.before.counts.BLOCK}/W${t.before.counts.WARN})`;
|
|
120
|
+
const a = `${t.after.verdict} (B${t.after.counts.BLOCK}/W${t.after.counts.WARN})`;
|
|
121
|
+
lines.push(`| ${t.step} | ${t.missionId} | ${b} | ${a} |`);
|
|
122
|
+
}
|
|
123
|
+
lines.push(``);
|
|
124
|
+
|
|
125
|
+
lines.push(`## Key Findings (Start)`);
|
|
126
|
+
for (const f of safeFindingDigest(startFindings, 12)) {
|
|
127
|
+
lines.push(`- **${f.severity}** ${f.id} — ${f.title}`);
|
|
128
|
+
if (f.evidence?.[0]) {
|
|
129
|
+
const ev = f.evidence[0];
|
|
130
|
+
lines.push(` - Evidence: \`${ev.file}:${ev.lines}\` (${ev.reason})`);
|
|
131
|
+
lines.push(` - SnipHash: \`${ev.snippetHash}\``);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
lines.push(``);
|
|
135
|
+
|
|
136
|
+
lines.push(`## Remaining Findings (End)`);
|
|
137
|
+
const endDigest = safeFindingDigest(endFindings, 12);
|
|
138
|
+
if (!endDigest.length) {
|
|
139
|
+
lines.push(`✅ None.`);
|
|
140
|
+
} else {
|
|
141
|
+
for (const f of endDigest) {
|
|
142
|
+
lines.push(`- **${f.severity}** ${f.id} — ${f.title}`);
|
|
143
|
+
if (f.evidence?.[0]) {
|
|
144
|
+
const ev = f.evidence[0];
|
|
145
|
+
lines.push(` - Evidence: \`${ev.file}:${ev.lines}\` (${ev.reason})`);
|
|
146
|
+
lines.push(` - SnipHash: \`${ev.snippetHash}\``);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
lines.push(``);
|
|
151
|
+
|
|
152
|
+
lines.push(`## Notes`);
|
|
153
|
+
lines.push(`This bundle is sanitized: no secrets, no raw code snippets, only hashes + file/line pointers for verification.`);
|
|
154
|
+
lines.push(``);
|
|
155
|
+
|
|
156
|
+
return lines.join("\n");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildPrComment(md) {
|
|
160
|
+
const max = 9000;
|
|
161
|
+
if (md.length <= max) return md;
|
|
162
|
+
return md.slice(0, max) + "\n\n…(truncated)";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function buildSharePack({ repoRoot, missionDirAbs, outputDirAbs }) {
|
|
166
|
+
const root = repoRoot || process.cwd();
|
|
167
|
+
const missionDir = missionDirAbs || findLatestMissionDir(root);
|
|
168
|
+
if (!missionDir) throw new Error("No mission packs found in .vibecheck/missions");
|
|
169
|
+
|
|
170
|
+
const outputDir = outputDirAbs || path.join(missionDir, "share");
|
|
171
|
+
ensureDir(outputDir);
|
|
172
|
+
|
|
173
|
+
const missionsPath = path.join(missionDir, "missions.json");
|
|
174
|
+
const missionsObj = fs.existsSync(missionsPath)
|
|
175
|
+
? JSON.parse(fs.readFileSync(missionsPath, "utf8"))
|
|
176
|
+
: { missions: [] };
|
|
177
|
+
|
|
178
|
+
const steps = parseStepFiles(missionDir);
|
|
179
|
+
const timeline = extractProgressTimeline(steps);
|
|
180
|
+
|
|
181
|
+
let startReport = null;
|
|
182
|
+
let endReport = null;
|
|
183
|
+
|
|
184
|
+
const firstStep = steps.find(s => s.files["before_ship"]);
|
|
185
|
+
const lastStep = [...steps].reverse().find(s => s.files["after_ship"]);
|
|
186
|
+
|
|
187
|
+
if (firstStep) startReport = JSON.parse(fs.readFileSync(firstStep.files["before_ship"], "utf8"));
|
|
188
|
+
if (lastStep) endReport = JSON.parse(fs.readFileSync(lastStep.files["after_ship"], "utf8"));
|
|
189
|
+
|
|
190
|
+
const fallbackLastShip = path.join(root, ".vibecheck", "last_ship.json");
|
|
191
|
+
if (!startReport && fs.existsSync(fallbackLastShip)) startReport = JSON.parse(fs.readFileSync(fallbackLastShip, "utf8"));
|
|
192
|
+
if (!endReport && fs.existsSync(fallbackLastShip)) endReport = JSON.parse(fs.readFileSync(fallbackLastShip, "utf8"));
|
|
193
|
+
|
|
194
|
+
const header = {
|
|
195
|
+
generatedAt: new Date().toISOString(),
|
|
196
|
+
repoName: path.basename(root),
|
|
197
|
+
missionPack: path.relative(root, missionDir).replace(/\\/g, "/"),
|
|
198
|
+
startVerdict: startReport?.meta?.verdict || "unknown",
|
|
199
|
+
endVerdict: endReport?.meta?.verdict || "unknown"
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const shareJson = redactObject({
|
|
203
|
+
header,
|
|
204
|
+
missions: missionsObj.missions || [],
|
|
205
|
+
timeline,
|
|
206
|
+
startFindings: safeFindingDigest(startReport?.findings || [], 25),
|
|
207
|
+
endFindings: safeFindingDigest(endReport?.findings || [], 25)
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const shareJsonPath = path.join(outputDir, "share.json");
|
|
211
|
+
fs.writeFileSync(shareJsonPath, JSON.stringify(shareJson, null, 2), "utf8");
|
|
212
|
+
|
|
213
|
+
const md = buildMarkdown({
|
|
214
|
+
header,
|
|
215
|
+
missions: missionsObj.missions || [],
|
|
216
|
+
timeline,
|
|
217
|
+
startFindings: startReport?.findings || [],
|
|
218
|
+
endFindings: endReport?.findings || []
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const mdPath = path.join(outputDir, "share.md");
|
|
222
|
+
fs.writeFileSync(mdPath, md, "utf8");
|
|
223
|
+
|
|
224
|
+
const prBody = buildPrComment(md);
|
|
225
|
+
const prPath = path.join(outputDir, "pr_comment.md");
|
|
226
|
+
fs.writeFileSync(prPath, prBody, "utf8");
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
missionDir,
|
|
230
|
+
outputDir,
|
|
231
|
+
files: {
|
|
232
|
+
shareJson: shareJsonPath,
|
|
233
|
+
shareMd: mdPath,
|
|
234
|
+
prComment: prPath
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = { buildSharePack, findLatestMissionDir };
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
// bin/runners/lib/snippets.js
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
|
|
5
|
-
function parseLineRange(s) {
|
|
6
|
-
const m = String(s || "").match(/^(\d+)-(\d+)$/);
|
|
7
|
-
if (!m) return { start: 1, end: 1 };
|
|
8
|
-
return { start: Number(m[1]), end: Number(m[2]) };
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function getSnippet(repoRoot, ev, pad = 6) {
|
|
12
|
-
const fileAbs = path.join(repoRoot, ev.file);
|
|
13
|
-
if (!fs.existsSync(fileAbs)) return null;
|
|
14
|
-
|
|
15
|
-
const lines = fs.readFileSync(fileAbs, "utf8").split(/\r?\n/);
|
|
16
|
-
const { start, end } = parseLineRange(ev.lines);
|
|
17
|
-
const s = Math.max(1, start - pad);
|
|
18
|
-
const e = Math.min(lines.length, end + pad);
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
file: ev.file,
|
|
22
|
-
range: `${s}-${e}`,
|
|
23
|
-
reason: ev.reason,
|
|
24
|
-
text: lines.slice(s - 1, e).join("\n")
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function collectEvidenceSnippets(repoRoot, findings, maxSnips = 12) {
|
|
29
|
-
const out = [];
|
|
30
|
-
for (const f of findings) {
|
|
31
|
-
for (const ev of (f.evidence || [])) {
|
|
32
|
-
const snip = getSnippet(repoRoot, ev);
|
|
33
|
-
if (snip) out.push(snip);
|
|
34
|
-
if (out.length >= maxSnips) return out;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return out;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function collectRegexSnippets(repoRoot, files, regex, { pad = 4, limit = 8 } = {}) {
|
|
41
|
-
const out = [];
|
|
42
|
-
const rx = regex instanceof RegExp ? regex : new RegExp(String(regex), "i");
|
|
43
|
-
|
|
44
|
-
for (const fileRel of files || []) {
|
|
45
|
-
const abs = path.join(repoRoot, fileRel);
|
|
46
|
-
if (!fs.existsSync(abs)) continue;
|
|
47
|
-
|
|
48
|
-
const lines = fs.readFileSync(abs, "utf8").split(/\r?\n/);
|
|
49
|
-
for (let i = 0; i < lines.length; i++) {
|
|
50
|
-
if (!rx.test(lines[i])) continue;
|
|
51
|
-
|
|
52
|
-
const s = Math.max(1, (i + 1) - pad);
|
|
53
|
-
const e = Math.min(lines.length, (i + 1) + pad);
|
|
54
|
-
out.push({
|
|
55
|
-
file: fileRel,
|
|
56
|
-
range: `${s}-${e}`,
|
|
57
|
-
reason: `regex:${rx.toString()}`,
|
|
58
|
-
text: lines.slice(s - 1, e).join("\n")
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (out.length >= limit) return out;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
module.exports = { collectEvidenceSnippets, collectRegexSnippets };
|
|
1
|
+
// bin/runners/lib/snippets.js
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
function parseLineRange(s) {
|
|
6
|
+
const m = String(s || "").match(/^(\d+)-(\d+)$/);
|
|
7
|
+
if (!m) return { start: 1, end: 1 };
|
|
8
|
+
return { start: Number(m[1]), end: Number(m[2]) };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getSnippet(repoRoot, ev, pad = 6) {
|
|
12
|
+
const fileAbs = path.join(repoRoot, ev.file);
|
|
13
|
+
if (!fs.existsSync(fileAbs)) return null;
|
|
14
|
+
|
|
15
|
+
const lines = fs.readFileSync(fileAbs, "utf8").split(/\r?\n/);
|
|
16
|
+
const { start, end } = parseLineRange(ev.lines);
|
|
17
|
+
const s = Math.max(1, start - pad);
|
|
18
|
+
const e = Math.min(lines.length, end + pad);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
file: ev.file,
|
|
22
|
+
range: `${s}-${e}`,
|
|
23
|
+
reason: ev.reason,
|
|
24
|
+
text: lines.slice(s - 1, e).join("\n")
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function collectEvidenceSnippets(repoRoot, findings, maxSnips = 12) {
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const f of findings) {
|
|
31
|
+
for (const ev of (f.evidence || [])) {
|
|
32
|
+
const snip = getSnippet(repoRoot, ev);
|
|
33
|
+
if (snip) out.push(snip);
|
|
34
|
+
if (out.length >= maxSnips) return out;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function collectRegexSnippets(repoRoot, files, regex, { pad = 4, limit = 8 } = {}) {
|
|
41
|
+
const out = [];
|
|
42
|
+
const rx = regex instanceof RegExp ? regex : new RegExp(String(regex), "i");
|
|
43
|
+
|
|
44
|
+
for (const fileRel of files || []) {
|
|
45
|
+
const abs = path.join(repoRoot, fileRel);
|
|
46
|
+
if (!fs.existsSync(abs)) continue;
|
|
47
|
+
|
|
48
|
+
const lines = fs.readFileSync(abs, "utf8").split(/\r?\n/);
|
|
49
|
+
for (let i = 0; i < lines.length; i++) {
|
|
50
|
+
if (!rx.test(lines[i])) continue;
|
|
51
|
+
|
|
52
|
+
const s = Math.max(1, (i + 1) - pad);
|
|
53
|
+
const e = Math.min(lines.length, (i + 1) + pad);
|
|
54
|
+
out.push({
|
|
55
|
+
file: fileRel,
|
|
56
|
+
range: `${s}-${e}`,
|
|
57
|
+
reason: `regex:${rx.toString()}`,
|
|
58
|
+
text: lines.slice(s - 1, e).join("\n")
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (out.length >= limit) return out;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { collectEvidenceSnippets, collectRegexSnippets };
|