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,51 @@
|
|
|
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 { eventInspectRequestSchema, eventRenderRequestSchema, eventCompareRequestSchema, } from '../schemas/event.js';
|
|
6
|
+
import { inspectEvents, renderEvents, compareEvents, normalizeEventInspectionRequest, normalizeEventRenderRequest, normalizeEventCompareRequest, } from './operations.js';
|
|
7
|
+
import { EventChainViewer } from './service.js';
|
|
8
|
+
const wireResult = z.record(z.string(), z.json());
|
|
9
|
+
/** Registers typed event operations without invoking transport handlers. */
|
|
10
|
+
export function registerEventJobs(operations, engine) {
|
|
11
|
+
const viewer = new EventChainViewer(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({ completed: 3, total: 3, message: 'Event operation complete' });
|
|
27
|
+
const wire = wireResult.parse(JSON.parse(JSON.stringify(toolResult(result))));
|
|
28
|
+
const revision = result.data !== null &&
|
|
29
|
+
typeof result.data === 'object' &&
|
|
30
|
+
'revision' in result.data &&
|
|
31
|
+
typeof result.data.revision === 'string'
|
|
32
|
+
? result.data.revision
|
|
33
|
+
: undefined;
|
|
34
|
+
if (revision !== undefined)
|
|
35
|
+
await context.stageResult(wire, { sourceRevision: revision });
|
|
36
|
+
return wire;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (context.signal.aborted)
|
|
40
|
+
throw error;
|
|
41
|
+
return wireResult.parse(JSON.parse(JSON.stringify(errorResult(error, context.workspaceId))));
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
release();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
register('hoi4.event_inspect', eventInspectRequestSchema, normalizeEventInspectionRequest, inspectEvents, 'Analyzing event chain');
|
|
49
|
+
register('hoi4.event_render', eventRenderRequestSchema, normalizeEventRenderRequest, renderEvents, 'Rendering event-chain view');
|
|
50
|
+
register('hoi4.event_compare', eventCompareRequestSchema, normalizeEventCompareRequest, compareEvents, 'Comparing event-chain graphs');
|
|
51
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { compareCodeUnits } from '../core/canonical.js';
|
|
2
|
+
import { helperExpansionValidation } from '../core/helper-expansion.js';
|
|
3
|
+
import { publicArtifactLink } from '../core/artifacts.js';
|
|
4
|
+
import { setInlineFilesScanned } from '../core/operation-result.js';
|
|
5
|
+
import { emptyServiceResult } from '../core/result.js';
|
|
6
|
+
import { eventInspectRequestSchema, eventRenderRequestSchema, eventCompareRequestSchema, } from '../schemas/event.js';
|
|
7
|
+
import { eventDiagnostics, } from './service.js';
|
|
8
|
+
export function normalizeEventInspectionRequest(input) {
|
|
9
|
+
// Validated request fields are JSON-only. Match wire semantics by removing optional
|
|
10
|
+
// undefined fields, including nested source-selector columns, before service dispatch.
|
|
11
|
+
return JSON.parse(JSON.stringify(eventInspectRequestSchema.parse(input)));
|
|
12
|
+
}
|
|
13
|
+
export function normalizeEventRenderRequest(input) {
|
|
14
|
+
return JSON.parse(JSON.stringify(eventRenderRequestSchema.parse(input)));
|
|
15
|
+
}
|
|
16
|
+
export function normalizeEventCompareRequest(input) {
|
|
17
|
+
return JSON.parse(JSON.stringify(eventCompareRequestSchema.parse(input)));
|
|
18
|
+
}
|
|
19
|
+
export function eventGraphCounts(graph, artifacts) {
|
|
20
|
+
const diagnostics = eventDiagnostics(graph);
|
|
21
|
+
return {
|
|
22
|
+
events: graph.statistics.eventCount,
|
|
23
|
+
options: graph.statistics.optionCount,
|
|
24
|
+
entries: graph.statistics.entryCount,
|
|
25
|
+
helpers: graph.statistics.helperCount,
|
|
26
|
+
unresolvedNodes: graph.statistics.unresolvedNodeCount,
|
|
27
|
+
terminals: graph.statistics.terminalCount,
|
|
28
|
+
edges: graph.statistics.edgeCount,
|
|
29
|
+
derivedEdges: graph.statistics.derivedEdgeCount,
|
|
30
|
+
stateAccesses: graph.statistics.stateAccessCount,
|
|
31
|
+
issues: graph.statistics.issueCount,
|
|
32
|
+
diagnostics: diagnostics.length,
|
|
33
|
+
blockingDiagnostics: diagnostics.filter(({ severity }) => severity === 'error' || severity === 'blocker').length,
|
|
34
|
+
skippedSources: graph.skippedSourceCount,
|
|
35
|
+
artifacts,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function eventGraphValidation(graph) {
|
|
39
|
+
const counts = eventGraphCounts(graph, 0);
|
|
40
|
+
return {
|
|
41
|
+
passed: graph.complete && counts.blockingDiagnostics === 0,
|
|
42
|
+
checks: [
|
|
43
|
+
{
|
|
44
|
+
id: 'event-analysis',
|
|
45
|
+
passed: graph.complete && counts.blockingDiagnostics === 0,
|
|
46
|
+
message: graph.analysisMode === 'focused'
|
|
47
|
+
? 'Large workspace analysis deferred workspace-wide helper projections and lifecycle passes; direct evidence is linked'
|
|
48
|
+
: graph.complete
|
|
49
|
+
? `${counts.blockingDiagnostics} blocking event-chain diagnostics; full evidence is linked`
|
|
50
|
+
: graph.issues.some(({ code }) => code === 'EVENT_HELPER_DEPTH_LIMIT' ||
|
|
51
|
+
code === 'EVENT_HELPER_PROJECTION_LIMIT' ||
|
|
52
|
+
code === 'EVENT_HELPER_STATE_PROJECTION_LIMIT')
|
|
53
|
+
? 'Helper expansion reached a depth or materialization boundary; helper_expansion mode provides bounded source-linked continuation'
|
|
54
|
+
: `${graph.skippedSourceCount} event-analysis source(s) were skipped; full evidence is linked`,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Transport-independent event inspection and its complete bounded result contract. */
|
|
60
|
+
export async function inspectEvents(viewer, input) {
|
|
61
|
+
const inspected = await viewer.inspect(input);
|
|
62
|
+
const result = emptyServiceResult(input.workspaceId, {
|
|
63
|
+
mode: inspected.mode,
|
|
64
|
+
analysisMode: inspected.graph.analysisMode ?? 'full',
|
|
65
|
+
revision: inspected.graph.revision,
|
|
66
|
+
graphHash: inspected.graphHash,
|
|
67
|
+
counts: eventGraphCounts(inspected.graph, inspected.artifacts.length),
|
|
68
|
+
...(inspected.helperExpansion === undefined
|
|
69
|
+
? {}
|
|
70
|
+
: { helperExpansion: inspected.helperExpansion }),
|
|
71
|
+
boundary: {
|
|
72
|
+
direction: input.direction ?? 'both',
|
|
73
|
+
maxDepth: inspected.helperExpansion?.maxDepth ?? input.maxDepth ?? 8,
|
|
74
|
+
maxNodes: inspected.helperExpansion === undefined ? (input.maxNodes ?? 500) : 0,
|
|
75
|
+
maxEdges: inspected.helperExpansion === undefined ? (input.maxEdges ?? 2_000) : 0,
|
|
76
|
+
expandHelpers: inspected.helperExpansion !== undefined ||
|
|
77
|
+
(input.expandHelpers ?? input.mode === 'explain_path'),
|
|
78
|
+
refresh: input.refresh ?? input.mode === 'scan',
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
result.code =
|
|
82
|
+
(inspected.helperExpansion?.complete ?? inspected.graph.complete)
|
|
83
|
+
? 'EVENT_INSPECTED'
|
|
84
|
+
: 'EVENT_INSPECTED_PARTIAL';
|
|
85
|
+
setInlineFilesScanned(result, inspected.graph.filesScanned);
|
|
86
|
+
result.artifacts = inspected.artifacts.map(publicArtifactLink);
|
|
87
|
+
result.validation = eventGraphValidation(inspected.graph);
|
|
88
|
+
if (inspected.helperExpansion !== undefined)
|
|
89
|
+
result.validation = helperExpansionValidation(inspected.helperExpansion);
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
export async function renderEvents(viewer, input) {
|
|
93
|
+
const rendered = await viewer.renderAndStore(input);
|
|
94
|
+
const result = emptyServiceResult(input.workspaceId, {
|
|
95
|
+
view: rendered.render.view,
|
|
96
|
+
analysisMode: rendered.graph.analysisMode ?? 'full',
|
|
97
|
+
revision: rendered.graph.revision,
|
|
98
|
+
graphHash: rendered.graphHash,
|
|
99
|
+
layoutHash: rendered.render.layout.layoutHash,
|
|
100
|
+
hashes: rendered.render.hashes,
|
|
101
|
+
counts: {
|
|
102
|
+
...eventGraphCounts(rendered.graph, rendered.artifacts.length),
|
|
103
|
+
selectedNodes: rendered.render.selectedNodeIds.length,
|
|
104
|
+
omittedNodes: rendered.render.omittedNodeCount,
|
|
105
|
+
branchRenders: rendered.branches.length,
|
|
106
|
+
},
|
|
107
|
+
boundary: {
|
|
108
|
+
direction: input.direction ?? 'both',
|
|
109
|
+
maxDepth: input.maxDepth ?? 4,
|
|
110
|
+
maxNodes: input.maxNodes ?? 120,
|
|
111
|
+
expandHelpers: input.expandHelpers ?? false,
|
|
112
|
+
includeHtml: input.includeHtml ?? false,
|
|
113
|
+
refresh: input.refresh ?? false,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
result.code = rendered.graph.complete ? 'EVENT_RENDERED' : 'EVENT_RENDERED_PARTIAL';
|
|
117
|
+
setInlineFilesScanned(result, rendered.graph.filesScanned);
|
|
118
|
+
result.artifacts = rendered.artifacts.map(publicArtifactLink);
|
|
119
|
+
result.validation = eventGraphValidation(rendered.graph);
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
export async function compareEvents(viewer, input) {
|
|
123
|
+
const compared = await viewer.compareAndStore(input);
|
|
124
|
+
const { comparison } = compared;
|
|
125
|
+
const result = emptyServiceResult(input.workspaceId, {
|
|
126
|
+
beforeRevision: comparison.beforeRevision,
|
|
127
|
+
afterRevision: comparison.afterRevision,
|
|
128
|
+
beforeGraphHash: comparison.beforeGraphHash,
|
|
129
|
+
afterGraphHash: comparison.afterGraphHash,
|
|
130
|
+
...(compared.render === undefined ? {} : { renderHashes: compared.render.hashes }),
|
|
131
|
+
counts: {
|
|
132
|
+
changes: comparison.changes.length,
|
|
133
|
+
addedNodes: comparison.addedNodeIds.length,
|
|
134
|
+
removedNodes: comparison.removedNodeIds.length,
|
|
135
|
+
changedNodes: comparison.changedNodeIds.length,
|
|
136
|
+
addedEdges: comparison.addedEdgeIds.length,
|
|
137
|
+
removedEdges: comparison.removedEdgeIds.length,
|
|
138
|
+
changedEdges: comparison.changedEdgeIds.length,
|
|
139
|
+
addedStateAccesses: comparison.addedStateAccessIds.length,
|
|
140
|
+
removedStateAccesses: comparison.removedStateAccessIds.length,
|
|
141
|
+
changedStateAccesses: comparison.changedStateAccessIds.length,
|
|
142
|
+
addedStateLinks: comparison.addedStateLinkIds.length,
|
|
143
|
+
removedStateLinks: comparison.removedStateLinkIds.length,
|
|
144
|
+
changedStateLinks: comparison.changedStateLinkIds.length,
|
|
145
|
+
addedDiagnostics: comparison.addedIssueIds.length,
|
|
146
|
+
resolvedDiagnostics: comparison.resolvedIssueIds.length,
|
|
147
|
+
addedUnresolved: comparison.addedUnresolvedIds.length,
|
|
148
|
+
resolvedUnresolved: comparison.resolvedUnresolvedIds.length,
|
|
149
|
+
disconnectedRoots: comparison.newlyDisconnectedRootIds.length,
|
|
150
|
+
disconnectedBranches: comparison.newlyDisconnectedBranchIds.length,
|
|
151
|
+
disconnectedTerminals: comparison.newlyDisconnectedTerminalIds.length,
|
|
152
|
+
beforeSkippedSources: compared.before.skippedSourceCount,
|
|
153
|
+
afterSkippedSources: compared.after.skippedSourceCount,
|
|
154
|
+
artifacts: compared.artifacts.length,
|
|
155
|
+
},
|
|
156
|
+
boundary: {
|
|
157
|
+
proposedSources: input.proposedSources?.length ?? 0,
|
|
158
|
+
render: input.render ?? true,
|
|
159
|
+
maxRenderNodes: input.maxRenderNodes ?? 120,
|
|
160
|
+
refresh: compared.refresh,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
result.code =
|
|
164
|
+
compared.before.complete && compared.after.complete
|
|
165
|
+
? 'EVENT_COMPARED'
|
|
166
|
+
: 'EVENT_COMPARED_PARTIAL';
|
|
167
|
+
setInlineFilesScanned(result, [...new Set([...compared.before.filesScanned, ...compared.after.filesScanned])].sort(compareCodeUnits));
|
|
168
|
+
result.artifacts = compared.artifacts.map(publicArtifactLink);
|
|
169
|
+
const beforeValidation = eventGraphValidation(compared.before);
|
|
170
|
+
const afterValidation = eventGraphValidation(compared.after);
|
|
171
|
+
result.validation = {
|
|
172
|
+
passed: beforeValidation.passed && afterValidation.passed,
|
|
173
|
+
checks: [...beforeValidation.checks, ...afterValidation.checks].map((check, index) => ({
|
|
174
|
+
...check,
|
|
175
|
+
id: `${index === 0 ? 'before' : 'after'}-${check.id}`,
|
|
176
|
+
})),
|
|
177
|
+
};
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { inspectHelperExpansion } from '../core/helper-expansion.js';
|
|
3
|
+
import { eventHelperInventory } 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, publicArtifactLink, } from '../core/artifacts.js';
|
|
4
7
|
import { ServiceError } from '../core/result.js';
|
|
@@ -6,7 +9,7 @@ import { RenderBudget } from '../core/render-budget.js';
|
|
|
6
9
|
import { SOURCE_MAX_BYTES } from '../core/source/index.js';
|
|
7
10
|
import { isPortablePathSegment } from '../core/workspace.js';
|
|
8
11
|
import { PACKAGE_VERSION } from '../version.js';
|
|
9
|
-
import { buildEventGraph } from './graph.js';
|
|
12
|
+
import { buildEventGraph, buildEventGraphAsync } from './graph.js';
|
|
10
13
|
import { EVENT_FRAGMENT_CACHE_MAX_ENTRIES, EVENT_FRAGMENT_CACHE_MAX_SOURCE_BYTES, EVENT_PROPOSED_SOURCE_MAX_BYTES, EVENT_PROPOSED_SOURCE_MAX_FILES, } from './limits.js';
|
|
11
14
|
import { analyzeEventImpact, discoverEventRoots, explainEventPath, inspectEventStateFlow, lintEventGraph, traceSelectedEvents, } from './queries.js';
|
|
12
15
|
import { compareEventGraphs, eventGraphHash } from './compare.js';
|
|
@@ -42,6 +45,7 @@ class BoundedEventFragmentCache {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
clear() {
|
|
48
|
+
clearSemanticDependencies(this);
|
|
45
49
|
this.#entries.clear();
|
|
46
50
|
this.#sourceBytes = 0;
|
|
47
51
|
}
|
|
@@ -717,14 +721,18 @@ export class EventChainViewer {
|
|
|
717
721
|
const cacheProjectHelpers = cacheAnalysisMode === 'focused' ? false : requestedProjectHelpers;
|
|
718
722
|
const cacheKey = eventGraphCacheKey(workspaceId, cacheProjectHelpers, cacheAnalysisMode);
|
|
719
723
|
const cached = this.#state.current.get(cacheKey);
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
724
|
+
// An engine generation observes server-owned edits, not changes made by
|
|
725
|
+
// another agent or process. Verify source identity before reusing a graph.
|
|
723
726
|
if (options.refresh !== true && requestedAnalysisMode === 'focused') {
|
|
724
|
-
const
|
|
725
|
-
this.#state.current.get(eventGraphCacheKey(workspaceId,
|
|
726
|
-
|
|
727
|
-
|
|
727
|
+
for (const expanded of [cacheProjectHelpers, !cacheProjectHelpers]) {
|
|
728
|
+
const sibling = this.#state.current.get(eventGraphCacheKey(workspaceId, expanded, 'full'));
|
|
729
|
+
if (sibling?.generation === generation)
|
|
730
|
+
return this.scan(workspaceId, {
|
|
731
|
+
...options,
|
|
732
|
+
analysisMode: 'full',
|
|
733
|
+
projectHelpers: expanded,
|
|
734
|
+
});
|
|
735
|
+
}
|
|
728
736
|
}
|
|
729
737
|
const scanFocused = requestedAnalysisMode === 'focused';
|
|
730
738
|
const snapshot = await scanEventSources(this.engine, workspaceId, workspace, scanFocused, options.signal);
|
|
@@ -733,7 +741,7 @@ export class EventChainViewer {
|
|
|
733
741
|
return cached.graph;
|
|
734
742
|
const buildAnalysisMode = requestedAnalysisMode;
|
|
735
743
|
const projectHelpers = buildAnalysisMode === 'focused' ? false : requestedProjectHelpers;
|
|
736
|
-
const graph =
|
|
744
|
+
const graph = await buildEventGraphAsync(snapshot, {
|
|
737
745
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
738
746
|
cache: this.#state.fragments,
|
|
739
747
|
workspaceIdentity: workspace.workspaceIdentity,
|
|
@@ -747,6 +755,24 @@ export class EventChainViewer {
|
|
|
747
755
|
}
|
|
748
756
|
async inspect(input) {
|
|
749
757
|
input.signal?.throwIfAborted();
|
|
758
|
+
if (input.mode === 'helper_expansion') {
|
|
759
|
+
const graph = await this.scan(input.workspaceId, {
|
|
760
|
+
projectHelpers: false,
|
|
761
|
+
analysisMode: 'full',
|
|
762
|
+
...(input.principal === undefined ? {} : { principal: input.principal }),
|
|
763
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
764
|
+
});
|
|
765
|
+
const expansion = await inspectHelperExpansion(this.engine, input.workspaceId, eventHelperInventory(graph), input.helperExpansion, input.principal, input.signal);
|
|
766
|
+
return {
|
|
767
|
+
graph,
|
|
768
|
+
graphHash: cachedEventGraphHash(graph, input.signal),
|
|
769
|
+
mode: input.mode,
|
|
770
|
+
report: expansion.report,
|
|
771
|
+
reportJson: expansion.reportJson,
|
|
772
|
+
artifacts: expansion.artifacts,
|
|
773
|
+
helperExpansion: expansion.summary,
|
|
774
|
+
};
|
|
775
|
+
}
|
|
750
776
|
const focused = input.mode === 'trace' ||
|
|
751
777
|
input.mode === 'explain_path' ||
|
|
752
778
|
(input.selector !== undefined &&
|
|
@@ -884,6 +910,7 @@ export class EventChainViewer {
|
|
|
884
910
|
}
|
|
885
911
|
const graphHash = cachedEventGraphHash(graph, input.signal);
|
|
886
912
|
const name = `${safeSlug(`event-${input.mode}`)}-${graph.revision.slice(0, 12)}.json`;
|
|
913
|
+
const graphSnapshotName = `event-graph-${graph.revision}.json`;
|
|
887
914
|
const projectedScan = input.mode === 'scan' &&
|
|
888
915
|
typeof report === 'object' &&
|
|
889
916
|
report !== null &&
|
|
@@ -919,7 +946,12 @@ export class EventChainViewer {
|
|
|
919
946
|
},
|
|
920
947
|
}
|
|
921
948
|
: {}),
|
|
922
|
-
resources: [
|
|
949
|
+
resources: [
|
|
950
|
+
{ name, mimeType: 'application/json' },
|
|
951
|
+
...(input.mode === 'scan'
|
|
952
|
+
? [{ name: graphSnapshotName, mimeType: 'application/json' }]
|
|
953
|
+
: []),
|
|
954
|
+
],
|
|
923
955
|
report,
|
|
924
956
|
})}\n`;
|
|
925
957
|
const workspace = this.engine.resolver.get(input.workspaceId, input.principal);
|
|
@@ -927,13 +959,16 @@ export class EventChainViewer {
|
|
|
927
959
|
mode: input.mode,
|
|
928
960
|
...(input.selector === undefined ? {} : { selector: input.selector }),
|
|
929
961
|
}, input.signal), `Authoritative Event Chain Viewer ${input.mode.replaceAll('_', ' ')} report`, input.signal);
|
|
962
|
+
const graphArtifact = input.mode === 'scan'
|
|
963
|
+
? await this.engine.artifacts.putChunked(workspace, graphSnapshotName, 'application/json', `${canonicalJson({ schemaVersion: 'event-graph-snapshot.v1', graph })}\n`, eventProvenance(graph, 'event-graph-snapshot', { revision: graph.revision, graphHash }, input.signal), 'Revision-addressed Event Chain Viewer graph snapshot', input.signal)
|
|
964
|
+
: undefined;
|
|
930
965
|
return {
|
|
931
966
|
graph,
|
|
932
967
|
graphHash,
|
|
933
968
|
mode: input.mode,
|
|
934
969
|
report,
|
|
935
970
|
reportJson,
|
|
936
|
-
artifacts: [artifact],
|
|
971
|
+
artifacts: [artifact, ...(graphArtifact === undefined ? [] : [graphArtifact])],
|
|
937
972
|
};
|
|
938
973
|
}
|
|
939
974
|
async renderAndStore(input) {
|
|
@@ -1243,10 +1278,27 @@ export class EventChainViewer {
|
|
|
1243
1278
|
return selected;
|
|
1244
1279
|
}
|
|
1245
1280
|
if (reference?.revision !== undefined) {
|
|
1246
|
-
const
|
|
1247
|
-
if (
|
|
1248
|
-
|
|
1249
|
-
|
|
1281
|
+
const cached = this.#state.history.get(workspaceId)?.get(reference.revision);
|
|
1282
|
+
if (cached !== undefined)
|
|
1283
|
+
return cached;
|
|
1284
|
+
const workspace = this.engine.resolver.get(workspaceId, principal);
|
|
1285
|
+
const names = new Set([
|
|
1286
|
+
`event-graph-${reference.revision}.json`,
|
|
1287
|
+
`event-scan-${reference.revision.slice(0, 12)}.json`,
|
|
1288
|
+
]);
|
|
1289
|
+
const candidates = (await this.engine.artifacts.list(workspace, signal)).filter(({ name }) => names.has(name));
|
|
1290
|
+
for (const artifact of candidates) {
|
|
1291
|
+
const selected = parseGraphArtifact(await readLogicalArtifact(this.engine, workspaceId, artifact.uri, principal, signal), signal);
|
|
1292
|
+
if (selected.revision === reference.revision &&
|
|
1293
|
+
selected.workspaceId === workspaceId &&
|
|
1294
|
+
selected.workspaceIdentity === current.workspaceIdentity) {
|
|
1295
|
+
rememberGraph(this.#state, workspaceId, selected);
|
|
1296
|
+
return selected;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (candidates.length === 0)
|
|
1300
|
+
throw new ServiceError('EVENT_REVISION_NOT_CACHED', 'Requested event graph revision is not cached or retained as a graph artifact');
|
|
1301
|
+
throw new ServiceError('EVENT_GRAPH_WORKSPACE_MISMATCH', 'Retained event graph evidence does not match the requested revision and workspace topology');
|
|
1250
1302
|
}
|
|
1251
1303
|
return current;
|
|
1252
1304
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compareCodeUnits, deterministicId } from '../core/canonical.js';
|
|
2
|
+
import { semanticSourceIdentity } from '../core/semantic-dependencies.js';
|
|
2
3
|
import { assignments, childBlocks, firstScalar, nodeLocation, parseClausewitz, parseLocalisation, sourcePartialLimitDiagnostics, } from '../core/source/index.js';
|
|
3
4
|
import { nativeEffectKeys } from '../focus/native-effects.js';
|
|
4
5
|
import { EVENT_FRAGMENT_CACHE_MAX_ENTRIES, EVENT_FRAGMENT_CACHE_MAX_SOURCE_BYTES, EVENT_GRAPH_MAX_CONDITION_TEXT, EventAnalysisBudget, } from './limits.js';
|
|
@@ -303,8 +304,7 @@ function issueFromDiagnostic(diagnostic) {
|
|
|
303
304
|
export function eventSemanticFragmentCacheKey(file, catalogFingerprint = '') {
|
|
304
305
|
return deterministicId('event_fragment', {
|
|
305
306
|
parser: 'clausewitz-cst.v1',
|
|
306
|
-
|
|
307
|
-
sourceHash: file.sha256,
|
|
307
|
+
source: semanticSourceIdentity(file),
|
|
308
308
|
catalogFingerprint,
|
|
309
309
|
});
|
|
310
310
|
}
|
|
@@ -634,7 +634,8 @@ class FragmentAnalyzer {
|
|
|
634
634
|
for (const assignment of assignments(this.document.root)) {
|
|
635
635
|
if (assignment.value.type !== 'block')
|
|
636
636
|
continue;
|
|
637
|
-
if (this
|
|
637
|
+
if (this.context.knownHelperIds !== undefined &&
|
|
638
|
+
!this.#knownHelpers.has(assignment.key.value))
|
|
638
639
|
continue;
|
|
639
640
|
const name = assignment.key.value;
|
|
640
641
|
if (this.context.activeHelperPaths !== undefined &&
|
|
@@ -1050,7 +1051,7 @@ class FragmentAnalyzer {
|
|
|
1050
1051
|
this.recordDynamicEvent(id, node, context.ownerId, context.semanticPath);
|
|
1051
1052
|
return;
|
|
1052
1053
|
}
|
|
1053
|
-
const known = this
|
|
1054
|
+
const known = this.context.knownEventIds === undefined || this.#knownEvents.has(id);
|
|
1054
1055
|
const target = known
|
|
1055
1056
|
? this.eventNodeId(id)
|
|
1056
1057
|
: this.unresolvedNode(this.context.inventoryComplete === false ? 'partial_source' : 'missing_event', id, node, context.ownerId, context.semanticPath);
|
|
@@ -0,0 +1,77 @@
|
|
|
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 { focusInspectRequestSchema, focusRenderRequestSchema, focusRewriteRequestSchema, } from '../schemas/focus-requests.js';
|
|
6
|
+
import { FocusWorkbench } from './index.js';
|
|
7
|
+
import { inspectFocus, completeFocusRewrite, normalizeFocusInspectRequest, normalizeFocusRenderRequest, normalizeFocusRewriteRequest, prepareFocusRewrite, renderFocus, } from './tool-operations.js';
|
|
8
|
+
const wireResult = z.record(z.string(), z.json());
|
|
9
|
+
/** Registers read-only focus operations without invoking transport handlers. */
|
|
10
|
+
export function registerFocusJobs(operations, engine) {
|
|
11
|
+
const workbench = new FocusWorkbench(engine.resolver, engine.transactions, engine.artifacts);
|
|
12
|
+
const lifetime = new IdleCacheLifetime(() => engine.releaseScanCaches());
|
|
13
|
+
function register(name, schema, normalize, execute) {
|
|
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
|
+
const execution = await execute(normalize(input), {
|
|
20
|
+
workspaceId: context.workspaceId,
|
|
21
|
+
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
22
|
+
signal: context.signal,
|
|
23
|
+
progress: (completed, total, message) => context.progress({ completed, total, message }),
|
|
24
|
+
});
|
|
25
|
+
const wire = wireResult.parse(JSON.parse(JSON.stringify(toolResult(execution.result))));
|
|
26
|
+
await context.stageResult(wire, { sourceRevision: execution.sourceRevision });
|
|
27
|
+
return wire;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (context.signal.aborted)
|
|
31
|
+
throw error;
|
|
32
|
+
return wireResult.parse(JSON.parse(JSON.stringify(errorResult(error, context.workspaceId))));
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
release();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
register('hoi4.focus_inspect', focusInspectRequestSchema, normalizeFocusInspectRequest, (input, context) => inspectFocus(engine, workbench, input, context));
|
|
40
|
+
register('hoi4.focus_render', focusRenderRequestSchema, normalizeFocusRenderRequest, (input, context) => renderFocus(engine, workbench, input, context, false));
|
|
41
|
+
register('hoi4.focus_raster', focusRenderRequestSchema, normalizeFocusRenderRequest, (input, context) => renderFocus(engine, workbench, input, context, true));
|
|
42
|
+
operations.registerWrite('hoi4.focus_rewrite', focusRewriteRequestSchema, async (input, context) => {
|
|
43
|
+
if (input.workspaceId !== 'current' && input.workspaceId !== context.workspaceId)
|
|
44
|
+
throw new ServiceError('JOB_ARGUMENT_SCOPE_MISMATCH', 'The request workspace does not match the authorized job workspace');
|
|
45
|
+
const release = lifetime.begin();
|
|
46
|
+
try {
|
|
47
|
+
return await prepareFocusRewrite(engine, workbench, normalizeFocusRewriteRequest(input), {
|
|
48
|
+
workspaceId: context.workspaceId,
|
|
49
|
+
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
50
|
+
signal: context.signal,
|
|
51
|
+
progress: (completed, total, message) => context.progress({ completed, total, message }),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
release();
|
|
56
|
+
}
|
|
57
|
+
}, async (input, execution, _reconciled, recipe, context) => {
|
|
58
|
+
const result = completeFocusRewrite(context.workspaceId, execution, recipe);
|
|
59
|
+
const continuous = input.mode === 'continuous';
|
|
60
|
+
await context.progress({
|
|
61
|
+
completed: 5,
|
|
62
|
+
total: 5,
|
|
63
|
+
message: execution.outcome === 'applied'
|
|
64
|
+
? continuous
|
|
65
|
+
? 'Continuous focus rewrite complete'
|
|
66
|
+
: 'Focus rewrite complete'
|
|
67
|
+
: execution.outcome === 'unchanged'
|
|
68
|
+
? continuous
|
|
69
|
+
? 'Continuous focus content already satisfied the plan'
|
|
70
|
+
: 'Focus content already satisfied the plan'
|
|
71
|
+
: continuous
|
|
72
|
+
? 'Continuous focus rewrite blocked'
|
|
73
|
+
: 'Focus rewrite blocked',
|
|
74
|
+
});
|
|
75
|
+
return wireResult.parse(JSON.parse(JSON.stringify(toolResult(result))));
|
|
76
|
+
});
|
|
77
|
+
}
|