hoi4-agent-tools 2.1.1 → 2.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +32 -18
  3. package/dist/bin/stdio.js +2 -1
  4. package/dist/hoi4_agent_tools/core/clausewitz-evaluation.js +90 -0
  5. package/dist/hoi4_agent_tools/core/engine.js +14 -0
  6. package/dist/hoi4_agent_tools/core/index.js +349 -2
  7. package/dist/hoi4_agent_tools/core/source/ast-path.js +41 -0
  8. package/dist/hoi4_agent_tools/core/source/index.js +1 -0
  9. package/dist/hoi4_agent_tools/event/limits.js +1 -1
  10. package/dist/hoi4_agent_tools/focus/render.js +37 -13
  11. package/dist/hoi4_agent_tools/mcp/server/create.js +5 -1
  12. package/dist/hoi4_agent_tools/mcp/server/result.js +4 -4
  13. package/dist/hoi4_agent_tools/mcp/tools/focus.js +39 -2
  14. package/dist/hoi4_agent_tools/mcp/tools/probability.js +413 -0
  15. package/dist/hoi4_agent_tools/mcp/tools/technology.js +368 -0
  16. package/dist/hoi4_agent_tools/probability/adapters.js +344 -0
  17. package/dist/hoi4_agent_tools/probability/compare.js +178 -0
  18. package/dist/hoi4_agent_tools/probability/evaluation.js +649 -0
  19. package/dist/hoi4_agent_tools/probability/index.js +13 -0
  20. package/dist/hoi4_agent_tools/probability/model.js +5 -0
  21. package/dist/hoi4_agent_tools/probability/rational.js +145 -0
  22. package/dist/hoi4_agent_tools/probability/render.js +307 -0
  23. package/dist/hoi4_agent_tools/probability/sequence.js +858 -0
  24. package/dist/hoi4_agent_tools/probability/service.js +1168 -0
  25. package/dist/hoi4_agent_tools/probability/simulation.js +545 -0
  26. package/dist/hoi4_agent_tools/probability/source-analysis.js +458 -0
  27. package/dist/hoi4_agent_tools/probability/sweep.js +265 -0
  28. package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +280 -0
  29. package/dist/hoi4_agent_tools/probability/value-evaluator.js +357 -0
  30. package/dist/hoi4_agent_tools/schemas/probability.js +930 -0
  31. package/dist/hoi4_agent_tools/schemas/technology.js +77 -0
  32. package/dist/hoi4_agent_tools/technology/compare.js +240 -0
  33. package/dist/hoi4_agent_tools/technology/graph.js +1196 -0
  34. package/dist/hoi4_agent_tools/technology/index.js +7 -0
  35. package/dist/hoi4_agent_tools/technology/model.js +2 -0
  36. package/dist/hoi4_agent_tools/technology/queries.js +300 -0
  37. package/dist/hoi4_agent_tools/technology/render.js +641 -0
  38. package/dist/hoi4_agent_tools/technology/service.js +686 -0
  39. package/dist/hoi4_agent_tools/technology/source-analysis.js +967 -0
  40. package/dist/hoi4_agent_tools/version.js +1 -1
  41. package/docs/README.md +2 -0
  42. package/docs/development.md +7 -1
  43. package/docs/probability.md +80 -0
  44. package/docs/research/probability-adapter-evidence.md +64 -0
  45. package/docs/setup.md +1 -1
  46. package/docs/technology.md +90 -0
  47. package/examples/clients/codex-config.toml +3 -0
  48. package/examples/clients/generic.json +8 -0
  49. package/examples/probability/adaptive-event-pool.json +92 -0
  50. package/examples/probability/adaptive-event-sequence-input.json +57 -0
  51. package/examples/probability/event-mtth-scenarios.json +57 -0
  52. package/examples/probability/event-mtth-simulate-input.json +37 -0
  53. package/examples/probability/focus-route-scenarios.json +61 -0
  54. package/examples/probability/focus-route-sweep-input.json +36 -0
  55. package/package.json +26 -8
  56. package/schemas/configuration.schema.json +747 -0
  57. package/schemas/continuous-focus-palette.schema.json +325 -0
  58. package/schemas/custom-weighted-pool.schema.json +298 -0
  59. package/schemas/event-feature-manifest.schema.json +63 -0
  60. package/schemas/focus-plan.schema.json +927 -0
  61. package/schemas/focus-planning-sidecar.schema.json +407 -0
  62. package/schemas/gui-animation-source.schema.json +176 -0
  63. package/schemas/gui-helper.schema.json +301 -0
  64. package/schemas/gui-scenario.schema.json +298 -0
  65. package/schemas/map-operation.schema.json +2284 -0
  66. package/schemas/operation-result.schema.json +327 -0
  67. package/schemas/probability-analysis-result.schema.json +2211 -0
  68. package/schemas/probability-compare-input.schema.json +664 -0
  69. package/schemas/probability-evaluate-input.schema.json +665 -0
  70. package/schemas/probability-inspect-input.schema.json +97 -0
  71. package/schemas/probability-render-input.schema.json +141 -0
  72. package/schemas/probability-scenario-set.schema.json +430 -0
  73. package/schemas/probability-sequence-input.schema.json +886 -0
  74. package/schemas/probability-simulate-input.schema.json +691 -0
  75. package/schemas/probability-sweep-input.schema.json +702 -0
  76. package/server.json +3 -3
