@vibgrate/cli 2026.711.1 → 2026.715.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +77 -11
- package/README.md +16 -12
- package/dist/baseline-BTLZWSNS.js +6 -0
- package/dist/{baseline-V5XGS76K.js.map → baseline-BTLZWSNS.js.map} +1 -1
- package/dist/{chunk-3BWPEPWH.js → chunk-6DKWB2OI.js} +628 -4
- package/dist/chunk-6DKWB2OI.js.map +1 -0
- package/dist/{chunk-2PCL4ZME.js → chunk-C5AVF7PT.js} +306 -70
- package/dist/chunk-C5AVF7PT.js.map +1 -0
- package/dist/{chunk-MIJ3ZSSF.js → chunk-D4BPWARI.js} +260 -92
- package/dist/chunk-D4BPWARI.js.map +1 -0
- package/dist/{chunk-X5YT263H.js → chunk-VFO5UDAT.js} +21 -2
- package/dist/chunk-VFO5UDAT.js.map +1 -0
- package/dist/cli.js +1016 -97
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +20 -6
- package/dist/index.js +3 -3
- package/dist/parse-worker.d.ts +1 -1
- package/dist/parse-worker.js +2 -1
- package/dist/parse-worker.js.map +1 -1
- package/dist/{types-CXnp1uGj.d.ts → types-BgNa-FZQ.d.ts} +8 -0
- package/package.json +2 -2
- package/skills/README.md +2 -2
- package/dist/baseline-V5XGS76K.js +0 -6
- package/dist/chunk-2PCL4ZME.js.map +0 -1
- package/dist/chunk-3BWPEPWH.js.map +0 -1
- package/dist/chunk-MIJ3ZSSF.js.map +0 -1
- package/dist/chunk-X5YT263H.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
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, 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,
|
|
3
|
-
import { resolvedGrammarFiles, grammarSetVersion, canonicalize } from './chunk-
|
|
4
|
-
import { loadAdvancedScanHook, baselineCommand } from './chunk-
|
|
5
|
-
import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-
|
|
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, 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, verifyDeterminism, renderHtml, isModelReady } from './chunk-D4BPWARI.js';
|
|
3
|
+
import { resolvedGrammarFiles, grammarSetVersion, canonicalize } from './chunk-VFO5UDAT.js';
|
|
4
|
+
import { loadAdvancedScanHook, baselineCommand } from './chunk-6DKWB2OI.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-C5AVF7PT.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 fs7 from 'fs';
|
|
9
|
+
import fs7__default, { realpathSync } from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import path__default from 'path';
|
|
12
|
-
import { fileURLToPath } from 'url';
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
13
13
|
import { Command, CommanderError, Option } from 'commander';
|
|
14
14
|
import { execSync, spawn, spawnSync, execFileSync, execFile } from 'child_process';
|
|
15
15
|
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 fs13 from 'fs/promises';
|
|
19
20
|
import * as readline from 'readline';
|
|
20
|
-
import * as fs15 from 'fs/promises';
|
|
21
21
|
import semver from 'semver';
|
|
22
22
|
|
|
23
23
|
var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
|
|
@@ -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 (!fs7.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
|
+
fs7.mkdirSync(path.dirname(keyPath), { recursive: true });
|
|
187
|
+
fs7.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
|
|
188
|
+
fs7.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(fs7.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
|
+
fs7.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
211
|
+
fs7.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 (!fs7.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(fs7.readFileSync(bundlePath, "utf8"));
|
|
236
|
+
const publicKeyPem = opts.pub ? fs7.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
|
}
|
|
@@ -245,15 +245,15 @@ async function gitCommitInfo(root) {
|
|
|
245
245
|
return { sha, shortSha: sha.slice(0, 7), branch, dirty: dirty === true ? true : dirty === false ? false : void 0 };
|
|
246
246
|
}
|
|
247
247
|
function gitBranch(root) {
|
|
248
|
-
return new Promise((
|
|
248
|
+
return new Promise((resolve17) => {
|
|
249
249
|
execFile(
|
|
250
250
|
"git",
|
|
251
251
|
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
252
252
|
{ cwd: root, timeout: 5e3, windowsHide: true, encoding: "utf8" },
|
|
253
253
|
(err, stdout) => {
|
|
254
|
-
if (err) return
|
|
254
|
+
if (err) return resolve17(void 0);
|
|
255
255
|
const name = String(stdout).trim();
|
|
256
|
-
|
|
256
|
+
resolve17(name && name !== "HEAD" ? name : void 0);
|
|
257
257
|
}
|
|
258
258
|
);
|
|
259
259
|
});
|
|
@@ -584,8 +584,8 @@ function writeExport(graph, target) {
|
|
|
584
584
|
ExitCode.USAGE_ERROR
|
|
585
585
|
);
|
|
586
586
|
}
|
|
587
|
-
|
|
588
|
-
|
|
587
|
+
fs7.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
|
|
588
|
+
fs7.writeFileSync(target, content);
|
|
589
589
|
}
|
|
590
590
|
function collect(value, prev) {
|
|
591
591
|
return [...prev, value];
|
|
@@ -604,7 +604,7 @@ async function runStatus(global) {
|
|
|
604
604
|
const root = path.resolve(global.cwd ?? ".");
|
|
605
605
|
const graphPath = global.graph ?? defaultGraphPath(root);
|
|
606
606
|
const graph = loadGraph(root, graphPath);
|
|
607
|
-
const hasCache =
|
|
607
|
+
const hasCache = fs7.existsSync(path.join(cacheDir(root), "parse-cache.json"));
|
|
608
608
|
let stale = null;
|
|
609
609
|
let staleExact = false;
|
|
610
610
|
if (graph) {
|
|
@@ -1289,14 +1289,14 @@ var INSTALL_ID_FILE = path.join(INSTALL_ID_DIR, "install-id");
|
|
|
1289
1289
|
var SEND_TIMEOUT_MS = 4e3;
|
|
1290
1290
|
function installId() {
|
|
1291
1291
|
try {
|
|
1292
|
-
const existing =
|
|
1292
|
+
const existing = fs7.readFileSync(INSTALL_ID_FILE, "utf8").trim();
|
|
1293
1293
|
if (isUuid(existing)) return existing;
|
|
1294
1294
|
} catch {
|
|
1295
1295
|
}
|
|
1296
1296
|
const id = randomUuid();
|
|
1297
1297
|
try {
|
|
1298
|
-
|
|
1299
|
-
|
|
1298
|
+
fs7.mkdirSync(INSTALL_ID_DIR, { recursive: true });
|
|
1299
|
+
fs7.writeFileSync(INSTALL_ID_FILE, id + "\n", { mode: 384 });
|
|
1300
1300
|
} catch {
|
|
1301
1301
|
}
|
|
1302
1302
|
return id;
|
|
@@ -1320,7 +1320,7 @@ function ledgerFilePath(root) {
|
|
|
1320
1320
|
}
|
|
1321
1321
|
function readOffset(root) {
|
|
1322
1322
|
try {
|
|
1323
|
-
const raw = JSON.parse(
|
|
1323
|
+
const raw = JSON.parse(fs7.readFileSync(offsetPath(root), "utf8"));
|
|
1324
1324
|
return typeof raw.offset === "number" && raw.offset >= 0 ? raw.offset : 0;
|
|
1325
1325
|
} catch {
|
|
1326
1326
|
return 0;
|
|
@@ -1328,8 +1328,8 @@ function readOffset(root) {
|
|
|
1328
1328
|
}
|
|
1329
1329
|
function writeOffset(root, offset) {
|
|
1330
1330
|
try {
|
|
1331
|
-
|
|
1332
|
-
|
|
1331
|
+
fs7.mkdirSync(cacheDir(root), { recursive: true });
|
|
1332
|
+
fs7.writeFileSync(offsetPath(root), JSON.stringify({ offset }) + "\n");
|
|
1333
1333
|
} catch {
|
|
1334
1334
|
}
|
|
1335
1335
|
}
|
|
@@ -1337,7 +1337,7 @@ function buildBatch(root, fromOffset, id = installId()) {
|
|
|
1337
1337
|
const file = ledgerFilePath(root);
|
|
1338
1338
|
let size = 0;
|
|
1339
1339
|
try {
|
|
1340
|
-
size =
|
|
1340
|
+
size = fs7.statSync(file).size;
|
|
1341
1341
|
} catch {
|
|
1342
1342
|
return { batch: null, newOffset: 0 };
|
|
1343
1343
|
}
|
|
@@ -1345,13 +1345,13 @@ function buildBatch(root, fromOffset, id = installId()) {
|
|
|
1345
1345
|
if (start >= size) return { batch: null, newOffset: size };
|
|
1346
1346
|
let text = "";
|
|
1347
1347
|
try {
|
|
1348
|
-
const fd =
|
|
1348
|
+
const fd = fs7.openSync(file, "r");
|
|
1349
1349
|
try {
|
|
1350
1350
|
const buf = Buffer.alloc(size - start);
|
|
1351
|
-
|
|
1351
|
+
fs7.readSync(fd, buf, 0, buf.length, start);
|
|
1352
1352
|
text = buf.toString("utf8");
|
|
1353
1353
|
} finally {
|
|
1354
|
-
|
|
1354
|
+
fs7.closeSync(fd);
|
|
1355
1355
|
}
|
|
1356
1356
|
} catch {
|
|
1357
1357
|
return { batch: null, newOffset: fromOffset };
|
|
@@ -1497,7 +1497,7 @@ function registerServe(program) {
|
|
|
1497
1497
|
}
|
|
1498
1498
|
async function ensureServableGraph(root, graphPath, global, refresh, opts = {}) {
|
|
1499
1499
|
if (refresh) {
|
|
1500
|
-
if (!
|
|
1500
|
+
if (!fs7.existsSync(graphPath)) {
|
|
1501
1501
|
info(c.dim("vg \xB7 no map found \u2014 building it before serving\u2026"));
|
|
1502
1502
|
await runBuild(
|
|
1503
1503
|
[],
|
|
@@ -1514,7 +1514,7 @@ async function ensureServableGraph(root, graphPath, global, refresh, opts = {})
|
|
|
1514
1514
|
}
|
|
1515
1515
|
}
|
|
1516
1516
|
}
|
|
1517
|
-
if (!
|
|
1517
|
+
if (!fs7.existsSync(graphPath)) {
|
|
1518
1518
|
throw new CliError(
|
|
1519
1519
|
`no map found at ${graphPath} \u2014 run \`vg\` to build one first`,
|
|
1520
1520
|
ExitCode.NOT_FOUND
|
|
@@ -1569,12 +1569,587 @@ async function serveHttp(graphPath, host, port, opts, freshness) {
|
|
|
1569
1569
|
info(c.red(`vg serve: request error: ${err.message}`));
|
|
1570
1570
|
}
|
|
1571
1571
|
});
|
|
1572
|
-
await new Promise((
|
|
1572
|
+
await new Promise((resolve17) => httpServer.listen(port, host, resolve17));
|
|
1573
1573
|
info(c.dim(`vg \xB7 MCP server on http://${host}:${port}/mcp (read-only, local, ${freshness})`));
|
|
1574
1574
|
await new Promise(() => {
|
|
1575
1575
|
});
|
|
1576
1576
|
}
|
|
1577
1577
|
|
|
1578
|
+
// src/lsp/protocol.ts
|
|
1579
|
+
var ErrorCodes = {
|
|
1580
|
+
MethodNotFound: -32601,
|
|
1581
|
+
InternalError: -32603
|
|
1582
|
+
};
|
|
1583
|
+
var MessageReader = class {
|
|
1584
|
+
buf = Buffer.alloc(0);
|
|
1585
|
+
/** Feed a chunk; returns every complete message now available. */
|
|
1586
|
+
push(chunk) {
|
|
1587
|
+
this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);
|
|
1588
|
+
const out2 = [];
|
|
1589
|
+
for (; ; ) {
|
|
1590
|
+
const headerEnd = this.buf.indexOf("\r\n\r\n");
|
|
1591
|
+
if (headerEnd === -1) break;
|
|
1592
|
+
const header = this.buf.subarray(0, headerEnd).toString("ascii");
|
|
1593
|
+
const match = /content-length:\s*(\d+)/i.exec(header);
|
|
1594
|
+
if (!match) {
|
|
1595
|
+
this.buf = this.buf.subarray(headerEnd + 4);
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
const length = Number(match[1]);
|
|
1599
|
+
const bodyStart = headerEnd + 4;
|
|
1600
|
+
if (this.buf.length < bodyStart + length) break;
|
|
1601
|
+
const body = this.buf.subarray(bodyStart, bodyStart + length).toString("utf8");
|
|
1602
|
+
this.buf = this.buf.subarray(bodyStart + length);
|
|
1603
|
+
try {
|
|
1604
|
+
out2.push(JSON.parse(body));
|
|
1605
|
+
} catch {
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return out2;
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
var MessageWriter = class {
|
|
1612
|
+
constructor(out2) {
|
|
1613
|
+
this.out = out2;
|
|
1614
|
+
}
|
|
1615
|
+
out;
|
|
1616
|
+
write(msg) {
|
|
1617
|
+
const body = Buffer.from(JSON.stringify(msg), "utf8");
|
|
1618
|
+
this.out.write(`Content-Length: ${body.length}\r
|
|
1619
|
+
\r
|
|
1620
|
+
`);
|
|
1621
|
+
this.out.write(body);
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
var Connection = class {
|
|
1625
|
+
reader = new MessageReader();
|
|
1626
|
+
writer;
|
|
1627
|
+
requests = /* @__PURE__ */ new Map();
|
|
1628
|
+
notifications = /* @__PURE__ */ new Map();
|
|
1629
|
+
constructor(input = process.stdin, output = process.stdout) {
|
|
1630
|
+
this.writer = new MessageWriter(output);
|
|
1631
|
+
input.on("data", (chunk) => {
|
|
1632
|
+
for (const msg of this.reader.push(chunk)) void this.dispatch(msg);
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
onRequest(method, handler) {
|
|
1636
|
+
this.requests.set(method, handler);
|
|
1637
|
+
}
|
|
1638
|
+
onNotification(method, handler) {
|
|
1639
|
+
this.notifications.set(method, handler);
|
|
1640
|
+
}
|
|
1641
|
+
/** Push a server→client notification (diagnostics, `vibgrate/score`, …). */
|
|
1642
|
+
notify(method, params) {
|
|
1643
|
+
this.writer.write({ jsonrpc: "2.0", method, params });
|
|
1644
|
+
}
|
|
1645
|
+
async dispatch(msg) {
|
|
1646
|
+
if (!msg.method) return;
|
|
1647
|
+
const isRequest = msg.id !== void 0 && msg.id !== null;
|
|
1648
|
+
if (!isRequest) {
|
|
1649
|
+
const handler2 = this.notifications.get(msg.method);
|
|
1650
|
+
if (handler2) {
|
|
1651
|
+
try {
|
|
1652
|
+
await handler2(msg.method, msg.params);
|
|
1653
|
+
} catch {
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
const handler = this.requests.get(msg.method);
|
|
1659
|
+
if (!handler) {
|
|
1660
|
+
this.writer.write({
|
|
1661
|
+
jsonrpc: "2.0",
|
|
1662
|
+
id: msg.id,
|
|
1663
|
+
error: { code: ErrorCodes.MethodNotFound, message: `Unhandled method: ${msg.method}` }
|
|
1664
|
+
});
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
try {
|
|
1668
|
+
const result = await handler(msg.method, msg.params);
|
|
1669
|
+
this.writer.write({ jsonrpc: "2.0", id: msg.id, result: result ?? null });
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
this.writer.write({
|
|
1672
|
+
jsonrpc: "2.0",
|
|
1673
|
+
id: msg.id,
|
|
1674
|
+
error: {
|
|
1675
|
+
code: ErrorCodes.InternalError,
|
|
1676
|
+
message: err instanceof Error ? err.message : "Internal error"
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
// src/lsp/manifest-positions.ts
|
|
1684
|
+
function manifestKind(filePath) {
|
|
1685
|
+
const base = (filePath.split(/[\\/]/).pop() ?? "").toLowerCase();
|
|
1686
|
+
if (base === "package.json") return "package.json";
|
|
1687
|
+
if (base === "go.mod") return "go.mod";
|
|
1688
|
+
if (base === "requirements.txt" || /^requirements.*\.txt$/.test(base)) return "requirements.txt";
|
|
1689
|
+
if (base === "pyproject.toml") return "pyproject.toml";
|
|
1690
|
+
if (base === "cargo.toml") return "Cargo.toml";
|
|
1691
|
+
if (base === "pom.xml") return "pom.xml";
|
|
1692
|
+
if (base === "build.gradle" || base === "build.gradle.kts") return "build.gradle";
|
|
1693
|
+
if (base === "gemfile") return "Gemfile";
|
|
1694
|
+
if (base === "composer.json") return "composer.json";
|
|
1695
|
+
if (base === "pubspec.yaml") return "pubspec.yaml";
|
|
1696
|
+
if (base === "mix.exs") return "mix.exs";
|
|
1697
|
+
if (base.endsWith(".csproj") || base.endsWith(".fsproj") || base.endsWith(".vbproj")) return "csproj";
|
|
1698
|
+
if (base === "dockerfile" || base.startsWith("dockerfile.")) return "Dockerfile";
|
|
1699
|
+
return "unknown";
|
|
1700
|
+
}
|
|
1701
|
+
function isManifest(filePath) {
|
|
1702
|
+
return manifestKind(filePath) !== "unknown";
|
|
1703
|
+
}
|
|
1704
|
+
function esc(s) {
|
|
1705
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1706
|
+
}
|
|
1707
|
+
function findPackageLine(text, pkg, kind) {
|
|
1708
|
+
const lines = text.split(/\r?\n/);
|
|
1709
|
+
const p = esc(pkg);
|
|
1710
|
+
const patterns = (() => {
|
|
1711
|
+
switch (kind) {
|
|
1712
|
+
case "package.json":
|
|
1713
|
+
case "composer.json":
|
|
1714
|
+
return [new RegExp(`^\\s*"${p}"\\s*:`)];
|
|
1715
|
+
case "go.mod":
|
|
1716
|
+
return [new RegExp(`^\\s*(?:require\\s+)?${p}\\s+v?\\d`)];
|
|
1717
|
+
case "requirements.txt":
|
|
1718
|
+
return [new RegExp(`^\\s*${p}\\s*(?:\\[[^\\]]*\\])?\\s*(?:[=<>!~]|$)`, "i")];
|
|
1719
|
+
case "pyproject.toml":
|
|
1720
|
+
case "Cargo.toml":
|
|
1721
|
+
return [
|
|
1722
|
+
new RegExp(`^\\s*${p}\\s*=`),
|
|
1723
|
+
new RegExp(`^\\s*"${p}\\s*(?:[=<>!~\\[]|")`)
|
|
1724
|
+
];
|
|
1725
|
+
case "pom.xml":
|
|
1726
|
+
return [new RegExp(`<artifactId>\\s*${p}\\s*</artifactId>`)];
|
|
1727
|
+
case "build.gradle":
|
|
1728
|
+
return [new RegExp(`["']${p}(?:["':])`)];
|
|
1729
|
+
case "Gemfile":
|
|
1730
|
+
return [new RegExp(`^\\s*gem\\s+["']${p}["']`)];
|
|
1731
|
+
case "pubspec.yaml":
|
|
1732
|
+
return [new RegExp(`^\\s*${p}\\s*:`)];
|
|
1733
|
+
case "mix.exs":
|
|
1734
|
+
return [new RegExp(`\\{\\s*:${p}\\s*,`)];
|
|
1735
|
+
case "csproj":
|
|
1736
|
+
return [new RegExp(`Include\\s*=\\s*"${p}"`, "i")];
|
|
1737
|
+
case "Dockerfile":
|
|
1738
|
+
return [new RegExp(`^\\s*FROM\\s+.*${p}`, "i")];
|
|
1739
|
+
default:
|
|
1740
|
+
return [];
|
|
1741
|
+
}
|
|
1742
|
+
})();
|
|
1743
|
+
for (const re of patterns) {
|
|
1744
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1745
|
+
if (re.test(lines[i])) return i;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
return -1;
|
|
1749
|
+
}
|
|
1750
|
+
function findRuntimeLine(text, kind) {
|
|
1751
|
+
const lines = text.split(/\r?\n/);
|
|
1752
|
+
const patterns = (() => {
|
|
1753
|
+
switch (kind) {
|
|
1754
|
+
case "package.json":
|
|
1755
|
+
return [/^\s*"node"\s*:/, /^\s*"engines"\s*:/];
|
|
1756
|
+
case "go.mod":
|
|
1757
|
+
return [/^\s*go\s+\d/];
|
|
1758
|
+
case "pyproject.toml":
|
|
1759
|
+
return [/^\s*(?:requires-python|python)\s*=/];
|
|
1760
|
+
case "pom.xml":
|
|
1761
|
+
return [/<(?:java\.version|maven\.compiler\.(?:source|target|release))>/];
|
|
1762
|
+
case "build.gradle":
|
|
1763
|
+
return [/(?:sourceCompatibility|targetCompatibility|JavaVersion)/];
|
|
1764
|
+
case "csproj":
|
|
1765
|
+
return [/<TargetFrameworks?>/i];
|
|
1766
|
+
case "Dockerfile":
|
|
1767
|
+
return [/^\s*FROM\s+/i];
|
|
1768
|
+
default:
|
|
1769
|
+
return [];
|
|
1770
|
+
}
|
|
1771
|
+
})();
|
|
1772
|
+
for (const re of patterns) {
|
|
1773
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1774
|
+
if (re.test(lines[i])) return i;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return -1;
|
|
1778
|
+
}
|
|
1779
|
+
function endOfLine(text, line) {
|
|
1780
|
+
const lines = text.split(/\r?\n/);
|
|
1781
|
+
return (lines[line] ?? "").length;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// src/lsp/server.ts
|
|
1785
|
+
function bandForDependency(dep) {
|
|
1786
|
+
if (dep.drift === "major-behind") {
|
|
1787
|
+
return (dep.majorsBehind ?? 1) >= 2 ? "high" : "moderate";
|
|
1788
|
+
}
|
|
1789
|
+
if (dep.drift === "minor-behind") return "moderate";
|
|
1790
|
+
return "low";
|
|
1791
|
+
}
|
|
1792
|
+
function severityFor(level) {
|
|
1793
|
+
if (level === "note") return 3;
|
|
1794
|
+
return 2;
|
|
1795
|
+
}
|
|
1796
|
+
var DIAGNOSTIC_RULES = /* @__PURE__ */ new Set([
|
|
1797
|
+
"vibgrate/runtime-eol",
|
|
1798
|
+
"vibgrate/runtime-lag",
|
|
1799
|
+
"vibgrate/unmaintained",
|
|
1800
|
+
"vibgrate/abandoned",
|
|
1801
|
+
"vibgrate/license-change",
|
|
1802
|
+
"vibgrate/eol"
|
|
1803
|
+
]);
|
|
1804
|
+
var VibgrateLanguageServer = class {
|
|
1805
|
+
constructor(opts, output = process.stdout) {
|
|
1806
|
+
this.opts = opts;
|
|
1807
|
+
this.conn = new Connection(process.stdin, output);
|
|
1808
|
+
this.register();
|
|
1809
|
+
}
|
|
1810
|
+
opts;
|
|
1811
|
+
conn;
|
|
1812
|
+
docs = /* @__PURE__ */ new Map();
|
|
1813
|
+
// uri → text
|
|
1814
|
+
artifact = null;
|
|
1815
|
+
scanning = false;
|
|
1816
|
+
queued = false;
|
|
1817
|
+
debounce = null;
|
|
1818
|
+
shuttingDown = false;
|
|
1819
|
+
register() {
|
|
1820
|
+
this.conn.onRequest("initialize", () => ({
|
|
1821
|
+
capabilities: {
|
|
1822
|
+
// 1 = Full sync. We only actually act on save, but full sync keeps our
|
|
1823
|
+
// buffer honest so hovers land on the text the user is looking at.
|
|
1824
|
+
textDocumentSync: 1,
|
|
1825
|
+
hoverProvider: true,
|
|
1826
|
+
codeLensProvider: { resolveProvider: false },
|
|
1827
|
+
executeCommandProvider: {
|
|
1828
|
+
commands: ["vibgrate.rescan", "vibgrate.explain", "vibgrate.routeFix"]
|
|
1829
|
+
}
|
|
1830
|
+
},
|
|
1831
|
+
serverInfo: { name: "Vibgrate", version: VERSION }
|
|
1832
|
+
}));
|
|
1833
|
+
this.conn.onNotification("initialized", () => this.scheduleScan(0));
|
|
1834
|
+
this.conn.onNotification("textDocument/didOpen", (_m, params) => {
|
|
1835
|
+
const p = params;
|
|
1836
|
+
const uri = p.textDocument?.uri;
|
|
1837
|
+
if (!uri || !isManifest(uriToPath(uri))) return;
|
|
1838
|
+
this.docs.set(uri, p.textDocument?.text ?? "");
|
|
1839
|
+
if (this.artifact) this.publishForDoc(uri);
|
|
1840
|
+
else this.scheduleScan(0);
|
|
1841
|
+
});
|
|
1842
|
+
this.conn.onNotification("textDocument/didChange", (_m, params) => {
|
|
1843
|
+
const p = params;
|
|
1844
|
+
const uri = p.textDocument?.uri;
|
|
1845
|
+
if (!uri || !this.docs.has(uri)) return;
|
|
1846
|
+
const full = p.contentChanges?.[p.contentChanges.length - 1]?.text;
|
|
1847
|
+
if (typeof full === "string") this.docs.set(uri, full);
|
|
1848
|
+
if (this.artifact) this.publishForDoc(uri);
|
|
1849
|
+
});
|
|
1850
|
+
this.conn.onNotification("textDocument/didSave", (_m, params) => {
|
|
1851
|
+
const p = params;
|
|
1852
|
+
const uri = p.textDocument?.uri;
|
|
1853
|
+
if (!uri || !this.docs.has(uri)) return;
|
|
1854
|
+
this.scheduleScan(400);
|
|
1855
|
+
});
|
|
1856
|
+
this.conn.onNotification("textDocument/didClose", (_m, params) => {
|
|
1857
|
+
const p = params;
|
|
1858
|
+
if (p.textDocument?.uri) this.docs.delete(p.textDocument.uri);
|
|
1859
|
+
});
|
|
1860
|
+
this.conn.onRequest("textDocument/hover", (_m, params) => this.onHover(params));
|
|
1861
|
+
this.conn.onRequest("textDocument/codeLens", (_m, params) => this.onCodeLens(params));
|
|
1862
|
+
this.conn.onRequest("workspace/executeCommand", (_m, params) => {
|
|
1863
|
+
const p = params;
|
|
1864
|
+
if (p.command === "vibgrate.rescan") {
|
|
1865
|
+
this.scheduleScan(0);
|
|
1866
|
+
return { ok: true };
|
|
1867
|
+
}
|
|
1868
|
+
return null;
|
|
1869
|
+
});
|
|
1870
|
+
this.conn.onRequest("shutdown", () => {
|
|
1871
|
+
this.shuttingDown = true;
|
|
1872
|
+
if (this.debounce) clearTimeout(this.debounce);
|
|
1873
|
+
return null;
|
|
1874
|
+
});
|
|
1875
|
+
this.conn.onNotification("exit", () => {
|
|
1876
|
+
process.exit(this.shuttingDown ? 0 : 1);
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
// ── Scanning ─────────────────────────────────────────────────────────────
|
|
1880
|
+
scheduleScan(delayMs) {
|
|
1881
|
+
if (this.debounce) clearTimeout(this.debounce);
|
|
1882
|
+
this.debounce = setTimeout(() => void this.scan(), delayMs);
|
|
1883
|
+
}
|
|
1884
|
+
async scan() {
|
|
1885
|
+
if (this.scanning) {
|
|
1886
|
+
this.queued = true;
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
this.scanning = true;
|
|
1890
|
+
try {
|
|
1891
|
+
const scanOpts = {
|
|
1892
|
+
format: "json",
|
|
1893
|
+
concurrency: 8,
|
|
1894
|
+
offline: this.opts.offline,
|
|
1895
|
+
noLocalArtifacts: true,
|
|
1896
|
+
// an editor scan must not litter the repo
|
|
1897
|
+
noGraph: true,
|
|
1898
|
+
// the code map is not needed to score drift; skip the cost
|
|
1899
|
+
quiet: true,
|
|
1900
|
+
// no spinners, no artifact dump — an editor is not a terminal
|
|
1901
|
+
vibgrateVersion: VERSION
|
|
1902
|
+
};
|
|
1903
|
+
const advanced = await loadAdvancedScanHook();
|
|
1904
|
+
this.artifact = await runCoreScan(this.opts.root, scanOpts, advanced);
|
|
1905
|
+
this.publishScore();
|
|
1906
|
+
for (const uri of this.docs.keys()) this.publishForDoc(uri);
|
|
1907
|
+
} catch (err) {
|
|
1908
|
+
this.conn.notify("window/logMessage", {
|
|
1909
|
+
type: 3,
|
|
1910
|
+
message: `Vibgrate scan failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1911
|
+
});
|
|
1912
|
+
this.conn.notify("vibgrate/status", { state: "error" });
|
|
1913
|
+
} finally {
|
|
1914
|
+
this.scanning = false;
|
|
1915
|
+
if (this.queued) {
|
|
1916
|
+
this.queued = false;
|
|
1917
|
+
this.scheduleScan(0);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
// ── Publishing ───────────────────────────────────────────────────────────
|
|
1922
|
+
publishScore() {
|
|
1923
|
+
const a = this.artifact;
|
|
1924
|
+
if (!a) return;
|
|
1925
|
+
let behind = 0;
|
|
1926
|
+
let unmaintained = 0;
|
|
1927
|
+
let total = 0;
|
|
1928
|
+
for (const proj of a.projects ?? []) {
|
|
1929
|
+
for (const dep of proj.dependencies ?? []) {
|
|
1930
|
+
total++;
|
|
1931
|
+
if (dep.drift === "major-behind" || dep.drift === "minor-behind") behind++;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
const eol = (a.findings ?? []).filter(
|
|
1935
|
+
(f) => f.ruleId === "vibgrate/runtime-eol" || f.ruleId === "vibgrate/eol"
|
|
1936
|
+
).length;
|
|
1937
|
+
unmaintained = (a.findings ?? []).filter(
|
|
1938
|
+
(f) => f.ruleId === "vibgrate/unmaintained" || f.ruleId === "vibgrate/abandoned"
|
|
1939
|
+
).length;
|
|
1940
|
+
const band = a.drift.riskLevel ?? "low";
|
|
1941
|
+
const payload = {
|
|
1942
|
+
score: a.drift.score,
|
|
1943
|
+
band,
|
|
1944
|
+
// v3 §2.4: `estimated` means "no timestamps at all" — it is NOT an
|
|
1945
|
+
// offline marker. An air-gapped scan against a dated snapshot is Verified.
|
|
1946
|
+
mode: hasReleaseDates(a) ? "verified" : "estimated",
|
|
1947
|
+
methodology: a.drift.methodologyVersion ?? "unknown",
|
|
1948
|
+
scale: "0 best, 100 worst",
|
|
1949
|
+
counts: { behind, eol, unmaintained, total },
|
|
1950
|
+
rootPath: a.rootPath,
|
|
1951
|
+
scannedAt: a.timestamp
|
|
1952
|
+
};
|
|
1953
|
+
this.conn.notify("vibgrate/score", payload);
|
|
1954
|
+
}
|
|
1955
|
+
/** Decorations + (optionally) diagnostics for one open manifest. */
|
|
1956
|
+
publishForDoc(uri) {
|
|
1957
|
+
const a = this.artifact;
|
|
1958
|
+
const text = this.docs.get(uri);
|
|
1959
|
+
if (!a || text === void 0) return;
|
|
1960
|
+
const filePath = uriToPath(uri);
|
|
1961
|
+
const kind = manifestKind(filePath);
|
|
1962
|
+
const project = projectForFile(this.opts.root, a, filePath);
|
|
1963
|
+
const items = [];
|
|
1964
|
+
for (const dep of project?.dependencies ?? []) {
|
|
1965
|
+
if (dep.drift === "current" || dep.drift === "unknown") continue;
|
|
1966
|
+
const line = findPackageLine(text, dep.package, kind);
|
|
1967
|
+
if (line === -1) continue;
|
|
1968
|
+
items.push({
|
|
1969
|
+
line,
|
|
1970
|
+
endCol: endOfLine(text, line),
|
|
1971
|
+
text: inlineLabel(dep),
|
|
1972
|
+
band: bandForDependency(dep),
|
|
1973
|
+
package: dep.package
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
this.conn.notify("vibgrate/inline", { uri, items });
|
|
1977
|
+
if (!this.opts.diagnostics) {
|
|
1978
|
+
this.conn.notify("textDocument/publishDiagnostics", { uri, diagnostics: [] });
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
const diagnostics = (a.findings ?? []).filter((f) => DIAGNOSTIC_RULES.has(f.ruleId)).filter((f) => project && sameProject(f.location, project.path)).map((f) => {
|
|
1982
|
+
const line = Math.max(0, findRuntimeLine(text, kind));
|
|
1983
|
+
return {
|
|
1984
|
+
range: {
|
|
1985
|
+
start: { line, character: 0 },
|
|
1986
|
+
end: { line, character: endOfLine(text, line) }
|
|
1987
|
+
},
|
|
1988
|
+
severity: severityFor(f.level),
|
|
1989
|
+
// never 1 — see file header
|
|
1990
|
+
source: "vibgrate",
|
|
1991
|
+
code: f.ruleId,
|
|
1992
|
+
message: f.message
|
|
1993
|
+
};
|
|
1994
|
+
});
|
|
1995
|
+
this.conn.notify("textDocument/publishDiagnostics", { uri, diagnostics });
|
|
1996
|
+
}
|
|
1997
|
+
// ── Requests ─────────────────────────────────────────────────────────────
|
|
1998
|
+
onHover(params) {
|
|
1999
|
+
const p = params;
|
|
2000
|
+
const uri = p.textDocument?.uri;
|
|
2001
|
+
const line = p.position?.line;
|
|
2002
|
+
const a = this.artifact;
|
|
2003
|
+
const text = uri ? this.docs.get(uri) : void 0;
|
|
2004
|
+
if (!uri || line === void 0 || !a || text === void 0) return null;
|
|
2005
|
+
const filePath = uriToPath(uri);
|
|
2006
|
+
const kind = manifestKind(filePath);
|
|
2007
|
+
const project = projectForFile(this.opts.root, a, filePath);
|
|
2008
|
+
const dep = (project?.dependencies ?? []).find(
|
|
2009
|
+
(d) => findPackageLine(text, d.package, kind) === line
|
|
2010
|
+
);
|
|
2011
|
+
if (!dep) return null;
|
|
2012
|
+
return {
|
|
2013
|
+
contents: { kind: "markdown", value: hoverMarkdown(dep) }
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
onCodeLens(params) {
|
|
2017
|
+
const p = params;
|
|
2018
|
+
const uri = p.textDocument?.uri;
|
|
2019
|
+
const a = this.artifact;
|
|
2020
|
+
if (!uri || !a || !this.docs.has(uri)) return [];
|
|
2021
|
+
const behind = (a.projects ?? []).reduce(
|
|
2022
|
+
(n, proj) => n + (proj.dependencies ?? []).filter(
|
|
2023
|
+
(d) => d.drift === "major-behind" || d.drift === "minor-behind"
|
|
2024
|
+
).length,
|
|
2025
|
+
0
|
|
2026
|
+
);
|
|
2027
|
+
const eol = (a.findings ?? []).filter(
|
|
2028
|
+
(f) => f.ruleId === "vibgrate/runtime-eol" || f.ruleId === "vibgrate/eol"
|
|
2029
|
+
).length;
|
|
2030
|
+
const mode = hasReleaseDates(a) ? "" : "~";
|
|
2031
|
+
const title = `Vibgrate \xB7 drift ${mode}${a.drift.score} (${a.drift.riskLevel}) \xB7 ${behind} behind \xB7 ${eol} EOL`;
|
|
2032
|
+
return [
|
|
2033
|
+
{
|
|
2034
|
+
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
|
|
2035
|
+
command: { title, command: "vibgrate.openPanel", arguments: [] }
|
|
2036
|
+
}
|
|
2037
|
+
];
|
|
2038
|
+
}
|
|
2039
|
+
};
|
|
2040
|
+
function uriToPath(uri) {
|
|
2041
|
+
try {
|
|
2042
|
+
return fileURLToPath(uri);
|
|
2043
|
+
} catch {
|
|
2044
|
+
return uri;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
function projectForFile(root, a, filePath) {
|
|
2048
|
+
const dir = path.dirname(path.resolve(filePath));
|
|
2049
|
+
let best;
|
|
2050
|
+
let bestLen = -1;
|
|
2051
|
+
for (const proj of a.projects ?? []) {
|
|
2052
|
+
const projDir = path.resolve(root, proj.path);
|
|
2053
|
+
if ((dir === projDir || dir.startsWith(projDir + path.sep)) && projDir.length > bestLen) {
|
|
2054
|
+
best = proj;
|
|
2055
|
+
bestLen = projDir.length;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
return best;
|
|
2059
|
+
}
|
|
2060
|
+
function sameProject(location, projectPath) {
|
|
2061
|
+
return path.normalize(location) === path.normalize(projectPath);
|
|
2062
|
+
}
|
|
2063
|
+
function hasReleaseDates(a) {
|
|
2064
|
+
for (const proj of a.projects ?? []) {
|
|
2065
|
+
for (const dep of proj.dependencies ?? []) {
|
|
2066
|
+
if (dep.ageDays !== null && dep.ageDays !== void 0) return true;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
function inlineLabel(dep) {
|
|
2072
|
+
const bits = [];
|
|
2073
|
+
if (dep.drift === "major-behind") {
|
|
2074
|
+
const n = dep.majorsBehind ?? 1;
|
|
2075
|
+
bits.push(`${n} major behind`);
|
|
2076
|
+
} else if (dep.drift === "minor-behind") {
|
|
2077
|
+
bits.push("minor behind");
|
|
2078
|
+
}
|
|
2079
|
+
if (dep.latestStable) bits.push(dep.latestStable);
|
|
2080
|
+
if (dep.ageDays != null && dep.ageDays >= 1) {
|
|
2081
|
+
bits.push(`${Math.round(dep.ageDays).toLocaleString("en-US")}d stale`);
|
|
2082
|
+
}
|
|
2083
|
+
return bits.join(" \xB7 ");
|
|
2084
|
+
}
|
|
2085
|
+
function hoverMarkdown(dep) {
|
|
2086
|
+
const lines = [];
|
|
2087
|
+
lines.push(`**${dep.package}**`);
|
|
2088
|
+
lines.push("");
|
|
2089
|
+
const yours = dep.resolvedVersion ?? dep.currentSpec;
|
|
2090
|
+
if (dep.latestStable && dep.latestStable !== yours) {
|
|
2091
|
+
lines.push(`| | |`);
|
|
2092
|
+
lines.push(`|---|---|`);
|
|
2093
|
+
lines.push(`| yours | \`${yours}\` |`);
|
|
2094
|
+
lines.push(`| latest | \`${dep.latestStable}\` |`);
|
|
2095
|
+
if (dep.majorsBehind) lines.push(`| majors behind | ${dep.majorsBehind} |`);
|
|
2096
|
+
if (dep.ageDays != null && dep.ageDays >= 1) {
|
|
2097
|
+
lines.push(`| age | ${Math.round(dep.ageDays).toLocaleString("en-US")} days |`);
|
|
2098
|
+
}
|
|
2099
|
+
if (dep.libyears != null && dep.libyears >= 0.01) {
|
|
2100
|
+
lines.push(`| libyears | ${dep.libyears.toFixed(2)} |`);
|
|
2101
|
+
}
|
|
2102
|
+
const license = dep.license?.spdxId ?? dep.license?.raw;
|
|
2103
|
+
if (license) lines.push(`| license | ${license} |`);
|
|
2104
|
+
} else {
|
|
2105
|
+
lines.push(`\`${yours}\` \u2014 current.`);
|
|
2106
|
+
}
|
|
2107
|
+
lines.push("");
|
|
2108
|
+
lines.push("$(shield) Resolved locally from your lockfile. No source uploaded.");
|
|
2109
|
+
return lines.join("\n");
|
|
2110
|
+
}
|
|
2111
|
+
function startLanguageServer(opts) {
|
|
2112
|
+
if (!fs7.existsSync(opts.root)) {
|
|
2113
|
+
process.stderr.write(`vg lsp: root does not exist: ${opts.root}
|
|
2114
|
+
`);
|
|
2115
|
+
process.exit(1);
|
|
2116
|
+
}
|
|
2117
|
+
const realWrite = process.stdout.write.bind(process.stdout);
|
|
2118
|
+
const protocolOut = {
|
|
2119
|
+
write(chunk) {
|
|
2120
|
+
return realWrite(chunk);
|
|
2121
|
+
}
|
|
2122
|
+
};
|
|
2123
|
+
process.stdout.write = ((chunk, ...rest) => process.stderr.write(chunk, ...rest));
|
|
2124
|
+
process.stderr.write(
|
|
2125
|
+
`vg lsp ${VERSION} \u2014 serving ${pathToFileURL(opts.root).href}${opts.offline ? " (offline)" : ""}
|
|
2126
|
+
`
|
|
2127
|
+
);
|
|
2128
|
+
new VibgrateLanguageServer(opts, protocolOut);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// src/commands/lsp.ts
|
|
2132
|
+
function registerLsp(program) {
|
|
2133
|
+
const cmd = program.command("lsp").description("start the Vibgrate language server (stdio) \u2014 the engine behind the IDE extensions").option(
|
|
2134
|
+
"--diagnostics",
|
|
2135
|
+
"also publish Problems-panel diagnostics (EOL runtime, unmaintained, license change). Off by default: drift is not a defect, and the Problems panel is not ours to fill",
|
|
2136
|
+
false
|
|
2137
|
+
).action(async function(opts) {
|
|
2138
|
+
const global = readGlobal(this);
|
|
2139
|
+
const root = rootOf(global);
|
|
2140
|
+
startLanguageServer({
|
|
2141
|
+
root,
|
|
2142
|
+
// `--local` is the established air-gap flag across the CLI; honour it
|
|
2143
|
+
// here too, so an offline editor session stays offline.
|
|
2144
|
+
offline: global.local === true,
|
|
2145
|
+
diagnostics: opts.diagnostics === true
|
|
2146
|
+
});
|
|
2147
|
+
await new Promise(() => {
|
|
2148
|
+
});
|
|
2149
|
+
});
|
|
2150
|
+
applyGlobalOptions(cmd);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
1578
2153
|
// src/commands/install.ts
|
|
1579
2154
|
var SMALL_REPO_FILES = 150;
|
|
1580
2155
|
function registerInstall(program) {
|
|
@@ -1593,7 +2168,7 @@ function registerInstall(program) {
|
|
|
1593
2168
|
}
|
|
1594
2169
|
return;
|
|
1595
2170
|
}
|
|
1596
|
-
const targets = opts.all ? ASSISTANTS : tools.map(
|
|
2171
|
+
const targets = opts.all ? ASSISTANTS : tools.map(resolve6);
|
|
1597
2172
|
if (targets.length === 0) throw usageError("name an assistant (e.g. `vg install claude`) or use --all / --list");
|
|
1598
2173
|
const graph = loadGraph(root, global.graph);
|
|
1599
2174
|
const fileCount = graph ? graph.nodes.filter((n) => n.kind === "file").length : 0;
|
|
@@ -1617,7 +2192,7 @@ function registerInstall(program) {
|
|
|
1617
2192
|
const uninstall = program.command("uninstall").description("remove vg from an AI assistant").argument("<tools...>", "assistant ids").option("--purge", "also delete the skill file").action(function(tools, opts) {
|
|
1618
2193
|
const global = readGlobal(this);
|
|
1619
2194
|
const root = rootOf(global);
|
|
1620
|
-
const results = tools.map(
|
|
2195
|
+
const results = tools.map(resolve6).map((a) => ({ id: a.id, removed: uninstallAssistant(a, root, !!opts.purge) }));
|
|
1621
2196
|
if (global.json) {
|
|
1622
2197
|
json({ root, results });
|
|
1623
2198
|
return;
|
|
@@ -1628,7 +2203,7 @@ function registerInstall(program) {
|
|
|
1628
2203
|
});
|
|
1629
2204
|
applyGlobalOptions(uninstall);
|
|
1630
2205
|
}
|
|
1631
|
-
function
|
|
2206
|
+
function resolve6(id) {
|
|
1632
2207
|
const a = assistantById(id);
|
|
1633
2208
|
if (!a) {
|
|
1634
2209
|
const near = ASSISTANTS.filter(
|
|
@@ -1649,7 +2224,7 @@ function registerShare(program) {
|
|
|
1649
2224
|
const global = readGlobal(this);
|
|
1650
2225
|
const root = rootOf(global);
|
|
1651
2226
|
const gitDir = path.join(root, ".git");
|
|
1652
|
-
if (!
|
|
2227
|
+
if (!fs7.existsSync(gitDir)) {
|
|
1653
2228
|
throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
|
|
1654
2229
|
}
|
|
1655
2230
|
if (opts.undo) {
|
|
@@ -1671,22 +2246,22 @@ function registerShare(program) {
|
|
|
1671
2246
|
}
|
|
1672
2247
|
function writeVibgrateGitignore(root, reports) {
|
|
1673
2248
|
const dir = path.join(root, ".vibgrate");
|
|
1674
|
-
|
|
2249
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
1675
2250
|
const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl"];
|
|
1676
2251
|
if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
|
|
1677
|
-
|
|
2252
|
+
fs7.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
|
|
1678
2253
|
`);
|
|
1679
2254
|
}
|
|
1680
2255
|
function writeGitAttributes(root) {
|
|
1681
2256
|
const file = path.join(root, ".gitattributes");
|
|
1682
2257
|
const line = ".vibgrate/graph.json merge=vg";
|
|
1683
|
-
let content =
|
|
2258
|
+
let content = fs7.existsSync(file) ? fs7.readFileSync(file, "utf8") : "";
|
|
1684
2259
|
if (!content.split("\n").some((l) => l.trim() === line)) {
|
|
1685
2260
|
content = content.length ? `${content.replace(/\s*$/, "")}
|
|
1686
2261
|
${line}
|
|
1687
2262
|
` : `${line}
|
|
1688
2263
|
`;
|
|
1689
|
-
|
|
2264
|
+
fs7.writeFileSync(file, content);
|
|
1690
2265
|
}
|
|
1691
2266
|
}
|
|
1692
2267
|
function configureMergeDriver(root) {
|
|
@@ -1695,7 +2270,7 @@ function configureMergeDriver(root) {
|
|
|
1695
2270
|
}
|
|
1696
2271
|
function installPreCommitHook(root) {
|
|
1697
2272
|
const hooksDir = hookDir(root);
|
|
1698
|
-
|
|
2273
|
+
fs7.mkdirSync(hooksDir, { recursive: true });
|
|
1699
2274
|
const file = path.join(hooksDir, "pre-commit");
|
|
1700
2275
|
const block = [
|
|
1701
2276
|
HOOK_BEGIN,
|
|
@@ -1705,7 +2280,7 @@ function installPreCommitHook(root) {
|
|
|
1705
2280
|
"fi",
|
|
1706
2281
|
HOOK_END
|
|
1707
2282
|
].join("\n");
|
|
1708
|
-
let content =
|
|
2283
|
+
let content = fs7.existsSync(file) ? fs7.readFileSync(file, "utf8") : "#!/bin/sh\n";
|
|
1709
2284
|
const re = new RegExp(`${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}`);
|
|
1710
2285
|
if (re.test(content)) content = content.replace(re, block);
|
|
1711
2286
|
else content = `${content.replace(/\s*$/, "")}
|
|
@@ -1714,21 +2289,21 @@ ${block}
|
|
|
1714
2289
|
`;
|
|
1715
2290
|
if (!content.startsWith("#!")) content = `#!/bin/sh
|
|
1716
2291
|
${content}`;
|
|
1717
|
-
|
|
1718
|
-
|
|
2292
|
+
fs7.writeFileSync(file, content);
|
|
2293
|
+
fs7.chmodSync(file, 493);
|
|
1719
2294
|
}
|
|
1720
2295
|
function undo(root) {
|
|
1721
2296
|
const hookFile = path.join(hookDir(root), "pre-commit");
|
|
1722
|
-
if (
|
|
2297
|
+
if (fs7.existsSync(hookFile)) {
|
|
1723
2298
|
const re = new RegExp(`\\n*${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}\\n*`);
|
|
1724
|
-
const next2 =
|
|
1725
|
-
if (next2.trim() === "#!/bin/sh")
|
|
1726
|
-
else
|
|
2299
|
+
const next2 = fs7.readFileSync(hookFile, "utf8").replace(re, "\n");
|
|
2300
|
+
if (next2.trim() === "#!/bin/sh") fs7.rmSync(hookFile);
|
|
2301
|
+
else fs7.writeFileSync(hookFile, next2);
|
|
1727
2302
|
}
|
|
1728
2303
|
const attr = path.join(root, ".gitattributes");
|
|
1729
|
-
if (
|
|
1730
|
-
const next2 =
|
|
1731
|
-
|
|
2304
|
+
if (fs7.existsSync(attr)) {
|
|
2305
|
+
const next2 = fs7.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
|
|
2306
|
+
fs7.writeFileSync(attr, next2);
|
|
1732
2307
|
}
|
|
1733
2308
|
try {
|
|
1734
2309
|
git(root, ["config", "--remove-section", "merge.vg"]);
|
|
@@ -1861,7 +2436,7 @@ function grepBaselineTokens(term, files) {
|
|
|
1861
2436
|
if (f.size > 512 * 1024) continue;
|
|
1862
2437
|
let content = "";
|
|
1863
2438
|
try {
|
|
1864
|
-
content =
|
|
2439
|
+
content = fs7.readFileSync(f.abs, "utf8");
|
|
1865
2440
|
} catch {
|
|
1866
2441
|
continue;
|
|
1867
2442
|
}
|
|
@@ -1871,7 +2446,7 @@ function grepBaselineTokens(term, files) {
|
|
|
1871
2446
|
}
|
|
1872
2447
|
function safeSize(abs) {
|
|
1873
2448
|
try {
|
|
1874
|
-
return
|
|
2449
|
+
return fs7.statSync(abs).size;
|
|
1875
2450
|
} catch {
|
|
1876
2451
|
return 0;
|
|
1877
2452
|
}
|
|
@@ -2205,7 +2780,7 @@ function loadStandards(root) {
|
|
|
2205
2780
|
const p = path__default.join(root, rel3);
|
|
2206
2781
|
let text;
|
|
2207
2782
|
try {
|
|
2208
|
-
text =
|
|
2783
|
+
text = fs7__default.readFileSync(p, "utf8");
|
|
2209
2784
|
} catch {
|
|
2210
2785
|
continue;
|
|
2211
2786
|
}
|
|
@@ -2443,7 +3018,7 @@ async function addCmd(root, rest, opts, asJson) {
|
|
|
2443
3018
|
}
|
|
2444
3019
|
function readPackageVersion(root) {
|
|
2445
3020
|
try {
|
|
2446
|
-
const pkg = JSON.parse(
|
|
3021
|
+
const pkg = JSON.parse(fs7__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
|
|
2447
3022
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
2448
3023
|
} catch {
|
|
2449
3024
|
return void 0;
|
|
@@ -2451,7 +3026,7 @@ function readPackageVersion(root) {
|
|
|
2451
3026
|
}
|
|
2452
3027
|
function readOptionalFile(p) {
|
|
2453
3028
|
try {
|
|
2454
|
-
const t =
|
|
3029
|
+
const t = fs7__default.readFileSync(p, "utf8");
|
|
2455
3030
|
return t.trim() ? t : void 0;
|
|
2456
3031
|
} catch {
|
|
2457
3032
|
return void 0;
|
|
@@ -2535,7 +3110,8 @@ async function showCmd(root, name, budget, asJson, opts = {}) {
|
|
|
2535
3110
|
if (online && !quality.sufficient) {
|
|
2536
3111
|
const dsn = resolveDsn();
|
|
2537
3112
|
const parsed = dsn ? parseDsn$1(dsn) : null;
|
|
2538
|
-
hosted = await
|
|
3113
|
+
hosted = await fetchHostedDocsCached(
|
|
3114
|
+
root,
|
|
2539
3115
|
{ name: displayName, query: name, maxTokens: budget },
|
|
2540
3116
|
{ region: opts.region, ingest: opts.ingest, auth: parsed ? { keyId: parsed.keyId, secret: parsed.secret } : void 0 }
|
|
2541
3117
|
);
|
|
@@ -2596,8 +3172,8 @@ function registerExport(program) {
|
|
|
2596
3172
|
out(content.trimEnd());
|
|
2597
3173
|
return;
|
|
2598
3174
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
3175
|
+
fs7.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
|
|
3176
|
+
fs7.writeFileSync(file, content);
|
|
2601
3177
|
info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
|
|
2602
3178
|
});
|
|
2603
3179
|
applyGlobalOptions(cmd);
|
|
@@ -2620,24 +3196,24 @@ function registerBundle(program) {
|
|
|
2620
3196
|
} catch {
|
|
2621
3197
|
throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
|
|
2622
3198
|
}
|
|
2623
|
-
|
|
3199
|
+
fs7.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
|
|
2624
3200
|
let grammarCount = 0;
|
|
2625
3201
|
for (const { fileName, absPath } of grammarFiles) {
|
|
2626
|
-
|
|
3202
|
+
fs7.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
|
|
2627
3203
|
grammarCount++;
|
|
2628
3204
|
}
|
|
2629
3205
|
const included = [`grammars/ (${grammarCount} .wasm)`];
|
|
2630
3206
|
const graphSrc = global.graph ?? defaultGraphPath(root);
|
|
2631
|
-
if (
|
|
2632
|
-
|
|
3207
|
+
if (fs7.existsSync(graphSrc)) {
|
|
3208
|
+
fs7.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
|
|
2633
3209
|
included.push("graph.json");
|
|
2634
3210
|
}
|
|
2635
3211
|
const cat = catalogPath(root);
|
|
2636
|
-
if (
|
|
2637
|
-
|
|
3212
|
+
if (fs7.existsSync(cat)) {
|
|
3213
|
+
fs7.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
|
|
2638
3214
|
included.push("vibgrate.lib.json");
|
|
2639
|
-
if (
|
|
2640
|
-
|
|
3215
|
+
if (fs7.existsSync(libDir(root))) {
|
|
3216
|
+
fs7.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
|
|
2641
3217
|
included.push("lib/");
|
|
2642
3218
|
}
|
|
2643
3219
|
}
|
|
@@ -2649,7 +3225,7 @@ function registerBundle(program) {
|
|
|
2649
3225
|
included,
|
|
2650
3226
|
usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
|
|
2651
3227
|
};
|
|
2652
|
-
|
|
3228
|
+
fs7.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
|
|
2653
3229
|
`);
|
|
2654
3230
|
if (global.json) {
|
|
2655
3231
|
json({ out: path.relative(root, outDir), ...manifest });
|
|
@@ -2674,7 +3250,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
2674
3250
|
console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold("vibgrate.config.ts")}`);
|
|
2675
3251
|
}
|
|
2676
3252
|
if (opts.baseline) {
|
|
2677
|
-
const { runBaseline } = await import('./baseline-
|
|
3253
|
+
const { runBaseline } = await import('./baseline-BTLZWSNS.js');
|
|
2678
3254
|
await runBaseline(rootDir);
|
|
2679
3255
|
}
|
|
2680
3256
|
console.log("");
|
|
@@ -2683,6 +3259,270 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
2683
3259
|
console.log(` ${chalk8.cyan("vibgrate baseline")} Create a drift baseline`);
|
|
2684
3260
|
console.log("");
|
|
2685
3261
|
});
|
|
3262
|
+
var REACHABILITY_ANALYZER_VERSION = "vg-reach-1.0";
|
|
3263
|
+
var ECOSYSTEM_MATCH_CONFIDENCE = {
|
|
3264
|
+
npm: 0.8,
|
|
3265
|
+
Go: 0.8,
|
|
3266
|
+
"crates.io": 0.7,
|
|
3267
|
+
PyPI: 0.65,
|
|
3268
|
+
RubyGems: 0.6
|
|
3269
|
+
};
|
|
3270
|
+
var GENERIC_SYMBOL_NAMES = /* @__PURE__ */ new Set([
|
|
3271
|
+
"get",
|
|
3272
|
+
"set",
|
|
3273
|
+
"run",
|
|
3274
|
+
"main",
|
|
3275
|
+
"init",
|
|
3276
|
+
"call",
|
|
3277
|
+
"apply",
|
|
3278
|
+
"exec",
|
|
3279
|
+
"send",
|
|
3280
|
+
"open",
|
|
3281
|
+
"read",
|
|
3282
|
+
"write",
|
|
3283
|
+
"parse",
|
|
3284
|
+
"load",
|
|
3285
|
+
"save",
|
|
3286
|
+
"start",
|
|
3287
|
+
"stop",
|
|
3288
|
+
"new",
|
|
3289
|
+
"create",
|
|
3290
|
+
"update",
|
|
3291
|
+
"delete",
|
|
3292
|
+
"handle",
|
|
3293
|
+
"process",
|
|
3294
|
+
"render",
|
|
3295
|
+
"value",
|
|
3296
|
+
"data",
|
|
3297
|
+
"next"
|
|
3298
|
+
]);
|
|
3299
|
+
var MAX_FILES_PER_PACKAGE = 25;
|
|
3300
|
+
var MAX_FILE_BYTES = 256 * 1024;
|
|
3301
|
+
var MAX_MODULES_PER_PACKAGE = 50;
|
|
3302
|
+
var MAX_IMPORTED_MODULE_PACKAGES = 500;
|
|
3303
|
+
var MAX_FINDINGS = 1e3;
|
|
3304
|
+
function normalizePython(name) {
|
|
3305
|
+
return name.toLowerCase().replace(/[-.]/g, "_");
|
|
3306
|
+
}
|
|
3307
|
+
function specifierMatchesPackage(ecosystem, pkg, spec) {
|
|
3308
|
+
switch (ecosystem) {
|
|
3309
|
+
case "npm":
|
|
3310
|
+
case "Go":
|
|
3311
|
+
case "RubyGems":
|
|
3312
|
+
return spec === pkg || spec.startsWith(`${pkg}/`);
|
|
3313
|
+
case "PyPI": {
|
|
3314
|
+
const top = spec.split(".")[0] ?? spec;
|
|
3315
|
+
return normalizePython(top) === normalizePython(pkg);
|
|
3316
|
+
}
|
|
3317
|
+
case "crates.io": {
|
|
3318
|
+
const top = (spec.split("::")[0] ?? spec).toLowerCase().replace(/-/g, "_");
|
|
3319
|
+
return top === pkg.toLowerCase().replace(/-/g, "_");
|
|
3320
|
+
}
|
|
3321
|
+
default:
|
|
3322
|
+
return false;
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
function symbolIdentifier(symbol) {
|
|
3326
|
+
const cleaned = symbol.replace(/\(.*\)$/, "");
|
|
3327
|
+
const segments = cleaned.split(/[./:#]|::/).filter(Boolean);
|
|
3328
|
+
return segments[segments.length - 1] ?? cleaned;
|
|
3329
|
+
}
|
|
3330
|
+
function isMatchableIdentifier(identifier) {
|
|
3331
|
+
return identifier.length >= 3 && !GENERIC_SYMBOL_NAMES.has(identifier.toLowerCase());
|
|
3332
|
+
}
|
|
3333
|
+
function collectPackageImports(graph, ecosystem, pkg) {
|
|
3334
|
+
if (!(ecosystem in ECOSYSTEM_MATCH_CONFIDENCE)) {
|
|
3335
|
+
return { supported: false, modules: [], importingFiles: [] };
|
|
3336
|
+
}
|
|
3337
|
+
const idx = indexFor(graph);
|
|
3338
|
+
const modules = /* @__PURE__ */ new Set();
|
|
3339
|
+
const files = /* @__PURE__ */ new Set();
|
|
3340
|
+
for (const node of graph.nodes) {
|
|
3341
|
+
if (node.kind !== "external") continue;
|
|
3342
|
+
if (!specifierMatchesPackage(ecosystem, pkg, node.name)) continue;
|
|
3343
|
+
modules.add(node.name);
|
|
3344
|
+
for (const edge of idx.in(node.id, "import")) {
|
|
3345
|
+
const src = idx.nodeById.get(edge.src);
|
|
3346
|
+
if (src?.file) files.add(src.file);
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
return {
|
|
3350
|
+
supported: true,
|
|
3351
|
+
modules: [...modules].sort(),
|
|
3352
|
+
importingFiles: [...files].sort()
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
async function fileReferencesIdentifier(rootDir, relFile, identifier, readFile3) {
|
|
3356
|
+
const content = await readFile3(path.join(rootDir, relFile));
|
|
3357
|
+
if (!content) return false;
|
|
3358
|
+
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3359
|
+
return new RegExp(`\\b${escaped}\\b`).test(content);
|
|
3360
|
+
}
|
|
3361
|
+
async function defaultReadFile(absPath) {
|
|
3362
|
+
try {
|
|
3363
|
+
const stat2 = await fs13.stat(absPath);
|
|
3364
|
+
if (!stat2.isFile() || stat2.size > MAX_FILE_BYTES) return null;
|
|
3365
|
+
return await fs13.readFile(absPath, "utf8");
|
|
3366
|
+
} catch {
|
|
3367
|
+
return null;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
async function analyzeReachability(input) {
|
|
3371
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3372
|
+
const readFile3 = input.readFile ?? defaultReadFile;
|
|
3373
|
+
if (!input.graph) {
|
|
3374
|
+
return {
|
|
3375
|
+
analyzerVersion: REACHABILITY_ANALYZER_VERSION,
|
|
3376
|
+
source: "none",
|
|
3377
|
+
generatedAt,
|
|
3378
|
+
manifestAdvisoryCount: input.manifest.length,
|
|
3379
|
+
findings: input.manifest.slice(0, MAX_FINDINGS).map((entry) => ({
|
|
3380
|
+
advisoryId: entry.advisoryId,
|
|
3381
|
+
ecosystem: entry.ecosystem,
|
|
3382
|
+
package: entry.package,
|
|
3383
|
+
version: entry.version,
|
|
3384
|
+
tier: "unknown",
|
|
3385
|
+
evidence: "no code map available for this scan",
|
|
3386
|
+
graphConfidence: 0
|
|
3387
|
+
}))
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
const evidenceByPackage = /* @__PURE__ */ new Map();
|
|
3391
|
+
const importedModules = [];
|
|
3392
|
+
const seenPackages = /* @__PURE__ */ new Set();
|
|
3393
|
+
for (const dep of input.dependencies) {
|
|
3394
|
+
const key = `${dep.ecosystem}|${dep.package}`;
|
|
3395
|
+
if (seenPackages.has(key)) continue;
|
|
3396
|
+
seenPackages.add(key);
|
|
3397
|
+
const evidence = collectPackageImports(input.graph, dep.ecosystem, dep.package);
|
|
3398
|
+
evidenceByPackage.set(key, evidence);
|
|
3399
|
+
if (evidence.supported && importedModules.length < MAX_IMPORTED_MODULE_PACKAGES) {
|
|
3400
|
+
importedModules.push({
|
|
3401
|
+
ecosystem: dep.ecosystem,
|
|
3402
|
+
package: dep.package,
|
|
3403
|
+
modules: evidence.modules.slice(0, MAX_MODULES_PER_PACKAGE),
|
|
3404
|
+
importingFiles: evidence.importingFiles.length
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
importedModules.sort(
|
|
3409
|
+
(a, b) => a.ecosystem.localeCompare(b.ecosystem) || a.package.localeCompare(b.package)
|
|
3410
|
+
);
|
|
3411
|
+
const findings = [];
|
|
3412
|
+
for (const entry of input.manifest) {
|
|
3413
|
+
const key = `${entry.ecosystem}|${entry.package}`;
|
|
3414
|
+
const evidence = evidenceByPackage.get(key) ?? collectPackageImports(input.graph, entry.ecosystem, entry.package);
|
|
3415
|
+
const baseConfidence = ECOSYSTEM_MATCH_CONFIDENCE[entry.ecosystem] ?? 0;
|
|
3416
|
+
if (!evidence.supported) {
|
|
3417
|
+
findings.push({
|
|
3418
|
+
advisoryId: entry.advisoryId,
|
|
3419
|
+
ecosystem: entry.ecosystem,
|
|
3420
|
+
package: entry.package,
|
|
3421
|
+
version: entry.version,
|
|
3422
|
+
tier: "unknown",
|
|
3423
|
+
evidence: `import matching is not supported for the ${entry.ecosystem} ecosystem yet`,
|
|
3424
|
+
graphConfidence: 0
|
|
3425
|
+
});
|
|
3426
|
+
continue;
|
|
3427
|
+
}
|
|
3428
|
+
if (evidence.importingFiles.length === 0) {
|
|
3429
|
+
findings.push({
|
|
3430
|
+
advisoryId: entry.advisoryId,
|
|
3431
|
+
ecosystem: entry.ecosystem,
|
|
3432
|
+
package: entry.package,
|
|
3433
|
+
version: entry.version,
|
|
3434
|
+
tier: "not_reached",
|
|
3435
|
+
evidence: `no import of ${entry.package} was found in the code map`,
|
|
3436
|
+
graphConfidence: baseConfidence
|
|
3437
|
+
});
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
const matchableSymbols = entry.symbols.map((s) => ({ symbol: s.symbol, identifier: symbolIdentifier(s.symbol) })).filter((s) => isMatchableIdentifier(s.identifier));
|
|
3441
|
+
if (entry.symbolCoverage !== "function" || matchableSymbols.length === 0) {
|
|
3442
|
+
findings.push({
|
|
3443
|
+
advisoryId: entry.advisoryId,
|
|
3444
|
+
ecosystem: entry.ecosystem,
|
|
3445
|
+
package: entry.package,
|
|
3446
|
+
version: entry.version,
|
|
3447
|
+
tier: "potentially_reachable",
|
|
3448
|
+
evidence: `${entry.package} is imported by ${evidence.importingFiles.length} file${evidence.importingFiles.length === 1 ? "" : "s"}; vulnerable symbols unresolved (coverage: ${entry.symbolCoverage})`,
|
|
3449
|
+
callPath: evidence.importingFiles.slice(0, 10),
|
|
3450
|
+
graphConfidence: Math.min(baseConfidence, 0.6)
|
|
3451
|
+
});
|
|
3452
|
+
continue;
|
|
3453
|
+
}
|
|
3454
|
+
const filesToCheck = evidence.importingFiles.slice(0, MAX_FILES_PER_PACKAGE);
|
|
3455
|
+
let anySymbolFound = false;
|
|
3456
|
+
for (const { symbol, identifier } of matchableSymbols) {
|
|
3457
|
+
const referencingFiles = [];
|
|
3458
|
+
for (const relFile of filesToCheck) {
|
|
3459
|
+
if (await fileReferencesIdentifier(input.rootDir, relFile, identifier, readFile3)) {
|
|
3460
|
+
referencingFiles.push(relFile);
|
|
3461
|
+
if (referencingFiles.length >= 5) break;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
if (referencingFiles.length > 0) {
|
|
3465
|
+
anySymbolFound = true;
|
|
3466
|
+
findings.push({
|
|
3467
|
+
advisoryId: entry.advisoryId,
|
|
3468
|
+
ecosystem: entry.ecosystem,
|
|
3469
|
+
package: entry.package,
|
|
3470
|
+
version: entry.version,
|
|
3471
|
+
symbol,
|
|
3472
|
+
tier: "reachable",
|
|
3473
|
+
evidence: `vulnerable symbol \`${identifier}\` is referenced in ${referencingFiles.length} importing file${referencingFiles.length === 1 ? "" : "s"}`,
|
|
3474
|
+
callPath: referencingFiles,
|
|
3475
|
+
graphConfidence: Math.min(baseConfidence, 0.75)
|
|
3476
|
+
});
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
if (!anySymbolFound) {
|
|
3480
|
+
findings.push({
|
|
3481
|
+
advisoryId: entry.advisoryId,
|
|
3482
|
+
ecosystem: entry.ecosystem,
|
|
3483
|
+
package: entry.package,
|
|
3484
|
+
version: entry.version,
|
|
3485
|
+
tier: "not_reached",
|
|
3486
|
+
evidence: `${entry.package} is imported, but none of its ${matchableSymbols.length} vulnerable symbol${matchableSymbols.length === 1 ? "" : "s"} is referenced in the ${filesToCheck.length} importing file${filesToCheck.length === 1 ? "" : "s"} checked`,
|
|
3487
|
+
callPath: evidence.importingFiles.slice(0, 10),
|
|
3488
|
+
graphConfidence: Math.min(baseConfidence, 0.55)
|
|
3489
|
+
});
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
findings.sort(
|
|
3493
|
+
(a, b) => a.ecosystem.localeCompare(b.ecosystem) || a.package.localeCompare(b.package) || a.advisoryId.localeCompare(b.advisoryId) || (a.symbol ?? "").localeCompare(b.symbol ?? "")
|
|
3494
|
+
);
|
|
3495
|
+
return {
|
|
3496
|
+
analyzerVersion: REACHABILITY_ANALYZER_VERSION,
|
|
3497
|
+
source: "graph",
|
|
3498
|
+
generatedAt,
|
|
3499
|
+
manifestAdvisoryCount: input.manifest.length,
|
|
3500
|
+
importedModules,
|
|
3501
|
+
findings: findings.slice(0, MAX_FINDINGS)
|
|
3502
|
+
};
|
|
3503
|
+
}
|
|
3504
|
+
function collectPreflightDependencies(projects, projectTypeToEcosystem) {
|
|
3505
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
3506
|
+
for (const project of projects) {
|
|
3507
|
+
const ecosystem = projectTypeToEcosystem[project.type];
|
|
3508
|
+
if (!ecosystem) continue;
|
|
3509
|
+
for (const dep of project.dependencies) {
|
|
3510
|
+
const version = dep.resolvedVersion || normalizeVersionSpec(dep.currentSpec);
|
|
3511
|
+
if (!version) continue;
|
|
3512
|
+
const key = `${ecosystem}:${dep.package}:${version}`;
|
|
3513
|
+
if (!deduped.has(key)) {
|
|
3514
|
+
deduped.set(key, { ecosystem, package: dep.package, version });
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
return [...deduped.values()].sort(
|
|
3519
|
+
(a, b) => a.ecosystem.localeCompare(b.ecosystem) || a.package.localeCompare(b.package) || a.version.localeCompare(b.version)
|
|
3520
|
+
);
|
|
3521
|
+
}
|
|
3522
|
+
function normalizeVersionSpec(spec) {
|
|
3523
|
+
const match = spec.trim().match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/);
|
|
3524
|
+
return match?.[0] ?? null;
|
|
3525
|
+
}
|
|
2686
3526
|
|
|
2687
3527
|
// src/reporting/utils/ingest-id-output.ts
|
|
2688
3528
|
function emitIngestIdLine(ingestId, options) {
|
|
@@ -2738,6 +3578,47 @@ function printAiContextPrompt(detectedAssistant) {
|
|
|
2738
3578
|
function shouldBuildCodeMap(opts) {
|
|
2739
3579
|
return opts.graph !== false && !opts.maxPrivacy && !opts.noLocalArtifacts;
|
|
2740
3580
|
}
|
|
3581
|
+
async function attachReachability(artifact, rootDir, opts, graph) {
|
|
3582
|
+
try {
|
|
3583
|
+
const dsn = resolveDsn(opts.dsn);
|
|
3584
|
+
if (!dsn) return;
|
|
3585
|
+
const parsed = parseDsn(dsn);
|
|
3586
|
+
if (!parsed) return;
|
|
3587
|
+
const ingestHost = opts.region ? resolveIngestHost(opts.region) : parsed.host;
|
|
3588
|
+
const dependencies = collectPreflightDependencies(artifact.projects, PROJECT_TYPE_TO_OSV_ECOSYSTEM);
|
|
3589
|
+
if (dependencies.length === 0) return;
|
|
3590
|
+
const manifest = await fetchRiskySymbols(parsed, ingestHost, dependencies);
|
|
3591
|
+
if (manifest.status !== "ok" || !manifest.advisories) return;
|
|
3592
|
+
artifact.reachability = await analyzeReachability({
|
|
3593
|
+
graph,
|
|
3594
|
+
rootDir,
|
|
3595
|
+
manifest: manifest.advisories,
|
|
3596
|
+
dependencies
|
|
3597
|
+
});
|
|
3598
|
+
if (!opts.quiet && manifest.advisories.length > 0) {
|
|
3599
|
+
const tiers = artifact.reachability.findings.reduce((acc, f) => {
|
|
3600
|
+
acc[f.tier] = (acc[f.tier] ?? 0) + 1;
|
|
3601
|
+
return acc;
|
|
3602
|
+
}, {});
|
|
3603
|
+
const parts = [
|
|
3604
|
+
tiers.reachable ? `${tiers.reachable} reachable` : null,
|
|
3605
|
+
tiers.potentially_reachable ? `${tiers.potentially_reachable} potentially reachable` : null,
|
|
3606
|
+
tiers.not_reached ? `${tiers.not_reached} not reached` : null,
|
|
3607
|
+
tiers.unknown ? `${tiers.unknown} unknown` : null
|
|
3608
|
+
].filter(Boolean);
|
|
3609
|
+
console.log(
|
|
3610
|
+
chalk8.dim(
|
|
3611
|
+
`Reachability: ${manifest.advisories.length} advisor${manifest.advisories.length === 1 ? "y" : "ies"} checked against the code map \u2014 ${parts.join(", ")}`
|
|
3612
|
+
)
|
|
3613
|
+
);
|
|
3614
|
+
}
|
|
3615
|
+
} catch (e) {
|
|
3616
|
+
if (!opts.quiet) {
|
|
3617
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3618
|
+
console.log(chalk8.dim(`Reachability check skipped: ${msg}`));
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
2741
3622
|
async function autoPush(artifact, rootDir, opts) {
|
|
2742
3623
|
const dsn = resolveDsn(opts.dsn);
|
|
2743
3624
|
if (!dsn) {
|
|
@@ -2752,7 +3633,9 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
2752
3633
|
if (opts.strict) process.exit(1);
|
|
2753
3634
|
return;
|
|
2754
3635
|
}
|
|
2755
|
-
const
|
|
3636
|
+
const config = await loadConfig(rootDir);
|
|
3637
|
+
const databaseSchemaCaps = config.scanners !== false ? config.scanners?.databaseSchema : void 0;
|
|
3638
|
+
const { body, contentEncoding } = await prepareCompressedUpload(artifact, { databaseSchemaCaps });
|
|
2756
3639
|
const timestamp = String(Date.now());
|
|
2757
3640
|
let host = parsed.host;
|
|
2758
3641
|
if (opts.region) {
|
|
@@ -2777,7 +3660,11 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
2777
3660
|
body,
|
|
2778
3661
|
contentEncoding,
|
|
2779
3662
|
timestamp,
|
|
2780
|
-
force: opts.force
|
|
3663
|
+
force: opts.force,
|
|
3664
|
+
// Set only by an automated caller running the scan on the workspace's
|
|
3665
|
+
// behalf (e.g. a Vibgrate-hosted remediation run) — never a customer flag.
|
|
3666
|
+
runId: process.env.VIBGRATE_SCAN_RUN_ID,
|
|
3667
|
+
runToken: process.env.VIBGRATE_SCAN_RUN_TOKEN
|
|
2781
3668
|
});
|
|
2782
3669
|
host = uploadedHost;
|
|
2783
3670
|
if (!response.ok) {
|
|
@@ -2852,7 +3739,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2852
3739
|
'Exclude paths matching a glob pattern. Repeatable, and a single value may list several patterns separated by commas or semicolons (e.g. --exclude "legacy/**,vendor/**"). Merged with excludes from the config file.',
|
|
2853
3740
|
collectExcludes,
|
|
2854
3741
|
[]
|
|
2855
|
-
).option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--full", "Comprehensive scan: turns on known-vulnerability detection (= --vulns) and, when a standards policy exists, a banned-dependency report \u2014 on top of drift scoring and the code map").option("--vulns", "Also scan installed dependencies for known vulnerabilities (OSV online, or advisories from --package-manifest when offline)").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--project-scan-timeout <seconds>", "Per-project scan timeout in seconds (default: 180)").option("--drift-budget <score>", "Fail if
|
|
3742
|
+
).option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--full", "Comprehensive scan: turns on known-vulnerability detection (= --vulns) and, when a standards policy exists, a banned-dependency report \u2014 on top of drift scoring and the code map").option("--vulns", "Also scan installed dependencies for known vulnerabilities (OSV online, or advisories from --package-manifest when offline)").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--project-scan-timeout <seconds>", "Per-project scan timeout in seconds (default: 180)").option("--drift-budget <score>", "Fail if DriftScore is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").option("--repository-name <name>", "Override the repository name recorded for this scan (defaults to the directory or package.json name)").option("--force", "Always create a fresh scan ingest, even if the repository is unchanged since the last scan (skips the unchanged/reuse optimization). Used by scheduled and dashboard-triggered scans.").option("--no-graph", "Skip building the local code map (the AI/docs index) that scan produces after scoring drift").option("--quiet", "Suppress promotional output (the free-plan tracking panel and the AI Context install prompt); scan results are unaffected").action(async (targetPath, opts) => {
|
|
2856
3743
|
const rootDir = path.resolve(targetPath);
|
|
2857
3744
|
if (!await pathExists$1(rootDir)) {
|
|
2858
3745
|
console.error(chalk8.red(`Path does not exist: ${rootDir}`));
|
|
@@ -2987,6 +3874,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2987
3874
|
invocation: resolveCliInvocation()
|
|
2988
3875
|
};
|
|
2989
3876
|
const wantGraph = shouldBuildCodeMap({ graph: opts.graph, maxPrivacy: opts.maxPrivacy, noLocalArtifacts });
|
|
3877
|
+
let builtGraph = null;
|
|
2990
3878
|
if (wantGraph) {
|
|
2991
3879
|
scanOpts.postScan = async (report) => {
|
|
2992
3880
|
const result = await buildGraph({
|
|
@@ -2994,6 +3882,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2994
3882
|
exclude: opts.exclude,
|
|
2995
3883
|
onParseProgress: (done, total) => report(done, total, "parsing")
|
|
2996
3884
|
});
|
|
3885
|
+
builtGraph = result.graph;
|
|
2997
3886
|
writeArtifacts(result.graph, { root: rootDir });
|
|
2998
3887
|
writeSnapshot(rootDir, result.graph.provenance.corpusHash, result.fileStats, {
|
|
2999
3888
|
exclude: opts.exclude
|
|
@@ -3024,7 +3913,7 @@ Failing: findings detected at warn level or above.`));
|
|
|
3024
3913
|
}
|
|
3025
3914
|
if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
|
|
3026
3915
|
console.error(chalk8.red(`
|
|
3027
|
-
Failing fitness function:
|
|
3916
|
+
Failing fitness function: DriftScore ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
|
|
3028
3917
|
process.exit(2);
|
|
3029
3918
|
}
|
|
3030
3919
|
if (scanOpts.driftWorseningPercent !== void 0) {
|
|
@@ -3043,6 +3932,9 @@ Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (thr
|
|
|
3043
3932
|
}
|
|
3044
3933
|
}
|
|
3045
3934
|
}
|
|
3935
|
+
if (willPush && hasDsn && builtGraph) {
|
|
3936
|
+
await attachReachability(artifact, rootDir, scanOpts, builtGraph);
|
|
3937
|
+
}
|
|
3046
3938
|
if (willPush) {
|
|
3047
3939
|
await autoPush(artifact, rootDir, scanOpts);
|
|
3048
3940
|
}
|
|
@@ -3115,7 +4007,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
3115
4007
|
function scanStaleness(rootDir, artifactPath) {
|
|
3116
4008
|
let ref;
|
|
3117
4009
|
try {
|
|
3118
|
-
ref =
|
|
4010
|
+
ref = fs7.statSync(artifactPath).mtimeMs;
|
|
3119
4011
|
} catch {
|
|
3120
4012
|
return { stale: false };
|
|
3121
4013
|
}
|
|
@@ -3127,7 +4019,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
3127
4019
|
if (depth > 8 || scanned > MAX_ENTRIES) return;
|
|
3128
4020
|
let entries;
|
|
3129
4021
|
try {
|
|
3130
|
-
entries =
|
|
4022
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
3131
4023
|
} catch {
|
|
3132
4024
|
return;
|
|
3133
4025
|
}
|
|
@@ -3142,7 +4034,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
3142
4034
|
const abs = path.join(dir, e.name);
|
|
3143
4035
|
let mtimeMs;
|
|
3144
4036
|
try {
|
|
3145
|
-
mtimeMs =
|
|
4037
|
+
mtimeMs = fs7.statSync(abs).mtimeMs;
|
|
3146
4038
|
} catch {
|
|
3147
4039
|
continue;
|
|
3148
4040
|
}
|
|
@@ -3229,7 +4121,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
3229
4121
|
]);
|
|
3230
4122
|
var MAX_FILES = 8e3;
|
|
3231
4123
|
var MAX_DEPTH = 12;
|
|
3232
|
-
var
|
|
4124
|
+
var MAX_FILE_BYTES2 = 1048576;
|
|
3233
4125
|
var EXT_ECOSYSTEM = {
|
|
3234
4126
|
".ts": "npm",
|
|
3235
4127
|
".tsx": "npm",
|
|
@@ -3276,7 +4168,7 @@ function analyzeTree(root, packages) {
|
|
|
3276
4168
|
if (depth > MAX_DEPTH || filesSeen >= MAX_FILES) return;
|
|
3277
4169
|
let entries;
|
|
3278
4170
|
try {
|
|
3279
|
-
entries =
|
|
4171
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
3280
4172
|
} catch {
|
|
3281
4173
|
return;
|
|
3282
4174
|
}
|
|
@@ -3294,8 +4186,8 @@ function analyzeTree(root, packages) {
|
|
|
3294
4186
|
const full = path.join(dir, entry.name);
|
|
3295
4187
|
let content;
|
|
3296
4188
|
try {
|
|
3297
|
-
if (
|
|
3298
|
-
content =
|
|
4189
|
+
if (fs7.statSync(full).size > MAX_FILE_BYTES2) continue;
|
|
4190
|
+
content = fs7.readFileSync(full, "utf8");
|
|
3299
4191
|
} catch {
|
|
3300
4192
|
continue;
|
|
3301
4193
|
}
|
|
@@ -3486,6 +4378,11 @@ function estimateDriftScore(artifact, upgraded) {
|
|
|
3486
4378
|
for (const dep of p.dependencies ?? []) {
|
|
3487
4379
|
if (!upgraded.has(dep.package)) continue;
|
|
3488
4380
|
const mb3 = dep.majorsBehind ?? 0;
|
|
4381
|
+
dep.majorsBehind = 0;
|
|
4382
|
+
dep.drift = "current";
|
|
4383
|
+
if (dep.libyears != null) dep.libyears = 0;
|
|
4384
|
+
if (dep.ageDays != null) dep.ageDays = 0;
|
|
4385
|
+
if (dep.latestStable) dep.resolvedVersion = dep.latestStable;
|
|
3489
4386
|
if (mb3 >= 2 && buckets.twoPlusBehind > 0) {
|
|
3490
4387
|
buckets.twoPlusBehind--;
|
|
3491
4388
|
buckets.current++;
|
|
@@ -3629,8 +4526,8 @@ async function fetchLatestVersion() {
|
|
|
3629
4526
|
}
|
|
3630
4527
|
async function writeCache(data) {
|
|
3631
4528
|
try {
|
|
3632
|
-
await
|
|
3633
|
-
await
|
|
4529
|
+
await fs13.mkdir(CACHE_DIR, { recursive: true });
|
|
4530
|
+
await fs13.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
3634
4531
|
} catch {
|
|
3635
4532
|
}
|
|
3636
4533
|
}
|
|
@@ -3707,11 +4604,11 @@ async function isDevDependency(cwd) {
|
|
|
3707
4604
|
async function confirmWorkspaceRoot(pm) {
|
|
3708
4605
|
if (!(process.stdin.isTTY && process.stdout.isTTY)) return false;
|
|
3709
4606
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3710
|
-
return new Promise((
|
|
4607
|
+
return new Promise((resolve17) => {
|
|
3711
4608
|
rl.question(chalk8.yellow(`Update the ${pm} workspace root with -w? [y/N]: `), (ans) => {
|
|
3712
4609
|
rl.close();
|
|
3713
4610
|
const t = ans.trim().toLowerCase();
|
|
3714
|
-
|
|
4611
|
+
resolve17(t === "y" || t === "yes");
|
|
3715
4612
|
});
|
|
3716
4613
|
});
|
|
3717
4614
|
}
|
|
@@ -3839,7 +4736,7 @@ async function repositoryIdentity(rootDir, override) {
|
|
|
3839
4736
|
return { name: override?.trim() || void 0 };
|
|
3840
4737
|
}
|
|
3841
4738
|
}
|
|
3842
|
-
var fixCommand = new Command("fix").description("
|
|
4739
|
+
var fixCommand = new Command("fix").description("Get ranked, risk-tiered upgrade plans from the hosted planner and optionally apply them (prompts before changing files; --yes applies non-interactively, --dry-run previews, --no-apply only prints). Requires login/DSN.").argument("[path]", "Path to analyse", ".").option("--in <file>", "Scan artifact to read", ".vibgrate/scan_result.json").option("--format <format>", "Output format (text|json|md)", "text").option("--dsn <dsn>", 'DSN token (or use VIBGRATE_DSN env / "vg login")').option("--region <region>", "Override data residency region (us, eu)").option("--repository-name <name>", "Override the repository name recorded for this plan").option("--plan <tier>", "Apply a specific plan non-interactively (safe|balanced|aggressive)").option("--yes", "Apply the recommended plan without prompting").option("--dry-run", "Show what would change without applying").option("--no-apply", "Only print the plans; never modify the project").option("--fail-on-vulns <severity>", "Exit non-zero if the recommended plan leaves an advisory at/above this severity unresolved (low|moderate|high|critical)").action(async (targetPath, opts) => {
|
|
3843
4740
|
const rootDir = path.resolve(targetPath);
|
|
3844
4741
|
if (!await pathExists(rootDir)) {
|
|
3845
4742
|
console.error(chalk8.red(`Path does not exist: ${rootDir}`));
|
|
@@ -4038,14 +4935,14 @@ function promptPlanSelection(response) {
|
|
|
4038
4935
|
const defaultIdx = plans.findIndex((p) => p.tier === response.recommended);
|
|
4039
4936
|
const def = defaultIdx >= 0 ? defaultIdx + 1 : 1;
|
|
4040
4937
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4041
|
-
return new Promise((
|
|
4938
|
+
return new Promise((resolve17) => {
|
|
4042
4939
|
rl.question(`Plan [1-${plans.length}, default ${def}, q to cancel]: `, (ans) => {
|
|
4043
4940
|
rl.close();
|
|
4044
4941
|
const t = ans.trim().toLowerCase();
|
|
4045
|
-
if (t === "q" || t === "quit") return
|
|
4942
|
+
if (t === "q" || t === "quit") return resolve17(null);
|
|
4046
4943
|
const n = t === "" ? def : Number.parseInt(t, 10);
|
|
4047
|
-
if (!Number.isInteger(n) || n < 1 || n > plans.length) return
|
|
4048
|
-
|
|
4944
|
+
if (!Number.isInteger(n) || n < 1 || n > plans.length) return resolve17(null);
|
|
4945
|
+
resolve17(plans[n - 1].tier);
|
|
4049
4946
|
});
|
|
4050
4947
|
});
|
|
4051
4948
|
}
|
|
@@ -4416,6 +5313,26 @@ function formatExtended(ext) {
|
|
|
4416
5313
|
}
|
|
4417
5314
|
lines.push("");
|
|
4418
5315
|
}
|
|
5316
|
+
if (ext.databaseSchema) {
|
|
5317
|
+
const ds = ext.databaseSchema;
|
|
5318
|
+
lines.push(chalk8.bold.underline(" Database Schema"));
|
|
5319
|
+
const dsHead = [];
|
|
5320
|
+
if (ds.providers.length > 0) dsHead.push(ds.providers.join(", "));
|
|
5321
|
+
dsHead.push(`${ds.models.length} model${ds.models.length !== 1 ? "s" : ""}`);
|
|
5322
|
+
dsHead.push(`${ds.enums.length} enum${ds.enums.length !== 1 ? "s" : ""}`);
|
|
5323
|
+
lines.push(` ${dsHead.join(" \xB7 ")}`);
|
|
5324
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
5325
|
+
for (const m of ds.models) bySource.set(m.source, (bySource.get(m.source) ?? 0) + 1);
|
|
5326
|
+
if (bySource.size > 1) {
|
|
5327
|
+
const sourceSummary = [...bySource.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([s, n]) => `${s}: ${n}`).join(", ");
|
|
5328
|
+
lines.push(` Sources: ${sourceSummary}`);
|
|
5329
|
+
}
|
|
5330
|
+
if (ds.models.length > 0) {
|
|
5331
|
+
const preview = ds.models.slice(0, 5).map((m) => m.name).join(", ");
|
|
5332
|
+
lines.push(` Models: ${chalk8.white(preview)}${ds.models.length > 5 ? chalk8.dim(` (+${ds.models.length - 5} more)`) : ""}`);
|
|
5333
|
+
}
|
|
5334
|
+
lines.push("");
|
|
5335
|
+
}
|
|
4419
5336
|
if (ext.dependencyGraph) {
|
|
4420
5337
|
const dg = ext.dependencyGraph;
|
|
4421
5338
|
if (dg.lockfileType) {
|
|
@@ -4857,7 +5774,7 @@ function openUrl(url) {
|
|
|
4857
5774
|
}
|
|
4858
5775
|
|
|
4859
5776
|
// src/reporting/commands/login.ts
|
|
4860
|
-
var delay = (ms) => new Promise((
|
|
5777
|
+
var delay = (ms) => new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
4861
5778
|
function isInside(dir, file) {
|
|
4862
5779
|
const rel3 = path.relative(dir, file);
|
|
4863
5780
|
return !!rel3 && !rel3.startsWith("..") && !path.isAbsolute(rel3);
|
|
@@ -5304,7 +6221,7 @@ function formatDeltaText(base, current) {
|
|
|
5304
6221
|
"===================",
|
|
5305
6222
|
`Baseline: ${base.timestamp}`,
|
|
5306
6223
|
`Current: ${current.timestamp}`,
|
|
5307
|
-
`
|
|
6224
|
+
`DriftScore delta: ${(current.drift.score - base.drift.score).toFixed(2)} points`,
|
|
5308
6225
|
"",
|
|
5309
6226
|
`Added dependencies (${added.length})`,
|
|
5310
6227
|
...added.map((d) => ` + ${d}`),
|
|
@@ -5382,6 +6299,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
5382
6299
|
"areas",
|
|
5383
6300
|
"oddities",
|
|
5384
6301
|
"serve",
|
|
6302
|
+
"lsp",
|
|
5385
6303
|
"install",
|
|
5386
6304
|
"uninstall",
|
|
5387
6305
|
"share",
|
|
@@ -5422,6 +6340,7 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
|
|
|
5422
6340
|
registerImpact(program);
|
|
5423
6341
|
registerUnknowns(program);
|
|
5424
6342
|
registerServe(program);
|
|
6343
|
+
registerLsp(program);
|
|
5425
6344
|
registerInstall(program);
|
|
5426
6345
|
registerShare(program);
|
|
5427
6346
|
registerBenchmark(program);
|
|
@@ -5503,7 +6422,7 @@ function dispatch(argv, cwd) {
|
|
|
5503
6422
|
}
|
|
5504
6423
|
const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
|
|
5505
6424
|
if (looksLikeQuestion) return ["ask", ...args];
|
|
5506
|
-
if (
|
|
6425
|
+
if (fs7.existsSync(path.resolve(cwd, first))) {
|
|
5507
6426
|
return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
|
|
5508
6427
|
}
|
|
5509
6428
|
const moved = MOVED_COMMANDS[first];
|