@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.
- package/dist/{chunk-IBLJOHJL.js → chunk-3QGW53GD.js} +2 -2
- package/dist/{chunk-NERKZSSE.js → chunk-5EDGAZQN.js} +438 -68
- package/dist/chunk-5EDGAZQN.js.map +1 -0
- package/dist/{chunk-JFDRBPYF.js → chunk-6JCKMIQY.js} +2 -2
- package/dist/{chunk-2XDPKFUW.js → chunk-AK2UAE32.js} +46 -20
- package/dist/chunk-AK2UAE32.js.map +1 -0
- package/dist/{chunk-GDJH7XJC.js → chunk-AL6VBTNA.js} +77 -3
- package/dist/chunk-AL6VBTNA.js.map +1 -0
- package/dist/{chunk-QBDJXGG5.js → chunk-AQHPEJR6.js} +93 -22
- package/dist/chunk-AQHPEJR6.js.map +1 -0
- package/dist/{chunk-OWWCDRXW.js → chunk-BQM7FJ7D.js} +6 -6
- package/dist/chunk-BQM7FJ7D.js.map +1 -0
- package/dist/{chunk-XBJQKCLU.js → chunk-CAEPHLHC.js} +13 -9
- package/dist/chunk-CAEPHLHC.js.map +1 -0
- package/dist/chunk-CG5B3LYG.js +66 -0
- package/dist/chunk-CG5B3LYG.js.map +1 -0
- package/dist/{chunk-IHQM2ZDV.js → chunk-DFV2SK6C.js} +1 -1
- package/dist/{chunk-IHQM2ZDV.js.map → chunk-DFV2SK6C.js.map} +1 -1
- package/dist/{chunk-IMFUYEZA.js → chunk-EEUFU7KV.js} +81 -54
- package/dist/chunk-EEUFU7KV.js.map +1 -0
- package/dist/{chunk-5MQOE4J6.js → chunk-ESZXCZIN.js} +3 -3
- package/dist/{chunk-J2EAZ4MA.js → chunk-GKNYMNFM.js} +22 -25
- package/dist/chunk-GKNYMNFM.js.map +1 -0
- package/dist/{chunk-JPHJBCZB.js → chunk-HPOR6YXA.js} +3 -2
- package/dist/chunk-HPOR6YXA.js.map +1 -0
- package/dist/{chunk-S6VEG4S2.js → chunk-JUAFCLRB.js} +28 -5
- package/dist/chunk-JUAFCLRB.js.map +1 -0
- package/dist/{chunk-5KZY6OTG.js → chunk-KNR2P2UY.js} +33 -83
- package/dist/chunk-KNR2P2UY.js.map +1 -0
- package/dist/{chunk-B6CO63SQ.js → chunk-LCSBL5TT.js} +21 -9
- package/dist/chunk-LCSBL5TT.js.map +1 -0
- package/dist/{chunk-5FW3PMNP.js → chunk-MSUAVR3R.js} +76 -11
- package/dist/chunk-MSUAVR3R.js.map +1 -0
- package/dist/{chunk-RAJJSCIP.js → chunk-SFMIMLXR.js} +2 -2
- package/dist/chunk-TGFTKWOR.js +16 -0
- package/dist/chunk-TGFTKWOR.js.map +1 -0
- package/dist/chunk-TUR6YKEE.js +55 -0
- package/dist/chunk-TUR6YKEE.js.map +1 -0
- package/dist/chunk-Y7D7PUBE.js +321 -0
- package/dist/chunk-Y7D7PUBE.js.map +1 -0
- package/dist/{chunk-R7CD6EXS.js → chunk-YJIA2MFL.js} +572 -377
- package/dist/chunk-YJIA2MFL.js.map +1 -0
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{cloud-7WM3YLI4.js → cloud-LXZUR3U2.js} +3 -3
- package/dist/{codebuddy-B_pCaQrV.d.ts → codebuddy-DO7ZhoWn.d.ts} +15 -1
- package/dist/config-GEZFLXK6.js +12 -0
- package/dist/index.d.ts +46 -3
- package/dist/index.js +22 -19
- package/dist/langgraph/index.d.ts +1 -1
- package/dist/lean-command-CWJAVQ5G.js +18 -0
- package/dist/{local-5MZURLCZ.js → local-ST4WMRCG.js} +3 -3
- package/dist/{main-MTBQZGJV.js → main-AHNGHC4L.js} +213 -183
- package/dist/main-AHNGHC4L.js.map +1 -0
- package/dist/{memory-KIC5FJVW.js → memory-TGHVTBME.js} +9 -8
- package/dist/memory-TGHVTBME.js.map +1 -0
- package/dist/operations-7BTAAUHF.js +26 -0
- package/dist/pretool-command-3SXQSMAO.js +13 -0
- package/dist/{rerank-5QZ3PNUW.js → rerank-IQAEWLQR.js} +6 -5
- package/dist/rerank-IQAEWLQR.js.map +1 -0
- package/dist/{runtime-CUBOOPZO.js → runtime-E7JQZ2BZ.js} +14 -11
- package/dist/summaries-XV3JGGCC.js +20 -0
- package/dist/{zvec-MSRKT7IV.js → zvec-KP4KNB7Y.js} +10 -3
- package/package.json +1 -1
- package/src/db/migrations/0006_fact_idempotency_key.sql +2 -0
- package/src/db/migrations/meta/0006_snapshot.json +1258 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/dist/chunk-2XDPKFUW.js.map +0 -1
- package/dist/chunk-5FW3PMNP.js.map +0 -1
- package/dist/chunk-5KZY6OTG.js.map +0 -1
- package/dist/chunk-B6CO63SQ.js.map +0 -1
- package/dist/chunk-GDJH7XJC.js.map +0 -1
- package/dist/chunk-IMFUYEZA.js.map +0 -1
- package/dist/chunk-J2EAZ4MA.js.map +0 -1
- package/dist/chunk-JPHJBCZB.js.map +0 -1
- package/dist/chunk-NERKZSSE.js.map +0 -1
- package/dist/chunk-OWWCDRXW.js.map +0 -1
- package/dist/chunk-PQK3GKZH.js +0 -98
- package/dist/chunk-PQK3GKZH.js.map +0 -1
- package/dist/chunk-QBDJXGG5.js.map +0 -1
- package/dist/chunk-R7CD6EXS.js.map +0 -1
- package/dist/chunk-S6VEG4S2.js.map +0 -1
- package/dist/chunk-XBJQKCLU.js.map +0 -1
- package/dist/config-DEVHUDIU.js +0 -11
- package/dist/lean-command-HTQCPH76.js +0 -15
- package/dist/main-MTBQZGJV.js.map +0 -1
- package/dist/memory-KIC5FJVW.js.map +0 -1
- package/dist/operations-POCHY3WR.js +0 -25
- package/dist/pretool-command-AX6JXP25.js +0 -10
- package/dist/rerank-5QZ3PNUW.js.map +0 -1
- package/dist/summaries-SB5L2TSW.js +0 -18
- /package/dist/{chunk-IBLJOHJL.js.map → chunk-3QGW53GD.js.map} +0 -0
- /package/dist/{chunk-JFDRBPYF.js.map → chunk-6JCKMIQY.js.map} +0 -0
- /package/dist/{chunk-5MQOE4J6.js.map → chunk-ESZXCZIN.js.map} +0 -0
- /package/dist/{chunk-RAJJSCIP.js.map → chunk-SFMIMLXR.js.map} +0 -0
- /package/dist/{cloud-7WM3YLI4.js.map → cloud-LXZUR3U2.js.map} +0 -0
- /package/dist/{config-DEVHUDIU.js.map → config-GEZFLXK6.js.map} +0 -0
- /package/dist/{lean-command-HTQCPH76.js.map → lean-command-CWJAVQ5G.js.map} +0 -0
- /package/dist/{local-5MZURLCZ.js.map → local-ST4WMRCG.js.map} +0 -0
- /package/dist/{operations-POCHY3WR.js.map → operations-7BTAAUHF.js.map} +0 -0
- /package/dist/{pretool-command-AX6JXP25.js.map → pretool-command-3SXQSMAO.js.map} +0 -0
- /package/dist/{runtime-CUBOOPZO.js.map → runtime-E7JQZ2BZ.js.map} +0 -0
- /package/dist/{summaries-SB5L2TSW.js.map → summaries-XV3JGGCC.js.map} +0 -0
- /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-
|
|
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-
|
|
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
|
|
33
|
-
|
|
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:
|
|
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
|
|
70
|
-
|
|
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:
|
|
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 =
|
|
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("\
|
|
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.
|
|
476
|
-
|
|
527
|
+
child.stdout.setEncoding("utf8");
|
|
528
|
+
child.stdout.on("data", (chunk2) => {
|
|
529
|
+
stdout += String(chunk2);
|
|
477
530
|
});
|
|
478
|
-
child.stderr.on("data", (
|
|
479
|
-
stderr += String(
|
|
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(`..${
|
|
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
|
|
819
|
-
import { isAbsolute as isAbsolute3, join as
|
|
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
|
|
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(`..${
|
|
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 =
|
|
1206
|
+
const absolute = join8(repositoryRoot, path);
|
|
1034
1207
|
if ((await stat4(absolute)).size > MAX_EVIDENCE_BYTES) return null;
|
|
1035
|
-
const source = await
|
|
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-
|
|
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-
|
|
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
|
|
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) =>
|
|
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
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
1471
|
-
|
|
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 [
|
|
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 =
|
|
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(
|
|
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-
|
|
1948
|
+
//# sourceMappingURL=chunk-5EDGAZQN.js.map
|