hoi4-agent-tools 3.0.9 → 3.2.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 +32 -1
- package/README.md +10 -2
- package/dist/bin/stdio.js +23 -12
- package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
- package/dist/hoi4_agent_tools/core/analysis-evidence.js +9 -0
- package/dist/hoi4_agent_tools/core/analysis-job-operations.js +38 -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 +16 -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/impact-comparison.js +49 -0
- package/dist/hoi4_agent_tools/core/impact-graph.js +301 -0
- package/dist/hoi4_agent_tools/core/impact-semantic.js +256 -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 +202 -0
- package/dist/hoi4_agent_tools/core/job-worker.js +64 -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 +387 -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/semantic-dependencies.js +233 -0
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
- 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/source-overlay.js +125 -0
- 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/decision/ai.js +104 -0
- package/dist/hoi4_agent_tools/decision/comparison.js +106 -0
- package/dist/hoi4_agent_tools/decision/cost.js +177 -0
- package/dist/hoi4_agent_tools/decision/evaluation.js +256 -0
- package/dist/hoi4_agent_tools/decision/lifecycle.js +114 -0
- package/dist/hoi4_agent_tools/decision/service.js +172 -0
- package/dist/hoi4_agent_tools/decision/source-inventory.js +182 -0
- 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/tool-operations.js +270 -0
- package/dist/hoi4_agent_tools/impact/scenario-suites.js +74 -0
- package/dist/hoi4_agent_tools/impact/service.js +127 -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 +18 -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 +45 -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 +16 -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/analysis.js +65 -0
- 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/service.js +24 -3
- package/dist/hoi4_agent_tools/schemas/analysis.js +228 -0
- 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 +3 -0
- package/docs/analysis.md +100 -0
- package/docs/development.md +2 -0
- package/docs/events.md +7 -2
- package/docs/helper-expansion.md +78 -0
- package/docs/http.md +2 -0
- package/docs/jobs.md +86 -0
- package/docs/technology.md +6 -2
- package/package.json +15 -6
- 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/decision-inspect-input.schema.json +290 -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 +44 -0
- package/schemas/helper-expansion-summary.schema.json +70 -0
- package/schemas/impact-inspect-input.schema.json +203 -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 +3 -3
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { customWeightedPoolManifestSchema, probabilityAcceptanceBandSchema, probabilityAdapterIdSchema, probabilityDiagnosticThresholdsSchema, probabilityMetricSchema, probabilityOutputSchema, probabilityRenderFilterSchema, probabilityScenarioSetSchema, probabilitySamplingMethodSchema, probabilitySourceSchema, } from './probability.js';
|
|
3
|
+
import { workspaceIdSchema } from './common.js';
|
|
4
|
+
function compact(schema, description) {
|
|
5
|
+
return z.unknown().describe(description).pipe(schema);
|
|
6
|
+
}
|
|
7
|
+
const nestedSource = compact(probabilitySourceSchema, 'Source selector or proposed source.');
|
|
8
|
+
const nestedScenarios = compact(probabilityScenarioSetSchema, 'Explicit world-state scenarios.');
|
|
9
|
+
const nestedManifest = compact(customWeightedPoolManifestSchema, 'Declared custom weighted pool and state transitions.');
|
|
10
|
+
const nestedRenderFilter = compact(probabilityRenderFilterSchema, 'Optional scenario, candidate, metric, diagnostic, source, and page filter.');
|
|
11
|
+
const nestedAcceptanceBands = compact(z.array(probabilityAcceptanceBandSchema).max(100_000), 'Optional named metric acceptance bands.');
|
|
12
|
+
const nestedDiagnosticThresholds = compact(probabilityDiagnosticThresholdsSchema, 'Optional diagnostic thresholds.');
|
|
13
|
+
const candidatePool = z.array(z.string().min(1).max(512)).max(100_000).optional();
|
|
14
|
+
const outputs = z.array(probabilityOutputSchema).max(10).optional();
|
|
15
|
+
const commonShape = {
|
|
16
|
+
workspaceId: workspaceIdSchema,
|
|
17
|
+
adapter: probabilityAdapterIdSchema.optional(),
|
|
18
|
+
scenarioSet: nestedScenarios,
|
|
19
|
+
candidatePool,
|
|
20
|
+
horizonDays: z.number().positive().max(1_000_000).optional(),
|
|
21
|
+
metrics: z.array(probabilityMetricSchema).min(1).max(4).optional(),
|
|
22
|
+
acceptanceBands: nestedAcceptanceBands.optional(),
|
|
23
|
+
diagnosticThresholds: nestedDiagnosticThresholds.optional(),
|
|
24
|
+
outputs,
|
|
25
|
+
refresh: z.boolean().optional(),
|
|
26
|
+
};
|
|
27
|
+
export const probabilityInspectRequestSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
workspaceId: workspaceIdSchema,
|
|
30
|
+
adapter: probabilityAdapterIdSchema.optional(),
|
|
31
|
+
source: nestedSource.optional(),
|
|
32
|
+
customPoolManifest: nestedManifest.optional(),
|
|
33
|
+
candidatePool,
|
|
34
|
+
refresh: z.boolean().optional(),
|
|
35
|
+
})
|
|
36
|
+
.strict()
|
|
37
|
+
.superRefine((value, context) => {
|
|
38
|
+
if (value.source !== undefined && value.customPoolManifest !== undefined)
|
|
39
|
+
context.addIssue({ code: 'custom', message: 'Provide either source or customPoolManifest' });
|
|
40
|
+
if (value.adapter !== undefined &&
|
|
41
|
+
value.source === undefined &&
|
|
42
|
+
value.customPoolManifest === undefined)
|
|
43
|
+
context.addIssue({
|
|
44
|
+
code: 'custom',
|
|
45
|
+
message: 'An adapter requires a source; provide a source alone to discover compatible adapters',
|
|
46
|
+
});
|
|
47
|
+
if (value.customPoolManifest !== undefined &&
|
|
48
|
+
value.adapter !== undefined &&
|
|
49
|
+
value.adapter !== 'custom_weighted_pool')
|
|
50
|
+
context.addIssue({
|
|
51
|
+
code: 'custom',
|
|
52
|
+
path: ['adapter'],
|
|
53
|
+
message: 'customPoolManifest requires adapter custom_weighted_pool',
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
export const probabilityEvaluateRequestSchema = z
|
|
57
|
+
.object({
|
|
58
|
+
...commonShape,
|
|
59
|
+
source: nestedSource.optional(),
|
|
60
|
+
customPoolManifest: nestedManifest.optional(),
|
|
61
|
+
})
|
|
62
|
+
.strict()
|
|
63
|
+
.superRefine((value, context) => {
|
|
64
|
+
if ((value.source === undefined) === (value.customPoolManifest === undefined))
|
|
65
|
+
context.addIssue({
|
|
66
|
+
code: 'custom',
|
|
67
|
+
message: 'Provide exactly one source or customPoolManifest',
|
|
68
|
+
});
|
|
69
|
+
if (value.customPoolManifest !== undefined &&
|
|
70
|
+
value.adapter !== undefined &&
|
|
71
|
+
value.adapter !== 'custom_weighted_pool')
|
|
72
|
+
context.addIssue({
|
|
73
|
+
code: 'custom',
|
|
74
|
+
path: ['adapter'],
|
|
75
|
+
message: 'customPoolManifest requires adapter custom_weighted_pool',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
export const probabilitySweepRequestSchema = z
|
|
79
|
+
.object({
|
|
80
|
+
...commonShape,
|
|
81
|
+
source: nestedSource,
|
|
82
|
+
sweep: z
|
|
83
|
+
.object({
|
|
84
|
+
paths: z.array(z.string().min(1).max(1024)).min(1).max(32),
|
|
85
|
+
steps: z.number().int().min(2).max(10_000).default(25),
|
|
86
|
+
pairwise: z.boolean().default(false),
|
|
87
|
+
findRankReversals: z.boolean().default(true),
|
|
88
|
+
})
|
|
89
|
+
.strict(),
|
|
90
|
+
})
|
|
91
|
+
.strict();
|
|
92
|
+
export const probabilitySimulateRequestSchema = z
|
|
93
|
+
.object({
|
|
94
|
+
...commonShape,
|
|
95
|
+
source: nestedSource,
|
|
96
|
+
samples: z.number().int().min(100).max(10_000_000).default(100_000),
|
|
97
|
+
seed: z.number().int().min(-2_147_483_648).max(2_147_483_647).default(1),
|
|
98
|
+
confidenceLevel: z.number().min(0.5).max(0.9999).default(0.95),
|
|
99
|
+
samplingMethod: probabilitySamplingMethodSchema.default('latin_hypercube'),
|
|
100
|
+
})
|
|
101
|
+
.strict();
|
|
102
|
+
export const probabilitySequenceRequestSchema = z
|
|
103
|
+
.object({
|
|
104
|
+
workspaceId: workspaceIdSchema,
|
|
105
|
+
scenarioSet: nestedScenarios,
|
|
106
|
+
customPoolManifest: nestedManifest,
|
|
107
|
+
horizonDays: z.number().positive().max(1_000_000),
|
|
108
|
+
maxSteps: z.number().int().min(1).max(100_000).default(1_000),
|
|
109
|
+
samples: z.number().int().min(100).max(10_000_000).default(100_000),
|
|
110
|
+
seed: z.number().int().min(-2_147_483_648).max(2_147_483_647).default(1),
|
|
111
|
+
confidenceLevel: z.number().min(0.5).max(0.9999).default(0.95),
|
|
112
|
+
acceptanceBands: nestedAcceptanceBands.optional(),
|
|
113
|
+
diagnosticThresholds: nestedDiagnosticThresholds.optional(),
|
|
114
|
+
outputs,
|
|
115
|
+
})
|
|
116
|
+
.strict();
|
|
117
|
+
export const probabilityCompareRequestSchema = z
|
|
118
|
+
.object({
|
|
119
|
+
workspaceId: workspaceIdSchema,
|
|
120
|
+
adapter: probabilityAdapterIdSchema.optional(),
|
|
121
|
+
before: nestedSource.optional(),
|
|
122
|
+
after: nestedSource.optional(),
|
|
123
|
+
beforeManifest: nestedManifest.optional(),
|
|
124
|
+
afterManifest: nestedManifest.optional(),
|
|
125
|
+
scenarioSet: nestedScenarios,
|
|
126
|
+
candidatePool,
|
|
127
|
+
horizonDays: z.number().positive().max(1_000_000).optional(),
|
|
128
|
+
acceptanceBands: nestedAcceptanceBands.optional(),
|
|
129
|
+
diagnosticThresholds: nestedDiagnosticThresholds.optional(),
|
|
130
|
+
outputs,
|
|
131
|
+
refresh: z.boolean().optional(),
|
|
132
|
+
})
|
|
133
|
+
.strict()
|
|
134
|
+
.superRefine((value, context) => {
|
|
135
|
+
const sourceMode = value.before !== undefined || value.after !== undefined;
|
|
136
|
+
const manifestMode = value.beforeManifest !== undefined || value.afterManifest !== undefined;
|
|
137
|
+
if (sourceMode === manifestMode)
|
|
138
|
+
context.addIssue({
|
|
139
|
+
code: 'custom',
|
|
140
|
+
message: 'Compare either before/after sources or beforeManifest/afterManifest',
|
|
141
|
+
});
|
|
142
|
+
if (sourceMode && (value.before === undefined || value.after === undefined))
|
|
143
|
+
context.addIssue({ code: 'custom', message: 'Source comparison requires before and after' });
|
|
144
|
+
if (manifestMode && (value.beforeManifest === undefined || value.afterManifest === undefined))
|
|
145
|
+
context.addIssue({
|
|
146
|
+
code: 'custom',
|
|
147
|
+
message: 'Custom-pool comparison requires beforeManifest and afterManifest',
|
|
148
|
+
});
|
|
149
|
+
if (manifestMode && value.adapter !== undefined && value.adapter !== 'custom_weighted_pool')
|
|
150
|
+
context.addIssue({
|
|
151
|
+
code: 'custom',
|
|
152
|
+
path: ['adapter'],
|
|
153
|
+
message: 'Manifest comparison requires adapter custom_weighted_pool',
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
export const probabilityRenderRequestSchema = z
|
|
157
|
+
.object({
|
|
158
|
+
workspaceId: workspaceIdSchema,
|
|
159
|
+
analysisId: z.string().min(1).max(256),
|
|
160
|
+
expectedScenarioHash: z
|
|
161
|
+
.string()
|
|
162
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
163
|
+
.optional(),
|
|
164
|
+
outputs: z.array(probabilityOutputSchema).min(1).max(10),
|
|
165
|
+
includeHtml: z.boolean().default(false),
|
|
166
|
+
filter: nestedRenderFilter.optional(),
|
|
167
|
+
})
|
|
168
|
+
.strict();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { SOURCE_MAX_BYTES } from '../core/source/index.js';
|
|
3
|
-
import { workspaceRelativePathSchema } from './common.js';
|
|
3
|
+
import { workspaceIdSchema, workspaceRelativePathSchema } from './common.js';
|
|
4
|
+
import { helperExpansionRequestSchema, validateHelperExpansionMode } from './helper-expansion.js';
|
|
4
5
|
export const technologyIdSchema = z.string().min(1).max(512);
|
|
5
6
|
export const technologyDirectionSchema = z.enum(['prerequisites', 'descendants', 'both']);
|
|
6
7
|
export const technologyUnlockKindSchema = z.enum([
|
|
@@ -75,3 +76,91 @@ export const technologyProposedSourceSchema = z
|
|
|
75
76
|
.optional(),
|
|
76
77
|
})
|
|
77
78
|
.strict();
|
|
79
|
+
export const technologyAnalysisModeSchema = z.enum([
|
|
80
|
+
'scan',
|
|
81
|
+
'folders',
|
|
82
|
+
'trace',
|
|
83
|
+
'explain',
|
|
84
|
+
'unlocks',
|
|
85
|
+
'bonus_coverage',
|
|
86
|
+
'lint',
|
|
87
|
+
'impact',
|
|
88
|
+
'helper_expansion',
|
|
89
|
+
]);
|
|
90
|
+
export function validateTechnologyInspectRequest(value, context) {
|
|
91
|
+
validateHelperExpansionMode(value, context);
|
|
92
|
+
if ((value.mode === 'trace' || value.mode === 'explain') && value.technologyId === undefined)
|
|
93
|
+
context.addIssue({
|
|
94
|
+
code: 'custom',
|
|
95
|
+
path: ['technologyId'],
|
|
96
|
+
message: `${value.mode} requires technologyId`,
|
|
97
|
+
});
|
|
98
|
+
if (value.mode === 'impact' && value.impact === undefined)
|
|
99
|
+
context.addIssue({
|
|
100
|
+
code: 'custom',
|
|
101
|
+
path: ['impact'],
|
|
102
|
+
message: 'Impact mode requires impact',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export const technologyInspectRequestSchema = z
|
|
106
|
+
.object({
|
|
107
|
+
workspaceId: workspaceIdSchema,
|
|
108
|
+
mode: technologyAnalysisModeSchema,
|
|
109
|
+
folderId: technologyIdSchema.optional(),
|
|
110
|
+
technologyId: technologyIdSchema.optional(),
|
|
111
|
+
categoryId: technologyIdSchema.optional(),
|
|
112
|
+
targetKind: technologyUnlockKindSchema.optional(),
|
|
113
|
+
targetId: technologyIdSchema.optional(),
|
|
114
|
+
direction: technologyDirectionSchema.optional(),
|
|
115
|
+
maxDepth: z.number().int().min(1).max(256).optional(),
|
|
116
|
+
maxNodes: z.number().int().min(1).max(25_000).optional(),
|
|
117
|
+
includeSubTechnologies: z.boolean().optional(),
|
|
118
|
+
classifications: z.array(technologyDefectClassSchema).max(4).optional(),
|
|
119
|
+
codes: z.array(z.string().min(1).max(256)).max(100).optional(),
|
|
120
|
+
impact: technologyImpactSchema.optional(),
|
|
121
|
+
helperExpansion: helperExpansionRequestSchema.optional(),
|
|
122
|
+
refresh: z.boolean().optional(),
|
|
123
|
+
})
|
|
124
|
+
.strict()
|
|
125
|
+
.superRefine(validateTechnologyInspectRequest);
|
|
126
|
+
export const technologyRenderRequestSchema = z
|
|
127
|
+
.object({
|
|
128
|
+
workspaceId: workspaceIdSchema,
|
|
129
|
+
view: technologyRenderViewSchema.exclude(['comparison']),
|
|
130
|
+
folderId: technologyIdSchema.optional(),
|
|
131
|
+
technologyId: technologyIdSchema.optional(),
|
|
132
|
+
categoryId: technologyIdSchema.optional(),
|
|
133
|
+
targetId: technologyIdSchema.optional(),
|
|
134
|
+
maxNodes: z.number().int().min(1).max(2_000).optional(),
|
|
135
|
+
includeHtml: z.boolean().optional(),
|
|
136
|
+
refresh: z.boolean().optional(),
|
|
137
|
+
})
|
|
138
|
+
.strict()
|
|
139
|
+
.superRefine((value, context) => {
|
|
140
|
+
if (value.view === 'folder' && value.folderId === undefined)
|
|
141
|
+
context.addIssue({
|
|
142
|
+
code: 'custom',
|
|
143
|
+
path: ['folderId'],
|
|
144
|
+
message: 'Folder view requires folderId',
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
export function validateTechnologyCompareRequest(value, context) {
|
|
148
|
+
if (value.after !== undefined && value.proposedSources !== undefined)
|
|
149
|
+
context.addIssue({
|
|
150
|
+
code: 'custom',
|
|
151
|
+
path: ['after'],
|
|
152
|
+
message: 'after conflicts with proposedSources',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
export const technologyCompareRequestSchema = z
|
|
156
|
+
.object({
|
|
157
|
+
workspaceId: workspaceIdSchema,
|
|
158
|
+
before: technologyGraphReferenceSchema.optional(),
|
|
159
|
+
after: technologyGraphReferenceSchema.optional(),
|
|
160
|
+
proposedSources: z.array(technologyProposedSourceSchema).min(1).max(128).optional(),
|
|
161
|
+
render: z.boolean().optional(),
|
|
162
|
+
maxRenderNodes: z.number().int().min(1).max(2_000).optional(),
|
|
163
|
+
refresh: z.boolean().optional(),
|
|
164
|
+
})
|
|
165
|
+
.strict()
|
|
166
|
+
.superRefine(validateTechnologyCompareRequest);
|
|
@@ -96,6 +96,7 @@ export const transactionManifestSchema = z
|
|
|
96
96
|
diagnostics: z.array(diagnosticSchema).max(TRANSACTION_MAX_DIAGNOSTICS),
|
|
97
97
|
validation: validationSummarySchema,
|
|
98
98
|
artifacts: z.array(artifactLinkSchema).max(TRANSACTION_MAX_ARTIFACTS),
|
|
99
|
+
executionArtifacts: z.array(artifactLinkSchema).max(TRANSACTION_MAX_ARTIFACTS).optional(),
|
|
99
100
|
appliedFiles: z.array(workspaceRelativePathSchema).max(TRANSACTION_MAX_APPLIED_FILES),
|
|
100
101
|
rollbackStatus: z.enum(['available', 'applied', 'failed']),
|
|
101
102
|
failure: z.object({ code: identifierSchema, message: summarySchema }).strict().optional(),
|
|
@@ -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
|
+
}
|