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.
- package/CHANGELOG.md +66 -0
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/docs/design.md +187 -0
- package/docs/tools/jev_ask.md +92 -0
- package/docs/tools/jev_ask_files.md +80 -0
- package/docs/tools/jev_check_diff.md +77 -0
- package/docs/tools/jev_find_files.md +59 -0
- package/docs/tools/jev_locate_in_file.md +51 -0
- package/docs/tools/jev_select_tests.md +59 -0
- package/package.json +85 -0
- package/rules/jev-ask.md +4 -0
- package/src/adapters/analysis-context.ts +100 -0
- package/src/adapters/ask-files.ts +236 -0
- package/src/adapters/ask-proof.ts +202 -0
- package/src/adapters/ask-syntax.ts +463 -0
- package/src/adapters/command.ts +240 -0
- package/src/adapters/docs.ts +222 -0
- package/src/adapters/files.ts +411 -0
- package/src/adapters/find.ts +151 -0
- package/src/adapters/git-base.ts +32 -0
- package/src/adapters/git-inventory.ts +94 -0
- package/src/adapters/git.ts +525 -0
- package/src/adapters/locate-file.ts +197 -0
- package/src/adapters/output-lines.ts +50 -0
- package/src/adapters/risk-callers.ts +525 -0
- package/src/adapters/runner-version.ts +102 -0
- package/src/adapters/syntax.ts +229 -0
- package/src/adapters/test-inventory.ts +168 -0
- package/src/adapters/usage.ts +21 -0
- package/src/adapters/utf8.ts +57 -0
- package/src/constants.ts +109 -0
- package/src/core/ask-closure.ts +419 -0
- package/src/core/ask-proof.ts +32 -0
- package/src/core/ask-references.ts +249 -0
- package/src/core/asks.ts +616 -0
- package/src/core/batches.ts +83 -0
- package/src/core/command-output.ts +249 -0
- package/src/core/diff.ts +226 -0
- package/src/core/docs.ts +399 -0
- package/src/core/find.ts +157 -0
- package/src/core/git.ts +5 -0
- package/src/core/import-boundaries.ts +102 -0
- package/src/core/imports.ts +691 -0
- package/src/core/integrity.ts +64 -0
- package/src/core/lexical.ts +130 -0
- package/src/core/locate.ts +213 -0
- package/src/core/output.ts +264 -0
- package/src/core/pointer.ts +51 -0
- package/src/core/risk-callers.ts +1270 -0
- package/src/core/runner-version.ts +66 -0
- package/src/core/sections.ts +269 -0
- package/src/core/state.ts +53 -0
- package/src/core/syntax.ts +8 -0
- package/src/core/test-commands.ts +430 -0
- package/src/core/test-coverage.ts +103 -0
- package/src/core/test-discovery.ts +1695 -0
- package/src/core/test-evidence.ts +649 -0
- package/src/core/test-state.ts +99 -0
- package/src/core/truncate.ts +14 -0
- package/src/core/units.ts +531 -0
- package/src/describe.ts +26 -0
- package/src/guide.ts +42 -0
- package/src/host.ts +22 -0
- package/src/index.ts +40 -0
- package/src/jev/client.ts +505 -0
- package/src/jev/pool.ts +60 -0
- package/src/jev/types.ts +60 -0
- package/src/presets/docs.ts +85 -0
- package/src/presets/risk.ts +263 -0
- package/src/presets/spec.ts +111 -0
- package/src/presets/witnesses.ts +313 -0
- package/src/render.ts +45 -0
- package/src/result.ts +4 -0
- package/src/run-end.ts +157 -0
- package/src/runtime.ts +16 -0
- package/src/session.ts +106 -0
- package/src/texts/ask-files.ts +2 -0
- package/src/texts/ask.ts +4 -0
- package/src/texts/check-diff.ts +24 -0
- package/src/texts/configuration.ts +2 -0
- package/src/texts/find.ts +14 -0
- package/src/texts/guide.ts +16 -0
- package/src/texts/locate.ts +10 -0
- package/src/texts/run-end.ts +13 -0
- package/src/texts/select-tests.ts +3 -0
- package/src/tools/ask-files.ts +263 -0
- package/src/tools/ask-schema.ts +116 -0
- package/src/tools/ask.ts +925 -0
- package/src/tools/check-diff.ts +510 -0
- package/src/tools/docs-check.ts +399 -0
- package/src/tools/find.ts +529 -0
- package/src/tools/locate.ts +369 -0
- package/src/tools/select-tests.ts +746 -0
- package/src/tools/spec-check.ts +210 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type VersionedRunner = "vitest" | "jest" | "mocha";
|
|
2
|
+
|
|
3
|
+
export function runnerVersion(value: unknown): string | undefined {
|
|
4
|
+
return typeof value === "string" &&
|
|
5
|
+
/^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(value)
|
|
6
|
+
? value
|
|
7
|
+
: undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Parse only resolved lock versions, never dependency ranges or executable configuration. */
|
|
11
|
+
export function lockedRunnerVersion(
|
|
12
|
+
runner: VersionedRunner,
|
|
13
|
+
path: string,
|
|
14
|
+
text: string,
|
|
15
|
+
): string | undefined {
|
|
16
|
+
if (path.endsWith("package-lock.json")) {
|
|
17
|
+
try {
|
|
18
|
+
const lock = JSON.parse(text) as {
|
|
19
|
+
packages?: Record<string, { version?: unknown }>;
|
|
20
|
+
dependencies?: Record<string, { version?: unknown }>;
|
|
21
|
+
};
|
|
22
|
+
return runnerVersion(
|
|
23
|
+
lock.packages?.[`node_modules/${runner}`]?.version ??
|
|
24
|
+
lock.dependencies?.[runner]?.version,
|
|
25
|
+
);
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (path.endsWith("bun.lock")) {
|
|
31
|
+
const match = new RegExp(
|
|
32
|
+
`"${runner}"\\s*:\\s*\\[\\s*"${runner}@(\\d+\\.\\d+\\.\\d+(?:-[\\w.-]+)?)"`,
|
|
33
|
+
).exec(text);
|
|
34
|
+
return runnerVersion(match?.[1]);
|
|
35
|
+
}
|
|
36
|
+
if (path.endsWith("pnpm-lock.yaml")) {
|
|
37
|
+
const rootImporter =
|
|
38
|
+
/(?:^|\n) {2}\.:\s*\n([\s\S]*?)(?=\n {2}[^ ]|\n[^ ]|$)/.exec(text)?.[1] ??
|
|
39
|
+
"";
|
|
40
|
+
const block = new RegExp(
|
|
41
|
+
`(?:^|\\n)\\s{4,}${runner}:\\s*\\n\\s+specifier:[^\\n]*\\n\\s+version:\\s*['"]?([^\\s'"(]+)`,
|
|
42
|
+
).exec(rootImporter);
|
|
43
|
+
return runnerVersion(block?.[1]);
|
|
44
|
+
}
|
|
45
|
+
if (path.endsWith("yarn.lock")) {
|
|
46
|
+
const expression = new RegExp(
|
|
47
|
+
`(?:^|\\n)["']?${runner}@[^\\n]*:\\s*\\n(?:[^\\n]*\\n)*?\\s+version[ :]+["']?([^\\s"']+)`,
|
|
48
|
+
"g",
|
|
49
|
+
);
|
|
50
|
+
const versions = new Set(
|
|
51
|
+
[...text.matchAll(expression)].map((match) => runnerVersion(match[1])),
|
|
52
|
+
);
|
|
53
|
+
return versions.size === 1 ? [...versions][0] : undefined;
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
// Only identity-smoked release lines can receive native name filters.
|
|
58
|
+
export const VERIFIED_NAME_FILTERS = {
|
|
59
|
+
vitest: [
|
|
60
|
+
{ version: /^3\.2\.\d+$/, separator: " " },
|
|
61
|
+
{ version: /^4\.1\.\d+$/, separator: " " },
|
|
62
|
+
{ version: /^5\.0\.\d+$/, separator: " > " },
|
|
63
|
+
],
|
|
64
|
+
jest: [{ version: /^30\.\d+\.\d+$/, separator: " " }],
|
|
65
|
+
mocha: [{ version: /^11\.8\.0$/, separator: " " }],
|
|
66
|
+
} as const;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOCATE_SECTION_MAX_LINES,
|
|
3
|
+
LOCATE_SECTION_MIN_LINES,
|
|
4
|
+
LOCATE_WINDOW_LINES,
|
|
5
|
+
} from "../constants.ts";
|
|
6
|
+
|
|
7
|
+
export interface SectionDeclaration {
|
|
8
|
+
name: string;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
}
|
|
12
|
+
export interface Section {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
text: string;
|
|
18
|
+
serializedTextChars?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface Sections {
|
|
21
|
+
sections: Section[];
|
|
22
|
+
lines: number;
|
|
23
|
+
method: "syntax" | "markdown" | "heuristic" | "windows";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Exact Markdown heading sections; callers may apply their own display grouping. */
|
|
27
|
+
export function markdownSections(
|
|
28
|
+
text: string,
|
|
29
|
+
): (Section & { level: number })[] {
|
|
30
|
+
const lines = text.split("\n");
|
|
31
|
+
if (lines.at(-1) === "") lines.pop();
|
|
32
|
+
const headings: { start: number; label: string; level: number }[] = [];
|
|
33
|
+
let fence: { character: string; length: number } | undefined;
|
|
34
|
+
let frontMatter = lines[0]?.trim() === "---";
|
|
35
|
+
let html: string | undefined;
|
|
36
|
+
let previousText = false;
|
|
37
|
+
for (let index = 0; index < lines.length; index++) {
|
|
38
|
+
const line = lines[index] ?? "";
|
|
39
|
+
if (frontMatter) {
|
|
40
|
+
if (index > 0 && /^(?:---|\.\.\.)\s*$/.test(line)) frontMatter = false;
|
|
41
|
+
previousText = false;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (html) {
|
|
45
|
+
if (html === "blank" ? !line.trim() : line.toLowerCase().includes(html))
|
|
46
|
+
html = undefined;
|
|
47
|
+
previousText = false;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const marker = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
51
|
+
if (fence) {
|
|
52
|
+
if (
|
|
53
|
+
marker &&
|
|
54
|
+
marker[1]?.[0] === fence.character &&
|
|
55
|
+
marker[1].length >= fence.length &&
|
|
56
|
+
!marker[2]?.trim()
|
|
57
|
+
)
|
|
58
|
+
fence = undefined;
|
|
59
|
+
previousText = false;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (marker && !(marker[1]?.[0] === "`" && marker[2]?.includes("`"))) {
|
|
63
|
+
fence = {
|
|
64
|
+
character: marker[1]?.[0] ?? "`",
|
|
65
|
+
length: marker[1]?.length ?? 3,
|
|
66
|
+
};
|
|
67
|
+
previousText = false;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const block = /^ {0,3}<(script|pre|style|textarea)(?:\s|>|$)/i.exec(line);
|
|
71
|
+
const closing = line.trimStart().startsWith("<!--")
|
|
72
|
+
? "-->"
|
|
73
|
+
: block
|
|
74
|
+
? `</${block[1]?.toLowerCase()}>`
|
|
75
|
+
: /^ {0,3}<\/?(?:address|article|aside|blockquote|div|dl|fieldset|figure|footer|form|h[1-6]|header|hr|li|main|nav|ol|p|section|table|tbody|td|th|tr|ul)(?:\s|\/?>|$)/i.test(
|
|
76
|
+
line,
|
|
77
|
+
)
|
|
78
|
+
? "blank"
|
|
79
|
+
: undefined;
|
|
80
|
+
if (closing) {
|
|
81
|
+
if (closing === "blank" || !line.toLowerCase().includes(closing))
|
|
82
|
+
html = closing;
|
|
83
|
+
previousText = false;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const heading = /^ {0,3}#{1,6}(?:[\t ]+|$)(.*?)\s*$/.exec(line);
|
|
87
|
+
if (heading) {
|
|
88
|
+
headings.push({
|
|
89
|
+
start: index + 1,
|
|
90
|
+
level: line.trimStart().match(/^#+/)?.[0].length ?? 1,
|
|
91
|
+
label: (heading[1] ?? "").replace(/[\t ]+#+[\t ]*$/, "").trim(),
|
|
92
|
+
});
|
|
93
|
+
previousText = false;
|
|
94
|
+
} else if (previousText && /^ {0,3}(?:=+|-+)\s*$/.test(line)) {
|
|
95
|
+
headings.push({
|
|
96
|
+
start: index,
|
|
97
|
+
level: line.trimStart().startsWith("=") ? 1 : 2,
|
|
98
|
+
label: lines[index - 1]?.trim() ?? "",
|
|
99
|
+
});
|
|
100
|
+
previousText = false;
|
|
101
|
+
} else previousText = Boolean(line.trim()) && !/^ {4}|^\t/.test(line);
|
|
102
|
+
}
|
|
103
|
+
return headings.map((heading, index) => {
|
|
104
|
+
const end = (headings[index + 1]?.start ?? lines.length + 1) - 1;
|
|
105
|
+
return {
|
|
106
|
+
id: `S${index + 1}`,
|
|
107
|
+
...heading,
|
|
108
|
+
end,
|
|
109
|
+
text: lines.slice(heading.start - 1, end).join("\n"),
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Produces disjoint, exhaustive ranges; labels never replace the underlying evidence. */
|
|
115
|
+
export function sectionFile(
|
|
116
|
+
path: string,
|
|
117
|
+
text: string,
|
|
118
|
+
declarations?: readonly SectionDeclaration[],
|
|
119
|
+
): Sections {
|
|
120
|
+
const lines = text.split("\n");
|
|
121
|
+
if (lines.at(-1) === "") lines.pop();
|
|
122
|
+
if (!lines.length) return { sections: [], lines: 0, method: "windows" };
|
|
123
|
+
const boundaries: { start: number; label: string }[] = [];
|
|
124
|
+
let method: Sections["method"] = "windows";
|
|
125
|
+
if (declarations?.length) {
|
|
126
|
+
const sorted = [...declarations].sort(
|
|
127
|
+
(a, b) => a.start - b.start || b.end - a.end,
|
|
128
|
+
);
|
|
129
|
+
const ancestors: SectionDeclaration[] = [];
|
|
130
|
+
for (let index = 0; index < sorted.length; index++) {
|
|
131
|
+
const declaration = sorted[index];
|
|
132
|
+
if (!declaration) continue;
|
|
133
|
+
while ((ancestors.at(-1)?.end ?? Infinity) < declaration.end)
|
|
134
|
+
ancestors.pop();
|
|
135
|
+
const enclosing = ancestors.at(-1);
|
|
136
|
+
const suppressed =
|
|
137
|
+
enclosing &&
|
|
138
|
+
enclosing.end - enclosing.start + 1 <= LOCATE_SECTION_MAX_LINES;
|
|
139
|
+
if (!suppressed && boundaries.at(-1)?.start !== declaration.start)
|
|
140
|
+
boundaries.push({ start: declaration.start, label: declaration.name });
|
|
141
|
+
ancestors.push(declaration);
|
|
142
|
+
if (!suppressed && declaration.end < lines.length)
|
|
143
|
+
boundaries.push({
|
|
144
|
+
start: declaration.end + 1,
|
|
145
|
+
label: `${enclosing?.name ?? declaration.name} continuation`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
boundaries.sort(
|
|
149
|
+
(a, b) =>
|
|
150
|
+
a.start - b.start ||
|
|
151
|
+
Number(a.label.endsWith(" continuation")) -
|
|
152
|
+
Number(b.label.endsWith(" continuation")),
|
|
153
|
+
);
|
|
154
|
+
for (let index = boundaries.length - 1; index > 0; index--)
|
|
155
|
+
if (boundaries[index]?.start === boundaries[index - 1]?.start)
|
|
156
|
+
boundaries.splice(index, 1);
|
|
157
|
+
method = "syntax";
|
|
158
|
+
} else if (/\.(?:md|mdx|markdown)$/i.test(path)) {
|
|
159
|
+
for (const section of markdownSections(text))
|
|
160
|
+
boundaries.push({ start: section.start, label: section.label });
|
|
161
|
+
if (boundaries.length) method = "markdown";
|
|
162
|
+
}
|
|
163
|
+
if (!boundaries.length) {
|
|
164
|
+
let pythonClass:
|
|
165
|
+
| { name: string; indent: number; start: number; seenMethod: boolean }
|
|
166
|
+
| undefined;
|
|
167
|
+
lines.forEach((line, index) => {
|
|
168
|
+
const indentation = line.length - line.trimStart().length;
|
|
169
|
+
if (
|
|
170
|
+
path.endsWith(".py") &&
|
|
171
|
+
line.trim() &&
|
|
172
|
+
!line.trimStart().startsWith("#")
|
|
173
|
+
) {
|
|
174
|
+
if (pythonClass && indentation <= pythonClass.indent)
|
|
175
|
+
pythonClass = undefined;
|
|
176
|
+
const className = /^\s*class\s+(\w+)/.exec(line)?.[1];
|
|
177
|
+
if (className) {
|
|
178
|
+
pythonClass = {
|
|
179
|
+
name: className,
|
|
180
|
+
indent: indentation,
|
|
181
|
+
start: index + 1,
|
|
182
|
+
seenMethod: false,
|
|
183
|
+
};
|
|
184
|
+
boundaries.push({ start: index + 1, label: className });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const declaration =
|
|
189
|
+
/^\s*(?:(?:export|default|async|public|private|protected|static|abstract)\s+)*(?:(?:function\s*\*?|class|interface|enum|type|def|fn|func|struct|impl)\s+([\w.$]+)|(?:const|let|var)\s+([\w$]+)\s*=|([\w$]+)\s*\([^;]*\)\s*(?::[^=]+)?\s*\{)/.exec(
|
|
190
|
+
line,
|
|
191
|
+
);
|
|
192
|
+
if (!declaration) return;
|
|
193
|
+
const indent = line.length - line.trimStart().length;
|
|
194
|
+
if (
|
|
195
|
+
indent > 4 ||
|
|
196
|
+
/^(?:if|for|while|switch|catch)$/.test(declaration[3] ?? "")
|
|
197
|
+
)
|
|
198
|
+
return;
|
|
199
|
+
let start = index + 1;
|
|
200
|
+
if (
|
|
201
|
+
path.endsWith(".py") &&
|
|
202
|
+
pythonClass &&
|
|
203
|
+
/^\s*(?:async\s+)?def\s/.test(line) &&
|
|
204
|
+
!pythonClass.seenMethod
|
|
205
|
+
) {
|
|
206
|
+
boundaries.pop();
|
|
207
|
+
pythonClass.seenMethod = true;
|
|
208
|
+
start = pythonClass.start;
|
|
209
|
+
}
|
|
210
|
+
boundaries.push({
|
|
211
|
+
start,
|
|
212
|
+
label: pythonClass
|
|
213
|
+
? `${pythonClass.name}.${declaration[1] ?? "method"}`
|
|
214
|
+
: (declaration[1] ?? declaration[2] ?? declaration[3] ?? "section"),
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
if (boundaries.length) method = "heuristic";
|
|
218
|
+
}
|
|
219
|
+
if (!boundaries.length) {
|
|
220
|
+
for (let start = 1; start <= lines.length; start += LOCATE_WINDOW_LINES)
|
|
221
|
+
boundaries.push({
|
|
222
|
+
start,
|
|
223
|
+
label: `lines ${start}-${Math.min(lines.length, start + LOCATE_WINDOW_LINES - 1)}`,
|
|
224
|
+
});
|
|
225
|
+
} else {
|
|
226
|
+
const first = boundaries[0];
|
|
227
|
+
if (first) first.start = 1;
|
|
228
|
+
}
|
|
229
|
+
const ranges = boundaries.map((boundary, index) => ({
|
|
230
|
+
...boundary,
|
|
231
|
+
end: (boundaries[index + 1]?.start ?? lines.length + 1) - 1,
|
|
232
|
+
}));
|
|
233
|
+
// Split oversized leaf nodes without losing a line or inventing syntax.
|
|
234
|
+
const chunks = ranges.flatMap((range) => {
|
|
235
|
+
const result: typeof ranges = [];
|
|
236
|
+
const size =
|
|
237
|
+
method === "windows" ? LOCATE_WINDOW_LINES : LOCATE_SECTION_MAX_LINES;
|
|
238
|
+
for (let start = range.start; start <= range.end; start += size)
|
|
239
|
+
result.push({
|
|
240
|
+
start,
|
|
241
|
+
end: Math.min(range.end, start + size - 1),
|
|
242
|
+
label: range.label,
|
|
243
|
+
});
|
|
244
|
+
return result;
|
|
245
|
+
});
|
|
246
|
+
const merged: typeof ranges = [];
|
|
247
|
+
for (const chunk of chunks) {
|
|
248
|
+
const previous = merged.at(-1);
|
|
249
|
+
if (
|
|
250
|
+
method !== "windows" &&
|
|
251
|
+
previous &&
|
|
252
|
+
(chunk.end - chunk.start + 1 < LOCATE_SECTION_MIN_LINES ||
|
|
253
|
+
previous.end - previous.start + 1 < LOCATE_SECTION_MIN_LINES) &&
|
|
254
|
+
chunk.end - previous.start + 1 <= LOCATE_SECTION_MAX_LINES
|
|
255
|
+
) {
|
|
256
|
+
previous.end = chunk.end;
|
|
257
|
+
previous.label += ` / ${chunk.label}`;
|
|
258
|
+
} else merged.push({ ...chunk });
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
lines: lines.length,
|
|
262
|
+
method,
|
|
263
|
+
sections: merged.map((range, index) => ({
|
|
264
|
+
id: `S${index + 1}`,
|
|
265
|
+
...range,
|
|
266
|
+
text: lines.slice(range.start - 1, range.end).join("\n"),
|
|
267
|
+
})),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ASK_NOTE_MAX_CHARS, STATE_MAX_CHARS } from "../constants.ts";
|
|
2
|
+
import type { State } from "../jev/types.ts";
|
|
3
|
+
import { isRecord, type Result } from "../result.ts";
|
|
4
|
+
|
|
5
|
+
const reservedKeys = ["files", "files_before", "base_sha", "closure"] as const;
|
|
6
|
+
|
|
7
|
+
export function assembleState(
|
|
8
|
+
note: string | undefined,
|
|
9
|
+
files: Record<string, string>,
|
|
10
|
+
): Result<{ state: State }> {
|
|
11
|
+
if (note !== undefined && note.length > ASK_NOTE_MAX_CHARS)
|
|
12
|
+
return {
|
|
13
|
+
ok: false,
|
|
14
|
+
error: `state exceeds ${ASK_NOTE_MAX_CHARS} chars (${note.length}); shorten the note.`,
|
|
15
|
+
};
|
|
16
|
+
let state: State = {};
|
|
17
|
+
if (note !== undefined) {
|
|
18
|
+
let parsed: unknown;
|
|
19
|
+
try {
|
|
20
|
+
parsed = JSON.parse(note);
|
|
21
|
+
} catch {
|
|
22
|
+
parsed = note;
|
|
23
|
+
}
|
|
24
|
+
const reserved = isRecord(parsed)
|
|
25
|
+
? reservedKeys.find((key) => Object.hasOwn(parsed, key))
|
|
26
|
+
: undefined;
|
|
27
|
+
if (reserved)
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: `state contains reserved key ${reserved}; rename it in your note and use paths for files read by code.`,
|
|
31
|
+
};
|
|
32
|
+
if (isRecord(parsed)) state = parsed as State;
|
|
33
|
+
else state.state = parsed as State[string];
|
|
34
|
+
}
|
|
35
|
+
const noteSize = JSON.stringify(state).length;
|
|
36
|
+
if (Object.keys(files).length) state.files = files;
|
|
37
|
+
const size = JSON.stringify(state).length;
|
|
38
|
+
if (size > STATE_MAX_CHARS)
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
error: `serialized state exceeds ${STATE_MAX_CHARS} chars (${size}); serialized parts: note ${noteSize}, ${Object.entries(
|
|
42
|
+
files,
|
|
43
|
+
)
|
|
44
|
+
.map(
|
|
45
|
+
([path, text]) =>
|
|
46
|
+
`${path} ${JSON.stringify({ [path]: text }).length}`,
|
|
47
|
+
)
|
|
48
|
+
.join(
|
|
49
|
+
", ",
|
|
50
|
+
)}. Split the situation into smaller states or provide smaller source files; files are never truncated.`,
|
|
51
|
+
};
|
|
52
|
+
return { ok: true, state };
|
|
53
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SgNode } from "@ast-grep/napi";
|
|
2
|
+
import type { Result } from "../result.ts";
|
|
3
|
+
import type { SyntaxParser } from "./units.ts";
|
|
4
|
+
|
|
5
|
+
export interface SyntaxRootParser extends SyntaxParser {
|
|
6
|
+
parse(path: string, text: string): Result<{ root: SgNode }>;
|
|
7
|
+
parseRaw(path: string, text: string): Result<{ root: SgNode }>;
|
|
8
|
+
}
|