clinkx 0.2.3 → 0.3.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 (88) hide show
  1. package/README.md +40 -1
  2. package/conf/prompts/json-codereviewer.txt +1 -0
  3. package/conf/prompts/json.txt +1 -0
  4. package/dist/allowed-roots.d.ts +12 -0
  5. package/dist/allowed-roots.js +94 -0
  6. package/dist/allowed-roots.js.map +1 -0
  7. package/dist/context-builder/anchors.d.ts +11 -0
  8. package/dist/context-builder/anchors.js +19 -0
  9. package/dist/context-builder/anchors.js.map +1 -0
  10. package/dist/context-builder/bundle.d.ts +76 -0
  11. package/dist/context-builder/bundle.js +3573 -0
  12. package/dist/context-builder/bundle.js.map +1 -0
  13. package/dist/context-builder/codemap.d.ts +17 -0
  14. package/dist/context-builder/codemap.js +301 -0
  15. package/dist/context-builder/codemap.js.map +1 -0
  16. package/dist/context-builder/config.d.ts +60 -0
  17. package/dist/context-builder/config.js +204 -0
  18. package/dist/context-builder/config.js.map +1 -0
  19. package/dist/context-builder/delegate-packet.d.ts +182 -0
  20. package/dist/context-builder/delegate-packet.js +898 -0
  21. package/dist/context-builder/delegate-packet.js.map +1 -0
  22. package/dist/context-builder/delegate-reconstruction.d.ts +13 -0
  23. package/dist/context-builder/delegate-reconstruction.js +220 -0
  24. package/dist/context-builder/delegate-reconstruction.js.map +1 -0
  25. package/dist/context-builder/delegate-validation.d.ts +18 -0
  26. package/dist/context-builder/delegate-validation.js +367 -0
  27. package/dist/context-builder/delegate-validation.js.map +1 -0
  28. package/dist/context-builder/delegate.d.ts +24 -0
  29. package/dist/context-builder/delegate.js +133 -0
  30. package/dist/context-builder/delegate.js.map +1 -0
  31. package/dist/context-builder/diagnostics.d.ts +27 -0
  32. package/dist/context-builder/diagnostics.js +74 -0
  33. package/dist/context-builder/diagnostics.js.map +1 -0
  34. package/dist/context-builder/discovery.d.ts +118 -0
  35. package/dist/context-builder/discovery.js +1829 -0
  36. package/dist/context-builder/discovery.js.map +1 -0
  37. package/dist/context-builder/globs.d.ts +14 -0
  38. package/dist/context-builder/globs.js +175 -0
  39. package/dist/context-builder/globs.js.map +1 -0
  40. package/dist/context-builder/handler.d.ts +17 -0
  41. package/dist/context-builder/handler.js +3848 -0
  42. package/dist/context-builder/handler.js.map +1 -0
  43. package/dist/context-builder/path-signals.d.ts +7 -0
  44. package/dist/context-builder/path-signals.js +66 -0
  45. package/dist/context-builder/path-signals.js.map +1 -0
  46. package/dist/context-builder/paths.d.ts +30 -0
  47. package/dist/context-builder/paths.js +153 -0
  48. package/dist/context-builder/paths.js.map +1 -0
  49. package/dist/context-builder/progress.d.ts +15 -0
  50. package/dist/context-builder/progress.js +70 -0
  51. package/dist/context-builder/progress.js.map +1 -0
  52. package/dist/context-builder/receipt.d.ts +89 -0
  53. package/dist/context-builder/receipt.js +169 -0
  54. package/dist/context-builder/receipt.js.map +1 -0
  55. package/dist/context-builder/run-id.d.ts +7 -0
  56. package/dist/context-builder/run-id.js +31 -0
  57. package/dist/context-builder/run-id.js.map +1 -0
  58. package/dist/context-builder/schema.d.ts +431 -0
  59. package/dist/context-builder/schema.js +293 -0
  60. package/dist/context-builder/schema.js.map +1 -0
  61. package/dist/context-builder/secrets.d.ts +13 -0
  62. package/dist/context-builder/secrets.js +222 -0
  63. package/dist/context-builder/secrets.js.map +1 -0
  64. package/dist/context-builder/selection.d.ts +52 -0
  65. package/dist/context-builder/selection.js +1649 -0
  66. package/dist/context-builder/selection.js.map +1 -0
  67. package/dist/context-builder/status.d.ts +45 -0
  68. package/dist/context-builder/status.js +105 -0
  69. package/dist/context-builder/status.js.map +1 -0
  70. package/dist/context-builder/storage.d.ts +85 -0
  71. package/dist/context-builder/storage.js +746 -0
  72. package/dist/context-builder/storage.js.map +1 -0
  73. package/dist/context-builder/token-estimate.d.ts +8 -0
  74. package/dist/context-builder/token-estimate.js +17 -0
  75. package/dist/context-builder/token-estimate.js.map +1 -0
  76. package/dist/paths.d.ts +8 -0
  77. package/dist/paths.js +31 -43
  78. package/dist/paths.js.map +1 -1
  79. package/dist/pipeline.d.ts +47 -3
  80. package/dist/pipeline.js +135 -39
  81. package/dist/pipeline.js.map +1 -1
  82. package/dist/server.d.ts +1 -0
  83. package/dist/server.js +40 -10
  84. package/dist/server.js.map +1 -1
  85. package/package.json +2 -1
  86. package/dist/artifacts.d.ts +0 -9
  87. package/dist/artifacts.js +0 -24
  88. package/dist/artifacts.js.map +0 -1
