@maxgfr/codeindex 2.17.1 → 2.19.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 +32 -3
- package/docs/MIGRATION.md +127 -0
- package/package.json +1 -1
- package/scripts/engine.d.mts +242 -18
- package/scripts/engine.mjs +2274 -1084
- 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/protocol.ts +176 -0
- package/src/mcp/session.ts +287 -0
- package/src/mcp/tools.ts +541 -0
- package/src/mcp.ts +176 -668
- 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.19.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();
|
|
8071
|
-
for (const f of scan2.files) {
|
|
8072
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8073
|
-
for (const s of f.symbols) {
|
|
8074
|
-
if (!s.exported || REFERENCE_KINDS3.has(s.kind)) continue;
|
|
8075
|
-
if (seen.has(s.name)) continue;
|
|
8076
|
-
seen.add(s.name);
|
|
8077
|
-
let arr = defs.get(s.name);
|
|
8078
|
-
if (!arr) defs.set(s.name, arr = []);
|
|
8079
|
-
arr.push(s);
|
|
8080
|
-
}
|
|
8081
|
-
}
|
|
8082
|
-
const localDefs = /* @__PURE__ */ new Map();
|
|
8134
|
+
function uniqueSymbolDefs(scan2) {
|
|
8135
|
+
const byName = /* @__PURE__ */ new Map();
|
|
8083
8136
|
for (const f of scan2.files) {
|
|
8084
|
-
const byName = /* @__PURE__ */ new Map();
|
|
8085
8137
|
for (const s of f.symbols) {
|
|
8086
|
-
if (!REFERENCE_KINDS3.has(s.kind)
|
|
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);
|
|
8087
8142
|
}
|
|
8088
|
-
localDefs.set(f.rel, byName);
|
|
8089
8143
|
}
|
|
8090
|
-
const
|
|
8091
|
-
const
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
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;
|
|
8154
|
+
}
|
|
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,487 +10415,216 @@ 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
|
-
// src/mcp.ts
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10468
|
+
// src/mcp/protocol.ts
|
|
10469
|
+
import { existsSync as existsSync6 } from "fs";
|
|
10470
|
+
import { join as join16 } from "path";
|
|
10471
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
10472
|
+
function validateArgs(schema, args2) {
|
|
10473
|
+
const props = schema.properties ?? {};
|
|
10474
|
+
for (const [key, value] of Object.entries(args2)) {
|
|
10475
|
+
if (value === void 0 || value === null) continue;
|
|
10476
|
+
const spec = props[key];
|
|
10477
|
+
if (!spec?.type) continue;
|
|
10478
|
+
const actual = Array.isArray(value) ? "array" : typeof value;
|
|
10479
|
+
if (spec.type === "number") {
|
|
10480
|
+
if (actual === "number") continue;
|
|
10481
|
+
if (actual === "string" && Number.isFinite(Number(value)) && value.trim() !== "") continue;
|
|
10482
|
+
return `\`${key}\` must be a number, got ${actual === "string" ? JSON.stringify(value) : actual}`;
|
|
10483
|
+
}
|
|
10484
|
+
if (spec.type === "array") {
|
|
10485
|
+
if (actual !== "array") return `\`${key}\` must be an array of strings, got ${actual}`;
|
|
10486
|
+
if (spec.items?.type === "string" && !value.every((x) => typeof x === "string")) {
|
|
10487
|
+
return `\`${key}\` must be an array of strings`;
|
|
10488
|
+
}
|
|
10489
|
+
continue;
|
|
10490
|
+
}
|
|
10491
|
+
if (actual !== spec.type) return `\`${key}\` must be a ${spec.type}, got ${actual}`;
|
|
10492
|
+
}
|
|
10493
|
+
return void 0;
|
|
10311
10494
|
}
|
|
10312
|
-
function
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
function errMessage(e) {
|
|
10316
|
-
return e instanceof Error ? e.message : String(e);
|
|
10317
|
-
}
|
|
10318
|
-
function scanFingerprint(scan2) {
|
|
10319
|
-
return sha1(scan2.files.map((f) => `${f.rel}:${f.hash}`).join("\n"));
|
|
10320
|
-
}
|
|
10321
|
-
async function memoizedEmbeddingIndex(key, build) {
|
|
10322
|
-
const cacheKey = `${key.mode}:${key.identity}:${scanFingerprint(key.scan)}`;
|
|
10323
|
-
if (embeddingIndexCache && embeddingIndexCache.key === cacheKey) return embeddingIndexCache.index;
|
|
10324
|
-
const index = await build();
|
|
10325
|
-
embeddingIndexCache = { key: cacheKey, index };
|
|
10326
|
-
return index;
|
|
10327
|
-
}
|
|
10328
|
-
function memoizedEmbedModel(modelDir) {
|
|
10329
|
-
let stat;
|
|
10495
|
+
function structuredContentFor(text, capped, hasSchema) {
|
|
10496
|
+
if (capped || !hasSchema) return void 0;
|
|
10497
|
+
let parsed;
|
|
10330
10498
|
try {
|
|
10331
|
-
|
|
10499
|
+
parsed = JSON.parse(text);
|
|
10332
10500
|
} catch {
|
|
10333
10501
|
return void 0;
|
|
10334
10502
|
}
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
const model = loadEmbedModel(modelDir);
|
|
10338
|
-
if (model) embedModelCache = { key, model };
|
|
10339
|
-
return model;
|
|
10340
|
-
}
|
|
10341
|
-
function sessionKey(repo, opts) {
|
|
10342
|
-
return repo + "\0" + JSON.stringify({
|
|
10343
|
-
scope: opts.scope,
|
|
10344
|
-
include: opts.include,
|
|
10345
|
-
exclude: opts.exclude,
|
|
10346
|
-
gitignore: opts.gitignore,
|
|
10347
|
-
ignoreDirs: opts.ignoreDirs,
|
|
10348
|
-
maxBytes: opts.maxBytes,
|
|
10349
|
-
maxFiles: opts.maxFiles,
|
|
10350
|
-
maxCallsPerFile: opts.maxCallsPerFile,
|
|
10351
|
-
out: opts.out,
|
|
10352
|
-
fullHash: opts.fullHash
|
|
10353
|
-
});
|
|
10503
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
10504
|
+
return parsed;
|
|
10354
10505
|
}
|
|
10355
|
-
function
|
|
10356
|
-
|
|
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;
|
|
10506
|
+
function negotiateProtocol(requested) {
|
|
10507
|
+
return typeof requested === "string" && PROTOCOL_VERSIONS.includes(requested) ? requested : LATEST_PROTOCOL;
|
|
10359
10508
|
}
|
|
10360
|
-
function
|
|
10509
|
+
function capResponse(text, tool, repo, maxBytes) {
|
|
10510
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
10511
|
+
if (bytes <= maxBytes) return text;
|
|
10512
|
+
const artifact = ARTIFACT_FOR[tool] ? join16(repo, INDEX_DIR, ARTIFACT_FOR[tool]) : void 0;
|
|
10513
|
+
return JSON.stringify(
|
|
10514
|
+
{
|
|
10515
|
+
truncated: true,
|
|
10516
|
+
tool,
|
|
10517
|
+
bytes,
|
|
10518
|
+
maxBytes,
|
|
10519
|
+
reason: "This response exceeds the configured limit and was withheld rather than sent as an unusable partial payload.",
|
|
10520
|
+
narrower: NARROWER[tool] ?? "narrow the request with `scope`, `include`/`exclude`, or a `limit`",
|
|
10521
|
+
...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.` } : {}
|
|
10522
|
+
},
|
|
10523
|
+
null,
|
|
10524
|
+
2
|
|
10525
|
+
) + "\n";
|
|
10526
|
+
}
|
|
10527
|
+
function resourceLinkFor(text, tool) {
|
|
10528
|
+
const artifactName = ARTIFACT_FOR[tool];
|
|
10529
|
+
if (!artifactName) return void 0;
|
|
10361
10530
|
let parsed;
|
|
10362
10531
|
try {
|
|
10363
|
-
parsed = JSON.parse(
|
|
10532
|
+
parsed = JSON.parse(text);
|
|
10364
10533
|
} catch {
|
|
10365
10534
|
return void 0;
|
|
10366
10535
|
}
|
|
10367
|
-
if (
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
graphSha1: parsed.graphSha1,
|
|
10375
|
-
symbolsSha1: parsed.symbolsSha1
|
|
10536
|
+
if (parsed.truncated !== true || typeof parsed.artifact !== "string") return void 0;
|
|
10537
|
+
return {
|
|
10538
|
+
type: "resource_link",
|
|
10539
|
+
uri: pathToFileURL2(parsed.artifact).href,
|
|
10540
|
+
name: artifactName,
|
|
10541
|
+
description: `The full ${tool} result this call was too large to inline.`,
|
|
10542
|
+
mimeType: "application/json"
|
|
10376
10543
|
};
|
|
10377
|
-
return { cacheMap, meta };
|
|
10378
10544
|
}
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
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;
|
|
10545
|
+
var PROTOCOL_VERSIONS, LATEST_PROTOCOL, ANNOTATIONS_SINCE, RICH_TOOLS_SINCE, DEFAULT_MAX_RESPONSE_BYTES, NARROWER, ARTIFACT_FOR;
|
|
10546
|
+
var init_protocol = __esm({
|
|
10547
|
+
"src/mcp/protocol.ts"() {
|
|
10548
|
+
"use strict";
|
|
10549
|
+
init_preload();
|
|
10550
|
+
PROTOCOL_VERSIONS = ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"];
|
|
10551
|
+
LATEST_PROTOCOL = PROTOCOL_VERSIONS[PROTOCOL_VERSIONS.length - 1];
|
|
10552
|
+
ANNOTATIONS_SINCE = "2025-03-26";
|
|
10553
|
+
RICH_TOOLS_SINCE = "2025-06-18";
|
|
10554
|
+
DEFAULT_MAX_RESPONSE_BYTES = 1e6;
|
|
10555
|
+
NARROWER = {
|
|
10556
|
+
graph: "pass `scope` to a subdirectory, or use repo_map / mermaid for an overview",
|
|
10557
|
+
symbols: "pass `name` to look up one symbol, or use find_symbol / symbols_overview",
|
|
10558
|
+
callers: "pass `name` to look up one symbol's call sites",
|
|
10559
|
+
dead_code: "pass `scope` to a subdirectory",
|
|
10560
|
+
find_references: "the symbol is referenced very widely \u2014 narrow with `scope` on a graph query",
|
|
10561
|
+
check_rules: "narrow the rule set, or pass `scope` to a subdirectory"
|
|
10562
|
+
};
|
|
10563
|
+
ARTIFACT_FOR = { graph: "graph.json", symbols: "symbols.json" };
|
|
10402
10564
|
}
|
|
10565
|
+
});
|
|
10566
|
+
|
|
10567
|
+
// src/mcp/tools.ts
|
|
10568
|
+
function annotationsFor(name2) {
|
|
10569
|
+
const meta = TOOL_META[name2];
|
|
10570
|
+
if (!meta) return void 0;
|
|
10571
|
+
return {
|
|
10572
|
+
readOnlyHint: !meta.write,
|
|
10573
|
+
...meta.write ? { destructiveHint: meta.destructive === true, idempotentHint: meta.idempotent === true } : {},
|
|
10574
|
+
openWorldHint: meta.openWorld === true
|
|
10575
|
+
};
|
|
10403
10576
|
}
|
|
10404
|
-
function
|
|
10405
|
-
const
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
}
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10577
|
+
function toolsFor(defaultRepo, protocolVersion = PROTOCOL_VERSIONS[0]) {
|
|
10578
|
+
const withAnnotations = protocolVersion >= ANNOTATIONS_SINCE;
|
|
10579
|
+
const withRich = protocolVersion >= RICH_TOOLS_SINCE;
|
|
10580
|
+
if (!defaultRepo && !withAnnotations && !withRich) return TOOLS;
|
|
10581
|
+
return TOOLS.map((t) => ({
|
|
10582
|
+
...t,
|
|
10583
|
+
...withRich && TOOL_META[t.name] ? { title: TOOL_META[t.name].title } : {},
|
|
10584
|
+
...withRich && OUTPUT_SCHEMAS[t.name] ? { outputSchema: OUTPUT_SCHEMAS[t.name] } : {},
|
|
10585
|
+
...withAnnotations ? { annotations: annotationsFor(t.name) } : {},
|
|
10586
|
+
inputSchema: !defaultRepo ? t.inputSchema : {
|
|
10587
|
+
...t.inputSchema,
|
|
10588
|
+
properties: {
|
|
10589
|
+
...t.inputSchema.properties,
|
|
10590
|
+
repo: {
|
|
10591
|
+
type: "string",
|
|
10592
|
+
description: `Absolute path to the repository root (optional \u2014 defaults to ${defaultRepo})`
|
|
10593
|
+
}
|
|
10594
|
+
},
|
|
10595
|
+
required: t.inputSchema.required.filter((r) => r !== "repo")
|
|
10419
10596
|
}
|
|
10420
|
-
|
|
10421
|
-
return fresh;
|
|
10422
|
-
}
|
|
10423
|
-
const preloaded = preloadSession(repo, opts);
|
|
10424
|
-
if (preloaded) {
|
|
10425
|
-
sessionCache = { key, scan: preloaded.scan, cacheMap: preloaded.cacheMap, arts: preloaded.arts };
|
|
10426
|
-
return preloaded.scan;
|
|
10427
|
-
}
|
|
10428
|
-
const scan2 = scanRepo(repo, opts);
|
|
10429
|
-
sessionCache = { key, scan: scan2, cacheMap: toCacheMap(scan2) };
|
|
10430
|
-
return scan2;
|
|
10431
|
-
}
|
|
10432
|
-
function getArtifacts(repo, opts = {}) {
|
|
10433
|
-
const scan2 = getScan(repo, opts);
|
|
10434
|
-
if (sessionCache && sessionCache.scan === scan2) {
|
|
10435
|
-
return sessionCache.arts ??= buildArtifactsFromScan(scan2, opts);
|
|
10436
|
-
}
|
|
10437
|
-
return buildArtifactsFromScan(scan2, opts);
|
|
10438
|
-
}
|
|
10439
|
-
async function warmGrammarsForRepo(repo) {
|
|
10440
|
-
const { files } = walk(repo, {});
|
|
10441
|
-
await ensureGrammars(grammarKeysForExts(files.map((f) => f.ext)));
|
|
10597
|
+
}));
|
|
10442
10598
|
}
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
{
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
return JSON.stringify(entry ?? { error: `no tracked callers for "${lookup}"` }, null, 2);
|
|
10473
|
-
}
|
|
10474
|
-
const obj = {};
|
|
10475
|
-
for (const [k, v] of index) obj[k] = v;
|
|
10476
|
-
return JSON.stringify(obj, null, 2);
|
|
10477
|
-
}
|
|
10478
|
-
if (name2 === "workspaces") {
|
|
10479
|
-
const info2 = detectWorkspaces(repo);
|
|
10480
|
-
return JSON.stringify({ packages: info2.packages, cycle: info2.cycle ?? null, topoOrder: info2.topoOrder }, null, 2);
|
|
10481
|
-
}
|
|
10482
|
-
if (name2 === "churn") {
|
|
10483
|
-
const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
|
|
10484
|
-
const sorted = {};
|
|
10485
|
-
for (const k of [...churn.keys()].sort()) sorted[k] = churn.get(k);
|
|
10486
|
-
return JSON.stringify({ ok, churn: sorted }, null, 2);
|
|
10487
|
-
}
|
|
10488
|
-
if (name2 === "symbols_overview") {
|
|
10489
|
-
const file = str(args2.file);
|
|
10490
|
-
if (!file) throw new Error("`file` is required");
|
|
10491
|
-
return JSON.stringify(symbolsOverview(getScan(repo, scanOpts), file), null, 2);
|
|
10492
|
-
}
|
|
10493
|
-
if (name2 === "find_symbol") {
|
|
10494
|
-
const namePath = str(args2.namePath);
|
|
10495
|
-
if (!namePath) throw new Error("`namePath` is required");
|
|
10496
|
-
const matches = findSymbol(getScan(repo, scanOpts), namePath, {
|
|
10497
|
-
substring: args2.substring === true,
|
|
10498
|
-
includeBody: args2.includeBody === true
|
|
10499
|
-
});
|
|
10500
|
-
return JSON.stringify(matches, null, 2);
|
|
10501
|
-
}
|
|
10502
|
-
if (name2 === "find_references") {
|
|
10503
|
-
const symName = str(args2.name);
|
|
10504
|
-
if (!symName) throw new Error("`name` is required");
|
|
10505
|
-
return JSON.stringify(findReferences(getScan(repo, scanOpts), symName), null, 2);
|
|
10506
|
-
}
|
|
10507
|
-
if (name2 === "replace_symbol_body" || name2 === "insert_after_symbol" || name2 === "insert_before_symbol") {
|
|
10508
|
-
const namePath = str(args2.namePath);
|
|
10509
|
-
const body2 = typeof args2.body === "string" ? args2.body : void 0;
|
|
10510
|
-
if (!namePath || body2 === void 0) throw new Error("`namePath` and `body` are required");
|
|
10511
|
-
const scan2 = getScan(repo, scanOpts);
|
|
10512
|
-
const fn = name2 === "replace_symbol_body" ? replaceSymbolBody : name2 === "insert_after_symbol" ? insertAfterSymbol : insertBeforeSymbol;
|
|
10513
|
-
const result = fn(scan2, namePath, body2, str(args2.file));
|
|
10514
|
-
sessionCache = void 0;
|
|
10515
|
-
return JSON.stringify(result, null, 2);
|
|
10516
|
-
}
|
|
10517
|
-
if (name2 === "write_memory") {
|
|
10518
|
-
const memName = str(args2.name);
|
|
10519
|
-
const content = typeof args2.content === "string" ? args2.content : void 0;
|
|
10520
|
-
if (!memName || content === void 0) throw new Error("`name` and `content` are required");
|
|
10521
|
-
return JSON.stringify({ written: writeMemory(repo, memName, content) }, null, 2);
|
|
10522
|
-
}
|
|
10523
|
-
if (name2 === "read_memory") {
|
|
10524
|
-
const memName = str(args2.name);
|
|
10525
|
-
if (!memName) throw new Error("`name` is required");
|
|
10526
|
-
const content = readMemory(repo, memName);
|
|
10527
|
-
if (content === void 0) throw new Error(`no memory named "${memName}" \u2014 see list_memories`);
|
|
10528
|
-
return content;
|
|
10529
|
-
}
|
|
10530
|
-
if (name2 === "list_memories") {
|
|
10531
|
-
return JSON.stringify(listMemories(repo), null, 2);
|
|
10532
|
-
}
|
|
10533
|
-
if (name2 === "delete_memory") {
|
|
10534
|
-
const memName = str(args2.name);
|
|
10535
|
-
if (!memName) throw new Error("`name` is required");
|
|
10536
|
-
return JSON.stringify({ deleted: deleteMemory(repo, memName) }, null, 2);
|
|
10537
|
-
}
|
|
10538
|
-
if (name2 === "dead_code") {
|
|
10539
|
-
return JSON.stringify(findDeadCode(getScan(repo, scanOpts)), null, 2);
|
|
10540
|
-
}
|
|
10541
|
-
if (name2 === "complexity") {
|
|
10542
|
-
const scan2 = getScan(repo, scanOpts);
|
|
10543
|
-
if (args2.risk === true) {
|
|
10544
|
-
const { churn, ok } = gitChurn(repo);
|
|
10545
|
-
return JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn) }, null, 2);
|
|
10546
|
-
}
|
|
10547
|
-
return JSON.stringify(symbolComplexity(scan2, str(args2.file)), null, 2);
|
|
10548
|
-
}
|
|
10549
|
-
if (name2 === "mermaid") {
|
|
10550
|
-
const { graph } = getArtifacts(repo, scanOpts);
|
|
10551
|
-
return renderMermaid(graph, { module: str(args2.module) });
|
|
10552
|
-
}
|
|
10553
|
-
if (name2 === "repo_map") {
|
|
10554
|
-
const { scan: scan2, graph } = getArtifacts(repo, scanOpts);
|
|
10555
|
-
return renderRepoMap(scan2, graph, { budgetTokens: typeof args2.budgetTokens === "number" ? args2.budgetTokens : void 0 });
|
|
10556
|
-
}
|
|
10557
|
-
if (name2 === "hotspots") {
|
|
10558
|
-
const scan2 = getScan(repo, scanOpts);
|
|
10559
|
-
const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
|
|
10560
|
-
return JSON.stringify({ churnOk: ok, hotspots: rankHotspots(scan2, churn) }, null, 2);
|
|
10561
|
-
}
|
|
10562
|
-
if (name2 === "coupling") {
|
|
10563
|
-
const { ok, couplings } = changeCoupling(repo, { since: str(args2.since) });
|
|
10564
|
-
return JSON.stringify({ ok, couplings }, null, 2);
|
|
10565
|
-
}
|
|
10566
|
-
if (name2 === "grep") {
|
|
10567
|
-
const pattern = str(args2.pattern);
|
|
10568
|
-
if (!pattern) throw new Error("`pattern` is required");
|
|
10569
|
-
const hits = grepRepo(repo, pattern, {
|
|
10570
|
-
globs: strArray(args2.globs),
|
|
10571
|
-
ignoreCase: args2.ignoreCase === true,
|
|
10572
|
-
maxHits: typeof args2.maxHits === "number" ? args2.maxHits : void 0
|
|
10573
|
-
});
|
|
10574
|
-
return JSON.stringify(hits, null, 2);
|
|
10575
|
-
}
|
|
10576
|
-
if (name2 === "search") {
|
|
10577
|
-
const query = str(args2.query);
|
|
10578
|
-
if (!query) throw new Error("`query` is required");
|
|
10579
|
-
const scan2 = getScan(repo, scanOpts);
|
|
10580
|
-
const limit = typeof args2.limit === "number" ? args2.limit : void 0;
|
|
10581
|
-
const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
|
|
10582
|
-
if (args2.semantic === true) {
|
|
10583
|
-
const endpoint = resolveEmbedEndpoint();
|
|
10584
|
-
if (endpoint) {
|
|
10585
|
-
try {
|
|
10586
|
-
const index = await memoizedEmbeddingIndex({ mode: "endpoint", identity: endpoint, scan: scan2 }, () => buildEndpointIndex(scan2));
|
|
10587
|
-
const queryVec = await encodeQueryViaEndpoint(query);
|
|
10588
|
-
const results2 = searchSemantic(scan2, query, index, { queryVec, limit, fuzzy });
|
|
10589
|
-
return JSON.stringify({ results: results2, tier: "endpoint" }, null, 2);
|
|
10590
|
-
} catch (e) {
|
|
10591
|
-
const results2 = searchIndex(scan2, query, { limit, fuzzy });
|
|
10592
|
-
return JSON.stringify(
|
|
10593
|
-
{ results: results2, tier: "lexical", degradedReason: `embedding endpoint failed: ${errMessage(e)}` },
|
|
10594
|
-
null,
|
|
10595
|
-
2
|
|
10596
|
-
);
|
|
10597
|
-
}
|
|
10598
|
-
}
|
|
10599
|
-
const modelDir = resolveEmbedModelDir(repo);
|
|
10600
|
-
const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
|
|
10601
|
-
if (model) {
|
|
10602
|
-
const index = await memoizedEmbeddingIndex(
|
|
10603
|
-
{ mode: "static", identity: `${modelDir}#${model.modelId}`, scan: scan2 },
|
|
10604
|
-
() => buildEmbeddingIndex(scan2, model)
|
|
10605
|
-
);
|
|
10606
|
-
const results2 = searchSemantic(scan2, query, index, { model, limit, fuzzy });
|
|
10607
|
-
return JSON.stringify({ results: results2, tier: "static" }, null, 2);
|
|
10608
|
-
}
|
|
10609
|
-
const results = searchIndex(scan2, query, { limit, fuzzy });
|
|
10610
|
-
return JSON.stringify(
|
|
10611
|
-
{ results, tier: "lexical", degradedReason: "no embedding endpoint or static model configured \u2014 see embed_status" },
|
|
10612
|
-
null,
|
|
10613
|
-
2
|
|
10614
|
-
);
|
|
10615
|
-
}
|
|
10616
|
-
return JSON.stringify(searchIndex(scan2, query, { limit, fuzzy }), null, 2);
|
|
10617
|
-
}
|
|
10618
|
-
if (name2 === "embed_status") {
|
|
10619
|
-
const modelDir = resolveEmbedModelDir(repo);
|
|
10620
|
-
const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
|
|
10621
|
-
const endpoint = resolveEmbedEndpoint();
|
|
10622
|
-
const mode = endpoint ? "endpoint" : model ? "static" : "none";
|
|
10623
|
-
const status = {
|
|
10624
|
-
embedVersion: EMBED_VERSION,
|
|
10625
|
-
mode,
|
|
10626
|
-
model: model ? { present: true, dir: modelDir, modelId: model.modelId, dim: model.dim, vocabSize: model.vocabSize } : { present: false },
|
|
10627
|
-
endpoint: endpoint ?? null
|
|
10628
|
-
};
|
|
10629
|
-
if (endpoint) status.endpointReachable = await probeEndpoint(endpoint);
|
|
10630
|
-
return JSON.stringify(status, null, 2);
|
|
10631
|
-
}
|
|
10632
|
-
if (name2 === "check_rules") {
|
|
10633
|
-
const rules = parseRules(args2.rules);
|
|
10634
|
-
const { graph } = getArtifacts(repo, scanOpts);
|
|
10635
|
-
return JSON.stringify(checkRules(graph, rules), null, 2);
|
|
10636
|
-
}
|
|
10637
|
-
throw new Error(`unknown tool: ${name2}`);
|
|
10638
|
-
}
|
|
10639
|
-
function toolsFor(defaultRepo) {
|
|
10640
|
-
if (!defaultRepo) return TOOLS;
|
|
10641
|
-
return TOOLS.map((t) => ({
|
|
10642
|
-
...t,
|
|
10643
|
-
inputSchema: {
|
|
10644
|
-
...t.inputSchema,
|
|
10645
|
-
properties: {
|
|
10646
|
-
...t.inputSchema.properties,
|
|
10647
|
-
repo: { type: "string", description: `Absolute path to the repository root (optional \u2014 defaults to ${defaultRepo})` }
|
|
10648
|
-
},
|
|
10649
|
-
required: t.inputSchema.required.filter((r) => r !== "repo")
|
|
10650
|
-
}
|
|
10651
|
-
}));
|
|
10652
|
-
}
|
|
10653
|
-
async function runMcpServer(opts = {}) {
|
|
10654
|
-
const serverInfo = {
|
|
10655
|
-
name: opts.serverInfo?.name ?? "codeindex",
|
|
10656
|
-
version: opts.serverInfo?.version ?? ENGINE_VERSION
|
|
10657
|
-
};
|
|
10658
|
-
const tools = toolsFor(opts.defaultRepo);
|
|
10659
|
-
const send = (msg) => {
|
|
10660
|
-
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...msg }) + "\n");
|
|
10661
|
-
};
|
|
10662
|
-
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
10663
|
-
for await (const line of rl) {
|
|
10664
|
-
const trimmed = line.trim();
|
|
10665
|
-
if (!trimmed) continue;
|
|
10666
|
-
let parsed;
|
|
10667
|
-
try {
|
|
10668
|
-
parsed = JSON.parse(trimmed);
|
|
10669
|
-
} catch {
|
|
10670
|
-
send({ id: null, error: { code: -32700, message: "parse error" } });
|
|
10671
|
-
continue;
|
|
10672
|
-
}
|
|
10673
|
-
const requests = Array.isArray(parsed) ? parsed : [parsed];
|
|
10674
|
-
for (const req of requests) await handle2(req);
|
|
10675
|
-
}
|
|
10676
|
-
async function handle2(req) {
|
|
10677
|
-
if (req.id === void 0 || req.id === null) return;
|
|
10678
|
-
try {
|
|
10679
|
-
if (req.method === "initialize") {
|
|
10680
|
-
send({
|
|
10681
|
-
id: req.id,
|
|
10682
|
-
result: {
|
|
10683
|
-
protocolVersion: "2024-11-05",
|
|
10684
|
-
capabilities: { tools: {} },
|
|
10685
|
-
serverInfo
|
|
10686
|
-
}
|
|
10687
|
-
});
|
|
10688
|
-
} else if (req.method === "ping") {
|
|
10689
|
-
send({ id: req.id, result: {} });
|
|
10690
|
-
} else if (req.method === "tools/list") {
|
|
10691
|
-
send({ id: req.id, result: { tools } });
|
|
10692
|
-
} else if (req.method === "tools/call") {
|
|
10693
|
-
const params = req.params ?? {};
|
|
10694
|
-
const name2 = str(params.name) ?? "";
|
|
10695
|
-
const args2 = params.arguments ?? {};
|
|
10696
|
-
try {
|
|
10697
|
-
const text = await callTool(name2, args2, opts.defaultRepo);
|
|
10698
|
-
send({ id: req.id, result: { content: [{ type: "text", text }] } });
|
|
10699
|
-
} catch (e) {
|
|
10700
|
-
send({
|
|
10701
|
-
id: req.id,
|
|
10702
|
-
result: { content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }], isError: true }
|
|
10703
|
-
});
|
|
10704
|
-
}
|
|
10705
|
-
} else {
|
|
10706
|
-
send({ id: req.id, error: { code: -32601, message: `method not found: ${req.method}` } });
|
|
10707
|
-
}
|
|
10708
|
-
} catch (e) {
|
|
10709
|
-
send({ id: req.id, error: { code: -32603, message: e instanceof Error ? e.message : String(e) } });
|
|
10710
|
-
}
|
|
10711
|
-
}
|
|
10712
|
-
}
|
|
10713
|
-
var repoProp, scopeProps, TOOLS, embeddingIndexCache, embedModelCache, sessionCache, SCANLESS_TOOLS;
|
|
10714
|
-
var init_mcp = __esm({
|
|
10715
|
-
"src/mcp.ts"() {
|
|
10716
|
-
"use strict";
|
|
10717
|
-
init_types();
|
|
10718
|
-
init_loader();
|
|
10719
|
-
init_pipeline();
|
|
10720
|
-
init_graph_json();
|
|
10721
|
-
init_scan();
|
|
10722
|
-
init_walk();
|
|
10723
|
-
init_callers();
|
|
10724
|
-
init_workspaces();
|
|
10725
|
-
init_git();
|
|
10726
|
-
init_grep();
|
|
10727
|
-
init_coupling();
|
|
10728
|
-
init_repomap();
|
|
10729
|
-
init_deadcode();
|
|
10730
|
-
init_complexity();
|
|
10731
|
-
init_viz();
|
|
10732
|
-
init_query();
|
|
10733
|
-
init_edit();
|
|
10734
|
-
init_memory();
|
|
10735
|
-
init_bm25();
|
|
10736
|
-
init_rules();
|
|
10737
|
-
init_model();
|
|
10738
|
-
init_embed();
|
|
10739
|
-
init_search();
|
|
10740
|
-
init_endpoint();
|
|
10741
|
-
init_hash();
|
|
10742
|
-
repoProp = { repo: { type: "string", description: "Absolute path to the repository root" } };
|
|
10743
|
-
scopeProps = {
|
|
10744
|
-
scope: { type: "string", description: "Restrict to one directory (repo-relative)" },
|
|
10745
|
-
include: { type: "array", items: { type: "string" }, description: "Include globs" },
|
|
10746
|
-
exclude: { type: "array", items: { type: "string" }, description: "Exclude globs" }
|
|
10747
|
-
};
|
|
10748
|
-
TOOLS = [
|
|
10749
|
-
{
|
|
10750
|
-
name: "scan_summary",
|
|
10751
|
-
description: "Deterministically scan a repository: file count, per-language file histogram, HEAD commit, and whether the walk was capped. Fast first look at any codebase.",
|
|
10752
|
-
inputSchema: { type: "object", properties: { ...repoProp, ...scopeProps }, required: ["repo"] }
|
|
10753
|
-
},
|
|
10754
|
-
{
|
|
10755
|
-
name: "graph",
|
|
10756
|
-
description: "Build the full typed cross-file link-graph (import/call/use/doc-link/mention edges, module grouping, PageRank centrality, Louvain communities, tests-map). Returns graph.json. Large on big repos \u2014 prefer scan_summary/symbols/callers for targeted questions.",
|
|
10757
|
-
inputSchema: { type: "object", properties: { ...repoProp, ...scopeProps }, required: ["repo"] }
|
|
10758
|
-
},
|
|
10759
|
-
{
|
|
10760
|
-
name: "symbols",
|
|
10761
|
-
description: "Where is a symbol defined and which files reference it? Returns the definition sites (file, line, kind, exported) and referencing files. Omit `name` for the full symbol index.",
|
|
10762
|
-
inputSchema: {
|
|
10763
|
-
type: "object",
|
|
10764
|
-
properties: { ...repoProp, name: { type: "string", description: "Symbol name to look up" } },
|
|
10765
|
-
required: ["repo"]
|
|
10599
|
+
var repoProp, scopeProps, TOOLS, strArr, anyObj, OUTPUT_SCHEMAS, TOOL_META;
|
|
10600
|
+
var init_tools = __esm({
|
|
10601
|
+
"src/mcp/tools.ts"() {
|
|
10602
|
+
"use strict";
|
|
10603
|
+
init_protocol();
|
|
10604
|
+
repoProp = { repo: { type: "string", description: "Absolute path to the repository root" } };
|
|
10605
|
+
scopeProps = {
|
|
10606
|
+
scope: { type: "string", description: "Restrict to one directory (repo-relative)" },
|
|
10607
|
+
include: { type: "array", items: { type: "string" }, description: "Include globs" },
|
|
10608
|
+
exclude: { type: "array", items: { type: "string" }, description: "Exclude globs" }
|
|
10609
|
+
};
|
|
10610
|
+
TOOLS = [
|
|
10611
|
+
{
|
|
10612
|
+
name: "scan_summary",
|
|
10613
|
+
description: "Deterministically scan a repository: file count, per-language file histogram, HEAD commit, and whether the walk was capped. Fast first look at any codebase.",
|
|
10614
|
+
inputSchema: { type: "object", properties: { ...repoProp, ...scopeProps }, required: ["repo"] }
|
|
10615
|
+
},
|
|
10616
|
+
{
|
|
10617
|
+
name: "graph",
|
|
10618
|
+
description: "Build the full typed cross-file link-graph (import/call/use/doc-link/mention edges, module grouping, PageRank centrality, Louvain communities, tests-map). Returns graph.json. Large on big repos \u2014 prefer scan_summary/symbols/callers for targeted questions.",
|
|
10619
|
+
inputSchema: { type: "object", properties: { ...repoProp, ...scopeProps }, required: ["repo"] }
|
|
10620
|
+
},
|
|
10621
|
+
{
|
|
10622
|
+
name: "symbols",
|
|
10623
|
+
description: "Where is a symbol defined and which files reference it? Returns the definition sites (file, line, kind, exported) and referencing files. Omit `name` for the full symbol index.",
|
|
10624
|
+
inputSchema: {
|
|
10625
|
+
type: "object",
|
|
10626
|
+
properties: { ...repoProp, name: { type: "string", description: "Symbol name to look up" } },
|
|
10627
|
+
required: ["repo"]
|
|
10766
10628
|
}
|
|
10767
10629
|
},
|
|
10768
10630
|
{
|
|
@@ -10770,7 +10632,14 @@ var init_mcp = __esm({
|
|
|
10770
10632
|
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
10633
|
inputSchema: {
|
|
10772
10634
|
type: "object",
|
|
10773
|
-
properties: {
|
|
10635
|
+
properties: {
|
|
10636
|
+
...repoProp,
|
|
10637
|
+
name: { type: "string", description: "Symbol name to look up" },
|
|
10638
|
+
recall: {
|
|
10639
|
+
type: "boolean",
|
|
10640
|
+
description: "Recall-oriented binding: relax the JS/TS import gate to unique repo-wide names, labelling each site corroborated|unique-name (default false = precision)"
|
|
10641
|
+
}
|
|
10642
|
+
},
|
|
10774
10643
|
required: ["repo"]
|
|
10775
10644
|
}
|
|
10776
10645
|
},
|
|
@@ -10806,7 +10675,8 @@ var init_mcp = __esm({
|
|
|
10806
10675
|
...repoProp,
|
|
10807
10676
|
namePath: { type: "string", description: "Symbol name or Parent/child path" },
|
|
10808
10677
|
substring: { type: "boolean" },
|
|
10809
|
-
includeBody: { type: "boolean" }
|
|
10678
|
+
includeBody: { type: "boolean" },
|
|
10679
|
+
maxResults: { type: "number", description: "Cap matches (default 50)" }
|
|
10810
10680
|
},
|
|
10811
10681
|
required: ["repo", "namePath"]
|
|
10812
10682
|
}
|
|
@@ -10913,8 +10783,16 @@ var init_mcp = __esm({
|
|
|
10913
10783
|
},
|
|
10914
10784
|
{
|
|
10915
10785
|
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: {
|
|
10786
|
+
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.",
|
|
10787
|
+
inputSchema: {
|
|
10788
|
+
type: "object",
|
|
10789
|
+
properties: {
|
|
10790
|
+
...repoProp,
|
|
10791
|
+
...scopeProps,
|
|
10792
|
+
limit: { type: "number", description: "Cap entries (default: all)" }
|
|
10793
|
+
},
|
|
10794
|
+
required: ["repo"]
|
|
10795
|
+
}
|
|
10918
10796
|
},
|
|
10919
10797
|
{
|
|
10920
10798
|
name: "complexity",
|
|
@@ -10942,6 +10820,7 @@ var init_mcp = __esm({
|
|
|
10942
10820
|
properties: {
|
|
10943
10821
|
...repoProp,
|
|
10944
10822
|
pattern: { type: "string", description: "Regular expression to search for" },
|
|
10823
|
+
scope: { type: "string", description: "Restrict to one directory (repo-relative)" },
|
|
10945
10824
|
globs: { type: "array", items: { type: "string" }, description: "Restrict to matching paths" },
|
|
10946
10825
|
ignoreCase: { type: "boolean" },
|
|
10947
10826
|
maxHits: { type: "number" }
|
|
@@ -10967,29 +10846,675 @@ var init_mcp = __esm({
|
|
|
10967
10846
|
type: "boolean",
|
|
10968
10847
|
description: 'RRF-fuse an embedding tier with lexical (default false). Precedence: the HTTP endpoint (CODEINDEX_EMBED_ENDPOINT) if set, else a local static model. The response reports the effective tier as a top-level `tier` field ("endpoint"/"static" on success, "lexical" plus `degradedReason` when neither is available/reachable) instead of degrading silently \u2014 see embed_status.'
|
|
10969
10848
|
}
|
|
10970
|
-
},
|
|
10971
|
-
required: ["repo", "query"]
|
|
10972
|
-
}
|
|
10973
|
-
},
|
|
10974
|
-
{
|
|
10975
|
-
name: "embed_status",
|
|
10976
|
-
description: "Report the embedding tier: the effective mode (none/static/endpoint; endpoint > static model), the resolved model (opt-in, never shipped in the package) with its modelId/dim, EMBED_VERSION, and the configured HTTP endpoint with its reachability. Use to check whether `search` with semantic:true will fuse embeddings or degrade to lexical.",
|
|
10977
|
-
inputSchema: { type: "object", properties: { ...repoProp }, required: ["repo"] }
|
|
10978
|
-
},
|
|
10979
|
-
{
|
|
10980
|
-
name: "check_rules",
|
|
10981
|
-
description: 'Validate dependency-cruiser-style architecture rules against the link-graph. Rules (inline JSON array): forbidden edges {name, from, to, kind?, severity?, comment?} with glob paths, plus builtins {name, builtin: "cycles"|"orphans"} (module-level import cycles; edge-less code files). Returns deterministic violations with severity error|warn \u2014 a CI gate.',
|
|
10982
|
-
inputSchema: {
|
|
10983
|
-
type: "object",
|
|
10984
|
-
properties: {
|
|
10985
|
-
...repoProp,
|
|
10986
|
-
...scopeProps,
|
|
10987
|
-
rules: { type: "array", description: "Rules array (inline JSON \u2014 see description)" }
|
|
10988
|
-
|
|
10989
|
-
|
|
10849
|
+
},
|
|
10850
|
+
required: ["repo", "query"]
|
|
10851
|
+
}
|
|
10852
|
+
},
|
|
10853
|
+
{
|
|
10854
|
+
name: "embed_status",
|
|
10855
|
+
description: "Report the embedding tier: the effective mode (none/static/endpoint; endpoint > static model), the resolved model (opt-in, never shipped in the package) with its modelId/dim, EMBED_VERSION, and the configured HTTP endpoint with its reachability. Use to check whether `search` with semantic:true will fuse embeddings or degrade to lexical.",
|
|
10856
|
+
inputSchema: { type: "object", properties: { ...repoProp }, required: ["repo"] }
|
|
10857
|
+
},
|
|
10858
|
+
{
|
|
10859
|
+
name: "check_rules",
|
|
10860
|
+
description: 'Validate dependency-cruiser-style architecture rules against the link-graph. Rules (inline JSON array): forbidden edges {name, from, to, kind?, severity?, comment?} with glob paths, plus builtins {name, builtin: "cycles"|"orphans"} (module-level import cycles; edge-less code files). Returns deterministic violations with severity error|warn \u2014 a CI gate.',
|
|
10861
|
+
inputSchema: {
|
|
10862
|
+
type: "object",
|
|
10863
|
+
properties: {
|
|
10864
|
+
...repoProp,
|
|
10865
|
+
...scopeProps,
|
|
10866
|
+
rules: { type: "array", description: "Rules array (inline JSON \u2014 see description)" },
|
|
10867
|
+
configPath: {
|
|
10868
|
+
type: "string",
|
|
10869
|
+
description: "Read the rules from this JSON file instead (repo-relative or absolute) \u2014 the CLI's --config. Ignored when `rules` is given."
|
|
10870
|
+
}
|
|
10871
|
+
},
|
|
10872
|
+
required: ["repo"]
|
|
10873
|
+
}
|
|
10874
|
+
}
|
|
10875
|
+
];
|
|
10876
|
+
strArr = { type: "array", items: { type: "string" } };
|
|
10877
|
+
anyObj = { type: "object" };
|
|
10878
|
+
OUTPUT_SCHEMAS = {
|
|
10879
|
+
scan_summary: {
|
|
10880
|
+
type: "object",
|
|
10881
|
+
properties: {
|
|
10882
|
+
engineVersion: { type: "string" },
|
|
10883
|
+
commit: { type: "string" },
|
|
10884
|
+
fileCount: { type: "integer" },
|
|
10885
|
+
languages: { type: "object", additionalProperties: { type: "integer" } },
|
|
10886
|
+
capped: { type: "boolean" }
|
|
10887
|
+
},
|
|
10888
|
+
required: ["engineVersion", "fileCount", "languages", "capped"]
|
|
10889
|
+
},
|
|
10890
|
+
graph: {
|
|
10891
|
+
type: "object",
|
|
10892
|
+
properties: {
|
|
10893
|
+
schemaVersion: { type: "integer" },
|
|
10894
|
+
version: { type: "string" },
|
|
10895
|
+
commit: { type: "string" },
|
|
10896
|
+
fileCount: { type: "integer" },
|
|
10897
|
+
languages: { type: "object", additionalProperties: { type: "integer" } },
|
|
10898
|
+
files: { type: "array", items: anyObj },
|
|
10899
|
+
modules: { type: "array", items: anyObj },
|
|
10900
|
+
fileEdges: { type: "array", items: anyObj },
|
|
10901
|
+
moduleEdges: { type: "array", items: anyObj }
|
|
10902
|
+
},
|
|
10903
|
+
required: ["schemaVersion", "files", "fileEdges", "modules", "moduleEdges"]
|
|
10904
|
+
},
|
|
10905
|
+
// Two shapes, both objects: the whole index, or one symbol's entry.
|
|
10906
|
+
symbols: {
|
|
10907
|
+
oneOf: [
|
|
10908
|
+
{
|
|
10909
|
+
type: "object",
|
|
10910
|
+
properties: { schemaVersion: { type: "integer" }, defs: anyObj, refs: anyObj },
|
|
10911
|
+
required: ["schemaVersion", "defs"]
|
|
10912
|
+
},
|
|
10913
|
+
{
|
|
10914
|
+
type: "object",
|
|
10915
|
+
properties: { name: { type: "string" }, defs: { type: "array", items: anyObj }, refs: strArr },
|
|
10916
|
+
required: ["name", "defs", "refs"]
|
|
10917
|
+
}
|
|
10918
|
+
]
|
|
10919
|
+
},
|
|
10920
|
+
// The whole index (symbol name -> entry), one entry, or the not-found notice.
|
|
10921
|
+
callers: {
|
|
10922
|
+
oneOf: [
|
|
10923
|
+
{ type: "object", additionalProperties: anyObj },
|
|
10924
|
+
{ type: "object", properties: { error: { type: "string" } }, required: ["error"] }
|
|
10925
|
+
]
|
|
10926
|
+
},
|
|
10927
|
+
workspaces: {
|
|
10928
|
+
type: "object",
|
|
10929
|
+
properties: {
|
|
10930
|
+
packages: { type: "array", items: anyObj },
|
|
10931
|
+
cycle: { type: ["array", "null"], items: { type: "string" } },
|
|
10932
|
+
topoOrder: strArr
|
|
10933
|
+
},
|
|
10934
|
+
required: ["packages", "topoOrder"]
|
|
10935
|
+
},
|
|
10936
|
+
churn: {
|
|
10937
|
+
type: "object",
|
|
10938
|
+
properties: { ok: { type: "boolean" }, churn: { type: "object", additionalProperties: { type: "integer" } } },
|
|
10939
|
+
required: ["ok", "churn"]
|
|
10940
|
+
},
|
|
10941
|
+
find_references: {
|
|
10942
|
+
type: "object",
|
|
10943
|
+
properties: {
|
|
10944
|
+
defs: { type: "array", items: anyObj },
|
|
10945
|
+
callSites: { type: "array", items: anyObj },
|
|
10946
|
+
referencingFiles: strArr
|
|
10947
|
+
},
|
|
10948
|
+
required: ["defs", "callSites", "referencingFiles"]
|
|
10949
|
+
},
|
|
10950
|
+
hotspots: {
|
|
10951
|
+
type: "object",
|
|
10952
|
+
properties: { churnOk: { type: "boolean" }, hotspots: { type: "array", items: anyObj } },
|
|
10953
|
+
required: ["churnOk", "hotspots"]
|
|
10954
|
+
},
|
|
10955
|
+
coupling: {
|
|
10956
|
+
type: "object",
|
|
10957
|
+
properties: { ok: { type: "boolean" }, couplings: { type: "array", items: anyObj } },
|
|
10958
|
+
required: ["ok", "couplings"]
|
|
10959
|
+
},
|
|
10960
|
+
embed_status: {
|
|
10961
|
+
type: "object",
|
|
10962
|
+
properties: {
|
|
10963
|
+
embedVersion: { type: "integer" },
|
|
10964
|
+
mode: { type: "string", enum: ["none", "static", "endpoint"] },
|
|
10965
|
+
model: {},
|
|
10966
|
+
endpoint: {},
|
|
10967
|
+
endpointReachable: { type: "boolean" }
|
|
10968
|
+
},
|
|
10969
|
+
required: ["embedVersion", "mode"]
|
|
10970
|
+
},
|
|
10971
|
+
write_memory: {
|
|
10972
|
+
type: "object",
|
|
10973
|
+
properties: { written: { type: "string" } },
|
|
10974
|
+
required: ["written"]
|
|
10975
|
+
},
|
|
10976
|
+
delete_memory: {
|
|
10977
|
+
type: "object",
|
|
10978
|
+
properties: { deleted: { type: "boolean" } },
|
|
10979
|
+
required: ["deleted"]
|
|
10980
|
+
}
|
|
10981
|
+
};
|
|
10982
|
+
for (const name2 of ["replace_symbol_body", "insert_after_symbol", "insert_before_symbol"]) {
|
|
10983
|
+
OUTPUT_SCHEMAS[name2] = {
|
|
10984
|
+
type: "object",
|
|
10985
|
+
properties: {
|
|
10986
|
+
file: { type: "string" },
|
|
10987
|
+
symbol: { type: "string" },
|
|
10988
|
+
startLine: { type: "integer" },
|
|
10989
|
+
endLine: { type: "integer" }
|
|
10990
|
+
},
|
|
10991
|
+
required: ["file"]
|
|
10992
|
+
};
|
|
10993
|
+
}
|
|
10994
|
+
TOOL_META = {
|
|
10995
|
+
scan_summary: { title: "Scan summary" },
|
|
10996
|
+
graph: { title: "Link graph" },
|
|
10997
|
+
symbols: { title: "Symbol index" },
|
|
10998
|
+
callers: { title: "Caller index" },
|
|
10999
|
+
workspaces: { title: "Monorepo workspaces" },
|
|
11000
|
+
churn: { title: "Git churn" },
|
|
11001
|
+
symbols_overview: { title: "File symbol overview" },
|
|
11002
|
+
find_symbol: { title: "Find symbol" },
|
|
11003
|
+
find_references: { title: "Find references" },
|
|
11004
|
+
repo_map: { title: "Repository map" },
|
|
11005
|
+
hotspots: { title: "Hotspots" },
|
|
11006
|
+
coupling: { title: "Change coupling" },
|
|
11007
|
+
replace_symbol_body: { title: "Replace symbol body", write: true, destructive: true, idempotent: true },
|
|
11008
|
+
insert_after_symbol: { title: "Insert after symbol", write: true, destructive: false, idempotent: false },
|
|
11009
|
+
insert_before_symbol: { title: "Insert before symbol", write: true, destructive: false, idempotent: false },
|
|
11010
|
+
write_memory: { title: "Write memory", write: true, destructive: false, idempotent: true },
|
|
11011
|
+
read_memory: { title: "Read memory" },
|
|
11012
|
+
list_memories: { title: "List memories" },
|
|
11013
|
+
delete_memory: { title: "Delete memory", write: true, destructive: true, idempotent: true },
|
|
11014
|
+
dead_code: { title: "Dead-code candidates" },
|
|
11015
|
+
complexity: { title: "Complexity" },
|
|
11016
|
+
mermaid: { title: "Mermaid module diagram" },
|
|
11017
|
+
grep: { title: "Grep file contents" },
|
|
11018
|
+
search: { title: "Lexical search", openWorld: true },
|
|
11019
|
+
embed_status: { title: "Embedding tier status", openWorld: true },
|
|
11020
|
+
check_rules: { title: "Check architecture rules" }
|
|
11021
|
+
};
|
|
11022
|
+
}
|
|
11023
|
+
});
|
|
11024
|
+
|
|
11025
|
+
// src/mcp/session.ts
|
|
11026
|
+
import { statSync as statSync5 } from "fs";
|
|
11027
|
+
import { join as join17 } from "path";
|
|
11028
|
+
function scanFingerprint(scan2) {
|
|
11029
|
+
return sha1(scan2.files.map((f) => `${f.rel}:${f.hash}`).join("\n"));
|
|
11030
|
+
}
|
|
11031
|
+
async function memoizedEmbeddingIndex(key, build) {
|
|
11032
|
+
const cacheKey = `${key.mode}:${key.identity}:${scanFingerprint(key.scan)}`;
|
|
11033
|
+
if (embeddingIndexCache && embeddingIndexCache.key === cacheKey) return embeddingIndexCache.index;
|
|
11034
|
+
const index = await build();
|
|
11035
|
+
embeddingIndexCache = { key: cacheKey, index };
|
|
11036
|
+
return index;
|
|
11037
|
+
}
|
|
11038
|
+
function memoizedEmbedModel(modelDir) {
|
|
11039
|
+
let stat;
|
|
11040
|
+
try {
|
|
11041
|
+
stat = statSync5(join17(modelDir, "model.json"));
|
|
11042
|
+
} catch {
|
|
11043
|
+
return void 0;
|
|
11044
|
+
}
|
|
11045
|
+
const key = `${modelDir}:${stat.mtimeMs}:${stat.size}`;
|
|
11046
|
+
if (embedModelCache && embedModelCache.key === key) return embedModelCache.model;
|
|
11047
|
+
const model = loadEmbedModel(modelDir);
|
|
11048
|
+
if (model) embedModelCache = { key, model };
|
|
11049
|
+
return model;
|
|
11050
|
+
}
|
|
11051
|
+
function sessionGet(key) {
|
|
11052
|
+
const i2 = sessionCaches.findIndex((e) => e.key === key);
|
|
11053
|
+
if (i2 < 0) return void 0;
|
|
11054
|
+
const [entry] = sessionCaches.splice(i2, 1);
|
|
11055
|
+
sessionCaches.unshift(entry);
|
|
11056
|
+
return entry;
|
|
11057
|
+
}
|
|
11058
|
+
function sessionPut(entry) {
|
|
11059
|
+
const i2 = sessionCaches.findIndex((e) => e.key === entry.key);
|
|
11060
|
+
if (i2 >= 0) sessionCaches.splice(i2, 1);
|
|
11061
|
+
sessionCaches.unshift(entry);
|
|
11062
|
+
sessionCaches.length = Math.min(sessionCaches.length, SESSION_CACHE_MAX);
|
|
11063
|
+
return entry;
|
|
11064
|
+
}
|
|
11065
|
+
function sessionClear() {
|
|
11066
|
+
sessionCaches.length = 0;
|
|
11067
|
+
}
|
|
11068
|
+
function sessionKey(repo, opts) {
|
|
11069
|
+
return repo + "\0" + JSON.stringify({
|
|
11070
|
+
scope: opts.scope,
|
|
11071
|
+
include: opts.include,
|
|
11072
|
+
exclude: opts.exclude,
|
|
11073
|
+
gitignore: opts.gitignore,
|
|
11074
|
+
ignoreDirs: opts.ignoreDirs,
|
|
11075
|
+
maxBytes: opts.maxBytes,
|
|
11076
|
+
maxFiles: opts.maxFiles,
|
|
11077
|
+
maxCallsPerFile: opts.maxCallsPerFile,
|
|
11078
|
+
out: opts.out,
|
|
11079
|
+
fullHash: opts.fullHash
|
|
11080
|
+
});
|
|
11081
|
+
}
|
|
11082
|
+
function getScan(repo, opts = {}, walked) {
|
|
11083
|
+
const key = sessionKey(repo, opts);
|
|
11084
|
+
const hit = sessionGet(key);
|
|
11085
|
+
if (hit) {
|
|
11086
|
+
const fresh = scanRepo(repo, { ...opts, cache: hit.cacheMap, precomputedWalk: walked });
|
|
11087
|
+
if (fresh.contentUnchanged) {
|
|
11088
|
+
if (fresh.cacheDirty) hit.cacheMap = toCacheMap(fresh);
|
|
11089
|
+
if (hit.scan.commit !== fresh.commit) hit.scan.commit = fresh.commit;
|
|
11090
|
+
return hit.scan;
|
|
11091
|
+
}
|
|
11092
|
+
sessionPut({ key, scan: fresh, cacheMap: toCacheMap(fresh) });
|
|
11093
|
+
return fresh;
|
|
11094
|
+
}
|
|
11095
|
+
const preloaded = preloadSession(repo, { ...opts, precomputedWalk: walked });
|
|
11096
|
+
if (preloaded) {
|
|
11097
|
+
sessionPut({ key, scan: preloaded.scan, cacheMap: preloaded.cacheMap, arts: preloaded.arts });
|
|
11098
|
+
return preloaded.scan;
|
|
11099
|
+
}
|
|
11100
|
+
const scan2 = scanRepo(repo, { ...opts, precomputedWalk: walked });
|
|
11101
|
+
sessionPut({ key, scan: scan2, cacheMap: toCacheMap(scan2) });
|
|
11102
|
+
return scan2;
|
|
11103
|
+
}
|
|
11104
|
+
function getScanSummary(repo, opts = {}, walked) {
|
|
11105
|
+
if (sessionCaches.some((e) => e.key === sessionKey(repo, opts))) {
|
|
11106
|
+
const scan2 = getScan(repo, opts, walked);
|
|
11107
|
+
return {
|
|
11108
|
+
root: scan2.root,
|
|
11109
|
+
commit: scan2.commit,
|
|
11110
|
+
fileCount: scan2.files.length,
|
|
11111
|
+
languages: scan2.languages,
|
|
11112
|
+
capped: scan2.capped,
|
|
11113
|
+
excluded: scan2.excluded
|
|
11114
|
+
};
|
|
11115
|
+
}
|
|
11116
|
+
return scanSummary(repo, { ...opts, precomputedWalk: walked });
|
|
11117
|
+
}
|
|
11118
|
+
function getArtifacts(repo, opts = {}, walked) {
|
|
11119
|
+
const scan2 = getScan(repo, opts, walked);
|
|
11120
|
+
const entry = sessionCaches.find((e) => e.scan === scan2);
|
|
11121
|
+
if (entry) return entry.arts ??= buildArtifactsFromScan(scan2, opts);
|
|
11122
|
+
return buildArtifactsFromScan(scan2, opts);
|
|
11123
|
+
}
|
|
11124
|
+
async function warmGrammarsForRepo(repo) {
|
|
11125
|
+
await warmGrammarsForWalk(walk(repo, {}));
|
|
11126
|
+
}
|
|
11127
|
+
async function warmGrammarsForWalk(walked) {
|
|
11128
|
+
await ensureGrammars(grammarKeysForExts(walked.files.map((f) => f.ext)));
|
|
11129
|
+
}
|
|
11130
|
+
var embeddingIndexCache, embedModelCache, SESSION_CACHE_MAX, sessionCaches;
|
|
11131
|
+
var init_session = __esm({
|
|
11132
|
+
"src/mcp/session.ts"() {
|
|
11133
|
+
"use strict";
|
|
11134
|
+
init_pipeline();
|
|
11135
|
+
init_scan();
|
|
11136
|
+
init_preload();
|
|
11137
|
+
init_walk();
|
|
11138
|
+
init_loader();
|
|
11139
|
+
init_model();
|
|
11140
|
+
init_embed();
|
|
11141
|
+
init_hash();
|
|
11142
|
+
SESSION_CACHE_MAX = 4;
|
|
11143
|
+
sessionCaches = [];
|
|
11144
|
+
}
|
|
11145
|
+
});
|
|
11146
|
+
|
|
11147
|
+
// src/mcp.ts
|
|
11148
|
+
var mcp_exports = {};
|
|
11149
|
+
__export(mcp_exports, {
|
|
11150
|
+
DEFAULT_MAX_RESPONSE_BYTES: () => DEFAULT_MAX_RESPONSE_BYTES,
|
|
11151
|
+
OUTPUT_SCHEMAS: () => OUTPUT_SCHEMAS,
|
|
11152
|
+
PROTOCOL_VERSIONS: () => PROTOCOL_VERSIONS,
|
|
11153
|
+
TOOLS: () => TOOLS,
|
|
11154
|
+
TOOL_META: () => TOOL_META,
|
|
11155
|
+
annotationsFor: () => annotationsFor,
|
|
11156
|
+
capResponse: () => capResponse,
|
|
11157
|
+
getArtifacts: () => getArtifacts,
|
|
11158
|
+
getScan: () => getScan,
|
|
11159
|
+
getScanSummary: () => getScanSummary,
|
|
11160
|
+
memoizedEmbedModel: () => memoizedEmbedModel,
|
|
11161
|
+
memoizedEmbeddingIndex: () => memoizedEmbeddingIndex,
|
|
11162
|
+
negotiateProtocol: () => negotiateProtocol,
|
|
11163
|
+
resourceLinkFor: () => resourceLinkFor,
|
|
11164
|
+
runMcpServer: () => runMcpServer,
|
|
11165
|
+
scanFingerprint: () => scanFingerprint,
|
|
11166
|
+
structuredContentFor: () => structuredContentFor,
|
|
11167
|
+
toCacheMap: () => toCacheMap,
|
|
11168
|
+
toolsFor: () => toolsFor,
|
|
11169
|
+
validateArgs: () => validateArgs,
|
|
11170
|
+
warmGrammarsForRepo: () => warmGrammarsForRepo,
|
|
11171
|
+
warmGrammarsForWalk: () => warmGrammarsForWalk
|
|
11172
|
+
});
|
|
11173
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
11174
|
+
import { isAbsolute, join as join18 } from "path";
|
|
11175
|
+
import { createInterface } from "readline";
|
|
11176
|
+
function str(v) {
|
|
11177
|
+
return typeof v === "string" && v ? v : void 0;
|
|
11178
|
+
}
|
|
11179
|
+
function strArray(v) {
|
|
11180
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string") && v.length ? v : void 0;
|
|
11181
|
+
}
|
|
11182
|
+
function num(v) {
|
|
11183
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
11184
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
11185
|
+
}
|
|
11186
|
+
function errMessage(e) {
|
|
11187
|
+
return e instanceof Error ? e.message : String(e);
|
|
11188
|
+
}
|
|
11189
|
+
async function callTool(name2, args2, defaultRepo) {
|
|
11190
|
+
const repo = str(args2.repo) ?? defaultRepo;
|
|
11191
|
+
if (!repo) throw new Error("`repo` is required (absolute path to the repository root)");
|
|
11192
|
+
const scanOpts = { scope: str(args2.scope), include: strArray(args2.include), exclude: strArray(args2.exclude) };
|
|
11193
|
+
let walked;
|
|
11194
|
+
if (!SCANLESS_TOOLS.has(name2)) {
|
|
11195
|
+
walked = walk(repo, {});
|
|
11196
|
+
await warmGrammarsForWalk(walked);
|
|
11197
|
+
}
|
|
11198
|
+
if (name2 === "scan_summary") {
|
|
11199
|
+
const s = getScanSummary(repo, scanOpts, walked);
|
|
11200
|
+
return JSON.stringify(
|
|
11201
|
+
{ engineVersion: ENGINE_VERSION, commit: s.commit, fileCount: s.fileCount, languages: s.languages, capped: s.capped },
|
|
11202
|
+
null,
|
|
11203
|
+
2
|
|
11204
|
+
);
|
|
11205
|
+
}
|
|
11206
|
+
if (name2 === "graph") {
|
|
11207
|
+
return renderGraphJson(getArtifacts(repo, scanOpts, walked).graph);
|
|
11208
|
+
}
|
|
11209
|
+
if (name2 === "symbols") {
|
|
11210
|
+
const { symbols } = getArtifacts(repo, scanOpts, walked);
|
|
11211
|
+
const lookup = str(args2.name);
|
|
11212
|
+
if (lookup) {
|
|
11213
|
+
return JSON.stringify({ name: lookup, defs: symbols.defs[lookup] ?? [], refs: symbols.refs[lookup] ?? [] }, null, 2);
|
|
11214
|
+
}
|
|
11215
|
+
return JSON.stringify(symbols, null, 2);
|
|
11216
|
+
}
|
|
11217
|
+
if (name2 === "callers") {
|
|
11218
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
11219
|
+
const index = args2.recall === true ? buildCallerIndex(scan2, void 0, { recall: true }) : callerIndexFor(scan2);
|
|
11220
|
+
const lookup = str(args2.name);
|
|
11221
|
+
if (lookup) {
|
|
11222
|
+
const entry = index.get(lookup);
|
|
11223
|
+
return JSON.stringify(entry ?? { error: `no tracked callers for "${lookup}"` }, null, 2);
|
|
11224
|
+
}
|
|
11225
|
+
const obj = {};
|
|
11226
|
+
for (const [k, v] of index) obj[k] = v;
|
|
11227
|
+
return JSON.stringify(obj, null, 2);
|
|
11228
|
+
}
|
|
11229
|
+
if (name2 === "workspaces") {
|
|
11230
|
+
const info2 = detectWorkspaces(repo);
|
|
11231
|
+
return JSON.stringify({ packages: info2.packages, cycle: info2.cycle ?? null, topoOrder: info2.topoOrder }, null, 2);
|
|
11232
|
+
}
|
|
11233
|
+
if (name2 === "churn") {
|
|
11234
|
+
const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
|
|
11235
|
+
const sorted = {};
|
|
11236
|
+
for (const k of [...churn.keys()].sort()) sorted[k] = churn.get(k);
|
|
11237
|
+
return JSON.stringify({ ok, churn: sorted }, null, 2);
|
|
11238
|
+
}
|
|
11239
|
+
if (name2 === "symbols_overview") {
|
|
11240
|
+
const file = str(args2.file);
|
|
11241
|
+
if (!file) throw new Error("`file` is required");
|
|
11242
|
+
return JSON.stringify(symbolsOverview(getScan(repo, scanOpts, walked), file), null, 2);
|
|
11243
|
+
}
|
|
11244
|
+
if (name2 === "find_symbol") {
|
|
11245
|
+
const namePath = str(args2.namePath);
|
|
11246
|
+
if (!namePath) throw new Error("`namePath` is required");
|
|
11247
|
+
const matches = findSymbol(getScan(repo, scanOpts, walked), namePath, {
|
|
11248
|
+
substring: args2.substring === true,
|
|
11249
|
+
includeBody: args2.includeBody === true,
|
|
11250
|
+
maxResults: num(args2.maxResults)
|
|
11251
|
+
});
|
|
11252
|
+
return JSON.stringify(matches, null, 2);
|
|
11253
|
+
}
|
|
11254
|
+
if (name2 === "find_references") {
|
|
11255
|
+
const symName = str(args2.name);
|
|
11256
|
+
if (!symName) throw new Error("`name` is required");
|
|
11257
|
+
return JSON.stringify(findReferences(getScan(repo, scanOpts, walked), symName), null, 2);
|
|
11258
|
+
}
|
|
11259
|
+
if (name2 === "replace_symbol_body" || name2 === "insert_after_symbol" || name2 === "insert_before_symbol") {
|
|
11260
|
+
const namePath = str(args2.namePath);
|
|
11261
|
+
const body2 = typeof args2.body === "string" ? args2.body : void 0;
|
|
11262
|
+
if (!namePath || body2 === void 0) throw new Error("`namePath` and `body` are required");
|
|
11263
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
11264
|
+
const fn = name2 === "replace_symbol_body" ? replaceSymbolBody : name2 === "insert_after_symbol" ? insertAfterSymbol : insertBeforeSymbol;
|
|
11265
|
+
const result = fn(scan2, namePath, body2, str(args2.file));
|
|
11266
|
+
sessionClear();
|
|
11267
|
+
return JSON.stringify(result, null, 2);
|
|
11268
|
+
}
|
|
11269
|
+
if (name2 === "write_memory") {
|
|
11270
|
+
const memName = str(args2.name);
|
|
11271
|
+
const content = typeof args2.content === "string" ? args2.content : void 0;
|
|
11272
|
+
if (!memName || content === void 0) throw new Error("`name` and `content` are required");
|
|
11273
|
+
return JSON.stringify({ written: writeMemory(repo, memName, content) }, null, 2);
|
|
11274
|
+
}
|
|
11275
|
+
if (name2 === "read_memory") {
|
|
11276
|
+
const memName = str(args2.name);
|
|
11277
|
+
if (!memName) throw new Error("`name` is required");
|
|
11278
|
+
const content = readMemory(repo, memName);
|
|
11279
|
+
if (content === void 0) throw new Error(`no memory named "${memName}" \u2014 see list_memories`);
|
|
11280
|
+
return content;
|
|
11281
|
+
}
|
|
11282
|
+
if (name2 === "list_memories") {
|
|
11283
|
+
return JSON.stringify(listMemories(repo), null, 2);
|
|
11284
|
+
}
|
|
11285
|
+
if (name2 === "delete_memory") {
|
|
11286
|
+
const memName = str(args2.name);
|
|
11287
|
+
if (!memName) throw new Error("`name` is required");
|
|
11288
|
+
return JSON.stringify({ deleted: deleteMemory(repo, memName) }, null, 2);
|
|
11289
|
+
}
|
|
11290
|
+
if (name2 === "dead_code") {
|
|
11291
|
+
const all = findDeadCode(getScan(repo, scanOpts, walked));
|
|
11292
|
+
const limit = num(args2.limit);
|
|
11293
|
+
if (limit === void 0 || all.length <= limit) return JSON.stringify(all, null, 2);
|
|
11294
|
+
return JSON.stringify({ total: all.length, shown: limit, truncated: true, candidates: all.slice(0, limit) }, null, 2);
|
|
11295
|
+
}
|
|
11296
|
+
if (name2 === "complexity") {
|
|
11297
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
11298
|
+
if (args2.risk === true) {
|
|
11299
|
+
const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
|
|
11300
|
+
return JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn, num(args2.top)) }, null, 2);
|
|
11301
|
+
}
|
|
11302
|
+
return JSON.stringify(symbolComplexity(scan2, str(args2.file), num(args2.top)), null, 2);
|
|
11303
|
+
}
|
|
11304
|
+
if (name2 === "mermaid") {
|
|
11305
|
+
const { graph } = getArtifacts(repo, scanOpts, walked);
|
|
11306
|
+
return renderMermaid(graph, { module: str(args2.module), maxEdges: num(args2.maxEdges) });
|
|
11307
|
+
}
|
|
11308
|
+
if (name2 === "repo_map") {
|
|
11309
|
+
const { scan: scan2, graph } = getArtifacts(repo, scanOpts, walked);
|
|
11310
|
+
return renderRepoMap(scan2, graph, { budgetTokens: typeof args2.budgetTokens === "number" ? args2.budgetTokens : void 0 });
|
|
11311
|
+
}
|
|
11312
|
+
if (name2 === "hotspots") {
|
|
11313
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
11314
|
+
const { churn, ok } = gitChurn(repo, { since: str(args2.since) });
|
|
11315
|
+
return JSON.stringify({ churnOk: ok, hotspots: rankHotspots(scan2, churn) }, null, 2);
|
|
11316
|
+
}
|
|
11317
|
+
if (name2 === "coupling") {
|
|
11318
|
+
const { ok, couplings } = changeCoupling(repo, { since: str(args2.since) });
|
|
11319
|
+
return JSON.stringify({ ok, couplings }, null, 2);
|
|
11320
|
+
}
|
|
11321
|
+
if (name2 === "grep") {
|
|
11322
|
+
const pattern = str(args2.pattern);
|
|
11323
|
+
if (!pattern) throw new Error("`pattern` is required");
|
|
11324
|
+
const scope = str(args2.scope);
|
|
11325
|
+
const globs = strArray(args2.globs);
|
|
11326
|
+
const hits = grepRepo(repo, pattern, {
|
|
11327
|
+
globs: scope ? [...globs ?? [], `${scope.replace(/\/+$/, "")}/**`] : globs,
|
|
11328
|
+
ignoreCase: args2.ignoreCase === true,
|
|
11329
|
+
maxHits: typeof args2.maxHits === "number" ? args2.maxHits : void 0
|
|
11330
|
+
});
|
|
11331
|
+
return JSON.stringify(hits, null, 2);
|
|
11332
|
+
}
|
|
11333
|
+
if (name2 === "search") {
|
|
11334
|
+
const query = str(args2.query);
|
|
11335
|
+
if (!query) throw new Error("`query` is required");
|
|
11336
|
+
const scan2 = getScan(repo, scanOpts, walked);
|
|
11337
|
+
const limit = typeof args2.limit === "number" ? args2.limit : void 0;
|
|
11338
|
+
const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
|
|
11339
|
+
if (args2.semantic === true) {
|
|
11340
|
+
const endpoint = resolveEmbedEndpoint();
|
|
11341
|
+
if (endpoint) {
|
|
11342
|
+
try {
|
|
11343
|
+
const index = await memoizedEmbeddingIndex({ mode: "endpoint", identity: endpoint, scan: scan2 }, () => buildEndpointIndex(scan2));
|
|
11344
|
+
const queryVec = await encodeQueryViaEndpoint(query);
|
|
11345
|
+
const results2 = searchSemantic(scan2, query, index, { queryVec, limit, fuzzy });
|
|
11346
|
+
return JSON.stringify({ results: results2, tier: "endpoint" }, null, 2);
|
|
11347
|
+
} catch (e) {
|
|
11348
|
+
const results2 = searchIndex(scan2, query, { limit, fuzzy });
|
|
11349
|
+
return JSON.stringify(
|
|
11350
|
+
{ results: results2, tier: "lexical", degradedReason: `embedding endpoint failed: ${errMessage(e)}` },
|
|
11351
|
+
null,
|
|
11352
|
+
2
|
|
11353
|
+
);
|
|
11354
|
+
}
|
|
11355
|
+
}
|
|
11356
|
+
const modelDir = resolveEmbedModelDir(repo);
|
|
11357
|
+
const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
|
|
11358
|
+
if (model) {
|
|
11359
|
+
const index = await memoizedEmbeddingIndex(
|
|
11360
|
+
{ mode: "static", identity: `${modelDir}#${model.modelId}`, scan: scan2 },
|
|
11361
|
+
() => buildEmbeddingIndex(scan2, model)
|
|
11362
|
+
);
|
|
11363
|
+
const results2 = searchSemantic(scan2, query, index, { model, limit, fuzzy });
|
|
11364
|
+
return JSON.stringify({ results: results2, tier: "static" }, null, 2);
|
|
11365
|
+
}
|
|
11366
|
+
const results = searchIndex(scan2, query, { limit, fuzzy });
|
|
11367
|
+
return JSON.stringify(
|
|
11368
|
+
{ results, tier: "lexical", degradedReason: "no embedding endpoint or static model configured \u2014 see embed_status" },
|
|
11369
|
+
null,
|
|
11370
|
+
2
|
|
11371
|
+
);
|
|
11372
|
+
}
|
|
11373
|
+
return JSON.stringify(searchIndex(scan2, query, { limit, fuzzy }), null, 2);
|
|
11374
|
+
}
|
|
11375
|
+
if (name2 === "embed_status") {
|
|
11376
|
+
const modelDir = resolveEmbedModelDir(repo);
|
|
11377
|
+
const model = modelDir ? memoizedEmbedModel(modelDir) : void 0;
|
|
11378
|
+
const endpoint = resolveEmbedEndpoint();
|
|
11379
|
+
const mode = endpoint ? "endpoint" : model ? "static" : "none";
|
|
11380
|
+
const status = {
|
|
11381
|
+
embedVersion: EMBED_VERSION,
|
|
11382
|
+
mode,
|
|
11383
|
+
model: model ? { present: true, dir: modelDir, modelId: model.modelId, dim: model.dim, vocabSize: model.vocabSize } : { present: false },
|
|
11384
|
+
endpoint: endpoint ?? null
|
|
11385
|
+
};
|
|
11386
|
+
if (endpoint) status.endpointReachable = await probeEndpoint(endpoint);
|
|
11387
|
+
return JSON.stringify(status, null, 2);
|
|
11388
|
+
}
|
|
11389
|
+
if (name2 === "check_rules") {
|
|
11390
|
+
const configPath = str(args2.configPath);
|
|
11391
|
+
let payload = args2.rules;
|
|
11392
|
+
if (payload === void 0 && configPath) {
|
|
11393
|
+
const abs = isAbsolute(configPath) ? configPath : join18(repo, configPath);
|
|
11394
|
+
try {
|
|
11395
|
+
payload = JSON.parse(readFileSync8(abs, "utf8"));
|
|
11396
|
+
} catch (e) {
|
|
11397
|
+
throw new Error(`cannot read rules from ${abs}: ${errMessage(e)}`);
|
|
11398
|
+
}
|
|
11399
|
+
}
|
|
11400
|
+
if (payload === void 0) throw new Error("`rules` (or `configPath`) is required");
|
|
11401
|
+
const rules = parseRules(payload);
|
|
11402
|
+
const { graph } = getArtifacts(repo, scanOpts, walked);
|
|
11403
|
+
return JSON.stringify(checkRules(graph, rules), null, 2);
|
|
11404
|
+
}
|
|
11405
|
+
throw new Error(`unknown tool: ${name2}`);
|
|
11406
|
+
}
|
|
11407
|
+
async function runMcpServer(opts = {}) {
|
|
11408
|
+
const serverInfo = {
|
|
11409
|
+
name: opts.serverInfo?.name ?? "codeindex",
|
|
11410
|
+
version: opts.serverInfo?.version ?? ENGINE_VERSION
|
|
11411
|
+
};
|
|
11412
|
+
let protocolVersion = PROTOCOL_VERSIONS[0];
|
|
11413
|
+
let tools = toolsFor(opts.defaultRepo, protocolVersion);
|
|
11414
|
+
const send = (msg) => {
|
|
11415
|
+
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...msg }) + "\n");
|
|
11416
|
+
};
|
|
11417
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
11418
|
+
for await (const line of rl) {
|
|
11419
|
+
const trimmed = line.trim();
|
|
11420
|
+
if (!trimmed) continue;
|
|
11421
|
+
let parsed;
|
|
11422
|
+
try {
|
|
11423
|
+
parsed = JSON.parse(trimmed);
|
|
11424
|
+
} catch {
|
|
11425
|
+
send({ id: null, error: { code: -32700, message: "parse error" } });
|
|
11426
|
+
continue;
|
|
11427
|
+
}
|
|
11428
|
+
const requests = Array.isArray(parsed) ? parsed : [parsed];
|
|
11429
|
+
for (const req of requests) await handle2(req);
|
|
11430
|
+
}
|
|
11431
|
+
async function handle2(req) {
|
|
11432
|
+
if (req.id === void 0 || req.id === null) return;
|
|
11433
|
+
try {
|
|
11434
|
+
if (req.method === "initialize") {
|
|
11435
|
+
protocolVersion = negotiateProtocol(req.params?.protocolVersion);
|
|
11436
|
+
tools = toolsFor(opts.defaultRepo, protocolVersion);
|
|
11437
|
+
send({
|
|
11438
|
+
id: req.id,
|
|
11439
|
+
result: {
|
|
11440
|
+
protocolVersion,
|
|
11441
|
+
capabilities: { tools: {} },
|
|
11442
|
+
serverInfo
|
|
11443
|
+
}
|
|
11444
|
+
});
|
|
11445
|
+
} else if (req.method === "ping") {
|
|
11446
|
+
send({ id: req.id, result: {} });
|
|
11447
|
+
} else if (req.method === "tools/list") {
|
|
11448
|
+
send({ id: req.id, result: { tools } });
|
|
11449
|
+
} else if (req.method === "tools/call") {
|
|
11450
|
+
const params = req.params ?? {};
|
|
11451
|
+
const name2 = str(params.name) ?? "";
|
|
11452
|
+
const args2 = params.arguments ?? {};
|
|
11453
|
+
try {
|
|
11454
|
+
const decl = tools.find(
|
|
11455
|
+
(t) => t.name === name2
|
|
11456
|
+
);
|
|
11457
|
+
const invalid = decl ? validateArgs(decl.inputSchema, args2) : void 0;
|
|
11458
|
+
if (invalid) throw new Error(invalid);
|
|
11459
|
+
const raw = await callTool(name2, args2, opts.defaultRepo);
|
|
11460
|
+
const repo = str(args2.repo) ?? opts.defaultRepo ?? "";
|
|
11461
|
+
const text = capResponse(raw, name2, repo, opts.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES);
|
|
11462
|
+
const capped = text !== raw;
|
|
11463
|
+
const link = capped && protocolVersion >= RICH_TOOLS_SINCE ? resourceLinkFor(text, name2) : void 0;
|
|
11464
|
+
const structured = protocolVersion >= RICH_TOOLS_SINCE ? structuredContentFor(text, capped, OUTPUT_SCHEMAS[name2] !== void 0) : void 0;
|
|
11465
|
+
send({
|
|
11466
|
+
id: req.id,
|
|
11467
|
+
result: {
|
|
11468
|
+
content: link ? [{ type: "text", text }, link] : [{ type: "text", text }],
|
|
11469
|
+
...structured ? { structuredContent: structured } : {}
|
|
11470
|
+
}
|
|
11471
|
+
});
|
|
11472
|
+
} catch (e) {
|
|
11473
|
+
send({
|
|
11474
|
+
id: req.id,
|
|
11475
|
+
result: { content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }], isError: true }
|
|
11476
|
+
});
|
|
10990
11477
|
}
|
|
11478
|
+
} else {
|
|
11479
|
+
send({ id: req.id, error: { code: -32601, message: `method not found: ${req.method}` } });
|
|
10991
11480
|
}
|
|
10992
|
-
|
|
11481
|
+
} catch (e) {
|
|
11482
|
+
send({ id: req.id, error: { code: -32603, message: e instanceof Error ? e.message : String(e) } });
|
|
11483
|
+
}
|
|
11484
|
+
}
|
|
11485
|
+
}
|
|
11486
|
+
var SCANLESS_TOOLS;
|
|
11487
|
+
var init_mcp = __esm({
|
|
11488
|
+
"src/mcp.ts"() {
|
|
11489
|
+
"use strict";
|
|
11490
|
+
init_types();
|
|
11491
|
+
init_graph_json();
|
|
11492
|
+
init_callers();
|
|
11493
|
+
init_derived();
|
|
11494
|
+
init_workspaces();
|
|
11495
|
+
init_git();
|
|
11496
|
+
init_grep();
|
|
11497
|
+
init_coupling();
|
|
11498
|
+
init_repomap();
|
|
11499
|
+
init_deadcode();
|
|
11500
|
+
init_complexity();
|
|
11501
|
+
init_viz();
|
|
11502
|
+
init_query();
|
|
11503
|
+
init_edit();
|
|
11504
|
+
init_memory();
|
|
11505
|
+
init_bm25();
|
|
11506
|
+
init_rules();
|
|
11507
|
+
init_model();
|
|
11508
|
+
init_embed();
|
|
11509
|
+
init_search();
|
|
11510
|
+
init_endpoint();
|
|
11511
|
+
init_walk();
|
|
11512
|
+
init_tools();
|
|
11513
|
+
init_protocol();
|
|
11514
|
+
init_session();
|
|
11515
|
+
init_tools();
|
|
11516
|
+
init_protocol();
|
|
11517
|
+
init_session();
|
|
10993
11518
|
SCANLESS_TOOLS = /* @__PURE__ */ new Set([
|
|
10994
11519
|
"workspaces",
|
|
10995
11520
|
"churn",
|
|
@@ -10999,7 +11524,11 @@ var init_mcp = __esm({
|
|
|
10999
11524
|
"read_memory",
|
|
11000
11525
|
"list_memories",
|
|
11001
11526
|
"delete_memory",
|
|
11002
|
-
"embed_status"
|
|
11527
|
+
"embed_status",
|
|
11528
|
+
// scan_summary counts and classifies by path only — it never parses, so the
|
|
11529
|
+
// grammar warm (a whole extra walk) would be pure overhead. When a scan is
|
|
11530
|
+
// already cached getScanSummary reuses it, warm grammars included.
|
|
11531
|
+
"scan_summary"
|
|
11003
11532
|
]);
|
|
11004
11533
|
}
|
|
11005
11534
|
});
|
|
@@ -11123,6 +11652,140 @@ var init_rewrite = __esm({
|
|
|
11123
11652
|
init_types();
|
|
11124
11653
|
init_walk();
|
|
11125
11654
|
init_scan();
|
|
11655
|
+
init_scan();
|
|
11656
|
+
init_preload();
|
|
11657
|
+
|
|
11658
|
+
// src/pool.ts
|
|
11659
|
+
init_hash();
|
|
11660
|
+
init_walk();
|
|
11661
|
+
init_registry();
|
|
11662
|
+
init_loader();
|
|
11663
|
+
init_scan();
|
|
11664
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
11665
|
+
import { availableParallelism } from "os";
|
|
11666
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
11667
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
11668
|
+
import { Worker } from "worker_threads";
|
|
11669
|
+
function resolveEngineUrl() {
|
|
11670
|
+
try {
|
|
11671
|
+
const here = fileURLToPath2(import.meta.url);
|
|
11672
|
+
if (here.endsWith("engine.mjs")) return pathToFileURL(here).href;
|
|
11673
|
+
const adjacent = join4(dirname2(here), "engine.mjs");
|
|
11674
|
+
if (existsSync2(adjacent)) return pathToFileURL(adjacent).href;
|
|
11675
|
+
return void 0;
|
|
11676
|
+
} catch {
|
|
11677
|
+
return void 0;
|
|
11678
|
+
}
|
|
11679
|
+
}
|
|
11680
|
+
var WORKER_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
11681
|
+
function workerCount(requested) {
|
|
11682
|
+
const env = process.env["CODEINDEX_WORKERS"];
|
|
11683
|
+
const raw = requested ?? (env !== void 0 && env !== "" ? Number(env) : void 0);
|
|
11684
|
+
if (raw !== void 0) return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 0;
|
|
11685
|
+
let cores = 1;
|
|
11686
|
+
try {
|
|
11687
|
+
cores = availableParallelism();
|
|
11688
|
+
} catch {
|
|
11689
|
+
cores = 1;
|
|
11690
|
+
}
|
|
11691
|
+
return Math.max(0, Math.min(cores - 1, 8));
|
|
11692
|
+
}
|
|
11693
|
+
async function runExtractWorker(input, post) {
|
|
11694
|
+
await ensureGrammars(input.grammarKeys);
|
|
11695
|
+
const ready = input.grammarKeys.filter((k) => grammarReady(k));
|
|
11696
|
+
const records = [];
|
|
11697
|
+
for (const job of input.jobs) {
|
|
11698
|
+
let size;
|
|
11699
|
+
let mtimeMs;
|
|
11700
|
+
try {
|
|
11701
|
+
const st = statSync2(job.abs);
|
|
11702
|
+
size = st.size;
|
|
11703
|
+
mtimeMs = st.mtimeMs;
|
|
11704
|
+
} catch {
|
|
11705
|
+
continue;
|
|
11706
|
+
}
|
|
11707
|
+
const content = readText(job.abs);
|
|
11708
|
+
const record = buildCodeRecord(job.rel, job.ext, size, content, sha1(content), extToLang(job.ext), {
|
|
11709
|
+
maxCallsPerFile: input.maxCallsPerFile
|
|
11710
|
+
});
|
|
11711
|
+
records.push({ rel: job.rel, size, mtimeMs, record });
|
|
11712
|
+
}
|
|
11713
|
+
post({ ready, records });
|
|
11714
|
+
}
|
|
11715
|
+
async function extractInParallel(jobs, grammarKeys, count, opts = {}) {
|
|
11716
|
+
if (count < 2 || jobs.length === 0) return void 0;
|
|
11717
|
+
const engineUrl = resolveEngineUrl();
|
|
11718
|
+
if (!engineUrl) return void 0;
|
|
11719
|
+
const wanted = grammarKeys.filter((k) => grammarReady(k)).sort();
|
|
11720
|
+
const shards = Array.from({ length: Math.min(count, jobs.length) }, () => []);
|
|
11721
|
+
jobs.forEach((j, i2) => shards[i2 % shards.length].push(j));
|
|
11722
|
+
const bootstrap = `import { runExtractWorker } from ${JSON.stringify(engineUrl)};
|
|
11723
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
11724
|
+
runExtractWorker(workerData.input, (o) => parentPort.postMessage(o)).catch((e) => parentPort.postMessage({ error: String(e) }));
|
|
11725
|
+
`;
|
|
11726
|
+
try {
|
|
11727
|
+
const outputs = await Promise.all(
|
|
11728
|
+
shards.map(
|
|
11729
|
+
(jobsForShard) => new Promise((resolve3, reject) => {
|
|
11730
|
+
const w = new Worker(bootstrap, {
|
|
11731
|
+
eval: true,
|
|
11732
|
+
workerData: { input: { jobs: jobsForShard, grammarKeys: wanted, maxCallsPerFile: opts.maxCallsPerFile } }
|
|
11733
|
+
});
|
|
11734
|
+
const timer = setTimeout(() => {
|
|
11735
|
+
reject(new Error("extraction worker timed out"));
|
|
11736
|
+
void w.terminate();
|
|
11737
|
+
}, WORKER_TIMEOUT_MS);
|
|
11738
|
+
const settle = (fn) => {
|
|
11739
|
+
clearTimeout(timer);
|
|
11740
|
+
fn();
|
|
11741
|
+
};
|
|
11742
|
+
w.once("message", (m) => {
|
|
11743
|
+
settle(() => resolve3(m));
|
|
11744
|
+
void w.terminate();
|
|
11745
|
+
});
|
|
11746
|
+
w.once("error", (e) => settle(() => reject(e)));
|
|
11747
|
+
w.once("exit", (code) => {
|
|
11748
|
+
if (code !== 0) settle(() => reject(new Error(`extraction worker exited with ${code}`)));
|
|
11749
|
+
});
|
|
11750
|
+
})
|
|
11751
|
+
)
|
|
11752
|
+
);
|
|
11753
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
11754
|
+
for (const o of outputs) {
|
|
11755
|
+
if ("error" in o) return void 0;
|
|
11756
|
+
if (o.ready.slice().sort().join(",") !== wanted.join(",")) return void 0;
|
|
11757
|
+
for (const r of o.records) out2.set(r.rel, { size: r.size, mtimeMs: r.mtimeMs, record: r.record });
|
|
11758
|
+
}
|
|
11759
|
+
return out2;
|
|
11760
|
+
} catch {
|
|
11761
|
+
return void 0;
|
|
11762
|
+
}
|
|
11763
|
+
}
|
|
11764
|
+
async function scanRepoParallel(root, opts = {}) {
|
|
11765
|
+
const count = workerCount(opts.workers);
|
|
11766
|
+
if (count < 2) return scanRepo(root, opts);
|
|
11767
|
+
const walked = opts.precomputedWalk ?? walk(root, {
|
|
11768
|
+
maxFileBytes: opts.maxBytes,
|
|
11769
|
+
maxFiles: opts.maxFiles,
|
|
11770
|
+
gitignore: opts.gitignore,
|
|
11771
|
+
ignoreDirs: opts.ignoreDirs
|
|
11772
|
+
});
|
|
11773
|
+
const scanOpts = { ...opts, precomputedWalk: walked };
|
|
11774
|
+
const jobs = [];
|
|
11775
|
+
for (const { f } of keptCodeFiles(root, scanOpts)) {
|
|
11776
|
+
const cached = opts.cache?.get(f.rel);
|
|
11777
|
+
if (!opts.fullHash && cached && cached.size !== void 0 && cached.mtimeMs !== void 0 && cached.size === f.size && cached.mtimeMs === f.mtimeMs) {
|
|
11778
|
+
continue;
|
|
11779
|
+
}
|
|
11780
|
+
jobs.push({ abs: f.abs, rel: f.rel, ext: f.ext });
|
|
11781
|
+
}
|
|
11782
|
+
if (jobs.length === 0) return scanRepo(root, scanOpts);
|
|
11783
|
+
const grammarKeys = grammarKeysForExts(walked.files.map((f) => f.ext));
|
|
11784
|
+
const extracted = await extractInParallel(jobs, grammarKeys, count, { maxCallsPerFile: opts.maxCallsPerFile });
|
|
11785
|
+
return scanRepo(root, extracted ? { ...scanOpts, extracted } : scanOpts);
|
|
11786
|
+
}
|
|
11787
|
+
|
|
11788
|
+
// src/engine.ts
|
|
11126
11789
|
init_glob();
|
|
11127
11790
|
init_ignore();
|
|
11128
11791
|
init_classify();
|
|
@@ -11260,8 +11923,8 @@ init_loader();
|
|
|
11260
11923
|
// src/ast/grammars-pull.ts
|
|
11261
11924
|
init_types();
|
|
11262
11925
|
import { createHash as createHash2 } from "crypto";
|
|
11263
|
-
import { existsSync as
|
|
11264
|
-
import { dirname as
|
|
11926
|
+
import { existsSync as existsSync3, mkdirSync, mkdtempSync, readFileSync as readFileSync4, renameSync, rmSync, writeFileSync } from "fs";
|
|
11927
|
+
import { dirname as dirname3, join as join5, resolve, sep as sep2 } from "path";
|
|
11265
11928
|
import { gunzipSync } from "zlib";
|
|
11266
11929
|
var DEFAULT_GRAMMARS_URL = `https://github.com/maxgfr/codeindex/releases/download/v${ENGINE_VERSION}/grammars-${ENGINE_VERSION}.tar.gz`;
|
|
11267
11930
|
function resolveGrammarsPullTarget() {
|
|
@@ -11342,7 +12005,7 @@ function extractTarInto(rawTar, destDir) {
|
|
|
11342
12005
|
if (dest !== root && !dest.startsWith(root + sep2)) {
|
|
11343
12006
|
throw new Error(`tar entry escapes destination: ${entry.name}`);
|
|
11344
12007
|
}
|
|
11345
|
-
mkdirSync(
|
|
12008
|
+
mkdirSync(dirname3(dest), { recursive: true });
|
|
11346
12009
|
writeFileSync(dest, entry.data);
|
|
11347
12010
|
written.push(rel);
|
|
11348
12011
|
}
|
|
@@ -11366,12 +12029,12 @@ async function pullGrammars(cacheDir, opts = {}) {
|
|
|
11366
12029
|
`);
|
|
11367
12030
|
}
|
|
11368
12031
|
}
|
|
11369
|
-
const runtime =
|
|
11370
|
-
const markerPath =
|
|
11371
|
-
if (
|
|
12032
|
+
const runtime = join5(cacheDir, "web-tree-sitter.wasm");
|
|
12033
|
+
const markerPath = join5(dirname3(cacheDir), `${ENGINE_VERSION}.sha256`);
|
|
12034
|
+
if (existsSync3(runtime) && expected && existsSync3(markerPath)) {
|
|
11372
12035
|
let marker = "";
|
|
11373
12036
|
try {
|
|
11374
|
-
marker =
|
|
12037
|
+
marker = readFileSync4(markerPath, "utf8").trim();
|
|
11375
12038
|
} catch {
|
|
11376
12039
|
}
|
|
11377
12040
|
if (marker === expected) {
|
|
@@ -11395,13 +12058,13 @@ async function pullGrammars(cacheDir, opts = {}) {
|
|
|
11395
12058
|
}
|
|
11396
12059
|
let tmp;
|
|
11397
12060
|
try {
|
|
11398
|
-
mkdirSync(
|
|
11399
|
-
tmp = mkdtempSync(
|
|
12061
|
+
mkdirSync(dirname3(cacheDir), { recursive: true });
|
|
12062
|
+
tmp = mkdtempSync(join5(dirname3(cacheDir), ".grammars-tmp-"));
|
|
11400
12063
|
extractGrammarsTarball(bytes, tmp);
|
|
11401
|
-
if (!
|
|
12064
|
+
if (!existsSync3(join5(tmp, "web-tree-sitter.wasm"))) {
|
|
11402
12065
|
throw new Error("archive is missing web-tree-sitter.wasm");
|
|
11403
12066
|
}
|
|
11404
|
-
if (
|
|
12067
|
+
if (existsSync3(cacheDir)) rmSync(cacheDir, { recursive: true, force: true });
|
|
11405
12068
|
renameSync(tmp, cacheDir);
|
|
11406
12069
|
tmp = void 0;
|
|
11407
12070
|
if (expected) writeFileSync(markerPath, expected + "\n");
|
|
@@ -11478,7 +12141,48 @@ init_graph_json();
|
|
|
11478
12141
|
init_types();
|
|
11479
12142
|
init_walk();
|
|
11480
12143
|
init_sort();
|
|
11481
|
-
import { join as
|
|
12144
|
+
import { join as join14 } from "path";
|
|
12145
|
+
var Bytes = class {
|
|
12146
|
+
buf;
|
|
12147
|
+
len = 0;
|
|
12148
|
+
constructor(capacity = 64) {
|
|
12149
|
+
this.buf = new Uint8Array(capacity);
|
|
12150
|
+
}
|
|
12151
|
+
get length() {
|
|
12152
|
+
return this.len;
|
|
12153
|
+
}
|
|
12154
|
+
grow(need) {
|
|
12155
|
+
if (this.len + need <= this.buf.length) return;
|
|
12156
|
+
let cap = this.buf.length * 2 || 64;
|
|
12157
|
+
while (cap < this.len + need) cap *= 2;
|
|
12158
|
+
const next = new Uint8Array(cap);
|
|
12159
|
+
next.set(this.buf.subarray(0, this.len));
|
|
12160
|
+
this.buf = next;
|
|
12161
|
+
}
|
|
12162
|
+
// Rewind without releasing the buffer, so a per-item scratch sink is
|
|
12163
|
+
// allocated once per document instead of once per occurrence/symbol.
|
|
12164
|
+
reset() {
|
|
12165
|
+
this.len = 0;
|
|
12166
|
+
}
|
|
12167
|
+
push(byte) {
|
|
12168
|
+
this.grow(1);
|
|
12169
|
+
this.buf[this.len++] = byte;
|
|
12170
|
+
}
|
|
12171
|
+
pushAll(src) {
|
|
12172
|
+
this.grow(src.length);
|
|
12173
|
+
if (src instanceof Uint8Array) this.buf.set(src, this.len);
|
|
12174
|
+
else for (let i2 = 0; i2 < src.length; i2++) this.buf[this.len + i2] = src[i2];
|
|
12175
|
+
this.len += src.length;
|
|
12176
|
+
}
|
|
12177
|
+
// A view over exactly the bytes written — no copy. Callers must not retain it
|
|
12178
|
+
// across further writes to this sink.
|
|
12179
|
+
view() {
|
|
12180
|
+
return this.buf.subarray(0, this.len);
|
|
12181
|
+
}
|
|
12182
|
+
toUint8Array() {
|
|
12183
|
+
return this.buf.slice(0, this.len);
|
|
12184
|
+
}
|
|
12185
|
+
};
|
|
11482
12186
|
var utf8 = new TextEncoder();
|
|
11483
12187
|
function pushVarint(out2, n) {
|
|
11484
12188
|
if (n < 0) throw new Error(`pushVarint: negative input ${n} is not a valid unsigned varint`);
|
|
@@ -11498,15 +12202,18 @@ function pushVarintField(out2, field, n) {
|
|
|
11498
12202
|
function pushLenDelim(out2, field, payload) {
|
|
11499
12203
|
pushTag(out2, field, 2);
|
|
11500
12204
|
pushVarint(out2, payload.length);
|
|
11501
|
-
|
|
12205
|
+
out2.pushAll(payload);
|
|
12206
|
+
}
|
|
12207
|
+
function pushMessage(out2, field, payload) {
|
|
12208
|
+
pushLenDelim(out2, field, payload.view());
|
|
11502
12209
|
}
|
|
11503
12210
|
function pushString(out2, field, s) {
|
|
11504
12211
|
pushLenDelim(out2, field, utf8.encode(s));
|
|
11505
12212
|
}
|
|
11506
12213
|
function pushPackedInt32(out2, field, values) {
|
|
11507
|
-
const payload =
|
|
12214
|
+
const payload = new Bytes(values.length * 2);
|
|
11508
12215
|
for (const v of values) pushVarint(payload, v);
|
|
11509
|
-
|
|
12216
|
+
pushMessage(out2, field, payload);
|
|
11510
12217
|
}
|
|
11511
12218
|
var F_INDEX_METADATA = 1;
|
|
11512
12219
|
var F_INDEX_DOCUMENTS = 2;
|
|
@@ -11648,7 +12355,7 @@ function renderScip(scan2, opts = {}) {
|
|
|
11648
12355
|
};
|
|
11649
12356
|
const documents = [];
|
|
11650
12357
|
for (const f of docs) {
|
|
11651
|
-
const text = readText(
|
|
12358
|
+
const text = readText(join14(scan2.root, f.rel));
|
|
11652
12359
|
const lines = text.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
|
|
11653
12360
|
const locate = (lineNo, name2) => {
|
|
11654
12361
|
const line = lines[lineNo - 1];
|
|
@@ -11677,41 +12384,45 @@ function renderScip(scan2, opts = {}) {
|
|
|
11677
12384
|
kind: KIND[sym.kind],
|
|
11678
12385
|
enclosing: sym.parent ? enclosingSymbolOf(f.rel, sym.parent) : void 0
|
|
11679
12386
|
})).sort((a, b) => byStr(a.symbol, b.symbol));
|
|
11680
|
-
const doc =
|
|
12387
|
+
const doc = new Bytes(1024);
|
|
11681
12388
|
pushString(doc, F_DOC_RELPATH, f.rel);
|
|
12389
|
+
const ob = new Bytes(64);
|
|
11682
12390
|
for (const o of occs) {
|
|
11683
12391
|
const key = `${o.range.join(",")} ${o.roles} ${o.symbol}`;
|
|
11684
12392
|
if (seenOcc.has(key)) continue;
|
|
11685
12393
|
seenOcc.add(key);
|
|
11686
|
-
|
|
12394
|
+
ob.reset();
|
|
11687
12395
|
pushPackedInt32(ob, F_OCC_RANGE, o.range);
|
|
11688
12396
|
pushString(ob, F_OCC_SYMBOL, o.symbol);
|
|
11689
12397
|
if (o.roles !== 0) pushVarintField(ob, F_OCC_ROLES, o.roles);
|
|
11690
|
-
|
|
12398
|
+
pushMessage(doc, F_DOC_OCCURRENCES, ob);
|
|
11691
12399
|
}
|
|
12400
|
+
const sb = new Bytes(64);
|
|
11692
12401
|
for (const si of infos) {
|
|
11693
|
-
|
|
12402
|
+
sb.reset();
|
|
11694
12403
|
pushString(sb, F_SI_SYMBOL, si.symbol);
|
|
11695
12404
|
if (si.kind !== void 0) pushVarintField(sb, F_SI_KIND, si.kind);
|
|
11696
12405
|
pushString(sb, F_SI_DISPLAY_NAME, si.displayName);
|
|
11697
12406
|
if (si.enclosing) pushString(sb, F_SI_ENCLOSING, si.enclosing);
|
|
11698
|
-
|
|
12407
|
+
pushMessage(doc, F_DOC_SYMBOLS, sb);
|
|
11699
12408
|
}
|
|
11700
12409
|
pushString(doc, F_DOC_LANGUAGE, f.lang);
|
|
11701
12410
|
pushVarintField(doc, F_DOC_POSITION_ENCODING, POSITION_ENCODING_UTF16);
|
|
11702
12411
|
documents.push(doc);
|
|
11703
12412
|
}
|
|
11704
|
-
const toolInfo =
|
|
12413
|
+
const toolInfo = new Bytes();
|
|
11705
12414
|
pushString(toolInfo, F_TOOL_NAME, "codeindex");
|
|
11706
12415
|
pushString(toolInfo, F_TOOL_VERSION, toolVersion);
|
|
11707
|
-
const metadata2 =
|
|
11708
|
-
|
|
12416
|
+
const metadata2 = new Bytes();
|
|
12417
|
+
pushMessage(metadata2, F_META_TOOL_INFO, toolInfo);
|
|
11709
12418
|
pushString(metadata2, F_META_PROJECT_ROOT, projectRoot);
|
|
11710
12419
|
pushVarintField(metadata2, F_META_TEXT_ENCODING, TEXT_ENCODING_UTF8);
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
12420
|
+
let total = 0;
|
|
12421
|
+
for (const d of documents) total += d.length;
|
|
12422
|
+
const index = new Bytes(total + metadata2.length + 16);
|
|
12423
|
+
pushMessage(index, F_INDEX_METADATA, metadata2);
|
|
12424
|
+
for (const d of documents) pushMessage(index, F_INDEX_DOCUMENTS, d);
|
|
12425
|
+
return index.toUint8Array();
|
|
11715
12426
|
}
|
|
11716
12427
|
|
|
11717
12428
|
// src/engine.ts
|
|
@@ -11730,6 +12441,375 @@ init_repomap();
|
|
|
11730
12441
|
init_deadcode();
|
|
11731
12442
|
init_complexity();
|
|
11732
12443
|
init_viz();
|
|
12444
|
+
|
|
12445
|
+
// src/traverse.ts
|
|
12446
|
+
init_sort();
|
|
12447
|
+
var DEPENDS_KINDS = /* @__PURE__ */ new Set(["import", "use", "call"]);
|
|
12448
|
+
function hubThreshold(degrees) {
|
|
12449
|
+
const sorted = degrees.slice().sort((a, b) => a - b);
|
|
12450
|
+
const n = sorted.length;
|
|
12451
|
+
const p99 = n === 0 ? 0 : sorted[Math.min(n - 1, Math.floor(0.99 * n))];
|
|
12452
|
+
return Math.max(50, p99);
|
|
12453
|
+
}
|
|
12454
|
+
function reverseClosure(edges, seeds, depth = Infinity) {
|
|
12455
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
12456
|
+
for (const e of edges) {
|
|
12457
|
+
if (e.dangling || !DEPENDS_KINDS.has(e.kind)) continue;
|
|
12458
|
+
let arr = dependents.get(e.to);
|
|
12459
|
+
if (!arr) dependents.set(e.to, arr = []);
|
|
12460
|
+
arr.push(e);
|
|
12461
|
+
}
|
|
12462
|
+
const depthOf = /* @__PURE__ */ new Map();
|
|
12463
|
+
const seen = new Set(seeds);
|
|
12464
|
+
let frontier = [...seeds];
|
|
12465
|
+
for (let d = 1; d <= depth && frontier.length; d++) {
|
|
12466
|
+
const next = [];
|
|
12467
|
+
for (const node of frontier) {
|
|
12468
|
+
for (const e of (dependents.get(node) ?? []).slice().sort((a, b) => byStr(a.from, b.from))) {
|
|
12469
|
+
if (seen.has(e.from)) continue;
|
|
12470
|
+
seen.add(e.from);
|
|
12471
|
+
depthOf.set(e.from, d);
|
|
12472
|
+
next.push(e.from);
|
|
12473
|
+
}
|
|
12474
|
+
}
|
|
12475
|
+
frontier = next;
|
|
12476
|
+
}
|
|
12477
|
+
return depthOf;
|
|
12478
|
+
}
|
|
12479
|
+
function impactOf(graph, target, depth = Infinity) {
|
|
12480
|
+
const moduleOf = new Map(graph.files.map((f) => [f.rel, f.module]));
|
|
12481
|
+
const mod = graph.modules.find((m) => m.slug === target);
|
|
12482
|
+
const file = mod ? void 0 : graph.files.find((f) => f.rel === target);
|
|
12483
|
+
if (!mod && !file) return void 0;
|
|
12484
|
+
const seeds = mod ? mod.members : [file.rel];
|
|
12485
|
+
const depthOf = reverseClosure(graph.fileEdges, seeds, depth);
|
|
12486
|
+
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));
|
|
12487
|
+
const modules = [...new Set(files.map((f) => f.module).filter((m) => m !== target))].sort(byStr);
|
|
12488
|
+
return { target, scope: mod ? "module" : "file", seeds, files, modules };
|
|
12489
|
+
}
|
|
12490
|
+
function bfs(edges, start2, depth, kinds) {
|
|
12491
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
12492
|
+
const inn = /* @__PURE__ */ new Map();
|
|
12493
|
+
const degree = /* @__PURE__ */ new Map();
|
|
12494
|
+
for (const e of edges) {
|
|
12495
|
+
if (e.dangling) continue;
|
|
12496
|
+
if (kinds && !kinds.has(e.kind)) continue;
|
|
12497
|
+
(out2.get(e.from) ?? out2.set(e.from, []).get(e.from)).push(e);
|
|
12498
|
+
(inn.get(e.to) ?? inn.set(e.to, []).get(e.to)).push(e);
|
|
12499
|
+
degree.set(e.from, (degree.get(e.from) ?? 0) + 1);
|
|
12500
|
+
degree.set(e.to, (degree.get(e.to) ?? 0) + 1);
|
|
12501
|
+
}
|
|
12502
|
+
const threshold = hubThreshold([...degree.values()]);
|
|
12503
|
+
const seen = /* @__PURE__ */ new Set([start2]);
|
|
12504
|
+
const links = [];
|
|
12505
|
+
let frontier = [start2];
|
|
12506
|
+
for (let d = 1; d <= depth; d++) {
|
|
12507
|
+
const next = [];
|
|
12508
|
+
for (const node of frontier) {
|
|
12509
|
+
if (node !== start2 && (degree.get(node) ?? 0) >= threshold) continue;
|
|
12510
|
+
for (const e of (out2.get(node) ?? []).slice().sort((a, b) => byStr(a.to, b.to))) {
|
|
12511
|
+
if (seen.has(e.to)) continue;
|
|
12512
|
+
links.push({ node: e.to, direction: "out", kind: e.kind, weight: e.weight, depth: d, confidence: e.confidence });
|
|
12513
|
+
seen.add(e.to);
|
|
12514
|
+
next.push(e.to);
|
|
12515
|
+
}
|
|
12516
|
+
for (const e of (inn.get(node) ?? []).slice().sort((a, b) => byStr(a.from, b.from))) {
|
|
12517
|
+
if (seen.has(e.from)) continue;
|
|
12518
|
+
links.push({ node: e.from, direction: "in", kind: e.kind, weight: e.weight, depth: d, confidence: e.confidence });
|
|
12519
|
+
seen.add(e.from);
|
|
12520
|
+
next.push(e.from);
|
|
12521
|
+
}
|
|
12522
|
+
}
|
|
12523
|
+
frontier = next;
|
|
12524
|
+
}
|
|
12525
|
+
return links;
|
|
12526
|
+
}
|
|
12527
|
+
function neighborsOf(graph, target, depth = 1, kinds) {
|
|
12528
|
+
const mod = graph.modules.find((m) => m.slug === target);
|
|
12529
|
+
if (mod) {
|
|
12530
|
+
return { target, scope: "module", links: bfs(graph.moduleEdges, target, depth, kinds), members: mod.members };
|
|
12531
|
+
}
|
|
12532
|
+
const file = graph.files.find((f) => f.rel === target);
|
|
12533
|
+
if (file) {
|
|
12534
|
+
return { target, scope: "file", links: bfs(graph.fileEdges, target, depth, kinds) };
|
|
12535
|
+
}
|
|
12536
|
+
return void 0;
|
|
12537
|
+
}
|
|
12538
|
+
|
|
12539
|
+
// src/delta.ts
|
|
12540
|
+
init_git();
|
|
12541
|
+
init_sort();
|
|
12542
|
+
init_util();
|
|
12543
|
+
var RISK_WEIGHTS = {
|
|
12544
|
+
exportedChange: 25,
|
|
12545
|
+
// an exported symbol changed: consumers may break
|
|
12546
|
+
hubHigh: 20,
|
|
12547
|
+
// pagerank percentile ≥ .90
|
|
12548
|
+
hubMed: 10,
|
|
12549
|
+
// pagerank percentile ≥ .75
|
|
12550
|
+
blastHigh: 20,
|
|
12551
|
+
// ≥ 20 dependent files or ≥ 5 dependent modules
|
|
12552
|
+
blastMed: 10,
|
|
12553
|
+
// ≥ 5 dependent files
|
|
12554
|
+
testGap: 20,
|
|
12555
|
+
// a testable module with no covering test
|
|
12556
|
+
surprise: 10,
|
|
12557
|
+
// the module sits on a surprising cross-community edge
|
|
12558
|
+
dangling: 15
|
|
12559
|
+
// a changed file carries a dangling import
|
|
12560
|
+
};
|
|
12561
|
+
var HIGH_MIN = 60;
|
|
12562
|
+
var MEDIUM_MIN = 30;
|
|
12563
|
+
var OPEN_CAP = 3;
|
|
12564
|
+
var DEFAULT_DELTA_DEPTH = 2;
|
|
12565
|
+
function symbolsInHunks(defs, hunks) {
|
|
12566
|
+
const out2 = [];
|
|
12567
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12568
|
+
const push = (d, approx) => {
|
|
12569
|
+
const key = `${d.name}:${d.line}`;
|
|
12570
|
+
if (seen.has(key)) return;
|
|
12571
|
+
seen.add(key);
|
|
12572
|
+
out2.push({
|
|
12573
|
+
name: d.name,
|
|
12574
|
+
kind: d.kind,
|
|
12575
|
+
exported: d.exported,
|
|
12576
|
+
line: d.line,
|
|
12577
|
+
...d.endLine !== void 0 ? { endLine: d.endLine } : {},
|
|
12578
|
+
...d.parent !== void 0 ? { parent: d.parent } : {},
|
|
12579
|
+
...approx ? { approx: true } : {}
|
|
12580
|
+
});
|
|
12581
|
+
};
|
|
12582
|
+
const span = (d) => (d.endLine ?? d.line) - d.line;
|
|
12583
|
+
for (const h of hunks) {
|
|
12584
|
+
const enclosing = defs.filter((d) => d.line <= h.end && (d.endLine ?? d.line) >= h.start);
|
|
12585
|
+
if (enclosing.length) {
|
|
12586
|
+
enclosing.sort((a, b) => span(a) - span(b) || b.line - a.line || byStr(a.name, b.name));
|
|
12587
|
+
for (const d of enclosing) push(d, false);
|
|
12588
|
+
} else {
|
|
12589
|
+
const above = defs.filter((d) => d.line <= h.start && d.endLine === void 0);
|
|
12590
|
+
const near = above[above.length - 1];
|
|
12591
|
+
if (near) push(near, true);
|
|
12592
|
+
}
|
|
12593
|
+
}
|
|
12594
|
+
return out2;
|
|
12595
|
+
}
|
|
12596
|
+
function percentile(values, mine) {
|
|
12597
|
+
if (values.length <= 1) return 0;
|
|
12598
|
+
let smaller = 0;
|
|
12599
|
+
for (const v of values) if (v < mine) smaller++;
|
|
12600
|
+
return smaller / (values.length - 1);
|
|
12601
|
+
}
|
|
12602
|
+
function computeDelta(graph, symbols, diff, depth = DEFAULT_DELTA_DEPTH) {
|
|
12603
|
+
const notes = [...diff.notes ?? []];
|
|
12604
|
+
if (!symbols) notes.push("symbol index missing \u2014 symbol-level attribution disabled");
|
|
12605
|
+
const fileByRel = new Map(graph.files.map((f) => [f.rel, f]));
|
|
12606
|
+
const defsByFile = /* @__PURE__ */ new Map();
|
|
12607
|
+
if (symbols) {
|
|
12608
|
+
for (const [name2, entries] of Object.entries(symbols.defs)) {
|
|
12609
|
+
for (const d of entries) {
|
|
12610
|
+
let arr = defsByFile.get(d.file);
|
|
12611
|
+
if (!arr) defsByFile.set(d.file, arr = []);
|
|
12612
|
+
arr.push({ name: name2, ...d });
|
|
12613
|
+
}
|
|
12614
|
+
}
|
|
12615
|
+
for (const arr of defsByFile.values()) arr.sort((a, b) => a.line - b.line || byStr(a.name, b.name));
|
|
12616
|
+
}
|
|
12617
|
+
const deleted = [];
|
|
12618
|
+
const unindexed = [];
|
|
12619
|
+
const changes = [];
|
|
12620
|
+
for (const df of [...diff.files].sort((a, b) => byStr(a.path, b.path))) {
|
|
12621
|
+
const carry = {
|
|
12622
|
+
...df.oldPath !== void 0 ? { oldPath: df.oldPath } : {},
|
|
12623
|
+
...df.binary ? { binary: true } : {},
|
|
12624
|
+
...df.linesAdded !== void 0 ? { linesAdded: df.linesAdded } : {},
|
|
12625
|
+
...df.linesDeleted !== void 0 ? { linesDeleted: df.linesDeleted } : {}
|
|
12626
|
+
};
|
|
12627
|
+
if (df.status === "deleted") {
|
|
12628
|
+
deleted.push(df.path);
|
|
12629
|
+
changes.push({ path: df.path, status: df.status, ...carry, hunks: [], symbols: [] });
|
|
12630
|
+
continue;
|
|
12631
|
+
}
|
|
12632
|
+
const node = fileByRel.get(df.path);
|
|
12633
|
+
if (!node) {
|
|
12634
|
+
unindexed.push(df.path);
|
|
12635
|
+
continue;
|
|
12636
|
+
}
|
|
12637
|
+
let hunks = diff.hunks.get(df.path) ?? [];
|
|
12638
|
+
if (!hunks.length && df.status === "added" && !df.binary) hunks = [{ start: 1, end: Math.max(node.lines, 1) }];
|
|
12639
|
+
const syms = df.binary ? [] : symbolsInHunks(defsByFile.get(df.path) ?? [], hunks);
|
|
12640
|
+
changes.push({
|
|
12641
|
+
path: df.path,
|
|
12642
|
+
status: df.status,
|
|
12643
|
+
...carry,
|
|
12644
|
+
module: node.module,
|
|
12645
|
+
hunks: hunks.map((h) => ({ start: h.start, end: h.end })),
|
|
12646
|
+
symbols: syms
|
|
12647
|
+
});
|
|
12648
|
+
}
|
|
12649
|
+
const changedRels = new Set(changes.filter((c2) => c2.status !== "deleted").map((c2) => c2.path));
|
|
12650
|
+
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));
|
|
12651
|
+
const byModule = /* @__PURE__ */ new Map();
|
|
12652
|
+
for (const c2 of changes) {
|
|
12653
|
+
if (c2.status === "deleted" || !c2.module) continue;
|
|
12654
|
+
let arr = byModule.get(c2.module);
|
|
12655
|
+
if (!arr) byModule.set(c2.module, arr = []);
|
|
12656
|
+
arr.push(c2);
|
|
12657
|
+
}
|
|
12658
|
+
const nonTestCode = /* @__PURE__ */ new Set();
|
|
12659
|
+
for (const f of graph.files) {
|
|
12660
|
+
if (f.fileKind === "code" && !f.testFile) nonTestCode.add(f.module);
|
|
12661
|
+
}
|
|
12662
|
+
const pagerankKnown = graph.modules.some((m) => m.pagerank !== void 0);
|
|
12663
|
+
const metricOf = (m) => pagerankKnown ? m.pagerank ?? 0 : m.degIn + m.degOut;
|
|
12664
|
+
const metricValues = graph.modules.map(metricOf);
|
|
12665
|
+
const metricName = pagerankKnown ? "pagerank" : "degree";
|
|
12666
|
+
const modules = [];
|
|
12667
|
+
for (const slug of [...byModule.keys()].sort(byStr)) {
|
|
12668
|
+
const m = graph.modules.find((x) => x.slug === slug);
|
|
12669
|
+
if (!m) continue;
|
|
12670
|
+
const moduleChanges = byModule.get(slug);
|
|
12671
|
+
const reasons = [];
|
|
12672
|
+
let score = 0;
|
|
12673
|
+
const exportedNames = [...new Set(moduleChanges.flatMap((c2) => c2.symbols.filter((s) => s.exported).map((s) => s.name)))].sort(byStr);
|
|
12674
|
+
if (exportedNames.length) {
|
|
12675
|
+
score += RISK_WEIGHTS.exportedChange;
|
|
12676
|
+
const shown = exportedNames.slice(0, 3).join(", ") + (exportedNames.length > 3 ? ", \u2026" : "");
|
|
12677
|
+
reasons.push(exportedNames.length === 1 ? `exported symbol ${shown} changed` : `exported symbols ${shown} changed`);
|
|
12678
|
+
}
|
|
12679
|
+
const pct = percentile(metricValues, metricOf(m));
|
|
12680
|
+
if (pct >= 0.9) {
|
|
12681
|
+
score += RISK_WEIGHTS.hubHigh;
|
|
12682
|
+
reasons.push(`${metricName} p${Math.round(pct * 100)} hub`);
|
|
12683
|
+
} else if (pct >= 0.75) {
|
|
12684
|
+
score += RISK_WEIGHTS.hubMed;
|
|
12685
|
+
reasons.push(`${metricName} p${Math.round(pct * 100)} hub`);
|
|
12686
|
+
}
|
|
12687
|
+
const depthByRel = /* @__PURE__ */ new Map();
|
|
12688
|
+
const impModules = /* @__PURE__ */ new Set();
|
|
12689
|
+
for (const c2 of moduleChanges) {
|
|
12690
|
+
const imp = impactOf(graph, c2.path, depth);
|
|
12691
|
+
if (!imp) continue;
|
|
12692
|
+
for (const f of imp.files) {
|
|
12693
|
+
const prev = depthByRel.get(f.rel);
|
|
12694
|
+
if (prev === void 0 || f.depth < prev) depthByRel.set(f.rel, f.depth);
|
|
12695
|
+
}
|
|
12696
|
+
for (const im of imp.modules) if (im !== slug) impModules.add(im);
|
|
12697
|
+
}
|
|
12698
|
+
const transitiveFiles = depthByRel.size;
|
|
12699
|
+
const directFiles = [...depthByRel.values()].filter((d) => d === 1).length;
|
|
12700
|
+
const impact = { directFiles, transitiveFiles, modules: [...impModules].sort(byStr) };
|
|
12701
|
+
if (transitiveFiles >= 20 || impact.modules.length >= 5) {
|
|
12702
|
+
score += RISK_WEIGHTS.blastHigh;
|
|
12703
|
+
reasons.push(`${transitiveFiles} dependent files across ${impact.modules.length} modules (depth ${depth})`);
|
|
12704
|
+
} else if (transitiveFiles >= 5) {
|
|
12705
|
+
score += RISK_WEIGHTS.blastMed;
|
|
12706
|
+
reasons.push(`${transitiveFiles} dependent files across ${impact.modules.length} modules (depth ${depth})`);
|
|
12707
|
+
}
|
|
12708
|
+
const testable = m.tier <= 1 && m.symbols > 0 && nonTestCode.has(slug);
|
|
12709
|
+
const coveredBy = m.testedBy ?? [];
|
|
12710
|
+
const tests = testable ? coveredBy.length ? { status: "covered", files: coveredBy } : { status: "gap", files: [] } : { status: "n/a", files: [] };
|
|
12711
|
+
if (tests.status === "gap") {
|
|
12712
|
+
score += RISK_WEIGHTS.testGap;
|
|
12713
|
+
reasons.push("no test covers this module");
|
|
12714
|
+
}
|
|
12715
|
+
const sup = (graph.surprises ?? []).find((s) => s.from === slug || s.to === slug);
|
|
12716
|
+
if (sup) {
|
|
12717
|
+
score += RISK_WEIGHTS.surprise;
|
|
12718
|
+
reasons.push(`cross-community edge to ${sup.from === slug ? sup.to : sup.from} (surprising)`);
|
|
12719
|
+
}
|
|
12720
|
+
const moduleDangling = dangling.filter((d) => moduleChanges.some((c2) => c2.path === d.from));
|
|
12721
|
+
if (moduleDangling.length) {
|
|
12722
|
+
score += RISK_WEIGHTS.dangling;
|
|
12723
|
+
const first = moduleDangling[0];
|
|
12724
|
+
const more = moduleDangling.length > 1 ? ` (+${moduleDangling.length - 1} more)` : "";
|
|
12725
|
+
reasons.push(`dangling import "${first.spec}" in ${first.from}${more}`);
|
|
12726
|
+
}
|
|
12727
|
+
score = Math.min(100, score);
|
|
12728
|
+
const changedFiles = moduleChanges.map((c2) => c2.path).sort(byStr);
|
|
12729
|
+
const allSyms = moduleChanges.flatMap((c2) => c2.symbols);
|
|
12730
|
+
const open = moduleChanges.slice().sort(
|
|
12731
|
+
(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)
|
|
12732
|
+
).slice(0, OPEN_CAP).map((c2) => c2.path);
|
|
12733
|
+
modules.push({
|
|
12734
|
+
slug,
|
|
12735
|
+
path: m.path,
|
|
12736
|
+
score,
|
|
12737
|
+
bucket: score >= HIGH_MIN ? "HIGH" : score >= MEDIUM_MIN ? "MEDIUM" : "LOW",
|
|
12738
|
+
reasons,
|
|
12739
|
+
changedFiles,
|
|
12740
|
+
changedSymbols: {
|
|
12741
|
+
total: new Set(allSyms.map((s) => `${s.name}:${s.line}`)).size,
|
|
12742
|
+
exported: new Set(allSyms.filter((s) => s.exported).map((s) => `${s.name}:${s.line}`)).size
|
|
12743
|
+
},
|
|
12744
|
+
impact,
|
|
12745
|
+
tests,
|
|
12746
|
+
open
|
|
12747
|
+
});
|
|
12748
|
+
}
|
|
12749
|
+
modules.sort((a, b) => b.score - a.score || byStr(a.slug, b.slug));
|
|
12750
|
+
return {
|
|
12751
|
+
base: diff.base,
|
|
12752
|
+
...graph.commit !== void 0 ? { indexCommit: graph.commit } : {},
|
|
12753
|
+
depth,
|
|
12754
|
+
changes,
|
|
12755
|
+
modules,
|
|
12756
|
+
dangling,
|
|
12757
|
+
deleted: deleted.sort(byStr),
|
|
12758
|
+
unindexed: unindexed.sort(byStr),
|
|
12759
|
+
notes
|
|
12760
|
+
};
|
|
12761
|
+
}
|
|
12762
|
+
function deltaFor(repo, graph, symbols, opts = {}) {
|
|
12763
|
+
if (!have("git")) return { error: "git is required for delta and was not found on PATH" };
|
|
12764
|
+
if (!isGitWorktree(repo)) return { error: `delta needs a git worktree \u2014 ${repo} is not inside one` };
|
|
12765
|
+
const notes = [];
|
|
12766
|
+
let base;
|
|
12767
|
+
if (opts.staged) {
|
|
12768
|
+
const head = sh("git", ["-C", repo, "rev-parse", "HEAD"]);
|
|
12769
|
+
if (!head.ok) return { error: "cannot resolve HEAD \u2014 empty repository?" };
|
|
12770
|
+
base = { ref: "HEAD", mergeBase: head.stdout.trim(), staged: true };
|
|
12771
|
+
} else {
|
|
12772
|
+
const r = resolveBaseRef(repo, opts.base);
|
|
12773
|
+
if ("error" in r) return { error: r.error };
|
|
12774
|
+
if (r.note) notes.push(r.note);
|
|
12775
|
+
base = { ref: r.ref, mergeBase: r.mergeBase, staged: false };
|
|
12776
|
+
}
|
|
12777
|
+
const spec = opts.staged ? { staged: true } : { mergeBase: base.mergeBase };
|
|
12778
|
+
const files = diffFiles(repo, spec);
|
|
12779
|
+
if (!opts.staged) {
|
|
12780
|
+
const known = new Set(files.map((f) => f.path));
|
|
12781
|
+
for (const u of untrackedFiles(repo)) {
|
|
12782
|
+
if (!known.has(u)) files.push({ path: u, status: "added" });
|
|
12783
|
+
}
|
|
12784
|
+
}
|
|
12785
|
+
return computeDelta(graph, symbols, { files, hunks: diffHunks(repo, spec), base, notes }, opts.depth ?? DEFAULT_DELTA_DEPTH);
|
|
12786
|
+
}
|
|
12787
|
+
function formatDeltaPanel(res) {
|
|
12788
|
+
const mb = res.base.mergeBase.slice(0, 7);
|
|
12789
|
+
const vs = `${res.base.staged ? "staged vs " : ""}${res.base.ref}`;
|
|
12790
|
+
if (!res.changes.length && !res.unindexed.length) {
|
|
12791
|
+
return `codeindex: no changes vs ${vs} (merge-base ${mb})
|
|
12792
|
+
`;
|
|
12793
|
+
}
|
|
12794
|
+
const changedCount = res.changes.length + res.unindexed.length;
|
|
12795
|
+
const lines = [
|
|
12796
|
+
`codeindex: delta vs ${vs} (merge-base ${mb}) \u2014 ${changedCount} changed file(s), ${res.modules.length} module(s)${res.indexCommit ? `, index @ ${res.indexCommit}` : ""}`
|
|
12797
|
+
];
|
|
12798
|
+
for (const n of res.notes) lines.push(` note: ${n}`);
|
|
12799
|
+
for (const m of res.modules) {
|
|
12800
|
+
lines.push(` ${m.bucket.padEnd(6)} ${m.slug} score ${m.score}${m.reasons.length ? ` \u2014 ${m.reasons.join("; ")}` : ""}`);
|
|
12801
|
+
const tests = m.tests.status === "gap" ? "GAP" : m.tests.status === "covered" ? `covered (${m.tests.files.length})` : "n/a";
|
|
12802
|
+
lines.push(` open: ${m.open.join(", ") || "\u2014"} \xB7 tests: ${tests}`);
|
|
12803
|
+
}
|
|
12804
|
+
if (res.dangling.length) {
|
|
12805
|
+
lines.push(` dangling: ${res.dangling.map((d) => `${d.spec} (from ${d.from})`).join(" \xB7 ")}`);
|
|
12806
|
+
}
|
|
12807
|
+
if (res.deleted.length) lines.push(` deleted: ${res.deleted.join(", ")}`);
|
|
12808
|
+
if (res.unindexed.length) lines.push(` unindexed: ${res.unindexed.join(", ")}`);
|
|
12809
|
+
return lines.join("\n") + "\n";
|
|
12810
|
+
}
|
|
12811
|
+
|
|
12812
|
+
// src/engine.ts
|
|
11733
12813
|
init_mcp();
|
|
11734
12814
|
init_rewrite();
|
|
11735
12815
|
init_hash();
|
|
@@ -11740,13 +12820,14 @@ init_util();
|
|
|
11740
12820
|
init_types();
|
|
11741
12821
|
init_types();
|
|
11742
12822
|
init_loader();
|
|
11743
|
-
import { existsSync as
|
|
11744
|
-
import { join as
|
|
12823
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
12824
|
+
import { join as join19, resolve as resolve2 } from "path";
|
|
11745
12825
|
init_pipeline();
|
|
11746
12826
|
init_hash();
|
|
11747
12827
|
init_graph_json();
|
|
11748
12828
|
init_symbols_json();
|
|
11749
12829
|
init_scan();
|
|
12830
|
+
init_preload();
|
|
11750
12831
|
init_walk();
|
|
11751
12832
|
init_callers();
|
|
11752
12833
|
init_workspaces();
|
|
@@ -11814,6 +12895,13 @@ Commands:
|
|
|
11814
12895
|
complexity Cyclomatic-complexity estimates, most-complex first. Pass a file
|
|
11815
12896
|
positional for one file; omit for the repo-wide top
|
|
11816
12897
|
risk Complexity \xD7 git-churn ranking (JSON; --since <ref> to bound)
|
|
12898
|
+
delta Review panel for the git diff: changed files -> enclosing symbols ->
|
|
12899
|
+
blast radius -> risk score with explained reasons
|
|
12900
|
+
(--base <ref> | --staged, --depth <n>, --json)
|
|
12901
|
+
impact Reverse dependency closure of a file or module: everything that
|
|
12902
|
+
transitively imports/uses/calls it (--depth <n>; JSON)
|
|
12903
|
+
neighbors Graph neighbours of a file or module, both directions
|
|
12904
|
+
(--depth <n>, --kind import,call,use,doc-link,mention; JSON)
|
|
11817
12905
|
mermaid Mermaid diagram of the module graph; pass a module positional to
|
|
11818
12906
|
focus on one neighborhood
|
|
11819
12907
|
rewrite Map an expensive tree-wide search onto its indexed equivalent:
|
|
@@ -11828,7 +12916,10 @@ Commands:
|
|
|
11828
12916
|
check_rules, the memory quartet and the three symbolic-edit
|
|
11829
12917
|
writes). Flags: --repo <dir> pins ONE repository so the per-tool
|
|
11830
12918
|
repo argument becomes optional (an explicit per-call repo still
|
|
11831
|
-
wins); --server-name <name> overrides the announced serverInfo
|
|
12919
|
+
wins); --server-name <name> overrides the announced serverInfo;
|
|
12920
|
+
--max-response-bytes <n> caps a single tool response (default 1e6;
|
|
12921
|
+
a response under the cap is byte-identical, one over it is
|
|
12922
|
+
replaced by an actionable notice instead of an unusable blob)
|
|
11832
12923
|
version Print the engine version
|
|
11833
12924
|
|
|
11834
12925
|
Flags (accepted before OR after the subcommand: '--repo X scan' and
|
|
@@ -11848,6 +12939,16 @@ Flags (accepted before OR after the subcommand: '--repo X scan' and
|
|
|
11848
12939
|
--max-bytes <n> Skip files above this size (default 1 MiB)
|
|
11849
12940
|
--max-calls <n> Per-file call-site cap for extraction (default 512)
|
|
11850
12941
|
--no-ast Skip tree-sitter grammars even when present (regex tier)
|
|
12942
|
+
--workers <n> \`index\`: extraction worker threads (default: cores-1,
|
|
12943
|
+
capped at 8; 0 or 1 forces the single-threaded path).
|
|
12944
|
+
Also settable with CODEINDEX_WORKERS. Artifacts are
|
|
12945
|
+
byte-identical either way
|
|
12946
|
+
--index <dir> Persisted index the READ commands reuse, relative to the
|
|
12947
|
+
repo (default .codeindex \u2014 i.e. what \`index --out\` wrote
|
|
12948
|
+
there). A fresh index turns the scan into a stat pass and,
|
|
12949
|
+
when it still matches the worktree, skips the pipeline
|
|
12950
|
+
entirely. Stale/absent/corrupt \u2192 a normal cold build
|
|
12951
|
+
--no-index-cache Never reuse a persisted index; always build from scratch
|
|
11851
12952
|
--config <file> Rules config for \`rules\` (JSON: [{name, from, to, \u2026}])
|
|
11852
12953
|
--limit <n> Max results for \`search\` (default 20)
|
|
11853
12954
|
--no-fuzzy \`search\`: disable trigram fuzzy fallback for query terms
|
|
@@ -11871,7 +12972,7 @@ function parseFlags(args2) {
|
|
|
11871
12972
|
if (v === void 0) throw new Error(`missing value for ${a}`);
|
|
11872
12973
|
return v;
|
|
11873
12974
|
};
|
|
11874
|
-
const
|
|
12975
|
+
const num2 = () => {
|
|
11875
12976
|
const raw = next();
|
|
11876
12977
|
const n = Number(raw);
|
|
11877
12978
|
if (!Number.isFinite(n) || n <= 0) throw new Error(`${a} expects a positive number, got "${raw}"`);
|
|
@@ -11887,20 +12988,32 @@ function parseFlags(args2) {
|
|
|
11887
12988
|
else if (a === "--scope") flags2.scope = next();
|
|
11888
12989
|
else if (a === "--no-gitignore") flags2.gitignore = false;
|
|
11889
12990
|
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 =
|
|
12991
|
+
else if (a === "--max-files") flags2.maxFiles = num2();
|
|
12992
|
+
else if (a === "--max-bytes") flags2.maxBytes = num2();
|
|
12993
|
+
else if (a === "--max-calls") flags2.maxCalls = num2();
|
|
11893
12994
|
else if (a === "--ignore-case") flags2.ignoreCase = true;
|
|
11894
|
-
else if (a === "--max-hits") flags2.maxHits =
|
|
11895
|
-
else if (a === "--budget-tokens") flags2.budgetTokens =
|
|
12995
|
+
else if (a === "--max-hits") flags2.maxHits = num2();
|
|
12996
|
+
else if (a === "--budget-tokens") flags2.budgetTokens = num2();
|
|
11896
12997
|
else if (a === "--no-ast") flags2.noAst = true;
|
|
11897
|
-
else if (a === "--
|
|
12998
|
+
else if (a === "--index") flags2.indexDir = next();
|
|
12999
|
+
else if (a === "--no-index-cache") flags2.noIndexCache = true;
|
|
13000
|
+
else if (a === "--workers") {
|
|
13001
|
+
const raw = next();
|
|
13002
|
+
const n = Number(raw);
|
|
13003
|
+
if (!Number.isInteger(n) || n < 0) throw new Error(`--workers expects a non-negative integer, got "${raw}"`);
|
|
13004
|
+
flags2.workers = n;
|
|
13005
|
+
} else if (a === "--since") flags2.since = next();
|
|
11898
13006
|
else if (a === "--config") flags2.config = resolve2(next());
|
|
11899
|
-
else if (a === "--limit") flags2.limit =
|
|
13007
|
+
else if (a === "--limit") flags2.limit = num2();
|
|
11900
13008
|
else if (a === "--no-fuzzy") flags2.fuzzy = false;
|
|
11901
13009
|
else if (a === "--semantic") flags2.semantic = true;
|
|
11902
13010
|
else if (a === "--recall") flags2.recall = true;
|
|
11903
13011
|
else if (a === "--run") flags2.run = true;
|
|
13012
|
+
else if (a === "--base") flags2.base = next();
|
|
13013
|
+
else if (a === "--staged") flags2.staged = true;
|
|
13014
|
+
else if (a === "--depth") flags2.depth = num2();
|
|
13015
|
+
else if (a === "--kind") flags2.kind = next();
|
|
13016
|
+
else if (a === "--json") flags2.json = true;
|
|
11904
13017
|
else if (!a.startsWith("--") && flags2.positional === void 0) flags2.positional = a;
|
|
11905
13018
|
else throw new Error(`unknown flag: ${a}`);
|
|
11906
13019
|
}
|
|
@@ -11930,6 +13043,7 @@ var SCANLESS_COMMANDS = /* @__PURE__ */ new Set(["grep", "churn", "coupling", "w
|
|
|
11930
13043
|
function parseMcpFlags(argv) {
|
|
11931
13044
|
let defaultRepo;
|
|
11932
13045
|
let name2;
|
|
13046
|
+
let maxResponseBytes;
|
|
11933
13047
|
for (let i2 = 0; i2 < argv.length; i2++) {
|
|
11934
13048
|
const a = argv[i2];
|
|
11935
13049
|
if (a === "--repo") {
|
|
@@ -11940,12 +13054,17 @@ function parseMcpFlags(argv) {
|
|
|
11940
13054
|
const v = argv[++i2];
|
|
11941
13055
|
if (!v) throw new Error("--server-name requires a value");
|
|
11942
13056
|
name2 = v;
|
|
13057
|
+
} else if (a === "--max-response-bytes") {
|
|
13058
|
+
const v = argv[++i2];
|
|
13059
|
+
const n = Number(v);
|
|
13060
|
+
if (!v || !Number.isFinite(n) || n <= 0) throw new Error("--max-response-bytes requires a positive number");
|
|
13061
|
+
maxResponseBytes = n;
|
|
11943
13062
|
} else {
|
|
11944
13063
|
throw new Error(`unknown flag for \`mcp\`: ${a}`);
|
|
11945
13064
|
}
|
|
11946
13065
|
}
|
|
11947
|
-
if (defaultRepo && !
|
|
11948
|
-
return { defaultRepo, serverInfo: name2 ? { name: name2 } : void 0 };
|
|
13066
|
+
if (defaultRepo && !existsSync7(defaultRepo)) throw new Error(`--repo path does not exist: ${defaultRepo}`);
|
|
13067
|
+
return { defaultRepo, serverInfo: name2 ? { name: name2 } : void 0, maxResponseBytes };
|
|
11949
13068
|
}
|
|
11950
13069
|
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
11951
13070
|
"--repo",
|
|
@@ -11963,7 +13082,10 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
11963
13082
|
"--since",
|
|
11964
13083
|
"--config",
|
|
11965
13084
|
"--limit",
|
|
11966
|
-
"--server-name"
|
|
13085
|
+
"--server-name",
|
|
13086
|
+
"--workers",
|
|
13087
|
+
"--index",
|
|
13088
|
+
"--max-response-bytes"
|
|
11967
13089
|
]);
|
|
11968
13090
|
function hoistLeadingFlags(argv) {
|
|
11969
13091
|
const lead = [];
|
|
@@ -12008,7 +13130,7 @@ async function runCli(rawArgv) {
|
|
|
12008
13130
|
return;
|
|
12009
13131
|
}
|
|
12010
13132
|
const flags2 = parseFlags(rest);
|
|
12011
|
-
if (!
|
|
13133
|
+
if (!existsSync7(flags2.repo)) throw new Error(`--repo path does not exist: ${flags2.repo}`);
|
|
12012
13134
|
const scans = !SCANLESS_COMMANDS.has(cmd) && !(cmd === "embed" && flags2.positional !== "build");
|
|
12013
13135
|
let precomputedWalk;
|
|
12014
13136
|
if (scans && !flags2.noAst) {
|
|
@@ -12020,15 +13142,33 @@ async function runCli(rawArgv) {
|
|
|
12020
13142
|
});
|
|
12021
13143
|
await ensureGrammars(grammarKeysForExts(precomputedWalk.files.map((f) => f.ext)));
|
|
12022
13144
|
}
|
|
13145
|
+
const indexDir = flags2.indexDir ?? INDEX_DIR;
|
|
13146
|
+
let preloadTried = false;
|
|
13147
|
+
let preloaded;
|
|
13148
|
+
const tryPreload = () => {
|
|
13149
|
+
if (preloadTried) return preloaded;
|
|
13150
|
+
preloadTried = true;
|
|
13151
|
+
if (flags2.noIndexCache) return void 0;
|
|
13152
|
+
const p = preloadSession(flags2.repo, scanOptions(flags2, precomputedWalk), indexDir);
|
|
13153
|
+
if (p) preloaded = { scan: p.scan, arts: p.arts };
|
|
13154
|
+
return preloaded;
|
|
13155
|
+
};
|
|
13156
|
+
const readScan = () => tryPreload()?.scan ?? scanRepo(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
13157
|
+
const readArtifacts = () => {
|
|
13158
|
+
const p = tryPreload();
|
|
13159
|
+
if (p?.arts) return p.arts;
|
|
13160
|
+
if (p) return buildArtifactsFromScan(p.scan, scanOptions(flags2, precomputedWalk));
|
|
13161
|
+
return buildIndexArtifacts(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
13162
|
+
};
|
|
12023
13163
|
if (cmd === "index") {
|
|
12024
13164
|
if (!flags2.out) throw new Error("index needs --out <dir>");
|
|
12025
13165
|
const outDir = flags2.out;
|
|
12026
13166
|
mkdirSync3(outDir, { recursive: true });
|
|
12027
|
-
const cachePath =
|
|
13167
|
+
const cachePath = join19(outDir, "cache.json");
|
|
12028
13168
|
let cache;
|
|
12029
13169
|
let meta = {};
|
|
12030
13170
|
try {
|
|
12031
|
-
const parsed = JSON.parse(
|
|
13171
|
+
const parsed = JSON.parse(readFileSync9(cachePath, "utf8"));
|
|
12032
13172
|
if (parsed.schemaVersion === SCHEMA_VERSION && parsed.extractorVersion === EXTRACTOR_VERSION) {
|
|
12033
13173
|
cache = new Map(Object.entries(parsed.files));
|
|
12034
13174
|
meta = {
|
|
@@ -12041,15 +13181,20 @@ async function runCli(rawArgv) {
|
|
|
12041
13181
|
}
|
|
12042
13182
|
} catch {
|
|
12043
13183
|
}
|
|
12044
|
-
const scan2 =
|
|
13184
|
+
const scan2 = await scanRepoParallel(flags2.repo, {
|
|
13185
|
+
...scanOptions(flags2, precomputedWalk),
|
|
13186
|
+
cache,
|
|
13187
|
+
out: outDir,
|
|
13188
|
+
workers: flags2.workers
|
|
13189
|
+
});
|
|
12045
13190
|
const modelDir = resolveEmbedModelDir(flags2.repo);
|
|
12046
13191
|
const model = modelDir ? loadEmbedModel(modelDir) : void 0;
|
|
12047
|
-
const graphPath =
|
|
12048
|
-
const symbolsPath =
|
|
12049
|
-
const embedPath =
|
|
13192
|
+
const graphPath = join19(outDir, "graph.json");
|
|
13193
|
+
const symbolsPath = join19(outDir, "symbols.json");
|
|
13194
|
+
const embedPath = join19(outDir, "embeddings.bin");
|
|
12050
13195
|
const artifactSha = (path) => {
|
|
12051
13196
|
try {
|
|
12052
|
-
return sha1(
|
|
13197
|
+
return sha1(readFileSync9(path));
|
|
12053
13198
|
} catch {
|
|
12054
13199
|
return void 0;
|
|
12055
13200
|
}
|
|
@@ -12104,23 +13249,23 @@ async function runCli(rawArgv) {
|
|
|
12104
13249
|
`);
|
|
12105
13250
|
}
|
|
12106
13251
|
} else if (cmd === "scan") {
|
|
12107
|
-
const
|
|
13252
|
+
const s = scanSummary(flags2.repo, scanOptions(flags2, precomputedWalk));
|
|
12108
13253
|
const summary = {
|
|
12109
13254
|
engineVersion: ENGINE_VERSION,
|
|
12110
|
-
commit:
|
|
12111
|
-
fileCount:
|
|
12112
|
-
languages:
|
|
12113
|
-
capped:
|
|
13255
|
+
commit: s.commit,
|
|
13256
|
+
fileCount: s.fileCount,
|
|
13257
|
+
languages: s.languages,
|
|
13258
|
+
capped: s.capped
|
|
12114
13259
|
};
|
|
12115
13260
|
emit(JSON.stringify(summary, null, 2) + "\n", flags2.out);
|
|
12116
13261
|
} else if (cmd === "graph") {
|
|
12117
|
-
const { graph } =
|
|
13262
|
+
const { graph } = readArtifacts();
|
|
12118
13263
|
emit(renderGraphJson(graph), flags2.out);
|
|
12119
13264
|
} else if (cmd === "symbols") {
|
|
12120
|
-
const { symbols } =
|
|
13265
|
+
const { symbols } = readArtifacts();
|
|
12121
13266
|
emit(renderSymbolsJson(symbols), flags2.out);
|
|
12122
13267
|
} else if (cmd === "scip") {
|
|
12123
|
-
const scan2 =
|
|
13268
|
+
const scan2 = readScan();
|
|
12124
13269
|
const bytes = renderScip(scan2, { projectRoot: flags2.projectRoot });
|
|
12125
13270
|
const out2 = flags2.out ?? resolve2("index.scip");
|
|
12126
13271
|
if (out2 === "-") process.stdout.write(Buffer.from(bytes));
|
|
@@ -12130,14 +13275,14 @@ async function runCli(rawArgv) {
|
|
|
12130
13275
|
`);
|
|
12131
13276
|
}
|
|
12132
13277
|
} else if (cmd === "callers") {
|
|
12133
|
-
const scan2 =
|
|
13278
|
+
const scan2 = readScan();
|
|
12134
13279
|
const index = buildCallerIndex(scan2, void 0, { recall: flags2.recall });
|
|
12135
13280
|
const obj = {};
|
|
12136
13281
|
for (const [name2, entry] of index) obj[name2] = entry;
|
|
12137
13282
|
emit(JSON.stringify(obj, null, 2) + "\n", flags2.out);
|
|
12138
13283
|
} else if (cmd === "search") {
|
|
12139
13284
|
if (!flags2.positional) throw new Error('search needs a query: cli.mjs search "<query>" --repo <dir>');
|
|
12140
|
-
const scan2 =
|
|
13285
|
+
const scan2 = readScan();
|
|
12141
13286
|
if (flags2.semantic) {
|
|
12142
13287
|
const endpoint = resolveEmbedEndpoint();
|
|
12143
13288
|
const lexical = () => {
|
|
@@ -12228,16 +13373,16 @@ async function runCli(rawArgv) {
|
|
|
12228
13373
|
}
|
|
12229
13374
|
const model = loadEmbedModel(modelDir);
|
|
12230
13375
|
mkdirSync3(flags2.out, { recursive: true });
|
|
12231
|
-
const scan2 =
|
|
13376
|
+
const scan2 = readScan();
|
|
12232
13377
|
const index = buildEmbeddingIndex(scan2, model);
|
|
12233
|
-
writeFileSync4(
|
|
13378
|
+
writeFileSync4(join19(flags2.out, "embeddings.bin"), serializeEmbeddings(index));
|
|
12234
13379
|
process.stderr.write(`codeindex: ${index.records.length} embedding records \u2192 ${flags2.out}/embeddings.bin (model ${model.modelId})
|
|
12235
13380
|
`);
|
|
12236
13381
|
} else if (sub === "pull") {
|
|
12237
13382
|
const { url, sha256 } = resolveEmbedPullUrl();
|
|
12238
|
-
const destDir = process.env.CODEINDEX_EMBED_DIR ??
|
|
13383
|
+
const destDir = process.env.CODEINDEX_EMBED_DIR ?? join19(flags2.repo, ".codeindex", "models");
|
|
12239
13384
|
mkdirSync3(destDir, { recursive: true });
|
|
12240
|
-
process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${
|
|
13385
|
+
process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${join19(destDir, "model.json")}
|
|
12241
13386
|
`);
|
|
12242
13387
|
let body2;
|
|
12243
13388
|
try {
|
|
@@ -12258,8 +13403,8 @@ async function runCli(rawArgv) {
|
|
|
12258
13403
|
process.exitCode = 1;
|
|
12259
13404
|
return;
|
|
12260
13405
|
}
|
|
12261
|
-
writeFileSync4(
|
|
12262
|
-
process.stderr.write(`codeindex: model written to ${
|
|
13406
|
+
writeFileSync4(join19(destDir, "model.json"), body2);
|
|
13407
|
+
process.stderr.write(`codeindex: model written to ${join19(destDir, "model.json")}
|
|
12263
13408
|
`);
|
|
12264
13409
|
} else {
|
|
12265
13410
|
throw new Error("embed needs a subcommand: status | build | pull | serve");
|
|
@@ -12269,7 +13414,7 @@ async function runCli(rawArgv) {
|
|
|
12269
13414
|
const cacheDir = sharedGrammarsCacheDir();
|
|
12270
13415
|
if (sub === "status") {
|
|
12271
13416
|
const info2 = resolveGrammarsTier();
|
|
12272
|
-
const runtimePresent = info2.dir ?
|
|
13417
|
+
const runtimePresent = info2.dir ? existsSync7(join19(info2.dir, "web-tree-sitter.wasm")) : false;
|
|
12273
13418
|
const target = resolveGrammarsPullTarget();
|
|
12274
13419
|
const status = {
|
|
12275
13420
|
engineVersion: ENGINE_VERSION,
|
|
@@ -12290,8 +13435,8 @@ async function runCli(rawArgv) {
|
|
|
12290
13435
|
}
|
|
12291
13436
|
} else if (cmd === "rules") {
|
|
12292
13437
|
if (!flags2.config) throw new Error("rules needs --config <codeindex.rules.json>");
|
|
12293
|
-
const rules = parseRules(JSON.parse(
|
|
12294
|
-
const { graph } =
|
|
13438
|
+
const rules = parseRules(JSON.parse(readFileSync9(flags2.config, "utf8")));
|
|
13439
|
+
const { graph } = readArtifacts();
|
|
12295
13440
|
const violations = checkRules(graph, rules);
|
|
12296
13441
|
const errors = violations.filter((v) => v.severity === "error").length;
|
|
12297
13442
|
emit(JSON.stringify({ errors, warnings: violations.length - errors, violations }, null, 2) + "\n", flags2.out);
|
|
@@ -12312,26 +13457,48 @@ async function runCli(rawArgv) {
|
|
|
12312
13457
|
for (const k of [...churn.keys()].sort()) sorted[k] = churn.get(k);
|
|
12313
13458
|
emit(JSON.stringify({ ok, churn: sorted }, null, 2) + "\n", flags2.out);
|
|
12314
13459
|
} else if (cmd === "repomap") {
|
|
12315
|
-
const { scan: scan2, graph } =
|
|
13460
|
+
const { scan: scan2, graph } = readArtifacts();
|
|
12316
13461
|
emit(renderRepoMap(scan2, graph, { budgetTokens: flags2.budgetTokens }), flags2.out);
|
|
12317
13462
|
} else if (cmd === "hotspots") {
|
|
12318
|
-
const scan2 =
|
|
13463
|
+
const scan2 = readScan();
|
|
12319
13464
|
const { churn, ok } = gitChurn(flags2.repo, { since: flags2.since });
|
|
12320
13465
|
emit(JSON.stringify({ churnOk: ok, hotspots: rankHotspots(scan2, churn) }, null, 2) + "\n", flags2.out);
|
|
12321
13466
|
} else if (cmd === "coupling") {
|
|
12322
13467
|
const { ok, couplings } = changeCoupling(flags2.repo, { since: flags2.since });
|
|
12323
13468
|
emit(JSON.stringify({ ok, couplings }, null, 2) + "\n", flags2.out);
|
|
12324
13469
|
} else if (cmd === "deadcode") {
|
|
12325
|
-
emit(JSON.stringify(findDeadCode(
|
|
13470
|
+
emit(JSON.stringify(findDeadCode(readScan()), null, 2) + "\n", flags2.out);
|
|
12326
13471
|
} else if (cmd === "complexity") {
|
|
12327
|
-
const scan2 =
|
|
13472
|
+
const scan2 = readScan();
|
|
12328
13473
|
emit(JSON.stringify(symbolComplexity(scan2, flags2.positional), null, 2) + "\n", flags2.out);
|
|
12329
13474
|
} else if (cmd === "risk") {
|
|
12330
|
-
const scan2 =
|
|
13475
|
+
const scan2 = readScan();
|
|
12331
13476
|
const { churn, ok } = gitChurn(flags2.repo, { since: flags2.since });
|
|
12332
13477
|
emit(JSON.stringify({ churnOk: ok, risks: riskHotspots(scan2, churn) }, null, 2) + "\n", flags2.out);
|
|
13478
|
+
} else if (cmd === "delta") {
|
|
13479
|
+
const { graph, symbols } = readArtifacts();
|
|
13480
|
+
const res = deltaFor(flags2.repo, graph, symbols, {
|
|
13481
|
+
base: flags2.base,
|
|
13482
|
+
staged: flags2.staged,
|
|
13483
|
+
depth: flags2.depth
|
|
13484
|
+
});
|
|
13485
|
+
if ("error" in res) throw new Error(res.error);
|
|
13486
|
+
emit(flags2.json ? JSON.stringify(res, null, 2) + "\n" : formatDeltaPanel(res), flags2.out);
|
|
13487
|
+
} else if (cmd === "impact") {
|
|
13488
|
+
if (!flags2.positional) throw new Error("impact needs a target: cli.mjs impact <file|module> --repo <dir>");
|
|
13489
|
+
const { graph } = readArtifacts();
|
|
13490
|
+
const res = impactOf(graph, flags2.positional, flags2.depth ?? Infinity);
|
|
13491
|
+
if (!res) throw new Error(`no such file or module in the index: ${flags2.positional}`);
|
|
13492
|
+
emit(JSON.stringify(res, null, 2) + "\n", flags2.out);
|
|
13493
|
+
} else if (cmd === "neighbors") {
|
|
13494
|
+
if (!flags2.positional) throw new Error("neighbors needs a target: cli.mjs neighbors <file|module> --repo <dir>");
|
|
13495
|
+
const { graph } = readArtifacts();
|
|
13496
|
+
const kinds = flags2.kind ? new Set(flags2.kind.split(",").map((k) => k.trim()).filter(Boolean)) : void 0;
|
|
13497
|
+
const res = neighborsOf(graph, flags2.positional, flags2.depth ?? 1, kinds);
|
|
13498
|
+
if (!res) throw new Error(`no such file or module in the index: ${flags2.positional}`);
|
|
13499
|
+
emit(JSON.stringify(res, null, 2) + "\n", flags2.out);
|
|
12333
13500
|
} else if (cmd === "mermaid") {
|
|
12334
|
-
const { graph } =
|
|
13501
|
+
const { graph } = readArtifacts();
|
|
12335
13502
|
emit(renderMermaid(graph, { module: flags2.positional }), flags2.out);
|
|
12336
13503
|
} else if (cmd === "grep") {
|
|
12337
13504
|
if (!flags2.positional) throw new Error("grep needs a pattern: cli.mjs grep <pattern> --repo <dir>");
|
|
@@ -12351,12 +13518,15 @@ ${HELP}`);
|
|
|
12351
13518
|
}
|
|
12352
13519
|
}
|
|
12353
13520
|
export {
|
|
13521
|
+
DEFAULT_DELTA_DEPTH,
|
|
12354
13522
|
DEFAULT_GRAMMARS_URL,
|
|
12355
13523
|
DEFAULT_MAX_FILES,
|
|
12356
13524
|
EMBED_VERSION,
|
|
12357
13525
|
ENGINE_VERSION,
|
|
12358
13526
|
EXTRACTOR_VERSION,
|
|
13527
|
+
INDEX_DIR,
|
|
12359
13528
|
MARKDOWN_EXT,
|
|
13529
|
+
RISK_WEIGHTS,
|
|
12360
13530
|
SCHEMA_VERSION,
|
|
12361
13531
|
allGrammarKeys,
|
|
12362
13532
|
applyCentrality,
|
|
@@ -12364,6 +13534,7 @@ export {
|
|
|
12364
13534
|
betweennessOf,
|
|
12365
13535
|
buildArtifactsFromScan,
|
|
12366
13536
|
buildCallerIndex,
|
|
13537
|
+
buildCodeRecord,
|
|
12367
13538
|
buildEmbeddingIndex,
|
|
12368
13539
|
buildEndpointIndex,
|
|
12369
13540
|
buildGraph,
|
|
@@ -12384,11 +13555,13 @@ export {
|
|
|
12384
13555
|
communityOf,
|
|
12385
13556
|
compileGlobs,
|
|
12386
13557
|
complexityOfSource,
|
|
13558
|
+
computeDelta,
|
|
12387
13559
|
computeImportPairs,
|
|
12388
13560
|
computeSurprises,
|
|
12389
13561
|
computeSymbolRefs,
|
|
12390
13562
|
computeTestMap,
|
|
12391
13563
|
deleteMemory,
|
|
13564
|
+
deltaFor,
|
|
12392
13565
|
deserializeEmbeddings,
|
|
12393
13566
|
detectCommunities,
|
|
12394
13567
|
detectWorkspaces,
|
|
@@ -12406,6 +13579,7 @@ export {
|
|
|
12406
13579
|
extractAst,
|
|
12407
13580
|
extractCode,
|
|
12408
13581
|
extractGrammarsTarball,
|
|
13582
|
+
extractInParallel,
|
|
12409
13583
|
extractMarkdown,
|
|
12410
13584
|
extractSymbols,
|
|
12411
13585
|
extractTarInto,
|
|
@@ -12415,6 +13589,7 @@ export {
|
|
|
12415
13589
|
findReferences,
|
|
12416
13590
|
findSymbol,
|
|
12417
13591
|
foldText,
|
|
13592
|
+
formatDeltaPanel,
|
|
12418
13593
|
gitChurn,
|
|
12419
13594
|
grammarKeyForExt,
|
|
12420
13595
|
grammarKeysForExts,
|
|
@@ -12424,6 +13599,8 @@ export {
|
|
|
12424
13599
|
have,
|
|
12425
13600
|
headCommit,
|
|
12426
13601
|
healthzUrl,
|
|
13602
|
+
hubThreshold,
|
|
13603
|
+
impactOf,
|
|
12427
13604
|
insertAfterSymbol,
|
|
12428
13605
|
insertBeforeSymbol,
|
|
12429
13606
|
intDot,
|
|
@@ -12434,22 +13611,28 @@ export {
|
|
|
12434
13611
|
isSurprising,
|
|
12435
13612
|
isTestFile,
|
|
12436
13613
|
isTestPath,
|
|
13614
|
+
keptCodeFiles,
|
|
12437
13615
|
keywords,
|
|
12438
13616
|
languageOf,
|
|
12439
13617
|
listMemories,
|
|
12440
13618
|
loadEmbedModel,
|
|
13619
|
+
neighborsOf,
|
|
12441
13620
|
pagerankOf,
|
|
12442
13621
|
parseGitignore,
|
|
12443
13622
|
parseRules,
|
|
13623
|
+
preloadArtifacts,
|
|
13624
|
+
preloadSession,
|
|
12444
13625
|
probeEndpoint,
|
|
12445
13626
|
pullGrammars,
|
|
12446
13627
|
quantize,
|
|
12447
13628
|
rankHotspots,
|
|
12448
13629
|
rankedKeywords,
|
|
12449
13630
|
readMemory,
|
|
13631
|
+
readPersistedIndex,
|
|
12450
13632
|
readText,
|
|
12451
13633
|
renderGraphJson,
|
|
12452
13634
|
renderMermaid,
|
|
13635
|
+
renderMermaidClustered,
|
|
12453
13636
|
renderRepoMap,
|
|
12454
13637
|
renderScip,
|
|
12455
13638
|
renderSymbolsJson,
|
|
@@ -12465,13 +13648,17 @@ export {
|
|
|
12465
13648
|
resolveGrammarsTier,
|
|
12466
13649
|
resolveImport,
|
|
12467
13650
|
resolveUniqueSymbol,
|
|
13651
|
+
reverseClosure,
|
|
12468
13652
|
rewriteCommand,
|
|
12469
13653
|
riskHotspots,
|
|
12470
13654
|
roundHalfToEven,
|
|
12471
13655
|
rrf,
|
|
12472
13656
|
runCli,
|
|
13657
|
+
runExtractWorker,
|
|
12473
13658
|
runMcpServer,
|
|
12474
13659
|
scanRepo,
|
|
13660
|
+
scanRepoParallel,
|
|
13661
|
+
scanSummary,
|
|
12475
13662
|
searchIndex,
|
|
12476
13663
|
searchSemantic,
|
|
12477
13664
|
serializeEmbeddings,
|
|
@@ -12482,9 +13669,11 @@ export {
|
|
|
12482
13669
|
slugify,
|
|
12483
13670
|
subtokens,
|
|
12484
13671
|
symbolComplexity,
|
|
13672
|
+
symbolsInHunks,
|
|
12485
13673
|
symbolsOverview,
|
|
12486
13674
|
testsForModule,
|
|
12487
13675
|
tierForPath,
|
|
13676
|
+
toCacheMap,
|
|
12488
13677
|
tokenize,
|
|
12489
13678
|
uniqueSymbolDefs,
|
|
12490
13679
|
untestedModules,
|
|
@@ -12492,6 +13681,7 @@ export {
|
|
|
12492
13681
|
walk,
|
|
12493
13682
|
warmGrammars,
|
|
12494
13683
|
wordpiece,
|
|
13684
|
+
workerCount,
|
|
12495
13685
|
writeMemory
|
|
12496
13686
|
};
|
|
12497
13687
|
// "Copyright" and "@license" are already caught by DIRECTIVE_RE.
|