@vibgrate/cli 2026.704.3 → 2026.708.2

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