@vibgrate/cli 2026.720.1 → 2026.720.3
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/baseline-FALF7ULT.js +6 -0
- package/dist/{baseline-5QPV6API.js.map → baseline-FALF7ULT.js.map} +1 -1
- package/dist/{chunk-AJDUMRVI.js → chunk-B6ZWB3WH.js} +3 -3
- package/dist/{chunk-AJDUMRVI.js.map → chunk-B6ZWB3WH.js.map} +1 -1
- package/dist/{chunk-VFO5UDAT.js → chunk-BHA7QIPD.js} +185 -16
- package/dist/chunk-BHA7QIPD.js.map +1 -0
- package/dist/{chunk-VYSTOEAK.js → chunk-IUKCKMR6.js} +3 -3
- package/dist/{chunk-VYSTOEAK.js.map → chunk-IUKCKMR6.js.map} +1 -1
- package/dist/{chunk-VQLOVQZP.js → chunk-ZFDVHKID.js} +803 -711
- package/dist/chunk-ZFDVHKID.js.map +1 -0
- package/dist/cli.js +349 -100
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +26 -7
- package/dist/index.js +3 -3
- package/dist/parse-worker.js +1 -1
- package/grammars/tree-sitter-objc.wasm +0 -0
- package/grammars/tree-sitter-ocaml.wasm +0 -0
- package/grammars/tree-sitter-rescript.wasm +0 -0
- package/grammars/tree-sitter-solidity.wasm +0 -0
- package/package.json +7 -7
- package/skills/vg/SKILL.md +29 -9
- package/dist/baseline-5QPV6API.js +0 -6
- package/dist/chunk-VFO5UDAT.js.map +0 -1
- package/dist/chunk-VQLOVQZP.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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, recordCliCall, CLI_TOOL_ALIASES, countPending, resolveOne, indexFor, countTokens, shortestPath, renderReport, impactOf, testsToRun, detectRunner, defaultGraphPath, serveStdio, ASSISTANTS, detectAssistants, 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, fetchHostedDocsCached, selectForBudget, formatForExt, hostedBase, verifyDeterminism, renderHtml, isModelReady, SAVINGS_TOOLS,
|
|
3
|
-
import { resolvedGrammarFiles, grammarSetVersion, hashString, canonicalize, hashBytes } from './chunk-
|
|
4
|
-
import { loadAdvancedScanHook, baselineCommand } from './chunk-
|
|
5
|
-
import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, loadConfig, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, PROJECT_TYPE_TO_OSV_ECOSYSTEM, fetchRiskySymbols, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-
|
|
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, recordCliCall, CLI_TOOL_ALIASES, countPending, resolveOne, indexFor, countTokens, shortestPath, renderReport, impactOf, testsToRun, detectRunner, defaultGraphPath, savingsLedgerPath, serveStdio, ASSISTANTS, detectAssistants, usageError, loadGraph, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, coveringTests, FREE_PACK, readScanArtifact, enrichOnline, ECOSYSTEMS, discoverModels, readSavings, readUsage, exportGraph, catalogPath, libDir, stableStringify, buildGraph, writeArtifacts, writeSnapshot, UsageError, ResourceLimitError, refreshInstalledInstructions, SMALL_REPO_FILES, 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, fetchHostedDocsCached, selectForBudget, formatForExt, hostedBase, verifyDeterminism, renderHtml, isModelReady, SAVINGS_TOOLS, sanitizeClient, readStoredCredentials, projectCredentialsPath, homeCredentialsPath, SKIP_DIRS, SKIP_FILES } from './chunk-ZFDVHKID.js';
|
|
3
|
+
import { resolvedGrammarFiles, grammarSetVersion, hashString, canonicalize, hashBytes } from './chunk-BHA7QIPD.js';
|
|
4
|
+
import { loadAdvancedScanHook, baselineCommand } from './chunk-IUKCKMR6.js';
|
|
5
|
+
import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, loadConfig, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, PROJECT_TYPE_TO_OSV_ECOSYSTEM, fetchRiskySymbols, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-B6ZWB3WH.js';
|
|
6
6
|
import './chunk-RXP66R2E.js';
|
|
7
7
|
import { pathExists as pathExists$1, parseExcludePatterns } from './chunk-GI6W53LM.js';
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
8
|
+
import * as fs10 from 'fs';
|
|
9
|
+
import fs10__default, { realpathSync } from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import path__default from 'path';
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
@@ -16,7 +16,7 @@ import * as crypto from 'crypto';
|
|
|
16
16
|
import { randomUUID } from 'crypto';
|
|
17
17
|
import chalk8 from 'chalk';
|
|
18
18
|
import * as os from 'os';
|
|
19
|
-
import * as
|
|
19
|
+
import * as fs17 from 'fs/promises';
|
|
20
20
|
import * as readline from 'readline';
|
|
21
21
|
import semver from 'semver';
|
|
22
22
|
|
|
@@ -178,14 +178,14 @@ async function signGraphAttestation(root, graph, opts) {
|
|
|
178
178
|
const explicitKey = opts.key ?? process.env.VG_ATTEST_KEY;
|
|
179
179
|
const keyPath = explicitKey ? path.resolve(explicitKey) : path.join(root, ".vibgrate", DEFAULT_KEY);
|
|
180
180
|
let keyGeneratedAt;
|
|
181
|
-
if (!
|
|
181
|
+
if (!fs10.existsSync(keyPath)) {
|
|
182
182
|
if (explicitKey) {
|
|
183
183
|
throw new CliError(`signing key not found: ${explicitKey}`, ExitCode.USAGE_ERROR);
|
|
184
184
|
}
|
|
185
185
|
const kp = generateKeypair();
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
fs10.mkdirSync(path.dirname(keyPath), { recursive: true });
|
|
187
|
+
fs10.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
|
|
188
|
+
fs10.writeFileSync(`${keyPath}.pub`, kp.publicPem);
|
|
189
189
|
keyGeneratedAt = keyPath;
|
|
190
190
|
notices.push(
|
|
191
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`
|
|
@@ -193,7 +193,7 @@ async function signGraphAttestation(root, graph, opts) {
|
|
|
193
193
|
}
|
|
194
194
|
let privateKey;
|
|
195
195
|
try {
|
|
196
|
-
privateKey = crypto.createPrivateKey(
|
|
196
|
+
privateKey = crypto.createPrivateKey(fs10.readFileSync(keyPath, "utf8"));
|
|
197
197
|
} catch {
|
|
198
198
|
throw new CliError(`could not read an Ed25519 private key from ${rel(root, keyPath)}`, ExitCode.USAGE_ERROR);
|
|
199
199
|
}
|
|
@@ -207,8 +207,8 @@ async function signGraphAttestation(root, graph, opts) {
|
|
|
207
207
|
const statement = buildStatement(graph, { commit });
|
|
208
208
|
const envelope = signStatement(statement, privateKey);
|
|
209
209
|
const outPath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
fs10.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
211
|
+
fs10.writeFileSync(outPath, serializeEnvelope(envelope));
|
|
212
212
|
return {
|
|
213
213
|
summary: {
|
|
214
214
|
keyid: envelope.signatures[0]?.keyid ?? "",
|
|
@@ -223,7 +223,7 @@ async function signGraphAttestation(root, graph, opts) {
|
|
|
223
223
|
}
|
|
224
224
|
function verifyGraphAttestation(root, opts) {
|
|
225
225
|
const bundlePath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
|
|
226
|
-
if (!
|
|
226
|
+
if (!fs10.existsSync(bundlePath)) {
|
|
227
227
|
if (opts.attestation || opts.pub) {
|
|
228
228
|
throw new CliError(
|
|
229
229
|
`no attestation at ${rel(root, bundlePath)} \u2014 sign one with \`vg build --attest\``,
|
|
@@ -232,8 +232,8 @@ function verifyGraphAttestation(root, opts) {
|
|
|
232
232
|
}
|
|
233
233
|
return { bundlePath, missing: true };
|
|
234
234
|
}
|
|
235
|
-
const envelope = parseEnvelope(
|
|
236
|
-
const publicKeyPem = opts.pub ?
|
|
235
|
+
const envelope = parseEnvelope(fs10.readFileSync(bundlePath, "utf8"));
|
|
236
|
+
const publicKeyPem = opts.pub ? fs10.readFileSync(path.resolve(opts.pub), "utf8") : void 0;
|
|
237
237
|
const graph = loadGraph(root) ?? void 0;
|
|
238
238
|
return { bundlePath, missing: false, result: verifyEnvelope(envelope, { publicKeyPem, graph }) };
|
|
239
239
|
}
|
|
@@ -431,6 +431,15 @@ async function runBuild(paths, opts, global) {
|
|
|
431
431
|
});
|
|
432
432
|
}
|
|
433
433
|
if (opts.export) writeExport(result.graph, opts.export);
|
|
434
|
+
if (!global.graph) {
|
|
435
|
+
const fileCount = result.graph.nodes.filter((n) => n.kind === "file").length;
|
|
436
|
+
const refreshed = refreshInstalledInstructions(root, fileCount > 0 && fileCount < SMALL_REPO_FILES);
|
|
437
|
+
if (interactive) {
|
|
438
|
+
for (const r of refreshed) {
|
|
439
|
+
info(c.dim(`vg \xB7 refreshed assistant instructions ${r.file} (v${r.from} \u2192 v${r.to})`));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
434
443
|
let attestation;
|
|
435
444
|
const attestNotices = [];
|
|
436
445
|
if (opts.attest) {
|
|
@@ -586,8 +595,8 @@ function writeExport(graph, target) {
|
|
|
586
595
|
ExitCode.USAGE_ERROR
|
|
587
596
|
);
|
|
588
597
|
}
|
|
589
|
-
|
|
590
|
-
|
|
598
|
+
fs10.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
|
|
599
|
+
fs10.writeFileSync(target, content);
|
|
591
600
|
}
|
|
592
601
|
function collect(value, prev) {
|
|
593
602
|
return [...prev, value];
|
|
@@ -606,7 +615,7 @@ async function runStatus(global) {
|
|
|
606
615
|
const root = path.resolve(global.cwd ?? ".");
|
|
607
616
|
const graphPath = global.graph ?? defaultGraphPath(root);
|
|
608
617
|
const graph = loadGraph(root, graphPath);
|
|
609
|
-
const hasCache =
|
|
618
|
+
const hasCache = fs10.existsSync(path.join(cacheDir(root), "parse-cache.json"));
|
|
610
619
|
let stale = null;
|
|
611
620
|
let staleExact = false;
|
|
612
621
|
if (graph) {
|
|
@@ -1315,14 +1324,14 @@ function isCI(env = process.env) {
|
|
|
1315
1324
|
function installId(env = process.env) {
|
|
1316
1325
|
if (telemetryOptOut(env) !== null || isCI(env)) return randomUuid();
|
|
1317
1326
|
try {
|
|
1318
|
-
const existing =
|
|
1327
|
+
const existing = fs10.readFileSync(INSTALL_ID_FILE, "utf8").trim();
|
|
1319
1328
|
if (isUuid(existing)) return existing;
|
|
1320
1329
|
} catch {
|
|
1321
1330
|
}
|
|
1322
1331
|
const id = randomUuid();
|
|
1323
1332
|
try {
|
|
1324
|
-
|
|
1325
|
-
|
|
1333
|
+
fs10.mkdirSync(INSTALL_ID_DIR, { recursive: true });
|
|
1334
|
+
fs10.writeFileSync(INSTALL_ID_FILE, id + "\n", { mode: 384 });
|
|
1326
1335
|
} catch {
|
|
1327
1336
|
}
|
|
1328
1337
|
return id;
|
|
@@ -1346,7 +1355,7 @@ function ledgerFilePath(root) {
|
|
|
1346
1355
|
}
|
|
1347
1356
|
function readOffset(root) {
|
|
1348
1357
|
try {
|
|
1349
|
-
const raw = JSON.parse(
|
|
1358
|
+
const raw = JSON.parse(fs10.readFileSync(offsetPath(root), "utf8"));
|
|
1350
1359
|
return typeof raw.offset === "number" && raw.offset >= 0 ? raw.offset : 0;
|
|
1351
1360
|
} catch {
|
|
1352
1361
|
return 0;
|
|
@@ -1354,8 +1363,8 @@ function readOffset(root) {
|
|
|
1354
1363
|
}
|
|
1355
1364
|
function writeOffset(root, offset) {
|
|
1356
1365
|
try {
|
|
1357
|
-
|
|
1358
|
-
|
|
1366
|
+
fs10.mkdirSync(cacheDir(root), { recursive: true });
|
|
1367
|
+
fs10.writeFileSync(offsetPath(root), JSON.stringify({ offset }) + "\n");
|
|
1359
1368
|
} catch {
|
|
1360
1369
|
}
|
|
1361
1370
|
}
|
|
@@ -1363,7 +1372,7 @@ function buildBatch(root, fromOffset, id = installId()) {
|
|
|
1363
1372
|
const file = ledgerFilePath(root);
|
|
1364
1373
|
let size = 0;
|
|
1365
1374
|
try {
|
|
1366
|
-
size =
|
|
1375
|
+
size = fs10.statSync(file).size;
|
|
1367
1376
|
} catch {
|
|
1368
1377
|
return { batch: null, newOffset: 0 };
|
|
1369
1378
|
}
|
|
@@ -1371,13 +1380,13 @@ function buildBatch(root, fromOffset, id = installId()) {
|
|
|
1371
1380
|
if (start >= size) return { batch: null, newOffset: size };
|
|
1372
1381
|
let text = "";
|
|
1373
1382
|
try {
|
|
1374
|
-
const fd =
|
|
1383
|
+
const fd = fs10.openSync(file, "r");
|
|
1375
1384
|
try {
|
|
1376
1385
|
const buf = Buffer.alloc(size - start);
|
|
1377
|
-
|
|
1386
|
+
fs10.readSync(fd, buf, 0, buf.length, start);
|
|
1378
1387
|
text = buf.toString("utf8");
|
|
1379
1388
|
} finally {
|
|
1380
|
-
|
|
1389
|
+
fs10.closeSync(fd);
|
|
1381
1390
|
}
|
|
1382
1391
|
} catch {
|
|
1383
1392
|
return { batch: null, newOffset: fromOffset };
|
|
@@ -1509,10 +1518,143 @@ function isLoopbackOrigin(origin) {
|
|
|
1509
1518
|
if (host === "localhost" || host === "[::1]" || host === "::1") return true;
|
|
1510
1519
|
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
1511
1520
|
}
|
|
1521
|
+
var PUBLISH_INTERVAL_MS = 2e3;
|
|
1522
|
+
var KEEPALIVE_MS = 1e4;
|
|
1523
|
+
var STALE_MS = 6e4;
|
|
1524
|
+
function liveStatsDir(root) {
|
|
1525
|
+
return path.join(cacheDir(root), "serve-live");
|
|
1526
|
+
}
|
|
1527
|
+
var LiveStatsBus = class {
|
|
1528
|
+
constructor(dir, stats, pid = process.pid) {
|
|
1529
|
+
this.dir = dir;
|
|
1530
|
+
this.stats = stats;
|
|
1531
|
+
this.pid = pid;
|
|
1532
|
+
this.file = path.join(dir, `serve-${this.pid}.json`);
|
|
1533
|
+
}
|
|
1534
|
+
dir;
|
|
1535
|
+
stats;
|
|
1536
|
+
pid;
|
|
1537
|
+
timer = null;
|
|
1538
|
+
lastRevision = -1;
|
|
1539
|
+
lastWriteAt = 0;
|
|
1540
|
+
file;
|
|
1541
|
+
start() {
|
|
1542
|
+
if (this.timer) return;
|
|
1543
|
+
this.publish();
|
|
1544
|
+
this.timer = setInterval(() => this.publish(), PUBLISH_INTERVAL_MS);
|
|
1545
|
+
this.timer.unref?.();
|
|
1546
|
+
process.on("exit", this.unlinkOwn);
|
|
1547
|
+
}
|
|
1548
|
+
stop() {
|
|
1549
|
+
if (this.timer) {
|
|
1550
|
+
clearInterval(this.timer);
|
|
1551
|
+
this.timer = null;
|
|
1552
|
+
}
|
|
1553
|
+
process.removeListener("exit", this.unlinkOwn);
|
|
1554
|
+
this.unlinkOwn();
|
|
1555
|
+
}
|
|
1556
|
+
/** Write the current snapshot when it changed (or as a keepalive). Never throws. */
|
|
1557
|
+
publish(now2 = Date.now()) {
|
|
1558
|
+
const snap = this.stats.snapshot();
|
|
1559
|
+
if (snap.revision === this.lastRevision && now2 - this.lastWriteAt < KEEPALIVE_MS) return;
|
|
1560
|
+
this.lastRevision = snap.revision;
|
|
1561
|
+
this.lastWriteAt = now2;
|
|
1562
|
+
const body = { pid: this.pid, updatedAt: now2, snapshot: snap };
|
|
1563
|
+
try {
|
|
1564
|
+
fs10.mkdirSync(this.dir, { recursive: true });
|
|
1565
|
+
const tmp = `${this.file}.tmp`;
|
|
1566
|
+
fs10.writeFileSync(tmp, JSON.stringify(body));
|
|
1567
|
+
fs10.renameSync(tmp, this.file);
|
|
1568
|
+
} catch {
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Fresh snapshots of the OTHER serve processes in this repo. Stale files
|
|
1573
|
+
* (crashed processes) are swept best-effort while reading.
|
|
1574
|
+
*/
|
|
1575
|
+
siblings(now2 = Date.now()) {
|
|
1576
|
+
let entries;
|
|
1577
|
+
try {
|
|
1578
|
+
entries = fs10.readdirSync(this.dir);
|
|
1579
|
+
} catch {
|
|
1580
|
+
return [];
|
|
1581
|
+
}
|
|
1582
|
+
const out2 = [];
|
|
1583
|
+
for (const name of entries.sort()) {
|
|
1584
|
+
if (!/^serve-\d+\.json$/.test(name)) continue;
|
|
1585
|
+
const file = path.join(this.dir, name);
|
|
1586
|
+
let parsed;
|
|
1587
|
+
try {
|
|
1588
|
+
parsed = JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
1589
|
+
} catch {
|
|
1590
|
+
continue;
|
|
1591
|
+
}
|
|
1592
|
+
if (typeof parsed?.pid !== "number" || typeof parsed.updatedAt !== "number" || !parsed.snapshot?.totals) continue;
|
|
1593
|
+
if (now2 - parsed.updatedAt > STALE_MS) {
|
|
1594
|
+
try {
|
|
1595
|
+
fs10.unlinkSync(file);
|
|
1596
|
+
} catch {
|
|
1597
|
+
}
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
if (parsed.pid === this.pid) continue;
|
|
1601
|
+
out2.push(parsed.snapshot);
|
|
1602
|
+
}
|
|
1603
|
+
return out2;
|
|
1604
|
+
}
|
|
1605
|
+
unlinkOwn = () => {
|
|
1606
|
+
try {
|
|
1607
|
+
fs10.unlinkSync(this.file);
|
|
1608
|
+
} catch {
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
};
|
|
1612
|
+
function addInto(target, add) {
|
|
1613
|
+
target.calls += add.calls;
|
|
1614
|
+
target.complete += add.complete;
|
|
1615
|
+
target.partial += add.partial;
|
|
1616
|
+
target.miss += add.miss;
|
|
1617
|
+
target.timed += add.timed;
|
|
1618
|
+
target.totalMs += add.totalMs;
|
|
1619
|
+
target.vgTokens += add.vgTokens;
|
|
1620
|
+
target.baselineTokens += add.baselineTokens;
|
|
1621
|
+
}
|
|
1622
|
+
function mergeRows(base, extra) {
|
|
1623
|
+
const byKey = new Map(base.map((r) => [r.key, { ...r }]));
|
|
1624
|
+
for (const row of extra) {
|
|
1625
|
+
const hit = byKey.get(row.key);
|
|
1626
|
+
if (hit) addInto(hit, row);
|
|
1627
|
+
else byKey.set(row.key, { ...row });
|
|
1628
|
+
}
|
|
1629
|
+
return [...byKey.values()].sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
|
|
1630
|
+
}
|
|
1631
|
+
function mergeSnapshots(own, siblings) {
|
|
1632
|
+
if (!siblings.length) return own;
|
|
1633
|
+
const merged = {
|
|
1634
|
+
startedAt: own.startedAt,
|
|
1635
|
+
revision: own.revision,
|
|
1636
|
+
lastCallAt: own.lastCallAt,
|
|
1637
|
+
totals: { ...own.totals },
|
|
1638
|
+
clients: [...own.clients.map((r) => ({ ...r }))],
|
|
1639
|
+
tools: [...own.tools.map((r) => ({ ...r }))],
|
|
1640
|
+
sources: [...own.sources.map((r) => ({ ...r }))]
|
|
1641
|
+
};
|
|
1642
|
+
for (const s of siblings) {
|
|
1643
|
+
merged.revision += s.revision;
|
|
1644
|
+
if (s.lastCallAt !== null && (merged.lastCallAt === null || s.lastCallAt > merged.lastCallAt)) {
|
|
1645
|
+
merged.lastCallAt = s.lastCallAt;
|
|
1646
|
+
}
|
|
1647
|
+
addInto(merged.totals, s.totals);
|
|
1648
|
+
merged.clients = mergeRows(merged.clients, s.clients);
|
|
1649
|
+
merged.tools = mergeRows(merged.tools, s.tools);
|
|
1650
|
+
merged.sources = mergeRows(merged.sources, s.sources);
|
|
1651
|
+
}
|
|
1652
|
+
return merged;
|
|
1653
|
+
}
|
|
1512
1654
|
|
|
1513
1655
|
// src/mcp/serve-stats.ts
|
|
1514
1656
|
function zeroRow(key2) {
|
|
1515
|
-
return { key: key2, calls: 0, complete: 0, partial: 0, miss: 0, totalMs: 0, vgTokens: 0, baselineTokens: 0 };
|
|
1657
|
+
return { key: key2, calls: 0, complete: 0, partial: 0, miss: 0, timed: 0, totalMs: 0, vgTokens: 0, baselineTokens: 0 };
|
|
1516
1658
|
}
|
|
1517
1659
|
var SessionStats = class {
|
|
1518
1660
|
startedAt;
|
|
@@ -1521,16 +1663,26 @@ var SessionStats = class {
|
|
|
1521
1663
|
totals = zeroRow("total");
|
|
1522
1664
|
byClient = /* @__PURE__ */ new Map();
|
|
1523
1665
|
byTool = /* @__PURE__ */ new Map();
|
|
1666
|
+
bySource = /* @__PURE__ */ new Map();
|
|
1524
1667
|
constructor(now2 = Date.now()) {
|
|
1525
1668
|
this.startedAt = now2;
|
|
1526
1669
|
}
|
|
1527
1670
|
record(sample, now2 = Date.now()) {
|
|
1528
1671
|
this.revision++;
|
|
1529
1672
|
this.lastCallAt = now2;
|
|
1530
|
-
|
|
1673
|
+
const rows = [
|
|
1674
|
+
this.totals,
|
|
1675
|
+
this.rowFor(this.byClient, sample.client),
|
|
1676
|
+
this.rowFor(this.byTool, sample.tool),
|
|
1677
|
+
this.rowFor(this.bySource, sample.source ?? "mcp")
|
|
1678
|
+
];
|
|
1679
|
+
for (const row of rows) {
|
|
1531
1680
|
row.calls++;
|
|
1532
1681
|
row[sample.outcome]++;
|
|
1533
|
-
|
|
1682
|
+
if (typeof sample.ms === "number") {
|
|
1683
|
+
row.timed++;
|
|
1684
|
+
row.totalMs += sample.ms;
|
|
1685
|
+
}
|
|
1534
1686
|
row.vgTokens += sample.vgTokens;
|
|
1535
1687
|
row.baselineTokens += sample.baselineTokens;
|
|
1536
1688
|
}
|
|
@@ -1543,7 +1695,8 @@ var SessionStats = class {
|
|
|
1543
1695
|
lastCallAt: this.lastCallAt,
|
|
1544
1696
|
totals: { ...this.totals },
|
|
1545
1697
|
clients: sorted(this.byClient),
|
|
1546
|
-
tools: sorted(this.byTool)
|
|
1698
|
+
tools: sorted(this.byTool),
|
|
1699
|
+
sources: sorted(this.bySource)
|
|
1547
1700
|
};
|
|
1548
1701
|
}
|
|
1549
1702
|
rowFor(m, key2) {
|
|
@@ -1571,29 +1724,33 @@ function fmtTokens(n) {
|
|
|
1571
1724
|
return String(n);
|
|
1572
1725
|
}
|
|
1573
1726
|
function fmtAvgMs(row) {
|
|
1574
|
-
if (row.
|
|
1575
|
-
const avg = row.totalMs / row.
|
|
1727
|
+
if (row.timed === 0) return "\u2014";
|
|
1728
|
+
const avg = row.totalMs / row.timed;
|
|
1576
1729
|
return avg >= 1e3 ? `${(avg / 1e3).toFixed(1)}s` : `${Math.round(avg)}ms`;
|
|
1577
1730
|
}
|
|
1578
1731
|
var teal2 = (s) => c.hex("#3FB0A4")(s);
|
|
1579
1732
|
var mint2 = (s) => c.hex("#4FE3C1")(s);
|
|
1580
1733
|
var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1581
1734
|
var MAX_TOOL_ROWS = 8;
|
|
1582
|
-
function serveStatusLines(snap, now2, tick = 0) {
|
|
1735
|
+
function serveStatusLines(snap, now2, tick = 0, siblingServers = 0) {
|
|
1583
1736
|
const spinner = mint2(SPINNER[tick % SPINNER.length]);
|
|
1584
1737
|
const up = fmtUptime(now2 - snap.startedAt);
|
|
1585
1738
|
const lines = [];
|
|
1739
|
+
const siblingNote = siblingServers > 0 ? ` ${c.dim(`incl. ${siblingServers} assistant-spawned server${siblingServers === 1 ? "" : "s"} in this repo`)}` : null;
|
|
1586
1740
|
if (snap.totals.calls === 0) {
|
|
1587
1741
|
lines.push(
|
|
1588
|
-
` ${spinner} ${c.bold.white("vg serve")} ${c.dim("\xB7")} up ${teal2(up)} ${c.dim("\xB7 waiting for your assistant\u2019s first tool call\u2026")}`
|
|
1742
|
+
` ${spinner} ${c.bold.white("vg serve")} ${c.dim("\xB7")} up ${teal2(up)} ${c.dim("\xB7 waiting for your assistant\u2019s first call (MCP tool call, or `vg \u2026 --client=<ai>` CLI)\u2026")}`
|
|
1589
1743
|
);
|
|
1744
|
+
if (siblingNote) lines.push(siblingNote);
|
|
1590
1745
|
return lines;
|
|
1591
1746
|
}
|
|
1592
1747
|
const t = snap.totals;
|
|
1593
1748
|
const answered = Math.round((t.complete + t.partial) / t.calls * 100);
|
|
1749
|
+
const cli = snap.sources.find((s) => s.key === "cli");
|
|
1750
|
+
const via = cli ? c.dim(" \xB7 via ") + snap.sources.map((s) => `${c.white(s.key)} ${teal2(String(s.calls))}`).join(c.dim(" \xB7 ")) : "";
|
|
1594
1751
|
const ago = snap.lastCallAt === null ? "" : c.dim(` \xB7 last call ${fmtUptime(now2 - snap.lastCallAt)} ago`);
|
|
1595
1752
|
lines.push(
|
|
1596
|
-
` ${spinner} ${c.bold.white("vg serve")} ${c.dim("\xB7")} up ${teal2(up)} ${c.dim("\xB7")} ${c.bold.white(String(t.calls))} call${t.calls === 1 ? "" : "s"} ${c.dim("\xB7")} ${mint2(`${answered}%`)} answered${ago}`
|
|
1753
|
+
` ${spinner} ${c.bold.white("vg serve")} ${c.dim("\xB7")} up ${teal2(up)} ${c.dim("\xB7")} ${c.bold.white(String(t.calls))} call${t.calls === 1 ? "" : "s"} ${c.dim("\xB7")} ${mint2(`${answered}%`)} answered${via}${ago}`
|
|
1597
1754
|
);
|
|
1598
1755
|
lines.push(
|
|
1599
1756
|
` ${c.dim("clients")} ` + snap.clients.map((cl) => `${c.white(cl.key)} ${teal2(String(cl.calls))}${c.dim(` (avg ${fmtAvgMs(cl)})`)}`).join(c.dim(" \xB7 "))
|
|
@@ -1613,15 +1770,15 @@ function serveStatusLines(snap, now2, tick = 0) {
|
|
|
1613
1770
|
}
|
|
1614
1771
|
if (t.baselineTokens > 0) {
|
|
1615
1772
|
const savedTokens = Math.max(0, t.baselineTokens - t.vgTokens);
|
|
1616
|
-
const savedUsd = savedTokens / 1e6 * DEFAULT_RATE_PER_M;
|
|
1617
1773
|
const ratio = t.vgTokens > 0 ? (t.baselineTokens / t.vgTokens).toFixed(1) : "\u2014";
|
|
1618
1774
|
lines.push(
|
|
1619
1775
|
` ${c.dim("session")} ctx ${c.bold.white(fmtTokens(t.vgTokens))} ` + c.dim(`vs grep/read \u2248${fmtTokens(t.baselineTokens)} \u2192 `) + mint2(`${ratio}\xD7 fewer`)
|
|
1620
1776
|
);
|
|
1621
1777
|
lines.push(
|
|
1622
|
-
` ${c.dim("est. saved \u2248")} ${mint2(`${fmtTokens(savedTokens)} tokens
|
|
1778
|
+
` ${c.dim("est. saved \u2248")} ${mint2(`${fmtTokens(savedTokens)} context tokens`)} ${c.dim("(estimate)")}`
|
|
1623
1779
|
);
|
|
1624
1780
|
}
|
|
1781
|
+
if (siblingNote) lines.push(siblingNote);
|
|
1625
1782
|
return lines;
|
|
1626
1783
|
}
|
|
1627
1784
|
function serveHeartbeatLine(snap, now2) {
|
|
@@ -1629,7 +1786,9 @@ function serveHeartbeatLine(snap, now2) {
|
|
|
1629
1786
|
const t = snap.totals;
|
|
1630
1787
|
if (t.calls === 0) return c.dim(`vg \xB7 serving for ${up} \u2014 no tool calls yet`);
|
|
1631
1788
|
const clients = snap.clients.map((cl) => `${cl.key} ${cl.calls}`).join(", ");
|
|
1789
|
+
const cli = snap.sources.find((s) => s.key === "cli");
|
|
1632
1790
|
let line = `vg \xB7 serving for ${up} \u2014 ${t.calls} call${t.calls === 1 ? "" : "s"} (${clients})`;
|
|
1791
|
+
if (cli) line += ` \xB7 ${cli.calls} via CLI`;
|
|
1633
1792
|
if (t.baselineTokens > 0) {
|
|
1634
1793
|
const savedTokens = Math.max(0, t.baselineTokens - t.vgTokens);
|
|
1635
1794
|
line += ` \xB7 ctx ${fmtTokens(t.vgTokens)} vs \u2248${fmtTokens(t.baselineTokens)} grep/read \xB7 est. saved \u2248 ${fmtTokens(savedTokens)} tokens`;
|
|
@@ -1639,13 +1798,15 @@ function serveHeartbeatLine(snap, now2) {
|
|
|
1639
1798
|
var TTY_INTERVAL_MS = 1e3;
|
|
1640
1799
|
var HEARTBEAT_INTERVAL_MS = 15 * 60 * 1e3;
|
|
1641
1800
|
var ServeStatusDisplay = class {
|
|
1642
|
-
constructor(stats, stream = process.stderr) {
|
|
1801
|
+
constructor(stats, stream = process.stderr, siblings) {
|
|
1643
1802
|
this.stats = stats;
|
|
1644
1803
|
this.stream = stream;
|
|
1804
|
+
this.siblings = siblings;
|
|
1645
1805
|
this.isTTY = (stream.isTTY ?? false) && process.env.VIBGRATE_PROGRESS_MODE !== "plain";
|
|
1646
1806
|
}
|
|
1647
1807
|
stats;
|
|
1648
1808
|
stream;
|
|
1809
|
+
siblings;
|
|
1649
1810
|
timer = null;
|
|
1650
1811
|
tick = 0;
|
|
1651
1812
|
lastRowCount = 0;
|
|
@@ -1688,7 +1849,9 @@ var ServeStatusDisplay = class {
|
|
|
1688
1849
|
}
|
|
1689
1850
|
renderTTY() {
|
|
1690
1851
|
this.tick++;
|
|
1691
|
-
const
|
|
1852
|
+
const sibs = this.siblings?.() ?? [];
|
|
1853
|
+
const snap = mergeSnapshots(this.stats.snapshot(), sibs);
|
|
1854
|
+
const lines = serveStatusLines(snap, Date.now(), this.tick, sibs.length);
|
|
1692
1855
|
const content = lines.join("\n") + "\n";
|
|
1693
1856
|
if (content === this.lastFrame) return;
|
|
1694
1857
|
this.lastFrame = content;
|
|
@@ -1723,6 +1886,89 @@ var ServeStatusDisplay = class {
|
|
|
1723
1886
|
this.cursorHidden = false;
|
|
1724
1887
|
};
|
|
1725
1888
|
};
|
|
1889
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
1890
|
+
var LedgerTail = class {
|
|
1891
|
+
constructor(file, stats, intervalMs = POLL_INTERVAL_MS) {
|
|
1892
|
+
this.file = file;
|
|
1893
|
+
this.stats = stats;
|
|
1894
|
+
this.intervalMs = intervalMs;
|
|
1895
|
+
}
|
|
1896
|
+
file;
|
|
1897
|
+
stats;
|
|
1898
|
+
intervalMs;
|
|
1899
|
+
timer = null;
|
|
1900
|
+
/** Byte offset of the next unread ledger byte (always at a line boundary). */
|
|
1901
|
+
offset = 0;
|
|
1902
|
+
start() {
|
|
1903
|
+
if (this.timer) return;
|
|
1904
|
+
this.offset = this.sizeOf();
|
|
1905
|
+
this.timer = setInterval(() => this.poll(), this.intervalMs);
|
|
1906
|
+
this.timer.unref?.();
|
|
1907
|
+
}
|
|
1908
|
+
stop() {
|
|
1909
|
+
if (this.timer) {
|
|
1910
|
+
clearInterval(this.timer);
|
|
1911
|
+
this.timer = null;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
/** One poll cycle — exposed for tests (deterministic, no timers needed). */
|
|
1915
|
+
poll(now2 = Date.now()) {
|
|
1916
|
+
let size;
|
|
1917
|
+
try {
|
|
1918
|
+
size = this.sizeOf();
|
|
1919
|
+
} catch {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
if (size < this.offset) this.offset = 0;
|
|
1923
|
+
if (size === this.offset) return;
|
|
1924
|
+
let chunk;
|
|
1925
|
+
try {
|
|
1926
|
+
const fd = fs10.openSync(this.file, "r");
|
|
1927
|
+
try {
|
|
1928
|
+
chunk = Buffer.alloc(size - this.offset);
|
|
1929
|
+
fs10.readSync(fd, chunk, 0, chunk.length, this.offset);
|
|
1930
|
+
} finally {
|
|
1931
|
+
fs10.closeSync(fd);
|
|
1932
|
+
}
|
|
1933
|
+
} catch {
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
const lastNewline = chunk.lastIndexOf(10);
|
|
1937
|
+
if (lastNewline < 0) return;
|
|
1938
|
+
const text = chunk.subarray(0, lastNewline).toString("utf8");
|
|
1939
|
+
this.offset += lastNewline + 1;
|
|
1940
|
+
for (const line of text.split("\n")) ingestLedgerLine(this.stats, line, now2);
|
|
1941
|
+
}
|
|
1942
|
+
sizeOf() {
|
|
1943
|
+
try {
|
|
1944
|
+
return fs10.statSync(this.file).size;
|
|
1945
|
+
} catch {
|
|
1946
|
+
return 0;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
function ingestLedgerLine(stats, line, now2) {
|
|
1951
|
+
if (!line.trim()) return;
|
|
1952
|
+
let e;
|
|
1953
|
+
try {
|
|
1954
|
+
e = JSON.parse(line);
|
|
1955
|
+
} catch {
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
if (e.source !== "cli" || typeof e.tool !== "string") return;
|
|
1959
|
+
stats.record(
|
|
1960
|
+
{
|
|
1961
|
+
tool: e.tool,
|
|
1962
|
+
client: sanitizeClient(e.client),
|
|
1963
|
+
outcome: e.outcome ?? "complete",
|
|
1964
|
+
source: "cli",
|
|
1965
|
+
// CLI lines carry no wall time — leave ms absent ("not measured").
|
|
1966
|
+
vgTokens: e.vgTokens ?? 0,
|
|
1967
|
+
baselineTokens: e.baselineTokens ?? 0
|
|
1968
|
+
},
|
|
1969
|
+
now2
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1726
1972
|
|
|
1727
1973
|
// src/commands/serve.ts
|
|
1728
1974
|
var SHARE_FLUSH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
@@ -1759,7 +2005,11 @@ function registerServe(program) {
|
|
|
1759
2005
|
info(c.dim(`vg \xB7 --share-stats disabled by ${optOut}: recording locally, not uploading.`));
|
|
1760
2006
|
}
|
|
1761
2007
|
if (shareStats) startSharing(root);
|
|
1762
|
-
const
|
|
2008
|
+
const interactive = process.stderr.isTTY === true;
|
|
2009
|
+
const bus = stats ? new LiveStatsBus(liveStatsDir(root), stats) : void 0;
|
|
2010
|
+
if (bus && !interactive) bus.start();
|
|
2011
|
+
const display = stats ? new ServeStatusDisplay(stats, process.stderr, bus && interactive ? () => bus.siblings() : void 0) : void 0;
|
|
2012
|
+
if (stats && interactive) new LedgerTail(savingsLedgerPath(root), stats).start();
|
|
1763
2013
|
const freshness = refresh ? "auto-refresh" : "as built";
|
|
1764
2014
|
if (opts.http) {
|
|
1765
2015
|
await serveHttp(graphPath, opts.host ?? "127.0.0.1", Number(opts.port) || 7437, serveOpts, freshness, () => display?.start());
|
|
@@ -1773,7 +2023,7 @@ function registerServe(program) {
|
|
|
1773
2023
|
}
|
|
1774
2024
|
async function ensureServableGraph(root, graphPath, global, refresh, opts = {}) {
|
|
1775
2025
|
if (refresh) {
|
|
1776
|
-
if (!
|
|
2026
|
+
if (!fs10.existsSync(graphPath)) {
|
|
1777
2027
|
info(c.dim("vg \xB7 no map found \u2014 building it before serving\u2026"));
|
|
1778
2028
|
await runBuild(
|
|
1779
2029
|
[],
|
|
@@ -1790,7 +2040,7 @@ async function ensureServableGraph(root, graphPath, global, refresh, opts = {})
|
|
|
1790
2040
|
}
|
|
1791
2041
|
}
|
|
1792
2042
|
}
|
|
1793
|
-
if (!
|
|
2043
|
+
if (!fs10.existsSync(graphPath)) {
|
|
1794
2044
|
throw new CliError(
|
|
1795
2045
|
`no map found at ${graphPath} \u2014 run \`vg\` to build one first`,
|
|
1796
2046
|
ExitCode.NOT_FOUND
|
|
@@ -2073,7 +2323,7 @@ function manifestHash(root) {
|
|
|
2073
2323
|
const walk2 = (dir) => {
|
|
2074
2324
|
let entries;
|
|
2075
2325
|
try {
|
|
2076
|
-
entries =
|
|
2326
|
+
entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
2077
2327
|
} catch {
|
|
2078
2328
|
return;
|
|
2079
2329
|
}
|
|
@@ -2089,7 +2339,7 @@ function manifestHash(root) {
|
|
|
2089
2339
|
if (!isManifest(abs) && !SKIP_FILES.has(base)) continue;
|
|
2090
2340
|
try {
|
|
2091
2341
|
const rel3 = path.relative(root, abs).split(path.sep).join("/");
|
|
2092
|
-
files.push({ rel: rel3, hash: hashBytes(
|
|
2342
|
+
files.push({ rel: rel3, hash: hashBytes(fs10.readFileSync(abs)) });
|
|
2093
2343
|
} catch {
|
|
2094
2344
|
}
|
|
2095
2345
|
}
|
|
@@ -2100,7 +2350,7 @@ function manifestHash(root) {
|
|
|
2100
2350
|
}
|
|
2101
2351
|
function loadScanCache(root, key2) {
|
|
2102
2352
|
try {
|
|
2103
|
-
const raw = JSON.parse(
|
|
2353
|
+
const raw = JSON.parse(fs10.readFileSync(cachePath(root), "utf8"));
|
|
2104
2354
|
if (raw.version === CACHE_VERSION && raw.manifestHash === key2.manifestHash && raw.toolVersion === key2.toolVersion && raw.offline === key2.offline && raw.artifact) {
|
|
2105
2355
|
return { manifestHash: raw.manifestHash, artifact: raw.artifact };
|
|
2106
2356
|
}
|
|
@@ -2110,8 +2360,8 @@ function loadScanCache(root, key2) {
|
|
|
2110
2360
|
}
|
|
2111
2361
|
function writeScanCache(root, key2, artifact) {
|
|
2112
2362
|
try {
|
|
2113
|
-
|
|
2114
|
-
|
|
2363
|
+
fs10.mkdirSync(cacheDir(root), { recursive: true });
|
|
2364
|
+
fs10.writeFileSync(
|
|
2115
2365
|
cachePath(root),
|
|
2116
2366
|
stableStringify(
|
|
2117
2367
|
{ version: CACHE_VERSION, manifestHash: key2.manifestHash, toolVersion: key2.toolVersion, offline: key2.offline, artifact },
|
|
@@ -2935,7 +3185,7 @@ function hoverMarkdown(dep, contribution) {
|
|
|
2935
3185
|
return lines.join("\n");
|
|
2936
3186
|
}
|
|
2937
3187
|
function startLanguageServer(opts) {
|
|
2938
|
-
if (!
|
|
3188
|
+
if (!fs10.existsSync(opts.root)) {
|
|
2939
3189
|
process.stderr.write(`vg lsp: root does not exist: ${opts.root}
|
|
2940
3190
|
`);
|
|
2941
3191
|
process.exit(1);
|
|
@@ -2979,7 +3229,6 @@ function registerLsp(program) {
|
|
|
2979
3229
|
}
|
|
2980
3230
|
|
|
2981
3231
|
// src/commands/install.ts
|
|
2982
|
-
var SMALL_REPO_FILES = 150;
|
|
2983
3232
|
function registerInstall(program) {
|
|
2984
3233
|
const install = program.command("install").description("add vg to your AI assistant(s): skill + MCP + advisory nudge").argument("[tools...]", `assistant ids: ${ASSISTANTS.map((a) => a.id).join(", ")}`).option("--all", "install for every supported assistant").option("--detect", "detect assistants in use (repo footprint, home config, PATH) and install for those; with --list, only report what was detected").option("--list", "show the support matrix and exit").option("--no-hook", "skip the advisory nudge").action(function(tools, opts) {
|
|
2985
3234
|
const global = readGlobal(this);
|
|
@@ -3074,7 +3323,7 @@ function registerShare(program) {
|
|
|
3074
3323
|
const global = readGlobal(this);
|
|
3075
3324
|
const root = rootOf(global);
|
|
3076
3325
|
const gitDir = path.join(root, ".git");
|
|
3077
|
-
if (!
|
|
3326
|
+
if (!fs10.existsSync(gitDir)) {
|
|
3078
3327
|
throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
|
|
3079
3328
|
}
|
|
3080
3329
|
if (opts.undo) {
|
|
@@ -3096,22 +3345,22 @@ function registerShare(program) {
|
|
|
3096
3345
|
}
|
|
3097
3346
|
function writeVibgrateGitignore(root, reports) {
|
|
3098
3347
|
const dir = path.join(root, ".vibgrate");
|
|
3099
|
-
|
|
3348
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
3100
3349
|
const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl"];
|
|
3101
3350
|
if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
|
|
3102
|
-
|
|
3351
|
+
fs10.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
|
|
3103
3352
|
`);
|
|
3104
3353
|
}
|
|
3105
3354
|
function writeGitAttributes(root) {
|
|
3106
3355
|
const file = path.join(root, ".gitattributes");
|
|
3107
3356
|
const line = ".vibgrate/graph.json merge=vg";
|
|
3108
|
-
let content =
|
|
3357
|
+
let content = fs10.existsSync(file) ? fs10.readFileSync(file, "utf8") : "";
|
|
3109
3358
|
if (!content.split("\n").some((l) => l.trim() === line)) {
|
|
3110
3359
|
content = content.length ? `${content.replace(/\s*$/, "")}
|
|
3111
3360
|
${line}
|
|
3112
3361
|
` : `${line}
|
|
3113
3362
|
`;
|
|
3114
|
-
|
|
3363
|
+
fs10.writeFileSync(file, content);
|
|
3115
3364
|
}
|
|
3116
3365
|
}
|
|
3117
3366
|
function configureMergeDriver(root) {
|
|
@@ -3120,7 +3369,7 @@ function configureMergeDriver(root) {
|
|
|
3120
3369
|
}
|
|
3121
3370
|
function installPreCommitHook(root) {
|
|
3122
3371
|
const hooksDir = hookDir(root);
|
|
3123
|
-
|
|
3372
|
+
fs10.mkdirSync(hooksDir, { recursive: true });
|
|
3124
3373
|
const file = path.join(hooksDir, "pre-commit");
|
|
3125
3374
|
const block = [
|
|
3126
3375
|
HOOK_BEGIN,
|
|
@@ -3130,7 +3379,7 @@ function installPreCommitHook(root) {
|
|
|
3130
3379
|
"fi",
|
|
3131
3380
|
HOOK_END
|
|
3132
3381
|
].join("\n");
|
|
3133
|
-
let content =
|
|
3382
|
+
let content = fs10.existsSync(file) ? fs10.readFileSync(file, "utf8") : "#!/bin/sh\n";
|
|
3134
3383
|
const re = new RegExp(`${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}`);
|
|
3135
3384
|
if (re.test(content)) content = content.replace(re, block);
|
|
3136
3385
|
else content = `${content.replace(/\s*$/, "")}
|
|
@@ -3139,21 +3388,21 @@ ${block}
|
|
|
3139
3388
|
`;
|
|
3140
3389
|
if (!content.startsWith("#!")) content = `#!/bin/sh
|
|
3141
3390
|
${content}`;
|
|
3142
|
-
|
|
3143
|
-
|
|
3391
|
+
fs10.writeFileSync(file, content);
|
|
3392
|
+
fs10.chmodSync(file, 493);
|
|
3144
3393
|
}
|
|
3145
3394
|
function undo(root) {
|
|
3146
3395
|
const hookFile = path.join(hookDir(root), "pre-commit");
|
|
3147
|
-
if (
|
|
3396
|
+
if (fs10.existsSync(hookFile)) {
|
|
3148
3397
|
const re = new RegExp(`\\n*${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}\\n*`);
|
|
3149
|
-
const next2 =
|
|
3150
|
-
if (next2.trim() === "#!/bin/sh")
|
|
3151
|
-
else
|
|
3398
|
+
const next2 = fs10.readFileSync(hookFile, "utf8").replace(re, "\n");
|
|
3399
|
+
if (next2.trim() === "#!/bin/sh") fs10.rmSync(hookFile);
|
|
3400
|
+
else fs10.writeFileSync(hookFile, next2);
|
|
3152
3401
|
}
|
|
3153
3402
|
const attr = path.join(root, ".gitattributes");
|
|
3154
|
-
if (
|
|
3155
|
-
const next2 =
|
|
3156
|
-
|
|
3403
|
+
if (fs10.existsSync(attr)) {
|
|
3404
|
+
const next2 = fs10.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
|
|
3405
|
+
fs10.writeFileSync(attr, next2);
|
|
3157
3406
|
}
|
|
3158
3407
|
try {
|
|
3159
3408
|
git(root, ["config", "--remove-section", "merge.vg"]);
|
|
@@ -3286,7 +3535,7 @@ function grepBaselineTokens(term, files) {
|
|
|
3286
3535
|
if (f.size > 512 * 1024) continue;
|
|
3287
3536
|
let content = "";
|
|
3288
3537
|
try {
|
|
3289
|
-
content =
|
|
3538
|
+
content = fs10.readFileSync(f.abs, "utf8");
|
|
3290
3539
|
} catch {
|
|
3291
3540
|
continue;
|
|
3292
3541
|
}
|
|
@@ -3296,7 +3545,7 @@ function grepBaselineTokens(term, files) {
|
|
|
3296
3545
|
}
|
|
3297
3546
|
function safeSize(abs) {
|
|
3298
3547
|
try {
|
|
3299
|
-
return
|
|
3548
|
+
return fs10.statSync(abs).size;
|
|
3300
3549
|
} catch {
|
|
3301
3550
|
return 0;
|
|
3302
3551
|
}
|
|
@@ -3630,7 +3879,7 @@ function loadStandards(root) {
|
|
|
3630
3879
|
const p = path__default.join(root, rel3);
|
|
3631
3880
|
let text;
|
|
3632
3881
|
try {
|
|
3633
|
-
text =
|
|
3882
|
+
text = fs10__default.readFileSync(p, "utf8");
|
|
3634
3883
|
} catch {
|
|
3635
3884
|
continue;
|
|
3636
3885
|
}
|
|
@@ -3869,7 +4118,7 @@ async function addCmd(root, rest, opts, asJson) {
|
|
|
3869
4118
|
}
|
|
3870
4119
|
function readPackageVersion(root) {
|
|
3871
4120
|
try {
|
|
3872
|
-
const pkg = JSON.parse(
|
|
4121
|
+
const pkg = JSON.parse(fs10__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
|
|
3873
4122
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
3874
4123
|
} catch {
|
|
3875
4124
|
return void 0;
|
|
@@ -3877,7 +4126,7 @@ function readPackageVersion(root) {
|
|
|
3877
4126
|
}
|
|
3878
4127
|
function readOptionalFile(p) {
|
|
3879
4128
|
try {
|
|
3880
|
-
const t =
|
|
4129
|
+
const t = fs10__default.readFileSync(p, "utf8");
|
|
3881
4130
|
return t.trim() ? t : void 0;
|
|
3882
4131
|
} catch {
|
|
3883
4132
|
return void 0;
|
|
@@ -4023,8 +4272,8 @@ function registerExport(program) {
|
|
|
4023
4272
|
out(content.trimEnd());
|
|
4024
4273
|
return;
|
|
4025
4274
|
}
|
|
4026
|
-
|
|
4027
|
-
|
|
4275
|
+
fs10.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
|
|
4276
|
+
fs10.writeFileSync(file, content);
|
|
4028
4277
|
info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
|
|
4029
4278
|
});
|
|
4030
4279
|
applyGlobalOptions(cmd);
|
|
@@ -4047,24 +4296,24 @@ function registerBundle(program) {
|
|
|
4047
4296
|
} catch {
|
|
4048
4297
|
throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
|
|
4049
4298
|
}
|
|
4050
|
-
|
|
4299
|
+
fs10.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
|
|
4051
4300
|
let grammarCount = 0;
|
|
4052
4301
|
for (const { fileName, absPath } of grammarFiles) {
|
|
4053
|
-
|
|
4302
|
+
fs10.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
|
|
4054
4303
|
grammarCount++;
|
|
4055
4304
|
}
|
|
4056
4305
|
const included = [`grammars/ (${grammarCount} .wasm)`];
|
|
4057
4306
|
const graphSrc = global.graph ?? defaultGraphPath(root);
|
|
4058
|
-
if (
|
|
4059
|
-
|
|
4307
|
+
if (fs10.existsSync(graphSrc)) {
|
|
4308
|
+
fs10.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
|
|
4060
4309
|
included.push("graph.json");
|
|
4061
4310
|
}
|
|
4062
4311
|
const cat = catalogPath(root);
|
|
4063
|
-
if (
|
|
4064
|
-
|
|
4312
|
+
if (fs10.existsSync(cat)) {
|
|
4313
|
+
fs10.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
|
|
4065
4314
|
included.push("vibgrate.lib.json");
|
|
4066
|
-
if (
|
|
4067
|
-
|
|
4315
|
+
if (fs10.existsSync(libDir(root))) {
|
|
4316
|
+
fs10.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
|
|
4068
4317
|
included.push("lib/");
|
|
4069
4318
|
}
|
|
4070
4319
|
}
|
|
@@ -4076,7 +4325,7 @@ function registerBundle(program) {
|
|
|
4076
4325
|
included,
|
|
4077
4326
|
usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
|
|
4078
4327
|
};
|
|
4079
|
-
|
|
4328
|
+
fs10.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
|
|
4080
4329
|
`);
|
|
4081
4330
|
if (global.json) {
|
|
4082
4331
|
json({ out: path.relative(root, outDir), ...manifest });
|
|
@@ -4100,7 +4349,7 @@ async function runDoctor(global) {
|
|
|
4100
4349
|
const root = rootOf(global);
|
|
4101
4350
|
const local = global.local === true;
|
|
4102
4351
|
const graphPath = global.graph ?? defaultGraphPath(root);
|
|
4103
|
-
const configFile = CONFIG_BASENAMES.find((f) =>
|
|
4352
|
+
const configFile = CONFIG_BASENAMES.find((f) => fs10.existsSync(path.join(root, f))) ?? null;
|
|
4104
4353
|
const graph = loadGraph(root, graphPath);
|
|
4105
4354
|
let staleFiles = null;
|
|
4106
4355
|
if (graph) {
|
|
@@ -4128,7 +4377,7 @@ async function runDoctor(global) {
|
|
|
4128
4377
|
},
|
|
4129
4378
|
libCatalog: {
|
|
4130
4379
|
path: path.relative(root, catalogPath(root)),
|
|
4131
|
-
present:
|
|
4380
|
+
present: fs10.existsSync(catalogPath(root)),
|
|
4132
4381
|
libraries: libCount
|
|
4133
4382
|
},
|
|
4134
4383
|
credentials: creds,
|
|
@@ -4220,7 +4469,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
4220
4469
|
console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold("vibgrate.config.ts")}`);
|
|
4221
4470
|
}
|
|
4222
4471
|
if (opts.baseline) {
|
|
4223
|
-
const { runBaseline } = await import('./baseline-
|
|
4472
|
+
const { runBaseline } = await import('./baseline-FALF7ULT.js');
|
|
4224
4473
|
await runBaseline(rootDir);
|
|
4225
4474
|
}
|
|
4226
4475
|
console.log("");
|
|
@@ -4330,9 +4579,9 @@ async function fileReferencesIdentifier(rootDir, relFile, identifier, readFile3)
|
|
|
4330
4579
|
}
|
|
4331
4580
|
async function defaultReadFile(absPath) {
|
|
4332
4581
|
try {
|
|
4333
|
-
const stat2 = await
|
|
4582
|
+
const stat2 = await fs17.stat(absPath);
|
|
4334
4583
|
if (!stat2.isFile() || stat2.size > MAX_FILE_BYTES) return null;
|
|
4335
|
-
return await
|
|
4584
|
+
return await fs17.readFile(absPath, "utf8");
|
|
4336
4585
|
} catch {
|
|
4337
4586
|
return null;
|
|
4338
4587
|
}
|
|
@@ -4977,7 +5226,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
4977
5226
|
function scanStaleness(rootDir, artifactPath) {
|
|
4978
5227
|
let ref;
|
|
4979
5228
|
try {
|
|
4980
|
-
ref =
|
|
5229
|
+
ref = fs10.statSync(artifactPath).mtimeMs;
|
|
4981
5230
|
} catch {
|
|
4982
5231
|
return { stale: false };
|
|
4983
5232
|
}
|
|
@@ -4989,7 +5238,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
4989
5238
|
if (depth > 8 || scanned > MAX_ENTRIES) return;
|
|
4990
5239
|
let entries;
|
|
4991
5240
|
try {
|
|
4992
|
-
entries =
|
|
5241
|
+
entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
4993
5242
|
} catch {
|
|
4994
5243
|
return;
|
|
4995
5244
|
}
|
|
@@ -5004,7 +5253,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
5004
5253
|
const abs = path.join(dir, e.name);
|
|
5005
5254
|
let mtimeMs;
|
|
5006
5255
|
try {
|
|
5007
|
-
mtimeMs =
|
|
5256
|
+
mtimeMs = fs10.statSync(abs).mtimeMs;
|
|
5008
5257
|
} catch {
|
|
5009
5258
|
continue;
|
|
5010
5259
|
}
|
|
@@ -5138,7 +5387,7 @@ function analyzeTree(root, packages) {
|
|
|
5138
5387
|
if (depth > MAX_DEPTH || filesSeen >= MAX_FILES) return;
|
|
5139
5388
|
let entries;
|
|
5140
5389
|
try {
|
|
5141
|
-
entries =
|
|
5390
|
+
entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
5142
5391
|
} catch {
|
|
5143
5392
|
return;
|
|
5144
5393
|
}
|
|
@@ -5156,8 +5405,8 @@ function analyzeTree(root, packages) {
|
|
|
5156
5405
|
const full = path.join(dir, entry.name);
|
|
5157
5406
|
let content;
|
|
5158
5407
|
try {
|
|
5159
|
-
if (
|
|
5160
|
-
content =
|
|
5408
|
+
if (fs10.statSync(full).size > MAX_FILE_BYTES2) continue;
|
|
5409
|
+
content = fs10.readFileSync(full, "utf8");
|
|
5161
5410
|
} catch {
|
|
5162
5411
|
continue;
|
|
5163
5412
|
}
|
|
@@ -5525,8 +5774,8 @@ async function fetchLatestVersion() {
|
|
|
5525
5774
|
}
|
|
5526
5775
|
async function writeCache(data) {
|
|
5527
5776
|
try {
|
|
5528
|
-
await
|
|
5529
|
-
await
|
|
5777
|
+
await fs17.mkdir(CACHE_DIR, { recursive: true });
|
|
5778
|
+
await fs17.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
5530
5779
|
} catch {
|
|
5531
5780
|
}
|
|
5532
5781
|
}
|
|
@@ -5537,7 +5786,7 @@ function detectGlobalInstall(execPath = process.argv[1] || "", cwd = process.cwd
|
|
|
5537
5786
|
if (!execPath) return null;
|
|
5538
5787
|
let resolved = execPath;
|
|
5539
5788
|
try {
|
|
5540
|
-
resolved =
|
|
5789
|
+
resolved = fs10.realpathSync(execPath);
|
|
5541
5790
|
} catch {
|
|
5542
5791
|
}
|
|
5543
5792
|
const p = resolved.replace(/\\/g, "/");
|
|
@@ -7477,7 +7726,7 @@ function dispatch(argv, cwd) {
|
|
|
7477
7726
|
}
|
|
7478
7727
|
const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
|
|
7479
7728
|
if (looksLikeQuestion) return ["ask", ...args];
|
|
7480
|
-
if (
|
|
7729
|
+
if (fs10.existsSync(path.resolve(cwd, first))) {
|
|
7481
7730
|
return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
|
|
7482
7731
|
}
|
|
7483
7732
|
const moved = MOVED_COMMANDS[first];
|