@vibgrate/cli 2026.704.3 → 2026.708.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,23 +1,267 @@
1
1
  #!/usr/bin/env node
2
- import { resolveDsn, resolveIngestHost, dashHostForIngestHost, resolveCliInvocation, availableRegionIds, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, inventory, uploadScanArtifact, dsnCommand, pushCommand, ExitCode, CliError, NPX_INVOCATION, verifyDeterminism, refreshIfStale, driftCount, embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, queryGraphSemantic, queryGraph, unavailableMessage, countPending, resolveOne, GraphIndex, shortestPath, renderReport, impactOf, testsToRun, detectRunner, defaultGraphPath, serveStdio, ASSISTANTS, usageError, loadGraph, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, coveringTests, FREE_PACK, readScanArtifact, enrichOnline, ECOSYSTEMS, discoverModels, readSavings, exportGraph, catalogPath, libDir, stableStringify, buildGraph, writeArtifacts, writeSnapshot, UsageError, ResourceLimitError, cacheDir, probeFreshness, discover, vibgrateDir, modelCacheInfo, clearModelCache, GraphSource, createServer, assistantById, serializeGraph, resolveLimits, loadCatalog, driftFor, addLibrary, parseDsn as parseDsn$1, publishPrivateLibrary, resolveLib, libId, resolveVersion, readDoc, localPackageDocs, localApiSurface, assessDocQuality, symbolsFromApi, fetchHostedDocs, selectForBudget, formatForExt, renderHtml, isModelReady } from './chunk-JL7FIC6W.js';
3
- import { resolvedGrammarFiles, grammarSetVersion } from './chunk-X5YT263H.js';
4
- import { loadAdvancedScanHook, baselineCommand } from './chunk-WHQRV66O.js';
5
- import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, prepareCompressedUpload, titleBox, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, brandProgressBar } from './chunk-YN5OGRWU.js';
2
+ import { resolveDsn, resolveIngestHost, dashHostForIngestHost, resolveCliInvocation, availableRegionIds, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, inventory, uploadScanArtifact, ingestHostForRegionId, dsnCommand, pushCommand, CliError, ExitCode, NPX_INVOCATION, refreshIfStale, driftCount, embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, queryGraphSemantic, queryGraph, unavailableMessage, countPending, resolveOne, GraphIndex, shortestPath, renderReport, impactOf, testsToRun, detectRunner, defaultGraphPath, serveStdio, ASSISTANTS, usageError, loadGraph, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, coveringTests, FREE_PACK, readScanArtifact, enrichOnline, ECOSYSTEMS, discoverModels, readSavings, exportGraph, catalogPath, libDir, stableStringify, buildGraph, writeArtifacts, writeSnapshot, UsageError, ResourceLimitError, epistemicBreakdown, cacheDir, probeFreshness, discover, vibgrateDir, modelCacheInfo, clearModelCache, GraphSource, createServer, assistantById, serializeGraph, resolveLimits, loadCatalog, driftFor, addLibrary, parseDsn as parseDsn$1, publishPrivateLibrary, resolveLib, libId, resolveVersion, readDoc, localPackageDocs, localApiSurface, assessDocQuality, symbolsFromApi, fetchHostedDocs, selectForBudget, formatForExt, verifyDeterminism, renderHtml, isModelReady } from './chunk-2J7DPIAL.js';
3
+ import { resolvedGrammarFiles, grammarSetVersion, canonicalize } from './chunk-X5YT263H.js';
4
+ import { loadAdvancedScanHook, baselineCommand } from './chunk-FMCI3MXH.js';
5
+ import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-SRWMZBLG.js';
6
6
  import './chunk-RXP66R2E.js';
7
7
  import { pathExists as pathExists$1, parseExcludePatterns } from './chunk-GI6W53LM.js';
8
- import * as fs4 from 'fs';
9
- import fs4__default, { realpathSync } from 'fs';
10
- import * as path9 from 'path';
11
- import path9__default from 'path';
8
+ import * as fs5 from 'fs';
9
+ import fs5__default, { realpathSync } from 'fs';
10
+ import * as path from 'path';
11
+ import path__default from 'path';
12
12
  import { fileURLToPath } from 'url';
13
13
  import { Command, CommanderError, Option } from 'commander';
14
- import { execSync, spawn, execFileSync } from 'child_process';
15
- import chalk5 from 'chalk';
16
- import * as fs10 from 'fs/promises';
14
+ import { execSync, spawn, spawnSync, execFileSync, execFile } from 'child_process';
15
+ import * as crypto from 'crypto';
16
+ import { randomUUID } from 'crypto';
17
+ import chalk7 from 'chalk';
17
18
  import * as os from 'os';
19
+ import * as readline from 'readline';
20
+ import * as fs14 from 'fs/promises';
18
21
  import semver from 'semver';
19
- import { randomUUID } from 'crypto';
20
22
 
