hoi4-agent-tools 3.1.0 → 3.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 (74) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +10 -2
  3. package/dist/hoi4_agent_tools/core/analysis-evidence.js +9 -0
  4. package/dist/hoi4_agent_tools/core/analysis-job-operations.js +48 -0
  5. package/dist/hoi4_agent_tools/core/condition-evaluator.js +21 -1
  6. package/dist/hoi4_agent_tools/core/impact-comparison.js +49 -0
  7. package/dist/hoi4_agent_tools/core/impact-graph.js +301 -0
  8. package/dist/hoi4_agent_tools/core/impact-semantic.js +256 -0
  9. package/dist/hoi4_agent_tools/core/job-worker-host.js +5 -0
  10. package/dist/hoi4_agent_tools/core/job-worker.js +2 -0
  11. package/dist/hoi4_agent_tools/core/operation-tasks.js +12 -0
  12. package/dist/hoi4_agent_tools/core/scenario-model.js +85 -0
  13. package/dist/hoi4_agent_tools/core/source-overlay.js +125 -0
  14. package/dist/hoi4_agent_tools/decision/ai.js +104 -0
  15. package/dist/hoi4_agent_tools/decision/comparison.js +106 -0
  16. package/dist/hoi4_agent_tools/decision/cost.js +177 -0
  17. package/dist/hoi4_agent_tools/decision/evaluation.js +256 -0
  18. package/dist/hoi4_agent_tools/decision/lifecycle.js +114 -0
  19. package/dist/hoi4_agent_tools/decision/service.js +172 -0
  20. package/dist/hoi4_agent_tools/decision/source-inventory.js +182 -0
  21. package/dist/hoi4_agent_tools/gui/scenario-conditions.js +2 -23
  22. package/dist/hoi4_agent_tools/impact/scenario-suites.js +74 -0
  23. package/dist/hoi4_agent_tools/impact/service.js +127 -0
  24. package/dist/hoi4_agent_tools/mcp/server/create.js +2 -0
  25. package/dist/hoi4_agent_tools/mcp/server/instructions.js +1 -1
  26. package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +5 -0
  27. package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +2 -0
  28. package/dist/hoi4_agent_tools/mcp/tools/analysis.js +155 -0
  29. package/dist/hoi4_agent_tools/mechanic/assertions.js +112 -0
  30. package/dist/hoi4_agent_tools/mechanic/interpreter.js +619 -0
  31. package/dist/hoi4_agent_tools/mechanic/service.js +145 -0
  32. package/dist/hoi4_agent_tools/mechanic/source.js +89 -0
  33. package/dist/hoi4_agent_tools/package-check/service.js +231 -0
  34. package/dist/hoi4_agent_tools/probability/evaluation.js +3 -1
  35. package/dist/hoi4_agent_tools/scenario-suite/service.js +341 -0
  36. package/dist/hoi4_agent_tools/schemas/analysis.js +228 -0
  37. package/dist/hoi4_agent_tools/schemas/scenarios.js +264 -0
  38. package/dist/hoi4_agent_tools/version.js +1 -1
  39. package/docs/README.md +2 -0
  40. package/docs/analysis.md +100 -0
  41. package/docs/jobs.md +2 -1
  42. package/docs/mechanics.md +70 -0
  43. package/package.json +6 -2
  44. package/schemas/configuration.schema.json +1 -1
  45. package/schemas/continuous-focus-palette.schema.json +1 -1
  46. package/schemas/custom-weighted-pool.schema.json +1 -1
  47. package/schemas/decision-inspect-input.schema.json +290 -0
  48. package/schemas/event-feature-manifest.schema.json +1 -1
  49. package/schemas/focus-plan.schema.json +1 -1
  50. package/schemas/focus-planning-sidecar.schema.json +1 -1
  51. package/schemas/gui-animation-source.schema.json +1 -1
  52. package/schemas/gui-helper.schema.json +1 -1
  53. package/schemas/gui-scenario.schema.json +1 -1
  54. package/schemas/helper-expansion-request.schema.json +1 -1
  55. package/schemas/helper-expansion-summary.schema.json +1 -1
  56. package/schemas/impact-inspect-input.schema.json +203 -0
  57. package/schemas/map-operation.schema.json +1 -1
  58. package/schemas/mechanic-test-input.schema.json +650 -0
  59. package/schemas/operation-result.schema.json +1 -1
  60. package/schemas/package-check-input.schema.json +164 -0
  61. package/schemas/package-manifest.schema.json +142 -0
  62. package/schemas/probability-analysis-result.schema.json +1 -1
  63. package/schemas/probability-compare-input.schema.json +1 -1
  64. package/schemas/probability-evaluate-input.schema.json +1 -1
  65. package/schemas/probability-inspect-input.schema.json +1 -1
  66. package/schemas/probability-render-input.schema.json +1 -1
  67. package/schemas/probability-scenario-set.schema.json +1 -1
  68. package/schemas/probability-sequence-input.schema.json +1 -1
  69. package/schemas/probability-simulate-input.schema.json +1 -1
  70. package/schemas/probability-sweep-input.schema.json +1 -1
  71. package/schemas/scenario-suite.schema.json +1004 -0
  72. package/schemas/scenario-test-input.schema.json +1057 -0
  73. package/schemas/shared-scenario.schema.json +237 -0
  74. package/server.json +3 -3
