@vibgrate/cli 2026.722.2 → 2026.727.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +418 -213
- package/README.md +34 -8
- package/dist/baseline-TG4ATZGZ.js +8 -0
- package/dist/{baseline-BD3A7EXD.js.map → baseline-TG4ATZGZ.js.map} +1 -1
- package/dist/chunk-2MBL43EJ.js +82 -0
- package/dist/chunk-2MBL43EJ.js.map +1 -0
- package/dist/chunk-2XQDMLKB.js +465 -0
- package/dist/chunk-2XQDMLKB.js.map +1 -0
- package/dist/{chunk-PY3DNX5H.js → chunk-3Q7QBWFW.js} +13 -2
- package/dist/chunk-3Q7QBWFW.js.map +1 -0
- package/dist/chunk-3V24O47U.js +478 -0
- package/dist/chunk-3V24O47U.js.map +1 -0
- package/dist/chunk-5I4VSBWQ.js +764 -0
- package/dist/chunk-5I4VSBWQ.js.map +1 -0
- package/dist/{chunk-WNIIKCNF.js → chunk-6ABXHZRN.js} +59 -311
- package/dist/chunk-6ABXHZRN.js.map +1 -0
- package/dist/chunk-6AYDFFP5.js +136 -0
- package/dist/chunk-6AYDFFP5.js.map +1 -0
- package/dist/chunk-6UQMXIAG.js +122 -0
- package/dist/chunk-6UQMXIAG.js.map +1 -0
- package/dist/chunk-7N4Y3E47.js +108 -0
- package/dist/chunk-7N4Y3E47.js.map +1 -0
- package/dist/chunk-BPF3YX2U.js +64 -0
- package/dist/chunk-BPF3YX2U.js.map +1 -0
- package/dist/chunk-BWPBB45M.js +88 -0
- package/dist/chunk-BWPBB45M.js.map +1 -0
- package/dist/{chunk-6CXTPC74.js → chunk-CY4WKXB2.js} +27 -6
- package/dist/chunk-CY4WKXB2.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +10 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/{chunk-NONYJLOJ.js → chunk-EX4PQT6W.js} +3 -3
- package/dist/{chunk-NONYJLOJ.js.map → chunk-EX4PQT6W.js.map} +1 -1
- package/dist/chunk-IS7VTH2Q.js +627 -0
- package/dist/chunk-IS7VTH2Q.js.map +1 -0
- package/dist/chunk-IZDH67DX.js +289 -0
- package/dist/chunk-IZDH67DX.js.map +1 -0
- package/dist/chunk-J4RFRKZH.js +2496 -0
- package/dist/chunk-J4RFRKZH.js.map +1 -0
- package/dist/chunk-J7LKEB2R.js +51 -0
- package/dist/chunk-J7LKEB2R.js.map +1 -0
- package/dist/chunk-JIBOJTGS.js +313 -0
- package/dist/chunk-JIBOJTGS.js.map +1 -0
- package/dist/chunk-K3MZIVZW.js +88 -0
- package/dist/chunk-K3MZIVZW.js.map +1 -0
- package/dist/chunk-K7REHDQS.js +776 -0
- package/dist/chunk-K7REHDQS.js.map +1 -0
- package/dist/{chunk-2O5YZVZV.js → chunk-KCKYKDMS.js} +23 -19
- package/dist/chunk-KCKYKDMS.js.map +1 -0
- package/dist/{chunk-3MXNNBLI.js → chunk-KNELIQJO.js} +1647 -2042
- package/dist/chunk-KNELIQJO.js.map +1 -0
- package/dist/chunk-KSBHGWRV.js +613 -0
- package/dist/chunk-KSBHGWRV.js.map +1 -0
- package/dist/chunk-LKVMI67M.js +1695 -0
- package/dist/chunk-LKVMI67M.js.map +1 -0
- package/dist/chunk-NDMDSPFX.js +69 -0
- package/dist/chunk-NDMDSPFX.js.map +1 -0
- package/dist/{chunk-CS37OBE3.js → chunk-OFUC6RYN.js} +791 -783
- package/dist/chunk-OFUC6RYN.js.map +1 -0
- package/dist/chunk-OOIG3RUM.js +276 -0
- package/dist/chunk-OOIG3RUM.js.map +1 -0
- package/dist/chunk-PMYWZRRS.js +62 -0
- package/dist/chunk-PMYWZRRS.js.map +1 -0
- package/dist/chunk-PXHNTC6D.js +382 -0
- package/dist/chunk-PXHNTC6D.js.map +1 -0
- package/dist/{chunk-I7GY7MVN.js → chunk-RNP3SMTV.js} +60 -12
- package/dist/chunk-RNP3SMTV.js.map +1 -0
- package/dist/chunk-TCDAZZNP.js +843 -0
- package/dist/chunk-TCDAZZNP.js.map +1 -0
- package/dist/{chunk-5BDHQ7DD.js → chunk-WGUL45AV.js} +24 -17
- package/dist/chunk-WGUL45AV.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +3373 -617
- package/dist/cli.js.map +1 -1
- package/dist/execution-env-ZU45JZ4G.js +4 -0
- package/dist/execution-env-ZU45JZ4G.js.map +1 -0
- package/dist/federation-SZWZIFZ7.js +7 -0
- package/dist/federation-SZWZIFZ7.js.map +1 -0
- package/dist/{fs-IPY4FJKH.js → fs-B5HZ2MIU.js} +3 -2
- package/dist/fs-B5HZ2MIU.js.map +1 -0
- package/dist/{fs-KCABDURV.js → fs-O5IQYXHO.js} +3 -2
- package/dist/fs-O5IQYXHO.js.map +1 -0
- package/dist/git-ref-XDXB6JP5.js +4 -0
- package/dist/git-ref-XDXB6JP5.js.map +1 -0
- package/dist/graph-backend-73URGTFY.js +11 -0
- package/dist/graph-backend-73URGTFY.js.map +1 -0
- package/dist/index.d.ts +472 -137
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/{interactive-A4DISSIW.js → interactive-372NLI5C.js} +186 -226
- package/dist/interactive-372NLI5C.js.map +1 -0
- package/dist/llm-host-7GWDO6IX.js +9 -0
- package/dist/llm-host-7GWDO6IX.js.map +1 -0
- package/dist/load-I333TGNI.js +8 -0
- package/dist/load-I333TGNI.js.map +1 -0
- package/dist/local-runtime-5O2NN3GA.js +5 -0
- package/dist/local-runtime-5O2NN3GA.js.map +1 -0
- package/dist/mcp-tools-I7JK3S2M.js +4 -0
- package/dist/{mcp-tools-I6PME3AV.js.map → mcp-tools-I7JK3S2M.js.map} +1 -1
- package/dist/model-execution-profile-TFBKCNJG.js +4 -0
- package/dist/model-execution-profile-TFBKCNJG.js.map +1 -0
- package/dist/model-orchestrator-TVONAUPS.js +9 -0
- package/dist/model-orchestrator-TVONAUPS.js.map +1 -0
- 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/paths-2RAH3GME.js +6 -0
- package/dist/paths-2RAH3GME.js.map +1 -0
- package/dist/resolve-gguf-VPL4YQ6J.js +8 -0
- package/dist/resolve-gguf-VPL4YQ6J.js.map +1 -0
- package/dist/runtime-session-JRKCQUHY.js +18 -0
- package/dist/runtime-session-JRKCQUHY.js.map +1 -0
- package/dist/session-ITPYQ5XI.js +11 -0
- package/dist/{session-POJUDFPH.js.map → session-ITPYQ5XI.js.map} +1 -1
- package/dist/session-store-WFBX7HXL.js +4 -0
- package/dist/{session-store-W5RZZHA3.js.map → session-store-WFBX7HXL.js.map} +1 -1
- package/dist/{stream-json-Q2JGWPHB.js → stream-json-SLMDKLXP.js} +17 -6
- package/dist/stream-json-SLMDKLXP.js.map +1 -0
- package/dist/{types-Jl6RJ175.d.ts → types-7AHBUbFm.d.ts} +8 -2
- package/dist/ui-Q6DJ3GCU.js +5 -0
- package/dist/ui-Q6DJ3GCU.js.map +1 -0
- package/dist/vgd-7JV5KCNJ.js +17 -0
- package/dist/vgd-7JV5KCNJ.js.map +1 -0
- package/package.json +9 -6
- package/dist/baseline-BD3A7EXD.js +0 -7
- package/dist/chunk-2O5YZVZV.js.map +0 -1
- package/dist/chunk-3MXNNBLI.js.map +0 -1
- package/dist/chunk-5BDHQ7DD.js.map +0 -1
- package/dist/chunk-6CXTPC74.js.map +0 -1
- package/dist/chunk-CS37OBE3.js.map +0 -1
- package/dist/chunk-GGJZA3Q6.js +0 -961
- package/dist/chunk-GGJZA3Q6.js.map +0 -1
- package/dist/chunk-I7GY7MVN.js.map +0 -1
- package/dist/chunk-JBXNQCGE.js +0 -484
- package/dist/chunk-JBXNQCGE.js.map +0 -1
- package/dist/chunk-PY3DNX5H.js.map +0 -1
- package/dist/chunk-WNIIKCNF.js.map +0 -1
- package/dist/fs-IPY4FJKH.js.map +0 -1
- package/dist/fs-KCABDURV.js.map +0 -1
- package/dist/interactive-A4DISSIW.js.map +0 -1
- package/dist/mcp-tools-I6PME3AV.js +0 -3
- package/dist/session-POJUDFPH.js +0 -6
- package/dist/session-store-W5RZZHA3.js +0 -3
- package/dist/stream-json-Q2JGWPHB.js.map +0 -1
- package/dist/ui-HOVOPWMX.js +0 -4
- package/dist/ui-HOVOPWMX.js.map +0 -1
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { impactOf, indexFor, withTimeout, getNodeEmbeddings, queryGraphSemantic, queryGraph, loadEmbedder, isModelReady,
|
|
1
|
+
import { loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-WGUL45AV.js';
|
|
2
|
+
import { resolveVersion, resolveLib, loadCatalog, driftFor, localPackageDocs, readDoc, inventory, localApiSurface, edgeId, buildModuleResolver, resolve, applyStaticTestLinkage, nodeId } from './chunk-LKVMI67M.js';
|
|
3
|
+
import { resolveOne, findNodes } from './chunk-J7LKEB2R.js';
|
|
4
|
+
import { discoverModels } from './chunk-OOIG3RUM.js';
|
|
5
|
+
import { impactOf, indexFor, withTimeout, getNodeEmbeddings, queryGraphSemantic, queryGraph, loadEmbedder, isModelReady, acquireLock, releaseLock } from './chunk-6ABXHZRN.js';
|
|
6
|
+
import { langById, grammarSetVersion, langForExtension, setGrammarsOverride, parseSource } from './chunk-3Q7QBWFW.js';
|
|
7
|
+
import { redactSecrets } from './chunk-RXP66R2E.js';
|
|
8
|
+
import { writeGraphIndex, SCHEMA_VERSION, loadGraph, ensureVibgrateGitignore, vibgrateDir, writeArtifacts } from './chunk-2XQDMLKB.js';
|
|
9
|
+
import { cacheDir, loadCache, serializeGraph, stableStringify } from './chunk-K3MZIVZW.js';
|
|
6
10
|
import { shortId, canonicalize, hashString, hashBytes } from './chunk-JFGYT6BI.js';
|
|
7
|
-
import
|
|
11
|
+
import { writeTextFile, pathExists, readJsonFile } from './chunk-CRAMOEBE.js';
|
|
12
|
+
import * as fs16 from 'fs';
|
|
8
13
|
import { spawnSync, execFileSync } from 'child_process';
|
|
9
|
-
import * as
|
|
14
|
+
import * as path18 from 'path';
|
|
10
15
|
import ignore from 'ignore';
|
|
11
16
|
import * as v8 from 'v8';
|
|
12
17
|
import Graph from 'graphology';
|
|
@@ -24,8 +29,8 @@ import { Command } from 'commander';
|
|
|
24
29
|
import chalk from 'chalk';
|
|
25
30
|
import * as zlib from 'zlib';
|
|
26
31
|
import { promisify } from 'util';
|
|
27
|
-
import { encode, decode } from 'gpt-tokenizer/encoding/cl100k_base';
|
|
28
32
|
import { Worker } from 'worker_threads';
|
|
33
|
+
import { encode, decode } from 'gpt-tokenizer/encoding/cl100k_base';
|
|
29
34
|
|
|
30
35
|
var NPX_INVOCATION = "npx @vibgrate/cli";
|
|
31
36
|
function whichOnPath(cmd) {
|
|
@@ -41,9 +46,9 @@ function whichOnPath(cmd) {
|
|
|
41
46
|
}
|
|
42
47
|
function isOwnBinary(binPath) {
|
|
43
48
|
try {
|
|
44
|
-
const real =
|
|
49
|
+
const real = fs16.realpathSync(binPath);
|
|
45
50
|
if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
|
|
46
|
-
const head =
|
|
51
|
+
const head = fs16.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
|
|
47
52
|
return head.includes("@vibgrate/cli") || head.includes("vibgrate");
|
|
48
53
|
} catch {
|
|
49
54
|
return false;
|
|
@@ -53,7 +58,7 @@ function isEphemeralNpxBinary(binPath) {
|
|
|
53
58
|
const NPX_SEGMENT = /[\\/]_npx[\\/]/;
|
|
54
59
|
if (NPX_SEGMENT.test(binPath)) return true;
|
|
55
60
|
try {
|
|
56
|
-
return NPX_SEGMENT.test(
|
|
61
|
+
return NPX_SEGMENT.test(fs16.realpathSync(binPath));
|
|
57
62
|
} catch {
|
|
58
63
|
return false;
|
|
59
64
|
}
|
|
@@ -88,6 +93,14 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
88
93
|
".wrangler",
|
|
89
94
|
".turbo",
|
|
90
95
|
".cache",
|
|
96
|
+
// Migration / tool backup trees (NativeScript, codemods, agent worktrees)
|
|
97
|
+
".migration_backup",
|
|
98
|
+
".update_backup",
|
|
99
|
+
".backup",
|
|
100
|
+
".bak",
|
|
101
|
+
".history",
|
|
102
|
+
".snapshots",
|
|
103
|
+
".snapshots_bak",
|
|
91
104
|
// JavaScript / Node — installed dependency trees
|
|
92
105
|
"node_modules",
|
|
93
106
|
"bower_components",
|
|
@@ -112,6 +125,9 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
112
125
|
"build",
|
|
113
126
|
"out",
|
|
114
127
|
"coverage",
|
|
128
|
+
// Native mobile platform build trees (NativeScript / Cordova / Capacitor)
|
|
129
|
+
"platforms",
|
|
130
|
+
"App_Resources",
|
|
115
131
|
// Vendored third-party source trees
|
|
116
132
|
"vendor",
|
|
117
133
|
"Pods",
|
|
@@ -219,19 +235,19 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
|
219
235
|
"flake.lock"
|
|
220
236
|
]);
|
|
221
237
|
function toPosix(p) {
|
|
222
|
-
return p.split(
|
|
238
|
+
return p.split(path18.sep).join("/");
|
|
223
239
|
}
|
|
224
240
|
function buildRootIgnore(root, exclude) {
|
|
225
241
|
const ig = ignore();
|
|
226
|
-
const gitignorePath =
|
|
227
|
-
if (
|
|
228
|
-
ig.add(
|
|
242
|
+
const gitignorePath = path18.join(root, ".gitignore");
|
|
243
|
+
if (fs16.existsSync(gitignorePath)) {
|
|
244
|
+
ig.add(fs16.readFileSync(gitignorePath, "utf8"));
|
|
229
245
|
}
|
|
230
246
|
if (exclude.length) ig.add(exclude);
|
|
231
247
|
return ig;
|
|
232
248
|
}
|
|
233
249
|
function discover(options) {
|
|
234
|
-
const root =
|
|
250
|
+
const root = path18.resolve(options.root);
|
|
235
251
|
const onlyLangs = (options.only ?? []).filter(Boolean);
|
|
236
252
|
const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
|
|
237
253
|
for (const id of onlyLangs) {
|
|
@@ -240,39 +256,39 @@ function discover(options) {
|
|
|
240
256
|
}
|
|
241
257
|
}
|
|
242
258
|
const rootIg = buildRootIgnore(root, options.exclude ?? []);
|
|
243
|
-
const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) =>
|
|
259
|
+
const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path18.resolve(root, p)) : [root]).filter((p) => fs16.existsSync(p));
|
|
244
260
|
const found = /* @__PURE__ */ new Map();
|
|
245
261
|
const considerFile = (abs) => {
|
|
246
|
-
const rel2 = toPosix(
|
|
262
|
+
const rel2 = toPosix(path18.relative(root, abs));
|
|
247
263
|
if (rel2.startsWith("..")) return;
|
|
248
264
|
if (rel2 === "" || rootIg.ignores(rel2)) return;
|
|
249
|
-
if (SKIP_FILES.has(
|
|
250
|
-
const lang = langForExtension(
|
|
265
|
+
if (SKIP_FILES.has(path18.basename(abs).toLowerCase())) return;
|
|
266
|
+
const lang = langForExtension(path18.extname(abs));
|
|
251
267
|
if (!lang || !allowLang(lang)) return;
|
|
252
268
|
found.set(rel2, { rel: rel2, abs, lang });
|
|
253
269
|
};
|
|
254
|
-
const
|
|
270
|
+
const walk = (dir) => {
|
|
255
271
|
let entries;
|
|
256
272
|
try {
|
|
257
|
-
entries =
|
|
273
|
+
entries = fs16.readdirSync(dir, { withFileTypes: true });
|
|
258
274
|
} catch {
|
|
259
275
|
return;
|
|
260
276
|
}
|
|
261
277
|
for (const entry of entries) {
|
|
262
|
-
const abs =
|
|
263
|
-
const rel2 = toPosix(
|
|
278
|
+
const abs = path18.join(dir, entry.name);
|
|
279
|
+
const rel2 = toPosix(path18.relative(root, abs));
|
|
264
280
|
if (entry.isDirectory()) {
|
|
265
281
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
266
282
|
if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
|
|
267
|
-
|
|
283
|
+
walk(abs);
|
|
268
284
|
} else if (entry.isFile()) {
|
|
269
285
|
considerFile(abs);
|
|
270
286
|
}
|
|
271
287
|
}
|
|
272
288
|
};
|
|
273
289
|
for (const scope of scopeAbs) {
|
|
274
|
-
const stat =
|
|
275
|
-
if (stat.isDirectory())
|
|
290
|
+
const stat = fs16.statSync(scope);
|
|
291
|
+
if (stat.isDirectory()) walk(scope);
|
|
276
292
|
else if (stat.isFile()) considerFile(scope);
|
|
277
293
|
}
|
|
278
294
|
return [...found.values()].sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
|
|
@@ -336,458 +352,6 @@ function formatBytes(n) {
|
|
|
336
352
|
if (n >= 1024) return `${(n / 1024).toFixed(1)} KiB`;
|
|
337
353
|
return `${n} B`;
|
|
338
354
|
}
|
|
339
|
-
var JS_TS_EXT = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte", ".astro"];
|
|
340
|
-
var OTHER_EXT = [
|
|
341
|
-
".py",
|
|
342
|
-
".pyi",
|
|
343
|
-
".go",
|
|
344
|
-
".rs",
|
|
345
|
-
".rb",
|
|
346
|
-
".java",
|
|
347
|
-
".cs",
|
|
348
|
-
".php",
|
|
349
|
-
".kt",
|
|
350
|
-
".kts",
|
|
351
|
-
".swift",
|
|
352
|
-
".scala",
|
|
353
|
-
".sc",
|
|
354
|
-
".dart",
|
|
355
|
-
".lua",
|
|
356
|
-
".ex",
|
|
357
|
-
".exs",
|
|
358
|
-
".sh",
|
|
359
|
-
".bash",
|
|
360
|
-
".zig",
|
|
361
|
-
".c",
|
|
362
|
-
".cpp",
|
|
363
|
-
".cc",
|
|
364
|
-
".cxx",
|
|
365
|
-
".hpp",
|
|
366
|
-
".hh",
|
|
367
|
-
".hxx",
|
|
368
|
-
".h",
|
|
369
|
-
".m",
|
|
370
|
-
".mm",
|
|
371
|
-
".ml",
|
|
372
|
-
".mli",
|
|
373
|
-
".res",
|
|
374
|
-
".sol"
|
|
375
|
-
];
|
|
376
|
-
var EXT_GROUPS = {
|
|
377
|
-
".py": [".py", ".pyi"],
|
|
378
|
-
".pyi": [".py", ".pyi"],
|
|
379
|
-
".java": [".java"],
|
|
380
|
-
".cs": [".cs"],
|
|
381
|
-
".go": [".go"],
|
|
382
|
-
".rs": [".rs"],
|
|
383
|
-
".rb": [".rb"]
|
|
384
|
-
};
|
|
385
|
-
for (const e of JS_TS_EXT) EXT_GROUPS[e] = JS_TS_EXT;
|
|
386
|
-
var CPP_EXT = [".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx", ".h"];
|
|
387
|
-
Object.assign(EXT_GROUPS, {
|
|
388
|
-
".php": [".php"],
|
|
389
|
-
".kt": [".kt", ".kts"],
|
|
390
|
-
".kts": [".kt", ".kts"],
|
|
391
|
-
".swift": [".swift"],
|
|
392
|
-
".scala": [".scala", ".sc"],
|
|
393
|
-
".sc": [".scala", ".sc"],
|
|
394
|
-
".dart": [".dart"],
|
|
395
|
-
".lua": [".lua"],
|
|
396
|
-
".ex": [".ex", ".exs"],
|
|
397
|
-
".exs": [".ex", ".exs"],
|
|
398
|
-
".sh": [".sh", ".bash"],
|
|
399
|
-
".bash": [".sh", ".bash"],
|
|
400
|
-
".zig": [".zig"],
|
|
401
|
-
".c": [".c", ".h"],
|
|
402
|
-
".cpp": CPP_EXT,
|
|
403
|
-
".cc": CPP_EXT,
|
|
404
|
-
".cxx": CPP_EXT,
|
|
405
|
-
".hpp": CPP_EXT,
|
|
406
|
-
".hh": CPP_EXT,
|
|
407
|
-
".hxx": CPP_EXT,
|
|
408
|
-
".h": [...CPP_EXT, ".c"],
|
|
409
|
-
// ObjC #imports headers; ObjC++ additionally reaches C++ headers.
|
|
410
|
-
".m": [".h", ".m", ".mm"],
|
|
411
|
-
".mm": [".h", ".m", ".mm", ...CPP_EXT],
|
|
412
|
-
".ml": [".ml", ".mli"],
|
|
413
|
-
".mli": [".ml", ".mli"],
|
|
414
|
-
".res": [".res"],
|
|
415
|
-
".sol": [".sol"],
|
|
416
|
-
// Template containers: ERB fragments are Ruby; HTML/EJS script is JS.
|
|
417
|
-
".erb": [".rb", ".erb"],
|
|
418
|
-
".html": JS_TS_EXT,
|
|
419
|
-
".htm": JS_TS_EXT,
|
|
420
|
-
".ejs": JS_TS_EXT
|
|
421
|
-
});
|
|
422
|
-
function buildModuleResolver(root, relSet) {
|
|
423
|
-
const probe = makeProbe(relSet);
|
|
424
|
-
const suffixIndex = buildSuffixIndex(relSet);
|
|
425
|
-
const tsPaths = loadTsconfigPaths(root);
|
|
426
|
-
const workspaces = loadWorkspacePackages(root);
|
|
427
|
-
function resolveAlias(source) {
|
|
428
|
-
if (!tsPaths) return null;
|
|
429
|
-
for (const [pattern, targets] of tsPaths.paths) {
|
|
430
|
-
const m = matchPattern(pattern, source);
|
|
431
|
-
if (m === null) continue;
|
|
432
|
-
for (const target of targets) {
|
|
433
|
-
const sub = target.replace("*", m);
|
|
434
|
-
const rel2 = posixJoin(tsPaths.baseUrlRel, sub);
|
|
435
|
-
const hit = probe(rel2);
|
|
436
|
-
if (hit) return hit;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return null;
|
|
440
|
-
}
|
|
441
|
-
function resolveWorkspace(source) {
|
|
442
|
-
if (!workspaces.size) return null;
|
|
443
|
-
let best = null;
|
|
444
|
-
for (const [name, dirRel] of workspaces) {
|
|
445
|
-
if (source === name || source.startsWith(`${name}/`)) {
|
|
446
|
-
const rest = source === name ? "" : source.slice(name.length + 1);
|
|
447
|
-
if (!best || name.length > best.dirRel.length) best = { dirRel, rest };
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
if (!best) return null;
|
|
451
|
-
const base = best.rest ? posixJoin(best.dirRel, best.rest) : best.dirRel;
|
|
452
|
-
return probe(base) ?? probe(posixJoin(base, "src/index")) ?? probe(posixJoin(base, "index"));
|
|
453
|
-
}
|
|
454
|
-
return {
|
|
455
|
-
resolve(fromRel, source) {
|
|
456
|
-
if (source.includes("\\")) source = source.split("\\").join(".");
|
|
457
|
-
if (source.startsWith("./") || source.startsWith("../")) {
|
|
458
|
-
return probe(posixJoin(path2.posix.dirname(fromRel), source));
|
|
459
|
-
}
|
|
460
|
-
if (/\.[A-Za-z0-9]+$/.test(source) && !source.startsWith("<")) {
|
|
461
|
-
const hit = probe(posixJoin(path2.posix.dirname(fromRel), source));
|
|
462
|
-
if (hit) return hit;
|
|
463
|
-
}
|
|
464
|
-
if (source.startsWith(".")) {
|
|
465
|
-
return resolvePyRelative(probe, fromRel, source);
|
|
466
|
-
}
|
|
467
|
-
return resolveAlias(source) ?? resolveWorkspace(source) ?? resolveSuffix(suffixIndex, fromRel, source) ?? resolveDotted(probe, source);
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
function buildSuffixIndex(relSet) {
|
|
472
|
-
const byLast = /* @__PURE__ */ new Map();
|
|
473
|
-
for (const rel2 of relSet) {
|
|
474
|
-
const dot = rel2.lastIndexOf(".");
|
|
475
|
-
if (dot < 0) continue;
|
|
476
|
-
const ext = rel2.slice(dot);
|
|
477
|
-
if (!EXT_GROUPS[ext]) continue;
|
|
478
|
-
let key = rel2.slice(0, dot);
|
|
479
|
-
const base = key.slice(key.lastIndexOf("/") + 1);
|
|
480
|
-
if (base === "__init__" || base === "index") {
|
|
481
|
-
key = key.slice(0, Math.max(0, key.length - base.length - 1));
|
|
482
|
-
}
|
|
483
|
-
if (!key) continue;
|
|
484
|
-
const last = key.slice(key.lastIndexOf("/") + 1);
|
|
485
|
-
const list = byLast.get(last);
|
|
486
|
-
if (list) list.push({ rel: rel2, key, ext });
|
|
487
|
-
else byLast.set(last, [{ rel: rel2, key, ext }]);
|
|
488
|
-
}
|
|
489
|
-
return byLast;
|
|
490
|
-
}
|
|
491
|
-
function resolveSuffix(index, fromRel, source) {
|
|
492
|
-
if (!/^[A-Za-z_][\w.]*$/.test(source) || !source.includes(".")) return null;
|
|
493
|
-
const fromDot = fromRel.lastIndexOf(".");
|
|
494
|
-
const fromExt = fromDot >= 0 ? fromRel.slice(fromDot) : "";
|
|
495
|
-
const allowed = EXT_GROUPS[fromExt];
|
|
496
|
-
if (!allowed) return null;
|
|
497
|
-
const q = source.replace(/\./g, "/");
|
|
498
|
-
const last = q.slice(q.lastIndexOf("/") + 1);
|
|
499
|
-
const cands = index.get(last);
|
|
500
|
-
if (!cands) return null;
|
|
501
|
-
const matches = cands.filter(
|
|
502
|
-
(c) => allowed.includes(c.ext) && (c.key === q || c.key.endsWith("/" + q))
|
|
503
|
-
);
|
|
504
|
-
if (matches.length === 0) return null;
|
|
505
|
-
if (matches.length === 1) return matches[0].rel;
|
|
506
|
-
return pickClosest(matches, fromRel).rel;
|
|
507
|
-
}
|
|
508
|
-
function pickClosest(matches, fromRel) {
|
|
509
|
-
const fromSegs = fromRel.split("/");
|
|
510
|
-
const score = (rel2) => {
|
|
511
|
-
const segs = rel2.split("/");
|
|
512
|
-
let i = 0;
|
|
513
|
-
while (i < segs.length && i < fromSegs.length && segs[i] === fromSegs[i]) i++;
|
|
514
|
-
return i;
|
|
515
|
-
};
|
|
516
|
-
return [...matches].sort((a, b) => {
|
|
517
|
-
const d = score(b.rel) - score(a.rel);
|
|
518
|
-
if (d) return d;
|
|
519
|
-
if (a.rel.length !== b.rel.length) return a.rel.length - b.rel.length;
|
|
520
|
-
return a.rel < b.rel ? -1 : 1;
|
|
521
|
-
})[0];
|
|
522
|
-
}
|
|
523
|
-
function resolveDotted(probe, source) {
|
|
524
|
-
if (!/^[A-Za-z_][\w.]*$/.test(source) || !source.includes(".")) return null;
|
|
525
|
-
const slashed = source.replace(/\./g, "/");
|
|
526
|
-
return probe(slashed) ?? probe(posixJoin("src", slashed));
|
|
527
|
-
}
|
|
528
|
-
function resolvePyRelative(probe, fromRel, source) {
|
|
529
|
-
let dots = 0;
|
|
530
|
-
while (source[dots] === ".") dots++;
|
|
531
|
-
const rest = source.slice(dots);
|
|
532
|
-
let dir = path2.posix.dirname(fromRel);
|
|
533
|
-
for (let i = 1; i < dots; i++) dir = path2.posix.dirname(dir);
|
|
534
|
-
const sub = rest.replace(/\./g, "/");
|
|
535
|
-
return probe(sub ? posixJoin(dir, sub) : dir);
|
|
536
|
-
}
|
|
537
|
-
function relativeResolver(relSet) {
|
|
538
|
-
const probe = makeProbe(relSet);
|
|
539
|
-
return {
|
|
540
|
-
resolve(fromRel, source) {
|
|
541
|
-
if (source.startsWith(".")) return probe(posixJoin(path2.posix.dirname(fromRel), source));
|
|
542
|
-
return resolveDotted(probe, source);
|
|
543
|
-
}
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
function makeProbe(relSet) {
|
|
547
|
-
return (base) => {
|
|
548
|
-
const norm = normalizeRel(base);
|
|
549
|
-
if (!norm) return null;
|
|
550
|
-
if (relSet.has(norm)) return norm;
|
|
551
|
-
for (const ext of JS_TS_EXT) if (relSet.has(norm + ext)) return norm + ext;
|
|
552
|
-
for (const ext of JS_TS_EXT) if (relSet.has(`${norm}/index${ext}`)) return `${norm}/index${ext}`;
|
|
553
|
-
for (const ext of OTHER_EXT) if (relSet.has(norm + ext)) return norm + ext;
|
|
554
|
-
if (relSet.has(`${norm}/__init__.py`)) return `${norm}/__init__.py`;
|
|
555
|
-
return null;
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
function normalizeRel(p) {
|
|
559
|
-
const norm = path2.posix.normalize(p);
|
|
560
|
-
const stripped = norm.startsWith("./") ? norm.slice(2) : norm;
|
|
561
|
-
return stripped.startsWith("..") ? "" : stripped;
|
|
562
|
-
}
|
|
563
|
-
function posixJoin(a, b) {
|
|
564
|
-
return path2.posix.normalize(path2.posix.join(a, b));
|
|
565
|
-
}
|
|
566
|
-
function matchPattern(pattern, source) {
|
|
567
|
-
const star = pattern.indexOf("*");
|
|
568
|
-
if (star === -1) return source === pattern ? "" : null;
|
|
569
|
-
const prefix = pattern.slice(0, star);
|
|
570
|
-
const suffix = pattern.slice(star + 1);
|
|
571
|
-
if (!source.startsWith(prefix) || !source.endsWith(suffix)) return null;
|
|
572
|
-
if (source.length < prefix.length + suffix.length) return null;
|
|
573
|
-
return source.slice(prefix.length, source.length - suffix.length);
|
|
574
|
-
}
|
|
575
|
-
function loadTsconfigPaths(root) {
|
|
576
|
-
for (const name of ["tsconfig.base.json", "tsconfig.json"]) {
|
|
577
|
-
const file = path2.join(root, name);
|
|
578
|
-
if (fs18.existsSync(file)) {
|
|
579
|
-
const merged = readTsconfigChain(root, file, /* @__PURE__ */ new Set());
|
|
580
|
-
if (merged && merged.paths.length) return merged;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
return null;
|
|
584
|
-
}
|
|
585
|
-
function readTsconfigChain(root, file, seen) {
|
|
586
|
-
if (seen.has(file) || seen.size > 6) return null;
|
|
587
|
-
seen.add(file);
|
|
588
|
-
let cfg;
|
|
589
|
-
try {
|
|
590
|
-
cfg = parseJsonc(fs18.readFileSync(file, "utf8"));
|
|
591
|
-
} catch {
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
const dir = path2.dirname(file);
|
|
595
|
-
let base = { baseUrlRel: "", paths: [] };
|
|
596
|
-
if (cfg.extends && cfg.extends.startsWith(".")) {
|
|
597
|
-
const extPath = path2.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
|
|
598
|
-
if (fs18.existsSync(extPath)) {
|
|
599
|
-
const inherited = readTsconfigChain(root, extPath, seen);
|
|
600
|
-
if (inherited) base = inherited;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
const co = cfg.compilerOptions ?? {};
|
|
604
|
-
const baseUrlRel = co.baseUrl !== void 0 ? path2.relative(root, path2.resolve(dir, co.baseUrl)).split(path2.sep).join("/") : base.baseUrlRel;
|
|
605
|
-
const paths = [...base.paths];
|
|
606
|
-
if (co.paths) for (const [k, v] of Object.entries(co.paths)) paths.push([k, v]);
|
|
607
|
-
return { baseUrlRel, paths };
|
|
608
|
-
}
|
|
609
|
-
function loadWorkspacePackages(root) {
|
|
610
|
-
const map = /* @__PURE__ */ new Map();
|
|
611
|
-
const globs = [];
|
|
612
|
-
const rootPkg = path2.join(root, "package.json");
|
|
613
|
-
if (fs18.existsSync(rootPkg)) {
|
|
614
|
-
try {
|
|
615
|
-
const pkg = JSON.parse(fs18.readFileSync(rootPkg, "utf8"));
|
|
616
|
-
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
617
|
-
if (ws) globs.push(...ws);
|
|
618
|
-
} catch {
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
const pnpmWs = path2.join(root, "pnpm-workspace.yaml");
|
|
622
|
-
if (fs18.existsSync(pnpmWs)) {
|
|
623
|
-
for (const line of fs18.readFileSync(pnpmWs, "utf8").split("\n")) {
|
|
624
|
-
const m = /^\s*-\s*['"]?([^'"\n]+)['"]?\s*$/.exec(line);
|
|
625
|
-
if (m) globs.push(m[1].trim());
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
for (const dir of expandWorkspaceGlobs(root, globs)) {
|
|
629
|
-
const pj = path2.join(dir, "package.json");
|
|
630
|
-
try {
|
|
631
|
-
const name = JSON.parse(fs18.readFileSync(pj, "utf8")).name;
|
|
632
|
-
if (name) map.set(name, path2.relative(root, dir).split(path2.sep).join("/"));
|
|
633
|
-
} catch {
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
return map;
|
|
637
|
-
}
|
|
638
|
-
function expandWorkspaceGlobs(root, globs) {
|
|
639
|
-
const out = /* @__PURE__ */ new Set();
|
|
640
|
-
for (const glob of globs) {
|
|
641
|
-
const clean = glob.replace(/\/\*\*$/, "").replace(/\/\*$/, "");
|
|
642
|
-
const baseDir = path2.join(root, clean);
|
|
643
|
-
const recurse = glob.endsWith("**");
|
|
644
|
-
if (glob === clean) {
|
|
645
|
-
if (fs18.existsSync(path2.join(baseDir, "package.json"))) out.add(baseDir);
|
|
646
|
-
continue;
|
|
647
|
-
}
|
|
648
|
-
collectPackageDirs(baseDir, recurse ? 4 : 1, out);
|
|
649
|
-
}
|
|
650
|
-
return [...out];
|
|
651
|
-
}
|
|
652
|
-
function collectPackageDirs(dir, depth, out) {
|
|
653
|
-
if (depth < 0) return;
|
|
654
|
-
let entries;
|
|
655
|
-
try {
|
|
656
|
-
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
657
|
-
} catch {
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
for (const e of entries) {
|
|
661
|
-
if (!e.isDirectory() || e.name === "node_modules" || e.name.startsWith(".")) continue;
|
|
662
|
-
const child = path2.join(dir, e.name);
|
|
663
|
-
if (fs18.existsSync(path2.join(child, "package.json"))) out.add(child);
|
|
664
|
-
else collectPackageDirs(child, depth - 1, out);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
function parseJsonc(text) {
|
|
668
|
-
return JSON.parse(stripJsonc(text));
|
|
669
|
-
}
|
|
670
|
-
function stripJsonc(text) {
|
|
671
|
-
let out = "";
|
|
672
|
-
let inStr = false;
|
|
673
|
-
let strCh = "";
|
|
674
|
-
for (let i = 0; i < text.length; i++) {
|
|
675
|
-
const ch = text[i];
|
|
676
|
-
const next = text[i + 1];
|
|
677
|
-
if (inStr) {
|
|
678
|
-
out += ch;
|
|
679
|
-
if (ch === "\\") {
|
|
680
|
-
out += next ?? "";
|
|
681
|
-
i++;
|
|
682
|
-
} else if (ch === strCh) {
|
|
683
|
-
inStr = false;
|
|
684
|
-
}
|
|
685
|
-
continue;
|
|
686
|
-
}
|
|
687
|
-
if (ch === '"' || ch === "'") {
|
|
688
|
-
inStr = true;
|
|
689
|
-
strCh = ch;
|
|
690
|
-
out += ch;
|
|
691
|
-
continue;
|
|
692
|
-
}
|
|
693
|
-
if (ch === "/" && next === "/") {
|
|
694
|
-
while (i < text.length && text[i] !== "\n") i++;
|
|
695
|
-
out += "\n";
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
if (ch === "/" && next === "*") {
|
|
699
|
-
i += 2;
|
|
700
|
-
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
|
|
701
|
-
i++;
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
704
|
-
out += ch;
|
|
705
|
-
}
|
|
706
|
-
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// src/engine/ids.ts
|
|
710
|
-
function nodeId(parts) {
|
|
711
|
-
return shortId(
|
|
712
|
-
canonicalize({
|
|
713
|
-
t: "node",
|
|
714
|
-
kind: parts.kind,
|
|
715
|
-
qn: parts.qualifiedName,
|
|
716
|
-
file: parts.file,
|
|
717
|
-
sig: parts.signature ?? ""
|
|
718
|
-
})
|
|
719
|
-
);
|
|
720
|
-
}
|
|
721
|
-
function edgeId(kind, src, dst) {
|
|
722
|
-
return shortId(canonicalize({ t: "edge", kind, src, dst }));
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
// src/engine/tests.ts
|
|
726
|
-
function isTestFile(rel2) {
|
|
727
|
-
const lower = rel2.toLowerCase();
|
|
728
|
-
const base = rel2.split("/").pop() ?? rel2;
|
|
729
|
-
const lbase = base.toLowerCase();
|
|
730
|
-
if (/(^|\/)(__tests__|__test__)\//.test(lower)) return true;
|
|
731
|
-
if (/(^|\/)(tests?|spec|specs)\//.test(lower)) return true;
|
|
732
|
-
if (/(^|\/)src\/test\//.test(lower)) return true;
|
|
733
|
-
if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(lbase)) return true;
|
|
734
|
-
if (/^test_.*\.py$/.test(lbase) || /_test\.py$/.test(lbase)) return true;
|
|
735
|
-
if (/_test\.go$/.test(lbase)) return true;
|
|
736
|
-
if (/[A-Za-z0-9](Test|Tests|IT)\.java$/.test(base)) return true;
|
|
737
|
-
if (/_spec\.rb$/.test(lbase) || /_test\.rb$/.test(lbase)) return true;
|
|
738
|
-
if (/[A-Za-z0-9](Test|Tests)\.cs$/.test(base)) return true;
|
|
739
|
-
if (/[A-Za-z0-9](Test|Tests)\.swift$/.test(base)) return true;
|
|
740
|
-
return false;
|
|
741
|
-
}
|
|
742
|
-
function applyStaticTestLinkage(nodes, edges) {
|
|
743
|
-
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
744
|
-
const testFiles = /* @__PURE__ */ new Set();
|
|
745
|
-
for (const n of nodes) if (n.kind === "file" && isTestFile(n.file)) testFiles.add(n.file);
|
|
746
|
-
const fileNodeIdByRel = /* @__PURE__ */ new Map();
|
|
747
|
-
for (const n of nodes) if (n.kind === "file") fileNodeIdByRel.set(n.file, n.id);
|
|
748
|
-
const covered = /* @__PURE__ */ new Set();
|
|
749
|
-
const newEdges = /* @__PURE__ */ new Map();
|
|
750
|
-
for (const e of edges) {
|
|
751
|
-
if (e.kind !== "call") continue;
|
|
752
|
-
const src = byId.get(e.src);
|
|
753
|
-
const dst = byId.get(e.dst);
|
|
754
|
-
if (!src || !dst) continue;
|
|
755
|
-
if (testFiles.has(src.file) && !testFiles.has(dst.file) && dst.kind !== "external") {
|
|
756
|
-
const testFileId = fileNodeIdByRel.get(src.file);
|
|
757
|
-
if (!testFileId) continue;
|
|
758
|
-
const id = edgeId("test", testFileId, dst.id);
|
|
759
|
-
if (!newEdges.has(id)) {
|
|
760
|
-
newEdges.set(id, {
|
|
761
|
-
id,
|
|
762
|
-
kind: "test",
|
|
763
|
-
src: testFileId,
|
|
764
|
-
dst: dst.id,
|
|
765
|
-
resolution: e.resolution,
|
|
766
|
-
confidence: e.confidence
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
covered.add(dst.id);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
const outNodes = nodes.map((n) => ({ ...n, tested: testedFlag(n, covered.has(n.id), testFiles) }));
|
|
773
|
-
const outEdges = [...edges, ...newEdges.values()].sort(
|
|
774
|
-
(a, b) => a.kind.localeCompare(b.kind) || a.src.localeCompare(b.src) || a.dst.localeCompare(b.dst)
|
|
775
|
-
);
|
|
776
|
-
return {
|
|
777
|
-
nodes: outNodes,
|
|
778
|
-
edges: outEdges,
|
|
779
|
-
testFiles: [...testFiles].sort(),
|
|
780
|
-
testEdgeCount: newEdges.size
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
function testedFlag(node, covered, testFiles) {
|
|
784
|
-
if (node.kind === "external" || node.kind === "file" || node.kind === "package" || node.kind === "module") {
|
|
785
|
-
return null;
|
|
786
|
-
}
|
|
787
|
-
if (testFiles.has(node.file)) return null;
|
|
788
|
-
if (node.kind === "function" || node.kind === "method") return covered;
|
|
789
|
-
return covered ? true : node.tested ?? null;
|
|
790
|
-
}
|
|
791
355
|
|
|
792
356
|
// src/engine/scip.ts
|
|
793
357
|
var ROLE_DEFINITION = 1;
|
|
@@ -976,25 +540,36 @@ function mulberry32(seed) {
|
|
|
976
540
|
var CLUSTER_SEED = 42;
|
|
977
541
|
|
|
978
542
|
// src/engine/analyze.ts
|
|
543
|
+
var TIER_LARGE_NODES = 5e3;
|
|
544
|
+
var TIER_XL_NODES = 5e4;
|
|
545
|
+
function pickAnalysisTier(nodeCount, forced) {
|
|
546
|
+
if (forced) return forced;
|
|
547
|
+
if (nodeCount > TIER_XL_NODES) return "xl";
|
|
548
|
+
if (nodeCount > TIER_LARGE_NODES) return "large";
|
|
549
|
+
return "full";
|
|
550
|
+
}
|
|
979
551
|
var DEP_EDGES = /* @__PURE__ */ new Set(["call", "import", "extends", "implements", "references"]);
|
|
980
552
|
var CLUSTER_EDGE_KINDS = /* @__PURE__ */ new Set([...DEP_EDGES, "contains"]);
|
|
981
553
|
var IMPORTANCE_WEIGHTS = { pagerank: 0.4, betweenness: 0.25, eigenvector: 0.2, degree: 0.15 };
|
|
982
554
|
var DEFAULT_BETWEENNESS_LIMIT = 5e3;
|
|
983
555
|
function analyze(nodes, edges, options = {}) {
|
|
984
|
-
const
|
|
556
|
+
const tier = pickAnalysisTier(nodes.length, options.tier);
|
|
557
|
+
const mode = options.cluster ?? (tier === "xl" ? "louvain" : "louvain");
|
|
985
558
|
const ids = [...nodes.map((n) => n.id)].sort((a, b) => a.localeCompare(b));
|
|
986
559
|
const dg = simpleGraph(ids, edges, DEP_EDGES, "directed");
|
|
987
560
|
const pr = safeMetric(() => pagerank(dg), ids);
|
|
988
|
-
const
|
|
989
|
-
const
|
|
561
|
+
const bwLimit = options.betweennessLimit ?? DEFAULT_BETWEENNESS_LIMIT;
|
|
562
|
+
const bw = tier === "full" && dg.order <= bwLimit ? safeMetric(() => betweenness(dg), ids) : zeros(ids);
|
|
563
|
+
const ev = tier === "full" || tier === "large" ? safeMetric(() => eigenvector(dg), ids) : zeros(ids);
|
|
990
564
|
const deg = degreeMap(dg, ids);
|
|
991
565
|
const prN = normalize(pr);
|
|
992
566
|
const bwN = normalize(bw);
|
|
993
567
|
const evN = normalize(ev);
|
|
994
568
|
const degN = normalize(deg);
|
|
569
|
+
const weights = tier === "xl" ? { pagerank: 0.7, betweenness: 0, eigenvector: 0, degree: 0.3 } : tier === "large" ? { pagerank: 0.5, betweenness: 0, eigenvector: 0.25, degree: 0.25 } : IMPORTANCE_WEIGHTS;
|
|
995
570
|
const importance = /* @__PURE__ */ new Map();
|
|
996
571
|
for (const id of ids) {
|
|
997
|
-
const score =
|
|
572
|
+
const score = weights.pagerank * (prN.get(id) ?? 0) + weights.betweenness * (bwN.get(id) ?? 0) + weights.eigenvector * (evN.get(id) ?? 0) + weights.degree * (degN.get(id) ?? 0);
|
|
998
573
|
importance.set(id, score);
|
|
999
574
|
}
|
|
1000
575
|
const importanceN = normalize(importance);
|
|
@@ -1003,7 +578,7 @@ function analyze(nodes, edges, options = {}) {
|
|
|
1003
578
|
const variance = vals.reduce((a, b) => a + (b - mean) ** 2, 0) / (vals.length || 1);
|
|
1004
579
|
const std = Math.sqrt(variance);
|
|
1005
580
|
const hubThreshold = mean + 2 * std;
|
|
1006
|
-
const { areaByNode, mode: actualMode } = cluster(ids, edges, mode);
|
|
581
|
+
const { areaByNode, mode: actualMode } = tier === "xl" ? clusterFileLevel(nodes, edges, mode) : cluster(ids, edges, mode);
|
|
1007
582
|
const outNodes = nodes.map((n) => ({
|
|
1008
583
|
...n,
|
|
1009
584
|
centrality: {
|
|
@@ -1018,7 +593,42 @@ function analyze(nodes, edges, options = {}) {
|
|
|
1018
593
|
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
1019
594
|
const areas = buildAreas(outNodes, edges, areaByNode);
|
|
1020
595
|
const outEdges = scoreSurprise(edges, areaByNode);
|
|
1021
|
-
return { nodes: outNodes, edges: outEdges, areas, cluster: actualMode };
|
|
596
|
+
return { nodes: outNodes, edges: outEdges, areas, cluster: actualMode, tier };
|
|
597
|
+
}
|
|
598
|
+
function clusterFileLevel(nodes, edges, mode) {
|
|
599
|
+
const fileNodes = nodes.filter((n) => n.kind === "file");
|
|
600
|
+
const fileIds = fileNodes.map((n) => n.id).sort((a, b) => a.localeCompare(b));
|
|
601
|
+
if (fileIds.length === 0) {
|
|
602
|
+
return { areaByNode: new Map(nodes.map((n) => [n.id, -1])), mode: "none" };
|
|
603
|
+
}
|
|
604
|
+
const fileIdByPath = new Map(fileNodes.map((n) => [n.file, n.id]));
|
|
605
|
+
const fileOf = /* @__PURE__ */ new Map();
|
|
606
|
+
for (const n of nodes) {
|
|
607
|
+
if (n.kind === "file") fileOf.set(n.id, n.id);
|
|
608
|
+
else if (n.file) {
|
|
609
|
+
const fid = fileIdByPath.get(n.file);
|
|
610
|
+
if (fid) fileOf.set(n.id, fid);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const fileEdges = [];
|
|
614
|
+
const seen = /* @__PURE__ */ new Set();
|
|
615
|
+
for (const e of edges) {
|
|
616
|
+
if (!DEP_EDGES.has(e.kind)) continue;
|
|
617
|
+
const sf = fileOf.get(e.src);
|
|
618
|
+
const df = fileOf.get(e.dst);
|
|
619
|
+
if (!sf || !df || sf === df) continue;
|
|
620
|
+
const key = `${e.kind}|${sf}|${df}`;
|
|
621
|
+
if (seen.has(key)) continue;
|
|
622
|
+
seen.add(key);
|
|
623
|
+
fileEdges.push({ ...e, src: sf, dst: df, id: key });
|
|
624
|
+
}
|
|
625
|
+
const { areaByNode: fileAreas, mode: used } = cluster(fileIds, fileEdges, mode);
|
|
626
|
+
const areaByNode = /* @__PURE__ */ new Map();
|
|
627
|
+
for (const n of nodes) {
|
|
628
|
+
const fid = fileOf.get(n.id);
|
|
629
|
+
areaByNode.set(n.id, fid ? fileAreas.get(fid) ?? -1 : -1);
|
|
630
|
+
}
|
|
631
|
+
return { areaByNode, mode: used };
|
|
1022
632
|
}
|
|
1023
633
|
function simpleGraph(ids, edges, kinds, type) {
|
|
1024
634
|
const g = new Graph({ type, multi: false, allowSelfLoops: false });
|
|
@@ -1159,14 +769,14 @@ var DEFAULT_PATHS = [
|
|
|
1159
769
|
];
|
|
1160
770
|
function loadCoverage(root, explicit) {
|
|
1161
771
|
const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
|
|
1162
|
-
(p) =>
|
|
772
|
+
(p) => path18.resolve(root, p)
|
|
1163
773
|
);
|
|
1164
|
-
const found = candidates.filter((p) =>
|
|
774
|
+
const found = candidates.filter((p) => fs16.existsSync(p));
|
|
1165
775
|
if (found.length === 0) return null;
|
|
1166
776
|
const map = /* @__PURE__ */ new Map();
|
|
1167
777
|
for (const file of found) {
|
|
1168
778
|
try {
|
|
1169
|
-
const text =
|
|
779
|
+
const text = fs16.readFileSync(file, "utf8");
|
|
1170
780
|
if (file.endsWith(".json")) mergeIstanbul(map, text, root);
|
|
1171
781
|
else mergeLcov(map, text, root);
|
|
1172
782
|
} catch {
|
|
@@ -1175,8 +785,8 @@ function loadCoverage(root, explicit) {
|
|
|
1175
785
|
return map.size ? map : null;
|
|
1176
786
|
}
|
|
1177
787
|
function rel(root, p) {
|
|
1178
|
-
const abs =
|
|
1179
|
-
return
|
|
788
|
+
const abs = path18.isAbsolute(p) ? p : path18.resolve(root, p);
|
|
789
|
+
return path18.relative(root, abs).split(path18.sep).join("/");
|
|
1180
790
|
}
|
|
1181
791
|
function bump(map, file, line, hits) {
|
|
1182
792
|
let lh = map.get(file);
|
|
@@ -1463,9 +1073,6 @@ function matchConfidence(entry, nameParts, imports, callees) {
|
|
|
1463
1073
|
}
|
|
1464
1074
|
return conf;
|
|
1465
1075
|
}
|
|
1466
|
-
|
|
1467
|
-
// src/schema.ts
|
|
1468
|
-
var SCHEMA_VERSION = "vg-graph/1.0";
|
|
1469
1076
|
var DEFAULT_INLINE_THRESHOLD = 24;
|
|
1470
1077
|
async function parseFiles(files, options = {}) {
|
|
1471
1078
|
const threshold = options.inlineThreshold ?? DEFAULT_INLINE_THRESHOLD;
|
|
@@ -1486,7 +1093,7 @@ async function parseInline(files, options) {
|
|
|
1486
1093
|
onProgress?.(0, files.length);
|
|
1487
1094
|
for (const file of files) {
|
|
1488
1095
|
try {
|
|
1489
|
-
const source =
|
|
1096
|
+
const source = fs16.readFileSync(file.abs, "utf8");
|
|
1490
1097
|
out.push(await parseSource(file.rel, file.lang.id, source));
|
|
1491
1098
|
} catch (err) {
|
|
1492
1099
|
out.push(emptyParse(file, `parse failed: ${err.message}`));
|
|
@@ -1541,9 +1148,9 @@ function isWorkerOom(err) {
|
|
|
1541
1148
|
return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
|
|
1542
1149
|
}
|
|
1543
1150
|
function resolveWorkerFile() {
|
|
1544
|
-
const here =
|
|
1545
|
-
const candidate =
|
|
1546
|
-
return
|
|
1151
|
+
const here = path18.dirname(fileURLToPath(import.meta.url));
|
|
1152
|
+
const candidate = path18.join(here, "parse-worker.js");
|
|
1153
|
+
return fs16.existsSync(candidate) ? candidate : null;
|
|
1547
1154
|
}
|
|
1548
1155
|
function chunk(items, buckets) {
|
|
1549
1156
|
const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
|
|
@@ -1569,335 +1176,6 @@ function emptyParse(file, warning) {
|
|
|
1569
1176
|
warnings: [warning]
|
|
1570
1177
|
};
|
|
1571
1178
|
}
|
|
1572
|
-
function resolve4(parses, resolver) {
|
|
1573
|
-
const baseNodes = /* @__PURE__ */ new Map();
|
|
1574
|
-
const relSet = new Set(parses.map((p) => p.rel));
|
|
1575
|
-
const mod = resolver ?? relativeResolver(relSet);
|
|
1576
|
-
const defsByName = /* @__PURE__ */ new Map();
|
|
1577
|
-
const defsByFile = /* @__PURE__ */ new Map();
|
|
1578
|
-
const fileNodeIdByRel = /* @__PURE__ */ new Map();
|
|
1579
|
-
for (const p of parses) {
|
|
1580
|
-
const fileId = nodeId({ kind: "file", qualifiedName: p.rel, file: p.rel });
|
|
1581
|
-
fileNodeIdByRel.set(p.rel, fileId);
|
|
1582
|
-
addNode(baseNodes, {
|
|
1583
|
-
id: fileId,
|
|
1584
|
-
kind: "file",
|
|
1585
|
-
name: path2.posix.basename(p.rel),
|
|
1586
|
-
qualifiedName: p.rel,
|
|
1587
|
-
file: p.rel,
|
|
1588
|
-
span: { start: 1, end: 1 },
|
|
1589
|
-
lang: p.lang
|
|
1590
|
-
});
|
|
1591
|
-
for (const d of p.defs) {
|
|
1592
|
-
const id = nodeId({
|
|
1593
|
-
kind: d.kind,
|
|
1594
|
-
qualifiedName: d.qualifiedName,
|
|
1595
|
-
file: p.rel,
|
|
1596
|
-
signature: d.signature
|
|
1597
|
-
});
|
|
1598
|
-
addNode(baseNodes, {
|
|
1599
|
-
id,
|
|
1600
|
-
kind: d.kind,
|
|
1601
|
-
name: d.name,
|
|
1602
|
-
qualifiedName: d.qualifiedName,
|
|
1603
|
-
file: p.rel,
|
|
1604
|
-
span: { start: d.startLine, end: d.endLine },
|
|
1605
|
-
lang: p.lang,
|
|
1606
|
-
signature: d.signature,
|
|
1607
|
-
doc: d.doc
|
|
1608
|
-
});
|
|
1609
|
-
const ref = {
|
|
1610
|
-
id,
|
|
1611
|
-
name: d.name,
|
|
1612
|
-
qualifiedName: d.qualifiedName,
|
|
1613
|
-
rel: p.rel,
|
|
1614
|
-
lang: p.lang,
|
|
1615
|
-
kind: d.kind,
|
|
1616
|
-
startByte: d.startByte,
|
|
1617
|
-
endByte: d.endByte
|
|
1618
|
-
};
|
|
1619
|
-
push(defsByName, d.name, ref);
|
|
1620
|
-
push(defsByFile, p.rel, ref);
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
const filesByNamespace = /* @__PURE__ */ new Map();
|
|
1624
|
-
for (const p of parses) for (const ns of p.namespaces ?? []) addToSet(filesByNamespace, ns, p.rel);
|
|
1625
|
-
const relsByLang = /* @__PURE__ */ new Map();
|
|
1626
|
-
for (const p of parses) push(relsByLang, p.lang, p.rel);
|
|
1627
|
-
const nsReachableByRel = /* @__PURE__ */ new Map();
|
|
1628
|
-
const moduleReachableByRel = /* @__PURE__ */ new Map();
|
|
1629
|
-
for (const p of parses) {
|
|
1630
|
-
if (MODULE_SCOPED_LANGS.has(p.lang)) {
|
|
1631
|
-
const mod2 = /* @__PURE__ */ new Set();
|
|
1632
|
-
for (const rel2 of relsByLang.get(p.lang) ?? []) if (rel2 !== p.rel) mod2.add(rel2);
|
|
1633
|
-
moduleReachableByRel.set(p.rel, mod2);
|
|
1634
|
-
}
|
|
1635
|
-
const reach = /* @__PURE__ */ new Set();
|
|
1636
|
-
const namespaces = new Set(p.namespaces ?? []);
|
|
1637
|
-
for (const imp of p.imports) {
|
|
1638
|
-
namespaces.add(imp.source);
|
|
1639
|
-
if (IMPORT_NAMES_A_TYPE.has(p.lang)) {
|
|
1640
|
-
const sep9 = Math.max(imp.source.lastIndexOf("."), imp.source.lastIndexOf("\\"));
|
|
1641
|
-
if (sep9 > 0) namespaces.add(imp.source.slice(0, sep9));
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
for (const ns of namespaces) for (const rel2 of filesByNamespace.get(ns) ?? []) if (rel2 !== p.rel) reach.add(rel2);
|
|
1645
|
-
nsReachableByRel.set(p.rel, reach);
|
|
1646
|
-
}
|
|
1647
|
-
const superTypesByType = /* @__PURE__ */ new Map();
|
|
1648
|
-
for (const p of parses) {
|
|
1649
|
-
const localDefs = defsByFile.get(p.rel) ?? [];
|
|
1650
|
-
for (const h of p.heritage) {
|
|
1651
|
-
const encl = enclosingDefRef(localDefs, h.byte);
|
|
1652
|
-
if (encl && (encl.kind === "class" || encl.kind === "interface")) addToSet(superTypesByType, encl.name, h.superName);
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
const edges = new EdgeSet();
|
|
1656
|
-
const unresolved2 = /* @__PURE__ */ new Map();
|
|
1657
|
-
const stats = {
|
|
1658
|
-
callsResolved: 0,
|
|
1659
|
-
callsUnresolved: 0,
|
|
1660
|
-
importsResolvedToFile: 0,
|
|
1661
|
-
importsExternal: 0,
|
|
1662
|
-
resolvers: ["heuristic"]
|
|
1663
|
-
};
|
|
1664
|
-
for (const p of parses) {
|
|
1665
|
-
const fileId = fileNodeIdByRel.get(p.rel);
|
|
1666
|
-
const localByQn = /* @__PURE__ */ new Map();
|
|
1667
|
-
for (const ref of defsByFile.get(p.rel) ?? []) localByQn.set(ref.qualifiedName, ref.id);
|
|
1668
|
-
for (const d of p.defs) {
|
|
1669
|
-
const id = localByQn.get(d.qualifiedName);
|
|
1670
|
-
const parentQn = parentQualifiedName(d.qualifiedName);
|
|
1671
|
-
const parentId = parentQn ? localByQn.get(parentQn) : void 0;
|
|
1672
|
-
edges.add("contains", parentId ?? fileId, id, "heuristic", 1);
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
const importedFilesByRel = /* @__PURE__ */ new Map();
|
|
1676
|
-
for (const p of parses) {
|
|
1677
|
-
const fileId = fileNodeIdByRel.get(p.rel);
|
|
1678
|
-
const importedRels = /* @__PURE__ */ new Set();
|
|
1679
|
-
for (const imp of p.imports) {
|
|
1680
|
-
const targetRel = mod.resolve(p.rel, imp.source);
|
|
1681
|
-
if (targetRel && targetRel !== p.rel) {
|
|
1682
|
-
importedRels.add(targetRel);
|
|
1683
|
-
edges.add("import", fileId, fileNodeIdByRel.get(targetRel), "heuristic", 0.9);
|
|
1684
|
-
stats.importsResolvedToFile++;
|
|
1685
|
-
} else {
|
|
1686
|
-
const extId = ensureExternalNode(baseNodes, imp.source);
|
|
1687
|
-
edges.add("import", fileId, extId, "heuristic", 1);
|
|
1688
|
-
stats.importsExternal++;
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
importedFilesByRel.set(p.rel, importedRels);
|
|
1692
|
-
}
|
|
1693
|
-
for (const p of parses) {
|
|
1694
|
-
const fileId = fileNodeIdByRel.get(p.rel);
|
|
1695
|
-
const localDefs = defsByFile.get(p.rel) ?? [];
|
|
1696
|
-
const imported = importedFilesByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1697
|
-
const testCaller = isTestFile(p.rel);
|
|
1698
|
-
const nsReach = nsReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1699
|
-
const modReach = moduleReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1700
|
-
for (const call of p.calls) {
|
|
1701
|
-
const srcId = enclosingDefId(localDefs, call.byte) ?? fileId;
|
|
1702
|
-
const resolved = resolveCall(call, p.rel, p.lang, imported, defsByName, srcId, testCaller, nsReach, superTypesByType, modReach);
|
|
1703
|
-
if (resolved) {
|
|
1704
|
-
edges.add("call", srcId, resolved.id, "heuristic", resolved.confidence);
|
|
1705
|
-
stats.callsResolved++;
|
|
1706
|
-
} else {
|
|
1707
|
-
stats.callsUnresolved++;
|
|
1708
|
-
bumpUnresolved(unresolved2, srcId, call.callee, "call", p.rel);
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
for (const p of parses) {
|
|
1713
|
-
const fileId = fileNodeIdByRel.get(p.rel);
|
|
1714
|
-
const localDefs = defsByFile.get(p.rel) ?? [];
|
|
1715
|
-
const imported = importedFilesByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1716
|
-
const testCaller = isTestFile(p.rel);
|
|
1717
|
-
const nsReach = nsReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1718
|
-
const modReach = moduleReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1719
|
-
for (const ref of p.typeRefs) {
|
|
1720
|
-
const srcId = enclosingDefId(localDefs, ref.byte) ?? fileId;
|
|
1721
|
-
const target = resolveType(ref.name, p.rel, p.lang, imported, defsByName, testCaller, nsReach, modReach);
|
|
1722
|
-
if (target) edges.add("references", srcId, target.id, "heuristic", 0.7);
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
for (const p of parses) {
|
|
1726
|
-
const localDefs = defsByFile.get(p.rel) ?? [];
|
|
1727
|
-
const imported = importedFilesByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1728
|
-
const testCaller = isTestFile(p.rel);
|
|
1729
|
-
for (const h of p.heritage) {
|
|
1730
|
-
const srcId = enclosingDefId(localDefs, h.byte);
|
|
1731
|
-
if (!srcId) continue;
|
|
1732
|
-
const nsReach = nsReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1733
|
-
const modReach = moduleReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1734
|
-
const target = resolveType(h.superName, p.rel, p.lang, imported, defsByName, testCaller, nsReach, modReach);
|
|
1735
|
-
if (target) edges.add(h.kind, srcId, target.id, "heuristic", 0.85);
|
|
1736
|
-
else bumpUnresolved(unresolved2, srcId, h.superName, h.kind, p.rel);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
const nodes = [...baseNodes.values()].map((n) => ({
|
|
1740
|
-
...n,
|
|
1741
|
-
importance: 0,
|
|
1742
|
-
centrality: { degree: 0, pagerank: 0, betweenness: 0, eigenvector: 0 },
|
|
1743
|
-
area: 0,
|
|
1744
|
-
isHub: false,
|
|
1745
|
-
tested: null
|
|
1746
|
-
}));
|
|
1747
|
-
const unresolvedList = [...unresolved2.values()].sort(
|
|
1748
|
-
(a, b) => a.from.localeCompare(b.from) || a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name)
|
|
1749
|
-
);
|
|
1750
|
-
return { nodes, edges: edges.toArray(), unresolved: unresolvedList, stats };
|
|
1751
|
-
}
|
|
1752
|
-
function bumpUnresolved(map, from, name, kind, fromRel) {
|
|
1753
|
-
const key = `${from}\0${kind}\0${name}`;
|
|
1754
|
-
const existing = map.get(key);
|
|
1755
|
-
if (existing) existing.count++;
|
|
1756
|
-
else map.set(key, { from, name, kind, count: 1, fromRel });
|
|
1757
|
-
}
|
|
1758
|
-
function addNode(map, n) {
|
|
1759
|
-
if (!map.has(n.id)) map.set(n.id, n);
|
|
1760
|
-
}
|
|
1761
|
-
function ensureExternalNode(map, source) {
|
|
1762
|
-
const id = nodeId({ kind: "external", qualifiedName: source, file: "" });
|
|
1763
|
-
if (!map.has(id)) {
|
|
1764
|
-
map.set(id, {
|
|
1765
|
-
id,
|
|
1766
|
-
kind: "external",
|
|
1767
|
-
name: source,
|
|
1768
|
-
qualifiedName: source,
|
|
1769
|
-
file: "",
|
|
1770
|
-
span: { start: 1, end: 1 },
|
|
1771
|
-
lang: "external"
|
|
1772
|
-
});
|
|
1773
|
-
}
|
|
1774
|
-
return id;
|
|
1775
|
-
}
|
|
1776
|
-
function push(map, key, value) {
|
|
1777
|
-
const list = map.get(key);
|
|
1778
|
-
if (list) list.push(value);
|
|
1779
|
-
else map.set(key, [value]);
|
|
1780
|
-
}
|
|
1781
|
-
function parentQualifiedName(qn) {
|
|
1782
|
-
const idx = qn.lastIndexOf(".");
|
|
1783
|
-
return idx > 0 ? qn.slice(0, idx) : null;
|
|
1784
|
-
}
|
|
1785
|
-
function enclosingDefRef(defs, byte) {
|
|
1786
|
-
let best;
|
|
1787
|
-
for (const d of defs) {
|
|
1788
|
-
if (d.startByte <= byte && d.endByte >= byte) {
|
|
1789
|
-
if (!best || d.endByte - d.startByte < best.endByte - best.startByte) best = d;
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
return best;
|
|
1793
|
-
}
|
|
1794
|
-
function enclosingDefId(defs, byte) {
|
|
1795
|
-
return enclosingDefRef(defs, byte)?.id;
|
|
1796
|
-
}
|
|
1797
|
-
function addToSet(map, key, value) {
|
|
1798
|
-
const s = map.get(key);
|
|
1799
|
-
if (s) s.add(value);
|
|
1800
|
-
else map.set(key, /* @__PURE__ */ new Set([value]));
|
|
1801
|
-
}
|
|
1802
|
-
function parentTypeName(qualifiedName) {
|
|
1803
|
-
const i = qualifiedName.lastIndexOf(".");
|
|
1804
|
-
return i > 0 ? qualifiedName.slice(0, i) : null;
|
|
1805
|
-
}
|
|
1806
|
-
function disambiguateInterfaceImpl(pool, superTypesByType) {
|
|
1807
|
-
const withParent = pool.map((c) => ({ c, parent: parentTypeName(c.qualifiedName) })).filter((x) => x.parent !== null);
|
|
1808
|
-
if (withParent.length < 2) return null;
|
|
1809
|
-
const parents = new Set(withParent.map((x) => x.parent));
|
|
1810
|
-
const impls = withParent.filter(
|
|
1811
|
-
(x) => [...parents].some((other) => other !== x.parent && superTypesByType.get(x.parent)?.has(other))
|
|
1812
|
-
);
|
|
1813
|
-
if (impls.length === 0) return null;
|
|
1814
|
-
const interfaceParents = /* @__PURE__ */ new Set();
|
|
1815
|
-
for (const impl of impls) for (const s of superTypesByType.get(impl.parent) ?? []) interfaceParents.add(s);
|
|
1816
|
-
const interfaces = withParent.filter((x) => interfaceParents.has(x.parent));
|
|
1817
|
-
const uniqueImplIds = new Set(impls.map((x) => x.c.id));
|
|
1818
|
-
if (uniqueImplIds.size === 1) return impls[0].c;
|
|
1819
|
-
const uniqueIfaceIds = new Set(interfaces.map((x) => x.c.id));
|
|
1820
|
-
if (uniqueIfaceIds.size === 1) return interfaces[0].c;
|
|
1821
|
-
return null;
|
|
1822
|
-
}
|
|
1823
|
-
var PACKAGE_SCOPED_LANGS = /* @__PURE__ */ new Set(["go", "java", "cs", "kotlin", "scala", "swift"]);
|
|
1824
|
-
var IMPORT_NAMES_A_TYPE = /* @__PURE__ */ new Set(["java", "kotlin", "scala", "php"]);
|
|
1825
|
-
var MODULE_SCOPED_LANGS = /* @__PURE__ */ new Set(["swift"]);
|
|
1826
|
-
function dirOf(rel2) {
|
|
1827
|
-
const i = rel2.lastIndexOf("/");
|
|
1828
|
-
return i >= 0 ? rel2.slice(0, i) : "";
|
|
1829
|
-
}
|
|
1830
|
-
function resolveCall(call, fromRel, fromLang, importedRels, defsByName, enclosingId, fromIsTestFile = false, nsReachableRels = /* @__PURE__ */ new Set(), superTypesByType = /* @__PURE__ */ new Map(), moduleReachableRels = /* @__PURE__ */ new Set()) {
|
|
1831
|
-
const candidates = defsByName.get(call.callee);
|
|
1832
|
-
if (!candidates || candidates.length === 0) return null;
|
|
1833
|
-
const callable = candidates.filter((c) => c.kind === "function" || c.kind === "method");
|
|
1834
|
-
const pool = callable.length ? callable : candidates;
|
|
1835
|
-
let sameFile = pool.filter((c) => c.rel === fromRel);
|
|
1836
|
-
if (call.qualified && enclosingId) sameFile = sameFile.filter((c) => c.id !== enclosingId);
|
|
1837
|
-
if (sameFile.length === 1) return { id: sameFile[0].id, confidence: 0.85 };
|
|
1838
|
-
if (sameFile.length > 1) {
|
|
1839
|
-
const pick = [...sameFile].sort((a, b) => a.id.localeCompare(b.id))[0];
|
|
1840
|
-
return { id: pick.id, confidence: 0.4 };
|
|
1841
|
-
}
|
|
1842
|
-
const dir = dirOf(fromRel);
|
|
1843
|
-
const notSelf = (c) => !(call.qualified && enclosingId && c.id === enclosingId);
|
|
1844
|
-
const strict = pool.filter(
|
|
1845
|
-
(c) => notSelf(c) && (importedRels.has(c.rel) || PACKAGE_SCOPED_LANGS.has(fromLang) && c.lang === fromLang && dirOf(c.rel) === dir || nsReachableRels.has(c.rel))
|
|
1846
|
-
);
|
|
1847
|
-
if (strict.length === 1) return { id: strict[0].id, confidence: 0.75 };
|
|
1848
|
-
const broad = pool.filter((c) => notSelf(c) && (moduleReachableRels.has(c.rel) || strict.includes(c)));
|
|
1849
|
-
if (broad.length > 1) {
|
|
1850
|
-
const dedup = disambiguateInterfaceImpl(broad, superTypesByType);
|
|
1851
|
-
if (dedup) return { id: dedup.id, confidence: 0.6 };
|
|
1852
|
-
}
|
|
1853
|
-
if (strict.length > 1) return null;
|
|
1854
|
-
if (fromLang === "swift" && fromIsTestFile && pool.length === 1) {
|
|
1855
|
-
return { id: pool[0].id, confidence: 0.5 };
|
|
1856
|
-
}
|
|
1857
|
-
return null;
|
|
1858
|
-
}
|
|
1859
|
-
function resolveType(name, fromRel, fromLang, importedRels, defsByName, fromIsTestFile = false, nsReachableRels = /* @__PURE__ */ new Set(), moduleReachableRels = /* @__PURE__ */ new Set()) {
|
|
1860
|
-
const candidates = (defsByName.get(name) ?? []).filter(
|
|
1861
|
-
(c) => c.kind === "class" || c.kind === "interface"
|
|
1862
|
-
);
|
|
1863
|
-
if (candidates.length === 0) return null;
|
|
1864
|
-
const sameFile = candidates.filter((c) => c.rel === fromRel);
|
|
1865
|
-
if (sameFile.length === 1) return { id: sameFile[0].id };
|
|
1866
|
-
const imported = candidates.filter((c) => importedRels.has(c.rel));
|
|
1867
|
-
if (imported.length === 1) return { id: imported[0].id };
|
|
1868
|
-
if (imported.length > 1) return null;
|
|
1869
|
-
if (PACKAGE_SCOPED_LANGS.has(fromLang)) {
|
|
1870
|
-
const dir = dirOf(fromRel);
|
|
1871
|
-
const samePkg = candidates.filter((c) => c.lang === fromLang && dirOf(c.rel) === dir);
|
|
1872
|
-
if (samePkg.length === 1) return { id: samePkg[0].id };
|
|
1873
|
-
}
|
|
1874
|
-
if (nsReachableRels.size || moduleReachableRels.size) {
|
|
1875
|
-
const reach = candidates.filter((c) => nsReachableRels.has(c.rel) || moduleReachableRels.has(c.rel));
|
|
1876
|
-
if (reach.length === 1) return { id: reach[0].id };
|
|
1877
|
-
}
|
|
1878
|
-
if (fromLang === "swift" && fromIsTestFile && candidates.length === 1) {
|
|
1879
|
-
return { id: candidates[0].id };
|
|
1880
|
-
}
|
|
1881
|
-
return null;
|
|
1882
|
-
}
|
|
1883
|
-
var EdgeSet = class {
|
|
1884
|
-
map = /* @__PURE__ */ new Map();
|
|
1885
|
-
add(kind, src, dst, resolution, confidence) {
|
|
1886
|
-
const id = edgeId(kind, src, dst);
|
|
1887
|
-
const existing = this.map.get(id);
|
|
1888
|
-
if (existing) {
|
|
1889
|
-
existing.count = (existing.count ?? 1) + 1;
|
|
1890
|
-
if (confidence > existing.confidence) existing.confidence = confidence;
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
this.map.set(id, { id, kind, src, dst, resolution, confidence, count: 1 });
|
|
1894
|
-
}
|
|
1895
|
-
toArray() {
|
|
1896
|
-
return [...this.map.values()].sort(
|
|
1897
|
-
(a, b) => a.kind.localeCompare(b.kind) || a.src.localeCompare(b.src) || a.dst.localeCompare(b.dst)
|
|
1898
|
-
);
|
|
1899
|
-
}
|
|
1900
|
-
};
|
|
1901
1179
|
var PRECISE_KIND = "tsc";
|
|
1902
1180
|
function tsResolveEdges(root, tsFiles, nodes) {
|
|
1903
1181
|
const empty = {
|
|
@@ -2121,7 +1399,7 @@ function enclosing3(nodes, line) {
|
|
|
2121
1399
|
return best;
|
|
2122
1400
|
}
|
|
2123
1401
|
function normalize2(p) {
|
|
2124
|
-
return
|
|
1402
|
+
return path18.resolve(p).split(path18.sep).join("/");
|
|
2125
1403
|
}
|
|
2126
1404
|
|
|
2127
1405
|
// src/engine/epistemic.ts
|
|
@@ -2147,66 +1425,565 @@ function epistemicBreakdown(edges) {
|
|
|
2147
1425
|
}
|
|
2148
1426
|
return out;
|
|
2149
1427
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
1428
|
+
var DOC_BODY_MAX_CHARS = 8e3;
|
|
1429
|
+
var DOC_FILE_MAX_BYTES = 512 * 1024;
|
|
1430
|
+
var DOC_MAX_FILES = 600;
|
|
1431
|
+
var DOC_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx", ".txt", ".rst", ".adoc"]);
|
|
1432
|
+
var ENV_EXAMPLE_NAMES = /* @__PURE__ */ new Set([
|
|
1433
|
+
"example.env",
|
|
1434
|
+
"env.example",
|
|
1435
|
+
".env.example",
|
|
1436
|
+
".env.sample",
|
|
1437
|
+
".env.template",
|
|
1438
|
+
".env.development.example",
|
|
1439
|
+
".env.local.example",
|
|
1440
|
+
"dotenv.example"
|
|
1441
|
+
]);
|
|
1442
|
+
var EXACT_BASENAME = {
|
|
1443
|
+
// Manifests / package managers
|
|
1444
|
+
"package.json": "manifest",
|
|
1445
|
+
"package.yaml": "manifest",
|
|
1446
|
+
"package.yml": "manifest",
|
|
1447
|
+
"pyproject.toml": "manifest",
|
|
1448
|
+
"setup.cfg": "manifest",
|
|
1449
|
+
"requirements.txt": "manifest",
|
|
1450
|
+
"requirements-dev.txt": "manifest",
|
|
1451
|
+
"pipfile": "manifest",
|
|
1452
|
+
"go.mod": "manifest",
|
|
1453
|
+
"go.work": "manifest",
|
|
1454
|
+
"cargo.toml": "manifest",
|
|
1455
|
+
"composer.json": "manifest",
|
|
1456
|
+
"gemfile": "manifest",
|
|
1457
|
+
"podfile": "manifest",
|
|
1458
|
+
"pubspec.yaml": "manifest",
|
|
1459
|
+
"mix.exs": "manifest",
|
|
1460
|
+
"build.gradle": "manifest",
|
|
1461
|
+
"build.gradle.kts": "manifest",
|
|
1462
|
+
"settings.gradle": "manifest",
|
|
1463
|
+
"settings.gradle.kts": "manifest",
|
|
1464
|
+
"pom.xml": "manifest",
|
|
1465
|
+
"project.clj": "manifest",
|
|
1466
|
+
"stack.yaml": "manifest",
|
|
1467
|
+
"package.swift": "manifest",
|
|
1468
|
+
// Workspace / monorepo
|
|
1469
|
+
"pnpm-workspace.yaml": "workspace",
|
|
1470
|
+
"pnpm-workspace.yml": "workspace",
|
|
1471
|
+
"lerna.json": "workspace",
|
|
1472
|
+
"nx.json": "workspace",
|
|
1473
|
+
"turbo.json": "workspace",
|
|
1474
|
+
"rush.json": "workspace",
|
|
1475
|
+
"workspace.json": "workspace",
|
|
1476
|
+
// Docker
|
|
1477
|
+
dockerfile: "docker",
|
|
1478
|
+
"dockerfile.dev": "docker",
|
|
1479
|
+
"dockerfile.prod": "docker",
|
|
1480
|
+
"dockerfile.local": "docker",
|
|
1481
|
+
containerfile: "docker",
|
|
1482
|
+
// Compose
|
|
1483
|
+
"docker-compose.yml": "compose",
|
|
1484
|
+
"docker-compose.yaml": "compose",
|
|
1485
|
+
"compose.yml": "compose",
|
|
1486
|
+
"compose.yaml": "compose",
|
|
1487
|
+
"docker-compose.dev.yml": "compose",
|
|
1488
|
+
"docker-compose.prod.yml": "compose",
|
|
1489
|
+
"docker-compose.override.yml": "compose",
|
|
1490
|
+
// Task runners
|
|
1491
|
+
makefile: "task-runner",
|
|
1492
|
+
gnumakefile: "task-runner",
|
|
1493
|
+
justfile: "task-runner",
|
|
1494
|
+
"taskfile.yml": "task-runner",
|
|
1495
|
+
"taskfile.yaml": "task-runner",
|
|
1496
|
+
rakefile: "task-runner",
|
|
1497
|
+
// Build / tool config (exact)
|
|
1498
|
+
"tsconfig.json": "build-config",
|
|
1499
|
+
"jsconfig.json": "build-config",
|
|
1500
|
+
"vite.config.ts": "build-config",
|
|
1501
|
+
"vite.config.js": "build-config",
|
|
1502
|
+
"vite.config.mjs": "build-config",
|
|
1503
|
+
"vitest.config.ts": "build-config",
|
|
1504
|
+
"vitest.config.js": "build-config",
|
|
1505
|
+
"jest.config.js": "build-config",
|
|
1506
|
+
"jest.config.ts": "build-config",
|
|
1507
|
+
"jest.config.cjs": "build-config",
|
|
1508
|
+
"webpack.config.js": "build-config",
|
|
1509
|
+
"webpack.config.ts": "build-config",
|
|
1510
|
+
"rollup.config.js": "build-config",
|
|
1511
|
+
"esbuild.config.js": "build-config",
|
|
1512
|
+
"next.config.js": "build-config",
|
|
1513
|
+
"next.config.mjs": "build-config",
|
|
1514
|
+
"next.config.ts": "build-config",
|
|
1515
|
+
"nuxt.config.ts": "build-config",
|
|
1516
|
+
"nuxt.config.js": "build-config",
|
|
1517
|
+
"astro.config.mjs": "build-config",
|
|
1518
|
+
"svelte.config.js": "build-config",
|
|
1519
|
+
"remix.config.js": "build-config",
|
|
1520
|
+
"angular.json": "build-config",
|
|
1521
|
+
"babel.config.js": "build-config",
|
|
1522
|
+
"babel.config.json": "build-config",
|
|
1523
|
+
".babelrc": "build-config",
|
|
1524
|
+
".babelrc.js": "build-config",
|
|
1525
|
+
".babelrc.json": "build-config",
|
|
1526
|
+
"eslint.config.js": "build-config",
|
|
1527
|
+
"eslint.config.mjs": "build-config",
|
|
1528
|
+
"eslint.config.cjs": "build-config",
|
|
1529
|
+
"eslint.config.ts": "build-config",
|
|
1530
|
+
".eslintrc": "build-config",
|
|
1531
|
+
".eslintrc.js": "build-config",
|
|
1532
|
+
".eslintrc.cjs": "build-config",
|
|
1533
|
+
".eslintrc.json": "build-config",
|
|
1534
|
+
".eslintrc.yml": "build-config",
|
|
1535
|
+
".prettierrc": "build-config",
|
|
1536
|
+
".prettierrc.js": "build-config",
|
|
1537
|
+
".prettierrc.json": "build-config",
|
|
1538
|
+
".prettierrc.yml": "build-config",
|
|
1539
|
+
"prettier.config.js": "build-config",
|
|
1540
|
+
"prettier.config.cjs": "build-config",
|
|
1541
|
+
"ruff.toml": "build-config",
|
|
1542
|
+
".ruff.toml": "build-config",
|
|
1543
|
+
"pytest.ini": "build-config",
|
|
1544
|
+
"tox.ini": "build-config",
|
|
1545
|
+
"mypy.ini": "build-config",
|
|
1546
|
+
".mypy.ini": "build-config",
|
|
1547
|
+
"phpunit.xml": "build-config",
|
|
1548
|
+
"phpunit.xml.dist": "build-config",
|
|
1549
|
+
// CI exact
|
|
1550
|
+
"jenkinsfile": "ci",
|
|
1551
|
+
".gitlab-ci.yml": "ci",
|
|
1552
|
+
".travis.yml": "ci",
|
|
1553
|
+
"azure-pipelines.yml": "ci",
|
|
1554
|
+
"buildkite.yml": "ci",
|
|
1555
|
+
"cloudbuild.yaml": "ci",
|
|
1556
|
+
"appveyor.yml": "ci",
|
|
1557
|
+
"circle.yml": "ci",
|
|
1558
|
+
// Deploy / infra (root-level common names)
|
|
1559
|
+
"vercel.json": "infra",
|
|
1560
|
+
"netlify.toml": "infra",
|
|
1561
|
+
"wrangler.toml": "infra",
|
|
1562
|
+
"wrangler.json": "infra",
|
|
1563
|
+
"wrangler.jsonc": "infra",
|
|
1564
|
+
"fly.toml": "infra",
|
|
1565
|
+
"railway.toml": "infra",
|
|
1566
|
+
"render.yaml": "infra",
|
|
1567
|
+
"app.yaml": "infra",
|
|
1568
|
+
"Procfile": "infra",
|
|
1569
|
+
"procfile": "infra",
|
|
1570
|
+
"heroku.yml": "infra",
|
|
1571
|
+
"serverless.yml": "infra",
|
|
1572
|
+
"serverless.yaml": "infra",
|
|
1573
|
+
"sam.yaml": "infra",
|
|
1574
|
+
"samconfig.toml": "infra",
|
|
1575
|
+
"cdk.json": "infra",
|
|
1576
|
+
"pulumi.yaml": "infra",
|
|
1577
|
+
"terraform.tf": "infra",
|
|
1578
|
+
// Agent / project meta (also covered by .md extension)
|
|
1579
|
+
"agents.md": "markdown",
|
|
1580
|
+
"claude.md": "markdown",
|
|
1581
|
+
"codeowners": "other-config",
|
|
1582
|
+
".nvmrc": "other-config",
|
|
1583
|
+
".node-version": "other-config",
|
|
1584
|
+
".python-version": "other-config",
|
|
1585
|
+
"runtime.txt": "other-config",
|
|
1586
|
+
".tool-versions": "other-config",
|
|
1587
|
+
"editorconfig": "other-config",
|
|
1588
|
+
".editorconfig": "other-config"
|
|
1589
|
+
};
|
|
1590
|
+
var BASENAME_PREFIX = [
|
|
1591
|
+
{ re: /^dockerfile(\.|$)/i, category: "docker" },
|
|
1592
|
+
{ re: /^docker-compose(\.|$)/i, category: "compose" },
|
|
1593
|
+
{ re: /^compose\.(ya?ml)$/i, category: "compose" },
|
|
1594
|
+
{ re: /^tsconfig(\..+)?\.json$/i, category: "build-config" },
|
|
1595
|
+
{ re: /^jsconfig(\..+)?\.json$/i, category: "build-config" },
|
|
1596
|
+
{ re: /^vite\.config\./i, category: "build-config" },
|
|
1597
|
+
{ re: /^vitest\.config\./i, category: "build-config" },
|
|
1598
|
+
{ re: /^jest\.config\./i, category: "build-config" },
|
|
1599
|
+
{ re: /^webpack\.config\./i, category: "build-config" },
|
|
1600
|
+
{ re: /^rollup\.config\./i, category: "build-config" },
|
|
1601
|
+
{ re: /^next\.config\./i, category: "build-config" },
|
|
1602
|
+
{ re: /^nuxt\.config\./i, category: "build-config" },
|
|
1603
|
+
{ re: /^astro\.config\./i, category: "build-config" },
|
|
1604
|
+
{ re: /^eslint\.config\./i, category: "build-config" },
|
|
1605
|
+
{ re: /^\.eslintrc/i, category: "build-config" },
|
|
1606
|
+
{ re: /^openapi\./i, category: "api-contract" },
|
|
1607
|
+
{ re: /^swagger\./i, category: "api-contract" },
|
|
1608
|
+
{ re: /^requirements(-\w+)?\.txt$/i, category: "manifest" },
|
|
1609
|
+
{ re: /^dockerfile/i, category: "docker" }
|
|
1610
|
+
];
|
|
1611
|
+
var PATH_PATTERNS = [
|
|
1612
|
+
// GitHub Actions / workflows
|
|
1613
|
+
{ re: /^\.github\/workflows\/.+\.ya?ml$/i, category: "ci" },
|
|
1614
|
+
{ re: /^\.github\/actions\/.+/i, category: "ci" },
|
|
1615
|
+
{ re: /^\.circleci\/.+\.ya?ml$/i, category: "ci" },
|
|
1616
|
+
{ re: /^\.buildkite\/.+/i, category: "ci" },
|
|
1617
|
+
// Helm / k8s (values + charts — not every template blob)
|
|
1618
|
+
{ re: /^charts\/[^/]+\/(chart\.ya?ml|values.*\.ya?ml)$/i, category: "infra" },
|
|
1619
|
+
{ re: /^deploy\/.+\.ya?ml$/i, category: "infra" },
|
|
1620
|
+
{ re: /^k8s\/.+\.ya?ml$/i, category: "infra" },
|
|
1621
|
+
{ re: /^kubernetes\/.+\.ya?ml$/i, category: "infra" },
|
|
1622
|
+
{ re: /^infra\/.+\.ya?ml$/i, category: "infra" },
|
|
1623
|
+
{ re: /^terraform\/.+\.tf$/i, category: "infra" },
|
|
1624
|
+
// API contracts by path
|
|
1625
|
+
{ re: /(^|\/)(openapi|swagger)(\.|\/)/i, category: "api-contract" },
|
|
1626
|
+
{ re: /(^|\/)api(-)?(spec|schema|contracts?)\//i, category: "api-contract" }
|
|
1627
|
+
];
|
|
1628
|
+
var API_EXTENSIONS = /* @__PURE__ */ new Set([".graphql", ".gql", ".proto", ".avsc", ".thrift"]);
|
|
1629
|
+
var IMPORTANCE = {
|
|
1630
|
+
markdown: 0.1,
|
|
1631
|
+
text: 0.06,
|
|
1632
|
+
"env-example": 0.12,
|
|
1633
|
+
manifest: 0.16,
|
|
1634
|
+
docker: 0.14,
|
|
1635
|
+
compose: 0.15,
|
|
1636
|
+
ci: 0.14,
|
|
1637
|
+
"build-config": 0.11,
|
|
1638
|
+
"api-contract": 0.13,
|
|
1639
|
+
"task-runner": 0.13,
|
|
1640
|
+
workspace: 0.14,
|
|
1641
|
+
infra: 0.12,
|
|
1642
|
+
"other-config": 0.08
|
|
1643
|
+
};
|
|
1644
|
+
function toPosix2(p) {
|
|
1645
|
+
return p.split(path18.sep).join("/");
|
|
1646
|
+
}
|
|
1647
|
+
function buildRootIgnore2(root, exclude) {
|
|
1648
|
+
const ig = ignore();
|
|
1649
|
+
const gitignorePath = path18.join(root, ".gitignore");
|
|
1650
|
+
if (fs16.existsSync(gitignorePath)) {
|
|
2181
1651
|
try {
|
|
2182
|
-
|
|
1652
|
+
ig.add(fs16.readFileSync(gitignorePath, "utf8"));
|
|
2183
1653
|
} catch {
|
|
2184
|
-
continue;
|
|
2185
|
-
}
|
|
2186
|
-
if (limits.maxFileBytes > 0 && stat.size > limits.maxFileBytes) {
|
|
2187
|
-
fileStats.push({
|
|
2188
|
-
rel: file.rel,
|
|
2189
|
-
size: stat.size,
|
|
2190
|
-
mtimeMs: stat.mtimeMs,
|
|
2191
|
-
hash: hashString(`vg:oversize:${stat.size}`)
|
|
2192
|
-
});
|
|
2193
|
-
buildWarnings.push(
|
|
2194
|
-
`${file.rel}: skipped \u2014 ${formatBytes(stat.size)} exceeds the ${formatBytes(limits.maxFileBytes)} per-file limit (set VG_MAX_FILE_BYTES to raise it, 0 to disable)`
|
|
2195
|
-
);
|
|
2196
|
-
continue;
|
|
2197
1654
|
}
|
|
2198
|
-
|
|
1655
|
+
}
|
|
1656
|
+
if (exclude.length) ig.add(exclude);
|
|
1657
|
+
return ig;
|
|
1658
|
+
}
|
|
1659
|
+
function classifyProjectContext(relOrName) {
|
|
1660
|
+
const rel2 = relOrName.replace(/\\/g, "/");
|
|
1661
|
+
const base = path18.basename(rel2);
|
|
1662
|
+
const baseLower = base.toLowerCase();
|
|
1663
|
+
if (/^\.env($|\.)/i.test(base) && !isEnvExampleName(baseLower)) return null;
|
|
1664
|
+
if (SKIP_FILES.has(baseLower)) return null;
|
|
1665
|
+
if (isEnvExampleName(baseLower)) return "env-example";
|
|
1666
|
+
const exact = EXACT_BASENAME[baseLower] ?? EXACT_BASENAME[base];
|
|
1667
|
+
if (exact) return exact;
|
|
1668
|
+
for (const { re, category } of BASENAME_PREFIX) {
|
|
1669
|
+
if (re.test(base)) return category;
|
|
1670
|
+
}
|
|
1671
|
+
for (const { re, category } of PATH_PATTERNS) {
|
|
1672
|
+
if (re.test(rel2)) return category;
|
|
1673
|
+
}
|
|
1674
|
+
const ext = path18.extname(baseLower);
|
|
1675
|
+
if (DOC_EXTENSIONS.has(ext)) return ext === ".txt" ? "text" : "markdown";
|
|
1676
|
+
if (API_EXTENSIONS.has(ext)) return "api-contract";
|
|
1677
|
+
if ((ext === ".yaml" || ext === ".yml" || ext === ".json") && /openapi|swagger|asyncapi/i.test(base)) {
|
|
1678
|
+
return "api-contract";
|
|
1679
|
+
}
|
|
1680
|
+
if (/^docker-compose\..+\.ya?ml$/i.test(base) || /^compose\..+\.ya?ml$/i.test(base)) {
|
|
1681
|
+
return "compose";
|
|
1682
|
+
}
|
|
1683
|
+
return null;
|
|
1684
|
+
}
|
|
1685
|
+
function isEnvExampleName(baseLower) {
|
|
1686
|
+
if (ENV_EXAMPLE_NAMES.has(baseLower)) return true;
|
|
1687
|
+
if (/^(\.)?env\.(example|sample|template)(\.|$)/i.test(baseLower)) return true;
|
|
1688
|
+
if (/^example\.env/i.test(baseLower)) return true;
|
|
1689
|
+
return false;
|
|
1690
|
+
}
|
|
1691
|
+
function discoverDocs(options) {
|
|
1692
|
+
const root = path18.resolve(options.root);
|
|
1693
|
+
const maxFiles = options.maxFiles ?? DOC_MAX_FILES;
|
|
1694
|
+
const rootIg = buildRootIgnore2(root, options.exclude ?? []);
|
|
1695
|
+
const scopeAbs = (options.paths?.length ? options.paths.map((p) => path18.resolve(root, p)) : [root]).filter(
|
|
1696
|
+
(p) => fs16.existsSync(p)
|
|
1697
|
+
);
|
|
1698
|
+
const found = /* @__PURE__ */ new Map();
|
|
1699
|
+
const consider = (abs) => {
|
|
1700
|
+
if (found.size >= maxFiles) return;
|
|
1701
|
+
const rel2 = toPosix2(path18.relative(root, abs));
|
|
1702
|
+
if (!rel2 || rel2.startsWith("..")) return;
|
|
1703
|
+
if (rootIg.ignores(rel2)) return;
|
|
1704
|
+
const base = path18.basename(abs);
|
|
1705
|
+
if (SKIP_FILES.has(base.toLowerCase())) return;
|
|
1706
|
+
const category = classifyProjectContext(rel2);
|
|
1707
|
+
if (!category) return;
|
|
2199
1708
|
try {
|
|
2200
|
-
|
|
1709
|
+
const st = fs16.statSync(abs);
|
|
1710
|
+
if (!st.isFile() || st.size > DOC_FILE_MAX_BYTES) return;
|
|
1711
|
+
if (st.size === 0) return;
|
|
2201
1712
|
} catch {
|
|
2202
|
-
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
found.set(rel2, { rel: rel2, abs, kind: category, category });
|
|
1716
|
+
};
|
|
1717
|
+
const walk = (dir, depth) => {
|
|
1718
|
+
if (found.size >= maxFiles || depth > 12) return;
|
|
1719
|
+
let entries;
|
|
1720
|
+
try {
|
|
1721
|
+
entries = fs16.readdirSync(dir, { withFileTypes: true });
|
|
1722
|
+
} catch {
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1726
|
+
if (found.size >= maxFiles) return;
|
|
1727
|
+
const abs = path18.join(dir, entry.name);
|
|
1728
|
+
const rel2 = toPosix2(path18.relative(root, abs));
|
|
1729
|
+
if (entry.isDirectory()) {
|
|
1730
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1731
|
+
if (rel2 && rootIg.ignores(`${rel2}/`) && !rel2.startsWith(".github")) continue;
|
|
1732
|
+
walk(abs, depth + 1);
|
|
1733
|
+
} else if (entry.isFile()) {
|
|
1734
|
+
consider(abs);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
for (const scope of scopeAbs) {
|
|
1739
|
+
try {
|
|
1740
|
+
const st = fs16.statSync(scope);
|
|
1741
|
+
if (st.isDirectory()) walk(scope, 0);
|
|
1742
|
+
else if (st.isFile()) consider(scope);
|
|
1743
|
+
} catch {
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return [...found.values()].sort((a, b) => a.rel.localeCompare(b.rel));
|
|
1747
|
+
}
|
|
1748
|
+
function prepareDocBody(rel2, raw, category) {
|
|
1749
|
+
const scrubbed = redactSecrets(raw);
|
|
1750
|
+
if (scrubbed.length <= DOC_BODY_MAX_CHARS) return scrubbed;
|
|
1751
|
+
if (category === "manifest" && path18.basename(rel2).toLowerCase() === "package.json") {
|
|
1752
|
+
return summarizePackageJson(scrubbed);
|
|
1753
|
+
}
|
|
1754
|
+
if (category === "api-contract" && /\.(ya?ml|json)$/i.test(rel2)) {
|
|
1755
|
+
return scrubbed.slice(0, DOC_BODY_MAX_CHARS);
|
|
1756
|
+
}
|
|
1757
|
+
if (category === "ci") {
|
|
1758
|
+
return scrubbed.slice(0, DOC_BODY_MAX_CHARS);
|
|
1759
|
+
}
|
|
1760
|
+
return scrubbed.slice(0, DOC_BODY_MAX_CHARS);
|
|
1761
|
+
}
|
|
1762
|
+
function summarizePackageJson(raw) {
|
|
1763
|
+
try {
|
|
1764
|
+
const o = JSON.parse(raw);
|
|
1765
|
+
const keep = {};
|
|
1766
|
+
for (const k of [
|
|
1767
|
+
"name",
|
|
1768
|
+
"version",
|
|
1769
|
+
"description",
|
|
1770
|
+
"private",
|
|
1771
|
+
"type",
|
|
1772
|
+
"packageManager",
|
|
1773
|
+
"engines",
|
|
1774
|
+
"workspaces",
|
|
1775
|
+
"scripts",
|
|
1776
|
+
"dependencies",
|
|
1777
|
+
"devDependencies",
|
|
1778
|
+
"peerDependencies",
|
|
1779
|
+
"optionalDependencies",
|
|
1780
|
+
"main",
|
|
1781
|
+
"bin",
|
|
1782
|
+
"exports"
|
|
1783
|
+
]) {
|
|
1784
|
+
if (o[k] !== void 0) keep[k] = o[k];
|
|
1785
|
+
}
|
|
1786
|
+
const s = JSON.stringify(keep, null, 2);
|
|
1787
|
+
return s.length <= DOC_BODY_MAX_CHARS ? s : s.slice(0, DOC_BODY_MAX_CHARS);
|
|
1788
|
+
} catch {
|
|
1789
|
+
return raw.slice(0, DOC_BODY_MAX_CHARS);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function documentNodesFromDocs(docs) {
|
|
1793
|
+
const nodes = [];
|
|
1794
|
+
for (const d of docs) {
|
|
1795
|
+
let raw = "";
|
|
1796
|
+
try {
|
|
1797
|
+
raw = fs16.readFileSync(d.abs, "utf8");
|
|
1798
|
+
} catch {
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
const category = d.category ?? d.kind;
|
|
1802
|
+
const scrubbed = prepareDocBody(d.rel, raw, category);
|
|
1803
|
+
if (!scrubbed.trim()) continue;
|
|
1804
|
+
const name = path18.basename(d.rel);
|
|
1805
|
+
const qualifiedName = d.rel.replace(/\\/g, "/");
|
|
1806
|
+
const id = nodeId({
|
|
1807
|
+
kind: "document",
|
|
1808
|
+
qualifiedName,
|
|
1809
|
+
file: qualifiedName,
|
|
1810
|
+
signature: category
|
|
1811
|
+
});
|
|
1812
|
+
const lineCount = Math.max(1, scrubbed.split("\n").length);
|
|
1813
|
+
const langTag = category === "markdown" ? "md" : category === "env-example" ? "env" : category === "manifest" ? "manifest" : category === "docker" || category === "compose" ? "docker" : category === "ci" ? "ci" : category === "api-contract" ? "api" : category === "build-config" ? "config" : category === "task-runner" ? "task" : category === "workspace" ? "workspace" : category === "infra" ? "infra" : "txt";
|
|
1814
|
+
nodes.push({
|
|
1815
|
+
id,
|
|
1816
|
+
kind: "document",
|
|
1817
|
+
name,
|
|
1818
|
+
qualifiedName,
|
|
1819
|
+
file: qualifiedName,
|
|
1820
|
+
span: { start: 1, end: lineCount },
|
|
1821
|
+
lang: langTag,
|
|
1822
|
+
signature: `document:${category}`,
|
|
1823
|
+
doc: scrubbed,
|
|
1824
|
+
importance: IMPORTANCE[category] ?? 0.05,
|
|
1825
|
+
centrality: { degree: 0, pagerank: 0, betweenness: 0, eigenvector: 0 },
|
|
1826
|
+
area: -1,
|
|
1827
|
+
isHub: false,
|
|
1828
|
+
tested: null
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
return nodes.sort((a, b) => a.file.localeCompare(b.file) || a.id.localeCompare(b.id));
|
|
1832
|
+
}
|
|
1833
|
+
function shardTsProjects(root, files) {
|
|
1834
|
+
if (files.length === 0) return [];
|
|
1835
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1836
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1837
|
+
for (const f of files) {
|
|
1838
|
+
const dir = path18.dirname(f.abs);
|
|
1839
|
+
const key = nearestProjectKey(root, dir, cache);
|
|
1840
|
+
const list = byKey.get(key);
|
|
1841
|
+
if (list) list.push(f);
|
|
1842
|
+
else byKey.set(key, [f]);
|
|
1843
|
+
}
|
|
1844
|
+
return [...byKey.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([key, shardFiles]) => ({
|
|
1845
|
+
key,
|
|
1846
|
+
files: [...shardFiles].sort((a, b) => a.rel.localeCompare(b.rel))
|
|
1847
|
+
}));
|
|
1848
|
+
}
|
|
1849
|
+
function nearestProjectKey(root, startDir, cache) {
|
|
1850
|
+
const hit = cache.get(startDir);
|
|
1851
|
+
if (hit !== void 0) return hit;
|
|
1852
|
+
let dir = startDir;
|
|
1853
|
+
const rootAbs = path18.resolve(root);
|
|
1854
|
+
while (true) {
|
|
1855
|
+
if (fs16.existsSync(path18.join(dir, "tsconfig.json")) || fs16.existsSync(path18.join(dir, "tsconfig.base.json")) || fs16.existsSync(path18.join(dir, "package.json"))) {
|
|
1856
|
+
const key = toPosix3(path18.relative(rootAbs, dir)) || ".";
|
|
1857
|
+
cache.set(startDir, key);
|
|
1858
|
+
return key;
|
|
1859
|
+
}
|
|
1860
|
+
if (dir === rootAbs || path18.dirname(dir) === dir) {
|
|
1861
|
+
cache.set(startDir, ".");
|
|
1862
|
+
return ".";
|
|
1863
|
+
}
|
|
1864
|
+
dir = path18.dirname(dir);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
function toPosix3(p) {
|
|
1868
|
+
return p.split(path18.sep).join("/");
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
// src/engine/timing.ts
|
|
1872
|
+
var StageTimer = class {
|
|
1873
|
+
marks = /* @__PURE__ */ new Map();
|
|
1874
|
+
durations = {};
|
|
1875
|
+
start(name) {
|
|
1876
|
+
this.marks.set(name, nowMs());
|
|
1877
|
+
}
|
|
1878
|
+
end(name) {
|
|
1879
|
+
const t0 = this.marks.get(name);
|
|
1880
|
+
if (t0 === void 0) return 0;
|
|
1881
|
+
const ms = Math.round((nowMs() - t0) * 1e3) / 1e3;
|
|
1882
|
+
this.durations[name] = ms;
|
|
1883
|
+
this.marks.delete(name);
|
|
1884
|
+
return ms;
|
|
1885
|
+
}
|
|
1886
|
+
/** Run `fn` under a named stage. */
|
|
1887
|
+
async measure(name, fn) {
|
|
1888
|
+
this.start(name);
|
|
1889
|
+
try {
|
|
1890
|
+
return await fn();
|
|
1891
|
+
} finally {
|
|
1892
|
+
this.end(name);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
snapshot() {
|
|
1896
|
+
return { ...this.durations };
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
function nowMs() {
|
|
1900
|
+
return Number(process.hrtime.bigint() / 1000n) / 1e3;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// src/engine/build.ts
|
|
1904
|
+
async function buildGraph(options) {
|
|
1905
|
+
const timer = new StageTimer();
|
|
1906
|
+
timer.start("total");
|
|
1907
|
+
const root = path18.resolve(options.root);
|
|
1908
|
+
timer.start("discover");
|
|
1909
|
+
const files = discover({
|
|
1910
|
+
root,
|
|
1911
|
+
only: options.only,
|
|
1912
|
+
exclude: options.exclude,
|
|
1913
|
+
paths: options.paths
|
|
1914
|
+
});
|
|
1915
|
+
timer.end("discover");
|
|
1916
|
+
const limits = resolveLimits(options.limits);
|
|
1917
|
+
if (limits.maxFiles > 0 && files.length > limits.maxFiles) {
|
|
1918
|
+
throw new ResourceLimitError(
|
|
1919
|
+
`graph build stopped: ${files.length.toLocaleString()} files exceed the ${limits.maxFiles.toLocaleString()}-file limit. Scope the build (pass sub-paths, add --exclude globs, or --only <langs>), or set VG_MAX_FILES to raise the limit (0 disables it).`
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
checkMemoryBudget("discovery", limits.memoryBudgetMb);
|
|
1923
|
+
const grammars = grammarSetVersion();
|
|
1924
|
+
const cache = loadCache(root, {
|
|
1925
|
+
toolVersion: VERSION,
|
|
1926
|
+
grammars,
|
|
1927
|
+
disabled: options.noCache
|
|
1928
|
+
});
|
|
1929
|
+
timer.start("hash");
|
|
1930
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
1931
|
+
const fileStats = [];
|
|
1932
|
+
const toParse = [];
|
|
1933
|
+
const reused = [];
|
|
1934
|
+
const buildWarnings = [];
|
|
1935
|
+
let statHits = 0;
|
|
1936
|
+
for (const file of files) {
|
|
1937
|
+
let stat;
|
|
1938
|
+
try {
|
|
1939
|
+
stat = fs16.statSync(file.abs);
|
|
1940
|
+
} catch {
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
if (limits.maxFileBytes > 0 && stat.size > limits.maxFileBytes) {
|
|
1944
|
+
fileStats.push({
|
|
1945
|
+
rel: file.rel,
|
|
1946
|
+
size: stat.size,
|
|
1947
|
+
mtimeMs: stat.mtimeMs,
|
|
1948
|
+
hash: hashString(`vg:oversize:${stat.size}`)
|
|
1949
|
+
});
|
|
1950
|
+
buildWarnings.push(
|
|
1951
|
+
`${file.rel}: skipped \u2014 ${formatBytes(stat.size)} exceeds the ${formatBytes(limits.maxFileBytes)} per-file limit (set VG_MAX_FILE_BYTES to raise it, 0 to disable)`
|
|
1952
|
+
);
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1955
|
+
const byStat = options.noCache ? void 0 : cache.getByStat(file.rel, stat.mtimeMs, stat.size);
|
|
1956
|
+
if (byStat) {
|
|
1957
|
+
hashes.set(file.rel, byStat.hash);
|
|
1958
|
+
if (byStat.parse.hash !== byStat.hash) byStat.parse.hash = byStat.hash;
|
|
1959
|
+
fileStats.push({
|
|
1960
|
+
rel: file.rel,
|
|
1961
|
+
size: stat.size,
|
|
1962
|
+
mtimeMs: stat.mtimeMs,
|
|
1963
|
+
hash: byStat.hash
|
|
1964
|
+
});
|
|
1965
|
+
reused.push(byStat.parse);
|
|
1966
|
+
statHits++;
|
|
1967
|
+
continue;
|
|
1968
|
+
}
|
|
1969
|
+
let hash = "";
|
|
1970
|
+
try {
|
|
1971
|
+
hash = hashBytes(fs16.readFileSync(file.abs));
|
|
1972
|
+
} catch {
|
|
1973
|
+
continue;
|
|
2203
1974
|
}
|
|
2204
1975
|
hashes.set(file.rel, hash);
|
|
2205
1976
|
fileStats.push({ rel: file.rel, size: stat.size, mtimeMs: stat.mtimeMs, hash });
|
|
2206
1977
|
const cached2 = cache.get(file.rel, hash);
|
|
2207
|
-
if (cached2)
|
|
2208
|
-
|
|
1978
|
+
if (cached2) {
|
|
1979
|
+
reused.push(cached2);
|
|
1980
|
+
cache.set(file.rel, cached2, { mtimeMs: stat.mtimeMs, size: stat.size });
|
|
1981
|
+
} else {
|
|
1982
|
+
toParse.push(file);
|
|
1983
|
+
}
|
|
2209
1984
|
}
|
|
1985
|
+
timer.end("hash");
|
|
1986
|
+
timer.start("parse");
|
|
2210
1987
|
const parsedNew = await parseFiles(toParse, {
|
|
2211
1988
|
jobs: options.jobs,
|
|
2212
1989
|
inline: options.inline,
|
|
@@ -2214,8 +1991,12 @@ async function buildGraph(options) {
|
|
|
2214
1991
|
grammarsDir: options.grammarsDir,
|
|
2215
1992
|
memoryBudgetMb: limits.memoryBudgetMb
|
|
2216
1993
|
});
|
|
1994
|
+
timer.end("parse");
|
|
2217
1995
|
checkMemoryBudget("parse", limits.memoryBudgetMb);
|
|
2218
|
-
for (const p of parsedNew)
|
|
1996
|
+
for (const p of parsedNew) {
|
|
1997
|
+
const st = fileStats.find((f) => f.rel === p.rel);
|
|
1998
|
+
cache.set(p.rel, p, st ? { mtimeMs: st.mtimeMs, size: st.size } : void 0);
|
|
1999
|
+
}
|
|
2219
2000
|
const currentRels = new Set(files.map((f) => f.rel));
|
|
2220
2001
|
cache.prune(currentRels);
|
|
2221
2002
|
cache.save();
|
|
@@ -2223,31 +2004,61 @@ async function buildGraph(options) {
|
|
|
2223
2004
|
(a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0
|
|
2224
2005
|
);
|
|
2225
2006
|
const warnings = [...buildWarnings, ...parses.flatMap((p) => p.warnings ?? [])];
|
|
2007
|
+
timer.start("resolve");
|
|
2226
2008
|
const moduleResolver = buildModuleResolver(root, new Set(parses.map((p) => p.rel)));
|
|
2227
|
-
const resolved =
|
|
2009
|
+
const resolved = resolve(parses, moduleResolver);
|
|
2010
|
+
timer.end("resolve");
|
|
2228
2011
|
checkMemoryBudget("resolve", limits.memoryBudgetMb);
|
|
2229
2012
|
const nodeFileById = new Map(resolved.nodes.map((n) => [n.id, n.file]));
|
|
2230
2013
|
let edges = resolved.edges;
|
|
2231
2014
|
const resolvers = [...resolved.stats.resolvers];
|
|
2232
2015
|
const preciseCoveredFiles = /* @__PURE__ */ new Set();
|
|
2233
2016
|
let tscStats;
|
|
2234
|
-
|
|
2017
|
+
const skipTsc = options.noTsc || options.fast;
|
|
2018
|
+
let tsFiles = skipTsc ? [] : 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 }));
|
|
2235
2019
|
if (limits.tscMaxFiles > 0 && tsFiles.length > limits.tscMaxFiles) {
|
|
2236
2020
|
warnings.push(
|
|
2237
2021
|
`typescript resolver skipped \u2014 ${tsFiles.length.toLocaleString()} TS/JS files exceed the ${limits.tscMaxFiles.toLocaleString()}-file limit; calls use the heuristic resolver (set VG_TSC_MAX_FILES to raise it, 0 to disable)`
|
|
2238
2022
|
);
|
|
2239
2023
|
tsFiles = [];
|
|
2240
2024
|
}
|
|
2025
|
+
timer.start("tsc");
|
|
2241
2026
|
if (tsFiles.length) {
|
|
2242
|
-
const
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2027
|
+
const shards = shardTsProjects(root, tsFiles);
|
|
2028
|
+
let filesCovered = 0;
|
|
2029
|
+
let calls = 0;
|
|
2030
|
+
let jsx = 0;
|
|
2031
|
+
let heritage = 0;
|
|
2032
|
+
let resolvedCount = 0;
|
|
2033
|
+
const coveredAll = /* @__PURE__ */ new Set();
|
|
2034
|
+
const preciseAll = [];
|
|
2035
|
+
for (const shard of shards) {
|
|
2036
|
+
const res = tsResolveEdges(root, shard.files, resolved.nodes);
|
|
2037
|
+
if (res.stats.files === 0) continue;
|
|
2038
|
+
filesCovered += res.stats.files;
|
|
2039
|
+
calls += res.stats.calls;
|
|
2040
|
+
jsx += res.stats.jsx;
|
|
2041
|
+
heritage += res.stats.heritage;
|
|
2042
|
+
resolvedCount += res.stats.resolved;
|
|
2043
|
+
for (const f of res.coveredFiles) coveredAll.add(f);
|
|
2044
|
+
preciseAll.push(...res.edges);
|
|
2045
|
+
}
|
|
2046
|
+
if (filesCovered > 0) {
|
|
2047
|
+
edges = mergePreciseEdges(edges, preciseAll, coveredAll, nodeFileById);
|
|
2048
|
+
for (const f of coveredAll) preciseCoveredFiles.add(f);
|
|
2246
2049
|
if (!resolvers.includes("tsc")) resolvers.unshift("tsc");
|
|
2247
|
-
tscStats =
|
|
2050
|
+
tscStats = {
|
|
2051
|
+
files: filesCovered,
|
|
2052
|
+
calls,
|
|
2053
|
+
jsx,
|
|
2054
|
+
heritage,
|
|
2055
|
+
resolved: resolvedCount,
|
|
2056
|
+
shards: shards.length
|
|
2057
|
+
};
|
|
2248
2058
|
}
|
|
2249
2059
|
checkMemoryBudget("typescript resolution", limits.memoryBudgetMb);
|
|
2250
2060
|
}
|
|
2061
|
+
timer.end("tsc");
|
|
2251
2062
|
const scip = options.noScip ? null : loadScipIndex(root, options.scip);
|
|
2252
2063
|
let scipStats;
|
|
2253
2064
|
if (scip) {
|
|
@@ -2261,9 +2072,30 @@ async function buildGraph(options) {
|
|
|
2261
2072
|
let nodes = linked.nodes;
|
|
2262
2073
|
const coverage = options.noCoverage ? null : loadCoverage(root, options.coverage);
|
|
2263
2074
|
if (coverage) nodes = applyCoverage(nodes, coverage);
|
|
2264
|
-
const
|
|
2075
|
+
const docs = discoverDocs({
|
|
2076
|
+
root,
|
|
2077
|
+
exclude: options.exclude,
|
|
2078
|
+
paths: options.paths
|
|
2079
|
+
});
|
|
2080
|
+
for (const d of docs) {
|
|
2081
|
+
try {
|
|
2082
|
+
const st = fs16.statSync(d.abs);
|
|
2083
|
+
const hash = hashBytes(fs16.readFileSync(d.abs));
|
|
2084
|
+
hashes.set(d.rel, hash);
|
|
2085
|
+
fileStats.push({ rel: d.rel, size: st.size, mtimeMs: st.mtimeMs, hash });
|
|
2086
|
+
} catch {
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
const docNodes = documentNodesFromDocs(docs);
|
|
2090
|
+
if (docNodes.length) nodes = [...nodes, ...docNodes];
|
|
2091
|
+
timer.start("analyze");
|
|
2092
|
+
const analysis = analyze(nodes, linked.edges, {
|
|
2093
|
+
cluster: options.cluster,
|
|
2094
|
+
tier: options.analysisTier
|
|
2095
|
+
});
|
|
2096
|
+
timer.end("analyze");
|
|
2265
2097
|
checkMemoryBudget("analysis", limits.memoryBudgetMb);
|
|
2266
|
-
const languages = [
|
|
2098
|
+
const languages = [.../* @__PURE__ */ new Set([...parses.map((p) => p.lang), ...docNodes.map((n) => n.lang)])].sort();
|
|
2267
2099
|
const edgeKinds = [...new Set(analysis.edges.map((e) => e.kind))].sort();
|
|
2268
2100
|
const corpusHash = computeCorpusHash(parses, hashes);
|
|
2269
2101
|
const nodeKindById = new Map(analysis.nodes.map((n) => [n.id, n.kind]));
|
|
@@ -2289,8 +2121,9 @@ async function buildGraph(options) {
|
|
|
2289
2121
|
toolchain
|
|
2290
2122
|
},
|
|
2291
2123
|
meta: {
|
|
2292
|
-
root:
|
|
2124
|
+
root: path18.basename(root) === "" ? "." : ".",
|
|
2293
2125
|
languages,
|
|
2126
|
+
analysisTier: analysis.tier,
|
|
2294
2127
|
counts: {
|
|
2295
2128
|
nodes: analysis.nodes.length,
|
|
2296
2129
|
edges: analysis.edges.length,
|
|
@@ -2316,16 +2149,27 @@ async function buildGraph(options) {
|
|
|
2316
2149
|
const grounding = groundGraph(analysis.nodes, analysis.edges, parses);
|
|
2317
2150
|
if (grounding.length) graph.grounding = grounding;
|
|
2318
2151
|
}
|
|
2152
|
+
let indexResult;
|
|
2153
|
+
if (!options.noIndex) {
|
|
2154
|
+
timer.start("index");
|
|
2155
|
+
const wr = writeGraphIndex(root, graph);
|
|
2156
|
+
indexResult = { ok: wr.ok, path: wr.ok ? wr.path : void 0, reason: wr.reason };
|
|
2157
|
+
timer.end("index");
|
|
2158
|
+
}
|
|
2159
|
+
timer.end("total");
|
|
2160
|
+
const stages = timer.snapshot();
|
|
2319
2161
|
return {
|
|
2320
2162
|
graph,
|
|
2321
|
-
timing: { totalMs:
|
|
2163
|
+
timing: { totalMs: stages.total ?? 0, stages },
|
|
2322
2164
|
reparsed: parsedNew.length,
|
|
2323
2165
|
reused: reused.length,
|
|
2166
|
+
statHits,
|
|
2324
2167
|
totalFiles: files.length,
|
|
2325
2168
|
fileStats,
|
|
2326
2169
|
resolveStats: resolved.stats,
|
|
2327
2170
|
tsc: tscStats,
|
|
2328
2171
|
scip: scipStats,
|
|
2172
|
+
index: indexResult,
|
|
2329
2173
|
warnings
|
|
2330
2174
|
};
|
|
2331
2175
|
}
|
|
@@ -2335,14 +2179,14 @@ function toRepoRel(p) {
|
|
|
2335
2179
|
function loadScipIndex(root, explicit) {
|
|
2336
2180
|
const candidates = [
|
|
2337
2181
|
explicit,
|
|
2338
|
-
|
|
2339
|
-
|
|
2182
|
+
path18.join(root, "index.scip"),
|
|
2183
|
+
path18.join(root, ".vibgrate", "index.scip")
|
|
2340
2184
|
].filter((p) => Boolean(p));
|
|
2341
2185
|
for (const file of candidates) {
|
|
2342
|
-
const abs =
|
|
2343
|
-
if (!
|
|
2186
|
+
const abs = path18.isAbsolute(file) ? file : path18.resolve(root, file);
|
|
2187
|
+
if (!fs16.existsSync(abs)) continue;
|
|
2344
2188
|
try {
|
|
2345
|
-
const index = decodeScipIndex(new Uint8Array(
|
|
2189
|
+
const index = decodeScipIndex(new Uint8Array(fs16.readFileSync(abs)));
|
|
2346
2190
|
if (index.documents.length) {
|
|
2347
2191
|
return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
|
|
2348
2192
|
}
|
|
@@ -2384,187 +2228,13 @@ function computeToolchain(grammars, resolvers) {
|
|
|
2384
2228
|
};
|
|
2385
2229
|
}
|
|
2386
2230
|
function computeCorpusHash(parses, hashes) {
|
|
2387
|
-
const
|
|
2231
|
+
const fromParses = parses.map((p) => [p.rel, hashes.get(p.rel) ?? p.hash]);
|
|
2232
|
+
const fromHashes = [...hashes.entries()].map(([rel2, hash]) => [rel2, hash]);
|
|
2233
|
+
const byRel = /* @__PURE__ */ new Map();
|
|
2234
|
+
for (const [rel2, hash] of [...fromParses, ...fromHashes]) byRel.set(rel2, hash);
|
|
2235
|
+
const list = [...byRel.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
2388
2236
|
return hashString(canonicalize(list));
|
|
2389
2237
|
}
|
|
2390
|
-
function nowMs() {
|
|
2391
|
-
return Number(process.hrtime.bigint() / 1000n) / 1e3;
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
// src/engine/report.ts
|
|
2395
|
-
function renderReport(graph) {
|
|
2396
|
-
const { meta, provenance, nodes, edges } = graph;
|
|
2397
|
-
const lines = [];
|
|
2398
|
-
lines.push("# Code Graph Report");
|
|
2399
|
-
lines.push("");
|
|
2400
|
-
lines.push(`Generated by \`vg\` ${provenance.version} \xB7 schema \`${graph.schemaVersion}\``);
|
|
2401
|
-
lines.push("");
|
|
2402
|
-
lines.push("## Summary");
|
|
2403
|
-
lines.push("");
|
|
2404
|
-
lines.push(`- **Nodes:** ${meta.counts.nodes.toLocaleString("en-US")}`);
|
|
2405
|
-
lines.push(`- **Edges:** ${meta.counts.edges.toLocaleString("en-US")}`);
|
|
2406
|
-
lines.push(`- **Areas:** ${meta.counts.areas.toLocaleString("en-US")}`);
|
|
2407
|
-
lines.push(`- **Languages:** ${meta.languages.join(", ") || "\u2014"}`);
|
|
2408
|
-
lines.push(`- **Clustering:** ${meta.cluster}`);
|
|
2409
|
-
lines.push(`- **Resolvers:** ${provenance.resolver.join(", ")}`);
|
|
2410
|
-
lines.push(`- **Edge kinds:** ${meta.edgeKinds.join(", ") || "\u2014"}`);
|
|
2411
|
-
lines.push("");
|
|
2412
|
-
const fileNodes = nodes.filter((n) => n.kind === "file");
|
|
2413
|
-
const defsByFile = /* @__PURE__ */ new Map();
|
|
2414
|
-
for (const n of nodes) {
|
|
2415
|
-
if (n.kind === "file" || n.kind === "external") continue;
|
|
2416
|
-
defsByFile.set(n.file, (defsByFile.get(n.file) ?? 0) + 1);
|
|
2417
|
-
}
|
|
2418
|
-
const topFiles = [...defsByFile.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 15);
|
|
2419
|
-
if (topFiles.length) {
|
|
2420
|
-
lines.push("## Largest files (by definitions)");
|
|
2421
|
-
lines.push("");
|
|
2422
|
-
lines.push("| File | Definitions |");
|
|
2423
|
-
lines.push("| --- | ---: |");
|
|
2424
|
-
for (const [file, count] of topFiles) lines.push(`| \`${file}\` | ${count} |`);
|
|
2425
|
-
lines.push("");
|
|
2426
|
-
}
|
|
2427
|
-
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);
|
|
2428
|
-
if (topNodes.length) {
|
|
2429
|
-
lines.push("## Most-connected definitions");
|
|
2430
|
-
lines.push("");
|
|
2431
|
-
lines.push("| Symbol | Kind | File | Importance |");
|
|
2432
|
-
lines.push("| --- | --- | --- | ---: |");
|
|
2433
|
-
for (const n of topNodes) {
|
|
2434
|
-
lines.push(
|
|
2435
|
-
`| \`${n.qualifiedName}\` | ${n.kind} | \`${n.file}\`:${n.span.start} | ${n.importance.toFixed(3)} |`
|
|
2436
|
-
);
|
|
2437
|
-
}
|
|
2438
|
-
lines.push("");
|
|
2439
|
-
}
|
|
2440
|
-
const byKind = /* @__PURE__ */ new Map();
|
|
2441
|
-
for (const e of edges) byKind.set(e.kind, (byKind.get(e.kind) ?? 0) + 1);
|
|
2442
|
-
if (byKind.size) {
|
|
2443
|
-
lines.push("## Edges by kind");
|
|
2444
|
-
lines.push("");
|
|
2445
|
-
lines.push("| Kind | Count |");
|
|
2446
|
-
lines.push("| --- | ---: |");
|
|
2447
|
-
for (const [kind, count] of [...byKind.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
2448
|
-
lines.push(`| ${kind} | ${count} |`);
|
|
2449
|
-
}
|
|
2450
|
-
lines.push("");
|
|
2451
|
-
}
|
|
2452
|
-
lines.push("---");
|
|
2453
|
-
lines.push("");
|
|
2454
|
-
lines.push(`_Files: ${fileNodes.length} \xB7 Corpus: \`${provenance.corpusHash.slice(0, 16)}\u2026\`_`);
|
|
2455
|
-
lines.push("");
|
|
2456
|
-
return lines.join("\n");
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
|
-
// src/engine/html.ts
|
|
2460
|
-
function renderHtml(graph) {
|
|
2461
|
-
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);
|
|
2462
|
-
const rows = top.map(
|
|
2463
|
-
(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>`
|
|
2464
|
-
).join("\n");
|
|
2465
|
-
const embedded = stableStringify(graph, 0);
|
|
2466
|
-
return `<!doctype html>
|
|
2467
|
-
<html lang="en">
|
|
2468
|
-
<head>
|
|
2469
|
-
<meta charset="utf-8">
|
|
2470
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2471
|
-
<title>vg \xB7 code graph</title>
|
|
2472
|
-
<style>
|
|
2473
|
-
:root { color-scheme: light dark; }
|
|
2474
|
-
body { font: 14px/1.5 system-ui, sans-serif; margin: 2rem auto; max-width: 60rem; padding: 0 1rem; }
|
|
2475
|
-
h1 { font-size: 1.4rem; } h2 { font-size: 1.1rem; margin-top: 2rem; }
|
|
2476
|
-
table { border-collapse: collapse; width: 100%; }
|
|
2477
|
-
th, td { text-align: left; padding: 4px 8px; border-bottom: 1px solid #8884; }
|
|
2478
|
-
td:last-child, th:last-child { text-align: right; }
|
|
2479
|
-
.meta { color: #888; } code { font-size: 0.9em; }
|
|
2480
|
-
</style>
|
|
2481
|
-
</head>
|
|
2482
|
-
<body>
|
|
2483
|
-
<h1>vg \xB7 code graph</h1>
|
|
2484
|
-
<p class="meta">${graph.meta.counts.nodes} nodes \xB7 ${graph.meta.counts.edges} edges \xB7
|
|
2485
|
-
${graph.meta.counts.areas} areas \xB7 ${esc(graph.meta.languages.join(", ") || "\u2014")} \xB7
|
|
2486
|
-
clustering: ${esc(graph.meta.cluster)} \xB7 vg ${esc(graph.provenance.version)}</p>
|
|
2487
|
-
<h2>Most-connected definitions</h2>
|
|
2488
|
-
<table>
|
|
2489
|
-
<thead><tr><th>Symbol</th><th>Kind</th><th>Location</th><th>Importance</th></tr></thead>
|
|
2490
|
-
<tbody>
|
|
2491
|
-
${rows}
|
|
2492
|
-
</tbody>
|
|
2493
|
-
</table>
|
|
2494
|
-
<p class="meta">Interactive WebGL view arrives in Phase 1. The full graph is embedded below for tooling.</p>
|
|
2495
|
-
<script type="application/json" id="vg-graph">
|
|
2496
|
-
${embedded}
|
|
2497
|
-
</script>
|
|
2498
|
-
</body>
|
|
2499
|
-
</html>
|
|
2500
|
-
`;
|
|
2501
|
-
}
|
|
2502
|
-
function esc(s) {
|
|
2503
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2504
|
-
}
|
|
2505
|
-
function vibgrateDir(root) {
|
|
2506
|
-
return path2.join(root, ".vibgrate");
|
|
2507
|
-
}
|
|
2508
|
-
var DEFAULT_GITIGNORE = [
|
|
2509
|
-
"# Created once by vg \u2014 local graph artifacts stay out of git by default.",
|
|
2510
|
-
"# Run `vg share` to commit the map for your team (it rewrites this file).",
|
|
2511
|
-
"# vg never touches an existing .vibgrate/.gitignore: edit it (or leave it",
|
|
2512
|
-
"# empty) to manage these ignores yourself.",
|
|
2513
|
-
".gitignore",
|
|
2514
|
-
"cache/",
|
|
2515
|
-
"graph.json",
|
|
2516
|
-
"graph.html",
|
|
2517
|
-
"GRAPH_REPORT.md",
|
|
2518
|
-
"facts.jsonl",
|
|
2519
|
-
"mcp-navigation.json"
|
|
2520
|
-
];
|
|
2521
|
-
function ensureVibgrateGitignore(root) {
|
|
2522
|
-
const file = path2.join(vibgrateDir(root), ".gitignore");
|
|
2523
|
-
if (fs18.existsSync(file)) return;
|
|
2524
|
-
fs18.mkdirSync(vibgrateDir(root), { recursive: true });
|
|
2525
|
-
fs18.writeFileSync(file, `${DEFAULT_GITIGNORE.join("\n")}
|
|
2526
|
-
`);
|
|
2527
|
-
}
|
|
2528
|
-
function defaultGraphPath(root) {
|
|
2529
|
-
return path2.join(vibgrateDir(root), "graph.json");
|
|
2530
|
-
}
|
|
2531
|
-
function writeArtifacts(graph, options) {
|
|
2532
|
-
const dir = vibgrateDir(options.root);
|
|
2533
|
-
fs18.mkdirSync(dir, { recursive: true });
|
|
2534
|
-
ensureVibgrateGitignore(options.root);
|
|
2535
|
-
const graphPath = options.graphPath ?? defaultGraphPath(options.root);
|
|
2536
|
-
fs18.mkdirSync(path2.dirname(graphPath), { recursive: true });
|
|
2537
|
-
const tmp = `${graphPath}.${process.pid}.tmp`;
|
|
2538
|
-
try {
|
|
2539
|
-
fs18.writeFileSync(tmp, serializeGraph(graph));
|
|
2540
|
-
fs18.renameSync(tmp, graphPath);
|
|
2541
|
-
} catch (err) {
|
|
2542
|
-
fs18.rmSync(tmp, { force: true });
|
|
2543
|
-
throw err;
|
|
2544
|
-
}
|
|
2545
|
-
const written = { graphPath };
|
|
2546
|
-
if (options.report !== false) {
|
|
2547
|
-
const reportPath = path2.join(dir, "GRAPH_REPORT.md");
|
|
2548
|
-
fs18.writeFileSync(reportPath, renderReport(graph));
|
|
2549
|
-
written.reportPath = reportPath;
|
|
2550
|
-
}
|
|
2551
|
-
if (options.html !== false) {
|
|
2552
|
-
const htmlPath = path2.join(dir, "graph.html");
|
|
2553
|
-
fs18.writeFileSync(htmlPath, renderHtml(graph));
|
|
2554
|
-
written.htmlPath = htmlPath;
|
|
2555
|
-
}
|
|
2556
|
-
if (graph.facts && graph.facts.length) {
|
|
2557
|
-
const factsPath = path2.join(dir, "facts.jsonl");
|
|
2558
|
-
fs18.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
|
|
2559
|
-
written.factsPath = factsPath;
|
|
2560
|
-
}
|
|
2561
|
-
return written;
|
|
2562
|
-
}
|
|
2563
|
-
function loadGraph(root, graphPath) {
|
|
2564
|
-
const file = graphPath ?? defaultGraphPath(root);
|
|
2565
|
-
if (!fs18.existsSync(file)) return null;
|
|
2566
|
-
return parseGraph(fs18.readFileSync(file, "utf8"));
|
|
2567
|
-
}
|
|
2568
2238
|
|
|
2569
2239
|
// src/engine/verify.ts
|
|
2570
2240
|
var PINNED = "1970-01-01T00:00:00.000Z";
|
|
@@ -2631,21 +2301,21 @@ function usageError(message) {
|
|
|
2631
2301
|
}
|
|
2632
2302
|
var SNAPSHOT_VERSION = "vg-freshness/1";
|
|
2633
2303
|
function snapshotPath(root) {
|
|
2634
|
-
return
|
|
2304
|
+
return path18.join(cacheDir(root), "freshness.json");
|
|
2635
2305
|
}
|
|
2636
2306
|
function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
|
|
2637
2307
|
const files = {};
|
|
2638
2308
|
for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
|
|
2639
2309
|
const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
|
|
2640
2310
|
try {
|
|
2641
|
-
|
|
2642
|
-
|
|
2311
|
+
fs16.mkdirSync(cacheDir(root), { recursive: true });
|
|
2312
|
+
fs16.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
2643
2313
|
} catch {
|
|
2644
2314
|
}
|
|
2645
2315
|
}
|
|
2646
2316
|
function loadSnapshot(root) {
|
|
2647
2317
|
try {
|
|
2648
|
-
const loaded = JSON.parse(
|
|
2318
|
+
const loaded = JSON.parse(fs16.readFileSync(snapshotPath(root), "utf8"));
|
|
2649
2319
|
if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
|
|
2650
2320
|
return { ...loaded, scope: loaded.scope ?? {} };
|
|
2651
2321
|
}
|
|
@@ -2681,7 +2351,7 @@ function probeFreshness(root) {
|
|
|
2681
2351
|
}
|
|
2682
2352
|
let stat;
|
|
2683
2353
|
try {
|
|
2684
|
-
stat =
|
|
2354
|
+
stat = fs16.statSync(file.abs);
|
|
2685
2355
|
} catch {
|
|
2686
2356
|
drift.removed.push(file.rel);
|
|
2687
2357
|
continue;
|
|
@@ -2689,7 +2359,7 @@ function probeFreshness(root) {
|
|
|
2689
2359
|
if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
|
|
2690
2360
|
let hash = "";
|
|
2691
2361
|
try {
|
|
2692
|
-
hash = hashBytes(
|
|
2362
|
+
hash = hashBytes(fs16.readFileSync(file.abs));
|
|
2693
2363
|
} catch {
|
|
2694
2364
|
drift.removed.push(file.rel);
|
|
2695
2365
|
continue;
|
|
@@ -2706,7 +2376,7 @@ function probeFreshness(root) {
|
|
|
2706
2376
|
}
|
|
2707
2377
|
if (absorbed && !hasDrift(drift)) {
|
|
2708
2378
|
try {
|
|
2709
|
-
|
|
2379
|
+
fs16.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
2710
2380
|
} catch {
|
|
2711
2381
|
}
|
|
2712
2382
|
}
|
|
@@ -2787,10 +2457,10 @@ function testsToRun(graph, rootId, depth = 4) {
|
|
|
2787
2457
|
};
|
|
2788
2458
|
}
|
|
2789
2459
|
function detectRunner(root, lang) {
|
|
2790
|
-
const pkgPath =
|
|
2791
|
-
if (
|
|
2460
|
+
const pkgPath = path18.join(root, "package.json");
|
|
2461
|
+
if (fs16.existsSync(pkgPath)) {
|
|
2792
2462
|
try {
|
|
2793
|
-
const pkg = JSON.parse(
|
|
2463
|
+
const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf8"));
|
|
2794
2464
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2795
2465
|
if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
|
|
2796
2466
|
if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
|
|
@@ -2798,10 +2468,10 @@ function detectRunner(root, lang) {
|
|
|
2798
2468
|
} catch {
|
|
2799
2469
|
}
|
|
2800
2470
|
}
|
|
2801
|
-
if (lang === "py" ||
|
|
2471
|
+
if (lang === "py" || fs16.existsSync(path18.join(root, "pyproject.toml")) || fs16.existsSync(path18.join(root, "pytest.ini"))) {
|
|
2802
2472
|
return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
|
|
2803
2473
|
}
|
|
2804
|
-
if (lang === "go" ||
|
|
2474
|
+
if (lang === "go" || fs16.existsSync(path18.join(root, "go.mod"))) {
|
|
2805
2475
|
return { name: "go test", command: () => "go test ./..." };
|
|
2806
2476
|
}
|
|
2807
2477
|
if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
|
|
@@ -2812,82 +2482,16 @@ function detectRunner(root, lang) {
|
|
|
2812
2482
|
}
|
|
2813
2483
|
function hasFile(root, re) {
|
|
2814
2484
|
try {
|
|
2815
|
-
return
|
|
2485
|
+
return fs16.readdirSync(root).some((f) => re.test(f));
|
|
2816
2486
|
} catch {
|
|
2817
2487
|
return false;
|
|
2818
2488
|
}
|
|
2819
2489
|
}
|
|
2820
|
-
function discoverModels(home = os.homedir()) {
|
|
2821
|
-
return [...ollama(home), ...lmStudio(home), ...looseGguf(home)].sort(
|
|
2822
|
-
(a, b) => a.runtime.localeCompare(b.runtime) || a.name.localeCompare(b.name)
|
|
2823
|
-
);
|
|
2824
|
-
}
|
|
2825
|
-
function ollama(home) {
|
|
2826
|
-
const base = path2.join(home, ".ollama", "models", "manifests");
|
|
2827
|
-
const out = [];
|
|
2828
|
-
walk(base, 4, (file) => {
|
|
2829
|
-
const rel2 = path2.relative(base, file).split(path2.sep);
|
|
2830
|
-
if (rel2.length >= 2) {
|
|
2831
|
-
const tag = rel2[rel2.length - 1];
|
|
2832
|
-
const model = rel2[rel2.length - 2];
|
|
2833
|
-
out.push({ runtime: "ollama", name: `${model}:${tag}`, path: file });
|
|
2834
|
-
}
|
|
2835
|
-
});
|
|
2836
|
-
return dedupe(out);
|
|
2837
|
-
}
|
|
2838
|
-
function lmStudio(home) {
|
|
2839
|
-
const bases = [path2.join(home, ".lmstudio", "models"), path2.join(home, ".cache", "lm-studio", "models")];
|
|
2840
|
-
const out = [];
|
|
2841
|
-
for (const base of bases) {
|
|
2842
|
-
walk(base, 5, (file) => {
|
|
2843
|
-
if (!file.endsWith(".gguf")) return;
|
|
2844
|
-
const rel2 = path2.relative(base, file).replace(/\\/g, "/");
|
|
2845
|
-
out.push({ runtime: "lm-studio", name: rel2, path: file });
|
|
2846
|
-
});
|
|
2847
|
-
}
|
|
2848
|
-
return dedupe(out);
|
|
2849
|
-
}
|
|
2850
|
-
function looseGguf(home) {
|
|
2851
|
-
const bases = [path2.join(home, "models"), path2.join(home, ".cache", "huggingface")];
|
|
2852
|
-
const out = [];
|
|
2853
|
-
for (const base of bases) {
|
|
2854
|
-
walk(base, 4, (file) => {
|
|
2855
|
-
if (!file.endsWith(".gguf")) return;
|
|
2856
|
-
out.push({ runtime: "gguf", name: path2.basename(file), path: file });
|
|
2857
|
-
});
|
|
2858
|
-
}
|
|
2859
|
-
return dedupe(out);
|
|
2860
|
-
}
|
|
2861
|
-
function walk(dir, depth, onFile) {
|
|
2862
|
-
if (depth < 0) return;
|
|
2863
|
-
let entries;
|
|
2864
|
-
try {
|
|
2865
|
-
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
2866
|
-
} catch {
|
|
2867
|
-
return;
|
|
2868
|
-
}
|
|
2869
|
-
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2870
|
-
const abs = path2.join(dir, e.name);
|
|
2871
|
-
if (e.isDirectory()) walk(abs, depth - 1, onFile);
|
|
2872
|
-
else if (e.isFile()) onFile(abs);
|
|
2873
|
-
}
|
|
2874
|
-
}
|
|
2875
|
-
function dedupe(models) {
|
|
2876
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2877
|
-
const out = [];
|
|
2878
|
-
for (const m of models) {
|
|
2879
|
-
const key = `${m.runtime}:${m.name}`;
|
|
2880
|
-
if (seen.has(key)) continue;
|
|
2881
|
-
seen.add(key);
|
|
2882
|
-
out.push(m);
|
|
2883
|
-
}
|
|
2884
|
-
return out;
|
|
2885
|
-
}
|
|
2886
2490
|
function findGitRoot(startDir = process.cwd()) {
|
|
2887
|
-
let dir =
|
|
2491
|
+
let dir = path18.resolve(startDir);
|
|
2888
2492
|
for (; ; ) {
|
|
2889
|
-
if (
|
|
2890
|
-
const parent =
|
|
2493
|
+
if (fs16.existsSync(path18.join(dir, ".git"))) return dir;
|
|
2494
|
+
const parent = path18.dirname(dir);
|
|
2891
2495
|
if (parent === dir) return null;
|
|
2892
2496
|
dir = parent;
|
|
2893
2497
|
}
|
|
@@ -2898,19 +2502,19 @@ function normalizeEntry(line) {
|
|
|
2898
2502
|
function ensureGitignored(entry, startDir = process.cwd()) {
|
|
2899
2503
|
const root = findGitRoot(startDir);
|
|
2900
2504
|
if (!root) return { status: "not-a-repo", entry };
|
|
2901
|
-
const gitignorePath =
|
|
2505
|
+
const gitignorePath = path18.join(root, ".gitignore");
|
|
2902
2506
|
const target = normalizeEntry(entry);
|
|
2903
2507
|
let existing = "";
|
|
2904
2508
|
let fileExisted = true;
|
|
2905
2509
|
try {
|
|
2906
|
-
existing =
|
|
2510
|
+
existing = fs16.readFileSync(gitignorePath, "utf8");
|
|
2907
2511
|
} catch {
|
|
2908
2512
|
fileExisted = false;
|
|
2909
2513
|
}
|
|
2910
2514
|
const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
|
|
2911
2515
|
if (alreadyPresent) return { status: "present", entry, gitignorePath };
|
|
2912
2516
|
const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
|
|
2913
|
-
|
|
2517
|
+
fs16.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
|
|
2914
2518
|
`, "utf8");
|
|
2915
2519
|
return { status: fileExisted ? "added" : "created", entry, gitignorePath };
|
|
2916
2520
|
}
|
|
@@ -3064,14 +2668,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
3064
2668
|
console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
|
|
3065
2669
|
}
|
|
3066
2670
|
if (opts.write) {
|
|
3067
|
-
const writePath =
|
|
2671
|
+
const writePath = path18.resolve(opts.write);
|
|
3068
2672
|
await writeTextFile(writePath, dsn + "\n");
|
|
3069
2673
|
console.log("");
|
|
3070
2674
|
console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
|
|
3071
|
-
const root = findGitRoot(
|
|
3072
|
-
const rel2 = root ?
|
|
2675
|
+
const root = findGitRoot(path18.dirname(writePath));
|
|
2676
|
+
const rel2 = root ? path18.relative(root, writePath).split(path18.sep).join("/") : "";
|
|
3073
2677
|
if (root && rel2 && !rel2.startsWith("..")) {
|
|
3074
|
-
const res = ensureGitignored(rel2,
|
|
2678
|
+
const res = ensureGitignored(rel2, path18.dirname(writePath));
|
|
3075
2679
|
if (res.status === "created") {
|
|
3076
2680
|
console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
|
|
3077
2681
|
} else if (res.status === "added") {
|
|
@@ -3089,30 +2693,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
3089
2693
|
var STORE_DIRNAME = ".vibgrate";
|
|
3090
2694
|
var STORE_FILENAME = "credentials.json";
|
|
3091
2695
|
function homeCredentialsPath() {
|
|
3092
|
-
return
|
|
2696
|
+
return path18.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
|
|
3093
2697
|
}
|
|
3094
2698
|
function projectCredentialsPath(cwd = process.cwd()) {
|
|
3095
2699
|
const root = findGitRoot(cwd) ?? cwd;
|
|
3096
|
-
return
|
|
2700
|
+
return path18.join(root, STORE_DIRNAME, STORE_FILENAME);
|
|
3097
2701
|
}
|
|
3098
2702
|
function credentialsPath(opts = {}) {
|
|
3099
2703
|
const override = process.env.VIBGRATE_CREDENTIALS?.trim();
|
|
3100
|
-
if (override) return
|
|
2704
|
+
if (override) return path18.resolve(override);
|
|
3101
2705
|
const local = projectCredentialsPath(opts.cwd);
|
|
3102
2706
|
if (opts.local) return local;
|
|
3103
|
-
if (
|
|
2707
|
+
if (fs16.existsSync(local)) return local;
|
|
3104
2708
|
return homeCredentialsPath();
|
|
3105
2709
|
}
|
|
3106
2710
|
function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
|
|
3107
|
-
const rel2 =
|
|
3108
|
-
if (rel2 && !rel2.startsWith("..") && !
|
|
3109
|
-
return rel2.split(
|
|
2711
|
+
const rel2 = path18.relative(repoRoot, credsFile);
|
|
2712
|
+
if (rel2 && !rel2.startsWith("..") && !path18.isAbsolute(rel2)) {
|
|
2713
|
+
return rel2.split(path18.sep).join("/");
|
|
3110
2714
|
}
|
|
3111
2715
|
return `${STORE_DIRNAME}/${STORE_FILENAME}`;
|
|
3112
2716
|
}
|
|
3113
2717
|
function readStoredCredentials(opts = {}) {
|
|
3114
2718
|
try {
|
|
3115
|
-
const raw =
|
|
2719
|
+
const raw = fs16.readFileSync(credentialsPath(opts), "utf8");
|
|
3116
2720
|
const parsed = JSON.parse(raw);
|
|
3117
2721
|
return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
|
|
3118
2722
|
} catch {
|
|
@@ -3121,16 +2725,16 @@ function readStoredCredentials(opts = {}) {
|
|
|
3121
2725
|
}
|
|
3122
2726
|
function writeStoredCredentials(creds, opts = {}) {
|
|
3123
2727
|
const file = credentialsPath(opts);
|
|
3124
|
-
|
|
3125
|
-
|
|
2728
|
+
fs16.mkdirSync(path18.dirname(file), { recursive: true });
|
|
2729
|
+
fs16.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
3126
2730
|
try {
|
|
3127
|
-
|
|
2731
|
+
fs16.chmodSync(file, 384);
|
|
3128
2732
|
} catch {
|
|
3129
2733
|
}
|
|
3130
2734
|
}
|
|
3131
2735
|
function clearStoredCredentials(opts = {}) {
|
|
3132
2736
|
try {
|
|
3133
|
-
|
|
2737
|
+
fs16.rmSync(credentialsPath(opts));
|
|
3134
2738
|
return true;
|
|
3135
2739
|
} catch {
|
|
3136
2740
|
return false;
|
|
@@ -3501,7 +3105,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
3501
3105
|
if (opts.strict) process.exit(1);
|
|
3502
3106
|
return;
|
|
3503
3107
|
}
|
|
3504
|
-
const filePath =
|
|
3108
|
+
const filePath = path18.resolve(opts.file);
|
|
3505
3109
|
if (!await pathExists(filePath)) {
|
|
3506
3110
|
console.error(chalk.red(`Scan artifact not found: ${filePath}`));
|
|
3507
3111
|
console.error(chalk.dim('Run "vibgrate scan" first.'));
|
|
@@ -3562,935 +3166,935 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
3562
3166
|
if (opts.strict) process.exit(1);
|
|
3563
3167
|
}
|
|
3564
3168
|
});
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3169
|
+
var MAX_FILE_BYTES = 1e6;
|
|
3170
|
+
var PREVIEW_CHARS = 120;
|
|
3171
|
+
var NUL = "\0";
|
|
3172
|
+
var PARALLEL_MIN_BYTES = 64e6;
|
|
3173
|
+
var PARALLEL_MIN_FILES = 2e3;
|
|
3174
|
+
var ROW_CAP = 1e4;
|
|
3175
|
+
async function scanCandidates(root, files, needle, opts) {
|
|
3176
|
+
const needleLower = needle.toLowerCase();
|
|
3177
|
+
if (!needleLower || files.length === 0) return { hits: [], total: 0, truncated: false };
|
|
3178
|
+
if (files.length < PARALLEL_MIN_FILES && !process.env.VG_PARALLEL_MIN_BYTES) {
|
|
3179
|
+
return opts.collectAll ? scanInline(root, files, needleLower, ROW_CAP, false, void 0) : scanInline(root, files, needleLower, opts.budget, true, void 0);
|
|
3180
|
+
}
|
|
3181
|
+
const { over, sizes } = measure(root, files);
|
|
3182
|
+
if (!over) {
|
|
3183
|
+
return opts.collectAll ? scanInline(root, files, needleLower, ROW_CAP, false, sizes) : scanInline(root, files, needleLower, opts.budget, true, sizes);
|
|
3184
|
+
}
|
|
3185
|
+
const out = await scanParallel(root, files, needleLower);
|
|
3186
|
+
if (!opts.collectAll && out.hits.length > opts.budget) {
|
|
3187
|
+
out.hits.length = opts.budget;
|
|
3188
|
+
out.truncated = true;
|
|
3571
3189
|
}
|
|
3190
|
+
return out;
|
|
3572
3191
|
}
|
|
3573
|
-
function
|
|
3574
|
-
|
|
3192
|
+
function parallelMinBytes() {
|
|
3193
|
+
const v = Number(process.env.VG_PARALLEL_MIN_BYTES);
|
|
3194
|
+
return Number.isFinite(v) && v >= 0 ? v : PARALLEL_MIN_BYTES;
|
|
3575
3195
|
}
|
|
3576
|
-
function
|
|
3577
|
-
const
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
};
|
|
3196
|
+
function measure(root, files) {
|
|
3197
|
+
const threshold = parallelMinBytes();
|
|
3198
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
3199
|
+
let total = 0;
|
|
3200
|
+
for (const rel2 of files) {
|
|
3201
|
+
let size;
|
|
3202
|
+
try {
|
|
3203
|
+
size = fs16.statSync(path18.join(root, rel2)).size;
|
|
3204
|
+
} catch {
|
|
3205
|
+
continue;
|
|
3587
3206
|
}
|
|
3207
|
+
sizes.set(rel2, size);
|
|
3208
|
+
if (size <= MAX_FILE_BYTES) total += size;
|
|
3209
|
+
if (total >= threshold) return { over: true, sizes };
|
|
3588
3210
|
}
|
|
3589
|
-
|
|
3590
|
-
if (rec) {
|
|
3591
|
-
const ecosystem = rec.ecosystem === "npm" ? "npm" : rec.ecosystem === "pypi" ? "pypi" : "unknown";
|
|
3592
|
-
return { ecosystem, currentVersion: rec.installed ?? null, latestVersion: rec.latest ?? null, majorsBehind: null };
|
|
3593
|
-
}
|
|
3594
|
-
return { ecosystem: "unknown", currentVersion: null, latestVersion: null, majorsBehind: null };
|
|
3211
|
+
return { over: false, sizes };
|
|
3595
3212
|
}
|
|
3596
|
-
function
|
|
3597
|
-
const
|
|
3598
|
-
|
|
3599
|
-
|
|
3213
|
+
function matchLines(text, needleLower) {
|
|
3214
|
+
const low = text.toLowerCase();
|
|
3215
|
+
if (!low.includes(needleLower)) return [];
|
|
3216
|
+
const lowLines = low.split("\n");
|
|
3217
|
+
const rawLines = text.split("\n");
|
|
3218
|
+
const hits = [];
|
|
3219
|
+
for (let i = 0; i < lowLines.length; i++) {
|
|
3220
|
+
if (lowLines[i].includes(needleLower)) hits.push({ line: i + 1, preview: rawLines[i].trim().slice(0, PREVIEW_CHARS) });
|
|
3221
|
+
}
|
|
3222
|
+
return hits;
|
|
3600
3223
|
}
|
|
3601
|
-
function
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3224
|
+
function readCandidate(abs, knownSize) {
|
|
3225
|
+
try {
|
|
3226
|
+
const size = knownSize ?? fs16.statSync(abs).size;
|
|
3227
|
+
if (size > MAX_FILE_BYTES) return null;
|
|
3228
|
+
const text = fs16.readFileSync(abs, "utf8");
|
|
3229
|
+
return text.includes(NUL) ? null : text;
|
|
3230
|
+
} catch {
|
|
3231
|
+
return null;
|
|
3232
|
+
}
|
|
3607
3233
|
}
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
}
|
|
3622
|
-
|
|
3623
|
-
|
|
3234
|
+
function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
|
|
3235
|
+
const hits = [];
|
|
3236
|
+
let total = 0;
|
|
3237
|
+
let truncated = false;
|
|
3238
|
+
for (const rel2 of files) {
|
|
3239
|
+
const text = readCandidate(path18.join(root, rel2), sizes?.get(rel2));
|
|
3240
|
+
if (text === null) continue;
|
|
3241
|
+
for (const h of matchLines(text, needleLower)) {
|
|
3242
|
+
total++;
|
|
3243
|
+
if (hits.length < cap) hits.push({ file: rel2, line: h.line, preview: h.preview });
|
|
3244
|
+
else {
|
|
3245
|
+
truncated = true;
|
|
3246
|
+
if (stopAtCap) return { hits, total, truncated };
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
return { hits, total, truncated };
|
|
3624
3251
|
}
|
|
3625
|
-
async function
|
|
3626
|
-
const
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3252
|
+
async function scanParallel(root, files, needleLower) {
|
|
3253
|
+
const n = Math.min(files.length, Math.max(2, Math.min(8, os.cpus().length - 1)));
|
|
3254
|
+
const shards = Array.from({ length: n }, () => []);
|
|
3255
|
+
files.forEach((f, i) => shards[i % n].push(f));
|
|
3256
|
+
let results;
|
|
3257
|
+
try {
|
|
3258
|
+
results = await Promise.all(shards.map((shard) => runWorker(root, shard, needleLower)));
|
|
3259
|
+
} catch {
|
|
3260
|
+
return scanInline(root, files, needleLower, ROW_CAP, false, void 0);
|
|
3261
|
+
}
|
|
3262
|
+
const hits = [];
|
|
3263
|
+
let total = 0;
|
|
3264
|
+
let truncated = false;
|
|
3265
|
+
for (const r of results) {
|
|
3266
|
+
total += r.total;
|
|
3267
|
+
truncated = truncated || r.truncated;
|
|
3268
|
+
for (const h of r.hits) hits.push(h);
|
|
3269
|
+
}
|
|
3270
|
+
hits.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line);
|
|
3271
|
+
if (hits.length > ROW_CAP) {
|
|
3272
|
+
hits.length = ROW_CAP;
|
|
3273
|
+
truncated = true;
|
|
3274
|
+
}
|
|
3275
|
+
return { hits, total, truncated };
|
|
3276
|
+
}
|
|
3277
|
+
var WORKER_SOURCE = [
|
|
3278
|
+
"const { parentPort, workerData } = require('worker_threads');",
|
|
3279
|
+
"const fs = require('fs');",
|
|
3280
|
+
"const path = require('path');",
|
|
3281
|
+
`const MAX = ${MAX_FILE_BYTES};`,
|
|
3282
|
+
`const PREVIEW = ${PREVIEW_CHARS};`,
|
|
3283
|
+
`const CAP = ${ROW_CAP};`,
|
|
3284
|
+
"const NUL = String.fromCharCode(0);",
|
|
3285
|
+
"const NL = String.fromCharCode(10);",
|
|
3286
|
+
"const { root, files, needleLower } = workerData;",
|
|
3287
|
+
"const hits = []; let total = 0, truncated = false;",
|
|
3288
|
+
"for (const rel of files) {",
|
|
3289
|
+
" const abs = path.join(root, rel);",
|
|
3290
|
+
" let text;",
|
|
3291
|
+
' try { if (fs.statSync(abs).size > MAX) continue; text = fs.readFileSync(abs, "utf8"); } catch { continue; }',
|
|
3292
|
+
" if (text.includes(NUL)) continue;",
|
|
3293
|
+
" const low = text.toLowerCase();",
|
|
3294
|
+
" if (!low.includes(needleLower)) continue;",
|
|
3295
|
+
" const lowLines = low.split(NL); const rawLines = text.split(NL);",
|
|
3296
|
+
" for (let i = 0; i < lowLines.length; i++) {",
|
|
3297
|
+
" if (!lowLines[i].includes(needleLower)) continue;",
|
|
3298
|
+
" total++;",
|
|
3299
|
+
" if (hits.length < CAP) hits.push({ file: rel, line: i + 1, preview: rawLines[i].trim().slice(0, PREVIEW) });",
|
|
3300
|
+
" else truncated = true;",
|
|
3301
|
+
" }",
|
|
3302
|
+
"}",
|
|
3303
|
+
"parentPort.postMessage({ hits, total, truncated });"
|
|
3304
|
+
].join("\n");
|
|
3305
|
+
function runWorker(root, files, needleLower) {
|
|
3306
|
+
return new Promise((resolve12, reject) => {
|
|
3307
|
+
const w = new Worker(WORKER_SOURCE, { eval: true, workerData: { root, files, needleLower } });
|
|
3308
|
+
let settled = false;
|
|
3309
|
+
w.once("message", (m) => {
|
|
3310
|
+
settled = true;
|
|
3311
|
+
resolve12(m);
|
|
3312
|
+
});
|
|
3313
|
+
w.once("error", reject);
|
|
3314
|
+
w.once("exit", (code) => {
|
|
3315
|
+
if (!settled && code !== 0) reject(new Error(`literal-scan worker exited with code ${code}`));
|
|
3316
|
+
});
|
|
3641
3317
|
});
|
|
3642
|
-
return { ...inv, records, attribution: "git" };
|
|
3643
3318
|
}
|
|
3644
|
-
|
|
3645
|
-
|
|
3319
|
+
|
|
3320
|
+
// src/engine/search.ts
|
|
3321
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
3322
|
+
".git",
|
|
3323
|
+
".vibgrate",
|
|
3324
|
+
"node_modules",
|
|
3325
|
+
"dist",
|
|
3326
|
+
"build",
|
|
3327
|
+
"out",
|
|
3328
|
+
"target",
|
|
3329
|
+
"vendor",
|
|
3330
|
+
"__pycache__",
|
|
3331
|
+
// .NET intermediate output and coverage reports: purely generated trees that a
|
|
3332
|
+
// C#/coverage-heavy repo pays for on every literal sweep. `bin` is deliberately
|
|
3333
|
+
// NOT here — plenty of repos keep real scripts in bin/ (rails, node CLIs), and
|
|
3334
|
+
// completeness beats speed; the binary-extension skip below keeps a .NET bin/
|
|
3335
|
+
// cheap anyway.
|
|
3336
|
+
"obj",
|
|
3337
|
+
"coverage"
|
|
3338
|
+
]);
|
|
3339
|
+
var MAX_FILES_SCANNED = 2e4;
|
|
3340
|
+
var BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
3341
|
+
".dll",
|
|
3342
|
+
".pdb",
|
|
3343
|
+
".exe",
|
|
3344
|
+
".so",
|
|
3345
|
+
".dylib",
|
|
3346
|
+
".a",
|
|
3347
|
+
".lib",
|
|
3348
|
+
".o",
|
|
3349
|
+
".class",
|
|
3350
|
+
".jar",
|
|
3351
|
+
".wasm",
|
|
3352
|
+
".node",
|
|
3353
|
+
".png",
|
|
3354
|
+
".jpg",
|
|
3355
|
+
".jpeg",
|
|
3356
|
+
".gif",
|
|
3357
|
+
".webp",
|
|
3358
|
+
".ico",
|
|
3359
|
+
".icns",
|
|
3360
|
+
".bmp",
|
|
3361
|
+
".zip",
|
|
3362
|
+
".gz",
|
|
3363
|
+
".tgz",
|
|
3364
|
+
".bz2",
|
|
3365
|
+
".xz",
|
|
3366
|
+
".7z",
|
|
3367
|
+
".rar",
|
|
3368
|
+
".woff",
|
|
3369
|
+
".woff2",
|
|
3370
|
+
".ttf",
|
|
3371
|
+
".otf",
|
|
3372
|
+
".eot",
|
|
3373
|
+
".mp3",
|
|
3374
|
+
".mp4",
|
|
3375
|
+
".mov",
|
|
3376
|
+
".avi",
|
|
3377
|
+
".pdf"
|
|
3378
|
+
]);
|
|
3379
|
+
function hasBinaryExt(rel2) {
|
|
3380
|
+
return BINARY_EXTS.has(path18.extname(rel2).toLowerCase());
|
|
3646
3381
|
}
|
|
3647
|
-
function
|
|
3648
|
-
|
|
3649
|
-
const tokens = encode(text);
|
|
3650
|
-
if (tokens.length <= budget) return { text, truncated: false, tokens: tokens.length };
|
|
3651
|
-
const markerTokens = encode(marker).length;
|
|
3652
|
-
const keep = Math.max(0, budget - markerTokens);
|
|
3653
|
-
let slice = decode(tokens.slice(0, keep));
|
|
3654
|
-
const lastNl = slice.lastIndexOf("\n");
|
|
3655
|
-
if (lastNl > 0) slice = slice.slice(0, lastNl);
|
|
3656
|
-
const out = slice + marker;
|
|
3657
|
-
return { text: out, truncated: true, tokens: countTokens(out) };
|
|
3382
|
+
function toPosix4(rel2) {
|
|
3383
|
+
return path18.sep === "\\" ? rel2.replaceAll("\\", "/") : rel2;
|
|
3658
3384
|
}
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
const
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3385
|
+
async function searchSymbols(graph, root, query, limit) {
|
|
3386
|
+
let q = query.trim();
|
|
3387
|
+
if (!q) return { matches: [], moreAvailable: false, hint: "query is required" };
|
|
3388
|
+
const quoted = /^(["'`])(.+)\1$/.exec(q);
|
|
3389
|
+
if (quoted && quoted[2].trim()) q = quoted[2].trim();
|
|
3390
|
+
const isPhrase = Boolean(quoted) || /\s/.test(q) || /[/\\]/.test(q) && !q.includes(":") && !/[*?]/.test(q);
|
|
3391
|
+
let nodes = findNodes(graph, q).filter((n) => n.kind !== "file");
|
|
3392
|
+
if (nodes.length === 0) {
|
|
3393
|
+
nodes = fileSymbolNodes(graph, q);
|
|
3394
|
+
}
|
|
3395
|
+
if (nodes.length === 0 && isPhrase) {
|
|
3396
|
+
nodes = reconstructedIdentifierNodes(graph, q);
|
|
3397
|
+
}
|
|
3398
|
+
if (nodes.length === 0) {
|
|
3399
|
+
const tokens = queryTokens(q);
|
|
3400
|
+
if (tokens.length >= 2) nodes = multiTokenNodes(graph, tokens);
|
|
3401
|
+
}
|
|
3402
|
+
const symbolCap = isPhrase ? Math.max(1, Math.floor(limit / 3)) : limit;
|
|
3403
|
+
const symbolHits = nodes.slice(0, symbolCap).map((n, i) => ({
|
|
3404
|
+
kind: n.kind,
|
|
3405
|
+
name: n.qualifiedName,
|
|
3406
|
+
file: n.file,
|
|
3407
|
+
line: n.span.start,
|
|
3408
|
+
score: Math.round((1 - i / Math.max(nodes.length, 1)) * 100) / 100
|
|
3409
|
+
}));
|
|
3410
|
+
const spare = limit - symbolHits.length;
|
|
3411
|
+
const textHits = [];
|
|
3412
|
+
let truncatedScan = false;
|
|
3413
|
+
let totalTextMatches;
|
|
3414
|
+
const skipScan = !isPhrase && hasExactSymbolMatch(nodes, q);
|
|
3415
|
+
if (spare > 0 && !skipScan) {
|
|
3416
|
+
const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
|
|
3417
|
+
const scan = await scanFiles(root, q, spare, seen, textHits, isPhrase);
|
|
3418
|
+
truncatedScan = scan.truncated;
|
|
3419
|
+
if (isPhrase) totalTextMatches = scan.total;
|
|
3693
3420
|
}
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
const flush = () => {
|
|
3702
|
-
const c = cur.join("\n").trim();
|
|
3703
|
-
if (c) chunks.push(c);
|
|
3704
|
-
cur = [];
|
|
3705
|
-
};
|
|
3706
|
-
for (const line of body.split("\n")) {
|
|
3707
|
-
if (/^\s*```/.test(line)) inFence = !inFence;
|
|
3708
|
-
if (!inFence && line.trim() === "") {
|
|
3709
|
-
flush();
|
|
3710
|
-
continue;
|
|
3711
|
-
}
|
|
3712
|
-
cur.push(line);
|
|
3421
|
+
const matches = [...symbolHits, ...textHits];
|
|
3422
|
+
if (matches.length === 0) {
|
|
3423
|
+
return {
|
|
3424
|
+
matches,
|
|
3425
|
+
moreAvailable: false,
|
|
3426
|
+
hint: "no symbol or text match \u2014 for meaning-level questions (symptoms, relationships, what-breaks-if) use query_graph"
|
|
3427
|
+
};
|
|
3713
3428
|
}
|
|
3714
|
-
|
|
3715
|
-
|
|
3429
|
+
const literalTruncated = totalTextMatches !== void 0 && totalTextMatches > textHits.length;
|
|
3430
|
+
const moreAvailable = isPhrase ? literalTruncated || truncatedScan : nodes.length > symbolHits.length || truncatedScan;
|
|
3431
|
+
const result = { matches, moreAvailable };
|
|
3432
|
+
if (totalTextMatches !== void 0) result.totalTextMatches = totalTextMatches;
|
|
3433
|
+
if (literalTruncated) {
|
|
3434
|
+
result.hint = `showing ${textHits.length} of ${totalTextMatches}${truncatedScan ? "+" : ""} literal matches \u2014 raise limit or narrow the query to get them all`;
|
|
3435
|
+
}
|
|
3436
|
+
return result;
|
|
3716
3437
|
}
|
|
3717
|
-
function
|
|
3718
|
-
|
|
3438
|
+
function fileSymbolNodes(graph, q) {
|
|
3439
|
+
if (!/[/\\]/.test(q) && !/\.\w+$/.test(q)) return [];
|
|
3440
|
+
const norm = q.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
3441
|
+
const inFile = graph.nodes.filter(
|
|
3442
|
+
(n) => n.kind !== "file" && (n.file === norm || n.file.endsWith(`/${norm}`))
|
|
3443
|
+
);
|
|
3444
|
+
return [...inFile].sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName));
|
|
3719
3445
|
}
|
|
3720
|
-
function
|
|
3721
|
-
if (
|
|
3722
|
-
|
|
3446
|
+
function hasExactSymbolMatch(nodes, q) {
|
|
3447
|
+
if (nodes.length === 0) return false;
|
|
3448
|
+
if (/^(.*):(\d+)$/.test(q)) return true;
|
|
3449
|
+
const lower = q.toLowerCase();
|
|
3450
|
+
return nodes.some((n) => n.id === q || n.qualifiedName.toLowerCase() === lower || n.name.toLowerCase() === lower);
|
|
3723
3451
|
}
|
|
3724
|
-
function
|
|
3725
|
-
|
|
3726
|
-
const out = /* @__PURE__ */ new Set();
|
|
3727
|
-
const re = /\b(?:function|class|interface|type|const|enum|namespace)\s+([A-Za-z_$][\w$]*)/g;
|
|
3728
|
-
let m;
|
|
3729
|
-
while (m = re.exec(api)) out.add(m[1]);
|
|
3730
|
-
return [...out];
|
|
3452
|
+
function queryTokens(q) {
|
|
3453
|
+
return [...new Set(q.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2))];
|
|
3731
3454
|
}
|
|
3732
|
-
function
|
|
3733
|
-
const
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
if (
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
if (codeBlocks > 0) score += w.hasCode + Math.min(codeBlocks, 5) * w.codeDensity;
|
|
3740
|
-
for (const t of queryTerms) if (text.includes(t)) score += w.queryOverlap;
|
|
3741
|
-
for (const sym of symbols) if (sym.length > 2 && text.includes(sym.toLowerCase())) score += w.symbolMatch;
|
|
3742
|
-
score += idx * w.position;
|
|
3743
|
-
const links = (s.body.match(/\]\(|https?:\/\//g) ?? []).length;
|
|
3744
|
-
const lineCount = Math.max(1, s.body.split("\n").filter((l) => l.trim()).length);
|
|
3745
|
-
if (links / lineCount > 0.5) score += w.linkDensity;
|
|
3746
|
-
return score;
|
|
3455
|
+
function reconstructedIdentifierNodes(graph, q) {
|
|
3456
|
+
const words = q.trim().split(/\s+/).filter(Boolean);
|
|
3457
|
+
if (words.length < 2) return [];
|
|
3458
|
+
const concat = findNodes(graph, words.join(""));
|
|
3459
|
+
if (concat.length) return concat.filter((n) => n.kind !== "file");
|
|
3460
|
+
const snake = findNodes(graph, words.join("_"));
|
|
3461
|
+
return snake.filter((n) => n.kind !== "file");
|
|
3747
3462
|
}
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
const budget = input.budget;
|
|
3762
|
-
const queryTerms = tokenizeQuery(input.query);
|
|
3763
|
-
const symbols = symbolsFromApi(input.apiSurface);
|
|
3764
|
-
const render = (s) => {
|
|
3765
|
-
const body = stripNoise(s.body);
|
|
3766
|
-
if (!body) return "";
|
|
3767
|
-
return `${s.heading ? `## ${s.heading}
|
|
3768
|
-
` : ""}${body}`.trim();
|
|
3769
|
-
};
|
|
3770
|
-
const sections = splitSections(readme).filter((s) => !PREAMBLE_RE.test(s.heading));
|
|
3771
|
-
const full = [api, ...sections.map(render)].filter(Boolean).join("\n\n").trim();
|
|
3772
|
-
if (countTokens(full) <= budget) {
|
|
3773
|
-
return { text: full, tokens: countTokens(full), truncated: false };
|
|
3774
|
-
}
|
|
3775
|
-
const units = [];
|
|
3776
|
-
for (const s of sections) {
|
|
3777
|
-
if (countTokens(s.body) > budget) {
|
|
3778
|
-
chunkBody(s.body).forEach((p, i) => units.push({ heading: i === 0 ? s.heading : "", level: s.level, body: p }));
|
|
3779
|
-
} else {
|
|
3780
|
-
units.push(s);
|
|
3463
|
+
function multiTokenNodes(graph, tokens) {
|
|
3464
|
+
const scored = [];
|
|
3465
|
+
for (const n of graph.nodes) {
|
|
3466
|
+
if (n.kind === "file") continue;
|
|
3467
|
+
const name = n.name.toLowerCase();
|
|
3468
|
+
const qn = n.qualifiedName.toLowerCase();
|
|
3469
|
+
let hits = 0;
|
|
3470
|
+
let exact = false;
|
|
3471
|
+
for (const t of tokens) {
|
|
3472
|
+
if (name === t || qn === t) {
|
|
3473
|
+
hits++;
|
|
3474
|
+
exact = true;
|
|
3475
|
+
} else if (qn.includes(t)) hits++;
|
|
3781
3476
|
}
|
|
3477
|
+
if (exact || hits >= 2) scored.push({ node: n, hits, exact });
|
|
3782
3478
|
}
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
const
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3479
|
+
return scored.sort((a, b) => Number(b.exact) - Number(a.exact) || b.hits - a.hits || b.node.importance - a.node.importance || a.node.qualifiedName.localeCompare(b.node.qualifiedName)).map((e) => e.node);
|
|
3480
|
+
}
|
|
3481
|
+
async function scanFiles(root, needle, budget, seen, out, countAll) {
|
|
3482
|
+
const listing = listCandidateFiles(root, needle);
|
|
3483
|
+
const outcome = await scanCandidates(root, listing.files, needle, { collectAll: countAll, budget });
|
|
3484
|
+
let overlap = 0;
|
|
3485
|
+
for (const h of outcome.hits) {
|
|
3486
|
+
if (seen.has(`${h.file}:${h.line}`)) {
|
|
3487
|
+
overlap++;
|
|
3488
|
+
continue;
|
|
3792
3489
|
}
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
if (api && !add3(api) && used < budget) {
|
|
3796
|
-
out.push(truncateToTokens(api, budget - used).text);
|
|
3797
|
-
used = budget;
|
|
3490
|
+
seen.add(`${h.file}:${h.line}`);
|
|
3491
|
+
if (out.length < budget) out.push({ kind: "text", file: h.file, line: h.line, preview: h.preview });
|
|
3798
3492
|
}
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3493
|
+
return { total: outcome.total - overlap, truncated: listing.truncated || outcome.truncated };
|
|
3494
|
+
}
|
|
3495
|
+
function listCandidateFiles(root, needle) {
|
|
3496
|
+
return ripgrepCandidates(root, needle) ?? walkCandidatesCached(root);
|
|
3497
|
+
}
|
|
3498
|
+
var LISTING_TTL_MS = 2e3;
|
|
3499
|
+
var listingCache = /* @__PURE__ */ new Map();
|
|
3500
|
+
function listingTtlMs() {
|
|
3501
|
+
const v = Number(process.env.VG_LISTING_TTL_MS);
|
|
3502
|
+
return Number.isFinite(v) && v >= 0 ? v : LISTING_TTL_MS;
|
|
3503
|
+
}
|
|
3504
|
+
function walkCandidatesCached(root) {
|
|
3505
|
+
const ttl = listingTtlMs();
|
|
3506
|
+
if (ttl > 0) {
|
|
3507
|
+
const hit = listingCache.get(root);
|
|
3508
|
+
if (hit && Date.now() - hit.at < ttl) return hit.listing;
|
|
3809
3509
|
}
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3510
|
+
const listing = walkCandidates(root);
|
|
3511
|
+
if (ttl > 0) listingCache.set(root, { listing, at: Date.now() });
|
|
3512
|
+
return listing;
|
|
3513
|
+
}
|
|
3514
|
+
function walkCandidates(root) {
|
|
3515
|
+
const files = [];
|
|
3516
|
+
let truncated = false;
|
|
3517
|
+
const stack = [root];
|
|
3518
|
+
while (stack.length) {
|
|
3519
|
+
const dir = stack.pop();
|
|
3520
|
+
let entries;
|
|
3521
|
+
try {
|
|
3522
|
+
entries = fs16.readdirSync(dir, { withFileTypes: true });
|
|
3523
|
+
} catch {
|
|
3524
|
+
continue;
|
|
3525
|
+
}
|
|
3526
|
+
for (const e of entries) {
|
|
3527
|
+
if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
|
|
3528
|
+
const abs = path18.join(dir, e.name);
|
|
3529
|
+
if (e.isDirectory()) {
|
|
3530
|
+
stack.push(abs);
|
|
3531
|
+
continue;
|
|
3532
|
+
}
|
|
3533
|
+
if (hasBinaryExt(e.name)) continue;
|
|
3534
|
+
if (files.length >= MAX_FILES_SCANNED) {
|
|
3535
|
+
truncated = true;
|
|
3536
|
+
continue;
|
|
3537
|
+
}
|
|
3538
|
+
files.push(toPosix4(path18.relative(root, abs)));
|
|
3817
3539
|
}
|
|
3818
3540
|
}
|
|
3819
|
-
|
|
3820
|
-
return {
|
|
3541
|
+
files.sort();
|
|
3542
|
+
return { files, truncated };
|
|
3821
3543
|
}
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
if (hasCode) score += 2;
|
|
3831
|
-
else reasons.push("no code example");
|
|
3832
|
-
const tokens = countTokens(content);
|
|
3833
|
-
if (tokens >= minTokens) score += 1;
|
|
3834
|
-
else reasons.push(`thin (${tokens} tokens)`);
|
|
3835
|
-
const bareName = opts.name?.toLowerCase().replace(/^@[^/]+\//, "");
|
|
3836
|
-
if (bareName && bareName.length > 1) {
|
|
3837
|
-
if (lc.includes(bareName)) score += 1;
|
|
3838
|
-
else reasons.push("library name absent");
|
|
3839
|
-
}
|
|
3840
|
-
const qt = tokenizeQuery(opts.query);
|
|
3841
|
-
let onTopic = qt.length === 0;
|
|
3842
|
-
if (qt.length) {
|
|
3843
|
-
const hits = qt.filter((t) => lc.includes(t)).length;
|
|
3844
|
-
if (hits) {
|
|
3845
|
-
score += Math.min(hits, 2);
|
|
3846
|
-
onTopic = true;
|
|
3847
|
-
} else {
|
|
3848
|
-
reasons.push("query terms absent");
|
|
3544
|
+
var rgAvailable;
|
|
3545
|
+
function hasRipgrep() {
|
|
3546
|
+
if (process.env.VG_DISABLE_RIPGREP) return false;
|
|
3547
|
+
if (rgAvailable === void 0) {
|
|
3548
|
+
try {
|
|
3549
|
+
rgAvailable = spawnSync("rg", ["--version"], { stdio: "ignore", timeout: 3e3 }).status === 0;
|
|
3550
|
+
} catch {
|
|
3551
|
+
rgAvailable = false;
|
|
3849
3552
|
}
|
|
3850
3553
|
}
|
|
3851
|
-
|
|
3852
|
-
if (opts.symbols.some((s) => s.length > 2 && lc.includes(s.toLowerCase()))) score += 1;
|
|
3853
|
-
else reasons.push("no API symbols present");
|
|
3854
|
-
}
|
|
3855
|
-
const sufficient = hasCode && tokens >= minTokens && onTopic;
|
|
3856
|
-
return { score, sufficient, reasons };
|
|
3554
|
+
return rgAvailable;
|
|
3857
3555
|
}
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
const
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
}
|
|
3869
|
-
return
|
|
3556
|
+
function ripgrepCandidates(root, needle) {
|
|
3557
|
+
if (!hasRipgrep()) return null;
|
|
3558
|
+
const globs = [...IGNORE_DIRS].flatMap((d) => ["--glob", `!**/${d}/**`, "--glob", `!${d}/**`]);
|
|
3559
|
+
const res = spawnSync(
|
|
3560
|
+
"rg",
|
|
3561
|
+
["--files-with-matches", "--fixed-strings", "--ignore-case", "--no-ignore", "--no-messages", ...globs, "--", needle, "."],
|
|
3562
|
+
{ cwd: root, encoding: "utf8", timeout: 15e3, maxBuffer: 32 * 1024 * 1024 }
|
|
3563
|
+
);
|
|
3564
|
+
if (res.error || res.status !== 0 && res.status !== 1 || res.signal) return null;
|
|
3565
|
+
const files = parseRgFileList(res.stdout);
|
|
3566
|
+
if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
|
|
3567
|
+
return { files, truncated: false };
|
|
3870
3568
|
}
|
|
3871
|
-
|
|
3872
|
-
const
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3569
|
+
function parseRgFileList(stdout, sep9 = path18.sep) {
|
|
3570
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3571
|
+
for (const raw of stdout.split("\n")) {
|
|
3572
|
+
if (!raw) continue;
|
|
3573
|
+
const posix = sep9 === "\\" ? raw.replaceAll("\\", "/") : raw;
|
|
3574
|
+
const rel2 = posix.startsWith("./") ? posix.slice(2) : posix;
|
|
3575
|
+
if (!rel2) continue;
|
|
3576
|
+
const segs = rel2.split("/");
|
|
3577
|
+
if (segs.some((s) => s.startsWith(".") || IGNORE_DIRS.has(s))) continue;
|
|
3578
|
+
if (hasBinaryExt(rel2)) continue;
|
|
3579
|
+
seen.add(rel2);
|
|
3881
3580
|
}
|
|
3581
|
+
return [...seen].sort();
|
|
3582
|
+
}
|
|
3583
|
+
function readScanArtifact(root) {
|
|
3882
3584
|
try {
|
|
3883
|
-
const
|
|
3884
|
-
|
|
3885
|
-
headers,
|
|
3886
|
-
body: JSON.stringify({
|
|
3887
|
-
name: req.name,
|
|
3888
|
-
targetId: req.targetId,
|
|
3889
|
-
query: req.query,
|
|
3890
|
-
verbosity: req.verbosity,
|
|
3891
|
-
max_tokens: req.maxTokens
|
|
3892
|
-
}),
|
|
3893
|
-
signal: ctrl.signal
|
|
3894
|
-
});
|
|
3895
|
-
if (!res.ok) return null;
|
|
3896
|
-
const data = await res.json();
|
|
3897
|
-
const content = typeof data.content === "string" ? data.content : typeof data.docs === "string" ? data.docs : "";
|
|
3898
|
-
if (!content.trim()) return null;
|
|
3899
|
-
return {
|
|
3900
|
-
content,
|
|
3901
|
-
version: typeof data.version === "string" ? data.version : null,
|
|
3902
|
-
source: "hosted",
|
|
3903
|
-
metadata: data.metadata ?? void 0
|
|
3904
|
-
};
|
|
3585
|
+
const file = path18.join(root, ".vibgrate", "scan_result.json");
|
|
3586
|
+
return JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
3905
3587
|
} catch {
|
|
3906
3588
|
return null;
|
|
3907
|
-
} finally {
|
|
3908
|
-
clearTimeout(timer);
|
|
3909
3589
|
}
|
|
3910
3590
|
}
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
const
|
|
3916
|
-
const
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
compliance: req.compliance,
|
|
3932
|
-
language: req.language,
|
|
3933
|
-
sourceUrl: req.sourceUrl
|
|
3934
|
-
}),
|
|
3935
|
-
signal: ctrl.signal
|
|
3936
|
-
});
|
|
3937
|
-
const data = await res.json().catch(() => ({}));
|
|
3938
|
-
if (!res.ok) return { ok: false, status: res.status, error: typeof data.error === "string" ? data.error : void 0, message: typeof data.message === "string" ? data.message : void 0 };
|
|
3939
|
-
return {
|
|
3940
|
-
ok: true,
|
|
3941
|
-
targetId: String(data.targetId ?? ""),
|
|
3942
|
-
ecosystem: String(data.ecosystem ?? ""),
|
|
3943
|
-
entities: Number(data.entities) || 0,
|
|
3944
|
-
indexedTokens: Number(data.indexedTokens) || 0
|
|
3945
|
-
};
|
|
3946
|
-
} catch (err) {
|
|
3947
|
-
return { ok: false, status: 0, message: err instanceof Error ? err.message : "request failed" };
|
|
3948
|
-
} finally {
|
|
3949
|
-
clearTimeout(timer);
|
|
3591
|
+
function loadVulnerabilities(root) {
|
|
3592
|
+
return readScanArtifact(root)?.extended?.vulnerabilities ?? null;
|
|
3593
|
+
}
|
|
3594
|
+
function resolvePackageTarget(root, pkg) {
|
|
3595
|
+
const artifact = readScanArtifact(root);
|
|
3596
|
+
for (const project of artifact?.projects ?? []) {
|
|
3597
|
+
const dep = project.dependencies?.find((d) => d.package === pkg);
|
|
3598
|
+
if (dep) {
|
|
3599
|
+
return {
|
|
3600
|
+
ecosystem: projectTypeToVulnEcosystem(project.type) ?? "unknown",
|
|
3601
|
+
currentVersion: dep.resolvedVersion,
|
|
3602
|
+
latestVersion: dep.latestStable,
|
|
3603
|
+
majorsBehind: dep.majorsBehind
|
|
3604
|
+
};
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
const rec = inventory(root).records.find((r) => r.name === pkg);
|
|
3608
|
+
if (rec) {
|
|
3609
|
+
const ecosystem = rec.ecosystem === "npm" ? "npm" : rec.ecosystem === "pypi" ? "pypi" : "unknown";
|
|
3610
|
+
return { ecosystem, currentVersion: rec.installed ?? null, latestVersion: rec.latest ?? null, majorsBehind: null };
|
|
3950
3611
|
}
|
|
3612
|
+
return { ecosystem: "unknown", currentVersion: null, latestVersion: null, majorsBehind: null };
|
|
3613
|
+
}
|
|
3614
|
+
function openFixableAdvisories(root, pkg) {
|
|
3615
|
+
const vulns = loadVulnerabilities(root);
|
|
3616
|
+
const match = vulns?.packages.find((p) => p.package === pkg);
|
|
3617
|
+
return (match?.advisories ?? []).filter((a) => a.fixedVersions.length).map((a) => a.id);
|
|
3618
|
+
}
|
|
3619
|
+
function filterBySeverity(result, minSeverity) {
|
|
3620
|
+
if (!minSeverity || minSeverity === "unknown") return result;
|
|
3621
|
+
const floor = severityRank(minSeverity);
|
|
3622
|
+
const packages = result.packages.map((p) => ({ ...p, advisories: p.advisories.filter((a) => severityRank(a.severity) >= floor) })).filter((p) => p.advisories.length > 0);
|
|
3623
|
+
const totalAdvisories = packages.reduce((n, p) => n + p.advisories.length, 0);
|
|
3624
|
+
return { ...result, packages, totalAdvisories };
|
|
3951
3625
|
}
|
|
3952
3626
|
|
|
3953
|
-
// src/
|
|
3954
|
-
var
|
|
3955
|
-
var
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3627
|
+
// src/mcp/attribution.ts
|
|
3628
|
+
var MCP_MAX_COMMITS = 300;
|
|
3629
|
+
var ECOSYSTEM_MAP = {
|
|
3630
|
+
npm: "npm",
|
|
3631
|
+
pypi: "pypi",
|
|
3632
|
+
go: "go",
|
|
3633
|
+
rust: "cargo",
|
|
3634
|
+
ruby: "rubygems",
|
|
3635
|
+
php: "composer",
|
|
3636
|
+
dotnet: "nuget",
|
|
3637
|
+
dart: "pub"
|
|
3638
|
+
// swift (no advisory ecosystem) and java (no resolved lockfile) have no timeline.
|
|
3639
|
+
};
|
|
3640
|
+
function brief(commit) {
|
|
3641
|
+
return { sha: commit.shortSha, author: commit.authorName, date: commit.date, subject: commit.subject };
|
|
3959
3642
|
}
|
|
3960
|
-
function
|
|
3961
|
-
const
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3643
|
+
async function attributedInventory(root, opts = {}) {
|
|
3644
|
+
const inv = inventory(root);
|
|
3645
|
+
if (!opts.attribute) return { ...inv, attribution: "off" };
|
|
3646
|
+
if (!await gitHistoryAvailable(root)) return { ...inv, attribution: "unavailable" };
|
|
3647
|
+
const timelines = await buildVersionTimelines(root, { maxCommits: MCP_MAX_COMMITS });
|
|
3648
|
+
if (!timelines) return { ...inv, attribution: "unavailable" };
|
|
3649
|
+
const records = inv.records.map((r) => {
|
|
3650
|
+
const ecosystem = ECOSYSTEM_MAP[r.ecosystem];
|
|
3651
|
+
if (!ecosystem) return r;
|
|
3652
|
+
const pt = findPackageTimeline(timelines, ecosystem, r.name);
|
|
3653
|
+
if (!pt || !pt.changes.length) return r;
|
|
3654
|
+
return {
|
|
3655
|
+
...r,
|
|
3656
|
+
addedBy: brief(pt.changes[0].commit),
|
|
3657
|
+
currentVersionBy: brief(pt.changes[pt.changes.length - 1].commit)
|
|
3658
|
+
};
|
|
3659
|
+
});
|
|
3660
|
+
return { ...inv, records, attribution: "git" };
|
|
3971
3661
|
}
|
|
3972
|
-
function
|
|
3973
|
-
|
|
3974
|
-
const parsed = JSON.parse(fs18.readFileSync(cachePath(root), "utf8"));
|
|
3975
|
-
if (parsed && parsed.version === CACHE_VERSION && parsed.entries && typeof parsed.entries === "object") return parsed;
|
|
3976
|
-
} catch {
|
|
3977
|
-
}
|
|
3978
|
-
return { version: CACHE_VERSION, entries: {} };
|
|
3662
|
+
function countTokens(text) {
|
|
3663
|
+
return encode(text).length;
|
|
3979
3664
|
}
|
|
3980
|
-
function
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
}
|
|
3665
|
+
function truncateToTokens(text, budget, marker = "\n\u2026(truncated)") {
|
|
3666
|
+
if (!Number.isFinite(budget) || budget <= 0) return { text, truncated: false, tokens: countTokens(text) };
|
|
3667
|
+
const tokens = encode(text);
|
|
3668
|
+
if (tokens.length <= budget) return { text, truncated: false, tokens: tokens.length };
|
|
3669
|
+
const markerTokens = encode(marker).length;
|
|
3670
|
+
const keep = Math.max(0, budget - markerTokens);
|
|
3671
|
+
let slice = decode(tokens.slice(0, keep));
|
|
3672
|
+
const lastNl = slice.lastIndexOf("\n");
|
|
3673
|
+
if (lastNl > 0) slice = slice.slice(0, lastNl);
|
|
3674
|
+
const out = slice + marker;
|
|
3675
|
+
return { text: out, truncated: true, tokens: countTokens(out) };
|
|
3992
3676
|
}
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
3677
|
+
|
|
3678
|
+
// src/engine/selection-weights.ts
|
|
3679
|
+
var DEFAULT_SELECTION_WEIGHTS = {
|
|
3680
|
+
headingUsage: 4,
|
|
3681
|
+
headingPreamble: -6,
|
|
3682
|
+
hasCode: 3,
|
|
3683
|
+
codeDensity: 0.5,
|
|
3684
|
+
queryOverlap: 5,
|
|
3685
|
+
symbolMatch: 2,
|
|
3686
|
+
position: -0.1,
|
|
3687
|
+
linkDensity: -3
|
|
3688
|
+
};
|
|
3689
|
+
|
|
3690
|
+
// src/engine/select.ts
|
|
3691
|
+
var STOPWORDS = /* @__PURE__ */ new Set(["the", "a", "an", "to", "of", "in", "for", "and", "or", "is", "how", "do", "i", "use", "using", "with", "on", "my"]);
|
|
3692
|
+
var USAGE_RE = /\b(usage|example|examples|quick ?start|getting started|api|guide|how to|reference|tutorial|basic)\b/i;
|
|
3693
|
+
var PREAMBLE_RE = /\b(install|installation|license|licence|contributing|contributors?|sponsors?|table of contents|toc|changelog|funding|code of conduct|security|acknowledge)\b/i;
|
|
3694
|
+
function splitSections(md) {
|
|
3695
|
+
const out = [];
|
|
3696
|
+
let cur = { heading: "", level: 0};
|
|
3697
|
+
let buf = [];
|
|
3698
|
+
const flush = () => {
|
|
3699
|
+
const body = buf.join("\n").trim();
|
|
3700
|
+
if (cur.heading || body) out.push({ heading: cur.heading, level: cur.level, body });
|
|
3701
|
+
buf = [];
|
|
3702
|
+
};
|
|
3703
|
+
for (const line of md.split("\n")) {
|
|
3704
|
+
const h = /^(#{1,6})\s+(.*\S)\s*$/.exec(line);
|
|
3705
|
+
if (h) {
|
|
3706
|
+
flush();
|
|
3707
|
+
cur = { heading: h[2].trim(), level: h[1].length, body: "" };
|
|
3708
|
+
} else {
|
|
3709
|
+
buf.push(line);
|
|
4002
3710
|
}
|
|
4003
3711
|
}
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
file.entries[key] = { at: now(), result };
|
|
4007
|
-
saveFile(root, file);
|
|
4008
|
-
}
|
|
4009
|
-
return result;
|
|
4010
|
-
}
|
|
4011
|
-
|
|
4012
|
-
// src/mcp/response.ts
|
|
4013
|
-
var MAX_RESULT_TOKENS = 25e3;
|
|
4014
|
-
function renderToolResult(result, maxTokens = MAX_RESULT_TOKENS) {
|
|
4015
|
-
return toolResult(clampToBudget(compactResult(result), maxTokens));
|
|
3712
|
+
flush();
|
|
3713
|
+
return out;
|
|
4016
3714
|
}
|
|
4017
|
-
function
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
3715
|
+
function chunkBody(body) {
|
|
3716
|
+
const chunks = [];
|
|
3717
|
+
let cur = [];
|
|
3718
|
+
let inFence = false;
|
|
3719
|
+
const flush = () => {
|
|
3720
|
+
const c = cur.join("\n").trim();
|
|
3721
|
+
if (c) chunks.push(c);
|
|
3722
|
+
cur = [];
|
|
4021
3723
|
};
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
if (Array.isArray(v)) return v.map(prune);
|
|
4028
|
-
if (v && typeof v === "object") {
|
|
4029
|
-
const out = {};
|
|
4030
|
-
for (const [k, raw] of Object.entries(v)) {
|
|
4031
|
-
const pv = prune(raw);
|
|
4032
|
-
if (!isEmpty(pv)) out[k] = pv;
|
|
3724
|
+
for (const line of body.split("\n")) {
|
|
3725
|
+
if (/^\s*```/.test(line)) inFence = !inFence;
|
|
3726
|
+
if (!inFence && line.trim() === "") {
|
|
3727
|
+
flush();
|
|
3728
|
+
continue;
|
|
4033
3729
|
}
|
|
4034
|
-
|
|
3730
|
+
cur.push(line);
|
|
4035
3731
|
}
|
|
4036
|
-
|
|
3732
|
+
flush();
|
|
3733
|
+
return chunks;
|
|
4037
3734
|
}
|
|
4038
|
-
function
|
|
4039
|
-
|
|
4040
|
-
if (Array.isArray(v)) return v.length === 0;
|
|
4041
|
-
if (typeof v === "object") return Object.keys(v).length === 0;
|
|
4042
|
-
return false;
|
|
3735
|
+
function stripNoise(body) {
|
|
3736
|
+
return body.split("\n").filter((l) => !/^\s*(?:!\[[^\]]*\]\([^)]*\)\s*)+$/.test(l)).join("\n").trim();
|
|
4043
3737
|
}
|
|
4044
|
-
function
|
|
4045
|
-
if (!
|
|
4046
|
-
|
|
4047
|
-
const obj2 = { ...value };
|
|
4048
|
-
const truncated = {};
|
|
4049
|
-
for (let guard = 0; guard < 64 && tokensOf(obj2) > maxTokens; guard++) {
|
|
4050
|
-
const arrays = Object.entries(obj2).filter(
|
|
4051
|
-
(e) => Array.isArray(e[1]) && e[1].length > 0
|
|
4052
|
-
);
|
|
4053
|
-
if (arrays.length === 0) break;
|
|
4054
|
-
arrays.sort((a, b) => tokensOf(b[1]) - tokensOf(a[1]));
|
|
4055
|
-
const [field, arr] = arrays[0];
|
|
4056
|
-
const total = truncated[field]?.total ?? arr.length;
|
|
4057
|
-
const shown = Math.max(1, Math.floor(arr.length / 2));
|
|
4058
|
-
obj2[field] = arr.slice(0, shown);
|
|
4059
|
-
truncated[field] = { shown, total };
|
|
4060
|
-
}
|
|
4061
|
-
if (Object.keys(truncated).length) obj2._truncated = truncated;
|
|
4062
|
-
return obj2;
|
|
3738
|
+
function tokenizeQuery(q) {
|
|
3739
|
+
if (!q) return [];
|
|
3740
|
+
return [...new Set(q.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1 && !STOPWORDS.has(w)))];
|
|
4063
3741
|
}
|
|
4064
|
-
function
|
|
4065
|
-
|
|
3742
|
+
function symbolsFromApi(api) {
|
|
3743
|
+
if (!api) return [];
|
|
3744
|
+
const out = /* @__PURE__ */ new Set();
|
|
3745
|
+
const re = /\b(?:function|class|interface|type|const|enum|namespace)\s+([A-Za-z_$][\w$]*)/g;
|
|
3746
|
+
let m;
|
|
3747
|
+
while (m = re.exec(api)) out.add(m[1]);
|
|
3748
|
+
return [...out];
|
|
4066
3749
|
}
|
|
4067
|
-
function
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
3750
|
+
function scoreSection(s, idx, queryTerms, symbols, w) {
|
|
3751
|
+
const text = `${s.heading}
|
|
3752
|
+
${s.body}`.toLowerCase();
|
|
3753
|
+
let score = 0;
|
|
3754
|
+
if (USAGE_RE.test(s.heading)) score += w.headingUsage;
|
|
3755
|
+
if (PREAMBLE_RE.test(s.heading)) score += w.headingPreamble;
|
|
3756
|
+
const codeBlocks = Math.floor((s.body.match(/```/g) ?? []).length / 2);
|
|
3757
|
+
if (codeBlocks > 0) score += w.hasCode + Math.min(codeBlocks, 5) * w.codeDensity;
|
|
3758
|
+
for (const t of queryTerms) if (text.includes(t)) score += w.queryOverlap;
|
|
3759
|
+
for (const sym of symbols) if (sym.length > 2 && text.includes(sym.toLowerCase())) score += w.symbolMatch;
|
|
3760
|
+
score += idx * w.position;
|
|
3761
|
+
const links = (s.body.match(/\]\(|https?:\/\//g) ?? []).length;
|
|
3762
|
+
const lineCount = Math.max(1, s.body.split("\n").filter((l) => l.trim()).length);
|
|
3763
|
+
if (links / lineCount > 0.5) score += w.linkDensity;
|
|
3764
|
+
return score;
|
|
4072
3765
|
}
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
3766
|
+
var apiBlock = (api) => api ? `## API (types)
|
|
3767
|
+
|
|
3768
|
+
\`\`\`ts
|
|
3769
|
+
${api}
|
|
3770
|
+
\`\`\`` : "";
|
|
3771
|
+
function selectForBudget(input) {
|
|
3772
|
+
const w = input.weights ?? DEFAULT_SELECTION_WEIGHTS;
|
|
3773
|
+
const api = apiBlock(input.apiSurface);
|
|
3774
|
+
const readme = input.readme.trim();
|
|
3775
|
+
if (!input.budget || !Number.isFinite(input.budget)) {
|
|
3776
|
+
const full2 = [readme, api].filter(Boolean).join("\n\n");
|
|
3777
|
+
return { text: full2, tokens: countTokens(full2), truncated: false };
|
|
4076
3778
|
}
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
const
|
|
4088
|
-
if (
|
|
4089
|
-
|
|
4090
|
-
return opts.collectAll ? scanInline(root, files, needleLower, ROW_CAP, false, void 0) : scanInline(root, files, needleLower, opts.budget, true, void 0);
|
|
3779
|
+
const budget = input.budget;
|
|
3780
|
+
const queryTerms = tokenizeQuery(input.query);
|
|
3781
|
+
const symbols = symbolsFromApi(input.apiSurface);
|
|
3782
|
+
const render = (s) => {
|
|
3783
|
+
const body = stripNoise(s.body);
|
|
3784
|
+
if (!body) return "";
|
|
3785
|
+
return `${s.heading ? `## ${s.heading}
|
|
3786
|
+
` : ""}${body}`.trim();
|
|
3787
|
+
};
|
|
3788
|
+
const sections = splitSections(readme).filter((s) => !PREAMBLE_RE.test(s.heading));
|
|
3789
|
+
const full = [api, ...sections.map(render)].filter(Boolean).join("\n\n").trim();
|
|
3790
|
+
if (countTokens(full) <= budget) {
|
|
3791
|
+
return { text: full, tokens: countTokens(full), truncated: false };
|
|
4091
3792
|
}
|
|
4092
|
-
const
|
|
4093
|
-
|
|
4094
|
-
|
|
3793
|
+
const units = [];
|
|
3794
|
+
for (const s of sections) {
|
|
3795
|
+
if (countTokens(s.body) > budget) {
|
|
3796
|
+
chunkBody(s.body).forEach((p, i) => units.push({ heading: i === 0 ? s.heading : "", level: s.level, body: p }));
|
|
3797
|
+
} else {
|
|
3798
|
+
units.push(s);
|
|
3799
|
+
}
|
|
4095
3800
|
}
|
|
4096
|
-
const
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
3801
|
+
const ranked = units.map((s, idx) => ({ s, idx, score: scoreSection(s, idx, queryTerms, symbols, w) })).sort((a, b) => b.score - a.score || a.idx - b.idx);
|
|
3802
|
+
const out = [];
|
|
3803
|
+
let used = 0;
|
|
3804
|
+
const add3 = (chunk2) => {
|
|
3805
|
+
const t = countTokens(chunk2);
|
|
3806
|
+
if (used + t <= budget) {
|
|
3807
|
+
out.push(chunk2);
|
|
3808
|
+
used += t;
|
|
3809
|
+
return true;
|
|
3810
|
+
}
|
|
3811
|
+
return false;
|
|
3812
|
+
};
|
|
3813
|
+
if (api && !add3(api) && used < budget) {
|
|
3814
|
+
out.push(truncateToTokens(api, budget - used).text);
|
|
3815
|
+
used = budget;
|
|
4100
3816
|
}
|
|
4101
|
-
|
|
4102
|
-
}
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
}
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
let total = 0;
|
|
4111
|
-
for (const rel2 of files) {
|
|
4112
|
-
let size;
|
|
4113
|
-
try {
|
|
4114
|
-
size = fs18.statSync(path2.join(root, rel2)).size;
|
|
4115
|
-
} catch {
|
|
4116
|
-
continue;
|
|
3817
|
+
const picked = [];
|
|
3818
|
+
for (const { s, idx } of ranked) {
|
|
3819
|
+
const chunk2 = render(s);
|
|
3820
|
+
if (!chunk2) continue;
|
|
3821
|
+
if (used + countTokens(`
|
|
3822
|
+
${chunk2}`) <= budget) {
|
|
3823
|
+
picked.push(idx);
|
|
3824
|
+
used += countTokens(`
|
|
3825
|
+
${chunk2}`);
|
|
4117
3826
|
}
|
|
4118
|
-
sizes.set(rel2, size);
|
|
4119
|
-
if (size <= MAX_FILE_BYTES) total += size;
|
|
4120
|
-
if (total >= threshold) return { over: true, sizes };
|
|
4121
3827
|
}
|
|
4122
|
-
|
|
3828
|
+
picked.sort((a, b) => a - b);
|
|
3829
|
+
for (const idx of picked) out.push(render(units[idx]));
|
|
3830
|
+
if (used < budget - 20) {
|
|
3831
|
+
const next = ranked.find((r) => !picked.includes(r.idx) && render(r.s));
|
|
3832
|
+
if (next) {
|
|
3833
|
+
const head = truncateToTokens(render(next.s), budget - used).text;
|
|
3834
|
+
if (head.trim()) out.push(head);
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
const text = out.join("\n\n").trim();
|
|
3838
|
+
return { text, tokens: countTokens(text), truncated: true };
|
|
4123
3839
|
}
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
const
|
|
4128
|
-
const
|
|
4129
|
-
const
|
|
4130
|
-
|
|
4131
|
-
|
|
3840
|
+
|
|
3841
|
+
// src/engine/quality.ts
|
|
3842
|
+
function assessDocQuality(content, opts = {}) {
|
|
3843
|
+
const minTokens = opts.minTokens ?? 60;
|
|
3844
|
+
const lc = content.toLowerCase();
|
|
3845
|
+
const reasons = [];
|
|
3846
|
+
let score = 0;
|
|
3847
|
+
const hasCode = /```/.test(content) || /(^|\n)( {4}|\t)\S/.test(content);
|
|
3848
|
+
if (hasCode) score += 2;
|
|
3849
|
+
else reasons.push("no code example");
|
|
3850
|
+
const tokens = countTokens(content);
|
|
3851
|
+
if (tokens >= minTokens) score += 1;
|
|
3852
|
+
else reasons.push(`thin (${tokens} tokens)`);
|
|
3853
|
+
const bareName = opts.name?.toLowerCase().replace(/^@[^/]+\//, "");
|
|
3854
|
+
if (bareName && bareName.length > 1) {
|
|
3855
|
+
if (lc.includes(bareName)) score += 1;
|
|
3856
|
+
else reasons.push("library name absent");
|
|
3857
|
+
}
|
|
3858
|
+
const qt = tokenizeQuery(opts.query);
|
|
3859
|
+
let onTopic = qt.length === 0;
|
|
3860
|
+
if (qt.length) {
|
|
3861
|
+
const hits = qt.filter((t) => lc.includes(t)).length;
|
|
3862
|
+
if (hits) {
|
|
3863
|
+
score += Math.min(hits, 2);
|
|
3864
|
+
onTopic = true;
|
|
3865
|
+
} else {
|
|
3866
|
+
reasons.push("query terms absent");
|
|
3867
|
+
}
|
|
4132
3868
|
}
|
|
4133
|
-
|
|
3869
|
+
if (opts.symbols?.length) {
|
|
3870
|
+
if (opts.symbols.some((s) => s.length > 2 && lc.includes(s.toLowerCase()))) score += 1;
|
|
3871
|
+
else reasons.push("no API symbols present");
|
|
3872
|
+
}
|
|
3873
|
+
const sufficient = hasCode && tokens >= minTokens && onTopic;
|
|
3874
|
+
return { score, sufficient, reasons };
|
|
4134
3875
|
}
|
|
4135
|
-
|
|
3876
|
+
|
|
3877
|
+
// src/engine/hosted.ts
|
|
3878
|
+
function hostedBase(opts = {}) {
|
|
3879
|
+
const explicit = opts.base || process.env.VIBGRATE_LIB_HOST;
|
|
3880
|
+
if (explicit) return explicit.replace(/\/+$/, "");
|
|
3881
|
+
let host;
|
|
4136
3882
|
try {
|
|
4137
|
-
|
|
4138
|
-
if (size > MAX_FILE_BYTES) return null;
|
|
4139
|
-
const text = fs18.readFileSync(abs, "utf8");
|
|
4140
|
-
return text.includes(NUL) ? null : text;
|
|
3883
|
+
host = resolveIngestHost(opts.region, opts.ingest);
|
|
4141
3884
|
} catch {
|
|
4142
|
-
|
|
3885
|
+
host = "us.ingest.vibgrate.com";
|
|
4143
3886
|
}
|
|
3887
|
+
return `https://${host}`;
|
|
4144
3888
|
}
|
|
4145
|
-
function
|
|
4146
|
-
const
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
else {
|
|
4156
|
-
truncated = true;
|
|
4157
|
-
if (stopAtCap) return { hits, total, truncated };
|
|
4158
|
-
}
|
|
4159
|
-
}
|
|
3889
|
+
async function fetchHostedDocs(req, opts = {}) {
|
|
3890
|
+
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
3891
|
+
if (!f) return null;
|
|
3892
|
+
const url = `${hostedBase(opts)}/v1/lib/docs`;
|
|
3893
|
+
const ctrl = new AbortController();
|
|
3894
|
+
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 4e3);
|
|
3895
|
+
const headers = { "content-type": "application/json" };
|
|
3896
|
+
if (opts.auth) {
|
|
3897
|
+
headers["Authorization"] = `VibgrateDSN ${opts.auth.keyId}:${opts.auth.secret}`;
|
|
3898
|
+
headers["X-Vibgrate-Timestamp"] = String(Date.now());
|
|
4160
3899
|
}
|
|
4161
|
-
return { hits, total, truncated };
|
|
4162
|
-
}
|
|
4163
|
-
async function scanParallel(root, files, needleLower) {
|
|
4164
|
-
const n = Math.min(files.length, Math.max(2, Math.min(8, os.cpus().length - 1)));
|
|
4165
|
-
const shards = Array.from({ length: n }, () => []);
|
|
4166
|
-
files.forEach((f, i) => shards[i % n].push(f));
|
|
4167
|
-
let results;
|
|
4168
3900
|
try {
|
|
4169
|
-
|
|
3901
|
+
const res = await f(url, {
|
|
3902
|
+
method: "POST",
|
|
3903
|
+
headers,
|
|
3904
|
+
body: JSON.stringify({
|
|
3905
|
+
name: req.name,
|
|
3906
|
+
targetId: req.targetId,
|
|
3907
|
+
query: req.query,
|
|
3908
|
+
verbosity: req.verbosity,
|
|
3909
|
+
max_tokens: req.maxTokens
|
|
3910
|
+
}),
|
|
3911
|
+
signal: ctrl.signal
|
|
3912
|
+
});
|
|
3913
|
+
if (!res.ok) return null;
|
|
3914
|
+
const data = await res.json();
|
|
3915
|
+
const content = typeof data.content === "string" ? data.content : typeof data.docs === "string" ? data.docs : "";
|
|
3916
|
+
if (!content.trim()) return null;
|
|
3917
|
+
return {
|
|
3918
|
+
content,
|
|
3919
|
+
version: typeof data.version === "string" ? data.version : null,
|
|
3920
|
+
source: "hosted",
|
|
3921
|
+
metadata: data.metadata ?? void 0
|
|
3922
|
+
};
|
|
4170
3923
|
} catch {
|
|
4171
|
-
return
|
|
4172
|
-
}
|
|
4173
|
-
|
|
4174
|
-
let total = 0;
|
|
4175
|
-
let truncated = false;
|
|
4176
|
-
for (const r of results) {
|
|
4177
|
-
total += r.total;
|
|
4178
|
-
truncated = truncated || r.truncated;
|
|
4179
|
-
for (const h of r.hits) hits.push(h);
|
|
4180
|
-
}
|
|
4181
|
-
hits.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line);
|
|
4182
|
-
if (hits.length > ROW_CAP) {
|
|
4183
|
-
hits.length = ROW_CAP;
|
|
4184
|
-
truncated = true;
|
|
3924
|
+
return null;
|
|
3925
|
+
} finally {
|
|
3926
|
+
clearTimeout(timer);
|
|
4185
3927
|
}
|
|
4186
|
-
return { hits, total, truncated };
|
|
4187
|
-
}
|
|
4188
|
-
var WORKER_SOURCE = [
|
|
4189
|
-
"const { parentPort, workerData } = require('worker_threads');",
|
|
4190
|
-
"const fs = require('fs');",
|
|
4191
|
-
"const path = require('path');",
|
|
4192
|
-
`const MAX = ${MAX_FILE_BYTES};`,
|
|
4193
|
-
`const PREVIEW = ${PREVIEW_CHARS};`,
|
|
4194
|
-
`const CAP = ${ROW_CAP};`,
|
|
4195
|
-
"const NUL = String.fromCharCode(0);",
|
|
4196
|
-
"const NL = String.fromCharCode(10);",
|
|
4197
|
-
"const { root, files, needleLower } = workerData;",
|
|
4198
|
-
"const hits = []; let total = 0, truncated = false;",
|
|
4199
|
-
"for (const rel of files) {",
|
|
4200
|
-
" const abs = path.join(root, rel);",
|
|
4201
|
-
" let text;",
|
|
4202
|
-
' try { if (fs.statSync(abs).size > MAX) continue; text = fs.readFileSync(abs, "utf8"); } catch { continue; }',
|
|
4203
|
-
" if (text.includes(NUL)) continue;",
|
|
4204
|
-
" const low = text.toLowerCase();",
|
|
4205
|
-
" if (!low.includes(needleLower)) continue;",
|
|
4206
|
-
" const lowLines = low.split(NL); const rawLines = text.split(NL);",
|
|
4207
|
-
" for (let i = 0; i < lowLines.length; i++) {",
|
|
4208
|
-
" if (!lowLines[i].includes(needleLower)) continue;",
|
|
4209
|
-
" total++;",
|
|
4210
|
-
" if (hits.length < CAP) hits.push({ file: rel, line: i + 1, preview: rawLines[i].trim().slice(0, PREVIEW) });",
|
|
4211
|
-
" else truncated = true;",
|
|
4212
|
-
" }",
|
|
4213
|
-
"}",
|
|
4214
|
-
"parentPort.postMessage({ hits, total, truncated });"
|
|
4215
|
-
].join("\n");
|
|
4216
|
-
function runWorker(root, files, needleLower) {
|
|
4217
|
-
return new Promise((resolve11, reject) => {
|
|
4218
|
-
const w = new Worker(WORKER_SOURCE, { eval: true, workerData: { root, files, needleLower } });
|
|
4219
|
-
let settled = false;
|
|
4220
|
-
w.once("message", (m) => {
|
|
4221
|
-
settled = true;
|
|
4222
|
-
resolve11(m);
|
|
4223
|
-
});
|
|
4224
|
-
w.once("error", reject);
|
|
4225
|
-
w.once("exit", (code) => {
|
|
4226
|
-
if (!settled && code !== 0) reject(new Error(`literal-scan worker exited with code ${code}`));
|
|
4227
|
-
});
|
|
4228
|
-
});
|
|
4229
|
-
}
|
|
4230
|
-
|
|
4231
|
-
// src/engine/search.ts
|
|
4232
|
-
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
4233
|
-
".git",
|
|
4234
|
-
".vibgrate",
|
|
4235
|
-
"node_modules",
|
|
4236
|
-
"dist",
|
|
4237
|
-
"build",
|
|
4238
|
-
"out",
|
|
4239
|
-
"target",
|
|
4240
|
-
"vendor",
|
|
4241
|
-
"__pycache__",
|
|
4242
|
-
// .NET intermediate output and coverage reports: purely generated trees that a
|
|
4243
|
-
// C#/coverage-heavy repo pays for on every literal sweep. `bin` is deliberately
|
|
4244
|
-
// NOT here — plenty of repos keep real scripts in bin/ (rails, node CLIs), and
|
|
4245
|
-
// completeness beats speed; the binary-extension skip below keeps a .NET bin/
|
|
4246
|
-
// cheap anyway.
|
|
4247
|
-
"obj",
|
|
4248
|
-
"coverage"
|
|
4249
|
-
]);
|
|
4250
|
-
var MAX_FILES_SCANNED = 2e4;
|
|
4251
|
-
var BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
4252
|
-
".dll",
|
|
4253
|
-
".pdb",
|
|
4254
|
-
".exe",
|
|
4255
|
-
".so",
|
|
4256
|
-
".dylib",
|
|
4257
|
-
".a",
|
|
4258
|
-
".lib",
|
|
4259
|
-
".o",
|
|
4260
|
-
".class",
|
|
4261
|
-
".jar",
|
|
4262
|
-
".wasm",
|
|
4263
|
-
".node",
|
|
4264
|
-
".png",
|
|
4265
|
-
".jpg",
|
|
4266
|
-
".jpeg",
|
|
4267
|
-
".gif",
|
|
4268
|
-
".webp",
|
|
4269
|
-
".ico",
|
|
4270
|
-
".icns",
|
|
4271
|
-
".bmp",
|
|
4272
|
-
".zip",
|
|
4273
|
-
".gz",
|
|
4274
|
-
".tgz",
|
|
4275
|
-
".bz2",
|
|
4276
|
-
".xz",
|
|
4277
|
-
".7z",
|
|
4278
|
-
".rar",
|
|
4279
|
-
".woff",
|
|
4280
|
-
".woff2",
|
|
4281
|
-
".ttf",
|
|
4282
|
-
".otf",
|
|
4283
|
-
".eot",
|
|
4284
|
-
".mp3",
|
|
4285
|
-
".mp4",
|
|
4286
|
-
".mov",
|
|
4287
|
-
".avi",
|
|
4288
|
-
".pdf"
|
|
4289
|
-
]);
|
|
4290
|
-
function hasBinaryExt(rel2) {
|
|
4291
|
-
return BINARY_EXTS.has(path2.extname(rel2).toLowerCase());
|
|
4292
3928
|
}
|
|
4293
|
-
function
|
|
4294
|
-
|
|
4295
|
-
}
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
const
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
const spare = limit - symbolHits.length;
|
|
4322
|
-
const textHits = [];
|
|
4323
|
-
let truncatedScan = false;
|
|
4324
|
-
let totalTextMatches;
|
|
4325
|
-
const skipScan = !isPhrase && hasExactSymbolMatch(nodes, q);
|
|
4326
|
-
if (spare > 0 && !skipScan) {
|
|
4327
|
-
const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
|
|
4328
|
-
const scan = await scanFiles(root, q, spare, seen, textHits, isPhrase);
|
|
4329
|
-
truncatedScan = scan.truncated;
|
|
4330
|
-
if (isPhrase) totalTextMatches = scan.total;
|
|
4331
|
-
}
|
|
4332
|
-
const matches = [...symbolHits, ...textHits];
|
|
4333
|
-
if (matches.length === 0) {
|
|
3929
|
+
async function publishPrivateLibrary(req, opts = {}) {
|
|
3930
|
+
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
3931
|
+
if (!f) return { ok: false, status: 0, message: "no network available" };
|
|
3932
|
+
if (!opts.auth) return { ok: false, status: 401, message: "a DSN is required to publish a private library (run `vibgrate login` or set VIBGRATE_DSN)" };
|
|
3933
|
+
const url = `${hostedBase(opts)}/v1/lib/private`;
|
|
3934
|
+
const ctrl = new AbortController();
|
|
3935
|
+
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
|
|
3936
|
+
try {
|
|
3937
|
+
const res = await f(url, {
|
|
3938
|
+
method: "POST",
|
|
3939
|
+
headers: {
|
|
3940
|
+
"content-type": "application/json",
|
|
3941
|
+
Authorization: `VibgrateDSN ${opts.auth.keyId}:${opts.auth.secret}`,
|
|
3942
|
+
"X-Vibgrate-Timestamp": String(Date.now())
|
|
3943
|
+
},
|
|
3944
|
+
body: JSON.stringify({
|
|
3945
|
+
name: req.name,
|
|
3946
|
+
version: req.version,
|
|
3947
|
+
readme: req.readme,
|
|
3948
|
+
dts: req.dts,
|
|
3949
|
+
compliance: req.compliance,
|
|
3950
|
+
language: req.language,
|
|
3951
|
+
sourceUrl: req.sourceUrl
|
|
3952
|
+
}),
|
|
3953
|
+
signal: ctrl.signal
|
|
3954
|
+
});
|
|
3955
|
+
const data = await res.json().catch(() => ({}));
|
|
3956
|
+
if (!res.ok) return { ok: false, status: res.status, error: typeof data.error === "string" ? data.error : void 0, message: typeof data.message === "string" ? data.message : void 0 };
|
|
4334
3957
|
return {
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
3958
|
+
ok: true,
|
|
3959
|
+
targetId: String(data.targetId ?? ""),
|
|
3960
|
+
ecosystem: String(data.ecosystem ?? ""),
|
|
3961
|
+
entities: Number(data.entities) || 0,
|
|
3962
|
+
indexedTokens: Number(data.indexedTokens) || 0
|
|
4338
3963
|
};
|
|
3964
|
+
} catch (err) {
|
|
3965
|
+
return { ok: false, status: 0, message: err instanceof Error ? err.message : "request failed" };
|
|
3966
|
+
} finally {
|
|
3967
|
+
clearTimeout(timer);
|
|
4339
3968
|
}
|
|
4340
|
-
const literalTruncated = totalTextMatches !== void 0 && totalTextMatches > textHits.length;
|
|
4341
|
-
const moreAvailable = isPhrase ? literalTruncated || truncatedScan : nodes.length > symbolHits.length || truncatedScan;
|
|
4342
|
-
const result = { matches, moreAvailable };
|
|
4343
|
-
if (totalTextMatches !== void 0) result.totalTextMatches = totalTextMatches;
|
|
4344
|
-
if (literalTruncated) {
|
|
4345
|
-
result.hint = `showing ${textHits.length} of ${totalTextMatches}${truncatedScan ? "+" : ""} literal matches \u2014 raise limit or narrow the query to get them all`;
|
|
4346
|
-
}
|
|
4347
|
-
return result;
|
|
4348
|
-
}
|
|
4349
|
-
function fileSymbolNodes(graph, q) {
|
|
4350
|
-
if (!/[/\\]/.test(q) && !/\.\w+$/.test(q)) return [];
|
|
4351
|
-
const norm = q.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
4352
|
-
const inFile = graph.nodes.filter(
|
|
4353
|
-
(n) => n.kind !== "file" && (n.file === norm || n.file.endsWith(`/${norm}`))
|
|
4354
|
-
);
|
|
4355
|
-
return [...inFile].sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName));
|
|
4356
3969
|
}
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
3970
|
+
|
|
3971
|
+
// src/engine/hosted-cache.ts
|
|
3972
|
+
var CACHE_VERSION = "vg-hosted-docs/1";
|
|
3973
|
+
var HOSTED_DOCS_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3974
|
+
var MAX_ENTRIES = 64;
|
|
3975
|
+
function cachePath(root) {
|
|
3976
|
+
return path18.join(cacheDir(root), "hosted-docs.json");
|
|
4362
3977
|
}
|
|
4363
|
-
function
|
|
4364
|
-
|
|
3978
|
+
function hostedDocsCacheKey(req, opts = {}) {
|
|
3979
|
+
const material = JSON.stringify([
|
|
3980
|
+
req.name ?? "",
|
|
3981
|
+
req.targetId ?? "",
|
|
3982
|
+
req.query ?? "",
|
|
3983
|
+
req.verbosity ?? "",
|
|
3984
|
+
req.maxTokens ?? 0,
|
|
3985
|
+
hostedBase(opts),
|
|
3986
|
+
opts.auth?.keyId ?? "anon"
|
|
3987
|
+
]);
|
|
3988
|
+
return createHash("sha256").update(material).digest("hex");
|
|
4365
3989
|
}
|
|
4366
|
-
function
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
return
|
|
3990
|
+
function loadFile(root) {
|
|
3991
|
+
try {
|
|
3992
|
+
const parsed = JSON.parse(fs16.readFileSync(cachePath(root), "utf8"));
|
|
3993
|
+
if (parsed && parsed.version === CACHE_VERSION && parsed.entries && typeof parsed.entries === "object") return parsed;
|
|
3994
|
+
} catch {
|
|
3995
|
+
}
|
|
3996
|
+
return { version: CACHE_VERSION, entries: {} };
|
|
4373
3997
|
}
|
|
4374
|
-
function
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
if (
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
let exact = false;
|
|
4382
|
-
for (const t of tokens) {
|
|
4383
|
-
if (name === t || qn === t) {
|
|
4384
|
-
hits++;
|
|
4385
|
-
exact = true;
|
|
4386
|
-
} else if (qn.includes(t)) hits++;
|
|
3998
|
+
function saveFile(root, file) {
|
|
3999
|
+
try {
|
|
4000
|
+
const keys = Object.keys(file.entries);
|
|
4001
|
+
if (keys.length > MAX_ENTRIES) {
|
|
4002
|
+
for (const k of keys.sort((a, b) => file.entries[a].at - file.entries[b].at).slice(0, keys.length - MAX_ENTRIES)) {
|
|
4003
|
+
delete file.entries[k];
|
|
4004
|
+
}
|
|
4387
4005
|
}
|
|
4388
|
-
|
|
4006
|
+
fs16.mkdirSync(cacheDir(root), { recursive: true });
|
|
4007
|
+
fs16.writeFileSync(cachePath(root), JSON.stringify(file));
|
|
4008
|
+
} catch {
|
|
4389
4009
|
}
|
|
4390
|
-
return scored.sort((a, b) => Number(b.exact) - Number(a.exact) || b.hits - a.hits || b.node.importance - a.node.importance || a.node.qualifiedName.localeCompare(b.node.qualifiedName)).map((e) => e.node);
|
|
4391
4010
|
}
|
|
4392
|
-
async function
|
|
4393
|
-
const
|
|
4394
|
-
const
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4011
|
+
async function fetchHostedDocsCached(root, req, opts = {}) {
|
|
4012
|
+
const now = opts.now ?? Date.now;
|
|
4013
|
+
const ttl = opts.ttlMs ?? HOSTED_DOCS_TTL_MS;
|
|
4014
|
+
const key = hostedDocsCacheKey(req, opts);
|
|
4015
|
+
const file = loadFile(root);
|
|
4016
|
+
if (!opts.bypass) {
|
|
4017
|
+
const hit = file.entries[key];
|
|
4018
|
+
if (hit && now() - hit.at < ttl && hit.result?.content) {
|
|
4019
|
+
return { ...hit.result, metadata: { ...hit.result.metadata ?? {}, cached: true } };
|
|
4400
4020
|
}
|
|
4401
|
-
seen.add(`${h.file}:${h.line}`);
|
|
4402
|
-
if (out.length < budget) out.push({ kind: "text", file: h.file, line: h.line, preview: h.preview });
|
|
4403
4021
|
}
|
|
4404
|
-
|
|
4022
|
+
const result = await fetchHostedDocs(req, opts);
|
|
4023
|
+
if (result) {
|
|
4024
|
+
file.entries[key] = { at: now(), result };
|
|
4025
|
+
saveFile(root, file);
|
|
4026
|
+
}
|
|
4027
|
+
return result;
|
|
4405
4028
|
}
|
|
4406
|
-
|
|
4407
|
-
|
|
4029
|
+
|
|
4030
|
+
// src/mcp/response.ts
|
|
4031
|
+
var MAX_RESULT_TOKENS = 25e3;
|
|
4032
|
+
function renderToolResult(result, maxTokens = MAX_RESULT_TOKENS) {
|
|
4033
|
+
return toolResult(clampToBudget(compactResult(result), maxTokens));
|
|
4408
4034
|
}
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4035
|
+
function toolResult(result) {
|
|
4036
|
+
return {
|
|
4037
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
4038
|
+
structuredContent: asStructured(result)
|
|
4039
|
+
};
|
|
4414
4040
|
}
|
|
4415
|
-
function
|
|
4416
|
-
|
|
4417
|
-
if (ttl > 0) {
|
|
4418
|
-
const hit = listingCache.get(root);
|
|
4419
|
-
if (hit && Date.now() - hit.at < ttl) return hit.listing;
|
|
4420
|
-
}
|
|
4421
|
-
const listing = walkCandidates(root);
|
|
4422
|
-
if (ttl > 0) listingCache.set(root, { listing, at: Date.now() });
|
|
4423
|
-
return listing;
|
|
4041
|
+
function compactResult(value) {
|
|
4042
|
+
return prune(value);
|
|
4424
4043
|
}
|
|
4425
|
-
function
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
try {
|
|
4433
|
-
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
4434
|
-
} catch {
|
|
4435
|
-
continue;
|
|
4436
|
-
}
|
|
4437
|
-
for (const e of entries) {
|
|
4438
|
-
if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
|
|
4439
|
-
const abs = path2.join(dir, e.name);
|
|
4440
|
-
if (e.isDirectory()) {
|
|
4441
|
-
stack.push(abs);
|
|
4442
|
-
continue;
|
|
4443
|
-
}
|
|
4444
|
-
if (hasBinaryExt(e.name)) continue;
|
|
4445
|
-
if (files.length >= MAX_FILES_SCANNED) {
|
|
4446
|
-
truncated = true;
|
|
4447
|
-
continue;
|
|
4448
|
-
}
|
|
4449
|
-
files.push(toPosix2(path2.relative(root, abs)));
|
|
4044
|
+
function prune(v) {
|
|
4045
|
+
if (Array.isArray(v)) return v.map(prune);
|
|
4046
|
+
if (v && typeof v === "object") {
|
|
4047
|
+
const out = {};
|
|
4048
|
+
for (const [k, raw] of Object.entries(v)) {
|
|
4049
|
+
const pv = prune(raw);
|
|
4050
|
+
if (!isEmpty(pv)) out[k] = pv;
|
|
4450
4051
|
}
|
|
4052
|
+
return out;
|
|
4451
4053
|
}
|
|
4452
|
-
|
|
4453
|
-
return { files, truncated };
|
|
4054
|
+
return v;
|
|
4454
4055
|
}
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
if (
|
|
4458
|
-
if (
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4056
|
+
function isEmpty(v) {
|
|
4057
|
+
if (v === null || v === void 0 || v === "") return true;
|
|
4058
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
4059
|
+
if (typeof v === "object") return Object.keys(v).length === 0;
|
|
4060
|
+
return false;
|
|
4061
|
+
}
|
|
4062
|
+
function clampToBudget(value, maxTokens = MAX_RESULT_TOKENS) {
|
|
4063
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
4064
|
+
if (tokensOf(value) <= maxTokens) return value;
|
|
4065
|
+
const obj2 = { ...value };
|
|
4066
|
+
const truncated = {};
|
|
4067
|
+
for (let guard = 0; guard < 64 && tokensOf(obj2) > maxTokens; guard++) {
|
|
4068
|
+
const arrays = Object.entries(obj2).filter(
|
|
4069
|
+
(e) => Array.isArray(e[1]) && e[1].length > 0
|
|
4070
|
+
);
|
|
4071
|
+
if (arrays.length === 0) break;
|
|
4072
|
+
arrays.sort((a, b) => tokensOf(b[1]) - tokensOf(a[1]));
|
|
4073
|
+
const [field, arr] = arrays[0];
|
|
4074
|
+
const total = truncated[field]?.total ?? arr.length;
|
|
4075
|
+
const shown = Math.max(1, Math.floor(arr.length / 2));
|
|
4076
|
+
obj2[field] = arr.slice(0, shown);
|
|
4077
|
+
truncated[field] = { shown, total };
|
|
4464
4078
|
}
|
|
4465
|
-
|
|
4079
|
+
if (Object.keys(truncated).length) obj2._truncated = truncated;
|
|
4080
|
+
return obj2;
|
|
4466
4081
|
}
|
|
4467
|
-
function
|
|
4468
|
-
|
|
4469
|
-
const globs = [...IGNORE_DIRS].flatMap((d) => ["--glob", `!**/${d}/**`, "--glob", `!${d}/**`]);
|
|
4470
|
-
const res = spawnSync(
|
|
4471
|
-
"rg",
|
|
4472
|
-
["--files-with-matches", "--fixed-strings", "--ignore-case", "--no-ignore", "--no-messages", ...globs, "--", needle, "."],
|
|
4473
|
-
{ cwd: root, encoding: "utf8", timeout: 15e3, maxBuffer: 32 * 1024 * 1024 }
|
|
4474
|
-
);
|
|
4475
|
-
if (res.error || res.status !== 0 && res.status !== 1 || res.signal) return null;
|
|
4476
|
-
const files = parseRgFileList(res.stdout);
|
|
4477
|
-
if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
|
|
4478
|
-
return { files, truncated: false };
|
|
4082
|
+
function tokensOf(v) {
|
|
4083
|
+
return countTokens(JSON.stringify(v));
|
|
4479
4084
|
}
|
|
4480
|
-
function
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
if (!raw) continue;
|
|
4484
|
-
const posix3 = sep9 === "\\" ? raw.replaceAll("\\", "/") : raw;
|
|
4485
|
-
const rel2 = posix3.startsWith("./") ? posix3.slice(2) : posix3;
|
|
4486
|
-
if (!rel2) continue;
|
|
4487
|
-
const segs = rel2.split("/");
|
|
4488
|
-
if (segs.some((s) => s.startsWith(".") || IGNORE_DIRS.has(s))) continue;
|
|
4489
|
-
if (hasBinaryExt(rel2)) continue;
|
|
4490
|
-
seen.add(rel2);
|
|
4085
|
+
function asStructured(result) {
|
|
4086
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
4087
|
+
return result;
|
|
4491
4088
|
}
|
|
4492
|
-
return
|
|
4089
|
+
return { result };
|
|
4090
|
+
}
|
|
4091
|
+
function boundList(items, max) {
|
|
4092
|
+
if (!Number.isFinite(max) || max < 0 || items.length <= max) {
|
|
4093
|
+
return { items, total: items.length };
|
|
4094
|
+
}
|
|
4095
|
+
return { items: items.slice(0, max), total: items.length };
|
|
4493
4096
|
}
|
|
4097
|
+
var NODE_EDGE_CAP = 50;
|
|
4494
4098
|
|
|
4495
4099
|
// src/mcp/tools.ts
|
|
4496
4100
|
var warmEmbedder = null;
|
|
@@ -5375,12 +4979,12 @@ function detectAssistants(root, opts = {}) {
|
|
|
5375
4979
|
const which = opts.which ?? whichOnPath;
|
|
5376
4980
|
const found = [];
|
|
5377
4981
|
for (const assistant of ASSISTANTS) {
|
|
5378
|
-
const repoHit = (assistant.markers ?? []).find((m) =>
|
|
4982
|
+
const repoHit = (assistant.markers ?? []).find((m) => fs16.existsSync(path18.join(root, m)));
|
|
5379
4983
|
if (repoHit) {
|
|
5380
4984
|
found.push({ assistant, via: "repo", marker: repoHit });
|
|
5381
4985
|
continue;
|
|
5382
4986
|
}
|
|
5383
|
-
const homeHit = (assistant.homeMarkers ?? []).find((m) =>
|
|
4987
|
+
const homeHit = (assistant.homeMarkers ?? []).find((m) => fs16.existsSync(path18.join(home, m)));
|
|
5384
4988
|
if (homeHit) {
|
|
5385
4989
|
found.push({ assistant, via: "home", marker: homeHit });
|
|
5386
4990
|
continue;
|
|
@@ -5412,19 +5016,19 @@ function installAssistant(a, opts) {
|
|
|
5412
5016
|
const skipped = [];
|
|
5413
5017
|
let note;
|
|
5414
5018
|
if (a.skill) {
|
|
5415
|
-
writeFileEnsured(
|
|
5019
|
+
writeFileEnsured(path18.join(opts.root, a.skill), skillMarkdown(a.id));
|
|
5416
5020
|
wrote.push(a.skill);
|
|
5417
5021
|
}
|
|
5418
5022
|
if (a.mcp) {
|
|
5419
5023
|
const launch = opts.launch ?? detectServeLaunch();
|
|
5420
|
-
upsertMcp(
|
|
5024
|
+
upsertMcp(path18.join(opts.root, a.mcp.file), a.mcp, launch);
|
|
5421
5025
|
wrote.push(a.mcp.file);
|
|
5422
5026
|
note = launch.note;
|
|
5423
5027
|
} else {
|
|
5424
5028
|
skipped.push("mcp (host-specific setup)");
|
|
5425
5029
|
}
|
|
5426
5030
|
if (a.nudge && opts.hook !== false) {
|
|
5427
|
-
writeNudge(
|
|
5031
|
+
writeNudge(path18.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
|
|
5428
5032
|
wrote.push(a.nudge.file);
|
|
5429
5033
|
} else if (a.nudge) {
|
|
5430
5034
|
skipped.push("nudge (--no-hook)");
|
|
@@ -5434,41 +5038,41 @@ function installAssistant(a, opts) {
|
|
|
5434
5038
|
function uninstallAssistant(a, root, purge) {
|
|
5435
5039
|
const removed = [];
|
|
5436
5040
|
if (a.mcp) {
|
|
5437
|
-
const file =
|
|
5041
|
+
const file = path18.join(root, a.mcp.file);
|
|
5438
5042
|
if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
|
|
5439
5043
|
}
|
|
5440
5044
|
if (a.nudge) {
|
|
5441
|
-
const file =
|
|
5045
|
+
const file = path18.join(root, a.nudge.file);
|
|
5442
5046
|
if (a.nudge.kind === "block") {
|
|
5443
5047
|
if (removeBlock(file)) removed.push(a.nudge.file);
|
|
5444
|
-
} else if (
|
|
5445
|
-
|
|
5048
|
+
} else if (fs16.existsSync(file)) {
|
|
5049
|
+
fs16.rmSync(file);
|
|
5446
5050
|
removed.push(a.nudge.file);
|
|
5447
5051
|
}
|
|
5448
5052
|
}
|
|
5449
5053
|
if (purge && a.skill) {
|
|
5450
|
-
const file =
|
|
5451
|
-
if (
|
|
5452
|
-
|
|
5054
|
+
const file = path18.join(root, a.skill);
|
|
5055
|
+
if (fs16.existsSync(file)) {
|
|
5056
|
+
fs16.rmSync(file);
|
|
5453
5057
|
removed.push(a.skill);
|
|
5454
5058
|
}
|
|
5455
5059
|
}
|
|
5456
5060
|
return removed;
|
|
5457
5061
|
}
|
|
5458
5062
|
function writeFileEnsured(file, content) {
|
|
5459
|
-
|
|
5460
|
-
|
|
5063
|
+
fs16.mkdirSync(path18.dirname(file), { recursive: true });
|
|
5064
|
+
fs16.writeFileSync(file, content);
|
|
5461
5065
|
}
|
|
5462
5066
|
function writeNavigationConfig(root) {
|
|
5463
5067
|
ensureVibgrateGitignore(root);
|
|
5464
|
-
const rel2 =
|
|
5068
|
+
const rel2 = path18.join(".vibgrate", "mcp-navigation.json");
|
|
5465
5069
|
const doc = {
|
|
5466
5070
|
_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.",
|
|
5467
5071
|
hot_core: [...HOT_TOOLS],
|
|
5468
5072
|
deferred: deferredToolNames(),
|
|
5469
5073
|
toolset: navigationToolsetConfig()
|
|
5470
5074
|
};
|
|
5471
|
-
writeFileEnsured(
|
|
5075
|
+
writeFileEnsured(path18.join(root, rel2), `${JSON.stringify(doc, null, 2)}
|
|
5472
5076
|
`);
|
|
5473
5077
|
return rel2;
|
|
5474
5078
|
}
|
|
@@ -5483,7 +5087,7 @@ function upsertMcp(file, target, launch) {
|
|
|
5483
5087
|
`);
|
|
5484
5088
|
}
|
|
5485
5089
|
function removeMcp(file, target) {
|
|
5486
|
-
if (!
|
|
5090
|
+
if (!fs16.existsSync(file)) return false;
|
|
5487
5091
|
const config = readJson(file);
|
|
5488
5092
|
const bag = config[target.key];
|
|
5489
5093
|
if (!bag || !(bag.vg !== void 0)) return false;
|
|
@@ -5502,8 +5106,8 @@ function refreshInstalledInstructions(root, smallRepo) {
|
|
|
5502
5106
|
};
|
|
5503
5107
|
for (const a of ASSISTANTS) {
|
|
5504
5108
|
if (a.skill) {
|
|
5505
|
-
const file =
|
|
5506
|
-
if (
|
|
5109
|
+
const file = path18.join(root, a.skill);
|
|
5110
|
+
if (fs16.existsSync(file)) {
|
|
5507
5111
|
const text = readTextSafe(file);
|
|
5508
5112
|
const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
|
|
5509
5113
|
if (from !== null) {
|
|
@@ -5513,8 +5117,8 @@ function refreshInstalledInstructions(root, smallRepo) {
|
|
|
5513
5117
|
}
|
|
5514
5118
|
}
|
|
5515
5119
|
if (a.nudge) {
|
|
5516
|
-
const file =
|
|
5517
|
-
if (!
|
|
5120
|
+
const file = path18.join(root, a.nudge.file);
|
|
5121
|
+
if (!fs16.existsSync(file)) continue;
|
|
5518
5122
|
const text = readTextSafe(file);
|
|
5519
5123
|
if (text === null) continue;
|
|
5520
5124
|
if (a.nudge.kind === "block") {
|
|
@@ -5538,7 +5142,7 @@ function refreshInstalledInstructions(root, smallRepo) {
|
|
|
5538
5142
|
}
|
|
5539
5143
|
function readTextSafe(file) {
|
|
5540
5144
|
try {
|
|
5541
|
-
return
|
|
5145
|
+
return fs16.readFileSync(file, "utf8");
|
|
5542
5146
|
} catch {
|
|
5543
5147
|
return null;
|
|
5544
5148
|
}
|
|
@@ -5557,8 +5161,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
|
|
|
5557
5161
|
upsertBlock(file, nudgeMarkdown(smallRepo, client));
|
|
5558
5162
|
}
|
|
5559
5163
|
function upsertBlock(file, block) {
|
|
5560
|
-
|
|
5561
|
-
let existing =
|
|
5164
|
+
fs16.mkdirSync(path18.dirname(file), { recursive: true });
|
|
5165
|
+
let existing = fs16.existsSync(file) ? fs16.readFileSync(file, "utf8") : "";
|
|
5562
5166
|
const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
|
|
5563
5167
|
if (re.test(existing)) existing = existing.replace(re, block);
|
|
5564
5168
|
else existing = existing.length ? `${existing.replace(/\s*$/, "")}
|
|
@@ -5566,20 +5170,20 @@ function upsertBlock(file, block) {
|
|
|
5566
5170
|
${block}
|
|
5567
5171
|
` : `${block}
|
|
5568
5172
|
`;
|
|
5569
|
-
|
|
5173
|
+
fs16.writeFileSync(file, existing);
|
|
5570
5174
|
}
|
|
5571
5175
|
function removeBlock(file) {
|
|
5572
|
-
if (!
|
|
5573
|
-
const existing =
|
|
5176
|
+
if (!fs16.existsSync(file)) return false;
|
|
5177
|
+
const existing = fs16.readFileSync(file, "utf8");
|
|
5574
5178
|
const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
|
|
5575
5179
|
if (!re.test(existing)) return false;
|
|
5576
|
-
|
|
5180
|
+
fs16.writeFileSync(file, existing.replace(re, "\n"));
|
|
5577
5181
|
return true;
|
|
5578
5182
|
}
|
|
5579
5183
|
function readJson(file) {
|
|
5580
|
-
if (!
|
|
5184
|
+
if (!fs16.existsSync(file)) return {};
|
|
5581
5185
|
try {
|
|
5582
|
-
const parsed = JSON.parse(
|
|
5186
|
+
const parsed = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
5583
5187
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
5584
5188
|
return parsed;
|
|
5585
5189
|
}
|
|
@@ -5599,7 +5203,7 @@ function escapeRe(s) {
|
|
|
5599
5203
|
}
|
|
5600
5204
|
var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
5601
5205
|
function refreshLockPath(root) {
|
|
5602
|
-
return
|
|
5206
|
+
return path18.join(cacheDir(root), "refresh.lock");
|
|
5603
5207
|
}
|
|
5604
5208
|
async function refreshIfStale(root, opts = {}) {
|
|
5605
5209
|
const probe = probeFreshness(root);
|
|
@@ -5630,8 +5234,9 @@ async function refreshIfStale(root, opts = {}) {
|
|
|
5630
5234
|
const dir = vibgrateDir(root);
|
|
5631
5235
|
writeArtifacts(result.graph, {
|
|
5632
5236
|
root,
|
|
5633
|
-
|
|
5634
|
-
|
|
5237
|
+
graphPath: opts.graphPath,
|
|
5238
|
+
report: fs16.existsSync(path18.join(dir, "GRAPH_REPORT.md")),
|
|
5239
|
+
html: fs16.existsSync(path18.join(dir, "graph.html"))
|
|
5635
5240
|
});
|
|
5636
5241
|
}
|
|
5637
5242
|
writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
|
|
@@ -5651,6 +5256,6 @@ async function refreshIfStale(root, opts = {}) {
|
|
|
5651
5256
|
}
|
|
5652
5257
|
}
|
|
5653
5258
|
|
|
5654
|
-
export { ASSISTANTS, CliError, ExitCode, FREE_PACK, NPX_INVOCATION, ResourceLimitError,
|
|
5655
|
-
//# sourceMappingURL=chunk-
|
|
5656
|
-
//# sourceMappingURL=chunk-
|
|
5259
|
+
export { ASSISTANTS, CliError, ExitCode, FREE_PACK, NPX_INVOCATION, ResourceLimitError, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, TOOLS, UsageError, analyze, applyCoverage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, clearStoredCredentials, countTokens, coveringTests, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, detectAssistants, detectRunner, detectServeLaunch, discover, driftCount, dsnCommand, ensureGitignored, epistemicBreakdown, fetchHostedDocsCached, findGitRoot, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, ingestHostForRegionId, installAssistant, loadCoverage, loadSnapshot, parseDsn, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, readScanArtifact, readStoredCredentials, refreshIfStale, refreshInstalledInstructions, renderToolResult, resolveCliInvocation, resolveDsn, resolveIngestHost, resolveLimits, scipEdges, searchSymbols, selectForBudget, shortestPath, symbolsFromApi, testsToRun, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, warmEmbedderInBackground, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
|
|
5260
|
+
//# sourceMappingURL=chunk-KNELIQJO.js.map
|
|
5261
|
+
//# sourceMappingURL=chunk-KNELIQJO.js.map
|