@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.
Files changed (115) hide show
  1. package/README.md +10 -0
  2. package/dist/adapters-S4HHTWJW.js +24 -0
  3. package/dist/{chunk-IBLJOHJL.js → chunk-3QGW53GD.js} +2 -2
  4. package/dist/{chunk-IMFUYEZA.js → chunk-6DONJW3W.js} +232 -64
  5. package/dist/chunk-6DONJW3W.js.map +1 -0
  6. package/dist/{chunk-JFDRBPYF.js → chunk-6JCKMIQY.js} +2 -2
  7. package/dist/{chunk-S6VEG4S2.js → chunk-6KGYAFG7.js} +28 -5
  8. package/dist/chunk-6KGYAFG7.js.map +1 -0
  9. package/dist/{chunk-OWWCDRXW.js → chunk-6NZEZJYD.js} +9 -7
  10. package/dist/chunk-6NZEZJYD.js.map +1 -0
  11. package/dist/{chunk-GDJH7XJC.js → chunk-AL6VBTNA.js} +77 -3
  12. package/dist/chunk-AL6VBTNA.js.map +1 -0
  13. package/dist/{chunk-XBJQKCLU.js → chunk-CAEPHLHC.js} +13 -9
  14. package/dist/chunk-CAEPHLHC.js.map +1 -0
  15. package/dist/chunk-CG5B3LYG.js +66 -0
  16. package/dist/chunk-CG5B3LYG.js.map +1 -0
  17. package/dist/{chunk-IHQM2ZDV.js → chunk-DFV2SK6C.js} +1 -1
  18. package/dist/{chunk-IHQM2ZDV.js.map → chunk-DFV2SK6C.js.map} +1 -1
  19. package/dist/{chunk-QBDJXGG5.js → chunk-EJQVGFH2.js} +93 -22
  20. package/dist/chunk-EJQVGFH2.js.map +1 -0
  21. package/dist/{chunk-5MQOE4J6.js → chunk-ESZXCZIN.js} +3 -3
  22. package/dist/{chunk-J2EAZ4MA.js → chunk-GKNYMNFM.js} +22 -25
  23. package/dist/chunk-GKNYMNFM.js.map +1 -0
  24. package/dist/{chunk-5FW3PMNP.js → chunk-GXOML4UZ.js} +76 -11
  25. package/dist/chunk-GXOML4UZ.js.map +1 -0
  26. package/dist/{chunk-JPHJBCZB.js → chunk-HPOR6YXA.js} +3 -2
  27. package/dist/chunk-HPOR6YXA.js.map +1 -0
  28. package/dist/chunk-ITQO632W.js +673 -0
  29. package/dist/chunk-ITQO632W.js.map +1 -0
  30. package/dist/chunk-J4T6HHI4.js +1444 -0
  31. package/dist/chunk-J4T6HHI4.js.map +1 -0
  32. package/dist/{chunk-5KZY6OTG.js → chunk-KNR2P2UY.js} +33 -83
  33. package/dist/chunk-KNR2P2UY.js.map +1 -0
  34. package/dist/{chunk-2XDPKFUW.js → chunk-NY2YXP3R.js} +46 -20
  35. package/dist/chunk-NY2YXP3R.js.map +1 -0
  36. package/dist/{chunk-RAJJSCIP.js → chunk-SFMIMLXR.js} +2 -2
  37. package/dist/chunk-TGFTKWOR.js +16 -0
  38. package/dist/chunk-TGFTKWOR.js.map +1 -0
  39. package/dist/{chunk-NERKZSSE.js → chunk-TLWGZVPC.js} +395 -686
  40. package/dist/chunk-TLWGZVPC.js.map +1 -0
  41. package/dist/{chunk-B6CO63SQ.js → chunk-UK6XTS2Y.js} +24 -10
  42. package/dist/chunk-UK6XTS2Y.js.map +1 -0
  43. package/dist/chunk-W7GAXL6C.js +104 -0
  44. package/dist/chunk-W7GAXL6C.js.map +1 -0
  45. package/dist/chunk-XL2HLACE.js +55 -0
  46. package/dist/chunk-XL2HLACE.js.map +1 -0
  47. package/dist/chunk-Y7D7PUBE.js +321 -0
  48. package/dist/chunk-Y7D7PUBE.js.map +1 -0
  49. package/dist/{chunk-R7CD6EXS.js → chunk-ZNLSCV3J.js} +578 -381
  50. package/dist/chunk-ZNLSCV3J.js.map +1 -0
  51. package/dist/cli/index.js +5 -5
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/{cloud-7WM3YLI4.js → cloud-LXZUR3U2.js} +3 -3
  54. package/dist/{codebuddy-B_pCaQrV.d.ts → codebuddy-DO7ZhoWn.d.ts} +15 -1
  55. package/dist/config-GEZFLXK6.js +12 -0
  56. package/dist/index.d.ts +46 -3
  57. package/dist/index.js +23 -19
  58. package/dist/install-BXM56QCD.js +19 -0
  59. package/dist/langgraph/index.d.ts +1 -1
  60. package/dist/lean-command-7DFW32MA.js +19 -0
  61. package/dist/{local-5MZURLCZ.js → local-ST4WMRCG.js} +3 -3
  62. package/dist/{main-MTBQZGJV.js → main-QUASSFP7.js} +987 -1741
  63. package/dist/main-QUASSFP7.js.map +1 -0
  64. package/dist/{memory-KIC5FJVW.js → memory-TGHVTBME.js} +9 -8
  65. package/dist/memory-TGHVTBME.js.map +1 -0
  66. package/dist/operations-WJR7AZZF.js +26 -0
  67. package/dist/pretool-command-JTKX6G3M.js +13 -0
  68. package/dist/{rerank-5QZ3PNUW.js → rerank-IQAEWLQR.js} +6 -5
  69. package/dist/rerank-IQAEWLQR.js.map +1 -0
  70. package/dist/{runtime-CUBOOPZO.js → runtime-OQQ22VJF.js} +15 -11
  71. package/dist/summaries-ML53R7EL.js +21 -0
  72. package/dist/summaries-ML53R7EL.js.map +1 -0
  73. package/dist/{zvec-MSRKT7IV.js → zvec-XDZNZ2OL.js} +11 -3
  74. package/dist/zvec-XDZNZ2OL.js.map +1 -0
  75. package/package.json +1 -1
  76. package/src/db/migrations/0006_fact_idempotency_key.sql +2 -0
  77. package/src/db/migrations/meta/0006_snapshot.json +1258 -0
  78. package/src/db/migrations/meta/_journal.json +7 -0
  79. package/dist/chunk-2XDPKFUW.js.map +0 -1
  80. package/dist/chunk-5FW3PMNP.js.map +0 -1
  81. package/dist/chunk-5KZY6OTG.js.map +0 -1
  82. package/dist/chunk-B6CO63SQ.js.map +0 -1
  83. package/dist/chunk-GDJH7XJC.js.map +0 -1
  84. package/dist/chunk-IMFUYEZA.js.map +0 -1
  85. package/dist/chunk-J2EAZ4MA.js.map +0 -1
  86. package/dist/chunk-JPHJBCZB.js.map +0 -1
  87. package/dist/chunk-NERKZSSE.js.map +0 -1
  88. package/dist/chunk-OWWCDRXW.js.map +0 -1
  89. package/dist/chunk-PQK3GKZH.js +0 -98
  90. package/dist/chunk-PQK3GKZH.js.map +0 -1
  91. package/dist/chunk-QBDJXGG5.js.map +0 -1
  92. package/dist/chunk-R7CD6EXS.js.map +0 -1
  93. package/dist/chunk-S6VEG4S2.js.map +0 -1
  94. package/dist/chunk-XBJQKCLU.js.map +0 -1
  95. package/dist/config-DEVHUDIU.js +0 -11
  96. package/dist/lean-command-HTQCPH76.js +0 -15
  97. package/dist/main-MTBQZGJV.js.map +0 -1
  98. package/dist/memory-KIC5FJVW.js.map +0 -1
  99. package/dist/operations-POCHY3WR.js +0 -25
  100. package/dist/pretool-command-AX6JXP25.js +0 -10
  101. package/dist/rerank-5QZ3PNUW.js.map +0 -1
  102. package/dist/summaries-SB5L2TSW.js +0 -18
  103. /package/dist/{cloud-7WM3YLI4.js.map → adapters-S4HHTWJW.js.map} +0 -0
  104. /package/dist/{chunk-IBLJOHJL.js.map → chunk-3QGW53GD.js.map} +0 -0
  105. /package/dist/{chunk-JFDRBPYF.js.map → chunk-6JCKMIQY.js.map} +0 -0
  106. /package/dist/{chunk-5MQOE4J6.js.map → chunk-ESZXCZIN.js.map} +0 -0
  107. /package/dist/{chunk-RAJJSCIP.js.map → chunk-SFMIMLXR.js.map} +0 -0
  108. /package/dist/{config-DEVHUDIU.js.map → cloud-LXZUR3U2.js.map} +0 -0
  109. /package/dist/{lean-command-HTQCPH76.js.map → config-GEZFLXK6.js.map} +0 -0
  110. /package/dist/{local-5MZURLCZ.js.map → install-BXM56QCD.js.map} +0 -0
  111. /package/dist/{operations-POCHY3WR.js.map → lean-command-7DFW32MA.js.map} +0 -0
  112. /package/dist/{pretool-command-AX6JXP25.js.map → local-ST4WMRCG.js.map} +0 -0
  113. /package/dist/{runtime-CUBOOPZO.js.map → operations-WJR7AZZF.js.map} +0 -0
  114. /package/dist/{summaries-SB5L2TSW.js.map → pretool-command-JTKX6G3M.js.map} +0 -0
  115. /package/dist/{zvec-MSRKT7IV.js.map → runtime-OQQ22VJF.js.map} +0 -0
