@orquentra/codebuddy 2.6.2 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/{chunk-IBLJOHJL.js → chunk-3QGW53GD.js} +2 -2
  2. package/dist/{chunk-NERKZSSE.js → chunk-5EDGAZQN.js} +438 -68
  3. package/dist/chunk-5EDGAZQN.js.map +1 -0
  4. package/dist/{chunk-JFDRBPYF.js → chunk-6JCKMIQY.js} +2 -2
  5. package/dist/{chunk-2XDPKFUW.js → chunk-AK2UAE32.js} +46 -20
  6. package/dist/chunk-AK2UAE32.js.map +1 -0
  7. package/dist/{chunk-GDJH7XJC.js → chunk-AL6VBTNA.js} +77 -3
  8. package/dist/chunk-AL6VBTNA.js.map +1 -0
  9. package/dist/{chunk-QBDJXGG5.js → chunk-AQHPEJR6.js} +93 -22
  10. package/dist/chunk-AQHPEJR6.js.map +1 -0
  11. package/dist/{chunk-OWWCDRXW.js → chunk-BQM7FJ7D.js} +6 -6
  12. package/dist/chunk-BQM7FJ7D.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-IMFUYEZA.js → chunk-EEUFU7KV.js} +81 -54
  20. package/dist/chunk-EEUFU7KV.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-JPHJBCZB.js → chunk-HPOR6YXA.js} +3 -2
  25. package/dist/chunk-HPOR6YXA.js.map +1 -0
  26. package/dist/{chunk-S6VEG4S2.js → chunk-JUAFCLRB.js} +28 -5
  27. package/dist/chunk-JUAFCLRB.js.map +1 -0
  28. package/dist/{chunk-5KZY6OTG.js → chunk-KNR2P2UY.js} +33 -83
  29. package/dist/chunk-KNR2P2UY.js.map +1 -0
  30. package/dist/{chunk-B6CO63SQ.js → chunk-LCSBL5TT.js} +21 -9
  31. package/dist/chunk-LCSBL5TT.js.map +1 -0
  32. package/dist/{chunk-5FW3PMNP.js → chunk-MSUAVR3R.js} +76 -11
  33. package/dist/chunk-MSUAVR3R.js.map +1 -0
  34. package/dist/{chunk-RAJJSCIP.js → chunk-SFMIMLXR.js} +2 -2
  35. package/dist/chunk-TGFTKWOR.js +16 -0
  36. package/dist/chunk-TGFTKWOR.js.map +1 -0
  37. package/dist/chunk-TUR6YKEE.js +55 -0
  38. package/dist/chunk-TUR6YKEE.js.map +1 -0
  39. package/dist/chunk-Y7D7PUBE.js +321 -0
  40. package/dist/chunk-Y7D7PUBE.js.map +1 -0
  41. package/dist/{chunk-R7CD6EXS.js → chunk-YJIA2MFL.js} +572 -377
  42. package/dist/chunk-YJIA2MFL.js.map +1 -0
  43. package/dist/cli/index.js +5 -5
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/{cloud-7WM3YLI4.js → cloud-LXZUR3U2.js} +3 -3
  46. package/dist/{codebuddy-B_pCaQrV.d.ts → codebuddy-DO7ZhoWn.d.ts} +15 -1
  47. package/dist/config-GEZFLXK6.js +12 -0
  48. package/dist/index.d.ts +46 -3
  49. package/dist/index.js +22 -19
  50. package/dist/langgraph/index.d.ts +1 -1
  51. package/dist/lean-command-CWJAVQ5G.js +18 -0
  52. package/dist/{local-5MZURLCZ.js → local-ST4WMRCG.js} +3 -3
  53. package/dist/{main-MTBQZGJV.js → main-AHNGHC4L.js} +213 -183
  54. package/dist/main-AHNGHC4L.js.map +1 -0
  55. package/dist/{memory-KIC5FJVW.js → memory-TGHVTBME.js} +9 -8
  56. package/dist/memory-TGHVTBME.js.map +1 -0
  57. package/dist/operations-7BTAAUHF.js +26 -0
  58. package/dist/pretool-command-3SXQSMAO.js +13 -0
  59. package/dist/{rerank-5QZ3PNUW.js → rerank-IQAEWLQR.js} +6 -5
  60. package/dist/rerank-IQAEWLQR.js.map +1 -0
  61. package/dist/{runtime-CUBOOPZO.js → runtime-E7JQZ2BZ.js} +14 -11
  62. package/dist/summaries-XV3JGGCC.js +20 -0
  63. package/dist/{zvec-MSRKT7IV.js → zvec-KP4KNB7Y.js} +10 -3
  64. package/package.json +1 -1
  65. package/src/db/migrations/0006_fact_idempotency_key.sql +2 -0
  66. package/src/db/migrations/meta/0006_snapshot.json +1258 -0
  67. package/src/db/migrations/meta/_journal.json +7 -0
  68. package/dist/chunk-2XDPKFUW.js.map +0 -1
  69. package/dist/chunk-5FW3PMNP.js.map +0 -1
  70. package/dist/chunk-5KZY6OTG.js.map +0 -1
  71. package/dist/chunk-B6CO63SQ.js.map +0 -1
  72. package/dist/chunk-GDJH7XJC.js.map +0 -1
  73. package/dist/chunk-IMFUYEZA.js.map +0 -1
  74. package/dist/chunk-J2EAZ4MA.js.map +0 -1
  75. package/dist/chunk-JPHJBCZB.js.map +0 -1
  76. package/dist/chunk-NERKZSSE.js.map +0 -1
  77. package/dist/chunk-OWWCDRXW.js.map +0 -1
  78. package/dist/chunk-PQK3GKZH.js +0 -98
  79. package/dist/chunk-PQK3GKZH.js.map +0 -1
  80. package/dist/chunk-QBDJXGG5.js.map +0 -1
  81. package/dist/chunk-R7CD6EXS.js.map +0 -1
  82. package/dist/chunk-S6VEG4S2.js.map +0 -1
  83. package/dist/chunk-XBJQKCLU.js.map +0 -1
  84. package/dist/config-DEVHUDIU.js +0 -11
  85. package/dist/lean-command-HTQCPH76.js +0 -15
  86. package/dist/main-MTBQZGJV.js.map +0 -1
  87. package/dist/memory-KIC5FJVW.js.map +0 -1
  88. package/dist/operations-POCHY3WR.js +0 -25
  89. package/dist/pretool-command-AX6JXP25.js +0 -10
  90. package/dist/rerank-5QZ3PNUW.js.map +0 -1
  91. package/dist/summaries-SB5L2TSW.js +0 -18
  92. /package/dist/{chunk-IBLJOHJL.js.map → chunk-3QGW53GD.js.map} +0 -0
  93. /package/dist/{chunk-JFDRBPYF.js.map → chunk-6JCKMIQY.js.map} +0 -0
  94. /package/dist/{chunk-5MQOE4J6.js.map → chunk-ESZXCZIN.js.map} +0 -0
  95. /package/dist/{chunk-RAJJSCIP.js.map → chunk-SFMIMLXR.js.map} +0 -0
  96. /package/dist/{cloud-7WM3YLI4.js.map → cloud-LXZUR3U2.js.map} +0 -0
  97. /package/dist/{config-DEVHUDIU.js.map → config-GEZFLXK6.js.map} +0 -0
  98. /package/dist/{lean-command-HTQCPH76.js.map → lean-command-CWJAVQ5G.js.map} +0 -0
  99. /package/dist/{local-5MZURLCZ.js.map → local-ST4WMRCG.js.map} +0 -0
  100. /package/dist/{operations-POCHY3WR.js.map → operations-7BTAAUHF.js.map} +0 -0
  101. /package/dist/{pretool-command-AX6JXP25.js.map → pretool-command-3SXQSMAO.js.map} +0 -0
  102. /package/dist/{runtime-CUBOOPZO.js.map → runtime-E7JQZ2BZ.js.map} +0 -0
  103. /package/dist/{summaries-SB5L2TSW.js.map → summaries-XV3JGGCC.js.map} +0 -0
  104. /package/dist/{zvec-MSRKT7IV.js.map → zvec-KP4KNB7Y.js.map} +0 -0
