jev-agent-tools 0.1.3

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 (95) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/LICENSE +21 -0
  3. package/README.md +119 -0
  4. package/docs/design.md +187 -0
  5. package/docs/tools/jev_ask.md +92 -0
  6. package/docs/tools/jev_ask_files.md +80 -0
  7. package/docs/tools/jev_check_diff.md +77 -0
  8. package/docs/tools/jev_find_files.md +59 -0
  9. package/docs/tools/jev_locate_in_file.md +51 -0
  10. package/docs/tools/jev_select_tests.md +59 -0
  11. package/package.json +85 -0
  12. package/rules/jev-ask.md +4 -0
  13. package/src/adapters/analysis-context.ts +100 -0
  14. package/src/adapters/ask-files.ts +236 -0
  15. package/src/adapters/ask-proof.ts +202 -0
  16. package/src/adapters/ask-syntax.ts +463 -0
  17. package/src/adapters/command.ts +240 -0
  18. package/src/adapters/docs.ts +222 -0
  19. package/src/adapters/files.ts +411 -0
  20. package/src/adapters/find.ts +151 -0
  21. package/src/adapters/git-base.ts +32 -0
  22. package/src/adapters/git-inventory.ts +94 -0
  23. package/src/adapters/git.ts +525 -0
  24. package/src/adapters/locate-file.ts +197 -0
  25. package/src/adapters/output-lines.ts +50 -0
  26. package/src/adapters/risk-callers.ts +525 -0
  27. package/src/adapters/runner-version.ts +102 -0
  28. package/src/adapters/syntax.ts +229 -0
  29. package/src/adapters/test-inventory.ts +168 -0
  30. package/src/adapters/usage.ts +21 -0
  31. package/src/adapters/utf8.ts +57 -0
  32. package/src/constants.ts +109 -0
  33. package/src/core/ask-closure.ts +419 -0
  34. package/src/core/ask-proof.ts +32 -0
  35. package/src/core/ask-references.ts +249 -0
  36. package/src/core/asks.ts +616 -0
  37. package/src/core/batches.ts +83 -0
  38. package/src/core/command-output.ts +249 -0
  39. package/src/core/diff.ts +226 -0
  40. package/src/core/docs.ts +399 -0
  41. package/src/core/find.ts +157 -0
  42. package/src/core/git.ts +5 -0
  43. package/src/core/import-boundaries.ts +102 -0
  44. package/src/core/imports.ts +691 -0
  45. package/src/core/integrity.ts +64 -0
  46. package/src/core/lexical.ts +130 -0
  47. package/src/core/locate.ts +213 -0
  48. package/src/core/output.ts +264 -0
  49. package/src/core/pointer.ts +51 -0
  50. package/src/core/risk-callers.ts +1270 -0
  51. package/src/core/runner-version.ts +66 -0
  52. package/src/core/sections.ts +269 -0
  53. package/src/core/state.ts +53 -0
  54. package/src/core/syntax.ts +8 -0
  55. package/src/core/test-commands.ts +430 -0
  56. package/src/core/test-coverage.ts +103 -0
  57. package/src/core/test-discovery.ts +1695 -0
  58. package/src/core/test-evidence.ts +649 -0
  59. package/src/core/test-state.ts +99 -0
  60. package/src/core/truncate.ts +14 -0
  61. package/src/core/units.ts +531 -0
  62. package/src/describe.ts +26 -0
  63. package/src/guide.ts +42 -0
  64. package/src/host.ts +22 -0
  65. package/src/index.ts +40 -0
  66. package/src/jev/client.ts +505 -0
  67. package/src/jev/pool.ts +60 -0
  68. package/src/jev/types.ts +60 -0
  69. package/src/presets/docs.ts +85 -0
  70. package/src/presets/risk.ts +263 -0
  71. package/src/presets/spec.ts +111 -0
  72. package/src/presets/witnesses.ts +313 -0
  73. package/src/render.ts +45 -0
  74. package/src/result.ts +4 -0
  75. package/src/run-end.ts +157 -0
  76. package/src/runtime.ts +16 -0
  77. package/src/session.ts +106 -0
  78. package/src/texts/ask-files.ts +2 -0
  79. package/src/texts/ask.ts +4 -0
  80. package/src/texts/check-diff.ts +24 -0
  81. package/src/texts/configuration.ts +2 -0
  82. package/src/texts/find.ts +14 -0
  83. package/src/texts/guide.ts +16 -0
  84. package/src/texts/locate.ts +10 -0
  85. package/src/texts/run-end.ts +13 -0
  86. package/src/texts/select-tests.ts +3 -0
  87. package/src/tools/ask-files.ts +263 -0
  88. package/src/tools/ask-schema.ts +116 -0
  89. package/src/tools/ask.ts +925 -0
  90. package/src/tools/check-diff.ts +510 -0
  91. package/src/tools/docs-check.ts +399 -0
  92. package/src/tools/find.ts +529 -0
  93. package/src/tools/locate.ts +369 -0
  94. package/src/tools/select-tests.ts +746 -0
  95. package/src/tools/spec-check.ts +210 -0
