@vibgrate/cli 2026.721.2 → 2026.722.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +185 -0
- package/README.md +2 -1
- package/dist/baseline-J44M7MWP.js +7 -0
- package/dist/{baseline-KK7ZLRZB.js.map → baseline-J44M7MWP.js.map} +1 -1
- package/dist/chunk-2EVQKZTO.js +696 -0
- package/dist/chunk-2EVQKZTO.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-MUMBLIDO.js → chunk-EXQV26MF.js} +4 -3
- package/dist/chunk-EXQV26MF.js.map +1 -0
- package/dist/chunk-GGJZA3Q6.js +961 -0
- package/dist/chunk-GGJZA3Q6.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-NHLSAZ6N.js → chunk-JSNHZCIV.js} +42 -71
- package/dist/chunk-JSNHZCIV.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-JWBS5GS6.js → chunk-PY3DNX5H.js} +94 -34
- package/dist/chunk-PY3DNX5H.js.map +1 -0
- package/dist/{chunk-3DPZQS2Q.js → chunk-VXT6LUGP.js} +356 -2532
- package/dist/chunk-VXT6LUGP.js.map +1 -0
- package/dist/chunk-WNIIKCNF.js +972 -0
- package/dist/chunk-WNIIKCNF.js.map +1 -0
- package/dist/chunk-XKXKWYJF.js +417 -0
- package/dist/chunk-XKXKWYJF.js.map +1 -0
- package/dist/cli.js +2479 -1234
- 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 +5 -3
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/interactive-736PBC7J.js +820 -0
- package/dist/interactive-736PBC7J.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.d.ts +1 -1
- 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/{types-BgNa-FZQ.d.ts → types-Jl6RJ175.d.ts} +8 -0
- package/dist/ui-HOVOPWMX.js +4 -0
- package/dist/ui-HOVOPWMX.js.map +1 -0
- package/package.json +3 -2
- package/dist/baseline-KK7ZLRZB.js +0 -6
- package/dist/chunk-3DPZQS2Q.js.map +0 -1
- package/dist/chunk-JWBS5GS6.js.map +0 -1
- package/dist/chunk-MUMBLIDO.js.map +0 -1
- package/dist/chunk-NHLSAZ6N.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-JSNHZCIV.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 },
|
|
@@ -1646,6 +1620,38 @@ function resolve4(parses, resolver) {
|
|
|
1646
1620
|
push(defsByFile, p.rel, ref);
|
|
1647
1621
|
}
|
|
1648
1622
|
}
|
|
1623
|
+
const filesByNamespace = /* @__PURE__ */ new Map();
|
|
1624
|
+
for (const p of parses) for (const ns of p.namespaces ?? []) addToSet(filesByNamespace, ns, p.rel);
|
|
1625
|
+
const relsByLang = /* @__PURE__ */ new Map();
|
|
1626
|
+
for (const p of parses) push(relsByLang, p.lang, p.rel);
|
|
1627
|
+
const nsReachableByRel = /* @__PURE__ */ new Map();
|
|
1628
|
+
const moduleReachableByRel = /* @__PURE__ */ new Map();
|
|
1629
|
+
for (const p of parses) {
|
|
1630
|
+
if (MODULE_SCOPED_LANGS.has(p.lang)) {
|
|
1631
|
+
const mod2 = /* @__PURE__ */ new Set();
|
|
1632
|
+
for (const rel2 of relsByLang.get(p.lang) ?? []) if (rel2 !== p.rel) mod2.add(rel2);
|
|
1633
|
+
moduleReachableByRel.set(p.rel, mod2);
|
|
1634
|
+
}
|
|
1635
|
+
const reach = /* @__PURE__ */ new Set();
|
|
1636
|
+
const namespaces = new Set(p.namespaces ?? []);
|
|
1637
|
+
for (const imp of p.imports) {
|
|
1638
|
+
namespaces.add(imp.source);
|
|
1639
|
+
if (IMPORT_NAMES_A_TYPE.has(p.lang)) {
|
|
1640
|
+
const sep9 = Math.max(imp.source.lastIndexOf("."), imp.source.lastIndexOf("\\"));
|
|
1641
|
+
if (sep9 > 0) namespaces.add(imp.source.slice(0, sep9));
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
for (const ns of namespaces) for (const rel2 of filesByNamespace.get(ns) ?? []) if (rel2 !== p.rel) reach.add(rel2);
|
|
1645
|
+
nsReachableByRel.set(p.rel, reach);
|
|
1646
|
+
}
|
|
1647
|
+
const superTypesByType = /* @__PURE__ */ new Map();
|
|
1648
|
+
for (const p of parses) {
|
|
1649
|
+
const localDefs = defsByFile.get(p.rel) ?? [];
|
|
1650
|
+
for (const h of p.heritage) {
|
|
1651
|
+
const encl = enclosingDefRef(localDefs, h.byte);
|
|
1652
|
+
if (encl && (encl.kind === "class" || encl.kind === "interface")) addToSet(superTypesByType, encl.name, h.superName);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1649
1655
|
const edges = new EdgeSet();
|
|
1650
1656
|
const unresolved2 = /* @__PURE__ */ new Map();
|
|
1651
1657
|
const stats = {
|
|
@@ -1689,9 +1695,11 @@ function resolve4(parses, resolver) {
|
|
|
1689
1695
|
const localDefs = defsByFile.get(p.rel) ?? [];
|
|
1690
1696
|
const imported = importedFilesByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1691
1697
|
const testCaller = isTestFile(p.rel);
|
|
1698
|
+
const nsReach = nsReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1699
|
+
const modReach = moduleReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1692
1700
|
for (const call of p.calls) {
|
|
1693
1701
|
const srcId = enclosingDefId(localDefs, call.byte) ?? fileId;
|
|
1694
|
-
const resolved = resolveCall(call, p.rel, p.lang, imported, defsByName, srcId, testCaller);
|
|
1702
|
+
const resolved = resolveCall(call, p.rel, p.lang, imported, defsByName, srcId, testCaller, nsReach, superTypesByType, modReach);
|
|
1695
1703
|
if (resolved) {
|
|
1696
1704
|
edges.add("call", srcId, resolved.id, "heuristic", resolved.confidence);
|
|
1697
1705
|
stats.callsResolved++;
|
|
@@ -1706,9 +1714,11 @@ function resolve4(parses, resolver) {
|
|
|
1706
1714
|
const localDefs = defsByFile.get(p.rel) ?? [];
|
|
1707
1715
|
const imported = importedFilesByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1708
1716
|
const testCaller = isTestFile(p.rel);
|
|
1717
|
+
const nsReach = nsReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1718
|
+
const modReach = moduleReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1709
1719
|
for (const ref of p.typeRefs) {
|
|
1710
1720
|
const srcId = enclosingDefId(localDefs, ref.byte) ?? fileId;
|
|
1711
|
-
const target = resolveType(ref.name, p.rel, p.lang, imported, defsByName, testCaller);
|
|
1721
|
+
const target = resolveType(ref.name, p.rel, p.lang, imported, defsByName, testCaller, nsReach, modReach);
|
|
1712
1722
|
if (target) edges.add("references", srcId, target.id, "heuristic", 0.7);
|
|
1713
1723
|
}
|
|
1714
1724
|
}
|
|
@@ -1719,7 +1729,9 @@ function resolve4(parses, resolver) {
|
|
|
1719
1729
|
for (const h of p.heritage) {
|
|
1720
1730
|
const srcId = enclosingDefId(localDefs, h.byte);
|
|
1721
1731
|
if (!srcId) continue;
|
|
1722
|
-
const
|
|
1732
|
+
const nsReach = nsReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1733
|
+
const modReach = moduleReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
|
|
1734
|
+
const target = resolveType(h.superName, p.rel, p.lang, imported, defsByName, testCaller, nsReach, modReach);
|
|
1723
1735
|
if (target) edges.add(h.kind, srcId, target.id, "heuristic", 0.85);
|
|
1724
1736
|
else bumpUnresolved(unresolved2, srcId, h.superName, h.kind, p.rel);
|
|
1725
1737
|
}
|
|
@@ -1770,21 +1782,52 @@ function parentQualifiedName(qn) {
|
|
|
1770
1782
|
const idx = qn.lastIndexOf(".");
|
|
1771
1783
|
return idx > 0 ? qn.slice(0, idx) : null;
|
|
1772
1784
|
}
|
|
1773
|
-
function
|
|
1785
|
+
function enclosingDefRef(defs, byte) {
|
|
1774
1786
|
let best;
|
|
1775
1787
|
for (const d of defs) {
|
|
1776
1788
|
if (d.startByte <= byte && d.endByte >= byte) {
|
|
1777
1789
|
if (!best || d.endByte - d.startByte < best.endByte - best.startByte) best = d;
|
|
1778
1790
|
}
|
|
1779
1791
|
}
|
|
1780
|
-
return best
|
|
1792
|
+
return best;
|
|
1793
|
+
}
|
|
1794
|
+
function enclosingDefId(defs, byte) {
|
|
1795
|
+
return enclosingDefRef(defs, byte)?.id;
|
|
1796
|
+
}
|
|
1797
|
+
function addToSet(map, key, value) {
|
|
1798
|
+
const s = map.get(key);
|
|
1799
|
+
if (s) s.add(value);
|
|
1800
|
+
else map.set(key, /* @__PURE__ */ new Set([value]));
|
|
1801
|
+
}
|
|
1802
|
+
function parentTypeName(qualifiedName) {
|
|
1803
|
+
const i = qualifiedName.lastIndexOf(".");
|
|
1804
|
+
return i > 0 ? qualifiedName.slice(0, i) : null;
|
|
1805
|
+
}
|
|
1806
|
+
function disambiguateInterfaceImpl(pool, superTypesByType) {
|
|
1807
|
+
const withParent = pool.map((c) => ({ c, parent: parentTypeName(c.qualifiedName) })).filter((x) => x.parent !== null);
|
|
1808
|
+
if (withParent.length < 2) return null;
|
|
1809
|
+
const parents = new Set(withParent.map((x) => x.parent));
|
|
1810
|
+
const impls = withParent.filter(
|
|
1811
|
+
(x) => [...parents].some((other) => other !== x.parent && superTypesByType.get(x.parent)?.has(other))
|
|
1812
|
+
);
|
|
1813
|
+
if (impls.length === 0) return null;
|
|
1814
|
+
const interfaceParents = /* @__PURE__ */ new Set();
|
|
1815
|
+
for (const impl of impls) for (const s of superTypesByType.get(impl.parent) ?? []) interfaceParents.add(s);
|
|
1816
|
+
const interfaces = withParent.filter((x) => interfaceParents.has(x.parent));
|
|
1817
|
+
const uniqueImplIds = new Set(impls.map((x) => x.c.id));
|
|
1818
|
+
if (uniqueImplIds.size === 1) return impls[0].c;
|
|
1819
|
+
const uniqueIfaceIds = new Set(interfaces.map((x) => x.c.id));
|
|
1820
|
+
if (uniqueIfaceIds.size === 1) return interfaces[0].c;
|
|
1821
|
+
return null;
|
|
1781
1822
|
}
|
|
1782
1823
|
var PACKAGE_SCOPED_LANGS = /* @__PURE__ */ new Set(["go", "java", "cs", "kotlin", "scala", "swift"]);
|
|
1824
|
+
var IMPORT_NAMES_A_TYPE = /* @__PURE__ */ new Set(["java", "kotlin", "scala", "php"]);
|
|
1825
|
+
var MODULE_SCOPED_LANGS = /* @__PURE__ */ new Set(["swift"]);
|
|
1783
1826
|
function dirOf(rel2) {
|
|
1784
1827
|
const i = rel2.lastIndexOf("/");
|
|
1785
1828
|
return i >= 0 ? rel2.slice(0, i) : "";
|
|
1786
1829
|
}
|
|
1787
|
-
function resolveCall(call, fromRel, fromLang, importedRels, defsByName, enclosingId, fromIsTestFile = false) {
|
|
1830
|
+
function resolveCall(call, fromRel, fromLang, importedRels, defsByName, enclosingId, fromIsTestFile = false, nsReachableRels = /* @__PURE__ */ new Set(), superTypesByType = /* @__PURE__ */ new Map(), moduleReachableRels = /* @__PURE__ */ new Set()) {
|
|
1788
1831
|
const candidates = defsByName.get(call.callee);
|
|
1789
1832
|
if (!candidates || candidates.length === 0) return null;
|
|
1790
1833
|
const callable = candidates.filter((c) => c.kind === "function" || c.kind === "method");
|
|
@@ -1796,21 +1839,24 @@ function resolveCall(call, fromRel, fromLang, importedRels, defsByName, enclosin
|
|
|
1796
1839
|
const pick = [...sameFile].sort((a, b) => a.id.localeCompare(b.id))[0];
|
|
1797
1840
|
return { id: pick.id, confidence: 0.4 };
|
|
1798
1841
|
}
|
|
1799
|
-
const
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1842
|
+
const dir = dirOf(fromRel);
|
|
1843
|
+
const notSelf = (c) => !(call.qualified && enclosingId && c.id === enclosingId);
|
|
1844
|
+
const strict = pool.filter(
|
|
1845
|
+
(c) => notSelf(c) && (importedRels.has(c.rel) || PACKAGE_SCOPED_LANGS.has(fromLang) && c.lang === fromLang && dirOf(c.rel) === dir || nsReachableRels.has(c.rel))
|
|
1846
|
+
);
|
|
1847
|
+
if (strict.length === 1) return { id: strict[0].id, confidence: 0.75 };
|
|
1848
|
+
const broad = pool.filter((c) => notSelf(c) && (moduleReachableRels.has(c.rel) || strict.includes(c)));
|
|
1849
|
+
if (broad.length > 1) {
|
|
1850
|
+
const dedup = disambiguateInterfaceImpl(broad, superTypesByType);
|
|
1851
|
+
if (dedup) return { id: dedup.id, confidence: 0.6 };
|
|
1807
1852
|
}
|
|
1853
|
+
if (strict.length > 1) return null;
|
|
1808
1854
|
if (fromLang === "swift" && fromIsTestFile && pool.length === 1) {
|
|
1809
1855
|
return { id: pool[0].id, confidence: 0.5 };
|
|
1810
1856
|
}
|
|
1811
1857
|
return null;
|
|
1812
1858
|
}
|
|
1813
|
-
function resolveType(name, fromRel, fromLang, importedRels, defsByName, fromIsTestFile = false) {
|
|
1859
|
+
function resolveType(name, fromRel, fromLang, importedRels, defsByName, fromIsTestFile = false, nsReachableRels = /* @__PURE__ */ new Set(), moduleReachableRels = /* @__PURE__ */ new Set()) {
|
|
1814
1860
|
const candidates = (defsByName.get(name) ?? []).filter(
|
|
1815
1861
|
(c) => c.kind === "class" || c.kind === "interface"
|
|
1816
1862
|
);
|
|
@@ -1825,6 +1871,10 @@ function resolveType(name, fromRel, fromLang, importedRels, defsByName, fromIsTe
|
|
|
1825
1871
|
const samePkg = candidates.filter((c) => c.lang === fromLang && dirOf(c.rel) === dir);
|
|
1826
1872
|
if (samePkg.length === 1) return { id: samePkg[0].id };
|
|
1827
1873
|
}
|
|
1874
|
+
if (nsReachableRels.size || moduleReachableRels.size) {
|
|
1875
|
+
const reach = candidates.filter((c) => nsReachableRels.has(c.rel) || moduleReachableRels.has(c.rel));
|
|
1876
|
+
if (reach.length === 1) return { id: reach[0].id };
|
|
1877
|
+
}
|
|
1828
1878
|
if (fromLang === "swift" && fromIsTestFile && candidates.length === 1) {
|
|
1829
1879
|
return { id: candidates[0].id };
|
|
1830
1880
|
}
|
|
@@ -1881,6 +1931,9 @@ function tsResolveEdges(root, tsFiles, nodes) {
|
|
|
1881
1931
|
const edges = /* @__PURE__ */ new Map();
|
|
1882
1932
|
const covered = /* @__PURE__ */ new Set();
|
|
1883
1933
|
const stats = empty.stats;
|
|
1934
|
+
const interfaceCalls = [];
|
|
1935
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1936
|
+
for (const list of nodesByFile.values()) for (const n of list) byId.set(n.id, n);
|
|
1884
1937
|
for (const file of tsFiles) {
|
|
1885
1938
|
const sf = program.getSourceFile(file.abs);
|
|
1886
1939
|
if (!sf) continue;
|
|
@@ -1898,6 +1951,9 @@ function tsResolveEdges(root, tsFiles, nodes) {
|
|
|
1898
1951
|
if (src && src.id !== target.id) {
|
|
1899
1952
|
add2(edges, callKind(target), src.id, target.id);
|
|
1900
1953
|
stats.resolved++;
|
|
1954
|
+
if (target.kind === "interface" && ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
1955
|
+
interfaceCalls.push({ srcId: src.id, interfaceId: target.id, method: node.expression.name.text });
|
|
1956
|
+
}
|
|
1901
1957
|
}
|
|
1902
1958
|
}
|
|
1903
1959
|
} else if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
@@ -1931,6 +1987,27 @@ function tsResolveEdges(root, tsFiles, nodes) {
|
|
|
1931
1987
|
};
|
|
1932
1988
|
visit(sf);
|
|
1933
1989
|
}
|
|
1990
|
+
const implsByInterface = /* @__PURE__ */ new Map();
|
|
1991
|
+
for (const e of edges.values()) {
|
|
1992
|
+
if (e.kind !== "implements" && e.kind !== "extends") continue;
|
|
1993
|
+
if (byId.get(e.dst)?.kind !== "interface") continue;
|
|
1994
|
+
const list = implsByInterface.get(e.dst);
|
|
1995
|
+
if (list) list.push(e.src);
|
|
1996
|
+
else implsByInterface.set(e.dst, [e.src]);
|
|
1997
|
+
}
|
|
1998
|
+
for (const { srcId, interfaceId, method } of interfaceCalls) {
|
|
1999
|
+
const impls = implsByInterface.get(interfaceId);
|
|
2000
|
+
if (!impls || impls.length !== 1) continue;
|
|
2001
|
+
const impl = byId.get(impls[0]);
|
|
2002
|
+
if (!impl) continue;
|
|
2003
|
+
const target = (nodesByFile.get(impl.file) ?? []).find(
|
|
2004
|
+
(n) => (n.kind === "method" || n.kind === "function") && n.qualifiedName === `${impl.qualifiedName}.${method}`
|
|
2005
|
+
);
|
|
2006
|
+
if (target && target.id !== srcId) {
|
|
2007
|
+
add2(edges, "call", srcId, target.id);
|
|
2008
|
+
stats.resolved++;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
1934
2011
|
return { edges: [...edges.values()], coveredFiles: covered, stats };
|
|
1935
2012
|
}
|
|
1936
2013
|
function callKind(target) {
|
|
@@ -2044,50 +2121,7 @@ function enclosing3(nodes, line) {
|
|
|
2044
2121
|
return best;
|
|
2045
2122
|
}
|
|
2046
2123
|
function normalize2(p) {
|
|
2047
|
-
return
|
|
2048
|
-
}
|
|
2049
|
-
var CACHE_VERSION = "vg-parse-cache/2";
|
|
2050
|
-
function cacheDir(root) {
|
|
2051
|
-
return path17.join(root, ".vibgrate", "cache");
|
|
2052
|
-
}
|
|
2053
|
-
function cachePath(root) {
|
|
2054
|
-
return path17.join(cacheDir(root), "parse-cache.json");
|
|
2055
|
-
}
|
|
2056
|
-
function loadCache(root, opts) {
|
|
2057
|
-
const file = cachePath(root);
|
|
2058
|
-
let data = {
|
|
2059
|
-
version: CACHE_VERSION,
|
|
2060
|
-
toolVersion: opts.toolVersion,
|
|
2061
|
-
grammars: opts.grammars,
|
|
2062
|
-
entries: {}
|
|
2063
|
-
};
|
|
2064
|
-
if (!opts.disabled && fs17.existsSync(file)) {
|
|
2065
|
-
try {
|
|
2066
|
-
const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2067
|
-
if (loaded.version === CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.grammars === opts.grammars && loaded.entries) {
|
|
2068
|
-
data = loaded;
|
|
2069
|
-
}
|
|
2070
|
-
} catch {
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2073
|
-
return {
|
|
2074
|
-
get(rel2, hash) {
|
|
2075
|
-
const entry = data.entries[rel2];
|
|
2076
|
-
return entry && entry.hash === hash ? entry.parse : void 0;
|
|
2077
|
-
},
|
|
2078
|
-
set(rel2, parse) {
|
|
2079
|
-
data.entries[rel2] = { hash: parse.hash, parse };
|
|
2080
|
-
},
|
|
2081
|
-
prune(currentRels) {
|
|
2082
|
-
for (const rel2 of Object.keys(data.entries)) {
|
|
2083
|
-
if (!currentRels.has(rel2)) delete data.entries[rel2];
|
|
2084
|
-
}
|
|
2085
|
-
},
|
|
2086
|
-
save() {
|
|
2087
|
-
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
2088
|
-
fs17.writeFileSync(file, stableStringify(data, 0));
|
|
2089
|
-
}
|
|
2090
|
-
};
|
|
2124
|
+
return path2.resolve(p).split(path2.sep).join("/");
|
|
2091
2125
|
}
|
|
2092
2126
|
|
|
2093
2127
|
// src/engine/epistemic.ts
|
|
@@ -2117,7 +2151,7 @@ function epistemicBreakdown(edges) {
|
|
|
2117
2151
|
// src/engine/build.ts
|
|
2118
2152
|
async function buildGraph(options) {
|
|
2119
2153
|
const start = nowMs();
|
|
2120
|
-
const root =
|
|
2154
|
+
const root = path2.resolve(options.root);
|
|
2121
2155
|
const files = discover({
|
|
2122
2156
|
root,
|
|
2123
2157
|
only: options.only,
|
|
@@ -2145,7 +2179,7 @@ async function buildGraph(options) {
|
|
|
2145
2179
|
for (const file of files) {
|
|
2146
2180
|
let stat;
|
|
2147
2181
|
try {
|
|
2148
|
-
stat =
|
|
2182
|
+
stat = fs18.statSync(file.abs);
|
|
2149
2183
|
} catch {
|
|
2150
2184
|
continue;
|
|
2151
2185
|
}
|
|
@@ -2163,7 +2197,7 @@ async function buildGraph(options) {
|
|
|
2163
2197
|
}
|
|
2164
2198
|
let hash = "";
|
|
2165
2199
|
try {
|
|
2166
|
-
hash = hashBytes(
|
|
2200
|
+
hash = hashBytes(fs18.readFileSync(file.abs));
|
|
2167
2201
|
} catch {
|
|
2168
2202
|
continue;
|
|
2169
2203
|
}
|
|
@@ -2255,7 +2289,7 @@ async function buildGraph(options) {
|
|
|
2255
2289
|
toolchain
|
|
2256
2290
|
},
|
|
2257
2291
|
meta: {
|
|
2258
|
-
root:
|
|
2292
|
+
root: path2.basename(root) === "" ? "." : ".",
|
|
2259
2293
|
languages,
|
|
2260
2294
|
counts: {
|
|
2261
2295
|
nodes: analysis.nodes.length,
|
|
@@ -2301,14 +2335,14 @@ function toRepoRel(p) {
|
|
|
2301
2335
|
function loadScipIndex(root, explicit) {
|
|
2302
2336
|
const candidates = [
|
|
2303
2337
|
explicit,
|
|
2304
|
-
|
|
2305
|
-
|
|
2338
|
+
path2.join(root, "index.scip"),
|
|
2339
|
+
path2.join(root, ".vibgrate", "index.scip")
|
|
2306
2340
|
].filter((p) => Boolean(p));
|
|
2307
2341
|
for (const file of candidates) {
|
|
2308
|
-
const abs =
|
|
2309
|
-
if (!
|
|
2342
|
+
const abs = path2.isAbsolute(file) ? file : path2.resolve(root, file);
|
|
2343
|
+
if (!fs18.existsSync(abs)) continue;
|
|
2310
2344
|
try {
|
|
2311
|
-
const index = decodeScipIndex(new Uint8Array(
|
|
2345
|
+
const index = decodeScipIndex(new Uint8Array(fs18.readFileSync(abs)));
|
|
2312
2346
|
if (index.documents.length) {
|
|
2313
2347
|
return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
|
|
2314
2348
|
}
|
|
@@ -2469,7 +2503,7 @@ function esc(s) {
|
|
|
2469
2503
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2470
2504
|
}
|
|
2471
2505
|
function vibgrateDir(root) {
|
|
2472
|
-
return
|
|
2506
|
+
return path2.join(root, ".vibgrate");
|
|
2473
2507
|
}
|
|
2474
2508
|
var DEFAULT_GITIGNORE = [
|
|
2475
2509
|
"# Created once by vg \u2014 local graph artifacts stay out of git by default.",
|
|
@@ -2485,51 +2519,51 @@ var DEFAULT_GITIGNORE = [
|
|
|
2485
2519
|
"mcp-navigation.json"
|
|
2486
2520
|
];
|
|
2487
2521
|
function ensureVibgrateGitignore(root) {
|
|
2488
|
-
const file =
|
|
2489
|
-
if (
|
|
2490
|
-
|
|
2491
|
-
|
|
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")}
|
|
2492
2526
|
`);
|
|
2493
2527
|
}
|
|
2494
2528
|
function defaultGraphPath(root) {
|
|
2495
|
-
return
|
|
2529
|
+
return path2.join(vibgrateDir(root), "graph.json");
|
|
2496
2530
|
}
|
|
2497
2531
|
function writeArtifacts(graph, options) {
|
|
2498
2532
|
const dir = vibgrateDir(options.root);
|
|
2499
|
-
|
|
2533
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
2500
2534
|
ensureVibgrateGitignore(options.root);
|
|
2501
2535
|
const graphPath = options.graphPath ?? defaultGraphPath(options.root);
|
|
2502
|
-
|
|
2536
|
+
fs18.mkdirSync(path2.dirname(graphPath), { recursive: true });
|
|
2503
2537
|
const tmp = `${graphPath}.${process.pid}.tmp`;
|
|
2504
2538
|
try {
|
|
2505
|
-
|
|
2506
|
-
|
|
2539
|
+
fs18.writeFileSync(tmp, serializeGraph(graph));
|
|
2540
|
+
fs18.renameSync(tmp, graphPath);
|
|
2507
2541
|
} catch (err) {
|
|
2508
|
-
|
|
2542
|
+
fs18.rmSync(tmp, { force: true });
|
|
2509
2543
|
throw err;
|
|
2510
2544
|
}
|
|
2511
2545
|
const written = { graphPath };
|
|
2512
2546
|
if (options.report !== false) {
|
|
2513
|
-
const reportPath =
|
|
2514
|
-
|
|
2547
|
+
const reportPath = path2.join(dir, "GRAPH_REPORT.md");
|
|
2548
|
+
fs18.writeFileSync(reportPath, renderReport(graph));
|
|
2515
2549
|
written.reportPath = reportPath;
|
|
2516
2550
|
}
|
|
2517
2551
|
if (options.html !== false) {
|
|
2518
|
-
const htmlPath =
|
|
2519
|
-
|
|
2552
|
+
const htmlPath = path2.join(dir, "graph.html");
|
|
2553
|
+
fs18.writeFileSync(htmlPath, renderHtml(graph));
|
|
2520
2554
|
written.htmlPath = htmlPath;
|
|
2521
2555
|
}
|
|
2522
2556
|
if (graph.facts && graph.facts.length) {
|
|
2523
|
-
const factsPath =
|
|
2524
|
-
|
|
2557
|
+
const factsPath = path2.join(dir, "facts.jsonl");
|
|
2558
|
+
fs18.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
|
|
2525
2559
|
written.factsPath = factsPath;
|
|
2526
2560
|
}
|
|
2527
2561
|
return written;
|
|
2528
2562
|
}
|
|
2529
2563
|
function loadGraph(root, graphPath) {
|
|
2530
2564
|
const file = graphPath ?? defaultGraphPath(root);
|
|
2531
|
-
if (!
|
|
2532
|
-
return parseGraph(
|
|
2565
|
+
if (!fs18.existsSync(file)) return null;
|
|
2566
|
+
return parseGraph(fs18.readFileSync(file, "utf8"));
|
|
2533
2567
|
}
|
|
2534
2568
|
|
|
2535
2569
|
// src/engine/verify.ts
|
|
@@ -2595,361 +2629,23 @@ var CliError = class extends Error {
|
|
|
2595
2629
|
function usageError(message) {
|
|
2596
2630
|
return new CliError(message, ExitCode.USAGE_ERROR);
|
|
2597
2631
|
}
|
|
2598
|
-
var DEFAULT_LOCK_STALE_MS = 15 * 60 * 1e3;
|
|
2599
|
-
function isProcessAlive(pid) {
|
|
2600
|
-
try {
|
|
2601
|
-
process.kill(pid, 0);
|
|
2602
|
-
return true;
|
|
2603
|
-
} catch (e) {
|
|
2604
|
-
return e.code === "EPERM";
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
function lockIsStale(file, staleMs) {
|
|
2608
|
-
try {
|
|
2609
|
-
const { pid, at } = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2610
|
-
if (typeof at === "number" && Date.now() - at > staleMs) return true;
|
|
2611
|
-
if (typeof pid === "number" && !isProcessAlive(pid)) return true;
|
|
2612
|
-
return false;
|
|
2613
|
-
} catch {
|
|
2614
|
-
return true;
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
|
|
2618
|
-
const write = () => {
|
|
2619
|
-
try {
|
|
2620
|
-
fs17.mkdirSync(path17.dirname(file), { recursive: true });
|
|
2621
|
-
const fd = fs17.openSync(file, "wx");
|
|
2622
|
-
fs17.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
|
|
2623
|
-
fs17.closeSync(fd);
|
|
2624
|
-
return true;
|
|
2625
|
-
} catch {
|
|
2626
|
-
return false;
|
|
2627
|
-
}
|
|
2628
|
-
};
|
|
2629
|
-
if (write()) return true;
|
|
2630
|
-
if (lockIsStale(file, staleMs)) {
|
|
2631
|
-
try {
|
|
2632
|
-
fs17.rmSync(file, { force: true });
|
|
2633
|
-
} catch {
|
|
2634
|
-
}
|
|
2635
|
-
return write();
|
|
2636
|
-
}
|
|
2637
|
-
return false;
|
|
2638
|
-
}
|
|
2639
|
-
function releaseLock(file) {
|
|
2640
|
-
try {
|
|
2641
|
-
fs17.rmSync(file, { force: true });
|
|
2642
|
-
} catch {
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
// src/engine/embeddings.ts
|
|
2647
|
-
function resolveEmbedModel(model) {
|
|
2648
|
-
return model ?? "bge-small-en-v1.5";
|
|
2649
|
-
}
|
|
2650
|
-
function modelCacheDir() {
|
|
2651
|
-
const base = process.env.XDG_CACHE_HOME || path17.join(os.homedir(), ".cache");
|
|
2652
|
-
return path17.join(base, "vibgrate", "models");
|
|
2653
|
-
}
|
|
2654
|
-
function modelReadyMarker(modelId) {
|
|
2655
|
-
return path17.join(modelCacheDir(), `.ready-${safe(modelId)}`);
|
|
2656
|
-
}
|
|
2657
|
-
function isModelReady(modelId = resolveEmbedModel()) {
|
|
2658
|
-
return fs17.existsSync(modelReadyMarker(modelId));
|
|
2659
|
-
}
|
|
2660
|
-
function unavailableMessage(reason) {
|
|
2661
|
-
switch (reason) {
|
|
2662
|
-
case "no-permission":
|
|
2663
|
-
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.)`;
|
|
2664
|
-
case "download-failed":
|
|
2665
|
-
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.`;
|
|
2666
|
-
case "not-installed":
|
|
2667
|
-
return `the optional semantic backend isn't available here \u2014 using fast lexical search.`;
|
|
2668
|
-
case "init-failed":
|
|
2669
|
-
default:
|
|
2670
|
-
return `the semantic model couldn't load \u2014 using fast lexical search.`;
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
function modelCacheInfo(modelId = resolveEmbedModel()) {
|
|
2674
|
-
const dir = modelCacheDir();
|
|
2675
|
-
return { dir, present: isModelReady(modelId) || fs17.existsSync(dir), bytes: dirSize(dir) };
|
|
2676
|
-
}
|
|
2677
|
-
function clearModelCache() {
|
|
2678
|
-
const dir = modelCacheDir();
|
|
2679
|
-
const bytes = dirSize(dir);
|
|
2680
|
-
try {
|
|
2681
|
-
fs17.rmSync(dir, { recursive: true, force: true });
|
|
2682
|
-
} catch {
|
|
2683
|
-
}
|
|
2684
|
-
return bytes;
|
|
2685
|
-
}
|
|
2686
|
-
function dirSize(dir) {
|
|
2687
|
-
let total = 0;
|
|
2688
|
-
let entries;
|
|
2689
|
-
try {
|
|
2690
|
-
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
2691
|
-
} catch {
|
|
2692
|
-
return 0;
|
|
2693
|
-
}
|
|
2694
|
-
for (const e of entries) {
|
|
2695
|
-
const p = path17.join(dir, e.name);
|
|
2696
|
-
if (e.isDirectory()) total += dirSize(p);
|
|
2697
|
-
else {
|
|
2698
|
-
try {
|
|
2699
|
-
total += fs17.statSync(p).size;
|
|
2700
|
-
} catch {
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
return total;
|
|
2705
|
-
}
|
|
2706
|
-
function isPermissionError(e) {
|
|
2707
|
-
const code = e?.code;
|
|
2708
|
-
return code === "EACCES" || code === "EPERM" || code === "EROFS";
|
|
2709
|
-
}
|
|
2710
|
-
function embeddingsCached(root, modelId) {
|
|
2711
|
-
return fs17.existsSync(path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
|
|
2712
|
-
}
|
|
2713
|
-
async function loadEmbedder(options = {}) {
|
|
2714
|
-
if (options.local) return null;
|
|
2715
|
-
const modelId = resolveEmbedModel(options.model);
|
|
2716
|
-
if (options.noDownload && !isModelReady(modelId)) return null;
|
|
2717
|
-
const fail = (reason) => {
|
|
2718
|
-
options.onUnavailable?.(reason);
|
|
2719
|
-
return null;
|
|
2720
|
-
};
|
|
2721
|
-
const mod = await importEmbedBackend();
|
|
2722
|
-
if (!mod?.FlagEmbedding) return fail("not-installed");
|
|
2723
|
-
const cache = modelCacheDir();
|
|
2724
|
-
try {
|
|
2725
|
-
fs17.mkdirSync(cache, { recursive: true });
|
|
2726
|
-
} catch (e) {
|
|
2727
|
-
return fail(isPermissionError(e) ? "no-permission" : "init-failed");
|
|
2728
|
-
}
|
|
2729
|
-
try {
|
|
2730
|
-
const model = await withTimeout(
|
|
2731
|
-
mod.FlagEmbedding.init({
|
|
2732
|
-
model: mapModel(mod, modelId),
|
|
2733
|
-
cacheDir: cache,
|
|
2734
|
-
showDownloadProgress: options.showDownloadProgress ?? false
|
|
2735
|
-
}),
|
|
2736
|
-
embedInitTimeoutMs(),
|
|
2737
|
-
"embedding model init timed out"
|
|
2738
|
-
);
|
|
2739
|
-
try {
|
|
2740
|
-
fs17.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
|
|
2741
|
-
} catch {
|
|
2742
|
-
}
|
|
2743
|
-
return {
|
|
2744
|
-
id: modelId,
|
|
2745
|
-
async embed(texts) {
|
|
2746
|
-
const out = [];
|
|
2747
|
-
for await (const batch of model.embed(texts, 64)) {
|
|
2748
|
-
for (const v of batch) out.push(Array.from(v));
|
|
2749
|
-
}
|
|
2750
|
-
return out;
|
|
2751
|
-
},
|
|
2752
|
-
async embedQuery(text) {
|
|
2753
|
-
const v = await model.queryEmbed(text);
|
|
2754
|
-
return Array.from(v);
|
|
2755
|
-
}
|
|
2756
|
-
};
|
|
2757
|
-
} catch (e) {
|
|
2758
|
-
return fail(isPermissionError(e) ? "no-permission" : "download-failed");
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2761
|
-
async function importEmbedBackend() {
|
|
2762
|
-
const hostDir = process.env.VIBGRATE_EMBEDDER_PATH;
|
|
2763
|
-
if (hostDir) {
|
|
2764
|
-
try {
|
|
2765
|
-
const req = createRequire(path17.join(hostDir, "package.json"));
|
|
2766
|
-
const mod = await import(pathToFileURL(req.resolve("fastembed")).href);
|
|
2767
|
-
const resolved = mod?.FlagEmbedding ? mod : mod?.default;
|
|
2768
|
-
if (resolved?.FlagEmbedding) return resolved;
|
|
2769
|
-
} catch {
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
return await import('fastembed').catch(() => null);
|
|
2773
|
-
}
|
|
2774
|
-
function embedInitTimeoutMs() {
|
|
2775
|
-
const n = Number(process.env.VG_EMBED_TIMEOUT_MS);
|
|
2776
|
-
return Number.isFinite(n) && n > 0 ? n : 6e4;
|
|
2777
|
-
}
|
|
2778
|
-
async function withTimeout(promise, ms, message) {
|
|
2779
|
-
let timer;
|
|
2780
|
-
try {
|
|
2781
|
-
return await Promise.race([
|
|
2782
|
-
promise,
|
|
2783
|
-
new Promise((_, reject) => {
|
|
2784
|
-
timer = setTimeout(() => reject(new Error(message)), ms);
|
|
2785
|
-
timer.unref?.();
|
|
2786
|
-
})
|
|
2787
|
-
]);
|
|
2788
|
-
} finally {
|
|
2789
|
-
clearTimeout(timer);
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
function mapModel(mod, id) {
|
|
2793
|
-
const m = mod.EmbeddingModel ?? {};
|
|
2794
|
-
if (/bge-small/i.test(id)) return m.BGESmallENV15 ?? m.BGESmallEN ?? id;
|
|
2795
|
-
if (/bge-base/i.test(id)) return m.BGEBaseENV15 ?? id;
|
|
2796
|
-
if (/all-minilm/i.test(id)) return m.AllMiniLML6V2 ?? id;
|
|
2797
|
-
return id;
|
|
2798
|
-
}
|
|
2799
|
-
var PATH_NOISE = /* @__PURE__ */ new Set([
|
|
2800
|
-
"src",
|
|
2801
|
-
"app",
|
|
2802
|
-
"apps",
|
|
2803
|
-
"lib",
|
|
2804
|
-
"libs",
|
|
2805
|
-
"dist",
|
|
2806
|
-
"build",
|
|
2807
|
-
"out",
|
|
2808
|
-
"index",
|
|
2809
|
-
"main",
|
|
2810
|
-
"packages",
|
|
2811
|
-
"pkg",
|
|
2812
|
-
"test",
|
|
2813
|
-
"tests",
|
|
2814
|
-
"__tests__",
|
|
2815
|
-
"spec",
|
|
2816
|
-
"node_modules",
|
|
2817
|
-
"internal",
|
|
2818
|
-
"com",
|
|
2819
|
-
"example",
|
|
2820
|
-
"demo",
|
|
2821
|
-
"java",
|
|
2822
|
-
"net",
|
|
2823
|
-
"org",
|
|
2824
|
-
"www"
|
|
2825
|
-
]);
|
|
2826
|
-
function pathContext(file) {
|
|
2827
|
-
const parts = file.split("/");
|
|
2828
|
-
const stem = (parts.pop() ?? "").replace(/\.[^.]+$/, "");
|
|
2829
|
-
const dirs = parts.filter((p) => p && !PATH_NOISE.has(p.toLowerCase()));
|
|
2830
|
-
return [...dirs.slice(-2), stem].filter(Boolean).join(" ");
|
|
2831
|
-
}
|
|
2832
|
-
function nodeEmbedText(node, areaLabel) {
|
|
2833
|
-
return [node.qualifiedName, node.kind, node.signature ?? "", node.doc ?? "", pathContext(node.file), areaLabel ?? ""].filter(Boolean).join(" ");
|
|
2834
|
-
}
|
|
2835
|
-
function cosine(a, b) {
|
|
2836
|
-
let dot2 = 0;
|
|
2837
|
-
let na = 0;
|
|
2838
|
-
let nb = 0;
|
|
2839
|
-
const n = Math.min(a.length, b.length);
|
|
2840
|
-
for (let i = 0; i < n; i++) {
|
|
2841
|
-
dot2 += a[i] * b[i];
|
|
2842
|
-
na += a[i] * a[i];
|
|
2843
|
-
nb += b[i] * b[i];
|
|
2844
|
-
}
|
|
2845
|
-
if (na === 0 || nb === 0) return 0;
|
|
2846
|
-
return dot2 / (Math.sqrt(na) * Math.sqrt(nb));
|
|
2847
|
-
}
|
|
2848
|
-
var EMBED_CHUNK = 256;
|
|
2849
|
-
var CACHE_WRITE_INTERVAL_MS = 1500;
|
|
2850
|
-
function vectorCachePath(root, modelId) {
|
|
2851
|
-
return path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
|
|
2852
|
-
}
|
|
2853
|
-
function countPending(graph, root, modelId) {
|
|
2854
|
-
const entries = readCacheEntries(vectorCachePath(root, modelId), modelId);
|
|
2855
|
-
const areaLabel = new Map(graph.areas.map((a) => [a.id, a.label]));
|
|
2856
|
-
let pending = 0;
|
|
2857
|
-
for (const n of graph.nodes) {
|
|
2858
|
-
if (n.kind === "file" || n.kind === "external") continue;
|
|
2859
|
-
const h = hashString(nodeEmbedText(n, areaLabel.get(n.area)));
|
|
2860
|
-
const cached2 = entries[n.id];
|
|
2861
|
-
if (!cached2 || cached2.hash !== h) pending++;
|
|
2862
|
-
}
|
|
2863
|
-
return pending;
|
|
2864
|
-
}
|
|
2865
|
-
function readCacheEntries(file, modelId) {
|
|
2866
|
-
if (!fs17.existsSync(file)) return {};
|
|
2867
|
-
try {
|
|
2868
|
-
const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2869
|
-
if (loaded.model === modelId && loaded.entries) return loaded.entries;
|
|
2870
|
-
} catch {
|
|
2871
|
-
}
|
|
2872
|
-
return {};
|
|
2873
|
-
}
|
|
2874
|
-
async function getNodeEmbeddings(graph, embedder, root, onProgress) {
|
|
2875
|
-
const file = vectorCachePath(root, embedder.id);
|
|
2876
|
-
let cache = { model: embedder.id, entries: {} };
|
|
2877
|
-
if (fs17.existsSync(file)) {
|
|
2878
|
-
try {
|
|
2879
|
-
const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2880
|
-
if (loaded.model === embedder.id && loaded.entries) cache = loaded;
|
|
2881
|
-
} catch {
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
const areaLabel = new Map(graph.areas.map((a) => [a.id, a.label]));
|
|
2885
|
-
const targets = graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external");
|
|
2886
|
-
const toEmbed = [];
|
|
2887
|
-
const vectors = /* @__PURE__ */ new Map();
|
|
2888
|
-
for (const n of targets) {
|
|
2889
|
-
const text = nodeEmbedText(n, areaLabel.get(n.area));
|
|
2890
|
-
const h = hashString(text);
|
|
2891
|
-
const cached2 = cache.entries[n.id];
|
|
2892
|
-
if (cached2 && cached2.hash === h) vectors.set(n.id, cached2.vec);
|
|
2893
|
-
else toEmbed.push({ id: n.id, text, hash: h });
|
|
2894
|
-
}
|
|
2895
|
-
const persist = () => {
|
|
2896
|
-
try {
|
|
2897
|
-
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
2898
|
-
const tmp = `${file}.tmp.${process.pid}`;
|
|
2899
|
-
fs17.writeFileSync(tmp, JSON.stringify(cache));
|
|
2900
|
-
fs17.renameSync(tmp, file);
|
|
2901
|
-
} catch {
|
|
2902
|
-
}
|
|
2903
|
-
};
|
|
2904
|
-
if (toEmbed.length) {
|
|
2905
|
-
const lock = `${file}.lock`;
|
|
2906
|
-
if (!acquireLock(lock)) return vectors;
|
|
2907
|
-
try {
|
|
2908
|
-
onProgress?.(0, toEmbed.length);
|
|
2909
|
-
let lastWrite = Date.now();
|
|
2910
|
-
for (let i = 0; i < toEmbed.length; i += EMBED_CHUNK) {
|
|
2911
|
-
const slice = toEmbed.slice(i, i + EMBED_CHUNK);
|
|
2912
|
-
const vecs = await embedder.embed(slice.map((t) => t.text));
|
|
2913
|
-
slice.forEach((t, j) => {
|
|
2914
|
-
const vec = vecs[j] ?? [];
|
|
2915
|
-
vectors.set(t.id, vec);
|
|
2916
|
-
cache.entries[t.id] = { hash: t.hash, vec };
|
|
2917
|
-
});
|
|
2918
|
-
onProgress?.(Math.min(i + EMBED_CHUNK, toEmbed.length), toEmbed.length);
|
|
2919
|
-
if (Date.now() - lastWrite >= CACHE_WRITE_INTERVAL_MS) {
|
|
2920
|
-
persist();
|
|
2921
|
-
lastWrite = Date.now();
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
const live = new Set(targets.map((n) => n.id));
|
|
2925
|
-
for (const id of Object.keys(cache.entries)) if (!live.has(id)) delete cache.entries[id];
|
|
2926
|
-
persist();
|
|
2927
|
-
} finally {
|
|
2928
|
-
releaseLock(lock);
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
return vectors;
|
|
2932
|
-
}
|
|
2933
|
-
function safe(id) {
|
|
2934
|
-
return id.replace(/[^a-z0-9.-]+/gi, "_");
|
|
2935
|
-
}
|
|
2936
2632
|
var SNAPSHOT_VERSION = "vg-freshness/1";
|
|
2937
2633
|
function snapshotPath(root) {
|
|
2938
|
-
return
|
|
2634
|
+
return path2.join(cacheDir(root), "freshness.json");
|
|
2939
2635
|
}
|
|
2940
2636
|
function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
|
|
2941
2637
|
const files = {};
|
|
2942
2638
|
for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
|
|
2943
2639
|
const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
|
|
2944
2640
|
try {
|
|
2945
|
-
|
|
2946
|
-
|
|
2641
|
+
fs18.mkdirSync(cacheDir(root), { recursive: true });
|
|
2642
|
+
fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
2947
2643
|
} catch {
|
|
2948
2644
|
}
|
|
2949
2645
|
}
|
|
2950
2646
|
function loadSnapshot(root) {
|
|
2951
2647
|
try {
|
|
2952
|
-
const loaded = JSON.parse(
|
|
2648
|
+
const loaded = JSON.parse(fs18.readFileSync(snapshotPath(root), "utf8"));
|
|
2953
2649
|
if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
|
|
2954
2650
|
return { ...loaded, scope: loaded.scope ?? {} };
|
|
2955
2651
|
}
|
|
@@ -2985,7 +2681,7 @@ function probeFreshness(root) {
|
|
|
2985
2681
|
}
|
|
2986
2682
|
let stat;
|
|
2987
2683
|
try {
|
|
2988
|
-
stat =
|
|
2684
|
+
stat = fs18.statSync(file.abs);
|
|
2989
2685
|
} catch {
|
|
2990
2686
|
drift.removed.push(file.rel);
|
|
2991
2687
|
continue;
|
|
@@ -2993,7 +2689,7 @@ function probeFreshness(root) {
|
|
|
2993
2689
|
if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
|
|
2994
2690
|
let hash = "";
|
|
2995
2691
|
try {
|
|
2996
|
-
hash = hashBytes(
|
|
2692
|
+
hash = hashBytes(fs18.readFileSync(file.abs));
|
|
2997
2693
|
} catch {
|
|
2998
2694
|
drift.removed.push(file.rel);
|
|
2999
2695
|
continue;
|
|
@@ -3010,7 +2706,7 @@ function probeFreshness(root) {
|
|
|
3010
2706
|
}
|
|
3011
2707
|
if (absorbed && !hasDrift(drift)) {
|
|
3012
2708
|
try {
|
|
3013
|
-
|
|
2709
|
+
fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
3014
2710
|
} catch {
|
|
3015
2711
|
}
|
|
3016
2712
|
}
|
|
@@ -3033,348 +2729,6 @@ function pruneScope(scope) {
|
|
|
3033
2729
|
if (scope.grammarsDir) out.grammarsDir = scope.grammarsDir;
|
|
3034
2730
|
return out;
|
|
3035
2731
|
}
|
|
3036
|
-
|
|
3037
|
-
// src/engine/relations.ts
|
|
3038
|
-
var GraphIndex = class {
|
|
3039
|
-
constructor(graph) {
|
|
3040
|
-
this.graph = graph;
|
|
3041
|
-
for (const n of graph.nodes) this.nodeById.set(n.id, n);
|
|
3042
|
-
for (const e of graph.edges) {
|
|
3043
|
-
push2(this.outById, e.src, e);
|
|
3044
|
-
push2(this.inById, e.dst, e);
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
graph;
|
|
3048
|
-
nodeById = /* @__PURE__ */ new Map();
|
|
3049
|
-
outById = /* @__PURE__ */ new Map();
|
|
3050
|
-
inById = /* @__PURE__ */ new Map();
|
|
3051
|
-
out(id, kind) {
|
|
3052
|
-
const all = this.outById.get(id) ?? [];
|
|
3053
|
-
return kind ? all.filter((e) => e.kind === kind) : all;
|
|
3054
|
-
}
|
|
3055
|
-
in(id, kind) {
|
|
3056
|
-
const all = this.inById.get(id) ?? [];
|
|
3057
|
-
return kind ? all.filter((e) => e.kind === kind) : all;
|
|
3058
|
-
}
|
|
3059
|
-
node(id) {
|
|
3060
|
-
return this.nodeById.get(id);
|
|
3061
|
-
}
|
|
3062
|
-
/**
|
|
3063
|
-
* Resolved nodes called by `id` — invocations (`call`) plus structural
|
|
3064
|
-
* dependency references (`references`, e.g. a constructor-injected field's
|
|
3065
|
-
* type), since both represent real usage a caller/impact question cares
|
|
3066
|
-
* about. `references` is otherwise only emitted by the precise SCIP/tsc
|
|
3067
|
-
* rungs and (for Java DI wiring) the heuristic rung — never a guess.
|
|
3068
|
-
*/
|
|
3069
|
-
callees(id) {
|
|
3070
|
-
return this.resolveTargets(this.out(id).filter(isUsageEdge), "dst");
|
|
3071
|
-
}
|
|
3072
|
-
/** Resolved nodes that call or structurally reference `id`. */
|
|
3073
|
-
callers(id) {
|
|
3074
|
-
return this.resolveTargets(this.in(id).filter(isUsageEdge), "src");
|
|
3075
|
-
}
|
|
3076
|
-
resolveTargets(edges, end) {
|
|
3077
|
-
const out = [];
|
|
3078
|
-
for (const e of edges) {
|
|
3079
|
-
const node = this.nodeById.get(e[end]);
|
|
3080
|
-
if (node) out.push({ edge: e, node });
|
|
3081
|
-
}
|
|
3082
|
-
return out;
|
|
3083
|
-
}
|
|
3084
|
-
};
|
|
3085
|
-
var USAGE_KINDS = /* @__PURE__ */ new Set(["call", "references"]);
|
|
3086
|
-
function isUsageEdge(e) {
|
|
3087
|
-
return USAGE_KINDS.has(e.kind);
|
|
3088
|
-
}
|
|
3089
|
-
function push2(map, key, value) {
|
|
3090
|
-
const list = map.get(key);
|
|
3091
|
-
if (list) list.push(value);
|
|
3092
|
-
else map.set(key, [value]);
|
|
3093
|
-
}
|
|
3094
|
-
var INDEX_CACHE = /* @__PURE__ */ new WeakMap();
|
|
3095
|
-
function indexFor(graph) {
|
|
3096
|
-
let idx = INDEX_CACHE.get(graph);
|
|
3097
|
-
if (!idx) {
|
|
3098
|
-
idx = new GraphIndex(graph);
|
|
3099
|
-
INDEX_CACHE.set(graph, idx);
|
|
3100
|
-
}
|
|
3101
|
-
return idx;
|
|
3102
|
-
}
|
|
3103
|
-
|
|
3104
|
-
// src/engine/query.ts
|
|
3105
|
-
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
3106
|
-
"the",
|
|
3107
|
-
"a",
|
|
3108
|
-
"an",
|
|
3109
|
-
"is",
|
|
3110
|
-
"are",
|
|
3111
|
-
"was",
|
|
3112
|
-
"were",
|
|
3113
|
-
"be",
|
|
3114
|
-
"to",
|
|
3115
|
-
"of",
|
|
3116
|
-
"in",
|
|
3117
|
-
"on",
|
|
3118
|
-
"for",
|
|
3119
|
-
"and",
|
|
3120
|
-
"or",
|
|
3121
|
-
"where",
|
|
3122
|
-
"what",
|
|
3123
|
-
"which",
|
|
3124
|
-
"how",
|
|
3125
|
-
"do",
|
|
3126
|
-
"does",
|
|
3127
|
-
"did",
|
|
3128
|
-
"i",
|
|
3129
|
-
"we",
|
|
3130
|
-
"it",
|
|
3131
|
-
"this",
|
|
3132
|
-
"that",
|
|
3133
|
-
"with",
|
|
3134
|
-
"when",
|
|
3135
|
-
"who",
|
|
3136
|
-
"why",
|
|
3137
|
-
"can",
|
|
3138
|
-
"should",
|
|
3139
|
-
"my",
|
|
3140
|
-
"our",
|
|
3141
|
-
"you",
|
|
3142
|
-
"your",
|
|
3143
|
-
"from",
|
|
3144
|
-
"by",
|
|
3145
|
-
"at",
|
|
3146
|
-
"as",
|
|
3147
|
-
// Discovery-question scaffolding: words a caller uses to FRAME the ask
|
|
3148
|
-
// ("find the code responsible for X", "I need to modify X, where do I
|
|
3149
|
-
// start?", "explain how X works in this codebase") rather than to name the
|
|
3150
|
-
// target. Left in, these compete on equal footing with the real identifier
|
|
3151
|
-
// terms and can outrank it outright — e.g. "find the code responsible for
|
|
3152
|
-
// deleteAsync" let "find" alone drag in every FindByIdAsync/FindAll method
|
|
3153
|
-
// in the repo, none of them the target (VG-LOCATE-FAILURE-ANALYSIS.md).
|
|
3154
|
-
"find",
|
|
3155
|
-
"code",
|
|
3156
|
-
"responsible",
|
|
3157
|
-
"need",
|
|
3158
|
-
"modify",
|
|
3159
|
-
"me",
|
|
3160
|
-
"implementation",
|
|
3161
|
-
"explain",
|
|
3162
|
-
"works",
|
|
3163
|
-
"codebase",
|
|
3164
|
-
"contains",
|
|
3165
|
-
"file"
|
|
3166
|
-
]);
|
|
3167
|
-
function queryGraph(graph, question, options = {}) {
|
|
3168
|
-
const budget = options.budget ?? 2e3;
|
|
3169
|
-
const limit = options.limit ?? 12;
|
|
3170
|
-
const terms = tokenize(question);
|
|
3171
|
-
const weightOf = termWeights(graph, terms);
|
|
3172
|
-
const index = indexFor(graph);
|
|
3173
|
-
const scored = [];
|
|
3174
|
-
for (const node of graph.nodes) {
|
|
3175
|
-
if (node.kind === "file" || node.kind === "external") continue;
|
|
3176
|
-
const { score, why } = scoreNode(node, terms, weightOf);
|
|
3177
|
-
if (score > 0) scored.push({ node, score: round2(score * (1 + IMPORTANCE_WEIGHT * node.importance)), why });
|
|
3178
|
-
}
|
|
3179
|
-
scored.sort((a, b) => b.score - a.score || a.node.qualifiedName.localeCompare(b.node.qualifiedName));
|
|
3180
|
-
const seeds = scored.slice(0, limit);
|
|
3181
|
-
const { context, tokensEstimate } = buildContext(graph, index, question, seeds, budget);
|
|
3182
|
-
return { question, matches: seeds, context, tokensEstimate };
|
|
3183
|
-
}
|
|
3184
|
-
async function queryGraphSemantic(graph, question, options) {
|
|
3185
|
-
const budget = options.budget ?? 2e3;
|
|
3186
|
-
const limit = options.limit ?? 12;
|
|
3187
|
-
const terms = tokenize(question);
|
|
3188
|
-
const weightOf = termWeights(graph, terms);
|
|
3189
|
-
const index = indexFor(graph);
|
|
3190
|
-
const queryVec = await options.embedder.embedQuery(question);
|
|
3191
|
-
const lexRaw = /* @__PURE__ */ new Map();
|
|
3192
|
-
let lexMax = 0;
|
|
3193
|
-
const whyById = /* @__PURE__ */ new Map();
|
|
3194
|
-
for (const node of graph.nodes) {
|
|
3195
|
-
if (node.kind === "file" || node.kind === "external") continue;
|
|
3196
|
-
const { score, why } = scoreNode(node, terms, weightOf);
|
|
3197
|
-
lexRaw.set(node.id, score);
|
|
3198
|
-
whyById.set(node.id, why);
|
|
3199
|
-
if (score > lexMax) lexMax = score;
|
|
3200
|
-
}
|
|
3201
|
-
const scored = [];
|
|
3202
|
-
for (const node of graph.nodes) {
|
|
3203
|
-
if (node.kind === "file" || node.kind === "external") continue;
|
|
3204
|
-
const lexNorm = lexMax > 0 ? (lexRaw.get(node.id) ?? 0) / lexMax : 0;
|
|
3205
|
-
const vec = options.nodeVectors.get(node.id);
|
|
3206
|
-
const sem = vec ? Math.max(0, cosine(queryVec, vec)) : 0;
|
|
3207
|
-
const hybrid = 0.5 * lexNorm + 0.5 * sem;
|
|
3208
|
-
if (hybrid <= 0) continue;
|
|
3209
|
-
const lexWhy = whyById.get(node.id);
|
|
3210
|
-
const why = lexWhy || (sem > 0.3 ? `semantic match (${sem.toFixed(2)})` : "weak match");
|
|
3211
|
-
scored.push({ node, score: round2(hybrid * (1 + IMPORTANCE_WEIGHT * node.importance)), why });
|
|
3212
|
-
}
|
|
3213
|
-
scored.sort((a, b) => b.score - a.score || a.node.qualifiedName.localeCompare(b.node.qualifiedName));
|
|
3214
|
-
const seeds = scored.slice(0, limit);
|
|
3215
|
-
const { context, tokensEstimate } = buildContext(graph, index, question, seeds, budget);
|
|
3216
|
-
return { question, matches: seeds, context, tokensEstimate };
|
|
3217
|
-
}
|
|
3218
|
-
function tokenize(q2) {
|
|
3219
|
-
return [
|
|
3220
|
-
...new Set(
|
|
3221
|
-
q2.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 1 && !STOPWORDS.has(t))
|
|
3222
|
-
)
|
|
3223
|
-
];
|
|
3224
|
-
}
|
|
3225
|
-
function scoreNode(node, terms, weightOf = () => 1) {
|
|
3226
|
-
let score = 0;
|
|
3227
|
-
const hits = [];
|
|
3228
|
-
const name = node.name.toLowerCase();
|
|
3229
|
-
const qn = node.qualifiedName.toLowerCase();
|
|
3230
|
-
const file = node.file.toLowerCase();
|
|
3231
|
-
const nameParts = identifierParts(node.name);
|
|
3232
|
-
for (const t of terms) {
|
|
3233
|
-
const w = weightOf(t);
|
|
3234
|
-
if (name === t) {
|
|
3235
|
-
score += 10 * w;
|
|
3236
|
-
hits.push(t);
|
|
3237
|
-
} else if (nameParts.has(t)) {
|
|
3238
|
-
score += 6 * w;
|
|
3239
|
-
hits.push(t);
|
|
3240
|
-
} else if (name.includes(t)) {
|
|
3241
|
-
score += 4 * w;
|
|
3242
|
-
hits.push(t);
|
|
3243
|
-
} else if (qn.includes(t)) {
|
|
3244
|
-
score += 3 * w;
|
|
3245
|
-
hits.push(t);
|
|
3246
|
-
} else if (fuzzyPartMatch(t, nameParts)) {
|
|
3247
|
-
score += 2 * w;
|
|
3248
|
-
hits.push(`~${t}`);
|
|
3249
|
-
} else if (file.includes(t)) {
|
|
3250
|
-
score += 1 * w;
|
|
3251
|
-
hits.push(t);
|
|
3252
|
-
}
|
|
3253
|
-
}
|
|
3254
|
-
return { score, why: hits.length ? `matched: ${hits.join(", ")}` : "" };
|
|
3255
|
-
}
|
|
3256
|
-
var IMPORTANCE_WEIGHT = 0.4;
|
|
3257
|
-
function termWeights(graph, terms) {
|
|
3258
|
-
if (terms.length === 0) return () => 1;
|
|
3259
|
-
const df = /* @__PURE__ */ new Map();
|
|
3260
|
-
let n = 0;
|
|
3261
|
-
for (const node of graph.nodes) {
|
|
3262
|
-
if (node.kind === "file" || node.kind === "external") continue;
|
|
3263
|
-
n++;
|
|
3264
|
-
const name = node.name.toLowerCase();
|
|
3265
|
-
const parts = identifierParts(node.name);
|
|
3266
|
-
for (const t of terms) {
|
|
3267
|
-
if (parts.has(t) || name.includes(t)) df.set(t, (df.get(t) ?? 0) + 1);
|
|
3268
|
-
}
|
|
3269
|
-
}
|
|
3270
|
-
const w = /* @__PURE__ */ new Map();
|
|
3271
|
-
for (const t of terms) {
|
|
3272
|
-
const idf = Math.log((n + 1) / ((df.get(t) ?? 0) + 1)) + 1;
|
|
3273
|
-
w.set(t, Math.max(0.5, Math.min(8, idf)));
|
|
3274
|
-
}
|
|
3275
|
-
return (t) => w.get(t) ?? 1;
|
|
3276
|
-
}
|
|
3277
|
-
function identifierParts(name) {
|
|
3278
|
-
return new Set(
|
|
3279
|
-
name.split(/[^\p{L}\p{N}]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/u).filter(Boolean).map((s) => s.toLowerCase())
|
|
3280
|
-
);
|
|
3281
|
-
}
|
|
3282
|
-
function fuzzyPartMatch(term, parts) {
|
|
3283
|
-
for (const part of parts) {
|
|
3284
|
-
const shared = sharedPrefixLen(term, part);
|
|
3285
|
-
if (shared >= 5 && shared >= 0.6 * Math.min(term.length, part.length)) return true;
|
|
3286
|
-
}
|
|
3287
|
-
return false;
|
|
3288
|
-
}
|
|
3289
|
-
function sharedPrefixLen(a, b) {
|
|
3290
|
-
const n = Math.min(a.length, b.length);
|
|
3291
|
-
let i = 0;
|
|
3292
|
-
while (i < n && a[i] === b[i]) i++;
|
|
3293
|
-
return i;
|
|
3294
|
-
}
|
|
3295
|
-
function buildContext(graph, index, question, seeds, budget) {
|
|
3296
|
-
const lines = [];
|
|
3297
|
-
lines.push(`# Context for: ${question}`);
|
|
3298
|
-
lines.push("");
|
|
3299
|
-
if (seeds.length === 0) {
|
|
3300
|
-
lines.push("_No matching symbols found in the map. Try different terms, or `vg hubs` for the most important code._");
|
|
3301
|
-
const text2 = lines.join("\n");
|
|
3302
|
-
return { context: text2, tokensEstimate: estimateTokens(text2) };
|
|
3303
|
-
}
|
|
3304
|
-
for (const { node, why } of seeds) {
|
|
3305
|
-
const block = [];
|
|
3306
|
-
block.push(`## ${node.qualifiedName} (${node.kind}, ${node.file}:${node.span.start})`);
|
|
3307
|
-
if (node.signature) block.push("`" + node.signature + "`");
|
|
3308
|
-
const callees = index.callees(node.id).map((x) => x.node.qualifiedName);
|
|
3309
|
-
const callers = index.callers(node.id).map((x) => x.node.qualifiedName);
|
|
3310
|
-
if (callees.length) block.push(`calls: ${unique(callees).slice(0, 8).join(", ")}`);
|
|
3311
|
-
if (callers.length) block.push(`called by: ${unique(callers).slice(0, 8).join(", ")}`);
|
|
3312
|
-
block.push(`importance ${node.importance.toFixed(3)} \xB7 area #${node.area}${node.isHub ? " \xB7 hub" : ""} \xB7 ${why}`);
|
|
3313
|
-
block.push("");
|
|
3314
|
-
const candidate = lines.concat(block).join("\n");
|
|
3315
|
-
if (estimateTokens(candidate) > budget && lines.length > 2) break;
|
|
3316
|
-
lines.push(...block);
|
|
3317
|
-
}
|
|
3318
|
-
const text = lines.join("\n");
|
|
3319
|
-
return { context: text, tokensEstimate: estimateTokens(text) };
|
|
3320
|
-
}
|
|
3321
|
-
function unique(xs) {
|
|
3322
|
-
return [...new Set(xs)];
|
|
3323
|
-
}
|
|
3324
|
-
function estimateTokens(text) {
|
|
3325
|
-
return Math.ceil(text.length / 4);
|
|
3326
|
-
}
|
|
3327
|
-
function round2(x) {
|
|
3328
|
-
return Math.round(x * 1e6) / 1e6;
|
|
3329
|
-
}
|
|
3330
|
-
|
|
3331
|
-
// src/engine/lookup.ts
|
|
3332
|
-
function findNodes(graph, query) {
|
|
3333
|
-
const q2 = query.trim();
|
|
3334
|
-
if (!q2) return [];
|
|
3335
|
-
const byId = graph.nodes.filter((n) => n.id === q2);
|
|
3336
|
-
if (byId.length) return byId;
|
|
3337
|
-
const fileLine = /^(.*):(\d+)$/.exec(q2);
|
|
3338
|
-
if (fileLine) {
|
|
3339
|
-
const file = fileLine[1];
|
|
3340
|
-
const line = Number(fileLine[2]);
|
|
3341
|
-
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));
|
|
3342
|
-
if (spanning.length) return spanning;
|
|
3343
|
-
}
|
|
3344
|
-
if (/[*?]/.test(q2)) {
|
|
3345
|
-
const re = globToRegExp(q2);
|
|
3346
|
-
return rank(graph.nodes.filter((n) => re.test(n.qualifiedName) || re.test(n.name) || re.test(n.file)));
|
|
3347
|
-
}
|
|
3348
|
-
const byQn = graph.nodes.filter((n) => n.qualifiedName === q2);
|
|
3349
|
-
if (byQn.length) return rank(byQn);
|
|
3350
|
-
const byName = graph.nodes.filter((n) => n.name === q2);
|
|
3351
|
-
if (byName.length) return rank(byName);
|
|
3352
|
-
const lower = q2.toLowerCase();
|
|
3353
|
-
const ci = graph.nodes.filter(
|
|
3354
|
-
(n) => n.qualifiedName.toLowerCase() === lower || n.name.toLowerCase() === lower
|
|
3355
|
-
);
|
|
3356
|
-
if (ci.length) return rank(ci);
|
|
3357
|
-
return rank(graph.nodes.filter((n) => n.qualifiedName.toLowerCase().includes(lower))).slice(0, 25);
|
|
3358
|
-
}
|
|
3359
|
-
function resolveOne(graph, query, pick) {
|
|
3360
|
-
const candidates = findNodes(graph, query);
|
|
3361
|
-
if (candidates.length === 0) return { candidates };
|
|
3362
|
-
if (pick && pick >= 1 && pick <= candidates.length) return { node: candidates[pick - 1], candidates };
|
|
3363
|
-
if (candidates.length === 1) return { node: candidates[0], candidates };
|
|
3364
|
-
return { candidates };
|
|
3365
|
-
}
|
|
3366
|
-
function nodeById(graph, id) {
|
|
3367
|
-
return graph.nodes.find((n) => n.id === id);
|
|
3368
|
-
}
|
|
3369
|
-
function rank(nodes) {
|
|
3370
|
-
return [...nodes].sort(
|
|
3371
|
-
(a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)
|
|
3372
|
-
);
|
|
3373
|
-
}
|
|
3374
|
-
function globToRegExp(glob) {
|
|
3375
|
-
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
3376
|
-
return new RegExp(`^${escaped}$`, "i");
|
|
3377
|
-
}
|
|
3378
2732
|
function buildGraphologyGraph(nodes, edges) {
|
|
3379
2733
|
const g = new Graph({ type: "directed", multi: true, allowSelfLoops: true });
|
|
3380
2734
|
for (const n of nodes) {
|
|
@@ -3398,57 +2752,6 @@ function shortestPath(graph, srcId, dstId) {
|
|
|
3398
2752
|
if (reverse) return { ids: reverse.slice().reverse(), direction: "reverse" };
|
|
3399
2753
|
return null;
|
|
3400
2754
|
}
|
|
3401
|
-
|
|
3402
|
-
// src/engine/impact.ts
|
|
3403
|
-
var DEPEND_EDGES = /* @__PURE__ */ new Set(["call", "import", "extends", "implements", "references"]);
|
|
3404
|
-
function impactOf(graph, rootId, opts = {}) {
|
|
3405
|
-
const maxDepth = Math.max(1, opts.depth ?? 4);
|
|
3406
|
-
const index = indexFor(graph);
|
|
3407
|
-
const root = index.node(rootId);
|
|
3408
|
-
const affected = /* @__PURE__ */ new Map();
|
|
3409
|
-
let minEdgeConfidence = 1;
|
|
3410
|
-
let frontier = [{ id: rootId, conf: 1 }];
|
|
3411
|
-
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
3412
|
-
const nextFrontier = [];
|
|
3413
|
-
for (const { id, conf } of frontier) {
|
|
3414
|
-
for (const e of index.in(id)) {
|
|
3415
|
-
if (!DEPEND_EDGES.has(e.kind)) continue;
|
|
3416
|
-
minEdgeConfidence = Math.min(minEdgeConfidence, e.confidence);
|
|
3417
|
-
const dependentId = e.src;
|
|
3418
|
-
if (dependentId === rootId || affected.has(dependentId)) continue;
|
|
3419
|
-
const node = index.node(dependentId);
|
|
3420
|
-
if (!node) continue;
|
|
3421
|
-
const conf2 = round3(conf * e.confidence * 0.9);
|
|
3422
|
-
affected.set(dependentId, {
|
|
3423
|
-
id: node.id,
|
|
3424
|
-
name: node.qualifiedName,
|
|
3425
|
-
kind: node.kind,
|
|
3426
|
-
file: node.file,
|
|
3427
|
-
line: node.span.start,
|
|
3428
|
-
depth,
|
|
3429
|
-
confidence: conf2
|
|
3430
|
-
});
|
|
3431
|
-
nextFrontier.push({ id: dependentId, conf: conf2 });
|
|
3432
|
-
}
|
|
3433
|
-
}
|
|
3434
|
-
frontier = nextFrontier;
|
|
3435
|
-
if (frontier.length === 0) break;
|
|
3436
|
-
}
|
|
3437
|
-
const items = [...affected.values()].sort(
|
|
3438
|
-
(a, b) => a.depth - b.depth || b.confidence - a.confidence || a.name.localeCompare(b.name)
|
|
3439
|
-
);
|
|
3440
|
-
return {
|
|
3441
|
-
root: { id: rootId, name: root?.qualifiedName ?? rootId },
|
|
3442
|
-
depth: maxDepth,
|
|
3443
|
-
affected: items,
|
|
3444
|
-
direct: items.filter((i) => i.depth === 1).length,
|
|
3445
|
-
transitive: items.filter((i) => i.depth > 1).length,
|
|
3446
|
-
minEdgeConfidence: round3(minEdgeConfidence)
|
|
3447
|
-
};
|
|
3448
|
-
}
|
|
3449
|
-
function round3(x) {
|
|
3450
|
-
return Math.round(x * 1e6) / 1e6;
|
|
3451
|
-
}
|
|
3452
2755
|
function coveringTests(graph, node, index) {
|
|
3453
2756
|
const idx = index ?? indexFor(graph);
|
|
3454
2757
|
const byId = idx.nodeById;
|
|
@@ -3484,10 +2787,10 @@ function testsToRun(graph, rootId, depth = 4) {
|
|
|
3484
2787
|
};
|
|
3485
2788
|
}
|
|
3486
2789
|
function detectRunner(root, lang) {
|
|
3487
|
-
const pkgPath =
|
|
3488
|
-
if (
|
|
2790
|
+
const pkgPath = path2.join(root, "package.json");
|
|
2791
|
+
if (fs18.existsSync(pkgPath)) {
|
|
3489
2792
|
try {
|
|
3490
|
-
const pkg = JSON.parse(
|
|
2793
|
+
const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
|
|
3491
2794
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3492
2795
|
if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
|
|
3493
2796
|
if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
|
|
@@ -3495,10 +2798,10 @@ function detectRunner(root, lang) {
|
|
|
3495
2798
|
} catch {
|
|
3496
2799
|
}
|
|
3497
2800
|
}
|
|
3498
|
-
if (lang === "py" ||
|
|
2801
|
+
if (lang === "py" || fs18.existsSync(path2.join(root, "pyproject.toml")) || fs18.existsSync(path2.join(root, "pytest.ini"))) {
|
|
3499
2802
|
return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
|
|
3500
2803
|
}
|
|
3501
|
-
if (lang === "go" ||
|
|
2804
|
+
if (lang === "go" || fs18.existsSync(path2.join(root, "go.mod"))) {
|
|
3502
2805
|
return { name: "go test", command: () => "go test ./..." };
|
|
3503
2806
|
}
|
|
3504
2807
|
if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
|
|
@@ -3507,469 +2810,12 @@ function detectRunner(root, lang) {
|
|
|
3507
2810
|
if (lang === "rb") return { name: "rspec", command: (f) => `bundle exec rspec ${f.join(" ")}`.trim() };
|
|
3508
2811
|
return { name: "tests", command: (f) => f.length ? f.join(" ") : "(no runner detected)" };
|
|
3509
2812
|
}
|
|
3510
|
-
function hasFile(root, re) {
|
|
3511
|
-
try {
|
|
3512
|
-
return
|
|
3513
|
-
} catch {
|
|
3514
|
-
return false;
|
|
3515
|
-
}
|
|
3516
|
-
}
|
|
3517
|
-
var ECOSYSTEMS = ["npm", "pypi", "go", "rust", "ruby", "php", "dotnet", "swift", "dart", "java"];
|
|
3518
|
-
function inventory(root) {
|
|
3519
|
-
const manifests = findManifests(root);
|
|
3520
|
-
const records = [];
|
|
3521
|
-
records.push(...npmDeps(manifests.npm));
|
|
3522
|
-
records.push(...pypiDeps(manifests.pypi));
|
|
3523
|
-
records.push(...goDeps(manifests.go));
|
|
3524
|
-
records.push(...cargoDeps(manifests.rust));
|
|
3525
|
-
records.push(...rubyDeps(manifests.ruby));
|
|
3526
|
-
records.push(...phpDeps(manifests.php));
|
|
3527
|
-
records.push(...dotnetDeps(manifests.dotnet));
|
|
3528
|
-
records.push(...swiftDeps(manifests.swift));
|
|
3529
|
-
records.push(...dartDeps(manifests.dart));
|
|
3530
|
-
records.push(...javaDeps(manifests.java));
|
|
3531
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3532
|
-
const deduped = records.filter((r) => {
|
|
3533
|
-
const key = `${r.ecosystem}\0${r.name}`;
|
|
3534
|
-
if (seen.has(key)) return false;
|
|
3535
|
-
seen.add(key);
|
|
3536
|
-
return true;
|
|
3537
|
-
});
|
|
3538
|
-
deduped.sort((a, b) => a.ecosystem.localeCompare(b.ecosystem) || a.name.localeCompare(b.name));
|
|
3539
|
-
const counts = { total: deduped.length };
|
|
3540
|
-
for (const e of ECOSYSTEMS) counts[e] = 0;
|
|
3541
|
-
for (const r of deduped) counts[r.ecosystem]++;
|
|
3542
|
-
return { records: deduped, counts };
|
|
3543
|
-
}
|
|
3544
|
-
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
3545
|
-
"node_modules",
|
|
3546
|
-
".git",
|
|
3547
|
-
"dist",
|
|
3548
|
-
"build",
|
|
3549
|
-
"out",
|
|
3550
|
-
".vibgrate",
|
|
3551
|
-
"vendor",
|
|
3552
|
-
".venv",
|
|
3553
|
-
"venv",
|
|
3554
|
-
"env",
|
|
3555
|
-
"__pycache__",
|
|
3556
|
-
"target",
|
|
3557
|
-
".next",
|
|
3558
|
-
".nuxt",
|
|
3559
|
-
"coverage",
|
|
3560
|
-
".cache"
|
|
3561
|
-
]);
|
|
3562
|
-
var MANIFEST_BY_FILE = {
|
|
3563
|
-
"package.json": "npm",
|
|
3564
|
-
"requirements.txt": "pypi",
|
|
3565
|
-
"pyproject.toml": "pypi",
|
|
3566
|
-
"go.mod": "go",
|
|
3567
|
-
"Cargo.toml": "rust",
|
|
3568
|
-
Gemfile: "ruby",
|
|
3569
|
-
"composer.json": "php",
|
|
3570
|
-
"Package.swift": "swift",
|
|
3571
|
-
"pubspec.yaml": "dart",
|
|
3572
|
-
"pom.xml": "java",
|
|
3573
|
-
"build.gradle": "java",
|
|
3574
|
-
"build.gradle.kts": "java"
|
|
3575
|
-
};
|
|
3576
|
-
function findManifests(root) {
|
|
3577
|
-
const set = Object.fromEntries(ECOSYSTEMS.map((e) => [e, []]));
|
|
3578
|
-
const MAX_ENTRIES2 = 2e4;
|
|
3579
|
-
let scanned = 0;
|
|
3580
|
-
const walk2 = (dir, depth) => {
|
|
3581
|
-
if (depth > 8 || scanned > MAX_ENTRIES2) return;
|
|
3582
|
-
let entries;
|
|
3583
|
-
try {
|
|
3584
|
-
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
3585
|
-
} catch {
|
|
3586
|
-
return;
|
|
3587
|
-
}
|
|
3588
|
-
for (const e of entries) {
|
|
3589
|
-
scanned++;
|
|
3590
|
-
if (scanned > MAX_ENTRIES2) break;
|
|
3591
|
-
if (e.isDirectory()) {
|
|
3592
|
-
if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path17.join(dir, e.name), depth + 1);
|
|
3593
|
-
continue;
|
|
3594
|
-
}
|
|
3595
|
-
const eco = MANIFEST_BY_FILE[e.name] ?? (/\.(cs|fs)proj$/i.test(e.name) ? "dotnet" : void 0);
|
|
3596
|
-
if (eco) set[eco].push(path17.join(dir, e.name));
|
|
3597
|
-
}
|
|
3598
|
-
};
|
|
3599
|
-
walk2(root, 0);
|
|
3600
|
-
for (const e of ECOSYSTEMS) set[e].sort();
|
|
3601
|
-
return set;
|
|
3602
|
-
}
|
|
3603
|
-
function npmDeps(files) {
|
|
3604
|
-
const out = [];
|
|
3605
|
-
for (const file of files) {
|
|
3606
|
-
let pkg;
|
|
3607
|
-
try {
|
|
3608
|
-
pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
3609
|
-
} catch {
|
|
3610
|
-
continue;
|
|
3611
|
-
}
|
|
3612
|
-
const dir = path17.dirname(file);
|
|
3613
|
-
const all = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3614
|
-
for (const [name, declared] of Object.entries(all)) {
|
|
3615
|
-
out.push({ name, ecosystem: "npm", declared, installed: installedNpmVersion(dir, name) });
|
|
3616
|
-
}
|
|
3617
|
-
}
|
|
3618
|
-
return out;
|
|
3619
|
-
}
|
|
3620
|
-
function installedNpmVersion(dir, name) {
|
|
3621
|
-
let cur = dir;
|
|
3622
|
-
for (let i = 0; i < 12; i++) {
|
|
3623
|
-
const p = path17.join(cur, "node_modules", name, "package.json");
|
|
3624
|
-
try {
|
|
3625
|
-
return JSON.parse(fs17.readFileSync(p, "utf8")).version;
|
|
3626
|
-
} catch {
|
|
3627
|
-
}
|
|
3628
|
-
const parent = path17.dirname(cur);
|
|
3629
|
-
if (parent === cur) break;
|
|
3630
|
-
cur = parent;
|
|
3631
|
-
}
|
|
3632
|
-
return void 0;
|
|
3633
|
-
}
|
|
3634
|
-
function installedTreeVersion(root, ecosystem, name) {
|
|
3635
|
-
if (ecosystem === "pypi") return installedPypiVersion(root, name);
|
|
3636
|
-
if (ecosystem === "php") return installedPhpVersion(root, name);
|
|
3637
|
-
return void 0;
|
|
3638
|
-
}
|
|
3639
|
-
function normalizePep503(name) {
|
|
3640
|
-
return name.trim().toLowerCase().replace(/[-_.]+/g, "-");
|
|
3641
|
-
}
|
|
3642
|
-
function sitePackagesDirs(base) {
|
|
3643
|
-
const out = [];
|
|
3644
|
-
try {
|
|
3645
|
-
for (const d of fs17.readdirSync(path17.join(base, "lib"))) {
|
|
3646
|
-
if (d.startsWith("python")) out.push(path17.join(base, "lib", d, "site-packages"));
|
|
3647
|
-
}
|
|
3648
|
-
} catch {
|
|
3649
|
-
}
|
|
3650
|
-
out.push(path17.join(base, "Lib", "site-packages"));
|
|
3651
|
-
return out;
|
|
3652
|
-
}
|
|
3653
|
-
function installedPypiVersion(root, name) {
|
|
3654
|
-
const target = normalizePep503(name);
|
|
3655
|
-
for (const venv of [".venv", "venv", "env", ".tox"]) {
|
|
3656
|
-
for (const sp of sitePackagesDirs(path17.join(root, venv))) {
|
|
3657
|
-
let entries;
|
|
3658
|
-
try {
|
|
3659
|
-
entries = fs17.readdirSync(sp);
|
|
3660
|
-
} catch {
|
|
3661
|
-
continue;
|
|
3662
|
-
}
|
|
3663
|
-
for (const entry of entries) {
|
|
3664
|
-
const m = /^(.+?)-(\d[^-]*)\.dist-info$/.exec(entry);
|
|
3665
|
-
if (m && normalizePep503(m[1]) === target) return m[2];
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
return void 0;
|
|
3670
|
-
}
|
|
3671
|
-
function installedPhpVersion(root, name) {
|
|
3672
|
-
let data;
|
|
3673
|
-
try {
|
|
3674
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "vendor", "composer", "installed.json"), "utf8"));
|
|
3675
|
-
} catch {
|
|
3676
|
-
return void 0;
|
|
3677
|
-
}
|
|
3678
|
-
const pkgs = Array.isArray(data) ? data : data.packages;
|
|
3679
|
-
if (!Array.isArray(pkgs)) return void 0;
|
|
3680
|
-
const p = pkgs.find((x) => x && typeof x === "object" && x.name === name);
|
|
3681
|
-
return typeof p?.version === "string" ? p.version.replace(/^v/, "") : void 0;
|
|
3682
|
-
}
|
|
3683
|
-
function pypiDeps(files) {
|
|
3684
|
-
const byName = /* @__PURE__ */ new Map();
|
|
3685
|
-
const add3 = (name, declared) => {
|
|
3686
|
-
const clean = name.trim().replace(/\[.*\]$/, "");
|
|
3687
|
-
if (!clean || clean.toLowerCase() === "python") return;
|
|
3688
|
-
if (!byName.has(clean)) byName.set(clean, { name: clean, ecosystem: "pypi", declared: declared.trim() || "*" });
|
|
3689
|
-
};
|
|
3690
|
-
for (const file of files) {
|
|
3691
|
-
let text;
|
|
3692
|
-
try {
|
|
3693
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3694
|
-
} catch {
|
|
3695
|
-
continue;
|
|
3696
|
-
}
|
|
3697
|
-
if (path17.basename(file) === "requirements.txt") {
|
|
3698
|
-
for (const line of text.split("\n")) {
|
|
3699
|
-
const t = line.trim();
|
|
3700
|
-
if (!t || t.startsWith("#") || t.startsWith("-")) continue;
|
|
3701
|
-
const m = /^([A-Za-z0-9._-]+(?:\[[^\]]*\])?)\s*([<>=!~).*]+.*)?$/.exec(t);
|
|
3702
|
-
if (m) add3(m[1], m[2] ?? "*");
|
|
3703
|
-
}
|
|
3704
|
-
} else {
|
|
3705
|
-
for (const dep of pep621Dependencies(text)) {
|
|
3706
|
-
const m = /^([A-Za-z0-9._-]+(?:\[[^\]]*\])?)\s*(.*)$/.exec(dep.trim());
|
|
3707
|
-
if (m) add3(m[1], m[2] ?? "*");
|
|
3708
|
-
}
|
|
3709
|
-
for (const [name, spec] of poetryDependencies(text)) add3(name, spec);
|
|
3710
|
-
}
|
|
3711
|
-
}
|
|
3712
|
-
return [...byName.values()];
|
|
3713
|
-
}
|
|
3714
|
-
function pep621Dependencies(text) {
|
|
3715
|
-
const section = sectionBody(text, "project");
|
|
3716
|
-
if (!section) return [];
|
|
3717
|
-
const start = /dependencies\s*=\s*\[/.exec(section);
|
|
3718
|
-
if (!start) return [];
|
|
3719
|
-
let depth = 0;
|
|
3720
|
-
let inStr = false;
|
|
3721
|
-
let strCh = "";
|
|
3722
|
-
const out = [];
|
|
3723
|
-
let cur = "";
|
|
3724
|
-
for (let i = start.index + start[0].length - 1; i < section.length; i++) {
|
|
3725
|
-
const ch = section[i];
|
|
3726
|
-
if (inStr) {
|
|
3727
|
-
if (ch === strCh) {
|
|
3728
|
-
out.push(cur);
|
|
3729
|
-
inStr = false;
|
|
3730
|
-
} else cur += ch;
|
|
3731
|
-
continue;
|
|
3732
|
-
}
|
|
3733
|
-
if (ch === '"' || ch === "'") {
|
|
3734
|
-
inStr = true;
|
|
3735
|
-
strCh = ch;
|
|
3736
|
-
cur = "";
|
|
3737
|
-
} else if (ch === "[") depth++;
|
|
3738
|
-
else if (ch === "]") {
|
|
3739
|
-
depth--;
|
|
3740
|
-
if (depth === 0) break;
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
return out;
|
|
3744
|
-
}
|
|
3745
|
-
function poetryDependencies(text) {
|
|
3746
|
-
const out = [];
|
|
3747
|
-
const lines = text.split("\n");
|
|
3748
|
-
let inDeps = false;
|
|
3749
|
-
for (const raw of lines) {
|
|
3750
|
-
const line = raw.trim();
|
|
3751
|
-
if (line.startsWith("[")) {
|
|
3752
|
-
inDeps = /^\[tool\.poetry(\.group\.[^.\]]+)?\.dependencies\]$/.test(line);
|
|
3753
|
-
continue;
|
|
3754
|
-
}
|
|
3755
|
-
if (!inDeps || !line || line.startsWith("#")) continue;
|
|
3756
|
-
const m = /^([A-Za-z0-9._-]+)\s*=\s*["']([^"']*)["']/.exec(line);
|
|
3757
|
-
if (m) out.push([m[1], m[2] || "*"]);
|
|
3758
|
-
}
|
|
3759
|
-
return out;
|
|
3760
|
-
}
|
|
3761
|
-
function sectionBody(text, name) {
|
|
3762
|
-
const lines = text.split("\n");
|
|
3763
|
-
let capturing = false;
|
|
3764
|
-
const body = [];
|
|
3765
|
-
for (const raw of lines) {
|
|
3766
|
-
const line = raw.trim();
|
|
3767
|
-
if (/^\[/.test(line)) {
|
|
3768
|
-
if (capturing) break;
|
|
3769
|
-
capturing = line === `[${name}]`;
|
|
3770
|
-
continue;
|
|
3771
|
-
}
|
|
3772
|
-
if (capturing) body.push(raw);
|
|
3773
|
-
}
|
|
3774
|
-
return capturing || body.length ? body.join("\n") : null;
|
|
3775
|
-
}
|
|
3776
|
-
function goDeps(files) {
|
|
3777
|
-
const out = [];
|
|
3778
|
-
for (const mod of files) {
|
|
3779
|
-
let text;
|
|
3780
|
-
try {
|
|
3781
|
-
text = fs17.readFileSync(mod, "utf8");
|
|
3782
|
-
} catch {
|
|
3783
|
-
continue;
|
|
3784
|
-
}
|
|
3785
|
-
const re = /^\s*(?:require\s+)?([\w./-]+)\s+v(\S+)/gm;
|
|
3786
|
-
let m;
|
|
3787
|
-
while (m = re.exec(text)) {
|
|
3788
|
-
if (m[1] === "go" || m[1] === "require" || m[1].startsWith("module")) continue;
|
|
3789
|
-
out.push({ name: m[1], ecosystem: "go", declared: `v${m[2]}` });
|
|
3790
|
-
}
|
|
3791
|
-
}
|
|
3792
|
-
return out;
|
|
3793
|
-
}
|
|
3794
|
-
function cargoDeps(files) {
|
|
3795
|
-
const out = [];
|
|
3796
|
-
for (const file of files) {
|
|
3797
|
-
let text;
|
|
3798
|
-
try {
|
|
3799
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3800
|
-
} catch {
|
|
3801
|
-
continue;
|
|
3802
|
-
}
|
|
3803
|
-
for (const table of ["dependencies", "dev-dependencies", "build-dependencies"]) {
|
|
3804
|
-
const body = sectionBody(text, table);
|
|
3805
|
-
if (!body) continue;
|
|
3806
|
-
for (const raw of body.split("\n")) {
|
|
3807
|
-
const line = raw.trim();
|
|
3808
|
-
if (!line || line.startsWith("#")) continue;
|
|
3809
|
-
const m = /^([A-Za-z0-9._-]+)\s*=\s*(.+)$/.exec(line);
|
|
3810
|
-
if (!m) continue;
|
|
3811
|
-
const rhs = m[2].trim();
|
|
3812
|
-
let declared = "*";
|
|
3813
|
-
if (rhs.startsWith('"')) {
|
|
3814
|
-
const sm = /^"([^"]+)"/.exec(rhs);
|
|
3815
|
-
if (sm) declared = sm[1];
|
|
3816
|
-
} else if (rhs.startsWith("{")) {
|
|
3817
|
-
const vm = /version\s*=\s*"([^"]+)"/.exec(rhs);
|
|
3818
|
-
if (vm) declared = vm[1];
|
|
3819
|
-
}
|
|
3820
|
-
out.push({ name: m[1], ecosystem: "rust", declared });
|
|
3821
|
-
}
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
3824
|
-
return out;
|
|
3825
|
-
}
|
|
3826
|
-
function rubyDeps(files) {
|
|
3827
|
-
const out = [];
|
|
3828
|
-
for (const file of files) {
|
|
3829
|
-
let text;
|
|
3830
|
-
try {
|
|
3831
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3832
|
-
} catch {
|
|
3833
|
-
continue;
|
|
3834
|
-
}
|
|
3835
|
-
const re = /^\s*gem\s+["']([^"']+)["']\s*(?:,\s*["']([^"']+)["'])?/gm;
|
|
3836
|
-
let m;
|
|
3837
|
-
while (m = re.exec(text)) out.push({ name: m[1], ecosystem: "ruby", declared: m[2] ?? "*" });
|
|
3838
|
-
}
|
|
3839
|
-
return out;
|
|
3840
|
-
}
|
|
3841
|
-
function phpDeps(files) {
|
|
3842
|
-
const out = [];
|
|
3843
|
-
for (const file of files) {
|
|
3844
|
-
let pkg;
|
|
3845
|
-
try {
|
|
3846
|
-
pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
3847
|
-
} catch {
|
|
3848
|
-
continue;
|
|
3849
|
-
}
|
|
3850
|
-
const all = { ...pkg.require, ...pkg["require-dev"] };
|
|
3851
|
-
for (const [name, declared] of Object.entries(all)) {
|
|
3852
|
-
if (!name.includes("/")) continue;
|
|
3853
|
-
out.push({ name, ecosystem: "php", declared });
|
|
3854
|
-
}
|
|
3855
|
-
}
|
|
3856
|
-
return out;
|
|
3857
|
-
}
|
|
3858
|
-
function dotnetDeps(files) {
|
|
3859
|
-
const out = [];
|
|
3860
|
-
for (const file of files) {
|
|
3861
|
-
let text;
|
|
3862
|
-
try {
|
|
3863
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3864
|
-
} catch {
|
|
3865
|
-
continue;
|
|
3866
|
-
}
|
|
3867
|
-
const tagRe = /<PackageReference\b[^>]*>/gi;
|
|
3868
|
-
let m;
|
|
3869
|
-
while (m = tagRe.exec(text)) {
|
|
3870
|
-
const inc = /Include\s*=\s*"([^"]+)"/i.exec(m[0]);
|
|
3871
|
-
if (!inc) continue;
|
|
3872
|
-
const ver = /Version\s*=\s*"([^"]+)"/i.exec(m[0]);
|
|
3873
|
-
out.push({ name: inc[1], ecosystem: "dotnet", declared: ver?.[1] ?? "*" });
|
|
3874
|
-
}
|
|
3875
|
-
}
|
|
3876
|
-
return out;
|
|
3877
|
-
}
|
|
3878
|
-
function swiftDeps(files) {
|
|
3879
|
-
const out = [];
|
|
3880
|
-
for (const file of files) {
|
|
3881
|
-
let text;
|
|
3882
|
-
try {
|
|
3883
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3884
|
-
} catch {
|
|
3885
|
-
continue;
|
|
3886
|
-
}
|
|
3887
|
-
const re = /\.package\(/g;
|
|
3888
|
-
let m;
|
|
3889
|
-
while (m = re.exec(text)) {
|
|
3890
|
-
const win = text.slice(m.index, m.index + 200);
|
|
3891
|
-
const urlM = /url:\s*"([^"]+)"/.exec(win);
|
|
3892
|
-
if (!urlM) continue;
|
|
3893
|
-
const name = (urlM[1].replace(/\.git$/, "").split("/").filter(Boolean).pop() ?? urlM[1]).toLowerCase();
|
|
3894
|
-
const verM = /"(\d+\.\d+(?:\.\d+)?)"/.exec(win.slice(urlM.index + urlM[0].length));
|
|
3895
|
-
out.push({ name, ecosystem: "swift", declared: verM?.[1] ?? "*" });
|
|
3896
|
-
}
|
|
3897
|
-
}
|
|
3898
|
-
return out;
|
|
3899
|
-
}
|
|
3900
|
-
function dartDeps(files) {
|
|
3901
|
-
const out = [];
|
|
3902
|
-
for (const file of files) {
|
|
3903
|
-
let text;
|
|
3904
|
-
try {
|
|
3905
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3906
|
-
} catch {
|
|
3907
|
-
continue;
|
|
3908
|
-
}
|
|
3909
|
-
let section = "";
|
|
3910
|
-
for (const raw of text.split("\n")) {
|
|
3911
|
-
const top = /^([a-z_]+):\s*$/.exec(raw);
|
|
3912
|
-
if (top) {
|
|
3913
|
-
section = top[1];
|
|
3914
|
-
continue;
|
|
3915
|
-
}
|
|
3916
|
-
if (section !== "dependencies" && section !== "dev_dependencies") continue;
|
|
3917
|
-
const m = /^ ([A-Za-z0-9_.]+):\s*(\S.*)?$/.exec(raw);
|
|
3918
|
-
if (!m || m[1] === "flutter" || m[1] === "sdk") continue;
|
|
3919
|
-
const declared = (m[2] ?? "").replace(/^["']|["']$/g, "").trim() || "*";
|
|
3920
|
-
out.push({ name: m[1], ecosystem: "dart", declared });
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
return out;
|
|
3924
|
-
}
|
|
3925
|
-
function javaDeps(files) {
|
|
3926
|
-
const out = [];
|
|
3927
|
-
for (const file of files) {
|
|
3928
|
-
let text;
|
|
3929
|
-
try {
|
|
3930
|
-
text = fs17.readFileSync(file, "utf8");
|
|
3931
|
-
} catch {
|
|
3932
|
-
continue;
|
|
3933
|
-
}
|
|
3934
|
-
if (path17.basename(file) === "pom.xml") {
|
|
3935
|
-
for (const block of text.match(/<dependency>[\s\S]*?<\/dependency>/g) ?? []) {
|
|
3936
|
-
const g = /<groupId>([^<]+)<\/groupId>/.exec(block);
|
|
3937
|
-
const a = /<artifactId>([^<]+)<\/artifactId>/.exec(block);
|
|
3938
|
-
if (!g || !a) continue;
|
|
3939
|
-
const v = /<version>([^<]+)<\/version>/.exec(block);
|
|
3940
|
-
const declared = v ? v[1].trim().startsWith("${") ? "*" : v[1].trim() : "*";
|
|
3941
|
-
out.push({ name: `${g[1].trim()}:${a[1].trim()}`, ecosystem: "java", declared });
|
|
3942
|
-
}
|
|
3943
|
-
} else {
|
|
3944
|
-
const re = /(?:implementation|api|compileOnly|runtimeOnly|testImplementation|testRuntimeOnly|annotationProcessor)\s*[(]?\s*["']([\w.-]+):([\w.-]+):([^"']+)["']/g;
|
|
3945
|
-
let m;
|
|
3946
|
-
while (m = re.exec(text)) out.push({ name: `${m[1]}:${m[2]}`, ecosystem: "java", declared: m[3] });
|
|
3947
|
-
}
|
|
3948
|
-
}
|
|
3949
|
-
return out;
|
|
3950
|
-
}
|
|
3951
|
-
async function enrichOnline(records, fetchImpl = globalThis.fetch) {
|
|
3952
|
-
for (const r of records) {
|
|
3953
|
-
if (r.ecosystem !== "npm" || !r.installed) continue;
|
|
3954
|
-
try {
|
|
3955
|
-
const res = await fetchImpl(`https://registry.npmjs.org/${encodeURIComponent(r.name)}/latest`);
|
|
3956
|
-
if (!res.ok) continue;
|
|
3957
|
-
const data = await res.json();
|
|
3958
|
-
r.latest = data.version;
|
|
3959
|
-
r.drift = classify(r.installed, r.latest);
|
|
3960
|
-
} catch {
|
|
3961
|
-
r.drift = "unknown";
|
|
3962
|
-
}
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
|
-
function classify(installed, latest) {
|
|
3966
|
-
if (!latest) return "unknown";
|
|
3967
|
-
const a = installed.split(".").map(Number);
|
|
3968
|
-
const b = latest.split(".").map(Number);
|
|
3969
|
-
if (a[0] !== b[0]) return "major";
|
|
3970
|
-
if (a[1] !== b[1]) return "minor";
|
|
3971
|
-
if (a[2] !== b[2]) return "patch";
|
|
3972
|
-
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
|
+
}
|
|
3973
2819
|
}
|
|
3974
2820
|
function discoverModels(home = os.homedir()) {
|
|
3975
2821
|
return [...ollama(home), ...lmStudio(home), ...looseGguf(home)].sort(
|
|
@@ -3977,10 +2823,10 @@ function discoverModels(home = os.homedir()) {
|
|
|
3977
2823
|
);
|
|
3978
2824
|
}
|
|
3979
2825
|
function ollama(home) {
|
|
3980
|
-
const base =
|
|
2826
|
+
const base = path2.join(home, ".ollama", "models", "manifests");
|
|
3981
2827
|
const out = [];
|
|
3982
2828
|
walk(base, 4, (file) => {
|
|
3983
|
-
const rel2 =
|
|
2829
|
+
const rel2 = path2.relative(base, file).split(path2.sep);
|
|
3984
2830
|
if (rel2.length >= 2) {
|
|
3985
2831
|
const tag = rel2[rel2.length - 1];
|
|
3986
2832
|
const model = rel2[rel2.length - 2];
|
|
@@ -3990,24 +2836,24 @@ function ollama(home) {
|
|
|
3990
2836
|
return dedupe(out);
|
|
3991
2837
|
}
|
|
3992
2838
|
function lmStudio(home) {
|
|
3993
|
-
const bases = [
|
|
2839
|
+
const bases = [path2.join(home, ".lmstudio", "models"), path2.join(home, ".cache", "lm-studio", "models")];
|
|
3994
2840
|
const out = [];
|
|
3995
2841
|
for (const base of bases) {
|
|
3996
2842
|
walk(base, 5, (file) => {
|
|
3997
2843
|
if (!file.endsWith(".gguf")) return;
|
|
3998
|
-
const rel2 =
|
|
2844
|
+
const rel2 = path2.relative(base, file).replace(/\\/g, "/");
|
|
3999
2845
|
out.push({ runtime: "lm-studio", name: rel2, path: file });
|
|
4000
2846
|
});
|
|
4001
2847
|
}
|
|
4002
2848
|
return dedupe(out);
|
|
4003
2849
|
}
|
|
4004
2850
|
function looseGguf(home) {
|
|
4005
|
-
const bases = [
|
|
2851
|
+
const bases = [path2.join(home, "models"), path2.join(home, ".cache", "huggingface")];
|
|
4006
2852
|
const out = [];
|
|
4007
2853
|
for (const base of bases) {
|
|
4008
2854
|
walk(base, 4, (file) => {
|
|
4009
2855
|
if (!file.endsWith(".gguf")) return;
|
|
4010
|
-
out.push({ runtime: "gguf", name:
|
|
2856
|
+
out.push({ runtime: "gguf", name: path2.basename(file), path: file });
|
|
4011
2857
|
});
|
|
4012
2858
|
}
|
|
4013
2859
|
return dedupe(out);
|
|
@@ -4016,12 +2862,12 @@ function walk(dir, depth, onFile) {
|
|
|
4016
2862
|
if (depth < 0) return;
|
|
4017
2863
|
let entries;
|
|
4018
2864
|
try {
|
|
4019
|
-
entries =
|
|
2865
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
4020
2866
|
} catch {
|
|
4021
2867
|
return;
|
|
4022
2868
|
}
|
|
4023
2869
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4024
|
-
const abs =
|
|
2870
|
+
const abs = path2.join(dir, e.name);
|
|
4025
2871
|
if (e.isDirectory()) walk(abs, depth - 1, onFile);
|
|
4026
2872
|
else if (e.isFile()) onFile(abs);
|
|
4027
2873
|
}
|
|
@@ -4037,457 +2883,11 @@ function dedupe(models) {
|
|
|
4037
2883
|
}
|
|
4038
2884
|
return out;
|
|
4039
2885
|
}
|
|
4040
|
-
function lockfileVersion(root, ecosystem, name) {
|
|
4041
|
-
if (ecosystem === "npm") return packageLockVersion(root, name) ?? yarnLockVersion(root, name);
|
|
4042
|
-
if (ecosystem === "pypi") return pypiLockVersion(root, name);
|
|
4043
|
-
if (ecosystem === "rust") return tomlPackageLock(root, "Cargo.lock", name, rustName);
|
|
4044
|
-
if (ecosystem === "ruby") return gemfileLock(root, name);
|
|
4045
|
-
if (ecosystem === "php") return composerLock(root, name);
|
|
4046
|
-
if (ecosystem === "dotnet") return packagesLock(root, name);
|
|
4047
|
-
if (ecosystem === "swift") return packageResolved(root, name);
|
|
4048
|
-
if (ecosystem === "dart") return pubspecLock(root, name);
|
|
4049
|
-
if (ecosystem === "java") return gradleLock(root, name);
|
|
4050
|
-
return void 0;
|
|
4051
|
-
}
|
|
4052
|
-
function gradleLock(root, name) {
|
|
4053
|
-
let text;
|
|
4054
|
-
try {
|
|
4055
|
-
text = fs17.readFileSync(path17.join(root, "gradle.lockfile"), "utf8");
|
|
4056
|
-
} catch {
|
|
4057
|
-
return void 0;
|
|
4058
|
-
}
|
|
4059
|
-
const m = new RegExp(`^${escapeRegExp(name)}:([^=\\s]+)=`, "m").exec(text);
|
|
4060
|
-
return m ? m[1] : void 0;
|
|
4061
|
-
}
|
|
4062
|
-
function packageResolved(root, name) {
|
|
4063
|
-
let data;
|
|
4064
|
-
try {
|
|
4065
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "Package.resolved"), "utf8"));
|
|
4066
|
-
} catch {
|
|
4067
|
-
return void 0;
|
|
4068
|
-
}
|
|
4069
|
-
const pins = data.pins ?? data.object?.pins;
|
|
4070
|
-
if (!Array.isArray(pins)) return void 0;
|
|
4071
|
-
const target = name.toLowerCase();
|
|
4072
|
-
for (const p of pins) {
|
|
4073
|
-
const id = (p.identity ?? p.package ?? "").toLowerCase();
|
|
4074
|
-
if (id === target && p.state?.version) return p.state.version;
|
|
4075
|
-
}
|
|
4076
|
-
return void 0;
|
|
4077
|
-
}
|
|
4078
|
-
function pubspecLock(root, name) {
|
|
4079
|
-
let text;
|
|
4080
|
-
try {
|
|
4081
|
-
text = fs17.readFileSync(path17.join(root, "pubspec.lock"), "utf8");
|
|
4082
|
-
} catch {
|
|
4083
|
-
return void 0;
|
|
4084
|
-
}
|
|
4085
|
-
let inPkg = false;
|
|
4086
|
-
for (const line of text.split("\n")) {
|
|
4087
|
-
const key = /^ ([A-Za-z0-9_.]+):\s*$/.exec(line);
|
|
4088
|
-
if (key) {
|
|
4089
|
-
inPkg = key[1] === name;
|
|
4090
|
-
continue;
|
|
4091
|
-
}
|
|
4092
|
-
if (inPkg) {
|
|
4093
|
-
const vm = /^\s+version:\s*"?([^"\s]+)"?/.exec(line);
|
|
4094
|
-
if (vm) return vm[1];
|
|
4095
|
-
}
|
|
4096
|
-
}
|
|
4097
|
-
return void 0;
|
|
4098
|
-
}
|
|
4099
|
-
function escapeRegExp(s) {
|
|
4100
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4101
|
-
}
|
|
4102
|
-
function gemfileLock(root, name) {
|
|
4103
|
-
let text;
|
|
4104
|
-
try {
|
|
4105
|
-
text = fs17.readFileSync(path17.join(root, "Gemfile.lock"), "utf8");
|
|
4106
|
-
} catch {
|
|
4107
|
-
return void 0;
|
|
4108
|
-
}
|
|
4109
|
-
const m = new RegExp(`^ ${escapeRegExp(name)} \\((\\d[^)]*)\\)`, "m").exec(text);
|
|
4110
|
-
return m ? m[1] : void 0;
|
|
4111
|
-
}
|
|
4112
|
-
function composerLock(root, name) {
|
|
4113
|
-
let data;
|
|
4114
|
-
try {
|
|
4115
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "composer.lock"), "utf8"));
|
|
4116
|
-
} catch {
|
|
4117
|
-
return void 0;
|
|
4118
|
-
}
|
|
4119
|
-
for (const section of ["packages", "packages-dev"]) {
|
|
4120
|
-
const arr = data[section];
|
|
4121
|
-
if (!Array.isArray(arr)) continue;
|
|
4122
|
-
const p = arr.find((x) => x && x.name === name);
|
|
4123
|
-
if (p && typeof p.version === "string") return p.version.replace(/^v/, "");
|
|
4124
|
-
}
|
|
4125
|
-
return void 0;
|
|
4126
|
-
}
|
|
4127
|
-
function packagesLock(root, name) {
|
|
4128
|
-
let data;
|
|
4129
|
-
try {
|
|
4130
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "packages.lock.json"), "utf8"));
|
|
4131
|
-
} catch {
|
|
4132
|
-
return void 0;
|
|
4133
|
-
}
|
|
4134
|
-
const target = name.toLowerCase();
|
|
4135
|
-
for (const fw of Object.values(data.dependencies ?? {})) {
|
|
4136
|
-
if (!fw || typeof fw !== "object") continue;
|
|
4137
|
-
for (const [id, v] of Object.entries(fw)) {
|
|
4138
|
-
if (id.toLowerCase() === target && v && typeof v.resolved === "string") return v.resolved;
|
|
4139
|
-
}
|
|
4140
|
-
}
|
|
4141
|
-
return void 0;
|
|
4142
|
-
}
|
|
4143
|
-
function pep503(name) {
|
|
4144
|
-
return name.trim().toLowerCase().replace(/[-_.]+/g, "-");
|
|
4145
|
-
}
|
|
4146
|
-
function rustName(name) {
|
|
4147
|
-
return name.trim().toLowerCase();
|
|
4148
|
-
}
|
|
4149
|
-
function pypiLockVersion(root, name) {
|
|
4150
|
-
return tomlPackageLock(root, "poetry.lock", name, pep503) ?? tomlPackageLock(root, "uv.lock", name, pep503) ?? pipfileLock(root, name);
|
|
4151
|
-
}
|
|
4152
|
-
function tomlPackageLock(root, file, name, normalize3) {
|
|
4153
|
-
let text;
|
|
4154
|
-
try {
|
|
4155
|
-
text = fs17.readFileSync(path17.join(root, file), "utf8");
|
|
4156
|
-
} catch {
|
|
4157
|
-
return void 0;
|
|
4158
|
-
}
|
|
4159
|
-
const target = normalize3(name);
|
|
4160
|
-
for (const block of text.split(/\[\[package\]\]/)) {
|
|
4161
|
-
const nm = /(?:^|\n)\s*name\s*=\s*"([^"]+)"/.exec(block);
|
|
4162
|
-
if (nm && normalize3(nm[1]) === target) {
|
|
4163
|
-
const ver = /(?:^|\n)\s*version\s*=\s*"([^"]+)"/.exec(block);
|
|
4164
|
-
if (ver) return ver[1];
|
|
4165
|
-
}
|
|
4166
|
-
}
|
|
4167
|
-
return void 0;
|
|
4168
|
-
}
|
|
4169
|
-
function pipfileLock(root, name) {
|
|
4170
|
-
let data;
|
|
4171
|
-
try {
|
|
4172
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "Pipfile.lock"), "utf8"));
|
|
4173
|
-
} catch {
|
|
4174
|
-
return void 0;
|
|
4175
|
-
}
|
|
4176
|
-
const target = pep503(name);
|
|
4177
|
-
for (const section of ["default", "develop"]) {
|
|
4178
|
-
const deps = data[section];
|
|
4179
|
-
if (!deps || typeof deps !== "object") continue;
|
|
4180
|
-
for (const [k, v] of Object.entries(deps)) {
|
|
4181
|
-
if (pep503(k) === target && v && typeof v.version === "string") {
|
|
4182
|
-
return v.version.replace(/^==/, "");
|
|
4183
|
-
}
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
return void 0;
|
|
4187
|
-
}
|
|
4188
|
-
function packageLockVersion(root, name) {
|
|
4189
|
-
let data;
|
|
4190
|
-
try {
|
|
4191
|
-
data = JSON.parse(fs17.readFileSync(path17.join(root, "package-lock.json"), "utf8"));
|
|
4192
|
-
} catch {
|
|
4193
|
-
return void 0;
|
|
4194
|
-
}
|
|
4195
|
-
const top = data.packages?.[`node_modules/${name}`]?.version;
|
|
4196
|
-
if (typeof top === "string") return top;
|
|
4197
|
-
const v1 = data.dependencies?.[name]?.version;
|
|
4198
|
-
return typeof v1 === "string" ? v1 : void 0;
|
|
4199
|
-
}
|
|
4200
|
-
function yarnLockVersion(root, name) {
|
|
4201
|
-
let text;
|
|
4202
|
-
try {
|
|
4203
|
-
text = fs17.readFileSync(path17.join(root, "yarn.lock"), "utf8");
|
|
4204
|
-
} catch {
|
|
4205
|
-
return void 0;
|
|
4206
|
-
}
|
|
4207
|
-
let inBlock = false;
|
|
4208
|
-
for (const line of text.split("\n")) {
|
|
4209
|
-
if (line && !/^\s/.test(line) && !line.startsWith("#")) {
|
|
4210
|
-
inBlock = headerNamesPackage(line, name);
|
|
4211
|
-
} else if (inBlock) {
|
|
4212
|
-
const m = /^\s+version:?\s+"?([^"\s]+)"?/.exec(line);
|
|
4213
|
-
if (m) return m[1];
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
return void 0;
|
|
4217
|
-
}
|
|
4218
|
-
function headerNamesPackage(header, name) {
|
|
4219
|
-
return header.replace(/:\s*$/, "").split(",").some((spec) => {
|
|
4220
|
-
const s = spec.trim().replace(/^"|"$/g, "");
|
|
4221
|
-
const at = s.lastIndexOf("@");
|
|
4222
|
-
if (at <= 0) return false;
|
|
4223
|
-
const specName = s.slice(0, at);
|
|
4224
|
-
return specName === name;
|
|
4225
|
-
});
|
|
4226
|
-
}
|
|
4227
|
-
|
|
4228
|
-
// src/engine/lib.ts
|
|
4229
|
-
var LIB_SCHEMA = "vg-lib/1.0";
|
|
4230
|
-
function catalogPath(root) {
|
|
4231
|
-
return path17.join(root, "vibgrate.lib.json");
|
|
4232
|
-
}
|
|
4233
|
-
function libDir(root) {
|
|
4234
|
-
return path17.join(root, ".vibgrate", "lib");
|
|
4235
|
-
}
|
|
4236
|
-
function libId(name) {
|
|
4237
|
-
return name.trim().toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
4238
|
-
}
|
|
4239
|
-
function loadCatalog(root) {
|
|
4240
|
-
const file = catalogPath(root);
|
|
4241
|
-
if (fs17.existsSync(file)) {
|
|
4242
|
-
try {
|
|
4243
|
-
const data = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
4244
|
-
if (data.schemaVersion === LIB_SCHEMA && data.libraries) return data;
|
|
4245
|
-
} catch {
|
|
4246
|
-
}
|
|
4247
|
-
}
|
|
4248
|
-
return { schemaVersion: LIB_SCHEMA, libraries: {} };
|
|
4249
|
-
}
|
|
4250
|
-
function saveCatalog(root, catalog) {
|
|
4251
|
-
fs17.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
|
|
4252
|
-
`);
|
|
4253
|
-
}
|
|
4254
|
-
function resolveLib(catalog, name) {
|
|
4255
|
-
const id = libId(name);
|
|
4256
|
-
if (catalog.libraries[id]) return catalog.libraries[id];
|
|
4257
|
-
const lower = name.toLowerCase();
|
|
4258
|
-
const all = Object.values(catalog.libraries);
|
|
4259
|
-
return all.find((e) => e.name.toLowerCase() === lower) ?? all.find((e) => e.id.includes(id) || e.name.toLowerCase().includes(lower));
|
|
4260
|
-
}
|
|
4261
|
-
function installedVersion(root, name) {
|
|
4262
|
-
const rec = inventory(root).records.find((r) => r.name === name);
|
|
4263
|
-
return rec?.installed ?? rec?.declared;
|
|
4264
|
-
}
|
|
4265
|
-
var stripRange = (v) => v?.replace(/^[v^~>=<\s]+/, "");
|
|
4266
|
-
function resolveVersion(root, name) {
|
|
4267
|
-
const rec = inventory(root).records.find((r) => r.name === name);
|
|
4268
|
-
const declared = rec?.declared;
|
|
4269
|
-
const installed = rec?.installed ?? (rec ? installedTreeVersion(root, rec.ecosystem, name) : void 0);
|
|
4270
|
-
const lockfile = rec ? lockfileVersion(root, rec.ecosystem, name) : void 0;
|
|
4271
|
-
let mismatch;
|
|
4272
|
-
if (lockfile && installed && stripRange(lockfile) !== stripRange(installed)) {
|
|
4273
|
-
mismatch = {
|
|
4274
|
-
lockfile,
|
|
4275
|
-
installed,
|
|
4276
|
-
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)`
|
|
4277
|
-
};
|
|
4278
|
-
}
|
|
4279
|
-
const served = lockfile ?? installed ?? declared;
|
|
4280
|
-
const source = lockfile ? "lockfile" : installed ? "installed" : declared ? "declared" : "unknown";
|
|
4281
|
-
return { served, source, lockfile, installed, declared, mismatch };
|
|
4282
|
-
}
|
|
4283
|
-
function driftFor(root, entry) {
|
|
4284
|
-
const installed = installedVersion(root, entry.name);
|
|
4285
|
-
if (!installed || entry.version === "*") return { cataloged: entry.version, installed, drift: "unknown" };
|
|
4286
|
-
const norm = (v) => v.replace(/^[v^~>=<\s]+/, "");
|
|
4287
|
-
const a = norm(entry.version);
|
|
4288
|
-
const b = norm(installed);
|
|
4289
|
-
if (a === b) return { cataloged: entry.version, installed, drift: "current" };
|
|
4290
|
-
return { cataloged: entry.version, installed, drift: cmp(a, b) < 0 ? "behind" : "ahead" };
|
|
4291
|
-
}
|
|
4292
|
-
function cmp(a, b) {
|
|
4293
|
-
const pa = a.split(".").map(Number);
|
|
4294
|
-
const pb = b.split(".").map(Number);
|
|
4295
|
-
for (let i = 0; i < 3; i++) {
|
|
4296
|
-
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
4297
|
-
if (d) return d;
|
|
4298
|
-
}
|
|
4299
|
-
return 0;
|
|
4300
|
-
}
|
|
4301
|
-
function parseGitSource(source) {
|
|
4302
|
-
if (source.startsWith("git+")) return source.slice(4);
|
|
4303
|
-
if (/^git@[^:]+:/.test(source)) return source;
|
|
4304
|
-
if (/\.git\/?$/.test(source)) return source;
|
|
4305
|
-
return null;
|
|
4306
|
-
}
|
|
4307
|
-
async function addLibrary(source, opts) {
|
|
4308
|
-
const { root } = opts;
|
|
4309
|
-
let content;
|
|
4310
|
-
let type = "local";
|
|
4311
|
-
const gitUrl = parseGitSource(source);
|
|
4312
|
-
if (gitUrl) {
|
|
4313
|
-
if (!opts.allowNetwork) {
|
|
4314
|
-
throw new Error(`refusing to clone ${source} without --online (offline by default)`);
|
|
4315
|
-
}
|
|
4316
|
-
content = cloneAndReadGit(gitUrl);
|
|
4317
|
-
type = "git";
|
|
4318
|
-
} else if (/^https?:\/\//.test(source)) {
|
|
4319
|
-
if (!opts.allowNetwork) {
|
|
4320
|
-
throw new Error(`refusing to fetch ${source} without --online (offline by default)`);
|
|
4321
|
-
}
|
|
4322
|
-
const res = await (opts.fetchImpl ?? globalThis.fetch)(source);
|
|
4323
|
-
if (!res.ok) throw new Error(`fetch failed (${res.status}) for ${source}`);
|
|
4324
|
-
content = await res.text();
|
|
4325
|
-
type = source.endsWith("llms.txt") ? "llms.txt" : source.endsWith(".json") ? "openapi" : "website";
|
|
4326
|
-
} else {
|
|
4327
|
-
const abs = path17.resolve(root, source);
|
|
4328
|
-
if (!fs17.existsSync(abs)) throw new Error(`source not found: ${source}`);
|
|
4329
|
-
content = readLocal(abs);
|
|
4330
|
-
type = "local";
|
|
4331
|
-
}
|
|
4332
|
-
const name = opts.name ?? inferName(source);
|
|
4333
|
-
const id = libId(name);
|
|
4334
|
-
const version = opts.version ?? installedVersion(root, name) ?? "*";
|
|
4335
|
-
fs17.mkdirSync(libDir(root), { recursive: true });
|
|
4336
|
-
const docFileAbs = path17.join(libDir(root), `${id}.md`);
|
|
4337
|
-
fs17.writeFileSync(docFileAbs, content);
|
|
4338
|
-
const entry = {
|
|
4339
|
-
id,
|
|
4340
|
-
name,
|
|
4341
|
-
version,
|
|
4342
|
-
source: { type, location: source },
|
|
4343
|
-
docFile: path17.relative(root, docFileAbs).split(path17.sep).join("/"),
|
|
4344
|
-
docHash: hashString(content),
|
|
4345
|
-
bytes: Buffer.byteLength(content, "utf8")
|
|
4346
|
-
};
|
|
4347
|
-
const catalog = loadCatalog(root);
|
|
4348
|
-
catalog.libraries[id] = entry;
|
|
4349
|
-
saveCatalog(root, catalog);
|
|
4350
|
-
return entry;
|
|
4351
|
-
}
|
|
4352
|
-
function readDoc(root, entry) {
|
|
4353
|
-
const abs = path17.resolve(root, entry.docFile);
|
|
4354
|
-
return fs17.existsSync(abs) ? fs17.readFileSync(abs, "utf8") : "";
|
|
4355
|
-
}
|
|
4356
|
-
function npmPackageDir(root, name) {
|
|
4357
|
-
let cur = root;
|
|
4358
|
-
for (let i = 0; i < 12; i++) {
|
|
4359
|
-
const dir = path17.join(cur, "node_modules", name);
|
|
4360
|
-
if (fs17.existsSync(path17.join(dir, "package.json"))) return dir;
|
|
4361
|
-
const parent = path17.dirname(cur);
|
|
4362
|
-
if (parent === cur) break;
|
|
4363
|
-
cur = parent;
|
|
4364
|
-
}
|
|
4365
|
-
return void 0;
|
|
4366
|
-
}
|
|
4367
|
-
function localPackageDocs(root, name) {
|
|
4368
|
-
const dir = npmPackageDir(root, name);
|
|
4369
|
-
if (!dir) return void 0;
|
|
4370
|
-
const version = resolveVersion(root, name).served;
|
|
4371
|
-
for (const f of ["llms.txt", "README.md", "README.mdx", "README", "readme.md"]) {
|
|
4372
|
-
const p = path17.join(dir, f);
|
|
4373
|
-
try {
|
|
4374
|
-
const docs = fs17.readFileSync(p, "utf8");
|
|
4375
|
-
if (docs.trim()) {
|
|
4376
|
-
return { docs, version, source: path17.relative(root, p).split(path17.sep).join("/") };
|
|
4377
|
-
}
|
|
4378
|
-
} catch {
|
|
4379
|
-
}
|
|
4380
|
-
}
|
|
4381
|
-
try {
|
|
4382
|
-
const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
|
|
4383
|
-
if (pkg.description?.trim()) {
|
|
4384
|
-
return {
|
|
4385
|
-
docs: `# ${name}
|
|
4386
|
-
|
|
4387
|
-
${pkg.description}`,
|
|
4388
|
-
version: version ?? pkg.version,
|
|
4389
|
-
source: path17.relative(root, path17.join(dir, "package.json")).split(path17.sep).join("/")
|
|
4390
|
-
};
|
|
4391
|
-
}
|
|
4392
|
-
} catch {
|
|
4393
|
-
}
|
|
4394
|
-
return void 0;
|
|
4395
|
-
}
|
|
4396
|
-
function dtsEntry(dir) {
|
|
4397
|
-
try {
|
|
4398
|
-
const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
|
|
4399
|
-
const rel2 = pkg.types ?? pkg.typings;
|
|
4400
|
-
if (typeof rel2 === "string") {
|
|
4401
|
-
const p = path17.join(dir, rel2);
|
|
4402
|
-
if (fs17.existsSync(p)) return p;
|
|
4403
|
-
}
|
|
4404
|
-
} catch {
|
|
4405
|
-
}
|
|
4406
|
-
for (const c of ["index.d.ts", "dist/index.d.ts", "lib/index.d.ts", "types/index.d.ts"]) {
|
|
4407
|
-
const p = path17.join(dir, c);
|
|
4408
|
-
if (fs17.existsSync(p)) return p;
|
|
4409
|
-
}
|
|
4410
|
-
return void 0;
|
|
4411
|
-
}
|
|
4412
|
-
function extractDtsApi(src, maxDecls = 200) {
|
|
4413
|
-
const out = [];
|
|
4414
|
-
let depth = 0;
|
|
4415
|
-
let capture = null;
|
|
4416
|
-
for (const raw of src.split("\n")) {
|
|
4417
|
-
const line = raw.trim();
|
|
4418
|
-
if (capture === null && depth === 0 && /^export\b/.test(line) && !/^export\s+(\{|\*|default\b|type\s+\{)/.test(line)) {
|
|
4419
|
-
capture = [];
|
|
4420
|
-
}
|
|
4421
|
-
if (capture) capture.push(raw);
|
|
4422
|
-
for (const ch of raw) {
|
|
4423
|
-
if (ch === "{") depth++;
|
|
4424
|
-
else if (ch === "}") depth = Math.max(0, depth - 1);
|
|
4425
|
-
}
|
|
4426
|
-
if (capture && depth === 0 && /[;}]\s*$/.test(line)) {
|
|
4427
|
-
out.push(capture.join("\n").trim());
|
|
4428
|
-
capture = null;
|
|
4429
|
-
if (out.length >= maxDecls) break;
|
|
4430
|
-
}
|
|
4431
|
-
}
|
|
4432
|
-
return out;
|
|
4433
|
-
}
|
|
4434
|
-
function localApiSurface(root, name) {
|
|
4435
|
-
const dir = npmPackageDir(root, name);
|
|
4436
|
-
if (!dir) return void 0;
|
|
4437
|
-
const dts = dtsEntry(dir);
|
|
4438
|
-
if (!dts) return void 0;
|
|
4439
|
-
let src;
|
|
4440
|
-
try {
|
|
4441
|
-
src = fs17.readFileSync(dts, "utf8");
|
|
4442
|
-
} catch {
|
|
4443
|
-
return void 0;
|
|
4444
|
-
}
|
|
4445
|
-
const decls = extractDtsApi(src);
|
|
4446
|
-
return decls.length ? decls.join("\n\n") : void 0;
|
|
4447
|
-
}
|
|
4448
|
-
function cloneAndReadGit(url) {
|
|
4449
|
-
const dir = fs17.mkdtempSync(path17.join(os.tmpdir(), "vg-lib-git-"));
|
|
4450
|
-
try {
|
|
4451
|
-
execFileSync("git", ["clone", "--depth", "1", "--quiet", url, dir], {
|
|
4452
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
4453
|
-
});
|
|
4454
|
-
} catch (err) {
|
|
4455
|
-
fs17.rmSync(dir, { recursive: true, force: true });
|
|
4456
|
-
const detail = err instanceof Error && err.message ? `: ${err.message.split("\n")[0]}` : "";
|
|
4457
|
-
throw new Error(`git clone failed for ${url}${detail}`);
|
|
4458
|
-
}
|
|
4459
|
-
try {
|
|
4460
|
-
return readGitDocs(dir);
|
|
4461
|
-
} finally {
|
|
4462
|
-
fs17.rmSync(dir, { recursive: true, force: true });
|
|
4463
|
-
}
|
|
4464
|
-
}
|
|
4465
|
-
function readGitDocs(dir) {
|
|
4466
|
-
const docsDir = path17.join(dir, "docs");
|
|
4467
|
-
if (fs17.existsSync(docsDir) && fs17.statSync(docsDir).isDirectory()) return readLocal(docsDir);
|
|
4468
|
-
const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path17.join(dir, f)).find((p) => fs17.existsSync(p));
|
|
4469
|
-
if (readme) return fs17.readFileSync(readme, "utf8");
|
|
4470
|
-
return readLocal(dir);
|
|
4471
|
-
}
|
|
4472
|
-
function readLocal(abs) {
|
|
4473
|
-
const stat = fs17.statSync(abs);
|
|
4474
|
-
if (stat.isFile()) return fs17.readFileSync(abs, "utf8");
|
|
4475
|
-
const parts = [];
|
|
4476
|
-
const files = fs17.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
|
|
4477
|
-
for (const f of files) parts.push(`<!-- ${f} -->
|
|
4478
|
-
${fs17.readFileSync(path17.join(abs, f), "utf8")}`);
|
|
4479
|
-
return parts.join("\n\n");
|
|
4480
|
-
}
|
|
4481
|
-
function inferName(source) {
|
|
4482
|
-
const cleaned = source.replace(/^git\+/, "").replace(/\.git\/?$/i, "");
|
|
4483
|
-
const base = cleaned.replace(/\/+$/, "").split(/[/:]/).pop() ?? cleaned;
|
|
4484
|
-
return base.replace(/\.(md|mdx|txt|rst|json)$/i, "").replace(/^llms$/, "docs");
|
|
4485
|
-
}
|
|
4486
2886
|
function findGitRoot(startDir = process.cwd()) {
|
|
4487
|
-
let dir =
|
|
2887
|
+
let dir = path2.resolve(startDir);
|
|
4488
2888
|
for (; ; ) {
|
|
4489
|
-
if (
|
|
4490
|
-
const parent =
|
|
2889
|
+
if (fs18.existsSync(path2.join(dir, ".git"))) return dir;
|
|
2890
|
+
const parent = path2.dirname(dir);
|
|
4491
2891
|
if (parent === dir) return null;
|
|
4492
2892
|
dir = parent;
|
|
4493
2893
|
}
|
|
@@ -4498,19 +2898,19 @@ function normalizeEntry(line) {
|
|
|
4498
2898
|
function ensureGitignored(entry, startDir = process.cwd()) {
|
|
4499
2899
|
const root = findGitRoot(startDir);
|
|
4500
2900
|
if (!root) return { status: "not-a-repo", entry };
|
|
4501
|
-
const gitignorePath =
|
|
2901
|
+
const gitignorePath = path2.join(root, ".gitignore");
|
|
4502
2902
|
const target = normalizeEntry(entry);
|
|
4503
2903
|
let existing = "";
|
|
4504
2904
|
let fileExisted = true;
|
|
4505
2905
|
try {
|
|
4506
|
-
existing =
|
|
2906
|
+
existing = fs18.readFileSync(gitignorePath, "utf8");
|
|
4507
2907
|
} catch {
|
|
4508
2908
|
fileExisted = false;
|
|
4509
2909
|
}
|
|
4510
2910
|
const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
|
|
4511
2911
|
if (alreadyPresent) return { status: "present", entry, gitignorePath };
|
|
4512
2912
|
const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
|
|
4513
|
-
|
|
2913
|
+
fs18.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
|
|
4514
2914
|
`, "utf8");
|
|
4515
2915
|
return { status: fileExisted ? "added" : "created", entry, gitignorePath };
|
|
4516
2916
|
}
|
|
@@ -4664,14 +3064,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
4664
3064
|
console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
|
|
4665
3065
|
}
|
|
4666
3066
|
if (opts.write) {
|
|
4667
|
-
const writePath =
|
|
3067
|
+
const writePath = path2.resolve(opts.write);
|
|
4668
3068
|
await writeTextFile(writePath, dsn + "\n");
|
|
4669
3069
|
console.log("");
|
|
4670
3070
|
console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
|
|
4671
|
-
const root = findGitRoot(
|
|
4672
|
-
const rel2 = root ?
|
|
3071
|
+
const root = findGitRoot(path2.dirname(writePath));
|
|
3072
|
+
const rel2 = root ? path2.relative(root, writePath).split(path2.sep).join("/") : "";
|
|
4673
3073
|
if (root && rel2 && !rel2.startsWith("..")) {
|
|
4674
|
-
const res = ensureGitignored(rel2,
|
|
3074
|
+
const res = ensureGitignored(rel2, path2.dirname(writePath));
|
|
4675
3075
|
if (res.status === "created") {
|
|
4676
3076
|
console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
|
|
4677
3077
|
} else if (res.status === "added") {
|
|
@@ -4689,30 +3089,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
4689
3089
|
var STORE_DIRNAME = ".vibgrate";
|
|
4690
3090
|
var STORE_FILENAME = "credentials.json";
|
|
4691
3091
|
function homeCredentialsPath() {
|
|
4692
|
-
return
|
|
3092
|
+
return path2.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
|
|
4693
3093
|
}
|
|
4694
3094
|
function projectCredentialsPath(cwd = process.cwd()) {
|
|
4695
3095
|
const root = findGitRoot(cwd) ?? cwd;
|
|
4696
|
-
return
|
|
3096
|
+
return path2.join(root, STORE_DIRNAME, STORE_FILENAME);
|
|
4697
3097
|
}
|
|
4698
3098
|
function credentialsPath(opts = {}) {
|
|
4699
3099
|
const override = process.env.VIBGRATE_CREDENTIALS?.trim();
|
|
4700
|
-
if (override) return
|
|
3100
|
+
if (override) return path2.resolve(override);
|
|
4701
3101
|
const local = projectCredentialsPath(opts.cwd);
|
|
4702
3102
|
if (opts.local) return local;
|
|
4703
|
-
if (
|
|
3103
|
+
if (fs18.existsSync(local)) return local;
|
|
4704
3104
|
return homeCredentialsPath();
|
|
4705
3105
|
}
|
|
4706
3106
|
function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
|
|
4707
|
-
const rel2 =
|
|
4708
|
-
if (rel2 && !rel2.startsWith("..") && !
|
|
4709
|
-
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("/");
|
|
4710
3110
|
}
|
|
4711
3111
|
return `${STORE_DIRNAME}/${STORE_FILENAME}`;
|
|
4712
3112
|
}
|
|
4713
3113
|
function readStoredCredentials(opts = {}) {
|
|
4714
3114
|
try {
|
|
4715
|
-
const raw =
|
|
3115
|
+
const raw = fs18.readFileSync(credentialsPath(opts), "utf8");
|
|
4716
3116
|
const parsed = JSON.parse(raw);
|
|
4717
3117
|
return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
|
|
4718
3118
|
} catch {
|
|
@@ -4721,16 +3121,16 @@ function readStoredCredentials(opts = {}) {
|
|
|
4721
3121
|
}
|
|
4722
3122
|
function writeStoredCredentials(creds, opts = {}) {
|
|
4723
3123
|
const file = credentialsPath(opts);
|
|
4724
|
-
|
|
4725
|
-
|
|
3124
|
+
fs18.mkdirSync(path2.dirname(file), { recursive: true });
|
|
3125
|
+
fs18.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
4726
3126
|
try {
|
|
4727
|
-
|
|
3127
|
+
fs18.chmodSync(file, 384);
|
|
4728
3128
|
} catch {
|
|
4729
3129
|
}
|
|
4730
3130
|
}
|
|
4731
3131
|
function clearStoredCredentials(opts = {}) {
|
|
4732
3132
|
try {
|
|
4733
|
-
|
|
3133
|
+
fs18.rmSync(credentialsPath(opts));
|
|
4734
3134
|
return true;
|
|
4735
3135
|
} catch {
|
|
4736
3136
|
return false;
|
|
@@ -5101,7 +3501,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
5101
3501
|
if (opts.strict) process.exit(1);
|
|
5102
3502
|
return;
|
|
5103
3503
|
}
|
|
5104
|
-
const filePath =
|
|
3504
|
+
const filePath = path2.resolve(opts.file);
|
|
5105
3505
|
if (!await pathExists(filePath)) {
|
|
5106
3506
|
console.error(chalk.red(`Scan artifact not found: ${filePath}`));
|
|
5107
3507
|
console.error(chalk.dim('Run "vibgrate scan" first.'));
|
|
@@ -5164,8 +3564,8 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
5164
3564
|
});
|
|
5165
3565
|
function readScanArtifact(root) {
|
|
5166
3566
|
try {
|
|
5167
|
-
const file =
|
|
5168
|
-
return JSON.parse(
|
|
3567
|
+
const file = path2.join(root, ".vibgrate", "scan_result.json");
|
|
3568
|
+
return JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
5169
3569
|
} catch {
|
|
5170
3570
|
return null;
|
|
5171
3571
|
}
|
|
@@ -5270,7 +3670,7 @@ var DEFAULT_SELECTION_WEIGHTS = {
|
|
|
5270
3670
|
};
|
|
5271
3671
|
|
|
5272
3672
|
// src/engine/select.ts
|
|
5273
|
-
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"]);
|
|
5274
3674
|
var USAGE_RE = /\b(usage|example|examples|quick ?start|getting started|api|guide|how to|reference|tutorial|basic)\b/i;
|
|
5275
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;
|
|
5276
3676
|
function splitSections(md) {
|
|
@@ -5317,9 +3717,9 @@ function chunkBody(body) {
|
|
|
5317
3717
|
function stripNoise(body) {
|
|
5318
3718
|
return body.split("\n").filter((l) => !/^\s*(?:!\[[^\]]*\]\([^)]*\)\s*)+$/.test(l)).join("\n").trim();
|
|
5319
3719
|
}
|
|
5320
|
-
function tokenizeQuery(
|
|
5321
|
-
if (!
|
|
5322
|
-
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)))];
|
|
5323
3723
|
}
|
|
5324
3724
|
function symbolsFromApi(api) {
|
|
5325
3725
|
if (!api) return [];
|
|
@@ -5551,11 +3951,11 @@ async function publishPrivateLibrary(req, opts = {}) {
|
|
|
5551
3951
|
}
|
|
5552
3952
|
|
|
5553
3953
|
// src/engine/hosted-cache.ts
|
|
5554
|
-
var
|
|
3954
|
+
var CACHE_VERSION = "vg-hosted-docs/1";
|
|
5555
3955
|
var HOSTED_DOCS_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5556
3956
|
var MAX_ENTRIES = 64;
|
|
5557
|
-
function
|
|
5558
|
-
return
|
|
3957
|
+
function cachePath(root) {
|
|
3958
|
+
return path2.join(cacheDir(root), "hosted-docs.json");
|
|
5559
3959
|
}
|
|
5560
3960
|
function hostedDocsCacheKey(req, opts = {}) {
|
|
5561
3961
|
const material = JSON.stringify([
|
|
@@ -5571,11 +3971,11 @@ function hostedDocsCacheKey(req, opts = {}) {
|
|
|
5571
3971
|
}
|
|
5572
3972
|
function loadFile(root) {
|
|
5573
3973
|
try {
|
|
5574
|
-
const parsed = JSON.parse(
|
|
5575
|
-
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;
|
|
5576
3976
|
} catch {
|
|
5577
3977
|
}
|
|
5578
|
-
return { version:
|
|
3978
|
+
return { version: CACHE_VERSION, entries: {} };
|
|
5579
3979
|
}
|
|
5580
3980
|
function saveFile(root, file) {
|
|
5581
3981
|
try {
|
|
@@ -5585,8 +3985,8 @@ function saveFile(root, file) {
|
|
|
5585
3985
|
delete file.entries[k];
|
|
5586
3986
|
}
|
|
5587
3987
|
}
|
|
5588
|
-
|
|
5589
|
-
|
|
3988
|
+
fs18.mkdirSync(cacheDir(root), { recursive: true });
|
|
3989
|
+
fs18.writeFileSync(cachePath(root), JSON.stringify(file));
|
|
5590
3990
|
} catch {
|
|
5591
3991
|
}
|
|
5592
3992
|
}
|
|
@@ -5711,7 +4111,7 @@ function measure(root, files) {
|
|
|
5711
4111
|
for (const rel2 of files) {
|
|
5712
4112
|
let size;
|
|
5713
4113
|
try {
|
|
5714
|
-
size =
|
|
4114
|
+
size = fs18.statSync(path2.join(root, rel2)).size;
|
|
5715
4115
|
} catch {
|
|
5716
4116
|
continue;
|
|
5717
4117
|
}
|
|
@@ -5734,9 +4134,9 @@ function matchLines(text, needleLower) {
|
|
|
5734
4134
|
}
|
|
5735
4135
|
function readCandidate(abs, knownSize) {
|
|
5736
4136
|
try {
|
|
5737
|
-
const size = knownSize ??
|
|
4137
|
+
const size = knownSize ?? fs18.statSync(abs).size;
|
|
5738
4138
|
if (size > MAX_FILE_BYTES) return null;
|
|
5739
|
-
const text =
|
|
4139
|
+
const text = fs18.readFileSync(abs, "utf8");
|
|
5740
4140
|
return text.includes(NUL) ? null : text;
|
|
5741
4141
|
} catch {
|
|
5742
4142
|
return null;
|
|
@@ -5747,7 +4147,7 @@ function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
|
|
|
5747
4147
|
let total = 0;
|
|
5748
4148
|
let truncated = false;
|
|
5749
4149
|
for (const rel2 of files) {
|
|
5750
|
-
const text = readCandidate(
|
|
4150
|
+
const text = readCandidate(path2.join(root, rel2), sizes?.get(rel2));
|
|
5751
4151
|
if (text === null) continue;
|
|
5752
4152
|
for (const h of matchLines(text, needleLower)) {
|
|
5753
4153
|
total++;
|
|
@@ -5814,12 +4214,12 @@ var WORKER_SOURCE = [
|
|
|
5814
4214
|
"parentPort.postMessage({ hits, total, truncated });"
|
|
5815
4215
|
].join("\n");
|
|
5816
4216
|
function runWorker(root, files, needleLower) {
|
|
5817
|
-
return new Promise((
|
|
4217
|
+
return new Promise((resolve11, reject) => {
|
|
5818
4218
|
const w = new Worker(WORKER_SOURCE, { eval: true, workerData: { root, files, needleLower } });
|
|
5819
4219
|
let settled = false;
|
|
5820
4220
|
w.once("message", (m) => {
|
|
5821
4221
|
settled = true;
|
|
5822
|
-
|
|
4222
|
+
resolve11(m);
|
|
5823
4223
|
});
|
|
5824
4224
|
w.once("error", reject);
|
|
5825
4225
|
w.once("exit", (code) => {
|
|
@@ -5888,26 +4288,26 @@ var BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
|
5888
4288
|
".pdf"
|
|
5889
4289
|
]);
|
|
5890
4290
|
function hasBinaryExt(rel2) {
|
|
5891
|
-
return BINARY_EXTS.has(
|
|
4291
|
+
return BINARY_EXTS.has(path2.extname(rel2).toLowerCase());
|
|
5892
4292
|
}
|
|
5893
4293
|
function toPosix2(rel2) {
|
|
5894
|
-
return
|
|
4294
|
+
return path2.sep === "\\" ? rel2.replaceAll("\\", "/") : rel2;
|
|
5895
4295
|
}
|
|
5896
4296
|
async function searchSymbols(graph, root, query, limit) {
|
|
5897
|
-
let
|
|
5898
|
-
if (!
|
|
5899
|
-
const quoted = /^(["'`])(.+)\1$/.exec(
|
|
5900
|
-
if (quoted && quoted[2].trim())
|
|
5901
|
-
const isPhrase = Boolean(quoted) || /\s/.test(
|
|
5902
|
-
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");
|
|
5903
4303
|
if (nodes.length === 0) {
|
|
5904
|
-
nodes = fileSymbolNodes(graph,
|
|
4304
|
+
nodes = fileSymbolNodes(graph, q);
|
|
5905
4305
|
}
|
|
5906
4306
|
if (nodes.length === 0 && isPhrase) {
|
|
5907
|
-
nodes = reconstructedIdentifierNodes(graph,
|
|
4307
|
+
nodes = reconstructedIdentifierNodes(graph, q);
|
|
5908
4308
|
}
|
|
5909
4309
|
if (nodes.length === 0) {
|
|
5910
|
-
const tokens = queryTokens(
|
|
4310
|
+
const tokens = queryTokens(q);
|
|
5911
4311
|
if (tokens.length >= 2) nodes = multiTokenNodes(graph, tokens);
|
|
5912
4312
|
}
|
|
5913
4313
|
const symbolCap = isPhrase ? Math.max(1, Math.floor(limit / 3)) : limit;
|
|
@@ -5922,10 +4322,10 @@ async function searchSymbols(graph, root, query, limit) {
|
|
|
5922
4322
|
const textHits = [];
|
|
5923
4323
|
let truncatedScan = false;
|
|
5924
4324
|
let totalTextMatches;
|
|
5925
|
-
const skipScan = !isPhrase && hasExactSymbolMatch(nodes,
|
|
4325
|
+
const skipScan = !isPhrase && hasExactSymbolMatch(nodes, q);
|
|
5926
4326
|
if (spare > 0 && !skipScan) {
|
|
5927
4327
|
const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
|
|
5928
|
-
const scan = await scanFiles(root,
|
|
4328
|
+
const scan = await scanFiles(root, q, spare, seen, textHits, isPhrase);
|
|
5929
4329
|
truncatedScan = scan.truncated;
|
|
5930
4330
|
if (isPhrase) totalTextMatches = scan.total;
|
|
5931
4331
|
}
|
|
@@ -5946,25 +4346,25 @@ async function searchSymbols(graph, root, query, limit) {
|
|
|
5946
4346
|
}
|
|
5947
4347
|
return result;
|
|
5948
4348
|
}
|
|
5949
|
-
function fileSymbolNodes(graph,
|
|
5950
|
-
if (!/[/\\]/.test(
|
|
5951
|
-
const norm =
|
|
4349
|
+
function fileSymbolNodes(graph, q) {
|
|
4350
|
+
if (!/[/\\]/.test(q) && !/\.\w+$/.test(q)) return [];
|
|
4351
|
+
const norm = q.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
5952
4352
|
const inFile = graph.nodes.filter(
|
|
5953
4353
|
(n) => n.kind !== "file" && (n.file === norm || n.file.endsWith(`/${norm}`))
|
|
5954
4354
|
);
|
|
5955
4355
|
return [...inFile].sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName));
|
|
5956
4356
|
}
|
|
5957
|
-
function hasExactSymbolMatch(nodes,
|
|
4357
|
+
function hasExactSymbolMatch(nodes, q) {
|
|
5958
4358
|
if (nodes.length === 0) return false;
|
|
5959
|
-
if (/^(.*):(\d+)$/.test(
|
|
5960
|
-
const lower =
|
|
5961
|
-
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);
|
|
5962
4362
|
}
|
|
5963
|
-
function queryTokens(
|
|
5964
|
-
return [...new Set(
|
|
4363
|
+
function queryTokens(q) {
|
|
4364
|
+
return [...new Set(q.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2))];
|
|
5965
4365
|
}
|
|
5966
|
-
function reconstructedIdentifierNodes(graph,
|
|
5967
|
-
const words =
|
|
4366
|
+
function reconstructedIdentifierNodes(graph, q) {
|
|
4367
|
+
const words = q.trim().split(/\s+/).filter(Boolean);
|
|
5968
4368
|
if (words.length < 2) return [];
|
|
5969
4369
|
const concat = findNodes(graph, words.join(""));
|
|
5970
4370
|
if (concat.length) return concat.filter((n) => n.kind !== "file");
|
|
@@ -6030,13 +4430,13 @@ function walkCandidates(root) {
|
|
|
6030
4430
|
const dir = stack.pop();
|
|
6031
4431
|
let entries;
|
|
6032
4432
|
try {
|
|
6033
|
-
entries =
|
|
4433
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
6034
4434
|
} catch {
|
|
6035
4435
|
continue;
|
|
6036
4436
|
}
|
|
6037
4437
|
for (const e of entries) {
|
|
6038
4438
|
if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
|
|
6039
|
-
const abs =
|
|
4439
|
+
const abs = path2.join(dir, e.name);
|
|
6040
4440
|
if (e.isDirectory()) {
|
|
6041
4441
|
stack.push(abs);
|
|
6042
4442
|
continue;
|
|
@@ -6046,7 +4446,7 @@ function walkCandidates(root) {
|
|
|
6046
4446
|
truncated = true;
|
|
6047
4447
|
continue;
|
|
6048
4448
|
}
|
|
6049
|
-
files.push(toPosix2(
|
|
4449
|
+
files.push(toPosix2(path2.relative(root, abs)));
|
|
6050
4450
|
}
|
|
6051
4451
|
}
|
|
6052
4452
|
files.sort();
|
|
@@ -6077,11 +4477,11 @@ function ripgrepCandidates(root, needle) {
|
|
|
6077
4477
|
if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
|
|
6078
4478
|
return { files, truncated: false };
|
|
6079
4479
|
}
|
|
6080
|
-
function parseRgFileList(stdout,
|
|
4480
|
+
function parseRgFileList(stdout, sep9 = path2.sep) {
|
|
6081
4481
|
const seen = /* @__PURE__ */ new Set();
|
|
6082
4482
|
for (const raw of stdout.split("\n")) {
|
|
6083
4483
|
if (!raw) continue;
|
|
6084
|
-
const posix3 =
|
|
4484
|
+
const posix3 = sep9 === "\\" ? raw.replaceAll("\\", "/") : raw;
|
|
6085
4485
|
const rel2 = posix3.startsWith("./") ? posix3.slice(2) : posix3;
|
|
6086
4486
|
if (!rel2) continue;
|
|
6087
4487
|
const segs = rel2.split("/");
|
|
@@ -6114,7 +4514,7 @@ function readyEmbedder(local) {
|
|
|
6114
4514
|
async function retrieve(graph, question, budget, ctx) {
|
|
6115
4515
|
let mode = "lexical";
|
|
6116
4516
|
try {
|
|
6117
|
-
const
|
|
4517
|
+
const q = await withTimeout(
|
|
6118
4518
|
(async () => {
|
|
6119
4519
|
const embedder = await readyEmbedder(ctx.local);
|
|
6120
4520
|
if (!embedder) return null;
|
|
@@ -6126,7 +4526,7 @@ async function retrieve(graph, question, budget, ctx) {
|
|
|
6126
4526
|
SEMANTIC_BUDGET_MS,
|
|
6127
4527
|
"semantic path over budget"
|
|
6128
4528
|
);
|
|
6129
|
-
if (
|
|
4529
|
+
if (q) return { q, mode };
|
|
6130
4530
|
} catch {
|
|
6131
4531
|
}
|
|
6132
4532
|
return { q: queryGraph(graph, question, { budget }), mode: "lexical" };
|
|
@@ -6175,10 +4575,10 @@ var TOOLS = [
|
|
|
6175
4575
|
const question = String(args.question ?? args.query ?? "");
|
|
6176
4576
|
if (!question) return { error: "bad_request", message: "question is required" };
|
|
6177
4577
|
const budget = numOr(args.budget, 1500);
|
|
6178
|
-
const { q
|
|
4578
|
+
const { q, mode } = await retrieve(graph, question, budget, ctx);
|
|
6179
4579
|
const rawScope = String(args.scope ?? "").trim();
|
|
6180
4580
|
const scope = rawScope === "." || rawScope === "./" ? "" : rawScope.replace(/^\.\//, "");
|
|
6181
|
-
const scoped = scope ?
|
|
4581
|
+
const scoped = scope ? q.matches.filter((m) => m.node.file.startsWith(scope)) : q.matches;
|
|
6182
4582
|
const detailed = isDetailed(args);
|
|
6183
4583
|
let topImpact = null;
|
|
6184
4584
|
const top = scoped[0]?.node;
|
|
@@ -6196,7 +4596,7 @@ var TOOLS = [
|
|
|
6196
4596
|
mode,
|
|
6197
4597
|
// The fact-annotated context block is detail: concise callers navigate
|
|
6198
4598
|
// by the ranked matches and fetch nodes on demand (plan P2).
|
|
6199
|
-
...detailed ? { context:
|
|
4599
|
+
...detailed ? { context: q.context } : {},
|
|
6200
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 })),
|
|
6201
4601
|
// A neutral availability flag, not a directive to keep querying: a
|
|
6202
4602
|
// retrieval tool that coaches "narrow the question / see more" nudges the
|
|
@@ -6975,12 +5375,12 @@ function detectAssistants(root, opts = {}) {
|
|
|
6975
5375
|
const which = opts.which ?? whichOnPath;
|
|
6976
5376
|
const found = [];
|
|
6977
5377
|
for (const assistant of ASSISTANTS) {
|
|
6978
|
-
const repoHit = (assistant.markers ?? []).find((m) =>
|
|
5378
|
+
const repoHit = (assistant.markers ?? []).find((m) => fs18.existsSync(path2.join(root, m)));
|
|
6979
5379
|
if (repoHit) {
|
|
6980
5380
|
found.push({ assistant, via: "repo", marker: repoHit });
|
|
6981
5381
|
continue;
|
|
6982
5382
|
}
|
|
6983
|
-
const homeHit = (assistant.homeMarkers ?? []).find((m) =>
|
|
5383
|
+
const homeHit = (assistant.homeMarkers ?? []).find((m) => fs18.existsSync(path2.join(home, m)));
|
|
6984
5384
|
if (homeHit) {
|
|
6985
5385
|
found.push({ assistant, via: "home", marker: homeHit });
|
|
6986
5386
|
continue;
|
|
@@ -7012,19 +5412,19 @@ function installAssistant(a, opts) {
|
|
|
7012
5412
|
const skipped = [];
|
|
7013
5413
|
let note;
|
|
7014
5414
|
if (a.skill) {
|
|
7015
|
-
writeFileEnsured(
|
|
5415
|
+
writeFileEnsured(path2.join(opts.root, a.skill), skillMarkdown(a.id));
|
|
7016
5416
|
wrote.push(a.skill);
|
|
7017
5417
|
}
|
|
7018
5418
|
if (a.mcp) {
|
|
7019
5419
|
const launch = opts.launch ?? detectServeLaunch();
|
|
7020
|
-
upsertMcp(
|
|
5420
|
+
upsertMcp(path2.join(opts.root, a.mcp.file), a.mcp, launch);
|
|
7021
5421
|
wrote.push(a.mcp.file);
|
|
7022
5422
|
note = launch.note;
|
|
7023
5423
|
} else {
|
|
7024
5424
|
skipped.push("mcp (host-specific setup)");
|
|
7025
5425
|
}
|
|
7026
5426
|
if (a.nudge && opts.hook !== false) {
|
|
7027
|
-
writeNudge(
|
|
5427
|
+
writeNudge(path2.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
|
|
7028
5428
|
wrote.push(a.nudge.file);
|
|
7029
5429
|
} else if (a.nudge) {
|
|
7030
5430
|
skipped.push("nudge (--no-hook)");
|
|
@@ -7034,41 +5434,41 @@ function installAssistant(a, opts) {
|
|
|
7034
5434
|
function uninstallAssistant(a, root, purge) {
|
|
7035
5435
|
const removed = [];
|
|
7036
5436
|
if (a.mcp) {
|
|
7037
|
-
const file =
|
|
5437
|
+
const file = path2.join(root, a.mcp.file);
|
|
7038
5438
|
if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
|
|
7039
5439
|
}
|
|
7040
5440
|
if (a.nudge) {
|
|
7041
|
-
const file =
|
|
5441
|
+
const file = path2.join(root, a.nudge.file);
|
|
7042
5442
|
if (a.nudge.kind === "block") {
|
|
7043
5443
|
if (removeBlock(file)) removed.push(a.nudge.file);
|
|
7044
|
-
} else if (
|
|
7045
|
-
|
|
5444
|
+
} else if (fs18.existsSync(file)) {
|
|
5445
|
+
fs18.rmSync(file);
|
|
7046
5446
|
removed.push(a.nudge.file);
|
|
7047
5447
|
}
|
|
7048
5448
|
}
|
|
7049
5449
|
if (purge && a.skill) {
|
|
7050
|
-
const file =
|
|
7051
|
-
if (
|
|
7052
|
-
|
|
5450
|
+
const file = path2.join(root, a.skill);
|
|
5451
|
+
if (fs18.existsSync(file)) {
|
|
5452
|
+
fs18.rmSync(file);
|
|
7053
5453
|
removed.push(a.skill);
|
|
7054
5454
|
}
|
|
7055
5455
|
}
|
|
7056
5456
|
return removed;
|
|
7057
5457
|
}
|
|
7058
5458
|
function writeFileEnsured(file, content) {
|
|
7059
|
-
|
|
7060
|
-
|
|
5459
|
+
fs18.mkdirSync(path2.dirname(file), { recursive: true });
|
|
5460
|
+
fs18.writeFileSync(file, content);
|
|
7061
5461
|
}
|
|
7062
5462
|
function writeNavigationConfig(root) {
|
|
7063
5463
|
ensureVibgrateGitignore(root);
|
|
7064
|
-
const rel2 =
|
|
5464
|
+
const rel2 = path2.join(".vibgrate", "mcp-navigation.json");
|
|
7065
5465
|
const doc = {
|
|
7066
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.",
|
|
7067
5467
|
hot_core: [...HOT_TOOLS],
|
|
7068
5468
|
deferred: deferredToolNames(),
|
|
7069
5469
|
toolset: navigationToolsetConfig()
|
|
7070
5470
|
};
|
|
7071
|
-
writeFileEnsured(
|
|
5471
|
+
writeFileEnsured(path2.join(root, rel2), `${JSON.stringify(doc, null, 2)}
|
|
7072
5472
|
`);
|
|
7073
5473
|
return rel2;
|
|
7074
5474
|
}
|
|
@@ -7083,7 +5483,7 @@ function upsertMcp(file, target, launch) {
|
|
|
7083
5483
|
`);
|
|
7084
5484
|
}
|
|
7085
5485
|
function removeMcp(file, target) {
|
|
7086
|
-
if (!
|
|
5486
|
+
if (!fs18.existsSync(file)) return false;
|
|
7087
5487
|
const config = readJson(file);
|
|
7088
5488
|
const bag = config[target.key];
|
|
7089
5489
|
if (!bag || !(bag.vg !== void 0)) return false;
|
|
@@ -7102,8 +5502,8 @@ function refreshInstalledInstructions(root, smallRepo) {
|
|
|
7102
5502
|
};
|
|
7103
5503
|
for (const a of ASSISTANTS) {
|
|
7104
5504
|
if (a.skill) {
|
|
7105
|
-
const file =
|
|
7106
|
-
if (
|
|
5505
|
+
const file = path2.join(root, a.skill);
|
|
5506
|
+
if (fs18.existsSync(file)) {
|
|
7107
5507
|
const text = readTextSafe(file);
|
|
7108
5508
|
const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
|
|
7109
5509
|
if (from !== null) {
|
|
@@ -7113,8 +5513,8 @@ function refreshInstalledInstructions(root, smallRepo) {
|
|
|
7113
5513
|
}
|
|
7114
5514
|
}
|
|
7115
5515
|
if (a.nudge) {
|
|
7116
|
-
const file =
|
|
7117
|
-
if (!
|
|
5516
|
+
const file = path2.join(root, a.nudge.file);
|
|
5517
|
+
if (!fs18.existsSync(file)) continue;
|
|
7118
5518
|
const text = readTextSafe(file);
|
|
7119
5519
|
if (text === null) continue;
|
|
7120
5520
|
if (a.nudge.kind === "block") {
|
|
@@ -7138,7 +5538,7 @@ function refreshInstalledInstructions(root, smallRepo) {
|
|
|
7138
5538
|
}
|
|
7139
5539
|
function readTextSafe(file) {
|
|
7140
5540
|
try {
|
|
7141
|
-
return
|
|
5541
|
+
return fs18.readFileSync(file, "utf8");
|
|
7142
5542
|
} catch {
|
|
7143
5543
|
return null;
|
|
7144
5544
|
}
|
|
@@ -7157,8 +5557,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
|
|
|
7157
5557
|
upsertBlock(file, nudgeMarkdown(smallRepo, client));
|
|
7158
5558
|
}
|
|
7159
5559
|
function upsertBlock(file, block) {
|
|
7160
|
-
|
|
7161
|
-
let existing =
|
|
5560
|
+
fs18.mkdirSync(path2.dirname(file), { recursive: true });
|
|
5561
|
+
let existing = fs18.existsSync(file) ? fs18.readFileSync(file, "utf8") : "";
|
|
7162
5562
|
const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
|
|
7163
5563
|
if (re.test(existing)) existing = existing.replace(re, block);
|
|
7164
5564
|
else existing = existing.length ? `${existing.replace(/\s*$/, "")}
|
|
@@ -7166,20 +5566,20 @@ function upsertBlock(file, block) {
|
|
|
7166
5566
|
${block}
|
|
7167
5567
|
` : `${block}
|
|
7168
5568
|
`;
|
|
7169
|
-
|
|
5569
|
+
fs18.writeFileSync(file, existing);
|
|
7170
5570
|
}
|
|
7171
5571
|
function removeBlock(file) {
|
|
7172
|
-
if (!
|
|
7173
|
-
const existing =
|
|
5572
|
+
if (!fs18.existsSync(file)) return false;
|
|
5573
|
+
const existing = fs18.readFileSync(file, "utf8");
|
|
7174
5574
|
const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
|
|
7175
5575
|
if (!re.test(existing)) return false;
|
|
7176
|
-
|
|
5576
|
+
fs18.writeFileSync(file, existing.replace(re, "\n"));
|
|
7177
5577
|
return true;
|
|
7178
5578
|
}
|
|
7179
5579
|
function readJson(file) {
|
|
7180
|
-
if (!
|
|
5580
|
+
if (!fs18.existsSync(file)) return {};
|
|
7181
5581
|
try {
|
|
7182
|
-
const parsed = JSON.parse(
|
|
5582
|
+
const parsed = JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
7183
5583
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
7184
5584
|
return parsed;
|
|
7185
5585
|
}
|
|
@@ -7199,7 +5599,7 @@ function escapeRe(s) {
|
|
|
7199
5599
|
}
|
|
7200
5600
|
var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
7201
5601
|
function refreshLockPath(root) {
|
|
7202
|
-
return
|
|
5602
|
+
return path2.join(cacheDir(root), "refresh.lock");
|
|
7203
5603
|
}
|
|
7204
5604
|
async function refreshIfStale(root, opts = {}) {
|
|
7205
5605
|
const probe = probeFreshness(root);
|
|
@@ -7230,8 +5630,8 @@ async function refreshIfStale(root, opts = {}) {
|
|
|
7230
5630
|
const dir = vibgrateDir(root);
|
|
7231
5631
|
writeArtifacts(result.graph, {
|
|
7232
5632
|
root,
|
|
7233
|
-
report:
|
|
7234
|
-
html:
|
|
5633
|
+
report: fs18.existsSync(path2.join(dir, "GRAPH_REPORT.md")),
|
|
5634
|
+
html: fs18.existsSync(path2.join(dir, "graph.html"))
|
|
7235
5635
|
});
|
|
7236
5636
|
}
|
|
7237
5637
|
writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
|
|
@@ -7250,583 +5650,7 @@ async function refreshIfStale(root, opts = {}) {
|
|
|
7250
5650
|
releaseLock(lock);
|
|
7251
5651
|
}
|
|
7252
5652
|
}
|
|
7253
|
-
var LEDGER = "savings.jsonl";
|
|
7254
|
-
var PER_FILE_TOKENS = 400;
|
|
7255
|
-
var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node", "search_symbols"]);
|
|
7256
|
-
var CLI_TOOL_ALIASES = {
|
|
7257
|
-
ask: "query_graph",
|
|
7258
|
-
show: "get_node",
|
|
7259
|
-
impact: "impact_of",
|
|
7260
|
-
path: "find_path",
|
|
7261
|
-
hubs: "list_hubs",
|
|
7262
|
-
areas: "list_areas",
|
|
7263
|
-
tree: "tree"
|
|
7264
|
-
};
|
|
7265
|
-
function sanitizeClient(name) {
|
|
7266
|
-
if (typeof name !== "string") return "unknown";
|
|
7267
|
-
const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
7268
|
-
return cleaned || "unknown";
|
|
7269
|
-
}
|
|
7270
|
-
function recordCliCall(root, entry, now) {
|
|
7271
|
-
recordSaving(
|
|
7272
|
-
root,
|
|
7273
|
-
{
|
|
7274
|
-
tool: entry.tool,
|
|
7275
|
-
source: "cli",
|
|
7276
|
-
client: sanitizeClient(entry.client),
|
|
7277
|
-
outcome: entry.outcome,
|
|
7278
|
-
vgTokens: entry.vgTokens ?? 0,
|
|
7279
|
-
baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
|
|
7280
|
-
},
|
|
7281
|
-
now
|
|
7282
|
-
);
|
|
7283
|
-
}
|
|
7284
|
-
function savingsLedgerPath(root) {
|
|
7285
|
-
return path17.join(cacheDir(root), LEDGER);
|
|
7286
|
-
}
|
|
7287
|
-
function ledgerPath(root) {
|
|
7288
|
-
return savingsLedgerPath(root);
|
|
7289
|
-
}
|
|
7290
|
-
function savingsRecorded(root) {
|
|
7291
|
-
return fs17.existsSync(ledgerPath(root));
|
|
7292
|
-
}
|
|
7293
|
-
function clearSavings(root) {
|
|
7294
|
-
const file = ledgerPath(root);
|
|
7295
|
-
const existed = fs17.existsSync(file);
|
|
7296
|
-
fs17.rmSync(file, { force: true });
|
|
7297
|
-
fs17.rmSync(path17.join(cacheDir(root), "stats-share.json"), { force: true });
|
|
7298
|
-
return existed;
|
|
7299
|
-
}
|
|
7300
|
-
function recordSaving(root, entry, now) {
|
|
7301
|
-
try {
|
|
7302
|
-
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
7303
|
-
const line = JSON.stringify({ ts: now, ...entry });
|
|
7304
|
-
fs17.appendFileSync(ledgerPath(root), line + "\n");
|
|
7305
|
-
} catch {
|
|
7306
|
-
}
|
|
7307
|
-
}
|
|
7308
|
-
var DEFAULT_RATE_PER_M = 3;
|
|
7309
|
-
var DEFAULT_RATE_LABEL = "input @ $3/1M";
|
|
7310
|
-
function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
|
|
7311
|
-
const file = ledgerPath(root);
|
|
7312
|
-
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
7313
|
-
let queries = 0;
|
|
7314
|
-
let vgTokens = 0;
|
|
7315
|
-
let baselineTokens = 0;
|
|
7316
|
-
if (fs17.existsSync(file)) {
|
|
7317
|
-
for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
|
|
7318
|
-
if (!line.trim()) continue;
|
|
7319
|
-
try {
|
|
7320
|
-
const e = JSON.parse(line);
|
|
7321
|
-
if (e.ts < cutoff) continue;
|
|
7322
|
-
if (!SAVINGS_TOOLS.has(e.tool)) continue;
|
|
7323
|
-
queries++;
|
|
7324
|
-
vgTokens += e.vgTokens;
|
|
7325
|
-
baselineTokens += e.baselineTokens;
|
|
7326
|
-
} catch {
|
|
7327
|
-
}
|
|
7328
|
-
}
|
|
7329
|
-
}
|
|
7330
|
-
const estCostVg = vgTokens / 1e6 * ratePerM;
|
|
7331
|
-
const estCostBaseline = baselineTokens / 1e6 * ratePerM;
|
|
7332
|
-
return {
|
|
7333
|
-
enabled: savingsRecorded(root),
|
|
7334
|
-
days,
|
|
7335
|
-
queries,
|
|
7336
|
-
vgTokens,
|
|
7337
|
-
baselineTokens,
|
|
7338
|
-
ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
|
|
7339
|
-
estCostVg: round22(estCostVg),
|
|
7340
|
-
estCostBaseline: round22(estCostBaseline),
|
|
7341
|
-
saved: round22(estCostBaseline - estCostVg),
|
|
7342
|
-
rateLabel: DEFAULT_RATE_LABEL
|
|
7343
|
-
};
|
|
7344
|
-
}
|
|
7345
|
-
function round22(x) {
|
|
7346
|
-
return Math.round(x * 100) / 100;
|
|
7347
|
-
}
|
|
7348
|
-
function toDimensionStats(byKey) {
|
|
7349
|
-
return [...byKey.entries()].map(([key, r]) => {
|
|
7350
|
-
const calls = r.complete + r.partial + r.miss;
|
|
7351
|
-
return {
|
|
7352
|
-
key,
|
|
7353
|
-
calls,
|
|
7354
|
-
complete: r.complete,
|
|
7355
|
-
partial: r.partial,
|
|
7356
|
-
miss: r.miss,
|
|
7357
|
-
successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
|
|
7358
|
-
};
|
|
7359
|
-
}).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
|
|
7360
|
-
}
|
|
7361
|
-
function readUsage(root, days, now) {
|
|
7362
|
-
const file = ledgerPath(root);
|
|
7363
|
-
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
7364
|
-
const byTool = /* @__PURE__ */ new Map();
|
|
7365
|
-
const bySource = /* @__PURE__ */ new Map();
|
|
7366
|
-
const byClient = /* @__PURE__ */ new Map();
|
|
7367
|
-
const msByTool = /* @__PURE__ */ new Map();
|
|
7368
|
-
const bump2 = (m, key, outcome) => {
|
|
7369
|
-
const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
|
|
7370
|
-
row[outcome]++;
|
|
7371
|
-
m.set(key, row);
|
|
7372
|
-
};
|
|
7373
|
-
if (fs17.existsSync(file)) {
|
|
7374
|
-
for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
|
|
7375
|
-
if (!line.trim()) continue;
|
|
7376
|
-
try {
|
|
7377
|
-
const e = JSON.parse(line);
|
|
7378
|
-
if (e.ts < cutoff) continue;
|
|
7379
|
-
const outcome = e.outcome ?? "complete";
|
|
7380
|
-
bump2(byTool, e.tool, outcome);
|
|
7381
|
-
bump2(bySource, e.source ?? "mcp", outcome);
|
|
7382
|
-
bump2(byClient, e.client ?? "unknown", outcome);
|
|
7383
|
-
if (typeof e.ms === "number" && e.ms >= 0) {
|
|
7384
|
-
const timing = msByTool.get(e.tool) ?? { sum: 0, n: 0 };
|
|
7385
|
-
timing.sum += e.ms;
|
|
7386
|
-
timing.n++;
|
|
7387
|
-
msByTool.set(e.tool, timing);
|
|
7388
|
-
}
|
|
7389
|
-
} catch {
|
|
7390
|
-
}
|
|
7391
|
-
}
|
|
7392
|
-
}
|
|
7393
|
-
const commands = toDimensionStats(byTool).map((d) => {
|
|
7394
|
-
const timing = msByTool.get(d.key);
|
|
7395
|
-
return {
|
|
7396
|
-
tool: d.key,
|
|
7397
|
-
calls: d.calls,
|
|
7398
|
-
complete: d.complete,
|
|
7399
|
-
partial: d.partial,
|
|
7400
|
-
miss: d.miss,
|
|
7401
|
-
successPct: d.successPct,
|
|
7402
|
-
avgMs: timing && timing.n > 0 ? Math.round(timing.sum / timing.n) : null
|
|
7403
|
-
};
|
|
7404
|
-
});
|
|
7405
|
-
const totals = commands.reduce(
|
|
7406
|
-
(t, c) => ({
|
|
7407
|
-
calls: t.calls + c.calls,
|
|
7408
|
-
complete: t.complete + c.complete,
|
|
7409
|
-
partial: t.partial + c.partial,
|
|
7410
|
-
miss: t.miss + c.miss
|
|
7411
|
-
}),
|
|
7412
|
-
{ calls: 0, complete: 0, partial: 0, miss: 0 }
|
|
7413
|
-
);
|
|
7414
|
-
const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
|
|
7415
|
-
return {
|
|
7416
|
-
enabled: savingsRecorded(root),
|
|
7417
|
-
days,
|
|
7418
|
-
commands,
|
|
7419
|
-
sources: toDimensionStats(bySource),
|
|
7420
|
-
clients: toDimensionStats(byClient),
|
|
7421
|
-
totals,
|
|
7422
|
-
avgSuccessPct
|
|
7423
|
-
};
|
|
7424
|
-
}
|
|
7425
|
-
var PROBE_INTERVAL_MS = 2e3;
|
|
7426
|
-
var MAX_PROBE_INTERVAL_MS = 3e4;
|
|
7427
|
-
var PROBE_DUTY_FACTOR = 20;
|
|
7428
|
-
var REFRESH_BUDGET_MS = 5e3;
|
|
7429
|
-
var FAILURE_COOLDOWN_MS = 6e4;
|
|
7430
|
-
var GraphSource = class {
|
|
7431
|
-
constructor(graphPath, refresh = false, tuning = {}) {
|
|
7432
|
-
this.graphPath = graphPath;
|
|
7433
|
-
this.refresh = refresh;
|
|
7434
|
-
this.tuning = tuning;
|
|
7435
|
-
this.root = path17.dirname(path17.dirname(graphPath));
|
|
7436
|
-
}
|
|
7437
|
-
graphPath;
|
|
7438
|
-
refresh;
|
|
7439
|
-
tuning;
|
|
7440
|
-
cachedMtimeMs = -1;
|
|
7441
|
-
cached = null;
|
|
7442
|
-
root;
|
|
7443
|
-
lastProbeAt = 0;
|
|
7444
|
-
failedUntil = 0;
|
|
7445
|
-
inflight = null;
|
|
7446
|
-
/** Self-tuned: grows with measured probe cost so huge repos aren't penalized. */
|
|
7447
|
-
probeIntervalMs = PROBE_INTERVAL_MS;
|
|
7448
|
-
/** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
|
|
7449
|
-
async get() {
|
|
7450
|
-
if (this.refresh) await this.maybeRefresh();
|
|
7451
|
-
const stat = fs17.statSync(this.graphPath);
|
|
7452
|
-
if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
|
|
7453
|
-
this.cached = parseGraph(fs17.readFileSync(this.graphPath, "utf8"));
|
|
7454
|
-
this.cachedMtimeMs = stat.mtimeMs;
|
|
7455
|
-
}
|
|
7456
|
-
return this.cached;
|
|
7457
|
-
}
|
|
7458
|
-
/**
|
|
7459
|
-
* Debounced, single-flight refresh. Never throws — a refresh problem must
|
|
7460
|
-
* degrade to "answer from the current map", not break the tool call.
|
|
7461
|
-
*/
|
|
7462
|
-
async maybeRefresh() {
|
|
7463
|
-
const now = Date.now();
|
|
7464
|
-
if (!this.inflight) {
|
|
7465
|
-
const interval = this.tuning.probeIntervalMs ?? this.probeIntervalMs;
|
|
7466
|
-
if (now < this.failedUntil || now - this.lastProbeAt < interval) return;
|
|
7467
|
-
this.lastProbeAt = now;
|
|
7468
|
-
this.inflight = refreshIfStale(this.root).then((r) => {
|
|
7469
|
-
if (r.status === "error") this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
|
|
7470
|
-
if (r.status === "fresh" || r.status === "no-snapshot" || r.status === "locked") {
|
|
7471
|
-
const cost = Date.now() - now;
|
|
7472
|
-
this.probeIntervalMs = Math.min(
|
|
7473
|
-
MAX_PROBE_INTERVAL_MS,
|
|
7474
|
-
Math.max(PROBE_INTERVAL_MS, cost * PROBE_DUTY_FACTOR)
|
|
7475
|
-
);
|
|
7476
|
-
}
|
|
7477
|
-
}).catch(() => {
|
|
7478
|
-
this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
|
|
7479
|
-
}).finally(() => {
|
|
7480
|
-
this.inflight = null;
|
|
7481
|
-
});
|
|
7482
|
-
}
|
|
7483
|
-
await Promise.race([this.inflight, sleep(this.tuning.refreshBudgetMs ?? REFRESH_BUDGET_MS)]);
|
|
7484
|
-
}
|
|
7485
|
-
};
|
|
7486
|
-
function createServer(source, opts = {}) {
|
|
7487
|
-
const { savings = false, shareStats = false, local = false, dedup = false, stats } = opts;
|
|
7488
|
-
const record = savings || shareStats;
|
|
7489
|
-
const root = path17.dirname(path17.dirname(source.graphPath));
|
|
7490
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7491
|
-
const server = new Server(
|
|
7492
|
-
{ name: "vg", version: VERSION },
|
|
7493
|
-
{
|
|
7494
|
-
capabilities: { tools: {} },
|
|
7495
|
-
// Routing guidance once at the server level (hosts that surface
|
|
7496
|
-
// `instructions` get it at zero per-step schema cost): the flashlight
|
|
7497
|
-
// vs the map.
|
|
7498
|
-
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.'
|
|
7499
|
-
}
|
|
7500
|
-
);
|
|
7501
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
7502
|
-
tools: TOOLS.map((t) => ({
|
|
7503
|
-
name: t.name,
|
|
7504
|
-
description: t.description,
|
|
7505
|
-
inputSchema: t.inputSchema,
|
|
7506
|
-
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
7507
|
-
}))
|
|
7508
|
-
}));
|
|
7509
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
7510
|
-
const tool = TOOLS.find((t) => t.name === request.params.name);
|
|
7511
|
-
if (!tool) {
|
|
7512
|
-
return errorResult(`unknown tool "${request.params.name}"`);
|
|
7513
|
-
}
|
|
7514
|
-
let graph;
|
|
7515
|
-
try {
|
|
7516
|
-
graph = await source.get();
|
|
7517
|
-
} catch {
|
|
7518
|
-
return errorResult(
|
|
7519
|
-
"no code map found. Run `vg` in the project to build .vibgrate/graph.json, then retry."
|
|
7520
|
-
);
|
|
7521
|
-
}
|
|
7522
|
-
const startedAt = Date.now();
|
|
7523
|
-
try {
|
|
7524
|
-
const args = request.params.arguments ?? {};
|
|
7525
|
-
const result = await tool.handler(graph, args, { root, local, dedup, seen });
|
|
7526
|
-
const ms = Date.now() - startedAt;
|
|
7527
|
-
stats?.record({ ...measureCall(tool.name, result), client: sanitizeClient(detectClient(server)), ms });
|
|
7528
|
-
if (record) recordUsage(root, tool.name, result, detectClient(server), ms);
|
|
7529
|
-
return renderToolResult(result);
|
|
7530
|
-
} catch (err) {
|
|
7531
|
-
stats?.record({
|
|
7532
|
-
tool: tool.name,
|
|
7533
|
-
client: sanitizeClient(detectClient(server)),
|
|
7534
|
-
outcome: "miss",
|
|
7535
|
-
ms: Date.now() - startedAt,
|
|
7536
|
-
vgTokens: 0,
|
|
7537
|
-
baselineTokens: 0
|
|
7538
|
-
});
|
|
7539
|
-
return errorResult(`tool "${tool.name}" failed: ${err.message}`);
|
|
7540
|
-
}
|
|
7541
|
-
});
|
|
7542
|
-
return server;
|
|
7543
|
-
}
|
|
7544
|
-
async function serveStdio(graphPath, opts = {}) {
|
|
7545
|
-
const source = new GraphSource(graphPath, opts.refresh !== false);
|
|
7546
|
-
const server = createServer(source, opts);
|
|
7547
|
-
warmEmbedderInBackground(opts.local);
|
|
7548
|
-
await server.connect(new StdioServerTransport());
|
|
7549
|
-
}
|
|
7550
|
-
function sleep(ms) {
|
|
7551
|
-
return new Promise((resolve12) => {
|
|
7552
|
-
const timer = setTimeout(resolve12, ms);
|
|
7553
|
-
timer.unref?.();
|
|
7554
|
-
});
|
|
7555
|
-
}
|
|
7556
|
-
function recordUsage(root, tool, result, client, ms) {
|
|
7557
|
-
recordSaving(
|
|
7558
|
-
root,
|
|
7559
|
-
{ ...measureCall(tool, result), source: "mcp", client: sanitizeClient(client), ...ms !== void 0 ? { ms } : {} },
|
|
7560
|
-
Date.now()
|
|
7561
|
-
);
|
|
7562
|
-
}
|
|
7563
|
-
function measureCall(tool, result) {
|
|
7564
|
-
const outcome = classifyOutcome(result);
|
|
7565
|
-
let vgTokens = 0;
|
|
7566
|
-
let baselineTokens = 0;
|
|
7567
|
-
if (SAVINGS_TOOLS.has(tool) && result && typeof result === "object") {
|
|
7568
|
-
vgTokens = countTokens(renderedText(renderToolResult(result)));
|
|
7569
|
-
baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
|
|
7570
|
-
}
|
|
7571
|
-
return { tool, outcome, vgTokens, baselineTokens };
|
|
7572
|
-
}
|
|
7573
|
-
function detectClient(server) {
|
|
7574
|
-
try {
|
|
7575
|
-
const info = server.getClientVersion?.();
|
|
7576
|
-
return typeof info?.name === "string" ? info.name : void 0;
|
|
7577
|
-
} catch {
|
|
7578
|
-
return void 0;
|
|
7579
|
-
}
|
|
7580
|
-
}
|
|
7581
|
-
function classifyOutcome(result) {
|
|
7582
|
-
if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
|
|
7583
|
-
if (!result || typeof result !== "object") return "miss";
|
|
7584
|
-
const r = result;
|
|
7585
|
-
if (typeof r.error === "string" && r.error) return "miss";
|
|
7586
|
-
if (r.connected === false) return "miss";
|
|
7587
|
-
if (Array.isArray(r.matches) && r.matches.length === 0) return "miss";
|
|
7588
|
-
return isPartial(r) ? "partial" : "complete";
|
|
7589
|
-
}
|
|
7590
|
-
function isPartial(r) {
|
|
7591
|
-
if (r.moreAvailable === true) return true;
|
|
7592
|
-
if (r._truncated && typeof r._truncated === "object") return true;
|
|
7593
|
-
for (const [key, value] of Object.entries(r)) {
|
|
7594
|
-
if (typeof value !== "number" || !key.endsWith("Total")) continue;
|
|
7595
|
-
const base = key.slice(0, -"Total".length);
|
|
7596
|
-
const shown = Array.isArray(r[base]) ? r[base].length : 0;
|
|
7597
|
-
if (value > shown) return true;
|
|
7598
|
-
}
|
|
7599
|
-
return false;
|
|
7600
|
-
}
|
|
7601
|
-
function renderedText(rendered) {
|
|
7602
|
-
const block = rendered.content?.find((b) => b.type === "text");
|
|
7603
|
-
return block && "text" in block && typeof block.text === "string" ? block.text : "";
|
|
7604
|
-
}
|
|
7605
|
-
function referencedFiles(result) {
|
|
7606
|
-
const r = result;
|
|
7607
|
-
const files = /* @__PURE__ */ new Set();
|
|
7608
|
-
if (typeof r.file === "string" && r.file) files.add(r.file);
|
|
7609
|
-
for (const m of asArray(r.matches)) {
|
|
7610
|
-
const f = m.file;
|
|
7611
|
-
if (typeof f === "string" && f) files.add(f);
|
|
7612
|
-
}
|
|
7613
|
-
for (const name of [...asArray(r.calls), ...asArray(r.calledBy)]) {
|
|
7614
|
-
if (typeof name !== "string") continue;
|
|
7615
|
-
const i = name.indexOf(":");
|
|
7616
|
-
if (i > 0) files.add(name.slice(0, i));
|
|
7617
|
-
}
|
|
7618
|
-
return files;
|
|
7619
|
-
}
|
|
7620
|
-
function asArray(v) {
|
|
7621
|
-
return Array.isArray(v) ? v : [];
|
|
7622
|
-
}
|
|
7623
|
-
function errorResult(message) {
|
|
7624
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
7625
|
-
}
|
|
7626
|
-
|
|
7627
|
-
// src/engine/export.ts
|
|
7628
|
-
function formatForExt(ext) {
|
|
7629
|
-
switch (ext.toLowerCase()) {
|
|
7630
|
-
case ".json":
|
|
7631
|
-
return "json";
|
|
7632
|
-
case ".ndjson":
|
|
7633
|
-
return "ndjson";
|
|
7634
|
-
case ".graphml":
|
|
7635
|
-
return "graphml";
|
|
7636
|
-
case ".dot":
|
|
7637
|
-
case ".gv":
|
|
7638
|
-
return "dot";
|
|
7639
|
-
case ".cypher":
|
|
7640
|
-
return "cypher";
|
|
7641
|
-
case ".sql":
|
|
7642
|
-
return "sql";
|
|
7643
|
-
case ".md":
|
|
7644
|
-
return "md";
|
|
7645
|
-
case ".html":
|
|
7646
|
-
return "html";
|
|
7647
|
-
default:
|
|
7648
|
-
return null;
|
|
7649
|
-
}
|
|
7650
|
-
}
|
|
7651
|
-
function exportGraph(format, ctx) {
|
|
7652
|
-
switch (format) {
|
|
7653
|
-
case "json":
|
|
7654
|
-
return serializeGraph(ctx.graph);
|
|
7655
|
-
case "md":
|
|
7656
|
-
return renderReport(ctx.graph);
|
|
7657
|
-
case "html":
|
|
7658
|
-
return renderHtml(ctx.graph);
|
|
7659
|
-
case "ndjson":
|
|
7660
|
-
return ndjson(ctx.graph);
|
|
7661
|
-
case "graphml":
|
|
7662
|
-
return graphml(ctx.graph);
|
|
7663
|
-
case "dot":
|
|
7664
|
-
return dot(ctx.graph);
|
|
7665
|
-
case "cypher":
|
|
7666
|
-
return cypher(ctx.graph);
|
|
7667
|
-
case "sql":
|
|
7668
|
-
return sql(ctx);
|
|
7669
|
-
case "cyclonedx":
|
|
7670
|
-
return cyclonedx(ctx);
|
|
7671
|
-
case "spdx":
|
|
7672
|
-
return spdx(ctx);
|
|
7673
|
-
}
|
|
7674
|
-
}
|
|
7675
|
-
function ndjson(graph) {
|
|
7676
|
-
const rows = graph.facts && graph.facts.length ? graph.facts : graph.nodes;
|
|
7677
|
-
return rows.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
7678
|
-
}
|
|
7679
|
-
function esc2(s) {
|
|
7680
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7681
|
-
}
|
|
7682
|
-
function graphml(graph) {
|
|
7683
|
-
const lines = [];
|
|
7684
|
-
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
7685
|
-
lines.push('<graphml xmlns="http://graphml.graphdrawing.org/xmlns">');
|
|
7686
|
-
lines.push(' <key id="name" for="node" attr.name="name" attr.type="string"/>');
|
|
7687
|
-
lines.push(' <key id="kind" for="node" attr.name="kind" attr.type="string"/>');
|
|
7688
|
-
lines.push(' <key id="file" for="node" attr.name="file" attr.type="string"/>');
|
|
7689
|
-
lines.push(' <key id="ekind" for="edge" attr.name="kind" attr.type="string"/>');
|
|
7690
|
-
lines.push(' <graph edgedefault="directed">');
|
|
7691
|
-
for (const n of graph.nodes) {
|
|
7692
|
-
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>`);
|
|
7693
|
-
}
|
|
7694
|
-
for (const e of graph.edges) {
|
|
7695
|
-
lines.push(` <edge source="${esc2(e.src)}" target="${esc2(e.dst)}"><data key="ekind">${esc2(e.kind)}</data></edge>`);
|
|
7696
|
-
}
|
|
7697
|
-
lines.push(" </graph>");
|
|
7698
|
-
lines.push("</graphml>");
|
|
7699
|
-
return lines.join("\n") + "\n";
|
|
7700
|
-
}
|
|
7701
|
-
function dot(graph) {
|
|
7702
|
-
const lines = ["digraph vg {"];
|
|
7703
|
-
for (const n of graph.nodes) lines.push(` "${n.id}" [label="${dotEsc(n.qualifiedName)}"];`);
|
|
7704
|
-
for (const e of graph.edges) lines.push(` "${e.src}" -> "${e.dst}" [label="${e.kind}"];`);
|
|
7705
|
-
lines.push("}");
|
|
7706
|
-
return lines.join("\n") + "\n";
|
|
7707
|
-
}
|
|
7708
|
-
function dotEsc(s) {
|
|
7709
|
-
return s.replace(/"/g, '\\"');
|
|
7710
|
-
}
|
|
7711
|
-
function cypher(graph) {
|
|
7712
|
-
const lines = [];
|
|
7713
|
-
for (const n of graph.nodes) {
|
|
7714
|
-
lines.push(
|
|
7715
|
-
`CREATE (:\`${cypherLabel(n.kind)}\` {id:${q(n.id)}, name:${q(n.qualifiedName)}, file:${q(n.file)}});`
|
|
7716
|
-
);
|
|
7717
|
-
}
|
|
7718
|
-
for (const e of graph.edges) {
|
|
7719
|
-
lines.push(
|
|
7720
|
-
`MATCH (a {id:${q(e.src)}}),(b {id:${q(e.dst)}}) CREATE (a)-[:\`${cypherLabel(e.kind)}\`]->(b);`
|
|
7721
|
-
);
|
|
7722
|
-
}
|
|
7723
|
-
return lines.join("\n") + "\n";
|
|
7724
|
-
}
|
|
7725
|
-
function cypherLabel(s) {
|
|
7726
|
-
return s.replace(/[^A-Za-z0-9_]/g, "_");
|
|
7727
|
-
}
|
|
7728
|
-
function q(s) {
|
|
7729
|
-
return JSON.stringify(s);
|
|
7730
|
-
}
|
|
7731
|
-
function sql(ctx) {
|
|
7732
|
-
const graph = ctx.graph;
|
|
7733
|
-
const lines = [];
|
|
7734
|
-
lines.push("BEGIN;");
|
|
7735
|
-
lines.push("CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);");
|
|
7736
|
-
const meta = [
|
|
7737
|
-
["schemaVersion", graph.schemaVersion],
|
|
7738
|
-
["generatedAt", ctx.generatedAt],
|
|
7739
|
-
["corpusHash", graph.provenance.corpusHash],
|
|
7740
|
-
["tool", graph.provenance.tool],
|
|
7741
|
-
["toolVersion", graph.provenance.version],
|
|
7742
|
-
["resolver", graph.provenance.resolver.join(",")],
|
|
7743
|
-
["fingerprint", graph.provenance.toolchain?.fingerprint ?? null]
|
|
7744
|
-
];
|
|
7745
|
-
for (const [k, v] of meta) lines.push(`INSERT INTO meta VALUES (${sqlStr(k)}, ${sqlStr(v)});`);
|
|
7746
|
-
lines.push(
|
|
7747
|
-
"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);"
|
|
7748
|
-
);
|
|
7749
|
-
for (const n of graph.nodes) {
|
|
7750
|
-
lines.push(
|
|
7751
|
-
`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)});`
|
|
7752
|
-
);
|
|
7753
|
-
}
|
|
7754
|
-
lines.push(
|
|
7755
|
-
'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);'
|
|
7756
|
-
);
|
|
7757
|
-
for (const e of graph.edges) {
|
|
7758
|
-
lines.push(
|
|
7759
|
-
`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)});`
|
|
7760
|
-
);
|
|
7761
|
-
}
|
|
7762
|
-
lines.push("CREATE TABLE IF NOT EXISTS areas (id INTEGER PRIMARY KEY, label TEXT, size INTEGER, cohesion REAL, external_edges INTEGER);");
|
|
7763
|
-
lines.push("CREATE TABLE IF NOT EXISTS area_members (area_id INTEGER, node_id TEXT);");
|
|
7764
|
-
for (const a of graph.areas) {
|
|
7765
|
-
lines.push(`INSERT INTO areas VALUES (${sqlNum(a.id)}, ${sqlStr(a.label)}, ${sqlNum(a.size)}, ${sqlNum(a.cohesion)}, ${sqlNum(a.externalEdges)});`);
|
|
7766
|
-
for (const m of a.members) lines.push(`INSERT INTO area_members VALUES (${sqlNum(a.id)}, ${sqlStr(m)});`);
|
|
7767
|
-
}
|
|
7768
|
-
if (graph.facts && graph.facts.length) {
|
|
7769
|
-
lines.push("CREATE TABLE IF NOT EXISTS facts (id TEXT PRIMARY KEY, kind TEXT, predicate_json TEXT, derived_by TEXT, confidence TEXT);");
|
|
7770
|
-
lines.push("CREATE TABLE IF NOT EXISTS fact_subjects (fact_id TEXT, node_id TEXT);");
|
|
7771
|
-
lines.push("CREATE TABLE IF NOT EXISTS fact_evidence (fact_id TEXT, file TEXT, span_start INTEGER, span_end INTEGER);");
|
|
7772
|
-
for (const f of graph.facts) {
|
|
7773
|
-
lines.push(`INSERT INTO facts VALUES (${sqlStr(f.id)}, ${sqlStr(f.kind)}, ${sqlStr(JSON.stringify(f.predicate))}, ${sqlStr(f.derivedBy)}, ${sqlStr(f.confidence)});`);
|
|
7774
|
-
for (const s of f.subjectIds) lines.push(`INSERT INTO fact_subjects VALUES (${sqlStr(f.id)}, ${sqlStr(s)});`);
|
|
7775
|
-
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)});`);
|
|
7776
|
-
}
|
|
7777
|
-
}
|
|
7778
|
-
lines.push("COMMIT;");
|
|
7779
|
-
return lines.join("\n") + "\n";
|
|
7780
|
-
}
|
|
7781
|
-
function sqlStr(v) {
|
|
7782
|
-
if (v == null) return "NULL";
|
|
7783
|
-
return `'${v.replace(/'/g, "''")}'`;
|
|
7784
|
-
}
|
|
7785
|
-
function sqlNum(v) {
|
|
7786
|
-
return v == null || !Number.isFinite(v) ? "NULL" : String(v);
|
|
7787
|
-
}
|
|
7788
|
-
function sqlBool(v) {
|
|
7789
|
-
return v == null ? "NULL" : v ? "1" : "0";
|
|
7790
|
-
}
|
|
7791
|
-
function cyclonedx(ctx) {
|
|
7792
|
-
const components = [];
|
|
7793
|
-
for (const d of ctx.deps ?? []) {
|
|
7794
|
-
components.push({
|
|
7795
|
-
type: "library",
|
|
7796
|
-
name: d.name,
|
|
7797
|
-
version: d.installed ?? d.declared,
|
|
7798
|
-
purl: d.ecosystem === "npm" ? `pkg:npm/${d.name}@${d.installed ?? ""}` : void 0
|
|
7799
|
-
});
|
|
7800
|
-
}
|
|
7801
|
-
for (const m of ctx.models ?? []) {
|
|
7802
|
-
components.push({ type: "machine-learning-model", name: m.name, properties: [{ name: "vg:runtime", value: m.runtime }] });
|
|
7803
|
-
}
|
|
7804
|
-
const bom = {
|
|
7805
|
-
bomFormat: "CycloneDX",
|
|
7806
|
-
specVersion: "1.6",
|
|
7807
|
-
metadata: { timestamp: ctx.generatedAt, tools: [{ name: "vg", version: ctx.graph.provenance.version }] },
|
|
7808
|
-
components
|
|
7809
|
-
};
|
|
7810
|
-
return JSON.stringify(bom, null, 2) + "\n";
|
|
7811
|
-
}
|
|
7812
|
-
function spdx(ctx) {
|
|
7813
|
-
const packages = (ctx.deps ?? []).map((d) => ({
|
|
7814
|
-
SPDXID: `SPDXRef-Package-${cypherLabel(d.name)}`,
|
|
7815
|
-
name: d.name,
|
|
7816
|
-
versionInfo: d.installed ?? d.declared,
|
|
7817
|
-
downloadLocation: "NOASSERTION"
|
|
7818
|
-
}));
|
|
7819
|
-
const doc = {
|
|
7820
|
-
spdxVersion: "SPDX-2.3",
|
|
7821
|
-
dataLicense: "CC0-1.0",
|
|
7822
|
-
SPDXID: "SPDXRef-DOCUMENT",
|
|
7823
|
-
name: "vg-sbom",
|
|
7824
|
-
creationInfo: { created: ctx.generatedAt, creators: [`Tool: vg-${ctx.graph.provenance.version}`] },
|
|
7825
|
-
packages
|
|
7826
|
-
};
|
|
7827
|
-
return JSON.stringify(doc, null, 2) + "\n";
|
|
7828
|
-
}
|
|
7829
5653
|
|
|
7830
|
-
export { ASSISTANTS,
|
|
7831
|
-
//# sourceMappingURL=chunk-
|
|
7832
|
-
//# 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-VXT6LUGP.js.map
|
|
5656
|
+
//# sourceMappingURL=chunk-VXT6LUGP.js.map
|