jev-agent-tools 0.2.0 → 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 +37 -1
- package/CONTRIBUTING.md +3 -0
- package/README.md +22 -14
- package/SECURITY.md +17 -1
- package/dist/adapters/ask-files.js +11 -2
- package/dist/adapters/ask-proof.js +63 -7
- package/dist/adapters/command.js +82 -29
- package/dist/adapters/docs.js +30 -10
- package/dist/adapters/evidence-context.js +119 -0
- package/dist/adapters/files.js +141 -16
- package/dist/adapters/find.js +34 -6
- package/dist/adapters/git-base.js +7 -1
- package/dist/adapters/git.js +51 -7
- package/dist/adapters/locate-file.js +47 -9
- package/dist/adapters/private-storage.js +14 -6
- package/dist/adapters/risk-callers.js +3 -0
- package/dist/adapters/shell.js +23 -7
- package/dist/adapters/test-inventory.js +10 -2
- package/dist/configuration.js +17 -7
- package/dist/constants.js +26 -5
- package/dist/core/ask-references.js +193 -109
- package/dist/core/asks.js +78 -7
- package/dist/core/locate.js +8 -8
- package/dist/core/output.js +17 -0
- package/dist/core/result-report.js +302 -0
- package/dist/core/secret-path.js +34 -0
- package/dist/core/state.js +8 -1
- package/dist/core/units.js +1 -1
- package/dist/jev/client.js +34 -12
- package/dist/mcp/protocol.js +50 -27
- package/dist/mcp/tools.js +20 -7
- package/dist/render.js +72 -0
- package/dist/report-schema.js +1356 -0
- package/dist/result-types.js +1 -0
- package/dist/texts/ask-files.js +3 -1
- package/dist/texts/ask.js +3 -1
- package/dist/texts/check-diff.js +7 -4
- package/dist/texts/find.js +7 -2
- package/dist/texts/guide.js +3 -16
- package/dist/texts/instructions.js +72 -0
- package/dist/texts/locate.js +7 -2
- package/dist/texts/select-tests.js +3 -1
- package/dist/tools/ask-files.js +248 -15
- package/dist/tools/ask.js +523 -62
- package/dist/tools/check-diff.js +222 -30
- package/dist/tools/docs-check.js +122 -13
- package/dist/tools/find.js +320 -27
- package/dist/tools/locate.js +317 -18
- package/dist/tools/review-report.js +230 -0
- package/dist/tools/select-tests.js +273 -19
- package/dist/tools/spec-check.js +119 -22
- package/docs/adr/0001-strict-typescript-pure-core-offline-tests.md +3 -3
- package/docs/agent-instructions.md +59 -30
- package/docs/design.md +13 -1
- package/docs/mcp.md +8 -6
- 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 +1 -1
- package/rules/jev-ask.md +22 -1
- package/server.json +2 -2
- package/src/adapters/ask-files.ts +11 -3
- package/src/adapters/ask-proof.ts +69 -11
- package/src/adapters/command.ts +96 -33
- package/src/adapters/docs.ts +33 -14
- package/src/adapters/evidence-context.ts +169 -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 +17 -5
- package/src/adapters/risk-callers.ts +3 -0
- package/src/adapters/shell.ts +23 -7
- package/src/adapters/test-inventory.ts +12 -4
- package/src/configuration.ts +16 -2
- package/src/constants.ts +26 -5
- package/src/core/ask-references.ts +262 -146
- package/src/core/asks.ts +79 -7
- 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/index.ts +3 -0
- package/src/jev/client.ts +54 -16
- package/src/jev/types.ts +18 -3
- package/src/mcp/protocol.ts +91 -41
- package/src/mcp/tools.ts +26 -13
- 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/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 +309 -14
- package/src/tools/ask.ts +700 -77
- package/src/tools/check-diff.ts +331 -28
- package/src/tools/docs-check.ts +241 -39
- package/src/tools/find.ts +386 -29
- package/src/tools/locate.ts +384 -19
- package/src/tools/review-report.ts +308 -0
- package/src/tools/select-tests.ts +479 -21
- package/src/tools/spec-check.ts +193 -19
package/dist/tools/docs-check.js
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { createAnalysisContext } from "../adapters/analysis-context.js";
|
|
2
2
|
import { collectDocsInventory, docsDeclarationSearch, } from "../adapters/docs.js";
|
|
3
|
+
import { resolveEvidenceContext, withEvidenceContext, } from "../adapters/evidence-context.js";
|
|
3
4
|
import { collectUnits } from "../adapters/git.js";
|
|
4
5
|
import { resolveBase } from "../adapters/git-base.js";
|
|
5
6
|
import { shareGitInventory } from "../adapters/git-inventory.js";
|
|
6
|
-
import { CHOICE_MAX_OPTIONS, DOCS_COLLECT_BUDGET_MS, DOCS_DISPLAY_MAX_SECTIONS, DOCS_MAX_SECTIONS, STATE_MAX_CHARS, } from "../constants.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";
|
|
7
8
|
import { collectDocsCandidates } from "../core/docs.js";
|
|
8
9
|
import { buildEnvelope, } from "../core/output.js";
|
|
10
|
+
import { known, } from "../core/result-report.js";
|
|
9
11
|
import { prepareDocsCheck, readDocsJudgment, } from "../presets/docs.js";
|
|
10
12
|
import { NOT_CONFIGURED } from "../texts/configuration.js";
|
|
13
|
+
import { ReviewReport, reportMetrics } from "./review-report.js";
|
|
11
14
|
/** Shared judgment seam: no guide delivery, rendering, or session answer recording. */
|
|
12
15
|
export async function runDocsCheck(deps, input) {
|
|
13
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";
|
|
14
29
|
deps = { ...deps, exec: shareGitInventory(deps.exec) };
|
|
15
30
|
const timeout = input.budgetMs === undefined
|
|
16
31
|
? undefined
|
|
@@ -31,7 +46,7 @@ export async function runDocsCheck(deps, input) {
|
|
|
31
46
|
let budget;
|
|
32
47
|
let sent = 0;
|
|
33
48
|
let emptyBase;
|
|
34
|
-
const finish = (refusal) => {
|
|
49
|
+
const finish = (refusal, cause = "internal_error") => {
|
|
35
50
|
if (unchecked.length > 5) {
|
|
36
51
|
unjudged = {
|
|
37
52
|
count: unchecked.length,
|
|
@@ -96,7 +111,9 @@ export async function runDocsCheck(deps, input) {
|
|
|
96
111
|
!collection &&
|
|
97
112
|
!unchecked.length &&
|
|
98
113
|
!findings.length &&
|
|
99
|
-
emptyBase === undefined
|
|
114
|
+
emptyBase === undefined &&
|
|
115
|
+
report.items.size > 0 &&
|
|
116
|
+
[...report.items.values()].every((item) => item.treatment === "judged")
|
|
100
117
|
? {
|
|
101
118
|
lines: [
|
|
102
119
|
{
|
|
@@ -110,12 +127,27 @@ export async function runDocsCheck(deps, input) {
|
|
|
110
127
|
yield: {
|
|
111
128
|
calls: judgments.reduce((n, j) => n + (j.calls ?? 0), 0),
|
|
112
129
|
questions: judgments.reduce((n, j) => n + (j.questions ?? 0), 0),
|
|
113
|
-
costUsd: judgments.
|
|
130
|
+
costUsd: judgments.length && judgments.every((j) => j.usage !== undefined)
|
|
131
|
+
? judgments.reduce((n, j) => n + (j.usage?.costUsd ?? 0), 0)
|
|
132
|
+
: undefined,
|
|
114
133
|
cacheHits: judgments.reduce((n, j) => n + (j.cacheHits ?? 0), 0),
|
|
115
134
|
cacheRequests: judgments.reduce((n, j) => n + (j.cacheRequests ?? 0), 0),
|
|
116
135
|
elapsedMs: performance.now() - started,
|
|
117
136
|
},
|
|
118
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));
|
|
119
151
|
return {
|
|
120
152
|
ok: !refusal,
|
|
121
153
|
status: timeout?.aborted
|
|
@@ -128,37 +160,58 @@ export async function runDocsCheck(deps, input) {
|
|
|
128
160
|
envelope,
|
|
129
161
|
judgments,
|
|
130
162
|
findings,
|
|
163
|
+
result,
|
|
131
164
|
...(collection ? { collection } : {}),
|
|
132
165
|
...(unjudged ? { unjudged } : {}),
|
|
133
166
|
};
|
|
134
167
|
};
|
|
135
168
|
const client = deps.client;
|
|
136
169
|
if (!client)
|
|
137
|
-
return finish(NOT_CONFIGURED);
|
|
170
|
+
return finish(NOT_CONFIGURED, "not_configured");
|
|
138
171
|
const sessionRefusal = deps.runtime.session.refusal();
|
|
139
172
|
if (sessionRefusal)
|
|
140
|
-
return finish(sessionRefusal);
|
|
173
|
+
return finish(sessionRefusal, "session_budget");
|
|
141
174
|
try {
|
|
142
175
|
const comparison = await resolveBase(deps.exec, input.cwd, input.base, signal);
|
|
143
176
|
if (!comparison.ok)
|
|
144
|
-
return finish(comparison.error);
|
|
177
|
+
return finish(comparison.error, comparison.cause ?? "invalid_base");
|
|
145
178
|
const base = comparison.base;
|
|
179
|
+
evidenceContext.resolvedBase = base;
|
|
146
180
|
const analysis = await createAnalysisContext();
|
|
147
181
|
const [collected, inventory] = await Promise.all([
|
|
148
182
|
collectUnits(deps.exec, { cwd: input.cwd, base, signal }, analysis.parser),
|
|
149
183
|
collectDocsInventory(deps.exec, input.cwd, signal),
|
|
150
184
|
]);
|
|
151
185
|
if (!collected.ok)
|
|
152
|
-
return finish(collected.error);
|
|
186
|
+
return finish(collected.error, collected.cause ?? "git_failure");
|
|
153
187
|
if (!inventory.ok)
|
|
154
|
-
return finish(inventory.error);
|
|
155
|
-
|
|
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);
|
|
156
198
|
limitations.push({
|
|
157
199
|
fact: `${limit.file} : ${limit.kind}`,
|
|
158
200
|
next: "Read the complete change before concluding.",
|
|
159
201
|
});
|
|
202
|
+
}
|
|
160
203
|
if (!collected.units.length) {
|
|
161
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
|
+
});
|
|
162
215
|
return finish();
|
|
163
216
|
}
|
|
164
217
|
const collectDeadline = performance.now() + DOCS_COLLECT_BUDGET_MS;
|
|
@@ -170,6 +223,38 @@ export async function runDocsCheck(deps, input) {
|
|
|
170
223
|
lexical: analysis,
|
|
171
224
|
shouldStop: () => performance.now() >= collectDeadline || !!signal?.aborted,
|
|
172
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);
|
|
173
258
|
for (const limit of inventory.limits)
|
|
174
259
|
limitations.push({
|
|
175
260
|
fact: `${limit.path} : ${limit.reason}`,
|
|
@@ -211,27 +296,35 @@ export async function runDocsCheck(deps, input) {
|
|
|
211
296
|
unchecked.push(`${omitted.path} § ${omitted.heading} (DOCS_MAX_SECTIONS)`);
|
|
212
297
|
await Promise.all(candidates.candidates.map(async (candidate) => {
|
|
213
298
|
const label = `${candidate.path} § ${candidate.heading}`;
|
|
299
|
+
const reportId = `docs:${candidate.path}:${candidate.start}`;
|
|
214
300
|
if (candidate.units.some((unit) => unit.before === null && unit.after === null)) {
|
|
215
301
|
unchecked.push(`${label} (changed source unavailable)`);
|
|
302
|
+
report.diagnose("binary_or_non_utf8", "Changed source unavailable", label, [reportId]);
|
|
216
303
|
return;
|
|
217
304
|
}
|
|
218
305
|
if (budget?.kind === "session") {
|
|
219
306
|
unchecked.push(`${label} (${budget.message})`);
|
|
307
|
+
report.diagnose("session_budget", budget.message, label, [reportId]);
|
|
220
308
|
return;
|
|
221
309
|
}
|
|
222
310
|
const prepared = prepareDocsCheck(candidate);
|
|
223
|
-
|
|
311
|
+
const state = withEvidenceContext(prepared.state, evidenceContext);
|
|
312
|
+
if (JSON.stringify(state).length > STATE_MAX_CHARS ||
|
|
224
313
|
candidate.sentences.length + 1 > CHOICE_MAX_OPTIONS) {
|
|
225
314
|
unchecked.push(`${label} (state or pointer too large)`);
|
|
315
|
+
report.diagnose("evidence_too_large", "Section state or sentence pointer exceeds limits", label, [reportId]);
|
|
226
316
|
return;
|
|
227
317
|
}
|
|
228
318
|
if (signal?.aborted) {
|
|
229
319
|
unchecked.push(`${label} (budget exceeded or canceled)`);
|
|
320
|
+
report.diagnose("cancelled", "Collection budget exceeded or check canceled", label, [reportId]);
|
|
230
321
|
return;
|
|
231
322
|
}
|
|
232
|
-
const judgment = await client.judge(
|
|
323
|
+
const judgment = await client.judge(state, prepared.questions, {
|
|
233
324
|
signal,
|
|
325
|
+
groups: [["status", "sentence"]],
|
|
234
326
|
...deps.runtime.session.requestGate(),
|
|
327
|
+
admissionCause: () => budget?.kind === "session" ? "session_budget" : "call_budget",
|
|
235
328
|
beforeRequest(questionCount) {
|
|
236
329
|
if (budget?.kind === "session")
|
|
237
330
|
return { ok: false, error: budget.message };
|
|
@@ -257,6 +350,22 @@ export async function runDocsCheck(deps, input) {
|
|
|
257
350
|
onUsage: (usage) => deps.runtime.session.recordUsage(usage),
|
|
258
351
|
});
|
|
259
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
|
+
}
|
|
260
369
|
if (!judgment.ok) {
|
|
261
370
|
unchecked.push(`${label} (${judgment.error})`);
|
|
262
371
|
return;
|
|
@@ -293,7 +402,7 @@ export async function runDocsCheck(deps, input) {
|
|
|
293
402
|
}
|
|
294
403
|
catch (error) {
|
|
295
404
|
if (signal?.aborted)
|
|
296
|
-
return finish(timeout?.aborted ? "Docs budget exceeded." : "Docs check canceled.");
|
|
405
|
+
return finish(timeout?.aborted ? "Docs budget exceeded." : "Docs check canceled.", "cancelled");
|
|
297
406
|
throw error;
|
|
298
407
|
}
|
|
299
408
|
}
|