@vibgrate/cli 2026.718.2 → 2026.720.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/DOCS.md +2 -0
- package/dist/baseline-WIZAFIJS.js +6 -0
- package/dist/{baseline-DKPQWOQ6.js.map → baseline-WIZAFIJS.js.map} +1 -1
- package/dist/{chunk-H5M7CXE6.js → chunk-7R4O5CZD.js} +71 -25
- package/dist/chunk-7R4O5CZD.js.map +1 -0
- package/dist/{chunk-VFO5UDAT.js → chunk-BHA7QIPD.js} +185 -16
- package/dist/chunk-BHA7QIPD.js.map +1 -0
- package/dist/{chunk-BFXDF6ZE.js → chunk-DDQ5JGBV.js} +3 -3
- package/dist/{chunk-BFXDF6ZE.js.map → chunk-DDQ5JGBV.js.map} +1 -1
- package/dist/{chunk-RME7BFMU.js → chunk-OD5654VF.js} +3 -3
- package/dist/{chunk-RME7BFMU.js.map → chunk-OD5654VF.js.map} +1 -1
- package/dist/cli.js +435 -111
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +132 -46
- 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 +9 -7
- package/dist/baseline-DKPQWOQ6.js +0 -6
- package/dist/chunk-H5M7CXE6.js.map +0 -1
- package/dist/chunk-VFO5UDAT.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, readStoredCredentials, projectCredentialsPath, homeCredentialsPath, SKIP_DIRS, SKIP_FILES } from './chunk-
|
|
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, 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-7R4O5CZD.js';
|
|
3
|
+
import { resolvedGrammarFiles, grammarSetVersion, hashString, canonicalize, hashBytes } from './chunk-BHA7QIPD.js';
|
|
4
|
+
import { loadAdvancedScanHook, baselineCommand } from './chunk-DDQ5JGBV.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-OD5654VF.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 fs9 from 'fs';
|
|
9
|
+
import fs9__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 fs16 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 (!fs9.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
|
+
fs9.mkdirSync(path.dirname(keyPath), { recursive: true });
|
|
187
|
+
fs9.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
|
|
188
|
+
fs9.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(fs9.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
|
+
fs9.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
211
|
+
fs9.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 (!fs9.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(fs9.readFileSync(bundlePath, "utf8"));
|
|
236
|
+
const publicKeyPem = opts.pub ? fs9.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
|
}
|
|
@@ -288,21 +288,23 @@ var ROBOT = [
|
|
|
288
288
|
" " + c.dim("\u2524") + teal("\u2502") + " " + c.dim("\u2581\u2581") + " " + teal("\u2502") + c.dim("\u251C"),
|
|
289
289
|
" " + teal("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F")
|
|
290
290
|
];
|
|
291
|
-
function logoLines(root) {
|
|
291
|
+
function logoLines(root, opts = {}) {
|
|
292
|
+
const product = opts.product ?? "Graph";
|
|
293
|
+
const tagline = opts.tagline ?? "Code Intelligence Engine";
|
|
292
294
|
return [
|
|
293
295
|
"",
|
|
294
296
|
` ${ROBOT[0]}`,
|
|
295
297
|
` ${ROBOT[1]}`,
|
|
296
298
|
` ${ROBOT[2]}`,
|
|
297
299
|
` ${ROBOT[3]}`,
|
|
298
|
-
` ${c.bold.white("Vibgrate")} ${teal(
|
|
300
|
+
` ${c.bold.white("Vibgrate")} ${teal(product)} ${c.dim(`\xB7 ${tagline} \xB7 v${VERSION}`)}`,
|
|
299
301
|
...root ? [` ${c.dim(root)}`] : [],
|
|
300
302
|
""
|
|
301
303
|
];
|
|
302
304
|
}
|
|
303
|
-
function printLogo(root) {
|
|
305
|
+
function printLogo(root, opts = {}) {
|
|
304
306
|
if (!process.stderr.isTTY) return;
|
|
305
|
-
process.stderr.write(`${logoLines(root).join("\n")}
|
|
307
|
+
process.stderr.write(`${logoLines(root, opts).join("\n")}
|
|
306
308
|
`);
|
|
307
309
|
}
|
|
308
310
|
|
|
@@ -584,8 +586,8 @@ function writeExport(graph, target) {
|
|
|
584
586
|
ExitCode.USAGE_ERROR
|
|
585
587
|
);
|
|
586
588
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
+
fs9.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
|
|
590
|
+
fs9.writeFileSync(target, content);
|
|
589
591
|
}
|
|
590
592
|
function collect(value, prev) {
|
|
591
593
|
return [...prev, value];
|
|
@@ -604,7 +606,7 @@ async function runStatus(global) {
|
|
|
604
606
|
const root = path.resolve(global.cwd ?? ".");
|
|
605
607
|
const graphPath = global.graph ?? defaultGraphPath(root);
|
|
606
608
|
const graph = loadGraph(root, graphPath);
|
|
607
|
-
const hasCache =
|
|
609
|
+
const hasCache = fs9.existsSync(path.join(cacheDir(root), "parse-cache.json"));
|
|
608
610
|
let stale = null;
|
|
609
611
|
let staleExact = false;
|
|
610
612
|
if (graph) {
|
|
@@ -1313,14 +1315,14 @@ function isCI(env = process.env) {
|
|
|
1313
1315
|
function installId(env = process.env) {
|
|
1314
1316
|
if (telemetryOptOut(env) !== null || isCI(env)) return randomUuid();
|
|
1315
1317
|
try {
|
|
1316
|
-
const existing =
|
|
1318
|
+
const existing = fs9.readFileSync(INSTALL_ID_FILE, "utf8").trim();
|
|
1317
1319
|
if (isUuid(existing)) return existing;
|
|
1318
1320
|
} catch {
|
|
1319
1321
|
}
|
|
1320
1322
|
const id = randomUuid();
|
|
1321
1323
|
try {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
+
fs9.mkdirSync(INSTALL_ID_DIR, { recursive: true });
|
|
1325
|
+
fs9.writeFileSync(INSTALL_ID_FILE, id + "\n", { mode: 384 });
|
|
1324
1326
|
} catch {
|
|
1325
1327
|
}
|
|
1326
1328
|
return id;
|
|
@@ -1344,7 +1346,7 @@ function ledgerFilePath(root) {
|
|
|
1344
1346
|
}
|
|
1345
1347
|
function readOffset(root) {
|
|
1346
1348
|
try {
|
|
1347
|
-
const raw = JSON.parse(
|
|
1349
|
+
const raw = JSON.parse(fs9.readFileSync(offsetPath(root), "utf8"));
|
|
1348
1350
|
return typeof raw.offset === "number" && raw.offset >= 0 ? raw.offset : 0;
|
|
1349
1351
|
} catch {
|
|
1350
1352
|
return 0;
|
|
@@ -1352,8 +1354,8 @@ function readOffset(root) {
|
|
|
1352
1354
|
}
|
|
1353
1355
|
function writeOffset(root, offset) {
|
|
1354
1356
|
try {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
+
fs9.mkdirSync(cacheDir(root), { recursive: true });
|
|
1358
|
+
fs9.writeFileSync(offsetPath(root), JSON.stringify({ offset }) + "\n");
|
|
1357
1359
|
} catch {
|
|
1358
1360
|
}
|
|
1359
1361
|
}
|
|
@@ -1361,7 +1363,7 @@ function buildBatch(root, fromOffset, id = installId()) {
|
|
|
1361
1363
|
const file = ledgerFilePath(root);
|
|
1362
1364
|
let size = 0;
|
|
1363
1365
|
try {
|
|
1364
|
-
size =
|
|
1366
|
+
size = fs9.statSync(file).size;
|
|
1365
1367
|
} catch {
|
|
1366
1368
|
return { batch: null, newOffset: 0 };
|
|
1367
1369
|
}
|
|
@@ -1369,13 +1371,13 @@ function buildBatch(root, fromOffset, id = installId()) {
|
|
|
1369
1371
|
if (start >= size) return { batch: null, newOffset: size };
|
|
1370
1372
|
let text = "";
|
|
1371
1373
|
try {
|
|
1372
|
-
const fd =
|
|
1374
|
+
const fd = fs9.openSync(file, "r");
|
|
1373
1375
|
try {
|
|
1374
1376
|
const buf = Buffer.alloc(size - start);
|
|
1375
|
-
|
|
1377
|
+
fs9.readSync(fd, buf, 0, buf.length, start);
|
|
1376
1378
|
text = buf.toString("utf8");
|
|
1377
1379
|
} finally {
|
|
1378
|
-
|
|
1380
|
+
fs9.closeSync(fd);
|
|
1379
1381
|
}
|
|
1380
1382
|
} catch {
|
|
1381
1383
|
return { batch: null, newOffset: fromOffset };
|
|
@@ -1508,6 +1510,317 @@ function isLoopbackOrigin(origin) {
|
|
|
1508
1510
|
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
1509
1511
|
}
|
|
1510
1512
|
|
|
1513
|
+
// src/mcp/serve-stats.ts
|
|
1514
|
+
function zeroRow(key2) {
|
|
1515
|
+
return { key: key2, calls: 0, complete: 0, partial: 0, miss: 0, timed: 0, totalMs: 0, vgTokens: 0, baselineTokens: 0 };
|
|
1516
|
+
}
|
|
1517
|
+
var SessionStats = class {
|
|
1518
|
+
startedAt;
|
|
1519
|
+
revision = 0;
|
|
1520
|
+
lastCallAt = null;
|
|
1521
|
+
totals = zeroRow("total");
|
|
1522
|
+
byClient = /* @__PURE__ */ new Map();
|
|
1523
|
+
byTool = /* @__PURE__ */ new Map();
|
|
1524
|
+
bySource = /* @__PURE__ */ new Map();
|
|
1525
|
+
constructor(now2 = Date.now()) {
|
|
1526
|
+
this.startedAt = now2;
|
|
1527
|
+
}
|
|
1528
|
+
record(sample, now2 = Date.now()) {
|
|
1529
|
+
this.revision++;
|
|
1530
|
+
this.lastCallAt = now2;
|
|
1531
|
+
const rows = [
|
|
1532
|
+
this.totals,
|
|
1533
|
+
this.rowFor(this.byClient, sample.client),
|
|
1534
|
+
this.rowFor(this.byTool, sample.tool),
|
|
1535
|
+
this.rowFor(this.bySource, sample.source ?? "mcp")
|
|
1536
|
+
];
|
|
1537
|
+
for (const row of rows) {
|
|
1538
|
+
row.calls++;
|
|
1539
|
+
row[sample.outcome]++;
|
|
1540
|
+
if (typeof sample.ms === "number") {
|
|
1541
|
+
row.timed++;
|
|
1542
|
+
row.totalMs += sample.ms;
|
|
1543
|
+
}
|
|
1544
|
+
row.vgTokens += sample.vgTokens;
|
|
1545
|
+
row.baselineTokens += sample.baselineTokens;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
snapshot() {
|
|
1549
|
+
const sorted = (m) => [...m.values()].map((r) => ({ ...r })).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
|
|
1550
|
+
return {
|
|
1551
|
+
startedAt: this.startedAt,
|
|
1552
|
+
revision: this.revision,
|
|
1553
|
+
lastCallAt: this.lastCallAt,
|
|
1554
|
+
totals: { ...this.totals },
|
|
1555
|
+
clients: sorted(this.byClient),
|
|
1556
|
+
tools: sorted(this.byTool),
|
|
1557
|
+
sources: sorted(this.bySource)
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
rowFor(m, key2) {
|
|
1561
|
+
let row = m.get(key2);
|
|
1562
|
+
if (!row) {
|
|
1563
|
+
row = zeroRow(key2);
|
|
1564
|
+
m.set(key2, row);
|
|
1565
|
+
}
|
|
1566
|
+
return row;
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
function fmtUptime(ms) {
|
|
1570
|
+
const s = Math.max(0, Math.floor(ms / 1e3));
|
|
1571
|
+
if (s < 60) return `${s}s`;
|
|
1572
|
+
const m = Math.floor(s / 60);
|
|
1573
|
+
if (m < 60) return `${m}m ${String(s % 60).padStart(2, "0")}s`;
|
|
1574
|
+
const h = Math.floor(m / 60);
|
|
1575
|
+
if (h < 24) return `${h}h ${String(m % 60).padStart(2, "0")}m`;
|
|
1576
|
+
const d = Math.floor(h / 24);
|
|
1577
|
+
return `${d}d ${String(h % 24).padStart(2, "0")}h`;
|
|
1578
|
+
}
|
|
1579
|
+
function fmtTokens(n) {
|
|
1580
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(2)}M`;
|
|
1581
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
1582
|
+
return String(n);
|
|
1583
|
+
}
|
|
1584
|
+
function fmtAvgMs(row) {
|
|
1585
|
+
if (row.timed === 0) return "\u2014";
|
|
1586
|
+
const avg = row.totalMs / row.timed;
|
|
1587
|
+
return avg >= 1e3 ? `${(avg / 1e3).toFixed(1)}s` : `${Math.round(avg)}ms`;
|
|
1588
|
+
}
|
|
1589
|
+
var teal2 = (s) => c.hex("#3FB0A4")(s);
|
|
1590
|
+
var mint2 = (s) => c.hex("#4FE3C1")(s);
|
|
1591
|
+
var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1592
|
+
var MAX_TOOL_ROWS = 8;
|
|
1593
|
+
function serveStatusLines(snap, now2, tick = 0) {
|
|
1594
|
+
const spinner = mint2(SPINNER[tick % SPINNER.length]);
|
|
1595
|
+
const up = fmtUptime(now2 - snap.startedAt);
|
|
1596
|
+
const lines = [];
|
|
1597
|
+
if (snap.totals.calls === 0) {
|
|
1598
|
+
lines.push(
|
|
1599
|
+
` ${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")}`
|
|
1600
|
+
);
|
|
1601
|
+
return lines;
|
|
1602
|
+
}
|
|
1603
|
+
const t = snap.totals;
|
|
1604
|
+
const answered = Math.round((t.complete + t.partial) / t.calls * 100);
|
|
1605
|
+
const cli = snap.sources.find((s) => s.key === "cli");
|
|
1606
|
+
const via = cli ? c.dim(" \xB7 via ") + snap.sources.map((s) => `${c.white(s.key)} ${teal2(String(s.calls))}`).join(c.dim(" \xB7 ")) : "";
|
|
1607
|
+
const ago = snap.lastCallAt === null ? "" : c.dim(` \xB7 last call ${fmtUptime(now2 - snap.lastCallAt)} ago`);
|
|
1608
|
+
lines.push(
|
|
1609
|
+
` ${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}`
|
|
1610
|
+
);
|
|
1611
|
+
lines.push(
|
|
1612
|
+
` ${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
|
+
);
|
|
1614
|
+
const shown = snap.tools.slice(0, MAX_TOOL_ROWS);
|
|
1615
|
+
const nameW = Math.max(...shown.map((r) => r.key.length));
|
|
1616
|
+
for (const row of shown) {
|
|
1617
|
+
let line = ` ${c.white(row.key.padEnd(nameW))} ${teal2(String(row.calls).padStart(4))} ${c.dim("\xB7")} ` + c.dim(`avg ${fmtAvgMs(row)}`);
|
|
1618
|
+
if (SAVINGS_TOOLS.has(row.key) && row.baselineTokens > 0) {
|
|
1619
|
+
line += c.dim(" \xB7 ctx ") + c.white(fmtTokens(row.vgTokens)) + c.dim(` vs \u2248${fmtTokens(row.baselineTokens)} grep/read`);
|
|
1620
|
+
}
|
|
1621
|
+
if (row.miss > 0) line += c.dim(" \xB7 ") + c.yellow(`${row.miss} miss`);
|
|
1622
|
+
lines.push(line);
|
|
1623
|
+
}
|
|
1624
|
+
if (snap.tools.length > shown.length) {
|
|
1625
|
+
lines.push(` ${c.dim(`\u2026 +${snap.tools.length - shown.length} more tools`)}`);
|
|
1626
|
+
}
|
|
1627
|
+
if (t.baselineTokens > 0) {
|
|
1628
|
+
const savedTokens = Math.max(0, t.baselineTokens - t.vgTokens);
|
|
1629
|
+
const ratio = t.vgTokens > 0 ? (t.baselineTokens / t.vgTokens).toFixed(1) : "\u2014";
|
|
1630
|
+
lines.push(
|
|
1631
|
+
` ${c.dim("session")} ctx ${c.bold.white(fmtTokens(t.vgTokens))} ` + c.dim(`vs grep/read \u2248${fmtTokens(t.baselineTokens)} \u2192 `) + mint2(`${ratio}\xD7 fewer`)
|
|
1632
|
+
);
|
|
1633
|
+
lines.push(
|
|
1634
|
+
` ${c.dim("est. saved \u2248")} ${mint2(`${fmtTokens(savedTokens)} context tokens`)} ${c.dim("(estimate)")}`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
return lines;
|
|
1638
|
+
}
|
|
1639
|
+
function serveHeartbeatLine(snap, now2) {
|
|
1640
|
+
const up = fmtUptime(now2 - snap.startedAt);
|
|
1641
|
+
const t = snap.totals;
|
|
1642
|
+
if (t.calls === 0) return c.dim(`vg \xB7 serving for ${up} \u2014 no tool calls yet`);
|
|
1643
|
+
const clients = snap.clients.map((cl) => `${cl.key} ${cl.calls}`).join(", ");
|
|
1644
|
+
const cli = snap.sources.find((s) => s.key === "cli");
|
|
1645
|
+
let line = `vg \xB7 serving for ${up} \u2014 ${t.calls} call${t.calls === 1 ? "" : "s"} (${clients})`;
|
|
1646
|
+
if (cli) line += ` \xB7 ${cli.calls} via CLI`;
|
|
1647
|
+
if (t.baselineTokens > 0) {
|
|
1648
|
+
const savedTokens = Math.max(0, t.baselineTokens - t.vgTokens);
|
|
1649
|
+
line += ` \xB7 ctx ${fmtTokens(t.vgTokens)} vs \u2248${fmtTokens(t.baselineTokens)} grep/read \xB7 est. saved \u2248 ${fmtTokens(savedTokens)} tokens`;
|
|
1650
|
+
}
|
|
1651
|
+
return c.dim(line);
|
|
1652
|
+
}
|
|
1653
|
+
var TTY_INTERVAL_MS = 1e3;
|
|
1654
|
+
var HEARTBEAT_INTERVAL_MS = 15 * 60 * 1e3;
|
|
1655
|
+
var ServeStatusDisplay = class {
|
|
1656
|
+
constructor(stats, stream = process.stderr) {
|
|
1657
|
+
this.stats = stats;
|
|
1658
|
+
this.stream = stream;
|
|
1659
|
+
this.isTTY = (stream.isTTY ?? false) && process.env.VIBGRATE_PROGRESS_MODE !== "plain";
|
|
1660
|
+
}
|
|
1661
|
+
stats;
|
|
1662
|
+
stream;
|
|
1663
|
+
timer = null;
|
|
1664
|
+
tick = 0;
|
|
1665
|
+
lastRowCount = 0;
|
|
1666
|
+
lastFrame = "";
|
|
1667
|
+
lastHeartbeatRevision = -1;
|
|
1668
|
+
cursorHidden = false;
|
|
1669
|
+
isTTY;
|
|
1670
|
+
start() {
|
|
1671
|
+
if (this.timer) return;
|
|
1672
|
+
if (this.isTTY) {
|
|
1673
|
+
this.hideCursor();
|
|
1674
|
+
process.on("exit", this.restoreCursor);
|
|
1675
|
+
process.once("SIGINT", () => {
|
|
1676
|
+
this.restoreCursor();
|
|
1677
|
+
process.exit(130);
|
|
1678
|
+
});
|
|
1679
|
+
process.once("SIGTERM", () => {
|
|
1680
|
+
this.restoreCursor();
|
|
1681
|
+
process.exit(143);
|
|
1682
|
+
});
|
|
1683
|
+
this.timer = setInterval(() => this.renderTTY(), TTY_INTERVAL_MS);
|
|
1684
|
+
this.renderTTY();
|
|
1685
|
+
} else {
|
|
1686
|
+
this.timer = setInterval(() => this.renderHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
1687
|
+
}
|
|
1688
|
+
this.timer.unref?.();
|
|
1689
|
+
}
|
|
1690
|
+
stop() {
|
|
1691
|
+
if (this.timer) {
|
|
1692
|
+
clearInterval(this.timer);
|
|
1693
|
+
this.timer = null;
|
|
1694
|
+
}
|
|
1695
|
+
if (this.isTTY) {
|
|
1696
|
+
let buf = "";
|
|
1697
|
+
if (this.lastRowCount > 0) buf += `\x1B[${this.lastRowCount}A\x1B[J`;
|
|
1698
|
+
this.stream.write(buf);
|
|
1699
|
+
this.lastRowCount = 0;
|
|
1700
|
+
this.restoreCursor();
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
renderTTY() {
|
|
1704
|
+
this.tick++;
|
|
1705
|
+
const lines = serveStatusLines(this.stats.snapshot(), Date.now(), this.tick);
|
|
1706
|
+
const content = lines.join("\n") + "\n";
|
|
1707
|
+
if (content === this.lastFrame) return;
|
|
1708
|
+
this.lastFrame = content;
|
|
1709
|
+
let buf = "";
|
|
1710
|
+
if (this.lastRowCount > 0) buf += `\x1B[${this.lastRowCount}A\x1B[J`;
|
|
1711
|
+
buf += content;
|
|
1712
|
+
this.stream.write(buf);
|
|
1713
|
+
this.lastRowCount = this.countRows(lines);
|
|
1714
|
+
}
|
|
1715
|
+
renderHeartbeat() {
|
|
1716
|
+
const snap = this.stats.snapshot();
|
|
1717
|
+
if (snap.revision === this.lastHeartbeatRevision) return;
|
|
1718
|
+
this.lastHeartbeatRevision = snap.revision;
|
|
1719
|
+
this.stream.write(serveHeartbeatLine(snap, Date.now()) + "\n");
|
|
1720
|
+
}
|
|
1721
|
+
/** Terminal rows the frame occupies, accounting for line wrap. */
|
|
1722
|
+
countRows(lines) {
|
|
1723
|
+
const columns = Math.max(this.stream.columns ?? 80, 20);
|
|
1724
|
+
return lines.reduce((total, line) => {
|
|
1725
|
+
const visible = line.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
1726
|
+
return total + Math.max(1, Math.ceil(visible / columns));
|
|
1727
|
+
}, 0);
|
|
1728
|
+
}
|
|
1729
|
+
hideCursor() {
|
|
1730
|
+
if (this.cursorHidden) return;
|
|
1731
|
+
this.stream.write("\x1B[?25l");
|
|
1732
|
+
this.cursorHidden = true;
|
|
1733
|
+
}
|
|
1734
|
+
restoreCursor = () => {
|
|
1735
|
+
if (!this.cursorHidden) return;
|
|
1736
|
+
this.stream.write("\x1B[?25h");
|
|
1737
|
+
this.cursorHidden = false;
|
|
1738
|
+
};
|
|
1739
|
+
};
|
|
1740
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
1741
|
+
var LedgerTail = class {
|
|
1742
|
+
constructor(file, stats, intervalMs = POLL_INTERVAL_MS) {
|
|
1743
|
+
this.file = file;
|
|
1744
|
+
this.stats = stats;
|
|
1745
|
+
this.intervalMs = intervalMs;
|
|
1746
|
+
}
|
|
1747
|
+
file;
|
|
1748
|
+
stats;
|
|
1749
|
+
intervalMs;
|
|
1750
|
+
timer = null;
|
|
1751
|
+
/** Byte offset of the next unread ledger byte (always at a line boundary). */
|
|
1752
|
+
offset = 0;
|
|
1753
|
+
start() {
|
|
1754
|
+
if (this.timer) return;
|
|
1755
|
+
this.offset = this.sizeOf();
|
|
1756
|
+
this.timer = setInterval(() => this.poll(), this.intervalMs);
|
|
1757
|
+
this.timer.unref?.();
|
|
1758
|
+
}
|
|
1759
|
+
stop() {
|
|
1760
|
+
if (this.timer) {
|
|
1761
|
+
clearInterval(this.timer);
|
|
1762
|
+
this.timer = null;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
/** One poll cycle — exposed for tests (deterministic, no timers needed). */
|
|
1766
|
+
poll(now2 = Date.now()) {
|
|
1767
|
+
let size;
|
|
1768
|
+
try {
|
|
1769
|
+
size = this.sizeOf();
|
|
1770
|
+
} catch {
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
if (size < this.offset) this.offset = 0;
|
|
1774
|
+
if (size === this.offset) return;
|
|
1775
|
+
let chunk;
|
|
1776
|
+
try {
|
|
1777
|
+
const fd = fs9.openSync(this.file, "r");
|
|
1778
|
+
try {
|
|
1779
|
+
chunk = Buffer.alloc(size - this.offset);
|
|
1780
|
+
fs9.readSync(fd, chunk, 0, chunk.length, this.offset);
|
|
1781
|
+
} finally {
|
|
1782
|
+
fs9.closeSync(fd);
|
|
1783
|
+
}
|
|
1784
|
+
} catch {
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
const lastNewline = chunk.lastIndexOf(10);
|
|
1788
|
+
if (lastNewline < 0) return;
|
|
1789
|
+
const text = chunk.subarray(0, lastNewline).toString("utf8");
|
|
1790
|
+
this.offset += lastNewline + 1;
|
|
1791
|
+
for (const line of text.split("\n")) ingestLedgerLine(this.stats, line, now2);
|
|
1792
|
+
}
|
|
1793
|
+
sizeOf() {
|
|
1794
|
+
try {
|
|
1795
|
+
return fs9.statSync(this.file).size;
|
|
1796
|
+
} catch {
|
|
1797
|
+
return 0;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
function ingestLedgerLine(stats, line, now2) {
|
|
1802
|
+
if (!line.trim()) return;
|
|
1803
|
+
let e;
|
|
1804
|
+
try {
|
|
1805
|
+
e = JSON.parse(line);
|
|
1806
|
+
} catch {
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (e.source !== "cli" || typeof e.tool !== "string") return;
|
|
1810
|
+
stats.record(
|
|
1811
|
+
{
|
|
1812
|
+
tool: e.tool,
|
|
1813
|
+
client: sanitizeClient(e.client),
|
|
1814
|
+
outcome: e.outcome ?? "complete",
|
|
1815
|
+
source: "cli",
|
|
1816
|
+
// CLI lines carry no wall time — leave ms absent ("not measured").
|
|
1817
|
+
vgTokens: e.vgTokens ?? 0,
|
|
1818
|
+
baselineTokens: e.baselineTokens ?? 0
|
|
1819
|
+
},
|
|
1820
|
+
now2
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1511
1824
|
// src/commands/serve.ts
|
|
1512
1825
|
var SHARE_FLUSH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1513
1826
|
function registerServe(program) {
|
|
@@ -1519,6 +1832,11 @@ function registerServe(program) {
|
|
|
1519
1832
|
const local = global.local === true;
|
|
1520
1833
|
const optOut = telemetryOptOut();
|
|
1521
1834
|
const shareStats = opts.shareStats === true && !local && optOut === null;
|
|
1835
|
+
const quiet = global.quiet === true;
|
|
1836
|
+
const stats = quiet ? void 0 : new SessionStats();
|
|
1837
|
+
if (!quiet) {
|
|
1838
|
+
printLogo(root, { product: "AI Context", tagline: "Local-first MCP for your AI" });
|
|
1839
|
+
}
|
|
1522
1840
|
const serveOpts = {
|
|
1523
1841
|
// A *requested* --share-stats always implies local recording, even when
|
|
1524
1842
|
// the upload itself is suppressed (--local / env opt-out) — the local
|
|
@@ -1528,7 +1846,8 @@ function registerServe(program) {
|
|
|
1528
1846
|
shareStats,
|
|
1529
1847
|
local,
|
|
1530
1848
|
dedup: opts.dedup === true,
|
|
1531
|
-
refresh
|
|
1849
|
+
refresh,
|
|
1850
|
+
stats
|
|
1532
1851
|
};
|
|
1533
1852
|
await ensureServableGraph(root, graphPath, global, refresh);
|
|
1534
1853
|
if (opts.shareStats === true && local) {
|
|
@@ -1537,19 +1856,22 @@ function registerServe(program) {
|
|
|
1537
1856
|
info(c.dim(`vg \xB7 --share-stats disabled by ${optOut}: recording locally, not uploading.`));
|
|
1538
1857
|
}
|
|
1539
1858
|
if (shareStats) startSharing(root);
|
|
1859
|
+
const display = stats ? new ServeStatusDisplay(stats) : void 0;
|
|
1860
|
+
if (stats) new LedgerTail(savingsLedgerPath(root), stats).start();
|
|
1540
1861
|
const freshness = refresh ? "auto-refresh" : "as built";
|
|
1541
1862
|
if (opts.http) {
|
|
1542
|
-
await serveHttp(graphPath, opts.host ?? "127.0.0.1", Number(opts.port) || 7437, serveOpts, freshness);
|
|
1863
|
+
await serveHttp(graphPath, opts.host ?? "127.0.0.1", Number(opts.port) || 7437, serveOpts, freshness, () => display?.start());
|
|
1543
1864
|
} else {
|
|
1544
1865
|
info(c.dim(`vg \xB7 MCP server on stdio (read-only, ${freshness}). Connect your assistant to this process.`));
|
|
1545
1866
|
await serveStdio(graphPath, serveOpts);
|
|
1867
|
+
display?.start();
|
|
1546
1868
|
}
|
|
1547
1869
|
});
|
|
1548
1870
|
applyGlobalOptions(cmd);
|
|
1549
1871
|
}
|
|
1550
1872
|
async function ensureServableGraph(root, graphPath, global, refresh, opts = {}) {
|
|
1551
1873
|
if (refresh) {
|
|
1552
|
-
if (!
|
|
1874
|
+
if (!fs9.existsSync(graphPath)) {
|
|
1553
1875
|
info(c.dim("vg \xB7 no map found \u2014 building it before serving\u2026"));
|
|
1554
1876
|
await runBuild(
|
|
1555
1877
|
[],
|
|
@@ -1566,7 +1888,7 @@ async function ensureServableGraph(root, graphPath, global, refresh, opts = {})
|
|
|
1566
1888
|
}
|
|
1567
1889
|
}
|
|
1568
1890
|
}
|
|
1569
|
-
if (!
|
|
1891
|
+
if (!fs9.existsSync(graphPath)) {
|
|
1570
1892
|
throw new CliError(
|
|
1571
1893
|
`no map found at ${graphPath} \u2014 run \`vg\` to build one first`,
|
|
1572
1894
|
ExitCode.NOT_FOUND
|
|
@@ -1598,7 +1920,7 @@ function startSharing(root) {
|
|
|
1598
1920
|
});
|
|
1599
1921
|
process.once("beforeExit", finalFlush);
|
|
1600
1922
|
}
|
|
1601
|
-
async function serveHttp(graphPath, host, port, opts, freshness) {
|
|
1923
|
+
async function serveHttp(graphPath, host, port, opts, freshness, onReady) {
|
|
1602
1924
|
const { createServer: createHttp } = await import('http');
|
|
1603
1925
|
const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
1604
1926
|
const source = new GraphSource(graphPath, opts.refresh !== false);
|
|
@@ -1630,6 +1952,7 @@ async function serveHttp(graphPath, host, port, opts, freshness) {
|
|
|
1630
1952
|
});
|
|
1631
1953
|
await new Promise((resolve17) => httpServer.listen(port, host, resolve17));
|
|
1632
1954
|
info(c.dim(`vg \xB7 MCP server on http://${host}:${port}/mcp (read-only, local, ${freshness})`));
|
|
1955
|
+
onReady?.();
|
|
1633
1956
|
await new Promise(() => {
|
|
1634
1957
|
});
|
|
1635
1958
|
}
|
|
@@ -1848,7 +2171,7 @@ function manifestHash(root) {
|
|
|
1848
2171
|
const walk2 = (dir) => {
|
|
1849
2172
|
let entries;
|
|
1850
2173
|
try {
|
|
1851
|
-
entries =
|
|
2174
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
1852
2175
|
} catch {
|
|
1853
2176
|
return;
|
|
1854
2177
|
}
|
|
@@ -1864,7 +2187,7 @@ function manifestHash(root) {
|
|
|
1864
2187
|
if (!isManifest(abs) && !SKIP_FILES.has(base)) continue;
|
|
1865
2188
|
try {
|
|
1866
2189
|
const rel3 = path.relative(root, abs).split(path.sep).join("/");
|
|
1867
|
-
files.push({ rel: rel3, hash: hashBytes(
|
|
2190
|
+
files.push({ rel: rel3, hash: hashBytes(fs9.readFileSync(abs)) });
|
|
1868
2191
|
} catch {
|
|
1869
2192
|
}
|
|
1870
2193
|
}
|
|
@@ -1875,7 +2198,7 @@ function manifestHash(root) {
|
|
|
1875
2198
|
}
|
|
1876
2199
|
function loadScanCache(root, key2) {
|
|
1877
2200
|
try {
|
|
1878
|
-
const raw = JSON.parse(
|
|
2201
|
+
const raw = JSON.parse(fs9.readFileSync(cachePath(root), "utf8"));
|
|
1879
2202
|
if (raw.version === CACHE_VERSION && raw.manifestHash === key2.manifestHash && raw.toolVersion === key2.toolVersion && raw.offline === key2.offline && raw.artifact) {
|
|
1880
2203
|
return { manifestHash: raw.manifestHash, artifact: raw.artifact };
|
|
1881
2204
|
}
|
|
@@ -1885,8 +2208,8 @@ function loadScanCache(root, key2) {
|
|
|
1885
2208
|
}
|
|
1886
2209
|
function writeScanCache(root, key2, artifact) {
|
|
1887
2210
|
try {
|
|
1888
|
-
|
|
1889
|
-
|
|
2211
|
+
fs9.mkdirSync(cacheDir(root), { recursive: true });
|
|
2212
|
+
fs9.writeFileSync(
|
|
1890
2213
|
cachePath(root),
|
|
1891
2214
|
stableStringify(
|
|
1892
2215
|
{ version: CACHE_VERSION, manifestHash: key2.manifestHash, toolVersion: key2.toolVersion, offline: key2.offline, artifact },
|
|
@@ -2710,7 +3033,7 @@ function hoverMarkdown(dep, contribution) {
|
|
|
2710
3033
|
return lines.join("\n");
|
|
2711
3034
|
}
|
|
2712
3035
|
function startLanguageServer(opts) {
|
|
2713
|
-
if (!
|
|
3036
|
+
if (!fs9.existsSync(opts.root)) {
|
|
2714
3037
|
process.stderr.write(`vg lsp: root does not exist: ${opts.root}
|
|
2715
3038
|
`);
|
|
2716
3039
|
process.exit(1);
|
|
@@ -2849,7 +3172,7 @@ function registerShare(program) {
|
|
|
2849
3172
|
const global = readGlobal(this);
|
|
2850
3173
|
const root = rootOf(global);
|
|
2851
3174
|
const gitDir = path.join(root, ".git");
|
|
2852
|
-
if (!
|
|
3175
|
+
if (!fs9.existsSync(gitDir)) {
|
|
2853
3176
|
throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
|
|
2854
3177
|
}
|
|
2855
3178
|
if (opts.undo) {
|
|
@@ -2871,22 +3194,22 @@ function registerShare(program) {
|
|
|
2871
3194
|
}
|
|
2872
3195
|
function writeVibgrateGitignore(root, reports) {
|
|
2873
3196
|
const dir = path.join(root, ".vibgrate");
|
|
2874
|
-
|
|
3197
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
2875
3198
|
const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl"];
|
|
2876
3199
|
if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
|
|
2877
|
-
|
|
3200
|
+
fs9.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
|
|
2878
3201
|
`);
|
|
2879
3202
|
}
|
|
2880
3203
|
function writeGitAttributes(root) {
|
|
2881
3204
|
const file = path.join(root, ".gitattributes");
|
|
2882
3205
|
const line = ".vibgrate/graph.json merge=vg";
|
|
2883
|
-
let content =
|
|
3206
|
+
let content = fs9.existsSync(file) ? fs9.readFileSync(file, "utf8") : "";
|
|
2884
3207
|
if (!content.split("\n").some((l) => l.trim() === line)) {
|
|
2885
3208
|
content = content.length ? `${content.replace(/\s*$/, "")}
|
|
2886
3209
|
${line}
|
|
2887
3210
|
` : `${line}
|
|
2888
3211
|
`;
|
|
2889
|
-
|
|
3212
|
+
fs9.writeFileSync(file, content);
|
|
2890
3213
|
}
|
|
2891
3214
|
}
|
|
2892
3215
|
function configureMergeDriver(root) {
|
|
@@ -2895,7 +3218,7 @@ function configureMergeDriver(root) {
|
|
|
2895
3218
|
}
|
|
2896
3219
|
function installPreCommitHook(root) {
|
|
2897
3220
|
const hooksDir = hookDir(root);
|
|
2898
|
-
|
|
3221
|
+
fs9.mkdirSync(hooksDir, { recursive: true });
|
|
2899
3222
|
const file = path.join(hooksDir, "pre-commit");
|
|
2900
3223
|
const block = [
|
|
2901
3224
|
HOOK_BEGIN,
|
|
@@ -2905,7 +3228,7 @@ function installPreCommitHook(root) {
|
|
|
2905
3228
|
"fi",
|
|
2906
3229
|
HOOK_END
|
|
2907
3230
|
].join("\n");
|
|
2908
|
-
let content =
|
|
3231
|
+
let content = fs9.existsSync(file) ? fs9.readFileSync(file, "utf8") : "#!/bin/sh\n";
|
|
2909
3232
|
const re = new RegExp(`${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}`);
|
|
2910
3233
|
if (re.test(content)) content = content.replace(re, block);
|
|
2911
3234
|
else content = `${content.replace(/\s*$/, "")}
|
|
@@ -2914,21 +3237,21 @@ ${block}
|
|
|
2914
3237
|
`;
|
|
2915
3238
|
if (!content.startsWith("#!")) content = `#!/bin/sh
|
|
2916
3239
|
${content}`;
|
|
2917
|
-
|
|
2918
|
-
|
|
3240
|
+
fs9.writeFileSync(file, content);
|
|
3241
|
+
fs9.chmodSync(file, 493);
|
|
2919
3242
|
}
|
|
2920
3243
|
function undo(root) {
|
|
2921
3244
|
const hookFile = path.join(hookDir(root), "pre-commit");
|
|
2922
|
-
if (
|
|
3245
|
+
if (fs9.existsSync(hookFile)) {
|
|
2923
3246
|
const re = new RegExp(`\\n*${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}\\n*`);
|
|
2924
|
-
const next2 =
|
|
2925
|
-
if (next2.trim() === "#!/bin/sh")
|
|
2926
|
-
else
|
|
3247
|
+
const next2 = fs9.readFileSync(hookFile, "utf8").replace(re, "\n");
|
|
3248
|
+
if (next2.trim() === "#!/bin/sh") fs9.rmSync(hookFile);
|
|
3249
|
+
else fs9.writeFileSync(hookFile, next2);
|
|
2927
3250
|
}
|
|
2928
3251
|
const attr = path.join(root, ".gitattributes");
|
|
2929
|
-
if (
|
|
2930
|
-
const next2 =
|
|
2931
|
-
|
|
3252
|
+
if (fs9.existsSync(attr)) {
|
|
3253
|
+
const next2 = fs9.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
|
|
3254
|
+
fs9.writeFileSync(attr, next2);
|
|
2932
3255
|
}
|
|
2933
3256
|
try {
|
|
2934
3257
|
git(root, ["config", "--remove-section", "merge.vg"]);
|
|
@@ -3061,7 +3384,7 @@ function grepBaselineTokens(term, files) {
|
|
|
3061
3384
|
if (f.size > 512 * 1024) continue;
|
|
3062
3385
|
let content = "";
|
|
3063
3386
|
try {
|
|
3064
|
-
content =
|
|
3387
|
+
content = fs9.readFileSync(f.abs, "utf8");
|
|
3065
3388
|
} catch {
|
|
3066
3389
|
continue;
|
|
3067
3390
|
}
|
|
@@ -3071,7 +3394,7 @@ function grepBaselineTokens(term, files) {
|
|
|
3071
3394
|
}
|
|
3072
3395
|
function safeSize(abs) {
|
|
3073
3396
|
try {
|
|
3074
|
-
return
|
|
3397
|
+
return fs9.statSync(abs).size;
|
|
3075
3398
|
} catch {
|
|
3076
3399
|
return 0;
|
|
3077
3400
|
}
|
|
@@ -3405,7 +3728,7 @@ function loadStandards(root) {
|
|
|
3405
3728
|
const p = path__default.join(root, rel3);
|
|
3406
3729
|
let text;
|
|
3407
3730
|
try {
|
|
3408
|
-
text =
|
|
3731
|
+
text = fs9__default.readFileSync(p, "utf8");
|
|
3409
3732
|
} catch {
|
|
3410
3733
|
continue;
|
|
3411
3734
|
}
|
|
@@ -3558,17 +3881,18 @@ function printBreakdown(usage) {
|
|
|
3558
3881
|
info("");
|
|
3559
3882
|
info(c.bold(" by command") + c.dim(" (complete = full result \xB7 partial = capped/paginated \xB7 miss = no result)"));
|
|
3560
3883
|
const nameW = Math.max(7, ...usage.commands.map((cmd) => cmd.tool.length));
|
|
3561
|
-
const header = " " + "command".padEnd(nameW) + ["calls", "complete", "partial", "miss", "success%"].map((h) => h.padStart(9)).join("");
|
|
3884
|
+
const header = " " + "command".padEnd(nameW) + ["calls", "complete", "partial", "miss", "success%", "avg ms"].map((h) => h.padStart(9)).join("");
|
|
3562
3885
|
info(c.dim(header));
|
|
3563
3886
|
for (const cmd of usage.commands) {
|
|
3564
3887
|
info(
|
|
3565
|
-
" " + 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)
|
|
3888
|
+
" " + 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) + // Absent ≠ zero: calls recorded before timing existed show '—'.
|
|
3889
|
+
(cmd.avgMs === null ? "\u2014" : String(cmd.avgMs)).padStart(9)
|
|
3566
3890
|
);
|
|
3567
3891
|
}
|
|
3568
3892
|
const t = usage.totals;
|
|
3569
|
-
info(c.dim(" " + "\u2500".repeat(nameW +
|
|
3893
|
+
info(c.dim(" " + "\u2500".repeat(nameW + 54)));
|
|
3570
3894
|
info(
|
|
3571
|
-
" " + 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)
|
|
3895
|
+
" " + 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) + "".padStart(9)
|
|
3572
3896
|
);
|
|
3573
3897
|
info(c.dim(` avg success across commands: ${usage.avgSuccessPct}%`));
|
|
3574
3898
|
}
|
|
@@ -3643,7 +3967,7 @@ async function addCmd(root, rest, opts, asJson) {
|
|
|
3643
3967
|
}
|
|
3644
3968
|
function readPackageVersion(root) {
|
|
3645
3969
|
try {
|
|
3646
|
-
const pkg = JSON.parse(
|
|
3970
|
+
const pkg = JSON.parse(fs9__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
|
|
3647
3971
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
3648
3972
|
} catch {
|
|
3649
3973
|
return void 0;
|
|
@@ -3651,7 +3975,7 @@ function readPackageVersion(root) {
|
|
|
3651
3975
|
}
|
|
3652
3976
|
function readOptionalFile(p) {
|
|
3653
3977
|
try {
|
|
3654
|
-
const t =
|
|
3978
|
+
const t = fs9__default.readFileSync(p, "utf8");
|
|
3655
3979
|
return t.trim() ? t : void 0;
|
|
3656
3980
|
} catch {
|
|
3657
3981
|
return void 0;
|
|
@@ -3797,8 +4121,8 @@ function registerExport(program) {
|
|
|
3797
4121
|
out(content.trimEnd());
|
|
3798
4122
|
return;
|
|
3799
4123
|
}
|
|
3800
|
-
|
|
3801
|
-
|
|
4124
|
+
fs9.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
|
|
4125
|
+
fs9.writeFileSync(file, content);
|
|
3802
4126
|
info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
|
|
3803
4127
|
});
|
|
3804
4128
|
applyGlobalOptions(cmd);
|
|
@@ -3821,24 +4145,24 @@ function registerBundle(program) {
|
|
|
3821
4145
|
} catch {
|
|
3822
4146
|
throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
|
|
3823
4147
|
}
|
|
3824
|
-
|
|
4148
|
+
fs9.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
|
|
3825
4149
|
let grammarCount = 0;
|
|
3826
4150
|
for (const { fileName, absPath } of grammarFiles) {
|
|
3827
|
-
|
|
4151
|
+
fs9.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
|
|
3828
4152
|
grammarCount++;
|
|
3829
4153
|
}
|
|
3830
4154
|
const included = [`grammars/ (${grammarCount} .wasm)`];
|
|
3831
4155
|
const graphSrc = global.graph ?? defaultGraphPath(root);
|
|
3832
|
-
if (
|
|
3833
|
-
|
|
4156
|
+
if (fs9.existsSync(graphSrc)) {
|
|
4157
|
+
fs9.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
|
|
3834
4158
|
included.push("graph.json");
|
|
3835
4159
|
}
|
|
3836
4160
|
const cat = catalogPath(root);
|
|
3837
|
-
if (
|
|
3838
|
-
|
|
4161
|
+
if (fs9.existsSync(cat)) {
|
|
4162
|
+
fs9.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
|
|
3839
4163
|
included.push("vibgrate.lib.json");
|
|
3840
|
-
if (
|
|
3841
|
-
|
|
4164
|
+
if (fs9.existsSync(libDir(root))) {
|
|
4165
|
+
fs9.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
|
|
3842
4166
|
included.push("lib/");
|
|
3843
4167
|
}
|
|
3844
4168
|
}
|
|
@@ -3850,7 +4174,7 @@ function registerBundle(program) {
|
|
|
3850
4174
|
included,
|
|
3851
4175
|
usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
|
|
3852
4176
|
};
|
|
3853
|
-
|
|
4177
|
+
fs9.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
|
|
3854
4178
|
`);
|
|
3855
4179
|
if (global.json) {
|
|
3856
4180
|
json({ out: path.relative(root, outDir), ...manifest });
|
|
@@ -3874,7 +4198,7 @@ async function runDoctor(global) {
|
|
|
3874
4198
|
const root = rootOf(global);
|
|
3875
4199
|
const local = global.local === true;
|
|
3876
4200
|
const graphPath = global.graph ?? defaultGraphPath(root);
|
|
3877
|
-
const configFile = CONFIG_BASENAMES.find((f) =>
|
|
4201
|
+
const configFile = CONFIG_BASENAMES.find((f) => fs9.existsSync(path.join(root, f))) ?? null;
|
|
3878
4202
|
const graph = loadGraph(root, graphPath);
|
|
3879
4203
|
let staleFiles = null;
|
|
3880
4204
|
if (graph) {
|
|
@@ -3902,7 +4226,7 @@ async function runDoctor(global) {
|
|
|
3902
4226
|
},
|
|
3903
4227
|
libCatalog: {
|
|
3904
4228
|
path: path.relative(root, catalogPath(root)),
|
|
3905
|
-
present:
|
|
4229
|
+
present: fs9.existsSync(catalogPath(root)),
|
|
3906
4230
|
libraries: libCount
|
|
3907
4231
|
},
|
|
3908
4232
|
credentials: creds,
|
|
@@ -3994,7 +4318,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
3994
4318
|
console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold("vibgrate.config.ts")}`);
|
|
3995
4319
|
}
|
|
3996
4320
|
if (opts.baseline) {
|
|
3997
|
-
const { runBaseline } = await import('./baseline-
|
|
4321
|
+
const { runBaseline } = await import('./baseline-WIZAFIJS.js');
|
|
3998
4322
|
await runBaseline(rootDir);
|
|
3999
4323
|
}
|
|
4000
4324
|
console.log("");
|
|
@@ -4104,9 +4428,9 @@ async function fileReferencesIdentifier(rootDir, relFile, identifier, readFile3)
|
|
|
4104
4428
|
}
|
|
4105
4429
|
async function defaultReadFile(absPath) {
|
|
4106
4430
|
try {
|
|
4107
|
-
const stat2 = await
|
|
4431
|
+
const stat2 = await fs16.stat(absPath);
|
|
4108
4432
|
if (!stat2.isFile() || stat2.size > MAX_FILE_BYTES) return null;
|
|
4109
|
-
return await
|
|
4433
|
+
return await fs16.readFile(absPath, "utf8");
|
|
4110
4434
|
} catch {
|
|
4111
4435
|
return null;
|
|
4112
4436
|
}
|
|
@@ -4299,22 +4623,22 @@ async function detectAiAssistant(rootDir) {
|
|
|
4299
4623
|
return null;
|
|
4300
4624
|
}
|
|
4301
4625
|
function printAiContextPrompt(detectedAssistant) {
|
|
4302
|
-
const
|
|
4303
|
-
const
|
|
4626
|
+
const teal3 = chalk8.hex("#3FB0A4");
|
|
4627
|
+
const mint3 = chalk8.hex("#4FE3C1");
|
|
4304
4628
|
const cli = resolveCliInvocation();
|
|
4305
4629
|
const installArgs = detectedAssistant ? `install ${detectedAssistant}` : "install --all";
|
|
4306
4630
|
const installCmd = `${cli} ${installArgs}`;
|
|
4307
4631
|
const detectedNote = detectedAssistant ? chalk8.dim(` (${detectedAssistant} config detected)`) : "";
|
|
4308
4632
|
console.log("");
|
|
4309
|
-
console.log(
|
|
4310
|
-
console.log(
|
|
4311
|
-
console.log(
|
|
4633
|
+
console.log(teal3(" \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"));
|
|
4634
|
+
console.log(teal3(" \u2502") + " " + mint3("\u25C6") + " " + chalk8.bold.white("Get AI-aware answers in your editor") + " " + teal3("\u2502"));
|
|
4635
|
+
console.log(teal3(" \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"));
|
|
4312
4636
|
console.log("");
|
|
4313
4637
|
console.log(" " + chalk8.bold.white(installCmd) + detectedNote);
|
|
4314
4638
|
console.log("");
|
|
4315
|
-
console.log(" " +
|
|
4316
|
-
console.log(" " +
|
|
4317
|
-
console.log(" " +
|
|
4639
|
+
console.log(" " + teal3("\xB7") + " " + chalk8.white("Code map in your assistant") + chalk8.dim(" \u2014 call trees, impact surfaces, import paths"));
|
|
4640
|
+
console.log(" " + teal3("\xB7") + " " + chalk8.white("Offline drift") + chalk8.dim(" \u2014 DriftScore and upgrade priorities, right inside your editor"));
|
|
4641
|
+
console.log(" " + teal3("\xB7") + " " + chalk8.white("Version-correct library docs") + chalk8.dim(" \u2014 pinned to your lockfile, no hallucinated APIs"));
|
|
4318
4642
|
console.log("");
|
|
4319
4643
|
}
|
|
4320
4644
|
|
|
@@ -4751,7 +5075,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
4751
5075
|
function scanStaleness(rootDir, artifactPath) {
|
|
4752
5076
|
let ref;
|
|
4753
5077
|
try {
|
|
4754
|
-
ref =
|
|
5078
|
+
ref = fs9.statSync(artifactPath).mtimeMs;
|
|
4755
5079
|
} catch {
|
|
4756
5080
|
return { stale: false };
|
|
4757
5081
|
}
|
|
@@ -4763,7 +5087,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
4763
5087
|
if (depth > 8 || scanned > MAX_ENTRIES) return;
|
|
4764
5088
|
let entries;
|
|
4765
5089
|
try {
|
|
4766
|
-
entries =
|
|
5090
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
4767
5091
|
} catch {
|
|
4768
5092
|
return;
|
|
4769
5093
|
}
|
|
@@ -4778,7 +5102,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
4778
5102
|
const abs = path.join(dir, e.name);
|
|
4779
5103
|
let mtimeMs;
|
|
4780
5104
|
try {
|
|
4781
|
-
mtimeMs =
|
|
5105
|
+
mtimeMs = fs9.statSync(abs).mtimeMs;
|
|
4782
5106
|
} catch {
|
|
4783
5107
|
continue;
|
|
4784
5108
|
}
|
|
@@ -4912,7 +5236,7 @@ function analyzeTree(root, packages) {
|
|
|
4912
5236
|
if (depth > MAX_DEPTH || filesSeen >= MAX_FILES) return;
|
|
4913
5237
|
let entries;
|
|
4914
5238
|
try {
|
|
4915
|
-
entries =
|
|
5239
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
4916
5240
|
} catch {
|
|
4917
5241
|
return;
|
|
4918
5242
|
}
|
|
@@ -4930,8 +5254,8 @@ function analyzeTree(root, packages) {
|
|
|
4930
5254
|
const full = path.join(dir, entry.name);
|
|
4931
5255
|
let content;
|
|
4932
5256
|
try {
|
|
4933
|
-
if (
|
|
4934
|
-
content =
|
|
5257
|
+
if (fs9.statSync(full).size > MAX_FILE_BYTES2) continue;
|
|
5258
|
+
content = fs9.readFileSync(full, "utf8");
|
|
4935
5259
|
} catch {
|
|
4936
5260
|
continue;
|
|
4937
5261
|
}
|
|
@@ -5299,8 +5623,8 @@ async function fetchLatestVersion() {
|
|
|
5299
5623
|
}
|
|
5300
5624
|
async function writeCache(data) {
|
|
5301
5625
|
try {
|
|
5302
|
-
await
|
|
5303
|
-
await
|
|
5626
|
+
await fs16.mkdir(CACHE_DIR, { recursive: true });
|
|
5627
|
+
await fs16.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
5304
5628
|
} catch {
|
|
5305
5629
|
}
|
|
5306
5630
|
}
|
|
@@ -5311,7 +5635,7 @@ function detectGlobalInstall(execPath = process.argv[1] || "", cwd = process.cwd
|
|
|
5311
5635
|
if (!execPath) return null;
|
|
5312
5636
|
let resolved = execPath;
|
|
5313
5637
|
try {
|
|
5314
|
-
resolved =
|
|
5638
|
+
resolved = fs9.realpathSync(execPath);
|
|
5315
5639
|
} catch {
|
|
5316
5640
|
}
|
|
5317
5641
|
const p = resolved.replace(/\\/g, "/");
|
|
@@ -5840,15 +6164,15 @@ Applied the ${chosen.label} plan:`));
|
|
|
5840
6164
|
}
|
|
5841
6165
|
function formatText(artifact) {
|
|
5842
6166
|
const lines = [];
|
|
5843
|
-
const
|
|
5844
|
-
const
|
|
6167
|
+
const teal3 = chalk8.hex("#3FB0A4");
|
|
6168
|
+
const mint3 = chalk8.hex("#4FE3C1");
|
|
5845
6169
|
lines.push("");
|
|
5846
|
-
lines.push(" " +
|
|
5847
|
-
lines.push(" " + chalk8.dim("\u2524") +
|
|
5848
|
-
lines.push(" " + chalk8.dim("\u2524") +
|
|
5849
|
-
lines.push(" " +
|
|
6170
|
+
lines.push(" " + teal3("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E") + mint3("\u279C"));
|
|
6171
|
+
lines.push(" " + chalk8.dim("\u2524") + teal3("\u2502") + " " + mint3("\u25FC") + " " + mint3("\u25FC") + " " + teal3("\u2502") + chalk8.dim("\u251C") + " " + chalk8.bold.white("vibgrate"));
|
|
6172
|
+
lines.push(" " + chalk8.dim("\u2524") + teal3("\u2502") + " " + chalk8.dim("\u2581\u2581") + " " + teal3("\u2502") + chalk8.dim("\u251C") + " " + chalk8.dim(`Code Intelligence Engine v${VERSION}`));
|
|
6173
|
+
lines.push(" " + teal3("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
5850
6174
|
lines.push("");
|
|
5851
|
-
lines.push(...titleBox("Vibgrate Drift Report",
|
|
6175
|
+
lines.push(...titleBox("Vibgrate Drift Report", teal3));
|
|
5852
6176
|
lines.push("");
|
|
5853
6177
|
for (const project of artifact.projects) {
|
|
5854
6178
|
lines.push(chalk8.bold(` \u2500\u2500 ${project.name} `) + chalk8.dim(`(${project.type}) ${project.path}`));
|
|
@@ -7251,7 +7575,7 @@ function dispatch(argv, cwd) {
|
|
|
7251
7575
|
}
|
|
7252
7576
|
const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
|
|
7253
7577
|
if (looksLikeQuestion) return ["ask", ...args];
|
|
7254
|
-
if (
|
|
7578
|
+
if (fs9.existsSync(path.resolve(cwd, first))) {
|
|
7255
7579
|
return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
|
|
7256
7580
|
}
|
|
7257
7581
|
const moved = MOVED_COMMANDS[first];
|