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/locate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveEvidenceContext, withEvidenceContext, } from "../adapters/evidence-context.js";
|
|
3
3
|
import { shareGitInventory } from "../adapters/git-inventory.js";
|
|
4
4
|
import { readLocateFile, scanRange } from "../adapters/locate-file.js";
|
|
5
5
|
import { loadSyntaxParser } from "../adapters/syntax.js";
|
|
@@ -8,12 +8,17 @@ import { CHOICE_MAX_OPTIONS, LOCATE_GRAY_MIN, LOCATE_MIN_KB, LOCATE_SECTION_MAX_
|
|
|
8
8
|
import { locateDisplay, outlineEvidence, sectionOutline, sectionState, sectionStateFits, } from "../core/locate.js";
|
|
9
9
|
import { buildEnvelope, } from "../core/output.js";
|
|
10
10
|
import { needsReverse, pointerQuestion, readPointer } from "../core/pointer.js";
|
|
11
|
+
import { answerItemFromInput, buildResultReport, contextFromEvidence, known, unknown, } from "../core/result-report.js";
|
|
11
12
|
import { sectionFile } from "../core/sections.js";
|
|
12
13
|
import { interpolate } from "../describe.js";
|
|
13
|
-
import {
|
|
14
|
+
import { renderResultReport } from "../render.js";
|
|
14
15
|
import { NOT_CONFIGURED } from "../texts/configuration.js";
|
|
15
16
|
import { LOCATE_DESCRIPTION } from "../texts/locate.js";
|
|
16
|
-
const parameters = Type.Object({
|
|
17
|
+
const parameters = Type.Object({
|
|
18
|
+
root: Type.Optional(Type.String()),
|
|
19
|
+
path: Type.String({ minLength: 1 }),
|
|
20
|
+
goal: Type.String({ minLength: 1 }),
|
|
21
|
+
}, { additionalProperties: false });
|
|
17
22
|
export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
18
23
|
const { host, runtime, exec: execute } = dependencies;
|
|
19
24
|
return {
|
|
@@ -30,11 +35,87 @@ export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
|
30
35
|
}),
|
|
31
36
|
async execute(_id, args, signal, _update, ctx) {
|
|
32
37
|
const client = dependencies.client;
|
|
33
|
-
const
|
|
38
|
+
const evidence = await resolveEvidenceContext(ctx.cwd, args.root, {
|
|
39
|
+
exec: execute,
|
|
40
|
+
signal,
|
|
41
|
+
origin: dependencies.evidenceOrigin,
|
|
42
|
+
});
|
|
43
|
+
const evidenceContext = evidence.context;
|
|
44
|
+
const cwd = evidence.ok ? evidence.cwd : evidenceContext.authority.path;
|
|
34
45
|
const started = performance.now();
|
|
35
46
|
const exec = shareGitInventory(execute);
|
|
36
47
|
const results = [];
|
|
37
48
|
const limitations = [];
|
|
49
|
+
const context = contextFromEvidence(evidenceContext);
|
|
50
|
+
const diagnostics = [];
|
|
51
|
+
const actions = [];
|
|
52
|
+
const auxiliary = {
|
|
53
|
+
controls: { fresh: 0, cache: 0, notJudged: 0, static: 0 },
|
|
54
|
+
passages: { fresh: 0, cache: 0, notJudged: 0 },
|
|
55
|
+
};
|
|
56
|
+
let primary;
|
|
57
|
+
let control;
|
|
58
|
+
let requiredControl = false;
|
|
59
|
+
let failureCause = "invalid_response";
|
|
60
|
+
const raw = (answer) => answer.type === "choice"
|
|
61
|
+
? Object.entries(answer.probabilities).map(([label, probability]) => ({
|
|
62
|
+
label,
|
|
63
|
+
value: label,
|
|
64
|
+
probability: known(probability),
|
|
65
|
+
}))
|
|
66
|
+
: [];
|
|
67
|
+
const diagnose = (cause, fact, members = []) => {
|
|
68
|
+
const id = `d${diagnostics.length + 1}`, actionId = `a${actions.length + 1}`;
|
|
69
|
+
const code = cause === "invalid_root"
|
|
70
|
+
? "correct_context"
|
|
71
|
+
: cause === "not_configured"
|
|
72
|
+
? "configure_client"
|
|
73
|
+
: cause === "transport_failure" || cause === "service_unavailable"
|
|
74
|
+
? "recover_service"
|
|
75
|
+
: "inspect_native";
|
|
76
|
+
actions.push({
|
|
77
|
+
id: actionId,
|
|
78
|
+
code,
|
|
79
|
+
target: known(args.path),
|
|
80
|
+
scope: { kind: "call" },
|
|
81
|
+
condition: code === "correct_context"
|
|
82
|
+
? "If the requested root is incorrect"
|
|
83
|
+
: code === "recover_service"
|
|
84
|
+
? "Only after service recovery is observed"
|
|
85
|
+
: "If the range decision remains open",
|
|
86
|
+
instruction: code === "correct_context"
|
|
87
|
+
? "Use the exact registered worktree top-level; no fallback was performed."
|
|
88
|
+
: code === "configure_client"
|
|
89
|
+
? "Configure the Jev client or inspect the file natively."
|
|
90
|
+
: "Read or search the cited file within existing permissions; revisit Jev only with decisive new evidence or materially changed context.",
|
|
91
|
+
repeatUnchanged: false,
|
|
92
|
+
});
|
|
93
|
+
diagnostics.push({
|
|
94
|
+
id,
|
|
95
|
+
cause,
|
|
96
|
+
fact,
|
|
97
|
+
target: known(cause === "invalid_root" ? (args.root ?? cwd) : args.path),
|
|
98
|
+
origin: cause === "invalid_root" || cause === "forbidden_path"
|
|
99
|
+
? "input"
|
|
100
|
+
: cause === "control_failure"
|
|
101
|
+
? "control"
|
|
102
|
+
: cause === "session_budget"
|
|
103
|
+
? "budget"
|
|
104
|
+
: cause === "service_unavailable" ||
|
|
105
|
+
cause === "transport_failure" ||
|
|
106
|
+
cause === "invalid_response" ||
|
|
107
|
+
cause === "provider_context_refusal" ||
|
|
108
|
+
cause === "not_configured"
|
|
109
|
+
? "provider"
|
|
110
|
+
: "collection",
|
|
111
|
+
scope: { kind: "call" },
|
|
112
|
+
effect: "blocking",
|
|
113
|
+
material: true,
|
|
114
|
+
omittedMembers: members,
|
|
115
|
+
memberCount: known(members.length),
|
|
116
|
+
actionIds: [actionId],
|
|
117
|
+
});
|
|
118
|
+
};
|
|
38
119
|
const finish = (input) => {
|
|
39
120
|
const usage = results.reduce((sum, result) => ({
|
|
40
121
|
inputTokens: sum.inputTokens + (result.usage?.inputTokens ?? 0),
|
|
@@ -61,23 +142,159 @@ export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
|
61
142
|
elapsedMs: performance.now() - started,
|
|
62
143
|
},
|
|
63
144
|
});
|
|
145
|
+
if (input.refusal)
|
|
146
|
+
diagnose(failureCause, input.refusal);
|
|
147
|
+
if (input.answers?.[0] && primary?.source)
|
|
148
|
+
actions.push({
|
|
149
|
+
id: `a${actions.length + 1}`,
|
|
150
|
+
code: "inspect_native",
|
|
151
|
+
target: known(input.answers[0].label === "none"
|
|
152
|
+
? args.path
|
|
153
|
+
: input.answers[0].label),
|
|
154
|
+
scope: { kind: "group", groupIds: ["pointer"] },
|
|
155
|
+
condition: "If the range decision remains open",
|
|
156
|
+
instruction: input.answers[0].label === "none"
|
|
157
|
+
? "Search elsewhere for the goal; read any cited alternative range if the result is unsure. Do not retry unchanged evidence."
|
|
158
|
+
: "Read the current primary and alternative ranges; retain uncertainty if no decisive new evidence is available. Do not retry unchanged evidence.",
|
|
159
|
+
repeatUnchanged: false,
|
|
160
|
+
});
|
|
161
|
+
if (!input.refusal &&
|
|
162
|
+
(!primary?.source || (requiredControl && !control?.source)))
|
|
163
|
+
diagnose("invalid_response", "pointer decision lacks established answer provenance or its required control");
|
|
164
|
+
const common = {
|
|
165
|
+
id: "pointer",
|
|
166
|
+
kind: "pointer",
|
|
167
|
+
label: args.path,
|
|
168
|
+
groupId: "pointer",
|
|
169
|
+
evidence: primary
|
|
170
|
+
? [
|
|
171
|
+
{
|
|
172
|
+
target: args.path,
|
|
173
|
+
canonicalPath: evidenceContext.effectiveRoot
|
|
174
|
+
? known(`${evidenceContext.effectiveRoot.path}/${args.path}`)
|
|
175
|
+
: unknown("file admission not established"),
|
|
176
|
+
aliases: [],
|
|
177
|
+
side: "current",
|
|
178
|
+
revision: context.resolvedBase,
|
|
179
|
+
},
|
|
180
|
+
]
|
|
181
|
+
: [],
|
|
182
|
+
diagnosticIds: diagnostics.map((d) => d.id),
|
|
183
|
+
actionIds: actions.map((a) => a.id),
|
|
184
|
+
};
|
|
185
|
+
if (input.answers?.[0])
|
|
186
|
+
common.label = input.answers[0].label;
|
|
187
|
+
const displayed = input.answers?.[0];
|
|
188
|
+
let item = { ...common, treatment: "not_judged", source: "none" };
|
|
189
|
+
if (displayed &&
|
|
190
|
+
primary?.type === "choice" &&
|
|
191
|
+
primary.source &&
|
|
192
|
+
(!requiredControl || (control?.type === "choice" && control.source))) {
|
|
193
|
+
auxiliary.passages[primary.source]--;
|
|
194
|
+
item = answerItemFromInput(common, {
|
|
195
|
+
...displayed,
|
|
196
|
+
answer: primary,
|
|
197
|
+
publicResult: displayed.value?.head ?? primary.choice,
|
|
198
|
+
reason: [...limitations, ...(input.limitations ?? [])]
|
|
199
|
+
.map((limit) => limit.fact)
|
|
200
|
+
.join("; "),
|
|
201
|
+
rawValues: raw(primary),
|
|
202
|
+
reportControls: control?.type === "choice" && control.source
|
|
203
|
+
? [
|
|
204
|
+
{
|
|
205
|
+
id: "pointer-order",
|
|
206
|
+
kind: "order",
|
|
207
|
+
source: control.source,
|
|
208
|
+
outcome: displayed.orderDependent
|
|
209
|
+
? "order-dependent"
|
|
210
|
+
: "consistent",
|
|
211
|
+
rawValues: raw(control),
|
|
212
|
+
},
|
|
213
|
+
]
|
|
214
|
+
: [],
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const report = buildResultReport({
|
|
218
|
+
tool: "jev_locate_in_file",
|
|
219
|
+
context,
|
|
220
|
+
items: [item],
|
|
221
|
+
diagnostics,
|
|
222
|
+
actions,
|
|
223
|
+
metrics: {
|
|
224
|
+
...metrics,
|
|
225
|
+
costUsd: results.every((r) => !r.calls || r.usage !== undefined)
|
|
226
|
+
? usage.costUsd
|
|
227
|
+
: undefined,
|
|
228
|
+
elapsedMs: performance.now() - started,
|
|
229
|
+
},
|
|
230
|
+
auxiliary,
|
|
231
|
+
total: known(1),
|
|
232
|
+
refused: Boolean(input.refusal &&
|
|
233
|
+
(failureCause === "invalid_root" ||
|
|
234
|
+
failureCause === "forbidden_path" ||
|
|
235
|
+
failureCause === "outside_inventory" ||
|
|
236
|
+
failureCause === "group_too_large" ||
|
|
237
|
+
failureCause === "evidence_too_large")),
|
|
238
|
+
});
|
|
64
239
|
runtime.session.record(envelope);
|
|
65
240
|
runtime.guide.deliver(ctx);
|
|
66
241
|
return {
|
|
67
|
-
content: [
|
|
68
|
-
|
|
242
|
+
content: [
|
|
243
|
+
{
|
|
244
|
+
type: "text",
|
|
245
|
+
text: renderResultReport(report, {
|
|
246
|
+
details: {
|
|
247
|
+
lines: [
|
|
248
|
+
...envelope.lines,
|
|
249
|
+
{
|
|
250
|
+
type: "list",
|
|
251
|
+
title: "pointer reservations",
|
|
252
|
+
items: [...limitations, ...(input.limitations ?? [])].map((limit) => limit.fact),
|
|
253
|
+
},
|
|
254
|
+
...(displayed
|
|
255
|
+
? [
|
|
256
|
+
{
|
|
257
|
+
type: "list",
|
|
258
|
+
title: "read ranges",
|
|
259
|
+
items: [
|
|
260
|
+
...(displayed.label === "none"
|
|
261
|
+
? []
|
|
262
|
+
: [displayed.label]),
|
|
263
|
+
...(displayed.candidates ?? [])
|
|
264
|
+
.filter((candidate) => candidate.label !== "none")
|
|
265
|
+
.map((candidate) => `${candidate.label} (${candidate.value.p})`),
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
]
|
|
269
|
+
: []),
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
}),
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
details: { results, evidenceContext, result: report },
|
|
69
276
|
...hostUsage(host.isOmp, results.some((r) => r.usage) ? usage : undefined),
|
|
70
277
|
};
|
|
71
278
|
};
|
|
279
|
+
if (!evidence.ok) {
|
|
280
|
+
failureCause = evidence.cause ?? "invalid_root";
|
|
281
|
+
return finish({ refusal: evidence.error });
|
|
282
|
+
}
|
|
283
|
+
failureCause = "file_unavailable";
|
|
72
284
|
const file = await readLocateFile(cwd, args.path, signal, exec);
|
|
73
|
-
if (!file.ok)
|
|
285
|
+
if (!file.ok) {
|
|
286
|
+
failureCause = file.cause ?? "file_unavailable";
|
|
74
287
|
return finish({ refusal: file.error });
|
|
288
|
+
}
|
|
289
|
+
failureCause = "outside_inventory";
|
|
75
290
|
if (file.bytes < LOCATE_MIN_KB * 1000)
|
|
76
291
|
return finish({
|
|
77
292
|
refusal: `small file (${file.lines} lines): read it directly`,
|
|
78
293
|
});
|
|
79
|
-
if (!client)
|
|
294
|
+
if (!client) {
|
|
295
|
+
failureCause = "not_configured";
|
|
80
296
|
return finish({ refusal: NOT_CONFIGURED });
|
|
297
|
+
}
|
|
81
298
|
let sections;
|
|
82
299
|
if (file.kind === "whole") {
|
|
83
300
|
const parser = await loadParser();
|
|
@@ -115,8 +332,56 @@ export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
|
115
332
|
});
|
|
116
333
|
}
|
|
117
334
|
const sectionCount = sections.length;
|
|
335
|
+
context.inventories.push({
|
|
336
|
+
id: "sections",
|
|
337
|
+
kind: "sections",
|
|
338
|
+
rules: [
|
|
339
|
+
file.kind === "whole"
|
|
340
|
+
? "declarations or heuristic sections from complete file"
|
|
341
|
+
: "bounded window outline",
|
|
342
|
+
],
|
|
343
|
+
restrictions: [
|
|
344
|
+
args.path,
|
|
345
|
+
...sections.map((section) => `${section.id}: ${section.start}-${section.end}`),
|
|
346
|
+
],
|
|
347
|
+
discovered: known(sectionCount),
|
|
348
|
+
considered: known(sectionCount),
|
|
349
|
+
scopeRestricted: true,
|
|
350
|
+
criteria: [],
|
|
351
|
+
});
|
|
352
|
+
if (!sectionCount) {
|
|
353
|
+
diagnose("collection_empty", "no sections discovered; no pointer judgment");
|
|
354
|
+
return finish({});
|
|
355
|
+
}
|
|
356
|
+
failureCause = "group_too_large";
|
|
118
357
|
const judge = async (candidates, outline = false) => {
|
|
119
|
-
|
|
358
|
+
primary = undefined;
|
|
359
|
+
control = undefined;
|
|
360
|
+
requiredControl = false;
|
|
361
|
+
const inventory = context.inventories.find((inventory) => inventory.id === "pointer-candidates");
|
|
362
|
+
const restricted = {
|
|
363
|
+
id: "pointer-candidates",
|
|
364
|
+
kind: "sections",
|
|
365
|
+
rules: ["actual candidates for the latest pointer stage"],
|
|
366
|
+
restrictions: [
|
|
367
|
+
args.path,
|
|
368
|
+
...candidates.map((section) => `${section.id}: ${section.start}-${section.end}`),
|
|
369
|
+
],
|
|
370
|
+
discovered: known(candidates.length),
|
|
371
|
+
considered: known(candidates.length),
|
|
372
|
+
scopeRestricted: true,
|
|
373
|
+
criteria: [],
|
|
374
|
+
};
|
|
375
|
+
if (inventory)
|
|
376
|
+
Object.assign(inventory, restricted);
|
|
377
|
+
else
|
|
378
|
+
context.inventories.push(restricted);
|
|
379
|
+
const state = withEvidenceContext(sectionState(args.path, args.goal, candidates, outline), evidenceContext);
|
|
380
|
+
if (JSON.stringify(state).length > STATE_MAX_CHARS)
|
|
381
|
+
return {
|
|
382
|
+
ok: false,
|
|
383
|
+
error: `required evidence exceeds STATE_MAX_CHARS=${STATE_MAX_CHARS}`,
|
|
384
|
+
};
|
|
120
385
|
const ask = async (reverse) => {
|
|
121
386
|
const result = await client.judge(state, {
|
|
122
387
|
pointer: pointerQuestion({
|
|
@@ -129,12 +394,36 @@ export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
|
129
394
|
signal,
|
|
130
395
|
...runtime.session.requestGate(),
|
|
131
396
|
beforeRequest: (n) => runtime.session.admit(n),
|
|
397
|
+
admissionCause: () => "session_budget",
|
|
132
398
|
onUsage: (usage) => runtime.session.recordUsage(usage),
|
|
133
399
|
});
|
|
134
400
|
results.push(result);
|
|
401
|
+
if (!result.ok) {
|
|
402
|
+
failureCause = result.failureCause ?? "invalid_response";
|
|
403
|
+
if (reverse)
|
|
404
|
+
auxiliary.controls.notJudged++;
|
|
405
|
+
else
|
|
406
|
+
auxiliary.passages.notJudged++;
|
|
407
|
+
}
|
|
135
408
|
if (!result.ok)
|
|
136
409
|
return { ok: false, error: result.error };
|
|
137
410
|
const answer = result.answers.pointer;
|
|
411
|
+
if (reverse)
|
|
412
|
+
control = answer;
|
|
413
|
+
else
|
|
414
|
+
primary = answer;
|
|
415
|
+
if (answer?.type === "unjudged")
|
|
416
|
+
failureCause = answer.cause ?? "invalid_response";
|
|
417
|
+
if (answer?.type === "choice" && answer.source) {
|
|
418
|
+
if (reverse)
|
|
419
|
+
auxiliary.controls[answer.source]++;
|
|
420
|
+
else
|
|
421
|
+
auxiliary.passages[answer.source]++;
|
|
422
|
+
}
|
|
423
|
+
else if (reverse)
|
|
424
|
+
auxiliary.controls.notJudged++;
|
|
425
|
+
else
|
|
426
|
+
auxiliary.passages.notJudged++;
|
|
138
427
|
return answer?.type === "choice"
|
|
139
428
|
? { ok: true, probabilities: answer.probabilities }
|
|
140
429
|
: {
|
|
@@ -149,24 +438,32 @@ export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
|
149
438
|
return first;
|
|
150
439
|
let pointer = readPointer(first.probabilities);
|
|
151
440
|
if (needsReverse(first.probabilities, candidates.length)) {
|
|
441
|
+
requiredControl = true;
|
|
152
442
|
const second = await ask(true);
|
|
153
|
-
if (!second.ok)
|
|
443
|
+
if (!second.ok) {
|
|
444
|
+
if (failureCause === "invalid_response")
|
|
445
|
+
failureCause = "control_failure";
|
|
154
446
|
return second;
|
|
447
|
+
}
|
|
155
448
|
pointer = readPointer(first.probabilities, second.probabilities);
|
|
156
449
|
}
|
|
157
450
|
return { ok: true, ...pointer };
|
|
158
451
|
};
|
|
159
|
-
const
|
|
452
|
+
const emptyState = sectionState(args.path, args.goal, []);
|
|
453
|
+
const contextChars = JSON.stringify(withEvidenceContext(emptyState, evidenceContext))
|
|
454
|
+
.length - JSON.stringify(emptyState).length;
|
|
455
|
+
const evidenceMaxChars = STATE_MAX_CHARS - contextChars;
|
|
456
|
+
const fits = (candidates) => sectionStateFits(args.path, args.goal, candidates, false, evidenceMaxChars);
|
|
160
457
|
const twoStage = file.kind === "outline" || !fits(sections);
|
|
161
458
|
let planUnsure = false;
|
|
162
459
|
if (twoStage) {
|
|
163
|
-
const outline = sectionOutline(args.path, args.goal, sections);
|
|
460
|
+
const outline = sectionOutline(args.path, args.goal, sections, evidenceMaxChars);
|
|
164
461
|
if (outline.length < 2 || outline.length >= CHOICE_MAX_OPTIONS)
|
|
165
462
|
return finish({
|
|
166
463
|
refusal: "No usable multi-section plan fits the budget; use grep or read directly.",
|
|
167
464
|
});
|
|
168
|
-
const evidence = outlineEvidence(args.path, args.goal, outline);
|
|
169
|
-
if (!
|
|
465
|
+
const evidence = outlineEvidence(args.path, args.goal, outline, evidenceMaxChars);
|
|
466
|
+
if (!fits(evidence))
|
|
170
467
|
return finish({
|
|
171
468
|
refusal: "Block plan exceeds the state budget; use grep.",
|
|
172
469
|
});
|
|
@@ -217,8 +514,10 @@ export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
|
217
514
|
}
|
|
218
515
|
if (file.kind === "outline") {
|
|
219
516
|
const selected = await scanRange(cwd, args.path, block, signal, exec);
|
|
220
|
-
if (!selected.ok)
|
|
517
|
+
if (!selected.ok) {
|
|
518
|
+
failureCause = selected.cause ?? "file_unavailable";
|
|
221
519
|
return finish({ refusal: selected.error });
|
|
520
|
+
}
|
|
222
521
|
const lines = selected.text.split("\n");
|
|
223
522
|
sections = sections
|
|
224
523
|
.filter((s) => s.start >= block.start && s.end <= block.end)
|
|
@@ -271,7 +570,7 @@ export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
|
271
570
|
const displayed = locateDisplay(args.path, sections, pointer.ranked, shrunk || planUnsure);
|
|
272
571
|
if (!displayed)
|
|
273
572
|
return finish({ refusal: "Pointer probabilities missing." });
|
|
274
|
-
const { primary, band, candidates } = displayed;
|
|
573
|
+
const { primary: displayedPrimary, band, candidates } = displayed;
|
|
275
574
|
if (band === "unsure" || head.id === "none")
|
|
276
575
|
limitations.push({
|
|
277
576
|
fact: head.id === "none"
|
|
@@ -282,14 +581,14 @@ export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
|
282
581
|
return finish({
|
|
283
582
|
answers: [
|
|
284
583
|
{
|
|
285
|
-
...
|
|
584
|
+
...displayedPrimary,
|
|
286
585
|
band,
|
|
287
586
|
orderDependent: pointer.orderDependent,
|
|
288
587
|
candidates,
|
|
289
588
|
},
|
|
290
589
|
],
|
|
291
590
|
lines: head.id !== "none"
|
|
292
|
-
? [{ type: "list", title: "read", items: [
|
|
591
|
+
? [{ type: "list", title: "read", items: [displayedPrimary.label] }]
|
|
293
592
|
: [],
|
|
294
593
|
limitations: [
|
|
295
594
|
{
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { answerItemFromInput, buildResultReport, contextFromEvidence, known, unknown, } from "../core/result-report.js";
|
|
2
|
+
export function reportMetrics(envelope) {
|
|
3
|
+
const line = envelope.lines.find((line) => line.type === "yield");
|
|
4
|
+
if (line?.type !== "yield")
|
|
5
|
+
throw new Error("Report metrics missing from envelope");
|
|
6
|
+
return line.value;
|
|
7
|
+
}
|
|
8
|
+
export function controlsFor(id, judgment, witnessIds) {
|
|
9
|
+
const batches = judgment.batches?.filter((batch) => batch.questionIds.includes(id));
|
|
10
|
+
const responses = batches?.length
|
|
11
|
+
? batches.map((batch) => batch.answers)
|
|
12
|
+
: [judgment.ok ? judgment.answers : {}];
|
|
13
|
+
return responses.flatMap((answers) => witnessIds.map((witnessId) => answers[witnessId] ?? {
|
|
14
|
+
type: "unjudged",
|
|
15
|
+
reason: "Required witness response unavailable",
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
/** Producer-owned inventory: register before admission, retain negative answers too. */
|
|
19
|
+
export class ReviewReport {
|
|
20
|
+
items = new Map();
|
|
21
|
+
diagnostics = [];
|
|
22
|
+
actions = [];
|
|
23
|
+
inventories = [];
|
|
24
|
+
auxiliary = {
|
|
25
|
+
controls: { fresh: 0, cache: 0, notJudged: 0, static: 0 },
|
|
26
|
+
passages: { fresh: 0, cache: 0, notJudged: 0 },
|
|
27
|
+
};
|
|
28
|
+
refusal = false;
|
|
29
|
+
missingWork = false;
|
|
30
|
+
totalUnknown = false;
|
|
31
|
+
countControls(judgment, ids) {
|
|
32
|
+
const batches = judgment.batches ?? [
|
|
33
|
+
{ answers: judgment.ok ? judgment.answers : {}, questionIds: ids },
|
|
34
|
+
];
|
|
35
|
+
for (const batch of batches)
|
|
36
|
+
for (const id of ids) {
|
|
37
|
+
if (!batch.questionIds.includes(id) && judgment.batches)
|
|
38
|
+
continue;
|
|
39
|
+
const answer = batch.answers[id];
|
|
40
|
+
this.auxiliary.controls[answer && answer.type !== "unjudged" && answer.source
|
|
41
|
+
? answer.source
|
|
42
|
+
: "notJudged"]++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
expect(id, label, kind, groupId = id) {
|
|
46
|
+
if (!this.items.has(id))
|
|
47
|
+
this.items.set(id, {
|
|
48
|
+
id,
|
|
49
|
+
label,
|
|
50
|
+
kind,
|
|
51
|
+
groupId,
|
|
52
|
+
evidence: [],
|
|
53
|
+
diagnosticIds: [],
|
|
54
|
+
actionIds: [],
|
|
55
|
+
treatment: "not_judged",
|
|
56
|
+
source: "none",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
diagnose(cause, fact, target, itemIds = [], blocking = true, omittedMembers = []) {
|
|
60
|
+
const id = `diagnostic:${this.diagnostics.length}`;
|
|
61
|
+
const actionId = `action:${this.actions.length}`;
|
|
62
|
+
const scope = itemIds.length
|
|
63
|
+
? { kind: "item", itemIds }
|
|
64
|
+
: { kind: "call" };
|
|
65
|
+
const code = cause === "invalid_root" || cause === "invalid_base"
|
|
66
|
+
? "correct_context"
|
|
67
|
+
: cause === "not_configured"
|
|
68
|
+
? "configure_client"
|
|
69
|
+
: cause === "service_unavailable" || cause === "transport_failure"
|
|
70
|
+
? "recover_service"
|
|
71
|
+
: "inspect_native";
|
|
72
|
+
this.actions.push({
|
|
73
|
+
id: actionId,
|
|
74
|
+
code,
|
|
75
|
+
target: target === undefined
|
|
76
|
+
? unknown("target not established")
|
|
77
|
+
: known(target),
|
|
78
|
+
scope,
|
|
79
|
+
condition: "Only after the named context, evidence, or service condition materially changes; otherwise use native inspection or the retained runner plan.",
|
|
80
|
+
instruction: code === "correct_context"
|
|
81
|
+
? "Correct the named repository root or base if it does not match the intended comparison."
|
|
82
|
+
: code === "configure_client"
|
|
83
|
+
? "Configure the required Jev endpoint before requesting judgment; use native inspection meanwhile."
|
|
84
|
+
: code === "recover_service"
|
|
85
|
+
? "Establish service recovery before another useful judgment request; do not repeat the unchanged call."
|
|
86
|
+
: "Inspect the named evidence natively or execute the retained runner plan; preserve this limitation.",
|
|
87
|
+
repeatUnchanged: false,
|
|
88
|
+
});
|
|
89
|
+
this.diagnostics.push({
|
|
90
|
+
id,
|
|
91
|
+
cause,
|
|
92
|
+
fact,
|
|
93
|
+
target: target === undefined
|
|
94
|
+
? unknown("target not established")
|
|
95
|
+
: known(target),
|
|
96
|
+
origin: cause === "control_failure"
|
|
97
|
+
? "control"
|
|
98
|
+
: cause === "call_budget" || cause === "session_budget"
|
|
99
|
+
? "budget"
|
|
100
|
+
: cause === "not_configured" ||
|
|
101
|
+
cause === "service_unavailable" ||
|
|
102
|
+
cause === "transport_failure" ||
|
|
103
|
+
cause === "invalid_response" ||
|
|
104
|
+
cause === "provider_context_refusal"
|
|
105
|
+
? "provider"
|
|
106
|
+
: "collection",
|
|
107
|
+
scope,
|
|
108
|
+
effect: blocking ? "blocking" : "reservation",
|
|
109
|
+
material: true,
|
|
110
|
+
omittedMembers,
|
|
111
|
+
memberCount: known(omittedMembers.length),
|
|
112
|
+
actionIds: [actionId],
|
|
113
|
+
});
|
|
114
|
+
for (const itemId of itemIds) {
|
|
115
|
+
const item = this.items.get(itemId);
|
|
116
|
+
if (item) {
|
|
117
|
+
item.diagnosticIds.push(id);
|
|
118
|
+
item.actionIds.push(actionId);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
static(id, reason, selected) {
|
|
123
|
+
const item = this.items.get(id);
|
|
124
|
+
if (!item)
|
|
125
|
+
throw new Error(`Unregistered review result ${id}`);
|
|
126
|
+
this.items.set(id, {
|
|
127
|
+
...item,
|
|
128
|
+
treatment: "static",
|
|
129
|
+
source: "none",
|
|
130
|
+
staticReason: reason,
|
|
131
|
+
...(selected === undefined ? {} : { selection: { selected, reason } }),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
answer(id, answer, input = {}, controls = [], countControls = true) {
|
|
135
|
+
const item = this.items.get(id);
|
|
136
|
+
if (!item)
|
|
137
|
+
throw new Error(`Unregistered review result ${id}`);
|
|
138
|
+
const unavailable = !answer || answer.type === "unjudged" || !answer.source;
|
|
139
|
+
const missingControl = controls.some((control) => control.type === "unjudged" || !control.source);
|
|
140
|
+
if (countControls)
|
|
141
|
+
for (const control of controls)
|
|
142
|
+
this.auxiliary.controls[control.type === "unjudged" || !control.source
|
|
143
|
+
? "notJudged"
|
|
144
|
+
: control.source]++;
|
|
145
|
+
if (unavailable || missingControl) {
|
|
146
|
+
this.diagnose(missingControl
|
|
147
|
+
? "control_failure"
|
|
148
|
+
: answer?.type === "unjudged"
|
|
149
|
+
? (answer.cause ?? "invalid_response")
|
|
150
|
+
: "invalid_response", missingControl
|
|
151
|
+
? "Required control response is unavailable; the requested group has no admissible judgment."
|
|
152
|
+
: answer?.type === "unjudged"
|
|
153
|
+
? answer.reason
|
|
154
|
+
: "Requested answer or its transport provenance is unavailable.", item.label, [id]);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const valid = answer;
|
|
158
|
+
const rawValues = valid.type === "bool"
|
|
159
|
+
? [{ label: "true", value: true, probability: known(valid.p) }]
|
|
160
|
+
: Object.entries(valid.probabilities).map(([label, p]) => ({
|
|
161
|
+
label,
|
|
162
|
+
value: label,
|
|
163
|
+
probability: known(p),
|
|
164
|
+
}));
|
|
165
|
+
this.items.set(id, answerItemFromInput(item, {
|
|
166
|
+
label: item.label,
|
|
167
|
+
answer: valid,
|
|
168
|
+
source: valid.source,
|
|
169
|
+
band: "verdict",
|
|
170
|
+
rawValues,
|
|
171
|
+
...input,
|
|
172
|
+
reportControls: controls.map((control, index) => {
|
|
173
|
+
const source = control.source;
|
|
174
|
+
if (!source)
|
|
175
|
+
throw new Error("Admitted control lacks provenance");
|
|
176
|
+
return {
|
|
177
|
+
id: `${id}:control:${index}`,
|
|
178
|
+
kind: "integrity",
|
|
179
|
+
source,
|
|
180
|
+
outcome: control.type === "bool"
|
|
181
|
+
? String(control.p)
|
|
182
|
+
: control.type === "choice"
|
|
183
|
+
? control.choice
|
|
184
|
+
: control.type === "score"
|
|
185
|
+
? String(control.score)
|
|
186
|
+
: control.reason,
|
|
187
|
+
rawValues: control.type === "choice" || control.type === "score"
|
|
188
|
+
? Object.entries(control.probabilities).map(([label, p]) => ({
|
|
189
|
+
label,
|
|
190
|
+
value: label,
|
|
191
|
+
probability: known(p),
|
|
192
|
+
}))
|
|
193
|
+
: control.type === "bool"
|
|
194
|
+
? [
|
|
195
|
+
{
|
|
196
|
+
label: "true",
|
|
197
|
+
value: true,
|
|
198
|
+
probability: known(control.p),
|
|
199
|
+
},
|
|
200
|
+
]
|
|
201
|
+
: [],
|
|
202
|
+
};
|
|
203
|
+
}),
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
failure(judgment, ids, cause) {
|
|
207
|
+
this.diagnose(cause ??
|
|
208
|
+
(!judgment.ok ? judgment.cause : undefined) ??
|
|
209
|
+
judgment.failureCause ??
|
|
210
|
+
"invalid_response", judgment.ok ? "Required responses unavailable" : judgment.error, undefined, ids);
|
|
211
|
+
}
|
|
212
|
+
build(tool, evidence, metrics) {
|
|
213
|
+
const context = contextFromEvidence(evidence);
|
|
214
|
+
context.inventories = this.inventories;
|
|
215
|
+
return buildResultReport({
|
|
216
|
+
tool,
|
|
217
|
+
context,
|
|
218
|
+
items: [...this.items.values()],
|
|
219
|
+
diagnostics: this.diagnostics,
|
|
220
|
+
actions: this.actions,
|
|
221
|
+
metrics,
|
|
222
|
+
auxiliary: this.auxiliary,
|
|
223
|
+
total: this.totalUnknown || (!this.inventories.length && !this.items.size)
|
|
224
|
+
? unknown("Expected inventory was not completely enumerable")
|
|
225
|
+
: known(this.items.size),
|
|
226
|
+
refused: this.refusal,
|
|
227
|
+
missingWork: this.missingWork,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|