@vibgrate/cli 2026.708.3 → 2026.709.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +451 -295
- package/README.md +26 -19
- package/dist/baseline-VGDQ33ZE.js +6 -0
- package/dist/{baseline-AUC3HAXW.js.map → baseline-VGDQ33ZE.js.map} +1 -1
- package/dist/{chunk-2DJ3UXG7.js → chunk-3TCE6IGZ.js} +721 -437
- package/dist/chunk-3TCE6IGZ.js.map +1 -0
- package/dist/chunk-FU4MOQTC.js +3735 -0
- package/dist/chunk-FU4MOQTC.js.map +1 -0
- package/dist/{chunk-QUELGOCR.js → chunk-PIWHQSAR.js} +4 -4
- package/dist/{chunk-QUELGOCR.js.map → chunk-PIWHQSAR.js.map} +1 -1
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +845 -532
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +2 -2
- package/package.json +4 -3
- package/skills/vg/SKILL.md +26 -15
- package/dist/baseline-AUC3HAXW.js +0 -6
- package/dist/chunk-2DJ3UXG7.js.map +0 -1
- package/dist/chunk-JXGVMXYE.js +0 -31
- package/dist/chunk-JXGVMXYE.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { hashString, langById, shortId, canonicalize, grammarSetVersion, hashBytes, langForExtension, setGrammarsOverride, parseSource } from './chunk-X5YT263H.js';
|
|
2
|
-
import { writeTextFile, pathExists, readJsonFile, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-
|
|
3
|
-
import * as
|
|
4
|
-
import { execFileSync } from 'child_process';
|
|
5
|
-
import * as
|
|
2
|
+
import { writeTextFile, pathExists, readJsonFile, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-PIWHQSAR.js';
|
|
3
|
+
import * as fs19 from 'fs';
|
|
4
|
+
import { spawnSync, execFileSync } from 'child_process';
|
|
5
|
+
import * as path19 from 'path';
|
|
6
6
|
import ignore from 'ignore';
|
|
7
7
|
import * as v8 from 'v8';
|
|
8
8
|
import Graph from 'graphology';
|
|
@@ -20,6 +20,7 @@ import chalk from 'chalk';
|
|
|
20
20
|
import * as zlib from 'zlib';
|
|
21
21
|
import { promisify } from 'util';
|
|
22
22
|
import { encode, decode } from 'gpt-tokenizer/encoding/cl100k_base';
|
|
23
|
+
import { Worker } from 'worker_threads';
|
|
23
24
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
24
25
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
25
26
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -38,9 +39,9 @@ function whichOnPath(cmd) {
|
|
|
38
39
|
}
|
|
39
40
|
function isOwnBinary(binPath) {
|
|
40
41
|
try {
|
|
41
|
-
const real =
|
|
42
|
+
const real = fs19.realpathSync(binPath);
|
|
42
43
|
if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
|
|
43
|
-
const head =
|
|
44
|
+
const head = fs19.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
|
|
44
45
|
return head.includes("@vibgrate/cli") || head.includes("vibgrate");
|
|
45
46
|
} catch {
|
|
46
47
|
return false;
|
|
@@ -50,7 +51,7 @@ function isEphemeralNpxBinary(binPath) {
|
|
|
50
51
|
const NPX_SEGMENT = /[\\/]_npx[\\/]/;
|
|
51
52
|
if (NPX_SEGMENT.test(binPath)) return true;
|
|
52
53
|
try {
|
|
53
|
-
return NPX_SEGMENT.test(
|
|
54
|
+
return NPX_SEGMENT.test(fs19.realpathSync(binPath));
|
|
54
55
|
} catch {
|
|
55
56
|
return false;
|
|
56
57
|
}
|
|
@@ -216,19 +217,19 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
|
216
217
|
"flake.lock"
|
|
217
218
|
]);
|
|
218
219
|
function toPosix(p) {
|
|
219
|
-
return p.split(
|
|
220
|
+
return p.split(path19.sep).join("/");
|
|
220
221
|
}
|
|
221
222
|
function buildRootIgnore(root, exclude) {
|
|
222
223
|
const ig = ignore();
|
|
223
|
-
const gitignorePath =
|
|
224
|
-
if (
|
|
225
|
-
ig.add(
|
|
224
|
+
const gitignorePath = path19.join(root, ".gitignore");
|
|
225
|
+
if (fs19.existsSync(gitignorePath)) {
|
|
226
|
+
ig.add(fs19.readFileSync(gitignorePath, "utf8"));
|
|
226
227
|
}
|
|
227
228
|
if (exclude.length) ig.add(exclude);
|
|
228
229
|
return ig;
|
|
229
230
|
}
|
|
230
231
|
function discover(options) {
|
|
231
|
-
const root =
|
|
232
|
+
const root = path19.resolve(options.root);
|
|
232
233
|
const onlyLangs = (options.only ?? []).filter(Boolean);
|
|
233
234
|
const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
|
|
234
235
|
for (const id of onlyLangs) {
|
|
@@ -237,27 +238,27 @@ function discover(options) {
|
|
|
237
238
|
}
|
|
238
239
|
}
|
|
239
240
|
const rootIg = buildRootIgnore(root, options.exclude ?? []);
|
|
240
|
-
const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) =>
|
|
241
|
+
const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path19.resolve(root, p)) : [root]).filter((p) => fs19.existsSync(p));
|
|
241
242
|
const found = /* @__PURE__ */ new Map();
|
|
242
243
|
const considerFile = (abs) => {
|
|
243
|
-
const rel2 = toPosix(
|
|
244
|
+
const rel2 = toPosix(path19.relative(root, abs));
|
|
244
245
|
if (rel2.startsWith("..")) return;
|
|
245
246
|
if (rel2 === "" || rootIg.ignores(rel2)) return;
|
|
246
|
-
if (SKIP_FILES.has(
|
|
247
|
-
const lang = langForExtension(
|
|
247
|
+
if (SKIP_FILES.has(path19.basename(abs).toLowerCase())) return;
|
|
248
|
+
const lang = langForExtension(path19.extname(abs));
|
|
248
249
|
if (!lang || !allowLang(lang)) return;
|
|
249
250
|
found.set(rel2, { rel: rel2, abs, lang });
|
|
250
251
|
};
|
|
251
252
|
const walk2 = (dir) => {
|
|
252
253
|
let entries;
|
|
253
254
|
try {
|
|
254
|
-
entries =
|
|
255
|
+
entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
255
256
|
} catch {
|
|
256
257
|
return;
|
|
257
258
|
}
|
|
258
259
|
for (const entry of entries) {
|
|
259
|
-
const abs =
|
|
260
|
-
const rel2 = toPosix(
|
|
260
|
+
const abs = path19.join(dir, entry.name);
|
|
261
|
+
const rel2 = toPosix(path19.relative(root, abs));
|
|
261
262
|
if (entry.isDirectory()) {
|
|
262
263
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
263
264
|
if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
|
|
@@ -268,7 +269,7 @@ function discover(options) {
|
|
|
268
269
|
}
|
|
269
270
|
};
|
|
270
271
|
for (const scope of scopeAbs) {
|
|
271
|
-
const stat =
|
|
272
|
+
const stat = fs19.statSync(scope);
|
|
272
273
|
if (stat.isDirectory()) walk2(scope);
|
|
273
274
|
else if (stat.isFile()) considerFile(scope);
|
|
274
275
|
}
|
|
@@ -434,10 +435,10 @@ function buildModuleResolver(root, relSet) {
|
|
|
434
435
|
resolve(fromRel, source) {
|
|
435
436
|
if (source.includes("\\")) source = source.split("\\").join(".");
|
|
436
437
|
if (source.startsWith("./") || source.startsWith("../")) {
|
|
437
|
-
return probe(posixJoin(
|
|
438
|
+
return probe(posixJoin(path19.posix.dirname(fromRel), source));
|
|
438
439
|
}
|
|
439
440
|
if (/\.[A-Za-z0-9]+$/.test(source) && !source.startsWith("<")) {
|
|
440
|
-
const hit = probe(posixJoin(
|
|
441
|
+
const hit = probe(posixJoin(path19.posix.dirname(fromRel), source));
|
|
441
442
|
if (hit) return hit;
|
|
442
443
|
}
|
|
443
444
|
if (source.startsWith(".")) {
|
|
@@ -508,8 +509,8 @@ function resolvePyRelative(probe, fromRel, source) {
|
|
|
508
509
|
let dots = 0;
|
|
509
510
|
while (source[dots] === ".") dots++;
|
|
510
511
|
const rest = source.slice(dots);
|
|
511
|
-
let dir =
|
|
512
|
-
for (let i = 1; i < dots; i++) dir =
|
|
512
|
+
let dir = path19.posix.dirname(fromRel);
|
|
513
|
+
for (let i = 1; i < dots; i++) dir = path19.posix.dirname(dir);
|
|
513
514
|
const sub = rest.replace(/\./g, "/");
|
|
514
515
|
return probe(sub ? posixJoin(dir, sub) : dir);
|
|
515
516
|
}
|
|
@@ -517,7 +518,7 @@ function relativeResolver(relSet) {
|
|
|
517
518
|
const probe = makeProbe(relSet);
|
|
518
519
|
return {
|
|
519
520
|
resolve(fromRel, source) {
|
|
520
|
-
if (source.startsWith(".")) return probe(posixJoin(
|
|
521
|
+
if (source.startsWith(".")) return probe(posixJoin(path19.posix.dirname(fromRel), source));
|
|
521
522
|
return resolveDotted(probe, source);
|
|
522
523
|
}
|
|
523
524
|
};
|
|
@@ -535,12 +536,12 @@ function makeProbe(relSet) {
|
|
|
535
536
|
};
|
|
536
537
|
}
|
|
537
538
|
function normalizeRel(p) {
|
|
538
|
-
const norm =
|
|
539
|
+
const norm = path19.posix.normalize(p);
|
|
539
540
|
const stripped = norm.startsWith("./") ? norm.slice(2) : norm;
|
|
540
541
|
return stripped.startsWith("..") ? "" : stripped;
|
|
541
542
|
}
|
|
542
543
|
function posixJoin(a, b) {
|
|
543
|
-
return
|
|
544
|
+
return path19.posix.normalize(path19.posix.join(a, b));
|
|
544
545
|
}
|
|
545
546
|
function matchPattern(pattern, source) {
|
|
546
547
|
const star = pattern.indexOf("*");
|
|
@@ -553,8 +554,8 @@ function matchPattern(pattern, source) {
|
|
|
553
554
|
}
|
|
554
555
|
function loadTsconfigPaths(root) {
|
|
555
556
|
for (const name of ["tsconfig.base.json", "tsconfig.json"]) {
|
|
556
|
-
const file =
|
|
557
|
-
if (
|
|
557
|
+
const file = path19.join(root, name);
|
|
558
|
+
if (fs19.existsSync(file)) {
|
|
558
559
|
const merged = readTsconfigChain(root, file, /* @__PURE__ */ new Set());
|
|
559
560
|
if (merged && merged.paths.length) return merged;
|
|
560
561
|
}
|
|
@@ -566,21 +567,21 @@ function readTsconfigChain(root, file, seen) {
|
|
|
566
567
|
seen.add(file);
|
|
567
568
|
let cfg;
|
|
568
569
|
try {
|
|
569
|
-
cfg = parseJsonc(
|
|
570
|
+
cfg = parseJsonc(fs19.readFileSync(file, "utf8"));
|
|
570
571
|
} catch {
|
|
571
572
|
return null;
|
|
572
573
|
}
|
|
573
|
-
const dir =
|
|
574
|
+
const dir = path19.dirname(file);
|
|
574
575
|
let base = { baseUrlRel: "", paths: [] };
|
|
575
576
|
if (cfg.extends && cfg.extends.startsWith(".")) {
|
|
576
|
-
const extPath =
|
|
577
|
-
if (
|
|
577
|
+
const extPath = path19.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
|
|
578
|
+
if (fs19.existsSync(extPath)) {
|
|
578
579
|
const inherited = readTsconfigChain(root, extPath, seen);
|
|
579
580
|
if (inherited) base = inherited;
|
|
580
581
|
}
|
|
581
582
|
}
|
|
582
583
|
const co = cfg.compilerOptions ?? {};
|
|
583
|
-
const baseUrlRel = co.baseUrl !== void 0 ?
|
|
584
|
+
const baseUrlRel = co.baseUrl !== void 0 ? path19.relative(root, path19.resolve(dir, co.baseUrl)).split(path19.sep).join("/") : base.baseUrlRel;
|
|
584
585
|
const paths = [...base.paths];
|
|
585
586
|
if (co.paths) for (const [k, v] of Object.entries(co.paths)) paths.push([k, v]);
|
|
586
587
|
return { baseUrlRel, paths };
|
|
@@ -588,27 +589,27 @@ function readTsconfigChain(root, file, seen) {
|
|
|
588
589
|
function loadWorkspacePackages(root) {
|
|
589
590
|
const map = /* @__PURE__ */ new Map();
|
|
590
591
|
const globs = [];
|
|
591
|
-
const rootPkg =
|
|
592
|
-
if (
|
|
592
|
+
const rootPkg = path19.join(root, "package.json");
|
|
593
|
+
if (fs19.existsSync(rootPkg)) {
|
|
593
594
|
try {
|
|
594
|
-
const pkg = JSON.parse(
|
|
595
|
+
const pkg = JSON.parse(fs19.readFileSync(rootPkg, "utf8"));
|
|
595
596
|
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
596
597
|
if (ws) globs.push(...ws);
|
|
597
598
|
} catch {
|
|
598
599
|
}
|
|
599
600
|
}
|
|
600
|
-
const pnpmWs =
|
|
601
|
-
if (
|
|
602
|
-
for (const line of
|
|
601
|
+
const pnpmWs = path19.join(root, "pnpm-workspace.yaml");
|
|
602
|
+
if (fs19.existsSync(pnpmWs)) {
|
|
603
|
+
for (const line of fs19.readFileSync(pnpmWs, "utf8").split("\n")) {
|
|
603
604
|
const m = /^\s*-\s*['"]?([^'"\n]+)['"]?\s*$/.exec(line);
|
|
604
605
|
if (m) globs.push(m[1].trim());
|
|
605
606
|
}
|
|
606
607
|
}
|
|
607
608
|
for (const dir of expandWorkspaceGlobs(root, globs)) {
|
|
608
|
-
const pj =
|
|
609
|
+
const pj = path19.join(dir, "package.json");
|
|
609
610
|
try {
|
|
610
|
-
const name = JSON.parse(
|
|
611
|
-
if (name) map.set(name,
|
|
611
|
+
const name = JSON.parse(fs19.readFileSync(pj, "utf8")).name;
|
|
612
|
+
if (name) map.set(name, path19.relative(root, dir).split(path19.sep).join("/"));
|
|
612
613
|
} catch {
|
|
613
614
|
}
|
|
614
615
|
}
|
|
@@ -618,10 +619,10 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
618
619
|
const out = /* @__PURE__ */ new Set();
|
|
619
620
|
for (const glob of globs) {
|
|
620
621
|
const clean = glob.replace(/\/\*\*$/, "").replace(/\/\*$/, "");
|
|
621
|
-
const baseDir =
|
|
622
|
+
const baseDir = path19.join(root, clean);
|
|
622
623
|
const recurse = glob.endsWith("**");
|
|
623
624
|
if (glob === clean) {
|
|
624
|
-
if (
|
|
625
|
+
if (fs19.existsSync(path19.join(baseDir, "package.json"))) out.add(baseDir);
|
|
625
626
|
continue;
|
|
626
627
|
}
|
|
627
628
|
collectPackageDirs(baseDir, recurse ? 4 : 1, out);
|
|
@@ -632,14 +633,14 @@ function collectPackageDirs(dir, depth, out) {
|
|
|
632
633
|
if (depth < 0) return;
|
|
633
634
|
let entries;
|
|
634
635
|
try {
|
|
635
|
-
entries =
|
|
636
|
+
entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
636
637
|
} catch {
|
|
637
638
|
return;
|
|
638
639
|
}
|
|
639
640
|
for (const e of entries) {
|
|
640
641
|
if (!e.isDirectory() || e.name === "node_modules" || e.name.startsWith(".")) continue;
|
|
641
|
-
const child =
|
|
642
|
-
if (
|
|
642
|
+
const child = path19.join(dir, e.name);
|
|
643
|
+
if (fs19.existsSync(path19.join(child, "package.json"))) out.add(child);
|
|
643
644
|
else collectPackageDirs(child, depth - 1, out);
|
|
644
645
|
}
|
|
645
646
|
}
|
|
@@ -1137,14 +1138,14 @@ var DEFAULT_PATHS = [
|
|
|
1137
1138
|
];
|
|
1138
1139
|
function loadCoverage(root, explicit) {
|
|
1139
1140
|
const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
|
|
1140
|
-
(p) =>
|
|
1141
|
+
(p) => path19.resolve(root, p)
|
|
1141
1142
|
);
|
|
1142
|
-
const found = candidates.filter((p) =>
|
|
1143
|
+
const found = candidates.filter((p) => fs19.existsSync(p));
|
|
1143
1144
|
if (found.length === 0) return null;
|
|
1144
1145
|
const map = /* @__PURE__ */ new Map();
|
|
1145
1146
|
for (const file of found) {
|
|
1146
1147
|
try {
|
|
1147
|
-
const text =
|
|
1148
|
+
const text = fs19.readFileSync(file, "utf8");
|
|
1148
1149
|
if (file.endsWith(".json")) mergeIstanbul(map, text, root);
|
|
1149
1150
|
else mergeLcov(map, text, root);
|
|
1150
1151
|
} catch {
|
|
@@ -1153,8 +1154,8 @@ function loadCoverage(root, explicit) {
|
|
|
1153
1154
|
return map.size ? map : null;
|
|
1154
1155
|
}
|
|
1155
1156
|
function rel(root, p) {
|
|
1156
|
-
const abs =
|
|
1157
|
-
return
|
|
1157
|
+
const abs = path19.isAbsolute(p) ? p : path19.resolve(root, p);
|
|
1158
|
+
return path19.relative(root, abs).split(path19.sep).join("/");
|
|
1158
1159
|
}
|
|
1159
1160
|
function bump(map, file, line, hits) {
|
|
1160
1161
|
let lh = map.get(file);
|
|
@@ -1490,7 +1491,7 @@ async function parseInline(files, options) {
|
|
|
1490
1491
|
onProgress?.(0, files.length);
|
|
1491
1492
|
for (const file of files) {
|
|
1492
1493
|
try {
|
|
1493
|
-
const source =
|
|
1494
|
+
const source = fs19.readFileSync(file.abs, "utf8");
|
|
1494
1495
|
out.push(await parseSource(file.rel, file.lang.id, source));
|
|
1495
1496
|
} catch (err) {
|
|
1496
1497
|
out.push(emptyParse(file, `parse failed: ${err.message}`));
|
|
@@ -1545,9 +1546,9 @@ function isWorkerOom(err) {
|
|
|
1545
1546
|
return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
|
|
1546
1547
|
}
|
|
1547
1548
|
function resolveWorkerFile() {
|
|
1548
|
-
const here =
|
|
1549
|
-
const candidate =
|
|
1550
|
-
return
|
|
1549
|
+
const here = path19.dirname(fileURLToPath(import.meta.url));
|
|
1550
|
+
const candidate = path19.join(here, "parse-worker.js");
|
|
1551
|
+
return fs19.existsSync(candidate) ? candidate : null;
|
|
1551
1552
|
}
|
|
1552
1553
|
function chunk(items, buckets) {
|
|
1553
1554
|
const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
|
|
@@ -1585,7 +1586,7 @@ function resolve4(parses, resolver) {
|
|
|
1585
1586
|
addNode(baseNodes, {
|
|
1586
1587
|
id: fileId,
|
|
1587
1588
|
kind: "file",
|
|
1588
|
-
name:
|
|
1589
|
+
name: path19.posix.basename(p.rel),
|
|
1589
1590
|
qualifiedName: p.rel,
|
|
1590
1591
|
file: p.rel,
|
|
1591
1592
|
span: { start: 1, end: 1 },
|
|
@@ -2002,14 +2003,14 @@ function enclosing3(nodes, line) {
|
|
|
2002
2003
|
return best;
|
|
2003
2004
|
}
|
|
2004
2005
|
function normalize2(p) {
|
|
2005
|
-
return
|
|
2006
|
+
return path19.resolve(p).split(path19.sep).join("/");
|
|
2006
2007
|
}
|
|
2007
2008
|
var CACHE_VERSION = "vg-parse-cache/2";
|
|
2008
2009
|
function cacheDir(root) {
|
|
2009
|
-
return
|
|
2010
|
+
return path19.join(root, ".vibgrate", "cache");
|
|
2010
2011
|
}
|
|
2011
2012
|
function cachePath(root) {
|
|
2012
|
-
return
|
|
2013
|
+
return path19.join(cacheDir(root), "parse-cache.json");
|
|
2013
2014
|
}
|
|
2014
2015
|
function loadCache(root, opts) {
|
|
2015
2016
|
const file = cachePath(root);
|
|
@@ -2019,9 +2020,9 @@ function loadCache(root, opts) {
|
|
|
2019
2020
|
grammars: opts.grammars,
|
|
2020
2021
|
entries: {}
|
|
2021
2022
|
};
|
|
2022
|
-
if (!opts.disabled &&
|
|
2023
|
+
if (!opts.disabled && fs19.existsSync(file)) {
|
|
2023
2024
|
try {
|
|
2024
|
-
const loaded = JSON.parse(
|
|
2025
|
+
const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
2025
2026
|
if (loaded.version === CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.grammars === opts.grammars && loaded.entries) {
|
|
2026
2027
|
data = loaded;
|
|
2027
2028
|
}
|
|
@@ -2042,8 +2043,8 @@ function loadCache(root, opts) {
|
|
|
2042
2043
|
}
|
|
2043
2044
|
},
|
|
2044
2045
|
save() {
|
|
2045
|
-
|
|
2046
|
-
|
|
2046
|
+
fs19.mkdirSync(cacheDir(root), { recursive: true });
|
|
2047
|
+
fs19.writeFileSync(file, stableStringify(data, 0));
|
|
2047
2048
|
}
|
|
2048
2049
|
};
|
|
2049
2050
|
}
|
|
@@ -2075,7 +2076,7 @@ function epistemicBreakdown(edges) {
|
|
|
2075
2076
|
// src/engine/build.ts
|
|
2076
2077
|
async function buildGraph(options) {
|
|
2077
2078
|
const start = nowMs();
|
|
2078
|
-
const root =
|
|
2079
|
+
const root = path19.resolve(options.root);
|
|
2079
2080
|
const files = discover({
|
|
2080
2081
|
root,
|
|
2081
2082
|
only: options.only,
|
|
@@ -2103,7 +2104,7 @@ async function buildGraph(options) {
|
|
|
2103
2104
|
for (const file of files) {
|
|
2104
2105
|
let stat;
|
|
2105
2106
|
try {
|
|
2106
|
-
stat =
|
|
2107
|
+
stat = fs19.statSync(file.abs);
|
|
2107
2108
|
} catch {
|
|
2108
2109
|
continue;
|
|
2109
2110
|
}
|
|
@@ -2121,7 +2122,7 @@ async function buildGraph(options) {
|
|
|
2121
2122
|
}
|
|
2122
2123
|
let hash = "";
|
|
2123
2124
|
try {
|
|
2124
|
-
hash = hashBytes(
|
|
2125
|
+
hash = hashBytes(fs19.readFileSync(file.abs));
|
|
2125
2126
|
} catch {
|
|
2126
2127
|
continue;
|
|
2127
2128
|
}
|
|
@@ -2213,7 +2214,7 @@ async function buildGraph(options) {
|
|
|
2213
2214
|
toolchain
|
|
2214
2215
|
},
|
|
2215
2216
|
meta: {
|
|
2216
|
-
root:
|
|
2217
|
+
root: path19.basename(root) === "" ? "." : ".",
|
|
2217
2218
|
languages,
|
|
2218
2219
|
counts: {
|
|
2219
2220
|
nodes: analysis.nodes.length,
|
|
@@ -2259,14 +2260,14 @@ function toRepoRel(p) {
|
|
|
2259
2260
|
function loadScipIndex(root, explicit) {
|
|
2260
2261
|
const candidates = [
|
|
2261
2262
|
explicit,
|
|
2262
|
-
|
|
2263
|
-
|
|
2263
|
+
path19.join(root, "index.scip"),
|
|
2264
|
+
path19.join(root, ".vibgrate", "index.scip")
|
|
2264
2265
|
].filter((p) => Boolean(p));
|
|
2265
2266
|
for (const file of candidates) {
|
|
2266
|
-
const abs =
|
|
2267
|
-
if (!
|
|
2267
|
+
const abs = path19.isAbsolute(file) ? file : path19.resolve(root, file);
|
|
2268
|
+
if (!fs19.existsSync(abs)) continue;
|
|
2268
2269
|
try {
|
|
2269
|
-
const index = decodeScipIndex(new Uint8Array(
|
|
2270
|
+
const index = decodeScipIndex(new Uint8Array(fs19.readFileSync(abs)));
|
|
2270
2271
|
if (index.documents.length) {
|
|
2271
2272
|
return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
|
|
2272
2273
|
}
|
|
@@ -2427,46 +2428,46 @@ function esc(s) {
|
|
|
2427
2428
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2428
2429
|
}
|
|
2429
2430
|
function vibgrateDir(root) {
|
|
2430
|
-
return
|
|
2431
|
+
return path19.join(root, ".vibgrate");
|
|
2431
2432
|
}
|
|
2432
2433
|
function defaultGraphPath(root) {
|
|
2433
|
-
return
|
|
2434
|
+
return path19.join(vibgrateDir(root), "graph.json");
|
|
2434
2435
|
}
|
|
2435
2436
|
function writeArtifacts(graph, options) {
|
|
2436
2437
|
const dir = vibgrateDir(options.root);
|
|
2437
|
-
|
|
2438
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
2438
2439
|
const graphPath = options.graphPath ?? defaultGraphPath(options.root);
|
|
2439
|
-
|
|
2440
|
+
fs19.mkdirSync(path19.dirname(graphPath), { recursive: true });
|
|
2440
2441
|
const tmp = `${graphPath}.${process.pid}.tmp`;
|
|
2441
2442
|
try {
|
|
2442
|
-
|
|
2443
|
-
|
|
2443
|
+
fs19.writeFileSync(tmp, serializeGraph(graph));
|
|
2444
|
+
fs19.renameSync(tmp, graphPath);
|
|
2444
2445
|
} catch (err) {
|
|
2445
|
-
|
|
2446
|
+
fs19.rmSync(tmp, { force: true });
|
|
2446
2447
|
throw err;
|
|
2447
2448
|
}
|
|
2448
2449
|
const written = { graphPath };
|
|
2449
2450
|
if (options.report !== false) {
|
|
2450
|
-
const reportPath =
|
|
2451
|
-
|
|
2451
|
+
const reportPath = path19.join(dir, "GRAPH_REPORT.md");
|
|
2452
|
+
fs19.writeFileSync(reportPath, renderReport(graph));
|
|
2452
2453
|
written.reportPath = reportPath;
|
|
2453
2454
|
}
|
|
2454
2455
|
if (options.html !== false) {
|
|
2455
|
-
const htmlPath =
|
|
2456
|
-
|
|
2456
|
+
const htmlPath = path19.join(dir, "graph.html");
|
|
2457
|
+
fs19.writeFileSync(htmlPath, renderHtml(graph));
|
|
2457
2458
|
written.htmlPath = htmlPath;
|
|
2458
2459
|
}
|
|
2459
2460
|
if (graph.facts && graph.facts.length) {
|
|
2460
|
-
const factsPath =
|
|
2461
|
-
|
|
2461
|
+
const factsPath = path19.join(dir, "facts.jsonl");
|
|
2462
|
+
fs19.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
|
|
2462
2463
|
written.factsPath = factsPath;
|
|
2463
2464
|
}
|
|
2464
2465
|
return written;
|
|
2465
2466
|
}
|
|
2466
2467
|
function loadGraph(root, graphPath) {
|
|
2467
2468
|
const file = graphPath ?? defaultGraphPath(root);
|
|
2468
|
-
if (!
|
|
2469
|
-
return parseGraph(
|
|
2469
|
+
if (!fs19.existsSync(file)) return null;
|
|
2470
|
+
return parseGraph(fs19.readFileSync(file, "utf8"));
|
|
2470
2471
|
}
|
|
2471
2472
|
|
|
2472
2473
|
// src/engine/verify.ts
|
|
@@ -2543,7 +2544,7 @@ function isProcessAlive(pid) {
|
|
|
2543
2544
|
}
|
|
2544
2545
|
function lockIsStale(file, staleMs) {
|
|
2545
2546
|
try {
|
|
2546
|
-
const { pid, at } = JSON.parse(
|
|
2547
|
+
const { pid, at } = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
2547
2548
|
if (typeof at === "number" && Date.now() - at > staleMs) return true;
|
|
2548
2549
|
if (typeof pid === "number" && !isProcessAlive(pid)) return true;
|
|
2549
2550
|
return false;
|
|
@@ -2554,10 +2555,10 @@ function lockIsStale(file, staleMs) {
|
|
|
2554
2555
|
function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
|
|
2555
2556
|
const write = () => {
|
|
2556
2557
|
try {
|
|
2557
|
-
|
|
2558
|
-
const fd =
|
|
2559
|
-
|
|
2560
|
-
|
|
2558
|
+
fs19.mkdirSync(path19.dirname(file), { recursive: true });
|
|
2559
|
+
const fd = fs19.openSync(file, "wx");
|
|
2560
|
+
fs19.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
|
|
2561
|
+
fs19.closeSync(fd);
|
|
2561
2562
|
return true;
|
|
2562
2563
|
} catch {
|
|
2563
2564
|
return false;
|
|
@@ -2566,7 +2567,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
|
|
|
2566
2567
|
if (write()) return true;
|
|
2567
2568
|
if (lockIsStale(file, staleMs)) {
|
|
2568
2569
|
try {
|
|
2569
|
-
|
|
2570
|
+
fs19.rmSync(file, { force: true });
|
|
2570
2571
|
} catch {
|
|
2571
2572
|
}
|
|
2572
2573
|
return write();
|
|
@@ -2575,7 +2576,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
|
|
|
2575
2576
|
}
|
|
2576
2577
|
function releaseLock(file) {
|
|
2577
2578
|
try {
|
|
2578
|
-
|
|
2579
|
+
fs19.rmSync(file, { force: true });
|
|
2579
2580
|
} catch {
|
|
2580
2581
|
}
|
|
2581
2582
|
}
|
|
@@ -2585,14 +2586,14 @@ function resolveEmbedModel(model) {
|
|
|
2585
2586
|
return model ?? "bge-small-en-v1.5";
|
|
2586
2587
|
}
|
|
2587
2588
|
function modelCacheDir() {
|
|
2588
|
-
const base = process.env.XDG_CACHE_HOME ||
|
|
2589
|
-
return
|
|
2589
|
+
const base = process.env.XDG_CACHE_HOME || path19.join(os.homedir(), ".cache");
|
|
2590
|
+
return path19.join(base, "vibgrate", "models");
|
|
2590
2591
|
}
|
|
2591
2592
|
function modelReadyMarker(modelId) {
|
|
2592
|
-
return
|
|
2593
|
+
return path19.join(modelCacheDir(), `.ready-${safe(modelId)}`);
|
|
2593
2594
|
}
|
|
2594
2595
|
function isModelReady(modelId = resolveEmbedModel()) {
|
|
2595
|
-
return
|
|
2596
|
+
return fs19.existsSync(modelReadyMarker(modelId));
|
|
2596
2597
|
}
|
|
2597
2598
|
function unavailableMessage(reason) {
|
|
2598
2599
|
switch (reason) {
|
|
@@ -2609,13 +2610,13 @@ function unavailableMessage(reason) {
|
|
|
2609
2610
|
}
|
|
2610
2611
|
function modelCacheInfo(modelId = resolveEmbedModel()) {
|
|
2611
2612
|
const dir = modelCacheDir();
|
|
2612
|
-
return { dir, present: isModelReady(modelId) ||
|
|
2613
|
+
return { dir, present: isModelReady(modelId) || fs19.existsSync(dir), bytes: dirSize(dir) };
|
|
2613
2614
|
}
|
|
2614
2615
|
function clearModelCache() {
|
|
2615
2616
|
const dir = modelCacheDir();
|
|
2616
2617
|
const bytes = dirSize(dir);
|
|
2617
2618
|
try {
|
|
2618
|
-
|
|
2619
|
+
fs19.rmSync(dir, { recursive: true, force: true });
|
|
2619
2620
|
} catch {
|
|
2620
2621
|
}
|
|
2621
2622
|
return bytes;
|
|
@@ -2624,16 +2625,16 @@ function dirSize(dir) {
|
|
|
2624
2625
|
let total = 0;
|
|
2625
2626
|
let entries;
|
|
2626
2627
|
try {
|
|
2627
|
-
entries =
|
|
2628
|
+
entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
2628
2629
|
} catch {
|
|
2629
2630
|
return 0;
|
|
2630
2631
|
}
|
|
2631
2632
|
for (const e of entries) {
|
|
2632
|
-
const p =
|
|
2633
|
+
const p = path19.join(dir, e.name);
|
|
2633
2634
|
if (e.isDirectory()) total += dirSize(p);
|
|
2634
2635
|
else {
|
|
2635
2636
|
try {
|
|
2636
|
-
total +=
|
|
2637
|
+
total += fs19.statSync(p).size;
|
|
2637
2638
|
} catch {
|
|
2638
2639
|
}
|
|
2639
2640
|
}
|
|
@@ -2645,7 +2646,7 @@ function isPermissionError(e) {
|
|
|
2645
2646
|
return code === "EACCES" || code === "EPERM" || code === "EROFS";
|
|
2646
2647
|
}
|
|
2647
2648
|
function embeddingsCached(root, modelId) {
|
|
2648
|
-
return
|
|
2649
|
+
return fs19.existsSync(path19.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
|
|
2649
2650
|
}
|
|
2650
2651
|
async function loadEmbedder(options = {}) {
|
|
2651
2652
|
if (options.local) return null;
|
|
@@ -2659,7 +2660,7 @@ async function loadEmbedder(options = {}) {
|
|
|
2659
2660
|
if (!mod?.FlagEmbedding) return fail("not-installed");
|
|
2660
2661
|
const cache = modelCacheDir();
|
|
2661
2662
|
try {
|
|
2662
|
-
|
|
2663
|
+
fs19.mkdirSync(cache, { recursive: true });
|
|
2663
2664
|
} catch (e) {
|
|
2664
2665
|
return fail(isPermissionError(e) ? "no-permission" : "init-failed");
|
|
2665
2666
|
}
|
|
@@ -2674,7 +2675,7 @@ async function loadEmbedder(options = {}) {
|
|
|
2674
2675
|
"embedding model init timed out"
|
|
2675
2676
|
);
|
|
2676
2677
|
try {
|
|
2677
|
-
|
|
2678
|
+
fs19.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
|
|
2678
2679
|
} catch {
|
|
2679
2680
|
}
|
|
2680
2681
|
return {
|
|
@@ -2772,7 +2773,7 @@ function cosine(a, b) {
|
|
|
2772
2773
|
var EMBED_CHUNK = 256;
|
|
2773
2774
|
var CACHE_WRITE_INTERVAL_MS = 1500;
|
|
2774
2775
|
function vectorCachePath(root, modelId) {
|
|
2775
|
-
return
|
|
2776
|
+
return path19.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
|
|
2776
2777
|
}
|
|
2777
2778
|
function countPending(graph, root, modelId) {
|
|
2778
2779
|
const entries = readCacheEntries(vectorCachePath(root, modelId), modelId);
|
|
@@ -2787,9 +2788,9 @@ function countPending(graph, root, modelId) {
|
|
|
2787
2788
|
return pending;
|
|
2788
2789
|
}
|
|
2789
2790
|
function readCacheEntries(file, modelId) {
|
|
2790
|
-
if (!
|
|
2791
|
+
if (!fs19.existsSync(file)) return {};
|
|
2791
2792
|
try {
|
|
2792
|
-
const loaded = JSON.parse(
|
|
2793
|
+
const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
2793
2794
|
if (loaded.model === modelId && loaded.entries) return loaded.entries;
|
|
2794
2795
|
} catch {
|
|
2795
2796
|
}
|
|
@@ -2798,9 +2799,9 @@ function readCacheEntries(file, modelId) {
|
|
|
2798
2799
|
async function getNodeEmbeddings(graph, embedder, root, onProgress) {
|
|
2799
2800
|
const file = vectorCachePath(root, embedder.id);
|
|
2800
2801
|
let cache = { model: embedder.id, entries: {} };
|
|
2801
|
-
if (
|
|
2802
|
+
if (fs19.existsSync(file)) {
|
|
2802
2803
|
try {
|
|
2803
|
-
const loaded = JSON.parse(
|
|
2804
|
+
const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
2804
2805
|
if (loaded.model === embedder.id && loaded.entries) cache = loaded;
|
|
2805
2806
|
} catch {
|
|
2806
2807
|
}
|
|
@@ -2818,10 +2819,10 @@ async function getNodeEmbeddings(graph, embedder, root, onProgress) {
|
|
|
2818
2819
|
}
|
|
2819
2820
|
const persist = () => {
|
|
2820
2821
|
try {
|
|
2821
|
-
|
|
2822
|
+
fs19.mkdirSync(cacheDir(root), { recursive: true });
|
|
2822
2823
|
const tmp = `${file}.tmp.${process.pid}`;
|
|
2823
|
-
|
|
2824
|
-
|
|
2824
|
+
fs19.writeFileSync(tmp, JSON.stringify(cache));
|
|
2825
|
+
fs19.renameSync(tmp, file);
|
|
2825
2826
|
} catch {
|
|
2826
2827
|
}
|
|
2827
2828
|
};
|
|
@@ -2859,21 +2860,21 @@ function safe(id) {
|
|
|
2859
2860
|
}
|
|
2860
2861
|
var SNAPSHOT_VERSION = "vg-freshness/1";
|
|
2861
2862
|
function snapshotPath(root) {
|
|
2862
|
-
return
|
|
2863
|
+
return path19.join(cacheDir(root), "freshness.json");
|
|
2863
2864
|
}
|
|
2864
2865
|
function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
|
|
2865
2866
|
const files = {};
|
|
2866
2867
|
for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
|
|
2867
2868
|
const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
|
|
2868
2869
|
try {
|
|
2869
|
-
|
|
2870
|
-
|
|
2870
|
+
fs19.mkdirSync(cacheDir(root), { recursive: true });
|
|
2871
|
+
fs19.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
2871
2872
|
} catch {
|
|
2872
2873
|
}
|
|
2873
2874
|
}
|
|
2874
2875
|
function loadSnapshot(root) {
|
|
2875
2876
|
try {
|
|
2876
|
-
const loaded = JSON.parse(
|
|
2877
|
+
const loaded = JSON.parse(fs19.readFileSync(snapshotPath(root), "utf8"));
|
|
2877
2878
|
if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
|
|
2878
2879
|
return { ...loaded, scope: loaded.scope ?? {} };
|
|
2879
2880
|
}
|
|
@@ -2909,7 +2910,7 @@ function probeFreshness(root) {
|
|
|
2909
2910
|
}
|
|
2910
2911
|
let stat;
|
|
2911
2912
|
try {
|
|
2912
|
-
stat =
|
|
2913
|
+
stat = fs19.statSync(file.abs);
|
|
2913
2914
|
} catch {
|
|
2914
2915
|
drift.removed.push(file.rel);
|
|
2915
2916
|
continue;
|
|
@@ -2917,7 +2918,7 @@ function probeFreshness(root) {
|
|
|
2917
2918
|
if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
|
|
2918
2919
|
let hash = "";
|
|
2919
2920
|
try {
|
|
2920
|
-
hash = hashBytes(
|
|
2921
|
+
hash = hashBytes(fs19.readFileSync(file.abs));
|
|
2921
2922
|
} catch {
|
|
2922
2923
|
drift.removed.push(file.rel);
|
|
2923
2924
|
continue;
|
|
@@ -2934,7 +2935,7 @@ function probeFreshness(root) {
|
|
|
2934
2935
|
}
|
|
2935
2936
|
if (absorbed && !hasDrift(drift)) {
|
|
2936
2937
|
try {
|
|
2937
|
-
|
|
2938
|
+
fs19.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
|
|
2938
2939
|
} catch {
|
|
2939
2940
|
}
|
|
2940
2941
|
}
|
|
@@ -3005,6 +3006,15 @@ function push2(map, key, value) {
|
|
|
3005
3006
|
if (list) list.push(value);
|
|
3006
3007
|
else map.set(key, [value]);
|
|
3007
3008
|
}
|
|
3009
|
+
var INDEX_CACHE = /* @__PURE__ */ new WeakMap();
|
|
3010
|
+
function indexFor(graph) {
|
|
3011
|
+
let idx = INDEX_CACHE.get(graph);
|
|
3012
|
+
if (!idx) {
|
|
3013
|
+
idx = new GraphIndex(graph);
|
|
3014
|
+
INDEX_CACHE.set(graph, idx);
|
|
3015
|
+
}
|
|
3016
|
+
return idx;
|
|
3017
|
+
}
|
|
3008
3018
|
|
|
3009
3019
|
// src/engine/query.ts
|
|
3010
3020
|
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
@@ -3055,7 +3065,7 @@ function queryGraph(graph, question, options = {}) {
|
|
|
3055
3065
|
const limit = options.limit ?? 12;
|
|
3056
3066
|
const terms = tokenize(question);
|
|
3057
3067
|
const weightOf = termWeights(graph, terms);
|
|
3058
|
-
const index =
|
|
3068
|
+
const index = indexFor(graph);
|
|
3059
3069
|
const scored = [];
|
|
3060
3070
|
for (const node of graph.nodes) {
|
|
3061
3071
|
if (node.kind === "file" || node.kind === "external") continue;
|
|
@@ -3072,7 +3082,7 @@ async function queryGraphSemantic(graph, question, options) {
|
|
|
3072
3082
|
const limit = options.limit ?? 12;
|
|
3073
3083
|
const terms = tokenize(question);
|
|
3074
3084
|
const weightOf = termWeights(graph, terms);
|
|
3075
|
-
const index =
|
|
3085
|
+
const index = indexFor(graph);
|
|
3076
3086
|
const queryVec = await options.embedder.embedQuery(question);
|
|
3077
3087
|
const lexRaw = /* @__PURE__ */ new Map();
|
|
3078
3088
|
let lexMax = 0;
|
|
@@ -3215,7 +3225,7 @@ function round2(x) {
|
|
|
3215
3225
|
}
|
|
3216
3226
|
var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
3217
3227
|
function refreshLockPath(root) {
|
|
3218
|
-
return
|
|
3228
|
+
return path19.join(cacheDir(root), "refresh.lock");
|
|
3219
3229
|
}
|
|
3220
3230
|
async function refreshIfStale(root, opts = {}) {
|
|
3221
3231
|
const probe = probeFreshness(root);
|
|
@@ -3246,8 +3256,8 @@ async function refreshIfStale(root, opts = {}) {
|
|
|
3246
3256
|
const dir = vibgrateDir(root);
|
|
3247
3257
|
writeArtifacts(result.graph, {
|
|
3248
3258
|
root,
|
|
3249
|
-
report:
|
|
3250
|
-
html:
|
|
3259
|
+
report: fs19.existsSync(path19.join(dir, "GRAPH_REPORT.md")),
|
|
3260
|
+
html: fs19.existsSync(path19.join(dir, "graph.html"))
|
|
3251
3261
|
});
|
|
3252
3262
|
}
|
|
3253
3263
|
writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
|
|
@@ -3266,6 +3276,157 @@ async function refreshIfStale(root, opts = {}) {
|
|
|
3266
3276
|
releaseLock(lock);
|
|
3267
3277
|
}
|
|
3268
3278
|
}
|
|
3279
|
+
var LEDGER = "savings.jsonl";
|
|
3280
|
+
var PER_FILE_TOKENS = 400;
|
|
3281
|
+
var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node"]);
|
|
3282
|
+
var CLI_TOOL_ALIASES = {
|
|
3283
|
+
ask: "query_graph",
|
|
3284
|
+
show: "get_node",
|
|
3285
|
+
impact: "impact_of",
|
|
3286
|
+
path: "find_path",
|
|
3287
|
+
hubs: "list_hubs",
|
|
3288
|
+
areas: "list_areas",
|
|
3289
|
+
tree: "tree"
|
|
3290
|
+
};
|
|
3291
|
+
function sanitizeClient(name) {
|
|
3292
|
+
if (typeof name !== "string") return "unknown";
|
|
3293
|
+
const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
3294
|
+
return cleaned || "unknown";
|
|
3295
|
+
}
|
|
3296
|
+
function recordCliCall(root, entry, now) {
|
|
3297
|
+
recordSaving(
|
|
3298
|
+
root,
|
|
3299
|
+
{
|
|
3300
|
+
tool: entry.tool,
|
|
3301
|
+
source: "cli",
|
|
3302
|
+
client: sanitizeClient(entry.client),
|
|
3303
|
+
outcome: entry.outcome,
|
|
3304
|
+
vgTokens: entry.vgTokens ?? 0,
|
|
3305
|
+
baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
|
|
3306
|
+
},
|
|
3307
|
+
now
|
|
3308
|
+
);
|
|
3309
|
+
}
|
|
3310
|
+
function ledgerPath(root) {
|
|
3311
|
+
return path19.join(cacheDir(root), LEDGER);
|
|
3312
|
+
}
|
|
3313
|
+
function savingsRecorded(root) {
|
|
3314
|
+
return fs19.existsSync(ledgerPath(root));
|
|
3315
|
+
}
|
|
3316
|
+
function recordSaving(root, entry, now) {
|
|
3317
|
+
try {
|
|
3318
|
+
fs19.mkdirSync(cacheDir(root), { recursive: true });
|
|
3319
|
+
const line = JSON.stringify({ ts: now, ...entry });
|
|
3320
|
+
fs19.appendFileSync(ledgerPath(root), line + "\n");
|
|
3321
|
+
} catch {
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
var DEFAULT_RATE_PER_M = 3;
|
|
3325
|
+
var DEFAULT_RATE_LABEL = "input @ $3/1M";
|
|
3326
|
+
function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
|
|
3327
|
+
const file = ledgerPath(root);
|
|
3328
|
+
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
3329
|
+
let queries = 0;
|
|
3330
|
+
let vgTokens = 0;
|
|
3331
|
+
let baselineTokens = 0;
|
|
3332
|
+
if (fs19.existsSync(file)) {
|
|
3333
|
+
for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
|
|
3334
|
+
if (!line.trim()) continue;
|
|
3335
|
+
try {
|
|
3336
|
+
const e = JSON.parse(line);
|
|
3337
|
+
if (e.ts < cutoff) continue;
|
|
3338
|
+
if (!SAVINGS_TOOLS.has(e.tool)) continue;
|
|
3339
|
+
queries++;
|
|
3340
|
+
vgTokens += e.vgTokens;
|
|
3341
|
+
baselineTokens += e.baselineTokens;
|
|
3342
|
+
} catch {
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
const estCostVg = vgTokens / 1e6 * ratePerM;
|
|
3347
|
+
const estCostBaseline = baselineTokens / 1e6 * ratePerM;
|
|
3348
|
+
return {
|
|
3349
|
+
enabled: savingsRecorded(root),
|
|
3350
|
+
days,
|
|
3351
|
+
queries,
|
|
3352
|
+
vgTokens,
|
|
3353
|
+
baselineTokens,
|
|
3354
|
+
ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
|
|
3355
|
+
estCostVg: round22(estCostVg),
|
|
3356
|
+
estCostBaseline: round22(estCostBaseline),
|
|
3357
|
+
saved: round22(estCostBaseline - estCostVg),
|
|
3358
|
+
rateLabel: DEFAULT_RATE_LABEL
|
|
3359
|
+
};
|
|
3360
|
+
}
|
|
3361
|
+
function round22(x) {
|
|
3362
|
+
return Math.round(x * 100) / 100;
|
|
3363
|
+
}
|
|
3364
|
+
function toDimensionStats(byKey) {
|
|
3365
|
+
return [...byKey.entries()].map(([key, r]) => {
|
|
3366
|
+
const calls = r.complete + r.partial + r.miss;
|
|
3367
|
+
return {
|
|
3368
|
+
key,
|
|
3369
|
+
calls,
|
|
3370
|
+
complete: r.complete,
|
|
3371
|
+
partial: r.partial,
|
|
3372
|
+
miss: r.miss,
|
|
3373
|
+
successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
|
|
3374
|
+
};
|
|
3375
|
+
}).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
|
|
3376
|
+
}
|
|
3377
|
+
function readUsage(root, days, now) {
|
|
3378
|
+
const file = ledgerPath(root);
|
|
3379
|
+
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
3380
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
3381
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
3382
|
+
const byClient = /* @__PURE__ */ new Map();
|
|
3383
|
+
const bump2 = (m, key, outcome) => {
|
|
3384
|
+
const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
|
|
3385
|
+
row[outcome]++;
|
|
3386
|
+
m.set(key, row);
|
|
3387
|
+
};
|
|
3388
|
+
if (fs19.existsSync(file)) {
|
|
3389
|
+
for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
|
|
3390
|
+
if (!line.trim()) continue;
|
|
3391
|
+
try {
|
|
3392
|
+
const e = JSON.parse(line);
|
|
3393
|
+
if (e.ts < cutoff) continue;
|
|
3394
|
+
const outcome = e.outcome ?? "complete";
|
|
3395
|
+
bump2(byTool, e.tool, outcome);
|
|
3396
|
+
bump2(bySource, e.source ?? "mcp", outcome);
|
|
3397
|
+
bump2(byClient, e.client ?? "unknown", outcome);
|
|
3398
|
+
} catch {
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
const commands = toDimensionStats(byTool).map((d) => ({
|
|
3403
|
+
tool: d.key,
|
|
3404
|
+
calls: d.calls,
|
|
3405
|
+
complete: d.complete,
|
|
3406
|
+
partial: d.partial,
|
|
3407
|
+
miss: d.miss,
|
|
3408
|
+
successPct: d.successPct
|
|
3409
|
+
}));
|
|
3410
|
+
const totals = commands.reduce(
|
|
3411
|
+
(t, c) => ({
|
|
3412
|
+
calls: t.calls + c.calls,
|
|
3413
|
+
complete: t.complete + c.complete,
|
|
3414
|
+
partial: t.partial + c.partial,
|
|
3415
|
+
miss: t.miss + c.miss
|
|
3416
|
+
}),
|
|
3417
|
+
{ calls: 0, complete: 0, partial: 0, miss: 0 }
|
|
3418
|
+
);
|
|
3419
|
+
const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
|
|
3420
|
+
return {
|
|
3421
|
+
enabled: savingsRecorded(root),
|
|
3422
|
+
days,
|
|
3423
|
+
commands,
|
|
3424
|
+
sources: toDimensionStats(bySource),
|
|
3425
|
+
clients: toDimensionStats(byClient),
|
|
3426
|
+
totals,
|
|
3427
|
+
avgSuccessPct
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3269
3430
|
|
|
3270
3431
|
// src/engine/lookup.ts
|
|
3271
3432
|
function findNodes(graph, query) {
|
|
@@ -3342,7 +3503,7 @@ function shortestPath(graph, srcId, dstId) {
|
|
|
3342
3503
|
var DEPEND_EDGES = /* @__PURE__ */ new Set(["call", "import", "extends", "implements", "references"]);
|
|
3343
3504
|
function impactOf(graph, rootId, opts = {}) {
|
|
3344
3505
|
const maxDepth = Math.max(1, opts.depth ?? 4);
|
|
3345
|
-
const index =
|
|
3506
|
+
const index = indexFor(graph);
|
|
3346
3507
|
const root = index.node(rootId);
|
|
3347
3508
|
const affected = /* @__PURE__ */ new Map();
|
|
3348
3509
|
let minEdgeConfidence = 1;
|
|
@@ -3389,7 +3550,7 @@ function round3(x) {
|
|
|
3389
3550
|
return Math.round(x * 1e6) / 1e6;
|
|
3390
3551
|
}
|
|
3391
3552
|
function coveringTests(graph, node, index) {
|
|
3392
|
-
const idx = index ??
|
|
3553
|
+
const idx = index ?? indexFor(graph);
|
|
3393
3554
|
const byId = idx.nodeById;
|
|
3394
3555
|
const out = /* @__PURE__ */ new Map();
|
|
3395
3556
|
for (const e of idx.in(node.id, "test")) {
|
|
@@ -3403,7 +3564,7 @@ function coveringTests(graph, node, index) {
|
|
|
3403
3564
|
return [...out.values()].sort((a, b) => b.confidence - a.confidence || a.file.localeCompare(b.file));
|
|
3404
3565
|
}
|
|
3405
3566
|
function testsToRun(graph, rootId, depth = 4) {
|
|
3406
|
-
const idx =
|
|
3567
|
+
const idx = indexFor(graph);
|
|
3407
3568
|
const impact = impactOf(graph, rootId, { depth });
|
|
3408
3569
|
const affectedIds = /* @__PURE__ */ new Set([rootId, ...impact.affected.map((a) => a.id)]);
|
|
3409
3570
|
const testFiles = /* @__PURE__ */ new Set();
|
|
@@ -3423,10 +3584,10 @@ function testsToRun(graph, rootId, depth = 4) {
|
|
|
3423
3584
|
};
|
|
3424
3585
|
}
|
|
3425
3586
|
function detectRunner(root, lang) {
|
|
3426
|
-
const pkgPath =
|
|
3427
|
-
if (
|
|
3587
|
+
const pkgPath = path19.join(root, "package.json");
|
|
3588
|
+
if (fs19.existsSync(pkgPath)) {
|
|
3428
3589
|
try {
|
|
3429
|
-
const pkg = JSON.parse(
|
|
3590
|
+
const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf8"));
|
|
3430
3591
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3431
3592
|
if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
|
|
3432
3593
|
if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
|
|
@@ -3434,10 +3595,10 @@ function detectRunner(root, lang) {
|
|
|
3434
3595
|
} catch {
|
|
3435
3596
|
}
|
|
3436
3597
|
}
|
|
3437
|
-
if (lang === "py" ||
|
|
3598
|
+
if (lang === "py" || fs19.existsSync(path19.join(root, "pyproject.toml")) || fs19.existsSync(path19.join(root, "pytest.ini"))) {
|
|
3438
3599
|
return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
|
|
3439
3600
|
}
|
|
3440
|
-
if (lang === "go" ||
|
|
3601
|
+
if (lang === "go" || fs19.existsSync(path19.join(root, "go.mod"))) {
|
|
3441
3602
|
return { name: "go test", command: () => "go test ./..." };
|
|
3442
3603
|
}
|
|
3443
3604
|
if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
|
|
@@ -3448,7 +3609,7 @@ function detectRunner(root, lang) {
|
|
|
3448
3609
|
}
|
|
3449
3610
|
function hasFile(root, re) {
|
|
3450
3611
|
try {
|
|
3451
|
-
return
|
|
3612
|
+
return fs19.readdirSync(root).some((f) => re.test(f));
|
|
3452
3613
|
} catch {
|
|
3453
3614
|
return false;
|
|
3454
3615
|
}
|
|
@@ -3520,7 +3681,7 @@ function findManifests(root) {
|
|
|
3520
3681
|
if (depth > 8 || scanned > MAX_ENTRIES) return;
|
|
3521
3682
|
let entries;
|
|
3522
3683
|
try {
|
|
3523
|
-
entries =
|
|
3684
|
+
entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
3524
3685
|
} catch {
|
|
3525
3686
|
return;
|
|
3526
3687
|
}
|
|
@@ -3528,11 +3689,11 @@ function findManifests(root) {
|
|
|
3528
3689
|
scanned++;
|
|
3529
3690
|
if (scanned > MAX_ENTRIES) break;
|
|
3530
3691
|
if (e.isDirectory()) {
|
|
3531
|
-
if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(
|
|
3692
|
+
if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path19.join(dir, e.name), depth + 1);
|
|
3532
3693
|
continue;
|
|
3533
3694
|
}
|
|
3534
3695
|
const eco = MANIFEST_BY_FILE[e.name] ?? (/\.(cs|fs)proj$/i.test(e.name) ? "dotnet" : void 0);
|
|
3535
|
-
if (eco) set[eco].push(
|
|
3696
|
+
if (eco) set[eco].push(path19.join(dir, e.name));
|
|
3536
3697
|
}
|
|
3537
3698
|
};
|
|
3538
3699
|
walk2(root, 0);
|
|
@@ -3544,11 +3705,11 @@ function npmDeps(files) {
|
|
|
3544
3705
|
for (const file of files) {
|
|
3545
3706
|
let pkg;
|
|
3546
3707
|
try {
|
|
3547
|
-
pkg = JSON.parse(
|
|
3708
|
+
pkg = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
3548
3709
|
} catch {
|
|
3549
3710
|
continue;
|
|
3550
3711
|
}
|
|
3551
|
-
const dir =
|
|
3712
|
+
const dir = path19.dirname(file);
|
|
3552
3713
|
const all = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3553
3714
|
for (const [name, declared] of Object.entries(all)) {
|
|
3554
3715
|
out.push({ name, ecosystem: "npm", declared, installed: installedNpmVersion(dir, name) });
|
|
@@ -3559,12 +3720,12 @@ function npmDeps(files) {
|
|
|
3559
3720
|
function installedNpmVersion(dir, name) {
|
|
3560
3721
|
let cur = dir;
|
|
3561
3722
|
for (let i = 0; i < 12; i++) {
|
|
3562
|
-
const p =
|
|
3723
|
+
const p = path19.join(cur, "node_modules", name, "package.json");
|
|
3563
3724
|
try {
|
|
3564
|
-
return JSON.parse(
|
|
3725
|
+
return JSON.parse(fs19.readFileSync(p, "utf8")).version;
|
|
3565
3726
|
} catch {
|
|
3566
3727
|
}
|
|
3567
|
-
const parent =
|
|
3728
|
+
const parent = path19.dirname(cur);
|
|
3568
3729
|
if (parent === cur) break;
|
|
3569
3730
|
cur = parent;
|
|
3570
3731
|
}
|
|
@@ -3581,21 +3742,21 @@ function normalizePep503(name) {
|
|
|
3581
3742
|
function sitePackagesDirs(base) {
|
|
3582
3743
|
const out = [];
|
|
3583
3744
|
try {
|
|
3584
|
-
for (const d of
|
|
3585
|
-
if (d.startsWith("python")) out.push(
|
|
3745
|
+
for (const d of fs19.readdirSync(path19.join(base, "lib"))) {
|
|
3746
|
+
if (d.startsWith("python")) out.push(path19.join(base, "lib", d, "site-packages"));
|
|
3586
3747
|
}
|
|
3587
3748
|
} catch {
|
|
3588
3749
|
}
|
|
3589
|
-
out.push(
|
|
3750
|
+
out.push(path19.join(base, "Lib", "site-packages"));
|
|
3590
3751
|
return out;
|
|
3591
3752
|
}
|
|
3592
3753
|
function installedPypiVersion(root, name) {
|
|
3593
3754
|
const target = normalizePep503(name);
|
|
3594
3755
|
for (const venv of [".venv", "venv", "env", ".tox"]) {
|
|
3595
|
-
for (const sp of sitePackagesDirs(
|
|
3756
|
+
for (const sp of sitePackagesDirs(path19.join(root, venv))) {
|
|
3596
3757
|
let entries;
|
|
3597
3758
|
try {
|
|
3598
|
-
entries =
|
|
3759
|
+
entries = fs19.readdirSync(sp);
|
|
3599
3760
|
} catch {
|
|
3600
3761
|
continue;
|
|
3601
3762
|
}
|
|
@@ -3610,7 +3771,7 @@ function installedPypiVersion(root, name) {
|
|
|
3610
3771
|
function installedPhpVersion(root, name) {
|
|
3611
3772
|
let data;
|
|
3612
3773
|
try {
|
|
3613
|
-
data = JSON.parse(
|
|
3774
|
+
data = JSON.parse(fs19.readFileSync(path19.join(root, "vendor", "composer", "installed.json"), "utf8"));
|
|
3614
3775
|
} catch {
|
|
3615
3776
|
return void 0;
|
|
3616
3777
|
}
|
|
@@ -3629,11 +3790,11 @@ function pypiDeps(files) {
|
|
|
3629
3790
|
for (const file of files) {
|
|
3630
3791
|
let text;
|
|
3631
3792
|
try {
|
|
3632
|
-
text =
|
|
3793
|
+
text = fs19.readFileSync(file, "utf8");
|
|
3633
3794
|
} catch {
|
|
3634
3795
|
continue;
|
|
3635
3796
|
}
|
|
3636
|
-
if (
|
|
3797
|
+
if (path19.basename(file) === "requirements.txt") {
|
|
3637
3798
|
for (const line of text.split("\n")) {
|
|
3638
3799
|
const t = line.trim();
|
|
3639
3800
|
if (!t || t.startsWith("#") || t.startsWith("-")) continue;
|
|
@@ -3717,7 +3878,7 @@ function goDeps(files) {
|
|
|
3717
3878
|
for (const mod of files) {
|
|
3718
3879
|
let text;
|
|
3719
3880
|
try {
|
|
3720
|
-
text =
|
|
3881
|
+
text = fs19.readFileSync(mod, "utf8");
|
|
3721
3882
|
} catch {
|
|
3722
3883
|
continue;
|
|
3723
3884
|
}
|
|
@@ -3735,7 +3896,7 @@ function cargoDeps(files) {
|
|
|
3735
3896
|
for (const file of files) {
|
|
3736
3897
|
let text;
|
|
3737
3898
|
try {
|
|
3738
|
-
text =
|
|
3899
|
+
text = fs19.readFileSync(file, "utf8");
|
|
3739
3900
|
} catch {
|
|
3740
3901
|
continue;
|
|
3741
3902
|
}
|
|
@@ -3767,7 +3928,7 @@ function rubyDeps(files) {
|
|
|
3767
3928
|
for (const file of files) {
|
|
3768
3929
|
let text;
|
|
3769
3930
|
try {
|
|
3770
|
-
text =
|
|
3931
|
+
text = fs19.readFileSync(file, "utf8");
|
|
3771
3932
|
} catch {
|
|
3772
3933
|
continue;
|
|
3773
3934
|
}
|
|
@@ -3782,7 +3943,7 @@ function phpDeps(files) {
|
|
|
3782
3943
|
for (const file of files) {
|
|
3783
3944
|
let pkg;
|
|
3784
3945
|
try {
|
|
3785
|
-
pkg = JSON.parse(
|
|
3946
|
+
pkg = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
3786
3947
|
} catch {
|
|
3787
3948
|
continue;
|
|
3788
3949
|
}
|
|
@@ -3799,7 +3960,7 @@ function dotnetDeps(files) {
|
|
|
3799
3960
|
for (const file of files) {
|
|
3800
3961
|
let text;
|
|
3801
3962
|
try {
|
|
3802
|
-
text =
|
|
3963
|
+
text = fs19.readFileSync(file, "utf8");
|
|
3803
3964
|
} catch {
|
|
3804
3965
|
continue;
|
|
3805
3966
|
}
|
|
@@ -3819,7 +3980,7 @@ function swiftDeps(files) {
|
|
|
3819
3980
|
for (const file of files) {
|
|
3820
3981
|
let text;
|
|
3821
3982
|
try {
|
|
3822
|
-
text =
|
|
3983
|
+
text = fs19.readFileSync(file, "utf8");
|
|
3823
3984
|
} catch {
|
|
3824
3985
|
continue;
|
|
3825
3986
|
}
|
|
@@ -3841,7 +4002,7 @@ function dartDeps(files) {
|
|
|
3841
4002
|
for (const file of files) {
|
|
3842
4003
|
let text;
|
|
3843
4004
|
try {
|
|
3844
|
-
text =
|
|
4005
|
+
text = fs19.readFileSync(file, "utf8");
|
|
3845
4006
|
} catch {
|
|
3846
4007
|
continue;
|
|
3847
4008
|
}
|
|
@@ -3866,11 +4027,11 @@ function javaDeps(files) {
|
|
|
3866
4027
|
for (const file of files) {
|
|
3867
4028
|
let text;
|
|
3868
4029
|
try {
|
|
3869
|
-
text =
|
|
4030
|
+
text = fs19.readFileSync(file, "utf8");
|
|
3870
4031
|
} catch {
|
|
3871
4032
|
continue;
|
|
3872
4033
|
}
|
|
3873
|
-
if (
|
|
4034
|
+
if (path19.basename(file) === "pom.xml") {
|
|
3874
4035
|
for (const block of text.match(/<dependency>[\s\S]*?<\/dependency>/g) ?? []) {
|
|
3875
4036
|
const g = /<groupId>([^<]+)<\/groupId>/.exec(block);
|
|
3876
4037
|
const a = /<artifactId>([^<]+)<\/artifactId>/.exec(block);
|
|
@@ -3916,10 +4077,10 @@ function discoverModels(home = os.homedir()) {
|
|
|
3916
4077
|
);
|
|
3917
4078
|
}
|
|
3918
4079
|
function ollama(home) {
|
|
3919
|
-
const base =
|
|
4080
|
+
const base = path19.join(home, ".ollama", "models", "manifests");
|
|
3920
4081
|
const out = [];
|
|
3921
4082
|
walk(base, 4, (file) => {
|
|
3922
|
-
const rel2 =
|
|
4083
|
+
const rel2 = path19.relative(base, file).split(path19.sep);
|
|
3923
4084
|
if (rel2.length >= 2) {
|
|
3924
4085
|
const tag = rel2[rel2.length - 1];
|
|
3925
4086
|
const model = rel2[rel2.length - 2];
|
|
@@ -3929,24 +4090,24 @@ function ollama(home) {
|
|
|
3929
4090
|
return dedupe(out);
|
|
3930
4091
|
}
|
|
3931
4092
|
function lmStudio(home) {
|
|
3932
|
-
const bases = [
|
|
4093
|
+
const bases = [path19.join(home, ".lmstudio", "models"), path19.join(home, ".cache", "lm-studio", "models")];
|
|
3933
4094
|
const out = [];
|
|
3934
4095
|
for (const base of bases) {
|
|
3935
4096
|
walk(base, 5, (file) => {
|
|
3936
4097
|
if (!file.endsWith(".gguf")) return;
|
|
3937
|
-
const rel2 =
|
|
4098
|
+
const rel2 = path19.relative(base, file).replace(/\\/g, "/");
|
|
3938
4099
|
out.push({ runtime: "lm-studio", name: rel2, path: file });
|
|
3939
4100
|
});
|
|
3940
4101
|
}
|
|
3941
4102
|
return dedupe(out);
|
|
3942
4103
|
}
|
|
3943
4104
|
function looseGguf(home) {
|
|
3944
|
-
const bases = [
|
|
4105
|
+
const bases = [path19.join(home, "models"), path19.join(home, ".cache", "huggingface")];
|
|
3945
4106
|
const out = [];
|
|
3946
4107
|
for (const base of bases) {
|
|
3947
4108
|
walk(base, 4, (file) => {
|
|
3948
4109
|
if (!file.endsWith(".gguf")) return;
|
|
3949
|
-
out.push({ runtime: "gguf", name:
|
|
4110
|
+
out.push({ runtime: "gguf", name: path19.basename(file), path: file });
|
|
3950
4111
|
});
|
|
3951
4112
|
}
|
|
3952
4113
|
return dedupe(out);
|
|
@@ -3955,12 +4116,12 @@ function walk(dir, depth, onFile) {
|
|
|
3955
4116
|
if (depth < 0) return;
|
|
3956
4117
|
let entries;
|
|
3957
4118
|
try {
|
|
3958
|
-
entries =
|
|
4119
|
+
entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
3959
4120
|
} catch {
|
|
3960
4121
|
return;
|
|
3961
4122
|
}
|
|
3962
4123
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
3963
|
-
const abs =
|
|
4124
|
+
const abs = path19.join(dir, e.name);
|
|
3964
4125
|
if (e.isDirectory()) walk(abs, depth - 1, onFile);
|
|
3965
4126
|
else if (e.isFile()) onFile(abs);
|
|
3966
4127
|
}
|
|
@@ -3991,7 +4152,7 @@ function lockfileVersion(root, ecosystem, name) {
|
|
|
3991
4152
|
function gradleLock(root, name) {
|
|
3992
4153
|
let text;
|
|
3993
4154
|
try {
|
|
3994
|
-
text =
|
|
4155
|
+
text = fs19.readFileSync(path19.join(root, "gradle.lockfile"), "utf8");
|
|
3995
4156
|
} catch {
|
|
3996
4157
|
return void 0;
|
|
3997
4158
|
}
|
|
@@ -4001,7 +4162,7 @@ function gradleLock(root, name) {
|
|
|
4001
4162
|
function packageResolved(root, name) {
|
|
4002
4163
|
let data;
|
|
4003
4164
|
try {
|
|
4004
|
-
data = JSON.parse(
|
|
4165
|
+
data = JSON.parse(fs19.readFileSync(path19.join(root, "Package.resolved"), "utf8"));
|
|
4005
4166
|
} catch {
|
|
4006
4167
|
return void 0;
|
|
4007
4168
|
}
|
|
@@ -4017,7 +4178,7 @@ function packageResolved(root, name) {
|
|
|
4017
4178
|
function pubspecLock(root, name) {
|
|
4018
4179
|
let text;
|
|
4019
4180
|
try {
|
|
4020
|
-
text =
|
|
4181
|
+
text = fs19.readFileSync(path19.join(root, "pubspec.lock"), "utf8");
|
|
4021
4182
|
} catch {
|
|
4022
4183
|
return void 0;
|
|
4023
4184
|
}
|
|
@@ -4041,7 +4202,7 @@ function escapeRegExp(s) {
|
|
|
4041
4202
|
function gemfileLock(root, name) {
|
|
4042
4203
|
let text;
|
|
4043
4204
|
try {
|
|
4044
|
-
text =
|
|
4205
|
+
text = fs19.readFileSync(path19.join(root, "Gemfile.lock"), "utf8");
|
|
4045
4206
|
} catch {
|
|
4046
4207
|
return void 0;
|
|
4047
4208
|
}
|
|
@@ -4051,7 +4212,7 @@ function gemfileLock(root, name) {
|
|
|
4051
4212
|
function composerLock(root, name) {
|
|
4052
4213
|
let data;
|
|
4053
4214
|
try {
|
|
4054
|
-
data = JSON.parse(
|
|
4215
|
+
data = JSON.parse(fs19.readFileSync(path19.join(root, "composer.lock"), "utf8"));
|
|
4055
4216
|
} catch {
|
|
4056
4217
|
return void 0;
|
|
4057
4218
|
}
|
|
@@ -4066,7 +4227,7 @@ function composerLock(root, name) {
|
|
|
4066
4227
|
function packagesLock(root, name) {
|
|
4067
4228
|
let data;
|
|
4068
4229
|
try {
|
|
4069
|
-
data = JSON.parse(
|
|
4230
|
+
data = JSON.parse(fs19.readFileSync(path19.join(root, "packages.lock.json"), "utf8"));
|
|
4070
4231
|
} catch {
|
|
4071
4232
|
return void 0;
|
|
4072
4233
|
}
|
|
@@ -4091,7 +4252,7 @@ function pypiLockVersion(root, name) {
|
|
|
4091
4252
|
function tomlPackageLock(root, file, name, normalize3) {
|
|
4092
4253
|
let text;
|
|
4093
4254
|
try {
|
|
4094
|
-
text =
|
|
4255
|
+
text = fs19.readFileSync(path19.join(root, file), "utf8");
|
|
4095
4256
|
} catch {
|
|
4096
4257
|
return void 0;
|
|
4097
4258
|
}
|
|
@@ -4108,7 +4269,7 @@ function tomlPackageLock(root, file, name, normalize3) {
|
|
|
4108
4269
|
function pipfileLock(root, name) {
|
|
4109
4270
|
let data;
|
|
4110
4271
|
try {
|
|
4111
|
-
data = JSON.parse(
|
|
4272
|
+
data = JSON.parse(fs19.readFileSync(path19.join(root, "Pipfile.lock"), "utf8"));
|
|
4112
4273
|
} catch {
|
|
4113
4274
|
return void 0;
|
|
4114
4275
|
}
|
|
@@ -4127,7 +4288,7 @@ function pipfileLock(root, name) {
|
|
|
4127
4288
|
function packageLockVersion(root, name) {
|
|
4128
4289
|
let data;
|
|
4129
4290
|
try {
|
|
4130
|
-
data = JSON.parse(
|
|
4291
|
+
data = JSON.parse(fs19.readFileSync(path19.join(root, "package-lock.json"), "utf8"));
|
|
4131
4292
|
} catch {
|
|
4132
4293
|
return void 0;
|
|
4133
4294
|
}
|
|
@@ -4139,7 +4300,7 @@ function packageLockVersion(root, name) {
|
|
|
4139
4300
|
function yarnLockVersion(root, name) {
|
|
4140
4301
|
let text;
|
|
4141
4302
|
try {
|
|
4142
|
-
text =
|
|
4303
|
+
text = fs19.readFileSync(path19.join(root, "yarn.lock"), "utf8");
|
|
4143
4304
|
} catch {
|
|
4144
4305
|
return void 0;
|
|
4145
4306
|
}
|
|
@@ -4167,19 +4328,19 @@ function headerNamesPackage(header, name) {
|
|
|
4167
4328
|
// src/engine/lib.ts
|
|
4168
4329
|
var LIB_SCHEMA = "vg-lib/1.0";
|
|
4169
4330
|
function catalogPath(root) {
|
|
4170
|
-
return
|
|
4331
|
+
return path19.join(root, "vibgrate.lib.json");
|
|
4171
4332
|
}
|
|
4172
4333
|
function libDir(root) {
|
|
4173
|
-
return
|
|
4334
|
+
return path19.join(root, ".vibgrate", "lib");
|
|
4174
4335
|
}
|
|
4175
4336
|
function libId(name) {
|
|
4176
4337
|
return name.trim().toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
4177
4338
|
}
|
|
4178
4339
|
function loadCatalog(root) {
|
|
4179
4340
|
const file = catalogPath(root);
|
|
4180
|
-
if (
|
|
4341
|
+
if (fs19.existsSync(file)) {
|
|
4181
4342
|
try {
|
|
4182
|
-
const data = JSON.parse(
|
|
4343
|
+
const data = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
4183
4344
|
if (data.schemaVersion === LIB_SCHEMA && data.libraries) return data;
|
|
4184
4345
|
} catch {
|
|
4185
4346
|
}
|
|
@@ -4187,7 +4348,7 @@ function loadCatalog(root) {
|
|
|
4187
4348
|
return { schemaVersion: LIB_SCHEMA, libraries: {} };
|
|
4188
4349
|
}
|
|
4189
4350
|
function saveCatalog(root, catalog) {
|
|
4190
|
-
|
|
4351
|
+
fs19.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
|
|
4191
4352
|
`);
|
|
4192
4353
|
}
|
|
4193
4354
|
function resolveLib(catalog, name) {
|
|
@@ -4263,23 +4424,23 @@ async function addLibrary(source, opts) {
|
|
|
4263
4424
|
content = await res.text();
|
|
4264
4425
|
type = source.endsWith("llms.txt") ? "llms.txt" : source.endsWith(".json") ? "openapi" : "website";
|
|
4265
4426
|
} else {
|
|
4266
|
-
const abs =
|
|
4267
|
-
if (!
|
|
4427
|
+
const abs = path19.resolve(root, source);
|
|
4428
|
+
if (!fs19.existsSync(abs)) throw new Error(`source not found: ${source}`);
|
|
4268
4429
|
content = readLocal(abs);
|
|
4269
4430
|
type = "local";
|
|
4270
4431
|
}
|
|
4271
4432
|
const name = opts.name ?? inferName(source);
|
|
4272
4433
|
const id = libId(name);
|
|
4273
4434
|
const version = opts.version ?? installedVersion(root, name) ?? "*";
|
|
4274
|
-
|
|
4275
|
-
const docFileAbs =
|
|
4276
|
-
|
|
4435
|
+
fs19.mkdirSync(libDir(root), { recursive: true });
|
|
4436
|
+
const docFileAbs = path19.join(libDir(root), `${id}.md`);
|
|
4437
|
+
fs19.writeFileSync(docFileAbs, content);
|
|
4277
4438
|
const entry = {
|
|
4278
4439
|
id,
|
|
4279
4440
|
name,
|
|
4280
4441
|
version,
|
|
4281
4442
|
source: { type, location: source },
|
|
4282
|
-
docFile:
|
|
4443
|
+
docFile: path19.relative(root, docFileAbs).split(path19.sep).join("/"),
|
|
4283
4444
|
docHash: hashString(content),
|
|
4284
4445
|
bytes: Buffer.byteLength(content, "utf8")
|
|
4285
4446
|
};
|
|
@@ -4289,15 +4450,15 @@ async function addLibrary(source, opts) {
|
|
|
4289
4450
|
return entry;
|
|
4290
4451
|
}
|
|
4291
4452
|
function readDoc(root, entry) {
|
|
4292
|
-
const abs =
|
|
4293
|
-
return
|
|
4453
|
+
const abs = path19.resolve(root, entry.docFile);
|
|
4454
|
+
return fs19.existsSync(abs) ? fs19.readFileSync(abs, "utf8") : "";
|
|
4294
4455
|
}
|
|
4295
4456
|
function npmPackageDir(root, name) {
|
|
4296
4457
|
let cur = root;
|
|
4297
4458
|
for (let i = 0; i < 12; i++) {
|
|
4298
|
-
const dir =
|
|
4299
|
-
if (
|
|
4300
|
-
const parent =
|
|
4459
|
+
const dir = path19.join(cur, "node_modules", name);
|
|
4460
|
+
if (fs19.existsSync(path19.join(dir, "package.json"))) return dir;
|
|
4461
|
+
const parent = path19.dirname(cur);
|
|
4301
4462
|
if (parent === cur) break;
|
|
4302
4463
|
cur = parent;
|
|
4303
4464
|
}
|
|
@@ -4308,24 +4469,24 @@ function localPackageDocs(root, name) {
|
|
|
4308
4469
|
if (!dir) return void 0;
|
|
4309
4470
|
const version = resolveVersion(root, name).served;
|
|
4310
4471
|
for (const f of ["llms.txt", "README.md", "README.mdx", "README", "readme.md"]) {
|
|
4311
|
-
const p =
|
|
4472
|
+
const p = path19.join(dir, f);
|
|
4312
4473
|
try {
|
|
4313
|
-
const docs =
|
|
4474
|
+
const docs = fs19.readFileSync(p, "utf8");
|
|
4314
4475
|
if (docs.trim()) {
|
|
4315
|
-
return { docs, version, source:
|
|
4476
|
+
return { docs, version, source: path19.relative(root, p).split(path19.sep).join("/") };
|
|
4316
4477
|
}
|
|
4317
4478
|
} catch {
|
|
4318
4479
|
}
|
|
4319
4480
|
}
|
|
4320
4481
|
try {
|
|
4321
|
-
const pkg = JSON.parse(
|
|
4482
|
+
const pkg = JSON.parse(fs19.readFileSync(path19.join(dir, "package.json"), "utf8"));
|
|
4322
4483
|
if (pkg.description?.trim()) {
|
|
4323
4484
|
return {
|
|
4324
4485
|
docs: `# ${name}
|
|
4325
4486
|
|
|
4326
4487
|
${pkg.description}`,
|
|
4327
4488
|
version: version ?? pkg.version,
|
|
4328
|
-
source:
|
|
4489
|
+
source: path19.relative(root, path19.join(dir, "package.json")).split(path19.sep).join("/")
|
|
4329
4490
|
};
|
|
4330
4491
|
}
|
|
4331
4492
|
} catch {
|
|
@@ -4334,17 +4495,17 @@ ${pkg.description}`,
|
|
|
4334
4495
|
}
|
|
4335
4496
|
function dtsEntry(dir) {
|
|
4336
4497
|
try {
|
|
4337
|
-
const pkg = JSON.parse(
|
|
4498
|
+
const pkg = JSON.parse(fs19.readFileSync(path19.join(dir, "package.json"), "utf8"));
|
|
4338
4499
|
const rel2 = pkg.types ?? pkg.typings;
|
|
4339
4500
|
if (typeof rel2 === "string") {
|
|
4340
|
-
const p =
|
|
4341
|
-
if (
|
|
4501
|
+
const p = path19.join(dir, rel2);
|
|
4502
|
+
if (fs19.existsSync(p)) return p;
|
|
4342
4503
|
}
|
|
4343
4504
|
} catch {
|
|
4344
4505
|
}
|
|
4345
4506
|
for (const c of ["index.d.ts", "dist/index.d.ts", "lib/index.d.ts", "types/index.d.ts"]) {
|
|
4346
|
-
const p =
|
|
4347
|
-
if (
|
|
4507
|
+
const p = path19.join(dir, c);
|
|
4508
|
+
if (fs19.existsSync(p)) return p;
|
|
4348
4509
|
}
|
|
4349
4510
|
return void 0;
|
|
4350
4511
|
}
|
|
@@ -4377,7 +4538,7 @@ function localApiSurface(root, name) {
|
|
|
4377
4538
|
if (!dts) return void 0;
|
|
4378
4539
|
let src;
|
|
4379
4540
|
try {
|
|
4380
|
-
src =
|
|
4541
|
+
src = fs19.readFileSync(dts, "utf8");
|
|
4381
4542
|
} catch {
|
|
4382
4543
|
return void 0;
|
|
4383
4544
|
}
|
|
@@ -4385,36 +4546,36 @@ function localApiSurface(root, name) {
|
|
|
4385
4546
|
return decls.length ? decls.join("\n\n") : void 0;
|
|
4386
4547
|
}
|
|
4387
4548
|
function cloneAndReadGit(url) {
|
|
4388
|
-
const dir =
|
|
4549
|
+
const dir = fs19.mkdtempSync(path19.join(os.tmpdir(), "vg-lib-git-"));
|
|
4389
4550
|
try {
|
|
4390
4551
|
execFileSync("git", ["clone", "--depth", "1", "--quiet", url, dir], {
|
|
4391
4552
|
stdio: ["ignore", "ignore", "pipe"]
|
|
4392
4553
|
});
|
|
4393
4554
|
} catch (err) {
|
|
4394
|
-
|
|
4555
|
+
fs19.rmSync(dir, { recursive: true, force: true });
|
|
4395
4556
|
const detail = err instanceof Error && err.message ? `: ${err.message.split("\n")[0]}` : "";
|
|
4396
4557
|
throw new Error(`git clone failed for ${url}${detail}`);
|
|
4397
4558
|
}
|
|
4398
4559
|
try {
|
|
4399
4560
|
return readGitDocs(dir);
|
|
4400
4561
|
} finally {
|
|
4401
|
-
|
|
4562
|
+
fs19.rmSync(dir, { recursive: true, force: true });
|
|
4402
4563
|
}
|
|
4403
4564
|
}
|
|
4404
4565
|
function readGitDocs(dir) {
|
|
4405
|
-
const docsDir =
|
|
4406
|
-
if (
|
|
4407
|
-
const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) =>
|
|
4408
|
-
if (readme) return
|
|
4566
|
+
const docsDir = path19.join(dir, "docs");
|
|
4567
|
+
if (fs19.existsSync(docsDir) && fs19.statSync(docsDir).isDirectory()) return readLocal(docsDir);
|
|
4568
|
+
const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path19.join(dir, f)).find((p) => fs19.existsSync(p));
|
|
4569
|
+
if (readme) return fs19.readFileSync(readme, "utf8");
|
|
4409
4570
|
return readLocal(dir);
|
|
4410
4571
|
}
|
|
4411
4572
|
function readLocal(abs) {
|
|
4412
|
-
const stat =
|
|
4413
|
-
if (stat.isFile()) return
|
|
4573
|
+
const stat = fs19.statSync(abs);
|
|
4574
|
+
if (stat.isFile()) return fs19.readFileSync(abs, "utf8");
|
|
4414
4575
|
const parts = [];
|
|
4415
|
-
const files =
|
|
4576
|
+
const files = fs19.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
|
|
4416
4577
|
for (const f of files) parts.push(`<!-- ${f} -->
|
|
4417
|
-
${
|
|
4578
|
+
${fs19.readFileSync(path19.join(abs, f), "utf8")}`);
|
|
4418
4579
|
return parts.join("\n\n");
|
|
4419
4580
|
}
|
|
4420
4581
|
function inferName(source) {
|
|
@@ -4423,10 +4584,10 @@ function inferName(source) {
|
|
|
4423
4584
|
return base.replace(/\.(md|mdx|txt|rst|json)$/i, "").replace(/^llms$/, "docs");
|
|
4424
4585
|
}
|
|
4425
4586
|
function findGitRoot(startDir = process.cwd()) {
|
|
4426
|
-
let dir =
|
|
4587
|
+
let dir = path19.resolve(startDir);
|
|
4427
4588
|
for (; ; ) {
|
|
4428
|
-
if (
|
|
4429
|
-
const parent =
|
|
4589
|
+
if (fs19.existsSync(path19.join(dir, ".git"))) return dir;
|
|
4590
|
+
const parent = path19.dirname(dir);
|
|
4430
4591
|
if (parent === dir) return null;
|
|
4431
4592
|
dir = parent;
|
|
4432
4593
|
}
|
|
@@ -4437,19 +4598,19 @@ function normalizeEntry(line) {
|
|
|
4437
4598
|
function ensureGitignored(entry, startDir = process.cwd()) {
|
|
4438
4599
|
const root = findGitRoot(startDir);
|
|
4439
4600
|
if (!root) return { status: "not-a-repo", entry };
|
|
4440
|
-
const gitignorePath =
|
|
4601
|
+
const gitignorePath = path19.join(root, ".gitignore");
|
|
4441
4602
|
const target = normalizeEntry(entry);
|
|
4442
4603
|
let existing = "";
|
|
4443
4604
|
let fileExisted = true;
|
|
4444
4605
|
try {
|
|
4445
|
-
existing =
|
|
4606
|
+
existing = fs19.readFileSync(gitignorePath, "utf8");
|
|
4446
4607
|
} catch {
|
|
4447
4608
|
fileExisted = false;
|
|
4448
4609
|
}
|
|
4449
4610
|
const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
|
|
4450
4611
|
if (alreadyPresent) return { status: "present", entry, gitignorePath };
|
|
4451
4612
|
const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
|
|
4452
|
-
|
|
4613
|
+
fs19.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
|
|
4453
4614
|
`, "utf8");
|
|
4454
4615
|
return { status: fileExisted ? "added" : "created", entry, gitignorePath };
|
|
4455
4616
|
}
|
|
@@ -4603,14 +4764,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
4603
4764
|
console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
|
|
4604
4765
|
}
|
|
4605
4766
|
if (opts.write) {
|
|
4606
|
-
const writePath =
|
|
4767
|
+
const writePath = path19.resolve(opts.write);
|
|
4607
4768
|
await writeTextFile(writePath, dsn + "\n");
|
|
4608
4769
|
console.log("");
|
|
4609
4770
|
console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
|
|
4610
|
-
const root = findGitRoot(
|
|
4611
|
-
const rel2 = root ?
|
|
4771
|
+
const root = findGitRoot(path19.dirname(writePath));
|
|
4772
|
+
const rel2 = root ? path19.relative(root, writePath).split(path19.sep).join("/") : "";
|
|
4612
4773
|
if (root && rel2 && !rel2.startsWith("..")) {
|
|
4613
|
-
const res = ensureGitignored(rel2,
|
|
4774
|
+
const res = ensureGitignored(rel2, path19.dirname(writePath));
|
|
4614
4775
|
if (res.status === "created") {
|
|
4615
4776
|
console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
|
|
4616
4777
|
} else if (res.status === "added") {
|
|
@@ -4628,30 +4789,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
|
|
|
4628
4789
|
var STORE_DIRNAME = ".vibgrate";
|
|
4629
4790
|
var STORE_FILENAME = "credentials.json";
|
|
4630
4791
|
function homeCredentialsPath() {
|
|
4631
|
-
return
|
|
4792
|
+
return path19.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
|
|
4632
4793
|
}
|
|
4633
4794
|
function projectCredentialsPath(cwd = process.cwd()) {
|
|
4634
4795
|
const root = findGitRoot(cwd) ?? cwd;
|
|
4635
|
-
return
|
|
4796
|
+
return path19.join(root, STORE_DIRNAME, STORE_FILENAME);
|
|
4636
4797
|
}
|
|
4637
4798
|
function credentialsPath(opts = {}) {
|
|
4638
4799
|
const override = process.env.VIBGRATE_CREDENTIALS?.trim();
|
|
4639
|
-
if (override) return
|
|
4800
|
+
if (override) return path19.resolve(override);
|
|
4640
4801
|
const local = projectCredentialsPath(opts.cwd);
|
|
4641
4802
|
if (opts.local) return local;
|
|
4642
|
-
if (
|
|
4803
|
+
if (fs19.existsSync(local)) return local;
|
|
4643
4804
|
return homeCredentialsPath();
|
|
4644
4805
|
}
|
|
4645
4806
|
function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
|
|
4646
|
-
const rel2 =
|
|
4647
|
-
if (rel2 && !rel2.startsWith("..") && !
|
|
4648
|
-
return rel2.split(
|
|
4807
|
+
const rel2 = path19.relative(repoRoot, credsFile);
|
|
4808
|
+
if (rel2 && !rel2.startsWith("..") && !path19.isAbsolute(rel2)) {
|
|
4809
|
+
return rel2.split(path19.sep).join("/");
|
|
4649
4810
|
}
|
|
4650
4811
|
return `${STORE_DIRNAME}/${STORE_FILENAME}`;
|
|
4651
4812
|
}
|
|
4652
4813
|
function readStoredCredentials(opts = {}) {
|
|
4653
4814
|
try {
|
|
4654
|
-
const raw =
|
|
4815
|
+
const raw = fs19.readFileSync(credentialsPath(opts), "utf8");
|
|
4655
4816
|
const parsed = JSON.parse(raw);
|
|
4656
4817
|
return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
|
|
4657
4818
|
} catch {
|
|
@@ -4660,16 +4821,16 @@ function readStoredCredentials(opts = {}) {
|
|
|
4660
4821
|
}
|
|
4661
4822
|
function writeStoredCredentials(creds, opts = {}) {
|
|
4662
4823
|
const file = credentialsPath(opts);
|
|
4663
|
-
|
|
4664
|
-
|
|
4824
|
+
fs19.mkdirSync(path19.dirname(file), { recursive: true });
|
|
4825
|
+
fs19.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
4665
4826
|
try {
|
|
4666
|
-
|
|
4827
|
+
fs19.chmodSync(file, 384);
|
|
4667
4828
|
} catch {
|
|
4668
4829
|
}
|
|
4669
4830
|
}
|
|
4670
4831
|
function clearStoredCredentials(opts = {}) {
|
|
4671
4832
|
try {
|
|
4672
|
-
|
|
4833
|
+
fs19.rmSync(credentialsPath(opts));
|
|
4673
4834
|
return true;
|
|
4674
4835
|
} catch {
|
|
4675
4836
|
return false;
|
|
@@ -5000,7 +5161,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
5000
5161
|
if (opts.strict) process.exit(1);
|
|
5001
5162
|
return;
|
|
5002
5163
|
}
|
|
5003
|
-
const filePath =
|
|
5164
|
+
const filePath = path19.resolve(opts.file);
|
|
5004
5165
|
if (!await pathExists(filePath)) {
|
|
5005
5166
|
console.error(chalk.red(`Scan artifact not found: ${filePath}`));
|
|
5006
5167
|
console.error(chalk.dim('Run "vibgrate scan" first.'));
|
|
@@ -5057,8 +5218,8 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
|
|
|
5057
5218
|
});
|
|
5058
5219
|
function readScanArtifact(root) {
|
|
5059
5220
|
try {
|
|
5060
|
-
const file =
|
|
5061
|
-
return JSON.parse(
|
|
5221
|
+
const file = path19.join(root, ".vibgrate", "scan_result.json");
|
|
5222
|
+
return JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
5062
5223
|
} catch {
|
|
5063
5224
|
return null;
|
|
5064
5225
|
}
|
|
@@ -5511,19 +5672,165 @@ function boundList(items, max) {
|
|
|
5511
5672
|
return { items: items.slice(0, max), total: items.length };
|
|
5512
5673
|
}
|
|
5513
5674
|
var NODE_EDGE_CAP = 50;
|
|
5514
|
-
var IGNORE_DIRS = /* @__PURE__ */ new Set([".git", ".vibgrate", "node_modules", "dist", "build", "out", "target", "vendor", "__pycache__"]);
|
|
5515
5675
|
var MAX_FILE_BYTES = 1e6;
|
|
5516
|
-
var MAX_FILES_SCANNED = 2e4;
|
|
5517
5676
|
var PREVIEW_CHARS = 120;
|
|
5518
|
-
|
|
5677
|
+
var NUL = "\0";
|
|
5678
|
+
var PARALLEL_MIN_BYTES = 64e6;
|
|
5679
|
+
var PARALLEL_MIN_FILES = 2e3;
|
|
5680
|
+
var ROW_CAP = 1e4;
|
|
5681
|
+
async function scanCandidates(root, files, needle, opts) {
|
|
5682
|
+
const needleLower = needle.toLowerCase();
|
|
5683
|
+
if (!needleLower || files.length === 0) return { hits: [], total: 0, truncated: false };
|
|
5684
|
+
if (!opts.collectAll) return scanInline(root, files, needleLower, opts.budget, true, void 0);
|
|
5685
|
+
if (files.length < PARALLEL_MIN_FILES && !process.env.VG_PARALLEL_MIN_BYTES) {
|
|
5686
|
+
return scanInline(root, files, needleLower, ROW_CAP, false, void 0);
|
|
5687
|
+
}
|
|
5688
|
+
const { over, sizes } = measure(root, files);
|
|
5689
|
+
if (!over) return scanInline(root, files, needleLower, ROW_CAP, false, sizes);
|
|
5690
|
+
return scanParallel(root, files, needleLower);
|
|
5691
|
+
}
|
|
5692
|
+
function parallelMinBytes() {
|
|
5693
|
+
const v = Number(process.env.VG_PARALLEL_MIN_BYTES);
|
|
5694
|
+
return Number.isFinite(v) && v >= 0 ? v : PARALLEL_MIN_BYTES;
|
|
5695
|
+
}
|
|
5696
|
+
function measure(root, files) {
|
|
5697
|
+
const threshold = parallelMinBytes();
|
|
5698
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
5699
|
+
let total = 0;
|
|
5700
|
+
for (const rel2 of files) {
|
|
5701
|
+
let size;
|
|
5702
|
+
try {
|
|
5703
|
+
size = fs19.statSync(path19.join(root, rel2)).size;
|
|
5704
|
+
} catch {
|
|
5705
|
+
continue;
|
|
5706
|
+
}
|
|
5707
|
+
sizes.set(rel2, size);
|
|
5708
|
+
if (size <= MAX_FILE_BYTES) total += size;
|
|
5709
|
+
if (total >= threshold) return { over: true, sizes };
|
|
5710
|
+
}
|
|
5711
|
+
return { over: false, sizes };
|
|
5712
|
+
}
|
|
5713
|
+
function matchLines(text, needleLower) {
|
|
5714
|
+
const low = text.toLowerCase();
|
|
5715
|
+
if (!low.includes(needleLower)) return [];
|
|
5716
|
+
const lowLines = low.split("\n");
|
|
5717
|
+
const rawLines = text.split("\n");
|
|
5718
|
+
const hits = [];
|
|
5719
|
+
for (let i = 0; i < lowLines.length; i++) {
|
|
5720
|
+
if (lowLines[i].includes(needleLower)) hits.push({ line: i + 1, preview: rawLines[i].trim().slice(0, PREVIEW_CHARS) });
|
|
5721
|
+
}
|
|
5722
|
+
return hits;
|
|
5723
|
+
}
|
|
5724
|
+
function readCandidate(abs, knownSize) {
|
|
5725
|
+
try {
|
|
5726
|
+
const size = knownSize ?? fs19.statSync(abs).size;
|
|
5727
|
+
if (size > MAX_FILE_BYTES) return null;
|
|
5728
|
+
const text = fs19.readFileSync(abs, "utf8");
|
|
5729
|
+
return text.includes(NUL) ? null : text;
|
|
5730
|
+
} catch {
|
|
5731
|
+
return null;
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
|
|
5735
|
+
const hits = [];
|
|
5736
|
+
let total = 0;
|
|
5737
|
+
let truncated = false;
|
|
5738
|
+
for (const rel2 of files) {
|
|
5739
|
+
const text = readCandidate(path19.join(root, rel2), sizes?.get(rel2));
|
|
5740
|
+
if (text === null) continue;
|
|
5741
|
+
for (const h of matchLines(text, needleLower)) {
|
|
5742
|
+
total++;
|
|
5743
|
+
if (hits.length < cap) hits.push({ file: rel2, line: h.line, preview: h.preview });
|
|
5744
|
+
else {
|
|
5745
|
+
truncated = true;
|
|
5746
|
+
if (stopAtCap) return { hits, total, truncated };
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
5750
|
+
return { hits, total, truncated };
|
|
5751
|
+
}
|
|
5752
|
+
async function scanParallel(root, files, needleLower) {
|
|
5753
|
+
const n = Math.min(files.length, Math.max(2, Math.min(8, os.cpus().length - 1)));
|
|
5754
|
+
const shards = Array.from({ length: n }, () => []);
|
|
5755
|
+
files.forEach((f, i) => shards[i % n].push(f));
|
|
5756
|
+
let results;
|
|
5757
|
+
try {
|
|
5758
|
+
results = await Promise.all(shards.map((shard) => runWorker(root, shard, needleLower)));
|
|
5759
|
+
} catch {
|
|
5760
|
+
return scanInline(root, files, needleLower, ROW_CAP, false, void 0);
|
|
5761
|
+
}
|
|
5762
|
+
const hits = [];
|
|
5763
|
+
let total = 0;
|
|
5764
|
+
let truncated = false;
|
|
5765
|
+
for (const r of results) {
|
|
5766
|
+
total += r.total;
|
|
5767
|
+
truncated = truncated || r.truncated;
|
|
5768
|
+
for (const h of r.hits) hits.push(h);
|
|
5769
|
+
}
|
|
5770
|
+
hits.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line);
|
|
5771
|
+
if (hits.length > ROW_CAP) {
|
|
5772
|
+
hits.length = ROW_CAP;
|
|
5773
|
+
truncated = true;
|
|
5774
|
+
}
|
|
5775
|
+
return { hits, total, truncated };
|
|
5776
|
+
}
|
|
5777
|
+
var WORKER_SOURCE = [
|
|
5778
|
+
"const { parentPort, workerData } = require('worker_threads');",
|
|
5779
|
+
"const fs = require('fs');",
|
|
5780
|
+
"const path = require('path');",
|
|
5781
|
+
`const MAX = ${MAX_FILE_BYTES};`,
|
|
5782
|
+
`const PREVIEW = ${PREVIEW_CHARS};`,
|
|
5783
|
+
`const CAP = ${ROW_CAP};`,
|
|
5784
|
+
"const NUL = String.fromCharCode(0);",
|
|
5785
|
+
"const NL = String.fromCharCode(10);",
|
|
5786
|
+
"const { root, files, needleLower } = workerData;",
|
|
5787
|
+
"const hits = []; let total = 0, truncated = false;",
|
|
5788
|
+
"for (const rel of files) {",
|
|
5789
|
+
" const abs = path.join(root, rel);",
|
|
5790
|
+
" let text;",
|
|
5791
|
+
' try { if (fs.statSync(abs).size > MAX) continue; text = fs.readFileSync(abs, "utf8"); } catch { continue; }',
|
|
5792
|
+
" if (text.includes(NUL)) continue;",
|
|
5793
|
+
" const low = text.toLowerCase();",
|
|
5794
|
+
" if (!low.includes(needleLower)) continue;",
|
|
5795
|
+
" const lowLines = low.split(NL); const rawLines = text.split(NL);",
|
|
5796
|
+
" for (let i = 0; i < lowLines.length; i++) {",
|
|
5797
|
+
" if (!lowLines[i].includes(needleLower)) continue;",
|
|
5798
|
+
" total++;",
|
|
5799
|
+
" if (hits.length < CAP) hits.push({ file: rel, line: i + 1, preview: rawLines[i].trim().slice(0, PREVIEW) });",
|
|
5800
|
+
" else truncated = true;",
|
|
5801
|
+
" }",
|
|
5802
|
+
"}",
|
|
5803
|
+
"parentPort.postMessage({ hits, total, truncated });"
|
|
5804
|
+
].join("\n");
|
|
5805
|
+
function runWorker(root, files, needleLower) {
|
|
5806
|
+
return new Promise((resolve12, reject) => {
|
|
5807
|
+
const w = new Worker(WORKER_SOURCE, { eval: true, workerData: { root, files, needleLower } });
|
|
5808
|
+
let settled = false;
|
|
5809
|
+
w.once("message", (m) => {
|
|
5810
|
+
settled = true;
|
|
5811
|
+
resolve12(m);
|
|
5812
|
+
});
|
|
5813
|
+
w.once("error", reject);
|
|
5814
|
+
w.once("exit", (code) => {
|
|
5815
|
+
if (!settled && code !== 0) reject(new Error(`literal-scan worker exited with code ${code}`));
|
|
5816
|
+
});
|
|
5817
|
+
});
|
|
5818
|
+
}
|
|
5819
|
+
|
|
5820
|
+
// src/engine/search.ts
|
|
5821
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([".git", ".vibgrate", "node_modules", "dist", "build", "out", "target", "vendor", "__pycache__"]);
|
|
5822
|
+
var MAX_FILES_SCANNED = 2e4;
|
|
5823
|
+
async function searchSymbols(graph, root, query, limit) {
|
|
5519
5824
|
const q2 = query.trim();
|
|
5520
5825
|
if (!q2) return { matches: [], moreAvailable: false, hint: "query is required" };
|
|
5826
|
+
const isPhrase = /\s/.test(q2);
|
|
5521
5827
|
let nodes = findNodes(graph, q2).filter((n) => n.kind !== "file");
|
|
5522
5828
|
if (nodes.length === 0) {
|
|
5523
5829
|
const tokens = queryTokens(q2);
|
|
5524
5830
|
if (tokens.length >= 2) nodes = multiTokenNodes(graph, tokens);
|
|
5525
5831
|
}
|
|
5526
|
-
const
|
|
5832
|
+
const symbolCap = isPhrase ? Math.max(1, Math.floor(limit / 3)) : limit;
|
|
5833
|
+
const symbolHits = nodes.slice(0, symbolCap).map((n, i) => ({
|
|
5527
5834
|
kind: n.kind,
|
|
5528
5835
|
name: n.qualifiedName,
|
|
5529
5836
|
file: n.file,
|
|
@@ -5533,9 +5840,12 @@ function searchSymbols(graph, root, query, limit) {
|
|
|
5533
5840
|
const spare = limit - symbolHits.length;
|
|
5534
5841
|
const textHits = [];
|
|
5535
5842
|
let truncatedScan = false;
|
|
5843
|
+
let totalTextMatches;
|
|
5536
5844
|
if (spare > 0) {
|
|
5537
5845
|
const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
|
|
5538
|
-
|
|
5846
|
+
const scan = await scanFiles(root, q2, spare, seen, textHits, isPhrase);
|
|
5847
|
+
truncatedScan = scan.truncated;
|
|
5848
|
+
if (isPhrase) totalTextMatches = scan.total;
|
|
5539
5849
|
}
|
|
5540
5850
|
const matches = [...symbolHits, ...textHits];
|
|
5541
5851
|
if (matches.length === 0) {
|
|
@@ -5545,7 +5855,14 @@ function searchSymbols(graph, root, query, limit) {
|
|
|
5545
5855
|
hint: "no symbol or text match \u2014 for meaning-level questions (symptoms, relationships, what-breaks-if) use query_graph"
|
|
5546
5856
|
};
|
|
5547
5857
|
}
|
|
5548
|
-
|
|
5858
|
+
const literalTruncated = totalTextMatches !== void 0 && totalTextMatches > textHits.length;
|
|
5859
|
+
const moreAvailable = isPhrase ? literalTruncated || truncatedScan : nodes.length > symbolHits.length || truncatedScan;
|
|
5860
|
+
const result = { matches, moreAvailable };
|
|
5861
|
+
if (totalTextMatches !== void 0) result.totalTextMatches = totalTextMatches;
|
|
5862
|
+
if (literalTruncated) {
|
|
5863
|
+
result.hint = `showing ${textHits.length} of ${totalTextMatches}${truncatedScan ? "+" : ""} literal matches \u2014 raise limit or narrow the query to get them all`;
|
|
5864
|
+
}
|
|
5865
|
+
return result;
|
|
5549
5866
|
}
|
|
5550
5867
|
function queryTokens(q2) {
|
|
5551
5868
|
return [...new Set(q2.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2))];
|
|
@@ -5562,48 +5879,84 @@ function multiTokenNodes(graph, tokens) {
|
|
|
5562
5879
|
}
|
|
5563
5880
|
return [...cov.values()].sort((a, b) => b.hits - a.hits || b.node.importance - a.node.importance).map((e) => e.node);
|
|
5564
5881
|
}
|
|
5565
|
-
function scanFiles(root, needle, budget, seen, out) {
|
|
5566
|
-
const
|
|
5567
|
-
|
|
5882
|
+
async function scanFiles(root, needle, budget, seen, out, countAll) {
|
|
5883
|
+
const listing = listCandidateFiles(root, needle);
|
|
5884
|
+
const outcome = await scanCandidates(root, listing.files, needle, { collectAll: countAll, budget });
|
|
5885
|
+
let overlap = 0;
|
|
5886
|
+
for (const h of outcome.hits) {
|
|
5887
|
+
if (seen.has(`${h.file}:${h.line}`)) {
|
|
5888
|
+
overlap++;
|
|
5889
|
+
continue;
|
|
5890
|
+
}
|
|
5891
|
+
seen.add(`${h.file}:${h.line}`);
|
|
5892
|
+
if (out.length < budget) out.push({ kind: "text", file: h.file, line: h.line, preview: h.preview });
|
|
5893
|
+
}
|
|
5894
|
+
return { total: outcome.total - overlap, truncated: listing.truncated || outcome.truncated };
|
|
5895
|
+
}
|
|
5896
|
+
function listCandidateFiles(root, needle) {
|
|
5897
|
+
return ripgrepCandidates(root, needle) ?? walkCandidates(root);
|
|
5898
|
+
}
|
|
5899
|
+
function walkCandidates(root) {
|
|
5900
|
+
const files = [];
|
|
5901
|
+
let truncated = false;
|
|
5568
5902
|
const stack = [root];
|
|
5569
5903
|
while (stack.length) {
|
|
5570
5904
|
const dir = stack.pop();
|
|
5571
5905
|
let entries;
|
|
5572
5906
|
try {
|
|
5573
|
-
entries =
|
|
5907
|
+
entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
5574
5908
|
} catch {
|
|
5575
5909
|
continue;
|
|
5576
5910
|
}
|
|
5577
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
5578
5911
|
for (const e of entries) {
|
|
5579
5912
|
if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
|
|
5580
|
-
const abs =
|
|
5913
|
+
const abs = path19.join(dir, e.name);
|
|
5581
5914
|
if (e.isDirectory()) {
|
|
5582
5915
|
stack.push(abs);
|
|
5583
5916
|
continue;
|
|
5584
5917
|
}
|
|
5585
|
-
if (
|
|
5586
|
-
|
|
5587
|
-
try {
|
|
5588
|
-
if (fs18.statSync(abs).size > MAX_FILE_BYTES) continue;
|
|
5589
|
-
text = fs18.readFileSync(abs, "utf8");
|
|
5590
|
-
} catch {
|
|
5918
|
+
if (files.length >= MAX_FILES_SCANNED) {
|
|
5919
|
+
truncated = true;
|
|
5591
5920
|
continue;
|
|
5592
5921
|
}
|
|
5593
|
-
|
|
5594
|
-
const rel2 = path18.relative(root, abs);
|
|
5595
|
-
const lines = text.split("\n");
|
|
5596
|
-
for (let i = 0; i < lines.length; i++) {
|
|
5597
|
-
if (!lines[i].toLowerCase().includes(lower)) continue;
|
|
5598
|
-
const key = `${rel2}:${i + 1}`;
|
|
5599
|
-
if (seen.has(key)) continue;
|
|
5600
|
-
seen.add(key);
|
|
5601
|
-
out.push({ kind: "text", file: rel2, line: i + 1, preview: lines[i].trim().slice(0, PREVIEW_CHARS) });
|
|
5602
|
-
if (out.length >= budget) return true;
|
|
5603
|
-
}
|
|
5922
|
+
files.push(path19.relative(root, abs));
|
|
5604
5923
|
}
|
|
5605
5924
|
}
|
|
5606
|
-
|
|
5925
|
+
files.sort();
|
|
5926
|
+
return { files, truncated };
|
|
5927
|
+
}
|
|
5928
|
+
var rgAvailable;
|
|
5929
|
+
function hasRipgrep() {
|
|
5930
|
+
if (process.env.VG_DISABLE_RIPGREP) return false;
|
|
5931
|
+
if (rgAvailable === void 0) {
|
|
5932
|
+
try {
|
|
5933
|
+
rgAvailable = spawnSync("rg", ["--version"], { stdio: "ignore", timeout: 3e3 }).status === 0;
|
|
5934
|
+
} catch {
|
|
5935
|
+
rgAvailable = false;
|
|
5936
|
+
}
|
|
5937
|
+
}
|
|
5938
|
+
return rgAvailable;
|
|
5939
|
+
}
|
|
5940
|
+
function ripgrepCandidates(root, needle) {
|
|
5941
|
+
if (!hasRipgrep()) return null;
|
|
5942
|
+
const globs = [...IGNORE_DIRS].flatMap((d) => ["--glob", `!**/${d}/**`, "--glob", `!${d}/**`]);
|
|
5943
|
+
const res = spawnSync(
|
|
5944
|
+
"rg",
|
|
5945
|
+
["--files-with-matches", "--fixed-strings", "--ignore-case", "--no-ignore", "--no-messages", ...globs, "--", needle, "."],
|
|
5946
|
+
{ cwd: root, encoding: "utf8", timeout: 15e3, maxBuffer: 32 * 1024 * 1024 }
|
|
5947
|
+
);
|
|
5948
|
+
if (res.error || res.status !== 0 && res.status !== 1 || res.signal) return null;
|
|
5949
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5950
|
+
for (const raw of res.stdout.split("\n")) {
|
|
5951
|
+
const rel2 = raw.startsWith("./") ? raw.slice(2) : raw;
|
|
5952
|
+
if (!rel2) continue;
|
|
5953
|
+
const segs = rel2.split("/");
|
|
5954
|
+
if (segs.some((s) => s.startsWith(".") || IGNORE_DIRS.has(s))) continue;
|
|
5955
|
+
seen.add(rel2);
|
|
5956
|
+
}
|
|
5957
|
+
const files = [...seen].sort();
|
|
5958
|
+
if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
|
|
5959
|
+
return { files, truncated: false };
|
|
5607
5960
|
}
|
|
5608
5961
|
|
|
5609
5962
|
// src/mcp/tools.ts
|
|
@@ -5722,16 +6075,16 @@ var TOOLS = [
|
|
|
5722
6075
|
},
|
|
5723
6076
|
{
|
|
5724
6077
|
name: "search_symbols",
|
|
5725
|
-
description: "Find a known name or string fast: ranked symbol lookup
|
|
6078
|
+
description: "Find a known name or literal string fast: ranked symbol lookup, plus a complete literal file-search for any quoted/multi-word phrase (config keys, log lines, UI copy). A phrase query reports totalTextMatches so you know you have every occurrence \u2014 use it instead of grep. Use first for most discovery; use query_graph for meaning.",
|
|
5726
6079
|
inputSchema: obj(
|
|
5727
6080
|
{
|
|
5728
|
-
query: { type: "string", maxLength: 120 },
|
|
5729
|
-
limit: { type: "number", description: "default 8, max
|
|
6081
|
+
query: { type: "string", maxLength: 120, description: "a symbol name, or a literal phrase to sweep for every occurrence of" },
|
|
6082
|
+
limit: { type: "number", description: "default 8, max 50 (raise it to fetch a whole literal sweep in one call)" }
|
|
5730
6083
|
},
|
|
5731
6084
|
["query"]
|
|
5732
6085
|
),
|
|
5733
|
-
handler: (graph, args, ctx) => {
|
|
5734
|
-
const limit = Math.min(
|
|
6086
|
+
handler: async (graph, args, ctx) => {
|
|
6087
|
+
const limit = Math.min(50, numOr(args.limit, 8));
|
|
5735
6088
|
return searchSymbols(graph, ctx.root, String(args.query ?? ""), limit);
|
|
5736
6089
|
}
|
|
5737
6090
|
},
|
|
@@ -5787,7 +6140,7 @@ var TOOLS = [
|
|
|
5787
6140
|
handler: (graph, args, ctx) => {
|
|
5788
6141
|
const { node, candidates } = resolveOne(graph, String(args.name ?? ""), numOrU(args.pick));
|
|
5789
6142
|
if (!node) return unresolved(candidates);
|
|
5790
|
-
const index =
|
|
6143
|
+
const index = indexFor(graph);
|
|
5791
6144
|
const edgeOk = edgeRecordFilter(args);
|
|
5792
6145
|
const calleeRecs = index.callees(node.id).filter(edgeOk);
|
|
5793
6146
|
const callerRecs = index.callers(node.id).filter(edgeOk);
|
|
@@ -6273,104 +6626,6 @@ function navigationToolsetConfig(serverName = "vibgrate") {
|
|
|
6273
6626
|
configs: Object.fromEntries(HOT_TOOLS.map((n) => [n, { defer_loading: false }]))
|
|
6274
6627
|
};
|
|
6275
6628
|
}
|
|
6276
|
-
var LEDGER = "savings.jsonl";
|
|
6277
|
-
var PER_FILE_TOKENS = 400;
|
|
6278
|
-
var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node"]);
|
|
6279
|
-
function ledgerPath(root) {
|
|
6280
|
-
return path18.join(cacheDir(root), LEDGER);
|
|
6281
|
-
}
|
|
6282
|
-
function savingsRecorded(root) {
|
|
6283
|
-
return fs18.existsSync(ledgerPath(root));
|
|
6284
|
-
}
|
|
6285
|
-
function recordSaving(root, entry, now) {
|
|
6286
|
-
try {
|
|
6287
|
-
fs18.mkdirSync(cacheDir(root), { recursive: true });
|
|
6288
|
-
const line = JSON.stringify({ ts: now, ...entry });
|
|
6289
|
-
fs18.appendFileSync(ledgerPath(root), line + "\n");
|
|
6290
|
-
} catch {
|
|
6291
|
-
}
|
|
6292
|
-
}
|
|
6293
|
-
var DEFAULT_RATE_PER_M = 3;
|
|
6294
|
-
var DEFAULT_RATE_LABEL = "input @ $3/1M";
|
|
6295
|
-
function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
|
|
6296
|
-
const file = ledgerPath(root);
|
|
6297
|
-
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
6298
|
-
let queries = 0;
|
|
6299
|
-
let vgTokens = 0;
|
|
6300
|
-
let baselineTokens = 0;
|
|
6301
|
-
if (fs18.existsSync(file)) {
|
|
6302
|
-
for (const line of fs18.readFileSync(file, "utf8").split("\n")) {
|
|
6303
|
-
if (!line.trim()) continue;
|
|
6304
|
-
try {
|
|
6305
|
-
const e = JSON.parse(line);
|
|
6306
|
-
if (e.ts < cutoff) continue;
|
|
6307
|
-
if (!SAVINGS_TOOLS.has(e.tool)) continue;
|
|
6308
|
-
queries++;
|
|
6309
|
-
vgTokens += e.vgTokens;
|
|
6310
|
-
baselineTokens += e.baselineTokens;
|
|
6311
|
-
} catch {
|
|
6312
|
-
}
|
|
6313
|
-
}
|
|
6314
|
-
}
|
|
6315
|
-
const estCostVg = vgTokens / 1e6 * ratePerM;
|
|
6316
|
-
const estCostBaseline = baselineTokens / 1e6 * ratePerM;
|
|
6317
|
-
return {
|
|
6318
|
-
enabled: savingsRecorded(root),
|
|
6319
|
-
days,
|
|
6320
|
-
queries,
|
|
6321
|
-
vgTokens,
|
|
6322
|
-
baselineTokens,
|
|
6323
|
-
ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
|
|
6324
|
-
estCostVg: round22(estCostVg),
|
|
6325
|
-
estCostBaseline: round22(estCostBaseline),
|
|
6326
|
-
saved: round22(estCostBaseline - estCostVg),
|
|
6327
|
-
rateLabel: DEFAULT_RATE_LABEL
|
|
6328
|
-
};
|
|
6329
|
-
}
|
|
6330
|
-
function round22(x) {
|
|
6331
|
-
return Math.round(x * 100) / 100;
|
|
6332
|
-
}
|
|
6333
|
-
function readUsage(root, days, now) {
|
|
6334
|
-
const file = ledgerPath(root);
|
|
6335
|
-
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
6336
|
-
const byTool = /* @__PURE__ */ new Map();
|
|
6337
|
-
if (fs18.existsSync(file)) {
|
|
6338
|
-
for (const line of fs18.readFileSync(file, "utf8").split("\n")) {
|
|
6339
|
-
if (!line.trim()) continue;
|
|
6340
|
-
try {
|
|
6341
|
-
const e = JSON.parse(line);
|
|
6342
|
-
if (e.ts < cutoff) continue;
|
|
6343
|
-
const outcome = e.outcome ?? "complete";
|
|
6344
|
-
const row = byTool.get(e.tool) ?? { complete: 0, partial: 0, miss: 0 };
|
|
6345
|
-
row[outcome]++;
|
|
6346
|
-
byTool.set(e.tool, row);
|
|
6347
|
-
} catch {
|
|
6348
|
-
}
|
|
6349
|
-
}
|
|
6350
|
-
}
|
|
6351
|
-
const commands = [...byTool.entries()].map(([tool, r]) => {
|
|
6352
|
-
const calls = r.complete + r.partial + r.miss;
|
|
6353
|
-
return {
|
|
6354
|
-
tool,
|
|
6355
|
-
calls,
|
|
6356
|
-
complete: r.complete,
|
|
6357
|
-
partial: r.partial,
|
|
6358
|
-
miss: r.miss,
|
|
6359
|
-
successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
|
|
6360
|
-
};
|
|
6361
|
-
}).sort((a, b) => b.calls - a.calls || a.tool.localeCompare(b.tool));
|
|
6362
|
-
const totals = commands.reduce(
|
|
6363
|
-
(t, c) => ({
|
|
6364
|
-
calls: t.calls + c.calls,
|
|
6365
|
-
complete: t.complete + c.complete,
|
|
6366
|
-
partial: t.partial + c.partial,
|
|
6367
|
-
miss: t.miss + c.miss
|
|
6368
|
-
}),
|
|
6369
|
-
{ calls: 0, complete: 0, partial: 0, miss: 0 }
|
|
6370
|
-
);
|
|
6371
|
-
const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
|
|
6372
|
-
return { enabled: savingsRecorded(root), days, commands, totals, avgSuccessPct };
|
|
6373
|
-
}
|
|
6374
6629
|
var PROBE_INTERVAL_MS = 2e3;
|
|
6375
6630
|
var MAX_PROBE_INTERVAL_MS = 3e4;
|
|
6376
6631
|
var PROBE_DUTY_FACTOR = 20;
|
|
@@ -6381,7 +6636,7 @@ var GraphSource = class {
|
|
|
6381
6636
|
this.graphPath = graphPath;
|
|
6382
6637
|
this.refresh = refresh;
|
|
6383
6638
|
this.tuning = tuning;
|
|
6384
|
-
this.root =
|
|
6639
|
+
this.root = path19.dirname(path19.dirname(graphPath));
|
|
6385
6640
|
}
|
|
6386
6641
|
graphPath;
|
|
6387
6642
|
refresh;
|
|
@@ -6397,9 +6652,9 @@ var GraphSource = class {
|
|
|
6397
6652
|
/** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
|
|
6398
6653
|
async get() {
|
|
6399
6654
|
if (this.refresh) await this.maybeRefresh();
|
|
6400
|
-
const stat =
|
|
6655
|
+
const stat = fs19.statSync(this.graphPath);
|
|
6401
6656
|
if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
|
|
6402
|
-
this.cached = parseGraph(
|
|
6657
|
+
this.cached = parseGraph(fs19.readFileSync(this.graphPath, "utf8"));
|
|
6403
6658
|
this.cachedMtimeMs = stat.mtimeMs;
|
|
6404
6659
|
}
|
|
6405
6660
|
return this.cached;
|
|
@@ -6433,8 +6688,9 @@ var GraphSource = class {
|
|
|
6433
6688
|
}
|
|
6434
6689
|
};
|
|
6435
6690
|
function createServer(source, opts = {}) {
|
|
6436
|
-
const { savings = false, local = false, dedup = false } = opts;
|
|
6437
|
-
const
|
|
6691
|
+
const { savings = false, shareStats = false, local = false, dedup = false } = opts;
|
|
6692
|
+
const record = savings || shareStats;
|
|
6693
|
+
const root = path19.dirname(path19.dirname(source.graphPath));
|
|
6438
6694
|
const seen = /* @__PURE__ */ new Set();
|
|
6439
6695
|
const server = new Server(
|
|
6440
6696
|
{ name: "vg", version: VERSION },
|
|
@@ -6443,7 +6699,7 @@ function createServer(source, opts = {}) {
|
|
|
6443
6699
|
// Routing guidance once at the server level (hosts that surface
|
|
6444
6700
|
// `instructions` get it at zero per-step schema cost): the flashlight
|
|
6445
6701
|
// vs the map.
|
|
6446
|
-
instructions: 'vg is a code map. Use search_symbols to find a known name or literal string fast. Use orient/query_graph for meaning: symptoms, relationships, and what-breaks-if. Responses are concise by default; pass response_format:"detailed" only when a node proves load-bearing. Navigate as little as possible: one good search/query usually locates the code. As soon as you have the file and line, read that file and make the edit \u2014 do not call further graph tools unless the edit fails or the match was wrong.'
|
|
6702
|
+
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.'
|
|
6447
6703
|
}
|
|
6448
6704
|
);
|
|
6449
6705
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -6470,7 +6726,7 @@ function createServer(source, opts = {}) {
|
|
|
6470
6726
|
try {
|
|
6471
6727
|
const args = request.params.arguments ?? {};
|
|
6472
6728
|
const result = await tool.handler(graph, args, { root, local, dedup, seen });
|
|
6473
|
-
if (
|
|
6729
|
+
if (record) recordUsage(root, tool.name, result, detectClient(server));
|
|
6474
6730
|
return renderToolResult(result);
|
|
6475
6731
|
} catch (err) {
|
|
6476
6732
|
return errorResult(`tool "${tool.name}" failed: ${err.message}`);
|
|
@@ -6490,7 +6746,7 @@ function sleep(ms) {
|
|
|
6490
6746
|
timer.unref?.();
|
|
6491
6747
|
});
|
|
6492
6748
|
}
|
|
6493
|
-
function recordUsage(root, tool, result) {
|
|
6749
|
+
function recordUsage(root, tool, result, client) {
|
|
6494
6750
|
const outcome = classifyOutcome(result);
|
|
6495
6751
|
let vgTokens = 0;
|
|
6496
6752
|
let baselineTokens = 0;
|
|
@@ -6498,7 +6754,19 @@ function recordUsage(root, tool, result) {
|
|
|
6498
6754
|
vgTokens = countTokens(renderedText(renderToolResult(result)));
|
|
6499
6755
|
baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
|
|
6500
6756
|
}
|
|
6501
|
-
recordSaving(
|
|
6757
|
+
recordSaving(
|
|
6758
|
+
root,
|
|
6759
|
+
{ tool, outcome, vgTokens, baselineTokens, source: "mcp", client: sanitizeClient(client) },
|
|
6760
|
+
Date.now()
|
|
6761
|
+
);
|
|
6762
|
+
}
|
|
6763
|
+
function detectClient(server) {
|
|
6764
|
+
try {
|
|
6765
|
+
const info = server.getClientVersion?.();
|
|
6766
|
+
return typeof info?.name === "string" ? info.name : void 0;
|
|
6767
|
+
} catch {
|
|
6768
|
+
return void 0;
|
|
6769
|
+
}
|
|
6502
6770
|
}
|
|
6503
6771
|
function classifyOutcome(result) {
|
|
6504
6772
|
if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
|
|
@@ -6549,7 +6817,11 @@ function errorResult(message) {
|
|
|
6549
6817
|
// src/install/content.ts
|
|
6550
6818
|
var NUDGE_BEGIN = "<!-- vg:begin -->";
|
|
6551
6819
|
var NUDGE_END = "<!-- vg:end -->";
|
|
6552
|
-
function
|
|
6820
|
+
function clientFlag(client) {
|
|
6821
|
+
return client ? `--client=${client}` : "--client=<your-ai>";
|
|
6822
|
+
}
|
|
6823
|
+
function skillMarkdown(client) {
|
|
6824
|
+
const cf = clientFlag(client);
|
|
6553
6825
|
return `---
|
|
6554
6826
|
name: vg
|
|
6555
6827
|
description: Query the local code graph (vg) for structure, impact, and navigation instead of grepping/reading many files.
|
|
@@ -6560,13 +6832,26 @@ description: Query the local code graph (vg) for structure, impact, and navigati
|
|
|
6560
6832
|
This repo has a deterministic code graph built by \`vg\`. Prefer it over reading or
|
|
6561
6833
|
grepping many files \u2014 it is smaller, more relevant context, and free.
|
|
6562
6834
|
|
|
6563
|
-
##
|
|
6835
|
+
## Prefer the MCP tools
|
|
6836
|
+
|
|
6837
|
+
If the \`vg\` MCP server is registered (it is after \`vg install\`), call its
|
|
6838
|
+
read-only tools directly \u2014 they are the **fastest** path. The server keeps the
|
|
6839
|
+
map parsed, the relation index warm, and the embedding model loaded across calls,
|
|
6840
|
+
so each query is cheaper than spawning the CLI fresh. Use:
|
|
6841
|
+
\`query_graph\`, \`get_node\`, \`impact_of\`, \`find_path\`, \`list_hubs\`, \`list_areas\`,
|
|
6842
|
+
\`get_graph_summary\`, \`search_symbols\`. They are side-effect-free and
|
|
6843
|
+
auto-approvable, and the server records which client is calling automatically.
|
|
6844
|
+
|
|
6845
|
+
## If you use the \`vg\` CLI instead
|
|
6564
6846
|
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
- **
|
|
6847
|
+
When the MCP server isn't available, use the CLI \u2014 and **always pass \`${cf}\`**
|
|
6848
|
+
so your calls are counted (that's how the CLI-vs-MCP split is measured and the
|
|
6849
|
+
tools improved):
|
|
6850
|
+
|
|
6851
|
+
- **Understand code:** \`vg "<question>" ${cf}\` \u2014 a budget-bounded, fact-annotated context block.
|
|
6852
|
+
- **Find a symbol:** \`vg show <name> ${cf}\` \u2014 what it is, what it calls, what calls it.
|
|
6853
|
+
- **Before changing something:** \`vg impact <name> ${cf}\` \u2014 what breaks if you change it.
|
|
6854
|
+
- **Navigate:** \`vg path <A> <B> ${cf}\`, \`vg tree <name> ${cf}\`.
|
|
6570
6855
|
|
|
6571
6856
|
## Dependencies & library docs
|
|
6572
6857
|
|
|
@@ -6576,12 +6861,6 @@ grepping many files \u2014 it is smaller, more relevant context, and free.
|
|
|
6576
6861
|
- **Version-correct docs:** \`vg lib <name>\` returns drift-annotated, version-
|
|
6577
6862
|
specific usage docs for a library \u2014 inject these instead of guessing an API.
|
|
6578
6863
|
|
|
6579
|
-
## Via MCP
|
|
6580
|
-
|
|
6581
|
-
If the \`vg\` MCP server is registered, call its read-only tools directly:
|
|
6582
|
-
\`query_graph\`, \`get_node\`, \`impact_of\`, \`find_path\`, \`list_hubs\`, \`list_areas\`,
|
|
6583
|
-
\`get_graph_summary\`. They are side-effect-free and auto-approvable.
|
|
6584
|
-
|
|
6585
6864
|
## Keep it fresh
|
|
6586
6865
|
|
|
6587
6866
|
The map keeps itself fresh: \`vg ask\` and the MCP tools detect changed files
|
|
@@ -6591,28 +6870,33 @@ The map lives at \`.vibgrate/graph.json\` and is deterministic (byte-identical
|
|
|
6591
6870
|
across machines).
|
|
6592
6871
|
`;
|
|
6593
6872
|
}
|
|
6594
|
-
function nudgeMarkdown(smallRepo) {
|
|
6873
|
+
function nudgeMarkdown(smallRepo, client) {
|
|
6874
|
+
const cf = clientFlag(client);
|
|
6595
6875
|
if (smallRepo) {
|
|
6596
6876
|
return `${NUDGE_BEGIN}
|
|
6597
6877
|
## Code navigation (vg)
|
|
6598
6878
|
|
|
6599
6879
|
This repo is small enough that searching files directly is fine. Still, before a
|
|
6600
|
-
change, \`vg impact <name
|
|
6601
|
-
compact, cited context block when you want one.
|
|
6880
|
+
change, \`vg impact <name> ${cf}\` shows what it breaks, and \`vg "<question>" ${cf}\`
|
|
6881
|
+
gives a compact, cited context block when you want one. Prefer the \`vg\` MCP tools
|
|
6882
|
+
when the server is registered; when you use the CLI, pass \`${cf}\` so the call is
|
|
6883
|
+
counted.
|
|
6602
6884
|
${NUDGE_END}`;
|
|
6603
6885
|
}
|
|
6604
6886
|
return `${NUDGE_BEGIN}
|
|
6605
6887
|
## Code navigation (vg) \u2014 prefer the map
|
|
6606
6888
|
|
|
6607
6889
|
A deterministic code graph is available. It is usually the faster, cheaper path
|
|
6608
|
-
than reading or grepping many files
|
|
6890
|
+
than reading or grepping many files. Prefer the \`vg\` MCP tools when the server is
|
|
6891
|
+
registered (fastest \u2014 the map and model stay warm across calls). When you use the
|
|
6892
|
+
CLI instead, pass \`${cf}\` so the call is counted:
|
|
6609
6893
|
|
|
6610
|
-
- \`vg "<question>"\` \u2014 a compact, fact-annotated context block for a question.
|
|
6611
|
-
- \`vg show <name
|
|
6612
|
-
- \`vg impact <name
|
|
6894
|
+
- \`vg "<question>" ${cf}\` \u2014 a compact, fact-annotated context block for a question.
|
|
6895
|
+
- \`vg show <name> ${cf}\` \u2014 what a symbol is, its callers and callees.
|
|
6896
|
+
- \`vg impact <name> ${cf}\` \u2014 what breaks if you change it.
|
|
6613
6897
|
|
|
6614
|
-
|
|
6615
|
-
|
|
6898
|
+
This is advisory, not mandatory \u2014 remove it with \`vg uninstall\` (or delete this
|
|
6899
|
+
block).
|
|
6616
6900
|
${NUDGE_END}`;
|
|
6617
6901
|
}
|
|
6618
6902
|
function mcpServerEntry(launch = { command: "vg", args: ["serve"] }) {
|
|
@@ -6702,19 +6986,19 @@ function installAssistant(a, opts) {
|
|
|
6702
6986
|
const skipped = [];
|
|
6703
6987
|
let note;
|
|
6704
6988
|
if (a.skill) {
|
|
6705
|
-
writeFileEnsured(
|
|
6989
|
+
writeFileEnsured(path19.join(opts.root, a.skill), skillMarkdown(a.id));
|
|
6706
6990
|
wrote.push(a.skill);
|
|
6707
6991
|
}
|
|
6708
6992
|
if (a.mcp) {
|
|
6709
6993
|
const launch = opts.launch ?? detectServeLaunch();
|
|
6710
|
-
upsertMcp(
|
|
6994
|
+
upsertMcp(path19.join(opts.root, a.mcp.file), a.mcp, launch);
|
|
6711
6995
|
wrote.push(a.mcp.file);
|
|
6712
6996
|
note = launch.note;
|
|
6713
6997
|
} else {
|
|
6714
6998
|
skipped.push("mcp (host-specific setup)");
|
|
6715
6999
|
}
|
|
6716
7000
|
if (a.nudge && opts.hook !== false) {
|
|
6717
|
-
writeNudge(
|
|
7001
|
+
writeNudge(path19.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
|
|
6718
7002
|
wrote.push(a.nudge.file);
|
|
6719
7003
|
} else if (a.nudge) {
|
|
6720
7004
|
skipped.push("nudge (--no-hook)");
|
|
@@ -6724,40 +7008,40 @@ function installAssistant(a, opts) {
|
|
|
6724
7008
|
function uninstallAssistant(a, root, purge) {
|
|
6725
7009
|
const removed = [];
|
|
6726
7010
|
if (a.mcp) {
|
|
6727
|
-
const file =
|
|
7011
|
+
const file = path19.join(root, a.mcp.file);
|
|
6728
7012
|
if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
|
|
6729
7013
|
}
|
|
6730
7014
|
if (a.nudge) {
|
|
6731
|
-
const file =
|
|
7015
|
+
const file = path19.join(root, a.nudge.file);
|
|
6732
7016
|
if (a.nudge.kind === "block") {
|
|
6733
7017
|
if (removeBlock(file)) removed.push(a.nudge.file);
|
|
6734
|
-
} else if (
|
|
6735
|
-
|
|
7018
|
+
} else if (fs19.existsSync(file)) {
|
|
7019
|
+
fs19.rmSync(file);
|
|
6736
7020
|
removed.push(a.nudge.file);
|
|
6737
7021
|
}
|
|
6738
7022
|
}
|
|
6739
7023
|
if (purge && a.skill) {
|
|
6740
|
-
const file =
|
|
6741
|
-
if (
|
|
6742
|
-
|
|
7024
|
+
const file = path19.join(root, a.skill);
|
|
7025
|
+
if (fs19.existsSync(file)) {
|
|
7026
|
+
fs19.rmSync(file);
|
|
6743
7027
|
removed.push(a.skill);
|
|
6744
7028
|
}
|
|
6745
7029
|
}
|
|
6746
7030
|
return removed;
|
|
6747
7031
|
}
|
|
6748
7032
|
function writeFileEnsured(file, content) {
|
|
6749
|
-
|
|
6750
|
-
|
|
7033
|
+
fs19.mkdirSync(path19.dirname(file), { recursive: true });
|
|
7034
|
+
fs19.writeFileSync(file, content);
|
|
6751
7035
|
}
|
|
6752
7036
|
function writeNavigationConfig(root) {
|
|
6753
|
-
const rel2 =
|
|
7037
|
+
const rel2 = path19.join(".vibgrate", "mcp-navigation.json");
|
|
6754
7038
|
const doc = {
|
|
6755
7039
|
_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.",
|
|
6756
7040
|
hot_core: [...HOT_TOOLS],
|
|
6757
7041
|
deferred: deferredToolNames(),
|
|
6758
7042
|
toolset: navigationToolsetConfig()
|
|
6759
7043
|
};
|
|
6760
|
-
writeFileEnsured(
|
|
7044
|
+
writeFileEnsured(path19.join(root, rel2), `${JSON.stringify(doc, null, 2)}
|
|
6761
7045
|
`);
|
|
6762
7046
|
return rel2;
|
|
6763
7047
|
}
|
|
@@ -6772,7 +7056,7 @@ function upsertMcp(file, target, launch) {
|
|
|
6772
7056
|
`);
|
|
6773
7057
|
}
|
|
6774
7058
|
function removeMcp(file, target) {
|
|
6775
|
-
if (!
|
|
7059
|
+
if (!fs19.existsSync(file)) return false;
|
|
6776
7060
|
const config = readJson(file);
|
|
6777
7061
|
const bag = config[target.key];
|
|
6778
7062
|
if (!bag || !(bag.vg !== void 0)) return false;
|
|
@@ -6781,22 +7065,22 @@ function removeMcp(file, target) {
|
|
|
6781
7065
|
`);
|
|
6782
7066
|
return true;
|
|
6783
7067
|
}
|
|
6784
|
-
function writeNudge(file, target, smallRepo) {
|
|
7068
|
+
function writeNudge(file, target, smallRepo, client) {
|
|
6785
7069
|
if (target.kind === "file") {
|
|
6786
7070
|
const body = file.endsWith(".mdc") ? `---
|
|
6787
7071
|
description: vg code graph
|
|
6788
7072
|
alwaysApply: true
|
|
6789
7073
|
---
|
|
6790
|
-
${stripMarkers(nudgeMarkdown(smallRepo))}` : stripMarkers(nudgeMarkdown(smallRepo));
|
|
7074
|
+
${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(smallRepo, client));
|
|
6791
7075
|
writeFileEnsured(file, `${body}
|
|
6792
7076
|
`);
|
|
6793
7077
|
return;
|
|
6794
7078
|
}
|
|
6795
|
-
upsertBlock(file, nudgeMarkdown(smallRepo));
|
|
7079
|
+
upsertBlock(file, nudgeMarkdown(smallRepo, client));
|
|
6796
7080
|
}
|
|
6797
7081
|
function upsertBlock(file, block) {
|
|
6798
|
-
|
|
6799
|
-
let existing =
|
|
7082
|
+
fs19.mkdirSync(path19.dirname(file), { recursive: true });
|
|
7083
|
+
let existing = fs19.existsSync(file) ? fs19.readFileSync(file, "utf8") : "";
|
|
6800
7084
|
const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
|
|
6801
7085
|
if (re.test(existing)) existing = existing.replace(re, block);
|
|
6802
7086
|
else existing = existing.length ? `${existing.replace(/\s*$/, "")}
|
|
@@ -6804,20 +7088,20 @@ function upsertBlock(file, block) {
|
|
|
6804
7088
|
${block}
|
|
6805
7089
|
` : `${block}
|
|
6806
7090
|
`;
|
|
6807
|
-
|
|
7091
|
+
fs19.writeFileSync(file, existing);
|
|
6808
7092
|
}
|
|
6809
7093
|
function removeBlock(file) {
|
|
6810
|
-
if (!
|
|
6811
|
-
const existing =
|
|
7094
|
+
if (!fs19.existsSync(file)) return false;
|
|
7095
|
+
const existing = fs19.readFileSync(file, "utf8");
|
|
6812
7096
|
const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
|
|
6813
7097
|
if (!re.test(existing)) return false;
|
|
6814
|
-
|
|
7098
|
+
fs19.writeFileSync(file, existing.replace(re, "\n"));
|
|
6815
7099
|
return true;
|
|
6816
7100
|
}
|
|
6817
7101
|
function readJson(file) {
|
|
6818
|
-
if (!
|
|
7102
|
+
if (!fs19.existsSync(file)) return {};
|
|
6819
7103
|
try {
|
|
6820
|
-
const parsed = JSON.parse(
|
|
7104
|
+
const parsed = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
6821
7105
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
6822
7106
|
return parsed;
|
|
6823
7107
|
}
|
|
@@ -7039,6 +7323,6 @@ function spdx(ctx) {
|
|
|
7039
7323
|
return JSON.stringify(doc, null, 2) + "\n";
|
|
7040
7324
|
}
|
|
7041
7325
|
|
|
7042
|
-
export { ASSISTANTS, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, readUsage, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, saveCatalog, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
|
|
7043
|
-
//# sourceMappingURL=chunk-
|
|
7044
|
-
//# sourceMappingURL=chunk-
|
|
7326
|
+
export { ASSISTANTS, CLI_TOOL_ALIASES, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, countTokens, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, indexFor, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, readUsage, recordCliCall, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, saveCatalog, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
|
|
7327
|
+
//# sourceMappingURL=chunk-3TCE6IGZ.js.map
|
|
7328
|
+
//# sourceMappingURL=chunk-3TCE6IGZ.js.map
|