@vibgrate/cli 2026.704.1 → 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, 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-YODVLV37.js';
3
- import { resolvedGrammarFiles, grammarSetVersion } from './chunk-X5YT263H.js';
4
- import { loadAdvancedScanHook, baselineCommand } from './chunk-W4ANUENJ.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-RJHYTD62.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,28 +479,84 @@ 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;
231
- const modelId = resolveEmbedModel();
232
- if (!isModelReady(modelId)) return;
233
- if (countPending(graph, root, modelId) === 0) return;
234
546
  const cli = process.argv[1];
235
547
  if (!cli) return;
548
+ const modelId = resolveEmbedModel();
549
+ const ready = isModelReady(modelId);
550
+ if (ready && countPending(graph, root, modelId) === 0) return;
551
+ const args = ready ? [cli, "embed", "-C", root, "--bg"] : [cli, "embed", "-C", root, "--bg", "--download"];
236
552
  try {
237
- const child = spawn(process.execPath, [cli, "embed", "-C", root, "--bg"], {
238
- detached: true,
239
- stdio: "ignore"
240
- });
553
+ const child = spawn(process.execPath, args, { detached: true, stdio: "ignore" });
241
554
  child.unref();
242
- info(c.dim(" warming the semantic index in the background \u2014 `vg ask` will be instant"));
555
+ info(
556
+ c.dim(
557
+ ready ? " warming the semantic index in the background \u2014 `vg ask` will be instant" : " downloading the semantic model in the background (once) \u2014 `vg ask`/`vg serve` will be instant; disable with --no-warm"
558
+ )
559
+ );
243
560
  } catch {
244
561
  }
245
562
  }
@@ -248,7 +565,7 @@ function writeExport(graph, target) {
248
565
  out(serializeGraph(graph).trimEnd());
249
566
  return;
250
567
  }
251
- const ext = path9.extname(target).toLowerCase();
568
+ const ext = path.extname(target).toLowerCase();
252
569
  let content;
253
570
  switch (ext) {
254
571
  case ".json":
@@ -266,14 +583,14 @@ function writeExport(graph, target) {
266
583
  ExitCode.USAGE_ERROR
267
584
  );
268
585
  }
269
- fs4.mkdirSync(path9.dirname(path9.resolve(target)), { recursive: true });
270
- fs4.writeFileSync(target, content);
586
+ fs5.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
587
+ fs5.writeFileSync(target, content);
271
588
  }
272
589
  function collect(value, prev) {
273
590
  return [...prev, value];
274
591
  }