@@ -0,0 +1,368 @@
1
+ import { z } from 'zod/v4';
2
+ import { publicArtifactLink } from '../../core/artifacts.js';
3
+ import { compareCodeUnits, hashCanonical } from '../../core/canonical.js';
4
+ import { emptyServiceResult } from '../../core/result.js';
5
+ import { workspaceIdSchema } from '../../schemas/common.js';
6
+ import { technologyDefectClassSchema, technologyDirectionSchema, technologyGraphReferenceSchema, technologyIdSchema, technologyImpactSchema, technologyProposedSourceSchema, technologyRenderViewSchema, technologyUnlockKindSchema, } from '../../schemas/technology.js';
7
+ import { TechnologyTreeViewer, technologyDiagnostics, } from '../../technology/service.js';
8
+ import { compactValidatedInputSchema } from '../server/context-schemas.js';
9
+ import { nonNegativeIntegerSchema, sha256Schema } from '../server/output-schemas.js';
10
+ import { progressReporter } from '../server/progress.js';
11
+ import { errorResult, setInlineFilesScanned, strictOperationResultSchema, toolResult, } from '../server/result.js';
12
+ const technologyMode = z.enum([
13
+ 'scan',
14
+ 'folders',
15
+ 'trace',
16
+ 'explain',
17
+ 'unlocks',
18
+ 'bonus_coverage',
19
+ 'lint',
20
+ 'impact',
21
+ ]);
22
+ const inspectInput = z
23
+ .object({
24
+ workspaceId: workspaceIdSchema,
25
+ mode: technologyMode,
26
+ folderId: technologyIdSchema.optional(),
27
+ technologyId: technologyIdSchema.optional(),
28
+ categoryId: technologyIdSchema.optional(),
29
+ targetKind: technologyUnlockKindSchema.optional(),
30
+ targetId: technologyIdSchema.optional(),
31
+ direction: technologyDirectionSchema.optional(),
32
+ maxDepth: z.number().int().min(1).max(256).optional(),
33
+ maxNodes: z.number().int().min(1).max(25_000).optional(),
34
+ includeSubTechnologies: z.boolean().optional(),
35
+ classifications: z.array(technologyDefectClassSchema).max(4).optional(),
36
+ codes: z.array(z.string().min(1).max(256)).max(100).optional(),
37
+ impact: compactValidatedInputSchema(technologyImpactSchema, 'Rename or removal subject.').optional(),
38
+ refresh: z.boolean().optional(),
39
+ })
40
+ .strict()
41
+ .superRefine((value, context) => {
42
+ if ((value.mode === 'trace' || value.mode === 'explain') && value.technologyId === undefined)
43
+ context.addIssue({
44
+ code: 'custom',
45
+ path: ['technologyId'],
46
+ message: `${value.mode} requires technologyId`,
47
+ });
48
+ if (value.mode === 'impact' && value.impact === undefined)
49
+ context.addIssue({
50
+ code: 'custom',
51
+ path: ['impact'],
52
+ message: 'Impact mode requires impact',
53
+ });
54
+ });
55
+ const renderInput = z
56
+ .object({
57
+ workspaceId: workspaceIdSchema,
58
+ view: technologyRenderViewSchema.exclude(['comparison']),
59
+ folderId: technologyIdSchema.optional(),
60
+ technologyId: technologyIdSchema.optional(),
61
+ categoryId: technologyIdSchema.optional(),
62
+ targetId: technologyIdSchema.optional(),
63
+ maxNodes: z.number().int().min(1).max(2_000).optional(),
64
+ includeHtml: z.boolean().optional(),
65
+ refresh: z.boolean().optional(),
66
+ })
67
+ .strict()
68
+ .superRefine((value, context) => {
69
+ if (value.view === 'folder' && value.folderId === undefined)
70
+ context.addIssue({
71
+ code: 'custom',
72
+ path: ['folderId'],
73
+ message: 'Folder view requires folderId',
74
+ });
75
+ });
76
+ const compareInput = z
77
+ .object({
78
+ workspaceId: workspaceIdSchema,
79
+ before: compactValidatedInputSchema(technologyGraphReferenceSchema, 'Revision or graph resource.').optional(),
80
+ after: compactValidatedInputSchema(technologyGraphReferenceSchema, 'Revision or graph resource.').optional(),
81
+ proposedSources: z
82
+ .array(compactValidatedInputSchema(technologyProposedSourceSchema, 'In-memory source overlay.'))
83
+ .min(1)
84
+ .max(128)
85
+ .optional(),
86
+ render: z.boolean().optional(),
87
+ maxRenderNodes: z.number().int().min(1).max(2_000).optional(),
88
+ refresh: z.boolean().optional(),
89
+ })
90
+ .strict()
91
+ .superRefine((value, context) => {
92
+ if (value.after !== undefined && value.proposedSources !== undefined)
93
+ context.addIssue({
94
+ code: 'custom',
95
+ path: ['after'],
96
+ message: 'after conflicts with proposedSources',
97
+ });
98
+ });
99
+ const countsSchema = z
100
+ .object({
101
+ technologies: nonNegativeIntegerSchema,
102
+ legacyDoctrines: nonNegativeIntegerSchema,
103
+ folders: nonNegativeIntegerSchema,
104
+ placements: nonNegativeIntegerSchema,
105
+ edges: nonNegativeIntegerSchema,
106
+ unlocks: nonNegativeIntegerSchema,
107
+ references: nonNegativeIntegerSchema,
108
+ issues: nonNegativeIntegerSchema,
109
+ unresolved: nonNegativeIntegerSchema,
110
+ artifacts: nonNegativeIntegerSchema,
111
+ })
112
+ .strict();
113
+ const analysisOutput = strictOperationResultSchema(z
114
+ .object({
115
+ mode: technologyMode,
116
+ revision: sha256Schema,
117
+ graphHash: sha256Schema,
118
+ counts: countsSchema,
119
+ })
120
+ .strict());
121
+ const renderOutput = strictOperationResultSchema(z
122
+ .object({
123
+ view: technologyRenderViewSchema,
124
+ revision: sha256Schema,
125
+ graphHash: sha256Schema,
126
+ hashes: z
127
+ .object({
128
+ json: sha256Schema,
129
+ svg: sha256Schema,
130
+ png: sha256Schema,
131
+ html: sha256Schema.optional(),
132
+ })
133
+ .strict(),
134
+ selectedNodes: nonNegativeIntegerSchema,
135
+ omittedNodes: nonNegativeIntegerSchema,
136
+ focusedRenders: nonNegativeIntegerSchema,
137
+ sourceAccurate: z.boolean(),
138
+ })
139
+ .strict());
140
+ const compareOutput = strictOperationResultSchema(z
141
+ .object({
142
+ beforeRevision: sha256Schema,
143
+ afterRevision: sha256Schema,
144
+ added: nonNegativeIntegerSchema,
145
+ removed: nonNegativeIntegerSchema,
146
+ renamed: nonNegativeIntegerSchema,
147
+ moved: nonNegativeIntegerSchema,
148
+ regressions: nonNegativeIntegerSchema,
149
+ artifacts: nonNegativeIntegerSchema,
150
+ renderHashes: z
151
+ .object({ json: sha256Schema, svg: sha256Schema, png: sha256Schema })
152
+ .strict()
153
+ .optional(),
154
+ })
155
+ .strict());
156
+ const readOnly = {
157
+ readOnlyHint: true,
158
+ destructiveHint: false,
159
+ idempotentHint: true,
160
+ openWorldHint: false,
161
+ };
162
+ function graphHash(graph) {
163
+ return hashCanonical({
164
+ schemaVersion: graph.schemaVersion,
165
+ workspaceIdentity: graph.workspaceIdentity,
166
+ revision: graph.revision,
167
+ statistics: graph.statistics,
168
+ sourceHashes: graph.sourceHashes,
169
+ });
170
+ }
171
+ function counts(graph, artifacts) {
172
+ return {
173
+ technologies: graph.statistics.technologyCount,
174
+ legacyDoctrines: graph.statistics.legacyDoctrineCount,
175
+ folders: graph.statistics.folderCount,
176
+ placements: graph.statistics.placementCount,
177
+ edges: graph.edges.length,
178
+ unlocks: graph.statistics.unlockCount,
179
+ references: graph.statistics.externalReferenceCount,
180
+ issues: graph.statistics.issueCount,
181
+ unresolved: graph.statistics.unresolvedCount,
182
+ artifacts,
183
+ };
184
+ }
185
+ function validation(graph) {
186
+ const blocking = technologyDiagnostics(graph).filter(({ severity }) => severity === 'error' || severity === 'blocker').length;
187
+ return {
188
+ passed: graph.complete && blocking === 0,
189
+ checks: [
190
+ {
191
+ id: 'technology-analysis',
192
+ passed: graph.complete && blocking === 0,
193
+ message: graph.complete
194
+ ? `${blocking} blocking technology diagnostics; full evidence is linked`
195
+ : `${graph.skippedSourceCount} source(s) were skipped; full evidence is linked`,
196
+ },
197
+ ],
198
+ };
199
+ }
200
+ function analysisRequest(input, workspaceId, context, signal) {
201
+ const impact = input.impact === undefined
202
+ ? undefined
203
+ : {
204
+ kind: input.impact.kind,
205
+ id: input.impact.id,
206
+ operation: input.impact.operation,
207
+ ...(input.impact.replacementId === undefined
208
+ ? {}
209
+ : { replacementId: input.impact.replacementId }),
210
+ };
211
+ const values = {
212
+ folderId: input.folderId,
213
+ technologyId: input.technologyId,
214
+ categoryId: input.categoryId,
215
+ targetKind: input.targetKind,
216
+ targetId: input.targetId,
217
+ direction: input.direction,
218
+ maxDepth: input.maxDepth,
219
+ maxNodes: input.maxNodes,
220
+ includeSubTechnologies: input.includeSubTechnologies,
221
+ classifications: input.classifications,
222
+ codes: input.codes,
223
+ impact,
224
+ refresh: input.refresh,
225
+ };
226
+ const compact = Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
227
+ return {
228
+ workspaceId,
229
+ mode: input.mode,
230
+ ...compact,
231
+ ...(context.principal === undefined ? {} : { principal: context.principal }),
232
+ signal,
233
+ };
234
+ }
235
+ export function registerTechnologyTools(server, engine, context) {
236
+ const viewer = new TechnologyTreeViewer(engine);
237
+ server.registerTool('hoi4.tech_inspect', {
238
+ title: 'Inspect technology trees',
239
+ description: 'Scan, discover folders, trace, explain, inspect unlocks or bonuses, lint, and assess impact.',
240
+ inputSchema: inspectInput,
241
+ outputSchema: analysisOutput,
242
+ annotations: readOnly,
243
+ }, async (input, extra) => {
244
+ const workspaceId = engine.resolver.resolveWorkspaceId(input.workspaceId, context.principal);
245
+ try {
246
+ const progress = progressReporter(extra);
247
+ await progress.report(0, 2, 'Analyzing technologies');
248
+ const output = await viewer.analyze(analysisRequest(input, workspaceId, context, progress.signal));
249
+ const result = emptyServiceResult(workspaceId, {
250
+ mode: input.mode,
251
+ revision: output.graph.revision,
252
+ graphHash: graphHash(output.graph),
253
+ counts: counts(output.graph, output.artifacts.length),
254
+ });
255
+ result.code = output.graph.complete ? 'TECH_INSPECTED' : 'TECH_INSPECTED_PARTIAL';
256
+ setInlineFilesScanned(result, output.graph.filesScanned);
257
+ result.artifacts = output.artifacts.map(publicArtifactLink);
258
+ result.validation = validation(output.graph);
259
+ await progress.report(2, 2, 'Technology analysis complete');
260
+ return toolResult(result);
261
+ }
262
+ catch (error) {
263
+ return errorResult(error, workspaceId);
264
+ }
265
+ });
266
+ server.registerTool('hoi4.tech_render', {
267
+ title: 'Render technology trees',
268
+ description: 'Render source-linked JSON, SVG, PNG, and optional HTML technology views.',
269
+ inputSchema: renderInput,
270
+ outputSchema: renderOutput,
271
+ annotations: readOnly,
272
+ }, async (input, extra) => {
273
+ const workspaceId = engine.resolver.resolveWorkspaceId(input.workspaceId, context.principal);
274
+ try {
275
+ const progress = progressReporter(extra);
276
+ const request = {
277
+ workspaceId,
278
+ view: input.view,
279
+ ...(input.folderId === undefined ? {} : { folderId: input.folderId }),
280
+ ...(input.technologyId === undefined ? {} : { technologyId: input.technologyId }),
281
+ ...(input.categoryId === undefined ? {} : { categoryId: input.categoryId }),
282
+ ...(input.targetId === undefined ? {} : { targetId: input.targetId }),
283
+ ...(input.maxNodes === undefined ? {} : { maxNodes: input.maxNodes }),
284
+ ...(input.includeHtml === undefined ? {} : { includeHtml: input.includeHtml }),
285
+ ...(input.refresh === undefined ? {} : { refresh: input.refresh }),
286
+ ...(context.principal === undefined ? {} : { principal: context.principal }),
287
+ signal: progress.signal,
288
+ };
289
+ const output = await viewer.renderAndStore(request);
290
+ const result = emptyServiceResult(workspaceId, {
291
+ view: output.render.view,
292
+ revision: output.graph.revision,
293
+ graphHash: graphHash(output.graph),
294
+ hashes: output.render.hashes,
295
+ selectedNodes: output.render.selectedIds.length,
296
+ omittedNodes: output.render.omittedNodeCount,
297
+ focusedRenders: output.focused.length,
298
+ sourceAccurate: output.render.sourceAccurate,
299
+ });
300
+ result.code = output.graph.complete ? 'TECH_RENDERED' : 'TECH_RENDERED_PARTIAL';
301
+ setInlineFilesScanned(result, output.graph.filesScanned);
302
+ result.artifacts = output.artifacts.map(publicArtifactLink);
303
+ result.validation = validation(output.graph);
304
+ return toolResult(result);
305
+ }
306
+ catch (error) {
307
+ return errorResult(error, workspaceId);
308
+ }
309
+ });
310
+ server.registerTool('hoi4.tech_compare', {
311
+ title: 'Compare technology trees',
312
+ description: 'Compare cached, resource-backed, current, or proposed source graphs without writes.',
313
+ inputSchema: compareInput,
314
+ outputSchema: compareOutput,
315
+ annotations: readOnly,
316
+ }, async (input, extra) => {
317
+ const workspaceId = engine.resolver.resolveWorkspaceId(input.workspaceId, context.principal);
318
+ try {
319
+ const progress = progressReporter(extra);
320
+ const request = {
321
+ workspaceId,
322
+ ...(input.before === undefined
323
+ ? {}
324
+ : { before: input.before }),
325
+ ...(input.after === undefined
326
+ ? {}
327
+ : { after: input.after }),
328
+ ...(input.proposedSources === undefined
329
+ ? {}
330
+ : {
331
+ proposedSources: input.proposedSources,
332
+ }),
333
+ ...(input.render === undefined ? {} : { render: input.render }),
334
+ ...(input.maxRenderNodes === undefined ? {} : { maxRenderNodes: input.maxRenderNodes }),
335
+ ...(input.refresh === undefined ? {} : { refresh: input.refresh }),
336
+ ...(context.principal === undefined ? {} : { principal: context.principal }),
337
+ signal: progress.signal,
338
+ };
339
+ const output = await viewer.compareAndStore(request);
340
+ const comparison = output.comparison;
341
+ const result = emptyServiceResult(workspaceId, {
342
+ beforeRevision: comparison.beforeRevision,
343
+ afterRevision: comparison.afterRevision,
344
+ added: comparison.technologies.added.length,
345
+ removed: comparison.technologies.removed.length,
346
+ renamed: comparison.technologies.renamed.length,
347
+ moved: comparison.technologies.moved.length,
348
+ regressions: comparison.regressions.length,
349
+ artifacts: output.artifacts.length,
350
+ ...(output.render === undefined ? {} : { renderHashes: output.render.hashes }),
351
+ });
352
+ result.code =
353
+ output.before.complete && output.after.complete
354
+ ? 'TECH_COMPARED'
355
+ : 'TECH_COMPARED_PARTIAL';
356
+ setInlineFilesScanned(result, [...new Set([...output.before.filesScanned, ...output.after.filesScanned])].sort(compareCodeUnits));
357
+ result.artifacts = output.artifacts.map(publicArtifactLink);
358
+ result.validation = {
359
+ passed: validation(output.before).passed && validation(output.after).passed,
360
+ checks: [...validation(output.before).checks, ...validation(output.after).checks],
361
+ };
362
+ return toolResult(result);
363
+ }
364
+ catch (error) {
365
+ return errorResult(error, workspaceId);
366
+ }
367
+ });
368
+ }