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.
- package/CHANGELOG.md +24 -0
- package/README.md +32 -18
- package/dist/bin/stdio.js +2 -1
- package/dist/hoi4_agent_tools/core/clausewitz-evaluation.js +90 -0
- package/dist/hoi4_agent_tools/core/engine.js +14 -0
- package/dist/hoi4_agent_tools/core/index.js +349 -2
- package/dist/hoi4_agent_tools/core/source/ast-path.js +41 -0
- package/dist/hoi4_agent_tools/core/source/index.js +1 -0
- package/dist/hoi4_agent_tools/event/limits.js +1 -1
- package/dist/hoi4_agent_tools/focus/render.js +37 -13
- package/dist/hoi4_agent_tools/mcp/server/create.js +5 -1
- package/dist/hoi4_agent_tools/mcp/server/result.js +4 -4
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +39 -2
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +413 -0
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +368 -0
- package/dist/hoi4_agent_tools/probability/adapters.js +344 -0
- package/dist/hoi4_agent_tools/probability/compare.js +178 -0
- package/dist/hoi4_agent_tools/probability/evaluation.js +649 -0
- package/dist/hoi4_agent_tools/probability/index.js +13 -0
- package/dist/hoi4_agent_tools/probability/model.js +5 -0
- package/dist/hoi4_agent_tools/probability/rational.js +145 -0
- package/dist/hoi4_agent_tools/probability/render.js +307 -0
- package/dist/hoi4_agent_tools/probability/sequence.js +858 -0
- package/dist/hoi4_agent_tools/probability/service.js +1168 -0
- package/dist/hoi4_agent_tools/probability/simulation.js +545 -0
- package/dist/hoi4_agent_tools/probability/source-analysis.js +458 -0
- package/dist/hoi4_agent_tools/probability/sweep.js +265 -0
- package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +280 -0
- package/dist/hoi4_agent_tools/probability/value-evaluator.js +357 -0
- package/dist/hoi4_agent_tools/schemas/probability.js +930 -0
- package/dist/hoi4_agent_tools/schemas/technology.js +77 -0
- package/dist/hoi4_agent_tools/technology/compare.js +240 -0
- package/dist/hoi4_agent_tools/technology/graph.js +1196 -0
- package/dist/hoi4_agent_tools/technology/index.js +7 -0
- package/dist/hoi4_agent_tools/technology/model.js +2 -0
- package/dist/hoi4_agent_tools/technology/queries.js +300 -0
- package/dist/hoi4_agent_tools/technology/render.js +641 -0
- package/dist/hoi4_agent_tools/technology/service.js +686 -0
- package/dist/hoi4_agent_tools/technology/source-analysis.js +967 -0
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +2 -0
- package/docs/development.md +7 -1
- package/docs/probability.md +80 -0
- package/docs/research/probability-adapter-evidence.md +64 -0
- package/docs/setup.md +1 -1
- package/docs/technology.md +90 -0
- package/examples/clients/codex-config.toml +3 -0
- package/examples/clients/generic.json +8 -0
- package/examples/probability/adaptive-event-pool.json +92 -0
- package/examples/probability/adaptive-event-sequence-input.json +57 -0
- package/examples/probability/event-mtth-scenarios.json +57 -0
- package/examples/probability/event-mtth-simulate-input.json +37 -0
- package/examples/probability/focus-route-scenarios.json +61 -0
- package/examples/probability/focus-route-sweep-input.json +36 -0
- package/package.json +26 -8
- package/schemas/configuration.schema.json +747 -0
- package/schemas/continuous-focus-palette.schema.json +325 -0
- package/schemas/custom-weighted-pool.schema.json +298 -0
- package/schemas/event-feature-manifest.schema.json +63 -0
- package/schemas/focus-plan.schema.json +927 -0
- package/schemas/focus-planning-sidecar.schema.json +407 -0
- package/schemas/gui-animation-source.schema.json +176 -0
- package/schemas/gui-helper.schema.json +301 -0
- package/schemas/gui-scenario.schema.json +298 -0
- package/schemas/map-operation.schema.json +2284 -0
- package/schemas/operation-result.schema.json +327 -0
- package/schemas/probability-analysis-result.schema.json +2211 -0
- package/schemas/probability-compare-input.schema.json +664 -0
- package/schemas/probability-evaluate-input.schema.json +665 -0
- package/schemas/probability-inspect-input.schema.json +97 -0
- package/schemas/probability-render-input.schema.json +141 -0
- package/schemas/probability-scenario-set.schema.json +430 -0
- package/schemas/probability-sequence-input.schema.json +886 -0
- package/schemas/probability-simulate-input.schema.json +691 -0
- package/schemas/probability-sweep-input.schema.json +702 -0
- package/server.json +3 -3
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { SOURCE_MAX_BYTES } from '../core/source/index.js';
|
|
3
|
+
import { workspaceRelativePathSchema } from './common.js';
|
|
4
|
+
export const technologyIdSchema = z.string().min(1).max(512);
|
|
5
|
+
export const technologyDirectionSchema = z.enum(['prerequisites', 'descendants', 'both']);
|
|
6
|
+
export const technologyUnlockKindSchema = z.enum([
|
|
7
|
+
'equipment',
|
|
8
|
+
'equipment_module',
|
|
9
|
+
'sub_unit',
|
|
10
|
+
'building',
|
|
11
|
+
'ability',
|
|
12
|
+
'tactic',
|
|
13
|
+
'other',
|
|
14
|
+
]);
|
|
15
|
+
export const technologyDefectClassSchema = z.enum([
|
|
16
|
+
'confirmed_error',
|
|
17
|
+
'probable_defect',
|
|
18
|
+
'design_warning',
|
|
19
|
+
'unresolved_analysis',
|
|
20
|
+
]);
|
|
21
|
+
export const technologyRenderViewSchema = z.enum([
|
|
22
|
+
'summary',
|
|
23
|
+
'folder',
|
|
24
|
+
'dependencies',
|
|
25
|
+
'technology',
|
|
26
|
+
'doctrine',
|
|
27
|
+
'exclusive',
|
|
28
|
+
'memberships',
|
|
29
|
+
'bonuses',
|
|
30
|
+
'grants',
|
|
31
|
+
'unlocks',
|
|
32
|
+
'metadata',
|
|
33
|
+
'assets',
|
|
34
|
+
'unresolved',
|
|
35
|
+
'comparison',
|
|
36
|
+
]);
|
|
37
|
+
export const technologyImpactSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
kind: z.enum(['technology', 'category', 'folder', 'unlock_target']),
|
|
40
|
+
id: technologyIdSchema,
|
|
41
|
+
operation: z.enum(['remove', 'rename']),
|
|
42
|
+
replacementId: technologyIdSchema.optional(),
|
|
43
|
+
})
|
|
44
|
+
.strict()
|
|
45
|
+
.superRefine((value, context) => {
|
|
46
|
+
if (value.operation === 'rename' && value.replacementId === undefined)
|
|
47
|
+
context.addIssue({
|
|
48
|
+
code: 'custom',
|
|
49
|
+
path: ['replacementId'],
|
|
50
|
+
message: 'Rename requires replacementId',
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
export const technologyGraphReferenceSchema = z
|
|
54
|
+
.object({
|
|
55
|
+
revision: z
|
|
56
|
+
.string()
|
|
57
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
58
|
+
.optional(),
|
|
59
|
+
artifactUri: z
|
|
60
|
+
.string()
|
|
61
|
+
.min(1)
|
|
62
|
+
.max(8_192)
|
|
63
|
+
.regex(/^hoi4-agent:\/\//u)
|
|
64
|
+
.optional(),
|
|
65
|
+
})
|
|
66
|
+
.strict()
|
|
67
|
+
.refine(({ revision, artifactUri }) => Number(revision !== undefined) + Number(artifactUri !== undefined) === 1, 'Provide exactly one revision or artifact URI');
|
|
68
|
+
export const technologyProposedSourceSchema = z
|
|
69
|
+
.object({
|
|
70
|
+
relativePath: workspaceRelativePathSchema,
|
|
71
|
+
source: z.string().max(SOURCE_MAX_BYTES).nullable(),
|
|
72
|
+
expectedSourceHash: z
|
|
73
|
+
.string()
|
|
74
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
75
|
+
.optional(),
|
|
76
|
+
})
|
|
77
|
+
.strict();
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { compareCodeUnits, hashCanonical } from '../core/canonical.js';
|
|
2
|
+
function byId(values) {
|
|
3
|
+
return new Map(values.map((value) => [value.id, value]));
|
|
4
|
+
}
|
|
5
|
+
function placementSignature(values) {
|
|
6
|
+
return hashCanonical(values
|
|
7
|
+
.map(({ folderId, x, y, xExpression, yExpression }) => ({
|
|
8
|
+
folderId,
|
|
9
|
+
x: x ?? null,
|
|
10
|
+
y: y ?? null,
|
|
11
|
+
xExpression: xExpression ?? null,
|
|
12
|
+
yExpression: yExpression ?? null,
|
|
13
|
+
}))
|
|
14
|
+
.sort((left, right) => compareCodeUnits(left.folderId, right.folderId) ||
|
|
15
|
+
(left.x ?? 0) - (right.x ?? 0) ||
|
|
16
|
+
(left.y ?? 0) - (right.y ?? 0)));
|
|
17
|
+
}
|
|
18
|
+
function edgeKey(edge) {
|
|
19
|
+
return hashCanonical({
|
|
20
|
+
kind: edge.kind,
|
|
21
|
+
from: edge.from,
|
|
22
|
+
to: edge.to,
|
|
23
|
+
coefficient: edge.coefficient ?? null,
|
|
24
|
+
ignoreForLayout: edge.ignoreForLayout ?? null,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function unlockKey(unlock) {
|
|
28
|
+
return hashCanonical({
|
|
29
|
+
technologyId: unlock.technologyId,
|
|
30
|
+
kind: unlock.kind,
|
|
31
|
+
targetId: unlock.targetId,
|
|
32
|
+
level: unlock.level ?? null,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function externalKey(reference) {
|
|
36
|
+
return hashCanonical({
|
|
37
|
+
kind: reference.kind,
|
|
38
|
+
sourceKind: reference.sourceKind,
|
|
39
|
+
sourceId: reference.sourceId,
|
|
40
|
+
technologyId: reference.technologyId ?? null,
|
|
41
|
+
categoryId: reference.categoryId ?? null,
|
|
42
|
+
helperStack: reference.helperStack,
|
|
43
|
+
expression: reference.expression,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function issueKey(issue) {
|
|
47
|
+
return hashCanonical({
|
|
48
|
+
code: issue.code,
|
|
49
|
+
classification: issue.classification,
|
|
50
|
+
details: issue.details,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function difference(before, after, key) {
|
|
54
|
+
const beforeKeys = new Set(before.map(key));
|
|
55
|
+
const afterKeys = new Set(after.map(key));
|
|
56
|
+
return {
|
|
57
|
+
added: after.filter((value) => !beforeKeys.has(key(value))),
|
|
58
|
+
removed: before.filter((value) => !afterKeys.has(key(value))),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function reachable(graph, signal) {
|
|
62
|
+
const incoming = new Map();
|
|
63
|
+
const outgoing = new Map();
|
|
64
|
+
for (const technology of graph.technologies) {
|
|
65
|
+
incoming.set(technology.id, 0);
|
|
66
|
+
outgoing.set(technology.id, []);
|
|
67
|
+
}
|
|
68
|
+
for (const edge of graph.edges.filter(({ kind }) => kind === 'prerequisite')) {
|
|
69
|
+
if (!incoming.has(edge.from) || !incoming.has(edge.to))
|
|
70
|
+
continue;
|
|
71
|
+
incoming.set(edge.to, (incoming.get(edge.to) ?? 0) + 1);
|
|
72
|
+
outgoing.get(edge.from).push(edge.to);
|
|
73
|
+
}
|
|
74
|
+
const result = new Set();
|
|
75
|
+
const pending = graph.technologies
|
|
76
|
+
.filter(({ id, hidden }) => hidden !== true && (incoming.get(id) ?? 0) === 0)
|
|
77
|
+
.map(({ id }) => id);
|
|
78
|
+
while (pending.length > 0) {
|
|
79
|
+
signal?.throwIfAborted();
|
|
80
|
+
const current = pending.pop();
|
|
81
|
+
if (result.has(current))
|
|
82
|
+
continue;
|
|
83
|
+
result.add(current);
|
|
84
|
+
pending.push(...(outgoing.get(current) ?? []));
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function technologyMetadata(technology) {
|
|
89
|
+
return {
|
|
90
|
+
kind: technology.kind,
|
|
91
|
+
startYear: technology.startYear ?? null,
|
|
92
|
+
researchCost: technology.researchCost ?? null,
|
|
93
|
+
doctrineName: technology.doctrineName ?? null,
|
|
94
|
+
hidden: technology.hidden,
|
|
95
|
+
folders: technology.folders,
|
|
96
|
+
categories: technology.categories,
|
|
97
|
+
tags: technology.tags,
|
|
98
|
+
subTechnologies: technology.subTechnologies,
|
|
99
|
+
ai: technology.ai,
|
|
100
|
+
effectSignature: technology.effectSignature,
|
|
101
|
+
localisation: technology.localisation,
|
|
102
|
+
icon: technology.icon,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function renameCandidates(removed, added, beforePlacements, afterPlacements, signal) {
|
|
106
|
+
const candidates = [];
|
|
107
|
+
const claimed = new Set();
|
|
108
|
+
for (const before of removed) {
|
|
109
|
+
signal?.throwIfAborted();
|
|
110
|
+
const matches = added
|
|
111
|
+
.filter((after) => !claimed.has(after.id))
|
|
112
|
+
.map((after) => {
|
|
113
|
+
const evidence = [];
|
|
114
|
+
if (before.effectSignature === after.effectSignature)
|
|
115
|
+
evidence.push('identical effect and unlock signature');
|
|
116
|
+
if (before.startYear === after.startYear && before.researchCost === after.researchCost)
|
|
117
|
+
evidence.push('identical year and research cost');
|
|
118
|
+
if (placementSignature(beforePlacements.filter(({ technologyId }) => technologyId === before.id)) ===
|
|
119
|
+
placementSignature(afterPlacements.filter(({ technologyId }) => technologyId === after.id)))
|
|
120
|
+
evidence.push('identical folder placements');
|
|
121
|
+
if (before.categories.join('\0') === after.categories.join('\0'))
|
|
122
|
+
evidence.push('identical category membership');
|
|
123
|
+
if (before.source.path === after.source.path)
|
|
124
|
+
evidence.push('same source file');
|
|
125
|
+
return { after, evidence };
|
|
126
|
+
})
|
|
127
|
+
.filter(({ evidence }) => evidence.length >= 4)
|
|
128
|
+
.sort((left, right) => right.evidence.length - left.evidence.length ||
|
|
129
|
+
compareCodeUnits(left.after.id, right.after.id));
|
|
130
|
+
if (matches.length !== 1)
|
|
131
|
+
continue;
|
|
132
|
+
const match = matches[0];
|
|
133
|
+
claimed.add(match.after.id);
|
|
134
|
+
candidates.push({
|
|
135
|
+
beforeId: before.id,
|
|
136
|
+
afterId: match.after.id,
|
|
137
|
+
confidence: match.evidence.length >= 5 ? 'high' : 'medium',
|
|
138
|
+
evidence: match.evidence,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return candidates.sort((left, right) => compareCodeUnits(left.beforeId, right.beforeId));
|
|
142
|
+
}
|
|
143
|
+
export function compareTechnologyGraphs(before, after, signal) {
|
|
144
|
+
signal?.throwIfAborted();
|
|
145
|
+
const beforeTech = byId(before.technologies);
|
|
146
|
+
const afterTech = byId(after.technologies);
|
|
147
|
+
const addedDefinitions = after.technologies.filter(({ id }) => !beforeTech.has(id));
|
|
148
|
+
const removedDefinitions = before.technologies.filter(({ id }) => !afterTech.has(id));
|
|
149
|
+
const added = addedDefinitions.map(({ id }) => id).sort(compareCodeUnits);
|
|
150
|
+
const removed = removedDefinitions.map(({ id }) => id).sort(compareCodeUnits);
|
|
151
|
+
const moved = [];
|
|
152
|
+
const metadataChanged = [];
|
|
153
|
+
for (const [technologyId, beforeDefinition] of beforeTech) {
|
|
154
|
+
signal?.throwIfAborted();
|
|
155
|
+
const afterDefinition = afterTech.get(technologyId);
|
|
156
|
+
if (afterDefinition === undefined)
|
|
157
|
+
continue;
|
|
158
|
+
const beforePlacement = before.placements.filter(({ technologyId: id }) => id === technologyId);
|
|
159
|
+
const afterPlacement = after.placements.filter(({ technologyId: id }) => id === technologyId);
|
|
160
|
+
if (placementSignature(beforePlacement) !== placementSignature(afterPlacement))
|
|
161
|
+
moved.push({ technologyId, before: beforePlacement, after: afterPlacement });
|
|
162
|
+
const beforeMetadata = technologyMetadata(beforeDefinition);
|
|
163
|
+
const afterMetadata = technologyMetadata(afterDefinition);
|
|
164
|
+
const fields = Object.keys(beforeMetadata)
|
|
165
|
+
.filter((field) => hashCanonical(beforeMetadata[field]) !== hashCanonical(afterMetadata[field]))
|
|
166
|
+
.sort(compareCodeUnits);
|
|
167
|
+
if (fields.length > 0)
|
|
168
|
+
metadataChanged.push({
|
|
169
|
+
technologyId,
|
|
170
|
+
fields,
|
|
171
|
+
before: Object.fromEntries(fields.map((field) => [field, beforeMetadata[field]])),
|
|
172
|
+
after: Object.fromEntries(fields.map((field) => [field, afterMetadata[field]])),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const edges = difference(before.edges, after.edges, edgeKey);
|
|
176
|
+
const unlocks = difference(before.unlocks, after.unlocks, unlockKey);
|
|
177
|
+
const externalReferences = difference(before.externalReferences, after.externalReferences, externalKey);
|
|
178
|
+
const beforeFolders = byId(before.folders);
|
|
179
|
+
const afterFolders = byId(after.folders);
|
|
180
|
+
const folderAdded = after.folders
|
|
181
|
+
.filter(({ id }) => !beforeFolders.has(id))
|
|
182
|
+
.map(({ id }) => id)
|
|
183
|
+
.sort(compareCodeUnits);
|
|
184
|
+
const folderRemoved = before.folders
|
|
185
|
+
.filter(({ id }) => !afterFolders.has(id))
|
|
186
|
+
.map(({ id }) => id)
|
|
187
|
+
.sort(compareCodeUnits);
|
|
188
|
+
const folderChanged = before.folders
|
|
189
|
+
.filter(({ id }) => {
|
|
190
|
+
const afterFolder = afterFolders.get(id);
|
|
191
|
+
return (afterFolder !== undefined &&
|
|
192
|
+
hashCanonical({ ...beforeFolders.get(id), source: undefined }) !==
|
|
193
|
+
hashCanonical({ ...afterFolder, source: undefined }));
|
|
194
|
+
})
|
|
195
|
+
.map(({ id }) => id)
|
|
196
|
+
.sort(compareCodeUnits);
|
|
197
|
+
const issueDifference = difference(before.issues, after.issues, issueKey);
|
|
198
|
+
const retained = before.issues.filter((issue) => new Set(after.issues.map(issueKey)).has(issueKey(issue))).length;
|
|
199
|
+
const beforeReachable = reachable(before, signal);
|
|
200
|
+
const afterReachable = reachable(after, signal);
|
|
201
|
+
const sourceFiles = {
|
|
202
|
+
added: Object.keys(after.sourceHashes)
|
|
203
|
+
.filter((sourcePath) => !(sourcePath in before.sourceHashes))
|
|
204
|
+
.sort(compareCodeUnits),
|
|
205
|
+
removed: Object.keys(before.sourceHashes)
|
|
206
|
+
.filter((sourcePath) => !(sourcePath in after.sourceHashes))
|
|
207
|
+
.sort(compareCodeUnits),
|
|
208
|
+
changed: Object.keys(before.sourceHashes)
|
|
209
|
+
.filter((sourcePath) => after.sourceHashes[sourcePath] !== undefined &&
|
|
210
|
+
after.sourceHashes[sourcePath] !== before.sourceHashes[sourcePath])
|
|
211
|
+
.sort(compareCodeUnits),
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
schemaVersion: 1,
|
|
215
|
+
beforeRevision: before.revision,
|
|
216
|
+
afterRevision: after.revision,
|
|
217
|
+
technologies: {
|
|
218
|
+
added,
|
|
219
|
+
removed,
|
|
220
|
+
renamed: renameCandidates(removedDefinitions, addedDefinitions, before.placements, after.placements, signal),
|
|
221
|
+
moved: moved.sort((left, right) => compareCodeUnits(left.technologyId, right.technologyId)),
|
|
222
|
+
metadataChanged: metadataChanged.sort((left, right) => compareCodeUnits(left.technologyId, right.technologyId)),
|
|
223
|
+
},
|
|
224
|
+
edges,
|
|
225
|
+
unlocks,
|
|
226
|
+
externalReferences,
|
|
227
|
+
folders: { added: folderAdded, removed: folderRemoved, changed: folderChanged },
|
|
228
|
+
issues: { introduced: issueDifference.added, resolved: issueDifference.removed, retained },
|
|
229
|
+
reachability: {
|
|
230
|
+
newlyReachable: [...afterReachable]
|
|
231
|
+
.filter((id) => !beforeReachable.has(id))
|
|
232
|
+
.sort(compareCodeUnits),
|
|
233
|
+
newlyOrphaned: [...beforeReachable]
|
|
234
|
+
.filter((id) => !afterReachable.has(id) && afterTech.has(id))
|
|
235
|
+
.sort(compareCodeUnits),
|
|
236
|
+
},
|
|
237
|
+
sourceFiles,
|
|
238
|
+
regressions: issueDifference.added.filter(({ classification }) => classification === 'confirmed_error' || classification === 'probable_defect'),
|
|
239
|
+
};
|
|
240
|
+
}
|