@orquentra/codebuddy 2.9.0 → 2.15.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 (117) hide show
  1. package/ARCHITECTURE.md +98 -0
  2. package/CLAUDE_CODEX.md +33 -0
  3. package/README.md +114 -9
  4. package/dist/{adapters-S4HHTWJW.js → adapters-OJRSVFV3.js} +2 -2
  5. package/dist/{chunk-GXOML4UZ.js → chunk-4BSO3HST.js} +126 -18
  6. package/dist/chunk-4BSO3HST.js.map +1 -0
  7. package/dist/{chunk-CAEPHLHC.js → chunk-57642NGU.js} +2 -44
  8. package/dist/chunk-57642NGU.js.map +1 -0
  9. package/dist/{chunk-ESZXCZIN.js → chunk-6YI2D5F5.js} +33 -5
  10. package/dist/chunk-6YI2D5F5.js.map +1 -0
  11. package/dist/{chunk-GKNYMNFM.js → chunk-7IPSDDO5.js} +10 -3
  12. package/dist/chunk-7IPSDDO5.js.map +1 -0
  13. package/dist/chunk-BTTQWSDS.js +372 -0
  14. package/dist/chunk-BTTQWSDS.js.map +1 -0
  15. package/dist/{chunk-2WH2Q4TA.js → chunk-D5E7SGI5.js} +304 -77
  16. package/dist/chunk-D5E7SGI5.js.map +1 -0
  17. package/dist/chunk-EMWGLKPR.js +45 -0
  18. package/dist/chunk-EMWGLKPR.js.map +1 -0
  19. package/dist/chunk-GQKWUHMA.js +20 -0
  20. package/dist/chunk-GQKWUHMA.js.map +1 -0
  21. package/dist/chunk-ITXE27SZ.js +358 -0
  22. package/dist/chunk-ITXE27SZ.js.map +1 -0
  23. package/dist/{chunk-UK6XTS2Y.js → chunk-JJQDVGF7.js} +23 -23
  24. package/dist/chunk-JJQDVGF7.js.map +1 -0
  25. package/dist/{chunk-F4BJ3DXW.js → chunk-KQCP3ZYH.js} +1273 -331
  26. package/dist/chunk-KQCP3ZYH.js.map +1 -0
  27. package/dist/{chunk-QN76UJP7.js → chunk-KYRM3HPP.js} +89 -21
  28. package/dist/chunk-KYRM3HPP.js.map +1 -0
  29. package/dist/{chunk-KNR2P2UY.js → chunk-M75SN4D4.js} +19 -4
  30. package/dist/chunk-M75SN4D4.js.map +1 -0
  31. package/dist/{chunk-J4T6HHI4.js → chunk-RZ266NHE.js} +538 -317
  32. package/dist/chunk-RZ266NHE.js.map +1 -0
  33. package/dist/chunk-U7KDM62P.js +43 -0
  34. package/dist/chunk-U7KDM62P.js.map +1 -0
  35. package/dist/chunk-UATJCD2T.js +368 -0
  36. package/dist/chunk-UATJCD2T.js.map +1 -0
  37. package/dist/{chunk-ENKDFLOO.js → chunk-UOTKHSQ5.js} +25 -8
  38. package/dist/chunk-UOTKHSQ5.js.map +1 -0
  39. package/dist/{chunk-TLWGZVPC.js → chunk-UR3DF74I.js} +1007 -132
  40. package/dist/chunk-UR3DF74I.js.map +1 -0
  41. package/dist/chunk-UTPC66MV.js +128 -0
  42. package/dist/chunk-UTPC66MV.js.map +1 -0
  43. package/dist/{chunk-W7GAXL6C.js → chunk-V7PT6ER5.js} +6 -5
  44. package/dist/chunk-V7PT6ER5.js.map +1 -0
  45. package/dist/chunk-WHKXUXHE.js +156 -0
  46. package/dist/chunk-WHKXUXHE.js.map +1 -0
  47. package/dist/{chunk-SRA4MDVG.js → chunk-YMXFSHXT.js} +978 -950
  48. package/dist/chunk-YMXFSHXT.js.map +1 -0
  49. package/dist/cli/index.js +23 -9
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
  52. package/dist/config-TXQJCCVI.js +15 -0
  53. package/dist/expand-KWYGNCKZ.js +98 -0
  54. package/dist/expand-KWYGNCKZ.js.map +1 -0
  55. package/dist/file-vectors-MKXTSGGH.js +19 -0
  56. package/dist/index-facts-4MTEHKMN.js +38 -0
  57. package/dist/index-pass-TBDUEZCB.js +14 -0
  58. package/dist/index.d.ts +6 -4
  59. package/dist/index.js +25 -23
  60. package/dist/install-E3JWINTN.js +31 -0
  61. package/dist/langgraph/index.d.ts +1 -1
  62. package/dist/lean-MLTAFS5L.js +29 -0
  63. package/dist/local-75DJECVR.js +48 -0
  64. package/dist/local-llm-MECVJTVU.js +19 -0
  65. package/dist/{main-YJKSYFPT.js → main-RLMKQZW4.js} +999 -328
  66. package/dist/main-RLMKQZW4.js.map +1 -0
  67. package/dist/{memory-TGHVTBME.js → memory-PFFVMTCH.js} +9 -6
  68. package/dist/{memory-TGHVTBME.js.map → memory-PFFVMTCH.js.map} +1 -1
  69. package/dist/{operations-OG4ISWIM.js → operations-STR7S5UO.js} +8 -7
  70. package/dist/operations-STR7S5UO.js.map +1 -0
  71. package/dist/{pretool-command-PRFOIHMW.js → pretool-command-B5GY5T6E.js} +2 -2
  72. package/dist/pretool-command-B5GY5T6E.js.map +1 -0
  73. package/dist/prompt-gate-TDTFFQW2.js +8 -0
  74. package/dist/prompt-gate-TDTFFQW2.js.map +1 -0
  75. package/dist/{rerank-IQAEWLQR.js → rerank-XXKWDI5Q.js} +7 -4
  76. package/dist/{rerank-IQAEWLQR.js.map → rerank-XXKWDI5Q.js.map} +1 -1
  77. package/dist/{runtime-GCSEIAVD.js → runtime-SWFWGTQ4.js} +18 -9
  78. package/dist/runtime-SWFWGTQ4.js.map +1 -0
  79. package/dist/summaries-QXWFFDUZ.js +31 -0
  80. package/dist/summaries-QXWFFDUZ.js.map +1 -0
  81. package/dist/{zvec-XDZNZ2OL.js → zvec-F73IBWO5.js} +13 -6
  82. package/dist/zvec-F73IBWO5.js.map +1 -0
  83. package/package.json +13 -3
  84. package/dist/chunk-2WH2Q4TA.js.map +0 -1
  85. package/dist/chunk-6NZEZJYD.js +0 -206
  86. package/dist/chunk-6NZEZJYD.js.map +0 -1
  87. package/dist/chunk-CAEPHLHC.js.map +0 -1
  88. package/dist/chunk-ENKDFLOO.js.map +0 -1
  89. package/dist/chunk-ESZXCZIN.js.map +0 -1
  90. package/dist/chunk-F4BJ3DXW.js.map +0 -1
  91. package/dist/chunk-GKNYMNFM.js.map +0 -1
  92. package/dist/chunk-GXOML4UZ.js.map +0 -1
  93. package/dist/chunk-ITQO632W.js +0 -673
  94. package/dist/chunk-ITQO632W.js.map +0 -1
  95. package/dist/chunk-J4T6HHI4.js.map +0 -1
  96. package/dist/chunk-KNR2P2UY.js.map +0 -1
  97. package/dist/chunk-QN76UJP7.js.map +0 -1
  98. package/dist/chunk-SFMIMLXR.js +0 -63
  99. package/dist/chunk-SFMIMLXR.js.map +0 -1
  100. package/dist/chunk-SRA4MDVG.js.map +0 -1
  101. package/dist/chunk-TLWGZVPC.js.map +0 -1
  102. package/dist/chunk-UK6XTS2Y.js.map +0 -1
  103. package/dist/chunk-W7GAXL6C.js.map +0 -1
  104. package/dist/config-GEZFLXK6.js +0 -12
  105. package/dist/install-BXM56QCD.js +0 -19
  106. package/dist/lean-command-7DFW32MA.js +0 -19
  107. package/dist/main-YJKSYFPT.js.map +0 -1
  108. package/dist/summaries-ML53R7EL.js +0 -21
  109. /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
  110. /package/dist/{config-GEZFLXK6.js.map → config-TXQJCCVI.js.map} +0 -0
  111. /package/dist/{install-BXM56QCD.js.map → file-vectors-MKXTSGGH.js.map} +0 -0
  112. /package/dist/{lean-command-7DFW32MA.js.map → index-facts-4MTEHKMN.js.map} +0 -0
  113. /package/dist/{operations-OG4ISWIM.js.map → index-pass-TBDUEZCB.js.map} +0 -0
  114. /package/dist/{pretool-command-PRFOIHMW.js.map → install-E3JWINTN.js.map} +0 -0
  115. /package/dist/{runtime-GCSEIAVD.js.map → lean-MLTAFS5L.js.map} +0 -0
  116. /package/dist/{summaries-ML53R7EL.js.map → local-75DJECVR.js.map} +0 -0
  117. /package/dist/{zvec-XDZNZ2OL.js.map → local-llm-MECVJTVU.js.map} +0 -0
@@ -1,37 +1,702 @@
1
1
  import {
2
- loadArchitectureMap,
3
- loadFreshIndex
4
- } from "./chunk-ITQO632W.js";
2
+ extractPythonImports,
3
+ extractSymbolTable,
4
+ supportsSymbols
5
+ } from "./chunk-Y7D7PUBE.js";
5
6
  import {
6
7
  isCredentialPath
7
8
  } from "./chunk-AL6VBTNA.js";
8
- import {
9
- LOCAL_EMBEDDING_MODEL
10
- } from "./chunk-HPOR6YXA.js";
11
9
  import {
12
10
  readConfigFile
13
- } from "./chunk-KNR2P2UY.js";
11
+ } from "./chunk-M75SN4D4.js";
14
12
  import {
13
+ assertNoSymlinkBetween,
15
14
  writeAtomic
16
15
  } from "./chunk-CG5B3LYG.js";
