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,210 @@
|
|
|
1
|
+
import { collectFiles } from "../adapters/files.ts";
|
|
2
|
+
import { collectUnits } from "../adapters/git.ts";
|
|
3
|
+
import { resolveBase } from "../adapters/git-base.ts";
|
|
4
|
+
import {
|
|
5
|
+
CHOICE_MAX_OPTIONS,
|
|
6
|
+
STATE_MAX_CHARS,
|
|
7
|
+
TIMEOUT_MS,
|
|
8
|
+
} from "../constants.ts";
|
|
9
|
+
import {
|
|
10
|
+
type AnswerInput,
|
|
11
|
+
type BudgetRefusal,
|
|
12
|
+
buildEnvelope,
|
|
13
|
+
type Envelope,
|
|
14
|
+
type Limitation,
|
|
15
|
+
} from "../core/output.ts";
|
|
16
|
+
import type { Judgment } from "../jev/types.ts";
|
|
17
|
+
import {
|
|
18
|
+
prepareSpecCheck,
|
|
19
|
+
readSpecJudgment,
|
|
20
|
+
type SpecFinding,
|
|
21
|
+
} from "../presets/spec.ts";
|
|
22
|
+
import type { ToolDependencies } from "../runtime.ts";
|
|
23
|
+
import { NOT_CONFIGURED } from "../texts/configuration.ts";
|
|
24
|
+
|
|
25
|
+
export interface SpecCheckResult {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
envelope: Envelope;
|
|
28
|
+
judgments: Judgment[];
|
|
29
|
+
findings: SpecFinding[];
|
|
30
|
+
}
|
|
31
|
+
export async function runSpecCheck(
|
|
32
|
+
deps: ToolDependencies,
|
|
33
|
+
input: {
|
|
34
|
+
cwd: string;
|
|
35
|
+
base?: string;
|
|
36
|
+
specPath?: string;
|
|
37
|
+
maxCalls?: number;
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
},
|
|
40
|
+
): Promise<SpecCheckResult> {
|
|
41
|
+
const started = performance.now();
|
|
42
|
+
const judgments: Judgment[] = [];
|
|
43
|
+
const findings: SpecFinding[] = [];
|
|
44
|
+
const limitations: Limitation[] = [];
|
|
45
|
+
const unchecked: string[] = [];
|
|
46
|
+
let budget: BudgetRefusal | undefined;
|
|
47
|
+
let sent = 0;
|
|
48
|
+
let incomplete = false;
|
|
49
|
+
let emptyBase: string | undefined;
|
|
50
|
+
const finish = (refusal?: string): SpecCheckResult => {
|
|
51
|
+
const answers: AnswerInput[] = findings.map((finding) => ({
|
|
52
|
+
label:
|
|
53
|
+
finding.kind === "requirement"
|
|
54
|
+
? `${input.specPath}:${finding.requirement?.start}-${finding.requirement?.end} ${finding.label} — requirement violated`
|
|
55
|
+
: `${finding.unit?.file}:${finding.unit?.afterRange?.start ?? finding.unit?.beforeRange?.start}-${finding.unit?.afterRange?.end ?? finding.unit?.beforeRange?.end} ${finding.label} — external behavior absent from the specification`,
|
|
56
|
+
value: {
|
|
57
|
+
head: finding.kind === "requirement" ? "violates" : finding.label,
|
|
58
|
+
p: finding.probability,
|
|
59
|
+
},
|
|
60
|
+
band: incomplete ? "unsure" : "verdict",
|
|
61
|
+
...(incomplete
|
|
62
|
+
? { reason: "incomplete change evidence: read the omitted pieces" }
|
|
63
|
+
: {}),
|
|
64
|
+
}));
|
|
65
|
+
const envelope = buildEnvelope({
|
|
66
|
+
answers,
|
|
67
|
+
limitations:
|
|
68
|
+
emptyBase !== undefined && !refusal
|
|
69
|
+
? [
|
|
70
|
+
...limitations,
|
|
71
|
+
{
|
|
72
|
+
fact: `no changed units against ${emptyBase}`,
|
|
73
|
+
next: "nothing judged; pass base= or check the working directory",
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
: limitations,
|
|
77
|
+
unchecked,
|
|
78
|
+
refusal,
|
|
79
|
+
budget,
|
|
80
|
+
...(!refusal &&
|
|
81
|
+
!budget &&
|
|
82
|
+
!unchecked.length &&
|
|
83
|
+
!findings.length &&
|
|
84
|
+
emptyBase === undefined
|
|
85
|
+
? {
|
|
86
|
+
lines: [
|
|
87
|
+
{
|
|
88
|
+
type: "list" as const,
|
|
89
|
+
title: "spec: no violations or drift reported",
|
|
90
|
+
items: [],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
}
|
|
94
|
+
: {}),
|
|
95
|
+
yield: {
|
|
96
|
+
calls: judgments.reduce((n, j) => n + (j.calls ?? 0), 0),
|
|
97
|
+
questions: judgments.reduce((n, j) => n + (j.questions ?? 0), 0),
|
|
98
|
+
costUsd: judgments.reduce((n, j) => n + (j.usage?.costUsd ?? 0), 0),
|
|
99
|
+
cacheHits: judgments.reduce((n, j) => n + (j.cacheHits ?? 0), 0),
|
|
100
|
+
cacheRequests: judgments.reduce(
|
|
101
|
+
(n, j) => n + (j.cacheRequests ?? 0),
|
|
102
|
+
0,
|
|
103
|
+
),
|
|
104
|
+
elapsedMs: performance.now() - started,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
return { ok: !refusal, envelope, judgments, findings };
|
|
108
|
+
};
|
|
109
|
+
if (!input.specPath)
|
|
110
|
+
return finish("spec_path required: no specification to check against.");
|
|
111
|
+
if (!deps.client) return finish(NOT_CONFIGURED);
|
|
112
|
+
const comparison = await resolveBase(
|
|
113
|
+
deps.exec,
|
|
114
|
+
input.cwd,
|
|
115
|
+
input.base,
|
|
116
|
+
input.signal,
|
|
117
|
+
);
|
|
118
|
+
if (!comparison.ok) return finish(comparison.error);
|
|
119
|
+
const base = comparison.base;
|
|
120
|
+
const root = await deps.exec("git", ["rev-parse", "--show-toplevel"], {
|
|
121
|
+
cwd: input.cwd,
|
|
122
|
+
timeout: TIMEOUT_MS,
|
|
123
|
+
signal: input.signal,
|
|
124
|
+
});
|
|
125
|
+
if (root.code || root.killed) return finish("Repository root not found.");
|
|
126
|
+
const cwd = root.stdout.trim();
|
|
127
|
+
const [collected, specification] = await Promise.all([
|
|
128
|
+
collectUnits(deps.exec, { cwd, base, signal: input.signal }),
|
|
129
|
+
collectFiles(cwd, [input.specPath], input.signal, { exec: deps.exec }),
|
|
130
|
+
]);
|
|
131
|
+
if (!collected.ok) return finish(collected.error);
|
|
132
|
+
if (!specification.ok) return finish(specification.error);
|
|
133
|
+
const text = Object.values(specification.files)[0];
|
|
134
|
+
if (!text) return finish("The specification is empty: nothing to check.");
|
|
135
|
+
for (const limit of collected.limits)
|
|
136
|
+
limitations.push({
|
|
137
|
+
fact: `${limit.file} : ${limit.kind}`,
|
|
138
|
+
next: "Read the complete change before concluding.",
|
|
139
|
+
});
|
|
140
|
+
const units = collected.units.filter((unit) => {
|
|
141
|
+
if (unit.before !== null || unit.after !== null) return true;
|
|
142
|
+
unchecked.push(`${unit.file} ${unit.name} (changed source unavailable)`);
|
|
143
|
+
return false;
|
|
144
|
+
});
|
|
145
|
+
const prepared = prepareSpecCheck(text, units);
|
|
146
|
+
if (!prepared.requirements.length)
|
|
147
|
+
return finish(
|
|
148
|
+
"No ### REQ-… requirements in the specification: nothing to check.",
|
|
149
|
+
);
|
|
150
|
+
if (!collected.units.length) {
|
|
151
|
+
emptyBase = base;
|
|
152
|
+
return finish();
|
|
153
|
+
}
|
|
154
|
+
if (unchecked.length) {
|
|
155
|
+
unchecked.push(
|
|
156
|
+
...prepared.requirements.map(
|
|
157
|
+
(requirement) => `${requirement.label} (changed source unavailable)`,
|
|
158
|
+
),
|
|
159
|
+
"drift (changed source unavailable)",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (prepared.tableWarning)
|
|
163
|
+
limitations.push({
|
|
164
|
+
fact: "The specification contains a Markdown table.",
|
|
165
|
+
next: "Read the table requirements: their interpretation is uncalibrated.",
|
|
166
|
+
});
|
|
167
|
+
incomplete = collected.limits.length > 0;
|
|
168
|
+
if (
|
|
169
|
+
units.length + 1 > CHOICE_MAX_OPTIONS ||
|
|
170
|
+
JSON.stringify(prepared.state).length > STATE_MAX_CHARS
|
|
171
|
+
)
|
|
172
|
+
return finish(
|
|
173
|
+
`State or spec pointer exceeds the limits; compare against a closer base (STATE_MAX_CHARS=${STATE_MAX_CHARS}).`,
|
|
174
|
+
);
|
|
175
|
+
if (!units.length) return finish();
|
|
176
|
+
const judgment = await deps.client.judge(prepared.state, prepared.questions, {
|
|
177
|
+
signal: input.signal,
|
|
178
|
+
beforeRequest(questionCount) {
|
|
179
|
+
if (input.maxCalls !== undefined && sent >= input.maxCalls) {
|
|
180
|
+
budget = {
|
|
181
|
+
kind: "max_calls",
|
|
182
|
+
message: `max_calls=${input.maxCalls} reached`,
|
|
183
|
+
};
|
|
184
|
+
return { ok: false, error: budget.message };
|
|
185
|
+
}
|
|
186
|
+
const admitted = deps.runtime.session.admit(questionCount);
|
|
187
|
+
if (!admitted.ok) budget = { kind: "session", message: admitted.error };
|
|
188
|
+
else sent++;
|
|
189
|
+
return admitted;
|
|
190
|
+
},
|
|
191
|
+
onUsage: (usage) => deps.runtime.session.recordUsage(usage),
|
|
192
|
+
});
|
|
193
|
+
judgments.push(judgment);
|
|
194
|
+
if (!judgment.ok) {
|
|
195
|
+
unchecked.push(
|
|
196
|
+
...prepared.requirements.map((req) => `${req.label} (${judgment.error})`),
|
|
197
|
+
`drift (${judgment.error})`,
|
|
198
|
+
);
|
|
199
|
+
return finish();
|
|
200
|
+
}
|
|
201
|
+
for (const requirement of prepared.requirements) {
|
|
202
|
+
const answer = judgment.answers[requirement.id];
|
|
203
|
+
if (!answer || answer.type === "unjudged")
|
|
204
|
+
unchecked.push(requirement.label);
|
|
205
|
+
}
|
|
206
|
+
const drift = judgment.answers.drift;
|
|
207
|
+
if (!drift || drift.type === "unjudged") unchecked.push("drift");
|
|
208
|
+
findings.push(...readSpecJudgment(prepared, judgment.answers));
|
|
209
|
+
return finish();
|
|
210
|
+
}
|