@orquentra/codebuddy 2.6.2 → 2.8.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-IBLJOHJL.js → chunk-3QGW53GD.js} +2 -2
- package/dist/{chunk-IMFUYEZA.js → chunk-6DONJW3W.js} +232 -64
- package/dist/chunk-6DONJW3W.js.map +1 -0
- package/dist/{chunk-JFDRBPYF.js → chunk-6JCKMIQY.js} +2 -2
- package/dist/{chunk-S6VEG4S2.js → chunk-6KGYAFG7.js} +28 -5
- package/dist/chunk-6KGYAFG7.js.map +1 -0
- package/dist/{chunk-OWWCDRXW.js → chunk-6NZEZJYD.js} +9 -7
- package/dist/chunk-6NZEZJYD.js.map +1 -0
- package/dist/{chunk-GDJH7XJC.js → chunk-AL6VBTNA.js} +77 -3
- package/dist/chunk-AL6VBTNA.js.map +1 -0
- package/dist/{chunk-XBJQKCLU.js → chunk-CAEPHLHC.js} +13 -9
- package/dist/chunk-CAEPHLHC.js.map +1 -0
- package/dist/chunk-CG5B3LYG.js +66 -0
- package/dist/chunk-CG5B3LYG.js.map +1 -0
- package/dist/{chunk-IHQM2ZDV.js → chunk-DFV2SK6C.js} +1 -1
- package/dist/{chunk-IHQM2ZDV.js.map → chunk-DFV2SK6C.js.map} +1 -1
- package/dist/{chunk-QBDJXGG5.js → chunk-EJQVGFH2.js} +93 -22
- package/dist/chunk-EJQVGFH2.js.map +1 -0
- package/dist/{chunk-5MQOE4J6.js → chunk-ESZXCZIN.js} +3 -3
- package/dist/{chunk-J2EAZ4MA.js → chunk-GKNYMNFM.js} +22 -25
- package/dist/chunk-GKNYMNFM.js.map +1 -0
- package/dist/{chunk-5FW3PMNP.js → chunk-GXOML4UZ.js} +76 -11
- package/dist/chunk-GXOML4UZ.js.map +1 -0
- package/dist/{chunk-JPHJBCZB.js → chunk-HPOR6YXA.js} +3 -2
- package/dist/chunk-HPOR6YXA.js.map +1 -0
- 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-5KZY6OTG.js → chunk-KNR2P2UY.js} +33 -83
- package/dist/chunk-KNR2P2UY.js.map +1 -0
- package/dist/{chunk-2XDPKFUW.js → chunk-NY2YXP3R.js} +46 -20
- package/dist/chunk-NY2YXP3R.js.map +1 -0
- package/dist/{chunk-RAJJSCIP.js → chunk-SFMIMLXR.js} +2 -2
- package/dist/chunk-TGFTKWOR.js +16 -0
- package/dist/chunk-TGFTKWOR.js.map +1 -0
- package/dist/{chunk-NERKZSSE.js → chunk-TLWGZVPC.js} +395 -686
- package/dist/chunk-TLWGZVPC.js.map +1 -0
- package/dist/{chunk-B6CO63SQ.js → chunk-UK6XTS2Y.js} +24 -10
- package/dist/chunk-UK6XTS2Y.js.map +1 -0
- package/dist/chunk-W7GAXL6C.js +104 -0
- package/dist/chunk-W7GAXL6C.js.map +1 -0
- package/dist/chunk-XL2HLACE.js +55 -0
- package/dist/chunk-XL2HLACE.js.map +1 -0
- package/dist/chunk-Y7D7PUBE.js +321 -0
- package/dist/chunk-Y7D7PUBE.js.map +1 -0
- package/dist/{chunk-R7CD6EXS.js → chunk-ZNLSCV3J.js} +578 -381
- package/dist/chunk-ZNLSCV3J.js.map +1 -0
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{cloud-7WM3YLI4.js → cloud-LXZUR3U2.js} +3 -3
- package/dist/{codebuddy-B_pCaQrV.d.ts → codebuddy-DO7ZhoWn.d.ts} +15 -1
- package/dist/config-GEZFLXK6.js +12 -0
- package/dist/index.d.ts +46 -3
- package/dist/index.js +23 -19
- package/dist/install-BXM56QCD.js +19 -0
- package/dist/langgraph/index.d.ts +1 -1
- package/dist/lean-command-7DFW32MA.js +19 -0
- package/dist/{local-5MZURLCZ.js → local-ST4WMRCG.js} +3 -3
- package/dist/{main-MTBQZGJV.js → main-QUASSFP7.js} +987 -1741
- package/dist/main-QUASSFP7.js.map +1 -0
- package/dist/{memory-KIC5FJVW.js → memory-TGHVTBME.js} +9 -8
- package/dist/memory-TGHVTBME.js.map +1 -0
- package/dist/operations-WJR7AZZF.js +26 -0
- package/dist/pretool-command-JTKX6G3M.js +13 -0
- package/dist/{rerank-5QZ3PNUW.js → rerank-IQAEWLQR.js} +6 -5
- package/dist/rerank-IQAEWLQR.js.map +1 -0
- package/dist/{runtime-CUBOOPZO.js → runtime-OQQ22VJF.js} +15 -11
- package/dist/summaries-ML53R7EL.js +21 -0
- package/dist/summaries-ML53R7EL.js.map +1 -0
- package/dist/{zvec-MSRKT7IV.js → zvec-XDZNZ2OL.js} +11 -3
- package/dist/zvec-XDZNZ2OL.js.map +1 -0
- package/package.json +1 -1
- package/src/db/migrations/0006_fact_idempotency_key.sql +2 -0
- package/src/db/migrations/meta/0006_snapshot.json +1258 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/dist/chunk-2XDPKFUW.js.map +0 -1
- package/dist/chunk-5FW3PMNP.js.map +0 -1
- package/dist/chunk-5KZY6OTG.js.map +0 -1
- package/dist/chunk-B6CO63SQ.js.map +0 -1
- package/dist/chunk-GDJH7XJC.js.map +0 -1
- package/dist/chunk-IMFUYEZA.js.map +0 -1
- package/dist/chunk-J2EAZ4MA.js.map +0 -1
- package/dist/chunk-JPHJBCZB.js.map +0 -1
- package/dist/chunk-NERKZSSE.js.map +0 -1
- package/dist/chunk-OWWCDRXW.js.map +0 -1
- package/dist/chunk-PQK3GKZH.js +0 -98
- package/dist/chunk-PQK3GKZH.js.map +0 -1
- package/dist/chunk-QBDJXGG5.js.map +0 -1
- package/dist/chunk-R7CD6EXS.js.map +0 -1
- package/dist/chunk-S6VEG4S2.js.map +0 -1
- package/dist/chunk-XBJQKCLU.js.map +0 -1
- package/dist/config-DEVHUDIU.js +0 -11
- package/dist/lean-command-HTQCPH76.js +0 -15
- package/dist/main-MTBQZGJV.js.map +0 -1
- package/dist/memory-KIC5FJVW.js.map +0 -1
- package/dist/operations-POCHY3WR.js +0 -25
- package/dist/pretool-command-AX6JXP25.js +0 -10
- package/dist/rerank-5QZ3PNUW.js.map +0 -1
- package/dist/summaries-SB5L2TSW.js +0 -18
- /package/dist/{cloud-7WM3YLI4.js.map → adapters-S4HHTWJW.js.map} +0 -0
- /package/dist/{chunk-IBLJOHJL.js.map → chunk-3QGW53GD.js.map} +0 -0
- /package/dist/{chunk-JFDRBPYF.js.map → chunk-6JCKMIQY.js.map} +0 -0
- /package/dist/{chunk-5MQOE4J6.js.map → chunk-ESZXCZIN.js.map} +0 -0
- /package/dist/{chunk-RAJJSCIP.js.map → chunk-SFMIMLXR.js.map} +0 -0
- /package/dist/{config-DEVHUDIU.js.map → cloud-LXZUR3U2.js.map} +0 -0
- /package/dist/{lean-command-HTQCPH76.js.map → config-GEZFLXK6.js.map} +0 -0
- /package/dist/{local-5MZURLCZ.js.map → install-BXM56QCD.js.map} +0 -0
- /package/dist/{operations-POCHY3WR.js.map → lean-command-7DFW32MA.js.map} +0 -0
- /package/dist/{pretool-command-AX6JXP25.js.map → local-ST4WMRCG.js.map} +0 -0
- /package/dist/{runtime-CUBOOPZO.js.map → operations-WJR7AZZF.js.map} +0 -0
- /package/dist/{summaries-SB5L2TSW.js.map → pretool-command-JTKX6G3M.js.map} +0 -0
- /package/dist/{zvec-MSRKT7IV.js.map → runtime-OQQ22VJF.js.map} +0 -0
|
@@ -1,640 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
2
|
+
loadArchitectureMap,
|
|
3
|
+
loadFreshIndex
|
|
4
|
+
} from "./chunk-ITQO632W.js";
|
|
5
|
+
import {
|
|
6
|
+
isCredentialPath
|
|
7
|
+
} from "./chunk-AL6VBTNA.js";
|
|
8
|
+
import {
|
|
9
|
+
LOCAL_EMBEDDING_MODEL
|
|
10
|
+
} from "./chunk-HPOR6YXA.js";
|
|
11
|
+
import {
|
|
12
|
+
readConfigFile
|
|
13
|
+
} from "./chunk-KNR2P2UY.js";
|
|
5
14
|
import {
|
|
6
|
-
assertNoSymlinkBetween,
|
|
7
|
-
readConfigFile,
|
|
8
15
|
writeAtomic
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
|
|
11
|
-
// src/map/indexer.ts
|
|
12
|
-
import { readdir, readFile, stat } from "fs/promises";
|
|
13
|
-
import { dirname, extname, join, relative, resolve } from "path";
|
|
14
|
-
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
15
|
-
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
16
|
-
".git",
|
|
17
|
-
"node_modules",
|
|
18
|
-
"dist",
|
|
19
|
-
"build",
|
|
20
|
-
"coverage",
|
|
21
|
-
".codebuddy"
|
|
22
|
-
]);
|
|
23
|
-
async function buildArchitectureMap(repositoryRoot = process.cwd()) {
|
|
24
|
-
const root = resolve(repositoryRoot);
|
|
25
|
-
const files = await listSourceFiles(root);
|
|
26
|
-
const fileSet = new Set(files.map((file) => normalize(relative(root, file))));
|
|
27
|
-
const modules = [];
|
|
28
|
-
const edges = [];
|
|
29
|
-
for (const absolutePath of files) {
|
|
30
|
-
const source = await readFile(absolutePath, "utf8");
|
|
31
|
-
const from = normalize(relative(root, absolutePath));
|
|
32
|
-
const imports = extractImports(source);
|
|
33
|
-
const resolvedImports = [];
|
|
34
|
-
for (const specifier of imports) {
|
|
35
|
-
const resolved = resolveImport(root, from, specifier.value, fileSet);
|
|
36
|
-
if (!resolved) continue;
|
|
37
|
-
resolvedImports.push(resolved);
|
|
38
|
-
edges.push({ from, to: resolved, kind: specifier.kind });
|
|
39
|
-
}
|
|
40
|
-
modules.push({
|
|
41
|
-
path: from,
|
|
42
|
-
language: extname(absolutePath).includes("ts") ? "ts" : "js",
|
|
43
|
-
imports: [...new Set(resolvedImports)].sort()
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
modules: modules.sort((left, right) => left.path.localeCompare(right.path)),
|
|
48
|
-
edges: edges.sort(
|
|
49
|
-
(left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
|
|
50
|
-
)
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
async function updateArchitectureMap(repositoryRoot, sourcePaths, previous, changedPaths) {
|
|
54
|
-
const root = resolve(repositoryRoot);
|
|
55
|
-
const normalizedPaths = sourcePaths.map(normalize).sort();
|
|
56
|
-
const fileSet = new Set(normalizedPaths);
|
|
57
|
-
const changed = new Set(changedPaths.map(normalize));
|
|
58
|
-
const retainedModules = (previous?.modules ?? []).filter(
|
|
59
|
-
(module) => fileSet.has(module.path) && !changed.has(module.path)
|
|
60
|
-
);
|
|
61
|
-
const retainedEdges = (previous?.edges ?? []).filter(
|
|
62
|
-
(edge) => fileSet.has(edge.from) && fileSet.has(edge.to) && !changed.has(edge.from)
|
|
63
|
-
);
|
|
64
|
-
const modules = [...retainedModules];
|
|
65
|
-
const edges = [...retainedEdges];
|
|
66
|
-
for (const path of normalizedPaths) {
|
|
67
|
-
if (!changed.has(path)) continue;
|
|
68
|
-
const source = await readFile(join(root, path), "utf8");
|
|
69
|
-
const imports = extractImports(source);
|
|
70
|
-
const resolvedImports = [];
|
|
71
|
-
for (const specifier of imports) {
|
|
72
|
-
const resolvedImport = resolveImport(root, path, specifier.value, fileSet);
|
|
73
|
-
if (!resolvedImport) continue;
|
|
74
|
-
resolvedImports.push(resolvedImport);
|
|
75
|
-
edges.push({ from: path, to: resolvedImport, kind: specifier.kind });
|
|
76
|
-
}
|
|
77
|
-
modules.push({
|
|
78
|
-
path,
|
|
79
|
-
language: extname(path).includes("ts") ? "ts" : "js",
|
|
80
|
-
imports: [...new Set(resolvedImports)].sort()
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
return {
|
|
84
|
-
map: {
|
|
85
|
-
modules: modules.sort((left, right) => left.path.localeCompare(right.path)),
|
|
86
|
-
edges: edges.sort(
|
|
87
|
-
(left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
|
|
88
|
-
)
|
|
89
|
-
},
|
|
90
|
-
stats: {
|
|
91
|
-
readFiles: changed.size,
|
|
92
|
-
reusedFiles: retainedModules.length,
|
|
93
|
-
rebuilt: previous === null || changed.size === normalizedPaths.length
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function queryMap(map, query) {
|
|
98
|
-
const limit = query.limit ?? 50;
|
|
99
|
-
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);
|
|
100
|
-
}
|
|
101
|
-
function neighbours(map, modulePath, direction = "both") {
|
|
102
|
-
const target = normalize(modulePath);
|
|
103
|
-
const edges = map.edges.filter((edge) => {
|
|
104
|
-
if (direction === "in") return edge.to === target;
|
|
105
|
-
if (direction === "out") return edge.from === target;
|
|
106
|
-
return edge.from === target || edge.to === target;
|
|
107
|
-
});
|
|
108
|
-
const paths = /* @__PURE__ */ new Set([target]);
|
|
109
|
-
for (const edge of edges) {
|
|
110
|
-
paths.add(edge.from);
|
|
111
|
-
paths.add(edge.to);
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
modules: map.modules.filter((module) => paths.has(module.path)),
|
|
115
|
-
edges
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
async function listSourceFiles(root) {
|
|
119
|
-
const files = [];
|
|
120
|
-
await walk(root, files);
|
|
121
|
-
return files.sort();
|
|
122
|
-
}
|
|
123
|
-
async function walk(directory, files) {
|
|
124
|
-
const entries = await readdir(directory, { withFileTypes: true });
|
|
125
|
-
for (const entry of entries) {
|
|
126
|
-
const absolute = join(directory, entry.name);
|
|
127
|
-
if (entry.isDirectory()) {
|
|
128
|
-
if (IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
129
|
-
await walk(absolute, files);
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (!entry.isFile()) continue;
|
|
133
|
-
if (!SUPPORTED_EXTENSIONS.has(extname(entry.name))) continue;
|
|
134
|
-
const info = await stat(absolute);
|
|
135
|
-
if (info.size > 5e5) continue;
|
|
136
|
-
files.push(absolute);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
function extractImports(source) {
|
|
140
|
-
const imports = [];
|
|
141
|
-
const stripped = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1 ");
|
|
142
|
-
const staticImport = /\bimport\s+(?:type\s+)?(?:[^'"()+]+?\s+from\s+)?["']([^"']+)["']/g;
|
|
143
|
-
const exportFrom = /\bexport\s+[^'"()+]+?\s+from\s+["']([^"']+)["']/g;
|
|
144
|
-
const dynamicImport = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
145
|
-
for (const match of stripped.matchAll(staticImport)) {
|
|
146
|
-
if (match[1]) imports.push({ value: match[1], kind: "import" });
|
|
147
|
-
}
|
|
148
|
-
for (const match of stripped.matchAll(exportFrom)) {
|
|
149
|
-
if (match[1]) imports.push({ value: match[1], kind: "import" });
|
|
150
|
-
}
|
|
151
|
-
for (const match of stripped.matchAll(dynamicImport)) {
|
|
152
|
-
if (match[1]) imports.push({ value: match[1], kind: "dynamic_import" });
|
|
153
|
-
}
|
|
154
|
-
return imports;
|
|
155
|
-
}
|
|
156
|
-
function resolveImport(root, from, specifier, fileSet) {
|
|
157
|
-
if (!specifier.startsWith(".")) return null;
|
|
158
|
-
const base = normalize(relative(root, resolve(root, dirname(from), specifier)));
|
|
159
|
-
const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
|
|
160
|
-
const bases = stem === base ? [base] : [base, stem];
|
|
161
|
-
const candidates = bases.flatMap((b) => [
|
|
162
|
-
b,
|
|
163
|
-
`${b}.ts`,
|
|
164
|
-
`${b}.tsx`,
|
|
165
|
-
`${b}.js`,
|
|
166
|
-
`${b}.jsx`,
|
|
167
|
-
`${b}.mjs`,
|
|
168
|
-
`${b}.cjs`,
|
|
169
|
-
`${b}/index.ts`,
|
|
170
|
-
`${b}/index.tsx`,
|
|
171
|
-
`${b}/index.js`,
|
|
172
|
-
`${b}/index.jsx`
|
|
173
|
-
]);
|
|
174
|
-
return candidates.find((candidate) => fileSet.has(candidate)) ?? null;
|
|
175
|
-
}
|
|
176
|
-
function normalize(path) {
|
|
177
|
-
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// src/indexer/engine.ts
|
|
181
|
-
import { createHash } from "crypto";
|
|
182
|
-
import { readFile as readFile4, stat as stat2 } from "fs/promises";
|
|
183
|
-
import { join as join5, resolve as resolve4 } from "path";
|
|
184
|
-
|
|
185
|
-
// src/map/cache.ts
|
|
186
|
-
import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
|
|
187
|
-
import { dirname as dirname3, extname as extname2, join as join3, resolve as resolve3 } from "path";
|
|
188
|
-
|
|
189
|
-
// src/indexer/store.ts
|
|
190
|
-
import { mkdir, readFile as readFile2 } from "fs/promises";
|
|
191
|
-
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
192
|
-
import { z } from "zod";
|
|
193
|
-
|
|
194
|
-
// src/indexer/types.ts
|
|
195
|
-
var INDEX_MANIFEST_VERSION = 2;
|
|
196
|
-
|
|
197
|
-
// src/indexer/store.ts
|
|
198
|
-
var symbolSpanSchema = z.object({
|
|
199
|
-
name: z.string(),
|
|
200
|
-
kind: z.enum(["function", "method", "class", "interface", "type", "enum", "const"]),
|
|
201
|
-
startLine: z.number().int().nonnegative(),
|
|
202
|
-
endLine: z.number().int().nonnegative(),
|
|
203
|
-
signature: z.string(),
|
|
204
|
-
exported: z.boolean()
|
|
205
|
-
});
|
|
206
|
-
var entrySchema = z.object({
|
|
207
|
-
path: z.string(),
|
|
208
|
-
hash: z.string(),
|
|
209
|
-
size: z.number().int().nonnegative(),
|
|
210
|
-
mtimeMs: z.number().optional(),
|
|
211
|
-
ctimeMs: z.number().optional(),
|
|
212
|
-
language: z.string(),
|
|
213
|
-
lines: z.number().int().nonnegative(),
|
|
214
|
-
symbols: z.array(z.string()),
|
|
215
|
-
symbolTable: z.array(symbolSpanSchema).optional(),
|
|
216
|
-
summary: z.string(),
|
|
217
|
-
indexedAt: z.string()
|
|
218
|
-
});
|
|
219
|
-
var manifestSchema = z.object({
|
|
220
|
-
version: z.literal(INDEX_MANIFEST_VERSION),
|
|
221
|
-
generatedAt: z.string(),
|
|
222
|
-
entries: z.record(z.string(), entrySchema)
|
|
223
|
-
});
|
|
224
|
-
var IndexStore = class {
|
|
225
|
-
repositoryRoot;
|
|
226
|
-
cacheDirectory;
|
|
227
|
-
manifestPath;
|
|
228
|
-
constructor(repositoryRoot = process.cwd()) {
|
|
229
|
-
this.repositoryRoot = resolve2(repositoryRoot);
|
|
230
|
-
this.cacheDirectory = join2(this.repositoryRoot, ".codebuddy", "cache");
|
|
231
|
-
this.manifestPath = join2(this.cacheDirectory, "index.json");
|
|
232
|
-
}
|
|
233
|
-
/** Read the manifest, or an empty one if absent/corrupt/version-mismatched. */
|
|
234
|
-
async read() {
|
|
235
|
-
try {
|
|
236
|
-
const raw = await readFile2(this.manifestPath, "utf8");
|
|
237
|
-
const parsed = manifestSchema.safeParse(JSON.parse(raw));
|
|
238
|
-
if (!parsed.success) return emptyManifest();
|
|
239
|
-
return parsed.data;
|
|
240
|
-
} catch (error) {
|
|
241
|
-
if (error.code === "ENOENT") return emptyManifest();
|
|
242
|
-
return emptyManifest();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
async write(manifest) {
|
|
246
|
-
await assertNoSymlinkBetween(this.repositoryRoot, dirname2(this.manifestPath));
|
|
247
|
-
await mkdir(this.cacheDirectory, { recursive: true, mode: 448 });
|
|
248
|
-
await writeAtomic(this.manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
249
|
-
`);
|
|
250
|
-
}
|
|
251
|
-
};
|
|
252
|
-
function emptyManifest() {
|
|
253
|
-
return { version: INDEX_MANIFEST_VERSION, generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(), entries: {} };
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// src/map/cache.ts
|
|
257
|
-
var CACHE_VERSION = 1;
|
|
258
|
-
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
259
|
-
async function loadArchitectureMap(repositoryRoot = process.cwd(), suppliedManifest) {
|
|
260
|
-
const root = resolve3(repositoryRoot);
|
|
261
|
-
const manifest = suppliedManifest ?? await new IndexStore(root).read();
|
|
262
|
-
if (Object.keys(manifest.entries).length === 0) {
|
|
263
|
-
const map = await buildArchitectureMap(root);
|
|
264
|
-
return {
|
|
265
|
-
map,
|
|
266
|
-
stats: { readFiles: map.modules.length, reusedFiles: 0, rebuilt: true },
|
|
267
|
-
source: "fallback"
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
return refreshArchitectureCache(root, manifest);
|
|
271
|
-
}
|
|
272
|
-
async function refreshArchitectureCache(repositoryRoot, manifest) {
|
|
273
|
-
const root = resolve3(repositoryRoot);
|
|
274
|
-
const path = join3(root, ".codebuddy", "cache", "architecture.json");
|
|
275
|
-
const currentHashes = sourceHashes(manifest);
|
|
276
|
-
const currentPaths = Object.keys(currentHashes).sort();
|
|
277
|
-
const cached = await readCache(path);
|
|
278
|
-
const cachedPaths = cached ? Object.keys(cached.sourceHashes).sort() : [];
|
|
279
|
-
const fileSetChanged = cachedPaths.join("\n") !== currentPaths.join("\n");
|
|
280
|
-
const changedPaths = !cached || fileSetChanged ? currentPaths : currentPaths.filter(
|
|
281
|
-
(sourcePath) => cached.sourceHashes[sourcePath] !== currentHashes[sourcePath]
|
|
282
|
-
);
|
|
283
|
-
if (cached && changedPaths.length === 0) {
|
|
284
|
-
return {
|
|
285
|
-
map: cached.map,
|
|
286
|
-
stats: { readFiles: 0, reusedFiles: cached.map.modules.length, rebuilt: false },
|
|
287
|
-
source: "cache"
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
const updated = await updateArchitectureMap(
|
|
291
|
-
root,
|
|
292
|
-
currentPaths,
|
|
293
|
-
fileSetChanged ? null : cached?.map ?? null,
|
|
294
|
-
changedPaths
|
|
295
|
-
);
|
|
296
|
-
const next = {
|
|
297
|
-
version: CACHE_VERSION,
|
|
298
|
-
generatedAt: manifest.generatedAt,
|
|
299
|
-
sourceHashes: currentHashes,
|
|
300
|
-
map: updated.map
|
|
301
|
-
};
|
|
302
|
-
await assertNoSymlinkBetween(root, dirname3(path));
|
|
303
|
-
await mkdir2(dirname3(path), { recursive: true, mode: 448 });
|
|
304
|
-
await writeAtomic(path, `${JSON.stringify(next, null, 2)}
|
|
305
|
-
`);
|
|
306
|
-
return { ...updated, source: fileSetChanged || !cached ? "rebuild" : "incremental" };
|
|
307
|
-
}
|
|
308
|
-
async function readCache(path) {
|
|
309
|
-
try {
|
|
310
|
-
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
311
|
-
if (parsed.version !== CACHE_VERSION || !parsed.map || !parsed.sourceHashes) return null;
|
|
312
|
-
if (!Array.isArray(parsed.map.modules) || !Array.isArray(parsed.map.edges)) return null;
|
|
313
|
-
if (!parsed.map.modules.every(
|
|
314
|
-
(module) => module && typeof module.path === "string" && typeof module.language === "string" && Array.isArray(module.imports)
|
|
315
|
-
) || !parsed.map.edges.every(
|
|
316
|
-
(edge) => edge && typeof edge.from === "string" && typeof edge.to === "string"
|
|
317
|
-
)) {
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
return parsed;
|
|
321
|
-
} catch {
|
|
322
|
-
return null;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
function sourceHashes(manifest) {
|
|
326
|
-
return Object.fromEntries(
|
|
327
|
-
Object.values(manifest.entries).filter((entry) => SOURCE_EXTENSIONS.has(extname2(entry.path).toLowerCase())).map((entry) => [entry.path, entry.hash])
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// src/indexer/scan.ts
|
|
332
|
-
import { spawn } from "child_process";
|
|
333
|
-
import { readdir as readdir2 } from "fs/promises";
|
|
334
|
-
import { extname as extname4, join as join4, relative as relative2 } from "path";
|
|
335
|
-
|
|
336
|
-
// src/indexer/summary.ts
|
|
337
|
-
import { extname as extname3 } from "path";
|
|
338
|
-
var LANGUAGE_BY_EXTENSION = {
|
|
339
|
-
".ts": "ts",
|
|
340
|
-
".tsx": "tsx",
|
|
341
|
-
".js": "js",
|
|
342
|
-
".jsx": "jsx",
|
|
343
|
-
".mjs": "js",
|
|
344
|
-
".cjs": "js",
|
|
345
|
-
".json": "json",
|
|
346
|
-
".md": "md",
|
|
347
|
-
".mdx": "md",
|
|
348
|
-
".yaml": "yaml",
|
|
349
|
-
".yml": "yaml",
|
|
350
|
-
".toml": "toml",
|
|
351
|
-
".css": "css",
|
|
352
|
-
".scss": "scss",
|
|
353
|
-
".html": "html",
|
|
354
|
-
".py": "py",
|
|
355
|
-
".go": "go",
|
|
356
|
-
".rs": "rust",
|
|
357
|
-
".java": "java",
|
|
358
|
-
".rb": "ruby",
|
|
359
|
-
".php": "php",
|
|
360
|
-
".sh": "shell",
|
|
361
|
-
".sql": "sql",
|
|
362
|
-
".txt": "text"
|
|
363
|
-
};
|
|
364
|
-
var SUPPORTED_EXTENSIONS2 = new Set(Object.keys(LANGUAGE_BY_EXTENSION));
|
|
365
|
-
var CODE_LANGUAGES = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx"]);
|
|
366
|
-
var SYMBOL_LIMIT = 8;
|
|
367
|
-
var SUMMARY_COMMENT_MAX = 80;
|
|
368
|
-
function languageFor(path) {
|
|
369
|
-
return LANGUAGE_BY_EXTENSION[extname3(path).toLowerCase()] ?? "text";
|
|
370
|
-
}
|
|
371
|
-
function summarizeSource(path, content) {
|
|
372
|
-
const language = languageFor(path);
|
|
373
|
-
const lines = countLines(content);
|
|
374
|
-
const symbols = CODE_LANGUAGES.has(language) ? extractSymbols(content) : [];
|
|
375
|
-
const summary = composeSummary(language, lines, symbols, content);
|
|
376
|
-
return { language, lines, symbols, summary };
|
|
377
|
-
}
|
|
378
|
-
function countLines(content) {
|
|
379
|
-
if (content.length === 0) return 0;
|
|
380
|
-
return content.replace(/\r/g, "").replace(/\n$/, "").split("\n").length;
|
|
381
|
-
}
|
|
382
|
-
function extractSymbols(content) {
|
|
383
|
-
const names = [];
|
|
384
|
-
const push = (name) => {
|
|
385
|
-
const trimmed = name?.trim();
|
|
386
|
-
if (trimmed && /^[A-Za-z_$][\w$]*$/.test(trimmed) && !names.includes(trimmed)) {
|
|
387
|
-
names.push(trimmed);
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
const declaration = /^export\s+(?:default\s+)?(?:async\s+)?(?:abstract\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/gm;
|
|
391
|
-
for (const match of content.matchAll(declaration)) push(match[1]);
|
|
392
|
-
const namedExports = /^export\s*\{([^}]*)\}/gm;
|
|
393
|
-
for (const match of content.matchAll(namedExports)) {
|
|
394
|
-
for (const part of (match[1] ?? "").split(",")) {
|
|
395
|
-
const alias = part.includes(" as ") ? part.split(" as ").pop() : part;
|
|
396
|
-
push(alias);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return names.slice(0, SYMBOL_LIMIT);
|
|
400
|
-
}
|
|
401
|
-
function composeSummary(language, lines, symbols, content) {
|
|
402
|
-
const head = `${language} \u2022 ${lines} line${lines === 1 ? "" : "s"}`;
|
|
403
|
-
if (symbols.length > 0) return `${head} \u2022 exports ${symbols.join(", ")}`;
|
|
404
|
-
const comment = leadingComment(content);
|
|
405
|
-
if (comment) return `${head} \u2022 ${comment}`;
|
|
406
|
-
return head;
|
|
407
|
-
}
|
|
408
|
-
function leadingComment(content) {
|
|
409
|
-
for (const raw of content.split(/\r?\n/)) {
|
|
410
|
-
const line = raw.trim();
|
|
411
|
-
if (line === "") continue;
|
|
412
|
-
const stripped = line.replace(/^\/\/+/, "").replace(/^\/\*+/, "").replace(/\*+\/$/, "").replace(/^[#*]+/, "").trim();
|
|
413
|
-
if (line.startsWith("//") || line.startsWith("/*") || line.startsWith("#") || line.startsWith("*")) {
|
|
414
|
-
if (stripped) return truncate(stripped, SUMMARY_COMMENT_MAX);
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
function truncate(value, max) {
|
|
422
|
-
return value.length <= max ? value : `${value.slice(0, max - 1)}\u2026`;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/indexer/scan.ts
|
|
426
|
-
var FALLBACK_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
427
|
-
".git",
|
|
428
|
-
"node_modules",
|
|
429
|
-
"dist",
|
|
430
|
-
"build",
|
|
431
|
-
"coverage",
|
|
432
|
-
".codebuddy",
|
|
433
|
-
"graphify-out",
|
|
434
|
-
".next",
|
|
435
|
-
".turbo",
|
|
436
|
-
"out"
|
|
437
|
-
]);
|
|
438
|
-
async function listIndexableFiles(repositoryRoot) {
|
|
439
|
-
const tracked = await gitListFiles(repositoryRoot);
|
|
440
|
-
const paths = tracked ?? await walkFiles(repositoryRoot);
|
|
441
|
-
return paths.map(normalize2).filter((path) => !path.startsWith(".codebuddy/") && !path.startsWith("graphify-out/")).filter((path) => SUPPORTED_EXTENSIONS2.has(extname4(path).toLowerCase())).sort();
|
|
442
|
-
}
|
|
443
|
-
async function gitListFiles(repositoryRoot) {
|
|
444
|
-
const result = await runGit(repositoryRoot, [
|
|
445
|
-
"ls-files",
|
|
446
|
-
"--cached",
|
|
447
|
-
"--others",
|
|
448
|
-
"--exclude-standard"
|
|
449
|
-
]);
|
|
450
|
-
if (result.code !== 0) return null;
|
|
451
|
-
return result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
452
|
-
}
|
|
453
|
-
async function walkFiles(root) {
|
|
454
|
-
const files = [];
|
|
455
|
-
const walk2 = async (directory) => {
|
|
456
|
-
const entries = await readdir2(directory, { withFileTypes: true });
|
|
457
|
-
for (const entry of entries) {
|
|
458
|
-
const absolute = join4(directory, entry.name);
|
|
459
|
-
if (entry.isDirectory()) {
|
|
460
|
-
if (FALLBACK_IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
461
|
-
await walk2(absolute);
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
if (entry.isFile()) files.push(relative2(root, absolute));
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
await walk2(root);
|
|
468
|
-
return files;
|
|
469
|
-
}
|
|
470
|
-
function runGit(cwd, args) {
|
|
471
|
-
return new Promise((resolveResult) => {
|
|
472
|
-
const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
473
|
-
let stdout = "";
|
|
474
|
-
let stderr = "";
|
|
475
|
-
child.stdout.on("data", (chunk) => {
|
|
476
|
-
stdout += String(chunk);
|
|
477
|
-
});
|
|
478
|
-
child.stderr.on("data", (chunk) => {
|
|
479
|
-
stderr += String(chunk);
|
|
480
|
-
});
|
|
481
|
-
child.on("error", () => resolveResult({ code: -1, stdout: "", stderr: "git unavailable" }));
|
|
482
|
-
child.on("close", (code) => resolveResult({ code: code ?? 0, stdout, stderr }));
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
function normalize2(path) {
|
|
486
|
-
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// src/indexer/engine.ts
|
|
490
|
-
var MAX_FILE_BYTES = 512e3;
|
|
491
|
-
var RACY_WINDOW_MS = 3e3;
|
|
492
|
-
async function indexRepository(options = {}) {
|
|
493
|
-
return (await refreshIndex(options)).result;
|
|
494
|
-
}
|
|
495
|
-
async function loadFreshIndex(repositoryRoot) {
|
|
496
|
-
try {
|
|
497
|
-
const { result, manifest } = await refreshIndex({ repositoryRoot });
|
|
498
|
-
const delta = result.added + result.changed + result.removed;
|
|
499
|
-
const failures = result.errors.length > 0 ? `; ${result.errors.length} file(s) could not be validated and were omitted` : "";
|
|
500
|
-
return {
|
|
501
|
-
manifest,
|
|
502
|
-
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}`
|
|
503
|
-
};
|
|
504
|
-
} catch (error) {
|
|
505
|
-
return {
|
|
506
|
-
manifest: emptyManifest(),
|
|
507
|
-
explain: `index: freshness check failed (${error.message}); indexed evidence omitted`
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
var passes = /* @__PURE__ */ new Map();
|
|
512
|
-
function refreshIndex(options) {
|
|
513
|
-
const key = resolve4(options.repositoryRoot ?? process.cwd());
|
|
514
|
-
const next = (passes.get(key) ?? Promise.resolve()).catch(() => void 0).then(() => runIndexPass(options));
|
|
515
|
-
passes.set(key, next);
|
|
516
|
-
void next.finally(() => {
|
|
517
|
-
if (passes.get(key) === next) passes.delete(key);
|
|
518
|
-
}).catch(() => void 0);
|
|
519
|
-
return next;
|
|
520
|
-
}
|
|
521
|
-
async function runIndexPass(options) {
|
|
522
|
-
const start = Date.now();
|
|
523
|
-
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
524
|
-
const repositoryRoot = resolve4(options.repositoryRoot ?? process.cwd());
|
|
525
|
-
const store = new IndexStore(repositoryRoot);
|
|
526
|
-
const [files, previous] = await Promise.all([listIndexableFiles(repositoryRoot), store.read()]);
|
|
527
|
-
const entries = {};
|
|
528
|
-
const errors = [];
|
|
529
|
-
let added = 0;
|
|
530
|
-
let changed = 0;
|
|
531
|
-
let unchanged = 0;
|
|
532
|
-
let skipped = 0;
|
|
533
|
-
let dirty = previous.generatedAt === emptyManifest().generatedAt;
|
|
534
|
-
for (const path of files) {
|
|
535
|
-
try {
|
|
536
|
-
const info = await stat2(join5(repositoryRoot, path));
|
|
537
|
-
if (!info.isFile()) continue;
|
|
538
|
-
if (info.size > MAX_FILE_BYTES) {
|
|
539
|
-
skipped++;
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
const existing = previous.entries[path];
|
|
543
|
-
if (!options.full && existing && statUnchanged(existing, info)) {
|
|
544
|
-
entries[path] = existing;
|
|
545
|
-
unchanged++;
|
|
546
|
-
continue;
|
|
547
|
-
}
|
|
548
|
-
const content = await readFile4(join5(repositoryRoot, path), "utf8");
|
|
549
|
-
const hash = hashContent(content);
|
|
550
|
-
const metadata = trustedMetadata(info);
|
|
551
|
-
dirty = true;
|
|
552
|
-
if (!options.full && existing && existing.hash === hash) {
|
|
553
|
-
const { mtimeMs: _mtime, ctimeMs: _ctime, ...rest } = existing;
|
|
554
|
-
entries[path] = { ...rest, size: info.size, ...metadata };
|
|
555
|
-
unchanged++;
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
const { language, lines, symbols, summary } = summarizeSource(path, content);
|
|
559
|
-
const symbolTable = supportsSymbols(path) ? extractSymbolTable(content) : [];
|
|
560
|
-
entries[path] = {
|
|
561
|
-
path,
|
|
562
|
-
hash,
|
|
563
|
-
size: info.size,
|
|
564
|
-
...metadata,
|
|
565
|
-
language,
|
|
566
|
-
lines,
|
|
567
|
-
symbols,
|
|
568
|
-
...symbolTable.length > 0 ? { symbolTable } : {},
|
|
569
|
-
summary,
|
|
570
|
-
indexedAt: now().toISOString()
|
|
571
|
-
};
|
|
572
|
-
if (existing) changed++;
|
|
573
|
-
else added++;
|
|
574
|
-
} catch (error) {
|
|
575
|
-
errors.push({ path, error: error.message });
|
|
576
|
-
skipped++;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
const removed = Object.keys(previous.entries).filter((path) => !(path in entries)).length;
|
|
580
|
-
const manifest = {
|
|
581
|
-
version: INDEX_MANIFEST_VERSION,
|
|
582
|
-
generatedAt: now().toISOString(),
|
|
583
|
-
entries: sortEntries(entries)
|
|
584
|
-
};
|
|
585
|
-
if (dirty || removed > 0) await store.write(manifest);
|
|
586
|
-
const architecture = await refreshArchitectureCache(repositoryRoot, manifest);
|
|
587
|
-
const result = {
|
|
588
|
-
scanned: files.length,
|
|
589
|
-
added,
|
|
590
|
-
changed,
|
|
591
|
-
unchanged,
|
|
592
|
-
removed,
|
|
593
|
-
skipped,
|
|
594
|
-
errors,
|
|
595
|
-
durationMs: Date.now() - start,
|
|
596
|
-
totalIndexed: Object.keys(entries).length,
|
|
597
|
-
architecture: {
|
|
598
|
-
source: architecture.source,
|
|
599
|
-
readFiles: architecture.stats.readFiles,
|
|
600
|
-
reusedFiles: architecture.stats.reusedFiles
|
|
601
|
-
}
|
|
602
|
-
};
|
|
603
|
-
return { result, manifest };
|
|
604
|
-
}
|
|
605
|
-
function statUnchanged(entry, info) {
|
|
606
|
-
return entry.mtimeMs !== void 0 && entry.size === info.size && entry.mtimeMs === info.mtimeMs && entry.ctimeMs === info.ctimeMs;
|
|
607
|
-
}
|
|
608
|
-
function trustedMetadata(info) {
|
|
609
|
-
if (Date.now() - Math.max(info.mtimeMs, info.ctimeMs) < RACY_WINDOW_MS) return {};
|
|
610
|
-
return { mtimeMs: info.mtimeMs, ctimeMs: info.ctimeMs };
|
|
611
|
-
}
|
|
612
|
-
function hashContent(content) {
|
|
613
|
-
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
614
|
-
}
|
|
615
|
-
function sortEntries(entries) {
|
|
616
|
-
return Object.fromEntries(
|
|
617
|
-
Object.keys(entries).sort().map((key) => [key, entries[key]])
|
|
618
|
-
);
|
|
619
|
-
}
|
|
16
|
+
} from "./chunk-CG5B3LYG.js";
|
|
620
17
|
|
|
621
18
|
// src/integrations/graphify.ts
|
|
622
19
|
import { execFile } from "child_process";
|
|
623
|
-
import { mkdir
|
|
20
|
+
import { mkdir, mkdtemp, readFile as readFile2, rm, stat, utimes } from "fs/promises";
|
|
624
21
|
import { tmpdir } from "os";
|
|
625
|
-
import { dirname
|
|
22
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
626
23
|
import { promisify } from "util";
|
|
627
24
|
|
|
628
25
|
// src/map/graph-freshness.ts
|
|
629
|
-
import { createHash
|
|
630
|
-
import { readFile
|
|
631
|
-
var digest = (value) =>
|
|
26
|
+
import { createHash } from "crypto";
|
|
27
|
+
import { readFile } from "fs/promises";
|
|
28
|
+
var digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
632
29
|
async function writeGraphSourceSnapshot(path, manifest) {
|
|
633
30
|
await writeAtomic(
|
|
634
31
|
`${path}.sources.json`,
|
|
635
32
|
JSON.stringify({
|
|
636
33
|
version: 1,
|
|
637
|
-
graphHash: digest(await
|
|
34
|
+
graphHash: digest(await readFile(path, "utf8")),
|
|
638
35
|
sources: Object.fromEntries(
|
|
639
36
|
Object.values(manifest.entries).map((entry) => [entry.path, entry.hash])
|
|
640
37
|
)
|
|
@@ -644,13 +41,13 @@ async function writeGraphSourceSnapshot(path, manifest) {
|
|
|
644
41
|
async function readGraphSourceSnapshot(path) {
|
|
645
42
|
let raw;
|
|
646
43
|
try {
|
|
647
|
-
raw = await
|
|
44
|
+
raw = await readFile(`${path}.sources.json`, "utf8");
|
|
648
45
|
} catch (error) {
|
|
649
46
|
return error.code === "ENOENT" ? null : {};
|
|
650
47
|
}
|
|
651
48
|
try {
|
|
652
49
|
const snapshot = JSON.parse(raw);
|
|
653
|
-
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))
|
|
654
51
|
return {};
|
|
655
52
|
return Object.fromEntries(
|
|
656
53
|
Object.entries(snapshot.sources).filter(
|
|
@@ -737,17 +134,17 @@ async function graphifyGraphExists(repositoryRoot, graphPath = DEFAULT_GRAPH_PAT
|
|
|
737
134
|
}
|
|
738
135
|
async function generateGraphifyGraph(options) {
|
|
739
136
|
const run = options.run ?? runCommand;
|
|
740
|
-
const repositoryRoot =
|
|
137
|
+
const repositoryRoot = resolve(options.repositoryRoot);
|
|
741
138
|
const graphPath = options.graphPath ?? DEFAULT_GRAPH_PATH;
|
|
742
139
|
const destination = resolveGraphPath(repositoryRoot, graphPath);
|
|
743
|
-
await
|
|
744
|
-
await ignoreGeneratedGraphDirectory(
|
|
140
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
141
|
+
await ignoreGeneratedGraphDirectory(dirname(destination));
|
|
745
142
|
const { manifest: sourceSnapshot } = await loadFreshIndex(repositoryRoot);
|
|
746
143
|
const current = await readGraphFileIfPresent(destination);
|
|
747
144
|
const isDefaultPath = graphPath === DEFAULT_GRAPH_PATH;
|
|
748
145
|
if (current && isDefaultPath) {
|
|
749
|
-
const backup = await
|
|
750
|
-
const backupStat = await
|
|
146
|
+
const backup = await readFile2(destination, "utf8");
|
|
147
|
+
const backupStat = await stat(destination);
|
|
751
148
|
try {
|
|
752
149
|
await run("graphify", ["update", repositoryRoot, "--force", "--no-cluster"], repositoryRoot);
|
|
753
150
|
const updated = await readGraphFile(destination);
|
|
@@ -761,16 +158,16 @@ async function generateGraphifyGraph(options) {
|
|
|
761
158
|
);
|
|
762
159
|
}
|
|
763
160
|
}
|
|
764
|
-
const stagingRoot = await mkdtemp(
|
|
161
|
+
const stagingRoot = await mkdtemp(join(tmpdir(), "codebuddy-graphify-"));
|
|
765
162
|
try {
|
|
766
163
|
await run(
|
|
767
164
|
"graphify",
|
|
768
165
|
["extract", repositoryRoot, "--out", stagingRoot, "--code-only", "--no-cluster"],
|
|
769
166
|
repositoryRoot
|
|
770
167
|
);
|
|
771
|
-
const stagedPath =
|
|
168
|
+
const stagedPath = join(stagingRoot, DEFAULT_GRAPH_PATH);
|
|
772
169
|
const staged = await readGraphFile(stagedPath);
|
|
773
|
-
await writeAtomic(destination, await
|
|
170
|
+
await writeAtomic(destination, await readFile2(stagedPath, "utf8"));
|
|
774
171
|
await writeGraphSourceSnapshot(destination, sourceSnapshot);
|
|
775
172
|
return { graphPath, generated: true, ...staged };
|
|
776
173
|
} finally {
|
|
@@ -778,16 +175,16 @@ async function generateGraphifyGraph(options) {
|
|
|
778
175
|
}
|
|
779
176
|
}
|
|
780
177
|
async function ignoreGeneratedGraphDirectory(directory) {
|
|
781
|
-
const path =
|
|
178
|
+
const path = join(directory, ".gitignore");
|
|
782
179
|
try {
|
|
783
|
-
await
|
|
180
|
+
await stat(path);
|
|
784
181
|
} catch {
|
|
785
182
|
await writeAtomic(path, "# CodeBuddy-generated Graphify output (derived, rebuildable)\n*\n");
|
|
786
183
|
}
|
|
787
184
|
}
|
|
788
185
|
function resolveGraphPath(repositoryRoot, graphPath) {
|
|
789
|
-
const absolute =
|
|
790
|
-
const relativePath =
|
|
186
|
+
const absolute = resolve(repositoryRoot, graphPath);
|
|
187
|
+
const relativePath = relative(repositoryRoot, absolute);
|
|
791
188
|
if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`)) {
|
|
792
189
|
throw new Error(`Graph path must be inside the project root: ${graphPath}`);
|
|
793
190
|
}
|
|
@@ -801,10 +198,10 @@ async function readGraphFileIfPresent(path) {
|
|
|
801
198
|
}
|
|
802
199
|
}
|
|
803
200
|
async function readGraphFile(path) {
|
|
804
|
-
const info = await
|
|
201
|
+
const info = await stat(path);
|
|
805
202
|
if (!info.isFile() || info.size === 0)
|
|
806
203
|
throw new Error(`Graphify graph is missing or empty: ${path}`);
|
|
807
|
-
const raw = JSON.parse(await
|
|
204
|
+
const raw = JSON.parse(await readFile2(path, "utf8"));
|
|
808
205
|
const nodes = arrayLength(raw.nodes ?? raw.entities);
|
|
809
206
|
const edges = arrayLength(raw.edges ?? raw.links ?? raw.relationships);
|
|
810
207
|
if (nodes === 0) throw new Error(`Graphify graph contains no nodes: ${path}`);
|
|
@@ -814,13 +211,17 @@ function arrayLength(value) {
|
|
|
814
211
|
return Array.isArray(value) ? value.length : 0;
|
|
815
212
|
}
|
|
816
213
|
|
|
214
|
+
// src/context/file-vectors.ts
|
|
215
|
+
import { mkdir as mkdir3, readFile as readFile6 } from "fs/promises";
|
|
216
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
217
|
+
|
|
817
218
|
// src/context/discovery.ts
|
|
818
|
-
import { readFile as
|
|
819
|
-
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";
|
|
820
221
|
|
|
821
222
|
// src/map/graphify.ts
|
|
822
|
-
import { readFile as
|
|
823
|
-
import { isAbsolute, relative as
|
|
223
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
224
|
+
import { isAbsolute, relative as relative2, resolve as resolve2 } from "path";
|
|
824
225
|
var NON_DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
|
|
825
226
|
"contains",
|
|
826
227
|
"part_of",
|
|
@@ -854,7 +255,7 @@ var DIRECT_DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
|
|
|
854
255
|
]);
|
|
855
256
|
var MAX_EDGE_SYMBOLS = 8;
|
|
856
257
|
async function loadGraphifyMap(graphPath, repositoryRoot = process.cwd()) {
|
|
857
|
-
const raw = JSON.parse(await
|
|
258
|
+
const raw = JSON.parse(await readFile3(graphPath, "utf8"));
|
|
858
259
|
const nodes = arrayRecord(raw.nodes ?? raw.entities);
|
|
859
260
|
const edges = arrayRecord(raw.edges ?? raw.links ?? raw.relationships);
|
|
860
261
|
const nodePaths = /* @__PURE__ */ new Map();
|
|
@@ -867,7 +268,7 @@ async function loadGraphifyMap(graphPath, repositoryRoot = process.cwd()) {
|
|
|
867
268
|
nodePaths.set(id, path);
|
|
868
269
|
const label = stringValue(node.label ?? node.name);
|
|
869
270
|
if (label && label !== path && !path.endsWith(`/${label}`)) nodeLabels.set(id, label);
|
|
870
|
-
modules.push({ path, language:
|
|
271
|
+
modules.push({ path, language: languageFor(path), imports: [] });
|
|
871
272
|
}
|
|
872
273
|
const modulePaths = new Set(modules.map((module) => module.path));
|
|
873
274
|
const importedEdges = [];
|
|
@@ -947,20 +348,21 @@ function graphifyPath(node, repositoryRoot) {
|
|
|
947
348
|
return candidate ? normalizeGraphPath(candidate, repositoryRoot) : null;
|
|
948
349
|
}
|
|
949
350
|
function normalizeGraphPath(path, repositoryRoot) {
|
|
950
|
-
const absolute = isAbsolute(path) ?
|
|
951
|
-
const relativePath =
|
|
351
|
+
const absolute = isAbsolute(path) ? resolve2(path) : resolve2(repositoryRoot, path);
|
|
352
|
+
const relativePath = relative2(resolve2(repositoryRoot), absolute).replace(/\\/g, "/");
|
|
952
353
|
if (relativePath === "" || relativePath === ".." || relativePath.startsWith("../")) return null;
|
|
953
354
|
return relativePath.replace(/^\.\//, "");
|
|
954
355
|
}
|
|
955
356
|
function stringValue(value) {
|
|
956
357
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
957
358
|
}
|
|
958
|
-
function
|
|
359
|
+
function languageFor(path) {
|
|
360
|
+
if (/\.py$/.test(path)) return "py";
|
|
959
361
|
return /\.tsx?$/.test(path) ? "ts" : "js";
|
|
960
362
|
}
|
|
961
363
|
|
|
962
364
|
// src/context/anchors.ts
|
|
963
|
-
import { isAbsolute as isAbsolute2, relative as
|
|
365
|
+
import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
|
|
964
366
|
var MAX_ANCHORS = 3;
|
|
965
367
|
var MAX_ERROR_SEARCH_FILES = 2e3;
|
|
966
368
|
var JS_FRAME = /\bat\s+(?:[^\s()]+\s+\()?([^\s()]+?):(\d+):\d+\)?/g;
|
|
@@ -968,7 +370,7 @@ var PY_FRAME = /\bFile\s+"([^"]+)",\s+line\s+(\d+)/g;
|
|
|
968
370
|
var PATH_TOKEN = /(?:^|[\s(`'"[])((?:\.{0,2}\/)?[\w@.-]+(?:\/[\w@.-]+)*\.[A-Za-z0-9]+)(?::(\d+))?/g;
|
|
969
371
|
var QUOTED = /"([^"\n]{8,200})"|'([^'\n]{8,200})'|`([^`\n]{8,200})`/g;
|
|
970
372
|
async function extractTaskAnchors(repositoryRoot, task, paths) {
|
|
971
|
-
const root =
|
|
373
|
+
const root = resolve3(repositoryRoot);
|
|
972
374
|
const found = [];
|
|
973
375
|
const add = (raw, line, kind, at) => {
|
|
974
376
|
const path = toIndexedPath(root, raw, paths);
|
|
@@ -996,8 +398,8 @@ async function extractTaskAnchors(repositoryRoot, task, paths) {
|
|
|
996
398
|
return anchors.slice(0, MAX_ANCHORS);
|
|
997
399
|
}
|
|
998
400
|
function toIndexedPath(root, raw, paths) {
|
|
999
|
-
const absolute = isAbsolute2(raw) ? raw :
|
|
1000
|
-
const rel =
|
|
401
|
+
const absolute = isAbsolute2(raw) ? raw : resolve3(root, raw);
|
|
402
|
+
const rel = relative3(root, absolute);
|
|
1001
403
|
if (rel === "" || rel.startsWith(`..${sep2}`) || rel === ".." || isAbsolute2(rel)) return null;
|
|
1002
404
|
const normalized = rel.replace(/\\/g, "/");
|
|
1003
405
|
return paths.has(normalized) ? normalized : null;
|
|
@@ -1023,16 +425,131 @@ async function errorAnchors(root, task, paths) {
|
|
|
1023
425
|
return matches.flatMap((found) => found.length === 1 ? found : []);
|
|
1024
426
|
}
|
|
1025
427
|
|
|
428
|
+
// src/context/content-index.ts
|
|
429
|
+
import { mkdir as mkdir2, readFile as readFile4 } from "fs/promises";
|
|
430
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
431
|
+
var PASSAGE_LINES = 40;
|
|
432
|
+
var K1 = 1.2;
|
|
433
|
+
var B = 0.75;
|
|
434
|
+
var MIN_TERMS = 2;
|
|
435
|
+
var MIN_SHARE = 0.5;
|
|
436
|
+
var MIN_IDF_COVERAGE = 0.3;
|
|
437
|
+
async function rankContent(repositoryRoot, manifest, terms, wantsTests, wantsDocs = false, wantsChangelog = false) {
|
|
438
|
+
const none = { matches: [], idf: /* @__PURE__ */ new Map() };
|
|
439
|
+
if (terms.length === 0) return none;
|
|
440
|
+
const paths = Object.keys(manifest.entries).filter(
|
|
441
|
+
(path) => (!NON_SOURCE_PATH.test(path) || wantsDocs && DOC_PATH.test(path) || wantsChangelog && CHANGELOG_PATH.test(path)) && (wantsTests || !TEST_PATH.test(path))
|
|
442
|
+
);
|
|
443
|
+
const cache = await loadCache(repositoryRoot);
|
|
444
|
+
let changed = false;
|
|
445
|
+
const files = {};
|
|
446
|
+
await Promise.all(
|
|
447
|
+
paths.map(async (path) => {
|
|
448
|
+
const hash = manifest.entries[path]?.hash ?? "";
|
|
449
|
+
const cached = cache.files[path];
|
|
450
|
+
if (cached && cached.hash === hash) {
|
|
451
|
+
files[path] = cached;
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const source = await readBoundedSource(repositoryRoot, path);
|
|
455
|
+
if (source === null) return;
|
|
456
|
+
const counts = {};
|
|
457
|
+
const words = splitWords(`${path} ${source}`);
|
|
458
|
+
for (const word of words) counts[word] = (counts[word] ?? 0) + 1;
|
|
459
|
+
const lines = source.split("\n");
|
|
460
|
+
const passages = [];
|
|
461
|
+
for (let start = 0; start === 0 || start + PASSAGE_LINES / 2 < lines.length; start += PASSAGE_LINES / 2) {
|
|
462
|
+
passages.push([
|
|
463
|
+
...new Set(splitWords(lines.slice(start, start + PASSAGE_LINES).join("\n")))
|
|
464
|
+
]);
|
|
465
|
+
}
|
|
466
|
+
files[path] = { hash, length: words.length, counts, passages };
|
|
467
|
+
changed = true;
|
|
468
|
+
})
|
|
469
|
+
);
|
|
470
|
+
const retained = Object.fromEntries(
|
|
471
|
+
Object.entries(cache.files).filter(
|
|
472
|
+
([path, file]) => !(path in files) && manifest.entries[path]?.hash === file.hash
|
|
473
|
+
)
|
|
474
|
+
);
|
|
475
|
+
const kept = Object.keys(retained).length + Object.keys(files).length;
|
|
476
|
+
if (changed || kept !== Object.keys(cache.files).length) {
|
|
477
|
+
await saveCache(repositoryRoot, { version: 2, files: { ...retained, ...files } });
|
|
478
|
+
}
|
|
479
|
+
const documents = Object.entries(files);
|
|
480
|
+
if (documents.length === 0) return none;
|
|
481
|
+
const averageLength = documents.reduce((sum, [, file]) => sum + file.length, 0) / documents.length;
|
|
482
|
+
const idf = new Map(
|
|
483
|
+
terms.map((term) => {
|
|
484
|
+
const df = documents.filter(([, file]) => (file.counts[term] ?? 0) > 0).length;
|
|
485
|
+
return [term, Math.log(1 + (documents.length - df + 0.5) / (df + 0.5))];
|
|
486
|
+
})
|
|
487
|
+
);
|
|
488
|
+
const totalIdf = [...idf.values()].reduce((sum, value) => sum + value, 0);
|
|
489
|
+
const scored = documents.map(([path, file]) => {
|
|
490
|
+
const fileName = new Set(splitWords(path.slice(path.lastIndexOf("/") + 1)));
|
|
491
|
+
let score = 0;
|
|
492
|
+
let covered = 0;
|
|
493
|
+
for (const term of terms) {
|
|
494
|
+
const tf = file.counts[term] ?? 0;
|
|
495
|
+
if (tf === 0) continue;
|
|
496
|
+
covered += idf.get(term) ?? 0;
|
|
497
|
+
const saturation = tf * (K1 + 1) / (tf + K1 * (1 - B + B * file.length / averageLength));
|
|
498
|
+
score += (idf.get(term) ?? 0) * saturation * (fileName.has(term) ? 1.5 : 1);
|
|
499
|
+
}
|
|
500
|
+
const named = terms.filter((term) => fileName.has(term));
|
|
501
|
+
const matched = Math.max(
|
|
502
|
+
named.length,
|
|
503
|
+
...file.passages.map(
|
|
504
|
+
(words) => (/* @__PURE__ */ new Set([...named, ...terms.filter((term) => words.includes(term))])).size
|
|
505
|
+
)
|
|
506
|
+
);
|
|
507
|
+
return { path, score, terms: matched, coverage: totalIdf > 0 ? covered / totalIdf : 0 };
|
|
508
|
+
}).filter((match) => match.score > 0).sort((left, right) => right.score - left.score || left.path.localeCompare(right.path));
|
|
509
|
+
const best = scored[0]?.score ?? 0;
|
|
510
|
+
const needed = Math.min(MIN_TERMS, terms.length);
|
|
511
|
+
const matches = scored.filter(
|
|
512
|
+
(match) => match.terms >= needed && match.score >= MIN_SHARE * best && match.coverage >= MIN_IDF_COVERAGE
|
|
513
|
+
).map(({ path, score, terms: matched }) => ({ path, score, terms: matched }));
|
|
514
|
+
return { matches, idf };
|
|
515
|
+
}
|
|
516
|
+
function idfCoverage(text, terms, idf) {
|
|
517
|
+
const total = terms.reduce((sum, term) => sum + (idf.get(term) ?? 0), 0);
|
|
518
|
+
if (total === 0) return 0;
|
|
519
|
+
const words = new Set(splitWords(text));
|
|
520
|
+
return terms.reduce((sum, term) => sum + (words.has(term) ? idf.get(term) ?? 0 : 0), 0) / total;
|
|
521
|
+
}
|
|
522
|
+
function cachePath(repositoryRoot) {
|
|
523
|
+
return join2(repositoryRoot, ".codebuddy", "cache", "content-terms.json");
|
|
524
|
+
}
|
|
525
|
+
async function loadCache(repositoryRoot) {
|
|
526
|
+
try {
|
|
527
|
+
const parsed = JSON.parse(await readFile4(cachePath(repositoryRoot), "utf8"));
|
|
528
|
+
if (parsed.version === 2 && parsed.files && typeof parsed.files === "object") return parsed;
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
return { version: 2, files: {} };
|
|
532
|
+
}
|
|
533
|
+
async function saveCache(repositoryRoot, cache) {
|
|
534
|
+
try {
|
|
535
|
+
const path = cachePath(repositoryRoot);
|
|
536
|
+
await mkdir2(dirname2(path), { recursive: true });
|
|
537
|
+
await writeAtomic(path, JSON.stringify(cache));
|
|
538
|
+
} catch {
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
1026
542
|
// src/context/discovery.ts
|
|
1027
543
|
var TASK_PATH_LIMIT = 10;
|
|
1028
544
|
var TASK_FILES = 3;
|
|
1029
545
|
var EXPANSION = { seeds: 2, files: 1 };
|
|
1030
546
|
var MAX_EVIDENCE_BYTES = 512e3;
|
|
1031
547
|
async function readBoundedSource(repositoryRoot, path) {
|
|
548
|
+
if (isCredentialPath(path)) return null;
|
|
1032
549
|
try {
|
|
1033
|
-
const absolute =
|
|
1034
|
-
if ((await
|
|
1035
|
-
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");
|
|
1036
553
|
return source.includes("\0") ? null : source;
|
|
1037
554
|
} catch {
|
|
1038
555
|
return null;
|
|
@@ -1077,10 +594,10 @@ async function discoverTaskPaths(repositoryRoot, task, manifest, mapSource, opti
|
|
|
1077
594
|
async function withLlmSummaries(repositoryRoot, manifest) {
|
|
1078
595
|
const entries = Object.values(manifest.entries);
|
|
1079
596
|
try {
|
|
1080
|
-
const { resolveLlmConfig } = await import("./config-
|
|
597
|
+
const { resolveLlmConfig } = await import("./config-GEZFLXK6.js");
|
|
1081
598
|
const config = await resolveLlmConfig(repositoryRoot);
|
|
1082
599
|
if (!config.enabled || !config.features.summaries) return entries;
|
|
1083
|
-
const { loadLlmSummaries } = await import("./summaries-
|
|
600
|
+
const { loadLlmSummaries } = await import("./summaries-ML53R7EL.js");
|
|
1084
601
|
const summaries = await loadLlmSummaries(repositoryRoot, manifest);
|
|
1085
602
|
if (summaries.size === 0) return entries;
|
|
1086
603
|
return entries.map((entry) => {
|
|
@@ -1092,7 +609,10 @@ async function withLlmSummaries(repositoryRoot, manifest) {
|
|
|
1092
609
|
}
|
|
1093
610
|
}
|
|
1094
611
|
async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options) {
|
|
1095
|
-
const
|
|
612
|
+
const wantsChangelog = asksForChangelog(task);
|
|
613
|
+
const entries = (await withLlmSummaries(repositoryRoot, manifest)).filter(
|
|
614
|
+
(entry) => wantsChangelog || !CHANGELOG_PATH.test(entry.path)
|
|
615
|
+
);
|
|
1096
616
|
const wantsTests = /\b(tests?|spec)\b/i.test(task);
|
|
1097
617
|
const identifiers = identifierTokens(task).filter((token) => CODE_IDENTIFIER.test(token));
|
|
1098
618
|
if (identifiers.length > 0) {
|
|
@@ -1100,7 +620,10 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
|
|
|
1100
620
|
path: entry.path,
|
|
1101
621
|
named: new Set(
|
|
1102
622
|
(entry.symbolTable ?? []).flatMap(
|
|
1103
|
-
(symbol) =>
|
|
623
|
+
(symbol) => (
|
|
624
|
+
// A doc heading naming an identifier is not its declaration.
|
|
625
|
+
symbol.kind === "heading" ? [] : symbol.name.split(".").filter((segment) => identifiers.includes(segment))
|
|
626
|
+
)
|
|
1104
627
|
)
|
|
1105
628
|
).size
|
|
1106
629
|
})).filter((entry) => entry.named > 0).sort((left, right) => right.named - left.named || left.path.localeCompare(right.path)).map((entry) => entry.path);
|
|
@@ -1123,16 +646,31 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
|
|
|
1123
646
|
}
|
|
1124
647
|
const commands = await commandFiles(repositoryRoot, task, entries);
|
|
1125
648
|
const scored = scoreTaskPaths(entries, task);
|
|
1126
|
-
const
|
|
649
|
+
const summaryKeyword = scored.filter((entry) => entry.score >= EVIDENCE_FLOOR.keywordScore).map((entry) => entry.path);
|
|
650
|
+
const { matches: content, idf } = await rankContent(
|
|
651
|
+
repositoryRoot,
|
|
652
|
+
manifest,
|
|
653
|
+
taskTerms(task),
|
|
654
|
+
wantsTests,
|
|
655
|
+
DOCS_TASK.test(task),
|
|
656
|
+
wantsChangelog
|
|
657
|
+
);
|
|
658
|
+
const keyword = content.length > 0 ? fuseRankings([summaryKeyword, content.map((match) => match.path)]) : summaryKeyword;
|
|
1127
659
|
const weakKeyword = scored.length - keyword.length;
|
|
1128
|
-
const semantic = options.semantic === false ? {
|
|
660
|
+
const semantic = options.semantic === false && options.zvecDeadlineAt === void 0 ? {
|
|
1129
661
|
status: "disabled",
|
|
1130
662
|
paths: [],
|
|
1131
663
|
hits: [],
|
|
1132
664
|
reason: "semantic retrieval off",
|
|
1133
665
|
stale: 0,
|
|
1134
666
|
changed: 0
|
|
1135
|
-
} : await import("./zvec-
|
|
667
|
+
} : await import("./zvec-XDZNZ2OL.js").then(
|
|
668
|
+
({ zvecCandidates }) => zvecCandidates(
|
|
669
|
+
repositoryRoot,
|
|
670
|
+
task,
|
|
671
|
+
options.semantic === false && options.zvecDeadlineAt !== void 0 ? { deadlineAt: options.zvecDeadlineAt, requireServer: true } : {}
|
|
672
|
+
)
|
|
673
|
+
).catch(
|
|
1136
674
|
() => ({
|
|
1137
675
|
status: "failed",
|
|
1138
676
|
paths: [],
|
|
@@ -1155,8 +693,11 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
|
|
|
1155
693
|
const vector = [];
|
|
1156
694
|
const partial = [];
|
|
1157
695
|
let uncorroborated = 0;
|
|
696
|
+
let belowFloor = 0;
|
|
697
|
+
const minScore = semantic.minScore ?? 0;
|
|
1158
698
|
for (const path of semantic.paths) {
|
|
1159
699
|
if (!wantsTests && TEST_PATH.test(path)) continue;
|
|
700
|
+
if (!wantsChangelog && CHANGELOG_PATH.test(path)) continue;
|
|
1160
701
|
if (keyword.includes(path)) vector.push(path);
|
|
1161
702
|
else if (await hasMatchedPassageEvidence(
|
|
1162
703
|
repositoryRoot,
|
|
@@ -1166,6 +707,10 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
|
|
|
1166
707
|
manifest.entries[path]?.symbolTable ?? []
|
|
1167
708
|
)) {
|
|
1168
709
|
const source = await readBoundedSource(repositoryRoot, path) ?? "";
|
|
710
|
+
if (idfCoverage(`${path} ${source}`, terms, idf) < minScore) {
|
|
711
|
+
belowFloor++;
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
1169
714
|
if (countTerms(source, terms) >= EVIDENCE_FLOOR.vectorTerms) vector.push(path);
|
|
1170
715
|
else partial.push(path);
|
|
1171
716
|
if (!evidence.has(path)) evidence.set(path, 0);
|
|
@@ -1181,6 +726,11 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
|
|
|
1181
726
|
explain.push(
|
|
1182
727
|
`retrieval: keyword ranking fused with ${semantic.reason}` + (uncorroborated > 0 ? `; ${uncorroborated} uncorroborated vector file(s) dropped` : "")
|
|
1183
728
|
);
|
|
729
|
+
if (belowFloor > 0) {
|
|
730
|
+
explain.push(
|
|
731
|
+
`abstention: ${belowFloor} vector-only file(s) below zvec.minScore ${minScore} (task IDF coverage of current source) dropped`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
1184
734
|
}
|
|
1185
735
|
let symbols;
|
|
1186
736
|
const reranked = await llmRerank({
|
|
@@ -1199,7 +749,8 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
|
|
|
1199
749
|
ranked = reranked.ranked;
|
|
1200
750
|
symbols = reranked.symbols;
|
|
1201
751
|
}
|
|
1202
|
-
|
|
752
|
+
const wantsDocs = DOCS_TASK.test(task);
|
|
753
|
+
if (!ranked.some((path) => countsAsSource(path, task))) {
|
|
1203
754
|
explain.push(
|
|
1204
755
|
"abstention: no source file reached the evidence floor (" + (ranked.length > 0 ? "only docs/config did" : `best keyword score ${scored[0]?.score ?? 0} < ${EVIDENCE_FLOOR.keywordScore}`) + (commands.length > 0 ? "); only the named CLI command returned" : "); no task targets returned")
|
|
1205
756
|
);
|
|
@@ -1210,6 +761,14 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
|
|
|
1210
761
|
`abstention: ${weakKeyword} weak keyword candidate(s) below the evidence floor (score < ${EVIDENCE_FLOOR.keywordScore}) dropped`
|
|
1211
762
|
);
|
|
1212
763
|
}
|
|
764
|
+
const vectors = options.semantic === false || reranked ? null : await rankFilesByVector(repositoryRoot, manifest, task);
|
|
765
|
+
if (vectors && vectors.length > 0) {
|
|
766
|
+
const doc = wantsDocs ? ranked.filter((path) => DOC_PATH.test(path)).slice(0, 1) : [];
|
|
767
|
+
ranked = [...doc, ...vectors.map((match) => match.path)].slice(0, TASK_FILES);
|
|
768
|
+
explain.push(
|
|
769
|
+
`retrieval: file vectors ordered the files (best ${vectors[0]?.score.toFixed(2)})`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
1213
772
|
const expansion = expandAlongImports(ranked, await mapSource, evidence, task);
|
|
1214
773
|
if (expansion.explain) explain.push(expansion.explain);
|
|
1215
774
|
if (commands.length > 0) {
|
|
@@ -1232,7 +791,7 @@ var MIN_RERANK_MS = 200;
|
|
|
1232
791
|
async function llmRerank(input) {
|
|
1233
792
|
let config;
|
|
1234
793
|
try {
|
|
1235
|
-
config = await (await import("./config-
|
|
794
|
+
config = await (await import("./config-GEZFLXK6.js")).resolveLlmConfig(input.repositoryRoot);
|
|
1236
795
|
} catch {
|
|
1237
796
|
return null;
|
|
1238
797
|
}
|
|
@@ -1257,7 +816,7 @@ async function llmRerank(input) {
|
|
|
1257
816
|
input.deadlineAt === void 0 ? config.timeoutMs : input.deadlineAt - Date.now()
|
|
1258
817
|
);
|
|
1259
818
|
if (deadlineMs < MIN_RERANK_MS) return skipped("no time left in the hook budget");
|
|
1260
|
-
const { rerankCandidates } = await import("./rerank-
|
|
819
|
+
const { rerankCandidates } = await import("./rerank-IQAEWLQR.js");
|
|
1261
820
|
const result = await rerankCandidates(
|
|
1262
821
|
input.repositoryRoot,
|
|
1263
822
|
config,
|
|
@@ -1329,7 +888,7 @@ async function commandFiles(repositoryRoot, task, entries) {
|
|
|
1329
888
|
var COMMAND_DIR = /(^|\/)(cli|cmd|commands?)\//;
|
|
1330
889
|
async function packageBins(repositoryRoot) {
|
|
1331
890
|
try {
|
|
1332
|
-
const manifest = JSON.parse(await
|
|
891
|
+
const manifest = JSON.parse(await readFile5(join3(repositoryRoot, "package.json"), "utf8"));
|
|
1333
892
|
if (typeof manifest.bin === "string" && typeof manifest.name === "string") {
|
|
1334
893
|
return [manifest.name.slice(manifest.name.lastIndexOf("/") + 1)];
|
|
1335
894
|
}
|
|
@@ -1419,8 +978,30 @@ async function hasMatchedPassageEvidence(repositoryRoot, path, hits, terms, symb
|
|
|
1419
978
|
);
|
|
1420
979
|
});
|
|
1421
980
|
}
|
|
1422
|
-
var TEST_PATH = /(^|\/)(__tests__|__fixtures__|fixtures?|test|tests)\/|\.(test|spec)\.[a-z]
|
|
1423
|
-
var
|
|
981
|
+
var TEST_PATH = /(^|\/)(__tests__|__fixtures__|fixtures?|test|tests)\/|\.(test|spec)\.[a-z]+$|(^|\/)(test_[^/]*|[^/]*_test|conftest)\.py$/;
|
|
982
|
+
var CHANGELOG_PATH = new RegExp(
|
|
983
|
+
[
|
|
984
|
+
"(?:CHANGES|CHANGELOG|HISTORY|NEWS|RELEASES?|RELEASE[-_]?NOTES)(?:[._-][^/]*)?",
|
|
985
|
+
"[Cc]hange[-_]?[Ll]og[^/]*",
|
|
986
|
+
"[Rr]elease[-_]?[Nn]otes[^/]*",
|
|
987
|
+
"(?:[Cc]hanges|[Hh]istory|[Nn]ews|[Rr]eleases?)\\.(?:md|mdx|rst|txt|adoc|markdown)"
|
|
988
|
+
].map((name) => `(?:^|/)${name}$`).join("|")
|
|
989
|
+
);
|
|
990
|
+
function asksForChangelog(task) {
|
|
991
|
+
return /\b(?:change[-_ ]?logs?|release[-_ ]?notes?|(?:release|version) history)\b/i.test(task) || /\b(?:CHANGES|HISTORY|NEWS|RELEASES?)\b/.test(task) || /\b(?:changes|history|news|releases?)\.(?:md|mdx|rst|txt|adoc)\b/i.test(task);
|
|
992
|
+
}
|
|
993
|
+
var NON_SOURCE_BASE = /(^|\/)(docs?|\.github)\/|\.(md|mdx|rst|adoc|txt|ya?ml|json|lock)$|(^|\/)CHANGELOG/i;
|
|
994
|
+
var NON_SOURCE_PATH = {
|
|
995
|
+
test: (path) => NON_SOURCE_BASE.test(path) || CHANGELOG_PATH.test(path)
|
|
996
|
+
};
|
|
997
|
+
var PROSE_PATH = /(^|\/)docs?\/|\.(md|mdx|rst|txt)$/i;
|
|
998
|
+
var DOC_PATH = {
|
|
999
|
+
test: (path) => PROSE_PATH.test(path) && !CHANGELOG_PATH.test(path)
|
|
1000
|
+
};
|
|
1001
|
+
var DOCS_TASK = /\b(docs?|documentation|documented|guides?|tutorials?|readme|manual)\b/i;
|
|
1002
|
+
function countsAsSource(path, task) {
|
|
1003
|
+
return !NON_SOURCE_PATH.test(path) || DOCS_TASK.test(task) && DOC_PATH.test(path) || asksForChangelog(task) && CHANGELOG_PATH.test(path);
|
|
1004
|
+
}
|
|
1424
1005
|
function scoreTaskPaths(entries, task) {
|
|
1425
1006
|
const terms = taskTerms(task);
|
|
1426
1007
|
if (terms.length === 0) return [];
|
|
@@ -1457,7 +1038,7 @@ function scoreTaskPaths(entries, task) {
|
|
|
1457
1038
|
}
|
|
1458
1039
|
}
|
|
1459
1040
|
if (weighted === 0) return [];
|
|
1460
|
-
if (
|
|
1041
|
+
if (!countsAsSource(path, task)) {
|
|
1461
1042
|
score *= 0.5;
|
|
1462
1043
|
weighted *= 0.5;
|
|
1463
1044
|
}
|
|
@@ -1467,12 +1048,22 @@ function scoreTaskPaths(entries, task) {
|
|
|
1467
1048
|
);
|
|
1468
1049
|
}
|
|
1469
1050
|
function splitWords(text) {
|
|
1470
|
-
return text
|
|
1471
|
-
|
|
1472
|
-
|
|
1051
|
+
return rawWords(text).map(foldPlural);
|
|
1052
|
+
}
|
|
1053
|
+
function rawWords(text) {
|
|
1054
|
+
return text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 2);
|
|
1055
|
+
}
|
|
1056
|
+
function foldPlural(word) {
|
|
1057
|
+
if (word.length <= 3 || !word.endsWith("s") || /(ss|us|is)$/.test(word)) return word;
|
|
1058
|
+
if (/(ss|sh|ch|x|z)es$/.test(word)) return word.slice(0, -2);
|
|
1059
|
+
return word.slice(0, -1);
|
|
1473
1060
|
}
|
|
1474
1061
|
function taskTerms(task) {
|
|
1475
|
-
return [
|
|
1062
|
+
return [
|
|
1063
|
+
...new Set(
|
|
1064
|
+
rawWords(task).filter((word) => !TASK_STOPWORDS.has(word)).map(foldPlural).filter((term) => !TASK_STOPWORDS.has(term))
|
|
1065
|
+
)
|
|
1066
|
+
];
|
|
1476
1067
|
}
|
|
1477
1068
|
function countTerms(text, terms) {
|
|
1478
1069
|
if (terms.length === 0) return 0;
|
|
@@ -1492,24 +1083,24 @@ async function loadProjectArchitectureMap(repositoryRoot, manifestSource) {
|
|
|
1492
1083
|
const builtIn = (await loadArchitectureMap(repositoryRoot, manifest)).map;
|
|
1493
1084
|
if (config.graphify?.enabled && await graphifyGraphExists(repositoryRoot, config.graphify.graphPath)) {
|
|
1494
1085
|
try {
|
|
1495
|
-
const graphPath =
|
|
1086
|
+
const graphPath = join3(
|
|
1496
1087
|
repositoryRoot,
|
|
1497
1088
|
config.graphify.graphPath ?? "graphify-out/graph.json"
|
|
1498
1089
|
);
|
|
1499
|
-
const [graph, graphFile,
|
|
1090
|
+
const [graph, graphFile, sourceHashes] = await Promise.all([
|
|
1500
1091
|
loadGraphifyMap(graphPath, repositoryRoot),
|
|
1501
|
-
|
|
1092
|
+
stat2(graphPath),
|
|
1502
1093
|
readGraphSourceSnapshot(graphPath)
|
|
1503
1094
|
]);
|
|
1504
1095
|
const livePaths = new Set(
|
|
1505
1096
|
(await Promise.all(
|
|
1506
1097
|
graph.modules.map(async (module) => {
|
|
1507
1098
|
try {
|
|
1508
|
-
const absolute = await realpath(
|
|
1509
|
-
const inside =
|
|
1099
|
+
const absolute = await realpath(join3(repositoryRoot, module.path));
|
|
1100
|
+
const inside = relative4(await realpath(repositoryRoot), absolute);
|
|
1510
1101
|
if (inside.startsWith("..") || isAbsolute3(inside)) return null;
|
|
1511
|
-
const source = await
|
|
1512
|
-
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;
|
|
1513
1104
|
return source.isFile() && fresh ? module.path : null;
|
|
1514
1105
|
} catch {
|
|
1515
1106
|
return null;
|
|
@@ -1554,20 +1145,138 @@ async function loadProjectArchitectureMap(repositoryRoot, manifestSource) {
|
|
|
1554
1145
|
return { ...builtIn, source: "imports" };
|
|
1555
1146
|
}
|
|
1556
1147
|
|
|
1148
|
+
// src/context/file-vectors.ts
|
|
1149
|
+
var CHUNK_LINES = 40;
|
|
1150
|
+
var CHUNK_STEP = 30;
|
|
1151
|
+
var CHUNK_CHARS = 2e3;
|
|
1152
|
+
var BATCH = 16;
|
|
1153
|
+
var MODEL = `${LOCAL_EMBEDDING_MODEL.id}@${LOCAL_EMBEDDING_MODEL.revision}`;
|
|
1154
|
+
async function localEmbed(texts) {
|
|
1155
|
+
const { LocalModelProvider } = await import("./local-ST4WMRCG.js");
|
|
1156
|
+
return (await new LocalModelProvider().embed({ input: texts })).vectors;
|
|
1157
|
+
}
|
|
1158
|
+
async function refreshFileVectors(repositoryRoot, manifest, deadlineAt, embed = localEmbed) {
|
|
1159
|
+
const store = await loadStore(repositoryRoot);
|
|
1160
|
+
const eligible = eligiblePaths(manifest);
|
|
1161
|
+
const files = {};
|
|
1162
|
+
const todo = [];
|
|
1163
|
+
for (const path of eligible) {
|
|
1164
|
+
const cached = store.files[path];
|
|
1165
|
+
if (cached && cached.hash === manifest.entries[path]?.hash) files[path] = cached;
|
|
1166
|
+
else todo.push(path);
|
|
1167
|
+
}
|
|
1168
|
+
let embedded = 0;
|
|
1169
|
+
let lastSave = Date.now();
|
|
1170
|
+
for (const path of todo) {
|
|
1171
|
+
if (Date.now() >= deadlineAt) break;
|
|
1172
|
+
const source = await readBoundedSource(repositoryRoot, path);
|
|
1173
|
+
if (source === null) continue;
|
|
1174
|
+
const vectors = [];
|
|
1175
|
+
const chunks = chunk(path, source);
|
|
1176
|
+
for (let start = 0; start < chunks.length; start += BATCH) {
|
|
1177
|
+
for (const vector of await embed(chunks.slice(start, start + BATCH))) {
|
|
1178
|
+
vectors.push(encode(vector));
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
files[path] = { hash: manifest.entries[path]?.hash ?? "", vectors };
|
|
1182
|
+
embedded += 1;
|
|
1183
|
+
if (Date.now() - lastSave > 3e4) {
|
|
1184
|
+
await saveStore(repositoryRoot, { version: 1, model: MODEL, files });
|
|
1185
|
+
lastSave = Date.now();
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
await saveStore(repositoryRoot, { version: 1, model: MODEL, files });
|
|
1189
|
+
return {
|
|
1190
|
+
embedded,
|
|
1191
|
+
files: Object.keys(files).length,
|
|
1192
|
+
pending: eligible.length - Object.keys(files).length
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
async function syncFileVectors(repositoryRoot, budgetMs) {
|
|
1196
|
+
const started = Date.now();
|
|
1197
|
+
try {
|
|
1198
|
+
const { manifest } = await loadFreshIndex(repositoryRoot);
|
|
1199
|
+
return await refreshFileVectors(repositoryRoot, manifest, started + budgetMs);
|
|
1200
|
+
} catch {
|
|
1201
|
+
return null;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
async function rankFilesByVector(repositoryRoot, manifest, task, embed = localEmbed) {
|
|
1205
|
+
if (process.env.CODEBUDDY_SEMANTIC === "0") return null;
|
|
1206
|
+
const store = await loadStore(repositoryRoot);
|
|
1207
|
+
const current = eligiblePaths(manifest).filter(
|
|
1208
|
+
(path) => store.files[path]?.hash === manifest.entries[path]?.hash
|
|
1209
|
+
);
|
|
1210
|
+
if (current.length === 0) return null;
|
|
1211
|
+
let query;
|
|
1212
|
+
try {
|
|
1213
|
+
const [vector] = await embed([task]);
|
|
1214
|
+
if (!vector) return null;
|
|
1215
|
+
query = vector;
|
|
1216
|
+
} catch {
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
return current.map((path) => ({
|
|
1220
|
+
path,
|
|
1221
|
+
score: Math.max(...(store.files[path]?.vectors ?? []).map((v) => dot(decode(v), query)))
|
|
1222
|
+
})).sort((left, right) => right.score - left.score || left.path.localeCompare(right.path));
|
|
1223
|
+
}
|
|
1224
|
+
function eligiblePaths(manifest) {
|
|
1225
|
+
return Object.keys(manifest.entries).filter(
|
|
1226
|
+
(path) => !NON_SOURCE_PATH.test(path) && !TEST_PATH.test(path)
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
function chunk(path, source) {
|
|
1230
|
+
const lines = source.split("\n");
|
|
1231
|
+
const chunks = [];
|
|
1232
|
+
for (let start = 0; start === 0 || start + 10 < lines.length; start += CHUNK_STEP) {
|
|
1233
|
+
chunks.push(
|
|
1234
|
+
`${path}
|
|
1235
|
+
${lines.slice(start, start + CHUNK_LINES).join("\n")}`.slice(0, CHUNK_CHARS)
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
return chunks;
|
|
1239
|
+
}
|
|
1240
|
+
function encode(vector) {
|
|
1241
|
+
return Buffer.from(Float32Array.from(vector).buffer).toString("base64");
|
|
1242
|
+
}
|
|
1243
|
+
function decode(text) {
|
|
1244
|
+
const bytes = Buffer.from(text, "base64");
|
|
1245
|
+
return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
|
|
1246
|
+
}
|
|
1247
|
+
function dot(left, right) {
|
|
1248
|
+
let sum = 0;
|
|
1249
|
+
for (let index = 0; index < right.length; index++)
|
|
1250
|
+
sum += (left[index] ?? 0) * (right[index] ?? 0);
|
|
1251
|
+
return sum;
|
|
1252
|
+
}
|
|
1253
|
+
function storePath(repositoryRoot) {
|
|
1254
|
+
return join4(repositoryRoot, ".codebuddy", "cache", "file-vectors.json");
|
|
1255
|
+
}
|
|
1256
|
+
async function loadStore(repositoryRoot) {
|
|
1257
|
+
try {
|
|
1258
|
+
const parsed = JSON.parse(await readFile6(storePath(repositoryRoot), "utf8"));
|
|
1259
|
+
if (parsed.version === 1 && parsed.model === MODEL && parsed.files) return parsed;
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
return { version: 1, model: MODEL, files: {} };
|
|
1263
|
+
}
|
|
1264
|
+
async function saveStore(repositoryRoot, store) {
|
|
1265
|
+
const path = storePath(repositoryRoot);
|
|
1266
|
+
await mkdir3(dirname3(path), { recursive: true });
|
|
1267
|
+
await writeAtomic(path, JSON.stringify(store));
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1557
1270
|
export {
|
|
1558
|
-
buildArchitectureMap,
|
|
1559
|
-
queryMap,
|
|
1560
|
-
neighbours,
|
|
1561
|
-
loadArchitectureMap,
|
|
1562
|
-
indexRepository,
|
|
1563
|
-
loadFreshIndex,
|
|
1564
1271
|
setupGraphify,
|
|
1565
1272
|
generateGraphifyGraph,
|
|
1566
1273
|
loadGraphifyMap,
|
|
1274
|
+
syncFileVectors,
|
|
1567
1275
|
TASK_PATH_LIMIT,
|
|
1568
1276
|
readBoundedSource,
|
|
1569
1277
|
discoverTaskPaths,
|
|
1570
1278
|
TEST_PATH,
|
|
1279
|
+
asksForChangelog,
|
|
1571
1280
|
NON_SOURCE_PATH,
|
|
1572
1281
|
taskTerms,
|
|
1573
1282
|
countTerms,
|
|
@@ -1575,4 +1284,4 @@ export {
|
|
|
1575
1284
|
namedSegments,
|
|
1576
1285
|
loadProjectArchitectureMap
|
|
1577
1286
|
};
|
|
1578
|
-
//# sourceMappingURL=chunk-
|
|
1287
|
+
//# sourceMappingURL=chunk-TLWGZVPC.js.map
|