16
+ import {
17
+ LOCAL_EMBEDDING_MODEL
18
+ } from "./chunk-HPOR6YXA.js";
19
+
20
+ // src/context/file-vectors.ts
21
+ import { createHash as createHash3 } from "crypto";
22
+ import { mkdir as mkdir5, readFile as readFile10 } from "fs/promises";
23
+ import { dirname as dirname6, join as join9 } from "path";
24
+
25
+ // src/indexer/engine.ts
26
+ import { createHash } from "crypto";
27
+ import { readFile as readFile4, stat as stat2 } from "fs/promises";
28
+ import { join as join5, resolve as resolve4 } from "path";
29
+
30
+ // src/map/cache.ts
31
+ import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
32
+ import { dirname as dirname3, extname as extname2, join as join3, resolve as resolve3 } from "path";
33
+
34
+ // src/indexer/store.ts
35
+ import { mkdir, readFile } from "fs/promises";
36
+ import { dirname, join, resolve } from "path";
37
+ import { z } from "zod";
38
+
39
+ // src/indexer/types.ts
40
+ var INDEX_MANIFEST_VERSION = 3;
41
+
42
+ // src/indexer/store.ts
43
+ var symbolSpanSchema = z.object({
44
+ name: z.string(),
45
+ kind: z.enum(["function", "method", "class", "interface", "type", "enum", "const", "heading"]),
46
+ startLine: z.number().int().nonnegative(),
47
+ endLine: z.number().int().nonnegative(),
48
+ signature: z.string(),
49
+ exported: z.boolean()
50
+ });
51
+ var entrySchema = z.object({
52
+ path: z.string(),
53
+ hash: z.string(),
54
+ size: z.number().int().nonnegative(),
55
+ mtimeMs: z.number().optional(),
56
+ ctimeMs: z.number().optional(),
57
+ language: z.string(),
58
+ lines: z.number().int().nonnegative(),
59
+ symbols: z.array(z.string()),
60
+ symbolTable: z.array(symbolSpanSchema).optional(),
61
+ summary: z.string(),
62
+ indexedAt: z.string()
63
+ });
64
+ var manifestSchema = z.object({
65
+ version: z.literal(INDEX_MANIFEST_VERSION),
66
+ generatedAt: z.string(),
67
+ entries: z.record(z.string(), entrySchema)
68
+ });
69
+ var IndexStore = class {
70
+ repositoryRoot;
71
+ cacheDirectory;
72
+ manifestPath;
73
+ constructor(repositoryRoot = process.cwd()) {
74
+ this.repositoryRoot = resolve(repositoryRoot);
75
+ this.cacheDirectory = join(this.repositoryRoot, ".codebuddy", "cache");
76
+ this.manifestPath = join(this.cacheDirectory, "index.json");
77
+ }
78
+ /** Read the manifest, or an empty one if absent/corrupt/version-mismatched. */
79
+ async read() {
80
+ try {
81
+ const raw = await readFile(this.manifestPath, "utf8");
82
+ const parsed = manifestSchema.safeParse(JSON.parse(raw));
83
+ if (!parsed.success) return emptyManifest();
84
+ return parsed.data;
85
+ } catch (error) {
86
+ if (error.code === "ENOENT") return emptyManifest();
87
+ return emptyManifest();
88
+ }
89
+ }
90
+ async write(manifest) {
91
+ await assertNoSymlinkBetween(this.repositoryRoot, dirname(this.manifestPath));
92
+ await mkdir(this.cacheDirectory, { recursive: true, mode: 448 });
93
+ await writeAtomic(this.manifestPath, `${JSON.stringify(manifest, null, 2)}
94
+ `);
95
+ }
96
+ };
97
+ function emptyManifest() {
98
+ return { version: INDEX_MANIFEST_VERSION, generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(), entries: {} };
99
+ }
100
+
101
+ // src/map/indexer.ts
102
+ import { readdir, readFile as readFile2, stat } from "fs/promises";
103
+ import { dirname as dirname2, extname, join as join2, relative, resolve as resolve2 } from "path";
104
+ var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"]);
105
+ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
106
+ ".git",
107
+ "node_modules",
108
+ "dist",
109
+ "build",
110
+ "coverage",
111
+ ".codebuddy",
112
+ // Python virtualenvs and caches would otherwise map site-packages.
113
+ ".venv",
114
+ "venv",
115
+ ".tox",
116
+ "__pycache__"
117
+ ]);
118
+ async function buildArchitectureMap(repositoryRoot = process.cwd()) {
119
+ const root = resolve2(repositoryRoot);
120
+ const files = await listSourceFiles(root);
121
+ const fileSet = new Set(files.map((file) => normalize(relative(root, file))));
122
+ const modules = [];
123
+ const edges = [];
124
+ for (const absolutePath of files) {
125
+ const source = await readFile2(absolutePath, "utf8");
126
+ const from = normalize(relative(root, absolutePath));
127
+ const resolvedImports = moduleImports(root, from, source, fileSet);
128
+ edges.push(...resolvedImports.map(({ to, kind }) => ({ from, to, kind })));
129
+ modules.push({
130
+ path: from,
131
+ language: moduleLanguage(from),
132
+ imports: [...new Set(resolvedImports.map(({ to }) => to))].sort()
133
+ });
134
+ }
135
+ return {
136
+ modules: modules.sort((left, right) => left.path.localeCompare(right.path)),
137
+ edges: edges.sort(
138
+ (left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
139
+ )
140
+ };
141
+ }
142
+ async function updateArchitectureMap(repositoryRoot, sourcePaths, previous, changedPaths) {
143
+ const root = resolve2(repositoryRoot);
144
+ const normalizedPaths = sourcePaths.map(normalize).sort();
145
+ const fileSet = new Set(normalizedPaths);
146
+ const changed = new Set(changedPaths.map(normalize));
147
+ const retainedModules = (previous?.modules ?? []).filter(
148
+ (module) => fileSet.has(module.path) && !changed.has(module.path)
149
+ );
150
+ const retainedEdges = (previous?.edges ?? []).filter(
151
+ (edge) => fileSet.has(edge.from) && fileSet.has(edge.to) && !changed.has(edge.from)
152
+ );
153
+ const modules = [...retainedModules];
154
+ const edges = [...retainedEdges];
155
+ for (const path of normalizedPaths) {
156
+ if (!changed.has(path)) continue;
157
+ const source = await readFile2(join2(root, path), "utf8");
158
+ const resolvedImports = moduleImports(root, path, source, fileSet);
159
+ edges.push(...resolvedImports.map(({ to, kind }) => ({ from: path, to, kind })));
160
+ modules.push({
161
+ path,
162
+ language: moduleLanguage(path),
163
+ imports: [...new Set(resolvedImports.map(({ to }) => to))].sort()
164
+ });
165
+ }
166
+ return {
167
+ map: {
168
+ modules: modules.sort((left, right) => left.path.localeCompare(right.path)),
169
+ edges: edges.sort(
170
+ (left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
171
+ )
172
+ },
173
+ stats: {
174
+ readFiles: changed.size,
175
+ reusedFiles: retainedModules.length,
176
+ rebuilt: previous === null || changed.size === normalizedPaths.length
177
+ }
178
+ };
179
+ }
180
+ function queryMap(map, query) {
181
+ const limit = query.limit ?? 50;
182
+ 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);
183
+ }
184
+ function neighbours(map, modulePath, direction = "both") {
185
+ const target = normalize(modulePath);
186
+ const edges = map.edges.filter((edge) => {
187
+ if (direction === "in") return edge.to === target;
188
+ if (direction === "out") return edge.from === target;
189
+ return edge.from === target || edge.to === target;
190
+ });
191
+ const paths = /* @__PURE__ */ new Set([target]);
192
+ for (const edge of edges) {
193
+ paths.add(edge.from);
194
+ paths.add(edge.to);
195
+ }
196
+ return {
197
+ modules: map.modules.filter((module) => paths.has(module.path)),
198
+ edges
199
+ };
200
+ }
201
+ async function listSourceFiles(root) {
202
+ const files = [];
203
+ await walk(root, files);
204
+ return files.sort();
205
+ }
206
+ async function walk(directory, files) {
207
+ const entries = await readdir(directory, { withFileTypes: true });
208
+ for (const entry of entries) {
209
+ const absolute = join2(directory, entry.name);
210
+ if (entry.isDirectory()) {
211
+ if (IGNORED_DIRECTORIES.has(entry.name)) continue;
212
+ await walk(absolute, files);
213
+ continue;
214
+ }
215
+ if (!entry.isFile()) continue;
216
+ if (!SUPPORTED_EXTENSIONS.has(extname(entry.name))) continue;
217
+ const info = await stat(absolute);
218
+ if (info.size > 5e5) continue;
219
+ files.push(absolute);
220
+ }
221
+ }
222
+ function moduleLanguage(path) {
223
+ const extension = extname(path);
224
+ if (extension === ".py") return "py";
225
+ return extension.includes("ts") ? "ts" : "js";
226
+ }
227
+ function moduleImports(root, from, source, fileSet) {
228
+ if (extname(from) === ".py") {
229
+ return pythonImports(from, source, fileSet).map((to) => ({ to, kind: "import" }));
230
+ }
231
+ return extractImports(source).flatMap((specifier) => {
232
+ const to = resolveImport(root, from, specifier.value, fileSet);
233
+ return to ? [{ to, kind: specifier.kind }] : [];
234
+ });
235
+ }
236
+ var PYTHON_ROOTS = ["", "src/"];
237
+ function pythonImports(from, source, fileSet) {
238
+ const pick = (...candidates) => candidates.find((candidate) => fileSet.has(candidate)) ?? null;
239
+ const moduleFile = (base) => pick(`${base}.py`, `${base}/__init__.py`);
240
+ const packageDir = from.split("/").slice(0, -1);
241
+ const targets = [];
242
+ for (const statement of extractPythonImports(source)) {
243
+ const parts = statement.module ? statement.module.split(".") : [];
244
+ let bases;
245
+ if (statement.level > 0) {
246
+ const up = statement.level - 1;
247
+ if (up > packageDir.length) continue;
248
+ bases = [[...packageDir.slice(0, packageDir.length - up), ...parts].join("/")];
249
+ } else {
250
+ bases = PYTHON_ROOTS.map((prefix) => `${prefix}${parts.join("/")}`);
251
+ }
252
+ for (const base of bases) {
253
+ const prefix = base ? `${base}/` : "";
254
+ const module = parts.length > 0 ? moduleFile(base) : pick(`${prefix}__init__.py`);
255
+ const submodules = statement.names.flatMap((name) => moduleFile(`${prefix}${name}`) ?? []);
256
+ if (!module && submodules.length === 0) continue;
257
+ targets.push(...submodules);
258
+ if (module && submodules.length < Math.max(1, statement.names.length)) targets.push(module);
259
+ break;
260
+ }
261
+ }
262
+ return [...new Set(targets)].filter((target) => target !== from);
263
+ }
264
+ function extractImports(source) {
265
+ const imports = [];
266
+ const stripped = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1 ");
267
+ const staticImport = /\bimport\s+(?:type\s+)?(?:[^'"()+]+?\s+from\s+)?["']([^"']+)["']/g;
268
+ const exportFrom = /\bexport\s+[^'"()+]+?\s+from\s+["']([^"']+)["']/g;
269
+ const dynamicImport = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
270
+ for (const match of stripped.matchAll(staticImport)) {
271
+ if (match[1]) imports.push({ value: match[1], kind: "import" });
272
+ }
273
+ for (const match of stripped.matchAll(exportFrom)) {
274
+ if (match[1]) imports.push({ value: match[1], kind: "import" });
275
+ }
276
+ for (const match of stripped.matchAll(dynamicImport)) {
277
+ if (match[1]) imports.push({ value: match[1], kind: "dynamic_import" });
278
+ }
279
+ return imports;
280
+ }
281
+ function resolveImport(root, from, specifier, fileSet) {
282
+ if (!specifier.startsWith(".")) return null;
283
+ const base = normalize(relative(root, resolve2(root, dirname2(from), specifier)));
284
+ const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
285
+ const bases = stem === base ? [base] : [base, stem];
286
+ const candidates = bases.flatMap((b) => [
287
+ b,
288
+ `${b}.ts`,
289
+ `${b}.tsx`,
290
+ `${b}.js`,
291
+ `${b}.jsx`,
292
+ `${b}.mjs`,
293
+ `${b}.cjs`,
294
+ `${b}/index.ts`,
295
+ `${b}/index.tsx`,
296
+ `${b}/index.js`,
297
+ `${b}/index.jsx`
298
+ ]);
299
+ return candidates.find((candidate) => fileSet.has(candidate)) ?? null;
300
+ }
301
+ function normalize(path) {
302
+ return path.replace(/\\/g, "/").replace(/^\.\//, "");
303
+ }
304
+
305
+ // src/map/cache.ts
306
+ var CACHE_VERSION = 1;
307
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"]);
308
+ async function loadArchitectureMap(repositoryRoot = process.cwd(), suppliedManifest) {
309
+ const root = resolve3(repositoryRoot);
310
+ const manifest = suppliedManifest ?? await new IndexStore(root).read();
311
+ if (Object.keys(manifest.entries).length === 0) {
312
+ const map = await buildArchitectureMap(root);
313
+ return {
314
+ map,
315
+ stats: { readFiles: map.modules.length, reusedFiles: 0, rebuilt: true },
316
+ source: "fallback"
317
+ };
318
+ }
319
+ return refreshArchitectureCache(root, manifest);
320
+ }
321
+ async function refreshArchitectureCache(repositoryRoot, manifest) {
322
+ const root = resolve3(repositoryRoot);
323
+ const path = join3(root, ".codebuddy", "cache", "architecture.json");
324
+ const currentHashes = sourceHashes(manifest);
325
+ const currentPaths = Object.keys(currentHashes).sort();
326
+ const cached = await readCache(path);
327
+ const cachedPaths = cached ? Object.keys(cached.sourceHashes).sort() : [];
328
+ const fileSetChanged = cachedPaths.join("\n") !== currentPaths.join("\n");
329
+ const changedPaths = !cached || fileSetChanged ? currentPaths : currentPaths.filter(
330
+ (sourcePath) => cached.sourceHashes[sourcePath] !== currentHashes[sourcePath]
331
+ );
332
+ if (cached && changedPaths.length === 0) {
333
+ return {
334
+ map: cached.map,
335
+ stats: { readFiles: 0, reusedFiles: cached.map.modules.length, rebuilt: false },
336
+ source: "cache"
337
+ };
338
+ }
339
+ const updated = await updateArchitectureMap(
340
+ root,
341
+ currentPaths,
342
+ fileSetChanged ? null : cached?.map ?? null,
343
+ changedPaths
344
+ );
345
+ const next = {
346
+ version: CACHE_VERSION,
347
+ generatedAt: manifest.generatedAt,
348
+ sourceHashes: currentHashes,
349
+ map: updated.map
350
+ };
351
+ await assertNoSymlinkBetween(root, dirname3(path));
352
+ await mkdir2(dirname3(path), { recursive: true, mode: 448 });
353
+ await writeAtomic(path, `${JSON.stringify(next, null, 2)}
354
+ `);
355
+ return { ...updated, source: fileSetChanged || !cached ? "rebuild" : "incremental" };
356
+ }
357
+ async function readCache(path) {
358
+ try {
359
+ const parsed = JSON.parse(await readFile3(path, "utf8"));
360
+ if (parsed.version !== CACHE_VERSION || !parsed.map || !parsed.sourceHashes) return null;
361
+ if (!Array.isArray(parsed.map.modules) || !Array.isArray(parsed.map.edges)) return null;
362
+ if (!parsed.map.modules.every(
363
+ (module) => module && typeof module.path === "string" && typeof module.language === "string" && Array.isArray(module.imports)
364
+ ) || !parsed.map.edges.every(
365
+ (edge) => edge && typeof edge.from === "string" && typeof edge.to === "string"
366
+ )) {
367
+ return null;
368
+ }
369
+ return parsed;
370
+ } catch {
371
+ return null;
372
+ }
373
+ }
374
+ function sourceHashes(manifest) {
375
+ return Object.fromEntries(
376
+ Object.values(manifest.entries).filter((entry) => SOURCE_EXTENSIONS.has(extname2(entry.path).toLowerCase())).map((entry) => [entry.path, entry.hash])
377
+ );
378
+ }
379
+
380
+ // src/indexer/scan.ts
381
+ import { spawn } from "child_process";
382
+ import { readdir as readdir2 } from "fs/promises";
383
+ import { extname as extname4, join as join4, relative as relative2, sep } from "path";
384
+
385
+ // src/indexer/summary.ts
386
+ import { extname as extname3 } from "path";
387
+ var LANGUAGE_BY_EXTENSION = {
388
+ ".ts": "ts",
389
+ ".tsx": "tsx",
390
+ ".js": "js",
391
+ ".jsx": "jsx",
392
+ ".mjs": "js",
393
+ ".cjs": "js",
394
+ ".json": "json",
395
+ ".md": "md",
396
+ ".mdx": "md",
397
+ ".rst": "rst",
398
+ ".yaml": "yaml",
399
+ ".yml": "yaml",
400
+ ".toml": "toml",
401
+ ".css": "css",
402
+ ".scss": "scss",
403
+ ".html": "html",
404
+ ".py": "py",
405
+ ".go": "go",
406
+ ".rs": "rust",
407
+ ".java": "java",
408
+ ".rb": "ruby",
409
+ ".php": "php",
410
+ ".sh": "shell",
411
+ ".sql": "sql",
412
+ ".txt": "text"
413
+ };
414
+ var SUPPORTED_EXTENSIONS2 = new Set(Object.keys(LANGUAGE_BY_EXTENSION));
415
+ var CODE_LANGUAGES = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx"]);
416
+ var SYMBOL_LIMIT = 8;
417
+ var SUMMARY_COMMENT_MAX = 80;
418
+ function languageFor(path) {
419
+ return LANGUAGE_BY_EXTENSION[extname3(path).toLowerCase()] ?? "text";
420
+ }
421
+ function summarizeSource(path, content) {
422
+ const language = languageFor(path);
423
+ const lines = countLines(content);
424
+ const symbols = CODE_LANGUAGES.has(language) ? extractSymbols(content) : language === "py" ? pythonPublicNames(content) : [];
425
+ const summary = composeSummary(language, lines, symbols, content);
426
+ return { language, lines, symbols, summary };
427
+ }
428
+ function countLines(content) {
429
+ if (content.length === 0) return 0;
430
+ return content.replace(/\r/g, "").replace(/\n$/, "").split("\n").length;
431
+ }
432
+ function extractSymbols(content) {
433
+ const names = [];
434
+ const push = (name) => {
435
+ const trimmed = name?.trim();
436
+ if (trimmed && /^[A-Za-z_$][\w$]*$/.test(trimmed) && !names.includes(trimmed)) {
437
+ names.push(trimmed);
438
+ }
439
+ };
440
+ const declaration = /^export\s+(?:default\s+)?(?:async\s+)?(?:abstract\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/gm;
441
+ for (const match of content.matchAll(declaration)) push(match[1]);
442
+ const namedExports = /^export\s*\{([^}]*)\}/gm;
443
+ for (const match of content.matchAll(namedExports)) {
444
+ for (const part of (match[1] ?? "").split(",")) {
445
+ const alias = part.includes(" as ") ? part.split(" as ").pop() : part;
446
+ push(alias);
447
+ }
448
+ }
449
+ return names.slice(0, SYMBOL_LIMIT);
450
+ }
451
+ function pythonPublicNames(content) {
452
+ return extractSymbolTable(content, "x.py").filter((symbol) => symbol.exported && symbol.kind !== "method").map((symbol) => symbol.name).slice(0, SYMBOL_LIMIT);
453
+ }
454
+ function composeSummary(language, lines, symbols, content) {
455
+ const head = `${language} \u2022 ${lines} line${lines === 1 ? "" : "s"}`;
456
+ if (symbols.length > 0) return `${head} \u2022 exports ${symbols.join(", ")}`;
457
+ const comment = leadingComment(content);
458
+ if (comment) return `${head} \u2022 ${comment}`;
459
+ return head;
460
+ }
461
+ function leadingComment(content) {
462
+ for (const raw of content.split(/\r?\n/)) {
463
+ const line = raw.trim();
464
+ if (line === "") continue;
465
+ const stripped = line.replace(/^\/\/+/, "").replace(/^\/\*+/, "").replace(/\*+\/$/, "").replace(/^[#*]+/, "").trim();
466
+ if (line.startsWith("//") || line.startsWith("/*") || line.startsWith("#") || line.startsWith("*")) {
467
+ if (stripped) return truncate(stripped, SUMMARY_COMMENT_MAX);
468
+ continue;
469
+ }
470
+ return null;
471
+ }
472
+ return null;
473
+ }
474
+ function truncate(value, max) {
475
+ return value.length <= max ? value : `${value.slice(0, max - 1)}\u2026`;
476
+ }
477
+
478
+ // src/indexer/scan.ts
479
+ var FALLBACK_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
480
+ ".git",
481
+ "node_modules",
482
+ "dist",
483
+ "build",
484
+ "coverage",
485
+ ".codebuddy",
486
+ "graphify-out",
487
+ ".next",
488
+ ".turbo",
489
+ "out",
490
+ ".venv",
491
+ "venv",
492
+ "__pycache__"
493
+ ]);
494
+ async function listIndexableFiles(repositoryRoot) {
495
+ const tracked = await gitListFiles(repositoryRoot);
496
+ const paths = tracked ?? await walkFiles(repositoryRoot);
497
+ return paths.map(normalize2).filter((path) => !path.startsWith(".codebuddy/") && !path.startsWith("graphify-out/")).filter((path) => SUPPORTED_EXTENSIONS2.has(extname4(path).toLowerCase())).sort();
498
+ }
499
+ async function gitListFiles(repositoryRoot) {
500
+ const result = await runGit(repositoryRoot, [
501
+ "ls-files",
502
+ "-z",
503
+ "--cached",
504
+ "--others",
505
+ "--exclude-standard"
506
+ ]);
507
+ if (result.code !== 0) return null;
508
+ return result.stdout.split("\0").filter(Boolean);
509
+ }
510
+ async function walkFiles(root) {
511
+ const files = [];
512
+ const walk2 = async (directory) => {
513
+ const entries = await readdir2(directory, { withFileTypes: true });
514
+ for (const entry of entries) {
515
+ const absolute = join4(directory, entry.name);
516
+ if (entry.isDirectory()) {
517
+ if (FALLBACK_IGNORED_DIRECTORIES.has(entry.name)) continue;
518
+ await walk2(absolute);
519
+ continue;
520
+ }
521
+ if (entry.isFile()) files.push(relative2(root, absolute));
522
+ }
523
+ };
524
+ await walk2(root);
525
+ return files;
526
+ }
527
+ function runGit(cwd, args) {
528
+ return new Promise((resolveResult) => {
529
+ const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
530
+ let stdout = "";
531
+ let stderr = "";
532
+ child.stdout.setEncoding("utf8");
533
+ child.stdout.on("data", (chunk2) => {
534
+ stdout += String(chunk2);
535
+ });
536
+ child.stderr.on("data", (chunk2) => {
537
+ stderr += String(chunk2);
538
+ });
539
+ child.on("error", () => resolveResult({ code: -1, stdout: "", stderr: "git unavailable" }));
540
+ child.on("close", (code) => resolveResult({ code: code ?? 0, stdout, stderr }));
541
+ });
542
+ }
543
+ function normalize2(path) {
544
+ return (sep === "\\" ? path.replace(/\\/g, "/") : path).replace(/^\.\//, "");
545
+ }
546
+
547
+ // src/indexer/engine.ts
548
+ var MAX_FILE_BYTES = 512e3;
549
+ var RACY_WINDOW_MS = 3e3;
550
+ async function indexRepository(options = {}) {
551
+ return (await refreshIndex(options)).result;
552
+ }
553
+ async function loadFreshIndex(repositoryRoot) {
554
+ try {
555
+ const { result, manifest } = await refreshIndex({ repositoryRoot });
556
+ const delta = result.added + result.changed + result.removed;
557
+ const failures = result.errors.length > 0 ? `; ${result.errors.length} file(s) could not be validated and were omitted` : "";
558
+ return {
559
+ manifest,
560
+ 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}`
561
+ };
562
+ } catch (error) {
563
+ return {
564
+ manifest: emptyManifest(),
565
+ explain: `index: freshness check failed (${error.message}); indexed evidence omitted`
566
+ };
567
+ }
568
+ }
569
+ var passes = /* @__PURE__ */ new Map();
570
+ function refreshIndex(options) {
571
+ const key = resolve4(options.repositoryRoot ?? process.cwd());
572
+ const next = (passes.get(key) ?? Promise.resolve()).catch(() => void 0).then(() => runIndexPass(options));
573
+ passes.set(key, next);
574
+ void next.finally(() => {
575
+ if (passes.get(key) === next) passes.delete(key);
576
+ }).catch(() => void 0);
577
+ return next;
578
+ }
579
+ async function runIndexPass(options) {
580
+ const start = Date.now();
581
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
582
+ const repositoryRoot = resolve4(options.repositoryRoot ?? process.cwd());
583
+ const store = new IndexStore(repositoryRoot);
584
+ const [files, previous] = await Promise.all([listIndexableFiles(repositoryRoot), store.read()]);
585
+ const entries = {};
586
+ const errors = [];
587
+ let added = 0;
588
+ let changed = 0;
589
+ let unchanged = 0;
590
+ let skipped = 0;
591
+ let dirty = previous.generatedAt === emptyManifest().generatedAt;
592
+ for (const path of files) {
593
+ try {
594
+ const info = await stat2(join5(repositoryRoot, path));
595
+ if (!info.isFile()) continue;
596
+ if (info.size > MAX_FILE_BYTES) {
597
+ skipped++;
598
+ continue;
599
+ }
600
+ const existing = previous.entries[path];
601
+ if (!options.full && existing && statUnchanged(existing, info)) {
602
+ entries[path] = existing;
603
+ unchanged++;
604
+ continue;
605
+ }
606
+ const content = await readFile4(join5(repositoryRoot, path), "utf8");
607
+ const hash = hashContent(content);
608
+ const metadata = trustedMetadata(info);
609
+ dirty = true;
610
+ if (!options.full && existing && existing.hash === hash) {
611
+ const { mtimeMs: _mtime, ctimeMs: _ctime, ...rest } = existing;
612
+ entries[path] = { ...rest, size: info.size, ...metadata };
613
+ unchanged++;
614
+ continue;
615
+ }
616
+ const { language, lines, symbols, summary } = summarizeSource(path, content);
617
+ const symbolTable = supportsSymbols(path) ? extractSymbolTable(content, path) : [];
618
+ entries[path] = {
619
+ path,
620
+ hash,
621
+ size: info.size,
622
+ ...metadata,
623
+ language,
624
+ lines,
625
+ symbols,
626
+ ...symbolTable.length > 0 ? { symbolTable } : {},
627
+ summary,
628
+ indexedAt: now().toISOString()
629
+ };
630
+ if (existing) changed++;
631
+ else added++;
632
+ } catch (error) {
633
+ errors.push({ path, error: error.message });
634
+ skipped++;
635
+ }
636
+ }
637
+ const removed = Object.keys(previous.entries).filter((path) => !(path in entries)).length;
638
+ const manifest = {
639
+ version: INDEX_MANIFEST_VERSION,
640
+ generatedAt: now().toISOString(),
641
+ entries: sortEntries(entries)
642
+ };
643
+ if (dirty || removed > 0) await store.write(manifest);
644
+ const architecture = await refreshArchitectureCache(repositoryRoot, manifest);
645
+ const result = {
646
+ scanned: files.length,
647
+ added,
648
+ changed,
649
+ unchanged,
650
+ removed,
651
+ skipped,
652
+ errors,
653
+ durationMs: Date.now() - start,
654
+ totalIndexed: Object.keys(entries).length,
655
+ architecture: {
656
+ source: architecture.source,
657
+ readFiles: architecture.stats.readFiles,
658
+ reusedFiles: architecture.stats.reusedFiles
659
+ }
660
+ };
661
+ return { result, manifest };
662
+ }
663
+ function statUnchanged(entry, info) {
664
+ return entry.mtimeMs !== void 0 && entry.size === info.size && entry.mtimeMs === info.mtimeMs && entry.ctimeMs === info.ctimeMs;
665
+ }
666
+ function trustedMetadata(info) {
667
+ if (Date.now() - Math.max(info.mtimeMs, info.ctimeMs) < RACY_WINDOW_MS) return {};
668
+ return { mtimeMs: info.mtimeMs, ctimeMs: info.ctimeMs };
669
+ }
670
+ function hashContent(content) {
671
+ return createHash("sha256").update(content, "utf8").digest("hex");
672
+ }
673
+ function sortEntries(entries) {
674
+ return Object.fromEntries(
675
+ Object.keys(entries).sort().map((key) => [key, entries[key]])
676
+ );
677
+ }
678
+
679
+ // src/context/discovery.ts
680
+ import { readFile as readFile9, realpath, stat as stat4 } from "fs/promises";
681
+ import { isAbsolute as isAbsolute3, join as join8, relative as relative6 } from "path";
17
682
 
18
683
  // src/integrations/graphify.ts
19
684
  import { execFile } from "child_process";
20
- import { mkdir, mkdtemp, readFile as readFile2, rm, stat, utimes } from "fs/promises";
685
+ import { mkdir as mkdir3, mkdtemp, readFile as readFile6, rm, stat as stat3, utimes } from "fs/promises";
21
686
  import { tmpdir } from "os";
22
- import { dirname, join, relative, resolve, sep } from "path";
687
+ import { dirname as dirname4, join as join6, relative as relative3, resolve as resolve5, sep as sep2 } from "path";
23
688
  import { promisify } from "util";
24
689
 
25
690
  // src/map/graph-freshness.ts
26
- import { createHash } from "crypto";
27
- import { readFile } from "fs/promises";
28
- var digest = (value) => createHash("sha256").update(value).digest("hex");
691
+ import { createHash as createHash2 } from "crypto";
692
+ import { readFile as readFile5 } from "fs/promises";
693
+ var digest = (value) => createHash2("sha256").update(value).digest("hex");
29
694
  async function writeGraphSourceSnapshot(path, manifest) {
30
695
  await writeAtomic(
31
696
  `${path}.sources.json`,
32
697
  JSON.stringify({
33
698
  version: 1,
34
- graphHash: digest(await readFile(path, "utf8")),
699
+ graphHash: digest(await readFile5(path, "utf8")),
35
700
  sources: Object.fromEntries(
36
701
  Object.values(manifest.entries).map((entry) => [entry.path, entry.hash])
37
702
  )
@@ -41,13 +706,13 @@ async function writeGraphSourceSnapshot(path, manifest) {
41
706
  async function readGraphSourceSnapshot(path) {
42
707
  let raw;
43
708
  try {
44
- raw = await readFile(`${path}.sources.json`, "utf8");
709
+ raw = await readFile5(`${path}.sources.json`, "utf8");
45
710
  } catch (error) {
46
711
  return error.code === "ENOENT" ? null : {};
47
712
  }
48
713
  try {
49
714
  const snapshot = JSON.parse(raw);
50
- if (snapshot.version !== 1 || snapshot.graphHash !== digest(await readFile(path, "utf8")) || !snapshot.sources || typeof snapshot.sources !== "object" || Array.isArray(snapshot.sources))
715
+ if (snapshot.version !== 1 || snapshot.graphHash !== digest(await readFile5(path, "utf8")) || !snapshot.sources || typeof snapshot.sources !== "object" || Array.isArray(snapshot.sources))
51
716
  return {};
52
717
  return Object.fromEntries(
53
718
  Object.entries(snapshot.sources).filter(
@@ -134,17 +799,17 @@ async function graphifyGraphExists(repositoryRoot, graphPath = DEFAULT_GRAPH_PAT
134
799
  }
135
800
  async function generateGraphifyGraph(options) {
136
801
  const run = options.run ?? runCommand;
137
- const repositoryRoot = resolve(options.repositoryRoot);
802
+ const repositoryRoot = resolve5(options.repositoryRoot);
138
803
  const graphPath = options.graphPath ?? DEFAULT_GRAPH_PATH;
139
804
  const destination = resolveGraphPath(repositoryRoot, graphPath);
140
- await mkdir(dirname(destination), { recursive: true });
141
- await ignoreGeneratedGraphDirectory(dirname(destination));
805
+ await mkdir3(dirname4(destination), { recursive: true });
806
+ await ignoreGeneratedGraphDirectory(dirname4(destination));
142
807
  const { manifest: sourceSnapshot } = await loadFreshIndex(repositoryRoot);
143
808
  const current = await readGraphFileIfPresent(destination);
144
809
  const isDefaultPath = graphPath === DEFAULT_GRAPH_PATH;
145
810
  if (current && isDefaultPath) {
146
- const backup = await readFile2(destination, "utf8");
147
- const backupStat = await stat(destination);
811
+ const backup = await readFile6(destination, "utf8");
812
+ const backupStat = await stat3(destination);
148
813
  try {
149
814
  await run("graphify", ["update", repositoryRoot, "--force", "--no-cluster"], repositoryRoot);
150
815
  const updated = await readGraphFile(destination);
@@ -158,16 +823,16 @@ async function generateGraphifyGraph(options) {
158
823
  );
159
824
  }
160
825
  }
161
- const stagingRoot = await mkdtemp(join(tmpdir(), "codebuddy-graphify-"));
826
+ const stagingRoot = await mkdtemp(join6(tmpdir(), "codebuddy-graphify-"));
162
827
  try {
163
828
  await run(
164
829
  "graphify",
165
830
  ["extract", repositoryRoot, "--out", stagingRoot, "--code-only", "--no-cluster"],
166
831
  repositoryRoot
167
832
  );
168
- const stagedPath = join(stagingRoot, DEFAULT_GRAPH_PATH);
833
+ const stagedPath = join6(stagingRoot, DEFAULT_GRAPH_PATH);
169
834
  const staged = await readGraphFile(stagedPath);
170
- await writeAtomic(destination, await readFile2(stagedPath, "utf8"));
835
+ await writeAtomic(destination, await readFile6(stagedPath, "utf8"));
171
836
  await writeGraphSourceSnapshot(destination, sourceSnapshot);
172
837
  return { graphPath, generated: true, ...staged };
173
838
  } finally {
@@ -175,17 +840,17 @@ async function generateGraphifyGraph(options) {
175
840
  }
176
841
  }
177
842
  async function ignoreGeneratedGraphDirectory(directory) {
178
- const path = join(directory, ".gitignore");
843
+ const path = join6(directory, ".gitignore");
179
844
  try {
180
- await stat(path);
845
+ await stat3(path);
181
846
  } catch {
182
847
  await writeAtomic(path, "# CodeBuddy-generated Graphify output (derived, rebuildable)\n*\n");
183
848
  }
184
849
  }
185
850
  function resolveGraphPath(repositoryRoot, graphPath) {
186
- const absolute = resolve(repositoryRoot, graphPath);
187
- const relativePath = relative(repositoryRoot, absolute);
188
- if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`)) {
851
+ const absolute = resolve5(repositoryRoot, graphPath);
852
+ const relativePath = relative3(repositoryRoot, absolute);
853
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep2}`)) {
189
854
  throw new Error(`Graph path must be inside the project root: ${graphPath}`);
190
855
  }
191
856
  return absolute;
@@ -198,10 +863,10 @@ async function readGraphFileIfPresent(path) {
198
863
  }
199
864
  }
200
865
  async function readGraphFile(path) {
201
- const info = await stat(path);
866
+ const info = await stat3(path);
202
867
  if (!info.isFile() || info.size === 0)
203
868
  throw new Error(`Graphify graph is missing or empty: ${path}`);
204
- const raw = JSON.parse(await readFile2(path, "utf8"));
869
+ const raw = JSON.parse(await readFile6(path, "utf8"));
205
870
  const nodes = arrayLength(raw.nodes ?? raw.entities);
206
871
  const edges = arrayLength(raw.edges ?? raw.links ?? raw.relationships);
207
872
  if (nodes === 0) throw new Error(`Graphify graph contains no nodes: ${path}`);
@@ -211,17 +876,9 @@ function arrayLength(value) {
211
876
  return Array.isArray(value) ? value.length : 0;
212
877
  }
213
878
 
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
-
218
- // src/context/discovery.ts
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";
221
-
222
879
  // src/map/graphify.ts
223
- import { readFile as readFile3 } from "fs/promises";
224
- import { isAbsolute, relative as relative2, resolve as resolve2 } from "path";
880
+ import { readFile as readFile7 } from "fs/promises";
881
+ import { isAbsolute, relative as relative4, resolve as resolve6 } from "path";
225
882
  var NON_DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
226
883
  "contains",
227
884
  "part_of",
@@ -255,7 +912,7 @@ var DIRECT_DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
255
912
  ]);
256
913
  var MAX_EDGE_SYMBOLS = 8;
257
914
  async function loadGraphifyMap(graphPath, repositoryRoot = process.cwd()) {
258
- const raw = JSON.parse(await readFile3(graphPath, "utf8"));
915
+ const raw = JSON.parse(await readFile7(graphPath, "utf8"));
259
916
  const nodes = arrayRecord(raw.nodes ?? raw.entities);
260
917
  const edges = arrayRecord(raw.edges ?? raw.links ?? raw.relationships);
261
918
  const nodePaths = /* @__PURE__ */ new Map();
@@ -268,7 +925,7 @@ async function loadGraphifyMap(graphPath, repositoryRoot = process.cwd()) {
268
925
  nodePaths.set(id, path);
269
926
  const label = stringValue(node.label ?? node.name);
270
927
  if (label && label !== path && !path.endsWith(`/${label}`)) nodeLabels.set(id, label);
271
- modules.push({ path, language: languageFor(path), imports: [] });
928
+ modules.push({ path, language: languageFor2(path), imports: [] });
272
929
  }
273
930
  const modulePaths = new Set(modules.map((module) => module.path));
274
931
  const importedEdges = [];
@@ -348,21 +1005,21 @@ function graphifyPath(node, repositoryRoot) {
348
1005
  return candidate ? normalizeGraphPath(candidate, repositoryRoot) : null;
349
1006
  }
350
1007
  function normalizeGraphPath(path, repositoryRoot) {
351
- const absolute = isAbsolute(path) ? resolve2(path) : resolve2(repositoryRoot, path);
352
- const relativePath = relative2(resolve2(repositoryRoot), absolute).replace(/\\/g, "/");
1008
+ const absolute = isAbsolute(path) ? resolve6(path) : resolve6(repositoryRoot, path);
1009
+ const relativePath = relative4(resolve6(repositoryRoot), absolute).replace(/\\/g, "/");
353
1010
  if (relativePath === "" || relativePath === ".." || relativePath.startsWith("../")) return null;
354
1011
  return relativePath.replace(/^\.\//, "");
355
1012
  }
356
1013
  function stringValue(value) {
357
1014
  return typeof value === "string" && value.length > 0 ? value : null;
358
1015
  }
359
- function languageFor(path) {
1016
+ function languageFor2(path) {
360
1017
  if (/\.py$/.test(path)) return "py";
361
1018
  return /\.tsx?$/.test(path) ? "ts" : "js";
362
1019
  }
363
1020
 
364
1021
  // src/context/anchors.ts
365
- import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
1022
+ import { isAbsolute as isAbsolute2, relative as relative5, resolve as resolve7, sep as sep3 } from "path";
366
1023
  var MAX_ANCHORS = 3;
367
1024
  var MAX_ERROR_SEARCH_FILES = 2e3;
368
1025
  var JS_FRAME = /\bat\s+(?:[^\s()]+\s+\()?([^\s()]+?):(\d+):\d+\)?/g;
@@ -370,7 +1027,7 @@ var PY_FRAME = /\bFile\s+"([^"]+)",\s+line\s+(\d+)/g;
370
1027
  var PATH_TOKEN = /(?:^|[\s(`'"[])((?:\.{0,2}\/)?[\w@.-]+(?:\/[\w@.-]+)*\.[A-Za-z0-9]+)(?::(\d+))?/g;
371
1028
  var QUOTED = /"([^"\n]{8,200})"|'([^'\n]{8,200})'|`([^`\n]{8,200})`/g;
372
1029
  async function extractTaskAnchors(repositoryRoot, task, paths) {
373
- const root = resolve3(repositoryRoot);
1030
+ const root = resolve7(repositoryRoot);
374
1031
  const found = [];
375
1032
  const add = (raw, line, kind, at) => {
376
1033
  const path = toIndexedPath(root, raw, paths);
@@ -398,9 +1055,9 @@ async function extractTaskAnchors(repositoryRoot, task, paths) {
398
1055
  return anchors.slice(0, MAX_ANCHORS);
399
1056
  }
400
1057
  function toIndexedPath(root, raw, paths) {
401
- const absolute = isAbsolute2(raw) ? raw : resolve3(root, raw);
402
- const rel = relative3(root, absolute);
403
- if (rel === "" || rel.startsWith(`..${sep2}`) || rel === ".." || isAbsolute2(rel)) return null;
1058
+ const absolute = isAbsolute2(raw) ? raw : resolve7(root, raw);
1059
+ const rel = relative5(root, absolute);
1060
+ if (rel === "" || rel.startsWith(`..${sep3}`) || rel === ".." || isAbsolute2(rel)) return null;
404
1061
  const normalized = rel.replace(/\\/g, "/");
405
1062
  return paths.has(normalized) ? normalized : null;
406
1063
  }
@@ -426,15 +1083,16 @@ async function errorAnchors(root, task, paths) {
426
1083
  }
427
1084
 
428
1085
  // 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";
1086
+ import { mkdir as mkdir4, readFile as readFile8 } from "fs/promises";
1087
+ import { dirname as dirname5, join as join7 } from "path";
431
1088
  var PASSAGE_LINES = 40;
432
1089
  var K1 = 1.2;
433
1090
  var B = 0.75;
434
1091
  var MIN_TERMS = 2;
435
1092
  var MIN_SHARE = 0.5;
436
1093
  var MIN_IDF_COVERAGE = 0.3;
437
- async function rankContent(repositoryRoot, manifest, terms, wantsTests, wantsDocs = false, wantsChangelog = false) {
1094
+ var CARD_WEIGHT = 0.5;
1095
+ async function rankContent(repositoryRoot, manifest, terms, wantsTests, wantsDocs = false, wantsChangelog = false, cards = /* @__PURE__ */ new Map()) {
438
1096
  const none = { matches: [], idf: /* @__PURE__ */ new Map() };
439
1097
  if (terms.length === 0) return none;
440
1098
  const paths = Object.keys(manifest.entries).filter(
@@ -478,29 +1136,38 @@ async function rankContent(repositoryRoot, manifest, terms, wantsTests, wantsDoc
478
1136
  }
479
1137
  const documents = Object.entries(files);
480
1138
  if (documents.length === 0) return none;
1139
+ const cardWords = new Map(
1140
+ documents.flatMap(([path]) => {
1141
+ const text = cards.get(path);
1142
+ return text ? [[path, new Set(splitWords(text))]] : [];
1143
+ })
1144
+ );
1145
+ const has = (path, file, term) => (file.counts[term] ?? 0) > 0 || cardWords.get(path)?.has(term) === true;
481
1146
  const averageLength = documents.reduce((sum, [, file]) => sum + file.length, 0) / documents.length;
482
1147
  const idf = new Map(
483
1148
  terms.map((term) => {
484
- const df = documents.filter(([, file]) => (file.counts[term] ?? 0) > 0).length;
1149
+ const df = documents.filter(([path, file]) => has(path, file, term)).length;
485
1150
  return [term, Math.log(1 + (documents.length - df + 0.5) / (df + 0.5))];
486
1151
  })
487
1152
  );
488
1153
  const totalIdf = [...idf.values()].reduce((sum, value) => sum + value, 0);
489
1154
  const scored = documents.map(([path, file]) => {
490
1155
  const fileName = new Set(splitWords(path.slice(path.lastIndexOf("/") + 1)));
1156
+ const card = cardWords.get(path);
491
1157
  let score = 0;
492
1158
  let covered = 0;
493
1159
  for (const term of terms) {
494
- const tf = file.counts[term] ?? 0;
1160
+ const inSource = file.counts[term] ?? 0;
1161
+ const tf = inSource || (card?.has(term) ? CARD_WEIGHT : 0);
495
1162
  if (tf === 0) continue;
496
- covered += idf.get(term) ?? 0;
1163
+ covered += (idf.get(term) ?? 0) * (inSource > 0 ? 1 : CARD_WEIGHT);
497
1164
  const saturation = tf * (K1 + 1) / (tf + K1 * (1 - B + B * file.length / averageLength));
498
1165
  score += (idf.get(term) ?? 0) * saturation * (fileName.has(term) ? 1.5 : 1);
499
1166
  }
500
1167
  const named = terms.filter((term) => fileName.has(term));
501
1168
  const matched = Math.max(
502
1169
  named.length,
503
- ...file.passages.map(
1170
+ ...[...file.passages, ...card ? [[...card]] : []].map(
504
1171
  (words) => (/* @__PURE__ */ new Set([...named, ...terms.filter((term) => words.includes(term))])).size
505
1172
  )
506
1173
  );
@@ -520,11 +1187,11 @@ function idfCoverage(text, terms, idf) {
520
1187
  return terms.reduce((sum, term) => sum + (words.has(term) ? idf.get(term) ?? 0 : 0), 0) / total;
521
1188
  }
522
1189
  function cachePath(repositoryRoot) {
523
- return join2(repositoryRoot, ".codebuddy", "cache", "content-terms.json");
1190
+ return join7(repositoryRoot, ".codebuddy", "cache", "content-terms.json");
524
1191
  }
525
1192
  async function loadCache(repositoryRoot) {
526
1193
  try {
527
- const parsed = JSON.parse(await readFile4(cachePath(repositoryRoot), "utf8"));
1194
+ const parsed = JSON.parse(await readFile8(cachePath(repositoryRoot), "utf8"));
528
1195
  if (parsed.version === 2 && parsed.files && typeof parsed.files === "object") return parsed;
529
1196
  } catch {
530
1197
  }
@@ -533,7 +1200,7 @@ async function loadCache(repositoryRoot) {
533
1200
  async function saveCache(repositoryRoot, cache) {
534
1201
  try {
535
1202
  const path = cachePath(repositoryRoot);
536
- await mkdir2(dirname2(path), { recursive: true });
1203
+ await mkdir4(dirname5(path), { recursive: true });
537
1204
  await writeAtomic(path, JSON.stringify(cache));
538
1205
  } catch {
539
1206
  }
@@ -547,22 +1214,25 @@ var MAX_EVIDENCE_BYTES = 512e3;
547
1214
  async function readBoundedSource(repositoryRoot, path) {
548
1215
  if (isCredentialPath(path)) return null;
549
1216
  try {
550
- const absolute = join3(repositoryRoot, path);
551
- if ((await stat2(absolute)).size > MAX_EVIDENCE_BYTES) return null;
552
- const source = await readFile5(absolute, "utf8");
1217
+ const absolute = join8(repositoryRoot, path);
1218
+ if ((await stat4(absolute)).size > MAX_EVIDENCE_BYTES) return null;
1219
+ const source = await readFile9(absolute, "utf8");
553
1220
  return source.includes("\0") ? null : source;
554
1221
  } catch {
555
1222
  return null;
556
1223
  }
557
1224
  }
558
1225
  async function discoverTaskPaths(repositoryRoot, task, manifest, mapSource, options = {}) {
1226
+ task = stripIssueTemplate(task);
559
1227
  const anchors = await extractTaskAnchors(
560
1228
  repositoryRoot,
561
1229
  task,
562
1230
  new Set(Object.keys(manifest.entries))
563
1231
  );
564
- if (anchors.length === 0)
565
- return rankTaskFiles(repositoryRoot, task, manifest, mapSource, options);
1232
+ if (anchors.length === 0) {
1233
+ const ranked2 = await rankTaskFiles(repositoryRoot, task, manifest, mapSource, options);
1234
+ return await withExpansion(repositoryRoot, task, ranked2, manifest, mapSource, options) ?? ranked2;
1235
+ }
566
1236
  const anchored = anchors.map((anchor) => anchor.path);
567
1237
  const hits = anchors.flatMap(
568
1238
  (anchor) => anchor.line === null ? [] : [
@@ -579,7 +1249,9 @@ async function discoverTaskPaths(repositoryRoot, task, manifest, mapSource, opti
579
1249
  const explain = `retrieval: anchors (${anchors.map(
580
1250
  (anchor) => `${anchor.kind} ${anchor.path}${anchor.line === null ? "" : `:${anchor.line}`}`
581
1251
  ).join(", ")})`;
582
- if (anchored.length >= TASK_FILES) return { paths: anchored, hits, explain: [explain] };
1252
+ if (anchored.length >= TASK_FILES) {
1253
+ return { paths: anchored, hits, explain: [explain], confidence: "high" };
1254
+ }
583
1255
  const ranked = await rankTaskFiles(repositoryRoot, task, manifest, mapSource, options);
584
1256
  return {
585
1257
  ...ranked.symbols ? { symbols: ranked.symbols } : {},
@@ -588,35 +1260,85 @@ async function discoverTaskPaths(repositoryRoot, task, manifest, mapSource, opti
588
1260
  TASK_FILES
589
1261
  ),
590
1262
  hits: [...hits, ...ranked.hits.filter((hit) => !anchored.includes(hit.path))],
591
- explain: [explain, ...ranked.explain]
1263
+ explain: [explain, ...ranked.explain],
1264
+ confidence: "high"
592
1265
  };
593
1266
  }
594
1267
  async function withLlmSummaries(repositoryRoot, manifest) {
595
1268
  const entries = Object.values(manifest.entries);
596
1269
  try {
597
- const { resolveLlmConfig } = await import("./config-GEZFLXK6.js");
598
- const config = await resolveLlmConfig(repositoryRoot);
599
- if (!config.enabled || !config.features.summaries) return entries;
600
- const { loadLlmSummaries } = await import("./summaries-ML53R7EL.js");
1270
+ if ((await readConfigFile(repositoryRoot)).llm?.enabled === false) return entries;
1271
+ const { loadLlmSummaries, cardWords } = await import("./summaries-QXWFFDUZ.js");
601
1272
  const summaries = await loadLlmSummaries(repositoryRoot, manifest);
602
1273
  if (summaries.size === 0) return entries;
603
1274
  return entries.map((entry) => {
604
1275
  const summary = summaries.get(entry.path);
605
- return summary ? { ...entry, llmText: `${summary.summary} ${Object.values(summary.symbols).join(" ")}` } : entry;
1276
+ return summary ? {
1277
+ ...entry,
1278
+ llmText: `${summary.summary} ${Object.values(summary.symbols).join(" ")}`,
1279
+ ...summary.symptoms ? { cardWords: cardWords(summary) } : {}
1280
+ } : entry;
606
1281
  });
607
1282
  } catch {
608
1283
  return entries;
609
1284
  }
610
1285
  }
1286
+ var EXPANSION_MS = { hook: 300, pack: 800 };
1287
+ async function withExpansion(repositoryRoot, task, ranked, manifest, mapSource, options) {
1288
+ if (ranked.paths.length > 0 && ranked.confidence !== "low") return null;
1289
+ if (identifierTokens(task).some((token) => CODE_IDENTIFIER.test(token))) return null;
1290
+ const deadlineAt = options.expansionDeadlineAt ?? Date.now() + EXPANSION_MS.pack;
1291
+ const { expandTask } = await import("./expand-KWYGNCKZ.js");
1292
+ const expansion = await expandTask(repositoryRoot, task, deadlineAt - Date.now());
1293
+ if (!expansion) return null;
1294
+ const expanded = await rankTaskFiles(
1295
+ repositoryRoot,
1296
+ `${task} ${expansion.terms.join(" ")}`,
1297
+ manifest,
1298
+ mapSource,
1299
+ options
1300
+ );
1301
+ const note = `expansion: task expanded with ${expansion.terms.length} code term(s) (${expansion.terms.join(", ")}; ${expansion.cached ? "cached" : "local model"})`;
1302
+ if (expanded.paths.length === 0)
1303
+ return { ...ranked, explain: [...ranked.explain, `${note}; still no match`] };
1304
+ return { ...expanded, explain: [...ranked.explain, note, ...expanded.explain] };
1305
+ }
1306
+ var AGREEMENT = { topK: 5, chunkFloor: 0.8, cardFloor: 0.75 };
1307
+ function agreementVotes(path, signals, config = AGREEMENT) {
1308
+ const names = [];
1309
+ let scored = 0;
1310
+ if (signals.zg.slice(0, config.topK).includes(path)) names.push("zg");
1311
+ const within = (ranking, floor) => (ranking ?? []).slice(0, config.topK).some((match) => match.path === path && match.score >= floor);
1312
+ if (within(signals.chunks, config.chunkFloor)) {
1313
+ names.push("chunk");
1314
+ scored++;
1315
+ }
1316
+ if (within(signals.cards, config.cardFloor)) {
1317
+ names.push("card");
1318
+ scored++;
1319
+ }
1320
+ if (signals.term) {
1321
+ names.push("terms");
1322
+ scored++;
1323
+ }
1324
+ return { votes: names.length, scored, names };
1325
+ }
1326
+ function agrees(vote) {
1327
+ return vote.votes >= 2 && vote.scored >= 1;
1328
+ }
611
1329
  async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options) {
612
1330
  const wantsChangelog = asksForChangelog(task);
613
1331
  const entries = (await withLlmSummaries(repositoryRoot, manifest)).filter(
614
1332
  (entry) => wantsChangelog || !CHANGELOG_PATH.test(entry.path)
615
1333
  );
616
1334
  const wantsTests = /\b(tests?|spec)\b/i.test(task);
617
- const identifiers = identifierTokens(task).filter((token) => CODE_IDENTIFIER.test(token));
1335
+ const identifiers = identifierTokens(task).filter(
1336
+ (token) => CODE_IDENTIFIER.test(token) && !ABBREVIATION.test(token)
1337
+ );
618
1338
  if (identifiers.length > 0) {
619
- const exact = entries.filter((entry) => wantsTests || !TEST_PATH.test(entry.path)).map((entry) => ({
1339
+ const exact = entries.filter(
1340
+ (entry) => (wantsTests || !TEST_PATH.test(entry.path)) && countsAsSource(entry.path, task) && !EXCEPTION_MODULE.test(entry.path)
1341
+ ).map((entry) => ({
620
1342
  path: entry.path,
621
1343
  named: new Set(
622
1344
  (entry.symbolTable ?? []).flatMap(
@@ -637,6 +1359,7 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
637
1359
  return {
638
1360
  paths: expansion2.paths.slice(0, TASK_FILES),
639
1361
  hits: [],
1362
+ confidence: "high",
640
1363
  explain: [
641
1364
  `retrieval: exact-symbol routing (${identifiers.slice(0, 5).join(", ")})`,
642
1365
  ...expansion2.explain ? [expansion2.explain] : []
@@ -653,7 +1376,8 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
653
1376
  taskTerms(task),
654
1377
  wantsTests,
655
1378
  DOCS_TASK.test(task),
656
- wantsChangelog
1379
+ wantsChangelog,
1380
+ new Map(entries.flatMap((entry) => entry.cardWords ? [[entry.path, entry.cardWords]] : []))
657
1381
  );
658
1382
  const keyword = content.length > 0 ? fuseRankings([summaryKeyword, content.map((match) => match.path)]) : summaryKeyword;
659
1383
  const weakKeyword = scored.length - keyword.length;
@@ -664,7 +1388,7 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
664
1388
  reason: "semantic retrieval off",
665
1389
  stale: 0,
666
1390
  changed: 0
667
- } : await import("./zvec-XDZNZ2OL.js").then(
1391
+ } : await import("./zvec-F73IBWO5.js").then(
668
1392
  ({ zvecCandidates }) => zvecCandidates(
669
1393
  repositoryRoot,
670
1394
  task,
@@ -686,51 +1410,64 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
686
1410
  let ranked = keyword;
687
1411
  let hits = [];
688
1412
  const explain = [];
1413
+ const agreement = await agreementConfig(repositoryRoot);
1414
+ const cardsOn = entries.some((entry) => entry.cardWords);
1415
+ const dense = options.semantic === false && options.vectorDeadlineAt === void 0 ? null : await raceDeadline(
1416
+ rankFileVectors(repositoryRoot, manifest, task, options.embed),
1417
+ options.semantic === false ? options.vectorDeadlineAt : void 0
1418
+ );
1419
+ const chunks = dense?.chunks ?? null;
1420
+ const cards = cardsOn ? dense?.cards ?? null : null;
1421
+ const usable = (path) => (wantsTests || !TEST_PATH.test(path)) && (wantsChangelog || !CHANGELOG_PATH.test(path));
1422
+ const zg = fuseRankings([semantic.paths, semantic.cardPaths ?? []]).filter(usable);
1423
+ const votes = /* @__PURE__ */ new Map();
1424
+ const denseAvailable = zg.length > 0 || chunks !== null || cards !== null;
689
1425
  if (semantic.status !== "disabled" && semantic.paths.length === 0) {
690
1426
  explain.push(`retrieval: keyword ranking only (${semantic.reason})`);
691
- } else if (semantic.status !== "disabled") {
1427
+ }
1428
+ if (denseAvailable) {
692
1429
  const terms = taskTerms(task);
693
1430
  const vector = [];
694
- const partial = [];
695
- let uncorroborated = 0;
696
- let belowFloor = 0;
697
- const minScore = semantic.minScore ?? 0;
698
- for (const path of semantic.paths) {
699
- if (!wantsTests && TEST_PATH.test(path)) continue;
700
- if (!wantsChangelog && CHANGELOG_PATH.test(path)) continue;
701
- if (keyword.includes(path)) vector.push(path);
702
- else if (await hasMatchedPassageEvidence(
703
- repositoryRoot,
704
- path,
705
- semantic.hits,
706
- terms,
707
- manifest.entries[path]?.symbolTable ?? []
708
- )) {
709
- const source = await readBoundedSource(repositoryRoot, path) ?? "";
710
- if (idfCoverage(`${path} ${source}`, terms, idf) < minScore) {
711
- belowFloor++;
712
- continue;
713
- }
714
- if (countTerms(source, terms) >= EVIDENCE_FLOOR.vectorTerms) vector.push(path);
715
- else partial.push(path);
716
- if (!evidence.has(path)) evidence.set(path, 0);
717
- } else uncorroborated++;
1431
+ let rejected = 0;
1432
+ const top = (ranking) => (ranking ?? []).slice(0, agreement.topK).map((match) => match.path);
1433
+ const candidates = [
1434
+ .../* @__PURE__ */ new Set([
1435
+ ...semantic.paths,
1436
+ ...zg.slice(0, agreement.topK),
1437
+ ...top(chunks),
1438
+ ...top(cards)
1439
+ ])
1440
+ ].filter(usable);
1441
+ for (const path of candidates) {
1442
+ const termVote = await termEvidence(repositoryRoot, path, semantic, terms, idf, manifest);
1443
+ const vote = agreementVotes(path, { zg, chunks, cards, term: termVote }, agreement);
1444
+ votes.set(path, vote);
1445
+ if (keyword.includes(path)) {
1446
+ vector.push(path);
1447
+ continue;
1448
+ }
1449
+ if (!countsAsSource(path, task) || !agrees(vote)) {
1450
+ rejected++;
1451
+ continue;
1452
+ }
1453
+ vector.push(path);
1454
+ if (!evidence.has(path)) evidence.set(path, 0);
718
1455
  }
719
1456
  ranked = rankEvidenceCandidates(
720
1457
  scored.map((entry) => entry.path),
721
1458
  keyword,
722
1459
  vector,
723
- partial
1460
+ []
724
1461
  );
725
- hits = semantic.hits.filter((hit) => vector.includes(hit.path) || partial.includes(hit.path));
1462
+ hits = semantic.hits.filter((hit) => vector.includes(hit.path));
1463
+ const sources = [
1464
+ ...zg.length > 0 ? [semantic.reason] : [],
1465
+ ...chunks ? ["file vectors"] : [],
1466
+ ...cards ? ["intent-card vectors"] : []
1467
+ ];
726
1468
  explain.push(
727
- `retrieval: keyword ranking fused with ${semantic.reason}` + (uncorroborated > 0 ? `; ${uncorroborated} uncorroborated vector file(s) dropped` : "")
1469
+ `retrieval: keyword ranking fused with ${sources.join(", ")}; agreement rule (2 of zg/chunk/card/terms in top ${agreement.topK})` + (rejected > 0 ? `; ${rejected} dense candidate(s) without agreement dropped` : "")
728
1470
  );
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
- }
734
1471
  }
735
1472
  let symbols;
736
1473
  const reranked = await llmRerank({
@@ -752,21 +1489,21 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
752
1489
  const wantsDocs = DOCS_TASK.test(task);
753
1490
  if (!ranked.some((path) => countsAsSource(path, task))) {
754
1491
  explain.push(
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")
1492
+ "abstention: no source file reached the evidence floor or the agreement rule (" + (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")
756
1493
  );
757
- return { paths: commands, hits: [], explain };
1494
+ return { paths: commands, hits: [], explain, confidence: commands.length > 0 ? "high" : "low" };
758
1495
  }
759
1496
  if (weakKeyword > 0) {
760
1497
  explain.push(
761
1498
  `abstention: ${weakKeyword} weak keyword candidate(s) below the evidence floor (score < ${EVIDENCE_FLOOR.keywordScore}) dropped`
762
1499
  );
763
1500
  }
764
- const vectors = options.semantic === false || reranked ? null : await rankFilesByVector(repositoryRoot, manifest, task);
1501
+ const vectors = reranked || !chunks ? null : cards ? fuseRankings([chunks, cards].map((ranking) => ranking.map((match) => match.path))) : chunks.map((match) => match.path);
765
1502
  if (vectors && vectors.length > 0) {
766
1503
  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);
1504
+ ranked = [...doc, ...vectors].slice(0, TASK_FILES);
768
1505
  explain.push(
769
- `retrieval: file vectors ordered the files (best ${vectors[0]?.score.toFixed(2)})`
1506
+ `retrieval: ${cards ? "file and card" : "file"} vectors ordered the files (best chunk ${chunks?.[0]?.score.toFixed(2)})`
770
1507
  );
771
1508
  }
772
1509
  const expansion = expandAlongImports(ranked, await mapSource, evidence, task);
@@ -780,18 +1517,82 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
780
1517
  `abstention: ${paths.length - TASK_FILES} lower-ranked candidate(s) beyond the top ${TASK_FILES} dropped`
781
1518
  );
782
1519
  }
1520
+ const returned = paths.slice(0, TASK_FILES);
1521
+ const confidence = confidenceOf(returned, {
1522
+ keyword,
1523
+ strongKeyword: scored.filter((entry) => entry.score >= 2 * EVIDENCE_FLOOR.keywordScore).map((entry) => entry.path),
1524
+ commands,
1525
+ votes,
1526
+ denseAvailable
1527
+ });
1528
+ if (confidence === "low") {
1529
+ explain.push(
1530
+ "abstention: low confidence (no returned file had two agreeing signals); no task targets returned"
1531
+ );
1532
+ return { paths: [], hits: [], explain, confidence };
1533
+ }
783
1534
  return {
784
- paths: paths.slice(0, TASK_FILES),
1535
+ paths: returned,
785
1536
  hits,
786
1537
  explain,
1538
+ confidence,
787
1539
  ...symbols && Object.keys(symbols).length > 0 ? { symbols } : {}
788
1540
  };
789
1541
  }
1542
+ function confidenceOf(paths, input) {
1543
+ if (paths.some((path) => input.commands.includes(path))) return "high";
1544
+ if (!input.denseAvailable) return "medium";
1545
+ let best = 0;
1546
+ for (const path of paths) {
1547
+ const dense = input.votes.get(path)?.votes ?? 0;
1548
+ const lexical = input.strongKeyword.includes(path) ? 2 : input.keyword.includes(path) ? 1 : 0;
1549
+ best = Math.max(best, dense + lexical);
1550
+ }
1551
+ return best >= 3 ? "high" : best >= 2 ? "medium" : "low";
1552
+ }
1553
+ async function termEvidence(repositoryRoot, path, semantic, terms, idf, manifest) {
1554
+ if (!await hasMatchedPassageEvidence(
1555
+ repositoryRoot,
1556
+ path,
1557
+ semantic.hits,
1558
+ terms,
1559
+ manifest.entries[path]?.symbolTable ?? []
1560
+ )) {
1561
+ return false;
1562
+ }
1563
+ const source = await readBoundedSource(repositoryRoot, path) ?? "";
1564
+ return idfCoverage(`${path} ${source}`, terms, idf) >= (semantic.minScore ?? DEFAULT_MIN_SCORE);
1565
+ }
1566
+ var DEFAULT_MIN_SCORE = 0.3;
1567
+ async function agreementConfig(repositoryRoot) {
1568
+ try {
1569
+ const configured = (await readConfigFile(repositoryRoot)).discovery?.agreement ?? {};
1570
+ return {
1571
+ topK: configured.topK ?? AGREEMENT.topK,
1572
+ chunkFloor: configured.chunkFloor ?? AGREEMENT.chunkFloor,
1573
+ cardFloor: configured.cardFloor ?? AGREEMENT.cardFloor
1574
+ };
1575
+ } catch {
1576
+ return AGREEMENT;
1577
+ }
1578
+ }
1579
+ async function raceDeadline(promise, deadlineAt) {
1580
+ if (deadlineAt === void 0) return promise;
1581
+ const ms = deadlineAt - Date.now();
1582
+ if (ms <= 0) return null;
1583
+ let timer;
1584
+ return Promise.race([
1585
+ promise,
1586
+ new Promise((resolve8) => {
1587
+ timer = setTimeout(() => resolve8(null), ms);
1588
+ })
1589
+ ]).finally(() => clearTimeout(timer));
1590
+ }
790
1591
  var MIN_RERANK_MS = 200;
791
1592
  async function llmRerank(input) {
792
1593
  let config;
793
1594
  try {
794
- config = await (await import("./config-GEZFLXK6.js")).resolveLlmConfig(input.repositoryRoot);
1595
+ config = await (await import("./config-TXQJCCVI.js")).resolveLlmConfig(input.repositoryRoot);
795
1596
  } catch {
796
1597
  return null;
797
1598
  }
@@ -816,7 +1617,7 @@ async function llmRerank(input) {
816
1617
  input.deadlineAt === void 0 ? config.timeoutMs : input.deadlineAt - Date.now()
817
1618
  );
818
1619
  if (deadlineMs < MIN_RERANK_MS) return skipped("no time left in the hook budget");
819
- const { rerankCandidates } = await import("./rerank-IQAEWLQR.js");
1620
+ const { rerankCandidates } = await import("./rerank-XXKWDI5Q.js");
820
1621
  const result = await rerankCandidates(
821
1622
  input.repositoryRoot,
822
1623
  config,
@@ -888,7 +1689,7 @@ async function commandFiles(repositoryRoot, task, entries) {
888
1689
  var COMMAND_DIR = /(^|\/)(cli|cmd|commands?)\//;
889
1690
  async function packageBins(repositoryRoot) {
890
1691
  try {
891
- const manifest = JSON.parse(await readFile5(join3(repositoryRoot, "package.json"), "utf8"));
1692
+ const manifest = JSON.parse(await readFile9(join8(repositoryRoot, "package.json"), "utf8"));
892
1693
  if (typeof manifest.bin === "string" && typeof manifest.name === "string") {
893
1694
  return [manifest.name.slice(manifest.name.lastIndexOf("/") + 1)];
894
1695
  }
@@ -1058,6 +1859,17 @@ function foldPlural(word) {
1058
1859
  if (/(ss|sh|ch|x|z)es$/.test(word)) return word.slice(0, -2);
1059
1860
  return word.slice(0, -1);
1060
1861
  }
1862
+ var ISSUE_TEMPLATE = [
1863
+ /_No response_/gi,
1864
+ /\(Please specify[^)]*\)/gi,
1865
+ /-\s*\[[ xX]\]\s*[\w.+-]+/g,
1866
+ /\d+\.\s*(?:Go to|Click on|Scroll down to)\s*'\.+'|\bSee error\b/gi,
1867
+ /#{1,6}\s*[\w-]+ Version\b(?:\s*v?\d+(?:\.\d+)+[\w.-]*,?)*/gi,
1868
+ /#{1,6}\s*(?:Description|Summary|URL to code causing the issue|MCVE|Minimal reproducible example|Steps to reproduce|Expected behaviou?r|Actual behaviou?r|Screenshots|Logs|Environment|Platform|Additional context)\b/gi
1869
+ ];
1870
+ function stripIssueTemplate(task) {
1871
+ return ISSUE_TEMPLATE.reduce((text, pattern) => text.replace(pattern, " "), task);
1872
+ }
1061
1873
  function taskTerms(task) {
1062
1874
  return [
1063
1875
  ...new Set(
@@ -1070,6 +1882,8 @@ function countTerms(text, terms) {
1070
1882
  const words = new Set(splitWords(text));
1071
1883
  return terms.filter((term) => words.has(term)).length;
1072
1884
  }
1885
+ var ABBREVIATION = /^[a-z](?:\.[a-z])+$/i;
1886
+ var EXCEPTION_MODULE = /(^|\/)(exceptions?|errors?)(\/|\.[a-z]+$)/i;
1073
1887
  var CODE_IDENTIFIER = /[a-z0-9][A-Z]|[_$.]/;
1074
1888
  function identifierTokens(task) {
1075
1889
  const tokens = task.match(/[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*/g) ?? [];
@@ -1083,24 +1897,24 @@ async function loadProjectArchitectureMap(repositoryRoot, manifestSource) {
1083
1897
  const builtIn = (await loadArchitectureMap(repositoryRoot, manifest)).map;
1084
1898
  if (config.graphify?.enabled && await graphifyGraphExists(repositoryRoot, config.graphify.graphPath)) {
1085
1899
  try {
1086
- const graphPath = join3(
1900
+ const graphPath = join8(
1087
1901
  repositoryRoot,
1088
1902
  config.graphify.graphPath ?? "graphify-out/graph.json"
1089
1903
  );
1090
- const [graph, graphFile, sourceHashes] = await Promise.all([
1904
+ const [graph, graphFile, sourceHashes2] = await Promise.all([
1091
1905
  loadGraphifyMap(graphPath, repositoryRoot),
1092
- stat2(graphPath),
1906
+ stat4(graphPath),
1093
1907
  readGraphSourceSnapshot(graphPath)
1094
1908
  ]);
1095
1909
  const livePaths = new Set(
1096
1910
  (await Promise.all(
1097
1911
  graph.modules.map(async (module) => {
1098
1912
  try {
1099
- const absolute = await realpath(join3(repositoryRoot, module.path));
1100
- const inside = relative4(await realpath(repositoryRoot), absolute);
1913
+ const absolute = await realpath(join8(repositoryRoot, module.path));
1914
+ const inside = relative6(await realpath(repositoryRoot), absolute);
1101
1915
  if (inside.startsWith("..") || isAbsolute3(inside)) return null;
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;
1916
+ const source = await stat4(absolute);
1917
+ const fresh = sourceHashes2 === null ? source.mtimeMs <= graphFile.mtimeMs : sourceHashes2[module.path] !== void 0 && sourceHashes2[module.path] === manifest.entries[module.path]?.hash;
1104
1918
  return source.isFile() && fresh ? module.path : null;
1105
1919
  } catch {
1106
1920
  return null;
@@ -1165,7 +1979,9 @@ async function refreshFileVectors(repositoryRoot, manifest, deadlineAt, embed =
1165
1979
  if (cached && cached.hash === manifest.entries[path]?.hash) files[path] = cached;
1166
1980
  else todo.push(path);
1167
1981
  }
1982
+ const cards = await currentCardTexts(repositoryRoot, manifest);
1168
1983
  let embedded = 0;
1984
+ let embeddedCards = 0;
1169
1985
  let lastSave = Date.now();
1170
1986
  for (const path of todo) {
1171
1987
  if (Date.now() >= deadlineAt) break;
@@ -1185,13 +2001,52 @@ async function refreshFileVectors(repositoryRoot, manifest, deadlineAt, embed =
1185
2001
  lastSave = Date.now();
1186
2002
  }
1187
2003
  }
2004
+ const stale = Object.keys(files).filter((path) => {
2005
+ const text = cards.get(path);
2006
+ return text !== void 0 && files[path]?.cardHash !== textHash(text);
2007
+ });
2008
+ for (let start = 0; start < stale.length && Date.now() < deadlineAt; start += BATCH) {
2009
+ const batch = stale.slice(start, start + BATCH);
2010
+ const vectors = await embed(batch.map((path) => cards.get(path)));
2011
+ batch.forEach((path, index) => {
2012
+ const file = files[path];
2013
+ const vector = vectors[index];
2014
+ if (!file || !vector) return;
2015
+ files[path] = {
2016
+ ...file,
2017
+ card: encode(vector),
2018
+ cardHash: textHash(cards.get(path))
2019
+ };
2020
+ embeddedCards += 1;
2021
+ });
2022
+ }
2023
+ for (const [path, file] of Object.entries(files)) {
2024
+ if (file.card && !cards.has(path)) files[path] = { hash: file.hash, vectors: file.vectors };
2025
+ }
1188
2026
  await saveStore(repositoryRoot, { version: 1, model: MODEL, files });
1189
2027
  return {
1190
2028
  embedded,
1191
2029
  files: Object.keys(files).length,
1192
- pending: eligible.length - Object.keys(files).length
2030
+ pending: eligible.length - Object.keys(files).length,
2031
+ ...embeddedCards > 0 ? { cards: embeddedCards } : {}
1193
2032
  };
1194
2033
  }
2034
+ async function currentCardTexts(repositoryRoot, manifest) {
2035
+ try {
2036
+ const { cardText, loadLlmSummaries } = await import("./summaries-QXWFFDUZ.js");
2037
+ const texts = /* @__PURE__ */ new Map();
2038
+ for (const [path, card] of await loadLlmSummaries(repositoryRoot, manifest)) {
2039
+ if (card.symptoms) texts.set(path, `${path}
2040
+ ${cardText(card)}`);
2041
+ }
2042
+ return texts;
2043
+ } catch {
2044
+ return /* @__PURE__ */ new Map();
2045
+ }
2046
+ }
2047
+ function textHash(text) {
2048
+ return createHash3("sha256").update(text).digest("hex").slice(0, 16);
2049
+ }
1195
2050
  async function syncFileVectors(repositoryRoot, budgetMs) {
1196
2051
  const started = Date.now();
1197
2052
  try {
@@ -1202,6 +2057,9 @@ async function syncFileVectors(repositoryRoot, budgetMs) {
1202
2057
  }
1203
2058
  }
1204
2059
  async function rankFilesByVector(repositoryRoot, manifest, task, embed = localEmbed) {
2060
+ return (await rankFileVectors(repositoryRoot, manifest, task, embed))?.chunks ?? null;
2061
+ }
2062
+ async function rankFileVectors(repositoryRoot, manifest, task, embed = localEmbed) {
1205
2063
  if (process.env.CODEBUDDY_SEMANTIC === "0") return null;
1206
2064
  const store = await loadStore(repositoryRoot);
1207
2065
  const current = eligiblePaths(manifest).filter(
@@ -1216,10 +2074,16 @@ async function rankFilesByVector(repositoryRoot, manifest, task, embed = localEm
1216
2074
  } catch {
1217
2075
  return null;
1218
2076
  }
1219
- return current.map((path) => ({
2077
+ const order = (left, right) => right.score - left.score || left.path.localeCompare(right.path);
2078
+ const chunks = current.map((path) => ({
1220
2079
  path,
1221
2080
  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));
2081
+ })).sort(order);
2082
+ const cards = current.flatMap((path) => {
2083
+ const card = store.files[path]?.card;
2084
+ return card ? [{ path, score: dot(decode(card), query) }] : [];
2085
+ }).sort(order);
2086
+ return { chunks, cards: cards.length > 0 ? cards : null };
1223
2087
  }
1224
2088
  function eligiblePaths(manifest) {
1225
2089
  return Object.keys(manifest.entries).filter(
@@ -1251,11 +2115,11 @@ function dot(left, right) {
1251
2115
  return sum;
1252
2116
  }
1253
2117
  function storePath(repositoryRoot) {
1254
- return join4(repositoryRoot, ".codebuddy", "cache", "file-vectors.json");
2118
+ return join9(repositoryRoot, ".codebuddy", "cache", "file-vectors.json");
1255
2119
  }
1256
2120
  async function loadStore(repositoryRoot) {
1257
2121
  try {
1258
- const parsed = JSON.parse(await readFile6(storePath(repositoryRoot), "utf8"));
2122
+ const parsed = JSON.parse(await readFile10(storePath(repositoryRoot), "utf8"));
1259
2123
  if (parsed.version === 1 && parsed.model === MODEL && parsed.files) return parsed;
1260
2124
  } catch {
1261
2125
  }
@@ -1263,19 +2127,30 @@ async function loadStore(repositoryRoot) {
1263
2127
  }
1264
2128
  async function saveStore(repositoryRoot, store) {
1265
2129
  const path = storePath(repositoryRoot);
1266
- await mkdir3(dirname3(path), { recursive: true });
2130
+ await mkdir5(dirname6(path), { recursive: true });
1267
2131
  await writeAtomic(path, JSON.stringify(store));
1268
2132
  }
1269
2133
 
1270
2134
  export {
2135
+ buildArchitectureMap,
2136
+ queryMap,
2137
+ neighbours,
2138
+ indexRepository,
2139
+ loadFreshIndex,
2140
+ hashContent,
1271
2141
  setupGraphify,
1272
2142
  generateGraphifyGraph,
1273
2143
  loadGraphifyMap,
2144
+ refreshFileVectors,
1274
2145
  syncFileVectors,
2146
+ rankFilesByVector,
2147
+ rankFileVectors,
1275
2148
  TASK_PATH_LIMIT,
1276
2149
  readBoundedSource,
1277
2150
  discoverTaskPaths,
2151
+ EXPANSION_MS,
1278
2152
  TEST_PATH,
2153
+ CHANGELOG_PATH,
1279
2154
  asksForChangelog,
1280
2155
  NON_SOURCE_PATH,
1281
2156
  taskTerms,
@@ -1284,4 +2159,4 @@ export {
1284
2159
  namedSegments,
1285
2160
  loadProjectArchitectureMap
1286
2161
  };
1287
- //# sourceMappingURL=chunk-TLWGZVPC.js.map
2162
+ //# sourceMappingURL=chunk-UR3DF74I.js.map