hoi4-agent-tools 3.10.0 → 4.0.1
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 +131 -0
- package/README.md +91 -147
- package/dist/bin/http.js +10 -0
- package/dist/bin/setup.js +3 -2
- package/dist/bin/stdio.js +10 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +40 -5
- package/dist/hoi4_agent_tools/core/canonical.js +18 -4
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +18 -0
- package/dist/hoi4_agent_tools/core/configuration.js +7 -0
- package/dist/hoi4_agent_tools/core/domain-scan-patterns.js +12 -0
- package/dist/hoi4_agent_tools/core/domain-tools.js +65 -0
- package/dist/hoi4_agent_tools/core/domain-validation.js +5 -2
- package/dist/hoi4_agent_tools/core/engine.js +29 -2
- package/dist/hoi4_agent_tools/core/index.js +2 -2
- package/dist/hoi4_agent_tools/core/job-executor.js +14 -1
- package/dist/hoi4_agent_tools/core/job-operation-registration.js +15 -7
- package/dist/hoi4_agent_tools/core/job-store.js +78 -5
- package/dist/hoi4_agent_tools/core/job-transactions.js +31 -2
- package/dist/hoi4_agent_tools/core/job-worker-host.js +314 -127
- package/dist/hoi4_agent_tools/core/job-worker.js +96 -37
- package/dist/hoi4_agent_tools/core/operation-result.js +123 -10
- package/dist/hoi4_agent_tools/core/operation-tasks.js +49 -89
- package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +25 -21
- package/dist/hoi4_agent_tools/core/server-registry.js +22 -5
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +26 -1
- package/dist/hoi4_agent_tools/core/source/cache.js +20 -10
- package/dist/hoi4_agent_tools/core/source/lexer.js +11 -3
- package/dist/hoi4_agent_tools/core/temporary-directories.js +293 -0
- package/dist/hoi4_agent_tools/core/transactions.js +114 -23
- package/dist/hoi4_agent_tools/core/worker-exit.js +80 -0
- package/dist/hoi4_agent_tools/core/workspace.js +84 -8
- package/dist/hoi4_agent_tools/decision/ai.js +1 -1
- package/dist/hoi4_agent_tools/decision/service.js +28 -0
- package/dist/hoi4_agent_tools/decision/source-inventory.js +7 -5
- package/dist/hoi4_agent_tools/event/job-operations.js +9 -5
- package/dist/hoi4_agent_tools/event/operations.js +14 -1
- package/dist/hoi4_agent_tools/focus/job-operations.js +1 -2
- package/dist/hoi4_agent_tools/focus/tool-operations.js +1 -1
- package/dist/hoi4_agent_tools/gui/assets.js +3 -0
- package/dist/hoi4_agent_tools/gui/dds.js +8 -4
- package/dist/hoi4_agent_tools/gui/layout.js +45 -5
- package/dist/hoi4_agent_tools/gui/native-state.js +244 -0
- package/dist/hoi4_agent_tools/gui/renderer.js +12 -7
- package/dist/hoi4_agent_tools/gui/source-graph.js +17 -4
- package/dist/hoi4_agent_tools/gui/studio.js +33 -21
- package/dist/hoi4_agent_tools/gui/validators.js +8 -6
- package/dist/hoi4_agent_tools/map/bmp.js +1 -1
- package/dist/hoi4_agent_tools/map/derived.js +748 -0
- package/dist/hoi4_agent_tools/map/job-operations.js +7 -3
- package/dist/hoi4_agent_tools/map/model.js +103 -0
- package/dist/hoi4_agent_tools/map/operations.js +427 -58
- package/dist/hoi4_agent_tools/map/references.js +483 -0
- package/dist/hoi4_agent_tools/map/service.js +136 -61
- package/dist/hoi4_agent_tools/map/tool-operations.js +2 -1
- package/dist/hoi4_agent_tools/map/validation.js +3 -0
- package/dist/hoi4_agent_tools/map/world.js +1280 -0
- package/dist/hoi4_agent_tools/mcp/prompts/probability.js +2 -2
- package/dist/hoi4_agent_tools/mcp/server/argument-errors.js +72 -0
- package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +23 -5
- package/dist/hoi4_agent_tools/mcp/server/create.js +7 -0
- package/dist/hoi4_agent_tools/mcp/server/instructions.js +1 -1
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +24 -30
- package/dist/hoi4_agent_tools/mcp/server/tool-listing.js +45 -0
- package/dist/hoi4_agent_tools/mcp/tools/analysis.js +28 -8
- package/dist/hoi4_agent_tools/mcp/tools/event.js +19 -30
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +5 -13
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +6 -6
- package/dist/hoi4_agent_tools/mcp/tools/job.js +4 -4
- package/dist/hoi4_agent_tools/mcp/tools/map.js +15 -18
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +42 -48
- package/dist/hoi4_agent_tools/mcp/tools/reference.js +26 -9
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +17 -30
- package/dist/hoi4_agent_tools/mcp/transports/http.js +28 -4
- package/dist/hoi4_agent_tools/mcp/transports/stdio-lifetime.js +31 -8
- package/dist/hoi4_agent_tools/probability/job-operations.js +4 -9
- package/dist/hoi4_agent_tools/probability/operations.js +175 -13
- package/dist/hoi4_agent_tools/probability/source-analysis.js +24 -1
- package/dist/hoi4_agent_tools/reference/error-log.js +359 -0
- package/dist/hoi4_agent_tools/reference/known-mistakes.js +307 -0
- package/dist/hoi4_agent_tools/reference/known-mistakes.json +154 -0
- package/dist/hoi4_agent_tools/reference/mod-index.js +101 -0
- package/dist/hoi4_agent_tools/reference/operations.js +88 -19
- package/dist/hoi4_agent_tools/reference/player-text.js +4 -3
- package/dist/hoi4_agent_tools/reference/script-validation.js +19 -1
- package/dist/hoi4_agent_tools/reference/service.js +6 -0
- package/dist/hoi4_agent_tools/runtime.js +14 -1
- package/dist/hoi4_agent_tools/schemas/event.js +78 -19
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +10 -0
- package/dist/hoi4_agent_tools/schemas/lenient-arguments.js +266 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +63 -20
- package/dist/hoi4_agent_tools/schemas/map.js +122 -3
- package/dist/hoi4_agent_tools/schemas/mod-tools.js +181 -0
- package/dist/hoi4_agent_tools/schemas/probability-requests.js +119 -9
- package/dist/hoi4_agent_tools/schemas/reference.js +13 -0
- package/dist/hoi4_agent_tools/schemas/scenarios.js +1 -3
- package/dist/hoi4_agent_tools/schemas/script-validation.js +13 -6
- package/dist/hoi4_agent_tools/schemas/technology.js +79 -21
- package/dist/hoi4_agent_tools/technology/job-operations.js +3 -4
- package/dist/hoi4_agent_tools/technology/operations.js +14 -1
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +7 -4
- package/docs/analysis.md +15 -2
- package/docs/configuration.md +281 -0
- package/docs/development.md +26 -2
- package/docs/events.md +29 -6
- package/docs/examples.md +13 -28
- package/docs/focus.md +127 -5
- package/docs/gui.md +105 -21
- package/docs/helper-expansion.md +9 -9
- package/docs/http.md +3 -1
- package/docs/jobs.md +23 -14
- package/docs/map.md +199 -7
- package/docs/mechanics.md +6 -2
- package/docs/probability.md +46 -16
- package/docs/reference.md +73 -5
- package/docs/setup.md +45 -14
- package/docs/technology.md +28 -4
- package/docs/third-party-notices.md +11 -0
- package/docs/tools.md +150 -42
- package/docs/troubleshooting.md +61 -0
- package/examples/clients/codex-config.toml +5 -2
- package/examples/clients/generic.json +2 -2
- package/examples/config/config.json +7 -0
- package/npm-shrinkwrap.json +151 -156
- package/package.json +13 -8
- package/schemas/configuration.schema.json +144 -128
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/decision-inspect-input.schema.json +1 -1
- package/schemas/error-log-input.schema.json +55 -0
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/helper-expansion-request.schema.json +1 -1
- package/schemas/helper-expansion-summary.schema.json +1 -1
- package/schemas/impact-inspect-input.schema.json +1 -1
- package/schemas/map-operation.schema.json +597 -126
- package/schemas/mechanic-test-input.schema.json +1 -1
- package/schemas/mod-index-input.schema.json +51 -0
- package/schemas/operation-result.schema.json +2 -2
- package/schemas/package-check-input.schema.json +1 -1
- package/schemas/package-manifest.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/schemas/reference-context-input.schema.json +1 -1
- package/schemas/reference-read-input.schema.json +1 -1
- package/schemas/reference-search-input.schema.json +1 -1
- package/schemas/scenario-suite.schema.json +3 -5
- package/schemas/scenario-test-input.schema.json +3 -5
- package/schemas/script-validate-input.schema.json +1 -1
- package/schemas/shared-scenario.schema.json +1 -1
- package/schemas/source-lookup-input.schema.json +1 -1
- package/server.json +3 -3
|
@@ -8,6 +8,9 @@ import { ReferenceService } from './service.js';
|
|
|
8
8
|
import { sourceLookup } from './source-lookup.js';
|
|
9
9
|
import { scriptValidateDataSchema, scriptValidateRequestSchema, } from '../schemas/script-validation.js';
|
|
10
10
|
import { validateScript } from './script-validation.js';
|
|
11
|
+
import { readErrorLog } from './error-log.js';
|
|
12
|
+
import { modIndex } from './mod-index.js';
|
|
13
|
+
import { errorLogDataSchema, errorLogRequestSchema, modIndexDataSchema, modIndexRequestSchema, } from '../schemas/mod-tools.js';
|
|
11
14
|
import { eventPopupDefinitions } from './event-popups.js';
|
|
12
15
|
import { locatePatterns, selectFile } from './source-locate.js';
|
|
13
16
|
/**
|
|
@@ -43,6 +46,8 @@ const schemas = {
|
|
|
43
46
|
'hoi4.reference_context': referenceContextRequestSchema,
|
|
44
47
|
'hoi4.source_lookup': sourceLookupRequestSchema,
|
|
45
48
|
'hoi4.script_validate': scriptValidateRequestSchema,
|
|
49
|
+
'hoi4.error_log': errorLogRequestSchema,
|
|
50
|
+
'hoi4.mod_index': modIndexRequestSchema,
|
|
46
51
|
};
|
|
47
52
|
export function isReferenceTool(name) {
|
|
48
53
|
return Object.hasOwn(schemas, name);
|
|
@@ -69,10 +74,34 @@ export class ReferenceToolService {
|
|
|
69
74
|
? await this.references.read(workspace, referenceReadRequestSchema.parse(parsed), signal)
|
|
70
75
|
: name === 'hoi4.reference_context'
|
|
71
76
|
? await this.references.context(workspace, referenceContextRequestSchema.parse(parsed), signal)
|
|
72
|
-
: name === 'hoi4.
|
|
73
|
-
? await
|
|
74
|
-
:
|
|
77
|
+
: name === 'hoi4.error_log'
|
|
78
|
+
? await readErrorLog(workspace, errorLogRequestSchema.parse(parsed), this.engine.resolver.config().gameUserRoot, signal)
|
|
79
|
+
: name === 'hoi4.mod_index'
|
|
80
|
+
? await modIndex(this.engine, workspaceId, modIndexRequestSchema.parse(parsed), context.principal, signal)
|
|
81
|
+
: name === 'hoi4.script_validate'
|
|
82
|
+
? await this.validateScript(workspaceId, workspace, scriptValidateRequestSchema.parse(parsed), context.principal, signal)
|
|
83
|
+
: await sourceLookup(this.engine, workspaceId, sourceLookupRequestSchema.parse(parsed), context.principal, signal);
|
|
75
84
|
const result = emptyServiceResult(workspaceId, data);
|
|
85
|
+
if (name === 'hoi4.error_log') {
|
|
86
|
+
const log = errorLogDataSchema.parse(data);
|
|
87
|
+
result.code = 'ERROR_LOG_READ';
|
|
88
|
+
result.validation = {
|
|
89
|
+
passed: log.modEntries === 0,
|
|
90
|
+
checks: [
|
|
91
|
+
{
|
|
92
|
+
id: 'error-log-mod-entries',
|
|
93
|
+
passed: log.modEntries === 0,
|
|
94
|
+
message: log.modEntries === 0
|
|
95
|
+
? 'The last game launch logged no errors located in this mod'
|
|
96
|
+
: `The last game launch logged ${log.modEntries} distinct errors located in this mod`,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (name === 'hoi4.mod_index') {
|
|
102
|
+
const index = modIndexDataSchema.parse(data);
|
|
103
|
+
result.code = index.complete ? 'MOD_INDEXED' : 'MOD_INDEXED_PARTIAL';
|
|
104
|
+
}
|
|
76
105
|
if (name === 'hoi4.script_validate') {
|
|
77
106
|
const checked = scriptValidateDataSchema.parse(data);
|
|
78
107
|
result.code = checked.valid === null ? 'SCRIPT_CHECK_PARTIAL' : 'SCRIPT_CHECKED';
|
|
@@ -117,20 +146,32 @@ export class ReferenceToolService {
|
|
|
117
146
|
#playerText = new Map();
|
|
118
147
|
/** File mode reads the scanned file and the mod's scripted helper names from one scan. */
|
|
119
148
|
async validateScript(workspaceId, workspace, request, principal, signal) {
|
|
120
|
-
if (request.path === undefined)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
149
|
+
if (request.path === undefined) {
|
|
150
|
+
if (request.kind !== undefined)
|
|
151
|
+
return validateScript(this.references, workspace, request, signal);
|
|
152
|
+
// A snippet without a kind is checked as an effect and as a trigger; the reading with
|
|
153
|
+
// fewer errors and unresolved commands is the one the author meant.
|
|
154
|
+
const effect = await validateScript(this.references, workspace, { ...request, kind: 'effect' }, signal);
|
|
155
|
+
const trigger = await validateScript(this.references, workspace, { ...request, kind: 'trigger' }, signal);
|
|
156
|
+
const misses = (checked) => checked.findings.filter(({ status }) => status === 'error').length * 100_000 +
|
|
157
|
+
checked.unresolvedCount;
|
|
158
|
+
return misses(trigger) < misses(effect) ? trigger : effect;
|
|
159
|
+
}
|
|
160
|
+
// Every file check of the supported families shares one scan, so checking files across
|
|
161
|
+
// folders reuses one snapshot and its incremental index; other folders add their own.
|
|
162
|
+
const scan = (extra) => this.engine.scan(workspaceId, { patterns: [...fileCheckPatterns, ...extra] }, principal, signal);
|
|
163
|
+
let snapshot = await scan([]);
|
|
164
|
+
let file;
|
|
165
|
+
try {
|
|
166
|
+
file = selectFile(snapshot, request.path);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const extra = locatePatterns(this.engine, workspaceId, request.path, principal);
|
|
170
|
+
if (extra === undefined)
|
|
171
|
+
throw error;
|
|
172
|
+
snapshot = await scan(extra);
|
|
173
|
+
file = selectFile(snapshot, request.path);
|
|
174
|
+
}
|
|
134
175
|
const playerText = playerTextFamily(file.relativePath)
|
|
135
176
|
? this.playerTextContext(workspaceId, snapshot)
|
|
136
177
|
: undefined;
|
|
@@ -143,8 +184,26 @@ export class ReferenceToolService {
|
|
|
143
184
|
implicitTriggers: implicitTriggerNames(snapshot),
|
|
144
185
|
...(playerText === undefined ? {} : { playerText }),
|
|
145
186
|
eventPopups: this.eventPopupContext(workspaceId, snapshot),
|
|
187
|
+
knownMistakes: { eventNamespaces: this.eventNamespaces(workspaceId, snapshot) },
|
|
146
188
|
});
|
|
147
189
|
}
|
|
190
|
+
#eventNamespaces = new Map();
|
|
191
|
+
/** Namespaces declared with `add_namespace` in every active events file, cached by hash. */
|
|
192
|
+
eventNamespaces(workspaceId, snapshot) {
|
|
193
|
+
const sources = [...snapshot.index.files.values()].filter(({ relativePath, shadowedBy }) => shadowedBy === undefined && relativePath.startsWith('events/'));
|
|
194
|
+
const cacheKey = hashCanonical(sources.map(({ displayPath, sha256 }) => [displayPath, sha256]));
|
|
195
|
+
const cached = this.#eventNamespaces.get(workspaceId);
|
|
196
|
+
if (cached?.key === cacheKey)
|
|
197
|
+
return cached.namespaces;
|
|
198
|
+
const namespaces = new Set();
|
|
199
|
+
for (const file of sources)
|
|
200
|
+
for (const match of file.bytes
|
|
201
|
+
.toString('utf8')
|
|
202
|
+
.matchAll(/^\s*add_namespace\s*=\s*"?([\w.]+)"?/gmu))
|
|
203
|
+
namespaces.add(match[1]);
|
|
204
|
+
this.#eventNamespaces.set(workspaceId, { key: cacheKey, namespaces });
|
|
205
|
+
return namespaces;
|
|
206
|
+
}
|
|
148
207
|
/** Every active event definition, cached by the hashes of the events files. */
|
|
149
208
|
eventPopupContext(workspaceId, snapshot) {
|
|
150
209
|
const sources = [...snapshot.index.files.values()]
|
|
@@ -170,7 +229,9 @@ export class ReferenceToolService {
|
|
|
170
229
|
return cached.context;
|
|
171
230
|
const localisation = new Map();
|
|
172
231
|
for (const symbol of snapshot.index.symbols) {
|
|
173
|
-
if (symbol.kind !== 'localisation' ||
|
|
232
|
+
if (symbol.kind !== 'localisation' ||
|
|
233
|
+
symbol.sourceShadowed ||
|
|
234
|
+
!symbol.id.startsWith('l_english:'))
|
|
174
235
|
continue;
|
|
175
236
|
const key = symbol.id.slice('l_english:'.length);
|
|
176
237
|
const previous = localisation.get(key);
|
|
@@ -216,7 +277,15 @@ export class ReferenceToolService {
|
|
|
216
277
|
return context;
|
|
217
278
|
}
|
|
218
279
|
}
|
|
219
|
-
|
|
280
|
+
/** The families file mode checks, the helpers and databases it resolves, and localisation. */
|
|
281
|
+
const fileCheckPatterns = [
|
|
282
|
+
'events/**/*.txt',
|
|
283
|
+
'common/decisions/**/*.txt',
|
|
284
|
+
'common/national_focus/**/*.txt',
|
|
285
|
+
'common/on_actions/**/*.txt',
|
|
286
|
+
'common/scripted_effects/**/*.txt',
|
|
287
|
+
'common/scripted_triggers/**/*.txt',
|
|
288
|
+
...Object.keys(implicitTriggerSources).map((folder) => `common/${folder}/*.txt`),
|
|
220
289
|
'localisation/**/*_l_english.yml',
|
|
221
290
|
'common/scripted_localisation/**/*.txt',
|
|
222
291
|
];
|
|
@@ -7,7 +7,8 @@ const tooltipTriggerBlocks = {
|
|
|
7
7
|
};
|
|
8
8
|
/** Words that describe the mod's development rather than the game world. */
|
|
9
9
|
const implementationWording = /\b(?:TODO|FIXME|TBD|hard-?coded|newly added|(?:has been|was|been) reworked|placeholder)\b/iu;
|
|
10
|
-
|
|
10
|
+
/** Colour codes; text icons (£) are legible on parchment and stay allowed. */
|
|
11
|
+
const colourCode = '§';
|
|
11
12
|
function blockOf(entry) {
|
|
12
13
|
return entry.value.type === 'block' ? entry.value : undefined;
|
|
13
14
|
}
|
|
@@ -61,14 +62,14 @@ export function playerTextFindings(family, document, context) {
|
|
|
61
62
|
add(node, 'SCRIPT_TEXT_IMPLEMENTATION_WORDING', `${key} (${where(text)}) describes development rather than the game: "${implementationWording.exec(text.value)[0]}"`);
|
|
62
63
|
if (!options.colourFree)
|
|
63
64
|
return;
|
|
64
|
-
if (
|
|
65
|
+
if (text.value.includes(colourCode)) {
|
|
65
66
|
add(node, 'SCRIPT_NEWS_TEXT_COLOUR_CODE', `${key} (${where(text)}) uses a colour code in news or report event text`);
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
69
|
for (const name of scriptedTokens(text.value, context))
|
|
69
70
|
for (const branchKey of context.scriptedLocalisation.get(name) ?? []) {
|
|
70
71
|
const branch = context.localisation.get(branchKey);
|
|
71
|
-
if (branch
|
|
72
|
+
if (branch?.value.includes(colourCode) === true) {
|
|
72
73
|
add(node, 'SCRIPT_NEWS_TEXT_COLOUR_CODE', `${key} (${where(text)}) shows [${name}], whose branch ${branchKey} (${where(branch)}) uses a colour code in news or report event text`);
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
@@ -4,6 +4,7 @@ import { ServiceError } from '../core/result.js';
|
|
|
4
4
|
import { locationFor, parseClausewitz, } from '../core/source/index.js';
|
|
5
5
|
import { repeatedPopupFindings } from './event-popups.js';
|
|
6
6
|
import { playerTextFindings, playerTextPosition } from './player-text.js';
|
|
7
|
+
import { knownMistakeFindings } from './known-mistakes.js';
|
|
7
8
|
import { editDistance } from './suggestions.js';
|
|
8
9
|
import { publicReferenceSection } from './service.js';
|
|
9
10
|
const MAX_COMMANDS = 256;
|
|
@@ -487,10 +488,26 @@ export async function validateScript(references, workspace, input, signal, file)
|
|
|
487
488
|
scope: UNKNOWN_SCOPE,
|
|
488
489
|
line: position.line,
|
|
489
490
|
column: position.column,
|
|
490
|
-
message: finding.message,
|
|
491
|
+
message: finding.message.slice(0, 500),
|
|
491
492
|
suggestions: [],
|
|
492
493
|
});
|
|
493
494
|
}
|
|
495
|
+
const knownMistakes = plan.family === 'snippet' || plan.family === 'unsupported' ? undefined : file?.knownMistakes;
|
|
496
|
+
if (knownMistakes !== undefined)
|
|
497
|
+
for (const finding of knownMistakeFindings(plan.family, document, knownMistakes)) {
|
|
498
|
+
const position = playerTextPosition(documentPath, document.lineIndex, finding);
|
|
499
|
+
findings.push({
|
|
500
|
+
code: finding.code,
|
|
501
|
+
status: finding.rule.severity,
|
|
502
|
+
command: finding.command.slice(0, 256),
|
|
503
|
+
kind: 'definition',
|
|
504
|
+
scope: UNKNOWN_SCOPE,
|
|
505
|
+
line: position.line,
|
|
506
|
+
column: position.column,
|
|
507
|
+
message: finding.message.slice(0, 500),
|
|
508
|
+
suggestions: [],
|
|
509
|
+
});
|
|
510
|
+
}
|
|
494
511
|
const unresolvedCount = findings.filter(({ status }) => status === 'unresolved').length;
|
|
495
512
|
const invalid = findings.some(({ status }) => status === 'error');
|
|
496
513
|
const truncated = visited > commandLimit;
|
|
@@ -513,6 +530,7 @@ export async function validateScript(references, workspace, input, signal, file)
|
|
|
513
530
|
'declared_scope',
|
|
514
531
|
...(playerTextChecked ? ['player_text'] : []),
|
|
515
532
|
...(eventPopups === undefined ? [] : ['event_popups']),
|
|
533
|
+
...(knownMistakes === undefined ? [] : ['known_mistakes']),
|
|
516
534
|
],
|
|
517
535
|
parametersChecked: false,
|
|
518
536
|
documentation: {
|
|
@@ -6,6 +6,7 @@ import { ServiceError } from '../core/result.js';
|
|
|
6
6
|
import { isWithin } from '../core/workspace.js';
|
|
7
7
|
import { prepareSource, verifySource, readBoundedFile, } from '../core/scanner.js';
|
|
8
8
|
import { decodeSource } from '../core/source/encoding.js';
|
|
9
|
+
import { activeKnownMistakes } from './known-mistakes.js';
|
|
9
10
|
import { readBoundedJsonLines } from './lines.js';
|
|
10
11
|
import { RankingIndex, sectionTerms } from './ranking.js';
|
|
11
12
|
const MAX_FILES = 512;
|
|
@@ -544,6 +545,11 @@ export class ReferenceService {
|
|
|
544
545
|
missing,
|
|
545
546
|
coverage: inventory.coverage,
|
|
546
547
|
skipped: inventory.skipped,
|
|
548
|
+
knownMistakes: activeKnownMistakes(input.surface).map(({ id, title, fix }) => ({
|
|
549
|
+
id,
|
|
550
|
+
title,
|
|
551
|
+
fix,
|
|
552
|
+
})),
|
|
547
553
|
};
|
|
548
554
|
}
|
|
549
555
|
publicSection(section) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { lstat, readdir, stat } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
3
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
4
|
import { loadConfiguration, serverConfigurationSchema, } from './core/configuration.js';
|
|
5
5
|
import { CoreEngine } from './core/engine.js';
|
|
6
6
|
import { ServiceError } from './core/result.js';
|
|
7
7
|
import { WorkspaceResolver } from './core/workspace.js';
|
|
8
8
|
import { CONFIG_VERSION } from './version.js';
|
|
9
|
+
import { sweepTemporaryDirectories } from './core/temporary-directories.js';
|
|
9
10
|
export function defaultConfigurationPath() {
|
|
10
11
|
const configRoot = process.env.APPDATA ?? process.env.XDG_CONFIG_HOME ?? path.join(homedir(), '.config');
|
|
11
12
|
return path.join(configRoot, 'hoi4-agent-tools', 'config.json');
|
|
@@ -236,6 +237,18 @@ export async function createEngine(configPath = configurationPath()) {
|
|
|
236
237
|
configuration = await automaticConfiguration();
|
|
237
238
|
}
|
|
238
239
|
const resolver = await WorkspaceResolver.create(configuration);
|
|
240
|
+
await sweepTemporaryDirectories(tmpdir(), {
|
|
241
|
+
protectedRoots: [
|
|
242
|
+
...resolver
|
|
243
|
+
.list()
|
|
244
|
+
.flatMap((workspace) => [
|
|
245
|
+
...workspace.roots.map((root) => root.path),
|
|
246
|
+
workspace.artifactRoot,
|
|
247
|
+
workspace.cacheRoot,
|
|
248
|
+
]),
|
|
249
|
+
...(resolver.serverState() === undefined ? [] : [resolver.serverState().root]),
|
|
250
|
+
],
|
|
251
|
+
}).catch(() => undefined);
|
|
239
252
|
const engine = new CoreEngine(resolver);
|
|
240
253
|
await engine.initialize();
|
|
241
254
|
return engine;
|
|
@@ -112,25 +112,6 @@ export function validateEventInspectRequest(value, context) {
|
|
|
112
112
|
message: 'Impact analysis requires impactSubject',
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
|
-
export const eventInspectRequestSchema = z
|
|
116
|
-
.object({
|
|
117
|
-
workspaceId: workspaceIdSchema,
|
|
118
|
-
mode: eventInspectModeSchema,
|
|
119
|
-
selector: eventSelectorSchema.optional(),
|
|
120
|
-
from: eventSelectorSchema.optional(),
|
|
121
|
-
to: eventSelectorSchema.optional(),
|
|
122
|
-
direction: eventDirectionSchema.optional(),
|
|
123
|
-
maxDepth: z.number().int().min(1).max(64).optional(),
|
|
124
|
-
maxNodes: z.number().int().min(1).max(5_000).optional(),
|
|
125
|
-
maxEdges: z.number().int().min(1).max(20_000).optional(),
|
|
126
|
-
expandHelpers: z.boolean().optional(),
|
|
127
|
-
stateSubject: eventStateSubjectSchema.optional(),
|
|
128
|
-
impactSubject: eventImpactSubjectSchema.optional(),
|
|
129
|
-
helperExpansion: helperExpansionRequestSchema.optional(),
|
|
130
|
-
refresh: z.boolean().optional(),
|
|
131
|
-
})
|
|
132
|
-
.strict()
|
|
133
|
-
.superRefine(validateEventInspectRequest);
|
|
134
115
|
export const eventRenderRequestSchema = z
|
|
135
116
|
.object({
|
|
136
117
|
workspaceId: workspaceIdSchema,
|
|
@@ -172,3 +153,81 @@ export const eventCompareRequestSchema = z
|
|
|
172
153
|
})
|
|
173
154
|
.strict()
|
|
174
155
|
.superRefine(validateEventCompareRequest);
|
|
156
|
+
export const eventInspectToolModeSchema = z.enum([...eventInspectModeSchema.options, 'compare']);
|
|
157
|
+
const compareOnlyFields = [
|
|
158
|
+
'before',
|
|
159
|
+
'after',
|
|
160
|
+
'proposedSources',
|
|
161
|
+
'maxChainNodes',
|
|
162
|
+
'render',
|
|
163
|
+
'maxRenderNodes',
|
|
164
|
+
];
|
|
165
|
+
const inspectOnlyFields = [
|
|
166
|
+
'from',
|
|
167
|
+
'to',
|
|
168
|
+
'direction',
|
|
169
|
+
'maxDepth',
|
|
170
|
+
'maxNodes',
|
|
171
|
+
'maxEdges',
|
|
172
|
+
'expandHelpers',
|
|
173
|
+
'stateSubject',
|
|
174
|
+
'impactSubject',
|
|
175
|
+
'helperExpansion',
|
|
176
|
+
];
|
|
177
|
+
/**
|
|
178
|
+
* Inspection and comparison share one tool: mode compare diffs a cached, artifact or proposed
|
|
179
|
+
* event graph against another, every other mode analyzes the current graph.
|
|
180
|
+
*/
|
|
181
|
+
export const eventInspectRequestSchema = z
|
|
182
|
+
.object({
|
|
183
|
+
workspaceId: workspaceIdSchema,
|
|
184
|
+
mode: eventInspectToolModeSchema,
|
|
185
|
+
selector: eventSelectorSchema.optional(),
|
|
186
|
+
from: eventSelectorSchema.optional(),
|
|
187
|
+
to: eventSelectorSchema.optional(),
|
|
188
|
+
direction: eventDirectionSchema.optional(),
|
|
189
|
+
maxDepth: z.number().int().min(1).max(64).optional(),
|
|
190
|
+
maxNodes: z.number().int().min(1).max(5_000).optional(),
|
|
191
|
+
maxEdges: z.number().int().min(1).max(20_000).optional(),
|
|
192
|
+
expandHelpers: z.boolean().optional(),
|
|
193
|
+
stateSubject: eventStateSubjectSchema.optional(),
|
|
194
|
+
impactSubject: eventImpactSubjectSchema.optional(),
|
|
195
|
+
helperExpansion: helperExpansionRequestSchema.optional(),
|
|
196
|
+
before: eventGraphReferenceSchema.optional(),
|
|
197
|
+
after: eventGraphReferenceSchema.optional(),
|
|
198
|
+
proposedSources: z.array(eventProposedSourceSchema).min(1).max(64).optional(),
|
|
199
|
+
maxChainNodes: z.number().int().min(1).max(5_000).optional(),
|
|
200
|
+
render: z.boolean().optional(),
|
|
201
|
+
maxRenderNodes: z.number().int().min(1).max(240).optional(),
|
|
202
|
+
refresh: z.boolean().optional(),
|
|
203
|
+
})
|
|
204
|
+
.strict()
|
|
205
|
+
.superRefine(validateEventInspectToolRequest);
|
|
206
|
+
export function validateEventInspectToolRequest(value, context) {
|
|
207
|
+
const compare = value.mode === 'compare';
|
|
208
|
+
for (const field of compare ? inspectOnlyFields : compareOnlyFields)
|
|
209
|
+
if (value[field] !== undefined)
|
|
210
|
+
context.addIssue({
|
|
211
|
+
code: 'custom',
|
|
212
|
+
path: [field],
|
|
213
|
+
message: compare
|
|
214
|
+
? `${field} is not used by mode compare`
|
|
215
|
+
: `${field} is used only by mode compare`,
|
|
216
|
+
});
|
|
217
|
+
if (compare)
|
|
218
|
+
validateEventCompareRequest(value, context);
|
|
219
|
+
else
|
|
220
|
+
validateEventInspectRequest(value, context);
|
|
221
|
+
}
|
|
222
|
+
/** Split an inspect request into a comparison or an analysis of the current graph. */
|
|
223
|
+
export function splitEventInspectRequest(input) {
|
|
224
|
+
const { mode, ...rest } = input;
|
|
225
|
+
if (mode === 'compare') {
|
|
226
|
+
const request = { workspaceId: rest.workspaceId };
|
|
227
|
+
for (const field of [...compareOnlyFields, 'selector', 'refresh'])
|
|
228
|
+
if (rest[field] !== undefined)
|
|
229
|
+
request[field] = rest[field];
|
|
230
|
+
return { compare: true, request: eventCompareRequestSchema.parse(request) };
|
|
231
|
+
}
|
|
232
|
+
return { compare: false, request: { ...rest, mode } };
|
|
233
|
+
}
|
|
@@ -77,10 +77,20 @@ export const focusRenderRequestSchema = z
|
|
|
77
77
|
cropFocusIds: z.array(z.string().min(1).max(256)).max(16).optional(),
|
|
78
78
|
columns: z.number().int().min(1).max(12).optional(),
|
|
79
79
|
padding: z.number().int().min(0).max(1000).optional(),
|
|
80
|
+
png: z
|
|
81
|
+
.boolean()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe('Default true: add a PNG with decoded source icons. False returns only the faster HTML, SVG and JSON.'),
|
|
80
84
|
})
|
|
81
85
|
.strict()
|
|
82
86
|
.superRefine((value, context) => {
|
|
83
87
|
const mode = value.mode ?? 'national';
|
|
88
|
+
if (value.png === false && (value.cropFocusIds?.length ?? 0) > 0)
|
|
89
|
+
context.addIssue({
|
|
90
|
+
code: 'custom',
|
|
91
|
+
path: ['cropFocusIds'],
|
|
92
|
+
message: 'cropFocusIds produce PNG crops; omit png or set it to true',
|
|
93
|
+
});
|
|
84
94
|
const invalid = mode === 'national'
|
|
85
95
|
? [
|
|
86
96
|
['paletteId', value.paletteId],
|