ctxloom-pro 1.5.6 → 1.7.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 +25 -3
- package/apps/dashboard/dist/server/index.js +544 -26
- package/apps/dashboard/package.json +2 -2
- package/dist/VectorStore-WDL3H7QT.js +9 -0
- package/dist/chunk-6FGTNOCP.js +397 -0
- package/dist/{chunk-JULFFD7O.js → chunk-7S2ELKNU.js} +123 -3
- package/dist/{chunk-FPMNXF4D.js → chunk-FFCLVZCO.js} +685 -43
- package/dist/{chunk-II2DPYRJ.js → chunk-YHLMQVBV.js} +200 -10
- package/dist/embedder-2JWDJUE2.js +26 -0
- package/dist/index.js +11 -11
- package/dist/setup/postinstall.js +1 -1
- package/dist/{src-DL44T55H.js → src-QAYZWPSL.js} +6 -4
- package/dist/workers/indexerWorker.js +2 -2
- package/package.json +1 -1
- package/dist/VectorStore-2LVECRTY.js +0 -8
- package/dist/chunk-WDX4PJGL.js +0 -214
- package/dist/embedder-3AE4CSR7.js +0 -14
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VectorStore
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7S2ELKNU.js";
|
|
4
4
|
import {
|
|
5
|
+
INDEXER_IGNORED_DIRS,
|
|
5
6
|
collectFiles,
|
|
6
7
|
generateEmbedding
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-6FGTNOCP.js";
|
|
8
9
|
import {
|
|
9
10
|
diskSink,
|
|
10
11
|
readEvents
|
|
@@ -503,6 +504,13 @@ var ASTParser = class {
|
|
|
503
504
|
extractTSNodes(rootNode, _filePath, lines) {
|
|
504
505
|
const nodes = [];
|
|
505
506
|
const processedIds = /* @__PURE__ */ new Set();
|
|
507
|
+
const hasCallableRight = (n) => {
|
|
508
|
+
const right = n.childForFieldName?.("right") ?? n.children[n.children.length - 1];
|
|
509
|
+
if (!right) return false;
|
|
510
|
+
if (right.type === "function" || right.type === "function_expression" || right.type === "arrow_function" || right.type === "function_declaration") return true;
|
|
511
|
+
if (right.type === "assignment_expression") return hasCallableRight(right);
|
|
512
|
+
return false;
|
|
513
|
+
};
|
|
506
514
|
const walk = (node) => {
|
|
507
515
|
if (processedIds.has(node.id)) return;
|
|
508
516
|
switch (node.type) {
|
|
@@ -674,6 +682,56 @@ var ASTParser = class {
|
|
|
674
682
|
processedIds.add(node.id);
|
|
675
683
|
return;
|
|
676
684
|
}
|
|
685
|
+
// ─── Prototype / object method assignments ──────────────────────
|
|
686
|
+
// CommonJS libraries (and pre-class-syntax ES) attach their public
|
|
687
|
+
// API via assignment expressions:
|
|
688
|
+
//
|
|
689
|
+
// res.send = function send(body) { ... }
|
|
690
|
+
// res.json = function (obj) { ... }
|
|
691
|
+
// res.contentType = res.type = function (type) { ... } // chained
|
|
692
|
+
// exports.foo = function foo() { ... }
|
|
693
|
+
// MyClass.prototype.bar = function () { ... }
|
|
694
|
+
//
|
|
695
|
+
// Without this case, none of those names enter the symbol index,
|
|
696
|
+
// so `lookupSymbolsByFile()` returns empty for libraries like
|
|
697
|
+
// express, and any downstream tool that wants to attribute callers
|
|
698
|
+
// to a file (blast-radius symbolCallers, ctx_get_definition, etc.)
|
|
699
|
+
// falls flat. The call graph itself already records callers of
|
|
700
|
+
// `send`/`json`/etc. — this case bridges the gap so we can match
|
|
701
|
+
// them back to the file that defines them.
|
|
702
|
+
//
|
|
703
|
+
// Heuristic:
|
|
704
|
+
// - left = member_expression → use the FINAL property as symbol
|
|
705
|
+
// - right = function | arrow_function | function_expression
|
|
706
|
+
// - right = assignment_expression → recurse for chained pattern
|
|
707
|
+
// Anything else (constants, identifiers being aliased) is skipped
|
|
708
|
+
// intentionally — those aren't callable API surface.
|
|
709
|
+
case "assignment_expression": {
|
|
710
|
+
const left = node.childForFieldName?.("left") ?? node.children.find(
|
|
711
|
+
(c) => c?.type === "member_expression" || c?.type === "identifier"
|
|
712
|
+
);
|
|
713
|
+
const right = node.childForFieldName?.("right") ?? node.children[node.children.length - 1];
|
|
714
|
+
if (left?.type === "member_expression" && right) {
|
|
715
|
+
const prop = left.childForFieldName?.("property") ?? left.children[left.children.length - 1];
|
|
716
|
+
const propName = prop?.text;
|
|
717
|
+
if (right.type === "assignment_expression") {
|
|
718
|
+
walk(right);
|
|
719
|
+
}
|
|
720
|
+
const rightIsCallable = right.type === "function" || right.type === "function_expression" || right.type === "arrow_function" || right.type === "function_declaration" || right.type === "assignment_expression" && hasCallableRight(right);
|
|
721
|
+
if (propName && rightIsCallable && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName)) {
|
|
722
|
+
const sig = lines[node.startPosition.row] ?? "";
|
|
723
|
+
nodes.push({
|
|
724
|
+
type: "method",
|
|
725
|
+
name: propName,
|
|
726
|
+
signature: sig.trim().slice(0, 200),
|
|
727
|
+
startLine: node.startPosition.row + 1,
|
|
728
|
+
endLine: node.endPosition.row + 1
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
processedIds.add(node.id);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
677
735
|
// ─── Lexical declarations (const fn = () => {}) ────────────────
|
|
678
736
|
case "lexical_declaration": {
|
|
679
737
|
for (const child of node.children) {
|
|
@@ -780,10 +838,34 @@ var ASTParser = class {
|
|
|
780
838
|
}
|
|
781
839
|
case "import_from_statement": {
|
|
782
840
|
const moduleNode = node.children.find((c) => c?.type === "dotted_name" || c?.type === "relative_import");
|
|
841
|
+
const sourceText = moduleNode?.text ?? "";
|
|
842
|
+
const importedNames = [];
|
|
843
|
+
let pastImportKeyword = false;
|
|
844
|
+
for (const child of node.children) {
|
|
845
|
+
if (!child) continue;
|
|
846
|
+
if (child.text === "import" && (child.type === "import" || child.children.length === 0)) {
|
|
847
|
+
pastImportKeyword = true;
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
if (!pastImportKeyword) continue;
|
|
851
|
+
if (child.type === "wildcard_import") continue;
|
|
852
|
+
if (child.type === "dotted_name") {
|
|
853
|
+
importedNames.push(child.text);
|
|
854
|
+
} else if (child.type === "aliased_import") {
|
|
855
|
+
const alias = child.childForFieldName?.("alias");
|
|
856
|
+
const aliasName = alias?.text;
|
|
857
|
+
if (aliasName) importedNames.push(aliasName);
|
|
858
|
+
else {
|
|
859
|
+
const name = child.childForFieldName?.("name")?.text;
|
|
860
|
+
if (name) importedNames.push(name);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
783
864
|
nodes.push({
|
|
784
865
|
type: "import",
|
|
785
|
-
name:
|
|
786
|
-
source:
|
|
866
|
+
name: sourceText,
|
|
867
|
+
source: sourceText,
|
|
868
|
+
importedNames: importedNames.length > 0 ? importedNames : void 0,
|
|
787
869
|
startLine: node.startPosition.row + 1,
|
|
788
870
|
endLine: node.endPosition.row + 1
|
|
789
871
|
});
|
|
@@ -1653,6 +1735,78 @@ var ASTParser = class {
|
|
|
1653
1735
|
tree.delete();
|
|
1654
1736
|
}
|
|
1655
1737
|
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Extract all call edges in a Python (.py / .ipynb) file. Mirrors
|
|
1740
|
+
* `parseAllCallEdges` but uses tree-sitter-python node types.
|
|
1741
|
+
*
|
|
1742
|
+
* Tree-sitter-python relevant shapes:
|
|
1743
|
+
* - call.function = identifier "foo" → callee = "foo"
|
|
1744
|
+
* - call.function = attribute (obj.method) → callee = "method"
|
|
1745
|
+
* - call.function = call (chained) → recurse into inner
|
|
1746
|
+
*
|
|
1747
|
+
* Enclosing-context tracking follows the same pattern: track the
|
|
1748
|
+
* innermost `function_definition`. Methods inside a class are also
|
|
1749
|
+
* function_definitions, so the same handler covers them.
|
|
1750
|
+
*/
|
|
1751
|
+
async parseAllPythonCallEdges(filePath) {
|
|
1752
|
+
if (!this.pyLang) await this.loadPython();
|
|
1753
|
+
if (!this.pyLang) return [];
|
|
1754
|
+
const parser = this.getParser(this.pyLang);
|
|
1755
|
+
let source;
|
|
1756
|
+
try {
|
|
1757
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
1758
|
+
source = filePath.endsWith(".ipynb") ? extractNotebookPythonSource(raw) : raw;
|
|
1759
|
+
} catch {
|
|
1760
|
+
return [];
|
|
1761
|
+
}
|
|
1762
|
+
if (!source.trim()) return [];
|
|
1763
|
+
const tree = parser.parse(source);
|
|
1764
|
+
if (!tree) return [];
|
|
1765
|
+
const results = [];
|
|
1766
|
+
const extractCalleeName = (fn) => {
|
|
1767
|
+
if (fn.type === "identifier") return fn.text;
|
|
1768
|
+
if (fn.type === "attribute") {
|
|
1769
|
+
const right = fn.childForFieldName?.("attribute") ?? fn.children[fn.children.length - 1];
|
|
1770
|
+
return right?.text ?? "";
|
|
1771
|
+
}
|
|
1772
|
+
if (fn.type === "call") {
|
|
1773
|
+
const innerFn = fn.childForFieldName?.("function");
|
|
1774
|
+
return innerFn ? extractCalleeName(innerFn) : "";
|
|
1775
|
+
}
|
|
1776
|
+
return "";
|
|
1777
|
+
};
|
|
1778
|
+
const walk = (node, contextStack) => {
|
|
1779
|
+
let newStack = contextStack;
|
|
1780
|
+
if (node.type === "function_definition") {
|
|
1781
|
+
const nameNode = node.childForFieldName?.("name");
|
|
1782
|
+
if (nameNode?.text) {
|
|
1783
|
+
newStack = [...contextStack, nameNode.text];
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
if (node.type === "call") {
|
|
1787
|
+
const fn = node.childForFieldName?.("function") ?? node.children.find((c) => c?.type === "identifier" || c?.type === "attribute" || c?.type === "call");
|
|
1788
|
+
if (fn) {
|
|
1789
|
+
const name = extractCalleeName(fn);
|
|
1790
|
+
if (name && name.length > 0) {
|
|
1791
|
+
results.push({
|
|
1792
|
+
callerSymbol: newStack[newStack.length - 1] ?? "",
|
|
1793
|
+
calleeSymbol: name,
|
|
1794
|
+
line: node.startPosition.row + 1
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
for (const child of node.children) {
|
|
1800
|
+
if (child) walk(child, newStack);
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
try {
|
|
1804
|
+
walk(tree.rootNode, []);
|
|
1805
|
+
return results;
|
|
1806
|
+
} finally {
|
|
1807
|
+
tree.delete();
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1656
1810
|
/**
|
|
1657
1811
|
* Extract all call edges in a TypeScript/TSX file.
|
|
1658
1812
|
* Tracks the enclosing function/method context for each call site.
|
|
@@ -1735,48 +1889,100 @@ var GoModuleResolver = class {
|
|
|
1735
1889
|
}
|
|
1736
1890
|
/**
|
|
1737
1891
|
* Resolve a module-path import (e.g. `github.com/myorg/myapp/internal/auth`)
|
|
1738
|
-
* to
|
|
1892
|
+
* to the FIRST relative project path (e.g. `internal/auth/auth.go`).
|
|
1739
1893
|
*
|
|
1740
1894
|
* Returns null for:
|
|
1741
1895
|
* - Third-party imports (different module prefix)
|
|
1742
1896
|
* - Relative imports (use resolveRelative() instead)
|
|
1743
1897
|
* - Imports where no .go files are found
|
|
1898
|
+
*
|
|
1899
|
+
* NOTE: A Go import imports a PACKAGE (a directory of .go files), not a
|
|
1900
|
+
* single file. Use `resolveAll()` to get every file in the package — that
|
|
1901
|
+
* matches Go's compile-unit semantics and is what the graph wants for
|
|
1902
|
+
* accurate reachability. `resolve()` is kept for back-compat callers
|
|
1903
|
+
* that only need a representative file.
|
|
1744
1904
|
*/
|
|
1745
1905
|
resolve(importPath) {
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1906
|
+
const all = this.resolveAll(importPath);
|
|
1907
|
+
return all[0] ?? null;
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Resolve a module-path import to ALL non-test .go files in the target
|
|
1911
|
+
* package directory. This matches Go's compile-unit semantics: a single
|
|
1912
|
+
* `import "github.com/foo/bar/pkg"` statement brings the entire `pkg/`
|
|
1913
|
+
* directory into the dependency graph — every exported symbol from
|
|
1914
|
+
* every .go file in that directory is accessible to the caller.
|
|
1915
|
+
*
|
|
1916
|
+
* Pre-fix the resolver returned only ONE file per import, which made
|
|
1917
|
+
* gin's `binding/` package (~20 files) appear to be a single file from
|
|
1918
|
+
* the graph's perspective. The bench's graphReachability on gin
|
|
1919
|
+
* collapsed to 0.32 because PRs that touched 4 files in `binding/`
|
|
1920
|
+
* had only ONE of them in the graph reach of the entry-point file.
|
|
1921
|
+
* Returning ALL package files fixes the structural model.
|
|
1922
|
+
*
|
|
1923
|
+
* Test files (_test.go) are intentionally excluded — they're not part
|
|
1924
|
+
* of a package's public API and aren't imported by callers. The
|
|
1925
|
+
* test↔source link is handled separately by per-directory sibling
|
|
1926
|
+
* edges (see DependencyGraph's Go intra-package pass).
|
|
1927
|
+
*/
|
|
1928
|
+
resolveAll(importPath) {
|
|
1929
|
+
if (!this.modulePath) return [];
|
|
1930
|
+
if (importPath.startsWith(".")) return [];
|
|
1931
|
+
if (!importPath.startsWith(this.modulePath)) return [];
|
|
1749
1932
|
const suffix = importPath.slice(this.modulePath.length);
|
|
1750
|
-
if (!suffix) return
|
|
1933
|
+
if (!suffix) return [];
|
|
1751
1934
|
const subPath = suffix.startsWith("/") ? suffix.slice(1) : suffix;
|
|
1752
1935
|
const absDir = path3.join(this.rootDir, subPath);
|
|
1753
|
-
return this.
|
|
1936
|
+
return this.allGoFilesInDir(
|
|
1937
|
+
absDir,
|
|
1938
|
+
subPath,
|
|
1939
|
+
/* includeTests */
|
|
1940
|
+
false
|
|
1941
|
+
);
|
|
1754
1942
|
}
|
|
1755
1943
|
/**
|
|
1756
1944
|
* Resolve a relative import (`./config`, `../pkg`) from a given Go source file.
|
|
1757
1945
|
* Returns the relative project path to the first .go file found, or null.
|
|
1946
|
+
* Kept for back-compat — most callers should prefer `resolveRelativeAll()`.
|
|
1758
1947
|
*/
|
|
1759
1948
|
resolveRelative(fromFile, importSpec) {
|
|
1949
|
+
const all = this.resolveRelativeAll(fromFile, importSpec);
|
|
1950
|
+
return all[0] ?? null;
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Resolve a relative import to ALL non-test .go files in the target
|
|
1954
|
+
* package directory. See `resolveAll()` for rationale.
|
|
1955
|
+
*/
|
|
1956
|
+
resolveRelativeAll(fromFile, importSpec) {
|
|
1760
1957
|
const fromDir = path3.dirname(fromFile);
|
|
1761
1958
|
const absTarget = path3.resolve(fromDir, importSpec);
|
|
1762
1959
|
const subPath = path3.relative(this.rootDir, absTarget);
|
|
1763
|
-
return this.
|
|
1960
|
+
return this.allGoFilesInDir(
|
|
1961
|
+
absTarget,
|
|
1962
|
+
subPath,
|
|
1963
|
+
/* includeTests */
|
|
1964
|
+
false
|
|
1965
|
+
);
|
|
1764
1966
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1967
|
+
/**
|
|
1968
|
+
* Enumerate every .go file in a directory, returning project-relative
|
|
1969
|
+
* paths sorted with non-test files first. Used by both the single-file
|
|
1970
|
+
* and all-files resolvers.
|
|
1971
|
+
*/
|
|
1972
|
+
allGoFilesInDir(absDir, relDir, includeTests) {
|
|
1973
|
+
if (!fs3.existsSync(absDir)) return [];
|
|
1767
1974
|
let entries;
|
|
1768
1975
|
try {
|
|
1769
1976
|
entries = fs3.readdirSync(absDir);
|
|
1770
1977
|
} catch {
|
|
1771
|
-
return
|
|
1978
|
+
return [];
|
|
1772
1979
|
}
|
|
1773
|
-
const goFiles = entries.filter((f) => f.endsWith(".go")).sort((a, b) => {
|
|
1980
|
+
const goFiles = entries.filter((f) => f.endsWith(".go")).filter((f) => includeTests || !f.endsWith("_test.go")).sort((a, b) => {
|
|
1774
1981
|
const aTest = a.endsWith("_test.go") ? 1 : 0;
|
|
1775
1982
|
const bTest = b.endsWith("_test.go") ? 1 : 0;
|
|
1776
1983
|
return aTest - bTest || a.localeCompare(b);
|
|
1777
1984
|
});
|
|
1778
|
-
|
|
1779
|
-
return path3.join(relDir, goFiles[0]).replace(/\\/g, "/");
|
|
1985
|
+
return goFiles.map((f) => path3.join(relDir, f).replace(/\\/g, "/"));
|
|
1780
1986
|
}
|
|
1781
1987
|
};
|
|
1782
1988
|
|
|
@@ -1818,6 +2024,24 @@ function extractImports(filePath, content) {
|
|
|
1818
2024
|
return extractNotebookImports(filePath, content);
|
|
1819
2025
|
case ".vue":
|
|
1820
2026
|
return extractVueImports(content);
|
|
2027
|
+
case ".c":
|
|
2028
|
+
case ".cc":
|
|
2029
|
+
case ".cpp":
|
|
2030
|
+
case ".cxx":
|
|
2031
|
+
case ".h":
|
|
2032
|
+
case ".hh":
|
|
2033
|
+
case ".hpp":
|
|
2034
|
+
case ".hxx":
|
|
2035
|
+
return extractCppImports(content);
|
|
2036
|
+
case ".scala":
|
|
2037
|
+
return extractScalaImports(content);
|
|
2038
|
+
case ".lua":
|
|
2039
|
+
return extractLuaImports(content);
|
|
2040
|
+
case ".ex":
|
|
2041
|
+
case ".exs":
|
|
2042
|
+
return extractElixirImports(content);
|
|
2043
|
+
case ".zig":
|
|
2044
|
+
return extractZigImports(content);
|
|
1821
2045
|
default:
|
|
1822
2046
|
return [];
|
|
1823
2047
|
}
|
|
@@ -1837,8 +2061,14 @@ function resolveImport(fromAbs, raw, rootDir) {
|
|
|
1837
2061
|
if (ext === ".dart") return resolveDartImport(fromAbs, fromDir, raw, rootDir);
|
|
1838
2062
|
if (ext === ".ipynb") return resolvePythonImport(fromAbs, fromDir, raw, rootDir);
|
|
1839
2063
|
if (ext === ".vue") return resolveVueImport(fromAbs, fromDir, raw, rootDir);
|
|
2064
|
+
if (CPP_EXTENSIONS.has(ext)) return resolveCppImport(fromDir, raw, rootDir);
|
|
2065
|
+
if (ext === ".scala") return resolveScalaImport(fromDir, raw, rootDir);
|
|
2066
|
+
if (ext === ".lua") return resolveLuaImport(fromDir, raw, rootDir);
|
|
2067
|
+
if (ext === ".ex" || ext === ".exs") return resolveElixirImport(fromDir, raw, rootDir);
|
|
2068
|
+
if (ext === ".zig") return resolveZigImport(fromAbs, fromDir, raw, rootDir);
|
|
1840
2069
|
return null;
|
|
1841
2070
|
}
|
|
2071
|
+
var CPP_EXTENSIONS = /* @__PURE__ */ new Set([".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"]);
|
|
1842
2072
|
function extractPythonImports(content) {
|
|
1843
2073
|
const results = [];
|
|
1844
2074
|
let m;
|
|
@@ -2125,6 +2355,124 @@ function resolveVueImport(fromAbs, fromDir, raw, rootDir) {
|
|
|
2125
2355
|
}
|
|
2126
2356
|
return null;
|
|
2127
2357
|
}
|
|
2358
|
+
function extractCppImports(content) {
|
|
2359
|
+
const results = [];
|
|
2360
|
+
const localInclude = /^\s*#\s*include\s+"([^"]+)"/gm;
|
|
2361
|
+
let m;
|
|
2362
|
+
while ((m = localInclude.exec(content)) !== null) {
|
|
2363
|
+
results.push({ specifier: m[1], isRelative: true });
|
|
2364
|
+
}
|
|
2365
|
+
return results;
|
|
2366
|
+
}
|
|
2367
|
+
function resolveCppImport(fromDir, raw, rootDir) {
|
|
2368
|
+
const rootResolved = path4.resolve(rootDir);
|
|
2369
|
+
const candidates = [
|
|
2370
|
+
path4.resolve(fromDir, raw.specifier),
|
|
2371
|
+
path4.resolve(rootDir, raw.specifier)
|
|
2372
|
+
];
|
|
2373
|
+
for (const c of candidates) {
|
|
2374
|
+
if (!c.startsWith(rootResolved + path4.sep) && c !== rootResolved) continue;
|
|
2375
|
+
if (fs4.existsSync(c)) return path4.relative(rootDir, c);
|
|
2376
|
+
}
|
|
2377
|
+
return null;
|
|
2378
|
+
}
|
|
2379
|
+
function extractScalaImports(content) {
|
|
2380
|
+
const results = [];
|
|
2381
|
+
const importRe = /^\s*import\s+((?:\w+\.)*\w+)(?:\.\{[^}]+\})?/gm;
|
|
2382
|
+
let m;
|
|
2383
|
+
while ((m = importRe.exec(content)) !== null) {
|
|
2384
|
+
results.push({ specifier: m[1], isRelative: false });
|
|
2385
|
+
}
|
|
2386
|
+
return results;
|
|
2387
|
+
}
|
|
2388
|
+
function resolveScalaImport(fromDir, raw, rootDir) {
|
|
2389
|
+
const asPath = raw.specifier.replace(/\./g, path4.sep);
|
|
2390
|
+
const candidates = [
|
|
2391
|
+
path4.join(rootDir, "src", "main", "scala", asPath + ".scala"),
|
|
2392
|
+
path4.join(rootDir, asPath + ".scala")
|
|
2393
|
+
];
|
|
2394
|
+
for (const c of candidates) {
|
|
2395
|
+
if (fs4.existsSync(c)) return path4.relative(rootDir, c);
|
|
2396
|
+
}
|
|
2397
|
+
const className = raw.specifier.split(".").pop() ?? raw.specifier;
|
|
2398
|
+
const local = path4.join(fromDir, className + ".scala");
|
|
2399
|
+
if (fs4.existsSync(local)) return path4.relative(rootDir, local);
|
|
2400
|
+
return null;
|
|
2401
|
+
}
|
|
2402
|
+
function extractLuaImports(content) {
|
|
2403
|
+
const results = [];
|
|
2404
|
+
const requireRe = /\brequire\s*\(?\s*['"]([^'"]+)['"]/g;
|
|
2405
|
+
let m;
|
|
2406
|
+
while ((m = requireRe.exec(content)) !== null) {
|
|
2407
|
+
results.push({ specifier: m[1], isRelative: false });
|
|
2408
|
+
}
|
|
2409
|
+
return results;
|
|
2410
|
+
}
|
|
2411
|
+
function resolveLuaImport(fromDir, raw, rootDir) {
|
|
2412
|
+
const asPath = raw.specifier.replace(/\./g, path4.sep);
|
|
2413
|
+
const candidates = [
|
|
2414
|
+
path4.join(fromDir, asPath + ".lua"),
|
|
2415
|
+
path4.join(rootDir, asPath + ".lua"),
|
|
2416
|
+
// Lua's package convention also supports init.lua as a directory
|
|
2417
|
+
// entry point — analogous to Python's __init__.py.
|
|
2418
|
+
path4.join(rootDir, asPath, "init.lua")
|
|
2419
|
+
];
|
|
2420
|
+
for (const c of candidates) {
|
|
2421
|
+
if (fs4.existsSync(c)) return path4.relative(rootDir, c);
|
|
2422
|
+
}
|
|
2423
|
+
return null;
|
|
2424
|
+
}
|
|
2425
|
+
function extractElixirImports(content) {
|
|
2426
|
+
const results = [];
|
|
2427
|
+
const re = /^\s*(?:alias|import|use|require)\s+([A-Z][\w.]*)/gm;
|
|
2428
|
+
let m;
|
|
2429
|
+
while ((m = re.exec(content)) !== null) {
|
|
2430
|
+
results.push({ specifier: m[1], isRelative: false });
|
|
2431
|
+
}
|
|
2432
|
+
return results;
|
|
2433
|
+
}
|
|
2434
|
+
function resolveElixirImport(fromDir, raw, rootDir) {
|
|
2435
|
+
const segments = raw.specifier.split(".").map(
|
|
2436
|
+
(s) => s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()
|
|
2437
|
+
);
|
|
2438
|
+
const asPath = segments.join(path4.sep);
|
|
2439
|
+
const candidates = [
|
|
2440
|
+
path4.join(rootDir, "lib", asPath + ".ex"),
|
|
2441
|
+
path4.join(rootDir, "lib", asPath + ".exs"),
|
|
2442
|
+
path4.join(rootDir, asPath + ".ex"),
|
|
2443
|
+
path4.join(rootDir, asPath + ".exs")
|
|
2444
|
+
];
|
|
2445
|
+
for (const c of candidates) {
|
|
2446
|
+
if (fs4.existsSync(c)) return path4.relative(rootDir, c);
|
|
2447
|
+
}
|
|
2448
|
+
const tail = segments[segments.length - 1];
|
|
2449
|
+
const local = path4.join(fromDir, tail + ".ex");
|
|
2450
|
+
if (fs4.existsSync(local)) return path4.relative(rootDir, local);
|
|
2451
|
+
return null;
|
|
2452
|
+
}
|
|
2453
|
+
function extractZigImports(content) {
|
|
2454
|
+
const results = [];
|
|
2455
|
+
const importRe = /@import\s*\(\s*"([^"]+)"\s*\)/g;
|
|
2456
|
+
let m;
|
|
2457
|
+
while ((m = importRe.exec(content)) !== null) {
|
|
2458
|
+
const spec = m[1];
|
|
2459
|
+
if (spec.endsWith(".zig")) {
|
|
2460
|
+
const isRelative = spec.startsWith(".") || !spec.includes("/");
|
|
2461
|
+
results.push({ specifier: spec, isRelative });
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return results;
|
|
2465
|
+
}
|
|
2466
|
+
function resolveZigImport(fromAbs, fromDir, raw, rootDir) {
|
|
2467
|
+
void fromAbs;
|
|
2468
|
+
const rootResolved = path4.resolve(rootDir);
|
|
2469
|
+
const candidate = path4.resolve(fromDir, raw.specifier);
|
|
2470
|
+
if (!candidate.startsWith(rootResolved + path4.sep) && candidate !== rootResolved) {
|
|
2471
|
+
return null;
|
|
2472
|
+
}
|
|
2473
|
+
if (fs4.existsSync(candidate)) return path4.relative(rootDir, candidate);
|
|
2474
|
+
return null;
|
|
2475
|
+
}
|
|
2128
2476
|
|
|
2129
2477
|
// packages/core/src/utils/TsConfigPathsResolver.ts
|
|
2130
2478
|
import fs5 from "fs";
|
|
@@ -2355,6 +2703,7 @@ var CallGraphIndex = class _CallGraphIndex {
|
|
|
2355
2703
|
|
|
2356
2704
|
// packages/core/src/graph/DependencyGraph.ts
|
|
2357
2705
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".vue"]);
|
|
2706
|
+
var PY_EXTENSIONS = /* @__PURE__ */ new Set([".py", ".ipynb"]);
|
|
2358
2707
|
var AST_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs", ".java", ".cs", ".rb", ".kt", ".kts", ".swift", ".ipynb", ".php", ".dart"]);
|
|
2359
2708
|
var DependencyGraph = class {
|
|
2360
2709
|
/** file → set of files it imports (forward edges) */
|
|
@@ -2364,6 +2713,31 @@ var DependencyGraph = class {
|
|
|
2364
2713
|
/** Symbol index: symbolName → { filePath, type, signature, startLine?, endLine? } */
|
|
2365
2714
|
symbolIndex = /* @__PURE__ */ new Map();
|
|
2366
2715
|
callGraphIndex = new CallGraphIndex();
|
|
2716
|
+
/**
|
|
2717
|
+
* Re-export tracing (v1.6.x):
|
|
2718
|
+
* reExportMap[barrelFile][symbol] = sourceFile
|
|
2719
|
+
*
|
|
2720
|
+
* Built during graph construction from `from .submodule import Name`
|
|
2721
|
+
* statements (Python). When file C imports `from <barrel> import Name`,
|
|
2722
|
+
* the import resolver consults this map and emits a parallel edge
|
|
2723
|
+
* C → sourceFile so blast-radius queries against sourceFile can find
|
|
2724
|
+
* C as a transitive consumer through the barrel.
|
|
2725
|
+
*
|
|
2726
|
+
* Concrete fastapi case:
|
|
2727
|
+
* tests/test_routing.py has `from fastapi import APIRouter`
|
|
2728
|
+
* fastapi/__init__.py has `from .routing import APIRouter`
|
|
2729
|
+
* → reExportMap['fastapi/__init__.py']['APIRouter'] = 'fastapi/routing.py'
|
|
2730
|
+
* → emit edge tests/test_routing.py → fastapi/routing.py
|
|
2731
|
+
*/
|
|
2732
|
+
reExportMap = /* @__PURE__ */ new Map();
|
|
2733
|
+
/**
|
|
2734
|
+
* Pending re-export queries. Populated during pass 1 (parse all
|
|
2735
|
+
* files, extract their imports); resolved in pass 2 once the
|
|
2736
|
+
* reExportMap is fully built. Required because file ordering would
|
|
2737
|
+
* otherwise miss re-exports whose source files are parsed AFTER the
|
|
2738
|
+
* consumer file.
|
|
2739
|
+
*/
|
|
2740
|
+
pendingReExportQueries = [];
|
|
2367
2741
|
parser = null;
|
|
2368
2742
|
rootDir = "";
|
|
2369
2743
|
snapshotDir = "";
|
|
@@ -2418,8 +2792,10 @@ var DependencyGraph = class {
|
|
|
2418
2792
|
for (const imp of importNodes) {
|
|
2419
2793
|
const spec = imp.source ?? imp.name;
|
|
2420
2794
|
const isRelative = spec.startsWith(".");
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2795
|
+
const resolvedAll = isRelative ? goResolver.resolveRelativeAll(absPath, spec) : goResolver.resolveAll(spec);
|
|
2796
|
+
for (const resolved of resolvedAll) {
|
|
2797
|
+
this.addEdge(relPath, resolved);
|
|
2798
|
+
}
|
|
2423
2799
|
}
|
|
2424
2800
|
} else {
|
|
2425
2801
|
const importNodes = nodes.filter((n) => n.type === "import");
|
|
@@ -2428,7 +2804,26 @@ var DependencyGraph = class {
|
|
|
2428
2804
|
const specifier = imp.source ?? imp.name;
|
|
2429
2805
|
const isRelative = specifier.startsWith(".");
|
|
2430
2806
|
const resolved = resolveImport(absPath, { specifier, isRelative }, rootDir);
|
|
2431
|
-
if (resolved)
|
|
2807
|
+
if (resolved) {
|
|
2808
|
+
this.addEdge(relPath, resolved);
|
|
2809
|
+
if (ext === ".py" && imp.importedNames && imp.importedNames.length > 0) {
|
|
2810
|
+
if (isRelative) {
|
|
2811
|
+
let map = this.reExportMap.get(relPath);
|
|
2812
|
+
if (!map) {
|
|
2813
|
+
map = /* @__PURE__ */ new Map();
|
|
2814
|
+
this.reExportMap.set(relPath, map);
|
|
2815
|
+
}
|
|
2816
|
+
for (const name of imp.importedNames) {
|
|
2817
|
+
map.set(name, resolved);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
this.pendingReExportQueries.push({
|
|
2821
|
+
caller: relPath,
|
|
2822
|
+
barrel: resolved,
|
|
2823
|
+
symbols: imp.importedNames
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2432
2827
|
}
|
|
2433
2828
|
} else {
|
|
2434
2829
|
const content = fs6.readFileSync(absPath, "utf-8");
|
|
@@ -2440,7 +2835,7 @@ var DependencyGraph = class {
|
|
|
2440
2835
|
}
|
|
2441
2836
|
}
|
|
2442
2837
|
for (const node of nodes) {
|
|
2443
|
-
if (node.type === "function" || node.type === "class" || node.type === "interface") {
|
|
2838
|
+
if (node.type === "function" || node.type === "class" || node.type === "interface" || node.type === "method") {
|
|
2444
2839
|
const existing = this.symbolIndex.get(node.name) ?? [];
|
|
2445
2840
|
existing.push({
|
|
2446
2841
|
filePath: relPath,
|
|
@@ -2457,6 +2852,11 @@ var DependencyGraph = class {
|
|
|
2457
2852
|
for (const edge of callEdges) {
|
|
2458
2853
|
this.callGraphIndex.addEdge({ callerFile: relPath, ...edge });
|
|
2459
2854
|
}
|
|
2855
|
+
} else if (PY_EXTENSIONS.has(ext)) {
|
|
2856
|
+
const callEdges = await this.parser.parseAllPythonCallEdges(absPath);
|
|
2857
|
+
for (const edge of callEdges) {
|
|
2858
|
+
this.callGraphIndex.addEdge({ callerFile: relPath, ...edge });
|
|
2859
|
+
}
|
|
2460
2860
|
}
|
|
2461
2861
|
} else {
|
|
2462
2862
|
const content = fs6.readFileSync(absPath, "utf-8");
|
|
@@ -2470,6 +2870,49 @@ var DependencyGraph = class {
|
|
|
2470
2870
|
logger.error("Failed to parse", { file: relPath, detail: err instanceof Error ? err.message : String(err) });
|
|
2471
2871
|
}
|
|
2472
2872
|
}
|
|
2873
|
+
let reExportEdgesAdded = 0;
|
|
2874
|
+
for (const { caller, barrel, symbols } of this.pendingReExportQueries) {
|
|
2875
|
+
const map = this.reExportMap.get(barrel);
|
|
2876
|
+
if (!map) continue;
|
|
2877
|
+
for (const sym of symbols) {
|
|
2878
|
+
const source = map.get(sym);
|
|
2879
|
+
if (source && source !== caller && source !== barrel) {
|
|
2880
|
+
this.addEdge(caller, source);
|
|
2881
|
+
reExportEdgesAdded += 1;
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
this.pendingReExportQueries = [];
|
|
2886
|
+
if (reExportEdgesAdded > 0) {
|
|
2887
|
+
logger.info("Re-export tracing added parallel edges", { count: reExportEdgesAdded });
|
|
2888
|
+
}
|
|
2889
|
+
let goTestEdgesAdded = 0;
|
|
2890
|
+
for (const relPath of this.forwardEdges.keys()) {
|
|
2891
|
+
if (!relPath.endsWith("_test.go")) continue;
|
|
2892
|
+
const dir = path6.dirname(relPath);
|
|
2893
|
+
const base = path6.basename(relPath, "_test.go");
|
|
2894
|
+
const namesake = path6.join(dir, base + ".go").replace(/\\/g, "/");
|
|
2895
|
+
if (this.forwardEdges.has(namesake)) {
|
|
2896
|
+
this.addEdge(relPath, namesake);
|
|
2897
|
+
this.addEdge(namesake, relPath);
|
|
2898
|
+
goTestEdgesAdded += 2;
|
|
2899
|
+
continue;
|
|
2900
|
+
}
|
|
2901
|
+
const absDir = path6.join(this.rootDir, dir);
|
|
2902
|
+
try {
|
|
2903
|
+
const siblings = fs6.readdirSync(absDir).filter((f) => f.endsWith(".go") && !f.endsWith("_test.go"));
|
|
2904
|
+
for (const sib of siblings) {
|
|
2905
|
+
const sibRel = path6.join(dir, sib).replace(/\\/g, "/");
|
|
2906
|
+
this.addEdge(relPath, sibRel);
|
|
2907
|
+
this.addEdge(sibRel, relPath);
|
|
2908
|
+
goTestEdgesAdded += 2;
|
|
2909
|
+
}
|
|
2910
|
+
} catch {
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
if (goTestEdgesAdded > 0) {
|
|
2914
|
+
logger.info("Go test\u2194source linkage added edges", { count: goTestEdgesAdded });
|
|
2915
|
+
}
|
|
2473
2916
|
await this.saveSnapshot();
|
|
2474
2917
|
logger.info("Graph built", { files: files.length, edges: this.edgeCount() });
|
|
2475
2918
|
if (options?.afterReady) {
|
|
@@ -2662,8 +3105,10 @@ var DependencyGraph = class {
|
|
|
2662
3105
|
for (const imp of importNodes) {
|
|
2663
3106
|
const spec = imp.source ?? imp.name;
|
|
2664
3107
|
const isRelative = spec.startsWith(".");
|
|
2665
|
-
const
|
|
2666
|
-
|
|
3108
|
+
const resolvedAll = isRelative ? goResolver.resolveRelativeAll(absPath, spec) : goResolver.resolveAll(spec);
|
|
3109
|
+
for (const resolved of resolvedAll) {
|
|
3110
|
+
this.addEdge(relPath, resolved);
|
|
3111
|
+
}
|
|
2667
3112
|
}
|
|
2668
3113
|
} else {
|
|
2669
3114
|
const importNodes = nodes.filter((n) => n.type === "import");
|
|
@@ -2684,7 +3129,7 @@ var DependencyGraph = class {
|
|
|
2684
3129
|
}
|
|
2685
3130
|
}
|
|
2686
3131
|
for (const node of nodes) {
|
|
2687
|
-
if (node.type === "function" || node.type === "class" || node.type === "interface") {
|
|
3132
|
+
if (node.type === "function" || node.type === "class" || node.type === "interface" || node.type === "method") {
|
|
2688
3133
|
const existing = this.symbolIndex.get(node.name) ?? [];
|
|
2689
3134
|
existing.push({
|
|
2690
3135
|
filePath: relPath,
|
|
@@ -2701,6 +3146,11 @@ var DependencyGraph = class {
|
|
|
2701
3146
|
for (const edge of callEdges) {
|
|
2702
3147
|
this.callGraphIndex.addEdge({ callerFile: relPath, ...edge });
|
|
2703
3148
|
}
|
|
3149
|
+
} else if (PY_EXTENSIONS.has(ext)) {
|
|
3150
|
+
const callEdges = await this.parser.parseAllPythonCallEdges(absPath);
|
|
3151
|
+
for (const edge of callEdges) {
|
|
3152
|
+
this.callGraphIndex.addEdge({ callerFile: relPath, ...edge });
|
|
3153
|
+
}
|
|
2704
3154
|
}
|
|
2705
3155
|
} else {
|
|
2706
3156
|
const content = fs6.readFileSync(absPath, "utf-8");
|
|
@@ -6133,7 +6583,7 @@ function buildHistoricalCouplingEntries(changedFiles, staticSet, overlay) {
|
|
|
6133
6583
|
const now = Math.floor(Date.now() / 1e3);
|
|
6134
6584
|
const coupling = [];
|
|
6135
6585
|
for (const seedFile of changedFiles) {
|
|
6136
|
-
const coupled = overlay.coChange.topFor({ node: seedFile, limit:
|
|
6586
|
+
const coupled = overlay.coChange.topFor({ node: seedFile, limit: 25, minConfidence: 0.1 });
|
|
6137
6587
|
for (const hit of coupled) {
|
|
6138
6588
|
const sibling = hit.nodeA === seedFile ? hit.nodeB : hit.nodeA;
|
|
6139
6589
|
if (!staticSet.has(sibling) && !coupling.some((h) => h.node === sibling)) {
|
|
@@ -6150,25 +6600,143 @@ function buildHistoricalCouplingEntries(changedFiles, staticSet, overlay) {
|
|
|
6150
6600
|
coupling.splice(10);
|
|
6151
6601
|
return coupling;
|
|
6152
6602
|
}
|
|
6603
|
+
function collectImportees(changedFiles, graph) {
|
|
6604
|
+
const changedSet = new Set(changedFiles);
|
|
6605
|
+
const importees = /* @__PURE__ */ new Set();
|
|
6606
|
+
for (const file of changedFiles) {
|
|
6607
|
+
for (const imp of graph.getImports(file)) {
|
|
6608
|
+
if (!changedSet.has(imp)) importees.add(imp);
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
return importees;
|
|
6612
|
+
}
|
|
6613
|
+
var SYMBOL_CALLERS_TOP_K = 25;
|
|
6614
|
+
var PATH_PROXIMITY_BONUS = 1;
|
|
6615
|
+
var SYMBOL_CALLERS_MIN_SCORE = 1;
|
|
6616
|
+
function pathProximityScore(callerFile, seedFile) {
|
|
6617
|
+
const lastSlash = seedFile.lastIndexOf("/");
|
|
6618
|
+
const lastDot = seedFile.lastIndexOf(".");
|
|
6619
|
+
const stem = seedFile.slice(
|
|
6620
|
+
lastSlash + 1,
|
|
6621
|
+
lastDot > lastSlash ? lastDot : seedFile.length
|
|
6622
|
+
);
|
|
6623
|
+
if (stem.length < 3) return 0;
|
|
6624
|
+
const shortPrefix = stem.slice(0, 3);
|
|
6625
|
+
const tokens = stem === shortPrefix ? [stem] : [stem, shortPrefix];
|
|
6626
|
+
const caller = callerFile.toLowerCase();
|
|
6627
|
+
for (const t of tokens) {
|
|
6628
|
+
const token = t.toLowerCase();
|
|
6629
|
+
const re = new RegExp(`(?:^|[/_.\\-])${token}(?:[/_.\\-]|$)`);
|
|
6630
|
+
if (re.test(caller)) return PATH_PROXIMITY_BONUS;
|
|
6631
|
+
}
|
|
6632
|
+
return 0;
|
|
6633
|
+
}
|
|
6634
|
+
function collectSymbolCallers(changedFiles, graph) {
|
|
6635
|
+
const changedSet = new Set(changedFiles);
|
|
6636
|
+
const callGraph = graph.getCallGraphIndex();
|
|
6637
|
+
const callerScores = /* @__PURE__ */ new Map();
|
|
6638
|
+
const callersWithProximityApplied = /* @__PURE__ */ new Set();
|
|
6639
|
+
for (const file of changedFiles) {
|
|
6640
|
+
const symbols = graph.lookupSymbolsByFile(file);
|
|
6641
|
+
for (const sym of symbols) {
|
|
6642
|
+
const defs = graph.lookupSymbol(sym);
|
|
6643
|
+
const definedInSeed = defs.some((d) => changedSet.has(d.filePath));
|
|
6644
|
+
if (!definedInSeed) continue;
|
|
6645
|
+
const specificity = 1 / defs.length;
|
|
6646
|
+
const seenForSym = /* @__PURE__ */ new Set();
|
|
6647
|
+
for (const caller of callGraph.getCallers(sym)) {
|
|
6648
|
+
if (changedSet.has(caller.file)) continue;
|
|
6649
|
+
if (seenForSym.has(caller.file)) continue;
|
|
6650
|
+
seenForSym.add(caller.file);
|
|
6651
|
+
const prev = callerScores.get(caller.file) ?? 0;
|
|
6652
|
+
const next = prev + specificity;
|
|
6653
|
+
callerScores.set(caller.file, next);
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
for (const callerFile of callerScores.keys()) {
|
|
6657
|
+
if (callersWithProximityApplied.has(callerFile)) continue;
|
|
6658
|
+
const bonus = pathProximityScore(callerFile, file);
|
|
6659
|
+
if (bonus > 0) {
|
|
6660
|
+
callerScores.set(callerFile, (callerScores.get(callerFile) ?? 0) + bonus);
|
|
6661
|
+
}
|
|
6662
|
+
callersWithProximityApplied.add(callerFile);
|
|
6663
|
+
}
|
|
6664
|
+
}
|
|
6665
|
+
const ranked = Array.from(callerScores.entries()).filter(([, score]) => score >= SYMBOL_CALLERS_MIN_SCORE).sort((a, b) => {
|
|
6666
|
+
if (b[1] !== a[1]) return b[1] - a[1];
|
|
6667
|
+
return a[0].localeCompare(b[0]);
|
|
6668
|
+
});
|
|
6669
|
+
return new Set(ranked.slice(0, SYMBOL_CALLERS_TOP_K).map(([file]) => file));
|
|
6670
|
+
}
|
|
6671
|
+
var SEMANTIC_TOP_K = 10;
|
|
6672
|
+
var SEMANTIC_DIST_THRESHOLD = 0.5;
|
|
6673
|
+
async function computeSemanticSimilar(changedFiles, vectorStore, staticSet) {
|
|
6674
|
+
return collectSemanticSimilar(changedFiles, vectorStore, staticSet);
|
|
6675
|
+
}
|
|
6676
|
+
async function collectSemanticSimilar(changedFiles, vectorStore, staticSet) {
|
|
6677
|
+
const changedSet = new Set(changedFiles);
|
|
6678
|
+
const scores = /* @__PURE__ */ new Map();
|
|
6679
|
+
for (const seedFile of changedFiles) {
|
|
6680
|
+
let embedding;
|
|
6681
|
+
try {
|
|
6682
|
+
embedding = await vectorStore.findEmbeddingByPath(seedFile);
|
|
6683
|
+
} catch {
|
|
6684
|
+
continue;
|
|
6685
|
+
}
|
|
6686
|
+
if (embedding === null) continue;
|
|
6687
|
+
let results;
|
|
6688
|
+
try {
|
|
6689
|
+
results = await vectorStore.search(embedding, SEMANTIC_TOP_K + 5);
|
|
6690
|
+
} catch {
|
|
6691
|
+
continue;
|
|
6692
|
+
}
|
|
6693
|
+
for (const r of results) {
|
|
6694
|
+
if (changedSet.has(r.filePath)) continue;
|
|
6695
|
+
if (staticSet.has(r.filePath)) continue;
|
|
6696
|
+
if (r.score >= SEMANTIC_DIST_THRESHOLD) continue;
|
|
6697
|
+
const prev = scores.get(r.filePath);
|
|
6698
|
+
if (prev === void 0 || r.score < prev) {
|
|
6699
|
+
scores.set(r.filePath, r.score);
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
return Array.from(scores.entries()).sort((a, b) => a[1] - b[1]).slice(0, SEMANTIC_TOP_K).map(([node, score]) => ({ node, score }));
|
|
6704
|
+
}
|
|
6153
6705
|
function getImpactRadius(input) {
|
|
6154
|
-
const {
|
|
6706
|
+
const {
|
|
6707
|
+
graph,
|
|
6708
|
+
overlay,
|
|
6709
|
+
changedFiles,
|
|
6710
|
+
depth = 3,
|
|
6711
|
+
includeImportees = false,
|
|
6712
|
+
includeSymbolCallers = false
|
|
6713
|
+
} = input;
|
|
6155
6714
|
const { directImporters, allReachable } = traverseImporters(changedFiles, graph, depth);
|
|
6156
6715
|
const transitiveImporters = [];
|
|
6157
6716
|
for (const file of allReachable) {
|
|
6158
6717
|
if (!directImporters.has(file)) transitiveImporters.push(file);
|
|
6159
6718
|
}
|
|
6719
|
+
const importeesSet = includeImportees ? collectImportees(changedFiles, graph) : /* @__PURE__ */ new Set();
|
|
6720
|
+
const directImportees = Array.from(importeesSet);
|
|
6721
|
+
const symbolCallersSet = includeSymbolCallers ? collectSymbolCallers(changedFiles, graph) : /* @__PURE__ */ new Set();
|
|
6722
|
+
const symbolCallers = Array.from(symbolCallersSet);
|
|
6160
6723
|
const staticSet = /* @__PURE__ */ new Set([
|
|
6161
6724
|
...changedFiles,
|
|
6162
6725
|
...directImporters,
|
|
6163
|
-
...transitiveImporters
|
|
6726
|
+
...transitiveImporters,
|
|
6727
|
+
...directImportees,
|
|
6728
|
+
...symbolCallers
|
|
6164
6729
|
]);
|
|
6165
6730
|
const historicalCoupling = overlay !== void 0 ? buildHistoricalCouplingEntries(changedFiles, staticSet, overlay) : [];
|
|
6166
|
-
const totalImpacted = directImporters.size + transitiveImporters.length;
|
|
6731
|
+
const totalImpacted = directImporters.size + transitiveImporters.length + directImportees.length + symbolCallers.length;
|
|
6167
6732
|
return {
|
|
6168
6733
|
seedFiles: [...changedFiles],
|
|
6169
6734
|
directImporters: Array.from(directImporters),
|
|
6170
6735
|
transitiveImporters,
|
|
6736
|
+
directImportees,
|
|
6737
|
+
symbolCallers,
|
|
6171
6738
|
historicalCoupling,
|
|
6739
|
+
semanticSimilar: [],
|
|
6172
6740
|
totalImpacted
|
|
6173
6741
|
};
|
|
6174
6742
|
}
|
|
@@ -8072,7 +8640,7 @@ function registerFullTextSearchTool(registry, ctx) {
|
|
|
8072
8640
|
};
|
|
8073
8641
|
if (mode === "semantic") {
|
|
8074
8642
|
try {
|
|
8075
|
-
const { generateEmbedding: generateEmbedding2 } = await import("./embedder-
|
|
8643
|
+
const { generateEmbedding: generateEmbedding2 } = await import("./embedder-2JWDJUE2.js");
|
|
8076
8644
|
const store = await ctx.getStore(project_root);
|
|
8077
8645
|
const embedding = await generateEmbedding2(query);
|
|
8078
8646
|
const results = await store.search(embedding, limit);
|
|
@@ -8109,7 +8677,7 @@ function registerFullTextSearchTool(registry, ctx) {
|
|
|
8109
8677
|
let merged = keywordResults.slice(0, limit);
|
|
8110
8678
|
if (mode === "hybrid") {
|
|
8111
8679
|
try {
|
|
8112
|
-
const { generateEmbedding: generateEmbedding2 } = await import("./embedder-
|
|
8680
|
+
const { generateEmbedding: generateEmbedding2 } = await import("./embedder-2JWDJUE2.js");
|
|
8113
8681
|
const store = await ctx.getStore(project_root);
|
|
8114
8682
|
const embedding = await generateEmbedding2(query);
|
|
8115
8683
|
const vectorResults = await store.search(embedding, Math.ceil(limit / 2));
|
|
@@ -9981,16 +10549,14 @@ var PathValidator = class {
|
|
|
9981
10549
|
|
|
9982
10550
|
// packages/core/src/watcher/FileWatcher.ts
|
|
9983
10551
|
import chokidar from "chokidar";
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
"**/.cache/**"
|
|
9993
|
-
];
|
|
10552
|
+
function isIgnoredPath(absPath) {
|
|
10553
|
+
const segments = absPath.split(/[\\/]/);
|
|
10554
|
+
for (const seg of segments) {
|
|
10555
|
+
if (INDEXER_IGNORED_DIRS.has(seg)) return true;
|
|
10556
|
+
}
|
|
10557
|
+
return false;
|
|
10558
|
+
}
|
|
10559
|
+
var IGNORED = isIgnoredPath;
|
|
9994
10560
|
var FileWatcher = class {
|
|
9995
10561
|
root;
|
|
9996
10562
|
onChange;
|
|
@@ -10346,10 +10912,10 @@ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
|
|
|
10346
10912
|
function getTelemetryLevel() {
|
|
10347
10913
|
return TELEMETRY_LEVEL;
|
|
10348
10914
|
}
|
|
10349
|
-
var CTXLOOM_VERSION = "1.
|
|
10915
|
+
var CTXLOOM_VERSION = "1.7.0".length > 0 ? "1.7.0" : "dev";
|
|
10350
10916
|
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
10351
10917
|
var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
|
|
10352
|
-
var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528
|
|
10918
|
+
var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
|
|
10353
10919
|
var cachedDistinctId = null;
|
|
10354
10920
|
function resolveDistinctId() {
|
|
10355
10921
|
if (cachedDistinctId) return cachedDistinctId;
|
|
@@ -10984,6 +11550,27 @@ The graph is faster, cheaper (fewer tokens), and gives you
|
|
|
10984
11550
|
structural context (callers, dependents, test coverage) that file
|
|
10985
11551
|
scanning cannot.
|
|
10986
11552
|
|
|
11553
|
+
### Operating principles
|
|
11554
|
+
|
|
11555
|
+
ctxloom's tools exist to operationalize four principles for working
|
|
11556
|
+
with an AI coding agent. They're the *why* behind every tool below.
|
|
11557
|
+
Adapted from Karpathy's LLM-coding-pitfalls notes
|
|
11558
|
+
(<https://github.com/multica-ai/andrej-karpathy-skills>, MIT).
|
|
11559
|
+
|
|
11560
|
+
1. **Think before coding** \u2014 read the relevant graph slice before
|
|
11561
|
+
editing. \`ctx_blast_radius\`, \`ctx_get_call_graph\`,
|
|
11562
|
+
\`ctx_get_review_context\` are how you do this without re-reading
|
|
11563
|
+
whole files.
|
|
11564
|
+
2. **Simplicity first** \u2014 prefer the smallest viable change. Use
|
|
11565
|
+
\`ctx_refactor_preview\` to see the full diff *before* applying;
|
|
11566
|
+
if the preview is sprawling, the plan is too big.
|
|
11567
|
+
3. **Surgical changes** \u2014 every changed line should trace directly
|
|
11568
|
+
to the user's request. \`ctx_detect_changes\` after each edit
|
|
11569
|
+
confirms scope hasn't drifted.
|
|
11570
|
+
4. **Goal-driven execution** \u2014 stop when the goal is met. Don't
|
|
11571
|
+
"polish" beyond the request. \`ctx_knowledge_gaps\` flags real
|
|
11572
|
+
risk surfaces; everything else is yak-shaving.
|
|
11573
|
+
|
|
10987
11574
|
### Start every workflow with \`ctx_get_minimal_context\`
|
|
10988
11575
|
|
|
10989
11576
|
The first MCP call into ctxloom should always be
|
|
@@ -11229,9 +11816,18 @@ description: Orient yourself to an unfamiliar codebase using ctxloom's structura
|
|
|
11229
11816
|
|
|
11230
11817
|
# Explore Codebase
|
|
11231
11818
|
|
|
11819
|
+
**Principle: Think Before Coding.** Exploration is "think" with a
|
|
11820
|
+
budget. The trap is reading every file in sight; the discipline is
|
|
11821
|
+
to read the *graph*, then the few files the graph nominates. This
|
|
11822
|
+
skill caps you at 5 tool calls and 2000 tokens \u2014 enough to ground
|
|
11823
|
+
the next edit, not enough to procrastinate.
|
|
11824
|
+
|
|
11232
11825
|
Use this when you need to understand a codebase you haven't worked in
|
|
11233
11826
|
before, or when re-orienting after time away.
|
|
11234
11827
|
|
|
11828
|
+
**Skip when:** you already know which files matter \u2014 just open them.
|
|
11829
|
+
Reserve this skill for "I don't know where to start" situations.
|
|
11830
|
+
|
|
11235
11831
|
## Steps
|
|
11236
11832
|
|
|
11237
11833
|
1. **Orientation anchor**: call \`ctx_get_minimal_context(task="explore this codebase")\`.
|
|
@@ -11280,9 +11876,17 @@ argument-hint: "<symbol-name | file-path>"
|
|
|
11280
11876
|
|
|
11281
11877
|
# Blast Radius
|
|
11282
11878
|
|
|
11879
|
+
**Principle: Think Before Coding.** You can't make a surgical change
|
|
11880
|
+
if you don't know who depends on the symbol. This skill is the
|
|
11881
|
+
"think" phase \u2014 read the graph slice before editing anything.
|
|
11882
|
+
|
|
11283
11883
|
Use this before any change to a public function, type, or file
|
|
11284
11884
|
where you're not sure who depends on it.
|
|
11285
11885
|
|
|
11886
|
+
**Skip when:** the change is purely internal to a private helper
|
|
11887
|
+
with no callers outside the file, OR you've already run this skill
|
|
11888
|
+
for the same symbol in the current task.
|
|
11889
|
+
|
|
11286
11890
|
## Inputs
|
|
11287
11891
|
|
|
11288
11892
|
- \`$ARGUMENTS\` \u2014 the symbol name (e.g. \`emitTelemetry\`) or file
|
|
@@ -11331,9 +11935,18 @@ argument-hint: "<old-name> <new-name>"
|
|
|
11331
11935
|
|
|
11332
11936
|
# Refactor Safely
|
|
11333
11937
|
|
|
11938
|
+
**Principle: Surgical Changes.** A rename touches every call site \u2014
|
|
11939
|
+
done blindly it's the opposite of surgical. This skill enforces
|
|
11940
|
+
"see the full diff before applying" so the change stays scoped to
|
|
11941
|
+
the user's request and doesn't accidentally rewrite tangents.
|
|
11942
|
+
|
|
11334
11943
|
Use this for renames, signature changes, or function moves. The
|
|
11335
11944
|
skill enforces preview-before-apply.
|
|
11336
11945
|
|
|
11946
|
+
**Skip when:** the symbol is local to a single file (just edit it),
|
|
11947
|
+
OR the user wants a behavior change, not a rename (use the regular
|
|
11948
|
+
edit flow with \`ctx_blast_radius\` for impact awareness).
|
|
11949
|
+
|
|
11337
11950
|
## Inputs
|
|
11338
11951
|
|
|
11339
11952
|
- \`$1\` \u2014 current symbol name (e.g. \`emitTelemetry\`)
|
|
@@ -11386,9 +11999,18 @@ description: Identify code that lacks test coverage, prioritized by caller frequ
|
|
|
11386
11999
|
|
|
11387
12000
|
# Coverage Gap Analysis
|
|
11388
12001
|
|
|
12002
|
+
**Principle: Goal-Driven Execution.** "Add tests everywhere" is
|
|
12003
|
+
yak-shaving. The goal is to add tests where they pay back the
|
|
12004
|
+
investment: high-caller, high-churn, low-coverage code. This skill
|
|
12005
|
+
ranks gaps so you write tests with intent, not by reflex.
|
|
12006
|
+
|
|
11389
12007
|
Use this to find untested code that genuinely matters \u2014 the
|
|
11390
12008
|
intersection of "no tests" + "many callers" + "high risk score."
|
|
11391
12009
|
|
|
12010
|
+
**Skip when:** the user asked for tests on a specific symbol \u2014
|
|
12011
|
+
write them directly. Reserve this skill for "where should we
|
|
12012
|
+
invest in tests next?" queries.
|
|
12013
|
+
|
|
11392
12014
|
## Steps
|
|
11393
12015
|
|
|
11394
12016
|
1. **Orientation**: call \`ctx_get_minimal_context(task="check test coverage")\`.
|
|
@@ -11436,10 +12058,20 @@ argument-hint: "<PR number | branch name>"
|
|
|
11436
12058
|
|
|
11437
12059
|
# Review PR
|
|
11438
12060
|
|
|
12061
|
+
**Principle: Think Before Coding.** A code review *is* the "think"
|
|
12062
|
+
phase for the author and the reviewer alike. This skill structures
|
|
12063
|
+
that thinking around graph slices rather than naive file-by-file
|
|
12064
|
+
reading, so you spot blast-radius and coverage risks the diff alone
|
|
12065
|
+
hides.
|
|
12066
|
+
|
|
11439
12067
|
Comprehensive PR review using ctxloom's graph. Mirrors the
|
|
11440
12068
|
multi-agent review the ctxloom-bot posts automatically \u2014 useful
|
|
11441
12069
|
when reviewing manually or when the bot isn't wired up.
|
|
11442
12070
|
|
|
12071
|
+
**Skip when:** the PR is one-file, \u226430 lines, with full test
|
|
12072
|
+
coverage \u2014 read the diff directly. Reserve this skill for changes
|
|
12073
|
+
where the structural impact isn't obvious from the diff.
|
|
12074
|
+
|
|
11443
12075
|
## Inputs
|
|
11444
12076
|
|
|
11445
12077
|
- \`$ARGUMENTS\` \u2014 PR number (e.g. \`142\`) or branch name (e.g. \`feat/foo\`).
|
|
@@ -11527,6 +12159,11 @@ description: Inspect ctxloom's per-tool budget telemetry \u2014 fallback distrib
|
|
|
11527
12159
|
|
|
11528
12160
|
# Budget Stats
|
|
11529
12161
|
|
|
12162
|
+
**Principle: Simplicity First.** Don't guess what \`DEFAULT_MAX_RESPONSE_TOKENS\`
|
|
12163
|
+
should be \u2014 measure. This skill turns real telemetry into the
|
|
12164
|
+
simplest viable constant: the p75 of actual usage. Tune what hurts;
|
|
12165
|
+
leave the rest alone.
|
|
12166
|
+
|
|
11530
12167
|
Wrapper around \`ctxloom budget-stats\` for inline use inside a
|
|
11531
12168
|
Claude Code session. Useful when:
|
|
11532
12169
|
|
|
@@ -11535,6 +12172,10 @@ Claude Code session. Useful when:
|
|
|
11535
12172
|
- Diagnosing why a tool keeps falling back to skeleton mode
|
|
11536
12173
|
- Understanding which tools dominate the user's token budget
|
|
11537
12174
|
|
|
12175
|
+
**Skip when:** there's no budget complaint to investigate \u2014 telemetry
|
|
12176
|
+
exists for tuning, not browsing. Reserve this skill for "this tool
|
|
12177
|
+
keeps hitting the budget" or scheduled tuning passes.
|
|
12178
|
+
|
|
11538
12179
|
## Steps
|
|
11539
12180
|
|
|
11540
12181
|
1. **Orientation**: call \`ctx_get_minimal_context(task="inspect budget telemetry")\`.
|
|
@@ -11862,6 +12503,7 @@ export {
|
|
|
11862
12503
|
emitTaskBudgetBreached,
|
|
11863
12504
|
ToolRegistry,
|
|
11864
12505
|
detectChanges,
|
|
12506
|
+
computeSemanticSimilar,
|
|
11865
12507
|
getImpactRadius,
|
|
11866
12508
|
buildBlastRadiusXml,
|
|
11867
12509
|
validateAlias,
|
|
@@ -11946,4 +12588,4 @@ export {
|
|
|
11946
12588
|
skillFilePath,
|
|
11947
12589
|
installHarness
|
|
11948
12590
|
};
|
|
11949
|
-
//# sourceMappingURL=chunk-
|
|
12591
|
+
//# sourceMappingURL=chunk-FFCLVZCO.js.map
|