@@ -0,0 +1,229 @@
1
+ import type { SgNode } from "@ast-grep/napi";
2
+ import type { SyntaxRootParser } from "../core/syntax.ts";
3
+ import type {
4
+ Declaration,
5
+ DeclarationResult,
6
+ SyntaxParser,
7
+ } from "../core/units.ts";
8
+
9
+ let parserPromise: Promise<SyntaxRootParser | undefined> | undefined;
10
+ /** Optional grammars resolve from this extension, not the repository under review. */
11
+ export function loadSyntaxParser(): Promise<SyntaxParser | undefined> {
12
+ parserPromise ??= createParser();
13
+ return parserPromise;
14
+ }
15
+ /** Raw AST access shares grammar availability and registration with declaration extraction. */
16
+ export function loadSyntaxRootParser(): Promise<SyntaxRootParser | undefined> {
17
+ parserPromise ??= createParser();
18
+ return parserPromise;
19
+ }
20
+ async function createParser(): Promise<SyntaxRootParser | undefined> {
21
+ try {
22
+ const napi = await import("@ast-grep/napi");
23
+ let python = false;
24
+ try {
25
+ const language = await import("@ast-grep/lang-python");
26
+ napi.registerDynamicLanguage({ python: language.default });
27
+ python = true;
28
+ } catch {
29
+ /* buildUnits reports the missing grammar for each affected file. */
30
+ }
31
+ const parser: SyntaxRootParser = {
32
+ supports: (path) =>
33
+ /\.[cm]?[jt]sx?$/.test(path) || (python && path.endsWith(".py")),
34
+ parseRaw(path, text) {
35
+ try {
36
+ const language = path.endsWith(".py")
37
+ ? "python"
38
+ : /\.[cm]?tsx?$/.test(path)
39
+ ? path.endsWith("tsx")
40
+ ? napi.Lang.Tsx
41
+ : napi.Lang.TypeScript
42
+ : path.endsWith(".jsx")
43
+ ? "jsx"
44
+ : napi.Lang.JavaScript;
45
+ const root = napi.parse(language, text).root();
46
+ return { ok: true, root };
47
+ } catch {
48
+ return { ok: false, error: "Source parser unavailable" };
49
+ }
50
+ },
51
+ parse(path, text) {
52
+ const parsed = this.parseRaw(path, text);
53
+ if (!parsed.ok) return parsed;
54
+ return parsed.root.find({ rule: { kind: "ERROR" } })
55
+ ? { ok: false, error: "Source syntax incomplete" }
56
+ : parsed;
57
+ },
58
+ declarations(path, text, options) {
59
+ const isPython = path.endsWith(".py");
60
+ const parsed = this.parseRaw(path, text);
61
+ if (!parsed.ok)
62
+ return { declarations: [], limits: [{ kind: "parse_failed" }] };
63
+ const root = parsed.root;
64
+ const result: Declaration[] = [];
65
+ const limits: DeclarationResult["limits"] = [];
66
+ const errors = root.findAll({ rule: { kind: "ERROR" } });
67
+ const declarationKind =
68
+ /^(?:function_declaration|generator_function_declaration|method_definition|function_definition|lexical_declaration|variable_declaration|type_alias_declaration|interface_declaration|enum_declaration|class_declaration|class_definition)$/;
69
+ if (
70
+ errors.some(
71
+ (error) =>
72
+ !error
73
+ .ancestors()
74
+ .some((node) => declarationKind.test(String(node.kind()))) &&
75
+ (error
76
+ .findAll({ rule: { regex: ".*" } })
77
+ .some((node) => declarationKind.test(String(node.kind()))) ||
78
+ /^(?:export\s+)?(?:function|const|let|var|class)\b/.test(
79
+ error.text().trim(),
80
+ )),
81
+ )
82
+ )
83
+ return { declarations: [], limits: [{ kind: "parse_failed" }] };
84
+ const attributedErrors = new Set<number>();
85
+ const source = text.split("\n");
86
+ const visit = (node: SgNode) => {
87
+ const kind = String(node.kind());
88
+ // Most AST nodes cannot yield a declaration; avoid constructing their ancestor chains.
89
+ if (
90
+ !/^(?:function_declaration|generator_function_declaration|method_definition|function_definition|lexical_declaration|variable_declaration|type_alias_declaration|interface_declaration|enum_declaration|expression_statement|class_declaration|class_definition)$/.test(
91
+ kind,
92
+ )
93
+ ) {
94
+ for (const child of node.children()) visit(child);
95
+ return;
96
+ }
97
+ const ancestors = node.ancestors();
98
+ const functionNode =
99
+ /^(?:function_declaration|generator_function_declaration|method_definition|function_definition)$/.test(
100
+ kind,
101
+ );
102
+ const variable =
103
+ /^(?:lexical_declaration|variable_declaration|type_alias_declaration|interface_declaration|enum_declaration)$/.test(
104
+ kind,
105
+ ) &&
106
+ ancestors.every(
107
+ (a) => !/function|method|statement_block/.test(String(a.kind())),
108
+ );
109
+ const callableVariable =
110
+ variable &&
111
+ node.find({
112
+ rule: {
113
+ any: [
114
+ { kind: "arrow_function" },
115
+ { kind: "function_expression" },
116
+ ],
117
+ },
118
+ }) !== null;
119
+ const assignment =
120
+ isPython &&
121
+ kind === "expression_statement" &&
122
+ node.parent()?.kind() === "module" &&
123
+ node.children().some((child) => child.kind() === "assignment");
124
+ const classNode =
125
+ options?.descendAbove !== undefined &&
126
+ /^(?:class_declaration|class_definition)$/.test(kind);
127
+ if (functionNode || variable || assignment || classNode) {
128
+ let envelope = node;
129
+ if (
130
+ node.parent()?.kind() === "decorated_definition" ||
131
+ node.parent()?.kind() === "export_statement"
132
+ )
133
+ envelope = node.parent() ?? node;
134
+ const named =
135
+ node.field("name") ?? node.find({ rule: { kind: "identifier" } });
136
+ const owner = ancestors
137
+ .filter((a) =>
138
+ /^(?:class_definition|class_declaration|internal_module|module)$/.test(
139
+ String(a.kind()),
140
+ ),
141
+ )
142
+ .reverse()
143
+ .map((a) => a.field("name")?.text())
144
+ .filter(Boolean);
145
+ let name = [...owner, named?.text() ?? "declaration"].join(".");
146
+ if (assignment)
147
+ name = node.text().split(/\s*[:=]/, 1)[0] ?? "declaration";
148
+ let start = envelope.range().start.line;
149
+ const body =
150
+ node.field("body") ??
151
+ (callableVariable
152
+ ? node
153
+ .find({
154
+ rule: {
155
+ any: [
156
+ { kind: "arrow_function" },
157
+ { kind: "function_expression" },
158
+ ],
159
+ },
160
+ })
161
+ ?.field("body")
162
+ : null);
163
+ const tokens = (part: SgNode): string[] => {
164
+ const children = part.children();
165
+ if (
166
+ !children.length ||
167
+ /string|comment/.test(String(part.kind()))
168
+ )
169
+ return part.kind() === "comment" ? [] : [part.text()];
170
+ return children.flatMap(tokens);
171
+ };
172
+ const accessor =
173
+ kind === "method_definition"
174
+ ? node
175
+ .children()
176
+ .find((child) => /^(get|set)$/.test(String(child.kind())))
177
+ ?.kind()
178
+ : undefined;
179
+ const end = envelope.range().end;
180
+ // Include the immediately preceding comment block without swallowing another declaration.
181
+ while (
182
+ start > 0 &&
183
+ /^\s*(?:\/\/|\/\*|\*|\*\/|#)/.test(source[start - 1] ?? "")
184
+ )
185
+ start--;
186
+ result.push({
187
+ kind:
188
+ functionNode || callableVariable ? "function" : "declaration",
189
+ name,
190
+ type: accessor ? `${kind}:${accessor}` : kind,
191
+ signatureEnd: body
192
+ ? body.range().start.line + (isPython ? 0 : 1)
193
+ : undefined,
194
+ body: body ? JSON.stringify(tokens(body)) : undefined,
195
+ exported: isPython
196
+ ? !name.split(".").some((part) => part.startsWith("_"))
197
+ : ancestors.some((a) => a.kind() === "export_statement") ||
198
+ envelope.kind() === "export_statement",
199
+ start: start + 1,
200
+ end: end.line + (end.column === 0 ? 0 : 1),
201
+ });
202
+ const declarationErrors = envelope.findAll({
203
+ rule: { kind: "ERROR" },
204
+ });
205
+ if (declarationErrors.length) {
206
+ limits.push({ kind: "parse_partial", declaration: name });
207
+ for (const error of declarationErrors)
208
+ attributedErrors.add(error.id());
209
+ }
210
+ if (
211
+ options?.descendAbove === undefined ||
212
+ end.line + (end.column === 0 ? 0 : 1) - start <=
213
+ options.descendAbove
214
+ )
215
+ return;
216
+ }
217
+ for (const child of node.children()) visit(child);
218
+ };
219
+ visit(root);
220
+ if (errors.some((error) => !attributedErrors.has(error.id())))
221
+ limits.push({ kind: "parse_partial" });
222
+ return { declarations: result, limits };
223
+ },
224
+ };
225
+ return parser;
226
+ } catch {
227
+ return undefined;
228
+ }
229
+ }
@@ -0,0 +1,168 @@
1
+ import { type FileHandle, lstat } from "node:fs/promises";
2
+ import { TEST_SOURCE_MAX_BYTES, TIMEOUT_MS } from "../constants.ts";
3
+ import type { GitExec } from "../core/git.ts";
4
+ import type { ImportSource } from "../core/imports.ts";
5
+ import type { Result } from "../result.ts";
6
+ import {
7
+ checkFileAdmission,
8
+ openRepoFile,
9
+ resolveInsideRepo,
10
+ } from "./files.ts";
11
+ import { decodeUtf8 } from "./utf8.ts";
12
+
13
+ export async function collectTestInventory(
14
+ exec: GitExec,
15
+ cwd: string,
16
+ signal?: AbortSignal,
17
+ ): Promise<
18
+ Result<{
19
+ cwd: string;
20
+ paths: readonly string[];
21
+ read(path: string): Promise<ImportSource | undefined>;
22
+ limits: readonly {
23
+ path: string;
24
+ kind: "unreadable" | "too_large" | "not UTF-8 text";
25
+ }[];
26
+ }>
27
+ > {
28
+ const root = await exec("git", ["rev-parse", "--show-toplevel"], {
29
+ cwd,
30
+ timeout: TIMEOUT_MS,
31
+ signal,
32
+ });
33
+ if (root.code !== 0 || root.killed)
34
+ return {
35
+ ok: false,
36
+ error: root.stderr || "Cannot identify repository root.",
37
+ };
38
+ cwd = root.stdout.replace(/\n$/, "");
39
+ const listed = await exec(
40
+ "git",
41
+ [
42
+ "ls-files",
43
+ "--cached",
44
+ "--others",
45
+ "--exclude-standard",
46
+ "--full-name",
47
+ "-z",
48
+ ],
49
+ { cwd, timeout: TIMEOUT_MS, signal },
50
+ );
51
+ if (listed.code !== 0 || listed.killed)
52
+ return {
53
+ ok: false,
54
+ error: listed.stderr || "Cannot inventory repository files.",
55
+ };
56
+ const ignored = await exec(
57
+ "git",
58
+ ["ls-files", "--cached", "--ignored", "--exclude-standard", "-z"],
59
+ { cwd, timeout: TIMEOUT_MS, signal },
60
+ );
61
+ if (ignored.code !== 0 || ignored.killed)
62
+ return {
63
+ ok: false,
64
+ error: ignored.stderr || "Cannot identify ignored tracked files.",
65
+ };
66
+ const excluded = new Set(ignored.stdout.split("\0").filter(Boolean));
67
+ const paths = listed.stdout
68
+ .split("\0")
69
+ .filter(
70
+ (path) =>
71
+ path &&
72
+ (!excluded.has(path) ||
73
+ /(?:^|\/)(?:package|tsconfig[^/]*)\.json$/.test(path)),
74
+ );
75
+ const admitted = new Set(paths);
76
+ const limits: {
77
+ path: string;
78
+ kind: "unreadable" | "too_large" | "not UTF-8 text";
79
+ }[] = [];
80
+ const cache = new Map<string, Promise<ImportSource | undefined>>();
81
+ for (let offset = 0; offset < paths.length; offset += 16) {
82
+ const inspected = await Promise.all(
83
+ paths.slice(offset, offset + 16).map(async (path) => {
84
+ try {
85
+ signal?.throwIfAborted();
86
+ const admission = await checkFileAdmission(
87
+ cwd,
88
+ path,
89
+ exec,
90
+ signal,
91
+ admitted,
92
+ );
93
+ if (!admission.ok) return "unreadable" as const;
94
+ const resolved = await resolveInsideRepo(cwd, path);
95
+ if (!resolved.ok) return "unreadable" as const;
96
+ const stat = await lstat(resolved.abs);
97
+ if (!stat.isFile()) return "unreadable" as const;
98
+ return stat.size > TEST_SOURCE_MAX_BYTES
99
+ ? ("too_large" as const)
100
+ : undefined;
101
+ } catch {
102
+ return "unreadable" as const;
103
+ }
104
+ }),
105
+ );
106
+ for (const [index, kind] of inspected.entries()) {
107
+ if (!kind) continue;
108
+ const path = paths[offset + index];
109
+ if (path === undefined || cache.has(path)) continue;
110
+ limits.push({ path, kind });
111
+ cache.set(path, Promise.resolve(undefined));
112
+ }
113
+ }
114
+ const read = (path: string): Promise<ImportSource | undefined> => {
115
+ const cached = cache.get(path);
116
+ if (cached) return cached;
117
+ const pending = (async () => {
118
+ let handle: FileHandle | undefined;
119
+ try {
120
+ signal?.throwIfAborted();
121
+ const opened = await openRepoFile(cwd, path, {
122
+ exec,
123
+ signal,
124
+ inventory: admitted,
125
+ });
126
+ if (!opened.ok) return { path, kind: "unreadable" as const };
127
+ handle = opened.handle;
128
+ const stat = { size: opened.size };
129
+ if (stat.size > TEST_SOURCE_MAX_BYTES)
130
+ return { path, kind: "too_large" as const };
131
+ const bytes = Buffer.alloc(
132
+ Math.min(stat.size + 1, TEST_SOURCE_MAX_BYTES + 1),
133
+ );
134
+ let count = 0;
135
+ while (count < bytes.length) {
136
+ signal?.throwIfAborted();
137
+ const part = await handle.read(
138
+ bytes,
139
+ count,
140
+ bytes.length - count,
141
+ null,
142
+ );
143
+ if (!part.bytesRead) break;
144
+ count += part.bytesRead;
145
+ }
146
+ if (count === bytes.length) return { path, kind: "too_large" as const };
147
+ if (bytes.subarray(0, count).includes(0)) return { path, text: "" };
148
+ const decoded = decodeUtf8(bytes.subarray(0, count));
149
+ if (!decoded.ok) return { path, kind: "not UTF-8 text" as const };
150
+ const text = decoded.text;
151
+ return { path, text };
152
+ } catch {
153
+ return { path, kind: "unreadable" as const };
154
+ } finally {
155
+ await handle?.close();
156
+ }
157
+ })().then((file) => {
158
+ if (file.kind) {
159
+ limits.push({ path: file.path, kind: file.kind });
160
+ return undefined;
161
+ }
162
+ return { path: file.path, text: file.text };
163
+ });
164
+ cache.set(path, pending);
165
+ return pending;
166
+ };
167
+ return { ok: true, cwd, paths, read, limits };
168
+ }
@@ -0,0 +1,21 @@
1
+ import type { JudgmentMetadata } from "../jev/types.ts";
2
+
3
+ export function hostUsage(isOmp: boolean, usage: JudgmentMetadata["usage"]) {
4
+ if (isOmp || !usage) return {};
5
+ return {
6
+ usage: {
7
+ input: usage.inputTokens,
8
+ output: 0,
9
+ cacheRead: 0,
10
+ cacheWrite: 0,
11
+ totalTokens: usage.inputTokens,
12
+ cost: {
13
+ input: usage.costUsd,
14
+ output: 0,
15
+ cacheRead: 0,
16
+ cacheWrite: 0,
17
+ total: usage.costUsd,
18
+ },
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,57 @@
1
+ import { isUtf8 } from "node:buffer";
2
+ import { createHash } from "node:crypto";
3
+ import type { Result } from "../result.ts";
4
+
5
+ /** Callers enforce byte budgets before decoding. BOM and legitimate controls are preserved. */
6
+ export function decodeUtf8(bytes: Uint8Array): Result<{ text: string }> {
7
+ return isUtf8(bytes)
8
+ ? {
9
+ ok: true,
10
+ text: Buffer.from(
11
+ bytes.buffer,
12
+ bytes.byteOffset,
13
+ bytes.byteLength,
14
+ ).toString("utf8"),
15
+ }
16
+ : { ok: false, error: "not UTF-8 text" };
17
+ }
18
+
19
+ /** Keep incomplete code points between chunks, but reject them at EOF. */
20
+ export function createUtf8Decoder() {
21
+ const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
22
+ return (bytes?: Uint8Array): Result<{ text: string }> => {
23
+ try {
24
+ return {
25
+ ok: true,
26
+ text: decoder.decode(bytes, { stream: bytes !== undefined }),
27
+ };
28
+ } catch {
29
+ return { ok: false, error: "not UTF-8 text" };
30
+ }
31
+ };
32
+ }
33
+
34
+ /** Verify host-decoded text against the raw Git object; replacement cannot silently become evidence. */
35
+ export function verifyGitUtf8(
36
+ text: string,
37
+ id?: string,
38
+ size?: number,
39
+ ): Result<{ text: string }> {
40
+ const bytes = Buffer.from(text, "utf8");
41
+ if (size !== undefined && bytes.length !== size)
42
+ return { ok: false, error: "not UTF-8 text" };
43
+ if (id && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(id)) {
44
+ const actual = createHash(id.length === 64 ? "sha256" : "sha1")
45
+ .update(`blob ${bytes.length}\0`)
46
+ .update(bytes)
47
+ .digest("hex");
48
+ if (actual !== id) return { ok: false, error: "not UTF-8 text" };
49
+ } else if (text.includes("\uFFFD")) {
50
+ return {
51
+ ok: false,
52
+ error:
53
+ "not verifiable as UTF-8 text (contains U+FFFD after host decoding)",
54
+ };
55
+ }
56
+ return decodeUtf8(bytes);
57
+ }
@@ -0,0 +1,109 @@
1
+ export const STATE_MAX_CHARS = 80_000;
2
+ export const TEST_SOURCE_MAX_BYTES = STATE_MAX_CHARS * 16;
3
+ export const SELECT_FILE_SHARE = 0.8;
4
+ export const ASK_NOTE_MAX_CHARS = 8_000;
5
+ export const ASK_MAX_FILES = 20;
6
+ export const ASK_TIMEOUT_S = 60;
7
+ export const ASK_TIMEOUT_MAX_S = 300;
8
+ export const OUTPUT_REPEAT_MIN = 5;
9
+ export const OUTPUT_CHUNK_CHARS = 2_500;
10
+ export const OUTPUT_FIND_MAX_CALLS = 40;
11
+ export const OUTPUT_HEAD_SHARE = 0.05;
12
+ export const OUTPUT_TAIL_SHARE = 0.15;
13
+ // Unmeasured admission bound for each command output file; never truncate it silently.
14
+ export const OUTPUT_FILE_MAX_BYTES = 64 * 1024 * 1024;
15
+ export const OUTPUT_LINE_MAX_CHARS = 8_192;
16
+ export const OUTPUT_SHAPE_MAX_COUNT = 2_048;
17
+ export const OUTPUT_FAILURE_WINDOW_LINES = 20;
18
+ export const CHOICE_MAX_OPTIONS = 255;
19
+ export const MAX_FILES = 255;
20
+ export const FILE_MAX_KB = STATE_MAX_CHARS / 1_000;
21
+ export const SCORE_MIN_LEVELS = 2;
22
+ export const SCORE_MAX_LEVELS = 10;
23
+ export const TIMEOUT_MS = 10_000;
24
+ export const UNIT_MAX_CHARS = 6_000;
25
+ export const UNIT_CONTEXT_LINES = 8;
26
+ export const SELECT_MIN = 0.5;
27
+ export const BAND_BOOL_GRAY_A = 0.2;
28
+ export const BAND_BOOL_YES_MIN = 1 - BAND_BOOL_GRAY_A;
29
+ export const ORDER_REVERSE_BELOW = 0.85;
30
+ export const ORDER_DISAGREE_MIN = 0.1;
31
+ export const BAND_CHOICE_VERDICT_MIN = ORDER_REVERSE_BELOW;
32
+ export const CANNOT_TELL_MIN = 0.3;
33
+ export const REQUEST_MAX_TOKENS = 60_000;
34
+ export const RATE_PER_SECOND = 8;
35
+ export const CONCURRENCY = 8;
36
+ export const REQUEST_ATTEMPTS = 3;
37
+ export const RETRY_BASE_MS = 500;
38
+ export const RETRY_MAX_MS = 5_000;
39
+ export const HTTP_ERROR_MAX_CHARS = 300;
40
+ export const QUESTION_TOKENS = 1 / 3.3;
41
+ export const STATE_TOKENS = 0.283;
42
+ export const REQUEST_BASE_TOKENS = 456;
43
+ export const FLAG_MIN = 0.7;
44
+ export const DOCS_CHECK_MIN = 0.2;
45
+ export const DOCS_MAX_SECTIONS = 40;
46
+ export const HOOK_BUDGET_MS = 15_000;
47
+ /** Progressive collection time bound, not a coverage or wall-time guarantee. */
48
+ export const DOCS_COLLECT_BUDGET_MS = 750;
49
+ export const DOCS_NAME_MAX_FILES = 32;
50
+ /** Initial per-anchor traversal bound; shared edge cache remains unbounded. */
51
+ export const DOCS_ANCHOR_MAX_FILES = 32;
52
+ export const WITNESS_LURE_MAX = 0.15;
53
+ /** Small coverage decoys only; risk and large-witness policies remain unchanged. */
54
+ export const COVERAGE_WITNESS_LURE_MAX = 0.2;
55
+ export const WITNESS_LARGE_LURE_MAX = 0.25;
56
+ export const WITNESS_LARGE_CHARS = 13_000;
57
+ export const WITNESS_ETALON_MIN = 0.7;
58
+ export const WITNESS_AUTO_MIN_CELLS = 8;
59
+ export const CLOSURE_MAX_CHARS = 20_000;
60
+ export const CALLER_DISPLAY_MAX_SPANS = 8;
61
+ export const LIMIT_DISPLAY_MAX_PATHS = 4;
62
+ export const DOCS_DISPLAY_MAX_SECTIONS = 5;
63
+ export const FIND_NAME_CANDIDATES = 128;
64
+ export const FIND_NAME_BATCH = 64;
65
+ export const FIND_FILES_READ = 20;
66
+ export const FIND_READ_PER_CALL = 5;
67
+ export const FIND_EXCERPT_CHARS = 3_000;
68
+ export const FIND_POINTER_EXCERPT_CHARS = 1_500;
69
+ export const FIND_POINTER_MAX = 8;
70
+ export const FIND_NAME_GUARD_MIN = 0.9;
71
+ export const FIND_CONFIRM_MIN = 0.8;
72
+ export const FIND_GOAL_MIN_WORDS = 5;
73
+ export const FIND_QUICK_CANDIDATES = 32;
74
+ export const FIND_QUICK_READ = 8;
75
+ export const FIND_THOROUGH_CANDIDATES = 256;
76
+ export const FIND_THOROUGH_READ = 40;
77
+ export const FIND_EXCERPT_HEAD_CHARS = 1_000;
78
+ export const FIND_CONTENT_MIN = 0.5;
79
+ export const FIND_NAME_GUARD_TOP = 3;
80
+ export const FIND_PATH_WEIGHT = 3;
81
+ export const FIND_GREP_PAGE_SIZE = 1024;
82
+ export const FIND_READ_CHUNK_BYTES = 16_384;
83
+ export const LOCATE_MIN_KB = 19;
84
+ export const LOCATE_VERDICT_MIN = 0.7;
85
+ export const LOCATE_GRAY_MIN = 0.4;
86
+ export const LOCATE_SHRINK_TOP = 3;
87
+ export const LOCATE_SECTION_MAX_LINES = 150;
88
+ export const LOCATE_SECTION_MIN_LINES = 8;
89
+ export const LOCATE_WINDOW_LINES = 80;
90
+ export const LOCATE_LABEL_MAX_CHARS = 120;
91
+ export const LOCATE_WHOLE_MAX_CHARS = STATE_MAX_CHARS * 4;
92
+ export const LOCATE_WHOLE_MAX_BYTES = LOCATE_WHOLE_MAX_CHARS * 4;
93
+ export const LOCATE_READ_BUFFER_BYTES = 64 * 1024;
94
+ // Unmeasured engineering bound: leaves room for multi-window refinement and JSON metadata.
95
+ export const LOCATE_WINDOW_MAX_SERIALIZED_CHARS = STATE_MAX_CHARS / 4;
96
+ // Keep SHA-256 IDs plus options below Windows' command-line limit.
97
+ export const GIT_BLOB_BATCH_SIZE = 400;
98
+ export const RUNNER_PACKAGE_MAX_BYTES = 64 * 1024;
99
+
100
+ // ADR 0001: allowed local dependencies, including erased type imports.
101
+ export const IMPORT_LAYERS = {
102
+ core: ["core", "constants.ts", "result.ts", "jev/types.ts"],
103
+ presets: ["presets", "core", "constants.ts", "result.ts", "jev/types.ts"],
104
+ adapters: ["adapters", "core", "constants.ts", "result.ts", "jev/types.ts"],
105
+ jev: ["jev", "core", "constants.ts", "result.ts"],
106
+ texts: ["texts", "constants.ts", "presets"],
107
+ "constants.ts": [],
108
+ "result.ts": [],
109
+ } as const;