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/src/tools/spec-check.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type EvidenceContext,
|
|
3
|
+
resolveEvidenceContext,
|
|
4
|
+
withEvidenceContext,
|
|
5
|
+
} from "../adapters/evidence-context.ts";
|
|
1
6
|
import { collectFiles } from "../adapters/files.ts";
|
|
2
7
|
import { collectUnits } from "../adapters/git.ts";
|
|
3
8
|
import { resolveBase } from "../adapters/git-base.ts";
|
|
@@ -13,6 +18,11 @@ import {
|
|
|
13
18
|
type Envelope,
|
|
14
19
|
type Limitation,
|
|
15
20
|
} from "../core/output.ts";
|
|
21
|
+
import {
|
|
22
|
+
type Cause,
|
|
23
|
+
known,
|
|
24
|
+
type ResultReportV1,
|
|
25
|
+
} from "../core/result-report.ts";
|
|
16
26
|
import type { Judgment } from "../jev/types.ts";
|
|
17
27
|
import {
|
|
18
28
|
prepareSpecCheck,
|
|
@@ -21,12 +31,14 @@ import {
|
|
|
21
31
|
} from "../presets/spec.ts";
|
|
22
32
|
import type { ToolDependencies } from "../runtime.ts";
|
|
23
33
|
import { NOT_CONFIGURED } from "../texts/configuration.ts";
|
|
34
|
+
import { ReviewReport, reportMetrics } from "./review-report.ts";
|
|
24
35
|
|
|
25
36
|
export interface SpecCheckResult {
|
|
26
37
|
ok: boolean;
|
|
27
38
|
envelope: Envelope;
|
|
28
39
|
judgments: Judgment[];
|
|
29
40
|
findings: SpecFinding[];
|
|
41
|
+
result: ResultReportV1;
|
|
30
42
|
}
|
|
31
43
|
export async function runSpecCheck(
|
|
32
44
|
deps: ToolDependencies,
|
|
@@ -36,9 +48,21 @@ export async function runSpecCheck(
|
|
|
36
48
|
specPath?: string;
|
|
37
49
|
maxCalls?: number;
|
|
38
50
|
signal?: AbortSignal;
|
|
51
|
+
evidenceContext?: EvidenceContext;
|
|
39
52
|
},
|
|
40
53
|
): Promise<SpecCheckResult> {
|
|
41
54
|
const started = performance.now();
|
|
55
|
+
const report = new ReviewReport();
|
|
56
|
+
const admission = input.evidenceContext
|
|
57
|
+
? undefined
|
|
58
|
+
: await resolveEvidenceContext(input.cwd, undefined, {
|
|
59
|
+
exec: deps.exec,
|
|
60
|
+
signal: input.signal,
|
|
61
|
+
origin: deps.evidenceOrigin,
|
|
62
|
+
});
|
|
63
|
+
const evidenceContext = input.evidenceContext ?? admission?.context;
|
|
64
|
+
if (!evidenceContext) throw new Error("Evidence context was not established");
|
|
65
|
+
evidenceContext.requestedBase = input.base ?? "HEAD";
|
|
42
66
|
const judgments: Judgment[] = [];
|
|
43
67
|
const findings: SpecFinding[] = [];
|
|
44
68
|
const limitations: Limitation[] = [];
|
|
@@ -47,7 +71,10 @@ export async function runSpecCheck(
|
|
|
47
71
|
let sent = 0;
|
|
48
72
|
let incomplete = false;
|
|
49
73
|
let emptyBase: string | undefined;
|
|
50
|
-
const finish = (
|
|
74
|
+
const finish = (
|
|
75
|
+
refusal?: string,
|
|
76
|
+
cause: Cause = "internal_error",
|
|
77
|
+
): SpecCheckResult => {
|
|
51
78
|
const answers: AnswerInput[] = findings.map((finding) => ({
|
|
52
79
|
label:
|
|
53
80
|
finding.kind === "requirement"
|
|
@@ -81,7 +108,9 @@ export async function runSpecCheck(
|
|
|
81
108
|
!budget &&
|
|
82
109
|
!unchecked.length &&
|
|
83
110
|
!findings.length &&
|
|
84
|
-
emptyBase === undefined
|
|
111
|
+
emptyBase === undefined &&
|
|
112
|
+
report.items.size > 0 &&
|
|
113
|
+
[...report.items.values()].every((item) => item.treatment === "judged")
|
|
85
114
|
? {
|
|
86
115
|
lines: [
|
|
87
116
|
{
|
|
@@ -95,7 +124,10 @@ export async function runSpecCheck(
|
|
|
95
124
|
yield: {
|
|
96
125
|
calls: judgments.reduce((n, j) => n + (j.calls ?? 0), 0),
|
|
97
126
|
questions: judgments.reduce((n, j) => n + (j.questions ?? 0), 0),
|
|
98
|
-
costUsd:
|
|
127
|
+
costUsd:
|
|
128
|
+
judgments.length && judgments.every((j) => j.usage !== undefined)
|
|
129
|
+
? judgments.reduce((n, j) => n + (j.usage?.costUsd ?? 0), 0)
|
|
130
|
+
: undefined,
|
|
99
131
|
cacheHits: judgments.reduce((n, j) => n + (j.cacheHits ?? 0), 0),
|
|
100
132
|
cacheRequests: judgments.reduce(
|
|
101
133
|
(n, j) => n + (j.cacheRequests ?? 0),
|
|
@@ -104,48 +136,134 @@ export async function runSpecCheck(
|
|
|
104
136
|
elapsedMs: performance.now() - started,
|
|
105
137
|
},
|
|
106
138
|
});
|
|
107
|
-
|
|
139
|
+
if (emptyBase !== undefined)
|
|
140
|
+
report.diagnose(
|
|
141
|
+
"no_changed_units",
|
|
142
|
+
`No changed units against ${emptyBase}`,
|
|
143
|
+
emptyBase,
|
|
144
|
+
[],
|
|
145
|
+
false,
|
|
146
|
+
);
|
|
147
|
+
if (refusal) {
|
|
148
|
+
report.refusal = cause !== "not_configured";
|
|
149
|
+
report.diagnose(cause, refusal, input.specPath);
|
|
150
|
+
}
|
|
151
|
+
const result = report.build(
|
|
152
|
+
"jev_check_diff",
|
|
153
|
+
evidenceContext,
|
|
154
|
+
reportMetrics(envelope),
|
|
155
|
+
);
|
|
156
|
+
return { ok: !refusal, envelope, judgments, findings, result };
|
|
108
157
|
};
|
|
109
158
|
if (!input.specPath)
|
|
110
|
-
return finish(
|
|
111
|
-
|
|
159
|
+
return finish(
|
|
160
|
+
"spec_path required: no specification to check against.",
|
|
161
|
+
"missing_required",
|
|
162
|
+
);
|
|
163
|
+
if (!deps.client) return finish(NOT_CONFIGURED, "not_configured");
|
|
112
164
|
const comparison = await resolveBase(
|
|
113
165
|
deps.exec,
|
|
114
166
|
input.cwd,
|
|
115
167
|
input.base,
|
|
116
168
|
input.signal,
|
|
117
169
|
);
|
|
118
|
-
if (!comparison.ok)
|
|
170
|
+
if (!comparison.ok)
|
|
171
|
+
return finish(comparison.error, comparison.cause ?? "invalid_base");
|
|
119
172
|
const base = comparison.base;
|
|
173
|
+
evidenceContext.resolvedBase = base;
|
|
120
174
|
const root = await deps.exec("git", ["rev-parse", "--show-toplevel"], {
|
|
121
175
|
cwd: input.cwd,
|
|
122
176
|
timeout: TIMEOUT_MS,
|
|
123
177
|
signal: input.signal,
|
|
124
178
|
});
|
|
125
|
-
if (root.code || root.killed)
|
|
179
|
+
if (root.code || root.killed)
|
|
180
|
+
return finish(
|
|
181
|
+
"Repository root not found.",
|
|
182
|
+
root.killed ? "cancelled" : "git_failure",
|
|
183
|
+
);
|
|
126
184
|
const cwd = root.stdout.trim();
|
|
185
|
+
if (evidenceContext.effectiveRoot) evidenceContext.effectiveRoot.path = cwd;
|
|
127
186
|
const [collected, specification] = await Promise.all([
|
|
128
187
|
collectUnits(deps.exec, { cwd, base, signal: input.signal }),
|
|
129
188
|
collectFiles(cwd, [input.specPath], input.signal, { exec: deps.exec }),
|
|
130
189
|
]);
|
|
131
|
-
if (!collected.ok)
|
|
132
|
-
|
|
190
|
+
if (!collected.ok)
|
|
191
|
+
return finish(collected.error, collected.cause ?? "git_failure");
|
|
192
|
+
if (!specification.ok)
|
|
193
|
+
return finish(
|
|
194
|
+
specification.error,
|
|
195
|
+
specification.cause ?? "file_unavailable",
|
|
196
|
+
);
|
|
133
197
|
const text = Object.values(specification.files)[0];
|
|
134
|
-
if (!text)
|
|
198
|
+
if (!text)
|
|
199
|
+
return finish(
|
|
200
|
+
"The specification is empty: nothing to check.",
|
|
201
|
+
"empty_required",
|
|
202
|
+
);
|
|
135
203
|
for (const limit of collected.limits)
|
|
136
204
|
limitations.push({
|
|
137
205
|
fact: `${limit.file} : ${limit.kind}`,
|
|
138
206
|
next: "Read the complete change before concluding.",
|
|
139
207
|
});
|
|
140
|
-
const
|
|
141
|
-
|
|
208
|
+
const unavailableUnits = collected.units.filter(
|
|
209
|
+
(unit) => unit.before === null && unit.after === null,
|
|
210
|
+
);
|
|
211
|
+
const units = collected.units.filter(
|
|
212
|
+
(unit) => unit.before !== null || unit.after !== null,
|
|
213
|
+
);
|
|
214
|
+
for (const unit of unavailableUnits)
|
|
142
215
|
unchecked.push(`${unit.file} ${unit.name} (changed source unavailable)`);
|
|
143
|
-
return false;
|
|
144
|
-
});
|
|
145
216
|
const prepared = prepareSpecCheck(text, units);
|
|
217
|
+
for (const requirement of prepared.requirements)
|
|
218
|
+
report.expect(`spec:${requirement.id}`, requirement.label, "requirement");
|
|
219
|
+
report.expect("spec:drift", "Specification drift pointer", "pointer");
|
|
220
|
+
report.inventories.push({
|
|
221
|
+
id: "spec-requirements",
|
|
222
|
+
kind: "sections",
|
|
223
|
+
rules: ["### REQ- headings in the supplied specification"],
|
|
224
|
+
restrictions: [input.specPath],
|
|
225
|
+
discovered: known(prepared.requirements.length),
|
|
226
|
+
considered: known(prepared.requirements.length),
|
|
227
|
+
scopeRestricted: true,
|
|
228
|
+
criteria: [],
|
|
229
|
+
});
|
|
230
|
+
report.inventories.push({
|
|
231
|
+
id: "changed-units",
|
|
232
|
+
kind: "units",
|
|
233
|
+
rules: ["Changed source units against resolved base"],
|
|
234
|
+
restrictions: [],
|
|
235
|
+
discovered: known(collected.units.length),
|
|
236
|
+
considered: known(units.length),
|
|
237
|
+
scopeRestricted: false,
|
|
238
|
+
criteria: [],
|
|
239
|
+
});
|
|
240
|
+
if (!collected.units.length && prepared.requirements.length)
|
|
241
|
+
report.items.clear();
|
|
242
|
+
const reportIds = [...report.items.keys()];
|
|
243
|
+
for (const unit of unavailableUnits)
|
|
244
|
+
report.diagnose(
|
|
245
|
+
"binary_or_non_utf8",
|
|
246
|
+
"Changed source unavailable",
|
|
247
|
+
unit.file,
|
|
248
|
+
reportIds,
|
|
249
|
+
units.length === 0,
|
|
250
|
+
[`${unit.file} ${unit.name}`],
|
|
251
|
+
);
|
|
252
|
+
for (const limit of collected.limits)
|
|
253
|
+
report.diagnose(
|
|
254
|
+
limit.kind === "secret_pattern" ? "secret_pattern" : "collection_omitted",
|
|
255
|
+
limit.kind,
|
|
256
|
+
limit.file,
|
|
257
|
+
reportIds,
|
|
258
|
+
units.length === 0,
|
|
259
|
+
["unit" in limit ? `${limit.file} ${limit.unit}` : limit.file],
|
|
260
|
+
);
|
|
261
|
+
report.missingWork =
|
|
262
|
+
unavailableUnits.length > 0 || collected.limits.length > 0;
|
|
146
263
|
if (!prepared.requirements.length)
|
|
147
264
|
return finish(
|
|
148
265
|
"No ### REQ-… requirements in the specification: nothing to check.",
|
|
266
|
+
"missing_required",
|
|
149
267
|
);
|
|
150
268
|
if (!collected.units.length) {
|
|
151
269
|
emptyBase = base;
|
|
@@ -159,23 +277,35 @@ export async function runSpecCheck(
|
|
|
159
277
|
"drift (changed source unavailable)",
|
|
160
278
|
);
|
|
161
279
|
}
|
|
162
|
-
if (prepared.tableWarning)
|
|
280
|
+
if (prepared.tableWarning) {
|
|
281
|
+
report.diagnose(
|
|
282
|
+
"unsupported_syntax",
|
|
283
|
+
"Specification Markdown table interpretation is uncalibrated",
|
|
284
|
+
input.specPath,
|
|
285
|
+
[],
|
|
286
|
+
false,
|
|
287
|
+
);
|
|
163
288
|
limitations.push({
|
|
164
289
|
fact: "The specification contains a Markdown table.",
|
|
165
290
|
next: "Read the table requirements: their interpretation is uncalibrated.",
|
|
166
291
|
});
|
|
167
|
-
|
|
292
|
+
}
|
|
293
|
+
incomplete = report.missingWork;
|
|
294
|
+
const state = withEvidenceContext(prepared.state, evidenceContext);
|
|
168
295
|
if (
|
|
169
296
|
units.length + 1 > CHOICE_MAX_OPTIONS ||
|
|
170
|
-
JSON.stringify(
|
|
297
|
+
JSON.stringify(state).length > STATE_MAX_CHARS
|
|
171
298
|
)
|
|
172
299
|
return finish(
|
|
173
300
|
`State or spec pointer exceeds the limits; compare against a closer base (STATE_MAX_CHARS=${STATE_MAX_CHARS}).`,
|
|
301
|
+
"evidence_too_large",
|
|
174
302
|
);
|
|
175
303
|
if (!units.length) return finish();
|
|
176
|
-
const judgment = await deps.client.judge(
|
|
304
|
+
const judgment = await deps.client.judge(state, prepared.questions, {
|
|
177
305
|
signal: input.signal,
|
|
178
306
|
...deps.runtime.session.requestGate(),
|
|
307
|
+
admissionCause: () =>
|
|
308
|
+
budget?.kind === "session" ? "session_budget" : "call_budget",
|
|
179
309
|
beforeRequest(questionCount) {
|
|
180
310
|
if (input.maxCalls !== undefined && sent >= input.maxCalls) {
|
|
181
311
|
budget = {
|
|
@@ -192,6 +322,50 @@ export async function runSpecCheck(
|
|
|
192
322
|
onUsage: (usage) => deps.runtime.session.recordUsage(usage),
|
|
193
323
|
});
|
|
194
324
|
judgments.push(judgment);
|
|
325
|
+
if (!judgment.ok)
|
|
326
|
+
report.failure(
|
|
327
|
+
judgment,
|
|
328
|
+
reportIds,
|
|
329
|
+
budget
|
|
330
|
+
? budget.kind === "session"
|
|
331
|
+
? "session_budget"
|
|
332
|
+
: "call_budget"
|
|
333
|
+
: undefined,
|
|
334
|
+
);
|
|
335
|
+
else {
|
|
336
|
+
for (const requirement of prepared.requirements)
|
|
337
|
+
report.answer(
|
|
338
|
+
`spec:${requirement.id}`,
|
|
339
|
+
judgment.answers[requirement.id],
|
|
340
|
+
{ band: incomplete ? "unsure" : "verdict" },
|
|
341
|
+
);
|
|
342
|
+
const driftAnswer = judgment.answers.drift;
|
|
343
|
+
const selectedUnit =
|
|
344
|
+
driftAnswer?.type === "choice"
|
|
345
|
+
? units.find((unit) => unit.id === driftAnswer.choice)
|
|
346
|
+
: undefined;
|
|
347
|
+
const pointer = report.items.get("spec:drift");
|
|
348
|
+
if (pointer && selectedUnit)
|
|
349
|
+
report.items.set("spec:drift", {
|
|
350
|
+
...pointer,
|
|
351
|
+
label: `${selectedUnit.file} ${selectedUnit.name} specification drift`,
|
|
352
|
+
});
|
|
353
|
+
const selectedProbability =
|
|
354
|
+
driftAnswer?.type === "choice"
|
|
355
|
+
? driftAnswer.probabilities[driftAnswer.choice]
|
|
356
|
+
: undefined;
|
|
357
|
+
report.answer("spec:drift", driftAnswer, {
|
|
358
|
+
band: incomplete ? "unsure" : "verdict",
|
|
359
|
+
...(driftAnswer?.type === "choice" && selectedProbability !== undefined
|
|
360
|
+
? {
|
|
361
|
+
value: {
|
|
362
|
+
head: driftAnswer.choice,
|
|
363
|
+
p: selectedProbability,
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
: {}),
|
|
367
|
+
});
|
|
368
|
+
}
|
|
195
369
|
if (!judgment.ok) {
|
|
196
370
|
unchecked.push(
|
|
197
371
|
...prepared.requirements.map((req) => `${req.label} (${judgment.error})`),
|