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,399 @@
|
|
|
1
|
+
import { createAnalysisContext } from "../adapters/analysis-context.ts";
|
|
2
|
+
import {
|
|
3
|
+
collectDocsInventory,
|
|
4
|
+
docsDeclarationSearch,
|
|
5
|
+
} from "../adapters/docs.ts";
|
|
6
|
+
import { collectUnits } from "../adapters/git.ts";
|
|
7
|
+
import { resolveBase } from "../adapters/git-base.ts";
|
|
8
|
+
import { shareGitInventory } from "../adapters/git-inventory.ts";
|
|
9
|
+
import {
|
|
10
|
+
CHOICE_MAX_OPTIONS,
|
|
11
|
+
DOCS_COLLECT_BUDGET_MS,
|
|
12
|
+
DOCS_DISPLAY_MAX_SECTIONS,
|
|
13
|
+
DOCS_MAX_SECTIONS,
|
|
14
|
+
STATE_MAX_CHARS,
|
|
15
|
+
} from "../constants.ts";
|
|
16
|
+
import { collectDocsCandidates } from "../core/docs.ts";
|
|
17
|
+
import {
|
|
18
|
+
type AnswerInput,
|
|
19
|
+
type BudgetRefusal,
|
|
20
|
+
buildEnvelope,
|
|
21
|
+
type Envelope,
|
|
22
|
+
type Limitation,
|
|
23
|
+
} from "../core/output.ts";
|
|
24
|
+
import type { Judgment } from "../jev/types.ts";
|
|
25
|
+
import {
|
|
26
|
+
type DocsFinding,
|
|
27
|
+
prepareDocsCheck,
|
|
28
|
+
readDocsJudgment,
|
|
29
|
+
} from "../presets/docs.ts";
|
|
30
|
+
import type { ToolDependencies } from "../runtime.ts";
|
|
31
|
+
import { NOT_CONFIGURED } from "../texts/configuration.ts";
|
|
32
|
+
|
|
33
|
+
export interface DocsCheckInput {
|
|
34
|
+
cwd: string;
|
|
35
|
+
base?: string;
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
budgetMs?: number;
|
|
38
|
+
maxCalls?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface DocsCheckResult {
|
|
41
|
+
ok: boolean;
|
|
42
|
+
status:
|
|
43
|
+
| "completed"
|
|
44
|
+
| "collect_budget_reached"
|
|
45
|
+
| "budget_exceeded"
|
|
46
|
+
| "refused";
|
|
47
|
+
envelope: Envelope;
|
|
48
|
+
judgments: Judgment[];
|
|
49
|
+
findings: DocsFinding[];
|
|
50
|
+
unjudged?: { count: number; sections: readonly string[]; truncated: boolean };
|
|
51
|
+
collection?: {
|
|
52
|
+
kind: "collection_budget";
|
|
53
|
+
count: number;
|
|
54
|
+
sections: readonly string[];
|
|
55
|
+
truncated: boolean;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Shared judgment seam: no guide delivery, rendering, or session answer recording. */
|
|
59
|
+
export async function runDocsCheck(
|
|
60
|
+
deps: ToolDependencies,
|
|
61
|
+
input: DocsCheckInput,
|
|
62
|
+
): Promise<DocsCheckResult> {
|
|
63
|
+
const started = performance.now();
|
|
64
|
+
deps = { ...deps, exec: shareGitInventory(deps.exec) };
|
|
65
|
+
const timeout =
|
|
66
|
+
input.budgetMs === undefined
|
|
67
|
+
? undefined
|
|
68
|
+
: AbortSignal.timeout(Math.max(0, Math.ceil(input.budgetMs)));
|
|
69
|
+
const signals = [input.signal, timeout].filter(
|
|
70
|
+
(value): value is AbortSignal => value !== undefined,
|
|
71
|
+
);
|
|
72
|
+
const signal = signals.length ? AbortSignal.any(signals) : undefined;
|
|
73
|
+
const judgments: Judgment[] = [];
|
|
74
|
+
const findings: DocsFinding[] = [];
|
|
75
|
+
const unchecked: string[] = [];
|
|
76
|
+
let unjudged: DocsCheckResult["unjudged"];
|
|
77
|
+
let collection: DocsCheckResult["collection"];
|
|
78
|
+
const limitations: Limitation[] = [
|
|
79
|
+
{
|
|
80
|
+
fact: "Measured D22 limit: 2/45 documentation obligations found across 180 partial got/zod commits.",
|
|
81
|
+
next: "Also review missing documentation: this check evaluates existing sentences, not documentation completeness.",
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
let budget: BudgetRefusal | undefined;
|
|
85
|
+
let sent = 0;
|
|
86
|
+
let emptyBase: string | undefined;
|
|
87
|
+
const finish = (refusal?: string): DocsCheckResult => {
|
|
88
|
+
if (unchecked.length > 5) {
|
|
89
|
+
unjudged = {
|
|
90
|
+
count: unchecked.length,
|
|
91
|
+
sections: unchecked.slice(0, DOCS_MAX_SECTIONS),
|
|
92
|
+
truncated: unchecked.length > DOCS_MAX_SECTIONS,
|
|
93
|
+
};
|
|
94
|
+
limitations.push({
|
|
95
|
+
fact: `${unchecked.length} matching sections unjudged.`,
|
|
96
|
+
next: "Narrow the diff with base=… or rerun on the relevant documentation.",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const answers: AnswerInput[] = findings.map((finding) => ({
|
|
100
|
+
label: `${finding.section.path}:${finding.section.start}-${finding.section.end} § ${finding.section.heading} — ${finding.sentence ? JSON.stringify(finding.sentence.text) : "sentence not identified"} — ${finding.band === "verdict" ? "now false" : "needs checking"} after ${finding.units.map((unit) => `${unit.name} (${unit.file})`).join(", ")}`,
|
|
101
|
+
value: { head: "now_false", p: finding.probability },
|
|
102
|
+
band: finding.band,
|
|
103
|
+
reason: finding.reason,
|
|
104
|
+
}));
|
|
105
|
+
const envelope = buildEnvelope({
|
|
106
|
+
answers,
|
|
107
|
+
limitations:
|
|
108
|
+
emptyBase !== undefined && !refusal
|
|
109
|
+
? [
|
|
110
|
+
...limitations,
|
|
111
|
+
{
|
|
112
|
+
fact: `no changed units against ${emptyBase}`,
|
|
113
|
+
next: "nothing judged; pass base= or check the working directory",
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
: limitations,
|
|
117
|
+
...(collection || unjudged
|
|
118
|
+
? {
|
|
119
|
+
lines: [
|
|
120
|
+
...(collection
|
|
121
|
+
? [
|
|
122
|
+
{
|
|
123
|
+
type: "collection" as const,
|
|
124
|
+
title:
|
|
125
|
+
"anchored documentation unexplored; directly cited sections were checked",
|
|
126
|
+
count: collection.count,
|
|
127
|
+
items: collection.sections.slice(
|
|
128
|
+
0,
|
|
129
|
+
DOCS_DISPLAY_MAX_SECTIONS,
|
|
130
|
+
),
|
|
131
|
+
next: "Read these sections; narrow the diff with base=… to target their dependency.",
|
|
132
|
+
},
|
|
133
|
+
]
|
|
134
|
+
: []),
|
|
135
|
+
...(unjudged
|
|
136
|
+
? [
|
|
137
|
+
{
|
|
138
|
+
type: "collection" as const,
|
|
139
|
+
title: "matching documentation unjudged",
|
|
140
|
+
count: unjudged.count,
|
|
141
|
+
items: unjudged.sections.slice(
|
|
142
|
+
0,
|
|
143
|
+
DOCS_DISPLAY_MAX_SECTIONS,
|
|
144
|
+
),
|
|
145
|
+
next: "Read these sections or rerun on the relevant documentation.",
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
: []),
|
|
149
|
+
],
|
|
150
|
+
}
|
|
151
|
+
: {}),
|
|
152
|
+
unchecked: unjudged ? [] : unchecked,
|
|
153
|
+
budget,
|
|
154
|
+
refusal,
|
|
155
|
+
...(!refusal &&
|
|
156
|
+
!budget &&
|
|
157
|
+
!collection &&
|
|
158
|
+
!unchecked.length &&
|
|
159
|
+
!findings.length &&
|
|
160
|
+
emptyBase === undefined
|
|
161
|
+
? {
|
|
162
|
+
lines: [
|
|
163
|
+
{
|
|
164
|
+
type: "list" as const,
|
|
165
|
+
title: "docs: no stale sentences reported",
|
|
166
|
+
items: [],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
}
|
|
170
|
+
: {}),
|
|
171
|
+
yield: {
|
|
172
|
+
calls: judgments.reduce((n, j) => n + (j.calls ?? 0), 0),
|
|
173
|
+
questions: judgments.reduce((n, j) => n + (j.questions ?? 0), 0),
|
|
174
|
+
costUsd: judgments.reduce((n, j) => n + (j.usage?.costUsd ?? 0), 0),
|
|
175
|
+
cacheHits: judgments.reduce((n, j) => n + (j.cacheHits ?? 0), 0),
|
|
176
|
+
cacheRequests: judgments.reduce(
|
|
177
|
+
(n, j) => n + (j.cacheRequests ?? 0),
|
|
178
|
+
0,
|
|
179
|
+
),
|
|
180
|
+
elapsedMs: performance.now() - started,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
ok: !refusal,
|
|
185
|
+
status: timeout?.aborted
|
|
186
|
+
? "budget_exceeded"
|
|
187
|
+
: refusal
|
|
188
|
+
? "refused"
|
|
189
|
+
: collection
|
|
190
|
+
? "collect_budget_reached"
|
|
191
|
+
: "completed",
|
|
192
|
+
envelope,
|
|
193
|
+
judgments,
|
|
194
|
+
findings,
|
|
195
|
+
...(collection ? { collection } : {}),
|
|
196
|
+
...(unjudged ? { unjudged } : {}),
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
const client = deps.client;
|
|
200
|
+
if (!client) return finish(NOT_CONFIGURED);
|
|
201
|
+
const sessionRefusal = deps.runtime.session.refusal();
|
|
202
|
+
if (sessionRefusal) return finish(sessionRefusal);
|
|
203
|
+
try {
|
|
204
|
+
const comparison = await resolveBase(
|
|
205
|
+
deps.exec,
|
|
206
|
+
input.cwd,
|
|
207
|
+
input.base,
|
|
208
|
+
signal,
|
|
209
|
+
);
|
|
210
|
+
if (!comparison.ok) return finish(comparison.error);
|
|
211
|
+
const base = comparison.base;
|
|
212
|
+
const analysis = await createAnalysisContext();
|
|
213
|
+
const [collected, inventory] = await Promise.all([
|
|
214
|
+
collectUnits(
|
|
215
|
+
deps.exec,
|
|
216
|
+
{ cwd: input.cwd, base, signal },
|
|
217
|
+
analysis.parser,
|
|
218
|
+
),
|
|
219
|
+
collectDocsInventory(deps.exec, input.cwd, signal),
|
|
220
|
+
]);
|
|
221
|
+
if (!collected.ok) return finish(collected.error);
|
|
222
|
+
if (!inventory.ok) return finish(inventory.error);
|
|
223
|
+
for (const limit of collected.limits)
|
|
224
|
+
limitations.push({
|
|
225
|
+
fact: `${limit.file} : ${limit.kind}`,
|
|
226
|
+
next: "Read the complete change before concluding.",
|
|
227
|
+
});
|
|
228
|
+
if (!collected.units.length) {
|
|
229
|
+
emptyBase = base;
|
|
230
|
+
return finish();
|
|
231
|
+
}
|
|
232
|
+
const collectDeadline = performance.now() + DOCS_COLLECT_BUDGET_MS;
|
|
233
|
+
const candidates = await collectDocsCandidates(
|
|
234
|
+
inventory.files,
|
|
235
|
+
collected.units,
|
|
236
|
+
{
|
|
237
|
+
known: inventory.tracked,
|
|
238
|
+
read: inventory.read,
|
|
239
|
+
declarations: docsDeclarationSearch(deps.exec, inventory, signal),
|
|
240
|
+
changedFiles: collected.files,
|
|
241
|
+
lexical: analysis,
|
|
242
|
+
shouldStop: () =>
|
|
243
|
+
performance.now() >= collectDeadline || !!signal?.aborted,
|
|
244
|
+
},
|
|
245
|
+
);
|
|
246
|
+
for (const limit of inventory.limits)
|
|
247
|
+
limitations.push({
|
|
248
|
+
fact: `${limit.path} : ${limit.reason}`,
|
|
249
|
+
next: "Read the omitted file; its documentation or imports are unchecked.",
|
|
250
|
+
});
|
|
251
|
+
for (const limit of candidates.limits) {
|
|
252
|
+
if (limit.kind === "collection_budget") {
|
|
253
|
+
const changedParts = collected.units.map((unit) =>
|
|
254
|
+
unit.file.split("/"),
|
|
255
|
+
);
|
|
256
|
+
const sections = (limit.sections ?? [])
|
|
257
|
+
.map((section) => {
|
|
258
|
+
const parts = (section.split(" § ")[0] ?? "").split("/");
|
|
259
|
+
const proximity = changedParts.reduce((best, changed) => {
|
|
260
|
+
let common = 0;
|
|
261
|
+
while (
|
|
262
|
+
common < parts.length &&
|
|
263
|
+
common < changed.length &&
|
|
264
|
+
parts[common] === changed[common]
|
|
265
|
+
)
|
|
266
|
+
common++;
|
|
267
|
+
return Math.max(best, common);
|
|
268
|
+
}, 0);
|
|
269
|
+
return { section, proximity };
|
|
270
|
+
})
|
|
271
|
+
.sort((a, b) => b.proximity - a.proximity);
|
|
272
|
+
collection = {
|
|
273
|
+
kind: limit.kind,
|
|
274
|
+
count: sections.length,
|
|
275
|
+
sections: sections
|
|
276
|
+
.slice(0, DOCS_MAX_SECTIONS)
|
|
277
|
+
.map((item) => item.section),
|
|
278
|
+
truncated: sections.length > DOCS_MAX_SECTIONS,
|
|
279
|
+
};
|
|
280
|
+
} else
|
|
281
|
+
limitations.push({
|
|
282
|
+
fact: `${limit.path} : ${limit.reason}`,
|
|
283
|
+
next: "Read the unresolved dependency; closure follows static imports only.",
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
for (const omitted of candidates.omitted)
|
|
287
|
+
unchecked.push(
|
|
288
|
+
`${omitted.path} § ${omitted.heading} (DOCS_MAX_SECTIONS)`,
|
|
289
|
+
);
|
|
290
|
+
await Promise.all(
|
|
291
|
+
candidates.candidates.map(async (candidate) => {
|
|
292
|
+
const label = `${candidate.path} § ${candidate.heading}`;
|
|
293
|
+
if (
|
|
294
|
+
candidate.units.some(
|
|
295
|
+
(unit) => unit.before === null && unit.after === null,
|
|
296
|
+
)
|
|
297
|
+
) {
|
|
298
|
+
unchecked.push(`${label} (changed source unavailable)`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (budget?.kind === "session") {
|
|
302
|
+
unchecked.push(`${label} (${budget.message})`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const prepared = prepareDocsCheck(candidate);
|
|
306
|
+
if (
|
|
307
|
+
JSON.stringify(prepared.state).length > STATE_MAX_CHARS ||
|
|
308
|
+
candidate.sentences.length + 1 > CHOICE_MAX_OPTIONS
|
|
309
|
+
) {
|
|
310
|
+
unchecked.push(`${label} (state or pointer too large)`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (signal?.aborted) {
|
|
314
|
+
unchecked.push(`${label} (budget exceeded or canceled)`);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const judgment = await client.judge(
|
|
318
|
+
prepared.state,
|
|
319
|
+
prepared.questions,
|
|
320
|
+
{
|
|
321
|
+
signal,
|
|
322
|
+
beforeRequest(questionCount) {
|
|
323
|
+
if (budget?.kind === "session")
|
|
324
|
+
return { ok: false, error: budget.message };
|
|
325
|
+
if (signal?.aborted)
|
|
326
|
+
return {
|
|
327
|
+
ok: false,
|
|
328
|
+
error: "Budget exceeded or call canceled.",
|
|
329
|
+
};
|
|
330
|
+
if (input.maxCalls !== undefined && sent >= input.maxCalls) {
|
|
331
|
+
budget = {
|
|
332
|
+
kind: "max_calls",
|
|
333
|
+
message: `max_calls=${input.maxCalls} reached`,
|
|
334
|
+
};
|
|
335
|
+
return { ok: false, error: budget.message };
|
|
336
|
+
}
|
|
337
|
+
const admitted = deps.runtime.session.admit(questionCount);
|
|
338
|
+
if (!admitted.ok)
|
|
339
|
+
budget = { kind: "session", message: admitted.error };
|
|
340
|
+
else sent++;
|
|
341
|
+
return admitted;
|
|
342
|
+
},
|
|
343
|
+
onUsage: (usage) => deps.runtime.session.recordUsage(usage),
|
|
344
|
+
},
|
|
345
|
+
);
|
|
346
|
+
judgments.push(judgment);
|
|
347
|
+
if (!judgment.ok) {
|
|
348
|
+
unchecked.push(`${label} (${judgment.error})`);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (
|
|
352
|
+
!judgment.answers.status ||
|
|
353
|
+
!judgment.answers.sentence ||
|
|
354
|
+
Object.values(judgment.answers).some(
|
|
355
|
+
(answer) => answer.type === "unjudged",
|
|
356
|
+
)
|
|
357
|
+
) {
|
|
358
|
+
unchecked.push(`${label} (unjudged question)`);
|
|
359
|
+
const finding = readDocsJudgment(candidate, judgment.answers);
|
|
360
|
+
if (finding)
|
|
361
|
+
findings.push({
|
|
362
|
+
...finding,
|
|
363
|
+
band: "unsure",
|
|
364
|
+
reason: "unjudged question: read the section",
|
|
365
|
+
});
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const finding = readDocsJudgment(candidate, judgment.answers);
|
|
369
|
+
if (finding) {
|
|
370
|
+
const incomplete = candidate.units.some((unit) =>
|
|
371
|
+
collected.limits.some((limit) => limit.file === unit.file),
|
|
372
|
+
);
|
|
373
|
+
findings.push(
|
|
374
|
+
incomplete
|
|
375
|
+
? {
|
|
376
|
+
...finding,
|
|
377
|
+
band: "unsure",
|
|
378
|
+
reason: "incomplete change evidence: read the omitted pieces",
|
|
379
|
+
}
|
|
380
|
+
: finding,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
385
|
+
findings.sort(
|
|
386
|
+
(a, b) =>
|
|
387
|
+
a.section.path.localeCompare(b.section.path) ||
|
|
388
|
+
a.section.start - b.section.start,
|
|
389
|
+
);
|
|
390
|
+
unchecked.sort();
|
|
391
|
+
return finish();
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (signal?.aborted)
|
|
394
|
+
return finish(
|
|
395
|
+
timeout?.aborted ? "Docs budget exceeded." : "Docs check canceled.",
|
|
396
|
+
);
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|