hoi4-agent-tools 3.0.8 → 3.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.
- package/CHANGELOG.md +35 -1
- package/README.md +4 -0
- package/dist/bin/stdio.js +23 -12
- package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
- package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +399 -0
- package/dist/hoi4_agent_tools/core/condition-model.js +1 -0
- package/dist/hoi4_agent_tools/core/condition-schema.js +42 -0
- package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
- package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
- package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
- package/dist/hoi4_agent_tools/core/engine.js +48 -5
- package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
- package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
- package/dist/hoi4_agent_tools/core/index.js +47 -4
- package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
- package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
- package/dist/hoi4_agent_tools/core/job-service.js +112 -0
- package/dist/hoi4_agent_tools/core/job-store.js +641 -0
- package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
- package/dist/hoi4_agent_tools/core/job-worker-host.js +200 -0
- package/dist/hoi4_agent_tools/core/job-worker.js +62 -0
- package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
- package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
- package/dist/hoi4_agent_tools/core/operation-tasks.js +382 -0
- package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
- package/dist/hoi4_agent_tools/core/result.js +1 -0
- package/dist/hoi4_agent_tools/core/scanner.js +15 -75
- package/dist/hoi4_agent_tools/core/scenario-state.js +97 -0
- package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +74 -25
- package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
- package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
- package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
- package/dist/hoi4_agent_tools/core/transactions.js +80 -27
- package/dist/hoi4_agent_tools/event/graph.js +214 -290
- package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
- package/dist/hoi4_agent_tools/event/operations.js +179 -0
- package/dist/hoi4_agent_tools/event/service.js +67 -15
- package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
- package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
- package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
- package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
- package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
- package/dist/hoi4_agent_tools/gui/scenario-conditions.js +119 -0
- package/dist/hoi4_agent_tools/gui/scenario-generator.js +59 -73
- package/dist/hoi4_agent_tools/gui/scenario.js +3 -0
- package/dist/hoi4_agent_tools/gui/studio.js +5 -2
- package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
- package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
- package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
- package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
- package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
- package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
- package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
- package/dist/hoi4_agent_tools/mcp/server/create.js +16 -6
- package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
- package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
- package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
- package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +43 -0
- package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
- package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +14 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
- package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
- package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
- package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
- package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
- package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
- package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
- package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
- package/dist/hoi4_agent_tools/probability/operations.js +183 -0
- package/dist/hoi4_agent_tools/probability/scenario-state.js +2 -95
- package/dist/hoi4_agent_tools/probability/service.js +24 -3
- package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +1 -287
- package/dist/hoi4_agent_tools/schemas/common.js +2 -0
- package/dist/hoi4_agent_tools/schemas/event.js +72 -1
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
- package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
- package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
- package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
- package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
- package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
- package/dist/hoi4_agent_tools/technology/graph.js +83 -78
- package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
- package/dist/hoi4_agent_tools/technology/operations.js +125 -0
- package/dist/hoi4_agent_tools/technology/service.js +89 -15
- package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +2 -0
- package/docs/development.md +2 -0
- package/docs/events.md +7 -2
- package/docs/gui.md +3 -1
- package/docs/helper-expansion.md +78 -0
- package/docs/http.md +2 -0
- package/docs/jobs.md +85 -0
- package/docs/technology.md +6 -2
- package/package.json +11 -5
- package/schemas/configuration.schema.json +1 -1
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- 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 +189 -33
- package/schemas/helper-expansion-request.schema.json +44 -0
- package/schemas/helper-expansion-summary.schema.json +70 -0
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/operation-result.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/server.json +2 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { canonicalJson, compareCodeUnits, deterministicId, hashCanonical, } from '../core/canonical.js';
|
|
2
2
|
import { sortDiagnostics } from '../core/diagnostics.js';
|
|
3
3
|
import { ServiceError } from '../core/result.js';
|
|
4
|
+
import { beginSemanticAnalysis, semanticFragment, SemanticCatalog, } from '../core/semantic-dependencies.js';
|
|
5
|
+
import { runDependencyAnalysis, runDependencyAnalysisAsync, } from '../core/dependency-walk.js';
|
|
4
6
|
import { analyzeTechnologySource, technologyAnalysisDiagnostics, technologySourceFiles, technologySourceFragmentCacheKey, } from './source-analysis.js';
|
|
5
7
|
import { TECHNOLOGY_GRAPH_SCHEMA_VERSION, TECHNOLOGY_PARSER_VERSION, } from './model.js';
|
|
6
8
|
const MAX_TECHNOLOGIES = 100_000;
|
|
@@ -374,7 +376,24 @@ function stronglyConnectedComponents(nodeIds, edges, signal) {
|
|
|
374
376
|
}
|
|
375
377
|
return components.sort((left, right) => compareCodeUnits(left[0] ?? '', right[0] ?? ''));
|
|
376
378
|
}
|
|
377
|
-
function
|
|
379
|
+
function technologyHelperWalk(calls, sourceRevision) {
|
|
380
|
+
return {
|
|
381
|
+
domain: 'technology-helper-references',
|
|
382
|
+
sourceRevision,
|
|
383
|
+
edges: calls.map(({ id, sourceKind, sourceId, helperId }) => ({
|
|
384
|
+
id,
|
|
385
|
+
from: `${sourceKind}:${sourceId}`,
|
|
386
|
+
to: `scripted_effect:${helperId}`,
|
|
387
|
+
})),
|
|
388
|
+
roots: calls.filter(({ sourceKind }) => sourceKind !== 'scripted_effect').map(({ id }) => id),
|
|
389
|
+
branches: [...new Set(calls.map(({ helperId }) => `scripted_effect:${helperId}`))].sort(compareCodeUnits),
|
|
390
|
+
maximumDepth: MAX_HELPER_DEPTH,
|
|
391
|
+
maximumSteps: 10_000_000,
|
|
392
|
+
visitPolicy: 'path',
|
|
393
|
+
reverseEdges: false,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function expandHelperReferences(references, calls, unresolved, analysisMode, walk, signal) {
|
|
378
397
|
const direct = references.filter(({ sourceKind }) => sourceKind === 'scripted_effect');
|
|
379
398
|
const publicReferences = references.filter(({ sourceKind }) => sourceKind !== 'scripted_effect');
|
|
380
399
|
if (analysisMode === 'focused') {
|
|
@@ -404,82 +423,59 @@ function expandHelperReferences(references, calls, unresolved, analysisMode, sig
|
|
|
404
423
|
group.push(reference);
|
|
405
424
|
refsByHelper.set(reference.sourceId, group);
|
|
406
425
|
}
|
|
407
|
-
const callsBySource = new Map();
|
|
408
|
-
for (const call of calls) {
|
|
409
|
-
const key = `${call.sourceKind}:${call.sourceId}`;
|
|
410
|
-
const group = callsBySource.get(key) ?? [];
|
|
411
|
-
group.push(call);
|
|
412
|
-
callsBySource.set(key, group);
|
|
413
|
-
}
|
|
414
426
|
const projected = [...publicReferences, ...direct];
|
|
415
427
|
let projectionCount = 0;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
blockers: [
|
|
441
|
-
{
|
|
442
|
-
code: 'TECH_HELPER_DEPTH_BLOCKED',
|
|
443
|
-
message: `Scripted-effect expansion exceeds depth ${MAX_HELPER_DEPTH}`,
|
|
444
|
-
location: rootCall.location,
|
|
445
|
-
},
|
|
446
|
-
],
|
|
447
|
-
});
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
for (const reference of refsByHelper.get(current.helperId) ?? []) {
|
|
451
|
-
projectionCount += 1;
|
|
452
|
-
if (projectionCount > MAX_HELPER_PROJECTIONS)
|
|
453
|
-
break;
|
|
454
|
-
projected.push({
|
|
455
|
-
...reference,
|
|
456
|
-
id: deterministicId('tech-external-projection', {
|
|
457
|
-
root: rootCall.id,
|
|
458
|
-
reference: reference.id,
|
|
459
|
-
helperStack: current.helperStack,
|
|
460
|
-
}),
|
|
461
|
-
sourceKind: rootCall.sourceKind,
|
|
462
|
-
sourceId: rootCall.sourceId,
|
|
463
|
-
helperStack: [...current.helperStack],
|
|
464
|
-
confidence: worseConfidence(current.confidence, reference.confidence),
|
|
465
|
-
metadata: {
|
|
466
|
-
...reference.metadata,
|
|
467
|
-
projectedFromScriptedEffect: reference.sourceId,
|
|
468
|
-
callLocation: canonicalJson(rootCall.location),
|
|
428
|
+
if (walk === undefined)
|
|
429
|
+
throw new Error('Full technology helper analysis requires a dependency traversal');
|
|
430
|
+
const roots = calls.filter(({ sourceKind }) => sourceKind !== 'scripted_effect');
|
|
431
|
+
const callById = new Map(calls.map((call) => [call.id, call]));
|
|
432
|
+
for (const record of walk.state.records) {
|
|
433
|
+
signal?.throwIfAborted();
|
|
434
|
+
if (record.kind !== 'visit' && record.kind !== 'depth')
|
|
435
|
+
continue;
|
|
436
|
+
const rootCall = roots[record.root];
|
|
437
|
+
const path = walk.path(record.path).map(({ id }) => callById.get(id));
|
|
438
|
+
const helperStack = path.map(({ helperId }) => helperId);
|
|
439
|
+
if (record.kind === 'depth') {
|
|
440
|
+
unresolved.push({
|
|
441
|
+
id: deterministicId('tech-unresolved', { rootCall, helperStack }),
|
|
442
|
+
kind: 'unsupported_construct',
|
|
443
|
+
expression: helperStack.join(' -> '),
|
|
444
|
+
ownerId: rootCall.sourceId,
|
|
445
|
+
location: rootCall.location,
|
|
446
|
+
confidence: 'unresolved',
|
|
447
|
+
blockers: [
|
|
448
|
+
{
|
|
449
|
+
code: 'TECH_HELPER_DEPTH_BLOCKED',
|
|
450
|
+
message: `Scripted-effect expansion exceeds depth ${MAX_HELPER_DEPTH}`,
|
|
451
|
+
location: rootCall.location,
|
|
469
452
|
},
|
|
470
|
-
|
|
471
|
-
}
|
|
453
|
+
],
|
|
454
|
+
});
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const confidence = path.reduce((value, call) => worseConfidence(value, call.confidence), rootCall.confidence);
|
|
458
|
+
for (const reference of refsByHelper.get(path.at(-1).helperId) ?? []) {
|
|
459
|
+
projectionCount++;
|
|
472
460
|
if (projectionCount > MAX_HELPER_PROJECTIONS)
|
|
473
461
|
break;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
helperStack
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
462
|
+
projected.push({
|
|
463
|
+
...reference,
|
|
464
|
+
id: deterministicId('tech-external-projection', {
|
|
465
|
+
root: rootCall.id,
|
|
466
|
+
reference: reference.id,
|
|
467
|
+
helperStack,
|
|
468
|
+
}),
|
|
469
|
+
sourceKind: rootCall.sourceKind,
|
|
470
|
+
sourceId: rootCall.sourceId,
|
|
471
|
+
helperStack,
|
|
472
|
+
confidence: worseConfidence(confidence, reference.confidence),
|
|
473
|
+
metadata: {
|
|
474
|
+
...reference.metadata,
|
|
475
|
+
projectedFromScriptedEffect: reference.sourceId,
|
|
476
|
+
callLocation: canonicalJson(rootCall.location),
|
|
477
|
+
},
|
|
478
|
+
});
|
|
483
479
|
}
|
|
484
480
|
if (projectionCount > MAX_HELPER_PROJECTIONS)
|
|
485
481
|
break;
|
|
@@ -1123,14 +1119,13 @@ function fragmentsFor(snapshot, options) {
|
|
|
1123
1119
|
};
|
|
1124
1120
|
const fingerprint = helperCatalogFingerprint(snapshot);
|
|
1125
1121
|
const fragments = [];
|
|
1122
|
+
const dependencies = beginSemanticAnalysis(options.cache, snapshot, 'technology', options.workspaceIdentity);
|
|
1123
|
+
const reads = new SemanticCatalog();
|
|
1124
|
+
context.helperIds = reads.bind('helpers', 'scripted_effect', helperIds);
|
|
1126
1125
|
for (const file of technologySourceFiles(snapshot.files)) {
|
|
1127
1126
|
options.signal?.throwIfAborted();
|
|
1128
1127
|
const key = technologySourceFragmentCacheKey(file, fingerprint);
|
|
1129
|
-
|
|
1130
|
-
if (fragment === undefined) {
|
|
1131
|
-
fragment = analyzeTechnologySource(file, context);
|
|
1132
|
-
options.cache?.set(key, fragment, file.bytes.length);
|
|
1133
|
-
}
|
|
1128
|
+
const fragment = semanticFragment(dependencies, options.cache, file, key, reads, () => analyzeTechnologySource(file, context));
|
|
1134
1129
|
fragments.push(fragment);
|
|
1135
1130
|
}
|
|
1136
1131
|
return fragments;
|
|
@@ -1143,6 +1138,12 @@ function resolveUnlocks(unlocks, targets, complete) {
|
|
|
1143
1138
|
}));
|
|
1144
1139
|
}
|
|
1145
1140
|
export function buildTechnologyGraph(snapshot, options) {
|
|
1141
|
+
return runDependencyAnalysis(technologyGraphAnalysis(snapshot, options), options.signal);
|
|
1142
|
+
}
|
|
1143
|
+
export function buildTechnologyGraphAsync(snapshot, options) {
|
|
1144
|
+
return runDependencyAnalysisAsync(technologyGraphAnalysis(snapshot, options), options.signal);
|
|
1145
|
+
}
|
|
1146
|
+
function* technologyGraphAnalysis(snapshot, options) {
|
|
1146
1147
|
options.signal?.throwIfAborted();
|
|
1147
1148
|
const fragments = fragmentsFor(snapshot, options);
|
|
1148
1149
|
const assets = assetMap(options.assetFiles ?? []);
|
|
@@ -1167,7 +1168,10 @@ export function buildTechnologyGraph(snapshot, options) {
|
|
|
1167
1168
|
const placements = enrichPlacements(basePlacements, edges, gridboxes, technologies, unlocks, itemLayouts, options.signal);
|
|
1168
1169
|
const unresolved = fragments.flatMap(({ unresolved }) => unresolved);
|
|
1169
1170
|
const helperCalls = fragments.flatMap(({ helperCalls }) => helperCalls);
|
|
1170
|
-
const
|
|
1171
|
+
const walk = options.analysisMode === 'focused'
|
|
1172
|
+
? undefined
|
|
1173
|
+
: yield technologyHelperWalk(helperCalls, snapshot.revision);
|
|
1174
|
+
const externalReferences = expandHelperReferences(fragments.flatMap(({ externalReferences }) => externalReferences), helperCalls, unresolved, options.analysisMode ?? 'full', walk, options.signal);
|
|
1171
1175
|
const issues = diagnose(technologies, folders, placements, gridboxes, edges, categories, doctrineDefinitions, unlocks, externalReferences, selectedTechnologies.duplicates.map((group) => group.map(({ iconSprite, effectSignatureFields, ...technology }) => ({
|
|
1172
1176
|
...technology,
|
|
1173
1177
|
icon: iconStatus(iconSprite, snapshot.index, assets),
|
|
@@ -1199,6 +1203,7 @@ export function buildTechnologyGraph(snapshot, options) {
|
|
|
1199
1203
|
revision,
|
|
1200
1204
|
complete: snapshot.complete &&
|
|
1201
1205
|
unresolved.every(({ kind }) => kind !== 'partial_source') &&
|
|
1206
|
+
!unresolved.some(({ blockers }) => blockers.some(({ code }) => code === 'TECH_HELPER_DEPTH_BLOCKED' || code === 'TECH_HELPER_PROJECTION_LIMIT')) &&
|
|
1202
1207
|
!(options.analysisMode === 'focused' && helperCalls.length > 0),
|
|
1203
1208
|
analysisBoundary: {
|
|
1204
1209
|
staticAnalysis: true,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { compareCodeUnits } from '../core/canonical.js';
|
|
2
|
+
export function technologyHelperInventory(graph, sourceComplete) {
|
|
3
|
+
return {
|
|
4
|
+
domain: 'technology',
|
|
5
|
+
sourceRevision: graph.revision,
|
|
6
|
+
sourceComplete,
|
|
7
|
+
sourceHashes: graph.sourceHashes,
|
|
8
|
+
unresolved: graph.unresolved.filter(({ id }) => id !== 'tech-unresolved-helper-expansion-deferred'),
|
|
9
|
+
edges: [
|
|
10
|
+
...graph.helperCalls.map((call) => ({
|
|
11
|
+
id: call.id,
|
|
12
|
+
from: `${call.sourceKind}:${call.sourceId}`,
|
|
13
|
+
to: `scripted_effect:${call.helperId}`,
|
|
14
|
+
evidence: { kind: 'helper_call', call },
|
|
15
|
+
})),
|
|
16
|
+
...graph.externalReferences
|
|
17
|
+
.filter(({ sourceKind }) => sourceKind === 'scripted_effect')
|
|
18
|
+
.map((reference) => ({
|
|
19
|
+
id: `reference:${reference.id}`,
|
|
20
|
+
from: `scripted_effect:${reference.sourceId}`,
|
|
21
|
+
to: `reference:${reference.id}`,
|
|
22
|
+
evidence: { kind: 'technology_reference', reference },
|
|
23
|
+
})),
|
|
24
|
+
].sort((left, right) => compareCodeUnits(left.id, right.id)),
|
|
25
|
+
roots: graph.helperCalls
|
|
26
|
+
.filter(({ sourceKind }) => sourceKind !== 'scripted_effect')
|
|
27
|
+
.map(({ id }) => id)
|
|
28
|
+
.sort(compareCodeUnits),
|
|
29
|
+
branches: [
|
|
30
|
+
...new Set(graph.helperCalls.map(({ helperId }) => `scripted_effect:${helperId}`)),
|
|
31
|
+
].sort(compareCodeUnits),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { IdleCacheLifetime } from '../core/idle-cache-lifetime.js';
|
|
3
|
+
import { errorResult, toolResult } from '../core/operation-result.js';
|
|
4
|
+
import { ServiceError } from '../core/result.js';
|
|
5
|
+
import { technologyCompareRequestSchema, technologyInspectRequestSchema, technologyRenderRequestSchema, } from '../schemas/technology.js';
|
|
6
|
+
import { compareTechnologies, inspectTechnologies, normalizeTechnologyCompareRequest, normalizeTechnologyInspectionRequest, normalizeTechnologyRenderRequest, renderTechnologies, } from './operations.js';
|
|
7
|
+
import { TechnologyTreeViewer } from './service.js';
|
|
8
|
+
const wireResult = z.record(z.string(), z.json());
|
|
9
|
+
/** Registers typed technology operations without invoking transport handlers. */
|
|
10
|
+
export function registerTechnologyJobs(operations, engine) {
|
|
11
|
+
const viewer = new TechnologyTreeViewer(engine);
|
|
12
|
+
const lifetime = new IdleCacheLifetime(() => viewer.clearCaches());
|
|
13
|
+
function register(name, schema, normalize, execute, message) {
|
|
14
|
+
operations.registerRead(name, schema, async (input, context) => {
|
|
15
|
+
const release = lifetime.begin();
|
|
16
|
+
try {
|
|
17
|
+
if (input.workspaceId !== 'current' && input.workspaceId !== context.workspaceId)
|
|
18
|
+
throw new ServiceError('JOB_ARGUMENT_SCOPE_MISMATCH', 'The request workspace does not match the authorized job workspace');
|
|
19
|
+
await context.progress({ completed: 0, total: 3, message });
|
|
20
|
+
const result = await execute(viewer, {
|
|
21
|
+
...normalize(input),
|
|
22
|
+
workspaceId: context.workspaceId,
|
|
23
|
+
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
24
|
+
signal: context.signal,
|
|
25
|
+
});
|
|
26
|
+
await context.progress({
|
|
27
|
+
completed: 3,
|
|
28
|
+
total: 3,
|
|
29
|
+
message: 'Technology operation complete',
|
|
30
|
+
});
|
|
31
|
+
const wire = wireResult.parse(JSON.parse(JSON.stringify(toolResult(result))));
|
|
32
|
+
const revision = result.data !== null &&
|
|
33
|
+
typeof result.data === 'object' &&
|
|
34
|
+
'revision' in result.data &&
|
|
35
|
+
typeof result.data.revision === 'string'
|
|
36
|
+
? result.data.revision
|
|
37
|
+
: result.data !== null &&
|
|
38
|
+
typeof result.data === 'object' &&
|
|
39
|
+
'afterRevision' in result.data &&
|
|
40
|
+
typeof result.data.afterRevision === 'string'
|
|
41
|
+
? result.data.afterRevision
|
|
42
|
+
: undefined;
|
|
43
|
+
if (revision !== undefined)
|
|
44
|
+
await context.stageResult(wire, { sourceRevision: revision });
|
|
45
|
+
return wire;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (context.signal.aborted)
|
|
49
|
+
throw error;
|
|
50
|
+
return wireResult.parse(JSON.parse(JSON.stringify(errorResult(error, context.workspaceId))));
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
release();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
register('hoi4.tech_inspect', technologyInspectRequestSchema, normalizeTechnologyInspectionRequest, inspectTechnologies, 'Analyzing technology trees');
|
|
58
|
+
register('hoi4.tech_render', technologyRenderRequestSchema, normalizeTechnologyRenderRequest, renderTechnologies, 'Rendering technology-tree view');
|
|
59
|
+
register('hoi4.tech_compare', technologyCompareRequestSchema, normalizeTechnologyCompareRequest, compareTechnologies, 'Comparing technology-tree graphs');
|
|
60
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { publicArtifactLink } from '../core/artifacts.js';
|
|
2
|
+
import { helperExpansionValidation } from '../core/helper-expansion.js';
|
|
3
|
+
import { compareCodeUnits, hashCanonical } from '../core/canonical.js';
|
|
4
|
+
import { emptyServiceResult } from '../core/result.js';
|
|
5
|
+
import { setInlineFilesScanned } from '../core/operation-result.js';
|
|
6
|
+
import { technologyCompareRequestSchema, technologyInspectRequestSchema, technologyRenderRequestSchema, } from '../schemas/technology.js';
|
|
7
|
+
import { technologyDiagnostics } from './service.js';
|
|
8
|
+
export function normalizeTechnologyInspectionRequest(input) {
|
|
9
|
+
return JSON.parse(JSON.stringify(technologyInspectRequestSchema.parse(input)));
|
|
10
|
+
}
|
|
11
|
+
export function normalizeTechnologyRenderRequest(input) {
|
|
12
|
+
return JSON.parse(JSON.stringify(technologyRenderRequestSchema.parse(input)));
|
|
13
|
+
}
|
|
14
|
+
export function normalizeTechnologyCompareRequest(input) {
|
|
15
|
+
return JSON.parse(JSON.stringify(technologyCompareRequestSchema.parse(input)));
|
|
16
|
+
}
|
|
17
|
+
export function technologyGraphHash(graph) {
|
|
18
|
+
return hashCanonical({
|
|
19
|
+
schemaVersion: graph.schemaVersion,
|
|
20
|
+
analysisMode: graph.analysisMode ?? 'full',
|
|
21
|
+
workspaceIdentity: graph.workspaceIdentity,
|
|
22
|
+
revision: graph.revision,
|
|
23
|
+
statistics: graph.statistics,
|
|
24
|
+
sourceHashes: graph.sourceHashes,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export function technologyCounts(graph, artifacts) {
|
|
28
|
+
return {
|
|
29
|
+
technologies: graph.statistics.technologyCount,
|
|
30
|
+
legacyDoctrines: graph.statistics.legacyDoctrineCount,
|
|
31
|
+
folders: graph.statistics.folderCount,
|
|
32
|
+
placements: graph.statistics.placementCount,
|
|
33
|
+
edges: graph.edges.length,
|
|
34
|
+
unlocks: graph.statistics.unlockCount,
|
|
35
|
+
references: graph.statistics.externalReferenceCount,
|
|
36
|
+
issues: graph.statistics.issueCount,
|
|
37
|
+
unresolved: graph.statistics.unresolvedCount,
|
|
38
|
+
artifacts,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function technologyValidation(graph) {
|
|
42
|
+
const blocking = technologyDiagnostics(graph).filter(({ severity }) => severity === 'error' || severity === 'blocker').length;
|
|
43
|
+
return {
|
|
44
|
+
passed: graph.complete && blocking === 0,
|
|
45
|
+
checks: [
|
|
46
|
+
{
|
|
47
|
+
id: 'technology-analysis',
|
|
48
|
+
passed: graph.complete && blocking === 0,
|
|
49
|
+
message: graph.complete
|
|
50
|
+
? `${blocking} blocking technology diagnostics; full evidence is linked`
|
|
51
|
+
: graph.analysisMode === 'focused'
|
|
52
|
+
? 'Helper projections were deferred for this large workspace; direct evidence is linked'
|
|
53
|
+
: graph.unresolved.some(({ blockers }) => blockers.some(({ code }) => code === 'TECH_HELPER_DEPTH_BLOCKED' ||
|
|
54
|
+
code === 'TECH_HELPER_PROJECTION_LIMIT'))
|
|
55
|
+
? 'Helper expansion reached a depth or materialization boundary; helper_expansion mode provides bounded source-linked continuation'
|
|
56
|
+
: `${graph.skippedSourceCount} source(s) were skipped; full evidence is linked`,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export async function inspectTechnologies(viewer, input) {
|
|
62
|
+
const output = await viewer.analyze(input);
|
|
63
|
+
const result = emptyServiceResult(input.workspaceId, {
|
|
64
|
+
mode: input.mode,
|
|
65
|
+
revision: output.graph.revision,
|
|
66
|
+
graphHash: technologyGraphHash(output.graph),
|
|
67
|
+
counts: technologyCounts(output.graph, output.artifacts.length),
|
|
68
|
+
...(output.helperExpansion === undefined ? {} : { helperExpansion: output.helperExpansion }),
|
|
69
|
+
});
|
|
70
|
+
result.code =
|
|
71
|
+
(output.helperExpansion?.complete ?? output.graph.complete)
|
|
72
|
+
? 'TECH_INSPECTED'
|
|
73
|
+
: 'TECH_INSPECTED_PARTIAL';
|
|
74
|
+
setInlineFilesScanned(result, output.graph.filesScanned);
|
|
75
|
+
result.artifacts = output.artifacts.map(publicArtifactLink);
|
|
76
|
+
result.validation = technologyValidation(output.graph);
|
|
77
|
+
if (output.helperExpansion !== undefined)
|
|
78
|
+
result.validation = helperExpansionValidation(output.helperExpansion);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
export async function renderTechnologies(viewer, input) {
|
|
82
|
+
const output = await viewer.renderAndStore(input);
|
|
83
|
+
const result = emptyServiceResult(input.workspaceId, {
|
|
84
|
+
view: output.render.view,
|
|
85
|
+
revision: output.graph.revision,
|
|
86
|
+
graphHash: technologyGraphHash(output.graph),
|
|
87
|
+
hashes: output.render.hashes,
|
|
88
|
+
selectedNodes: output.render.selectedIds.length,
|
|
89
|
+
omittedNodes: output.render.omittedNodeCount,
|
|
90
|
+
focusedRenders: output.focused.length,
|
|
91
|
+
sourceAccurate: output.render.sourceAccurate,
|
|
92
|
+
});
|
|
93
|
+
result.code = output.graph.complete ? 'TECH_RENDERED' : 'TECH_RENDERED_PARTIAL';
|
|
94
|
+
setInlineFilesScanned(result, output.graph.filesScanned);
|
|
95
|
+
result.artifacts = output.artifacts.map(publicArtifactLink);
|
|
96
|
+
result.validation = technologyValidation(output.graph);
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
export async function compareTechnologies(viewer, input) {
|
|
100
|
+
const output = await viewer.compareAndStore(input);
|
|
101
|
+
const comparison = output.comparison;
|
|
102
|
+
const result = emptyServiceResult(input.workspaceId, {
|
|
103
|
+
beforeRevision: comparison.beforeRevision,
|
|
104
|
+
afterRevision: comparison.afterRevision,
|
|
105
|
+
added: comparison.technologies.added.length,
|
|
106
|
+
removed: comparison.technologies.removed.length,
|
|
107
|
+
renamed: comparison.technologies.renamed.length,
|
|
108
|
+
moved: comparison.technologies.moved.length,
|
|
109
|
+
regressions: comparison.regressions.length,
|
|
110
|
+
artifacts: output.artifacts.length,
|
|
111
|
+
...(output.render === undefined ? {} : { renderHashes: output.render.hashes }),
|
|
112
|
+
});
|
|
113
|
+
result.code =
|
|
114
|
+
output.before.complete && output.after.complete ? 'TECH_COMPARED' : 'TECH_COMPARED_PARTIAL';
|
|
115
|
+
setInlineFilesScanned(result, [...new Set([...output.before.filesScanned, ...output.after.filesScanned])].sort(compareCodeUnits));
|
|
116
|
+
result.artifacts = output.artifacts.map(publicArtifactLink);
|
|
117
|
+
result.validation = {
|
|
118
|
+
passed: technologyValidation(output.before).passed && technologyValidation(output.after).passed,
|
|
119
|
+
checks: [
|
|
120
|
+
...technologyValidation(output.before).checks,
|
|
121
|
+
...technologyValidation(output.after).checks,
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { inspectHelperExpansion } from '../core/helper-expansion.js';
|
|
3
|
+
import { technologyHelperInventory } from './helper-expansion.js';
|
|
4
|
+
import { clearSemanticDependencies } from '../core/semantic-dependencies.js';
|
|
2
5
|
import { canonicalJson, compareCodeUnits, hashCanonical, sha256Bytes } from '../core/canonical.js';
|
|
3
6
|
import { boundedSourceHashEvidence, } from '../core/artifacts.js';
|
|
4
7
|
import { RenderBudget } from '../core/render-budget.js';
|
|
@@ -9,7 +12,7 @@ import { PACKAGE_VERSION } from '../version.js';
|
|
|
9
12
|
import { GuiAssetCatalog } from '../gui/assets.js';
|
|
10
13
|
import { buildGuiSourceGraph } from '../gui/source-graph.js';
|
|
11
14
|
import { compareTechnologyGraphs } from './compare.js';
|
|
12
|
-
import { buildTechnologyGraph, technologyAssetPatterns } from './graph.js';
|
|
15
|
+
import { buildTechnologyGraph, buildTechnologyGraphAsync, technologyAssetPatterns, } from './graph.js';
|
|
13
16
|
import { TECHNOLOGY_GRAPH_SCHEMA_VERSION } from './model.js';
|
|
14
17
|
import { analyzeTechnologyImpact, discoverTechnologyFolders, explainTechnology, inspectTechnologyUnlocks, lintTechnologyGraph, technologyBonusCoverage, technologyScanReport, traceTechnology, } from './queries.js';
|
|
15
18
|
import { renderTechnologyGraph, technologyRenderIconSprites, } from './render.js';
|
|
@@ -48,6 +51,7 @@ class BoundedTechnologyFragmentCache {
|
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
clear() {
|
|
54
|
+
clearSemanticDependencies(this);
|
|
51
55
|
this.#values.clear();
|
|
52
56
|
this.#bytes = 0;
|
|
53
57
|
}
|
|
@@ -445,16 +449,17 @@ export class TechnologyTreeViewer {
|
|
|
445
449
|
const workspace = this.engine.resolver.get(workspaceId, options.principal);
|
|
446
450
|
const generation = this.engine.generation(workspaceId);
|
|
447
451
|
const cached = this.#state.current.get(workspaceId);
|
|
448
|
-
if (options.refresh !== true && cached?.generation === generation)
|
|
449
|
-
return cached.graph;
|
|
450
452
|
const snapshot = await scanTechnologySources(this.engine, workspaceId, workspace, options.principal, options.signal);
|
|
451
453
|
const analysisMode = 'full';
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
454
|
+
const unchangedSource = cached?.generation === generation && cached.snapshot.revision === snapshot.revision;
|
|
455
|
+
const preliminary = unchangedSource
|
|
456
|
+
? cached.graph
|
|
457
|
+
: await buildTechnologyGraphAsync(snapshot, {
|
|
458
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
459
|
+
cache: this.#state.fragments,
|
|
460
|
+
analysisMode,
|
|
461
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
462
|
+
});
|
|
458
463
|
const patterns = technologyAssetPatterns(preliminary);
|
|
459
464
|
const assetFiles = patterns.length === 0
|
|
460
465
|
? []
|
|
@@ -462,7 +467,15 @@ export class TechnologyTreeViewer {
|
|
|
462
467
|
patterns,
|
|
463
468
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
464
469
|
});
|
|
465
|
-
const
|
|
470
|
+
const assetIdentity = (files) => hashCanonical(files.map(({ absolutePath, displayPath, loadOrder, sha256 }) => ({
|
|
471
|
+
absolutePath,
|
|
472
|
+
displayPath,
|
|
473
|
+
loadOrder,
|
|
474
|
+
sha256,
|
|
475
|
+
})));
|
|
476
|
+
if (unchangedSource && assetIdentity(assetFiles) === assetIdentity(cached.assetFiles))
|
|
477
|
+
return cached.graph;
|
|
478
|
+
const graph = await buildTechnologyGraphAsync(snapshot, {
|
|
466
479
|
workspaceIdentity: workspace.workspaceIdentity,
|
|
467
480
|
cache: this.#state.fragments,
|
|
468
481
|
assetFiles,
|
|
@@ -474,6 +487,24 @@ export class TechnologyTreeViewer {
|
|
|
474
487
|
return graph;
|
|
475
488
|
}
|
|
476
489
|
async analyze(input) {
|
|
490
|
+
if (input.mode === 'helper_expansion') {
|
|
491
|
+
const workspace = this.engine.resolver.get(input.workspaceId, input.principal);
|
|
492
|
+
const snapshot = await scanTechnologySources(this.engine, input.workspaceId, workspace, input.principal, input.signal);
|
|
493
|
+
const graph = await buildTechnologyGraphAsync(snapshot, {
|
|
494
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
495
|
+
cache: this.#state.fragments,
|
|
496
|
+
analysisMode: 'focused',
|
|
497
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
498
|
+
});
|
|
499
|
+
const expansion = await inspectHelperExpansion(this.engine, input.workspaceId, technologyHelperInventory(graph, snapshot.complete && graph.unresolved.every(({ kind }) => kind !== 'partial_source')), input.helperExpansion, input.principal, input.signal);
|
|
500
|
+
return {
|
|
501
|
+
graph,
|
|
502
|
+
report: expansion.report,
|
|
503
|
+
reportJson: expansion.reportJson,
|
|
504
|
+
artifacts: expansion.artifacts,
|
|
505
|
+
helperExpansion: expansion.summary,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
477
508
|
const graph = await this.scan(input.workspaceId, {
|
|
478
509
|
refresh: input.refresh ?? input.mode === 'scan',
|
|
479
510
|
...(input.principal === undefined ? {} : { principal: input.principal }),
|
|
@@ -542,11 +573,37 @@ export class TechnologyTreeViewer {
|
|
|
542
573
|
complete: graph.complete,
|
|
543
574
|
analysisBoundary: graph.analysisBoundary,
|
|
544
575
|
mode: input.mode,
|
|
576
|
+
resources: [
|
|
577
|
+
{
|
|
578
|
+
name: `${safeSlug(`technology-${input.mode}`)}-${graph.revision.slice(0, 12)}.json`,
|
|
579
|
+
mimeType: 'application/json',
|
|
580
|
+
},
|
|
581
|
+
...(input.mode === 'scan'
|
|
582
|
+
? [
|
|
583
|
+
{
|
|
584
|
+
name: `technology-graph-${graph.revision}.json`,
|
|
585
|
+
mimeType: 'application/json',
|
|
586
|
+
},
|
|
587
|
+
]
|
|
588
|
+
: []),
|
|
589
|
+
],
|
|
545
590
|
report,
|
|
546
591
|
})}\n`;
|
|
547
592
|
const workspace = this.engine.resolver.get(input.workspaceId, input.principal);
|
|
593
|
+
const graphHashValue = graphHash(graph);
|
|
548
594
|
const artifact = await this.engine.artifacts.putChunked(workspace, `${safeSlug(`technology-${input.mode}`)}-${graph.revision.slice(0, 12)}.json`, 'application/json', reportJson, technologyProvenance(graph, `technology-${input.mode}`, { mode: input.mode }), `Authoritative Technology Tree Viewer ${input.mode.replaceAll('_', ' ')} report`, input.signal);
|
|
549
|
-
|
|
595
|
+
const graphArtifact = input.mode === 'scan'
|
|
596
|
+
? await this.engine.artifacts.putChunked(workspace, `technology-graph-${graph.revision}.json`, 'application/json', `${canonicalJson({ schemaVersion: 'technology-graph-snapshot.v1', graph })}\n`, technologyProvenance(graph, 'technology-graph-snapshot', {
|
|
597
|
+
revision: graph.revision,
|
|
598
|
+
graphHash: graphHashValue,
|
|
599
|
+
}), 'Revision-addressed Technology Tree Viewer graph snapshot', input.signal)
|
|
600
|
+
: undefined;
|
|
601
|
+
return {
|
|
602
|
+
graph,
|
|
603
|
+
report,
|
|
604
|
+
reportJson,
|
|
605
|
+
artifacts: [artifact, ...(graphArtifact === undefined ? [] : [graphArtifact])],
|
|
606
|
+
};
|
|
550
607
|
}
|
|
551
608
|
async renderAndStore(input) {
|
|
552
609
|
const graph = await this.scan(input.workspaceId, {
|
|
@@ -747,10 +804,27 @@ export class TechnologyTreeViewer {
|
|
|
747
804
|
return graph;
|
|
748
805
|
}
|
|
749
806
|
if (reference?.revision !== undefined) {
|
|
750
|
-
const
|
|
751
|
-
if (
|
|
752
|
-
|
|
753
|
-
|
|
807
|
+
const cached = this.#state.history.get(workspaceId)?.get(reference.revision);
|
|
808
|
+
if (cached !== undefined)
|
|
809
|
+
return cached;
|
|
810
|
+
const workspace = this.engine.resolver.get(workspaceId, principal);
|
|
811
|
+
const names = new Set([
|
|
812
|
+
`technology-graph-${reference.revision}.json`,
|
|
813
|
+
`technology-scan-${reference.revision.slice(0, 12)}.json`,
|
|
814
|
+
]);
|
|
815
|
+
const candidates = (await this.engine.artifacts.list(workspace, signal)).filter(({ name }) => names.has(name));
|
|
816
|
+
for (const artifact of candidates) {
|
|
817
|
+
const selected = parseGraphArtifact(await readLogicalArtifact(this.engine, workspaceId, artifact.uri, principal, signal));
|
|
818
|
+
if (selected.revision === reference.revision &&
|
|
819
|
+
selected.workspaceId === workspaceId &&
|
|
820
|
+
selected.workspaceIdentity === current.workspaceIdentity) {
|
|
821
|
+
rememberGraph(this.#state, selected);
|
|
822
|
+
return selected;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (candidates.length === 0)
|
|
826
|
+
throw new ServiceError('TECH_REVISION_NOT_CACHED', 'Requested technology revision is not cached or retained as a graph artifact');
|
|
827
|
+
throw new ServiceError('TECH_GRAPH_WORKSPACE_MISMATCH', 'Retained technology graph evidence does not match the requested revision and workspace topology');
|
|
754
828
|
}
|
|
755
829
|
return current;
|
|
756
830
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { semanticSourceIdentity } from '../core/semantic-dependencies.js';
|
|
2
3
|
import { compareCodeUnits, deterministicId } from '../core/canonical.js';
|
|
3
4
|
import { assignments, childBlocks, firstScalar, nodeLocation, parseClausewitz, sourcePartialLimitDiagnostics, } from '../core/source/index.js';
|
|
4
5
|
const technologyStructuralFields = new Set([
|
|
@@ -1031,7 +1032,7 @@ export function analyzeTechnologySource(file, context) {
|
|
|
1031
1032
|
return fragment;
|
|
1032
1033
|
}
|
|
1033
1034
|
export function technologySourceFragmentCacheKey(file, catalogFingerprint) {
|
|
1034
|
-
return `${file
|
|
1035
|
+
return `${semanticSourceIdentity(file)}:${catalogFingerprint}`;
|
|
1035
1036
|
}
|
|
1036
1037
|
export function technologySourceFiles(files) {
|
|
1037
1038
|
return files
|
|
@@ -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 = '3.0
|
|
3
|
+
export const PACKAGE_VERSION = '3.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/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
- [Setup](setup.md): install the server, configure HOI4 paths, and connect an MCP client.
|
|
4
4
|
- [Event chains](events.md): inspect, trace, lint, render, and compare event chains.
|
|
5
5
|
- [Technology trees](technology.md): inspect, trace, lint, render, and compare technologies and doctrines.
|
|
6
|
+
- [Bounded helper expansion](helper-expansion.md): resume source-linked event and technology helper paths with exact coverage boundaries.
|
|
7
|
+
- [Persistent jobs](jobs.md): run, inspect, cancel, and recover long operations.
|
|
6
8
|
- [AI and MTTH analysis](probability.md): evaluate weighted logic, timing, uncertainty, sequences, and patches.
|
|
7
9
|
- [Focus trees](focus.md): inspect, create, render, and clean national or continuous focus content.
|
|
8
10
|
- [Scripted GUIs](gui.md): inspect linked UI source, render states, and create or repair GUIs.
|
package/docs/development.md
CHANGED
|
@@ -25,6 +25,8 @@ The technology acceptance fixture contains 1,040 project-owned technologies acro
|
|
|
25
25
|
|
|
26
26
|
`npx vitest run tests/integration/mcp-concurrent-workloads.test.ts` tests simultaneous production calls across all six domains over six HTTP sessions and four independent stdio processes, including artifact retrieval and queued cancellation. Execution limits and connection behavior are described in [HTTP](http.md) and the [concurrency decision](https://github.com/klimPaskov/hoi4-agent-tools/blob/main/docs/adr/0026-concurrent-request-execution.md).
|
|
27
27
|
|
|
28
|
+
Persistent-job qualification covers authenticated record publication, request-key conflicts, read-result checkpoints, transaction-journal reconciliation, stopped-owner takeover, native task negotiation, ordinary-call parity, reconnect isolation, rewrite cancellation before source application, and fixed-entry worker execution. The public lifecycle contract is documented in [Persistent jobs and MCP tasks](jobs.md).
|
|
29
|
+
|
|
28
30
|
The probability fixture contains more than 150 weighted blocks, 250 scenarios, and a declared stateful pool. Run `npm run benchmark` after analyzer changes. On the July 22, 2026 reference run, cold indexing took 28 ms, a cached block evaluation 29 ms, the 250-row matrix 85 ms, one million draws 9 ms, 3/12/25-candidate sequence cases 20/2,079/356 ms, and retrieval of a 1.84 MB result 3 ms. Review budgets are 250 ms, 150 ms, 750 ms, 100 ms, 200/10,000/3,000 ms, and 100 ms respectively; accuracy, method metadata, and omitted probability must not be weakened to meet them.
|
|
29
31
|
|
|
30
32
|
Local opt-in tests can also read an installed game and an external mod without copying their sources:
|