@@ -1,24 +1,38 @@
1
1
  import {
2
+ extractPythonImports,
2
3
  extractSymbolTable,
3
4
  supportsSymbols
4
- } from "./chunk-PQK3GKZH.js";
5
+ } from "./chunk-Y7D7PUBE.js";
6
+ import {
7
+ isCredentialPath
8
+ } from "./chunk-AL6VBTNA.js";
9
+ import {
10
+ LOCAL_EMBEDDING_MODEL
11
+ } from "./chunk-HPOR6YXA.js";
12
+ import {
13
+ readConfigFile
14
+ } from "./chunk-KNR2P2UY.js";
5
15
  import {
6
16
  assertNoSymlinkBetween,
7
- readConfigFile,
8
17
  writeAtomic
9
- } from "./chunk-5KZY6OTG.js";
18
+ } from "./chunk-CG5B3LYG.js";
10
19
 
11
20
  // src/map/indexer.ts
12
21
  import { readdir, readFile, stat } from "fs/promises";
13
22
  import { dirname, extname, join, relative, resolve } from "path";
14
- var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
23
+ var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"]);
15
24
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
16
25
  ".git",
17
26
  "node_modules",
18
27
  "dist",
19
28
  "build",
20
29
  "coverage",
21
- ".codebuddy"
30
+ ".codebuddy",
31
+ // Python virtualenvs and caches would otherwise map site-packages.
32
+ ".venv",
33
+ "venv",
34
+ ".tox",
35
+ "__pycache__"
22
36
  ]);
