hoi4-agent-tools 1.1.0 → 2.0.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 +19 -0
- package/README.md +21 -18
- package/dist/hoi4_agent_tools/core/engine.js +12 -0
- package/dist/hoi4_agent_tools/core/workspace.js +31 -3
- package/dist/hoi4_agent_tools/event/algorithms.js +208 -0
- package/dist/hoi4_agent_tools/event/artifact-validation.js +572 -0
- package/dist/hoi4_agent_tools/event/compare.js +413 -0
- package/dist/hoi4_agent_tools/event/graph.js +1586 -0
- package/dist/hoi4_agent_tools/event/index.js +10 -0
- package/dist/hoi4_agent_tools/event/layout.js +218 -0
- package/dist/hoi4_agent_tools/event/limits.js +41 -0
- package/dist/hoi4_agent_tools/event/model.js +2 -0
- package/dist/hoi4_agent_tools/event/queries.js +643 -0
- package/dist/hoi4_agent_tools/event/render.js +443 -0
- package/dist/hoi4_agent_tools/event/scc.js +72 -0
- package/dist/hoi4_agent_tools/event/service.js +1147 -0
- package/dist/hoi4_agent_tools/event/source-analysis.js +1458 -0
- package/dist/hoi4_agent_tools/focus/native-effects.js +3 -1
- package/dist/hoi4_agent_tools/gui/layout.js +122 -27
- package/dist/hoi4_agent_tools/gui/renderer.js +16 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.js +1 -1
- package/dist/hoi4_agent_tools/mcp/server/create.js +4 -2
- package/dist/hoi4_agent_tools/mcp/server/result.js +8 -14
- package/dist/hoi4_agent_tools/mcp/tools/event.js +459 -0
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +6 -3
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +6 -3
- package/dist/hoi4_agent_tools/mcp/tools/map.js +6 -3
- package/dist/hoi4_agent_tools/runtime.js +112 -2
- package/dist/hoi4_agent_tools/schemas/common.js +5 -1
- package/dist/hoi4_agent_tools/schemas/event.js +95 -0
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +1 -0
- package/docs/development.md +10 -0
- package/docs/events.md +113 -0
- package/docs/focus.md +1 -1
- package/docs/gui.md +3 -1
- package/docs/map.md +1 -1
- package/docs/setup.md +10 -7
- package/package.json +7 -3
- package/server.json +3 -3
- package/dist/hoi4_agent_tools/mcp/tools/mods.js +0 -54
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Source: the official HOI4 1.19.2 effects documentation reviewed 2026-07-10.
|
|
6
6
|
* Keep this list synchronized during compatibility reviews.
|
|
7
7
|
*/
|
|
8
|
-
export const
|
|
8
|
+
export const nativeEffectKeys = new Set([
|
|
9
9
|
'activate_advisor',
|
|
10
10
|
'activate_decision',
|
|
11
11
|
'activate_mission',
|
|
@@ -560,3 +560,5 @@ export const nativeFocusEffectKeys = new Set([
|
|
|
560
560
|
'while_loop_effect',
|
|
561
561
|
'white_peace',
|
|
562
562
|
]);
|
|
563
|
+
/** Backward-compatible focus-domain name for the shared official effect catalog. */
|
|
564
|
+
export const nativeFocusEffectKeys = nativeEffectKeys;
|
|
@@ -40,8 +40,55 @@ function scalarString(value) {
|
|
|
40
40
|
return String(value);
|
|
41
41
|
return undefined;
|
|
42
42
|
}
|
|
43
|
+
const defaultTextColour = '#f5f2e8';
|
|
44
|
+
const hoi4TextColours = {
|
|
45
|
+
B: '#4aa3df',
|
|
46
|
+
C: '#55d6d6',
|
|
47
|
+
G: '#56b870',
|
|
48
|
+
H: '#e7b454',
|
|
49
|
+
L: '#9aa7b3',
|
|
50
|
+
M: '#d16bd4',
|
|
51
|
+
O: '#e58b3c',
|
|
52
|
+
R: '#e05a5a',
|
|
53
|
+
T: '#4bc6b9',
|
|
54
|
+
W: defaultTextColour,
|
|
55
|
+
Y: '#f1c75b',
|
|
56
|
+
b: '#4aa3df',
|
|
57
|
+
g: '#87939e',
|
|
58
|
+
};
|
|
43
59
|
function visibleHoiText(value) {
|
|
44
|
-
|
|
60
|
+
const characters = [];
|
|
61
|
+
const colours = [];
|
|
62
|
+
let colour;
|
|
63
|
+
let hasColourMarkup = false;
|
|
64
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
65
|
+
const character = value[index] ?? '';
|
|
66
|
+
const next = value[index + 1];
|
|
67
|
+
if (character === '\u00a7' && next !== undefined && /^[A-Za-z0-9!]$/u.test(next)) {
|
|
68
|
+
hasColourMarkup = true;
|
|
69
|
+
colour = next === '!' ? undefined : hoi4TextColours[next];
|
|
70
|
+
index += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (character === '\u00a3') {
|
|
74
|
+
let cursor = index + 1;
|
|
75
|
+
while (cursor < value.length && !/[\s\u00a3]/u.test(value[cursor] ?? ''))
|
|
76
|
+
cursor += 1;
|
|
77
|
+
characters.push('\u25c6');
|
|
78
|
+
colours.push(colour);
|
|
79
|
+
index = cursor - 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (character === '\\' && next === 'n') {
|
|
83
|
+
characters.push('\n');
|
|
84
|
+
colours.push(colour);
|
|
85
|
+
index += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
characters.push(character);
|
|
89
|
+
colours.push(colour);
|
|
90
|
+
}
|
|
91
|
+
return { text: characters.join(''), colours, hasColourMarkup };
|
|
45
92
|
}
|
|
46
93
|
function scalarNumber(value, reference) {
|
|
47
94
|
if (typeof value === 'number')
|
|
@@ -167,13 +214,15 @@ function resolveTokenText(value, scenario, localisation, rowValues) {
|
|
|
167
214
|
const key = match[1] ?? '';
|
|
168
215
|
const replacement = rowValues?.[key] ?? scenario.variables[key] ?? scenario.scriptedGui[key];
|
|
169
216
|
if (replacement === undefined) {
|
|
170
|
-
unresolved.push(
|
|
171
|
-
return
|
|
217
|
+
unresolved.push(match[0]);
|
|
218
|
+
return '[X]';
|
|
172
219
|
}
|
|
173
220
|
return String(replacement);
|
|
174
221
|
}, 'GUI variable substitution');
|
|
175
222
|
text = replaceTextBounded(text, /\[([A-Za-z0-9_.:-]+)\]/gu, (match) => {
|
|
176
223
|
const key = match[1] ?? '';
|
|
224
|
+
if (key === 'X')
|
|
225
|
+
return match[0];
|
|
177
226
|
const countryKey = key.replace(/^(?:ROOT|This)\./u, '');
|
|
178
227
|
const replacement = rowValues?.[countryKey] ??
|
|
179
228
|
scenario.country?.[countryKey] ??
|
|
@@ -181,16 +230,16 @@ function resolveTokenText(value, scenario, localisation, rowValues) {
|
|
|
181
230
|
scenario.scriptedGui[key];
|
|
182
231
|
if (replacement === undefined) {
|
|
183
232
|
unresolved.push(match[0]);
|
|
184
|
-
return
|
|
233
|
+
return '[X]';
|
|
185
234
|
}
|
|
186
235
|
return String(replacement);
|
|
187
236
|
}, 'GUI scope substitution');
|
|
188
237
|
return { text, unresolved: [...new Set(unresolved)].sort(), missingLocalisation };
|
|
189
238
|
}
|
|
190
|
-
function wrapText(catalog, fontName, text, fontSize, maximumWidth, work) {
|
|
191
|
-
const paragraphs = text.replaceAll('\\n', '\n').split('\n');
|
|
239
|
+
function wrapText(catalog, fontName, text, colours, fontSize, maximumWidth, work) {
|
|
192
240
|
const lines = [];
|
|
193
241
|
const widths = [];
|
|
242
|
+
const colourLines = [];
|
|
194
243
|
const missingGlyphs = new Set();
|
|
195
244
|
const retainMissingGlyphs = (values) => {
|
|
196
245
|
for (const value of values) {
|
|
@@ -201,12 +250,44 @@ function wrapText(catalog, fontName, text, fontSize, maximumWidth, work) {
|
|
|
201
250
|
};
|
|
202
251
|
let lineHeight = fontSize * 1.2;
|
|
203
252
|
let metricSource = 'approximation';
|
|
204
|
-
|
|
253
|
+
const commitLine = (words) => {
|
|
254
|
+
const characters = [];
|
|
255
|
+
const lineColours = [];
|
|
256
|
+
for (const [index, word] of words.entries()) {
|
|
257
|
+
if (index > 0) {
|
|
258
|
+
characters.push(' ');
|
|
259
|
+
lineColours.push(lineColours.at(-1) ?? word.colours[0]);
|
|
260
|
+
}
|
|
261
|
+
characters.push(word.text);
|
|
262
|
+
lineColours.push(...word.colours);
|
|
263
|
+
}
|
|
264
|
+
const committedText = characters.join('');
|
|
265
|
+
work.spendTextLayout('committed text line measurement');
|
|
266
|
+
const committed = catalog.measureText(fontName, committedText, fontSize);
|
|
267
|
+
lines.push(committedText);
|
|
268
|
+
widths.push(committed.width);
|
|
269
|
+
colourLines.push(lineColours);
|
|
270
|
+
retainMissingGlyphs(committed.missingGlyphs);
|
|
271
|
+
lineHeight = committed.lineHeight;
|
|
272
|
+
metricSource = committed.source;
|
|
273
|
+
};
|
|
274
|
+
let paragraphStart = 0;
|
|
275
|
+
for (let paragraphEnd = 0; paragraphEnd <= text.length; paragraphEnd += 1) {
|
|
276
|
+
if (paragraphEnd < text.length && text[paragraphEnd] !== '\n')
|
|
277
|
+
continue;
|
|
205
278
|
work.spendTextLayout('text paragraph layout');
|
|
206
|
-
const
|
|
279
|
+
const paragraph = text.slice(paragraphStart, paragraphEnd);
|
|
280
|
+
const paragraphColours = colours.slice(paragraphStart, paragraphEnd);
|
|
281
|
+
const words = [...paragraph.matchAll(/\S+/gu)].map((match) => {
|
|
282
|
+
const start = match.index;
|
|
283
|
+
const word = match[0];
|
|
284
|
+
return { text: word, colours: paragraphColours.slice(start, start + word.length) };
|
|
285
|
+
});
|
|
286
|
+
paragraphStart = paragraphEnd + 1;
|
|
207
287
|
if (words.length === 0) {
|
|
208
288
|
lines.push('');
|
|
209
289
|
widths.push(0);
|
|
290
|
+
colourLines.push([]);
|
|
210
291
|
continue;
|
|
211
292
|
}
|
|
212
293
|
let currentWords = [];
|
|
@@ -215,22 +296,17 @@ function wrapText(catalog, fontName, text, fontSize, maximumWidth, work) {
|
|
|
215
296
|
let previousWordWidth = 0;
|
|
216
297
|
for (const word of words) {
|
|
217
298
|
work.spendTextLayout('text word measurement');
|
|
218
|
-
const measuredWord = catalog.measureText(fontName, word, fontSize);
|
|
299
|
+
const measuredWord = catalog.measureText(fontName, word.text, fontSize);
|
|
219
300
|
retainMissingGlyphs(measuredWord.missingGlyphs);
|
|
220
301
|
let candidateWidth = measuredWord.width;
|
|
221
302
|
if (previousWord !== undefined) {
|
|
222
303
|
work.spendTextLayout('text word-boundary measurement');
|
|
223
|
-
const boundary = catalog.measureText(fontName, `${previousWord} ${word}`, fontSize);
|
|
304
|
+
const boundary = catalog.measureText(fontName, `${previousWord} ${word.text}`, fontSize);
|
|
224
305
|
retainMissingGlyphs(boundary.missingGlyphs);
|
|
225
306
|
candidateWidth = currentWidth + boundary.width - previousWordWidth;
|
|
226
307
|
}
|
|
227
308
|
if (maximumWidth > 0 && candidateWidth > maximumWidth && currentWords.length > 0) {
|
|
228
|
-
|
|
229
|
-
work.spendTextLayout('committed text line measurement');
|
|
230
|
-
const committed = catalog.measureText(fontName, committedText, fontSize);
|
|
231
|
-
lines.push(committedText);
|
|
232
|
-
widths.push(committed.width);
|
|
233
|
-
retainMissingGlyphs(committed.missingGlyphs);
|
|
309
|
+
commitLine(currentWords);
|
|
234
310
|
currentWords = [word];
|
|
235
311
|
currentWidth = measuredWord.width;
|
|
236
312
|
}
|
|
@@ -238,26 +314,40 @@ function wrapText(catalog, fontName, text, fontSize, maximumWidth, work) {
|
|
|
238
314
|
currentWords.push(word);
|
|
239
315
|
currentWidth = candidateWidth;
|
|
240
316
|
}
|
|
241
|
-
previousWord = word;
|
|
317
|
+
previousWord = word.text;
|
|
242
318
|
previousWordWidth = measuredWord.width;
|
|
243
319
|
}
|
|
244
|
-
|
|
245
|
-
work.spendTextLayout('committed text line measurement');
|
|
246
|
-
const committed = catalog.measureText(fontName, committedText, fontSize);
|
|
247
|
-
lines.push(committedText);
|
|
248
|
-
widths.push(committed.width);
|
|
249
|
-
retainMissingGlyphs(committed.missingGlyphs);
|
|
250
|
-
lineHeight = committed.lineHeight;
|
|
251
|
-
metricSource = committed.source;
|
|
320
|
+
commitLine(currentWords);
|
|
252
321
|
}
|
|
253
322
|
return {
|
|
254
323
|
lines,
|
|
255
324
|
widths,
|
|
325
|
+
colourLines,
|
|
256
326
|
lineHeight,
|
|
257
327
|
metricSource,
|
|
258
328
|
missingGlyphs: [...missingGlyphs].sort((a, b) => a - b),
|
|
259
329
|
};
|
|
260
330
|
}
|
|
331
|
+
function colourRunsForLine(catalog, fontName, line, colours, fontSize, work) {
|
|
332
|
+
if (!colours.some((colour) => colour !== undefined) || line.length === 0)
|
|
333
|
+
return [];
|
|
334
|
+
const runs = [];
|
|
335
|
+
let start = 0;
|
|
336
|
+
let offsetX = 0;
|
|
337
|
+
while (start < line.length) {
|
|
338
|
+
const colour = colours[start] ?? defaultTextColour;
|
|
339
|
+
let end = start + 1;
|
|
340
|
+
while (end < line.length && (colours[end] ?? defaultTextColour) === colour)
|
|
341
|
+
end += 1;
|
|
342
|
+
const text = line.slice(start, end);
|
|
343
|
+
work.spendTextLayout('localisation colour-run measurement');
|
|
344
|
+
const width = catalog.measureText(fontName, text, fontSize).width;
|
|
345
|
+
runs.push({ text, colour, offsetX, width });
|
|
346
|
+
offsetX += width;
|
|
347
|
+
start = end;
|
|
348
|
+
}
|
|
349
|
+
return runs;
|
|
350
|
+
}
|
|
261
351
|
function frameFor(element, sprite, scenario) {
|
|
262
352
|
const frameCount = Math.max(1, sprite.frameCount);
|
|
263
353
|
const selected = scenario.selectedFrames[element.name] ?? scenario.selectedFrames[sprite.name];
|
|
@@ -439,7 +529,8 @@ async function layoutElement(definition, parentRect, inheritedClip, parentScale,
|
|
|
439
529
|
displayText = `${displayText} — ${displayText} — ${displayText}`;
|
|
440
530
|
if (state === 'missing-localisation')
|
|
441
531
|
displayText = `\u00a7R${rawText}_MISSING\u00a7!`;
|
|
442
|
-
|
|
532
|
+
const visibleText = visibleHoiText(displayText);
|
|
533
|
+
displayText = visibleText.text;
|
|
443
534
|
context.work.admitText(displayText, `GUI text for ${definition.name}`);
|
|
444
535
|
const fontName = scalarString(property(definition.attributes, 'font', 'buttonFont'));
|
|
445
536
|
const resolvedFontMetrics = catalog.resolvedFontMetrics(fontName);
|
|
@@ -450,10 +541,13 @@ async function layoutElement(definition, parentRect, inheritedClip, parentScale,
|
|
|
450
541
|
16;
|
|
451
542
|
const maxWidth = (scalarNumber(property(definition.attributes, 'maxWidth'), width / scale) ?? width / scale) *
|
|
452
543
|
scale;
|
|
453
|
-
const wrapped = wrapText(catalog, fontName, displayText, fontSize * scale, maxWidth, context.work);
|
|
544
|
+
const wrapped = wrapText(catalog, fontName, displayText, visibleText.colours, fontSize * scale, maxWidth, context.work);
|
|
454
545
|
const glyphLines = [];
|
|
455
546
|
for (const line of wrapped.lines)
|
|
456
547
|
glyphLines.push(await catalog.shapeText(fontName, line, fontSize * scale));
|
|
548
|
+
const colourRuns = visibleText.hasColourMarkup
|
|
549
|
+
? wrapped.lines.map((line, index) => colourRunsForLine(catalog, fontName, line, wrapped.colourLines[index] ?? [], fontSize * scale, context.work))
|
|
550
|
+
: undefined;
|
|
457
551
|
const maximumLineWidth = wrapped.widths.reduce((maximum, value) => Math.max(maximum, value), 0);
|
|
458
552
|
if (width === 0)
|
|
459
553
|
width = maximumLineWidth;
|
|
@@ -476,6 +570,7 @@ async function layoutElement(definition, parentRect, inheritedClip, parentScale,
|
|
|
476
570
|
overflowX: width > 0 && measuredWidth > width + 0.01,
|
|
477
571
|
overflowY: height > 0 && measuredHeight > height + 0.01,
|
|
478
572
|
unresolvedTokens: resolved.unresolved,
|
|
573
|
+
...(colourRuns === undefined ? {} : { colourRuns }),
|
|
479
574
|
};
|
|
480
575
|
if (wrapped.metricSource === 'approximation')
|
|
481
576
|
addFidelity(fidelity, 'approximated', 'font_metrics', `No supplied font metrics for ${fontName ?? '<default>'}.`, definition);
|
|
@@ -86,6 +86,22 @@ function renderText(element, toolText) {
|
|
|
86
86
|
(glyphLine === undefined || glyphLine.source === 'deterministic-fallback'
|
|
87
87
|
? text.lineHeight * 0.8
|
|
88
88
|
: glyphLine.baseline);
|
|
89
|
+
const colourRuns = text.colourRuns?.[index] ?? [];
|
|
90
|
+
if (colourRuns.length > 0) {
|
|
91
|
+
const runWidth = colourRuns.reduce((maximum, run) => Math.max(maximum, run.offsetX + run.width), 0);
|
|
92
|
+
const horizontalScale = runWidth > 0 && width > 0 ? width / runWidth : 1;
|
|
93
|
+
return `<g data-hoi4-colour-runs="true">${colourRuns
|
|
94
|
+
.map((run) => toolText.render(run.text, {
|
|
95
|
+
x: originX + run.offsetX * horizontalScale,
|
|
96
|
+
y: baseline,
|
|
97
|
+
fontSize: text.fontSize,
|
|
98
|
+
fill: run.colour,
|
|
99
|
+
stroke: '#12151a',
|
|
100
|
+
strokeWidth: 0.6,
|
|
101
|
+
...(run.width > 0 ? { targetWidth: run.width * horizontalScale } : {}),
|
|
102
|
+
}))
|
|
103
|
+
.join('')}</g>`;
|
|
104
|
+
}
|
|
89
105
|
if (glyphLine?.source === 'fontkit-path') {
|
|
90
106
|
const horizontalScale = glyphLine.width > 0 && width > 0 ? width / glyphLine.width : 1;
|
|
91
107
|
return `<g data-font-sha256="${glyphLine.sourceHash}">${glyphLine.glyphs
|
|
@@ -6,7 +6,7 @@ const artifactChunkMetadataKey = 'io.github.klimpaskov/hoi4-agent-tools.artifact
|
|
|
6
6
|
const fatalUtf8Decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
7
7
|
const artifactResourceMetadata = {
|
|
8
8
|
title: 'HOI4 review artifact',
|
|
9
|
-
description: 'Complete plans, diagnostics, layouts, previews, and diffs. Reads accept zero-based byte offset and positive byte length query selectors; each response reports its exact byte range and continuation URI in _meta.',
|
|
9
|
+
description: 'Complete plans, diagnostics, layouts, previews, event graphs, routes, state and scope reports, comparisons, and diffs. Reads accept zero-based byte offset and positive byte length query selectors; each response reports its exact byte range and continuation URI in _meta.',
|
|
10
10
|
_meta: {
|
|
11
11
|
[artifactChunkMetadataKey]: {
|
|
12
12
|
version: 1,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { PACKAGE_NAME, PACKAGE_VERSION } from '../../version.js';
|
|
3
|
-
import { registerModsTool } from '../tools/mods.js';
|
|
4
3
|
import { registerFocusTools } from '../tools/focus.js';
|
|
5
4
|
import { registerGuiTools } from '../tools/gui.js';
|
|
6
5
|
import { registerMapTools } from '../tools/map.js';
|
|
6
|
+
import { registerEventTools } from '../tools/event.js';
|
|
7
7
|
import { registerMcpResources } from '../resources/register.js';
|
|
8
|
+
export const SERVER_INSTRUCTIONS = 'The workspace defaults to the mod containing the MCP working directory; pass workspaceId only when selecting another configured mod. Use hoi4.event_inspect for scan, roots, trace, explain_path, state_flow, lint, and impact; hoi4.event_render and hoi4.event_compare return linked evidence. JSON resources are authoritative. Event tools are read-only. Only focus, GUI, and map rewrite tools edit configured mods.';
|
|
8
9
|
export function createMcpServer(engine, context = {}) {
|
|
9
10
|
const server = new McpServer({
|
|
10
11
|
name: PACKAGE_NAME,
|
|
@@ -12,11 +13,12 @@ export function createMcpServer(engine, context = {}) {
|
|
|
12
13
|
websiteUrl: 'https://github.com/klimPaskov/hoi4-agent-tools',
|
|
13
14
|
}, {
|
|
14
15
|
capabilities: { logging: {} },
|
|
16
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
15
17
|
});
|
|
16
|
-
registerModsTool(server, engine, context);
|
|
17
18
|
registerFocusTools(server, engine, context);
|
|
18
19
|
registerGuiTools(server, engine, context);
|
|
19
20
|
registerMapTools(server, engine, context);
|
|
21
|
+
registerEventTools(server, engine, context);
|
|
20
22
|
registerMcpResources(server, engine, context);
|
|
21
23
|
return server;
|
|
22
24
|
}
|
|
@@ -42,24 +42,18 @@ const emptyToolDataSchema = z.object({}).strict();
|
|
|
42
42
|
const compactPublicOperationResultSchema = z
|
|
43
43
|
.object({
|
|
44
44
|
status: z.enum(['ok', 'blocked', 'error']),
|
|
45
|
-
code: z.string()
|
|
46
|
-
workspaceId: z.string()
|
|
47
|
-
filesScanned: z.array(z.
|
|
48
|
-
proposedFiles: z.array(z.
|
|
49
|
-
changedFiles: z.array(z.
|
|
45
|
+
code: z.string(),
|
|
46
|
+
workspaceId: z.string(),
|
|
47
|
+
filesScanned: z.array(z.unknown()).max(MAX_INLINE_FILES_SCANNED),
|
|
48
|
+
proposedFiles: z.array(z.unknown()).max(MAX_INLINE_PROPOSED_FILES),
|
|
49
|
+
changedFiles: z.array(z.unknown()).max(MAX_INLINE_CHANGED_FILES),
|
|
50
50
|
diagnostics: z.array(z.unknown()).max(MAX_INLINE_DIAGNOSTICS),
|
|
51
51
|
artifacts: z.array(z.unknown()).max(MAX_INLINE_ARTIFACT_LINKS),
|
|
52
|
-
validation: z
|
|
53
|
-
.object({
|
|
54
|
-
passed: z.boolean(),
|
|
55
|
-
checks: z.array(z.unknown()).max(MAX_INLINE_VALIDATION_CHECKS),
|
|
56
|
-
})
|
|
57
|
-
.strict(),
|
|
52
|
+
validation: z.unknown(),
|
|
58
53
|
blockers: z.array(z.unknown()).max(MAX_INLINE_BLOCKERS),
|
|
59
|
-
data: z.
|
|
54
|
+
data: z.object({}).catchall(z.unknown()),
|
|
60
55
|
})
|
|
61
|
-
.strict()
|
|
62
|
-
.describe('Stable operation envelope; data is tool-specific and artifacts link bulk evidence.');
|
|
56
|
+
.strict();
|
|
63
57
|
/**
|
|
64
58
|
* Advertise one compact operation envelope while retaining exact per-tool runtime validation.
|
|
65
59
|
* The generic operation-result schema remains available for stored envelopes.
|