@vibgrate/cli 2026.704.1 → 2026.708.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 +90 -4
- package/README.md +1 -1
- package/dist/baseline-RRO4NNNF.js +6 -0
- package/dist/{baseline-FESLZK5O.js.map → baseline-RRO4NNNF.js.map} +1 -1
- package/dist/{chunk-YODVLV37.js → chunk-2J7DPIAL.js} +748 -371
- package/dist/chunk-2J7DPIAL.js.map +1 -0
- package/dist/{chunk-W4ANUENJ.js → chunk-FMCI3MXH.js} +3 -3
- package/dist/{chunk-W4ANUENJ.js.map → chunk-FMCI3MXH.js.map} +1 -1
- package/dist/{chunk-RJHYTD62.js → chunk-SRWMZBLG.js} +54 -21
- package/dist/chunk-SRWMZBLG.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +1594 -336
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +20 -4
- package/dist/index.js +2 -2
- package/dist/parse-worker.d.ts +1 -1
- package/dist/{types-C6ZyQpa9.d.ts → types-CXnp1uGj.d.ts} +33 -1
- package/package.json +3 -3
- package/dist/baseline-FESLZK5O.js +0 -6
- package/dist/chunk-RJHYTD62.js.map +0 -1
- package/dist/chunk-YODVLV37.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { langById, shortId, canonicalize, grammarSetVersion,
|
|
2
|
-
import { writeTextFile, pathExists, readJsonFile,
|
|
1
|
+
import { hashString, langById, shortId, canonicalize, grammarSetVersion, hashBytes, langForExtension, setGrammarsOverride, parseSource } from './chunk-X5YT263H.js';
|
|
2
|
+
import { writeTextFile, pathExists, readJsonFile, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-SRWMZBLG.js';
|
|
3
3
|
import * as fs18 from 'fs';
|
|
4
4
|
import { execFileSync } from 'child_process';
|
|
5
5
|
import * as path18 from 'path';
|
|
@@ -1624,6 +1624,7 @@ function resolve4(parses, resolver) {
|
|
|
1624
1624
|
}
|
|
1625
1625
|
}
|
|
1626
1626
|
const edges = new EdgeSet();
|
|
1627
|
+
const unresolved2 = /* @__PURE__ */ new Map();
|
|
1627
1628
|
const stats = {
|
|
1628
1629
|
callsResolved: 0,
|
|
1629
1630
|
callsUnresolved: 0,
|
|
@@ -1672,6 +1673,7 @@ function resolve4(parses, resolver) {
|
|
|
1672
1673
|
stats.callsResolved++;
|
|
1673
1674
|
} else {
|
|
1674
1675
|
stats.callsUnresolved++;
|
|
1676
|
+
bumpUnresolved(unresolved2, srcId, call.callee, "call", p.rel);
|
|
1675
1677
|
}
|
|
1676
1678
|
}
|
|
1677
1679
|
}
|
|
@@ -1683,6 +1685,7 @@ function resolve4(parses, resolver) {
|
|
|
1683
1685
|
if (!srcId) continue;
|
|
1684
1686
|
const target = resolveType(h.superName, p.rel, p.lang, imported, defsByName);
|
|
1685
1687
|
if (target) edges.add(h.kind, srcId, target.id, "heuristic", 0.85);
|
|
1688
|
+
else bumpUnresolved(unresolved2, srcId, h.superName, h.kind, p.rel);
|
|
1686
1689
|
}
|
|
1687
1690
|
}
|
|
1688
1691
|
const nodes = [...baseNodes.values()].map((n) => ({
|
|
@@ -1693,7 +1696,16 @@ function resolve4(parses, resolver) {
|
|
|
1693
1696
|
isHub: false,
|
|
1694
1697
|
tested: null
|
|
1695
1698
|
}));
|
|
1696
|
-
|
|
1699
|
+
const unresolvedList = [...unresolved2.values()].sort(
|
|
1700
|
+
(a, b) => a.from.localeCompare(b.from) || a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name)
|
|
1701
|
+
);
|
|
1702
|
+
return { nodes, edges: edges.toArray(), unresolved: unresolvedList, stats };
|
|
1703
|
+
}
|
|
1704
|
+
function bumpUnresolved(map, from, name, kind, fromRel) {
|
|
1705
|
+
const key = `${from}\0${kind}\0${name}`;
|
|
1706
|
+
const existing = map.get(key);
|
|
1707
|
+
if (existing) existing.count++;
|
|
1708
|
+
else map.set(key, { from, name, kind, count: 1, fromRel });
|
|
1697
1709
|
}
|
|
1698
1710
|
function addNode(map, n) {
|
|
1699
1711
|
if (!map.has(n.id)) map.set(n.id, n);
|
|
@@ -2036,6 +2048,30 @@ function loadCache(root, opts) {
|
|
|
2036
2048
|
};
|
|
2037
2049
|
}
|
|
2038
2050
|
|
|
2051
|
+
// src/engine/epistemic.ts
|
|
2052
|
+
var SEMANTIC_RESOLVERS = /* @__PURE__ */ new Set(["scip", "stackgraph", "tsc"]);
|
|
2053
|
+
function classifyEpistemic(edge, dstKind) {
|
|
2054
|
+
if (SEMANTIC_RESOLVERS.has(edge.resolution)) return "observed";
|
|
2055
|
+
switch (edge.kind) {
|
|
2056
|
+
case "contains":
|
|
2057
|
+
return "observed";
|
|
2058
|
+
case "import":
|
|
2059
|
+
return dstKind === "external" || dstKind === void 0 ? "declared" : "observed";
|
|
2060
|
+
case "test":
|
|
2061
|
+
case "coverage":
|
|
2062
|
+
return "declared";
|
|
2063
|
+
default:
|
|
2064
|
+
return "name-matched";
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
function epistemicBreakdown(edges) {
|
|
2068
|
+
const out = { observed: 0, "name-matched": 0, declared: 0 };
|
|
2069
|
+
for (const e of edges) {
|
|
2070
|
+
if (e.epistemic) out[e.epistemic]++;
|
|
2071
|
+
}
|
|
2072
|
+
return out;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2039
2075
|
// src/engine/build.ts
|
|
2040
2076
|
async function buildGraph(options) {
|
|
2041
2077
|
const start = nowMs();
|
|
@@ -2117,6 +2153,7 @@ async function buildGraph(options) {
|
|
|
2117
2153
|
const nodeFileById = new Map(resolved.nodes.map((n) => [n.id, n.file]));
|
|
2118
2154
|
let edges = resolved.edges;
|
|
2119
2155
|
const resolvers = [...resolved.stats.resolvers];
|
|
2156
|
+
const preciseCoveredFiles = /* @__PURE__ */ new Set();
|
|
2120
2157
|
let tscStats;
|
|
2121
2158
|
let tsFiles = options.noTsc ? [] : files.filter((f) => (f.lang.id === "ts" || f.lang.id === "tsx" || f.lang.id === "js") && hashes.has(f.rel)).map((f) => ({ rel: f.rel, abs: f.abs }));
|
|
2122
2159
|
if (limits.tscMaxFiles > 0 && tsFiles.length > limits.tscMaxFiles) {
|
|
@@ -2129,6 +2166,7 @@ async function buildGraph(options) {
|
|
|
2129
2166
|
const res = tsResolveEdges(root, tsFiles, resolved.nodes);
|
|
2130
2167
|
if (res.stats.files > 0) {
|
|
2131
2168
|
edges = mergePreciseEdges(edges, res.edges, res.coveredFiles, nodeFileById);
|
|
2169
|
+
for (const f of res.coveredFiles) preciseCoveredFiles.add(f);
|
|
2132
2170
|
if (!resolvers.includes("tsc")) resolvers.unshift("tsc");
|
|
2133
2171
|
tscStats = res.stats;
|
|
2134
2172
|
}
|
|
@@ -2139,6 +2177,7 @@ async function buildGraph(options) {
|
|
|
2139
2177
|
if (scip) {
|
|
2140
2178
|
const res = scipEdges(scip.index, resolved.nodes, toRepoRel);
|
|
2141
2179
|
edges = mergePreciseEdges(edges, res.edges, res.coveredFiles, nodeFileById);
|
|
2180
|
+
for (const f of res.coveredFiles) preciseCoveredFiles.add(f);
|
|
2142
2181
|
if (!resolvers.includes("scip")) resolvers.unshift("scip");
|
|
2143
2182
|
scipStats = { ...res.stats, tool: scip.tool };
|
|
2144
2183
|
}
|
|
@@ -2151,6 +2190,11 @@ async function buildGraph(options) {
|
|
|
2151
2190
|
const languages = [...new Set(parses.map((p) => p.lang))].sort();
|
|
2152
2191
|
const edgeKinds = [...new Set(analysis.edges.map((e) => e.kind))].sort();
|
|
2153
2192
|
const corpusHash = computeCorpusHash(parses, hashes);
|
|
2193
|
+
const nodeKindById = new Map(analysis.nodes.map((n) => [n.id, n.kind]));
|
|
2194
|
+
for (const e of analysis.edges) {
|
|
2195
|
+
e.epistemic = classifyEpistemic(e, nodeKindById.get(e.dst));
|
|
2196
|
+
}
|
|
2197
|
+
const toolchain = computeToolchain(grammars, resolvers);
|
|
2154
2198
|
const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2155
2199
|
const testsCount = linked.testFiles.length;
|
|
2156
2200
|
const untestedCount = analysis.nodes.filter(
|
|
@@ -2165,7 +2209,8 @@ async function buildGraph(options) {
|
|
|
2165
2209
|
grammars: Object.fromEntries(languages.map((l) => [l, grammars])),
|
|
2166
2210
|
resolver: resolvers,
|
|
2167
2211
|
deep: options.deep ?? false,
|
|
2168
|
-
corpusHash
|
|
2212
|
+
corpusHash,
|
|
2213
|
+
toolchain
|
|
2169
2214
|
},
|
|
2170
2215
|
meta: {
|
|
2171
2216
|
root: path18.basename(root) === "" ? "." : ".",
|
|
@@ -2184,6 +2229,9 @@ async function buildGraph(options) {
|
|
|
2184
2229
|
edges: analysis.edges,
|
|
2185
2230
|
areas: analysis.areas
|
|
2186
2231
|
};
|
|
2232
|
+
const survivingIds = new Set(analysis.nodes.map((n) => n.id));
|
|
2233
|
+
const unknowns = resolved.unresolved.filter((u) => !preciseCoveredFiles.has(u.fromRel) && survivingIds.has(u.from)).map((u) => ({ from: u.from, name: u.name, kind: u.kind, count: u.count }));
|
|
2234
|
+
if (unknowns.length) graph.unknowns = unknowns;
|
|
2187
2235
|
if (options.deep) {
|
|
2188
2236
|
const facts = buildFacts(parses, analysis.nodes, analysis.edges);
|
|
2189
2237
|
if (facts.length) graph.facts = facts;
|
|
@@ -2242,6 +2290,23 @@ function mergePreciseEdges(base, precise, coveredFiles, nodeFileById) {
|
|
|
2242
2290
|
(a, b) => a.kind.localeCompare(b.kind) || a.src.localeCompare(b.src) || a.dst.localeCompare(b.dst)
|
|
2243
2291
|
);
|
|
2244
2292
|
}
|
|
2293
|
+
function computeToolchain(grammars, resolvers) {
|
|
2294
|
+
const sortedResolvers = [...new Set(resolvers)].sort();
|
|
2295
|
+
return {
|
|
2296
|
+
schema: SCHEMA_VERSION,
|
|
2297
|
+
tool: VERSION,
|
|
2298
|
+
grammars,
|
|
2299
|
+
resolvers: sortedResolvers,
|
|
2300
|
+
fingerprint: shortId(
|
|
2301
|
+
canonicalize({
|
|
2302
|
+
schema: SCHEMA_VERSION,
|
|
2303
|
+
tool: VERSION,
|
|
2304
|
+
grammars,
|
|
2305
|
+
resolvers: sortedResolvers
|
|
2306
|
+
})
|
|
2307
|
+
)
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2245
2310
|
function computeCorpusHash(parses, hashes) {
|
|
2246
2311
|
const list = parses.map((p) => [p.rel, hashes.get(p.rel) ?? p.hash]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
2247
2312
|
return hashString(canonicalize(list));
|
|
@@ -2249,6 +2314,224 @@ function computeCorpusHash(parses, hashes) {
|
|
|
2249
2314
|
function nowMs() {
|
|
2250
2315
|
return Number(process.hrtime.bigint() / 1000n) / 1e3;
|
|
2251
2316
|
}
|
|
2317
|
+
|
|
2318
|
+
// src/engine/report.ts
|
|
2319
|
+
function renderReport(graph) {
|
|
2320
|
+
const { meta, provenance, nodes, edges } = graph;
|
|
2321
|
+
const lines = [];
|
|
2322
|
+
lines.push("# Code Graph Report");
|
|
2323
|
+
lines.push("");
|
|
2324
|
+
lines.push(`Generated by \`vg\` ${provenance.version} \xB7 schema \`${graph.schemaVersion}\``);
|
|
2325
|
+
lines.push("");
|
|
2326
|
+
lines.push("## Summary");
|
|
2327
|
+
lines.push("");
|
|
2328
|
+
lines.push(`- **Nodes:** ${meta.counts.nodes.toLocaleString("en-US")}`);
|
|
2329
|
+
lines.push(`- **Edges:** ${meta.counts.edges.toLocaleString("en-US")}`);
|
|
2330
|
+
lines.push(`- **Areas:** ${meta.counts.areas.toLocaleString("en-US")}`);
|
|
2331
|
+
lines.push(`- **Languages:** ${meta.languages.join(", ") || "\u2014"}`);
|
|
2332
|
+
lines.push(`- **Clustering:** ${meta.cluster}`);
|
|
2333
|
+
lines.push(`- **Resolvers:** ${provenance.resolver.join(", ")}`);
|
|
2334
|
+
lines.push(`- **Edge kinds:** ${meta.edgeKinds.join(", ") || "\u2014"}`);
|
|
2335
|
+
lines.push("");
|
|
2336
|
+
const fileNodes = nodes.filter((n) => n.kind === "file");
|
|
2337
|
+
const defsByFile = /* @__PURE__ */ new Map();
|
|
2338
|
+
for (const n of nodes) {
|
|
2339
|
+
if (n.kind === "file" || n.kind === "external") continue;
|
|
2340
|
+
defsByFile.set(n.file, (defsByFile.get(n.file) ?? 0) + 1);
|
|
2341
|
+
}
|
|
2342
|
+
const topFiles = [...defsByFile.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 15);
|
|
2343
|
+
if (topFiles.length) {
|
|
2344
|
+
lines.push("## Largest files (by definitions)");
|
|
2345
|
+
lines.push("");
|
|
2346
|
+
lines.push("| File | Definitions |");
|
|
2347
|
+
lines.push("| --- | ---: |");
|
|
2348
|
+
for (const [file, count] of topFiles) lines.push(`| \`${file}\` | ${count} |`);
|
|
2349
|
+
lines.push("");
|
|
2350
|
+
}
|
|
2351
|
+
const topNodes = nodes.filter((n) => n.kind !== "file" && n.kind !== "external").sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)).slice(0, 15);
|
|
2352
|
+
if (topNodes.length) {
|
|
2353
|
+
lines.push("## Most-connected definitions");
|
|
2354
|
+
lines.push("");
|
|
2355
|
+
lines.push("| Symbol | Kind | File | Importance |");
|
|
2356
|
+
lines.push("| --- | --- | --- | ---: |");
|
|
2357
|
+
for (const n of topNodes) {
|
|
2358
|
+
lines.push(
|
|
2359
|
+
`| \`${n.qualifiedName}\` | ${n.kind} | \`${n.file}\`:${n.span.start} | ${n.importance.toFixed(3)} |`
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
lines.push("");
|
|
2363
|
+
}
|
|
2364
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
2365
|
+
for (const e of edges) byKind.set(e.kind, (byKind.get(e.kind) ?? 0) + 1);
|
|
2366
|
+
if (byKind.size) {
|
|
2367
|
+
lines.push("## Edges by kind");
|
|
2368
|
+
lines.push("");
|
|
2369
|
+
lines.push("| Kind | Count |");
|
|
2370
|
+
lines.push("| --- | ---: |");
|
|
2371
|
+
for (const [kind, count] of [...byKind.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
2372
|
+
lines.push(`| ${kind} | ${count} |`);
|
|
2373
|
+
}
|
|
2374
|
+
lines.push("");
|
|
2375
|
+
}
|
|
2376
|
+
lines.push("---");
|
|
2377
|
+
lines.push("");
|
|
2378
|
+
lines.push(`_Files: ${fileNodes.length} \xB7 Corpus: \`${provenance.corpusHash.slice(0, 16)}\u2026\`_`);
|
|
2379
|
+
lines.push("");
|
|
2380
|
+
return lines.join("\n");
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// src/engine/html.ts
|
|
2384
|
+
function renderHtml(graph) {
|
|
2385
|
+
const top = graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external").sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)).slice(0, 50);
|
|
2386
|
+
const rows = top.map(
|
|
2387
|
+
(n) => `<tr><td><code>${esc(n.qualifiedName)}</code></td><td>${esc(n.kind)}</td><td><code>${esc(n.file)}</code>:${n.span.start}</td><td>${n.importance.toFixed(3)}</td></tr>`
|
|
2388
|
+
).join("\n");
|
|
2389
|
+
const embedded = stableStringify(graph, 0);
|
|
2390
|
+
return `<!doctype html>
|
|
2391
|
+
<html lang="en">
|
|
2392
|
+
<head>
|
|
2393
|
+
<meta charset="utf-8">
|
|
2394
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2395
|
+
<title>vg \xB7 code graph</title>
|
|
2396
|
+
<style>
|
|
2397
|
+
:root { color-scheme: light dark; }
|
|
2398
|
+
body { font: 14px/1.5 system-ui, sans-serif; margin: 2rem auto; max-width: 60rem; padding: 0 1rem; }
|
|
2399
|
+
h1 { font-size: 1.4rem; } h2 { font-size: 1.1rem; margin-top: 2rem; }
|
|
2400
|
+
table { border-collapse: collapse; width: 100%; }
|
|
2401
|
+
th, td { text-align: left; padding: 4px 8px; border-bottom: 1px solid #8884; }
|
|
2402
|
+
td:last-child, th:last-child { text-align: right; }
|
|
2403
|
+
.meta { color: #888; } code { font-size: 0.9em; }
|
|
2404
|
+
</style>
|
|
2405
|
+
</head>
|
|
2406
|
+
<body>
|
|
2407
|
+
<h1>vg \xB7 code graph</h1>
|
|
2408
|
+
<p class="meta">${graph.meta.counts.nodes} nodes \xB7 ${graph.meta.counts.edges} edges \xB7
|
|
2409
|
+
${graph.meta.counts.areas} areas \xB7 ${esc(graph.meta.languages.join(", ") || "\u2014")} \xB7
|
|
2410
|
+
clustering: ${esc(graph.meta.cluster)} \xB7 vg ${esc(graph.provenance.version)}</p>
|
|
2411
|
+
<h2>Most-connected definitions</h2>
|
|
2412
|
+
<table>
|
|
2413
|
+
<thead><tr><th>Symbol</th><th>Kind</th><th>Location</th><th>Importance</th></tr></thead>
|
|
2414
|
+
<tbody>
|
|
2415
|
+
${rows}
|
|
2416
|
+
</tbody>
|
|
2417
|
+
</table>
|
|
2418
|
+
<p class="meta">Interactive WebGL view arrives in Phase 1. The full graph is embedded below for tooling.</p>
|
|
2419
|
+
<script type="application/json" id="vg-graph">
|
|
2420
|
+
${embedded}
|
|
2421
|
+
</script>
|
|
2422
|
+
</body>
|
|
2423
|
+
</html>
|
|
2424
|
+
`;
|
|
2425
|
+
}
|
|
2426
|
+
function esc(s) {
|
|
2427
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2428
|
+
}
|
|
2429
|
+
function vibgrateDir(root) {
|
|
2430
|
+
return path18.join(root, ".vibgrate");
|
|
2431
|
+
}
|
|
2432
|
+
function defaultGraphPath(root) {
|
|
2433
|
+
return path18.join(vibgrateDir(root), "graph.json");
|
|
2434
|
+
}
|
|
2435
|
+
function writeArtifacts(graph, options) {
|
|
2436
|
+
const dir = vibgrateDir(options.root);
|
|
2437
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
2438
|
+
const graphPath = options.graphPath ?? defaultGraphPath(options.root);
|
|
2439
|
+
fs18.mkdirSync(path18.dirname(graphPath), { recursive: true });
|
|
2440
|
+
const tmp = `${graphPath}.${process.pid}.tmp`;
|
|
2441
|
+
try {
|
|
2442
|
+
fs18.writeFileSync(tmp, serializeGraph(graph));
|
|
2443
|
+
fs18.renameSync(tmp, graphPath);
|
|
2444
|
+
} catch (err) {
|
|
2445
|
+
fs18.rmSync(tmp, { force: true });
|
|
2446
|
+
throw err;
|
|
2447
|
+
}
|
|
2448
|
+
const written = { graphPath };
|
|
2449
|
+
if (options.report !== false) {
|
|
2450
|
+
const reportPath = path18.join(dir, "GRAPH_REPORT.md");
|
|
2451
|
+
fs18.writeFileSync(reportPath, renderReport(graph));
|
|
2452
|
+
written.reportPath = reportPath;
|
|
2453
|
+
}
|
|
2454
|
+
if (options.html !== false) {
|
|
2455
|
+
const htmlPath = path18.join(dir, "graph.html");
|
|
2456
|
+
fs18.writeFileSync(htmlPath, renderHtml(graph));
|
|
2457
|
+
written.htmlPath = htmlPath;
|
|
2458
|
+
}
|
|
2459
|
+
if (graph.facts && graph.facts.length) {
|
|
2460
|
+
const factsPath = path18.join(dir, "facts.jsonl");
|
|
2461
|
+
fs18.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
|
|
2462
|
+
written.factsPath = factsPath;
|
|
2463
|
+
}
|
|
2464
|
+
return written;
|
|
2465
|
+
}
|
|
2466
|
+
function loadGraph(root, graphPath) {
|
|
2467
|
+
const file = graphPath ?? defaultGraphPath(root);
|
|
2468
|
+
if (!fs18.existsSync(file)) return null;
|
|
2469
|
+
return parseGraph(fs18.readFileSync(file, "utf8"));
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/engine/verify.ts
|
|
2473
|
+
var PINNED = "1970-01-01T00:00:00.000Z";
|
|
2474
|
+
async function verifyDeterminism(opts) {
|
|
2475
|
+
const base = { root: opts.root, only: opts.only, exclude: opts.exclude, generatedAt: PINNED };
|
|
2476
|
+
const buildA = await buildGraph({ ...base, noCache: true, jobs: opts.jobs });
|
|
2477
|
+
const a = serializeGraph(buildA.graph);
|
|
2478
|
+
const b = serializeGraph((await buildGraph({ ...base, noCache: true, jobs: 1 })).graph);
|
|
2479
|
+
const cached2 = serializeGraph((await buildGraph({ ...base, noCache: false })).graph);
|
|
2480
|
+
const checks = [
|
|
2481
|
+
{ name: "run-to-run determinism", ok: a === b, detail: diffHint(a, b) },
|
|
2482
|
+
{ name: "cache safety (incremental == full)", ok: a === cached2, detail: diffHint(a, cached2) }
|
|
2483
|
+
];
|
|
2484
|
+
const scoped = Boolean(opts.only?.length || opts.exclude?.length);
|
|
2485
|
+
const committed = scoped ? null : loadGraph(opts.root);
|
|
2486
|
+
const committedFp = committed?.provenance.toolchain?.fingerprint;
|
|
2487
|
+
const currentFp = buildA.graph.provenance.toolchain?.fingerprint;
|
|
2488
|
+
if (committedFp && currentFp) {
|
|
2489
|
+
const ok = committedFp === currentFp;
|
|
2490
|
+
checks.push({
|
|
2491
|
+
name: "toolchain fingerprint matches committed graph",
|
|
2492
|
+
ok,
|
|
2493
|
+
detail: ok ? void 0 : `committed toolchain ${committedFp} != current ${currentFp} (grammar/resolver versions differ \u2014 rebuild or align the toolchain)`
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
return {
|
|
2497
|
+
ok: checks.every((c) => c.ok),
|
|
2498
|
+
checks,
|
|
2499
|
+
digest: hashString(a)
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
function diffHint(x, y) {
|
|
2503
|
+
if (x === y) return void 0;
|
|
2504
|
+
const xl = x.split("\n");
|
|
2505
|
+
const yl = y.split("\n");
|
|
2506
|
+
const n = Math.max(xl.length, yl.length);
|
|
2507
|
+
for (let i = 0; i < n; i++) {
|
|
2508
|
+
if (xl[i] !== yl[i]) {
|
|
2509
|
+
return `first divergence at line ${i + 1}`;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return "outputs differ in length";
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// src/util/exit.ts
|
|
2516
|
+
var ExitCode = {
|
|
2517
|
+
OK: 0,
|
|
2518
|
+
ERROR: 1,
|
|
2519
|
+
GATE_FAILED: 2,
|
|
2520
|
+
NOT_FOUND: 3,
|
|
2521
|
+
NON_DETERMINISTIC: 4,
|
|
2522
|
+
USAGE_ERROR: 5
|
|
2523
|
+
};
|
|
2524
|
+
var CliError = class extends Error {
|
|
2525
|
+
code;
|
|
2526
|
+
constructor(message, code = ExitCode.ERROR) {
|
|
2527
|
+
super(message);
|
|
2528
|
+
this.name = "CliError";
|
|
2529
|
+
this.code = code;
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2532
|
+
function usageError(message) {
|
|
2533
|
+
return new CliError(message, ExitCode.USAGE_ERROR);
|
|
2534
|
+
}
|
|
2252
2535
|
var DEFAULT_LOCK_STALE_MS = 15 * 60 * 1e3;
|
|
2253
2536
|
function isProcessAlive(pid) {
|
|
2254
2537
|
try {
|
|
@@ -2508,220 +2791,71 @@ function readCacheEntries(file, modelId) {
|
|
|
2508
2791
|
try {
|
|
2509
2792
|
const loaded = JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
2510
2793
|
if (loaded.model === modelId && loaded.entries) return loaded.entries;
|
|
2511
|
-
} catch {
|
|
2512
|
-
}
|
|
2513
|
-
return {};
|
|
2514
|
-
}
|
|
2515
|
-
async function getNodeEmbeddings(graph, embedder, root, onProgress) {
|
|
2516
|
-
const file = vectorCachePath(root, embedder.id);
|
|
2517
|
-
let cache = { model: embedder.id, entries: {} };
|
|
2518
|
-
if (fs18.existsSync(file)) {
|
|
2519
|
-
try {
|
|
2520
|
-
const loaded = JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
2521
|
-
if (loaded.model === embedder.id && loaded.entries) cache = loaded;
|
|
2522
|
-
} catch {
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
const areaLabel = new Map(graph.areas.map((a) => [a.id, a.label]));
|
|
2526
|
-
const targets = graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external");
|
|
2527
|
-
const toEmbed = [];
|
|
2528
|
-
const vectors = /* @__PURE__ */ new Map();
|
|
2529
|
-
for (const n of targets) {
|
|
2530
|
-
const text = nodeEmbedText(n, areaLabel.get(n.area));
|
|
2531
|
-
const h = hashString(text);
|
|
2532
|
-
const cached2 = cache.entries[n.id];
|
|
2533
|
-
if (cached2 && cached2.hash === h) vectors.set(n.id, cached2.vec);
|
|
2534
|
-
else toEmbed.push({ id: n.id, text, hash: h });
|
|
2535
|
-
}
|
|
2536
|
-
const persist = () => {
|
|
2537
|
-
try {
|
|
2538
|
-
fs18.mkdirSync(cacheDir(root), { recursive: true });
|
|
2539
|
-
const tmp = `${file}.tmp.${process.pid}`;
|
|
2540
|
-
fs18.writeFileSync(tmp, JSON.stringify(cache));
|
|
2541
|
-
fs18.renameSync(tmp, file);
|
|
2542
|
-
} catch {
|
|
2543
|
-
}
|
|
2544
|
-
};
|
|
2545
|
-
if (toEmbed.length) {
|
|
2546
|
-
const lock = `${file}.lock`;
|
|
2547
|
-
if (!acquireLock(lock)) return vectors;
|
|
2548
|
-
try {
|
|
2549
|
-
onProgress?.(0, toEmbed.length);
|
|
2550
|
-
let lastWrite = Date.now();
|
|
2551
|
-
for (let i = 0; i < toEmbed.length; i += EMBED_CHUNK) {
|
|
2552
|
-
const slice = toEmbed.slice(i, i + EMBED_CHUNK);
|
|
2553
|
-
const vecs = await embedder.embed(slice.map((t) => t.text));
|
|
2554
|
-
slice.forEach((t, j) => {
|
|
2555
|
-
const vec = vecs[j] ?? [];
|
|
2556
|
-
vectors.set(t.id, vec);
|
|
2557
|
-
cache.entries[t.id] = { hash: t.hash, vec };
|
|
2558
|
-
});
|
|
2559
|
-
onProgress?.(Math.min(i + EMBED_CHUNK, toEmbed.length), toEmbed.length);
|
|
2560
|
-
if (Date.now() - lastWrite >= CACHE_WRITE_INTERVAL_MS) {
|
|
2561
|
-
persist();
|
|
2562
|
-
lastWrite = Date.now();
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
const live = new Set(targets.map((n) => n.id));
|
|
2566
|
-
for (const id of Object.keys(cache.entries)) if (!live.has(id)) delete cache.entries[id];
|
|
2567
|
-
persist();
|
|
2568
|
-
} finally {
|
|
2569
|
-
releaseLock(lock);
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
return vectors;
|
|
2573
|
-
}
|
|
2574
|
-
function safe(id) {
|
|
2575
|
-
return id.replace(/[^a-z0-9.-]+/gi, "_");
|
|
2576
|
-
}
|
|
2577
|
-
|
|
2578
|
-
// src/engine/report.ts
|
|
2579
|
-
function renderReport(graph) {
|
|
2580
|
-
const { meta, provenance, nodes, edges } = graph;
|
|
2581
|
-
const lines = [];
|
|
2582
|
-
lines.push("# Code Graph Report");
|
|
2583
|
-
lines.push("");
|
|
2584
|
-
lines.push(`Generated by \`vg\` ${provenance.version} \xB7 schema \`${graph.schemaVersion}\``);
|
|
2585
|
-
lines.push("");
|
|
2586
|
-
lines.push("## Summary");
|
|
2587
|
-
lines.push("");
|
|
2588
|
-
lines.push(`- **Nodes:** ${meta.counts.nodes.toLocaleString("en-US")}`);
|
|
2589
|
-
lines.push(`- **Edges:** ${meta.counts.edges.toLocaleString("en-US")}`);
|
|
2590
|
-
lines.push(`- **Areas:** ${meta.counts.areas.toLocaleString("en-US")}`);
|
|
2591
|
-
lines.push(`- **Languages:** ${meta.languages.join(", ") || "\u2014"}`);
|
|
2592
|
-
lines.push(`- **Clustering:** ${meta.cluster}`);
|
|
2593
|
-
lines.push(`- **Resolvers:** ${provenance.resolver.join(", ")}`);
|
|
2594
|
-
lines.push(`- **Edge kinds:** ${meta.edgeKinds.join(", ") || "\u2014"}`);
|
|
2595
|
-
lines.push("");
|
|
2596
|
-
const fileNodes = nodes.filter((n) => n.kind === "file");
|
|
2597
|
-
const defsByFile = /* @__PURE__ */ new Map();
|
|
2598
|
-
for (const n of nodes) {
|
|
2599
|
-
if (n.kind === "file" || n.kind === "external") continue;
|
|
2600
|
-
defsByFile.set(n.file, (defsByFile.get(n.file) ?? 0) + 1);
|
|
2601
|
-
}
|
|
2602
|
-
const topFiles = [...defsByFile.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 15);
|
|
2603
|
-
if (topFiles.length) {
|
|
2604
|
-
lines.push("## Largest files (by definitions)");
|
|
2605
|
-
lines.push("");
|
|
2606
|
-
lines.push("| File | Definitions |");
|
|
2607
|
-
lines.push("| --- | ---: |");
|
|
2608
|
-
for (const [file, count] of topFiles) lines.push(`| \`${file}\` | ${count} |`);
|
|
2609
|
-
lines.push("");
|
|
2610
|
-
}
|
|
2611
|
-
const topNodes = nodes.filter((n) => n.kind !== "file" && n.kind !== "external").sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)).slice(0, 15);
|
|
2612
|
-
if (topNodes.length) {
|
|
2613
|
-
lines.push("## Most-connected definitions");
|
|
2614
|
-
lines.push("");
|
|
2615
|
-
lines.push("| Symbol | Kind | File | Importance |");
|
|
2616
|
-
lines.push("| --- | --- | --- | ---: |");
|
|
2617
|
-
for (const n of topNodes) {
|
|
2618
|
-
lines.push(
|
|
2619
|
-
`| \`${n.qualifiedName}\` | ${n.kind} | \`${n.file}\`:${n.span.start} | ${n.importance.toFixed(3)} |`
|
|
2620
|
-
);
|
|
2621
|
-
}
|
|
2622
|
-
lines.push("");
|
|
2623
|
-
}
|
|
2624
|
-
const byKind = /* @__PURE__ */ new Map();
|
|
2625
|
-
for (const e of edges) byKind.set(e.kind, (byKind.get(e.kind) ?? 0) + 1);
|
|
2626
|
-
if (byKind.size) {
|
|
2627
|
-
lines.push("## Edges by kind");
|
|
2628
|
-
lines.push("");
|
|
2629
|
-
lines.push("| Kind | Count |");
|
|
2630
|
-
lines.push("| --- | ---: |");
|
|
2631
|
-
for (const [kind, count] of [...byKind.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
2632
|
-
lines.push(`| ${kind} | ${count} |`);
|
|
2633
|
-
}
|
|
2634
|
-
lines.push("");
|
|
2635
|
-
}
|
|
2636
|
-
lines.push("---");
|
|
2637
|
-
lines.push("");
|
|
2638
|
-
lines.push(`_Files: ${fileNodes.length} \xB7 Corpus: \`${provenance.corpusHash.slice(0, 16)}\u2026\`_`);
|
|
2639
|
-
lines.push("");
|
|
2640
|
-
return lines.join("\n");
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
// src/engine/html.ts
|
|
2644
|
-
function renderHtml(graph) {
|
|
2645
|
-
const top = graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external").sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)).slice(0, 50);
|
|
2646
|
-
const rows = top.map(
|
|
2647
|
-
(n) => `<tr><td><code>${esc(n.qualifiedName)}</code></td><td>${esc(n.kind)}</td><td><code>${esc(n.file)}</code>:${n.span.start}</td><td>${n.importance.toFixed(3)}</td></tr>`
|
|
2648
|
-
).join("\n");
|
|
2649
|
-
const embedded = stableStringify(graph, 0);
|
|
2650
|
-
return `<!doctype html>
|
|
2651
|
-
<html lang="en">
|
|
2652
|
-
<head>
|
|
2653
|
-
<meta charset="utf-8">
|
|
2654
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2655
|
-
<title>vg \xB7 code graph</title>
|
|
2656
|
-
<style>
|
|
2657
|
-
:root { color-scheme: light dark; }
|
|
2658
|
-
body { font: 14px/1.5 system-ui, sans-serif; margin: 2rem auto; max-width: 60rem; padding: 0 1rem; }
|
|
2659
|
-
h1 { font-size: 1.4rem; } h2 { font-size: 1.1rem; margin-top: 2rem; }
|
|
2660
|
-
table { border-collapse: collapse; width: 100%; }
|
|
2661
|
-
th, td { text-align: left; padding: 4px 8px; border-bottom: 1px solid #8884; }
|
|
2662
|
-
td:last-child, th:last-child { text-align: right; }
|
|
2663
|
-
.meta { color: #888; } code { font-size: 0.9em; }
|
|
2664
|
-
</style>
|
|
2665
|
-
</head>
|
|
2666
|
-
<body>
|
|
2667
|
-
<h1>vg \xB7 code graph</h1>
|
|
2668
|
-
<p class="meta">${graph.meta.counts.nodes} nodes \xB7 ${graph.meta.counts.edges} edges \xB7
|
|
2669
|
-
${graph.meta.counts.areas} areas \xB7 ${esc(graph.meta.languages.join(", ") || "\u2014")} \xB7
|
|
2670
|
-
clustering: ${esc(graph.meta.cluster)} \xB7 vg ${esc(graph.provenance.version)}</p>
|
|
2671
|
-
<h2>Most-connected definitions</h2>
|
|
2672
|
-
<table>
|
|
2673
|
-
<thead><tr><th>Symbol</th><th>Kind</th><th>Location</th><th>Importance</th></tr></thead>
|
|
2674
|
-
<tbody>
|
|
2675
|
-
${rows}
|
|
2676
|
-
</tbody>
|
|
2677
|
-
</table>
|
|
2678
|
-
<p class="meta">Interactive WebGL view arrives in Phase 1. The full graph is embedded below for tooling.</p>
|
|
2679
|
-
<script type="application/json" id="vg-graph">
|
|
2680
|
-
${embedded}
|
|
2681
|
-
</script>
|
|
2682
|
-
</body>
|
|
2683
|
-
</html>
|
|
2684
|
-
`;
|
|
2685
|
-
}
|
|
2686
|
-
function esc(s) {
|
|
2687
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2688
|
-
}
|
|
2689
|
-
function vibgrateDir(root) {
|
|
2690
|
-
return path18.join(root, ".vibgrate");
|
|
2691
|
-
}
|
|
2692
|
-
function defaultGraphPath(root) {
|
|
2693
|
-
return path18.join(vibgrateDir(root), "graph.json");
|
|
2694
|
-
}
|
|
2695
|
-
function writeArtifacts(graph, options) {
|
|
2696
|
-
const dir = vibgrateDir(options.root);
|
|
2697
|
-
fs18.mkdirSync(dir, { recursive: true });
|
|
2698
|
-
const graphPath = options.graphPath ?? defaultGraphPath(options.root);
|
|
2699
|
-
fs18.mkdirSync(path18.dirname(graphPath), { recursive: true });
|
|
2700
|
-
const tmp = `${graphPath}.${process.pid}.tmp`;
|
|
2701
|
-
try {
|
|
2702
|
-
fs18.writeFileSync(tmp, serializeGraph(graph));
|
|
2703
|
-
fs18.renameSync(tmp, graphPath);
|
|
2704
|
-
} catch (err) {
|
|
2705
|
-
fs18.rmSync(tmp, { force: true });
|
|
2706
|
-
throw err;
|
|
2794
|
+
} catch {
|
|
2707
2795
|
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2796
|
+
return {};
|
|
2797
|
+
}
|
|
2798
|
+
async function getNodeEmbeddings(graph, embedder, root, onProgress) {
|
|
2799
|
+
const file = vectorCachePath(root, embedder.id);
|
|
2800
|
+
let cache = { model: embedder.id, entries: {} };
|
|
2801
|
+
if (fs18.existsSync(file)) {
|
|
2802
|
+
try {
|
|
2803
|
+
const loaded = JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
2804
|
+
if (loaded.model === embedder.id && loaded.entries) cache = loaded;
|
|
2805
|
+
} catch {
|
|
2806
|
+
}
|
|
2713
2807
|
}
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2808
|
+
const areaLabel = new Map(graph.areas.map((a) => [a.id, a.label]));
|
|
2809
|
+
const targets = graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external");
|
|
2810
|
+
const toEmbed = [];
|
|
2811
|
+
const vectors = /* @__PURE__ */ new Map();
|
|
2812
|
+
for (const n of targets) {
|
|
2813
|
+
const text = nodeEmbedText(n, areaLabel.get(n.area));
|
|
2814
|
+
const h = hashString(text);
|
|
2815
|
+
const cached2 = cache.entries[n.id];
|
|
2816
|
+
if (cached2 && cached2.hash === h) vectors.set(n.id, cached2.vec);
|
|
2817
|
+
else toEmbed.push({ id: n.id, text, hash: h });
|
|
2718
2818
|
}
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2819
|
+
const persist = () => {
|
|
2820
|
+
try {
|
|
2821
|
+
fs18.mkdirSync(cacheDir(root), { recursive: true });
|
|
2822
|
+
const tmp = `${file}.tmp.${process.pid}`;
|
|
2823
|
+
fs18.writeFileSync(tmp, JSON.stringify(cache));
|
|
2824
|
+
fs18.renameSync(tmp, file);
|
|
2825
|
+
} catch {
|
|
2826
|
+
}
|
|
2827
|
+
};
|
|
2828
|
+
if (toEmbed.length) {
|
|
2829
|
+
const lock = `${file}.lock`;
|
|
2830
|
+
if (!acquireLock(lock)) return vectors;
|
|
2831
|
+
try {
|
|
2832
|
+
onProgress?.(0, toEmbed.length);
|
|
2833
|
+
let lastWrite = Date.now();
|
|
2834
|
+
for (let i = 0; i < toEmbed.length; i += EMBED_CHUNK) {
|
|
2835
|
+
const slice = toEmbed.slice(i, i + EMBED_CHUNK);
|
|
2836
|
+
const vecs = await embedder.embed(slice.map((t) => t.text));
|
|
2837
|
+
slice.forEach((t, j) => {
|
|
2838
|
+
const vec = vecs[j] ?? [];
|
|
2839
|
+
vectors.set(t.id, vec);
|
|
2840
|
+
cache.entries[t.id] = { hash: t.hash, vec };
|
|
2841
|
+
});
|
|
2842
|
+
onProgress?.(Math.min(i + EMBED_CHUNK, toEmbed.length), toEmbed.length);
|
|
2843
|
+
if (Date.now() - lastWrite >= CACHE_WRITE_INTERVAL_MS) {
|
|
2844
|
+
persist();
|
|
2845
|
+
lastWrite = Date.now();
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
const live = new Set(targets.map((n) => n.id));
|
|
2849
|
+
for (const id of Object.keys(cache.entries)) if (!live.has(id)) delete cache.entries[id];
|
|
2850
|
+
persist();
|
|
2851
|
+
} finally {
|
|
2852
|
+
releaseLock(lock);
|
|
2853
|
+
}
|
|
2723
2854
|
}
|
|
2724
|
-
return
|
|
2855
|
+
return vectors;
|
|
2856
|
+
}
|
|
2857
|
+
function safe(id) {
|
|
2858
|
+
return id.replace(/[^a-z0-9.-]+/gi, "_");
|
|
2725
2859
|
}
|
|
2726
2860
|
var SNAPSHOT_VERSION = "vg-freshness/1";
|
|
2727
2861
|
function snapshotPath(root) {
|
|
@@ -2824,62 +2958,6 @@ function pruneScope(scope) {
|
|
|
2824
2958
|
return out;
|
|
2825
2959
|
}
|
|
2826
2960
|
|
|
2827
|
-
// src/util/exit.ts
|
|
2828
|
-
var ExitCode = {
|
|
2829
|
-
OK: 0,
|
|
2830
|
-
ERROR: 1,
|
|
2831
|
-
GATE_FAILED: 2,
|
|
2832
|
-
NOT_FOUND: 3,
|
|
2833
|
-
NON_DETERMINISTIC: 4,
|
|
2834
|
-
USAGE_ERROR: 5
|
|
2835
|
-
};
|
|
2836
|
-
var CliError = class extends Error {
|
|
2837
|
-
code;
|
|
2838
|
-
constructor(message, code = ExitCode.ERROR) {
|
|
2839
|
-
super(message);
|
|
2840
|
-
this.name = "CliError";
|
|
2841
|
-
this.code = code;
|
|
2842
|
-
}
|
|
2843
|
-
};
|
|
2844
|
-
function usageError(message) {
|
|
2845
|
-
return new CliError(message, ExitCode.USAGE_ERROR);
|
|
2846
|
-
}
|
|
2847
|
-
function loadGraph(root, graphPath) {
|
|
2848
|
-
const file = graphPath ?? defaultGraphPath(root);
|
|
2849
|
-
if (!fs18.existsSync(file)) return null;
|
|
2850
|
-
return parseGraph(fs18.readFileSync(file, "utf8"));
|
|
2851
|
-
}
|
|
2852
|
-
|
|
2853
|
-
// src/engine/verify.ts
|
|
2854
|
-
var PINNED = "1970-01-01T00:00:00.000Z";
|
|
2855
|
-
async function verifyDeterminism(opts) {
|
|
2856
|
-
const base = { root: opts.root, only: opts.only, exclude: opts.exclude, generatedAt: PINNED };
|
|
2857
|
-
const a = serializeGraph((await buildGraph({ ...base, noCache: true, jobs: opts.jobs })).graph);
|
|
2858
|
-
const b = serializeGraph((await buildGraph({ ...base, noCache: true, jobs: 1 })).graph);
|
|
2859
|
-
const cached2 = serializeGraph((await buildGraph({ ...base, noCache: false })).graph);
|
|
2860
|
-
const checks = [
|
|
2861
|
-
{ name: "run-to-run determinism", ok: a === b, detail: diffHint(a, b) },
|
|
2862
|
-
{ name: "cache safety (incremental == full)", ok: a === cached2, detail: diffHint(a, cached2) }
|
|
2863
|
-
];
|
|
2864
|
-
return {
|
|
2865
|
-
ok: checks.every((c) => c.ok),
|
|
2866
|
-
checks,
|
|
2867
|
-
digest: hashString(a)
|
|
2868
|
-
};
|
|
2869
|
-
}
|
|
2870
|
-
function diffHint(x, y) {
|
|
2871
|
-
if (x === y) return void 0;
|
|
2872
|
-
const xl = x.split("\n");
|
|
2873
|
-
const yl = y.split("\n");
|
|
2874
|
-
const n = Math.max(xl.length, yl.length);
|
|
2875
|
-
for (let i = 0; i < n; i++) {
|
|
2876
|
-
if (xl[i] !== yl[i]) {
|
|
2877
|
-
return `first divergence at line ${i + 1}`;
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
return "outputs differ in length";
|
|
2881
|
-
}
|
|
2882
|
-
|
|
2883
2961
|
// src/engine/relations.ts
|
|
2884
2962
|
var GraphIndex = class {
|
|
2885
2963
|
constructor(graph) {
|
|
@@ -5409,33 +5487,155 @@ function boundList(items, max) {
|
|
|
5409
5487
|
return { items: items.slice(0, max), total: items.length };
|
|
5410
5488
|
}
|
|
5411
5489
|
var NODE_EDGE_CAP = 50;
|
|
5490
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([".git", ".vibgrate", "node_modules", "dist", "build", "out", "target", "vendor", "__pycache__"]);
|
|
5491
|
+
var MAX_FILE_BYTES = 1e6;
|
|
5492
|
+
var MAX_FILES_SCANNED = 2e4;
|
|
5493
|
+
var PREVIEW_CHARS = 120;
|
|
5494
|
+
function searchSymbols(graph, root, query, limit) {
|
|
5495
|
+
const q2 = query.trim();
|
|
5496
|
+
if (!q2) return { matches: [], moreAvailable: false, hint: "query is required" };
|
|
5497
|
+
const nodes = findNodes(graph, q2).filter((n) => n.kind !== "file");
|
|
5498
|
+
const symbolHits = nodes.slice(0, limit).map((n, i) => ({
|
|
5499
|
+
kind: n.kind,
|
|
5500
|
+
name: n.qualifiedName,
|
|
5501
|
+
file: n.file,
|
|
5502
|
+
line: n.span.start,
|
|
5503
|
+
score: Math.round((1 - i / Math.max(nodes.length, 1)) * 100) / 100
|
|
5504
|
+
}));
|
|
5505
|
+
const spare = limit - symbolHits.length;
|
|
5506
|
+
const textHits = [];
|
|
5507
|
+
let truncatedScan = false;
|
|
5508
|
+
if (spare > 0) {
|
|
5509
|
+
const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
|
|
5510
|
+
truncatedScan = scanFiles(root, q2, spare, seen, textHits);
|
|
5511
|
+
}
|
|
5512
|
+
const matches = [...symbolHits, ...textHits];
|
|
5513
|
+
if (matches.length === 0) {
|
|
5514
|
+
return {
|
|
5515
|
+
matches,
|
|
5516
|
+
moreAvailable: false,
|
|
5517
|
+
hint: "no symbol or text match \u2014 for meaning-level questions (symptoms, relationships, what-breaks-if) use query_graph"
|
|
5518
|
+
};
|
|
5519
|
+
}
|
|
5520
|
+
return { matches, moreAvailable: nodes.length > symbolHits.length || truncatedScan };
|
|
5521
|
+
}
|
|
5522
|
+
function scanFiles(root, needle, budget, seen, out) {
|
|
5523
|
+
const lower = needle.toLowerCase();
|
|
5524
|
+
let scanned = 0;
|
|
5525
|
+
const stack = [root];
|
|
5526
|
+
while (stack.length) {
|
|
5527
|
+
const dir = stack.pop();
|
|
5528
|
+
let entries;
|
|
5529
|
+
try {
|
|
5530
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
5531
|
+
} catch {
|
|
5532
|
+
continue;
|
|
5533
|
+
}
|
|
5534
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
5535
|
+
for (const e of entries) {
|
|
5536
|
+
if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
|
|
5537
|
+
const abs = path18.join(dir, e.name);
|
|
5538
|
+
if (e.isDirectory()) {
|
|
5539
|
+
stack.push(abs);
|
|
5540
|
+
continue;
|
|
5541
|
+
}
|
|
5542
|
+
if (++scanned > MAX_FILES_SCANNED) return true;
|
|
5543
|
+
let text;
|
|
5544
|
+
try {
|
|
5545
|
+
if (fs18.statSync(abs).size > MAX_FILE_BYTES) continue;
|
|
5546
|
+
text = fs18.readFileSync(abs, "utf8");
|
|
5547
|
+
} catch {
|
|
5548
|
+
continue;
|
|
5549
|
+
}
|
|
5550
|
+
if (!text.toLowerCase().includes(lower)) continue;
|
|
5551
|
+
const rel2 = path18.relative(root, abs);
|
|
5552
|
+
const lines = text.split("\n");
|
|
5553
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5554
|
+
if (!lines[i].toLowerCase().includes(lower)) continue;
|
|
5555
|
+
const key = `${rel2}:${i + 1}`;
|
|
5556
|
+
if (seen.has(key)) continue;
|
|
5557
|
+
seen.add(key);
|
|
5558
|
+
out.push({ kind: "text", file: rel2, line: i + 1, preview: lines[i].trim().slice(0, PREVIEW_CHARS) });
|
|
5559
|
+
if (out.length >= budget) return true;
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
return false;
|
|
5564
|
+
}
|
|
5412
5565
|
|
|
5413
5566
|
// src/mcp/tools.ts
|
|
5414
|
-
var
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
return
|
|
5567
|
+
var warmEmbedder = null;
|
|
5568
|
+
var bgWarmStarted = false;
|
|
5569
|
+
function warmEmbedderInBackground(local) {
|
|
5570
|
+
if (local || bgWarmStarted || isModelReady()) return;
|
|
5571
|
+
bgWarmStarted = true;
|
|
5572
|
+
loadEmbedder({}).then((e) => {
|
|
5573
|
+
if (e) warmEmbedder = Promise.resolve(e);
|
|
5574
|
+
}).catch(() => {
|
|
5575
|
+
bgWarmStarted = false;
|
|
5576
|
+
});
|
|
5577
|
+
}
|
|
5578
|
+
function readyEmbedder(local) {
|
|
5579
|
+
if (warmEmbedder) return warmEmbedder;
|
|
5580
|
+
if (local) return warmEmbedder = loadEmbedder({ local, noDownload: true });
|
|
5581
|
+
if (isModelReady()) return warmEmbedder = loadEmbedder({ noDownload: true });
|
|
5582
|
+
warmEmbedderInBackground(local);
|
|
5583
|
+
return Promise.resolve(null);
|
|
5584
|
+
}
|
|
5585
|
+
async function retrieve(graph, question, budget, ctx) {
|
|
5586
|
+
let mode = "lexical";
|
|
5587
|
+
try {
|
|
5588
|
+
const q2 = await withTimeout(
|
|
5589
|
+
(async () => {
|
|
5590
|
+
const embedder = await readyEmbedder(ctx.local);
|
|
5591
|
+
if (!embedder) return null;
|
|
5592
|
+
const nodeVectors = await getNodeEmbeddings(graph, embedder, ctx.root);
|
|
5593
|
+
const r = await queryGraphSemantic(graph, question, { budget, embedder, nodeVectors });
|
|
5594
|
+
mode = `semantic (${embedder.id})`;
|
|
5595
|
+
return r;
|
|
5596
|
+
})(),
|
|
5597
|
+
SEMANTIC_BUDGET_MS,
|
|
5598
|
+
"semantic path over budget"
|
|
5599
|
+
);
|
|
5600
|
+
if (q2) return { q: q2, mode };
|
|
5601
|
+
} catch {
|
|
5602
|
+
}
|
|
5603
|
+
return { q: queryGraph(graph, question, { budget }), mode: "lexical" };
|
|
5418
5604
|
}
|
|
5605
|
+
var SEMANTIC_BUDGET_MS = (() => {
|
|
5606
|
+
const n = Number(process.env.VG_SEMANTIC_BUDGET_MS);
|
|
5607
|
+
return Number.isFinite(n) && n > 0 ? n : 4e3;
|
|
5608
|
+
})();
|
|
5419
5609
|
var obj = (properties, required = []) => ({
|
|
5420
5610
|
type: "object",
|
|
5421
5611
|
properties,
|
|
5422
|
-
required,
|
|
5612
|
+
...required.length ? { required } : {},
|
|
5423
5613
|
additionalProperties: false
|
|
5424
5614
|
});
|
|
5615
|
+
var RESPONSE_FORMAT = { type: "string", enum: ["concise", "detailed"] };
|
|
5616
|
+
var isDetailed = (args) => args.response_format === "detailed";
|
|
5617
|
+
var CONCISE_MATCHES = 5;
|
|
5618
|
+
var EPISTEMIC_TIERS = ["observed", "name-matched", "declared"];
|
|
5619
|
+
var EDGE_FILTER_SCHEMA = {
|
|
5620
|
+
min_edge_confidence: { type: "number", description: "0..1 \u2014 drop edges below this confidence" },
|
|
5621
|
+
epistemic: { type: "string", enum: EPISTEMIC_TIERS, description: "keep only edges of this evidence tier" }
|
|
5622
|
+
};
|
|
5623
|
+
function edgeRecordFilter(args) {
|
|
5624
|
+
const min = numOrU(args.min_edge_confidence);
|
|
5625
|
+
const tier = EPISTEMIC_TIERS.includes(String(args.epistemic)) ? String(args.epistemic) : void 0;
|
|
5626
|
+
if (min == null && !tier) return () => true;
|
|
5627
|
+
return (x) => (min == null || x.edge.confidence >= min) && (!tier || x.edge.epistemic === tier);
|
|
5628
|
+
}
|
|
5425
5629
|
var TOOLS = [
|
|
5426
|
-
{
|
|
5427
|
-
name: "get_graph_summary",
|
|
5428
|
-
description: "High-level summary of the code map: counts, languages, clustering, top areas and hubs.",
|
|
5429
|
-
inputSchema: obj({}),
|
|
5430
|
-
handler: (graph) => summarize(graph)
|
|
5431
|
-
},
|
|
5432
5630
|
{
|
|
5433
5631
|
name: "orient",
|
|
5434
|
-
description: "
|
|
5632
|
+
description: "Start here: one call returns the map overview, ranked matches for your question, and the top hit\u2019s blast radius \u2014 replaces summary+query+impact round-trips.",
|
|
5435
5633
|
inputSchema: obj(
|
|
5436
5634
|
{
|
|
5437
|
-
question: { type: "string",
|
|
5438
|
-
|
|
5635
|
+
question: { type: "string", maxLength: 300 },
|
|
5636
|
+
scope: { type: "string", description: "path prefix to orient within" },
|
|
5637
|
+
budget: { type: "number", description: "token budget in detailed mode (default 1500)" },
|
|
5638
|
+
response_format: RESPONSE_FORMAT
|
|
5439
5639
|
},
|
|
5440
5640
|
["question"]
|
|
5441
5641
|
),
|
|
@@ -5443,73 +5643,103 @@ var TOOLS = [
|
|
|
5443
5643
|
const question = String(args.question ?? "");
|
|
5444
5644
|
if (!question) return { error: "bad_request", message: "question is required" };
|
|
5445
5645
|
const budget = numOr(args.budget, 1500);
|
|
5446
|
-
const
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
q2 = await queryGraphSemantic(graph, question, { budget, embedder, nodeVectors });
|
|
5452
|
-
mode = `semantic (${embedder.id})`;
|
|
5453
|
-
} else {
|
|
5454
|
-
q2 = queryGraph(graph, question, { budget });
|
|
5455
|
-
}
|
|
5646
|
+
const { q: q2, mode } = await retrieve(graph, question, budget, ctx);
|
|
5647
|
+
const rawScope = String(args.scope ?? "").trim();
|
|
5648
|
+
const scope = rawScope === "." || rawScope === "./" ? "" : rawScope.replace(/^\.\//, "");
|
|
5649
|
+
const scoped = scope ? q2.matches.filter((m) => m.node.file.startsWith(scope)) : q2.matches;
|
|
5650
|
+
const detailed = isDetailed(args);
|
|
5456
5651
|
let topImpact = null;
|
|
5457
|
-
const top =
|
|
5458
|
-
if (top) {
|
|
5652
|
+
const top = scoped[0]?.node;
|
|
5653
|
+
if (detailed && top) {
|
|
5459
5654
|
const r = impactOf(graph, top.id, { depth: 3 });
|
|
5460
5655
|
topImpact = { node: top.qualifiedName, direct: r.direct, transitive: r.transitive, affected: r.affected.slice(0, 10).map(stripId) };
|
|
5461
5656
|
}
|
|
5657
|
+
const shown = detailed ? scoped : scoped.slice(0, CONCISE_MATCHES);
|
|
5462
5658
|
return {
|
|
5463
|
-
summary: summarize(graph),
|
|
5659
|
+
summary: summarize(graph, detailed ? 10 : CONCISE_MATCHES),
|
|
5464
5660
|
mode,
|
|
5465
|
-
context:
|
|
5466
|
-
|
|
5467
|
-
|
|
5661
|
+
// The fact-annotated context block is detail: concise callers navigate
|
|
5662
|
+
// by the ranked matches and fetch nodes on demand (plan P2).
|
|
5663
|
+
...detailed ? { context: q2.context } : {},
|
|
5664
|
+
matches: shown.map((m) => ({ name: m.node.qualifiedName, kind: m.node.kind, file: m.node.file, line: m.node.span.start, score: m.score })),
|
|
5665
|
+
// A neutral availability flag, not a directive to keep querying: a
|
|
5666
|
+
// retrieval tool that coaches "narrow the question / see more" nudges the
|
|
5667
|
+
// over-navigation that re-bills the whole context on every extra step.
|
|
5668
|
+
...scoped.length > shown.length ? { moreAvailable: true } : {},
|
|
5669
|
+
...detailed ? { topImpact } : {}
|
|
5468
5670
|
};
|
|
5469
5671
|
}
|
|
5470
5672
|
},
|
|
5673
|
+
{
|
|
5674
|
+
name: "search_symbols",
|
|
5675
|
+
description: "Find a known name or string fast: ranked symbol lookup with literal file-search fallthrough. Use first for most discovery; use query_graph for meaning.",
|
|
5676
|
+
inputSchema: obj(
|
|
5677
|
+
{
|
|
5678
|
+
query: { type: "string", maxLength: 120 },
|
|
5679
|
+
limit: { type: "number", description: "default 8, max 20" }
|
|
5680
|
+
},
|
|
5681
|
+
["query"]
|
|
5682
|
+
),
|
|
5683
|
+
handler: (graph, args, ctx) => {
|
|
5684
|
+
const limit = Math.min(20, numOr(args.limit, 8));
|
|
5685
|
+
return searchSymbols(graph, ctx.root, String(args.query ?? ""), limit);
|
|
5686
|
+
}
|
|
5687
|
+
},
|
|
5471
5688
|
{
|
|
5472
5689
|
name: "query_graph",
|
|
5473
|
-
description: "
|
|
5690
|
+
description: "Find code by meaning when you don\u2019t know the name: symptoms, relationships, what-breaks-if. For a known name or literal string use search_symbols.",
|
|
5474
5691
|
inputSchema: obj(
|
|
5475
5692
|
{
|
|
5476
|
-
question: { type: "string",
|
|
5477
|
-
|
|
5693
|
+
question: { type: "string", maxLength: 300 },
|
|
5694
|
+
limit: { type: "number", description: "ranked matches to return (default 5)" },
|
|
5695
|
+
offset: { type: "number" },
|
|
5696
|
+
budget: { type: "number", description: "context-block token budget in detailed mode (default 2000)" },
|
|
5697
|
+
response_format: RESPONSE_FORMAT
|
|
5478
5698
|
},
|
|
5479
5699
|
["question"]
|
|
5480
5700
|
),
|
|
5481
5701
|
handler: async (graph, args, ctx) => {
|
|
5482
5702
|
const question = String(args.question ?? "");
|
|
5483
5703
|
const budget = numOr(args.budget, 2e3);
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
if (embedder) {
|
|
5488
|
-
const nodeVectors = await getNodeEmbeddings(graph, embedder, ctx.root);
|
|
5489
|
-
r = await queryGraphSemantic(graph, question, { budget, embedder, nodeVectors });
|
|
5490
|
-
mode = `semantic (${embedder.id})`;
|
|
5491
|
-
} else {
|
|
5492
|
-
r = queryGraph(graph, question, { budget });
|
|
5704
|
+
const { q: r, mode } = await retrieve(graph, question, budget, ctx);
|
|
5705
|
+
if (r.matches.length === 0) {
|
|
5706
|
+
return { mode, matches: [], hint: "no semantic match \u2014 for a known name or literal string use search_symbols; otherwise rephrase around the behaviour you observe" };
|
|
5493
5707
|
}
|
|
5708
|
+
const limit = numOr(args.limit, CONCISE_MATCHES);
|
|
5709
|
+
const offset = Math.max(0, numOrU(args.offset) ?? 0);
|
|
5710
|
+
const page = r.matches.slice(offset, offset + limit);
|
|
5494
5711
|
return {
|
|
5495
5712
|
mode,
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5713
|
+
summary: `${r.matches.length} match${r.matches.length === 1 ? "" : "es"}; top: ${r.matches[0].node.qualifiedName}`,
|
|
5714
|
+
// The fact-annotated context block is detail (plan P2): ranked matches
|
|
5715
|
+
// first, fetch nodes on demand.
|
|
5716
|
+
...isDetailed(args) ? { context: r.context, tokensEstimate: r.tokensEstimate } : {},
|
|
5717
|
+
matches: page.map((m) => ({ name: m.node.qualifiedName, kind: m.node.kind, file: m.node.file, line: m.node.span.start, score: m.score })),
|
|
5718
|
+
// Neutral pagination fact (nextOffset if the model genuinely needs more),
|
|
5719
|
+
// not a "narrow the question" nudge that invites another discovery round.
|
|
5720
|
+
...offset + page.length < r.matches.length ? { moreAvailable: true, nextOffset: offset + page.length } : {}
|
|
5499
5721
|
};
|
|
5500
5722
|
}
|
|
5501
5723
|
},
|
|
5502
5724
|
{
|
|
5503
5725
|
name: "get_node",
|
|
5504
|
-
description: "
|
|
5726
|
+
description: "Inspect one symbol: signature, callers, callees, area. Accepts name, file:line, glob or id.",
|
|
5505
5727
|
inputSchema: obj(
|
|
5506
|
-
{
|
|
5728
|
+
{
|
|
5729
|
+
name: { type: "string" },
|
|
5730
|
+
pick: { type: "number", description: "nth candidate if ambiguous" },
|
|
5731
|
+
...EDGE_FILTER_SCHEMA,
|
|
5732
|
+
response_format: RESPONSE_FORMAT
|
|
5733
|
+
},
|
|
5507
5734
|
["name"]
|
|
5508
5735
|
),
|
|
5509
5736
|
handler: (graph, args, ctx) => {
|
|
5510
5737
|
const { node, candidates } = resolveOne(graph, String(args.name ?? ""), numOrU(args.pick));
|
|
5511
5738
|
if (!node) return unresolved(candidates);
|
|
5512
5739
|
const index = new GraphIndex(graph);
|
|
5740
|
+
const edgeOk = edgeRecordFilter(args);
|
|
5741
|
+
const calleeRecs = index.callees(node.id).filter(edgeOk);
|
|
5742
|
+
const callerRecs = index.callers(node.id).filter(edgeOk);
|
|
5513
5743
|
const base = {
|
|
5514
5744
|
name: node.qualifiedName,
|
|
5515
5745
|
kind: node.kind,
|
|
@@ -5521,10 +5751,11 @@ var TOOLS = [
|
|
|
5521
5751
|
area: node.area
|
|
5522
5752
|
};
|
|
5523
5753
|
if (ctx?.dedup && ctx.seen?.has(node.id)) {
|
|
5524
|
-
return { ...base, repeat: true, callsTotal: uniqueNames(
|
|
5754
|
+
return { ...base, repeat: true, callsTotal: uniqueNames(calleeRecs.map((x) => x.node.qualifiedName)).length, calledByTotal: uniqueNames(callerRecs.map((x) => x.node.qualifiedName)).length };
|
|
5525
5755
|
}
|
|
5526
|
-
const
|
|
5527
|
-
const
|
|
5756
|
+
const edgeCap = isDetailed(args) ? NODE_EDGE_CAP : CONCISE_MATCHES;
|
|
5757
|
+
const calls = boundList(uniqueNames(calleeRecs.map((x) => x.node.qualifiedName)), edgeCap);
|
|
5758
|
+
const calledBy = boundList(uniqueNames(callerRecs.map((x) => x.node.qualifiedName)), edgeCap);
|
|
5528
5759
|
ctx?.seen?.add(node.id);
|
|
5529
5760
|
return {
|
|
5530
5761
|
...base,
|
|
@@ -5537,13 +5768,13 @@ var TOOLS = [
|
|
|
5537
5768
|
},
|
|
5538
5769
|
{
|
|
5539
5770
|
name: "find_path",
|
|
5540
|
-
description: "Shortest connection
|
|
5771
|
+
description: "Shortest connection from a to b.",
|
|
5541
5772
|
inputSchema: obj(
|
|
5542
5773
|
{
|
|
5543
5774
|
a: { type: "string" },
|
|
5544
5775
|
b: { type: "string" },
|
|
5545
|
-
pick_a: { type: "number"
|
|
5546
|
-
pick_b: { type: "number"
|
|
5776
|
+
pick_a: { type: "number" },
|
|
5777
|
+
pick_b: { type: "number" }
|
|
5547
5778
|
},
|
|
5548
5779
|
["a", "b"]
|
|
5549
5780
|
),
|
|
@@ -5560,25 +5791,85 @@ var TOOLS = [
|
|
|
5560
5791
|
},
|
|
5561
5792
|
{
|
|
5562
5793
|
name: "impact_of",
|
|
5563
|
-
description: "
|
|
5794
|
+
description: "Blast radius of a change: what breaks if this symbol changes \u2014 dependents, files, covering tests, risk.",
|
|
5564
5795
|
inputSchema: obj(
|
|
5565
5796
|
{
|
|
5566
5797
|
name: { type: "string" },
|
|
5567
|
-
|
|
5568
|
-
|
|
5798
|
+
change_type: { type: "string", enum: ["modify", "delete", "rename", "add_dependency"] },
|
|
5799
|
+
depth: { type: "number", description: "default 4" },
|
|
5800
|
+
pick: { type: "number", description: "nth candidate if ambiguous" },
|
|
5801
|
+
min_edge_confidence: { type: "number", description: "0..1 \u2014 drop dependents below this (decayed) confidence" },
|
|
5802
|
+
response_format: RESPONSE_FORMAT
|
|
5569
5803
|
},
|
|
5570
5804
|
["name"]
|
|
5571
5805
|
),
|
|
5572
5806
|
handler: (graph, args) => {
|
|
5573
5807
|
const { node, candidates } = resolveOne(graph, String(args.name ?? ""), numOrU(args.pick));
|
|
5574
5808
|
if (!node) return unresolved(candidates);
|
|
5809
|
+
const changeType = ["modify", "delete", "rename", "add_dependency"].includes(String(args.change_type)) ? String(args.change_type) : "modify";
|
|
5575
5810
|
const r = impactOf(graph, node.id, { depth: numOr(args.depth, 4) });
|
|
5576
|
-
|
|
5811
|
+
const minConf = numOrU(args.min_edge_confidence);
|
|
5812
|
+
if (minConf != null) {
|
|
5813
|
+
const kept = r.affected.filter((a) => a.confidence >= minConf);
|
|
5814
|
+
r.affected = kept;
|
|
5815
|
+
r.direct = kept.filter((i) => i.depth === 1).length;
|
|
5816
|
+
r.transitive = kept.filter((i) => i.depth > 1).length;
|
|
5817
|
+
r.minEdgeConfidence = kept.length ? Math.min(...kept.map((i) => i.confidence)) : 1;
|
|
5818
|
+
}
|
|
5819
|
+
const tests = coveringTests(graph, node);
|
|
5820
|
+
const filesAffected = [...new Set(r.affected.map((a) => a.file))];
|
|
5821
|
+
const heavyChange = changeType === "delete" || changeType === "rename";
|
|
5822
|
+
const riskLevel = node.isHub || r.direct >= 10 || r.transitive >= 50 || heavyChange && r.direct >= 3 ? "high" : r.direct >= 3 || r.transitive >= 10 ? "medium" : "low";
|
|
5823
|
+
const summary = `${changeType} ${r.root.name}: ${r.direct} direct dependent${r.direct === 1 ? "" : "s"}, ${r.transitive} transitive across ${filesAffected.length} file${filesAffected.length === 1 ? "" : "s"}; ${tests.length} covering test${tests.length === 1 ? "" : "s"} \u2014 ${riskLevel} risk.`;
|
|
5824
|
+
return {
|
|
5825
|
+
root: r.root.name,
|
|
5826
|
+
changeType,
|
|
5827
|
+
directCallers: r.direct,
|
|
5828
|
+
transitiveCount: r.transitive,
|
|
5829
|
+
filesAffected: filesAffected.slice(0, 20),
|
|
5830
|
+
testsAffected: tests.length,
|
|
5831
|
+
riskLevel,
|
|
5832
|
+
summary,
|
|
5833
|
+
// The full row set is detail; concise callers act on the contract above.
|
|
5834
|
+
...isDetailed(args) ? { affected: r.affected.slice(0, 100).map(stripId) } : r.affected.length > 0 ? { hint: 'response_format:"detailed" lists the affected nodes' } : {}
|
|
5835
|
+
};
|
|
5836
|
+
}
|
|
5837
|
+
},
|
|
5838
|
+
{
|
|
5839
|
+
name: "tests_for",
|
|
5840
|
+
description: "Which tests cover this symbol \u2014 is a change here tested?",
|
|
5841
|
+
inputSchema: obj(
|
|
5842
|
+
{
|
|
5843
|
+
name: { type: "string" },
|
|
5844
|
+
pick: { type: "number", description: "nth candidate if ambiguous" },
|
|
5845
|
+
response_format: RESPONSE_FORMAT
|
|
5846
|
+
},
|
|
5847
|
+
["name"]
|
|
5848
|
+
),
|
|
5849
|
+
handler: (graph, args) => {
|
|
5850
|
+
const { node, candidates } = resolveOne(graph, String(args.name ?? ""), numOrU(args.pick));
|
|
5851
|
+
if (!node) return unresolved(candidates);
|
|
5852
|
+
const covers = coveringTests(graph, node);
|
|
5853
|
+
return {
|
|
5854
|
+
node: node.qualifiedName,
|
|
5855
|
+
tested: node.tested,
|
|
5856
|
+
coverage: node.coverage ?? null,
|
|
5857
|
+
testCount: covers.length,
|
|
5858
|
+
covers: isDetailed(args) ? covers : covers.slice(0, 10),
|
|
5859
|
+
...covers.length > 10 && !isDetailed(args) ? { moreAvailable: true } : {},
|
|
5860
|
+
...covers.length === 0 ? { hint: "no covering tests found \u2014 a change here lands untested" } : {}
|
|
5861
|
+
};
|
|
5577
5862
|
}
|
|
5578
5863
|
},
|
|
5864
|
+
{
|
|
5865
|
+
name: "get_graph_summary",
|
|
5866
|
+
description: "Code map overview: counts, languages, top areas and hubs.",
|
|
5867
|
+
inputSchema: obj({}),
|
|
5868
|
+
handler: (graph) => summarize(graph)
|
|
5869
|
+
},
|
|
5579
5870
|
{
|
|
5580
5871
|
name: "list_areas",
|
|
5581
|
-
description: "
|
|
5872
|
+
description: "Code areas (communities) by size.",
|
|
5582
5873
|
inputSchema: obj({ limit: { type: "number" } }),
|
|
5583
5874
|
// Strip `members` (raw content-hash id arrays): a model cannot use them and
|
|
5584
5875
|
// on a large repo they ballooned this result past 20k tokens.
|
|
@@ -5586,23 +5877,13 @@ var TOOLS = [
|
|
|
5586
5877
|
},
|
|
5587
5878
|
{
|
|
5588
5879
|
name: "list_hubs",
|
|
5589
|
-
description: "
|
|
5880
|
+
description: "Most-depended-on symbols.",
|
|
5590
5881
|
inputSchema: obj({ limit: { type: "number" } }),
|
|
5591
5882
|
handler: (graph, args) => graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external").sort((a, b) => b.importance - a.importance).slice(0, numOr(args.limit, 20)).map((n) => ({ name: n.qualifiedName, kind: n.kind, file: n.file, line: n.span.start, importance: n.importance, isHub: n.isHub }))
|
|
5592
5883
|
},
|
|
5593
|
-
{
|
|
5594
|
-
name: "tests_for",
|
|
5595
|
-
description: "Which tests cover a node (static call linkage + runtime coverage), with the linkage basis.",
|
|
5596
|
-
inputSchema: obj({ name: { type: "string" }, pick: { type: "number", description: "choose nth candidate when ambiguous" } }, ["name"]),
|
|
5597
|
-
handler: (graph, args) => {
|
|
5598
|
-
const { node, candidates } = resolveOne(graph, String(args.name ?? ""), numOrU(args.pick));
|
|
5599
|
-
if (!node) return unresolved(candidates);
|
|
5600
|
-
return { node: node.qualifiedName, tested: node.tested, coverage: node.coverage ?? null, covers: coveringTests(graph, node) };
|
|
5601
|
-
}
|
|
5602
|
-
},
|
|
5603
5884
|
{
|
|
5604
5885
|
name: "get_facts",
|
|
5605
|
-
description: "
|
|
5886
|
+
description: "Deterministic facts for a node (contract/invariant); needs a --deep build.",
|
|
5606
5887
|
inputSchema: obj({ name: { type: "string" } }, ["name"]),
|
|
5607
5888
|
handler: (graph, args) => {
|
|
5608
5889
|
const { node, candidates } = resolveOne(graph, String(args.name ?? ""));
|
|
@@ -5613,7 +5894,7 @@ var TOOLS = [
|
|
|
5613
5894
|
},
|
|
5614
5895
|
{
|
|
5615
5896
|
name: "guide_node",
|
|
5616
|
-
description: "Cited
|
|
5897
|
+
description: "Cited standards/practices for a node (OWASP/CWE).",
|
|
5617
5898
|
inputSchema: obj({ name: { type: "string" } }, ["name"]),
|
|
5618
5899
|
handler: (graph, args) => {
|
|
5619
5900
|
const { node, candidates } = resolveOne(graph, String(args.name ?? ""));
|
|
@@ -5627,17 +5908,17 @@ var TOOLS = [
|
|
|
5627
5908
|
},
|
|
5628
5909
|
{
|
|
5629
5910
|
name: "check_drift",
|
|
5630
|
-
description:
|
|
5911
|
+
description: "Offline dependency inventory (npm/pypi/go); attribute:true adds git who-added attribution.",
|
|
5631
5912
|
inputSchema: obj(
|
|
5632
|
-
{ attribute: { type: "boolean"
|
|
5913
|
+
{ attribute: { type: "boolean" } },
|
|
5633
5914
|
[]
|
|
5634
5915
|
),
|
|
5635
5916
|
handler: (_graph, args, ctx) => attributedInventory(ctx.root, { attribute: args.attribute === true })
|
|
5636
5917
|
},
|
|
5637
5918
|
{
|
|
5638
5919
|
name: "vuln_attribution",
|
|
5639
|
-
description: "Who introduced each open vulnerability
|
|
5640
|
-
inputSchema: obj({ package: { type: "string", description: "
|
|
5920
|
+
description: "Who introduced each open vulnerability, exposure windows, and CRA remediation metrics (MTTR, SLA breaches). Reads the last `vg scan --vulns`.",
|
|
5921
|
+
inputSchema: obj({ package: { type: "string", description: "restrict to one package" } }, []),
|
|
5641
5922
|
handler: (_graph, args, ctx) => {
|
|
5642
5923
|
const data = loadVulnerabilities(ctx.root);
|
|
5643
5924
|
if (!data) {
|
|
@@ -5666,9 +5947,9 @@ var TOOLS = [
|
|
|
5666
5947
|
},
|
|
5667
5948
|
{
|
|
5668
5949
|
name: "list_vulnerabilities",
|
|
5669
|
-
description: "Known vulnerabilities
|
|
5950
|
+
description: "Known vulnerabilities from the last `vg scan --vulns`: id/CVE, severity, CVSS, fixed version.",
|
|
5670
5951
|
inputSchema: obj(
|
|
5671
|
-
{ severity: { type: "string", enum: ["low", "moderate", "high", "critical"], description: "
|
|
5952
|
+
{ severity: { type: "string", enum: ["low", "moderate", "high", "critical"], description: "minimum severity" } },
|
|
5672
5953
|
[]
|
|
5673
5954
|
),
|
|
5674
5955
|
handler: (_graph, args, ctx) => {
|
|
@@ -5704,11 +5985,11 @@ var TOOLS = [
|
|
|
5704
5985
|
},
|
|
5705
5986
|
{
|
|
5706
5987
|
name: "upgrade_impact",
|
|
5707
|
-
description:
|
|
5988
|
+
description: "What breaks if you upgrade a package: major distance, import blast radius, vulns fixed, recommended posture. changelog:true adds GitHub breaking-change signals (online).",
|
|
5708
5989
|
inputSchema: obj(
|
|
5709
5990
|
{
|
|
5710
|
-
package: { type: "string"
|
|
5711
|
-
changelog: { type: "boolean"
|
|
5991
|
+
package: { type: "string" },
|
|
5992
|
+
changelog: { type: "boolean" }
|
|
5712
5993
|
},
|
|
5713
5994
|
["package"]
|
|
5714
5995
|
),
|
|
@@ -5724,19 +6005,19 @@ var TOOLS = [
|
|
|
5724
6005
|
},
|
|
5725
6006
|
{
|
|
5726
6007
|
name: "list_models",
|
|
5727
|
-
description: "
|
|
6008
|
+
description: "Local models on disk (Ollama / LM Studio / gguf).",
|
|
5728
6009
|
inputSchema: obj({}),
|
|
5729
6010
|
handler: () => ({ models: discoverModels() })
|
|
5730
6011
|
},
|
|
5731
6012
|
{
|
|
5732
6013
|
name: "resolve_library",
|
|
5733
|
-
description: "Resolve a library
|
|
6014
|
+
description: "Resolve a library to its canonical id and the version YOUR project uses (drift-annotated).",
|
|
5734
6015
|
// Canonical §4: `query` (+ optional `context_project`). `name` kept as a back-compat alias.
|
|
5735
6016
|
inputSchema: obj(
|
|
5736
6017
|
{
|
|
5737
|
-
query: { type: "string", description: "library name or
|
|
5738
|
-
name: { type: "string"
|
|
5739
|
-
context_project: { type: "string"
|
|
6018
|
+
query: { type: "string", description: "library name or query" },
|
|
6019
|
+
name: { type: "string" },
|
|
6020
|
+
context_project: { type: "string" }
|
|
5740
6021
|
},
|
|
5741
6022
|
[]
|
|
5742
6023
|
),
|
|
@@ -5779,19 +6060,19 @@ var TOOLS = [
|
|
|
5779
6060
|
},
|
|
5780
6061
|
{
|
|
5781
6062
|
name: "library_docs",
|
|
5782
|
-
description: "Version-correct
|
|
6063
|
+
description: "Version-correct usage docs for a library, sliced to a token budget.",
|
|
5783
6064
|
// Canonical §4: `targetId`/`query`, `verbosity`, `max_tokens`. `name`/`tokens` kept as aliases.
|
|
5784
6065
|
inputSchema: obj(
|
|
5785
6066
|
{
|
|
5786
|
-
targetId: { type: "string", description: "
|
|
6067
|
+
targetId: { type: "string", description: "id from resolve_library" },
|
|
5787
6068
|
query: { type: "string", description: "library name or query" },
|
|
5788
|
-
name: { type: "string"
|
|
5789
|
-
context_project: { type: "string"
|
|
5790
|
-
verbosity: { type: "string", enum: ["concise", "balanced", "exhaustive"]
|
|
5791
|
-
max_tokens: { type: "number", description: "
|
|
5792
|
-
tokens: { type: "number"
|
|
5793
|
-
enterprise_strict: { type: "boolean"
|
|
5794
|
-
follow_up: { type: "boolean"
|
|
6069
|
+
name: { type: "string" },
|
|
6070
|
+
context_project: { type: "string" },
|
|
6071
|
+
verbosity: { type: "string", enum: ["concise", "balanced", "exhaustive"] },
|
|
6072
|
+
max_tokens: { type: "number", description: "token budget" },
|
|
6073
|
+
tokens: { type: "number" },
|
|
6074
|
+
enterprise_strict: { type: "boolean" },
|
|
6075
|
+
follow_up: { type: "boolean" }
|
|
5795
6076
|
},
|
|
5796
6077
|
[]
|
|
5797
6078
|
),
|
|
@@ -5886,15 +6167,15 @@ var TOOLS = [
|
|
|
5886
6167
|
}
|
|
5887
6168
|
];
|
|
5888
6169
|
var VERBOSITY_BUDGET = { concise: 1500, balanced: 4e3, exhaustive: 12e3 };
|
|
5889
|
-
function summarize(graph) {
|
|
6170
|
+
function summarize(graph, top = 10) {
|
|
5890
6171
|
return {
|
|
5891
6172
|
counts: graph.meta.counts,
|
|
5892
6173
|
languages: graph.meta.languages,
|
|
5893
6174
|
cluster: graph.meta.cluster,
|
|
5894
6175
|
resolver: graph.provenance.resolver,
|
|
5895
6176
|
generatedAt: graph.generatedAt,
|
|
5896
|
-
topAreas: [...graph.areas].sort((a, b) => b.size - a.size).slice(0,
|
|
5897
|
-
topHubs: graph.nodes.filter((n) => n.isHub).sort((a, b) => b.importance - a.importance).slice(0,
|
|
6177
|
+
topAreas: [...graph.areas].sort((a, b) => b.size - a.size).slice(0, top).map((a) => ({ id: a.id, label: a.label, size: a.size })),
|
|
6178
|
+
topHubs: graph.nodes.filter((n) => n.isHub).sort((a, b) => b.importance - a.importance).slice(0, top).map((n) => ({ name: n.qualifiedName, file: n.file, importance: n.importance }))
|
|
5898
6179
|
};
|
|
5899
6180
|
}
|
|
5900
6181
|
function stripId(item) {
|
|
@@ -5925,6 +6206,19 @@ function numOrU(v) {
|
|
|
5925
6206
|
function uniqueNames(xs) {
|
|
5926
6207
|
return [...new Set(xs)];
|
|
5927
6208
|
}
|
|
6209
|
+
var HOT_TOOLS = ["orient", "search_symbols", "query_graph", "get_node"];
|
|
6210
|
+
function deferredToolNames() {
|
|
6211
|
+
const hot = new Set(HOT_TOOLS);
|
|
6212
|
+
return TOOLS.map((t) => t.name).filter((n) => !hot.has(n));
|
|
6213
|
+
}
|
|
6214
|
+
function navigationToolsetConfig(serverName = "vibgrate") {
|
|
6215
|
+
return {
|
|
6216
|
+
type: "mcp_toolset",
|
|
6217
|
+
mcp_server_name: serverName,
|
|
6218
|
+
default_config: { defer_loading: true },
|
|
6219
|
+
configs: Object.fromEntries(HOT_TOOLS.map((n) => [n, { defer_loading: false }]))
|
|
6220
|
+
};
|
|
6221
|
+
}
|
|
5928
6222
|
var LEDGER = "savings.jsonl";
|
|
5929
6223
|
var PER_FILE_TOKENS = 400;
|
|
5930
6224
|
function ledgerPath(root) {
|
|
@@ -6047,7 +6341,13 @@ function createServer(source, opts = {}) {
|
|
|
6047
6341
|
const seen = /* @__PURE__ */ new Set();
|
|
6048
6342
|
const server = new Server(
|
|
6049
6343
|
{ name: "vg", version: VERSION },
|
|
6050
|
-
{
|
|
6344
|
+
{
|
|
6345
|
+
capabilities: { tools: {} },
|
|
6346
|
+
// Routing guidance once at the server level (hosts that surface
|
|
6347
|
+
// `instructions` get it at zero per-step schema cost): the flashlight
|
|
6348
|
+
// vs the map.
|
|
6349
|
+
instructions: 'vg is a code map. Use search_symbols to find a known name or literal string fast. Use orient/query_graph for meaning: symptoms, relationships, and what-breaks-if. Responses are concise by default; pass response_format:"detailed" only when a node proves load-bearing. Navigate as little as possible: one good search/query usually locates the code. As soon as you have the file and line, read that file and make the edit \u2014 do not call further graph tools unless the edit fails or the match was wrong.'
|
|
6350
|
+
}
|
|
6051
6351
|
);
|
|
6052
6352
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
6053
6353
|
tools: TOOLS.map((t) => ({
|
|
@@ -6086,6 +6386,7 @@ function createServer(source, opts = {}) {
|
|
|
6086
6386
|
async function serveStdio(graphPath, opts = {}) {
|
|
6087
6387
|
const source = new GraphSource(graphPath, opts.refresh !== false);
|
|
6088
6388
|
const server = createServer(source, opts);
|
|
6389
|
+
warmEmbedderInBackground(opts.local);
|
|
6089
6390
|
await server.connect(new StdioServerTransport());
|
|
6090
6391
|
}
|
|
6091
6392
|
function sleep(ms) {
|
|
@@ -6310,6 +6611,18 @@ function writeFileEnsured(file, content) {
|
|
|
6310
6611
|
fs18.mkdirSync(path18.dirname(file), { recursive: true });
|
|
6311
6612
|
fs18.writeFileSync(file, content);
|
|
6312
6613
|
}
|
|
6614
|
+
function writeNavigationConfig(root) {
|
|
6615
|
+
const rel2 = path18.join(".vibgrate", "mcp-navigation.json");
|
|
6616
|
+
const doc = {
|
|
6617
|
+
_readme: "Deferred-loading config for agents embedding `vg serve` via the Claude API (defer_loading + tool-search). The hot navigation core stays in context; the rest load on demand \u2014 ~350-450 schema tokens/step vs ~1,881 for the full set. Hosts without defer_loading ignore this and serve the whole (already optimized) tool set. See docs/graph/VG-NAVIGATION-PROFILE.md.",
|
|
6618
|
+
hot_core: [...HOT_TOOLS],
|
|
6619
|
+
deferred: deferredToolNames(),
|
|
6620
|
+
toolset: navigationToolsetConfig()
|
|
6621
|
+
};
|
|
6622
|
+
writeFileEnsured(path18.join(root, rel2), `${JSON.stringify(doc, null, 2)}
|
|
6623
|
+
`);
|
|
6624
|
+
return rel2;
|
|
6625
|
+
}
|
|
6313
6626
|
function upsertMcp(file, target, launch) {
|
|
6314
6627
|
const config = readJson(file);
|
|
6315
6628
|
const entry = mcpServerEntry(launch);
|
|
@@ -6399,6 +6712,8 @@ function formatForExt(ext) {
|
|
|
6399
6712
|
return "dot";
|
|
6400
6713
|
case ".cypher":
|
|
6401
6714
|
return "cypher";
|
|
6715
|
+
case ".sql":
|
|
6716
|
+
return "sql";
|
|
6402
6717
|
case ".md":
|
|
6403
6718
|
return "md";
|
|
6404
6719
|
case ".html":
|
|
@@ -6423,6 +6738,8 @@ function exportGraph(format, ctx) {
|
|
|
6423
6738
|
return dot(ctx.graph);
|
|
6424
6739
|
case "cypher":
|
|
6425
6740
|
return cypher(ctx.graph);
|
|
6741
|
+
case "sql":
|
|
6742
|
+
return sql(ctx);
|
|
6426
6743
|
case "cyclonedx":
|
|
6427
6744
|
return cyclonedx(ctx);
|
|
6428
6745
|
case "spdx":
|
|
@@ -6485,6 +6802,66 @@ function cypherLabel(s) {
|
|
|
6485
6802
|
function q(s) {
|
|
6486
6803
|
return JSON.stringify(s);
|
|
6487
6804
|
}
|
|
6805
|
+
function sql(ctx) {
|
|
6806
|
+
const graph = ctx.graph;
|
|
6807
|
+
const lines = [];
|
|
6808
|
+
lines.push("BEGIN;");
|
|
6809
|
+
lines.push("CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);");
|
|
6810
|
+
const meta = [
|
|
6811
|
+
["schemaVersion", graph.schemaVersion],
|
|
6812
|
+
["generatedAt", ctx.generatedAt],
|
|
6813
|
+
["corpusHash", graph.provenance.corpusHash],
|
|
6814
|
+
["tool", graph.provenance.tool],
|
|
6815
|
+
["toolVersion", graph.provenance.version],
|
|
6816
|
+
["resolver", graph.provenance.resolver.join(",")],
|
|
6817
|
+
["fingerprint", graph.provenance.toolchain?.fingerprint ?? null]
|
|
6818
|
+
];
|
|
6819
|
+
for (const [k, v] of meta) lines.push(`INSERT INTO meta VALUES (${sqlStr(k)}, ${sqlStr(v)});`);
|
|
6820
|
+
lines.push(
|
|
6821
|
+
"CREATE TABLE IF NOT EXISTS nodes (id TEXT PRIMARY KEY, kind TEXT, name TEXT, qualified_name TEXT, file TEXT, span_start INTEGER, span_end INTEGER, lang TEXT, visibility TEXT, signature TEXT, importance REAL, area INTEGER, is_hub INTEGER, tested INTEGER, coverage REAL);"
|
|
6822
|
+
);
|
|
6823
|
+
for (const n of graph.nodes) {
|
|
6824
|
+
lines.push(
|
|
6825
|
+
`INSERT INTO nodes VALUES (${sqlStr(n.id)}, ${sqlStr(n.kind)}, ${sqlStr(n.name)}, ${sqlStr(n.qualifiedName)}, ${sqlStr(n.file)}, ${sqlNum(n.span.start)}, ${sqlNum(n.span.end)}, ${sqlStr(n.lang)}, ${sqlStr(n.visibility)}, ${sqlStr(n.signature)}, ${sqlNum(n.importance)}, ${sqlNum(n.area)}, ${sqlBool(n.isHub)}, ${sqlBool(n.tested)}, ${sqlNum(n.coverage)});`
|
|
6826
|
+
);
|
|
6827
|
+
}
|
|
6828
|
+
lines.push(
|
|
6829
|
+
'CREATE TABLE IF NOT EXISTS edges (id TEXT PRIMARY KEY, kind TEXT, src TEXT, dst TEXT, resolution TEXT, confidence REAL, epistemic TEXT, surprise REAL, "count" INTEGER);'
|
|
6830
|
+
);
|
|
6831
|
+
for (const e of graph.edges) {
|
|
6832
|
+
lines.push(
|
|
6833
|
+
`INSERT INTO edges VALUES (${sqlStr(e.id)}, ${sqlStr(e.kind)}, ${sqlStr(e.src)}, ${sqlStr(e.dst)}, ${sqlStr(e.resolution)}, ${sqlNum(e.confidence)}, ${sqlStr(e.epistemic)}, ${sqlNum(e.surprise)}, ${sqlNum(e.count)});`
|
|
6834
|
+
);
|
|
6835
|
+
}
|
|
6836
|
+
lines.push("CREATE TABLE IF NOT EXISTS areas (id INTEGER PRIMARY KEY, label TEXT, size INTEGER, cohesion REAL, external_edges INTEGER);");
|
|
6837
|
+
lines.push("CREATE TABLE IF NOT EXISTS area_members (area_id INTEGER, node_id TEXT);");
|
|
6838
|
+
for (const a of graph.areas) {
|
|
6839
|
+
lines.push(`INSERT INTO areas VALUES (${sqlNum(a.id)}, ${sqlStr(a.label)}, ${sqlNum(a.size)}, ${sqlNum(a.cohesion)}, ${sqlNum(a.externalEdges)});`);
|
|
6840
|
+
for (const m of a.members) lines.push(`INSERT INTO area_members VALUES (${sqlNum(a.id)}, ${sqlStr(m)});`);
|
|
6841
|
+
}
|
|
6842
|
+
if (graph.facts && graph.facts.length) {
|
|
6843
|
+
lines.push("CREATE TABLE IF NOT EXISTS facts (id TEXT PRIMARY KEY, kind TEXT, predicate_json TEXT, derived_by TEXT, confidence TEXT);");
|
|
6844
|
+
lines.push("CREATE TABLE IF NOT EXISTS fact_subjects (fact_id TEXT, node_id TEXT);");
|
|
6845
|
+
lines.push("CREATE TABLE IF NOT EXISTS fact_evidence (fact_id TEXT, file TEXT, span_start INTEGER, span_end INTEGER);");
|
|
6846
|
+
for (const f of graph.facts) {
|
|
6847
|
+
lines.push(`INSERT INTO facts VALUES (${sqlStr(f.id)}, ${sqlStr(f.kind)}, ${sqlStr(JSON.stringify(f.predicate))}, ${sqlStr(f.derivedBy)}, ${sqlStr(f.confidence)});`);
|
|
6848
|
+
for (const s of f.subjectIds) lines.push(`INSERT INTO fact_subjects VALUES (${sqlStr(f.id)}, ${sqlStr(s)});`);
|
|
6849
|
+
for (const ev of f.evidence) lines.push(`INSERT INTO fact_evidence VALUES (${sqlStr(f.id)}, ${sqlStr(ev.file)}, ${sqlNum(ev.span.start)}, ${sqlNum(ev.span.end)});`);
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6852
|
+
lines.push("COMMIT;");
|
|
6853
|
+
return lines.join("\n") + "\n";
|
|
6854
|
+
}
|
|
6855
|
+
function sqlStr(v) {
|
|
6856
|
+
if (v == null) return "NULL";
|
|
6857
|
+
return `'${v.replace(/'/g, "''")}'`;
|
|
6858
|
+
}
|
|
6859
|
+
function sqlNum(v) {
|
|
6860
|
+
return v == null || !Number.isFinite(v) ? "NULL" : String(v);
|
|
6861
|
+
}
|
|
6862
|
+
function sqlBool(v) {
|
|
6863
|
+
return v == null ? "NULL" : v ? "1" : "0";
|
|
6864
|
+
}
|
|
6488
6865
|
function cyclonedx(ctx) {
|
|
6489
6866
|
const components = [];
|
|
6490
6867
|
for (const d of ctx.deps ?? []) {
|
|
@@ -6524,6 +6901,6 @@ function spdx(ctx) {
|
|
|
6524
6901
|
return JSON.stringify(doc, null, 2) + "\n";
|
|
6525
6902
|
}
|
|
6526
6903
|
|
|
6527
|
-
export { ASSISTANTS, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, saveCatalog, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeSnapshot, writeStoredCredentials };
|
|
6528
|
-
//# sourceMappingURL=chunk-
|
|
6529
|
-
//# sourceMappingURL=chunk-
|
|
6904
|
+
export { ASSISTANTS, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, saveCatalog, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
|
|
6905
|
+
//# sourceMappingURL=chunk-2J7DPIAL.js.map
|
|
6906
|
+
//# sourceMappingURL=chunk-2J7DPIAL.js.map
|