@vibgrate/cli 2026.721.3 → 2026.722.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 +185 -0
- package/README.md +2 -1
- package/dist/baseline-BD3A7EXD.js +7 -0
- package/dist/{baseline-37G4TINM.js.map → baseline-BD3A7EXD.js.map} +1 -1
- package/dist/chunk-2O5YZVZV.js +417 -0
- package/dist/chunk-2O5YZVZV.js.map +1 -0
- package/dist/{chunk-ELUFCQDR.js → chunk-3MXNNBLI.js} +240 -2519
- package/dist/chunk-3MXNNBLI.js.map +1 -0
- package/dist/{chunk-JLRF5TFA.js → chunk-5BDHQ7DD.js} +42 -71
- package/dist/chunk-5BDHQ7DD.js.map +1 -0
- package/dist/chunk-6CXTPC74.js +234 -0
- package/dist/chunk-6CXTPC74.js.map +1 -0
- package/dist/chunk-CRAMOEBE.js +675 -0
- package/dist/chunk-CRAMOEBE.js.map +1 -0
- package/dist/chunk-CS37OBE3.js +861 -0
- package/dist/chunk-CS37OBE3.js.map +1 -0
- package/dist/chunk-CT5BS56U.js +23 -0
- package/dist/chunk-CT5BS56U.js.map +1 -0
- package/dist/chunk-GGJZA3Q6.js +961 -0
- package/dist/chunk-GGJZA3Q6.js.map +1 -0
- package/dist/chunk-I7GY7MVN.js +833 -0
- package/dist/chunk-I7GY7MVN.js.map +1 -0
- package/dist/chunk-JBXNQCGE.js +484 -0
- package/dist/chunk-JBXNQCGE.js.map +1 -0
- package/dist/chunk-JFGYT6BI.js +32 -0
- package/dist/chunk-JFGYT6BI.js.map +1 -0
- package/dist/chunk-JYS4OBN2.js +148 -0
- package/dist/chunk-JYS4OBN2.js.map +1 -0
- package/dist/chunk-K3SLGULW.js +50 -0
- package/dist/chunk-K3SLGULW.js.map +1 -0
- package/dist/chunk-LEPTUB4H.js +108 -0
- package/dist/chunk-LEPTUB4H.js.map +1 -0
- package/dist/{chunk-5PLKADD6.js → chunk-NONYJLOJ.js} +4 -3
- package/dist/chunk-NONYJLOJ.js.map +1 -0
- package/dist/{chunk-IKJBELUV.js → chunk-PY3DNX5H.js} +4 -29
- package/dist/chunk-PY3DNX5H.js.map +1 -0
- package/dist/chunk-WNIIKCNF.js +972 -0
- package/dist/chunk-WNIIKCNF.js.map +1 -0
- package/dist/cli.js +2512 -1224
- package/dist/cli.js.map +1 -1
- package/dist/fs-KCABDURV.js +3 -0
- package/dist/fs-KCABDURV.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/interactive-A4DISSIW.js +681 -0
- package/dist/interactive-A4DISSIW.js.map +1 -0
- package/dist/mcp-tools-I6PME3AV.js +3 -0
- package/dist/mcp-tools-I6PME3AV.js.map +1 -0
- package/dist/parse-worker.js +2 -1
- package/dist/parse-worker.js.map +1 -1
- package/dist/session-POJUDFPH.js +6 -0
- package/dist/session-POJUDFPH.js.map +1 -0
- package/dist/session-store-W5RZZHA3.js +3 -0
- package/dist/session-store-W5RZZHA3.js.map +1 -0
- package/dist/stream-json-Q2JGWPHB.js +65 -0
- package/dist/stream-json-Q2JGWPHB.js.map +1 -0
- package/dist/ui-HOVOPWMX.js +4 -0
- package/dist/ui-HOVOPWMX.js.map +1 -0
- package/package.json +1 -1
- package/dist/baseline-37G4TINM.js +0 -6
- package/dist/chunk-5PLKADD6.js.map +0 -1
- package/dist/chunk-ELUFCQDR.js.map +0 -1
- package/dist/chunk-IKJBELUV.js.map +0 -1
- package/dist/chunk-JLRF5TFA.js.map +0 -1
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import { resolveOne, resolveVersion, resolveLib, loadCatalog, driftFor, localPackageDocs, readDoc, findNodes, inventory, localApiSurface } from './chunk-GGJZA3Q6.js';
|
|
2
|
+
import { loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-5BDHQ7DD.js';
|
|
3
|
+
import { langById, grammarSetVersion, langForExtension, setGrammarsOverride, parseSource } from './chunk-PY3DNX5H.js';
|
|
4
|
+
import { writeTextFile, pathExists, readJsonFile } from './chunk-CRAMOEBE.js';
|
|
5
|
+
import { impactOf, indexFor, withTimeout, getNodeEmbeddings, queryGraphSemantic, queryGraph, loadEmbedder, isModelReady, cacheDir, loadCache, stableStringify, serializeGraph, parseGraph, acquireLock, releaseLock } from './chunk-WNIIKCNF.js';
|
|
6
|
+
import { shortId, canonicalize, hashString, hashBytes } from './chunk-JFGYT6BI.js';
|
|
7
|
+
import * as fs18 from 'fs';
|
|
4
8
|
import { spawnSync, execFileSync } from 'child_process';
|
|
5
|
-
import * as
|
|
9
|
+
import * as path2 from 'path';
|
|
6
10
|
import ignore from 'ignore';
|
|
7
11
|
import * as v8 from 'v8';
|
|
8
12
|
import Graph from 'graphology';
|
|
@@ -11,9 +15,8 @@ import betweenness from 'graphology-metrics/centrality/betweenness.js';
|
|
|
11
15
|
import eigenvector from 'graphology-metrics/centrality/eigenvector.js';
|
|
12
16
|
import louvain from 'graphology-communities-louvain';
|
|
13
17
|
import * as os from 'os';
|
|
14
|
-
import {
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
15
19
|
import ts from 'typescript';
|
|
16
|
-
import { createRequire } from 'module';
|
|
17
20
|
import { bidirectional } from 'graphology-shortest-path/unweighted.js';
|
|
18
21
|
import * as crypto from 'crypto';
|
|
19
22
|
import { createHash } from 'crypto';
|
|
@@ -23,9 +26,6 @@ import * as zlib from 'zlib';
|
|
|
23
26
|
import { promisify } from 'util';
|
|
24
27
|
import { encode, decode } from 'gpt-tokenizer/encoding/cl100k_base';
|
|
25
28
|
import { Worker } from 'worker_threads';
|
|
26
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
27
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
28
|
-
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
29
29
|
|
|
30
30
|
var NPX_INVOCATION = "npx @vibgrate/cli";
|
|
31
31
|
function whichOnPath(cmd) {
|
|
@@ -41,9 +41,9 @@ function whichOnPath(cmd) {
|
|
|
41
41
|
}
|
|
42
42
|
function isOwnBinary(binPath) {
|
|
43
43
|
try {
|
|
44
|
-
const real =
|
|
44
|
+
const real = fs18.realpathSync(binPath);
|
|
45
45
|
if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
|
|
46
|
-
const head =
|
|
46
|
+
const head = fs18.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
|
|
47
47
|
return head.includes("@vibgrate/cli") || head.includes("vibgrate");
|
|
48
48
|
} catch {
|
|
49
49
|
return false;
|
|
@@ -53,7 +53,7 @@ function isEphemeralNpxBinary(binPath) {
|
|
|
53
53
|
const NPX_SEGMENT = /[\\/]_npx[\\/]/;
|
|
54
54
|
if (NPX_SEGMENT.test(binPath)) return true;
|
|
55
55
|
try {
|
|
56
|
-
return NPX_SEGMENT.test(
|
|
56
|
+
return NPX_SEGMENT.test(fs18.realpathSync(binPath));
|
|
57
57
|
} catch {
|
|
58
58
|
return false;
|
|
59
59
|
}
|
|
@@ -219,19 +219,19 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
|
219
219
|
"flake.lock"
|
|
220
220
|
]);
|
|
221
221
|
function toPosix(p) {
|
|
222
|
-
return p.split(
|
|
222
|
+
return p.split(path2.sep).join("/");
|
|
223
223
|
}
|
|
224
224
|
function buildRootIgnore(root, exclude) {
|
|
225
225
|
const ig = ignore();
|
|
226
|
-
const gitignorePath =
|
|
227
|
-
if (
|
|
228
|
-
ig.add(
|
|
226
|
+
const gitignorePath = path2.join(root, ".gitignore");
|
|
227
|
+
if (fs18.existsSync(gitignorePath)) {
|
|
228
|
+
ig.add(fs18.readFileSync(gitignorePath, "utf8"));
|
|
229
229
|
}
|
|
230
230
|
if (exclude.length) ig.add(exclude);
|
|
231
231
|
return ig;
|
|
232
232
|
}
|
|
233
233
|
function discover(options) {
|
|
234
|
-
const root =
|
|
234
|
+
const root = path2.resolve(options.root);
|
|
235
235
|
const onlyLangs = (options.only ?? []).filter(Boolean);
|
|
236
236
|
const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
|
|
237
237
|
for (const id of onlyLangs) {
|
|
@@ -240,27 +240,27 @@ function discover(options) {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
const rootIg = buildRootIgnore(root, options.exclude ?? []);
|
|
243
|
-
const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) =>
|
|
243
|
+
const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path2.resolve(root, p)) : [root]).filter((p) => fs18.existsSync(p));
|
|
244
244
|
const found = /* @__PURE__ */ new Map();
|
|
245
245
|
const considerFile = (abs) => {
|
|
246
|
-
const rel2 = toPosix(
|
|
246
|
+
const rel2 = toPosix(path2.relative(root, abs));
|
|
247
247
|
if (rel2.startsWith("..")) return;
|
|
248
248
|
if (rel2 === "" || rootIg.ignores(rel2)) return;
|
|
249
|
-
if (SKIP_FILES.has(
|
|
250
|
-
const lang = langForExtension(
|
|
249
|
+
if (SKIP_FILES.has(path2.basename(abs).toLowerCase())) return;
|
|
250
|
+
const lang = langForExtension(path2.extname(abs));
|
|
251
251
|
if (!lang || !allowLang(lang)) return;
|
|
252
252
|
found.set(rel2, { rel: rel2, abs, lang });
|
|
253
253
|
};
|
|
254
254
|
const walk2 = (dir) => {
|
|
255
255
|
let entries;
|
|
256
256
|
try {
|
|
257
|
-
entries =
|
|
257
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
258
258
|
} catch {
|
|
259
259
|
return;
|
|
260
260
|
}
|
|
261
261
|
for (const entry of entries) {
|
|
262
|
-
const abs =
|
|
263
|
-
const rel2 = toPosix(
|
|
262
|
+
const abs = path2.join(dir, entry.name);
|
|
263
|
+
const rel2 = toPosix(path2.relative(root, abs));
|
|
264
264
|
if (entry.isDirectory()) {
|
|
265
265
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
266
266
|
if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
|
|
@@ -271,7 +271,7 @@ function discover(options) {
|
|
|
271
271
|
}
|
|
272
272
|
};
|
|
273
273
|
for (const scope of scopeAbs) {
|
|
274
|
-
const stat =
|
|
274
|
+
const stat = fs18.statSync(scope);
|
|
275
275
|
if (stat.isDirectory()) walk2(scope);
|
|
276
276
|
else if (stat.isFile()) considerFile(scope);
|
|
277
277
|
}
|
|
@@ -455,10 +455,10 @@ function buildModuleResolver(root, relSet) {
|
|
|
455
455
|
resolve(fromRel, source) {
|
|
456
456
|
if (source.includes("\\")) source = source.split("\\").join(".");
|
|
457
457
|
if (source.startsWith("./") || source.startsWith("../")) {
|
|
458
|
-
return probe(posixJoin(
|
|
458
|
+
return probe(posixJoin(path2.posix.dirname(fromRel), source));
|
|
459
459
|
}
|
|
460
460
|
if (/\.[A-Za-z0-9]+$/.test(source) && !source.startsWith("<")) {
|
|
461
|
-
const hit = probe(posixJoin(
|
|
461
|
+
const hit = probe(posixJoin(path2.posix.dirname(fromRel), source));
|
|
462
462
|
if (hit) return hit;
|
|
463
463
|
}
|
|
464
464
|
if (source.startsWith(".")) {
|
|
@@ -471,11 +471,11 @@ function buildModuleResolver(root, relSet) {
|
|
|
471
471
|
function buildSuffixIndex(relSet) {
|
|
472
472
|
const byLast = /* @__PURE__ */ new Map();
|
|
473
473
|
for (const rel2 of relSet) {
|
|
474
|
-
const
|
|
475
|
-
if (
|
|
476
|
-
const ext = rel2.slice(
|
|
474
|
+
const dot = rel2.lastIndexOf(".");
|
|
475
|
+
if (dot < 0) continue;
|
|
476
|
+
const ext = rel2.slice(dot);
|
|
477
477
|
if (!EXT_GROUPS[ext]) continue;
|
|
478
|
-
let key = rel2.slice(0,
|
|
478
|
+
let key = rel2.slice(0, dot);
|
|
479
479
|
const base = key.slice(key.lastIndexOf("/") + 1);
|
|
480
480
|
if (base === "__init__" || base === "index") {
|
|
481
481
|
key = key.slice(0, Math.max(0, key.length - base.length - 1));
|
|
@@ -494,12 +494,12 @@ function resolveSuffix(index, fromRel, source) {
|
|
|
494
494
|
const fromExt = fromDot >= 0 ? fromRel.slice(fromDot) : "";
|
|
495
495
|
const allowed = EXT_GROUPS[fromExt];
|
|
496
496
|
if (!allowed) return null;
|
|
497
|
-
const
|
|
498
|
-
const last =
|
|
497
|
+
const q = source.replace(/\./g, "/");
|
|
498
|
+
const last = q.slice(q.lastIndexOf("/") + 1);
|
|
499
499
|
const cands = index.get(last);
|
|
500
500
|
if (!cands) return null;
|
|
501
501
|
const matches = cands.filter(
|
|
502
|
-
(c) => allowed.includes(c.ext) && (c.key ===
|
|
502
|
+
(c) => allowed.includes(c.ext) && (c.key === q || c.key.endsWith("/" + q))
|
|
503
503
|
);
|
|
504
504
|
if (matches.length === 0) return null;
|
|
505
505
|
if (matches.length === 1) return matches[0].rel;
|
|
@@ -529,8 +529,8 @@ function resolvePyRelative(probe, fromRel, source) {
|
|
|
529
529
|
let dots = 0;
|
|
530
530
|
while (source[dots] === ".") dots++;
|
|
531
531
|
const rest = source.slice(dots);
|
|
532
|
-
let dir =
|
|
533
|
-
for (let i = 1; i < dots; i++) dir =
|
|
532
|
+
let dir = path2.posix.dirname(fromRel);
|
|
533
|
+
for (let i = 1; i < dots; i++) dir = path2.posix.dirname(dir);
|
|
534
534
|
const sub = rest.replace(/\./g, "/");
|
|
535
535
|
return probe(sub ? posixJoin(dir, sub) : dir);
|
|
536
536
|
}
|
|
@@ -538,7 +538,7 @@ function relativeResolver(relSet) {
|
|
|
538
538
|
const probe = makeProbe(relSet);
|
|
539
539
|
return {
|
|
540
540
|
resolve(fromRel, source) {
|
|
541
|
-
if (source.startsWith(".")) return probe(posixJoin(
|
|
541
|
+
if (source.startsWith(".")) return probe(posixJoin(path2.posix.dirname(fromRel), source));
|
|
542
542
|
return resolveDotted(probe, source);
|
|
543
543
|
}
|
|
544
544
|
};
|
|
@@ -556,12 +556,12 @@ function makeProbe(relSet) {
|
|
|
556
556
|
};
|
|
557
557
|
}
|
|
558
558
|
function normalizeRel(p) {
|
|
559
|
-
const norm =
|
|
559
|
+
const norm = path2.posix.normalize(p);
|
|
560
560
|
const stripped = norm.startsWith("./") ? norm.slice(2) : norm;
|
|
561
561
|
return stripped.startsWith("..") ? "" : stripped;
|
|
562
562
|
}
|
|
563
563
|
function posixJoin(a, b) {
|
|
564
|
-
return
|
|
564
|
+
return path2.posix.normalize(path2.posix.join(a, b));
|
|
565
565
|
}
|
|
566
566
|
function matchPattern(pattern, source) {
|
|
567
567
|
const star = pattern.indexOf("*");
|
|
@@ -574,8 +574,8 @@ function matchPattern(pattern, source) {
|
|
|
574
574
|
}
|
|
575
575
|
function loadTsconfigPaths(root) {
|
|
576
576
|
for (const name of ["tsconfig.base.json", "tsconfig.json"]) {
|
|
577
|
-
const file =
|
|
578
|
-
if (
|
|
577
|
+
const file = path2.join(root, name);
|
|
578
|
+
if (fs18.existsSync(file)) {
|
|
579
579
|
const merged = readTsconfigChain(root, file, /* @__PURE__ */ new Set());
|
|
580
580
|
if (merged && merged.paths.length) return merged;
|
|
581
581
|
}
|
|
@@ -587,21 +587,21 @@ function readTsconfigChain(root, file, seen) {
|
|
|
587
587
|
seen.add(file);
|
|
588
588
|
let cfg;
|
|
589
589
|
try {
|
|
590
|
-
cfg = parseJsonc(
|
|
590
|
+
cfg = parseJsonc(fs18.readFileSync(file, "utf8"));
|
|
591
591
|
} catch {
|
|
592
592
|
return null;
|
|
593
593
|
}
|
|
594
|
-
const dir =
|
|
594
|
+
const dir = path2.dirname(file);
|
|
595
595
|
let base = { baseUrlRel: "", paths: [] };
|
|
596
596
|
if (cfg.extends && cfg.extends.startsWith(".")) {
|
|
597
|
-
const extPath =
|
|
598
|
-
if (
|
|
597
|
+
const extPath = path2.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
|
|
598
|
+
if (fs18.existsSync(extPath)) {
|
|
599
599
|
const inherited = readTsconfigChain(root, extPath, seen);
|
|
600
600
|
if (inherited) base = inherited;
|
|
601
601
|
}
|
|
602
602
|
}
|
|
603
603
|
const co = cfg.compilerOptions ?? {};
|
|
604
|
-
const baseUrlRel = co.baseUrl !== void 0 ?
|
|
604
|
+
const baseUrlRel = co.baseUrl !== void 0 ? path2.relative(root, path2.resolve(dir, co.baseUrl)).split(path2.sep).join("/") : base.baseUrlRel;
|
|
605
605
|
const paths = [...base.paths];
|
|
606
606
|
if (co.paths) for (const [k, v] of Object.entries(co.paths)) paths.push([k, v]);
|
|
607
607
|
return { baseUrlRel, paths };
|
|
@@ -609,27 +609,27 @@ function readTsconfigChain(root, file, seen) {
|
|
|
609
609
|
function loadWorkspacePackages(root) {
|
|
610
610
|
const map = /* @__PURE__ */ new Map();
|
|
611
611
|
const globs = [];
|
|
612
|
-
const rootPkg =
|
|
613
|
-
if (
|
|
612
|
+
const rootPkg = path2.join(root, "package.json");
|
|
613
|
+
if (fs18.existsSync(rootPkg)) {
|
|
614
614
|
try {
|
|
615
|
-
const pkg = JSON.parse(
|
|
615
|
+
const pkg = JSON.parse(fs18.readFileSync(rootPkg, "utf8"));
|
|
616
616
|
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
617
617
|
if (ws) globs.push(...ws);
|
|
618
618
|
} catch {
|
|
619
619
|
}
|
|
620
620
|
}
|
|
621
|
-
const pnpmWs =
|
|
622
|
-
if (
|
|
623
|
-
for (const line of
|
|
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
624
|
const m = /^\s*-\s*['"]?([^'"\n]+)['"]?\s*$/.exec(line);
|
|
625
625
|
if (m) globs.push(m[1].trim());
|
|
626
626
|
}
|
|
627
627
|
}
|
|
628
628
|
for (const dir of expandWorkspaceGlobs(root, globs)) {
|
|
629
|
-
const pj =
|
|
629
|
+
const pj = path2.join(dir, "package.json");
|
|
630
630
|
try {
|
|
631
|
-
const name = JSON.parse(
|
|
632
|
-
if (name) map.set(name,
|
|
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
633
|
} catch {
|
|
634
634
|
}
|
|
635
635
|
}
|
|
@@ -639,10 +639,10 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
639
639
|
const out = /* @__PURE__ */ new Set();
|
|
640
640
|
for (const glob of globs) {
|
|
641
641
|
const clean = glob.replace(/\/\*\*$/, "").replace(/\/\*$/, "");
|
|
642
|
-
const baseDir =
|
|
642
|
+
const baseDir = path2.join(root, clean);
|
|
643
643
|
const recurse = glob.endsWith("**");
|
|
644
644
|
if (glob === clean) {
|
|
645
|
-
if (
|
|
645
|
+
if (fs18.existsSync(path2.join(baseDir, "package.json"))) out.add(baseDir);
|
|
646
646
|
continue;
|
|
647
647
|
}
|
|
648
648
|
collectPackageDirs(baseDir, recurse ? 4 : 1, out);
|
|
@@ -653,14 +653,14 @@ function collectPackageDirs(dir, depth, out) {
|
|
|
653
653
|
if (depth < 0) return;
|
|
654
654
|
let entries;
|
|
655
655
|
try {
|
|
656
|
-
entries =
|
|
656
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
657
657
|
} catch {
|
|
658
658
|
return;
|
|
659
659
|
}
|
|
660
660
|
for (const e of entries) {
|
|
661
661
|
if (!e.isDirectory() || e.name === "node_modules" || e.name.startsWith(".")) continue;
|
|
662
|
-
const child =
|
|
663
|
-
if (
|
|
662
|
+
const child = path2.join(dir, e.name);
|
|
663
|
+
if (fs18.existsSync(path2.join(child, "package.json"))) out.add(child);
|
|
664
664
|
else collectPackageDirs(child, depth - 1, out);
|
|
665
665
|
}
|
|
666
666
|
}
|
|
@@ -1159,14 +1159,14 @@ var DEFAULT_PATHS = [
|
|
|
1159
1159
|
];
|
|
1160
1160
|
function loadCoverage(root, explicit) {
|
|
1161
1161
|
const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
|
|
1162
|
-
(p) =>
|
|
1162
|
+
(p) => path2.resolve(root, p)
|
|
1163
1163
|
);
|
|
1164
|
-
const found = candidates.filter((p) =>
|
|
1164
|
+
const found = candidates.filter((p) => fs18.existsSync(p));
|
|
1165
1165
|
if (found.length === 0) return null;
|
|
1166
1166
|
const map = /* @__PURE__ */ new Map();
|
|
1167
1167
|
for (const file of found) {
|
|
1168
1168
|
try {
|
|
1169
|
-
const text =
|
|
1169
|
+
const text = fs18.readFileSync(file, "utf8");
|
|
1170
1170
|
if (file.endsWith(".json")) mergeIstanbul(map, text, root);
|
|
1171
1171
|
else mergeLcov(map, text, root);
|
|
1172
1172
|
} catch {
|
|
@@ -1175,8 +1175,8 @@ function loadCoverage(root, explicit) {
|
|
|
1175
1175
|
return map.size ? map : null;
|
|
1176
1176
|
}
|
|
1177
1177
|
function rel(root, p) {
|
|
1178
|
-
const abs =
|
|
1179
|
-
return
|
|
1178
|
+
const abs = path2.isAbsolute(p) ? p : path2.resolve(root, p);
|
|
1179
|
+
return path2.relative(root, abs).split(path2.sep).join("/");
|
|
1180
1180
|
}
|
|
1181
1181
|
function bump(map, file, line, hits) {
|
|
1182
1182
|
let lh = map.get(file);
|
|
@@ -1464,32 +1464,6 @@ function matchConfidence(entry, nameParts, imports, callees) {
|
|
|
1464
1464
|
return conf;
|
|
1465
1465
|
}
|
|
1466
1466
|
|
|
1467
|
-
// src/engine/serialize.ts
|
|
1468
|
-
function serializeGraph(graph) {
|
|
1469
|
-
return `${stableStringify(graph, 2)}
|
|
1470
|
-
`;
|
|
1471
|
-
}
|
|
1472
|
-
function stableStringify(value, indent = 2) {
|
|
1473
|
-
return JSON.stringify(sortKeys(value), null, indent);
|
|
1474
|
-
}
|
|
1475
|
-
function sortKeys(value) {
|
|
1476
|
-
if (Array.isArray(value)) return value.map(sortKeys);
|
|
1477
|
-
if (value && typeof value === "object") {
|
|
1478
|
-
const input = value;
|
|
1479
|
-
const out = {};
|
|
1480
|
-
for (const key of Object.keys(input).sort()) {
|
|
1481
|
-
const v = input[key];
|
|
1482
|
-
if (v === void 0) continue;
|
|
1483
|
-
out[key] = sortKeys(v);
|
|
1484
|
-
}
|
|
1485
|
-
return out;
|
|
1486
|
-
}
|
|
1487
|
-
return value;
|
|
1488
|
-
}
|
|
1489
|
-
function parseGraph(json) {
|
|
1490
|
-
return JSON.parse(json);
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
1467
|
// src/schema.ts
|
|
1494
1468
|
var SCHEMA_VERSION = "vg-graph/1.0";
|
|
1495
1469
|
var DEFAULT_INLINE_THRESHOLD = 24;
|
|
@@ -1512,7 +1486,7 @@ async function parseInline(files, options) {
|
|
|
1512
1486
|
onProgress?.(0, files.length);
|
|
1513
1487
|
for (const file of files) {
|
|
1514
1488
|
try {
|
|
1515
|
-
const source =
|
|
1489
|
+
const source = fs18.readFileSync(file.abs, "utf8");
|
|
1516
1490
|
out.push(await parseSource(file.rel, file.lang.id, source));
|
|
1517
1491
|
} catch (err) {
|
|
1518
1492
|
out.push(emptyParse(file, `parse failed: ${err.message}`));
|
|
@@ -1567,9 +1541,9 @@ function isWorkerOom(err) {
|
|
|
1567
1541
|
return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
|
|
1568
1542
|
}
|
|
1569
1543
|
function resolveWorkerFile() {
|
|
1570
|
-
const here =
|
|
1571
|
-
const candidate =
|
|
1572
|
-
return
|
|
1544
|
+
const here = path2.dirname(fileURLToPath(import.meta.url));
|
|
1545
|
+
const candidate = path2.join(here, "parse-worker.js");
|
|
1546
|
+
return fs18.existsSync(candidate) ? candidate : null;
|
|
1573
1547
|
}
|
|
1574
1548
|
function chunk(items, buckets) {
|
|
1575
1549
|
const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
|
|
@@ -1608,7 +1582,7 @@ function resolve4(parses, resolver) {
|
|
|
1608
1582
|
addNode(baseNodes, {
|
|
1609
1583
|
id: fileId,
|
|
1610
1584
|
kind: "file",
|
|
1611
|
-
name:
|
|
1585
|
+
name: path2.posix.basename(p.rel),
|
|
1612
1586
|
qualifiedName: p.rel,
|
|
1613
1587
|
file: p.rel,
|
|
1614
1588
|
span: { start: 1, end: 1 },
|
|
@@ -1663,8 +1637,8 @@ function resolve4(parses, resolver) {
|
|
|
1663
1637
|
for (const imp of p.imports) {
|
|
1664
1638
|
namespaces.add(imp.source);
|
|
1665
1639
|
if (IMPORT_NAMES_A_TYPE.has(p.lang)) {
|
|
1666
|
-
const
|
|
1667
|
-
if (
|
|
1640
|
+
const sep9 = Math.max(imp.source.lastIndexOf("."), imp.source.lastIndexOf("\\"));
|
|
1641
|
+
if (sep9 > 0) namespaces.add(imp.source.slice(0, sep9));
|
|
1668
1642
|
}
|
|
1669
1643
|
}
|
|
1670
1644
|
for (const ns of namespaces) for (const rel2 of filesByNamespace.get(ns) ?? []) if (rel2 !== p.rel) reach.add(rel2);
|
|
@@ -2147,50 +2121,7 @@ function enclosing3(nodes, line) {
|
|
|
2147
2121
|
return best;
|
|
2148
2122
|
}
|
|
2149
2123
|
function normalize2(p) {
|
|
2150
|
-
return
|
|
2151
|
-
}
|
|
2152
|
-
var CACHE_VERSION = "vg-parse-cache/3";
|
|
2153
|
-
function cacheDir(root) {
|
|
2154
|
-
return path17.join(root, ".vibgrate", "cache");
|
|
2155
|
-
}
|
|
2156
|
-
function cachePath(root) {
|
|
2157
|
-
return path17.join(cacheDir(root), "parse-cache.json");
|
|
2158
|
-
}
|
|
2159
|
-
function loadCache(root, opts) {
|
|
2160
|
-
const file = cachePath(root);
|
|
2161
|
-
let data = {
|
|
2162
|
-
version: CACHE_VERSION,
|
|
2163
|
-
toolVersion: opts.toolVersion,
|
|
2164
|
-
grammars: opts.grammars,
|
|
2165
|
-
entries: {}
|
|
2166
|
-
};
|
|
2167
|
-
if (!opts.disabled && fs17.existsSync(file)) {
|
|
2168
|
-
try {
|
|
2169
|
-
const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2170
|
-
if (loaded.version === CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.grammars === opts.grammars && loaded.entries) {
|
|
2171
|
-
data = loaded;
|
|
2172
|
-
}
|
|
2173
|
-
} catch {
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
return {
|
|
2177
|
-
get(rel2, hash) {
|
|
2178
|
-
const entry = data.entries[rel2];
|
|
2179
|
-
return entry && entry.hash === hash ? entry.parse : void 0;
|
|
2180
|
-
},
|
|
2181
|
-
set(rel2, parse) {
|
|
2182
|
-
data.entries[rel2] = { hash: parse.hash, parse };
|
|
2183
|
-
},
|
|
2184
|
-
prune(currentRels) {
|
|
2185
|
-
for (const rel2 of Object.keys(data.entries)) {
|
|
2186
|
-
if (!currentRels.has(rel2)) delete data.entries[rel2];
|
|
2187
|
-
}
|
|
2188
|
-
},
|
|
2189
|
-
save() {
|
|
2190
|
-
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
2191
|
-
fs17.writeFileSync(file, stableStringify(data, 0));
|
|
2192
|
-
}
|
|
2193
|
-
};
|
|
2124
|
+
return path2.resolve(p).split(path2.sep).join("/");
|
|
2194
2125
|
}
|
|
2195
2126
|
|
|
2196
2127
|
// src/engine/epistemic.ts
|
|
@@ -2220,7 +2151,7 @@ function epistemicBreakdown(edges) {
|
|
|
2220
2151
|
// src/engine/build.ts
|
|
2221
2152
|
async function buildGraph(options) {
|
|
2222
2153
|
const start = nowMs();
|
|
2223
|
-
const root =
|
|
2154
|
+
const root = path2.resolve(options.root);
|
|
2224
2155
|
const files = discover({
|
|
2225
2156
|
root,
|
|
2226
2157
|
only: options.only,
|
|
@@ -2248,7 +2179,7 @@ async function buildGraph(options) {
|
|
|
2248
2179
|
for (const file of files) {
|
|
2249
2180
|
let stat;
|
|
2250
2181
|
try {
|
|
2251
|
-
stat =
|
|
2182
|
+
stat = fs18.statSync(file.abs);
|
|
2252
2183
|
} catch {
|
|
2253
2184
|
continue;
|
|
2254
2185
|
}
|
|
@@ -2266,7 +2197,7 @@ async function buildGraph(options) {
|
|
|
2266
2197
|
}
|
|
2267
2198
|
let hash = "";
|
|
2268
2199
|
try {
|
|
2269
|
-
hash = hashBytes(
|
|
2200
|
+
hash = hashBytes(fs18.readFileSync(file.abs));
|
|
2270
2201
|
} catch {
|
|
2271
2202
|
continue;
|
|
2272
2203
|
}
|
|
@@ -2358,7 +2289,7 @@ async function buildGraph(options) {
|
|
|
2358
2289
|
toolchain
|
|
2359
2290
|
},
|
|
2360
2291
|
meta: {
|
|
2361
|
-
root:
|
|
2292
|
+
root: path2.basename(root) === "" ? "." : ".",
|
|
2362
2293
|
languages,
|
|
2363
2294
|
counts: {
|
|
2364
2295
|
nodes: analysis.nodes.length,
|
|
@@ -2404,14 +2335,14 @@ function toRepoRel(p) {
|
|
|
2404
2335
|
function loadScipIndex(root, explicit) {
|
|
2405
2336
|
const candidates = [
|
|
2406
2337
|
explicit,
|
|
2407
|
-
|
|
2408
|
-
|
|
2338
|
+
path2.join(root, "index.scip"),
|
|
2339
|
+
path2.join(root, ".vibgrate", "index.scip")
|
|
2409
2340
|
].filter((p) => Boolean(p));
|
|
2410
2341
|
for (const file of candidates) {
|
|
2411
|
-
const abs =
|
|
2412
|
-
if (!
|
|
2342
|
+
const abs = path2.isAbsolute(file) ? file : path2.resolve(root, file);
|
|
2343
|
+
if (!fs18.existsSync(abs)) continue;
|
|
2413
2344
|
try {
|
|
2414
|
-
const index = decodeScipIndex(new Uint8Array(
|
|
2345
|
+
const index = decodeScipIndex(new Uint8Array(fs18.readFileSync(abs)));
|
|
2415
2346
|
if (index.documents.length) {
|
|
2416
2347
|
return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
|
|
2417
2348
|
}
|
|
@@ -2572,7 +2503,7 @@ function esc(s) {
|
|
|
2572
2503
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2573
2504
|
}
|
|
2574
2505
|
function vibgrateDir(root) {
|
|
2575
|
-
return
|
|
2506
|
+
return path2.join(root, ".vibgrate");
|
|
2576
2507
|
}
|
|
2577
2508
|
var DEFAULT_GITIGNORE = [
|
|
2578
2509
|
"# Created once by vg \u2014 local graph artifacts stay out of git by default.",
|
|
@@ -2588,51 +2519,51 @@ var DEFAULT_GITIGNORE = [
|
|
|
2588
2519
|
"mcp-navigation.json"
|
|
2589
2520
|
];
|
|
2590
2521
|
function ensureVibgrateGitignore(root) {
|
|
2591
|
-
const file =
|
|
2592
|
-
if (
|
|
2593
|
-
|
|
2594
|
-
|
|
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")}
|
|
2595
2526
|
`);
|
|
2596
2527
|
}
|
|
2597
2528
|
function defaultGraphPath(root) {
|
|
2598
|
-
return
|
|
2529
|
+
return path2.join(vibgrateDir(root), "graph.json");
|
|
2599
2530
|
}
|
|
2600
2531
|
function writeArtifacts(graph, options) {
|
|
2601
2532
|
const dir = vibgrateDir(options.root);
|
|
2602
|
-
|
|
2533
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
2603
2534
|
ensureVibgrateGitignore(options.root);
|
|
2604
2535
|
const graphPath = options.graphPath ?? defaultGraphPath(options.root);
|
|
2605
|
-
|
|
2536
|
+
fs18.mkdirSync(path2.dirname(graphPath), { recursive: true });
|
|
2606
2537
|
const tmp = `${graphPath}.${process.pid}.tmp`;
|
|
2607
2538
|
try {
|
|
2608
|
-
|
|
2609
|
-
|
|
2539
|
+
fs18.writeFileSync(tmp, serializeGraph(graph));
|
|
2540
|
+
fs18.renameSync(tmp, graphPath);
|
|
2610
2541
|
} catch (err) {
|
|
2611
|
-
|
|
2542
|
+
fs18.rmSync(tmp, { force: true });
|
|
2612
2543
|
throw err;
|
|
2613
2544
|
}
|
|
2614
2545
|
const written = { graphPath };
|
|
2615
2546
|
if (options.report !== false) {
|
|
2616
|
-
const reportPath =
|
|
2617
|
-
|
|
2547
|
+
const reportPath = path2.join(dir, "GRAPH_REPORT.md");
|
|
2548
|
+
fs18.writeFileSync(reportPath, renderReport(graph));
|
|
2618
2549
|
written.reportPath = reportPath;
|
|
2619
2550
|
}
|
|
2620
2551
|
if (options.html !== false) {
|
|
2621
|
-
const htmlPath =
|
|
2622
|
-
|
|
2552
|
+
const htmlPath = path2.join(dir, "graph.html");
|
|
2553
|
+
fs18.writeFileSync(htmlPath, renderHtml(graph));
|
|
2623
2554
|
written.htmlPath = htmlPath;
|
|
2624
2555
|
}
|
|
2625
2556
|
if (graph.facts && graph.facts.length) {
|
|
2626
|
-
const factsPath =
|
|
2627
|
-
|
|
2557
|
+
const factsPath = path2.join(dir, "facts.jsonl");
|
|
2558
|
+
fs18.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
|
|
2628
2559
|
written.factsPath = factsPath;
|
|
2629
2560
|
}
|
|
2630
2561
|
return written;
|
|
2631
2562
|
}
|
|
2632
2563
|
function loadGraph(root, graphPath) {
|
|
2633
2564
|
const file = graphPath ?? defaultGraphPath(root);
|
|
2634
|
-
if (!
|
|
2635
|
-
return parseGraph(
|
|
2565
|
+
if (!fs18.existsSync(file)) return null;
|
|
2566
|
+
return parseGraph(fs18.readFileSync(file, "utf8"));
|
|
2636
2567
|
}
|
|
2637
2568
|
|
|
2638
2569
|
// src/engine/verify.ts
|
|
@@ -2698,361 +2629,23 @@ var CliError = class extends Error {
|
|
|
2698
2629
|
function usageError(message) {
|
|
2699
2630
|
return new CliError(message, ExitCode.USAGE_ERROR);
|
|
2700
2631
|
}
|
|
2701
|
-
var DEFAULT_LOCK_STALE_MS = 15 * 60 * 1e3;
|
|
2702
|
-
function isProcessAlive(pid) {
|
|
2703
|
-
try {
|
|
2704
|
-
process.kill(pid, 0);
|
|
2705
|
-
return true;
|
|
2706
|
-
} catch (e) {
|
|
2707
|
-
return e.code === "EPERM";
|
|
2708
|
-
}
|
|
2709
|
-
}
|
|
2710
|
-
function lockIsStale(file, staleMs) {
|
|
2711
|
-
try {
|
|
2712
|
-
const { pid, at } = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2713
|
-
if (typeof at === "number" && Date.now() - at > staleMs) return true;
|
|
2714
|
-
if (typeof pid === "number" && !isProcessAlive(pid)) return true;
|
|
2715
|
-
return false;
|
|
2716
|
-
} catch {
|
|
2717
|
-
return true;
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
|
|
2721
|
-
const write = () => {
|
|
2722
|
-
try {
|
|
2723
|
-
fs17.mkdirSync(path17.dirname(file), { recursive: true });
|
|
2724
|
-
const fd = fs17.openSync(file, "wx");
|
|
2725
|
-
fs17.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
|
|
2726
|
-
fs17.closeSync(fd);
|
|
2727
|
-
return true;
|
|
2728
|
-
} catch {
|
|
2729
|
-
return false;
|
|
2730
|
-
}
|
|
2731
|
-
};
|
|
2732
|
-
if (write()) return true;
|
|
2733
|
-
if (lockIsStale(file, staleMs)) {
|
|
2734
|
-
try {
|
|
2735
|
-
fs17.rmSync(file, { force: true });
|
|
2736
|
-
} catch {
|
|
2737
|
-
}
|
|
2738
|
-
return write();
|
|
2739
|
-
}
|
|
2740
|
-
return false;
|
|
2741
|
-
}
|
|
2742
|
-
function releaseLock(file) {
|
|
2743
|
-
try {
|
|
2744
|
-
fs17.rmSync(file, { force: true });
|
|
2745
|
-
} catch {
|
|
2746
|
-
}
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
// src/engine/embeddings.ts
|
|
2750
|
-
function resolveEmbedModel(model) {
|
|
2751
|
-
return model ?? "bge-small-en-v1.5";
|
|
2752
|
-
}
|
|
2753
|
-
function modelCacheDir() {
|
|
2754
|
-
const base = process.env.XDG_CACHE_HOME || path17.join(os.homedir(), ".cache");
|
|
2755
|
-
return path17.join(base, "vibgrate", "models");
|
|
2756
|
-
}
|
|
2757
|
-
function modelReadyMarker(modelId) {
|
|
2758
|
-
return path17.join(modelCacheDir(), `.ready-${safe(modelId)}`);
|
|
2759
|
-
}
|
|
2760
|
-
function isModelReady(modelId = resolveEmbedModel()) {
|
|
2761
|
-
return fs17.existsSync(modelReadyMarker(modelId));
|
|
2762
|
-
}
|
|
2763
|
-
function unavailableMessage(reason) {
|
|
2764
|
-
switch (reason) {
|
|
2765
|
-
case "no-permission":
|
|
2766
|
-
return `semantic search couldn't write its model cache at ${modelCacheDir()} \u2014 using fast lexical search instead. Relocate it with the standard XDG_CACHE_HOME, or run with --local to stay lexical quietly. (No admin/sudo is ever needed \u2014 the cache lives in your home folder.)`;
|
|
2767
|
-
case "download-failed":
|
|
2768
|
-
return `the semantic model isn't downloaded yet and couldn't be fetched (offline?) \u2014 using fast lexical search. It'll try again next time; run with --local to keep lexical without retrying.`;
|
|
2769
|
-
case "not-installed":
|
|
2770
|
-
return `the optional semantic backend isn't available here \u2014 using fast lexical search.`;
|
|
2771
|
-
case "init-failed":
|
|
2772
|
-
default:
|
|
2773
|
-
return `the semantic model couldn't load \u2014 using fast lexical search.`;
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
function modelCacheInfo(modelId = resolveEmbedModel()) {
|
|
2777
|
-
const dir = modelCacheDir();
|
|
2778
|
-
return { dir, present: isModelReady(modelId) || fs17.existsSync(dir), bytes: dirSize(dir) };
|
|
2779
|
-
}
|
|
2780
|
-
function clearModelCache() {
|
|
2781
|
-
const dir = modelCacheDir();
|
|
2782
|
-
const bytes = dirSize(dir);
|
|
2783
|
-
try {
|
|
2784
|
-
fs17.rmSync(dir, { recursive: true, force: true });
|
|
2785
|
-
} catch {
|
|
2786
|
-
}
|
|
2787
|
-
return bytes;
|
|
2788
|
-
}
|
|
2789
|
-
function dirSize(dir) {
|
|
2790
|
-
let total = 0;
|
|
2791
|
-
let entries;
|
|
2792
|
-
try {
|
|
2793
|
-
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
2794
|
-
} catch {
|
|
2795
|
-
return 0;
|
|
2796
|
-
}
|
|
2797
|
-
for (const e of entries) {
|
|
2798
|
-
const p = path17.join(dir, e.name);
|
|
2799
|
-
if (e.isDirectory()) total += dirSize(p);
|
|
2800
|
-
else {
|
|
2801
|
-
try {
|
|
2802
|
-
total += fs17.statSync(p).size;
|
|
2803
|
-
} catch {
|
|
2804
|
-
}
|
|
2805
|
-
}
|
|
2806
|
-
}
|
|
2807
|
-
return total;
|
|
2808
|
-
}
|
|
2809
|
-
function isPermissionError(e) {
|
|
2810
|
-
const code = e?.code;
|
|
2811
|
-
return code === "EACCES" || code === "EPERM" || code === "EROFS";
|
|
2812
|
-
}
|
|
2813
|
-
function embeddingsCached(root, modelId) {
|
|
2814
|
-
return fs17.existsSync(path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
|
|
2815
|
-
}
|
|
2816
|
-
async function loadEmbedder(options = {}) {
|
|
2817
|
-
if (options.local) return null;
|
|
2818
|
-
const modelId = resolveEmbedModel(options.model);
|
|
2819
|
-
if (options.noDownload && !isModelReady(modelId)) return null;
|
|
2820
|
-
const fail = (reason) => {
|
|
2821
|
-
options.onUnavailable?.(reason);
|
|
2822
|
-
return null;
|
|
2823
|
-
};
|
|
2824
|
-
const mod = await importEmbedBackend();
|
|
2825
|
-
if (!mod?.FlagEmbedding) return fail("not-installed");
|
|
2826
|
-
const cache = modelCacheDir();
|
|
2827
|
-
try {
|
|
2828
|
-
fs17.mkdirSync(cache, { recursive: true });
|
|
2829
|
-
} catch (e) {
|
|
2830
|
-
return fail(isPermissionError(e) ? "no-permission" : "init-failed");
|
|
2831
|
-
}
|
|
2832
|
-
try {
|
|
2833
|
-
const model = await withTimeout(
|
|
2834
|
-
mod.FlagEmbedding.init({
|
|
2835
|
-
model: mapModel(mod, modelId),
|
|
2836
|
-
cacheDir: cache,
|
|
2837
|
-
showDownloadProgress: options.showDownloadProgress ?? false
|
|
2838
|
-
}),
|
|
2839
|
-
embedInitTimeoutMs(),
|
|
2840
|
-
"embedding model init timed out"
|
|
2841
|
-
);
|
|
2842
|
-
try {
|
|
2843
|
-
fs17.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
|
|
2844
|
-
} catch {
|
|
2845
|
-
}
|
|
2846
|
-
return {
|
|
2847
|
-
id: modelId,
|
|
2848
|
-
async embed(texts) {
|
|
2849
|
-
const out = [];
|
|
2850
|
-
for await (const batch of model.embed(texts, 64)) {
|
|
2851
|
-
for (const v of batch) out.push(Array.from(v));
|
|
2852
|
-
}
|
|
2853
|
-
return out;
|
|
2854
|
-
},
|
|
2855
|
-
async embedQuery(text) {
|
|
2856
|
-
const v = await model.queryEmbed(text);
|
|
2857
|
-
return Array.from(v);
|
|
2858
|
-
}
|
|
2859
|
-
};
|
|
2860
|
-
} catch (e) {
|
|
2861
|
-
return fail(isPermissionError(e) ? "no-permission" : "download-failed");
|
|
2862
|
-
}
|
|
2863
|
-
}
|
|
2864
|
-
async function importEmbedBackend() {
|
|
2865
|
-
const hostDir = process.env.VIBGRATE_EMBEDDER_PATH;
|
|
2866
|
-
if (hostDir) {
|
|
2867
|
-
try {
|
|
2868
|
-
const req = createRequire(path17.join(hostDir, "package.json"));
|
|
2869
|
-
const mod = await import(pathToFileURL(req.resolve("fastembed")).href);
|
|
2870
|
-
const resolved = mod?.FlagEmbedding ? mod : mod?.default;
|
|
2871
|
-
if (resolved?.FlagEmbedding) return resolved;
|
|
2872
|
-
} catch {
|
|
2873
|
-
}
|
|
2874
|
-
}
|
|
2875
|
-
return await import('fastembed').catch(() => null);
|
|
2876
|
-
}
|
|
2877
|
-
function embedInitTimeoutMs() {
|
|
2878
|
-
const n = Number(process.env.VG_EMBED_TIMEOUT_MS);
|
|
2879
|
-
return Number.isFinite(n) && n > 0 ? n : 6e4;
|
|
2880
|
-
}
|
|
2881
|
-
async function withTimeout(promise, ms, message) {
|
|
2882
|
-
let timer;
|
|
2883
|
-
try {
|
|
2884
|
-
return await Promise.race([
|
|
2885
|
-
promise,
|
|
2886
|
-
new Promise((_, reject) => {
|
|
2887
|
-
timer = setTimeout(() => reject(new Error(message)), ms);
|
|
2888
|
-
timer.unref?.();
|
|
2889
|
-
})
|
|
2890
|
-
]);
|
|
2891
|
-
} finally {
|
|
2892
|
-
clearTimeout(timer);
|
|
2893
|
-
}
|
|
2894
|
-
}
|
|
2895
|
-
function mapModel(mod, id) {
|
|
2896
|
-
const m = mod.EmbeddingModel ?? {};
|
|
2897
|
-
if (/bge-small/i.test(id)) return m.BGESmallENV15 ?? m.BGESmallEN ?? id;
|
|
2898
|
-
if (/bge-base/i.test(id)) return m.BGEBaseENV15 ?? id;
|
|
2899
|
-
if (/all-minilm/i.test(id)) return m.AllMiniLML6V2 ?? id;
|
|
2900
|
-
return id;
|
|
2901
|
-
}
|
|
2902
|
-
var PATH_NOISE = /* @__PURE__ */ new Set([
|
|
2903
|
-
"src",
|
|
2904
|
-
"app",
|
|
2905
|
-
"apps",
|
|
2906
|
-
"lib",
|
|
2907
|
-
"libs",
|
|
2908
|
-
"dist",
|
|
2909
|
-
"build",
|
|
2910
|
-
"out",
|
|
2911
|
-
"index",
|
|
2912
|
-
"main",
|
|
2913
|
-
"packages",
|
|
2914
|
-
"pkg",
|
|
2915
|
-
"test",
|
|
2916
|
-
"tests",
|
|
2917
|
-
"__tests__",
|
|
2918
|
-
"spec",
|
|
2919
|
-
"node_modules",
|
|
2920
|
-
"internal",
|
|
2921
|
-
"com",
|
|
2922
|
-
"example",
|
|
2923
|
-
"demo",
|
|
2924
|
-
"java",
|
|
2925
|
-
"net",
|
|
2926
|
-
"org",
|
|
2927
|
-
"www"
|
|
2928
|
-
]);
|
|
2929
|
-
function pathContext(file) {
|
|
2930
|
-
const parts = file.split("/");
|
|
2931
|
-
const stem = (parts.pop() ?? "").replace(/\.[^.]+$/, "");
|
|
2932
|
-
const dirs = parts.filter((p) => p && !PATH_NOISE.has(p.toLowerCase()));
|
|
2933
|
-
return [...dirs.slice(-2), stem].filter(Boolean).join(" ");
|
|
2934
|
-
}
|
|
2935
|
-
function nodeEmbedText(node, areaLabel) {
|
|
2936
|
-
return [node.qualifiedName, node.kind, node.signature ?? "", node.doc ?? "", pathContext(node.file), areaLabel ?? ""].filter(Boolean).join(" ");
|
|
2937
|
-
}
|
|
2938
|
-
function cosine(a, b) {
|
|
2939
|
-
let dot2 = 0;
|
|
2940
|
-
let na = 0;
|
|
2941
|
-
let nb = 0;
|
|
2942
|
-
const n = Math.min(a.length, b.length);
|
|
2943
|
-
for (let i = 0; i < n; i++) {
|
|
2944
|
-
dot2 += a[i] * b[i];
|
|
2945
|
-
na += a[i] * a[i];
|
|
2946
|
-
nb += b[i] * b[i];
|
|
2947
|
-
}
|
|
2948
|
-
if (na === 0 || nb === 0) return 0;
|
|
2949
|
-
return dot2 / (Math.sqrt(na) * Math.sqrt(nb));
|
|
2950
|
-
}
|
|
2951
|
-
var EMBED_CHUNK = 256;
|
|
2952
|
-
var CACHE_WRITE_INTERVAL_MS = 1500;
|
|
2953
|
-
function vectorCachePath(root, modelId) {
|
|
2954
|
-
return path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
|
|
2955
|
-
}
|
|
2956
|
-
function countPending(graph, root, modelId) {
|
|
2957
|
-
const entries = readCacheEntries(vectorCachePath(root, modelId), modelId);
|
|
2958
|
-
const areaLabel = new Map(graph.areas.map((a) => [a.id, a.label]));
|
|
2959
|
-
let pending = 0;
|
|
2960
|
-
for (const n of graph.nodes) {
|
|
2961
|
-
if (n.kind === "file" || n.kind === "external") continue;
|
|
2962
|
-
const h = hashString(nodeEmbedText(n, areaLabel.get(n.area)));
|
|
2963
|
-
const cached2 = entries[n.id];
|
|
2964
|
-
if (!cached2 || cached2.hash !== h) pending++;
|
|
2965
|
-
}
|
|
2966
|
-
return pending;
|
|
2967
|
-
}
|
|
2968
|
-
function readCacheEntries(file, modelId) {
|
|
2969
|
-
if (!fs17.existsSync(file)) return {};
|
|
2970
|
-
try {
|
|
2971
|
-
const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2972
|
-
if (loaded.model === modelId && loaded.entries) return loaded.entries;
|
|
2973
|
-
} catch {
|
|
2974
|
-
}
|
|
2975
|
-
return {};
|
|
2976
|
-
}
|
|
2977
|
-
async function getNodeEmbeddings(graph, embedder, root, onProgress) {
|
|
2978
|
-
const file = vectorCachePath(root, embedder.id);
|
|
2979
|
-
let cache = { model: embedder.id, entries: {} };
|
|
2980
|
-
if (fs17.existsSync(file)) {
|
|
2981
|
-
try {
|
|
2982
|
-
const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2983
|
-
if (loaded.model === embedder.id && loaded.entries) cache = loaded;
|
|
2984
|
-
} catch {
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
const areaLabel = new Map(graph.areas.map((a) => [a.id, a.label]));
|
|
2988
|
-
const targets = graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external");
|
|
2989
|
-
const toEmbed = [];
|
|
2990
|
-
const vectors = /* @__PURE__ */ new Map();
|
|
2991
|
-
for (const n of targets) {
|
|
2992
|
-
const text = nodeEmbedText(n, areaLabel.get(n.area));
|
|
2993
|
-
const h = hashString(text);
|
|
2994
|
-
const cached2 = cache.entries[n.id];
|
|
2995
|
-
if (cached2 && cached2.hash === h) vectors.set(n.id, cached2.vec);
|
|
2996
|
-
else toEmbed.push({ id: n.id, text, hash: h });
|
|
2997
|
-
}
|
|
2998
|
-
const persist = () => {
|
|
2999
|
-
try {
|
|
3000
|
-
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
3001
|
-
const tmp = `${file}.tmp.${process.pid}`;
|
|
3002
|
-
fs17.writeFileSync(tmp, JSON.stringify(cache));
|
|
3003
|
-
fs17.renameSync(tmp, file);
|
|
3004
|
-
} catch {
|
|
3005
|
-
}
|
|
3006
|
-
};
|
|
3007
|
-
if (toEmbed.length) {
|
|
3008
|
-
const lock = `${file}.lock`;
|
|
3009
|
-
if (!acquireLock(lock)) return vectors;
|
|
3010
|
-
try {
|
|
3011
|
-
onProgress?.(0, toEmbed.length);
|
|
3012
|
-
let lastWrite = Date.now();
|
|
3013
|
-
for (let i = 0; i < toEmbed.length; i += EMBED_CHUNK) {
|
|
3014
|
-
const slice = toEmbed.slice(i, i + EMBED_CHUNK);
|
|
3015
|
-
const vecs = await embedder.embed(slice.map((t) => t.text));
|
|
3016
|
-
slice.forEach((t, j) => {
|
|
3017
|
-
const vec = vecs[j] ?? [];
|
|
3018
|
-
vectors.set(t.id, vec);
|
|
3019
|
-
cache.entries[t.id] = { hash: t.hash, vec };
|
|
3020
|
-
});
|
|
3021
|
-
onProgress?.(Math.min(i + EMBED_CHUNK, toEmbed.length), toEmbed.length);
|
|
3022
|
-
if (Date.now() - lastWrite >= CACHE_WRITE_INTERVAL_MS) {
|
|
3023
|
-
persist();
|
|
3024
|
-
lastWrite = Date.now();
|
|
3025
|
-
}
|
|
3026
|
-
}
|
|
3027
|
-
const live = new Set(targets.map((n) => n.id));
|
|
3028
|
-
for (const id of Object.keys(cache.entries)) if (!live.has(id)) delete cache.entries[id];
|
|
3029
|
-
persist();
|
|
3030
|
-
} finally {
|
|
3031
|
-
releaseLock(lock);
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
return vectors;
|
|
3035
|
-
}
|
|
3036
|
-
function safe(id) {
|
|
3037
|
-
return id.replace(/[^a-z0-9.-]+/gi, "_");
|
|
3038
|
-
}
|
|
3039
2632
|
var SNAPSHOT_VERSION = "vg-freshness/1";
|
|
3040
2633
|
function snapshotPath(root) {
|
|
3041
|
-
return
|
|
2634
|
+
return path2.join(cacheDir(root), "freshness.json");
|
|
3042
2635
|
}
|
|
3043
2636
|
function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
|
|
3044
2637
|
const files = {};
|
|
3045
2638
|
for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
|
|
3046
2639
|
const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
|
|
3047
2640
|
try {
|
|
3048
|
-
|
|
3049
|
-
|
|
2641
|
+
fs18.mkdirSync(cacheDir(root), { recursive: true });
|
|
2642
|
+
fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
3050
2643
|
} catch {
|
|
3051
2644
|
}
|
|
3052
2645
|
}
|
|
3053
2646
|
function loadSnapshot(root) {
|
|
3054
2647
|
try {
|
|
3055
|
-
const loaded = JSON.parse(
|
|
2648
|
+
const loaded = JSON.parse(fs18.readFileSync(snapshotPath(root), "utf8"));
|
|
3056
2649
|
if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
|
|
3057
2650
|
return { ...loaded, scope: loaded.scope ?? {} };
|
|
3058
2651
|
}
|
|
@@ -3088,7 +2681,7 @@ function probeFreshness(root) {
|
|
|
3088
2681
|
}
|
|
3089
2682
|
let stat;
|
|
3090
2683
|
try {
|
|
3091
|
-
stat =
|
|
2684
|
+
stat = fs18.statSync(file.abs);
|
|
3092
2685
|
} catch {
|
|
3093
2686
|
drift.removed.push(file.rel);
|
|
3094
2687
|
continue;
|
|
@@ -3096,7 +2689,7 @@ function probeFreshness(root) {
|
|
|
3096
2689
|
if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
|
|
3097
2690
|
let hash = "";
|
|
3098
2691
|
try {
|
|
3099
|
-
hash = hashBytes(
|
|
2692
|
+
hash = hashBytes(fs18.readFileSync(file.abs));
|
|
3100
2693
|
} catch {
|
|
3101
2694
|
drift.removed.push(file.rel);
|
|
3102
2695
|
continue;
|
|
@@ -3113,7 +2706,7 @@ function probeFreshness(root) {
|
|
|
3113
2706
|
}
|
|
3114
2707
|
if (absorbed && !hasDrift(drift)) {
|
|
3115
2708
|
try {
|
|
3116
|
-
|
|
2709
|
+
fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
3117
2710
|
} catch {
|
|
3118
2711
|
}
|
|
3119
2712
|
}
|
|
@@ -3136,348 +2729,6 @@ function pruneScope(scope) {
|
|
|
3136
2729
|
if (scope.grammarsDir) out.grammarsDir = scope.grammarsDir;
|
|
3137
2730
|
return out;
|
|
3138
2731
|
}
|
|
3139
|
-
|
|
3140
|
-
// src/engine/relations.ts
|
|
3141
|
-
var GraphIndex = class {
|
|
3142
|
-
constructor(graph) {
|
|
3143
|
-
this.graph = graph;
|
|
3144
|
-
for (const n of graph.nodes) this.nodeById.set(n.id, n);
|
|
3145
|
-
for (const e of graph.edges) {
|
|
3146
|
-
push2(this.outById, e.src, e);
|
|
3147
|
-
push2(this.inById, e.dst, e);
|
|
3148
|
-
}
|
|
3149
|
-
}
|
|
3150
|
-
graph;
|
|
3151
|
-
nodeById = /* @__PURE__ */ new Map();
|
|
3152
|
-
outById = /* @__PURE__ */ new Map();
|
|
3153
|
-
inById = /* @__PURE__ */ new Map();
|
|
3154
|
-
out(id, kind) {
|
|
3155
|
-
const all = this.outById.get(id) ?? [];
|
|
3156
|
-
return kind ? all.filter((e) => e.kind === kind) : all;
|
|
3157
|
-
}
|
|
3158
|
-
in(id, kind) {
|
|
3159
|
-
const all = this.inById.get(id) ?? [];
|
|
3160
|
-
return kind ? all.filter((e) => e.kind === kind) : all;
|
|
3161
|
-
}
|
|
3162
|
-
node(id) {
|
|
3163
|
-
return this.nodeById.get(id);
|
|
3164
|
-
}
|
|
3165
|
-
/**
|
|
3166
|
-
* Resolved nodes called by `id` — invocations (`call`) plus structural
|
|
3167
|
-
* dependency references (`references`, e.g. a constructor-injected field's
|
|
3168
|
-
* type), since both represent real usage a caller/impact question cares
|
|
3169
|
-
* about. `references` is otherwise only emitted by the precise SCIP/tsc
|
|
3170
|
-
* rungs and (for Java DI wiring) the heuristic rung — never a guess.
|
|
3171
|
-
*/
|
|
3172
|
-
callees(id) {
|
|
3173
|
-
return this.resolveTargets(this.out(id).filter(isUsageEdge), "dst");
|
|
3174
|
-
}
|
|
3175
|
-
/** Resolved nodes that call or structurally reference `id`. */
|
|
3176
|
-
callers(id) {
|
|
3177
|
-
return this.resolveTargets(this.in(id).filter(isUsageEdge), "src");
|
|
3178
|
-
}
|
|
3179
|
-
resolveTargets(edges, end) {
|
|
3180
|
-
const out = [];
|
|
3181
|
-
for (const e of edges) {
|
|
3182
|
-
const node = this.nodeById.get(e[end]);
|
|
3183
|
-
if (node) out.push({ edge: e, node });
|
|
3184
|
-
}
|
|
3185
|
-
return out;
|
|
3186
|
-
}
|
|
3187
|
-
};
|
|
3188
|
-
var USAGE_KINDS = /* @__PURE__ */ new Set(["call", "references"]);
|
|
3189
|
-
function isUsageEdge(e) {
|
|
3190
|
-
return USAGE_KINDS.has(e.kind);
|
|
3191
|
-
}
|
|
3192
|
-
function push2(map, key, value) {
|
|
3193
|
-
const list = map.get(key);
|
|
3194
|
-
if (list) list.push(value);
|
|
3195
|
-
else map.set(key, [value]);
|
|
3196
|
-
}
|
|
3197
|
-
var INDEX_CACHE = /* @__PURE__ */ new WeakMap();
|
|
3198
|
-
function indexFor(graph) {
|
|
3199
|
-
let idx = INDEX_CACHE.get(graph);
|
|
3200
|
-
if (!idx) {
|
|
3201
|
-
idx = new GraphIndex(graph);
|
|
3202
|
-
INDEX_CACHE.set(graph, idx);
|
|
3203
|
-
}
|
|
3204
|
-
return idx;
|
|
3205
|
-
}
|
|
3206
|
-
|
|
3207
|
-
// src/engine/query.ts
|
|
3208
|
-
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
3209
|
-
"the",
|
|
3210
|
-
"a",
|
|
3211
|
-
"an",
|
|
3212
|
-
"is",
|
|
3213
|
-
"are",
|
|
3214
|
-
"was",
|
|
3215
|
-
"were",
|
|
3216
|
-
"be",
|
|
3217
|
-
"to",
|
|
3218
|
-
"of",
|
|
3219
|
-
"in",
|
|
3220
|
-
"on",
|
|
3221
|
-
"for",
|
|
3222
|
-
"and",
|
|
3223
|
-
"or",
|
|
3224
|
-
"where",
|
|
3225
|
-
"what",
|
|
3226
|
-
"which",
|
|
3227
|
-
"how",
|
|
3228
|
-
"do",
|
|
3229
|
-
"does",
|
|
3230
|
-
"did",
|
|
3231
|
-
"i",
|
|
3232
|
-
"we",
|
|
3233
|
-
"it",
|
|
3234
|
-
"this",
|
|
3235
|
-
"that",
|
|
3236
|
-
"with",
|
|
3237
|
-
"when",
|
|
3238
|
-
"who",
|
|
3239
|
-
"why",
|
|
3240
|
-
"can",
|
|
3241
|
-
"should",
|
|
3242
|
-
"my",
|
|
3243
|
-
"our",
|
|
3244
|
-
"you",
|
|
3245
|
-
"your",
|
|
3246
|
-
"from",
|
|
3247
|
-
"by",
|
|
3248
|
-
"at",
|
|
3249
|
-
"as",
|
|
3250
|
-
// Discovery-question scaffolding: words a caller uses to FRAME the ask
|
|
3251
|
-
// ("find the code responsible for X", "I need to modify X, where do I
|
|
3252
|
-
// start?", "explain how X works in this codebase") rather than to name the
|
|
3253
|
-
// target. Left in, these compete on equal footing with the real identifier
|
|
3254
|
-
// terms and can outrank it outright — e.g. "find the code responsible for
|
|
3255
|
-
// deleteAsync" let "find" alone drag in every FindByIdAsync/FindAll method
|
|
3256
|
-
// in the repo, none of them the target (VG-LOCATE-FAILURE-ANALYSIS.md).
|
|
3257
|
-
"find",
|
|
3258
|
-
"code",
|
|
3259
|
-
"responsible",
|
|
3260
|
-
"need",
|
|
3261
|
-
"modify",
|
|
3262
|
-
"me",
|
|
3263
|
-
"implementation",
|
|
3264
|
-
"explain",
|
|
3265
|
-
"works",
|
|
3266
|
-
"codebase",
|
|
3267
|
-
"contains",
|
|
3268
|
-
"file"
|
|
3269
|
-
]);
|
|
3270
|
-
function queryGraph(graph, question, options = {}) {
|
|
3271
|
-
const budget = options.budget ?? 2e3;
|
|
3272
|
-
const limit = options.limit ?? 12;
|
|
3273
|
-
const terms = tokenize(question);
|
|
3274
|
-
const weightOf = termWeights(graph, terms);
|
|
3275
|
-
const index = indexFor(graph);
|
|
3276
|
-
const scored = [];
|
|
3277
|
-
for (const node of graph.nodes) {
|
|
3278
|
-
if (node.kind === "file" || node.kind === "external") continue;
|
|
3279
|
-
const { score, why } = scoreNode(node, terms, weightOf);
|
|
3280
|
-
if (score > 0) scored.push({ node, score: round2(score * (1 + IMPORTANCE_WEIGHT * node.importance)), why });
|
|
3281
|
-
}
|
|
3282
|
-
scored.sort((a, b) => b.score - a.score || a.node.qualifiedName.localeCompare(b.node.qualifiedName));
|
|
3283
|
-
const seeds = scored.slice(0, limit);
|
|
3284
|
-
const { context, tokensEstimate } = buildContext(graph, index, question, seeds, budget);
|
|
3285
|
-
return { question, matches: seeds, context, tokensEstimate };
|
|
3286
|
-
}
|
|
3287
|
-
async function queryGraphSemantic(graph, question, options) {
|
|
3288
|
-
const budget = options.budget ?? 2e3;
|
|
3289
|
-
const limit = options.limit ?? 12;
|
|
3290
|
-
const terms = tokenize(question);
|
|
3291
|
-
const weightOf = termWeights(graph, terms);
|
|
3292
|
-
const index = indexFor(graph);
|
|
3293
|
-
const queryVec = await options.embedder.embedQuery(question);
|
|
3294
|
-
const lexRaw = /* @__PURE__ */ new Map();
|
|
3295
|
-
let lexMax = 0;
|
|
3296
|
-
const whyById = /* @__PURE__ */ new Map();
|
|
3297
|
-
for (const node of graph.nodes) {
|
|
3298
|
-
if (node.kind === "file" || node.kind === "external") continue;
|
|
3299
|
-
const { score, why } = scoreNode(node, terms, weightOf);
|
|
3300
|
-
lexRaw.set(node.id, score);
|
|
3301
|
-
whyById.set(node.id, why);
|
|
3302
|
-
if (score > lexMax) lexMax = score;
|
|
3303
|
-
}
|
|
3304
|
-
const scored = [];
|
|
3305
|
-
for (const node of graph.nodes) {
|
|
3306
|
-
if (node.kind === "file" || node.kind === "external") continue;
|
|
3307
|
-
const lexNorm = lexMax > 0 ? (lexRaw.get(node.id) ?? 0) / lexMax : 0;
|
|
3308
|
-
const vec = options.nodeVectors.get(node.id);
|
|
3309
|
-
const sem = vec ? Math.max(0, cosine(queryVec, vec)) : 0;
|
|
3310
|
-
const hybrid = 0.5 * lexNorm + 0.5 * sem;
|
|
3311
|
-
if (hybrid <= 0) continue;
|
|
3312
|
-
const lexWhy = whyById.get(node.id);
|
|
3313
|
-
const why = lexWhy || (sem > 0.3 ? `semantic match (${sem.toFixed(2)})` : "weak match");
|
|
3314
|
-
scored.push({ node, score: round2(hybrid * (1 + IMPORTANCE_WEIGHT * node.importance)), why });
|
|
3315
|
-
}
|
|
3316
|
-
scored.sort((a, b) => b.score - a.score || a.node.qualifiedName.localeCompare(b.node.qualifiedName));
|
|
3317
|
-
const seeds = scored.slice(0, limit);
|
|
3318
|
-
const { context, tokensEstimate } = buildContext(graph, index, question, seeds, budget);
|
|
3319
|
-
return { question, matches: seeds, context, tokensEstimate };
|
|
3320
|
-
}
|
|
3321
|
-
function tokenize(q2) {
|
|
3322
|
-
return [
|
|
3323
|
-
...new Set(
|
|
3324
|
-
q2.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 1 && !STOPWORDS.has(t))
|
|
3325
|
-
)
|
|
3326
|
-
];
|
|
3327
|
-
}
|
|
3328
|
-
function scoreNode(node, terms, weightOf = () => 1) {
|
|
3329
|
-
let score = 0;
|
|
3330
|
-
const hits = [];
|
|
3331
|
-
const name = node.name.toLowerCase();
|
|
3332
|
-
const qn = node.qualifiedName.toLowerCase();
|
|
3333
|
-
const file = node.file.toLowerCase();
|
|
3334
|
-
const nameParts = identifierParts(node.name);
|
|
3335
|
-
for (const t of terms) {
|
|
3336
|
-
const w = weightOf(t);
|
|
3337
|
-
if (name === t) {
|
|
3338
|
-
score += 10 * w;
|
|
3339
|
-
hits.push(t);
|
|
3340
|
-
} else if (nameParts.has(t)) {
|
|
3341
|
-
score += 6 * w;
|
|
3342
|
-
hits.push(t);
|
|
3343
|
-
} else if (name.includes(t)) {
|
|
3344
|
-
score += 4 * w;
|
|
3345
|
-
hits.push(t);
|
|
3346
|
-
} else if (qn.includes(t)) {
|
|
3347
|
-
score += 3 * w;
|
|
3348
|
-
hits.push(t);
|
|
3349
|
-
} else if (fuzzyPartMatch(t, nameParts)) {
|
|
3350
|
-
score += 2 * w;
|
|
3351
|
-
hits.push(`~${t}`);
|
|
3352
|
-
} else if (file.includes(t)) {
|
|
3353
|
-
score += 1 * w;
|
|
3354
|
-
hits.push(t);
|
|
3355
|
-
}
|
|
3356
|
-
}
|
|
3357
|
-
return { score, why: hits.length ? `matched: ${hits.join(", ")}` : "" };
|
|
3358
|
-
}
|
|
3359
|
-
var IMPORTANCE_WEIGHT = 0.4;
|
|
3360
|
-
function termWeights(graph, terms) {
|
|
3361
|
-
if (terms.length === 0) return () => 1;
|
|
3362
|
-
const df = /* @__PURE__ */ new Map();
|
|
3363
|
-
let n = 0;
|
|
3364
|
-
for (const node of graph.nodes) {
|
|
3365
|
-
if (node.kind === "file" || node.kind === "external") continue;
|
|
3366
|
-
n++;
|
|
3367
|
-
const name = node.name.toLowerCase();
|
|
3368
|
-
const parts = identifierParts(node.name);
|
|
3369
|
-
for (const t of terms) {
|
|
3370
|
-
if (parts.has(t) || name.includes(t)) df.set(t, (df.get(t) ?? 0) + 1);
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
const w = /* @__PURE__ */ new Map();
|
|
3374
|
-
for (const t of terms) {
|
|
3375
|
-
const idf = Math.log((n + 1) / ((df.get(t) ?? 0) + 1)) + 1;
|
|
3376
|
-
w.set(t, Math.max(0.5, Math.min(8, idf)));
|
|
3377
|
-
}
|
|
3378
|
-
return (t) => w.get(t) ?? 1;
|
|
3379
|
-
}
|
|
3380
|
-
function identifierParts(name) {
|
|
3381
|
-
return new Set(
|
|
3382
|
-
name.split(/[^\p{L}\p{N}]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/u).filter(Boolean).map((s) => s.toLowerCase())
|
|
3383
|
-
);
|
|
3384
|
-
}
|
|
3385
|
-
function fuzzyPartMatch(term, parts) {
|
|
3386
|
-
for (const part of parts) {
|
|
3387
|
-
const shared = sharedPrefixLen(term, part);
|
|
3388
|
-
if (shared >= 5 && shared >= 0.6 * Math.min(term.length, part.length)) return true;
|
|
3389
|
-
}
|
|
3390
|
-
return false;
|
|
3391
|
-
}
|
|
3392
|
-
function sharedPrefixLen(a, b) {
|
|
3393
|
-
const n = Math.min(a.length, b.length);
|
|
3394
|
-
let i = 0;
|
|
3395
|
-
while (i < n && a[i] === b[i]) i++;
|
|
3396
|
-
return i;
|
|
3397
|
-
}
|
|
3398
|
-
function buildContext(graph, index, question, seeds, budget) {
|
|
3399
|
-
const lines = [];
|
|
3400
|
-
lines.push(`# Context for: ${question}`);
|
|
3401
|
-
lines.push("");
|
|
3402
|
-
if (seeds.length === 0) {
|
|
3403
|
-
lines.push("_No matching symbols found in the map. Try different terms, or `vg hubs` for the most important code._");
|
|
3404
|
-
const text2 = lines.join("\n");
|
|
3405
|
-
return { context: text2, tokensEstimate: estimateTokens(text2) };
|
|
3406
|
-
}
|
|
3407
|
-
for (const { node, why } of seeds) {
|
|
3408
|
-
const block = [];
|
|
3409
|
-
block.push(`## ${node.qualifiedName} (${node.kind}, ${node.file}:${node.span.start})`);
|
|
3410
|
-
if (node.signature) block.push("`" + node.signature + "`");
|
|
3411
|
-
const callees = index.callees(node.id).map((x) => x.node.qualifiedName);
|
|
3412
|
-
const callers = index.callers(node.id).map((x) => x.node.qualifiedName);
|
|
3413
|
-
if (callees.length) block.push(`calls: ${unique(callees).slice(0, 8).join(", ")}`);
|
|
3414
|
-
if (callers.length) block.push(`called by: ${unique(callers).slice(0, 8).join(", ")}`);
|
|
3415
|
-
block.push(`importance ${node.importance.toFixed(3)} \xB7 area #${node.area}${node.isHub ? " \xB7 hub" : ""} \xB7 ${why}`);
|
|
3416
|
-
block.push("");
|
|
3417
|
-
const candidate = lines.concat(block).join("\n");
|
|
3418
|
-
if (estimateTokens(candidate) > budget && lines.length > 2) break;
|
|
3419
|
-
lines.push(...block);
|
|
3420
|
-
}
|
|
3421
|
-
const text = lines.join("\n");
|
|
3422
|
-
return { context: text, tokensEstimate: estimateTokens(text) };
|
|
3423
|
-
}
|
|
3424
|
-
function unique(xs) {
|
|
3425
|
-
return [...new Set(xs)];
|
|
3426
|
-
}
|
|
3427
|
-
function estimateTokens(text) {
|
|
3428
|
-
return Math.ceil(text.length / 4);
|
|
3429
|
-
}
|
|
3430
|
-
function round2(x) {
|
|
3431
|
-
return Math.round(x * 1e6) / 1e6;
|
|
3432
|
-
}
|
|
3433
|
-
|
|
3434
|
-
// src/engine/lookup.ts
|
|
3435
|
-
function findNodes(graph, query) {
|
|
3436
|
-
const q2 = query.trim();
|
|
3437
|
-
if (!q2) return [];
|
|
3438
|
-
const byId = graph.nodes.filter((n) => n.id === q2);
|
|
3439
|
-
if (byId.length) return byId;
|
|
3440
|
-
const fileLine = /^(.*):(\d+)$/.exec(q2);
|
|
3441
|
-
if (fileLine) {
|
|
3442
|
-
const file = fileLine[1];
|
|
3443
|
-
const line = Number(fileLine[2]);
|
|
3444
|
-
const spanning = graph.nodes.filter((n) => n.file === file && n.span.start <= line && n.span.end >= line && n.kind !== "file").sort((a, b) => a.span.end - a.span.start - (b.span.end - b.span.start));
|
|
3445
|
-
if (spanning.length) return spanning;
|
|
3446
|
-
}
|
|
3447
|
-
if (/[*?]/.test(q2)) {
|
|
3448
|
-
const re = globToRegExp(q2);
|
|
3449
|
-
return rank(graph.nodes.filter((n) => re.test(n.qualifiedName) || re.test(n.name) || re.test(n.file)));
|
|
3450
|
-
}
|
|
3451
|
-
const byQn = graph.nodes.filter((n) => n.qualifiedName === q2);
|
|
3452
|
-
if (byQn.length) return rank(byQn);
|
|
3453
|
-
const byName = graph.nodes.filter((n) => n.name === q2);
|
|
3454
|
-
if (byName.length) return rank(byName);
|
|
3455
|
-
const lower = q2.toLowerCase();
|
|
3456
|
-
const ci = graph.nodes.filter(
|
|
3457
|
-
(n) => n.qualifiedName.toLowerCase() === lower || n.name.toLowerCase() === lower
|
|
3458
|
-
);
|
|
3459
|
-
if (ci.length) return rank(ci);
|
|
3460
|
-
return rank(graph.nodes.filter((n) => n.qualifiedName.toLowerCase().includes(lower))).slice(0, 25);
|
|
3461
|
-
}
|
|
3462
|
-
function resolveOne(graph, query, pick) {
|
|
3463
|
-
const candidates = findNodes(graph, query);
|
|
3464
|
-
if (candidates.length === 0) return { candidates };
|
|
3465
|
-
if (pick && pick >= 1 && pick <= candidates.length) return { node: candidates[pick - 1], candidates };
|
|
3466
|
-
if (candidates.length === 1) return { node: candidates[0], candidates };
|
|
3467
|
-
return { candidates };
|
|
3468
|
-
}
|
|
3469
|
-
function nodeById(graph, id) {
|
|
3470
|
-
return graph.nodes.find((n) => n.id === id);
|
|
3471
|
-
}
|
|
3472
|
-
function rank(nodes) {
|
|
3473
|
-
return [...nodes].sort(
|
|
3474
|
-
(a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)
|
|
3475
|
-
);
|
|
3476
|
-
}
|
|
3477
|
-
function globToRegExp(glob) {
|
|
3478
|
-
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
3479
|
-
return new RegExp(`^${escaped}$`, "i");
|
|
3480
|
-
}
|
|
3481
2732
|
function buildGraphologyGraph(nodes, edges) {
|
|
3482
2733
|
const g = new Graph({ type: "directed", multi: true, allowSelfLoops: true });
|
|
3483
2734
|
for (const n of nodes) {
|
|
@@ -3501,57 +2752,6 @@ function shortestPath(graph, srcId, dstId) {
|
|
|
3501
2752
|
if (reverse) return { ids: reverse.slice().reverse(), direction: "reverse" };
|
|
3502
2753
|
return null;
|
|
3503
2754
|
}
|
|
3504
|
-
|
|
3505
|
-
// src/engine/impact.ts
|
|
3506
|
-
var DEPEND_EDGES = /* @__PURE__ */ new Set(["call", "import", "extends", "implements", "references"]);
|
|
3507
|
-
function impactOf(graph, rootId, opts = {}) {
|
|
3508
|
-
const maxDepth = Math.max(1, opts.depth ?? 4);
|
|
3509
|
-
const index = indexFor(graph);
|
|
3510
|
-
const root = index.node(rootId);
|
|
3511
|
-
const affected = /* @__PURE__ */ new Map();
|
|
3512
|
-
let minEdgeConfidence = 1;
|
|
3513
|
-
let frontier = [{ id: rootId, conf: 1 }];
|
|
3514
|
-
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
3515
|
-
const nextFrontier = [];
|
|
3516
|
-
for (const { id, conf } of frontier) {
|
|
3517
|
-
for (const e of index.in(id)) {
|
|
3518
|
-
if (!DEPEND_EDGES.has(e.kind)) continue;
|
|
3519
|
-
minEdgeConfidence = Math.min(minEdgeConfidence, e.confidence);
|
|
3520
|
-
const dependentId = e.src;
|
|
3521
|
-
if (dependentId === rootId || affected.has(dependentId)) continue;
|
|
3522
|
-
const node = index.node(dependentId);
|
|
3523
|
-
if (!node) continue;
|
|
3524
|
-
const conf2 = round3(conf * e.confidence * 0.9);
|
|
3525
|
-
affected.set(dependentId, {
|
|
3526
|
-
id: node.id,
|
|
3527
|
-
name: node.qualifiedName,
|
|
3528
|
-
kind: node.kind,
|
|
3529
|
-
file: node.file,
|
|
3530
|
-
line: node.span.start,
|
|
3531
|
-
depth,
|
|
3532
|
-
confidence: conf2
|
|
3533
|
-
});
|
|
3534
|
-
nextFrontier.push({ id: dependentId, conf: conf2 });
|
|
3535
|
-
}
|
|
3536
|
-
}
|
|
3537
|
-
frontier = nextFrontier;
|
|
3538
|
-
if (frontier.length === 0) break;
|
|
3539
|
-
}
|
|
3540
|
-
const items = [...affected.values()].sort(
|
|
3541
|
-
(a, b) => a.depth - b.depth || b.confidence - a.confidence || a.name.localeCompare(b.name)
|
|
3542
|
-
);
|
|
3543
|
-
return {
|
|
3544
|
-
root: { id: rootId, name: root?.qualifiedName ?? rootId },
|
|
3545
|
-
depth: maxDepth,
|
|
3546
|
-
affected: items,
|
|
3547
|
-
direct: items.filter((i) => i.depth === 1).length,
|
|
3548
|
-
transitive: items.filter((i) => i.depth > 1).length,
|
|
3549
|
-
minEdgeConfidence: round3(minEdgeConfidence)
|
|
3550
|
-
};
|
|
3551
|
-
}
|
|
3552
|
-
function round3(x) {
|
|
3553
|
-
return Math.round(x * 1e6) / 1e6;
|
|
3554
|
-
}
|
|
3555
2755
|
function coveringTests(graph, node, index) {
|
|
3556
2756
|
const idx = index ?? indexFor(graph);
|
|
3557
2757
|
const byId = idx.nodeById;
|
|
@@ -3587,10 +2787,10 @@ function testsToRun(graph, rootId, depth = 4) {
|
|
|
3587
2787
|
};
|
|
3588
2788
|
}
|
|
3589
2789
|
function detectRunner(root, lang) {
|
|
3590
|
-
const pkgPath =
|
|
3591
|
-
if (
|
|
2790
|
+
const pkgPath = path2.join(root, "package.json");
|
|
2791
|
+
if (fs18.existsSync(pkgPath)) {
|
|
3592
2792
|
try {
|
|
3593
|
-
const pkg = JSON.parse(
|
|
2793
|
+
const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
|
|
3594
2794
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3595
2795
|
if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
|
|
3596
2796
|
if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
|
|
@@ -3598,10 +2798,10 @@ function detectRunner(root, lang) {
|
|
|
3598
2798
|
} catch {
|
|
3599
2799
|
}
|
|
3600
2800
|
}
|
|
3601
|
-
if (lang === "py" ||
|
|
2801
|
+
if (lang === "py" || fs18.existsSync(path2.join(root, "pyproject.toml")) || fs18.existsSync(path2.join(root, "pytest.ini"))) {
|
|
3602
2802
|
return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
|
|
3603
2803
|
}
|
|
3604
|
-
if (lang === "go" ||
|
|
2804
|
+
if (lang === "go" || fs18.existsSync(path2.join(root, "go.mod"))) {
|
|
3605
2805
|
return { name: "go test", command: () => "go test ./..." };
|
|
3606
2806
|
}
|
|
3607
2807
|
if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
|
|
@@ -3610,469 +2810,12 @@ function detectRunner(root, lang) {
|
|
|
3610
2810
|
if (lang === "rb") return { name: "rspec", command: (f) => `bundle exec rspec ${f.join(" ")}`.trim() };
|
|
3611
2811
|
return { name: "tests", command: (f) => f.length ? f.join(" ") : "(no runner detected)" };
|
|
3612
2812
|
}
|
|
3613
|
-
function hasFile(root, re) {
|
|
3614
|
-
try {
|
|
3615
|
-
return
|
|
3616
|
-
} catch {
|
|
3617
|
-
return false;
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
var ECOSYSTEMS = ["npm", "pypi", "go", "rust", "ruby", "php", "dotnet", "swift", "dart", "java"];
|
|
3621
|
-
function inventory(root) {
|
|
3622
|
-
const manifests = findManifests(root);
|
|
3623
|
-
const records = [];
|
|
3624
|
-
records.push(...npmDeps(manifests.npm));
|
|
3625
|
-
records.push(...pypiDeps(manifests.pypi));
|
|
3626
|
-
records.push(...goDeps(manifests.go));
|
|
3627
|
-
records.push(...cargoDeps(manifests.rust));
|
|
3628
|
-
records.push(...rubyDeps(manifests.ruby));
|
|
3629
|
-
records.push(...phpDeps(manifests.php));
|
|
3630
|
-
records.push(...dotnetDeps(manifests.dotnet));
|
|
3631
|
-
records.push(...swiftDeps(manifests.swift));
|
|
3632
|
-
records.push(...dartDeps(manifests.dart));
|
|
3633
|
-
records.push(...javaDeps(manifests.java));
|
|
3634
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3635
|
-
const deduped = records.filter((r) => {
|
|
3636
|
-
const key = `${r.ecosystem}\0${r.name}`;
|
|
3637
|
-
if (seen.has(key)) return false;
|
|
3638
|
-
seen.add(key);
|
|
3639
|
-
return true;
|
|
3640
|
-
});
|
|
3641
|
-
deduped.sort((a, b) => a.ecosystem.localeCompare(b.ecosystem) || a.name.localeCompare(b.name));
|
|
3642
|
-
const counts = { total: deduped.length };
|
|
3643
|
-
for (const e of ECOSYSTEMS) counts[e] = 0;
|
|
3644
|
-
for (const r of deduped) counts[r.ecosystem]++;
|
|
3645
|
-
return { records: deduped, counts };
|
|
3646
|
-
}
|
|
3647
|
-
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
3648
|
-
"node_modules",
|
|
3649
|
-
".git",
|
|
3650
|
-
"dist",
|
|
3651
|
-
"build",
|
|
3652
|
-
"out",
|
|
3653
|
-
".vibgrate",
|
|
3654
|
-
"vendor",
|
|
3655
|
-
".venv",
|
|
3656
|
-
"venv",
|
|
3657
|
-
"env",
|
|
3658
|
-
"__pycache__",
|
|
3659
|
-
"target",
|
|
3660
|
-
".next",
|
|
3661
|
-
".nuxt",
|
|
3662
|
-
"coverage",
|
|
3663
|
-
".cache"
|
|
3664
|
-
]);
|
|
3665
|
-
var MANIFEST_BY_FILE = {
|
|
3666
|
-
"package.json": "npm",
|
|
3667
|
-
"requirements.txt": "pypi",
|
|
3668
|
-
"pyproject.toml": "pypi",
|
|
3669
|
-
"go.mod": "go",
|
|
3670
|
-
"Cargo.toml": "rust",
|
|
3671
|
-
Gemfile: "ruby",
|
|
3672
|
-
"composer.json": "php",
|
|
3673
|
-
"Package.swift": "swift",
|
|
3674
|
-
"pubspec.yaml": "dart",
|
|
3675
|
-
"pom.xml": "java",
|
|
3676
|
-
"build.gradle": "java",
|
|
3677
|
-
"build.gradle.kts": "java"
|
|
3678
|
-
};
|
|
3679
|
-
function findManifests(root) {
|
|
3680
|
-
const set = Object.fromEntries(ECOSYSTEMS.map((e) => [e, []]));
|
|
3681
|
-
const MAX_ENTRIES2 = 2e4;
|
|
3682
|
-
let scanned = 0;
|
|
3683
|
-
const walk2 = (dir, depth) => {
|
|
3684
|
-
if (depth > 8 || scanned > MAX_ENTRIES2) return;
|
|
3685
|
-
let entries;
|
|
3686
|
-
try {
|
|
3687
|
-
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
3688
|
-
} catch {
|
|
3689
|
-
return;
|
|
3690
|
-
}
|
|
3691
|
-
for (const e of entries) {
|
|
3692
|
-
scanned++;
|
|
3693
|
-
if (scanned > MAX_ENTRIES2) break;
|
|
3694
|
-
if (e.isDirectory()) {
|
|
3695
|
-
if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path17.join(dir, e.name), depth + 1);
|
|
3696
|
-
continue;
|
|
3697
|
-
}
|
|
3698
|
-
const eco = MANIFEST_BY_FILE[e.name] ?? (/\.(cs|fs)proj$/i.test(e.name) ? "dotnet" : void 0);
|
|
3699
|
-
if (eco) set[eco].push(path17.join(dir, e.name));
|
|
3700
|
-
}
|
|
3701
|
-
};
|
|
3702
|
-
walk2(root, 0);
|
|
3703
|
-
for (const e of ECOSYSTEMS) set[e].sort();
|
|
3704
|
-
return set;
|
|
3705
|
-
}
|
|
3706
|
-
function npmDeps(files) {
|
|
3707
|
-
const out = [];
|
|
3708
|
-
for (const file of files) {
|
|
3709
|
-
let pkg;
|
|
3710
|
-
try {
|
|
3711
|
-
pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
3712
|
-
} catch {
|
|
3713
|
-
continue;
|
|
3714
|
-
}
|
|
3715
|
-
const dir = path17.dirname(file);
|
|
3716
|
-
const all = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3717
|
-
for (const [name, declared] of Object.entries(all)) {
|
|
3718
|
-
out.push({ name, ecosystem: "npm", declared, installed: installedNpmVersion(dir, name) });
|
|
3719
|
-
}
|
|
3720
|
-
}
|
|
3721
|
-
return out;
|
|
3722
|
-
}
|
|
3723
|
-
function installedNpmVersion(dir, name) {
|
|
3724
|
-
let cur = dir;
|
|
3725
|
-
for (let i = 0; i < 12; i++) {
|
|
3726
|
-
const p = path17.join(cur, "node_modules", name, "package.json");
|
|
3727
|
-
try {
|
|
3728
|
-
return JSON.parse(fs17.readFileSync(p, "utf8")).version;
|
|
3729
|
-
} catch {
|
|
3730
|
-
}
|
|
3731
|
-
const parent = path17.dirname(cur);
|
|
3732
|
-
if (parent === cur) break;
|
|
3733
|
-
cur = parent;
|
|
3734
|
-
}
|
|
3735
|
-
return void 0;
|
|
3736
|
-
}
|
|
3737
|
-
function installedTreeVersion(root, ecosystem, name) {
|
|
3738
|
-
if (ecosystem === "pypi") return installedPypiVersion(root, name);
|
|
3739
|
-
if (ecosystem === "php") return installedPhpVersion(root, name);
|
|
3740
|
-
return void 0;
|
|
3741
|
-
}
|
|
3742
|
-
function normalizePep503(name) {
|
|
3743
|
-
return name.trim().toLowerCase().replace(/[-_.]+/g, "-");
|
|
3744
|
-
}
|
|
3745
|
-
function sitePackagesDirs(base) {
|
|
3746
|
-
const out = [];
|
|
3747
|
-
try {
|
|
3748
|
-
for (const d of fs17.readdirSync(path17.join(base, "lib"))) {
|
|
3749
|
-
if (d.startsWith("python")) out.push(path17.join(base, "lib", d, "site-packages"));
|
|
3750
|
-
}
|
|
3751
|
-
} catch {
|
|
3752
|
-
}
|
|
3753
|
-
out.push(path17.join(base, "Lib", "site-packages"));
|
|
3754
|
-
return out;
|
|
3755
|
-
}
|
|
3756
|
-
function installedPypiVersion(root, name) {
|
|
3757
|
-
const target = normalizePep503(name);
|
|
3758
|
-
for (const venv of [".venv", "venv", "env", ".tox"]) {
|
|
3759
|
-
for (const sp of sitePackagesDirs(path17.join(root, venv))) {
|
|
3760
|
-
let entries;
|
|
3761
|
-
try {
|
|
3762
|
-
entries = fs17.readdirSync(sp);
|
|
3763
|
-
} catch {
|
|
3764
|
-
continue;
|
|
3765
|
-
}
|
|
3766
|
-
for (const entry of entries) {
|
|
3767
|
-
const m = /^(.+?)-(\d[^-]*)\.dist-info$/.exec(entry);
|
|
3768
|
-
if (m && normalizePep503(m[1]) === target) return m[2];
|
|
3769
|
-
}
|
|
3770
|
-
}
|
|
3771
|
-
}
|
|
3772
|
-
return void 0;
|
|
3773
|
-
}
|
|
3774
|
-
function installedPhpVersion(root, name) {
|
|
3775
|
-
let data;
|
|
3776
|
-
try {
|
|
3777
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "vendor", "composer", "installed.json"), "utf8"));
|
|
3778
|
-
} catch {
|
|
3779
|
-
return void 0;
|
|
3780
|
-
}
|
|
3781
|
-
const pkgs = Array.isArray(data) ? data : data.packages;
|
|
3782
|
-
if (!Array.isArray(pkgs)) return void 0;
|
|
3783
|
-
const p = pkgs.find((x) => x && typeof x === "object" && x.name === name);
|
|
3784
|
-
return typeof p?.version === "string" ? p.version.replace(/^v/, "") : void 0;
|
|
3785
|
-
}
|
|
3786
|
-
function pypiDeps(files) {
|
|
3787
|
-
const byName = /* @__PURE__ */ new Map();
|
|
3788
|
-
const add3 = (name, declared) => {
|
|
3789
|
-
const clean = name.trim().replace(/\[.*\]$/, "");
|
|
3790
|
-
if (!clean || clean.toLowerCase() === "python") return;
|
|
3791
|
-
if (!byName.has(clean)) byName.set(clean, { name: clean, ecosystem: "pypi", declared: declared.trim() || "*" });
|
|
3792
|
-
};
|
|
3793
|
-
for (const file of files) {
|
|
3794
|
-
let text;
|
|
3795
|
-
try {
|
|
3796
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3797
|
-
} catch {
|
|
3798
|
-
continue;
|
|
3799
|
-
}
|
|
3800
|
-
if (path17.basename(file) === "requirements.txt") {
|
|
3801
|
-
for (const line of text.split("\n")) {
|
|
3802
|
-
const t = line.trim();
|
|
3803
|
-
if (!t || t.startsWith("#") || t.startsWith("-")) continue;
|
|
3804
|
-
const m = /^([A-Za-z0-9._-]+(?:\[[^\]]*\])?)\s*([<>=!~).*]+.*)?$/.exec(t);
|
|
3805
|
-
if (m) add3(m[1], m[2] ?? "*");
|
|
3806
|
-
}
|
|
3807
|
-
} else {
|
|
3808
|
-
for (const dep of pep621Dependencies(text)) {
|
|
3809
|
-
const m = /^([A-Za-z0-9._-]+(?:\[[^\]]*\])?)\s*(.*)$/.exec(dep.trim());
|
|
3810
|
-
if (m) add3(m[1], m[2] ?? "*");
|
|
3811
|
-
}
|
|
3812
|
-
for (const [name, spec] of poetryDependencies(text)) add3(name, spec);
|
|
3813
|
-
}
|
|
3814
|
-
}
|
|
3815
|
-
return [...byName.values()];
|
|
3816
|
-
}
|
|
3817
|
-
function pep621Dependencies(text) {
|
|
3818
|
-
const section = sectionBody(text, "project");
|
|
3819
|
-
if (!section) return [];
|
|
3820
|
-
const start = /dependencies\s*=\s*\[/.exec(section);
|
|
3821
|
-
if (!start) return [];
|
|
3822
|
-
let depth = 0;
|
|
3823
|
-
let inStr = false;
|
|
3824
|
-
let strCh = "";
|
|
3825
|
-
const out = [];
|
|
3826
|
-
let cur = "";
|
|
3827
|
-
for (let i = start.index + start[0].length - 1; i < section.length; i++) {
|
|
3828
|
-
const ch = section[i];
|
|
3829
|
-
if (inStr) {
|
|
3830
|
-
if (ch === strCh) {
|
|
3831
|
-
out.push(cur);
|
|
3832
|
-
inStr = false;
|
|
3833
|
-
} else cur += ch;
|
|
3834
|
-
continue;
|
|
3835
|
-
}
|
|
3836
|
-
if (ch === '"' || ch === "'") {
|
|
3837
|
-
inStr = true;
|
|
3838
|
-
strCh = ch;
|
|
3839
|
-
cur = "";
|
|
3840
|
-
} else if (ch === "[") depth++;
|
|
3841
|
-
else if (ch === "]") {
|
|
3842
|
-
depth--;
|
|
3843
|
-
if (depth === 0) break;
|
|
3844
|
-
}
|
|
3845
|
-
}
|
|
3846
|
-
return out;
|
|
3847
|
-
}
|
|
3848
|
-
function poetryDependencies(text) {
|
|
3849
|
-
const out = [];
|
|
3850
|
-
const lines = text.split("\n");
|
|
3851
|
-
let inDeps = false;
|
|
3852
|
-
for (const raw of lines) {
|
|
3853
|
-
const line = raw.trim();
|
|
3854
|
-
if (line.startsWith("[")) {
|
|
3855
|
-
inDeps = /^\[tool\.poetry(\.group\.[^.\]]+)?\.dependencies\]$/.test(line);
|
|
3856
|
-
continue;
|
|
3857
|
-
}
|
|
3858
|
-
if (!inDeps || !line || line.startsWith("#")) continue;
|
|
3859
|
-
const m = /^([A-Za-z0-9._-]+)\s*=\s*["']([^"']*)["']/.exec(line);
|
|
3860
|
-
if (m) out.push([m[1], m[2] || "*"]);
|
|
3861
|
-
}
|
|
3862
|
-
return out;
|
|
3863
|
-
}
|
|
3864
|
-
function sectionBody(text, name) {
|
|
3865
|
-
const lines = text.split("\n");
|
|
3866
|
-
let capturing = false;
|
|
3867
|
-
const body = [];
|
|
3868
|
-
for (const raw of lines) {
|
|
3869
|
-
const line = raw.trim();
|
|
3870
|
-
if (/^\[/.test(line)) {
|
|
3871
|
-
if (capturing) break;
|
|
3872
|
-
capturing = line === `[${name}]`;
|
|
3873
|
-
continue;
|
|
3874
|
-
}
|
|
3875
|
-
if (capturing) body.push(raw);
|
|
3876
|
-
}
|
|
3877
|
-
return capturing || body.length ? body.join("\n") : null;
|
|
3878
|
-
}
|
|
3879
|
-
function goDeps(files) {
|
|
3880
|
-
const out = [];
|
|
3881
|
-
for (const mod of files) {
|
|
3882
|
-
let text;
|
|
3883
|
-
try {
|
|
3884
|
-
text = fs17.readFileSync(mod, "utf8");
|
|
3885
|
-
} catch {
|
|
3886
|
-
continue;
|
|
3887
|
-
}
|
|
3888
|
-
const re = /^\s*(?:require\s+)?([\w./-]+)\s+v(\S+)/gm;
|
|
3889
|
-
let m;
|
|
3890
|
-
while (m = re.exec(text)) {
|
|
3891
|
-
if (m[1] === "go" || m[1] === "require" || m[1].startsWith("module")) continue;
|
|
3892
|
-
out.push({ name: m[1], ecosystem: "go", declared: `v${m[2]}` });
|
|
3893
|
-
}
|
|
3894
|
-
}
|
|
3895
|
-
return out;
|
|
3896
|
-
}
|
|
3897
|
-
function cargoDeps(files) {
|
|
3898
|
-
const out = [];
|
|
3899
|
-
for (const file of files) {
|
|
3900
|
-
let text;
|
|
3901
|
-
try {
|
|
3902
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3903
|
-
} catch {
|
|
3904
|
-
continue;
|
|
3905
|
-
}
|
|
3906
|
-
for (const table of ["dependencies", "dev-dependencies", "build-dependencies"]) {
|
|
3907
|
-
const body = sectionBody(text, table);
|
|
3908
|
-
if (!body) continue;
|
|
3909
|
-
for (const raw of body.split("\n")) {
|
|
3910
|
-
const line = raw.trim();
|
|
3911
|
-
if (!line || line.startsWith("#")) continue;
|
|
3912
|
-
const m = /^([A-Za-z0-9._-]+)\s*=\s*(.+)$/.exec(line);
|
|
3913
|
-
if (!m) continue;
|
|
3914
|
-
const rhs = m[2].trim();
|
|
3915
|
-
let declared = "*";
|
|
3916
|
-
if (rhs.startsWith('"')) {
|
|
3917
|
-
const sm = /^"([^"]+)"/.exec(rhs);
|
|
3918
|
-
if (sm) declared = sm[1];
|
|
3919
|
-
} else if (rhs.startsWith("{")) {
|
|
3920
|
-
const vm = /version\s*=\s*"([^"]+)"/.exec(rhs);
|
|
3921
|
-
if (vm) declared = vm[1];
|
|
3922
|
-
}
|
|
3923
|
-
out.push({ name: m[1], ecosystem: "rust", declared });
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
3926
|
-
}
|
|
3927
|
-
return out;
|
|
3928
|
-
}
|
|
3929
|
-
function rubyDeps(files) {
|
|
3930
|
-
const out = [];
|
|
3931
|
-
for (const file of files) {
|
|
3932
|
-
let text;
|
|
3933
|
-
try {
|
|
3934
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3935
|
-
} catch {
|
|
3936
|
-
continue;
|
|
3937
|
-
}
|
|
3938
|
-
const re = /^\s*gem\s+["']([^"']+)["']\s*(?:,\s*["']([^"']+)["'])?/gm;
|
|
3939
|
-
let m;
|
|
3940
|
-
while (m = re.exec(text)) out.push({ name: m[1], ecosystem: "ruby", declared: m[2] ?? "*" });
|
|
3941
|
-
}
|
|
3942
|
-
return out;
|
|
3943
|
-
}
|
|
3944
|
-
function phpDeps(files) {
|
|
3945
|
-
const out = [];
|
|
3946
|
-
for (const file of files) {
|
|
3947
|
-
let pkg;
|
|
3948
|
-
try {
|
|
3949
|
-
pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
3950
|
-
} catch {
|
|
3951
|
-
continue;
|
|
3952
|
-
}
|
|
3953
|
-
const all = { ...pkg.require, ...pkg["require-dev"] };
|
|
3954
|
-
for (const [name, declared] of Object.entries(all)) {
|
|
3955
|
-
if (!name.includes("/")) continue;
|
|
3956
|
-
out.push({ name, ecosystem: "php", declared });
|
|
3957
|
-
}
|
|
3958
|
-
}
|
|
3959
|
-
return out;
|
|
3960
|
-
}
|
|
3961
|
-
function dotnetDeps(files) {
|
|
3962
|
-
const out = [];
|
|
3963
|
-
for (const file of files) {
|
|
3964
|
-
let text;
|
|
3965
|
-
try {
|
|
3966
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3967
|
-
} catch {
|
|
3968
|
-
continue;
|
|
3969
|
-
}
|
|
3970
|
-
const tagRe = /<PackageReference\b[^>]*>/gi;
|
|
3971
|
-
let m;
|
|
3972
|
-
while (m = tagRe.exec(text)) {
|
|
3973
|
-
const inc = /Include\s*=\s*"([^"]+)"/i.exec(m[0]);
|
|
3974
|
-
if (!inc) continue;
|
|
3975
|
-
const ver = /Version\s*=\s*"([^"]+)"/i.exec(m[0]);
|
|
3976
|
-
out.push({ name: inc[1], ecosystem: "dotnet", declared: ver?.[1] ?? "*" });
|
|
3977
|
-
}
|
|
3978
|
-
}
|
|
3979
|
-
return out;
|
|
3980
|
-
}
|
|
3981
|
-
function swiftDeps(files) {
|
|
3982
|
-
const out = [];
|
|
3983
|
-
for (const file of files) {
|
|
3984
|
-
let text;
|
|
3985
|
-
try {
|
|
3986
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3987
|
-
} catch {
|
|
3988
|
-
continue;
|
|
3989
|
-
}
|
|
3990
|
-
const re = /\.package\(/g;
|
|
3991
|
-
let m;
|
|
3992
|
-
while (m = re.exec(text)) {
|
|
3993
|
-
const win = text.slice(m.index, m.index + 200);
|
|
3994
|
-
const urlM = /url:\s*"([^"]+)"/.exec(win);
|
|
3995
|
-
if (!urlM) continue;
|
|
3996
|
-
const name = (urlM[1].replace(/\.git$/, "").split("/").filter(Boolean).pop() ?? urlM[1]).toLowerCase();
|
|
3997
|
-
const verM = /"(\d+\.\d+(?:\.\d+)?)"/.exec(win.slice(urlM.index + urlM[0].length));
|
|
3998
|
-
out.push({ name, ecosystem: "swift", declared: verM?.[1] ?? "*" });
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
return out;
|
|
4002
|
-
}
|
|
4003
|
-
function dartDeps(files) {
|
|
4004
|
-
const out = [];
|
|
4005
|
-
for (const file of files) {
|
|
4006
|
-
let text;
|
|
4007
|
-
try {
|
|
4008
|
-
text = fs17.readFileSync(file, "utf8");
|
|
4009
|
-
} catch {
|
|
4010
|
-
continue;
|
|
4011
|
-
}
|
|
4012
|
-
let section = "";
|
|
4013
|
-
for (const raw of text.split("\n")) {
|
|
4014
|
-
const top = /^([a-z_]+):\s*$/.exec(raw);
|
|
4015
|
-
if (top) {
|
|
4016
|
-
section = top[1];
|
|
4017
|
-
continue;
|
|
4018
|
-
}
|
|
4019
|
-
if (section !== "dependencies" && section !== "dev_dependencies") continue;
|
|
4020
|
-
const m = /^ ([A-Za-z0-9_.]+):\s*(\S.*)?$/.exec(raw);
|
|
4021
|
-
if (!m || m[1] === "flutter" || m[1] === "sdk") continue;
|
|
4022
|
-
const declared = (m[2] ?? "").replace(/^["']|["']$/g, "").trim() || "*";
|
|
4023
|
-
out.push({ name: m[1], ecosystem: "dart", declared });
|
|
4024
|
-
}
|
|
4025
|
-
}
|
|
4026
|
-
return out;
|
|
4027
|
-
}
|
|
4028
|
-
function javaDeps(files) {
|
|
4029
|
-
const out = [];
|
|
4030
|
-
for (const file of files) {
|
|
4031
|
-
let text;
|
|
4032
|
-
try {
|
|
4033
|
-
text = fs17.readFileSync(file, "utf8");
|
|
4034
|
-
} catch {
|
|
4035
|
-
continue;
|
|
4036
|
-
}
|
|
4037
|
-
if (path17.basename(file) === "pom.xml") {
|
|
4038
|
-
for (const block of text.match(/<dependency>[\s\S]*?<\/dependency>/g) ?? []) {
|
|
4039
|
-
const g = /<groupId>([^<]+)<\/groupId>/.exec(block);
|
|
4040
|
-
const a = /<artifactId>([^<]+)<\/artifactId>/.exec(block);
|
|
4041
|
-
if (!g || !a) continue;
|
|
4042
|
-
const v = /<version>([^<]+)<\/version>/.exec(block);
|
|
4043
|
-
const declared = v ? v[1].trim().startsWith("${") ? "*" : v[1].trim() : "*";
|
|
4044
|
-
out.push({ name: `${g[1].trim()}:${a[1].trim()}`, ecosystem: "java", declared });
|
|
4045
|
-
}
|
|
4046
|
-
} else {
|
|
4047
|
-
const re = /(?:implementation|api|compileOnly|runtimeOnly|testImplementation|testRuntimeOnly|annotationProcessor)\s*[(]?\s*["']([\w.-]+):([\w.-]+):([^"']+)["']/g;
|
|
4048
|
-
let m;
|
|
4049
|
-
while (m = re.exec(text)) out.push({ name: `${m[1]}:${m[2]}`, ecosystem: "java", declared: m[3] });
|
|
4050
|
-
}
|
|
4051
|
-
}
|
|
4052
|
-
return out;
|
|
4053
|
-
}
|
|
4054
|
-
async function enrichOnline(records, fetchImpl = globalThis.fetch) {
|
|
4055
|
-
for (const r of records) {
|
|
4056
|
-
if (r.ecosystem !== "npm" || !r.installed) continue;
|
|
4057
|
-
try {
|
|
4058
|
-
const res = await fetchImpl(`https://registry.npmjs.org/${encodeURIComponent(r.name)}/latest`);
|
|
4059
|
-
if (!res.ok) continue;
|
|
4060
|
-
const data = await res.json();
|
|
4061
|
-
r.latest = data.version;
|
|
4062
|
-
r.drift = classify(r.installed, r.latest);
|
|
4063
|
-
} catch {
|
|
4064
|
-
r.drift = "unknown";
|
|
4065
|
-
}
|
|
4066
|
-
}
|
|
4067
|
-
}
|
|
4068
|
-
function classify(installed, latest) {
|
|
4069
|
-
if (!latest) return "unknown";
|
|
4070
|
-
const a = installed.split(".").map(Number);
|
|
4071
|
-
const b = latest.split(".").map(Number);
|
|
4072
|
-
if (a[0] !== b[0]) return "major";
|
|
4073
|
-
if (a[1] !== b[1]) return "minor";
|
|
4074
|
-
if (a[2] !== b[2]) return "patch";
|
|
4075
|
-
return "current";
|
|
2813
|
+
function hasFile(root, re) {
|
|
2814
|
+
try {
|
|
2815
|
+
return fs18.readdirSync(root).some((f) => re.test(f));
|
|
2816
|
+
} catch {
|
|
2817
|
+
return false;
|
|
2818
|
+
}
|
|
4076
2819
|
}
|
|
4077
2820
|
function discoverModels(home = os.homedir()) {
|
|
4078
2821
|
return [...ollama(home), ...lmStudio(home), ...looseGguf(home)].sort(
|
|
@@ -4080,10 +2823,10 @@ function discoverModels(home = os.homedir()) {
|
|
|
4080
2823
|
);
|
|
4081
2824
|
}
|
|
4082
2825
|
function ollama(home) {
|
|
4083
|
-
const base =
|
|
2826
|
+
const base = path2.join(home, ".ollama", "models", "manifests");
|
|
4084
2827
|
const out = [];
|
|
4085
2828
|
walk(base, 4, (file) => {
|
|
4086
|
-
const rel2 =
|
|
2829
|
+
const rel2 = path2.relative(base, file).split(path2.sep);
|
|
4087
2830
|
if (rel2.length >= 2) {
|
|
4088
2831
|
const tag = rel2[rel2.length - 1];
|
|
4089
2832
|
const model = rel2[rel2.length - 2];
|
|
@@ -4093,24 +2836,24 @@ function ollama(home) {
|
|
|
4093
2836
|
return dedupe(out);
|
|
4094
2837
|
}
|
|
4095
2838
|
function lmStudio(home) {
|
|
4096
|
-
const bases = [
|
|
2839
|
+
const bases = [path2.join(home, ".lmstudio", "models"), path2.join(home, ".cache", "lm-studio", "models")];
|
|
4097
2840
|
const out = [];
|
|
4098
2841
|
for (const base of bases) {
|
|
4099
2842
|
walk(base, 5, (file) => {
|
|
4100
2843
|
if (!file.endsWith(".gguf")) return;
|
|
4101
|
-
const rel2 =
|
|
2844
|
+
const rel2 = path2.relative(base, file).replace(/\\/g, "/");
|
|
4102
2845
|
out.push({ runtime: "lm-studio", name: rel2, path: file });
|
|
4103
2846
|
});
|
|
4104
2847
|
}
|
|
4105
2848
|
return dedupe(out);
|
|
4106
2849
|
}
|
|
4107
2850
|
function looseGguf(home) {
|
|
4108
|
-
const bases = [
|
|
2851
|
+
const bases = [path2.join(home, "models"), path2.join(home, ".cache", "huggingface")];
|
|
4109
2852
|
const out = [];
|
|
4110
2853
|
for (const base of bases) {
|
|
4111
2854
|
walk(base, 4, (file) => {
|
|
4112
2855
|
if (!file.endsWith(".gguf")) return;
|
|
4113
|
-
out.push({ runtime: "gguf", name:
|
|
2856
|
+
out.push({ runtime: "gguf", name: path2.basename(file), path: file });
|
|
4114
2857
|
});
|
|
4115
2858
|
}
|
|
4116
2859
|
return dedupe(out);
|
|
@@ -4119,12 +2862,12 @@ function walk(dir, depth, onFile) {
|
|
|
4119
2862
|
if (depth < 0) return;
|
|
4120
2863
|
let entries;
|
|
4121
2864
|
try {
|
|
4122
|
-
entries =
|
|
2865
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
4123
2866
|
} catch {
|
|
4124
2867
|
return;
|
|
4125
2868
|
}
|
|
4126
2869
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4127
|
-
const abs =
|
|
2870
|
+
const abs = path2.join(dir, e.name);
|
|
4128
2871
|
if (e.isDirectory()) walk(abs, depth - 1, onFile);
|
|
4129
2872
|
else if (e.isFile()) onFile(abs);
|
|
4130
2873
|
}
|
|
@@ -4140,457 +2883,11 @@ function dedupe(models) {
|
|
|
4140
2883
|
}
|
|
4141
2884
|
return out;
|
|
4142
2885
|
}
|
|
4143
|
-
function lockfileVersion(root, ecosystem, name) {
|
|
4144
|
-
if (ecosystem === "npm") return packageLockVersion(root, name) ?? yarnLockVersion(root, name);
|
|
4145
|
-
if (ecosystem === "pypi") return pypiLockVersion(root, name);
|
|
4146
|
-
if (ecosystem === "rust") return tomlPackageLock(root, "Cargo.lock", name, rustName);
|
|
4147
|
-
if (ecosystem === "ruby") return gemfileLock(root, name);
|
|
4148
|
-
if (ecosystem === "php") return composerLock(root, name);
|
|
4149
|
-
if (ecosystem === "dotnet") return packagesLock(root, name);
|
|
4150
|
-
if (ecosystem === "swift") return packageResolved(root, name);
|
|
4151
|
-
if (ecosystem === "dart") return pubspecLock(root, name);
|
|
4152
|
-
if (ecosystem === "java") return gradleLock(root, name);
|
|
4153
|
-
return void 0;
|
|
4154
|
-
}
|
|
4155
|
-
function gradleLock(root, name) {
|
|
4156
|
-
let text;
|
|
4157
|
-
try {
|
|
4158
|
-
text = fs17.readFileSync(path17.join(root, "gradle.lockfile"), "utf8");
|
|
4159
|
-
} catch {
|
|
4160
|
-
return void 0;
|
|
4161
|
-
}
|
|
4162
|
-
const m = new RegExp(`^${escapeRegExp(name)}:([^=\\s]+)=`, "m").exec(text);
|
|
4163
|
-
return m ? m[1] : void 0;
|
|
4164
|
-
}
|
|
4165
|
-
function packageResolved(root, name) {
|
|
4166
|
-
let data;
|
|
4167
|
-
try {
|
|
4168
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "Package.resolved"), "utf8"));
|
|
4169
|
-
} catch {
|
|
4170
|
-
return void 0;
|
|
4171
|
-
}
|
|
4172
|
-
const pins = data.pins ?? data.object?.pins;
|
|
4173
|
-
if (!Array.isArray(pins)) return void 0;
|
|
4174
|
-
const target = name.toLowerCase();
|
|
4175
|
-
for (const p of pins) {
|
|
4176
|
-
const id = (p.identity ?? p.package ?? "").toLowerCase();
|
|
4177
|
-
if (id === target && p.state?.version) return p.state.version;
|
|
4178
|
-
}
|
|
4179
|
-
return void 0;
|
|
4180
|
-
}
|
|
4181
|
-
function pubspecLock(root, name) {
|
|
4182
|
-
let text;
|
|
4183
|
-
try {
|
|
4184
|
-
text = fs17.readFileSync(path17.join(root, "pubspec.lock"), "utf8");
|
|
4185
|
-
} catch {
|
|
4186
|
-
return void 0;
|
|
4187
|
-
}
|
|
4188
|
-
let inPkg = false;
|
|
4189
|
-
for (const line of text.split("\n")) {
|
|
4190
|
-
const key = /^ ([A-Za-z0-9_.]+):\s*$/.exec(line);
|
|
4191
|
-
if (key) {
|
|
4192
|
-
inPkg = key[1] === name;
|
|
4193
|
-
continue;
|
|
4194
|
-
}
|
|
4195
|
-
if (inPkg) {
|
|
4196
|
-
const vm = /^\s+version:\s*"?([^"\s]+)"?/.exec(line);
|
|
4197
|
-
if (vm) return vm[1];
|
|
4198
|
-
}
|
|
4199
|
-
}
|
|
4200
|
-
return void 0;
|
|
4201
|
-
}
|
|
4202
|
-
function escapeRegExp(s) {
|
|
4203
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4204
|
-
}
|
|
4205
|
-
function gemfileLock(root, name) {
|
|
4206
|
-
let text;
|
|
4207
|
-
try {
|
|
4208
|
-
text = fs17.readFileSync(path17.join(root, "Gemfile.lock"), "utf8");
|
|
4209
|
-
} catch {
|
|
4210
|
-
return void 0;
|
|
4211
|
-
}
|
|
4212
|
-
const m = new RegExp(`^ ${escapeRegExp(name)} \\((\\d[^)]*)\\)`, "m").exec(text);
|
|
4213
|
-
return m ? m[1] : void 0;
|
|
4214
|
-
}
|
|
4215
|
-
function composerLock(root, name) {
|
|
4216
|
-
let data;
|
|
4217
|
-
try {
|
|
4218
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "composer.lock"), "utf8"));
|
|
4219
|
-
} catch {
|
|
4220
|
-
return void 0;
|
|
4221
|
-
}
|
|
4222
|
-
for (const section of ["packages", "packages-dev"]) {
|
|
4223
|
-
const arr = data[section];
|
|
4224
|
-
if (!Array.isArray(arr)) continue;
|
|
4225
|
-
const p = arr.find((x) => x && x.name === name);
|
|
4226
|
-
if (p && typeof p.version === "string") return p.version.replace(/^v/, "");
|
|
4227
|
-
}
|
|
4228
|
-
return void 0;
|
|
4229
|
-
}
|
|
4230
|
-
function packagesLock(root, name) {
|
|
4231
|
-
let data;
|
|
4232
|
-
try {
|
|
4233
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "packages.lock.json"), "utf8"));
|
|
4234
|
-
} catch {
|
|
4235
|
-
return void 0;
|
|
4236
|
-
}
|
|
4237
|
-
const target = name.toLowerCase();
|
|
4238
|
-
for (const fw of Object.values(data.dependencies ?? {})) {
|
|
4239
|
-
if (!fw || typeof fw !== "object") continue;
|
|
4240
|
-
for (const [id, v] of Object.entries(fw)) {
|
|
4241
|
-
if (id.toLowerCase() === target && v && typeof v.resolved === "string") return v.resolved;
|
|
4242
|
-
}
|
|
4243
|
-
}
|
|
4244
|
-
return void 0;
|
|
4245
|
-
}
|
|
4246
|
-
function pep503(name) {
|
|
4247
|
-
return name.trim().toLowerCase().replace(/[-_.]+/g, "-");
|
|
4248
|
-
}
|
|
4249
|
-
function rustName(name) {
|
|
4250
|
-
return name.trim().toLowerCase();
|
|
4251
|
-
}
|
|
4252
|
-
function pypiLockVersion(root, name) {
|
|
4253
|
-
return tomlPackageLock(root, "poetry.lock", name, pep503) ?? tomlPackageLock(root, "uv.lock", name, pep503) ?? pipfileLock(root, name);
|
|
4254
|
-
}
|
|
4255
|
-
function tomlPackageLock(root, file, name, normalize3) {
|
|
4256
|
-
let text;
|
|
4257
|
-
try {
|
|
4258
|
-
text = fs17.readFileSync(path17.join(root, file), "utf8");
|
|
4259
|
-
} catch {
|
|
4260
|
-
return void 0;
|
|
4261
|
-
}
|
|
4262
|
-
const target = normalize3(name);
|
|
4263
|
-
for (const block of text.split(/\[\[package\]\]/)) {
|
|
4264
|
-
const nm = /(?:^|\n)\s*name\s*=\s*"([^"]+)"/.exec(block);
|
|
4265
|
-
if (nm && normalize3(nm[1]) === target) {
|
|
4266
|
-
const ver = /(?:^|\n)\s*version\s*=\s*"([^"]+)"/.exec(block);
|
|
4267
|
-
if (ver) return ver[1];
|
|
4268
|
-
}
|
|
4269
|
-
}
|
|
4270
|
-
return void 0;
|
|
4271
|
-
}
|
|
4272
|
-
function pipfileLock(root, name) {
|
|
4273
|
-
let data;
|
|
4274
|
-
try {
|
|
4275
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "Pipfile.lock"), "utf8"));
|
|
4276
|
-
} catch {
|
|
4277
|
-
return void 0;
|
|
4278
|
-
}
|
|
4279
|
-
const target = pep503(name);
|
|
4280
|
-
for (const section of ["default", "develop"]) {
|
|
4281
|
-
const deps = data[section];
|
|
4282
|
-
if (!deps || typeof deps !== "object") continue;
|
|
4283
|
-
for (const [k, v] of Object.entries(deps)) {
|
|
4284
|
-
if (pep503(k) === target && v && typeof v.version === "string") {
|
|
4285
|
-
return v.version.replace(/^==/, "");
|
|
4286
|
-
}
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
return void 0;
|
|
4290
|
-
}
|
|
4291
|
-
function packageLockVersion(root, name) {
|
|
4292
|
-
let data;
|
|
4293
|
-
try {
|
|
4294
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "package-lock.json"), "utf8"));
|
|
4295
|
-
} catch {
|
|
4296
|
-
return void 0;
|
|
4297
|
-
}
|
|
4298
|
-
const top = data.packages?.[`node_modules/${name}`]?.version;
|
|
4299
|
-
if (typeof top === "string") return top;
|
|
4300
|
-
const v1 = data.dependencies?.[name]?.version;
|
|
4301
|
-
return typeof v1 === "string" ? v1 : void 0;
|
|
4302
|
-
}
|
|
4303
|
-
function yarnLockVersion(root, name) {
|
|
4304
|
-
let text;
|
|
4305
|
-
try {
|
|
4306
|
-
text = fs17.readFileSync(path17.join(root, "yarn.lock"), "utf8");
|
|
4307
|
-
} catch {
|
|
4308
|
-
return void 0;
|
|
4309
|
-
}
|
|
4310
|
-
let inBlock = false;
|
|
4311
|
-
for (const line of text.split("\n")) {
|
|
4312
|
-
if (line && !/^\s/.test(line) && !line.startsWith("#")) {
|
|
4313
|
-
inBlock = headerNamesPackage(line, name);
|
|
4314
|
-
} else if (inBlock) {
|
|
4315
|
-
const m = /^\s+version:?\s+"?([^"\s]+)"?/.exec(line);
|
|
4316
|
-
if (m) return m[1];
|
|
4317
|
-
}
|
|
4318
|
-
}
|
|
4319
|
-
return void 0;
|
|
4320
|
-
}
|
|
4321
|
-
function headerNamesPackage(header, name) {
|
|
4322
|
-
return header.replace(/:\s*$/, "").split(",").some((spec) => {
|
|
4323
|
-
const s = spec.trim().replace(/^"|"$/g, "");
|
|
4324
|
-
const at = s.lastIndexOf("@");
|
|
4325
|
-
if (at <= 0) return false;
|
|
4326
|
-
const specName = s.slice(0, at);
|
|
4327
|
-
return specName === name;
|
|
4328
|
-
});
|
|
4329
|
-
}
|
|
4330
|
-
|
|
4331
|
-
// src/engine/lib.ts
|
|
4332
|
-
var LIB_SCHEMA = "vg-lib/1.0";
|
|
4333
|
-
function catalogPath(root) {
|
|
4334
|
-
return path17.join(root, "vibgrate.lib.json");
|
|
4335
|
-
}
|
|
4336
|
-
function libDir(root) {
|
|
4337
|
-
return path17.join(root, ".vibgrate", "lib");
|
|
4338
|
-
}
|
|
4339
|
-
function libId(name) {
|
|
4340
|
-
return name.trim().toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
4341
|
-
}
|
|
4342
|
-
function loadCatalog(root) {
|
|
4343
|
-
const file = catalogPath(root);
|
|
4344
|
-
if (fs17.existsSync(file)) {
|
|
4345
|
-
try {
|
|
4346
|
-
const data = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
4347
|
-
if (data.schemaVersion === LIB_SCHEMA && data.libraries) return data;
|
|
4348
|
-
} catch {
|
|
4349
|
-
}
|
|
4350
|
-
}
|
|
4351
|
-
return { schemaVersion: LIB_SCHEMA, libraries: {} };
|
|
4352
|
-
}
|
|
4353
|
-
function saveCatalog(root, catalog) {
|
|
4354
|
-
fs17.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
|
|
4355
|
-
`);
|
|
4356
|
-
}
|
|
4357
|
-
function resolveLib(catalog, name) {
|
|
4358
|
-
const id = libId(name);
|
|
4359
|
-
if (catalog.libraries[id]) return catalog.libraries[id];
|
|
4360
|
-
const lower = name.toLowerCase();
|
|
4361
|
-
const all = Object.values(catalog.libraries);
|
|
4362
|
-
return all.find((e) => e.name.toLowerCase() === lower) ?? all.find((e) => e.id.includes(id) || e.name.toLowerCase().includes(lower));
|
|
4363
|
-
}
|
|
4364
|
-
function installedVersion(root, name) {
|
|
4365
|
-
const rec = inventory(root).records.find((r) => r.name === name);
|
|
4366
|
-
return rec?.installed ?? rec?.declared;
|
|
4367
|
-
}
|
|
4368
|
-
var stripRange = (v) => v?.replace(/^[v^~>=<\s]+/, "");
|
|
4369
|
-
function resolveVersion(root, name) {
|
|
4370
|
-
const rec = inventory(root).records.find((r) => r.name === name);
|
|
4371
|
-
const declared = rec?.declared;
|
|
4372
|
-
const installed = rec?.installed ?? (rec ? installedTreeVersion(root, rec.ecosystem, name) : void 0);
|
|
4373
|
-
const lockfile = rec ? lockfileVersion(root, rec.ecosystem, name) : void 0;
|
|
4374
|
-
let mismatch;
|
|
4375
|
-
if (lockfile && installed && stripRange(lockfile) !== stripRange(installed)) {
|
|
4376
|
-
mismatch = {
|
|
4377
|
-
lockfile,
|
|
4378
|
-
installed,
|
|
4379
|
-
note: `lockfile pins ${lockfile} but the installed tree has ${installed} \u2014 your install is out of sync with the lockfile (re-install or commit the lock)`
|
|
4380
|
-
};
|
|
4381
|
-
}
|
|
4382
|
-
const served = lockfile ?? installed ?? declared;
|
|
4383
|
-
const source = lockfile ? "lockfile" : installed ? "installed" : declared ? "declared" : "unknown";
|
|
4384
|
-
return { served, source, lockfile, installed, declared, mismatch };
|
|
4385
|
-
}
|
|
4386
|
-
function driftFor(root, entry) {
|
|
4387
|
-
const installed = installedVersion(root, entry.name);
|
|
4388
|
-
if (!installed || entry.version === "*") return { cataloged: entry.version, installed, drift: "unknown" };
|
|
4389
|
-
const norm = (v) => v.replace(/^[v^~>=<\s]+/, "");
|
|
4390
|
-
const a = norm(entry.version);
|
|
4391
|
-
const b = norm(installed);
|
|
4392
|
-
if (a === b) return { cataloged: entry.version, installed, drift: "current" };
|
|
4393
|
-
return { cataloged: entry.version, installed, drift: cmp(a, b) < 0 ? "behind" : "ahead" };
|
|
4394
|
-
}
|
|
4395
|
-
function cmp(a, b) {
|
|
4396
|
-
const pa = a.split(".").map(Number);
|
|
4397
|
-
const pb = b.split(".").map(Number);
|
|
4398
|
-
for (let i = 0; i < 3; i++) {
|
|
4399
|
-
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
4400
|
-
if (d) return d;
|
|
4401
|
-
}
|
|
4402
|
-
return 0;
|
|
4403
|
-
}
|
|
4404
|
-
function parseGitSource(source) {
|
|
4405
|
-
if (source.startsWith("git+")) return source.slice(4);
|
|
4406
|
-
if (/^git@[^:]+:/.test(source)) return source;
|
|
4407
|
-
if (/\.git\/?$/.test(source)) return source;
|
|
4408
|
-
return null;
|
|
4409
|
-
}
|
|
4410
|
-
async function addLibrary(source, opts) {
|
|
4411
|
-
const { root } = opts;
|
|
4412
|
-
let content;
|
|
4413
|
-
let type = "local";
|
|
4414
|
-
const gitUrl = parseGitSource(source);
|
|
4415
|
-
if (gitUrl) {
|
|
4416
|
-
if (!opts.allowNetwork) {
|
|
4417
|
-
throw new Error(`refusing to clone ${source} without --online (offline by default)`);
|
|
4418
|
-
}
|
|
4419
|
-
content = cloneAndReadGit(gitUrl);
|
|
4420
|
-
type = "git";
|
|
4421
|
-
} else if (/^https?:\/\//.test(source)) {
|
|
4422
|
-
if (!opts.allowNetwork) {
|
|
4423
|
-
throw new Error(`refusing to fetch ${source} without --online (offline by default)`);
|
|
4424
|
-
}
|
|
4425
|
-
const res = await (opts.fetchImpl ?? globalThis.fetch)(source);
|
|
4426
|
-
if (!res.ok) throw new Error(`fetch failed (${res.status}) for ${source}`);
|
|
4427
|
-
content = await res.text();
|
|
4428
|
-
type = source.endsWith("llms.txt") ? "llms.txt" : source.endsWith(".json") ? "openapi" : "website";
|
|
4429
|
-
} else {
|
|
4430
|
-
const abs = path17.resolve(root, source);
|
|
4431
|
-
if (!fs17.existsSync(abs)) throw new Error(`source not found: ${source}`);
|
|
4432
|
-
content = readLocal(abs);
|
|
4433
|
-
type = "local";
|
|
4434
|
-
}
|
|
4435
|
-
const name = opts.name ?? inferName(source);
|
|
4436
|
-
const id = libId(name);
|
|
4437
|
-
const version = opts.version ?? installedVersion(root, name) ?? "*";
|
|
4438
|
-
fs17.mkdirSync(libDir(root), { recursive: true });
|
|
4439
|
-
const docFileAbs = path17.join(libDir(root), `${id}.md`);
|
|
4440
|
-
fs17.writeFileSync(docFileAbs, content);
|
|
4441
|
-
const entry = {
|
|
4442
|
-
id,
|
|
4443
|
-
name,
|
|
4444
|
-
version,
|
|
4445
|
-
source: { type, location: source },
|
|
4446
|
-
docFile: path17.relative(root, docFileAbs).split(path17.sep).join("/"),
|
|
4447
|
-
docHash: hashString(content),
|
|
4448
|
-
bytes: Buffer.byteLength(content, "utf8")
|
|
4449
|
-
};
|
|
4450
|
-
const catalog = loadCatalog(root);
|
|
4451
|
-
catalog.libraries[id] = entry;
|
|
4452
|
-
saveCatalog(root, catalog);
|
|
4453
|
-
return entry;
|
|
4454
|
-
}
|
|
4455
|
-
function readDoc(root, entry) {
|
|
4456
|
-
const abs = path17.resolve(root, entry.docFile);
|
|
4457
|
-
return fs17.existsSync(abs) ? fs17.readFileSync(abs, "utf8") : "";
|
|
4458
|
-
}
|
|
4459
|
-
function npmPackageDir(root, name) {
|
|
4460
|
-
let cur = root;
|
|
4461
|
-
for (let i = 0; i < 12; i++) {
|
|
4462
|
-
const dir = path17.join(cur, "node_modules", name);
|
|
4463
|
-
if (fs17.existsSync(path17.join(dir, "package.json"))) return dir;
|
|
4464
|
-
const parent = path17.dirname(cur);
|
|
4465
|
-
if (parent === cur) break;
|
|
4466
|
-
cur = parent;
|
|
4467
|
-
}
|
|
4468
|
-
return void 0;
|
|
4469
|
-
}
|
|
4470
|
-
function localPackageDocs(root, name) {
|
|
4471
|
-
const dir = npmPackageDir(root, name);
|
|
4472
|
-
if (!dir) return void 0;
|
|
4473
|
-
const version = resolveVersion(root, name).served;
|
|
4474
|
-
for (const f of ["llms.txt", "README.md", "README.mdx", "README", "readme.md"]) {
|
|
4475
|
-
const p = path17.join(dir, f);
|
|
4476
|
-
try {
|
|
4477
|
-
const docs = fs17.readFileSync(p, "utf8");
|
|
4478
|
-
if (docs.trim()) {
|
|
4479
|
-
return { docs, version, source: path17.relative(root, p).split(path17.sep).join("/") };
|
|
4480
|
-
}
|
|
4481
|
-
} catch {
|
|
4482
|
-
}
|
|
4483
|
-
}
|
|
4484
|
-
try {
|
|
4485
|
-
const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
|
|
4486
|
-
if (pkg.description?.trim()) {
|
|
4487
|
-
return {
|
|
4488
|
-
docs: `# ${name}
|
|
4489
|
-
|
|
4490
|
-
${pkg.description}`,
|
|
4491
|
-
version: version ?? pkg.version,
|
|
4492
|
-
source: path17.relative(root, path17.join(dir, "package.json")).split(path17.sep).join("/")
|
|
4493
|
-
};
|
|
4494
|
-
}
|
|
4495
|
-
} catch {
|
|
4496
|
-
}
|
|
4497
|
-
return void 0;
|
|
4498
|
-
}
|
|
4499
|
-
function dtsEntry(dir) {
|
|
4500
|
-
try {
|
|
4501
|
-
const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
|
|
4502
|
-
const rel2 = pkg.types ?? pkg.typings;
|
|
4503
|
-
if (typeof rel2 === "string") {
|
|
4504
|
-
const p = path17.join(dir, rel2);
|
|
4505
|
-
if (fs17.existsSync(p)) return p;
|
|
4506
|
-
}
|
|
4507
|
-
} catch {
|
|
4508
|
-
}
|
|
4509
|
-
for (const c of ["index.d.ts", "dist/index.d.ts", "lib/index.d.ts", "types/index.d.ts"]) {
|
|
4510
|
-
const p = path17.join(dir, c);
|
|
4511
|
-
if (fs17.existsSync(p)) return p;
|
|
4512
|
-
}
|
|
4513
|
-
return void 0;
|
|
4514
|
-
}
|
|
4515
|
-
function extractDtsApi(src, maxDecls = 200) {
|
|
4516
|
-
const out = [];
|
|
4517
|
-
let depth = 0;
|
|
4518
|
-
let capture = null;
|
|
4519
|
-
for (const raw of src.split("\n")) {
|
|
4520
|
-
const line = raw.trim();
|
|
4521
|
-
if (capture === null && depth === 0 && /^export\b/.test(line) && !/^export\s+(\{|\*|default\b|type\s+\{)/.test(line)) {
|
|
4522
|
-
capture = [];
|
|
4523
|
-
}
|
|
4524
|
-
if (capture) capture.push(raw);
|
|
4525
|
-
for (const ch of raw) {
|
|
4526
|
-
if (ch === "{") depth++;
|
|
4527
|
-
else if (ch === "}") depth = Math.max(0, depth - 1);
|
|
4528
|
-
}
|
|
4529
|
-
if (capture && depth === 0 && /[;}]\s*$/.test(line)) {
|
|
4530
|
-
out.push(capture.join("\n").trim());
|
|
4531
|
-
capture = null;
|
|
4532
|
-
if (out.length >= maxDecls) break;
|
|
4533
|
-
}
|
|
4534
|
-
}
|
|
4535
|
-
return out;
|
|
4536
|
-
}
|
|
4537
|
-
function localApiSurface(root, name) {
|
|
4538
|
-
const dir = npmPackageDir(root, name);
|
|
4539
|
-
if (!dir) return void 0;
|
|
4540
|
-
const dts = dtsEntry(dir);
|
|
4541
|
-
if (!dts) return void 0;
|
|
4542
|
-
let src;
|
|
4543
|
-
try {
|
|
4544
|
-
src = fs17.readFileSync(dts, "utf8");
|
|
4545
|
-
} catch {
|
|
4546
|
-
return void 0;
|
|
4547
|
-
}
|
|
4548
|
-
const decls = extractDtsApi(src);
|
|
4549
|
-
return decls.length ? decls.join("\n\n") : void 0;
|
|
4550
|
-
}
|
|
4551
|
-
function cloneAndReadGit(url) {
|
|
4552
|
-
const dir = fs17.mkdtempSync(path17.join(os.tmpdir(), "vg-lib-git-"));
|
|
4553
|
-
try {
|
|
4554
|
-
execFileSync("git", ["clone", "--depth", "1", "--quiet", url, dir], {
|
|
4555
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
4556
|
-
});
|
|
4557
|
-
} catch (err) {
|
|
4558
|
-
fs17.rmSync(dir, { recursive: true, force: true });
|
|
4559
|
-
const detail = err instanceof Error && err.message ? `: ${err.message.split("\n")[0]}` : "";
|
|
4560
|
-
throw new Error(`git clone failed for ${url}${detail}`);
|
|
4561
|
-
}
|
|
4562
|
-
try {
|
|
4563
|
-
return readGitDocs(dir);
|
|
4564
|
-
} finally {
|
|
4565
|
-
fs17.rmSync(dir, { recursive: true, force: true });
|
|
4566
|
-
}
|
|
4567
|
-
}
|
|
4568
|
-
function readGitDocs(dir) {
|
|
4569
|
-
const docsDir = path17.join(dir, "docs");
|
|
4570
|
-
if (fs17.existsSync(docsDir) && fs17.statSync(docsDir).isDirectory()) return readLocal(docsDir);
|
|
4571
|
-
const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path17.join(dir, f)).find((p) => fs17.existsSync(p));
|
|
4572
|
-
if (readme) return fs17.readFileSync(readme, "utf8");
|
|
4573
|
-
return readLocal(dir);
|
|
4574
|
-
}
|
|
4575
|
-
function readLocal(abs) {
|
|
4576
|
-
const stat = fs17.statSync(abs);
|
|
4577
|
-
if (stat.isFile()) return fs17.readFileSync(abs, "utf8");
|
|
4578
|
-
const parts = [];
|
|
4579
|
-
const files = fs17.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
|
|
4580
|
-
for (const f of files) parts.push(`<!-- ${f} -->
|
|
4581
|
-
${fs17.readFileSync(path17.join(abs, f), "utf8")}`);
|
|
4582
|
-
return parts.join("\n\n");
|
|
4583
|
-
}
|
|
4584
|
-
function inferName(source) {
|
|
4585
|
-
const cleaned = source.replace(/^git\+/, "").replace(/\.git\/?$/i, "");
|
|
4586
|
-
const base = cleaned.replace(/\/+$/, "").split(/[/:]/).pop() ?? cleaned;
|
|
4587
|
-
return base.replace(/\.(md|mdx|txt|rst|json)$/i, "").replace(/^llms$/, "docs");
|
|
4588
|
-
}
|
|
4589
2886
|
function findGitRoot(startDir = process.cwd()) {
|
|
4590
|
-
let dir =
|
|
2887
|
+
let dir = path2.resolve(startDir);
|
|
4591
2888
|
for (; ; ) {
|
|
4592
|
-
if (
|
|
4593
|
-
const parent =
|
|
2889
|
+
if (fs18.existsSync(path2.join(dir, ".git"))) return dir;
|
|
2890
|
+
const parent = path2.dirname(dir);
|
|
4594
2891
|
if (parent === dir) return null;
|
|
4595
2892
|
dir = parent;
|
|
4596
2893
|
}
|
|
@@ -4601,19 +2898,19 @@ function normalizeEntry(line) {
|
|
|
4601
2898
|
function ensureGitignored(entry, startDir = process.cwd()) {
|
|
4602
2899
|
const root = findGitRoot(startDir);
|
|
4603
2900
|
if (!root) return { status: "not-a-repo", entry };
|
|
4604
|
-
const gitignorePath =
|
|
2901
|
+
const gitignorePath = path2.join(root, ".gitignore");
|
|
4605
2902
|
const target = normalizeEntry(entry);
|
|
4606
2903
|
let existing = "";
|
|
4607
2904
|
let fileExisted = true;
|
|
4608
2905
|
try {
|
|
4609
|
-
existing =
|
|
2906
|
+
existing = fs18.readFileSync(gitignorePath, "utf8");
|
|
4610
2907
|
} catch {
|
|
4611
2908
|
fileExisted = false;
|
|
4612
2909
|
}
|
|
4613
2910
|
const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
|
|
4614
2911
|
if (alreadyPresent) return { status: "present", entry, gitignorePath };
|
|
4615
2912
|
const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
|
|
4616
|
-
|
|
2913
|
+
fs18.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
|
|
4617
2914
|
`, "utf8");
|
|
4618
2915
|
return { status: fileExisted ? "added" : "created", entry, gitignorePath };
|
|
4619
2916
|
}
|
|
@@ -4767,14 +3064,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
4767
3064
|
console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
|
|
4768
3065
|
}
|
|
4769
3066
|
if (opts.write) {
|
|
4770
|
-
const writePath =
|
|
3067
|
+
const writePath = path2.resolve(opts.write);
|
|
4771
3068
|
await writeTextFile(writePath, dsn + "\n");
|
|
4772
3069
|
console.log("");
|
|
4773
3070
|
console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
|
|
4774
|
-
const root = findGitRoot(
|
|
4775
|
-
const rel2 = root ?
|
|
3071
|
+
const root = findGitRoot(path2.dirname(writePath));
|
|
3072
|
+
const rel2 = root ? path2.relative(root, writePath).split(path2.sep).join("/") : "";
|
|
4776
3073
|
if (root && rel2 && !rel2.startsWith("..")) {
|
|
4777
|
-
const res = ensureGitignored(rel2,
|
|
3074
|
+
const res = ensureGitignored(rel2, path2.dirname(writePath));
|
|
4778
3075
|
if (res.status === "created") {
|
|
4779
3076
|
console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
|
|
4780
3077
|
} else if (res.status === "added") {
|
|
@@ -4792,30 +3089,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
4792
3089
|
var STORE_DIRNAME = ".vibgrate";
|
|
4793
3090
|
var STORE_FILENAME = "credentials.json";
|
|
4794
3091
|
function homeCredentialsPath() {
|
|
4795
|
-
return
|
|
3092
|
+
return path2.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
|
|
4796
3093
|
}
|
|
4797
3094
|
function projectCredentialsPath(cwd = process.cwd()) {
|
|
4798
3095
|
const root = findGitRoot(cwd) ?? cwd;
|
|
4799
|
-
return
|
|
3096
|
+
return path2.join(root, STORE_DIRNAME, STORE_FILENAME);
|
|
4800
3097
|
}
|
|
4801
3098
|
function credentialsPath(opts = {}) {
|
|
4802
3099
|
const override = process.env.VIBGRATE_CREDENTIALS?.trim();
|
|
4803
|
-
if (override) return
|
|
3100
|
+
if (override) return path2.resolve(override);
|
|
4804
3101
|
const local = projectCredentialsPath(opts.cwd);
|
|
4805
3102
|
if (opts.local) return local;
|
|
4806
|
-
if (
|
|
3103
|
+
if (fs18.existsSync(local)) return local;
|
|
4807
3104
|
return homeCredentialsPath();
|
|
4808
3105
|
}
|
|
4809
3106
|
function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
|
|
4810
|
-
const rel2 =
|
|
4811
|
-
if (rel2 && !rel2.startsWith("..") && !
|
|
4812
|
-
return rel2.split(
|
|
3107
|
+
const rel2 = path2.relative(repoRoot, credsFile);
|
|
3108
|
+
if (rel2 && !rel2.startsWith("..") && !path2.isAbsolute(rel2)) {
|
|
3109
|
+
return rel2.split(path2.sep).join("/");
|
|
4813
3110
|
}
|
|
4814
3111
|
return `${STORE_DIRNAME}/${STORE_FILENAME}`;
|
|
4815
3112
|
}
|
|
4816
3113
|
function readStoredCredentials(opts = {}) {
|
|
4817
3114
|
try {
|
|
4818
|
-
const raw =
|
|
3115
|
+
const raw = fs18.readFileSync(credentialsPath(opts), "utf8");
|
|
4819
3116
|
const parsed = JSON.parse(raw);
|
|
4820
3117
|
return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
|
|
4821
3118
|
} catch {
|
|
@@ -4824,16 +3121,16 @@ function readStoredCredentials(opts = {}) {
|
|
|
4824
3121
|
}
|
|
4825
3122
|
function writeStoredCredentials(creds, opts = {}) {
|
|
4826
3123
|
const file = credentialsPath(opts);
|
|
4827
|
-
|
|
4828
|
-
|
|
3124
|
+
fs18.mkdirSync(path2.dirname(file), { recursive: true });
|
|
3125
|
+
fs18.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
4829
3126
|
try {
|
|
4830
|
-
|
|
3127
|
+
fs18.chmodSync(file, 384);
|
|
4831
3128
|
} catch {
|
|
4832
3129
|
}
|
|
4833
3130
|
}
|
|
4834
3131
|
function clearStoredCredentials(opts = {}) {
|
|
4835
3132
|
try {
|
|
4836
|
-
|
|
3133
|
+
fs18.rmSync(credentialsPath(opts));
|
|
4837
3134
|
return true;
|
|
4838
3135
|
} catch {
|
|
4839
3136
|
return false;
|
|
@@ -5204,7 +3501,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
5204
3501
|
if (opts.strict) process.exit(1);
|
|
5205
3502
|
return;
|
|
5206
3503
|
}
|
|
5207
|
-
const filePath =
|
|
3504
|
+
const filePath = path2.resolve(opts.file);
|
|
5208
3505
|
if (!await pathExists(filePath)) {
|
|
5209
3506
|
console.error(chalk.red(`Scan artifact not found: ${filePath}`));
|
|
5210
3507
|
console.error(chalk.dim('Run "vibgrate scan" first.'));
|
|
@@ -5267,8 +3564,8 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
5267
3564
|
});
|
|
5268
3565
|
function readScanArtifact(root) {
|
|
5269
3566
|
try {
|
|
5270
|
-
const file =
|
|
5271
|
-
return JSON.parse(
|
|
3567
|
+
const file = path2.join(root, ".vibgrate", "scan_result.json");
|
|
3568
|
+
return JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
5272
3569
|
} catch {
|
|
5273
3570
|
return null;
|
|
5274
3571
|
}
|
|
@@ -5373,7 +3670,7 @@ var DEFAULT_SELECTION_WEIGHTS = {
|
|
|
5373
3670
|
};
|
|
5374
3671
|
|
|
5375
3672
|
// src/engine/select.ts
|
|
5376
|
-
var
|
|
3673
|
+
var STOPWORDS = /* @__PURE__ */ new Set(["the", "a", "an", "to", "of", "in", "for", "and", "or", "is", "how", "do", "i", "use", "using", "with", "on", "my"]);
|
|
5377
3674
|
var USAGE_RE = /\b(usage|example|examples|quick ?start|getting started|api|guide|how to|reference|tutorial|basic)\b/i;
|
|
5378
3675
|
var PREAMBLE_RE = /\b(install|installation|license|licence|contributing|contributors?|sponsors?|table of contents|toc|changelog|funding|code of conduct|security|acknowledge)\b/i;
|
|
5379
3676
|
function splitSections(md) {
|
|
@@ -5420,9 +3717,9 @@ function chunkBody(body) {
|
|
|
5420
3717
|
function stripNoise(body) {
|
|
5421
3718
|
return body.split("\n").filter((l) => !/^\s*(?:!\[[^\]]*\]\([^)]*\)\s*)+$/.test(l)).join("\n").trim();
|
|
5422
3719
|
}
|
|
5423
|
-
function tokenizeQuery(
|
|
5424
|
-
if (!
|
|
5425
|
-
return [...new Set(
|
|
3720
|
+
function tokenizeQuery(q) {
|
|
3721
|
+
if (!q) return [];
|
|
3722
|
+
return [...new Set(q.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1 && !STOPWORDS.has(w)))];
|
|
5426
3723
|
}
|
|
5427
3724
|
function symbolsFromApi(api) {
|
|
5428
3725
|
if (!api) return [];
|
|
@@ -5654,11 +3951,11 @@ async function publishPrivateLibrary(req, opts = {}) {
|
|
|
5654
3951
|
}
|
|
5655
3952
|
|
|
5656
3953
|
// src/engine/hosted-cache.ts
|
|
5657
|
-
var
|
|
3954
|
+
var CACHE_VERSION = "vg-hosted-docs/1";
|
|
5658
3955
|
var HOSTED_DOCS_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5659
3956
|
var MAX_ENTRIES = 64;
|
|
5660
|
-
function
|
|
5661
|
-
return
|
|
3957
|
+
function cachePath(root) {
|
|
3958
|
+
return path2.join(cacheDir(root), "hosted-docs.json");
|
|
5662
3959
|
}
|
|
5663
3960
|
function hostedDocsCacheKey(req, opts = {}) {
|
|
5664
3961
|
const material = JSON.stringify([
|
|
@@ -5674,11 +3971,11 @@ function hostedDocsCacheKey(req, opts = {}) {
|
|
|
5674
3971
|
}
|
|
5675
3972
|
function loadFile(root) {
|
|
5676
3973
|
try {
|
|
5677
|
-
const parsed = JSON.parse(
|
|
5678
|
-
if (parsed && parsed.version ===
|
|
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;
|
|
5679
3976
|
} catch {
|
|
5680
3977
|
}
|
|
5681
|
-
return { version:
|
|
3978
|
+
return { version: CACHE_VERSION, entries: {} };
|
|
5682
3979
|
}
|
|
5683
3980
|
function saveFile(root, file) {
|
|
5684
3981
|
try {
|
|
@@ -5688,8 +3985,8 @@ function saveFile(root, file) {
|
|
|
5688
3985
|
delete file.entries[k];
|
|
5689
3986
|
}
|
|
5690
3987
|
}
|
|
5691
|
-
|
|
5692
|
-
|
|
3988
|
+
fs18.mkdirSync(cacheDir(root), { recursive: true });
|
|
3989
|
+
fs18.writeFileSync(cachePath(root), JSON.stringify(file));
|
|
5693
3990
|
} catch {
|
|
5694
3991
|
}
|
|
5695
3992
|
}
|
|
@@ -5814,7 +4111,7 @@ function measure(root, files) {
|
|
|
5814
4111
|
for (const rel2 of files) {
|
|
5815
4112
|
let size;
|
|
5816
4113
|
try {
|
|
5817
|
-
size =
|
|
4114
|
+
size = fs18.statSync(path2.join(root, rel2)).size;
|
|
5818
4115
|
} catch {
|
|
5819
4116
|
continue;
|
|
5820
4117
|
}
|
|
@@ -5837,9 +4134,9 @@ function matchLines(text, needleLower) {
|
|
|
5837
4134
|
}
|
|
5838
4135
|
function readCandidate(abs, knownSize) {
|
|
5839
4136
|
try {
|
|
5840
|
-
const size = knownSize ??
|
|
4137
|
+
const size = knownSize ?? fs18.statSync(abs).size;
|
|
5841
4138
|
if (size > MAX_FILE_BYTES) return null;
|
|
5842
|
-
const text =
|
|
4139
|
+
const text = fs18.readFileSync(abs, "utf8");
|
|
5843
4140
|
return text.includes(NUL) ? null : text;
|
|
5844
4141
|
} catch {
|
|
5845
4142
|
return null;
|
|
@@ -5850,7 +4147,7 @@ function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
|
|
|
5850
4147
|
let total = 0;
|
|
5851
4148
|
let truncated = false;
|
|
5852
4149
|
for (const rel2 of files) {
|
|
5853
|
-
const text = readCandidate(
|
|
4150
|
+
const text = readCandidate(path2.join(root, rel2), sizes?.get(rel2));
|
|
5854
4151
|
if (text === null) continue;
|
|
5855
4152
|
for (const h of matchLines(text, needleLower)) {
|
|
5856
4153
|
total++;
|
|
@@ -5917,12 +4214,12 @@ var WORKER_SOURCE = [
|
|
|
5917
4214
|
"parentPort.postMessage({ hits, total, truncated });"
|
|
5918
4215
|
].join("\n");
|
|
5919
4216
|
function runWorker(root, files, needleLower) {
|
|
5920
|
-
return new Promise((
|
|
4217
|
+
return new Promise((resolve11, reject) => {
|
|
5921
4218
|
const w = new Worker(WORKER_SOURCE, { eval: true, workerData: { root, files, needleLower } });
|
|
5922
4219
|
let settled = false;
|
|
5923
4220
|
w.once("message", (m) => {
|
|
5924
4221
|
settled = true;
|
|
5925
|
-
|
|
4222
|
+
resolve11(m);
|
|
5926
4223
|
});
|
|
5927
4224
|
w.once("error", reject);
|
|
5928
4225
|
w.once("exit", (code) => {
|
|
@@ -5991,26 +4288,26 @@ var BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
|
5991
4288
|
".pdf"
|
|
5992
4289
|
]);
|
|
5993
4290
|
function hasBinaryExt(rel2) {
|
|
5994
|
-
return BINARY_EXTS.has(
|
|
4291
|
+
return BINARY_EXTS.has(path2.extname(rel2).toLowerCase());
|
|
5995
4292
|
}
|
|
5996
4293
|
function toPosix2(rel2) {
|
|
5997
|
-
return
|
|
4294
|
+
return path2.sep === "\\" ? rel2.replaceAll("\\", "/") : rel2;
|
|
5998
4295
|
}
|
|
5999
4296
|
async function searchSymbols(graph, root, query, limit) {
|
|
6000
|
-
let
|
|
6001
|
-
if (!
|
|
6002
|
-
const quoted = /^(["'`])(.+)\1$/.exec(
|
|
6003
|
-
if (quoted && quoted[2].trim())
|
|
6004
|
-
const isPhrase = Boolean(quoted) || /\s/.test(
|
|
6005
|
-
let nodes = findNodes(graph,
|
|
4297
|
+
let q = query.trim();
|
|
4298
|
+
if (!q) return { matches: [], moreAvailable: false, hint: "query is required" };
|
|
4299
|
+
const quoted = /^(["'`])(.+)\1$/.exec(q);
|
|
4300
|
+
if (quoted && quoted[2].trim()) q = quoted[2].trim();
|
|
4301
|
+
const isPhrase = Boolean(quoted) || /\s/.test(q) || /[/\\]/.test(q) && !q.includes(":") && !/[*?]/.test(q);
|
|
4302
|
+
let nodes = findNodes(graph, q).filter((n) => n.kind !== "file");
|
|
6006
4303
|
if (nodes.length === 0) {
|
|
6007
|
-
nodes = fileSymbolNodes(graph,
|
|
4304
|
+
nodes = fileSymbolNodes(graph, q);
|
|
6008
4305
|
}
|
|
6009
4306
|
if (nodes.length === 0 && isPhrase) {
|
|
6010
|
-
nodes = reconstructedIdentifierNodes(graph,
|
|
4307
|
+
nodes = reconstructedIdentifierNodes(graph, q);
|
|
6011
4308
|
}
|
|
6012
4309
|
if (nodes.length === 0) {
|
|
6013
|
-
const tokens = queryTokens(
|
|
4310
|
+
const tokens = queryTokens(q);
|
|
6014
4311
|
if (tokens.length >= 2) nodes = multiTokenNodes(graph, tokens);
|
|
6015
4312
|
}
|
|
6016
4313
|
const symbolCap = isPhrase ? Math.max(1, Math.floor(limit / 3)) : limit;
|
|
@@ -6025,10 +4322,10 @@ async function searchSymbols(graph, root, query, limit) {
|
|
|
6025
4322
|
const textHits = [];
|
|
6026
4323
|
let truncatedScan = false;
|
|
6027
4324
|
let totalTextMatches;
|
|
6028
|
-
const skipScan = !isPhrase && hasExactSymbolMatch(nodes,
|
|
4325
|
+
const skipScan = !isPhrase && hasExactSymbolMatch(nodes, q);
|
|
6029
4326
|
if (spare > 0 && !skipScan) {
|
|
6030
4327
|
const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
|
|
6031
|
-
const scan = await scanFiles(root,
|
|
4328
|
+
const scan = await scanFiles(root, q, spare, seen, textHits, isPhrase);
|
|
6032
4329
|
truncatedScan = scan.truncated;
|
|
6033
4330
|
if (isPhrase) totalTextMatches = scan.total;
|
|
6034
4331
|
}
|
|
@@ -6049,25 +4346,25 @@ async function searchSymbols(graph, root, query, limit) {
|
|
|
6049
4346
|
}
|
|
6050
4347
|
return result;
|
|
6051
4348
|
}
|
|
6052
|
-
function fileSymbolNodes(graph,
|
|
6053
|
-
if (!/[/\\]/.test(
|
|
6054
|
-
const norm =
|
|
4349
|
+
function fileSymbolNodes(graph, q) {
|
|
4350
|
+
if (!/[/\\]/.test(q) && !/\.\w+$/.test(q)) return [];
|
|
4351
|
+
const norm = q.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
6055
4352
|
const inFile = graph.nodes.filter(
|
|
6056
4353
|
(n) => n.kind !== "file" && (n.file === norm || n.file.endsWith(`/${norm}`))
|
|
6057
4354
|
);
|
|
6058
4355
|
return [...inFile].sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName));
|
|
6059
4356
|
}
|
|
6060
|
-
function hasExactSymbolMatch(nodes,
|
|
4357
|
+
function hasExactSymbolMatch(nodes, q) {
|
|
6061
4358
|
if (nodes.length === 0) return false;
|
|
6062
|
-
if (/^(.*):(\d+)$/.test(
|
|
6063
|
-
const lower =
|
|
6064
|
-
return nodes.some((n) => n.id ===
|
|
4359
|
+
if (/^(.*):(\d+)$/.test(q)) return true;
|
|
4360
|
+
const lower = q.toLowerCase();
|
|
4361
|
+
return nodes.some((n) => n.id === q || n.qualifiedName.toLowerCase() === lower || n.name.toLowerCase() === lower);
|
|
6065
4362
|
}
|
|
6066
|
-
function queryTokens(
|
|
6067
|
-
return [...new Set(
|
|
4363
|
+
function queryTokens(q) {
|
|
4364
|
+
return [...new Set(q.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2))];
|
|
6068
4365
|
}
|
|
6069
|
-
function reconstructedIdentifierNodes(graph,
|
|
6070
|
-
const words =
|
|
4366
|
+
function reconstructedIdentifierNodes(graph, q) {
|
|
4367
|
+
const words = q.trim().split(/\s+/).filter(Boolean);
|
|
6071
4368
|
if (words.length < 2) return [];
|
|
6072
4369
|
const concat = findNodes(graph, words.join(""));
|
|
6073
4370
|
if (concat.length) return concat.filter((n) => n.kind !== "file");
|
|
@@ -6133,13 +4430,13 @@ function walkCandidates(root) {
|
|
|
6133
4430
|
const dir = stack.pop();
|
|
6134
4431
|
let entries;
|
|
6135
4432
|
try {
|
|
6136
|
-
entries =
|
|
4433
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
6137
4434
|
} catch {
|
|
6138
4435
|
continue;
|
|
6139
4436
|
}
|
|
6140
4437
|
for (const e of entries) {
|
|
6141
4438
|
if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
|
|
6142
|
-
const abs =
|
|
4439
|
+
const abs = path2.join(dir, e.name);
|
|
6143
4440
|
if (e.isDirectory()) {
|
|
6144
4441
|
stack.push(abs);
|
|
6145
4442
|
continue;
|
|
@@ -6149,7 +4446,7 @@ function walkCandidates(root) {
|
|
|
6149
4446
|
truncated = true;
|
|
6150
4447
|
continue;
|
|
6151
4448
|
}
|
|
6152
|
-
files.push(toPosix2(
|
|
4449
|
+
files.push(toPosix2(path2.relative(root, abs)));
|
|
6153
4450
|
}
|
|
6154
4451
|
}
|
|
6155
4452
|
files.sort();
|
|
@@ -6180,11 +4477,11 @@ function ripgrepCandidates(root, needle) {
|
|
|
6180
4477
|
if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
|
|
6181
4478
|
return { files, truncated: false };
|
|
6182
4479
|
}
|
|
6183
|
-
function parseRgFileList(stdout,
|
|
4480
|
+
function parseRgFileList(stdout, sep9 = path2.sep) {
|
|
6184
4481
|
const seen = /* @__PURE__ */ new Set();
|
|
6185
4482
|
for (const raw of stdout.split("\n")) {
|
|
6186
4483
|
if (!raw) continue;
|
|
6187
|
-
const posix3 =
|
|
4484
|
+
const posix3 = sep9 === "\\" ? raw.replaceAll("\\", "/") : raw;
|
|
6188
4485
|
const rel2 = posix3.startsWith("./") ? posix3.slice(2) : posix3;
|
|
6189
4486
|
if (!rel2) continue;
|
|
6190
4487
|
const segs = rel2.split("/");
|
|
@@ -6217,7 +4514,7 @@ function readyEmbedder(local) {
|
|
|
6217
4514
|
async function retrieve(graph, question, budget, ctx) {
|
|
6218
4515
|
let mode = "lexical";
|
|
6219
4516
|
try {
|
|
6220
|
-
const
|
|
4517
|
+
const q = await withTimeout(
|
|
6221
4518
|
(async () => {
|
|
6222
4519
|
const embedder = await readyEmbedder(ctx.local);
|
|
6223
4520
|
if (!embedder) return null;
|
|
@@ -6229,7 +4526,7 @@ async function retrieve(graph, question, budget, ctx) {
|
|
|
6229
4526
|
SEMANTIC_BUDGET_MS,
|
|
6230
4527
|
"semantic path over budget"
|
|
6231
4528
|
);
|
|
6232
|
-
if (
|
|
4529
|
+
if (q) return { q, mode };
|
|
6233
4530
|
} catch {
|
|
6234
4531
|
}
|
|
6235
4532
|
return { q: queryGraph(graph, question, { budget }), mode: "lexical" };
|
|
@@ -6278,10 +4575,10 @@ var TOOLS = [
|
|
|
6278
4575
|
const question = String(args.question ?? args.query ?? "");
|
|
6279
4576
|
if (!question) return { error: "bad_request", message: "question is required" };
|
|
6280
4577
|
const budget = numOr(args.budget, 1500);
|
|
6281
|
-
const { q
|
|
4578
|
+
const { q, mode } = await retrieve(graph, question, budget, ctx);
|
|
6282
4579
|
const rawScope = String(args.scope ?? "").trim();
|
|
6283
4580
|
const scope = rawScope === "." || rawScope === "./" ? "" : rawScope.replace(/^\.\//, "");
|
|
6284
|
-
const scoped = scope ?
|
|
4581
|
+
const scoped = scope ? q.matches.filter((m) => m.node.file.startsWith(scope)) : q.matches;
|
|
6285
4582
|
const detailed = isDetailed(args);
|
|
6286
4583
|
let topImpact = null;
|
|
6287
4584
|
const top = scoped[0]?.node;
|
|
@@ -6299,7 +4596,7 @@ var TOOLS = [
|
|
|
6299
4596
|
mode,
|
|
6300
4597
|
// The fact-annotated context block is detail: concise callers navigate
|
|
6301
4598
|
// by the ranked matches and fetch nodes on demand (plan P2).
|
|
6302
|
-
...detailed ? { context:
|
|
4599
|
+
...detailed ? { context: q.context } : {},
|
|
6303
4600
|
matches: shown.map((m) => ({ name: m.node.qualifiedName, kind: m.node.kind, file: m.node.file, line: m.node.span.start, score: m.score })),
|
|
6304
4601
|
// A neutral availability flag, not a directive to keep querying: a
|
|
6305
4602
|
// retrieval tool that coaches "narrow the question / see more" nudges the
|
|
@@ -7078,12 +5375,12 @@ function detectAssistants(root, opts = {}) {
|
|
|
7078
5375
|
const which = opts.which ?? whichOnPath;
|
|
7079
5376
|
const found = [];
|
|
7080
5377
|
for (const assistant of ASSISTANTS) {
|
|
7081
|
-
const repoHit = (assistant.markers ?? []).find((m) =>
|
|
5378
|
+
const repoHit = (assistant.markers ?? []).find((m) => fs18.existsSync(path2.join(root, m)));
|
|
7082
5379
|
if (repoHit) {
|
|
7083
5380
|
found.push({ assistant, via: "repo", marker: repoHit });
|
|
7084
5381
|
continue;
|
|
7085
5382
|
}
|
|
7086
|
-
const homeHit = (assistant.homeMarkers ?? []).find((m) =>
|
|
5383
|
+
const homeHit = (assistant.homeMarkers ?? []).find((m) => fs18.existsSync(path2.join(home, m)));
|
|
7087
5384
|
if (homeHit) {
|
|
7088
5385
|
found.push({ assistant, via: "home", marker: homeHit });
|
|
7089
5386
|
continue;
|
|
@@ -7115,19 +5412,19 @@ function installAssistant(a, opts) {
|
|
|
7115
5412
|
const skipped = [];
|
|
7116
5413
|
let note;
|
|
7117
5414
|
if (a.skill) {
|
|
7118
|
-
writeFileEnsured(
|
|
5415
|
+
writeFileEnsured(path2.join(opts.root, a.skill), skillMarkdown(a.id));
|
|
7119
5416
|
wrote.push(a.skill);
|
|
7120
5417
|
}
|
|
7121
5418
|
if (a.mcp) {
|
|
7122
5419
|
const launch = opts.launch ?? detectServeLaunch();
|
|
7123
|
-
upsertMcp(
|
|
5420
|
+
upsertMcp(path2.join(opts.root, a.mcp.file), a.mcp, launch);
|
|
7124
5421
|
wrote.push(a.mcp.file);
|
|
7125
5422
|
note = launch.note;
|
|
7126
5423
|
} else {
|
|
7127
5424
|
skipped.push("mcp (host-specific setup)");
|
|
7128
5425
|
}
|
|
7129
5426
|
if (a.nudge && opts.hook !== false) {
|
|
7130
|
-
writeNudge(
|
|
5427
|
+
writeNudge(path2.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
|
|
7131
5428
|
wrote.push(a.nudge.file);
|
|
7132
5429
|
} else if (a.nudge) {
|
|
7133
5430
|
skipped.push("nudge (--no-hook)");
|
|
@@ -7137,41 +5434,41 @@ function installAssistant(a, opts) {
|
|
|
7137
5434
|
function uninstallAssistant(a, root, purge) {
|
|
7138
5435
|
const removed = [];
|
|
7139
5436
|
if (a.mcp) {
|
|
7140
|
-
const file =
|
|
5437
|
+
const file = path2.join(root, a.mcp.file);
|
|
7141
5438
|
if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
|
|
7142
5439
|
}
|
|
7143
5440
|
if (a.nudge) {
|
|
7144
|
-
const file =
|
|
5441
|
+
const file = path2.join(root, a.nudge.file);
|
|
7145
5442
|
if (a.nudge.kind === "block") {
|
|
7146
5443
|
if (removeBlock(file)) removed.push(a.nudge.file);
|
|
7147
|
-
} else if (
|
|
7148
|
-
|
|
5444
|
+
} else if (fs18.existsSync(file)) {
|
|
5445
|
+
fs18.rmSync(file);
|
|
7149
5446
|
removed.push(a.nudge.file);
|
|
7150
5447
|
}
|
|
7151
5448
|
}
|
|
7152
5449
|
if (purge && a.skill) {
|
|
7153
|
-
const file =
|
|
7154
|
-
if (
|
|
7155
|
-
|
|
5450
|
+
const file = path2.join(root, a.skill);
|
|
5451
|
+
if (fs18.existsSync(file)) {
|
|
5452
|
+
fs18.rmSync(file);
|
|
7156
5453
|
removed.push(a.skill);
|
|
7157
5454
|
}
|
|
7158
5455
|
}
|
|
7159
5456
|
return removed;
|
|
7160
5457
|
}
|
|
7161
5458
|
function writeFileEnsured(file, content) {
|
|
7162
|
-
|
|
7163
|
-
|
|
5459
|
+
fs18.mkdirSync(path2.dirname(file), { recursive: true });
|
|
5460
|
+
fs18.writeFileSync(file, content);
|
|
7164
5461
|
}
|
|
7165
5462
|
function writeNavigationConfig(root) {
|
|
7166
5463
|
ensureVibgrateGitignore(root);
|
|
7167
|
-
const rel2 =
|
|
5464
|
+
const rel2 = path2.join(".vibgrate", "mcp-navigation.json");
|
|
7168
5465
|
const doc = {
|
|
7169
5466
|
_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.",
|
|
7170
5467
|
hot_core: [...HOT_TOOLS],
|
|
7171
5468
|
deferred: deferredToolNames(),
|
|
7172
5469
|
toolset: navigationToolsetConfig()
|
|
7173
5470
|
};
|
|
7174
|
-
writeFileEnsured(
|
|
5471
|
+
writeFileEnsured(path2.join(root, rel2), `${JSON.stringify(doc, null, 2)}
|
|
7175
5472
|
`);
|
|
7176
5473
|
return rel2;
|
|
7177
5474
|
}
|
|
@@ -7186,7 +5483,7 @@ function upsertMcp(file, target, launch) {
|
|
|
7186
5483
|
`);
|
|
7187
5484
|
}
|
|
7188
5485
|
function removeMcp(file, target) {
|
|
7189
|
-
if (!
|
|
5486
|
+
if (!fs18.existsSync(file)) return false;
|
|
7190
5487
|
const config = readJson(file);
|
|
7191
5488
|
const bag = config[target.key];
|
|
7192
5489
|
if (!bag || !(bag.vg !== void 0)) return false;
|
|
@@ -7205,8 +5502,8 @@ function refreshInstalledInstructions(root, smallRepo) {
|
|
|
7205
5502
|
};
|
|
7206
5503
|
for (const a of ASSISTANTS) {
|
|
7207
5504
|
if (a.skill) {
|
|
7208
|
-
const file =
|
|
7209
|
-
if (
|
|
5505
|
+
const file = path2.join(root, a.skill);
|
|
5506
|
+
if (fs18.existsSync(file)) {
|
|
7210
5507
|
const text = readTextSafe(file);
|
|
7211
5508
|
const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
|
|
7212
5509
|
if (from !== null) {
|
|
@@ -7216,8 +5513,8 @@ function refreshInstalledInstructions(root, smallRepo) {
|
|
|
7216
5513
|
}
|
|
7217
5514
|
}
|
|
7218
5515
|
if (a.nudge) {
|
|
7219
|
-
const file =
|
|
7220
|
-
if (!
|
|
5516
|
+
const file = path2.join(root, a.nudge.file);
|
|
5517
|
+
if (!fs18.existsSync(file)) continue;
|
|
7221
5518
|
const text = readTextSafe(file);
|
|
7222
5519
|
if (text === null) continue;
|
|
7223
5520
|
if (a.nudge.kind === "block") {
|
|
@@ -7241,7 +5538,7 @@ function refreshInstalledInstructions(root, smallRepo) {
|
|
|
7241
5538
|
}
|
|
7242
5539
|
function readTextSafe(file) {
|
|
7243
5540
|
try {
|
|
7244
|
-
return
|
|
5541
|
+
return fs18.readFileSync(file, "utf8");
|
|
7245
5542
|
} catch {
|
|
7246
5543
|
return null;
|
|
7247
5544
|
}
|
|
@@ -7260,8 +5557,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
|
|
|
7260
5557
|
upsertBlock(file, nudgeMarkdown(smallRepo, client));
|
|
7261
5558
|
}
|
|
7262
5559
|
function upsertBlock(file, block) {
|
|
7263
|
-
|
|
7264
|
-
let existing =
|
|
5560
|
+
fs18.mkdirSync(path2.dirname(file), { recursive: true });
|
|
5561
|
+
let existing = fs18.existsSync(file) ? fs18.readFileSync(file, "utf8") : "";
|
|
7265
5562
|
const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
|
|
7266
5563
|
if (re.test(existing)) existing = existing.replace(re, block);
|
|
7267
5564
|
else existing = existing.length ? `${existing.replace(/\s*$/, "")}
|
|
@@ -7269,20 +5566,20 @@ function upsertBlock(file, block) {
|
|
|
7269
5566
|
${block}
|
|
7270
5567
|
` : `${block}
|
|
7271
5568
|
`;
|
|
7272
|
-
|
|
5569
|
+
fs18.writeFileSync(file, existing);
|
|
7273
5570
|
}
|
|
7274
5571
|
function removeBlock(file) {
|
|
7275
|
-
if (!
|
|
7276
|
-
const existing =
|
|
5572
|
+
if (!fs18.existsSync(file)) return false;
|
|
5573
|
+
const existing = fs18.readFileSync(file, "utf8");
|
|
7277
5574
|
const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
|
|
7278
5575
|
if (!re.test(existing)) return false;
|
|
7279
|
-
|
|
5576
|
+
fs18.writeFileSync(file, existing.replace(re, "\n"));
|
|
7280
5577
|
return true;
|
|
7281
5578
|
}
|
|
7282
5579
|
function readJson(file) {
|
|
7283
|
-
if (!
|
|
5580
|
+
if (!fs18.existsSync(file)) return {};
|
|
7284
5581
|
try {
|
|
7285
|
-
const parsed = JSON.parse(
|
|
5582
|
+
const parsed = JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
7286
5583
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
7287
5584
|
return parsed;
|
|
7288
5585
|
}
|
|
@@ -7302,7 +5599,7 @@ function escapeRe(s) {
|
|
|
7302
5599
|
}
|
|
7303
5600
|
var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
7304
5601
|
function refreshLockPath(root) {
|
|
7305
|
-
return
|
|
5602
|
+
return path2.join(cacheDir(root), "refresh.lock");
|
|
7306
5603
|
}
|
|
7307
5604
|
async function refreshIfStale(root, opts = {}) {
|
|
7308
5605
|
const probe = probeFreshness(root);
|
|
@@ -7333,8 +5630,8 @@ async function refreshIfStale(root, opts = {}) {
|
|
|
7333
5630
|
const dir = vibgrateDir(root);
|
|
7334
5631
|
writeArtifacts(result.graph, {
|
|
7335
5632
|
root,
|
|
7336
|
-
report:
|
|
7337
|
-
html:
|
|
5633
|
+
report: fs18.existsSync(path2.join(dir, "GRAPH_REPORT.md")),
|
|
5634
|
+
html: fs18.existsSync(path2.join(dir, "graph.html"))
|
|
7338
5635
|
});
|
|
7339
5636
|
}
|
|
7340
5637
|
writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
|
|
@@ -7353,583 +5650,7 @@ async function refreshIfStale(root, opts = {}) {
|
|
|
7353
5650
|
releaseLock(lock);
|
|
7354
5651
|
}
|
|
7355
5652
|
}
|
|
7356
|
-
var LEDGER = "savings.jsonl";
|
|
7357
|
-
var PER_FILE_TOKENS = 400;
|
|
7358
|
-
var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node", "search_symbols"]);
|
|
7359
|
-
var CLI_TOOL_ALIASES = {
|
|
7360
|
-
ask: "query_graph",
|
|
7361
|
-
show: "get_node",
|
|
7362
|
-
impact: "impact_of",
|
|
7363
|
-
path: "find_path",
|
|
7364
|
-
hubs: "list_hubs",
|
|
7365
|
-
areas: "list_areas",
|
|
7366
|
-
tree: "tree"
|
|
7367
|
-
};
|
|
7368
|
-
function sanitizeClient(name) {
|
|
7369
|
-
if (typeof name !== "string") return "unknown";
|
|
7370
|
-
const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
7371
|
-
return cleaned || "unknown";
|
|
7372
|
-
}
|
|
7373
|
-
function recordCliCall(root, entry, now) {
|
|
7374
|
-
recordSaving(
|
|
7375
|
-
root,
|
|
7376
|
-
{
|
|
7377
|
-
tool: entry.tool,
|
|
7378
|
-
source: "cli",
|
|
7379
|
-
client: sanitizeClient(entry.client),
|
|
7380
|
-
outcome: entry.outcome,
|
|
7381
|
-
vgTokens: entry.vgTokens ?? 0,
|
|
7382
|
-
baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
|
|
7383
|
-
},
|
|
7384
|
-
now
|
|
7385
|
-
);
|
|
7386
|
-
}
|
|
7387
|
-
function savingsLedgerPath(root) {
|
|
7388
|
-
return path17.join(cacheDir(root), LEDGER);
|
|
7389
|
-
}
|
|
7390
|
-
function ledgerPath(root) {
|
|
7391
|
-
return savingsLedgerPath(root);
|
|
7392
|
-
}
|
|
7393
|
-
function savingsRecorded(root) {
|
|
7394
|
-
return fs17.existsSync(ledgerPath(root));
|
|
7395
|
-
}
|
|
7396
|
-
function clearSavings(root) {
|
|
7397
|
-
const file = ledgerPath(root);
|
|
7398
|
-
const existed = fs17.existsSync(file);
|
|
7399
|
-
fs17.rmSync(file, { force: true });
|
|
7400
|
-
fs17.rmSync(path17.join(cacheDir(root), "stats-share.json"), { force: true });
|
|
7401
|
-
return existed;
|
|
7402
|
-
}
|
|
7403
|
-
function recordSaving(root, entry, now) {
|
|
7404
|
-
try {
|
|
7405
|
-
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
7406
|
-
const line = JSON.stringify({ ts: now, ...entry });
|
|
7407
|
-
fs17.appendFileSync(ledgerPath(root), line + "\n");
|
|
7408
|
-
} catch {
|
|
7409
|
-
}
|
|
7410
|
-
}
|
|
7411
|
-
var DEFAULT_RATE_PER_M = 3;
|
|
7412
|
-
var DEFAULT_RATE_LABEL = "input @ $3/1M";
|
|
7413
|
-
function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
|
|
7414
|
-
const file = ledgerPath(root);
|
|
7415
|
-
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
7416
|
-
let queries = 0;
|
|
7417
|
-
let vgTokens = 0;
|
|
7418
|
-
let baselineTokens = 0;
|
|
7419
|
-
if (fs17.existsSync(file)) {
|
|
7420
|
-
for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
|
|
7421
|
-
if (!line.trim()) continue;
|
|
7422
|
-
try {
|
|
7423
|
-
const e = JSON.parse(line);
|
|
7424
|
-
if (e.ts < cutoff) continue;
|
|
7425
|
-
if (!SAVINGS_TOOLS.has(e.tool)) continue;
|
|
7426
|
-
queries++;
|
|
7427
|
-
vgTokens += e.vgTokens;
|
|
7428
|
-
baselineTokens += e.baselineTokens;
|
|
7429
|
-
} catch {
|
|
7430
|
-
}
|
|
7431
|
-
}
|
|
7432
|
-
}
|
|
7433
|
-
const estCostVg = vgTokens / 1e6 * ratePerM;
|
|
7434
|
-
const estCostBaseline = baselineTokens / 1e6 * ratePerM;
|
|
7435
|
-
return {
|
|
7436
|
-
enabled: savingsRecorded(root),
|
|
7437
|
-
days,
|
|
7438
|
-
queries,
|
|
7439
|
-
vgTokens,
|
|
7440
|
-
baselineTokens,
|
|
7441
|
-
ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
|
|
7442
|
-
estCostVg: round22(estCostVg),
|
|
7443
|
-
estCostBaseline: round22(estCostBaseline),
|
|
7444
|
-
saved: round22(estCostBaseline - estCostVg),
|
|
7445
|
-
rateLabel: DEFAULT_RATE_LABEL
|
|
7446
|
-
};
|
|
7447
|
-
}
|
|
7448
|
-
function round22(x) {
|
|
7449
|
-
return Math.round(x * 100) / 100;
|
|
7450
|
-
}
|
|
7451
|
-
function toDimensionStats(byKey) {
|
|
7452
|
-
return [...byKey.entries()].map(([key, r]) => {
|
|
7453
|
-
const calls = r.complete + r.partial + r.miss;
|
|
7454
|
-
return {
|
|
7455
|
-
key,
|
|
7456
|
-
calls,
|
|
7457
|
-
complete: r.complete,
|
|
7458
|
-
partial: r.partial,
|
|
7459
|
-
miss: r.miss,
|
|
7460
|
-
successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
|
|
7461
|
-
};
|
|
7462
|
-
}).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
|
|
7463
|
-
}
|
|
7464
|
-
function readUsage(root, days, now) {
|
|
7465
|
-
const file = ledgerPath(root);
|
|
7466
|
-
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
7467
|
-
const byTool = /* @__PURE__ */ new Map();
|
|
7468
|
-
const bySource = /* @__PURE__ */ new Map();
|
|
7469
|
-
const byClient = /* @__PURE__ */ new Map();
|
|
7470
|
-
const msByTool = /* @__PURE__ */ new Map();
|
|
7471
|
-
const bump2 = (m, key, outcome) => {
|
|
7472
|
-
const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
|
|
7473
|
-
row[outcome]++;
|
|
7474
|
-
m.set(key, row);
|
|
7475
|
-
};
|
|
7476
|
-
if (fs17.existsSync(file)) {
|
|
7477
|
-
for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
|
|
7478
|
-
if (!line.trim()) continue;
|
|
7479
|
-
try {
|
|
7480
|
-
const e = JSON.parse(line);
|
|
7481
|
-
if (e.ts < cutoff) continue;
|
|
7482
|
-
const outcome = e.outcome ?? "complete";
|
|
7483
|
-
bump2(byTool, e.tool, outcome);
|
|
7484
|
-
bump2(bySource, e.source ?? "mcp", outcome);
|
|
7485
|
-
bump2(byClient, e.client ?? "unknown", outcome);
|
|
7486
|
-
if (typeof e.ms === "number" && e.ms >= 0) {
|
|
7487
|
-
const timing = msByTool.get(e.tool) ?? { sum: 0, n: 0 };
|
|
7488
|
-
timing.sum += e.ms;
|
|
7489
|
-
timing.n++;
|
|
7490
|
-
msByTool.set(e.tool, timing);
|
|
7491
|
-
}
|
|
7492
|
-
} catch {
|
|
7493
|
-
}
|
|
7494
|
-
}
|
|
7495
|
-
}
|
|
7496
|
-
const commands = toDimensionStats(byTool).map((d) => {
|
|
7497
|
-
const timing = msByTool.get(d.key);
|
|
7498
|
-
return {
|
|
7499
|
-
tool: d.key,
|
|
7500
|
-
calls: d.calls,
|
|
7501
|
-
complete: d.complete,
|
|
7502
|
-
partial: d.partial,
|
|
7503
|
-
miss: d.miss,
|
|
7504
|
-
successPct: d.successPct,
|
|
7505
|
-
avgMs: timing && timing.n > 0 ? Math.round(timing.sum / timing.n) : null
|
|
7506
|
-
};
|
|
7507
|
-
});
|
|
7508
|
-
const totals = commands.reduce(
|
|
7509
|
-
(t, c) => ({
|
|
7510
|
-
calls: t.calls + c.calls,
|
|
7511
|
-
complete: t.complete + c.complete,
|
|
7512
|
-
partial: t.partial + c.partial,
|
|
7513
|
-
miss: t.miss + c.miss
|
|
7514
|
-
}),
|
|
7515
|
-
{ calls: 0, complete: 0, partial: 0, miss: 0 }
|
|
7516
|
-
);
|
|
7517
|
-
const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
|
|
7518
|
-
return {
|
|
7519
|
-
enabled: savingsRecorded(root),
|
|
7520
|
-
days,
|
|
7521
|
-
commands,
|
|
7522
|
-
sources: toDimensionStats(bySource),
|
|
7523
|
-
clients: toDimensionStats(byClient),
|
|
7524
|
-
totals,
|
|
7525
|
-
avgSuccessPct
|
|
7526
|
-
};
|
|
7527
|
-
}
|
|
7528
|
-
var PROBE_INTERVAL_MS = 2e3;
|
|
7529
|
-
var MAX_PROBE_INTERVAL_MS = 3e4;
|
|
7530
|
-
var PROBE_DUTY_FACTOR = 20;
|
|
7531
|
-
var REFRESH_BUDGET_MS = 5e3;
|
|
7532
|
-
var FAILURE_COOLDOWN_MS = 6e4;
|
|
7533
|
-
var GraphSource = class {
|
|
7534
|
-
constructor(graphPath, refresh = false, tuning = {}) {
|
|
7535
|
-
this.graphPath = graphPath;
|
|
7536
|
-
this.refresh = refresh;
|
|
7537
|
-
this.tuning = tuning;
|
|
7538
|
-
this.root = path17.dirname(path17.dirname(graphPath));
|
|
7539
|
-
}
|
|
7540
|
-
graphPath;
|
|
7541
|
-
refresh;
|
|
7542
|
-
tuning;
|
|
7543
|
-
cachedMtimeMs = -1;
|
|
7544
|
-
cached = null;
|
|
7545
|
-
root;
|
|
7546
|
-
lastProbeAt = 0;
|
|
7547
|
-
failedUntil = 0;
|
|
7548
|
-
inflight = null;
|
|
7549
|
-
/** Self-tuned: grows with measured probe cost so huge repos aren't penalized. */
|
|
7550
|
-
probeIntervalMs = PROBE_INTERVAL_MS;
|
|
7551
|
-
/** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
|
|
7552
|
-
async get() {
|
|
7553
|
-
if (this.refresh) await this.maybeRefresh();
|
|
7554
|
-
const stat = fs17.statSync(this.graphPath);
|
|
7555
|
-
if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
|
|
7556
|
-
this.cached = parseGraph(fs17.readFileSync(this.graphPath, "utf8"));
|
|
7557
|
-
this.cachedMtimeMs = stat.mtimeMs;
|
|
7558
|
-
}
|
|
7559
|
-
return this.cached;
|
|
7560
|
-
}
|
|
7561
|
-
/**
|
|
7562
|
-
* Debounced, single-flight refresh. Never throws — a refresh problem must
|
|
7563
|
-
* degrade to "answer from the current map", not break the tool call.
|
|
7564
|
-
*/
|
|
7565
|
-
async maybeRefresh() {
|
|
7566
|
-
const now = Date.now();
|
|
7567
|
-
if (!this.inflight) {
|
|
7568
|
-
const interval = this.tuning.probeIntervalMs ?? this.probeIntervalMs;
|
|
7569
|
-
if (now < this.failedUntil || now - this.lastProbeAt < interval) return;
|
|
7570
|
-
this.lastProbeAt = now;
|
|
7571
|
-
this.inflight = refreshIfStale(this.root).then((r) => {
|
|
7572
|
-
if (r.status === "error") this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
|
|
7573
|
-
if (r.status === "fresh" || r.status === "no-snapshot" || r.status === "locked") {
|
|
7574
|
-
const cost = Date.now() - now;
|
|
7575
|
-
this.probeIntervalMs = Math.min(
|
|
7576
|
-
MAX_PROBE_INTERVAL_MS,
|
|
7577
|
-
Math.max(PROBE_INTERVAL_MS, cost * PROBE_DUTY_FACTOR)
|
|
7578
|
-
);
|
|
7579
|
-
}
|
|
7580
|
-
}).catch(() => {
|
|
7581
|
-
this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
|
|
7582
|
-
}).finally(() => {
|
|
7583
|
-
this.inflight = null;
|
|
7584
|
-
});
|
|
7585
|
-
}
|
|
7586
|
-
await Promise.race([this.inflight, sleep(this.tuning.refreshBudgetMs ?? REFRESH_BUDGET_MS)]);
|
|
7587
|
-
}
|
|
7588
|
-
};
|
|
7589
|
-
function createServer(source, opts = {}) {
|
|
7590
|
-
const { savings = false, shareStats = false, local = false, dedup = false, stats } = opts;
|
|
7591
|
-
const record = savings || shareStats;
|
|
7592
|
-
const root = path17.dirname(path17.dirname(source.graphPath));
|
|
7593
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7594
|
-
const server = new Server(
|
|
7595
|
-
{ name: "vg", version: VERSION },
|
|
7596
|
-
{
|
|
7597
|
-
capabilities: { tools: {} },
|
|
7598
|
-
// Routing guidance once at the server level (hosts that surface
|
|
7599
|
-
// `instructions` get it at zero per-step schema cost): the flashlight
|
|
7600
|
-
// vs the map.
|
|
7601
|
-
instructions: 'vg is a code map. Use search_symbols to find a known name or literal string fast \u2014 a multi-word/quoted phrase runs a complete literal sweep and reports totalTextMatches, so reach for it instead of grep even for plain-string "find every occurrence" lookups. Use orient/query_graph for meaning: symptoms, relationships, and what-breaks-if. Responses are concise by default; pass response_format:"detailed" only when a node proves load-bearing. Navigate as little as possible: one good search/query usually locates the code. As soon as you have the file and line, read that file and make the edit \u2014 do not call further graph tools unless the edit fails or the match was wrong. For how-do-I-use-this-library questions call resolve_library once, then library_docs with the returned targetId and a focused query: the docs are official and matched to the version THIS project has installed (drift-annotated) \u2014 prefer them over web search or training-data recall when they conflict. Skip them for language built-ins or APIs already shown in context. If two library_docs calls have not surfaced the section you need, read the package source under node_modules instead of searching again.'
|
|
7602
|
-
}
|
|
7603
|
-
);
|
|
7604
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
7605
|
-
tools: TOOLS.map((t) => ({
|
|
7606
|
-
name: t.name,
|
|
7607
|
-
description: t.description,
|
|
7608
|
-
inputSchema: t.inputSchema,
|
|
7609
|
-
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
7610
|
-
}))
|
|
7611
|
-
}));
|
|
7612
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
7613
|
-
const tool = TOOLS.find((t) => t.name === request.params.name);
|
|
7614
|
-
if (!tool) {
|
|
7615
|
-
return errorResult(`unknown tool "${request.params.name}"`);
|
|
7616
|
-
}
|
|
7617
|
-
let graph;
|
|
7618
|
-
try {
|
|
7619
|
-
graph = await source.get();
|
|
7620
|
-
} catch {
|
|
7621
|
-
return errorResult(
|
|
7622
|
-
"no code map found. Run `vg` in the project to build .vibgrate/graph.json, then retry."
|
|
7623
|
-
);
|
|
7624
|
-
}
|
|
7625
|
-
const startedAt = Date.now();
|
|
7626
|
-
try {
|
|
7627
|
-
const args = request.params.arguments ?? {};
|
|
7628
|
-
const result = await tool.handler(graph, args, { root, local, dedup, seen });
|
|
7629
|
-
const ms = Date.now() - startedAt;
|
|
7630
|
-
stats?.record({ ...measureCall(tool.name, result), client: sanitizeClient(detectClient(server)), ms });
|
|
7631
|
-
if (record) recordUsage(root, tool.name, result, detectClient(server), ms);
|
|
7632
|
-
return renderToolResult(result);
|
|
7633
|
-
} catch (err) {
|
|
7634
|
-
stats?.record({
|
|
7635
|
-
tool: tool.name,
|
|
7636
|
-
client: sanitizeClient(detectClient(server)),
|
|
7637
|
-
outcome: "miss",
|
|
7638
|
-
ms: Date.now() - startedAt,
|
|
7639
|
-
vgTokens: 0,
|
|
7640
|
-
baselineTokens: 0
|
|
7641
|
-
});
|
|
7642
|
-
return errorResult(`tool "${tool.name}" failed: ${err.message}`);
|
|
7643
|
-
}
|
|
7644
|
-
});
|
|
7645
|
-
return server;
|
|
7646
|
-
}
|
|
7647
|
-
async function serveStdio(graphPath, opts = {}) {
|
|
7648
|
-
const source = new GraphSource(graphPath, opts.refresh !== false);
|
|
7649
|
-
const server = createServer(source, opts);
|
|
7650
|
-
warmEmbedderInBackground(opts.local);
|
|
7651
|
-
await server.connect(new StdioServerTransport());
|
|
7652
|
-
}
|
|
7653
|
-
function sleep(ms) {
|
|
7654
|
-
return new Promise((resolve12) => {
|
|
7655
|
-
const timer = setTimeout(resolve12, ms);
|
|
7656
|
-
timer.unref?.();
|
|
7657
|
-
});
|
|
7658
|
-
}
|
|
7659
|
-
function recordUsage(root, tool, result, client, ms) {
|
|
7660
|
-
recordSaving(
|
|
7661
|
-
root,
|
|
7662
|
-
{ ...measureCall(tool, result), source: "mcp", client: sanitizeClient(client), ...ms !== void 0 ? { ms } : {} },
|
|
7663
|
-
Date.now()
|
|
7664
|
-
);
|
|
7665
|
-
}
|
|
7666
|
-
function measureCall(tool, result) {
|
|
7667
|
-
const outcome = classifyOutcome(result);
|
|
7668
|
-
let vgTokens = 0;
|
|
7669
|
-
let baselineTokens = 0;
|
|
7670
|
-
if (SAVINGS_TOOLS.has(tool) && result && typeof result === "object") {
|
|
7671
|
-
vgTokens = countTokens(renderedText(renderToolResult(result)));
|
|
7672
|
-
baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
|
|
7673
|
-
}
|
|
7674
|
-
return { tool, outcome, vgTokens, baselineTokens };
|
|
7675
|
-
}
|
|
7676
|
-
function detectClient(server) {
|
|
7677
|
-
try {
|
|
7678
|
-
const info = server.getClientVersion?.();
|
|
7679
|
-
return typeof info?.name === "string" ? info.name : void 0;
|
|
7680
|
-
} catch {
|
|
7681
|
-
return void 0;
|
|
7682
|
-
}
|
|
7683
|
-
}
|
|
7684
|
-
function classifyOutcome(result) {
|
|
7685
|
-
if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
|
|
7686
|
-
if (!result || typeof result !== "object") return "miss";
|
|
7687
|
-
const r = result;
|
|
7688
|
-
if (typeof r.error === "string" && r.error) return "miss";
|
|
7689
|
-
if (r.connected === false) return "miss";
|
|
7690
|
-
if (Array.isArray(r.matches) && r.matches.length === 0) return "miss";
|
|
7691
|
-
return isPartial(r) ? "partial" : "complete";
|
|
7692
|
-
}
|
|
7693
|
-
function isPartial(r) {
|
|
7694
|
-
if (r.moreAvailable === true) return true;
|
|
7695
|
-
if (r._truncated && typeof r._truncated === "object") return true;
|
|
7696
|
-
for (const [key, value] of Object.entries(r)) {
|
|
7697
|
-
if (typeof value !== "number" || !key.endsWith("Total")) continue;
|
|
7698
|
-
const base = key.slice(0, -"Total".length);
|
|
7699
|
-
const shown = Array.isArray(r[base]) ? r[base].length : 0;
|
|
7700
|
-
if (value > shown) return true;
|
|
7701
|
-
}
|
|
7702
|
-
return false;
|
|
7703
|
-
}
|
|
7704
|
-
function renderedText(rendered) {
|
|
7705
|
-
const block = rendered.content?.find((b) => b.type === "text");
|
|
7706
|
-
return block && "text" in block && typeof block.text === "string" ? block.text : "";
|
|
7707
|
-
}
|
|
7708
|
-
function referencedFiles(result) {
|
|
7709
|
-
const r = result;
|
|
7710
|
-
const files = /* @__PURE__ */ new Set();
|
|
7711
|
-
if (typeof r.file === "string" && r.file) files.add(r.file);
|
|
7712
|
-
for (const m of asArray(r.matches)) {
|
|
7713
|
-
const f = m.file;
|
|
7714
|
-
if (typeof f === "string" && f) files.add(f);
|
|
7715
|
-
}
|
|
7716
|
-
for (const name of [...asArray(r.calls), ...asArray(r.calledBy)]) {
|
|
7717
|
-
if (typeof name !== "string") continue;
|
|
7718
|
-
const i = name.indexOf(":");
|
|
7719
|
-
if (i > 0) files.add(name.slice(0, i));
|
|
7720
|
-
}
|
|
7721
|
-
return files;
|
|
7722
|
-
}
|
|
7723
|
-
function asArray(v) {
|
|
7724
|
-
return Array.isArray(v) ? v : [];
|
|
7725
|
-
}
|
|
7726
|
-
function errorResult(message) {
|
|
7727
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
7728
|
-
}
|
|
7729
|
-
|
|
7730
|
-
// src/engine/export.ts
|
|
7731
|
-
function formatForExt(ext) {
|
|
7732
|
-
switch (ext.toLowerCase()) {
|
|
7733
|
-
case ".json":
|
|
7734
|
-
return "json";
|
|
7735
|
-
case ".ndjson":
|
|
7736
|
-
return "ndjson";
|
|
7737
|
-
case ".graphml":
|
|
7738
|
-
return "graphml";
|
|
7739
|
-
case ".dot":
|
|
7740
|
-
case ".gv":
|
|
7741
|
-
return "dot";
|
|
7742
|
-
case ".cypher":
|
|
7743
|
-
return "cypher";
|
|
7744
|
-
case ".sql":
|
|
7745
|
-
return "sql";
|
|
7746
|
-
case ".md":
|
|
7747
|
-
return "md";
|
|
7748
|
-
case ".html":
|
|
7749
|
-
return "html";
|
|
7750
|
-
default:
|
|
7751
|
-
return null;
|
|
7752
|
-
}
|
|
7753
|
-
}
|
|
7754
|
-
function exportGraph(format, ctx) {
|
|
7755
|
-
switch (format) {
|
|
7756
|
-
case "json":
|
|
7757
|
-
return serializeGraph(ctx.graph);
|
|
7758
|
-
case "md":
|
|
7759
|
-
return renderReport(ctx.graph);
|
|
7760
|
-
case "html":
|
|
7761
|
-
return renderHtml(ctx.graph);
|
|
7762
|
-
case "ndjson":
|
|
7763
|
-
return ndjson(ctx.graph);
|
|
7764
|
-
case "graphml":
|
|
7765
|
-
return graphml(ctx.graph);
|
|
7766
|
-
case "dot":
|
|
7767
|
-
return dot(ctx.graph);
|
|
7768
|
-
case "cypher":
|
|
7769
|
-
return cypher(ctx.graph);
|
|
7770
|
-
case "sql":
|
|
7771
|
-
return sql(ctx);
|
|
7772
|
-
case "cyclonedx":
|
|
7773
|
-
return cyclonedx(ctx);
|
|
7774
|
-
case "spdx":
|
|
7775
|
-
return spdx(ctx);
|
|
7776
|
-
}
|
|
7777
|
-
}
|
|
7778
|
-
function ndjson(graph) {
|
|
7779
|
-
const rows = graph.facts && graph.facts.length ? graph.facts : graph.nodes;
|
|
7780
|
-
return rows.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
7781
|
-
}
|
|
7782
|
-
function esc2(s) {
|
|
7783
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7784
|
-
}
|
|
7785
|
-
function graphml(graph) {
|
|
7786
|
-
const lines = [];
|
|
7787
|
-
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
7788
|
-
lines.push('<graphml xmlns="http://graphml.graphdrawing.org/xmlns">');
|
|
7789
|
-
lines.push(' <key id="name" for="node" attr.name="name" attr.type="string"/>');
|
|
7790
|
-
lines.push(' <key id="kind" for="node" attr.name="kind" attr.type="string"/>');
|
|
7791
|
-
lines.push(' <key id="file" for="node" attr.name="file" attr.type="string"/>');
|
|
7792
|
-
lines.push(' <key id="ekind" for="edge" attr.name="kind" attr.type="string"/>');
|
|
7793
|
-
lines.push(' <graph edgedefault="directed">');
|
|
7794
|
-
for (const n of graph.nodes) {
|
|
7795
|
-
lines.push(` <node id="${esc2(n.id)}"><data key="name">${esc2(n.qualifiedName)}</data><data key="kind">${esc2(n.kind)}</data><data key="file">${esc2(n.file)}</data></node>`);
|
|
7796
|
-
}
|
|
7797
|
-
for (const e of graph.edges) {
|
|
7798
|
-
lines.push(` <edge source="${esc2(e.src)}" target="${esc2(e.dst)}"><data key="ekind">${esc2(e.kind)}</data></edge>`);
|
|
7799
|
-
}
|
|
7800
|
-
lines.push(" </graph>");
|
|
7801
|
-
lines.push("</graphml>");
|
|
7802
|
-
return lines.join("\n") + "\n";
|
|
7803
|
-
}
|
|
7804
|
-
function dot(graph) {
|
|
7805
|
-
const lines = ["digraph vg {"];
|
|
7806
|
-
for (const n of graph.nodes) lines.push(` "${n.id}" [label="${dotEsc(n.qualifiedName)}"];`);
|
|
7807
|
-
for (const e of graph.edges) lines.push(` "${e.src}" -> "${e.dst}" [label="${e.kind}"];`);
|
|
7808
|
-
lines.push("}");
|
|
7809
|
-
return lines.join("\n") + "\n";
|
|
7810
|
-
}
|
|
7811
|
-
function dotEsc(s) {
|
|
7812
|
-
return s.replace(/"/g, '\\"');
|
|
7813
|
-
}
|
|
7814
|
-
function cypher(graph) {
|
|
7815
|
-
const lines = [];
|
|
7816
|
-
for (const n of graph.nodes) {
|
|
7817
|
-
lines.push(
|
|
7818
|
-
`CREATE (:\`${cypherLabel(n.kind)}\` {id:${q(n.id)}, name:${q(n.qualifiedName)}, file:${q(n.file)}});`
|
|
7819
|
-
);
|
|
7820
|
-
}
|
|
7821
|
-
for (const e of graph.edges) {
|
|
7822
|
-
lines.push(
|
|
7823
|
-
`MATCH (a {id:${q(e.src)}}),(b {id:${q(e.dst)}}) CREATE (a)-[:\`${cypherLabel(e.kind)}\`]->(b);`
|
|
7824
|
-
);
|
|
7825
|
-
}
|
|
7826
|
-
return lines.join("\n") + "\n";
|
|
7827
|
-
}
|
|
7828
|
-
function cypherLabel(s) {
|
|
7829
|
-
return s.replace(/[^A-Za-z0-9_]/g, "_");
|
|
7830
|
-
}
|
|
7831
|
-
function q(s) {
|
|
7832
|
-
return JSON.stringify(s);
|
|
7833
|
-
}
|
|
7834
|
-
function sql(ctx) {
|
|
7835
|
-
const graph = ctx.graph;
|
|
7836
|
-
const lines = [];
|
|
7837
|
-
lines.push("BEGIN;");
|
|
7838
|
-
lines.push("CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);");
|
|
7839
|
-
const meta = [
|
|
7840
|
-
["schemaVersion", graph.schemaVersion],
|
|
7841
|
-
["generatedAt", ctx.generatedAt],
|
|
7842
|
-
["corpusHash", graph.provenance.corpusHash],
|
|
7843
|
-
["tool", graph.provenance.tool],
|
|
7844
|
-
["toolVersion", graph.provenance.version],
|
|
7845
|
-
["resolver", graph.provenance.resolver.join(",")],
|
|
7846
|
-
["fingerprint", graph.provenance.toolchain?.fingerprint ?? null]
|
|
7847
|
-
];
|
|
7848
|
-
for (const [k, v] of meta) lines.push(`INSERT INTO meta VALUES (${sqlStr(k)}, ${sqlStr(v)});`);
|
|
7849
|
-
lines.push(
|
|
7850
|
-
"CREATE TABLE IF NOT EXISTS nodes (id TEXT PRIMARY KEY, kind TEXT, name TEXT, qualified_name TEXT, file TEXT, span_start INTEGER, span_end INTEGER, lang TEXT, visibility TEXT, signature TEXT, importance REAL, area INTEGER, is_hub INTEGER, tested INTEGER, coverage REAL);"
|
|
7851
|
-
);
|
|
7852
|
-
for (const n of graph.nodes) {
|
|
7853
|
-
lines.push(
|
|
7854
|
-
`INSERT INTO nodes VALUES (${sqlStr(n.id)}, ${sqlStr(n.kind)}, ${sqlStr(n.name)}, ${sqlStr(n.qualifiedName)}, ${sqlStr(n.file)}, ${sqlNum(n.span.start)}, ${sqlNum(n.span.end)}, ${sqlStr(n.lang)}, ${sqlStr(n.visibility)}, ${sqlStr(n.signature)}, ${sqlNum(n.importance)}, ${sqlNum(n.area)}, ${sqlBool(n.isHub)}, ${sqlBool(n.tested)}, ${sqlNum(n.coverage)});`
|
|
7855
|
-
);
|
|
7856
|
-
}
|
|
7857
|
-
lines.push(
|
|
7858
|
-
'CREATE TABLE IF NOT EXISTS edges (id TEXT PRIMARY KEY, kind TEXT, src TEXT, dst TEXT, resolution TEXT, confidence REAL, epistemic TEXT, surprise REAL, "count" INTEGER);'
|
|
7859
|
-
);
|
|
7860
|
-
for (const e of graph.edges) {
|
|
7861
|
-
lines.push(
|
|
7862
|
-
`INSERT INTO edges VALUES (${sqlStr(e.id)}, ${sqlStr(e.kind)}, ${sqlStr(e.src)}, ${sqlStr(e.dst)}, ${sqlStr(e.resolution)}, ${sqlNum(e.confidence)}, ${sqlStr(e.epistemic)}, ${sqlNum(e.surprise)}, ${sqlNum(e.count)});`
|
|
7863
|
-
);
|
|
7864
|
-
}
|
|
7865
|
-
lines.push("CREATE TABLE IF NOT EXISTS areas (id INTEGER PRIMARY KEY, label TEXT, size INTEGER, cohesion REAL, external_edges INTEGER);");
|
|
7866
|
-
lines.push("CREATE TABLE IF NOT EXISTS area_members (area_id INTEGER, node_id TEXT);");
|
|
7867
|
-
for (const a of graph.areas) {
|
|
7868
|
-
lines.push(`INSERT INTO areas VALUES (${sqlNum(a.id)}, ${sqlStr(a.label)}, ${sqlNum(a.size)}, ${sqlNum(a.cohesion)}, ${sqlNum(a.externalEdges)});`);
|
|
7869
|
-
for (const m of a.members) lines.push(`INSERT INTO area_members VALUES (${sqlNum(a.id)}, ${sqlStr(m)});`);
|
|
7870
|
-
}
|
|
7871
|
-
if (graph.facts && graph.facts.length) {
|
|
7872
|
-
lines.push("CREATE TABLE IF NOT EXISTS facts (id TEXT PRIMARY KEY, kind TEXT, predicate_json TEXT, derived_by TEXT, confidence TEXT);");
|
|
7873
|
-
lines.push("CREATE TABLE IF NOT EXISTS fact_subjects (fact_id TEXT, node_id TEXT);");
|
|
7874
|
-
lines.push("CREATE TABLE IF NOT EXISTS fact_evidence (fact_id TEXT, file TEXT, span_start INTEGER, span_end INTEGER);");
|
|
7875
|
-
for (const f of graph.facts) {
|
|
7876
|
-
lines.push(`INSERT INTO facts VALUES (${sqlStr(f.id)}, ${sqlStr(f.kind)}, ${sqlStr(JSON.stringify(f.predicate))}, ${sqlStr(f.derivedBy)}, ${sqlStr(f.confidence)});`);
|
|
7877
|
-
for (const s of f.subjectIds) lines.push(`INSERT INTO fact_subjects VALUES (${sqlStr(f.id)}, ${sqlStr(s)});`);
|
|
7878
|
-
for (const ev of f.evidence) lines.push(`INSERT INTO fact_evidence VALUES (${sqlStr(f.id)}, ${sqlStr(ev.file)}, ${sqlNum(ev.span.start)}, ${sqlNum(ev.span.end)});`);
|
|
7879
|
-
}
|
|
7880
|
-
}
|
|
7881
|
-
lines.push("COMMIT;");
|
|
7882
|
-
return lines.join("\n") + "\n";
|
|
7883
|
-
}
|
|
7884
|
-
function sqlStr(v) {
|
|
7885
|
-
if (v == null) return "NULL";
|
|
7886
|
-
return `'${v.replace(/'/g, "''")}'`;
|
|
7887
|
-
}
|
|
7888
|
-
function sqlNum(v) {
|
|
7889
|
-
return v == null || !Number.isFinite(v) ? "NULL" : String(v);
|
|
7890
|
-
}
|
|
7891
|
-
function sqlBool(v) {
|
|
7892
|
-
return v == null ? "NULL" : v ? "1" : "0";
|
|
7893
|
-
}
|
|
7894
|
-
function cyclonedx(ctx) {
|
|
7895
|
-
const components = [];
|
|
7896
|
-
for (const d of ctx.deps ?? []) {
|
|
7897
|
-
components.push({
|
|
7898
|
-
type: "library",
|
|
7899
|
-
name: d.name,
|
|
7900
|
-
version: d.installed ?? d.declared,
|
|
7901
|
-
purl: d.ecosystem === "npm" ? `pkg:npm/${d.name}@${d.installed ?? ""}` : void 0
|
|
7902
|
-
});
|
|
7903
|
-
}
|
|
7904
|
-
for (const m of ctx.models ?? []) {
|
|
7905
|
-
components.push({ type: "machine-learning-model", name: m.name, properties: [{ name: "vg:runtime", value: m.runtime }] });
|
|
7906
|
-
}
|
|
7907
|
-
const bom = {
|
|
7908
|
-
bomFormat: "CycloneDX",
|
|
7909
|
-
specVersion: "1.6",
|
|
7910
|
-
metadata: { timestamp: ctx.generatedAt, tools: [{ name: "vg", version: ctx.graph.provenance.version }] },
|
|
7911
|
-
components
|
|
7912
|
-
};
|
|
7913
|
-
return JSON.stringify(bom, null, 2) + "\n";
|
|
7914
|
-
}
|
|
7915
|
-
function spdx(ctx) {
|
|
7916
|
-
const packages = (ctx.deps ?? []).map((d) => ({
|
|
7917
|
-
SPDXID: `SPDXRef-Package-${cypherLabel(d.name)}`,
|
|
7918
|
-
name: d.name,
|
|
7919
|
-
versionInfo: d.installed ?? d.declared,
|
|
7920
|
-
downloadLocation: "NOASSERTION"
|
|
7921
|
-
}));
|
|
7922
|
-
const doc = {
|
|
7923
|
-
spdxVersion: "SPDX-2.3",
|
|
7924
|
-
dataLicense: "CC0-1.0",
|
|
7925
|
-
SPDXID: "SPDXRef-DOCUMENT",
|
|
7926
|
-
name: "vg-sbom",
|
|
7927
|
-
creationInfo: { created: ctx.generatedAt, creators: [`Tool: vg-${ctx.graph.provenance.version}`] },
|
|
7928
|
-
packages
|
|
7929
|
-
};
|
|
7930
|
-
return JSON.stringify(doc, null, 2) + "\n";
|
|
7931
|
-
}
|
|
7932
5653
|
|
|
7933
|
-
export { ASSISTANTS,
|
|
7934
|
-
//# sourceMappingURL=chunk-
|
|
7935
|
-
//# sourceMappingURL=chunk-
|
|
5654
|
+
export { ASSISTANTS, CliError, ExitCode, FREE_PACK, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, TOOLS, UsageError, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, clearStoredCredentials, countTokens, coveringTests, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectAssistants, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, dsnCommand, ensureGitignored, epistemicBreakdown, fetchHostedDocsCached, findGitRoot, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, ingestHostForRegionId, installAssistant, isTestFile, loadCoverage, loadGraph, loadSnapshot, parseDsn, parseJsonc, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, readScanArtifact, readStoredCredentials, refreshIfStale, refreshInstalledInstructions, relativeResolver, renderHtml, renderReport, renderToolResult, resolveCliInvocation, resolveDsn, resolveIngestHost, resolveLimits, scipEdges, selectForBudget, shortestPath, symbolsFromApi, testsToRun, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, warmEmbedderInBackground, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
|
|
5655
|
+
//# sourceMappingURL=chunk-3MXNNBLI.js.map
|
|
5656
|
+
//# sourceMappingURL=chunk-3MXNNBLI.js.map
|