@vibgrate/cli 2026.721.2 → 2026.722.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/DOCS.md +185 -0
  2. package/README.md +2 -1
  3. package/dist/baseline-J44M7MWP.js +7 -0
  4. package/dist/{baseline-KK7ZLRZB.js.map → baseline-J44M7MWP.js.map} +1 -1
  5. package/dist/chunk-2EVQKZTO.js +696 -0
  6. package/dist/chunk-2EVQKZTO.js.map +1 -0
  7. package/dist/chunk-6CXTPC74.js +234 -0
  8. package/dist/chunk-6CXTPC74.js.map +1 -0
  9. package/dist/chunk-CRAMOEBE.js +675 -0
  10. package/dist/chunk-CRAMOEBE.js.map +1 -0
  11. package/dist/chunk-CS37OBE3.js +861 -0
  12. package/dist/chunk-CS37OBE3.js.map +1 -0
  13. package/dist/chunk-CT5BS56U.js +23 -0
  14. package/dist/chunk-CT5BS56U.js.map +1 -0
  15. package/dist/{chunk-MUMBLIDO.js → chunk-EXQV26MF.js} +4 -3
  16. package/dist/chunk-EXQV26MF.js.map +1 -0
  17. package/dist/chunk-GGJZA3Q6.js +961 -0
  18. package/dist/chunk-GGJZA3Q6.js.map +1 -0
  19. package/dist/chunk-JBXNQCGE.js +484 -0
  20. package/dist/chunk-JBXNQCGE.js.map +1 -0
  21. package/dist/chunk-JFGYT6BI.js +32 -0
  22. package/dist/chunk-JFGYT6BI.js.map +1 -0
  23. package/dist/{chunk-NHLSAZ6N.js → chunk-JSNHZCIV.js} +42 -71
  24. package/dist/chunk-JSNHZCIV.js.map +1 -0
  25. package/dist/chunk-JYS4OBN2.js +148 -0
  26. package/dist/chunk-JYS4OBN2.js.map +1 -0
  27. package/dist/chunk-K3SLGULW.js +50 -0
  28. package/dist/chunk-K3SLGULW.js.map +1 -0
  29. package/dist/chunk-LEPTUB4H.js +108 -0
  30. package/dist/chunk-LEPTUB4H.js.map +1 -0
  31. package/dist/{chunk-JWBS5GS6.js → chunk-PY3DNX5H.js} +94 -34
  32. package/dist/chunk-PY3DNX5H.js.map +1 -0
  33. package/dist/{chunk-3DPZQS2Q.js → chunk-VXT6LUGP.js} +356 -2532
  34. package/dist/chunk-VXT6LUGP.js.map +1 -0
  35. package/dist/chunk-WNIIKCNF.js +972 -0
  36. package/dist/chunk-WNIIKCNF.js.map +1 -0
  37. package/dist/chunk-XKXKWYJF.js +417 -0
  38. package/dist/chunk-XKXKWYJF.js.map +1 -0
  39. package/dist/cli.js +2479 -1234
  40. package/dist/cli.js.map +1 -1
  41. package/dist/fs-KCABDURV.js +3 -0
  42. package/dist/fs-KCABDURV.js.map +1 -0
  43. package/dist/index.d.ts +5 -3
  44. package/dist/index.js +8 -3
  45. package/dist/index.js.map +1 -1
  46. package/dist/interactive-736PBC7J.js +820 -0
  47. package/dist/interactive-736PBC7J.js.map +1 -0
  48. package/dist/mcp-tools-I6PME3AV.js +3 -0
  49. package/dist/mcp-tools-I6PME3AV.js.map +1 -0
  50. package/dist/parse-worker.d.ts +1 -1
  51. package/dist/parse-worker.js +2 -1
  52. package/dist/parse-worker.js.map +1 -1
  53. package/dist/session-POJUDFPH.js +6 -0
  54. package/dist/session-POJUDFPH.js.map +1 -0
  55. package/dist/session-store-W5RZZHA3.js +3 -0
  56. package/dist/session-store-W5RZZHA3.js.map +1 -0
  57. package/dist/stream-json-Q2JGWPHB.js +65 -0
  58. package/dist/stream-json-Q2JGWPHB.js.map +1 -0
  59. package/dist/{types-BgNa-FZQ.d.ts → types-Jl6RJ175.d.ts} +8 -0
  60. package/dist/ui-HOVOPWMX.js +4 -0
  61. package/dist/ui-HOVOPWMX.js.map +1 -0
  62. package/package.json +3 -2
  63. package/dist/baseline-KK7ZLRZB.js +0 -6
  64. package/dist/chunk-3DPZQS2Q.js.map +0 -1
  65. package/dist/chunk-JWBS5GS6.js.map +0 -1
  66. package/dist/chunk-MUMBLIDO.js.map +0 -1
  67. package/dist/chunk-NHLSAZ6N.js.map +0 -1
