@vibgrate/cli 2026.711.2 → 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 +54 -1
- package/README.md +2 -2
- package/dist/baseline-BTLZWSNS.js +6 -0
- package/dist/{baseline-SRVG4ZAD.js.map → baseline-BTLZWSNS.js.map} +1 -1
- package/dist/{chunk-4IHO6VUL.js → chunk-6DKWB2OI.js} +628 -4
- package/dist/chunk-6DKWB2OI.js.map +1 -0
- package/dist/{chunk-NNU2PW2H.js → chunk-C5AVF7PT.js} +236 -50
- package/dist/chunk-C5AVF7PT.js.map +1 -0
- package/dist/{chunk-75ZJYYJE.js → chunk-D4BPWARI.js} +194 -87
- 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 +705 -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/dist/baseline-SRVG4ZAD.js +0 -6
- package/dist/chunk-4IHO6VUL.js.map +0 -1
- package/dist/chunk-75ZJYYJE.js.map +0 -1
- package/dist/chunk-NNU2PW2H.js.map +0 -1
- package/dist/chunk-X5YT263H.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
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, fetchHostedDocsCached, selectForBudget, formatForExt, verifyDeterminism, renderHtml, isModelReady } from './chunk-
|
|
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, 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, 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
|
|
19
|
+
import * as fs13 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 (!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;
|
|
@@ -2597,8 +3172,8 @@ function registerExport(program) {
|
|
|
2597
3172
|
out(content.trimEnd());
|
|
2598
3173
|
return;
|
|
2599
3174
|
}
|
|
2600
|
-
|
|
2601
|
-
|
|
3175
|
+
fs7.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
|
|
3176
|
+
fs7.writeFileSync(file, content);
|
|
2602
3177
|
info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
|
|
2603
3178
|
});
|
|
2604
3179
|
applyGlobalOptions(cmd);
|
|
@@ -2621,24 +3196,24 @@ function registerBundle(program) {
|
|
|
2621
3196
|
} catch {
|
|
2622
3197
|
throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
|
|
2623
3198
|
}
|
|
2624
|
-
|
|
3199
|
+
fs7.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
|
|
2625
3200
|
let grammarCount = 0;
|
|
2626
3201
|
for (const { fileName, absPath } of grammarFiles) {
|
|
2627
|
-
|
|
3202
|
+
fs7.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
|
|
2628
3203
|
grammarCount++;
|
|
2629
3204
|
}
|
|
2630
3205
|
const included = [`grammars/ (${grammarCount} .wasm)`];
|
|
2631
3206
|
const graphSrc = global.graph ?? defaultGraphPath(root);
|
|
2632
|
-
if (
|
|
2633
|
-
|
|
3207
|
+
if (fs7.existsSync(graphSrc)) {
|
|
3208
|
+
fs7.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
|
|
2634
3209
|
included.push("graph.json");
|
|
2635
3210
|
}
|
|
2636
3211
|
const cat = catalogPath(root);
|
|
2637
|
-
if (
|
|
2638
|
-
|
|
3212
|
+
if (fs7.existsSync(cat)) {
|
|
3213
|
+
fs7.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
|
|
2639
3214
|
included.push("vibgrate.lib.json");
|
|
2640
|
-
if (
|
|
2641
|
-
|
|
3215
|
+
if (fs7.existsSync(libDir(root))) {
|
|
3216
|
+
fs7.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
|
|
2642
3217
|
included.push("lib/");
|
|
2643
3218
|
}
|
|
2644
3219
|
}
|
|
@@ -2650,7 +3225,7 @@ function registerBundle(program) {
|
|
|
2650
3225
|
included,
|
|
2651
3226
|
usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
|
|
2652
3227
|
};
|
|
2653
|
-
|
|
3228
|
+
fs7.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
|
|
2654
3229
|
`);
|
|
2655
3230
|
if (global.json) {
|
|
2656
3231
|
json({ out: path.relative(root, outDir), ...manifest });
|
|
@@ -2675,7 +3250,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
2675
3250
|
console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold("vibgrate.config.ts")}`);
|
|
2676
3251
|
}
|
|
2677
3252
|
if (opts.baseline) {
|
|
2678
|
-
const { runBaseline } = await import('./baseline-
|
|
3253
|
+
const { runBaseline } = await import('./baseline-BTLZWSNS.js');
|
|
2679
3254
|
await runBaseline(rootDir);
|
|
2680
3255
|
}
|
|
2681
3256
|
console.log("");
|
|
@@ -2785,9 +3360,9 @@ async function fileReferencesIdentifier(rootDir, relFile, identifier, readFile3)
|
|
|
2785
3360
|
}
|
|
2786
3361
|
async function defaultReadFile(absPath) {
|
|
2787
3362
|
try {
|
|
2788
|
-
const stat2 = await
|
|
3363
|
+
const stat2 = await fs13.stat(absPath);
|
|
2789
3364
|
if (!stat2.isFile() || stat2.size > MAX_FILE_BYTES) return null;
|
|
2790
|
-
return await
|
|
3365
|
+
return await fs13.readFile(absPath, "utf8");
|
|
2791
3366
|
} catch {
|
|
2792
3367
|
return null;
|
|
2793
3368
|
}
|
|
@@ -3058,7 +3633,9 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
3058
3633
|
if (opts.strict) process.exit(1);
|
|
3059
3634
|
return;
|
|
3060
3635
|
}
|
|
3061
|
-
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 });
|
|
3062
3639
|
const timestamp = String(Date.now());
|
|
3063
3640
|
let host = parsed.host;
|
|
3064
3641
|
if (opts.region) {
|
|
@@ -3083,7 +3660,11 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
3083
3660
|
body,
|
|
3084
3661
|
contentEncoding,
|
|
3085
3662
|
timestamp,
|
|
3086
|
-
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
|
|
3087
3668
|
});
|
|
3088
3669
|
host = uploadedHost;
|
|
3089
3670
|
if (!response.ok) {
|
|
@@ -3158,7 +3739,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
3158
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.',
|
|
3159
3740
|
collectExcludes,
|
|
3160
3741
|
[]
|
|
3161
|
-
).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) => {
|
|
3162
3743
|
const rootDir = path.resolve(targetPath);
|
|
3163
3744
|
if (!await pathExists$1(rootDir)) {
|
|
3164
3745
|
console.error(chalk8.red(`Path does not exist: ${rootDir}`));
|
|
@@ -3332,7 +3913,7 @@ Failing: findings detected at warn level or above.`));
|
|
|
3332
3913
|
}
|
|
3333
3914
|
if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
|
|
3334
3915
|
console.error(chalk8.red(`
|
|
3335
|
-
Failing fitness function:
|
|
3916
|
+
Failing fitness function: DriftScore ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
|
|
3336
3917
|
process.exit(2);
|
|
3337
3918
|
}
|
|
3338
3919
|
if (scanOpts.driftWorseningPercent !== void 0) {
|
|
@@ -3426,7 +4007,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
3426
4007
|
function scanStaleness(rootDir, artifactPath) {
|
|
3427
4008
|
let ref;
|
|
3428
4009
|
try {
|
|
3429
|
-
ref =
|
|
4010
|
+
ref = fs7.statSync(artifactPath).mtimeMs;
|
|
3430
4011
|
} catch {
|
|
3431
4012
|
return { stale: false };
|
|
3432
4013
|
}
|
|
@@ -3438,7 +4019,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
3438
4019
|
if (depth > 8 || scanned > MAX_ENTRIES) return;
|
|
3439
4020
|
let entries;
|
|
3440
4021
|
try {
|
|
3441
|
-
entries =
|
|
4022
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
3442
4023
|
} catch {
|
|
3443
4024
|
return;
|
|
3444
4025
|
}
|
|
@@ -3453,7 +4034,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
3453
4034
|
const abs = path.join(dir, e.name);
|
|
3454
4035
|
let mtimeMs;
|
|
3455
4036
|
try {
|
|
3456
|
-
mtimeMs =
|
|
4037
|
+
mtimeMs = fs7.statSync(abs).mtimeMs;
|
|
3457
4038
|
} catch {
|
|
3458
4039
|
continue;
|
|
3459
4040
|
}
|
|
@@ -3587,7 +4168,7 @@ function analyzeTree(root, packages) {
|
|
|
3587
4168
|
if (depth > MAX_DEPTH || filesSeen >= MAX_FILES) return;
|
|
3588
4169
|
let entries;
|
|
3589
4170
|
try {
|
|
3590
|
-
entries =
|
|
4171
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
3591
4172
|
} catch {
|
|
3592
4173
|
return;
|
|
3593
4174
|
}
|
|
@@ -3605,8 +4186,8 @@ function analyzeTree(root, packages) {
|
|
|
3605
4186
|
const full = path.join(dir, entry.name);
|
|
3606
4187
|
let content;
|
|
3607
4188
|
try {
|
|
3608
|
-
if (
|
|
3609
|
-
content =
|
|
4189
|
+
if (fs7.statSync(full).size > MAX_FILE_BYTES2) continue;
|
|
4190
|
+
content = fs7.readFileSync(full, "utf8");
|
|
3610
4191
|
} catch {
|
|
3611
4192
|
continue;
|
|
3612
4193
|
}
|
|
@@ -3797,6 +4378,11 @@ function estimateDriftScore(artifact, upgraded) {
|
|
|
3797
4378
|
for (const dep of p.dependencies ?? []) {
|
|
3798
4379
|
if (!upgraded.has(dep.package)) continue;
|
|
3799
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;
|
|
3800
4386
|
if (mb3 >= 2 && buckets.twoPlusBehind > 0) {
|
|
3801
4387
|
buckets.twoPlusBehind--;
|
|
3802
4388
|
buckets.current++;
|
|
@@ -3940,8 +4526,8 @@ async function fetchLatestVersion() {
|
|
|
3940
4526
|
}
|
|
3941
4527
|
async function writeCache(data) {
|
|
3942
4528
|
try {
|
|
3943
|
-
await
|
|
3944
|
-
await
|
|
4529
|
+
await fs13.mkdir(CACHE_DIR, { recursive: true });
|
|
4530
|
+
await fs13.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
3945
4531
|
} catch {
|
|
3946
4532
|
}
|
|
3947
4533
|
}
|
|
@@ -4018,11 +4604,11 @@ async function isDevDependency(cwd) {
|
|
|
4018
4604
|
async function confirmWorkspaceRoot(pm) {
|
|
4019
4605
|
if (!(process.stdin.isTTY && process.stdout.isTTY)) return false;
|
|
4020
4606
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4021
|
-
return new Promise((
|
|
4607
|
+
return new Promise((resolve17) => {
|
|
4022
4608
|
rl.question(chalk8.yellow(`Update the ${pm} workspace root with -w? [y/N]: `), (ans) => {
|
|
4023
4609
|
rl.close();
|
|
4024
4610
|
const t = ans.trim().toLowerCase();
|
|
4025
|
-
|
|
4611
|
+
resolve17(t === "y" || t === "yes");
|
|
4026
4612
|
});
|
|
4027
4613
|
});
|
|
4028
4614
|
}
|
|
@@ -4150,7 +4736,7 @@ async function repositoryIdentity(rootDir, override) {
|
|
|
4150
4736
|
return { name: override?.trim() || void 0 };
|
|
4151
4737
|
}
|
|
4152
4738
|
}
|
|
4153
|
-
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) => {
|
|
4154
4740
|
const rootDir = path.resolve(targetPath);
|
|
4155
4741
|
if (!await pathExists(rootDir)) {
|
|
4156
4742
|
console.error(chalk8.red(`Path does not exist: ${rootDir}`));
|
|
@@ -4349,14 +4935,14 @@ function promptPlanSelection(response) {
|
|
|
4349
4935
|
const defaultIdx = plans.findIndex((p) => p.tier === response.recommended);
|
|
4350
4936
|
const def = defaultIdx >= 0 ? defaultIdx + 1 : 1;
|
|
4351
4937
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4352
|
-
return new Promise((
|
|
4938
|
+
return new Promise((resolve17) => {
|
|
4353
4939
|
rl.question(`Plan [1-${plans.length}, default ${def}, q to cancel]: `, (ans) => {
|
|
4354
4940
|
rl.close();
|
|
4355
4941
|
const t = ans.trim().toLowerCase();
|
|
4356
|
-
if (t === "q" || t === "quit") return
|
|
4942
|
+
if (t === "q" || t === "quit") return resolve17(null);
|
|
4357
4943
|
const n = t === "" ? def : Number.parseInt(t, 10);
|
|
4358
|
-
if (!Number.isInteger(n) || n < 1 || n > plans.length) return
|
|
4359
|
-
|
|
4944
|
+
if (!Number.isInteger(n) || n < 1 || n > plans.length) return resolve17(null);
|
|
4945
|
+
resolve17(plans[n - 1].tier);
|
|
4360
4946
|
});
|
|
4361
4947
|
});
|
|
4362
4948
|
}
|
|
@@ -4727,6 +5313,26 @@ function formatExtended(ext) {
|
|
|
4727
5313
|
}
|
|
4728
5314
|
lines.push("");
|
|
4729
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
|
+
}
|
|
4730
5336
|
if (ext.dependencyGraph) {
|
|
4731
5337
|
const dg = ext.dependencyGraph;
|
|
4732
5338
|
if (dg.lockfileType) {
|
|
@@ -5168,7 +5774,7 @@ function openUrl(url) {
|
|
|
5168
5774
|
}
|
|
5169
5775
|
|
|
5170
5776
|
// src/reporting/commands/login.ts
|
|
5171
|
-
var delay = (ms) => new Promise((
|
|
5777
|
+
var delay = (ms) => new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
5172
5778
|
function isInside(dir, file) {
|
|
5173
5779
|
const rel3 = path.relative(dir, file);
|
|
5174
5780
|
return !!rel3 && !rel3.startsWith("..") && !path.isAbsolute(rel3);
|
|
@@ -5615,7 +6221,7 @@ function formatDeltaText(base, current) {
|
|
|
5615
6221
|
"===================",
|
|
5616
6222
|
`Baseline: ${base.timestamp}`,
|
|
5617
6223
|
`Current: ${current.timestamp}`,
|
|
5618
|
-
`
|
|
6224
|
+
`DriftScore delta: ${(current.drift.score - base.drift.score).toFixed(2)} points`,
|
|
5619
6225
|
"",
|
|
5620
6226
|
`Added dependencies (${added.length})`,
|
|
5621
6227
|
...added.map((d) => ` + ${d}`),
|
|
@@ -5693,6 +6299,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
5693
6299
|
"areas",
|
|
5694
6300
|
"oddities",
|
|
5695
6301
|
"serve",
|
|
6302
|
+
"lsp",
|
|
5696
6303
|
"install",
|
|
5697
6304
|
"uninstall",
|
|
5698
6305
|
"share",
|
|
@@ -5733,6 +6340,7 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
|
|
|
5733
6340
|
registerImpact(program);
|
|
5734
6341
|
registerUnknowns(program);
|
|
5735
6342
|
registerServe(program);
|
|
6343
|
+
registerLsp(program);
|
|
5736
6344
|
registerInstall(program);
|
|
5737
6345
|
registerShare(program);
|
|
5738
6346
|
registerBenchmark(program);
|
|
@@ -5814,7 +6422,7 @@ function dispatch(argv, cwd) {
|
|
|
5814
6422
|
}
|
|
5815
6423
|
const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
|
|
5816
6424
|
if (looksLikeQuestion) return ["ask", ...args];
|
|
5817
|
-
if (
|
|
6425
|
+
if (fs7.existsSync(path.resolve(cwd, first))) {
|
|
5818
6426
|
return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
|
|
5819
6427
|
}
|
|
5820
6428
|
const moved = MOVED_COMMANDS[first];
|