@maxgfr/codeindex 2.12.0 → 2.14.0
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/README.md +35 -11
- package/docs/MIGRATION.md +155 -36
- package/docs/SEMANTIC.md +12 -5
- package/package.json +1 -1
- package/scripts/engine.d.mts +49 -7
- package/scripts/engine.mjs +1233 -749
- package/src/ast/extract.ts +7 -5
- package/src/ast/grammars-pull.ts +178 -0
- package/src/ast/loader.ts +77 -15
- package/src/bm25.ts +16 -6
- package/src/callers.ts +0 -0
- package/src/complexity.ts +7 -1
- package/src/deadcode.ts +5 -4
- package/src/derived.ts +128 -0
- package/src/embed/model.ts +23 -14
- package/src/engine-cli.ts +307 -51
- package/src/engine.ts +21 -4
- package/src/extract/code.ts +9 -5
- package/src/hash.ts +5 -2
- package/src/lang/js-ts.ts +1 -1
- package/src/mcp.ts +220 -26
- package/src/pipeline.ts +16 -5
- package/src/query.ts +8 -5
- package/src/scan.ts +74 -8
- package/src/types.ts +6 -3
- package/src/walk.ts +41 -11
package/scripts/engine.mjs
CHANGED
|
@@ -14,9 +14,9 @@ var ENGINE_VERSION, SCHEMA_VERSION, EXTRACTOR_VERSION;
|
|
|
14
14
|
var init_types = __esm({
|
|
15
15
|
"src/types.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
ENGINE_VERSION = "2.
|
|
17
|
+
ENGINE_VERSION = "2.14.0";
|
|
18
18
|
SCHEMA_VERSION = 4;
|
|
19
|
-
EXTRACTOR_VERSION =
|
|
19
|
+
EXTRACTOR_VERSION = 10;
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
22
|
|
|
@@ -320,6 +320,7 @@ function walk(root, opts = {}) {
|
|
|
320
320
|
const maxFileBytes = opts.maxFileBytes ?? 1024 * 1024;
|
|
321
321
|
const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES;
|
|
322
322
|
const useGitignore = opts.gitignore !== false;
|
|
323
|
+
const ignoreDirs = opts.ignoreDirs ? new Set(opts.ignoreDirs) : IGNORE_DIRS;
|
|
323
324
|
const out2 = [];
|
|
324
325
|
let capped = false;
|
|
325
326
|
let excluded = 0;
|
|
@@ -347,28 +348,31 @@ function walk(root, opts = {}) {
|
|
|
347
348
|
if (!contained(real)) continue;
|
|
348
349
|
let entries;
|
|
349
350
|
try {
|
|
350
|
-
entries = readdirSync(frame.dir).sort(
|
|
351
|
+
entries = readdirSync(frame.dir, { withFileTypes: true }).sort(
|
|
352
|
+
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
353
|
+
);
|
|
351
354
|
} catch {
|
|
352
355
|
continue;
|
|
353
356
|
}
|
|
354
357
|
let rules = frame.rules;
|
|
355
|
-
if (useGitignore && entries.
|
|
358
|
+
if (useGitignore && entries.some((e) => e.name === ".gitignore")) {
|
|
356
359
|
const parsed = parseGitignore(readText(join(frame.dir, ".gitignore")), frame.rel);
|
|
357
360
|
if (parsed.length) rules = [...rules, ...parsed];
|
|
358
361
|
}
|
|
359
|
-
for (const
|
|
362
|
+
for (const entry of entries) {
|
|
363
|
+
const name2 = entry.name;
|
|
360
364
|
const abs = join(frame.dir, name2);
|
|
361
365
|
const rel = frame.rel ? `${frame.rel}/${name2}` : name2;
|
|
366
|
+
const isLink = entry.isSymbolicLink();
|
|
367
|
+
if (entry.isDirectory() && ignoreDirs.has(name2)) continue;
|
|
362
368
|
let st;
|
|
363
|
-
let isLink;
|
|
364
369
|
try {
|
|
365
|
-
st = statSync(abs);
|
|
366
|
-
isLink = lstatSync(abs).isSymbolicLink();
|
|
370
|
+
st = isLink ? statSync(abs) : lstatSync(abs);
|
|
367
371
|
} catch {
|
|
368
372
|
continue;
|
|
369
373
|
}
|
|
370
374
|
if (st.isDirectory()) {
|
|
371
|
-
if (
|
|
375
|
+
if (ignoreDirs.has(name2)) continue;
|
|
372
376
|
if (isLink) continue;
|
|
373
377
|
if (useGitignore && rules.length && isIgnored(rules, rel, true)) continue;
|
|
374
378
|
stack.push({ dir: abs, rel, rules });
|
|
@@ -463,6 +467,7 @@ var init_walk = __esm({
|
|
|
463
467
|
".cache",
|
|
464
468
|
"tmp",
|
|
465
469
|
".ultraindex",
|
|
470
|
+
".codeindex",
|
|
466
471
|
"Pods",
|
|
467
472
|
"DerivedData",
|
|
468
473
|
".terraform",
|
|
@@ -901,7 +906,7 @@ var init_js_ts = __esm({
|
|
|
901
906
|
RULES = [
|
|
902
907
|
{ re: /^\s*export\s+(?:async\s+)?function\s+(?<name>[\w$]+)/, kind: "function", exported: true },
|
|
903
908
|
{ re: /^\s*export\s+default\s+(?:async\s+)?function\s+(?<name>[\w$]+)/, kind: "function", exported: true },
|
|
904
|
-
{ re: /^\s*export\s+default\s+(?:abstract\s+)?class\s+(?<name>[\w$]+)/, kind: "class", exported: true },
|
|
909
|
+
{ re: /^\s*export\s+default\s+(?:abstract\s+)?class\s+(?!extends\b)(?<name>[\w$]+)/, kind: "class", exported: true },
|
|
905
910
|
{ re: /^\s*(?:async\s+)?function\s+(?<name>[\w$]+)/, kind: "function", exported: false },
|
|
906
911
|
{ re: /^\s*export\s+(?:abstract\s+)?class\s+(?<name>[\w$]+)/, kind: "class", exported: true },
|
|
907
912
|
{ re: /^\s*(?:abstract\s+)?class\s+(?<name>[\w$]+)/, kind: "class", exported: false },
|
|
@@ -1770,13 +1775,13 @@ async function Module2(moduleArg = {}) {
|
|
|
1770
1775
|
}
|
|
1771
1776
|
readAsync = /* @__PURE__ */ __name(async (url) => {
|
|
1772
1777
|
if (isFileURI(url)) {
|
|
1773
|
-
return new Promise((
|
|
1778
|
+
return new Promise((resolve3, reject) => {
|
|
1774
1779
|
var xhr = new XMLHttpRequest();
|
|
1775
1780
|
xhr.open("GET", url, true);
|
|
1776
1781
|
xhr.responseType = "arraybuffer";
|
|
1777
1782
|
xhr.onload = () => {
|
|
1778
1783
|
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
|
|
1779
|
-
|
|
1784
|
+
resolve3(xhr.response);
|
|
1780
1785
|
return;
|
|
1781
1786
|
}
|
|
1782
1787
|
reject(xhr.status);
|
|
@@ -1972,9 +1977,9 @@ async function Module2(moduleArg = {}) {
|
|
|
1972
1977
|
__name(receiveInstantiationResult, "receiveInstantiationResult");
|
|
1973
1978
|
var info2 = getWasmImports();
|
|
1974
1979
|
if (Module["instantiateWasm"]) {
|
|
1975
|
-
return new Promise((
|
|
1980
|
+
return new Promise((resolve3, reject) => {
|
|
1976
1981
|
Module["instantiateWasm"](info2, (mod, inst) => {
|
|
1977
|
-
|
|
1982
|
+
resolve3(receiveInstance(mod, inst));
|
|
1978
1983
|
});
|
|
1979
1984
|
});
|
|
1980
1985
|
}
|
|
@@ -3305,8 +3310,8 @@ async function Module2(moduleArg = {}) {
|
|
|
3305
3310
|
if (runtimeInitialized) {
|
|
3306
3311
|
moduleRtn = Module;
|
|
3307
3312
|
} else {
|
|
3308
|
-
moduleRtn = new Promise((
|
|
3309
|
-
readyPromiseResolve =
|
|
3313
|
+
moduleRtn = new Promise((resolve3, reject) => {
|
|
3314
|
+
readyPromiseResolve = resolve3;
|
|
3310
3315
|
readyPromiseReject = reject;
|
|
3311
3316
|
});
|
|
3312
3317
|
}
|
|
@@ -5537,27 +5542,41 @@ ${JSON.stringify(symbolNames, null, 2)}`);
|
|
|
5537
5542
|
|
|
5538
5543
|
// src/ast/loader.ts
|
|
5539
5544
|
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
5545
|
+
import { homedir } from "os";
|
|
5540
5546
|
import { dirname, join as join2 } from "path";
|
|
5541
5547
|
import { fileURLToPath } from "url";
|
|
5542
5548
|
function grammarKeyForExt(ext) {
|
|
5543
5549
|
return EXT_GRAMMAR[ext];
|
|
5544
5550
|
}
|
|
5545
|
-
function
|
|
5546
|
-
const
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5551
|
+
function sharedGrammarsCacheDir() {
|
|
5552
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
5553
|
+
const base = xdg && xdg.trim() ? xdg.trim() : join2(homedir(), ".cache");
|
|
5554
|
+
return join2(base, "codeindex", "grammars", ENGINE_VERSION);
|
|
5555
|
+
}
|
|
5556
|
+
function resolveGrammarsTier(opts = {}) {
|
|
5557
|
+
const cacheDir = sharedGrammarsCacheDir();
|
|
5558
|
+
const legacy = process.env.CODEINDEX_GRAMMAR_DIR ?? process.env.ULTRAINDEX_GRAMMAR_DIR;
|
|
5559
|
+
if (legacy && legacy.trim() && existsSync(legacy)) return { tier: "env", dir: legacy, cacheDir };
|
|
5560
|
+
const here = opts.moduleDir ?? dirname(fileURLToPath(import.meta.url));
|
|
5561
|
+
const adjacent = [
|
|
5550
5562
|
join2(here, "grammars"),
|
|
5551
5563
|
// bundle: <...>/scripts/grammars
|
|
5552
5564
|
join2(here, "..", "..", "scripts", "grammars"),
|
|
5553
5565
|
// dev: src/ast → <repo>/scripts/grammars
|
|
5554
5566
|
join2(here, "..", "scripts", "grammars")
|
|
5555
5567
|
];
|
|
5556
|
-
for (const c2 of
|
|
5557
|
-
|
|
5568
|
+
for (const c2 of adjacent) if (existsSync(c2)) return { tier: "adjacent", dir: c2, cacheDir };
|
|
5569
|
+
const env = process.env.CODEINDEX_GRAMMARS_DIR;
|
|
5570
|
+
if (env && env.trim() && existsSync(env)) return { tier: "env", dir: env, cacheDir };
|
|
5571
|
+
if (existsSync(cacheDir)) return { tier: "cache", dir: cacheDir, cacheDir };
|
|
5572
|
+
return { tier: "none", cacheDir };
|
|
5573
|
+
}
|
|
5574
|
+
function resolveGrammarsDir(opts) {
|
|
5575
|
+
return resolveGrammarsTier(opts).dir;
|
|
5558
5576
|
}
|
|
5559
5577
|
async function ensureGrammars(keys) {
|
|
5560
|
-
const dir =
|
|
5578
|
+
const dir = resolveGrammarsDir();
|
|
5579
|
+
if (!dir) return;
|
|
5561
5580
|
if (!runtimeReady) {
|
|
5562
5581
|
const runtime = join2(dir, "web-tree-sitter.wasm");
|
|
5563
5582
|
if (!existsSync(runtime)) return;
|
|
@@ -5582,6 +5601,14 @@ async function ensureGrammars(keys) {
|
|
|
5582
5601
|
function allGrammarKeys() {
|
|
5583
5602
|
return [...new Set(Object.values(EXT_GRAMMAR))];
|
|
5584
5603
|
}
|
|
5604
|
+
function grammarKeysForExts(exts) {
|
|
5605
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5606
|
+
for (const ext of exts) {
|
|
5607
|
+
const key = EXT_GRAMMAR[ext];
|
|
5608
|
+
if (key !== void 0) keys.add(key);
|
|
5609
|
+
}
|
|
5610
|
+
return [...keys].sort();
|
|
5611
|
+
}
|
|
5585
5612
|
function grammarReady(key) {
|
|
5586
5613
|
return loaded.has(key);
|
|
5587
5614
|
}
|
|
@@ -5596,6 +5623,7 @@ var init_loader = __esm({
|
|
|
5596
5623
|
"src/ast/loader.ts"() {
|
|
5597
5624
|
"use strict";
|
|
5598
5625
|
init_web_tree_sitter();
|
|
5626
|
+
init_types();
|
|
5599
5627
|
EXT_GRAMMAR = {
|
|
5600
5628
|
".ts": "typescript",
|
|
5601
5629
|
".mts": "typescript",
|
|
@@ -5718,7 +5746,7 @@ function readReceiver(node) {
|
|
|
5718
5746
|
const name2 = obj ? readName(obj) : void 0;
|
|
5719
5747
|
return name2 && /^[A-Za-z_]\w*$/.test(name2) ? name2 : void 0;
|
|
5720
5748
|
}
|
|
5721
|
-
function collectCalls(root, spec) {
|
|
5749
|
+
function collectCalls(root, spec, maxCalls = MAX_CALLS) {
|
|
5722
5750
|
if (!spec.calls) return [];
|
|
5723
5751
|
const out2 = [];
|
|
5724
5752
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5749,7 +5777,7 @@ function collectCalls(root, spec) {
|
|
|
5749
5777
|
};
|
|
5750
5778
|
visit(root);
|
|
5751
5779
|
out2.sort((a, b) => byStr(a.name, b.name) || a.line - b.line);
|
|
5752
|
-
return out2.slice(0,
|
|
5780
|
+
return out2.slice(0, maxCalls);
|
|
5753
5781
|
}
|
|
5754
5782
|
function collectImportedNames(root, spec) {
|
|
5755
5783
|
if (!spec.imports?.import_statement) return [];
|
|
@@ -5776,7 +5804,7 @@ function collectImportedNames(root, spec) {
|
|
|
5776
5804
|
visit(root);
|
|
5777
5805
|
return [...found].sort(byStr).slice(0, MAX_IMPORTED_NAMES);
|
|
5778
5806
|
}
|
|
5779
|
-
function extractAst(rel, ext, content) {
|
|
5807
|
+
function extractAst(rel, ext, content, opts = {}) {
|
|
5780
5808
|
const key = grammarKeyForExt(ext);
|
|
5781
5809
|
if (!key || !grammarReady(key)) return void 0;
|
|
5782
5810
|
const spec = SPECS[key];
|
|
@@ -5962,7 +5990,7 @@ function extractAst(rel, ext, content) {
|
|
|
5962
5990
|
}
|
|
5963
5991
|
const refs = collectImports(root, spec);
|
|
5964
5992
|
const idents = collectRefIdents(root, new Set(symbols.map((s) => s.name)));
|
|
5965
|
-
const calls = collectCalls(root, spec);
|
|
5993
|
+
const calls = collectCalls(root, spec, opts.maxCalls);
|
|
5966
5994
|
const importedNames = collectImportedNames(root, spec);
|
|
5967
5995
|
let pkg;
|
|
5968
5996
|
if (spec.lang === "java") {
|
|
@@ -6398,12 +6426,12 @@ function extractImports(ext, content) {
|
|
|
6398
6426
|
}
|
|
6399
6427
|
return [...specs].map((spec) => ({ kind: "import", spec }));
|
|
6400
6428
|
}
|
|
6401
|
-
function collectCallsRegex(content, symbols = []) {
|
|
6429
|
+
function collectCallsRegex(content, symbols = [], maxCalls = 512) {
|
|
6402
6430
|
const out2 = /* @__PURE__ */ new Map();
|
|
6403
6431
|
const ownDefLines = new Set(symbols.map((s) => `${s.name} ${s.line}`));
|
|
6404
6432
|
const lines = content.split("\n");
|
|
6405
6433
|
const CALL_RE = /(?:\bnew\s+)?(?:([A-Za-z_$][\w$]*)\s*\.\s*)?([A-Za-z_$][\w$]*)\s*\(/g;
|
|
6406
|
-
for (let i2 = 0; i2 < lines.length && out2.size <
|
|
6434
|
+
for (let i2 = 0; i2 < lines.length && out2.size < maxCalls; i2++) {
|
|
6407
6435
|
const line = lines[i2];
|
|
6408
6436
|
const trimmed = line.trimStart();
|
|
6409
6437
|
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*")) continue;
|
|
@@ -6418,7 +6446,7 @@ function collectCallsRegex(content, symbols = []) {
|
|
|
6418
6446
|
CALL_RE.lastIndex = 0;
|
|
6419
6447
|
let m;
|
|
6420
6448
|
const fallbackExcluded = /* @__PURE__ */ new Set();
|
|
6421
|
-
while ((m = CALL_RE.exec(line)) !== null && out2.size <
|
|
6449
|
+
while ((m = CALL_RE.exec(line)) !== null && out2.size < maxCalls) {
|
|
6422
6450
|
const receiver = m[1];
|
|
6423
6451
|
const name2 = m[2];
|
|
6424
6452
|
if (name2.length < 2 || CALL_KEYWORDS.has(name2)) continue;
|
|
@@ -6435,8 +6463,8 @@ function collectCallsRegex(content, symbols = []) {
|
|
|
6435
6463
|
}
|
|
6436
6464
|
return [...out2.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : a.line - b.line);
|
|
6437
6465
|
}
|
|
6438
|
-
function extractCode(rel, ext, content) {
|
|
6439
|
-
const ast = extractAst(rel, ext, content);
|
|
6466
|
+
function extractCode(rel, ext, content, opts = {}) {
|
|
6467
|
+
const ast = extractAst(rel, ext, content, { maxCalls: opts.maxCallsPerFile });
|
|
6440
6468
|
const symbols = (ast ? ast.symbols : extractSymbols(rel, ext, content)).slice(0, 400);
|
|
6441
6469
|
const known = new Set(symbols.map((s) => s.name));
|
|
6442
6470
|
const reexports = extractReexports(rel, content, symbols).filter((s) => !known.has(s.name));
|
|
@@ -6452,7 +6480,7 @@ function extractCode(rel, ext, content) {
|
|
|
6452
6480
|
// collector otherwise, so caller indexes exist without the wasm sidecar.
|
|
6453
6481
|
// `symbols` (this file's own regex-extracted defs) lets the collector
|
|
6454
6482
|
// exclude a definition's own name+line from its call candidates.
|
|
6455
|
-
calls: ast ? ast.calls : collectCallsRegex(content, symbols),
|
|
6483
|
+
calls: ast ? ast.calls : collectCallsRegex(content, symbols, opts.maxCallsPerFile),
|
|
6456
6484
|
importedNames: ast?.importedNames
|
|
6457
6485
|
};
|
|
6458
6486
|
}
|
|
@@ -6521,16 +6549,20 @@ function scanRepo(root, opts = {}) {
|
|
|
6521
6549
|
const scoped = opts.scope ? [...opts.include ?? [], `${opts.scope.replace(/\/+$/, "")}/**`] : opts.include;
|
|
6522
6550
|
const include = compileGlobs(scoped);
|
|
6523
6551
|
const exclude = compileGlobs(opts.exclude);
|
|
6524
|
-
const { files: walked, capped, excluded } = walk(root, {
|
|
6552
|
+
const { files: walked, capped, excluded } = opts.precomputedWalk ?? walk(root, {
|
|
6525
6553
|
maxFileBytes: opts.maxBytes,
|
|
6526
6554
|
maxFiles: opts.maxFiles,
|
|
6527
|
-
gitignore: opts.gitignore
|
|
6555
|
+
gitignore: opts.gitignore,
|
|
6556
|
+
ignoreDirs: opts.ignoreDirs
|
|
6528
6557
|
});
|
|
6529
6558
|
const outPrefix = opts.out ? opts.out.replace(/\/+$/, "") + "/" : null;
|
|
6530
6559
|
const files = [];
|
|
6531
6560
|
const languages = {};
|
|
6532
6561
|
const docText = /* @__PURE__ */ new Map();
|
|
6533
6562
|
const mtimes = /* @__PURE__ */ new Map();
|
|
6563
|
+
const cache = opts.cache;
|
|
6564
|
+
let allReused = cache !== void 0;
|
|
6565
|
+
let cacheDirty = cache === void 0;
|
|
6534
6566
|
for (const f of walked) {
|
|
6535
6567
|
if (outPrefix && (f.abs === opts.out || f.abs.startsWith(outPrefix))) continue;
|
|
6536
6568
|
if (include && !include(f.rel)) continue;
|
|
@@ -6549,8 +6581,11 @@ function scanRepo(root, opts = {}) {
|
|
|
6549
6581
|
if (cached && cached.hash === hash) {
|
|
6550
6582
|
files.push(cached.record);
|
|
6551
6583
|
if (kind === "doc" && content) docText.set(f.rel, content);
|
|
6584
|
+
if (cached.size !== f.size || cached.mtimeMs !== f.mtimeMs) cacheDirty = true;
|
|
6552
6585
|
continue;
|
|
6553
6586
|
}
|
|
6587
|
+
allReused = false;
|
|
6588
|
+
cacheDirty = true;
|
|
6554
6589
|
const record = {
|
|
6555
6590
|
rel: f.rel,
|
|
6556
6591
|
ext: f.ext,
|
|
@@ -6573,7 +6608,7 @@ function scanRepo(root, opts = {}) {
|
|
|
6573
6608
|
} else if (kind === "doc") {
|
|
6574
6609
|
record.title = basename(f.rel);
|
|
6575
6610
|
} else if (kind === "code") {
|
|
6576
|
-
const code = extractCode(f.rel, f.ext, content);
|
|
6611
|
+
const code = extractCode(f.rel, f.ext, content, { maxCallsPerFile: opts.maxCallsPerFile });
|
|
6577
6612
|
record.title = basename(f.rel);
|
|
6578
6613
|
record.summary = code.summary;
|
|
6579
6614
|
record.symbols = code.symbols;
|
|
@@ -6592,7 +6627,22 @@ function scanRepo(root, opts = {}) {
|
|
|
6592
6627
|
files.push(record);
|
|
6593
6628
|
}
|
|
6594
6629
|
files.sort(byKey((f) => f.rel));
|
|
6595
|
-
|
|
6630
|
+
if (cache !== void 0 && files.length !== cache.size) {
|
|
6631
|
+
allReused = false;
|
|
6632
|
+
cacheDirty = true;
|
|
6633
|
+
}
|
|
6634
|
+
return {
|
|
6635
|
+
root,
|
|
6636
|
+
commit: headCommit(root),
|
|
6637
|
+
files,
|
|
6638
|
+
languages,
|
|
6639
|
+
docText,
|
|
6640
|
+
mtimes,
|
|
6641
|
+
capped,
|
|
6642
|
+
excluded,
|
|
6643
|
+
contentUnchanged: allReused,
|
|
6644
|
+
cacheDirty
|
|
6645
|
+
};
|
|
6596
6646
|
}
|
|
6597
6647
|
var init_scan = __esm({
|
|
6598
6648
|
"src/scan.ts"() {
|
|
@@ -6611,7 +6661,7 @@ var init_scan = __esm({
|
|
|
6611
6661
|
|
|
6612
6662
|
// src/resolve.ts
|
|
6613
6663
|
import { posix } from "path";
|
|
6614
|
-
import { join as
|
|
6664
|
+
import { join as join4 } from "path";
|
|
6615
6665
|
function distToSrcCandidates(target) {
|
|
6616
6666
|
const segs = norm(target).split("/").filter((s) => s !== ".");
|
|
6617
6667
|
const out2 = [];
|
|
@@ -6698,7 +6748,7 @@ function resolveExtends(fileSet, fromDir, ext) {
|
|
|
6698
6748
|
function readTsConfig(root, fileSet, rel, warnings, seen) {
|
|
6699
6749
|
if (seen.has(rel)) return void 0;
|
|
6700
6750
|
seen.add(rel);
|
|
6701
|
-
const cfg = tolerantJsonParse(readText(
|
|
6751
|
+
const cfg = tolerantJsonParse(readText(join4(root, rel)));
|
|
6702
6752
|
if (cfg === void 0) {
|
|
6703
6753
|
warnings.push(`unparseable ${rel} \u2014 its path aliases were ignored`);
|
|
6704
6754
|
return void 0;
|
|
@@ -6827,7 +6877,7 @@ function buildResolveContext(scan2) {
|
|
|
6827
6877
|
const goModules = [];
|
|
6828
6878
|
for (const rel of fileSet) {
|
|
6829
6879
|
if (rel !== "go.mod" && !rel.endsWith("/go.mod")) continue;
|
|
6830
|
-
const text = readText(
|
|
6880
|
+
const text = readText(join4(scan2.root, rel));
|
|
6831
6881
|
const m = /^\s*module\s+(\S+)/m.exec(text);
|
|
6832
6882
|
if (!m) continue;
|
|
6833
6883
|
const dir = rel.includes("/") ? posix.dirname(rel) : "";
|
|
@@ -6837,7 +6887,7 @@ function buildResolveContext(scan2) {
|
|
|
6837
6887
|
const rustCrates = [];
|
|
6838
6888
|
for (const rel of fileSet) {
|
|
6839
6889
|
if (rel !== "Cargo.toml" && !rel.endsWith("/Cargo.toml")) continue;
|
|
6840
|
-
const text = readText(
|
|
6890
|
+
const text = readText(join4(scan2.root, rel));
|
|
6841
6891
|
const m = /\[package\][^[]*?^\s*name\s*=\s*"([^"]+)"/ms.exec(text);
|
|
6842
6892
|
if (!m) continue;
|
|
6843
6893
|
const dir = rel.includes("/") ? posix.dirname(rel) : "";
|
|
@@ -6864,7 +6914,7 @@ function buildResolveContext(scan2) {
|
|
|
6864
6914
|
const workspacePackages = [];
|
|
6865
6915
|
for (const rel of fileSet) {
|
|
6866
6916
|
if (rel !== "package.json" && !rel.endsWith("/package.json")) continue;
|
|
6867
|
-
const pkg = tolerantJsonParse(readText(
|
|
6917
|
+
const pkg = tolerantJsonParse(readText(join4(scan2.root, rel)));
|
|
6868
6918
|
if (pkg === void 0) {
|
|
6869
6919
|
warnings.push(`unparseable ${rel} \u2014 skipped for workspace resolution`);
|
|
6870
6920
|
continue;
|
|
@@ -6891,7 +6941,7 @@ function buildResolveContext(scan2) {
|
|
|
6891
6941
|
const phpPsr4 = [];
|
|
6892
6942
|
for (const rel of fileSet) {
|
|
6893
6943
|
if (rel !== "composer.json" && !rel.endsWith("/composer.json")) continue;
|
|
6894
|
-
const composer = tolerantJsonParse(readText(
|
|
6944
|
+
const composer = tolerantJsonParse(readText(join4(scan2.root, rel)));
|
|
6895
6945
|
if (!composer) {
|
|
6896
6946
|
warnings.push(`unparseable ${rel} \u2014 skipped for PHP PSR-4 resolution`);
|
|
6897
6947
|
continue;
|
|
@@ -7466,7 +7516,7 @@ var init_calls = __esm({
|
|
|
7466
7516
|
});
|
|
7467
7517
|
|
|
7468
7518
|
// src/graph.ts
|
|
7469
|
-
import { join as
|
|
7519
|
+
import { join as join5 } from "path";
|
|
7470
7520
|
function isDistinctive(name2) {
|
|
7471
7521
|
if (name2.length < 5) return false;
|
|
7472
7522
|
const internalUpper = /[a-z][A-Z]/.test(name2) || /[A-Z]{2}/.test(name2);
|
|
@@ -7545,7 +7595,7 @@ function buildGraph(scan2, ctx, modules, moduleOf, meta) {
|
|
|
7545
7595
|
if (unique.size) {
|
|
7546
7596
|
for (const f of scan2.files) {
|
|
7547
7597
|
if (f.kind !== "doc") continue;
|
|
7548
|
-
const content = scan2.docText.get(f.rel) ?? readText(
|
|
7598
|
+
const content = scan2.docText.get(f.rel) ?? readText(join5(scan2.root, f.rel));
|
|
7549
7599
|
if (!content) continue;
|
|
7550
7600
|
const tokens = /* @__PURE__ */ new Map();
|
|
7551
7601
|
for (const tok of content.split(/[^A-Za-z0-9_]+/)) {
|
|
@@ -7650,217 +7700,567 @@ var init_graph = __esm({
|
|
|
7650
7700
|
}
|
|
7651
7701
|
});
|
|
7652
7702
|
|
|
7653
|
-
// src/
|
|
7654
|
-
function
|
|
7655
|
-
const
|
|
7656
|
-
const
|
|
7703
|
+
// src/render/symbols-json.ts
|
|
7704
|
+
function computeSymbolRefs(scan2) {
|
|
7705
|
+
const unique = uniqueSymbolDefs(scan2);
|
|
7706
|
+
const refs = /* @__PURE__ */ new Map();
|
|
7707
|
+
if (!unique.size) return refs;
|
|
7708
|
+
const add = (name2, file) => {
|
|
7709
|
+
let set = refs.get(name2);
|
|
7710
|
+
if (!set) refs.set(name2, set = /* @__PURE__ */ new Set());
|
|
7711
|
+
set.add(file);
|
|
7712
|
+
};
|
|
7657
7713
|
for (const f of scan2.files) {
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7714
|
+
if (f.kind === "code" && f.idents) {
|
|
7715
|
+
for (const id of f.idents) {
|
|
7716
|
+
const target = unique.get(id);
|
|
7717
|
+
if (target && target !== f.rel) add(id, f.rel);
|
|
7718
|
+
}
|
|
7719
|
+
} else if (f.kind === "doc") {
|
|
7720
|
+
const content = scan2.docText.get(f.rel);
|
|
7721
|
+
if (!content) continue;
|
|
7722
|
+
for (const tok of content.split(/[^A-Za-z0-9_]+/)) {
|
|
7723
|
+
const target = unique.get(tok);
|
|
7724
|
+
if (target && target !== f.rel) add(tok, f.rel);
|
|
7725
|
+
}
|
|
7662
7726
|
}
|
|
7663
7727
|
}
|
|
7664
|
-
return
|
|
7728
|
+
return refs;
|
|
7665
7729
|
}
|
|
7666
|
-
function
|
|
7667
|
-
const
|
|
7668
|
-
const recall = opts.recall === true;
|
|
7669
|
-
const defs = /* @__PURE__ */ new Map();
|
|
7670
|
-
for (const f of scan2.files) {
|
|
7671
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7672
|
-
for (const s of f.symbols) {
|
|
7673
|
-
if (!s.exported || REFERENCE_KINDS3.has(s.kind)) continue;
|
|
7674
|
-
if (seen.has(s.name)) continue;
|
|
7675
|
-
seen.add(s.name);
|
|
7676
|
-
let arr = defs.get(s.name);
|
|
7677
|
-
if (!arr) defs.set(s.name, arr = []);
|
|
7678
|
-
arr.push(s);
|
|
7679
|
-
}
|
|
7680
|
-
}
|
|
7681
|
-
const localDefs = /* @__PURE__ */ new Map();
|
|
7730
|
+
function buildSymbolIndex(scan2, refs = /* @__PURE__ */ new Map()) {
|
|
7731
|
+
const defsByName = /* @__PURE__ */ new Map();
|
|
7682
7732
|
for (const f of scan2.files) {
|
|
7683
|
-
const byName = /* @__PURE__ */ new Map();
|
|
7684
7733
|
for (const s of f.symbols) {
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
if (!f.calls?.length) continue;
|
|
7697
|
-
const family = familyOf(f.lang);
|
|
7698
|
-
const own = localDefs.get(f.rel);
|
|
7699
|
-
for (const c2 of f.calls) {
|
|
7700
|
-
const local = own.get(c2.name);
|
|
7701
|
-
if (local) {
|
|
7702
|
-
if (local.line !== c2.line)
|
|
7703
|
-
record(local, recall ? { file: f.rel, line: c2.line, confidence: "corroborated" } : { file: f.rel, line: c2.line });
|
|
7704
|
-
continue;
|
|
7705
|
-
}
|
|
7706
|
-
const cands = (defs.get(c2.name) ?? []).filter((d) => familyOf(d.lang) === family && d.file !== f.rel).map((d) => ({ file: d.file, lang: d.lang }));
|
|
7707
|
-
if (!cands.length) continue;
|
|
7708
|
-
const imported = cands.filter((d) => pairs.has(`${f.rel}|${d.file}`));
|
|
7709
|
-
const chosen = family === "js" ? imported.length ? pickCandidate(f.rel, imported) : (
|
|
7710
|
-
// JS/TS gate: no corroborating import → no binding. Recall mode
|
|
7711
|
-
// relaxes this to a unique-repo-wide name match (issue #7).
|
|
7712
|
-
recall && cands.length === 1 ? cands[0] : void 0
|
|
7713
|
-
) : imported.length ? pickCandidate(f.rel, imported) : pickCandidate(f.rel, cands);
|
|
7714
|
-
if (!chosen) continue;
|
|
7715
|
-
const def = defs.get(c2.name).find((d) => d.file === chosen.file);
|
|
7716
|
-
record(
|
|
7717
|
-
def,
|
|
7718
|
-
recall ? { file: f.rel, line: c2.line, confidence: imported.length ? "corroborated" : "unique-name" } : { file: f.rel, line: c2.line }
|
|
7719
|
-
);
|
|
7734
|
+
let arr = defsByName.get(s.name);
|
|
7735
|
+
if (!arr) defsByName.set(s.name, arr = []);
|
|
7736
|
+
arr.push({
|
|
7737
|
+
file: s.file,
|
|
7738
|
+
line: s.line,
|
|
7739
|
+
...s.endLine !== void 0 ? { endLine: s.endLine } : {},
|
|
7740
|
+
kind: s.kind,
|
|
7741
|
+
exported: s.exported,
|
|
7742
|
+
lang: s.lang,
|
|
7743
|
+
...s.parent ? { parent: s.parent } : {}
|
|
7744
|
+
});
|
|
7720
7745
|
}
|
|
7721
7746
|
}
|
|
7722
|
-
const
|
|
7723
|
-
const
|
|
7724
|
-
|
|
7725
|
-
const { def, callers } = sites.get(key);
|
|
7726
|
-
callers.sort((a, b) => byStr(a.file, b.file) || a.line - b.line);
|
|
7727
|
-
if (!index.has(def.name)) index.set(def.name, { def, callers });
|
|
7728
|
-
else index.set(`${def.name}@${def.file}`, { def, callers });
|
|
7747
|
+
const defs = {};
|
|
7748
|
+
for (const name2 of [...defsByName.keys()].sort(byStr)) {
|
|
7749
|
+
defs[name2] = defsByName.get(name2).slice().sort((a, b) => byStr(a.file, b.file) || a.line - b.line || byStr(a.kind, b.kind));
|
|
7729
7750
|
}
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
if (!f?.symbols.length) return void 0;
|
|
7735
|
-
return enclosingAmong(f.symbols, line);
|
|
7736
|
-
}
|
|
7737
|
-
function enclosingAmong(symbols, line) {
|
|
7738
|
-
let best;
|
|
7739
|
-
for (const s of symbols) {
|
|
7740
|
-
if (REFERENCE_KINDS3.has(s.kind)) continue;
|
|
7741
|
-
if (s.line > line) continue;
|
|
7742
|
-
if (s.endLine !== void 0 && line > s.endLine) continue;
|
|
7743
|
-
if (!best || s.line > best.line || s.line === best.line && (s.endLine ?? Infinity) <= (best.endLine ?? Infinity)) {
|
|
7744
|
-
best = s;
|
|
7745
|
-
}
|
|
7751
|
+
const refsOut = {};
|
|
7752
|
+
for (const name2 of [...refs.keys()].sort(byStr)) {
|
|
7753
|
+
const files = [...refs.get(name2)].sort(byStr);
|
|
7754
|
+
if (files.length) refsOut[name2] = files;
|
|
7746
7755
|
}
|
|
7747
|
-
return
|
|
7756
|
+
return { schemaVersion: SCHEMA_VERSION, defs, refs: refsOut };
|
|
7748
7757
|
}
|
|
7749
|
-
function
|
|
7750
|
-
|
|
7751
|
-
for (const f of scan2.files) {
|
|
7752
|
-
if (!f.calls?.length) continue;
|
|
7753
|
-
const symbols = f.symbols.filter((s) => !REFERENCE_KINDS3.has(s.kind));
|
|
7754
|
-
for (const c2 of f.calls) {
|
|
7755
|
-
const site = { file: f.rel, line: c2.line };
|
|
7756
|
-
if (c2.receiver !== void 0) site.receiver = c2.receiver;
|
|
7757
|
-
const enc = enclosingAmong(symbols, c2.line);
|
|
7758
|
-
if (enc) site.enclosingSymbol = enc;
|
|
7759
|
-
let arr = byName.get(c2.name);
|
|
7760
|
-
if (!arr) byName.set(c2.name, arr = []);
|
|
7761
|
-
arr.push(site);
|
|
7762
|
-
}
|
|
7763
|
-
}
|
|
7764
|
-
const index = /* @__PURE__ */ new Map();
|
|
7765
|
-
for (const name2 of [...byName.keys()].sort(byStr)) {
|
|
7766
|
-
const sites = byName.get(name2);
|
|
7767
|
-
sites.sort((a, b) => byStr(a.file, b.file) || a.line - b.line);
|
|
7768
|
-
index.set(name2, sites);
|
|
7769
|
-
}
|
|
7770
|
-
return index;
|
|
7758
|
+
function renderSymbolsJson(index) {
|
|
7759
|
+
return JSON.stringify(index, null, 2) + "\n";
|
|
7771
7760
|
}
|
|
7772
|
-
var
|
|
7773
|
-
|
|
7774
|
-
"src/callers.ts"() {
|
|
7761
|
+
var init_symbols_json = __esm({
|
|
7762
|
+
"src/render/symbols-json.ts"() {
|
|
7775
7763
|
"use strict";
|
|
7776
|
-
|
|
7777
|
-
init_resolve();
|
|
7764
|
+
init_types();
|
|
7778
7765
|
init_sort();
|
|
7779
|
-
|
|
7766
|
+
init_graph();
|
|
7780
7767
|
}
|
|
7781
7768
|
});
|
|
7782
7769
|
|
|
7783
|
-
// src/
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
const f = scan2.files.find((x) => x.rel === rel);
|
|
7787
|
-
if (!f) return [];
|
|
7788
|
-
return [...f.symbols].filter((s) => !REFERENCE_KINDS4.has(s.kind)).sort((a, b) => a.line - b.line || byStr(a.name, b.name));
|
|
7789
|
-
}
|
|
7790
|
-
function findSymbol(scan2, namePath, opts = {}) {
|
|
7791
|
-
const segments = namePath.split("/").filter(Boolean);
|
|
7792
|
-
if (!segments.length) return [];
|
|
7793
|
-
const leaf = segments[segments.length - 1];
|
|
7794
|
-
const parents = segments.slice(0, -1);
|
|
7795
|
-
const matchName = (name2, wanted) => opts.substring ? name2.toLowerCase().includes(wanted.toLowerCase()) : name2 === wanted;
|
|
7770
|
+
// src/bm25.ts
|
|
7771
|
+
function subtokens(raw) {
|
|
7772
|
+
const folded = foldText(raw).replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
|
|
7796
7773
|
const out2 = [];
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
(
|
|
7810
|
-
|
|
7811
|
-
const capped = out2.slice(0, opts.maxResults ?? 50);
|
|
7812
|
-
if (opts.includeBody) {
|
|
7813
|
-
for (const m of capped) {
|
|
7814
|
-
const end = m.endLine ?? m.line;
|
|
7815
|
-
const content = readText(join5(scan2.root, m.file));
|
|
7816
|
-
if (!content) continue;
|
|
7817
|
-
m.body = content.split("\n").slice(m.line - 1, end).join("\n");
|
|
7818
|
-
}
|
|
7774
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7775
|
+
const push = (t) => {
|
|
7776
|
+
if (t.length < 2 || seen.has(t)) return;
|
|
7777
|
+
seen.add(t);
|
|
7778
|
+
out2.push(t);
|
|
7779
|
+
};
|
|
7780
|
+
if (!/\s/.test(raw.trim())) push(foldText(raw).toLowerCase().replace(/[^a-z0-9_]+/g, ""));
|
|
7781
|
+
for (const part of folded.split(/[^A-Za-z0-9]+/)) push(part.toLowerCase());
|
|
7782
|
+
return out2;
|
|
7783
|
+
}
|
|
7784
|
+
function addTerms(doc, text) {
|
|
7785
|
+
for (const t of subtokens(text)) {
|
|
7786
|
+
doc.tf.set(t, (doc.tf.get(t) ?? 0) + 1);
|
|
7787
|
+
doc.len++;
|
|
7819
7788
|
}
|
|
7820
|
-
return capped;
|
|
7821
7789
|
}
|
|
7822
|
-
function
|
|
7823
|
-
const
|
|
7790
|
+
function buildDocs(scan2) {
|
|
7791
|
+
const docs = [];
|
|
7824
7792
|
for (const f of scan2.files) {
|
|
7793
|
+
const doc = { file: f.rel, tf: /* @__PURE__ */ new Map(), len: 0, symbols: [] };
|
|
7794
|
+
const seenSym = /* @__PURE__ */ new Set();
|
|
7825
7795
|
for (const s of f.symbols) {
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
const index = buildCallerIndex(scan2);
|
|
7831
|
-
const entry = index.get(name2);
|
|
7832
|
-
const callSites = entry ? entry.callers : [];
|
|
7833
|
-
const referencingFiles = /* @__PURE__ */ new Set();
|
|
7834
|
-
const unique = uniqueSymbolDefs(scan2);
|
|
7835
|
-
const defFile = unique.get(name2);
|
|
7836
|
-
for (const f of scan2.files) {
|
|
7837
|
-
if (f.rel === defFile) continue;
|
|
7838
|
-
if (f.kind === "code" && f.idents?.includes(name2)) referencingFiles.add(f.rel);
|
|
7839
|
-
else if (f.kind === "doc") {
|
|
7840
|
-
const content = scan2.docText.get(f.rel);
|
|
7841
|
-
if (content && new RegExp(`\\b${name2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(content)) {
|
|
7842
|
-
referencingFiles.add(f.rel);
|
|
7796
|
+
addTerms(doc, s.name);
|
|
7797
|
+
if (!seenSym.has(s.name)) {
|
|
7798
|
+
seenSym.add(s.name);
|
|
7799
|
+
doc.symbols.push(s.name);
|
|
7843
7800
|
}
|
|
7844
7801
|
}
|
|
7802
|
+
for (const seg of f.rel.split("/")) addTerms(doc, seg);
|
|
7803
|
+
for (const h of f.headings) addTerms(doc, h);
|
|
7804
|
+
if (f.summary) addTerms(doc, f.summary);
|
|
7805
|
+
docs.push(doc);
|
|
7845
7806
|
}
|
|
7846
|
-
|
|
7847
|
-
return { defs, callSites, referencingFiles: [...referencingFiles].sort(byStr) };
|
|
7807
|
+
return docs;
|
|
7848
7808
|
}
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7809
|
+
function charTrigrams(term) {
|
|
7810
|
+
const padded = `^^${term}$$`;
|
|
7811
|
+
const grams = /* @__PURE__ */ new Set();
|
|
7812
|
+
for (let i2 = 0; i2 + 3 <= padded.length; i2++) grams.add(padded.slice(i2, i2 + 3));
|
|
7813
|
+
return grams;
|
|
7814
|
+
}
|
|
7815
|
+
function diceCoefficient(a, b) {
|
|
7816
|
+
if (!a.size || !b.size) return 0;
|
|
7817
|
+
let inter = 0;
|
|
7818
|
+
for (const g of a) if (b.has(g)) inter++;
|
|
7819
|
+
return 2 * inter / (a.size + b.size);
|
|
7820
|
+
}
|
|
7821
|
+
function buildTrigramIndex(docs) {
|
|
7822
|
+
const index = /* @__PURE__ */ new Map();
|
|
7823
|
+
for (const d of docs) {
|
|
7824
|
+
for (const term of d.tf.keys()) {
|
|
7825
|
+
if (!index.has(term)) index.set(term, charTrigrams(term));
|
|
7826
|
+
}
|
|
7827
|
+
}
|
|
7828
|
+
return index;
|
|
7829
|
+
}
|
|
7830
|
+
function searchIndex(scan2, query, opts = {}) {
|
|
7831
|
+
const terms = [];
|
|
7832
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7833
|
+
for (const kw of keywords(query)) {
|
|
7834
|
+
for (const t of subtokens(kw)) {
|
|
7835
|
+
if (seen.has(t)) continue;
|
|
7836
|
+
seen.add(t);
|
|
7837
|
+
terms.push(t);
|
|
7838
|
+
}
|
|
7839
|
+
}
|
|
7840
|
+
if (!terms.length) return [];
|
|
7841
|
+
const docs = bm25DocsFor(scan2);
|
|
7842
|
+
const n = docs.length;
|
|
7843
|
+
if (!n) return [];
|
|
7844
|
+
let totalLen = 0;
|
|
7845
|
+
for (const d of docs) totalLen += d.len;
|
|
7846
|
+
const avgLen = totalLen / n || 1;
|
|
7847
|
+
const df = /* @__PURE__ */ new Map();
|
|
7848
|
+
for (const t of terms) {
|
|
7849
|
+
let count = 0;
|
|
7850
|
+
for (const d of docs) if (d.tf.has(t)) count++;
|
|
7851
|
+
df.set(t, count);
|
|
7852
|
+
}
|
|
7853
|
+
const fuzzyEnabled = opts.fuzzy ?? true;
|
|
7854
|
+
const fuzzyCandidates = /* @__PURE__ */ new Map();
|
|
7855
|
+
if (fuzzyEnabled) {
|
|
7856
|
+
const unmatched = terms.filter((t) => df.get(t) === 0);
|
|
7857
|
+
if (unmatched.length) {
|
|
7858
|
+
const trigramIndex = bm25TrigramsFor(scan2);
|
|
7859
|
+
for (const t of unmatched) {
|
|
7860
|
+
const grams = charTrigrams(t);
|
|
7861
|
+
const candidates = [];
|
|
7862
|
+
for (const [vocabTerm, vocabGrams] of trigramIndex) {
|
|
7863
|
+
const dice = diceCoefficient(grams, vocabGrams);
|
|
7864
|
+
if (dice >= FUZZY_DICE_THRESHOLD) candidates.push({ term: vocabTerm, dice });
|
|
7865
|
+
}
|
|
7866
|
+
candidates.sort((a, b) => b.dice - a.dice || byStr(a.term, b.term));
|
|
7867
|
+
fuzzyCandidates.set(t, candidates.slice(0, FUZZY_CAP));
|
|
7868
|
+
}
|
|
7869
|
+
}
|
|
7870
|
+
}
|
|
7871
|
+
const vocabDf = /* @__PURE__ */ new Map();
|
|
7872
|
+
const dfOfVocabTerm = (term) => {
|
|
7873
|
+
const known = df.get(term) ?? vocabDf.get(term);
|
|
7874
|
+
if (known !== void 0) return known;
|
|
7875
|
+
let count = 0;
|
|
7876
|
+
for (const d of docs) if (d.tf.has(term)) count++;
|
|
7877
|
+
vocabDf.set(term, count);
|
|
7878
|
+
return count;
|
|
7879
|
+
};
|
|
7880
|
+
const results = [];
|
|
7881
|
+
for (const d of docs) {
|
|
7882
|
+
let score = 0;
|
|
7883
|
+
const matched = [];
|
|
7884
|
+
const symbolTerms = /* @__PURE__ */ new Set();
|
|
7885
|
+
const fuzzyHit = /* @__PURE__ */ new Set();
|
|
7886
|
+
for (const t of terms) {
|
|
7887
|
+
const tf = d.tf.get(t);
|
|
7888
|
+
if (tf) {
|
|
7889
|
+
matched.push(t);
|
|
7890
|
+
symbolTerms.add(t);
|
|
7891
|
+
const idf = Math.log(1 + (n - df.get(t) + 0.5) / (df.get(t) + 0.5));
|
|
7892
|
+
score += idf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * d.len / avgLen));
|
|
7893
|
+
continue;
|
|
7894
|
+
}
|
|
7895
|
+
const candidates = fuzzyCandidates.get(t);
|
|
7896
|
+
if (!candidates) continue;
|
|
7897
|
+
for (const cand of candidates) {
|
|
7898
|
+
const ctf = d.tf.get(cand.term);
|
|
7899
|
+
if (!ctf) continue;
|
|
7900
|
+
const cdf = dfOfVocabTerm(cand.term);
|
|
7901
|
+
const idf = Math.log(1 + (n - cdf + 0.5) / (cdf + 0.5));
|
|
7902
|
+
const contribution = idf * (ctf * (K1 + 1)) / (ctf + K1 * (1 - B + B * d.len / avgLen));
|
|
7903
|
+
score += contribution * cand.dice;
|
|
7904
|
+
symbolTerms.add(cand.term);
|
|
7905
|
+
fuzzyHit.add(t);
|
|
7906
|
+
}
|
|
7907
|
+
}
|
|
7908
|
+
if (!matched.length && !fuzzyHit.size) continue;
|
|
7909
|
+
const scored = d.symbols.map((name2) => {
|
|
7910
|
+
const toks = new Set(subtokens(name2));
|
|
7911
|
+
let hits = 0;
|
|
7912
|
+
for (const t of symbolTerms) if (toks.has(t)) hits++;
|
|
7913
|
+
return { name: name2, hits };
|
|
7914
|
+
}).filter((s) => s.hits > 0).sort((a, b) => b.hits - a.hits || byStr(a.name, b.name));
|
|
7915
|
+
const result = {
|
|
7916
|
+
file: d.file,
|
|
7917
|
+
score: Number(score.toFixed(4)),
|
|
7918
|
+
matchedTerms: matched.sort(byStr),
|
|
7919
|
+
topSymbols: scored.slice(0, TOP_SYMBOLS).map((s) => s.name)
|
|
7920
|
+
};
|
|
7921
|
+
if (fuzzyHit.size) result.fuzzyTerms = [...fuzzyHit].sort(byStr);
|
|
7922
|
+
results.push(result);
|
|
7923
|
+
}
|
|
7924
|
+
results.sort((a, b) => b.score - a.score || byStr(a.file, b.file));
|
|
7925
|
+
return results.slice(0, opts.limit ?? DEFAULT_LIMIT);
|
|
7926
|
+
}
|
|
7927
|
+
var K1, B, DEFAULT_LIMIT, TOP_SYMBOLS, FUZZY_DICE_THRESHOLD, FUZZY_CAP;
|
|
7928
|
+
var init_bm25 = __esm({
|
|
7929
|
+
"src/bm25.ts"() {
|
|
7852
7930
|
"use strict";
|
|
7931
|
+
init_derived();
|
|
7932
|
+
init_util();
|
|
7933
|
+
init_sort();
|
|
7934
|
+
K1 = 1.2;
|
|
7935
|
+
B = 0.75;
|
|
7936
|
+
DEFAULT_LIMIT = 20;
|
|
7937
|
+
TOP_SYMBOLS = 5;
|
|
7938
|
+
FUZZY_DICE_THRESHOLD = 0.6;
|
|
7939
|
+
FUZZY_CAP = 3;
|
|
7940
|
+
}
|
|
7941
|
+
});
|
|
7942
|
+
|
|
7943
|
+
// src/complexity.ts
|
|
7944
|
+
import { join as join6 } from "path";
|
|
7945
|
+
function complexityOfSource(source) {
|
|
7946
|
+
return 1 + (source.match(BRANCH_RE) ?? []).length;
|
|
7947
|
+
}
|
|
7948
|
+
function symbolComplexity(scan2, rel, top = 50) {
|
|
7949
|
+
const out2 = [];
|
|
7950
|
+
for (const f of scan2.files) {
|
|
7951
|
+
if (f.kind !== "code") continue;
|
|
7952
|
+
if (rel && f.rel !== rel) continue;
|
|
7953
|
+
if (!f.symbols.length) continue;
|
|
7954
|
+
const lines = readText(join6(scan2.root, f.rel)).split("\n");
|
|
7955
|
+
for (const s of f.symbols) {
|
|
7956
|
+
if (s.kind === "reexport" || s.kind === "reexport-all") continue;
|
|
7957
|
+
const end = s.endLine ?? s.line;
|
|
7958
|
+
const body2 = lines.slice(s.line - 1, end).join("\n");
|
|
7959
|
+
const entry = { file: f.rel, name: s.name, line: s.line, complexity: complexityOfSource(body2) };
|
|
7960
|
+
if (s.endLine !== void 0) entry.endLine = s.endLine;
|
|
7961
|
+
out2.push(entry);
|
|
7962
|
+
}
|
|
7963
|
+
}
|
|
7964
|
+
out2.sort((a, b) => b.complexity - a.complexity || byStr(a.file, b.file) || a.line - b.line);
|
|
7965
|
+
return out2.slice(0, top);
|
|
7966
|
+
}
|
|
7967
|
+
function riskHotspots(scan2, churn, top = 20) {
|
|
7968
|
+
const complexityByFile = fileComplexityFor(scan2);
|
|
7969
|
+
const out2 = scan2.files.filter((f) => f.kind === "code").map((f) => {
|
|
7970
|
+
const complexity = complexityByFile.get(f.rel);
|
|
7971
|
+
const commits = churn.get(f.rel) ?? 0;
|
|
7972
|
+
return { file: f.rel, complexity, commits, score: (commits + 1) * complexity };
|
|
7973
|
+
});
|
|
7974
|
+
out2.sort((a, b) => b.score - a.score || byStr(a.file, b.file));
|
|
7975
|
+
return out2.slice(0, top);
|
|
7976
|
+
}
|
|
7977
|
+
var BRANCH_RE;
|
|
7978
|
+
var init_complexity = __esm({
|
|
7979
|
+
"src/complexity.ts"() {
|
|
7980
|
+
"use strict";
|
|
7981
|
+
init_derived();
|
|
7853
7982
|
init_walk();
|
|
7854
|
-
|
|
7983
|
+
init_sort();
|
|
7984
|
+
BRANCH_RE = /\b(if|elif|elsif|else\s+if|for|foreach|while|until|unless|case|when|match|catch|rescue|except)\b|&&|\|\||(?<![?:])\?(?![?.:])/g;
|
|
7985
|
+
}
|
|
7986
|
+
});
|
|
7987
|
+
|
|
7988
|
+
// src/derived.ts
|
|
7989
|
+
import { join as join7 } from "path";
|
|
7990
|
+
function cacheFor(scan2) {
|
|
7991
|
+
let c2 = caches.get(scan2);
|
|
7992
|
+
if (!c2) caches.set(scan2, c2 = {});
|
|
7993
|
+
return c2;
|
|
7994
|
+
}
|
|
7995
|
+
function resolveContextFor(scan2) {
|
|
7996
|
+
const c2 = cacheFor(scan2);
|
|
7997
|
+
return c2.resolveCtx ??= buildResolveContext(scan2);
|
|
7998
|
+
}
|
|
7999
|
+
function importPairsFor(scan2) {
|
|
8000
|
+
const c2 = cacheFor(scan2);
|
|
8001
|
+
if (!c2.importPairs) {
|
|
8002
|
+
const ctx = resolveContextFor(scan2);
|
|
8003
|
+
const pairs = /* @__PURE__ */ new Set();
|
|
8004
|
+
for (const f of scan2.files) {
|
|
8005
|
+
for (const ref of f.refs) {
|
|
8006
|
+
if (ref.kind !== "import") continue;
|
|
8007
|
+
const r = resolveImport(f.rel, f.ext, ref.spec, ctx);
|
|
8008
|
+
if (r.kind === "resolved" && r.target !== f.rel) pairs.add(`${f.rel}|${r.target}`);
|
|
8009
|
+
}
|
|
8010
|
+
}
|
|
8011
|
+
c2.importPairs = pairs;
|
|
8012
|
+
}
|
|
8013
|
+
return c2.importPairs;
|
|
8014
|
+
}
|
|
8015
|
+
function uniqueDefsFor(scan2) {
|
|
8016
|
+
const c2 = cacheFor(scan2);
|
|
8017
|
+
return c2.uniqueDefs ??= uniqueSymbolDefs(scan2);
|
|
8018
|
+
}
|
|
8019
|
+
function symbolRefsFor(scan2) {
|
|
8020
|
+
const c2 = cacheFor(scan2);
|
|
8021
|
+
return c2.symbolRefs ??= computeSymbolRefs(scan2);
|
|
8022
|
+
}
|
|
8023
|
+
function callerIndexFor(scan2) {
|
|
8024
|
+
const c2 = cacheFor(scan2);
|
|
8025
|
+
return c2.callerIndex ??= buildCallerIndex(scan2, importPairsFor(scan2));
|
|
8026
|
+
}
|
|
8027
|
+
function bm25DocsFor(scan2) {
|
|
8028
|
+
const c2 = cacheFor(scan2);
|
|
8029
|
+
return (c2.bm25 ??= { docs: buildDocs(scan2) }).docs;
|
|
8030
|
+
}
|
|
8031
|
+
function bm25TrigramsFor(scan2) {
|
|
8032
|
+
const c2 = cacheFor(scan2);
|
|
8033
|
+
const bm25 = c2.bm25 ??= { docs: buildDocs(scan2) };
|
|
8034
|
+
return bm25.trigrams ??= buildTrigramIndex(bm25.docs);
|
|
8035
|
+
}
|
|
8036
|
+
function fileComplexityFor(scan2) {
|
|
8037
|
+
const c2 = cacheFor(scan2);
|
|
8038
|
+
if (!c2.fileComplexity) {
|
|
8039
|
+
const m = /* @__PURE__ */ new Map();
|
|
8040
|
+
for (const f of scan2.files) {
|
|
8041
|
+
if (f.kind !== "code") continue;
|
|
8042
|
+
m.set(f.rel, complexityOfSource(readText(join7(scan2.root, f.rel))));
|
|
8043
|
+
}
|
|
8044
|
+
c2.fileComplexity = m;
|
|
8045
|
+
}
|
|
8046
|
+
return c2.fileComplexity;
|
|
8047
|
+
}
|
|
8048
|
+
var caches;
|
|
8049
|
+
var init_derived = __esm({
|
|
8050
|
+
"src/derived.ts"() {
|
|
8051
|
+
"use strict";
|
|
8052
|
+
init_resolve();
|
|
7855
8053
|
init_graph();
|
|
8054
|
+
init_symbols_json();
|
|
8055
|
+
init_callers();
|
|
8056
|
+
init_bm25();
|
|
8057
|
+
init_complexity();
|
|
8058
|
+
init_walk();
|
|
8059
|
+
caches = /* @__PURE__ */ new WeakMap();
|
|
8060
|
+
}
|
|
8061
|
+
});
|
|
8062
|
+
|
|
8063
|
+
// src/callers.ts
|
|
8064
|
+
function computeImportPairs(scan2) {
|
|
8065
|
+
return new Set(importPairsFor(scan2));
|
|
8066
|
+
}
|
|
8067
|
+
function buildCallerIndex(scan2, importPairs, opts = {}) {
|
|
8068
|
+
const pairs = importPairs ?? importPairsFor(scan2);
|
|
8069
|
+
const recall = opts.recall === true;
|
|
8070
|
+
const defs = /* @__PURE__ */ new Map();
|
|
8071
|
+
for (const f of scan2.files) {
|
|
8072
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8073
|
+
for (const s of f.symbols) {
|
|
8074
|
+
if (!s.exported || REFERENCE_KINDS3.has(s.kind)) continue;
|
|
8075
|
+
if (seen.has(s.name)) continue;
|
|
8076
|
+
seen.add(s.name);
|
|
8077
|
+
let arr = defs.get(s.name);
|
|
8078
|
+
if (!arr) defs.set(s.name, arr = []);
|
|
8079
|
+
arr.push(s);
|
|
8080
|
+
}
|
|
8081
|
+
}
|
|
8082
|
+
const localDefs = /* @__PURE__ */ new Map();
|
|
8083
|
+
for (const f of scan2.files) {
|
|
8084
|
+
const byName = /* @__PURE__ */ new Map();
|
|
8085
|
+
for (const s of f.symbols) {
|
|
8086
|
+
if (!REFERENCE_KINDS3.has(s.kind) && !byName.has(s.name)) byName.set(s.name, s);
|
|
8087
|
+
}
|
|
8088
|
+
localDefs.set(f.rel, byName);
|
|
8089
|
+
}
|
|
8090
|
+
const sites = /* @__PURE__ */ new Map();
|
|
8091
|
+
const record = (def, caller) => {
|
|
8092
|
+
let entry = sites.get(def.name + "\0" + def.file);
|
|
8093
|
+
if (!entry) sites.set(def.name + "\0" + def.file, entry = { def, callers: [] });
|
|
8094
|
+
entry.callers.push(caller);
|
|
8095
|
+
};
|
|
8096
|
+
for (const f of scan2.files) {
|
|
8097
|
+
if (!f.calls?.length) continue;
|
|
8098
|
+
const family = familyOf(f.lang);
|
|
8099
|
+
const own = localDefs.get(f.rel);
|
|
8100
|
+
for (const c2 of f.calls) {
|
|
8101
|
+
const local = own.get(c2.name);
|
|
8102
|
+
if (local) {
|
|
8103
|
+
if (local.line !== c2.line)
|
|
8104
|
+
record(local, recall ? { file: f.rel, line: c2.line, confidence: "corroborated" } : { file: f.rel, line: c2.line });
|
|
8105
|
+
continue;
|
|
8106
|
+
}
|
|
8107
|
+
const cands = (defs.get(c2.name) ?? []).filter((d) => familyOf(d.lang) === family && d.file !== f.rel).map((d) => ({ file: d.file, lang: d.lang }));
|
|
8108
|
+
if (!cands.length) continue;
|
|
8109
|
+
const imported = cands.filter((d) => pairs.has(`${f.rel}|${d.file}`));
|
|
8110
|
+
const chosen = family === "js" ? imported.length ? pickCandidate(f.rel, imported) : (
|
|
8111
|
+
// JS/TS gate: no corroborating import → no binding. Recall mode
|
|
8112
|
+
// relaxes this to a unique-repo-wide name match (issue #7).
|
|
8113
|
+
recall && cands.length === 1 ? cands[0] : void 0
|
|
8114
|
+
) : imported.length ? pickCandidate(f.rel, imported) : pickCandidate(f.rel, cands);
|
|
8115
|
+
if (!chosen) continue;
|
|
8116
|
+
const def = defs.get(c2.name).find((d) => d.file === chosen.file);
|
|
8117
|
+
record(
|
|
8118
|
+
def,
|
|
8119
|
+
recall ? { file: f.rel, line: c2.line, confidence: imported.length ? "corroborated" : "unique-name" } : { file: f.rel, line: c2.line }
|
|
8120
|
+
);
|
|
8121
|
+
}
|
|
8122
|
+
}
|
|
8123
|
+
const index = /* @__PURE__ */ new Map();
|
|
8124
|
+
const keys = [...sites.keys()].sort(byStr);
|
|
8125
|
+
for (const key of keys) {
|
|
8126
|
+
const { def, callers } = sites.get(key);
|
|
8127
|
+
callers.sort((a, b) => byStr(a.file, b.file) || a.line - b.line);
|
|
8128
|
+
if (!index.has(def.name)) index.set(def.name, { def, callers });
|
|
8129
|
+
else index.set(`${def.name}@${def.file}`, { def, callers });
|
|
8130
|
+
}
|
|
8131
|
+
return index;
|
|
8132
|
+
}
|
|
8133
|
+
function enclosingSymbol(scan2, file, line) {
|
|
8134
|
+
const f = scan2.files.find((x) => x.rel === file);
|
|
8135
|
+
if (!f?.symbols.length) return void 0;
|
|
8136
|
+
return enclosingAmong(f.symbols, line);
|
|
8137
|
+
}
|
|
8138
|
+
function enclosingAmong(symbols, line) {
|
|
8139
|
+
let best;
|
|
8140
|
+
for (const s of symbols) {
|
|
8141
|
+
if (REFERENCE_KINDS3.has(s.kind)) continue;
|
|
8142
|
+
if (s.line > line) continue;
|
|
8143
|
+
if (s.endLine !== void 0 && line > s.endLine) continue;
|
|
8144
|
+
if (!best || s.line > best.line || s.line === best.line && (s.endLine ?? Infinity) <= (best.endLine ?? Infinity)) {
|
|
8145
|
+
best = s;
|
|
8146
|
+
}
|
|
8147
|
+
}
|
|
8148
|
+
return best;
|
|
8149
|
+
}
|
|
8150
|
+
function buildRawCallerIndex(scan2) {
|
|
8151
|
+
const byName = /* @__PURE__ */ new Map();
|
|
8152
|
+
for (const f of scan2.files) {
|
|
8153
|
+
if (!f.calls?.length) continue;
|
|
8154
|
+
const symbols = f.symbols.filter((s) => !REFERENCE_KINDS3.has(s.kind));
|
|
8155
|
+
for (const c2 of f.calls) {
|
|
8156
|
+
const site = { file: f.rel, line: c2.line };
|
|
8157
|
+
if (c2.receiver !== void 0) site.receiver = c2.receiver;
|
|
8158
|
+
const enc = enclosingAmong(symbols, c2.line);
|
|
8159
|
+
if (enc) site.enclosingSymbol = enc;
|
|
8160
|
+
let arr = byName.get(c2.name);
|
|
8161
|
+
if (!arr) byName.set(c2.name, arr = []);
|
|
8162
|
+
arr.push(site);
|
|
8163
|
+
}
|
|
8164
|
+
}
|
|
8165
|
+
const index = /* @__PURE__ */ new Map();
|
|
8166
|
+
for (const name2 of [...byName.keys()].sort(byStr)) {
|
|
8167
|
+
const sites = byName.get(name2);
|
|
8168
|
+
sites.sort((a, b) => byStr(a.file, b.file) || a.line - b.line);
|
|
8169
|
+
index.set(name2, sites);
|
|
8170
|
+
}
|
|
8171
|
+
return index;
|
|
8172
|
+
}
|
|
8173
|
+
var REFERENCE_KINDS3;
|
|
8174
|
+
var init_callers = __esm({
|
|
8175
|
+
"src/callers.ts"() {
|
|
8176
|
+
"use strict";
|
|
8177
|
+
init_calls();
|
|
8178
|
+
init_derived();
|
|
8179
|
+
init_sort();
|
|
8180
|
+
REFERENCE_KINDS3 = /* @__PURE__ */ new Set(["reexport", "reexport-all", "default"]);
|
|
8181
|
+
}
|
|
8182
|
+
});
|
|
8183
|
+
|
|
8184
|
+
// src/query.ts
|
|
8185
|
+
import { join as join8 } from "path";
|
|
8186
|
+
function symbolsOverview(scan2, rel) {
|
|
8187
|
+
const f = scan2.files.find((x) => x.rel === rel);
|
|
8188
|
+
if (!f) return [];
|
|
8189
|
+
return [...f.symbols].filter((s) => !REFERENCE_KINDS4.has(s.kind)).sort((a, b) => a.line - b.line || byStr(a.name, b.name));
|
|
8190
|
+
}
|
|
8191
|
+
function findSymbol(scan2, namePath, opts = {}) {
|
|
8192
|
+
const segments = namePath.split("/").filter(Boolean);
|
|
8193
|
+
if (!segments.length) return [];
|
|
8194
|
+
const leaf = segments[segments.length - 1];
|
|
8195
|
+
const parents = segments.slice(0, -1);
|
|
8196
|
+
const matchName = (name2, wanted) => opts.substring ? name2.toLowerCase().includes(wanted.toLowerCase()) : name2 === wanted;
|
|
8197
|
+
const out2 = [];
|
|
8198
|
+
for (const f of scan2.files) {
|
|
8199
|
+
for (const s of f.symbols) {
|
|
8200
|
+
if (REFERENCE_KINDS4.has(s.kind)) continue;
|
|
8201
|
+
if (!matchName(s.name, leaf)) continue;
|
|
8202
|
+
if (parents.length) {
|
|
8203
|
+
const parent = parents[parents.length - 1];
|
|
8204
|
+
if (!s.parent || s.parent !== parent) continue;
|
|
8205
|
+
}
|
|
8206
|
+
out2.push({ ...s });
|
|
8207
|
+
}
|
|
8208
|
+
}
|
|
8209
|
+
out2.sort(
|
|
8210
|
+
(a, b) => Number(b.name === leaf) - Number(a.name === leaf) || byStr(a.file, b.file) || a.line - b.line
|
|
8211
|
+
);
|
|
8212
|
+
const capped = out2.slice(0, opts.maxResults ?? 50);
|
|
8213
|
+
if (opts.includeBody) {
|
|
8214
|
+
for (const m of capped) {
|
|
8215
|
+
const end = m.endLine ?? m.line;
|
|
8216
|
+
const content = readText(join8(scan2.root, m.file));
|
|
8217
|
+
if (!content) continue;
|
|
8218
|
+
m.body = content.split("\n").slice(m.line - 1, end).join("\n");
|
|
8219
|
+
}
|
|
8220
|
+
}
|
|
8221
|
+
return capped;
|
|
8222
|
+
}
|
|
8223
|
+
function findReferences(scan2, name2) {
|
|
8224
|
+
const defs = [];
|
|
8225
|
+
for (const f of scan2.files) {
|
|
8226
|
+
for (const s of f.symbols) {
|
|
8227
|
+
if (s.name === name2 && !REFERENCE_KINDS4.has(s.kind)) defs.push(s);
|
|
8228
|
+
}
|
|
8229
|
+
}
|
|
8230
|
+
defs.sort((a, b) => byStr(a.file, b.file) || a.line - b.line);
|
|
8231
|
+
const index = callerIndexFor(scan2);
|
|
8232
|
+
const entry = index.get(name2);
|
|
8233
|
+
const callSites = entry ? [...entry.callers] : [];
|
|
8234
|
+
const referencingFiles = /* @__PURE__ */ new Set();
|
|
8235
|
+
const unique = uniqueDefsFor(scan2);
|
|
8236
|
+
const defFile = unique.get(name2);
|
|
8237
|
+
for (const f of scan2.files) {
|
|
8238
|
+
if (f.rel === defFile) continue;
|
|
8239
|
+
if (f.kind === "code" && f.idents?.includes(name2)) referencingFiles.add(f.rel);
|
|
8240
|
+
else if (f.kind === "doc") {
|
|
8241
|
+
const content = scan2.docText.get(f.rel);
|
|
8242
|
+
if (content && new RegExp(`\\b${name2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(content)) {
|
|
8243
|
+
referencingFiles.add(f.rel);
|
|
8244
|
+
}
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8247
|
+
for (const site of callSites) referencingFiles.add(site.file);
|
|
8248
|
+
return { defs, callSites, referencingFiles: [...referencingFiles].sort(byStr) };
|
|
8249
|
+
}
|
|
8250
|
+
var REFERENCE_KINDS4;
|
|
8251
|
+
var init_query = __esm({
|
|
8252
|
+
"src/query.ts"() {
|
|
8253
|
+
"use strict";
|
|
8254
|
+
init_walk();
|
|
8255
|
+
init_derived();
|
|
7856
8256
|
init_sort();
|
|
7857
8257
|
REFERENCE_KINDS4 = /* @__PURE__ */ new Set(["reexport", "reexport-all", "default"]);
|
|
7858
8258
|
}
|
|
7859
8259
|
});
|
|
7860
8260
|
|
|
7861
8261
|
// src/edit.ts
|
|
7862
|
-
import { readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
7863
|
-
import { join as
|
|
8262
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
8263
|
+
import { join as join9 } from "path";
|
|
7864
8264
|
function resolveUniqueSymbol(scan2, namePath, file) {
|
|
7865
8265
|
let matches = findSymbol(scan2, namePath);
|
|
7866
8266
|
if (file) matches = matches.filter((m) => m.file === file);
|
|
@@ -7878,15 +8278,15 @@ function readLines(abs) {
|
|
|
7878
8278
|
function replaceSymbolBody(scan2, namePath, body2, file) {
|
|
7879
8279
|
const sym = resolveUniqueSymbol(scan2, namePath, file);
|
|
7880
8280
|
const end = sym.endLine ?? sym.line;
|
|
7881
|
-
const abs =
|
|
8281
|
+
const abs = join9(scan2.root, sym.file);
|
|
7882
8282
|
const lines = readLines(abs);
|
|
7883
8283
|
const newLines = body2.replace(/^\n+|\n+$/g, "").split("\n");
|
|
7884
8284
|
lines.splice(sym.line - 1, end - sym.line + 1, ...newLines);
|
|
7885
|
-
|
|
8285
|
+
writeFileSync2(abs, lines.join("\n"));
|
|
7886
8286
|
return { file: sym.file, startLine: sym.line, endLine: sym.line + newLines.length - 1, lines: newLines.length };
|
|
7887
8287
|
}
|
|
7888
8288
|
function insertAt(scan2, sym, body2, index, blankBefore, blankAfter) {
|
|
7889
|
-
const abs =
|
|
8289
|
+
const abs = join9(scan2.root, sym.file);
|
|
7890
8290
|
const lines = readLines(abs);
|
|
7891
8291
|
const minGap = SEPARATED_KINDS.has(sym.kind) ? 1 : 0;
|
|
7892
8292
|
const newLines = body2.replace(/^\n+|\n+$/g, "").split("\n");
|
|
@@ -7895,7 +8295,7 @@ function insertAt(scan2, sym, body2, index, blankBefore, blankAfter) {
|
|
|
7895
8295
|
block.push(...newLines);
|
|
7896
8296
|
if (blankAfter && minGap && lines[index]?.trim() !== "") block.push("");
|
|
7897
8297
|
lines.splice(index, 0, ...block);
|
|
7898
|
-
|
|
8298
|
+
writeFileSync2(abs, lines.join("\n"));
|
|
7899
8299
|
return { file: sym.file, startLine: index + 1, endLine: index + block.length, lines: block.length };
|
|
7900
8300
|
}
|
|
7901
8301
|
function insertAfterSymbol(scan2, namePath, body2, file) {
|
|
@@ -7917,8 +8317,8 @@ var init_edit = __esm({
|
|
|
7917
8317
|
});
|
|
7918
8318
|
|
|
7919
8319
|
// src/memory.ts
|
|
7920
|
-
import { mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as
|
|
7921
|
-
import { dirname as
|
|
8320
|
+
import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
8321
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
7922
8322
|
function sanitize(name2) {
|
|
7923
8323
|
const clean = name2.replace(/^mem:/, "").replace(/\.md$/, "");
|
|
7924
8324
|
if (!clean) throw new Error("memory name is empty");
|
|
@@ -7932,12 +8332,12 @@ function sanitize(name2) {
|
|
|
7932
8332
|
return clean;
|
|
7933
8333
|
}
|
|
7934
8334
|
function memoryPath(repo, name2) {
|
|
7935
|
-
return
|
|
8335
|
+
return join10(repo, ...MEMORY_DIR, `${sanitize(name2)}.md`);
|
|
7936
8336
|
}
|
|
7937
8337
|
function writeMemory(repo, name2, content) {
|
|
7938
8338
|
const path = memoryPath(repo, name2);
|
|
7939
|
-
|
|
7940
|
-
|
|
8339
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
8340
|
+
writeFileSync3(path, content.endsWith("\n") ? content : content + "\n");
|
|
7941
8341
|
return sanitize(name2);
|
|
7942
8342
|
}
|
|
7943
8343
|
function readMemory(repo, name2) {
|
|
@@ -7958,7 +8358,7 @@ function deleteMemory(repo, name2) {
|
|
|
7958
8358
|
return true;
|
|
7959
8359
|
}
|
|
7960
8360
|
function listMemories(repo) {
|
|
7961
|
-
const root =
|
|
8361
|
+
const root = join10(repo, ...MEMORY_DIR);
|
|
7962
8362
|
const out2 = [];
|
|
7963
8363
|
const walk2 = (dir, prefix) => {
|
|
7964
8364
|
let entries;
|
|
@@ -7968,7 +8368,7 @@ function listMemories(repo) {
|
|
|
7968
8368
|
return;
|
|
7969
8369
|
}
|
|
7970
8370
|
for (const e of entries) {
|
|
7971
|
-
if (e.isDirectory()) walk2(
|
|
8371
|
+
if (e.isDirectory()) walk2(join10(dir, e.name), prefix ? `${prefix}/${e.name}` : e.name);
|
|
7972
8372
|
else if (e.name.endsWith(".md")) out2.push(prefix ? `${prefix}/${e.name.slice(0, -3)}` : e.name.slice(0, -3));
|
|
7973
8373
|
}
|
|
7974
8374
|
};
|
|
@@ -7985,7 +8385,7 @@ var init_memory = __esm({
|
|
|
7985
8385
|
|
|
7986
8386
|
// src/workspaces.ts
|
|
7987
8387
|
import { existsSync as existsSync2, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
7988
|
-
import { join as
|
|
8388
|
+
import { join as join11 } from "path";
|
|
7989
8389
|
function readJson(path, label, warnings) {
|
|
7990
8390
|
const raw = readText(path);
|
|
7991
8391
|
if (!raw) return void 0;
|
|
@@ -8036,7 +8436,7 @@ function wsGlobToRegExp(pat) {
|
|
|
8036
8436
|
return new RegExp(`^${re}($|/)`);
|
|
8037
8437
|
}
|
|
8038
8438
|
function probeNodePkg(root, dir, kind, warnings) {
|
|
8039
|
-
const path =
|
|
8439
|
+
const path = join11(root, dir, "package.json");
|
|
8040
8440
|
if (!existsSync2(path)) return void 0;
|
|
8041
8441
|
const manifest = `${dir}/package.json`;
|
|
8042
8442
|
const pkg = readJson(path, manifest, warnings);
|
|
@@ -8050,7 +8450,7 @@ function probeNodePkg(root, dir, kind, warnings) {
|
|
|
8050
8450
|
return out2;
|
|
8051
8451
|
}
|
|
8052
8452
|
function probeCargo(root, dir) {
|
|
8053
|
-
const path =
|
|
8453
|
+
const path = join11(root, dir, "Cargo.toml");
|
|
8054
8454
|
if (!existsSync2(path)) return void 0;
|
|
8055
8455
|
const body2 = tomlSectionBody(readText(path), "package");
|
|
8056
8456
|
const out2 = {
|
|
@@ -8064,18 +8464,18 @@ function probeCargo(root, dir) {
|
|
|
8064
8464
|
return out2;
|
|
8065
8465
|
}
|
|
8066
8466
|
function probeGoMod(root, dir) {
|
|
8067
|
-
const path =
|
|
8467
|
+
const path = join11(root, dir, "go.mod");
|
|
8068
8468
|
if (!existsSync2(path)) return void 0;
|
|
8069
8469
|
const name2 = readText(path).match(/^module\s+(\S+)/m)?.[1] ?? dir;
|
|
8070
8470
|
return { name: name2, dir, kind: "go", manifest: `${dir}/go.mod` };
|
|
8071
8471
|
}
|
|
8072
8472
|
function probeMaven(root, dir) {
|
|
8073
|
-
const path =
|
|
8473
|
+
const path = join11(root, dir, "pom.xml");
|
|
8074
8474
|
if (!existsSync2(path)) return void 0;
|
|
8075
8475
|
return { name: ownArtifactId(readText(path)) ?? dir, dir, kind: "maven", manifest: `${dir}/pom.xml` };
|
|
8076
8476
|
}
|
|
8077
8477
|
function probePyproject(root, dir) {
|
|
8078
|
-
const path =
|
|
8478
|
+
const path = join11(root, dir, "pyproject.toml");
|
|
8079
8479
|
if (!existsSync2(path)) return void 0;
|
|
8080
8480
|
const toml = readText(path);
|
|
8081
8481
|
const project = tomlSectionBody(toml, "project");
|
|
@@ -8091,7 +8491,7 @@ function probePyproject(root, dir) {
|
|
|
8091
8491
|
return out2;
|
|
8092
8492
|
}
|
|
8093
8493
|
function probeComposer(root, dir, warnings) {
|
|
8094
|
-
const path =
|
|
8494
|
+
const path = join11(root, dir, "composer.json");
|
|
8095
8495
|
if (!existsSync2(path)) return void 0;
|
|
8096
8496
|
const manifest = `${dir}/composer.json`;
|
|
8097
8497
|
const pkg = readJson(path, manifest, warnings);
|
|
@@ -8105,7 +8505,7 @@ function probeComposer(root, dir, warnings) {
|
|
|
8105
8505
|
return out2;
|
|
8106
8506
|
}
|
|
8107
8507
|
function probeNxProject(root, dir, warnings) {
|
|
8108
|
-
const path =
|
|
8508
|
+
const path = join11(root, dir, "project.json");
|
|
8109
8509
|
if (!existsSync2(path)) return void 0;
|
|
8110
8510
|
const manifest = `${dir}/project.json`;
|
|
8111
8511
|
const proj = readJson(path, manifest, warnings);
|
|
@@ -8118,7 +8518,7 @@ function probeNxProject(root, dir, warnings) {
|
|
|
8118
8518
|
}
|
|
8119
8519
|
function probeGradle(root, dir) {
|
|
8120
8520
|
for (const f of ["build.gradle", "build.gradle.kts"]) {
|
|
8121
|
-
if (existsSync2(
|
|
8521
|
+
if (existsSync2(join11(root, dir, f))) {
|
|
8122
8522
|
return { name: dir, dir, kind: "gradle", manifest: `${dir}/${f}` };
|
|
8123
8523
|
}
|
|
8124
8524
|
}
|
|
@@ -8153,7 +8553,7 @@ function addPackage(root, dir, found, kind, warnings) {
|
|
|
8153
8553
|
}
|
|
8154
8554
|
function isDirAt(root, rel) {
|
|
8155
8555
|
try {
|
|
8156
|
-
return statSync3(
|
|
8556
|
+
return statSync3(join11(root, rel)).isDirectory();
|
|
8157
8557
|
} catch {
|
|
8158
8558
|
return false;
|
|
8159
8559
|
}
|
|
@@ -8161,7 +8561,7 @@ function isDirAt(root, rel) {
|
|
|
8161
8561
|
function subdirsOf(root, base) {
|
|
8162
8562
|
let entries;
|
|
8163
8563
|
try {
|
|
8164
|
-
entries = readdirSync3(base ?
|
|
8564
|
+
entries = readdirSync3(base ? join11(root, base) : root, { withFileTypes: true });
|
|
8165
8565
|
} catch {
|
|
8166
8566
|
return [];
|
|
8167
8567
|
}
|
|
@@ -8223,14 +8623,14 @@ function npmFamilyPatterns(root, warnings) {
|
|
|
8223
8623
|
if (t.startsWith("!")) negations.push(t.slice(1));
|
|
8224
8624
|
else positives.push({ pattern: t, kind });
|
|
8225
8625
|
};
|
|
8226
|
-
const pkg = readJson(
|
|
8626
|
+
const pkg = readJson(join11(root, "package.json"), "package.json", warnings);
|
|
8227
8627
|
const ws = pkg?.workspaces;
|
|
8228
8628
|
if (Array.isArray(ws)) {
|
|
8229
8629
|
for (const x of ws) if (typeof x === "string") push(x, "npm");
|
|
8230
8630
|
} else if (ws && typeof ws === "object" && Array.isArray(ws.packages)) {
|
|
8231
8631
|
for (const x of ws.packages) if (typeof x === "string") push(x, "npm");
|
|
8232
8632
|
}
|
|
8233
|
-
const pnpm = readText(
|
|
8633
|
+
const pnpm = readText(join11(root, "pnpm-workspace.yaml"));
|
|
8234
8634
|
let inPackages = false;
|
|
8235
8635
|
for (const line of pnpm.split(/\r?\n/)) {
|
|
8236
8636
|
if (/^\S/.test(line)) {
|
|
@@ -8244,11 +8644,11 @@ function npmFamilyPatterns(root, warnings) {
|
|
|
8244
8644
|
return { positives, negations };
|
|
8245
8645
|
}
|
|
8246
8646
|
function fallbackNpmPatterns(root, warnings) {
|
|
8247
|
-
const lerna = readJson(
|
|
8647
|
+
const lerna = readJson(join11(root, "lerna.json"), "lerna.json", warnings);
|
|
8248
8648
|
if (lerna && Array.isArray(lerna.packages)) {
|
|
8249
8649
|
return lerna.packages.filter((x) => typeof x === "string").map((pattern) => ({ pattern, kind: "lerna" }));
|
|
8250
8650
|
}
|
|
8251
|
-
const nx = readJson(
|
|
8651
|
+
const nx = readJson(join11(root, "nx.json"), "nx.json", warnings);
|
|
8252
8652
|
if (nx) {
|
|
8253
8653
|
const layout = nx.workspaceLayout ?? {};
|
|
8254
8654
|
const appsDir = typeof layout.appsDir === "string" ? layout.appsDir : "apps";
|
|
@@ -8258,7 +8658,7 @@ function fallbackNpmPatterns(root, warnings) {
|
|
|
8258
8658
|
return [];
|
|
8259
8659
|
}
|
|
8260
8660
|
function detectCargoMembers(root, found, warnings) {
|
|
8261
|
-
const toml = readText(
|
|
8661
|
+
const toml = readText(join11(root, "Cargo.toml"));
|
|
8262
8662
|
if (!toml) return;
|
|
8263
8663
|
const body2 = tomlSectionBody(toml, "workspace");
|
|
8264
8664
|
if (!body2) return;
|
|
@@ -8273,7 +8673,7 @@ function detectCargoMembers(root, found, warnings) {
|
|
|
8273
8673
|
}
|
|
8274
8674
|
}
|
|
8275
8675
|
function detectGoWork(root, found, warnings) {
|
|
8276
|
-
const gowork = readText(
|
|
8676
|
+
const gowork = readText(join11(root, "go.work"));
|
|
8277
8677
|
if (!gowork) return;
|
|
8278
8678
|
const dirs = [];
|
|
8279
8679
|
for (const block of gowork.matchAll(/^use\s*\(([\s\S]*?)\)/gm)) {
|
|
@@ -8289,7 +8689,7 @@ function detectGoWork(root, found, warnings) {
|
|
|
8289
8689
|
}
|
|
8290
8690
|
}
|
|
8291
8691
|
function detectMavenModules(root, found, warnings) {
|
|
8292
|
-
const pom = readText(
|
|
8692
|
+
const pom = readText(join11(root, "pom.xml"));
|
|
8293
8693
|
if (!pom) return;
|
|
8294
8694
|
const modules = pom.match(/<modules>([\s\S]*?)<\/modules>/)?.[1];
|
|
8295
8695
|
if (!modules) return;
|
|
@@ -8298,7 +8698,7 @@ function detectMavenModules(root, found, warnings) {
|
|
|
8298
8698
|
}
|
|
8299
8699
|
}
|
|
8300
8700
|
function detectUvMembers(root, found, warnings) {
|
|
8301
|
-
const toml = readText(
|
|
8701
|
+
const toml = readText(join11(root, "pyproject.toml"));
|
|
8302
8702
|
if (!toml) return;
|
|
8303
8703
|
const body2 = tomlSectionBody(toml, "tool.uv.workspace");
|
|
8304
8704
|
if (!body2) return;
|
|
@@ -8313,7 +8713,7 @@ function detectUvMembers(root, found, warnings) {
|
|
|
8313
8713
|
}
|
|
8314
8714
|
}
|
|
8315
8715
|
function detectComposerPathRepos(root, found, warnings) {
|
|
8316
|
-
const composer = readJson(
|
|
8716
|
+
const composer = readJson(join11(root, "composer.json"), "composer.json", warnings);
|
|
8317
8717
|
const repos = composer?.repositories;
|
|
8318
8718
|
if (!Array.isArray(repos)) return;
|
|
8319
8719
|
for (const r of repos) {
|
|
@@ -8324,7 +8724,7 @@ function detectComposerPathRepos(root, found, warnings) {
|
|
|
8324
8724
|
}
|
|
8325
8725
|
function detectGradleIncludes(root, found, warnings) {
|
|
8326
8726
|
for (const f of ["settings.gradle", "settings.gradle.kts"]) {
|
|
8327
|
-
const text = readText(
|
|
8727
|
+
const text = readText(join11(root, f));
|
|
8328
8728
|
if (!text) continue;
|
|
8329
8729
|
for (const line of text.split(/\r?\n/)) {
|
|
8330
8730
|
if (!/^\s*include[\s(]/.test(line)) continue;
|
|
@@ -8336,7 +8736,7 @@ function detectGradleIncludes(root, found, warnings) {
|
|
|
8336
8736
|
}
|
|
8337
8737
|
}
|
|
8338
8738
|
function npmEdges(root, pkg, byName, warnings) {
|
|
8339
|
-
const manifest = readJson(
|
|
8739
|
+
const manifest = readJson(join11(root, pkg.dir, "package.json"), `${pkg.dir}/package.json`, warnings);
|
|
8340
8740
|
if (!manifest) return [];
|
|
8341
8741
|
const edges = /* @__PURE__ */ new Set();
|
|
8342
8742
|
for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
@@ -8359,7 +8759,7 @@ function normalizeDepPath(fromDir, rel) {
|
|
|
8359
8759
|
return out2.join("/");
|
|
8360
8760
|
}
|
|
8361
8761
|
function cargoEdges(root, pkg, byName, byDir) {
|
|
8362
|
-
const toml = readText(
|
|
8762
|
+
const toml = readText(join11(root, pkg.dir, "Cargo.toml"));
|
|
8363
8763
|
if (!toml) return [];
|
|
8364
8764
|
const edges = /* @__PURE__ */ new Set();
|
|
8365
8765
|
for (const section of ["dependencies", "dev-dependencies", "build-dependencies"]) {
|
|
@@ -8383,7 +8783,7 @@ function cargoEdges(root, pkg, byName, byDir) {
|
|
|
8383
8783
|
return [...edges];
|
|
8384
8784
|
}
|
|
8385
8785
|
function goPkgEdges(root, pkg, byName, byDir) {
|
|
8386
|
-
const gomod = readText(
|
|
8786
|
+
const gomod = readText(join11(root, pkg.dir, "go.mod"));
|
|
8387
8787
|
if (!gomod) return [];
|
|
8388
8788
|
const edges = /* @__PURE__ */ new Set();
|
|
8389
8789
|
for (const m of gomod.matchAll(/^\s*(?:require\s+)?([^\s/(][^\s]*)\s+v[^\s]+/gm)) {
|
|
@@ -8397,7 +8797,7 @@ function goPkgEdges(root, pkg, byName, byDir) {
|
|
|
8397
8797
|
return [...edges];
|
|
8398
8798
|
}
|
|
8399
8799
|
function mavenEdges(root, pkg, byName) {
|
|
8400
|
-
const pom = readText(
|
|
8800
|
+
const pom = readText(join11(root, pkg.dir, "pom.xml"));
|
|
8401
8801
|
if (!pom) return [];
|
|
8402
8802
|
const edges = /* @__PURE__ */ new Set();
|
|
8403
8803
|
for (const m of pom.replace(/<parent>[\s\S]*?<\/parent>/g, "").matchAll(/<dependency>([\s\S]*?)<\/dependency>/g)) {
|
|
@@ -8407,7 +8807,7 @@ function mavenEdges(root, pkg, byName) {
|
|
|
8407
8807
|
return [...edges];
|
|
8408
8808
|
}
|
|
8409
8809
|
function uvEdges(root, pkg, byName) {
|
|
8410
|
-
const toml = readText(
|
|
8810
|
+
const toml = readText(join11(root, pkg.dir, "pyproject.toml"));
|
|
8411
8811
|
if (!toml) return [];
|
|
8412
8812
|
const edges = /* @__PURE__ */ new Set();
|
|
8413
8813
|
const project = tomlSectionBody(toml, "project");
|
|
@@ -8427,7 +8827,7 @@ function uvEdges(root, pkg, byName) {
|
|
|
8427
8827
|
return [...edges];
|
|
8428
8828
|
}
|
|
8429
8829
|
function composerEdges(root, pkg, byName, warnings) {
|
|
8430
|
-
const manifest = readJson(
|
|
8830
|
+
const manifest = readJson(join11(root, pkg.dir, "composer.json"), `${pkg.dir}/composer.json`, warnings);
|
|
8431
8831
|
if (!manifest) return [];
|
|
8432
8832
|
const edges = /* @__PURE__ */ new Set();
|
|
8433
8833
|
for (const field of ["require", "require-dev"]) {
|
|
@@ -8441,7 +8841,7 @@ function composerEdges(root, pkg, byName, warnings) {
|
|
|
8441
8841
|
}
|
|
8442
8842
|
function gradleEdges(root, pkg, byName, byDir) {
|
|
8443
8843
|
for (const f of ["build.gradle", "build.gradle.kts"]) {
|
|
8444
|
-
const text = readText(
|
|
8844
|
+
const text = readText(join11(root, pkg.dir, f));
|
|
8445
8845
|
if (!text) continue;
|
|
8446
8846
|
const edges = /* @__PURE__ */ new Set();
|
|
8447
8847
|
for (const m of text.matchAll(/project\s*\(\s*["']:?([^"']+)["']\s*\)/g)) {
|
|
@@ -8977,117 +9377,50 @@ var init_tests_map = __esm({
|
|
|
8977
9377
|
];
|
|
8978
9378
|
TEST_DIR = /(^|\/)(tests?|__tests?__|spec|specs|e2e)(\/|$)/i;
|
|
8979
9379
|
}
|
|
8980
|
-
});
|
|
8981
|
-
|
|
8982
|
-
// src/surprise.ts
|
|
8983
|
-
function computeSurprises(graph) {
|
|
8984
|
-
const commOf = /* @__PURE__ */ new Map();
|
|
8985
|
-
const tierOf2 = /* @__PURE__ */ new Map();
|
|
8986
|
-
for (const m of graph.modules) {
|
|
8987
|
-
if (m.community !== void 0) commOf.set(m.slug, m.community);
|
|
8988
|
-
tierOf2.set(m.slug, m.tier);
|
|
8989
|
-
}
|
|
8990
|
-
const pairCount = /* @__PURE__ */ new Map();
|
|
8991
|
-
const pairKey = (a, b) => a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
8992
|
-
const candidates = [];
|
|
8993
|
-
for (const e of graph.moduleEdges) {
|
|
8994
|
-
if (e.dangling) continue;
|
|
8995
|
-
const ca = commOf.get(e.from);
|
|
8996
|
-
const cb = commOf.get(e.to);
|
|
8997
|
-
if (ca === void 0 || cb === void 0 || ca === cb) continue;
|
|
8998
|
-
pairCount.set(pairKey(ca, cb), (pairCount.get(pairKey(ca, cb)) ?? 0) + 1);
|
|
8999
|
-
if (!DEP_KINDS.has(e.kind)) continue;
|
|
9000
|
-
if (tierOf2.get(e.to) === 0) continue;
|
|
9001
|
-
candidates.push({ edge: e, comms: [ca, cb] });
|
|
9002
|
-
}
|
|
9003
|
-
return candidates.filter((c2) => pairCount.get(pairKey(c2.comms[0], c2.comms[1])) <= MAX_PAIR_EDGES).map((c2) => ({
|
|
9004
|
-
from: c2.edge.from,
|
|
9005
|
-
to: c2.edge.to,
|
|
9006
|
-
kind: c2.edge.kind,
|
|
9007
|
-
weight: c2.edge.weight,
|
|
9008
|
-
communities: c2.comms,
|
|
9009
|
-
pairEdges: pairCount.get(pairKey(c2.comms[0], c2.comms[1]))
|
|
9010
|
-
})).sort((a, b) => a.pairEdges - b.pairEdges || byStr(a.from, b.from) || byStr(a.to, b.to)).slice(0, SURPRISE_CAP);
|
|
9011
|
-
}
|
|
9012
|
-
function isSurprising(graph, from, to) {
|
|
9013
|
-
const list = graph.surprises ?? computeSurprises(graph);
|
|
9014
|
-
return list.some((s) => s.from === from && s.to === to);
|
|
9015
|
-
}
|
|
9016
|
-
var SURPRISE_CAP, MAX_PAIR_EDGES, DEP_KINDS;
|
|
9017
|
-
var init_surprise = __esm({
|
|
9018
|
-
"src/surprise.ts"() {
|
|
9019
|
-
"use strict";
|
|
9020
|
-
init_sort();
|
|
9021
|
-
SURPRISE_CAP = 24;
|
|
9022
|
-
MAX_PAIR_EDGES = 2;
|
|
9023
|
-
DEP_KINDS = /* @__PURE__ */ new Set(["import", "call", "use"]);
|
|
9024
|
-
}
|
|
9025
|
-
});
|
|
9026
|
-
|
|
9027
|
-
// src/render/symbols-json.ts
|
|
9028
|
-
function computeSymbolRefs(scan2) {
|
|
9029
|
-
const unique = uniqueSymbolDefs(scan2);
|
|
9030
|
-
const refs = /* @__PURE__ */ new Map();
|
|
9031
|
-
if (!unique.size) return refs;
|
|
9032
|
-
const add = (name2, file) => {
|
|
9033
|
-
let set = refs.get(name2);
|
|
9034
|
-
if (!set) refs.set(name2, set = /* @__PURE__ */ new Set());
|
|
9035
|
-
set.add(file);
|
|
9036
|
-
};
|
|
9037
|
-
for (const f of scan2.files) {
|
|
9038
|
-
if (f.kind === "code" && f.idents) {
|
|
9039
|
-
for (const id of f.idents) {
|
|
9040
|
-
const target = unique.get(id);
|
|
9041
|
-
if (target && target !== f.rel) add(id, f.rel);
|
|
9042
|
-
}
|
|
9043
|
-
} else if (f.kind === "doc") {
|
|
9044
|
-
const content = scan2.docText.get(f.rel);
|
|
9045
|
-
if (!content) continue;
|
|
9046
|
-
for (const tok of content.split(/[^A-Za-z0-9_]+/)) {
|
|
9047
|
-
const target = unique.get(tok);
|
|
9048
|
-
if (target && target !== f.rel) add(tok, f.rel);
|
|
9049
|
-
}
|
|
9050
|
-
}
|
|
9051
|
-
}
|
|
9052
|
-
return refs;
|
|
9053
|
-
}
|
|
9054
|
-
function buildSymbolIndex(scan2, refs = /* @__PURE__ */ new Map()) {
|
|
9055
|
-
const defsByName = /* @__PURE__ */ new Map();
|
|
9056
|
-
for (const f of scan2.files) {
|
|
9057
|
-
for (const s of f.symbols) {
|
|
9058
|
-
let arr = defsByName.get(s.name);
|
|
9059
|
-
if (!arr) defsByName.set(s.name, arr = []);
|
|
9060
|
-
arr.push({
|
|
9061
|
-
file: s.file,
|
|
9062
|
-
line: s.line,
|
|
9063
|
-
...s.endLine !== void 0 ? { endLine: s.endLine } : {},
|
|
9064
|
-
kind: s.kind,
|
|
9065
|
-
exported: s.exported,
|
|
9066
|
-
lang: s.lang,
|
|
9067
|
-
...s.parent ? { parent: s.parent } : {}
|
|
9068
|
-
});
|
|
9069
|
-
}
|
|
9070
|
-
}
|
|
9071
|
-
const defs = {};
|
|
9072
|
-
for (const name2 of [...defsByName.keys()].sort(byStr)) {
|
|
9073
|
-
defs[name2] = defsByName.get(name2).slice().sort((a, b) => byStr(a.file, b.file) || a.line - b.line || byStr(a.kind, b.kind));
|
|
9380
|
+
});
|
|
9381
|
+
|
|
9382
|
+
// src/surprise.ts
|
|
9383
|
+
function computeSurprises(graph) {
|
|
9384
|
+
const commOf = /* @__PURE__ */ new Map();
|
|
9385
|
+
const tierOf2 = /* @__PURE__ */ new Map();
|
|
9386
|
+
for (const m of graph.modules) {
|
|
9387
|
+
if (m.community !== void 0) commOf.set(m.slug, m.community);
|
|
9388
|
+
tierOf2.set(m.slug, m.tier);
|
|
9074
9389
|
}
|
|
9075
|
-
const
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9390
|
+
const pairCount = /* @__PURE__ */ new Map();
|
|
9391
|
+
const pairKey = (a, b) => a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
9392
|
+
const candidates = [];
|
|
9393
|
+
for (const e of graph.moduleEdges) {
|
|
9394
|
+
if (e.dangling) continue;
|
|
9395
|
+
const ca = commOf.get(e.from);
|
|
9396
|
+
const cb = commOf.get(e.to);
|
|
9397
|
+
if (ca === void 0 || cb === void 0 || ca === cb) continue;
|
|
9398
|
+
pairCount.set(pairKey(ca, cb), (pairCount.get(pairKey(ca, cb)) ?? 0) + 1);
|
|
9399
|
+
if (!DEP_KINDS.has(e.kind)) continue;
|
|
9400
|
+
if (tierOf2.get(e.to) === 0) continue;
|
|
9401
|
+
candidates.push({ edge: e, comms: [ca, cb] });
|
|
9079
9402
|
}
|
|
9080
|
-
return
|
|
9403
|
+
return candidates.filter((c2) => pairCount.get(pairKey(c2.comms[0], c2.comms[1])) <= MAX_PAIR_EDGES).map((c2) => ({
|
|
9404
|
+
from: c2.edge.from,
|
|
9405
|
+
to: c2.edge.to,
|
|
9406
|
+
kind: c2.edge.kind,
|
|
9407
|
+
weight: c2.edge.weight,
|
|
9408
|
+
communities: c2.comms,
|
|
9409
|
+
pairEdges: pairCount.get(pairKey(c2.comms[0], c2.comms[1]))
|
|
9410
|
+
})).sort((a, b) => a.pairEdges - b.pairEdges || byStr(a.from, b.from) || byStr(a.to, b.to)).slice(0, SURPRISE_CAP);
|
|
9081
9411
|
}
|
|
9082
|
-
function
|
|
9083
|
-
|
|
9412
|
+
function isSurprising(graph, from, to) {
|
|
9413
|
+
const list = graph.surprises ?? computeSurprises(graph);
|
|
9414
|
+
return list.some((s) => s.from === from && s.to === to);
|
|
9084
9415
|
}
|
|
9085
|
-
var
|
|
9086
|
-
|
|
9416
|
+
var SURPRISE_CAP, MAX_PAIR_EDGES, DEP_KINDS;
|
|
9417
|
+
var init_surprise = __esm({
|
|
9418
|
+
"src/surprise.ts"() {
|
|
9087
9419
|
"use strict";
|
|
9088
|
-
init_types();
|
|
9089
9420
|
init_sort();
|
|
9090
|
-
|
|
9421
|
+
SURPRISE_CAP = 24;
|
|
9422
|
+
MAX_PAIR_EDGES = 2;
|
|
9423
|
+
DEP_KINDS = /* @__PURE__ */ new Set(["import", "call", "use"]);
|
|
9091
9424
|
}
|
|
9092
9425
|
});
|
|
9093
9426
|
|
|
@@ -9110,8 +9443,10 @@ var init_graph_json = __esm({
|
|
|
9110
9443
|
|
|
9111
9444
|
// src/pipeline.ts
|
|
9112
9445
|
function buildIndexArtifacts(repo, opts = {}) {
|
|
9113
|
-
|
|
9114
|
-
|
|
9446
|
+
return buildArtifactsFromScan(scanRepo(repo, opts), opts);
|
|
9447
|
+
}
|
|
9448
|
+
function buildArtifactsFromScan(scan2, opts = {}) {
|
|
9449
|
+
const ctx = resolveContextFor(scan2);
|
|
9115
9450
|
const { modules, moduleOf } = buildModules(scan2);
|
|
9116
9451
|
const graph = buildGraph(scan2, ctx, modules, moduleOf, opts.meta);
|
|
9117
9452
|
const communities = detectCommunities(graph.modules, graph.moduleEdges, opts.previousCommunities);
|
|
@@ -9130,14 +9465,14 @@ function buildIndexArtifacts(repo, opts = {}) {
|
|
|
9130
9465
|
}
|
|
9131
9466
|
const surprises = computeSurprises(graph);
|
|
9132
9467
|
if (surprises.length) graph.surprises = surprises;
|
|
9133
|
-
const symbols = buildSymbolIndex(scan2,
|
|
9468
|
+
const symbols = buildSymbolIndex(scan2, symbolRefsFor(scan2));
|
|
9134
9469
|
return { scan: scan2, graph, symbols };
|
|
9135
9470
|
}
|
|
9136
9471
|
var init_pipeline = __esm({
|
|
9137
9472
|
"src/pipeline.ts"() {
|
|
9138
9473
|
"use strict";
|
|
9139
9474
|
init_scan();
|
|
9140
|
-
|
|
9475
|
+
init_derived();
|
|
9141
9476
|
init_modules();
|
|
9142
9477
|
init_graph();
|
|
9143
9478
|
init_community();
|
|
@@ -9159,275 +9494,99 @@ function rgBackend(root, pattern, opts) {
|
|
|
9159
9494
|
"--null",
|
|
9160
9495
|
// path\0line:text — a `:12:` inside a filename can't corrupt parsing
|
|
9161
9496
|
"--color=never",
|
|
9162
|
-
"--no-messages",
|
|
9163
|
-
"--hidden",
|
|
9164
|
-
"--no-require-git",
|
|
9165
|
-
"--no-ignore-global",
|
|
9166
|
-
"--no-ignore-exclude",
|
|
9167
|
-
"--no-ignore-parent",
|
|
9168
|
-
"--no-ignore-dot",
|
|
9169
|
-
"--max-filesize",
|
|
9170
|
-
"1M"
|
|
9171
|
-
];
|
|
9172
|
-
for (const d of IGNORE_DIRS) args2.push("--glob", `!**/${d}/**`);
|
|
9173
|
-
for (const l of LOCKFILES) args2.push("--iglob", `!**/${l}`);
|
|
9174
|
-
for (const ext of BINARY_EXT) args2.push("--iglob", `!**/*${ext}`);
|
|
9175
|
-
args2.push("--glob", "!*.min.js", "--glob", "!*.min.css");
|
|
9176
|
-
if (opts.ignoreCase) args2.push("--ignore-case");
|
|
9177
|
-
const user = opts.globs ?? [];
|
|
9178
|
-
const anchor = (g) => g.startsWith("/") ? g : `/${g}`;
|
|
9179
|
-
for (const g of user.filter((g2) => !g2.startsWith("!"))) args2.push("--glob", anchor(g));
|
|
9180
|
-
for (const g of user.filter((g2) => g2.startsWith("!"))) args2.push("--glob", `!${anchor(g.slice(1))}`);
|
|
9181
|
-
args2.push("--regexp", pattern, "./");
|
|
9182
|
-
const res = sh("rg", args2, { cwd: root });
|
|
9183
|
-
if (res.missing || !res.ok && res.status !== 1) return void 0;
|
|
9184
|
-
const hits = [];
|
|
9185
|
-
for (const line of res.stdout.split("\n")) {
|
|
9186
|
-
if (!line) continue;
|
|
9187
|
-
const nul = line.indexOf("\0");
|
|
9188
|
-
if (nul === -1) continue;
|
|
9189
|
-
const file = line.slice(0, nul).replace(/^\.\//, "");
|
|
9190
|
-
const rest = line.slice(nul + 1);
|
|
9191
|
-
const colon = rest.indexOf(":");
|
|
9192
|
-
if (colon === -1) continue;
|
|
9193
|
-
hits.push({ file, line: Number(rest.slice(0, colon)), text: rest.slice(colon + 1) });
|
|
9194
|
-
}
|
|
9195
|
-
return hits;
|
|
9196
|
-
}
|
|
9197
|
-
function jsBackend(root, re, opts) {
|
|
9198
|
-
const filter = compileGlobFilter(opts.globs?.map((g) => g.replace(/^(!?)\//, "$1")));
|
|
9199
|
-
const hits = [];
|
|
9200
|
-
for (const f of walk(root).files) {
|
|
9201
|
-
if (filter && !filter(f.rel)) continue;
|
|
9202
|
-
const content = readText(f.abs);
|
|
9203
|
-
if (!content) continue;
|
|
9204
|
-
const lines = content.split("\n");
|
|
9205
|
-
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
9206
|
-
if (re.test(lines[i2])) hits.push({ file: f.rel, line: i2 + 1, text: lines[i2] });
|
|
9207
|
-
}
|
|
9208
|
-
}
|
|
9209
|
-
return hits;
|
|
9210
|
-
}
|
|
9211
|
-
function grepRepo(root, pattern, opts = {}) {
|
|
9212
|
-
const re = new RegExp(pattern, opts.ignoreCase ? "i" : "");
|
|
9213
|
-
const max = opts.maxHits ?? DEFAULT_MAX_HITS;
|
|
9214
|
-
let hits;
|
|
9215
|
-
if (!opts.noRipgrep && have("rg")) hits = rgBackend(root, pattern, opts);
|
|
9216
|
-
hits ??= jsBackend(root, re, opts);
|
|
9217
|
-
return sortHits(hits).slice(0, max);
|
|
9218
|
-
}
|
|
9219
|
-
var DEFAULT_MAX_HITS;
|
|
9220
|
-
var init_grep = __esm({
|
|
9221
|
-
"src/grep.ts"() {
|
|
9222
|
-
"use strict";
|
|
9223
|
-
init_walk();
|
|
9224
|
-
init_glob();
|
|
9225
|
-
init_util();
|
|
9226
|
-
init_sort();
|
|
9227
|
-
DEFAULT_MAX_HITS = 200;
|
|
9228
|
-
}
|
|
9229
|
-
});
|
|
9230
|
-
|
|
9231
|
-
// src/bm25.ts
|
|
9232
|
-
function subtokens(raw) {
|
|
9233
|
-
const folded = foldText(raw).replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
|
|
9234
|
-
const out2 = [];
|
|
9235
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9236
|
-
const push = (t) => {
|
|
9237
|
-
if (t.length < 2 || seen.has(t)) return;
|
|
9238
|
-
seen.add(t);
|
|
9239
|
-
out2.push(t);
|
|
9240
|
-
};
|
|
9241
|
-
if (!/\s/.test(raw.trim())) push(foldText(raw).toLowerCase().replace(/[^a-z0-9_]+/g, ""));
|
|
9242
|
-
for (const part of folded.split(/[^A-Za-z0-9]+/)) push(part.toLowerCase());
|
|
9243
|
-
return out2;
|
|
9244
|
-
}
|
|
9245
|
-
function addTerms(doc, text) {
|
|
9246
|
-
for (const t of subtokens(text)) {
|
|
9247
|
-
doc.tf.set(t, (doc.tf.get(t) ?? 0) + 1);
|
|
9248
|
-
doc.len++;
|
|
9249
|
-
}
|
|
9250
|
-
}
|
|
9251
|
-
function buildDocs(scan2) {
|
|
9252
|
-
const docs = [];
|
|
9253
|
-
for (const f of scan2.files) {
|
|
9254
|
-
const doc = { file: f.rel, tf: /* @__PURE__ */ new Map(), len: 0, symbols: [] };
|
|
9255
|
-
const seenSym = /* @__PURE__ */ new Set();
|
|
9256
|
-
for (const s of f.symbols) {
|
|
9257
|
-
addTerms(doc, s.name);
|
|
9258
|
-
if (!seenSym.has(s.name)) {
|
|
9259
|
-
seenSym.add(s.name);
|
|
9260
|
-
doc.symbols.push(s.name);
|
|
9261
|
-
}
|
|
9262
|
-
}
|
|
9263
|
-
for (const seg of f.rel.split("/")) addTerms(doc, seg);
|
|
9264
|
-
for (const h of f.headings) addTerms(doc, h);
|
|
9265
|
-
if (f.summary) addTerms(doc, f.summary);
|
|
9266
|
-
docs.push(doc);
|
|
9267
|
-
}
|
|
9268
|
-
return docs;
|
|
9269
|
-
}
|
|
9270
|
-
function charTrigrams(term) {
|
|
9271
|
-
const padded = `^^${term}$$`;
|
|
9272
|
-
const grams = /* @__PURE__ */ new Set();
|
|
9273
|
-
for (let i2 = 0; i2 + 3 <= padded.length; i2++) grams.add(padded.slice(i2, i2 + 3));
|
|
9274
|
-
return grams;
|
|
9275
|
-
}
|
|
9276
|
-
function diceCoefficient(a, b) {
|
|
9277
|
-
if (!a.size || !b.size) return 0;
|
|
9278
|
-
let inter = 0;
|
|
9279
|
-
for (const g of a) if (b.has(g)) inter++;
|
|
9280
|
-
return 2 * inter / (a.size + b.size);
|
|
9281
|
-
}
|
|
9282
|
-
function buildTrigramIndex(docs) {
|
|
9283
|
-
const index = /* @__PURE__ */ new Map();
|
|
9284
|
-
for (const d of docs) {
|
|
9285
|
-
for (const term of d.tf.keys()) {
|
|
9286
|
-
if (!index.has(term)) index.set(term, charTrigrams(term));
|
|
9287
|
-
}
|
|
9288
|
-
}
|
|
9289
|
-
return index;
|
|
9290
|
-
}
|
|
9291
|
-
function searchIndex(scan2, query, opts = {}) {
|
|
9292
|
-
const terms = [];
|
|
9293
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9294
|
-
for (const kw of keywords(query)) {
|
|
9295
|
-
for (const t of subtokens(kw)) {
|
|
9296
|
-
if (seen.has(t)) continue;
|
|
9297
|
-
seen.add(t);
|
|
9298
|
-
terms.push(t);
|
|
9299
|
-
}
|
|
9300
|
-
}
|
|
9301
|
-
if (!terms.length) return [];
|
|
9302
|
-
const docs = buildDocs(scan2);
|
|
9303
|
-
const n = docs.length;
|
|
9304
|
-
if (!n) return [];
|
|
9305
|
-
let totalLen = 0;
|
|
9306
|
-
for (const d of docs) totalLen += d.len;
|
|
9307
|
-
const avgLen = totalLen / n || 1;
|
|
9308
|
-
const df = /* @__PURE__ */ new Map();
|
|
9309
|
-
for (const t of terms) {
|
|
9310
|
-
let count = 0;
|
|
9311
|
-
for (const d of docs) if (d.tf.has(t)) count++;
|
|
9312
|
-
df.set(t, count);
|
|
9313
|
-
}
|
|
9314
|
-
const fuzzyEnabled = opts.fuzzy ?? true;
|
|
9315
|
-
const fuzzyCandidates = /* @__PURE__ */ new Map();
|
|
9316
|
-
if (fuzzyEnabled) {
|
|
9317
|
-
const unmatched = terms.filter((t) => df.get(t) === 0);
|
|
9318
|
-
if (unmatched.length) {
|
|
9319
|
-
const trigramIndex = buildTrigramIndex(docs);
|
|
9320
|
-
for (const t of unmatched) {
|
|
9321
|
-
const grams = charTrigrams(t);
|
|
9322
|
-
const candidates = [];
|
|
9323
|
-
for (const [vocabTerm, vocabGrams] of trigramIndex) {
|
|
9324
|
-
const dice = diceCoefficient(grams, vocabGrams);
|
|
9325
|
-
if (dice >= FUZZY_DICE_THRESHOLD) candidates.push({ term: vocabTerm, dice });
|
|
9326
|
-
}
|
|
9327
|
-
candidates.sort((a, b) => b.dice - a.dice || byStr(a.term, b.term));
|
|
9328
|
-
fuzzyCandidates.set(t, candidates.slice(0, FUZZY_CAP));
|
|
9329
|
-
}
|
|
9330
|
-
}
|
|
9331
|
-
}
|
|
9332
|
-
const vocabDf = /* @__PURE__ */ new Map();
|
|
9333
|
-
const dfOfVocabTerm = (term) => {
|
|
9334
|
-
const known = df.get(term) ?? vocabDf.get(term);
|
|
9335
|
-
if (known !== void 0) return known;
|
|
9336
|
-
let count = 0;
|
|
9337
|
-
for (const d of docs) if (d.tf.has(term)) count++;
|
|
9338
|
-
vocabDf.set(term, count);
|
|
9339
|
-
return count;
|
|
9340
|
-
};
|
|
9341
|
-
const results = [];
|
|
9342
|
-
for (const d of docs) {
|
|
9343
|
-
let score = 0;
|
|
9344
|
-
const matched = [];
|
|
9345
|
-
const symbolTerms = /* @__PURE__ */ new Set();
|
|
9346
|
-
const fuzzyHit = /* @__PURE__ */ new Set();
|
|
9347
|
-
for (const t of terms) {
|
|
9348
|
-
const tf = d.tf.get(t);
|
|
9349
|
-
if (tf) {
|
|
9350
|
-
matched.push(t);
|
|
9351
|
-
symbolTerms.add(t);
|
|
9352
|
-
const idf = Math.log(1 + (n - df.get(t) + 0.5) / (df.get(t) + 0.5));
|
|
9353
|
-
score += idf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * d.len / avgLen));
|
|
9354
|
-
continue;
|
|
9355
|
-
}
|
|
9356
|
-
const candidates = fuzzyCandidates.get(t);
|
|
9357
|
-
if (!candidates) continue;
|
|
9358
|
-
for (const cand of candidates) {
|
|
9359
|
-
const ctf = d.tf.get(cand.term);
|
|
9360
|
-
if (!ctf) continue;
|
|
9361
|
-
const cdf = dfOfVocabTerm(cand.term);
|
|
9362
|
-
const idf = Math.log(1 + (n - cdf + 0.5) / (cdf + 0.5));
|
|
9363
|
-
const contribution = idf * (ctf * (K1 + 1)) / (ctf + K1 * (1 - B + B * d.len / avgLen));
|
|
9364
|
-
score += contribution * cand.dice;
|
|
9365
|
-
symbolTerms.add(cand.term);
|
|
9366
|
-
fuzzyHit.add(t);
|
|
9367
|
-
}
|
|
9497
|
+
"--no-messages",
|
|
9498
|
+
"--hidden",
|
|
9499
|
+
"--no-require-git",
|
|
9500
|
+
"--no-ignore-global",
|
|
9501
|
+
"--no-ignore-exclude",
|
|
9502
|
+
"--no-ignore-parent",
|
|
9503
|
+
"--no-ignore-dot",
|
|
9504
|
+
"--max-filesize",
|
|
9505
|
+
"1M"
|
|
9506
|
+
];
|
|
9507
|
+
for (const d of IGNORE_DIRS) args2.push("--glob", `!**/${d}/**`);
|
|
9508
|
+
for (const l of LOCKFILES) args2.push("--iglob", `!**/${l}`);
|
|
9509
|
+
for (const ext of BINARY_EXT) args2.push("--iglob", `!**/*${ext}`);
|
|
9510
|
+
args2.push("--glob", "!*.min.js", "--glob", "!*.min.css");
|
|
9511
|
+
if (opts.ignoreCase) args2.push("--ignore-case");
|
|
9512
|
+
const user = opts.globs ?? [];
|
|
9513
|
+
const anchor = (g) => g.startsWith("/") ? g : `/${g}`;
|
|
9514
|
+
for (const g of user.filter((g2) => !g2.startsWith("!"))) args2.push("--glob", anchor(g));
|
|
9515
|
+
for (const g of user.filter((g2) => g2.startsWith("!"))) args2.push("--glob", `!${anchor(g.slice(1))}`);
|
|
9516
|
+
args2.push("--regexp", pattern, "./");
|
|
9517
|
+
const res = sh("rg", args2, { cwd: root });
|
|
9518
|
+
if (res.missing || !res.ok && res.status !== 1) return void 0;
|
|
9519
|
+
const hits = [];
|
|
9520
|
+
for (const line of res.stdout.split("\n")) {
|
|
9521
|
+
if (!line) continue;
|
|
9522
|
+
const nul = line.indexOf("\0");
|
|
9523
|
+
if (nul === -1) continue;
|
|
9524
|
+
const file = line.slice(0, nul).replace(/^\.\//, "");
|
|
9525
|
+
const rest = line.slice(nul + 1);
|
|
9526
|
+
const colon = rest.indexOf(":");
|
|
9527
|
+
if (colon === -1) continue;
|
|
9528
|
+
hits.push({ file, line: Number(rest.slice(0, colon)), text: rest.slice(colon + 1) });
|
|
9529
|
+
}
|
|
9530
|
+
return hits;
|
|
9531
|
+
}
|
|
9532
|
+
function jsBackend(root, re, opts) {
|
|
9533
|
+
const filter = compileGlobFilter(opts.globs?.map((g) => g.replace(/^(!?)\//, "$1")));
|
|
9534
|
+
const hits = [];
|
|
9535
|
+
for (const f of walk(root).files) {
|
|
9536
|
+
if (filter && !filter(f.rel)) continue;
|
|
9537
|
+
const content = readText(f.abs);
|
|
9538
|
+
if (!content) continue;
|
|
9539
|
+
const lines = content.split("\n");
|
|
9540
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
9541
|
+
if (re.test(lines[i2])) hits.push({ file: f.rel, line: i2 + 1, text: lines[i2] });
|
|
9368
9542
|
}
|
|
9369
|
-
if (!matched.length && !fuzzyHit.size) continue;
|
|
9370
|
-
const scored = d.symbols.map((name2) => {
|
|
9371
|
-
const toks = new Set(subtokens(name2));
|
|
9372
|
-
let hits = 0;
|
|
9373
|
-
for (const t of symbolTerms) if (toks.has(t)) hits++;
|
|
9374
|
-
return { name: name2, hits };
|
|
9375
|
-
}).filter((s) => s.hits > 0).sort((a, b) => b.hits - a.hits || byStr(a.name, b.name));
|
|
9376
|
-
const result = {
|
|
9377
|
-
file: d.file,
|
|
9378
|
-
score: Number(score.toFixed(4)),
|
|
9379
|
-
matchedTerms: matched.sort(byStr),
|
|
9380
|
-
topSymbols: scored.slice(0, TOP_SYMBOLS).map((s) => s.name)
|
|
9381
|
-
};
|
|
9382
|
-
if (fuzzyHit.size) result.fuzzyTerms = [...fuzzyHit].sort(byStr);
|
|
9383
|
-
results.push(result);
|
|
9384
9543
|
}
|
|
9385
|
-
|
|
9386
|
-
return results.slice(0, opts.limit ?? DEFAULT_LIMIT);
|
|
9544
|
+
return hits;
|
|
9387
9545
|
}
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9546
|
+
function grepRepo(root, pattern, opts = {}) {
|
|
9547
|
+
const re = new RegExp(pattern, opts.ignoreCase ? "i" : "");
|
|
9548
|
+
const max = opts.maxHits ?? DEFAULT_MAX_HITS;
|
|
9549
|
+
let hits;
|
|
9550
|
+
if (!opts.noRipgrep && have("rg")) hits = rgBackend(root, pattern, opts);
|
|
9551
|
+
hits ??= jsBackend(root, re, opts);
|
|
9552
|
+
return sortHits(hits).slice(0, max);
|
|
9553
|
+
}
|
|
9554
|
+
var DEFAULT_MAX_HITS;
|
|
9555
|
+
var init_grep = __esm({
|
|
9556
|
+
"src/grep.ts"() {
|
|
9391
9557
|
"use strict";
|
|
9558
|
+
init_walk();
|
|
9559
|
+
init_glob();
|
|
9392
9560
|
init_util();
|
|
9393
9561
|
init_sort();
|
|
9394
|
-
|
|
9395
|
-
B = 0.75;
|
|
9396
|
-
DEFAULT_LIMIT = 20;
|
|
9397
|
-
TOP_SYMBOLS = 5;
|
|
9398
|
-
FUZZY_DICE_THRESHOLD = 0.6;
|
|
9399
|
-
FUZZY_CAP = 3;
|
|
9562
|
+
DEFAULT_MAX_HITS = 200;
|
|
9400
9563
|
}
|
|
9401
9564
|
});
|
|
9402
9565
|
|
|
9403
9566
|
// src/embed/model.ts
|
|
9404
|
-
import { createHash as
|
|
9567
|
+
import { createHash as createHash3 } from "crypto";
|
|
9405
9568
|
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
9406
|
-
import { join as
|
|
9569
|
+
import { join as join13 } from "path";
|
|
9407
9570
|
function resolveEmbedModelDir(repo) {
|
|
9408
9571
|
const env = process.env.CODEINDEX_EMBED_DIR;
|
|
9409
9572
|
const candidates = [];
|
|
9410
9573
|
if (env) candidates.push(env);
|
|
9411
|
-
if (repo) candidates.push(
|
|
9412
|
-
candidates.push(
|
|
9574
|
+
if (repo) candidates.push(join13(repo, ".codeindex", DEFAULT_EMBED_DIRNAME));
|
|
9575
|
+
candidates.push(join13(process.cwd(), ".codeindex", DEFAULT_EMBED_DIRNAME));
|
|
9413
9576
|
for (const c2 of candidates) {
|
|
9414
|
-
if (existsSync3(
|
|
9577
|
+
if (existsSync3(join13(c2, "model.json"))) return c2;
|
|
9415
9578
|
}
|
|
9416
9579
|
return void 0;
|
|
9417
9580
|
}
|
|
9418
9581
|
function hasEmbedModel(repo) {
|
|
9419
9582
|
return resolveEmbedModelDir(repo) !== void 0;
|
|
9420
9583
|
}
|
|
9421
|
-
function
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
if (!
|
|
9425
|
-
const raw = JSON.parse(readFileSync5(path, "utf8"));
|
|
9426
|
-
const { modelId, dim, vocab, weights } = raw;
|
|
9427
|
-
if (typeof modelId !== "string" || !modelId) throw new Error(`embed model: missing modelId in ${path}`);
|
|
9428
|
-
if (!Number.isInteger(dim) || dim <= 0) throw new Error(`embed model: bad dim ${dim} in ${path}`);
|
|
9584
|
+
function parseEmbedModel(raw, source) {
|
|
9585
|
+
const { modelId, dim, vocab, weights, unk: rawUnk } = raw ?? {};
|
|
9586
|
+
if (typeof modelId !== "string" || !modelId) throw new Error(`embed model: missing modelId in ${source}`);
|
|
9587
|
+
if (!Number.isInteger(dim) || dim <= 0) throw new Error(`embed model: bad dim ${dim} in ${source}`);
|
|
9429
9588
|
if (!Array.isArray(vocab) || !Array.isArray(weights) || vocab.length !== weights.length) {
|
|
9430
|
-
throw new Error(`embed model: vocab/weights length mismatch in ${
|
|
9589
|
+
throw new Error(`embed model: vocab/weights length mismatch in ${source}`);
|
|
9431
9590
|
}
|
|
9432
9591
|
const vocabSize = vocab.length;
|
|
9433
9592
|
const flat = new Float64Array(vocabSize * dim);
|
|
@@ -9442,10 +9601,17 @@ function loadEmbedModel(dir) {
|
|
|
9442
9601
|
}
|
|
9443
9602
|
for (let d = 0; d < dim; d++) flat[i2 * dim + d] = Number(row[d]);
|
|
9444
9603
|
}
|
|
9445
|
-
const unk = typeof
|
|
9604
|
+
const unk = typeof rawUnk === "string" ? rawUnk : "[UNK]";
|
|
9446
9605
|
const unkId = vmap.has(unk) ? vmap.get(unk) : -1;
|
|
9447
9606
|
return { modelId, dim, unk, unkId, vocabSize, vocab: vmap, weights: flat };
|
|
9448
9607
|
}
|
|
9608
|
+
function loadEmbedModel(dir) {
|
|
9609
|
+
if (!dir) return void 0;
|
|
9610
|
+
const path = join13(dir, "model.json");
|
|
9611
|
+
if (!existsSync3(path)) return void 0;
|
|
9612
|
+
const raw = JSON.parse(readFileSync5(path, "utf8"));
|
|
9613
|
+
return parseEmbedModel(raw, path);
|
|
9614
|
+
}
|
|
9449
9615
|
function resolveEmbedPullUrl() {
|
|
9450
9616
|
const env = process.env.CODEINDEX_EMBED_URL;
|
|
9451
9617
|
if (env && env.trim()) return { url: env.trim() };
|
|
@@ -9456,7 +9622,7 @@ async function fetchEmbedModel(url, expectedSha256) {
|
|
|
9456
9622
|
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
|
|
9457
9623
|
const body2 = await res.text();
|
|
9458
9624
|
if (expectedSha256) {
|
|
9459
|
-
const got =
|
|
9625
|
+
const got = createHash3("sha256").update(body2).digest("hex");
|
|
9460
9626
|
if (got !== expectedSha256) {
|
|
9461
9627
|
throw new Error(`sha256 mismatch: expected ${expectedSha256}, got ${got}`);
|
|
9462
9628
|
}
|
|
@@ -10060,8 +10226,8 @@ var init_repomap = __esm({
|
|
|
10060
10226
|
|
|
10061
10227
|
// src/deadcode.ts
|
|
10062
10228
|
function findDeadCode(scan2) {
|
|
10063
|
-
const callers =
|
|
10064
|
-
const refs =
|
|
10229
|
+
const callers = callerIndexFor(scan2);
|
|
10230
|
+
const refs = symbolRefsFor(scan2);
|
|
10065
10231
|
const out2 = [];
|
|
10066
10232
|
const consider = (s) => s.exported && !REFERENCE_KINDS6.has(s.kind) && !isTestPath(s.file) && !ENTRYPOINT_RE.test(s.file);
|
|
10067
10233
|
for (const f of scan2.files) {
|
|
@@ -10080,8 +10246,7 @@ var REFERENCE_KINDS6, ENTRYPOINT_RE;
|
|
|
10080
10246
|
var init_deadcode = __esm({
|
|
10081
10247
|
"src/deadcode.ts"() {
|
|
10082
10248
|
"use strict";
|
|
10083
|
-
|
|
10084
|
-
init_symbols_json();
|
|
10249
|
+
init_derived();
|
|
10085
10250
|
init_tests_map();
|
|
10086
10251
|
init_sort();
|
|
10087
10252
|
REFERENCE_KINDS6 = /* @__PURE__ */ new Set(["reexport", "reexport-all", "default"]);
|
|
@@ -10089,49 +10254,6 @@ var init_deadcode = __esm({
|
|
|
10089
10254
|
}
|
|
10090
10255
|
});
|
|
10091
10256
|
|
|
10092
|
-
// src/complexity.ts
|
|
10093
|
-
import { join as join11 } from "path";
|
|
10094
|
-
function complexityOfSource(source) {
|
|
10095
|
-
return 1 + (source.match(BRANCH_RE) ?? []).length;
|
|
10096
|
-
}
|
|
10097
|
-
function symbolComplexity(scan2, rel, top = 50) {
|
|
10098
|
-
const out2 = [];
|
|
10099
|
-
for (const f of scan2.files) {
|
|
10100
|
-
if (f.kind !== "code") continue;
|
|
10101
|
-
if (rel && f.rel !== rel) continue;
|
|
10102
|
-
if (!f.symbols.length) continue;
|
|
10103
|
-
const lines = readText(join11(scan2.root, f.rel)).split("\n");
|
|
10104
|
-
for (const s of f.symbols) {
|
|
10105
|
-
if (s.kind === "reexport" || s.kind === "reexport-all") continue;
|
|
10106
|
-
const end = s.endLine ?? s.line;
|
|
10107
|
-
const body2 = lines.slice(s.line - 1, end).join("\n");
|
|
10108
|
-
const entry = { file: f.rel, name: s.name, line: s.line, complexity: complexityOfSource(body2) };
|
|
10109
|
-
if (s.endLine !== void 0) entry.endLine = s.endLine;
|
|
10110
|
-
out2.push(entry);
|
|
10111
|
-
}
|
|
10112
|
-
}
|
|
10113
|
-
out2.sort((a, b) => b.complexity - a.complexity || byStr(a.file, b.file) || a.line - b.line);
|
|
10114
|
-
return out2.slice(0, top);
|
|
10115
|
-
}
|
|
10116
|
-
function riskHotspots(scan2, churn, top = 20) {
|
|
10117
|
-
const out2 = scan2.files.filter((f) => f.kind === "code").map((f) => {
|
|
10118
|
-
const complexity = complexityOfSource(readText(join11(scan2.root, f.rel)));
|
|
10119
|
-
const commits = churn.get(f.rel) ?? 0;
|
|
10120
|
-
return { file: f.rel, complexity, commits, score: (commits + 1) * complexity };
|
|
10121
|
-
});
|
|
10122
|
-
out2.sort((a, b) => b.score - a.score || byStr(a.file, b.file));
|
|
10123
|
-
return out2.slice(0, top);
|
|
10124
|
-
}
|
|
10125
|
-
var BRANCH_RE;
|
|
10126
|
-
var init_complexity = __esm({
|
|
10127
|
-
"src/complexity.ts"() {
|
|
10128
|
-
"use strict";
|
|
10129
|
-
init_walk();
|
|
10130
|
-
init_sort();
|
|
10131
|
-
BRANCH_RE = /\b(if|elif|elsif|else\s+if|for|foreach|while|until|unless|case|when|match|catch|rescue|except)\b|&&|\|\||(?<![?:])\?(?![?.:])/g;
|
|
10132
|
-
}
|
|
10133
|
-
});
|
|
10134
|
-
|
|
10135
10257
|
// src/viz.ts
|
|
10136
10258
|
function renderMermaid(graph, opts = {}) {
|
|
10137
10259
|
const maxEdges = opts.maxEdges ?? 80;
|
|
@@ -10172,10 +10294,17 @@ var init_viz = __esm({
|
|
|
10172
10294
|
// src/mcp.ts
|
|
10173
10295
|
var mcp_exports = {};
|
|
10174
10296
|
__export(mcp_exports, {
|
|
10297
|
+
getArtifacts: () => getArtifacts,
|
|
10298
|
+
getScan: () => getScan,
|
|
10299
|
+
memoizedEmbedModel: () => memoizedEmbedModel,
|
|
10175
10300
|
memoizedEmbeddingIndex: () => memoizedEmbeddingIndex,
|
|
10176
10301
|
runMcpServer: () => runMcpServer,
|
|
10177
|
-
scanFingerprint: () => scanFingerprint
|
|
10302
|
+
scanFingerprint: () => scanFingerprint,
|
|
10303
|
+
toCacheMap: () => toCacheMap,
|
|
10304
|
+
warmGrammarsForRepo: () => warmGrammarsForRepo
|
|
10178
10305
|
});
|
|
10306
|
+
import { statSync as statSync4 } from "fs";
|
|
10307
|
+
import { join as join14 } from "path";
|
|
10179
10308
|
import { createInterface } from "readline";
|
|
10180
10309
|
function str(v) {
|
|
10181
10310
|
return typeof v === "string" && v ? v : void 0;
|
|
@@ -10196,12 +10325,72 @@ async function memoizedEmbeddingIndex(key, build) {
|
|
|
10196
10325
|
embeddingIndexCache = { key: cacheKey, index };
|
|
10197
10326
|
return index;
|
|
10198
10327
|
}
|
|
10328
|
+
function memoizedEmbedModel(modelDir) {
|
|
10329
|
+
let stat;
|
|
10330
|
+
try {
|
|
10331
|
+
stat = statSync4(join14(modelDir, "model.json"));
|
|
10332
|
+
} catch {
|
|
10333
|
+
return void 0;
|
|
10334
|
+
}
|
|
10335
|
+
const key = `${modelDir}:${stat.mtimeMs}:${stat.size}`;
|
|
10336
|
+
if (embedModelCache && embedModelCache.key === key) return embedModelCache.model;
|
|
10337
|
+
const model = loadEmbedModel(modelDir);
|
|
10338
|
+
if (model) embedModelCache = { key, model };
|
|
10339
|
+
return model;
|
|
10340
|
+
}
|
|
10341
|
+
function sessionKey(repo, opts) {
|
|
10342
|
+
return repo + "\0" + JSON.stringify({
|
|
10343
|
+
scope: opts.scope,
|
|
10344
|
+
include: opts.include,
|
|
10345
|
+
exclude: opts.exclude,
|
|
10346
|
+
gitignore: opts.gitignore,
|
|
10347
|
+
ignoreDirs: opts.ignoreDirs,
|
|
10348
|
+
maxBytes: opts.maxBytes,
|
|
10349
|
+
maxFiles: opts.maxFiles,
|
|
10350
|
+
maxCallsPerFile: opts.maxCallsPerFile,
|
|
10351
|
+
out: opts.out,
|
|
10352
|
+
fullHash: opts.fullHash
|
|
10353
|
+
});
|
|
10354
|
+
}
|
|
10355
|
+
function toCacheMap(scan2) {
|
|
10356
|
+
const m = /* @__PURE__ */ new Map();
|
|
10357
|
+
for (const f of scan2.files) m.set(f.rel, { hash: f.hash, record: f, size: f.size, mtimeMs: scan2.mtimes.get(f.rel) });
|
|
10358
|
+
return m;
|
|
10359
|
+
}
|
|
10360
|
+
function getScan(repo, opts = {}) {
|
|
10361
|
+
const key = sessionKey(repo, opts);
|
|
10362
|
+
if (sessionCache && sessionCache.key === key) {
|
|
10363
|
+
const fresh = scanRepo(repo, { ...opts, cache: sessionCache.cacheMap });
|
|
10364
|
+
if (fresh.contentUnchanged) {
|
|
10365
|
+
if (fresh.cacheDirty) sessionCache.cacheMap = toCacheMap(fresh);
|
|
10366
|
+
if (sessionCache.scan.commit !== fresh.commit) sessionCache.scan.commit = fresh.commit;
|
|
10367
|
+
return sessionCache.scan;
|
|
10368
|
+
}
|
|
10369
|
+
sessionCache = { key, scan: fresh, cacheMap: toCacheMap(fresh) };
|
|
10370
|
+
return fresh;
|
|
10371
|
+
}
|
|
10372
|
+
const scan2 = scanRepo(repo, opts);
|
|
10373
|
+
sessionCache = { key, scan: scan2, cacheMap: toCacheMap(scan2) };
|
|
10374
|
+
return scan2;
|
|
10375
|
+
}
|
|
10376
|
+
function getArtifacts(repo, opts = {}) {
|
|
10377
|
+
const scan2 = getScan(repo, opts);
|
|
10378
|
+
if (sessionCache && sessionCache.scan === scan2) {
|
|
10379
|
+
return sessionCache.arts ??= buildArtifactsFromScan(scan2, opts);
|
|
10380
|
+
}
|
|
10381
|
+
return buildArtifactsFromScan(scan2, opts);
|
|
10382
|
+
}
|
|
10383
|
+
async function warmGrammarsForRepo(repo) {
|
|
10384
|
+
const { files } = walk(repo, {});
|
|
10385
|
+
await ensureGrammars(grammarKeysForExts(files.map((f) => f.ext)));
|
|
10386
|
+
}
|
|
10199
10387
|
async function callTool(name2, args2) {
|
|
10200
10388
|
const repo = str(args2.repo);
|
|
10201
10389
|
if (!repo) throw new Error("`repo` is required (absolute path to the repository root)");
|
|
10202
10390
|
const scanOpts = { scope: str(args2.scope), include: strArray(args2.include), exclude: strArray(args2.exclude) };
|
|
10391
|
+
if (!SCANLESS_TOOLS.has(name2)) await warmGrammarsForRepo(repo);
|
|
10203
10392
|
if (name2 === "scan_summary") {
|
|
10204
|
-
const scan2 =
|
|
10393
|
+
const scan2 = getScan(repo, scanOpts);
|
|
10205
10394
|
return JSON.stringify(
|
|
10206
10395
|
{ engineVersion: ENGINE_VERSION, commit: scan2.commit, fileCount: scan2.files.length, languages: scan2.languages, capped: scan2.capped },
|
|
10207
10396
|
null,
|
|
@@ -10209,10 +10398,10 @@ async function callTool(name2, args2) {
|
|
|
10209
10398
|
);
|
|
10210
10399
|
}
|
|
10211
10400
|
if (name2 === "graph") {
|
|
10212
|
-
return renderGraphJson(
|
|
10401
|
+
return renderGraphJson(getArtifacts(repo, scanOpts).graph);
|
|
10213
10402
|
}
|
|
10214
10403
|
if (name2 === "symbols") {
|
|
10215
|
-
const { symbols } =
|
|
10404
|
+
const { symbols } = getArtifacts(repo, scanOpts);
|
|
10216
10405
|
const lookup = str(args2.name);
|
|
10217
10406
|
if (lookup) {
|
|
10218
10407
|
return JSON.stringify({ name: lookup, defs: symbols.defs[lookup] ?? [], refs: symbols.refs[lookup] ?? [] }, null, 2);
|
|
@@ -10220,7 +10409,7 @@ async function callTool(name2, args2) {
|
|
|
10220
10409
|
return JSON.stringify(symbols, null, 2);
|
|
10221
10410
|
}
|
|
10222
10411
|
if (name2 === "callers") {
|
|
10223
|
-
const index = buildCallerIndex(
|
|
10412
|
+
const index = buildCallerIndex(getScan(repo, scanOpts));
|
|
10224
10413
|
const lookup = str(args2.name);
|
|
10225
10414
|
if (lookup) {
|
|
10226
10415
|
const entry = index.get(lookup);
|
|
@@ -10243,12 +10432,12 @@ async function callTool(name2, args2) {
|
|
|
10243
10432
|
if (name2 === "symbols_overview") {
|
|
10244
10433
|
const file = str(args2.file);
|
|
10245
10434
|
if (!file) throw new Error("`file` is required");
|
|
10246
|
-
return JSON.stringify(symbolsOverview(
|
|
10435
|
+
return JSON.stringify(symbolsOverview(getScan(repo, scanOpts), file), null, 2);
|
|
10247
10436
|
}
|
|
10248
10437
|
if (name2 === "find_symbol") {
|
|
10249
10438
|
const namePath = str(args2.namePath);
|
|
10250
10439
|
if (!namePath) throw new Error("`namePath` is required");
|
|
10251
|
-
const matches = findSymbol(
|
|
10440
|
+
const matches = findSymbol(getScan(repo, scanOpts), namePath, {
|
|
10252
10441
|
substring: args2.substring === true,
|
|
10253
10442
|
includeBody: args2.includeBody === true
|
|
10254
10443
|
});
|
|
@@ -10257,15 +10446,17 @@ async function callTool(name2, args2) {
|
|
|
10257
10446
|
if (name2 === "find_references") {
|
|
10258
10447
|
const symName = str(args2.name);
|
|
10259
10448
|
if (!symName) throw new Error("`name` is required");
|
|
10260
|
-
return JSON.stringify(findReferences(
|
|
10449
|
+
return JSON.stringify(findReferences(getScan(repo, scanOpts), symName), null, 2);
|
|
10261
10450
|
}
|
|
10262
10451
|
if (name2 === "replace_symbol_body" || name2 === "insert_after_symbol" || name2 === "insert_before_symbol") {
|
|
10263
10452
|
const namePath = str(args2.namePath);
|
|
10264
10453
|
const body2 = typeof args2.body === "string" ? args2.body : void 0;
|
|
10265
10454
|
if (!namePath || body2 === void 0) throw new Error("`namePath` and `body` are required");
|
|
10266
|
-
const scan2 =
|
|
10455
|
+
const scan2 = getScan(repo, scanOpts);
|
|
10267
10456
|
const fn = name2 === "replace_symbol_body" ? replaceSymbolBody : name2 === "insert_after_symbol" ? insertAfterSymbol : insertBeforeSymbol;
|
|
10268
|
-
|
|
10457
|
+
const result = fn(scan2, namePath, body2, str(args2.file));
|
|
10458
|
+
sessionCache = void 0;
|
|
10459
|
+
return JSON.stringify(result, null, 2);
|
|
10269
10460
|
}
|
|
10270
10461
|
if (name2 === "write_memory") {
|
|
10271
10462
|
const memName = str(args2.name);
|
|
@@ -10289,10 +10480,10 @@ async function callTool(name2, args2) {
|
|
|
10289
10480
|
return JSON.stringify({ deleted: deleteMemory(repo, memName) }, null, 2);
|
|
10290
10481
|
}
|
|
10291
10482
|
if (name2 === "dead_code") {
|
|
10292
|
-
return JSON.stringify(findDeadCode(
|
|
10483
|
+
return JSON.stringify(findDeadCode(getScan(repo, scanOpts)), null, 2);
|
|
10293
10484
|
}
|
|
10294
10485
|
if (name2 === "complexity") {
|
|
10295
|
-
const scan2 =
|
|
10486
|
+
const scan2 = getScan(repo, scanOpts);
|
|
10296
10487
|
if (args2.risk === true) {
|
|
10297
10488
|
const { churn, ok } = gitChurn(repo);
|
|
10298
10489
|
return JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn) }, null, 2);
|
|
@@ -10300,15 +10491,15 @@ async function callTool(name2, args2) {
|
|
|
10300
10491
|
return JSON.stringify(symbolComplexity(scan2, str(args2.file)), null, 2);
|
|
10301
10492
|
}
|
|
10302
10493
|
if (name2 === "mermaid") {
|
|
10303
|
-
const { graph } =
|
|
10494
|
+
const { graph } = getArtifacts(repo, scanOpts);
|
|
10304
10495
|
return renderMermaid(graph, { module: str(args2.module) });
|
|
10305
10496
|
}
|
|
10306
10497
|
if (name2 === "repo_map") {
|
|
10307
|
-
const { scan: scan2, graph } =
|
|
10498
|
+
const { scan: scan2, graph } = getArtifacts(repo, scanOpts);
|
|
10308
10499
|
return renderRepoMap(scan2, graph, { budgetTokens: typeof args2.budgetTokens === "number" ? args2.budgetTokens : void 0 });
|
|
10309
10500
|
}
|
|
10310
10501
|
if (name2 === "hotspots") {
|
|
10311
|
-
const scan2 =
|
|
10502
|
+
const scan2 = getScan(repo, scanOpts);
|
|
10312
10503
|
const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
|
|
10313
10504
|
return JSON.stringify({ churnOk: ok, hotspots: rankHotspots(scan2, churn) }, null, 2);
|
|
10314
10505
|
}
|
|
@@ -10329,7 +10520,7 @@ async function callTool(name2, args2) {
|
|
|
10329
10520
|
if (name2 === "search") {
|
|
10330
10521
|
const query = str(args2.query);
|
|
10331
10522
|
if (!query) throw new Error("`query` is required");
|
|
10332
|
-
const scan2 =
|
|
10523
|
+
const scan2 = getScan(repo, scanOpts);
|
|
10333
10524
|
const limit = typeof args2.limit === "number" ? args2.limit : void 0;
|
|
10334
10525
|
const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
|
|
10335
10526
|
if (args2.semantic === true) {
|
|
@@ -10350,7 +10541,7 @@ async function callTool(name2, args2) {
|
|
|
10350
10541
|
}
|
|
10351
10542
|
}
|
|
10352
10543
|
const modelDir = resolveEmbedModelDir(repo);
|
|
10353
|
-
const model = modelDir ?
|
|
10544
|
+
const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
|
|
10354
10545
|
if (model) {
|
|
10355
10546
|
const index = await memoizedEmbeddingIndex(
|
|
10356
10547
|
{ mode: "static", identity: `${modelDir}#${model.modelId}`, scan: scan2 },
|
|
@@ -10370,7 +10561,7 @@ async function callTool(name2, args2) {
|
|
|
10370
10561
|
}
|
|
10371
10562
|
if (name2 === "embed_status") {
|
|
10372
10563
|
const modelDir = resolveEmbedModelDir(repo);
|
|
10373
|
-
const model = modelDir ?
|
|
10564
|
+
const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
|
|
10374
10565
|
const endpoint = resolveEmbedEndpoint();
|
|
10375
10566
|
const mode = endpoint ? "endpoint" : model ? "static" : "none";
|
|
10376
10567
|
const status = {
|
|
@@ -10384,13 +10575,16 @@ async function callTool(name2, args2) {
|
|
|
10384
10575
|
}
|
|
10385
10576
|
if (name2 === "check_rules") {
|
|
10386
10577
|
const rules = parseRules(args2.rules);
|
|
10387
|
-
const { graph } =
|
|
10578
|
+
const { graph } = getArtifacts(repo, scanOpts);
|
|
10388
10579
|
return JSON.stringify(checkRules(graph, rules), null, 2);
|
|
10389
10580
|
}
|
|
10390
10581
|
throw new Error(`unknown tool: ${name2}`);
|
|
10391
10582
|
}
|
|
10392
|
-
async function runMcpServer() {
|
|
10393
|
-
|
|
10583
|
+
async function runMcpServer(opts = {}) {
|
|
10584
|
+
const serverInfo = {
|
|
10585
|
+
name: opts.serverInfo?.name ?? "codeindex",
|
|
10586
|
+
version: opts.serverInfo?.version ?? ENGINE_VERSION
|
|
10587
|
+
};
|
|
10394
10588
|
const send = (msg) => {
|
|
10395
10589
|
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...msg }) + "\n");
|
|
10396
10590
|
};
|
|
@@ -10417,7 +10611,7 @@ async function runMcpServer() {
|
|
|
10417
10611
|
result: {
|
|
10418
10612
|
protocolVersion: "2024-11-05",
|
|
10419
10613
|
capabilities: { tools: {} },
|
|
10420
|
-
serverInfo
|
|
10614
|
+
serverInfo
|
|
10421
10615
|
}
|
|
10422
10616
|
});
|
|
10423
10617
|
} else if (req.method === "ping") {
|
|
@@ -10445,7 +10639,7 @@ async function runMcpServer() {
|
|
|
10445
10639
|
}
|
|
10446
10640
|
}
|
|
10447
10641
|
}
|
|
10448
|
-
var repoProp, scopeProps, TOOLS, embeddingIndexCache;
|
|
10642
|
+
var repoProp, scopeProps, TOOLS, embeddingIndexCache, embedModelCache, sessionCache, SCANLESS_TOOLS;
|
|
10449
10643
|
var init_mcp = __esm({
|
|
10450
10644
|
"src/mcp.ts"() {
|
|
10451
10645
|
"use strict";
|
|
@@ -10454,6 +10648,7 @@ var init_mcp = __esm({
|
|
|
10454
10648
|
init_pipeline();
|
|
10455
10649
|
init_graph_json();
|
|
10456
10650
|
init_scan();
|
|
10651
|
+
init_walk();
|
|
10457
10652
|
init_callers();
|
|
10458
10653
|
init_workspaces();
|
|
10459
10654
|
init_git();
|
|
@@ -10724,6 +10919,17 @@ var init_mcp = __esm({
|
|
|
10724
10919
|
}
|
|
10725
10920
|
}
|
|
10726
10921
|
];
|
|
10922
|
+
SCANLESS_TOOLS = /* @__PURE__ */ new Set([
|
|
10923
|
+
"workspaces",
|
|
10924
|
+
"churn",
|
|
10925
|
+
"coupling",
|
|
10926
|
+
"grep",
|
|
10927
|
+
"write_memory",
|
|
10928
|
+
"read_memory",
|
|
10929
|
+
"list_memories",
|
|
10930
|
+
"delete_memory",
|
|
10931
|
+
"embed_status"
|
|
10932
|
+
]);
|
|
10727
10933
|
}
|
|
10728
10934
|
});
|
|
10729
10935
|
|
|
@@ -10863,6 +11069,106 @@ init_code();
|
|
|
10863
11069
|
init_markdown();
|
|
10864
11070
|
init_loader();
|
|
10865
11071
|
init_extract();
|
|
11072
|
+
init_loader();
|
|
11073
|
+
|
|
11074
|
+
// src/ast/grammars-pull.ts
|
|
11075
|
+
init_types();
|
|
11076
|
+
import { createHash as createHash2 } from "crypto";
|
|
11077
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
11078
|
+
import { dirname as dirname2, resolve, sep as sep2 } from "path";
|
|
11079
|
+
import { gunzipSync } from "zlib";
|
|
11080
|
+
var DEFAULT_GRAMMARS_URL = `https://github.com/maxgfr/codeindex/releases/download/v${ENGINE_VERSION}/grammars-${ENGINE_VERSION}.tar.gz`;
|
|
11081
|
+
function resolveGrammarsPullTarget() {
|
|
11082
|
+
const env = process.env.CODEINDEX_GRAMMARS_URL;
|
|
11083
|
+
if (env && env.trim()) return { url: env.trim() };
|
|
11084
|
+
return { url: DEFAULT_GRAMMARS_URL, sha256Url: `${DEFAULT_GRAMMARS_URL}.sha256` };
|
|
11085
|
+
}
|
|
11086
|
+
async function fetchGrammarsTarball(url, expectedSha256) {
|
|
11087
|
+
const res = await fetch(url);
|
|
11088
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
|
|
11089
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
11090
|
+
if (expectedSha256) {
|
|
11091
|
+
const got = createHash2("sha256").update(buf).digest("hex");
|
|
11092
|
+
if (got !== expectedSha256) {
|
|
11093
|
+
throw new Error(`sha256 mismatch: expected ${expectedSha256}, got ${got}`);
|
|
11094
|
+
}
|
|
11095
|
+
}
|
|
11096
|
+
return buf;
|
|
11097
|
+
}
|
|
11098
|
+
function asBuffer(u) {
|
|
11099
|
+
return Buffer.isBuffer(u) ? u : Buffer.from(u.buffer, u.byteOffset, u.byteLength);
|
|
11100
|
+
}
|
|
11101
|
+
async function fetchExpectedSha256(url) {
|
|
11102
|
+
const res = await fetch(url);
|
|
11103
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
|
|
11104
|
+
const text = await res.text();
|
|
11105
|
+
const hex = (text.trim().split(/\s+/)[0] ?? "").toLowerCase();
|
|
11106
|
+
if (!/^[0-9a-f]{64}$/.test(hex)) throw new Error(`invalid sha256 sidecar at ${url}`);
|
|
11107
|
+
return hex;
|
|
11108
|
+
}
|
|
11109
|
+
function cstr(block, start2, len) {
|
|
11110
|
+
const slice = block.subarray(start2, start2 + len);
|
|
11111
|
+
const nul = slice.indexOf(0);
|
|
11112
|
+
return slice.toString("utf8", 0, nul === -1 ? slice.length : nul);
|
|
11113
|
+
}
|
|
11114
|
+
function* readTar(buf) {
|
|
11115
|
+
let off = 0;
|
|
11116
|
+
while (off + 512 <= buf.length) {
|
|
11117
|
+
const block = buf.subarray(off, off + 512);
|
|
11118
|
+
let allZero = true;
|
|
11119
|
+
for (let i2 = 0; i2 < 512; i2++) {
|
|
11120
|
+
if (block[i2] !== 0) {
|
|
11121
|
+
allZero = false;
|
|
11122
|
+
break;
|
|
11123
|
+
}
|
|
11124
|
+
}
|
|
11125
|
+
if (allZero) break;
|
|
11126
|
+
const name2 = cstr(block, 0, 100);
|
|
11127
|
+
const prefix = cstr(block, 345, 155);
|
|
11128
|
+
const sizeStr = cstr(block, 124, 12).trim();
|
|
11129
|
+
const size = sizeStr ? parseInt(sizeStr, 8) : 0;
|
|
11130
|
+
const type = String.fromCharCode(block[156] ?? 0);
|
|
11131
|
+
off += 512;
|
|
11132
|
+
const data = buf.subarray(off, off + size);
|
|
11133
|
+
off += Math.ceil(size / 512) * 512;
|
|
11134
|
+
yield { name: prefix ? `${prefix}/${name2}` : name2, type, data };
|
|
11135
|
+
}
|
|
11136
|
+
}
|
|
11137
|
+
function safeRelPath(name2) {
|
|
11138
|
+
if (!name2 || name2.includes("\0")) return null;
|
|
11139
|
+
if (name2.startsWith("/") || name2.startsWith("\\") || /^[A-Za-z]:/.test(name2)) return null;
|
|
11140
|
+
const out2 = [];
|
|
11141
|
+
for (const part of name2.split(/[/\\]/)) {
|
|
11142
|
+
if (part === "" || part === ".") continue;
|
|
11143
|
+
if (part === "..") return null;
|
|
11144
|
+
out2.push(part);
|
|
11145
|
+
}
|
|
11146
|
+
return out2.length ? out2.join("/") : null;
|
|
11147
|
+
}
|
|
11148
|
+
function extractTarInto(rawTar, destDir) {
|
|
11149
|
+
const root = resolve(destDir);
|
|
11150
|
+
const written = [];
|
|
11151
|
+
for (const entry of readTar(asBuffer(rawTar))) {
|
|
11152
|
+
if (entry.type !== "0" && entry.type !== "\0") continue;
|
|
11153
|
+
const rel = safeRelPath(entry.name);
|
|
11154
|
+
if (rel === null) throw new Error(`refusing unsafe tar entry: ${entry.name}`);
|
|
11155
|
+
const dest = resolve(destDir, rel);
|
|
11156
|
+
if (dest !== root && !dest.startsWith(root + sep2)) {
|
|
11157
|
+
throw new Error(`tar entry escapes destination: ${entry.name}`);
|
|
11158
|
+
}
|
|
11159
|
+
mkdirSync(dirname2(dest), { recursive: true });
|
|
11160
|
+
writeFileSync(dest, entry.data);
|
|
11161
|
+
written.push(rel);
|
|
11162
|
+
}
|
|
11163
|
+
return written;
|
|
11164
|
+
}
|
|
11165
|
+
function extractGrammarsTarball(bytes, destDir) {
|
|
11166
|
+
const b = asBuffer(bytes);
|
|
11167
|
+
const raw = b.length >= 2 && b[0] === 31 && b[1] === 139 ? gunzipSync(b) : b;
|
|
11168
|
+
return extractTarInto(raw, destDir);
|
|
11169
|
+
}
|
|
11170
|
+
|
|
11171
|
+
// src/engine.ts
|
|
10866
11172
|
init_resolve();
|
|
10867
11173
|
init_modules();
|
|
10868
11174
|
init_graph();
|
|
@@ -10883,7 +11189,7 @@ init_graph_json();
|
|
|
10883
11189
|
init_types();
|
|
10884
11190
|
init_walk();
|
|
10885
11191
|
init_sort();
|
|
10886
|
-
import { join as
|
|
11192
|
+
import { join as join12 } from "path";
|
|
10887
11193
|
var utf8 = new TextEncoder();
|
|
10888
11194
|
function pushVarint(out2, n) {
|
|
10889
11195
|
if (n < 0) throw new Error(`pushVarint: negative input ${n} is not a valid unsigned varint`);
|
|
@@ -11053,7 +11359,7 @@ function renderScip(scan2, opts = {}) {
|
|
|
11053
11359
|
};
|
|
11054
11360
|
const documents = [];
|
|
11055
11361
|
for (const f of docs) {
|
|
11056
|
-
const text = readText(
|
|
11362
|
+
const text = readText(join12(scan2.root, f.rel));
|
|
11057
11363
|
const lines = text.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
|
|
11058
11364
|
const locate = (lineNo, name2) => {
|
|
11059
11365
|
const line = lines[lineNo - 1];
|
|
@@ -11144,12 +11450,14 @@ init_util();
|
|
|
11144
11450
|
init_types();
|
|
11145
11451
|
init_types();
|
|
11146
11452
|
init_loader();
|
|
11453
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync6, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
11454
|
+
import { dirname as dirname4, join as join15, resolve as resolve2 } from "path";
|
|
11147
11455
|
init_pipeline();
|
|
11456
|
+
init_hash();
|
|
11148
11457
|
init_graph_json();
|
|
11149
11458
|
init_symbols_json();
|
|
11150
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
11151
|
-
import { join as join12, resolve } from "path";
|
|
11152
11459
|
init_scan();
|
|
11460
|
+
init_walk();
|
|
11153
11461
|
init_callers();
|
|
11154
11462
|
init_workspaces();
|
|
11155
11463
|
init_git();
|
|
@@ -11196,6 +11504,14 @@ Commands:
|
|
|
11196
11504
|
the source with CODEINDEX_EMBED_URL
|
|
11197
11505
|
embed serve Print (or --run) the docker command that starts the
|
|
11198
11506
|
containerized embedding server (rich tier)
|
|
11507
|
+
grammars Tree-sitter wasm grammars (optional AST tier; regex without them).
|
|
11508
|
+
Precedence: bundle-adjacent > CODEINDEX_GRAMMARS_DIR > shared cache:
|
|
11509
|
+
grammars status Active tier (adjacent/env/cache/none), resolved
|
|
11510
|
+
dir, pinned ENGINE_VERSION, pull-needed (JSON)
|
|
11511
|
+
grammars pull Fetch the per-release grammars-<version>.tar.gz
|
|
11512
|
+
asset into the shared cache (sha256-verified,
|
|
11513
|
+
atomic). Override the source with
|
|
11514
|
+
CODEINDEX_GRAMMARS_URL
|
|
11199
11515
|
rules Architecture rules (forbidden edges, cycles, orphans) validated
|
|
11200
11516
|
against the link-graph: --config <codeindex.rules.json>; exits 1
|
|
11201
11517
|
on any error-severity violation (a CI gate)
|
|
@@ -11216,8 +11532,11 @@ Flags:
|
|
|
11216
11532
|
--exclude <glob> Exclude matching paths (repeatable)
|
|
11217
11533
|
--scope <dir> Restrict to one directory (sugar for --include '<dir>/**')
|
|
11218
11534
|
--no-gitignore Do not honor .gitignore files (default: honored)
|
|
11535
|
+
--ignore-dir <name> Directory names to skip (repeatable) \u2014 REPLACES the
|
|
11536
|
+
default ignored-directory set, never merges with it
|
|
11219
11537
|
--max-files <n> Cap walked files (default 20000)
|
|
11220
11538
|
--max-bytes <n> Skip files above this size (default 1 MiB)
|
|
11539
|
+
--max-calls <n> Per-file call-site cap for extraction (default 512)
|
|
11221
11540
|
--no-ast Skip tree-sitter grammars even when present (regex tier)
|
|
11222
11541
|
--config <file> Rules config for \`rules\` (JSON: [{name, from, to, \u2026}])
|
|
11223
11542
|
--limit <n> Max results for \`search\` (default 20)
|
|
@@ -11232,7 +11551,7 @@ Flags:
|
|
|
11232
11551
|
each site corroborated|unique-name
|
|
11233
11552
|
`;
|
|
11234
11553
|
function parseFlags(args2) {
|
|
11235
|
-
const flags2 = { repo: process.cwd(), include: [], exclude: [], gitignore: true, noAst: false, fuzzy: true, semantic: false };
|
|
11554
|
+
const flags2 = { repo: process.cwd(), include: [], exclude: [], gitignore: true, ignoreDirs: [], noAst: false, fuzzy: true, semantic: false };
|
|
11236
11555
|
for (let i2 = 0; i2 < args2.length; i2++) {
|
|
11237
11556
|
const a = args2[i2];
|
|
11238
11557
|
const next = () => {
|
|
@@ -11246,23 +11565,25 @@ function parseFlags(args2) {
|
|
|
11246
11565
|
if (!Number.isFinite(n) || n <= 0) throw new Error(`${a} expects a positive number, got "${raw}"`);
|
|
11247
11566
|
return n;
|
|
11248
11567
|
};
|
|
11249
|
-
if (a === "--repo") flags2.repo =
|
|
11568
|
+
if (a === "--repo") flags2.repo = resolve2(next());
|
|
11250
11569
|
else if (a === "--out") {
|
|
11251
11570
|
const v = next();
|
|
11252
|
-
flags2.out = v === "-" ? "-" :
|
|
11571
|
+
flags2.out = v === "-" ? "-" : resolve2(v);
|
|
11253
11572
|
} else if (a === "--project-root") flags2.projectRoot = next();
|
|
11254
11573
|
else if (a === "--include") flags2.include.push(next());
|
|
11255
11574
|
else if (a === "--exclude") flags2.exclude.push(next());
|
|
11256
11575
|
else if (a === "--scope") flags2.scope = next();
|
|
11257
11576
|
else if (a === "--no-gitignore") flags2.gitignore = false;
|
|
11577
|
+
else if (a === "--ignore-dir") flags2.ignoreDirs.push(next());
|
|
11258
11578
|
else if (a === "--max-files") flags2.maxFiles = num();
|
|
11259
11579
|
else if (a === "--max-bytes") flags2.maxBytes = num();
|
|
11580
|
+
else if (a === "--max-calls") flags2.maxCalls = num();
|
|
11260
11581
|
else if (a === "--ignore-case") flags2.ignoreCase = true;
|
|
11261
11582
|
else if (a === "--max-hits") flags2.maxHits = num();
|
|
11262
11583
|
else if (a === "--budget-tokens") flags2.budgetTokens = num();
|
|
11263
11584
|
else if (a === "--no-ast") flags2.noAst = true;
|
|
11264
11585
|
else if (a === "--since") flags2.since = next();
|
|
11265
|
-
else if (a === "--config") flags2.config =
|
|
11586
|
+
else if (a === "--config") flags2.config = resolve2(next());
|
|
11266
11587
|
else if (a === "--limit") flags2.limit = num();
|
|
11267
11588
|
else if (a === "--no-fuzzy") flags2.fuzzy = false;
|
|
11268
11589
|
else if (a === "--semantic") flags2.semantic = true;
|
|
@@ -11274,19 +11595,26 @@ function parseFlags(args2) {
|
|
|
11274
11595
|
return flags2;
|
|
11275
11596
|
}
|
|
11276
11597
|
function emit(content, out2) {
|
|
11277
|
-
if (out2)
|
|
11598
|
+
if (out2) writeFileSync4(out2, content);
|
|
11278
11599
|
else process.stdout.write(content);
|
|
11279
11600
|
}
|
|
11280
|
-
function scanOptions(flags2) {
|
|
11601
|
+
function scanOptions(flags2, precomputedWalk) {
|
|
11281
11602
|
return {
|
|
11282
11603
|
include: flags2.include.length ? flags2.include : void 0,
|
|
11283
11604
|
exclude: flags2.exclude.length ? flags2.exclude : void 0,
|
|
11284
11605
|
scope: flags2.scope,
|
|
11285
11606
|
gitignore: flags2.gitignore,
|
|
11607
|
+
ignoreDirs: flags2.ignoreDirs.length ? flags2.ignoreDirs : void 0,
|
|
11286
11608
|
maxFiles: flags2.maxFiles,
|
|
11287
|
-
maxBytes: flags2.maxBytes
|
|
11609
|
+
maxBytes: flags2.maxBytes,
|
|
11610
|
+
maxCallsPerFile: flags2.maxCalls,
|
|
11611
|
+
// The walk performed once in runCli to warm the present-language grammars,
|
|
11612
|
+
// reused here so scanRepo does not traverse the tree a second time. Absent
|
|
11613
|
+
// for --no-ast / scan-less commands: scanRepo walks itself, unchanged.
|
|
11614
|
+
precomputedWalk
|
|
11288
11615
|
};
|
|
11289
11616
|
}
|
|
11617
|
+
var SCANLESS_COMMANDS = /* @__PURE__ */ new Set(["grep", "churn", "coupling", "workspaces", "grammars"]);
|
|
11290
11618
|
async function runCli(argv) {
|
|
11291
11619
|
const [cmd, ...rest] = argv;
|
|
11292
11620
|
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
@@ -11304,46 +11632,102 @@ async function runCli(argv) {
|
|
|
11304
11632
|
}
|
|
11305
11633
|
const flags2 = parseFlags(rest);
|
|
11306
11634
|
if (!existsSync4(flags2.repo)) throw new Error(`--repo path does not exist: ${flags2.repo}`);
|
|
11307
|
-
|
|
11635
|
+
const scans = !SCANLESS_COMMANDS.has(cmd) && !(cmd === "embed" && flags2.positional !== "build");
|
|
11636
|
+
let precomputedWalk;
|
|
11637
|
+
if (scans && !flags2.noAst) {
|
|
11638
|
+
precomputedWalk = walk(flags2.repo, {
|
|
11639
|
+
maxFileBytes: flags2.maxBytes,
|
|
11640
|
+
maxFiles: flags2.maxFiles,
|
|
11641
|
+
gitignore: flags2.gitignore,
|
|
11642
|
+
ignoreDirs: flags2.ignoreDirs.length ? flags2.ignoreDirs : void 0
|
|
11643
|
+
});
|
|
11644
|
+
await ensureGrammars(grammarKeysForExts(precomputedWalk.files.map((f) => f.ext)));
|
|
11645
|
+
}
|
|
11308
11646
|
if (cmd === "index") {
|
|
11309
11647
|
if (!flags2.out) throw new Error("index needs --out <dir>");
|
|
11310
11648
|
const outDir = flags2.out;
|
|
11311
|
-
|
|
11312
|
-
const cachePath =
|
|
11649
|
+
mkdirSync3(outDir, { recursive: true });
|
|
11650
|
+
const cachePath = join15(outDir, "cache.json");
|
|
11313
11651
|
let cache;
|
|
11652
|
+
let meta = {};
|
|
11314
11653
|
try {
|
|
11315
11654
|
const parsed = JSON.parse(readFileSync6(cachePath, "utf8"));
|
|
11316
11655
|
if (parsed.schemaVersion === SCHEMA_VERSION && parsed.extractorVersion === EXTRACTOR_VERSION) {
|
|
11317
11656
|
cache = new Map(Object.entries(parsed.files));
|
|
11657
|
+
meta = {
|
|
11658
|
+
engineVersion: parsed.engineVersion,
|
|
11659
|
+
commit: parsed.commit,
|
|
11660
|
+
graphSha1: parsed.graphSha1,
|
|
11661
|
+
symbolsSha1: parsed.symbolsSha1,
|
|
11662
|
+
embed: parsed.embed
|
|
11663
|
+
};
|
|
11318
11664
|
}
|
|
11319
11665
|
} catch {
|
|
11320
11666
|
}
|
|
11321
|
-
const
|
|
11322
|
-
writeFileSync3(join12(outDir, "graph.json"), renderGraphJson(graph));
|
|
11323
|
-
writeFileSync3(join12(outDir, "symbols.json"), renderSymbolsJson(symbols));
|
|
11324
|
-
const files = {};
|
|
11325
|
-
for (const f of scan2.files) {
|
|
11326
|
-
const entry = { hash: f.hash, record: f, size: f.size };
|
|
11327
|
-
const mtime = scan2.mtimes.get(f.rel);
|
|
11328
|
-
if (mtime !== void 0) entry.mtimeMs = mtime;
|
|
11329
|
-
files[f.rel] = entry;
|
|
11330
|
-
}
|
|
11331
|
-
writeFileSync3(
|
|
11332
|
-
cachePath,
|
|
11333
|
-
JSON.stringify({ schemaVersion: SCHEMA_VERSION, extractorVersion: EXTRACTOR_VERSION, files }) + "\n"
|
|
11334
|
-
);
|
|
11335
|
-
let embedNote = "";
|
|
11667
|
+
const scan2 = scanRepo(flags2.repo, { ...scanOptions(flags2, precomputedWalk), cache, out: outDir });
|
|
11336
11668
|
const modelDir = resolveEmbedModelDir(flags2.repo);
|
|
11337
11669
|
const model = modelDir ? loadEmbedModel(modelDir) : void 0;
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11670
|
+
const graphPath = join15(outDir, "graph.json");
|
|
11671
|
+
const symbolsPath = join15(outDir, "symbols.json");
|
|
11672
|
+
const embedPath = join15(outDir, "embeddings.bin");
|
|
11673
|
+
const artifactSha = (path) => {
|
|
11674
|
+
try {
|
|
11675
|
+
return sha1(readFileSync6(path));
|
|
11676
|
+
} catch {
|
|
11677
|
+
return void 0;
|
|
11678
|
+
}
|
|
11679
|
+
};
|
|
11680
|
+
const writeCache = (out2) => {
|
|
11681
|
+
const files = {};
|
|
11682
|
+
for (const f of scan2.files) {
|
|
11683
|
+
const entry = { hash: f.hash, record: f, size: f.size };
|
|
11684
|
+
const mtime = scan2.mtimes.get(f.rel);
|
|
11685
|
+
if (mtime !== void 0) entry.mtimeMs = mtime;
|
|
11686
|
+
files[f.rel] = entry;
|
|
11687
|
+
}
|
|
11688
|
+
writeFileSync4(
|
|
11689
|
+
cachePath,
|
|
11690
|
+
JSON.stringify({
|
|
11691
|
+
schemaVersion: SCHEMA_VERSION,
|
|
11692
|
+
extractorVersion: EXTRACTOR_VERSION,
|
|
11693
|
+
engineVersion: ENGINE_VERSION,
|
|
11694
|
+
commit: scan2.commit,
|
|
11695
|
+
graphSha1: out2.graphSha1,
|
|
11696
|
+
symbolsSha1: out2.symbolsSha1,
|
|
11697
|
+
embed: out2.embed,
|
|
11698
|
+
files
|
|
11699
|
+
}) + "\n"
|
|
11700
|
+
);
|
|
11701
|
+
};
|
|
11702
|
+
const embedUnchanged = !model || meta.embed !== void 0 && meta.embed.embedVersion === EMBED_VERSION && meta.embed.modelId === model.modelId && meta.embed.sha1 !== void 0 && artifactSha(embedPath) === meta.embed.sha1;
|
|
11703
|
+
const fastpath = scan2.contentUnchanged && meta.engineVersion === ENGINE_VERSION && meta.commit === scan2.commit && meta.graphSha1 !== void 0 && artifactSha(graphPath) === meta.graphSha1 && meta.symbolsSha1 !== void 0 && artifactSha(symbolsPath) === meta.symbolsSha1 && embedUnchanged;
|
|
11704
|
+
if (fastpath) {
|
|
11705
|
+
if (scan2.cacheDirty) writeCache(meta);
|
|
11706
|
+
process.stderr.write(
|
|
11707
|
+
`codeindex: ${scan2.files.length} files \u2192 ${outDir}/graph.json + symbols.json${scan2.capped ? " (capped)" : ""} (unchanged \u2014 artifacts reused)
|
|
11708
|
+
`
|
|
11709
|
+
);
|
|
11710
|
+
} else {
|
|
11711
|
+
const { graph, symbols } = buildArtifactsFromScan(scan2);
|
|
11712
|
+
const graphJson = renderGraphJson(graph);
|
|
11713
|
+
const symbolsJson = renderSymbolsJson(symbols);
|
|
11714
|
+
writeFileSync4(graphPath, graphJson);
|
|
11715
|
+
writeFileSync4(symbolsPath, symbolsJson);
|
|
11716
|
+
let embedNote = "";
|
|
11717
|
+
let embedMeta;
|
|
11718
|
+
if (model) {
|
|
11719
|
+
const index = buildEmbeddingIndex(scan2, model);
|
|
11720
|
+
const bytes = serializeEmbeddings(index);
|
|
11721
|
+
writeFileSync4(embedPath, bytes);
|
|
11722
|
+
embedMeta = { embedVersion: EMBED_VERSION, modelId: model.modelId, sha1: sha1(bytes) };
|
|
11723
|
+
embedNote = ` + embeddings.bin (${index.records.length} records, model ${model.modelId})`;
|
|
11724
|
+
}
|
|
11725
|
+
writeCache({ graphSha1: sha1(graphJson), symbolsSha1: sha1(symbolsJson), embed: embedMeta });
|
|
11726
|
+
process.stderr.write(`codeindex: ${scan2.files.length} files \u2192 ${outDir}/graph.json + symbols.json${embedNote}${scan2.capped ? " (capped)" : ""}
|
|
11344
11727
|
`);
|
|
11728
|
+
}
|
|
11345
11729
|
} else if (cmd === "scan") {
|
|
11346
|
-
const { scan: scan2 } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
|
|
11730
|
+
const { scan: scan2 } = buildIndexArtifacts(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11347
11731
|
const summary = {
|
|
11348
11732
|
engineVersion: ENGINE_VERSION,
|
|
11349
11733
|
commit: scan2.commit,
|
|
@@ -11353,30 +11737,30 @@ async function runCli(argv) {
|
|
|
11353
11737
|
};
|
|
11354
11738
|
emit(JSON.stringify(summary, null, 2) + "\n", flags2.out);
|
|
11355
11739
|
} else if (cmd === "graph") {
|
|
11356
|
-
const { graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
|
|
11740
|
+
const { graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11357
11741
|
emit(renderGraphJson(graph), flags2.out);
|
|
11358
11742
|
} else if (cmd === "symbols") {
|
|
11359
|
-
const { symbols } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
|
|
11743
|
+
const { symbols } = buildIndexArtifacts(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11360
11744
|
emit(renderSymbolsJson(symbols), flags2.out);
|
|
11361
11745
|
} else if (cmd === "scip") {
|
|
11362
|
-
const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
|
|
11746
|
+
const scan2 = scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11363
11747
|
const bytes = renderScip(scan2, { projectRoot: flags2.projectRoot });
|
|
11364
|
-
const out2 = flags2.out ??
|
|
11748
|
+
const out2 = flags2.out ?? resolve2("index.scip");
|
|
11365
11749
|
if (out2 === "-") process.stdout.write(Buffer.from(bytes));
|
|
11366
11750
|
else {
|
|
11367
|
-
|
|
11751
|
+
writeFileSync4(out2, bytes);
|
|
11368
11752
|
process.stderr.write(`codeindex: SCIP index \u2192 ${out2} (${bytes.length} bytes)
|
|
11369
11753
|
`);
|
|
11370
11754
|
}
|
|
11371
11755
|
} else if (cmd === "callers") {
|
|
11372
|
-
const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
|
|
11756
|
+
const scan2 = scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11373
11757
|
const index = buildCallerIndex(scan2, void 0, { recall: flags2.recall });
|
|
11374
11758
|
const obj = {};
|
|
11375
11759
|
for (const [name2, entry] of index) obj[name2] = entry;
|
|
11376
11760
|
emit(JSON.stringify(obj, null, 2) + "\n", flags2.out);
|
|
11377
11761
|
} else if (cmd === "search") {
|
|
11378
11762
|
if (!flags2.positional) throw new Error('search needs a query: cli.mjs search "<query>" --repo <dir>');
|
|
11379
|
-
const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
|
|
11763
|
+
const scan2 = scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11380
11764
|
if (flags2.semantic) {
|
|
11381
11765
|
const endpoint = resolveEmbedEndpoint();
|
|
11382
11766
|
const lexical = () => {
|
|
@@ -11466,17 +11850,17 @@ async function runCli(argv) {
|
|
|
11466
11850
|
return;
|
|
11467
11851
|
}
|
|
11468
11852
|
const model = loadEmbedModel(modelDir);
|
|
11469
|
-
|
|
11470
|
-
const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
|
|
11853
|
+
mkdirSync3(flags2.out, { recursive: true });
|
|
11854
|
+
const scan2 = scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11471
11855
|
const index = buildEmbeddingIndex(scan2, model);
|
|
11472
|
-
|
|
11856
|
+
writeFileSync4(join15(flags2.out, "embeddings.bin"), serializeEmbeddings(index));
|
|
11473
11857
|
process.stderr.write(`codeindex: ${index.records.length} embedding records \u2192 ${flags2.out}/embeddings.bin (model ${model.modelId})
|
|
11474
11858
|
`);
|
|
11475
11859
|
} else if (sub === "pull") {
|
|
11476
11860
|
const { url, sha256 } = resolveEmbedPullUrl();
|
|
11477
|
-
const destDir = process.env.CODEINDEX_EMBED_DIR ??
|
|
11478
|
-
|
|
11479
|
-
process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${
|
|
11861
|
+
const destDir = process.env.CODEINDEX_EMBED_DIR ?? join15(flags2.repo, ".codeindex", "models");
|
|
11862
|
+
mkdirSync3(destDir, { recursive: true });
|
|
11863
|
+
process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${join15(destDir, "model.json")}
|
|
11480
11864
|
`);
|
|
11481
11865
|
let body2;
|
|
11482
11866
|
try {
|
|
@@ -11488,22 +11872,111 @@ async function runCli(argv) {
|
|
|
11488
11872
|
return;
|
|
11489
11873
|
}
|
|
11490
11874
|
try {
|
|
11491
|
-
JSON.parse(body2);
|
|
11492
|
-
} catch {
|
|
11493
|
-
process.stderr.write(
|
|
11875
|
+
parseEmbedModel(JSON.parse(body2), url);
|
|
11876
|
+
} catch (e) {
|
|
11877
|
+
process.stderr.write(
|
|
11878
|
+
`codeindex: pull failed \u2014 response is not a valid model.json (${e instanceof Error ? e.message : String(e)}) (nothing written)
|
|
11879
|
+
`
|
|
11880
|
+
);
|
|
11494
11881
|
process.exitCode = 1;
|
|
11495
11882
|
return;
|
|
11496
11883
|
}
|
|
11497
|
-
|
|
11498
|
-
process.stderr.write(`codeindex: model written to ${
|
|
11884
|
+
writeFileSync4(join15(destDir, "model.json"), body2);
|
|
11885
|
+
process.stderr.write(`codeindex: model written to ${join15(destDir, "model.json")}
|
|
11499
11886
|
`);
|
|
11500
11887
|
} else {
|
|
11501
11888
|
throw new Error("embed needs a subcommand: status | build | pull | serve");
|
|
11502
11889
|
}
|
|
11890
|
+
} else if (cmd === "grammars") {
|
|
11891
|
+
const sub = flags2.positional;
|
|
11892
|
+
const cacheDir = sharedGrammarsCacheDir();
|
|
11893
|
+
if (sub === "status") {
|
|
11894
|
+
const info2 = resolveGrammarsTier();
|
|
11895
|
+
const runtimePresent = info2.dir ? existsSync4(join15(info2.dir, "web-tree-sitter.wasm")) : false;
|
|
11896
|
+
const target = resolveGrammarsPullTarget();
|
|
11897
|
+
const status = {
|
|
11898
|
+
engineVersion: ENGINE_VERSION,
|
|
11899
|
+
tier: info2.tier,
|
|
11900
|
+
dir: info2.dir ?? null,
|
|
11901
|
+
cacheDir,
|
|
11902
|
+
runtimePresent,
|
|
11903
|
+
pullNeeded: !runtimePresent,
|
|
11904
|
+
url: target.url
|
|
11905
|
+
};
|
|
11906
|
+
emit(JSON.stringify(status, null, 2) + "\n", flags2.out);
|
|
11907
|
+
} else if (sub === "pull") {
|
|
11908
|
+
const target = resolveGrammarsPullTarget();
|
|
11909
|
+
let expected;
|
|
11910
|
+
if (target.sha256Url) {
|
|
11911
|
+
try {
|
|
11912
|
+
expected = await fetchExpectedSha256(target.sha256Url);
|
|
11913
|
+
} catch (e) {
|
|
11914
|
+
process.stderr.write(
|
|
11915
|
+
`codeindex: could not fetch checksum (${e instanceof Error ? e.message : String(e)}) \u2014 proceeding unverified
|
|
11916
|
+
`
|
|
11917
|
+
);
|
|
11918
|
+
}
|
|
11919
|
+
}
|
|
11920
|
+
const runtime = join15(cacheDir, "web-tree-sitter.wasm");
|
|
11921
|
+
const markerPath = join15(dirname4(cacheDir), `${ENGINE_VERSION}.sha256`);
|
|
11922
|
+
if (existsSync4(runtime) && expected && existsSync4(markerPath)) {
|
|
11923
|
+
let marker = "";
|
|
11924
|
+
try {
|
|
11925
|
+
marker = readFileSync6(markerPath, "utf8").trim();
|
|
11926
|
+
} catch {
|
|
11927
|
+
}
|
|
11928
|
+
if (marker === expected) {
|
|
11929
|
+
process.stderr.write(`codeindex: grammars already present at ${cacheDir} (up to date)
|
|
11930
|
+
`);
|
|
11931
|
+
return;
|
|
11932
|
+
}
|
|
11933
|
+
}
|
|
11934
|
+
process.stderr.write(`codeindex: fetching grammars from ${target.url} \u2192 ${cacheDir}
|
|
11935
|
+
`);
|
|
11936
|
+
let bytes;
|
|
11937
|
+
try {
|
|
11938
|
+
bytes = await fetchGrammarsTarball(target.url, expected);
|
|
11939
|
+
} catch (e) {
|
|
11940
|
+
process.stderr.write(`codeindex: pull failed \u2014 ${e instanceof Error ? e.message : String(e)} (nothing written)
|
|
11941
|
+
`);
|
|
11942
|
+
process.exitCode = 1;
|
|
11943
|
+
return;
|
|
11944
|
+
}
|
|
11945
|
+
let tmp;
|
|
11946
|
+
try {
|
|
11947
|
+
mkdirSync3(dirname4(cacheDir), { recursive: true });
|
|
11948
|
+
tmp = mkdtempSync(join15(dirname4(cacheDir), ".grammars-tmp-"));
|
|
11949
|
+
extractGrammarsTarball(bytes, tmp);
|
|
11950
|
+
if (!existsSync4(join15(tmp, "web-tree-sitter.wasm"))) {
|
|
11951
|
+
throw new Error("archive is missing web-tree-sitter.wasm");
|
|
11952
|
+
}
|
|
11953
|
+
if (existsSync4(cacheDir)) rmSync2(cacheDir, { recursive: true, force: true });
|
|
11954
|
+
renameSync(tmp, cacheDir);
|
|
11955
|
+
tmp = void 0;
|
|
11956
|
+
if (expected) writeFileSync4(markerPath, expected + "\n");
|
|
11957
|
+
} catch (e) {
|
|
11958
|
+
if (tmp) {
|
|
11959
|
+
try {
|
|
11960
|
+
rmSync2(tmp, { recursive: true, force: true });
|
|
11961
|
+
} catch {
|
|
11962
|
+
}
|
|
11963
|
+
}
|
|
11964
|
+
process.stderr.write(
|
|
11965
|
+
`codeindex: pull failed \u2014 ${e instanceof Error ? e.message : String(e)} (nothing written)
|
|
11966
|
+
`
|
|
11967
|
+
);
|
|
11968
|
+
process.exitCode = 1;
|
|
11969
|
+
return;
|
|
11970
|
+
}
|
|
11971
|
+
process.stderr.write(`codeindex: grammars extracted \u2192 ${cacheDir}
|
|
11972
|
+
`);
|
|
11973
|
+
} else {
|
|
11974
|
+
throw new Error("grammars needs a subcommand: status | pull");
|
|
11975
|
+
}
|
|
11503
11976
|
} else if (cmd === "rules") {
|
|
11504
11977
|
if (!flags2.config) throw new Error("rules needs --config <codeindex.rules.json>");
|
|
11505
11978
|
const rules = parseRules(JSON.parse(readFileSync6(flags2.config, "utf8")));
|
|
11506
|
-
const { graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
|
|
11979
|
+
const { graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11507
11980
|
const violations = checkRules(graph, rules);
|
|
11508
11981
|
const errors = violations.filter((v) => v.severity === "error").length;
|
|
11509
11982
|
emit(JSON.stringify({ errors, warnings: violations.length - errors, violations }, null, 2) + "\n", flags2.out);
|
|
@@ -11524,26 +11997,26 @@ async function runCli(argv) {
|
|
|
11524
11997
|
for (const k of [...churn.keys()].sort()) sorted[k] = churn.get(k);
|
|
11525
11998
|
emit(JSON.stringify({ ok, churn: sorted }, null, 2) + "\n", flags2.out);
|
|
11526
11999
|
} else if (cmd === "repomap") {
|
|
11527
|
-
const { scan: scan2, graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
|
|
12000
|
+
const { scan: scan2, graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11528
12001
|
emit(renderRepoMap(scan2, graph, { budgetTokens: flags2.budgetTokens }), flags2.out);
|
|
11529
12002
|
} else if (cmd === "hotspots") {
|
|
11530
|
-
const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
|
|
12003
|
+
const scan2 = scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11531
12004
|
const { churn, ok } = gitChurn(flags2.repo, { since: flags2.since });
|
|
11532
12005
|
emit(JSON.stringify({ churnOk: ok, hotspots: rankHotspots(scan2, churn) }, null, 2) + "\n", flags2.out);
|
|
11533
12006
|
} else if (cmd === "coupling") {
|
|
11534
12007
|
const { ok, couplings } = changeCoupling(flags2.repo, { since: flags2.since });
|
|
11535
12008
|
emit(JSON.stringify({ ok, couplings }, null, 2) + "\n", flags2.out);
|
|
11536
12009
|
} else if (cmd === "deadcode") {
|
|
11537
|
-
emit(JSON.stringify(findDeadCode(scanRepo(flags2.repo, scanOptions(flags2))), null, 2) + "\n", flags2.out);
|
|
12010
|
+
emit(JSON.stringify(findDeadCode(scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk))), null, 2) + "\n", flags2.out);
|
|
11538
12011
|
} else if (cmd === "complexity") {
|
|
11539
|
-
const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
|
|
12012
|
+
const scan2 = scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11540
12013
|
emit(JSON.stringify(symbolComplexity(scan2, flags2.positional), null, 2) + "\n", flags2.out);
|
|
11541
12014
|
} else if (cmd === "risk") {
|
|
11542
|
-
const scan2 = scanRepo(flags2.repo, scanOptions(flags2));
|
|
12015
|
+
const scan2 = scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11543
12016
|
const { churn, ok } = gitChurn(flags2.repo, { since: flags2.since });
|
|
11544
12017
|
emit(JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn) }, null, 2) + "\n", flags2.out);
|
|
11545
12018
|
} else if (cmd === "mermaid") {
|
|
11546
|
-
const { graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2));
|
|
12019
|
+
const { graph } = buildIndexArtifacts(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
11547
12020
|
emit(renderMermaid(graph, { module: flags2.positional }), flags2.out);
|
|
11548
12021
|
} else if (cmd === "grep") {
|
|
11549
12022
|
if (!flags2.positional) throw new Error("grep needs a pattern: cli.mjs grep <pattern> --repo <dir>");
|
|
@@ -11562,6 +12035,7 @@ ${HELP}`);
|
|
|
11562
12035
|
}
|
|
11563
12036
|
}
|
|
11564
12037
|
export {
|
|
12038
|
+
DEFAULT_GRAMMARS_URL,
|
|
11565
12039
|
DEFAULT_MAX_FILES,
|
|
11566
12040
|
EMBED_VERSION,
|
|
11567
12041
|
ENGINE_VERSION,
|
|
@@ -11572,6 +12046,7 @@ export {
|
|
|
11572
12046
|
applyCentrality,
|
|
11573
12047
|
basicTokenize,
|
|
11574
12048
|
betweennessOf,
|
|
12049
|
+
buildArtifactsFromScan,
|
|
11575
12050
|
buildCallerIndex,
|
|
11576
12051
|
buildEmbeddingIndex,
|
|
11577
12052
|
buildEndpointIndex,
|
|
@@ -11614,14 +12089,19 @@ export {
|
|
|
11614
12089
|
extToLang,
|
|
11615
12090
|
extractAst,
|
|
11616
12091
|
extractCode,
|
|
12092
|
+
extractGrammarsTarball,
|
|
11617
12093
|
extractMarkdown,
|
|
11618
12094
|
extractSymbols,
|
|
12095
|
+
extractTarInto,
|
|
12096
|
+
fetchExpectedSha256,
|
|
12097
|
+
fetchGrammarsTarball,
|
|
11619
12098
|
findDeadCode,
|
|
11620
12099
|
findReferences,
|
|
11621
12100
|
findSymbol,
|
|
11622
12101
|
foldText,
|
|
11623
12102
|
gitChurn,
|
|
11624
12103
|
grammarKeyForExt,
|
|
12104
|
+
grammarKeysForExts,
|
|
11625
12105
|
grammarReady,
|
|
11626
12106
|
grepRepo,
|
|
11627
12107
|
hasEmbedModel,
|
|
@@ -11663,6 +12143,9 @@ export {
|
|
|
11663
12143
|
resolveEmbedEndpoint,
|
|
11664
12144
|
resolveEmbedModelDir,
|
|
11665
12145
|
resolveEmbedPullUrl,
|
|
12146
|
+
resolveGrammarsDir,
|
|
12147
|
+
resolveGrammarsPullTarget,
|
|
12148
|
+
resolveGrammarsTier,
|
|
11666
12149
|
resolveImport,
|
|
11667
12150
|
resolveUniqueSymbol,
|
|
11668
12151
|
riskHotspots,
|
|
@@ -11676,6 +12159,7 @@ export {
|
|
|
11676
12159
|
serializeEmbeddings,
|
|
11677
12160
|
sh,
|
|
11678
12161
|
sha1,
|
|
12162
|
+
sharedGrammarsCacheDir,
|
|
11679
12163
|
shortHash,
|
|
11680
12164
|
slugify,
|
|
11681
12165
|
subtokens,
|