@orquentra/codebuddy 2.7.0 → 2.9.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 +10 -0
- package/dist/adapters-S4HHTWJW.js +24 -0
- package/dist/{chunk-EEUFU7KV.js → chunk-2WH2Q4TA.js} +180 -22
- package/dist/chunk-2WH2Q4TA.js.map +1 -0
- package/dist/{chunk-BQM7FJ7D.js → chunk-6NZEZJYD.js} +5 -3
- package/dist/{chunk-BQM7FJ7D.js.map → chunk-6NZEZJYD.js.map} +1 -1
- package/dist/{chunk-AQHPEJR6.js → chunk-ENKDFLOO.js} +2 -2
- package/dist/{chunk-YJIA2MFL.js → chunk-F4BJ3DXW.js} +15 -13
- package/dist/{chunk-YJIA2MFL.js.map → chunk-F4BJ3DXW.js.map} +1 -1
- package/dist/{chunk-MSUAVR3R.js → chunk-GXOML4UZ.js} +2 -2
- package/dist/chunk-ITQO632W.js +673 -0
- package/dist/chunk-ITQO632W.js.map +1 -0
- package/dist/chunk-J4T6HHI4.js +1444 -0
- package/dist/chunk-J4T6HHI4.js.map +1 -0
- package/dist/{chunk-AK2UAE32.js → chunk-QN76UJP7.js} +2 -2
- package/dist/{chunk-JUAFCLRB.js → chunk-SRA4MDVG.js} +2 -2
- package/dist/{chunk-JUAFCLRB.js.map → chunk-SRA4MDVG.js.map} +1 -1
- package/dist/{chunk-5EDGAZQN.js → chunk-TLWGZVPC.js} +64 -725
- package/dist/chunk-TLWGZVPC.js.map +1 -0
- package/dist/{chunk-LCSBL5TT.js → chunk-UK6XTS2Y.js} +5 -3
- package/dist/{chunk-LCSBL5TT.js.map → chunk-UK6XTS2Y.js.map} +1 -1
- package/dist/chunk-W7GAXL6C.js +104 -0
- package/dist/chunk-W7GAXL6C.js.map +1 -0
- package/dist/{chunk-TUR6YKEE.js → chunk-YN7TEIUH.js} +1 -1
- package/dist/chunk-YN7TEIUH.js.map +1 -0
- package/dist/cli/index.js +4 -4
- package/dist/index.js +9 -8
- package/dist/install-BXM56QCD.js +19 -0
- package/dist/{lean-command-CWJAVQ5G.js → lean-command-7DFW32MA.js} +4 -3
- package/dist/{main-AHNGHC4L.js → main-YJKSYFPT.js} +1422 -1772
- package/dist/main-YJKSYFPT.js.map +1 -0
- package/dist/{operations-7BTAAUHF.js → operations-OG4ISWIM.js} +3 -3
- package/dist/{pretool-command-3SXQSMAO.js → pretool-command-PRFOIHMW.js} +3 -3
- package/dist/{runtime-E7JQZ2BZ.js → runtime-GCSEIAVD.js} +7 -6
- package/dist/{summaries-XV3JGGCC.js → summaries-ML53R7EL.js} +4 -3
- package/dist/summaries-ML53R7EL.js.map +1 -0
- package/dist/{zvec-KP4KNB7Y.js → zvec-XDZNZ2OL.js} +4 -3
- package/dist/zvec-XDZNZ2OL.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-5EDGAZQN.js.map +0 -1
- package/dist/chunk-EEUFU7KV.js.map +0 -1
- package/dist/chunk-TUR6YKEE.js.map +0 -1
- package/dist/main-AHNGHC4L.js.map +0 -1
- /package/dist/{lean-command-CWJAVQ5G.js.map → adapters-S4HHTWJW.js.map} +0 -0
- /package/dist/{chunk-AQHPEJR6.js.map → chunk-ENKDFLOO.js.map} +0 -0
- /package/dist/{chunk-MSUAVR3R.js.map → chunk-GXOML4UZ.js.map} +0 -0
- /package/dist/{chunk-AK2UAE32.js.map → chunk-QN76UJP7.js.map} +0 -0
- /package/dist/{operations-7BTAAUHF.js.map → install-BXM56QCD.js.map} +0 -0
- /package/dist/{pretool-command-3SXQSMAO.js.map → lean-command-7DFW32MA.js.map} +0 -0
- /package/dist/{runtime-E7JQZ2BZ.js.map → operations-OG4ISWIM.js.map} +0 -0
- /package/dist/{summaries-XV3JGGCC.js.map → pretool-command-PRFOIHMW.js.map} +0 -0
- /package/dist/{zvec-KP4KNB7Y.js.map → runtime-GCSEIAVD.js.map} +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-Y7D7PUBE.js";
|
|
2
|
+
loadArchitectureMap,
|
|
3
|
+
loadFreshIndex
|
|
4
|
+
} from "./chunk-ITQO632W.js";
|
|
6
5
|
import {
|
|
7
6
|
isCredentialPath
|
|
8
7
|
} from "./chunk-AL6VBTNA.js";
|
|
@@ -13,681 +12,26 @@ import {
|
|
|
13
12
|
readConfigFile
|
|
14
13
|
} from "./chunk-KNR2P2UY.js";
|
|
15
14
|
import {
|
|
16
|
-
assertNoSymlinkBetween,
|
|
17
15
|
writeAtomic
|
|
18
16
|
} from "./chunk-CG5B3LYG.js";
|
|
19
17
|
|
|
20
|
-
// src/map/indexer.ts
|
|
21
|
-
import { readdir, readFile, stat } from "fs/promises";
|
|
22
|
-
import { dirname, extname, join, relative, resolve } from "path";
|
|
23
|
-
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"]);
|
|
24
|
-
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
25
|
-
".git",
|
|
26
|
-
"node_modules",
|
|
27
|
-
"dist",
|
|
28
|
-
"build",
|
|
29
|
-
"coverage",
|
|
30
|
-
".codebuddy",
|
|
31
|
-
// Python virtualenvs and caches would otherwise map site-packages.
|
|
32
|
-
".venv",
|
|
33
|
-
"venv",
|
|
34
|
-
".tox",
|
|
35
|
-
"__pycache__"
|
|
36
|
-
]);
|
|
37
|
-
async function buildArchitectureMap(repositoryRoot = process.cwd()) {
|
|
38
|
-
const root = resolve(repositoryRoot);
|
|
39
|
-
const files = await listSourceFiles(root);
|
|
40
|
-
const fileSet = new Set(files.map((file) => normalize(relative(root, file))));
|
|
41
|
-
const modules = [];
|
|
42
|
-
const edges = [];
|
|
43
|
-
for (const absolutePath of files) {
|
|
44
|
-
const source = await readFile(absolutePath, "utf8");
|
|
45
|
-
const from = normalize(relative(root, absolutePath));
|
|
46
|
-
const resolvedImports = moduleImports(root, from, source, fileSet);
|
|
47
|
-
edges.push(...resolvedImports.map(({ to, kind }) => ({ from, to, kind })));
|
|
48
|
-
modules.push({
|
|
49
|
-
path: from,
|
|
50
|
-
language: moduleLanguage(from),
|
|
51
|
-
imports: [...new Set(resolvedImports.map(({ to }) => to))].sort()
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
modules: modules.sort((left, right) => left.path.localeCompare(right.path)),
|
|
56
|
-
edges: edges.sort(
|
|
57
|
-
(left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
|
|
58
|
-
)
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
async function updateArchitectureMap(repositoryRoot, sourcePaths, previous, changedPaths) {
|
|
62
|
-
const root = resolve(repositoryRoot);
|
|
63
|
-
const normalizedPaths = sourcePaths.map(normalize).sort();
|
|
64
|
-
const fileSet = new Set(normalizedPaths);
|
|
65
|
-
const changed = new Set(changedPaths.map(normalize));
|
|
66
|
-
const retainedModules = (previous?.modules ?? []).filter(
|
|
67
|
-
(module) => fileSet.has(module.path) && !changed.has(module.path)
|
|
68
|
-
);
|
|
69
|
-
const retainedEdges = (previous?.edges ?? []).filter(
|
|
70
|
-
(edge) => fileSet.has(edge.from) && fileSet.has(edge.to) && !changed.has(edge.from)
|
|
71
|
-
);
|
|
72
|
-
const modules = [...retainedModules];
|
|
73
|
-
const edges = [...retainedEdges];
|
|
74
|
-
for (const path of normalizedPaths) {
|
|
75
|
-
if (!changed.has(path)) continue;
|
|
76
|
-
const source = await readFile(join(root, path), "utf8");
|
|
77
|
-
const resolvedImports = moduleImports(root, path, source, fileSet);
|
|
78
|
-
edges.push(...resolvedImports.map(({ to, kind }) => ({ from: path, to, kind })));
|
|
79
|
-
modules.push({
|
|
80
|
-
path,
|
|
81
|
-
language: moduleLanguage(path),
|
|
82
|
-
imports: [...new Set(resolvedImports.map(({ to }) => to))].sort()
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
map: {
|
|
87
|
-
modules: modules.sort((left, right) => left.path.localeCompare(right.path)),
|
|
88
|
-
edges: edges.sort(
|
|
89
|
-
(left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
|
|
90
|
-
)
|
|
91
|
-
},
|
|
92
|
-
stats: {
|
|
93
|
-
readFiles: changed.size,
|
|
94
|
-
reusedFiles: retainedModules.length,
|
|
95
|
-
rebuilt: previous === null || changed.size === normalizedPaths.length
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
function queryMap(map, query) {
|
|
100
|
-
const limit = query.limit ?? 50;
|
|
101
|
-
return map.edges.filter((edge) => query.from ? edge.from === normalize(query.from) : true).filter((edge) => query.to ? edge.to === normalize(query.to) : true).slice(0, limit);
|
|
102
|
-
}
|
|
103
|
-
function neighbours(map, modulePath, direction = "both") {
|
|
104
|
-
const target = normalize(modulePath);
|
|
105
|
-
const edges = map.edges.filter((edge) => {
|
|
106
|
-
if (direction === "in") return edge.to === target;
|
|
107
|
-
if (direction === "out") return edge.from === target;
|
|
108
|
-
return edge.from === target || edge.to === target;
|
|
109
|
-
});
|
|
110
|
-
const paths = /* @__PURE__ */ new Set([target]);
|
|
111
|
-
for (const edge of edges) {
|
|
112
|
-
paths.add(edge.from);
|
|
113
|
-
paths.add(edge.to);
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
modules: map.modules.filter((module) => paths.has(module.path)),
|
|
117
|
-
edges
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
async function listSourceFiles(root) {
|
|
121
|
-
const files = [];
|
|
122
|
-
await walk(root, files);
|
|
123
|
-
return files.sort();
|
|
124
|
-
}
|
|
125
|
-
async function walk(directory, files) {
|
|
126
|
-
const entries = await readdir(directory, { withFileTypes: true });
|
|
127
|
-
for (const entry of entries) {
|
|
128
|
-
const absolute = join(directory, entry.name);
|
|
129
|
-
if (entry.isDirectory()) {
|
|
130
|
-
if (IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
131
|
-
await walk(absolute, files);
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
if (!entry.isFile()) continue;
|
|
135
|
-
if (!SUPPORTED_EXTENSIONS.has(extname(entry.name))) continue;
|
|
136
|
-
const info = await stat(absolute);
|
|
137
|
-
if (info.size > 5e5) continue;
|
|
138
|
-
files.push(absolute);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function moduleLanguage(path) {
|
|
142
|
-
const extension = extname(path);
|
|
143
|
-
if (extension === ".py") return "py";
|
|
144
|
-
return extension.includes("ts") ? "ts" : "js";
|
|
145
|
-
}
|
|
146
|
-
function moduleImports(root, from, source, fileSet) {
|
|
147
|
-
if (extname(from) === ".py") {
|
|
148
|
-
return pythonImports(from, source, fileSet).map((to) => ({ to, kind: "import" }));
|
|
149
|
-
}
|
|
150
|
-
return extractImports(source).flatMap((specifier) => {
|
|
151
|
-
const to = resolveImport(root, from, specifier.value, fileSet);
|
|
152
|
-
return to ? [{ to, kind: specifier.kind }] : [];
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
var PYTHON_ROOTS = ["", "src/"];
|
|
156
|
-
function pythonImports(from, source, fileSet) {
|
|
157
|
-
const pick = (...candidates) => candidates.find((candidate) => fileSet.has(candidate)) ?? null;
|
|
158
|
-
const moduleFile = (base) => pick(`${base}.py`, `${base}/__init__.py`);
|
|
159
|
-
const packageDir = from.split("/").slice(0, -1);
|
|
160
|
-
const targets = [];
|
|
161
|
-
for (const statement of extractPythonImports(source)) {
|
|
162
|
-
const parts = statement.module ? statement.module.split(".") : [];
|
|
163
|
-
let bases;
|
|
164
|
-
if (statement.level > 0) {
|
|
165
|
-
const up = statement.level - 1;
|
|
166
|
-
if (up > packageDir.length) continue;
|
|
167
|
-
bases = [[...packageDir.slice(0, packageDir.length - up), ...parts].join("/")];
|
|
168
|
-
} else {
|
|
169
|
-
bases = PYTHON_ROOTS.map((prefix) => `${prefix}${parts.join("/")}`);
|
|
170
|
-
}
|
|
171
|
-
for (const base of bases) {
|
|
172
|
-
const prefix = base ? `${base}/` : "";
|
|
173
|
-
const module = parts.length > 0 ? moduleFile(base) : pick(`${prefix}__init__.py`);
|
|
174
|
-
const submodules = statement.names.flatMap((name) => moduleFile(`${prefix}${name}`) ?? []);
|
|
175
|
-
if (!module && submodules.length === 0) continue;
|
|
176
|
-
targets.push(...submodules);
|
|
177
|
-
if (module && submodules.length < Math.max(1, statement.names.length)) targets.push(module);
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return [...new Set(targets)].filter((target) => target !== from);
|
|
182
|
-
}
|
|
183
|
-
function extractImports(source) {
|
|
184
|
-
const imports = [];
|
|
185
|
-
const stripped = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1 ");
|
|
186
|
-
const staticImport = /\bimport\s+(?:type\s+)?(?:[^'"()+]+?\s+from\s+)?["']([^"']+)["']/g;
|
|
187
|
-
const exportFrom = /\bexport\s+[^'"()+]+?\s+from\s+["']([^"']+)["']/g;
|
|
188
|
-
const dynamicImport = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
189
|
-
for (const match of stripped.matchAll(staticImport)) {
|
|
190
|
-
if (match[1]) imports.push({ value: match[1], kind: "import" });
|
|
191
|
-
}
|
|
192
|
-
for (const match of stripped.matchAll(exportFrom)) {
|
|
193
|
-
if (match[1]) imports.push({ value: match[1], kind: "import" });
|
|
194
|
-
}
|
|
195
|
-
for (const match of stripped.matchAll(dynamicImport)) {
|
|
196
|
-
if (match[1]) imports.push({ value: match[1], kind: "dynamic_import" });
|
|
197
|
-
}
|
|
198
|
-
return imports;
|
|
199
|
-
}
|
|
200
|
-
function resolveImport(root, from, specifier, fileSet) {
|
|
201
|
-
if (!specifier.startsWith(".")) return null;
|
|
202
|
-
const base = normalize(relative(root, resolve(root, dirname(from), specifier)));
|
|
203
|
-
const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
|
|
204
|
-
const bases = stem === base ? [base] : [base, stem];
|
|
205
|
-
const candidates = bases.flatMap((b) => [
|
|
206
|
-
b,
|
|
207
|
-
`${b}.ts`,
|
|
208
|
-
`${b}.tsx`,
|
|
209
|
-
`${b}.js`,
|
|
210
|
-
`${b}.jsx`,
|
|
211
|
-
`${b}.mjs`,
|
|
212
|
-
`${b}.cjs`,
|
|
213
|
-
`${b}/index.ts`,
|
|
214
|
-
`${b}/index.tsx`,
|
|
215
|
-
`${b}/index.js`,
|
|
216
|
-
`${b}/index.jsx`
|
|
217
|
-
]);
|
|
218
|
-
return candidates.find((candidate) => fileSet.has(candidate)) ?? null;
|
|
219
|
-
}
|
|
220
|
-
function normalize(path) {
|
|
221
|
-
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// src/indexer/engine.ts
|
|
225
|
-
import { createHash } from "crypto";
|
|
226
|
-
import { readFile as readFile4, stat as stat2 } from "fs/promises";
|
|
227
|
-
import { join as join5, resolve as resolve4 } from "path";
|
|
228
|
-
|
|
229
|
-
// src/map/cache.ts
|
|
230
|
-
import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
|
|
231
|
-
import { dirname as dirname3, extname as extname2, join as join3, resolve as resolve3 } from "path";
|
|
232
|
-
|
|
233
|
-
// src/indexer/store.ts
|
|
234
|
-
import { mkdir, readFile as readFile2 } from "fs/promises";
|
|
235
|
-
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
236
|
-
import { z } from "zod";
|
|
237
|
-
|
|
238
|
-
// src/indexer/types.ts
|
|
239
|
-
var INDEX_MANIFEST_VERSION = 3;
|
|
240
|
-
|
|
241
|
-
// src/indexer/store.ts
|
|
242
|
-
var symbolSpanSchema = z.object({
|
|
243
|
-
name: z.string(),
|
|
244
|
-
kind: z.enum(["function", "method", "class", "interface", "type", "enum", "const", "heading"]),
|
|
245
|
-
startLine: z.number().int().nonnegative(),
|
|
246
|
-
endLine: z.number().int().nonnegative(),
|
|
247
|
-
signature: z.string(),
|
|
248
|
-
exported: z.boolean()
|
|
249
|
-
});
|
|
250
|
-
var entrySchema = z.object({
|
|
251
|
-
path: z.string(),
|
|
252
|
-
hash: z.string(),
|
|
253
|
-
size: z.number().int().nonnegative(),
|
|
254
|
-
mtimeMs: z.number().optional(),
|
|
255
|
-
ctimeMs: z.number().optional(),
|
|
256
|
-
language: z.string(),
|
|
257
|
-
lines: z.number().int().nonnegative(),
|
|
258
|
-
symbols: z.array(z.string()),
|
|
259
|
-
symbolTable: z.array(symbolSpanSchema).optional(),
|
|
260
|
-
summary: z.string(),
|
|
261
|
-
indexedAt: z.string()
|
|
262
|
-
});
|
|
263
|
-
var manifestSchema = z.object({
|
|
264
|
-
version: z.literal(INDEX_MANIFEST_VERSION),
|
|
265
|
-
generatedAt: z.string(),
|
|
266
|
-
entries: z.record(z.string(), entrySchema)
|
|
267
|
-
});
|
|
268
|
-
var IndexStore = class {
|
|
269
|
-
repositoryRoot;
|
|
270
|
-
cacheDirectory;
|
|
271
|
-
manifestPath;
|
|
272
|
-
constructor(repositoryRoot = process.cwd()) {
|
|
273
|
-
this.repositoryRoot = resolve2(repositoryRoot);
|
|
274
|
-
this.cacheDirectory = join2(this.repositoryRoot, ".codebuddy", "cache");
|
|
275
|
-
this.manifestPath = join2(this.cacheDirectory, "index.json");
|
|
276
|
-
}
|
|
277
|
-
/** Read the manifest, or an empty one if absent/corrupt/version-mismatched. */
|
|
278
|
-
async read() {
|
|
279
|
-
try {
|
|
280
|
-
const raw = await readFile2(this.manifestPath, "utf8");
|
|
281
|
-
const parsed = manifestSchema.safeParse(JSON.parse(raw));
|
|
282
|
-
if (!parsed.success) return emptyManifest();
|
|
283
|
-
return parsed.data;
|
|
284
|
-
} catch (error) {
|
|
285
|
-
if (error.code === "ENOENT") return emptyManifest();
|
|
286
|
-
return emptyManifest();
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
async write(manifest) {
|
|
290
|
-
await assertNoSymlinkBetween(this.repositoryRoot, dirname2(this.manifestPath));
|
|
291
|
-
await mkdir(this.cacheDirectory, { recursive: true, mode: 448 });
|
|
292
|
-
await writeAtomic(this.manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
293
|
-
`);
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
function emptyManifest() {
|
|
297
|
-
return { version: INDEX_MANIFEST_VERSION, generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(), entries: {} };
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// src/map/cache.ts
|
|
301
|
-
var CACHE_VERSION = 1;
|
|
302
|
-
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"]);
|
|
303
|
-
async function loadArchitectureMap(repositoryRoot = process.cwd(), suppliedManifest) {
|
|
304
|
-
const root = resolve3(repositoryRoot);
|
|
305
|
-
const manifest = suppliedManifest ?? await new IndexStore(root).read();
|
|
306
|
-
if (Object.keys(manifest.entries).length === 0) {
|
|
307
|
-
const map = await buildArchitectureMap(root);
|
|
308
|
-
return {
|
|
309
|
-
map,
|
|
310
|
-
stats: { readFiles: map.modules.length, reusedFiles: 0, rebuilt: true },
|
|
311
|
-
source: "fallback"
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
return refreshArchitectureCache(root, manifest);
|
|
315
|
-
}
|
|
316
|
-
async function refreshArchitectureCache(repositoryRoot, manifest) {
|
|
317
|
-
const root = resolve3(repositoryRoot);
|
|
318
|
-
const path = join3(root, ".codebuddy", "cache", "architecture.json");
|
|
319
|
-
const currentHashes = sourceHashes(manifest);
|
|
320
|
-
const currentPaths = Object.keys(currentHashes).sort();
|
|
321
|
-
const cached = await readCache(path);
|
|
322
|
-
const cachedPaths = cached ? Object.keys(cached.sourceHashes).sort() : [];
|
|
323
|
-
const fileSetChanged = cachedPaths.join("\n") !== currentPaths.join("\n");
|
|
324
|
-
const changedPaths = !cached || fileSetChanged ? currentPaths : currentPaths.filter(
|
|
325
|
-
(sourcePath) => cached.sourceHashes[sourcePath] !== currentHashes[sourcePath]
|
|
326
|
-
);
|
|
327
|
-
if (cached && changedPaths.length === 0) {
|
|
328
|
-
return {
|
|
329
|
-
map: cached.map,
|
|
330
|
-
stats: { readFiles: 0, reusedFiles: cached.map.modules.length, rebuilt: false },
|
|
331
|
-
source: "cache"
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
const updated = await updateArchitectureMap(
|
|
335
|
-
root,
|
|
336
|
-
currentPaths,
|
|
337
|
-
fileSetChanged ? null : cached?.map ?? null,
|
|
338
|
-
changedPaths
|
|
339
|
-
);
|
|
340
|
-
const next = {
|
|
341
|
-
version: CACHE_VERSION,
|
|
342
|
-
generatedAt: manifest.generatedAt,
|
|
343
|
-
sourceHashes: currentHashes,
|
|
344
|
-
map: updated.map
|
|
345
|
-
};
|
|
346
|
-
await assertNoSymlinkBetween(root, dirname3(path));
|
|
347
|
-
await mkdir2(dirname3(path), { recursive: true, mode: 448 });
|
|
348
|
-
await writeAtomic(path, `${JSON.stringify(next, null, 2)}
|
|
349
|
-
`);
|
|
350
|
-
return { ...updated, source: fileSetChanged || !cached ? "rebuild" : "incremental" };
|
|
351
|
-
}
|
|
352
|
-
async function readCache(path) {
|
|
353
|
-
try {
|
|
354
|
-
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
355
|
-
if (parsed.version !== CACHE_VERSION || !parsed.map || !parsed.sourceHashes) return null;
|
|
356
|
-
if (!Array.isArray(parsed.map.modules) || !Array.isArray(parsed.map.edges)) return null;
|
|
357
|
-
if (!parsed.map.modules.every(
|
|
358
|
-
(module) => module && typeof module.path === "string" && typeof module.language === "string" && Array.isArray(module.imports)
|
|
359
|
-
) || !parsed.map.edges.every(
|
|
360
|
-
(edge) => edge && typeof edge.from === "string" && typeof edge.to === "string"
|
|
361
|
-
)) {
|
|
362
|
-
return null;
|
|
363
|
-
}
|
|
364
|
-
return parsed;
|
|
365
|
-
} catch {
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
function sourceHashes(manifest) {
|
|
370
|
-
return Object.fromEntries(
|
|
371
|
-
Object.values(manifest.entries).filter((entry) => SOURCE_EXTENSIONS.has(extname2(entry.path).toLowerCase())).map((entry) => [entry.path, entry.hash])
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// src/indexer/scan.ts
|
|
376
|
-
import { spawn } from "child_process";
|
|
377
|
-
import { readdir as readdir2 } from "fs/promises";
|
|
378
|
-
import { extname as extname4, join as join4, relative as relative2, sep } from "path";
|
|
379
|
-
|
|
380
|
-
// src/indexer/summary.ts
|
|
381
|
-
import { extname as extname3 } from "path";
|
|
382
|
-
var LANGUAGE_BY_EXTENSION = {
|
|
383
|
-
".ts": "ts",
|
|
384
|
-
".tsx": "tsx",
|
|
385
|
-
".js": "js",
|
|
386
|
-
".jsx": "jsx",
|
|
387
|
-
".mjs": "js",
|
|
388
|
-
".cjs": "js",
|
|
389
|
-
".json": "json",
|
|
390
|
-
".md": "md",
|
|
391
|
-
".mdx": "md",
|
|
392
|
-
".rst": "rst",
|
|
393
|
-
".yaml": "yaml",
|
|
394
|
-
".yml": "yaml",
|
|
395
|
-
".toml": "toml",
|
|
396
|
-
".css": "css",
|
|
397
|
-
".scss": "scss",
|
|
398
|
-
".html": "html",
|
|
399
|
-
".py": "py",
|
|
400
|
-
".go": "go",
|
|
401
|
-
".rs": "rust",
|
|
402
|
-
".java": "java",
|
|
403
|
-
".rb": "ruby",
|
|
404
|
-
".php": "php",
|
|
405
|
-
".sh": "shell",
|
|
406
|
-
".sql": "sql",
|
|
407
|
-
".txt": "text"
|
|
408
|
-
};
|
|
409
|
-
var SUPPORTED_EXTENSIONS2 = new Set(Object.keys(LANGUAGE_BY_EXTENSION));
|
|
410
|
-
var CODE_LANGUAGES = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx"]);
|
|
411
|
-
var SYMBOL_LIMIT = 8;
|
|
412
|
-
var SUMMARY_COMMENT_MAX = 80;
|
|
413
|
-
function languageFor(path) {
|
|
414
|
-
return LANGUAGE_BY_EXTENSION[extname3(path).toLowerCase()] ?? "text";
|
|
415
|
-
}
|
|
416
|
-
function summarizeSource(path, content) {
|
|
417
|
-
const language = languageFor(path);
|
|
418
|
-
const lines = countLines(content);
|
|
419
|
-
const symbols = CODE_LANGUAGES.has(language) ? extractSymbols(content) : language === "py" ? pythonPublicNames(content) : [];
|
|
420
|
-
const summary = composeSummary(language, lines, symbols, content);
|
|
421
|
-
return { language, lines, symbols, summary };
|
|
422
|
-
}
|
|
423
|
-
function countLines(content) {
|
|
424
|
-
if (content.length === 0) return 0;
|
|
425
|
-
return content.replace(/\r/g, "").replace(/\n$/, "").split("\n").length;
|
|
426
|
-
}
|
|
427
|
-
function extractSymbols(content) {
|
|
428
|
-
const names = [];
|
|
429
|
-
const push = (name) => {
|
|
430
|
-
const trimmed = name?.trim();
|
|
431
|
-
if (trimmed && /^[A-Za-z_$][\w$]*$/.test(trimmed) && !names.includes(trimmed)) {
|
|
432
|
-
names.push(trimmed);
|
|
433
|
-
}
|
|
434
|
-
};
|
|
435
|
-
const declaration = /^export\s+(?:default\s+)?(?:async\s+)?(?:abstract\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/gm;
|
|
436
|
-
for (const match of content.matchAll(declaration)) push(match[1]);
|
|
437
|
-
const namedExports = /^export\s*\{([^}]*)\}/gm;
|
|
438
|
-
for (const match of content.matchAll(namedExports)) {
|
|
439
|
-
for (const part of (match[1] ?? "").split(",")) {
|
|
440
|
-
const alias = part.includes(" as ") ? part.split(" as ").pop() : part;
|
|
441
|
-
push(alias);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return names.slice(0, SYMBOL_LIMIT);
|
|
445
|
-
}
|
|
446
|
-
function pythonPublicNames(content) {
|
|
447
|
-
return extractSymbolTable(content, "x.py").filter((symbol) => symbol.exported && symbol.kind !== "method").map((symbol) => symbol.name).slice(0, SYMBOL_LIMIT);
|
|
448
|
-
}
|
|
449
|
-
function composeSummary(language, lines, symbols, content) {
|
|
450
|
-
const head = `${language} \u2022 ${lines} line${lines === 1 ? "" : "s"}`;
|
|
451
|
-
if (symbols.length > 0) return `${head} \u2022 exports ${symbols.join(", ")}`;
|
|
452
|
-
const comment = leadingComment(content);
|
|
453
|
-
if (comment) return `${head} \u2022 ${comment}`;
|
|
454
|
-
return head;
|
|
455
|
-
}
|
|
456
|
-
function leadingComment(content) {
|
|
457
|
-
for (const raw of content.split(/\r?\n/)) {
|
|
458
|
-
const line = raw.trim();
|
|
459
|
-
if (line === "") continue;
|
|
460
|
-
const stripped = line.replace(/^\/\/+/, "").replace(/^\/\*+/, "").replace(/\*+\/$/, "").replace(/^[#*]+/, "").trim();
|
|
461
|
-
if (line.startsWith("//") || line.startsWith("/*") || line.startsWith("#") || line.startsWith("*")) {
|
|
462
|
-
if (stripped) return truncate(stripped, SUMMARY_COMMENT_MAX);
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
return null;
|
|
466
|
-
}
|
|
467
|
-
return null;
|
|
468
|
-
}
|
|
469
|
-
function truncate(value, max) {
|
|
470
|
-
return value.length <= max ? value : `${value.slice(0, max - 1)}\u2026`;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// src/indexer/scan.ts
|
|
474
|
-
var FALLBACK_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
475
|
-
".git",
|
|
476
|
-
"node_modules",
|
|
477
|
-
"dist",
|
|
478
|
-
"build",
|
|
479
|
-
"coverage",
|
|
480
|
-
".codebuddy",
|
|
481
|
-
"graphify-out",
|
|
482
|
-
".next",
|
|
483
|
-
".turbo",
|
|
484
|
-
"out",
|
|
485
|
-
".venv",
|
|
486
|
-
"venv",
|
|
487
|
-
"__pycache__"
|
|
488
|
-
]);
|
|
489
|
-
async function listIndexableFiles(repositoryRoot) {
|
|
490
|
-
const tracked = await gitListFiles(repositoryRoot);
|
|
491
|
-
const paths = tracked ?? await walkFiles(repositoryRoot);
|
|
492
|
-
return paths.map(normalize2).filter((path) => !path.startsWith(".codebuddy/") && !path.startsWith("graphify-out/")).filter((path) => SUPPORTED_EXTENSIONS2.has(extname4(path).toLowerCase())).sort();
|
|
493
|
-
}
|
|
494
|
-
async function gitListFiles(repositoryRoot) {
|
|
495
|
-
const result = await runGit(repositoryRoot, [
|
|
496
|
-
"ls-files",
|
|
497
|
-
"-z",
|
|
498
|
-
"--cached",
|
|
499
|
-
"--others",
|
|
500
|
-
"--exclude-standard"
|
|
501
|
-
]);
|
|
502
|
-
if (result.code !== 0) return null;
|
|
503
|
-
return result.stdout.split("\0").filter(Boolean);
|
|
504
|
-
}
|
|
505
|
-
async function walkFiles(root) {
|
|
506
|
-
const files = [];
|
|
507
|
-
const walk2 = async (directory) => {
|
|
508
|
-
const entries = await readdir2(directory, { withFileTypes: true });
|
|
509
|
-
for (const entry of entries) {
|
|
510
|
-
const absolute = join4(directory, entry.name);
|
|
511
|
-
if (entry.isDirectory()) {
|
|
512
|
-
if (FALLBACK_IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
513
|
-
await walk2(absolute);
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
if (entry.isFile()) files.push(relative2(root, absolute));
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
await walk2(root);
|
|
520
|
-
return files;
|
|
521
|
-
}
|
|
522
|
-
function runGit(cwd, args) {
|
|
523
|
-
return new Promise((resolveResult) => {
|
|
524
|
-
const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
525
|
-
let stdout = "";
|
|
526
|
-
let stderr = "";
|
|
527
|
-
child.stdout.setEncoding("utf8");
|
|
528
|
-
child.stdout.on("data", (chunk2) => {
|
|
529
|
-
stdout += String(chunk2);
|
|
530
|
-
});
|
|
531
|
-
child.stderr.on("data", (chunk2) => {
|
|
532
|
-
stderr += String(chunk2);
|
|
533
|
-
});
|
|
534
|
-
child.on("error", () => resolveResult({ code: -1, stdout: "", stderr: "git unavailable" }));
|
|
535
|
-
child.on("close", (code) => resolveResult({ code: code ?? 0, stdout, stderr }));
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
function normalize2(path) {
|
|
539
|
-
return (sep === "\\" ? path.replace(/\\/g, "/") : path).replace(/^\.\//, "");
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// src/indexer/engine.ts
|
|
543
|
-
var MAX_FILE_BYTES = 512e3;
|
|
544
|
-
var RACY_WINDOW_MS = 3e3;
|
|
545
|
-
async function indexRepository(options = {}) {
|
|
546
|
-
return (await refreshIndex(options)).result;
|
|
547
|
-
}
|
|
548
|
-
async function loadFreshIndex(repositoryRoot) {
|
|
549
|
-
try {
|
|
550
|
-
const { result, manifest } = await refreshIndex({ repositoryRoot });
|
|
551
|
-
const delta = result.added + result.changed + result.removed;
|
|
552
|
-
const failures = result.errors.length > 0 ? `; ${result.errors.length} file(s) could not be validated and were omitted` : "";
|
|
553
|
-
return {
|
|
554
|
-
manifest,
|
|
555
|
-
explain: delta === 0 ? `index: ${result.totalIndexed} indexed file(s) match current source${failures}` : `index: refreshed against current source (${result.changed} changed, ${result.added} added, ${result.removed} removed; ${result.totalIndexed} indexed)${failures}`
|
|
556
|
-
};
|
|
557
|
-
} catch (error) {
|
|
558
|
-
return {
|
|
559
|
-
manifest: emptyManifest(),
|
|
560
|
-
explain: `index: freshness check failed (${error.message}); indexed evidence omitted`
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
var passes = /* @__PURE__ */ new Map();
|
|
565
|
-
function refreshIndex(options) {
|
|
566
|
-
const key = resolve4(options.repositoryRoot ?? process.cwd());
|
|
567
|
-
const next = (passes.get(key) ?? Promise.resolve()).catch(() => void 0).then(() => runIndexPass(options));
|
|
568
|
-
passes.set(key, next);
|
|
569
|
-
void next.finally(() => {
|
|
570
|
-
if (passes.get(key) === next) passes.delete(key);
|
|
571
|
-
}).catch(() => void 0);
|
|
572
|
-
return next;
|
|
573
|
-
}
|
|
574
|
-
async function runIndexPass(options) {
|
|
575
|
-
const start = Date.now();
|
|
576
|
-
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
577
|
-
const repositoryRoot = resolve4(options.repositoryRoot ?? process.cwd());
|
|
578
|
-
const store = new IndexStore(repositoryRoot);
|
|
579
|
-
const [files, previous] = await Promise.all([listIndexableFiles(repositoryRoot), store.read()]);
|
|
580
|
-
const entries = {};
|
|
581
|
-
const errors = [];
|
|
582
|
-
let added = 0;
|
|
583
|
-
let changed = 0;
|
|
584
|
-
let unchanged = 0;
|
|
585
|
-
let skipped = 0;
|
|
586
|
-
let dirty = previous.generatedAt === emptyManifest().generatedAt;
|
|
587
|
-
for (const path of files) {
|
|
588
|
-
try {
|
|
589
|
-
const info = await stat2(join5(repositoryRoot, path));
|
|
590
|
-
if (!info.isFile()) continue;
|
|
591
|
-
if (info.size > MAX_FILE_BYTES) {
|
|
592
|
-
skipped++;
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
const existing = previous.entries[path];
|
|
596
|
-
if (!options.full && existing && statUnchanged(existing, info)) {
|
|
597
|
-
entries[path] = existing;
|
|
598
|
-
unchanged++;
|
|
599
|
-
continue;
|
|
600
|
-
}
|
|
601
|
-
const content = await readFile4(join5(repositoryRoot, path), "utf8");
|
|
602
|
-
const hash = hashContent(content);
|
|
603
|
-
const metadata = trustedMetadata(info);
|
|
604
|
-
dirty = true;
|
|
605
|
-
if (!options.full && existing && existing.hash === hash) {
|
|
606
|
-
const { mtimeMs: _mtime, ctimeMs: _ctime, ...rest } = existing;
|
|
607
|
-
entries[path] = { ...rest, size: info.size, ...metadata };
|
|
608
|
-
unchanged++;
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
const { language, lines, symbols, summary } = summarizeSource(path, content);
|
|
612
|
-
const symbolTable = supportsSymbols(path) ? extractSymbolTable(content, path) : [];
|
|
613
|
-
entries[path] = {
|
|
614
|
-
path,
|
|
615
|
-
hash,
|
|
616
|
-
size: info.size,
|
|
617
|
-
...metadata,
|
|
618
|
-
language,
|
|
619
|
-
lines,
|
|
620
|
-
symbols,
|
|
621
|
-
...symbolTable.length > 0 ? { symbolTable } : {},
|
|
622
|
-
summary,
|
|
623
|
-
indexedAt: now().toISOString()
|
|
624
|
-
};
|
|
625
|
-
if (existing) changed++;
|
|
626
|
-
else added++;
|
|
627
|
-
} catch (error) {
|
|
628
|
-
errors.push({ path, error: error.message });
|
|
629
|
-
skipped++;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
const removed = Object.keys(previous.entries).filter((path) => !(path in entries)).length;
|
|
633
|
-
const manifest = {
|
|
634
|
-
version: INDEX_MANIFEST_VERSION,
|
|
635
|
-
generatedAt: now().toISOString(),
|
|
636
|
-
entries: sortEntries(entries)
|
|
637
|
-
};
|
|
638
|
-
if (dirty || removed > 0) await store.write(manifest);
|
|
639
|
-
const architecture = await refreshArchitectureCache(repositoryRoot, manifest);
|
|
640
|
-
const result = {
|
|
641
|
-
scanned: files.length,
|
|
642
|
-
added,
|
|
643
|
-
changed,
|
|
644
|
-
unchanged,
|
|
645
|
-
removed,
|
|
646
|
-
skipped,
|
|
647
|
-
errors,
|
|
648
|
-
durationMs: Date.now() - start,
|
|
649
|
-
totalIndexed: Object.keys(entries).length,
|
|
650
|
-
architecture: {
|
|
651
|
-
source: architecture.source,
|
|
652
|
-
readFiles: architecture.stats.readFiles,
|
|
653
|
-
reusedFiles: architecture.stats.reusedFiles
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
|
-
return { result, manifest };
|
|
657
|
-
}
|
|
658
|
-
function statUnchanged(entry, info) {
|
|
659
|
-
return entry.mtimeMs !== void 0 && entry.size === info.size && entry.mtimeMs === info.mtimeMs && entry.ctimeMs === info.ctimeMs;
|
|
660
|
-
}
|
|
661
|
-
function trustedMetadata(info) {
|
|
662
|
-
if (Date.now() - Math.max(info.mtimeMs, info.ctimeMs) < RACY_WINDOW_MS) return {};
|
|
663
|
-
return { mtimeMs: info.mtimeMs, ctimeMs: info.ctimeMs };
|
|
664
|
-
}
|
|
665
|
-
function hashContent(content) {
|
|
666
|
-
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
667
|
-
}
|
|
668
|
-
function sortEntries(entries) {
|
|
669
|
-
return Object.fromEntries(
|
|
670
|
-
Object.keys(entries).sort().map((key) => [key, entries[key]])
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
18
|
// src/integrations/graphify.ts
|
|
675
19
|
import { execFile } from "child_process";
|
|
676
|
-
import { mkdir
|
|
20
|
+
import { mkdir, mkdtemp, readFile as readFile2, rm, stat, utimes } from "fs/promises";
|
|
677
21
|
import { tmpdir } from "os";
|
|
678
|
-
import { dirname
|
|
22
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
679
23
|
import { promisify } from "util";
|
|
680
24
|
|
|
681
25
|
// src/map/graph-freshness.ts
|
|
682
|
-
import { createHash
|
|
683
|
-
import { readFile
|
|
684
|
-
var digest = (value) =>
|
|
26
|
+
import { createHash } from "crypto";
|
|
27
|
+
import { readFile } from "fs/promises";
|
|
28
|
+
var digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
685
29
|
async function writeGraphSourceSnapshot(path, manifest) {
|
|
686
30
|
await writeAtomic(
|
|
687
31
|
`${path}.sources.json`,
|
|
688
32
|
JSON.stringify({
|
|
689
33
|
version: 1,
|
|
690
|
-
graphHash: digest(await
|
|
34
|
+
graphHash: digest(await readFile(path, "utf8")),
|
|
691
35
|
sources: Object.fromEntries(
|
|
692
36
|
Object.values(manifest.entries).map((entry) => [entry.path, entry.hash])
|
|
693
37
|
)
|
|
@@ -697,13 +41,13 @@ async function writeGraphSourceSnapshot(path, manifest) {
|
|
|
697
41
|
async function readGraphSourceSnapshot(path) {
|
|
698
42
|
let raw;
|
|
699
43
|
try {
|
|
700
|
-
raw = await
|
|
44
|
+
raw = await readFile(`${path}.sources.json`, "utf8");
|
|
701
45
|
} catch (error) {
|
|
702
46
|
return error.code === "ENOENT" ? null : {};
|
|
703
47
|
}
|
|
704
48
|
try {
|
|
705
49
|
const snapshot = JSON.parse(raw);
|
|
706
|
-
if (snapshot.version !== 1 || snapshot.graphHash !== digest(await
|
|
50
|
+
if (snapshot.version !== 1 || snapshot.graphHash !== digest(await readFile(path, "utf8")) || !snapshot.sources || typeof snapshot.sources !== "object" || Array.isArray(snapshot.sources))
|
|
707
51
|
return {};
|
|
708
52
|
return Object.fromEntries(
|
|
709
53
|
Object.entries(snapshot.sources).filter(
|
|
@@ -790,17 +134,17 @@ async function graphifyGraphExists(repositoryRoot, graphPath = DEFAULT_GRAPH_PAT
|
|
|
790
134
|
}
|
|
791
135
|
async function generateGraphifyGraph(options) {
|
|
792
136
|
const run = options.run ?? runCommand;
|
|
793
|
-
const repositoryRoot =
|
|
137
|
+
const repositoryRoot = resolve(options.repositoryRoot);
|
|
794
138
|
const graphPath = options.graphPath ?? DEFAULT_GRAPH_PATH;
|
|
795
139
|
const destination = resolveGraphPath(repositoryRoot, graphPath);
|
|
796
|
-
await
|
|
797
|
-
await ignoreGeneratedGraphDirectory(
|
|
140
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
141
|
+
await ignoreGeneratedGraphDirectory(dirname(destination));
|
|
798
142
|
const { manifest: sourceSnapshot } = await loadFreshIndex(repositoryRoot);
|
|
799
143
|
const current = await readGraphFileIfPresent(destination);
|
|
800
144
|
const isDefaultPath = graphPath === DEFAULT_GRAPH_PATH;
|
|
801
145
|
if (current && isDefaultPath) {
|
|
802
|
-
const backup = await
|
|
803
|
-
const backupStat = await
|
|
146
|
+
const backup = await readFile2(destination, "utf8");
|
|
147
|
+
const backupStat = await stat(destination);
|
|
804
148
|
try {
|
|
805
149
|
await run("graphify", ["update", repositoryRoot, "--force", "--no-cluster"], repositoryRoot);
|
|
806
150
|
const updated = await readGraphFile(destination);
|
|
@@ -814,16 +158,16 @@ async function generateGraphifyGraph(options) {
|
|
|
814
158
|
);
|
|
815
159
|
}
|
|
816
160
|
}
|
|
817
|
-
const stagingRoot = await mkdtemp(
|
|
161
|
+
const stagingRoot = await mkdtemp(join(tmpdir(), "codebuddy-graphify-"));
|
|
818
162
|
try {
|
|
819
163
|
await run(
|
|
820
164
|
"graphify",
|
|
821
165
|
["extract", repositoryRoot, "--out", stagingRoot, "--code-only", "--no-cluster"],
|
|
822
166
|
repositoryRoot
|
|
823
167
|
);
|
|
824
|
-
const stagedPath =
|
|
168
|
+
const stagedPath = join(stagingRoot, DEFAULT_GRAPH_PATH);
|
|
825
169
|
const staged = await readGraphFile(stagedPath);
|
|
826
|
-
await writeAtomic(destination, await
|
|
170
|
+
await writeAtomic(destination, await readFile2(stagedPath, "utf8"));
|
|
827
171
|
await writeGraphSourceSnapshot(destination, sourceSnapshot);
|
|
828
172
|
return { graphPath, generated: true, ...staged };
|
|
829
173
|
} finally {
|
|
@@ -831,17 +175,17 @@ async function generateGraphifyGraph(options) {
|
|
|
831
175
|
}
|
|
832
176
|
}
|
|
833
177
|
async function ignoreGeneratedGraphDirectory(directory) {
|
|
834
|
-
const path =
|
|
178
|
+
const path = join(directory, ".gitignore");
|
|
835
179
|
try {
|
|
836
|
-
await
|
|
180
|
+
await stat(path);
|
|
837
181
|
} catch {
|
|
838
182
|
await writeAtomic(path, "# CodeBuddy-generated Graphify output (derived, rebuildable)\n*\n");
|
|
839
183
|
}
|
|
840
184
|
}
|
|
841
185
|
function resolveGraphPath(repositoryRoot, graphPath) {
|
|
842
|
-
const absolute =
|
|
843
|
-
const relativePath =
|
|
844
|
-
if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${
|
|
186
|
+
const absolute = resolve(repositoryRoot, graphPath);
|
|
187
|
+
const relativePath = relative(repositoryRoot, absolute);
|
|
188
|
+
if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`)) {
|
|
845
189
|
throw new Error(`Graph path must be inside the project root: ${graphPath}`);
|
|
846
190
|
}
|
|
847
191
|
return absolute;
|
|
@@ -854,10 +198,10 @@ async function readGraphFileIfPresent(path) {
|
|
|
854
198
|
}
|
|
855
199
|
}
|
|
856
200
|
async function readGraphFile(path) {
|
|
857
|
-
const info = await
|
|
201
|
+
const info = await stat(path);
|
|
858
202
|
if (!info.isFile() || info.size === 0)
|
|
859
203
|
throw new Error(`Graphify graph is missing or empty: ${path}`);
|
|
860
|
-
const raw = JSON.parse(await
|
|
204
|
+
const raw = JSON.parse(await readFile2(path, "utf8"));
|
|
861
205
|
const nodes = arrayLength(raw.nodes ?? raw.entities);
|
|
862
206
|
const edges = arrayLength(raw.edges ?? raw.links ?? raw.relationships);
|
|
863
207
|
if (nodes === 0) throw new Error(`Graphify graph contains no nodes: ${path}`);
|
|
@@ -868,16 +212,16 @@ function arrayLength(value) {
|
|
|
868
212
|
}
|
|
869
213
|
|
|
870
214
|
// src/context/file-vectors.ts
|
|
871
|
-
import { mkdir as
|
|
872
|
-
import { dirname as
|
|
215
|
+
import { mkdir as mkdir3, readFile as readFile6 } from "fs/promises";
|
|
216
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
873
217
|
|
|
874
218
|
// src/context/discovery.ts
|
|
875
|
-
import { readFile as
|
|
876
|
-
import { isAbsolute as isAbsolute3, join as
|
|
219
|
+
import { readFile as readFile5, realpath, stat as stat2 } from "fs/promises";
|
|
220
|
+
import { isAbsolute as isAbsolute3, join as join3, relative as relative4 } from "path";
|
|
877
221
|
|
|
878
222
|
// src/map/graphify.ts
|
|
879
|
-
import { readFile as
|
|
880
|
-
import { isAbsolute, relative as
|
|
223
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
224
|
+
import { isAbsolute, relative as relative2, resolve as resolve2 } from "path";
|
|
881
225
|
var NON_DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
|
|
882
226
|
"contains",
|
|
883
227
|
"part_of",
|
|
@@ -911,7 +255,7 @@ var DIRECT_DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
|
|
|
911
255
|
]);
|
|
912
256
|
var MAX_EDGE_SYMBOLS = 8;
|
|
913
257
|
async function loadGraphifyMap(graphPath, repositoryRoot = process.cwd()) {
|
|
914
|
-
const raw = JSON.parse(await
|
|
258
|
+
const raw = JSON.parse(await readFile3(graphPath, "utf8"));
|
|
915
259
|
const nodes = arrayRecord(raw.nodes ?? raw.entities);
|
|
916
260
|
const edges = arrayRecord(raw.edges ?? raw.links ?? raw.relationships);
|
|
917
261
|
const nodePaths = /* @__PURE__ */ new Map();
|
|
@@ -924,7 +268,7 @@ async function loadGraphifyMap(graphPath, repositoryRoot = process.cwd()) {
|
|
|
924
268
|
nodePaths.set(id, path);
|
|
925
269
|
const label = stringValue(node.label ?? node.name);
|
|
926
270
|
if (label && label !== path && !path.endsWith(`/${label}`)) nodeLabels.set(id, label);
|
|
927
|
-
modules.push({ path, language:
|
|
271
|
+
modules.push({ path, language: languageFor(path), imports: [] });
|
|
928
272
|
}
|
|
929
273
|
const modulePaths = new Set(modules.map((module) => module.path));
|
|
930
274
|
const importedEdges = [];
|
|
@@ -1004,21 +348,21 @@ function graphifyPath(node, repositoryRoot) {
|
|
|
1004
348
|
return candidate ? normalizeGraphPath(candidate, repositoryRoot) : null;
|
|
1005
349
|
}
|
|
1006
350
|
function normalizeGraphPath(path, repositoryRoot) {
|
|
1007
|
-
const absolute = isAbsolute(path) ?
|
|
1008
|
-
const relativePath =
|
|
351
|
+
const absolute = isAbsolute(path) ? resolve2(path) : resolve2(repositoryRoot, path);
|
|
352
|
+
const relativePath = relative2(resolve2(repositoryRoot), absolute).replace(/\\/g, "/");
|
|
1009
353
|
if (relativePath === "" || relativePath === ".." || relativePath.startsWith("../")) return null;
|
|
1010
354
|
return relativePath.replace(/^\.\//, "");
|
|
1011
355
|
}
|
|
1012
356
|
function stringValue(value) {
|
|
1013
357
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1014
358
|
}
|
|
1015
|
-
function
|
|
359
|
+
function languageFor(path) {
|
|
1016
360
|
if (/\.py$/.test(path)) return "py";
|
|
1017
361
|
return /\.tsx?$/.test(path) ? "ts" : "js";
|
|
1018
362
|
}
|
|
1019
363
|
|
|
1020
364
|
// src/context/anchors.ts
|
|
1021
|
-
import { isAbsolute as isAbsolute2, relative as
|
|
365
|
+
import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
|
|
1022
366
|
var MAX_ANCHORS = 3;
|
|
1023
367
|
var MAX_ERROR_SEARCH_FILES = 2e3;
|
|
1024
368
|
var JS_FRAME = /\bat\s+(?:[^\s()]+\s+\()?([^\s()]+?):(\d+):\d+\)?/g;
|
|
@@ -1026,7 +370,7 @@ var PY_FRAME = /\bFile\s+"([^"]+)",\s+line\s+(\d+)/g;
|
|
|
1026
370
|
var PATH_TOKEN = /(?:^|[\s(`'"[])((?:\.{0,2}\/)?[\w@.-]+(?:\/[\w@.-]+)*\.[A-Za-z0-9]+)(?::(\d+))?/g;
|
|
1027
371
|
var QUOTED = /"([^"\n]{8,200})"|'([^'\n]{8,200})'|`([^`\n]{8,200})`/g;
|
|
1028
372
|
async function extractTaskAnchors(repositoryRoot, task, paths) {
|
|
1029
|
-
const root =
|
|
373
|
+
const root = resolve3(repositoryRoot);
|
|
1030
374
|
const found = [];
|
|
1031
375
|
const add = (raw, line, kind, at) => {
|
|
1032
376
|
const path = toIndexedPath(root, raw, paths);
|
|
@@ -1054,9 +398,9 @@ async function extractTaskAnchors(repositoryRoot, task, paths) {
|
|
|
1054
398
|
return anchors.slice(0, MAX_ANCHORS);
|
|
1055
399
|
}
|
|
1056
400
|
function toIndexedPath(root, raw, paths) {
|
|
1057
|
-
const absolute = isAbsolute2(raw) ? raw :
|
|
1058
|
-
const rel =
|
|
1059
|
-
if (rel === "" || rel.startsWith(`..${
|
|
401
|
+
const absolute = isAbsolute2(raw) ? raw : resolve3(root, raw);
|
|
402
|
+
const rel = relative3(root, absolute);
|
|
403
|
+
if (rel === "" || rel.startsWith(`..${sep2}`) || rel === ".." || isAbsolute2(rel)) return null;
|
|
1060
404
|
const normalized = rel.replace(/\\/g, "/");
|
|
1061
405
|
return paths.has(normalized) ? normalized : null;
|
|
1062
406
|
}
|
|
@@ -1082,8 +426,8 @@ async function errorAnchors(root, task, paths) {
|
|
|
1082
426
|
}
|
|
1083
427
|
|
|
1084
428
|
// src/context/content-index.ts
|
|
1085
|
-
import { mkdir as
|
|
1086
|
-
import { dirname as
|
|
429
|
+
import { mkdir as mkdir2, readFile as readFile4 } from "fs/promises";
|
|
430
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1087
431
|
var PASSAGE_LINES = 40;
|
|
1088
432
|
var K1 = 1.2;
|
|
1089
433
|
var B = 0.75;
|
|
@@ -1176,11 +520,11 @@ function idfCoverage(text, terms, idf) {
|
|
|
1176
520
|
return terms.reduce((sum, term) => sum + (words.has(term) ? idf.get(term) ?? 0 : 0), 0) / total;
|
|
1177
521
|
}
|
|
1178
522
|
function cachePath(repositoryRoot) {
|
|
1179
|
-
return
|
|
523
|
+
return join2(repositoryRoot, ".codebuddy", "cache", "content-terms.json");
|
|
1180
524
|
}
|
|
1181
525
|
async function loadCache(repositoryRoot) {
|
|
1182
526
|
try {
|
|
1183
|
-
const parsed = JSON.parse(await
|
|
527
|
+
const parsed = JSON.parse(await readFile4(cachePath(repositoryRoot), "utf8"));
|
|
1184
528
|
if (parsed.version === 2 && parsed.files && typeof parsed.files === "object") return parsed;
|
|
1185
529
|
} catch {
|
|
1186
530
|
}
|
|
@@ -1189,7 +533,7 @@ async function loadCache(repositoryRoot) {
|
|
|
1189
533
|
async function saveCache(repositoryRoot, cache) {
|
|
1190
534
|
try {
|
|
1191
535
|
const path = cachePath(repositoryRoot);
|
|
1192
|
-
await
|
|
536
|
+
await mkdir2(dirname2(path), { recursive: true });
|
|
1193
537
|
await writeAtomic(path, JSON.stringify(cache));
|
|
1194
538
|
} catch {
|
|
1195
539
|
}
|
|
@@ -1203,9 +547,9 @@ var MAX_EVIDENCE_BYTES = 512e3;
|
|
|
1203
547
|
async function readBoundedSource(repositoryRoot, path) {
|
|
1204
548
|
if (isCredentialPath(path)) return null;
|
|
1205
549
|
try {
|
|
1206
|
-
const absolute =
|
|
1207
|
-
if ((await
|
|
1208
|
-
const source = await
|
|
550
|
+
const absolute = join3(repositoryRoot, path);
|
|
551
|
+
if ((await stat2(absolute)).size > MAX_EVIDENCE_BYTES) return null;
|
|
552
|
+
const source = await readFile5(absolute, "utf8");
|
|
1209
553
|
return source.includes("\0") ? null : source;
|
|
1210
554
|
} catch {
|
|
1211
555
|
return null;
|
|
@@ -1253,7 +597,7 @@ async function withLlmSummaries(repositoryRoot, manifest) {
|
|
|
1253
597
|
const { resolveLlmConfig } = await import("./config-GEZFLXK6.js");
|
|
1254
598
|
const config = await resolveLlmConfig(repositoryRoot);
|
|
1255
599
|
if (!config.enabled || !config.features.summaries) return entries;
|
|
1256
|
-
const { loadLlmSummaries } = await import("./summaries-
|
|
600
|
+
const { loadLlmSummaries } = await import("./summaries-ML53R7EL.js");
|
|
1257
601
|
const summaries = await loadLlmSummaries(repositoryRoot, manifest);
|
|
1258
602
|
if (summaries.size === 0) return entries;
|
|
1259
603
|
return entries.map((entry) => {
|
|
@@ -1320,7 +664,7 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
|
|
|
1320
664
|
reason: "semantic retrieval off",
|
|
1321
665
|
stale: 0,
|
|
1322
666
|
changed: 0
|
|
1323
|
-
} : await import("./zvec-
|
|
667
|
+
} : await import("./zvec-XDZNZ2OL.js").then(
|
|
1324
668
|
({ zvecCandidates }) => zvecCandidates(
|
|
1325
669
|
repositoryRoot,
|
|
1326
670
|
task,
|
|
@@ -1544,7 +888,7 @@ async function commandFiles(repositoryRoot, task, entries) {
|
|
|
1544
888
|
var COMMAND_DIR = /(^|\/)(cli|cmd|commands?)\//;
|
|
1545
889
|
async function packageBins(repositoryRoot) {
|
|
1546
890
|
try {
|
|
1547
|
-
const manifest = JSON.parse(await
|
|
891
|
+
const manifest = JSON.parse(await readFile5(join3(repositoryRoot, "package.json"), "utf8"));
|
|
1548
892
|
if (typeof manifest.bin === "string" && typeof manifest.name === "string") {
|
|
1549
893
|
return [manifest.name.slice(manifest.name.lastIndexOf("/") + 1)];
|
|
1550
894
|
}
|
|
@@ -1739,24 +1083,24 @@ async function loadProjectArchitectureMap(repositoryRoot, manifestSource) {
|
|
|
1739
1083
|
const builtIn = (await loadArchitectureMap(repositoryRoot, manifest)).map;
|
|
1740
1084
|
if (config.graphify?.enabled && await graphifyGraphExists(repositoryRoot, config.graphify.graphPath)) {
|
|
1741
1085
|
try {
|
|
1742
|
-
const graphPath =
|
|
1086
|
+
const graphPath = join3(
|
|
1743
1087
|
repositoryRoot,
|
|
1744
1088
|
config.graphify.graphPath ?? "graphify-out/graph.json"
|
|
1745
1089
|
);
|
|
1746
|
-
const [graph, graphFile,
|
|
1090
|
+
const [graph, graphFile, sourceHashes] = await Promise.all([
|
|
1747
1091
|
loadGraphifyMap(graphPath, repositoryRoot),
|
|
1748
|
-
|
|
1092
|
+
stat2(graphPath),
|
|
1749
1093
|
readGraphSourceSnapshot(graphPath)
|
|
1750
1094
|
]);
|
|
1751
1095
|
const livePaths = new Set(
|
|
1752
1096
|
(await Promise.all(
|
|
1753
1097
|
graph.modules.map(async (module) => {
|
|
1754
1098
|
try {
|
|
1755
|
-
const absolute = await realpath(
|
|
1756
|
-
const inside =
|
|
1099
|
+
const absolute = await realpath(join3(repositoryRoot, module.path));
|
|
1100
|
+
const inside = relative4(await realpath(repositoryRoot), absolute);
|
|
1757
1101
|
if (inside.startsWith("..") || isAbsolute3(inside)) return null;
|
|
1758
|
-
const source = await
|
|
1759
|
-
const fresh =
|
|
1102
|
+
const source = await stat2(absolute);
|
|
1103
|
+
const fresh = sourceHashes === null ? source.mtimeMs <= graphFile.mtimeMs : sourceHashes[module.path] !== void 0 && sourceHashes[module.path] === manifest.entries[module.path]?.hash;
|
|
1760
1104
|
return source.isFile() && fresh ? module.path : null;
|
|
1761
1105
|
} catch {
|
|
1762
1106
|
return null;
|
|
@@ -1907,11 +1251,11 @@ function dot(left, right) {
|
|
|
1907
1251
|
return sum;
|
|
1908
1252
|
}
|
|
1909
1253
|
function storePath(repositoryRoot) {
|
|
1910
|
-
return
|
|
1254
|
+
return join4(repositoryRoot, ".codebuddy", "cache", "file-vectors.json");
|
|
1911
1255
|
}
|
|
1912
1256
|
async function loadStore(repositoryRoot) {
|
|
1913
1257
|
try {
|
|
1914
|
-
const parsed = JSON.parse(await
|
|
1258
|
+
const parsed = JSON.parse(await readFile6(storePath(repositoryRoot), "utf8"));
|
|
1915
1259
|
if (parsed.version === 1 && parsed.model === MODEL && parsed.files) return parsed;
|
|
1916
1260
|
} catch {
|
|
1917
1261
|
}
|
|
@@ -1919,16 +1263,11 @@ async function loadStore(repositoryRoot) {
|
|
|
1919
1263
|
}
|
|
1920
1264
|
async function saveStore(repositoryRoot, store) {
|
|
1921
1265
|
const path = storePath(repositoryRoot);
|
|
1922
|
-
await
|
|
1266
|
+
await mkdir3(dirname3(path), { recursive: true });
|
|
1923
1267
|
await writeAtomic(path, JSON.stringify(store));
|
|
1924
1268
|
}
|
|
1925
1269
|
|
|
1926
1270
|
export {
|
|
1927
|
-
buildArchitectureMap,
|
|
1928
|
-
queryMap,
|
|
1929
|
-
neighbours,
|
|
1930
|
-
indexRepository,
|
|
1931
|
-
loadFreshIndex,
|
|
1932
1271
|
setupGraphify,
|
|
1933
1272
|
generateGraphifyGraph,
|
|
1934
1273
|
loadGraphifyMap,
|
|
@@ -1945,4 +1284,4 @@ export {
|
|
|
1945
1284
|
namedSegments,
|
|
1946
1285
|
loadProjectArchitectureMap
|
|
1947
1286
|
};
|
|
1948
|
-
//# sourceMappingURL=chunk-
|
|
1287
|
+
//# sourceMappingURL=chunk-TLWGZVPC.js.map
|