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
|
@@ -9,108 +9,185 @@ function unquote(value) {
|
|
|
9
9
|
return parsed;
|
|
10
10
|
}
|
|
11
11
|
catch {
|
|
12
|
-
|
|
12
|
+
/* retain the literal selector */
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
return value.slice(1, -1).replace(/\\(['\\])/g, "$1");
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
function normalized(path) {
|
|
18
|
+
return path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
|
|
19
|
+
}
|
|
20
|
+
function forbidden(path) {
|
|
21
|
+
return (/^(?:\/|[A-Za-z]:|[a-z][a-z\d+.-]*:\/\/)/i.test(path) ||
|
|
22
|
+
path.split(/[\\/]/).some((part) => part === ".." || part === ".git"));
|
|
23
|
+
}
|
|
24
|
+
function explicitPaths(text) {
|
|
25
|
+
const references = new Map();
|
|
26
|
+
for (const match of text.matchAll(fileSelector)) {
|
|
27
|
+
const reference = unquote(match[2] ?? "");
|
|
28
|
+
const side = match[1];
|
|
29
|
+
references.set(JSON.stringify([side, reference]), { reference, side });
|
|
30
|
+
}
|
|
31
|
+
return [...references.values()];
|
|
29
32
|
}
|
|
30
|
-
/**
|
|
33
|
+
/** Lexical discovery hints, never requirements. Ordinary member expressions are excluded. */
|
|
31
34
|
export function citedPaths(text) {
|
|
32
|
-
const found = new Set();
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const withoutFields = withoutSelectors.replace(selector, (value) => value.includes("[") || /^(?:state|docs?|files|files_before)\./.test(value)
|
|
35
|
+
const found = new Set(explicitPaths(text).map(({ reference }) => reference));
|
|
36
|
+
const cleaned = text
|
|
37
|
+
.replace(/\b(?:[a-z][a-z\d+.-]*:\/\/|www\.)[^\s<>"'`]+/gi, " ")
|
|
38
|
+
.replace(fileSelector, " ")
|
|
39
|
+
.replace(selector, (value) => value.includes("[") ||
|
|
40
|
+
/^(?:state|output|docs?|files|files_before)\./.test(value)
|
|
39
41
|
? " "
|
|
40
42
|
: value);
|
|
41
|
-
const
|
|
42
|
-
const value =
|
|
43
|
-
if (
|
|
44
|
-
found.add(value);
|
|
45
|
-
return " ";
|
|
46
|
-
}
|
|
47
|
-
return value;
|
|
48
|
-
});
|
|
49
|
-
for (const match of unquoted.matchAll(/[\w.@+-]+(?:\/[\w.@+-]+)*/g)) {
|
|
50
|
-
const value = match[0].replace(/[.,;:!?]+$/, "");
|
|
51
|
-
if (looksLikePath(value) &&
|
|
52
|
-
(!value.includes("/") ||
|
|
53
|
-
looksLikePath(value.slice(value.lastIndexOf("/") + 1))))
|
|
43
|
+
for (const match of cleaned.matchAll(/`([^`\n]+)`|"([^"\n]+)"|'([^'\n]+)'/g)) {
|
|
44
|
+
const value = match[1] ?? match[2] ?? match[3] ?? "";
|
|
45
|
+
if (/^(?:[\w@+-]+\/)+[\w.@+-]+$/.test(value))
|
|
54
46
|
found.add(value);
|
|
55
47
|
}
|
|
48
|
+
for (const match of cleaned.matchAll(/\b(?:README|LICENSE|LICENCE|Makefile|Dockerfile|CHANGELOG|AGENTS|CLAUDE)(?:\.[\w-]+)?\b/g))
|
|
49
|
+
found.add(match[0]);
|
|
50
|
+
for (const match of cleaned.matchAll(/(?:\.?\.?\/)?[\w@+-]+(?:\/[\w.@+-]+)*\.(?:[cm]?[jt]sx?|py|jsonc?|ya?ml|toml|ini|cfg|conf|mdx?|txt|rst|html?|css|scss|sql|sh|bash|rs|go|java|rb|php|cpp|hpp|vue|svelte|xml|csv|lock)\b/g)) {
|
|
51
|
+
const start = match.index ?? 0;
|
|
52
|
+
if ((start && /[\w.]/.test(cleaned[start - 1] ?? "")) ||
|
|
53
|
+
/^(?:Node|Next|Vue)\.js$/i.test(match[0]))
|
|
54
|
+
continue;
|
|
55
|
+
found.add(match[0]);
|
|
56
|
+
}
|
|
56
57
|
return [...found];
|
|
57
58
|
}
|
|
58
59
|
function questionText(question) {
|
|
59
|
-
const criteria = question.criteria;
|
|
60
60
|
return [
|
|
61
61
|
question.instructions,
|
|
62
|
-
...
|
|
62
|
+
...Object.values(question.criteria ?? {}),
|
|
63
63
|
].join("\n");
|
|
64
64
|
}
|
|
65
65
|
function readingTexts(plan) {
|
|
66
66
|
return plan.readings.map((reading) => ({
|
|
67
67
|
id: reading.id,
|
|
68
|
+
about: plan.asks[reading.askIndex]?.about,
|
|
68
69
|
text: [reading.id, reading.twin, ...Object.values(reading.controls ?? {})]
|
|
69
70
|
.flatMap((id) => id && plan.questions[id] ? [questionText(plan.questions[id])] : [])
|
|
70
71
|
.join("\n"),
|
|
71
72
|
}));
|
|
72
73
|
}
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
export function resolveAskReferences(plan, note, evidence, inventory) {
|
|
75
|
+
const index = evidence.files !== null && typeof evidence.files === "object"
|
|
76
|
+
? evidence
|
|
77
|
+
: { files: evidence };
|
|
78
|
+
const supplied = [
|
|
79
|
+
...new Set([
|
|
80
|
+
...Object.keys(index.files),
|
|
81
|
+
...Object.keys(index.files_before ?? {}),
|
|
82
|
+
]),
|
|
83
|
+
].sort();
|
|
84
|
+
const currentKnown = [...new Set(inventory)].sort();
|
|
85
|
+
const beforeKnown = [
|
|
86
|
+
...new Set([...inventory, ...(index.beforeInventory ?? [])]),
|
|
87
|
+
].sort();
|
|
88
|
+
const selected = new Set(supplied.map(normalized));
|
|
89
|
+
const versions = new Set([
|
|
90
|
+
...Object.keys(index.files).map((path) => `files:${normalized(path)}`),
|
|
91
|
+
...Object.keys(index.files_before ?? {}).map((path) => `files_before:${normalized(path)}`),
|
|
92
|
+
]);
|
|
83
93
|
const additions = [];
|
|
84
94
|
const unresolved = [];
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
95
|
+
const invalid = [];
|
|
96
|
+
const aliases = Object.create(null);
|
|
97
|
+
const sources = [
|
|
98
|
+
...readingTexts(plan).map(({ id, text, about }) => ({
|
|
99
|
+
text: `${about ?? ""}\n${text}`,
|
|
100
|
+
scope: id,
|
|
101
|
+
origin: "question",
|
|
102
|
+
output: about === "output",
|
|
103
|
+
})),
|
|
104
|
+
{
|
|
105
|
+
text: note ?? "",
|
|
106
|
+
scope: "global",
|
|
107
|
+
origin: "note",
|
|
108
|
+
output: false,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
for (const source of sources) {
|
|
112
|
+
const explicit = explicitPaths(source.text);
|
|
113
|
+
const explicitNames = new Set(explicit.map(({ reference }) => reference));
|
|
114
|
+
const references = [
|
|
115
|
+
...explicit.map((ref) => ({ ...ref, required: true })),
|
|
116
|
+
...(source.output
|
|
117
|
+
? []
|
|
118
|
+
: citedPaths(source.text)
|
|
119
|
+
.filter((reference) => !explicitNames.has(reference))
|
|
120
|
+
.map((reference) => ({
|
|
121
|
+
reference,
|
|
122
|
+
side: "files",
|
|
123
|
+
required: false,
|
|
124
|
+
}))),
|
|
125
|
+
];
|
|
126
|
+
for (const ref of references) {
|
|
127
|
+
const { reference, required, side } = ref;
|
|
128
|
+
const known = side === "files_before" ? beforeKnown : currentKnown;
|
|
129
|
+
const facts = {
|
|
105
130
|
reference,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
131
|
+
required,
|
|
132
|
+
side,
|
|
133
|
+
origin: source.origin,
|
|
134
|
+
scope: source.scope,
|
|
135
|
+
};
|
|
136
|
+
if (forbidden(reference)) {
|
|
137
|
+
if (required)
|
|
138
|
+
invalid.push({
|
|
139
|
+
...facts,
|
|
140
|
+
reason: `Path not permitted: ${reference}`,
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const key = normalized(reference);
|
|
145
|
+
// Directory-bearing explicit paths are exact, never suffix fallbacks.
|
|
146
|
+
const exact = supplied.find((path) => normalized(path) === key);
|
|
147
|
+
const abbreviated = !key.includes("/");
|
|
148
|
+
let matches = exact ? [exact] : [];
|
|
149
|
+
if (!matches.length && abbreviated) {
|
|
150
|
+
const alias = index.aliases && Object.hasOwn(index.aliases, reference)
|
|
151
|
+
? index.aliases[reference]
|
|
152
|
+
: undefined;
|
|
153
|
+
if (alias !== undefined)
|
|
154
|
+
matches = (typeof alias === "string" ? [alias] : [...alias]).filter((path) => supplied.includes(path));
|
|
155
|
+
if (!matches.length)
|
|
156
|
+
matches = supplied.filter((path) => normalized(path).endsWith(`/${key}`));
|
|
157
|
+
}
|
|
158
|
+
if (!matches.length)
|
|
159
|
+
matches = known.filter((path) => normalized(path) === key);
|
|
160
|
+
if (!matches.length && abbreviated)
|
|
161
|
+
matches = known.filter((path) => normalized(path).endsWith(`/${key}`));
|
|
162
|
+
if (matches.length !== 1) {
|
|
163
|
+
if (required)
|
|
164
|
+
unresolved.push({
|
|
165
|
+
...facts,
|
|
166
|
+
reason: `${reference} is required but missing from state (${matches.length ? "ambiguous path" : "not found"})`,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const path = matches[0];
|
|
171
|
+
if (path === undefined)
|
|
172
|
+
continue;
|
|
173
|
+
aliases[reference] = path;
|
|
174
|
+
const identity = normalized(path);
|
|
175
|
+
const version = `${side}:${identity}`;
|
|
176
|
+
if (versions.has(version))
|
|
177
|
+
continue;
|
|
178
|
+
if (!selected.has(identity) && selected.size >= ASK_MAX_FILES) {
|
|
179
|
+
unresolved.push({
|
|
180
|
+
...facts,
|
|
181
|
+
reason: `${reference} omitted (ASK_MAX_FILES limit: ${ASK_MAX_FILES})`,
|
|
182
|
+
});
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
selected.add(identity);
|
|
186
|
+
versions.add(version);
|
|
187
|
+
additions.push({ ...facts, path });
|
|
109
188
|
}
|
|
110
|
-
selected.add(normalized(path));
|
|
111
|
-
additions.push({ reference, path });
|
|
112
189
|
}
|
|
113
|
-
return { additions, unresolved };
|
|
190
|
+
return { additions, unresolved, aliases, invalid };
|
|
114
191
|
}
|
|
115
192
|
function object(value) {
|
|
116
193
|
return value !== null && typeof value === "object";
|
|
@@ -126,69 +203,76 @@ function unpack(value) {
|
|
|
126
203
|
}
|
|
127
204
|
}
|
|
128
205
|
function empty(value) {
|
|
129
|
-
return (value ===
|
|
206
|
+
return (value === undefined ||
|
|
207
|
+
value === null ||
|
|
130
208
|
(typeof value === "string" && value.trim() === "") ||
|
|
131
209
|
(object(value) && Object.keys(value).length === 0));
|
|
132
210
|
}
|
|
133
211
|
function emptyReference(text, state) {
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
if (object(files)) {
|
|
137
|
-
for (const reference of citedPaths(text)) {
|
|
138
|
-
if (Object.hasOwn(files, reference) && empty(files[reference]))
|
|
139
|
-
return reference;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
212
|
+
const metadata = object(state.evidence) ? state.evidence : {};
|
|
213
|
+
const aliases = object(metadata.aliases) ? metadata.aliases : {};
|
|
142
214
|
for (const match of text.matchAll(selector)) {
|
|
143
215
|
const parts = [
|
|
144
216
|
...match[0].matchAll(/[A-Za-z_$][\w$]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\d+/g),
|
|
145
217
|
].map(([part]) => (/^["']/.test(part) ? unquote(part) : part));
|
|
146
218
|
const root = parts[0];
|
|
147
|
-
if (root
|
|
219
|
+
if (!root)
|
|
148
220
|
continue;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (root === "state") {
|
|
152
|
-
value = state;
|
|
153
|
-
offset = 1;
|
|
154
|
-
}
|
|
155
|
-
else if (Object.hasOwn(state, root)) {
|
|
156
|
-
value = state[root];
|
|
157
|
-
offset = 1;
|
|
158
|
-
}
|
|
159
|
-
else
|
|
221
|
+
if (!["state", "output", "files", "files_before"].includes(root) &&
|
|
222
|
+
!Object.hasOwn(state, root))
|
|
160
223
|
continue;
|
|
161
|
-
|
|
162
|
-
|
|
224
|
+
if (!/[.[]/.test(match[0]) && text.trim() !== match[0].trim())
|
|
225
|
+
continue;
|
|
226
|
+
let value = root === "state"
|
|
227
|
+
? Object.hasOwn(state, "state")
|
|
228
|
+
? state.state
|
|
229
|
+
: state
|
|
230
|
+
: state[root];
|
|
231
|
+
const tail = parts.slice(1);
|
|
232
|
+
if ((root === "files" || root === "files_before") &&
|
|
233
|
+
tail[0] &&
|
|
234
|
+
object(value) &&
|
|
235
|
+
!Object.hasOwn(value, tail[0])) {
|
|
236
|
+
const alias = Object.hasOwn(aliases, tail[0])
|
|
237
|
+
? aliases[tail[0]]
|
|
238
|
+
: undefined;
|
|
239
|
+
if (typeof alias === "string")
|
|
240
|
+
tail[0] = alias;
|
|
241
|
+
}
|
|
242
|
+
for (const part of tail) {
|
|
163
243
|
value = unpack(value);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
value = value[part];
|
|
244
|
+
value =
|
|
245
|
+
object(value) && Object.hasOwn(value, part) ? value[part] : undefined;
|
|
169
246
|
}
|
|
170
|
-
//
|
|
171
|
-
if (root === "files_before" &&
|
|
247
|
+
// null before is admitted only by the base reader for a tracked-tree absence.
|
|
248
|
+
if (root === "files_before" &&
|
|
249
|
+
value === null &&
|
|
250
|
+
typeof state.base_sha === "string" &&
|
|
251
|
+
state.base_sha.length > 0)
|
|
172
252
|
continue;
|
|
173
|
-
if (
|
|
253
|
+
if (empty(root === "files" || root === "files_before" ? value : unpack(value)))
|
|
174
254
|
return match[0].trim();
|
|
175
255
|
}
|
|
176
256
|
return undefined;
|
|
177
257
|
}
|
|
178
258
|
export function blockedAskReadings(plan, state, unresolved, note) {
|
|
179
259
|
const blocked = new Map();
|
|
180
|
-
const notePaths = new Set(citedPaths(note ?? ""));
|
|
181
260
|
const noteEmpty = emptyReference(note ?? "", state);
|
|
182
|
-
for (const { id, text } of readingTexts(plan)) {
|
|
183
|
-
const
|
|
184
|
-
const missing = unresolved.find((
|
|
261
|
+
for (const { id, text, about } of readingTexts(plan)) {
|
|
262
|
+
const explicit = new Set(explicitPaths(`${about ?? ""}\n${text}`).map(({ reference }) => reference));
|
|
263
|
+
const missing = unresolved.find((ref) => ref.required !== false &&
|
|
264
|
+
(ref.scope === "global" ||
|
|
265
|
+
ref.scope === id ||
|
|
266
|
+
(ref.scope === undefined && explicit.has(ref.reference))));
|
|
185
267
|
if (missing) {
|
|
186
268
|
blocked.set(id, missing.reason);
|
|
187
269
|
continue;
|
|
188
270
|
}
|
|
189
|
-
const reference = noteEmpty ??
|
|
271
|
+
const reference = noteEmpty ??
|
|
272
|
+
(about ? emptyReference(about, state) : undefined) ??
|
|
273
|
+
emptyReference(text, state);
|
|
190
274
|
if (reference)
|
|
191
|
-
blocked.set(id, `${reference} is empty`);
|
|
275
|
+
blocked.set(id, `${reference} is missing or empty`);
|
|
192
276
|
}
|
|
193
277
|
return blocked;
|
|
194
278
|
}
|
package/dist/core/asks.js
CHANGED
|
@@ -335,8 +335,7 @@ export function readAsks(plan, answers, reversed = {}) {
|
|
|
335
335
|
.join("; ");
|
|
336
336
|
result.push({
|
|
337
337
|
label: r.label,
|
|
338
|
-
|
|
339
|
-
band: "unsure",
|
|
338
|
+
unjudged: true,
|
|
340
339
|
reason: `${reason}; ${r.missing}`,
|
|
341
340
|
uncalibrated: r.uncalibrated,
|
|
342
341
|
});
|
|
@@ -350,7 +349,22 @@ export function readAsks(plan, answers, reversed = {}) {
|
|
|
350
349
|
: undefined;
|
|
351
350
|
result.push({
|
|
352
351
|
label: level ? `${r.label} ${JSON.stringify(level)}` : r.label,
|
|
352
|
+
source: answer.source,
|
|
353
|
+
...(answer.type === "bool" ? { publicResult: answer.p >= 0.5 } : {}),
|
|
353
354
|
answer,
|
|
355
|
+
rawValues: answer.type === "bool"
|
|
356
|
+
? [
|
|
357
|
+
{
|
|
358
|
+
label: "bool",
|
|
359
|
+
value: answer.p >= 0.5,
|
|
360
|
+
probability: { status: "known", value: answer.p },
|
|
361
|
+
},
|
|
362
|
+
]
|
|
363
|
+
: Object.entries(answer.probabilities).map(([label, value]) => ({
|
|
364
|
+
label,
|
|
365
|
+
value: label,
|
|
366
|
+
probability: { status: "known", value },
|
|
367
|
+
})),
|
|
354
368
|
uncalibrated: r.uncalibrated,
|
|
355
369
|
});
|
|
356
370
|
continue;
|
|
@@ -404,13 +418,22 @@ export function readAsks(plan, answers, reversed = {}) {
|
|
|
404
418
|
}
|
|
405
419
|
}
|
|
406
420
|
if (Object.values(r.controls ?? {}).some((id) => answers[id]?.type !== "bool")) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
421
|
+
result.push({
|
|
422
|
+
label: r.label,
|
|
423
|
+
unjudged: true,
|
|
424
|
+
reason: "required coherence control has no valid bool response",
|
|
425
|
+
missing: r.missing,
|
|
426
|
+
});
|
|
427
|
+
continue;
|
|
410
428
|
}
|
|
411
429
|
if (!r.twin && Object.hasOwn(reversed, r.id) && !second) {
|
|
412
|
-
|
|
413
|
-
|
|
430
|
+
result.push({
|
|
431
|
+
label: r.label,
|
|
432
|
+
unjudged: true,
|
|
433
|
+
reason: "required reverse-order control unjudged",
|
|
434
|
+
missing: r.missing,
|
|
435
|
+
});
|
|
436
|
+
continue;
|
|
414
437
|
}
|
|
415
438
|
const firstMerged = merged(original, r.kind === "verify");
|
|
416
439
|
const secondMerged = second
|
|
@@ -422,6 +445,54 @@ export function readAsks(plan, answers, reversed = {}) {
|
|
|
422
445
|
? `${r.label} ${JSON.stringify(r.options[head])}`
|
|
423
446
|
: r.label,
|
|
424
447
|
answer: { ...answer, probabilities: p, choice: head },
|
|
448
|
+
source: answer.source,
|
|
449
|
+
rawValues: Object.entries(original).map(([label, value]) => ({
|
|
450
|
+
label,
|
|
451
|
+
value: label,
|
|
452
|
+
probability: { status: "known", value },
|
|
453
|
+
})),
|
|
454
|
+
reportControls: [
|
|
455
|
+
...members.slice(1).map(([id, label]) => ({
|
|
456
|
+
id,
|
|
457
|
+
label,
|
|
458
|
+
answer: answers[id],
|
|
459
|
+
kind: "cross_check",
|
|
460
|
+
})),
|
|
461
|
+
...(!r.twin && reversed[r.id]
|
|
462
|
+
? [
|
|
463
|
+
{
|
|
464
|
+
id: `${r.id}:reverse`,
|
|
465
|
+
label: "reverse order",
|
|
466
|
+
answer: reversed[r.id],
|
|
467
|
+
kind: "order",
|
|
468
|
+
},
|
|
469
|
+
]
|
|
470
|
+
: []),
|
|
471
|
+
].flatMap(({ id, label, answer: a, kind }) => {
|
|
472
|
+
if (!a || a.type === "unjudged" || !a.source)
|
|
473
|
+
return [];
|
|
474
|
+
return [
|
|
475
|
+
{
|
|
476
|
+
id,
|
|
477
|
+
kind,
|
|
478
|
+
source: a.source,
|
|
479
|
+
outcome: label,
|
|
480
|
+
rawValues: a.type === "bool"
|
|
481
|
+
? [
|
|
482
|
+
{
|
|
483
|
+
label,
|
|
484
|
+
value: a.p >= 0.5,
|
|
485
|
+
probability: { status: "known", value: a.p },
|
|
486
|
+
},
|
|
487
|
+
]
|
|
488
|
+
: Object.entries(a.probabilities).map(([key, value]) => ({
|
|
489
|
+
label: key,
|
|
490
|
+
value: key,
|
|
491
|
+
probability: { status: "known", value },
|
|
492
|
+
})),
|
|
493
|
+
},
|
|
494
|
+
];
|
|
495
|
+
}),
|
|
425
496
|
value: { head, p: mass },
|
|
426
497
|
band,
|
|
427
498
|
reason,
|
package/dist/core/locate.js
CHANGED
|
@@ -11,13 +11,13 @@ export function sectionState(path, goal, sections, outline = false) {
|
|
|
11
11
|
})),
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
export function sectionStateFits(path, goal, sections, outline = false) {
|
|
14
|
+
export function sectionStateFits(path, goal, sections, outline = false, maxChars = STATE_MAX_CHARS) {
|
|
15
15
|
return (sections.length < CHOICE_MAX_OPTIONS &&
|
|
16
16
|
JSON.stringify(sectionState(path, goal, sections, outline)).length <=
|
|
17
|
-
|
|
17
|
+
maxChars);
|
|
18
18
|
}
|
|
19
19
|
/** Every planned block has at least two sections and fits its full-text refinement budget. */
|
|
20
|
-
export function sectionOutline(path, goal, sections) {
|
|
20
|
+
export function sectionOutline(path, goal, sections, maxChars = STATE_MAX_CHARS) {
|
|
21
21
|
const base = JSON.stringify(sectionState(path, goal, [])).length;
|
|
22
22
|
const costs = new Map(sections.map((section) => [
|
|
23
23
|
section,
|
|
@@ -31,16 +31,16 @@ export function sectionOutline(path, goal, sections) {
|
|
|
31
31
|
]));
|
|
32
32
|
const fits = (items) => items.length < CHOICE_MAX_OPTIONS &&
|
|
33
33
|
base + items.reduce((sum, item) => sum + (costs.get(item) ?? 0) + 1, 0) <=
|
|
34
|
-
|
|
34
|
+
maxChars;
|
|
35
35
|
const groups = [];
|
|
36
36
|
let current = [], currentSize = base;
|
|
37
37
|
for (const section of sections) {
|
|
38
38
|
const length = (costs.get(section) ?? 0) + 1;
|
|
39
|
-
if (base + length >
|
|
39
|
+
if (base + length > maxChars)
|
|
40
40
|
return [];
|
|
41
41
|
const owner = section.label.split(".")[0], previousOwner = current.at(-1)?.label.split(".")[0];
|
|
42
42
|
if (current.length &&
|
|
43
|
-
(currentSize + length >
|
|
43
|
+
(currentSize + length > maxChars ||
|
|
44
44
|
current.length + 1 >= CHOICE_MAX_OPTIONS ||
|
|
45
45
|
(current.length >= 2 &&
|
|
46
46
|
section.label.includes(".") &&
|
|
@@ -98,9 +98,9 @@ export function sectionOutline(path, goal, sections) {
|
|
|
98
98
|
}));
|
|
99
99
|
}
|
|
100
100
|
/** JSON-escaped budget, allocated equally then redistributed to larger blocks. */
|
|
101
|
-
export function outlineEvidence(path, goal, blocks) {
|
|
101
|
+
export function outlineEvidence(path, goal, blocks, maxChars = STATE_MAX_CHARS) {
|
|
102
102
|
const overhead = JSON.stringify(sectionState(path, goal, blocks.map((block) => ({ ...block, text: "" })))).length;
|
|
103
|
-
let remaining = Math.max(0,
|
|
103
|
+
let remaining = Math.max(0, maxChars - overhead);
|
|
104
104
|
const allocations = blocks.map(() => 0);
|
|
105
105
|
let active = blocks.map((_, index) => index);
|
|
106
106
|
while (remaining > 0 && active.length) {
|
package/dist/core/output.js
CHANGED
|
@@ -38,6 +38,15 @@ export function buildEnvelope(input) {
|
|
|
38
38
|
.map((control) => control.fact)
|
|
39
39
|
.join("; ");
|
|
40
40
|
for (const item of input.answers ?? []) {
|
|
41
|
+
if (item.unjudged) {
|
|
42
|
+
lines.push({
|
|
43
|
+
type: "unjudged",
|
|
44
|
+
label: item.label,
|
|
45
|
+
reason: item.reason ?? "required group answer missing",
|
|
46
|
+
next: item.missing ?? "inspect the available evidence without a judgment",
|
|
47
|
+
});
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
41
50
|
const value = displayValue(item);
|
|
42
51
|
const initial = item.band ?? (input.policy ?? askBand)(value, item);
|
|
43
52
|
const band = initial === "verdict" && failed ? "unsure" : initial;
|
|
@@ -54,6 +63,14 @@ export function buildEnvelope(input) {
|
|
|
54
63
|
value,
|
|
55
64
|
band,
|
|
56
65
|
uncalibrated: item.uncalibrated ?? false,
|
|
66
|
+
...((item.source ?? item.answer?.source)
|
|
67
|
+
? { source: item.source ?? item.answer?.source }
|
|
68
|
+
: {}),
|
|
69
|
+
...(item.publicResult !== undefined
|
|
70
|
+
? { publicResult: item.publicResult }
|
|
71
|
+
: {}),
|
|
72
|
+
...(item.rawValues ? { rawValues: item.rawValues } : {}),
|
|
73
|
+
...(item.reportControls ? { reportControls: item.reportControls } : {}),
|
|
57
74
|
...(reason ? { reason } : {}),
|
|
58
75
|
...(item.orderDependent !== undefined
|
|
59
76
|
? { orderDependent: item.orderDependent }
|