@orquentra/codebuddy 0.0.0-stage → 2.4.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 (82) hide show
  1. package/ARCHITECTURE.md +269 -0
  2. package/CLAUDE_CODEX.md +433 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1118 -2
  5. package/WORKFLOW.md +152 -0
  6. package/dist/chunk-3ZELDCR3.js +204 -0
  7. package/dist/chunk-3ZELDCR3.js.map +1 -0
  8. package/dist/chunk-5OWZVZSO.js +476 -0
  9. package/dist/chunk-5OWZVZSO.js.map +1 -0
  10. package/dist/chunk-DSN5CEHO.js +175 -0
  11. package/dist/chunk-DSN5CEHO.js.map +1 -0
  12. package/dist/chunk-GDJH7XJC.js +119 -0
  13. package/dist/chunk-GDJH7XJC.js.map +1 -0
  14. package/dist/chunk-IBMJYIEP.js +1578 -0
  15. package/dist/chunk-IBMJYIEP.js.map +1 -0
  16. package/dist/chunk-IE2OUO54.js +199 -0
  17. package/dist/chunk-IE2OUO54.js.map +1 -0
  18. package/dist/chunk-IHQM2ZDV.js +19 -0
  19. package/dist/chunk-IHQM2ZDV.js.map +1 -0
  20. package/dist/chunk-ITGRANYW.js +63 -0
  21. package/dist/chunk-ITGRANYW.js.map +1 -0
  22. package/dist/chunk-K63EOA4B.js +416 -0
  23. package/dist/chunk-K63EOA4B.js.map +1 -0
  24. package/dist/chunk-PQK3GKZH.js +98 -0
  25. package/dist/chunk-PQK3GKZH.js.map +1 -0
  26. package/dist/chunk-PYLH4MH5.js +34 -0
  27. package/dist/chunk-PYLH4MH5.js.map +1 -0
  28. package/dist/chunk-PZ5AY32C.js +10 -0
  29. package/dist/chunk-PZ5AY32C.js.map +1 -0
  30. package/dist/chunk-SYHB3AMM.js +8580 -0
  31. package/dist/chunk-SYHB3AMM.js.map +1 -0
  32. package/dist/chunk-TI5TQD6D.js +324 -0
  33. package/dist/chunk-TI5TQD6D.js.map +1 -0
  34. package/dist/chunk-WHZSVY2L.js +144 -0
  35. package/dist/chunk-WHZSVY2L.js.map +1 -0
  36. package/dist/cli/index.d.ts +1 -0
  37. package/dist/cli/index.js +15 -0
  38. package/dist/cli/index.js.map +1 -0
  39. package/dist/codebuddy-D2xJ6hFk.d.ts +841 -0
  40. package/dist/config-S2TULOMK.js +11 -0
  41. package/dist/config-S2TULOMK.js.map +1 -0
  42. package/dist/index.d.ts +2527 -0
  43. package/dist/index.js +116 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/langgraph/index.d.ts +53 -0
  46. package/dist/langgraph/index.js +143 -0
  47. package/dist/langgraph/index.js.map +1 -0
  48. package/dist/lean-command-D6TZJEFX.js +13 -0
  49. package/dist/lean-command-D6TZJEFX.js.map +1 -0
  50. package/dist/main-ZZ3EQLYA.js +4575 -0
  51. package/dist/main-ZZ3EQLYA.js.map +1 -0
  52. package/dist/memory-NIBFAMY4.js +57 -0
  53. package/dist/memory-NIBFAMY4.js.map +1 -0
  54. package/dist/pretool-command-MQQ2UNQM.js +10 -0
  55. package/dist/pretool-command-MQQ2UNQM.js.map +1 -0
  56. package/dist/rerank-Q7MIFNKA.js +60 -0
  57. package/dist/rerank-Q7MIFNKA.js.map +1 -0
  58. package/dist/summaries-TSE44KUQ.js +18 -0
  59. package/dist/summaries-TSE44KUQ.js.map +1 -0
  60. package/dist/zvec-F27AR357.js +23 -0
  61. package/dist/zvec-F27AR357.js.map +1 -0
  62. package/docker-compose.yml +20 -0
  63. package/examples/claude-code/README.md +67 -0
  64. package/examples/claude-desktop/README.md +54 -0
  65. package/examples/claude-desktop/claude_desktop_config.json +13 -0
  66. package/examples/langgraph-agent/README.md +32 -0
  67. package/examples/langgraph-agent/index.ts +71 -0
  68. package/examples/multi-agent-handoff/README.md +35 -0
  69. package/examples/multi-agent-handoff/index.ts +68 -0
  70. package/package.json +108 -4
  71. package/src/db/migrations/0000_cultured_satana.sql +144 -0
  72. package/src/db/migrations/0001_embedding_lease.sql +3 -0
  73. package/src/db/migrations/0002_embedding_lease_token.sql +1 -0
  74. package/src/db/migrations/0003_embedding_space.sql +9 -0
  75. package/src/db/migrations/0004_markdown_reconcile.sql +5 -0
  76. package/src/db/migrations/0005_fact_propositions.sql +1 -0
  77. package/src/db/migrations/meta/0000_snapshot.json +1169 -0
  78. package/src/db/migrations/meta/0002_snapshot.json +1181 -0
  79. package/src/db/migrations/meta/0003_snapshot.json +1214 -0
  80. package/src/db/migrations/meta/0004_snapshot.json +1225 -0
  81. package/src/db/migrations/meta/0005_snapshot.json +1231 -0
  82. package/src/db/migrations/meta/_journal.json +48 -0