@@ -0,0 +1,155 @@
1
+ import { z } from 'zod/v4';
2
+ import { decisionInspectRequestSchema, impactInspectRequestSchema, } from '../../schemas/analysis.js';
3
+ import { mechanicCaseSchema, mechanicTestRequestSchema, packageManifestSchema, packageCheckRequestSchema, scenarioSuiteSchema, scenarioTestRequestSchema, } from '../../schemas/scenarios.js';
4
+ import { compactValidatedInputSchema } from '../server/context-schemas.js';
5
+ import { nonNegativeIntegerSchema, sha256Schema } from '../server/output-schemas.js';
6
+ import { strictOperationResultSchema } from '../server/result.js';
7
+ import { registerLegacyTaskTools } from '../server/legacy-task-tools.js';
8
+ const impactOutputSchema = strictOperationResultSchema(z
9
+ .object({
10
+ mode: z.enum(['inspect', 'compare']),
11
+ sourceRevision: sha256Schema,
12
+ proposedRevision: sha256Schema.optional(),
13
+ reportHash: sha256Schema,
14
+ complete: z.boolean(),
15
+ definitions: nonNegativeIntegerSchema,
16
+ directConsumers: nonNegativeIntegerSchema,
17
+ transitiveConsumers: nonNegativeIntegerSchema,
18
+ affectedFiles: nonNegativeIntegerSchema,
19
+ cycles: nonNegativeIntegerSchema,
20
+ scenarioSuites: nonNegativeIntegerSchema,
21
+ affectedCases: nonNegativeIntegerSchema,
22
+ unresolved: nonNegativeIntegerSchema,
23
+ addedConsumers: nonNegativeIntegerSchema.optional(),
24
+ removedConsumers: nonNegativeIntegerSchema.optional(),
25
+ })
26
+ .strict());
27
+ const decisionOutputSchema = strictOperationResultSchema(z
28
+ .object({
29
+ mode: z.enum(['inventory', 'inspect', 'compare']),
30
+ sourceRevision: sha256Schema,
31
+ proposedRevision: sha256Schema.optional(),
32
+ reportHash: sha256Schema,
33
+ complete: z.boolean(),
34
+ decisions: nonNegativeIntegerSchema,
35
+ categories: nonNegativeIntegerSchema,
36
+ overrides: nonNegativeIntegerSchema,
37
+ scenarios: nonNegativeIntegerSchema,
38
+ unresolvedDefinitions: nonNegativeIntegerSchema,
39
+ })
40
+ .strict());
41
+ const mechanicOutputSchema = strictOperationResultSchema(z
42
+ .object({
43
+ sourceRevision: sha256Schema,
44
+ reportHash: sha256Schema,
45
+ status: z.enum(['passed', 'failed', 'unresolved']),
46
+ assertions: nonNegativeIntegerSchema,
47
+ passed: nonNegativeIntegerSchema,
48
+ failed: nonNegativeIntegerSchema,
49
+ unresolved: nonNegativeIntegerSchema,
50
+ steps: nonNegativeIntegerSchema,
51
+ })
52
+ .strict());
53
+ const packageOutputSchema = strictOperationResultSchema(z
54
+ .object({
55
+ sourceRevision: sha256Schema,
56
+ reportHash: sha256Schema,
57
+ complete: z.boolean(),
58
+ items: nonNegativeIntegerSchema,
59
+ present: nonNegativeIntegerSchema,
60
+ absent: nonNegativeIntegerSchema,
61
+ shadowed: nonNegativeIntegerSchema,
62
+ unresolved: nonNegativeIntegerSchema,
63
+ })
64
+ .strict());
65
+ const scenarioOutputSchema = strictOperationResultSchema(z
66
+ .object({
67
+ sourceRevision: sha256Schema,
68
+ suiteHash: sha256Schema,
69
+ reportHash: sha256Schema,
70
+ start: nonNegativeIntegerSchema,
71
+ end: nonNegativeIntegerSchema,
72
+ total: nonNegativeIntegerSchema,
73
+ completed: nonNegativeIntegerSchema,
74
+ failed: nonNegativeIntegerSchema,
75
+ unresolved: nonNegativeIntegerSchema,
76
+ pending: nonNegativeIntegerSchema,
77
+ continuation: z.string().optional(),
78
+ })
79
+ .strict());
80
+ const mechanicInputSchema = z
81
+ .object({
82
+ ...mechanicTestRequestSchema.shape,
83
+ test: compactValidatedInputSchema(mechanicCaseSchema, 'Versioned declared scenario, explicit effect steps, and typed assertions. See docs/mechanics.md.'),
84
+ })
85
+ .strict();
86
+ const packageInputSchema = z
87
+ .object({
88
+ ...packageCheckRequestSchema.shape,
89
+ manifest: compactValidatedInputSchema(packageManifestSchema, 'Declarative definitions, calls, registrations, localisation, assets, and required cases. See docs/mechanics.md.'),
90
+ })
91
+ .strict();
92
+ const scenarioInputSchema = z
93
+ .object({
94
+ ...scenarioTestRequestSchema.shape,
95
+ suite: compactValidatedInputSchema(scenarioSuiteSchema, 'Named mechanic, package, or fixed read-only tool cases. See docs/mechanics.md.').optional(),
96
+ })
97
+ .strict()
98
+ .superRefine((input, context) => {
99
+ if ((input.suite === undefined) === (input.suitePath === undefined))
100
+ context.addIssue({
101
+ code: 'custom',
102
+ message: 'Specify exactly one inline suite or suitePath',
103
+ });
104
+ });
105
+ const readOnlyAnalysisTool = {
106
+ readOnlyHint: true,
107
+ destructiveHint: false,
108
+ idempotentHint: true,
109
+ openWorldHint: false,
110
+ };
111
+ export const analysisTaskTools = [
112
+ {
113
+ name: 'hoi4.impact_inspect',
114
+ title: 'Inspect cross-system impact',
115
+ description: 'Trace definitions and consumers of symbols or changed source across HOI4 systems. Proposed sources are analyzed in memory; full revision-bound evidence is linked.',
116
+ inputSchema: impactInspectRequestSchema,
117
+ outputSchema: impactOutputSchema,
118
+ annotations: readOnlyAnalysisTool,
119
+ },
120
+ {
121
+ name: 'hoi4.decision_inspect',
122
+ title: 'Inspect decisions and missions',
123
+ description: 'Inventory decisions or evaluate declared actor and target scenarios for gates, payment, lifecycle, and proposed-source differences. Full evidence is linked.',
124
+ inputSchema: decisionInspectRequestSchema,
125
+ outputSchema: decisionOutputSchema,
126
+ annotations: readOnlyAnalysisTool,
127
+ },
128
+ {
129
+ name: 'hoi4.mechanic_test',
130
+ title: 'Test declared mechanic steps',
131
+ description: 'Interpret bounded, source-backed effect steps on isolated declared state and check typed invariants. Unsupported effects remain unresolved; no game or mod source is changed.',
132
+ inputSchema: mechanicInputSchema,
133
+ outputSchema: mechanicOutputSchema,
134
+ annotations: readOnlyAnalysisTool,
135
+ },
136
+ {
137
+ name: 'hoi4.package_check',
138
+ title: 'Check package connections',
139
+ description: 'Check a declarative package manifest against active definitions, references, registrations, localisation, assets, and required suite cases.',
140
+ inputSchema: packageInputSchema,
141
+ outputSchema: packageOutputSchema,
142
+ annotations: readOnlyAnalysisTool,
143
+ },
144
+ {
145
+ name: 'hoi4.scenario_test',
146
+ title: 'Run scenario suite',
147
+ description: 'Run named inline or workspace-relative cases through bounded typed read-only domain services. Large suites return an authenticated continuation.',
148
+ inputSchema: scenarioInputSchema,
149
+ outputSchema: scenarioOutputSchema,
150
+ annotations: readOnlyAnalysisTool,
151
+ },
152
+ ];
153
+ export function registerAnalysisTools(server) {
154
+ registerLegacyTaskTools(server, analysisTaskTools);
155
+ }
@@ -0,0 +1,112 @@
1
+ import { canonicalJson } from '../core/canonical.js';
2
+ import { scenarioValue } from '../core/scenario-model.js';
3
+ function value(state, path) {
4
+ return scenarioValue(state, path.key, path.scope);
5
+ }
6
+ function number(state, path) {
7
+ const found = value(state, path);
8
+ return typeof found === 'number' && Number.isFinite(found) ? found : undefined;
9
+ }
10
+ function result(assertion, status, observed, reason) {
11
+ return {
12
+ id: assertion.id,
13
+ kind: assertion.kind,
14
+ status,
15
+ ...(observed === undefined ? {} : { observed }),
16
+ ...(reason === undefined ? {} : { reason }),
17
+ };
18
+ }
19
+ export function evaluateMechanicAssertions(assertions, before, after, checkpoints, trace) {
20
+ return assertions.map((assertion) => {
21
+ if (before.unknown.has('*') || after.unknown.has('*'))
22
+ return result(assertion, 'unresolved', undefined, 'An unsupported effect may write this state');
23
+ if (assertion.kind === 'equals' || assertion.kind === 'end_state') {
24
+ const found = value(after, assertion.path);
25
+ if (found === undefined)
26
+ return result(assertion, 'unresolved', undefined, 'End-state value is unknown');
27
+ return result(assertion, found === assertion.expected ? 'passed' : 'failed', found);
28
+ }
29
+ if (assertion.kind === 'conservation') {
30
+ const starting = assertion.paths.map((path) => number(before, path));
31
+ const ending = assertion.paths.map((path) => number(after, path));
32
+ if ([...starting, ...ending].some((item) => item === undefined))
33
+ return result(assertion, 'unresolved', undefined, 'Conserved values must be declared numbers');
34
+ const initial = starting.reduce((sum, item) => sum + item, 0);
35
+ const final = ending.reduce((sum, item) => sum + item, 0);
36
+ return result(assertion, Math.abs(initial - final) <= assertion.tolerance ? 'passed' : 'failed', { initial, final, difference: final - initial });
37
+ }
38
+ if (assertion.kind === 'array_alignment') {
39
+ const arrays = assertion.paths.map((path) => value(after, path));
40
+ if (arrays.some((item) => !Array.isArray(item)))
41
+ return result(assertion, 'unresolved', undefined, 'All aligned values must be declared arrays');
42
+ const lengths = arrays.map((array) => array.length);
43
+ return result(assertion, lengths.every((length) => length === lengths[0]) ? 'passed' : 'failed', lengths);
44
+ }
45
+ if (assertion.kind === 'affordability') {
46
+ const available = number(before, assertion.balance);
47
+ return available === undefined
48
+ ? result(assertion, 'unresolved', undefined, 'Starting balance is unknown')
49
+ : result(assertion, available >= assertion.cost ? 'passed' : 'failed', {
50
+ available,
51
+ cost: assertion.cost,
52
+ });
53
+ }
54
+ if (assertion.kind === 'single_payment') {
55
+ const starting = number(before, assertion.balance);
56
+ const ending = number(after, assertion.balance);
57
+ const reductions = trace.flatMap((step) => step.status === 'applied' &&
58
+ step.scope === assertion.balance.scope &&
59
+ step.target === assertion.balance.key &&
60
+ typeof step.before === 'number' &&
61
+ typeof step.after === 'number' &&
62
+ step.before > step.after
63
+ ? [step.before - step.after]
64
+ : []);
65
+ return starting === undefined || ending === undefined
66
+ ? result(assertion, 'unresolved', undefined, 'Payment balance is unknown')
67
+ : result(assertion, reductions.length === 1 &&
68
+ reductions[0] === assertion.cost &&
69
+ starting - ending === assertion.cost
70
+ ? 'passed'
71
+ : 'failed', {
72
+ paid: starting - ending,
73
+ expected: assertion.cost,
74
+ deductions: reductions,
75
+ });
76
+ }
77
+ if (assertion.kind === 'repeated_setup') {
78
+ const first = checkpoints[assertion.firstStep];
79
+ const second = checkpoints[assertion.secondStep];
80
+ if (first === undefined ||
81
+ second === undefined ||
82
+ first.unknown.size > 0 ||
83
+ second.unknown.size > 0)
84
+ return result(assertion, 'unresolved', undefined, 'Repeated setup checkpoints are unavailable');
85
+ return result(assertion, canonicalJson(first.scenario) === canonicalJson(second.scenario) ? 'passed' : 'failed', { firstStep: assertion.firstStep, secondStep: assertion.secondStep });
86
+ }
87
+ if (assertion.kind === 'exclusive_flags') {
88
+ const flags = assertion.scope === 'GLOBAL'
89
+ ? after.scenario.globalFlags
90
+ : assertion.scope === 'ROOT'
91
+ ? after.scenario.flags
92
+ : after.scenario.scopes?.[assertion.scope]?.flags;
93
+ if (flags === undefined)
94
+ return result(assertion, 'unresolved', undefined, 'Flag scope is undeclared');
95
+ const active = assertion.flags.filter((flag) => flags.includes(flag));
96
+ return result(assertion, active.length <= 1 ? 'passed' : 'failed', active);
97
+ }
98
+ const flags = assertion.scope === 'GLOBAL'
99
+ ? after.scenario.globalFlags
100
+ : assertion.scope === 'ROOT'
101
+ ? after.scenario.flags
102
+ : after.scenario.scopes?.[assertion.scope]?.flags;
103
+ const targets = assertion.scope === 'ROOT'
104
+ ? after.scenario.eventTargets
105
+ : after.scenario.scopes?.[assertion.scope]?.eventTargets;
106
+ if (flags === undefined && assertion.flags.length > 0)
107
+ return result(assertion, 'unresolved', undefined, 'Cleanup flag scope is undeclared');
108
+ const lingeringFlags = assertion.flags.filter((flag) => flags?.includes(flag));
109
+ const lingeringTargets = assertion.targets.filter((target) => targets?.[target] !== undefined);
110
+ return result(assertion, lingeringFlags.length === 0 && lingeringTargets.length === 0 ? 'passed' : 'failed', { lingeringFlags, lingeringTargets });
111
+ });
112
+ }