275
- function rel(root) {
276
- const r = path9.relative(process.cwd(), root);
592
+ function rel2(root) {
593
+ const r = path.relative(process.cwd(), root);
277
594
  return r === "" ? "." : r;
278
595
  }
279
596
  function registerStatus(program) {
@@ -283,10 +600,10 @@ function registerStatus(program) {
283
600
  applyGlobalOptions(cmd);
284
601
  }
285
602
  async function runStatus(global) {
286
- const root = path9.resolve(global.cwd ?? ".");
603
+ const root = path.resolve(global.cwd ?? ".");
287
604
  const graphPath = global.graph ?? defaultGraphPath(root);
288
605
  const graph = loadGraph(root, graphPath);
289
- const hasCache = fs4.existsSync(path9.join(cacheDir(root), "parse-cache.json"));
606
+ const hasCache = fs5.existsSync(path.join(cacheDir(root), "parse-cache.json"));
290
607
  let stale = null;
291
608
  let staleExact = false;
292
609
  if (graph) {
@@ -306,8 +623,8 @@ async function runStatus(global) {
306
623
  }
307
624
  if (global.json) {
308
625
  json({
309
- root: path9.relative(process.cwd(), root) || ".",
310
- graphPath: path9.relative(root, graphPath),
626
+ root: path.relative(process.cwd(), root) || ".",
627
+ graphPath: path.relative(root, graphPath),
311
628
  built: graph !== null,
312
629
  generatedAt: graph?.generatedAt ?? null,
313
630
  counts: graph?.meta.counts ?? null,
@@ -322,11 +639,11 @@ async function runStatus(global) {
322
639
  return;
323
640
  }
324
641
  if (!graph) {
325
- 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))}`);
326
643
  info(` run ${c.bold("vg")} to build one`);
327
644
  return;
328
645
  }
329
- info(`${c.cyan("vg")} \xB7 ${path9.relative(root, graphPath)}`);
646
+ info(`${c.cyan("vg")} \xB7 ${path.relative(root, graphPath)}`);
330
647
  info(` generated ${graph.generatedAt}`);
331
648
  info(
332
649
  ` nodes ${c.bold(String(graph.meta.counts.nodes))} edges ${c.bold(
@@ -342,34 +659,8 @@ async function runStatus(global) {
342
659
  }
343
660
  if (vibgrateDir(root)) ;
344
661
  }
345
- function registerVerify(program) {
346
- 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) {
347
- const global = readGlobal(this);
348
- const root = path9.resolve(global.cwd ?? ".");
349
- const result = await verifyDeterminism({
350
- root,
351
- only: opts.only ? opts.only.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
352
- jobs: opts.jobs ? Number(opts.jobs) : void 0
353
- });
354
- if (global.json) {
355
- json({ ok: result.ok, checks: result.checks, digest: result.digest });
356
- } else {
357
- info(`${c.cyan("vg verify")} \xB7 ${path9.relative(process.cwd(), root) || "."}`);
358
- for (const check of result.checks) {
359
- const mark = check.ok ? c.green("\u2714") : c.red("\u2718");
360
- const detail = check.detail ? c.dim(` (${check.detail})`) : "";
361
- info(` ${mark} ${check.name}${detail}`);
362
- }
363
- info(result.ok ? c.green(` deterministic \xB7 digest ${result.digest.slice(0, 16)}\u2026`) : c.red(" NON-DETERMINISTIC"));
364
- }
365
- if (!result.ok) {
366
- throw new CliError("determinism self-check failed", ExitCode.NON_DETERMINISTIC);
367
- }
368
- });
369
- applyGlobalOptions(cmd);
370
- }
371
662
  function rootOf(global) {
372
- return path9.resolve(global.cwd ?? ".");
663
+ return path.resolve(global.cwd ?? ".");
373
664
  }
374
665
  function requireGraph(global) {
375
666
  const root = rootOf(global);
@@ -456,7 +747,7 @@ function mb(bytes) {
456
747
  return `${Math.round(bytes / (1024 * 1024))} MB`;
457
748
  }
458
749
  function registerEmbed(program) {
459
- const cmd = program.command("embed").description("precompute the semantic index for instant `vg ask` (or --where / --clear)").option("--where", "show where the model is cached, and its size").option("--clear", "remove the downloaded model from the shared cache").addOption(new Option("--bg").hideHelp()).action(async function(opts) {
750
+ const cmd = program.command("embed").description("precompute the semantic index for instant `vg ask` (or --where / --clear)").option("--where", "show where the model is cached, and its size").option("--clear", "remove the downloaded model from the shared cache").addOption(new Option("--bg").hideHelp()).addOption(new Option("--download").hideHelp()).action(async function(opts) {
460
751
  const global = readGlobal(this);
461
752
  const modelId = resolveEmbedModel();
462
753
  if (opts.where) return showWhere(rootOf(global), modelId, global.json);
@@ -477,8 +768,10 @@ function registerEmbed(program) {
477
768
  let reason;
478
769
  const embedder = await loadEmbedder({
479
770
  local: global.local,
480
- noDownload: bg,
481
- // background warm-up never downloads
771
+ // Background warm-ups skip the download unless explicitly asked to fetch
772
+ // (`--bg --download`, spawned by `vg build` so the model is ready by the
773
+ // time `vg serve`/`vg ask` needs it).
774
+ noDownload: bg && opts.download !== true,
482
775
  showDownloadProgress: !bg,
483
776
  onUnavailable: (r) => reason = r
484
777
  });
@@ -651,10 +944,10 @@ function registerTree(program) {
651
944
  applyGlobalOptions(cmd);
652
945
  }
653
946
  function next(index, node, direction) {
654
- 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);
655
948
  const seen = /* @__PURE__ */ new Set();
656
949
  const out2 = [];
657
- for (const { node: n } of rel2) {
950
+ for (const { node: n } of rel3) {
658
951
  if (seen.has(n.id)) continue;
659
952
  seen.add(n.id);
660
953
  out2.push(n);
@@ -830,6 +1123,113 @@ function registerImpact(program) {
830
1123
  });
831
1124
  applyGlobalOptions(cmd);
832
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
+ }
833
1233
  function registerServe(program) {
834
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) {
835
1235
  const global = readGlobal(this);
@@ -842,7 +1242,7 @@ function registerServe(program) {
842
1242
  dedup: opts.dedup === true,
843
1243
  refresh
844
1244
  };
845
- if (!fs4.existsSync(graphPath)) {
1245
+ if (!fs5.existsSync(graphPath)) {
846
1246
  throw new CliError(
847
1247
  `no map found at ${graphPath} \u2014 run \`vg\` to build one first`,
848
1248
  ExitCode.NOT_FOUND
@@ -881,7 +1281,7 @@ async function serveHttp(graphPath, host, port, opts, freshness) {
881
1281
  info(c.red(`vg serve: request error: ${err.message}`));
882
1282
  }
883
1283
  });
884
- await new Promise((resolve15) => httpServer.listen(port, host, resolve15));
1284
+ await new Promise((resolve16) => httpServer.listen(port, host, resolve16));
885
1285
  info(c.dim(`vg \xB7 MCP server on http://${host}:${port}/mcp (read-only, local, ${freshness})`));
886
1286
  await new Promise(() => {
887
1287
  });
@@ -898,9 +1298,9 @@ function registerInstall(program) {
898
1298
  json(ASSISTANTS.map((a) => ({ id: a.id, label: a.label, mcp: !!a.mcp, skill: !!a.skill, nudge: !!a.nudge })));
899
1299
  } else {
900
1300
  info(`${c.cyan("vg install")} \xB7 supported assistants`);
901
- const pad3 = Math.max(...ASSISTANTS.map((a) => a.id.length)) + 2;
1301
+ const pad4 = Math.max(...ASSISTANTS.map((a) => a.id.length)) + 2;
902
1302
  for (const a of ASSISTANTS) {
903
- 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"}`)}`);
904
1304
  }
905
1305
  }
906
1306
  return;
@@ -912,8 +1312,9 @@ function registerInstall(program) {
912
1312
  const smallRepo = graph !== null && fileCount > 0 && fileCount < SMALL_REPO_FILES;
913
1313
  const launch = detectServeLaunch();
914
1314
  const results = targets.map((a) => ({ id: a.id, ...installAssistant(a, { root, hook: opts.hook, smallRepo, launch }) }));
1315
+ const navConfig = writeNavigationConfig(root);
915
1316
  if (global.json) {
916
- json({ root, smallRepo, launch: { command: launch.command, args: launch.args, note: launch.note ?? null }, results });
1317
+ json({ root, smallRepo, navConfig, launch: { command: launch.command, args: launch.args, note: launch.note ?? null }, results });
917
1318
  return;
918
1319
  }
919
1320
  for (const r of results) {
@@ -921,6 +1322,7 @@ function registerInstall(program) {
921
1322
  }
922
1323
  if (launch.note && results.some((r) => r.note)) info(`${c.yellow("!")} ${launch.note}`);
923
1324
  if (smallRepo) info(c.dim(` note: small repo (${fileCount} files) \u2014 nudge says searching is fine; vg is still used for impact/tests`));
1325
+ info(c.dim(` wrote ${navConfig} \u2014 deferred-loading config for Claude-API agents (lower per-step token cost)`));
924
1326
  info(c.dim(" run `vg serve` is wired via MCP; build the map with `vg` if you have not yet"));
925
1327
  });
926
1328
  applyGlobalOptions(install);
@@ -958,8 +1360,8 @@ function registerShare(program) {
958
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) {
959
1361
  const global = readGlobal(this);
960
1362
  const root = rootOf(global);
961
- const gitDir = path9.join(root, ".git");
962
- if (!fs4.existsSync(gitDir)) {
1363
+ const gitDir = path.join(root, ".git");
1364
+ if (!fs5.existsSync(gitDir)) {
963
1365
  throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
964
1366
  }
965
1367
  if (opts.undo) {
@@ -980,23 +1382,23 @@ function registerShare(program) {
980
1382
  applyGlobalOptions(cmd);
981
1383
  }
982
1384
  function writeVibgrateGitignore(root, reports) {
983
- const dir = path9.join(root, ".vibgrate");
984
- fs4.mkdirSync(dir, { recursive: true });
1385
+ const dir = path.join(root, ".vibgrate");
1386
+ fs5.mkdirSync(dir, { recursive: true });
985
1387
  const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl"];
986
1388
  if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
987
- fs4.writeFileSync(path9.join(dir, ".gitignore"), `${lines.join("\n")}
1389
+ fs5.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
988
1390
  `);
989
1391
  }
990
1392
  function writeGitAttributes(root) {
991
- const file = path9.join(root, ".gitattributes");
1393
+ const file = path.join(root, ".gitattributes");
992
1394
  const line = ".vibgrate/graph.json merge=vg";
993
- let content = fs4.existsSync(file) ? fs4.readFileSync(file, "utf8") : "";
1395
+ let content = fs5.existsSync(file) ? fs5.readFileSync(file, "utf8") : "";
994
1396
  if (!content.split("\n").some((l) => l.trim() === line)) {
995
1397
  content = content.length ? `${content.replace(/\s*$/, "")}
996
1398
  ${line}
997
1399
  ` : `${line}
998
1400
  `;
999
- fs4.writeFileSync(file, content);
1401
+ fs5.writeFileSync(file, content);
1000
1402
  }
1001
1403
  }
1002
1404
  function configureMergeDriver(root) {
@@ -1005,8 +1407,8 @@ function configureMergeDriver(root) {
1005
1407
  }
1006
1408
  function installPreCommitHook(root) {
1007
1409
  const hooksDir = hookDir(root);
1008
- fs4.mkdirSync(hooksDir, { recursive: true });
1009
- const file = path9.join(hooksDir, "pre-commit");
1410
+ fs5.mkdirSync(hooksDir, { recursive: true });
1411
+ const file = path.join(hooksDir, "pre-commit");
1010
1412
  const block = [
1011
1413
  HOOK_BEGIN,
1012
1414
  "if command -v vg >/dev/null 2>&1; then",
@@ -1015,7 +1417,7 @@ function installPreCommitHook(root) {
1015
1417
  "fi",
1016
1418
  HOOK_END
1017
1419
  ].join("\n");
1018
- 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";
1019
1421
  const re = new RegExp(`${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}`);
1020
1422
  if (re.test(content)) content = content.replace(re, block);
1021
1423
  else content = `${content.replace(/\s*$/, "")}
@@ -1024,21 +1426,21 @@ ${block}
1024
1426
  `;
1025
1427
  if (!content.startsWith("#!")) content = `#!/bin/sh
1026
1428
  ${content}`;
1027
- fs4.writeFileSync(file, content);
1028
- fs4.chmodSync(file, 493);
1429
+ fs5.writeFileSync(file, content);
1430
+ fs5.chmodSync(file, 493);
1029
1431
  }
1030
1432
  function undo(root) {
1031
- const hookFile = path9.join(hookDir(root), "pre-commit");
1032
- if (fs4.existsSync(hookFile)) {
1433
+ const hookFile = path.join(hookDir(root), "pre-commit");
1434
+ if (fs5.existsSync(hookFile)) {
1033
1435
  const re = new RegExp(`\\n*${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}\\n*`);
1034
- const next2 = fs4.readFileSync(hookFile, "utf8").replace(re, "\n");
1035
- if (next2.trim() === "#!/bin/sh") fs4.rmSync(hookFile);
1036
- 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);
1037
1439
  }
1038
- const attr = path9.join(root, ".gitattributes");
1039
- if (fs4.existsSync(attr)) {
1040
- const next2 = fs4.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
1041
- 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);
1042
1444
  }
1043
1445
  try {
1044
1446
  git(root, ["config", "--remove-section", "merge.vg"]);
@@ -1048,10 +1450,10 @@ function undo(root) {
1048
1450
  function hookDir(root) {
1049
1451
  try {
1050
1452
  const configured = git(root, ["config", "--get", "core.hooksPath"]).trim();
1051
- if (configured) return path9.resolve(root, configured);
1453
+ if (configured) return path.resolve(root, configured);
1052
1454
  } catch {
1053
1455
  }
1054
- return path9.join(root, ".git", "hooks");
1456
+ return path.join(root, ".git", "hooks");
1055
1457
  }
1056
1458
  function git(root, args) {
1057
1459
  return execFileSync("git", args, { cwd: root, encoding: "utf8" });
@@ -1082,7 +1484,7 @@ function registerBenchmark(program) {
1082
1484
  if (tokenReduction.questions.length) {
1083
1485
  info(` token reduction vs grep/read baseline (${c.dim("estimates")}):`);
1084
1486
  for (const q of tokenReduction.questions) {
1085
- 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}"`);
1086
1488
  }
1087
1489
  info(` ${c.bold(`aggregate \u2248 ${tokenReduction.aggregateRatio.toFixed(1)}\xD7 fewer tokens`)} ${c.dim("(honest estimate, scales with repo size)")}`);
1088
1490
  }
@@ -1171,7 +1573,7 @@ function grepBaselineTokens(term, files) {
1171
1573
  if (f.size > 512 * 1024) continue;
1172
1574
  let content = "";
1173
1575
  try {
1174
- content = fs4.readFileSync(f.abs, "utf8");
1576
+ content = fs5.readFileSync(f.abs, "utf8");
1175
1577
  } catch {
1176
1578
  continue;
1177
1579
  }
@@ -1181,7 +1583,7 @@ function grepBaselineTokens(term, files) {
1181
1583
  }
1182
1584
  function safeSize(abs) {
1183
1585
  try {
1184
- return fs4.statSync(abs).size;
1586
+ return fs5.statSync(abs).size;
1185
1587
  } catch {
1186
1588
  return 0;
1187
1589
  }
@@ -1198,7 +1600,7 @@ function round(x) {
1198
1600
  function round6(x) {
1199
1601
  return Math.round(x * 1e6) / 1e6;
1200
1602
  }
1201
- function pad2(s, n) {
1603
+ function pad3(s, n) {
1202
1604
  return s.padStart(n, " ");
1203
1605
  }
1204
1606
  function registerTests(program) {
@@ -1511,11 +1913,11 @@ function normalizeStandards(raw) {
1511
1913
  return { banned };
1512
1914
  }
1513
1915
  function loadStandards(root) {
1514
- for (const rel2 of STANDARDS_FILES) {
1515
- const p = path9__default.join(root, rel2);
1916
+ for (const rel3 of STANDARDS_FILES) {
1917
+ const p = path__default.join(root, rel3);
1516
1918
  let text;
1517
1919
  try {
1518
- text = fs4__default.readFileSync(p, "utf8");
1920
+ text = fs5__default.readFileSync(p, "utf8");
1519
1921
  } catch {
1520
1922
  continue;
1521
1923
  }
@@ -1710,7 +2112,7 @@ async function addCmd(root, rest, opts, asJson) {
1710
2112
  }
1711
2113
  function readPackageVersion(root) {
1712
2114
  try {
1713
- 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"));
1714
2116
  return typeof pkg.version === "string" ? pkg.version : void 0;
1715
2117
  } catch {
1716
2118
  return void 0;
@@ -1718,7 +2120,7 @@ function readPackageVersion(root) {
1718
2120
  }
1719
2121
  function readOptionalFile(p) {
1720
2122
  try {
1721
- const t = fs4__default.readFileSync(p, "utf8");
2123
+ const t = fs5__default.readFileSync(p, "utf8");
1722
2124
  return t.trim() ? t : void 0;
1723
2125
  } catch {
1724
2126
  return void 0;
@@ -1733,8 +2135,8 @@ async function publishCmd(root, rest, opts, global, asJson) {
1733
2135
  if (!parsed) throw new CliError("publishing a private library requires a DSN \u2014 run `vibgrate login` or set VIBGRATE_DSN", ExitCode.USAGE_ERROR);
1734
2136
  const version = (opts.version || readPackageVersion(root) || "").trim();
1735
2137
  if (!version) throw usageError("a --version is required (or run from a package whose package.json has a version)");
1736
- const readme = readOptionalFile(opts.readme ? path9__default.resolve(opts.readme) : path9__default.join(root, "README.md"));
1737
- 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;
1738
2140
  if (!readme && !dts) throw usageError("nothing to publish \u2014 no README.md found and no --dts given");
1739
2141
  const result = await publishPrivateLibrary(
1740
2142
  { name, version, readme, dts, language: opts.language },
@@ -1846,7 +2248,7 @@ async function showCmd(root, name, budget, asJson, opts = {}) {
1846
2248
  out(doc);
1847
2249
  }
1848
2250
  function registerExport(program) {
1849
- 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) {
1850
2252
  const global = readGlobal(this);
1851
2253
  const { graph } = requireGraph(global);
1852
2254
  const root = rootOf(global);
@@ -1863,8 +2265,8 @@ function registerExport(program) {
1863
2265
  out(content.trimEnd());
1864
2266
  return;
1865
2267
  }
1866
- fs4.mkdirSync(path9.dirname(path9.resolve(file)), { recursive: true });
1867
- fs4.writeFileSync(file, content);
2268
+ fs5.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
2269
+ fs5.writeFileSync(file, content);
1868
2270
  info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
1869
2271
  });
1870
2272
  applyGlobalOptions(cmd);
@@ -1874,37 +2276,37 @@ function detectFormat(file) {
1874
2276
  if (lower.endsWith(".cdx.json")) return "cyclonedx";
1875
2277
  if (lower.endsWith(".spdx.json")) return "spdx";
1876
2278
  if (file === "-") return "json";
1877
- return formatForExt(path9.extname(file));
2279
+ return formatForExt(path.extname(file));
1878
2280
  }
1879
2281
  function registerBundle(program) {
1880
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) {
1881
2283
  const global = readGlobal(this);
1882
2284
  const root = rootOf(global);
1883
- const outDir = path9.resolve(root, opts.out ?? "vg-bundle");
2285
+ const outDir = path.resolve(root, opts.out ?? "vg-bundle");
1884
2286
  let grammarFiles;
1885
2287
  try {
1886
2288
  grammarFiles = resolvedGrammarFiles();
1887
2289
  } catch {
1888
2290
  throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
1889
2291
  }
1890
- fs4.mkdirSync(path9.join(outDir, "grammars"), { recursive: true });
2292
+ fs5.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
1891
2293
  let grammarCount = 0;
1892
2294
  for (const { fileName, absPath } of grammarFiles) {
1893
- fs4.copyFileSync(absPath, path9.join(outDir, "grammars", fileName));
2295
+ fs5.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
1894
2296
  grammarCount++;
1895
2297
  }
1896
2298
  const included = [`grammars/ (${grammarCount} .wasm)`];
1897
2299
  const graphSrc = global.graph ?? defaultGraphPath(root);
1898
- if (fs4.existsSync(graphSrc)) {
1899
- fs4.copyFileSync(graphSrc, path9.join(outDir, "graph.json"));
2300
+ if (fs5.existsSync(graphSrc)) {
2301
+ fs5.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
1900
2302
  included.push("graph.json");
1901
2303
  }
1902
2304
  const cat = catalogPath(root);
1903
- if (fs4.existsSync(cat)) {
1904
- fs4.copyFileSync(cat, path9.join(outDir, "vibgrate.lib.json"));
2305
+ if (fs5.existsSync(cat)) {
2306
+ fs5.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
1905
2307
  included.push("vibgrate.lib.json");
1906
- if (fs4.existsSync(libDir(root))) {
1907
- 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 });
1908
2310
  included.push("lib/");
1909
2311
  }
1910
2312
  }
@@ -1916,38 +2318,38 @@ function registerBundle(program) {
1916
2318
  included,
1917
2319
  usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
1918
2320
  };
1919
- fs4.writeFileSync(path9.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
2321
+ fs5.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
1920
2322
  `);
1921
2323
  if (global.json) {
1922
- json({ out: path9.relative(root, outDir), ...manifest });
2324
+ json({ out: path.relative(root, outDir), ...manifest });
1923
2325
  return;
1924
2326
  }
1925
- info(`${c.cyan("vg bundle")} \xB7 wrote ${path9.relative(root, outDir)}/`);
2327
+ info(`${c.cyan("vg bundle")} \xB7 wrote ${path.relative(root, outDir)}/`);
1926
2328
  for (const i of included) info(` ${c.green("\u2714")} ${i}`);
1927
- 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"`));
1928
2330
  });
1929
2331
  applyGlobalOptions(cmd);
1930
2332
  }
1931
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) => {
1932
- const rootDir = path9.resolve(targetPath);
1933
- const vibgrateDir2 = path9.join(rootDir, ".vibgrate");
2334
+ const rootDir = path.resolve(targetPath);
2335
+ const vibgrateDir2 = path.join(rootDir, ".vibgrate");
1934
2336
  await ensureDir(vibgrateDir2);
1935
- console.log(chalk5.green("\u2714") + ` Created ${chalk5.bold(".vibgrate/")} directory`);
1936
- 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");
1937
2339
  if (await pathExists(configPath)) {
1938
- console.log(chalk5.dim(" vibgrate.config.ts already exists, skipping"));
2340
+ console.log(chalk7.dim(" vibgrate.config.ts already exists, skipping"));
1939
2341
  } else {
1940
2342
  await writeDefaultConfig(rootDir);
1941
- console.log(chalk5.green("\u2714") + ` Created ${chalk5.bold("vibgrate.config.ts")}`);
2343
+ console.log(chalk7.green("\u2714") + ` Created ${chalk7.bold("vibgrate.config.ts")}`);
1942
2344
  }
1943
2345
  if (opts.baseline) {
1944
- const { runBaseline } = await import('./baseline-FESLZK5O.js');
2346
+ const { runBaseline } = await import('./baseline-RRO4NNNF.js');
1945
2347
  await runBaseline(rootDir);
1946
2348
  }
1947
2349
  console.log("");
1948
- console.log(chalk5.bold("Next steps:"));
1949
- console.log(` ${chalk5.cyan("vibgrate scan")} Scan for upgrade drift`);
1950
- 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`);
1951
2353
  console.log("");
1952
2354
  });
1953
2355
 
@@ -1974,7 +2376,7 @@ var AI_ASSISTANT_DETECTORS = [
1974
2376
  async function detectAiAssistant(rootDir) {
1975
2377
  for (const detector of AI_ASSISTANT_DETECTORS) {
1976
2378
  for (const file of detector.files) {
1977
- if (await pathExists$1(path9.join(rootDir, file))) {
2379
+ if (await pathExists$1(path.join(rootDir, file))) {
1978
2380
  return detector.name;
1979
2381
  }
1980
2382
  }
@@ -1982,22 +2384,22 @@ async function detectAiAssistant(rootDir) {
1982
2384
  return null;
1983
2385
  }
1984
2386
  function printAiContextPrompt(detectedAssistant) {
1985
- const teal2 = chalk5.hex("#3FB0A4");
1986
- const mint2 = chalk5.hex("#4FE3C1");
2387
+ const teal2 = chalk7.hex("#3FB0A4");
2388
+ const mint2 = chalk7.hex("#4FE3C1");
1987
2389
  const cli = resolveCliInvocation();
1988
2390
  const installArgs = detectedAssistant ? `install ${detectedAssistant}` : "install --all";
1989
2391
  const installCmd = `${cli} ${installArgs}`;
1990
- const detectedNote = detectedAssistant ? chalk5.dim(` (${detectedAssistant} config detected)`) : "";
2392
+ const detectedNote = detectedAssistant ? chalk7.dim(` (${detectedAssistant} config detected)`) : "";
1991
2393
  console.log("");
1992
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"));
1993
- 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"));
1994
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"));
1995
2397
  console.log("");
1996
- console.log(" " + chalk5.bold.white(installCmd) + detectedNote);
2398
+ console.log(" " + chalk7.bold.white(installCmd) + detectedNote);
1997
2399
  console.log("");
1998
- console.log(" " + teal2("\xB7") + " " + chalk5.white("Code map in your assistant") + chalk5.dim(" \u2014 call trees, impact surfaces, import paths"));
1999
- console.log(" " + teal2("\xB7") + " " + chalk5.white("Offline drift") + chalk5.dim(" \u2014 DriftScore and upgrade priorities, right inside your editor"));
2000
- 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"));
2001
2403
  console.log("");
2002
2404
  }
2003
2405
 
@@ -2008,14 +2410,14 @@ function shouldBuildCodeMap(opts) {
2008
2410
  async function autoPush(artifact, rootDir, opts) {
2009
2411
  const dsn = resolveDsn(opts.dsn);
2010
2412
  if (!dsn) {
2011
- console.error(chalk5.red("No DSN provided for push."));
2012
- 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.'));
2013
2415
  if (opts.strict) process.exit(1);
2014
2416
  return;
2015
2417
  }
2016
2418
  const parsed = parseDsn(dsn);
2017
2419
  if (!parsed) {
2018
- console.error(chalk5.red("Invalid DSN format."));
2420
+ console.error(chalk7.red("Invalid DSN format."));
2019
2421
  if (opts.strict) process.exit(1);
2020
2422
  return;
2021
2423
  }
@@ -2026,7 +2428,7 @@ async function autoPush(artifact, rootDir, opts) {
2026
2428
  try {
2027
2429
  host = resolveIngestHost(opts.region);
2028
2430
  } catch (e) {
2029
- console.error(chalk5.red(e instanceof Error ? e.message : String(e)));
2431
+ console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
2030
2432
  if (opts.strict) process.exit(1);
2031
2433
  return;
2032
2434
  }
@@ -2034,7 +2436,7 @@ async function autoPush(artifact, rootDir, opts) {
2034
2436
  const originalSize = JSON.stringify(artifact).length;
2035
2437
  const compressedSize = body.length;
2036
2438
  const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
2037
- 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)`));
2038
2440
  try {
2039
2441
  const { response, host: uploadedHost } = await uploadScanArtifact({
2040
2442
  scheme: parsed.scheme,
@@ -2054,36 +2456,36 @@ async function autoPush(artifact, rootDir, opts) {
2054
2456
  const result = await response.json();
2055
2457
  if (result.unchanged) {
2056
2458
  console.log(
2057
- 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).`
2058
2460
  );
2059
2461
  if (result.previousIngestId) {
2060
2462
  emitIngestIdLine(result.previousIngestId, { unchanged: true });
2061
2463
  const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.previousIngestId}`;
2062
- console.log(chalk5.dim(` Previous report: ${dashUrl}`));
2464
+ console.log(chalk7.dim(` Previous report: ${dashUrl}`));
2063
2465
  } else if (opts.strict) {
2064
- 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."));
2065
2467
  process.exit(1);
2066
2468
  }
2067
2469
  return;
2068
2470
  }
2069
- 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"})`);
2070
2472
  if (result.ingestId) {
2071
2473
  emitIngestIdLine(result.ingestId);
2072
2474
  const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.ingestId}`;
2073
2475
  const CLEAR_LINE = process.platform === "win32" ? "\x1B[0G\x1B[2K" : "";
2074
2476
  console.log("");
2075
- 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."));
2076
2478
  console.log("");
2077
- 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"));
2078
- console.log(CLEAR_LINE + chalk5.bold(" \u{1F4CA} View Scan Report"));
2079
- console.log(CLEAR_LINE + " " + chalk5.underline.cyan(dashUrl));
2080
- 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"));
2081
2483
  console.log("");
2082
2484
  console.log("");
2083
2485
  }
2084
2486
  } catch (e) {
2085
2487
  const msg = e instanceof Error ? e.message : String(e);
2086
- console.error(chalk5.red(`Upload failed: ${msg}`));
2488
+ console.error(chalk7.red(`Upload failed: ${msg}`));
2087
2489
  if (opts.strict) process.exit(1);
2088
2490
  }
2089
2491
  }
@@ -2095,15 +2497,15 @@ function reportStandards(rootDir) {
2095
2497
  if (!loaded.policy) return;
2096
2498
  const violations = checkStandards(loaded.policy, inventory(rootDir).records);
2097
2499
  if (violations.length === 0) {
2098
- console.log(chalk5.green("\u2714") + " Standards: no banned dependencies in use");
2500
+ console.log(chalk7.green("\u2714") + " Standards: no banned dependencies in use");
2099
2501
  return;
2100
2502
  }
2101
- console.log(chalk5.red(`
2503
+ console.log(chalk7.red(`
2102
2504
  \u26A0 Standards: ${violations.length} banned dependency(ies) in use`));
2103
2505
  for (const v of violations) {
2104
- const fix = v.use ? chalk5.dim(` \u2192 use ${v.use}`) : "";
2105
- const why = v.reason ? chalk5.dim(` (${v.reason})`) : "";
2106
- 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}`);
2107
2509
  }
2108
2510
  }
2109
2511
  function parseNonNegativeNumber(value, label) {
@@ -2120,16 +2522,18 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2120
2522
  collectExcludes,
2121
2523
  []
2122
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) => {
2123
- const rootDir = path9.resolve(targetPath);
2525
+ const rootDir = path.resolve(targetPath);
2124
2526
  if (!await pathExists$1(rootDir)) {
2125
- console.error(chalk5.red(`Path does not exist: ${rootDir}`));
2527
+ console.error(chalk7.red(`Path does not exist: ${rootDir}`));
2126
2528
  process.exit(1);
2127
2529
  }
2128
2530
  const hasDsn = !!resolveDsn(opts.dsn);
2129
2531
  const willPush = !opts.offline && (opts.push || hasDsn);
2130
2532
  const noLocalArtifacts = opts.localArtifacts === false;
2131
- 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"));
2132
2534
  let pinnedRegion;
2535
+ let planTier;
2536
+ let upgradeUrl;
2133
2537
  if (willPush && hasDsn) {
2134
2538
  const dsn = resolveDsn(opts.dsn);
2135
2539
  const parsed = parseDsn(dsn);
@@ -2144,10 +2548,12 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2144
2548
  vcsSha: fingerprint.vcsSha
2145
2549
  });
2146
2550
  pinnedRegion = preflight.region;
2551
+ planTier = preflight.plan?.tier;
2552
+ upgradeUrl = preflight.upgradeUrl ?? `https://${dashHostForIngestHost(preflight.ingestHost ?? ingestHost)}/${parsed.workspaceId}`;
2147
2553
  if (preflight.vm && !preflight.vm.allowed) {
2148
- console.error(chalk5.red(preflight.error ?? "VM meter usage exhausted"));
2554
+ console.error(chalk7.red(preflight.error ?? "VM meter usage exhausted"));
2149
2555
  console.error(
2150
- chalk5.dim(
2556
+ chalk7.dim(
2151
2557
  `VM minutes: ${preflight.vm.used}/${preflight.vm.limit} (${preflight.plan.label} plan) \u2014 enable overages or upgrade your plan.`
2152
2558
  )
2153
2559
  );
@@ -2155,25 +2561,25 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2155
2561
  }
2156
2562
  if (preflight.repositories && !preflight.repositories.allowed) {
2157
2563
  console.error(
2158
- chalk5.red(
2564
+ chalk7.red(
2159
2565
  preflight.error ?? `Repository limit reached for the ${preflight.plan.label} plan \u2014 cannot scan a new repository.`
2160
2566
  )
2161
2567
  );
2162
2568
  const max = preflight.repositories.max < 0 ? "unlimited" : String(preflight.repositories.max);
2163
2569
  console.error(
2164
- chalk5.dim(
2570
+ chalk7.dim(
2165
2571
  `Repositories: ${preflight.repositories.total}/${max} (${preflight.plan.label} plan) \u2014 archive a repository or upgrade your plan.`
2166
2572
  )
2167
2573
  );
2168
2574
  if (preflight.upgradeUrl) {
2169
- console.error(chalk5.dim(` Upgrade: ${preflight.upgradeUrl}`));
2575
+ console.error(chalk7.dim(` Upgrade: ${preflight.upgradeUrl}`));
2170
2576
  }
2171
2577
  process.exit(1);
2172
2578
  }
2173
2579
  if (preflight.status === "error" || !preflight.scans.allowed) {
2174
- 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."));
2175
2581
  console.error(
2176
- chalk5.dim(
2582
+ chalk7.dim(
2177
2583
  `Credits: ${preflight.scans.used}/${preflight.scans.limit} (${preflight.plan.label} plan)`
2178
2584
  )
2179
2585
  );
@@ -2181,27 +2587,27 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2181
2587
  process.exit(1);
2182
2588
  }
2183
2589
  console.log(
2184
- chalk5.dim(
2590
+ chalk7.dim(
2185
2591
  `Plan: ${preflight.plan.label} \u2014 scan credits ${preflight.scans.used}/${preflight.scans.limit} this month`
2186
2592
  )
2187
2593
  );
2188
2594
  if (preflight.repository?.unchanged && !opts.force) {
2189
2595
  console.log(
2190
- 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.`
2191
2597
  );
2192
2598
  if (preflight.repository.lastIngestId) {
2193
2599
  emitIngestIdLine(preflight.repository.lastIngestId, { unchanged: true });
2194
2600
  const dashUrl = `https://${dashHostForIngestHost(ingestHost)}/${parsed.workspaceId}/scan/${preflight.repository.lastIngestId}`;
2195
- console.log(chalk5.dim(` Latest report: ${dashUrl}`));
2601
+ console.log(chalk7.dim(` Latest report: ${dashUrl}`));
2196
2602
  } else if (opts.strict) {
2197
- 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."));
2198
2604
  process.exit(1);
2199
2605
  }
2200
2606
  return;
2201
2607
  }
2202
2608
  } catch (e) {
2203
2609
  const msg = e instanceof Error ? e.message : String(e);
2204
- console.error(chalk5.yellow(`Preflight check failed: ${msg}`));
2610
+ console.error(chalk7.yellow(`Preflight check failed: ${msg}`));
2205
2611
  if (opts.strict) process.exit(1);
2206
2612
  }
2207
2613
  }
@@ -2234,6 +2640,17 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2234
2640
  repositoryName: opts.repositoryName?.trim() || void 0,
2235
2641
  force: opts.force,
2236
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,
2237
2654
  // Prefix for the upsell panel's `login → push` hint — `vg` when installed,
2238
2655
  // `npx @vibgrate/cli` when the user ran via npx (where bare `vg` fails).
2239
2656
  invocation: resolveCliInvocation()
@@ -2264,24 +2681,24 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2264
2681
  const hasErrors = artifact.findings.some((f) => f.level === "error");
2265
2682
  const hasWarnings = artifact.findings.some((f) => f.level === "warning");
2266
2683
  if (opts.failOn === "error" && hasErrors) {
2267
- console.error(chalk5.red(`
2684
+ console.error(chalk7.red(`
2268
2685
  Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
2269
2686
  process.exit(2);
2270
2687
  }
2271
2688
  if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
2272
- console.error(chalk5.red(`
2689
+ console.error(chalk7.red(`
2273
2690
  Failing: findings detected at warn level or above.`));
2274
2691
  process.exit(2);
2275
2692
  }
2276
2693
  }
2277
2694
  if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
2278
- console.error(chalk5.red(`
2695
+ console.error(chalk7.red(`
2279
2696
  Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
2280
2697
  process.exit(2);
2281
2698
  }
2282
2699
  if (scanOpts.driftWorseningPercent !== void 0) {
2283
2700
  if (artifact.delta === void 0) {
2284
- 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."));
2285
2702
  process.exit(2);
2286
2703
  }
2287
2704
  if (artifact.delta > 0) {
@@ -2289,7 +2706,7 @@ Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget
2289
2706
  const denominator = Math.max(Math.abs(baselineScore), 1e-4);
2290
2707
  const worseningPercent = artifact.delta / denominator * 100;
2291
2708
  if (worseningPercent > scanOpts.driftWorseningPercent) {
2292
- console.error(chalk5.red(`
2709
+ console.error(chalk7.red(`
2293
2710
  Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (threshold ${scanOpts.driftWorseningPercent}%).`));
2294
2711
  process.exit(2);
2295
2712
  }
@@ -2304,22 +2721,853 @@ Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (thr
2304
2721
  printAiContextPrompt(detectedAssistant);
2305
2722
  }
2306
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
+ }
2307
3555
  function formatText(artifact) {
2308
3556
  const lines = [];
2309
- const teal2 = chalk5.hex("#3FB0A4");
2310
- const mint2 = chalk5.hex("#4FE3C1");
3557
+ const teal2 = chalk7.hex("#3FB0A4");
3558
+ const mint2 = chalk7.hex("#4FE3C1");
2311
3559
  lines.push("");
2312
3560
  lines.push(" " + teal2("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E") + mint2("\u279C"));
2313
- lines.push(" " + chalk5.dim("\u2524") + teal2("\u2502") + " " + mint2("\u25FC") + " " + mint2("\u25FC") + " " + teal2("\u2502") + chalk5.dim("\u251C") + " " + chalk5.bold.white("vibgrate"));
2314
- 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}`));
2315
3563
  lines.push(" " + teal2("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
2316
3564
  lines.push("");
2317
3565
  lines.push(...titleBox("Vibgrate Drift Report", teal2));
2318
3566
  lines.push("");
2319
3567
  for (const project of artifact.projects) {
2320
- 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}`));
2321
3569
  if (project.runtime) {
2322
- 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)");
2323
3571
  lines.push(` Runtime: ${project.runtime}${behindStr}`);
2324
3572
  }
2325
3573
  if (project.targetFramework) {
@@ -2328,7 +3576,7 @@ function formatText(artifact) {
2328
3576
  if (project.frameworks.length > 0) {
2329
3577
  lines.push(" Frameworks:");
2330
3578
  for (const fw of project.frameworks) {
2331
- 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");
2332
3580
  lines.push(` ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
2333
3581
  }
2334
3582
  }
@@ -2336,13 +3584,13 @@ function formatText(artifact) {
2336
3584
  const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
2337
3585
  if (total > 0) {
2338
3586
  lines.push(" Dependencies:");
2339
- 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`)}`);
2340
3588
  }
2341
3589
  lines.push("");
2342
3590
  }
2343
3591
  if (artifact.delta !== void 0) {
2344
- const deltaStr = artifact.delta > 0 ? chalk5.red(`+${artifact.delta}`) : artifact.delta < 0 ? chalk5.green(`${artifact.delta}`) : chalk5.dim("0");
2345
- 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)");
2346
3594
  lines.push("");
2347
3595
  }
2348
3596
  if (artifact.extended) {
@@ -2353,15 +3601,15 @@ function formatText(artifact) {
2353
3601
  const warnings = artifact.findings.filter((f) => f.level === "warning");
2354
3602
  const notes = artifact.findings.filter((f) => f.level === "note");
2355
3603
  const summary = [
2356
- errors.length > 0 ? chalk5.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
2357
- warnings.length > 0 ? chalk5.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
2358
- notes.length > 0 ? chalk5.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
2359
- ].filter(Boolean).join(chalk5.dim(", "));
2360
- 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})`));
2361
3609
  for (const f of artifact.findings) {
2362
- 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");
2363
3611
  lines.push(` ${icon} ${f.message}`);
2364
- lines.push(chalk5.dim(` ${f.ruleId} in ${f.location}`));
3612
+ lines.push(chalk7.dim(` ${f.ruleId} in ${f.location}`));
2365
3613
  }
2366
3614
  lines.push("");
2367
3615
  }
@@ -2371,10 +3619,10 @@ function formatText(artifact) {
2371
3619
  lines.push("");
2372
3620
  for (let i = 0; i < actions.length; i++) {
2373
3621
  const a = actions[i];
2374
- const num = chalk5.bold.cyan(` ${i + 1}.`);
2375
- lines.push(`${num} ${chalk5.bold(a.title)}`);
2376
- lines.push(chalk5.dim(` ${a.explanation}`));
2377
- 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)}`);
2378
3626
  lines.push("");
2379
3627
  }
2380
3628
  }
@@ -2386,30 +3634,30 @@ function formatText(artifact) {
2386
3634
  lines.push("");
2387
3635
  for (const solution of artifact.solutions) {
2388
3636
  const solScore = solution.drift?.score;
2389
- const color = typeof solScore === "number" ? solScore <= 30 ? chalk5.green : solScore <= 60 ? chalk5.yellow : chalk5.red : chalk5.dim;
2390
- 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")}`);
2391
3639
  }
2392
3640
  lines.push("");
2393
3641
  }
2394
- const scoreColor = artifact.drift.score <= 30 ? chalk5.green : artifact.drift.score <= 60 ? chalk5.yellow : chalk5.red;
2395
- 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"));
2396
3644
  lines.push("");
2397
- lines.push(chalk5.bold(" Drift Score: ") + scoreColor.bold(`${artifact.drift.score}/100`));
2398
- lines.push(chalk5.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
2399
- 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}`);
2400
3648
  if (artifact.vcs) {
2401
3649
  const vcsParts = [artifact.vcs.type];
2402
3650
  if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
2403
- if (artifact.vcs.shortSha) vcsParts.push(chalk5.dim(artifact.vcs.shortSha));
2404
- 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(" "));
2405
3653
  }
2406
3654
  lines.push("");
2407
3655
  const m = new Set(artifact.drift.measured ?? ["runtime", "framework", "dependency", "eol"]);
2408
- lines.push(" " + chalk5.bold.underline("Score Breakdown"));
2409
- lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk5.dim("n/a")}`);
2410
- lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk5.dim("n/a")}`);
2411
- lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk5.dim("n/a")}`);
2412
- 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")}`);
2413
3661
  lines.push("");
2414
3662
  const scannedParts = [`Scanned at ${artifact.timestamp}`];
2415
3663
  if (artifact.durationMs !== void 0) {
@@ -2423,18 +3671,18 @@ function formatText(artifact) {
2423
3671
  scannedParts.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files`);
2424
3672
  scannedParts.push(`${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
2425
3673
  }
2426
- lines.push(chalk5.dim(` ${scannedParts.join(" \xB7 ")}`));
3674
+ lines.push(chalk7.dim(` ${scannedParts.join(" \xB7 ")}`));
2427
3675
  lines.push("");
2428
3676
  return lines.join("\n");
2429
3677
  }
2430
3678
  function riskBadge(level) {
2431
3679
  switch (level) {
2432
3680
  case "low":
2433
- return chalk5.bgGreen.black(" LOW ");
3681
+ return chalk7.bgGreen.black(" LOW ");
2434
3682
  case "moderate":
2435
- return chalk5.bgYellow.black(" MODERATE ");
3683
+ return chalk7.bgYellow.black(" MODERATE ");
2436
3684
  case "high":
2437
- return chalk5.bgRed.white(" HIGH ");
3685
+ return chalk7.bgRed.white(" HIGH ");
2438
3686
  default:
2439
3687
  return level;
2440
3688
  }
@@ -2469,11 +3717,11 @@ function formatExtended(ext) {
2469
3717
  const inv = ext.toolingInventory;
2470
3718
  const categories = Object.entries(inv).filter(([, items]) => items.length > 0);
2471
3719
  if (categories.length > 0) {
2472
- lines.push(chalk5.bold.underline(" Tech Stack"));
3720
+ lines.push(chalk7.bold.underline(" Tech Stack"));
2473
3721
  for (const [cat, items] of categories) {
2474
3722
  const label = CATEGORY_LABELS[cat] ?? cat;
2475
- const names = items.map((i) => chalk5.white(i.name)).join(chalk5.dim(", "));
2476
- 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}`);
2477
3725
  }
2478
3726
  lines.push("");
2479
3727
  }
@@ -2482,14 +3730,14 @@ function formatExtended(ext) {
2482
3730
  const svc = ext.serviceDependencies;
2483
3731
  const categories = Object.entries(svc).filter(([, items]) => items.length > 0);
2484
3732
  if (categories.length > 0) {
2485
- lines.push(chalk5.bold.underline(" Services & Integrations"));
3733
+ lines.push(chalk7.bold.underline(" Services & Integrations"));
2486
3734
  for (const [cat, items] of categories) {
2487
3735
  const label = CATEGORY_LABELS[cat] ?? cat;
2488
3736
  const names = items.map((i) => {
2489
- const ver = i.version ? chalk5.dim(` ${i.version}`) : "";
2490
- return chalk5.white(i.name) + ver;
2491
- }).join(chalk5.dim(", "));
2492
- 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}`);
2493
3741
  }
2494
3742
  lines.push("");
2495
3743
  }
@@ -2497,19 +3745,19 @@ function formatExtended(ext) {
2497
3745
  if (ext.breakingChangeExposure) {
2498
3746
  const bc = ext.breakingChangeExposure;
2499
3747
  if (bc.deprecatedPackages.length > 0 || bc.legacyPolyfills.length > 0) {
2500
- lines.push(chalk5.bold.underline(" Breaking Change Exposure"));
2501
- 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;
2502
3750
  lines.push(` Exposure Score: ${exposureColor.bold(`${bc.exposureScore}/100`)}`);
2503
3751
  if (bc.deprecatedPackages.length > 0) {
2504
- 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(", ")}`);
2505
3753
  }
2506
3754
  if (bc.legacyPolyfills.length > 0) {
2507
- 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(", ")}`);
2508
3756
  }
2509
3757
  if (bc.peerConflictsDetected) {
2510
- lines.push(` ${chalk5.red("\u26A0")} Peer dependency conflicts detected`);
3758
+ lines.push(` ${chalk7.red("\u26A0")} Peer dependency conflicts detected`);
2511
3759
  }
2512
- lines.push(` Recommendation: ${chalk5.bold(bc.overallRecommendation)}`);
3760
+ lines.push(` Recommendation: ${chalk7.bold(bc.overallRecommendation)}`);
2513
3761
  const projectsWithPlans = bc.projectIntelligence.filter((p) => p.packages.length > 0).slice(0, 3);
2514
3762
  if (projectsWithPlans.length > 0) {
2515
3763
  lines.push(" Major Upgrade Intelligence:");
@@ -2525,21 +3773,21 @@ function formatExtended(ext) {
2525
3773
  }
2526
3774
  if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
2527
3775
  const ts = ext.tsModernity;
2528
- lines.push(chalk5.bold.underline(" TypeScript"));
3776
+ lines.push(chalk7.bold.underline(" TypeScript"));
2529
3777
  const parts = [];
2530
3778
  parts.push(`v${ts.typescriptVersion}`);
2531
- if (ts.strict === true) parts.push(chalk5.green("strict \u2714"));
2532
- 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"));
2533
3781
  if (ts.moduleType) parts.push(ts.moduleType.toUpperCase());
2534
3782
  if (ts.target) parts.push(`target: ${ts.target}`);
2535
- lines.push(` ${parts.join(chalk5.dim(" \xB7 "))}`);
3783
+ lines.push(` ${parts.join(chalk7.dim(" \xB7 "))}`);
2536
3784
  lines.push("");
2537
3785
  }
2538
3786
  if (ext.buildDeploy) {
2539
3787
  const bd = ext.buildDeploy;
2540
3788
  const hasSomething = bd.ci.length > 0 || bd.docker.dockerfileCount > 0 || bd.packageManagers.length > 0;
2541
3789
  if (hasSomething) {
2542
- lines.push(chalk5.bold.underline(" Build & Deploy"));
3790
+ lines.push(chalk7.bold.underline(" Build & Deploy"));
2543
3791
  if (bd.ci.length > 0) lines.push(` CI: ${bd.ci.join(", ")}`);
2544
3792
  if (bd.docker.dockerfileCount > 0) {
2545
3793
  lines.push(` Docker: ${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""} (${bd.docker.baseImages.join(", ")})`);
@@ -2552,42 +3800,42 @@ function formatExtended(ext) {
2552
3800
  }
2553
3801
  if (ext.uiPurpose) {
2554
3802
  const up = ext.uiPurpose;
2555
- lines.push(chalk5.bold.underline(" Product Purpose Signals"));
2556
- lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk5.dim("unknown")}`);
2557
- 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)`) : ""}`);
2558
3806
  const top = up.topEvidence.slice(0, 8);
2559
3807
  if (top.length > 0) {
2560
3808
  lines.push(" Top Signals:");
2561
3809
  for (const item of top) {
2562
- lines.push(` - [${item.kind}] ${item.value} ${chalk5.dim(`(${item.file})`)}`);
3810
+ lines.push(` - [${item.kind}] ${item.value} ${chalk7.dim(`(${item.file})`)}`);
2563
3811
  }
2564
3812
  }
2565
3813
  if (up.unknownSignals.length > 0) {
2566
3814
  lines.push(" Unknowns:");
2567
3815
  for (const u of up.unknownSignals.slice(0, 4)) {
2568
- lines.push(` - ${chalk5.yellow(u)}`);
3816
+ lines.push(` - ${chalk7.yellow(u)}`);
2569
3817
  }
2570
3818
  }
2571
3819
  lines.push("");
2572
3820
  }
2573
3821
  if (ext.securityPosture) {
2574
3822
  const sec = ext.securityPosture;
2575
- lines.push(chalk5.bold.underline(" Security Posture"));
3823
+ lines.push(chalk7.bold.underline(" Security Posture"));
2576
3824
  const checks = [];
2577
- checks.push(sec.lockfilePresent ? chalk5.green("Lockfile \u2714") : chalk5.red("Lockfile \u2716"));
2578
- checks.push(sec.gitignoreCoversEnv ? chalk5.green(".env \u2714") : chalk5.red(".env \u2716"));
2579
- checks.push(sec.gitignoreCoversNodeModules ? chalk5.green("node_modules \u2714") : chalk5.yellow("node_modules \u2716"));
2580
- if (sec.multipleLockfileTypes) checks.push(chalk5.yellow("Multiple lockfiles \u26A0"));
2581
- if (sec.envFilesTracked) checks.push(chalk5.red("Env files tracked \u2716"));
2582
- 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 "))}`);
2583
3831
  lines.push("");
2584
3832
  }
2585
3833
  if (ext.platformMatrix) {
2586
3834
  const pm = ext.platformMatrix;
2587
3835
  if (pm.nativeModules.length > 0 || pm.dockerBaseImages.length > 0) {
2588
- lines.push(chalk5.bold.underline(" Platform"));
3836
+ lines.push(chalk7.bold.underline(" Platform"));
2589
3837
  if (pm.nativeModules.length > 0) {
2590
- 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(", ")}`);
2591
3839
  }
2592
3840
  if (pm.osAssumptions.length > 0) {
2593
3841
  lines.push(` OS assumptions: ${pm.osAssumptions.join(", ")}`);
@@ -2597,25 +3845,25 @@ function formatExtended(ext) {
2597
3845
  }
2598
3846
  if (ext.codeQuality) {
2599
3847
  const cq = ext.codeQuality;
2600
- lines.push(chalk5.bold.underline(" Code Quality"));
2601
- 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`);
2602
3850
  lines.push(` Max nesting: ${cq.maxNestingDepth} \xB7 Circular deps: ${cq.circularDependencies} \xB7 Dead code: ${cq.deadCodePercent}%`);
2603
3851
  if (cq.godFiles.length > 0) {
2604
3852
  const preview = cq.godFiles.slice(0, 3).map((f) => `${f.path} (${f.lines} lines)`).join(", ");
2605
- lines.push(` ${chalk5.yellow("God files")}: ${preview}`);
3853
+ lines.push(` ${chalk7.yellow("God files")}: ${preview}`);
2606
3854
  }
2607
3855
  lines.push("");
2608
3856
  }
2609
3857
  if (ext.dependencyGraph) {
2610
3858
  const dg = ext.dependencyGraph;
2611
3859
  if (dg.lockfileType) {
2612
- lines.push(chalk5.bold.underline(" Dependency Graph"));
2613
- 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`);
2614
3862
  if (dg.duplicatedPackages.length > 0) {
2615
- lines.push(` ${chalk5.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
3863
+ lines.push(` ${chalk7.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
2616
3864
  }
2617
3865
  if (dg.phantomDependencies.length > 0) {
2618
- lines.push(` ${chalk5.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
3866
+ lines.push(` ${chalk7.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
2619
3867
  }
2620
3868
  lines.push("");
2621
3869
  }
@@ -2626,13 +3874,13 @@ function formatArchitectureDiagram(arch) {
2626
3874
  const lines = [];
2627
3875
  lines.push(...titleBox("Architecture Layers"));
2628
3876
  lines.push("");
2629
- lines.push(chalk5.bold(" Archetype: ") + `${arch.archetype}` + chalk5.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
2630
- 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)`) : ""));
2631
3879
  lines.push("");
2632
3880
  if (arch.layers.length > 0) {
2633
3881
  for (const layer of arch.layers) {
2634
- const risk = layer.riskLevel === "none" ? chalk5.dim("none") : layer.riskLevel === "low" ? chalk5.green("low") : layer.riskLevel === "moderate" ? chalk5.yellow("moderate") : chalk5.red("high");
2635
- 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}`);
2636
3884
  }
2637
3885
  lines.push("");
2638
3886
  }
@@ -2894,7 +4142,7 @@ function formatMarkdown(artifact) {
2894
4142
  lines.push("");
2895
4143
  lines.push(`| Metric | Value |`);
2896
4144
  lines.push(`|--------|-------|`);
2897
- 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)_ |`);
2898
4146
  lines.push(`| **Risk Level** | ${artifact.drift.riskLevel.toUpperCase()} |`);
2899
4147
  lines.push(`| **Projects** | ${artifact.projects.length} |`);
2900
4148
  const scannedMeta = [artifact.timestamp];
@@ -3001,10 +4249,10 @@ function formatMarkdown(artifact) {
3001
4249
 
3002
4250
  // src/reporting/commands/report.ts
3003
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) => {
3004
- const artifactPath = path9.resolve(opts.in);
4252
+ const artifactPath = path.resolve(opts.in);
3005
4253
  if (!await pathExists(artifactPath)) {
3006
- console.error(chalk5.red(`Artifact not found: ${artifactPath}`));
3007
- 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.'));
3008
4256
  process.exit(1);
3009
4257
  }
3010
4258
  const artifact = await readJsonFile(artifactPath);
@@ -3047,10 +4295,10 @@ function openUrl(url) {
3047
4295
  }
3048
4296
 
3049
4297
  // src/reporting/commands/login.ts
3050
- var delay = (ms) => new Promise((resolve15) => setTimeout(resolve15, ms));
4298
+ var delay = (ms) => new Promise((resolve16) => setTimeout(resolve16, ms));
3051
4299
  function isInside(dir, file) {
3052
- const rel2 = path9.relative(dir, file);
3053
- return !!rel2 && !rel2.startsWith("..") && !path9.isAbsolute(rel2);
4300
+ const rel3 = path.relative(dir, file);
4301
+ return !!rel3 && !rel3.startsWith("..") && !path.isAbsolute(rel3);
3054
4302
  }
3055
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(
3056
4304
  "--local",
@@ -3060,7 +4308,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3060
4308
  try {
3061
4309
  ingestHost = resolveIngestHost(opts.region, opts.ingest);
3062
4310
  } catch (e) {
3063
- console.error(chalk5.red(e instanceof Error ? e.message : String(e)));
4311
+ console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
3064
4312
  process.exit(1);
3065
4313
  }
3066
4314
  const base = `https://${ingestHost}/v1/auth/device`;
@@ -3072,28 +4320,28 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3072
4320
  body: "{}"
3073
4321
  });
3074
4322
  if (!res.ok) {
3075
- console.error(chalk5.red(`Failed to start login (HTTP ${res.status}).`));
4323
+ console.error(chalk7.red(`Failed to start login (HTTP ${res.status}).`));
3076
4324
  process.exit(1);
3077
4325
  }
3078
4326
  start = await res.json();
3079
4327
  } catch (e) {
3080
- 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)}`));
3081
4329
  process.exit(1);
3082
4330
  }
3083
4331
  console.log("");
3084
4332
  console.log("To finish signing in, open this URL and approve the request:");
3085
4333
  console.log("");
3086
- console.log(" " + chalk5.cyan(start.verificationUri));
4334
+ console.log(" " + chalk7.cyan(start.verificationUri));
3087
4335
  console.log("");
3088
- console.log(" Your code: " + chalk5.bold(start.userCode));
4336
+ console.log(" Your code: " + chalk7.bold(start.userCode));
3089
4337
  console.log("");
3090
4338
  if (opts.browser) {
3091
4339
  const opened = openUrl(start.verificationUriComplete);
3092
4340
  if (opened) {
3093
- 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)"));
3094
4342
  }
3095
4343
  }
3096
- console.log(chalk5.dim("Waiting for approval\u2026"));
4344
+ console.log(chalk7.dim("Waiting for approval\u2026"));
3097
4345
  const intervalMs = Math.max(2, start.interval || 5) * 1e3;
3098
4346
  const deadline = Date.now() + (start.expiresIn || 900) * 1e3;
3099
4347
  while (Date.now() < deadline) {
@@ -3121,7 +4369,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3121
4369
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
3122
4370
  };
3123
4371
  } else {
3124
- console.log(chalk5.dim("Setting up your workspace\u2026"));
4372
+ console.log(chalk7.dim("Setting up your workspace\u2026"));
3125
4373
  try {
3126
4374
  const provisioned = await createWorkspaceDsn({
3127
4375
  region: opts.region,
@@ -3136,10 +4384,10 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3136
4384
  };
3137
4385
  } catch (e) {
3138
4386
  console.error(
3139
- 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))
3140
4388
  );
3141
4389
  console.error(
3142
- chalk5.dim(' Finish setup with "vibgrate dsn create --workspace new".')
4390
+ chalk7.dim(' Finish setup with "vibgrate dsn create --workspace new".')
3143
4391
  );
3144
4392
  process.exit(1);
3145
4393
  }
@@ -3148,40 +4396,40 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3148
4396
  writeStoredCredentials(creds, storeOpts);
3149
4397
  const credsFile = credentialsPath(storeOpts);
3150
4398
  console.log("");
3151
- console.log(chalk5.green("\u2714") + " Logged in.");
4399
+ console.log(chalk7.green("\u2714") + " Logged in.");
3152
4400
  if (creds.workspaceId) {
3153
- console.log(" Workspace: " + chalk5.bold(creds.workspaceId));
4401
+ console.log(" Workspace: " + chalk7.bold(creds.workspaceId));
3154
4402
  }
3155
- console.log(chalk5.dim(` Credentials saved to ${credsFile}`));
4403
+ console.log(chalk7.dim(` Credentials saved to ${credsFile}`));
3156
4404
  try {
3157
4405
  const root = findGitRoot();
3158
4406
  if (root && isInside(root, credsFile)) {
3159
4407
  const res = ensureGitignored(gitignoreEntryForCredentials(root, credsFile), root);
3160
4408
  if (res.status === "created") {
3161
- console.log(chalk5.dim(` Created .gitignore and ignored ${res.entry}`));
4409
+ console.log(chalk7.dim(` Created .gitignore and ignored ${res.entry}`));
3162
4410
  } else if (res.status === "added") {
3163
- console.log(chalk5.dim(` Added ${res.entry} to .gitignore`));
4411
+ console.log(chalk7.dim(` Added ${res.entry} to .gitignore`));
3164
4412
  }
3165
4413
  }
3166
4414
  } catch {
3167
4415
  }
3168
- console.log(chalk5.dim(` You can now run "${resolveCliInvocation()} scan --push".`));
4416
+ console.log(chalk7.dim(` You can now run "${resolveCliInvocation()} scan --push".`));
3169
4417
  return;
3170
4418
  }
3171
4419
  if (token.status === "access_denied") {
3172
- console.error(chalk5.red("\u2716 Login was denied in the browser."));
4420
+ console.error(chalk7.red("\u2716 Login was denied in the browser."));
3173
4421
  process.exit(1);
3174
4422
  }
3175
4423
  if (token.status === "expired" || token.status === "invalid") {
3176
- 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.'));
3177
4425
  process.exit(1);
3178
4426
  }
3179
4427
  if (token.status === "error") {
3180
- console.error(chalk5.red(`\u2716 ${token.error ?? "Login failed."}`));
4428
+ console.error(chalk7.red(`\u2716 ${token.error ?? "Login failed."}`));
3181
4429
  process.exit(1);
3182
4430
  }
3183
4431
  }
3184
- 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.'));
3185
4433
  process.exit(1);
3186
4434
  });
3187
4435
 
@@ -3202,23 +4450,23 @@ function unsetEnvCommand(varName, env = {}) {
3202
4450
  var logoutCommand = new Command("logout").description("Clear stored Vibgrate login credentials").action(() => {
3203
4451
  const cleared = clearStoredCredentials();
3204
4452
  if (cleared) {
3205
- console.log(chalk5.green("\u2714") + " Logged out. Stored credentials removed.");
4453
+ console.log(chalk7.green("\u2714") + " Logged out. Stored credentials removed.");
3206
4454
  } else {
3207
- console.log(chalk5.dim(`No stored credentials found at ${credentialsPath()}.`));
4455
+ console.log(chalk7.dim(`No stored credentials found at ${credentialsPath()}.`));
3208
4456
  }
3209
4457
  if (process.env.VIBGRATE_DSN) {
3210
4458
  console.log("");
3211
4459
  console.log(
3212
- 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."
3213
4461
  );
3214
4462
  console.log(
3215
- chalk5.dim(` Unset it to fully sign out: ${unsetEnvCommand("VIBGRATE_DSN")}`)
4463
+ chalk7.dim(` Unset it to fully sign out: ${unsetEnvCommand("VIBGRATE_DSN")}`)
3216
4464
  );
3217
4465
  }
3218
4466
  });
3219
4467
  var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
3220
- var CACHE_DIR = path9.join(os.homedir(), ".vibgrate");
3221
- 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");
3222
4470
  async function fetchLatestVersion() {
3223
4471
  try {
3224
4472
  const controller = new AbortController();
@@ -3245,8 +4493,8 @@ async function fetchLatestVersion() {
3245
4493
  }
3246
4494
  async function writeCache(data) {
3247
4495
  try {
3248
- await fs10.mkdir(CACHE_DIR, { recursive: true });
3249
- 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");
3250
4498
  } catch {
3251
4499
  }
3252
4500
  }
@@ -3279,9 +4527,9 @@ function getGlobalUpdateCommand(pm, pkg, version) {
3279
4527
  }
3280
4528
  }
3281
4529
  async function detectPackageManager(cwd) {
3282
- if (await pathExists(path9.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
3283
- if (await pathExists(path9.join(cwd, "bun.lockb"))) return "bun";
3284
- 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";
3285
4533
  return "npm";
3286
4534
  }
3287
4535
  function getInstallCommand(pm, pkg, version, isDev) {
@@ -3300,7 +4548,7 @@ function getInstallCommand(pm, pkg, version, isDev) {
3300
4548
  }
3301
4549
  async function isDevDependency(cwd) {
3302
4550
  try {
3303
- const pkgPath = path9.join(cwd, "package.json");
4551
+ const pkgPath = path.join(cwd, "package.json");
3304
4552
  const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
3305
4553
  const pkg = JSON.parse(raw);
3306
4554
  return Boolean(pkg.devDependencies?.["@vibgrate/cli"]);
@@ -3309,21 +4557,21 @@ async function isDevDependency(cwd) {
3309
4557
  }
3310
4558
  }
3311
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) => {
3312
- console.log(chalk5.dim(`Current version: ${VERSION}`));
3313
- console.log(chalk5.dim("Checking npm registry..."));
4560
+ console.log(chalk7.dim(`Current version: ${VERSION}`));
4561
+ console.log(chalk7.dim("Checking npm registry..."));
3314
4562
  const latest = await fetchLatestVersion();
3315
4563
  if (!latest) {
3316
- 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."));
3317
4565
  process.exit(1);
3318
4566
  }
3319
4567
  const semver2 = await import('semver');
3320
4568
  if (!semver2.gt(latest, VERSION)) {
3321
- 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}).`);
3322
4570
  return;
3323
4571
  }
3324
- console.log(chalk5.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
4572
+ console.log(chalk7.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
3325
4573
  if (opts.check) {
3326
- console.log(chalk5.dim('Run "vibgrate update" to install.'));
4574
+ console.log(chalk7.dim('Run "vibgrate update" to install.'));
3327
4575
  return;
3328
4576
  }
3329
4577
  const cwd = process.cwd();
@@ -3333,17 +4581,17 @@ var updateCommand = new Command("update").description("Update vibgrate to the la
3333
4581
  let cmd;
3334
4582
  if (isGlobal) {
3335
4583
  cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
3336
- console.log(chalk5.dim(`Updating global installation with ${pm}: ${cmd}`));
4584
+ console.log(chalk7.dim(`Updating global installation with ${pm}: ${cmd}`));
3337
4585
  } else {
3338
4586
  const isDev = await isDevDependency(cwd);
3339
4587
  cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev);
3340
- console.log(chalk5.dim(`Using ${pm}: ${cmd}`));
4588
+ console.log(chalk7.dim(`Using ${pm}: ${cmd}`));
3341
4589
  }
3342
4590
  try {
3343
4591
  execSync(cmd, { cwd, stdio: "inherit" });
3344
- console.log(chalk5.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
4592
+ console.log(chalk7.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
3345
4593
  } catch {
3346
- console.error(chalk5.red(`Update failed. Run manually: ${cmd}`));
4594
+ console.error(chalk7.red(`Update failed. Run manually: ${cmd}`));
3347
4595
  process.exit(1);
3348
4596
  }
3349
4597
  });
@@ -3436,9 +4684,9 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
3436
4684
  try {
3437
4685
  const statements = [];
3438
4686
  if (opts.from) {
3439
- const fromPath = path9.resolve(opts.from);
4687
+ const fromPath = path.resolve(opts.from);
3440
4688
  if (!await pathExists(fromPath)) {
3441
- process.stderr.write(chalk5.red(`Statements file not found: ${fromPath}
4689
+ process.stderr.write(chalk7.red(`Statements file not found: ${fromPath}
3442
4690
  `));
3443
4691
  process.exit(1);
3444
4692
  }
@@ -3454,9 +4702,9 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
3454
4702
  });
3455
4703
  const json2 = JSON.stringify(doc, null, 2);
3456
4704
  if (opts.out) {
3457
- await writeTextFile(path9.resolve(opts.out), json2 + "\n");
4705
+ await writeTextFile(path.resolve(opts.out), json2 + "\n");
3458
4706
  process.stderr.write(
3459
- 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)}
3460
4708
  `
3461
4709
  );
3462
4710
  } else {
@@ -3464,7 +4712,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
3464
4712
  }
3465
4713
  } catch (err) {
3466
4714
  if (err instanceof VexValidationError) {
3467
- process.stderr.write(chalk5.red(`VEX validation error: ${err.message}
4715
+ process.stderr.write(chalk7.red(`VEX validation error: ${err.message}
3468
4716
  `));
3469
4717
  process.exit(1);
3470
4718
  }
@@ -3639,9 +4887,9 @@ function formatDeltaText(base, current) {
3639
4887
  return lines.join("\n");
3640
4888
  }
3641
4889
  async function readArtifactOrExit(filePath) {
3642
- const absolutePath = path9.resolve(filePath);
4890
+ const absolutePath = path.resolve(filePath);
3643
4891
  if (!await pathExists(absolutePath)) {
3644
- console.error(chalk5.red(`Artifact not found: ${absolutePath}`));
4892
+ console.error(chalk7.red(`Artifact not found: ${absolutePath}`));
3645
4893
  process.exit(1);
3646
4894
  }
3647
4895
  return readJsonFile(absolutePath);
@@ -3650,14 +4898,14 @@ var exportCommand = new Command("export").description("Export scan artifact as S
3650
4898
  const artifact = await readArtifactOrExit(opts.in);
3651
4899
  const format = opts.format.toLowerCase();
3652
4900
  if (format !== "cyclonedx" && format !== "spdx") {
3653
- console.error(chalk5.red("Invalid SBOM format. Use cyclonedx or spdx."));
4901
+ console.error(chalk7.red("Invalid SBOM format. Use cyclonedx or spdx."));
3654
4902
  process.exit(1);
3655
4903
  }
3656
4904
  const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
3657
4905
  const body = JSON.stringify(sbom, null, 2);
3658
4906
  if (opts.out) {
3659
- await writeTextFile(path9.resolve(opts.out), body);
3660
- 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}`);
3661
4909
  } else {
3662
4910
  console.log(body);
3663
4911
  }
@@ -3667,8 +4915,8 @@ var deltaCommand = new Command("delta").description("Show SBOM delta between two
3667
4915
  const current = await readArtifactOrExit(opts.to);
3668
4916
  const report = formatDeltaText(base, current);
3669
4917
  if (opts.out) {
3670
- await writeTextFile(path9.resolve(opts.out), report);
3671
- 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}`);
3672
4920
  } else {
3673
4921
  console.log(report);
3674
4922
  }
@@ -3679,13 +4927,13 @@ var sbomCommand = new Command("sbom").description("Supply-chain evidence: SBOM e
3679
4927
  var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
3680
4928
  "build",
3681
4929
  "status",
3682
- "verify",
3683
4930
  "ask",
3684
4931
  "embed",
3685
4932
  "show",
3686
4933
  "path",
3687
4934
  "tree",
3688
4935
  "impact",
4936
+ "unknowns",
3689
4937
  "tests",
3690
4938
  "facts",
3691
4939
  "guide",
@@ -3711,6 +4959,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
3711
4959
  // Drift-reporting verbs (merged from the Vibgrate CLI).
3712
4960
  "init",
3713
4961
  "scan",
4962
+ "fix",
3714
4963
  "baseline",
3715
4964
  "report",
3716
4965
  "login",
@@ -3733,7 +4982,6 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
3733
4982
  });
3734
4983
  registerBuild(program);
3735
4984
  registerStatus(program);
3736
- registerVerify(program);
3737
4985
  registerAsk(program);
3738
4986
  registerEmbed(program);
3739
4987
  registerShow(program);
@@ -3741,6 +4989,7 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
3741
4989
  registerTree(program);
3742
4990
  registerInsights(program);
3743
4991
  registerImpact(program);
4992
+ registerUnknowns(program);
3744
4993
  registerServe(program);
3745
4994
  registerInstall(program);
3746
4995
  registerShare(program);
@@ -3758,6 +5007,7 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
3758
5007
  registerBundle(program);
3759
5008
  program.addCommand(initCommand);
3760
5009
  program.addCommand(scanCommand);
5010
+ program.addCommand(fixCommand);
3761
5011
  program.addCommand(baselineCommand);
3762
5012
  program.addCommand(reportCommand);
3763
5013
  program.addCommand(loginCommand);
@@ -3822,11 +5072,19 @@ function dispatch(argv, cwd) {
3822
5072
  }
3823
5073
  const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
3824
5074
  if (looksLikeQuestion) return ["ask", ...args];
3825
- if (fs4.existsSync(path9.resolve(cwd, first))) {
5075
+ if (fs5.existsSync(path.resolve(cwd, first))) {
3826
5076
  return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
3827
5077
  }
5078
+ const moved = MOVED_COMMANDS[first];
5079
+ if (moved) {
5080
+ throw new CliError(`\`vg ${first}\` has moved to \`${moved}\``, ExitCode.USAGE_ERROR);
5081
+ }
3828
5082
  return ["ask", ...args];
3829
5083
  }
5084
+ var MOVED_COMMANDS = {
5085
+ verify: "vg build --verify",
5086
+ attest: "vg build --attest"
5087
+ };
3830
5088
  function findFirstPositional(args) {
3831
5089
  for (let i = 0; i < args.length; i++) {
3832
5090
  const a = args[i];
@@ -3842,7 +5100,6 @@ async function main(argv = process.argv) {
3842
5100
  const raw = argv.slice(2);
3843
5101
  if (process.env.NO_COLOR || raw.includes("--no-color")) disableColor();
3844
5102
  const cwd = readCwd(raw);
3845
- const dispatched = dispatch(raw, cwd);
3846
5103
  const program = buildProgram();
3847
5104
  program.exitOverride((err) => {
3848
5105
  throw err;
@@ -3852,6 +5109,7 @@ async function main(argv = process.argv) {
3852
5109
  writeOut: (str) => process.stdout.write(str)
3853
5110
  });
3854
5111
  try {
5112
+ const dispatched = dispatch(raw, cwd);
3855
5113
  await program.parseAsync(dispatched, { from: "user" });
3856
5114
  } catch (err) {
3857
5115
  handleError(err);
@@ -3859,9 +5117,9 @@ async function main(argv = process.argv) {
3859
5117
  }
3860
5118
  function readCwd(raw) {
3861
5119
  const i = raw.findIndex((a) => a === "-C" || a === "--cwd");
3862
- 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]);
3863
5121
  const eq = raw.find((a) => a.startsWith("--cwd="));
3864
- if (eq) return path9.resolve(eq.slice("--cwd=".length));
5122
+ if (eq) return path.resolve(eq.slice("--cwd=".length));
3865
5123
  return process.cwd();
3866
5124
  }
3867
5125
  function handleError(err) {