@@ -0,0 +1,1829 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { lstat as fsLstat, open as fsOpen, readdir as fsReaddir, realpath as fsRealpath, stat as fsStat, } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { isSamePathOrDescendant } from "../allowed-roots.js";
5
+ import { CancellationError } from "../errors.js";
6
+ import { CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS, } from "./config.js";
7
+ import { CONTEXT_BUILDER_REQUESTED_EVIDENCE_OMISSION_REASON, requestedEvidenceOmission, } from "./diagnostics.js";
8
+ import { compileContextBuilderGlobs, CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS, } from "./globs.js";
9
+ import { normalizeContextBuilderRootRelativePath, rootRelativePathForAbsolutePath, } from "./paths.js";
10
+ import { directoryDistance, genericPathKind, isTestPath, normalizedPathStem, } from "./path-signals.js";
11
+ import { isBlockingSecretFinding, redactHighConfidenceAssignmentSecrets, scanHighConfidenceSecretContent, scanSecretPath, } from "./secrets.js";
12
+ import { CONTEXT_BUILDER_PUBLIC_BUDGET_HARD_LIMITS, } from "./schema.js";
13
+ const SEARCH_WINDOW_LINES_BEFORE = 19;
14
+ const SEARCH_WINDOW_LINES_AFTER = 20;
15
+ const SEARCH_BRIDGE_GAP_LINES = 20;
16
+ const SEARCH_MAX_MERGED_SPAN_LINES = 160;
17
+ const DEFAULT_MAX_FULL_FOCUS_SLICE_BYTES = 48_000;
18
+ const DEFAULT_MAX_FULL_IMPLEMENTATION_FOCUS_SLICE_BYTES = 96_000;
19
+ const IMPORT_EXPANSION_PASSES = 3;
20
+ const PROJECT_SUPPORT_FILENAMES = Object.freeze([
21
+ "package.json",
22
+ "vitest.config.ts",
23
+ "vite.config.ts",
24
+ ]);
25
+ export async function discoverContextBuilderCandidates(options) {
26
+ const internalCaps = options.internal_caps ?? CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS;
27
+ const matcher = compileContextBuilderGlobs({
28
+ include: [...(options.include_globs ?? [])],
29
+ exclude: [
30
+ ...CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS,
31
+ ...(options.exclude_globs ?? []),
32
+ ],
33
+ });
34
+ const state = {
35
+ tree: [],
36
+ contentCandidates: [],
37
+ omissions: [],
38
+ requestedEvidenceOmissionKeys: new Set(),
39
+ processedFileKeys: new Set(),
40
+ metrics: {
41
+ files_seen: 0,
42
+ files_considered: 0,
43
+ scan_cap_hit: false,
44
+ search_cap_hit: false,
45
+ discovery_byte_cap_hit: false,
46
+ discovery_candidate_cap_hit: false,
47
+ candidate_cap_hit: false,
48
+ bytes_read: 0,
49
+ },
50
+ stopped: false,
51
+ };
52
+ for (const root of options.roots) {
53
+ if (state.stopped) {
54
+ break;
55
+ }
56
+ assertNotAborted(options.signal);
57
+ await processExactFocusFiles({
58
+ root,
59
+ focusPaths: options.focus_paths ?? [],
60
+ matcher,
61
+ budget: options.budget,
62
+ internalCaps,
63
+ ...(options.signal == null ? {} : { signal: options.signal }),
64
+ fs: {
65
+ readdir: options.fs?.readdir ?? fsReaddir,
66
+ lstat: options.fs?.lstat ?? fsLstat,
67
+ open: options.fs?.open ?? fsOpen,
68
+ realpath: options.fs?.realpath ?? fsRealpath,
69
+ stat: options.fs?.stat ?? fsStat,
70
+ },
71
+ state,
72
+ });
73
+ await traverseDirectory({
74
+ root,
75
+ absoluteDir: root.path,
76
+ relativeDir: "",
77
+ matcher,
78
+ focusPaths: options.focus_paths ?? [],
79
+ budget: options.budget,
80
+ internalCaps,
81
+ excludedRealRoots: options.excluded_real_roots ?? [],
82
+ ...(options.signal == null ? {} : { signal: options.signal }),
83
+ fs: {
84
+ readdir: options.fs?.readdir ?? fsReaddir,
85
+ lstat: options.fs?.lstat ?? fsLstat,
86
+ open: options.fs?.open ?? fsOpen,
87
+ realpath: options.fs?.realpath ?? fsRealpath,
88
+ stat: options.fs?.stat ?? fsStat,
89
+ },
90
+ state,
91
+ });
92
+ await processImportAndSupportFiles({
93
+ root,
94
+ matcher,
95
+ focusPaths: options.focus_paths ?? [],
96
+ budget: options.budget,
97
+ internalCaps,
98
+ ...(options.signal == null ? {} : { signal: options.signal }),
99
+ fs: {
100
+ readdir: options.fs?.readdir ?? fsReaddir,
101
+ lstat: options.fs?.lstat ?? fsLstat,
102
+ open: options.fs?.open ?? fsOpen,
103
+ realpath: options.fs?.realpath ?? fsRealpath,
104
+ stat: options.fs?.stat ?? fsStat,
105
+ },
106
+ state,
107
+ });
108
+ }
109
+ state.tree.sort(compareTreeFiles);
110
+ state.contentCandidates.sort(compareContentCandidates);
111
+ const search = buildCandidateSearchRecords({
112
+ candidates: state.contentCandidates,
113
+ ...(options.task == null ? {} : { task: options.task }),
114
+ focusPaths: [...(options.focus_paths ?? [])],
115
+ searchTerms: expandSearchTerms([...(options.search_terms ?? [])]),
116
+ maxSearchResults: options.budget.max_search_results,
117
+ maxPreviewBytes: internalCaps.max_search_preview_bytes,
118
+ maxCandidateBytes: internalCaps.max_candidate_bytes,
119
+ ...(options.signal == null ? {} : { signal: options.signal }),
120
+ });
121
+ if (search.capHit) {
122
+ state.metrics.search_cap_hit = true;
123
+ }
124
+ const slices = buildCandidateSliceRecords({
125
+ candidates: state.contentCandidates,
126
+ focusPaths: [...(options.focus_paths ?? [])],
127
+ searchRecords: search.records,
128
+ maxCandidateSlices: internalCaps.max_candidate_slices,
129
+ maxCandidateBytes: Math.max(0, internalCaps.max_candidate_bytes - search.candidateBytes),
130
+ maxSearchSliceBytesPerCandidate: options.budget.max_bytes_per_file,
131
+ maxFullFocusSliceBytes: Math.min(options.budget.max_bytes_per_file, DEFAULT_MAX_FULL_IMPLEMENTATION_FOCUS_SLICE_BYTES),
132
+ ...(options.signal == null ? {} : { signal: options.signal }),
133
+ });
134
+ state.omissions.push(...slices.omissions);
135
+ if (slices.capHit) {
136
+ state.metrics.candidate_cap_hit = true;
137
+ }
138
+ auditScanCapFocusedPaths({
139
+ focusPaths: [...(options.focus_paths ?? [])],
140
+ state,
141
+ });
142
+ return {
143
+ tree: state.tree,
144
+ content_candidates: state.contentCandidates,
145
+ search: search.records,
146
+ slices: slices.records,
147
+ omissions: state.omissions,
148
+ metrics: state.metrics,
149
+ };
150
+ }
151
+ function expandSearchTerms(searchTerms) {
152
+ const expanded = [];
153
+ const seen = new Set();
154
+ for (const term of searchTerms) {
155
+ addExpandedSearchTerm(expanded, seen, term);
156
+ for (const derived of deriveGenericSearchTerms(term)) {
157
+ addExpandedSearchTerm(expanded, seen, derived);
158
+ }
159
+ }
160
+ return expanded;
161
+ }
162
+ function addExpandedSearchTerm(expanded, seen, term) {
163
+ const normalized = term.trim();
164
+ if (normalized.length === 0) {
165
+ return;
166
+ }
167
+ const key = normalized.toLowerCase();
168
+ if (seen.has(key)) {
169
+ return;
170
+ }
171
+ expanded.push(normalized);
172
+ seen.add(key);
173
+ }
174
+ function deriveGenericSearchTerms(term) {
175
+ const tokens = tokenizeGenericSearchTerm(term);
176
+ const terms = new Set(tokens);
177
+ for (let index = 0; index < tokens.length - 1; index++) {
178
+ const current = tokens[index];
179
+ const next = tokens[index + 1];
180
+ if (current == null || next == null) {
181
+ continue;
182
+ }
183
+ terms.add(`${current}_${next}`);
184
+ terms.add(`${current}${next}`);
185
+ }
186
+ return [...terms].filter((value) => value.length >= 3).sort();
187
+ }
188
+ function tokenizeGenericSearchTerm(value) {
189
+ return value
190
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
191
+ .replace(/\.[A-Za-z0-9]+$/g, " ")
192
+ .split(/[^A-Za-z0-9]+/)
193
+ .map((word) => normalizeGenericSearchWord(word))
194
+ .filter((word) => word.length >= 3 && !GENERIC_SEARCH_STOPWORDS.has(word));
195
+ }
196
+ function normalizeGenericSearchWord(word) {
197
+ const lower = word.toLowerCase();
198
+ if (lower.length > 4 && lower.endsWith("ies")) {
199
+ return `${lower.slice(0, -3)}y`;
200
+ }
201
+ if (lower.length > 3 && lower.endsWith("s")) {
202
+ return lower.slice(0, -1);
203
+ }
204
+ return lower;
205
+ }
206
+ const GENERIC_SEARCH_STOPWORDS = new Set([
207
+ "the",
208
+ "and",
209
+ "for",
210
+ "with",
211
+ "that",
212
+ "this",
213
+ "from",
214
+ "into",
215
+ "without",
216
+ "change",
217
+ "fix",
218
+ "add",
219
+ "new",
220
+ "safe",
221
+ "safely",
222
+ "test",
223
+ "tests",
224
+ ]);
225
+ async function processImportAndSupportFiles(input) {
226
+ for (let pass = 0; pass < IMPORT_EXPANSION_PASSES; pass++) {
227
+ const before = input.state.contentCandidates.length;
228
+ const dependencies = dependencyPathChoicesForCandidates(input.root, input.state.contentCandidates.filter((candidate) => candidate.root_index === input.root.root_index));
229
+ for (const choices of dependencies) {
230
+ assertNotAborted(input.signal);
231
+ const relativePath = await firstExistingPath(input.root, choices, input.fs);
232
+ if (relativePath == null) {
233
+ continue;
234
+ }
235
+ await processFile({
236
+ root: input.root,
237
+ absolutePath: path.join(input.root.path, relativePath),
238
+ relativePath,
239
+ matcher: input.matcher,
240
+ focusPaths: input.focusPaths,
241
+ budget: input.budget,
242
+ internalCaps: input.internalCaps,
243
+ ignoreIncludeGlob: true,
244
+ ignoreScanCap: true,
245
+ selectionHint: "local_dependency",
246
+ ...(input.signal == null ? {} : { signal: input.signal }),
247
+ fs: input.fs,
248
+ state: input.state,
249
+ });
250
+ }
251
+ if (input.state.contentCandidates.length === before) {
252
+ break;
253
+ }
254
+ }
255
+ const supportFiles = projectSupportFilesForCandidates(input.root, input.state.contentCandidates.filter((candidate) => candidate.root_index === input.root.root_index));
256
+ for (const relativePath of supportFiles) {
257
+ assertNotAborted(input.signal);
258
+ if ((await firstExistingPath(input.root, [relativePath], input.fs)) == null) {
259
+ continue;
260
+ }
261
+ await processFile({
262
+ root: input.root,
263
+ absolutePath: path.join(input.root.path, relativePath),
264
+ relativePath,
265
+ matcher: input.matcher,
266
+ focusPaths: input.focusPaths,
267
+ budget: input.budget,
268
+ internalCaps: input.internalCaps,
269
+ ignoreIncludeGlob: true,
270
+ ignoreScanCap: true,
271
+ selectionHint: "project_support",
272
+ ...(input.signal == null ? {} : { signal: input.signal }),
273
+ fs: input.fs,
274
+ state: input.state,
275
+ });
276
+ }
277
+ }
278
+ function dependencyPathChoicesForCandidates(root, candidates) {
279
+ const choices = [];
280
+ const seen = new Set();
281
+ for (const candidate of candidates) {
282
+ for (const specifier of importSpecifiers(candidate.content)) {
283
+ const resolved = resolveImportSpecifierChoices(root, candidate.path, specifier);
284
+ const useful = resolved.filter((pathChoice) => isUsefulLocalDependency(candidate.path, pathChoice));
285
+ if (useful.length === 0) {
286
+ continue;
287
+ }
288
+ const key = useful.join("\u0000");
289
+ if (!seen.has(key)) {
290
+ choices.push(useful);
291
+ seen.add(key);
292
+ }
293
+ }
294
+ }
295
+ return choices.sort((left, right) => left[0].localeCompare(right[0]));
296
+ }
297
+ async function firstExistingPath(root, choices, fs) {
298
+ for (const relativePath of choices) {
299
+ try {
300
+ const stats = await fs.stat(path.join(root.path, relativePath));
301
+ if (stats.isFile()) {
302
+ return relativePath;
303
+ }
304
+ }
305
+ catch (error) {
306
+ if (isExpectedDescendantFilesystemError(error)) {
307
+ continue;
308
+ }
309
+ throw error;
310
+ }
311
+ }
312
+ return null;
313
+ }
314
+ function projectSupportFilesForCandidates(root, candidates) {
315
+ const prefixes = new Set();
316
+ for (const candidate of candidates) {
317
+ const prefix = projectPackagePrefix(candidate.path);
318
+ if (prefix != null) {
319
+ prefixes.add(prefix);
320
+ }
321
+ }
322
+ const paths = [];
323
+ for (const prefix of [...prefixes].sort()) {
324
+ for (const filename of PROJECT_SUPPORT_FILENAMES) {
325
+ const relativePath = prefix.length === 0 ? filename : `${prefix}/${filename}`;
326
+ if (isSamePathOrDescendant(path.join(root.path, relativePath), root.path)) {
327
+ paths.push(relativePath);
328
+ }
329
+ }
330
+ }
331
+ return paths;
332
+ }
333
+ function importSpecifiers(content) {
334
+ const specifiers = [];
335
+ const patterns = [
336
+ /\bimport\s+(?:type\s+)?(?:[^'";]+?\s+from\s+)?['"]([^'"]+)['"]/g,
337
+ /\bexport\s+(?:type\s+)?[^'";]+?\s+from\s+['"]([^'"]+)['"]/g,
338
+ /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
339
+ /^\s*from\s+([A-Za-z_][\w.]*|\.+[\w.]*)\s+import\b/gm,
340
+ /^\s*import\s+(app(?:\.[A-Za-z_][\w]*)+)\b/gm,
341
+ ];
342
+ for (const pattern of patterns) {
343
+ let match;
344
+ while ((match = pattern.exec(content)) != null) {
345
+ const specifier = match[1];
346
+ if (specifier != null) {
347
+ specifiers.push(specifier);
348
+ }
349
+ }
350
+ }
351
+ return specifiers;
352
+ }
353
+ function resolveImportSpecifierChoices(root, importerPath, specifier) {
354
+ const base = resolveImportBase(importerPath, specifier);
355
+ if (base == null) {
356
+ return [];
357
+ }
358
+ const choices = [];
359
+ for (const candidate of importResolutionCandidates(base)) {
360
+ const absolute = path.join(root.path, candidate);
361
+ if (!isSamePathOrDescendant(absolute, root.path)) {
362
+ continue;
363
+ }
364
+ try {
365
+ const normalized = normalizeContextBuilderRootRelativePath(candidate);
366
+ if (normalized === candidate) {
367
+ choices.push(candidate);
368
+ }
369
+ }
370
+ catch {
371
+ continue;
372
+ }
373
+ }
374
+ return choices;
375
+ }
376
+ function resolveImportBase(importerPath, specifier) {
377
+ if (specifier.startsWith("@/")) {
378
+ const srcPrefix = sourceRootPrefix(importerPath);
379
+ return srcPrefix == null ? null : `${srcPrefix}/${specifier.slice(2)}`;
380
+ }
381
+ if (specifier.startsWith("app.") && importerPath.endsWith(".py")) {
382
+ const appPrefix = pythonAppRootPrefix(importerPath);
383
+ return appPrefix == null
384
+ ? null
385
+ : `${appPrefix}/${specifier.slice("app.".length).replaceAll(".", "/")}`;
386
+ }
387
+ if (specifier.startsWith(".") && importerPath.endsWith(".py")) {
388
+ return resolvePythonRelativeImportBase(importerPath, specifier);
389
+ }
390
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
391
+ return normalizeSlash(path.normalize(path.join(path.dirname(importerPath), specifier)));
392
+ }
393
+ return null;
394
+ }
395
+ function importResolutionCandidates(base) {
396
+ if (/\.(?:[cm]?[jt]sx?|json)$/.test(base)) {
397
+ return [base];
398
+ }
399
+ return [
400
+ `${base}.ts`,
401
+ `${base}.tsx`,
402
+ `${base}.js`,
403
+ `${base}.jsx`,
404
+ `${base}.py`,
405
+ `${base}.json`,
406
+ `${base}/index.ts`,
407
+ `${base}/index.tsx`,
408
+ `${base}/index.js`,
409
+ `${base}/index.jsx`,
410
+ `${base}/__init__.py`,
411
+ ];
412
+ }
413
+ // Adjacency radius (in directory hops) within which an imported file is treated
414
+ // as a useful local dependency regardless of stem/kind heuristics.
415
+ const LOCAL_DEPENDENCY_MAX_DIRECTORY_DISTANCE = 6;
416
+ function isUsefulLocalDependency(importerPath, dependencyPath) {
417
+ if (importerPath === dependencyPath) {
418
+ return false;
419
+ }
420
+ if (directoryDistance(importerPath, dependencyPath) <=
421
+ LOCAL_DEPENDENCY_MAX_DIRECTORY_DISTANCE) {
422
+ return true;
423
+ }
424
+ if (normalizedPathStem(importerPath) !== normalizedPathStem(dependencyPath)) {
425
+ return false;
426
+ }
427
+ return genericPathKind(importerPath) !== genericPathKind(dependencyPath);
428
+ }
429
+ function sourceRootPrefix(importerPath) {
430
+ const srcIndex = importerPath.indexOf("/src/");
431
+ if (srcIndex < 0) {
432
+ return importerPath.startsWith("src/") ? "src" : null;
433
+ }
434
+ return importerPath.slice(0, srcIndex + "/src".length);
435
+ }
436
+ function projectPackagePrefix(candidatePath) {
437
+ const srcIndex = candidatePath.indexOf("/src/");
438
+ if (srcIndex < 0) {
439
+ return null;
440
+ }
441
+ return candidatePath.slice(0, srcIndex);
442
+ }
443
+ function pythonAppRootPrefix(importerPath) {
444
+ const appIndex = importerPath.indexOf("/app/");
445
+ if (appIndex >= 0) {
446
+ return importerPath.slice(0, appIndex + "/app".length);
447
+ }
448
+ return importerPath.startsWith("app/") ? "app" : null;
449
+ }
450
+ function resolvePythonRelativeImportBase(importerPath, specifier) {
451
+ const match = /^(\.+)(.*)$/.exec(specifier);
452
+ if (match == null) {
453
+ return null;
454
+ }
455
+ const dots = match[1] ?? "";
456
+ const moduleTail = match[2] ?? "";
457
+ let baseDir = path.dirname(importerPath);
458
+ for (let index = 1; index < dots.length; index++) {
459
+ baseDir = path.dirname(baseDir);
460
+ }
461
+ const suffix = moduleTail.length === 0 ? "" : moduleTail.replaceAll(".", "/");
462
+ return normalizeSlash(path.normalize(suffix.length === 0 ? baseDir : path.join(baseDir, suffix)));
463
+ }
464
+ function normalizeSlash(value) {
465
+ return value.replaceAll(path.sep, "/");
466
+ }
467
+ async function processExactFocusFiles(input) {
468
+ const focusFiles = [...input.focusPaths]
469
+ .filter((focus) => focus.root_index === input.root.root_index)
470
+ .filter((focus) => focus.kind === "file")
471
+ .sort(compareFocusPathsForDiscovery);
472
+ for (const focus of focusFiles) {
473
+ assertNotAborted(input.signal);
474
+ await processFile({
475
+ root: input.root,
476
+ absolutePath: focus.absolute_path,
477
+ relativePath: focus.root_relative_path,
478
+ matcher: input.matcher,
479
+ focusPaths: input.focusPaths,
480
+ budget: input.budget,
481
+ internalCaps: input.internalCaps,
482
+ ignoreScanCap: true,
483
+ ...(input.signal == null ? {} : { signal: input.signal }),
484
+ fs: input.fs,
485
+ state: input.state,
486
+ });
487
+ }
488
+ }
489
+ export function buildCandidateSearchRecords(input) {
490
+ const maxPreviewBytes = input.maxPreviewBytes ??
491
+ CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_search_preview_bytes;
492
+ const maxCandidateBytes = input.maxCandidateBytes ??
493
+ CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_candidate_bytes;
494
+ const records = [];
495
+ let previewBytes = 0;
496
+ let candidateBytes = 0;
497
+ let capHit = false;
498
+ if (input.searchTerms.length === 0) {
499
+ return { records, capHit, candidateBytes };
500
+ }
501
+ const searchTerms = input.searchTerms
502
+ .map((term, index) => ({ term, index }))
503
+ .filter(({ term }) => term.trim().length > 0);
504
+ if (searchTerms.length === 0) {
505
+ return { records, capHit, candidateBytes };
506
+ }
507
+ const focusRank = new Map();
508
+ for (const focus of input.focusPaths ?? []) {
509
+ if (focus.kind !== "file") {
510
+ continue;
511
+ }
512
+ const key = candidateKey({
513
+ root_index: focus.root_index,
514
+ path: focus.root_relative_path,
515
+ });
516
+ if (!focusRank.has(key)) {
517
+ focusRank.set(key, focusRank.size);
518
+ }
519
+ }
520
+ const candidates = [...input.candidates].sort((left, right) => {
521
+ const leftRank = focusRank.get(candidateKey(left)) ?? Number.MAX_SAFE_INTEGER;
522
+ const rightRank = focusRank.get(candidateKey(right)) ?? Number.MAX_SAFE_INTEGER;
523
+ return (leftRank - rightRank ||
524
+ discoveryTaskClosureRank(left.path, input.task ?? "") -
525
+ discoveryTaskClosureRank(right.path, input.task ?? "") ||
526
+ compareContentCandidates(left, right));
527
+ });
528
+ const streams = [];
529
+ for (const candidate of candidates) {
530
+ assertNotAborted(input.signal);
531
+ const lines = splitTextLines(candidate.content);
532
+ for (const searchTerm of searchTerms) {
533
+ streams.push({
534
+ candidate,
535
+ searchTerm,
536
+ lines,
537
+ lineIndex: 0,
538
+ fromIndex: 0,
539
+ done: false,
540
+ });
541
+ }
542
+ }
543
+ let activeStreams = streams.length;
544
+ while (activeStreams > 0) {
545
+ let foundInRound = false;
546
+ for (const stream of streams) {
547
+ assertNotAborted(input.signal);
548
+ if (stream.done) {
549
+ continue;
550
+ }
551
+ const hit = nextSearchHit(stream);
552
+ if (hit == null) {
553
+ stream.done = true;
554
+ activeStreams--;
555
+ continue;
556
+ }
557
+ foundInRound = true;
558
+ if (records.length >= input.maxSearchResults) {
559
+ capHit = true;
560
+ return { records, capHit, candidateBytes };
561
+ }
562
+ const preview = boundedPreview(hit.line, hit.columnIndex, stream.searchTerm.term.length);
563
+ const previewSize = Buffer.byteLength(preview, "utf8");
564
+ const bounded = previewBytes + previewSize <= maxPreviewBytes ? preview : "";
565
+ const record = {
566
+ root_index: stream.candidate.root_index,
567
+ path: stream.candidate.path,
568
+ line: hit.lineIndex + 1,
569
+ column: hit.columnIndex + 1,
570
+ search_term: stream.searchTerm.term,
571
+ search_term_index: stream.searchTerm.index,
572
+ preview: bounded,
573
+ };
574
+ const recordBytes = Buffer.byteLength(`${JSON.stringify(record)}\n`, "utf8");
575
+ if (candidateBytes + recordBytes > maxCandidateBytes) {
576
+ capHit = true;
577
+ return { records, capHit, candidateBytes };
578
+ }
579
+ previewBytes += Buffer.byteLength(bounded, "utf8");
580
+ candidateBytes += recordBytes;
581
+ records.push(record);
582
+ }
583
+ if (!foundInRound) {
584
+ break;
585
+ }
586
+ }
587
+ return { records, capHit, candidateBytes };
588
+ }
589
+ export function buildCandidateSliceRecords(input) {
590
+ const maxCandidateSlices = input.maxCandidateSlices ??
591
+ CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_candidate_slices;
592
+ const maxCandidateBytes = input.maxCandidateBytes ??
593
+ CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_candidate_bytes;
594
+ const maxFullFocusSliceBytes = input.maxFullFocusSliceBytes ??
595
+ DEFAULT_MAX_FULL_FOCUS_SLICE_BYTES;
596
+ const candidateByIdentity = new Map(input.candidates.map((candidate) => [candidateKey(candidate), candidate]));
597
+ const records = [];
598
+ const omissions = [];
599
+ let bytes = 0;
600
+ let capHit = false;
601
+ const searchSliceBytesByCandidate = new Map();
602
+ const focusedLargeSearchCandidates = new Map();
603
+ const expectedFocusedSearchRanges = new Map();
604
+ const survivedFocusedSearchRanges = new Map();
605
+ const focusSliceSurvivors = new Set();
606
+ const omittedLargeFocusedCandidates = new Set();
607
+ const omittedRequestedCandidates = new Set();
608
+ const pushRequestedCandidateOmission = (candidate, kind, message) => {
609
+ const key = candidateKey(candidate);
610
+ if (omittedRequestedCandidates.has(key)) {
611
+ return;
612
+ }
613
+ omissions.push(requestedEvidenceOmission({
614
+ root_index: candidate.root_index,
615
+ kind,
616
+ message,
617
+ paths: [candidate.path],
618
+ }));
619
+ omittedRequestedCandidates.add(key);
620
+ };
621
+ const pushMissingFocusedSearchOmissions = (keys) => {
622
+ for (const key of keys) {
623
+ if (focusedSearchRangesCovered(expectedFocusedSearchRanges.get(key) ?? [], survivedFocusedSearchRanges.get(key) ?? []) ||
624
+ omittedRequestedCandidates.has(key)) {
625
+ continue;
626
+ }
627
+ const candidate = focusedLargeSearchCandidates.get(key);
628
+ if (candidate == null) {
629
+ continue;
630
+ }
631
+ pushRequestedCandidateOmission(candidate, "candidate_cap", "focused file had search evidence, but no bounded search slice survived max_bytes_per_file or candidate caps");
632
+ }
633
+ };
634
+ const pushMissingFocusedRangeOmissions = (keys) => {
635
+ for (const key of keys) {
636
+ if (focusSliceSurvivors.has(key) || omittedRequestedCandidates.has(key)) {
637
+ continue;
638
+ }
639
+ const candidate = candidateByIdentity.get(key);
640
+ if (candidate == null) {
641
+ continue;
642
+ }
643
+ pushRequestedCandidateOmission(candidate, "candidate_cap", "focused file slice could not be represented within candidate slice or byte caps");
644
+ }
645
+ };
646
+ const append = (record) => {
647
+ const key = candidateKey(record);
648
+ if (record.source === "search" &&
649
+ input.maxSearchSliceBytesPerCandidate != null) {
650
+ const contentBytes = Buffer.byteLength(record.content, "utf8");
651
+ const candidateBytes = searchSliceBytesByCandidate.get(key) ?? 0;
652
+ if (candidateBytes + contentBytes > input.maxSearchSliceBytesPerCandidate) {
653
+ capHit = true;
654
+ return true;
655
+ }
656
+ searchSliceBytesByCandidate.set(key, candidateBytes + contentBytes);
657
+ }
658
+ const recordBytes = Buffer.byteLength(`${JSON.stringify(record)}\n`, "utf8");
659
+ if (records.length >= maxCandidateSlices || bytes + recordBytes > maxCandidateBytes) {
660
+ capHit = true;
661
+ return false;
662
+ }
663
+ records.push(record);
664
+ bytes += recordBytes;
665
+ if (record.source === "search") {
666
+ const survived = survivedFocusedSearchRanges.get(key);
667
+ if (survived != null) {
668
+ survived.push({
669
+ start_line: record.start_line,
670
+ end_line: record.end_line,
671
+ ...(record.search_term_index == null
672
+ ? {}
673
+ : { search_term_index: record.search_term_index }),
674
+ });
675
+ }
676
+ }
677
+ else {
678
+ focusSliceSurvivors.add(key);
679
+ }
680
+ return true;
681
+ };
682
+ const searchRanges = new Map();
683
+ for (const hit of [...input.searchRecords].sort(compareSearchRecords)) {
684
+ const candidate = candidateByIdentity.get(searchKey(hit));
685
+ if (candidate == null || candidate.line_count === 0) {
686
+ continue;
687
+ }
688
+ const ranges = searchRanges.get(candidateKey(candidate)) ?? [];
689
+ ranges.push({
690
+ start_line: Math.max(1, hit.line - SEARCH_WINDOW_LINES_BEFORE),
691
+ end_line: Math.min(candidate.line_count, hit.line + SEARCH_WINDOW_LINES_AFTER),
692
+ search_term_index: hit.search_term_index,
693
+ });
694
+ searchRanges.set(candidateKey(candidate), ranges);
695
+ }
696
+ const focusRanges = new Map();
697
+ for (const focus of [...input.focusPaths].sort(compareFocusPathsForDiscovery)) {
698
+ for (const candidate of input.candidates) {
699
+ if (candidate.root_index === focus.root_index &&
700
+ (candidate.path === focus.root_relative_path ||
701
+ (focus.kind === "directory" &&
702
+ isRootRelativeDescendant(candidate.path, focus.root_relative_path)))) {
703
+ const key = candidateKey(candidate);
704
+ if (candidate.byte_length > fullFocusSliceByteLimit(candidate.path, maxFullFocusSliceBytes)) {
705
+ if (!searchRanges.has(key) && !omittedLargeFocusedCandidates.has(key)) {
706
+ pushRequestedCandidateOmission(candidate, "too_large", "focused file exceeded bounded full-file focus size and had no per-file search evidence");
707
+ omittedLargeFocusedCandidates.add(key);
708
+ }
709
+ else if (searchRanges.has(key)) {
710
+ focusedLargeSearchCandidates.set(key, candidate);
711
+ expectedFocusedSearchRanges.set(key, mergeLineRanges(searchRanges.get(key) ?? [], {
712
+ bridgeGapLines: SEARCH_BRIDGE_GAP_LINES,
713
+ maxMergedSpanLines: SEARCH_MAX_MERGED_SPAN_LINES,
714
+ }));
715
+ survivedFocusedSearchRanges.set(key, []);
716
+ }
717
+ continue;
718
+ }
719
+ const ranges = focusRanges.get(candidateKey(candidate)) ?? [];
720
+ ranges.push({ start_line: 1, end_line: Math.max(1, candidate.line_count) });
721
+ focusRanges.set(candidateKey(candidate), ranges);
722
+ }
723
+ }
724
+ }
725
+ const dependencyRanges = new Map();
726
+ for (const candidate of input.candidates) {
727
+ if (candidate.selection_hint == null || candidate.line_count === 0) {
728
+ continue;
729
+ }
730
+ if (candidate.byte_length >
731
+ (input.maxSearchSliceBytesPerCandidate ?? Number.MAX_SAFE_INTEGER)) {
732
+ continue;
733
+ }
734
+ dependencyRanges.set(candidateKey(candidate), [
735
+ { start_line: 1, end_line: Math.max(1, candidate.line_count) },
736
+ ]);
737
+ }
738
+ for (const [key, ranges] of [...searchRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
739
+ assertNotAborted(input.signal);
740
+ const candidate = candidateByIdentity.get(key);
741
+ if (candidate == null || candidate.line_count === 0) {
742
+ continue;
743
+ }
744
+ for (const range of mergeLineRanges(ranges, {
745
+ bridgeGapLines: SEARCH_BRIDGE_GAP_LINES,
746
+ maxMergedSpanLines: SEARCH_MAX_MERGED_SPAN_LINES,
747
+ })) {
748
+ if (!append({
749
+ root_index: candidate.root_index,
750
+ path: candidate.path,
751
+ start_line: range.start_line,
752
+ end_line: range.end_line,
753
+ source: "search",
754
+ content: sliceContent(candidate.content, range.start_line, range.end_line),
755
+ ...(range.search_term_index == null
756
+ ? {}
757
+ : { search_term_index: range.search_term_index }),
758
+ })) {
759
+ pushMissingFocusedSearchOmissions(focusedLargeSearchCandidates.keys());
760
+ pushMissingFocusedRangeOmissions(focusRanges.keys());
761
+ return { records, capHit, omissions };
762
+ }
763
+ }
764
+ }
765
+ pushMissingFocusedSearchOmissions(focusedLargeSearchCandidates.keys());
766
+ for (const [key, ranges] of [...dependencyRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
767
+ if (searchRanges.has(key) || focusRanges.has(key)) {
768
+ continue;
769
+ }
770
+ assertNotAborted(input.signal);
771
+ const candidate = candidateByIdentity.get(key);
772
+ if (candidate == null) {
773
+ continue;
774
+ }
775
+ for (const range of mergeLineRanges(ranges)) {
776
+ if (!append({
777
+ root_index: candidate.root_index,
778
+ path: candidate.path,
779
+ start_line: range.start_line,
780
+ end_line: range.end_line,
781
+ source: "search",
782
+ content: sliceContent(candidate.content, range.start_line, range.end_line),
783
+ search_term_index: Number.MAX_SAFE_INTEGER - 1,
784
+ })) {
785
+ pushMissingFocusedRangeOmissions(focusRanges.keys());
786
+ return { records, capHit, omissions };
787
+ }
788
+ }
789
+ }
790
+ for (const [key, ranges] of [...focusRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
791
+ assertNotAborted(input.signal);
792
+ const candidate = candidateByIdentity.get(key);
793
+ if (candidate == null) {
794
+ continue;
795
+ }
796
+ for (const range of mergeLineRanges(ranges)) {
797
+ if (!append({
798
+ root_index: candidate.root_index,
799
+ path: candidate.path,
800
+ start_line: range.start_line,
801
+ end_line: range.end_line,
802
+ source: "focus",
803
+ content: sliceContent(candidate.content, range.start_line, range.end_line),
804
+ })) {
805
+ pushMissingFocusedRangeOmissions(focusRanges.keys());
806
+ return { records, capHit, omissions };
807
+ }
808
+ }
809
+ }
810
+ return { records, capHit, omissions };
811
+ }
812
+ export function splitTextLines(text) {
813
+ if (text.length === 0) {
814
+ return [];
815
+ }
816
+ const withoutFinalNewline = text.endsWith("\n")
817
+ ? text.slice(0, text.endsWith("\r\n") ? -2 : -1)
818
+ : text;
819
+ if (withoutFinalNewline.length === 0) {
820
+ return [""];
821
+ }
822
+ return withoutFinalNewline.split(/\r\n|\n/);
823
+ }
824
+ function nextSearchHit(stream) {
825
+ while (stream.lineIndex < stream.lines.length) {
826
+ const line = stream.lines[stream.lineIndex] ?? "";
827
+ const columnIndex = line.indexOf(stream.searchTerm.term, stream.fromIndex);
828
+ if (columnIndex >= 0) {
829
+ stream.fromIndex = columnIndex + stream.searchTerm.term.length;
830
+ return { lineIndex: stream.lineIndex, columnIndex, line };
831
+ }
832
+ stream.lineIndex++;
833
+ stream.fromIndex = 0;
834
+ }
835
+ return undefined;
836
+ }
837
+ export function mergeLineRanges(ranges, options = {}) {
838
+ const bridgeGapLines = options.bridgeGapLines ?? 0;
839
+ const maxMergedSpanLines = options.maxMergedSpanLines ?? Number.MAX_SAFE_INTEGER;
840
+ const sorted = [...ranges].sort((left, right) => left.start_line - right.start_line ||
841
+ left.end_line - right.end_line ||
842
+ (left.search_term_index ?? Number.MAX_SAFE_INTEGER) -
843
+ (right.search_term_index ?? Number.MAX_SAFE_INTEGER));
844
+ const merged = [];
845
+ for (const range of sorted) {
846
+ const last = merged.at(-1);
847
+ if (last != null &&
848
+ range.start_line <= last.end_line + bridgeGapLines + 1 &&
849
+ Math.max(last.end_line, range.end_line) - last.start_line + 1 <=
850
+ maxMergedSpanLines) {
851
+ last.end_line = Math.max(last.end_line, range.end_line);
852
+ last.search_term_index = Math.min(last.search_term_index ?? Number.MAX_SAFE_INTEGER, range.search_term_index ?? Number.MAX_SAFE_INTEGER);
853
+ if (last.search_term_index === Number.MAX_SAFE_INTEGER) {
854
+ delete last.search_term_index;
855
+ }
856
+ continue;
857
+ }
858
+ merged.push({ ...range });
859
+ }
860
+ return merged;
861
+ }
862
+ function fullFocusSliceByteLimit(path, maxFullFocusSliceBytes) {
863
+ if (isTestPath(path)) {
864
+ return Math.min(maxFullFocusSliceBytes, DEFAULT_MAX_FULL_FOCUS_SLICE_BYTES);
865
+ }
866
+ return maxFullFocusSliceBytes;
867
+ }
868
+ function focusedSearchRangesCovered(expected, survived) {
869
+ return expected.every((range) => rangeCoveredByRanges(range, survived));
870
+ }
871
+ function rangeCoveredByRanges(target, ranges) {
872
+ let nextLine = target.start_line;
873
+ for (const range of mergeLineRanges(ranges)) {
874
+ if (range.end_line < nextLine) {
875
+ continue;
876
+ }
877
+ if (range.start_line > nextLine) {
878
+ return false;
879
+ }
880
+ nextLine = Math.max(nextLine, range.end_line + 1);
881
+ if (nextLine > target.end_line) {
882
+ return true;
883
+ }
884
+ }
885
+ return false;
886
+ }
887
+ async function traverseDirectory(input) {
888
+ assertNotAborted(input.signal);
889
+ if (input.state.stopped) {
890
+ return;
891
+ }
892
+ if (input.relativeDir !== "" &&
893
+ (input.matcher.excludes(input.relativeDir) ||
894
+ input.matcher.excludes(`${input.relativeDir}/__dir__`))) {
895
+ input.state.omissions.push({
896
+ root_index: input.root.root_index,
897
+ reason: "exclude_glob",
898
+ message: "Directory omitted by exclude globs",
899
+ paths: [input.relativeDir],
900
+ });
901
+ pushRequestedFocusOmission({
902
+ root_index: input.root.root_index,
903
+ relativePath: input.relativeDir,
904
+ focusPaths: input.focusPaths,
905
+ state: input.state,
906
+ kind: "unavailable",
907
+ message: "focused path was omitted by exclude globs",
908
+ });
909
+ return;
910
+ }
911
+ const initialVerification = await verifyDirectoryStillExpected(input);
912
+ if (initialVerification.kind === "omitted") {
913
+ pushDirectoryOmission(input, initialVerification.omission);
914
+ pushRequestedFocusOmission({
915
+ root_index: input.root.root_index,
916
+ relativePath: input.relativeDir,
917
+ focusPaths: input.focusPaths,
918
+ state: input.state,
919
+ kind: "unavailable",
920
+ message: sanitizedFocusedOmissionMessage(initialVerification.omission.reason),
921
+ });
922
+ return;
923
+ }
924
+ if (isExcludedRealRoot(initialVerification.realPath, input.excludedRealRoots)) {
925
+ return;
926
+ }
927
+ let entries;
928
+ try {
929
+ entries = await input.fs.readdir(input.absoluteDir, {
930
+ withFileTypes: true,
931
+ });
932
+ }
933
+ catch (error) {
934
+ if (isExpectedDescendantFilesystemError(error)) {
935
+ pushDirectoryOmission(input, filesystemOmission("Directory omitted after filesystem error", error));
936
+ pushRequestedFocusOmission({
937
+ root_index: input.root.root_index,
938
+ relativePath: input.relativeDir,
939
+ focusPaths: input.focusPaths,
940
+ state: input.state,
941
+ kind: "unavailable",
942
+ message: sanitizedFocusedOmissionMessage("filesystem_error"),
943
+ });
944
+ return;
945
+ }
946
+ throw error;
947
+ }
948
+ assertNotAborted(input.signal);
949
+ const finalVerification = await verifyDirectoryStillExpected(input);
950
+ if (finalVerification.kind === "omitted") {
951
+ pushDirectoryOmission(input, finalVerification.omission);
952
+ pushRequestedFocusOmission({
953
+ root_index: input.root.root_index,
954
+ relativePath: input.relativeDir,
955
+ focusPaths: input.focusPaths,
956
+ state: input.state,
957
+ kind: "unavailable",
958
+ message: sanitizedFocusedOmissionMessage(finalVerification.omission.reason),
959
+ });
960
+ return;
961
+ }
962
+ if (isExcludedRealRoot(finalVerification.realPath, input.excludedRealRoots)) {
963
+ return;
964
+ }
965
+ entries.sort((left, right) => left.name.localeCompare(right.name));
966
+ for (const entry of entries) {
967
+ assertNotAborted(input.signal);
968
+ if (input.state.stopped) {
969
+ return;
970
+ }
971
+ const relativePath = normalizeContextBuilderRootRelativePath(input.relativeDir === "" ? entry.name : `${input.relativeDir}/${entry.name}`);
972
+ const absolutePath = path.join(input.absoluteDir, entry.name);
973
+ if (entry.isSymbolicLink()) {
974
+ input.state.omissions.push({
975
+ root_index: input.root.root_index,
976
+ reason: "symlink_omitted",
977
+ message: "Symlink omitted during context_builder discovery",
978
+ paths: [relativePath],
979
+ });
980
+ pushRequestedFocusOmission({
981
+ root_index: input.root.root_index,
982
+ relativePath,
983
+ focusPaths: input.focusPaths,
984
+ state: input.state,
985
+ kind: "unavailable",
986
+ message: sanitizedFocusedOmissionMessage("symlink_omitted"),
987
+ });
988
+ continue;
989
+ }
990
+ if (entry.isDirectory()) {
991
+ if (!input.matcher.canContainIncludedPath(relativePath)) {
992
+ pushRequestedFocusOmission({
993
+ root_index: input.root.root_index,
994
+ relativePath,
995
+ focusPaths: input.focusPaths,
996
+ state: input.state,
997
+ kind: "unavailable",
998
+ message: sanitizedFocusedOmissionMessage("caller_include_glob"),
999
+ });
1000
+ continue;
1001
+ }
1002
+ await traverseDirectory({
1003
+ ...input,
1004
+ absoluteDir: absolutePath,
1005
+ relativeDir: relativePath,
1006
+ });
1007
+ continue;
1008
+ }
1009
+ if (!entry.isFile()) {
1010
+ continue;
1011
+ }
1012
+ await processFile({
1013
+ ...input,
1014
+ absolutePath,
1015
+ relativePath,
1016
+ });
1017
+ }
1018
+ }
1019
+ function pushDirectoryOmission(input, omission) {
1020
+ input.state.omissions.push({
1021
+ root_index: input.root.root_index,
1022
+ reason: omission.reason,
1023
+ message: omission.message,
1024
+ ...(input.relativeDir === "" ? {} : { paths: [input.relativeDir] }),
1025
+ });
1026
+ }
1027
+ async function processFile(input) {
1028
+ const fileKey = candidateKey({
1029
+ root_index: input.root.root_index,
1030
+ path: input.relativePath,
1031
+ });
1032
+ const alreadyProcessed = input.state.processedFileKeys.has(fileKey);
1033
+ if (alreadyProcessed &&
1034
+ (input.ignoreIncludeGlob !== true ||
1035
+ input.state.contentCandidates.some((candidate) => candidateKey(candidate) === fileKey))) {
1036
+ return;
1037
+ }
1038
+ if (!alreadyProcessed) {
1039
+ input.state.processedFileKeys.add(fileKey);
1040
+ }
1041
+ const omitted = (reason, message) => {
1042
+ input.state.omissions.push({
1043
+ root_index: input.root.root_index,
1044
+ reason,
1045
+ message,
1046
+ paths: [input.relativePath],
1047
+ });
1048
+ pushRequestedFocusOmission({
1049
+ root_index: input.root.root_index,
1050
+ relativePath: input.relativePath,
1051
+ focusPaths: input.focusPaths,
1052
+ state: input.state,
1053
+ kind: requestedOmissionKindForDiscoveryReason(reason),
1054
+ message: sanitizedFocusedOmissionMessage(reason),
1055
+ });
1056
+ };
1057
+ const requestedOmitted = (kind, message) => {
1058
+ pushRequestedFocusOmission({
1059
+ root_index: input.root.root_index,
1060
+ relativePath: input.relativePath,
1061
+ focusPaths: input.focusPaths,
1062
+ state: input.state,
1063
+ kind,
1064
+ message,
1065
+ });
1066
+ };
1067
+ const pushTree = (file) => {
1068
+ input.state.tree.push({
1069
+ root_index: input.root.root_index,
1070
+ path: input.relativePath,
1071
+ size_bytes: file.size_bytes ?? 0,
1072
+ eligible: file.eligible,
1073
+ content_eligible: file.content_eligible,
1074
+ ...(file.omission_reason == null
1075
+ ? {}
1076
+ : { omission_reason: file.omission_reason }),
1077
+ });
1078
+ };
1079
+ if (!isSamePathOrDescendant(input.absolutePath, input.root.path)) {
1080
+ omitted("path_escape", "File path escaped root during discovery");
1081
+ return;
1082
+ }
1083
+ const normalizedPath = rootRelativePathForAbsolutePath(input.root, input.absolutePath);
1084
+ if (normalizedPath !== input.relativePath) {
1085
+ omitted("path_normalized_mismatch", "File path changed after normalization");
1086
+ return;
1087
+ }
1088
+ if (input.ignoreIncludeGlob !== true && !input.matcher.includes(input.relativePath)) {
1089
+ requestedOmitted("unavailable", sanitizedFocusedOmissionMessage("caller_include_glob"));
1090
+ pushTree({
1091
+ eligible: false,
1092
+ content_eligible: false,
1093
+ omission_reason: "caller_include_glob",
1094
+ });
1095
+ return;
1096
+ }
1097
+ if (input.matcher.excludes(input.relativePath)) {
1098
+ requestedOmitted("unavailable", sanitizedFocusedOmissionMessage("exclude_glob"));
1099
+ pushTree({
1100
+ eligible: false,
1101
+ content_eligible: false,
1102
+ omission_reason: "exclude_glob",
1103
+ });
1104
+ return;
1105
+ }
1106
+ if (input.ignoreScanCap !== true &&
1107
+ input.state.metrics.files_seen >= input.budget.max_scan_files) {
1108
+ input.state.metrics.scan_cap_hit = true;
1109
+ pushRequestedFocusOmission({
1110
+ root_index: input.root.root_index,
1111
+ relativePath: input.relativePath,
1112
+ focusPaths: input.focusPaths,
1113
+ state: input.state,
1114
+ kind: "unavailable",
1115
+ message: "focused path was not reached before max_scan_files stopped discovery",
1116
+ });
1117
+ input.state.stopped = true;
1118
+ return;
1119
+ }
1120
+ input.state.metrics.files_seen++;
1121
+ const pathSecrets = scanSecretPath(input.relativePath);
1122
+ if (pathSecrets.length > 0) {
1123
+ omitted("secret_path", `Secret-looking path omitted (${pathSecrets[0]?.provenance})`);
1124
+ pushTree({
1125
+ eligible: false,
1126
+ content_eligible: false,
1127
+ omission_reason: "secret_path",
1128
+ });
1129
+ return;
1130
+ }
1131
+ let stats;
1132
+ try {
1133
+ stats = await input.fs.lstat(input.absolutePath);
1134
+ }
1135
+ catch (error) {
1136
+ if (isExpectedDescendantFilesystemError(error)) {
1137
+ omittedFromError("File omitted after lstat error", error);
1138
+ return;
1139
+ }
1140
+ throw error;
1141
+ }
1142
+ assertNotAborted(input.signal);
1143
+ if (stats.isSymbolicLink()) {
1144
+ omitted("symlink_omitted", "Symlink omitted during context_builder discovery");
1145
+ return;
1146
+ }
1147
+ if (!stats.isFile()) {
1148
+ return;
1149
+ }
1150
+ const fileVerification = await verifyFileStillExpected({
1151
+ root: input.root,
1152
+ absolutePath: input.absolutePath,
1153
+ relativePath: input.relativePath,
1154
+ expectedStats: stats,
1155
+ fs: input.fs,
1156
+ });
1157
+ if (fileVerification.kind === "omitted") {
1158
+ omitted(fileVerification.omission.reason, fileVerification.omission.message);
1159
+ return;
1160
+ }
1161
+ const sizeBytes = saneFileSize(stats.size);
1162
+ if (sizeBytes == null) {
1163
+ omitted("file_changed_during_read", "File size could not be verified safely");
1164
+ return;
1165
+ }
1166
+ input.state.metrics.files_considered++;
1167
+ if (isNoisyFile(input.relativePath, sizeBytes)) {
1168
+ omitted("noisy_file", "Noisy generated file omitted");
1169
+ pushTree({
1170
+ size_bytes: sizeBytes,
1171
+ eligible: false,
1172
+ content_eligible: false,
1173
+ omission_reason: "noisy_file",
1174
+ });
1175
+ return;
1176
+ }
1177
+ if (input.state.metrics.bytes_read + sizeBytes > input.internalCaps.max_discovery_bytes) {
1178
+ input.state.metrics.discovery_byte_cap_hit = true;
1179
+ requestedOmitted("unavailable", "focused file could not be read before the aggregate discovery byte cap");
1180
+ pushTree({
1181
+ size_bytes: sizeBytes,
1182
+ eligible: true,
1183
+ content_eligible: false,
1184
+ omission_reason: "discovery_byte_cap",
1185
+ });
1186
+ return;
1187
+ }
1188
+ let read;
1189
+ try {
1190
+ read = await readBoundedRegularFile({
1191
+ absolutePath: input.absolutePath,
1192
+ relativePath: input.relativePath,
1193
+ root: input.root,
1194
+ maxBytes: Math.max(input.budget.max_bytes_per_file, CONTEXT_BUILDER_PUBLIC_BUDGET_HARD_LIMITS.max_bytes_per_file),
1195
+ expectedStats: stats,
1196
+ fs: input.fs,
1197
+ });
1198
+ }
1199
+ catch (error) {
1200
+ if (isExpectedDescendantFilesystemError(error)) {
1201
+ const omission = filesystemOmission("File omitted after open/read error", error);
1202
+ omitted(omission.reason, omission.message);
1203
+ pushTree({
1204
+ size_bytes: sizeBytes,
1205
+ eligible: true,
1206
+ content_eligible: false,
1207
+ omission_reason: omission.reason,
1208
+ });
1209
+ return;
1210
+ }
1211
+ throw error;
1212
+ }
1213
+ assertNotAborted(input.signal);
1214
+ if (read.kind === "omitted") {
1215
+ omitted(read.omission.reason, read.omission.message);
1216
+ requestedOmitted(read.omission.reason === "file_too_large" ? "too_large" : "unavailable", read.omission.message);
1217
+ pushTree({
1218
+ size_bytes: read.sizeBytes ?? sizeBytes,
1219
+ eligible: true,
1220
+ content_eligible: false,
1221
+ omission_reason: read.omission.reason,
1222
+ });
1223
+ return;
1224
+ }
1225
+ const contentBuffer = read.buffer;
1226
+ if (isBinaryBuffer(contentBuffer)) {
1227
+ omitted("binary_file", "Binary file omitted from content-bearing artifacts");
1228
+ pushTree({
1229
+ size_bytes: sizeBytes,
1230
+ eligible: true,
1231
+ content_eligible: false,
1232
+ omission_reason: "binary_file",
1233
+ });
1234
+ return;
1235
+ }
1236
+ const rawContent = contentBuffer.toString("utf8");
1237
+ input.state.metrics.bytes_read += contentBuffer.byteLength;
1238
+ const contentSecrets = scanHighConfidenceSecretContent(rawContent);
1239
+ const blockingSecrets = contentSecrets.filter(isBlockingSecretFinding);
1240
+ if (blockingSecrets.length > 0) {
1241
+ omitted("content_secret", `Content omitted due to high-confidence secret (${summarizeSecretFindings(blockingSecrets)})`);
1242
+ pushTree({
1243
+ size_bytes: read.sizeBytes,
1244
+ eligible: true,
1245
+ content_eligible: false,
1246
+ omission_reason: "content_secret",
1247
+ });
1248
+ return;
1249
+ }
1250
+ const content = contentSecrets.length > 0
1251
+ ? redactHighConfidenceAssignmentSecrets(rawContent)
1252
+ : rawContent;
1253
+ const contentBytes = Buffer.byteLength(content, "utf8");
1254
+ if (input.state.contentCandidates.length >= input.internalCaps.max_discovery_candidates) {
1255
+ input.state.metrics.discovery_candidate_cap_hit = true;
1256
+ requestedOmitted("discovery_candidate_cap", "focused file could not be represented because max_discovery_candidates was reached");
1257
+ pushTree({
1258
+ size_bytes: read.sizeBytes,
1259
+ eligible: true,
1260
+ content_eligible: false,
1261
+ omission_reason: "discovery_candidate_cap",
1262
+ });
1263
+ return;
1264
+ }
1265
+ pushTree({
1266
+ size_bytes: read.sizeBytes,
1267
+ eligible: true,
1268
+ content_eligible: true,
1269
+ });
1270
+ input.state.contentCandidates.push({
1271
+ root_index: input.root.root_index,
1272
+ path: input.relativePath,
1273
+ absolute_path: input.absolutePath,
1274
+ content,
1275
+ byte_length: contentBytes,
1276
+ line_count: splitTextLines(content).length,
1277
+ has_final_newline: content.endsWith("\n"),
1278
+ ...(input.selectionHint == null ? {} : { selection_hint: input.selectionHint }),
1279
+ });
1280
+ function omittedFromError(message, error) {
1281
+ const omission = filesystemOmission(message, error);
1282
+ omitted(omission.reason, omission.message);
1283
+ }
1284
+ }
1285
+ async function verifyDirectoryStillExpected(input) {
1286
+ let lstatStats;
1287
+ try {
1288
+ lstatStats = await input.fs.lstat(input.absoluteDir);
1289
+ }
1290
+ catch (error) {
1291
+ if (isExpectedDescendantFilesystemError(error)) {
1292
+ return {
1293
+ kind: "omitted",
1294
+ omission: filesystemOmission("Directory omitted after filesystem error", error),
1295
+ };
1296
+ }
1297
+ throw error;
1298
+ }
1299
+ if (lstatStats.isSymbolicLink()) {
1300
+ return {
1301
+ kind: "omitted",
1302
+ omission: {
1303
+ reason: "symlink_omitted",
1304
+ message: "Symlink omitted during context_builder discovery",
1305
+ },
1306
+ };
1307
+ }
1308
+ if (!lstatStats.isDirectory()) {
1309
+ return {
1310
+ kind: "omitted",
1311
+ omission: {
1312
+ reason: "file_changed_during_read",
1313
+ message: "Directory changed before it could be read safely",
1314
+ },
1315
+ };
1316
+ }
1317
+ const target = await verifyPathTarget({
1318
+ root: input.root,
1319
+ absolutePath: input.absoluteDir,
1320
+ relativePath: input.relativeDir,
1321
+ expectedStats: lstatStats,
1322
+ expectedKind: "directory",
1323
+ fs: input.fs,
1324
+ });
1325
+ if (target.kind === "omitted") {
1326
+ return target;
1327
+ }
1328
+ return { kind: "verified", realPath: target.realPath };
1329
+ }
1330
+ async function verifyFileStillExpected(input) {
1331
+ return verifyPathTarget({
1332
+ root: input.root,
1333
+ absolutePath: input.absolutePath,
1334
+ relativePath: input.relativePath,
1335
+ expectedStats: input.expectedStats,
1336
+ expectedKind: "file",
1337
+ fs: input.fs,
1338
+ });
1339
+ }
1340
+ async function verifyPathTarget(input) {
1341
+ let realPath;
1342
+ try {
1343
+ realPath = await input.fs.realpath(input.absolutePath);
1344
+ }
1345
+ catch (error) {
1346
+ if (isExpectedDescendantFilesystemError(error)) {
1347
+ return {
1348
+ kind: "omitted",
1349
+ omission: filesystemOmission("Path omitted after filesystem error", error),
1350
+ };
1351
+ }
1352
+ throw error;
1353
+ }
1354
+ if (!isSamePathOrDescendant(realPath, input.root.path)) {
1355
+ return {
1356
+ kind: "omitted",
1357
+ omission: {
1358
+ reason: "path_escape",
1359
+ message: "Path resolved outside root during discovery",
1360
+ },
1361
+ };
1362
+ }
1363
+ const normalizedPath = rootRelativePathForAbsolutePath(input.root, realPath);
1364
+ if (normalizedPath !== input.relativePath) {
1365
+ return {
1366
+ kind: "omitted",
1367
+ omission: {
1368
+ reason: "path_escape",
1369
+ message: "Path resolved to an unexpected root-relative target",
1370
+ },
1371
+ };
1372
+ }
1373
+ let targetStats;
1374
+ try {
1375
+ targetStats = await input.fs.stat(realPath);
1376
+ }
1377
+ catch (error) {
1378
+ if (isExpectedDescendantFilesystemError(error)) {
1379
+ return {
1380
+ kind: "omitted",
1381
+ omission: filesystemOmission("Path omitted after filesystem error", error),
1382
+ };
1383
+ }
1384
+ throw error;
1385
+ }
1386
+ const expectedKindMatches = input.expectedKind === "file"
1387
+ ? targetStats.isFile()
1388
+ : targetStats.isDirectory();
1389
+ if (!expectedKindMatches || !sameFileIdentity(input.expectedStats, targetStats)) {
1390
+ return {
1391
+ kind: "omitted",
1392
+ omission: {
1393
+ reason: "file_changed_during_read",
1394
+ message: "Path changed before it could be read safely",
1395
+ },
1396
+ };
1397
+ }
1398
+ return { kind: "verified", realPath };
1399
+ }
1400
+ function compareTreeFiles(left, right) {
1401
+ return left.root_index - right.root_index || left.path.localeCompare(right.path);
1402
+ }
1403
+ function compareContentCandidates(left, right) {
1404
+ return left.root_index - right.root_index || left.path.localeCompare(right.path);
1405
+ }
1406
+ function discoveryTaskClosureRank(path, task) {
1407
+ const normalizedTask = task.toLowerCase();
1408
+ if (normalizedTask.length === 0) {
1409
+ return 0;
1410
+ }
1411
+ const taskTokens = new Set(tokenizeGenericSearchTerm(normalizedTask));
1412
+ const normalizedPath = path.toLowerCase();
1413
+ if (/\b(discover|discovery)\b.*\b(provider-specific|provider\s+semantics|providers?|claude|agent|symmetry)\b|\b(provider-specific|provider\s+semantics|providers?|claude|agent|symmetry)\b.*\b(discover|discovery)\b/.test(normalizedTask) &&
1414
+ hasAnyToken(taskTokens, ["provider", "runtime", "claude", "agent", "symmetry"]) &&
1415
+ [/\/claude_agent\.py$/, /\/runtime\/base\.py$/].some((pattern) => pattern.test(normalizedPath))) {
1416
+ return -30;
1417
+ }
1418
+ if (/\b(discover|discovery)\b/.test(normalizedTask) &&
1419
+ hasAnyToken(taskTokens, ["queue", "migration", "runtime", "schema", "setting", "selector"]) &&
1420
+ [
1421
+ /\/tasks\/chat_processor\.py$/,
1422
+ /\/hooks\/usemessageactions\.ts$/,
1423
+ /\/hooks\/usechatstreaming\.ts$/,
1424
+ /\/hooks\/usechatruntimesettings\.ts$/,
1425
+ /\/components\/chat\/chat-window\/chat\.tsx$/,
1426
+ /\/components\/chat\/message-input\/input\.tsx$/,
1427
+ /\/components\/chat\/message-input\/inputsettingspopover\.tsx$/,
1428
+ /\/pages\/chatpage\.tsx$/,
1429
+ /\/landingpage\.tsx$/,
1430
+ /\/models\/db_models\/chat\.py$/,
1431
+ /\/[^/]*(permission|thinking).*selector[^/]*\.(ts|tsx)$/,
1432
+ /\/tests\/test_chat\.py$/,
1433
+ /\/tests?\/.*runtime.*\.(py|ts|tsx)$/,
1434
+ ].some((pattern) => pattern.test(normalizedPath))) {
1435
+ return -24;
1436
+ }
1437
+ if (/\breview\b/.test(normalizedTask) &&
1438
+ hasAnyToken(taskTokens, ["codex", "approval", "permission", "session", "runtime"]) &&
1439
+ [
1440
+ /\/api\/endpoints\/permissions\.py$/,
1441
+ /\/services\/runtime\/codex_approval\.py$/,
1442
+ /\/services\/permission_manager\.py$/,
1443
+ /\/services\/chat\.py$/,
1444
+ /\/api\/endpoints\/chat\.py$/,
1445
+ /\/models\/db_models\/ai_model\.py$/,
1446
+ /\/services\/chatservice\.ts$/,
1447
+ /\/types\/chat\.types\.ts$/,
1448
+ /\/core\/config\.py$/,
1449
+ /\/constants\.py$/,
1450
+ /\/hooks\/usepermissionrequest\.ts$/,
1451
+ /\/tests\/test_permissions\.py$/,
1452
+ /(^|\/)docs?\/.*(codex|approval|permission|session|runtime).*\.md$/,
1453
+ ].some((pattern) => pattern.test(normalizedPath))) {
1454
+ return -24;
1455
+ }
1456
+ if (/\b(debug|debugging|diagnose|diagnosis|diagnostic)\b/.test(normalizedTask) &&
1457
+ hasAnyToken(taskTokens, ["devtool", "trace", "archive", "message"]) &&
1458
+ [
1459
+ /\/api\/endpoints\/devtools\.py$/,
1460
+ /\/services\/session_trace_archive\.py$/,
1461
+ /\/models\/db_models\/session_trace_archive\.py$/,
1462
+ /\/.*trace.*archive.*\.test\.(ts|tsx|py)$/,
1463
+ /\/tests?\/.*trace.*archive.*\.(py|ts|tsx)$/,
1464
+ /\/messagerenderer\.tsx$/,
1465
+ /\/devtools\.types\.ts$/,
1466
+ ].some((pattern) => pattern.test(normalizedPath))) {
1467
+ return -24;
1468
+ }
1469
+ return 0;
1470
+ }
1471
+ function hasAnyToken(tokens, candidates) {
1472
+ return candidates.some((candidate) => tokens.has(candidate));
1473
+ }
1474
+ function compareSearchRecords(left, right) {
1475
+ return (left.root_index - right.root_index ||
1476
+ left.path.localeCompare(right.path) ||
1477
+ left.line - right.line ||
1478
+ left.search_term_index - right.search_term_index ||
1479
+ left.column - right.column);
1480
+ }
1481
+ function compareFocusPathsForDiscovery(left, right) {
1482
+ return (left.root_index - right.root_index ||
1483
+ left.root_relative_path.localeCompare(right.root_relative_path) ||
1484
+ left.input_index - right.input_index);
1485
+ }
1486
+ function auditScanCapFocusedPaths(input) {
1487
+ if (!input.state.metrics.scan_cap_hit) {
1488
+ return;
1489
+ }
1490
+ for (const focus of input.focusPaths) {
1491
+ if (hasSurvivingFocusEvidence(input.state, focus) ||
1492
+ hasRequestedEvidenceOmissionForPath(input.state.omissions, focus.root_index, focus.root_relative_path)) {
1493
+ continue;
1494
+ }
1495
+ pushRequestedFocusOmission({
1496
+ root_index: focus.root_index,
1497
+ relativePath: focus.root_relative_path,
1498
+ focusPaths: input.focusPaths,
1499
+ state: input.state,
1500
+ kind: "unavailable",
1501
+ message: "focused path was not reached before max_scan_files stopped discovery",
1502
+ });
1503
+ }
1504
+ }
1505
+ function hasSurvivingFocusEvidence(state, focus) {
1506
+ return (state.contentCandidates.some((candidate) => focusPathCoversEvidencePath(focus, candidate.root_index, candidate.path)) ||
1507
+ state.tree.some((file) => focusPathCoversEvidencePath(focus, file.root_index, file.path)));
1508
+ }
1509
+ function focusPathCoversEvidencePath(focus, rootIndex, relativePath) {
1510
+ return (focus.root_index === rootIndex &&
1511
+ (relativePath === focus.root_relative_path ||
1512
+ (focus.kind === "directory" &&
1513
+ isRootRelativeDescendant(relativePath, focus.root_relative_path))));
1514
+ }
1515
+ function pushRequestedFocusOmission(input) {
1516
+ const paths = requestedEvidencePathsForFocusedOmission(input.root_index, input.relativePath, input.focusPaths).filter((requestedPath) => {
1517
+ const key = requestedEvidenceOmissionKey(input.root_index, requestedPath);
1518
+ if (input.state.requestedEvidenceOmissionKeys.has(key)) {
1519
+ return false;
1520
+ }
1521
+ if (hasRequestedEvidenceOmissionForPath(input.state.omissions, input.root_index, requestedPath)) {
1522
+ input.state.requestedEvidenceOmissionKeys.add(key);
1523
+ return false;
1524
+ }
1525
+ return true;
1526
+ });
1527
+ if (paths.length === 0) {
1528
+ return;
1529
+ }
1530
+ input.state.omissions.push(requestedEvidenceOmission({
1531
+ root_index: input.root_index,
1532
+ kind: input.kind,
1533
+ message: input.message,
1534
+ paths,
1535
+ }));
1536
+ for (const requestedPath of paths) {
1537
+ input.state.requestedEvidenceOmissionKeys.add(requestedEvidenceOmissionKey(input.root_index, requestedPath));
1538
+ }
1539
+ }
1540
+ function requestedEvidencePathsForFocusedOmission(rootIndex, relativePath, focusPaths) {
1541
+ const paths = new Set();
1542
+ for (const focus of focusPaths) {
1543
+ if (focus.root_index !== rootIndex) {
1544
+ continue;
1545
+ }
1546
+ if (relativePath === focus.root_relative_path) {
1547
+ paths.add(relativePath);
1548
+ continue;
1549
+ }
1550
+ if (focus.kind === "directory" &&
1551
+ isRootRelativeDescendant(relativePath, focus.root_relative_path)) {
1552
+ paths.add(relativePath);
1553
+ continue;
1554
+ }
1555
+ if (isRootRelativeDescendant(focus.root_relative_path, relativePath)) {
1556
+ paths.add(focus.root_relative_path);
1557
+ }
1558
+ }
1559
+ return [...paths].sort();
1560
+ }
1561
+ function hasRequestedEvidenceOmissionForPath(omissions, rootIndex, relativePath) {
1562
+ return omissions.some((omission) => omission.reason === CONTEXT_BUILDER_REQUESTED_EVIDENCE_OMISSION_REASON &&
1563
+ omission.root_index === rootIndex &&
1564
+ (omission.paths ?? []).includes(relativePath));
1565
+ }
1566
+ function requestedEvidenceOmissionKey(rootIndex, relativePath) {
1567
+ return `${rootIndex}\u0000${relativePath}`;
1568
+ }
1569
+ function requestedOmissionKindForDiscoveryReason(reason) {
1570
+ return reason === "file_too_large" ? "too_large" : "unavailable";
1571
+ }
1572
+ function sanitizedFocusedOmissionMessage(reason) {
1573
+ switch (reason) {
1574
+ case "caller_include_glob":
1575
+ return "focused path was outside include globs";
1576
+ case "exclude_glob":
1577
+ return "focused path was omitted by exclude globs";
1578
+ case "secret_path":
1579
+ return "focused path was omitted because it matched a secret-path rule";
1580
+ case "symlink_omitted":
1581
+ return "focused path was omitted because symlinks are not followed";
1582
+ case "path_escape":
1583
+ case "path_normalized_mismatch":
1584
+ return "focused path could not be safely resolved within the root";
1585
+ case "filesystem_error":
1586
+ return "focused path could not be read due to an expected filesystem error";
1587
+ case "file_changed_during_read":
1588
+ return "focused path changed before it could be read safely";
1589
+ case "noisy_file":
1590
+ return "focused generated file was omitted as noisy";
1591
+ case "binary_file":
1592
+ return "focused binary file cannot be represented as text";
1593
+ case "content_secret":
1594
+ return "focused file content was omitted because secret-like content was detected";
1595
+ case "file_too_large":
1596
+ return "focused file exceeded max_bytes_per_file";
1597
+ default:
1598
+ return "focused path could not be represented as local evidence";
1599
+ }
1600
+ }
1601
+ function candidateKey(candidate) {
1602
+ return `${candidate.root_index}\u0000${candidate.path}`;
1603
+ }
1604
+ function searchKey(search) {
1605
+ return `${search.root_index}\u0000${search.path}`;
1606
+ }
1607
+ function isRootRelativeDescendant(child, parent) {
1608
+ if (parent === "") {
1609
+ return true;
1610
+ }
1611
+ return child === parent || child.startsWith(`${parent}/`);
1612
+ }
1613
+ function isExcludedRealRoot(realPath, excludedRealRoots) {
1614
+ return excludedRealRoots.some((root) => isSamePathOrDescendant(realPath, root));
1615
+ }
1616
+ function sliceContent(text, startLine, endLine) {
1617
+ return splitTextLines(text)
1618
+ .slice(startLine - 1, endLine)
1619
+ .join("\n");
1620
+ }
1621
+ function boundedPreview(line, columnIndex, termLength) {
1622
+ const radius = 80;
1623
+ const start = Math.max(0, columnIndex - radius);
1624
+ const end = Math.min(line.length, columnIndex + termLength + radius);
1625
+ const prefix = start > 0 ? "..." : "";
1626
+ const suffix = end < line.length ? "..." : "";
1627
+ return `${prefix}${line.slice(start, end)}${suffix}`;
1628
+ }
1629
+ function isBinaryBuffer(buffer) {
1630
+ if (buffer.length === 0) {
1631
+ return false;
1632
+ }
1633
+ if (buffer.includes(0)) {
1634
+ return true;
1635
+ }
1636
+ let suspicious = 0;
1637
+ const sampleLength = Math.min(buffer.length, 8_192);
1638
+ for (let index = 0; index < sampleLength; index++) {
1639
+ const byte = buffer[index] ?? 0;
1640
+ const allowedControl = byte === 0x09 || byte === 0x0a || byte === 0x0d || byte === 0x1b;
1641
+ if (byte < 0x20 && !allowedControl) {
1642
+ suspicious++;
1643
+ }
1644
+ }
1645
+ return suspicious / sampleLength > 0.05;
1646
+ }
1647
+ function isNoisyFile(rootRelativePath, sizeBytes) {
1648
+ const basename = path.posix.basename(rootRelativePath);
1649
+ return sizeBytes > 20_000 && (basename.endsWith(".min.js") || basename.endsWith(".map"));
1650
+ }
1651
+ function summarizeSecretFindings(findings) {
1652
+ return findings.map((finding) => finding.provenance).join(", ");
1653
+ }
1654
+ async function readBoundedRegularFile(input) {
1655
+ const initialVerification = await verifyFileStillExpected({
1656
+ root: input.root,
1657
+ absolutePath: input.absolutePath,
1658
+ relativePath: input.relativePath,
1659
+ expectedStats: input.expectedStats,
1660
+ fs: input.fs,
1661
+ });
1662
+ if (initialVerification.kind === "omitted") {
1663
+ return {
1664
+ kind: "omitted",
1665
+ omission: initialVerification.omission,
1666
+ };
1667
+ }
1668
+ let handle;
1669
+ try {
1670
+ handle = await input.fs.open(input.absolutePath, noFollowReadFlags());
1671
+ }
1672
+ catch (error) {
1673
+ if (filesystemErrorCode(error) === "ELOOP") {
1674
+ return {
1675
+ kind: "omitted",
1676
+ omission: {
1677
+ reason: "symlink_omitted",
1678
+ message: "Symlink omitted during context_builder discovery",
1679
+ },
1680
+ };
1681
+ }
1682
+ throw error;
1683
+ }
1684
+ try {
1685
+ const openedStats = await handle.stat();
1686
+ const openedVerification = await verifyFileStillExpected({
1687
+ root: input.root,
1688
+ absolutePath: input.absolutePath,
1689
+ relativePath: input.relativePath,
1690
+ expectedStats: openedStats,
1691
+ fs: input.fs,
1692
+ });
1693
+ if (openedVerification.kind === "omitted") {
1694
+ return {
1695
+ kind: "omitted",
1696
+ omission: openedVerification.omission,
1697
+ };
1698
+ }
1699
+ const openedSize = saneFileSize(openedStats.size);
1700
+ if (!openedStats.isFile() || openedSize == null) {
1701
+ return {
1702
+ kind: "omitted",
1703
+ omission: {
1704
+ reason: "file_changed_during_read",
1705
+ message: "File changed before it could be read safely",
1706
+ },
1707
+ };
1708
+ }
1709
+ if (!sameFileIdentity(input.expectedStats, openedStats)) {
1710
+ return {
1711
+ kind: "omitted",
1712
+ omission: {
1713
+ reason: "file_changed_during_read",
1714
+ message: "File changed before it could be read safely",
1715
+ },
1716
+ sizeBytes: openedSize,
1717
+ };
1718
+ }
1719
+ if (openedSize > input.maxBytes) {
1720
+ return {
1721
+ kind: "omitted",
1722
+ omission: {
1723
+ reason: "file_too_large",
1724
+ message: "File exceeded max_bytes_per_file while opening",
1725
+ },
1726
+ sizeBytes: openedSize,
1727
+ };
1728
+ }
1729
+ const readLength = Math.min(input.maxBytes + 1, openedSize + 1);
1730
+ const buffer = Buffer.alloc(readLength);
1731
+ const { bytesRead } = await handle.read(buffer, 0, readLength, 0);
1732
+ if (bytesRead !== openedSize) {
1733
+ return {
1734
+ kind: "omitted",
1735
+ omission: {
1736
+ reason: "file_changed_during_read",
1737
+ message: "File changed before it could be read safely",
1738
+ },
1739
+ sizeBytes: openedSize,
1740
+ };
1741
+ }
1742
+ const finalVerification = await verifyFileStillExpected({
1743
+ root: input.root,
1744
+ absolutePath: input.absolutePath,
1745
+ relativePath: input.relativePath,
1746
+ expectedStats: openedStats,
1747
+ fs: input.fs,
1748
+ });
1749
+ if (finalVerification.kind === "omitted") {
1750
+ return {
1751
+ kind: "omitted",
1752
+ omission: finalVerification.omission,
1753
+ sizeBytes: openedSize,
1754
+ };
1755
+ }
1756
+ return {
1757
+ kind: "read",
1758
+ buffer: buffer.subarray(0, bytesRead),
1759
+ sizeBytes: openedSize,
1760
+ };
1761
+ }
1762
+ finally {
1763
+ await handle.close();
1764
+ }
1765
+ }
1766
+ let _noFollowAvailable;
1767
+ function noFollowReadFlags() {
1768
+ if (_noFollowAvailable === undefined) {
1769
+ _noFollowAvailable = typeof fsConstants.O_NOFOLLOW === "number";
1770
+ }
1771
+ return _noFollowAvailable
1772
+ ? fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW
1773
+ : fsConstants.O_RDONLY;
1774
+ }
1775
+ function sameFileIdentity(left, right) {
1776
+ if (left.dev !== right.dev || left.ino !== right.ino) {
1777
+ return false;
1778
+ }
1779
+ if (left.isFile() || right.isFile()) {
1780
+ return left.isFile() && right.isFile()
1781
+ && left.size === right.size
1782
+ && left.mtimeMs === right.mtimeMs;
1783
+ }
1784
+ if (left.isDirectory() || right.isDirectory()) {
1785
+ return left.isDirectory() && right.isDirectory();
1786
+ }
1787
+ return false;
1788
+ }
1789
+ function saneFileSize(size) {
1790
+ return Number.isSafeInteger(size) && size >= 0 ? size : undefined;
1791
+ }
1792
+ const EXPECTED_DESCENDANT_FILESYSTEM_ERROR_CODES = new Set([
1793
+ "EACCES",
1794
+ "EPERM",
1795
+ "ENOENT",
1796
+ "ENOTDIR",
1797
+ "ELOOP",
1798
+ ]);
1799
+ function isExpectedDescendantFilesystemError(error) {
1800
+ const code = filesystemErrorCode(error);
1801
+ return (code != null &&
1802
+ EXPECTED_DESCENDANT_FILESYSTEM_ERROR_CODES.has(code));
1803
+ }
1804
+ function filesystemOmission(message, error) {
1805
+ const code = filesystemErrorCode(error);
1806
+ if (code === "ELOOP") {
1807
+ return {
1808
+ reason: "symlink_omitted",
1809
+ message: "Symlink omitted during context_builder discovery",
1810
+ };
1811
+ }
1812
+ return {
1813
+ reason: "filesystem_error",
1814
+ message: code == null ? message : `${message} (${code})`,
1815
+ };
1816
+ }
1817
+ function filesystemErrorCode(error) {
1818
+ if (typeof error !== "object" || error == null || !("code" in error)) {
1819
+ return undefined;
1820
+ }
1821
+ const code = error.code;
1822
+ return typeof code === "string" ? code : undefined;
1823
+ }
1824
+ function assertNotAborted(signal) {
1825
+ if (signal?.aborted === true) {
1826
+ throw new CancellationError();
1827
+ }
1828
+ }
1829
+ //# sourceMappingURL=discovery.js.map