ctxloom-pro 1.5.5 → 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 +653 -130
- package/apps/dashboard/package.json +2 -2
- package/dist/VectorStore-WDL3H7QT.js +9 -0
- package/dist/chunk-6FGTNOCP.js +397 -0
- package/dist/{chunk-R56D54Y7.js → chunk-7S2ELKNU.js} +132 -4
- package/dist/{chunk-5R4P7VEE.js → chunk-FFCLVZCO.js} +913 -214
- package/dist/{chunk-II2DPYRJ.js → chunk-YHLMQVBV.js} +200 -10
- package/dist/embedder-2JWDJUE2.js +26 -0
- package/dist/index.js +139 -22
- package/dist/setup/postinstall.js +1 -1
- package/dist/{src-QMDQDATD.js → src-QAYZWPSL.js} +10 -4
- package/dist/workers/indexerWorker.js +2 -2
- package/package.json +1 -1
- package/README.md.bak +0 -832
- package/dist/VectorStore-4VWT2ZMW.js +0 -8
- package/dist/chunk-COH5WYZS.js +0 -214
- package/dist/embedder-7YOG4DFN.js +0 -14
|
@@ -79,6 +79,27 @@ var init_logger = __esm({
|
|
|
79
79
|
// ../../packages/core/src/indexer/embedder.ts
|
|
80
80
|
import fs3 from "fs";
|
|
81
81
|
import path3 from "path";
|
|
82
|
+
function resolveEmbeddingModel(env = process.env) {
|
|
83
|
+
const envModel = env.CTXLOOM_EMBEDDING_MODEL?.trim();
|
|
84
|
+
if (!envModel) return MODEL_REGISTRY.minilm;
|
|
85
|
+
const registered = MODEL_REGISTRY[envModel];
|
|
86
|
+
if (registered) return registered;
|
|
87
|
+
const envDim = env.CTXLOOM_EMBEDDING_DIM ? Number.parseInt(env.CTXLOOM_EMBEDDING_DIM, 10) : NaN;
|
|
88
|
+
if (!Number.isFinite(envDim) || envDim <= 0) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`CTXLOOM_EMBEDDING_MODEL=${envModel} is not a known alias. Either use one of [${Object.keys(MODEL_REGISTRY).join(", ")}] or set CTXLOOM_EMBEDDING_DIM=<vector-length> alongside a raw HF id.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
hfId: envModel,
|
|
95
|
+
dim: envDim,
|
|
96
|
+
// Without a known artifact size we can't enforce the truncated-download
|
|
97
|
+
// guard. Use 1 MB as the minimum; the worst case is a redundant retry
|
|
98
|
+
// rather than a hung process.
|
|
99
|
+
minBytes: 1024 * 1024,
|
|
100
|
+
description: `User-supplied model: ${envModel} (${envDim}-dim)`
|
|
101
|
+
};
|
|
102
|
+
}
|
|
82
103
|
function collectFiles(dir, results = []) {
|
|
83
104
|
const IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
84
105
|
// Build artifacts + dependency caches
|
|
@@ -144,12 +165,37 @@ function collectFiles(dir, results = []) {
|
|
|
144
165
|
}
|
|
145
166
|
return results;
|
|
146
167
|
}
|
|
147
|
-
var MIN_MODEL_BYTES;
|
|
168
|
+
var MODEL_REGISTRY, ACTIVE_MODEL, EMBEDDING_DIMENSION, MODEL_ID, MIN_MODEL_BYTES;
|
|
148
169
|
var init_embedder = __esm({
|
|
149
170
|
"../../packages/core/src/indexer/embedder.ts"() {
|
|
150
171
|
"use strict";
|
|
151
172
|
init_logger();
|
|
152
|
-
|
|
173
|
+
MODEL_REGISTRY = {
|
|
174
|
+
// The historical default. General English, 384-dim, ~90 MB.
|
|
175
|
+
// Kept as the free-tier default so existing users see zero change.
|
|
176
|
+
minilm: {
|
|
177
|
+
hfId: "sentence-transformers/all-MiniLM-L6-v2",
|
|
178
|
+
dim: 384,
|
|
179
|
+
minBytes: 80 * 1024 * 1024,
|
|
180
|
+
description: "General-purpose English sentence embedder (2020). 384-dim. The legacy default."
|
|
181
|
+
},
|
|
182
|
+
// Code-specific embedding model (Jina AI). 768-dim, ~140 MB.
|
|
183
|
+
// Empirically 20-40% better recall on code-similarity queries than
|
|
184
|
+
// MiniLM — the upgrade path recommended in the v1.7.0 analysis.
|
|
185
|
+
// Runs through the same @huggingface/transformers pipeline so the
|
|
186
|
+
// privacy story (fully local, no network at inference time) is
|
|
187
|
+
// preserved.
|
|
188
|
+
"jina-code": {
|
|
189
|
+
hfId: "jinaai/jina-embeddings-v2-base-code",
|
|
190
|
+
dim: 768,
|
|
191
|
+
minBytes: 130 * 1024 * 1024,
|
|
192
|
+
description: "Code-specific embedder (Jina, 2024). 768-dim. Better recall on code-similarity tasks."
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
ACTIVE_MODEL = resolveEmbeddingModel();
|
|
196
|
+
EMBEDDING_DIMENSION = ACTIVE_MODEL.dim;
|
|
197
|
+
MODEL_ID = ACTIVE_MODEL.hfId;
|
|
198
|
+
MIN_MODEL_BYTES = ACTIVE_MODEL.minBytes;
|
|
153
199
|
}
|
|
154
200
|
});
|
|
155
201
|
|
|
@@ -160,18 +206,19 @@ var init_VectorStore = __esm({
|
|
|
160
206
|
"../../packages/core/src/db/VectorStore.ts"() {
|
|
161
207
|
"use strict";
|
|
162
208
|
init_logger();
|
|
209
|
+
init_embedder();
|
|
163
210
|
}
|
|
164
211
|
});
|
|
165
212
|
|
|
166
213
|
// server/index.ts
|
|
167
214
|
import express from "express";
|
|
168
215
|
import cors from "cors";
|
|
169
|
-
import
|
|
170
|
-
import
|
|
216
|
+
import path47 from "path";
|
|
217
|
+
import fs35 from "fs";
|
|
171
218
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
172
219
|
|
|
173
220
|
// server/loader.ts
|
|
174
|
-
import
|
|
221
|
+
import path41 from "path";
|
|
175
222
|
|
|
176
223
|
// ../../packages/core/src/graph/DependencyGraph.ts
|
|
177
224
|
import fs7 from "fs";
|
|
@@ -657,6 +704,13 @@ var ASTParser = class {
|
|
|
657
704
|
extractTSNodes(rootNode, _filePath, lines) {
|
|
658
705
|
const nodes = [];
|
|
659
706
|
const processedIds = /* @__PURE__ */ new Set();
|
|
707
|
+
const hasCallableRight = (n) => {
|
|
708
|
+
const right = n.childForFieldName?.("right") ?? n.children[n.children.length - 1];
|
|
709
|
+
if (!right) return false;
|
|
710
|
+
if (right.type === "function" || right.type === "function_expression" || right.type === "arrow_function" || right.type === "function_declaration") return true;
|
|
711
|
+
if (right.type === "assignment_expression") return hasCallableRight(right);
|
|
712
|
+
return false;
|
|
713
|
+
};
|
|
660
714
|
const walk = (node) => {
|
|
661
715
|
if (processedIds.has(node.id)) return;
|
|
662
716
|
switch (node.type) {
|
|
@@ -828,6 +882,56 @@ var ASTParser = class {
|
|
|
828
882
|
processedIds.add(node.id);
|
|
829
883
|
return;
|
|
830
884
|
}
|
|
885
|
+
// ─── Prototype / object method assignments ──────────────────────
|
|
886
|
+
// CommonJS libraries (and pre-class-syntax ES) attach their public
|
|
887
|
+
// API via assignment expressions:
|
|
888
|
+
//
|
|
889
|
+
// res.send = function send(body) { ... }
|
|
890
|
+
// res.json = function (obj) { ... }
|
|
891
|
+
// res.contentType = res.type = function (type) { ... } // chained
|
|
892
|
+
// exports.foo = function foo() { ... }
|
|
893
|
+
// MyClass.prototype.bar = function () { ... }
|
|
894
|
+
//
|
|
895
|
+
// Without this case, none of those names enter the symbol index,
|
|
896
|
+
// so `lookupSymbolsByFile()` returns empty for libraries like
|
|
897
|
+
// express, and any downstream tool that wants to attribute callers
|
|
898
|
+
// to a file (blast-radius symbolCallers, ctx_get_definition, etc.)
|
|
899
|
+
// falls flat. The call graph itself already records callers of
|
|
900
|
+
// `send`/`json`/etc. — this case bridges the gap so we can match
|
|
901
|
+
// them back to the file that defines them.
|
|
902
|
+
//
|
|
903
|
+
// Heuristic:
|
|
904
|
+
// - left = member_expression → use the FINAL property as symbol
|
|
905
|
+
// - right = function | arrow_function | function_expression
|
|
906
|
+
// - right = assignment_expression → recurse for chained pattern
|
|
907
|
+
// Anything else (constants, identifiers being aliased) is skipped
|
|
908
|
+
// intentionally — those aren't callable API surface.
|
|
909
|
+
case "assignment_expression": {
|
|
910
|
+
const left = node.childForFieldName?.("left") ?? node.children.find(
|
|
911
|
+
(c) => c?.type === "member_expression" || c?.type === "identifier"
|
|
912
|
+
);
|
|
913
|
+
const right = node.childForFieldName?.("right") ?? node.children[node.children.length - 1];
|
|
914
|
+
if (left?.type === "member_expression" && right) {
|
|
915
|
+
const prop = left.childForFieldName?.("property") ?? left.children[left.children.length - 1];
|
|
916
|
+
const propName = prop?.text;
|
|
917
|
+
if (right.type === "assignment_expression") {
|
|
918
|
+
walk(right);
|
|
919
|
+
}
|
|
920
|
+
const rightIsCallable = right.type === "function" || right.type === "function_expression" || right.type === "arrow_function" || right.type === "function_declaration" || right.type === "assignment_expression" && hasCallableRight(right);
|
|
921
|
+
if (propName && rightIsCallable && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName)) {
|
|
922
|
+
const sig = lines[node.startPosition.row] ?? "";
|
|
923
|
+
nodes.push({
|
|
924
|
+
type: "method",
|
|
925
|
+
name: propName,
|
|
926
|
+
signature: sig.trim().slice(0, 200),
|
|
927
|
+
startLine: node.startPosition.row + 1,
|
|
928
|
+
endLine: node.endPosition.row + 1
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
processedIds.add(node.id);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
831
935
|
// ─── Lexical declarations (const fn = () => {}) ────────────────
|
|
832
936
|
case "lexical_declaration": {
|
|
833
937
|
for (const child of node.children) {
|
|
@@ -934,10 +1038,34 @@ var ASTParser = class {
|
|
|
934
1038
|
}
|
|
935
1039
|
case "import_from_statement": {
|
|
936
1040
|
const moduleNode = node.children.find((c) => c?.type === "dotted_name" || c?.type === "relative_import");
|
|
1041
|
+
const sourceText = moduleNode?.text ?? "";
|
|
1042
|
+
const importedNames = [];
|
|
1043
|
+
let pastImportKeyword = false;
|
|
1044
|
+
for (const child of node.children) {
|
|
1045
|
+
if (!child) continue;
|
|
1046
|
+
if (child.text === "import" && (child.type === "import" || child.children.length === 0)) {
|
|
1047
|
+
pastImportKeyword = true;
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
if (!pastImportKeyword) continue;
|
|
1051
|
+
if (child.type === "wildcard_import") continue;
|
|
1052
|
+
if (child.type === "dotted_name") {
|
|
1053
|
+
importedNames.push(child.text);
|
|
1054
|
+
} else if (child.type === "aliased_import") {
|
|
1055
|
+
const alias = child.childForFieldName?.("alias");
|
|
1056
|
+
const aliasName = alias?.text;
|
|
1057
|
+
if (aliasName) importedNames.push(aliasName);
|
|
1058
|
+
else {
|
|
1059
|
+
const name = child.childForFieldName?.("name")?.text;
|
|
1060
|
+
if (name) importedNames.push(name);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
937
1064
|
nodes.push({
|
|
938
1065
|
type: "import",
|
|
939
|
-
name:
|
|
940
|
-
source:
|
|
1066
|
+
name: sourceText,
|
|
1067
|
+
source: sourceText,
|
|
1068
|
+
importedNames: importedNames.length > 0 ? importedNames : void 0,
|
|
941
1069
|
startLine: node.startPosition.row + 1,
|
|
942
1070
|
endLine: node.endPosition.row + 1
|
|
943
1071
|
});
|
|
@@ -1807,6 +1935,78 @@ var ASTParser = class {
|
|
|
1807
1935
|
tree.delete();
|
|
1808
1936
|
}
|
|
1809
1937
|
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Extract all call edges in a Python (.py / .ipynb) file. Mirrors
|
|
1940
|
+
* `parseAllCallEdges` but uses tree-sitter-python node types.
|
|
1941
|
+
*
|
|
1942
|
+
* Tree-sitter-python relevant shapes:
|
|
1943
|
+
* - call.function = identifier "foo" → callee = "foo"
|
|
1944
|
+
* - call.function = attribute (obj.method) → callee = "method"
|
|
1945
|
+
* - call.function = call (chained) → recurse into inner
|
|
1946
|
+
*
|
|
1947
|
+
* Enclosing-context tracking follows the same pattern: track the
|
|
1948
|
+
* innermost `function_definition`. Methods inside a class are also
|
|
1949
|
+
* function_definitions, so the same handler covers them.
|
|
1950
|
+
*/
|
|
1951
|
+
async parseAllPythonCallEdges(filePath) {
|
|
1952
|
+
if (!this.pyLang) await this.loadPython();
|
|
1953
|
+
if (!this.pyLang) return [];
|
|
1954
|
+
const parser = this.getParser(this.pyLang);
|
|
1955
|
+
let source;
|
|
1956
|
+
try {
|
|
1957
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
1958
|
+
source = filePath.endsWith(".ipynb") ? extractNotebookPythonSource(raw) : raw;
|
|
1959
|
+
} catch {
|
|
1960
|
+
return [];
|
|
1961
|
+
}
|
|
1962
|
+
if (!source.trim()) return [];
|
|
1963
|
+
const tree = parser.parse(source);
|
|
1964
|
+
if (!tree) return [];
|
|
1965
|
+
const results = [];
|
|
1966
|
+
const extractCalleeName = (fn) => {
|
|
1967
|
+
if (fn.type === "identifier") return fn.text;
|
|
1968
|
+
if (fn.type === "attribute") {
|
|
1969
|
+
const right = fn.childForFieldName?.("attribute") ?? fn.children[fn.children.length - 1];
|
|
1970
|
+
return right?.text ?? "";
|
|
1971
|
+
}
|
|
1972
|
+
if (fn.type === "call") {
|
|
1973
|
+
const innerFn = fn.childForFieldName?.("function");
|
|
1974
|
+
return innerFn ? extractCalleeName(innerFn) : "";
|
|
1975
|
+
}
|
|
1976
|
+
return "";
|
|
1977
|
+
};
|
|
1978
|
+
const walk = (node, contextStack) => {
|
|
1979
|
+
let newStack = contextStack;
|
|
1980
|
+
if (node.type === "function_definition") {
|
|
1981
|
+
const nameNode = node.childForFieldName?.("name");
|
|
1982
|
+
if (nameNode?.text) {
|
|
1983
|
+
newStack = [...contextStack, nameNode.text];
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
if (node.type === "call") {
|
|
1987
|
+
const fn = node.childForFieldName?.("function") ?? node.children.find((c) => c?.type === "identifier" || c?.type === "attribute" || c?.type === "call");
|
|
1988
|
+
if (fn) {
|
|
1989
|
+
const name = extractCalleeName(fn);
|
|
1990
|
+
if (name && name.length > 0) {
|
|
1991
|
+
results.push({
|
|
1992
|
+
callerSymbol: newStack[newStack.length - 1] ?? "",
|
|
1993
|
+
calleeSymbol: name,
|
|
1994
|
+
line: node.startPosition.row + 1
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
for (const child of node.children) {
|
|
2000
|
+
if (child) walk(child, newStack);
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
try {
|
|
2004
|
+
walk(tree.rootNode, []);
|
|
2005
|
+
return results;
|
|
2006
|
+
} finally {
|
|
2007
|
+
tree.delete();
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1810
2010
|
/**
|
|
1811
2011
|
* Extract all call edges in a TypeScript/TSX file.
|
|
1812
2012
|
* Tracks the enclosing function/method context for each call site.
|
|
@@ -1893,48 +2093,100 @@ var GoModuleResolver = class {
|
|
|
1893
2093
|
}
|
|
1894
2094
|
/**
|
|
1895
2095
|
* Resolve a module-path import (e.g. `github.com/myorg/myapp/internal/auth`)
|
|
1896
|
-
* to
|
|
2096
|
+
* to the FIRST relative project path (e.g. `internal/auth/auth.go`).
|
|
1897
2097
|
*
|
|
1898
2098
|
* Returns null for:
|
|
1899
2099
|
* - Third-party imports (different module prefix)
|
|
1900
2100
|
* - Relative imports (use resolveRelative() instead)
|
|
1901
2101
|
* - Imports where no .go files are found
|
|
2102
|
+
*
|
|
2103
|
+
* NOTE: A Go import imports a PACKAGE (a directory of .go files), not a
|
|
2104
|
+
* single file. Use `resolveAll()` to get every file in the package — that
|
|
2105
|
+
* matches Go's compile-unit semantics and is what the graph wants for
|
|
2106
|
+
* accurate reachability. `resolve()` is kept for back-compat callers
|
|
2107
|
+
* that only need a representative file.
|
|
1902
2108
|
*/
|
|
1903
2109
|
resolve(importPath) {
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2110
|
+
const all = this.resolveAll(importPath);
|
|
2111
|
+
return all[0] ?? null;
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Resolve a module-path import to ALL non-test .go files in the target
|
|
2115
|
+
* package directory. This matches Go's compile-unit semantics: a single
|
|
2116
|
+
* `import "github.com/foo/bar/pkg"` statement brings the entire `pkg/`
|
|
2117
|
+
* directory into the dependency graph — every exported symbol from
|
|
2118
|
+
* every .go file in that directory is accessible to the caller.
|
|
2119
|
+
*
|
|
2120
|
+
* Pre-fix the resolver returned only ONE file per import, which made
|
|
2121
|
+
* gin's `binding/` package (~20 files) appear to be a single file from
|
|
2122
|
+
* the graph's perspective. The bench's graphReachability on gin
|
|
2123
|
+
* collapsed to 0.32 because PRs that touched 4 files in `binding/`
|
|
2124
|
+
* had only ONE of them in the graph reach of the entry-point file.
|
|
2125
|
+
* Returning ALL package files fixes the structural model.
|
|
2126
|
+
*
|
|
2127
|
+
* Test files (_test.go) are intentionally excluded — they're not part
|
|
2128
|
+
* of a package's public API and aren't imported by callers. The
|
|
2129
|
+
* test↔source link is handled separately by per-directory sibling
|
|
2130
|
+
* edges (see DependencyGraph's Go intra-package pass).
|
|
2131
|
+
*/
|
|
2132
|
+
resolveAll(importPath) {
|
|
2133
|
+
if (!this.modulePath) return [];
|
|
2134
|
+
if (importPath.startsWith(".")) return [];
|
|
2135
|
+
if (!importPath.startsWith(this.modulePath)) return [];
|
|
1907
2136
|
const suffix = importPath.slice(this.modulePath.length);
|
|
1908
|
-
if (!suffix) return
|
|
2137
|
+
if (!suffix) return [];
|
|
1909
2138
|
const subPath = suffix.startsWith("/") ? suffix.slice(1) : suffix;
|
|
1910
2139
|
const absDir = path4.join(this.rootDir, subPath);
|
|
1911
|
-
return this.
|
|
2140
|
+
return this.allGoFilesInDir(
|
|
2141
|
+
absDir,
|
|
2142
|
+
subPath,
|
|
2143
|
+
/* includeTests */
|
|
2144
|
+
false
|
|
2145
|
+
);
|
|
1912
2146
|
}
|
|
1913
2147
|
/**
|
|
1914
2148
|
* Resolve a relative import (`./config`, `../pkg`) from a given Go source file.
|
|
1915
2149
|
* Returns the relative project path to the first .go file found, or null.
|
|
2150
|
+
* Kept for back-compat — most callers should prefer `resolveRelativeAll()`.
|
|
1916
2151
|
*/
|
|
1917
2152
|
resolveRelative(fromFile, importSpec) {
|
|
2153
|
+
const all = this.resolveRelativeAll(fromFile, importSpec);
|
|
2154
|
+
return all[0] ?? null;
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Resolve a relative import to ALL non-test .go files in the target
|
|
2158
|
+
* package directory. See `resolveAll()` for rationale.
|
|
2159
|
+
*/
|
|
2160
|
+
resolveRelativeAll(fromFile, importSpec) {
|
|
1918
2161
|
const fromDir = path4.dirname(fromFile);
|
|
1919
2162
|
const absTarget = path4.resolve(fromDir, importSpec);
|
|
1920
2163
|
const subPath = path4.relative(this.rootDir, absTarget);
|
|
1921
|
-
return this.
|
|
2164
|
+
return this.allGoFilesInDir(
|
|
2165
|
+
absTarget,
|
|
2166
|
+
subPath,
|
|
2167
|
+
/* includeTests */
|
|
2168
|
+
false
|
|
2169
|
+
);
|
|
1922
2170
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
2171
|
+
/**
|
|
2172
|
+
* Enumerate every .go file in a directory, returning project-relative
|
|
2173
|
+
* paths sorted with non-test files first. Used by both the single-file
|
|
2174
|
+
* and all-files resolvers.
|
|
2175
|
+
*/
|
|
2176
|
+
allGoFilesInDir(absDir, relDir, includeTests) {
|
|
2177
|
+
if (!fs4.existsSync(absDir)) return [];
|
|
1925
2178
|
let entries;
|
|
1926
2179
|
try {
|
|
1927
2180
|
entries = fs4.readdirSync(absDir);
|
|
1928
2181
|
} catch {
|
|
1929
|
-
return
|
|
2182
|
+
return [];
|
|
1930
2183
|
}
|
|
1931
|
-
const goFiles = entries.filter((f) => f.endsWith(".go")).sort((a, b) => {
|
|
2184
|
+
const goFiles = entries.filter((f) => f.endsWith(".go")).filter((f) => includeTests || !f.endsWith("_test.go")).sort((a, b) => {
|
|
1932
2185
|
const aTest = a.endsWith("_test.go") ? 1 : 0;
|
|
1933
2186
|
const bTest = b.endsWith("_test.go") ? 1 : 0;
|
|
1934
2187
|
return aTest - bTest || a.localeCompare(b);
|
|
1935
2188
|
});
|
|
1936
|
-
|
|
1937
|
-
return path4.join(relDir, goFiles[0]).replace(/\\/g, "/");
|
|
2189
|
+
return goFiles.map((f) => path4.join(relDir, f).replace(/\\/g, "/"));
|
|
1938
2190
|
}
|
|
1939
2191
|
};
|
|
1940
2192
|
|
|
@@ -1976,6 +2228,24 @@ function extractImports(filePath, content) {
|
|
|
1976
2228
|
return extractNotebookImports(filePath, content);
|
|
1977
2229
|
case ".vue":
|
|
1978
2230
|
return extractVueImports(content);
|
|
2231
|
+
case ".c":
|
|
2232
|
+
case ".cc":
|
|
2233
|
+
case ".cpp":
|
|
2234
|
+
case ".cxx":
|
|
2235
|
+
case ".h":
|
|
2236
|
+
case ".hh":
|
|
2237
|
+
case ".hpp":
|
|
2238
|
+
case ".hxx":
|
|
2239
|
+
return extractCppImports(content);
|
|
2240
|
+
case ".scala":
|
|
2241
|
+
return extractScalaImports(content);
|
|
2242
|
+
case ".lua":
|
|
2243
|
+
return extractLuaImports(content);
|
|
2244
|
+
case ".ex":
|
|
2245
|
+
case ".exs":
|
|
2246
|
+
return extractElixirImports(content);
|
|
2247
|
+
case ".zig":
|
|
2248
|
+
return extractZigImports(content);
|
|
1979
2249
|
default:
|
|
1980
2250
|
return [];
|
|
1981
2251
|
}
|
|
@@ -1995,8 +2265,14 @@ function resolveImport(fromAbs, raw, rootDir) {
|
|
|
1995
2265
|
if (ext === ".dart") return resolveDartImport(fromAbs, fromDir, raw, rootDir);
|
|
1996
2266
|
if (ext === ".ipynb") return resolvePythonImport(fromAbs, fromDir, raw, rootDir);
|
|
1997
2267
|
if (ext === ".vue") return resolveVueImport(fromAbs, fromDir, raw, rootDir);
|
|
2268
|
+
if (CPP_EXTENSIONS.has(ext)) return resolveCppImport(fromDir, raw, rootDir);
|
|
2269
|
+
if (ext === ".scala") return resolveScalaImport(fromDir, raw, rootDir);
|
|
2270
|
+
if (ext === ".lua") return resolveLuaImport(fromDir, raw, rootDir);
|
|
2271
|
+
if (ext === ".ex" || ext === ".exs") return resolveElixirImport(fromDir, raw, rootDir);
|
|
2272
|
+
if (ext === ".zig") return resolveZigImport(fromAbs, fromDir, raw, rootDir);
|
|
1998
2273
|
return null;
|
|
1999
2274
|
}
|
|
2275
|
+
var CPP_EXTENSIONS = /* @__PURE__ */ new Set([".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"]);
|
|
2000
2276
|
function extractPythonImports(content) {
|
|
2001
2277
|
const results = [];
|
|
2002
2278
|
let m;
|
|
@@ -2283,6 +2559,124 @@ function resolveVueImport(fromAbs, fromDir, raw, rootDir) {
|
|
|
2283
2559
|
}
|
|
2284
2560
|
return null;
|
|
2285
2561
|
}
|
|
2562
|
+
function extractCppImports(content) {
|
|
2563
|
+
const results = [];
|
|
2564
|
+
const localInclude = /^\s*#\s*include\s+"([^"]+)"/gm;
|
|
2565
|
+
let m;
|
|
2566
|
+
while ((m = localInclude.exec(content)) !== null) {
|
|
2567
|
+
results.push({ specifier: m[1], isRelative: true });
|
|
2568
|
+
}
|
|
2569
|
+
return results;
|
|
2570
|
+
}
|
|
2571
|
+
function resolveCppImport(fromDir, raw, rootDir) {
|
|
2572
|
+
const rootResolved = path5.resolve(rootDir);
|
|
2573
|
+
const candidates = [
|
|
2574
|
+
path5.resolve(fromDir, raw.specifier),
|
|
2575
|
+
path5.resolve(rootDir, raw.specifier)
|
|
2576
|
+
];
|
|
2577
|
+
for (const c of candidates) {
|
|
2578
|
+
if (!c.startsWith(rootResolved + path5.sep) && c !== rootResolved) continue;
|
|
2579
|
+
if (fs5.existsSync(c)) return path5.relative(rootDir, c);
|
|
2580
|
+
}
|
|
2581
|
+
return null;
|
|
2582
|
+
}
|
|
2583
|
+
function extractScalaImports(content) {
|
|
2584
|
+
const results = [];
|
|
2585
|
+
const importRe = /^\s*import\s+((?:\w+\.)*\w+)(?:\.\{[^}]+\})?/gm;
|
|
2586
|
+
let m;
|
|
2587
|
+
while ((m = importRe.exec(content)) !== null) {
|
|
2588
|
+
results.push({ specifier: m[1], isRelative: false });
|
|
2589
|
+
}
|
|
2590
|
+
return results;
|
|
2591
|
+
}
|
|
2592
|
+
function resolveScalaImport(fromDir, raw, rootDir) {
|
|
2593
|
+
const asPath = raw.specifier.replace(/\./g, path5.sep);
|
|
2594
|
+
const candidates = [
|
|
2595
|
+
path5.join(rootDir, "src", "main", "scala", asPath + ".scala"),
|
|
2596
|
+
path5.join(rootDir, asPath + ".scala")
|
|
2597
|
+
];
|
|
2598
|
+
for (const c of candidates) {
|
|
2599
|
+
if (fs5.existsSync(c)) return path5.relative(rootDir, c);
|
|
2600
|
+
}
|
|
2601
|
+
const className = raw.specifier.split(".").pop() ?? raw.specifier;
|
|
2602
|
+
const local = path5.join(fromDir, className + ".scala");
|
|
2603
|
+
if (fs5.existsSync(local)) return path5.relative(rootDir, local);
|
|
2604
|
+
return null;
|
|
2605
|
+
}
|
|
2606
|
+
function extractLuaImports(content) {
|
|
2607
|
+
const results = [];
|
|
2608
|
+
const requireRe = /\brequire\s*\(?\s*['"]([^'"]+)['"]/g;
|
|
2609
|
+
let m;
|
|
2610
|
+
while ((m = requireRe.exec(content)) !== null) {
|
|
2611
|
+
results.push({ specifier: m[1], isRelative: false });
|
|
2612
|
+
}
|
|
2613
|
+
return results;
|
|
2614
|
+
}
|
|
2615
|
+
function resolveLuaImport(fromDir, raw, rootDir) {
|
|
2616
|
+
const asPath = raw.specifier.replace(/\./g, path5.sep);
|
|
2617
|
+
const candidates = [
|
|
2618
|
+
path5.join(fromDir, asPath + ".lua"),
|
|
2619
|
+
path5.join(rootDir, asPath + ".lua"),
|
|
2620
|
+
// Lua's package convention also supports init.lua as a directory
|
|
2621
|
+
// entry point — analogous to Python's __init__.py.
|
|
2622
|
+
path5.join(rootDir, asPath, "init.lua")
|
|
2623
|
+
];
|
|
2624
|
+
for (const c of candidates) {
|
|
2625
|
+
if (fs5.existsSync(c)) return path5.relative(rootDir, c);
|
|
2626
|
+
}
|
|
2627
|
+
return null;
|
|
2628
|
+
}
|
|
2629
|
+
function extractElixirImports(content) {
|
|
2630
|
+
const results = [];
|
|
2631
|
+
const re = /^\s*(?:alias|import|use|require)\s+([A-Z][\w.]*)/gm;
|
|
2632
|
+
let m;
|
|
2633
|
+
while ((m = re.exec(content)) !== null) {
|
|
2634
|
+
results.push({ specifier: m[1], isRelative: false });
|
|
2635
|
+
}
|
|
2636
|
+
return results;
|
|
2637
|
+
}
|
|
2638
|
+
function resolveElixirImport(fromDir, raw, rootDir) {
|
|
2639
|
+
const segments = raw.specifier.split(".").map(
|
|
2640
|
+
(s) => s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()
|
|
2641
|
+
);
|
|
2642
|
+
const asPath = segments.join(path5.sep);
|
|
2643
|
+
const candidates = [
|
|
2644
|
+
path5.join(rootDir, "lib", asPath + ".ex"),
|
|
2645
|
+
path5.join(rootDir, "lib", asPath + ".exs"),
|
|
2646
|
+
path5.join(rootDir, asPath + ".ex"),
|
|
2647
|
+
path5.join(rootDir, asPath + ".exs")
|
|
2648
|
+
];
|
|
2649
|
+
for (const c of candidates) {
|
|
2650
|
+
if (fs5.existsSync(c)) return path5.relative(rootDir, c);
|
|
2651
|
+
}
|
|
2652
|
+
const tail = segments[segments.length - 1];
|
|
2653
|
+
const local = path5.join(fromDir, tail + ".ex");
|
|
2654
|
+
if (fs5.existsSync(local)) return path5.relative(rootDir, local);
|
|
2655
|
+
return null;
|
|
2656
|
+
}
|
|
2657
|
+
function extractZigImports(content) {
|
|
2658
|
+
const results = [];
|
|
2659
|
+
const importRe = /@import\s*\(\s*"([^"]+)"\s*\)/g;
|
|
2660
|
+
let m;
|
|
2661
|
+
while ((m = importRe.exec(content)) !== null) {
|
|
2662
|
+
const spec = m[1];
|
|
2663
|
+
if (spec.endsWith(".zig")) {
|
|
2664
|
+
const isRelative = spec.startsWith(".") || !spec.includes("/");
|
|
2665
|
+
results.push({ specifier: spec, isRelative });
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return results;
|
|
2669
|
+
}
|
|
2670
|
+
function resolveZigImport(fromAbs, fromDir, raw, rootDir) {
|
|
2671
|
+
void fromAbs;
|
|
2672
|
+
const rootResolved = path5.resolve(rootDir);
|
|
2673
|
+
const candidate = path5.resolve(fromDir, raw.specifier);
|
|
2674
|
+
if (!candidate.startsWith(rootResolved + path5.sep) && candidate !== rootResolved) {
|
|
2675
|
+
return null;
|
|
2676
|
+
}
|
|
2677
|
+
if (fs5.existsSync(candidate)) return path5.relative(rootDir, candidate);
|
|
2678
|
+
return null;
|
|
2679
|
+
}
|
|
2286
2680
|
|
|
2287
2681
|
// ../../packages/core/src/utils/TsConfigPathsResolver.ts
|
|
2288
2682
|
import fs6 from "fs";
|
|
@@ -2513,6 +2907,7 @@ var CallGraphIndex = class _CallGraphIndex {
|
|
|
2513
2907
|
|
|
2514
2908
|
// ../../packages/core/src/graph/DependencyGraph.ts
|
|
2515
2909
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".vue"]);
|
|
2910
|
+
var PY_EXTENSIONS = /* @__PURE__ */ new Set([".py", ".ipynb"]);
|
|
2516
2911
|
var AST_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs", ".java", ".cs", ".rb", ".kt", ".kts", ".swift", ".ipynb", ".php", ".dart"]);
|
|
2517
2912
|
var DependencyGraph = class {
|
|
2518
2913
|
/** file → set of files it imports (forward edges) */
|
|
@@ -2522,6 +2917,31 @@ var DependencyGraph = class {
|
|
|
2522
2917
|
/** Symbol index: symbolName → { filePath, type, signature, startLine?, endLine? } */
|
|
2523
2918
|
symbolIndex = /* @__PURE__ */ new Map();
|
|
2524
2919
|
callGraphIndex = new CallGraphIndex();
|
|
2920
|
+
/**
|
|
2921
|
+
* Re-export tracing (v1.6.x):
|
|
2922
|
+
* reExportMap[barrelFile][symbol] = sourceFile
|
|
2923
|
+
*
|
|
2924
|
+
* Built during graph construction from `from .submodule import Name`
|
|
2925
|
+
* statements (Python). When file C imports `from <barrel> import Name`,
|
|
2926
|
+
* the import resolver consults this map and emits a parallel edge
|
|
2927
|
+
* C → sourceFile so blast-radius queries against sourceFile can find
|
|
2928
|
+
* C as a transitive consumer through the barrel.
|
|
2929
|
+
*
|
|
2930
|
+
* Concrete fastapi case:
|
|
2931
|
+
* tests/test_routing.py has `from fastapi import APIRouter`
|
|
2932
|
+
* fastapi/__init__.py has `from .routing import APIRouter`
|
|
2933
|
+
* → reExportMap['fastapi/__init__.py']['APIRouter'] = 'fastapi/routing.py'
|
|
2934
|
+
* → emit edge tests/test_routing.py → fastapi/routing.py
|
|
2935
|
+
*/
|
|
2936
|
+
reExportMap = /* @__PURE__ */ new Map();
|
|
2937
|
+
/**
|
|
2938
|
+
* Pending re-export queries. Populated during pass 1 (parse all
|
|
2939
|
+
* files, extract their imports); resolved in pass 2 once the
|
|
2940
|
+
* reExportMap is fully built. Required because file ordering would
|
|
2941
|
+
* otherwise miss re-exports whose source files are parsed AFTER the
|
|
2942
|
+
* consumer file.
|
|
2943
|
+
*/
|
|
2944
|
+
pendingReExportQueries = [];
|
|
2525
2945
|
parser = null;
|
|
2526
2946
|
rootDir = "";
|
|
2527
2947
|
snapshotDir = "";
|
|
@@ -2576,8 +2996,10 @@ var DependencyGraph = class {
|
|
|
2576
2996
|
for (const imp of importNodes) {
|
|
2577
2997
|
const spec = imp.source ?? imp.name;
|
|
2578
2998
|
const isRelative = spec.startsWith(".");
|
|
2579
|
-
const
|
|
2580
|
-
|
|
2999
|
+
const resolvedAll = isRelative ? goResolver.resolveRelativeAll(absPath, spec) : goResolver.resolveAll(spec);
|
|
3000
|
+
for (const resolved of resolvedAll) {
|
|
3001
|
+
this.addEdge(relPath, resolved);
|
|
3002
|
+
}
|
|
2581
3003
|
}
|
|
2582
3004
|
} else {
|
|
2583
3005
|
const importNodes = nodes.filter((n) => n.type === "import");
|
|
@@ -2586,7 +3008,26 @@ var DependencyGraph = class {
|
|
|
2586
3008
|
const specifier = imp.source ?? imp.name;
|
|
2587
3009
|
const isRelative = specifier.startsWith(".");
|
|
2588
3010
|
const resolved = resolveImport(absPath, { specifier, isRelative }, rootDir);
|
|
2589
|
-
if (resolved)
|
|
3011
|
+
if (resolved) {
|
|
3012
|
+
this.addEdge(relPath, resolved);
|
|
3013
|
+
if (ext === ".py" && imp.importedNames && imp.importedNames.length > 0) {
|
|
3014
|
+
if (isRelative) {
|
|
3015
|
+
let map2 = this.reExportMap.get(relPath);
|
|
3016
|
+
if (!map2) {
|
|
3017
|
+
map2 = /* @__PURE__ */ new Map();
|
|
3018
|
+
this.reExportMap.set(relPath, map2);
|
|
3019
|
+
}
|
|
3020
|
+
for (const name of imp.importedNames) {
|
|
3021
|
+
map2.set(name, resolved);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
this.pendingReExportQueries.push({
|
|
3025
|
+
caller: relPath,
|
|
3026
|
+
barrel: resolved,
|
|
3027
|
+
symbols: imp.importedNames
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
2590
3031
|
}
|
|
2591
3032
|
} else {
|
|
2592
3033
|
const content = fs7.readFileSync(absPath, "utf-8");
|
|
@@ -2598,7 +3039,7 @@ var DependencyGraph = class {
|
|
|
2598
3039
|
}
|
|
2599
3040
|
}
|
|
2600
3041
|
for (const node of nodes) {
|
|
2601
|
-
if (node.type === "function" || node.type === "class" || node.type === "interface") {
|
|
3042
|
+
if (node.type === "function" || node.type === "class" || node.type === "interface" || node.type === "method") {
|
|
2602
3043
|
const existing = this.symbolIndex.get(node.name) ?? [];
|
|
2603
3044
|
existing.push({
|
|
2604
3045
|
filePath: relPath,
|
|
@@ -2615,6 +3056,11 @@ var DependencyGraph = class {
|
|
|
2615
3056
|
for (const edge of callEdges) {
|
|
2616
3057
|
this.callGraphIndex.addEdge({ callerFile: relPath, ...edge });
|
|
2617
3058
|
}
|
|
3059
|
+
} else if (PY_EXTENSIONS.has(ext)) {
|
|
3060
|
+
const callEdges = await this.parser.parseAllPythonCallEdges(absPath);
|
|
3061
|
+
for (const edge of callEdges) {
|
|
3062
|
+
this.callGraphIndex.addEdge({ callerFile: relPath, ...edge });
|
|
3063
|
+
}
|
|
2618
3064
|
}
|
|
2619
3065
|
} else {
|
|
2620
3066
|
const content = fs7.readFileSync(absPath, "utf-8");
|
|
@@ -2628,6 +3074,49 @@ var DependencyGraph = class {
|
|
|
2628
3074
|
logger.error("Failed to parse", { file: relPath, detail: err instanceof Error ? err.message : String(err) });
|
|
2629
3075
|
}
|
|
2630
3076
|
}
|
|
3077
|
+
let reExportEdgesAdded = 0;
|
|
3078
|
+
for (const { caller, barrel, symbols } of this.pendingReExportQueries) {
|
|
3079
|
+
const map2 = this.reExportMap.get(barrel);
|
|
3080
|
+
if (!map2) continue;
|
|
3081
|
+
for (const sym of symbols) {
|
|
3082
|
+
const source = map2.get(sym);
|
|
3083
|
+
if (source && source !== caller && source !== barrel) {
|
|
3084
|
+
this.addEdge(caller, source);
|
|
3085
|
+
reExportEdgesAdded += 1;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
this.pendingReExportQueries = [];
|
|
3090
|
+
if (reExportEdgesAdded > 0) {
|
|
3091
|
+
logger.info("Re-export tracing added parallel edges", { count: reExportEdgesAdded });
|
|
3092
|
+
}
|
|
3093
|
+
let goTestEdgesAdded = 0;
|
|
3094
|
+
for (const relPath of this.forwardEdges.keys()) {
|
|
3095
|
+
if (!relPath.endsWith("_test.go")) continue;
|
|
3096
|
+
const dir = path7.dirname(relPath);
|
|
3097
|
+
const base = path7.basename(relPath, "_test.go");
|
|
3098
|
+
const namesake = path7.join(dir, base + ".go").replace(/\\/g, "/");
|
|
3099
|
+
if (this.forwardEdges.has(namesake)) {
|
|
3100
|
+
this.addEdge(relPath, namesake);
|
|
3101
|
+
this.addEdge(namesake, relPath);
|
|
3102
|
+
goTestEdgesAdded += 2;
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
const absDir = path7.join(this.rootDir, dir);
|
|
3106
|
+
try {
|
|
3107
|
+
const siblings = fs7.readdirSync(absDir).filter((f) => f.endsWith(".go") && !f.endsWith("_test.go"));
|
|
3108
|
+
for (const sib of siblings) {
|
|
3109
|
+
const sibRel = path7.join(dir, sib).replace(/\\/g, "/");
|
|
3110
|
+
this.addEdge(relPath, sibRel);
|
|
3111
|
+
this.addEdge(sibRel, relPath);
|
|
3112
|
+
goTestEdgesAdded += 2;
|
|
3113
|
+
}
|
|
3114
|
+
} catch {
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
if (goTestEdgesAdded > 0) {
|
|
3118
|
+
logger.info("Go test\u2194source linkage added edges", { count: goTestEdgesAdded });
|
|
3119
|
+
}
|
|
2631
3120
|
await this.saveSnapshot();
|
|
2632
3121
|
logger.info("Graph built", { files: files.length, edges: this.edgeCount() });
|
|
2633
3122
|
if (options?.afterReady) {
|
|
@@ -2820,8 +3309,10 @@ var DependencyGraph = class {
|
|
|
2820
3309
|
for (const imp of importNodes) {
|
|
2821
3310
|
const spec = imp.source ?? imp.name;
|
|
2822
3311
|
const isRelative = spec.startsWith(".");
|
|
2823
|
-
const
|
|
2824
|
-
|
|
3312
|
+
const resolvedAll = isRelative ? goResolver.resolveRelativeAll(absPath, spec) : goResolver.resolveAll(spec);
|
|
3313
|
+
for (const resolved of resolvedAll) {
|
|
3314
|
+
this.addEdge(relPath, resolved);
|
|
3315
|
+
}
|
|
2825
3316
|
}
|
|
2826
3317
|
} else {
|
|
2827
3318
|
const importNodes = nodes.filter((n) => n.type === "import");
|
|
@@ -2842,7 +3333,7 @@ var DependencyGraph = class {
|
|
|
2842
3333
|
}
|
|
2843
3334
|
}
|
|
2844
3335
|
for (const node of nodes) {
|
|
2845
|
-
if (node.type === "function" || node.type === "class" || node.type === "interface") {
|
|
3336
|
+
if (node.type === "function" || node.type === "class" || node.type === "interface" || node.type === "method") {
|
|
2846
3337
|
const existing = this.symbolIndex.get(node.name) ?? [];
|
|
2847
3338
|
existing.push({
|
|
2848
3339
|
filePath: relPath,
|
|
@@ -2859,6 +3350,11 @@ var DependencyGraph = class {
|
|
|
2859
3350
|
for (const edge of callEdges) {
|
|
2860
3351
|
this.callGraphIndex.addEdge({ callerFile: relPath, ...edge });
|
|
2861
3352
|
}
|
|
3353
|
+
} else if (PY_EXTENSIONS.has(ext)) {
|
|
3354
|
+
const callEdges = await this.parser.parseAllPythonCallEdges(absPath);
|
|
3355
|
+
for (const edge of callEdges) {
|
|
3356
|
+
this.callGraphIndex.addEdge({ callerFile: relPath, ...edge });
|
|
3357
|
+
}
|
|
2862
3358
|
}
|
|
2863
3359
|
} else {
|
|
2864
3360
|
const content = fs7.readFileSync(absPath, "utf-8");
|
|
@@ -2915,12 +3411,12 @@ var DependencyGraph = class {
|
|
|
2915
3411
|
symbolIndex: Object.fromEntries(this.symbolIndex.entries())
|
|
2916
3412
|
};
|
|
2917
3413
|
const snapshotPath = this.getSnapshotPath();
|
|
2918
|
-
const tmpPath = snapshotPath
|
|
3414
|
+
const tmpPath = `${snapshotPath}.${process.pid}.tmp`;
|
|
2919
3415
|
fs7.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
2920
3416
|
fs7.renameSync(tmpPath, snapshotPath);
|
|
2921
3417
|
const callData = this.callGraphIndex.toJSON();
|
|
2922
3418
|
const callPath = path7.join(this.snapshotDir, "call-graph-snapshot.json");
|
|
2923
|
-
const callTmp = callPath
|
|
3419
|
+
const callTmp = `${callPath}.${process.pid}.tmp`;
|
|
2924
3420
|
fs7.writeFileSync(callTmp, JSON.stringify(callData));
|
|
2925
3421
|
fs7.renameSync(callTmp, callPath);
|
|
2926
3422
|
}
|
|
@@ -3267,8 +3763,8 @@ var CoChangeIndex = class _CoChangeIndex {
|
|
|
3267
3763
|
if (event.isBulk || event.isMerge) return;
|
|
3268
3764
|
const paths = event.files.map((f) => f.path);
|
|
3269
3765
|
if (paths.length === 0) return;
|
|
3270
|
-
for (const
|
|
3271
|
-
this.nodeCounts.set(
|
|
3766
|
+
for (const path48 of paths) {
|
|
3767
|
+
this.nodeCounts.set(path48, (this.nodeCounts.get(path48) ?? 0) + 1);
|
|
3272
3768
|
}
|
|
3273
3769
|
for (let i = 0; i < paths.length; i++) {
|
|
3274
3770
|
for (let j = i + 1; j < paths.length; j++) {
|
|
@@ -3415,8 +3911,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3415
3911
|
*/
|
|
3416
3912
|
snapshot() {
|
|
3417
3913
|
const nodes = {};
|
|
3418
|
-
for (const [
|
|
3419
|
-
nodes[
|
|
3914
|
+
for (const [path48, raw] of this.nodes) {
|
|
3915
|
+
nodes[path48] = {
|
|
3420
3916
|
commits: raw.commits,
|
|
3421
3917
|
churnLines: raw.churnLines,
|
|
3422
3918
|
bugCommits: raw.bugCommits,
|
|
@@ -3431,8 +3927,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3431
3927
|
*/
|
|
3432
3928
|
static load(s) {
|
|
3433
3929
|
const idx = new _ChurnIndex();
|
|
3434
|
-
for (const [
|
|
3435
|
-
idx.nodes.set(
|
|
3930
|
+
for (const [path48, raw] of Object.entries(s.nodes)) {
|
|
3931
|
+
idx.nodes.set(path48, {
|
|
3436
3932
|
commits: raw.commits,
|
|
3437
3933
|
churnLines: raw.churnLines,
|
|
3438
3934
|
bugCommits: raw.bugCommits,
|
|
@@ -3445,8 +3941,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3445
3941
|
// -------------------------------------------------------------------------
|
|
3446
3942
|
// Private helpers
|
|
3447
3943
|
// -------------------------------------------------------------------------
|
|
3448
|
-
getOrCreate(
|
|
3449
|
-
const existing = this.nodes.get(
|
|
3944
|
+
getOrCreate(path48) {
|
|
3945
|
+
const existing = this.nodes.get(path48);
|
|
3450
3946
|
if (existing !== void 0) return existing;
|
|
3451
3947
|
const fresh = {
|
|
3452
3948
|
commits: 0,
|
|
@@ -3455,7 +3951,7 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3455
3951
|
authorCounts: {},
|
|
3456
3952
|
lastTouch: 0
|
|
3457
3953
|
};
|
|
3458
|
-
this.nodes.set(
|
|
3954
|
+
this.nodes.set(path48, fresh);
|
|
3459
3955
|
return fresh;
|
|
3460
3956
|
}
|
|
3461
3957
|
};
|
|
@@ -3536,12 +4032,12 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3536
4032
|
*/
|
|
3537
4033
|
snapshot() {
|
|
3538
4034
|
const nodes = {};
|
|
3539
|
-
for (const [
|
|
4035
|
+
for (const [path48, raw] of this.nodes) {
|
|
3540
4036
|
const authorWeights = {};
|
|
3541
4037
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3542
4038
|
authorWeights[email] = { ...entry };
|
|
3543
4039
|
}
|
|
3544
|
-
nodes[
|
|
4040
|
+
nodes[path48] = { authorWeights, lastTouch: raw.lastTouch };
|
|
3545
4041
|
}
|
|
3546
4042
|
return { version: 1, nodes };
|
|
3547
4043
|
}
|
|
@@ -3550,23 +4046,23 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3550
4046
|
*/
|
|
3551
4047
|
static load(s) {
|
|
3552
4048
|
const idx = new _OwnershipIndex();
|
|
3553
|
-
for (const [
|
|
4049
|
+
for (const [path48, raw] of Object.entries(s.nodes)) {
|
|
3554
4050
|
const authorWeights = {};
|
|
3555
4051
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3556
4052
|
authorWeights[email] = { ...entry };
|
|
3557
4053
|
}
|
|
3558
|
-
idx.nodes.set(
|
|
4054
|
+
idx.nodes.set(path48, { authorWeights, lastTouch: raw.lastTouch });
|
|
3559
4055
|
}
|
|
3560
4056
|
return idx;
|
|
3561
4057
|
}
|
|
3562
4058
|
// -------------------------------------------------------------------------
|
|
3563
4059
|
// Private helpers
|
|
3564
4060
|
// -------------------------------------------------------------------------
|
|
3565
|
-
getOrCreate(
|
|
3566
|
-
const existing = this.nodes.get(
|
|
4061
|
+
getOrCreate(path48) {
|
|
4062
|
+
const existing = this.nodes.get(path48);
|
|
3567
4063
|
if (existing !== void 0) return existing;
|
|
3568
4064
|
const fresh = { authorWeights: {}, lastTouch: 0 };
|
|
3569
|
-
this.nodes.set(
|
|
4065
|
+
this.nodes.set(path48, fresh);
|
|
3570
4066
|
return fresh;
|
|
3571
4067
|
}
|
|
3572
4068
|
};
|
|
@@ -4326,6 +4822,11 @@ ${methodLines.join("\n")}
|
|
|
4326
4822
|
init_embedder();
|
|
4327
4823
|
init_VectorStore();
|
|
4328
4824
|
|
|
4825
|
+
// ../../packages/core/src/db/vectorsCleanup.ts
|
|
4826
|
+
import fs16 from "fs";
|
|
4827
|
+
import path16 from "path";
|
|
4828
|
+
var VECTOR_DB_REL = path16.join(".ctxloom", "vectors.lancedb");
|
|
4829
|
+
|
|
4329
4830
|
// ../../node_modules/zod/v3/external.js
|
|
4330
4831
|
var external_exports = {};
|
|
4331
4832
|
__export(external_exports, {
|
|
@@ -4804,8 +5305,8 @@ function getErrorMap() {
|
|
|
4804
5305
|
|
|
4805
5306
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
4806
5307
|
var makeIssue = (params) => {
|
|
4807
|
-
const { data, path:
|
|
4808
|
-
const fullPath = [...
|
|
5308
|
+
const { data, path: path48, errorMaps, issueData } = params;
|
|
5309
|
+
const fullPath = [...path48, ...issueData.path || []];
|
|
4809
5310
|
const fullIssue = {
|
|
4810
5311
|
...issueData,
|
|
4811
5312
|
path: fullPath
|
|
@@ -4921,11 +5422,11 @@ var errorUtil;
|
|
|
4921
5422
|
|
|
4922
5423
|
// ../../node_modules/zod/v3/types.js
|
|
4923
5424
|
var ParseInputLazyPath = class {
|
|
4924
|
-
constructor(parent, value,
|
|
5425
|
+
constructor(parent, value, path48, key) {
|
|
4925
5426
|
this._cachedPath = [];
|
|
4926
5427
|
this.parent = parent;
|
|
4927
5428
|
this.data = value;
|
|
4928
|
-
this._path =
|
|
5429
|
+
this._path = path48;
|
|
4929
5430
|
this._key = key;
|
|
4930
5431
|
}
|
|
4931
5432
|
get path() {
|
|
@@ -8379,13 +8880,13 @@ init_logger();
|
|
|
8379
8880
|
|
|
8380
8881
|
// ../../packages/core/src/budget/eventCollector.ts
|
|
8381
8882
|
init_logger();
|
|
8382
|
-
import
|
|
8883
|
+
import fs17 from "fs";
|
|
8383
8884
|
import os2 from "os";
|
|
8384
|
-
import
|
|
8385
|
-
var DEFAULT_TELEMETRY_DIR =
|
|
8885
|
+
import path17 from "path";
|
|
8886
|
+
var DEFAULT_TELEMETRY_DIR = path17.join(os2.homedir(), ".ctxloom", "telemetry");
|
|
8386
8887
|
function telemetryDir() {
|
|
8387
8888
|
const raw = process.env.CTXLOOM_TELEMETRY_DIR ?? DEFAULT_TELEMETRY_DIR;
|
|
8388
|
-
if (raw.includes("..") || !
|
|
8889
|
+
if (raw.includes("..") || !path17.isAbsolute(raw)) {
|
|
8389
8890
|
if (!telemetryDirWarned) {
|
|
8390
8891
|
telemetryDirWarned = true;
|
|
8391
8892
|
logger.warn('CTXLOOM_TELEMETRY_DIR rejected \u2014 must be an absolute path with no ".." segments; using default', {
|
|
@@ -8395,7 +8896,7 @@ function telemetryDir() {
|
|
|
8395
8896
|
}
|
|
8396
8897
|
return DEFAULT_TELEMETRY_DIR;
|
|
8397
8898
|
}
|
|
8398
|
-
return
|
|
8899
|
+
return path17.resolve(raw);
|
|
8399
8900
|
}
|
|
8400
8901
|
var telemetryDirWarned = false;
|
|
8401
8902
|
function filenameForDate(date) {
|
|
@@ -8408,12 +8909,12 @@ function readEvents(opts = {}) {
|
|
|
8408
8909
|
const until = opts.until ?? /* @__PURE__ */ new Date();
|
|
8409
8910
|
const since = opts.since ?? new Date(until.getTime() - 14 * 24 * 60 * 60 * 1e3);
|
|
8410
8911
|
const dir = telemetryDir();
|
|
8411
|
-
if (!
|
|
8912
|
+
if (!fs17.existsSync(dir)) return [];
|
|
8412
8913
|
const out = [];
|
|
8413
8914
|
for (let cursor = new Date(Date.UTC(since.getUTCFullYear(), since.getUTCMonth(), since.getUTCDate())); cursor.getTime() <= until.getTime(); cursor = new Date(cursor.getTime() + 24 * 60 * 60 * 1e3)) {
|
|
8414
|
-
const file =
|
|
8415
|
-
if (!
|
|
8416
|
-
const text =
|
|
8915
|
+
const file = path17.join(dir, filenameForDate(cursor));
|
|
8916
|
+
if (!fs17.existsSync(file)) continue;
|
|
8917
|
+
const text = fs17.readFileSync(file, "utf-8");
|
|
8417
8918
|
for (const line of text.split("\n")) {
|
|
8418
8919
|
if (line.trim() === "") continue;
|
|
8419
8920
|
let parsed;
|
|
@@ -8474,7 +8975,7 @@ var Schema2 = external_exports.object({
|
|
|
8474
8975
|
});
|
|
8475
8976
|
|
|
8476
8977
|
// ../../packages/core/src/tools/context-packet.ts
|
|
8477
|
-
import
|
|
8978
|
+
import path18 from "path";
|
|
8478
8979
|
var Schema3 = external_exports.object({
|
|
8479
8980
|
target_file: external_exports.string().describe("Relative path to the primary file"),
|
|
8480
8981
|
mode: external_exports.enum(["edit", "read"]).optional().default("edit").describe("Context mode"),
|
|
@@ -8486,7 +8987,7 @@ var Schema3 = external_exports.object({
|
|
|
8486
8987
|
});
|
|
8487
8988
|
|
|
8488
8989
|
// ../../packages/core/src/tools/findCallers.ts
|
|
8489
|
-
import
|
|
8990
|
+
import path19 from "path";
|
|
8490
8991
|
|
|
8491
8992
|
// ../../packages/core/src/tools/call-graph.ts
|
|
8492
8993
|
var Schema4 = external_exports.object({
|
|
@@ -8587,7 +9088,7 @@ var Schema12 = external_exports.object({
|
|
|
8587
9088
|
});
|
|
8588
9089
|
|
|
8589
9090
|
// ../../packages/core/src/tools/knowledge-gaps.ts
|
|
8590
|
-
import
|
|
9091
|
+
import path20 from "path";
|
|
8591
9092
|
var Schema13 = external_exports.object({
|
|
8592
9093
|
min_importers: external_exports.number().min(1).max(50).optional().default(3).describe(
|
|
8593
9094
|
"Minimum importers to qualify as an untested hub (default: 3)"
|
|
@@ -8616,7 +9117,7 @@ var Schema14 = external_exports.object({
|
|
|
8616
9117
|
});
|
|
8617
9118
|
|
|
8618
9119
|
// ../../packages/core/src/tools/wiki-generate.ts
|
|
8619
|
-
import
|
|
9120
|
+
import fs18 from "fs";
|
|
8620
9121
|
var Schema15 = external_exports.object({
|
|
8621
9122
|
force: external_exports.boolean().optional().default(false).describe(
|
|
8622
9123
|
"Regenerate all pages even if content unchanged (default: false)"
|
|
@@ -8664,8 +9165,8 @@ var Schema17 = external_exports.object({
|
|
|
8664
9165
|
});
|
|
8665
9166
|
|
|
8666
9167
|
// ../../packages/core/src/tools/refactor-preview.ts
|
|
8667
|
-
import
|
|
8668
|
-
import
|
|
9168
|
+
import fs19 from "fs";
|
|
9169
|
+
import path21 from "path";
|
|
8669
9170
|
var Schema18 = external_exports.object({
|
|
8670
9171
|
symbol: external_exports.string().min(1).describe("Symbol name to rename (exact match, case-sensitive)"),
|
|
8671
9172
|
new_name: external_exports.string().min(1).describe("New name for the symbol"),
|
|
@@ -8700,8 +9201,8 @@ var Schema19 = external_exports.object({
|
|
|
8700
9201
|
init_embedder();
|
|
8701
9202
|
init_VectorStore();
|
|
8702
9203
|
init_logger();
|
|
8703
|
-
import
|
|
8704
|
-
import
|
|
9204
|
+
import fs20 from "fs";
|
|
9205
|
+
import path22 from "path";
|
|
8705
9206
|
var Schema20 = external_exports.object({
|
|
8706
9207
|
query: external_exports.string().min(1).describe("Search query \u2014 natural language or code fragment"),
|
|
8707
9208
|
limit: external_exports.number().min(1).max(100).optional().default(10).describe(
|
|
@@ -8718,8 +9219,8 @@ var Schema20 = external_exports.object({
|
|
|
8718
9219
|
});
|
|
8719
9220
|
|
|
8720
9221
|
// ../../packages/core/src/tools/apply-refactor.ts
|
|
8721
|
-
import
|
|
8722
|
-
import
|
|
9222
|
+
import fs21 from "fs";
|
|
9223
|
+
import path23 from "path";
|
|
8723
9224
|
var Schema21 = external_exports.object({
|
|
8724
9225
|
symbol: external_exports.string().min(1).describe("Symbol name to rename (exact, case-sensitive)"),
|
|
8725
9226
|
new_name: external_exports.string().min(1).describe("New name for the symbol"),
|
|
@@ -8752,8 +9253,8 @@ var Schema22 = external_exports.object({
|
|
|
8752
9253
|
});
|
|
8753
9254
|
|
|
8754
9255
|
// ../../packages/core/src/tools/full-text-search.ts
|
|
8755
|
-
import
|
|
8756
|
-
import
|
|
9256
|
+
import fs22 from "fs";
|
|
9257
|
+
import path24 from "path";
|
|
8757
9258
|
var Schema23 = external_exports.object({
|
|
8758
9259
|
query: external_exports.string().min(1).describe("Search term \u2014 literal or /regex/"),
|
|
8759
9260
|
mode: external_exports.enum(["hybrid", "keyword", "semantic"]).optional().default("hybrid"),
|
|
@@ -8785,8 +9286,8 @@ var Schema25 = external_exports.object({
|
|
|
8785
9286
|
});
|
|
8786
9287
|
|
|
8787
9288
|
// ../../packages/core/src/tools/graph-snapshot.ts
|
|
8788
|
-
import
|
|
8789
|
-
import
|
|
9289
|
+
import fs23 from "fs";
|
|
9290
|
+
import path25 from "path";
|
|
8790
9291
|
var schema = external_exports.object({
|
|
8791
9292
|
name: external_exports.string().min(1).max(64).regex(/^[\w.-]+$/, "Name may only contain letters, digits, dots, underscores, hyphens").describe(
|
|
8792
9293
|
'Snapshot name (e.g. "before-refactor", "v1.0"). Used as the filename.'
|
|
@@ -8798,8 +9299,8 @@ var schema = external_exports.object({
|
|
|
8798
9299
|
});
|
|
8799
9300
|
|
|
8800
9301
|
// ../../packages/core/src/tools/graph-diff.ts
|
|
8801
|
-
import
|
|
8802
|
-
import
|
|
9302
|
+
import fs24 from "fs";
|
|
9303
|
+
import path26 from "path";
|
|
8803
9304
|
var schema2 = external_exports.object({
|
|
8804
9305
|
baseline: external_exports.string().min(1).describe('Name of the baseline snapshot (the "before" state).'),
|
|
8805
9306
|
current: external_exports.string().min(1).describe('Name of the current snapshot (the "after" state).'),
|
|
@@ -8840,8 +9341,8 @@ var Schema27 = external_exports.object({
|
|
|
8840
9341
|
});
|
|
8841
9342
|
|
|
8842
9343
|
// ../../packages/core/src/rules/loadConfig.ts
|
|
8843
|
-
import
|
|
8844
|
-
import
|
|
9344
|
+
import fs25 from "fs/promises";
|
|
9345
|
+
import path27 from "path";
|
|
8845
9346
|
|
|
8846
9347
|
// ../../node_modules/js-yaml/dist/js-yaml.mjs
|
|
8847
9348
|
function isNothing(subject) {
|
|
@@ -11482,35 +11983,36 @@ var Schema30 = external_exports.object({
|
|
|
11482
11983
|
});
|
|
11483
11984
|
|
|
11484
11985
|
// ../../packages/core/src/tools/ruleManager.ts
|
|
11485
|
-
import
|
|
11486
|
-
import path27 from "path";
|
|
11487
|
-
|
|
11488
|
-
// ../../packages/core/src/review/AuthorResolver.ts
|
|
11489
|
-
import fs26 from "fs/promises";
|
|
11986
|
+
import fs26 from "fs";
|
|
11490
11987
|
import path28 from "path";
|
|
11491
11988
|
|
|
11492
|
-
// ../../packages/core/src/review/
|
|
11989
|
+
// ../../packages/core/src/review/AuthorResolver.ts
|
|
11493
11990
|
import fs27 from "fs/promises";
|
|
11494
11991
|
import path29 from "path";
|
|
11495
11992
|
|
|
11496
|
-
// ../../packages/core/src/review/
|
|
11993
|
+
// ../../packages/core/src/review/CodeownersWriter.ts
|
|
11497
11994
|
import fs28 from "fs/promises";
|
|
11498
11995
|
import path30 from "path";
|
|
11499
11996
|
|
|
11500
|
-
// ../../packages/core/src/
|
|
11997
|
+
// ../../packages/core/src/review/loadConfig.ts
|
|
11998
|
+
import fs29 from "fs/promises";
|
|
11501
11999
|
import path31 from "path";
|
|
11502
|
-
|
|
12000
|
+
|
|
12001
|
+
// ../../packages/core/src/security/PathValidator.ts
|
|
12002
|
+
import path32 from "path";
|
|
12003
|
+
import fs30 from "fs";
|
|
11503
12004
|
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
11504
12005
|
|
|
11505
12006
|
// ../../packages/core/src/index.ts
|
|
11506
12007
|
init_logger();
|
|
11507
12008
|
|
|
11508
12009
|
// ../../packages/core/src/watcher/FileWatcher.ts
|
|
12010
|
+
init_embedder();
|
|
11509
12011
|
init_logger();
|
|
11510
12012
|
|
|
11511
12013
|
// ../../packages/core/src/license/LicenseStore.ts
|
|
11512
12014
|
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, chmodSync, existsSync } from "fs";
|
|
11513
|
-
import
|
|
12015
|
+
import path33 from "path";
|
|
11514
12016
|
|
|
11515
12017
|
// ../../packages/core/src/license/types.ts
|
|
11516
12018
|
var FINGERPRINT_RE = /^sha256:[0-9a-f]{64}$/;
|
|
@@ -11541,11 +12043,11 @@ import os6 from "os";
|
|
|
11541
12043
|
|
|
11542
12044
|
// ../../packages/core/src/license/DistinctIdStore.ts
|
|
11543
12045
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
11544
|
-
import
|
|
12046
|
+
import path34 from "path";
|
|
11545
12047
|
import os4 from "os";
|
|
11546
12048
|
var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
11547
12049
|
function distinctIdPath(home) {
|
|
11548
|
-
return
|
|
12050
|
+
return path34.join(home ?? os4.homedir(), ".ctxloom", "distinct_id");
|
|
11549
12051
|
}
|
|
11550
12052
|
function isValidV4(id) {
|
|
11551
12053
|
return typeof id === "string" && UUID_V4_REGEX.test(id);
|
|
@@ -11566,7 +12068,7 @@ function getOrCreateDistinctId(home) {
|
|
|
11566
12068
|
id: crypto.randomUUID(),
|
|
11567
12069
|
alias_pending: os4.hostname()
|
|
11568
12070
|
};
|
|
11569
|
-
mkdirSync2(
|
|
12071
|
+
mkdirSync2(path34.dirname(filePath), { recursive: true });
|
|
11570
12072
|
writeFileSync2(filePath, JSON.stringify(record), { mode: 384 });
|
|
11571
12073
|
return record;
|
|
11572
12074
|
}
|
|
@@ -11592,10 +12094,10 @@ function resolveTelemetryLevel() {
|
|
|
11592
12094
|
}
|
|
11593
12095
|
var TELEMETRY_LEVEL = resolveTelemetryLevel();
|
|
11594
12096
|
var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
|
|
11595
|
-
var CTXLOOM_VERSION = "1.
|
|
12097
|
+
var CTXLOOM_VERSION = "1.7.0".length > 0 ? "1.7.0" : "dev";
|
|
11596
12098
|
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
11597
12099
|
var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
|
|
11598
|
-
var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528
|
|
12100
|
+
var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
|
|
11599
12101
|
var cachedDistinctId = null;
|
|
11600
12102
|
function resolveDistinctId() {
|
|
11601
12103
|
if (cachedDistinctId) return cachedDistinctId;
|
|
@@ -11725,32 +12227,32 @@ function parseStack(stack) {
|
|
|
11725
12227
|
|
|
11726
12228
|
// ../../packages/core/src/license/FunnelMilestones.ts
|
|
11727
12229
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
11728
|
-
import
|
|
12230
|
+
import path35 from "path";
|
|
11729
12231
|
import os5 from "os";
|
|
11730
12232
|
|
|
11731
12233
|
// ../../packages/core/src/license/TelemetryNotice.ts
|
|
11732
12234
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
11733
|
-
import
|
|
12235
|
+
import path36 from "path";
|
|
11734
12236
|
import os7 from "os";
|
|
11735
12237
|
|
|
11736
12238
|
// ../../packages/core/src/server/ProjectState.ts
|
|
11737
|
-
import
|
|
12239
|
+
import path38 from "path";
|
|
11738
12240
|
|
|
11739
12241
|
// ../../packages/core/src/server/projectId.ts
|
|
11740
12242
|
import crypto5 from "crypto";
|
|
11741
|
-
import
|
|
12243
|
+
import path37 from "path";
|
|
11742
12244
|
|
|
11743
12245
|
// ../../packages/core/src/server/ProjectStateManager.ts
|
|
11744
12246
|
init_logger();
|
|
11745
12247
|
|
|
11746
12248
|
// ../../packages/core/src/server/resolveProjectRoot.ts
|
|
11747
|
-
import fs30 from "fs";
|
|
11748
|
-
import path38 from "path";
|
|
11749
|
-
|
|
11750
|
-
// ../../packages/core/src/install/installer.ts
|
|
11751
12249
|
import fs31 from "fs";
|
|
11752
12250
|
import path39 from "path";
|
|
11753
12251
|
|
|
12252
|
+
// ../../packages/core/src/install/installer.ts
|
|
12253
|
+
import fs32 from "fs";
|
|
12254
|
+
import path40 from "path";
|
|
12255
|
+
|
|
11754
12256
|
// ../../packages/core/src/install/templates.ts
|
|
11755
12257
|
var RULES_BLOCK_CONTENT = `## MCP Tools: ctxloom
|
|
11756
12258
|
|
|
@@ -11760,6 +12262,27 @@ The graph is faster, cheaper (fewer tokens), and gives you
|
|
|
11760
12262
|
structural context (callers, dependents, test coverage) that file
|
|
11761
12263
|
scanning cannot.
|
|
11762
12264
|
|
|
12265
|
+
### Operating principles
|
|
12266
|
+
|
|
12267
|
+
ctxloom's tools exist to operationalize four principles for working
|
|
12268
|
+
with an AI coding agent. They're the *why* behind every tool below.
|
|
12269
|
+
Adapted from Karpathy's LLM-coding-pitfalls notes
|
|
12270
|
+
(<https://github.com/multica-ai/andrej-karpathy-skills>, MIT).
|
|
12271
|
+
|
|
12272
|
+
1. **Think before coding** \u2014 read the relevant graph slice before
|
|
12273
|
+
editing. \`ctx_blast_radius\`, \`ctx_get_call_graph\`,
|
|
12274
|
+
\`ctx_get_review_context\` are how you do this without re-reading
|
|
12275
|
+
whole files.
|
|
12276
|
+
2. **Simplicity first** \u2014 prefer the smallest viable change. Use
|
|
12277
|
+
\`ctx_refactor_preview\` to see the full diff *before* applying;
|
|
12278
|
+
if the preview is sprawling, the plan is too big.
|
|
12279
|
+
3. **Surgical changes** \u2014 every changed line should trace directly
|
|
12280
|
+
to the user's request. \`ctx_detect_changes\` after each edit
|
|
12281
|
+
confirms scope hasn't drifted.
|
|
12282
|
+
4. **Goal-driven execution** \u2014 stop when the goal is met. Don't
|
|
12283
|
+
"polish" beyond the request. \`ctx_knowledge_gaps\` flags real
|
|
12284
|
+
risk surfaces; everything else is yak-shaving.
|
|
12285
|
+
|
|
11763
12286
|
### Start every workflow with \`ctx_get_minimal_context\`
|
|
11764
12287
|
|
|
11765
12288
|
The first MCP call into ctxloom should always be
|
|
@@ -11989,7 +12512,7 @@ function summarize(events, windowStart, windowEnd) {
|
|
|
11989
12512
|
|
|
11990
12513
|
// server/loader.ts
|
|
11991
12514
|
async function loadContext(root) {
|
|
11992
|
-
const absRoot =
|
|
12515
|
+
const absRoot = path41.resolve(root);
|
|
11993
12516
|
const overlay = new GitOverlayStore(absRoot);
|
|
11994
12517
|
const gitEnabled = await overlay.loadSnapshot();
|
|
11995
12518
|
const graph = new DependencyGraph();
|
|
@@ -12242,21 +12765,21 @@ function buildOwnershipRouter(ctx) {
|
|
|
12242
12765
|
|
|
12243
12766
|
// server/routes/file.ts
|
|
12244
12767
|
import { Router as Router7 } from "express";
|
|
12245
|
-
import
|
|
12246
|
-
import
|
|
12768
|
+
import fs33 from "fs/promises";
|
|
12769
|
+
import path42 from "path";
|
|
12247
12770
|
function buildFileRouter(ctx) {
|
|
12248
12771
|
const router = Router7();
|
|
12249
12772
|
router.get("/", async (req, res) => {
|
|
12250
12773
|
const rel = req.query.path;
|
|
12251
12774
|
if (!rel) return res.status(400).json({ error: "missing path" });
|
|
12252
|
-
const abs =
|
|
12253
|
-
const rootBoundary = ctx.root.endsWith(
|
|
12775
|
+
const abs = path42.resolve(ctx.root, rel);
|
|
12776
|
+
const rootBoundary = ctx.root.endsWith(path42.sep) ? ctx.root : ctx.root + path42.sep;
|
|
12254
12777
|
if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
|
|
12255
12778
|
return res.status(403).json({ error: "forbidden" });
|
|
12256
12779
|
}
|
|
12257
12780
|
try {
|
|
12258
|
-
const content = await
|
|
12259
|
-
const ext =
|
|
12781
|
+
const content = await fs33.readFile(abs, "utf-8");
|
|
12782
|
+
const ext = path42.extname(abs).slice(1);
|
|
12260
12783
|
res.json({ content, lines: content.split("\n").length, ext });
|
|
12261
12784
|
} catch {
|
|
12262
12785
|
res.status(404).json({ error: "not found" });
|
|
@@ -12268,7 +12791,7 @@ function buildFileRouter(ctx) {
|
|
|
12268
12791
|
// server/routes/open.ts
|
|
12269
12792
|
import { Router as Router8 } from "express";
|
|
12270
12793
|
import { execFile as execFile2 } from "child_process";
|
|
12271
|
-
import
|
|
12794
|
+
import path43 from "path";
|
|
12272
12795
|
function tryOpen(bin, abs) {
|
|
12273
12796
|
return new Promise((resolve) => {
|
|
12274
12797
|
execFile2(bin, [abs], { timeout: 5e3 }, (err) => resolve(!err));
|
|
@@ -12279,8 +12802,8 @@ function buildOpenRouter(ctx) {
|
|
|
12279
12802
|
router.post("/", async (req, res) => {
|
|
12280
12803
|
const rel = req.body?.path;
|
|
12281
12804
|
if (!rel || typeof rel !== "string") return res.status(400).json({ error: "missing path" });
|
|
12282
|
-
const abs =
|
|
12283
|
-
const rootBoundary = ctx.root.endsWith(
|
|
12805
|
+
const abs = path43.resolve(ctx.root, rel);
|
|
12806
|
+
const rootBoundary = ctx.root.endsWith(path43.sep) ? ctx.root : ctx.root + path43.sep;
|
|
12284
12807
|
if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
|
|
12285
12808
|
return res.status(403).json({ error: "forbidden" });
|
|
12286
12809
|
}
|
|
@@ -12292,8 +12815,8 @@ function buildOpenRouter(ctx) {
|
|
|
12292
12815
|
|
|
12293
12816
|
// server/routes/tokens.ts
|
|
12294
12817
|
import { Router as Router9 } from "express";
|
|
12295
|
-
import
|
|
12296
|
-
import
|
|
12818
|
+
import path44 from "path";
|
|
12819
|
+
import fs34 from "fs";
|
|
12297
12820
|
var CHARS_PER_TOKEN = 4;
|
|
12298
12821
|
var cache = null;
|
|
12299
12822
|
function buildTokensRouter(ctx) {
|
|
@@ -12308,9 +12831,9 @@ function buildTokensRouter(ctx) {
|
|
|
12308
12831
|
let fullChars = 0;
|
|
12309
12832
|
let skeletonChars = 0;
|
|
12310
12833
|
for (const file of files) {
|
|
12311
|
-
const absPath =
|
|
12834
|
+
const absPath = path44.join(ctx.root, file);
|
|
12312
12835
|
try {
|
|
12313
|
-
const content =
|
|
12836
|
+
const content = fs34.readFileSync(absPath, "utf-8");
|
|
12314
12837
|
fullChars += content.length;
|
|
12315
12838
|
const skeleton = await skeletonizer.skeletonize(absPath);
|
|
12316
12839
|
skeletonChars += skeleton.length;
|
|
@@ -12411,17 +12934,17 @@ function buildFileTrendsRouter(ctx) {
|
|
|
12411
12934
|
|
|
12412
12935
|
// server/routes/projects.ts
|
|
12413
12936
|
import { Router as Router12 } from "express";
|
|
12414
|
-
import
|
|
12937
|
+
import path46 from "path";
|
|
12415
12938
|
|
|
12416
12939
|
// server/projects.ts
|
|
12417
12940
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
12418
12941
|
import os8 from "os";
|
|
12419
|
-
import
|
|
12942
|
+
import path45 from "path";
|
|
12420
12943
|
import crypto7 from "crypto";
|
|
12421
12944
|
var HOME = os8.homedir();
|
|
12422
|
-
var REGISTRY_PATH =
|
|
12945
|
+
var REGISTRY_PATH = path45.join(HOME, ".ctxloom", "repos.json");
|
|
12423
12946
|
function slugFor(root) {
|
|
12424
|
-
const abs =
|
|
12947
|
+
const abs = path45.resolve(root);
|
|
12425
12948
|
return crypto7.createHash("sha1").update(abs).digest("hex").slice(0, 12);
|
|
12426
12949
|
}
|
|
12427
12950
|
function readRegistry() {
|
|
@@ -12436,27 +12959,27 @@ function readRegistry() {
|
|
|
12436
12959
|
}
|
|
12437
12960
|
}
|
|
12438
12961
|
function listProjects(defaultRoot) {
|
|
12439
|
-
const absDefault =
|
|
12962
|
+
const absDefault = path45.resolve(defaultRoot);
|
|
12440
12963
|
const out = [
|
|
12441
12964
|
{
|
|
12442
12965
|
slug: slugFor(absDefault),
|
|
12443
|
-
name:
|
|
12966
|
+
name: path45.basename(absDefault) || absDefault,
|
|
12444
12967
|
root: absDefault,
|
|
12445
12968
|
isDefault: true,
|
|
12446
|
-
hasSnapshot: existsSync5(
|
|
12969
|
+
hasSnapshot: existsSync5(path45.join(absDefault, ".ctxloom"))
|
|
12447
12970
|
}
|
|
12448
12971
|
];
|
|
12449
12972
|
const seen = /* @__PURE__ */ new Set([absDefault]);
|
|
12450
12973
|
for (const entry of readRegistry()) {
|
|
12451
|
-
const abs =
|
|
12974
|
+
const abs = path45.resolve(entry.root);
|
|
12452
12975
|
if (seen.has(abs)) continue;
|
|
12453
12976
|
seen.add(abs);
|
|
12454
12977
|
const item = {
|
|
12455
12978
|
slug: slugFor(abs),
|
|
12456
|
-
name: entry.name ?? (
|
|
12979
|
+
name: entry.name ?? (path45.basename(abs) || abs),
|
|
12457
12980
|
root: abs,
|
|
12458
12981
|
isDefault: false,
|
|
12459
|
-
hasSnapshot: existsSync5(
|
|
12982
|
+
hasSnapshot: existsSync5(path45.join(abs, ".ctxloom"))
|
|
12460
12983
|
};
|
|
12461
12984
|
if (entry.alias !== void 0) item.alias = entry.alias;
|
|
12462
12985
|
out.push(item);
|
|
@@ -12509,7 +13032,7 @@ function buildProjectsRouter(deps) {
|
|
|
12509
13032
|
} catch (err) {
|
|
12510
13033
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12511
13034
|
res.status(500).json({
|
|
12512
|
-
error: `failed to switch to ${
|
|
13035
|
+
error: `failed to switch to ${path46.basename(target.root)}: ${detail}`
|
|
12513
13036
|
});
|
|
12514
13037
|
}
|
|
12515
13038
|
});
|
|
@@ -12619,7 +13142,7 @@ function buildBudgetEventsRouter() {
|
|
|
12619
13142
|
}
|
|
12620
13143
|
|
|
12621
13144
|
// server/index.ts
|
|
12622
|
-
var __dirname2 =
|
|
13145
|
+
var __dirname2 = path47.dirname(fileURLToPath2(import.meta.url));
|
|
12623
13146
|
async function startDashboard(options) {
|
|
12624
13147
|
const { root, port, open } = options;
|
|
12625
13148
|
console.log(`ctxloom dashboard \u2014 loading context from ${root}...`);
|
|
@@ -12679,9 +13202,9 @@ async function startDashboard(options) {
|
|
|
12679
13202
|
}
|
|
12680
13203
|
activeWatcher = null;
|
|
12681
13204
|
}
|
|
12682
|
-
const snapshotDir =
|
|
13205
|
+
const snapshotDir = path47.join(targetRoot, ".ctxloom");
|
|
12683
13206
|
try {
|
|
12684
|
-
activeWatcher =
|
|
13207
|
+
activeWatcher = fs35.watch(snapshotDir, (_event, filename) => {
|
|
12685
13208
|
if (!filename || !filename.includes("snapshot")) return;
|
|
12686
13209
|
if (debounce) clearTimeout(debounce);
|
|
12687
13210
|
debounce = setTimeout(async () => {
|
|
@@ -12710,12 +13233,12 @@ async function startDashboard(options) {
|
|
|
12710
13233
|
attachSnapshotWatcher(newRoot);
|
|
12711
13234
|
}
|
|
12712
13235
|
}));
|
|
12713
|
-
const clientDist =
|
|
12714
|
-
const clientDistExists =
|
|
13236
|
+
const clientDist = path47.join(__dirname2, "../dashboard/client");
|
|
13237
|
+
const clientDistExists = fs35.existsSync(path47.join(clientDist, "index.html"));
|
|
12715
13238
|
if (clientDistExists) {
|
|
12716
13239
|
app.use(express.static(clientDist, { dotfiles: "allow" }));
|
|
12717
13240
|
app.get(/.*/, (_req, res) => {
|
|
12718
|
-
res.sendFile(
|
|
13241
|
+
res.sendFile(path47.join(clientDist, "index.html"), { dotfiles: "allow" });
|
|
12719
13242
|
});
|
|
12720
13243
|
} else {
|
|
12721
13244
|
app.get(/^\/(?!api\/).*/, (_req, res) => {
|