jev-agent-tools 0.1.4 → 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.
- package/CHANGELOG.md +106 -1
- package/CONTRIBUTING.md +43 -0
- package/README.md +58 -17
- package/SECURITY.md +43 -0
- package/dist/adapters/analysis-context.js +75 -0
- package/dist/adapters/ask-files.js +198 -0
- package/dist/adapters/ask-proof.js +200 -0
- package/dist/adapters/ask-syntax.js +385 -0
- package/dist/adapters/canonical-path.js +17 -0
- package/dist/adapters/command.js +234 -0
- package/dist/adapters/docs.js +192 -0
- package/dist/adapters/evidence-context.js +119 -0
- package/dist/adapters/exec.js +207 -0
- package/dist/adapters/files.js +418 -0
- package/dist/adapters/find.js +150 -0
- package/dist/adapters/git-base.js +32 -0
- package/dist/adapters/git-inventory.js +71 -0
- package/dist/adapters/git.js +483 -0
- package/dist/adapters/locate-file.js +197 -0
- package/dist/adapters/output-lines.js +46 -0
- package/dist/adapters/private-storage.js +106 -0
- package/dist/adapters/risk-callers.js +429 -0
- package/dist/adapters/runner-version.js +78 -0
- package/dist/adapters/shell.js +92 -0
- package/dist/adapters/syntax.js +187 -0
- package/dist/adapters/test-inventory.js +139 -0
- package/dist/adapters/usage.js +20 -0
- package/dist/adapters/utf8.js +47 -0
- package/dist/configuration.js +267 -0
- package/dist/constants.js +140 -0
- package/dist/core/ask-closure.js +282 -0
- package/dist/core/ask-proof.js +1 -0
- package/dist/core/ask-references.js +278 -0
- package/dist/core/asks.js +507 -0
- package/dist/core/batches.js +65 -0
- package/dist/core/command-output.js +224 -0
- package/dist/core/diff.js +178 -0
- package/dist/core/docs.js +302 -0
- package/dist/core/find.js +108 -0
- package/dist/core/git.js +1 -0
- package/dist/core/imports.js +550 -0
- package/dist/core/integrity.js +45 -0
- package/dist/core/lexical.js +132 -0
- package/dist/core/locate.js +169 -0
- package/dist/core/output.js +137 -0
- package/dist/core/pointer.js +29 -0
- package/dist/core/result-report.js +302 -0
- package/dist/core/risk-callers.js +851 -0
- package/dist/core/runner-version.js +45 -0
- package/dist/core/secret-path.js +34 -0
- package/dist/core/sections.js +230 -0
- package/dist/core/state.js +51 -0
- package/dist/core/syntax.js +1 -0
- package/dist/core/test-commands.js +334 -0
- package/dist/core/test-coverage.js +74 -0
- package/dist/core/test-discovery.js +1382 -0
- package/dist/core/test-evidence.js +527 -0
- package/dist/core/test-state.js +81 -0
- package/dist/core/truncate.js +12 -0
- package/dist/core/units.js +349 -0
- package/dist/describe.js +23 -0
- package/dist/guide.js +33 -0
- package/dist/host.js +24 -0
- package/dist/jev/client.js +456 -0
- package/dist/jev/pool.js +54 -0
- package/dist/jev/types.js +1 -0
- package/dist/mcp/main.js +124 -0
- package/dist/mcp/protocol.js +210 -0
- package/dist/mcp/tools.js +129 -0
- package/dist/presets/docs.js +62 -0
- package/dist/presets/risk.js +179 -0
- package/dist/presets/spec.js +81 -0
- package/dist/presets/witnesses.js +249 -0
- package/dist/render.js +114 -0
- package/dist/report-schema.js +1356 -0
- package/dist/result-types.js +1 -0
- package/dist/result.js +3 -0
- package/dist/runtime.js +1 -0
- package/dist/session.js +147 -0
- package/dist/texts/ask-files.js +3 -0
- package/dist/texts/ask.js +4 -0
- package/dist/texts/check-diff.js +20 -0
- package/dist/texts/configuration.js +1 -0
- package/dist/texts/find.js +19 -0
- package/dist/texts/guide.js +3 -0
- package/dist/texts/instructions.js +72 -0
- package/dist/texts/locate.js +15 -0
- package/dist/texts/select-tests.js +4 -0
- package/dist/tools/ask-files.js +450 -0
- package/dist/tools/ask-schema.js +70 -0
- package/dist/tools/ask.js +1147 -0
- package/dist/tools/check-diff.js +594 -0
- package/dist/tools/docs-check.js +408 -0
- package/dist/tools/find.js +682 -0
- package/dist/tools/locate.js +602 -0
- package/dist/tools/review-report.js +230 -0
- package/dist/tools/select-tests.js +821 -0
- package/dist/tools/spec-check.js +263 -0
- package/docs/adr/0001-strict-typescript-pure-core-offline-tests.md +31 -0
- package/docs/adr/0002-one-http-protocol-across-hosts.md +17 -0
- package/docs/adr/0003-explicit-scope-conservative-automation.md +19 -0
- package/docs/adr/0004-compiled-typed-intents.md +19 -0
- package/docs/adr/0005-evidence-construction-before-judgment.md +19 -0
- package/docs/adr/0006-visible-uncertainty-constrained-controls.md +21 -0
- package/docs/adr/0007-bounded-evidence-visible-limits.md +21 -0
- package/docs/adr/0008-static-test-discovery-conservative-plans.md +19 -0
- package/docs/adr/0009-session-cache-requested-model-identity.md +17 -0
- package/docs/adr/0010-mcp-server-thin-host.md +23 -0
- package/docs/agent-instructions.md +120 -0
- package/docs/design.md +16 -4
- package/docs/mcp.md +233 -0
- package/docs/tools/jev_ask.md +8 -5
- package/docs/tools/jev_ask_files.md +2 -1
- package/docs/tools/jev_check_diff.md +4 -1
- package/docs/tools/jev_find_files.md +2 -1
- package/docs/tools/jev_locate_in_file.md +5 -0
- package/docs/tools/jev_select_tests.md +4 -1
- package/package.json +19 -4
- package/rules/jev-ask.md +22 -1
- package/server.json +57 -0
- package/src/adapters/ask-files.ts +11 -3
- package/src/adapters/ask-proof.ts +69 -11
- package/src/adapters/canonical-path.ts +18 -0
- package/src/adapters/command.ts +102 -36
- package/src/adapters/docs.ts +33 -14
- package/src/adapters/evidence-context.ts +169 -0
- package/src/adapters/exec.ts +226 -0
- package/src/adapters/files.ts +146 -16
- package/src/adapters/find.ts +37 -7
- package/src/adapters/git-base.ts +7 -1
- package/src/adapters/git.ts +61 -8
- package/src/adapters/locate-file.ts +51 -9
- package/src/adapters/private-storage.ts +155 -0
- package/src/adapters/risk-callers.ts +7 -2
- package/src/adapters/shell.ts +113 -0
- package/src/adapters/test-inventory.ts +12 -4
- package/src/configuration.ts +55 -14
- package/src/constants.ts +37 -5
- package/src/core/ask-references.ts +262 -146
- package/src/core/asks.ts +79 -7
- package/src/core/command-output.ts +17 -1
- package/src/core/import-boundaries.ts +8 -3
- package/src/core/locate.ts +8 -5
- package/src/core/output.ts +34 -0
- package/src/core/result-report.ts +410 -0
- package/src/core/secret-path.ts +37 -0
- package/src/core/state.ts +8 -1
- package/src/core/units.ts +3 -2
- package/src/host.ts +11 -0
- package/src/index.ts +3 -0
- package/src/jev/client.ts +66 -16
- package/src/jev/types.ts +24 -3
- package/src/mcp/main.ts +135 -0
- package/src/mcp/protocol.ts +332 -0
- package/src/mcp/tools.ts +179 -0
- package/src/render.ts +109 -0
- package/src/report-schema.ts +1380 -0
- package/src/result-types.ts +234 -0
- package/src/result.ts +4 -1
- package/src/runtime.ts +6 -0
- package/src/session.ts +59 -0
- package/src/setup.ts +13 -5
- package/src/texts/ask-files.ts +4 -1
- package/src/texts/ask.ts +8 -1
- package/src/texts/check-diff.ts +7 -4
- package/src/texts/find.ts +8 -2
- package/src/texts/guide.ts +8 -16
- package/src/texts/instructions.ts +98 -0
- package/src/texts/locate.ts +8 -2
- package/src/texts/run-end.ts +2 -2
- package/src/texts/select-tests.ts +4 -1
- package/src/tools/ask-files.ts +311 -18
- package/src/tools/ask.ts +722 -95
- package/src/tools/check-diff.ts +337 -31
- package/src/tools/docs-check.ts +241 -38
- package/src/tools/find.ts +389 -29
- package/src/tools/locate.ts +387 -25
- package/src/tools/review-report.ts +308 -0
- package/src/tools/select-tests.ts +484 -23
- package/src/tools/spec-check.ts +194 -19
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { createAnalysisContext } from "../adapters/analysis-context.js";
|
|
2
|
+
import { collectDocsInventory, docsDeclarationSearch, } from "../adapters/docs.js";
|
|
3
|
+
import { resolveEvidenceContext, withEvidenceContext, } from "../adapters/evidence-context.js";
|
|
4
|
+
import { collectUnits } from "../adapters/git.js";
|
|
5
|
+
import { resolveBase } from "../adapters/git-base.js";
|
|
6
|
+
import { shareGitInventory } from "../adapters/git-inventory.js";
|
|
7
|
+
import { CHOICE_MAX_OPTIONS, DOCS_COLLECT_BUDGET_MS, DOCS_DISPLAY_MAX_SECTIONS, DOCS_MAX_SECTIONS, STATE_MAX_CHARS, TIMEOUT_MS, } from "../constants.js";
|
|
8
|
+
import { collectDocsCandidates } from "../core/docs.js";
|
|
9
|
+
import { buildEnvelope, } from "../core/output.js";
|
|
10
|
+
import { known, } from "../core/result-report.js";
|
|
11
|
+
import { prepareDocsCheck, readDocsJudgment, } from "../presets/docs.js";
|
|
12
|
+
import { NOT_CONFIGURED } from "../texts/configuration.js";
|
|
13
|
+
import { ReviewReport, reportMetrics } from "./review-report.js";
|
|
14
|
+
/** Shared judgment seam: no guide delivery, rendering, or session answer recording. */
|
|
15
|
+
export async function runDocsCheck(deps, input) {
|
|
16
|
+
const started = performance.now();
|
|
17
|
+
const report = new ReviewReport();
|
|
18
|
+
const admission = input.evidenceContext
|
|
19
|
+
? undefined
|
|
20
|
+
: await resolveEvidenceContext(input.cwd, undefined, {
|
|
21
|
+
exec: deps.exec,
|
|
22
|
+
signal: input.signal,
|
|
23
|
+
origin: deps.evidenceOrigin,
|
|
24
|
+
});
|
|
25
|
+
const evidenceContext = input.evidenceContext ?? admission?.context;
|
|
26
|
+
if (!evidenceContext)
|
|
27
|
+
throw new Error("Evidence context was not established");
|
|
28
|
+
evidenceContext.requestedBase = input.base ?? "HEAD";
|
|
29
|
+
deps = { ...deps, exec: shareGitInventory(deps.exec) };
|
|
30
|
+
const timeout = input.budgetMs === undefined
|
|
31
|
+
? undefined
|
|
32
|
+
: AbortSignal.timeout(Math.max(0, Math.ceil(input.budgetMs)));
|
|
33
|
+
const signals = [input.signal, timeout].filter((value) => value !== undefined);
|
|
34
|
+
const signal = signals.length ? AbortSignal.any(signals) : undefined;
|
|
35
|
+
const judgments = [];
|
|
36
|
+
const findings = [];
|
|
37
|
+
const unchecked = [];
|
|
38
|
+
let unjudged;
|
|
39
|
+
let collection;
|
|
40
|
+
const limitations = [
|
|
41
|
+
{
|
|
42
|
+
fact: "Measured D22 limit: 2/45 documentation obligations found across 180 partial got/zod commits.",
|
|
43
|
+
next: "Also review missing documentation: this check evaluates existing sentences, not documentation completeness.",
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
let budget;
|
|
47
|
+
let sent = 0;
|
|
48
|
+
let emptyBase;
|
|
49
|
+
const finish = (refusal, cause = "internal_error") => {
|
|
50
|
+
if (unchecked.length > 5) {
|
|
51
|
+
unjudged = {
|
|
52
|
+
count: unchecked.length,
|
|
53
|
+
sections: unchecked.slice(0, DOCS_MAX_SECTIONS),
|
|
54
|
+
truncated: unchecked.length > DOCS_MAX_SECTIONS,
|
|
55
|
+
};
|
|
56
|
+
limitations.push({
|
|
57
|
+
fact: `${unchecked.length} matching sections unjudged.`,
|
|
58
|
+
next: "Narrow the diff with base=… or rerun on the relevant documentation.",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const answers = findings.map((finding) => ({
|
|
62
|
+
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(", ")}`,
|
|
63
|
+
value: { head: "now_false", p: finding.probability },
|
|
64
|
+
band: finding.band,
|
|
65
|
+
reason: finding.reason,
|
|
66
|
+
}));
|
|
67
|
+
const envelope = buildEnvelope({
|
|
68
|
+
answers,
|
|
69
|
+
limitations: emptyBase !== undefined && !refusal
|
|
70
|
+
? [
|
|
71
|
+
...limitations,
|
|
72
|
+
{
|
|
73
|
+
fact: `no changed units against ${emptyBase}`,
|
|
74
|
+
next: "nothing judged; pass base= or check the working directory",
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
: limitations,
|
|
78
|
+
...(collection || unjudged
|
|
79
|
+
? {
|
|
80
|
+
lines: [
|
|
81
|
+
...(collection
|
|
82
|
+
? [
|
|
83
|
+
{
|
|
84
|
+
type: "collection",
|
|
85
|
+
title: "anchored documentation unexplored; directly cited sections were checked",
|
|
86
|
+
count: collection.count,
|
|
87
|
+
items: collection.sections.slice(0, DOCS_DISPLAY_MAX_SECTIONS),
|
|
88
|
+
next: "Read these sections; narrow the diff with base=… to target their dependency.",
|
|
89
|
+
},
|
|
90
|
+
]
|
|
91
|
+
: []),
|
|
92
|
+
...(unjudged
|
|
93
|
+
? [
|
|
94
|
+
{
|
|
95
|
+
type: "collection",
|
|
96
|
+
title: "matching documentation unjudged",
|
|
97
|
+
count: unjudged.count,
|
|
98
|
+
items: unjudged.sections.slice(0, DOCS_DISPLAY_MAX_SECTIONS),
|
|
99
|
+
next: "Read these sections or rerun on the relevant documentation.",
|
|
100
|
+
},
|
|
101
|
+
]
|
|
102
|
+
: []),
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
: {}),
|
|
106
|
+
unchecked: unjudged ? [] : unchecked,
|
|
107
|
+
budget,
|
|
108
|
+
refusal,
|
|
109
|
+
...(!refusal &&
|
|
110
|
+
!budget &&
|
|
111
|
+
!collection &&
|
|
112
|
+
!unchecked.length &&
|
|
113
|
+
!findings.length &&
|
|
114
|
+
emptyBase === undefined &&
|
|
115
|
+
report.items.size > 0 &&
|
|
116
|
+
[...report.items.values()].every((item) => item.treatment === "judged")
|
|
117
|
+
? {
|
|
118
|
+
lines: [
|
|
119
|
+
{
|
|
120
|
+
type: "list",
|
|
121
|
+
title: "docs: no stale sentences reported",
|
|
122
|
+
items: [],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
}
|
|
126
|
+
: {}),
|
|
127
|
+
yield: {
|
|
128
|
+
calls: judgments.reduce((n, j) => n + (j.calls ?? 0), 0),
|
|
129
|
+
questions: judgments.reduce((n, j) => n + (j.questions ?? 0), 0),
|
|
130
|
+
costUsd: judgments.length && judgments.every((j) => j.usage !== undefined)
|
|
131
|
+
? judgments.reduce((n, j) => n + (j.usage?.costUsd ?? 0), 0)
|
|
132
|
+
: undefined,
|
|
133
|
+
cacheHits: judgments.reduce((n, j) => n + (j.cacheHits ?? 0), 0),
|
|
134
|
+
cacheRequests: judgments.reduce((n, j) => n + (j.cacheRequests ?? 0), 0),
|
|
135
|
+
elapsedMs: performance.now() - started,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
report.diagnose("collection_omitted", "Existing documentation sentences only; missing documentation is not exhaustively detected (measured 2/45 obligations across 180 partial commits).", undefined, [], false);
|
|
139
|
+
if (emptyBase !== undefined)
|
|
140
|
+
report.diagnose("no_changed_units", `No changed units against ${emptyBase}`, emptyBase, [], false);
|
|
141
|
+
if (!refusal && emptyBase === undefined && !report.items.size)
|
|
142
|
+
report.diagnose("collection_empty", "No matching documentation sections were collected; this does not establish documentation completeness.", undefined, [], false);
|
|
143
|
+
if (refusal) {
|
|
144
|
+
report.refusal =
|
|
145
|
+
cause === "invalid_base" ||
|
|
146
|
+
cause === "invalid_root" ||
|
|
147
|
+
cause === "git_failure";
|
|
148
|
+
report.diagnose(cause, refusal);
|
|
149
|
+
}
|
|
150
|
+
const result = report.build("jev_check_diff", evidenceContext, reportMetrics(envelope));
|
|
151
|
+
return {
|
|
152
|
+
ok: !refusal,
|
|
153
|
+
status: timeout?.aborted
|
|
154
|
+
? "budget_exceeded"
|
|
155
|
+
: refusal
|
|
156
|
+
? "refused"
|
|
157
|
+
: collection
|
|
158
|
+
? "collect_budget_reached"
|
|
159
|
+
: "completed",
|
|
160
|
+
envelope,
|
|
161
|
+
judgments,
|
|
162
|
+
findings,
|
|
163
|
+
result,
|
|
164
|
+
...(collection ? { collection } : {}),
|
|
165
|
+
...(unjudged ? { unjudged } : {}),
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
const client = deps.client;
|
|
169
|
+
if (!client)
|
|
170
|
+
return finish(NOT_CONFIGURED, "not_configured");
|
|
171
|
+
const sessionRefusal = deps.runtime.session.refusal();
|
|
172
|
+
if (sessionRefusal)
|
|
173
|
+
return finish(sessionRefusal, "session_budget");
|
|
174
|
+
try {
|
|
175
|
+
const comparison = await resolveBase(deps.exec, input.cwd, input.base, signal);
|
|
176
|
+
if (!comparison.ok)
|
|
177
|
+
return finish(comparison.error, comparison.cause ?? "invalid_base");
|
|
178
|
+
const base = comparison.base;
|
|
179
|
+
evidenceContext.resolvedBase = base;
|
|
180
|
+
const analysis = await createAnalysisContext();
|
|
181
|
+
const [collected, inventory] = await Promise.all([
|
|
182
|
+
collectUnits(deps.exec, { cwd: input.cwd, base, signal }, analysis.parser),
|
|
183
|
+
collectDocsInventory(deps.exec, input.cwd, signal),
|
|
184
|
+
]);
|
|
185
|
+
if (!collected.ok)
|
|
186
|
+
return finish(collected.error, collected.cause ?? "git_failure");
|
|
187
|
+
if (!inventory.ok)
|
|
188
|
+
return finish(inventory.error, inventory.cause ?? "file_unavailable");
|
|
189
|
+
const repositoryRoot = await deps.exec("git", ["rev-parse", "--show-toplevel"], { cwd: input.cwd, timeout: TIMEOUT_MS, signal });
|
|
190
|
+
if (!repositoryRoot.code &&
|
|
191
|
+
!repositoryRoot.killed &&
|
|
192
|
+
evidenceContext.effectiveRoot)
|
|
193
|
+
evidenceContext.effectiveRoot.path = repositoryRoot.stdout.trim();
|
|
194
|
+
for (const limit of collected.limits) {
|
|
195
|
+
report.diagnose(limit.kind === "secret_pattern"
|
|
196
|
+
? "secret_pattern"
|
|
197
|
+
: "collection_omitted", limit.kind, limit.file);
|
|
198
|
+
limitations.push({
|
|
199
|
+
fact: `${limit.file} : ${limit.kind}`,
|
|
200
|
+
next: "Read the complete change before concluding.",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (!collected.units.length) {
|
|
204
|
+
emptyBase = base;
|
|
205
|
+
report.inventories.push({
|
|
206
|
+
id: "changed-units",
|
|
207
|
+
kind: "units",
|
|
208
|
+
rules: ["Changed source units against resolved base"],
|
|
209
|
+
restrictions: [],
|
|
210
|
+
discovered: known(0),
|
|
211
|
+
considered: known(0),
|
|
212
|
+
scopeRestricted: false,
|
|
213
|
+
criteria: [],
|
|
214
|
+
});
|
|
215
|
+
return finish();
|
|
216
|
+
}
|
|
217
|
+
const collectDeadline = performance.now() + DOCS_COLLECT_BUDGET_MS;
|
|
218
|
+
const candidates = await collectDocsCandidates(inventory.files, collected.units, {
|
|
219
|
+
known: inventory.tracked,
|
|
220
|
+
read: inventory.read,
|
|
221
|
+
declarations: docsDeclarationSearch(deps.exec, inventory, signal),
|
|
222
|
+
changedFiles: collected.files,
|
|
223
|
+
lexical: analysis,
|
|
224
|
+
shouldStop: () => performance.now() >= collectDeadline || !!signal?.aborted,
|
|
225
|
+
});
|
|
226
|
+
report.inventories.push({
|
|
227
|
+
id: "docs-sections",
|
|
228
|
+
kind: "sections",
|
|
229
|
+
rules: [
|
|
230
|
+
"Tracked Markdown sections referring to changed declarations or static import closure",
|
|
231
|
+
`At most ${DOCS_MAX_SECTIONS} admitted sections`,
|
|
232
|
+
],
|
|
233
|
+
restrictions: [],
|
|
234
|
+
discovered: known(candidates.candidates.length + candidates.omitted.length),
|
|
235
|
+
considered: known(candidates.candidates.length),
|
|
236
|
+
scopeRestricted: false,
|
|
237
|
+
criteria: [],
|
|
238
|
+
});
|
|
239
|
+
for (const candidate of candidates.candidates)
|
|
240
|
+
report.expect(`docs:${candidate.path}:${candidate.start}`, `${candidate.path} § ${candidate.heading}`, "section");
|
|
241
|
+
for (const candidate of candidates.omitted) {
|
|
242
|
+
const id = `docs:${candidate.path}:${candidate.start}`;
|
|
243
|
+
report.expect(id, `${candidate.path} § ${candidate.heading}`, "section");
|
|
244
|
+
report.diagnose("collection_omitted", "Section exceeds DOCS_MAX_SECTIONS", candidate.path, [id]);
|
|
245
|
+
}
|
|
246
|
+
for (const limit of candidates.limits) {
|
|
247
|
+
if (limit.kind === "collection_budget") {
|
|
248
|
+
report.missingWork = true;
|
|
249
|
+
report.diagnose("collection_omitted", limit.reason, limit.path, [], true, [...(limit.sections ?? [])]);
|
|
250
|
+
}
|
|
251
|
+
else
|
|
252
|
+
report.diagnose("dynamic_dependency", limit.reason, limit.path, [], false);
|
|
253
|
+
}
|
|
254
|
+
for (const limit of inventory.limits)
|
|
255
|
+
report.diagnose(limit.cause === "secret_pattern"
|
|
256
|
+
? "secret_pattern"
|
|
257
|
+
: "collection_omitted", limit.reason, limit.path);
|
|
258
|
+
for (const limit of inventory.limits)
|
|
259
|
+
limitations.push({
|
|
260
|
+
fact: `${limit.path} : ${limit.reason}`,
|
|
261
|
+
next: "Read the omitted file; its documentation or imports are unchecked.",
|
|
262
|
+
});
|
|
263
|
+
for (const limit of candidates.limits) {
|
|
264
|
+
if (limit.kind === "collection_budget") {
|
|
265
|
+
const changedParts = collected.units.map((unit) => unit.file.split("/"));
|
|
266
|
+
const sections = (limit.sections ?? [])
|
|
267
|
+
.map((section) => {
|
|
268
|
+
const parts = (section.split(" § ")[0] ?? "").split("/");
|
|
269
|
+
const proximity = changedParts.reduce((best, changed) => {
|
|
270
|
+
let common = 0;
|
|
271
|
+
while (common < parts.length &&
|
|
272
|
+
common < changed.length &&
|
|
273
|
+
parts[common] === changed[common])
|
|
274
|
+
common++;
|
|
275
|
+
return Math.max(best, common);
|
|
276
|
+
}, 0);
|
|
277
|
+
return { section, proximity };
|
|
278
|
+
})
|
|
279
|
+
.sort((a, b) => b.proximity - a.proximity);
|
|
280
|
+
collection = {
|
|
281
|
+
kind: limit.kind,
|
|
282
|
+
count: sections.length,
|
|
283
|
+
sections: sections
|
|
284
|
+
.slice(0, DOCS_MAX_SECTIONS)
|
|
285
|
+
.map((item) => item.section),
|
|
286
|
+
truncated: sections.length > DOCS_MAX_SECTIONS,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
else
|
|
290
|
+
limitations.push({
|
|
291
|
+
fact: `${limit.path} : ${limit.reason}`,
|
|
292
|
+
next: "Read the unresolved dependency; closure follows static imports only.",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
for (const omitted of candidates.omitted)
|
|
296
|
+
unchecked.push(`${omitted.path} § ${omitted.heading} (DOCS_MAX_SECTIONS)`);
|
|
297
|
+
await Promise.all(candidates.candidates.map(async (candidate) => {
|
|
298
|
+
const label = `${candidate.path} § ${candidate.heading}`;
|
|
299
|
+
const reportId = `docs:${candidate.path}:${candidate.start}`;
|
|
300
|
+
if (candidate.units.some((unit) => unit.before === null && unit.after === null)) {
|
|
301
|
+
unchecked.push(`${label} (changed source unavailable)`);
|
|
302
|
+
report.diagnose("binary_or_non_utf8", "Changed source unavailable", label, [reportId]);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (budget?.kind === "session") {
|
|
306
|
+
unchecked.push(`${label} (${budget.message})`);
|
|
307
|
+
report.diagnose("session_budget", budget.message, label, [reportId]);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const prepared = prepareDocsCheck(candidate);
|
|
311
|
+
const state = withEvidenceContext(prepared.state, evidenceContext);
|
|
312
|
+
if (JSON.stringify(state).length > STATE_MAX_CHARS ||
|
|
313
|
+
candidate.sentences.length + 1 > CHOICE_MAX_OPTIONS) {
|
|
314
|
+
unchecked.push(`${label} (state or pointer too large)`);
|
|
315
|
+
report.diagnose("evidence_too_large", "Section state or sentence pointer exceeds limits", label, [reportId]);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (signal?.aborted) {
|
|
319
|
+
unchecked.push(`${label} (budget exceeded or canceled)`);
|
|
320
|
+
report.diagnose("cancelled", "Collection budget exceeded or check canceled", label, [reportId]);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const judgment = await client.judge(state, prepared.questions, {
|
|
324
|
+
signal,
|
|
325
|
+
groups: [["status", "sentence"]],
|
|
326
|
+
...deps.runtime.session.requestGate(),
|
|
327
|
+
admissionCause: () => budget?.kind === "session" ? "session_budget" : "call_budget",
|
|
328
|
+
beforeRequest(questionCount) {
|
|
329
|
+
if (budget?.kind === "session")
|
|
330
|
+
return { ok: false, error: budget.message };
|
|
331
|
+
if (signal?.aborted)
|
|
332
|
+
return {
|
|
333
|
+
ok: false,
|
|
334
|
+
error: "Budget exceeded or call canceled.",
|
|
335
|
+
};
|
|
336
|
+
if (input.maxCalls !== undefined && sent >= input.maxCalls) {
|
|
337
|
+
budget = {
|
|
338
|
+
kind: "max_calls",
|
|
339
|
+
message: `max_calls=${input.maxCalls} reached`,
|
|
340
|
+
};
|
|
341
|
+
return { ok: false, error: budget.message };
|
|
342
|
+
}
|
|
343
|
+
const admitted = deps.runtime.session.admit(questionCount);
|
|
344
|
+
if (!admitted.ok)
|
|
345
|
+
budget = { kind: "session", message: admitted.error };
|
|
346
|
+
else
|
|
347
|
+
sent++;
|
|
348
|
+
return admitted;
|
|
349
|
+
},
|
|
350
|
+
onUsage: (usage) => deps.runtime.session.recordUsage(usage),
|
|
351
|
+
});
|
|
352
|
+
judgments.push(judgment);
|
|
353
|
+
if (!judgment.ok)
|
|
354
|
+
report.failure(judgment, [reportId]);
|
|
355
|
+
else {
|
|
356
|
+
const status = judgment.answers.status;
|
|
357
|
+
const pointer = judgment.answers.sentence;
|
|
358
|
+
const finding = readDocsJudgment(candidate, judgment.answers);
|
|
359
|
+
const item = report.items.get(reportId);
|
|
360
|
+
if (finding && item)
|
|
361
|
+
item.label = `${label} — ${finding.sentence ? JSON.stringify(finding.sentence.text) : "sentence not identified"} — after ${finding.units.map((unit) => `${unit.name} (${unit.file})`).join(", ")}`;
|
|
362
|
+
report.answer(reportId, status, { band: finding?.band ?? "verdict", reason: finding?.reason }, [
|
|
363
|
+
pointer ?? {
|
|
364
|
+
type: "unjudged",
|
|
365
|
+
reason: "Required sentence pointer missing",
|
|
366
|
+
},
|
|
367
|
+
]);
|
|
368
|
+
}
|
|
369
|
+
if (!judgment.ok) {
|
|
370
|
+
unchecked.push(`${label} (${judgment.error})`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (!judgment.answers.status ||
|
|
374
|
+
!judgment.answers.sentence ||
|
|
375
|
+
Object.values(judgment.answers).some((answer) => answer.type === "unjudged")) {
|
|
376
|
+
unchecked.push(`${label} (unjudged question)`);
|
|
377
|
+
const finding = readDocsJudgment(candidate, judgment.answers);
|
|
378
|
+
if (finding)
|
|
379
|
+
findings.push({
|
|
380
|
+
...finding,
|
|
381
|
+
band: "unsure",
|
|
382
|
+
reason: "unjudged question: read the section",
|
|
383
|
+
});
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const finding = readDocsJudgment(candidate, judgment.answers);
|
|
387
|
+
if (finding) {
|
|
388
|
+
const incomplete = candidate.units.some((unit) => collected.limits.some((limit) => limit.file === unit.file));
|
|
389
|
+
findings.push(incomplete
|
|
390
|
+
? {
|
|
391
|
+
...finding,
|
|
392
|
+
band: "unsure",
|
|
393
|
+
reason: "incomplete change evidence: read the omitted pieces",
|
|
394
|
+
}
|
|
395
|
+
: finding);
|
|
396
|
+
}
|
|
397
|
+
}));
|
|
398
|
+
findings.sort((a, b) => a.section.path.localeCompare(b.section.path) ||
|
|
399
|
+
a.section.start - b.section.start);
|
|
400
|
+
unchecked.sort();
|
|
401
|
+
return finish();
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
if (signal?.aborted)
|
|
405
|
+
return finish(timeout?.aborted ? "Docs budget exceeded." : "Docs check canceled.", "cancelled");
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
}
|