@maxgfr/codeindex 2.17.1 → 2.18.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 +26 -3
- package/docs/MIGRATION.md +107 -0
- package/package.json +1 -1
- package/scripts/engine.d.mts +242 -18
- package/scripts/engine.mjs +1730 -721
- package/src/ast/extract.ts +180 -142
- package/src/coupling.ts +0 -0
- package/src/delta.ts +417 -0
- package/src/derived.ts +17 -0
- package/src/engine-cli.ts +145 -24
- package/src/engine.ts +31 -4
- package/src/extract/code.ts +4 -1
- package/src/graph.ts +0 -0
- package/src/mcp.ts +469 -212
- package/src/pool.ts +271 -0
- package/src/preload.ts +159 -0
- package/src/render/scip.ts +80 -20
- package/src/render/symbols-json.ts +3 -2
- package/src/scan.ts +142 -17
- package/src/traverse.ts +164 -0
- package/src/types.ts +1 -1
- package/src/viz.ts +91 -1
package/scripts/engine.mjs
CHANGED
|
@@ -14,7 +14,7 @@ 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.18.0";
|
|
18
18
|
SCHEMA_VERSION = 4;
|
|
19
19
|
EXTRACTOR_VERSION = 10;
|
|
20
20
|
}
|
|
@@ -606,9 +606,9 @@ function diffFiles(dir, spec) {
|
|
|
606
606
|
}
|
|
607
607
|
}
|
|
608
608
|
const byPath = new Map(out2.map((f) => [f.path, f]));
|
|
609
|
-
const
|
|
610
|
-
if (
|
|
611
|
-
const toks =
|
|
609
|
+
const num2 = sh("git", [...gitArgs(dir), "diff", "-z", "-M", "--numstat", ...rangeArgs(spec)]);
|
|
610
|
+
if (num2.ok) {
|
|
611
|
+
const toks = num2.stdout.split("\0");
|
|
612
612
|
let i2 = 0;
|
|
613
613
|
while (i2 < toks.length) {
|
|
614
614
|
const head = toks[i2++];
|
|
@@ -2841,7 +2841,7 @@ async function Module2(moduleArg = {}) {
|
|
|
2841
2841
|
_fd_close.sig = "ii";
|
|
2842
2842
|
var INT53_MAX = 9007199254740992;
|
|
2843
2843
|
var INT53_MIN = -9007199254740992;
|
|
2844
|
-
var bigintToI53Checked = /* @__PURE__ */ __name((
|
|
2844
|
+
var bigintToI53Checked = /* @__PURE__ */ __name((num2) => num2 < INT53_MIN || num2 > INT53_MAX ? NaN : Number(num2), "bigintToI53Checked");
|
|
2845
2845
|
function _fd_seek(fd, offset, whence, newOffset) {
|
|
2846
2846
|
offset = bigintToI53Checked(offset);
|
|
2847
2847
|
return 70;
|
|
@@ -2859,7 +2859,7 @@ async function Module2(moduleArg = {}) {
|
|
|
2859
2859
|
}
|
|
2860
2860
|
}, "printChar");
|
|
2861
2861
|
var _fd_write = /* @__PURE__ */ __name((fd, iov, iovcnt, pnum) => {
|
|
2862
|
-
var
|
|
2862
|
+
var num2 = 0;
|
|
2863
2863
|
for (var i2 = 0; i2 < iovcnt; i2++) {
|
|
2864
2864
|
var ptr = LE_HEAP_LOAD_U32((iov >> 2) * 4);
|
|
2865
2865
|
var len = LE_HEAP_LOAD_U32((iov + 4 >> 2) * 4);
|
|
@@ -2867,9 +2867,9 @@ async function Module2(moduleArg = {}) {
|
|
|
2867
2867
|
for (var j = 0; j < len; j++) {
|
|
2868
2868
|
printChar(fd, HEAPU8[ptr + j]);
|
|
2869
2869
|
}
|
|
2870
|
-
|
|
2870
|
+
num2 += len;
|
|
2871
2871
|
}
|
|
2872
|
-
LE_HEAP_STORE_U32((pnum >> 2) * 4,
|
|
2872
|
+
LE_HEAP_STORE_U32((pnum >> 2) * 4, num2);
|
|
2873
2873
|
return 0;
|
|
2874
2874
|
}, "_fd_write");
|
|
2875
2875
|
_fd_write.sig = "iippp";
|
|
@@ -5663,65 +5663,30 @@ var init_loader = __esm({
|
|
|
5663
5663
|
});
|
|
5664
5664
|
|
|
5665
5665
|
// src/ast/extract.ts
|
|
5666
|
-
function
|
|
5667
|
-
const
|
|
5668
|
-
const
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
for (let i2 = 0; i2 < node.namedChildCount; i2++) visit(node.namedChild(i2));
|
|
5673
|
-
};
|
|
5674
|
-
visit(root);
|
|
5675
|
-
return [...found].sort().slice(0, MAX_REF_IDENTS);
|
|
5676
|
-
}
|
|
5677
|
-
function firstLine(node) {
|
|
5678
|
-
const nl = node.text.indexOf("\n");
|
|
5679
|
-
return (nl === -1 ? node.text : node.text.slice(0, nl)).trim().slice(0, 200);
|
|
5666
|
+
function firstLine(node, src) {
|
|
5667
|
+
const start2 = node.startIndex;
|
|
5668
|
+
const end = node.endIndex;
|
|
5669
|
+
const nl = src.indexOf("\n", start2);
|
|
5670
|
+
const stop2 = nl === -1 || nl > end ? end : nl;
|
|
5671
|
+
return src.slice(start2, stop2).trim().slice(0, 200);
|
|
5680
5672
|
}
|
|
5681
5673
|
function nameOf(node) {
|
|
5682
5674
|
const named = node.childForFieldName("name");
|
|
5683
5675
|
if (named?.text) return named.text;
|
|
5684
5676
|
let decl = node.childForFieldName("declarator");
|
|
5685
5677
|
while (decl) {
|
|
5686
|
-
if (decl.
|
|
5678
|
+
if (decl.namedChildren.length === 0 && /(^|_)identifier$/.test(decl.type)) return decl.text;
|
|
5687
5679
|
const next = decl.childForFieldName("declarator");
|
|
5688
5680
|
if (!next || next === decl) break;
|
|
5689
5681
|
decl = next;
|
|
5690
5682
|
}
|
|
5691
|
-
for (
|
|
5692
|
-
const c2 = node.namedChild(i2);
|
|
5683
|
+
for (const c2 of node.namedChildren) {
|
|
5693
5684
|
if (/(^|_)(identifier|name|constant)$/.test(c2.type)) return c2.text;
|
|
5694
5685
|
}
|
|
5695
5686
|
return void 0;
|
|
5696
5687
|
}
|
|
5697
|
-
function collectImports(root, spec) {
|
|
5698
|
-
if (!spec.imports) return [];
|
|
5699
|
-
const out2 = [];
|
|
5700
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5701
|
-
const add = (s) => {
|
|
5702
|
-
const v = s.trim();
|
|
5703
|
-
if (v && !seen.has(v)) {
|
|
5704
|
-
seen.add(v);
|
|
5705
|
-
out2.push({ kind: "import", spec: v });
|
|
5706
|
-
}
|
|
5707
|
-
};
|
|
5708
|
-
const visit = (node) => {
|
|
5709
|
-
const how = spec.imports[node.type];
|
|
5710
|
-
if (how === "string") {
|
|
5711
|
-
const str2 = findFirst(node, (n) => /string/.test(n.type));
|
|
5712
|
-
if (str2) add(str2.text.replace(/^['"]|['"]$/g, ""));
|
|
5713
|
-
} else if (how === "path") {
|
|
5714
|
-
const name2 = node.childForFieldName("name") ?? node.childForFieldName("module_name");
|
|
5715
|
-
add((name2 ?? node).text.replace(/^(import|from)\s+/, "").split(/\s+/)[0]);
|
|
5716
|
-
}
|
|
5717
|
-
for (let i2 = 0; i2 < node.namedChildCount; i2++) visit(node.namedChild(i2));
|
|
5718
|
-
};
|
|
5719
|
-
visit(root);
|
|
5720
|
-
return out2;
|
|
5721
|
-
}
|
|
5722
5688
|
function findFirst(node, pred) {
|
|
5723
|
-
for (
|
|
5724
|
-
const c2 = node.namedChild(i2);
|
|
5689
|
+
for (const c2 of node.namedChildren) {
|
|
5725
5690
|
if (pred(c2)) return c2;
|
|
5726
5691
|
const deep = findFirst(c2, pred);
|
|
5727
5692
|
if (deep) return deep;
|
|
@@ -5730,79 +5695,104 @@ function findFirst(node, pred) {
|
|
|
5730
5695
|
}
|
|
5731
5696
|
function readName(node) {
|
|
5732
5697
|
if (!node) return void 0;
|
|
5733
|
-
|
|
5698
|
+
const kids = node.namedChildren;
|
|
5699
|
+
if (kids.length === 0) return IDENT_LEAF.test(node.type) ? node.text : void 0;
|
|
5734
5700
|
const seg = node.childForFieldName("name") ?? node.childForFieldName("property") ?? node.childForFieldName("attribute") ?? node.childForFieldName("field") ?? // Callee wrappers that point at the real callee via a `function` field:
|
|
5735
5701
|
// scala's generic_function (`foo[Int](x)`) and a curried/chained
|
|
5736
5702
|
// call_expression callee (`curried(a)(b)`) — descend to the inner name
|
|
5737
5703
|
// instead of tripping over type_arguments/arguments as the last child.
|
|
5738
5704
|
node.childForFieldName("function");
|
|
5739
5705
|
if (seg) return readName(seg);
|
|
5740
|
-
const last =
|
|
5706
|
+
const last = kids[kids.length - 1];
|
|
5741
5707
|
return last && last !== node ? readName(last) : void 0;
|
|
5742
5708
|
}
|
|
5743
5709
|
function readReceiver(node) {
|
|
5744
|
-
if (!node || node.
|
|
5710
|
+
if (!node || node.namedChildren.length === 0) return void 0;
|
|
5745
5711
|
const obj = node.childForFieldName("object") ?? node.childForFieldName("operand") ?? node.childForFieldName("value") ?? node.childForFieldName("path") ?? node.childForFieldName("expression") ?? node.childForFieldName("argument") ?? node.childForFieldName("receiver") ?? node.childForFieldName("table");
|
|
5746
5712
|
const name2 = obj ? readName(obj) : void 0;
|
|
5747
5713
|
return name2 && /^[A-Za-z_]\w*$/.test(name2) ? name2 : void 0;
|
|
5748
5714
|
}
|
|
5749
|
-
function
|
|
5750
|
-
|
|
5751
|
-
const
|
|
5752
|
-
const
|
|
5753
|
-
const
|
|
5715
|
+
function collectAll(root, spec, defNames, maxCalls, wantImports) {
|
|
5716
|
+
const identsFound = /* @__PURE__ */ new Set();
|
|
5717
|
+
const wantCalls = spec.calls !== void 0;
|
|
5718
|
+
const calls = [];
|
|
5719
|
+
const callSeen = /* @__PURE__ */ new Set();
|
|
5720
|
+
const addCall = (name2, node, receiver) => {
|
|
5754
5721
|
if (!name2 || name2.length < 2 || !/^[A-Za-z_]\w*$/.test(name2)) return;
|
|
5755
5722
|
const line = node.startPosition.row + 1;
|
|
5756
5723
|
const key = `${name2} ${line}`;
|
|
5757
|
-
if (
|
|
5758
|
-
|
|
5759
|
-
|
|
5724
|
+
if (callSeen.has(key)) return;
|
|
5725
|
+
callSeen.add(key);
|
|
5726
|
+
calls.push(receiver ? { name: name2, line, receiver } : { name: name2, line });
|
|
5760
5727
|
};
|
|
5761
|
-
const
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
if (IDENT_LEAF.test(c2.type)) t = c2;
|
|
5773
|
-
}
|
|
5774
|
-
add(readName(t), node, readReceiver(t ?? null));
|
|
5775
|
-
}
|
|
5776
|
-
for (let i2 = 0; i2 < node.namedChildCount; i2++) visit(node.namedChild(i2));
|
|
5728
|
+
const wantNames = spec.imports?.import_statement !== void 0;
|
|
5729
|
+
const namesFound = /* @__PURE__ */ new Set();
|
|
5730
|
+
const wantRefs = wantImports && spec.imports !== void 0;
|
|
5731
|
+
const refs = [];
|
|
5732
|
+
const refSeen = /* @__PURE__ */ new Set();
|
|
5733
|
+
const addRef = (s) => {
|
|
5734
|
+
const v = s.trim();
|
|
5735
|
+
if (v && !refSeen.has(v)) {
|
|
5736
|
+
refSeen.add(v);
|
|
5737
|
+
refs.push({ kind: "import", spec: v });
|
|
5738
|
+
}
|
|
5777
5739
|
};
|
|
5778
|
-
visit(root);
|
|
5779
|
-
out2.sort((a, b) => byStr(a.name, b.name) || a.line - b.line);
|
|
5780
|
-
return out2.slice(0, maxCalls);
|
|
5781
|
-
}
|
|
5782
|
-
function collectImportedNames(root, spec) {
|
|
5783
|
-
if (!spec.imports?.import_statement) return [];
|
|
5784
|
-
const found = /* @__PURE__ */ new Set();
|
|
5785
5740
|
const visit = (node) => {
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5741
|
+
const type = node.type;
|
|
5742
|
+
const kids = node.namedChildren;
|
|
5743
|
+
if (kids.length === 0 && REF_IDENT_TYPE.test(type)) {
|
|
5744
|
+
const text = node.text;
|
|
5745
|
+
if (REF_IDENT_TEXT.test(text) && !defNames.has(text)) identsFound.add(text);
|
|
5746
|
+
}
|
|
5747
|
+
if (wantCalls) {
|
|
5748
|
+
const how = spec.calls[type];
|
|
5749
|
+
if (how === "function") {
|
|
5750
|
+
const callee = node.childForFieldName("function") ?? node.childForFieldName("callee") ?? node.childForFieldName("method") ?? node.childForFieldName("name");
|
|
5751
|
+
addCall(readName(callee), node, readReceiver(callee) ?? readReceiver(node));
|
|
5752
|
+
} else if (how === "member") {
|
|
5753
|
+
addCall(readName(node.childForFieldName("name")), node, readReceiver(node));
|
|
5754
|
+
} else if (how === "constructor") {
|
|
5755
|
+
let t = node.childForFieldName("constructor") ?? node.childForFieldName("type") ?? node.childForFieldName("name");
|
|
5756
|
+
for (let i2 = 0; !t && i2 < kids.length; i2++) {
|
|
5757
|
+
const c2 = kids[i2];
|
|
5758
|
+
if (IDENT_LEAF.test(c2.type)) t = c2;
|
|
5759
|
+
}
|
|
5760
|
+
addCall(readName(t), node, readReceiver(t ?? null));
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
if (wantNames && type === "import_statement") {
|
|
5764
|
+
for (const clause of kids) {
|
|
5789
5765
|
if (clause.type !== "import_clause") continue;
|
|
5790
|
-
for (
|
|
5791
|
-
const named = clause.namedChild(j);
|
|
5766
|
+
for (const named of clause.namedChildren) {
|
|
5792
5767
|
if (named.type !== "named_imports") continue;
|
|
5793
|
-
for (
|
|
5794
|
-
const specifier = named.namedChild(k);
|
|
5768
|
+
for (const specifier of named.namedChildren) {
|
|
5795
5769
|
if (specifier.type !== "import_specifier") continue;
|
|
5796
|
-
const nm = specifier.childForFieldName("name") ?? specifier.
|
|
5797
|
-
if (nm?.text)
|
|
5770
|
+
const nm = specifier.childForFieldName("name") ?? specifier.namedChildren[0];
|
|
5771
|
+
if (nm?.text) namesFound.add(nm.text);
|
|
5798
5772
|
}
|
|
5799
5773
|
}
|
|
5800
5774
|
}
|
|
5801
5775
|
}
|
|
5802
|
-
|
|
5776
|
+
if (wantRefs) {
|
|
5777
|
+
const how = spec.imports[type];
|
|
5778
|
+
if (how === "string") {
|
|
5779
|
+
const str2 = findFirst(node, (n) => /string/.test(n.type));
|
|
5780
|
+
if (str2) addRef(str2.text.replace(/^['"]|['"]$/g, ""));
|
|
5781
|
+
} else if (how === "path") {
|
|
5782
|
+
const name2 = node.childForFieldName("name") ?? node.childForFieldName("module_name");
|
|
5783
|
+
addRef((name2 ?? node).text.replace(/^(import|from)\s+/, "").split(/\s+/)[0]);
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
for (const c2 of kids) visit(c2);
|
|
5803
5787
|
};
|
|
5804
5788
|
visit(root);
|
|
5805
|
-
|
|
5789
|
+
calls.sort((a, b) => byStr(a.name, b.name) || a.line - b.line);
|
|
5790
|
+
return {
|
|
5791
|
+
refs,
|
|
5792
|
+
idents: [...identsFound].sort().slice(0, MAX_REF_IDENTS),
|
|
5793
|
+
calls: calls.slice(0, maxCalls),
|
|
5794
|
+
importedNames: [...namesFound].sort(byStr).slice(0, MAX_IMPORTED_NAMES)
|
|
5795
|
+
};
|
|
5806
5796
|
}
|
|
5807
5797
|
function extractAst(rel, ext, content, opts = {}) {
|
|
5808
5798
|
const key = grammarKeyForExt(ext);
|
|
@@ -5822,20 +5812,17 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
5822
5812
|
const walk2 = (node, parent, exported) => {
|
|
5823
5813
|
const nowExported = exported || node.type === "export_statement";
|
|
5824
5814
|
if (node.type === "export_statement") {
|
|
5825
|
-
for (
|
|
5826
|
-
const c2 = node.namedChild(i2);
|
|
5815
|
+
for (const c2 of node.namedChildren) {
|
|
5827
5816
|
if (c2.type === "identifier") exportedNames.add(c2.text);
|
|
5828
5817
|
else if (c2.type === "export_clause") {
|
|
5829
|
-
for (
|
|
5830
|
-
const
|
|
5831
|
-
const nm = spec2.childForFieldName("name") ?? spec2.namedChild(0);
|
|
5818
|
+
for (const spec2 of c2.namedChildren) {
|
|
5819
|
+
const nm = spec2.childForFieldName("name") ?? spec2.namedChildren[0];
|
|
5832
5820
|
if (nm?.text) exportedNames.add(nm.text);
|
|
5833
5821
|
}
|
|
5834
5822
|
}
|
|
5835
5823
|
}
|
|
5836
5824
|
if (stem && node.children.some((c2) => c2.type === "default")) {
|
|
5837
|
-
for (
|
|
5838
|
-
const c2 = node.namedChild(i2);
|
|
5825
|
+
for (const c2 of node.namedChildren) {
|
|
5839
5826
|
const fnLike = ANON_DEFAULT_FN.has(c2.type);
|
|
5840
5827
|
const classLike = ANON_DEFAULT_CLASS.has(c2.type);
|
|
5841
5828
|
if ((fnLike || classLike) && !c2.childForFieldName("name")) {
|
|
@@ -5845,7 +5832,7 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
5845
5832
|
file: rel,
|
|
5846
5833
|
line: node.startPosition.row + 1,
|
|
5847
5834
|
endLine: node.endPosition.row + 1,
|
|
5848
|
-
signature: firstLine(node),
|
|
5835
|
+
signature: firstLine(node, content),
|
|
5849
5836
|
exported: true,
|
|
5850
5837
|
lang: spec.lang
|
|
5851
5838
|
});
|
|
@@ -5855,14 +5842,13 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
5855
5842
|
}
|
|
5856
5843
|
}
|
|
5857
5844
|
if (spec.assignments && node.type === "expression_statement") {
|
|
5858
|
-
const expr = node.
|
|
5845
|
+
const expr = node.namedChildren[0];
|
|
5859
5846
|
if (expr?.type === "assignment_expression") {
|
|
5860
5847
|
const left = expr.childForFieldName("left");
|
|
5861
5848
|
const right = expr.childForFieldName("right");
|
|
5862
5849
|
if (left?.type === "member_expression" && left.text === "module.exports" && right) {
|
|
5863
5850
|
if (right.type === "object") {
|
|
5864
|
-
for (
|
|
5865
|
-
const p = right.namedChild(i2);
|
|
5851
|
+
for (const p of right.namedChildren) {
|
|
5866
5852
|
if (p.type === "shorthand_property_identifier") exportedNames.add(p.text);
|
|
5867
5853
|
else if (p.type === "pair") {
|
|
5868
5854
|
const k = p.childForFieldName("key");
|
|
@@ -5900,7 +5886,7 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
5900
5886
|
line: expr.startPosition.row + 1,
|
|
5901
5887
|
endLine: expr.endPosition.row + 1,
|
|
5902
5888
|
...parent ? { parent } : {},
|
|
5903
|
-
signature: firstLine(expr),
|
|
5889
|
+
signature: firstLine(expr, content),
|
|
5904
5890
|
exported: nowExported || exportedAssign,
|
|
5905
5891
|
lang: spec.lang
|
|
5906
5892
|
});
|
|
@@ -5920,7 +5906,7 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
5920
5906
|
line: expr.startPosition.row + 1,
|
|
5921
5907
|
endLine: expr.endPosition.row + 1,
|
|
5922
5908
|
...parent ? { parent } : {},
|
|
5923
|
-
signature: firstLine(expr),
|
|
5909
|
+
signature: firstLine(expr, content),
|
|
5924
5910
|
exported: true,
|
|
5925
5911
|
lang: spec.lang
|
|
5926
5912
|
});
|
|
@@ -5934,11 +5920,14 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
5934
5920
|
if (spec.assignments && node.type === "assignment_statement") {
|
|
5935
5921
|
const vars = node.children.find((c2) => c2.type === "variable_list");
|
|
5936
5922
|
const vals = node.children.find((c2) => c2.type === "expression_list");
|
|
5937
|
-
const
|
|
5923
|
+
const targets = vars?.namedChildren ?? [];
|
|
5924
|
+
const values = vals?.namedChildren ?? [];
|
|
5925
|
+
const pairs = Math.min(targets.length, values.length);
|
|
5938
5926
|
for (let i2 = 0; i2 < pairs; i2++) {
|
|
5939
|
-
const target =
|
|
5940
|
-
const value =
|
|
5927
|
+
const target = targets[i2];
|
|
5928
|
+
const value = values[i2];
|
|
5941
5929
|
if (value.type !== "function_definition" || !/^[\w.:]+$/.test(target.text)) continue;
|
|
5930
|
+
const line = firstLine(node, content);
|
|
5942
5931
|
symbols.push({
|
|
5943
5932
|
name: target.text,
|
|
5944
5933
|
kind: "function",
|
|
@@ -5946,8 +5935,8 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
5946
5935
|
line: node.startPosition.row + 1,
|
|
5947
5936
|
endLine: node.endPosition.row + 1,
|
|
5948
5937
|
...parent ? { parent } : {},
|
|
5949
|
-
signature:
|
|
5950
|
-
exported: nowExported || spec.exported(
|
|
5938
|
+
signature: line,
|
|
5939
|
+
exported: nowExported || spec.exported(line, target.text),
|
|
5951
5940
|
lang: spec.lang
|
|
5952
5941
|
});
|
|
5953
5942
|
}
|
|
@@ -5957,7 +5946,7 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
5957
5946
|
if (kind) {
|
|
5958
5947
|
const name2 = nameOf(node);
|
|
5959
5948
|
if (name2) {
|
|
5960
|
-
const line = firstLine(node);
|
|
5949
|
+
const line = firstLine(node, content);
|
|
5961
5950
|
symbols.push({
|
|
5962
5951
|
name: name2,
|
|
5963
5952
|
kind,
|
|
@@ -5969,31 +5958,33 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
5969
5958
|
exported: nowExported || spec.exported(line, name2),
|
|
5970
5959
|
lang: spec.lang
|
|
5971
5960
|
});
|
|
5972
|
-
for (
|
|
5973
|
-
walkBody(node.namedChild(i2), name2, nowExported);
|
|
5974
|
-
}
|
|
5961
|
+
for (const c2 of node.namedChildren) walkBody(c2, name2, nowExported);
|
|
5975
5962
|
return;
|
|
5976
5963
|
}
|
|
5977
5964
|
}
|
|
5978
5965
|
if (spec.containers.has(node.type)) {
|
|
5979
|
-
for (
|
|
5966
|
+
for (const c2 of node.namedChildren) walk2(c2, parent, nowExported);
|
|
5980
5967
|
}
|
|
5981
5968
|
};
|
|
5982
5969
|
const walkBody = (node, parent, exported) => {
|
|
5983
5970
|
if (spec.containers.has(node.type)) {
|
|
5984
|
-
for (
|
|
5971
|
+
for (const c2 of node.namedChildren) walk2(c2, parent, exported);
|
|
5985
5972
|
}
|
|
5986
5973
|
};
|
|
5987
5974
|
walk2(root, void 0, false);
|
|
5988
5975
|
if (exportedNames.size) {
|
|
5989
5976
|
for (const s of symbols) if (!s.exported && exportedNames.has(s.name)) s.exported = true;
|
|
5990
5977
|
}
|
|
5991
|
-
const
|
|
5992
|
-
const
|
|
5993
|
-
|
|
5994
|
-
|
|
5978
|
+
const wantImports = opts.imports !== false;
|
|
5979
|
+
const { refs, idents, calls, importedNames } = collectAll(
|
|
5980
|
+
root,
|
|
5981
|
+
spec,
|
|
5982
|
+
new Set(symbols.map((s) => s.name)),
|
|
5983
|
+
opts.maxCalls ?? MAX_CALLS,
|
|
5984
|
+
wantImports
|
|
5985
|
+
);
|
|
5995
5986
|
let pkg;
|
|
5996
|
-
if (spec.lang === "java") {
|
|
5987
|
+
if (wantImports && spec.lang === "java") {
|
|
5997
5988
|
const p = findFirst(root, (n) => n.type === "package_declaration");
|
|
5998
5989
|
if (p) pkg = p.text.replace(/^package\s+/, "").replace(/;.*$/, "").trim();
|
|
5999
5990
|
}
|
|
@@ -6004,7 +5995,7 @@ function extractAst(rel, ext, content, opts = {}) {
|
|
|
6004
5995
|
tree?.delete();
|
|
6005
5996
|
}
|
|
6006
5997
|
}
|
|
6007
|
-
var MAX_REF_IDENTS, MAX_CALLS, MAX_IMPORTED_NAMES, ANON_DEFAULT_FN, ANON_DEFAULT_CLASS, byPublicKeyword, byNotPrivate, byNotLocal, byPub, byCapital, byPyConvention, always, neverExport, TS_SPEC, SPECS, IDENT_LEAF;
|
|
5998
|
+
var MAX_REF_IDENTS, MAX_CALLS, MAX_IMPORTED_NAMES, ANON_DEFAULT_FN, ANON_DEFAULT_CLASS, REF_IDENT_TYPE, REF_IDENT_TEXT, byPublicKeyword, byNotPrivate, byNotLocal, byPub, byCapital, byPyConvention, always, neverExport, TS_SPEC, SPECS, IDENT_LEAF;
|
|
6008
5999
|
var init_extract = __esm({
|
|
6009
6000
|
"src/ast/extract.ts"() {
|
|
6010
6001
|
"use strict";
|
|
@@ -6022,6 +6013,8 @@ var init_extract = __esm({
|
|
|
6022
6013
|
"arrow_function"
|
|
6023
6014
|
]);
|
|
6024
6015
|
ANON_DEFAULT_CLASS = /* @__PURE__ */ new Set(["class", "class_declaration", "abstract_class_declaration"]);
|
|
6016
|
+
REF_IDENT_TYPE = /identifier|constant|(^|_)name$/;
|
|
6017
|
+
REF_IDENT_TEXT = /^[A-Za-z_]\w{4,}$/;
|
|
6025
6018
|
byPublicKeyword = (line) => /\b(public|internal)\b/.test(line);
|
|
6026
6019
|
byNotPrivate = (line) => !/\b(private|protected)\b/.test(line);
|
|
6027
6020
|
byNotLocal = (line) => !/^local\b/.test(line);
|
|
@@ -6464,7 +6457,7 @@ function collectCallsRegex(content, symbols = [], maxCalls = 512) {
|
|
|
6464
6457
|
return [...out2.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : a.line - b.line);
|
|
6465
6458
|
}
|
|
6466
6459
|
function extractCode(rel, ext, content, opts = {}) {
|
|
6467
|
-
const ast = extractAst(rel, ext, content, { maxCalls: opts.maxCallsPerFile });
|
|
6460
|
+
const ast = extractAst(rel, ext, content, { maxCalls: opts.maxCallsPerFile, imports: false });
|
|
6468
6461
|
const symbols = (ast ? ast.symbols : extractSymbols(rel, ext, content)).slice(0, 400);
|
|
6469
6462
|
const known = new Set(symbols.map((s) => s.name));
|
|
6470
6463
|
const reexports = extractReexports(rel, content, symbols).filter((s) => !known.has(s.name));
|
|
@@ -6545,7 +6538,35 @@ function countLines(s) {
|
|
|
6545
6538
|
for (let i2 = 0; i2 < s.length; i2++) if (s.charCodeAt(i2) === 10) n++;
|
|
6546
6539
|
return n;
|
|
6547
6540
|
}
|
|
6548
|
-
function
|
|
6541
|
+
function buildCodeRecord(rel, ext, size, content, hash, lang, opts = {}) {
|
|
6542
|
+
const record = {
|
|
6543
|
+
rel,
|
|
6544
|
+
ext,
|
|
6545
|
+
size,
|
|
6546
|
+
lines: countLines(content),
|
|
6547
|
+
hash,
|
|
6548
|
+
kind: "code",
|
|
6549
|
+
lang,
|
|
6550
|
+
headings: [],
|
|
6551
|
+
symbols: [],
|
|
6552
|
+
refs: []
|
|
6553
|
+
};
|
|
6554
|
+
if (content) {
|
|
6555
|
+
const code = extractCode(rel, ext, content, { maxCallsPerFile: opts.maxCallsPerFile });
|
|
6556
|
+
record.title = basename(rel);
|
|
6557
|
+
record.summary = code.summary;
|
|
6558
|
+
record.symbols = code.symbols;
|
|
6559
|
+
record.refs = code.refs;
|
|
6560
|
+
record.pkg = code.pkg;
|
|
6561
|
+
record.idents = code.idents;
|
|
6562
|
+
record.calls = code.calls;
|
|
6563
|
+
record.importedNames = code.importedNames;
|
|
6564
|
+
} else {
|
|
6565
|
+
record.title = basename(rel);
|
|
6566
|
+
}
|
|
6567
|
+
return record;
|
|
6568
|
+
}
|
|
6569
|
+
function* keptFiles(root, opts) {
|
|
6549
6570
|
const scoped = opts.scope ? [...opts.include ?? [], `${opts.scope.replace(/\/+$/, "")}/**`] : opts.include;
|
|
6550
6571
|
const include = compileGlobs(scoped);
|
|
6551
6572
|
const exclude = compileGlobs(opts.exclude);
|
|
@@ -6556,6 +6577,34 @@ function scanRepo(root, opts = {}) {
|
|
|
6556
6577
|
ignoreDirs: opts.ignoreDirs
|
|
6557
6578
|
});
|
|
6558
6579
|
const outPrefix = opts.out ? opts.out.replace(/\/+$/, "") + "/" : null;
|
|
6580
|
+
for (const f of walked) {
|
|
6581
|
+
if (outPrefix && (f.abs === opts.out || f.abs.startsWith(outPrefix))) continue;
|
|
6582
|
+
if (include && !include(f.rel)) continue;
|
|
6583
|
+
if (exclude && exclude(f.rel)) continue;
|
|
6584
|
+
yield { f, kind: classify(f.rel, f.ext), lang: extToLang(f.ext) };
|
|
6585
|
+
}
|
|
6586
|
+
return { capped, excluded };
|
|
6587
|
+
}
|
|
6588
|
+
function keptCodeFiles(root, opts = {}) {
|
|
6589
|
+
const out2 = [];
|
|
6590
|
+
const it = keptFiles(root, opts);
|
|
6591
|
+
for (let step = it.next(); !step.done; step = it.next()) {
|
|
6592
|
+
if (step.value.kind === "code") out2.push({ f: step.value.f, lang: step.value.lang });
|
|
6593
|
+
}
|
|
6594
|
+
return out2;
|
|
6595
|
+
}
|
|
6596
|
+
function scanSummary(root, opts = {}) {
|
|
6597
|
+
const languages = {};
|
|
6598
|
+
let fileCount = 0;
|
|
6599
|
+
const it = keptFiles(root, opts);
|
|
6600
|
+
let step = it.next();
|
|
6601
|
+
for (; !step.done; step = it.next()) {
|
|
6602
|
+
languages[step.value.lang] = (languages[step.value.lang] ?? 0) + 1;
|
|
6603
|
+
fileCount++;
|
|
6604
|
+
}
|
|
6605
|
+
return { root, commit: headCommit(root), fileCount, languages, capped: step.value.capped, excluded: step.value.excluded };
|
|
6606
|
+
}
|
|
6607
|
+
function scanRepo(root, opts = {}) {
|
|
6559
6608
|
const files = [];
|
|
6560
6609
|
const languages = {};
|
|
6561
6610
|
const docText = /* @__PURE__ */ new Map();
|
|
@@ -6563,12 +6612,10 @@ function scanRepo(root, opts = {}) {
|
|
|
6563
6612
|
const cache = opts.cache;
|
|
6564
6613
|
let allReused = cache !== void 0;
|
|
6565
6614
|
let cacheDirty = cache === void 0;
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
const kind = classify(f.rel, f.ext);
|
|
6571
|
-
const lang = extToLang(f.ext);
|
|
6615
|
+
const it = keptFiles(root, opts);
|
|
6616
|
+
let step = it.next();
|
|
6617
|
+
for (; !step.done; step = it.next()) {
|
|
6618
|
+
const { f, kind, lang } = step.value;
|
|
6572
6619
|
languages[lang] = (languages[lang] ?? 0) + 1;
|
|
6573
6620
|
mtimes.set(f.rel, f.mtimeMs);
|
|
6574
6621
|
const cached = opts.cache?.get(f.rel);
|
|
@@ -6576,8 +6623,10 @@ function scanRepo(root, opts = {}) {
|
|
|
6576
6623
|
files.push(cached.record);
|
|
6577
6624
|
continue;
|
|
6578
6625
|
}
|
|
6579
|
-
const
|
|
6580
|
-
const
|
|
6626
|
+
const pre = opts.extracted?.get(f.rel);
|
|
6627
|
+
const preUsable = pre && pre.size === f.size && pre.mtimeMs === f.mtimeMs ? pre : void 0;
|
|
6628
|
+
const content = preUsable ? void 0 : readText(f.abs);
|
|
6629
|
+
const hash = preUsable ? preUsable.record.hash : sha1(content);
|
|
6581
6630
|
if (cached && cached.hash === hash) {
|
|
6582
6631
|
files.push(cached.record);
|
|
6583
6632
|
if (kind === "doc" && content) docText.set(f.rel, content);
|
|
@@ -6586,6 +6635,10 @@ function scanRepo(root, opts = {}) {
|
|
|
6586
6635
|
}
|
|
6587
6636
|
allReused = false;
|
|
6588
6637
|
cacheDirty = true;
|
|
6638
|
+
if (preUsable) {
|
|
6639
|
+
files.push(preUsable.record);
|
|
6640
|
+
continue;
|
|
6641
|
+
}
|
|
6589
6642
|
const record = {
|
|
6590
6643
|
rel: f.rel,
|
|
6591
6644
|
ext: f.ext,
|
|
@@ -6638,8 +6691,8 @@ function scanRepo(root, opts = {}) {
|
|
|
6638
6691
|
languages,
|
|
6639
6692
|
docText,
|
|
6640
6693
|
mtimes,
|
|
6641
|
-
capped,
|
|
6642
|
-
excluded,
|
|
6694
|
+
capped: step.value.capped,
|
|
6695
|
+
excluded: step.value.excluded,
|
|
6643
6696
|
contentUnchanged: allReused,
|
|
6644
6697
|
cacheDirty
|
|
6645
6698
|
};
|
|
@@ -6659,9 +6712,79 @@ var init_scan = __esm({
|
|
|
6659
6712
|
}
|
|
6660
6713
|
});
|
|
6661
6714
|
|
|
6715
|
+
// src/preload.ts
|
|
6716
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
6717
|
+
import { join as join3 } from "path";
|
|
6718
|
+
function toCacheMap(scan2) {
|
|
6719
|
+
const m = /* @__PURE__ */ new Map();
|
|
6720
|
+
for (const f of scan2.files) m.set(f.rel, { hash: f.hash, record: f, size: f.size, mtimeMs: scan2.mtimes.get(f.rel) });
|
|
6721
|
+
return m;
|
|
6722
|
+
}
|
|
6723
|
+
function readPersistedIndex(repo, indexDir = INDEX_DIR) {
|
|
6724
|
+
let parsed;
|
|
6725
|
+
try {
|
|
6726
|
+
parsed = JSON.parse(readFileSync3(join3(repo, indexDir, "cache.json"), "utf8"));
|
|
6727
|
+
} catch {
|
|
6728
|
+
return void 0;
|
|
6729
|
+
}
|
|
6730
|
+
if (!parsed || parsed.schemaVersion !== SCHEMA_VERSION || parsed.extractorVersion !== EXTRACTOR_VERSION || !parsed.files) {
|
|
6731
|
+
return void 0;
|
|
6732
|
+
}
|
|
6733
|
+
return {
|
|
6734
|
+
cacheMap: new Map(Object.entries(parsed.files)),
|
|
6735
|
+
meta: {
|
|
6736
|
+
engineVersion: parsed.engineVersion,
|
|
6737
|
+
commit: parsed.commit,
|
|
6738
|
+
graphSha1: parsed.graphSha1,
|
|
6739
|
+
symbolsSha1: parsed.symbolsSha1
|
|
6740
|
+
}
|
|
6741
|
+
};
|
|
6742
|
+
}
|
|
6743
|
+
function preloadArtifacts(repo, scan2, meta, indexDir = INDEX_DIR) {
|
|
6744
|
+
if (!scan2.contentUnchanged || meta.engineVersion !== ENGINE_VERSION || meta.commit !== scan2.commit || meta.graphSha1 === void 0 || meta.symbolsSha1 === void 0) {
|
|
6745
|
+
return void 0;
|
|
6746
|
+
}
|
|
6747
|
+
const dir = join3(repo, indexDir);
|
|
6748
|
+
let graphBytes;
|
|
6749
|
+
let symbolsBytes;
|
|
6750
|
+
try {
|
|
6751
|
+
graphBytes = readFileSync3(join3(dir, "graph.json"));
|
|
6752
|
+
symbolsBytes = readFileSync3(join3(dir, "symbols.json"));
|
|
6753
|
+
} catch {
|
|
6754
|
+
return void 0;
|
|
6755
|
+
}
|
|
6756
|
+
if (sha1(graphBytes) !== meta.graphSha1 || sha1(symbolsBytes) !== meta.symbolsSha1) {
|
|
6757
|
+
return void 0;
|
|
6758
|
+
}
|
|
6759
|
+
try {
|
|
6760
|
+
const graph = JSON.parse(graphBytes.toString("utf8"));
|
|
6761
|
+
const symbols = JSON.parse(symbolsBytes.toString("utf8"));
|
|
6762
|
+
if (graph.schemaVersion !== SCHEMA_VERSION || symbols.schemaVersion !== SCHEMA_VERSION) return void 0;
|
|
6763
|
+
return { scan: scan2, graph, symbols };
|
|
6764
|
+
} catch {
|
|
6765
|
+
return void 0;
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
function preloadSession(repo, opts, indexDir = INDEX_DIR) {
|
|
6769
|
+
const persisted = readPersistedIndex(repo, indexDir);
|
|
6770
|
+
if (!persisted) return void 0;
|
|
6771
|
+
const scan2 = scanRepo(repo, { ...opts, cache: persisted.cacheMap });
|
|
6772
|
+
return { scan: scan2, cacheMap: toCacheMap(scan2), arts: preloadArtifacts(repo, scan2, persisted.meta, indexDir) };
|
|
6773
|
+
}
|
|
6774
|
+
var INDEX_DIR;
|
|
6775
|
+
var init_preload = __esm({
|
|
6776
|
+
"src/preload.ts"() {
|
|
6777
|
+
"use strict";
|
|
6778
|
+
init_types();
|
|
6779
|
+
init_scan();
|
|
6780
|
+
init_hash();
|
|
6781
|
+
INDEX_DIR = ".codeindex";
|
|
6782
|
+
}
|
|
6783
|
+
});
|
|
6784
|
+
|
|
6662
6785
|
// src/resolve.ts
|
|
6663
6786
|
import { posix } from "path";
|
|
6664
|
-
import { join as
|
|
6787
|
+
import { join as join6 } from "path";
|
|
6665
6788
|
function distToSrcCandidates(target) {
|
|
6666
6789
|
const segs = norm(target).split("/").filter((s) => s !== ".");
|
|
6667
6790
|
const out2 = [];
|
|
@@ -6748,7 +6871,7 @@ function resolveExtends(fileSet, fromDir, ext) {
|
|
|
6748
6871
|
function readTsConfig(root, fileSet, rel, warnings, seen) {
|
|
6749
6872
|
if (seen.has(rel)) return void 0;
|
|
6750
6873
|
seen.add(rel);
|
|
6751
|
-
const cfg = tolerantJsonParse(readText(
|
|
6874
|
+
const cfg = tolerantJsonParse(readText(join6(root, rel)));
|
|
6752
6875
|
if (cfg === void 0) {
|
|
6753
6876
|
warnings.push(`unparseable ${rel} \u2014 its path aliases were ignored`);
|
|
6754
6877
|
return void 0;
|
|
@@ -6877,7 +7000,7 @@ function buildResolveContext(scan2) {
|
|
|
6877
7000
|
const goModules = [];
|
|
6878
7001
|
for (const rel of fileSet) {
|
|
6879
7002
|
if (rel !== "go.mod" && !rel.endsWith("/go.mod")) continue;
|
|
6880
|
-
const text = readText(
|
|
7003
|
+
const text = readText(join6(scan2.root, rel));
|
|
6881
7004
|
const m = /^\s*module\s+(\S+)/m.exec(text);
|
|
6882
7005
|
if (!m) continue;
|
|
6883
7006
|
const dir = rel.includes("/") ? posix.dirname(rel) : "";
|
|
@@ -6887,7 +7010,7 @@ function buildResolveContext(scan2) {
|
|
|
6887
7010
|
const rustCrates = [];
|
|
6888
7011
|
for (const rel of fileSet) {
|
|
6889
7012
|
if (rel !== "Cargo.toml" && !rel.endsWith("/Cargo.toml")) continue;
|
|
6890
|
-
const text = readText(
|
|
7013
|
+
const text = readText(join6(scan2.root, rel));
|
|
6891
7014
|
const m = /\[package\][^[]*?^\s*name\s*=\s*"([^"]+)"/ms.exec(text);
|
|
6892
7015
|
if (!m) continue;
|
|
6893
7016
|
const dir = rel.includes("/") ? posix.dirname(rel) : "";
|
|
@@ -6914,7 +7037,7 @@ function buildResolveContext(scan2) {
|
|
|
6914
7037
|
const workspacePackages = [];
|
|
6915
7038
|
for (const rel of fileSet) {
|
|
6916
7039
|
if (rel !== "package.json" && !rel.endsWith("/package.json")) continue;
|
|
6917
|
-
const pkg = tolerantJsonParse(readText(
|
|
7040
|
+
const pkg = tolerantJsonParse(readText(join6(scan2.root, rel)));
|
|
6918
7041
|
if (pkg === void 0) {
|
|
6919
7042
|
warnings.push(`unparseable ${rel} \u2014 skipped for workspace resolution`);
|
|
6920
7043
|
continue;
|
|
@@ -6941,7 +7064,7 @@ function buildResolveContext(scan2) {
|
|
|
6941
7064
|
const phpPsr4 = [];
|
|
6942
7065
|
for (const rel of fileSet) {
|
|
6943
7066
|
if (rel !== "composer.json" && !rel.endsWith("/composer.json")) continue;
|
|
6944
|
-
const composer = tolerantJsonParse(readText(
|
|
7067
|
+
const composer = tolerantJsonParse(readText(join6(scan2.root, rel)));
|
|
6945
7068
|
if (!composer) {
|
|
6946
7069
|
warnings.push(`unparseable ${rel} \u2014 skipped for PHP PSR-4 resolution`);
|
|
6947
7070
|
continue;
|
|
@@ -7515,255 +7638,191 @@ var init_calls = __esm({
|
|
|
7515
7638
|
}
|
|
7516
7639
|
});
|
|
7517
7640
|
|
|
7518
|
-
// src/
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7641
|
+
// src/render/symbols-json.ts
|
|
7642
|
+
function computeSymbolRefs(scan2) {
|
|
7643
|
+
const unique = uniqueDefsFor(scan2);
|
|
7644
|
+
const refs = /* @__PURE__ */ new Map();
|
|
7645
|
+
if (!unique.size) return refs;
|
|
7646
|
+
const add = (name2, file) => {
|
|
7647
|
+
let set = refs.get(name2);
|
|
7648
|
+
if (!set) refs.set(name2, set = /* @__PURE__ */ new Set());
|
|
7649
|
+
set.add(file);
|
|
7650
|
+
};
|
|
7651
|
+
for (const f of scan2.files) {
|
|
7652
|
+
if (f.kind === "code" && f.idents) {
|
|
7653
|
+
for (const id of f.idents) {
|
|
7654
|
+
const target = unique.get(id);
|
|
7655
|
+
if (target && target !== f.rel) add(id, f.rel);
|
|
7656
|
+
}
|
|
7657
|
+
} else if (f.kind === "doc") {
|
|
7658
|
+
const content = scan2.docText.get(f.rel);
|
|
7659
|
+
if (!content) continue;
|
|
7660
|
+
for (const tok of content.split(/[^A-Za-z0-9_]+/)) {
|
|
7661
|
+
const target = unique.get(tok);
|
|
7662
|
+
if (target && target !== f.rel) add(tok, f.rel);
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
7666
|
+
return refs;
|
|
7524
7667
|
}
|
|
7525
|
-
function
|
|
7526
|
-
const
|
|
7668
|
+
function buildSymbolIndex(scan2, refs = /* @__PURE__ */ new Map()) {
|
|
7669
|
+
const defsByName = /* @__PURE__ */ new Map();
|
|
7527
7670
|
for (const f of scan2.files) {
|
|
7528
7671
|
for (const s of f.symbols) {
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7672
|
+
let arr = defsByName.get(s.name);
|
|
7673
|
+
if (!arr) defsByName.set(s.name, arr = []);
|
|
7674
|
+
arr.push({
|
|
7675
|
+
file: s.file,
|
|
7676
|
+
line: s.line,
|
|
7677
|
+
...s.endLine !== void 0 ? { endLine: s.endLine } : {},
|
|
7678
|
+
kind: s.kind,
|
|
7679
|
+
exported: s.exported,
|
|
7680
|
+
lang: s.lang,
|
|
7681
|
+
...s.parent ? { parent: s.parent } : {}
|
|
7682
|
+
});
|
|
7533
7683
|
}
|
|
7534
7684
|
}
|
|
7535
|
-
const
|
|
7536
|
-
for (const
|
|
7537
|
-
|
|
7685
|
+
const defs = {};
|
|
7686
|
+
for (const name2 of [...defsByName.keys()].sort(byStr)) {
|
|
7687
|
+
defs[name2] = defsByName.get(name2).slice().sort((a, b) => byStr(a.file, b.file) || a.line - b.line || byStr(a.kind, b.kind));
|
|
7688
|
+
}
|
|
7689
|
+
const refsOut = {};
|
|
7690
|
+
for (const name2 of [...refs.keys()].sort(byStr)) {
|
|
7691
|
+
const files = [...refs.get(name2)].sort(byStr);
|
|
7692
|
+
if (files.length) refsOut[name2] = files;
|
|
7693
|
+
}
|
|
7694
|
+
return { schemaVersion: SCHEMA_VERSION, defs, refs: refsOut };
|
|
7538
7695
|
}
|
|
7539
|
-
function
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7696
|
+
function renderSymbolsJson(index) {
|
|
7697
|
+
return JSON.stringify(index, null, 2) + "\n";
|
|
7698
|
+
}
|
|
7699
|
+
var init_symbols_json = __esm({
|
|
7700
|
+
"src/render/symbols-json.ts"() {
|
|
7701
|
+
"use strict";
|
|
7702
|
+
init_types();
|
|
7703
|
+
init_sort();
|
|
7704
|
+
init_derived();
|
|
7545
7705
|
}
|
|
7546
|
-
|
|
7706
|
+
});
|
|
7707
|
+
|
|
7708
|
+
// src/callers.ts
|
|
7709
|
+
function computeImportPairs(scan2) {
|
|
7710
|
+
return new Set(importPairsFor(scan2));
|
|
7547
7711
|
}
|
|
7548
|
-
function
|
|
7549
|
-
const
|
|
7550
|
-
const
|
|
7712
|
+
function buildCallerIndex(scan2, importPairs, opts = {}) {
|
|
7713
|
+
const pairs = importPairs ?? importPairsFor(scan2);
|
|
7714
|
+
const recall = opts.recall === true;
|
|
7715
|
+
const defs = /* @__PURE__ */ new Map();
|
|
7551
7716
|
for (const f of scan2.files) {
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
}
|
|
7561
|
-
} else {
|
|
7562
|
-
const r = resolveImport(f.rel, f.ext, ref.spec, ctx);
|
|
7563
|
-
if (r.kind === "external") continue;
|
|
7564
|
-
if (r.kind === "dangling") {
|
|
7565
|
-
collect(fileEdgeMap, { from: f.rel, to: ref.spec, kind: "import", weight: 1, dangling: true, reason: r.reason });
|
|
7566
|
-
} else if (r.target !== f.rel) {
|
|
7567
|
-
collect(fileEdgeMap, { from: f.rel, to: r.target, kind: "import", weight: 1 });
|
|
7568
|
-
importPairs.add(`${f.rel}|${r.target}`);
|
|
7569
|
-
}
|
|
7570
|
-
}
|
|
7717
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7718
|
+
for (const s of f.symbols) {
|
|
7719
|
+
if (!s.exported || REFERENCE_KINDS2.has(s.kind)) continue;
|
|
7720
|
+
if (seen.has(s.name)) continue;
|
|
7721
|
+
seen.add(s.name);
|
|
7722
|
+
let arr = defs.get(s.name);
|
|
7723
|
+
if (!arr) defs.set(s.name, arr = []);
|
|
7724
|
+
arr.push(s);
|
|
7571
7725
|
}
|
|
7572
7726
|
}
|
|
7573
|
-
const
|
|
7574
|
-
for (const
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
const unique = uniqueSymbolDefs(scan2);
|
|
7579
|
-
if (unique.size) {
|
|
7580
|
-
for (const f of scan2.files) {
|
|
7581
|
-
if (f.kind !== "code" || !f.idents?.length) continue;
|
|
7582
|
-
const perTarget = /* @__PURE__ */ new Map();
|
|
7583
|
-
for (const id of f.idents) {
|
|
7584
|
-
const target = unique.get(id);
|
|
7585
|
-
if (!target || target === f.rel) continue;
|
|
7586
|
-
perTarget.set(target, (perTarget.get(target) ?? 0) + 1);
|
|
7587
|
-
}
|
|
7588
|
-
for (const [target, count] of perTarget) {
|
|
7589
|
-
const pair = `${f.rel}|${target}`;
|
|
7590
|
-
if (importPairs.has(pair) || callPairs.has(pair)) continue;
|
|
7591
|
-
collect(fileEdgeMap, { from: f.rel, to: target, kind: "use", weight: Math.min(count, 5) });
|
|
7592
|
-
}
|
|
7727
|
+
const localDefs = /* @__PURE__ */ new Map();
|
|
7728
|
+
for (const f of scan2.files) {
|
|
7729
|
+
const byName = /* @__PURE__ */ new Map();
|
|
7730
|
+
for (const s of f.symbols) {
|
|
7731
|
+
if (!REFERENCE_KINDS2.has(s.kind) && !byName.has(s.name)) byName.set(s.name, s);
|
|
7593
7732
|
}
|
|
7733
|
+
localDefs.set(f.rel, byName);
|
|
7594
7734
|
}
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7735
|
+
const sites = /* @__PURE__ */ new Map();
|
|
7736
|
+
const record = (def, caller) => {
|
|
7737
|
+
let entry = sites.get(def.name + "\0" + def.file);
|
|
7738
|
+
if (!entry) sites.set(def.name + "\0" + def.file, entry = { def, callers: [] });
|
|
7739
|
+
entry.callers.push(caller);
|
|
7740
|
+
};
|
|
7741
|
+
for (const f of scan2.files) {
|
|
7742
|
+
if (!f.calls?.length) continue;
|
|
7743
|
+
const family = familyOf(f.lang);
|
|
7744
|
+
const own = localDefs.get(f.rel);
|
|
7745
|
+
for (const c2 of f.calls) {
|
|
7746
|
+
const local = own.get(c2.name);
|
|
7747
|
+
if (local) {
|
|
7748
|
+
if (local.line !== c2.line)
|
|
7749
|
+
record(local, recall ? { file: f.rel, line: c2.line, confidence: "corroborated" } : { file: f.rel, line: c2.line });
|
|
7750
|
+
continue;
|
|
7608
7751
|
}
|
|
7752
|
+
const cands = (defs.get(c2.name) ?? []).filter((d) => familyOf(d.lang) === family && d.file !== f.rel).map((d) => ({ file: d.file, lang: d.lang }));
|
|
7753
|
+
if (!cands.length) continue;
|
|
7754
|
+
const imported = cands.filter((d) => pairs.has(`${f.rel}|${d.file}`));
|
|
7755
|
+
const chosen = family === "js" ? imported.length ? pickCandidate(f.rel, imported) : (
|
|
7756
|
+
// JS/TS gate: no corroborating import → no binding. Recall mode
|
|
7757
|
+
// relaxes this to a unique-repo-wide name match (issue #7).
|
|
7758
|
+
recall && cands.length === 1 ? cands[0] : void 0
|
|
7759
|
+
) : imported.length ? pickCandidate(f.rel, imported) : pickCandidate(f.rel, cands);
|
|
7760
|
+
if (!chosen) continue;
|
|
7761
|
+
const def = defs.get(c2.name).find((d) => d.file === chosen.file);
|
|
7762
|
+
record(
|
|
7763
|
+
def,
|
|
7764
|
+
recall ? { file: f.rel, line: c2.line, confidence: imported.length ? "corroborated" : "unique-name" } : { file: f.rel, line: c2.line }
|
|
7765
|
+
);
|
|
7609
7766
|
}
|
|
7610
7767
|
}
|
|
7611
|
-
const
|
|
7612
|
-
|
|
7613
|
-
)
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
if (e.dangling || !fileSet.has(e.to)) continue;
|
|
7619
|
-
degOut.set(e.from, (degOut.get(e.from) ?? 0) + 1);
|
|
7620
|
-
degIn.set(e.to, (degIn.get(e.to) ?? 0) + 1);
|
|
7621
|
-
}
|
|
7622
|
-
const KIND_RANK = { import: 5, call: 4, use: 3, "doc-link": 2, mention: 1, contains: 0 };
|
|
7623
|
-
const modEdgeMap = /* @__PURE__ */ new Map();
|
|
7624
|
-
for (const e of fileEdges) {
|
|
7625
|
-
if (e.dangling || !fileSet.has(e.to)) continue;
|
|
7626
|
-
const from = moduleOf.get(e.from);
|
|
7627
|
-
const to = moduleOf.get(e.to);
|
|
7628
|
-
if (!from || !to || from === to) continue;
|
|
7629
|
-
const k = `${from}\0${to}`;
|
|
7630
|
-
const prev = modEdgeMap.get(k);
|
|
7631
|
-
if (prev) {
|
|
7632
|
-
prev.weight += e.weight;
|
|
7633
|
-
if ((KIND_RANK[e.kind] ?? 0) > (KIND_RANK[prev.kind] ?? 0)) prev.kind = e.kind;
|
|
7634
|
-
} else {
|
|
7635
|
-
modEdgeMap.set(k, { from, to, kind: e.kind, weight: e.weight });
|
|
7636
|
-
}
|
|
7637
|
-
}
|
|
7638
|
-
const moduleEdges = [...modEdgeMap.values()].sort((a, b) => byStr(a.from, b.from) || byStr(a.to, b.to));
|
|
7639
|
-
const modDegIn = /* @__PURE__ */ new Map();
|
|
7640
|
-
const modDegOut = /* @__PURE__ */ new Map();
|
|
7641
|
-
for (const e of moduleEdges) {
|
|
7642
|
-
modDegOut.set(e.from, (modDegOut.get(e.from) ?? 0) + 1);
|
|
7643
|
-
modDegIn.set(e.to, (modDegIn.get(e.to) ?? 0) + 1);
|
|
7644
|
-
}
|
|
7645
|
-
const files = scan2.files.map((f) => ({
|
|
7646
|
-
id: f.rel,
|
|
7647
|
-
kind: "file",
|
|
7648
|
-
rel: f.rel,
|
|
7649
|
-
fileKind: f.kind,
|
|
7650
|
-
lang: f.lang,
|
|
7651
|
-
module: moduleOf.get(f.rel) ?? "root",
|
|
7652
|
-
title: f.title,
|
|
7653
|
-
summary: f.summary,
|
|
7654
|
-
symbols: f.symbols.length,
|
|
7655
|
-
lines: f.lines,
|
|
7656
|
-
degIn: degIn.get(f.rel) ?? 0,
|
|
7657
|
-
degOut: degOut.get(f.rel) ?? 0
|
|
7658
|
-
})).sort((a, b) => byStr(a.rel, b.rel));
|
|
7659
|
-
const symbolsByModule = /* @__PURE__ */ new Map();
|
|
7660
|
-
for (const f of scan2.files) {
|
|
7661
|
-
const slug = moduleOf.get(f.rel) ?? "root";
|
|
7662
|
-
symbolsByModule.set(slug, (symbolsByModule.get(slug) ?? 0) + f.symbols.length);
|
|
7768
|
+
const index = /* @__PURE__ */ new Map();
|
|
7769
|
+
const keys = [...sites.keys()].sort(byStr);
|
|
7770
|
+
for (const key of keys) {
|
|
7771
|
+
const { def, callers } = sites.get(key);
|
|
7772
|
+
callers.sort((a, b) => byStr(a.file, b.file) || a.line - b.line);
|
|
7773
|
+
if (!index.has(def.name)) index.set(def.name, { def, callers });
|
|
7774
|
+
else index.set(`${def.name}@${def.file}`, { def, callers });
|
|
7663
7775
|
}
|
|
7664
|
-
|
|
7665
|
-
id: m.slug,
|
|
7666
|
-
kind: "module",
|
|
7667
|
-
slug: m.slug,
|
|
7668
|
-
path: m.path,
|
|
7669
|
-
title: m.title,
|
|
7670
|
-
summary: m.summary,
|
|
7671
|
-
tier: m.tier,
|
|
7672
|
-
members: m.members,
|
|
7673
|
-
symbols: symbolsByModule.get(m.slug) ?? 0,
|
|
7674
|
-
degIn: modDegIn.get(m.slug) ?? 0,
|
|
7675
|
-
degOut: modDegOut.get(m.slug) ?? 0
|
|
7676
|
-
})).sort((a, b) => byStr(a.slug, b.slug));
|
|
7677
|
-
return {
|
|
7678
|
-
schemaVersion: meta?.schemaVersion ?? SCHEMA_VERSION,
|
|
7679
|
-
version: meta?.version ?? ENGINE_VERSION,
|
|
7680
|
-
commit: scan2.commit,
|
|
7681
|
-
fileCount: scan2.files.length,
|
|
7682
|
-
languages: scan2.languages,
|
|
7683
|
-
files,
|
|
7684
|
-
modules: moduleNodes,
|
|
7685
|
-
fileEdges,
|
|
7686
|
-
moduleEdges
|
|
7687
|
-
};
|
|
7776
|
+
return index;
|
|
7688
7777
|
}
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
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
|
-
};
|
|
7713
|
-
for (const f of scan2.files) {
|
|
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
|
-
}
|
|
7778
|
+
function enclosingSymbol(scan2, file, line) {
|
|
7779
|
+
const f = scan2.files.find((x) => x.rel === file);
|
|
7780
|
+
if (!f?.symbols.length) return void 0;
|
|
7781
|
+
return enclosingAmong(f.symbols, line);
|
|
7782
|
+
}
|
|
7783
|
+
function enclosingAmong(symbols, line) {
|
|
7784
|
+
let best;
|
|
7785
|
+
for (const s of symbols) {
|
|
7786
|
+
if (REFERENCE_KINDS2.has(s.kind)) continue;
|
|
7787
|
+
if (s.line > line) continue;
|
|
7788
|
+
if (s.endLine !== void 0 && line > s.endLine) continue;
|
|
7789
|
+
if (!best || s.line > best.line || s.line === best.line && (s.endLine ?? Infinity) <= (best.endLine ?? Infinity)) {
|
|
7790
|
+
best = s;
|
|
7726
7791
|
}
|
|
7727
7792
|
}
|
|
7728
|
-
return
|
|
7793
|
+
return best;
|
|
7729
7794
|
}
|
|
7730
|
-
function
|
|
7731
|
-
const
|
|
7795
|
+
function buildRawCallerIndex(scan2) {
|
|
7796
|
+
const byName = /* @__PURE__ */ new Map();
|
|
7732
7797
|
for (const f of scan2.files) {
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
...s.parent ? { parent: s.parent } : {}
|
|
7744
|
-
});
|
|
7798
|
+
if (!f.calls?.length) continue;
|
|
7799
|
+
const symbols = f.symbols.filter((s) => !REFERENCE_KINDS2.has(s.kind));
|
|
7800
|
+
for (const c2 of f.calls) {
|
|
7801
|
+
const site = { file: f.rel, line: c2.line };
|
|
7802
|
+
if (c2.receiver !== void 0) site.receiver = c2.receiver;
|
|
7803
|
+
const enc = enclosingAmong(symbols, c2.line);
|
|
7804
|
+
if (enc) site.enclosingSymbol = enc;
|
|
7805
|
+
let arr = byName.get(c2.name);
|
|
7806
|
+
if (!arr) byName.set(c2.name, arr = []);
|
|
7807
|
+
arr.push(site);
|
|
7745
7808
|
}
|
|
7746
7809
|
}
|
|
7747
|
-
const
|
|
7748
|
-
for (const name2 of [...
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
for (const name2 of [...refs.keys()].sort(byStr)) {
|
|
7753
|
-
const files = [...refs.get(name2)].sort(byStr);
|
|
7754
|
-
if (files.length) refsOut[name2] = files;
|
|
7810
|
+
const index = /* @__PURE__ */ new Map();
|
|
7811
|
+
for (const name2 of [...byName.keys()].sort(byStr)) {
|
|
7812
|
+
const sites = byName.get(name2);
|
|
7813
|
+
sites.sort((a, b) => byStr(a.file, b.file) || a.line - b.line);
|
|
7814
|
+
index.set(name2, sites);
|
|
7755
7815
|
}
|
|
7756
|
-
return
|
|
7757
|
-
}
|
|
7758
|
-
function renderSymbolsJson(index) {
|
|
7759
|
-
return JSON.stringify(index, null, 2) + "\n";
|
|
7816
|
+
return index;
|
|
7760
7817
|
}
|
|
7761
|
-
var
|
|
7762
|
-
|
|
7818
|
+
var REFERENCE_KINDS2;
|
|
7819
|
+
var init_callers = __esm({
|
|
7820
|
+
"src/callers.ts"() {
|
|
7763
7821
|
"use strict";
|
|
7764
|
-
|
|
7822
|
+
init_calls();
|
|
7823
|
+
init_derived();
|
|
7765
7824
|
init_sort();
|
|
7766
|
-
|
|
7825
|
+
REFERENCE_KINDS2 = /* @__PURE__ */ new Set(["reexport", "reexport-all", "default"]);
|
|
7767
7826
|
}
|
|
7768
7827
|
});
|
|
7769
7828
|
|
|
@@ -7941,7 +8000,7 @@ var init_bm25 = __esm({
|
|
|
7941
8000
|
});
|
|
7942
8001
|
|
|
7943
8002
|
// src/complexity.ts
|
|
7944
|
-
import { join as
|
|
8003
|
+
import { join as join7 } from "path";
|
|
7945
8004
|
function complexityOfSource(source) {
|
|
7946
8005
|
return 1 + (source.match(BRANCH_RE) ?? []).length;
|
|
7947
8006
|
}
|
|
@@ -7951,7 +8010,7 @@ function symbolComplexity(scan2, rel, top = 50) {
|
|
|
7951
8010
|
if (f.kind !== "code") continue;
|
|
7952
8011
|
if (rel && f.rel !== rel) continue;
|
|
7953
8012
|
if (!f.symbols.length) continue;
|
|
7954
|
-
const lines = readText(
|
|
8013
|
+
const lines = readText(join7(scan2.root, f.rel)).split("\n");
|
|
7955
8014
|
for (const s of f.symbols) {
|
|
7956
8015
|
if (s.kind === "reexport" || s.kind === "reexport-all") continue;
|
|
7957
8016
|
const end = s.endLine ?? s.line;
|
|
@@ -7986,7 +8045,7 @@ var init_complexity = __esm({
|
|
|
7986
8045
|
});
|
|
7987
8046
|
|
|
7988
8047
|
// src/derived.ts
|
|
7989
|
-
import { join as
|
|
8048
|
+
import { join as join8 } from "path";
|
|
7990
8049
|
function cacheFor(scan2) {
|
|
7991
8050
|
let c2 = caches.get(scan2);
|
|
7992
8051
|
if (!c2) caches.set(scan2, c2 = {});
|
|
@@ -8012,6 +8071,11 @@ function importPairsFor(scan2) {
|
|
|
8012
8071
|
}
|
|
8013
8072
|
return c2.importPairs;
|
|
8014
8073
|
}
|
|
8074
|
+
function publishImportPairs(scan2, ctx, pairs) {
|
|
8075
|
+
const c2 = caches.get(scan2);
|
|
8076
|
+
if (!c2 || c2.resolveCtx !== ctx || c2.importPairs) return;
|
|
8077
|
+
c2.importPairs = pairs;
|
|
8078
|
+
}
|
|
8015
8079
|
function uniqueDefsFor(scan2) {
|
|
8016
8080
|
const c2 = cacheFor(scan2);
|
|
8017
8081
|
return c2.uniqueDefs ??= uniqueSymbolDefs(scan2);
|
|
@@ -8039,7 +8103,7 @@ function fileComplexityFor(scan2) {
|
|
|
8039
8103
|
const m = /* @__PURE__ */ new Map();
|
|
8040
8104
|
for (const f of scan2.files) {
|
|
8041
8105
|
if (f.kind !== "code") continue;
|
|
8042
|
-
m.set(f.rel, complexityOfSource(readText(
|
|
8106
|
+
m.set(f.rel, complexityOfSource(readText(join8(scan2.root, f.rel))));
|
|
8043
8107
|
}
|
|
8044
8108
|
c2.fileComplexity = m;
|
|
8045
8109
|
}
|
|
@@ -8060,129 +8124,196 @@ var init_derived = __esm({
|
|
|
8060
8124
|
}
|
|
8061
8125
|
});
|
|
8062
8126
|
|
|
8063
|
-
// src/
|
|
8064
|
-
|
|
8065
|
-
|
|
8127
|
+
// src/graph.ts
|
|
8128
|
+
import { join as join9 } from "path";
|
|
8129
|
+
function isDistinctive(name2) {
|
|
8130
|
+
if (name2.length < 5) return false;
|
|
8131
|
+
const internalUpper = /[a-z][A-Z]/.test(name2) || /[A-Z]{2}/.test(name2);
|
|
8132
|
+
return internalUpper || name2.includes("_") || /\d/.test(name2);
|
|
8066
8133
|
}
|
|
8067
|
-
function
|
|
8068
|
-
const
|
|
8069
|
-
const recall = opts.recall === true;
|
|
8070
|
-
const defs = /* @__PURE__ */ new Map();
|
|
8134
|
+
function uniqueSymbolDefs(scan2) {
|
|
8135
|
+
const byName = /* @__PURE__ */ new Map();
|
|
8071
8136
|
for (const f of scan2.files) {
|
|
8072
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8073
8137
|
for (const s of f.symbols) {
|
|
8074
|
-
if (!s.exported || REFERENCE_KINDS3.has(s.kind)) continue;
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
if (!arr) defs.set(s.name, arr = []);
|
|
8079
|
-
arr.push(s);
|
|
8138
|
+
if (!s.exported || REFERENCE_KINDS3.has(s.kind) || !isDistinctive(s.name)) continue;
|
|
8139
|
+
let set = byName.get(s.name);
|
|
8140
|
+
if (!set) byName.set(s.name, set = /* @__PURE__ */ new Set());
|
|
8141
|
+
set.add(f.rel);
|
|
8080
8142
|
}
|
|
8081
8143
|
}
|
|
8082
|
-
const
|
|
8083
|
-
for (const
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8144
|
+
const unique = /* @__PURE__ */ new Map();
|
|
8145
|
+
for (const [name2, files] of byName) if (files.size === 1) unique.set(name2, [...files][0]);
|
|
8146
|
+
return unique;
|
|
8147
|
+
}
|
|
8148
|
+
function collect(edges, e) {
|
|
8149
|
+
const k = keyOf(e.from, e.to, e.kind);
|
|
8150
|
+
const prev = edges.get(k);
|
|
8151
|
+
if (prev) {
|
|
8152
|
+
prev.weight += e.weight;
|
|
8153
|
+
return;
|
|
8089
8154
|
}
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
};
|
|
8155
|
+
edges.set(k, { ...e });
|
|
8156
|
+
}
|
|
8157
|
+
function buildGraph(scan2, ctx, modules, moduleOf, meta) {
|
|
8158
|
+
const fileEdgeMap = /* @__PURE__ */ new Map();
|
|
8159
|
+
const importPairs = /* @__PURE__ */ new Set();
|
|
8096
8160
|
for (const f of scan2.files) {
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
if (
|
|
8104
|
-
|
|
8105
|
-
|
|
8161
|
+
for (const ref of f.refs) {
|
|
8162
|
+
if (ref.kind === "doc-link") {
|
|
8163
|
+
const r = resolveDocLink(f.rel, ref.spec, ctx);
|
|
8164
|
+
if (r.kind === "external") continue;
|
|
8165
|
+
if (r.kind === "dangling") {
|
|
8166
|
+
collect(fileEdgeMap, { from: f.rel, to: ref.spec, kind: "doc-link", weight: 1, dangling: true, reason: r.reason });
|
|
8167
|
+
} else if (r.target !== f.rel) {
|
|
8168
|
+
collect(fileEdgeMap, { from: f.rel, to: r.target, kind: "doc-link", weight: 1 });
|
|
8169
|
+
}
|
|
8170
|
+
} else {
|
|
8171
|
+
const r = resolveImport(f.rel, f.ext, ref.spec, ctx);
|
|
8172
|
+
if (r.kind === "external") continue;
|
|
8173
|
+
if (r.kind === "dangling") {
|
|
8174
|
+
collect(fileEdgeMap, { from: f.rel, to: ref.spec, kind: "import", weight: 1, dangling: true, reason: r.reason });
|
|
8175
|
+
} else if (r.target !== f.rel) {
|
|
8176
|
+
collect(fileEdgeMap, { from: f.rel, to: r.target, kind: "import", weight: 1 });
|
|
8177
|
+
importPairs.add(`${f.rel}|${r.target}`);
|
|
8178
|
+
}
|
|
8106
8179
|
}
|
|
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
8180
|
}
|
|
8122
8181
|
}
|
|
8123
|
-
const
|
|
8124
|
-
const
|
|
8125
|
-
|
|
8126
|
-
|
|
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 });
|
|
8182
|
+
const callPairs = /* @__PURE__ */ new Set();
|
|
8183
|
+
for (const e of resolveCallEdges(scan2, importPairs)) {
|
|
8184
|
+
collect(fileEdgeMap, e);
|
|
8185
|
+
callPairs.add(`${e.from}|${e.to}`);
|
|
8130
8186
|
}
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8187
|
+
publishImportPairs(scan2, ctx, importPairs);
|
|
8188
|
+
const unique = uniqueDefsFor(scan2);
|
|
8189
|
+
if (unique.size) {
|
|
8190
|
+
for (const f of scan2.files) {
|
|
8191
|
+
if (f.kind !== "code" || !f.idents?.length) continue;
|
|
8192
|
+
const perTarget = /* @__PURE__ */ new Map();
|
|
8193
|
+
for (const id of f.idents) {
|
|
8194
|
+
const target = unique.get(id);
|
|
8195
|
+
if (!target || target === f.rel) continue;
|
|
8196
|
+
perTarget.set(target, (perTarget.get(target) ?? 0) + 1);
|
|
8197
|
+
}
|
|
8198
|
+
for (const [target, count] of perTarget) {
|
|
8199
|
+
const pair = `${f.rel}|${target}`;
|
|
8200
|
+
if (importPairs.has(pair) || callPairs.has(pair)) continue;
|
|
8201
|
+
collect(fileEdgeMap, { from: f.rel, to: target, kind: "use", weight: Math.min(count, 5) });
|
|
8202
|
+
}
|
|
8146
8203
|
}
|
|
8147
8204
|
}
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
arr.push(site);
|
|
8205
|
+
if (unique.size) {
|
|
8206
|
+
for (const f of scan2.files) {
|
|
8207
|
+
if (f.kind !== "doc") continue;
|
|
8208
|
+
const content = scan2.docText.get(f.rel) ?? readText(join9(scan2.root, f.rel));
|
|
8209
|
+
if (!content) continue;
|
|
8210
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
8211
|
+
for (const tok of content.split(/[^A-Za-z0-9_]+/)) {
|
|
8212
|
+
if (unique.has(tok)) tokens.set(tok, (tokens.get(tok) ?? 0) + 1);
|
|
8213
|
+
}
|
|
8214
|
+
for (const [name2, count] of tokens) {
|
|
8215
|
+
const target = unique.get(name2);
|
|
8216
|
+
if (target === f.rel) continue;
|
|
8217
|
+
collect(fileEdgeMap, { from: f.rel, to: target, kind: "mention", weight: Math.min(count, 5) });
|
|
8218
|
+
}
|
|
8163
8219
|
}
|
|
8164
8220
|
}
|
|
8165
|
-
const
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8221
|
+
const fileEdges = [...fileEdgeMap.values()].sort(
|
|
8222
|
+
(a, b) => byStr(a.from, b.from) || byStr(a.to, b.to) || byStr(a.kind, b.kind)
|
|
8223
|
+
);
|
|
8224
|
+
const degIn = /* @__PURE__ */ new Map();
|
|
8225
|
+
const degOut = /* @__PURE__ */ new Map();
|
|
8226
|
+
const fileSet = new Set(scan2.files.map((f) => f.rel));
|
|
8227
|
+
for (const e of fileEdges) {
|
|
8228
|
+
if (e.dangling || !fileSet.has(e.to)) continue;
|
|
8229
|
+
degOut.set(e.from, (degOut.get(e.from) ?? 0) + 1);
|
|
8230
|
+
degIn.set(e.to, (degIn.get(e.to) ?? 0) + 1);
|
|
8170
8231
|
}
|
|
8171
|
-
|
|
8232
|
+
const KIND_RANK = { import: 5, call: 4, use: 3, "doc-link": 2, mention: 1, contains: 0 };
|
|
8233
|
+
const modEdgeMap = /* @__PURE__ */ new Map();
|
|
8234
|
+
for (const e of fileEdges) {
|
|
8235
|
+
if (e.dangling || !fileSet.has(e.to)) continue;
|
|
8236
|
+
const from = moduleOf.get(e.from);
|
|
8237
|
+
const to = moduleOf.get(e.to);
|
|
8238
|
+
if (!from || !to || from === to) continue;
|
|
8239
|
+
const k = `${from}${SEP}${to}`;
|
|
8240
|
+
const prev = modEdgeMap.get(k);
|
|
8241
|
+
if (prev) {
|
|
8242
|
+
prev.weight += e.weight;
|
|
8243
|
+
if ((KIND_RANK[e.kind] ?? 0) > (KIND_RANK[prev.kind] ?? 0)) prev.kind = e.kind;
|
|
8244
|
+
} else {
|
|
8245
|
+
modEdgeMap.set(k, { from, to, kind: e.kind, weight: e.weight });
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
const moduleEdges = [...modEdgeMap.values()].sort((a, b) => byStr(a.from, b.from) || byStr(a.to, b.to));
|
|
8249
|
+
const modDegIn = /* @__PURE__ */ new Map();
|
|
8250
|
+
const modDegOut = /* @__PURE__ */ new Map();
|
|
8251
|
+
for (const e of moduleEdges) {
|
|
8252
|
+
modDegOut.set(e.from, (modDegOut.get(e.from) ?? 0) + 1);
|
|
8253
|
+
modDegIn.set(e.to, (modDegIn.get(e.to) ?? 0) + 1);
|
|
8254
|
+
}
|
|
8255
|
+
const files = scan2.files.map((f) => ({
|
|
8256
|
+
id: f.rel,
|
|
8257
|
+
kind: "file",
|
|
8258
|
+
rel: f.rel,
|
|
8259
|
+
fileKind: f.kind,
|
|
8260
|
+
lang: f.lang,
|
|
8261
|
+
module: moduleOf.get(f.rel) ?? "root",
|
|
8262
|
+
title: f.title,
|
|
8263
|
+
summary: f.summary,
|
|
8264
|
+
symbols: f.symbols.length,
|
|
8265
|
+
lines: f.lines,
|
|
8266
|
+
degIn: degIn.get(f.rel) ?? 0,
|
|
8267
|
+
degOut: degOut.get(f.rel) ?? 0
|
|
8268
|
+
})).sort((a, b) => byStr(a.rel, b.rel));
|
|
8269
|
+
const symbolsByModule = /* @__PURE__ */ new Map();
|
|
8270
|
+
for (const f of scan2.files) {
|
|
8271
|
+
const slug = moduleOf.get(f.rel) ?? "root";
|
|
8272
|
+
symbolsByModule.set(slug, (symbolsByModule.get(slug) ?? 0) + f.symbols.length);
|
|
8273
|
+
}
|
|
8274
|
+
const moduleNodes = modules.map((m) => ({
|
|
8275
|
+
id: m.slug,
|
|
8276
|
+
kind: "module",
|
|
8277
|
+
slug: m.slug,
|
|
8278
|
+
path: m.path,
|
|
8279
|
+
title: m.title,
|
|
8280
|
+
summary: m.summary,
|
|
8281
|
+
tier: m.tier,
|
|
8282
|
+
members: m.members,
|
|
8283
|
+
symbols: symbolsByModule.get(m.slug) ?? 0,
|
|
8284
|
+
degIn: modDegIn.get(m.slug) ?? 0,
|
|
8285
|
+
degOut: modDegOut.get(m.slug) ?? 0
|
|
8286
|
+
})).sort((a, b) => byStr(a.slug, b.slug));
|
|
8287
|
+
return {
|
|
8288
|
+
schemaVersion: meta?.schemaVersion ?? SCHEMA_VERSION,
|
|
8289
|
+
version: meta?.version ?? ENGINE_VERSION,
|
|
8290
|
+
commit: scan2.commit,
|
|
8291
|
+
fileCount: scan2.files.length,
|
|
8292
|
+
languages: scan2.languages,
|
|
8293
|
+
files,
|
|
8294
|
+
modules: moduleNodes,
|
|
8295
|
+
fileEdges,
|
|
8296
|
+
moduleEdges
|
|
8297
|
+
};
|
|
8172
8298
|
}
|
|
8173
|
-
var REFERENCE_KINDS3;
|
|
8174
|
-
var
|
|
8175
|
-
"src/
|
|
8299
|
+
var REFERENCE_KINDS3, SEP, keyOf;
|
|
8300
|
+
var init_graph = __esm({
|
|
8301
|
+
"src/graph.ts"() {
|
|
8176
8302
|
"use strict";
|
|
8303
|
+
init_types();
|
|
8304
|
+
init_resolve();
|
|
8177
8305
|
init_calls();
|
|
8178
8306
|
init_derived();
|
|
8307
|
+
init_walk();
|
|
8179
8308
|
init_sort();
|
|
8180
8309
|
REFERENCE_KINDS3 = /* @__PURE__ */ new Set(["reexport", "reexport-all", "default"]);
|
|
8310
|
+
SEP = "\0";
|
|
8311
|
+
keyOf = (from, to, kind) => `${from}${SEP}${to}${SEP}${kind}`;
|
|
8181
8312
|
}
|
|
8182
8313
|
});
|
|
8183
8314
|
|
|
8184
8315
|
// src/query.ts
|
|
8185
|
-
import { join as
|
|
8316
|
+
import { join as join10 } from "path";
|
|
8186
8317
|
function symbolsOverview(scan2, rel) {
|
|
8187
8318
|
const f = scan2.files.find((x) => x.rel === rel);
|
|
8188
8319
|
if (!f) return [];
|
|
@@ -8213,7 +8344,7 @@ function findSymbol(scan2, namePath, opts = {}) {
|
|
|
8213
8344
|
if (opts.includeBody) {
|
|
8214
8345
|
for (const m of capped) {
|
|
8215
8346
|
const end = m.endLine ?? m.line;
|
|
8216
|
-
const content = readText(
|
|
8347
|
+
const content = readText(join10(scan2.root, m.file));
|
|
8217
8348
|
if (!content) continue;
|
|
8218
8349
|
m.body = content.split("\n").slice(m.line - 1, end).join("\n");
|
|
8219
8350
|
}
|
|
@@ -8259,8 +8390,8 @@ var init_query = __esm({
|
|
|
8259
8390
|
});
|
|
8260
8391
|
|
|
8261
8392
|
// src/edit.ts
|
|
8262
|
-
import { readFileSync as
|
|
8263
|
-
import { join as
|
|
8393
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
8394
|
+
import { join as join11 } from "path";
|
|
8264
8395
|
function resolveUniqueSymbol(scan2, namePath, file) {
|
|
8265
8396
|
let matches = findSymbol(scan2, namePath);
|
|
8266
8397
|
if (file) matches = matches.filter((m) => m.file === file);
|
|
@@ -8273,12 +8404,12 @@ function resolveUniqueSymbol(scan2, namePath, file) {
|
|
|
8273
8404
|
throw new Error(`"${namePath}" is ambiguous (${matches.length} matches: ${list}) \u2014 qualify with \`file\` or a Parent/name path`);
|
|
8274
8405
|
}
|
|
8275
8406
|
function readLines(abs) {
|
|
8276
|
-
return
|
|
8407
|
+
return readFileSync5(abs, "utf8").split("\n");
|
|
8277
8408
|
}
|
|
8278
8409
|
function replaceSymbolBody(scan2, namePath, body2, file) {
|
|
8279
8410
|
const sym = resolveUniqueSymbol(scan2, namePath, file);
|
|
8280
8411
|
const end = sym.endLine ?? sym.line;
|
|
8281
|
-
const abs =
|
|
8412
|
+
const abs = join11(scan2.root, sym.file);
|
|
8282
8413
|
const lines = readLines(abs);
|
|
8283
8414
|
const newLines = body2.replace(/^\n+|\n+$/g, "").split("\n");
|
|
8284
8415
|
lines.splice(sym.line - 1, end - sym.line + 1, ...newLines);
|
|
@@ -8286,7 +8417,7 @@ function replaceSymbolBody(scan2, namePath, body2, file) {
|
|
|
8286
8417
|
return { file: sym.file, startLine: sym.line, endLine: sym.line + newLines.length - 1, lines: newLines.length };
|
|
8287
8418
|
}
|
|
8288
8419
|
function insertAt(scan2, sym, body2, index, blankBefore, blankAfter) {
|
|
8289
|
-
const abs =
|
|
8420
|
+
const abs = join11(scan2.root, sym.file);
|
|
8290
8421
|
const lines = readLines(abs);
|
|
8291
8422
|
const minGap = SEPARATED_KINDS.has(sym.kind) ? 1 : 0;
|
|
8292
8423
|
const newLines = body2.replace(/^\n+|\n+$/g, "").split("\n");
|
|
@@ -8317,8 +8448,8 @@ var init_edit = __esm({
|
|
|
8317
8448
|
});
|
|
8318
8449
|
|
|
8319
8450
|
// src/memory.ts
|
|
8320
|
-
import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as
|
|
8321
|
-
import { dirname as
|
|
8451
|
+
import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
8452
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
8322
8453
|
function sanitize(name2) {
|
|
8323
8454
|
const clean = name2.replace(/^mem:/, "").replace(/\.md$/, "");
|
|
8324
8455
|
if (!clean) throw new Error("memory name is empty");
|
|
@@ -8332,17 +8463,17 @@ function sanitize(name2) {
|
|
|
8332
8463
|
return clean;
|
|
8333
8464
|
}
|
|
8334
8465
|
function memoryPath(repo, name2) {
|
|
8335
|
-
return
|
|
8466
|
+
return join12(repo, ...MEMORY_DIR, `${sanitize(name2)}.md`);
|
|
8336
8467
|
}
|
|
8337
8468
|
function writeMemory(repo, name2, content) {
|
|
8338
8469
|
const path = memoryPath(repo, name2);
|
|
8339
|
-
mkdirSync2(
|
|
8470
|
+
mkdirSync2(dirname4(path), { recursive: true });
|
|
8340
8471
|
writeFileSync3(path, content.endsWith("\n") ? content : content + "\n");
|
|
8341
8472
|
return sanitize(name2);
|
|
8342
8473
|
}
|
|
8343
8474
|
function readMemory(repo, name2) {
|
|
8344
8475
|
try {
|
|
8345
|
-
return
|
|
8476
|
+
return readFileSync6(memoryPath(repo, name2), "utf8");
|
|
8346
8477
|
} catch {
|
|
8347
8478
|
return void 0;
|
|
8348
8479
|
}
|
|
@@ -8350,7 +8481,7 @@ function readMemory(repo, name2) {
|
|
|
8350
8481
|
function deleteMemory(repo, name2) {
|
|
8351
8482
|
const path = memoryPath(repo, name2);
|
|
8352
8483
|
try {
|
|
8353
|
-
|
|
8484
|
+
statSync3(path);
|
|
8354
8485
|
} catch {
|
|
8355
8486
|
return false;
|
|
8356
8487
|
}
|
|
@@ -8358,7 +8489,7 @@ function deleteMemory(repo, name2) {
|
|
|
8358
8489
|
return true;
|
|
8359
8490
|
}
|
|
8360
8491
|
function listMemories(repo) {
|
|
8361
|
-
const root =
|
|
8492
|
+
const root = join12(repo, ...MEMORY_DIR);
|
|
8362
8493
|
const out2 = [];
|
|
8363
8494
|
const walk2 = (dir, prefix) => {
|
|
8364
8495
|
let entries;
|
|
@@ -8368,7 +8499,7 @@ function listMemories(repo) {
|
|
|
8368
8499
|
return;
|
|
8369
8500
|
}
|
|
8370
8501
|
for (const e of entries) {
|
|
8371
|
-
if (e.isDirectory()) walk2(
|
|
8502
|
+
if (e.isDirectory()) walk2(join12(dir, e.name), prefix ? `${prefix}/${e.name}` : e.name);
|
|
8372
8503
|
else if (e.name.endsWith(".md")) out2.push(prefix ? `${prefix}/${e.name.slice(0, -3)}` : e.name.slice(0, -3));
|
|
8373
8504
|
}
|
|
8374
8505
|
};
|
|
@@ -8384,8 +8515,8 @@ var init_memory = __esm({
|
|
|
8384
8515
|
});
|
|
8385
8516
|
|
|
8386
8517
|
// src/workspaces.ts
|
|
8387
|
-
import { existsSync as
|
|
8388
|
-
import { join as
|
|
8518
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
8519
|
+
import { join as join13 } from "path";
|
|
8389
8520
|
function readJson(path, label, warnings) {
|
|
8390
8521
|
const raw = readText(path);
|
|
8391
8522
|
if (!raw) return void 0;
|
|
@@ -8436,8 +8567,8 @@ function wsGlobToRegExp(pat) {
|
|
|
8436
8567
|
return new RegExp(`^${re}($|/)`);
|
|
8437
8568
|
}
|
|
8438
8569
|
function probeNodePkg(root, dir, kind, warnings) {
|
|
8439
|
-
const path =
|
|
8440
|
-
if (!
|
|
8570
|
+
const path = join13(root, dir, "package.json");
|
|
8571
|
+
if (!existsSync4(path)) return void 0;
|
|
8441
8572
|
const manifest = `${dir}/package.json`;
|
|
8442
8573
|
const pkg = readJson(path, manifest, warnings);
|
|
8443
8574
|
const out2 = {
|
|
@@ -8450,8 +8581,8 @@ function probeNodePkg(root, dir, kind, warnings) {
|
|
|
8450
8581
|
return out2;
|
|
8451
8582
|
}
|
|
8452
8583
|
function probeCargo(root, dir) {
|
|
8453
|
-
const path =
|
|
8454
|
-
if (!
|
|
8584
|
+
const path = join13(root, dir, "Cargo.toml");
|
|
8585
|
+
if (!existsSync4(path)) return void 0;
|
|
8455
8586
|
const body2 = tomlSectionBody(readText(path), "package");
|
|
8456
8587
|
const out2 = {
|
|
8457
8588
|
name: tomlString(body2, "name") ?? dir,
|
|
@@ -8464,19 +8595,19 @@ function probeCargo(root, dir) {
|
|
|
8464
8595
|
return out2;
|
|
8465
8596
|
}
|
|
8466
8597
|
function probeGoMod(root, dir) {
|
|
8467
|
-
const path =
|
|
8468
|
-
if (!
|
|
8598
|
+
const path = join13(root, dir, "go.mod");
|
|
8599
|
+
if (!existsSync4(path)) return void 0;
|
|
8469
8600
|
const name2 = readText(path).match(/^module\s+(\S+)/m)?.[1] ?? dir;
|
|
8470
8601
|
return { name: name2, dir, kind: "go", manifest: `${dir}/go.mod` };
|
|
8471
8602
|
}
|
|
8472
8603
|
function probeMaven(root, dir) {
|
|
8473
|
-
const path =
|
|
8474
|
-
if (!
|
|
8604
|
+
const path = join13(root, dir, "pom.xml");
|
|
8605
|
+
if (!existsSync4(path)) return void 0;
|
|
8475
8606
|
return { name: ownArtifactId(readText(path)) ?? dir, dir, kind: "maven", manifest: `${dir}/pom.xml` };
|
|
8476
8607
|
}
|
|
8477
8608
|
function probePyproject(root, dir) {
|
|
8478
|
-
const path =
|
|
8479
|
-
if (!
|
|
8609
|
+
const path = join13(root, dir, "pyproject.toml");
|
|
8610
|
+
if (!existsSync4(path)) return void 0;
|
|
8480
8611
|
const toml = readText(path);
|
|
8481
8612
|
const project = tomlSectionBody(toml, "project");
|
|
8482
8613
|
const poetry = tomlSectionBody(toml, "tool.poetry");
|
|
@@ -8491,8 +8622,8 @@ function probePyproject(root, dir) {
|
|
|
8491
8622
|
return out2;
|
|
8492
8623
|
}
|
|
8493
8624
|
function probeComposer(root, dir, warnings) {
|
|
8494
|
-
const path =
|
|
8495
|
-
if (!
|
|
8625
|
+
const path = join13(root, dir, "composer.json");
|
|
8626
|
+
if (!existsSync4(path)) return void 0;
|
|
8496
8627
|
const manifest = `${dir}/composer.json`;
|
|
8497
8628
|
const pkg = readJson(path, manifest, warnings);
|
|
8498
8629
|
const out2 = {
|
|
@@ -8505,8 +8636,8 @@ function probeComposer(root, dir, warnings) {
|
|
|
8505
8636
|
return out2;
|
|
8506
8637
|
}
|
|
8507
8638
|
function probeNxProject(root, dir, warnings) {
|
|
8508
|
-
const path =
|
|
8509
|
-
if (!
|
|
8639
|
+
const path = join13(root, dir, "project.json");
|
|
8640
|
+
if (!existsSync4(path)) return void 0;
|
|
8510
8641
|
const manifest = `${dir}/project.json`;
|
|
8511
8642
|
const proj = readJson(path, manifest, warnings);
|
|
8512
8643
|
return {
|
|
@@ -8518,7 +8649,7 @@ function probeNxProject(root, dir, warnings) {
|
|
|
8518
8649
|
}
|
|
8519
8650
|
function probeGradle(root, dir) {
|
|
8520
8651
|
for (const f of ["build.gradle", "build.gradle.kts"]) {
|
|
8521
|
-
if (
|
|
8652
|
+
if (existsSync4(join13(root, dir, f))) {
|
|
8522
8653
|
return { name: dir, dir, kind: "gradle", manifest: `${dir}/${f}` };
|
|
8523
8654
|
}
|
|
8524
8655
|
}
|
|
@@ -8553,7 +8684,7 @@ function addPackage(root, dir, found, kind, warnings) {
|
|
|
8553
8684
|
}
|
|
8554
8685
|
function isDirAt(root, rel) {
|
|
8555
8686
|
try {
|
|
8556
|
-
return
|
|
8687
|
+
return statSync4(join13(root, rel)).isDirectory();
|
|
8557
8688
|
} catch {
|
|
8558
8689
|
return false;
|
|
8559
8690
|
}
|
|
@@ -8561,7 +8692,7 @@ function isDirAt(root, rel) {
|
|
|
8561
8692
|
function subdirsOf(root, base) {
|
|
8562
8693
|
let entries;
|
|
8563
8694
|
try {
|
|
8564
|
-
entries = readdirSync3(base ?
|
|
8695
|
+
entries = readdirSync3(base ? join13(root, base) : root, { withFileTypes: true });
|
|
8565
8696
|
} catch {
|
|
8566
8697
|
return [];
|
|
8567
8698
|
}
|
|
@@ -8623,14 +8754,14 @@ function npmFamilyPatterns(root, warnings) {
|
|
|
8623
8754
|
if (t.startsWith("!")) negations.push(t.slice(1));
|
|
8624
8755
|
else positives.push({ pattern: t, kind });
|
|
8625
8756
|
};
|
|
8626
|
-
const pkg = readJson(
|
|
8757
|
+
const pkg = readJson(join13(root, "package.json"), "package.json", warnings);
|
|
8627
8758
|
const ws = pkg?.workspaces;
|
|
8628
8759
|
if (Array.isArray(ws)) {
|
|
8629
8760
|
for (const x of ws) if (typeof x === "string") push(x, "npm");
|
|
8630
8761
|
} else if (ws && typeof ws === "object" && Array.isArray(ws.packages)) {
|
|
8631
8762
|
for (const x of ws.packages) if (typeof x === "string") push(x, "npm");
|
|
8632
8763
|
}
|
|
8633
|
-
const pnpm = readText(
|
|
8764
|
+
const pnpm = readText(join13(root, "pnpm-workspace.yaml"));
|
|
8634
8765
|
let inPackages = false;
|
|
8635
8766
|
for (const line of pnpm.split(/\r?\n/)) {
|
|
8636
8767
|
if (/^\S/.test(line)) {
|
|
@@ -8644,11 +8775,11 @@ function npmFamilyPatterns(root, warnings) {
|
|
|
8644
8775
|
return { positives, negations };
|
|
8645
8776
|
}
|
|
8646
8777
|
function fallbackNpmPatterns(root, warnings) {
|
|
8647
|
-
const lerna = readJson(
|
|
8778
|
+
const lerna = readJson(join13(root, "lerna.json"), "lerna.json", warnings);
|
|
8648
8779
|
if (lerna && Array.isArray(lerna.packages)) {
|
|
8649
8780
|
return lerna.packages.filter((x) => typeof x === "string").map((pattern) => ({ pattern, kind: "lerna" }));
|
|
8650
8781
|
}
|
|
8651
|
-
const nx = readJson(
|
|
8782
|
+
const nx = readJson(join13(root, "nx.json"), "nx.json", warnings);
|
|
8652
8783
|
if (nx) {
|
|
8653
8784
|
const layout = nx.workspaceLayout ?? {};
|
|
8654
8785
|
const appsDir = typeof layout.appsDir === "string" ? layout.appsDir : "apps";
|
|
@@ -8658,7 +8789,7 @@ function fallbackNpmPatterns(root, warnings) {
|
|
|
8658
8789
|
return [];
|
|
8659
8790
|
}
|
|
8660
8791
|
function detectCargoMembers(root, found, warnings) {
|
|
8661
|
-
const toml = readText(
|
|
8792
|
+
const toml = readText(join13(root, "Cargo.toml"));
|
|
8662
8793
|
if (!toml) return;
|
|
8663
8794
|
const body2 = tomlSectionBody(toml, "workspace");
|
|
8664
8795
|
if (!body2) return;
|
|
@@ -8673,7 +8804,7 @@ function detectCargoMembers(root, found, warnings) {
|
|
|
8673
8804
|
}
|
|
8674
8805
|
}
|
|
8675
8806
|
function detectGoWork(root, found, warnings) {
|
|
8676
|
-
const gowork = readText(
|
|
8807
|
+
const gowork = readText(join13(root, "go.work"));
|
|
8677
8808
|
if (!gowork) return;
|
|
8678
8809
|
const dirs = [];
|
|
8679
8810
|
for (const block of gowork.matchAll(/^use\s*\(([\s\S]*?)\)/gm)) {
|
|
@@ -8689,7 +8820,7 @@ function detectGoWork(root, found, warnings) {
|
|
|
8689
8820
|
}
|
|
8690
8821
|
}
|
|
8691
8822
|
function detectMavenModules(root, found, warnings) {
|
|
8692
|
-
const pom = readText(
|
|
8823
|
+
const pom = readText(join13(root, "pom.xml"));
|
|
8693
8824
|
if (!pom) return;
|
|
8694
8825
|
const modules = pom.match(/<modules>([\s\S]*?)<\/modules>/)?.[1];
|
|
8695
8826
|
if (!modules) return;
|
|
@@ -8698,7 +8829,7 @@ function detectMavenModules(root, found, warnings) {
|
|
|
8698
8829
|
}
|
|
8699
8830
|
}
|
|
8700
8831
|
function detectUvMembers(root, found, warnings) {
|
|
8701
|
-
const toml = readText(
|
|
8832
|
+
const toml = readText(join13(root, "pyproject.toml"));
|
|
8702
8833
|
if (!toml) return;
|
|
8703
8834
|
const body2 = tomlSectionBody(toml, "tool.uv.workspace");
|
|
8704
8835
|
if (!body2) return;
|
|
@@ -8713,7 +8844,7 @@ function detectUvMembers(root, found, warnings) {
|
|
|
8713
8844
|
}
|
|
8714
8845
|
}
|
|
8715
8846
|
function detectComposerPathRepos(root, found, warnings) {
|
|
8716
|
-
const composer = readJson(
|
|
8847
|
+
const composer = readJson(join13(root, "composer.json"), "composer.json", warnings);
|
|
8717
8848
|
const repos = composer?.repositories;
|
|
8718
8849
|
if (!Array.isArray(repos)) return;
|
|
8719
8850
|
for (const r of repos) {
|
|
@@ -8724,7 +8855,7 @@ function detectComposerPathRepos(root, found, warnings) {
|
|
|
8724
8855
|
}
|
|
8725
8856
|
function detectGradleIncludes(root, found, warnings) {
|
|
8726
8857
|
for (const f of ["settings.gradle", "settings.gradle.kts"]) {
|
|
8727
|
-
const text = readText(
|
|
8858
|
+
const text = readText(join13(root, f));
|
|
8728
8859
|
if (!text) continue;
|
|
8729
8860
|
for (const line of text.split(/\r?\n/)) {
|
|
8730
8861
|
if (!/^\s*include[\s(]/.test(line)) continue;
|
|
@@ -8736,7 +8867,7 @@ function detectGradleIncludes(root, found, warnings) {
|
|
|
8736
8867
|
}
|
|
8737
8868
|
}
|
|
8738
8869
|
function npmEdges(root, pkg, byName, warnings) {
|
|
8739
|
-
const manifest = readJson(
|
|
8870
|
+
const manifest = readJson(join13(root, pkg.dir, "package.json"), `${pkg.dir}/package.json`, warnings);
|
|
8740
8871
|
if (!manifest) return [];
|
|
8741
8872
|
const edges = /* @__PURE__ */ new Set();
|
|
8742
8873
|
for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
@@ -8759,7 +8890,7 @@ function normalizeDepPath(fromDir, rel) {
|
|
|
8759
8890
|
return out2.join("/");
|
|
8760
8891
|
}
|
|
8761
8892
|
function cargoEdges(root, pkg, byName, byDir) {
|
|
8762
|
-
const toml = readText(
|
|
8893
|
+
const toml = readText(join13(root, pkg.dir, "Cargo.toml"));
|
|
8763
8894
|
if (!toml) return [];
|
|
8764
8895
|
const edges = /* @__PURE__ */ new Set();
|
|
8765
8896
|
for (const section of ["dependencies", "dev-dependencies", "build-dependencies"]) {
|
|
@@ -8783,7 +8914,7 @@ function cargoEdges(root, pkg, byName, byDir) {
|
|
|
8783
8914
|
return [...edges];
|
|
8784
8915
|
}
|
|
8785
8916
|
function goPkgEdges(root, pkg, byName, byDir) {
|
|
8786
|
-
const gomod = readText(
|
|
8917
|
+
const gomod = readText(join13(root, pkg.dir, "go.mod"));
|
|
8787
8918
|
if (!gomod) return [];
|
|
8788
8919
|
const edges = /* @__PURE__ */ new Set();
|
|
8789
8920
|
for (const m of gomod.matchAll(/^\s*(?:require\s+)?([^\s/(][^\s]*)\s+v[^\s]+/gm)) {
|
|
@@ -8797,7 +8928,7 @@ function goPkgEdges(root, pkg, byName, byDir) {
|
|
|
8797
8928
|
return [...edges];
|
|
8798
8929
|
}
|
|
8799
8930
|
function mavenEdges(root, pkg, byName) {
|
|
8800
|
-
const pom = readText(
|
|
8931
|
+
const pom = readText(join13(root, pkg.dir, "pom.xml"));
|
|
8801
8932
|
if (!pom) return [];
|
|
8802
8933
|
const edges = /* @__PURE__ */ new Set();
|
|
8803
8934
|
for (const m of pom.replace(/<parent>[\s\S]*?<\/parent>/g, "").matchAll(/<dependency>([\s\S]*?)<\/dependency>/g)) {
|
|
@@ -8807,7 +8938,7 @@ function mavenEdges(root, pkg, byName) {
|
|
|
8807
8938
|
return [...edges];
|
|
8808
8939
|
}
|
|
8809
8940
|
function uvEdges(root, pkg, byName) {
|
|
8810
|
-
const toml = readText(
|
|
8941
|
+
const toml = readText(join13(root, pkg.dir, "pyproject.toml"));
|
|
8811
8942
|
if (!toml) return [];
|
|
8812
8943
|
const edges = /* @__PURE__ */ new Set();
|
|
8813
8944
|
const project = tomlSectionBody(toml, "project");
|
|
@@ -8827,7 +8958,7 @@ function uvEdges(root, pkg, byName) {
|
|
|
8827
8958
|
return [...edges];
|
|
8828
8959
|
}
|
|
8829
8960
|
function composerEdges(root, pkg, byName, warnings) {
|
|
8830
|
-
const manifest = readJson(
|
|
8961
|
+
const manifest = readJson(join13(root, pkg.dir, "composer.json"), `${pkg.dir}/composer.json`, warnings);
|
|
8831
8962
|
if (!manifest) return [];
|
|
8832
8963
|
const edges = /* @__PURE__ */ new Set();
|
|
8833
8964
|
for (const field of ["require", "require-dev"]) {
|
|
@@ -8841,7 +8972,7 @@ function composerEdges(root, pkg, byName, warnings) {
|
|
|
8841
8972
|
}
|
|
8842
8973
|
function gradleEdges(root, pkg, byName, byDir) {
|
|
8843
8974
|
for (const f of ["build.gradle", "build.gradle.kts"]) {
|
|
8844
|
-
const text = readText(
|
|
8975
|
+
const text = readText(join13(root, pkg.dir, f));
|
|
8845
8976
|
if (!text) continue;
|
|
8846
8977
|
const edges = /* @__PURE__ */ new Set();
|
|
8847
8978
|
for (const m of text.matchAll(/project\s*\(\s*["']:?([^"']+)["']\s*\)/g)) {
|
|
@@ -9565,16 +9696,16 @@ var init_grep = __esm({
|
|
|
9565
9696
|
|
|
9566
9697
|
// src/embed/model.ts
|
|
9567
9698
|
import { createHash as createHash3 } from "crypto";
|
|
9568
|
-
import { existsSync as
|
|
9569
|
-
import { join as
|
|
9699
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
9700
|
+
import { join as join15 } from "path";
|
|
9570
9701
|
function resolveEmbedModelDir(repo) {
|
|
9571
9702
|
const env = process.env.CODEINDEX_EMBED_DIR;
|
|
9572
9703
|
const candidates = [];
|
|
9573
9704
|
if (env) candidates.push(env);
|
|
9574
|
-
if (repo) candidates.push(
|
|
9575
|
-
candidates.push(
|
|
9705
|
+
if (repo) candidates.push(join15(repo, ".codeindex", DEFAULT_EMBED_DIRNAME));
|
|
9706
|
+
candidates.push(join15(process.cwd(), ".codeindex", DEFAULT_EMBED_DIRNAME));
|
|
9576
9707
|
for (const c2 of candidates) {
|
|
9577
|
-
if (
|
|
9708
|
+
if (existsSync5(join15(c2, "model.json"))) return c2;
|
|
9578
9709
|
}
|
|
9579
9710
|
return void 0;
|
|
9580
9711
|
}
|
|
@@ -9607,9 +9738,9 @@ function parseEmbedModel(raw, source) {
|
|
|
9607
9738
|
}
|
|
9608
9739
|
function loadEmbedModel(dir) {
|
|
9609
9740
|
if (!dir) return void 0;
|
|
9610
|
-
const path =
|
|
9611
|
-
if (!
|
|
9612
|
-
const raw = JSON.parse(
|
|
9741
|
+
const path = join15(dir, "model.json");
|
|
9742
|
+
if (!existsSync5(path)) return void 0;
|
|
9743
|
+
const raw = JSON.parse(readFileSync7(path, "utf8"));
|
|
9613
9744
|
return parseEmbedModel(raw, path);
|
|
9614
9745
|
}
|
|
9615
9746
|
function resolveEmbedPullUrl() {
|
|
@@ -10153,7 +10284,7 @@ function changeCoupling(dir, opts = {}) {
|
|
|
10153
10284
|
for (const f of unique) totals.set(f, (totals.get(f) ?? 0) + 1);
|
|
10154
10285
|
for (let i2 = 0; i2 < unique.length; i2++) {
|
|
10155
10286
|
for (let j = i2 + 1; j < unique.length; j++) {
|
|
10156
|
-
const key = `${unique[i2]}
|
|
10287
|
+
const key = `${unique[i2]}${SEP2}${unique[j]}`;
|
|
10157
10288
|
pairs.set(key, (pairs.get(key) ?? 0) + 1);
|
|
10158
10289
|
}
|
|
10159
10290
|
}
|
|
@@ -10161,7 +10292,7 @@ function changeCoupling(dir, opts = {}) {
|
|
|
10161
10292
|
const out2 = [];
|
|
10162
10293
|
for (const [key, together] of pairs) {
|
|
10163
10294
|
if (together < minTogether) continue;
|
|
10164
|
-
const [a, b] = key.split(
|
|
10295
|
+
const [a, b] = key.split(SEP2);
|
|
10165
10296
|
const totalA = totals.get(a) ?? together;
|
|
10166
10297
|
const totalB = totals.get(b) ?? together;
|
|
10167
10298
|
out2.push({ a, b, together, totalA, totalB, strength: Number((together / Math.min(totalA, totalB)).toFixed(3)) });
|
|
@@ -10177,11 +10308,13 @@ function rankHotspots(scan2, churn, top = 20) {
|
|
|
10177
10308
|
out2.sort((a, b) => b.score - a.score || b.lines - a.lines || byStr(a.rel, b.rel));
|
|
10178
10309
|
return out2.slice(0, top);
|
|
10179
10310
|
}
|
|
10311
|
+
var SEP2;
|
|
10180
10312
|
var init_coupling = __esm({
|
|
10181
10313
|
"src/coupling.ts"() {
|
|
10182
10314
|
"use strict";
|
|
10183
10315
|
init_util();
|
|
10184
10316
|
init_sort();
|
|
10317
|
+
SEP2 = "\0";
|
|
10185
10318
|
}
|
|
10186
10319
|
});
|
|
10187
10320
|
|
|
@@ -10282,29 +10415,78 @@ function renderMermaid(graph, opts = {}) {
|
|
|
10282
10415
|
if (dropped) lines.push(` %% ${dropped} lighter edges omitted (maxEdges=${maxEdges})`);
|
|
10283
10416
|
return lines.join("\n") + "\n";
|
|
10284
10417
|
}
|
|
10285
|
-
|
|
10418
|
+
function clusterNodeId(slug) {
|
|
10419
|
+
return "m_" + slug.replace(/[^A-Za-z0-9_]/g, "_");
|
|
10420
|
+
}
|
|
10421
|
+
function renderMermaidClustered(graph, opts = {}) {
|
|
10422
|
+
const maxModules = opts.maxModules ?? CLUSTER_MAX_MODULES;
|
|
10423
|
+
const maxEdges = opts.maxEdges ?? CLUSTER_MAX_EDGES;
|
|
10424
|
+
const ranked = graph.modules.slice().sort((a, b) => degreeOf(b) - degreeOf(a) || byStr(a.slug, b.slug));
|
|
10425
|
+
const shown = ranked.slice(0, maxModules);
|
|
10426
|
+
const shownSet = new Set(shown.map((m) => m.slug));
|
|
10427
|
+
const edges = graph.moduleEdges.filter((e) => shownSet.has(e.from) && shownSet.has(e.to)).sort((a, b) => b.weight - a.weight || byStr(a.from, b.from) || byStr(a.to, b.to)).slice(0, maxEdges);
|
|
10428
|
+
const lines = [];
|
|
10429
|
+
lines.push(
|
|
10430
|
+
`%% ${opts.title ?? "module graph"} \u2014 ${shown.length} of ${graph.modules.length} modules, ${edges.length} of ${graph.moduleEdges.length} edges`
|
|
10431
|
+
);
|
|
10432
|
+
if (shown.length < graph.modules.length || edges.length < graph.moduleEdges.length) {
|
|
10433
|
+
lines.push("%% truncated to the most-connected modules/edges; see graph.json for the full graph");
|
|
10434
|
+
}
|
|
10435
|
+
lines.push("flowchart LR");
|
|
10436
|
+
for (const tier of [0, 1, 2]) {
|
|
10437
|
+
const inTier = shown.filter((m) => m.tier === tier);
|
|
10438
|
+
if (!inTier.length) continue;
|
|
10439
|
+
lines.push(` subgraph ${TIER_LABEL[tier]}`);
|
|
10440
|
+
for (const m of inTier) lines.push(` ${clusterNodeId(m.slug)}["${m.path.replace(/"/g, "'")}"]`);
|
|
10441
|
+
lines.push(" end");
|
|
10442
|
+
}
|
|
10443
|
+
for (const e of edges) {
|
|
10444
|
+
const label = e.weight > 1 ? `|${e.weight}| ` : "";
|
|
10445
|
+
lines.push(` ${clusterNodeId(e.from)} -->${label ? " " + label : " "}${clusterNodeId(e.to)}`);
|
|
10446
|
+
}
|
|
10447
|
+
return {
|
|
10448
|
+
content: "```mermaid\n" + lines.join("\n") + "\n```\n",
|
|
10449
|
+
shownModules: shown.length,
|
|
10450
|
+
totalModules: graph.modules.length,
|
|
10451
|
+
shownEdges: edges.length,
|
|
10452
|
+
totalEdges: graph.moduleEdges.length
|
|
10453
|
+
};
|
|
10454
|
+
}
|
|
10455
|
+
var sanitizeId, TIER_LABEL, CLUSTER_MAX_MODULES, CLUSTER_MAX_EDGES, degreeOf;
|
|
10286
10456
|
var init_viz = __esm({
|
|
10287
10457
|
"src/viz.ts"() {
|
|
10288
10458
|
"use strict";
|
|
10289
10459
|
init_sort();
|
|
10290
10460
|
sanitizeId = (slug) => slug.replace(/[^\w]/g, "_");
|
|
10461
|
+
TIER_LABEL = { 0: "Foundations", 1: "Features", 2: "Tail" };
|
|
10462
|
+
CLUSTER_MAX_MODULES = 40;
|
|
10463
|
+
CLUSTER_MAX_EDGES = 80;
|
|
10464
|
+
degreeOf = (m) => m.degIn + m.degOut;
|
|
10291
10465
|
}
|
|
10292
10466
|
});
|
|
10293
10467
|
|
|
10294
10468
|
// src/mcp.ts
|
|
10295
10469
|
var mcp_exports = {};
|
|
10296
10470
|
__export(mcp_exports, {
|
|
10471
|
+
DEFAULT_MAX_RESPONSE_BYTES: () => DEFAULT_MAX_RESPONSE_BYTES,
|
|
10472
|
+
capResponse: () => capResponse,
|
|
10297
10473
|
getArtifacts: () => getArtifacts,
|
|
10298
10474
|
getScan: () => getScan,
|
|
10475
|
+
getScanSummary: () => getScanSummary,
|
|
10299
10476
|
memoizedEmbedModel: () => memoizedEmbedModel,
|
|
10300
10477
|
memoizedEmbeddingIndex: () => memoizedEmbeddingIndex,
|
|
10478
|
+
negotiateProtocol: () => negotiateProtocol,
|
|
10479
|
+
resourceLinkFor: () => resourceLinkFor,
|
|
10301
10480
|
runMcpServer: () => runMcpServer,
|
|
10302
10481
|
scanFingerprint: () => scanFingerprint,
|
|
10303
10482
|
toCacheMap: () => toCacheMap,
|
|
10304
|
-
|
|
10483
|
+
validateArgs: () => validateArgs,
|
|
10484
|
+
warmGrammarsForRepo: () => warmGrammarsForRepo,
|
|
10485
|
+
warmGrammarsForWalk: () => warmGrammarsForWalk
|
|
10305
10486
|
});
|
|
10306
|
-
import { readFileSync as
|
|
10307
|
-
import { join as
|
|
10487
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
10488
|
+
import { isAbsolute, join as join16 } from "path";
|
|
10489
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
10308
10490
|
import { createInterface } from "readline";
|
|
10309
10491
|
function str(v) {
|
|
10310
10492
|
return typeof v === "string" && v ? v : void 0;
|
|
@@ -10312,6 +10494,10 @@ function str(v) {
|
|
|
10312
10494
|
function strArray(v) {
|
|
10313
10495
|
return Array.isArray(v) && v.every((x) => typeof x === "string") && v.length ? v : void 0;
|
|
10314
10496
|
}
|
|
10497
|
+
function num(v) {
|
|
10498
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
10499
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
10500
|
+
}
|
|
10315
10501
|
function errMessage(e) {
|
|
10316
10502
|
return e instanceof Error ? e.message : String(e);
|
|
10317
10503
|
}
|
|
@@ -10328,7 +10514,7 @@ async function memoizedEmbeddingIndex(key, build) {
|
|
|
10328
10514
|
function memoizedEmbedModel(modelDir) {
|
|
10329
10515
|
let stat;
|
|
10330
10516
|
try {
|
|
10331
|
-
stat =
|
|
10517
|
+
stat = statSync5(join16(modelDir, "model.json"));
|
|
10332
10518
|
} catch {
|
|
10333
10519
|
return void 0;
|
|
10334
10520
|
}
|
|
@@ -10338,6 +10524,23 @@ function memoizedEmbedModel(modelDir) {
|
|
|
10338
10524
|
if (model) embedModelCache = { key, model };
|
|
10339
10525
|
return model;
|
|
10340
10526
|
}
|
|
10527
|
+
function sessionGet(key) {
|
|
10528
|
+
const i2 = sessionCaches.findIndex((e) => e.key === key);
|
|
10529
|
+
if (i2 < 0) return void 0;
|
|
10530
|
+
const [entry] = sessionCaches.splice(i2, 1);
|
|
10531
|
+
sessionCaches.unshift(entry);
|
|
10532
|
+
return entry;
|
|
10533
|
+
}
|
|
10534
|
+
function sessionPut(entry) {
|
|
10535
|
+
const i2 = sessionCaches.findIndex((e) => e.key === entry.key);
|
|
10536
|
+
if (i2 >= 0) sessionCaches.splice(i2, 1);
|
|
10537
|
+
sessionCaches.unshift(entry);
|
|
10538
|
+
sessionCaches.length = Math.min(sessionCaches.length, SESSION_CACHE_MAX);
|
|
10539
|
+
return entry;
|
|
10540
|
+
}
|
|
10541
|
+
function sessionClear() {
|
|
10542
|
+
sessionCaches.length = 0;
|
|
10543
|
+
}
|
|
10341
10544
|
function sessionKey(repo, opts) {
|
|
10342
10545
|
return repo + "\0" + JSON.stringify({
|
|
10343
10546
|
scope: opts.scope,
|
|
@@ -10352,112 +10555,76 @@ function sessionKey(repo, opts) {
|
|
|
10352
10555
|
fullHash: opts.fullHash
|
|
10353
10556
|
});
|
|
10354
10557
|
}
|
|
10355
|
-
function
|
|
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 readPersistedIndex(repo) {
|
|
10361
|
-
let parsed;
|
|
10362
|
-
try {
|
|
10363
|
-
parsed = JSON.parse(readFileSync7(join14(repo, ".codeindex", "cache.json"), "utf8"));
|
|
10364
|
-
} catch {
|
|
10365
|
-
return void 0;
|
|
10366
|
-
}
|
|
10367
|
-
if (!parsed || parsed.schemaVersion !== SCHEMA_VERSION || parsed.extractorVersion !== EXTRACTOR_VERSION || !parsed.files) {
|
|
10368
|
-
return void 0;
|
|
10369
|
-
}
|
|
10370
|
-
const cacheMap = new Map(Object.entries(parsed.files));
|
|
10371
|
-
const meta = {
|
|
10372
|
-
engineVersion: parsed.engineVersion,
|
|
10373
|
-
commit: parsed.commit,
|
|
10374
|
-
graphSha1: parsed.graphSha1,
|
|
10375
|
-
symbolsSha1: parsed.symbolsSha1
|
|
10376
|
-
};
|
|
10377
|
-
return { cacheMap, meta };
|
|
10378
|
-
}
|
|
10379
|
-
function preloadArtifacts(repo, scan2, meta) {
|
|
10380
|
-
if (!scan2.contentUnchanged || meta.engineVersion !== ENGINE_VERSION || meta.commit !== scan2.commit || meta.graphSha1 === void 0 || meta.symbolsSha1 === void 0) {
|
|
10381
|
-
return void 0;
|
|
10382
|
-
}
|
|
10383
|
-
const dir = join14(repo, ".codeindex");
|
|
10384
|
-
let graphBytes;
|
|
10385
|
-
let symbolsBytes;
|
|
10386
|
-
try {
|
|
10387
|
-
graphBytes = readFileSync7(join14(dir, "graph.json"));
|
|
10388
|
-
symbolsBytes = readFileSync7(join14(dir, "symbols.json"));
|
|
10389
|
-
} catch {
|
|
10390
|
-
return void 0;
|
|
10391
|
-
}
|
|
10392
|
-
if (sha1(graphBytes) !== meta.graphSha1 || sha1(symbolsBytes) !== meta.symbolsSha1) {
|
|
10393
|
-
return void 0;
|
|
10394
|
-
}
|
|
10395
|
-
try {
|
|
10396
|
-
const graph = JSON.parse(graphBytes.toString("utf8"));
|
|
10397
|
-
const symbols = JSON.parse(symbolsBytes.toString("utf8"));
|
|
10398
|
-
if (graph.schemaVersion !== SCHEMA_VERSION || symbols.schemaVersion !== SCHEMA_VERSION) return void 0;
|
|
10399
|
-
return { scan: scan2, graph, symbols };
|
|
10400
|
-
} catch {
|
|
10401
|
-
return void 0;
|
|
10402
|
-
}
|
|
10403
|
-
}
|
|
10404
|
-
function preloadSession(repo, opts) {
|
|
10405
|
-
const persisted = readPersistedIndex(repo);
|
|
10406
|
-
if (!persisted) return void 0;
|
|
10407
|
-
const scan2 = scanRepo(repo, { ...opts, cache: persisted.cacheMap });
|
|
10408
|
-
const arts = preloadArtifacts(repo, scan2, persisted.meta);
|
|
10409
|
-
return { scan: scan2, cacheMap: toCacheMap(scan2), arts };
|
|
10410
|
-
}
|
|
10411
|
-
function getScan(repo, opts = {}) {
|
|
10558
|
+
function getScan(repo, opts = {}, walked) {
|
|
10412
10559
|
const key = sessionKey(repo, opts);
|
|
10413
|
-
|
|
10414
|
-
|
|
10560
|
+
const hit = sessionGet(key);
|
|
10561
|
+
if (hit) {
|
|
10562
|
+
const fresh = scanRepo(repo, { ...opts, cache: hit.cacheMap, precomputedWalk: walked });
|
|
10415
10563
|
if (fresh.contentUnchanged) {
|
|
10416
|
-
if (fresh.cacheDirty)
|
|
10417
|
-
if (
|
|
10418
|
-
return
|
|
10564
|
+
if (fresh.cacheDirty) hit.cacheMap = toCacheMap(fresh);
|
|
10565
|
+
if (hit.scan.commit !== fresh.commit) hit.scan.commit = fresh.commit;
|
|
10566
|
+
return hit.scan;
|
|
10419
10567
|
}
|
|
10420
|
-
|
|
10568
|
+
sessionPut({ key, scan: fresh, cacheMap: toCacheMap(fresh) });
|
|
10421
10569
|
return fresh;
|
|
10422
10570
|
}
|
|
10423
|
-
const preloaded = preloadSession(repo, opts);
|
|
10571
|
+
const preloaded = preloadSession(repo, { ...opts, precomputedWalk: walked });
|
|
10424
10572
|
if (preloaded) {
|
|
10425
|
-
|
|
10573
|
+
sessionPut({ key, scan: preloaded.scan, cacheMap: preloaded.cacheMap, arts: preloaded.arts });
|
|
10426
10574
|
return preloaded.scan;
|
|
10427
10575
|
}
|
|
10428
|
-
const scan2 = scanRepo(repo, opts);
|
|
10429
|
-
|
|
10576
|
+
const scan2 = scanRepo(repo, { ...opts, precomputedWalk: walked });
|
|
10577
|
+
sessionPut({ key, scan: scan2, cacheMap: toCacheMap(scan2) });
|
|
10430
10578
|
return scan2;
|
|
10431
10579
|
}
|
|
10432
|
-
function
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
return
|
|
10580
|
+
function getScanSummary(repo, opts = {}, walked) {
|
|
10581
|
+
if (sessionCaches.some((e) => e.key === sessionKey(repo, opts))) {
|
|
10582
|
+
const scan2 = getScan(repo, opts, walked);
|
|
10583
|
+
return {
|
|
10584
|
+
root: scan2.root,
|
|
10585
|
+
commit: scan2.commit,
|
|
10586
|
+
fileCount: scan2.files.length,
|
|
10587
|
+
languages: scan2.languages,
|
|
10588
|
+
capped: scan2.capped,
|
|
10589
|
+
excluded: scan2.excluded
|
|
10590
|
+
};
|
|
10436
10591
|
}
|
|
10592
|
+
return scanSummary(repo, { ...opts, precomputedWalk: walked });
|
|
10593
|
+
}
|
|
10594
|
+
function getArtifacts(repo, opts = {}, walked) {
|
|
10595
|
+
const scan2 = getScan(repo, opts, walked);
|
|
10596
|
+
const entry = sessionCaches.find((e) => e.scan === scan2);
|
|
10597
|
+
if (entry) return entry.arts ??= buildArtifactsFromScan(scan2, opts);
|
|
10437
10598
|
return buildArtifactsFromScan(scan2, opts);
|
|
10438
10599
|
}
|
|
10439
10600
|
async function warmGrammarsForRepo(repo) {
|
|
10440
|
-
|
|
10441
|
-
|
|
10601
|
+
await warmGrammarsForWalk(walk(repo, {}));
|
|
10602
|
+
}
|
|
10603
|
+
async function warmGrammarsForWalk(walked) {
|
|
10604
|
+
await ensureGrammars(grammarKeysForExts(walked.files.map((f) => f.ext)));
|
|
10442
10605
|
}
|
|
10443
10606
|
async function callTool(name2, args2, defaultRepo) {
|
|
10444
10607
|
const repo = str(args2.repo) ?? defaultRepo;
|
|
10445
10608
|
if (!repo) throw new Error("`repo` is required (absolute path to the repository root)");
|
|
10446
10609
|
const scanOpts = { scope: str(args2.scope), include: strArray(args2.include), exclude: strArray(args2.exclude) };
|
|
10447
|
-
|
|
10610
|
+
let walked;
|
|
10611
|
+
if (!SCANLESS_TOOLS.has(name2)) {
|
|
10612
|
+
walked = walk(repo, {});
|
|
10613
|
+
await warmGrammarsForWalk(walked);
|
|
10614
|
+
}
|
|
10448
10615
|
if (name2 === "scan_summary") {
|
|
10449
|
-
const
|
|
10616
|
+
const s = getScanSummary(repo, scanOpts, walked);
|
|
10450
10617
|
return JSON.stringify(
|
|
10451
|
-
{ engineVersion: ENGINE_VERSION, commit:
|
|
10618
|
+
{ engineVersion: ENGINE_VERSION, commit: s.commit, fileCount: s.fileCount, languages: s.languages, capped: s.capped },
|
|
10452
10619
|
null,
|
|
10453
10620
|
2
|
|
10454
10621
|
);
|
|
10455
10622
|
}
|
|
10456
10623
|
if (name2 === "graph") {
|
|
10457
|
-
return renderGraphJson(getArtifacts(repo, scanOpts).graph);
|
|
10624
|
+
return renderGraphJson(getArtifacts(repo, scanOpts, walked).graph);
|
|
10458
10625
|
}
|
|
10459
10626
|
if (name2 === "symbols") {
|
|
10460
|
-
const { symbols } = getArtifacts(repo, scanOpts);
|
|
10627
|
+
const { symbols } = getArtifacts(repo, scanOpts, walked);
|
|
10461
10628
|
const lookup = str(args2.name);
|
|
10462
10629
|
if (lookup) {
|
|
10463
10630
|
return JSON.stringify({ name: lookup, defs: symbols.defs[lookup] ?? [], refs: symbols.refs[lookup] ?? [] }, null, 2);
|
|
@@ -10465,7 +10632,8 @@ async function callTool(name2, args2, defaultRepo) {
|
|
|
10465
10632
|
return JSON.stringify(symbols, null, 2);
|
|
10466
10633
|
}
|
|
10467
10634
|
if (name2 === "callers") {
|
|
10468
|
-
const
|
|
10635
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
10636
|
+
const index = args2.recall === true ? buildCallerIndex(scan2, void 0, { recall: true }) : callerIndexFor(scan2);
|
|
10469
10637
|
const lookup = str(args2.name);
|
|
10470
10638
|
if (lookup) {
|
|
10471
10639
|
const entry = index.get(lookup);
|
|
@@ -10488,30 +10656,31 @@ async function callTool(name2, args2, defaultRepo) {
|
|
|
10488
10656
|
if (name2 === "symbols_overview") {
|
|
10489
10657
|
const file = str(args2.file);
|
|
10490
10658
|
if (!file) throw new Error("`file` is required");
|
|
10491
|
-
return JSON.stringify(symbolsOverview(getScan(repo, scanOpts), file), null, 2);
|
|
10659
|
+
return JSON.stringify(symbolsOverview(getScan(repo, scanOpts, walked), file), null, 2);
|
|
10492
10660
|
}
|
|
10493
10661
|
if (name2 === "find_symbol") {
|
|
10494
10662
|
const namePath = str(args2.namePath);
|
|
10495
10663
|
if (!namePath) throw new Error("`namePath` is required");
|
|
10496
|
-
const matches = findSymbol(getScan(repo, scanOpts), namePath, {
|
|
10664
|
+
const matches = findSymbol(getScan(repo, scanOpts, walked), namePath, {
|
|
10497
10665
|
substring: args2.substring === true,
|
|
10498
|
-
includeBody: args2.includeBody === true
|
|
10666
|
+
includeBody: args2.includeBody === true,
|
|
10667
|
+
maxResults: num(args2.maxResults)
|
|
10499
10668
|
});
|
|
10500
10669
|
return JSON.stringify(matches, null, 2);
|
|
10501
10670
|
}
|
|
10502
10671
|
if (name2 === "find_references") {
|
|
10503
10672
|
const symName = str(args2.name);
|
|
10504
10673
|
if (!symName) throw new Error("`name` is required");
|
|
10505
|
-
return JSON.stringify(findReferences(getScan(repo, scanOpts), symName), null, 2);
|
|
10674
|
+
return JSON.stringify(findReferences(getScan(repo, scanOpts, walked), symName), null, 2);
|
|
10506
10675
|
}
|
|
10507
10676
|
if (name2 === "replace_symbol_body" || name2 === "insert_after_symbol" || name2 === "insert_before_symbol") {
|
|
10508
10677
|
const namePath = str(args2.namePath);
|
|
10509
10678
|
const body2 = typeof args2.body === "string" ? args2.body : void 0;
|
|
10510
10679
|
if (!namePath || body2 === void 0) throw new Error("`namePath` and `body` are required");
|
|
10511
|
-
const scan2 = getScan(repo, scanOpts);
|
|
10680
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
10512
10681
|
const fn = name2 === "replace_symbol_body" ? replaceSymbolBody : name2 === "insert_after_symbol" ? insertAfterSymbol : insertBeforeSymbol;
|
|
10513
10682
|
const result = fn(scan2, namePath, body2, str(args2.file));
|
|
10514
|
-
|
|
10683
|
+
sessionClear();
|
|
10515
10684
|
return JSON.stringify(result, null, 2);
|
|
10516
10685
|
}
|
|
10517
10686
|
if (name2 === "write_memory") {
|
|
@@ -10536,26 +10705,29 @@ async function callTool(name2, args2, defaultRepo) {
|
|
|
10536
10705
|
return JSON.stringify({ deleted: deleteMemory(repo, memName) }, null, 2);
|
|
10537
10706
|
}
|
|
10538
10707
|
if (name2 === "dead_code") {
|
|
10539
|
-
|
|
10708
|
+
const all = findDeadCode(getScan(repo, scanOpts, walked));
|
|
10709
|
+
const limit = num(args2.limit);
|
|
10710
|
+
if (limit === void 0 || all.length <= limit) return JSON.stringify(all, null, 2);
|
|
10711
|
+
return JSON.stringify({ total: all.length, shown: limit, truncated: true, candidates: all.slice(0, limit) }, null, 2);
|
|
10540
10712
|
}
|
|
10541
10713
|
if (name2 === "complexity") {
|
|
10542
|
-
const scan2 = getScan(repo, scanOpts);
|
|
10714
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
10543
10715
|
if (args2.risk === true) {
|
|
10544
|
-
const { churn, ok } = gitChurn(repo);
|
|
10545
|
-
return JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn) }, null, 2);
|
|
10716
|
+
const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
|
|
10717
|
+
return JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn, num(args2.top)) }, null, 2);
|
|
10546
10718
|
}
|
|
10547
|
-
return JSON.stringify(symbolComplexity(scan2, str(args2.file)), null, 2);
|
|
10719
|
+
return JSON.stringify(symbolComplexity(scan2, str(args2.file), num(args2.top)), null, 2);
|
|
10548
10720
|
}
|
|
10549
10721
|
if (name2 === "mermaid") {
|
|
10550
|
-
const { graph } = getArtifacts(repo, scanOpts);
|
|
10551
|
-
return renderMermaid(graph, { module: str(args2.module) });
|
|
10722
|
+
const { graph } = getArtifacts(repo, scanOpts, walked);
|
|
10723
|
+
return renderMermaid(graph, { module: str(args2.module), maxEdges: num(args2.maxEdges) });
|
|
10552
10724
|
}
|
|
10553
10725
|
if (name2 === "repo_map") {
|
|
10554
|
-
const { scan: scan2, graph } = getArtifacts(repo, scanOpts);
|
|
10726
|
+
const { scan: scan2, graph } = getArtifacts(repo, scanOpts, walked);
|
|
10555
10727
|
return renderRepoMap(scan2, graph, { budgetTokens: typeof args2.budgetTokens === "number" ? args2.budgetTokens : void 0 });
|
|
10556
10728
|
}
|
|
10557
10729
|
if (name2 === "hotspots") {
|
|
10558
|
-
const scan2 = getScan(repo, scanOpts);
|
|
10730
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
10559
10731
|
const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
|
|
10560
10732
|
return JSON.stringify({ churnOk: ok, hotspots: rankHotspots(scan2, churn) }, null, 2);
|
|
10561
10733
|
}
|
|
@@ -10566,8 +10738,10 @@ async function callTool(name2, args2, defaultRepo) {
|
|
|
10566
10738
|
if (name2 === "grep") {
|
|
10567
10739
|
const pattern = str(args2.pattern);
|
|
10568
10740
|
if (!pattern) throw new Error("`pattern` is required");
|
|
10741
|
+
const scope = str(args2.scope);
|
|
10742
|
+
const globs = strArray(args2.globs);
|
|
10569
10743
|
const hits = grepRepo(repo, pattern, {
|
|
10570
|
-
globs:
|
|
10744
|
+
globs: scope ? [...globs ?? [], `${scope.replace(/\/+$/, "")}/**`] : globs,
|
|
10571
10745
|
ignoreCase: args2.ignoreCase === true,
|
|
10572
10746
|
maxHits: typeof args2.maxHits === "number" ? args2.maxHits : void 0
|
|
10573
10747
|
});
|
|
@@ -10576,7 +10750,7 @@ async function callTool(name2, args2, defaultRepo) {
|
|
|
10576
10750
|
if (name2 === "search") {
|
|
10577
10751
|
const query = str(args2.query);
|
|
10578
10752
|
if (!query) throw new Error("`query` is required");
|
|
10579
|
-
const scan2 = getScan(repo, scanOpts);
|
|
10753
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
10580
10754
|
const limit = typeof args2.limit === "number" ? args2.limit : void 0;
|
|
10581
10755
|
const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
|
|
10582
10756
|
if (args2.semantic === true) {
|
|
@@ -10630,32 +10804,122 @@ async function callTool(name2, args2, defaultRepo) {
|
|
|
10630
10804
|
return JSON.stringify(status, null, 2);
|
|
10631
10805
|
}
|
|
10632
10806
|
if (name2 === "check_rules") {
|
|
10633
|
-
const
|
|
10634
|
-
|
|
10807
|
+
const configPath = str(args2.configPath);
|
|
10808
|
+
let payload = args2.rules;
|
|
10809
|
+
if (payload === void 0 && configPath) {
|
|
10810
|
+
const abs = isAbsolute(configPath) ? configPath : join16(repo, configPath);
|
|
10811
|
+
try {
|
|
10812
|
+
payload = JSON.parse(readFileSync8(abs, "utf8"));
|
|
10813
|
+
} catch (e) {
|
|
10814
|
+
throw new Error(`cannot read rules from ${abs}: ${errMessage(e)}`);
|
|
10815
|
+
}
|
|
10816
|
+
}
|
|
10817
|
+
if (payload === void 0) throw new Error("`rules` (or `configPath`) is required");
|
|
10818
|
+
const rules = parseRules(payload);
|
|
10819
|
+
const { graph } = getArtifacts(repo, scanOpts, walked);
|
|
10635
10820
|
return JSON.stringify(checkRules(graph, rules), null, 2);
|
|
10636
10821
|
}
|
|
10637
10822
|
throw new Error(`unknown tool: ${name2}`);
|
|
10638
10823
|
}
|
|
10639
|
-
function
|
|
10640
|
-
|
|
10824
|
+
function validateArgs(schema, args2) {
|
|
10825
|
+
const props = schema.properties ?? {};
|
|
10826
|
+
for (const [key, value] of Object.entries(args2)) {
|
|
10827
|
+
if (value === void 0 || value === null) continue;
|
|
10828
|
+
const spec = props[key];
|
|
10829
|
+
if (!spec?.type) continue;
|
|
10830
|
+
const actual = Array.isArray(value) ? "array" : typeof value;
|
|
10831
|
+
if (spec.type === "number") {
|
|
10832
|
+
if (actual === "number") continue;
|
|
10833
|
+
if (actual === "string" && Number.isFinite(Number(value)) && value.trim() !== "") continue;
|
|
10834
|
+
return `\`${key}\` must be a number, got ${actual === "string" ? JSON.stringify(value) : actual}`;
|
|
10835
|
+
}
|
|
10836
|
+
if (spec.type === "array") {
|
|
10837
|
+
if (actual !== "array") return `\`${key}\` must be an array of strings, got ${actual}`;
|
|
10838
|
+
if (spec.items?.type === "string" && !value.every((x) => typeof x === "string")) {
|
|
10839
|
+
return `\`${key}\` must be an array of strings`;
|
|
10840
|
+
}
|
|
10841
|
+
continue;
|
|
10842
|
+
}
|
|
10843
|
+
if (actual !== spec.type) return `\`${key}\` must be a ${spec.type}, got ${actual}`;
|
|
10844
|
+
}
|
|
10845
|
+
return void 0;
|
|
10846
|
+
}
|
|
10847
|
+
function negotiateProtocol(requested) {
|
|
10848
|
+
return typeof requested === "string" && PROTOCOL_VERSIONS.includes(requested) ? requested : LATEST_PROTOCOL;
|
|
10849
|
+
}
|
|
10850
|
+
function annotationsFor(name2) {
|
|
10851
|
+
const meta = TOOL_META[name2];
|
|
10852
|
+
if (!meta) return void 0;
|
|
10853
|
+
return {
|
|
10854
|
+
readOnlyHint: !meta.write,
|
|
10855
|
+
...meta.write ? { destructiveHint: meta.destructive === true, idempotentHint: meta.idempotent === true } : {},
|
|
10856
|
+
openWorldHint: meta.openWorld === true
|
|
10857
|
+
};
|
|
10858
|
+
}
|
|
10859
|
+
function toolsFor(defaultRepo, protocolVersion = PROTOCOL_VERSIONS[0]) {
|
|
10860
|
+
const withAnnotations = protocolVersion >= ANNOTATIONS_SINCE;
|
|
10861
|
+
const withTitle = protocolVersion >= RICH_TOOLS_SINCE;
|
|
10862
|
+
if (!defaultRepo && !withAnnotations && !withTitle) return TOOLS;
|
|
10641
10863
|
return TOOLS.map((t) => ({
|
|
10642
10864
|
...t,
|
|
10643
|
-
|
|
10865
|
+
...withTitle && TOOL_META[t.name] ? { title: TOOL_META[t.name].title } : {},
|
|
10866
|
+
...withAnnotations ? { annotations: annotationsFor(t.name) } : {},
|
|
10867
|
+
inputSchema: !defaultRepo ? t.inputSchema : {
|
|
10644
10868
|
...t.inputSchema,
|
|
10645
10869
|
properties: {
|
|
10646
10870
|
...t.inputSchema.properties,
|
|
10647
|
-
repo: {
|
|
10871
|
+
repo: {
|
|
10872
|
+
type: "string",
|
|
10873
|
+
description: `Absolute path to the repository root (optional \u2014 defaults to ${defaultRepo})`
|
|
10874
|
+
}
|
|
10648
10875
|
},
|
|
10649
10876
|
required: t.inputSchema.required.filter((r) => r !== "repo")
|
|
10650
10877
|
}
|
|
10651
10878
|
}));
|
|
10652
10879
|
}
|
|
10880
|
+
function capResponse(text, tool, repo, maxBytes) {
|
|
10881
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
10882
|
+
if (bytes <= maxBytes) return text;
|
|
10883
|
+
const artifact = ARTIFACT_FOR[tool] ? join16(repo, INDEX_DIR, ARTIFACT_FOR[tool]) : void 0;
|
|
10884
|
+
return JSON.stringify(
|
|
10885
|
+
{
|
|
10886
|
+
truncated: true,
|
|
10887
|
+
tool,
|
|
10888
|
+
bytes,
|
|
10889
|
+
maxBytes,
|
|
10890
|
+
reason: "This response exceeds the configured limit and was withheld rather than sent as an unusable partial payload.",
|
|
10891
|
+
narrower: NARROWER[tool] ?? "narrow the request with `scope`, `include`/`exclude`, or a `limit`",
|
|
10892
|
+
...artifact && existsSync6(artifact) ? { artifact, artifactNote: "The full result is already on disk here \u2014 read it directly if you need all of it." } : artifact ? { artifactNote: `Run \`codeindex index --repo ${repo} --out ${join16(repo, INDEX_DIR)}\` to get this as a file.` } : {}
|
|
10893
|
+
},
|
|
10894
|
+
null,
|
|
10895
|
+
2
|
|
10896
|
+
) + "\n";
|
|
10897
|
+
}
|
|
10898
|
+
function resourceLinkFor(text, tool) {
|
|
10899
|
+
const artifactName = ARTIFACT_FOR[tool];
|
|
10900
|
+
if (!artifactName) return void 0;
|
|
10901
|
+
let parsed;
|
|
10902
|
+
try {
|
|
10903
|
+
parsed = JSON.parse(text);
|
|
10904
|
+
} catch {
|
|
10905
|
+
return void 0;
|
|
10906
|
+
}
|
|
10907
|
+
if (parsed.truncated !== true || typeof parsed.artifact !== "string") return void 0;
|
|
10908
|
+
return {
|
|
10909
|
+
type: "resource_link",
|
|
10910
|
+
uri: pathToFileURL2(parsed.artifact).href,
|
|
10911
|
+
name: artifactName,
|
|
10912
|
+
description: `The full ${tool} result this call was too large to inline.`,
|
|
10913
|
+
mimeType: "application/json"
|
|
10914
|
+
};
|
|
10915
|
+
}
|
|
10653
10916
|
async function runMcpServer(opts = {}) {
|
|
10654
10917
|
const serverInfo = {
|
|
10655
10918
|
name: opts.serverInfo?.name ?? "codeindex",
|
|
10656
10919
|
version: opts.serverInfo?.version ?? ENGINE_VERSION
|
|
10657
10920
|
};
|
|
10658
|
-
|
|
10921
|
+
let protocolVersion = PROTOCOL_VERSIONS[0];
|
|
10922
|
+
let tools = toolsFor(opts.defaultRepo, protocolVersion);
|
|
10659
10923
|
const send = (msg) => {
|
|
10660
10924
|
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...msg }) + "\n");
|
|
10661
10925
|
};
|
|
@@ -10677,10 +10941,12 @@ async function runMcpServer(opts = {}) {
|
|
|
10677
10941
|
if (req.id === void 0 || req.id === null) return;
|
|
10678
10942
|
try {
|
|
10679
10943
|
if (req.method === "initialize") {
|
|
10944
|
+
protocolVersion = negotiateProtocol(req.params?.protocolVersion);
|
|
10945
|
+
tools = toolsFor(opts.defaultRepo, protocolVersion);
|
|
10680
10946
|
send({
|
|
10681
10947
|
id: req.id,
|
|
10682
10948
|
result: {
|
|
10683
|
-
protocolVersion
|
|
10949
|
+
protocolVersion,
|
|
10684
10950
|
capabilities: { tools: {} },
|
|
10685
10951
|
serverInfo
|
|
10686
10952
|
}
|
|
@@ -10694,8 +10960,19 @@ async function runMcpServer(opts = {}) {
|
|
|
10694
10960
|
const name2 = str(params.name) ?? "";
|
|
10695
10961
|
const args2 = params.arguments ?? {};
|
|
10696
10962
|
try {
|
|
10697
|
-
const
|
|
10698
|
-
|
|
10963
|
+
const decl = tools.find(
|
|
10964
|
+
(t) => t.name === name2
|
|
10965
|
+
);
|
|
10966
|
+
const invalid = decl ? validateArgs(decl.inputSchema, args2) : void 0;
|
|
10967
|
+
if (invalid) throw new Error(invalid);
|
|
10968
|
+
const raw = await callTool(name2, args2, opts.defaultRepo);
|
|
10969
|
+
const repo = str(args2.repo) ?? opts.defaultRepo ?? "";
|
|
10970
|
+
const text = capResponse(raw, name2, repo, opts.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES);
|
|
10971
|
+
const link = text !== raw && protocolVersion >= RICH_TOOLS_SINCE ? resourceLinkFor(text, name2) : void 0;
|
|
10972
|
+
send({
|
|
10973
|
+
id: req.id,
|
|
10974
|
+
result: { content: link ? [{ type: "text", text }, link] : [{ type: "text", text }] }
|
|
10975
|
+
});
|
|
10699
10976
|
} catch (e) {
|
|
10700
10977
|
send({
|
|
10701
10978
|
id: req.id,
|
|
@@ -10710,7 +10987,7 @@ async function runMcpServer(opts = {}) {
|
|
|
10710
10987
|
}
|
|
10711
10988
|
}
|
|
10712
10989
|
}
|
|
10713
|
-
var repoProp, scopeProps, TOOLS, embeddingIndexCache, embedModelCache,
|
|
10990
|
+
var repoProp, scopeProps, TOOLS, embeddingIndexCache, embedModelCache, SESSION_CACHE_MAX, sessionCaches, SCANLESS_TOOLS, PROTOCOL_VERSIONS, LATEST_PROTOCOL, ANNOTATIONS_SINCE, RICH_TOOLS_SINCE, TOOL_META, DEFAULT_MAX_RESPONSE_BYTES, NARROWER, ARTIFACT_FOR;
|
|
10714
10991
|
var init_mcp = __esm({
|
|
10715
10992
|
"src/mcp.ts"() {
|
|
10716
10993
|
"use strict";
|
|
@@ -10719,8 +10996,10 @@ var init_mcp = __esm({
|
|
|
10719
10996
|
init_pipeline();
|
|
10720
10997
|
init_graph_json();
|
|
10721
10998
|
init_scan();
|
|
10999
|
+
init_preload();
|
|
10722
11000
|
init_walk();
|
|
10723
11001
|
init_callers();
|
|
11002
|
+
init_derived();
|
|
10724
11003
|
init_workspaces();
|
|
10725
11004
|
init_git();
|
|
10726
11005
|
init_grep();
|
|
@@ -10770,7 +11049,14 @@ var init_mcp = __esm({
|
|
|
10770
11049
|
description: "Who calls a function? Per-symbol caller index: each defined symbol with the exact (file, line) call sites that bind to it. Omit `name` for the full index.",
|
|
10771
11050
|
inputSchema: {
|
|
10772
11051
|
type: "object",
|
|
10773
|
-
properties: {
|
|
11052
|
+
properties: {
|
|
11053
|
+
...repoProp,
|
|
11054
|
+
name: { type: "string", description: "Symbol name to look up" },
|
|
11055
|
+
recall: {
|
|
11056
|
+
type: "boolean",
|
|
11057
|
+
description: "Recall-oriented binding: relax the JS/TS import gate to unique repo-wide names, labelling each site corroborated|unique-name (default false = precision)"
|
|
11058
|
+
}
|
|
11059
|
+
},
|
|
10774
11060
|
required: ["repo"]
|
|
10775
11061
|
}
|
|
10776
11062
|
},
|
|
@@ -10806,7 +11092,8 @@ var init_mcp = __esm({
|
|
|
10806
11092
|
...repoProp,
|
|
10807
11093
|
namePath: { type: "string", description: "Symbol name or Parent/child path" },
|
|
10808
11094
|
substring: { type: "boolean" },
|
|
10809
|
-
includeBody: { type: "boolean" }
|
|
11095
|
+
includeBody: { type: "boolean" },
|
|
11096
|
+
maxResults: { type: "number", description: "Cap matches (default 50)" }
|
|
10810
11097
|
},
|
|
10811
11098
|
required: ["repo", "namePath"]
|
|
10812
11099
|
}
|
|
@@ -10913,8 +11200,16 @@ var init_mcp = __esm({
|
|
|
10913
11200
|
},
|
|
10914
11201
|
{
|
|
10915
11202
|
name: "dead_code",
|
|
10916
|
-
description: "Dead-code candidates in two labeled tiers: 'unreferenced' (no call site binds AND nothing references the name) and 'uncalled' (referenced somewhere \u2014 re-export, type position \u2014 but never called). Exported symbols only; test files and entrypoint-looking files excluded as roots.",
|
|
10917
|
-
inputSchema: {
|
|
11203
|
+
description: "Dead-code candidates in two labeled tiers: 'unreferenced' (no call site binds AND nothing references the name) and 'uncalled' (referenced somewhere \u2014 re-export, type position \u2014 but never called). Exported symbols only; test files and entrypoint-looking files excluded as roots. On a large repo this list runs to thousands of entries \u2014 pass `limit`, or `scope` to one subdirectory.",
|
|
11204
|
+
inputSchema: {
|
|
11205
|
+
type: "object",
|
|
11206
|
+
properties: {
|
|
11207
|
+
...repoProp,
|
|
11208
|
+
...scopeProps,
|
|
11209
|
+
limit: { type: "number", description: "Cap entries (default: all)" }
|
|
11210
|
+
},
|
|
11211
|
+
required: ["repo"]
|
|
11212
|
+
}
|
|
10918
11213
|
},
|
|
10919
11214
|
{
|
|
10920
11215
|
name: "complexity",
|
|
@@ -10942,6 +11237,7 @@ var init_mcp = __esm({
|
|
|
10942
11237
|
properties: {
|
|
10943
11238
|
...repoProp,
|
|
10944
11239
|
pattern: { type: "string", description: "Regular expression to search for" },
|
|
11240
|
+
scope: { type: "string", description: "Restrict to one directory (repo-relative)" },
|
|
10945
11241
|
globs: { type: "array", items: { type: "string" }, description: "Restrict to matching paths" },
|
|
10946
11242
|
ignoreCase: { type: "boolean" },
|
|
10947
11243
|
maxHits: { type: "number" }
|
|
@@ -10984,12 +11280,18 @@ var init_mcp = __esm({
|
|
|
10984
11280
|
properties: {
|
|
10985
11281
|
...repoProp,
|
|
10986
11282
|
...scopeProps,
|
|
10987
|
-
rules: { type: "array", description: "Rules array (inline JSON \u2014 see description)" }
|
|
11283
|
+
rules: { type: "array", description: "Rules array (inline JSON \u2014 see description)" },
|
|
11284
|
+
configPath: {
|
|
11285
|
+
type: "string",
|
|
11286
|
+
description: "Read the rules from this JSON file instead (repo-relative or absolute) \u2014 the CLI's --config. Ignored when `rules` is given."
|
|
11287
|
+
}
|
|
10988
11288
|
},
|
|
10989
|
-
required: ["repo"
|
|
11289
|
+
required: ["repo"]
|
|
10990
11290
|
}
|
|
10991
11291
|
}
|
|
10992
11292
|
];
|
|
11293
|
+
SESSION_CACHE_MAX = 4;
|
|
11294
|
+
sessionCaches = [];
|
|
10993
11295
|
SCANLESS_TOOLS = /* @__PURE__ */ new Set([
|
|
10994
11296
|
"workspaces",
|
|
10995
11297
|
"churn",
|
|
@@ -10999,8 +11301,54 @@ var init_mcp = __esm({
|
|
|
10999
11301
|
"read_memory",
|
|
11000
11302
|
"list_memories",
|
|
11001
11303
|
"delete_memory",
|
|
11002
|
-
"embed_status"
|
|
11304
|
+
"embed_status",
|
|
11305
|
+
// scan_summary counts and classifies by path only — it never parses, so the
|
|
11306
|
+
// grammar warm (a whole extra walk) would be pure overhead. When a scan is
|
|
11307
|
+
// already cached getScanSummary reuses it, warm grammars included.
|
|
11308
|
+
"scan_summary"
|
|
11003
11309
|
]);
|
|
11310
|
+
PROTOCOL_VERSIONS = ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"];
|
|
11311
|
+
LATEST_PROTOCOL = PROTOCOL_VERSIONS[PROTOCOL_VERSIONS.length - 1];
|
|
11312
|
+
ANNOTATIONS_SINCE = "2025-03-26";
|
|
11313
|
+
RICH_TOOLS_SINCE = "2025-06-18";
|
|
11314
|
+
TOOL_META = {
|
|
11315
|
+
scan_summary: { title: "Scan summary" },
|
|
11316
|
+
graph: { title: "Link graph" },
|
|
11317
|
+
symbols: { title: "Symbol index" },
|
|
11318
|
+
callers: { title: "Caller index" },
|
|
11319
|
+
workspaces: { title: "Monorepo workspaces" },
|
|
11320
|
+
churn: { title: "Git churn" },
|
|
11321
|
+
symbols_overview: { title: "File symbol overview" },
|
|
11322
|
+
find_symbol: { title: "Find symbol" },
|
|
11323
|
+
find_references: { title: "Find references" },
|
|
11324
|
+
repo_map: { title: "Repository map" },
|
|
11325
|
+
hotspots: { title: "Hotspots" },
|
|
11326
|
+
coupling: { title: "Change coupling" },
|
|
11327
|
+
replace_symbol_body: { title: "Replace symbol body", write: true, destructive: true, idempotent: true },
|
|
11328
|
+
insert_after_symbol: { title: "Insert after symbol", write: true, destructive: false, idempotent: false },
|
|
11329
|
+
insert_before_symbol: { title: "Insert before symbol", write: true, destructive: false, idempotent: false },
|
|
11330
|
+
write_memory: { title: "Write memory", write: true, destructive: false, idempotent: true },
|
|
11331
|
+
read_memory: { title: "Read memory" },
|
|
11332
|
+
list_memories: { title: "List memories" },
|
|
11333
|
+
delete_memory: { title: "Delete memory", write: true, destructive: true, idempotent: true },
|
|
11334
|
+
dead_code: { title: "Dead-code candidates" },
|
|
11335
|
+
complexity: { title: "Complexity" },
|
|
11336
|
+
mermaid: { title: "Mermaid module diagram" },
|
|
11337
|
+
grep: { title: "Grep file contents" },
|
|
11338
|
+
search: { title: "Lexical search", openWorld: true },
|
|
11339
|
+
embed_status: { title: "Embedding tier status", openWorld: true },
|
|
11340
|
+
check_rules: { title: "Check architecture rules" }
|
|
11341
|
+
};
|
|
11342
|
+
DEFAULT_MAX_RESPONSE_BYTES = 1e6;
|
|
11343
|
+
NARROWER = {
|
|
11344
|
+
graph: "pass `scope` to a subdirectory, or use repo_map / mermaid for an overview",
|
|
11345
|
+
symbols: "pass `name` to look up one symbol, or use find_symbol / symbols_overview",
|
|
11346
|
+
callers: "pass `name` to look up one symbol's call sites",
|
|
11347
|
+
dead_code: "pass `scope` to a subdirectory",
|
|
11348
|
+
find_references: "the symbol is referenced very widely \u2014 narrow with `scope` on a graph query",
|
|
11349
|
+
check_rules: "narrow the rule set, or pass `scope` to a subdirectory"
|
|
11350
|
+
};
|
|
11351
|
+
ARTIFACT_FOR = { graph: "graph.json", symbols: "symbols.json" };
|
|
11004
11352
|
}
|
|
11005
11353
|
});
|
|
11006
11354
|
|
|
@@ -11123,6 +11471,140 @@ var init_rewrite = __esm({
|
|
|
11123
11471
|
init_types();
|
|
11124
11472
|
init_walk();
|
|
11125
11473
|
init_scan();
|
|
11474
|
+
init_scan();
|
|
11475
|
+
init_preload();
|
|
11476
|
+
|
|
11477
|
+
// src/pool.ts
|
|
11478
|
+
init_hash();
|
|
11479
|
+
init_walk();
|
|
11480
|
+
init_registry();
|
|
11481
|
+
init_loader();
|
|
11482
|
+
init_scan();
|
|
11483
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
11484
|
+
import { availableParallelism } from "os";
|
|
11485
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
11486
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
11487
|
+
import { Worker } from "worker_threads";
|
|
11488
|
+
function resolveEngineUrl() {
|
|
11489
|
+
try {
|
|
11490
|
+
const here = fileURLToPath2(import.meta.url);
|
|
11491
|
+
if (here.endsWith("engine.mjs")) return pathToFileURL(here).href;
|
|
11492
|
+
const adjacent = join4(dirname2(here), "engine.mjs");
|
|
11493
|
+
if (existsSync2(adjacent)) return pathToFileURL(adjacent).href;
|
|
11494
|
+
return void 0;
|
|
11495
|
+
} catch {
|
|
11496
|
+
return void 0;
|
|
11497
|
+
}
|
|
11498
|
+
}
|
|
11499
|
+
var WORKER_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
11500
|
+
function workerCount(requested) {
|
|
11501
|
+
const env = process.env["CODEINDEX_WORKERS"];
|
|
11502
|
+
const raw = requested ?? (env !== void 0 && env !== "" ? Number(env) : void 0);
|
|
11503
|
+
if (raw !== void 0) return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 0;
|
|
11504
|
+
let cores = 1;
|
|
11505
|
+
try {
|
|
11506
|
+
cores = availableParallelism();
|
|
11507
|
+
} catch {
|
|
11508
|
+
cores = 1;
|
|
11509
|
+
}
|
|
11510
|
+
return Math.max(0, Math.min(cores - 1, 8));
|
|
11511
|
+
}
|
|
11512
|
+
async function runExtractWorker(input, post) {
|
|
11513
|
+
await ensureGrammars(input.grammarKeys);
|
|
11514
|
+
const ready = input.grammarKeys.filter((k) => grammarReady(k));
|
|
11515
|
+
const records = [];
|
|
11516
|
+
for (const job of input.jobs) {
|
|
11517
|
+
let size;
|
|
11518
|
+
let mtimeMs;
|
|
11519
|
+
try {
|
|
11520
|
+
const st = statSync2(job.abs);
|
|
11521
|
+
size = st.size;
|
|
11522
|
+
mtimeMs = st.mtimeMs;
|
|
11523
|
+
} catch {
|
|
11524
|
+
continue;
|
|
11525
|
+
}
|
|
11526
|
+
const content = readText(job.abs);
|
|
11527
|
+
const record = buildCodeRecord(job.rel, job.ext, size, content, sha1(content), extToLang(job.ext), {
|
|
11528
|
+
maxCallsPerFile: input.maxCallsPerFile
|
|
11529
|
+
});
|
|
11530
|
+
records.push({ rel: job.rel, size, mtimeMs, record });
|
|
11531
|
+
}
|
|
11532
|
+
post({ ready, records });
|
|
11533
|
+
}
|
|
11534
|
+
async function extractInParallel(jobs, grammarKeys, count, opts = {}) {
|
|
11535
|
+
if (count < 2 || jobs.length === 0) return void 0;
|
|
11536
|
+
const engineUrl = resolveEngineUrl();
|
|
11537
|
+
if (!engineUrl) return void 0;
|
|
11538
|
+
const wanted = grammarKeys.filter((k) => grammarReady(k)).sort();
|
|
11539
|
+
const shards = Array.from({ length: Math.min(count, jobs.length) }, () => []);
|
|
11540
|
+
jobs.forEach((j, i2) => shards[i2 % shards.length].push(j));
|
|
11541
|
+
const bootstrap = `import { runExtractWorker } from ${JSON.stringify(engineUrl)};
|
|
11542
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
11543
|
+
runExtractWorker(workerData.input, (o) => parentPort.postMessage(o)).catch((e) => parentPort.postMessage({ error: String(e) }));
|
|
11544
|
+
`;
|
|
11545
|
+
try {
|
|
11546
|
+
const outputs = await Promise.all(
|
|
11547
|
+
shards.map(
|
|
11548
|
+
(jobsForShard) => new Promise((resolve3, reject) => {
|
|
11549
|
+
const w = new Worker(bootstrap, {
|
|
11550
|
+
eval: true,
|
|
11551
|
+
workerData: { input: { jobs: jobsForShard, grammarKeys: wanted, maxCallsPerFile: opts.maxCallsPerFile } }
|
|
11552
|
+
});
|
|
11553
|
+
const timer = setTimeout(() => {
|
|
11554
|
+
reject(new Error("extraction worker timed out"));
|
|
11555
|
+
void w.terminate();
|
|
11556
|
+
}, WORKER_TIMEOUT_MS);
|
|
11557
|
+
const settle = (fn) => {
|
|
11558
|
+
clearTimeout(timer);
|
|
11559
|
+
fn();
|
|
11560
|
+
};
|
|
11561
|
+
w.once("message", (m) => {
|
|
11562
|
+
settle(() => resolve3(m));
|
|
11563
|
+
void w.terminate();
|
|
11564
|
+
});
|
|
11565
|
+
w.once("error", (e) => settle(() => reject(e)));
|
|
11566
|
+
w.once("exit", (code) => {
|
|
11567
|
+
if (code !== 0) settle(() => reject(new Error(`extraction worker exited with ${code}`)));
|
|
11568
|
+
});
|
|
11569
|
+
})
|
|
11570
|
+
)
|
|
11571
|
+
);
|
|
11572
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
11573
|
+
for (const o of outputs) {
|
|
11574
|
+
if ("error" in o) return void 0;
|
|
11575
|
+
if (o.ready.slice().sort().join(",") !== wanted.join(",")) return void 0;
|
|
11576
|
+
for (const r of o.records) out2.set(r.rel, { size: r.size, mtimeMs: r.mtimeMs, record: r.record });
|
|
11577
|
+
}
|
|
11578
|
+
return out2;
|
|
11579
|
+
} catch {
|
|
11580
|
+
return void 0;
|
|
11581
|
+
}
|
|
11582
|
+
}
|
|
11583
|
+
async function scanRepoParallel(root, opts = {}) {
|
|
11584
|
+
const count = workerCount(opts.workers);
|
|
11585
|
+
if (count < 2) return scanRepo(root, opts);
|
|
11586
|
+
const walked = opts.precomputedWalk ?? walk(root, {
|
|
11587
|
+
maxFileBytes: opts.maxBytes,
|
|
11588
|
+
maxFiles: opts.maxFiles,
|
|
11589
|
+
gitignore: opts.gitignore,
|
|
11590
|
+
ignoreDirs: opts.ignoreDirs
|
|
11591
|
+
});
|
|
11592
|
+
const scanOpts = { ...opts, precomputedWalk: walked };
|
|
11593
|
+
const jobs = [];
|
|
11594
|
+
for (const { f } of keptCodeFiles(root, scanOpts)) {
|
|
11595
|
+
const cached = opts.cache?.get(f.rel);
|
|
11596
|
+
if (!opts.fullHash && cached && cached.size !== void 0 && cached.mtimeMs !== void 0 && cached.size === f.size && cached.mtimeMs === f.mtimeMs) {
|
|
11597
|
+
continue;
|
|
11598
|
+
}
|
|
11599
|
+
jobs.push({ abs: f.abs, rel: f.rel, ext: f.ext });
|
|
11600
|
+
}
|
|
11601
|
+
if (jobs.length === 0) return scanRepo(root, scanOpts);
|
|
11602
|
+
const grammarKeys = grammarKeysForExts(walked.files.map((f) => f.ext));
|
|
11603
|
+
const extracted = await extractInParallel(jobs, grammarKeys, count, { maxCallsPerFile: opts.maxCallsPerFile });
|
|
11604
|
+
return scanRepo(root, extracted ? { ...scanOpts, extracted } : scanOpts);
|
|
11605
|
+
}
|
|
11606
|
+
|
|
11607
|
+
// src/engine.ts
|
|
11126
11608
|
init_glob();
|
|
11127
11609
|
init_ignore();
|
|
11128
11610
|
init_classify();
|
|
@@ -11260,8 +11742,8 @@ init_loader();
|
|
|
11260
11742
|
// src/ast/grammars-pull.ts
|
|
11261
11743
|
init_types();
|
|
11262
11744
|
import { createHash as createHash2 } from "crypto";
|
|
11263
|
-
import { existsSync as
|
|
11264
|
-
import { dirname as
|
|
11745
|
+
import { existsSync as existsSync3, mkdirSync, mkdtempSync, readFileSync as readFileSync4, renameSync, rmSync, writeFileSync } from "fs";
|
|
11746
|
+
import { dirname as dirname3, join as join5, resolve, sep as sep2 } from "path";
|
|
11265
11747
|
import { gunzipSync } from "zlib";
|
|
11266
11748
|
var DEFAULT_GRAMMARS_URL = `https://github.com/maxgfr/codeindex/releases/download/v${ENGINE_VERSION}/grammars-${ENGINE_VERSION}.tar.gz`;
|
|
11267
11749
|
function resolveGrammarsPullTarget() {
|
|
@@ -11342,7 +11824,7 @@ function extractTarInto(rawTar, destDir) {
|
|
|
11342
11824
|
if (dest !== root && !dest.startsWith(root + sep2)) {
|
|
11343
11825
|
throw new Error(`tar entry escapes destination: ${entry.name}`);
|
|
11344
11826
|
}
|
|
11345
|
-
mkdirSync(
|
|
11827
|
+
mkdirSync(dirname3(dest), { recursive: true });
|
|
11346
11828
|
writeFileSync(dest, entry.data);
|
|
11347
11829
|
written.push(rel);
|
|
11348
11830
|
}
|
|
@@ -11366,12 +11848,12 @@ async function pullGrammars(cacheDir, opts = {}) {
|
|
|
11366
11848
|
`);
|
|
11367
11849
|
}
|
|
11368
11850
|
}
|
|
11369
|
-
const runtime =
|
|
11370
|
-
const markerPath =
|
|
11371
|
-
if (
|
|
11851
|
+
const runtime = join5(cacheDir, "web-tree-sitter.wasm");
|
|
11852
|
+
const markerPath = join5(dirname3(cacheDir), `${ENGINE_VERSION}.sha256`);
|
|
11853
|
+
if (existsSync3(runtime) && expected && existsSync3(markerPath)) {
|
|
11372
11854
|
let marker = "";
|
|
11373
11855
|
try {
|
|
11374
|
-
marker =
|
|
11856
|
+
marker = readFileSync4(markerPath, "utf8").trim();
|
|
11375
11857
|
} catch {
|
|
11376
11858
|
}
|
|
11377
11859
|
if (marker === expected) {
|
|
@@ -11395,13 +11877,13 @@ async function pullGrammars(cacheDir, opts = {}) {
|
|
|
11395
11877
|
}
|
|
11396
11878
|
let tmp;
|
|
11397
11879
|
try {
|
|
11398
|
-
mkdirSync(
|
|
11399
|
-
tmp = mkdtempSync(
|
|
11880
|
+
mkdirSync(dirname3(cacheDir), { recursive: true });
|
|
11881
|
+
tmp = mkdtempSync(join5(dirname3(cacheDir), ".grammars-tmp-"));
|
|
11400
11882
|
extractGrammarsTarball(bytes, tmp);
|
|
11401
|
-
if (!
|
|
11883
|
+
if (!existsSync3(join5(tmp, "web-tree-sitter.wasm"))) {
|
|
11402
11884
|
throw new Error("archive is missing web-tree-sitter.wasm");
|
|
11403
11885
|
}
|
|
11404
|
-
if (
|
|
11886
|
+
if (existsSync3(cacheDir)) rmSync(cacheDir, { recursive: true, force: true });
|
|
11405
11887
|
renameSync(tmp, cacheDir);
|
|
11406
11888
|
tmp = void 0;
|
|
11407
11889
|
if (expected) writeFileSync(markerPath, expected + "\n");
|
|
@@ -11478,7 +11960,48 @@ init_graph_json();
|
|
|
11478
11960
|
init_types();
|
|
11479
11961
|
init_walk();
|
|
11480
11962
|
init_sort();
|
|
11481
|
-
import { join as
|
|
11963
|
+
import { join as join14 } from "path";
|
|
11964
|
+
var Bytes = class {
|
|
11965
|
+
buf;
|
|
11966
|
+
len = 0;
|
|
11967
|
+
constructor(capacity = 64) {
|
|
11968
|
+
this.buf = new Uint8Array(capacity);
|
|
11969
|
+
}
|
|
11970
|
+
get length() {
|
|
11971
|
+
return this.len;
|
|
11972
|
+
}
|
|
11973
|
+
grow(need) {
|
|
11974
|
+
if (this.len + need <= this.buf.length) return;
|
|
11975
|
+
let cap = this.buf.length * 2 || 64;
|
|
11976
|
+
while (cap < this.len + need) cap *= 2;
|
|
11977
|
+
const next = new Uint8Array(cap);
|
|
11978
|
+
next.set(this.buf.subarray(0, this.len));
|
|
11979
|
+
this.buf = next;
|
|
11980
|
+
}
|
|
11981
|
+
// Rewind without releasing the buffer, so a per-item scratch sink is
|
|
11982
|
+
// allocated once per document instead of once per occurrence/symbol.
|
|
11983
|
+
reset() {
|
|
11984
|
+
this.len = 0;
|
|
11985
|
+
}
|
|
11986
|
+
push(byte) {
|
|
11987
|
+
this.grow(1);
|
|
11988
|
+
this.buf[this.len++] = byte;
|
|
11989
|
+
}
|
|
11990
|
+
pushAll(src) {
|
|
11991
|
+
this.grow(src.length);
|
|
11992
|
+
if (src instanceof Uint8Array) this.buf.set(src, this.len);
|
|
11993
|
+
else for (let i2 = 0; i2 < src.length; i2++) this.buf[this.len + i2] = src[i2];
|
|
11994
|
+
this.len += src.length;
|
|
11995
|
+
}
|
|
11996
|
+
// A view over exactly the bytes written — no copy. Callers must not retain it
|
|
11997
|
+
// across further writes to this sink.
|
|
11998
|
+
view() {
|
|
11999
|
+
return this.buf.subarray(0, this.len);
|
|
12000
|
+
}
|
|
12001
|
+
toUint8Array() {
|
|
12002
|
+
return this.buf.slice(0, this.len);
|
|
12003
|
+
}
|
|
12004
|
+
};
|
|
11482
12005
|
var utf8 = new TextEncoder();
|
|
11483
12006
|
function pushVarint(out2, n) {
|
|
11484
12007
|
if (n < 0) throw new Error(`pushVarint: negative input ${n} is not a valid unsigned varint`);
|
|
@@ -11498,15 +12021,18 @@ function pushVarintField(out2, field, n) {
|
|
|
11498
12021
|
function pushLenDelim(out2, field, payload) {
|
|
11499
12022
|
pushTag(out2, field, 2);
|
|
11500
12023
|
pushVarint(out2, payload.length);
|
|
11501
|
-
|
|
12024
|
+
out2.pushAll(payload);
|
|
12025
|
+
}
|
|
12026
|
+
function pushMessage(out2, field, payload) {
|
|
12027
|
+
pushLenDelim(out2, field, payload.view());
|
|
11502
12028
|
}
|
|
11503
12029
|
function pushString(out2, field, s) {
|
|
11504
12030
|
pushLenDelim(out2, field, utf8.encode(s));
|
|
11505
12031
|
}
|
|
11506
12032
|
function pushPackedInt32(out2, field, values) {
|
|
11507
|
-
const payload =
|
|
12033
|
+
const payload = new Bytes(values.length * 2);
|
|
11508
12034
|
for (const v of values) pushVarint(payload, v);
|
|
11509
|
-
|
|
12035
|
+
pushMessage(out2, field, payload);
|
|
11510
12036
|
}
|
|
11511
12037
|
var F_INDEX_METADATA = 1;
|
|
11512
12038
|
var F_INDEX_DOCUMENTS = 2;
|
|
@@ -11648,7 +12174,7 @@ function renderScip(scan2, opts = {}) {
|
|
|
11648
12174
|
};
|
|
11649
12175
|
const documents = [];
|
|
11650
12176
|
for (const f of docs) {
|
|
11651
|
-
const text = readText(
|
|
12177
|
+
const text = readText(join14(scan2.root, f.rel));
|
|
11652
12178
|
const lines = text.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
|
|
11653
12179
|
const locate = (lineNo, name2) => {
|
|
11654
12180
|
const line = lines[lineNo - 1];
|
|
@@ -11677,41 +12203,45 @@ function renderScip(scan2, opts = {}) {
|
|
|
11677
12203
|
kind: KIND[sym.kind],
|
|
11678
12204
|
enclosing: sym.parent ? enclosingSymbolOf(f.rel, sym.parent) : void 0
|
|
11679
12205
|
})).sort((a, b) => byStr(a.symbol, b.symbol));
|
|
11680
|
-
const doc =
|
|
12206
|
+
const doc = new Bytes(1024);
|
|
11681
12207
|
pushString(doc, F_DOC_RELPATH, f.rel);
|
|
12208
|
+
const ob = new Bytes(64);
|
|
11682
12209
|
for (const o of occs) {
|
|
11683
12210
|
const key = `${o.range.join(",")} ${o.roles} ${o.symbol}`;
|
|
11684
12211
|
if (seenOcc.has(key)) continue;
|
|
11685
12212
|
seenOcc.add(key);
|
|
11686
|
-
|
|
12213
|
+
ob.reset();
|
|
11687
12214
|
pushPackedInt32(ob, F_OCC_RANGE, o.range);
|
|
11688
12215
|
pushString(ob, F_OCC_SYMBOL, o.symbol);
|
|
11689
12216
|
if (o.roles !== 0) pushVarintField(ob, F_OCC_ROLES, o.roles);
|
|
11690
|
-
|
|
12217
|
+
pushMessage(doc, F_DOC_OCCURRENCES, ob);
|
|
11691
12218
|
}
|
|
12219
|
+
const sb = new Bytes(64);
|
|
11692
12220
|
for (const si of infos) {
|
|
11693
|
-
|
|
12221
|
+
sb.reset();
|
|
11694
12222
|
pushString(sb, F_SI_SYMBOL, si.symbol);
|
|
11695
12223
|
if (si.kind !== void 0) pushVarintField(sb, F_SI_KIND, si.kind);
|
|
11696
12224
|
pushString(sb, F_SI_DISPLAY_NAME, si.displayName);
|
|
11697
12225
|
if (si.enclosing) pushString(sb, F_SI_ENCLOSING, si.enclosing);
|
|
11698
|
-
|
|
12226
|
+
pushMessage(doc, F_DOC_SYMBOLS, sb);
|
|
11699
12227
|
}
|
|
11700
12228
|
pushString(doc, F_DOC_LANGUAGE, f.lang);
|
|
11701
12229
|
pushVarintField(doc, F_DOC_POSITION_ENCODING, POSITION_ENCODING_UTF16);
|
|
11702
12230
|
documents.push(doc);
|
|
11703
12231
|
}
|
|
11704
|
-
const toolInfo =
|
|
12232
|
+
const toolInfo = new Bytes();
|
|
11705
12233
|
pushString(toolInfo, F_TOOL_NAME, "codeindex");
|
|
11706
12234
|
pushString(toolInfo, F_TOOL_VERSION, toolVersion);
|
|
11707
|
-
const metadata2 =
|
|
11708
|
-
|
|
12235
|
+
const metadata2 = new Bytes();
|
|
12236
|
+
pushMessage(metadata2, F_META_TOOL_INFO, toolInfo);
|
|
11709
12237
|
pushString(metadata2, F_META_PROJECT_ROOT, projectRoot);
|
|
11710
12238
|
pushVarintField(metadata2, F_META_TEXT_ENCODING, TEXT_ENCODING_UTF8);
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
12239
|
+
let total = 0;
|
|
12240
|
+
for (const d of documents) total += d.length;
|
|
12241
|
+
const index = new Bytes(total + metadata2.length + 16);
|
|
12242
|
+
pushMessage(index, F_INDEX_METADATA, metadata2);
|
|
12243
|
+
for (const d of documents) pushMessage(index, F_INDEX_DOCUMENTS, d);
|
|
12244
|
+
return index.toUint8Array();
|
|
11715
12245
|
}
|
|
11716
12246
|
|
|
11717
12247
|
// src/engine.ts
|
|
@@ -11730,6 +12260,375 @@ init_repomap();
|
|
|
11730
12260
|
init_deadcode();
|
|
11731
12261
|
init_complexity();
|
|
11732
12262
|
init_viz();
|
|
12263
|
+
|
|
12264
|
+
// src/traverse.ts
|
|
12265
|
+
init_sort();
|
|
12266
|
+
var DEPENDS_KINDS = /* @__PURE__ */ new Set(["import", "use", "call"]);
|
|
12267
|
+
function hubThreshold(degrees) {
|
|
12268
|
+
const sorted = degrees.slice().sort((a, b) => a - b);
|
|
12269
|
+
const n = sorted.length;
|
|
12270
|
+
const p99 = n === 0 ? 0 : sorted[Math.min(n - 1, Math.floor(0.99 * n))];
|
|
12271
|
+
return Math.max(50, p99);
|
|
12272
|
+
}
|
|
12273
|
+
function reverseClosure(edges, seeds, depth = Infinity) {
|
|
12274
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
12275
|
+
for (const e of edges) {
|
|
12276
|
+
if (e.dangling || !DEPENDS_KINDS.has(e.kind)) continue;
|
|
12277
|
+
let arr = dependents.get(e.to);
|
|
12278
|
+
if (!arr) dependents.set(e.to, arr = []);
|
|
12279
|
+
arr.push(e);
|
|
12280
|
+
}
|
|
12281
|
+
const depthOf = /* @__PURE__ */ new Map();
|
|
12282
|
+
const seen = new Set(seeds);
|
|
12283
|
+
let frontier = [...seeds];
|
|
12284
|
+
for (let d = 1; d <= depth && frontier.length; d++) {
|
|
12285
|
+
const next = [];
|
|
12286
|
+
for (const node of frontier) {
|
|
12287
|
+
for (const e of (dependents.get(node) ?? []).slice().sort((a, b) => byStr(a.from, b.from))) {
|
|
12288
|
+
if (seen.has(e.from)) continue;
|
|
12289
|
+
seen.add(e.from);
|
|
12290
|
+
depthOf.set(e.from, d);
|
|
12291
|
+
next.push(e.from);
|
|
12292
|
+
}
|
|
12293
|
+
}
|
|
12294
|
+
frontier = next;
|
|
12295
|
+
}
|
|
12296
|
+
return depthOf;
|
|
12297
|
+
}
|
|
12298
|
+
function impactOf(graph, target, depth = Infinity) {
|
|
12299
|
+
const moduleOf = new Map(graph.files.map((f) => [f.rel, f.module]));
|
|
12300
|
+
const mod = graph.modules.find((m) => m.slug === target);
|
|
12301
|
+
const file = mod ? void 0 : graph.files.find((f) => f.rel === target);
|
|
12302
|
+
if (!mod && !file) return void 0;
|
|
12303
|
+
const seeds = mod ? mod.members : [file.rel];
|
|
12304
|
+
const depthOf = reverseClosure(graph.fileEdges, seeds, depth);
|
|
12305
|
+
const files = [...depthOf.entries()].map(([rel, d]) => ({ rel, module: moduleOf.get(rel) ?? "root", depth: d })).sort((a, b) => a.depth - b.depth || byStr(a.rel, b.rel));
|
|
12306
|
+
const modules = [...new Set(files.map((f) => f.module).filter((m) => m !== target))].sort(byStr);
|
|
12307
|
+
return { target, scope: mod ? "module" : "file", seeds, files, modules };
|
|
12308
|
+
}
|
|
12309
|
+
function bfs(edges, start2, depth, kinds) {
|
|
12310
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
12311
|
+
const inn = /* @__PURE__ */ new Map();
|
|
12312
|
+
const degree = /* @__PURE__ */ new Map();
|
|
12313
|
+
for (const e of edges) {
|
|
12314
|
+
if (e.dangling) continue;
|
|
12315
|
+
if (kinds && !kinds.has(e.kind)) continue;
|
|
12316
|
+
(out2.get(e.from) ?? out2.set(e.from, []).get(e.from)).push(e);
|
|
12317
|
+
(inn.get(e.to) ?? inn.set(e.to, []).get(e.to)).push(e);
|
|
12318
|
+
degree.set(e.from, (degree.get(e.from) ?? 0) + 1);
|
|
12319
|
+
degree.set(e.to, (degree.get(e.to) ?? 0) + 1);
|
|
12320
|
+
}
|
|
12321
|
+
const threshold = hubThreshold([...degree.values()]);
|
|
12322
|
+
const seen = /* @__PURE__ */ new Set([start2]);
|
|
12323
|
+
const links = [];
|
|
12324
|
+
let frontier = [start2];
|
|
12325
|
+
for (let d = 1; d <= depth; d++) {
|
|
12326
|
+
const next = [];
|
|
12327
|
+
for (const node of frontier) {
|
|
12328
|
+
if (node !== start2 && (degree.get(node) ?? 0) >= threshold) continue;
|
|
12329
|
+
for (const e of (out2.get(node) ?? []).slice().sort((a, b) => byStr(a.to, b.to))) {
|
|
12330
|
+
if (seen.has(e.to)) continue;
|
|
12331
|
+
links.push({ node: e.to, direction: "out", kind: e.kind, weight: e.weight, depth: d, confidence: e.confidence });
|
|
12332
|
+
seen.add(e.to);
|
|
12333
|
+
next.push(e.to);
|
|
12334
|
+
}
|
|
12335
|
+
for (const e of (inn.get(node) ?? []).slice().sort((a, b) => byStr(a.from, b.from))) {
|
|
12336
|
+
if (seen.has(e.from)) continue;
|
|
12337
|
+
links.push({ node: e.from, direction: "in", kind: e.kind, weight: e.weight, depth: d, confidence: e.confidence });
|
|
12338
|
+
seen.add(e.from);
|
|
12339
|
+
next.push(e.from);
|
|
12340
|
+
}
|
|
12341
|
+
}
|
|
12342
|
+
frontier = next;
|
|
12343
|
+
}
|
|
12344
|
+
return links;
|
|
12345
|
+
}
|
|
12346
|
+
function neighborsOf(graph, target, depth = 1, kinds) {
|
|
12347
|
+
const mod = graph.modules.find((m) => m.slug === target);
|
|
12348
|
+
if (mod) {
|
|
12349
|
+
return { target, scope: "module", links: bfs(graph.moduleEdges, target, depth, kinds), members: mod.members };
|
|
12350
|
+
}
|
|
12351
|
+
const file = graph.files.find((f) => f.rel === target);
|
|
12352
|
+
if (file) {
|
|
12353
|
+
return { target, scope: "file", links: bfs(graph.fileEdges, target, depth, kinds) };
|
|
12354
|
+
}
|
|
12355
|
+
return void 0;
|
|
12356
|
+
}
|
|
12357
|
+
|
|
12358
|
+
// src/delta.ts
|
|
12359
|
+
init_git();
|
|
12360
|
+
init_sort();
|
|
12361
|
+
init_util();
|
|
12362
|
+
var RISK_WEIGHTS = {
|
|
12363
|
+
exportedChange: 25,
|
|
12364
|
+
// an exported symbol changed: consumers may break
|
|
12365
|
+
hubHigh: 20,
|
|
12366
|
+
// pagerank percentile ≥ .90
|
|
12367
|
+
hubMed: 10,
|
|
12368
|
+
// pagerank percentile ≥ .75
|
|
12369
|
+
blastHigh: 20,
|
|
12370
|
+
// ≥ 20 dependent files or ≥ 5 dependent modules
|
|
12371
|
+
blastMed: 10,
|
|
12372
|
+
// ≥ 5 dependent files
|
|
12373
|
+
testGap: 20,
|
|
12374
|
+
// a testable module with no covering test
|
|
12375
|
+
surprise: 10,
|
|
12376
|
+
// the module sits on a surprising cross-community edge
|
|
12377
|
+
dangling: 15
|
|
12378
|
+
// a changed file carries a dangling import
|
|
12379
|
+
};
|
|
12380
|
+
var HIGH_MIN = 60;
|
|
12381
|
+
var MEDIUM_MIN = 30;
|
|
12382
|
+
var OPEN_CAP = 3;
|
|
12383
|
+
var DEFAULT_DELTA_DEPTH = 2;
|
|
12384
|
+
function symbolsInHunks(defs, hunks) {
|
|
12385
|
+
const out2 = [];
|
|
12386
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12387
|
+
const push = (d, approx) => {
|
|
12388
|
+
const key = `${d.name}:${d.line}`;
|
|
12389
|
+
if (seen.has(key)) return;
|
|
12390
|
+
seen.add(key);
|
|
12391
|
+
out2.push({
|
|
12392
|
+
name: d.name,
|
|
12393
|
+
kind: d.kind,
|
|
12394
|
+
exported: d.exported,
|
|
12395
|
+
line: d.line,
|
|
12396
|
+
...d.endLine !== void 0 ? { endLine: d.endLine } : {},
|
|
12397
|
+
...d.parent !== void 0 ? { parent: d.parent } : {},
|
|
12398
|
+
...approx ? { approx: true } : {}
|
|
12399
|
+
});
|
|
12400
|
+
};
|
|
12401
|
+
const span = (d) => (d.endLine ?? d.line) - d.line;
|
|
12402
|
+
for (const h of hunks) {
|
|
12403
|
+
const enclosing = defs.filter((d) => d.line <= h.end && (d.endLine ?? d.line) >= h.start);
|
|
12404
|
+
if (enclosing.length) {
|
|
12405
|
+
enclosing.sort((a, b) => span(a) - span(b) || b.line - a.line || byStr(a.name, b.name));
|
|
12406
|
+
for (const d of enclosing) push(d, false);
|
|
12407
|
+
} else {
|
|
12408
|
+
const above = defs.filter((d) => d.line <= h.start && d.endLine === void 0);
|
|
12409
|
+
const near = above[above.length - 1];
|
|
12410
|
+
if (near) push(near, true);
|
|
12411
|
+
}
|
|
12412
|
+
}
|
|
12413
|
+
return out2;
|
|
12414
|
+
}
|
|
12415
|
+
function percentile(values, mine) {
|
|
12416
|
+
if (values.length <= 1) return 0;
|
|
12417
|
+
let smaller = 0;
|
|
12418
|
+
for (const v of values) if (v < mine) smaller++;
|
|
12419
|
+
return smaller / (values.length - 1);
|
|
12420
|
+
}
|
|
12421
|
+
function computeDelta(graph, symbols, diff, depth = DEFAULT_DELTA_DEPTH) {
|
|
12422
|
+
const notes = [...diff.notes ?? []];
|
|
12423
|
+
if (!symbols) notes.push("symbol index missing \u2014 symbol-level attribution disabled");
|
|
12424
|
+
const fileByRel = new Map(graph.files.map((f) => [f.rel, f]));
|
|
12425
|
+
const defsByFile = /* @__PURE__ */ new Map();
|
|
12426
|
+
if (symbols) {
|
|
12427
|
+
for (const [name2, entries] of Object.entries(symbols.defs)) {
|
|
12428
|
+
for (const d of entries) {
|
|
12429
|
+
let arr = defsByFile.get(d.file);
|
|
12430
|
+
if (!arr) defsByFile.set(d.file, arr = []);
|
|
12431
|
+
arr.push({ name: name2, ...d });
|
|
12432
|
+
}
|
|
12433
|
+
}
|
|
12434
|
+
for (const arr of defsByFile.values()) arr.sort((a, b) => a.line - b.line || byStr(a.name, b.name));
|
|
12435
|
+
}
|
|
12436
|
+
const deleted = [];
|
|
12437
|
+
const unindexed = [];
|
|
12438
|
+
const changes = [];
|
|
12439
|
+
for (const df of [...diff.files].sort((a, b) => byStr(a.path, b.path))) {
|
|
12440
|
+
const carry = {
|
|
12441
|
+
...df.oldPath !== void 0 ? { oldPath: df.oldPath } : {},
|
|
12442
|
+
...df.binary ? { binary: true } : {},
|
|
12443
|
+
...df.linesAdded !== void 0 ? { linesAdded: df.linesAdded } : {},
|
|
12444
|
+
...df.linesDeleted !== void 0 ? { linesDeleted: df.linesDeleted } : {}
|
|
12445
|
+
};
|
|
12446
|
+
if (df.status === "deleted") {
|
|
12447
|
+
deleted.push(df.path);
|
|
12448
|
+
changes.push({ path: df.path, status: df.status, ...carry, hunks: [], symbols: [] });
|
|
12449
|
+
continue;
|
|
12450
|
+
}
|
|
12451
|
+
const node = fileByRel.get(df.path);
|
|
12452
|
+
if (!node) {
|
|
12453
|
+
unindexed.push(df.path);
|
|
12454
|
+
continue;
|
|
12455
|
+
}
|
|
12456
|
+
let hunks = diff.hunks.get(df.path) ?? [];
|
|
12457
|
+
if (!hunks.length && df.status === "added" && !df.binary) hunks = [{ start: 1, end: Math.max(node.lines, 1) }];
|
|
12458
|
+
const syms = df.binary ? [] : symbolsInHunks(defsByFile.get(df.path) ?? [], hunks);
|
|
12459
|
+
changes.push({
|
|
12460
|
+
path: df.path,
|
|
12461
|
+
status: df.status,
|
|
12462
|
+
...carry,
|
|
12463
|
+
module: node.module,
|
|
12464
|
+
hunks: hunks.map((h) => ({ start: h.start, end: h.end })),
|
|
12465
|
+
symbols: syms
|
|
12466
|
+
});
|
|
12467
|
+
}
|
|
12468
|
+
const changedRels = new Set(changes.filter((c2) => c2.status !== "deleted").map((c2) => c2.path));
|
|
12469
|
+
const dangling = graph.fileEdges.filter((e) => e.dangling && (e.kind === "import" || e.kind === "doc-link") && changedRels.has(e.from)).map((e) => ({ from: e.from, spec: e.to, reason: e.reason ?? "unknown" })).sort((a, b) => byStr(a.from, b.from) || byStr(a.spec, b.spec));
|
|
12470
|
+
const byModule = /* @__PURE__ */ new Map();
|
|
12471
|
+
for (const c2 of changes) {
|
|
12472
|
+
if (c2.status === "deleted" || !c2.module) continue;
|
|
12473
|
+
let arr = byModule.get(c2.module);
|
|
12474
|
+
if (!arr) byModule.set(c2.module, arr = []);
|
|
12475
|
+
arr.push(c2);
|
|
12476
|
+
}
|
|
12477
|
+
const nonTestCode = /* @__PURE__ */ new Set();
|
|
12478
|
+
for (const f of graph.files) {
|
|
12479
|
+
if (f.fileKind === "code" && !f.testFile) nonTestCode.add(f.module);
|
|
12480
|
+
}
|
|
12481
|
+
const pagerankKnown = graph.modules.some((m) => m.pagerank !== void 0);
|
|
12482
|
+
const metricOf = (m) => pagerankKnown ? m.pagerank ?? 0 : m.degIn + m.degOut;
|
|
12483
|
+
const metricValues = graph.modules.map(metricOf);
|
|
12484
|
+
const metricName = pagerankKnown ? "pagerank" : "degree";
|
|
12485
|
+
const modules = [];
|
|
12486
|
+
for (const slug of [...byModule.keys()].sort(byStr)) {
|
|
12487
|
+
const m = graph.modules.find((x) => x.slug === slug);
|
|
12488
|
+
if (!m) continue;
|
|
12489
|
+
const moduleChanges = byModule.get(slug);
|
|
12490
|
+
const reasons = [];
|
|
12491
|
+
let score = 0;
|
|
12492
|
+
const exportedNames = [...new Set(moduleChanges.flatMap((c2) => c2.symbols.filter((s) => s.exported).map((s) => s.name)))].sort(byStr);
|
|
12493
|
+
if (exportedNames.length) {
|
|
12494
|
+
score += RISK_WEIGHTS.exportedChange;
|
|
12495
|
+
const shown = exportedNames.slice(0, 3).join(", ") + (exportedNames.length > 3 ? ", \u2026" : "");
|
|
12496
|
+
reasons.push(exportedNames.length === 1 ? `exported symbol ${shown} changed` : `exported symbols ${shown} changed`);
|
|
12497
|
+
}
|
|
12498
|
+
const pct = percentile(metricValues, metricOf(m));
|
|
12499
|
+
if (pct >= 0.9) {
|
|
12500
|
+
score += RISK_WEIGHTS.hubHigh;
|
|
12501
|
+
reasons.push(`${metricName} p${Math.round(pct * 100)} hub`);
|
|
12502
|
+
} else if (pct >= 0.75) {
|
|
12503
|
+
score += RISK_WEIGHTS.hubMed;
|
|
12504
|
+
reasons.push(`${metricName} p${Math.round(pct * 100)} hub`);
|
|
12505
|
+
}
|
|
12506
|
+
const depthByRel = /* @__PURE__ */ new Map();
|
|
12507
|
+
const impModules = /* @__PURE__ */ new Set();
|
|
12508
|
+
for (const c2 of moduleChanges) {
|
|
12509
|
+
const imp = impactOf(graph, c2.path, depth);
|
|
12510
|
+
if (!imp) continue;
|
|
12511
|
+
for (const f of imp.files) {
|
|
12512
|
+
const prev = depthByRel.get(f.rel);
|
|
12513
|
+
if (prev === void 0 || f.depth < prev) depthByRel.set(f.rel, f.depth);
|
|
12514
|
+
}
|
|
12515
|
+
for (const im of imp.modules) if (im !== slug) impModules.add(im);
|
|
12516
|
+
}
|
|
12517
|
+
const transitiveFiles = depthByRel.size;
|
|
12518
|
+
const directFiles = [...depthByRel.values()].filter((d) => d === 1).length;
|
|
12519
|
+
const impact = { directFiles, transitiveFiles, modules: [...impModules].sort(byStr) };
|
|
12520
|
+
if (transitiveFiles >= 20 || impact.modules.length >= 5) {
|
|
12521
|
+
score += RISK_WEIGHTS.blastHigh;
|
|
12522
|
+
reasons.push(`${transitiveFiles} dependent files across ${impact.modules.length} modules (depth ${depth})`);
|
|
12523
|
+
} else if (transitiveFiles >= 5) {
|
|
12524
|
+
score += RISK_WEIGHTS.blastMed;
|
|
12525
|
+
reasons.push(`${transitiveFiles} dependent files across ${impact.modules.length} modules (depth ${depth})`);
|
|
12526
|
+
}
|
|
12527
|
+
const testable = m.tier <= 1 && m.symbols > 0 && nonTestCode.has(slug);
|
|
12528
|
+
const coveredBy = m.testedBy ?? [];
|
|
12529
|
+
const tests = testable ? coveredBy.length ? { status: "covered", files: coveredBy } : { status: "gap", files: [] } : { status: "n/a", files: [] };
|
|
12530
|
+
if (tests.status === "gap") {
|
|
12531
|
+
score += RISK_WEIGHTS.testGap;
|
|
12532
|
+
reasons.push("no test covers this module");
|
|
12533
|
+
}
|
|
12534
|
+
const sup = (graph.surprises ?? []).find((s) => s.from === slug || s.to === slug);
|
|
12535
|
+
if (sup) {
|
|
12536
|
+
score += RISK_WEIGHTS.surprise;
|
|
12537
|
+
reasons.push(`cross-community edge to ${sup.from === slug ? sup.to : sup.from} (surprising)`);
|
|
12538
|
+
}
|
|
12539
|
+
const moduleDangling = dangling.filter((d) => moduleChanges.some((c2) => c2.path === d.from));
|
|
12540
|
+
if (moduleDangling.length) {
|
|
12541
|
+
score += RISK_WEIGHTS.dangling;
|
|
12542
|
+
const first = moduleDangling[0];
|
|
12543
|
+
const more = moduleDangling.length > 1 ? ` (+${moduleDangling.length - 1} more)` : "";
|
|
12544
|
+
reasons.push(`dangling import "${first.spec}" in ${first.from}${more}`);
|
|
12545
|
+
}
|
|
12546
|
+
score = Math.min(100, score);
|
|
12547
|
+
const changedFiles = moduleChanges.map((c2) => c2.path).sort(byStr);
|
|
12548
|
+
const allSyms = moduleChanges.flatMap((c2) => c2.symbols);
|
|
12549
|
+
const open = moduleChanges.slice().sort(
|
|
12550
|
+
(a, b) => b.symbols.filter((s) => s.exported).length - a.symbols.filter((s) => s.exported).length || b.symbols.length - a.symbols.length || byStr(a.path, b.path)
|
|
12551
|
+
).slice(0, OPEN_CAP).map((c2) => c2.path);
|
|
12552
|
+
modules.push({
|
|
12553
|
+
slug,
|
|
12554
|
+
path: m.path,
|
|
12555
|
+
score,
|
|
12556
|
+
bucket: score >= HIGH_MIN ? "HIGH" : score >= MEDIUM_MIN ? "MEDIUM" : "LOW",
|
|
12557
|
+
reasons,
|
|
12558
|
+
changedFiles,
|
|
12559
|
+
changedSymbols: {
|
|
12560
|
+
total: new Set(allSyms.map((s) => `${s.name}:${s.line}`)).size,
|
|
12561
|
+
exported: new Set(allSyms.filter((s) => s.exported).map((s) => `${s.name}:${s.line}`)).size
|
|
12562
|
+
},
|
|
12563
|
+
impact,
|
|
12564
|
+
tests,
|
|
12565
|
+
open
|
|
12566
|
+
});
|
|
12567
|
+
}
|
|
12568
|
+
modules.sort((a, b) => b.score - a.score || byStr(a.slug, b.slug));
|
|
12569
|
+
return {
|
|
12570
|
+
base: diff.base,
|
|
12571
|
+
...graph.commit !== void 0 ? { indexCommit: graph.commit } : {},
|
|
12572
|
+
depth,
|
|
12573
|
+
changes,
|
|
12574
|
+
modules,
|
|
12575
|
+
dangling,
|
|
12576
|
+
deleted: deleted.sort(byStr),
|
|
12577
|
+
unindexed: unindexed.sort(byStr),
|
|
12578
|
+
notes
|
|
12579
|
+
};
|
|
12580
|
+
}
|
|
12581
|
+
function deltaFor(repo, graph, symbols, opts = {}) {
|
|
12582
|
+
if (!have("git")) return { error: "git is required for delta and was not found on PATH" };
|
|
12583
|
+
if (!isGitWorktree(repo)) return { error: `delta needs a git worktree \u2014 ${repo} is not inside one` };
|
|
12584
|
+
const notes = [];
|
|
12585
|
+
let base;
|
|
12586
|
+
if (opts.staged) {
|
|
12587
|
+
const head = sh("git", ["-C", repo, "rev-parse", "HEAD"]);
|
|
12588
|
+
if (!head.ok) return { error: "cannot resolve HEAD \u2014 empty repository?" };
|
|
12589
|
+
base = { ref: "HEAD", mergeBase: head.stdout.trim(), staged: true };
|
|
12590
|
+
} else {
|
|
12591
|
+
const r = resolveBaseRef(repo, opts.base);
|
|
12592
|
+
if ("error" in r) return { error: r.error };
|
|
12593
|
+
if (r.note) notes.push(r.note);
|
|
12594
|
+
base = { ref: r.ref, mergeBase: r.mergeBase, staged: false };
|
|
12595
|
+
}
|
|
12596
|
+
const spec = opts.staged ? { staged: true } : { mergeBase: base.mergeBase };
|
|
12597
|
+
const files = diffFiles(repo, spec);
|
|
12598
|
+
if (!opts.staged) {
|
|
12599
|
+
const known = new Set(files.map((f) => f.path));
|
|
12600
|
+
for (const u of untrackedFiles(repo)) {
|
|
12601
|
+
if (!known.has(u)) files.push({ path: u, status: "added" });
|
|
12602
|
+
}
|
|
12603
|
+
}
|
|
12604
|
+
return computeDelta(graph, symbols, { files, hunks: diffHunks(repo, spec), base, notes }, opts.depth ?? DEFAULT_DELTA_DEPTH);
|
|
12605
|
+
}
|
|
12606
|
+
function formatDeltaPanel(res) {
|
|
12607
|
+
const mb = res.base.mergeBase.slice(0, 7);
|
|
12608
|
+
const vs = `${res.base.staged ? "staged vs " : ""}${res.base.ref}`;
|
|
12609
|
+
if (!res.changes.length && !res.unindexed.length) {
|
|
12610
|
+
return `codeindex: no changes vs ${vs} (merge-base ${mb})
|
|
12611
|
+
`;
|
|
12612
|
+
}
|
|
12613
|
+
const changedCount = res.changes.length + res.unindexed.length;
|
|
12614
|
+
const lines = [
|
|
12615
|
+
`codeindex: delta vs ${vs} (merge-base ${mb}) \u2014 ${changedCount} changed file(s), ${res.modules.length} module(s)${res.indexCommit ? `, index @ ${res.indexCommit}` : ""}`
|
|
12616
|
+
];
|
|
12617
|
+
for (const n of res.notes) lines.push(` note: ${n}`);
|
|
12618
|
+
for (const m of res.modules) {
|
|
12619
|
+
lines.push(` ${m.bucket.padEnd(6)} ${m.slug} score ${m.score}${m.reasons.length ? ` \u2014 ${m.reasons.join("; ")}` : ""}`);
|
|
12620
|
+
const tests = m.tests.status === "gap" ? "GAP" : m.tests.status === "covered" ? `covered (${m.tests.files.length})` : "n/a";
|
|
12621
|
+
lines.push(` open: ${m.open.join(", ") || "\u2014"} \xB7 tests: ${tests}`);
|
|
12622
|
+
}
|
|
12623
|
+
if (res.dangling.length) {
|
|
12624
|
+
lines.push(` dangling: ${res.dangling.map((d) => `${d.spec} (from ${d.from})`).join(" \xB7 ")}`);
|
|
12625
|
+
}
|
|
12626
|
+
if (res.deleted.length) lines.push(` deleted: ${res.deleted.join(", ")}`);
|
|
12627
|
+
if (res.unindexed.length) lines.push(` unindexed: ${res.unindexed.join(", ")}`);
|
|
12628
|
+
return lines.join("\n") + "\n";
|
|
12629
|
+
}
|
|
12630
|
+
|
|
12631
|
+
// src/engine.ts
|
|
11733
12632
|
init_mcp();
|
|
11734
12633
|
init_rewrite();
|
|
11735
12634
|
init_hash();
|
|
@@ -11740,13 +12639,14 @@ init_util();
|
|
|
11740
12639
|
init_types();
|
|
11741
12640
|
init_types();
|
|
11742
12641
|
init_loader();
|
|
11743
|
-
import { existsSync as
|
|
11744
|
-
import { join as
|
|
12642
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
12643
|
+
import { join as join17, resolve as resolve2 } from "path";
|
|
11745
12644
|
init_pipeline();
|
|
11746
12645
|
init_hash();
|
|
11747
12646
|
init_graph_json();
|
|
11748
12647
|
init_symbols_json();
|
|
11749
12648
|
init_scan();
|
|
12649
|
+
init_preload();
|
|
11750
12650
|
init_walk();
|
|
11751
12651
|
init_callers();
|
|
11752
12652
|
init_workspaces();
|
|
@@ -11814,6 +12714,13 @@ Commands:
|
|
|
11814
12714
|
complexity Cyclomatic-complexity estimates, most-complex first. Pass a file
|
|
11815
12715
|
positional for one file; omit for the repo-wide top
|
|
11816
12716
|
risk Complexity \xD7 git-churn ranking (JSON; --since <ref> to bound)
|
|
12717
|
+
delta Review panel for the git diff: changed files -> enclosing symbols ->
|
|
12718
|
+
blast radius -> risk score with explained reasons
|
|
12719
|
+
(--base <ref> | --staged, --depth <n>, --json)
|
|
12720
|
+
impact Reverse dependency closure of a file or module: everything that
|
|
12721
|
+
transitively imports/uses/calls it (--depth <n>; JSON)
|
|
12722
|
+
neighbors Graph neighbours of a file or module, both directions
|
|
12723
|
+
(--depth <n>, --kind import,call,use,doc-link,mention; JSON)
|
|
11817
12724
|
mermaid Mermaid diagram of the module graph; pass a module positional to
|
|
11818
12725
|
focus on one neighborhood
|
|
11819
12726
|
rewrite Map an expensive tree-wide search onto its indexed equivalent:
|
|
@@ -11828,7 +12735,10 @@ Commands:
|
|
|
11828
12735
|
check_rules, the memory quartet and the three symbolic-edit
|
|
11829
12736
|
writes). Flags: --repo <dir> pins ONE repository so the per-tool
|
|
11830
12737
|
repo argument becomes optional (an explicit per-call repo still
|
|
11831
|
-
wins); --server-name <name> overrides the announced serverInfo
|
|
12738
|
+
wins); --server-name <name> overrides the announced serverInfo;
|
|
12739
|
+
--max-response-bytes <n> caps a single tool response (default 1e6;
|
|
12740
|
+
a response under the cap is byte-identical, one over it is
|
|
12741
|
+
replaced by an actionable notice instead of an unusable blob)
|
|
11832
12742
|
version Print the engine version
|
|
11833
12743
|
|
|
11834
12744
|
Flags (accepted before OR after the subcommand: '--repo X scan' and
|
|
@@ -11848,6 +12758,16 @@ Flags (accepted before OR after the subcommand: '--repo X scan' and
|
|
|
11848
12758
|
--max-bytes <n> Skip files above this size (default 1 MiB)
|
|
11849
12759
|
--max-calls <n> Per-file call-site cap for extraction (default 512)
|
|
11850
12760
|
--no-ast Skip tree-sitter grammars even when present (regex tier)
|
|
12761
|
+
--workers <n> \`index\`: extraction worker threads (default: cores-1,
|
|
12762
|
+
capped at 8; 0 or 1 forces the single-threaded path).
|
|
12763
|
+
Also settable with CODEINDEX_WORKERS. Artifacts are
|
|
12764
|
+
byte-identical either way
|
|
12765
|
+
--index <dir> Persisted index the READ commands reuse, relative to the
|
|
12766
|
+
repo (default .codeindex \u2014 i.e. what \`index --out\` wrote
|
|
12767
|
+
there). A fresh index turns the scan into a stat pass and,
|
|
12768
|
+
when it still matches the worktree, skips the pipeline
|
|
12769
|
+
entirely. Stale/absent/corrupt \u2192 a normal cold build
|
|
12770
|
+
--no-index-cache Never reuse a persisted index; always build from scratch
|
|
11851
12771
|
--config <file> Rules config for \`rules\` (JSON: [{name, from, to, \u2026}])
|
|
11852
12772
|
--limit <n> Max results for \`search\` (default 20)
|
|
11853
12773
|
--no-fuzzy \`search\`: disable trigram fuzzy fallback for query terms
|
|
@@ -11871,7 +12791,7 @@ function parseFlags(args2) {
|
|
|
11871
12791
|
if (v === void 0) throw new Error(`missing value for ${a}`);
|
|
11872
12792
|
return v;
|
|
11873
12793
|
};
|
|
11874
|
-
const
|
|
12794
|
+
const num2 = () => {
|
|
11875
12795
|
const raw = next();
|
|
11876
12796
|
const n = Number(raw);
|
|
11877
12797
|
if (!Number.isFinite(n) || n <= 0) throw new Error(`${a} expects a positive number, got "${raw}"`);
|
|
@@ -11887,20 +12807,32 @@ function parseFlags(args2) {
|
|
|
11887
12807
|
else if (a === "--scope") flags2.scope = next();
|
|
11888
12808
|
else if (a === "--no-gitignore") flags2.gitignore = false;
|
|
11889
12809
|
else if (a === "--ignore-dir") flags2.ignoreDirs.push(next());
|
|
11890
|
-
else if (a === "--max-files") flags2.maxFiles =
|
|
11891
|
-
else if (a === "--max-bytes") flags2.maxBytes =
|
|
11892
|
-
else if (a === "--max-calls") flags2.maxCalls =
|
|
12810
|
+
else if (a === "--max-files") flags2.maxFiles = num2();
|
|
12811
|
+
else if (a === "--max-bytes") flags2.maxBytes = num2();
|
|
12812
|
+
else if (a === "--max-calls") flags2.maxCalls = num2();
|
|
11893
12813
|
else if (a === "--ignore-case") flags2.ignoreCase = true;
|
|
11894
|
-
else if (a === "--max-hits") flags2.maxHits =
|
|
11895
|
-
else if (a === "--budget-tokens") flags2.budgetTokens =
|
|
12814
|
+
else if (a === "--max-hits") flags2.maxHits = num2();
|
|
12815
|
+
else if (a === "--budget-tokens") flags2.budgetTokens = num2();
|
|
11896
12816
|
else if (a === "--no-ast") flags2.noAst = true;
|
|
11897
|
-
else if (a === "--
|
|
12817
|
+
else if (a === "--index") flags2.indexDir = next();
|
|
12818
|
+
else if (a === "--no-index-cache") flags2.noIndexCache = true;
|
|
12819
|
+
else if (a === "--workers") {
|
|
12820
|
+
const raw = next();
|
|
12821
|
+
const n = Number(raw);
|
|
12822
|
+
if (!Number.isInteger(n) || n < 0) throw new Error(`--workers expects a non-negative integer, got "${raw}"`);
|
|
12823
|
+
flags2.workers = n;
|
|
12824
|
+
} else if (a === "--since") flags2.since = next();
|
|
11898
12825
|
else if (a === "--config") flags2.config = resolve2(next());
|
|
11899
|
-
else if (a === "--limit") flags2.limit =
|
|
12826
|
+
else if (a === "--limit") flags2.limit = num2();
|
|
11900
12827
|
else if (a === "--no-fuzzy") flags2.fuzzy = false;
|
|
11901
12828
|
else if (a === "--semantic") flags2.semantic = true;
|
|
11902
12829
|
else if (a === "--recall") flags2.recall = true;
|
|
11903
12830
|
else if (a === "--run") flags2.run = true;
|
|
12831
|
+
else if (a === "--base") flags2.base = next();
|
|
12832
|
+
else if (a === "--staged") flags2.staged = true;
|
|
12833
|
+
else if (a === "--depth") flags2.depth = num2();
|
|
12834
|
+
else if (a === "--kind") flags2.kind = next();
|
|
12835
|
+
else if (a === "--json") flags2.json = true;
|
|
11904
12836
|
else if (!a.startsWith("--") && flags2.positional === void 0) flags2.positional = a;
|
|
11905
12837
|
else throw new Error(`unknown flag: ${a}`);
|
|
11906
12838
|
}
|
|
@@ -11930,6 +12862,7 @@ var SCANLESS_COMMANDS = /* @__PURE__ */ new Set(["grep", "churn", "coupling", "w
|
|
|
11930
12862
|
function parseMcpFlags(argv) {
|
|
11931
12863
|
let defaultRepo;
|
|
11932
12864
|
let name2;
|
|
12865
|
+
let maxResponseBytes;
|
|
11933
12866
|
for (let i2 = 0; i2 < argv.length; i2++) {
|
|
11934
12867
|
const a = argv[i2];
|
|
11935
12868
|
if (a === "--repo") {
|
|
@@ -11940,12 +12873,17 @@ function parseMcpFlags(argv) {
|
|
|
11940
12873
|
const v = argv[++i2];
|
|
11941
12874
|
if (!v) throw new Error("--server-name requires a value");
|
|
11942
12875
|
name2 = v;
|
|
12876
|
+
} else if (a === "--max-response-bytes") {
|
|
12877
|
+
const v = argv[++i2];
|
|
12878
|
+
const n = Number(v);
|
|
12879
|
+
if (!v || !Number.isFinite(n) || n <= 0) throw new Error("--max-response-bytes requires a positive number");
|
|
12880
|
+
maxResponseBytes = n;
|
|
11943
12881
|
} else {
|
|
11944
12882
|
throw new Error(`unknown flag for \`mcp\`: ${a}`);
|
|
11945
12883
|
}
|
|
11946
12884
|
}
|
|
11947
|
-
if (defaultRepo && !
|
|
11948
|
-
return { defaultRepo, serverInfo: name2 ? { name: name2 } : void 0 };
|
|
12885
|
+
if (defaultRepo && !existsSync7(defaultRepo)) throw new Error(`--repo path does not exist: ${defaultRepo}`);
|
|
12886
|
+
return { defaultRepo, serverInfo: name2 ? { name: name2 } : void 0, maxResponseBytes };
|
|
11949
12887
|
}
|
|
11950
12888
|
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
11951
12889
|
"--repo",
|
|
@@ -11963,7 +12901,10 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
11963
12901
|
"--since",
|
|
11964
12902
|
"--config",
|
|
11965
12903
|
"--limit",
|
|
11966
|
-
"--server-name"
|
|
12904
|
+
"--server-name",
|
|
12905
|
+
"--workers",
|
|
12906
|
+
"--index",
|
|
12907
|
+
"--max-response-bytes"
|
|
11967
12908
|
]);
|
|
11968
12909
|
function hoistLeadingFlags(argv) {
|
|
11969
12910
|
const lead = [];
|
|
@@ -12008,7 +12949,7 @@ async function runCli(rawArgv) {
|
|
|
12008
12949
|
return;
|
|
12009
12950
|
}
|
|
12010
12951
|
const flags2 = parseFlags(rest);
|
|
12011
|
-
if (!
|
|
12952
|
+
if (!existsSync7(flags2.repo)) throw new Error(`--repo path does not exist: ${flags2.repo}`);
|
|
12012
12953
|
const scans = !SCANLESS_COMMANDS.has(cmd) && !(cmd === "embed" && flags2.positional !== "build");
|
|
12013
12954
|
let precomputedWalk;
|
|
12014
12955
|
if (scans && !flags2.noAst) {
|
|
@@ -12020,15 +12961,33 @@ async function runCli(rawArgv) {
|
|
|
12020
12961
|
});
|
|
12021
12962
|
await ensureGrammars(grammarKeysForExts(precomputedWalk.files.map((f) => f.ext)));
|
|
12022
12963
|
}
|
|
12964
|
+
const indexDir = flags2.indexDir ?? INDEX_DIR;
|
|
12965
|
+
let preloadTried = false;
|
|
12966
|
+
let preloaded;
|
|
12967
|
+
const tryPreload = () => {
|
|
12968
|
+
if (preloadTried) return preloaded;
|
|
12969
|
+
preloadTried = true;
|
|
12970
|
+
if (flags2.noIndexCache) return void 0;
|
|
12971
|
+
const p = preloadSession(flags2.repo, scanOptions(flags2, precomputedWalk), indexDir);
|
|
12972
|
+
if (p) preloaded = { scan: p.scan, arts: p.arts };
|
|
12973
|
+
return preloaded;
|
|
12974
|
+
};
|
|
12975
|
+
const readScan = () => tryPreload()?.scan ?? scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
12976
|
+
const readArtifacts = () => {
|
|
12977
|
+
const p = tryPreload();
|
|
12978
|
+
if (p?.arts) return p.arts;
|
|
12979
|
+
if (p) return buildArtifactsFromScan(p.scan, scanOptions(flags2, precomputedWalk));
|
|
12980
|
+
return buildIndexArtifacts(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
12981
|
+
};
|
|
12023
12982
|
if (cmd === "index") {
|
|
12024
12983
|
if (!flags2.out) throw new Error("index needs --out <dir>");
|
|
12025
12984
|
const outDir = flags2.out;
|
|
12026
12985
|
mkdirSync3(outDir, { recursive: true });
|
|
12027
|
-
const cachePath =
|
|
12986
|
+
const cachePath = join17(outDir, "cache.json");
|
|
12028
12987
|
let cache;
|
|
12029
12988
|
let meta = {};
|
|
12030
12989
|
try {
|
|
12031
|
-
const parsed = JSON.parse(
|
|
12990
|
+
const parsed = JSON.parse(readFileSync9(cachePath, "utf8"));
|
|
12032
12991
|
if (parsed.schemaVersion === SCHEMA_VERSION && parsed.extractorVersion === EXTRACTOR_VERSION) {
|
|
12033
12992
|
cache = new Map(Object.entries(parsed.files));
|
|
12034
12993
|
meta = {
|
|
@@ -12041,15 +13000,20 @@ async function runCli(rawArgv) {
|
|
|
12041
13000
|
}
|
|
12042
13001
|
} catch {
|
|
12043
13002
|
}
|
|
12044
|
-
const scan2 =
|
|
13003
|
+
const scan2 = await scanRepoParallel(flags2.repo, {
|
|
13004
|
+
...scanOptions(flags2, precomputedWalk),
|
|
13005
|
+
cache,
|
|
13006
|
+
out: outDir,
|
|
13007
|
+
workers: flags2.workers
|
|
13008
|
+
});
|
|
12045
13009
|
const modelDir = resolveEmbedModelDir(flags2.repo);
|
|
12046
13010
|
const model = modelDir ? loadEmbedModel(modelDir) : void 0;
|
|
12047
|
-
const graphPath =
|
|
12048
|
-
const symbolsPath =
|
|
12049
|
-
const embedPath =
|
|
13011
|
+
const graphPath = join17(outDir, "graph.json");
|
|
13012
|
+
const symbolsPath = join17(outDir, "symbols.json");
|
|
13013
|
+
const embedPath = join17(outDir, "embeddings.bin");
|
|
12050
13014
|
const artifactSha = (path) => {
|
|
12051
13015
|
try {
|
|
12052
|
-
return sha1(
|
|
13016
|
+
return sha1(readFileSync9(path));
|
|
12053
13017
|
} catch {
|
|
12054
13018
|
return void 0;
|
|
12055
13019
|
}
|
|
@@ -12104,23 +13068,23 @@ async function runCli(rawArgv) {
|
|
|
12104
13068
|
`);
|
|
12105
13069
|
}
|
|
12106
13070
|
} else if (cmd === "scan") {
|
|
12107
|
-
const
|
|
13071
|
+
const s = scanSummary(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
12108
13072
|
const summary = {
|
|
12109
13073
|
engineVersion: ENGINE_VERSION,
|
|
12110
|
-
commit:
|
|
12111
|
-
fileCount:
|
|
12112
|
-
languages:
|
|
12113
|
-
capped:
|
|
13074
|
+
commit: s.commit,
|
|
13075
|
+
fileCount: s.fileCount,
|
|
13076
|
+
languages: s.languages,
|
|
13077
|
+
capped: s.capped
|
|
12114
13078
|
};
|
|
12115
13079
|
emit(JSON.stringify(summary, null, 2) + "\n", flags2.out);
|
|
12116
13080
|
} else if (cmd === "graph") {
|
|
12117
|
-
const { graph } =
|
|
13081
|
+
const { graph } = readArtifacts();
|
|
12118
13082
|
emit(renderGraphJson(graph), flags2.out);
|
|
12119
13083
|
} else if (cmd === "symbols") {
|
|
12120
|
-
const { symbols } =
|
|
13084
|
+
const { symbols } = readArtifacts();
|
|
12121
13085
|
emit(renderSymbolsJson(symbols), flags2.out);
|
|
12122
13086
|
} else if (cmd === "scip") {
|
|
12123
|
-
const scan2 =
|
|
13087
|
+
const scan2 = readScan();
|
|
12124
13088
|
const bytes = renderScip(scan2, { projectRoot: flags2.projectRoot });
|
|
12125
13089
|
const out2 = flags2.out ?? resolve2("index.scip");
|
|
12126
13090
|
if (out2 === "-") process.stdout.write(Buffer.from(bytes));
|
|
@@ -12130,14 +13094,14 @@ async function runCli(rawArgv) {
|
|
|
12130
13094
|
`);
|
|
12131
13095
|
}
|
|
12132
13096
|
} else if (cmd === "callers") {
|
|
12133
|
-
const scan2 =
|
|
13097
|
+
const scan2 = readScan();
|
|
12134
13098
|
const index = buildCallerIndex(scan2, void 0, { recall: flags2.recall });
|
|
12135
13099
|
const obj = {};
|
|
12136
13100
|
for (const [name2, entry] of index) obj[name2] = entry;
|
|
12137
13101
|
emit(JSON.stringify(obj, null, 2) + "\n", flags2.out);
|
|
12138
13102
|
} else if (cmd === "search") {
|
|
12139
13103
|
if (!flags2.positional) throw new Error('search needs a query: cli.mjs search "<query>" --repo <dir>');
|
|
12140
|
-
const scan2 =
|
|
13104
|
+
const scan2 = readScan();
|
|
12141
13105
|
if (flags2.semantic) {
|
|
12142
13106
|
const endpoint = resolveEmbedEndpoint();
|
|
12143
13107
|
const lexical = () => {
|
|
@@ -12228,16 +13192,16 @@ async function runCli(rawArgv) {
|
|
|
12228
13192
|
}
|
|
12229
13193
|
const model = loadEmbedModel(modelDir);
|
|
12230
13194
|
mkdirSync3(flags2.out, { recursive: true });
|
|
12231
|
-
const scan2 =
|
|
13195
|
+
const scan2 = readScan();
|
|
12232
13196
|
const index = buildEmbeddingIndex(scan2, model);
|
|
12233
|
-
writeFileSync4(
|
|
13197
|
+
writeFileSync4(join17(flags2.out, "embeddings.bin"), serializeEmbeddings(index));
|
|
12234
13198
|
process.stderr.write(`codeindex: ${index.records.length} embedding records \u2192 ${flags2.out}/embeddings.bin (model ${model.modelId})
|
|
12235
13199
|
`);
|
|
12236
13200
|
} else if (sub === "pull") {
|
|
12237
13201
|
const { url, sha256 } = resolveEmbedPullUrl();
|
|
12238
|
-
const destDir = process.env.CODEINDEX_EMBED_DIR ??
|
|
13202
|
+
const destDir = process.env.CODEINDEX_EMBED_DIR ?? join17(flags2.repo, ".codeindex", "models");
|
|
12239
13203
|
mkdirSync3(destDir, { recursive: true });
|
|
12240
|
-
process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${
|
|
13204
|
+
process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${join17(destDir, "model.json")}
|
|
12241
13205
|
`);
|
|
12242
13206
|
let body2;
|
|
12243
13207
|
try {
|
|
@@ -12258,8 +13222,8 @@ async function runCli(rawArgv) {
|
|
|
12258
13222
|
process.exitCode = 1;
|
|
12259
13223
|
return;
|
|
12260
13224
|
}
|
|
12261
|
-
writeFileSync4(
|
|
12262
|
-
process.stderr.write(`codeindex: model written to ${
|
|
13225
|
+
writeFileSync4(join17(destDir, "model.json"), body2);
|
|
13226
|
+
process.stderr.write(`codeindex: model written to ${join17(destDir, "model.json")}
|
|
12263
13227
|
`);
|
|
12264
13228
|
} else {
|
|
12265
13229
|
throw new Error("embed needs a subcommand: status | build | pull | serve");
|
|
@@ -12269,7 +13233,7 @@ async function runCli(rawArgv) {
|
|
|
12269
13233
|
const cacheDir = sharedGrammarsCacheDir();
|
|
12270
13234
|
if (sub === "status") {
|
|
12271
13235
|
const info2 = resolveGrammarsTier();
|
|
12272
|
-
const runtimePresent = info2.dir ?
|
|
13236
|
+
const runtimePresent = info2.dir ? existsSync7(join17(info2.dir, "web-tree-sitter.wasm")) : false;
|
|
12273
13237
|
const target = resolveGrammarsPullTarget();
|
|
12274
13238
|
const status = {
|
|
12275
13239
|
engineVersion: ENGINE_VERSION,
|
|
@@ -12290,8 +13254,8 @@ async function runCli(rawArgv) {
|
|
|
12290
13254
|
}
|
|
12291
13255
|
} else if (cmd === "rules") {
|
|
12292
13256
|
if (!flags2.config) throw new Error("rules needs --config <codeindex.rules.json>");
|
|
12293
|
-
const rules = parseRules(JSON.parse(
|
|
12294
|
-
const { graph } =
|
|
13257
|
+
const rules = parseRules(JSON.parse(readFileSync9(flags2.config, "utf8")));
|
|
13258
|
+
const { graph } = readArtifacts();
|
|
12295
13259
|
const violations = checkRules(graph, rules);
|
|
12296
13260
|
const errors = violations.filter((v) => v.severity === "error").length;
|
|
12297
13261
|
emit(JSON.stringify({ errors, warnings: violations.length - errors, violations }, null, 2) + "\n", flags2.out);
|
|
@@ -12312,26 +13276,48 @@ async function runCli(rawArgv) {
|
|
|
12312
13276
|
for (const k of [...churn.keys()].sort()) sorted[k] = churn.get(k);
|
|
12313
13277
|
emit(JSON.stringify({ ok, churn: sorted }, null, 2) + "\n", flags2.out);
|
|
12314
13278
|
} else if (cmd === "repomap") {
|
|
12315
|
-
const { scan: scan2, graph } =
|
|
13279
|
+
const { scan: scan2, graph } = readArtifacts();
|
|
12316
13280
|
emit(renderRepoMap(scan2, graph, { budgetTokens: flags2.budgetTokens }), flags2.out);
|
|
12317
13281
|
} else if (cmd === "hotspots") {
|
|
12318
|
-
const scan2 =
|
|
13282
|
+
const scan2 = readScan();
|
|
12319
13283
|
const { churn, ok } = gitChurn(flags2.repo, { since: flags2.since });
|
|
12320
13284
|
emit(JSON.stringify({ churnOk: ok, hotspots: rankHotspots(scan2, churn) }, null, 2) + "\n", flags2.out);
|
|
12321
13285
|
} else if (cmd === "coupling") {
|
|
12322
13286
|
const { ok, couplings } = changeCoupling(flags2.repo, { since: flags2.since });
|
|
12323
13287
|
emit(JSON.stringify({ ok, couplings }, null, 2) + "\n", flags2.out);
|
|
12324
13288
|
} else if (cmd === "deadcode") {
|
|
12325
|
-
emit(JSON.stringify(findDeadCode(
|
|
13289
|
+
emit(JSON.stringify(findDeadCode(readScan()), null, 2) + "\n", flags2.out);
|
|
12326
13290
|
} else if (cmd === "complexity") {
|
|
12327
|
-
const scan2 =
|
|
13291
|
+
const scan2 = readScan();
|
|
12328
13292
|
emit(JSON.stringify(symbolComplexity(scan2, flags2.positional), null, 2) + "\n", flags2.out);
|
|
12329
13293
|
} else if (cmd === "risk") {
|
|
12330
|
-
const scan2 =
|
|
13294
|
+
const scan2 = readScan();
|
|
12331
13295
|
const { churn, ok } = gitChurn(flags2.repo, { since: flags2.since });
|
|
12332
13296
|
emit(JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn) }, null, 2) + "\n", flags2.out);
|
|
13297
|
+
} else if (cmd === "delta") {
|
|
13298
|
+
const { graph, symbols } = readArtifacts();
|
|
13299
|
+
const res = deltaFor(flags2.repo, graph, symbols, {
|
|
13300
|
+
base: flags2.base,
|
|
13301
|
+
staged: flags2.staged,
|
|
13302
|
+
depth: flags2.depth
|
|
13303
|
+
});
|
|
13304
|
+
if ("error" in res) throw new Error(res.error);
|
|
13305
|
+
emit(flags2.json ? JSON.stringify(res, null, 2) + "\n" : formatDeltaPanel(res), flags2.out);
|
|
13306
|
+
} else if (cmd === "impact") {
|
|
13307
|
+
if (!flags2.positional) throw new Error("impact needs a target: cli.mjs impact <file|module> --repo <dir>");
|
|
13308
|
+
const { graph } = readArtifacts();
|
|
13309
|
+
const res = impactOf(graph, flags2.positional, flags2.depth ?? Infinity);
|
|
13310
|
+
if (!res) throw new Error(`no such file or module in the index: ${flags2.positional}`);
|
|
13311
|
+
emit(JSON.stringify(res, null, 2) + "\n", flags2.out);
|
|
13312
|
+
} else if (cmd === "neighbors") {
|
|
13313
|
+
if (!flags2.positional) throw new Error("neighbors needs a target: cli.mjs neighbors <file|module> --repo <dir>");
|
|
13314
|
+
const { graph } = readArtifacts();
|
|
13315
|
+
const kinds = flags2.kind ? new Set(flags2.kind.split(",").map((k) => k.trim()).filter(Boolean)) : void 0;
|
|
13316
|
+
const res = neighborsOf(graph, flags2.positional, flags2.depth ?? 1, kinds);
|
|
13317
|
+
if (!res) throw new Error(`no such file or module in the index: ${flags2.positional}`);
|
|
13318
|
+
emit(JSON.stringify(res, null, 2) + "\n", flags2.out);
|
|
12333
13319
|
} else if (cmd === "mermaid") {
|
|
12334
|
-
const { graph } =
|
|
13320
|
+
const { graph } = readArtifacts();
|
|
12335
13321
|
emit(renderMermaid(graph, { module: flags2.positional }), flags2.out);
|
|
12336
13322
|
} else if (cmd === "grep") {
|
|
12337
13323
|
if (!flags2.positional) throw new Error("grep needs a pattern: cli.mjs grep <pattern> --repo <dir>");
|
|
@@ -12351,12 +13337,15 @@ ${HELP}`);
|
|
|
12351
13337
|
}
|
|
12352
13338
|
}
|
|
12353
13339
|
export {
|
|
13340
|
+
DEFAULT_DELTA_DEPTH,
|
|
12354
13341
|
DEFAULT_GRAMMARS_URL,
|
|
12355
13342
|
DEFAULT_MAX_FILES,
|
|
12356
13343
|
EMBED_VERSION,
|
|
12357
13344
|
ENGINE_VERSION,
|
|
12358
13345
|
EXTRACTOR_VERSION,
|
|
13346
|
+
INDEX_DIR,
|
|
12359
13347
|
MARKDOWN_EXT,
|
|
13348
|
+
RISK_WEIGHTS,
|
|
12360
13349
|
SCHEMA_VERSION,
|
|
12361
13350
|
allGrammarKeys,
|
|
12362
13351
|
applyCentrality,
|
|
@@ -12364,6 +13353,7 @@ export {
|
|
|
12364
13353
|
betweennessOf,
|
|
12365
13354
|
buildArtifactsFromScan,
|
|
12366
13355
|
buildCallerIndex,
|
|
13356
|
+
buildCodeRecord,
|
|
12367
13357
|
buildEmbeddingIndex,
|
|
12368
13358
|
buildEndpointIndex,
|
|
12369
13359
|
buildGraph,
|
|
@@ -12384,11 +13374,13 @@ export {
|
|
|
12384
13374
|
communityOf,
|
|
12385
13375
|
compileGlobs,
|
|
12386
13376
|
complexityOfSource,
|
|
13377
|
+
computeDelta,
|
|
12387
13378
|
computeImportPairs,
|
|
12388
13379
|
computeSurprises,
|
|
12389
13380
|
computeSymbolRefs,
|
|
12390
13381
|
computeTestMap,
|
|
12391
13382
|
deleteMemory,
|
|
13383
|
+
deltaFor,
|
|
12392
13384
|
deserializeEmbeddings,
|
|
12393
13385
|
detectCommunities,
|
|
12394
13386
|
detectWorkspaces,
|
|
@@ -12406,6 +13398,7 @@ export {
|
|
|
12406
13398
|
extractAst,
|
|
12407
13399
|
extractCode,
|
|
12408
13400
|
extractGrammarsTarball,
|
|
13401
|
+
extractInParallel,
|
|
12409
13402
|
extractMarkdown,
|
|
12410
13403
|
extractSymbols,
|
|
12411
13404
|
extractTarInto,
|
|
@@ -12415,6 +13408,7 @@ export {
|
|
|
12415
13408
|
findReferences,
|
|
12416
13409
|
findSymbol,
|
|
12417
13410
|
foldText,
|
|
13411
|
+
formatDeltaPanel,
|
|
12418
13412
|
gitChurn,
|
|
12419
13413
|
grammarKeyForExt,
|
|
12420
13414
|
grammarKeysForExts,
|
|
@@ -12424,6 +13418,8 @@ export {
|
|
|
12424
13418
|
have,
|
|
12425
13419
|
headCommit,
|
|
12426
13420
|
healthzUrl,
|
|
13421
|
+
hubThreshold,
|
|
13422
|
+
impactOf,
|
|
12427
13423
|
insertAfterSymbol,
|
|
12428
13424
|
insertBeforeSymbol,
|
|
12429
13425
|
intDot,
|
|
@@ -12434,22 +13430,28 @@ export {
|
|
|
12434
13430
|
isSurprising,
|
|
12435
13431
|
isTestFile,
|
|
12436
13432
|
isTestPath,
|
|
13433
|
+
keptCodeFiles,
|
|
12437
13434
|
keywords,
|
|
12438
13435
|
languageOf,
|
|
12439
13436
|
listMemories,
|
|
12440
13437
|
loadEmbedModel,
|
|
13438
|
+
neighborsOf,
|
|
12441
13439
|
pagerankOf,
|
|
12442
13440
|
parseGitignore,
|
|
12443
13441
|
parseRules,
|
|
13442
|
+
preloadArtifacts,
|
|
13443
|
+
preloadSession,
|
|
12444
13444
|
probeEndpoint,
|
|
12445
13445
|
pullGrammars,
|
|
12446
13446
|
quantize,
|
|
12447
13447
|
rankHotspots,
|
|
12448
13448
|
rankedKeywords,
|
|
12449
13449
|
readMemory,
|
|
13450
|
+
readPersistedIndex,
|
|
12450
13451
|
readText,
|
|
12451
13452
|
renderGraphJson,
|
|
12452
13453
|
renderMermaid,
|
|
13454
|
+
renderMermaidClustered,
|
|
12453
13455
|
renderRepoMap,
|
|
12454
13456
|
renderScip,
|
|
12455
13457
|
renderSymbolsJson,
|
|
@@ -12465,13 +13467,17 @@ export {
|
|
|
12465
13467
|
resolveGrammarsTier,
|
|
12466
13468
|
resolveImport,
|
|
12467
13469
|
resolveUniqueSymbol,
|
|
13470
|
+
reverseClosure,
|
|
12468
13471
|
rewriteCommand,
|
|
12469
13472
|
riskHotspots,
|
|
12470
13473
|
roundHalfToEven,
|
|
12471
13474
|
rrf,
|
|
12472
13475
|
runCli,
|
|
13476
|
+
runExtractWorker,
|
|
12473
13477
|
runMcpServer,
|
|
12474
13478
|
scanRepo,
|
|
13479
|
+
scanRepoParallel,
|
|
13480
|
+
scanSummary,
|
|
12475
13481
|
searchIndex,
|
|
12476
13482
|
searchSemantic,
|
|
12477
13483
|
serializeEmbeddings,
|
|
@@ -12482,9 +13488,11 @@ export {
|
|
|
12482
13488
|
slugify,
|
|
12483
13489
|
subtokens,
|
|
12484
13490
|
symbolComplexity,
|
|
13491
|
+
symbolsInHunks,
|
|
12485
13492
|
symbolsOverview,
|
|
12486
13493
|
testsForModule,
|
|
12487
13494
|
tierForPath,
|
|
13495
|
+
toCacheMap,
|
|
12488
13496
|
tokenize,
|
|
12489
13497
|
uniqueSymbolDefs,
|
|
12490
13498
|
untestedModules,
|
|
@@ -12492,6 +13500,7 @@ export {
|
|
|
12492
13500
|
walk,
|
|
12493
13501
|
warmGrammars,
|
|
12494
13502
|
wordpiece,
|
|
13503
|
+
workerCount,
|
|
12495
13504
|
writeMemory
|
|
12496
13505
|
};
|
|
12497
13506
|
// "Copyright" and "@license" are already caught by DIRECTIVE_RE.
|