@@ -1,640 +1,37 @@
1
1
  import {
2
- extractSymbolTable,
3
- supportsSymbols
4
- } from "./chunk-PQK3GKZH.js";
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-5KZY6OTG.js";
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 as mkdir3, mkdtemp, readFile as readFile6, rm, stat as stat3, utimes } from "fs/promises";
20
+ import { mkdir, mkdtemp, readFile as readFile2, rm, stat, utimes } from "fs/promises";
624
21
  import { tmpdir } from "os";
625
- import { dirname as dirname4, join as join6, relative as relative3, resolve as resolve5, sep } from "path";
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 as createHash2 } from "crypto";
630
- import { readFile as readFile5 } from "fs/promises";
631
- var digest = (value) => createHash2("sha256").update(value).digest("hex");
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 readFile5(path, "utf8")),
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 readFile5(`${path}.sources.json`, "utf8");
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 readFile5(path, "utf8")) || !snapshot.sources || typeof snapshot.sources !== "object" || Array.isArray(snapshot.sources))
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 = resolve5(options.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 mkdir3(dirname4(destination), { recursive: true });
744
- await ignoreGeneratedGraphDirectory(dirname4(destination));
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 readFile6(destination, "utf8");
750
- const backupStat = await stat3(destination);
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(join6(tmpdir(), "codebuddy-graphify-"));
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 = join6(stagingRoot, DEFAULT_GRAPH_PATH);
168
+ const stagedPath = join(stagingRoot, DEFAULT_GRAPH_PATH);
772
169
  const staged = await readGraphFile(stagedPath);
773
- await writeAtomic(destination, await readFile6(stagedPath, "utf8"));
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 = join6(directory, ".gitignore");
178
+ const path = join(directory, ".gitignore");
782
179
  try {
783
- await stat3(path);
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 = resolve5(repositoryRoot, graphPath);
790
- const relativePath = relative3(repositoryRoot, absolute);
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 stat3(path);
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 readFile6(path, "utf8"));
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 readFile8, realpath, stat as stat4 } from "fs/promises";
819
- import { isAbsolute as isAbsolute3, join as join7, relative as relative6 } from "path";
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 readFile7 } from "fs/promises";
823
- import { isAbsolute, relative as relative4, resolve as resolve6 } from "path";
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 readFile7(graphPath, "utf8"));
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: languageFor2(path), imports: [] });
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) ? resolve6(path) : resolve6(repositoryRoot, path);
951
- const relativePath = relative4(resolve6(repositoryRoot), absolute).replace(/\\/g, "/");
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 languageFor2(path) {
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 relative5, resolve as resolve7, sep as sep2 } from "path";
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 = resolve7(repositoryRoot);
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 : resolve7(root, raw);
1000
- const rel = relative5(root, absolute);
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 = join7(repositoryRoot, path);
1034
- if ((await stat4(absolute)).size > MAX_EVIDENCE_BYTES) return null;
1035
- const source = await readFile8(absolute, "utf8");
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-DEVHUDIU.js");
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-SB5L2TSW.js");
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 entries = await withLlmSummaries(repositoryRoot, manifest);
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) => symbol.name.split(".").filter((segment) => identifiers.includes(segment))
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 keyword = scored.filter((entry) => entry.score >= EVIDENCE_FLOOR.keywordScore).map((entry) => entry.path);
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-MSRKT7IV.js").then(({ zvecCandidates }) => zvecCandidates(repositoryRoot, task)).catch(
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
- if (!ranked.some((path) => !NON_SOURCE_PATH.test(path))) {
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-DEVHUDIU.js")).resolveLlmConfig(input.repositoryRoot);
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-5QZ3PNUW.js");
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 readFile8(join7(repositoryRoot, "package.json"), "utf8"));
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 NON_SOURCE_PATH = /(^|\/)(docs?|\.github)\/|\.(md|mdx|txt|ya?ml|json|lock)$|(^|\/)CHANGELOG/i;
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 (NON_SOURCE_PATH.test(path)) {
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.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 2).map(
1471
- (word) => word.length > 3 && word.endsWith("s") && !word.endsWith("ss") ? word.slice(0, -1) : word
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 [...new Set(splitWords(task).filter((term) => !TASK_STOPWORDS.has(term)))];
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 = join7(
1086
+ const graphPath = join3(
1496
1087
  repositoryRoot,
1497
1088
  config.graphify.graphPath ?? "graphify-out/graph.json"
1498
1089
  );
1499
- const [graph, graphFile, sourceHashes2] = await Promise.all([
1090
+ const [graph, graphFile, sourceHashes] = await Promise.all([
1500
1091
  loadGraphifyMap(graphPath, repositoryRoot),
1501
- stat4(graphPath),
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(join7(repositoryRoot, module.path));
1509
- const inside = relative6(await realpath(repositoryRoot), absolute);
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 stat4(absolute);
1512
- const fresh = sourceHashes2 === null ? source.mtimeMs <= graphFile.mtimeMs : sourceHashes2[module.path] !== void 0 && sourceHashes2[module.path] === manifest.entries[module.path]?.hash;
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-NERKZSSE.js.map
1287
+ //# sourceMappingURL=chunk-TLWGZVPC.js.map