@@ -0,0 +1,696 @@
1
+ import { usageError, buildGraph, UsageError, ResourceLimitError, CliError, ExitCode, writeArtifacts, writeSnapshot, refreshInstalledInstructions, SMALL_REPO_FILES, epistemicBreakdown, verifyDeterminism, renderHtml, renderReport, discoverModels, loadGraph } from './chunk-VXT6LUGP.js';
2
+ import { VERSION, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-JSNHZCIV.js';
3
+ import { MockProvider, OPENAI_COMPATIBLE, OllamaProvider, LocalLlamaProvider, OpenAiCompatibleProvider } from './chunk-CS37OBE3.js';
4
+ import { resolveEmbedModel, isModelReady, countPending, serializeGraph } from './chunk-WNIIKCNF.js';
5
+ import { canonicalize } from './chunk-JFGYT6BI.js';
6
+ import { c, info, json, out } from './chunk-CT5BS56U.js';
7
+ import * as path from 'path';
8
+ import * as fs from 'fs';
9
+ import { spawn, execFile } from 'child_process';
10
+ import * as crypto from 'crypto';
11
+
12
+ // src/util/logo.ts
13
+ var teal = (s) => c.hex("#3FB0A4")(s);
14
+ var mint = (s) => c.hex("#4FE3C1")(s);
15
+ var ROBOT = [
16
+ " " + teal("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E") + mint("\u279C"),
17
+ " " + c.dim("\u2524") + teal("\u2502") + " " + mint("\u25FC") + " " + mint("\u25FC") + " " + teal("\u2502") + c.dim("\u251C"),
18
+ " " + c.dim("\u2524") + teal("\u2502") + " " + c.dim("\u2581\u2581") + " " + teal("\u2502") + c.dim("\u251C"),
19
+ " " + teal("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F")
20
+ ];
21
+ function logoLines(root, opts = {}) {
22
+ const product = opts.product ?? "Graph";
23
+ const tagline = opts.tagline ?? "Code Intelligence Engine";
24
+ return [
25
+ "",
26
+ ` ${ROBOT[0]}`,
27
+ ` ${ROBOT[1]}`,
28
+ ` ${ROBOT[2]}`,
29
+ ` ${ROBOT[3]}`,
30
+ ` ${c.bold.white("Vibgrate")} ${teal(product)} ${c.dim(`\xB7 ${tagline} \xB7 v${VERSION}`)}`,
31
+ ...root ? [` ${c.dim(root)}`] : [],
32
+ ""
33
+ ];
34
+ }
35
+ function printLogo(root, opts = {}) {
36
+ if (!process.stderr.isTTY) return;
37
+ process.stderr.write(`${logoLines(root, opts).join("\n")}
38
+ `);
39
+ }
40
+ function codeLogoLines(root) {
41
+ return [
42
+ "",
43
+ ` ${ROBOT[0]}`,
44
+ ` ${ROBOT[1]}`,
45
+ ` ${ROBOT[2]}`,
46
+ ` ${ROBOT[3]}`,
47
+ ` ${c.bold.white("VG")} ${teal("Code")} ${c.dim(`\xB7 graph-grounded coding \xB7 v${VERSION}`)}`,
48
+ ...root ? [` ${c.dim(root)}`] : [],
49
+ ""
50
+ ];
51
+ }
52
+ function printCodeLogo(root) {
53
+ if (!process.stderr.isTTY) return;
54
+ process.stderr.write(`${codeLogoLines(root).join("\n")}
55
+ `);
56
+ }
57
+ var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
58
+ var IN_TOTO_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
59
+ var VG_PREDICATE_TYPE = "https://vibgrate.com/attestation/code-graph/v1";
60
+ function sha256Hex(input) {
61
+ return crypto.createHash("sha256").update(input).digest("hex");
62
+ }
63
+ function graphSubjectDigest(graph) {
64
+ const { generatedAt: _omit, ...rest } = graph;
65
+ return sha256Hex(canonicalize(rest));
66
+ }
67
+ function keyId(publicKey) {
68
+ return sha256Hex(publicKey.export({ type: "spki", format: "der" })).slice(0, 16);
69
+ }
70
+ function buildStatement(graph, input = {}) {
71
+ const graphDigest = graphSubjectDigest(graph);
72
+ const predicate = {
73
+ tool: { name: "vg", version: VERSION },
74
+ toolchain: graph.provenance.toolchain,
75
+ corpusHash: graph.provenance.corpusHash,
76
+ graphDigest,
77
+ counts: graph.meta.counts
78
+ };
79
+ if (input.commit) predicate.commit = input.commit;
80
+ if (input.sbomDigest) predicate.sbomDigest = input.sbomDigest;
81
+ if (input.timestamp) predicate.timestamp = input.timestamp;
82
+ return {
83
+ _type: IN_TOTO_STATEMENT_TYPE,
84
+ subject: [{ name: "graph.json", digest: { sha256: graphDigest } }],
85
+ predicateType: VG_PREDICATE_TYPE,
86
+ predicate
87
+ };
88
+ }
89
+ function dssePae(payloadType, body) {
90
+ const typeBytes = Buffer.from(payloadType, "utf8");
91
+ const header = Buffer.from(
92
+ `DSSEv1 ${typeBytes.length} ${payloadType} ${body.length} `,
93
+ "utf8"
94
+ );
95
+ return Buffer.concat([header, Buffer.from(body)]);
96
+ }
97
+ function signStatement(statement, privateKey) {
98
+ const body = Buffer.from(JSON.stringify(statement), "utf8");
99
+ const pae = dssePae(DSSE_PAYLOAD_TYPE, body);
100
+ const sig = crypto.sign(null, pae, privateKey);
101
+ const publicKey = crypto.createPublicKey(privateKey.export({ type: "pkcs8", format: "pem" }).toString());
102
+ return {
103
+ payloadType: DSSE_PAYLOAD_TYPE,
104
+ payload: body.toString("base64"),
105
+ signatures: [
106
+ {
107
+ keyid: keyId(publicKey),
108
+ sig: sig.toString("base64"),
109
+ publicKey: publicKey.export({ type: "spki", format: "pem" }).toString()
110
+ }
111
+ ]
112
+ };
113
+ }
114
+ function emptyStatement() {
115
+ return {
116
+ _type: "",
117
+ subject: [],
118
+ predicateType: "",
119
+ predicate: {
120
+ tool: { name: "vg", version: "" },
121
+ corpusHash: "",
122
+ graphDigest: "",
123
+ counts: { nodes: 0, edges: 0, areas: 0, tests: 0, untested: 0 }
124
+ }
125
+ };
126
+ }
127
+ function failedResult(reason) {
128
+ return {
129
+ status: "failed",
130
+ signatureValid: false,
131
+ signerPinned: false,
132
+ keyid: "",
133
+ reason,
134
+ statement: emptyStatement()
135
+ };
136
+ }
137
+ function verifyEnvelope(env, opts = {}) {
138
+ let body;
139
+ let statement;
140
+ try {
141
+ body = Buffer.from(env?.payload ?? "", "base64");
142
+ const parsed = JSON.parse(body.toString("utf8"));
143
+ if (!parsed || typeof parsed !== "object" || !parsed.predicate) {
144
+ throw new Error("missing predicate");
145
+ }
146
+ statement = parsed;
147
+ } catch {
148
+ return failedResult("malformed attestation payload (not a valid in-toto statement)");
149
+ }
150
+ const pae = dssePae(env.payloadType, body);
151
+ const sig0 = env.signatures?.[0];
152
+ const keyid = sig0?.keyid ?? "";
153
+ const sig = sig0?.sig ? Buffer.from(sig0.sig, "base64") : Buffer.alloc(0);
154
+ let signatureValid = false;
155
+ let signerPinned = false;
156
+ try {
157
+ if (sig0 && sig.length) {
158
+ if (opts.publicKeyPem) {
159
+ const pinnedKey = crypto.createPublicKey(opts.publicKeyPem);
160
+ signatureValid = crypto.verify(null, pae, pinnedKey, sig);
161
+ signerPinned = signatureValid;
162
+ } else if (sig0.publicKey) {
163
+ const embeddedKey = crypto.createPublicKey(sig0.publicKey);
164
+ signatureValid = crypto.verify(null, pae, embeddedKey, sig);
165
+ }
166
+ }
167
+ } catch {
168
+ signatureValid = false;
169
+ signerPinned = false;
170
+ }
171
+ const digestMatches = opts.graph ? graphSubjectDigest(opts.graph) === statement.predicate.graphDigest : void 0;
172
+ const dirty = statement.predicate.commit?.dirty;
173
+ let status;
174
+ let reason;
175
+ if (!signatureValid) {
176
+ status = "failed";
177
+ reason = sig0 ? "signature verification failed" : "no signature in envelope";
178
+ } else if (digestMatches === false) {
179
+ status = "failed";
180
+ reason = "graph.json no longer matches the attested digest (content changed since signing)";
181
+ } else if (signerPinned && !dirty) {
182
+ status = "verified";
183
+ reason = digestMatches === true ? "signature valid, signer trusted, graph digest matches" : "signature valid, signer trusted";
184
+ } else {
185
+ status = "signature-valid";
186
+ reason = dirty ? "signature valid but the attested tree was dirty (uncommitted changes)" : "signature valid but signer not pinned \u2014 pass --pub to establish trust";
187
+ }
188
+ return { status, signatureValid, signerPinned, digestMatches, dirty, keyid, reason, statement };
189
+ }
190
+ function generateKeypair() {
191
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
192
+ return {
193
+ privatePem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
194
+ publicPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
195
+ keyid: keyId(publicKey)
196
+ };
197
+ }
198
+ function serializeEnvelope(env) {
199
+ return `${JSON.stringify(env)}
200
+ `;
201
+ }
202
+ function parseEnvelope(text) {
203
+ const line = text.trim().split("\n")[0];
204
+ return JSON.parse(line);
205
+ }
206
+
207
+ // src/commands/attest-actions.ts
208
+ var DEFAULT_ATTESTATION = "attestation.intoto.jsonl";
209
+ var DEFAULT_KEY = "attest-key.pem";
210
+ async function signGraphAttestation(root, graph, opts) {
211
+ const notices = [];
212
+ const explicitKey = opts.key ?? process.env.VG_ATTEST_KEY;
213
+ const keyPath = explicitKey ? path.resolve(explicitKey) : path.join(root, ".vibgrate", DEFAULT_KEY);
214
+ let keyGeneratedAt;
215
+ if (!fs.existsSync(keyPath)) {
216
+ if (explicitKey) {
217
+ throw new CliError(`signing key not found: ${explicitKey}`, ExitCode.USAGE_ERROR);
218
+ }
219
+ const kp = generateKeypair();
220
+ fs.mkdirSync(path.dirname(keyPath), { recursive: true });
221
+ fs.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
222
+ fs.writeFileSync(`${keyPath}.pub`, kp.publicPem);
223
+ keyGeneratedAt = keyPath;
224
+ notices.push(
225
+ `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`
226
+ );
227
+ }
228
+ let privateKey;
229
+ try {
230
+ privateKey = crypto.createPrivateKey(fs.readFileSync(keyPath, "utf8"));
231
+ } catch {
232
+ throw new CliError(`could not read an Ed25519 private key from ${rel(root, keyPath)}`, ExitCode.USAGE_ERROR);
233
+ }
234
+ if (privateKey.asymmetricKeyType !== "ed25519") {
235
+ throw new CliError(
236
+ `attest requires an Ed25519 key, but ${rel(root, keyPath)} is ${privateKey.asymmetricKeyType ?? "unknown"}`,
237
+ ExitCode.USAGE_ERROR
238
+ );
239
+ }
240
+ const commit = await gitCommitInfo(root);
241
+ const statement = buildStatement(graph, { commit });
242
+ const envelope = signStatement(statement, privateKey);
243
+ const outPath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
244
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
245
+ fs.writeFileSync(outPath, serializeEnvelope(envelope));
246
+ return {
247
+ summary: {
248
+ keyid: envelope.signatures[0]?.keyid ?? "",
249
+ graphDigest: statement.predicate.graphDigest,
250
+ fingerprint: statement.predicate.toolchain?.fingerprint,
251
+ commit,
252
+ out: rel(root, outPath),
253
+ keyGeneratedAt: keyGeneratedAt ? rel(root, keyGeneratedAt) : void 0
254
+ },
255
+ notices
256
+ };
257
+ }
258
+ function verifyGraphAttestation(root, opts) {
259
+ const bundlePath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
260
+ if (!fs.existsSync(bundlePath)) {
261
+ if (opts.attestation || opts.pub) {
262
+ throw new CliError(
263
+ `no attestation at ${rel(root, bundlePath)} \u2014 sign one with \`vg build --attest\``,
264
+ ExitCode.NOT_FOUND
265
+ );
266
+ }
267
+ return { bundlePath, missing: true };
268
+ }
269
+ const envelope = parseEnvelope(fs.readFileSync(bundlePath, "utf8"));
270
+ const publicKeyPem = opts.pub ? fs.readFileSync(path.resolve(opts.pub), "utf8") : void 0;
271
+ const graph = loadGraph(root) ?? void 0;
272
+ return { bundlePath, missing: false, result: verifyEnvelope(envelope, { publicKeyPem, graph }) };
273
+ }
274
+ async function gitCommitInfo(root) {
275
+ const sha = await resolveHead(root);
276
+ if (!sha) return void 0;
277
+ const dirty = await workingTreeDirty(root);
278
+ const branch = await gitBranch(root);
279
+ return { sha, shortSha: sha.slice(0, 7), branch, dirty: dirty === true ? true : dirty === false ? false : void 0 };
280
+ }
281
+ function gitBranch(root) {
282
+ return new Promise((resolve3) => {
283
+ execFile(
284
+ "git",
285
+ ["rev-parse", "--abbrev-ref", "HEAD"],
286
+ { cwd: root, timeout: 5e3, windowsHide: true, encoding: "utf8" },
287
+ (err, stdout) => {
288
+ if (err) return resolve3(void 0);
289
+ const name = String(stdout).trim();
290
+ resolve3(name && name !== "HEAD" ? name : void 0);
291
+ }
292
+ );
293
+ });
294
+ }
295
+ function rel(root, p) {
296
+ const r = path.relative(root, p);
297
+ return r.startsWith("..") ? p : r;
298
+ }
299
+
300
+ // src/util/progress.ts
301
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
302
+ var WIDTH = 30;
303
+ function fmtDuration(ms) {
304
+ const s = Math.max(0, Math.round(ms / 1e3));
305
+ if (s < 60) return `${s}s`;
306
+ return `${Math.floor(s / 60)}m${String(s % 60).padStart(2, "0")}s`;
307
+ }
308
+ function formatProgressLine(label, done, total, elapsedMs, frame) {
309
+ const ratio = total > 0 ? Math.min(done / total, 1) : 0;
310
+ const bar = brandProgressBar(ratio, WIDTH);
311
+ const pct = Math.round(ratio * 100);
312
+ const spin = c.cyan(FRAMES[(frame % FRAMES.length + FRAMES.length) % FRAMES.length]);
313
+ const eta = ratio > 0.02 && ratio < 1 ? ` \xB7 eta ${fmtDuration(elapsedMs / ratio * (1 - ratio))}` : "";
314
+ return ` ${spin} ${label} ${bar} ${c.bold(`${pct}%`)} ${c.dim(`${done}/${total} \xB7 ${fmtDuration(elapsedMs)}${eta}`)}`;
315
+ }
316
+ var ProgressBar = class {
317
+ constructor(label) {
318
+ this.label = label;
319
+ }
320
+ label;
321
+ start = Date.now();
322
+ frame = 0;
323
+ tty = Boolean(process.stderr.isTTY);
324
+ update(done, total) {
325
+ if (!this.tty) return;
326
+ const line = formatProgressLine(this.label, done, total, Date.now() - this.start, this.frame++);
327
+ process.stderr.write(`\r\x1B[2K${line}`);
328
+ }
329
+ /** Clear the bar; optionally print a final summary line. */
330
+ done(summary) {
331
+ if (!this.tty) return;
332
+ process.stderr.write("\r\x1B[2K");
333
+ if (summary) process.stderr.write(`${summary}
334
+ `);
335
+ }
336
+ };
337
+
338
+ // src/cli-options.ts
339
+ function applyGlobalOptions(cmd) {
340
+ return cmd.option("-C, --cwd <dir>", "run as if started in <dir>", ".").option("--deep", "do more: precise resolution, all edges, semantic links").option("--json", "machine-readable JSON on stdout").option("--generated-at <iso>", "pin the artifact timestamp for byte-deterministic output").option("--graph <file>", "override the map path").option("--no-cache", "full rebuild (ignore the incremental cache)").option("--local", "never touch the network (lexical-only semantic)").option("--quiet", "suppress progress output").option("--client <name>", "identify the AI client (e.g. claude) so navigation calls are counted in vg savings").option("--no-color", "disable colored output");
341
+ }
342
+ function readGlobal(cmd) {
343
+ const o = cmd.opts();
344
+ return {
345
+ cwd: o.cwd,
346
+ deep: o.deep,
347
+ json: o.json,
348
+ generatedAt: o.generatedAt,
349
+ graph: o.graph,
350
+ noCache: o.cache === false,
351
+ local: o.local,
352
+ quiet: o.quiet,
353
+ client: o.client
354
+ };
355
+ }
356
+
357
+ // src/commands/build.ts
358
+ function registerBuild(program) {
359
+ 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) {
360
+ await runBuild(paths, opts, readGlobal(this));
361
+ });
362
+ applyGlobalOptions(cmd);
363
+ }
364
+ async function runBuild(paths, opts, global) {
365
+ const root = path.resolve(global.cwd ?? ".");
366
+ if (opts.verify) {
367
+ await verifyGraph(root, opts, global);
368
+ return;
369
+ }
370
+ const interactive = !global.json && !global.quiet;
371
+ if (interactive) printLogo(path.basename(root) || root);
372
+ const bar = interactive ? new ProgressBar(c.dim("parsing")) : void 0;
373
+ const jobs = opts.jobs ? Number(opts.jobs) : void 0;
374
+ if (jobs !== void 0 && (!Number.isInteger(jobs) || jobs < 1)) {
375
+ throw usageError(`--jobs must be a positive integer (got "${opts.jobs}")`);
376
+ }
377
+ const only = opts.only ? opts.only.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
378
+ let result;
379
+ try {
380
+ result = await buildGraph({
381
+ root,
382
+ paths: paths.length ? paths : void 0,
383
+ only,
384
+ exclude: opts.exclude,
385
+ jobs,
386
+ noCache: global.noCache,
387
+ deep: global.deep,
388
+ noGround: opts.ground === false,
389
+ scip: typeof opts.scip === "string" ? opts.scip : void 0,
390
+ noScip: opts.scip === false,
391
+ noTsc: opts.tsc === false,
392
+ generatedAt: global.generatedAt,
393
+ onParseProgress: bar ? (done, total) => bar.update(done, total) : void 0,
394
+ grammarsDir: opts.grammars
395
+ });
396
+ } catch (err) {
397
+ bar?.done();
398
+ if (err instanceof UsageError) throw usageError(err.message);
399
+ if (err instanceof ResourceLimitError) throw new CliError(err.message, ExitCode.ERROR);
400
+ throw err;
401
+ }
402
+ bar?.done();
403
+ const written = writeArtifacts(result.graph, {
404
+ root,
405
+ html: opts.html,
406
+ report: opts.report,
407
+ graphPath: global.graph
408
+ });
409
+ if (!global.graph) {
410
+ writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, {
411
+ only,
412
+ exclude: opts.exclude,
413
+ paths: paths.length ? paths : void 0,
414
+ deep: global.deep,
415
+ noGround: opts.ground === false,
416
+ scip: typeof opts.scip === "string" ? opts.scip : void 0,
417
+ noScip: opts.scip === false,
418
+ noTsc: opts.tsc === false,
419
+ grammarsDir: opts.grammars
420
+ });
421
+ }
422
+ if (opts.export) writeExport(result.graph, opts.export);
423
+ if (!global.graph) {
424
+ const fileCount = result.graph.nodes.filter((n) => n.kind === "file").length;
425
+ const refreshed = refreshInstalledInstructions(root, fileCount > 0 && fileCount < SMALL_REPO_FILES);
426
+ if (interactive) {
427
+ for (const r of refreshed) {
428
+ info(c.dim(`vg \xB7 refreshed assistant instructions ${r.file} (v${r.from} \u2192 v${r.to})`));
429
+ }
430
+ }
431
+ }
432
+ let attestation;
433
+ const attestNotices = [];
434
+ if (opts.attest) {
435
+ const signed = await signGraphAttestation(root, result.graph, {
436
+ key: opts.attestKey,
437
+ attestation: opts.attestation
438
+ });
439
+ attestation = signed.summary;
440
+ attestNotices.push(...signed.notices);
441
+ }
442
+ if (global.json) {
443
+ json({
444
+ ok: true,
445
+ counts: result.graph.meta.counts,
446
+ languages: result.graph.meta.languages,
447
+ reparsed: result.reparsed,
448
+ reused: result.reused,
449
+ totalFiles: result.totalFiles,
450
+ resolve: result.resolveStats,
451
+ tsc: result.tsc,
452
+ scip: result.scip,
453
+ epistemic: epistemicBreakdown(result.graph.edges),
454
+ artifacts: written,
455
+ corpusHash: result.graph.provenance.corpusHash,
456
+ toolchain: result.graph.provenance.toolchain,
457
+ attestation,
458
+ timingMs: result.timing.totalMs,
459
+ warnings: result.warnings
460
+ });
461
+ return;
462
+ }
463
+ const { counts } = result.graph.meta;
464
+ const incremental = result.reused > 0 ? `incremental: ${result.reparsed} of ${result.totalFiles} files re-parsed` : `${result.totalFiles} files parsed`;
465
+ const seconds = (result.timing.totalMs / 1e3).toFixed(2);
466
+ info(`${c.cyan("vg")} \xB7 mapped ${rel2(root)} in ${seconds}s (${incremental})`);
467
+ info(
468
+ ` nodes ${c.bold(String(counts.nodes))} edges ${c.bold(String(counts.edges))} areas ${counts.areas} langs ${result.graph.meta.languages.join(",") || "\u2014"}`
469
+ );
470
+ const callPct = result.resolveStats.callsResolved + result.resolveStats.callsUnresolved > 0 ? Math.round(
471
+ 100 * result.resolveStats.callsResolved / (result.resolveStats.callsResolved + result.resolveStats.callsUnresolved)
472
+ ) : 100;
473
+ const precise = result.tsc || result.scip;
474
+ const callLabel = precise ? `heuristic floor ${callPct}%` : `calls resolved ${callPct}%`;
475
+ info(c.dim(` ${callLabel} \xB7 resolver ${result.graph.provenance.resolver.join(",")}`));
476
+ if (result.tsc) {
477
+ const jsx = result.tsc.jsx > 0 ? `, ${result.tsc.jsx} JSX` : "";
478
+ info(c.dim(` tsc: ${result.tsc.resolved} precise edges across ${result.tsc.files} TS/JS files (${result.tsc.calls} calls${jsx})`));
479
+ }
480
+ if (result.scip) {
481
+ info(c.dim(` scip: ${result.scip.resolved} precise edges from ${result.scip.tool ?? "index"} (${result.scip.documents} docs)`));
482
+ }
483
+ const ep = epistemicBreakdown(result.graph.edges);
484
+ info(
485
+ c.dim(
486
+ ` edges by evidence \xB7 observed ${ep.observed} \xB7 name-matched ${ep["name-matched"]} \xB7 declared ${ep.declared}`
487
+ )
488
+ );
489
+ const artifactList = [written.graphPath, written.htmlPath, written.reportPath].filter(Boolean).map((p) => path.relative(root, p)).join(" ");
490
+ info(` \u2192 ${artifactList}`);
491
+ if (result.warnings.length) {
492
+ info(c.yellow(` ${result.warnings.length} parse warning(s) \u2014 run with --json for detail`));
493
+ }
494
+ if (attestation) {
495
+ for (const n of attestNotices) info(c.yellow(` ${n}`));
496
+ info(
497
+ c.dim(
498
+ ` attested \xB7 keyid ${attestation.keyid} \xB7 digest ${attestation.graphDigest.slice(0, 16)}\u2026 \u2192 ${attestation.out}`
499
+ )
500
+ );
501
+ }
502
+ maybeWarmEmbeddings(root, result.graph, global, opts.warm !== false);
503
+ }
504
+ async function verifyGraph(root, opts, global) {
505
+ const only = opts.only ? opts.only.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
506
+ const jobs = opts.jobs ? Number(opts.jobs) : void 0;
507
+ const det = await verifyDeterminism({ root, only, exclude: opts.exclude, jobs });
508
+ const attest = verifyGraphAttestation(root, { attestation: opts.attestation, pub: opts.pub });
509
+ const attestFailed = !attest.missing && attest.result?.status === "failed";
510
+ if (global.json) {
511
+ json({
512
+ ok: det.ok && !attestFailed,
513
+ determinism: { ok: det.ok, checks: det.checks, digest: det.digest },
514
+ attestation: attest.missing ? null : {
515
+ status: attest.result?.status,
516
+ signatureValid: attest.result?.signatureValid,
517
+ signerPinned: attest.result?.signerPinned,
518
+ digestMatches: attest.result?.digestMatches,
519
+ dirty: attest.result?.dirty,
520
+ keyid: attest.result?.keyid,
521
+ reason: attest.result?.reason
522
+ }
523
+ });
524
+ } else {
525
+ info(`${c.cyan("vg build --verify")} \xB7 ${path.relative(process.cwd(), root) || "."}`);
526
+ for (const check of det.checks) {
527
+ const mark = check.ok ? c.green("\u2714") : c.red("\u2718");
528
+ const detail = check.detail ? c.dim(` (${check.detail})`) : "";
529
+ info(` ${mark} ${check.name}${detail}`);
530
+ }
531
+ info(det.ok ? c.green(` deterministic \xB7 digest ${det.digest.slice(0, 16)}\u2026`) : c.red(" NON-DETERMINISTIC"));
532
+ if (attest.missing) {
533
+ info(c.dim(` attestation: none (sign one with \`vg build --attest\`)`));
534
+ } else {
535
+ const r = attest.result;
536
+ const badge = r?.status === "verified" ? c.green("\u2714 attestation verified") : r?.status === "signature-valid" ? c.yellow("~ attestation signature valid") : c.red("\u2718 attestation failed");
537
+ info(` ${badge}${r?.keyid ? c.dim(` \xB7 keyid ${r.keyid}`) : ""}`);
538
+ info(c.dim(` ${r?.reason ?? ""}`));
539
+ }
540
+ }
541
+ if (!det.ok) throw new CliError("determinism self-check failed", ExitCode.NON_DETERMINISTIC);
542
+ if (attestFailed) throw new CliError("attestation verification failed", ExitCode.GATE_FAILED);
543
+ }
544
+ function maybeWarmEmbeddings(root, graph, global, warm) {
545
+ if (!warm || global.json || global.quiet || global.local) return;
546
+ if (!process.stdout.isTTY && !process.stderr.isTTY) return;
547
+ const cli = process.argv[1];
548
+ if (!cli) return;
549
+ const modelId = resolveEmbedModel();
550
+ const ready = isModelReady(modelId);
551
+ if (ready && countPending(graph, root, modelId) === 0) return;
552
+ const args = ready ? [cli, "embed", "-C", root, "--bg"] : [cli, "embed", "-C", root, "--bg", "--download"];
553
+ try {
554
+ const child = spawn(process.execPath, args, { detached: true, stdio: "ignore" });
555
+ child.unref();
556
+ info(
557
+ c.dim(
558
+ ready ? " warming the semantic index in the background \u2014 `vg ask` will be instant" : " downloading the semantic model in the background (once) \u2014 `vg ask`/`vg serve` will be instant; disable with --no-warm"
559
+ )
560
+ );
561
+ } catch {
562
+ }
563
+ }
564
+ function writeExport(graph, target) {
565
+ if (target === "-") {
566
+ out(serializeGraph(graph).trimEnd());
567
+ return;
568
+ }
569
+ const ext = path.extname(target).toLowerCase();
570
+ let content;
571
+ switch (ext) {
572
+ case ".json":
573
+ content = serializeGraph(graph);
574
+ break;
575
+ case ".md":
576
+ content = renderReport(graph);
577
+ break;
578
+ case ".html":
579
+ content = renderHtml(graph);
580
+ break;
581
+ default:
582
+ throw new CliError(
583
+ `cannot export to "${ext || target}" yet \u2014 supported in Phase 0: .json, .md, .html, "-" (stdout). More formats (graphml, dot, cypher) arrive with \`vg export\` in Phase 1.`,
584
+ ExitCode.USAGE_ERROR
585
+ );
586
+ }
587
+ fs.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
588
+ fs.writeFileSync(target, content);
589
+ }
590
+ function collect(value, prev) {
591
+ return [...prev, value];
592
+ }
593
+ function rel2(root) {
594
+ const r = path.relative(process.cwd(), root);
595
+ return r === "" ? "." : r;
596
+ }
597
+
598
+ // src/code/router.ts
599
+ var HOSTED_IDS = /* @__PURE__ */ new Set(["openrouter", "litellm", "openai", "together"]);
600
+ function resolveProviders(opts, deps = {}) {
601
+ const env = deps.env ?? process.env;
602
+ const discover = deps.discover ?? discoverModels;
603
+ if (opts.mockReply !== void 0) {
604
+ return { providers: [new MockProvider(opts.model ?? "mock-1", opts.mockReply)], reason: "mock provider (scripted, offline)" };
605
+ }
606
+ if (opts.provider) {
607
+ const provider = buildExplicit(opts.provider, opts, env, discover);
608
+ const fallbacks = opts.local || provider.local ? localFallbacks(opts, env, discover, provider.id) : [];
609
+ return { providers: [provider, ...fallbacks], reason: `explicit --provider ${opts.provider}` };
610
+ }
611
+ if (opts.local) {
612
+ const locals2 = localFallbacks(opts, env, discover, "");
613
+ if (locals2.length === 0) {
614
+ throw new CliError(
615
+ "no local model backend available for --local. Start Ollama (`ollama serve` + `ollama pull <model>`), run LM Studio, or point --provider llama-cpp at a gguf. See `vg models`.",
616
+ ExitCode.NOT_FOUND
617
+ );
618
+ }
619
+ return { providers: locals2, reason: "--local (on-device backends only)" };
620
+ }
621
+ if (env.OPENROUTER_API_KEY) {
622
+ const model = resolveHostedModel(opts, env, "openrouter");
623
+ const primary = buildHosted("openrouter", model);
624
+ return { providers: [primary, ...localFallbacks(opts, env, discover, "openrouter")], reason: "OPENROUTER_API_KEY is set \u2192 OpenRouter (with local fallback)" };
625
+ }
626
+ const locals = localFallbacks(opts, env, discover, "");
627
+ if (locals.length) return { providers: locals, reason: "a local model is available \u2192 on-device (no key needed)" };
628
+ for (const id of ["litellm", "openai", "together"]) {
629
+ const keyEnv = OPENAI_COMPATIBLE[id].apiKeyEnv;
630
+ if (keyEnv && env[keyEnv]) {
631
+ const model = resolveHostedModel(opts, env, id);
632
+ return { providers: [buildHosted(id, model)], reason: `${keyEnv} is set \u2192 ${OPENAI_COMPATIBLE[id].label}` };
633
+ }
634
+ }
635
+ throw new CliError(
636
+ "no model backend configured. Do one of: set OPENROUTER_API_KEY for the hosted router, run Ollama locally (`ollama serve`), or use --local with a local model. Set the model with --model or VG_CODE_MODEL. (No models are installed by default.)",
637
+ ExitCode.NOT_FOUND
638
+ );
639
+ }
640
+ function buildExplicit(id, opts, env, discover) {
641
+ if (id === "ollama") return new OllamaProvider(resolveLocalModel(opts, discover, "ollama"), env.OLLAMA_HOST);
642
+ if (id === "llama-cpp") {
643
+ const modelPath = opts.modelPath || env.VG_CODE_MODEL_PATH;
644
+ if (!modelPath) throw new CliError("--provider llama-cpp needs a gguf path via --model-path or VG_CODE_MODEL_PATH (no weights are downloaded automatically; see `vg models`).", ExitCode.USAGE_ERROR);
645
+ return new LocalLlamaProvider(opts.model ?? basename2(modelPath), modelPath, void 0, !!opts.consent);
646
+ }
647
+ if (id === "lmstudio") return buildHosted("lmstudio", resolveHostedModel(opts, env, "lmstudio"));
648
+ if (HOSTED_IDS.has(id)) return buildHosted(id, resolveHostedModel(opts, env, id));
649
+ throw new CliError(`unknown --provider "${id}". Known: ollama, lmstudio, openrouter, litellm, openai, together, llama-cpp.`, ExitCode.USAGE_ERROR);
650
+ }
651
+ function buildHosted(id, model) {
652
+ const base = OPENAI_COMPATIBLE[id];
653
+ const config = { ...base, id };
654
+ return new OpenAiCompatibleProvider(model, config);
655
+ }
656
+ function localFallbacks(opts, env, discover, exclude) {
657
+ const out2 = [];
658
+ const models = safeDiscover(discover);
659
+ const ollamaModel = opts.model ?? firstOfRuntime(models, "ollama");
660
+ if (exclude !== "ollama" && ollamaModel) out2.push(new OllamaProvider(ollamaModel, env.OLLAMA_HOST));
661
+ const lmModel = opts.model ?? firstOfRuntime(models, "lm-studio");
662
+ if (exclude !== "lmstudio" && lmModel) out2.push(buildHosted("lmstudio", lmModel));
663
+ return out2;
664
+ }
665
+ function resolveLocalModel(opts, discover, runtime) {
666
+ if (opts.model) return opts.model;
667
+ const found = firstOfRuntime(safeDiscover(discover), runtime);
668
+ if (found) return found;
669
+ throw new CliError(`--provider ${runtime} needs a model. Pull one (\`ollama pull <model>\`) then re-run, or pass --model. See \`vg models\`.`, ExitCode.NOT_FOUND);
670
+ }
671
+ function resolveHostedModel(opts, env, id) {
672
+ const model = opts.model || env.VG_CODE_MODEL;
673
+ if (model) return model;
674
+ throw new CliError(
675
+ `${OPENAI_COMPATIBLE[id].label} needs a model id \u2014 pass --model (e.g. a current best-in-breed coding model) or set VG_CODE_MODEL. We don't hard-code a model so you always pick the current best one.`,
676
+ ExitCode.USAGE_ERROR
677
+ );
678
+ }
679
+ function firstOfRuntime(models, runtime) {
680
+ const m = models.find((x) => x.runtime === runtime);
681
+ return m?.name;
682
+ }
683
+ function safeDiscover(discover) {
684
+ try {
685
+ return discover();
686
+ } catch {
687
+ return [];
688
+ }
689
+ }
690
+ function basename2(p) {
691
+ return p.replace(/\\/g, "/").split("/").pop() ?? p;
692
+ }
693
+
694
+ export { DSSE_PAYLOAD_TYPE, ProgressBar, applyGlobalOptions, dssePae, generateKeypair, keyId, printCodeLogo, printLogo, readGlobal, registerBuild, resolveProviders, runBuild, sha256Hex };
695
+ //# sourceMappingURL=chunk-2EVQKZTO.js.map
696
+ //# sourceMappingURL=chunk-2EVQKZTO.js.map