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,249 @@
|
|
|
1
|
+
import { COVERAGE_WITNESS_LURE_MAX, WITNESS_ETALON_MIN, WITNESS_LARGE_CHARS, WITNESS_LARGE_LURE_MAX, WITNESS_LURE_MAX, } from "../constants.js";
|
|
2
|
+
import { isTestFile } from "../core/diff.js";
|
|
3
|
+
export function buildWitnessUnits(units, options = {}) {
|
|
4
|
+
const result = [];
|
|
5
|
+
const templates = [
|
|
6
|
+
{
|
|
7
|
+
kind: "function",
|
|
8
|
+
file: "src/slug.ts",
|
|
9
|
+
name: "slugify",
|
|
10
|
+
before: "function slugify(s) { return s.trim().toLowerCase(); }",
|
|
11
|
+
after: "function slugify(s) { const trimmed = s.trim(); return trimmed.toLowerCase(); }",
|
|
12
|
+
label: "neutral refactor",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
kind: "function",
|
|
16
|
+
file: "src/format.ts",
|
|
17
|
+
name: "formatLine",
|
|
18
|
+
before: "function formatLine(s) { return s + '\\n'; }",
|
|
19
|
+
after: "function formatLine(s) { return s + '\\n'; }",
|
|
20
|
+
label: "unchanged function",
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const classes = new Set(units.map((unit) => (isTestFile(unit.file) ? "test" : unit.kind)));
|
|
24
|
+
if (options.testFilesPresent)
|
|
25
|
+
classes.add("test");
|
|
26
|
+
for (const kind of classes) {
|
|
27
|
+
if (kind === "function")
|
|
28
|
+
continue;
|
|
29
|
+
if (kind === "test")
|
|
30
|
+
templates.push({
|
|
31
|
+
kind: "file",
|
|
32
|
+
file: "test/cli.test.ts",
|
|
33
|
+
name: "cli test",
|
|
34
|
+
before: "test('flag', () => expect(parseArgs(['--legacy'])).toEqual({ mode: true }));",
|
|
35
|
+
after: "test('flag', () => expect(parseArgs(['--compat'])).toEqual({ mode: true }));",
|
|
36
|
+
label: "test that follows a rename",
|
|
37
|
+
});
|
|
38
|
+
else if (kind === "file")
|
|
39
|
+
templates.push({
|
|
40
|
+
kind,
|
|
41
|
+
file: "config/settings.json",
|
|
42
|
+
name: "settings",
|
|
43
|
+
before: '{"port":8080,"enabled":true}',
|
|
44
|
+
after: '{"enabled":true,"port":8080}',
|
|
45
|
+
label: "configuration",
|
|
46
|
+
});
|
|
47
|
+
else if (kind === "declaration")
|
|
48
|
+
templates.push({
|
|
49
|
+
kind,
|
|
50
|
+
file: "src/limits.ts",
|
|
51
|
+
name: "MAX_ITEMS",
|
|
52
|
+
before: "export const MAX_ITEMS = 1000;",
|
|
53
|
+
after: "export const MAX_ITEMS = 1_000;",
|
|
54
|
+
label: "declaration",
|
|
55
|
+
});
|
|
56
|
+
else
|
|
57
|
+
templates.push({
|
|
58
|
+
kind,
|
|
59
|
+
file: "src/render.ts",
|
|
60
|
+
name: "render",
|
|
61
|
+
before: "const value = item.name;\nreturn value;",
|
|
62
|
+
after: "const label = item.name;\nreturn label;",
|
|
63
|
+
label: `${kind} neutral rename`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
templates.push({
|
|
67
|
+
kind: "function",
|
|
68
|
+
file: "src/accounts.ts",
|
|
69
|
+
name: "deleteAccount",
|
|
70
|
+
before: "function deleteAccount(user, account) { if (user.role !== 'admin') throw Error('forbidden'); return db.delete(account); }",
|
|
71
|
+
after: "function deleteAccount(user, account) { return db.delete(account); }",
|
|
72
|
+
reference: "security",
|
|
73
|
+
label: "authorization removed",
|
|
74
|
+
}, {
|
|
75
|
+
kind: "function",
|
|
76
|
+
file: "src/money.ts",
|
|
77
|
+
name: "formatPrice",
|
|
78
|
+
before: "export function formatPrice(amount) { return amount.toFixed(2); }",
|
|
79
|
+
after: "export function formatMoney(amount) { return amount.toFixed(2); }",
|
|
80
|
+
reference: "compatibility",
|
|
81
|
+
label: "export renamed",
|
|
82
|
+
}, {
|
|
83
|
+
kind: "function",
|
|
84
|
+
file: "src/discount.ts",
|
|
85
|
+
name: "applyDiscount",
|
|
86
|
+
before: "function applyDiscount(price, discount) { return price - discount; }",
|
|
87
|
+
after: "function applyDiscount(price, discount) { return price + discount; }",
|
|
88
|
+
reference: "correctness",
|
|
89
|
+
label: "discount sign reversed",
|
|
90
|
+
});
|
|
91
|
+
const ids = new Set(units.map((unit) => unit.id));
|
|
92
|
+
let number = 1;
|
|
93
|
+
for (const template of templates) {
|
|
94
|
+
let unitId;
|
|
95
|
+
do {
|
|
96
|
+
unitId = `w${String(number++).padStart(3, "0")}`;
|
|
97
|
+
} while (ids.has(unitId));
|
|
98
|
+
ids.add(unitId);
|
|
99
|
+
const unit = {
|
|
100
|
+
id: unitId,
|
|
101
|
+
kind: template.kind,
|
|
102
|
+
file: template.file,
|
|
103
|
+
name: template.name,
|
|
104
|
+
exported: true,
|
|
105
|
+
before: template.before,
|
|
106
|
+
after: template.after,
|
|
107
|
+
beforeRange: { start: 1, end: template.before.split("\n").length },
|
|
108
|
+
afterRange: { start: 1, end: template.after.split("\n").length },
|
|
109
|
+
};
|
|
110
|
+
result.push({
|
|
111
|
+
unit,
|
|
112
|
+
label: template.label,
|
|
113
|
+
...(template.reference ? { reference: template.reference } : {}),
|
|
114
|
+
limit: template.reference
|
|
115
|
+
? WITNESS_ETALON_MIN
|
|
116
|
+
: template.before.length + template.after.length >= WITNESS_LARGE_CHARS
|
|
117
|
+
? WITNESS_LARGE_LURE_MAX
|
|
118
|
+
: WITNESS_LURE_MAX,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
/** Frozen embedded-after coverage template; independent of risk witnesses. */
|
|
124
|
+
export function buildCoverageWitnessUnits(units) {
|
|
125
|
+
const templates = [
|
|
126
|
+
[
|
|
127
|
+
"normalize",
|
|
128
|
+
"src/normalize.js",
|
|
129
|
+
"export function normalize(s) { return s.trim().toLowerCase(); }",
|
|
130
|
+
"export function normalize(s) { const v = s.trim(); return v.toLowerCase(); }",
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
"formatLine",
|
|
134
|
+
"src/format.js",
|
|
135
|
+
"export function formatLine(s) { return s + '\\n'; }",
|
|
136
|
+
"export function formatLine(s) { return s + '\\n'; }",
|
|
137
|
+
],
|
|
138
|
+
[
|
|
139
|
+
"MAX_ITEMS",
|
|
140
|
+
"src/count.js",
|
|
141
|
+
"export const MAX_ITEMS = 1000;",
|
|
142
|
+
"export const MAX_ITEMS = 1_000;",
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
"increment",
|
|
146
|
+
"src/increment.js",
|
|
147
|
+
"export function increment(n) { return n - 1; }",
|
|
148
|
+
"export function increment(n) { return n + 1; }",
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
"formatPrice",
|
|
152
|
+
"src/price.js",
|
|
153
|
+
"export function formatPrice(n) { return n.toFixed(2); }",
|
|
154
|
+
"export function formatMoney(n) { return n.toFixed(2); }",
|
|
155
|
+
],
|
|
156
|
+
[
|
|
157
|
+
"LIMIT",
|
|
158
|
+
"src/limit.js",
|
|
159
|
+
"export const LIMIT = 3;",
|
|
160
|
+
"export const LIMIT = 4;",
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
"withinLimit",
|
|
164
|
+
"src/limit.js",
|
|
165
|
+
"export function withinLimit(n) { return n <= LIMIT; }",
|
|
166
|
+
"export function withinLimit(n) { return n <= LIMIT; }",
|
|
167
|
+
],
|
|
168
|
+
];
|
|
169
|
+
const ids = new Set(units.map((unit) => unit.id));
|
|
170
|
+
let number = 1;
|
|
171
|
+
return templates.map(([name, file, before, after], index) => {
|
|
172
|
+
let id;
|
|
173
|
+
do {
|
|
174
|
+
id = `w${String(number++).padStart(3, "0")}`;
|
|
175
|
+
} while (ids.has(id));
|
|
176
|
+
ids.add(id);
|
|
177
|
+
const reference = index >= 3 && index < 6;
|
|
178
|
+
return {
|
|
179
|
+
unit: {
|
|
180
|
+
id,
|
|
181
|
+
name,
|
|
182
|
+
file,
|
|
183
|
+
before,
|
|
184
|
+
after,
|
|
185
|
+
kind: name === "LIMIT" || name === "MAX_ITEMS" ? "declaration" : "function",
|
|
186
|
+
exported: true,
|
|
187
|
+
beforeRange: null,
|
|
188
|
+
afterRange: null,
|
|
189
|
+
},
|
|
190
|
+
label: name,
|
|
191
|
+
...(reference ? { reference: "correctness" } : {}),
|
|
192
|
+
...(name === "withinLimit" ? { evidenceOnly: true } : {}),
|
|
193
|
+
limit: reference ? WITNESS_ETALON_MIN : COVERAGE_WITNESS_LURE_MAX,
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
export function evaluateBatchWitnessHealth(questionIds, witnesses, judgment) {
|
|
198
|
+
const failures = [];
|
|
199
|
+
const unhealthyQuestionIds = new Map();
|
|
200
|
+
const realIds = new Set(questionIds);
|
|
201
|
+
const batches = judgment.batches ?? [
|
|
202
|
+
{
|
|
203
|
+
questionIds: [...questionIds],
|
|
204
|
+
answers: judgment.ok ? judgment.answers : {},
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
for (const batch of batches) {
|
|
208
|
+
if (!batch.questionIds.some((id) => realIds.has(id)))
|
|
209
|
+
continue;
|
|
210
|
+
const unavailable = [];
|
|
211
|
+
const reasons = [];
|
|
212
|
+
for (const witness of witnesses) {
|
|
213
|
+
const answer = batch.answers[witness.id];
|
|
214
|
+
const dimension = witness.dimension ? ` ${witness.dimension}` : "";
|
|
215
|
+
if (!judgment.ok)
|
|
216
|
+
unavailable.push(judgment.error);
|
|
217
|
+
else if (answer?.type !== "bool")
|
|
218
|
+
unavailable.push(`${witness.label}${dimension} unjudged${answer?.type === "unjudged" ? `: ${answer.reason}` : ""}`);
|
|
219
|
+
else if (witness.expected === "no"
|
|
220
|
+
? answer.p > witness.limit
|
|
221
|
+
: answer.p < witness.limit)
|
|
222
|
+
reasons.push(`${witness.expected === "no" ? "decoy" : "reference"} "${witness.label}"${dimension} at ${answer.p.toFixed(2)} (limit ${witness.limit})`);
|
|
223
|
+
}
|
|
224
|
+
if (!reasons.length && !unavailable.length)
|
|
225
|
+
continue;
|
|
226
|
+
const batchFailures = [];
|
|
227
|
+
if (reasons.length)
|
|
228
|
+
batchFailures.push({
|
|
229
|
+
fact: `set-up check failed: ${reasons.join("; ")}`,
|
|
230
|
+
next: "rerun with base= a nearer ref and complete before/after units",
|
|
231
|
+
});
|
|
232
|
+
if (unavailable.length)
|
|
233
|
+
batchFailures.push({
|
|
234
|
+
fact: `witness control unavailable: ${[...new Set(unavailable)].join("; ")}`,
|
|
235
|
+
next: "check Jev availability or raise max_calls",
|
|
236
|
+
});
|
|
237
|
+
failures.push(...batchFailures);
|
|
238
|
+
const reason = batchFailures.map((failure) => failure.fact).join("; ");
|
|
239
|
+
for (const id of batch.questionIds)
|
|
240
|
+
if (realIds.has(id))
|
|
241
|
+
unhealthyQuestionIds.set(id, reason);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
healthy: failures.length === 0,
|
|
245
|
+
failures,
|
|
246
|
+
controls: failures,
|
|
247
|
+
unhealthyQuestionIds,
|
|
248
|
+
};
|
|
249
|
+
}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
function renderLine(line) {
|
|
2
|
+
switch (line.type) {
|
|
3
|
+
case "state":
|
|
4
|
+
return `state: ${line.files} files · integrity ${line.integrity}${line.warnings.length ? ` · ${line.warnings.length} warnings: ${line.warnings.join("; ")}` : ""}`;
|
|
5
|
+
case "answer":
|
|
6
|
+
return `${line.band === "verdict" ? "" : `${line.band} `}${line.uncalibrated ? "uncalibrated " : ""}${line.label} = ${line.value.head} (${line.value.p})${line.orderDependent ? " · order-dependent" : ""}${line.reason ? ` — ${line.reason}` : ""}${line.candidates?.map((candidate) => `\nalso: ${candidate.label} = ${candidate.value.head} (${candidate.value.p})`).join("") ?? ""}`;
|
|
7
|
+
case "unjudged":
|
|
8
|
+
return `unjudged ${line.label} — ${line.reason}; ${line.next}`;
|
|
9
|
+
case "entry": {
|
|
10
|
+
const readable = [line.candidate, ...(line.candidates ?? [])]
|
|
11
|
+
.map((candidate) => candidate.value.head)
|
|
12
|
+
.filter((head) => typeof head === "string" && head !== "none");
|
|
13
|
+
const action = readable.length > 1
|
|
14
|
+
? "read both"
|
|
15
|
+
: readable.length === 1
|
|
16
|
+
? `read ${readable[0]}; rephrase goal or widen scope if needed`
|
|
17
|
+
: "rephrase goal or widen scope";
|
|
18
|
+
return `${line.band === "unsure" ? `unsure entry — ${action}` : `${line.band === "verdict" ? "" : `${line.band} `}entry: ${line.candidate.value.head} (${line.candidate.value.p.toFixed(2)})`}${line.band === "unsure" ? `\n ${line.candidate.value.head} ${line.candidate.value.p.toFixed(2)}` : ""}${line.orderDependent && line.orderProbabilities ? ` (order-dependent: ${line.orderProbabilities.map((p) => p.toFixed(2)).join(" / ")})` : ""}${line.candidates?.map((candidate) => `\n ${candidate.value.head} ${candidate.value.p.toFixed(2)}`).join("") ?? ""}${line.relevant?.length ? `\nrelevant:\n${line.relevant.map((candidate) => ` ${candidate.value.head} ${candidate.value.p.toFixed(2)}`).join("\n")}` : ""}${line.nextRead?.length ? `\nread ${line.nextRead.join(" · ")}` : ""}`;
|
|
19
|
+
}
|
|
20
|
+
case "list":
|
|
21
|
+
return `${line.title}:\n${line.items.map((item) => ` - ${item}`).join("\n")}`;
|
|
22
|
+
case "command":
|
|
23
|
+
return `run: cwd: ${line.cwd} · framework: ${line.framework}${line.config ? ` · config: ${line.config}` : ""}${line.project ? ` · project: ${line.project}` : ""} · command: ${[line.executable, ...line.args].map((arg) => (/^[a-zA-Z0-9_./:@+-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", "'\\''")}'`)).join(" ")}`;
|
|
24
|
+
case "fact":
|
|
25
|
+
return `[${line.fact}${line.next ? `; ${line.next}` : ""}]`;
|
|
26
|
+
case "limit-group":
|
|
27
|
+
return `[${line.cause}: ${line.count} limit${line.count === 1 ? "" : "s"} (${line.priority ? "selected tests / changed units" : "remaining inventory"}) — ${line.examples.join(" · ")}${line.remaining ? ` · +${line.remaining} more (bounded path list)` : ""}; ${line.next}]`;
|
|
28
|
+
case "collection":
|
|
29
|
+
return `[${line.title}: ${line.count} sections — ${line.items.join(" · ")}${line.count > line.items.length ? ` · +${line.count - line.items.length} more (bounded section list)` : ""}; ${line.next}]`;
|
|
30
|
+
case "unchecked":
|
|
31
|
+
return `unchecked: ${line.items.join(" · ")}`;
|
|
32
|
+
case "refusal":
|
|
33
|
+
return line.message;
|
|
34
|
+
case "yield": {
|
|
35
|
+
const value = line.value;
|
|
36
|
+
return `${value.calls} calls · ${value.questions} questions · ${value.costUsd === undefined ? "cost unavailable" : `$${value.costUsd.toFixed(5)}`} · cache ${value.cacheHits}/${value.cacheRequests} · ${(value.elapsedMs / 1000).toFixed(1)} s`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function renderEnvelope(envelope) {
|
|
41
|
+
return envelope.lines.map(renderLine).join("\n");
|
|
42
|
+
}
|
|
43
|
+
function knowledge(value, format = String) {
|
|
44
|
+
return value.status === "known"
|
|
45
|
+
? format(value.value)
|
|
46
|
+
: `${value.status} (${value.reason})`;
|
|
47
|
+
}
|
|
48
|
+
function scopeLabel(scope) {
|
|
49
|
+
return scope.kind === "call"
|
|
50
|
+
? "call"
|
|
51
|
+
: scope.kind === "item"
|
|
52
|
+
? `items ${scope.itemIds.join(", ")}`
|
|
53
|
+
: scope.kind === "group"
|
|
54
|
+
? `groups ${scope.groupIds.join(", ")}`
|
|
55
|
+
: `inventory ${scope.inventoryIds.join(", ")}`;
|
|
56
|
+
}
|
|
57
|
+
/** Human projection uses the same typed facts as structured transport, never prose inference. */
|
|
58
|
+
export function renderResultReport(report, options = {}) {
|
|
59
|
+
const { context: c, accounting: a } = report;
|
|
60
|
+
const counts = a.requestedResults;
|
|
61
|
+
const mode = counts.fresh + counts.cache
|
|
62
|
+
? `${counts.fresh} fresh / ${counts.cache} cache Jev results`
|
|
63
|
+
: counts.static
|
|
64
|
+
? "static selection; no Jev judgment"
|
|
65
|
+
: report.items.some((item) => item.selection?.selected)
|
|
66
|
+
? "conservative fallback selection; no Jev judgment"
|
|
67
|
+
: "no admissible judgment";
|
|
68
|
+
const lines = [
|
|
69
|
+
`${report.execution} — ${mode}${counts.notJudged ? `; ${counts.notJudged} not judged` : ""}`,
|
|
70
|
+
`context: authority ${knowledge(c.authority, (v) => `${v.path} (${v.origin})`)} · requested root ${knowledge(c.requestedRoot)} · effective root ${knowledge(c.effectiveRoot, (v) => `${v.path} (${v.origin}); common-dir ${knowledge(v.commonDir)}`)} · base ${knowledge(c.requestedBase)} → ${knowledge(c.resolvedBase)}`,
|
|
71
|
+
];
|
|
72
|
+
for (const inv of c.inventories) {
|
|
73
|
+
lines.push(`inventory ${inv.id} (${inv.kind}): discovered ${knowledge(inv.discovered)} · considered ${knowledge(inv.considered)} · scopeRestricted ${inv.scopeRestricted} · rules ${inv.rules.join("; ")} · restrictions ${inv.restrictions.join("; ") || "none"}`);
|
|
74
|
+
for (const criterion of inv.criteria)
|
|
75
|
+
lines.push(`criterion ${JSON.stringify(criterion.criterion)}: ${criterion.outcome}; matches ${knowledge(criterion.matches)}`);
|
|
76
|
+
}
|
|
77
|
+
if (c.command.execution !== "not_requested")
|
|
78
|
+
lines.push(`command: ${c.command.execution} · cwd ${knowledge(c.command.cwd)} · exit ${knowledge(c.command.exitCode)} · timedOut ${knowledge(c.command.timedOut)}`);
|
|
79
|
+
for (const item of report.items) {
|
|
80
|
+
if (item.treatment === "judged") {
|
|
81
|
+
const j = item.judgment;
|
|
82
|
+
lines.push(`${j.band === "verdict" ? "" : `${j.band} `}${j.uncalibrated ? "uncalibrated " : ""}${item.label} = ${String(j.result)} (${j.measure.kind} ${knowledge(j.measure.value)}) [${item.source}]${j.reason ? ` — ${j.reason}` : ""}`);
|
|
83
|
+
for (const raw of j.rawValues)
|
|
84
|
+
lines.push(` raw ${raw.label} = ${String(raw.value)}; probability ${knowledge(raw.probability)}`);
|
|
85
|
+
for (const control of j.controls) {
|
|
86
|
+
lines.push(` control ${control.id} (${control.kind}, ${control.source}): ${control.outcome}`);
|
|
87
|
+
for (const raw of control.rawValues)
|
|
88
|
+
lines.push(` raw ${raw.label} = ${String(raw.value)}; probability ${knowledge(raw.probability)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else
|
|
92
|
+
lines.push(`${item.treatment === "static" ? "static" : "unjudged"} ${item.label}${item.treatment === "static" ? ` — ${item.staticReason}` : ""}`);
|
|
93
|
+
for (const evidence of item.evidence)
|
|
94
|
+
lines.push(` evidence ${evidence.target} (${evidence.side}): ${knowledge(evidence.canonicalPath)} · aliases ${evidence.aliases.join(", ") || "none"} · revision ${knowledge(evidence.revision)}`);
|
|
95
|
+
if (item.selection)
|
|
96
|
+
lines.push(` selection: ${item.selection.selected ? "selected" : "not selected"} — ${item.selection.reason}`);
|
|
97
|
+
if (item.diagnosticIds.length)
|
|
98
|
+
lines.push(` diagnostics ${item.diagnosticIds.join(", ")}`);
|
|
99
|
+
if (item.actionIds.length)
|
|
100
|
+
lines.push(` actions ${item.actionIds.join(", ")}`);
|
|
101
|
+
}
|
|
102
|
+
// Complete diagnostic lists keep legacy text-only clients self-contained.
|
|
103
|
+
for (const d of report.diagnostics)
|
|
104
|
+
lines.push(`${d.material ? "material " : ""}${d.effect} ${d.id} — ${d.cause}: ${d.fact} · target ${knowledge(d.target)} · origin ${d.origin} · scope ${scopeLabel(d.scope)} · members ${knowledge(d.memberCount)}${d.omittedMembers.length ? ` · omitted ${d.omittedMembers.join(", ")}` : ""} · actions ${d.actionIds.join(", ") || "none"}`);
|
|
105
|
+
for (const action of report.actions)
|
|
106
|
+
lines.push(`next ${action.id} (${action.code}; ${scopeLabel(action.scope)}; target ${knowledge(action.target)}): ${action.condition} — ${action.instruction}`);
|
|
107
|
+
if (options.details) {
|
|
108
|
+
const detailLines = options.details.lines.filter((line) => line.type === "command" || line.type === "list");
|
|
109
|
+
if (detailLines.length)
|
|
110
|
+
lines.push("details:", renderEnvelope({ lines: detailLines }));
|
|
111
|
+
}
|
|
112
|
+
lines.push(`HTTP attempts ${a.httpAttempts} · questions sent ${a.questionsSent} · requested results total ${knowledge(counts.total)} / fresh ${counts.fresh} / cache ${counts.cache} / not judged ${counts.notJudged} / static ${counts.static} · cache probes ${a.cacheHits}/${a.cacheRequests} · auxiliary controls fresh ${a.auxiliary.controls.fresh} / cache ${a.auxiliary.controls.cache} / not judged ${a.auxiliary.controls.notJudged} / static ${a.auxiliary.controls.static} · passages fresh ${a.auxiliary.passages.fresh} / cache ${a.auxiliary.passages.cache} / not judged ${a.auxiliary.passages.notJudged} · current cost USD ${knowledge(a.costUsd)} · elapsed ${a.elapsedMs} ms`);
|
|
113
|
+
return lines.join("\n");
|
|
114
|
+
}
|