23
37
  async function buildArchitectureMap(repositoryRoot = process.cwd()) {
24
38
  const root = resolve(repositoryRoot);
@@ -29,18 +43,12 @@ async function buildArchitectureMap(repositoryRoot = process.cwd()) {
29
43
  for (const absolutePath of files) {
30
44
  const source = await readFile(absolutePath, "utf8");
31
45
  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
- }
46
+ const resolvedImports = moduleImports(root, from, source, fileSet);
47
+ edges.push(...resolvedImports.map(({ to, kind }) => ({ from, to, kind })));
40
48
  modules.push({
41
49
  path: from,
42
- language: extname(absolutePath).includes("ts") ? "ts" : "js",
43
- imports: [...new Set(resolvedImports)].sort()
50
+ language: moduleLanguage(from),
51
+ imports: [...new Set(resolvedImports.map(({ to }) => to))].sort()
44
52
  });
45
53
  }
46
54
  return {
@@ -66,18 +74,12 @@ async function updateArchitectureMap(repositoryRoot, sourcePaths, previous, chan
66
74
  for (const path of normalizedPaths) {
67
75
  if (!changed.has(path)) continue;
68
76
  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
+ const resolvedImports = moduleImports(root, path, source, fileSet);
78
+ edges.push(...resolvedImports.map(({ to, kind }) => ({ from: path, to, kind })));
77
79
  modules.push({
78
80
  path,
79
- language: extname(path).includes("ts") ? "ts" : "js",
80
- imports: [...new Set(resolvedImports)].sort()
81
+ language: moduleLanguage(path),
82
+ imports: [...new Set(resolvedImports.map(({ to }) => to))].sort()
81
83
  });
82
84
  }
83
85
  return {
@@ -136,6 +138,48 @@ async function walk(directory, files) {
136
138
  files.push(absolute);
137
139
  }
138
140
  }
141
+ function moduleLanguage(path) {
142
+ const extension = extname(path);
143
+ if (extension === ".py") return "py";
144
+ return extension.includes("ts") ? "ts" : "js";
145
+ }
146
+ function moduleImports(root, from, source, fileSet) {
147
+ if (extname(from) === ".py") {
148
+ return pythonImports(from, source, fileSet).map((to) => ({ to, kind: "import" }));
149
+ }
150
+ return extractImports(source).flatMap((specifier) => {
151
+ const to = resolveImport(root, from, specifier.value, fileSet);
152
+ return to ? [{ to, kind: specifier.kind }] : [];
153
+ });
154
+ }
155
+ var PYTHON_ROOTS = ["", "src/"];
156
+ function pythonImports(from, source, fileSet) {
157
+ const pick = (...candidates) => candidates.find((candidate) => fileSet.has(candidate)) ?? null;
158
+ const moduleFile = (base) => pick(`${base}.py`, `${base}/__init__.py`);
159
+ const packageDir = from.split("/").slice(0, -1);
160
+ const targets = [];
161
+ for (const statement of extractPythonImports(source)) {
162
+ const parts = statement.module ? statement.module.split(".") : [];
163
+ let bases;
164
+ if (statement.level > 0) {
165
+ const up = statement.level - 1;
166
+ if (up > packageDir.length) continue;
167
+ bases = [[...packageDir.slice(0, packageDir.length - up), ...parts].join("/")];
168
+ } else {
169
+ bases = PYTHON_ROOTS.map((prefix) => `${prefix}${parts.join("/")}`);
170
+ }
171
+ for (const base of bases) {
172
+ const prefix = base ? `${base}/` : "";
173
+ const module = parts.length > 0 ? moduleFile(base) : pick(`${prefix}__init__.py`);
174
+ const submodules = statement.names.flatMap((name) => moduleFile(`${prefix}${name}`) ?? []);
175
+ if (!module && submodules.length === 0) continue;
176
+ targets.push(...submodules);
177
+ if (module && submodules.length < Math.max(1, statement.names.length)) targets.push(module);
178
+ break;
179
+ }
180
+ }
181
+ return [...new Set(targets)].filter((target) => target !== from);
182
+ }
139
183
  function extractImports(source) {
140
184
  const imports = [];
141
185
  const stripped = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1 ");
@@ -192,12 +236,12 @@ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
192
236
  import { z } from "zod";
193
237
 
194
238
  // src/indexer/types.ts
195
- var INDEX_MANIFEST_VERSION = 2;
239
+ var INDEX_MANIFEST_VERSION = 3;
196
240
 
197
241
  // src/indexer/store.ts
198
242
  var symbolSpanSchema = z.object({
199
243
  name: z.string(),
200
- kind: z.enum(["function", "method", "class", "interface", "type", "enum", "const"]),
244
+ kind: z.enum(["function", "method", "class", "interface", "type", "enum", "const", "heading"]),
201
245
  startLine: z.number().int().nonnegative(),
202
246
  endLine: z.number().int().nonnegative(),
203
247
  signature: z.string(),
@@ -255,7 +299,7 @@ function emptyManifest() {
255
299
 
256
300
  // src/map/cache.ts
257
301
  var CACHE_VERSION = 1;
258
- var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
302
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"]);
259
303
  async function loadArchitectureMap(repositoryRoot = process.cwd(), suppliedManifest) {
260
304
  const root = resolve3(repositoryRoot);
261
305
  const manifest = suppliedManifest ?? await new IndexStore(root).read();
@@ -331,7 +375,7 @@ function sourceHashes(manifest) {
331
375
  // src/indexer/scan.ts
332
376
  import { spawn } from "child_process";
333
377
  import { readdir as readdir2 } from "fs/promises";
334
- import { extname as extname4, join as join4, relative as relative2 } from "path";
378
+ import { extname as extname4, join as join4, relative as relative2, sep } from "path";
335
379
 
336
380
  // src/indexer/summary.ts
337
381
  import { extname as extname3 } from "path";
@@ -345,6 +389,7 @@ var LANGUAGE_BY_EXTENSION = {
345
389
  ".json": "json",
346
390
  ".md": "md",
347
391
  ".mdx": "md",
392
+ ".rst": "rst",
348
393
  ".yaml": "yaml",
349
394
  ".yml": "yaml",
350
395
  ".toml": "toml",
@@ -371,7 +416,7 @@ function languageFor(path) {
371
416
  function summarizeSource(path, content) {
372
417
  const language = languageFor(path);
373
418
  const lines = countLines(content);
374
- const symbols = CODE_LANGUAGES.has(language) ? extractSymbols(content) : [];
419
+ const symbols = CODE_LANGUAGES.has(language) ? extractSymbols(content) : language === "py" ? pythonPublicNames(content) : [];
375
420
  const summary = composeSummary(language, lines, symbols, content);
376
421
  return { language, lines, symbols, summary };
377
422
  }
@@ -398,6 +443,9 @@ function extractSymbols(content) {
398
443
  }
399
444
  return names.slice(0, SYMBOL_LIMIT);
400
445
  }
446
+ function pythonPublicNames(content) {
447
+ return extractSymbolTable(content, "x.py").filter((symbol) => symbol.exported && symbol.kind !== "method").map((symbol) => symbol.name).slice(0, SYMBOL_LIMIT);
448
+ }
401
449
  function composeSummary(language, lines, symbols, content) {
402
450
  const head = `${language} \u2022 ${lines} line${lines === 1 ? "" : "s"}`;
403
451
  if (symbols.length > 0) return `${head} \u2022 exports ${symbols.join(", ")}`;
@@ -433,7 +481,10 @@ var FALLBACK_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
433
481
  "graphify-out",
434
482
  ".next",
435
483
  ".turbo",
436
- "out"
484
+ "out",
485
+ ".venv",
486
+ "venv",
487
+ "__pycache__"
437
488
  ]);
438
489
  async function listIndexableFiles(repositoryRoot) {
439
490
  const tracked = await gitListFiles(repositoryRoot);
@@ -443,12 +494,13 @@ async function listIndexableFiles(repositoryRoot) {
443
494
  async function gitListFiles(repositoryRoot) {
444
495
  const result = await runGit(repositoryRoot, [
445
496
  "ls-files",
497
+ "-z",
446
498
  "--cached",
447
499
  "--others",
448
500
  "--exclude-standard"
449
501
  ]);
450
502
  if (result.code !== 0) return null;
451
- return result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
503
+ return result.stdout.split("\0").filter(Boolean);
452
504
  }
453
505
  async function walkFiles(root) {
454
506
  const files = [];
@@ -472,18 +524,19 @@ function runGit(cwd, args) {
472
524
  const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
473
525
  let stdout = "";
474
526
  let stderr = "";
475
- child.stdout.on("data", (chunk) => {
476
- stdout += String(chunk);
527
+ child.stdout.setEncoding("utf8");
528
+ child.stdout.on("data", (chunk2) => {
529
+ stdout += String(chunk2);
477
530
  });
478
- child.stderr.on("data", (chunk) => {
479
- stderr += String(chunk);
531
+ child.stderr.on("data", (chunk2) => {
532
+ stderr += String(chunk2);
480
533
  });
481
534
  child.on("error", () => resolveResult({ code: -1, stdout: "", stderr: "git unavailable" }));
482
535
  child.on("close", (code) => resolveResult({ code: code ?? 0, stdout, stderr }));
483
536
  });
484
537
  }
485
538
  function normalize2(path) {
486
- return path.replace(/\\/g, "/").replace(/^\.\//, "");
539
+ return (sep === "\\" ? path.replace(/\\/g, "/") : path).replace(/^\.\//, "");
487
540
  }
488
541
 
489
542
  // src/indexer/engine.ts
@@ -556,7 +609,7 @@ async function runIndexPass(options) {
556
609
  continue;
557
610
  }
558
611
  const { language, lines, symbols, summary } = summarizeSource(path, content);
559
- const symbolTable = supportsSymbols(path) ? extractSymbolTable(content) : [];
612
+ const symbolTable = supportsSymbols(path) ? extractSymbolTable(content, path) : [];
560
613
  entries[path] = {
561
614
  path,
562
615
  hash,
@@ -622,7 +675,7 @@ function sortEntries(entries) {
622
675
  import { execFile } from "child_process";
623
676
  import { mkdir as mkdir3, mkdtemp, readFile as readFile6, rm, stat as stat3, utimes } from "fs/promises";
624
677
  import { tmpdir } from "os";
625
- import { dirname as dirname4, join as join6, relative as relative3, resolve as resolve5, sep } from "path";
678
+ import { dirname as dirname4, join as join6, relative as relative3, resolve as resolve5, sep as sep2 } from "path";
626
679
  import { promisify } from "util";
627
680
 
628
681
  // src/map/graph-freshness.ts
@@ -788,7 +841,7 @@ async function ignoreGeneratedGraphDirectory(directory) {
788
841
  function resolveGraphPath(repositoryRoot, graphPath) {
789
842
  const absolute = resolve5(repositoryRoot, graphPath);
790
843
  const relativePath = relative3(repositoryRoot, absolute);
791
- if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`)) {
844
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep2}`)) {
792
845
  throw new Error(`Graph path must be inside the project root: ${graphPath}`);
793
846
  }
794
847
  return absolute;
@@ -814,9 +867,13 @@ function arrayLength(value) {
814
867
  return Array.isArray(value) ? value.length : 0;
815
868
  }
816
869
 
870
+ // src/context/file-vectors.ts
871
+ import { mkdir as mkdir5, readFile as readFile10 } from "fs/promises";
872
+ import { dirname as dirname6, join as join9 } from "path";
873
+
817
874
  // 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";
875
+ import { readFile as readFile9, realpath, stat as stat4 } from "fs/promises";
876
+ import { isAbsolute as isAbsolute3, join as join8, relative as relative6 } from "path";
820
877
 
821
878
  // src/map/graphify.ts
822
879
  import { readFile as readFile7 } from "fs/promises";
@@ -956,11 +1013,12 @@ function stringValue(value) {
956
1013
  return typeof value === "string" && value.length > 0 ? value : null;
957
1014
  }
958
1015
  function languageFor2(path) {
1016
+ if (/\.py$/.test(path)) return "py";
959
1017
  return /\.tsx?$/.test(path) ? "ts" : "js";
960
1018
  }
961
1019
 
962
1020
  // src/context/anchors.ts
963
- import { isAbsolute as isAbsolute2, relative as relative5, resolve as resolve7, sep as sep2 } from "path";
1021
+ import { isAbsolute as isAbsolute2, relative as relative5, resolve as resolve7, sep as sep3 } from "path";
964
1022
  var MAX_ANCHORS = 3;
965
1023
  var MAX_ERROR_SEARCH_FILES = 2e3;
966
1024
  var JS_FRAME = /\bat\s+(?:[^\s()]+\s+\()?([^\s()]+?):(\d+):\d+\)?/g;
@@ -998,7 +1056,7 @@ async function extractTaskAnchors(repositoryRoot, task, paths) {
998
1056
  function toIndexedPath(root, raw, paths) {
999
1057
  const absolute = isAbsolute2(raw) ? raw : resolve7(root, raw);
1000
1058
  const rel = relative5(root, absolute);
1001
- if (rel === "" || rel.startsWith(`..${sep2}`) || rel === ".." || isAbsolute2(rel)) return null;
1059
+ if (rel === "" || rel.startsWith(`..${sep3}`) || rel === ".." || isAbsolute2(rel)) return null;
1002
1060
  const normalized = rel.replace(/\\/g, "/");
1003
1061
  return paths.has(normalized) ? normalized : null;
1004
1062
  }
@@ -1023,16 +1081,131 @@ async function errorAnchors(root, task, paths) {
1023
1081
  return matches.flatMap((found) => found.length === 1 ? found : []);
1024
1082
  }
1025
1083
 
1084
+ // src/context/content-index.ts
1085
+ import { mkdir as mkdir4, readFile as readFile8 } from "fs/promises";
1086
+ import { dirname as dirname5, join as join7 } from "path";
1087
+ var PASSAGE_LINES = 40;
1088
+ var K1 = 1.2;
1089
+ var B = 0.75;
1090
+ var MIN_TERMS = 2;
1091
+ var MIN_SHARE = 0.5;
1092
+ var MIN_IDF_COVERAGE = 0.3;
1093
+ async function rankContent(repositoryRoot, manifest, terms, wantsTests, wantsDocs = false, wantsChangelog = false) {
1094
+ const none = { matches: [], idf: /* @__PURE__ */ new Map() };
1095
+ if (terms.length === 0) return none;
1096
+ const paths = Object.keys(manifest.entries).filter(
1097
+ (path) => (!NON_SOURCE_PATH.test(path) || wantsDocs && DOC_PATH.test(path) || wantsChangelog && CHANGELOG_PATH.test(path)) && (wantsTests || !TEST_PATH.test(path))
1098
+ );
1099
+ const cache = await loadCache(repositoryRoot);
1100
+ let changed = false;
1101
+ const files = {};
1102
+ await Promise.all(
1103
+ paths.map(async (path) => {
1104
+ const hash = manifest.entries[path]?.hash ?? "";
1105
+ const cached = cache.files[path];
1106
+ if (cached && cached.hash === hash) {
1107
+ files[path] = cached;
1108
+ return;
1109
+ }
1110
+ const source = await readBoundedSource(repositoryRoot, path);
1111
+ if (source === null) return;
1112
+ const counts = {};
1113
+ const words = splitWords(`${path} ${source}`);
1114
+ for (const word of words) counts[word] = (counts[word] ?? 0) + 1;
1115
+ const lines = source.split("\n");
1116
+ const passages = [];
1117
+ for (let start = 0; start === 0 || start + PASSAGE_LINES / 2 < lines.length; start += PASSAGE_LINES / 2) {
1118
+ passages.push([
1119
+ ...new Set(splitWords(lines.slice(start, start + PASSAGE_LINES).join("\n")))
1120
+ ]);
1121
+ }
1122
+ files[path] = { hash, length: words.length, counts, passages };
1123
+ changed = true;
1124
+ })
1125
+ );
1126
+ const retained = Object.fromEntries(
1127
+ Object.entries(cache.files).filter(
1128
+ ([path, file]) => !(path in files) && manifest.entries[path]?.hash === file.hash
1129
+ )
1130
+ );
1131
+ const kept = Object.keys(retained).length + Object.keys(files).length;
1132
+ if (changed || kept !== Object.keys(cache.files).length) {
1133
+ await saveCache(repositoryRoot, { version: 2, files: { ...retained, ...files } });
1134
+ }
1135
+ const documents = Object.entries(files);
1136
+ if (documents.length === 0) return none;
1137
+ const averageLength = documents.reduce((sum, [, file]) => sum + file.length, 0) / documents.length;
1138
+ const idf = new Map(
1139
+ terms.map((term) => {
1140
+ const df = documents.filter(([, file]) => (file.counts[term] ?? 0) > 0).length;
1141
+ return [term, Math.log(1 + (documents.length - df + 0.5) / (df + 0.5))];
1142
+ })
1143
+ );
1144
+ const totalIdf = [...idf.values()].reduce((sum, value) => sum + value, 0);
1145
+ const scored = documents.map(([path, file]) => {
1146
+ const fileName = new Set(splitWords(path.slice(path.lastIndexOf("/") + 1)));
1147
+ let score = 0;
1148
+ let covered = 0;
1149
+ for (const term of terms) {
1150
+ const tf = file.counts[term] ?? 0;
1151
+ if (tf === 0) continue;
1152
+ covered += idf.get(term) ?? 0;
1153
+ const saturation = tf * (K1 + 1) / (tf + K1 * (1 - B + B * file.length / averageLength));
1154
+ score += (idf.get(term) ?? 0) * saturation * (fileName.has(term) ? 1.5 : 1);
1155
+ }
1156
+ const named = terms.filter((term) => fileName.has(term));
1157
+ const matched = Math.max(
1158
+ named.length,
1159
+ ...file.passages.map(
1160
+ (words) => (/* @__PURE__ */ new Set([...named, ...terms.filter((term) => words.includes(term))])).size
1161
+ )
1162
+ );
1163
+ return { path, score, terms: matched, coverage: totalIdf > 0 ? covered / totalIdf : 0 };
1164
+ }).filter((match) => match.score > 0).sort((left, right) => right.score - left.score || left.path.localeCompare(right.path));
1165
+ const best = scored[0]?.score ?? 0;
1166
+ const needed = Math.min(MIN_TERMS, terms.length);
1167
+ const matches = scored.filter(
1168
+ (match) => match.terms >= needed && match.score >= MIN_SHARE * best && match.coverage >= MIN_IDF_COVERAGE
1169
+ ).map(({ path, score, terms: matched }) => ({ path, score, terms: matched }));
1170
+ return { matches, idf };
1171
+ }
1172
+ function idfCoverage(text, terms, idf) {
1173
+ const total = terms.reduce((sum, term) => sum + (idf.get(term) ?? 0), 0);
1174
+ if (total === 0) return 0;
1175
+ const words = new Set(splitWords(text));
1176
+ return terms.reduce((sum, term) => sum + (words.has(term) ? idf.get(term) ?? 0 : 0), 0) / total;
1177
+ }
1178
+ function cachePath(repositoryRoot) {
1179
+ return join7(repositoryRoot, ".codebuddy", "cache", "content-terms.json");
1180
+ }
1181
+ async function loadCache(repositoryRoot) {
1182
+ try {
1183
+ const parsed = JSON.parse(await readFile8(cachePath(repositoryRoot), "utf8"));
1184
+ if (parsed.version === 2 && parsed.files && typeof parsed.files === "object") return parsed;
1185
+ } catch {
1186
+ }
1187
+ return { version: 2, files: {} };
1188
+ }
1189
+ async function saveCache(repositoryRoot, cache) {
1190
+ try {
1191
+ const path = cachePath(repositoryRoot);
1192
+ await mkdir4(dirname5(path), { recursive: true });
1193
+ await writeAtomic(path, JSON.stringify(cache));
1194
+ } catch {
1195
+ }
1196
+ }
1197
+
1026
1198
  // src/context/discovery.ts
1027
1199
  var TASK_PATH_LIMIT = 10;
1028
1200
  var TASK_FILES = 3;
1029
1201
  var EXPANSION = { seeds: 2, files: 1 };
1030
1202
  var MAX_EVIDENCE_BYTES = 512e3;
1031
1203
  async function readBoundedSource(repositoryRoot, path) {
1204
+ if (isCredentialPath(path)) return null;
1032
1205
  try {
1033
- const absolute = join7(repositoryRoot, path);
1206
+ const absolute = join8(repositoryRoot, path);
1034
1207
  if ((await stat4(absolute)).size > MAX_EVIDENCE_BYTES) return null;
1035
- const source = await readFile8(absolute, "utf8");
1208
+ const source = await readFile9(absolute, "utf8");
1036
1209
  return source.includes("\0") ? null : source;
1037
1210
  } catch {
1038
1211
  return null;
@@ -1077,10 +1250,10 @@ async function discoverTaskPaths(repositoryRoot, task, manifest, mapSource, opti
1077
1250
  async function withLlmSummaries(repositoryRoot, manifest) {
1078
1251
  const entries = Object.values(manifest.entries);
1079
1252
  try {
1080
- const { resolveLlmConfig } = await import("./config-DEVHUDIU.js");
1253
+ const { resolveLlmConfig } = await import("./config-GEZFLXK6.js");
1081
1254
  const config = await resolveLlmConfig(repositoryRoot);
1082
1255
  if (!config.enabled || !config.features.summaries) return entries;
1083
- const { loadLlmSummaries } = await import("./summaries-SB5L2TSW.js");
1256
+ const { loadLlmSummaries } = await import("./summaries-XV3JGGCC.js");
1084
1257
  const summaries = await loadLlmSummaries(repositoryRoot, manifest);
1085
1258
  if (summaries.size === 0) return entries;
1086
1259
  return entries.map((entry) => {
@@ -1092,7 +1265,10 @@ async function withLlmSummaries(repositoryRoot, manifest) {
1092
1265
  }
1093
1266
  }
1094
1267
  async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options) {
1095
- const entries = await withLlmSummaries(repositoryRoot, manifest);
1268
+ const wantsChangelog = asksForChangelog(task);
1269
+ const entries = (await withLlmSummaries(repositoryRoot, manifest)).filter(
1270
+ (entry) => wantsChangelog || !CHANGELOG_PATH.test(entry.path)
1271
+ );
1096
1272
  const wantsTests = /\b(tests?|spec)\b/i.test(task);
1097
1273
  const identifiers = identifierTokens(task).filter((token) => CODE_IDENTIFIER.test(token));
1098
1274
  if (identifiers.length > 0) {
@@ -1100,7 +1276,10 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1100
1276
  path: entry.path,
1101
1277
  named: new Set(
1102
1278
  (entry.symbolTable ?? []).flatMap(
1103
- (symbol) => symbol.name.split(".").filter((segment) => identifiers.includes(segment))
1279
+ (symbol) => (
1280
+ // A doc heading naming an identifier is not its declaration.
1281
+ symbol.kind === "heading" ? [] : symbol.name.split(".").filter((segment) => identifiers.includes(segment))
1282
+ )
1104
1283
  )
1105
1284
  ).size
1106
1285
  })).filter((entry) => entry.named > 0).sort((left, right) => right.named - left.named || left.path.localeCompare(right.path)).map((entry) => entry.path);
@@ -1123,16 +1302,31 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1123
1302
  }
1124
1303
  const commands = await commandFiles(repositoryRoot, task, entries);
1125
1304
  const scored = scoreTaskPaths(entries, task);
1126
- const keyword = scored.filter((entry) => entry.score >= EVIDENCE_FLOOR.keywordScore).map((entry) => entry.path);
1305
+ const summaryKeyword = scored.filter((entry) => entry.score >= EVIDENCE_FLOOR.keywordScore).map((entry) => entry.path);
1306
+ const { matches: content, idf } = await rankContent(
1307
+ repositoryRoot,
1308
+ manifest,
1309
+ taskTerms(task),
1310
+ wantsTests,
1311
+ DOCS_TASK.test(task),
1312
+ wantsChangelog
1313
+ );
1314
+ const keyword = content.length > 0 ? fuseRankings([summaryKeyword, content.map((match) => match.path)]) : summaryKeyword;
1127
1315
  const weakKeyword = scored.length - keyword.length;
1128
- const semantic = options.semantic === false ? {
1316
+ const semantic = options.semantic === false && options.zvecDeadlineAt === void 0 ? {
1129
1317
  status: "disabled",
1130
1318
  paths: [],
1131
1319
  hits: [],
1132
1320
  reason: "semantic retrieval off",
1133
1321
  stale: 0,
1134
1322
  changed: 0
1135
- } : await import("./zvec-MSRKT7IV.js").then(({ zvecCandidates }) => zvecCandidates(repositoryRoot, task)).catch(
1323
+ } : await import("./zvec-KP4KNB7Y.js").then(
1324
+ ({ zvecCandidates }) => zvecCandidates(
1325
+ repositoryRoot,
1326
+ task,
1327
+ options.semantic === false && options.zvecDeadlineAt !== void 0 ? { deadlineAt: options.zvecDeadlineAt, requireServer: true } : {}
1328
+ )
1329
+ ).catch(
1136
1330
  () => ({
1137
1331
  status: "failed",
1138
1332
  paths: [],
@@ -1155,8 +1349,11 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1155
1349
  const vector = [];
1156
1350
  const partial = [];
1157
1351
  let uncorroborated = 0;
1352
+ let belowFloor = 0;
1353
+ const minScore = semantic.minScore ?? 0;
1158
1354
  for (const path of semantic.paths) {
1159
1355
  if (!wantsTests && TEST_PATH.test(path)) continue;
1356
+ if (!wantsChangelog && CHANGELOG_PATH.test(path)) continue;
1160
1357
  if (keyword.includes(path)) vector.push(path);
1161
1358
  else if (await hasMatchedPassageEvidence(
1162
1359
  repositoryRoot,
@@ -1166,6 +1363,10 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1166
1363
  manifest.entries[path]?.symbolTable ?? []
1167
1364
  )) {
1168
1365
  const source = await readBoundedSource(repositoryRoot, path) ?? "";
1366
+ if (idfCoverage(`${path} ${source}`, terms, idf) < minScore) {
1367
+ belowFloor++;
1368
+ continue;
1369
+ }
1169
1370
  if (countTerms(source, terms) >= EVIDENCE_FLOOR.vectorTerms) vector.push(path);
1170
1371
  else partial.push(path);
1171
1372
  if (!evidence.has(path)) evidence.set(path, 0);
@@ -1181,6 +1382,11 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1181
1382
  explain.push(
1182
1383
  `retrieval: keyword ranking fused with ${semantic.reason}` + (uncorroborated > 0 ? `; ${uncorroborated} uncorroborated vector file(s) dropped` : "")
1183
1384
  );
1385
+ if (belowFloor > 0) {
1386
+ explain.push(
1387
+ `abstention: ${belowFloor} vector-only file(s) below zvec.minScore ${minScore} (task IDF coverage of current source) dropped`
1388
+ );
1389
+ }
1184
1390
  }
1185
1391
  let symbols;
1186
1392
  const reranked = await llmRerank({
@@ -1199,7 +1405,8 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1199
1405
  ranked = reranked.ranked;
1200
1406
  symbols = reranked.symbols;
1201
1407
  }
1202
- if (!ranked.some((path) => !NON_SOURCE_PATH.test(path))) {
1408
+ const wantsDocs = DOCS_TASK.test(task);
1409
+ if (!ranked.some((path) => countsAsSource(path, task))) {
1203
1410
  explain.push(
1204
1411
  "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
1412
  );
@@ -1210,6 +1417,14 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1210
1417
  `abstention: ${weakKeyword} weak keyword candidate(s) below the evidence floor (score < ${EVIDENCE_FLOOR.keywordScore}) dropped`
1211
1418
  );
1212
1419
  }
1420
+ const vectors = options.semantic === false || reranked ? null : await rankFilesByVector(repositoryRoot, manifest, task);
1421
+ if (vectors && vectors.length > 0) {
1422
+ const doc = wantsDocs ? ranked.filter((path) => DOC_PATH.test(path)).slice(0, 1) : [];
1423
+ ranked = [...doc, ...vectors.map((match) => match.path)].slice(0, TASK_FILES);
1424
+ explain.push(
1425
+ `retrieval: file vectors ordered the files (best ${vectors[0]?.score.toFixed(2)})`
1426
+ );
1427
+ }
1213
1428
  const expansion = expandAlongImports(ranked, await mapSource, evidence, task);
1214
1429
  if (expansion.explain) explain.push(expansion.explain);
1215
1430
  if (commands.length > 0) {
@@ -1232,7 +1447,7 @@ var MIN_RERANK_MS = 200;
1232
1447
  async function llmRerank(input) {
1233
1448
  let config;
1234
1449
  try {
1235
- config = await (await import("./config-DEVHUDIU.js")).resolveLlmConfig(input.repositoryRoot);
1450
+ config = await (await import("./config-GEZFLXK6.js")).resolveLlmConfig(input.repositoryRoot);
1236
1451
  } catch {
1237
1452
  return null;
1238
1453
  }
@@ -1257,7 +1472,7 @@ async function llmRerank(input) {
1257
1472
  input.deadlineAt === void 0 ? config.timeoutMs : input.deadlineAt - Date.now()
1258
1473
  );
1259
1474
  if (deadlineMs < MIN_RERANK_MS) return skipped("no time left in the hook budget");
1260
- const { rerankCandidates } = await import("./rerank-5QZ3PNUW.js");
1475
+ const { rerankCandidates } = await import("./rerank-IQAEWLQR.js");
1261
1476
  const result = await rerankCandidates(
1262
1477
  input.repositoryRoot,
1263
1478
  config,
@@ -1329,7 +1544,7 @@ async function commandFiles(repositoryRoot, task, entries) {
1329
1544
  var COMMAND_DIR = /(^|\/)(cli|cmd|commands?)\//;
1330
1545
  async function packageBins(repositoryRoot) {
1331
1546
  try {
1332
- const manifest = JSON.parse(await readFile8(join7(repositoryRoot, "package.json"), "utf8"));
1547
+ const manifest = JSON.parse(await readFile9(join8(repositoryRoot, "package.json"), "utf8"));
1333
1548
  if (typeof manifest.bin === "string" && typeof manifest.name === "string") {
1334
1549
  return [manifest.name.slice(manifest.name.lastIndexOf("/") + 1)];
1335
1550
  }
@@ -1419,8 +1634,30 @@ async function hasMatchedPassageEvidence(repositoryRoot, path, hits, terms, symb
1419
1634
  );
1420
1635
  });
1421
1636
  }
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;
1637
+ var TEST_PATH = /(^|\/)(__tests__|__fixtures__|fixtures?|test|tests)\/|\.(test|spec)\.[a-z]+$|(^|\/)(test_[^/]*|[^/]*_test|conftest)\.py$/;
1638
+ var CHANGELOG_PATH = new RegExp(
1639
+ [
1640
+ "(?:CHANGES|CHANGELOG|HISTORY|NEWS|RELEASES?|RELEASE[-_]?NOTES)(?:[._-][^/]*)?",
1641
+ "[Cc]hange[-_]?[Ll]og[^/]*",
1642
+ "[Rr]elease[-_]?[Nn]otes[^/]*",
1643
+ "(?:[Cc]hanges|[Hh]istory|[Nn]ews|[Rr]eleases?)\\.(?:md|mdx|rst|txt|adoc|markdown)"
1644
+ ].map((name) => `(?:^|/)${name}$`).join("|")
1645
+ );
1646
+ function asksForChangelog(task) {
1647
+ 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);
1648
+ }
1649
+ var NON_SOURCE_BASE = /(^|\/)(docs?|\.github)\/|\.(md|mdx|rst|adoc|txt|ya?ml|json|lock)$|(^|\/)CHANGELOG/i;
1650
+ var NON_SOURCE_PATH = {
1651
+ test: (path) => NON_SOURCE_BASE.test(path) || CHANGELOG_PATH.test(path)
1652
+ };
1653
+ var PROSE_PATH = /(^|\/)docs?\/|\.(md|mdx|rst|txt)$/i;
1654
+ var DOC_PATH = {
1655
+ test: (path) => PROSE_PATH.test(path) && !CHANGELOG_PATH.test(path)
1656
+ };
1657
+ var DOCS_TASK = /\b(docs?|documentation|documented|guides?|tutorials?|readme|manual)\b/i;
1658
+ function countsAsSource(path, task) {
1659
+ return !NON_SOURCE_PATH.test(path) || DOCS_TASK.test(task) && DOC_PATH.test(path) || asksForChangelog(task) && CHANGELOG_PATH.test(path);
1660
+ }
1424
1661
  function scoreTaskPaths(entries, task) {
1425
1662
  const terms = taskTerms(task);
1426
1663
  if (terms.length === 0) return [];
@@ -1457,7 +1694,7 @@ function scoreTaskPaths(entries, task) {
1457
1694
  }
1458
1695
  }
1459
1696
  if (weighted === 0) return [];
1460
- if (NON_SOURCE_PATH.test(path)) {
1697
+ if (!countsAsSource(path, task)) {
1461
1698
  score *= 0.5;
1462
1699
  weighted *= 0.5;
1463
1700
  }
@@ -1467,12 +1704,22 @@ function scoreTaskPaths(entries, task) {
1467
1704
  );
1468
1705
  }
1469
1706
  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
- );
1707
+ return rawWords(text).map(foldPlural);
1708
+ }
1709
+ function rawWords(text) {
1710
+ return text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 2);
1711
+ }
1712
+ function foldPlural(word) {
1713
+ if (word.length <= 3 || !word.endsWith("s") || /(ss|us|is)$/.test(word)) return word;
1714
+ if (/(ss|sh|ch|x|z)es$/.test(word)) return word.slice(0, -2);
1715
+ return word.slice(0, -1);
1473
1716
  }
1474
1717
  function taskTerms(task) {
1475
- return [...new Set(splitWords(task).filter((term) => !TASK_STOPWORDS.has(term)))];
1718
+ return [
1719
+ ...new Set(
1720
+ rawWords(task).filter((word) => !TASK_STOPWORDS.has(word)).map(foldPlural).filter((term) => !TASK_STOPWORDS.has(term))
1721
+ )
1722
+ ];
1476
1723
  }
1477
1724
  function countTerms(text, terms) {
1478
1725
  if (terms.length === 0) return 0;
@@ -1492,7 +1739,7 @@ async function loadProjectArchitectureMap(repositoryRoot, manifestSource) {
1492
1739
  const builtIn = (await loadArchitectureMap(repositoryRoot, manifest)).map;
1493
1740
  if (config.graphify?.enabled && await graphifyGraphExists(repositoryRoot, config.graphify.graphPath)) {
1494
1741
  try {
1495
- const graphPath = join7(
1742
+ const graphPath = join8(
1496
1743
  repositoryRoot,
1497
1744
  config.graphify.graphPath ?? "graphify-out/graph.json"
1498
1745
  );
@@ -1505,7 +1752,7 @@ async function loadProjectArchitectureMap(repositoryRoot, manifestSource) {
1505
1752
  (await Promise.all(
1506
1753
  graph.modules.map(async (module) => {
1507
1754
  try {
1508
- const absolute = await realpath(join7(repositoryRoot, module.path));
1755
+ const absolute = await realpath(join8(repositoryRoot, module.path));
1509
1756
  const inside = relative6(await realpath(repositoryRoot), absolute);
1510
1757
  if (inside.startsWith("..") || isAbsolute3(inside)) return null;
1511
1758
  const source = await stat4(absolute);
@@ -1554,20 +1801,143 @@ async function loadProjectArchitectureMap(repositoryRoot, manifestSource) {
1554
1801
  return { ...builtIn, source: "imports" };
1555
1802
  }
1556
1803
 
1804
+ // src/context/file-vectors.ts
1805
+ var CHUNK_LINES = 40;
1806
+ var CHUNK_STEP = 30;
1807
+ var CHUNK_CHARS = 2e3;
1808
+ var BATCH = 16;
1809
+ var MODEL = `${LOCAL_EMBEDDING_MODEL.id}@${LOCAL_EMBEDDING_MODEL.revision}`;
1810
+ async function localEmbed(texts) {
1811
+ const { LocalModelProvider } = await import("./local-ST4WMRCG.js");
1812
+ return (await new LocalModelProvider().embed({ input: texts })).vectors;
1813
+ }
1814
+ async function refreshFileVectors(repositoryRoot, manifest, deadlineAt, embed = localEmbed) {
1815
+ const store = await loadStore(repositoryRoot);
1816
+ const eligible = eligiblePaths(manifest);
1817
+ const files = {};
1818
+ const todo = [];
1819
+ for (const path of eligible) {
1820
+ const cached = store.files[path];
1821
+ if (cached && cached.hash === manifest.entries[path]?.hash) files[path] = cached;
1822
+ else todo.push(path);
1823
+ }
1824
+ let embedded = 0;
1825
+ let lastSave = Date.now();
1826
+ for (const path of todo) {
1827
+ if (Date.now() >= deadlineAt) break;
1828
+ const source = await readBoundedSource(repositoryRoot, path);
1829
+ if (source === null) continue;
1830
+ const vectors = [];
1831
+ const chunks = chunk(path, source);
1832
+ for (let start = 0; start < chunks.length; start += BATCH) {
1833
+ for (const vector of await embed(chunks.slice(start, start + BATCH))) {
1834
+ vectors.push(encode(vector));
1835
+ }
1836
+ }
1837
+ files[path] = { hash: manifest.entries[path]?.hash ?? "", vectors };
1838
+ embedded += 1;
1839
+ if (Date.now() - lastSave > 3e4) {
1840
+ await saveStore(repositoryRoot, { version: 1, model: MODEL, files });
1841
+ lastSave = Date.now();
1842
+ }
1843
+ }
1844
+ await saveStore(repositoryRoot, { version: 1, model: MODEL, files });
1845
+ return {
1846
+ embedded,
1847
+ files: Object.keys(files).length,
1848
+ pending: eligible.length - Object.keys(files).length
1849
+ };
1850
+ }
1851
+ async function syncFileVectors(repositoryRoot, budgetMs) {
1852
+ const started = Date.now();
1853
+ try {
1854
+ const { manifest } = await loadFreshIndex(repositoryRoot);
1855
+ return await refreshFileVectors(repositoryRoot, manifest, started + budgetMs);
1856
+ } catch {
1857
+ return null;
1858
+ }
1859
+ }
1860
+ async function rankFilesByVector(repositoryRoot, manifest, task, embed = localEmbed) {
1861
+ if (process.env.CODEBUDDY_SEMANTIC === "0") return null;
1862
+ const store = await loadStore(repositoryRoot);
1863
+ const current = eligiblePaths(manifest).filter(
1864
+ (path) => store.files[path]?.hash === manifest.entries[path]?.hash
1865
+ );
1866
+ if (current.length === 0) return null;
1867
+ let query;
1868
+ try {
1869
+ const [vector] = await embed([task]);
1870
+ if (!vector) return null;
1871
+ query = vector;
1872
+ } catch {
1873
+ return null;
1874
+ }
1875
+ return current.map((path) => ({
1876
+ path,
1877
+ score: Math.max(...(store.files[path]?.vectors ?? []).map((v) => dot(decode(v), query)))
1878
+ })).sort((left, right) => right.score - left.score || left.path.localeCompare(right.path));
1879
+ }
1880
+ function eligiblePaths(manifest) {
1881
+ return Object.keys(manifest.entries).filter(
1882
+ (path) => !NON_SOURCE_PATH.test(path) && !TEST_PATH.test(path)
1883
+ );
1884
+ }
1885
+ function chunk(path, source) {
1886
+ const lines = source.split("\n");
1887
+ const chunks = [];
1888
+ for (let start = 0; start === 0 || start + 10 < lines.length; start += CHUNK_STEP) {
1889
+ chunks.push(
1890
+ `${path}
1891
+ ${lines.slice(start, start + CHUNK_LINES).join("\n")}`.slice(0, CHUNK_CHARS)
1892
+ );
1893
+ }
1894
+ return chunks;
1895
+ }
1896
+ function encode(vector) {
1897
+ return Buffer.from(Float32Array.from(vector).buffer).toString("base64");
1898
+ }
1899
+ function decode(text) {
1900
+ const bytes = Buffer.from(text, "base64");
1901
+ return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
1902
+ }
1903
+ function dot(left, right) {
1904
+ let sum = 0;
1905
+ for (let index = 0; index < right.length; index++)
1906
+ sum += (left[index] ?? 0) * (right[index] ?? 0);
1907
+ return sum;
1908
+ }
1909
+ function storePath(repositoryRoot) {
1910
+ return join9(repositoryRoot, ".codebuddy", "cache", "file-vectors.json");
1911
+ }
1912
+ async function loadStore(repositoryRoot) {
1913
+ try {
1914
+ const parsed = JSON.parse(await readFile10(storePath(repositoryRoot), "utf8"));
1915
+ if (parsed.version === 1 && parsed.model === MODEL && parsed.files) return parsed;
1916
+ } catch {
1917
+ }
1918
+ return { version: 1, model: MODEL, files: {} };
1919
+ }
1920
+ async function saveStore(repositoryRoot, store) {
1921
+ const path = storePath(repositoryRoot);
1922
+ await mkdir5(dirname6(path), { recursive: true });
1923
+ await writeAtomic(path, JSON.stringify(store));
1924
+ }
1925
+
1557
1926
  export {
1558
1927
  buildArchitectureMap,
1559
1928
  queryMap,
1560
1929
  neighbours,
1561
- loadArchitectureMap,
1562
1930
  indexRepository,
1563
1931
  loadFreshIndex,
1564
1932
  setupGraphify,
1565
1933
  generateGraphifyGraph,
1566
1934
  loadGraphifyMap,
1935
+ syncFileVectors,
1567
1936
  TASK_PATH_LIMIT,
1568
1937
  readBoundedSource,
1569
1938
  discoverTaskPaths,
1570
1939
  TEST_PATH,
1940
+ asksForChangelog,
1571
1941
  NON_SOURCE_PATH,
1572
1942
  taskTerms,
1573
1943
  countTerms,
@@ -1575,4 +1945,4 @@ export {
1575
1945
  namedSegments,
1576
1946
  loadProjectArchitectureMap
1577
1947
  };
1578
- //# sourceMappingURL=chunk-NERKZSSE.js.map
1948
+ //# sourceMappingURL=chunk-5EDGAZQN.js.map