hoi4-agent-tools 1.0.0 → 1.1.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.
@@ -8,6 +8,7 @@ import { GuiHelperDocumentSchema, GuiPreviewScenarioSchema, GUI_TEXT_PACKAGE_MAX
8
8
  import { workspaceIdSchema, workspaceRelativePathSchema } from '../../schemas/common.js';
9
9
  import { PACKAGE_VERSION } from '../../version.js';
10
10
  import { requireServerScope } from '../server/base-tools.js';
11
+ import { compactValidatedInputSchema } from '../server/context-schemas.js';
11
12
  import { autonomousFailureContext, autonomousResultArtifacts, executePlannedTransaction, } from '../server/transaction-execution.js';
12
13
  import { indexSkippedSourceSchema, nonNegativeIntegerSchema, sha256Schema, } from '../server/output-schemas.js';
13
14
  import { progressReporter } from '../server/progress.js';
@@ -88,19 +89,21 @@ const previewStateSchema = z.enum([
88
89
  'long-text',
89
90
  'missing-localisation',
90
91
  ]);
92
+ const compactGuiScenarioSchema = compactValidatedInputSchema(GuiPreviewScenarioSchema, `Complete GUI preview scenario: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/gui.md`);
93
+ const compactGuiHelperSchema = compactValidatedInputSchema(GuiHelperDocumentSchema, `Complete GUI helper document: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/gui.md`);
91
94
  const guiBaseInput = z
92
95
  .object({
93
96
  workspaceId: workspaceIdSchema,
94
97
  windowName: z.string().min(1).max(256),
95
- scenario: GuiPreviewScenarioSchema,
98
+ scenario: compactGuiScenarioSchema,
96
99
  })
97
100
  .strict();
98
101
  const guiInspectInput = z
99
102
  .object({
100
103
  workspaceId: workspaceIdSchema,
101
104
  windowName: z.string().min(1).max(256).optional(),
102
- scenario: GuiPreviewScenarioSchema.optional(),
103
- relatedScenarios: z.array(GuiPreviewScenarioSchema).max(32).optional(),
105
+ scenario: compactGuiScenarioSchema.optional(),
106
+ relatedScenarios: z.array(compactGuiScenarioSchema).max(32).optional(),
104
107
  })
105
108
  .strict()
106
109
  .superRefine((value, context) => {
@@ -142,7 +145,7 @@ export function registerGuiTools(server, engine, context) {
142
145
  const studio = new ScriptedGuiStudio(engine);
143
146
  server.registerTool('hoi4.gui_inspect', {
144
147
  title: 'Inspect scripted GUI',
145
- description: 'Inspect GUI, GFX, scripted logic, localisation, sprites, fonts, interactions, and animation sources for creation and cleanup. Provide a window scenario to include visual, state, click, AI, and cost diagnostics.',
148
+ description: 'Inspect GUI, GFX, script, localisation, and animation sources. Window/scenario selectors add offline visual and interaction diagnostics.',
146
149
  inputSchema: guiInspectInput,
147
150
  outputSchema: guiScanOutputSchema,
148
151
  annotations: artifactProducing,
@@ -315,7 +318,7 @@ export function registerGuiTools(server, engine, context) {
315
318
  .extend({
316
319
  states: z.array(previewStateSchema).max(14).optional(),
317
320
  resolutions: z.array(resolutionSchema).min(1).max(16).optional(),
318
- comparisonScenario: GuiPreviewScenarioSchema.optional(),
321
+ comparisonScenario: compactGuiScenarioSchema.optional(),
319
322
  })
320
323
  .strict()
321
324
  .superRefine(({ scenario, states, resolutions }, context) => {
@@ -351,23 +354,23 @@ export function registerGuiTools(server, engine, context) {
351
354
  });
352
355
  server.registerTool('hoi4.gui_render', {
353
356
  title: 'Render scripted GUI artifacts',
354
- description: 'Render deterministic full, cropped, annotated, click, hierarchy, state, resolution, comparison, and fidelity artifacts for GUI creation and cleanup review.',
357
+ description: 'Render deterministic offline review artifacts for one GUI window and scenario.',
355
358
  inputSchema: renderInput,
356
359
  outputSchema: guiRenderOutputSchema,
357
360
  annotations: artifactProducing,
358
361
  }, (input, extra) => render(input, extra, 'GUI_RENDERED'));
359
362
  server.registerTool('hoi4.gui_rewrite', {
360
363
  title: 'Create or clean up scripted GUI',
361
- description: 'Create or replace a bounded scripted-GUI text package, or clean up one mod-owned .gui with exact patches. Source/helper mode accepts a main .gui plus additional .gui, .gfx, common/scripted_guis .txt, and localisation .yml source; binary art is referenced by those files, not uploaded. The complete package is validated, rendered, and applied in one call.',
364
+ description: 'Apply one validated source, helper, or exact-patch GUI package. Text dependencies use additionalFiles; binary art stays workspace-referenced.',
362
365
  inputSchema: z
363
366
  .object({
364
367
  mode: z.enum(['source', 'helpers', 'patches']),
365
368
  workspaceId: workspaceIdSchema,
366
369
  relativePath: workspaceRelativePathSchema,
367
370
  windowName: z.string().min(1).max(256),
368
- scenario: GuiPreviewScenarioSchema,
371
+ scenario: compactGuiScenarioSchema,
369
372
  source: z.string().max(SOURCE_MAX_BYTES).optional(),
370
- helper: GuiHelperDocumentSchema.optional(),
373
+ helper: compactGuiHelperSchema.optional(),
371
374
  additionalFiles: z
372
375
  .array(z
373
376
  .object({
@@ -7,6 +7,7 @@ import { workspaceIdSchema } from '../../schemas/common.js';
7
7
  import { mapAllocationRequestSchema, mapOperationSchema } from '../../schemas/map.js';
8
8
  import { PACKAGE_VERSION } from '../../version.js';
9
9
  import { requireServerScope } from '../server/base-tools.js';
10
+ import { compactValidatedInputSchema } from '../server/context-schemas.js';
10
11
  import { autonomousFailureContext, autonomousResultArtifacts, executePlannedTransaction, } from '../server/transaction-execution.js';
11
12
  import { allocationEvidenceSchema, bitmapRenderHashesSchema, nonNegativeIntegerSchema, operationBlockerDataSchema, sha256Schema, } from '../server/output-schemas.js';
12
13
  import { progressReporter } from '../server/progress.js';
@@ -235,6 +236,7 @@ const mapOverlaySchema = z.enum([
235
236
  'unit-positions',
236
237
  'weather-positions',
237
238
  ]);
239
+ const compactMapOperationSchema = compactValidatedInputSchema(mapOperationSchema, `Complete map operation: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/map.md`);
238
240
  function validationSummary(validation) {
239
241
  return { passed: validation.passed, checks: validation.checks };
240
242
  }
@@ -346,7 +348,7 @@ export function registerMapTools(server, engine, context) {
346
348
  const nudger = new AgentNudger(engine);
347
349
  server.registerTool('hoi4.map_inspect', {
348
350
  title: 'Inspect HOI4 map',
349
- description: 'Inspect map geometry and records for creation and cleanup, validate the complete map, return selected province/state/region details, preview requested free IDs and province colors, and export exact canonical province row runs when provinceIds are supplied.',
351
+ description: 'Validate the map and write linked inspection evidence. ID selectors return bounded records; provinceIds also export exact row-run geometry.',
350
352
  inputSchema: z
351
353
  .object({
352
354
  workspaceId: workspaceIdSchema,
@@ -499,7 +501,7 @@ export function registerMapTools(server, engine, context) {
499
501
  });
500
502
  server.registerTool('hoi4.map_render', {
501
503
  title: 'Render map inspection artifacts',
502
- description: 'Render deterministic PNG, JSON, and pan-and-zoom HTML artifacts for map creation and cleanup review, with one semantic layer and bounded overlays.',
504
+ description: 'Render deterministic offline map artifacts for one layer and bounded overlays.',
503
505
  inputSchema: z
504
506
  .object({
505
507
  workspaceId: workspaceIdSchema,
@@ -540,11 +542,11 @@ export function registerMapTools(server, engine, context) {
540
542
  });
541
543
  server.registerTool('hoi4.map_rewrite', {
542
544
  title: 'Create or clean up map content',
543
- description: 'Create or clean up states, provinces, regions, adjacencies, networks, positions, locators, and localisation with exact declarative operations, apply them in one call, and return pixel and semantic review artifacts.',
545
+ description: 'Apply validated declarative map operations in one call and return linked pixel and semantic evidence.',
544
546
  inputSchema: z
545
547
  .object({
546
548
  workspaceId: workspaceIdSchema,
547
- operations: z.array(mapOperationSchema).min(1).max(100),
549
+ operations: z.array(compactMapOperationSchema).min(1).max(100),
548
550
  diffScale: z.number().int().min(1).max(16).optional(),
549
551
  })
550
552
  .strict(),
@@ -20,7 +20,7 @@ const modsOutputSchema = strictOperationResultSchema(z
20
20
  export function registerModsTool(server, engine, context) {
21
21
  server.registerTool('hoi4.mods', {
22
22
  title: 'List available HOI4 mods',
23
- description: 'List the configured HOI4 mods available for focus-tree, GUI, and map inspection, rendering, creation, and cleanup.',
23
+ description: 'List configured HOI4 mod workspaces, optionally selecting one by ID.',
24
24
  inputSchema: z.object({ workspaceId: z.string().min(1).max(64).optional() }).strict(),
25
25
  outputSchema: modsOutputSchema,
26
26
  annotations: {
@@ -32,6 +32,14 @@ const prerequisitesSchema = z
32
32
  .max(10_000),
33
33
  })
34
34
  .strict();
35
+ const automaticPositionSchema = z
36
+ .object({
37
+ mode: z.literal('auto'),
38
+ pinned: z.literal(false),
39
+ preferredX: z.number().int().min(-100_000).max(100_000).optional(),
40
+ preferredY: z.number().int().min(-100_000).max(100_000).optional(),
41
+ })
42
+ .strict();
35
43
  const positionSchema = z.discriminatedUnion('mode', [
36
44
  z
37
45
  .object({
@@ -50,14 +58,7 @@ const positionSchema = z.discriminatedUnion('mode', [
50
58
  pinned: z.boolean(),
51
59
  })
52
60
  .strict(),
53
- z
54
- .object({
55
- mode: z.literal('auto'),
56
- pinned: z.literal(false),
57
- preferredX: z.number().int().min(-100_000).max(100_000).optional(),
58
- preferredY: z.number().int().min(-100_000).max(100_000).optional(),
59
- })
60
- .strict(),
61
+ automaticPositionSchema,
61
62
  ]);
62
63
  const routeLockSchema = z
63
64
  .object({
@@ -305,6 +306,7 @@ export const focusPlanningSidecarSchema = z
305
306
  aiStrategyIds: z.array(z.string()),
306
307
  payoff: z.string().optional(),
307
308
  terminalKind: focusNodeSchema.shape.terminalKind,
309
+ autoPosition: automaticPositionSchema.optional(),
308
310
  })
309
311
  .strict()),
310
312
  })
@@ -317,12 +319,65 @@ export const focusLayoutDecisionSchema = z
317
319
  'relative',
318
320
  'placed',
319
321
  'moved_for_collision',
322
+ 'moved_for_spacing',
320
323
  'moved_for_mutual_exclusion',
324
+ 'moved_for_parent_order',
325
+ 'moved_for_lane_bounds',
321
326
  'moved_to_reduce_crossings',
322
327
  ]),
323
328
  message: z.string(),
324
329
  })
325
330
  .strict();
331
+ export const focusLayoutMetricsSchema = z
332
+ .object({
333
+ bounds: z
334
+ .object({
335
+ minimumX: z.number().int(),
336
+ maximumX: z.number().int(),
337
+ minimumY: z.number().int(),
338
+ maximumY: z.number().int(),
339
+ columnSpan: z.number().int().nonnegative(),
340
+ rowSpan: z.number().int().nonnegative(),
341
+ columnCount: z.number().int().nonnegative(),
342
+ rowCount: z.number().int().nonnegative(),
343
+ })
344
+ .strict(),
345
+ connectors: z
346
+ .object({
347
+ count: z.number().int().nonnegative(),
348
+ crossingCount: z.number().int().nonnegative(),
349
+ nodeIntersectionCount: z.number().int().nonnegative(),
350
+ longConnectorCount: z.number().int().nonnegative(),
351
+ totalHorizontalSpan: z.number().int().nonnegative(),
352
+ maximumHorizontalSpan: z.number().int().nonnegative(),
353
+ totalVerticalSpan: z.number().int().nonnegative(),
354
+ maximumVerticalSpan: z.number().int().nonnegative(),
355
+ totalManhattanSpan: z.number().int().nonnegative(),
356
+ maximumManhattanSpan: z.number().int().nonnegative(),
357
+ })
358
+ .strict(),
359
+ symmetry: z
360
+ .object({
361
+ siblingCohortCount: z.number().int().nonnegative(),
362
+ asymmetricSiblingCohortCount: z.number().int().nonnegative(),
363
+ totalSiblingDeviation: z.number().int().nonnegative(),
364
+ maximumSiblingDeviation: z.number().int().nonnegative(),
365
+ offAnchorSiblingCohortCount: z.number().int().nonnegative(),
366
+ totalSiblingAnchorDeviation: z.number().int().nonnegative(),
367
+ maximumSiblingAnchorDeviation: z.number().int().nonnegative(),
368
+ boundingCenterOffsetTwice: z.number().int().nonnegative(),
369
+ })
370
+ .strict(),
371
+ spacing: z
372
+ .object({
373
+ requiredSameRowSpacing: z.number().int().positive(),
374
+ sameRowPairCount: z.number().int().nonnegative(),
375
+ tooCloseSameRowPairCount: z.number().int().nonnegative(),
376
+ minimumSameRowSpacing: z.number().int().nonnegative(),
377
+ })
378
+ .strict(),
379
+ })
380
+ .strict();
326
381
  export const focusLayoutSchema = z
327
382
  .object({
328
383
  treeId: z.string(),
@@ -338,6 +393,7 @@ export const focusLayoutSchema = z
338
393
  .strict()),
339
394
  decisions: z.array(focusLayoutDecisionSchema),
340
395
  diagnostics: z.array(z.unknown()),
396
+ metrics: focusLayoutMetricsSchema.optional(),
341
397
  layoutHash: z.string().regex(/^[a-f0-9]{64}$/u),
342
398
  })
343
399
  .strict();
@@ -1,6 +1,6 @@
1
1
  import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
2
2
  export const PACKAGE_NAME = 'hoi4-agent-tools';
3
- export const PACKAGE_VERSION = '1.0.0';
3
+ export const PACKAGE_VERSION = '1.1.0';
4
4
  export const MCP_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION;
5
5
  export const CONFIG_VERSION = 1;
6
6
  export const TRANSACTION_VERSION = 1;
package/docs/focus.md CHANGED
@@ -18,12 +18,22 @@ For a missing target, call `hoi4.focus_rewrite` with `createIfMissing: true`. Re
18
18
 
19
19
  ## Clean an existing tree
20
20
 
21
- 1. Inspect the tree and read its diagnostics.
22
- 2. Render the current layout before changing coordinates.
23
- 3. Keep prerequisites, mutual exclusions, availability, bypasses, rewards, icons, localisation, and AI behavior unless the task explicitly changes them.
24
- 4. Group branches and lanes by their gameplay role. Existing coordinates remain fixed until movable focuses are marked for automatic placement; keep only deliberate anchors fixed.
25
- 5. Submit the complete plan to `hoi4.focus_rewrite` and review its proposed layout for overlaps, connector crossings, branch spacing, and route readability.
26
- 6. Inspect and render the result.
21
+ For a layout-only cleanup of an existing national tree, call `hoi4.focus_rewrite` without a plan:
22
+
23
+ ```json
24
+ {
25
+ "workspaceId": "my-mod",
26
+ "relativePath": "common/national_focus/my_tree.txt",
27
+ "treeId": "my_tree",
28
+ "layoutMode": "compact"
29
+ }
30
+ ```
31
+
32
+ Compact mode imports the tree, resets authored presentation coordinates and lane bounds, and performs a deterministic reflow in the same rewrite call. It keeps prerequisites, mutual exclusions, availability, bypasses, rewards, icons, localisation, AI behavior, and route/lane membership unchanged.
33
+
34
+ The rewrite measures canvas bounds, same-row spacing, the same curved connector paths used by the renderer, connector lengths, connector-node intersections, branch balance, and overall centering. A bounded refinement pass moves automatic gateways and blockers only when the measured connector result improves. The rewrite refuses changes with close focuses, rendered crossings, excessive branch imbalance, an off-center canvas, or connector and size metrics outside absolute and source-relative limits.
35
+
36
+ Use the default `layoutMode: "authored"` with a complete plan when deliberately designing coordinates or changing gameplay. Set `layoutMode: "compact"` with a complete plan when creating a large automatically arranged tree. Inspect and render calls remain useful before or after either rewrite mode, but a separate plan is not required for compact cleanup.
27
37
 
28
38
  A cleanup should not change gameplay relationships to improve the picture. One `prerequisite` block containing several focus IDs is an OR group; several prerequisite blocks are AND requirements.
29
39
 
@@ -35,6 +45,6 @@ If malformed or unsupported script makes a requested change ambiguous, the rewri
35
45
 
36
46
  ## What inspection checks
37
47
 
38
- Inspection and rendering can report missing references, invalid prerequisite structure, duplicate or overlapping positions, route conflicts, missing localisation or sprites, weak terminal branches, repeated rewards, and missing AI metadata. Renders are offline review artifacts, not game screenshots.
48
+ Inspection and rendering can report missing references, invalid prerequisite structure, duplicate or overlapping positions, insufficient spacing, long or crossing connectors, branch asymmetry, route conflicts, missing localisation or sprites, weak terminal branches, repeated rewards, and missing AI metadata. Renders are offline review artifacts, not game screenshots.
39
49
 
40
50
  National trees with 200 or more focuses render review PNGs at half scale by default so large agent workflows stay responsive. Pass an explicit `reviewScale` from `0.25` through `1` when a different raster size is needed; HTML, SVG, JSON, layout, and diagnostics remain complete.
package/docs/setup.md CHANGED
@@ -5,7 +5,7 @@ HOI4 Agent Tools requires Node.js 22 or 24.
5
5
  ## Install and initialize
6
6
 
7
7
  ```bash
8
- npm install --global hoi4-agent-tools@1.0.0
8
+ npm install --global hoi4-agent-tools@1.1.0
9
9
  hoi4-agent-tools-setup --init
10
10
  ```
11
11
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hoi4-agent-tools",
3
3
  "mcpName": "io.github.klimPaskov/hoi4-agent-tools",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "description": "MCP tools for coding agents to create and clean HOI4 focus trees, scripted GUIs, and maps",
6
6
  "type": "module",
7
7
  "license": "Apache-2.0",
@@ -84,7 +84,7 @@
84
84
  "fast-glob": "3.3.3",
85
85
  "fontkit": "2.0.4",
86
86
  "iconv-lite": "0.7.3",
87
- "jose": "6.1.3",
87
+ "jose": "6.2.3",
88
88
  "sharp": "0.35.3",
89
89
  "zod": "4.4.3"
90
90
  },
@@ -98,7 +98,7 @@
98
98
  "@vitest/coverage-v8": "4.1.10",
99
99
  "ajv": "8.20.0",
100
100
  "ajv-formats": "3.0.1",
101
- "eslint": "10.6.0",
101
+ "eslint": "10.7.0",
102
102
  "fast-check": "4.9.0",
103
103
  "pixelmatch": "7.2.0",
104
104
  "prettier": "3.9.5",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.klimPaskov/hoi4-agent-tools",
4
4
  "title": "HOI4 Agent Tools",
5
5
  "description": "Create and clean HOI4 focus trees, scripted GUIs, and maps with coding agents.",
6
- "version": "1.0.0",
6
+ "version": "1.1.0",
7
7
  "repository": {
8
8
  "url": "https://github.com/klimPaskov/hoi4-agent-tools",
9
9
  "source": "github"
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "hoi4-agent-tools",
16
- "version": "1.0.0",
16
+ "version": "1.1.0",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  }