@@ -0,0 +1,1578 @@
1
+ import {
2
+ extractSymbolTable,
3
+ supportsSymbols
4
+ } from "./chunk-PQK3GKZH.js";
5
+ import {
6
+ assertNoSymlinkBetween,
7
+ readConfigFile,
8
+ writeAtomic
9
+ } from "./chunk-K63EOA4B.js";
10
+
11
+ // src/map/indexer.ts
12
+ import { readdir, readFile, stat } from "fs/promises";
13
+ import { dirname, extname, join, relative, resolve } from "path";
14
+ var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
15
+ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
16
+ ".git",
17
+ "node_modules",
18
+ "dist",
19
+ "build",
20
+ "coverage",
21
+ ".codebuddy"
22
+ ]);
23
+ async function buildArchitectureMap(repositoryRoot = process.cwd()) {
24
+ const root = resolve(repositoryRoot);
25
+ const files = await listSourceFiles(root);
26
+ const fileSet = new Set(files.map((file) => normalize(relative(root, file))));
27
+ const modules = [];
28
+ const edges = [];
29
+ for (const absolutePath of files) {
30
+ const source = await readFile(absolutePath, "utf8");
31
+ const from = normalize(relative(root, absolutePath));
32
+ const imports = extractImports(source);
33
+ const resolvedImports = [];
34
+ for (const specifier of imports) {
35
+ const resolved = resolveImport(root, from, specifier.value, fileSet);
36
+ if (!resolved) continue;
37
+ resolvedImports.push(resolved);
38
+ edges.push({ from, to: resolved, kind: specifier.kind });
39
+ }
40
+ modules.push({
41
+ path: from,
42
+ language: extname(absolutePath).includes("ts") ? "ts" : "js",
43
+ imports: [...new Set(resolvedImports)].sort()
44
+ });
45
+ }
46
+ return {
47
+ modules: modules.sort((left, right) => left.path.localeCompare(right.path)),
48
+ edges: edges.sort(
49
+ (left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
50
+ )
51
+ };
52
+ }
53
+ async function updateArchitectureMap(repositoryRoot, sourcePaths, previous, changedPaths) {
54
+ const root = resolve(repositoryRoot);
55
+ const normalizedPaths = sourcePaths.map(normalize).sort();
56
+ const fileSet = new Set(normalizedPaths);
57
+ const changed = new Set(changedPaths.map(normalize));
58
+ const retainedModules = (previous?.modules ?? []).filter(
59
+ (module) => fileSet.has(module.path) && !changed.has(module.path)
60
+ );
61
+ const retainedEdges = (previous?.edges ?? []).filter(
62
+ (edge) => fileSet.has(edge.from) && fileSet.has(edge.to) && !changed.has(edge.from)
63
+ );
64
+ const modules = [...retainedModules];
65
+ const edges = [...retainedEdges];
66
+ for (const path of normalizedPaths) {
67
+ if (!changed.has(path)) continue;
68
+ const source = await readFile(join(root, path), "utf8");
69
+ const imports = extractImports(source);
70
+ const resolvedImports = [];
71
+ for (const specifier of imports) {
72
+ const resolvedImport = resolveImport(root, path, specifier.value, fileSet);
73
+ if (!resolvedImport) continue;
74
+ resolvedImports.push(resolvedImport);
75
+ edges.push({ from: path, to: resolvedImport, kind: specifier.kind });
76
+ }
77
+ modules.push({
78
+ path,
79
+ language: extname(path).includes("ts") ? "ts" : "js",
80
+ imports: [...new Set(resolvedImports)].sort()
81
+ });
82
+ }
83
+ return {
84
+ map: {
85
+ modules: modules.sort((left, right) => left.path.localeCompare(right.path)),
86
+ edges: edges.sort(
87
+ (left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
88
+ )
89
+ },
90
+ stats: {
91
+ readFiles: changed.size,
92
+ reusedFiles: retainedModules.length,
93
+ rebuilt: previous === null || changed.size === normalizedPaths.length
94
+ }
95
+ };
96
+ }
97
+ function queryMap(map, query) {
98
+ const limit = query.limit ?? 50;
99
+ return map.edges.filter((edge) => query.from ? edge.from === normalize(query.from) : true).filter((edge) => query.to ? edge.to === normalize(query.to) : true).slice(0, limit);
100
+ }
101
+ function neighbours(map, modulePath, direction = "both") {
102
+ const target = normalize(modulePath);
103
+ const edges = map.edges.filter((edge) => {
104
+ if (direction === "in") return edge.to === target;
105
+ if (direction === "out") return edge.from === target;
106
+ return edge.from === target || edge.to === target;
107
+ });
108
+ const paths = /* @__PURE__ */ new Set([target]);
109
+ for (const edge of edges) {
110
+ paths.add(edge.from);
111
+ paths.add(edge.to);
112
+ }
113
+ return {
114
+ modules: map.modules.filter((module) => paths.has(module.path)),
115
+ edges
116
+ };
117
+ }
118
+ async function listSourceFiles(root) {
119
+ const files = [];
120
+ await walk(root, files);
121
+ return files.sort();
122
+ }
123
+ async function walk(directory, files) {
124
+ const entries = await readdir(directory, { withFileTypes: true });
125
+ for (const entry of entries) {
126
+ const absolute = join(directory, entry.name);
127
+ if (entry.isDirectory()) {
128
+ if (IGNORED_DIRECTORIES.has(entry.name)) continue;
129
+ await walk(absolute, files);
130
+ continue;
131
+ }
132
+ if (!entry.isFile()) continue;
133
+ if (!SUPPORTED_EXTENSIONS.has(extname(entry.name))) continue;
134
+ const info = await stat(absolute);
135
+ if (info.size > 5e5) continue;
136
+ files.push(absolute);
137
+ }
138
+ }
139
+ function extractImports(source) {
140
+ const imports = [];
141
+ const stripped = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1 ");
142
+ const staticImport = /\bimport\s+(?:type\s+)?(?:[^'"()+]+?\s+from\s+)?["']([^"']+)["']/g;
143
+ const exportFrom = /\bexport\s+[^'"()+]+?\s+from\s+["']([^"']+)["']/g;
144
+ const dynamicImport = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
145
+ for (const match of stripped.matchAll(staticImport)) {
146
+ if (match[1]) imports.push({ value: match[1], kind: "import" });
147
+ }
148
+ for (const match of stripped.matchAll(exportFrom)) {
149
+ if (match[1]) imports.push({ value: match[1], kind: "import" });
150
+ }
151
+ for (const match of stripped.matchAll(dynamicImport)) {
152
+ if (match[1]) imports.push({ value: match[1], kind: "dynamic_import" });
153
+ }
154
+ return imports;
155
+ }
156
+ function resolveImport(root, from, specifier, fileSet) {
157
+ if (!specifier.startsWith(".")) return null;
158
+ const base = normalize(relative(root, resolve(root, dirname(from), specifier)));
159
+ const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
160
+ const bases = stem === base ? [base] : [base, stem];
161
+ const candidates = bases.flatMap((b) => [
162
+ b,
163
+ `${b}.ts`,
164
+ `${b}.tsx`,
165
+ `${b}.js`,
166
+ `${b}.jsx`,
167
+ `${b}.mjs`,
168
+ `${b}.cjs`,
169
+ `${b}/index.ts`,
170
+ `${b}/index.tsx`,
171
+ `${b}/index.js`,
172
+ `${b}/index.jsx`
173
+ ]);
174
+ return candidates.find((candidate) => fileSet.has(candidate)) ?? null;
175
+ }
176
+ function normalize(path) {
177
+ return path.replace(/\\/g, "/").replace(/^\.\//, "");
178
+ }
179
+
180
+ // src/indexer/engine.ts
181
+ import { createHash } from "crypto";
182
+ import { readFile as readFile4, stat as stat2 } from "fs/promises";
183
+ import { join as join5, resolve as resolve4 } from "path";
184
+
185
+ // src/map/cache.ts
186
+ import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
187
+ import { dirname as dirname3, extname as extname2, join as join3, resolve as resolve3 } from "path";
188
+
189
+ // src/indexer/store.ts
190
+ import { mkdir, readFile as readFile2 } from "fs/promises";
191
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
192
+ import { z } from "zod";
193
+
194
+ // src/indexer/types.ts
195
+ var INDEX_MANIFEST_VERSION = 2;
196
+
197
+ // src/indexer/store.ts
198
+ var symbolSpanSchema = z.object({
199
+ name: z.string(),
200
+ kind: z.enum(["function", "method", "class", "interface", "type", "enum", "const"]),
201
+ startLine: z.number().int().nonnegative(),
202
+ endLine: z.number().int().nonnegative(),
203
+ signature: z.string(),
204
+ exported: z.boolean()
205
+ });
206
+ var entrySchema = z.object({
207
+ path: z.string(),
208
+ hash: z.string(),
209
+ size: z.number().int().nonnegative(),
210
+ mtimeMs: z.number().optional(),
211
+ ctimeMs: z.number().optional(),
212
+ language: z.string(),
213
+ lines: z.number().int().nonnegative(),
214
+ symbols: z.array(z.string()),
215
+ symbolTable: z.array(symbolSpanSchema).optional(),
216
+ summary: z.string(),
217
+ indexedAt: z.string()
218
+ });
219
+ var manifestSchema = z.object({
220
+ version: z.literal(INDEX_MANIFEST_VERSION),
221
+ generatedAt: z.string(),
222
+ entries: z.record(z.string(), entrySchema)
223
+ });
224
+ var IndexStore = class {
225
+ repositoryRoot;
226
+ cacheDirectory;
227
+ manifestPath;
228
+ constructor(repositoryRoot = process.cwd()) {
229
+ this.repositoryRoot = resolve2(repositoryRoot);
230
+ this.cacheDirectory = join2(this.repositoryRoot, ".codebuddy", "cache");
231
+ this.manifestPath = join2(this.cacheDirectory, "index.json");
232
+ }
233
+ /** Read the manifest, or an empty one if absent/corrupt/version-mismatched. */
234
+ async read() {
235
+ try {
236
+ const raw = await readFile2(this.manifestPath, "utf8");
237
+ const parsed = manifestSchema.safeParse(JSON.parse(raw));
238
+ if (!parsed.success) return emptyManifest();
239
+ return parsed.data;
240
+ } catch (error) {
241
+ if (error.code === "ENOENT") return emptyManifest();
242
+ return emptyManifest();
243
+ }
244
+ }
245
+ async write(manifest) {
246
+ await assertNoSymlinkBetween(this.repositoryRoot, dirname2(this.manifestPath));
247
+ await mkdir(this.cacheDirectory, { recursive: true, mode: 448 });
248
+ await writeAtomic(this.manifestPath, `${JSON.stringify(manifest, null, 2)}
249
+ `);
250
+ }
251
+ };
252
+ function emptyManifest() {
253
+ return { version: INDEX_MANIFEST_VERSION, generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(), entries: {} };
254
+ }
255
+
256
+ // src/map/cache.ts
257
+ var CACHE_VERSION = 1;
258
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
259
+ async function loadArchitectureMap(repositoryRoot = process.cwd(), suppliedManifest) {
260
+ const root = resolve3(repositoryRoot);
261
+ const manifest = suppliedManifest ?? await new IndexStore(root).read();
262
+ if (Object.keys(manifest.entries).length === 0) {
263
+ const map = await buildArchitectureMap(root);
264
+ return {
265
+ map,
266
+ stats: { readFiles: map.modules.length, reusedFiles: 0, rebuilt: true },
267
+ source: "fallback"
268
+ };
269
+ }
270
+ return refreshArchitectureCache(root, manifest);
271
+ }
272
+ async function refreshArchitectureCache(repositoryRoot, manifest) {
273
+ const root = resolve3(repositoryRoot);
274
+ const path = join3(root, ".codebuddy", "cache", "architecture.json");
275
+ const currentHashes = sourceHashes(manifest);
276
+ const currentPaths = Object.keys(currentHashes).sort();
277
+ const cached = await readCache(path);
278
+ const cachedPaths = cached ? Object.keys(cached.sourceHashes).sort() : [];
279
+ const fileSetChanged = cachedPaths.join("\n") !== currentPaths.join("\n");
280
+ const changedPaths = !cached || fileSetChanged ? currentPaths : currentPaths.filter(
281
+ (sourcePath) => cached.sourceHashes[sourcePath] !== currentHashes[sourcePath]
282
+ );
283
+ if (cached && changedPaths.length === 0) {
284
+ return {
285
+ map: cached.map,
286
+ stats: { readFiles: 0, reusedFiles: cached.map.modules.length, rebuilt: false },
287
+ source: "cache"
288
+ };
289
+ }
290
+ const updated = await updateArchitectureMap(
291
+ root,
292
+ currentPaths,
293
+ fileSetChanged ? null : cached?.map ?? null,
294
+ changedPaths
295
+ );
296
+ const next = {
297
+ version: CACHE_VERSION,
298
+ generatedAt: manifest.generatedAt,
299
+ sourceHashes: currentHashes,
300
+ map: updated.map
301
+ };
302
+ await assertNoSymlinkBetween(root, dirname3(path));
303
+ await mkdir2(dirname3(path), { recursive: true, mode: 448 });
304
+ await writeAtomic(path, `${JSON.stringify(next, null, 2)}
305
+ `);
306
+ return { ...updated, source: fileSetChanged || !cached ? "rebuild" : "incremental" };
307
+ }
308
+ async function readCache(path) {
309
+ try {
310
+ const parsed = JSON.parse(await readFile3(path, "utf8"));
311
+ if (parsed.version !== CACHE_VERSION || !parsed.map || !parsed.sourceHashes) return null;
312
+ if (!Array.isArray(parsed.map.modules) || !Array.isArray(parsed.map.edges)) return null;
313
+ if (!parsed.map.modules.every(
314
+ (module) => module && typeof module.path === "string" && typeof module.language === "string" && Array.isArray(module.imports)
315
+ ) || !parsed.map.edges.every(
316
+ (edge) => edge && typeof edge.from === "string" && typeof edge.to === "string"
317
+ )) {
318
+ return null;
319
+ }
320
+ return parsed;
321
+ } catch {
322
+ return null;
323
+ }
324
+ }
325
+ function sourceHashes(manifest) {
326
+ return Object.fromEntries(
327
+ Object.values(manifest.entries).filter((entry) => SOURCE_EXTENSIONS.has(extname2(entry.path).toLowerCase())).map((entry) => [entry.path, entry.hash])
328
+ );
329
+ }
330
+
331
+ // src/indexer/scan.ts
332
+ import { spawn } from "child_process";
333
+ import { readdir as readdir2 } from "fs/promises";
334
+ import { extname as extname4, join as join4, relative as relative2 } from "path";
335
+
336
+ // src/indexer/summary.ts
337
+ import { extname as extname3 } from "path";
338
+ var LANGUAGE_BY_EXTENSION = {
339
+ ".ts": "ts",
340
+ ".tsx": "tsx",
341
+ ".js": "js",
342
+ ".jsx": "jsx",
343
+ ".mjs": "js",
344
+ ".cjs": "js",
345
+ ".json": "json",
346
+ ".md": "md",
347
+ ".mdx": "md",
348
+ ".yaml": "yaml",
349
+ ".yml": "yaml",
350
+ ".toml": "toml",
351
+ ".css": "css",
352
+ ".scss": "scss",
353
+ ".html": "html",
354
+ ".py": "py",
355
+ ".go": "go",
356
+ ".rs": "rust",
357
+ ".java": "java",
358
+ ".rb": "ruby",
359
+ ".php": "php",
360
+ ".sh": "shell",
361
+ ".sql": "sql",
362
+ ".txt": "text"
363
+ };
364
+ var SUPPORTED_EXTENSIONS2 = new Set(Object.keys(LANGUAGE_BY_EXTENSION));
365
+ var CODE_LANGUAGES = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx"]);
366
+ var SYMBOL_LIMIT = 8;
367
+ var SUMMARY_COMMENT_MAX = 80;
368
+ function languageFor(path) {
369
+ return LANGUAGE_BY_EXTENSION[extname3(path).toLowerCase()] ?? "text";
370
+ }
371
+ function summarizeSource(path, content) {
372
+ const language = languageFor(path);
373
+ const lines = countLines(content);
374
+ const symbols = CODE_LANGUAGES.has(language) ? extractSymbols(content) : [];
375
+ const summary = composeSummary(language, lines, symbols, content);
376
+ return { language, lines, symbols, summary };
377
+ }
378
+ function countLines(content) {
379
+ if (content.length === 0) return 0;
380
+ return content.replace(/\r/g, "").replace(/\n$/, "").split("\n").length;
381
+ }
382
+ function extractSymbols(content) {
383
+ const names = [];
384
+ const push = (name) => {
385
+ const trimmed = name?.trim();
386
+ if (trimmed && /^[A-Za-z_$][\w$]*$/.test(trimmed) && !names.includes(trimmed)) {
387
+ names.push(trimmed);
388
+ }
389
+ };
390
+ const declaration = /^export\s+(?:default\s+)?(?:async\s+)?(?:abstract\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/gm;
391
+ for (const match of content.matchAll(declaration)) push(match[1]);
392
+ const namedExports = /^export\s*\{([^}]*)\}/gm;
393
+ for (const match of content.matchAll(namedExports)) {
394
+ for (const part of (match[1] ?? "").split(",")) {
395
+ const alias = part.includes(" as ") ? part.split(" as ").pop() : part;
396
+ push(alias);
397
+ }
398
+ }
399
+ return names.slice(0, SYMBOL_LIMIT);
400
+ }
401
+ function composeSummary(language, lines, symbols, content) {
402
+ const head = `${language} \u2022 ${lines} line${lines === 1 ? "" : "s"}`;
403
+ if (symbols.length > 0) return `${head} \u2022 exports ${symbols.join(", ")}`;
404
+ const comment = leadingComment(content);
405
+ if (comment) return `${head} \u2022 ${comment}`;
406
+ return head;
407
+ }
408
+ function leadingComment(content) {
409
+ for (const raw of content.split(/\r?\n/)) {
410
+ const line = raw.trim();
411
+ if (line === "") continue;
412
+ const stripped = line.replace(/^\/\/+/, "").replace(/^\/\*+/, "").replace(/\*+\/$/, "").replace(/^[#*]+/, "").trim();
413
+ if (line.startsWith("//") || line.startsWith("/*") || line.startsWith("#") || line.startsWith("*")) {
414
+ if (stripped) return truncate(stripped, SUMMARY_COMMENT_MAX);
415
+ continue;
416
+ }
417
+ return null;
418
+ }
419
+ return null;
420
+ }
421
+ function truncate(value, max) {
422
+ return value.length <= max ? value : `${value.slice(0, max - 1)}\u2026`;
423
+ }
424
+
425
+ // src/indexer/scan.ts
426
+ var FALLBACK_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
427
+ ".git",
428
+ "node_modules",
429
+ "dist",
430
+ "build",
431
+ "coverage",
432
+ ".codebuddy",
433
+ "graphify-out",
434
+ ".next",
435
+ ".turbo",
436
+ "out"
437
+ ]);
438
+ async function listIndexableFiles(repositoryRoot) {
439
+ const tracked = await gitListFiles(repositoryRoot);
440
+ const paths = tracked ?? await walkFiles(repositoryRoot);
441
+ return paths.map(normalize2).filter((path) => !path.startsWith(".codebuddy/") && !path.startsWith("graphify-out/")).filter((path) => SUPPORTED_EXTENSIONS2.has(extname4(path).toLowerCase())).sort();
442
+ }
443
+ async function gitListFiles(repositoryRoot) {
444
+ const result = await runGit(repositoryRoot, [
445
+ "ls-files",
446
+ "--cached",
447
+ "--others",
448
+ "--exclude-standard"
449
+ ]);
450
+ if (result.code !== 0) return null;
451
+ return result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
452
+ }
453
+ async function walkFiles(root) {
454
+ const files = [];
455
+ const walk2 = async (directory) => {
456
+ const entries = await readdir2(directory, { withFileTypes: true });
457
+ for (const entry of entries) {
458
+ const absolute = join4(directory, entry.name);
459
+ if (entry.isDirectory()) {
460
+ if (FALLBACK_IGNORED_DIRECTORIES.has(entry.name)) continue;
461
+ await walk2(absolute);
462
+ continue;
463
+ }
464
+ if (entry.isFile()) files.push(relative2(root, absolute));
465
+ }
466
+ };
467
+ await walk2(root);
468
+ return files;
469
+ }
470
+ function runGit(cwd, args) {
471
+ return new Promise((resolveResult) => {
472
+ const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
473
+ let stdout = "";
474
+ let stderr = "";
475
+ child.stdout.on("data", (chunk) => {
476
+ stdout += String(chunk);
477
+ });
478
+ child.stderr.on("data", (chunk) => {
479
+ stderr += String(chunk);
480
+ });
481
+ child.on("error", () => resolveResult({ code: -1, stdout: "", stderr: "git unavailable" }));
482
+ child.on("close", (code) => resolveResult({ code: code ?? 0, stdout, stderr }));
483
+ });
484
+ }
485
+ function normalize2(path) {
486
+ return path.replace(/\\/g, "/").replace(/^\.\//, "");
487
+ }
488
+
489
+ // src/indexer/engine.ts
490
+ var MAX_FILE_BYTES = 512e3;
491
+ var RACY_WINDOW_MS = 3e3;
492
+ async function indexRepository(options = {}) {
493
+ return (await refreshIndex(options)).result;
494
+ }
495
+ async function loadFreshIndex(repositoryRoot) {
496
+ try {
497
+ const { result, manifest } = await refreshIndex({ repositoryRoot });
498
+ const delta = result.added + result.changed + result.removed;
499
+ const failures = result.errors.length > 0 ? `; ${result.errors.length} file(s) could not be validated and were omitted` : "";
500
+ return {
501
+ manifest,
502
+ explain: delta === 0 ? `index: ${result.totalIndexed} indexed file(s) match current source${failures}` : `index: refreshed against current source (${result.changed} changed, ${result.added} added, ${result.removed} removed; ${result.totalIndexed} indexed)${failures}`
503
+ };
504
+ } catch (error) {
505
+ return {
506
+ manifest: emptyManifest(),
507
+ explain: `index: freshness check failed (${error.message}); indexed evidence omitted`
508
+ };
509
+ }
510
+ }
511
+ var passes = /* @__PURE__ */ new Map();
512
+ function refreshIndex(options) {
513
+ const key = resolve4(options.repositoryRoot ?? process.cwd());
514
+ const next = (passes.get(key) ?? Promise.resolve()).catch(() => void 0).then(() => runIndexPass(options));
515
+ passes.set(key, next);
516
+ void next.finally(() => {
517
+ if (passes.get(key) === next) passes.delete(key);
518
+ }).catch(() => void 0);
519
+ return next;
520
+ }
521
+ async function runIndexPass(options) {
522
+ const start = Date.now();
523
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
524
+ const repositoryRoot = resolve4(options.repositoryRoot ?? process.cwd());
525
+ const store = new IndexStore(repositoryRoot);
526
+ const [files, previous] = await Promise.all([listIndexableFiles(repositoryRoot), store.read()]);
527
+ const entries = {};
528
+ const errors = [];
529
+ let added = 0;
530
+ let changed = 0;
531
+ let unchanged = 0;
532
+ let skipped = 0;
533
+ let dirty = previous.generatedAt === emptyManifest().generatedAt;
534
+ for (const path of files) {
535
+ try {
536
+ const info = await stat2(join5(repositoryRoot, path));
537
+ if (!info.isFile()) continue;
538
+ if (info.size > MAX_FILE_BYTES) {
539
+ skipped++;
540
+ continue;
541
+ }
542
+ const existing = previous.entries[path];
543
+ if (!options.full && existing && statUnchanged(existing, info)) {
544
+ entries[path] = existing;
545
+ unchanged++;
546
+ continue;
547
+ }
548
+ const content = await readFile4(join5(repositoryRoot, path), "utf8");
549
+ const hash = hashContent(content);
550
+ const metadata = trustedMetadata(info);
551
+ dirty = true;
552
+ if (!options.full && existing && existing.hash === hash) {
553
+ const { mtimeMs: _mtime, ctimeMs: _ctime, ...rest } = existing;
554
+ entries[path] = { ...rest, size: info.size, ...metadata };
555
+ unchanged++;
556
+ continue;
557
+ }
558
+ const { language, lines, symbols, summary } = summarizeSource(path, content);
559
+ const symbolTable = supportsSymbols(path) ? extractSymbolTable(content) : [];
560
+ entries[path] = {
561
+ path,
562
+ hash,
563
+ size: info.size,
564
+ ...metadata,
565
+ language,
566
+ lines,
567
+ symbols,
568
+ ...symbolTable.length > 0 ? { symbolTable } : {},
569
+ summary,
570
+ indexedAt: now().toISOString()
571
+ };
572
+ if (existing) changed++;
573
+ else added++;
574
+ } catch (error) {
575
+ errors.push({ path, error: error.message });
576
+ skipped++;
577
+ }
578
+ }
579
+ const removed = Object.keys(previous.entries).filter((path) => !(path in entries)).length;
580
+ const manifest = {
581
+ version: INDEX_MANIFEST_VERSION,
582
+ generatedAt: now().toISOString(),
583
+ entries: sortEntries(entries)
584
+ };
585
+ if (dirty || removed > 0) await store.write(manifest);
586
+ const architecture = await refreshArchitectureCache(repositoryRoot, manifest);
587
+ const result = {
588
+ scanned: files.length,
589
+ added,
590
+ changed,
591
+ unchanged,
592
+ removed,
593
+ skipped,
594
+ errors,
595
+ durationMs: Date.now() - start,
596
+ totalIndexed: Object.keys(entries).length,
597
+ architecture: {
598
+ source: architecture.source,
599
+ readFiles: architecture.stats.readFiles,
600
+ reusedFiles: architecture.stats.reusedFiles
601
+ }
602
+ };
603
+ return { result, manifest };
604
+ }
605
+ function statUnchanged(entry, info) {
606
+ return entry.mtimeMs !== void 0 && entry.size === info.size && entry.mtimeMs === info.mtimeMs && entry.ctimeMs === info.ctimeMs;
607
+ }
608
+ function trustedMetadata(info) {
609
+ if (Date.now() - Math.max(info.mtimeMs, info.ctimeMs) < RACY_WINDOW_MS) return {};
610
+ return { mtimeMs: info.mtimeMs, ctimeMs: info.ctimeMs };
611
+ }
612
+ function hashContent(content) {
613
+ return createHash("sha256").update(content, "utf8").digest("hex");
614
+ }
615
+ function sortEntries(entries) {
616
+ return Object.fromEntries(
617
+ Object.keys(entries).sort().map((key) => [key, entries[key]])
618
+ );
619
+ }
620
+
621
+ // src/integrations/graphify.ts
622
+ import { execFile } from "child_process";
623
+ import { mkdir as mkdir3, mkdtemp, readFile as readFile6, rm, stat as stat3, utimes } from "fs/promises";
624
+ import { tmpdir } from "os";
625
+ import { dirname as dirname4, join as join6, relative as relative3, resolve as resolve5, sep } from "path";
626
+ import { promisify } from "util";
627
+
628
+ // src/map/graph-freshness.ts
629
+ import { createHash as createHash2 } from "crypto";
630
+ import { readFile as readFile5 } from "fs/promises";
631
+ var digest = (value) => createHash2("sha256").update(value).digest("hex");
632
+ async function writeGraphSourceSnapshot(path, manifest) {
633
+ await writeAtomic(
634
+ `${path}.sources.json`,
635
+ JSON.stringify({
636
+ version: 1,
637
+ graphHash: digest(await readFile5(path, "utf8")),
638
+ sources: Object.fromEntries(
639
+ Object.values(manifest.entries).map((entry) => [entry.path, entry.hash])
640
+ )
641
+ })
642
+ );
643
+ }
644
+ async function readGraphSourceSnapshot(path) {
645
+ let raw;
646
+ try {
647
+ raw = await readFile5(`${path}.sources.json`, "utf8");
648
+ } catch (error) {
649
+ return error.code === "ENOENT" ? null : {};
650
+ }
651
+ try {
652
+ const snapshot = JSON.parse(raw);
653
+ if (snapshot.version !== 1 || snapshot.graphHash !== digest(await readFile5(path, "utf8")) || !snapshot.sources || typeof snapshot.sources !== "object" || Array.isArray(snapshot.sources))
654
+ return {};
655
+ return Object.fromEntries(
656
+ Object.entries(snapshot.sources).filter(
657
+ (entry) => typeof entry[1] === "string"
658
+ )
659
+ );
660
+ } catch {
661
+ return {};
662
+ }
663
+ }
664
+
665
+ // src/integrations/graphify.ts
666
+ var execFileAsync = promisify(execFile);
667
+ var DEFAULT_GRAPH_PATH = "graphify-out/graph.json";
668
+ async function setupGraphify(options) {
669
+ const run = options.run ?? runCommand;
670
+ let available = await commandWorks(run, "graphify", ["--version"], options.repositoryRoot);
671
+ let installed = false;
672
+ if (!available) {
673
+ const installers = [
674
+ ["uv", ["tool", "install", "graphifyy"]],
675
+ ["pipx", ["install", "graphifyy"]],
676
+ ["python", ["-m", "pip", "install", "--user", "graphifyy"]]
677
+ ];
678
+ for (const [command, args] of installers) {
679
+ try {
680
+ await run(command, [...args], options.repositoryRoot);
681
+ available = await commandWorks(run, "graphify", ["--version"], options.repositoryRoot);
682
+ if (available) {
683
+ installed = true;
684
+ break;
685
+ }
686
+ } catch {
687
+ }
688
+ }
689
+ }
690
+ if (!available) {
691
+ return {
692
+ available: false,
693
+ installed,
694
+ generated: false,
695
+ graphPath: DEFAULT_GRAPH_PATH,
696
+ message: "Graphify was selected but is not available. Install graphifyy with uv/pipx, then run `codebuddy graphify index`."
697
+ };
698
+ }
699
+ try {
700
+ await run("graphify", ["install"], options.repositoryRoot).catch(() => void 0);
701
+ const graph = await generateGraphifyGraph({ repositoryRoot: options.repositoryRoot, run });
702
+ return {
703
+ available: true,
704
+ installed,
705
+ generated: true,
706
+ graphPath: graph.graphPath,
707
+ message: `Graphify graph generated (${graph.nodes} nodes, ${graph.edges} edges).`
708
+ };
709
+ } catch (error) {
710
+ return {
711
+ available: true,
712
+ installed,
713
+ generated: false,
714
+ graphPath: DEFAULT_GRAPH_PATH,
715
+ message: `Graphify is installed but its graph could not be generated: ${error.message}`
716
+ };
717
+ }
718
+ }
719
+ async function commandWorks(run, command, args, cwd) {
720
+ try {
721
+ await run(command, args, cwd);
722
+ return true;
723
+ } catch {
724
+ return false;
725
+ }
726
+ }
727
+ async function runCommand(command, args, cwd) {
728
+ return execFileAsync(command, args, { cwd });
729
+ }
730
+ async function graphifyGraphExists(repositoryRoot, graphPath = DEFAULT_GRAPH_PATH) {
731
+ try {
732
+ const parsed = await readGraphFile(resolveGraphPath(repositoryRoot, graphPath));
733
+ return parsed.nodes > 0;
734
+ } catch {
735
+ return false;
736
+ }
737
+ }
738
+ async function generateGraphifyGraph(options) {
739
+ const run = options.run ?? runCommand;
740
+ const repositoryRoot = resolve5(options.repositoryRoot);
741
+ const graphPath = options.graphPath ?? DEFAULT_GRAPH_PATH;
742
+ const destination = resolveGraphPath(repositoryRoot, graphPath);
743
+ await mkdir3(dirname4(destination), { recursive: true });
744
+ await ignoreGeneratedGraphDirectory(dirname4(destination));
745
+ const { manifest: sourceSnapshot } = await loadFreshIndex(repositoryRoot);
746
+ const current = await readGraphFileIfPresent(destination);
747
+ const isDefaultPath = graphPath === DEFAULT_GRAPH_PATH;
748
+ if (current && isDefaultPath) {
749
+ const backup = await readFile6(destination, "utf8");
750
+ const backupStat = await stat3(destination);
751
+ try {
752
+ await run("graphify", ["update", repositoryRoot, "--force", "--no-cluster"], repositoryRoot);
753
+ const updated = await readGraphFile(destination);
754
+ await writeGraphSourceSnapshot(destination, sourceSnapshot);
755
+ return { graphPath, generated: true, ...updated };
756
+ } catch (error) {
757
+ await writeAtomic(destination, backup);
758
+ await utimes(destination, backupStat.atime, backupStat.mtime);
759
+ throw new Error(
760
+ `Graphify update failed; the previous graph was preserved: ${error.message}`
761
+ );
762
+ }
763
+ }
764
+ const stagingRoot = await mkdtemp(join6(tmpdir(), "codebuddy-graphify-"));
765
+ try {
766
+ await run(
767
+ "graphify",
768
+ ["extract", repositoryRoot, "--out", stagingRoot, "--code-only", "--no-cluster"],
769
+ repositoryRoot
770
+ );
771
+ const stagedPath = join6(stagingRoot, DEFAULT_GRAPH_PATH);
772
+ const staged = await readGraphFile(stagedPath);
773
+ await writeAtomic(destination, await readFile6(stagedPath, "utf8"));
774
+ await writeGraphSourceSnapshot(destination, sourceSnapshot);
775
+ return { graphPath, generated: true, ...staged };
776
+ } finally {
777
+ await rm(stagingRoot, { recursive: true, force: true });
778
+ }
779
+ }
780
+ async function ignoreGeneratedGraphDirectory(directory) {
781
+ const path = join6(directory, ".gitignore");
782
+ try {
783
+ await stat3(path);
784
+ } catch {
785
+ await writeAtomic(path, "# CodeBuddy-generated Graphify output (derived, rebuildable)\n*\n");
786
+ }
787
+ }
788
+ function resolveGraphPath(repositoryRoot, graphPath) {
789
+ const absolute = resolve5(repositoryRoot, graphPath);
790
+ const relativePath = relative3(repositoryRoot, absolute);
791
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`)) {
792
+ throw new Error(`Graph path must be inside the project root: ${graphPath}`);
793
+ }
794
+ return absolute;
795
+ }
796
+ async function readGraphFileIfPresent(path) {
797
+ try {
798
+ return await readGraphFile(path);
799
+ } catch {
800
+ return null;
801
+ }
802
+ }
803
+ async function readGraphFile(path) {
804
+ const info = await stat3(path);
805
+ if (!info.isFile() || info.size === 0)
806
+ throw new Error(`Graphify graph is missing or empty: ${path}`);
807
+ const raw = JSON.parse(await readFile6(path, "utf8"));
808
+ const nodes = arrayLength(raw.nodes ?? raw.entities);
809
+ const edges = arrayLength(raw.edges ?? raw.links ?? raw.relationships);
810
+ if (nodes === 0) throw new Error(`Graphify graph contains no nodes: ${path}`);
811
+ return { nodes, edges };
812
+ }
813
+ function arrayLength(value) {
814
+ return Array.isArray(value) ? value.length : 0;
815
+ }
816
+
817
+ // 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";
820
+
821
+ // src/map/graphify.ts
822
+ import { readFile as readFile7 } from "fs/promises";
823
+ import { isAbsolute, relative as relative4, resolve as resolve6 } from "path";
824
+ var NON_DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
825
+ "contains",
826
+ "part_of",
827
+ "defines",
828
+ "declares",
829
+ "references",
830
+ "requires_env",
831
+ "rationale_for"
832
+ ]);
833
+ var INVERSE_RELATIONS = /* @__PURE__ */ new Set([
834
+ "imported_by",
835
+ "called_by",
836
+ "used_by",
837
+ "referenced_by",
838
+ "extended_by",
839
+ "implemented_by",
840
+ "dependency_of",
841
+ "depended_on_by"
842
+ ]);
843
+ var DIRECT_DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
844
+ "import",
845
+ "imports",
846
+ "imports_from",
847
+ "dynamic_import",
848
+ "dynamic_imports",
849
+ "calls",
850
+ "uses",
851
+ "extends",
852
+ "implements",
853
+ "depends_on"
854
+ ]);
855
+ var MAX_EDGE_SYMBOLS = 8;
856
+ async function loadGraphifyMap(graphPath, repositoryRoot = process.cwd()) {
857
+ const raw = JSON.parse(await readFile7(graphPath, "utf8"));
858
+ const nodes = arrayRecord(raw.nodes ?? raw.entities);
859
+ const edges = arrayRecord(raw.edges ?? raw.links ?? raw.relationships);
860
+ const nodePaths = /* @__PURE__ */ new Map();
861
+ const nodeLabels = /* @__PURE__ */ new Map();
862
+ const modules = [];
863
+ for (const node of nodes) {
864
+ const id = stringValue(node.id ?? node.key ?? node.name);
865
+ const path = graphifyPath(node, repositoryRoot);
866
+ if (!id || !path) continue;
867
+ nodePaths.set(id, path);
868
+ const label = stringValue(node.label ?? node.name);
869
+ if (label && label !== path && !path.endsWith(`/${label}`)) nodeLabels.set(id, label);
870
+ modules.push({ path, language: languageFor2(path), imports: [] });
871
+ }
872
+ const modulePaths = new Set(modules.map((module) => module.path));
873
+ const importedEdges = [];
874
+ for (const edge of edges) {
875
+ const relation = stringValue(edge.relation ?? edge.type ?? edge.kind)?.toLowerCase() ?? null;
876
+ if (relation && NON_DEPENDENCY_RELATIONS.has(relation)) continue;
877
+ if (relation && !DIRECT_DEPENDENCY_RELATIONS.has(relation) && !INVERSE_RELATIONS.has(relation))
878
+ continue;
879
+ const inverse = relation !== null && INVERSE_RELATIONS.has(relation);
880
+ const sourceEndpoint = edge.from ?? edge.source;
881
+ const targetEndpoint = edge.to ?? edge.target;
882
+ const [tail, head] = inverse ? [targetEndpoint, sourceEndpoint] : [sourceEndpoint, targetEndpoint];
883
+ const from = endpointPath(tail, nodePaths, repositoryRoot);
884
+ const to = endpointPath(head, nodePaths, repositoryRoot);
885
+ if (!from || !to || !modulePaths.has(from) || !modulePaths.has(to) || from === to) continue;
886
+ const symbol = endpointLabel(head, nodeLabels);
887
+ importedEdges.push({
888
+ from,
889
+ to,
890
+ kind: relation?.includes("dynamic") ? "dynamic_import" : "import",
891
+ ...symbol ? { symbols: [symbol] } : {}
892
+ });
893
+ }
894
+ const uniqueModules = [...new Map(modules.map((module) => [module.path, module])).values()].sort(
895
+ (left, right) => left.path.localeCompare(right.path)
896
+ );
897
+ const byEndpoints = /* @__PURE__ */ new Map();
898
+ for (const edge of importedEdges) {
899
+ const key = `${edge.from}:${edge.to}`;
900
+ const existing = byEndpoints.get(key);
901
+ if (!existing) {
902
+ byEndpoints.set(key, edge);
903
+ continue;
904
+ }
905
+ const symbols = mergeSymbols(existing.symbols, edge.symbols);
906
+ byEndpoints.set(key, {
907
+ ...existing.kind === "dynamic_import" && edge.kind === "import" ? edge : existing,
908
+ ...symbols ? { symbols } : {}
909
+ });
910
+ }
911
+ const uniqueEdges = [...byEndpoints.values()].sort(
912
+ (left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
913
+ );
914
+ if (uniqueModules.length === 0 || uniqueEdges.length === 0) {
915
+ throw new Error(`Graphify graph contains no usable file relationships: ${graphPath}`);
916
+ }
917
+ for (const module of uniqueModules) {
918
+ module.imports = uniqueEdges.filter((edge) => edge.from === module.path).map((edge) => edge.to).sort();
919
+ }
920
+ return { modules: uniqueModules, edges: uniqueEdges };
921
+ }
922
+ function arrayRecord(value) {
923
+ return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object")) : [];
924
+ }
925
+ function endpointPath(endpoint, nodePaths, repositoryRoot) {
926
+ if (typeof endpoint === "string")
927
+ return nodePaths.get(endpoint) ?? normalizeGraphPath(endpoint, repositoryRoot);
928
+ if (!endpoint || typeof endpoint !== "object") return null;
929
+ const record = endpoint;
930
+ const id = stringValue(record.id ?? record.key ?? record.name);
931
+ return nodePaths.get(id ?? "") ?? graphifyPath(record, repositoryRoot);
932
+ }
933
+ function endpointLabel(endpoint, nodeLabels) {
934
+ if (typeof endpoint === "string") return nodeLabels.get(endpoint) ?? null;
935
+ if (!endpoint || typeof endpoint !== "object") return null;
936
+ const id = stringValue(endpoint.id);
937
+ return id ? nodeLabels.get(id) ?? null : null;
938
+ }
939
+ function mergeSymbols(left, right) {
940
+ if (!left && !right) return void 0;
941
+ return [.../* @__PURE__ */ new Set([...left ?? [], ...right ?? []])].sort().slice(0, MAX_EDGE_SYMBOLS);
942
+ }
943
+ function graphifyPath(node, repositoryRoot) {
944
+ const source = node.source;
945
+ const location = node.location;
946
+ const candidate = stringValue(node.path ?? node.file ?? node.file_path ?? node.source_file) ?? (source && typeof source === "object" ? stringValue(source.path) : null) ?? (location && typeof location === "object" ? stringValue(location.file ?? location.path) : null);
947
+ return candidate ? normalizeGraphPath(candidate, repositoryRoot) : null;
948
+ }
949
+ function normalizeGraphPath(path, repositoryRoot) {
950
+ const absolute = isAbsolute(path) ? resolve6(path) : resolve6(repositoryRoot, path);
951
+ const relativePath = relative4(resolve6(repositoryRoot), absolute).replace(/\\/g, "/");
952
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith("../")) return null;
953
+ return relativePath.replace(/^\.\//, "");
954
+ }
955
+ function stringValue(value) {
956
+ return typeof value === "string" && value.length > 0 ? value : null;
957
+ }
958
+ function languageFor2(path) {
959
+ return /\.tsx?$/.test(path) ? "ts" : "js";
960
+ }
961
+
962
+ // src/context/anchors.ts
963
+ import { isAbsolute as isAbsolute2, relative as relative5, resolve as resolve7, sep as sep2 } from "path";
964
+ var MAX_ANCHORS = 3;
965
+ var MAX_ERROR_SEARCH_FILES = 2e3;
966
+ var JS_FRAME = /\bat\s+(?:[^\s()]+\s+\()?([^\s()]+?):(\d+):\d+\)?/g;
967
+ var PY_FRAME = /\bFile\s+"([^"]+)",\s+line\s+(\d+)/g;
968
+ var PATH_TOKEN = /(?:^|[\s(`'"[])((?:\.{0,2}\/)?[\w@.-]+(?:\/[\w@.-]+)*\.[A-Za-z0-9]+)(?::(\d+))?/g;
969
+ var QUOTED = /"([^"\n]{8,200})"|'([^'\n]{8,200})'|`([^`\n]{8,200})`/g;
970
+ async function extractTaskAnchors(repositoryRoot, task, paths) {
971
+ const root = resolve7(repositoryRoot);
972
+ const found = [];
973
+ const add = (raw, line, kind, at) => {
974
+ const path = toIndexedPath(root, raw, paths);
975
+ if (path) found.push({ path, line: line ? Number(line) : null, kind, at });
976
+ };
977
+ for (const match of task.matchAll(JS_FRAME)) add(match[1] ?? "", match[2], "stack", match.index);
978
+ for (const match of task.matchAll(PY_FRAME)) add(match[1] ?? "", match[2], "stack", match.index);
979
+ for (const match of task.matchAll(PATH_TOKEN)) {
980
+ add(match[1] ?? "", match[2], match[2] ? "line" : "path", match.index);
981
+ }
982
+ const anchors = [];
983
+ const seen = /* @__PURE__ */ new Set();
984
+ for (const { at: _at, ...anchor } of found.sort((left, right) => left.at - right.at)) {
985
+ if (seen.has(anchor.path)) continue;
986
+ seen.add(anchor.path);
987
+ anchors.push(anchor);
988
+ }
989
+ if (anchors.length < MAX_ANCHORS) {
990
+ for (const error of await errorAnchors(root, task, paths)) {
991
+ if (seen.has(error.path)) continue;
992
+ seen.add(error.path);
993
+ anchors.push(error);
994
+ }
995
+ }
996
+ return anchors.slice(0, MAX_ANCHORS);
997
+ }
998
+ function toIndexedPath(root, raw, paths) {
999
+ const absolute = isAbsolute2(raw) ? raw : resolve7(root, raw);
1000
+ const rel = relative5(root, absolute);
1001
+ if (rel === "" || rel.startsWith(`..${sep2}`) || rel === ".." || isAbsolute2(rel)) return null;
1002
+ const normalized = rel.replace(/\\/g, "/");
1003
+ return paths.has(normalized) ? normalized : null;
1004
+ }
1005
+ async function errorAnchors(root, task, paths) {
1006
+ const quotes = [...task.matchAll(QUOTED)].map((match) => match[1] ?? match[2] ?? match[3] ?? "").filter((text) => text.trim().length >= 8 && !toIndexedPath(root, text, paths));
1007
+ if (quotes.length === 0) return [];
1008
+ const sources = [...paths].filter((path) => !TEST_PATH.test(path) && !NON_SOURCE_PATH.test(path)).sort().slice(0, MAX_ERROR_SEARCH_FILES);
1009
+ const matches = quotes.map(() => []);
1010
+ for (const path of sources) {
1011
+ if (matches.every((found) => found.length > 1)) break;
1012
+ const source = await readBoundedSource(root, path);
1013
+ if (!source) continue;
1014
+ quotes.forEach((quote, i) => {
1015
+ const found = matches[i];
1016
+ if (found.length > 1) return;
1017
+ const index = source.indexOf(quote);
1018
+ if (index >= 0) {
1019
+ found.push({ path, line: source.slice(0, index).split("\n").length, kind: "error" });
1020
+ }
1021
+ });
1022
+ }
1023
+ return matches.flatMap((found) => found.length === 1 ? found : []);
1024
+ }
1025
+
1026
+ // src/context/discovery.ts
1027
+ var TASK_PATH_LIMIT = 10;
1028
+ var TASK_FILES = 3;
1029
+ var EXPANSION = { seeds: 2, files: 1 };
1030
+ var MAX_EVIDENCE_BYTES = 512e3;
1031
+ async function readBoundedSource(repositoryRoot, path) {
1032
+ try {
1033
+ const absolute = join7(repositoryRoot, path);
1034
+ if ((await stat4(absolute)).size > MAX_EVIDENCE_BYTES) return null;
1035
+ const source = await readFile8(absolute, "utf8");
1036
+ return source.includes("\0") ? null : source;
1037
+ } catch {
1038
+ return null;
1039
+ }
1040
+ }
1041
+ async function discoverTaskPaths(repositoryRoot, task, manifest, mapSource, options = {}) {
1042
+ const anchors = await extractTaskAnchors(
1043
+ repositoryRoot,
1044
+ task,
1045
+ new Set(Object.keys(manifest.entries))
1046
+ );
1047
+ if (anchors.length === 0)
1048
+ return rankTaskFiles(repositoryRoot, task, manifest, mapSource, options);
1049
+ const anchored = anchors.map((anchor) => anchor.path);
1050
+ const hits = anchors.flatMap(
1051
+ (anchor) => anchor.line === null ? [] : [
1052
+ {
1053
+ path: anchor.path,
1054
+ startLine: anchor.line,
1055
+ endLine: anchor.line,
1056
+ rank: 0,
1057
+ source: "anchor",
1058
+ freshness: "unverified"
1059
+ }
1060
+ ]
1061
+ );
1062
+ const explain = `retrieval: anchors (${anchors.map(
1063
+ (anchor) => `${anchor.kind} ${anchor.path}${anchor.line === null ? "" : `:${anchor.line}`}`
1064
+ ).join(", ")})`;
1065
+ if (anchored.length >= TASK_FILES) return { paths: anchored, hits, explain: [explain] };
1066
+ const ranked = await rankTaskFiles(repositoryRoot, task, manifest, mapSource, options);
1067
+ return {
1068
+ ...ranked.symbols ? { symbols: ranked.symbols } : {},
1069
+ paths: [...anchored, ...ranked.paths.filter((path) => !anchored.includes(path))].slice(
1070
+ 0,
1071
+ TASK_FILES
1072
+ ),
1073
+ hits: [...hits, ...ranked.hits.filter((hit) => !anchored.includes(hit.path))],
1074
+ explain: [explain, ...ranked.explain]
1075
+ };
1076
+ }
1077
+ async function withLlmSummaries(repositoryRoot, manifest) {
1078
+ const entries = Object.values(manifest.entries);
1079
+ try {
1080
+ const { resolveLlmConfig } = await import("./config-S2TULOMK.js");
1081
+ const config = await resolveLlmConfig(repositoryRoot);
1082
+ if (!config.enabled || !config.features.summaries) return entries;
1083
+ const { loadLlmSummaries } = await import("./summaries-TSE44KUQ.js");
1084
+ const summaries = await loadLlmSummaries(repositoryRoot, manifest);
1085
+ if (summaries.size === 0) return entries;
1086
+ return entries.map((entry) => {
1087
+ const summary = summaries.get(entry.path);
1088
+ return summary ? { ...entry, llmText: `${summary.summary} ${Object.values(summary.symbols).join(" ")}` } : entry;
1089
+ });
1090
+ } catch {
1091
+ return entries;
1092
+ }
1093
+ }
1094
+ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options) {
1095
+ const entries = await withLlmSummaries(repositoryRoot, manifest);
1096
+ const wantsTests = /\b(tests?|spec)\b/i.test(task);
1097
+ const identifiers = identifierTokens(task).filter((token) => CODE_IDENTIFIER.test(token));
1098
+ if (identifiers.length > 0) {
1099
+ const exact = entries.filter((entry) => wantsTests || !TEST_PATH.test(entry.path)).map((entry) => ({
1100
+ path: entry.path,
1101
+ named: new Set(
1102
+ (entry.symbolTable ?? []).flatMap(
1103
+ (symbol) => symbol.name.split(".").filter((segment) => identifiers.includes(segment))
1104
+ )
1105
+ ).size
1106
+ })).filter((entry) => entry.named > 0).sort((left, right) => right.named - left.named || left.path.localeCompare(right.path)).map((entry) => entry.path);
1107
+ if (exact.length > 0) {
1108
+ const evidence2 = new Map(
1109
+ scoreTaskPaths(entries, task).filter((entry) => entry.score > 0).map((entry) => [entry.path, entry.score])
1110
+ );
1111
+ const expansion2 = expandAlongImports(exact, await mapSource, evidence2, task, {
1112
+ allowGraphOnly: true
1113
+ });
1114
+ return {
1115
+ paths: expansion2.paths.slice(0, TASK_FILES),
1116
+ hits: [],
1117
+ explain: [
1118
+ `retrieval: exact-symbol routing (${identifiers.slice(0, 5).join(", ")})`,
1119
+ ...expansion2.explain ? [expansion2.explain] : []
1120
+ ]
1121
+ };
1122
+ }
1123
+ }
1124
+ const commands = await commandFiles(repositoryRoot, task, entries);
1125
+ const scored = scoreTaskPaths(entries, task);
1126
+ const keyword = scored.filter((entry) => entry.score >= EVIDENCE_FLOOR.keywordScore).map((entry) => entry.path);
1127
+ const weakKeyword = scored.length - keyword.length;
1128
+ const semantic = options.semantic === false ? {
1129
+ status: "disabled",
1130
+ paths: [],
1131
+ hits: [],
1132
+ reason: "semantic retrieval off",
1133
+ stale: 0,
1134
+ changed: 0
1135
+ } : await import("./zvec-F27AR357.js").then(({ zvecCandidates }) => zvecCandidates(repositoryRoot, task)).catch(
1136
+ () => ({
1137
+ status: "failed",
1138
+ paths: [],
1139
+ hits: [],
1140
+ reason: "zvec-grep failed",
1141
+ stale: 0,
1142
+ changed: 0
1143
+ })
1144
+ );
1145
+ const evidence = new Map(
1146
+ scored.filter((entry) => entry.score > 0).map((entry) => [entry.path, entry.score])
1147
+ );
1148
+ let ranked = keyword;
1149
+ let hits = [];
1150
+ const explain = [];
1151
+ if (semantic.status !== "disabled" && semantic.paths.length === 0) {
1152
+ explain.push(`retrieval: keyword ranking only (${semantic.reason})`);
1153
+ } else if (semantic.status !== "disabled") {
1154
+ const terms = taskTerms(task);
1155
+ const vector = [];
1156
+ const partial = [];
1157
+ let uncorroborated = 0;
1158
+ for (const path of semantic.paths) {
1159
+ if (!wantsTests && TEST_PATH.test(path)) continue;
1160
+ if (keyword.includes(path)) vector.push(path);
1161
+ else if (await hasMatchedPassageEvidence(
1162
+ repositoryRoot,
1163
+ path,
1164
+ semantic.hits,
1165
+ terms,
1166
+ manifest.entries[path]?.symbolTable ?? []
1167
+ )) {
1168
+ const source = await readBoundedSource(repositoryRoot, path) ?? "";
1169
+ if (countTerms(source, terms) >= EVIDENCE_FLOOR.vectorTerms) vector.push(path);
1170
+ else partial.push(path);
1171
+ if (!evidence.has(path)) evidence.set(path, 0);
1172
+ } else uncorroborated++;
1173
+ }
1174
+ ranked = rankEvidenceCandidates(
1175
+ scored.map((entry) => entry.path),
1176
+ keyword,
1177
+ vector,
1178
+ partial
1179
+ );
1180
+ hits = semantic.hits.filter((hit) => vector.includes(hit.path) || partial.includes(hit.path));
1181
+ explain.push(
1182
+ `retrieval: keyword ranking fused with ${semantic.reason}` + (uncorroborated > 0 ? `; ${uncorroborated} uncorroborated vector file(s) dropped` : "")
1183
+ );
1184
+ }
1185
+ let symbols;
1186
+ const reranked = await llmRerank({
1187
+ repositoryRoot,
1188
+ task,
1189
+ manifest,
1190
+ entries,
1191
+ scored,
1192
+ semantic: semantic.paths,
1193
+ ranked,
1194
+ wantsTests,
1195
+ deadlineAt: options.llmDeadlineAt
1196
+ });
1197
+ if (reranked) {
1198
+ explain.push(reranked.explain);
1199
+ ranked = reranked.ranked;
1200
+ symbols = reranked.symbols;
1201
+ }
1202
+ if (!ranked.some((path) => !NON_SOURCE_PATH.test(path))) {
1203
+ explain.push(
1204
+ "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
+ );
1206
+ return { paths: commands, hits: [], explain };
1207
+ }
1208
+ if (weakKeyword > 0) {
1209
+ explain.push(
1210
+ `abstention: ${weakKeyword} weak keyword candidate(s) below the evidence floor (score < ${EVIDENCE_FLOOR.keywordScore}) dropped`
1211
+ );
1212
+ }
1213
+ const expansion = expandAlongImports(ranked, await mapSource, evidence, task);
1214
+ if (expansion.explain) explain.push(expansion.explain);
1215
+ if (commands.length > 0) {
1216
+ explain.push(`retrieval: CLI command routing (${commands.join(", ")})`);
1217
+ }
1218
+ const paths = [.../* @__PURE__ */ new Set([...commands, ...expansion.paths])];
1219
+ if (paths.length > TASK_FILES) {
1220
+ explain.push(
1221
+ `abstention: ${paths.length - TASK_FILES} lower-ranked candidate(s) beyond the top ${TASK_FILES} dropped`
1222
+ );
1223
+ }
1224
+ return {
1225
+ paths: paths.slice(0, TASK_FILES),
1226
+ hits,
1227
+ explain,
1228
+ ...symbols && Object.keys(symbols).length > 0 ? { symbols } : {}
1229
+ };
1230
+ }
1231
+ var MIN_RERANK_MS = 200;
1232
+ async function llmRerank(input) {
1233
+ let config;
1234
+ try {
1235
+ config = await (await import("./config-S2TULOMK.js")).resolveLlmConfig(input.repositoryRoot);
1236
+ } catch {
1237
+ return null;
1238
+ }
1239
+ if (!config.enabled || !config.features.rerank) return null;
1240
+ const skipped = (reason) => ({
1241
+ ranked: input.ranked,
1242
+ explain: `retrieval: llm rerank skipped (${reason})`,
1243
+ symbols: {}
1244
+ });
1245
+ const usable = (path) => input.wantsTests || !TEST_PATH.test(path);
1246
+ const candidatePaths = [
1247
+ .../* @__PURE__ */ new Set([
1248
+ ...input.ranked,
1249
+ ...input.scored.map((entry) => entry.path).filter(usable).slice(0, 12),
1250
+ ...input.semantic.filter(usable).slice(0, 8)
1251
+ ])
1252
+ ].slice(0, 15);
1253
+ if (candidatePaths.length === 0) return null;
1254
+ const byPath = new Map(input.entries.map((entry) => [entry.path, entry]));
1255
+ const deadlineMs = Math.min(
1256
+ config.timeoutMs,
1257
+ input.deadlineAt === void 0 ? config.timeoutMs : input.deadlineAt - Date.now()
1258
+ );
1259
+ if (deadlineMs < MIN_RERANK_MS) return skipped("no time left in the hook budget");
1260
+ const { rerankCandidates } = await import("./rerank-Q7MIFNKA.js");
1261
+ const result = await rerankCandidates(
1262
+ input.repositoryRoot,
1263
+ config,
1264
+ input.task,
1265
+ candidatePaths.map((path) => ({
1266
+ path,
1267
+ summary: byPath.get(path)?.llmText || byPath.get(path)?.summary || "",
1268
+ signatures: (input.manifest.entries[path]?.symbolTable ?? []).map((s) => s.signature)
1269
+ })),
1270
+ deadlineMs
1271
+ ).catch(() => ({ ok: false, reason: "invalid" }));
1272
+ if (!result.ok) return skipped(result.reason);
1273
+ const label = `${config.provider} ${config.model}, ${result.cached ? "cached" : `${result.latencyMs} ms`}`;
1274
+ if (result.value.abstain) {
1275
+ return {
1276
+ ranked: [],
1277
+ explain: `retrieval: llm rerank (${label}, model abstained over ${candidatePaths.length} candidates)`,
1278
+ symbols: {}
1279
+ };
1280
+ }
1281
+ const terms = taskTerms(input.task);
1282
+ const picks = [];
1283
+ const symbols = {};
1284
+ let promoted = 0;
1285
+ for (const file of result.value.files) {
1286
+ if (!input.ranked.includes(file.path)) {
1287
+ const source = await readBoundedSource(input.repositoryRoot, file.path) ?? "";
1288
+ const llmText = byPath.get(file.path)?.llmText ?? "";
1289
+ if (Math.max(countTerms(llmText, terms), countTerms(source, terms)) < EVIDENCE_FLOOR.vectorTerms)
1290
+ continue;
1291
+ promoted++;
1292
+ }
1293
+ picks.push(file.path);
1294
+ const known = new Set(
1295
+ (input.manifest.entries[file.path]?.symbolTable ?? []).map((s) => s.name)
1296
+ );
1297
+ const named = file.symbols.filter((name) => known.has(name));
1298
+ if (named.length > 0) symbols[file.path] = named;
1299
+ }
1300
+ return {
1301
+ ranked: [...picks, ...input.ranked.filter((path) => !picks.includes(path))],
1302
+ explain: `retrieval: llm rerank (${label}, ${picks.length} of ${candidatePaths.length} candidates` + (promoted > 0 ? `, ${promoted} promoted` : "") + ")",
1303
+ symbols
1304
+ };
1305
+ }
1306
+ async function commandFiles(repositoryRoot, task, entries) {
1307
+ const bins = await packageBins(repositoryRoot);
1308
+ if (bins.length === 0) return [];
1309
+ const stems = /* @__PURE__ */ new Set();
1310
+ const pattern = new RegExp(
1311
+ `\\b(?:${bins.map(escapeRegExp).join("|")})\\s+([a-z][\\w-]*)(?:\\s+([a-z][\\w-]*))?`,
1312
+ "gi"
1313
+ );
1314
+ for (const [, command, sub] of task.matchAll(pattern)) {
1315
+ if (!command) continue;
1316
+ stems.add(command.toLowerCase());
1317
+ if (sub) stems.add(`${command}-${sub}`.toLowerCase());
1318
+ }
1319
+ if (stems.size === 0) return [];
1320
+ const matches = entries.map((entry) => entry.path).filter((path) => {
1321
+ if (TEST_PATH.test(path) || NON_SOURCE_PATH.test(path) || !COMMAND_DIR.test(path)) {
1322
+ return false;
1323
+ }
1324
+ const name = path.slice(path.lastIndexOf("/") + 1);
1325
+ return stems.has(name.slice(0, name.lastIndexOf(".")).toLowerCase());
1326
+ });
1327
+ return matches.sort((left, right) => right.length - left.length || left.localeCompare(right)).slice(0, TASK_FILES);
1328
+ }
1329
+ var COMMAND_DIR = /(^|\/)(cli|cmd|commands?)\//;
1330
+ async function packageBins(repositoryRoot) {
1331
+ try {
1332
+ const manifest = JSON.parse(await readFile8(join7(repositoryRoot, "package.json"), "utf8"));
1333
+ if (typeof manifest.bin === "string" && typeof manifest.name === "string") {
1334
+ return [manifest.name.slice(manifest.name.lastIndexOf("/") + 1)];
1335
+ }
1336
+ if (manifest.bin && typeof manifest.bin === "object") return Object.keys(manifest.bin);
1337
+ } catch {
1338
+ }
1339
+ return [];
1340
+ }
1341
+ function escapeRegExp(value) {
1342
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1343
+ }
1344
+ var MULTI_FILE_TASK = /\b(trace|follow|flows?|through|across|between|end[- ]to[- ]end|pipeline)\b|\bfrom\b.+\bto\b/i;
1345
+ function expandAlongImports(ranked, map, evidence, task, options = {}) {
1346
+ if (!MULTI_FILE_TASK.test(task)) return { paths: ranked, explain: null };
1347
+ const seeds = ranked.slice(0, EXPANSION.seeds);
1348
+ const links = /* @__PURE__ */ new Map();
1349
+ const graphOnly = /* @__PURE__ */ new Map();
1350
+ for (const edge of map.edges) {
1351
+ for (const [seed, other] of [
1352
+ [edge.from, edge.to],
1353
+ [edge.to, edge.from]
1354
+ ]) {
1355
+ if (!seeds.includes(seed) || seeds.includes(other)) continue;
1356
+ if (!evidence.has(other)) {
1357
+ if (!options.allowGraphOnly || edge.from !== seed || TEST_PATH.test(other) || NON_SOURCE_PATH.test(other))
1358
+ continue;
1359
+ graphOnly.set(
1360
+ other,
1361
+ Math.max(
1362
+ graphOnly.get(other) ?? 0,
1363
+ edge.symbols?.length ? 2 : edge.kind === "import" ? 1 : 0
1364
+ )
1365
+ );
1366
+ }
1367
+ links.set(other, (links.get(other) ?? /* @__PURE__ */ new Set()).add(seed));
1368
+ }
1369
+ }
1370
+ const scoreOf = (path) => evidence.get(path) ?? 0;
1371
+ const added = [...links].sort(
1372
+ ([left, a], [right, b]) => b.size - a.size || scoreOf(right) - scoreOf(left) || (graphOnly.get(right) ?? 0) - (graphOnly.get(left) ?? 0) || left.localeCompare(right)
1373
+ ).slice(0, EXPANSION.files).map(([path]) => path);
1374
+ if (added.length === 0) return { paths: ranked, explain: null };
1375
+ const paths = ranked.filter((path) => !added.includes(path));
1376
+ for (const path of added) {
1377
+ const credited = scoreOf(path) + 1;
1378
+ const at = paths.findIndex(
1379
+ (other, index) => index >= seeds.length && scoreOf(other) < credited
1380
+ );
1381
+ paths.splice(at < 0 ? paths.length : at, 0, path);
1382
+ }
1383
+ return {
1384
+ paths,
1385
+ explain: `expansion: ${added.length} import neighbour(s) of the top ${seeds.length} target(s) ranked with the import edge credited as a task term (${added.join(", ")}; multi-file task wording)` + (added.some((path) => graphOnly.has(path)) ? "; graph-only import bridge" : "")
1386
+ };
1387
+ }
1388
+ function rankEvidenceCandidates(lexical, admittedKeywords, vector, partial) {
1389
+ const admitted = /* @__PURE__ */ new Set([...admittedKeywords, ...vector]);
1390
+ const established = fuseRankings([lexical, vector]).filter((path) => admitted.has(path));
1391
+ return [.../* @__PURE__ */ new Set([...established, ...partial])];
1392
+ }
1393
+ function fuseRankings(lists, k = 60) {
1394
+ const scores = /* @__PURE__ */ new Map();
1395
+ for (const list of lists) {
1396
+ list.forEach((path, index) => {
1397
+ scores.set(path, (scores.get(path) ?? 0) + 1 / (k + index + 1));
1398
+ });
1399
+ }
1400
+ return [...scores].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([path]) => path);
1401
+ }
1402
+ var TASK_STOPWORDS = new Set(
1403
+ "the and for are was were how does did which where when what why who with without from into onto that this these those there their them then than have has had not but can could should would will its it's our your you all any each some such only also just like make makes made use uses used get gets set sets new old way".split(
1404
+ " "
1405
+ )
1406
+ );
1407
+ var EVIDENCE_FLOOR = { keywordScore: 1.5, vectorTerms: 2 };
1408
+ async function hasMatchedPassageEvidence(repositoryRoot, path, hits, terms, symbols) {
1409
+ const source = await readBoundedSource(repositoryRoot, path);
1410
+ if (source === null) return false;
1411
+ if (countTerms(source, terms) >= EVIDENCE_FLOOR.vectorTerms) return true;
1412
+ const lines = source.split(/\r?\n/);
1413
+ return hits.some((hit) => {
1414
+ if (hit.path !== path || hit.endLine > lines.length) return false;
1415
+ const passage = lines.slice(hit.startLine - 1, hit.endLine);
1416
+ const matched = countTerms(passage.join(" "), terms);
1417
+ return matched >= EVIDENCE_FLOOR.vectorTerms || matched >= 1 && terms.length === 2 && hit.rank <= 3 && symbols.some(
1418
+ (symbol) => hit.startLine <= symbol.endLine && hit.endLine >= symbol.startLine
1419
+ );
1420
+ });
1421
+ }
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;
1424
+ function scoreTaskPaths(entries, task) {
1425
+ const terms = taskTerms(task);
1426
+ if (terms.length === 0) return [];
1427
+ const wantsTests = terms.some((term) => term === "test" || term === "spec");
1428
+ const candidates = entries.flatMap((entry) => {
1429
+ if (TEST_PATH.test(entry.path) && !wantsTests) return [];
1430
+ const words = new Set(
1431
+ splitWords(
1432
+ `${entry.path} ${entry.summary} ${entry.symbols.join(" ")} ${entry.symbolTable?.map((symbol) => symbol.name).join(" ") ?? ""}`
1433
+ )
1434
+ );
1435
+ const llmWords = new Set(entry.llmText ? splitWords(entry.llmText) : []);
1436
+ const fileWords = new Set(splitWords(entry.path.slice(entry.path.lastIndexOf("/") + 1)));
1437
+ return [{ path: entry.path, words, llmWords, fileWords }];
1438
+ });
1439
+ const idf = new Map(
1440
+ terms.map((term) => {
1441
+ const df = candidates.filter(
1442
+ (candidate) => candidate.words.has(term) || candidate.llmWords.has(term)
1443
+ ).length;
1444
+ return [term, Math.log((candidates.length + 1) / (df + 1)) + 1];
1445
+ })
1446
+ );
1447
+ return candidates.flatMap(({ path, words, llmWords, fileWords }) => {
1448
+ let score = 0;
1449
+ let weighted = 0;
1450
+ for (const term of terms) {
1451
+ if (words.has(term)) {
1452
+ const points = fileWords.has(term) ? 1.5 : 1;
1453
+ score += points;
1454
+ weighted += points * (idf.get(term) ?? 1);
1455
+ } else if (llmWords.has(term)) {
1456
+ weighted += 0.5 * (idf.get(term) ?? 1);
1457
+ }
1458
+ }
1459
+ if (weighted === 0) return [];
1460
+ if (NON_SOURCE_PATH.test(path)) {
1461
+ score *= 0.5;
1462
+ weighted *= 0.5;
1463
+ }
1464
+ return [{ path, score, order: weighted }];
1465
+ }).sort(
1466
+ (left, right) => right.order - left.order || right.score - left.score || left.path.localeCompare(right.path)
1467
+ );
1468
+ }
1469
+ 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
+ );
1473
+ }
1474
+ function taskTerms(task) {
1475
+ return [...new Set(splitWords(task).filter((term) => !TASK_STOPWORDS.has(term)))];
1476
+ }
1477
+ function countTerms(text, terms) {
1478
+ if (terms.length === 0) return 0;
1479
+ const words = new Set(splitWords(text));
1480
+ return terms.filter((term) => words.has(term)).length;
1481
+ }
1482
+ var CODE_IDENTIFIER = /[a-z0-9][A-Z]|[_$.]/;
1483
+ function identifierTokens(task) {
1484
+ const tokens = task.match(/[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*/g) ?? [];
1485
+ return [...new Set(tokens.flatMap((token) => [token, ...token.split(".")]))];
1486
+ }
1487
+ function namedSegments(name, tokens) {
1488
+ return name.split(".").filter((segment) => tokens.includes(segment)).length;
1489
+ }
1490
+ async function loadProjectArchitectureMap(repositoryRoot, manifestSource) {
1491
+ const [config, manifest] = await Promise.all([readConfigFile(repositoryRoot), manifestSource]);
1492
+ const builtIn = (await loadArchitectureMap(repositoryRoot, manifest)).map;
1493
+ if (config.graphify?.enabled && await graphifyGraphExists(repositoryRoot, config.graphify.graphPath)) {
1494
+ try {
1495
+ const graphPath = join7(
1496
+ repositoryRoot,
1497
+ config.graphify.graphPath ?? "graphify-out/graph.json"
1498
+ );
1499
+ const [graph, graphFile, sourceHashes2] = await Promise.all([
1500
+ loadGraphifyMap(graphPath, repositoryRoot),
1501
+ stat4(graphPath),
1502
+ readGraphSourceSnapshot(graphPath)
1503
+ ]);
1504
+ const livePaths = new Set(
1505
+ (await Promise.all(
1506
+ graph.modules.map(async (module) => {
1507
+ try {
1508
+ const absolute = await realpath(join7(repositoryRoot, module.path));
1509
+ const inside = relative6(await realpath(repositoryRoot), absolute);
1510
+ if (inside.startsWith("..") || isAbsolute3(inside)) return null;
1511
+ const source = await stat4(absolute);
1512
+ const fresh = sourceHashes2 === null ? source.mtimeMs <= graphFile.mtimeMs : sourceHashes2[module.path] !== void 0 && sourceHashes2[module.path] === manifest.entries[module.path]?.hash;
1513
+ return source.isFile() && fresh ? module.path : null;
1514
+ } catch {
1515
+ return null;
1516
+ }
1517
+ })
1518
+ )).filter((path) => path !== null)
1519
+ );
1520
+ const modules = new Map(builtIn.modules.map((module) => [module.path, module]));
1521
+ for (const module of graph.modules) {
1522
+ if (livePaths.has(module.path) && !modules.has(module.path))
1523
+ modules.set(module.path, module);
1524
+ }
1525
+ const edges = new Map(builtIn.edges.map((edge) => [`${edge.from}\0${edge.to}`, edge]));
1526
+ let freshGraphEdges = 0;
1527
+ for (const edge of graph.edges) {
1528
+ if (!livePaths.has(edge.from) || !livePaths.has(edge.to)) continue;
1529
+ freshGraphEdges++;
1530
+ const key = `${edge.from}\0${edge.to}`;
1531
+ const scanned = edges.get(key);
1532
+ edges.set(
1533
+ key,
1534
+ scanned ? { ...scanned, ...edge.symbols ? { symbols: edge.symbols } : {} } : edge
1535
+ );
1536
+ }
1537
+ const mergedEdges = [...edges.values()].sort(
1538
+ (left, right) => `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`)
1539
+ );
1540
+ const importsByPath = /* @__PURE__ */ new Map();
1541
+ for (const edge of mergedEdges) {
1542
+ const imports = importsByPath.get(edge.from) ?? [];
1543
+ imports.push(edge.to);
1544
+ importsByPath.set(edge.from, imports);
1545
+ }
1546
+ return {
1547
+ modules: [...modules.values()].map((module) => ({ ...module, imports: importsByPath.get(module.path) ?? [] })).sort((left, right) => left.path.localeCompare(right.path)),
1548
+ edges: mergedEdges,
1549
+ source: freshGraphEdges > 0 ? "graphify+imports" : "imports"
1550
+ };
1551
+ } catch {
1552
+ }
1553
+ }
1554
+ return { ...builtIn, source: "imports" };
1555
+ }
1556
+
1557
+ export {
1558
+ buildArchitectureMap,
1559
+ queryMap,
1560
+ neighbours,
1561
+ loadArchitectureMap,
1562
+ indexRepository,
1563
+ loadFreshIndex,
1564
+ setupGraphify,
1565
+ generateGraphifyGraph,
1566
+ loadGraphifyMap,
1567
+ TASK_PATH_LIMIT,
1568
+ readBoundedSource,
1569
+ discoverTaskPaths,
1570
+ TEST_PATH,
1571
+ NON_SOURCE_PATH,
1572
+ taskTerms,
1573
+ countTerms,
1574
+ identifierTokens,
1575
+ namedSegments,
1576
+ loadProjectArchitectureMap
1577
+ };
1578
+ //# sourceMappingURL=chunk-IBMJYIEP.js.map