@vibgrate/cli 2026.720.2 → 2026.720.3
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/dist/baseline-FALF7ULT.js +6 -0
- package/dist/{baseline-WIZAFIJS.js.map → baseline-FALF7ULT.js.map} +1 -1
- package/dist/{chunk-OD5654VF.js → chunk-B6ZWB3WH.js} +3 -3
- package/dist/{chunk-OD5654VF.js.map → chunk-B6ZWB3WH.js.map} +1 -1
- package/dist/{chunk-DDQ5JGBV.js → chunk-IUKCKMR6.js} +3 -3
- package/dist/{chunk-DDQ5JGBV.js.map → chunk-IUKCKMR6.js.map} +1 -1
- package/dist/{chunk-7R4O5CZD.js → chunk-ZFDVHKID.js} +781 -710
- package/dist/chunk-ZFDVHKID.js.map +1 -0
- package/dist/cli.js +245 -94
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/skills/vg/SKILL.md +29 -9
- package/dist/baseline-WIZAFIJS.js +0 -6
- package/dist/chunk-7R4O5CZD.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { hashString, langById, shortId, canonicalize, grammarSetVersion, hashBytes, langForExtension, setGrammarsOverride, parseSource } from './chunk-BHA7QIPD.js';
|
|
2
|
-
import { writeTextFile, pathExists, readJsonFile, loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-
|
|
3
|
-
import * as
|
|
2
|
+
import { writeTextFile, pathExists, readJsonFile, loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-B6ZWB3WH.js';
|
|
3
|
+
import * as fs17 from 'fs';
|
|
4
4
|
import { spawnSync, execFileSync } from 'child_process';
|
|
5
|
-
import * as
|
|
5
|
+
import * as path17 from 'path';
|
|
6
6
|
import ignore from 'ignore';
|
|
7
7
|
import * as v8 from 'v8';
|
|
8
8
|
import Graph from 'graphology';
|
|
@@ -41,9 +41,9 @@ function whichOnPath(cmd) {
|
|
|
41
41
|
}
|
|
42
42
|
function isOwnBinary(binPath) {
|
|
43
43
|
try {
|
|
44
|
-
const real =
|
|
44
|
+
const real = fs17.realpathSync(binPath);
|
|
45
45
|
if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
|
|
46
|
-
const head =
|
|
46
|
+
const head = fs17.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(fs17.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(path17.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 = path17.join(root, ".gitignore");
|
|
227
|
+
if (fs17.existsSync(gitignorePath)) {
|
|
228
|
+
ig.add(fs17.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 = path17.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) => path17.resolve(root, p)) : [root]).filter((p) => fs17.existsSync(p));
|
|
244
244
|
const found = /* @__PURE__ */ new Map();
|
|
245
245
|
const considerFile = (abs) => {
|
|
246
|
-
const rel2 = toPosix(
|
|
246
|
+
const rel2 = toPosix(path17.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(path17.basename(abs).toLowerCase())) return;
|
|
250
|
+
const lang = langForExtension(path17.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 = fs17.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 = path17.join(dir, entry.name);
|
|
263
|
+
const rel2 = toPosix(path17.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 = fs17.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(path17.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(path17.posix.dirname(fromRel), source));
|
|
462
462
|
if (hit) return hit;
|
|
463
463
|
}
|
|
464
464
|
if (source.startsWith(".")) {
|
|
@@ -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 = path17.posix.dirname(fromRel);
|
|
533
|
+
for (let i = 1; i < dots; i++) dir = path17.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(path17.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 = path17.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 path17.posix.normalize(path17.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 = path17.join(root, name);
|
|
578
|
+
if (fs17.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(fs17.readFileSync(file, "utf8"));
|
|
591
591
|
} catch {
|
|
592
592
|
return null;
|
|
593
593
|
}
|
|
594
|
-
const dir =
|
|
594
|
+
const dir = path17.dirname(file);
|
|
595
595
|
let base = { baseUrlRel: "", paths: [] };
|
|
596
596
|
if (cfg.extends && cfg.extends.startsWith(".")) {
|
|
597
|
-
const extPath =
|
|
598
|
-
if (
|
|
597
|
+
const extPath = path17.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
|
|
598
|
+
if (fs17.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 ? path17.relative(root, path17.resolve(dir, co.baseUrl)).split(path17.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 = path17.join(root, "package.json");
|
|
613
|
+
if (fs17.existsSync(rootPkg)) {
|
|
614
614
|
try {
|
|
615
|
-
const pkg = JSON.parse(
|
|
615
|
+
const pkg = JSON.parse(fs17.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 = path17.join(root, "pnpm-workspace.yaml");
|
|
622
|
+
if (fs17.existsSync(pnpmWs)) {
|
|
623
|
+
for (const line of fs17.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 = path17.join(dir, "package.json");
|
|
630
630
|
try {
|
|
631
|
-
const name = JSON.parse(
|
|
632
|
-
if (name) map.set(name,
|
|
631
|
+
const name = JSON.parse(fs17.readFileSync(pj, "utf8")).name;
|
|
632
|
+
if (name) map.set(name, path17.relative(root, dir).split(path17.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 = path17.join(root, clean);
|
|
643
643
|
const recurse = glob.endsWith("**");
|
|
644
644
|
if (glob === clean) {
|
|
645
|
-
if (
|
|
645
|
+
if (fs17.existsSync(path17.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 = fs17.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 = path17.join(dir, e.name);
|
|
663
|
+
if (fs17.existsSync(path17.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) => path17.resolve(root, p)
|
|
1163
1163
|
);
|
|
1164
|
-
const found = candidates.filter((p) =>
|
|
1164
|
+
const found = candidates.filter((p) => fs17.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 = fs17.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 = path17.isAbsolute(p) ? p : path17.resolve(root, p);
|
|
1179
|
+
return path17.relative(root, abs).split(path17.sep).join("/");
|
|
1180
1180
|
}
|
|
1181
1181
|
function bump(map, file, line, hits) {
|
|
1182
1182
|
let lh = map.get(file);
|
|
@@ -1512,7 +1512,7 @@ async function parseInline(files, options) {
|
|
|
1512
1512
|
onProgress?.(0, files.length);
|
|
1513
1513
|
for (const file of files) {
|
|
1514
1514
|
try {
|
|
1515
|
-
const source =
|
|
1515
|
+
const source = fs17.readFileSync(file.abs, "utf8");
|
|
1516
1516
|
out.push(await parseSource(file.rel, file.lang.id, source));
|
|
1517
1517
|
} catch (err) {
|
|
1518
1518
|
out.push(emptyParse(file, `parse failed: ${err.message}`));
|
|
@@ -1567,9 +1567,9 @@ function isWorkerOom(err) {
|
|
|
1567
1567
|
return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
|
|
1568
1568
|
}
|
|
1569
1569
|
function resolveWorkerFile() {
|
|
1570
|
-
const here =
|
|
1571
|
-
const candidate =
|
|
1572
|
-
return
|
|
1570
|
+
const here = path17.dirname(fileURLToPath(import.meta.url));
|
|
1571
|
+
const candidate = path17.join(here, "parse-worker.js");
|
|
1572
|
+
return fs17.existsSync(candidate) ? candidate : null;
|
|
1573
1573
|
}
|
|
1574
1574
|
function chunk(items, buckets) {
|
|
1575
1575
|
const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
|
|
@@ -1608,7 +1608,7 @@ function resolve4(parses, resolver) {
|
|
|
1608
1608
|
addNode(baseNodes, {
|
|
1609
1609
|
id: fileId,
|
|
1610
1610
|
kind: "file",
|
|
1611
|
-
name:
|
|
1611
|
+
name: path17.posix.basename(p.rel),
|
|
1612
1612
|
qualifiedName: p.rel,
|
|
1613
1613
|
file: p.rel,
|
|
1614
1614
|
span: { start: 1, end: 1 },
|
|
@@ -2044,14 +2044,14 @@ function enclosing3(nodes, line) {
|
|
|
2044
2044
|
return best;
|
|
2045
2045
|
}
|
|
2046
2046
|
function normalize2(p) {
|
|
2047
|
-
return
|
|
2047
|
+
return path17.resolve(p).split(path17.sep).join("/");
|
|
2048
2048
|
}
|
|
2049
2049
|
var CACHE_VERSION = "vg-parse-cache/2";
|
|
2050
2050
|
function cacheDir(root) {
|
|
2051
|
-
return
|
|
2051
|
+
return path17.join(root, ".vibgrate", "cache");
|
|
2052
2052
|
}
|
|
2053
2053
|
function cachePath(root) {
|
|
2054
|
-
return
|
|
2054
|
+
return path17.join(cacheDir(root), "parse-cache.json");
|
|
2055
2055
|
}
|
|
2056
2056
|
function loadCache(root, opts) {
|
|
2057
2057
|
const file = cachePath(root);
|
|
@@ -2061,9 +2061,9 @@ function loadCache(root, opts) {
|
|
|
2061
2061
|
grammars: opts.grammars,
|
|
2062
2062
|
entries: {}
|
|
2063
2063
|
};
|
|
2064
|
-
if (!opts.disabled &&
|
|
2064
|
+
if (!opts.disabled && fs17.existsSync(file)) {
|
|
2065
2065
|
try {
|
|
2066
|
-
const loaded = JSON.parse(
|
|
2066
|
+
const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2067
2067
|
if (loaded.version === CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.grammars === opts.grammars && loaded.entries) {
|
|
2068
2068
|
data = loaded;
|
|
2069
2069
|
}
|
|
@@ -2084,8 +2084,8 @@ function loadCache(root, opts) {
|
|
|
2084
2084
|
}
|
|
2085
2085
|
},
|
|
2086
2086
|
save() {
|
|
2087
|
-
|
|
2088
|
-
|
|
2087
|
+
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
2088
|
+
fs17.writeFileSync(file, stableStringify(data, 0));
|
|
2089
2089
|
}
|
|
2090
2090
|
};
|
|
2091
2091
|
}
|
|
@@ -2117,7 +2117,7 @@ function epistemicBreakdown(edges) {
|
|
|
2117
2117
|
// src/engine/build.ts
|
|
2118
2118
|
async function buildGraph(options) {
|
|
2119
2119
|
const start = nowMs();
|
|
2120
|
-
const root =
|
|
2120
|
+
const root = path17.resolve(options.root);
|
|
2121
2121
|
const files = discover({
|
|
2122
2122
|
root,
|
|
2123
2123
|
only: options.only,
|
|
@@ -2145,7 +2145,7 @@ async function buildGraph(options) {
|
|
|
2145
2145
|
for (const file of files) {
|
|
2146
2146
|
let stat;
|
|
2147
2147
|
try {
|
|
2148
|
-
stat =
|
|
2148
|
+
stat = fs17.statSync(file.abs);
|
|
2149
2149
|
} catch {
|
|
2150
2150
|
continue;
|
|
2151
2151
|
}
|
|
@@ -2163,7 +2163,7 @@ async function buildGraph(options) {
|
|
|
2163
2163
|
}
|
|
2164
2164
|
let hash = "";
|
|
2165
2165
|
try {
|
|
2166
|
-
hash = hashBytes(
|
|
2166
|
+
hash = hashBytes(fs17.readFileSync(file.abs));
|
|
2167
2167
|
} catch {
|
|
2168
2168
|
continue;
|
|
2169
2169
|
}
|
|
@@ -2255,7 +2255,7 @@ async function buildGraph(options) {
|
|
|
2255
2255
|
toolchain
|
|
2256
2256
|
},
|
|
2257
2257
|
meta: {
|
|
2258
|
-
root:
|
|
2258
|
+
root: path17.basename(root) === "" ? "." : ".",
|
|
2259
2259
|
languages,
|
|
2260
2260
|
counts: {
|
|
2261
2261
|
nodes: analysis.nodes.length,
|
|
@@ -2301,14 +2301,14 @@ function toRepoRel(p) {
|
|
|
2301
2301
|
function loadScipIndex(root, explicit) {
|
|
2302
2302
|
const candidates = [
|
|
2303
2303
|
explicit,
|
|
2304
|
-
|
|
2305
|
-
|
|
2304
|
+
path17.join(root, "index.scip"),
|
|
2305
|
+
path17.join(root, ".vibgrate", "index.scip")
|
|
2306
2306
|
].filter((p) => Boolean(p));
|
|
2307
2307
|
for (const file of candidates) {
|
|
2308
|
-
const abs =
|
|
2309
|
-
if (!
|
|
2308
|
+
const abs = path17.isAbsolute(file) ? file : path17.resolve(root, file);
|
|
2309
|
+
if (!fs17.existsSync(abs)) continue;
|
|
2310
2310
|
try {
|
|
2311
|
-
const index = decodeScipIndex(new Uint8Array(
|
|
2311
|
+
const index = decodeScipIndex(new Uint8Array(fs17.readFileSync(abs)));
|
|
2312
2312
|
if (index.documents.length) {
|
|
2313
2313
|
return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
|
|
2314
2314
|
}
|
|
@@ -2469,46 +2469,46 @@ function esc(s) {
|
|
|
2469
2469
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2470
2470
|
}
|
|
2471
2471
|
function vibgrateDir(root) {
|
|
2472
|
-
return
|
|
2472
|
+
return path17.join(root, ".vibgrate");
|
|
2473
2473
|
}
|
|
2474
2474
|
function defaultGraphPath(root) {
|
|
2475
|
-
return
|
|
2475
|
+
return path17.join(vibgrateDir(root), "graph.json");
|
|
2476
2476
|
}
|
|
2477
2477
|
function writeArtifacts(graph, options) {
|
|
2478
2478
|
const dir = vibgrateDir(options.root);
|
|
2479
|
-
|
|
2479
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
2480
2480
|
const graphPath = options.graphPath ?? defaultGraphPath(options.root);
|
|
2481
|
-
|
|
2481
|
+
fs17.mkdirSync(path17.dirname(graphPath), { recursive: true });
|
|
2482
2482
|
const tmp = `${graphPath}.${process.pid}.tmp`;
|
|
2483
2483
|
try {
|
|
2484
|
-
|
|
2485
|
-
|
|
2484
|
+
fs17.writeFileSync(tmp, serializeGraph(graph));
|
|
2485
|
+
fs17.renameSync(tmp, graphPath);
|
|
2486
2486
|
} catch (err) {
|
|
2487
|
-
|
|
2487
|
+
fs17.rmSync(tmp, { force: true });
|
|
2488
2488
|
throw err;
|
|
2489
2489
|
}
|
|
2490
2490
|
const written = { graphPath };
|
|
2491
2491
|
if (options.report !== false) {
|
|
2492
|
-
const reportPath =
|
|
2493
|
-
|
|
2492
|
+
const reportPath = path17.join(dir, "GRAPH_REPORT.md");
|
|
2493
|
+
fs17.writeFileSync(reportPath, renderReport(graph));
|
|
2494
2494
|
written.reportPath = reportPath;
|
|
2495
2495
|
}
|
|
2496
2496
|
if (options.html !== false) {
|
|
2497
|
-
const htmlPath =
|
|
2498
|
-
|
|
2497
|
+
const htmlPath = path17.join(dir, "graph.html");
|
|
2498
|
+
fs17.writeFileSync(htmlPath, renderHtml(graph));
|
|
2499
2499
|
written.htmlPath = htmlPath;
|
|
2500
2500
|
}
|
|
2501
2501
|
if (graph.facts && graph.facts.length) {
|
|
2502
|
-
const factsPath =
|
|
2503
|
-
|
|
2502
|
+
const factsPath = path17.join(dir, "facts.jsonl");
|
|
2503
|
+
fs17.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
|
|
2504
2504
|
written.factsPath = factsPath;
|
|
2505
2505
|
}
|
|
2506
2506
|
return written;
|
|
2507
2507
|
}
|
|
2508
2508
|
function loadGraph(root, graphPath) {
|
|
2509
2509
|
const file = graphPath ?? defaultGraphPath(root);
|
|
2510
|
-
if (!
|
|
2511
|
-
return parseGraph(
|
|
2510
|
+
if (!fs17.existsSync(file)) return null;
|
|
2511
|
+
return parseGraph(fs17.readFileSync(file, "utf8"));
|
|
2512
2512
|
}
|
|
2513
2513
|
|
|
2514
2514
|
// src/engine/verify.ts
|
|
@@ -2585,7 +2585,7 @@ function isProcessAlive(pid) {
|
|
|
2585
2585
|
}
|
|
2586
2586
|
function lockIsStale(file, staleMs) {
|
|
2587
2587
|
try {
|
|
2588
|
-
const { pid, at } = JSON.parse(
|
|
2588
|
+
const { pid, at } = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2589
2589
|
if (typeof at === "number" && Date.now() - at > staleMs) return true;
|
|
2590
2590
|
if (typeof pid === "number" && !isProcessAlive(pid)) return true;
|
|
2591
2591
|
return false;
|
|
@@ -2596,10 +2596,10 @@ function lockIsStale(file, staleMs) {
|
|
|
2596
2596
|
function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
|
|
2597
2597
|
const write = () => {
|
|
2598
2598
|
try {
|
|
2599
|
-
|
|
2600
|
-
const fd =
|
|
2601
|
-
|
|
2602
|
-
|
|
2599
|
+
fs17.mkdirSync(path17.dirname(file), { recursive: true });
|
|
2600
|
+
const fd = fs17.openSync(file, "wx");
|
|
2601
|
+
fs17.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
|
|
2602
|
+
fs17.closeSync(fd);
|
|
2603
2603
|
return true;
|
|
2604
2604
|
} catch {
|
|
2605
2605
|
return false;
|
|
@@ -2608,7 +2608,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
|
|
|
2608
2608
|
if (write()) return true;
|
|
2609
2609
|
if (lockIsStale(file, staleMs)) {
|
|
2610
2610
|
try {
|
|
2611
|
-
|
|
2611
|
+
fs17.rmSync(file, { force: true });
|
|
2612
2612
|
} catch {
|
|
2613
2613
|
}
|
|
2614
2614
|
return write();
|
|
@@ -2617,7 +2617,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
|
|
|
2617
2617
|
}
|
|
2618
2618
|
function releaseLock(file) {
|
|
2619
2619
|
try {
|
|
2620
|
-
|
|
2620
|
+
fs17.rmSync(file, { force: true });
|
|
2621
2621
|
} catch {
|
|
2622
2622
|
}
|
|
2623
2623
|
}
|
|
@@ -2627,14 +2627,14 @@ function resolveEmbedModel(model) {
|
|
|
2627
2627
|
return model ?? "bge-small-en-v1.5";
|
|
2628
2628
|
}
|
|
2629
2629
|
function modelCacheDir() {
|
|
2630
|
-
const base = process.env.XDG_CACHE_HOME ||
|
|
2631
|
-
return
|
|
2630
|
+
const base = process.env.XDG_CACHE_HOME || path17.join(os.homedir(), ".cache");
|
|
2631
|
+
return path17.join(base, "vibgrate", "models");
|
|
2632
2632
|
}
|
|
2633
2633
|
function modelReadyMarker(modelId) {
|
|
2634
|
-
return
|
|
2634
|
+
return path17.join(modelCacheDir(), `.ready-${safe(modelId)}`);
|
|
2635
2635
|
}
|
|
2636
2636
|
function isModelReady(modelId = resolveEmbedModel()) {
|
|
2637
|
-
return
|
|
2637
|
+
return fs17.existsSync(modelReadyMarker(modelId));
|
|
2638
2638
|
}
|
|
2639
2639
|
function unavailableMessage(reason) {
|
|
2640
2640
|
switch (reason) {
|
|
@@ -2651,13 +2651,13 @@ function unavailableMessage(reason) {
|
|
|
2651
2651
|
}
|
|
2652
2652
|
function modelCacheInfo(modelId = resolveEmbedModel()) {
|
|
2653
2653
|
const dir = modelCacheDir();
|
|
2654
|
-
return { dir, present: isModelReady(modelId) ||
|
|
2654
|
+
return { dir, present: isModelReady(modelId) || fs17.existsSync(dir), bytes: dirSize(dir) };
|
|
2655
2655
|
}
|
|
2656
2656
|
function clearModelCache() {
|
|
2657
2657
|
const dir = modelCacheDir();
|
|
2658
2658
|
const bytes = dirSize(dir);
|
|
2659
2659
|
try {
|
|
2660
|
-
|
|
2660
|
+
fs17.rmSync(dir, { recursive: true, force: true });
|
|
2661
2661
|
} catch {
|
|
2662
2662
|
}
|
|
2663
2663
|
return bytes;
|
|
@@ -2666,16 +2666,16 @@ function dirSize(dir) {
|
|
|
2666
2666
|
let total = 0;
|
|
2667
2667
|
let entries;
|
|
2668
2668
|
try {
|
|
2669
|
-
entries =
|
|
2669
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
2670
2670
|
} catch {
|
|
2671
2671
|
return 0;
|
|
2672
2672
|
}
|
|
2673
2673
|
for (const e of entries) {
|
|
2674
|
-
const p =
|
|
2674
|
+
const p = path17.join(dir, e.name);
|
|
2675
2675
|
if (e.isDirectory()) total += dirSize(p);
|
|
2676
2676
|
else {
|
|
2677
2677
|
try {
|
|
2678
|
-
total +=
|
|
2678
|
+
total += fs17.statSync(p).size;
|
|
2679
2679
|
} catch {
|
|
2680
2680
|
}
|
|
2681
2681
|
}
|
|
@@ -2687,7 +2687,7 @@ function isPermissionError(e) {
|
|
|
2687
2687
|
return code === "EACCES" || code === "EPERM" || code === "EROFS";
|
|
2688
2688
|
}
|
|
2689
2689
|
function embeddingsCached(root, modelId) {
|
|
2690
|
-
return
|
|
2690
|
+
return fs17.existsSync(path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
|
|
2691
2691
|
}
|
|
2692
2692
|
async function loadEmbedder(options = {}) {
|
|
2693
2693
|
if (options.local) return null;
|
|
@@ -2701,7 +2701,7 @@ async function loadEmbedder(options = {}) {
|
|
|
2701
2701
|
if (!mod?.FlagEmbedding) return fail("not-installed");
|
|
2702
2702
|
const cache = modelCacheDir();
|
|
2703
2703
|
try {
|
|
2704
|
-
|
|
2704
|
+
fs17.mkdirSync(cache, { recursive: true });
|
|
2705
2705
|
} catch (e) {
|
|
2706
2706
|
return fail(isPermissionError(e) ? "no-permission" : "init-failed");
|
|
2707
2707
|
}
|
|
@@ -2716,7 +2716,7 @@ async function loadEmbedder(options = {}) {
|
|
|
2716
2716
|
"embedding model init timed out"
|
|
2717
2717
|
);
|
|
2718
2718
|
try {
|
|
2719
|
-
|
|
2719
|
+
fs17.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
|
|
2720
2720
|
} catch {
|
|
2721
2721
|
}
|
|
2722
2722
|
return {
|
|
@@ -2741,7 +2741,7 @@ async function importEmbedBackend() {
|
|
|
2741
2741
|
const hostDir = process.env.VIBGRATE_EMBEDDER_PATH;
|
|
2742
2742
|
if (hostDir) {
|
|
2743
2743
|
try {
|
|
2744
|
-
const req = createRequire(
|
|
2744
|
+
const req = createRequire(path17.join(hostDir, "package.json"));
|
|
2745
2745
|
const mod = await import(pathToFileURL(req.resolve("fastembed")).href);
|
|
2746
2746
|
const resolved = mod?.FlagEmbedding ? mod : mod?.default;
|
|
2747
2747
|
if (resolved?.FlagEmbedding) return resolved;
|
|
@@ -2827,7 +2827,7 @@ function cosine(a, b) {
|
|
|
2827
2827
|
var EMBED_CHUNK = 256;
|
|
2828
2828
|
var CACHE_WRITE_INTERVAL_MS = 1500;
|
|
2829
2829
|
function vectorCachePath(root, modelId) {
|
|
2830
|
-
return
|
|
2830
|
+
return path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
|
|
2831
2831
|
}
|
|
2832
2832
|
function countPending(graph, root, modelId) {
|
|
2833
2833
|
const entries = readCacheEntries(vectorCachePath(root, modelId), modelId);
|
|
@@ -2842,9 +2842,9 @@ function countPending(graph, root, modelId) {
|
|
|
2842
2842
|
return pending;
|
|
2843
2843
|
}
|
|
2844
2844
|
function readCacheEntries(file, modelId) {
|
|
2845
|
-
if (!
|
|
2845
|
+
if (!fs17.existsSync(file)) return {};
|
|
2846
2846
|
try {
|
|
2847
|
-
const loaded = JSON.parse(
|
|
2847
|
+
const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2848
2848
|
if (loaded.model === modelId && loaded.entries) return loaded.entries;
|
|
2849
2849
|
} catch {
|
|
2850
2850
|
}
|
|
@@ -2853,9 +2853,9 @@ function readCacheEntries(file, modelId) {
|
|
|
2853
2853
|
async function getNodeEmbeddings(graph, embedder, root, onProgress) {
|
|
2854
2854
|
const file = vectorCachePath(root, embedder.id);
|
|
2855
2855
|
let cache = { model: embedder.id, entries: {} };
|
|
2856
|
-
if (
|
|
2856
|
+
if (fs17.existsSync(file)) {
|
|
2857
2857
|
try {
|
|
2858
|
-
const loaded = JSON.parse(
|
|
2858
|
+
const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2859
2859
|
if (loaded.model === embedder.id && loaded.entries) cache = loaded;
|
|
2860
2860
|
} catch {
|
|
2861
2861
|
}
|
|
@@ -2873,10 +2873,10 @@ async function getNodeEmbeddings(graph, embedder, root, onProgress) {
|
|
|
2873
2873
|
}
|
|
2874
2874
|
const persist = () => {
|
|
2875
2875
|
try {
|
|
2876
|
-
|
|
2876
|
+
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
2877
2877
|
const tmp = `${file}.tmp.${process.pid}`;
|
|
2878
|
-
|
|
2879
|
-
|
|
2878
|
+
fs17.writeFileSync(tmp, JSON.stringify(cache));
|
|
2879
|
+
fs17.renameSync(tmp, file);
|
|
2880
2880
|
} catch {
|
|
2881
2881
|
}
|
|
2882
2882
|
};
|
|
@@ -2914,21 +2914,21 @@ function safe(id) {
|
|
|
2914
2914
|
}
|
|
2915
2915
|
var SNAPSHOT_VERSION = "vg-freshness/1";
|
|
2916
2916
|
function snapshotPath(root) {
|
|
2917
|
-
return
|
|
2917
|
+
return path17.join(cacheDir(root), "freshness.json");
|
|
2918
2918
|
}
|
|
2919
2919
|
function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
|
|
2920
2920
|
const files = {};
|
|
2921
2921
|
for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
|
|
2922
2922
|
const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
|
|
2923
2923
|
try {
|
|
2924
|
-
|
|
2925
|
-
|
|
2924
|
+
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
2925
|
+
fs17.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
2926
2926
|
} catch {
|
|
2927
2927
|
}
|
|
2928
2928
|
}
|
|
2929
2929
|
function loadSnapshot(root) {
|
|
2930
2930
|
try {
|
|
2931
|
-
const loaded = JSON.parse(
|
|
2931
|
+
const loaded = JSON.parse(fs17.readFileSync(snapshotPath(root), "utf8"));
|
|
2932
2932
|
if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
|
|
2933
2933
|
return { ...loaded, scope: loaded.scope ?? {} };
|
|
2934
2934
|
}
|
|
@@ -2964,7 +2964,7 @@ function probeFreshness(root) {
|
|
|
2964
2964
|
}
|
|
2965
2965
|
let stat;
|
|
2966
2966
|
try {
|
|
2967
|
-
stat =
|
|
2967
|
+
stat = fs17.statSync(file.abs);
|
|
2968
2968
|
} catch {
|
|
2969
2969
|
drift.removed.push(file.rel);
|
|
2970
2970
|
continue;
|
|
@@ -2972,7 +2972,7 @@ function probeFreshness(root) {
|
|
|
2972
2972
|
if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
|
|
2973
2973
|
let hash = "";
|
|
2974
2974
|
try {
|
|
2975
|
-
hash = hashBytes(
|
|
2975
|
+
hash = hashBytes(fs17.readFileSync(file.abs));
|
|
2976
2976
|
} catch {
|
|
2977
2977
|
drift.removed.push(file.rel);
|
|
2978
2978
|
continue;
|
|
@@ -2989,7 +2989,7 @@ function probeFreshness(root) {
|
|
|
2989
2989
|
}
|
|
2990
2990
|
if (absorbed && !hasDrift(drift)) {
|
|
2991
2991
|
try {
|
|
2992
|
-
|
|
2992
|
+
fs17.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
2993
2993
|
} catch {
|
|
2994
2994
|
}
|
|
2995
2995
|
}
|
|
@@ -3306,224 +3306,6 @@ function estimateTokens(text) {
|
|
|
3306
3306
|
function round2(x) {
|
|
3307
3307
|
return Math.round(x * 1e6) / 1e6;
|
|
3308
3308
|
}
|
|
3309
|
-
var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
3310
|
-
function refreshLockPath(root) {
|
|
3311
|
-
return path19.join(cacheDir(root), "refresh.lock");
|
|
3312
|
-
}
|
|
3313
|
-
async function refreshIfStale(root, opts = {}) {
|
|
3314
|
-
const probe = probeFreshness(root);
|
|
3315
|
-
if (!probe) return { status: "no-snapshot" };
|
|
3316
|
-
if (!hasDrift(probe.drift)) return { status: "fresh" };
|
|
3317
|
-
const lock = refreshLockPath(root);
|
|
3318
|
-
if (!acquireLock(lock, REFRESH_LOCK_STALE_MS)) return { status: "locked" };
|
|
3319
|
-
const start = process.hrtime.bigint();
|
|
3320
|
-
try {
|
|
3321
|
-
const { scope } = probe;
|
|
3322
|
-
const result = await buildGraph({
|
|
3323
|
-
root,
|
|
3324
|
-
only: scope.only,
|
|
3325
|
-
exclude: scope.exclude,
|
|
3326
|
-
paths: scope.paths,
|
|
3327
|
-
deep: scope.deep,
|
|
3328
|
-
noGround: scope.noGround,
|
|
3329
|
-
scip: scope.scip,
|
|
3330
|
-
noScip: scope.noScip,
|
|
3331
|
-
noTsc: scope.noTsc,
|
|
3332
|
-
cluster: scope.cluster,
|
|
3333
|
-
grammarsDir: scope.grammarsDir,
|
|
3334
|
-
inline: opts.inline,
|
|
3335
|
-
jobs: opts.jobs
|
|
3336
|
-
});
|
|
3337
|
-
const wrote = result.graph.provenance.corpusHash !== probe.corpusHash;
|
|
3338
|
-
if (wrote) {
|
|
3339
|
-
const dir = vibgrateDir(root);
|
|
3340
|
-
writeArtifacts(result.graph, {
|
|
3341
|
-
root,
|
|
3342
|
-
report: fs19.existsSync(path19.join(dir, "GRAPH_REPORT.md")),
|
|
3343
|
-
html: fs19.existsSync(path19.join(dir, "graph.html"))
|
|
3344
|
-
});
|
|
3345
|
-
}
|
|
3346
|
-
writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
|
|
3347
|
-
const ms = Number((process.hrtime.bigint() - start) / 1000000n);
|
|
3348
|
-
return {
|
|
3349
|
-
status: "refreshed",
|
|
3350
|
-
drift: probe.drift,
|
|
3351
|
-
ms,
|
|
3352
|
-
reparsed: result.reparsed,
|
|
3353
|
-
totalFiles: result.totalFiles,
|
|
3354
|
-
wrote
|
|
3355
|
-
};
|
|
3356
|
-
} catch (err) {
|
|
3357
|
-
return { status: "error", message: err.message };
|
|
3358
|
-
} finally {
|
|
3359
|
-
releaseLock(lock);
|
|
3360
|
-
}
|
|
3361
|
-
}
|
|
3362
|
-
var LEDGER = "savings.jsonl";
|
|
3363
|
-
var PER_FILE_TOKENS = 400;
|
|
3364
|
-
var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node"]);
|
|
3365
|
-
var CLI_TOOL_ALIASES = {
|
|
3366
|
-
ask: "query_graph",
|
|
3367
|
-
show: "get_node",
|
|
3368
|
-
impact: "impact_of",
|
|
3369
|
-
path: "find_path",
|
|
3370
|
-
hubs: "list_hubs",
|
|
3371
|
-
areas: "list_areas",
|
|
3372
|
-
tree: "tree"
|
|
3373
|
-
};
|
|
3374
|
-
function sanitizeClient(name) {
|
|
3375
|
-
if (typeof name !== "string") return "unknown";
|
|
3376
|
-
const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
3377
|
-
return cleaned || "unknown";
|
|
3378
|
-
}
|
|
3379
|
-
function recordCliCall(root, entry, now) {
|
|
3380
|
-
recordSaving(
|
|
3381
|
-
root,
|
|
3382
|
-
{
|
|
3383
|
-
tool: entry.tool,
|
|
3384
|
-
source: "cli",
|
|
3385
|
-
client: sanitizeClient(entry.client),
|
|
3386
|
-
outcome: entry.outcome,
|
|
3387
|
-
vgTokens: entry.vgTokens ?? 0,
|
|
3388
|
-
baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
|
|
3389
|
-
},
|
|
3390
|
-
now
|
|
3391
|
-
);
|
|
3392
|
-
}
|
|
3393
|
-
function savingsLedgerPath(root) {
|
|
3394
|
-
return path19.join(cacheDir(root), LEDGER);
|
|
3395
|
-
}
|
|
3396
|
-
function ledgerPath(root) {
|
|
3397
|
-
return savingsLedgerPath(root);
|
|
3398
|
-
}
|
|
3399
|
-
function savingsRecorded(root) {
|
|
3400
|
-
return fs19.existsSync(ledgerPath(root));
|
|
3401
|
-
}
|
|
3402
|
-
function recordSaving(root, entry, now) {
|
|
3403
|
-
try {
|
|
3404
|
-
fs19.mkdirSync(cacheDir(root), { recursive: true });
|
|
3405
|
-
const line = JSON.stringify({ ts: now, ...entry });
|
|
3406
|
-
fs19.appendFileSync(ledgerPath(root), line + "\n");
|
|
3407
|
-
} catch {
|
|
3408
|
-
}
|
|
3409
|
-
}
|
|
3410
|
-
var DEFAULT_RATE_PER_M = 3;
|
|
3411
|
-
var DEFAULT_RATE_LABEL = "input @ $3/1M";
|
|
3412
|
-
function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
|
|
3413
|
-
const file = ledgerPath(root);
|
|
3414
|
-
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
3415
|
-
let queries = 0;
|
|
3416
|
-
let vgTokens = 0;
|
|
3417
|
-
let baselineTokens = 0;
|
|
3418
|
-
if (fs19.existsSync(file)) {
|
|
3419
|
-
for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
|
|
3420
|
-
if (!line.trim()) continue;
|
|
3421
|
-
try {
|
|
3422
|
-
const e = JSON.parse(line);
|
|
3423
|
-
if (e.ts < cutoff) continue;
|
|
3424
|
-
if (!SAVINGS_TOOLS.has(e.tool)) continue;
|
|
3425
|
-
queries++;
|
|
3426
|
-
vgTokens += e.vgTokens;
|
|
3427
|
-
baselineTokens += e.baselineTokens;
|
|
3428
|
-
} catch {
|
|
3429
|
-
}
|
|
3430
|
-
}
|
|
3431
|
-
}
|
|
3432
|
-
const estCostVg = vgTokens / 1e6 * ratePerM;
|
|
3433
|
-
const estCostBaseline = baselineTokens / 1e6 * ratePerM;
|
|
3434
|
-
return {
|
|
3435
|
-
enabled: savingsRecorded(root),
|
|
3436
|
-
days,
|
|
3437
|
-
queries,
|
|
3438
|
-
vgTokens,
|
|
3439
|
-
baselineTokens,
|
|
3440
|
-
ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
|
|
3441
|
-
estCostVg: round22(estCostVg),
|
|
3442
|
-
estCostBaseline: round22(estCostBaseline),
|
|
3443
|
-
saved: round22(estCostBaseline - estCostVg),
|
|
3444
|
-
rateLabel: DEFAULT_RATE_LABEL
|
|
3445
|
-
};
|
|
3446
|
-
}
|
|
3447
|
-
function round22(x) {
|
|
3448
|
-
return Math.round(x * 100) / 100;
|
|
3449
|
-
}
|
|
3450
|
-
function toDimensionStats(byKey) {
|
|
3451
|
-
return [...byKey.entries()].map(([key, r]) => {
|
|
3452
|
-
const calls = r.complete + r.partial + r.miss;
|
|
3453
|
-
return {
|
|
3454
|
-
key,
|
|
3455
|
-
calls,
|
|
3456
|
-
complete: r.complete,
|
|
3457
|
-
partial: r.partial,
|
|
3458
|
-
miss: r.miss,
|
|
3459
|
-
successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
|
|
3460
|
-
};
|
|
3461
|
-
}).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
|
|
3462
|
-
}
|
|
3463
|
-
function readUsage(root, days, now) {
|
|
3464
|
-
const file = ledgerPath(root);
|
|
3465
|
-
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
3466
|
-
const byTool = /* @__PURE__ */ new Map();
|
|
3467
|
-
const bySource = /* @__PURE__ */ new Map();
|
|
3468
|
-
const byClient = /* @__PURE__ */ new Map();
|
|
3469
|
-
const msByTool = /* @__PURE__ */ new Map();
|
|
3470
|
-
const bump2 = (m, key, outcome) => {
|
|
3471
|
-
const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
|
|
3472
|
-
row[outcome]++;
|
|
3473
|
-
m.set(key, row);
|
|
3474
|
-
};
|
|
3475
|
-
if (fs19.existsSync(file)) {
|
|
3476
|
-
for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
|
|
3477
|
-
if (!line.trim()) continue;
|
|
3478
|
-
try {
|
|
3479
|
-
const e = JSON.parse(line);
|
|
3480
|
-
if (e.ts < cutoff) continue;
|
|
3481
|
-
const outcome = e.outcome ?? "complete";
|
|
3482
|
-
bump2(byTool, e.tool, outcome);
|
|
3483
|
-
bump2(bySource, e.source ?? "mcp", outcome);
|
|
3484
|
-
bump2(byClient, e.client ?? "unknown", outcome);
|
|
3485
|
-
if (typeof e.ms === "number" && e.ms >= 0) {
|
|
3486
|
-
const timing = msByTool.get(e.tool) ?? { sum: 0, n: 0 };
|
|
3487
|
-
timing.sum += e.ms;
|
|
3488
|
-
timing.n++;
|
|
3489
|
-
msByTool.set(e.tool, timing);
|
|
3490
|
-
}
|
|
3491
|
-
} catch {
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
}
|
|
3495
|
-
const commands = toDimensionStats(byTool).map((d) => {
|
|
3496
|
-
const timing = msByTool.get(d.key);
|
|
3497
|
-
return {
|
|
3498
|
-
tool: d.key,
|
|
3499
|
-
calls: d.calls,
|
|
3500
|
-
complete: d.complete,
|
|
3501
|
-
partial: d.partial,
|
|
3502
|
-
miss: d.miss,
|
|
3503
|
-
successPct: d.successPct,
|
|
3504
|
-
avgMs: timing && timing.n > 0 ? Math.round(timing.sum / timing.n) : null
|
|
3505
|
-
};
|
|
3506
|
-
});
|
|
3507
|
-
const totals = commands.reduce(
|
|
3508
|
-
(t, c) => ({
|
|
3509
|
-
calls: t.calls + c.calls,
|
|
3510
|
-
complete: t.complete + c.complete,
|
|
3511
|
-
partial: t.partial + c.partial,
|
|
3512
|
-
miss: t.miss + c.miss
|
|
3513
|
-
}),
|
|
3514
|
-
{ calls: 0, complete: 0, partial: 0, miss: 0 }
|
|
3515
|
-
);
|
|
3516
|
-
const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
|
|
3517
|
-
return {
|
|
3518
|
-
enabled: savingsRecorded(root),
|
|
3519
|
-
days,
|
|
3520
|
-
commands,
|
|
3521
|
-
sources: toDimensionStats(bySource),
|
|
3522
|
-
clients: toDimensionStats(byClient),
|
|
3523
|
-
totals,
|
|
3524
|
-
avgSuccessPct
|
|
3525
|
-
};
|
|
3526
|
-
}
|
|
3527
3309
|
|
|
3528
3310
|
// src/engine/lookup.ts
|
|
3529
3311
|
function findNodes(graph, query) {
|
|
@@ -3681,10 +3463,10 @@ function testsToRun(graph, rootId, depth = 4) {
|
|
|
3681
3463
|
};
|
|
3682
3464
|
}
|
|
3683
3465
|
function detectRunner(root, lang) {
|
|
3684
|
-
const pkgPath =
|
|
3685
|
-
if (
|
|
3466
|
+
const pkgPath = path17.join(root, "package.json");
|
|
3467
|
+
if (fs17.existsSync(pkgPath)) {
|
|
3686
3468
|
try {
|
|
3687
|
-
const pkg = JSON.parse(
|
|
3469
|
+
const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
|
|
3688
3470
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3689
3471
|
if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
|
|
3690
3472
|
if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
|
|
@@ -3692,10 +3474,10 @@ function detectRunner(root, lang) {
|
|
|
3692
3474
|
} catch {
|
|
3693
3475
|
}
|
|
3694
3476
|
}
|
|
3695
|
-
if (lang === "py" ||
|
|
3477
|
+
if (lang === "py" || fs17.existsSync(path17.join(root, "pyproject.toml")) || fs17.existsSync(path17.join(root, "pytest.ini"))) {
|
|
3696
3478
|
return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
|
|
3697
3479
|
}
|
|
3698
|
-
if (lang === "go" ||
|
|
3480
|
+
if (lang === "go" || fs17.existsSync(path17.join(root, "go.mod"))) {
|
|
3699
3481
|
return { name: "go test", command: () => "go test ./..." };
|
|
3700
3482
|
}
|
|
3701
3483
|
if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
|
|
@@ -3706,7 +3488,7 @@ function detectRunner(root, lang) {
|
|
|
3706
3488
|
}
|
|
3707
3489
|
function hasFile(root, re) {
|
|
3708
3490
|
try {
|
|
3709
|
-
return
|
|
3491
|
+
return fs17.readdirSync(root).some((f) => re.test(f));
|
|
3710
3492
|
} catch {
|
|
3711
3493
|
return false;
|
|
3712
3494
|
}
|
|
@@ -3778,7 +3560,7 @@ function findManifests(root) {
|
|
|
3778
3560
|
if (depth > 8 || scanned > MAX_ENTRIES2) return;
|
|
3779
3561
|
let entries;
|
|
3780
3562
|
try {
|
|
3781
|
-
entries =
|
|
3563
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
3782
3564
|
} catch {
|
|
3783
3565
|
return;
|
|
3784
3566
|
}
|
|
@@ -3786,11 +3568,11 @@ function findManifests(root) {
|
|
|
3786
3568
|
scanned++;
|
|
3787
3569
|
if (scanned > MAX_ENTRIES2) break;
|
|
3788
3570
|
if (e.isDirectory()) {
|
|
3789
|
-
if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(
|
|
3571
|
+
if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path17.join(dir, e.name), depth + 1);
|
|
3790
3572
|
continue;
|
|
3791
3573
|
}
|
|
3792
3574
|
const eco = MANIFEST_BY_FILE[e.name] ?? (/\.(cs|fs)proj$/i.test(e.name) ? "dotnet" : void 0);
|
|
3793
|
-
if (eco) set[eco].push(
|
|
3575
|
+
if (eco) set[eco].push(path17.join(dir, e.name));
|
|
3794
3576
|
}
|
|
3795
3577
|
};
|
|
3796
3578
|
walk2(root, 0);
|
|
@@ -3802,11 +3584,11 @@ function npmDeps(files) {
|
|
|
3802
3584
|
for (const file of files) {
|
|
3803
3585
|
let pkg;
|
|
3804
3586
|
try {
|
|
3805
|
-
pkg = JSON.parse(
|
|
3587
|
+
pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
3806
3588
|
} catch {
|
|
3807
3589
|
continue;
|
|
3808
3590
|
}
|
|
3809
|
-
const dir =
|
|
3591
|
+
const dir = path17.dirname(file);
|
|
3810
3592
|
const all = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3811
3593
|
for (const [name, declared] of Object.entries(all)) {
|
|
3812
3594
|
out.push({ name, ecosystem: "npm", declared, installed: installedNpmVersion(dir, name) });
|
|
@@ -3817,12 +3599,12 @@ function npmDeps(files) {
|
|
|
3817
3599
|
function installedNpmVersion(dir, name) {
|
|
3818
3600
|
let cur = dir;
|
|
3819
3601
|
for (let i = 0; i < 12; i++) {
|
|
3820
|
-
const p =
|
|
3602
|
+
const p = path17.join(cur, "node_modules", name, "package.json");
|
|
3821
3603
|
try {
|
|
3822
|
-
return JSON.parse(
|
|
3604
|
+
return JSON.parse(fs17.readFileSync(p, "utf8")).version;
|
|
3823
3605
|
} catch {
|
|
3824
3606
|
}
|
|
3825
|
-
const parent =
|
|
3607
|
+
const parent = path17.dirname(cur);
|
|
3826
3608
|
if (parent === cur) break;
|
|
3827
3609
|
cur = parent;
|
|
3828
3610
|
}
|
|
@@ -3839,21 +3621,21 @@ function normalizePep503(name) {
|
|
|
3839
3621
|
function sitePackagesDirs(base) {
|
|
3840
3622
|
const out = [];
|
|
3841
3623
|
try {
|
|
3842
|
-
for (const d of
|
|
3843
|
-
if (d.startsWith("python")) out.push(
|
|
3624
|
+
for (const d of fs17.readdirSync(path17.join(base, "lib"))) {
|
|
3625
|
+
if (d.startsWith("python")) out.push(path17.join(base, "lib", d, "site-packages"));
|
|
3844
3626
|
}
|
|
3845
3627
|
} catch {
|
|
3846
3628
|
}
|
|
3847
|
-
out.push(
|
|
3629
|
+
out.push(path17.join(base, "Lib", "site-packages"));
|
|
3848
3630
|
return out;
|
|
3849
3631
|
}
|
|
3850
3632
|
function installedPypiVersion(root, name) {
|
|
3851
3633
|
const target = normalizePep503(name);
|
|
3852
3634
|
for (const venv of [".venv", "venv", "env", ".tox"]) {
|
|
3853
|
-
for (const sp of sitePackagesDirs(
|
|
3635
|
+
for (const sp of sitePackagesDirs(path17.join(root, venv))) {
|
|
3854
3636
|
let entries;
|
|
3855
3637
|
try {
|
|
3856
|
-
entries =
|
|
3638
|
+
entries = fs17.readdirSync(sp);
|
|
3857
3639
|
} catch {
|
|
3858
3640
|
continue;
|
|
3859
3641
|
}
|
|
@@ -3868,7 +3650,7 @@ function installedPypiVersion(root, name) {
|
|
|
3868
3650
|
function installedPhpVersion(root, name) {
|
|
3869
3651
|
let data;
|
|
3870
3652
|
try {
|
|
3871
|
-
data = JSON.parse(
|
|
3653
|
+
data = JSON.parse(fs17.readFileSync(path17.join(root, "vendor", "composer", "installed.json"), "utf8"));
|
|
3872
3654
|
} catch {
|
|
3873
3655
|
return void 0;
|
|
3874
3656
|
}
|
|
@@ -3887,11 +3669,11 @@ function pypiDeps(files) {
|
|
|
3887
3669
|
for (const file of files) {
|
|
3888
3670
|
let text;
|
|
3889
3671
|
try {
|
|
3890
|
-
text =
|
|
3672
|
+
text = fs17.readFileSync(file, "utf8");
|
|
3891
3673
|
} catch {
|
|
3892
3674
|
continue;
|
|
3893
3675
|
}
|
|
3894
|
-
if (
|
|
3676
|
+
if (path17.basename(file) === "requirements.txt") {
|
|
3895
3677
|
for (const line of text.split("\n")) {
|
|
3896
3678
|
const t = line.trim();
|
|
3897
3679
|
if (!t || t.startsWith("#") || t.startsWith("-")) continue;
|
|
@@ -3975,7 +3757,7 @@ function goDeps(files) {
|
|
|
3975
3757
|
for (const mod of files) {
|
|
3976
3758
|
let text;
|
|
3977
3759
|
try {
|
|
3978
|
-
text =
|
|
3760
|
+
text = fs17.readFileSync(mod, "utf8");
|
|
3979
3761
|
} catch {
|
|
3980
3762
|
continue;
|
|
3981
3763
|
}
|
|
@@ -3993,7 +3775,7 @@ function cargoDeps(files) {
|
|
|
3993
3775
|
for (const file of files) {
|
|
3994
3776
|
let text;
|
|
3995
3777
|
try {
|
|
3996
|
-
text =
|
|
3778
|
+
text = fs17.readFileSync(file, "utf8");
|
|
3997
3779
|
} catch {
|
|
3998
3780
|
continue;
|
|
3999
3781
|
}
|
|
@@ -4025,7 +3807,7 @@ function rubyDeps(files) {
|
|
|
4025
3807
|
for (const file of files) {
|
|
4026
3808
|
let text;
|
|
4027
3809
|
try {
|
|
4028
|
-
text =
|
|
3810
|
+
text = fs17.readFileSync(file, "utf8");
|
|
4029
3811
|
} catch {
|
|
4030
3812
|
continue;
|
|
4031
3813
|
}
|
|
@@ -4040,7 +3822,7 @@ function phpDeps(files) {
|
|
|
4040
3822
|
for (const file of files) {
|
|
4041
3823
|
let pkg;
|
|
4042
3824
|
try {
|
|
4043
|
-
pkg = JSON.parse(
|
|
3825
|
+
pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
4044
3826
|
} catch {
|
|
4045
3827
|
continue;
|
|
4046
3828
|
}
|
|
@@ -4057,7 +3839,7 @@ function dotnetDeps(files) {
|
|
|
4057
3839
|
for (const file of files) {
|
|
4058
3840
|
let text;
|
|
4059
3841
|
try {
|
|
4060
|
-
text =
|
|
3842
|
+
text = fs17.readFileSync(file, "utf8");
|
|
4061
3843
|
} catch {
|
|
4062
3844
|
continue;
|
|
4063
3845
|
}
|
|
@@ -4077,7 +3859,7 @@ function swiftDeps(files) {
|
|
|
4077
3859
|
for (const file of files) {
|
|
4078
3860
|
let text;
|
|
4079
3861
|
try {
|
|
4080
|
-
text =
|
|
3862
|
+
text = fs17.readFileSync(file, "utf8");
|
|
4081
3863
|
} catch {
|
|
4082
3864
|
continue;
|
|
4083
3865
|
}
|
|
@@ -4099,7 +3881,7 @@ function dartDeps(files) {
|
|
|
4099
3881
|
for (const file of files) {
|
|
4100
3882
|
let text;
|
|
4101
3883
|
try {
|
|
4102
|
-
text =
|
|
3884
|
+
text = fs17.readFileSync(file, "utf8");
|
|
4103
3885
|
} catch {
|
|
4104
3886
|
continue;
|
|
4105
3887
|
}
|
|
@@ -4124,11 +3906,11 @@ function javaDeps(files) {
|
|
|
4124
3906
|
for (const file of files) {
|
|
4125
3907
|
let text;
|
|
4126
3908
|
try {
|
|
4127
|
-
text =
|
|
3909
|
+
text = fs17.readFileSync(file, "utf8");
|
|
4128
3910
|
} catch {
|
|
4129
3911
|
continue;
|
|
4130
3912
|
}
|
|
4131
|
-
if (
|
|
3913
|
+
if (path17.basename(file) === "pom.xml") {
|
|
4132
3914
|
for (const block of text.match(/<dependency>[\s\S]*?<\/dependency>/g) ?? []) {
|
|
4133
3915
|
const g = /<groupId>([^<]+)<\/groupId>/.exec(block);
|
|
4134
3916
|
const a = /<artifactId>([^<]+)<\/artifactId>/.exec(block);
|
|
@@ -4174,10 +3956,10 @@ function discoverModels(home = os.homedir()) {
|
|
|
4174
3956
|
);
|
|
4175
3957
|
}
|
|
4176
3958
|
function ollama(home) {
|
|
4177
|
-
const base =
|
|
3959
|
+
const base = path17.join(home, ".ollama", "models", "manifests");
|
|
4178
3960
|
const out = [];
|
|
4179
3961
|
walk(base, 4, (file) => {
|
|
4180
|
-
const rel2 =
|
|
3962
|
+
const rel2 = path17.relative(base, file).split(path17.sep);
|
|
4181
3963
|
if (rel2.length >= 2) {
|
|
4182
3964
|
const tag = rel2[rel2.length - 1];
|
|
4183
3965
|
const model = rel2[rel2.length - 2];
|
|
@@ -4187,24 +3969,24 @@ function ollama(home) {
|
|
|
4187
3969
|
return dedupe(out);
|
|
4188
3970
|
}
|
|
4189
3971
|
function lmStudio(home) {
|
|
4190
|
-
const bases = [
|
|
3972
|
+
const bases = [path17.join(home, ".lmstudio", "models"), path17.join(home, ".cache", "lm-studio", "models")];
|
|
4191
3973
|
const out = [];
|
|
4192
3974
|
for (const base of bases) {
|
|
4193
3975
|
walk(base, 5, (file) => {
|
|
4194
3976
|
if (!file.endsWith(".gguf")) return;
|
|
4195
|
-
const rel2 =
|
|
3977
|
+
const rel2 = path17.relative(base, file).replace(/\\/g, "/");
|
|
4196
3978
|
out.push({ runtime: "lm-studio", name: rel2, path: file });
|
|
4197
3979
|
});
|
|
4198
3980
|
}
|
|
4199
3981
|
return dedupe(out);
|
|
4200
3982
|
}
|
|
4201
3983
|
function looseGguf(home) {
|
|
4202
|
-
const bases = [
|
|
3984
|
+
const bases = [path17.join(home, "models"), path17.join(home, ".cache", "huggingface")];
|
|
4203
3985
|
const out = [];
|
|
4204
3986
|
for (const base of bases) {
|
|
4205
3987
|
walk(base, 4, (file) => {
|
|
4206
3988
|
if (!file.endsWith(".gguf")) return;
|
|
4207
|
-
out.push({ runtime: "gguf", name:
|
|
3989
|
+
out.push({ runtime: "gguf", name: path17.basename(file), path: file });
|
|
4208
3990
|
});
|
|
4209
3991
|
}
|
|
4210
3992
|
return dedupe(out);
|
|
@@ -4213,12 +3995,12 @@ function walk(dir, depth, onFile) {
|
|
|
4213
3995
|
if (depth < 0) return;
|
|
4214
3996
|
let entries;
|
|
4215
3997
|
try {
|
|
4216
|
-
entries =
|
|
3998
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
4217
3999
|
} catch {
|
|
4218
4000
|
return;
|
|
4219
4001
|
}
|
|
4220
4002
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4221
|
-
const abs =
|
|
4003
|
+
const abs = path17.join(dir, e.name);
|
|
4222
4004
|
if (e.isDirectory()) walk(abs, depth - 1, onFile);
|
|
4223
4005
|
else if (e.isFile()) onFile(abs);
|
|
4224
4006
|
}
|
|
@@ -4249,7 +4031,7 @@ function lockfileVersion(root, ecosystem, name) {
|
|
|
4249
4031
|
function gradleLock(root, name) {
|
|
4250
4032
|
let text;
|
|
4251
4033
|
try {
|
|
4252
|
-
text =
|
|
4034
|
+
text = fs17.readFileSync(path17.join(root, "gradle.lockfile"), "utf8");
|
|
4253
4035
|
} catch {
|
|
4254
4036
|
return void 0;
|
|
4255
4037
|
}
|
|
@@ -4259,7 +4041,7 @@ function gradleLock(root, name) {
|
|
|
4259
4041
|
function packageResolved(root, name) {
|
|
4260
4042
|
let data;
|
|
4261
4043
|
try {
|
|
4262
|
-
data = JSON.parse(
|
|
4044
|
+
data = JSON.parse(fs17.readFileSync(path17.join(root, "Package.resolved"), "utf8"));
|
|
4263
4045
|
} catch {
|
|
4264
4046
|
return void 0;
|
|
4265
4047
|
}
|
|
@@ -4275,7 +4057,7 @@ function packageResolved(root, name) {
|
|
|
4275
4057
|
function pubspecLock(root, name) {
|
|
4276
4058
|
let text;
|
|
4277
4059
|
try {
|
|
4278
|
-
text =
|
|
4060
|
+
text = fs17.readFileSync(path17.join(root, "pubspec.lock"), "utf8");
|
|
4279
4061
|
} catch {
|
|
4280
4062
|
return void 0;
|
|
4281
4063
|
}
|
|
@@ -4299,7 +4081,7 @@ function escapeRegExp(s) {
|
|
|
4299
4081
|
function gemfileLock(root, name) {
|
|
4300
4082
|
let text;
|
|
4301
4083
|
try {
|
|
4302
|
-
text =
|
|
4084
|
+
text = fs17.readFileSync(path17.join(root, "Gemfile.lock"), "utf8");
|
|
4303
4085
|
} catch {
|
|
4304
4086
|
return void 0;
|
|
4305
4087
|
}
|
|
@@ -4309,7 +4091,7 @@ function gemfileLock(root, name) {
|
|
|
4309
4091
|
function composerLock(root, name) {
|
|
4310
4092
|
let data;
|
|
4311
4093
|
try {
|
|
4312
|
-
data = JSON.parse(
|
|
4094
|
+
data = JSON.parse(fs17.readFileSync(path17.join(root, "composer.lock"), "utf8"));
|
|
4313
4095
|
} catch {
|
|
4314
4096
|
return void 0;
|
|
4315
4097
|
}
|
|
@@ -4324,7 +4106,7 @@ function composerLock(root, name) {
|
|
|
4324
4106
|
function packagesLock(root, name) {
|
|
4325
4107
|
let data;
|
|
4326
4108
|
try {
|
|
4327
|
-
data = JSON.parse(
|
|
4109
|
+
data = JSON.parse(fs17.readFileSync(path17.join(root, "packages.lock.json"), "utf8"));
|
|
4328
4110
|
} catch {
|
|
4329
4111
|
return void 0;
|
|
4330
4112
|
}
|
|
@@ -4349,7 +4131,7 @@ function pypiLockVersion(root, name) {
|
|
|
4349
4131
|
function tomlPackageLock(root, file, name, normalize3) {
|
|
4350
4132
|
let text;
|
|
4351
4133
|
try {
|
|
4352
|
-
text =
|
|
4134
|
+
text = fs17.readFileSync(path17.join(root, file), "utf8");
|
|
4353
4135
|
} catch {
|
|
4354
4136
|
return void 0;
|
|
4355
4137
|
}
|
|
@@ -4366,7 +4148,7 @@ function tomlPackageLock(root, file, name, normalize3) {
|
|
|
4366
4148
|
function pipfileLock(root, name) {
|
|
4367
4149
|
let data;
|
|
4368
4150
|
try {
|
|
4369
|
-
data = JSON.parse(
|
|
4151
|
+
data = JSON.parse(fs17.readFileSync(path17.join(root, "Pipfile.lock"), "utf8"));
|
|
4370
4152
|
} catch {
|
|
4371
4153
|
return void 0;
|
|
4372
4154
|
}
|
|
@@ -4385,7 +4167,7 @@ function pipfileLock(root, name) {
|
|
|
4385
4167
|
function packageLockVersion(root, name) {
|
|
4386
4168
|
let data;
|
|
4387
4169
|
try {
|
|
4388
|
-
data = JSON.parse(
|
|
4170
|
+
data = JSON.parse(fs17.readFileSync(path17.join(root, "package-lock.json"), "utf8"));
|
|
4389
4171
|
} catch {
|
|
4390
4172
|
return void 0;
|
|
4391
4173
|
}
|
|
@@ -4397,7 +4179,7 @@ function packageLockVersion(root, name) {
|
|
|
4397
4179
|
function yarnLockVersion(root, name) {
|
|
4398
4180
|
let text;
|
|
4399
4181
|
try {
|
|
4400
|
-
text =
|
|
4182
|
+
text = fs17.readFileSync(path17.join(root, "yarn.lock"), "utf8");
|
|
4401
4183
|
} catch {
|
|
4402
4184
|
return void 0;
|
|
4403
4185
|
}
|
|
@@ -4425,19 +4207,19 @@ function headerNamesPackage(header, name) {
|
|
|
4425
4207
|
// src/engine/lib.ts
|
|
4426
4208
|
var LIB_SCHEMA = "vg-lib/1.0";
|
|
4427
4209
|
function catalogPath(root) {
|
|
4428
|
-
return
|
|
4210
|
+
return path17.join(root, "vibgrate.lib.json");
|
|
4429
4211
|
}
|
|
4430
4212
|
function libDir(root) {
|
|
4431
|
-
return
|
|
4213
|
+
return path17.join(root, ".vibgrate", "lib");
|
|
4432
4214
|
}
|
|
4433
4215
|
function libId(name) {
|
|
4434
4216
|
return name.trim().toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
4435
4217
|
}
|
|
4436
4218
|
function loadCatalog(root) {
|
|
4437
4219
|
const file = catalogPath(root);
|
|
4438
|
-
if (
|
|
4220
|
+
if (fs17.existsSync(file)) {
|
|
4439
4221
|
try {
|
|
4440
|
-
const data = JSON.parse(
|
|
4222
|
+
const data = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
4441
4223
|
if (data.schemaVersion === LIB_SCHEMA && data.libraries) return data;
|
|
4442
4224
|
} catch {
|
|
4443
4225
|
}
|
|
@@ -4445,7 +4227,7 @@ function loadCatalog(root) {
|
|
|
4445
4227
|
return { schemaVersion: LIB_SCHEMA, libraries: {} };
|
|
4446
4228
|
}
|
|
4447
4229
|
function saveCatalog(root, catalog) {
|
|
4448
|
-
|
|
4230
|
+
fs17.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
|
|
4449
4231
|
`);
|
|
4450
4232
|
}
|
|
4451
4233
|
function resolveLib(catalog, name) {
|
|
@@ -4521,23 +4303,23 @@ async function addLibrary(source, opts) {
|
|
|
4521
4303
|
content = await res.text();
|
|
4522
4304
|
type = source.endsWith("llms.txt") ? "llms.txt" : source.endsWith(".json") ? "openapi" : "website";
|
|
4523
4305
|
} else {
|
|
4524
|
-
const abs =
|
|
4525
|
-
if (!
|
|
4306
|
+
const abs = path17.resolve(root, source);
|
|
4307
|
+
if (!fs17.existsSync(abs)) throw new Error(`source not found: ${source}`);
|
|
4526
4308
|
content = readLocal(abs);
|
|
4527
4309
|
type = "local";
|
|
4528
4310
|
}
|
|
4529
4311
|
const name = opts.name ?? inferName(source);
|
|
4530
4312
|
const id = libId(name);
|
|
4531
4313
|
const version = opts.version ?? installedVersion(root, name) ?? "*";
|
|
4532
|
-
|
|
4533
|
-
const docFileAbs =
|
|
4534
|
-
|
|
4314
|
+
fs17.mkdirSync(libDir(root), { recursive: true });
|
|
4315
|
+
const docFileAbs = path17.join(libDir(root), `${id}.md`);
|
|
4316
|
+
fs17.writeFileSync(docFileAbs, content);
|
|
4535
4317
|
const entry = {
|
|
4536
4318
|
id,
|
|
4537
4319
|
name,
|
|
4538
4320
|
version,
|
|
4539
4321
|
source: { type, location: source },
|
|
4540
|
-
docFile:
|
|
4322
|
+
docFile: path17.relative(root, docFileAbs).split(path17.sep).join("/"),
|
|
4541
4323
|
docHash: hashString(content),
|
|
4542
4324
|
bytes: Buffer.byteLength(content, "utf8")
|
|
4543
4325
|
};
|
|
@@ -4547,15 +4329,15 @@ async function addLibrary(source, opts) {
|
|
|
4547
4329
|
return entry;
|
|
4548
4330
|
}
|
|
4549
4331
|
function readDoc(root, entry) {
|
|
4550
|
-
const abs =
|
|
4551
|
-
return
|
|
4332
|
+
const abs = path17.resolve(root, entry.docFile);
|
|
4333
|
+
return fs17.existsSync(abs) ? fs17.readFileSync(abs, "utf8") : "";
|
|
4552
4334
|
}
|
|
4553
4335
|
function npmPackageDir(root, name) {
|
|
4554
4336
|
let cur = root;
|
|
4555
4337
|
for (let i = 0; i < 12; i++) {
|
|
4556
|
-
const dir =
|
|
4557
|
-
if (
|
|
4558
|
-
const parent =
|
|
4338
|
+
const dir = path17.join(cur, "node_modules", name);
|
|
4339
|
+
if (fs17.existsSync(path17.join(dir, "package.json"))) return dir;
|
|
4340
|
+
const parent = path17.dirname(cur);
|
|
4559
4341
|
if (parent === cur) break;
|
|
4560
4342
|
cur = parent;
|
|
4561
4343
|
}
|
|
@@ -4566,24 +4348,24 @@ function localPackageDocs(root, name) {
|
|
|
4566
4348
|
if (!dir) return void 0;
|
|
4567
4349
|
const version = resolveVersion(root, name).served;
|
|
4568
4350
|
for (const f of ["llms.txt", "README.md", "README.mdx", "README", "readme.md"]) {
|
|
4569
|
-
const p =
|
|
4351
|
+
const p = path17.join(dir, f);
|
|
4570
4352
|
try {
|
|
4571
|
-
const docs =
|
|
4353
|
+
const docs = fs17.readFileSync(p, "utf8");
|
|
4572
4354
|
if (docs.trim()) {
|
|
4573
|
-
return { docs, version, source:
|
|
4355
|
+
return { docs, version, source: path17.relative(root, p).split(path17.sep).join("/") };
|
|
4574
4356
|
}
|
|
4575
4357
|
} catch {
|
|
4576
4358
|
}
|
|
4577
4359
|
}
|
|
4578
4360
|
try {
|
|
4579
|
-
const pkg = JSON.parse(
|
|
4361
|
+
const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
|
|
4580
4362
|
if (pkg.description?.trim()) {
|
|
4581
4363
|
return {
|
|
4582
4364
|
docs: `# ${name}
|
|
4583
4365
|
|
|
4584
4366
|
${pkg.description}`,
|
|
4585
4367
|
version: version ?? pkg.version,
|
|
4586
|
-
source:
|
|
4368
|
+
source: path17.relative(root, path17.join(dir, "package.json")).split(path17.sep).join("/")
|
|
4587
4369
|
};
|
|
4588
4370
|
}
|
|
4589
4371
|
} catch {
|
|
@@ -4592,17 +4374,17 @@ ${pkg.description}`,
|
|
|
4592
4374
|
}
|
|
4593
4375
|
function dtsEntry(dir) {
|
|
4594
4376
|
try {
|
|
4595
|
-
const pkg = JSON.parse(
|
|
4377
|
+
const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
|
|
4596
4378
|
const rel2 = pkg.types ?? pkg.typings;
|
|
4597
4379
|
if (typeof rel2 === "string") {
|
|
4598
|
-
const p =
|
|
4599
|
-
if (
|
|
4380
|
+
const p = path17.join(dir, rel2);
|
|
4381
|
+
if (fs17.existsSync(p)) return p;
|
|
4600
4382
|
}
|
|
4601
4383
|
} catch {
|
|
4602
4384
|
}
|
|
4603
4385
|
for (const c of ["index.d.ts", "dist/index.d.ts", "lib/index.d.ts", "types/index.d.ts"]) {
|
|
4604
|
-
const p =
|
|
4605
|
-
if (
|
|
4386
|
+
const p = path17.join(dir, c);
|
|
4387
|
+
if (fs17.existsSync(p)) return p;
|
|
4606
4388
|
}
|
|
4607
4389
|
return void 0;
|
|
4608
4390
|
}
|
|
@@ -4635,7 +4417,7 @@ function localApiSurface(root, name) {
|
|
|
4635
4417
|
if (!dts) return void 0;
|
|
4636
4418
|
let src;
|
|
4637
4419
|
try {
|
|
4638
|
-
src =
|
|
4420
|
+
src = fs17.readFileSync(dts, "utf8");
|
|
4639
4421
|
} catch {
|
|
4640
4422
|
return void 0;
|
|
4641
4423
|
}
|
|
@@ -4643,36 +4425,36 @@ function localApiSurface(root, name) {
|
|
|
4643
4425
|
return decls.length ? decls.join("\n\n") : void 0;
|
|
4644
4426
|
}
|
|
4645
4427
|
function cloneAndReadGit(url) {
|
|
4646
|
-
const dir =
|
|
4428
|
+
const dir = fs17.mkdtempSync(path17.join(os.tmpdir(), "vg-lib-git-"));
|
|
4647
4429
|
try {
|
|
4648
4430
|
execFileSync("git", ["clone", "--depth", "1", "--quiet", url, dir], {
|
|
4649
4431
|
stdio: ["ignore", "ignore", "pipe"]
|
|
4650
4432
|
});
|
|
4651
4433
|
} catch (err) {
|
|
4652
|
-
|
|
4434
|
+
fs17.rmSync(dir, { recursive: true, force: true });
|
|
4653
4435
|
const detail = err instanceof Error && err.message ? `: ${err.message.split("\n")[0]}` : "";
|
|
4654
4436
|
throw new Error(`git clone failed for ${url}${detail}`);
|
|
4655
4437
|
}
|
|
4656
4438
|
try {
|
|
4657
4439
|
return readGitDocs(dir);
|
|
4658
4440
|
} finally {
|
|
4659
|
-
|
|
4441
|
+
fs17.rmSync(dir, { recursive: true, force: true });
|
|
4660
4442
|
}
|
|
4661
4443
|
}
|
|
4662
4444
|
function readGitDocs(dir) {
|
|
4663
|
-
const docsDir =
|
|
4664
|
-
if (
|
|
4665
|
-
const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) =>
|
|
4666
|
-
if (readme) return
|
|
4445
|
+
const docsDir = path17.join(dir, "docs");
|
|
4446
|
+
if (fs17.existsSync(docsDir) && fs17.statSync(docsDir).isDirectory()) return readLocal(docsDir);
|
|
4447
|
+
const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path17.join(dir, f)).find((p) => fs17.existsSync(p));
|
|
4448
|
+
if (readme) return fs17.readFileSync(readme, "utf8");
|
|
4667
4449
|
return readLocal(dir);
|
|
4668
4450
|
}
|
|
4669
4451
|
function readLocal(abs) {
|
|
4670
|
-
const stat =
|
|
4671
|
-
if (stat.isFile()) return
|
|
4452
|
+
const stat = fs17.statSync(abs);
|
|
4453
|
+
if (stat.isFile()) return fs17.readFileSync(abs, "utf8");
|
|
4672
4454
|
const parts = [];
|
|
4673
|
-
const files =
|
|
4455
|
+
const files = fs17.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
|
|
4674
4456
|
for (const f of files) parts.push(`<!-- ${f} -->
|
|
4675
|
-
${
|
|
4457
|
+
${fs17.readFileSync(path17.join(abs, f), "utf8")}`);
|
|
4676
4458
|
return parts.join("\n\n");
|
|
4677
4459
|
}
|
|
4678
4460
|
function inferName(source) {
|
|
@@ -4681,10 +4463,10 @@ function inferName(source) {
|
|
|
4681
4463
|
return base.replace(/\.(md|mdx|txt|rst|json)$/i, "").replace(/^llms$/, "docs");
|
|
4682
4464
|
}
|
|
4683
4465
|
function findGitRoot(startDir = process.cwd()) {
|
|
4684
|
-
let dir =
|
|
4466
|
+
let dir = path17.resolve(startDir);
|
|
4685
4467
|
for (; ; ) {
|
|
4686
|
-
if (
|
|
4687
|
-
const parent =
|
|
4468
|
+
if (fs17.existsSync(path17.join(dir, ".git"))) return dir;
|
|
4469
|
+
const parent = path17.dirname(dir);
|
|
4688
4470
|
if (parent === dir) return null;
|
|
4689
4471
|
dir = parent;
|
|
4690
4472
|
}
|
|
@@ -4695,19 +4477,19 @@ function normalizeEntry(line) {
|
|
|
4695
4477
|
function ensureGitignored(entry, startDir = process.cwd()) {
|
|
4696
4478
|
const root = findGitRoot(startDir);
|
|
4697
4479
|
if (!root) return { status: "not-a-repo", entry };
|
|
4698
|
-
const gitignorePath =
|
|
4480
|
+
const gitignorePath = path17.join(root, ".gitignore");
|
|
4699
4481
|
const target = normalizeEntry(entry);
|
|
4700
4482
|
let existing = "";
|
|
4701
4483
|
let fileExisted = true;
|
|
4702
4484
|
try {
|
|
4703
|
-
existing =
|
|
4485
|
+
existing = fs17.readFileSync(gitignorePath, "utf8");
|
|
4704
4486
|
} catch {
|
|
4705
4487
|
fileExisted = false;
|
|
4706
4488
|
}
|
|
4707
4489
|
const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
|
|
4708
4490
|
if (alreadyPresent) return { status: "present", entry, gitignorePath };
|
|
4709
4491
|
const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
|
|
4710
|
-
|
|
4492
|
+
fs17.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
|
|
4711
4493
|
`, "utf8");
|
|
4712
4494
|
return { status: fileExisted ? "added" : "created", entry, gitignorePath };
|
|
4713
4495
|
}
|
|
@@ -4861,14 +4643,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
4861
4643
|
console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
|
|
4862
4644
|
}
|
|
4863
4645
|
if (opts.write) {
|
|
4864
|
-
const writePath =
|
|
4646
|
+
const writePath = path17.resolve(opts.write);
|
|
4865
4647
|
await writeTextFile(writePath, dsn + "\n");
|
|
4866
4648
|
console.log("");
|
|
4867
4649
|
console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
|
|
4868
|
-
const root = findGitRoot(
|
|
4869
|
-
const rel2 = root ?
|
|
4650
|
+
const root = findGitRoot(path17.dirname(writePath));
|
|
4651
|
+
const rel2 = root ? path17.relative(root, writePath).split(path17.sep).join("/") : "";
|
|
4870
4652
|
if (root && rel2 && !rel2.startsWith("..")) {
|
|
4871
|
-
const res = ensureGitignored(rel2,
|
|
4653
|
+
const res = ensureGitignored(rel2, path17.dirname(writePath));
|
|
4872
4654
|
if (res.status === "created") {
|
|
4873
4655
|
console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
|
|
4874
4656
|
} else if (res.status === "added") {
|
|
@@ -4886,30 +4668,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
4886
4668
|
var STORE_DIRNAME = ".vibgrate";
|
|
4887
4669
|
var STORE_FILENAME = "credentials.json";
|
|
4888
4670
|
function homeCredentialsPath() {
|
|
4889
|
-
return
|
|
4671
|
+
return path17.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
|
|
4890
4672
|
}
|
|
4891
4673
|
function projectCredentialsPath(cwd = process.cwd()) {
|
|
4892
4674
|
const root = findGitRoot(cwd) ?? cwd;
|
|
4893
|
-
return
|
|
4675
|
+
return path17.join(root, STORE_DIRNAME, STORE_FILENAME);
|
|
4894
4676
|
}
|
|
4895
4677
|
function credentialsPath(opts = {}) {
|
|
4896
4678
|
const override = process.env.VIBGRATE_CREDENTIALS?.trim();
|
|
4897
|
-
if (override) return
|
|
4679
|
+
if (override) return path17.resolve(override);
|
|
4898
4680
|
const local = projectCredentialsPath(opts.cwd);
|
|
4899
4681
|
if (opts.local) return local;
|
|
4900
|
-
if (
|
|
4682
|
+
if (fs17.existsSync(local)) return local;
|
|
4901
4683
|
return homeCredentialsPath();
|
|
4902
4684
|
}
|
|
4903
4685
|
function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
|
|
4904
|
-
const rel2 =
|
|
4905
|
-
if (rel2 && !rel2.startsWith("..") && !
|
|
4906
|
-
return rel2.split(
|
|
4686
|
+
const rel2 = path17.relative(repoRoot, credsFile);
|
|
4687
|
+
if (rel2 && !rel2.startsWith("..") && !path17.isAbsolute(rel2)) {
|
|
4688
|
+
return rel2.split(path17.sep).join("/");
|
|
4907
4689
|
}
|
|
4908
4690
|
return `${STORE_DIRNAME}/${STORE_FILENAME}`;
|
|
4909
4691
|
}
|
|
4910
4692
|
function readStoredCredentials(opts = {}) {
|
|
4911
4693
|
try {
|
|
4912
|
-
const raw =
|
|
4694
|
+
const raw = fs17.readFileSync(credentialsPath(opts), "utf8");
|
|
4913
4695
|
const parsed = JSON.parse(raw);
|
|
4914
4696
|
return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
|
|
4915
4697
|
} catch {
|
|
@@ -4918,16 +4700,16 @@ function readStoredCredentials(opts = {}) {
|
|
|
4918
4700
|
}
|
|
4919
4701
|
function writeStoredCredentials(creds, opts = {}) {
|
|
4920
4702
|
const file = credentialsPath(opts);
|
|
4921
|
-
|
|
4922
|
-
|
|
4703
|
+
fs17.mkdirSync(path17.dirname(file), { recursive: true });
|
|
4704
|
+
fs17.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
4923
4705
|
try {
|
|
4924
|
-
|
|
4706
|
+
fs17.chmodSync(file, 384);
|
|
4925
4707
|
} catch {
|
|
4926
4708
|
}
|
|
4927
4709
|
}
|
|
4928
4710
|
function clearStoredCredentials(opts = {}) {
|
|
4929
4711
|
try {
|
|
4930
|
-
|
|
4712
|
+
fs17.rmSync(credentialsPath(opts));
|
|
4931
4713
|
return true;
|
|
4932
4714
|
} catch {
|
|
4933
4715
|
return false;
|
|
@@ -5298,7 +5080,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
5298
5080
|
if (opts.strict) process.exit(1);
|
|
5299
5081
|
return;
|
|
5300
5082
|
}
|
|
5301
|
-
const filePath =
|
|
5083
|
+
const filePath = path17.resolve(opts.file);
|
|
5302
5084
|
if (!await pathExists(filePath)) {
|
|
5303
5085
|
console.error(chalk.red(`Scan artifact not found: ${filePath}`));
|
|
5304
5086
|
console.error(chalk.dim('Run "vibgrate scan" first.'));
|
|
@@ -5361,8 +5143,8 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
5361
5143
|
});
|
|
5362
5144
|
function readScanArtifact(root) {
|
|
5363
5145
|
try {
|
|
5364
|
-
const file =
|
|
5365
|
-
return JSON.parse(
|
|
5146
|
+
const file = path17.join(root, ".vibgrate", "scan_result.json");
|
|
5147
|
+
return JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
5366
5148
|
} catch {
|
|
5367
5149
|
return null;
|
|
5368
5150
|
}
|
|
@@ -5752,7 +5534,7 @@ var CACHE_VERSION2 = "vg-hosted-docs/1";
|
|
|
5752
5534
|
var HOSTED_DOCS_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5753
5535
|
var MAX_ENTRIES = 64;
|
|
5754
5536
|
function cachePath2(root) {
|
|
5755
|
-
return
|
|
5537
|
+
return path17.join(cacheDir(root), "hosted-docs.json");
|
|
5756
5538
|
}
|
|
5757
5539
|
function hostedDocsCacheKey(req, opts = {}) {
|
|
5758
5540
|
const material = JSON.stringify([
|
|
@@ -5768,7 +5550,7 @@ function hostedDocsCacheKey(req, opts = {}) {
|
|
|
5768
5550
|
}
|
|
5769
5551
|
function loadFile(root) {
|
|
5770
5552
|
try {
|
|
5771
|
-
const parsed = JSON.parse(
|
|
5553
|
+
const parsed = JSON.parse(fs17.readFileSync(cachePath2(root), "utf8"));
|
|
5772
5554
|
if (parsed && parsed.version === CACHE_VERSION2 && parsed.entries && typeof parsed.entries === "object") return parsed;
|
|
5773
5555
|
} catch {
|
|
5774
5556
|
}
|
|
@@ -5782,8 +5564,8 @@ function saveFile(root, file) {
|
|
|
5782
5564
|
delete file.entries[k];
|
|
5783
5565
|
}
|
|
5784
5566
|
}
|
|
5785
|
-
|
|
5786
|
-
|
|
5567
|
+
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
5568
|
+
fs17.writeFileSync(cachePath2(root), JSON.stringify(file));
|
|
5787
5569
|
} catch {
|
|
5788
5570
|
}
|
|
5789
5571
|
}
|
|
@@ -5902,7 +5684,7 @@ function measure(root, files) {
|
|
|
5902
5684
|
for (const rel2 of files) {
|
|
5903
5685
|
let size;
|
|
5904
5686
|
try {
|
|
5905
|
-
size =
|
|
5687
|
+
size = fs17.statSync(path17.join(root, rel2)).size;
|
|
5906
5688
|
} catch {
|
|
5907
5689
|
continue;
|
|
5908
5690
|
}
|
|
@@ -5925,9 +5707,9 @@ function matchLines(text, needleLower) {
|
|
|
5925
5707
|
}
|
|
5926
5708
|
function readCandidate(abs, knownSize) {
|
|
5927
5709
|
try {
|
|
5928
|
-
const size = knownSize ??
|
|
5710
|
+
const size = knownSize ?? fs17.statSync(abs).size;
|
|
5929
5711
|
if (size > MAX_FILE_BYTES) return null;
|
|
5930
|
-
const text =
|
|
5712
|
+
const text = fs17.readFileSync(abs, "utf8");
|
|
5931
5713
|
return text.includes(NUL) ? null : text;
|
|
5932
5714
|
} catch {
|
|
5933
5715
|
return null;
|
|
@@ -5938,7 +5720,7 @@ function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
|
|
|
5938
5720
|
let total = 0;
|
|
5939
5721
|
let truncated = false;
|
|
5940
5722
|
for (const rel2 of files) {
|
|
5941
|
-
const text = readCandidate(
|
|
5723
|
+
const text = readCandidate(path17.join(root, rel2), sizes?.get(rel2));
|
|
5942
5724
|
if (text === null) continue;
|
|
5943
5725
|
for (const h of matchLines(text, needleLower)) {
|
|
5944
5726
|
total++;
|
|
@@ -6117,13 +5899,13 @@ function walkCandidates(root) {
|
|
|
6117
5899
|
const dir = stack.pop();
|
|
6118
5900
|
let entries;
|
|
6119
5901
|
try {
|
|
6120
|
-
entries =
|
|
5902
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
6121
5903
|
} catch {
|
|
6122
5904
|
continue;
|
|
6123
5905
|
}
|
|
6124
5906
|
for (const e of entries) {
|
|
6125
5907
|
if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
|
|
6126
|
-
const abs =
|
|
5908
|
+
const abs = path17.join(dir, e.name);
|
|
6127
5909
|
if (e.isDirectory()) {
|
|
6128
5910
|
stack.push(abs);
|
|
6129
5911
|
continue;
|
|
@@ -6132,7 +5914,7 @@ function walkCandidates(root) {
|
|
|
6132
5914
|
truncated = true;
|
|
6133
5915
|
continue;
|
|
6134
5916
|
}
|
|
6135
|
-
files.push(
|
|
5917
|
+
files.push(path17.relative(root, abs));
|
|
6136
5918
|
}
|
|
6137
5919
|
}
|
|
6138
5920
|
files.sort();
|
|
@@ -6848,211 +6630,19 @@ function navigationToolsetConfig(serverName = "vibgrate") {
|
|
|
6848
6630
|
configs: Object.fromEntries(HOT_TOOLS.map((n) => [n, { defer_loading: false }]))
|
|
6849
6631
|
};
|
|
6850
6632
|
}
|
|
6851
|
-
var PROBE_INTERVAL_MS = 2e3;
|
|
6852
|
-
var MAX_PROBE_INTERVAL_MS = 3e4;
|
|
6853
|
-
var PROBE_DUTY_FACTOR = 20;
|
|
6854
|
-
var REFRESH_BUDGET_MS = 5e3;
|
|
6855
|
-
var FAILURE_COOLDOWN_MS = 6e4;
|
|
6856
|
-
var GraphSource = class {
|
|
6857
|
-
constructor(graphPath, refresh = false, tuning = {}) {
|
|
6858
|
-
this.graphPath = graphPath;
|
|
6859
|
-
this.refresh = refresh;
|
|
6860
|
-
this.tuning = tuning;
|
|
6861
|
-
this.root = path19.dirname(path19.dirname(graphPath));
|
|
6862
|
-
}
|
|
6863
|
-
graphPath;
|
|
6864
|
-
refresh;
|
|
6865
|
-
tuning;
|
|
6866
|
-
cachedMtimeMs = -1;
|
|
6867
|
-
cached = null;
|
|
6868
|
-
root;
|
|
6869
|
-
lastProbeAt = 0;
|
|
6870
|
-
failedUntil = 0;
|
|
6871
|
-
inflight = null;
|
|
6872
|
-
/** Self-tuned: grows with measured probe cost so huge repos aren't penalized. */
|
|
6873
|
-
probeIntervalMs = PROBE_INTERVAL_MS;
|
|
6874
|
-
/** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
|
|
6875
|
-
async get() {
|
|
6876
|
-
if (this.refresh) await this.maybeRefresh();
|
|
6877
|
-
const stat = fs19.statSync(this.graphPath);
|
|
6878
|
-
if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
|
|
6879
|
-
this.cached = parseGraph(fs19.readFileSync(this.graphPath, "utf8"));
|
|
6880
|
-
this.cachedMtimeMs = stat.mtimeMs;
|
|
6881
|
-
}
|
|
6882
|
-
return this.cached;
|
|
6883
|
-
}
|
|
6884
|
-
/**
|
|
6885
|
-
* Debounced, single-flight refresh. Never throws — a refresh problem must
|
|
6886
|
-
* degrade to "answer from the current map", not break the tool call.
|
|
6887
|
-
*/
|
|
6888
|
-
async maybeRefresh() {
|
|
6889
|
-
const now = Date.now();
|
|
6890
|
-
if (!this.inflight) {
|
|
6891
|
-
const interval = this.tuning.probeIntervalMs ?? this.probeIntervalMs;
|
|
6892
|
-
if (now < this.failedUntil || now - this.lastProbeAt < interval) return;
|
|
6893
|
-
this.lastProbeAt = now;
|
|
6894
|
-
this.inflight = refreshIfStale(this.root).then((r) => {
|
|
6895
|
-
if (r.status === "error") this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
|
|
6896
|
-
if (r.status === "fresh" || r.status === "no-snapshot" || r.status === "locked") {
|
|
6897
|
-
const cost = Date.now() - now;
|
|
6898
|
-
this.probeIntervalMs = Math.min(
|
|
6899
|
-
MAX_PROBE_INTERVAL_MS,
|
|
6900
|
-
Math.max(PROBE_INTERVAL_MS, cost * PROBE_DUTY_FACTOR)
|
|
6901
|
-
);
|
|
6902
|
-
}
|
|
6903
|
-
}).catch(() => {
|
|
6904
|
-
this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
|
|
6905
|
-
}).finally(() => {
|
|
6906
|
-
this.inflight = null;
|
|
6907
|
-
});
|
|
6908
|
-
}
|
|
6909
|
-
await Promise.race([this.inflight, sleep(this.tuning.refreshBudgetMs ?? REFRESH_BUDGET_MS)]);
|
|
6910
|
-
}
|
|
6911
|
-
};
|
|
6912
|
-
function createServer(source, opts = {}) {
|
|
6913
|
-
const { savings = false, shareStats = false, local = false, dedup = false, stats } = opts;
|
|
6914
|
-
const record = savings || shareStats;
|
|
6915
|
-
const root = path19.dirname(path19.dirname(source.graphPath));
|
|
6916
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6917
|
-
const server = new Server(
|
|
6918
|
-
{ name: "vg", version: VERSION },
|
|
6919
|
-
{
|
|
6920
|
-
capabilities: { tools: {} },
|
|
6921
|
-
// Routing guidance once at the server level (hosts that surface
|
|
6922
|
-
// `instructions` get it at zero per-step schema cost): the flashlight
|
|
6923
|
-
// vs the map.
|
|
6924
|
-
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.'
|
|
6925
|
-
}
|
|
6926
|
-
);
|
|
6927
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
6928
|
-
tools: TOOLS.map((t) => ({
|
|
6929
|
-
name: t.name,
|
|
6930
|
-
description: t.description,
|
|
6931
|
-
inputSchema: t.inputSchema,
|
|
6932
|
-
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
6933
|
-
}))
|
|
6934
|
-
}));
|
|
6935
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
6936
|
-
const tool = TOOLS.find((t) => t.name === request.params.name);
|
|
6937
|
-
if (!tool) {
|
|
6938
|
-
return errorResult(`unknown tool "${request.params.name}"`);
|
|
6939
|
-
}
|
|
6940
|
-
let graph;
|
|
6941
|
-
try {
|
|
6942
|
-
graph = await source.get();
|
|
6943
|
-
} catch {
|
|
6944
|
-
return errorResult(
|
|
6945
|
-
"no code map found. Run `vg` in the project to build .vibgrate/graph.json, then retry."
|
|
6946
|
-
);
|
|
6947
|
-
}
|
|
6948
|
-
const startedAt = Date.now();
|
|
6949
|
-
try {
|
|
6950
|
-
const args = request.params.arguments ?? {};
|
|
6951
|
-
const result = await tool.handler(graph, args, { root, local, dedup, seen });
|
|
6952
|
-
const ms = Date.now() - startedAt;
|
|
6953
|
-
stats?.record({ ...measureCall(tool.name, result), client: sanitizeClient(detectClient(server)), ms });
|
|
6954
|
-
if (record) recordUsage(root, tool.name, result, detectClient(server), ms);
|
|
6955
|
-
return renderToolResult(result);
|
|
6956
|
-
} catch (err) {
|
|
6957
|
-
stats?.record({
|
|
6958
|
-
tool: tool.name,
|
|
6959
|
-
client: sanitizeClient(detectClient(server)),
|
|
6960
|
-
outcome: "miss",
|
|
6961
|
-
ms: Date.now() - startedAt,
|
|
6962
|
-
vgTokens: 0,
|
|
6963
|
-
baselineTokens: 0
|
|
6964
|
-
});
|
|
6965
|
-
return errorResult(`tool "${tool.name}" failed: ${err.message}`);
|
|
6966
|
-
}
|
|
6967
|
-
});
|
|
6968
|
-
return server;
|
|
6969
|
-
}
|
|
6970
|
-
async function serveStdio(graphPath, opts = {}) {
|
|
6971
|
-
const source = new GraphSource(graphPath, opts.refresh !== false);
|
|
6972
|
-
const server = createServer(source, opts);
|
|
6973
|
-
warmEmbedderInBackground(opts.local);
|
|
6974
|
-
await server.connect(new StdioServerTransport());
|
|
6975
|
-
}
|
|
6976
|
-
function sleep(ms) {
|
|
6977
|
-
return new Promise((resolve12) => {
|
|
6978
|
-
const timer = setTimeout(resolve12, ms);
|
|
6979
|
-
timer.unref?.();
|
|
6980
|
-
});
|
|
6981
|
-
}
|
|
6982
|
-
function recordUsage(root, tool, result, client, ms) {
|
|
6983
|
-
recordSaving(
|
|
6984
|
-
root,
|
|
6985
|
-
{ ...measureCall(tool, result), source: "mcp", client: sanitizeClient(client), ...ms !== void 0 ? { ms } : {} },
|
|
6986
|
-
Date.now()
|
|
6987
|
-
);
|
|
6988
|
-
}
|
|
6989
|
-
function measureCall(tool, result) {
|
|
6990
|
-
const outcome = classifyOutcome(result);
|
|
6991
|
-
let vgTokens = 0;
|
|
6992
|
-
let baselineTokens = 0;
|
|
6993
|
-
if (SAVINGS_TOOLS.has(tool) && result && typeof result === "object") {
|
|
6994
|
-
vgTokens = countTokens(renderedText(renderToolResult(result)));
|
|
6995
|
-
baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
|
|
6996
|
-
}
|
|
6997
|
-
return { tool, outcome, vgTokens, baselineTokens };
|
|
6998
|
-
}
|
|
6999
|
-
function detectClient(server) {
|
|
7000
|
-
try {
|
|
7001
|
-
const info = server.getClientVersion?.();
|
|
7002
|
-
return typeof info?.name === "string" ? info.name : void 0;
|
|
7003
|
-
} catch {
|
|
7004
|
-
return void 0;
|
|
7005
|
-
}
|
|
7006
|
-
}
|
|
7007
|
-
function classifyOutcome(result) {
|
|
7008
|
-
if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
|
|
7009
|
-
if (!result || typeof result !== "object") return "miss";
|
|
7010
|
-
const r = result;
|
|
7011
|
-
if (typeof r.error === "string" && r.error) return "miss";
|
|
7012
|
-
if (r.connected === false) return "miss";
|
|
7013
|
-
if (Array.isArray(r.matches) && r.matches.length === 0) return "miss";
|
|
7014
|
-
return isPartial(r) ? "partial" : "complete";
|
|
7015
|
-
}
|
|
7016
|
-
function isPartial(r) {
|
|
7017
|
-
if (r.moreAvailable === true) return true;
|
|
7018
|
-
if (r._truncated && typeof r._truncated === "object") return true;
|
|
7019
|
-
for (const [key, value] of Object.entries(r)) {
|
|
7020
|
-
if (typeof value !== "number" || !key.endsWith("Total")) continue;
|
|
7021
|
-
const base = key.slice(0, -"Total".length);
|
|
7022
|
-
const shown = Array.isArray(r[base]) ? r[base].length : 0;
|
|
7023
|
-
if (value > shown) return true;
|
|
7024
|
-
}
|
|
7025
|
-
return false;
|
|
7026
|
-
}
|
|
7027
|
-
function renderedText(rendered) {
|
|
7028
|
-
const block = rendered.content?.find((b) => b.type === "text");
|
|
7029
|
-
return block && "text" in block && typeof block.text === "string" ? block.text : "";
|
|
7030
|
-
}
|
|
7031
|
-
function referencedFiles(result) {
|
|
7032
|
-
const r = result;
|
|
7033
|
-
const files = /* @__PURE__ */ new Set();
|
|
7034
|
-
if (typeof r.file === "string" && r.file) files.add(r.file);
|
|
7035
|
-
for (const m of asArray(r.matches)) {
|
|
7036
|
-
const f = m.file;
|
|
7037
|
-
if (typeof f === "string" && f) files.add(f);
|
|
7038
|
-
}
|
|
7039
|
-
for (const name of [...asArray(r.calls), ...asArray(r.calledBy)]) {
|
|
7040
|
-
if (typeof name !== "string") continue;
|
|
7041
|
-
const i = name.indexOf(":");
|
|
7042
|
-
if (i > 0) files.add(name.slice(0, i));
|
|
7043
|
-
}
|
|
7044
|
-
return files;
|
|
7045
|
-
}
|
|
7046
|
-
function asArray(v) {
|
|
7047
|
-
return Array.isArray(v) ? v : [];
|
|
7048
|
-
}
|
|
7049
|
-
function errorResult(message) {
|
|
7050
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
7051
|
-
}
|
|
7052
6633
|
|
|
7053
6634
|
// src/install/content.ts
|
|
7054
6635
|
var NUDGE_BEGIN = "<!-- vg:begin -->";
|
|
7055
6636
|
var NUDGE_END = "<!-- vg:end -->";
|
|
6637
|
+
var INSTALL_CONTENT_VERSION = 2;
|
|
6638
|
+
var VERSION_MARKER_RE = /<!--\s*vg:v(\d+)\b[^>]*-->/;
|
|
6639
|
+
function versionMarker() {
|
|
6640
|
+
return `<!-- vg:v${INSTALL_CONTENT_VERSION} \xB7 managed by \`vg install\` \u2014 auto-refreshed when these instructions evolve; remove this line to opt out -->`;
|
|
6641
|
+
}
|
|
6642
|
+
function installedContentVersion(text) {
|
|
6643
|
+
const m = VERSION_MARKER_RE.exec(text);
|
|
6644
|
+
return m ? Number(m[1]) : null;
|
|
6645
|
+
}
|
|
7056
6646
|
function clientFlag(client) {
|
|
7057
6647
|
return client ? `--client=${client}` : "--client=<your-ai>";
|
|
7058
6648
|
}
|
|
@@ -7063,26 +6653,31 @@ name: vg
|
|
|
7063
6653
|
description: Query the local code graph (vg) for structure, impact, and navigation instead of grepping/reading many files.
|
|
7064
6654
|
---
|
|
7065
6655
|
|
|
6656
|
+
${versionMarker()}
|
|
6657
|
+
|
|
7066
6658
|
# vg \u2014 the code map
|
|
7067
6659
|
|
|
7068
6660
|
This repo has a deterministic code graph built by \`vg\`. Prefer it over reading or
|
|
7069
6661
|
grepping many files \u2014 it is smaller, more relevant context, and free.
|
|
7070
6662
|
|
|
7071
|
-
##
|
|
6663
|
+
## Use the MCP tools \u2014 not the CLI
|
|
7072
6664
|
|
|
7073
|
-
|
|
7074
|
-
read-only tools
|
|
7075
|
-
map parsed, the relation index warm, and the embedding model loaded across
|
|
7076
|
-
so
|
|
6665
|
+
When the \`vg\` MCP server is registered (it is after \`vg install\`), **always call
|
|
6666
|
+
its read-only tools instead of shelling out to the CLI.** The server keeps the
|
|
6667
|
+
map parsed, the relation index warm, and the embedding model loaded across
|
|
6668
|
+
calls, so an MCP call answers in milliseconds \u2014 while every CLI invocation pays
|
|
6669
|
+
Node startup plus a fresh map parse, hundreds of times more. Tools:
|
|
7077
6670
|
\`query_graph\`, \`get_node\`, \`impact_of\`, \`find_path\`, \`list_hubs\`, \`list_areas\`,
|
|
7078
6671
|
\`get_graph_summary\`, \`search_symbols\`. They are side-effect-free and
|
|
7079
6672
|
auto-approvable, and the server records which client is calling automatically.
|
|
6673
|
+
Reach for the CLI only when the MCP server is genuinely unavailable \u2014 never as
|
|
6674
|
+
the first resort.
|
|
7080
6675
|
|
|
7081
|
-
##
|
|
6676
|
+
## CLI fallback \u2014 only when the MCP server is unavailable
|
|
7082
6677
|
|
|
7083
|
-
|
|
7084
|
-
so your calls are counted (that's how the CLI-vs-MCP split is
|
|
7085
|
-
tools improved):
|
|
6678
|
+
If (and only if) no \`vg\` MCP server is available, use the CLI \u2014 and **always
|
|
6679
|
+
pass \`${cf}\`** so your calls are counted (that's how the CLI-vs-MCP split is
|
|
6680
|
+
measured and the tools improved):
|
|
7086
6681
|
|
|
7087
6682
|
- **Understand code:** \`vg "<question>" ${cf}\` \u2014 a budget-bounded, fact-annotated context block.
|
|
7088
6683
|
- **Find a symbol:** \`vg show <name> ${cf}\` \u2014 what it is, what it calls, what calls it.
|
|
@@ -7125,22 +6720,28 @@ function nudgeMarkdown(smallRepo, client) {
|
|
|
7125
6720
|
const cf = clientFlag(client);
|
|
7126
6721
|
if (smallRepo) {
|
|
7127
6722
|
return `${NUDGE_BEGIN}
|
|
6723
|
+
${versionMarker()}
|
|
7128
6724
|
## Code navigation (vg)
|
|
7129
6725
|
|
|
7130
6726
|
This repo is small enough that searching files directly is fine. Still, before a
|
|
7131
|
-
change, \`vg
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
counted
|
|
6727
|
+
change, the \`vg\` graph shows what breaks: use the \`vg\` MCP tools when the server
|
|
6728
|
+
is registered (\`impact_of\`, \`query_graph\` \u2014 the warm server answers far faster
|
|
6729
|
+
than spawning the CLI). Only without the server, fall back to the CLI and pass
|
|
6730
|
+
\`${cf}\` so the call is counted: \`vg impact <name> ${cf}\`,
|
|
6731
|
+
\`vg "<question>" ${cf}\`.
|
|
7135
6732
|
${NUDGE_END}`;
|
|
7136
6733
|
}
|
|
7137
6734
|
return `${NUDGE_BEGIN}
|
|
7138
|
-
|
|
6735
|
+
${versionMarker()}
|
|
6736
|
+
## Code navigation (vg) \u2014 use the MCP tools first
|
|
7139
6737
|
|
|
7140
6738
|
A deterministic code graph is available. It is usually the faster, cheaper path
|
|
7141
|
-
than reading or grepping many files.
|
|
7142
|
-
|
|
7143
|
-
|
|
6739
|
+
than reading or grepping many files. **When the \`vg\` MCP server is registered,
|
|
6740
|
+
always use its tools** (\`query_graph\`, \`get_node\`, \`impact_of\`,
|
|
6741
|
+
\`search_symbols\`, \u2026) \u2014 the map, indexes, and embedding model stay warm across
|
|
6742
|
+
calls, so they answer in milliseconds, while every CLI invocation pays Node
|
|
6743
|
+
startup plus a fresh map parse. Fall back to the CLI only when the server is
|
|
6744
|
+
unavailable, and then pass \`${cf}\` so the call is counted:
|
|
7144
6745
|
|
|
7145
6746
|
- \`vg "<question>" ${cf}\` \u2014 a compact, fact-annotated context block for a question.
|
|
7146
6747
|
- \`vg show <name> ${cf}\` \u2014 what a symbol is, its callers and callees.
|
|
@@ -7236,12 +6837,12 @@ function detectAssistants(root, opts = {}) {
|
|
|
7236
6837
|
const which = opts.which ?? whichOnPath;
|
|
7237
6838
|
const found = [];
|
|
7238
6839
|
for (const assistant of ASSISTANTS) {
|
|
7239
|
-
const repoHit = (assistant.markers ?? []).find((m) =>
|
|
6840
|
+
const repoHit = (assistant.markers ?? []).find((m) => fs17.existsSync(path17.join(root, m)));
|
|
7240
6841
|
if (repoHit) {
|
|
7241
6842
|
found.push({ assistant, via: "repo", marker: repoHit });
|
|
7242
6843
|
continue;
|
|
7243
6844
|
}
|
|
7244
|
-
const homeHit = (assistant.homeMarkers ?? []).find((m) =>
|
|
6845
|
+
const homeHit = (assistant.homeMarkers ?? []).find((m) => fs17.existsSync(path17.join(home, m)));
|
|
7245
6846
|
if (homeHit) {
|
|
7246
6847
|
found.push({ assistant, via: "home", marker: homeHit });
|
|
7247
6848
|
continue;
|
|
@@ -7273,19 +6874,19 @@ function installAssistant(a, opts) {
|
|
|
7273
6874
|
const skipped = [];
|
|
7274
6875
|
let note;
|
|
7275
6876
|
if (a.skill) {
|
|
7276
|
-
writeFileEnsured(
|
|
6877
|
+
writeFileEnsured(path17.join(opts.root, a.skill), skillMarkdown(a.id));
|
|
7277
6878
|
wrote.push(a.skill);
|
|
7278
6879
|
}
|
|
7279
6880
|
if (a.mcp) {
|
|
7280
6881
|
const launch = opts.launch ?? detectServeLaunch();
|
|
7281
|
-
upsertMcp(
|
|
6882
|
+
upsertMcp(path17.join(opts.root, a.mcp.file), a.mcp, launch);
|
|
7282
6883
|
wrote.push(a.mcp.file);
|
|
7283
6884
|
note = launch.note;
|
|
7284
6885
|
} else {
|
|
7285
6886
|
skipped.push("mcp (host-specific setup)");
|
|
7286
6887
|
}
|
|
7287
6888
|
if (a.nudge && opts.hook !== false) {
|
|
7288
|
-
writeNudge(
|
|
6889
|
+
writeNudge(path17.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
|
|
7289
6890
|
wrote.push(a.nudge.file);
|
|
7290
6891
|
} else if (a.nudge) {
|
|
7291
6892
|
skipped.push("nudge (--no-hook)");
|
|
@@ -7295,40 +6896,40 @@ function installAssistant(a, opts) {
|
|
|
7295
6896
|
function uninstallAssistant(a, root, purge) {
|
|
7296
6897
|
const removed = [];
|
|
7297
6898
|
if (a.mcp) {
|
|
7298
|
-
const file =
|
|
6899
|
+
const file = path17.join(root, a.mcp.file);
|
|
7299
6900
|
if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
|
|
7300
6901
|
}
|
|
7301
6902
|
if (a.nudge) {
|
|
7302
|
-
const file =
|
|
6903
|
+
const file = path17.join(root, a.nudge.file);
|
|
7303
6904
|
if (a.nudge.kind === "block") {
|
|
7304
6905
|
if (removeBlock(file)) removed.push(a.nudge.file);
|
|
7305
|
-
} else if (
|
|
7306
|
-
|
|
6906
|
+
} else if (fs17.existsSync(file)) {
|
|
6907
|
+
fs17.rmSync(file);
|
|
7307
6908
|
removed.push(a.nudge.file);
|
|
7308
6909
|
}
|
|
7309
6910
|
}
|
|
7310
6911
|
if (purge && a.skill) {
|
|
7311
|
-
const file =
|
|
7312
|
-
if (
|
|
7313
|
-
|
|
6912
|
+
const file = path17.join(root, a.skill);
|
|
6913
|
+
if (fs17.existsSync(file)) {
|
|
6914
|
+
fs17.rmSync(file);
|
|
7314
6915
|
removed.push(a.skill);
|
|
7315
6916
|
}
|
|
7316
6917
|
}
|
|
7317
6918
|
return removed;
|
|
7318
6919
|
}
|
|
7319
6920
|
function writeFileEnsured(file, content) {
|
|
7320
|
-
|
|
7321
|
-
|
|
6921
|
+
fs17.mkdirSync(path17.dirname(file), { recursive: true });
|
|
6922
|
+
fs17.writeFileSync(file, content);
|
|
7322
6923
|
}
|
|
7323
6924
|
function writeNavigationConfig(root) {
|
|
7324
|
-
const rel2 =
|
|
6925
|
+
const rel2 = path17.join(".vibgrate", "mcp-navigation.json");
|
|
7325
6926
|
const doc = {
|
|
7326
6927
|
_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.",
|
|
7327
6928
|
hot_core: [...HOT_TOOLS],
|
|
7328
6929
|
deferred: deferredToolNames(),
|
|
7329
6930
|
toolset: navigationToolsetConfig()
|
|
7330
6931
|
};
|
|
7331
|
-
writeFileEnsured(
|
|
6932
|
+
writeFileEnsured(path17.join(root, rel2), `${JSON.stringify(doc, null, 2)}
|
|
7332
6933
|
`);
|
|
7333
6934
|
return rel2;
|
|
7334
6935
|
}
|
|
@@ -7343,7 +6944,7 @@ function upsertMcp(file, target, launch) {
|
|
|
7343
6944
|
`);
|
|
7344
6945
|
}
|
|
7345
6946
|
function removeMcp(file, target) {
|
|
7346
|
-
if (!
|
|
6947
|
+
if (!fs17.existsSync(file)) return false;
|
|
7347
6948
|
const config = readJson(file);
|
|
7348
6949
|
const bag = config[target.key];
|
|
7349
6950
|
if (!bag || !(bag.vg !== void 0)) return false;
|
|
@@ -7352,6 +6953,57 @@ function removeMcp(file, target) {
|
|
|
7352
6953
|
`);
|
|
7353
6954
|
return true;
|
|
7354
6955
|
}
|
|
6956
|
+
var SMALL_REPO_FILES = 150;
|
|
6957
|
+
function refreshInstalledInstructions(root, smallRepo) {
|
|
6958
|
+
const out = [];
|
|
6959
|
+
const staleVersion = (text, legacySignature) => {
|
|
6960
|
+
const v = installedContentVersion(text);
|
|
6961
|
+
if (v !== null) return v < INSTALL_CONTENT_VERSION ? v : null;
|
|
6962
|
+
return text.includes(legacySignature) ? 0 : null;
|
|
6963
|
+
};
|
|
6964
|
+
for (const a of ASSISTANTS) {
|
|
6965
|
+
if (a.skill) {
|
|
6966
|
+
const file = path17.join(root, a.skill);
|
|
6967
|
+
if (fs17.existsSync(file)) {
|
|
6968
|
+
const text = readTextSafe(file);
|
|
6969
|
+
const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
|
|
6970
|
+
if (from !== null) {
|
|
6971
|
+
writeFileEnsured(file, skillMarkdown(a.id));
|
|
6972
|
+
out.push({ file: a.skill, from, to: INSTALL_CONTENT_VERSION });
|
|
6973
|
+
}
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6976
|
+
if (a.nudge) {
|
|
6977
|
+
const file = path17.join(root, a.nudge.file);
|
|
6978
|
+
if (!fs17.existsSync(file)) continue;
|
|
6979
|
+
const text = readTextSafe(file);
|
|
6980
|
+
if (text === null) continue;
|
|
6981
|
+
if (a.nudge.kind === "block") {
|
|
6982
|
+
const block = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`).exec(text);
|
|
6983
|
+
if (!block) continue;
|
|
6984
|
+
const from = staleVersion(block[0], "## Code navigation (vg)");
|
|
6985
|
+
if (from !== null) {
|
|
6986
|
+
writeNudge(file, a.nudge, smallRepo, a.id);
|
|
6987
|
+
out.push({ file: a.nudge.file, from, to: INSTALL_CONTENT_VERSION });
|
|
6988
|
+
}
|
|
6989
|
+
} else {
|
|
6990
|
+
const from = staleVersion(text, "## Code navigation (vg)");
|
|
6991
|
+
if (from !== null) {
|
|
6992
|
+
writeNudge(file, a.nudge, smallRepo, a.id);
|
|
6993
|
+
out.push({ file: a.nudge.file, from, to: INSTALL_CONTENT_VERSION });
|
|
6994
|
+
}
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
}
|
|
6998
|
+
return out;
|
|
6999
|
+
}
|
|
7000
|
+
function readTextSafe(file) {
|
|
7001
|
+
try {
|
|
7002
|
+
return fs17.readFileSync(file, "utf8");
|
|
7003
|
+
} catch {
|
|
7004
|
+
return null;
|
|
7005
|
+
}
|
|
7006
|
+
}
|
|
7355
7007
|
function writeNudge(file, target, smallRepo, client) {
|
|
7356
7008
|
if (target.kind === "file") {
|
|
7357
7009
|
const body = file.endsWith(".mdc") ? `---
|
|
@@ -7366,8 +7018,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
|
|
|
7366
7018
|
upsertBlock(file, nudgeMarkdown(smallRepo, client));
|
|
7367
7019
|
}
|
|
7368
7020
|
function upsertBlock(file, block) {
|
|
7369
|
-
|
|
7370
|
-
let existing =
|
|
7021
|
+
fs17.mkdirSync(path17.dirname(file), { recursive: true });
|
|
7022
|
+
let existing = fs17.existsSync(file) ? fs17.readFileSync(file, "utf8") : "";
|
|
7371
7023
|
const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
|
|
7372
7024
|
if (re.test(existing)) existing = existing.replace(re, block);
|
|
7373
7025
|
else existing = existing.length ? `${existing.replace(/\s*$/, "")}
|
|
@@ -7375,20 +7027,20 @@ function upsertBlock(file, block) {
|
|
|
7375
7027
|
${block}
|
|
7376
7028
|
` : `${block}
|
|
7377
7029
|
`;
|
|
7378
|
-
|
|
7030
|
+
fs17.writeFileSync(file, existing);
|
|
7379
7031
|
}
|
|
7380
7032
|
function removeBlock(file) {
|
|
7381
|
-
if (!
|
|
7382
|
-
const existing =
|
|
7033
|
+
if (!fs17.existsSync(file)) return false;
|
|
7034
|
+
const existing = fs17.readFileSync(file, "utf8");
|
|
7383
7035
|
const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
|
|
7384
7036
|
if (!re.test(existing)) return false;
|
|
7385
|
-
|
|
7037
|
+
fs17.writeFileSync(file, existing.replace(re, "\n"));
|
|
7386
7038
|
return true;
|
|
7387
7039
|
}
|
|
7388
7040
|
function readJson(file) {
|
|
7389
|
-
if (!
|
|
7041
|
+
if (!fs17.existsSync(file)) return {};
|
|
7390
7042
|
try {
|
|
7391
|
-
const parsed = JSON.parse(
|
|
7043
|
+
const parsed = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
7392
7044
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
7393
7045
|
return parsed;
|
|
7394
7046
|
}
|
|
@@ -7406,6 +7058,425 @@ function stripMarkers(s) {
|
|
|
7406
7058
|
function escapeRe(s) {
|
|
7407
7059
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7408
7060
|
}
|
|
7061
|
+
var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
7062
|
+
function refreshLockPath(root) {
|
|
7063
|
+
return path17.join(cacheDir(root), "refresh.lock");
|
|
7064
|
+
}
|
|
7065
|
+
async function refreshIfStale(root, opts = {}) {
|
|
7066
|
+
const probe = probeFreshness(root);
|
|
7067
|
+
if (!probe) return { status: "no-snapshot" };
|
|
7068
|
+
if (!hasDrift(probe.drift)) return { status: "fresh" };
|
|
7069
|
+
const lock = refreshLockPath(root);
|
|
7070
|
+
if (!acquireLock(lock, REFRESH_LOCK_STALE_MS)) return { status: "locked" };
|
|
7071
|
+
const start = process.hrtime.bigint();
|
|
7072
|
+
try {
|
|
7073
|
+
const { scope } = probe;
|
|
7074
|
+
const result = await buildGraph({
|
|
7075
|
+
root,
|
|
7076
|
+
only: scope.only,
|
|
7077
|
+
exclude: scope.exclude,
|
|
7078
|
+
paths: scope.paths,
|
|
7079
|
+
deep: scope.deep,
|
|
7080
|
+
noGround: scope.noGround,
|
|
7081
|
+
scip: scope.scip,
|
|
7082
|
+
noScip: scope.noScip,
|
|
7083
|
+
noTsc: scope.noTsc,
|
|
7084
|
+
cluster: scope.cluster,
|
|
7085
|
+
grammarsDir: scope.grammarsDir,
|
|
7086
|
+
inline: opts.inline,
|
|
7087
|
+
jobs: opts.jobs
|
|
7088
|
+
});
|
|
7089
|
+
const wrote = result.graph.provenance.corpusHash !== probe.corpusHash;
|
|
7090
|
+
if (wrote) {
|
|
7091
|
+
const dir = vibgrateDir(root);
|
|
7092
|
+
writeArtifacts(result.graph, {
|
|
7093
|
+
root,
|
|
7094
|
+
report: fs17.existsSync(path17.join(dir, "GRAPH_REPORT.md")),
|
|
7095
|
+
html: fs17.existsSync(path17.join(dir, "graph.html"))
|
|
7096
|
+
});
|
|
7097
|
+
}
|
|
7098
|
+
writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
|
|
7099
|
+
const ms = Number((process.hrtime.bigint() - start) / 1000000n);
|
|
7100
|
+
return {
|
|
7101
|
+
status: "refreshed",
|
|
7102
|
+
drift: probe.drift,
|
|
7103
|
+
ms,
|
|
7104
|
+
reparsed: result.reparsed,
|
|
7105
|
+
totalFiles: result.totalFiles,
|
|
7106
|
+
wrote
|
|
7107
|
+
};
|
|
7108
|
+
} catch (err) {
|
|
7109
|
+
return { status: "error", message: err.message };
|
|
7110
|
+
} finally {
|
|
7111
|
+
releaseLock(lock);
|
|
7112
|
+
}
|
|
7113
|
+
}
|
|
7114
|
+
var LEDGER = "savings.jsonl";
|
|
7115
|
+
var PER_FILE_TOKENS = 400;
|
|
7116
|
+
var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node"]);
|
|
7117
|
+
var CLI_TOOL_ALIASES = {
|
|
7118
|
+
ask: "query_graph",
|
|
7119
|
+
show: "get_node",
|
|
7120
|
+
impact: "impact_of",
|
|
7121
|
+
path: "find_path",
|
|
7122
|
+
hubs: "list_hubs",
|
|
7123
|
+
areas: "list_areas",
|
|
7124
|
+
tree: "tree"
|
|
7125
|
+
};
|
|
7126
|
+
function sanitizeClient(name) {
|
|
7127
|
+
if (typeof name !== "string") return "unknown";
|
|
7128
|
+
const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
7129
|
+
return cleaned || "unknown";
|
|
7130
|
+
}
|
|
7131
|
+
function recordCliCall(root, entry, now) {
|
|
7132
|
+
recordSaving(
|
|
7133
|
+
root,
|
|
7134
|
+
{
|
|
7135
|
+
tool: entry.tool,
|
|
7136
|
+
source: "cli",
|
|
7137
|
+
client: sanitizeClient(entry.client),
|
|
7138
|
+
outcome: entry.outcome,
|
|
7139
|
+
vgTokens: entry.vgTokens ?? 0,
|
|
7140
|
+
baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
|
|
7141
|
+
},
|
|
7142
|
+
now
|
|
7143
|
+
);
|
|
7144
|
+
}
|
|
7145
|
+
function savingsLedgerPath(root) {
|
|
7146
|
+
return path17.join(cacheDir(root), LEDGER);
|
|
7147
|
+
}
|
|
7148
|
+
function ledgerPath(root) {
|
|
7149
|
+
return savingsLedgerPath(root);
|
|
7150
|
+
}
|
|
7151
|
+
function savingsRecorded(root) {
|
|
7152
|
+
return fs17.existsSync(ledgerPath(root));
|
|
7153
|
+
}
|
|
7154
|
+
function recordSaving(root, entry, now) {
|
|
7155
|
+
try {
|
|
7156
|
+
fs17.mkdirSync(cacheDir(root), { recursive: true });
|
|
7157
|
+
const line = JSON.stringify({ ts: now, ...entry });
|
|
7158
|
+
fs17.appendFileSync(ledgerPath(root), line + "\n");
|
|
7159
|
+
} catch {
|
|
7160
|
+
}
|
|
7161
|
+
}
|
|
7162
|
+
var DEFAULT_RATE_PER_M = 3;
|
|
7163
|
+
var DEFAULT_RATE_LABEL = "input @ $3/1M";
|
|
7164
|
+
function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
|
|
7165
|
+
const file = ledgerPath(root);
|
|
7166
|
+
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
7167
|
+
let queries = 0;
|
|
7168
|
+
let vgTokens = 0;
|
|
7169
|
+
let baselineTokens = 0;
|
|
7170
|
+
if (fs17.existsSync(file)) {
|
|
7171
|
+
for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
|
|
7172
|
+
if (!line.trim()) continue;
|
|
7173
|
+
try {
|
|
7174
|
+
const e = JSON.parse(line);
|
|
7175
|
+
if (e.ts < cutoff) continue;
|
|
7176
|
+
if (!SAVINGS_TOOLS.has(e.tool)) continue;
|
|
7177
|
+
queries++;
|
|
7178
|
+
vgTokens += e.vgTokens;
|
|
7179
|
+
baselineTokens += e.baselineTokens;
|
|
7180
|
+
} catch {
|
|
7181
|
+
}
|
|
7182
|
+
}
|
|
7183
|
+
}
|
|
7184
|
+
const estCostVg = vgTokens / 1e6 * ratePerM;
|
|
7185
|
+
const estCostBaseline = baselineTokens / 1e6 * ratePerM;
|
|
7186
|
+
return {
|
|
7187
|
+
enabled: savingsRecorded(root),
|
|
7188
|
+
days,
|
|
7189
|
+
queries,
|
|
7190
|
+
vgTokens,
|
|
7191
|
+
baselineTokens,
|
|
7192
|
+
ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
|
|
7193
|
+
estCostVg: round22(estCostVg),
|
|
7194
|
+
estCostBaseline: round22(estCostBaseline),
|
|
7195
|
+
saved: round22(estCostBaseline - estCostVg),
|
|
7196
|
+
rateLabel: DEFAULT_RATE_LABEL
|
|
7197
|
+
};
|
|
7198
|
+
}
|
|
7199
|
+
function round22(x) {
|
|
7200
|
+
return Math.round(x * 100) / 100;
|
|
7201
|
+
}
|
|
7202
|
+
function toDimensionStats(byKey) {
|
|
7203
|
+
return [...byKey.entries()].map(([key, r]) => {
|
|
7204
|
+
const calls = r.complete + r.partial + r.miss;
|
|
7205
|
+
return {
|
|
7206
|
+
key,
|
|
7207
|
+
calls,
|
|
7208
|
+
complete: r.complete,
|
|
7209
|
+
partial: r.partial,
|
|
7210
|
+
miss: r.miss,
|
|
7211
|
+
successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
|
|
7212
|
+
};
|
|
7213
|
+
}).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
|
|
7214
|
+
}
|
|
7215
|
+
function readUsage(root, days, now) {
|
|
7216
|
+
const file = ledgerPath(root);
|
|
7217
|
+
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
7218
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
7219
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
7220
|
+
const byClient = /* @__PURE__ */ new Map();
|
|
7221
|
+
const msByTool = /* @__PURE__ */ new Map();
|
|
7222
|
+
const bump2 = (m, key, outcome) => {
|
|
7223
|
+
const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
|
|
7224
|
+
row[outcome]++;
|
|
7225
|
+
m.set(key, row);
|
|
7226
|
+
};
|
|
7227
|
+
if (fs17.existsSync(file)) {
|
|
7228
|
+
for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
|
|
7229
|
+
if (!line.trim()) continue;
|
|
7230
|
+
try {
|
|
7231
|
+
const e = JSON.parse(line);
|
|
7232
|
+
if (e.ts < cutoff) continue;
|
|
7233
|
+
const outcome = e.outcome ?? "complete";
|
|
7234
|
+
bump2(byTool, e.tool, outcome);
|
|
7235
|
+
bump2(bySource, e.source ?? "mcp", outcome);
|
|
7236
|
+
bump2(byClient, e.client ?? "unknown", outcome);
|
|
7237
|
+
if (typeof e.ms === "number" && e.ms >= 0) {
|
|
7238
|
+
const timing = msByTool.get(e.tool) ?? { sum: 0, n: 0 };
|
|
7239
|
+
timing.sum += e.ms;
|
|
7240
|
+
timing.n++;
|
|
7241
|
+
msByTool.set(e.tool, timing);
|
|
7242
|
+
}
|
|
7243
|
+
} catch {
|
|
7244
|
+
}
|
|
7245
|
+
}
|
|
7246
|
+
}
|
|
7247
|
+
const commands = toDimensionStats(byTool).map((d) => {
|
|
7248
|
+
const timing = msByTool.get(d.key);
|
|
7249
|
+
return {
|
|
7250
|
+
tool: d.key,
|
|
7251
|
+
calls: d.calls,
|
|
7252
|
+
complete: d.complete,
|
|
7253
|
+
partial: d.partial,
|
|
7254
|
+
miss: d.miss,
|
|
7255
|
+
successPct: d.successPct,
|
|
7256
|
+
avgMs: timing && timing.n > 0 ? Math.round(timing.sum / timing.n) : null
|
|
7257
|
+
};
|
|
7258
|
+
});
|
|
7259
|
+
const totals = commands.reduce(
|
|
7260
|
+
(t, c) => ({
|
|
7261
|
+
calls: t.calls + c.calls,
|
|
7262
|
+
complete: t.complete + c.complete,
|
|
7263
|
+
partial: t.partial + c.partial,
|
|
7264
|
+
miss: t.miss + c.miss
|
|
7265
|
+
}),
|
|
7266
|
+
{ calls: 0, complete: 0, partial: 0, miss: 0 }
|
|
7267
|
+
);
|
|
7268
|
+
const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
|
|
7269
|
+
return {
|
|
7270
|
+
enabled: savingsRecorded(root),
|
|
7271
|
+
days,
|
|
7272
|
+
commands,
|
|
7273
|
+
sources: toDimensionStats(bySource),
|
|
7274
|
+
clients: toDimensionStats(byClient),
|
|
7275
|
+
totals,
|
|
7276
|
+
avgSuccessPct
|
|
7277
|
+
};
|
|
7278
|
+
}
|
|
7279
|
+
var PROBE_INTERVAL_MS = 2e3;
|
|
7280
|
+
var MAX_PROBE_INTERVAL_MS = 3e4;
|
|
7281
|
+
var PROBE_DUTY_FACTOR = 20;
|
|
7282
|
+
var REFRESH_BUDGET_MS = 5e3;
|
|
7283
|
+
var FAILURE_COOLDOWN_MS = 6e4;
|
|
7284
|
+
var GraphSource = class {
|
|
7285
|
+
constructor(graphPath, refresh = false, tuning = {}) {
|
|
7286
|
+
this.graphPath = graphPath;
|
|
7287
|
+
this.refresh = refresh;
|
|
7288
|
+
this.tuning = tuning;
|
|
7289
|
+
this.root = path17.dirname(path17.dirname(graphPath));
|
|
7290
|
+
}
|
|
7291
|
+
graphPath;
|
|
7292
|
+
refresh;
|
|
7293
|
+
tuning;
|
|
7294
|
+
cachedMtimeMs = -1;
|
|
7295
|
+
cached = null;
|
|
7296
|
+
root;
|
|
7297
|
+
lastProbeAt = 0;
|
|
7298
|
+
failedUntil = 0;
|
|
7299
|
+
inflight = null;
|
|
7300
|
+
/** Self-tuned: grows with measured probe cost so huge repos aren't penalized. */
|
|
7301
|
+
probeIntervalMs = PROBE_INTERVAL_MS;
|
|
7302
|
+
/** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
|
|
7303
|
+
async get() {
|
|
7304
|
+
if (this.refresh) await this.maybeRefresh();
|
|
7305
|
+
const stat = fs17.statSync(this.graphPath);
|
|
7306
|
+
if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
|
|
7307
|
+
this.cached = parseGraph(fs17.readFileSync(this.graphPath, "utf8"));
|
|
7308
|
+
this.cachedMtimeMs = stat.mtimeMs;
|
|
7309
|
+
}
|
|
7310
|
+
return this.cached;
|
|
7311
|
+
}
|
|
7312
|
+
/**
|
|
7313
|
+
* Debounced, single-flight refresh. Never throws — a refresh problem must
|
|
7314
|
+
* degrade to "answer from the current map", not break the tool call.
|
|
7315
|
+
*/
|
|
7316
|
+
async maybeRefresh() {
|
|
7317
|
+
const now = Date.now();
|
|
7318
|
+
if (!this.inflight) {
|
|
7319
|
+
const interval = this.tuning.probeIntervalMs ?? this.probeIntervalMs;
|
|
7320
|
+
if (now < this.failedUntil || now - this.lastProbeAt < interval) return;
|
|
7321
|
+
this.lastProbeAt = now;
|
|
7322
|
+
this.inflight = refreshIfStale(this.root).then((r) => {
|
|
7323
|
+
if (r.status === "error") this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
|
|
7324
|
+
if (r.status === "fresh" || r.status === "no-snapshot" || r.status === "locked") {
|
|
7325
|
+
const cost = Date.now() - now;
|
|
7326
|
+
this.probeIntervalMs = Math.min(
|
|
7327
|
+
MAX_PROBE_INTERVAL_MS,
|
|
7328
|
+
Math.max(PROBE_INTERVAL_MS, cost * PROBE_DUTY_FACTOR)
|
|
7329
|
+
);
|
|
7330
|
+
}
|
|
7331
|
+
}).catch(() => {
|
|
7332
|
+
this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
|
|
7333
|
+
}).finally(() => {
|
|
7334
|
+
this.inflight = null;
|
|
7335
|
+
});
|
|
7336
|
+
}
|
|
7337
|
+
await Promise.race([this.inflight, sleep(this.tuning.refreshBudgetMs ?? REFRESH_BUDGET_MS)]);
|
|
7338
|
+
}
|
|
7339
|
+
};
|
|
7340
|
+
function createServer(source, opts = {}) {
|
|
7341
|
+
const { savings = false, shareStats = false, local = false, dedup = false, stats } = opts;
|
|
7342
|
+
const record = savings || shareStats;
|
|
7343
|
+
const root = path17.dirname(path17.dirname(source.graphPath));
|
|
7344
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7345
|
+
const server = new Server(
|
|
7346
|
+
{ name: "vg", version: VERSION },
|
|
7347
|
+
{
|
|
7348
|
+
capabilities: { tools: {} },
|
|
7349
|
+
// Routing guidance once at the server level (hosts that surface
|
|
7350
|
+
// `instructions` get it at zero per-step schema cost): the flashlight
|
|
7351
|
+
// vs the map.
|
|
7352
|
+
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.'
|
|
7353
|
+
}
|
|
7354
|
+
);
|
|
7355
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
7356
|
+
tools: TOOLS.map((t) => ({
|
|
7357
|
+
name: t.name,
|
|
7358
|
+
description: t.description,
|
|
7359
|
+
inputSchema: t.inputSchema,
|
|
7360
|
+
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
7361
|
+
}))
|
|
7362
|
+
}));
|
|
7363
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
7364
|
+
const tool = TOOLS.find((t) => t.name === request.params.name);
|
|
7365
|
+
if (!tool) {
|
|
7366
|
+
return errorResult(`unknown tool "${request.params.name}"`);
|
|
7367
|
+
}
|
|
7368
|
+
let graph;
|
|
7369
|
+
try {
|
|
7370
|
+
graph = await source.get();
|
|
7371
|
+
} catch {
|
|
7372
|
+
return errorResult(
|
|
7373
|
+
"no code map found. Run `vg` in the project to build .vibgrate/graph.json, then retry."
|
|
7374
|
+
);
|
|
7375
|
+
}
|
|
7376
|
+
const startedAt = Date.now();
|
|
7377
|
+
try {
|
|
7378
|
+
const args = request.params.arguments ?? {};
|
|
7379
|
+
const result = await tool.handler(graph, args, { root, local, dedup, seen });
|
|
7380
|
+
const ms = Date.now() - startedAt;
|
|
7381
|
+
stats?.record({ ...measureCall(tool.name, result), client: sanitizeClient(detectClient(server)), ms });
|
|
7382
|
+
if (record) recordUsage(root, tool.name, result, detectClient(server), ms);
|
|
7383
|
+
return renderToolResult(result);
|
|
7384
|
+
} catch (err) {
|
|
7385
|
+
stats?.record({
|
|
7386
|
+
tool: tool.name,
|
|
7387
|
+
client: sanitizeClient(detectClient(server)),
|
|
7388
|
+
outcome: "miss",
|
|
7389
|
+
ms: Date.now() - startedAt,
|
|
7390
|
+
vgTokens: 0,
|
|
7391
|
+
baselineTokens: 0
|
|
7392
|
+
});
|
|
7393
|
+
return errorResult(`tool "${tool.name}" failed: ${err.message}`);
|
|
7394
|
+
}
|
|
7395
|
+
});
|
|
7396
|
+
return server;
|
|
7397
|
+
}
|
|
7398
|
+
async function serveStdio(graphPath, opts = {}) {
|
|
7399
|
+
const source = new GraphSource(graphPath, opts.refresh !== false);
|
|
7400
|
+
const server = createServer(source, opts);
|
|
7401
|
+
warmEmbedderInBackground(opts.local);
|
|
7402
|
+
await server.connect(new StdioServerTransport());
|
|
7403
|
+
}
|
|
7404
|
+
function sleep(ms) {
|
|
7405
|
+
return new Promise((resolve12) => {
|
|
7406
|
+
const timer = setTimeout(resolve12, ms);
|
|
7407
|
+
timer.unref?.();
|
|
7408
|
+
});
|
|
7409
|
+
}
|
|
7410
|
+
function recordUsage(root, tool, result, client, ms) {
|
|
7411
|
+
recordSaving(
|
|
7412
|
+
root,
|
|
7413
|
+
{ ...measureCall(tool, result), source: "mcp", client: sanitizeClient(client), ...ms !== void 0 ? { ms } : {} },
|
|
7414
|
+
Date.now()
|
|
7415
|
+
);
|
|
7416
|
+
}
|
|
7417
|
+
function measureCall(tool, result) {
|
|
7418
|
+
const outcome = classifyOutcome(result);
|
|
7419
|
+
let vgTokens = 0;
|
|
7420
|
+
let baselineTokens = 0;
|
|
7421
|
+
if (SAVINGS_TOOLS.has(tool) && result && typeof result === "object") {
|
|
7422
|
+
vgTokens = countTokens(renderedText(renderToolResult(result)));
|
|
7423
|
+
baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
|
|
7424
|
+
}
|
|
7425
|
+
return { tool, outcome, vgTokens, baselineTokens };
|
|
7426
|
+
}
|
|
7427
|
+
function detectClient(server) {
|
|
7428
|
+
try {
|
|
7429
|
+
const info = server.getClientVersion?.();
|
|
7430
|
+
return typeof info?.name === "string" ? info.name : void 0;
|
|
7431
|
+
} catch {
|
|
7432
|
+
return void 0;
|
|
7433
|
+
}
|
|
7434
|
+
}
|
|
7435
|
+
function classifyOutcome(result) {
|
|
7436
|
+
if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
|
|
7437
|
+
if (!result || typeof result !== "object") return "miss";
|
|
7438
|
+
const r = result;
|
|
7439
|
+
if (typeof r.error === "string" && r.error) return "miss";
|
|
7440
|
+
if (r.connected === false) return "miss";
|
|
7441
|
+
if (Array.isArray(r.matches) && r.matches.length === 0) return "miss";
|
|
7442
|
+
return isPartial(r) ? "partial" : "complete";
|
|
7443
|
+
}
|
|
7444
|
+
function isPartial(r) {
|
|
7445
|
+
if (r.moreAvailable === true) return true;
|
|
7446
|
+
if (r._truncated && typeof r._truncated === "object") return true;
|
|
7447
|
+
for (const [key, value] of Object.entries(r)) {
|
|
7448
|
+
if (typeof value !== "number" || !key.endsWith("Total")) continue;
|
|
7449
|
+
const base = key.slice(0, -"Total".length);
|
|
7450
|
+
const shown = Array.isArray(r[base]) ? r[base].length : 0;
|
|
7451
|
+
if (value > shown) return true;
|
|
7452
|
+
}
|
|
7453
|
+
return false;
|
|
7454
|
+
}
|
|
7455
|
+
function renderedText(rendered) {
|
|
7456
|
+
const block = rendered.content?.find((b) => b.type === "text");
|
|
7457
|
+
return block && "text" in block && typeof block.text === "string" ? block.text : "";
|
|
7458
|
+
}
|
|
7459
|
+
function referencedFiles(result) {
|
|
7460
|
+
const r = result;
|
|
7461
|
+
const files = /* @__PURE__ */ new Set();
|
|
7462
|
+
if (typeof r.file === "string" && r.file) files.add(r.file);
|
|
7463
|
+
for (const m of asArray(r.matches)) {
|
|
7464
|
+
const f = m.file;
|
|
7465
|
+
if (typeof f === "string" && f) files.add(f);
|
|
7466
|
+
}
|
|
7467
|
+
for (const name of [...asArray(r.calls), ...asArray(r.calledBy)]) {
|
|
7468
|
+
if (typeof name !== "string") continue;
|
|
7469
|
+
const i = name.indexOf(":");
|
|
7470
|
+
if (i > 0) files.add(name.slice(0, i));
|
|
7471
|
+
}
|
|
7472
|
+
return files;
|
|
7473
|
+
}
|
|
7474
|
+
function asArray(v) {
|
|
7475
|
+
return Array.isArray(v) ? v : [];
|
|
7476
|
+
}
|
|
7477
|
+
function errorResult(message) {
|
|
7478
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
7479
|
+
}
|
|
7409
7480
|
|
|
7410
7481
|
// src/engine/export.ts
|
|
7411
7482
|
function formatForExt(ext) {
|
|
@@ -7610,6 +7681,6 @@ function spdx(ctx) {
|
|
|
7610
7681
|
return JSON.stringify(doc, null, 2) + "\n";
|
|
7611
7682
|
}
|
|
7612
7683
|
|
|
7613
|
-
export { ASSISTANTS, CLI_TOOL_ALIASES, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SAVINGS_TOOLS, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, countTokens, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectAssistants, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocsCached, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, identifierParts, impactOf, indexFor, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, readStoredCredentials, readUsage, recordCliCall, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, sanitizeClient, saveCatalog, savingsLedgerPath, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
|
|
7614
|
-
//# sourceMappingURL=chunk-
|
|
7615
|
-
//# sourceMappingURL=chunk-
|
|
7684
|
+
export { ASSISTANTS, CLI_TOOL_ALIASES, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SAVINGS_TOOLS, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, countTokens, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectAssistants, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocsCached, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, identifierParts, impactOf, indexFor, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, readStoredCredentials, readUsage, recordCliCall, recordSaving, refreshIfStale, refreshInstalledInstructions, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, sanitizeClient, saveCatalog, savingsLedgerPath, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
|
|
7685
|
+
//# sourceMappingURL=chunk-ZFDVHKID.js.map
|
|
7686
|
+
//# sourceMappingURL=chunk-ZFDVHKID.js.map
|