23
+ var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
24
+ var IN_TOTO_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
25
+ var VG_PREDICATE_TYPE = "https://vibgrate.com/attestation/code-graph/v1";
26
+ function sha256Hex(input) {
27
+ return crypto.createHash("sha256").update(input).digest("hex");
28
+ }
29
+ function graphSubjectDigest(graph) {
30
+ const { generatedAt: _omit, ...rest } = graph;
31
+ return sha256Hex(canonicalize(rest));
32
+ }
33
+ function keyId(publicKey) {
34
+ return sha256Hex(publicKey.export({ type: "spki", format: "der" })).slice(0, 16);
35
+ }
36
+ function buildStatement(graph, input = {}) {
37
+ const graphDigest = graphSubjectDigest(graph);
38
+ const predicate = {
39
+ tool: { name: "vg", version: VERSION },
40
+ toolchain: graph.provenance.toolchain,
41
+ corpusHash: graph.provenance.corpusHash,
42
+ graphDigest,
43
+ counts: graph.meta.counts
44
+ };
45
+ if (input.commit) predicate.commit = input.commit;
46
+ if (input.sbomDigest) predicate.sbomDigest = input.sbomDigest;
47
+ if (input.timestamp) predicate.timestamp = input.timestamp;
48
+ return {
49
+ _type: IN_TOTO_STATEMENT_TYPE,
50
+ subject: [{ name: "graph.json", digest: { sha256: graphDigest } }],
51
+ predicateType: VG_PREDICATE_TYPE,
52
+ predicate
53
+ };
54
+ }
55
+ function dssePae(payloadType, body) {
56
+ const typeBytes = Buffer.from(payloadType, "utf8");
57
+ const header = Buffer.from(
58
+ `DSSEv1 ${typeBytes.length} ${payloadType} ${body.length} `,
59
+ "utf8"
60
+ );
61
+ return Buffer.concat([header, Buffer.from(body)]);
62
+ }
63
+ function signStatement(statement, privateKey) {
64
+ const body = Buffer.from(JSON.stringify(statement), "utf8");
65
+ const pae = dssePae(DSSE_PAYLOAD_TYPE, body);
66
+ const sig = crypto.sign(null, pae, privateKey);
67
+ const publicKey = crypto.createPublicKey(privateKey.export({ type: "pkcs8", format: "pem" }).toString());
68
+ return {
69
+ payloadType: DSSE_PAYLOAD_TYPE,
70
+ payload: body.toString("base64"),
71
+ signatures: [
72
+ {
73
+ keyid: keyId(publicKey),
74
+ sig: sig.toString("base64"),
75
+ publicKey: publicKey.export({ type: "spki", format: "pem" }).toString()
76
+ }
77
+ ]
78
+ };
79
+ }
80
+ function emptyStatement() {
81
+ return {
82
+ _type: "",
83
+ subject: [],
84
+ predicateType: "",
85
+ predicate: {
86
+ tool: { name: "vg", version: "" },
87
+ corpusHash: "",
88
+ graphDigest: "",
89
+ counts: { nodes: 0, edges: 0, areas: 0, tests: 0, untested: 0 }
90
+ }
91
+ };
92
+ }
93
+ function failedResult(reason) {
94
+ return {
95
+ status: "failed",
96
+ signatureValid: false,
97
+ signerPinned: false,
98
+ keyid: "",
99
+ reason,
100
+ statement: emptyStatement()
101
+ };
102
+ }
103
+ function verifyEnvelope(env, opts = {}) {
104
+ let body;
105
+ let statement;
106
+ try {
107
+ body = Buffer.from(env?.payload ?? "", "base64");
108
+ const parsed = JSON.parse(body.toString("utf8"));
109
+ if (!parsed || typeof parsed !== "object" || !parsed.predicate) {
110
+ throw new Error("missing predicate");
111
+ }
112
+ statement = parsed;
113
+ } catch {
114
+ return failedResult("malformed attestation payload (not a valid in-toto statement)");
115
+ }
116
+ const pae = dssePae(env.payloadType, body);
117
+ const sig0 = env.signatures?.[0];
118
+ const keyid = sig0?.keyid ?? "";
119
+ const sig = sig0?.sig ? Buffer.from(sig0.sig, "base64") : Buffer.alloc(0);
120
+ let signatureValid = false;
121
+ let signerPinned = false;
122
+ try {
123
+ if (sig0 && sig.length) {
124
+ if (opts.publicKeyPem) {
125
+ const pinnedKey = crypto.createPublicKey(opts.publicKeyPem);
126
+ signatureValid = crypto.verify(null, pae, pinnedKey, sig);
127
+ signerPinned = signatureValid;
128
+ } else if (sig0.publicKey) {
129
+ const embeddedKey = crypto.createPublicKey(sig0.publicKey);
130
+ signatureValid = crypto.verify(null, pae, embeddedKey, sig);
131
+ }
132
+ }
133
+ } catch {
134
+ signatureValid = false;
135
+ signerPinned = false;
136
+ }
137
+ const digestMatches = opts.graph ? graphSubjectDigest(opts.graph) === statement.predicate.graphDigest : void 0;
138
+ const dirty = statement.predicate.commit?.dirty;
139
+ let status;
140
+ let reason;
141
+ if (!signatureValid) {
142
+ status = "failed";
143
+ reason = sig0 ? "signature verification failed" : "no signature in envelope";
144
+ } else if (digestMatches === false) {
145
+ status = "failed";
146
+ reason = "graph.json no longer matches the attested digest (content changed since signing)";
147
+ } else if (signerPinned && !dirty) {
148
+ status = "verified";
149
+ reason = digestMatches === true ? "signature valid, signer trusted, graph digest matches" : "signature valid, signer trusted";
150
+ } else {
151
+ status = "signature-valid";
152
+ reason = dirty ? "signature valid but the attested tree was dirty (uncommitted changes)" : "signature valid but signer not pinned \u2014 pass --pub to establish trust";
153
+ }
154
+ return { status, signatureValid, signerPinned, digestMatches, dirty, keyid, reason, statement };
155
+ }
156
+ function generateKeypair() {
157
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
158
+ return {
159
+ privatePem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
160
+ publicPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
161
+ keyid: keyId(publicKey)
162
+ };
163
+ }
164
+ function serializeEnvelope(env) {
165
+ return `${JSON.stringify(env)}
166
+ `;
167
+ }
168
+ function parseEnvelope(text) {
169
+ const line = text.trim().split("\n")[0];
170
+ return JSON.parse(line);
171
+ }
172
+
173
+ // src/commands/attest-actions.ts
174
+ var DEFAULT_ATTESTATION = "attestation.intoto.jsonl";
175
+ var DEFAULT_KEY = "attest-key.pem";
176
+ async function signGraphAttestation(root, graph, opts) {
177
+ const notices = [];
178
+ const explicitKey = opts.key ?? process.env.VG_ATTEST_KEY;
179
+ const keyPath = explicitKey ? path.resolve(explicitKey) : path.join(root, ".vibgrate", DEFAULT_KEY);
180
+ let keyGeneratedAt;
181
+ if (!fs5.existsSync(keyPath)) {
182
+ if (explicitKey) {
183
+ throw new CliError(`signing key not found: ${explicitKey}`, ExitCode.USAGE_ERROR);
184
+ }
185
+ const kp = generateKeypair();
186
+ fs5.mkdirSync(path.dirname(keyPath), { recursive: true });
187
+ fs5.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
188
+ fs5.writeFileSync(`${keyPath}.pub`, kp.publicPem);
189
+ keyGeneratedAt = keyPath;
190
+ notices.push(
191
+ `minted a new Ed25519 signing key at ${rel(root, keyPath)} (keyid ${kp.keyid}) \u2014 keep it, add it to .gitignore, and reuse it to re-sign reproducibly`
192
+ );
193
+ }
194
+ let privateKey;
195
+ try {
196
+ privateKey = crypto.createPrivateKey(fs5.readFileSync(keyPath, "utf8"));
197
+ } catch {
198
+ throw new CliError(`could not read an Ed25519 private key from ${rel(root, keyPath)}`, ExitCode.USAGE_ERROR);
199
+ }
200
+ if (privateKey.asymmetricKeyType !== "ed25519") {
201
+ throw new CliError(
202
+ `attest requires an Ed25519 key, but ${rel(root, keyPath)} is ${privateKey.asymmetricKeyType ?? "unknown"}`,
203
+ ExitCode.USAGE_ERROR
204
+ );
205
+ }
206
+ const commit = await gitCommitInfo(root);
207
+ const statement = buildStatement(graph, { commit });
208
+ const envelope = signStatement(statement, privateKey);
209
+ const outPath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
210
+ fs5.mkdirSync(path.dirname(outPath), { recursive: true });
211
+ fs5.writeFileSync(outPath, serializeEnvelope(envelope));
212
+ return {
213
+ summary: {
214
+ keyid: envelope.signatures[0]?.keyid ?? "",
215
+ graphDigest: statement.predicate.graphDigest,
216
+ fingerprint: statement.predicate.toolchain?.fingerprint,
217
+ commit,
218
+ out: rel(root, outPath),
219
+ keyGeneratedAt: keyGeneratedAt ? rel(root, keyGeneratedAt) : void 0
220
+ },
221
+ notices
222
+ };
223
+ }
224
+ function verifyGraphAttestation(root, opts) {
225
+ const bundlePath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
226
+ if (!fs5.existsSync(bundlePath)) {
227
+ if (opts.attestation || opts.pub) {
228
+ throw new CliError(
229
+ `no attestation at ${rel(root, bundlePath)} \u2014 sign one with \`vg build --attest\``,
230
+ ExitCode.NOT_FOUND
231
+ );
232
+ }
233
+ return { bundlePath, missing: true };
234
+ }
235
+ const envelope = parseEnvelope(fs5.readFileSync(bundlePath, "utf8"));
236
+ const publicKeyPem = opts.pub ? fs5.readFileSync(path.resolve(opts.pub), "utf8") : void 0;
237
+ const graph = loadGraph(root) ?? void 0;
238
+ return { bundlePath, missing: false, result: verifyEnvelope(envelope, { publicKeyPem, graph }) };
239
+ }
240
+ async function gitCommitInfo(root) {
241
+ const sha = await resolveHead(root);
242
+ if (!sha) return void 0;
243
+ const dirty = await workingTreeDirty(root);
244
+ const branch = await gitBranch(root);
245
+ return { sha, shortSha: sha.slice(0, 7), branch, dirty: dirty === true ? true : dirty === false ? false : void 0 };
246
+ }
247
+ function gitBranch(root) {
248
+ return new Promise((resolve16) => {
249
+ execFile(
250
+ "git",
251
+ ["rev-parse", "--abbrev-ref", "HEAD"],
252
+ { cwd: root, timeout: 5e3, windowsHide: true, encoding: "utf8" },
253
+ (err, stdout) => {
254
+ if (err) return resolve16(void 0);
255
+ const name = String(stdout).trim();
256
+ resolve16(name && name !== "HEAD" ? name : void 0);
257
+ }
258
+ );
259
+ });
260
+ }
261
+ function rel(root, p) {
262
+ const r = path.relative(root, p);
263
+ return r.startsWith("..") ? p : r;
264
+ }
21
265
  function info(message = "") {
22
266
  process.stderr.write(`${message}
23
267
  `);
@@ -30,9 +274,9 @@ function json(value) {
30
274
  process.stdout.write(`${JSON.stringify(value, null, 2)}
31
275
  `);
32
276
  }
33
- var c = chalk5;
277
+ var c = chalk7;
34
278
  function disableColor() {
35
- chalk5.level = 0;
279
+ chalk7.level = 0;
36
280
  }
37
281
 
38
282
  // src/util/logo.ts
@@ -120,15 +364,19 @@ function readGlobal(cmd) {
120
364
 
121
365
  // src/commands/build.ts
122
366
  function registerBuild(program) {
123
- const cmd = program.command("build").description("build / update the code map (incremental, deterministic)").argument("[paths...]", "folders or files to map (default: current folder)").option("--only <langs>", "restrict to languages, e.g. ts,py,go").option("--exclude <glob>", "extra ignore glob (repeatable)", collect, []).option("--no-html", "do not write graph.html").option("--no-report", "do not write GRAPH_REPORT.md").option("--no-ground", "do not attach grounding (Phase 2)").option("--jobs <n>", "worker count (1 = single-threaded)").option("--scip <file>", "ingest a SCIP index for precise resolution (default: auto-detect index.scip)").option("--no-scip", "ignore any SCIP index").option("--no-tsc", "skip the in-process TypeScript resolver (heuristic floor only)").option("--no-warm", "do not warm the semantic index in the background after building").option("--grammars <dir>", "directory of grammar .wasm files (offline / air-gapped)").option("-o, --export <file>", "also write the map to a file (format inferred)").action(async function(paths, opts) {
367
+ const cmd = program.command("build").description("build / update the code map (incremental, deterministic)").argument("[paths...]", "folders or files to map (default: current folder)").option("--only <langs>", "restrict to languages, e.g. ts,py,go").option("--exclude <glob>", "extra ignore glob (repeatable)", collect, []).option("--no-html", "do not write graph.html").option("--no-report", "do not write GRAPH_REPORT.md").option("--no-ground", "do not attach grounding (Phase 2)").option("--jobs <n>", "worker count (1 = single-threaded)").option("--scip <file>", "ingest a SCIP index for precise resolution (default: auto-detect index.scip)").option("--no-scip", "ignore any SCIP index").option("--no-tsc", "skip the in-process TypeScript resolver (heuristic floor only)").option("--no-warm", "do not warm the semantic index in the background after building").option("--grammars <dir>", "directory of grammar .wasm files (offline / air-gapped)").option("-o, --export <file>", "also write the map to a file (format inferred)").option("--attest", "sign the built graph \u2192 .vibgrate/attestation.intoto.jsonl").option("--verify", "verify a committed attestation against the graph (no rebuild)").option("--attest-key <path>", "signing key PEM (else $VG_ATTEST_KEY, else .vibgrate/attest-key.pem)").option("--attestation <file>", "attestation path (out for --attest, in for --verify)").option("--pub <path>", "public key PEM to pin the signer (with --verify)").action(async function(paths, opts) {
124
368
  await runBuild(paths, opts, readGlobal(this));
125
369
  });
126
370
  applyGlobalOptions(cmd);
127
371
  }
128
372
  async function runBuild(paths, opts, global) {
129
- const root = path9.resolve(global.cwd ?? ".");
373
+ const root = path.resolve(global.cwd ?? ".");
374
+ if (opts.verify) {
375
+ await verifyGraph(root, opts, global);
376
+ return;
377
+ }
130
378
  const interactive = !global.json && !global.quiet;
131
- if (interactive) printLogo(path9.basename(root) || root);
379
+ if (interactive) printLogo(path.basename(root) || root);
132
380
  const bar = interactive ? new ProgressBar(c.dim("parsing")) : void 0;
133
381
  const jobs = opts.jobs ? Number(opts.jobs) : void 0;
134
382
  if (jobs !== void 0 && (!Number.isInteger(jobs) || jobs < 1)) {
@@ -180,6 +428,16 @@ async function runBuild(paths, opts, global) {
180
428
  });
181
429
  }
182
430
  if (opts.export) writeExport(result.graph, opts.export);
431
+ let attestation;
432
+ const attestNotices = [];
433
+ if (opts.attest) {
434
+ const signed = await signGraphAttestation(root, result.graph, {
435
+ key: opts.attestKey,
436
+ attestation: opts.attestation
437
+ });
438
+ attestation = signed.summary;
439
+ attestNotices.push(...signed.notices);
440
+ }
183
441
  if (global.json) {
184
442
  json({
185
443
  ok: true,
@@ -191,8 +449,11 @@ async function runBuild(paths, opts, global) {
191
449
  resolve: result.resolveStats,
192
450
  tsc: result.tsc,
193
451
  scip: result.scip,
452
+ epistemic: epistemicBreakdown(result.graph.edges),
194
453
  artifacts: written,
195
454
  corpusHash: result.graph.provenance.corpusHash,
455
+ toolchain: result.graph.provenance.toolchain,
456
+ attestation,
196
457
  timingMs: result.timing.totalMs,
197
458
  warnings: result.warnings
198
459
  });
@@ -201,7 +462,7 @@ async function runBuild(paths, opts, global) {
201
462
  const { counts } = result.graph.meta;
202
463
  const incremental = result.reused > 0 ? `incremental: ${result.reparsed} of ${result.totalFiles} files re-parsed` : `${result.totalFiles} files parsed`;
203
464
  const seconds = (result.timing.totalMs / 1e3).toFixed(2);
204
- info(`${c.cyan("vg")} \xB7 mapped ${rel(root)} in ${seconds}s (${incremental})`);
465
+ info(`${c.cyan("vg")} \xB7 mapped ${rel2(root)} in ${seconds}s (${incremental})`);
205
466
  info(
206
467
  ` nodes ${c.bold(String(counts.nodes))} edges ${c.bold(String(counts.edges))} areas ${counts.areas} langs ${result.graph.meta.languages.join(",") || "\u2014"}`
207
468
  );
@@ -218,13 +479,67 @@ async function runBuild(paths, opts, global) {
218
479
  if (result.scip) {
219
480
  info(c.dim(` scip: ${result.scip.resolved} precise edges from ${result.scip.tool ?? "index"} (${result.scip.documents} docs)`));
220
481
  }
221
- const artifactList = [written.graphPath, written.htmlPath, written.reportPath].filter(Boolean).map((p) => path9.relative(root, p)).join(" ");
482
+ const ep = epistemicBreakdown(result.graph.edges);
483
+ info(
484
+ c.dim(
485
+ ` edges by evidence \xB7 observed ${ep.observed} \xB7 name-matched ${ep["name-matched"]} \xB7 declared ${ep.declared}`
486
+ )
487
+ );
488
+ const artifactList = [written.graphPath, written.htmlPath, written.reportPath].filter(Boolean).map((p) => path.relative(root, p)).join(" ");
222
489
  info(` \u2192 ${artifactList}`);
223
490
  if (result.warnings.length) {
224
491
  info(c.yellow(` ${result.warnings.length} parse warning(s) \u2014 run with --json for detail`));
225
492
  }
493
+ if (attestation) {
494
+ for (const n of attestNotices) info(c.yellow(` ${n}`));
495
+ info(
496
+ c.dim(
497
+ ` attested \xB7 keyid ${attestation.keyid} \xB7 digest ${attestation.graphDigest.slice(0, 16)}\u2026 \u2192 ${attestation.out}`
498
+ )
499
+ );
500
+ }
226
501
  maybeWarmEmbeddings(root, result.graph, global, opts.warm !== false);
227
502
  }
503
+ async function verifyGraph(root, opts, global) {
504
+ const only = opts.only ? opts.only.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
505
+ const jobs = opts.jobs ? Number(opts.jobs) : void 0;
506
+ const det = await verifyDeterminism({ root, only, exclude: opts.exclude, jobs });
507
+ const attest = verifyGraphAttestation(root, { attestation: opts.attestation, pub: opts.pub });
508
+ const attestFailed = !attest.missing && attest.result?.status === "failed";
509
+ if (global.json) {
510
+ json({
511
+ ok: det.ok && !attestFailed,
512
+ determinism: { ok: det.ok, checks: det.checks, digest: det.digest },
513
+ attestation: attest.missing ? null : {
514
+ status: attest.result?.status,
515
+ signatureValid: attest.result?.signatureValid,
516
+ signerPinned: attest.result?.signerPinned,
517
+ digestMatches: attest.result?.digestMatches,
518
+ dirty: attest.result?.dirty,
519
+ keyid: attest.result?.keyid,
520
+ reason: attest.result?.reason
521
+ }
522
+ });
523
+ } else {
524
+ info(`${c.cyan("vg build --verify")} \xB7 ${path.relative(process.cwd(), root) || "."}`);
525
+ for (const check of det.checks) {
526
+ const mark = check.ok ? c.green("\u2714") : c.red("\u2718");
527
+ const detail = check.detail ? c.dim(` (${check.detail})`) : "";
528
+ info(` ${mark} ${check.name}${detail}`);
529
+ }
530
+ info(det.ok ? c.green(` deterministic \xB7 digest ${det.digest.slice(0, 16)}\u2026`) : c.red(" NON-DETERMINISTIC"));
531
+ if (attest.missing) {
532
+ info(c.dim(` attestation: none (sign one with \`vg build --attest\`)`));
533
+ } else {
534
+ const r = attest.result;
535
+ const badge = r?.status === "verified" ? c.green("\u2714 attestation verified") : r?.status === "signature-valid" ? c.yellow("~ attestation signature valid") : c.red("\u2718 attestation failed");
536
+ info(` ${badge}${r?.keyid ? c.dim(` \xB7 keyid ${r.keyid}`) : ""}`);
537
+ info(c.dim(` ${r?.reason ?? ""}`));
538
+ }
539
+ }
540
+ if (!det.ok) throw new CliError("determinism self-check failed", ExitCode.NON_DETERMINISTIC);
541
+ if (attestFailed) throw new CliError("attestation verification failed", ExitCode.GATE_FAILED);
542
+ }
228
543
  function maybeWarmEmbeddings(root, graph, global, warm) {
229
544
  if (!warm || global.json || global.quiet || global.local) return;
230
545
  if (!process.stdout.isTTY && !process.stderr.isTTY) return;
@@ -250,7 +565,7 @@ function writeExport(graph, target) {
250
565
  out(serializeGraph(graph).trimEnd());
251
566
  return;
252
567
  }
253
- const ext = path9.extname(target).toLowerCase();
568
+ const ext = path.extname(target).toLowerCase();
254
569
  let content;
255
570
  switch (ext) {
256
571
  case ".json":
@@ -268,14 +583,14 @@ function writeExport(graph, target) {
268
583
  ExitCode.USAGE_ERROR
269
584
  );
270
585
  }
271
- fs4.mkdirSync(path9.dirname(path9.resolve(target)), { recursive: true });
272
- fs4.writeFileSync(target, content);
586
+ fs5.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
587
+ fs5.writeFileSync(target, content);
273
588
  }
274
589
  function collect(value, prev) {
275
590
  return [...prev, value];
276
591
  }
277
- function rel(root) {
278
- const r = path9.relative(process.cwd(), root);
592
+ function rel2(root) {
593
+ const r = path.relative(process.cwd(), root);
279
594
  return r === "" ? "." : r;
280
595
  }
281
596
  function registerStatus(program) {
@@ -285,10 +600,10 @@ function registerStatus(program) {
285
600
  applyGlobalOptions(cmd);
286
601
  }
287
602
  async function runStatus(global) {
288
- const root = path9.resolve(global.cwd ?? ".");
603
+ const root = path.resolve(global.cwd ?? ".");
289
604
  const graphPath = global.graph ?? defaultGraphPath(root);
290
605
  const graph = loadGraph(root, graphPath);
291
- const hasCache = fs4.existsSync(path9.join(cacheDir(root), "parse-cache.json"));
606
+ const hasCache = fs5.existsSync(path.join(cacheDir(root), "parse-cache.json"));
292
607
  let stale = null;
293
608
  let staleExact = false;
294
609
  if (graph) {
@@ -308,8 +623,8 @@ async function runStatus(global) {
308
623
  }
309
624
  if (global.json) {
310
625
  json({
311
- root: path9.relative(process.cwd(), root) || ".",
312
- graphPath: path9.relative(root, graphPath),
626
+ root: path.relative(process.cwd(), root) || ".",
627
+ graphPath: path.relative(root, graphPath),
313
628
  built: graph !== null,
314
629
  generatedAt: graph?.generatedAt ?? null,
315
630
  counts: graph?.meta.counts ?? null,
@@ -324,11 +639,11 @@ async function runStatus(global) {
324
639
  return;
325
640
  }
326
641
  if (!graph) {
327
- info(`${c.cyan("vg")} \xB7 no map yet at ${c.dim(path9.relative(root, graphPath))}`);
642
+ info(`${c.cyan("vg")} \xB7 no map yet at ${c.dim(path.relative(root, graphPath))}`);
328
643
  info(` run ${c.bold("vg")} to build one`);
329
644
  return;
330
645
  }
331
- info(`${c.cyan("vg")} \xB7 ${path9.relative(root, graphPath)}`);
646
+ info(`${c.cyan("vg")} \xB7 ${path.relative(root, graphPath)}`);
332
647
  info(` generated ${graph.generatedAt}`);
333
648
  info(
334
649
  ` nodes ${c.bold(String(graph.meta.counts.nodes))} edges ${c.bold(
@@ -344,34 +659,8 @@ async function runStatus(global) {
344
659
  }
345
660
  if (vibgrateDir(root)) ;
346
661
  }
347
- function registerVerify(program) {
348
- const cmd = program.command("verify").description("determinism self-check (exit 4 on mismatch)").option("--only <langs>", "restrict to languages, e.g. ts,py").option("--jobs <n>", "worker count").action(async function(opts) {
349
- const global = readGlobal(this);
350
- const root = path9.resolve(global.cwd ?? ".");
351
- const result = await verifyDeterminism({
352
- root,
353
- only: opts.only ? opts.only.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
354
- jobs: opts.jobs ? Number(opts.jobs) : void 0
355
- });
356
- if (global.json) {
357
- json({ ok: result.ok, checks: result.checks, digest: result.digest });
358
- } else {
359
- info(`${c.cyan("vg verify")} \xB7 ${path9.relative(process.cwd(), root) || "."}`);
360
- for (const check of result.checks) {
361
- const mark = check.ok ? c.green("\u2714") : c.red("\u2718");
362
- const detail = check.detail ? c.dim(` (${check.detail})`) : "";
363
- info(` ${mark} ${check.name}${detail}`);
364
- }
365
- info(result.ok ? c.green(` deterministic \xB7 digest ${result.digest.slice(0, 16)}\u2026`) : c.red(" NON-DETERMINISTIC"));
366
- }
367
- if (!result.ok) {
368
- throw new CliError("determinism self-check failed", ExitCode.NON_DETERMINISTIC);
369
- }
370
- });
371
- applyGlobalOptions(cmd);
372
- }
373
662
  function rootOf(global) {
374
- return path9.resolve(global.cwd ?? ".");
663
+ return path.resolve(global.cwd ?? ".");
375
664
  }
376
665
  function requireGraph(global) {
377
666
  const root = rootOf(global);
@@ -655,10 +944,10 @@ function registerTree(program) {
655
944
  applyGlobalOptions(cmd);
656
945
  }
657
946
  function next(index, node, direction) {
658
- const rel2 = direction === "callees" ? index.callees(node.id) : index.callers(node.id);
947
+ const rel3 = direction === "callees" ? index.callees(node.id) : index.callers(node.id);
659
948
  const seen = /* @__PURE__ */ new Set();
660
949
  const out2 = [];
661
- for (const { node: n } of rel2) {
950
+ for (const { node: n } of rel3) {
662
951
  if (seen.has(n.id)) continue;
663
952
  seen.add(n.id);
664
953
  out2.push(n);
@@ -834,6 +1123,113 @@ function registerImpact(program) {
834
1123
  });
835
1124
  applyGlobalOptions(cmd);
836
1125
  }
1126
+
1127
+ // src/commands/unknowns.ts
1128
+ var IMPACT_CANDIDATE_CAP = 200;
1129
+ function registerUnknowns(program) {
1130
+ const cmd = program.command("unknowns").description("what the graph cannot resolve, ranked by blast radius").option("-n, --limit <n>", "how many sites to show", "20").action(function(opts) {
1131
+ const global = readGlobal(this);
1132
+ const { graph } = requireGraph(global);
1133
+ const limit = Number(opts.limit) || 20;
1134
+ const ranked = rankUnknowns(graph, limit);
1135
+ const { sites, totalSites, capped } = ranked;
1136
+ const totalRefs = (graph.unknowns ?? []).reduce((n, u) => n + u.count, 0);
1137
+ if (global.json) {
1138
+ json({
1139
+ unresolvedReferences: totalRefs,
1140
+ sites: totalSites,
1141
+ shown: sites.length,
1142
+ blastRankedCandidateCap: capped ? IMPACT_CANDIDATE_CAP : null,
1143
+ top: sites.map((s) => ({
1144
+ id: s.node.id,
1145
+ name: s.node.qualifiedName,
1146
+ kind: s.node.kind,
1147
+ file: s.node.file,
1148
+ line: s.node.span.start,
1149
+ importance: s.node.importance,
1150
+ blastRadius: s.blastRadius,
1151
+ direct: s.direct,
1152
+ transitive: s.transitive,
1153
+ unresolved: s.refs.map((r) => ({ name: r.name, kind: r.kind, count: r.count }))
1154
+ }))
1155
+ });
1156
+ return;
1157
+ }
1158
+ if (totalRefs === 0) {
1159
+ info(
1160
+ `${c.cyan("vg unknowns")} \xB7 none \u2014 every heuristic reference resolved (or was covered by a precise rung)`
1161
+ );
1162
+ return;
1163
+ }
1164
+ info(
1165
+ `${c.cyan("vg unknowns")} \xB7 ${c.bold(String(totalRefs))} unresolved reference(s) at ${totalSites} site(s) \xB7 top ${sites.length} by blast radius`
1166
+ );
1167
+ for (const s of sites) {
1168
+ info(
1169
+ ` ${c.yellow(pad2(String(s.blastRadius), 5))} ${c.bold(s.node.qualifiedName)} ` + c.dim(`${s.node.file}:${s.node.span.start} \xB7 importance ${s.node.importance.toFixed(3)}`)
1170
+ );
1171
+ info(` ${c.dim("cannot resolve:")} ${s.refs.map(fmtRef).join(", ")}`);
1172
+ }
1173
+ info(
1174
+ c.dim(
1175
+ ` blast radius = transitive dependents; these are the code paths the graph is blind about`
1176
+ )
1177
+ );
1178
+ if (capped) {
1179
+ info(
1180
+ c.dim(
1181
+ ` note: ${totalSites} sites have unknowns; only the top ${IMPACT_CANDIDATE_CAP} by centrality were blast-radius scored (the tail is ordered by centrality alone)`
1182
+ )
1183
+ );
1184
+ }
1185
+ });
1186
+ applyGlobalOptions(cmd);
1187
+ }
1188
+ function rankUnknowns(graph, limit) {
1189
+ const unknowns = graph.unknowns ?? [];
1190
+ if (unknowns.length === 0) return { sites: [], totalSites: 0, capped: false };
1191
+ const byId = new Map(graph.nodes.map((n) => [n.id, n]));
1192
+ const grouped = /* @__PURE__ */ new Map();
1193
+ for (const u of unknowns) {
1194
+ const list = grouped.get(u.from);
1195
+ if (list) list.push(u);
1196
+ else grouped.set(u.from, [u]);
1197
+ }
1198
+ const candidates = [];
1199
+ for (const [from, refs] of grouped) {
1200
+ const node = byId.get(from);
1201
+ if (!node) continue;
1202
+ candidates.push({ node, refs, total: refs.reduce((n, r) => n + r.count, 0) });
1203
+ }
1204
+ candidates.sort(
1205
+ (a, b) => b.node.importance - a.node.importance || a.node.id.localeCompare(b.node.id)
1206
+ );
1207
+ const scored = candidates.slice(0, IMPACT_CANDIDATE_CAP).map((cand) => {
1208
+ const impact = impactOf(graph, cand.node.id);
1209
+ return {
1210
+ ...cand,
1211
+ blastRadius: impact.affected.length,
1212
+ direct: impact.direct,
1213
+ transitive: impact.transitive
1214
+ };
1215
+ });
1216
+ scored.sort(
1217
+ (a, b) => b.blastRadius - a.blastRadius || b.node.importance - a.node.importance || b.total - a.total || a.node.id.localeCompare(b.node.id)
1218
+ );
1219
+ return {
1220
+ sites: scored.slice(0, limit),
1221
+ totalSites: candidates.length,
1222
+ capped: candidates.length > IMPACT_CANDIDATE_CAP
1223
+ };
1224
+ }
1225
+ function fmtRef(r) {
1226
+ const times = r.count > 1 ? ` \xD7${r.count}` : "";
1227
+ const kind = r.kind === "call" ? "" : ` (${r.kind})`;
1228
+ return `${r.name}${kind}${times}`;
1229
+ }
1230
+ function pad2(s, n) {
1231
+ return s.padStart(n, " ");
1232
+ }
837
1233
  function registerServe(program) {
838
1234
  const cmd = program.command("serve").description("start Vibgrate AI Context \u2014 local-first MCP serving your code map, drift & version-correct docs to your AI").option("--http", "serve over streamable HTTP instead of stdio").option("--port <n>", "port for --http", "7437").option("--host <h>", "host for --http", "127.0.0.1").option("--savings", "record local, counts-only usage savings (opt-in; off by default)").option("--dedup", "collapse a node's heavy relation lists on repeat reads within a session (opt-in; saves tokens)").option("--no-refresh", "serve the map as built \u2014 skip the auto-rebuild when files change").action(async function(opts) {
839
1235
  const global = readGlobal(this);
@@ -846,7 +1242,7 @@ function registerServe(program) {
846
1242
  dedup: opts.dedup === true,
847
1243
  refresh
848
1244
  };
849
- if (!fs4.existsSync(graphPath)) {
1245
+ if (!fs5.existsSync(graphPath)) {
850
1246
  throw new CliError(
851
1247
  `no map found at ${graphPath} \u2014 run \`vg\` to build one first`,
852
1248
  ExitCode.NOT_FOUND
@@ -885,7 +1281,7 @@ async function serveHttp(graphPath, host, port, opts, freshness) {
885
1281
  info(c.red(`vg serve: request error: ${err.message}`));
886
1282
  }
887
1283
  });
888
- await new Promise((resolve15) => httpServer.listen(port, host, resolve15));
1284
+ await new Promise((resolve16) => httpServer.listen(port, host, resolve16));
889
1285
  info(c.dim(`vg \xB7 MCP server on http://${host}:${port}/mcp (read-only, local, ${freshness})`));
890
1286
  await new Promise(() => {
891
1287
  });
@@ -902,9 +1298,9 @@ function registerInstall(program) {
902
1298
  json(ASSISTANTS.map((a) => ({ id: a.id, label: a.label, mcp: !!a.mcp, skill: !!a.skill, nudge: !!a.nudge })));
903
1299
  } else {
904
1300
  info(`${c.cyan("vg install")} \xB7 supported assistants`);
905
- const pad3 = Math.max(...ASSISTANTS.map((a) => a.id.length)) + 2;
1301
+ const pad4 = Math.max(...ASSISTANTS.map((a) => a.id.length)) + 2;
906
1302
  for (const a of ASSISTANTS) {
907
- info(` ${c.bold(a.id.padEnd(pad3))} ${a.label} ${c.dim(`mcp:${a.mcp ? "\u2713" : "\u2014"} skill:${a.skill ? "\u2713" : "\u2014"} nudge:${a.nudge ? "\u2713" : "\u2014"}`)}`);
1303
+ info(` ${c.bold(a.id.padEnd(pad4))} ${a.label} ${c.dim(`mcp:${a.mcp ? "\u2713" : "\u2014"} skill:${a.skill ? "\u2713" : "\u2014"} nudge:${a.nudge ? "\u2713" : "\u2014"}`)}`);
908
1304
  }
909
1305
  }
910
1306
  return;
@@ -964,8 +1360,8 @@ function registerShare(program) {
964
1360
  const cmd = program.command("share").description("make the map committable + auto-updating for your team (git)").option("--undo", "reverse what `vg share` installed").option("--reports", "also commit graph.html / GRAPH_REPORT.md (default: gitignored)").action(function(opts) {
965
1361
  const global = readGlobal(this);
966
1362
  const root = rootOf(global);
967
- const gitDir = path9.join(root, ".git");
968
- if (!fs4.existsSync(gitDir)) {
1363
+ const gitDir = path.join(root, ".git");
1364
+ if (!fs5.existsSync(gitDir)) {
969
1365
  throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
970
1366
  }
971
1367
  if (opts.undo) {
@@ -986,23 +1382,23 @@ function registerShare(program) {
986
1382
  applyGlobalOptions(cmd);
987
1383
  }
988
1384
  function writeVibgrateGitignore(root, reports) {
989
- const dir = path9.join(root, ".vibgrate");
990
- fs4.mkdirSync(dir, { recursive: true });
1385
+ const dir = path.join(root, ".vibgrate");
1386
+ fs5.mkdirSync(dir, { recursive: true });
991
1387
  const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl"];
992
1388
  if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
993
- fs4.writeFileSync(path9.join(dir, ".gitignore"), `${lines.join("\n")}
1389
+ fs5.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
994
1390
  `);
995
1391
  }
996
1392
  function writeGitAttributes(root) {
997
- const file = path9.join(root, ".gitattributes");
1393
+ const file = path.join(root, ".gitattributes");
998
1394
  const line = ".vibgrate/graph.json merge=vg";
999
- let content = fs4.existsSync(file) ? fs4.readFileSync(file, "utf8") : "";
1395
+ let content = fs5.existsSync(file) ? fs5.readFileSync(file, "utf8") : "";
1000
1396
  if (!content.split("\n").some((l) => l.trim() === line)) {
1001
1397
  content = content.length ? `${content.replace(/\s*$/, "")}
1002
1398
  ${line}
1003
1399
  ` : `${line}
1004
1400
  `;
1005
- fs4.writeFileSync(file, content);
1401
+ fs5.writeFileSync(file, content);
1006
1402
  }
1007
1403
  }
1008
1404
  function configureMergeDriver(root) {
@@ -1011,8 +1407,8 @@ function configureMergeDriver(root) {
1011
1407
  }
1012
1408
  function installPreCommitHook(root) {
1013
1409
  const hooksDir = hookDir(root);
1014
- fs4.mkdirSync(hooksDir, { recursive: true });
1015
- const file = path9.join(hooksDir, "pre-commit");
1410
+ fs5.mkdirSync(hooksDir, { recursive: true });
1411
+ const file = path.join(hooksDir, "pre-commit");
1016
1412
  const block = [
1017
1413
  HOOK_BEGIN,
1018
1414
  "if command -v vg >/dev/null 2>&1; then",
@@ -1021,7 +1417,7 @@ function installPreCommitHook(root) {
1021
1417
  "fi",
1022
1418
  HOOK_END
1023
1419
  ].join("\n");
1024
- let content = fs4.existsSync(file) ? fs4.readFileSync(file, "utf8") : "#!/bin/sh\n";
1420
+ let content = fs5.existsSync(file) ? fs5.readFileSync(file, "utf8") : "#!/bin/sh\n";
1025
1421
  const re = new RegExp(`${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}`);
1026
1422
  if (re.test(content)) content = content.replace(re, block);
1027
1423
  else content = `${content.replace(/\s*$/, "")}
@@ -1030,21 +1426,21 @@ ${block}
1030
1426
  `;
1031
1427
  if (!content.startsWith("#!")) content = `#!/bin/sh
1032
1428
  ${content}`;
1033
- fs4.writeFileSync(file, content);
1034
- fs4.chmodSync(file, 493);
1429
+ fs5.writeFileSync(file, content);
1430
+ fs5.chmodSync(file, 493);
1035
1431
  }
1036
1432
  function undo(root) {
1037
- const hookFile = path9.join(hookDir(root), "pre-commit");
1038
- if (fs4.existsSync(hookFile)) {
1433
+ const hookFile = path.join(hookDir(root), "pre-commit");
1434
+ if (fs5.existsSync(hookFile)) {
1039
1435
  const re = new RegExp(`\\n*${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}\\n*`);
1040
- const next2 = fs4.readFileSync(hookFile, "utf8").replace(re, "\n");
1041
- if (next2.trim() === "#!/bin/sh") fs4.rmSync(hookFile);
1042
- else fs4.writeFileSync(hookFile, next2);
1436
+ const next2 = fs5.readFileSync(hookFile, "utf8").replace(re, "\n");
1437
+ if (next2.trim() === "#!/bin/sh") fs5.rmSync(hookFile);
1438
+ else fs5.writeFileSync(hookFile, next2);
1043
1439
  }
1044
- const attr = path9.join(root, ".gitattributes");
1045
- if (fs4.existsSync(attr)) {
1046
- const next2 = fs4.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
1047
- fs4.writeFileSync(attr, next2);
1440
+ const attr = path.join(root, ".gitattributes");
1441
+ if (fs5.existsSync(attr)) {
1442
+ const next2 = fs5.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
1443
+ fs5.writeFileSync(attr, next2);
1048
1444
  }
1049
1445
  try {
1050
1446
  git(root, ["config", "--remove-section", "merge.vg"]);
@@ -1054,10 +1450,10 @@ function undo(root) {
1054
1450
  function hookDir(root) {
1055
1451
  try {
1056
1452
  const configured = git(root, ["config", "--get", "core.hooksPath"]).trim();
1057
- if (configured) return path9.resolve(root, configured);
1453
+ if (configured) return path.resolve(root, configured);
1058
1454
  } catch {
1059
1455
  }
1060
- return path9.join(root, ".git", "hooks");
1456
+ return path.join(root, ".git", "hooks");
1061
1457
  }
1062
1458
  function git(root, args) {
1063
1459
  return execFileSync("git", args, { cwd: root, encoding: "utf8" });
@@ -1088,7 +1484,7 @@ function registerBenchmark(program) {
1088
1484
  if (tokenReduction.questions.length) {
1089
1485
  info(` token reduction vs grep/read baseline (${c.dim("estimates")}):`);
1090
1486
  for (const q of tokenReduction.questions) {
1091
- info(` ${pad2(`${q.ratio.toFixed(1)}\xD7`, 6)} ${c.dim(`vg ${q.vgTokens} vs ~${q.baselineTokens}`)} "${q.question}"`);
1487
+ info(` ${pad3(`${q.ratio.toFixed(1)}\xD7`, 6)} ${c.dim(`vg ${q.vgTokens} vs ~${q.baselineTokens}`)} "${q.question}"`);
1092
1488
  }
1093
1489
  info(` ${c.bold(`aggregate \u2248 ${tokenReduction.aggregateRatio.toFixed(1)}\xD7 fewer tokens`)} ${c.dim("(honest estimate, scales with repo size)")}`);
1094
1490
  }
@@ -1177,7 +1573,7 @@ function grepBaselineTokens(term, files) {
1177
1573
  if (f.size > 512 * 1024) continue;
1178
1574
  let content = "";
1179
1575
  try {
1180
- content = fs4.readFileSync(f.abs, "utf8");
1576
+ content = fs5.readFileSync(f.abs, "utf8");
1181
1577
  } catch {
1182
1578
  continue;
1183
1579
  }
@@ -1187,7 +1583,7 @@ function grepBaselineTokens(term, files) {
1187
1583
  }
1188
1584
  function safeSize(abs) {
1189
1585
  try {
1190
- return fs4.statSync(abs).size;
1586
+ return fs5.statSync(abs).size;
1191
1587
  } catch {
1192
1588
  return 0;
1193
1589
  }
@@ -1204,7 +1600,7 @@ function round(x) {
1204
1600
  function round6(x) {
1205
1601
  return Math.round(x * 1e6) / 1e6;
1206
1602
  }
1207
- function pad2(s, n) {
1603
+ function pad3(s, n) {
1208
1604
  return s.padStart(n, " ");
1209
1605
  }
1210
1606
  function registerTests(program) {
@@ -1517,11 +1913,11 @@ function normalizeStandards(raw) {
1517
1913
  return { banned };
1518
1914
  }
1519
1915
  function loadStandards(root) {
1520
- for (const rel2 of STANDARDS_FILES) {
1521
- const p = path9__default.join(root, rel2);
1916
+ for (const rel3 of STANDARDS_FILES) {
1917
+ const p = path__default.join(root, rel3);
1522
1918
  let text;
1523
1919
  try {
1524
- text = fs4__default.readFileSync(p, "utf8");
1920
+ text = fs5__default.readFileSync(p, "utf8");
1525
1921
  } catch {
1526
1922
  continue;
1527
1923
  }
@@ -1716,7 +2112,7 @@ async function addCmd(root, rest, opts, asJson) {
1716
2112
  }
1717
2113
  function readPackageVersion(root) {
1718
2114
  try {
1719
- const pkg = JSON.parse(fs4__default.readFileSync(path9__default.join(root, "package.json"), "utf8"));
2115
+ const pkg = JSON.parse(fs5__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
1720
2116
  return typeof pkg.version === "string" ? pkg.version : void 0;
1721
2117
  } catch {
1722
2118
  return void 0;
@@ -1724,7 +2120,7 @@ function readPackageVersion(root) {
1724
2120
  }
1725
2121
  function readOptionalFile(p) {
1726
2122
  try {
1727
- const t = fs4__default.readFileSync(p, "utf8");
2123
+ const t = fs5__default.readFileSync(p, "utf8");
1728
2124
  return t.trim() ? t : void 0;
1729
2125
  } catch {
1730
2126
  return void 0;
@@ -1739,8 +2135,8 @@ async function publishCmd(root, rest, opts, global, asJson) {
1739
2135
  if (!parsed) throw new CliError("publishing a private library requires a DSN \u2014 run `vibgrate login` or set VIBGRATE_DSN", ExitCode.USAGE_ERROR);
1740
2136
  const version = (opts.version || readPackageVersion(root) || "").trim();
1741
2137
  if (!version) throw usageError("a --version is required (or run from a package whose package.json has a version)");
1742
- const readme = readOptionalFile(opts.readme ? path9__default.resolve(opts.readme) : path9__default.join(root, "README.md"));
1743
- const dts = opts.dts ? readOptionalFile(path9__default.resolve(opts.dts)) : void 0;
2138
+ const readme = readOptionalFile(opts.readme ? path__default.resolve(opts.readme) : path__default.join(root, "README.md"));
2139
+ const dts = opts.dts ? readOptionalFile(path__default.resolve(opts.dts)) : void 0;
1744
2140
  if (!readme && !dts) throw usageError("nothing to publish \u2014 no README.md found and no --dts given");
1745
2141
  const result = await publishPrivateLibrary(
1746
2142
  { name, version, readme, dts, language: opts.language },
@@ -1852,7 +2248,7 @@ async function showCmd(root, name, budget, asJson, opts = {}) {
1852
2248
  out(doc);
1853
2249
  }
1854
2250
  function registerExport(program) {
1855
- const cmd = program.command("export").description("export the map (format inferred: json|ndjson|graphml|dot|cypher|md|html|cdx.json|spdx.json)").argument("[file]", "target file (or - for stdout)", "map.json").action(function(file) {
2251
+ const cmd = program.command("export").description("export the map (format inferred: json|ndjson|graphml|dot|cypher|sql|md|html|cdx.json|spdx.json)").argument("[file]", "target file (or - for stdout)", "map.json").action(function(file) {
1856
2252
  const global = readGlobal(this);
1857
2253
  const { graph } = requireGraph(global);
1858
2254
  const root = rootOf(global);
@@ -1869,8 +2265,8 @@ function registerExport(program) {
1869
2265
  out(content.trimEnd());
1870
2266
  return;
1871
2267
  }
1872
- fs4.mkdirSync(path9.dirname(path9.resolve(file)), { recursive: true });
1873
- fs4.writeFileSync(file, content);
2268
+ fs5.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
2269
+ fs5.writeFileSync(file, content);
1874
2270
  info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
1875
2271
  });
1876
2272
  applyGlobalOptions(cmd);
@@ -1880,37 +2276,37 @@ function detectFormat(file) {
1880
2276
  if (lower.endsWith(".cdx.json")) return "cyclonedx";
1881
2277
  if (lower.endsWith(".spdx.json")) return "spdx";
1882
2278
  if (file === "-") return "json";
1883
- return formatForExt(path9.extname(file));
2279
+ return formatForExt(path.extname(file));
1884
2280
  }
1885
2281
  function registerBundle(program) {
1886
2282
  const cmd = program.command("bundle").description("build an air-gapped bundle (grammars + graph + library catalog)").option("--offline", "offline bundle (default; present for clarity)").option("-o, --out <dir>", "output directory", "vg-bundle").action(function(opts) {
1887
2283
  const global = readGlobal(this);
1888
2284
  const root = rootOf(global);
1889
- const outDir = path9.resolve(root, opts.out ?? "vg-bundle");
2285
+ const outDir = path.resolve(root, opts.out ?? "vg-bundle");
1890
2286
  let grammarFiles;
1891
2287
  try {
1892
2288
  grammarFiles = resolvedGrammarFiles();
1893
2289
  } catch {
1894
2290
  throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
1895
2291
  }
1896
- fs4.mkdirSync(path9.join(outDir, "grammars"), { recursive: true });
2292
+ fs5.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
1897
2293
  let grammarCount = 0;
1898
2294
  for (const { fileName, absPath } of grammarFiles) {
1899
- fs4.copyFileSync(absPath, path9.join(outDir, "grammars", fileName));
2295
+ fs5.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
1900
2296
  grammarCount++;
1901
2297
  }
1902
2298
  const included = [`grammars/ (${grammarCount} .wasm)`];
1903
2299
  const graphSrc = global.graph ?? defaultGraphPath(root);
1904
- if (fs4.existsSync(graphSrc)) {
1905
- fs4.copyFileSync(graphSrc, path9.join(outDir, "graph.json"));
2300
+ if (fs5.existsSync(graphSrc)) {
2301
+ fs5.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
1906
2302
  included.push("graph.json");
1907
2303
  }
1908
2304
  const cat = catalogPath(root);
1909
- if (fs4.existsSync(cat)) {
1910
- fs4.copyFileSync(cat, path9.join(outDir, "vibgrate.lib.json"));
2305
+ if (fs5.existsSync(cat)) {
2306
+ fs5.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
1911
2307
  included.push("vibgrate.lib.json");
1912
- if (fs4.existsSync(libDir(root))) {
1913
- fs4.cpSync(libDir(root), path9.join(outDir, "lib"), { recursive: true });
2308
+ if (fs5.existsSync(libDir(root))) {
2309
+ fs5.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
1914
2310
  included.push("lib/");
1915
2311
  }
1916
2312
  }
@@ -1922,38 +2318,38 @@ function registerBundle(program) {
1922
2318
  included,
1923
2319
  usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
1924
2320
  };
1925
- fs4.writeFileSync(path9.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
2321
+ fs5.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
1926
2322
  `);
1927
2323
  if (global.json) {
1928
- json({ out: path9.relative(root, outDir), ...manifest });
2324
+ json({ out: path.relative(root, outDir), ...manifest });
1929
2325
  return;
1930
2326
  }
1931
- info(`${c.cyan("vg bundle")} \xB7 wrote ${path9.relative(root, outDir)}/`);
2327
+ info(`${c.cyan("vg bundle")} \xB7 wrote ${path.relative(root, outDir)}/`);
1932
2328
  for (const i of included) info(` ${c.green("\u2714")} ${i}`);
1933
- info(c.dim(` offline: vg build --grammars "${path9.relative(root, outDir)}/grammars"`));
2329
+ info(c.dim(` offline: vg build --grammars "${path.relative(root, outDir)}/grammars"`));
1934
2330
  });
1935
2331
  applyGlobalOptions(cmd);
1936
2332
  }
1937
2333
  var initCommand = new Command("init").description("Initialize vibgrate in a project").argument("[path]", "Path to initialize", ".").option("--baseline", "Create initial baseline after init").option("--yes", "Skip confirmation prompts").action(async (targetPath, opts) => {
1938
- const rootDir = path9.resolve(targetPath);
1939
- const vibgrateDir2 = path9.join(rootDir, ".vibgrate");
2334
+ const rootDir = path.resolve(targetPath);
2335
+ const vibgrateDir2 = path.join(rootDir, ".vibgrate");
1940
2336
  await ensureDir(vibgrateDir2);
1941
- console.log(chalk5.green("\u2714") + ` Created ${chalk5.bold(".vibgrate/")} directory`);
1942
- const configPath = path9.join(rootDir, "vibgrate.config.ts");
2337
+ console.log(chalk7.green("\u2714") + ` Created ${chalk7.bold(".vibgrate/")} directory`);
2338
+ const configPath = path.join(rootDir, "vibgrate.config.ts");
1943
2339
  if (await pathExists(configPath)) {
1944
- console.log(chalk5.dim(" vibgrate.config.ts already exists, skipping"));
2340
+ console.log(chalk7.dim(" vibgrate.config.ts already exists, skipping"));
1945
2341
  } else {
1946
2342
  await writeDefaultConfig(rootDir);
1947
- console.log(chalk5.green("\u2714") + ` Created ${chalk5.bold("vibgrate.config.ts")}`);
2343
+ console.log(chalk7.green("\u2714") + ` Created ${chalk7.bold("vibgrate.config.ts")}`);
1948
2344
  }
1949
2345
  if (opts.baseline) {
1950
- const { runBaseline } = await import('./baseline-64EUPWUH.js');
2346
+ const { runBaseline } = await import('./baseline-RRO4NNNF.js');
1951
2347
  await runBaseline(rootDir);
1952
2348
  }
1953
2349
  console.log("");
1954
- console.log(chalk5.bold("Next steps:"));
1955
- console.log(` ${chalk5.cyan("vibgrate scan")} Scan for upgrade drift`);
1956
- console.log(` ${chalk5.cyan("vibgrate baseline")} Create a drift baseline`);
2350
+ console.log(chalk7.bold("Next steps:"));
2351
+ console.log(` ${chalk7.cyan("vibgrate scan")} Scan for upgrade drift`);
2352
+ console.log(` ${chalk7.cyan("vibgrate baseline")} Create a drift baseline`);
1957
2353
  console.log("");
1958
2354
  });
1959
2355
 
@@ -1980,7 +2376,7 @@ var AI_ASSISTANT_DETECTORS = [
1980
2376
  async function detectAiAssistant(rootDir) {
1981
2377
  for (const detector of AI_ASSISTANT_DETECTORS) {
1982
2378
  for (const file of detector.files) {
1983
- if (await pathExists$1(path9.join(rootDir, file))) {
2379
+ if (await pathExists$1(path.join(rootDir, file))) {
1984
2380
  return detector.name;
1985
2381
  }
1986
2382
  }
@@ -1988,22 +2384,22 @@ async function detectAiAssistant(rootDir) {
1988
2384
  return null;
1989
2385
  }
1990
2386
  function printAiContextPrompt(detectedAssistant) {
1991
- const teal2 = chalk5.hex("#3FB0A4");
1992
- const mint2 = chalk5.hex("#4FE3C1");
2387
+ const teal2 = chalk7.hex("#3FB0A4");
2388
+ const mint2 = chalk7.hex("#4FE3C1");
1993
2389
  const cli = resolveCliInvocation();
1994
2390
  const installArgs = detectedAssistant ? `install ${detectedAssistant}` : "install --all";
1995
2391
  const installCmd = `${cli} ${installArgs}`;
1996
- const detectedNote = detectedAssistant ? chalk5.dim(` (${detectedAssistant} config detected)`) : "";
2392
+ const detectedNote = detectedAssistant ? chalk7.dim(` (${detectedAssistant} config detected)`) : "";
1997
2393
  console.log("");
1998
2394
  console.log(teal2(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
1999
- console.log(teal2(" \u2502") + " " + mint2("\u25C6") + " " + chalk5.bold.white("Get AI-aware answers in your editor") + " " + teal2("\u2502"));
2395
+ console.log(teal2(" \u2502") + " " + mint2("\u25C6") + " " + chalk7.bold.white("Get AI-aware answers in your editor") + " " + teal2("\u2502"));
2000
2396
  console.log(teal2(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
2001
2397
  console.log("");
2002
- console.log(" " + chalk5.bold.white(installCmd) + detectedNote);
2398
+ console.log(" " + chalk7.bold.white(installCmd) + detectedNote);
2003
2399
  console.log("");
2004
- console.log(" " + teal2("\xB7") + " " + chalk5.white("Code map in your assistant") + chalk5.dim(" \u2014 call trees, impact surfaces, import paths"));
2005
- console.log(" " + teal2("\xB7") + " " + chalk5.white("Offline drift") + chalk5.dim(" \u2014 DriftScore and upgrade priorities, right inside your editor"));
2006
- console.log(" " + teal2("\xB7") + " " + chalk5.white("Version-correct library docs") + chalk5.dim(" \u2014 pinned to your lockfile, no hallucinated APIs"));
2400
+ console.log(" " + teal2("\xB7") + " " + chalk7.white("Code map in your assistant") + chalk7.dim(" \u2014 call trees, impact surfaces, import paths"));
2401
+ console.log(" " + teal2("\xB7") + " " + chalk7.white("Offline drift") + chalk7.dim(" \u2014 DriftScore and upgrade priorities, right inside your editor"));
2402
+ console.log(" " + teal2("\xB7") + " " + chalk7.white("Version-correct library docs") + chalk7.dim(" \u2014 pinned to your lockfile, no hallucinated APIs"));
2007
2403
  console.log("");
2008
2404
  }
2009
2405
 
@@ -2014,14 +2410,14 @@ function shouldBuildCodeMap(opts) {
2014
2410
  async function autoPush(artifact, rootDir, opts) {
2015
2411
  const dsn = resolveDsn(opts.dsn);
2016
2412
  if (!dsn) {
2017
- console.error(chalk5.red("No DSN provided for push."));
2018
- console.error(chalk5.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
2413
+ console.error(chalk7.red("No DSN provided for push."));
2414
+ console.error(chalk7.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
2019
2415
  if (opts.strict) process.exit(1);
2020
2416
  return;
2021
2417
  }
2022
2418
  const parsed = parseDsn(dsn);
2023
2419
  if (!parsed) {
2024
- console.error(chalk5.red("Invalid DSN format."));
2420
+ console.error(chalk7.red("Invalid DSN format."));
2025
2421
  if (opts.strict) process.exit(1);
2026
2422
  return;
2027
2423
  }
@@ -2032,7 +2428,7 @@ async function autoPush(artifact, rootDir, opts) {
2032
2428
  try {
2033
2429
  host = resolveIngestHost(opts.region);
2034
2430
  } catch (e) {
2035
- console.error(chalk5.red(e instanceof Error ? e.message : String(e)));
2431
+ console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
2036
2432
  if (opts.strict) process.exit(1);
2037
2433
  return;
2038
2434
  }
@@ -2040,7 +2436,7 @@ async function autoPush(artifact, rootDir, opts) {
2040
2436
  const originalSize = JSON.stringify(artifact).length;
2041
2437
  const compressedSize = body.length;
2042
2438
  const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
2043
- console.log(chalk5.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
2439
+ console.log(chalk7.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
2044
2440
  try {
2045
2441
  const { response, host: uploadedHost } = await uploadScanArtifact({
2046
2442
  scheme: parsed.scheme,
@@ -2060,36 +2456,36 @@ async function autoPush(artifact, rootDir, opts) {
2060
2456
  const result = await response.json();
2061
2457
  if (result.unchanged) {
2062
2458
  console.log(
2063
- chalk5.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
2459
+ chalk7.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
2064
2460
  );
2065
2461
  if (result.previousIngestId) {
2066
2462
  emitIngestIdLine(result.previousIngestId, { unchanged: true });
2067
2463
  const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.previousIngestId}`;
2068
- console.log(chalk5.dim(` Previous report: ${dashUrl}`));
2464
+ console.log(chalk7.dim(` Previous report: ${dashUrl}`));
2069
2465
  } else if (opts.strict) {
2070
- console.error(chalk5.red("Repository unchanged but no previous ingest id returned."));
2466
+ console.error(chalk7.red("Repository unchanged but no previous ingest id returned."));
2071
2467
  process.exit(1);
2072
2468
  }
2073
2469
  return;
2074
2470
  }
2075
- console.log(chalk5.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
2471
+ console.log(chalk7.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
2076
2472
  if (result.ingestId) {
2077
2473
  emitIngestIdLine(result.ingestId);
2078
2474
  const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.ingestId}`;
2079
2475
  const CLEAR_LINE = process.platform === "win32" ? "\x1B[0G\x1B[2K" : "";
2080
2476
  console.log("");
2081
- console.log(CLEAR_LINE + chalk5.dim(" Processing continues in the background. Results available shortly."));
2477
+ console.log(CLEAR_LINE + chalk7.dim(" Processing continues in the background. Results available shortly."));
2082
2478
  console.log("");
2083
- console.log(CLEAR_LINE + chalk5.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2084
- console.log(CLEAR_LINE + chalk5.bold(" \u{1F4CA} View Scan Report"));
2085
- console.log(CLEAR_LINE + " " + chalk5.underline.cyan(dashUrl));
2086
- console.log(CLEAR_LINE + chalk5.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2479
+ console.log(CLEAR_LINE + chalk7.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2480
+ console.log(CLEAR_LINE + chalk7.bold(" \u{1F4CA} View Scan Report"));
2481
+ console.log(CLEAR_LINE + " " + chalk7.underline.cyan(dashUrl));
2482
+ console.log(CLEAR_LINE + chalk7.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2087
2483
  console.log("");
2088
2484
  console.log("");
2089
2485
  }
2090
2486
  } catch (e) {
2091
2487
  const msg = e instanceof Error ? e.message : String(e);
2092
- console.error(chalk5.red(`Upload failed: ${msg}`));
2488
+ console.error(chalk7.red(`Upload failed: ${msg}`));
2093
2489
  if (opts.strict) process.exit(1);
2094
2490
  }
2095
2491
  }
@@ -2101,15 +2497,15 @@ function reportStandards(rootDir) {
2101
2497
  if (!loaded.policy) return;
2102
2498
  const violations = checkStandards(loaded.policy, inventory(rootDir).records);
2103
2499
  if (violations.length === 0) {
2104
- console.log(chalk5.green("\u2714") + " Standards: no banned dependencies in use");
2500
+ console.log(chalk7.green("\u2714") + " Standards: no banned dependencies in use");
2105
2501
  return;
2106
2502
  }
2107
- console.log(chalk5.red(`
2503
+ console.log(chalk7.red(`
2108
2504
  \u26A0 Standards: ${violations.length} banned dependency(ies) in use`));
2109
2505
  for (const v of violations) {
2110
- const fix = v.use ? chalk5.dim(` \u2192 use ${v.use}`) : "";
2111
- const why = v.reason ? chalk5.dim(` (${v.reason})`) : "";
2112
- console.log(` ${chalk5.red("banned")} ${v.ecosystem}:${v.name}${v.installed ? chalk5.dim(` ${v.installed}`) : ""}${fix}${why}`);
2506
+ const fix = v.use ? chalk7.dim(` \u2192 use ${v.use}`) : "";
2507
+ const why = v.reason ? chalk7.dim(` (${v.reason})`) : "";
2508
+ console.log(` ${chalk7.red("banned")} ${v.ecosystem}:${v.name}${v.installed ? chalk7.dim(` ${v.installed}`) : ""}${fix}${why}`);
2113
2509
  }
2114
2510
  }
2115
2511
  function parseNonNegativeNumber(value, label) {
@@ -2126,16 +2522,18 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2126
2522
  collectExcludes,
2127
2523
  []
2128
2524
  ).option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--full", "Comprehensive scan: turns on known-vulnerability detection (= --vulns) and, when a standards policy exists, a banned-dependency report \u2014 on top of drift scoring and the code map").option("--vulns", "Also scan installed dependencies for known vulnerabilities (OSV online, or advisories from --package-manifest when offline)").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--project-scan-timeout <seconds>", "Per-project scan timeout in seconds (default: 180)").option("--drift-budget <score>", "Fail if drift score is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").option("--repository-name <name>", "Override the repository name recorded for this scan (defaults to the directory or package.json name)").option("--force", "Always create a fresh scan ingest, even if the repository is unchanged since the last scan (skips the unchanged/reuse optimization). Used by scheduled and dashboard-triggered scans.").option("--no-graph", "Skip building the local code map (the AI/docs index) that scan produces after scoring drift").option("--quiet", "Suppress promotional output (the free-plan tracking panel and the AI Context install prompt); scan results are unaffected").action(async (targetPath, opts) => {
2129
- const rootDir = path9.resolve(targetPath);
2525
+ const rootDir = path.resolve(targetPath);
2130
2526
  if (!await pathExists$1(rootDir)) {
2131
- console.error(chalk5.red(`Path does not exist: ${rootDir}`));
2527
+ console.error(chalk7.red(`Path does not exist: ${rootDir}`));
2132
2528
  process.exit(1);
2133
2529
  }
2134
2530
  const hasDsn = !!resolveDsn(opts.dsn);
2135
2531
  const willPush = !opts.offline && (opts.push || hasDsn);
2136
2532
  const noLocalArtifacts = opts.localArtifacts === false;
2137
- const isFirstRun = !await pathExists$1(path9.join(rootDir, ".vibgrate", "scan_result.json"));
2533
+ const isFirstRun = !await pathExists$1(path.join(rootDir, ".vibgrate", "scan_result.json"));
2138
2534
  let pinnedRegion;
2535
+ let planTier;
2536
+ let upgradeUrl;
2139
2537
  if (willPush && hasDsn) {
2140
2538
  const dsn = resolveDsn(opts.dsn);
2141
2539
  const parsed = parseDsn(dsn);
@@ -2150,10 +2548,12 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2150
2548
  vcsSha: fingerprint.vcsSha
2151
2549
  });
2152
2550
  pinnedRegion = preflight.region;
2551
+ planTier = preflight.plan?.tier;
2552
+ upgradeUrl = preflight.upgradeUrl ?? `https://${dashHostForIngestHost(preflight.ingestHost ?? ingestHost)}/${parsed.workspaceId}`;
2153
2553
  if (preflight.vm && !preflight.vm.allowed) {
2154
- console.error(chalk5.red(preflight.error ?? "VM meter usage exhausted"));
2554
+ console.error(chalk7.red(preflight.error ?? "VM meter usage exhausted"));
2155
2555
  console.error(
2156
- chalk5.dim(
2556
+ chalk7.dim(
2157
2557
  `VM minutes: ${preflight.vm.used}/${preflight.vm.limit} (${preflight.plan.label} plan) \u2014 enable overages or upgrade your plan.`
2158
2558
  )
2159
2559
  );
@@ -2161,25 +2561,25 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2161
2561
  }
2162
2562
  if (preflight.repositories && !preflight.repositories.allowed) {
2163
2563
  console.error(
2164
- chalk5.red(
2564
+ chalk7.red(
2165
2565
  preflight.error ?? `Repository limit reached for the ${preflight.plan.label} plan \u2014 cannot scan a new repository.`
2166
2566
  )
2167
2567
  );
2168
2568
  const max = preflight.repositories.max < 0 ? "unlimited" : String(preflight.repositories.max);
2169
2569
  console.error(
2170
- chalk5.dim(
2570
+ chalk7.dim(
2171
2571
  `Repositories: ${preflight.repositories.total}/${max} (${preflight.plan.label} plan) \u2014 archive a repository or upgrade your plan.`
2172
2572
  )
2173
2573
  );
2174
2574
  if (preflight.upgradeUrl) {
2175
- console.error(chalk5.dim(` Upgrade: ${preflight.upgradeUrl}`));
2575
+ console.error(chalk7.dim(` Upgrade: ${preflight.upgradeUrl}`));
2176
2576
  }
2177
2577
  process.exit(1);
2178
2578
  }
2179
2579
  if (preflight.status === "error" || !preflight.scans.allowed) {
2180
- console.error(chalk5.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
2580
+ console.error(chalk7.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
2181
2581
  console.error(
2182
- chalk5.dim(
2582
+ chalk7.dim(
2183
2583
  `Credits: ${preflight.scans.used}/${preflight.scans.limit} (${preflight.plan.label} plan)`
2184
2584
  )
2185
2585
  );
@@ -2187,27 +2587,27 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2187
2587
  process.exit(1);
2188
2588
  }
2189
2589
  console.log(
2190
- chalk5.dim(
2590
+ chalk7.dim(
2191
2591
  `Plan: ${preflight.plan.label} \u2014 scan credits ${preflight.scans.used}/${preflight.scans.limit} this month`
2192
2592
  )
2193
2593
  );
2194
2594
  if (preflight.repository?.unchanged && !opts.force) {
2195
2595
  console.log(
2196
- chalk5.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
2596
+ chalk7.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
2197
2597
  );
2198
2598
  if (preflight.repository.lastIngestId) {
2199
2599
  emitIngestIdLine(preflight.repository.lastIngestId, { unchanged: true });
2200
2600
  const dashUrl = `https://${dashHostForIngestHost(ingestHost)}/${parsed.workspaceId}/scan/${preflight.repository.lastIngestId}`;
2201
- console.log(chalk5.dim(` Latest report: ${dashUrl}`));
2601
+ console.log(chalk7.dim(` Latest report: ${dashUrl}`));
2202
2602
  } else if (opts.strict) {
2203
- console.error(chalk5.red("Repository unchanged but no previous ingest id available."));
2603
+ console.error(chalk7.red("Repository unchanged but no previous ingest id available."));
2204
2604
  process.exit(1);
2205
2605
  }
2206
2606
  return;
2207
2607
  }
2208
2608
  } catch (e) {
2209
2609
  const msg = e instanceof Error ? e.message : String(e);
2210
- console.error(chalk5.yellow(`Preflight check failed: ${msg}`));
2610
+ console.error(chalk7.yellow(`Preflight check failed: ${msg}`));
2211
2611
  if (opts.strict) process.exit(1);
2212
2612
  }
2213
2613
  }
@@ -2240,6 +2640,17 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2240
2640
  repositoryName: opts.repositoryName?.trim() || void 0,
2241
2641
  force: opts.force,
2242
2642
  quiet: opts.quiet,
2643
+ // Auth + plan signals for the free-plan upsell panel. `hasDsn` resolves the
2644
+ // full credential precedence — `--dsn`, `VIBGRATE_DSN`, and the stored login
2645
+ // credential (`~/.vibgrate/credentials.json`) — which the scanner's own
2646
+ // dsn/env-only check cannot see. `planTier`/`upgradeUrl` come from preflight.
2647
+ // Together they pick the panel audience: signed out → login CTA; signed in
2648
+ // on free → upgrade CTA; signed in on a paid plan → no panel. Without the
2649
+ // auth signal, a logged-in user running a bare scan was mislabelled
2650
+ // "Vibgrate Free" and shown the login panel even as the run pushed.
2651
+ authenticated: hasDsn,
2652
+ planTier,
2653
+ upgradeUrl,
2243
2654
  // Prefix for the upsell panel's `login → push` hint — `vg` when installed,
2244
2655
  // `npx @vibgrate/cli` when the user ran via npx (where bare `vg` fails).
2245
2656
  invocation: resolveCliInvocation()
@@ -2270,24 +2681,24 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2270
2681
  const hasErrors = artifact.findings.some((f) => f.level === "error");
2271
2682
  const hasWarnings = artifact.findings.some((f) => f.level === "warning");
2272
2683
  if (opts.failOn === "error" && hasErrors) {
2273
- console.error(chalk5.red(`
2684
+ console.error(chalk7.red(`
2274
2685
  Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
2275
2686
  process.exit(2);
2276
2687
  }
2277
2688
  if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
2278
- console.error(chalk5.red(`
2689
+ console.error(chalk7.red(`
2279
2690
  Failing: findings detected at warn level or above.`));
2280
2691
  process.exit(2);
2281
2692
  }
2282
2693
  }
2283
2694
  if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
2284
- console.error(chalk5.red(`
2695
+ console.error(chalk7.red(`
2285
2696
  Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
2286
2697
  process.exit(2);
2287
2698
  }
2288
2699
  if (scanOpts.driftWorseningPercent !== void 0) {
2289
2700
  if (artifact.delta === void 0) {
2290
- console.error(chalk5.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
2701
+ console.error(chalk7.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
2291
2702
  process.exit(2);
2292
2703
  }
2293
2704
  if (artifact.delta > 0) {
@@ -2295,7 +2706,7 @@ Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget
2295
2706
  const denominator = Math.max(Math.abs(baselineScore), 1e-4);
2296
2707
  const worseningPercent = artifact.delta / denominator * 100;
2297
2708
  if (worseningPercent > scanOpts.driftWorseningPercent) {
2298
- console.error(chalk5.red(`
2709
+ console.error(chalk7.red(`
2299
2710
  Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (threshold ${scanOpts.driftWorseningPercent}%).`));
2300
2711
  process.exit(2);
2301
2712
  }
@@ -2310,22 +2721,853 @@ Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (thr
2310
2721
  printAiContextPrompt(detectedAssistant);
2311
2722
  }
2312
2723
  });
2724
+ var WATCHED_FILES = /* @__PURE__ */ new Set([
2725
+ // npm
2726
+ "package.json",
2727
+ "package-lock.json",
2728
+ "npm-shrinkwrap.json",
2729
+ "pnpm-lock.yaml",
2730
+ "yarn.lock",
2731
+ "bun.lockb",
2732
+ // pypi
2733
+ "requirements.txt",
2734
+ "pyproject.toml",
2735
+ "poetry.lock",
2736
+ "Pipfile",
2737
+ "Pipfile.lock",
2738
+ "uv.lock",
2739
+ // go
2740
+ "go.mod",
2741
+ "go.sum",
2742
+ // rust
2743
+ "Cargo.toml",
2744
+ "Cargo.lock",
2745
+ // ruby
2746
+ "Gemfile",
2747
+ "Gemfile.lock",
2748
+ // php
2749
+ "composer.json",
2750
+ "composer.lock",
2751
+ // swift
2752
+ "Package.swift",
2753
+ "Package.resolved",
2754
+ // dart
2755
+ "pubspec.yaml",
2756
+ "pubspec.lock",
2757
+ // java
2758
+ "pom.xml",
2759
+ "build.gradle",
2760
+ "build.gradle.kts",
2761
+ "gradle.lockfile",
2762
+ // dotnet
2763
+ "packages.lock.json"
2764
+ ]);
2765
+ var WATCHED_EXT = /\.(cs|fs)proj$/i;
2766
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
2767
+ "node_modules",
2768
+ ".git",
2769
+ "dist",
2770
+ "build",
2771
+ "out",
2772
+ ".vibgrate",
2773
+ "vendor",
2774
+ ".venv",
2775
+ "venv",
2776
+ "env",
2777
+ "__pycache__",
2778
+ "target",
2779
+ ".next",
2780
+ ".nuxt",
2781
+ "coverage",
2782
+ ".cache"
2783
+ ]);
2784
+ function scanStaleness(rootDir, artifactPath) {
2785
+ let ref;
2786
+ try {
2787
+ ref = fs5.statSync(artifactPath).mtimeMs;
2788
+ } catch {
2789
+ return { stale: false };
2790
+ }
2791
+ let newest = -Infinity;
2792
+ let newestChanged;
2793
+ const MAX_ENTRIES = 2e4;
2794
+ let scanned = 0;
2795
+ const walk2 = (dir, depth) => {
2796
+ if (depth > 8 || scanned > MAX_ENTRIES) return;
2797
+ let entries;
2798
+ try {
2799
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
2800
+ } catch {
2801
+ return;
2802
+ }
2803
+ for (const e of entries) {
2804
+ scanned++;
2805
+ if (scanned > MAX_ENTRIES) break;
2806
+ if (e.isDirectory()) {
2807
+ if (!SKIP_DIRS.has(e.name) && !e.name.startsWith(".")) walk2(path.join(dir, e.name), depth + 1);
2808
+ continue;
2809
+ }
2810
+ if (!WATCHED_FILES.has(e.name) && !WATCHED_EXT.test(e.name)) continue;
2811
+ const abs = path.join(dir, e.name);
2812
+ let mtimeMs;
2813
+ try {
2814
+ mtimeMs = fs5.statSync(abs).mtimeMs;
2815
+ } catch {
2816
+ continue;
2817
+ }
2818
+ if (mtimeMs > newest) {
2819
+ newest = mtimeMs;
2820
+ newestChanged = path.relative(rootDir, abs) || e.name;
2821
+ }
2822
+ }
2823
+ };
2824
+ walk2(rootDir, 0);
2825
+ return newest > ref ? { stale: true, newestChanged } : { stale: false };
2826
+ }
2827
+ var MAX_CONTRACTS = 40;
2828
+ function escapeRegExp(s) {
2829
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2830
+ }
2831
+ function parseJsClause(clause, out2) {
2832
+ const trimmed = clause.trim();
2833
+ if (!trimmed) return;
2834
+ if (/^\*\s+as\s+\w+/.test(trimmed)) {
2835
+ out2.add("* (namespace)");
2836
+ return;
2837
+ }
2838
+ const braceMatch = /\{([^}]*)\}/.exec(trimmed);
2839
+ const beforeBrace = braceMatch ? trimmed.slice(0, braceMatch.index) : trimmed;
2840
+ const defaultId = beforeBrace.replace(/,/g, "").trim();
2841
+ if (defaultId && /^\w+$/.test(defaultId)) out2.add("default");
2842
+ if (braceMatch) {
2843
+ for (const raw of braceMatch[1].split(",")) {
2844
+ const name = raw.trim().split(/\s+as\s+/)[0]?.trim();
2845
+ if (name && /^[\w$]+$/.test(name)) out2.add(name);
2846
+ }
2847
+ }
2848
+ }
2849
+ function extractContracts(content, pkg, ecosystem) {
2850
+ const out2 = /* @__PURE__ */ new Set();
2851
+ const e = escapeRegExp(pkg);
2852
+ if (ecosystem === "npm") {
2853
+ const importRe = new RegExp(`import\\s+([^;'"\\n]*?)\\s+from\\s+['"]${e}(?:/[^'"]*)?['"]`, "g");
2854
+ let m;
2855
+ while (m = importRe.exec(content)) parseJsClause(m[1], out2);
2856
+ const requireDestructureRe = new RegExp(`\\{([^}]*)\\}\\s*=\\s*require\\(\\s*['"]${e}(?:/[^'"]*)?['"]\\s*\\)`, "g");
2857
+ while (m = requireDestructureRe.exec(content)) {
2858
+ for (const raw of m[1].split(",")) {
2859
+ const name = raw.trim().split(":")[0]?.trim();
2860
+ if (name && /^[\w$]+$/.test(name)) out2.add(name);
2861
+ }
2862
+ }
2863
+ } else if (ecosystem === "pypi") {
2864
+ const fromRe = new RegExp(`^\\s*from\\s+${e}(?:\\.[\\w.]+)?\\s+import\\s+(.+)$`, "gm");
2865
+ let m;
2866
+ while (m = fromRe.exec(content)) {
2867
+ const names = m[1].replace(/[()]/g, "").split(",");
2868
+ for (const raw of names) {
2869
+ const name = raw.trim().split(/\s+as\s+/)[0]?.trim();
2870
+ if (name === "*") out2.add("* (module)");
2871
+ else if (name && /^[\w.]+$/.test(name)) out2.add(name);
2872
+ }
2873
+ }
2874
+ const importRe = new RegExp(`^\\s*import\\s+${e}\\b`, "gm");
2875
+ if (importRe.test(content)) out2.add(`${pkg} (module)`);
2876
+ }
2877
+ return [...out2].sort();
2878
+ }
2879
+
2880
+ // src/reporting/planning/usage.ts
2881
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
2882
+ "node_modules",
2883
+ ".git",
2884
+ "dist",
2885
+ "build",
2886
+ "out",
2887
+ ".vibgrate",
2888
+ "vendor",
2889
+ ".venv",
2890
+ "venv",
2891
+ "env",
2892
+ "__pycache__",
2893
+ "target",
2894
+ ".next",
2895
+ ".nuxt",
2896
+ "coverage",
2897
+ ".cache"
2898
+ ]);
2899
+ var MAX_FILES = 8e3;
2900
+ var MAX_DEPTH = 12;
2901
+ var MAX_FILE_BYTES = 1048576;
2902
+ var EXT_ECOSYSTEM = {
2903
+ ".ts": "npm",
2904
+ ".tsx": "npm",
2905
+ ".js": "npm",
2906
+ ".jsx": "npm",
2907
+ ".mjs": "npm",
2908
+ ".cjs": "npm",
2909
+ ".mts": "npm",
2910
+ ".cts": "npm",
2911
+ ".vue": "npm",
2912
+ ".svelte": "npm",
2913
+ ".py": "pypi"
2914
+ };
2915
+ function escapeRegExp2(s) {
2916
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2917
+ }
2918
+ function importPattern(ecosystem, pkg) {
2919
+ const e = escapeRegExp2(pkg);
2920
+ if (ecosystem === "npm") {
2921
+ return new RegExp(`(?:from\\s+|require\\(\\s*|import\\(\\s*)['"]${e}(?:/[^'"]*)?['"]`, "g");
2922
+ }
2923
+ return new RegExp(`^\\s*(?:import\\s+${e}(?:[.\\s]|$)|from\\s+${e}(?:[.\\s]))`, "gm");
2924
+ }
2925
+ function analyzeTree(root, packages) {
2926
+ const result = /* @__PURE__ */ new Map();
2927
+ const tracked = [];
2928
+ for (const p of packages) {
2929
+ if (p.ecosystem === "npm" || p.ecosystem === "pypi") {
2930
+ tracked.push({
2931
+ name: p.name,
2932
+ ecosystem: p.ecosystem,
2933
+ pattern: importPattern(p.ecosystem, p.name),
2934
+ importSites: 0,
2935
+ filesTouched: 0,
2936
+ contracts: /* @__PURE__ */ new Set()
2937
+ });
2938
+ } else {
2939
+ result.set(p.name, { importSites: 0, filesTouched: 0, contracts: [] });
2940
+ }
2941
+ }
2942
+ if (tracked.length === 0) return result;
2943
+ let filesSeen = 0;
2944
+ const walk2 = (dir, depth) => {
2945
+ if (depth > MAX_DEPTH || filesSeen >= MAX_FILES) return;
2946
+ let entries;
2947
+ try {
2948
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
2949
+ } catch {
2950
+ return;
2951
+ }
2952
+ entries.sort((a, b) => a.name.localeCompare(b.name));
2953
+ for (const entry of entries) {
2954
+ if (filesSeen >= MAX_FILES) return;
2955
+ if (entry.isDirectory()) {
2956
+ if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
2957
+ walk2(path.join(dir, entry.name), depth + 1);
2958
+ continue;
2959
+ }
2960
+ const fileEco = EXT_ECOSYSTEM[path.extname(entry.name)];
2961
+ if (!fileEco || fileEco === "unknown") continue;
2962
+ filesSeen++;
2963
+ const full = path.join(dir, entry.name);
2964
+ let content;
2965
+ try {
2966
+ if (fs5.statSync(full).size > MAX_FILE_BYTES) continue;
2967
+ content = fs5.readFileSync(full, "utf8");
2968
+ } catch {
2969
+ continue;
2970
+ }
2971
+ for (const t of tracked) {
2972
+ if (t.ecosystem !== fileEco) continue;
2973
+ t.pattern.lastIndex = 0;
2974
+ const matches = content.match(t.pattern);
2975
+ if (matches && matches.length) {
2976
+ t.importSites += matches.length;
2977
+ t.filesTouched++;
2978
+ if (t.contracts.size < MAX_CONTRACTS) {
2979
+ for (const sym of extractContracts(content, t.name, t.ecosystem)) t.contracts.add(sym);
2980
+ }
2981
+ }
2982
+ }
2983
+ }
2984
+ };
2985
+ walk2(root, 0);
2986
+ for (const t of tracked) {
2987
+ result.set(t.name, {
2988
+ importSites: t.importSites,
2989
+ filesTouched: t.filesTouched,
2990
+ contracts: [...t.contracts].sort().slice(0, MAX_CONTRACTS)
2991
+ });
2992
+ }
2993
+ return result;
2994
+ }
2995
+
2996
+ // src/reporting/utils/fix-plan.ts
2997
+ function postOnce(input, host) {
2998
+ return fetch(`${input.scheme}://${host}/v1/fix/plan`, {
2999
+ method: "POST",
3000
+ headers: {
3001
+ "Content-Type": "application/json",
3002
+ "X-Vibgrate-Timestamp": input.timestamp,
3003
+ Authorization: `VibgrateDSN ${input.keyId}:${input.secret}`,
3004
+ Connection: "close"
3005
+ },
3006
+ body: JSON.stringify(input.request)
3007
+ });
3008
+ }
3009
+ async function regionRedirectHost(response) {
3010
+ try {
3011
+ const payload = await response.clone().json();
3012
+ if (payload?.code !== "REGION_MISMATCH" || !payload.region) return void 0;
3013
+ return ingestHostForRegionId(payload.region);
3014
+ } catch {
3015
+ return void 0;
3016
+ }
3017
+ }
3018
+ async function requestFixPlan(input) {
3019
+ let host = input.host;
3020
+ let response = await postOnce(input, host);
3021
+ if (response.status === 409) {
3022
+ const target = await regionRedirectHost(response);
3023
+ if (target && target !== host) {
3024
+ host = target;
3025
+ response = await postOnce(input, host);
3026
+ }
3027
+ }
3028
+ return { response, host };
3029
+ }
3030
+ async function parseFixPlanResponse(response) {
3031
+ return await response.json();
3032
+ }
3033
+ var SEVERITY_ORDER = ["critical", "high", "moderate", "low", "unknown"];
3034
+ function deltaSummary(delta) {
3035
+ if (delta.total === 0) return "none";
3036
+ const parts = SEVERITY_ORDER.filter((s) => delta.bySeverity[s]).map((s) => `${delta.bySeverity[s]} ${s}`);
3037
+ return parts.length ? parts.join(", ") : `${delta.total}`;
3038
+ }
3039
+ function riskLabel(score) {
3040
+ if (score <= 15) return chalk7.green(`${score}/100 low`);
3041
+ if (score <= 40) return chalk7.yellow(`${score}/100 moderate`);
3042
+ return chalk7.red(`${score}/100 high`);
3043
+ }
3044
+ function planBlock(plan, recommended) {
3045
+ const lines = [];
3046
+ const marker = recommended ? chalk7.green("\u25CF recommended") : chalk7.dim("\u25CB");
3047
+ lines.push(`${marker} ${chalk7.bold(plan.label)} \u2014 ${chalk7.dim(plan.description)}`);
3048
+ const drift = typeof plan.expectedDriftScore === "number" && typeof plan.driftDelta === "number" ? ` \xB7 DriftScore \u2192${plan.expectedDriftScore} (${plan.driftDelta <= 0 ? "" : "+"}${plan.driftDelta})` : "";
3049
+ lines.push(
3050
+ ` risk ${riskLabel(plan.riskScore)} \xB7 ${plan.upgrades.length} upgrade(s) \xB7 fixes ${deltaSummary(plan.fixes)}${drift}`
3051
+ );
3052
+ if (plan.introduces.total) {
3053
+ lines.push(chalk7.red(` \u26A0 introduces ${deltaSummary(plan.introduces)} advisory(ies) in target versions`));
3054
+ }
3055
+ const shown = plan.upgrades.slice(0, 12);
3056
+ for (const u of shown) {
3057
+ const codemod = u.playbook?.codemod ? chalk7.magenta(` [codemod: ${u.playbook.codemod}]`) : "";
3058
+ lines.push(` ${chalk7.cyan(u.package)} ${chalk7.dim(`${u.from ?? "?"} \u2192 ${u.to ?? "?"}`)} ${chalk7.dim(u.reason)}${codemod}`);
3059
+ }
3060
+ if (plan.upgrades.length > shown.length) {
3061
+ lines.push(chalk7.dim(` \u2026 and ${plan.upgrades.length - shown.length} more`));
3062
+ }
3063
+ if (plan.upgrades.length === 0) {
3064
+ lines.push(chalk7.dim(" (no upgrades in this plan)"));
3065
+ }
3066
+ return lines.join("\n");
3067
+ }
3068
+ function dataNote(report) {
3069
+ if (report.vulnerabilityData === "unavailable") {
3070
+ return chalk7.dim("Advisory data was unavailable \u2014 vulnerability impact is not shown.");
3071
+ }
3072
+ if (report.vulnerabilityData === "partial") {
3073
+ return chalk7.dim("Advisory data was partial \u2014 some ecosystems were not checked.");
3074
+ }
3075
+ return null;
3076
+ }
3077
+ function renderText(report) {
3078
+ const out2 = [];
3079
+ out2.push(chalk7.bold("Vibgrate fix \u2014 upgrade plan"));
3080
+ out2.push(
3081
+ chalk7.dim(
3082
+ `${report.totalCandidates} drifted dependency(ies) analysed \xB7 ${report.deepAnalysis ? "deep (major) analysis on" : "preflight only"}`
3083
+ )
3084
+ );
3085
+ const note = dataNote(report);
3086
+ if (note) out2.push(note);
3087
+ if (report.exploitability && report.exploitability.kevPackages > 0) {
3088
+ const epss = report.exploitability.maxEpss != null ? `, peak EPSS ${(report.exploitability.maxEpss * 100).toFixed(0)}%` : "";
3089
+ out2.push(chalk7.red(`\u26A0 ${report.exploitability.kevPackages} package(s) carry a KNOWN-EXPLOITED (KEV) advisory${epss} \u2014 prioritise these.`));
3090
+ }
3091
+ out2.push("");
3092
+ for (const plan of report.plans) {
3093
+ out2.push(planBlock(plan, plan.tier === report.recommended));
3094
+ out2.push("");
3095
+ }
3096
+ out2.push(chalk7.bold("Recommendation"));
3097
+ out2.push(` ${chalk7.green(report.plans.find((p) => p.tier === report.recommended)?.label ?? report.recommended)} \u2014 ${report.rationale}`);
3098
+ if (report.unresolved.total) {
3099
+ out2.push("");
3100
+ out2.push(chalk7.red(`\u26A0 ${deltaSummary(report.unresolved)} advisory(ies) have no upgrade path in any plan.`));
3101
+ }
3102
+ return out2.join("\n");
3103
+ }
3104
+ function renderMarkdown(report) {
3105
+ const out2 = [];
3106
+ out2.push("# Vibgrate fix \u2014 upgrade plan");
3107
+ out2.push("");
3108
+ out2.push(
3109
+ `${report.totalCandidates} drifted dependency(ies) analysed \xB7 ${report.deepAnalysis ? "deep (major) analysis on" : "preflight only"}.`
3110
+ );
3111
+ const note = dataNote(report);
3112
+ if (note) {
3113
+ out2.push("");
3114
+ out2.push(`_${report.vulnerabilityData === "unavailable" ? "Advisory data was unavailable \u2014 vulnerability impact is not shown." : "Advisory data was partial \u2014 some ecosystems were not checked."}_`);
3115
+ }
3116
+ out2.push("");
3117
+ for (const plan of report.plans) {
3118
+ const rec = plan.tier === report.recommended ? " \u2705 **recommended**" : "";
3119
+ out2.push(`## ${plan.label}${rec}`);
3120
+ out2.push("");
3121
+ out2.push(`_${plan.description}_`);
3122
+ out2.push("");
3123
+ out2.push(`- Risk: **${plan.riskScore}/100** (${plan.confidence} confidence)`);
3124
+ out2.push(`- Upgrades: **${plan.upgrades.length}**`);
3125
+ out2.push(`- Fixes advisories: ${deltaSummary(plan.fixes)}`);
3126
+ if (plan.introduces.total) {
3127
+ out2.push(`- \u26A0 Introduces advisories in target versions: ${deltaSummary(plan.introduces)}`);
3128
+ }
3129
+ out2.push("");
3130
+ if (plan.upgrades.length) {
3131
+ out2.push("| Package | From | To | Kind | Reason |");
3132
+ out2.push("|---|---|---|---|---|");
3133
+ for (const u of plan.upgrades) {
3134
+ out2.push(`| \`${u.package}\` | ${u.from ?? "?"} | ${u.to ?? "?"} | ${u.kind} | ${u.reason} |`);
3135
+ }
3136
+ out2.push("");
3137
+ }
3138
+ }
3139
+ out2.push("## Recommendation");
3140
+ out2.push("");
3141
+ const recLabel = report.plans.find((p) => p.tier === report.recommended)?.label ?? report.recommended;
3142
+ out2.push(`**${recLabel}** \u2014 ${report.rationale}`);
3143
+ if (report.unresolved.total) {
3144
+ out2.push("");
3145
+ out2.push(`> \u26A0 ${deltaSummary(report.unresolved)} advisory(ies) have no upgrade path in any plan.`);
3146
+ }
3147
+ return out2.join("\n");
3148
+ }
3149
+
3150
+ // src/reporting/planning/expected-drift.ts
3151
+ function estimateDriftScore(artifact, upgraded) {
3152
+ const projects = JSON.parse(JSON.stringify(artifact.projects ?? []));
3153
+ for (const p of projects) {
3154
+ const buckets = p.dependencyAgeBuckets;
3155
+ for (const dep of p.dependencies ?? []) {
3156
+ if (!upgraded.has(dep.package)) continue;
3157
+ const mb3 = dep.majorsBehind ?? 0;
3158
+ if (mb3 >= 2 && buckets.twoPlusBehind > 0) {
3159
+ buckets.twoPlusBehind--;
3160
+ buckets.current++;
3161
+ } else if (mb3 === 1 && buckets.oneBehind > 0) {
3162
+ buckets.oneBehind--;
3163
+ buckets.current++;
3164
+ }
3165
+ }
3166
+ for (const fw of p.frameworks ?? []) {
3167
+ if (upgraded.has(fw.name)) fw.majorsBehind = 0;
3168
+ }
3169
+ }
3170
+ return computeDriftScore(projects).score;
3171
+ }
3172
+ function pmCommandFor(ecosystem, pkg, version, pm = "npm") {
3173
+ switch (ecosystem) {
3174
+ case "npm":
3175
+ if (pm === "pnpm") return { cmd: "pnpm", args: ["add", `${pkg}@${version}`] };
3176
+ if (pm === "yarn") return { cmd: "yarn", args: ["add", `${pkg}@${version}`] };
3177
+ if (pm === "bun") return { cmd: "bun", args: ["add", `${pkg}@${version}`] };
3178
+ return { cmd: "npm", args: ["install", `${pkg}@${version}`] };
3179
+ case "pypi":
3180
+ return { cmd: "pip", args: ["install", `${pkg}==${version}`] };
3181
+ case "cargo":
3182
+ return { cmd: "cargo", args: ["add", `${pkg}@${version}`] };
3183
+ case "go":
3184
+ return { cmd: "go", args: ["get", `${pkg}@${version.startsWith("v") ? version : `v${version}`}`] };
3185
+ case "composer":
3186
+ return { cmd: "composer", args: ["require", `${pkg}:${version}`] };
3187
+ case "nuget":
3188
+ return { cmd: "dotnet", args: ["add", "package", pkg, "--version", version] };
3189
+ case "dotnet":
3190
+ return { cmd: "dotnet", args: ["add", "package", pkg, "--version", version] };
3191
+ case "dart":
3192
+ case "pub":
3193
+ return { cmd: "dart", args: ["pub", "add", `${pkg}:${version}`] };
3194
+ case "rubygems":
3195
+ case "ruby":
3196
+ return { cmd: "bundle", args: ["update", pkg] };
3197
+ default:
3198
+ return null;
3199
+ }
3200
+ }
3201
+ function defaultRun(cmd, cwd) {
3202
+ const res = spawnSync(cmd.cmd, cmd.args, { cwd, stdio: "inherit", shell: false });
3203
+ if (res.error) return { ok: false, detail: res.error.message };
3204
+ if (res.status !== 0) return { ok: false, detail: `exit ${res.status ?? "signal"}` };
3205
+ return { ok: true };
3206
+ }
3207
+ function applyPlan(rootDir, upgrades, opts = {}) {
3208
+ const run = opts.run ?? defaultRun;
3209
+ const results = [];
3210
+ for (const u of upgrades) {
3211
+ if (!u.to) {
3212
+ results.push({ package: u.package, to: u.to, status: "skipped", detail: "no target version" });
3213
+ continue;
3214
+ }
3215
+ const command = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager);
3216
+ if (!command) {
3217
+ results.push({ package: u.package, to: u.to, status: "manual", detail: `${u.ecosystem}: update the manifest to ${u.to} manually` });
3218
+ continue;
3219
+ }
3220
+ if (opts.dryRun) {
3221
+ results.push({ package: u.package, to: u.to, status: "skipped", detail: `would run: ${command.cmd} ${command.args.join(" ")}` });
3222
+ continue;
3223
+ }
3224
+ const outcome = run(command, rootDir);
3225
+ results.push({
3226
+ package: u.package,
3227
+ to: u.to,
3228
+ status: outcome.ok ? "applied" : "failed",
3229
+ detail: outcome.detail
3230
+ });
3231
+ }
3232
+ return results;
3233
+ }
3234
+
3235
+ // src/reporting/commands/fix.ts
3236
+ var SEVERITY_RANK = { unknown: 0, low: 1, moderate: 2, high: 3, critical: 4 };
3237
+ function ecosystemId(type) {
3238
+ return projectTypeToVulnEcosystem(type) ?? type;
3239
+ }
3240
+ function sourceEcosystem(ecosystem) {
3241
+ if (ecosystem === "npm") return "npm";
3242
+ if (ecosystem === "pypi") return "pypi";
3243
+ return "unknown";
3244
+ }
3245
+ function collectCandidates(artifact) {
3246
+ const seen = /* @__PURE__ */ new Set();
3247
+ const out2 = [];
3248
+ for (const project of artifact.projects ?? []) {
3249
+ const ecosystem = ecosystemId(project.type);
3250
+ for (const dep of project.dependencies ?? []) {
3251
+ const key = `${ecosystem}\0${dep.package}`;
3252
+ if (seen.has(key)) continue;
3253
+ if (!dep.latestStable || dep.latestStable === dep.resolvedVersion) continue;
3254
+ if (dep.drift === "current" || dep.drift === "unknown") continue;
3255
+ seen.add(key);
3256
+ out2.push({
3257
+ package: dep.package,
3258
+ ecosystem,
3259
+ source: sourceEcosystem(ecosystem),
3260
+ from: dep.resolvedVersion,
3261
+ to: dep.latestStable,
3262
+ majorsBehind: dep.majorsBehind,
3263
+ section: dep.section
3264
+ });
3265
+ }
3266
+ }
3267
+ out2.sort((a, b) => a.ecosystem.localeCompare(b.ecosystem) || a.package.localeCompare(b.package));
3268
+ return out2;
3269
+ }
3270
+ var defaultFixScanner = async (rootDir) => {
3271
+ const scratch = path.join(os.tmpdir(), `vg-fix-scan-${process.pid}.json`);
3272
+ const scanOpts = { vibgrateVersion: VERSION, format: "json", out: scratch, quiet: true, concurrency: 8 };
3273
+ const advanced = await loadAdvancedScanHook();
3274
+ return runCoreScan(rootDir, scanOpts, advanced);
3275
+ };
3276
+ async function loadArtifact(rootDir, inFile, scan = defaultFixScanner) {
3277
+ const artifactPath = path.isAbsolute(inFile) ? inFile : path.join(rootDir, inFile);
3278
+ if (await pathExists(artifactPath)) {
3279
+ const staleness = scanStaleness(rootDir, artifactPath);
3280
+ if (!staleness.stale) {
3281
+ return readJsonFile(artifactPath);
3282
+ }
3283
+ console.error(
3284
+ chalk7.dim(
3285
+ `Scan is out of date (${staleness.newestChanged ?? "a dependency manifest"} changed since the last scan) \u2014 re-running a drift scan first (code map skipped)\u2026`
3286
+ )
3287
+ );
3288
+ } else {
3289
+ console.error(chalk7.dim("No scan found \u2014 running a drift scan first (code map skipped)\u2026"));
3290
+ }
3291
+ return scan(rootDir);
3292
+ }
3293
+ async function repositoryIdentity(rootDir, override) {
3294
+ try {
3295
+ const vcs = await detectVcs(rootDir);
3296
+ const fingerprint = await computeRepoFingerprint(rootDir, vcs);
3297
+ const name = override?.trim() || await resolveRepositoryName(rootDir);
3298
+ return { name, vcsSha: fingerprint.vcsSha };
3299
+ } catch {
3300
+ return { name: override?.trim() || void 0 };
3301
+ }
3302
+ }
3303
+ var fixCommand = new Command("fix").description("Analyse drift and get ranked, risk-tiered upgrade plans from the hosted planner (requires login/DSN; read-only \u2014 never edits your project)").argument("[path]", "Path to analyse", ".").option("--in <file>", "Scan artifact to read", ".vibgrate/scan_result.json").option("--format <format>", "Output format (text|json|md)", "text").option("--dsn <dsn>", 'DSN token (or use VIBGRATE_DSN env / "vg login")').option("--region <region>", "Override data residency region (us, eu)").option("--repository-name <name>", "Override the repository name recorded for this plan").option("--plan <tier>", "Apply a specific plan non-interactively (safe|balanced|aggressive)").option("--yes", "Apply the recommended plan without prompting").option("--dry-run", "Show what would change without applying").option("--no-apply", "Only print the plans; never modify the project").option("--fail-on-vulns <severity>", "Exit non-zero if the recommended plan leaves an advisory at/above this severity unresolved (low|moderate|high|critical)").action(async (targetPath, opts) => {
3304
+ const rootDir = path.resolve(targetPath);
3305
+ if (!await pathExists(rootDir)) {
3306
+ console.error(chalk7.red(`Path does not exist: ${rootDir}`));
3307
+ process.exit(1);
3308
+ }
3309
+ const failOn = opts.failOnVulns;
3310
+ if (failOn && !(failOn in SEVERITY_RANK)) {
3311
+ console.error(chalk7.red(`Invalid --fail-on-vulns value '${opts.failOnVulns}'. Use one of: low, moderate, high, critical.`));
3312
+ process.exit(1);
3313
+ }
3314
+ const dsn = resolveDsn(opts.dsn);
3315
+ if (!dsn) {
3316
+ const cli = resolveCliInvocation();
3317
+ console.error(chalk7.red("vg fix needs a Vibgrate login."));
3318
+ console.error(
3319
+ chalk7.dim(
3320
+ `Run "${cli} login" (or set VIBGRATE_DSN / pass --dsn) to analyse upgrades with the hosted planner. See https://vibgrate.com/cli for details.`
3321
+ )
3322
+ );
3323
+ process.exit(1);
3324
+ }
3325
+ const parsed = parseDsn(dsn);
3326
+ if (!parsed) {
3327
+ console.error(chalk7.red('Invalid DSN format. Re-run "vg login" or check VIBGRATE_DSN.'));
3328
+ process.exit(1);
3329
+ }
3330
+ let host;
3331
+ try {
3332
+ host = opts.region ? resolveIngestHost(opts.region) : parsed.host;
3333
+ } catch (e) {
3334
+ console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
3335
+ process.exit(1);
3336
+ }
3337
+ let artifact;
3338
+ try {
3339
+ artifact = await loadArtifact(rootDir, opts.in);
3340
+ } catch (e) {
3341
+ console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
3342
+ process.exit(1);
3343
+ }
3344
+ const raw = collectCandidates(artifact);
3345
+ const usage = analyzeTree(
3346
+ rootDir,
3347
+ raw.filter((c2) => c2.source !== "unknown").map((c2) => ({ name: c2.package, ecosystem: c2.source }))
3348
+ );
3349
+ const candidates = raw.map((c2) => {
3350
+ const u = usage.get(c2.package);
3351
+ return {
3352
+ package: c2.package,
3353
+ ecosystem: c2.ecosystem,
3354
+ currentVersion: c2.from,
3355
+ latestVersion: c2.to,
3356
+ majorsBehind: c2.majorsBehind,
3357
+ section: c2.section,
3358
+ ...u && (u.importSites > 0 || u.filesTouched > 0) ? { usage: { importSites: u.importSites, filesTouched: u.filesTouched } } : {},
3359
+ ...u && u.contracts.length ? { contracts: u.contracts } : {}
3360
+ };
3361
+ });
3362
+ const request = {
3363
+ cliVersion: VERSION,
3364
+ repository: await repositoryIdentity(rootDir, opts.repositoryName),
3365
+ candidates
3366
+ };
3367
+ let response;
3368
+ try {
3369
+ const { response: httpResponse } = await requestFixPlan({
3370
+ scheme: parsed.scheme,
3371
+ host,
3372
+ keyId: parsed.keyId,
3373
+ secret: parsed.secret,
3374
+ request,
3375
+ timestamp: String(Date.now())
3376
+ });
3377
+ if (!httpResponse.ok) {
3378
+ handleHttpError(httpResponse, parsed.workspaceId, host);
3379
+ return;
3380
+ }
3381
+ response = await parseFixPlanResponse(httpResponse);
3382
+ } catch (e) {
3383
+ const msg = e instanceof Error ? e.message : String(e);
3384
+ console.error(chalk7.red(`Could not reach the upgrade planner: ${msg}`));
3385
+ console.error(chalk7.dim("Check your connection and try again, or see https://vibgrate.com/help."));
3386
+ process.exit(1);
3387
+ }
3388
+ if (response.status === "error") {
3389
+ console.error(chalk7.red(response.error ?? "The planner returned an error."));
3390
+ if (response.requestId) console.error(chalk7.dim(` (ref ${response.requestId})`));
3391
+ process.exit(1);
3392
+ }
3393
+ const currentDrift = artifact.drift?.score;
3394
+ if (typeof currentDrift === "number") {
3395
+ response.currentDriftScore = currentDrift;
3396
+ for (const plan of response.plans) {
3397
+ const upgraded = new Set(plan.upgrades.map((u) => u.package));
3398
+ const expected = estimateDriftScore(artifact, upgraded);
3399
+ plan.expectedDriftScore = expected;
3400
+ plan.driftDelta = expected - currentDrift;
3401
+ }
3402
+ }
3403
+ emit(response, opts.format);
3404
+ if (opts.format === "text" && opts.apply !== false) {
3405
+ await runApplyFlow(rootDir, artifact, response, opts);
3406
+ }
3407
+ if (failOn) {
3408
+ const recommended = response.plans.find((p) => p.tier === response.recommended);
3409
+ const threshold = SEVERITY_RANK[failOn];
3410
+ let stillOpen = 0;
3411
+ for (const sev of Object.keys(SEVERITY_RANK)) {
3412
+ if (SEVERITY_RANK[sev] < threshold) continue;
3413
+ const open = response.unresolved.bySeverity[sev] ?? 0;
3414
+ const notFixedByRecommended = (response.plans.find((p) => p.tier === "aggressive")?.fixes.bySeverity[sev] ?? 0) - (recommended?.fixes.bySeverity[sev] ?? 0);
3415
+ stillOpen += open + Math.max(0, notFixedByRecommended);
3416
+ }
3417
+ if (stillOpen > 0) {
3418
+ console.error(
3419
+ chalk7.red(`
3420
+ Failing: the recommended plan leaves ${stillOpen} advisory(ies) at/above ${failOn} unresolved.`)
3421
+ );
3422
+ process.exit(2);
3423
+ }
3424
+ }
3425
+ });
3426
+ function handleHttpError(response, workspaceId, host) {
3427
+ const upgradeUrl = `https://${dashHostForIngestHost(host)}/${workspaceId}`;
3428
+ if (response.status === 401 || response.status === 403) {
3429
+ console.error(chalk7.red("Not authorised. Your DSN may be invalid or lack access to this workspace."));
3430
+ console.error(chalk7.dim('Re-run "vg login" or check VIBGRATE_DSN.'));
3431
+ process.exit(1);
3432
+ }
3433
+ if (response.status === 402) {
3434
+ console.error(chalk7.red("vg fix is a paid capability that is not enabled on your current plan."));
3435
+ console.error(chalk7.dim(`Upgrade to enable the hosted upgrade planner: ${upgradeUrl}`));
3436
+ process.exit(1);
3437
+ }
3438
+ if (response.status === 429) {
3439
+ console.error(chalk7.red("Rate limited by the planner. Wait a moment and try again."));
3440
+ process.exit(1);
3441
+ }
3442
+ console.error(chalk7.red(`The planner returned HTTP ${response.status}.`));
3443
+ console.error(chalk7.dim("Try again shortly, or see https://vibgrate.com/help."));
3444
+ process.exit(1);
3445
+ }
3446
+ function emit(report, format) {
3447
+ switch (format) {
3448
+ case "json":
3449
+ console.log(JSON.stringify(report, null, 2));
3450
+ break;
3451
+ case "md":
3452
+ console.log(renderMarkdown(report));
3453
+ break;
3454
+ case "text":
3455
+ default:
3456
+ console.log(renderText(report));
3457
+ break;
3458
+ }
3459
+ }
3460
+ function npmPackageManager(artifact) {
3461
+ for (const p of artifact.projects ?? []) {
3462
+ if ((p.type === "node" || p.type === "typescript") && p.packageManager) {
3463
+ const pm = p.packageManager;
3464
+ if (pm === "pnpm" || pm === "yarn" || pm === "bun" || pm === "npm") return pm;
3465
+ }
3466
+ }
3467
+ return "npm";
3468
+ }
3469
+ function promptPlanSelection(response) {
3470
+ const plans = response.plans.filter((p) => p.upgrades.length > 0);
3471
+ console.log(chalk7.bold("\nSelect a plan to apply:"));
3472
+ plans.forEach((p, i) => {
3473
+ const rec = p.tier === response.recommended ? chalk7.green(" (recommended)") : "";
3474
+ const drift = typeof p.expectedDriftScore === "number" && typeof response.currentDriftScore === "number" ? ` \xB7 DriftScore ${response.currentDriftScore}\u2192${p.expectedDriftScore}` : "";
3475
+ console.log(` ${i + 1}) ${chalk7.bold(p.label)}${rec} risk ${p.riskScore}/100 \xB7 ${p.upgrades.length} upgrade(s)${drift}`);
3476
+ });
3477
+ const defaultIdx = plans.findIndex((p) => p.tier === response.recommended);
3478
+ const def = defaultIdx >= 0 ? defaultIdx + 1 : 1;
3479
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3480
+ return new Promise((resolve16) => {
3481
+ rl.question(`Plan [1-${plans.length}, default ${def}, q to cancel]: `, (ans) => {
3482
+ rl.close();
3483
+ const t = ans.trim().toLowerCase();
3484
+ if (t === "q" || t === "quit") return resolve16(null);
3485
+ const n = t === "" ? def : Number.parseInt(t, 10);
3486
+ if (!Number.isInteger(n) || n < 1 || n > plans.length) return resolve16(null);
3487
+ resolve16(plans[n - 1].tier);
3488
+ });
3489
+ });
3490
+ }
3491
+ async function runApplyFlow(rootDir, artifact, response, opts) {
3492
+ const nonEmpty = response.plans.filter((p) => p.upgrades.length > 0);
3493
+ if (nonEmpty.length === 0) {
3494
+ console.log(chalk7.green("\n\u2714 Nothing to upgrade \u2014 every tracked dependency is current."));
3495
+ return;
3496
+ }
3497
+ let chosen;
3498
+ if (opts.plan) {
3499
+ chosen = response.plans.find((p) => p.tier === opts.plan);
3500
+ if (!chosen) {
3501
+ console.error(chalk7.red(`Unknown plan '${opts.plan}'. Use safe, balanced, or aggressive.`));
3502
+ process.exit(1);
3503
+ }
3504
+ } else if (nonEmpty.length === 1) {
3505
+ chosen = nonEmpty[0];
3506
+ } else if (opts.yes) {
3507
+ chosen = response.plans.find((p) => p.tier === response.recommended) ?? nonEmpty[0];
3508
+ } else if (process.stdin.isTTY && process.stdout.isTTY) {
3509
+ const tier = await promptPlanSelection(response);
3510
+ if (!tier) {
3511
+ console.log(chalk7.dim("No plan applied."));
3512
+ return;
3513
+ }
3514
+ chosen = response.plans.find((p) => p.tier === tier);
3515
+ } else {
3516
+ console.log(chalk7.dim("\nMultiple plans available \u2014 re-run with --plan <tier> or --yes to apply, or --dry-run to preview."));
3517
+ return;
3518
+ }
3519
+ if (!chosen || chosen.upgrades.length === 0) {
3520
+ console.log(chalk7.dim("Selected plan has no upgrades."));
3521
+ return;
3522
+ }
3523
+ const pm = npmPackageManager(artifact);
3524
+ const results = applyPlan(rootDir, chosen.upgrades, { dryRun: opts.dryRun, packageManager: pm });
3525
+ if (opts.dryRun) {
3526
+ console.log(chalk7.bold(`
3527
+ Dry run \u2014 ${chosen.label} plan (${chosen.upgrades.length} upgrade(s)):`));
3528
+ for (const r of results) {
3529
+ if (r.status === "manual") console.log(chalk7.yellow(` \u26A0 ${r.package}: ${r.detail}`));
3530
+ else console.log(chalk7.dim(` ${r.package}: ${r.detail}`));
3531
+ }
3532
+ if (typeof chosen.expectedDriftScore === "number") {
3533
+ console.log(chalk7.dim(`Expected DriftScore after apply: ~${chosen.expectedDriftScore} (was ${response.currentDriftScore ?? "?"}).`));
3534
+ }
3535
+ return;
3536
+ }
3537
+ const applied = results.filter((r) => r.status === "applied");
3538
+ const failed = results.filter((r) => r.status === "failed");
3539
+ const manual = results.filter((r) => r.status === "manual");
3540
+ console.log(chalk7.bold(`
3541
+ Applied the ${chosen.label} plan:`));
3542
+ console.log(
3543
+ chalk7.green(` \u2714 ${applied.length} upgraded`) + (failed.length ? chalk7.red(` \u2716 ${failed.length} failed`) : "") + (manual.length ? chalk7.yellow(` \u26A0 ${manual.length} need manual work`) : "")
3544
+ );
3545
+ for (const r of failed) console.log(chalk7.red(` \u2716 ${r.package} \u2192 ${r.to}: ${r.detail ?? "failed"}`));
3546
+ for (const r of manual) console.log(chalk7.yellow(` \u26A0 ${r.package} \u2192 ${r.to}: ${r.detail}`));
3547
+ if (typeof chosen.expectedDriftScore === "number" && typeof response.currentDriftScore === "number") {
3548
+ const delta = chosen.expectedDriftScore - response.currentDriftScore;
3549
+ console.log(
3550
+ ` Expected DriftScore: ${response.currentDriftScore} \u2192 ~${chosen.expectedDriftScore} (${delta <= 0 ? "" : "+"}${delta}). Re-run \`vg\` to confirm the actual score.`
3551
+ );
3552
+ }
3553
+ if (failed.length) process.exitCode = 2;
3554
+ }
2313
3555
  function formatText(artifact) {
2314
3556
  const lines = [];
2315
- const teal2 = chalk5.hex("#3FB0A4");
2316
- const mint2 = chalk5.hex("#4FE3C1");
3557
+ const teal2 = chalk7.hex("#3FB0A4");
3558
+ const mint2 = chalk7.hex("#4FE3C1");
2317
3559
  lines.push("");
2318
3560
  lines.push(" " + teal2("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E") + mint2("\u279C"));
2319
- lines.push(" " + chalk5.dim("\u2524") + teal2("\u2502") + " " + mint2("\u25FC") + " " + mint2("\u25FC") + " " + teal2("\u2502") + chalk5.dim("\u251C") + " " + chalk5.bold.white("vibgrate"));
2320
- lines.push(" " + chalk5.dim("\u2524") + teal2("\u2502") + " " + chalk5.dim("\u2581\u2581") + " " + teal2("\u2502") + chalk5.dim("\u251C") + " " + chalk5.dim(`Drift Intelligence Engine v${VERSION}`));
3561
+ lines.push(" " + chalk7.dim("\u2524") + teal2("\u2502") + " " + mint2("\u25FC") + " " + mint2("\u25FC") + " " + teal2("\u2502") + chalk7.dim("\u251C") + " " + chalk7.bold.white("vibgrate"));
3562
+ lines.push(" " + chalk7.dim("\u2524") + teal2("\u2502") + " " + chalk7.dim("\u2581\u2581") + " " + teal2("\u2502") + chalk7.dim("\u251C") + " " + chalk7.dim(`Drift Intelligence Engine v${VERSION}`));
2321
3563
  lines.push(" " + teal2("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
2322
3564
  lines.push("");
2323
3565
  lines.push(...titleBox("Vibgrate Drift Report", teal2));
2324
3566
  lines.push("");
2325
3567
  for (const project of artifact.projects) {
2326
- lines.push(chalk5.bold(` \u2500\u2500 ${project.name} `) + chalk5.dim(`(${project.type}) ${project.path}`));
3568
+ lines.push(chalk7.bold(` \u2500\u2500 ${project.name} `) + chalk7.dim(`(${project.type}) ${project.path}`));
2327
3569
  if (project.runtime) {
2328
- const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk5.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk5.green(" (current)");
3570
+ const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk7.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk7.green(" (current)");
2329
3571
  lines.push(` Runtime: ${project.runtime}${behindStr}`);
2330
3572
  }
2331
3573
  if (project.targetFramework) {
@@ -2334,7 +3576,7 @@ function formatText(artifact) {
2334
3576
  if (project.frameworks.length > 0) {
2335
3577
  lines.push(" Frameworks:");
2336
3578
  for (const fw of project.frameworks) {
2337
- const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk5.green("current") : chalk5.yellow(`${fw.majorsBehind} behind`) : chalk5.dim("unknown");
3579
+ const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk7.green("current") : chalk7.yellow(`${fw.majorsBehind} behind`) : chalk7.dim("unknown");
2338
3580
  lines.push(` ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
2339
3581
  }
2340
3582
  }
@@ -2342,13 +3584,13 @@ function formatText(artifact) {
2342
3584
  const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
2343
3585
  if (total > 0) {
2344
3586
  lines.push(" Dependencies:");
2345
- lines.push(` ${chalk5.green(`${b.current} current`)} ${chalk5.yellow(`${b.oneBehind} 1-behind`)} ${chalk5.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk5.dim(`${b.unknown} unknown`)}`);
3587
+ lines.push(` ${chalk7.green(`${b.current} current`)} ${chalk7.yellow(`${b.oneBehind} 1-behind`)} ${chalk7.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk7.dim(`${b.unknown} unknown`)}`);
2346
3588
  }
2347
3589
  lines.push("");
2348
3590
  }
2349
3591
  if (artifact.delta !== void 0) {
2350
- const deltaStr = artifact.delta > 0 ? chalk5.red(`+${artifact.delta}`) : artifact.delta < 0 ? chalk5.green(`${artifact.delta}`) : chalk5.dim("0");
2351
- lines.push(chalk5.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
3592
+ const deltaStr = artifact.delta > 0 ? chalk7.red(`+${artifact.delta}`) : artifact.delta < 0 ? chalk7.green(`${artifact.delta}`) : chalk7.dim("0");
3593
+ lines.push(chalk7.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
2352
3594
  lines.push("");
2353
3595
  }
2354
3596
  if (artifact.extended) {
@@ -2359,15 +3601,15 @@ function formatText(artifact) {
2359
3601
  const warnings = artifact.findings.filter((f) => f.level === "warning");
2360
3602
  const notes = artifact.findings.filter((f) => f.level === "note");
2361
3603
  const summary = [
2362
- errors.length > 0 ? chalk5.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
2363
- warnings.length > 0 ? chalk5.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
2364
- notes.length > 0 ? chalk5.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
2365
- ].filter(Boolean).join(chalk5.dim(", "));
2366
- lines.push(chalk5.bold.underline(` Findings`) + chalk5.dim(` (${summary})`));
3604
+ errors.length > 0 ? chalk7.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
3605
+ warnings.length > 0 ? chalk7.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
3606
+ notes.length > 0 ? chalk7.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
3607
+ ].filter(Boolean).join(chalk7.dim(", "));
3608
+ lines.push(chalk7.bold.underline(` Findings`) + chalk7.dim(` (${summary})`));
2367
3609
  for (const f of artifact.findings) {
2368
- const icon = f.level === "error" ? chalk5.red("\u2716") : f.level === "warning" ? chalk5.yellow("\u26A0") : chalk5.blue("\u2139");
3610
+ const icon = f.level === "error" ? chalk7.red("\u2716") : f.level === "warning" ? chalk7.yellow("\u26A0") : chalk7.blue("\u2139");
2369
3611
  lines.push(` ${icon} ${f.message}`);
2370
- lines.push(chalk5.dim(` ${f.ruleId} in ${f.location}`));
3612
+ lines.push(chalk7.dim(` ${f.ruleId} in ${f.location}`));
2371
3613
  }
2372
3614
  lines.push("");
2373
3615
  }
@@ -2377,10 +3619,10 @@ function formatText(artifact) {
2377
3619
  lines.push("");
2378
3620
  for (let i = 0; i < actions.length; i++) {
2379
3621
  const a = actions[i];
2380
- const num = chalk5.bold.cyan(` ${i + 1}.`);
2381
- lines.push(`${num} ${chalk5.bold(a.title)}`);
2382
- lines.push(chalk5.dim(` ${a.explanation}`));
2383
- if (a.impact) lines.push(` Impact: ${chalk5.green(a.impact)}`);
3622
+ const num = chalk7.bold.cyan(` ${i + 1}.`);
3623
+ lines.push(`${num} ${chalk7.bold(a.title)}`);
3624
+ lines.push(chalk7.dim(` ${a.explanation}`));
3625
+ if (a.impact) lines.push(` Impact: ${chalk7.green(a.impact)}`);
2384
3626
  lines.push("");
2385
3627
  }
2386
3628
  }
@@ -2392,30 +3634,30 @@ function formatText(artifact) {
2392
3634
  lines.push("");
2393
3635
  for (const solution of artifact.solutions) {
2394
3636
  const solScore = solution.drift?.score;
2395
- const color = typeof solScore === "number" ? solScore <= 30 ? chalk5.green : solScore <= 60 ? chalk5.yellow : chalk5.red : chalk5.dim;
2396
- lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk5.dim("n/a")}`);
3637
+ const color = typeof solScore === "number" ? solScore <= 30 ? chalk7.green : solScore <= 60 ? chalk7.yellow : chalk7.red : chalk7.dim;
3638
+ lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk7.dim("n/a")}`);
2397
3639
  }
2398
3640
  lines.push("");
2399
3641
  }
2400
- const scoreColor = artifact.drift.score <= 30 ? chalk5.green : artifact.drift.score <= 60 ? chalk5.yellow : chalk5.red;
2401
- lines.push(...titleBox("Drift Score Summary"));
3642
+ const scoreColor = artifact.drift.score <= 30 ? chalk7.green : artifact.drift.score <= 60 ? chalk7.yellow : chalk7.red;
3643
+ lines.push(...titleBox("DriftScore Summary"));
2402
3644
  lines.push("");
2403
- lines.push(chalk5.bold(" Drift Score: ") + scoreColor.bold(`${artifact.drift.score}/100`));
2404
- lines.push(chalk5.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
2405
- lines.push(chalk5.bold(" Projects: ") + `${artifact.projects.length}`);
3645
+ lines.push(chalk7.bold(" DriftScore: ") + scoreColor.bold(`${artifact.drift.score}/100`));
3646
+ lines.push(chalk7.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
3647
+ lines.push(chalk7.bold(" Projects: ") + `${artifact.projects.length}`);
2406
3648
  if (artifact.vcs) {
2407
3649
  const vcsParts = [artifact.vcs.type];
2408
3650
  if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
2409
- if (artifact.vcs.shortSha) vcsParts.push(chalk5.dim(artifact.vcs.shortSha));
2410
- lines.push(chalk5.bold(" VCS: ") + vcsParts.join(" "));
3651
+ if (artifact.vcs.shortSha) vcsParts.push(chalk7.dim(artifact.vcs.shortSha));
3652
+ lines.push(chalk7.bold(" VCS: ") + vcsParts.join(" "));
2411
3653
  }
2412
3654
  lines.push("");
2413
3655
  const m = new Set(artifact.drift.measured ?? ["runtime", "framework", "dependency", "eol"]);
2414
- lines.push(" " + chalk5.bold.underline("Score Breakdown"));
2415
- lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk5.dim("n/a")}`);
2416
- lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk5.dim("n/a")}`);
2417
- lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk5.dim("n/a")}`);
2418
- lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk5.dim("n/a")}`);
3656
+ lines.push(" " + chalk7.bold.underline("Score Breakdown"));
3657
+ lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk7.dim("n/a")}`);
3658
+ lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk7.dim("n/a")}`);
3659
+ lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk7.dim("n/a")}`);
3660
+ lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk7.dim("n/a")}`);
2419
3661
  lines.push("");
2420
3662
  const scannedParts = [`Scanned at ${artifact.timestamp}`];
2421
3663
  if (artifact.durationMs !== void 0) {
@@ -2429,18 +3671,18 @@ function formatText(artifact) {
2429
3671
  scannedParts.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files`);
2430
3672
  scannedParts.push(`${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
2431
3673
  }
2432
- lines.push(chalk5.dim(` ${scannedParts.join(" \xB7 ")}`));
3674
+ lines.push(chalk7.dim(` ${scannedParts.join(" \xB7 ")}`));
2433
3675
  lines.push("");
2434
3676
  return lines.join("\n");
2435
3677
  }
2436
3678
  function riskBadge(level) {
2437
3679
  switch (level) {
2438
3680
  case "low":
2439
- return chalk5.bgGreen.black(" LOW ");
3681
+ return chalk7.bgGreen.black(" LOW ");
2440
3682
  case "moderate":
2441
- return chalk5.bgYellow.black(" MODERATE ");
3683
+ return chalk7.bgYellow.black(" MODERATE ");
2442
3684
  case "high":
2443
- return chalk5.bgRed.white(" HIGH ");
3685
+ return chalk7.bgRed.white(" HIGH ");
2444
3686
  default:
2445
3687
  return level;
2446
3688
  }
@@ -2475,11 +3717,11 @@ function formatExtended(ext) {
2475
3717
  const inv = ext.toolingInventory;
2476
3718
  const categories = Object.entries(inv).filter(([, items]) => items.length > 0);
2477
3719
  if (categories.length > 0) {
2478
- lines.push(chalk5.bold.underline(" Tech Stack"));
3720
+ lines.push(chalk7.bold.underline(" Tech Stack"));
2479
3721
  for (const [cat, items] of categories) {
2480
3722
  const label = CATEGORY_LABELS[cat] ?? cat;
2481
- const names = items.map((i) => chalk5.white(i.name)).join(chalk5.dim(", "));
2482
- lines.push(` ${chalk5.cyan(label)}: ${names}`);
3723
+ const names = items.map((i) => chalk7.white(i.name)).join(chalk7.dim(", "));
3724
+ lines.push(` ${chalk7.cyan(label)}: ${names}`);
2483
3725
  }
2484
3726
  lines.push("");
2485
3727
  }
@@ -2488,14 +3730,14 @@ function formatExtended(ext) {
2488
3730
  const svc = ext.serviceDependencies;
2489
3731
  const categories = Object.entries(svc).filter(([, items]) => items.length > 0);
2490
3732
  if (categories.length > 0) {
2491
- lines.push(chalk5.bold.underline(" Services & Integrations"));
3733
+ lines.push(chalk7.bold.underline(" Services & Integrations"));
2492
3734
  for (const [cat, items] of categories) {
2493
3735
  const label = CATEGORY_LABELS[cat] ?? cat;
2494
3736
  const names = items.map((i) => {
2495
- const ver = i.version ? chalk5.dim(` ${i.version}`) : "";
2496
- return chalk5.white(i.name) + ver;
2497
- }).join(chalk5.dim(", "));
2498
- lines.push(` ${chalk5.cyan(label)}: ${names}`);
3737
+ const ver = i.version ? chalk7.dim(` ${i.version}`) : "";
3738
+ return chalk7.white(i.name) + ver;
3739
+ }).join(chalk7.dim(", "));
3740
+ lines.push(` ${chalk7.cyan(label)}: ${names}`);
2499
3741
  }
2500
3742
  lines.push("");
2501
3743
  }
@@ -2503,19 +3745,19 @@ function formatExtended(ext) {
2503
3745
  if (ext.breakingChangeExposure) {
2504
3746
  const bc = ext.breakingChangeExposure;
2505
3747
  if (bc.deprecatedPackages.length > 0 || bc.legacyPolyfills.length > 0) {
2506
- lines.push(chalk5.bold.underline(" Breaking Change Exposure"));
2507
- const exposureColor = bc.exposureScore >= 40 ? chalk5.red : bc.exposureScore >= 20 ? chalk5.yellow : chalk5.green;
3748
+ lines.push(chalk7.bold.underline(" Breaking Change Exposure"));
3749
+ const exposureColor = bc.exposureScore >= 40 ? chalk7.red : bc.exposureScore >= 20 ? chalk7.yellow : chalk7.green;
2508
3750
  lines.push(` Exposure Score: ${exposureColor.bold(`${bc.exposureScore}/100`)}`);
2509
3751
  if (bc.deprecatedPackages.length > 0) {
2510
- lines.push(` ${chalk5.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk5.dim(p)).join(", ")}`);
3752
+ lines.push(` ${chalk7.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk7.dim(p)).join(", ")}`);
2511
3753
  }
2512
3754
  if (bc.legacyPolyfills.length > 0) {
2513
- lines.push(` ${chalk5.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk5.dim(p)).join(", ")}`);
3755
+ lines.push(` ${chalk7.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk7.dim(p)).join(", ")}`);
2514
3756
  }
2515
3757
  if (bc.peerConflictsDetected) {
2516
- lines.push(` ${chalk5.red("\u26A0")} Peer dependency conflicts detected`);
3758
+ lines.push(` ${chalk7.red("\u26A0")} Peer dependency conflicts detected`);
2517
3759
  }
2518
- lines.push(` Recommendation: ${chalk5.bold(bc.overallRecommendation)}`);
3760
+ lines.push(` Recommendation: ${chalk7.bold(bc.overallRecommendation)}`);
2519
3761
  const projectsWithPlans = bc.projectIntelligence.filter((p) => p.packages.length > 0).slice(0, 3);
2520
3762
  if (projectsWithPlans.length > 0) {
2521
3763
  lines.push(" Major Upgrade Intelligence:");
@@ -2531,21 +3773,21 @@ function formatExtended(ext) {
2531
3773
  }
2532
3774
  if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
2533
3775
  const ts = ext.tsModernity;
2534
- lines.push(chalk5.bold.underline(" TypeScript"));
3776
+ lines.push(chalk7.bold.underline(" TypeScript"));
2535
3777
  const parts = [];
2536
3778
  parts.push(`v${ts.typescriptVersion}`);
2537
- if (ts.strict === true) parts.push(chalk5.green("strict \u2714"));
2538
- else if (ts.strict === false) parts.push(chalk5.yellow("strict \u2716"));
3779
+ if (ts.strict === true) parts.push(chalk7.green("strict \u2714"));
3780
+ else if (ts.strict === false) parts.push(chalk7.yellow("strict \u2716"));
2539
3781
  if (ts.moduleType) parts.push(ts.moduleType.toUpperCase());
2540
3782
  if (ts.target) parts.push(`target: ${ts.target}`);
2541
- lines.push(` ${parts.join(chalk5.dim(" \xB7 "))}`);
3783
+ lines.push(` ${parts.join(chalk7.dim(" \xB7 "))}`);
2542
3784
  lines.push("");
2543
3785
  }
2544
3786
  if (ext.buildDeploy) {
2545
3787
  const bd = ext.buildDeploy;
2546
3788
  const hasSomething = bd.ci.length > 0 || bd.docker.dockerfileCount > 0 || bd.packageManagers.length > 0;
2547
3789
  if (hasSomething) {
2548
- lines.push(chalk5.bold.underline(" Build & Deploy"));
3790
+ lines.push(chalk7.bold.underline(" Build & Deploy"));
2549
3791
  if (bd.ci.length > 0) lines.push(` CI: ${bd.ci.join(", ")}`);
2550
3792
  if (bd.docker.dockerfileCount > 0) {
2551
3793
  lines.push(` Docker: ${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""} (${bd.docker.baseImages.join(", ")})`);
@@ -2558,42 +3800,42 @@ function formatExtended(ext) {
2558
3800
  }
2559
3801
  if (ext.uiPurpose) {
2560
3802
  const up = ext.uiPurpose;
2561
- lines.push(chalk5.bold.underline(" Product Purpose Signals"));
2562
- lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk5.dim("unknown")}`);
2563
- lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk5.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
3803
+ lines.push(chalk7.bold.underline(" Product Purpose Signals"));
3804
+ lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk7.dim("unknown")}`);
3805
+ lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk7.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
2564
3806
  const top = up.topEvidence.slice(0, 8);
2565
3807
  if (top.length > 0) {
2566
3808
  lines.push(" Top Signals:");
2567
3809
  for (const item of top) {
2568
- lines.push(` - [${item.kind}] ${item.value} ${chalk5.dim(`(${item.file})`)}`);
3810
+ lines.push(` - [${item.kind}] ${item.value} ${chalk7.dim(`(${item.file})`)}`);
2569
3811
  }
2570
3812
  }
2571
3813
  if (up.unknownSignals.length > 0) {
2572
3814
  lines.push(" Unknowns:");
2573
3815
  for (const u of up.unknownSignals.slice(0, 4)) {
2574
- lines.push(` - ${chalk5.yellow(u)}`);
3816
+ lines.push(` - ${chalk7.yellow(u)}`);
2575
3817
  }
2576
3818
  }
2577
3819
  lines.push("");
2578
3820
  }
2579
3821
  if (ext.securityPosture) {
2580
3822
  const sec = ext.securityPosture;
2581
- lines.push(chalk5.bold.underline(" Security Posture"));
3823
+ lines.push(chalk7.bold.underline(" Security Posture"));
2582
3824
  const checks = [];
2583
- checks.push(sec.lockfilePresent ? chalk5.green("Lockfile \u2714") : chalk5.red("Lockfile \u2716"));
2584
- checks.push(sec.gitignoreCoversEnv ? chalk5.green(".env \u2714") : chalk5.red(".env \u2716"));
2585
- checks.push(sec.gitignoreCoversNodeModules ? chalk5.green("node_modules \u2714") : chalk5.yellow("node_modules \u2716"));
2586
- if (sec.multipleLockfileTypes) checks.push(chalk5.yellow("Multiple lockfiles \u26A0"));
2587
- if (sec.envFilesTracked) checks.push(chalk5.red("Env files tracked \u2716"));
2588
- lines.push(` ${checks.join(chalk5.dim(" \xB7 "))}`);
3825
+ checks.push(sec.lockfilePresent ? chalk7.green("Lockfile \u2714") : chalk7.red("Lockfile \u2716"));
3826
+ checks.push(sec.gitignoreCoversEnv ? chalk7.green(".env \u2714") : chalk7.red(".env \u2716"));
3827
+ checks.push(sec.gitignoreCoversNodeModules ? chalk7.green("node_modules \u2714") : chalk7.yellow("node_modules \u2716"));
3828
+ if (sec.multipleLockfileTypes) checks.push(chalk7.yellow("Multiple lockfiles \u26A0"));
3829
+ if (sec.envFilesTracked) checks.push(chalk7.red("Env files tracked \u2716"));
3830
+ lines.push(` ${checks.join(chalk7.dim(" \xB7 "))}`);
2589
3831
  lines.push("");
2590
3832
  }
2591
3833
  if (ext.platformMatrix) {
2592
3834
  const pm = ext.platformMatrix;
2593
3835
  if (pm.nativeModules.length > 0 || pm.dockerBaseImages.length > 0) {
2594
- lines.push(chalk5.bold.underline(" Platform"));
3836
+ lines.push(chalk7.bold.underline(" Platform"));
2595
3837
  if (pm.nativeModules.length > 0) {
2596
- lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk5.dim(m)).join(", ")}`);
3838
+ lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk7.dim(m)).join(", ")}`);
2597
3839
  }
2598
3840
  if (pm.osAssumptions.length > 0) {
2599
3841
  lines.push(` OS assumptions: ${pm.osAssumptions.join(", ")}`);
@@ -2603,25 +3845,25 @@ function formatExtended(ext) {
2603
3845
  }
2604
3846
  if (ext.codeQuality) {
2605
3847
  const cq = ext.codeQuality;
2606
- lines.push(chalk5.bold.underline(" Code Quality"));
2607
- lines.push(` Files: ${chalk5.white(`${cq.filesAnalyzed}`)} \xB7 Functions: ${chalk5.white(`${cq.functionsAnalyzed}`)} \xB7 Avg complexity: ${chalk5.white(`${cq.avgCyclomaticComplexity}`)} \xB7 Avg length: ${chalk5.white(`${cq.avgFunctionLength}`)} lines`);
3848
+ lines.push(chalk7.bold.underline(" Code Quality"));
3849
+ lines.push(` Files: ${chalk7.white(`${cq.filesAnalyzed}`)} \xB7 Functions: ${chalk7.white(`${cq.functionsAnalyzed}`)} \xB7 Avg complexity: ${chalk7.white(`${cq.avgCyclomaticComplexity}`)} \xB7 Avg length: ${chalk7.white(`${cq.avgFunctionLength}`)} lines`);
2608
3850
  lines.push(` Max nesting: ${cq.maxNestingDepth} \xB7 Circular deps: ${cq.circularDependencies} \xB7 Dead code: ${cq.deadCodePercent}%`);
2609
3851
  if (cq.godFiles.length > 0) {
2610
3852
  const preview = cq.godFiles.slice(0, 3).map((f) => `${f.path} (${f.lines} lines)`).join(", ");
2611
- lines.push(` ${chalk5.yellow("God files")}: ${preview}`);
3853
+ lines.push(` ${chalk7.yellow("God files")}: ${preview}`);
2612
3854
  }
2613
3855
  lines.push("");
2614
3856
  }
2615
3857
  if (ext.dependencyGraph) {
2616
3858
  const dg = ext.dependencyGraph;
2617
3859
  if (dg.lockfileType) {
2618
- lines.push(chalk5.bold.underline(" Dependency Graph"));
2619
- lines.push(` ${dg.lockfileType}: ${chalk5.white(`${dg.totalUnique}`)} unique, ${chalk5.white(`${dg.totalInstalled}`)} installed`);
3860
+ lines.push(chalk7.bold.underline(" Dependency Graph"));
3861
+ lines.push(` ${dg.lockfileType}: ${chalk7.white(`${dg.totalUnique}`)} unique, ${chalk7.white(`${dg.totalInstalled}`)} installed`);
2620
3862
  if (dg.duplicatedPackages.length > 0) {
2621
- lines.push(` ${chalk5.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
3863
+ lines.push(` ${chalk7.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
2622
3864
  }
2623
3865
  if (dg.phantomDependencies.length > 0) {
2624
- lines.push(` ${chalk5.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
3866
+ lines.push(` ${chalk7.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
2625
3867
  }
2626
3868
  lines.push("");
2627
3869
  }
@@ -2632,13 +3874,13 @@ function formatArchitectureDiagram(arch) {
2632
3874
  const lines = [];
2633
3875
  lines.push(...titleBox("Architecture Layers"));
2634
3876
  lines.push("");
2635
- lines.push(chalk5.bold(" Archetype: ") + `${arch.archetype}` + chalk5.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
2636
- lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk5.dim(` (${arch.unclassified} unclassified)`) : ""));
3877
+ lines.push(chalk7.bold(" Archetype: ") + `${arch.archetype}` + chalk7.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
3878
+ lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk7.dim(` (${arch.unclassified} unclassified)`) : ""));
2637
3879
  lines.push("");
2638
3880
  if (arch.layers.length > 0) {
2639
3881
  for (const layer of arch.layers) {
2640
- const risk = layer.riskLevel === "none" ? chalk5.dim("none") : layer.riskLevel === "low" ? chalk5.green("low") : layer.riskLevel === "moderate" ? chalk5.yellow("moderate") : chalk5.red("high");
2641
- lines.push(` ${chalk5.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
3882
+ const risk = layer.riskLevel === "none" ? chalk7.dim("none") : layer.riskLevel === "low" ? chalk7.green("low") : layer.riskLevel === "moderate" ? chalk7.yellow("moderate") : chalk7.red("high");
3883
+ lines.push(` ${chalk7.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
2642
3884
  }
2643
3885
  lines.push("");
2644
3886
  }
@@ -2900,7 +4142,7 @@ function formatMarkdown(artifact) {
2900
4142
  lines.push("");
2901
4143
  lines.push(`| Metric | Value |`);
2902
4144
  lines.push(`|--------|-------|`);
2903
- lines.push(`| **Drift Score** | ${artifact.drift.score}/100 _(lower is better; 0 = no drift)_ |`);
4145
+ lines.push(`| **DriftScore** | ${artifact.drift.score}/100 _(lower is better; 0 = no drift)_ |`);
2904
4146
  lines.push(`| **Risk Level** | ${artifact.drift.riskLevel.toUpperCase()} |`);
2905
4147
  lines.push(`| **Projects** | ${artifact.projects.length} |`);
2906
4148
  const scannedMeta = [artifact.timestamp];
@@ -3007,10 +4249,10 @@ function formatMarkdown(artifact) {
3007
4249
 
3008
4250
  // src/reporting/commands/report.ts
3009
4251
  var reportCommand = new Command("report").description("Generate a drift report from a scan artifact").option("--in <file>", "Input artifact file", ".vibgrate/scan_result.json").option("--format <format>", "Output format (md|text|json)", "text").action(async (opts) => {
3010
- const artifactPath = path9.resolve(opts.in);
4252
+ const artifactPath = path.resolve(opts.in);
3011
4253
  if (!await pathExists(artifactPath)) {
3012
- console.error(chalk5.red(`Artifact not found: ${artifactPath}`));
3013
- console.error(chalk5.dim('Run "vibgrate scan" first to generate a scan artifact.'));
4254
+ console.error(chalk7.red(`Artifact not found: ${artifactPath}`));
4255
+ console.error(chalk7.dim('Run "vibgrate scan" first to generate a scan artifact.'));
3014
4256
  process.exit(1);
3015
4257
  }
3016
4258
  const artifact = await readJsonFile(artifactPath);
@@ -3053,10 +4295,10 @@ function openUrl(url) {
3053
4295
  }
3054
4296
 
3055
4297
  // src/reporting/commands/login.ts
3056
- var delay = (ms) => new Promise((resolve15) => setTimeout(resolve15, ms));
4298
+ var delay = (ms) => new Promise((resolve16) => setTimeout(resolve16, ms));
3057
4299
  function isInside(dir, file) {
3058
- const rel2 = path9.relative(dir, file);
3059
- return !!rel2 && !rel2.startsWith("..") && !path9.isAbsolute(rel2);
4300
+ const rel3 = path.relative(dir, file);
4301
+ return !!rel3 && !rel3.startsWith("..") && !path.isAbsolute(rel3);
3060
4302
  }
3061
4303
  var loginCommand = new Command("login").description("Authenticate the CLI with your Vibgrate workspace via the browser").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>", `Data residency region (${availableRegionIds().join(", ")})`, "us").option("--no-browser", "Do not attempt to open a browser automatically").option(
3062
4304
  "--local",
@@ -3066,7 +4308,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3066
4308
  try {
3067
4309
  ingestHost = resolveIngestHost(opts.region, opts.ingest);
3068
4310
  } catch (e) {
3069
- console.error(chalk5.red(e instanceof Error ? e.message : String(e)));
4311
+ console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
3070
4312
  process.exit(1);
3071
4313
  }
3072
4314
  const base = `https://${ingestHost}/v1/auth/device`;
@@ -3078,28 +4320,28 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3078
4320
  body: "{}"
3079
4321
  });
3080
4322
  if (!res.ok) {
3081
- console.error(chalk5.red(`Failed to start login (HTTP ${res.status}).`));
4323
+ console.error(chalk7.red(`Failed to start login (HTTP ${res.status}).`));
3082
4324
  process.exit(1);
3083
4325
  }
3084
4326
  start = await res.json();
3085
4327
  } catch (e) {
3086
- console.error(chalk5.red(`Could not reach ${ingestHost}: ${e instanceof Error ? e.message : String(e)}`));
4328
+ console.error(chalk7.red(`Could not reach ${ingestHost}: ${e instanceof Error ? e.message : String(e)}`));
3087
4329
  process.exit(1);
3088
4330
  }
3089
4331
  console.log("");
3090
4332
  console.log("To finish signing in, open this URL and approve the request:");
3091
4333
  console.log("");
3092
- console.log(" " + chalk5.cyan(start.verificationUri));
4334
+ console.log(" " + chalk7.cyan(start.verificationUri));
3093
4335
  console.log("");
3094
- console.log(" Your code: " + chalk5.bold(start.userCode));
4336
+ console.log(" Your code: " + chalk7.bold(start.userCode));
3095
4337
  console.log("");
3096
4338
  if (opts.browser) {
3097
4339
  const opened = openUrl(start.verificationUriComplete);
3098
4340
  if (opened) {
3099
- console.log(chalk5.dim("Opening your browser\u2026 (if it does not open, use the URL above)"));
4341
+ console.log(chalk7.dim("Opening your browser\u2026 (if it does not open, use the URL above)"));
3100
4342
  }
3101
4343
  }
3102
- console.log(chalk5.dim("Waiting for approval\u2026"));
4344
+ console.log(chalk7.dim("Waiting for approval\u2026"));
3103
4345
  const intervalMs = Math.max(2, start.interval || 5) * 1e3;
3104
4346
  const deadline = Date.now() + (start.expiresIn || 900) * 1e3;
3105
4347
  while (Date.now() < deadline) {
@@ -3127,7 +4369,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3127
4369
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
3128
4370
  };
3129
4371
  } else {
3130
- console.log(chalk5.dim("Setting up your workspace\u2026"));
4372
+ console.log(chalk7.dim("Setting up your workspace\u2026"));
3131
4373
  try {
3132
4374
  const provisioned = await createWorkspaceDsn({
3133
4375
  region: opts.region,
@@ -3142,10 +4384,10 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3142
4384
  };
3143
4385
  } catch (e) {
3144
4386
  console.error(
3145
- chalk5.red("\u2716 Signed in, but workspace setup failed: ") + (e instanceof Error ? e.message : String(e))
4387
+ chalk7.red("\u2716 Signed in, but workspace setup failed: ") + (e instanceof Error ? e.message : String(e))
3146
4388
  );
3147
4389
  console.error(
3148
- chalk5.dim(' Finish setup with "vibgrate dsn create --workspace new".')
4390
+ chalk7.dim(' Finish setup with "vibgrate dsn create --workspace new".')
3149
4391
  );
3150
4392
  process.exit(1);
3151
4393
  }
@@ -3154,40 +4396,40 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3154
4396
  writeStoredCredentials(creds, storeOpts);
3155
4397
  const credsFile = credentialsPath(storeOpts);
3156
4398
  console.log("");
3157
- console.log(chalk5.green("\u2714") + " Logged in.");
4399
+ console.log(chalk7.green("\u2714") + " Logged in.");
3158
4400
  if (creds.workspaceId) {
3159
- console.log(" Workspace: " + chalk5.bold(creds.workspaceId));
4401
+ console.log(" Workspace: " + chalk7.bold(creds.workspaceId));
3160
4402
  }
3161
- console.log(chalk5.dim(` Credentials saved to ${credsFile}`));
4403
+ console.log(chalk7.dim(` Credentials saved to ${credsFile}`));
3162
4404
  try {
3163
4405
  const root = findGitRoot();
3164
4406
  if (root && isInside(root, credsFile)) {
3165
4407
  const res = ensureGitignored(gitignoreEntryForCredentials(root, credsFile), root);
3166
4408
  if (res.status === "created") {
3167
- console.log(chalk5.dim(` Created .gitignore and ignored ${res.entry}`));
4409
+ console.log(chalk7.dim(` Created .gitignore and ignored ${res.entry}`));
3168
4410
  } else if (res.status === "added") {
3169
- console.log(chalk5.dim(` Added ${res.entry} to .gitignore`));
4411
+ console.log(chalk7.dim(` Added ${res.entry} to .gitignore`));
3170
4412
  }
3171
4413
  }
3172
4414
  } catch {
3173
4415
  }
3174
- console.log(chalk5.dim(` You can now run "${resolveCliInvocation()} scan --push".`));
4416
+ console.log(chalk7.dim(` You can now run "${resolveCliInvocation()} scan --push".`));
3175
4417
  return;
3176
4418
  }
3177
4419
  if (token.status === "access_denied") {
3178
- console.error(chalk5.red("\u2716 Login was denied in the browser."));
4420
+ console.error(chalk7.red("\u2716 Login was denied in the browser."));
3179
4421
  process.exit(1);
3180
4422
  }
3181
4423
  if (token.status === "expired" || token.status === "invalid") {
3182
- console.error(chalk5.red('\u2716 Login request expired. Run "vibgrate login" again.'));
4424
+ console.error(chalk7.red('\u2716 Login request expired. Run "vibgrate login" again.'));
3183
4425
  process.exit(1);
3184
4426
  }
3185
4427
  if (token.status === "error") {
3186
- console.error(chalk5.red(`\u2716 ${token.error ?? "Login failed."}`));
4428
+ console.error(chalk7.red(`\u2716 ${token.error ?? "Login failed."}`));
3187
4429
  process.exit(1);
3188
4430
  }
3189
4431
  }
3190
- console.error(chalk5.red('\u2716 Timed out waiting for approval. Run "vibgrate login" again.'));
4432
+ console.error(chalk7.red('\u2716 Timed out waiting for approval. Run "vibgrate login" again.'));
3191
4433
  process.exit(1);
3192
4434
  });
3193
4435
 
@@ -3208,23 +4450,23 @@ function unsetEnvCommand(varName, env = {}) {
3208
4450
  var logoutCommand = new Command("logout").description("Clear stored Vibgrate login credentials").action(() => {
3209
4451
  const cleared = clearStoredCredentials();
3210
4452
  if (cleared) {
3211
- console.log(chalk5.green("\u2714") + " Logged out. Stored credentials removed.");
4453
+ console.log(chalk7.green("\u2714") + " Logged out. Stored credentials removed.");
3212
4454
  } else {
3213
- console.log(chalk5.dim(`No stored credentials found at ${credentialsPath()}.`));
4455
+ console.log(chalk7.dim(`No stored credentials found at ${credentialsPath()}.`));
3214
4456
  }
3215
4457
  if (process.env.VIBGRATE_DSN) {
3216
4458
  console.log("");
3217
4459
  console.log(
3218
- chalk5.yellow("\u26A0") + " VIBGRATE_DSN is still set in your environment, so commands will keep using it."
4460
+ chalk7.yellow("\u26A0") + " VIBGRATE_DSN is still set in your environment, so commands will keep using it."
3219
4461
  );
3220
4462
  console.log(
3221
- chalk5.dim(` Unset it to fully sign out: ${unsetEnvCommand("VIBGRATE_DSN")}`)
4463
+ chalk7.dim(` Unset it to fully sign out: ${unsetEnvCommand("VIBGRATE_DSN")}`)
3222
4464
  );
3223
4465
  }
3224
4466
  });
3225
4467
  var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
3226
- var CACHE_DIR = path9.join(os.homedir(), ".vibgrate");
3227
- var CACHE_FILE = path9.join(CACHE_DIR, "update-check.json");
4468
+ var CACHE_DIR = path.join(os.homedir(), ".vibgrate");
4469
+ var CACHE_FILE = path.join(CACHE_DIR, "update-check.json");
3228
4470
  async function fetchLatestVersion() {
3229
4471
  try {
3230
4472
  const controller = new AbortController();
@@ -3251,8 +4493,8 @@ async function fetchLatestVersion() {
3251
4493
  }
3252
4494
  async function writeCache(data) {
3253
4495
  try {
3254
- await fs10.mkdir(CACHE_DIR, { recursive: true });
3255
- await fs10.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
4496
+ await fs14.mkdir(CACHE_DIR, { recursive: true });
4497
+ await fs14.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
3256
4498
  } catch {
3257
4499
  }
3258
4500
  }
@@ -3285,9 +4527,9 @@ function getGlobalUpdateCommand(pm, pkg, version) {
3285
4527
  }
3286
4528
  }
3287
4529
  async function detectPackageManager(cwd) {
3288
- if (await pathExists(path9.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
3289
- if (await pathExists(path9.join(cwd, "bun.lockb"))) return "bun";
3290
- if (await pathExists(path9.join(cwd, "yarn.lock"))) return "yarn";
4530
+ if (await pathExists(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
4531
+ if (await pathExists(path.join(cwd, "bun.lockb"))) return "bun";
4532
+ if (await pathExists(path.join(cwd, "yarn.lock"))) return "yarn";
3291
4533
  return "npm";
3292
4534
  }
3293
4535
  function getInstallCommand(pm, pkg, version, isDev) {
@@ -3306,7 +4548,7 @@ function getInstallCommand(pm, pkg, version, isDev) {
3306
4548
  }
3307
4549
  async function isDevDependency(cwd) {
3308
4550
  try {
3309
- const pkgPath = path9.join(cwd, "package.json");
4551
+ const pkgPath = path.join(cwd, "package.json");
3310
4552
  const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
3311
4553
  const pkg = JSON.parse(raw);
3312
4554
  return Boolean(pkg.devDependencies?.["@vibgrate/cli"]);
@@ -3315,21 +4557,21 @@ async function isDevDependency(cwd) {
3315
4557
  }
3316
4558
  }
3317
4559
  var updateCommand = new Command("update").description("Update vibgrate to the latest version").option("--check", "Only check for updates, do not install").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").option("--global", "Update global installation").action(async (opts) => {
3318
- console.log(chalk5.dim(`Current version: ${VERSION}`));
3319
- console.log(chalk5.dim("Checking npm registry..."));
4560
+ console.log(chalk7.dim(`Current version: ${VERSION}`));
4561
+ console.log(chalk7.dim("Checking npm registry..."));
3320
4562
  const latest = await fetchLatestVersion();
3321
4563
  if (!latest) {
3322
- console.error(chalk5.red("Could not reach the npm registry. Check your network connection."));
4564
+ console.error(chalk7.red("Could not reach the npm registry. Check your network connection."));
3323
4565
  process.exit(1);
3324
4566
  }
3325
4567
  const semver2 = await import('semver');
3326
4568
  if (!semver2.gt(latest, VERSION)) {
3327
- console.log(chalk5.green("\u2714") + ` You are on the latest version (${VERSION}).`);
4569
+ console.log(chalk7.green("\u2714") + ` You are on the latest version (${VERSION}).`);
3328
4570
  return;
3329
4571
  }
3330
- console.log(chalk5.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
4572
+ console.log(chalk7.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
3331
4573
  if (opts.check) {
3332
- console.log(chalk5.dim('Run "vibgrate update" to install.'));
4574
+ console.log(chalk7.dim('Run "vibgrate update" to install.'));
3333
4575
  return;
3334
4576
  }
3335
4577
  const cwd = process.cwd();
@@ -3339,17 +4581,17 @@ var updateCommand = new Command("update").description("Update vibgrate to the la
3339
4581
  let cmd;
3340
4582
  if (isGlobal) {
3341
4583
  cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
3342
- console.log(chalk5.dim(`Updating global installation with ${pm}: ${cmd}`));
4584
+ console.log(chalk7.dim(`Updating global installation with ${pm}: ${cmd}`));
3343
4585
  } else {
3344
4586
  const isDev = await isDevDependency(cwd);
3345
4587
  cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev);
3346
- console.log(chalk5.dim(`Using ${pm}: ${cmd}`));
4588
+ console.log(chalk7.dim(`Using ${pm}: ${cmd}`));
3347
4589
  }
3348
4590
  try {
3349
4591
  execSync(cmd, { cwd, stdio: "inherit" });
3350
- console.log(chalk5.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
4592
+ console.log(chalk7.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
3351
4593
  } catch {
3352
- console.error(chalk5.red(`Update failed. Run manually: ${cmd}`));
4594
+ console.error(chalk7.red(`Update failed. Run manually: ${cmd}`));
3353
4595
  process.exit(1);
3354
4596
  }
3355
4597
  });
@@ -3442,9 +4684,9 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
3442
4684
  try {
3443
4685
  const statements = [];
3444
4686
  if (opts.from) {
3445
- const fromPath = path9.resolve(opts.from);
4687
+ const fromPath = path.resolve(opts.from);
3446
4688
  if (!await pathExists(fromPath)) {
3447
- process.stderr.write(chalk5.red(`Statements file not found: ${fromPath}
4689
+ process.stderr.write(chalk7.red(`Statements file not found: ${fromPath}
3448
4690
  `));
3449
4691
  process.exit(1);
3450
4692
  }
@@ -3460,9 +4702,9 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
3460
4702
  });
3461
4703
  const json2 = JSON.stringify(doc, null, 2);
3462
4704
  if (opts.out) {
3463
- await writeTextFile(path9.resolve(opts.out), json2 + "\n");
4705
+ await writeTextFile(path.resolve(opts.out), json2 + "\n");
3464
4706
  process.stderr.write(
3465
- chalk5.green("\u2714") + ` Wrote OpenVEX document (${doc.statements.length} statement(s)) to ${path9.resolve(opts.out)}
4707
+ chalk7.green("\u2714") + ` Wrote OpenVEX document (${doc.statements.length} statement(s)) to ${path.resolve(opts.out)}
3466
4708
  `
3467
4709
  );
3468
4710
  } else {
@@ -3470,7 +4712,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
3470
4712
  }
3471
4713
  } catch (err) {
3472
4714
  if (err instanceof VexValidationError) {
3473
- process.stderr.write(chalk5.red(`VEX validation error: ${err.message}
4715
+ process.stderr.write(chalk7.red(`VEX validation error: ${err.message}
3474
4716
  `));
3475
4717
  process.exit(1);
3476
4718
  }
@@ -3645,9 +4887,9 @@ function formatDeltaText(base, current) {
3645
4887
  return lines.join("\n");
3646
4888
  }
3647
4889
  async function readArtifactOrExit(filePath) {
3648
- const absolutePath = path9.resolve(filePath);
4890
+ const absolutePath = path.resolve(filePath);
3649
4891
  if (!await pathExists(absolutePath)) {
3650
- console.error(chalk5.red(`Artifact not found: ${absolutePath}`));
4892
+ console.error(chalk7.red(`Artifact not found: ${absolutePath}`));
3651
4893
  process.exit(1);
3652
4894
  }
3653
4895
  return readJsonFile(absolutePath);
@@ -3656,14 +4898,14 @@ var exportCommand = new Command("export").description("Export scan artifact as S
3656
4898
  const artifact = await readArtifactOrExit(opts.in);
3657
4899
  const format = opts.format.toLowerCase();
3658
4900
  if (format !== "cyclonedx" && format !== "spdx") {
3659
- console.error(chalk5.red("Invalid SBOM format. Use cyclonedx or spdx."));
4901
+ console.error(chalk7.red("Invalid SBOM format. Use cyclonedx or spdx."));
3660
4902
  process.exit(1);
3661
4903
  }
3662
4904
  const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
3663
4905
  const body = JSON.stringify(sbom, null, 2);
3664
4906
  if (opts.out) {
3665
- await writeTextFile(path9.resolve(opts.out), body);
3666
- console.log(chalk5.green("\u2714") + ` SBOM written to ${opts.out}`);
4907
+ await writeTextFile(path.resolve(opts.out), body);
4908
+ console.log(chalk7.green("\u2714") + ` SBOM written to ${opts.out}`);
3667
4909
  } else {
3668
4910
  console.log(body);
3669
4911
  }
@@ -3673,8 +4915,8 @@ var deltaCommand = new Command("delta").description("Show SBOM delta between two
3673
4915
  const current = await readArtifactOrExit(opts.to);
3674
4916
  const report = formatDeltaText(base, current);
3675
4917
  if (opts.out) {
3676
- await writeTextFile(path9.resolve(opts.out), report);
3677
- console.log(chalk5.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
4918
+ await writeTextFile(path.resolve(opts.out), report);
4919
+ console.log(chalk7.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
3678
4920
  } else {
3679
4921
  console.log(report);
3680
4922
  }
@@ -3685,13 +4927,13 @@ var sbomCommand = new Command("sbom").description("Supply-chain evidence: SBOM e
3685
4927
  var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
3686
4928
  "build",
3687
4929
  "status",
3688
- "verify",
3689
4930
  "ask",
3690
4931
  "embed",
3691
4932
  "show",
3692
4933
  "path",
3693
4934
  "tree",
3694
4935
  "impact",
4936
+ "unknowns",
3695
4937
  "tests",
3696
4938
  "facts",
3697
4939
  "guide",
@@ -3717,6 +4959,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
3717
4959
  // Drift-reporting verbs (merged from the Vibgrate CLI).
3718
4960
  "init",
3719
4961
  "scan",
4962
+ "fix",
3720
4963
  "baseline",
3721
4964
  "report",
3722
4965
  "login",
@@ -3739,7 +4982,6 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
3739
4982
  });
3740
4983
  registerBuild(program);
3741
4984
  registerStatus(program);
3742
- registerVerify(program);
3743
4985
  registerAsk(program);
3744
4986
  registerEmbed(program);
3745
4987
  registerShow(program);
@@ -3747,6 +4989,7 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
3747
4989
  registerTree(program);
3748
4990
  registerInsights(program);
3749
4991
  registerImpact(program);
4992
+ registerUnknowns(program);
3750
4993
  registerServe(program);
3751
4994
  registerInstall(program);
3752
4995
  registerShare(program);
@@ -3764,6 +5007,7 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
3764
5007
  registerBundle(program);
3765
5008
  program.addCommand(initCommand);
3766
5009
  program.addCommand(scanCommand);
5010
+ program.addCommand(fixCommand);
3767
5011
  program.addCommand(baselineCommand);
3768
5012
  program.addCommand(reportCommand);
3769
5013
  program.addCommand(loginCommand);
@@ -3828,11 +5072,19 @@ function dispatch(argv, cwd) {
3828
5072
  }
3829
5073
  const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
3830
5074
  if (looksLikeQuestion) return ["ask", ...args];
3831
- if (fs4.existsSync(path9.resolve(cwd, first))) {
5075
+ if (fs5.existsSync(path.resolve(cwd, first))) {
3832
5076
  return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
3833
5077
  }
5078
+ const moved = MOVED_COMMANDS[first];
5079
+ if (moved) {
5080
+ throw new CliError(`\`vg ${first}\` has moved to \`${moved}\``, ExitCode.USAGE_ERROR);
5081
+ }
3834
5082
  return ["ask", ...args];
3835
5083
  }
5084
+ var MOVED_COMMANDS = {
5085
+ verify: "vg build --verify",
5086
+ attest: "vg build --attest"
5087
+ };
3836
5088
  function findFirstPositional(args) {
3837
5089
  for (let i = 0; i < args.length; i++) {
3838
5090
  const a = args[i];
@@ -3848,7 +5100,6 @@ async function main(argv = process.argv) {
3848
5100
  const raw = argv.slice(2);
3849
5101
  if (process.env.NO_COLOR || raw.includes("--no-color")) disableColor();
3850
5102
  const cwd = readCwd(raw);
3851
- const dispatched = dispatch(raw, cwd);
3852
5103
  const program = buildProgram();
3853
5104
  program.exitOverride((err) => {
3854
5105
  throw err;
@@ -3858,6 +5109,7 @@ async function main(argv = process.argv) {
3858
5109
  writeOut: (str) => process.stdout.write(str)
3859
5110
  });
3860
5111
  try {
5112
+ const dispatched = dispatch(raw, cwd);
3861
5113
  await program.parseAsync(dispatched, { from: "user" });
3862
5114
  } catch (err) {
3863
5115
  handleError(err);
@@ -3865,9 +5117,9 @@ async function main(argv = process.argv) {
3865
5117
  }
3866
5118
  function readCwd(raw) {
3867
5119
  const i = raw.findIndex((a) => a === "-C" || a === "--cwd");
3868
- if (i >= 0 && raw[i + 1]) return path9.resolve(raw[i + 1]);
5120
+ if (i >= 0 && raw[i + 1]) return path.resolve(raw[i + 1]);
3869
5121
  const eq = raw.find((a) => a.startsWith("--cwd="));
3870
- if (eq) return path9.resolve(eq.slice("--cwd=".length));
5122
+ if (eq) return path.resolve(eq.slice("--cwd=".length));
3871
5123
  return process.cwd();
3872
5124
  }
3873
5125
  function handleError(err) {