hoi4-agent-tools 3.0.9 → 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 +23 -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/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/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/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/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/service.js +24 -3
- 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/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 +1 -1
- 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,22 +1,10 @@
|
|
|
1
|
+
import { registerLegacyTaskTools } from '../server/legacy-task-tools.js';
|
|
1
2
|
import { z } from 'zod/v4';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { IdleCacheLifetime } from '../../core/idle-cache-lifetime.js';
|
|
6
|
-
import { RenderBudget } from '../../core/render-budget.js';
|
|
7
|
-
import { emptyServiceResult, ServiceError } from '../../core/result.js';
|
|
8
|
-
import { parseClausewitz } from '../../core/source/index.js';
|
|
9
|
-
import { readDependenciesFromScannedFiles } from '../../core/transactions.js';
|
|
10
|
-
import { FocusWorkbench, FOCUS_RENDER_MAX_OUTPUT_SCALE, FOCUS_RENDER_MIN_OUTPUT_SCALE, assertCompactLayoutQuality, compactFocusTreePlanAsync, focusPlanHash, focusPresentationEvidence, focusPlanningSidecarPath, importContinuousFocusPalettes, importFocusTrees, linkContinuousFocusPalettes, parseFocusPlanningSidecar, resolveFocusPresentation, enrichFocusPlanFromSidecar, } from '../../focus/index.js';
|
|
11
|
-
import { continuousFocusPaletteSchema, focusLayoutMetricsSchema, focusLayoutSchema, focusTreePlanSchema, } from '../../schemas/focus.js';
|
|
12
|
-
import { workspaceIdSchema, workspaceRelativePathSchema } from '../../schemas/common.js';
|
|
13
|
-
import { PACKAGE_VERSION } from '../../version.js';
|
|
3
|
+
import { computeFocusVisualRevisions as computeCoreFocusVisualRevisions, } from '../../focus/tool-operations.js';
|
|
4
|
+
import { focusLayoutMetricsSchema } from '../../schemas/focus.js';
|
|
5
|
+
import { focusInspectRequestSchema, focusRenderRequestSchema, focusRewriteRequestSchema, } from '../../schemas/focus-requests.js';
|
|
14
6
|
import { nonNegativeIntegerSchema, renderHashesSchema, sha256Schema, } from '../server/output-schemas.js';
|
|
15
|
-
import {
|
|
16
|
-
import { errorResult, setInlineFilesScanned, strictOperationResultSchema, toolResult, } from '../server/result.js';
|
|
17
|
-
import { requireServerScope, resolveServerWorkspaceForSource, } from '../server/base-tools.js';
|
|
18
|
-
import { compactValidatedInputSchema } from '../server/context-schemas.js';
|
|
19
|
-
import { autonomousFailureContext, autonomousResultArtifacts, executePlannedTransaction, } from '../server/transaction-execution.js';
|
|
7
|
+
import { strictOperationResultSchema } from '../server/result.js';
|
|
20
8
|
const focusInspectOutputSchema = strictOperationResultSchema(z
|
|
21
9
|
.object({
|
|
22
10
|
mode: z.enum(['national', 'continuous']),
|
|
@@ -79,7 +67,7 @@ const focusRenderOutputSchema = strictOperationResultSchema(z.discriminatedUnion
|
|
|
79
67
|
})
|
|
80
68
|
.strict(),
|
|
81
69
|
]));
|
|
82
|
-
const
|
|
70
|
+
const focusDriftOutputSchema = z
|
|
83
71
|
.object({
|
|
84
72
|
status: z.enum([
|
|
85
73
|
'clean',
|
|
@@ -95,8 +83,6 @@ const focusDriftBaseOutputSchema = z
|
|
|
95
83
|
planChanged: z.boolean(),
|
|
96
84
|
})
|
|
97
85
|
.strict();
|
|
98
|
-
const focusDriftOutputSchema = focusDriftBaseOutputSchema;
|
|
99
|
-
const continuousFocusDriftOutputSchema = focusDriftBaseOutputSchema;
|
|
100
86
|
const focusPlanOutputSchema = strictOperationResultSchema(z.discriminatedUnion('mode', [
|
|
101
87
|
z
|
|
102
88
|
.object({
|
|
@@ -114,7 +100,7 @@ const focusPlanOutputSchema = strictOperationResultSchema(z.discriminatedUnion('
|
|
|
114
100
|
.object({
|
|
115
101
|
mode: z.literal('continuous'),
|
|
116
102
|
paletteId: z.string().max(256),
|
|
117
|
-
drift:
|
|
103
|
+
drift: focusDriftOutputSchema,
|
|
118
104
|
created: z.boolean(),
|
|
119
105
|
execution: z.enum(['applied', 'blocked', 'unchanged']),
|
|
120
106
|
fileCount: nonNegativeIntegerSchema,
|
|
@@ -122,733 +108,49 @@ const focusPlanOutputSchema = strictOperationResultSchema(z.discriminatedUnion('
|
|
|
122
108
|
})
|
|
123
109
|
.strict(),
|
|
124
110
|
]));
|
|
125
|
-
const compactFocusPlanSchema = compactValidatedInputSchema(z.union([focusTreePlanSchema, continuousFocusPaletteSchema]), `Complete focus plan: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/focus.md`);
|
|
126
|
-
const compactFocusLayoutSchema = compactValidatedInputSchema(focusLayoutSchema, 'Prior deterministic focus layout used to stabilize unchanged positions.');
|
|
127
|
-
const focusInspectInput = z
|
|
128
|
-
.object({
|
|
129
|
-
mode: z.enum(['national', 'continuous']).optional(),
|
|
130
|
-
workspaceId: workspaceIdSchema,
|
|
131
|
-
relativePath: workspaceRelativePathSchema.optional(),
|
|
132
|
-
treeId: z.string().min(1).max(256).optional(),
|
|
133
|
-
paletteId: z.string().min(1).max(256).optional(),
|
|
134
|
-
previous: compactFocusLayoutSchema.optional(),
|
|
135
|
-
laneSpacing: z.number().int().min(1).max(100).optional(),
|
|
136
|
-
nodeSpacing: z.number().int().min(1).max(100).optional(),
|
|
137
|
-
})
|
|
138
|
-
.strict()
|
|
139
|
-
.superRefine((value, context) => {
|
|
140
|
-
const mode = value.mode ?? 'national';
|
|
141
|
-
if (mode === 'national' && value.paletteId !== undefined)
|
|
142
|
-
context.addIssue({
|
|
143
|
-
code: 'custom',
|
|
144
|
-
path: ['paletteId'],
|
|
145
|
-
message: 'paletteId is only valid in continuous mode',
|
|
146
|
-
});
|
|
147
|
-
if (mode === 'continuous' && value.treeId !== undefined)
|
|
148
|
-
context.addIssue({
|
|
149
|
-
code: 'custom',
|
|
150
|
-
path: ['treeId'],
|
|
151
|
-
message: 'treeId is only valid in national mode',
|
|
152
|
-
});
|
|
153
|
-
if (mode === 'continuous') {
|
|
154
|
-
for (const field of ['previous', 'laneSpacing', 'nodeSpacing']) {
|
|
155
|
-
if (value[field] !== undefined)
|
|
156
|
-
context.addIssue({
|
|
157
|
-
code: 'custom',
|
|
158
|
-
path: [field],
|
|
159
|
-
message: `${field} is only valid in national mode`,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (value.previous !== undefined && value.treeId === undefined)
|
|
164
|
-
context.addIssue({
|
|
165
|
-
code: 'custom',
|
|
166
|
-
path: ['previous'],
|
|
167
|
-
message: 'previous requires treeId so it applies to exactly one tree',
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
const focusRenderInput = z
|
|
171
|
-
.object({
|
|
172
|
-
mode: z.enum(['national', 'continuous']).optional(),
|
|
173
|
-
workspaceId: workspaceIdSchema,
|
|
174
|
-
relativePath: workspaceRelativePathSchema,
|
|
175
|
-
treeId: z.string().min(1).max(256).optional(),
|
|
176
|
-
paletteId: z.string().min(1).max(256).optional(),
|
|
177
|
-
horizontalSpacing: z.number().int().min(80).max(1000).optional(),
|
|
178
|
-
verticalSpacing: z.number().int().min(60).max(1000).optional(),
|
|
179
|
-
reviewScale: z
|
|
180
|
-
.number()
|
|
181
|
-
.min(FOCUS_RENDER_MIN_OUTPUT_SCALE)
|
|
182
|
-
.max(FOCUS_RENDER_MAX_OUTPUT_SCALE)
|
|
183
|
-
.optional(),
|
|
184
|
-
columns: z.number().int().min(1).max(12).optional(),
|
|
185
|
-
padding: z.number().int().min(0).max(1000).optional(),
|
|
186
|
-
})
|
|
187
|
-
.strict()
|
|
188
|
-
.superRefine((value, context) => {
|
|
189
|
-
const mode = value.mode ?? 'national';
|
|
190
|
-
const invalid = mode === 'national'
|
|
191
|
-
? [
|
|
192
|
-
['paletteId', value.paletteId],
|
|
193
|
-
['columns', value.columns],
|
|
194
|
-
]
|
|
195
|
-
: [
|
|
196
|
-
['treeId', value.treeId],
|
|
197
|
-
['horizontalSpacing', value.horizontalSpacing],
|
|
198
|
-
['verticalSpacing', value.verticalSpacing],
|
|
199
|
-
['reviewScale', value.reviewScale],
|
|
200
|
-
];
|
|
201
|
-
for (const [field, fieldValue] of invalid) {
|
|
202
|
-
if (fieldValue !== undefined)
|
|
203
|
-
context.addIssue({
|
|
204
|
-
code: 'custom',
|
|
205
|
-
path: [field],
|
|
206
|
-
message: `${field} is not valid in ${mode} mode`,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
if (mode === 'continuous' && value.padding !== undefined && value.padding < 24)
|
|
210
|
-
context.addIssue({
|
|
211
|
-
code: 'custom',
|
|
212
|
-
path: ['padding'],
|
|
213
|
-
message: 'continuous focus padding must be at least 24 pixels',
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
const focusPlanInput = z
|
|
217
|
-
.object({
|
|
218
|
-
mode: z.enum(['national', 'continuous']).optional(),
|
|
219
|
-
workspaceId: workspaceIdSchema,
|
|
220
|
-
relativePath: workspaceRelativePathSchema,
|
|
221
|
-
treeId: z.string().min(1).max(256).optional(),
|
|
222
|
-
layoutMode: z.enum(['authored', 'compact']).default('authored'),
|
|
223
|
-
plan: compactFocusPlanSchema.optional(),
|
|
224
|
-
createIfMissing: z.boolean().default(false),
|
|
225
|
-
horizontalSpacing: z.number().int().min(80).max(1000).optional(),
|
|
226
|
-
verticalSpacing: z.number().int().min(60).max(1000).optional(),
|
|
227
|
-
padding: z.number().int().min(0).max(1000).optional(),
|
|
228
|
-
reviewScale: z
|
|
229
|
-
.number()
|
|
230
|
-
.min(FOCUS_RENDER_MIN_OUTPUT_SCALE)
|
|
231
|
-
.max(FOCUS_RENDER_MAX_OUTPUT_SCALE)
|
|
232
|
-
.optional(),
|
|
233
|
-
})
|
|
234
|
-
.strict()
|
|
235
|
-
.superRefine((value, context) => {
|
|
236
|
-
const mode = value.mode ?? 'national';
|
|
237
|
-
const schema = mode === 'continuous' ? continuousFocusPaletteSchema : focusTreePlanSchema;
|
|
238
|
-
if (value.plan !== undefined && !schema.safeParse(value.plan).success)
|
|
239
|
-
context.addIssue({
|
|
240
|
-
code: 'custom',
|
|
241
|
-
path: ['plan'],
|
|
242
|
-
message: `plan must be a ${mode} focus plan`,
|
|
243
|
-
});
|
|
244
|
-
if (mode === 'continuous') {
|
|
245
|
-
if (value.plan === undefined)
|
|
246
|
-
context.addIssue({
|
|
247
|
-
code: 'custom',
|
|
248
|
-
path: ['plan'],
|
|
249
|
-
message: 'plan is required in continuous mode',
|
|
250
|
-
});
|
|
251
|
-
if (value.treeId !== undefined)
|
|
252
|
-
context.addIssue({
|
|
253
|
-
code: 'custom',
|
|
254
|
-
path: ['treeId'],
|
|
255
|
-
message: 'treeId is only valid in national mode',
|
|
256
|
-
});
|
|
257
|
-
if (value.layoutMode !== 'authored')
|
|
258
|
-
context.addIssue({
|
|
259
|
-
code: 'custom',
|
|
260
|
-
path: ['layoutMode'],
|
|
261
|
-
message: 'compact layout mode is only valid in national mode',
|
|
262
|
-
});
|
|
263
|
-
for (const [field, fieldValue] of [
|
|
264
|
-
['horizontalSpacing', value.horizontalSpacing],
|
|
265
|
-
['verticalSpacing', value.verticalSpacing],
|
|
266
|
-
['padding', value.padding],
|
|
267
|
-
['reviewScale', value.reviewScale],
|
|
268
|
-
]) {
|
|
269
|
-
if (fieldValue !== undefined)
|
|
270
|
-
context.addIssue({
|
|
271
|
-
code: 'custom',
|
|
272
|
-
path: [field],
|
|
273
|
-
message: `${field} is only valid for national focus review renders`,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
if (value.plan === undefined && value.layoutMode !== 'compact')
|
|
279
|
-
context.addIssue({
|
|
280
|
-
code: 'custom',
|
|
281
|
-
path: ['plan'],
|
|
282
|
-
message: 'plan is required unless layoutMode is compact',
|
|
283
|
-
});
|
|
284
|
-
if (value.plan === undefined && value.treeId === undefined)
|
|
285
|
-
context.addIssue({
|
|
286
|
-
code: 'custom',
|
|
287
|
-
path: ['treeId'],
|
|
288
|
-
message: 'treeId is required for plan-free compact reflow',
|
|
289
|
-
});
|
|
290
|
-
if (value.plan === undefined && value.createIfMissing)
|
|
291
|
-
context.addIssue({
|
|
292
|
-
code: 'custom',
|
|
293
|
-
path: ['createIfMissing'],
|
|
294
|
-
message: 'plan-free compact reflow requires an existing focus source',
|
|
295
|
-
});
|
|
296
|
-
if (value.plan !== undefined &&
|
|
297
|
-
value.treeId !== undefined &&
|
|
298
|
-
'id' in value.plan &&
|
|
299
|
-
value.plan.id !== value.treeId)
|
|
300
|
-
context.addIssue({
|
|
301
|
-
code: 'custom',
|
|
302
|
-
path: ['treeId'],
|
|
303
|
-
message: 'treeId must match the supplied national focus plan',
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
111
|
const artifactProducing = {
|
|
308
112
|
readOnlyHint: false,
|
|
309
113
|
destructiveHint: false,
|
|
310
114
|
idempotentHint: true,
|
|
311
115
|
openWorldHint: false,
|
|
312
116
|
};
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
signal?.throwIfAborted();
|
|
324
|
-
const imported = importFocusFile(snapshot, selectedFocusFile(snapshot, workspace.registration.roots.focus, selector.relativePath), paletteImport.palettes);
|
|
325
|
-
const plan = selectPlan(imported, selector.treeId);
|
|
326
|
-
const linkedPalettes = paletteImport.palettes.filter(({ id }) => plan.continuousFocusPaletteIds.includes(id));
|
|
327
|
-
const presentation = await resolveFocusPresentation({
|
|
328
|
-
plans: [plan],
|
|
329
|
-
palettes: linkedPalettes,
|
|
330
|
-
files: snapshot.files,
|
|
331
|
-
index: snapshot.index,
|
|
332
|
-
scanner: engine.scanner,
|
|
333
|
-
workspace,
|
|
334
|
-
decodeIcons: false,
|
|
335
|
-
budget: new RenderBudget(),
|
|
336
|
-
...(signal === undefined ? {} : { signal }),
|
|
337
|
-
});
|
|
338
|
-
const filesScanned = [
|
|
339
|
-
...new Set([plan.provenance.sourcePath, ...presentation.filesScanned]),
|
|
340
|
-
].sort(compareCodeUnits);
|
|
341
|
-
revisions.push({
|
|
342
|
-
relativePath: selector.relativePath,
|
|
343
|
-
treeId: plan.id,
|
|
344
|
-
revision: hashCanonical({
|
|
345
|
-
planHash: focusPlanHash(plan),
|
|
346
|
-
linkedPalettes,
|
|
347
|
-
presentationSourceHashes: presentation.sourceHashes,
|
|
348
|
-
}),
|
|
349
|
-
filesScanned,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
return revisions;
|
|
353
|
-
}
|
|
354
|
-
async function executeFocusVisualTool(engine, workbench, context, input, extra, rasterize) {
|
|
355
|
-
const { workspaceId: requestedWorkspaceId, relativePath } = input;
|
|
356
|
-
const workspaceId = await resolveServerWorkspaceForSource(engine, context, requestedWorkspaceId, relativePath, extra.signal);
|
|
357
|
-
const outputName = rasterize ? 'raster' : 'render';
|
|
358
|
-
try {
|
|
359
|
-
const progress = progressReporter(extra);
|
|
360
|
-
await progress.report(0, 4, 'Importing and indexing focus source');
|
|
361
|
-
const workspace = engine.resolver.get(workspaceId, context.principal);
|
|
362
|
-
const snapshot = await engine.scan(workspaceId, { patterns: focusDomainScanPatterns(workspace) }, context.principal, progress.signal);
|
|
363
|
-
await progress.report(1, 4, 'Focus source index complete');
|
|
364
|
-
const renderBudget = new RenderBudget();
|
|
365
|
-
if (input.mode === 'continuous') {
|
|
366
|
-
selectedContinuousFocusFile(snapshot, relativePath);
|
|
367
|
-
const imported = await workbench.importContinuousPath(workspaceId, relativePath, context.principal);
|
|
368
|
-
const plan = selectContinuousPlan(imported.result, input.paletteId);
|
|
369
|
-
const presentation = await resolveFocusPresentation({
|
|
370
|
-
plans: [],
|
|
371
|
-
palettes: [plan],
|
|
372
|
-
files: snapshot.files,
|
|
373
|
-
index: snapshot.index,
|
|
374
|
-
scanner: engine.scanner,
|
|
375
|
-
workspace,
|
|
376
|
-
decodeIcons: rasterize,
|
|
377
|
-
budget: renderBudget,
|
|
378
|
-
signal: progress.signal,
|
|
379
|
-
});
|
|
380
|
-
await progress.report(2, 4, `Producing continuous focus ${outputName} artifacts`);
|
|
381
|
-
const rendered = await workbench.renderContinuousAndStore(workspaceId, plan, {
|
|
382
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
383
|
-
presentation,
|
|
384
|
-
sourceHashes: presentation.sourceHashes,
|
|
385
|
-
...(input.columns === undefined ? {} : { columns: input.columns }),
|
|
386
|
-
...(input.padding === undefined ? {} : { padding: input.padding }),
|
|
387
|
-
renderProfile: { sourceRevision: snapshot.revision, output: outputName },
|
|
388
|
-
rasterize,
|
|
389
|
-
budget: renderBudget,
|
|
390
|
-
signal: progress.signal,
|
|
391
|
-
});
|
|
392
|
-
const diagnostics = [...imported.result.diagnostics, ...rendered.diagnostics];
|
|
393
|
-
const result = emptyServiceResult(workspaceId, {
|
|
394
|
-
mode: 'continuous',
|
|
395
|
-
paletteId: plan.id,
|
|
396
|
-
focusCount: plan.focuses.length,
|
|
397
|
-
hashes: rendered.bundle.hashes,
|
|
398
|
-
width: rendered.bundle.width,
|
|
399
|
-
height: rendered.bundle.height,
|
|
400
|
-
});
|
|
401
|
-
result.code = rasterize ? 'CONTINUOUS_FOCUS_RASTERIZED' : 'CONTINUOUS_FOCUS_RENDERED';
|
|
402
|
-
setInlineFilesScanned(result, [...new Set([plan.provenance.sourcePath, ...presentation.filesScanned])].sort((left, right) => compareCodeUnits(left, right)));
|
|
403
|
-
result.diagnostics = diagnostics.slice(0, 100);
|
|
404
|
-
result.artifacts = rendered.artifacts.map(publicArtifactLink);
|
|
405
|
-
result.validation = validationFromDiagnostics(diagnostics);
|
|
406
|
-
await progress.report(4, 4, `Continuous focus ${outputName} complete`);
|
|
407
|
-
return toolResult(result);
|
|
408
|
-
}
|
|
409
|
-
const paletteImport = continuousPalettes(snapshot);
|
|
410
|
-
const imported = importFocusFile(snapshot, selectedFocusFile(snapshot, workspace.registration.roots.focus, relativePath), paletteImport.palettes);
|
|
411
|
-
const plan = selectPlan(imported, input.treeId);
|
|
412
|
-
const linkedPalettes = paletteImport.palettes.filter(({ id }) => plan.continuousFocusPaletteIds.includes(id));
|
|
413
|
-
const presentation = await resolveFocusPresentation({
|
|
414
|
-
plans: [plan],
|
|
415
|
-
palettes: linkedPalettes,
|
|
416
|
-
files: snapshot.files,
|
|
417
|
-
index: snapshot.index,
|
|
418
|
-
scanner: engine.scanner,
|
|
419
|
-
workspace,
|
|
420
|
-
decodeIcons: rasterize,
|
|
421
|
-
budget: renderBudget,
|
|
422
|
-
signal: progress.signal,
|
|
423
|
-
});
|
|
424
|
-
await progress.report(2, 4, `Producing focus ${outputName} artifacts`);
|
|
425
|
-
const effectiveReviewScale = input.reviewScale ?? automaticFocusRenderScale(plan.focuses.length);
|
|
426
|
-
const rendered = await workbench.renderAndStore(workspaceId, plan, {
|
|
427
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
428
|
-
index: snapshot.index,
|
|
429
|
-
references: referenceCatalog(snapshot),
|
|
430
|
-
presentation,
|
|
431
|
-
sourceHashes: presentation.sourceHashes,
|
|
432
|
-
...(input.horizontalSpacing === undefined
|
|
433
|
-
? {}
|
|
434
|
-
: { horizontalSpacing: input.horizontalSpacing }),
|
|
435
|
-
...(input.verticalSpacing === undefined ? {} : { verticalSpacing: input.verticalSpacing }),
|
|
436
|
-
...(input.padding === undefined ? {} : { padding: input.padding }),
|
|
437
|
-
outputScale: effectiveReviewScale,
|
|
438
|
-
rasterize,
|
|
439
|
-
renderProfile: {
|
|
440
|
-
sourceRevision: snapshot.revision,
|
|
441
|
-
reviewScale: effectiveReviewScale,
|
|
442
|
-
output: outputName,
|
|
443
|
-
},
|
|
444
|
-
budget: renderBudget,
|
|
445
|
-
signal: progress.signal,
|
|
446
|
-
});
|
|
447
|
-
const diagnostics = [
|
|
448
|
-
...paletteImport.diagnostics,
|
|
449
|
-
...imported.diagnostics,
|
|
450
|
-
...rendered.diagnostics,
|
|
451
|
-
];
|
|
452
|
-
const result = emptyServiceResult(workspaceId, {
|
|
453
|
-
mode: 'national',
|
|
454
|
-
treeId: plan.id,
|
|
455
|
-
layoutHash: rendered.layout.layoutHash,
|
|
456
|
-
hashes: rendered.bundle.hashes,
|
|
457
|
-
width: rendered.bundle.width,
|
|
458
|
-
height: rendered.bundle.height,
|
|
459
|
-
});
|
|
460
|
-
result.code = rasterize ? 'FOCUS_RASTERIZED' : 'FOCUS_RENDERED';
|
|
461
|
-
setInlineFilesScanned(result, [...new Set([plan.provenance.sourcePath, ...presentation.filesScanned])].sort((left, right) => compareCodeUnits(left, right)));
|
|
462
|
-
result.diagnostics = diagnostics.slice(0, 100);
|
|
463
|
-
result.artifacts = rendered.artifacts.map(publicArtifactLink);
|
|
464
|
-
result.validation = validationFromDiagnostics(diagnostics);
|
|
465
|
-
await progress.report(4, 4, `Focus ${outputName} complete`);
|
|
466
|
-
return toolResult(result);
|
|
467
|
-
}
|
|
468
|
-
catch (error) {
|
|
469
|
-
return errorResult(error, workspaceId);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
function compactDrift(drift) {
|
|
473
|
-
return {
|
|
474
|
-
status: drift.status,
|
|
475
|
-
sourceChanged: drift.sourceChanged,
|
|
476
|
-
planChanged: drift.planChanged,
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
function normalizedRoot(value) {
|
|
480
|
-
return value.replaceAll('\\', '/').replace(/^\.\//u, '').replace(/\/+$/u, '').toLowerCase();
|
|
481
|
-
}
|
|
482
|
-
function underConfiguredRoot(relativePath, roots) {
|
|
483
|
-
const candidate = relativePath.replaceAll('\\', '/').toLowerCase();
|
|
484
|
-
return roots.some((root) => {
|
|
485
|
-
const normalized = normalizedRoot(root);
|
|
486
|
-
return candidate === normalized || candidate.startsWith(`${normalized}/`);
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
function activeFocusFiles(snapshot, focusRoots) {
|
|
490
|
-
return snapshot.files.filter(({ relativePath, shadowedBy }) => shadowedBy === undefined &&
|
|
491
|
-
underConfiguredRoot(relativePath, focusRoots) &&
|
|
492
|
-
relativePath.toLowerCase().endsWith('.txt'));
|
|
493
|
-
}
|
|
494
|
-
function activeContinuousFocusFiles(snapshot) {
|
|
495
|
-
return snapshot.files.filter(({ relativePath, shadowedBy }) => shadowedBy === undefined &&
|
|
496
|
-
relativePath.replaceAll('\\', '/').toLowerCase().startsWith('common/continuous_focus/') &&
|
|
497
|
-
relativePath.toLowerCase().endsWith('.txt'));
|
|
498
|
-
}
|
|
499
|
-
function continuousPalettes(snapshot) {
|
|
500
|
-
const palettes = [];
|
|
501
|
-
const diagnostics = [];
|
|
502
|
-
for (const file of activeContinuousFocusFiles(snapshot)) {
|
|
503
|
-
const document = parseClausewitz(file.bytes, file.displayPath);
|
|
504
|
-
const imported = importContinuousFocusPalettes(document);
|
|
505
|
-
palettes.push(...imported.continuousFocusPalettes);
|
|
506
|
-
diagnostics.push(...imported.diagnostics);
|
|
507
|
-
}
|
|
508
|
-
return { palettes, diagnostics };
|
|
509
|
-
}
|
|
510
|
-
function activeSidecar(snapshot, focusFile) {
|
|
511
|
-
const relativePath = focusPlanningSidecarPath(focusFile.relativePath).toLowerCase();
|
|
512
|
-
const file = snapshot.files.find((candidate) => candidate.shadowedBy === undefined && candidate.relativePath.toLowerCase() === relativePath);
|
|
513
|
-
return file === undefined ? undefined : { file, sidecar: parseFocusPlanningSidecar(file.bytes) };
|
|
514
|
-
}
|
|
515
|
-
function importFocusFile(snapshot, file, palettes) {
|
|
516
|
-
const document = parseClausewitz(file.bytes, file.displayPath);
|
|
517
|
-
const imported = importFocusTrees(document, { references: referenceCatalog(snapshot) });
|
|
518
|
-
const sidecar = activeSidecar(snapshot, file);
|
|
519
|
-
imported.plans = imported.plans.map((plan) => {
|
|
520
|
-
const enriched = sidecar === undefined
|
|
521
|
-
? { plan, diagnostics: [] }
|
|
522
|
-
: enrichFocusPlanFromSidecar(plan, sidecar.sidecar);
|
|
523
|
-
imported.diagnostics.push(...enriched.diagnostics);
|
|
524
|
-
return linkContinuousFocusPalettes(enriched.plan, palettes);
|
|
525
|
-
});
|
|
526
|
-
return imported;
|
|
527
|
-
}
|
|
528
|
-
function selectedFocusFile(snapshot, focusRoots, relativePath) {
|
|
529
|
-
const file = activeFocusFiles(snapshot, focusRoots).find((candidate) => candidate.relativePath === relativePath);
|
|
530
|
-
if (file === undefined) {
|
|
531
|
-
throw new ServiceError('FOCUS_SOURCE_NOT_FOUND', `Focus source was not found: ${relativePath}`);
|
|
532
|
-
}
|
|
533
|
-
return file;
|
|
534
|
-
}
|
|
535
|
-
function selectedContinuousFocusFile(snapshot, relativePath) {
|
|
536
|
-
const file = activeContinuousFocusFiles(snapshot).find((candidate) => candidate.relativePath === relativePath);
|
|
537
|
-
if (file === undefined) {
|
|
538
|
-
throw new ServiceError('CONTINUOUS_FOCUS_SOURCE_NOT_FOUND', `Continuous focus source was not found: ${relativePath}`);
|
|
539
|
-
}
|
|
540
|
-
return file;
|
|
541
|
-
}
|
|
542
|
-
function selectPlan(result, treeId) {
|
|
543
|
-
if (result.plans.length === 0) {
|
|
544
|
-
throw new ServiceError('FOCUS_TREE_NOT_FOUND', 'The source file contains no focus tree');
|
|
545
|
-
}
|
|
546
|
-
if (treeId === undefined) {
|
|
547
|
-
if (result.plans.length !== 1) {
|
|
548
|
-
throw new ServiceError('FOCUS_TREE_ID_REQUIRED', 'The source file contains multiple focus trees; provide treeId', { treeIds: result.plans.map(({ id }) => id) });
|
|
549
|
-
}
|
|
550
|
-
return result.plans[0];
|
|
551
|
-
}
|
|
552
|
-
const plan = result.plans.find(({ id }) => id === treeId);
|
|
553
|
-
if (plan === undefined) {
|
|
554
|
-
throw new ServiceError('FOCUS_TREE_NOT_FOUND', `Focus tree was not found: ${treeId}`);
|
|
555
|
-
}
|
|
556
|
-
return plan;
|
|
557
|
-
}
|
|
558
|
-
function selectContinuousPlan(result, paletteId) {
|
|
559
|
-
if (result.continuousFocusPalettes.length === 0) {
|
|
560
|
-
throw new ServiceError('CONTINUOUS_FOCUS_PALETTE_NOT_FOUND', 'The source file contains no continuous focus palette');
|
|
561
|
-
}
|
|
562
|
-
if (paletteId === undefined) {
|
|
563
|
-
if (result.continuousFocusPalettes.length !== 1) {
|
|
564
|
-
throw new ServiceError('CONTINUOUS_FOCUS_PALETTE_ID_REQUIRED', 'The source file contains multiple continuous focus palettes; provide paletteId', { paletteIds: result.continuousFocusPalettes.map(({ id }) => id) });
|
|
565
|
-
}
|
|
566
|
-
return result.continuousFocusPalettes[0];
|
|
567
|
-
}
|
|
568
|
-
const plan = result.continuousFocusPalettes.find(({ id }) => id === paletteId);
|
|
569
|
-
if (plan === undefined) {
|
|
570
|
-
throw new ServiceError('CONTINUOUS_FOCUS_PALETTE_NOT_FOUND', `Continuous focus palette was not found: ${paletteId}`);
|
|
571
|
-
}
|
|
572
|
-
return plan;
|
|
573
|
-
}
|
|
574
|
-
function referenceCatalog(snapshot) {
|
|
575
|
-
const identifiers = (kind) => [
|
|
576
|
-
...new Set(snapshot.index
|
|
577
|
-
.findAll(kind)
|
|
578
|
-
.filter(({ overridden }) => !overridden)
|
|
579
|
-
.map(({ id }) => id)),
|
|
580
|
-
].sort((left, right) => compareCodeUnits(left, right));
|
|
581
|
-
return {
|
|
582
|
-
decision: identifiers('decision'),
|
|
583
|
-
decision_category: identifiers('decision_category'),
|
|
584
|
-
event: identifiers('event'),
|
|
585
|
-
idea: identifiers('idea'),
|
|
586
|
-
leader: identifiers('leader'),
|
|
587
|
-
formable: identifiers('formable'),
|
|
588
|
-
helper: identifiers('scripted_effect'),
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
function validationFromDiagnostics(diagnostics) {
|
|
592
|
-
const blocking = diagnostics.filter(({ severity }) => severity === 'error' || severity === 'blocker');
|
|
593
|
-
return {
|
|
594
|
-
passed: blocking.length === 0,
|
|
595
|
-
checks: [
|
|
596
|
-
{
|
|
597
|
-
id: 'focus-diagnostics',
|
|
598
|
-
passed: blocking.length === 0,
|
|
599
|
-
message: blocking.length === 0
|
|
600
|
-
? 'Focus source, references, and layout have no blocking diagnostics'
|
|
601
|
-
: `${blocking.length} blocking focus diagnostics`,
|
|
602
|
-
},
|
|
603
|
-
],
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
export function registerFocusTools(server, engine, context) {
|
|
607
|
-
const workbench = new FocusWorkbench(engine.resolver, engine.transactions, engine.artifacts);
|
|
608
|
-
const cacheLifetime = new IdleCacheLifetime(() => engine.releaseScanCaches());
|
|
609
|
-
server.registerTool('hoi4.focus_inspect', {
|
|
117
|
+
/** Compatibility wrapper retained for callers that supplied the signal separately. */
|
|
118
|
+
export function computeFocusVisualRevisions(engine, context, workspaceId, snapshot, selectors, signal) {
|
|
119
|
+
return computeCoreFocusVisualRevisions(engine, {
|
|
120
|
+
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
121
|
+
...(signal === undefined ? {} : { signal }),
|
|
122
|
+
}, workspaceId, snapshot, selectors);
|
|
123
|
+
}
|
|
124
|
+
export const focusTaskTools = [
|
|
125
|
+
{
|
|
126
|
+
name: 'hoi4.focus_inspect',
|
|
610
127
|
title: 'Inspect focus trees',
|
|
611
128
|
description: 'Inspect national trees or continuous palettes for creation and cleanup, including continuous-focus placement, complete plans, references, diagnostics, and stable layout decisions.',
|
|
612
|
-
inputSchema:
|
|
129
|
+
inputSchema: focusInspectRequestSchema,
|
|
613
130
|
outputSchema: focusInspectOutputSchema,
|
|
614
131
|
annotations: artifactProducing,
|
|
615
|
-
},
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const releaseCaches = cacheLifetime.begin();
|
|
619
|
-
try {
|
|
620
|
-
const progress = progressReporter(extra);
|
|
621
|
-
await progress.report(0, 3, 'Building shared workspace index');
|
|
622
|
-
const workspace = engine.resolver.get(workspaceId, context.principal);
|
|
623
|
-
const snapshot = await engine.scan(workspaceId, { patterns: focusDomainScanPatterns(workspace) }, context.principal, progress.signal);
|
|
624
|
-
if (input.mode === 'continuous') {
|
|
625
|
-
const selectedFiles = activeContinuousFocusFiles(snapshot).filter((file) => relativePath === undefined || file.relativePath === relativePath);
|
|
626
|
-
if (selectedFiles.length === 0) {
|
|
627
|
-
throw new ServiceError('CONTINUOUS_FOCUS_SOURCE_NOT_FOUND', 'No active continuous focus source matched the request');
|
|
628
|
-
}
|
|
629
|
-
const diagnostics = [];
|
|
630
|
-
const importedPalettes = [];
|
|
631
|
-
for (const file of selectedFiles) {
|
|
632
|
-
progress.signal.throwIfAborted();
|
|
633
|
-
const imported = importContinuousFocusPalettes(parseClausewitz(file.bytes, file.displayPath));
|
|
634
|
-
importedPalettes.push(...imported.continuousFocusPalettes);
|
|
635
|
-
diagnostics.push(...imported.diagnostics);
|
|
636
|
-
}
|
|
637
|
-
const palettes = importedPalettes.filter(({ id }) => input.paletteId === undefined || id === input.paletteId);
|
|
638
|
-
if (palettes.length === 0) {
|
|
639
|
-
throw new ServiceError('CONTINUOUS_FOCUS_PALETTE_NOT_FOUND', input.paletteId === undefined
|
|
640
|
-
? 'The selected source contains no continuous focus palette'
|
|
641
|
-
: `Continuous focus palette was not found: ${input.paletteId}`);
|
|
642
|
-
}
|
|
643
|
-
const paletteDiagnosticCounts = new Map();
|
|
644
|
-
for (const palette of palettes) {
|
|
645
|
-
const lintDiagnostics = workbench.lintContinuous(palette);
|
|
646
|
-
paletteDiagnosticCounts.set(palette.id, lintDiagnostics.length);
|
|
647
|
-
diagnostics.push(...lintDiagnostics);
|
|
648
|
-
}
|
|
649
|
-
const presentation = await resolveFocusPresentation({
|
|
650
|
-
plans: [],
|
|
651
|
-
palettes,
|
|
652
|
-
files: snapshot.files,
|
|
653
|
-
index: snapshot.index,
|
|
654
|
-
scanner: engine.scanner,
|
|
655
|
-
workspace,
|
|
656
|
-
decodeIcons: false,
|
|
657
|
-
signal: progress.signal,
|
|
658
|
-
});
|
|
659
|
-
diagnostics.push(...presentation.diagnostics);
|
|
660
|
-
await progress.report(2, 3, 'Writing continuous focus inspection');
|
|
661
|
-
const completeSourceHashes = Object.fromEntries(Object.entries({
|
|
662
|
-
...Object.fromEntries(selectedFiles.map(({ displayPath, sha256 }) => [displayPath, sha256])),
|
|
663
|
-
...presentation.sourceHashes,
|
|
664
|
-
}).sort(([left], [right]) => compareCodeUnits(left, right)));
|
|
665
|
-
const sourceEvidence = boundedSourceHashEvidence(completeSourceHashes);
|
|
666
|
-
const artifact = await engine.artifacts.putChunked(workspace, `focus-inspect.${snapshot.revision.slice(0, 16)}.json`, 'application/json', `${canonicalJson({
|
|
667
|
-
schemaVersion: 1,
|
|
668
|
-
mode: 'continuous',
|
|
669
|
-
revision: snapshot.revision,
|
|
670
|
-
continuousFocusPalettes: palettes,
|
|
671
|
-
presentation: focusPresentationEvidence(presentation),
|
|
672
|
-
sourceHashes: completeSourceHashes,
|
|
673
|
-
diagnostics,
|
|
674
|
-
})}\n`, {
|
|
675
|
-
kind: 'focus-inspect',
|
|
676
|
-
toolVersion: PACKAGE_VERSION,
|
|
677
|
-
schemaVersion: 'focus-inspect.v1',
|
|
678
|
-
sourceHashes: sourceEvidence.sourceHashes,
|
|
679
|
-
metadata: { sourceHashInventory: sourceEvidence.inventory },
|
|
680
|
-
}, 'Continuous focus plans and diagnostics', progress.signal);
|
|
681
|
-
const result = emptyServiceResult(workspaceId, {
|
|
682
|
-
mode: 'continuous',
|
|
683
|
-
revision: snapshot.revision,
|
|
684
|
-
treeCount: 0,
|
|
685
|
-
paletteCount: palettes.length,
|
|
686
|
-
trees: [],
|
|
687
|
-
palettes: palettes.slice(0, 100).map((palette) => ({
|
|
688
|
-
id: palette.id,
|
|
689
|
-
sourcePath: palette.provenance.sourcePath,
|
|
690
|
-
focusCount: palette.focuses.length,
|
|
691
|
-
diagnosticCount: paletteDiagnosticCounts.get(palette.id) ?? 0,
|
|
692
|
-
})),
|
|
693
|
-
});
|
|
694
|
-
result.code = 'FOCUS_INSPECTED';
|
|
695
|
-
setInlineFilesScanned(result, [
|
|
696
|
-
...new Set([
|
|
697
|
-
...selectedFiles.map(({ displayPath }) => displayPath),
|
|
698
|
-
...presentation.filesScanned,
|
|
699
|
-
]),
|
|
700
|
-
].sort((left, right) => compareCodeUnits(left, right)));
|
|
701
|
-
result.diagnostics = diagnostics.slice(0, 100);
|
|
702
|
-
result.artifacts = [publicArtifactLink(artifact)];
|
|
703
|
-
result.validation = validationFromDiagnostics(diagnostics);
|
|
704
|
-
await progress.report(3, 3, 'Continuous focus inspection complete');
|
|
705
|
-
return toolResult(result);
|
|
706
|
-
}
|
|
707
|
-
const selectedFiles = activeFocusFiles(snapshot, workspace.registration.roots.focus).filter((file) => relativePath === undefined || file.relativePath === relativePath);
|
|
708
|
-
if (selectedFiles.length === 0) {
|
|
709
|
-
throw new ServiceError('FOCUS_SOURCE_NOT_FOUND', 'No active focus source matched the request');
|
|
710
|
-
}
|
|
711
|
-
const selectedSidecarFiles = selectedFiles.flatMap((file) => {
|
|
712
|
-
const resolved = activeSidecar(snapshot, file);
|
|
713
|
-
return resolved === undefined ? [] : [resolved.file];
|
|
714
|
-
});
|
|
715
|
-
const importedPlans = [];
|
|
716
|
-
const diagnostics = [];
|
|
717
|
-
const paletteImport = continuousPalettes(snapshot);
|
|
718
|
-
diagnostics.push(...paletteImport.diagnostics);
|
|
719
|
-
for (const file of selectedFiles) {
|
|
720
|
-
progress.signal.throwIfAborted();
|
|
721
|
-
const imported = importFocusFile(snapshot, file, paletteImport.palettes);
|
|
722
|
-
importedPlans.push(...imported.plans);
|
|
723
|
-
diagnostics.push(...imported.diagnostics);
|
|
724
|
-
}
|
|
725
|
-
const plans = importedPlans.filter(({ id }) => input.treeId === undefined || id === input.treeId);
|
|
726
|
-
if (plans.length === 0) {
|
|
727
|
-
throw new ServiceError('FOCUS_TREE_NOT_FOUND', input.treeId === undefined
|
|
728
|
-
? 'The selected source contains no national focus tree'
|
|
729
|
-
: `Focus tree was not found: ${input.treeId}`);
|
|
730
|
-
}
|
|
731
|
-
const inspectedPlans = [];
|
|
732
|
-
for (const plan of plans) {
|
|
733
|
-
const layout = await workbench.layoutAsync(plan, {
|
|
734
|
-
...(input.previous === undefined
|
|
735
|
-
? {}
|
|
736
|
-
: { previous: input.previous }),
|
|
737
|
-
...(input.laneSpacing === undefined ? {} : { laneSpacing: input.laneSpacing }),
|
|
738
|
-
...(input.nodeSpacing === undefined ? {} : { nodeSpacing: input.nodeSpacing }),
|
|
739
|
-
signal: progress.signal,
|
|
740
|
-
});
|
|
741
|
-
const lintDiagnostics = workbench.lint(plan, {
|
|
742
|
-
index: snapshot.index,
|
|
743
|
-
references: referenceCatalog(snapshot),
|
|
744
|
-
layout,
|
|
745
|
-
});
|
|
746
|
-
diagnostics.push(...lintDiagnostics);
|
|
747
|
-
inspectedPlans.push({ plan, layout, diagnosticCount: lintDiagnostics.length });
|
|
748
|
-
}
|
|
749
|
-
const presentation = await resolveFocusPresentation({
|
|
750
|
-
plans,
|
|
751
|
-
palettes: paletteImport.palettes,
|
|
752
|
-
files: snapshot.files,
|
|
753
|
-
index: snapshot.index,
|
|
754
|
-
scanner: engine.scanner,
|
|
755
|
-
workspace,
|
|
756
|
-
decodeIcons: false,
|
|
757
|
-
signal: progress.signal,
|
|
758
|
-
});
|
|
759
|
-
diagnostics.push(...presentation.diagnostics);
|
|
760
|
-
await progress.report(2, 3, 'Writing focus plans, diagnostics, and layouts');
|
|
761
|
-
const completeSourceHashes = Object.fromEntries(Object.entries({
|
|
762
|
-
...Object.fromEntries([
|
|
763
|
-
...selectedFiles,
|
|
764
|
-
...selectedSidecarFiles,
|
|
765
|
-
...activeContinuousFocusFiles(snapshot),
|
|
766
|
-
].map(({ displayPath, sha256 }) => [displayPath, sha256])),
|
|
767
|
-
...presentation.sourceHashes,
|
|
768
|
-
}).sort(([left], [right]) => compareCodeUnits(left, right)));
|
|
769
|
-
const sourceEvidence = boundedSourceHashEvidence(completeSourceHashes);
|
|
770
|
-
const artifact = await engine.artifacts.putChunked(workspace, `focus-inspect.${snapshot.revision.slice(0, 16)}.json`, 'application/json', `${canonicalJson({
|
|
771
|
-
schemaVersion: 1,
|
|
772
|
-
revision: snapshot.revision,
|
|
773
|
-
plans,
|
|
774
|
-
layouts: inspectedPlans.map(({ plan, layout, diagnosticCount }) => ({
|
|
775
|
-
treeId: plan.id,
|
|
776
|
-
layout,
|
|
777
|
-
diagnosticCount,
|
|
778
|
-
})),
|
|
779
|
-
continuousFocusPalettes: paletteImport.palettes,
|
|
780
|
-
presentation: focusPresentationEvidence(presentation),
|
|
781
|
-
sourceHashes: completeSourceHashes,
|
|
782
|
-
diagnostics,
|
|
783
|
-
})}\n`, {
|
|
784
|
-
kind: 'focus-inspect',
|
|
785
|
-
toolVersion: PACKAGE_VERSION,
|
|
786
|
-
schemaVersion: 'focus-inspect.v1',
|
|
787
|
-
sourceHashes: sourceEvidence.sourceHashes,
|
|
788
|
-
metadata: { sourceHashInventory: sourceEvidence.inventory },
|
|
789
|
-
}, 'Focus plans, diagnostics, and stable layout decisions', progress.signal);
|
|
790
|
-
const result = emptyServiceResult(workspaceId, {
|
|
791
|
-
mode: 'national',
|
|
792
|
-
revision: snapshot.revision,
|
|
793
|
-
treeCount: plans.length,
|
|
794
|
-
paletteCount: paletteImport.palettes.length,
|
|
795
|
-
trees: inspectedPlans.slice(0, 100).map(({ plan, layout, diagnosticCount }) => ({
|
|
796
|
-
id: plan.id,
|
|
797
|
-
sourcePath: plan.provenance.sourcePath,
|
|
798
|
-
focusCount: plan.focuses.length,
|
|
799
|
-
branchCount: plan.branchGroups.length,
|
|
800
|
-
continuousPaletteCount: plan.continuousFocusPaletteIds.length,
|
|
801
|
-
continuousFocusCount: plan.continuousFocusIds.length,
|
|
802
|
-
continuousFocusPosition: plan.continuousFocusPosition ?? null,
|
|
803
|
-
continuousFocusPaletteIds: plan.continuousFocusPaletteIds.slice(0, 100),
|
|
804
|
-
resolvedTitleCount: plan.focuses.filter(({ id }) => presentation.entries[id]?.titleSourceLocation !== undefined).length,
|
|
805
|
-
layoutHash: layout.layoutHash,
|
|
806
|
-
layoutDecisionCount: layout.decisions.length,
|
|
807
|
-
layoutMetrics: layout.metrics,
|
|
808
|
-
diagnosticCount,
|
|
809
|
-
})),
|
|
810
|
-
palettes: [],
|
|
811
|
-
});
|
|
812
|
-
result.code = 'FOCUS_INSPECTED';
|
|
813
|
-
setInlineFilesScanned(result, [
|
|
814
|
-
...new Set([
|
|
815
|
-
...selectedFiles.map(({ displayPath }) => displayPath),
|
|
816
|
-
...selectedSidecarFiles.map(({ displayPath }) => displayPath),
|
|
817
|
-
...activeContinuousFocusFiles(snapshot).map(({ displayPath }) => displayPath),
|
|
818
|
-
...presentation.filesScanned,
|
|
819
|
-
]),
|
|
820
|
-
].sort((left, right) => compareCodeUnits(left, right)));
|
|
821
|
-
result.diagnostics = diagnostics.slice(0, 100);
|
|
822
|
-
result.artifacts = [publicArtifactLink(artifact)];
|
|
823
|
-
result.validation = validationFromDiagnostics(diagnostics);
|
|
824
|
-
await progress.report(3, 3, 'Focus inspection complete');
|
|
825
|
-
return toolResult(result);
|
|
826
|
-
}
|
|
827
|
-
catch (error) {
|
|
828
|
-
return errorResult(error, workspaceId);
|
|
829
|
-
}
|
|
830
|
-
finally {
|
|
831
|
-
releaseCaches();
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
server.registerTool('hoi4.focus_render', {
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'hoi4.focus_render',
|
|
835
135
|
title: 'Render focus review artifacts',
|
|
836
136
|
description: 'Render a national tree or continuous palette as fast deterministic HTML, SVG, JSON, and source-map artifacts. Use hoi4.focus_raster when decoded icons and PNG output are needed.',
|
|
837
|
-
inputSchema:
|
|
137
|
+
inputSchema: focusRenderRequestSchema,
|
|
838
138
|
outputSchema: focusRenderOutputSchema,
|
|
839
139
|
annotations: artifactProducing,
|
|
840
|
-
},
|
|
841
|
-
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'hoi4.focus_raster',
|
|
842
143
|
title: 'Rasterize focus review artifacts',
|
|
843
144
|
description: 'Produce the high-fidelity focus review with decoded source icons and deterministic PNG output. Use focus_render for the faster structural HTML, SVG, and JSON view.',
|
|
844
|
-
inputSchema:
|
|
145
|
+
inputSchema: focusRenderRequestSchema,
|
|
845
146
|
outputSchema: focusRenderOutputSchema,
|
|
846
147
|
annotations: artifactProducing,
|
|
847
|
-
},
|
|
848
|
-
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'hoi4.focus_rewrite',
|
|
849
151
|
title: 'Create or clean up focus content',
|
|
850
152
|
description: 'Create or clean up a national tree or continuous palette, validate it, and apply it in one call. Supply a complete plan; set layoutMode compact for automatic arrangement. Existing national trees can omit the plan and use treeId plus layoutMode compact. Set createIfMissing for a new file.',
|
|
851
|
-
inputSchema:
|
|
153
|
+
inputSchema: focusRewriteRequestSchema,
|
|
852
154
|
outputSchema: focusPlanOutputSchema,
|
|
853
155
|
annotations: {
|
|
854
156
|
readOnlyHint: false,
|
|
@@ -856,315 +158,8 @@ export function registerFocusTools(server, engine, context) {
|
|
|
856
158
|
idempotentHint: false,
|
|
857
159
|
openWorldHint: false,
|
|
858
160
|
},
|
|
859
|
-
},
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
try {
|
|
864
|
-
requireServerScope(context, 'hoi4:write');
|
|
865
|
-
const progress = progressReporter(extra);
|
|
866
|
-
await progress.report(0, 5, 'Validating shared index and current source');
|
|
867
|
-
const workspace = engine.resolver.get(workspaceId, context.principal);
|
|
868
|
-
const snapshot = await engine.scan(workspaceId, { patterns: focusDomainScanPatterns(workspace) }, context.principal, progress.signal);
|
|
869
|
-
if (input.mode === 'continuous') {
|
|
870
|
-
const plan = input.plan;
|
|
871
|
-
if (createIfMissing &&
|
|
872
|
-
(!relativePath.toLowerCase().endsWith('.txt') ||
|
|
873
|
-
!underConfiguredRoot(relativePath, ['common/continuous_focus'])))
|
|
874
|
-
throw new ServiceError('CONTINUOUS_FOCUS_SOURCE_PATH_INVALID', 'New continuous focus sources must be .txt files beneath common/continuous_focus', { relativePath });
|
|
875
|
-
const sourceFile = activeContinuousFocusFiles(snapshot).find((candidate) => candidate.relativePath === relativePath);
|
|
876
|
-
if (sourceFile === undefined && !createIfMissing)
|
|
877
|
-
selectedContinuousFocusFile(snapshot, relativePath);
|
|
878
|
-
if (sourceFile !== undefined && sourceFile.rootKind !== 'mod')
|
|
879
|
-
throw new ServiceError('CONTINUOUS_FOCUS_SOURCE_READ_ONLY', 'Continuous focus changes require a mod-owned source; create a new mod source path instead of shadowing a read-only file', { relativePath, rootKind: sourceFile.rootKind });
|
|
880
|
-
const imported = sourceFile === undefined
|
|
881
|
-
? undefined
|
|
882
|
-
: await workbench.importContinuousPath(workspaceId, relativePath, context.principal);
|
|
883
|
-
const currentPlan = imported?.result.continuousFocusPalettes.find(({ id }) => id === plan.id);
|
|
884
|
-
if (currentPlan === undefined && !createIfMissing) {
|
|
885
|
-
if (imported === undefined)
|
|
886
|
-
selectedContinuousFocusFile(snapshot, relativePath);
|
|
887
|
-
else
|
|
888
|
-
selectContinuousPlan(imported.result, plan.id);
|
|
889
|
-
}
|
|
890
|
-
if (currentPlan === undefined && createIfMissing && sourceFile !== undefined)
|
|
891
|
-
throw new ServiceError('CONTINUOUS_FOCUS_CREATE_REQUIRES_NEW_SOURCE', 'Creating a continuous-focus palette requires a new mod source file so unrelated existing source is never repurposed', { relativePath, paletteId: plan.id });
|
|
892
|
-
const renderBudget = new RenderBudget();
|
|
893
|
-
const presentation = await resolveFocusPresentation({
|
|
894
|
-
plans: [],
|
|
895
|
-
palettes: [...(currentPlan === undefined ? [] : [currentPlan]), plan],
|
|
896
|
-
files: snapshot.files,
|
|
897
|
-
index: snapshot.index,
|
|
898
|
-
scanner: engine.scanner,
|
|
899
|
-
workspace,
|
|
900
|
-
decodeIcons: false,
|
|
901
|
-
budget: renderBudget,
|
|
902
|
-
signal: progress.signal,
|
|
903
|
-
});
|
|
904
|
-
await progress.report(1, 5, 'Building proposed continuous focus review');
|
|
905
|
-
const proposedPlanHash = focusPlanHash(plan);
|
|
906
|
-
const reviewPlan = {
|
|
907
|
-
...plan,
|
|
908
|
-
provenance: {
|
|
909
|
-
sourcePath: `plan:${plan.id}`,
|
|
910
|
-
sourceHash: proposedPlanHash,
|
|
911
|
-
importedPlanHash: proposedPlanHash,
|
|
912
|
-
},
|
|
913
|
-
};
|
|
914
|
-
const proposed = await workbench.renderContinuousAndStore(workspaceId, reviewPlan, {
|
|
915
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
916
|
-
presentation,
|
|
917
|
-
sourceHashes: presentation.sourceHashes,
|
|
918
|
-
renderProfile: {
|
|
919
|
-
sourceRevision: snapshot.revision,
|
|
920
|
-
proposedPlanHash,
|
|
921
|
-
output: 'vector',
|
|
922
|
-
},
|
|
923
|
-
rasterize: false,
|
|
924
|
-
budget: renderBudget,
|
|
925
|
-
signal: progress.signal,
|
|
926
|
-
});
|
|
927
|
-
const reviewArtifacts = proposed.artifacts.map(publicArtifactLink);
|
|
928
|
-
await progress.report(3, 5, 'Preparing the continuous focus rewrite');
|
|
929
|
-
const planned = await workbench.planContinuousChanges({
|
|
930
|
-
workspaceId,
|
|
931
|
-
relativePath,
|
|
932
|
-
plan,
|
|
933
|
-
createIfMissing,
|
|
934
|
-
authority: 'plan',
|
|
935
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
936
|
-
artifacts: reviewArtifacts,
|
|
937
|
-
readDependencies: readDependenciesFromScannedFiles(snapshot.files),
|
|
938
|
-
signal: progress.signal,
|
|
939
|
-
});
|
|
940
|
-
await progress.report(4, 5, 'Applying and validating the continuous focus rewrite');
|
|
941
|
-
const execution = await executePlannedTransaction(engine, planned.transaction, context.principal, progress.signal);
|
|
942
|
-
const transaction = execution.transaction;
|
|
943
|
-
const result = emptyServiceResult(workspaceId, {
|
|
944
|
-
mode: 'continuous',
|
|
945
|
-
paletteId: plan.id,
|
|
946
|
-
drift: compactDrift(planned.drift),
|
|
947
|
-
created: planned.drift.status === 'target_missing',
|
|
948
|
-
execution: execution.outcome,
|
|
949
|
-
fileCount: transaction.files.length,
|
|
950
|
-
artifactCount: transaction.artifacts.length,
|
|
951
|
-
});
|
|
952
|
-
result.status = transaction.validation.passed ? 'ok' : 'blocked';
|
|
953
|
-
result.code =
|
|
954
|
-
execution.outcome === 'applied'
|
|
955
|
-
? 'CONTINUOUS_FOCUS_CHANGES_APPLIED'
|
|
956
|
-
: execution.outcome === 'unchanged'
|
|
957
|
-
? 'CONTINUOUS_FOCUS_CHANGES_UNCHANGED'
|
|
958
|
-
: 'CONTINUOUS_FOCUS_CHANGES_BLOCKED';
|
|
959
|
-
setInlineFilesScanned(result, [
|
|
960
|
-
...new Set([
|
|
961
|
-
...(sourceFile === undefined ? [] : [sourceFile.displayPath]),
|
|
962
|
-
...presentation.filesScanned,
|
|
963
|
-
]),
|
|
964
|
-
].sort((left, right) => compareCodeUnits(left, right)));
|
|
965
|
-
result.proposedFiles = transaction.files
|
|
966
|
-
.slice(0, 100)
|
|
967
|
-
.map(({ relativePath: file }) => file);
|
|
968
|
-
result.changedFiles = transaction.appliedFiles.slice(0, 100);
|
|
969
|
-
result.diagnostics = transaction.diagnostics.slice(0, 100);
|
|
970
|
-
result.artifacts = autonomousResultArtifacts(execution);
|
|
971
|
-
result.validation = transaction.validation;
|
|
972
|
-
result.blockers = transaction.diagnostics
|
|
973
|
-
.filter(({ severity }) => severity === 'error' || severity === 'blocker')
|
|
974
|
-
.map(({ code, message, details }) => ({
|
|
975
|
-
code,
|
|
976
|
-
message,
|
|
977
|
-
...(details === undefined ? {} : { details }),
|
|
978
|
-
}));
|
|
979
|
-
await progress.report(5, 5, execution.outcome === 'applied'
|
|
980
|
-
? 'Continuous focus rewrite complete'
|
|
981
|
-
: execution.outcome === 'unchanged'
|
|
982
|
-
? 'Continuous focus content already satisfied the plan'
|
|
983
|
-
: 'Continuous focus rewrite blocked');
|
|
984
|
-
return toolResult(result);
|
|
985
|
-
}
|
|
986
|
-
const suppliedPlan = input.plan;
|
|
987
|
-
if (createIfMissing &&
|
|
988
|
-
(!relativePath.toLowerCase().endsWith('.txt') ||
|
|
989
|
-
!underConfiguredRoot(relativePath, workspace.registration.roots.focus)))
|
|
990
|
-
throw new ServiceError('FOCUS_SOURCE_PATH_INVALID', 'New national focus sources must be .txt files beneath a configured focus root', { relativePath });
|
|
991
|
-
const focusFile = activeFocusFiles(snapshot, workspace.registration.roots.focus).find((candidate) => candidate.relativePath === relativePath);
|
|
992
|
-
if (focusFile === undefined && !createIfMissing)
|
|
993
|
-
selectedFocusFile(snapshot, workspace.registration.roots.focus, relativePath);
|
|
994
|
-
if (focusFile !== undefined && focusFile.rootKind !== 'mod')
|
|
995
|
-
throw new ServiceError('FOCUS_SOURCE_READ_ONLY', 'National focus changes require a mod-owned source; create a new mod source path instead of shadowing a read-only file', { relativePath, rootKind: focusFile.rootKind });
|
|
996
|
-
const sidecar = focusFile === undefined ? undefined : activeSidecar(snapshot, focusFile);
|
|
997
|
-
const catalog = referenceCatalog(snapshot);
|
|
998
|
-
const imported = focusFile === undefined
|
|
999
|
-
? undefined
|
|
1000
|
-
: await workbench.importPath(workspaceId, relativePath, context.principal, sidecar?.sidecar, catalog);
|
|
1001
|
-
const paletteImport = continuousPalettes(snapshot);
|
|
1002
|
-
if (imported !== undefined)
|
|
1003
|
-
imported.result.plans = imported.result.plans.map((current) => linkContinuousFocusPalettes(current, paletteImport.palettes));
|
|
1004
|
-
let compactPlanning;
|
|
1005
|
-
let compactSourcePlan;
|
|
1006
|
-
let plan;
|
|
1007
|
-
if (suppliedPlan !== undefined) {
|
|
1008
|
-
if (input.layoutMode === 'compact') {
|
|
1009
|
-
compactSourcePlan = suppliedPlan;
|
|
1010
|
-
compactPlanning = await compactFocusTreePlanAsync(suppliedPlan, {
|
|
1011
|
-
signal: progress.signal,
|
|
1012
|
-
});
|
|
1013
|
-
plan = compactPlanning.plan;
|
|
1014
|
-
}
|
|
1015
|
-
else {
|
|
1016
|
-
plan = suppliedPlan;
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
if (imported === undefined)
|
|
1021
|
-
throw new ServiceError('FOCUS_COMPACT_SOURCE_REQUIRED', 'Plan-free compact reflow requires an existing national focus source', { relativePath });
|
|
1022
|
-
compactSourcePlan = selectPlan(imported.result, input.treeId);
|
|
1023
|
-
compactPlanning = await compactFocusTreePlanAsync(compactSourcePlan, {
|
|
1024
|
-
signal: progress.signal,
|
|
1025
|
-
});
|
|
1026
|
-
plan = compactPlanning.plan;
|
|
1027
|
-
}
|
|
1028
|
-
const currentPlan = imported?.result.plans.find(({ id }) => id === plan.id);
|
|
1029
|
-
if (currentPlan === undefined && !createIfMissing) {
|
|
1030
|
-
if (imported === undefined)
|
|
1031
|
-
selectedFocusFile(snapshot, workspace.registration.roots.focus, relativePath);
|
|
1032
|
-
else
|
|
1033
|
-
selectPlan(imported.result, plan.id);
|
|
1034
|
-
}
|
|
1035
|
-
if (currentPlan === undefined && createIfMissing && focusFile !== undefined)
|
|
1036
|
-
throw new ServiceError('FOCUS_CREATE_REQUIRES_NEW_SOURCE', 'Creating a national focus tree requires a new mod source file so an existing source and its planning sidecar cannot be repurposed', { relativePath, treeId: plan.id });
|
|
1037
|
-
const renderBudget = new RenderBudget();
|
|
1038
|
-
const effectiveReviewScale = reviewScale ?? automaticFocusRenderScale(plan.focuses.length);
|
|
1039
|
-
const reviewRenderOptions = {
|
|
1040
|
-
...(horizontalSpacing === undefined ? {} : { horizontalSpacing }),
|
|
1041
|
-
...(verticalSpacing === undefined ? {} : { verticalSpacing }),
|
|
1042
|
-
...(padding === undefined ? {} : { padding }),
|
|
1043
|
-
outputScale: effectiveReviewScale,
|
|
1044
|
-
};
|
|
1045
|
-
const reviewRenderProfile = {
|
|
1046
|
-
...(horizontalSpacing === undefined ? {} : { horizontalSpacing }),
|
|
1047
|
-
...(verticalSpacing === undefined ? {} : { verticalSpacing }),
|
|
1048
|
-
...(padding === undefined ? {} : { padding }),
|
|
1049
|
-
reviewScale: effectiveReviewScale,
|
|
1050
|
-
};
|
|
1051
|
-
const presentation = await resolveFocusPresentation({
|
|
1052
|
-
plans: [...(currentPlan === undefined ? [] : [currentPlan]), plan],
|
|
1053
|
-
palettes: paletteImport.palettes.filter(({ id }) => plan.continuousFocusPaletteIds.includes(id)),
|
|
1054
|
-
files: snapshot.files,
|
|
1055
|
-
index: snapshot.index,
|
|
1056
|
-
scanner: engine.scanner,
|
|
1057
|
-
workspace,
|
|
1058
|
-
decodeIcons: false,
|
|
1059
|
-
budget: renderBudget,
|
|
1060
|
-
signal: progress.signal,
|
|
1061
|
-
});
|
|
1062
|
-
const currentLayout = currentPlan === undefined
|
|
1063
|
-
? undefined
|
|
1064
|
-
: currentPlan === compactSourcePlan && compactPlanning !== undefined
|
|
1065
|
-
? compactPlanning.currentLayout
|
|
1066
|
-
: await workbench.layoutAsync(currentPlan, { signal: progress.signal });
|
|
1067
|
-
await progress.report(1, 5, 'Building proposed focus review');
|
|
1068
|
-
const proposedPlanHash = focusPlanHash(plan);
|
|
1069
|
-
const reviewPlan = {
|
|
1070
|
-
...plan,
|
|
1071
|
-
provenance: {
|
|
1072
|
-
sourcePath: `plan:${plan.id}`,
|
|
1073
|
-
sourceHash: proposedPlanHash,
|
|
1074
|
-
importedPlanHash: proposedPlanHash,
|
|
1075
|
-
},
|
|
1076
|
-
};
|
|
1077
|
-
const proposedLayout = compactPlanning?.proposedLayout ??
|
|
1078
|
-
(await workbench.layoutAsync(reviewPlan, { signal: progress.signal }));
|
|
1079
|
-
if (input.layoutMode === 'compact') {
|
|
1080
|
-
assertCompactLayoutQuality(currentLayout, proposedLayout);
|
|
1081
|
-
}
|
|
1082
|
-
const proposed = await workbench.renderAndStore(workspaceId, reviewPlan, {
|
|
1083
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
1084
|
-
index: snapshot.index,
|
|
1085
|
-
references: catalog,
|
|
1086
|
-
presentation,
|
|
1087
|
-
sourceHashes: presentation.sourceHashes,
|
|
1088
|
-
...reviewRenderOptions,
|
|
1089
|
-
rasterize: false,
|
|
1090
|
-
renderProfile: {
|
|
1091
|
-
sourceRevision: snapshot.revision,
|
|
1092
|
-
proposedPlanHash,
|
|
1093
|
-
reviewRenderProfile,
|
|
1094
|
-
output: 'vector',
|
|
1095
|
-
},
|
|
1096
|
-
budget: renderBudget,
|
|
1097
|
-
signal: progress.signal,
|
|
1098
|
-
layout: proposedLayout,
|
|
1099
|
-
});
|
|
1100
|
-
const reviewArtifacts = proposed.artifacts.map(publicArtifactLink);
|
|
1101
|
-
await progress.report(3, 5, 'Preparing the focus-tree rewrite');
|
|
1102
|
-
const planned = await workbench.planChanges({
|
|
1103
|
-
workspaceId,
|
|
1104
|
-
relativePath,
|
|
1105
|
-
plan,
|
|
1106
|
-
createIfMissing,
|
|
1107
|
-
authority: 'plan',
|
|
1108
|
-
layout: proposed.layout,
|
|
1109
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
1110
|
-
index: snapshot.index,
|
|
1111
|
-
references: catalog,
|
|
1112
|
-
artifacts: reviewArtifacts,
|
|
1113
|
-
readDependencies: readDependenciesFromScannedFiles(snapshot.files),
|
|
1114
|
-
signal: progress.signal,
|
|
1115
|
-
});
|
|
1116
|
-
await progress.report(4, 5, 'Applying and validating the focus-tree rewrite');
|
|
1117
|
-
const execution = await executePlannedTransaction(engine, planned.transaction, context.principal, progress.signal);
|
|
1118
|
-
const transaction = execution.transaction;
|
|
1119
|
-
const result = emptyServiceResult(workspaceId, {
|
|
1120
|
-
mode: 'national',
|
|
1121
|
-
treeId: plan.id,
|
|
1122
|
-
drift: compactDrift(planned.drift),
|
|
1123
|
-
created: planned.drift.status === 'target_missing',
|
|
1124
|
-
execution: execution.outcome,
|
|
1125
|
-
layoutHash: planned.layout.layoutHash,
|
|
1126
|
-
fileCount: transaction.files.length,
|
|
1127
|
-
artifactCount: transaction.artifacts.length,
|
|
1128
|
-
});
|
|
1129
|
-
result.status = transaction.validation.passed ? 'ok' : 'blocked';
|
|
1130
|
-
result.code =
|
|
1131
|
-
execution.outcome === 'applied'
|
|
1132
|
-
? 'FOCUS_CHANGES_APPLIED'
|
|
1133
|
-
: execution.outcome === 'unchanged'
|
|
1134
|
-
? 'FOCUS_CHANGES_UNCHANGED'
|
|
1135
|
-
: 'FOCUS_CHANGES_BLOCKED';
|
|
1136
|
-
setInlineFilesScanned(result, [
|
|
1137
|
-
...new Set([
|
|
1138
|
-
...(focusFile === undefined ? [] : [focusFile.displayPath]),
|
|
1139
|
-
...presentation.filesScanned,
|
|
1140
|
-
]),
|
|
1141
|
-
].sort((left, right) => compareCodeUnits(left, right)));
|
|
1142
|
-
result.proposedFiles = transaction.files
|
|
1143
|
-
.slice(0, 100)
|
|
1144
|
-
.map(({ relativePath: file }) => file);
|
|
1145
|
-
result.changedFiles = transaction.appliedFiles.slice(0, 100);
|
|
1146
|
-
result.diagnostics = transaction.diagnostics.slice(0, 100);
|
|
1147
|
-
result.artifacts = autonomousResultArtifacts(execution);
|
|
1148
|
-
result.validation = transaction.validation;
|
|
1149
|
-
result.blockers = transaction.diagnostics
|
|
1150
|
-
.filter(({ severity }) => severity === 'error' || severity === 'blocker')
|
|
1151
|
-
.map(({ code, message, details }) => ({
|
|
1152
|
-
code,
|
|
1153
|
-
message,
|
|
1154
|
-
...(details === undefined ? {} : { details }),
|
|
1155
|
-
}));
|
|
1156
|
-
await progress.report(5, 5, execution.outcome === 'applied'
|
|
1157
|
-
? 'Focus rewrite complete'
|
|
1158
|
-
: execution.outcome === 'unchanged'
|
|
1159
|
-
? 'Focus content already satisfied the plan'
|
|
1160
|
-
: 'Focus rewrite blocked');
|
|
1161
|
-
return toolResult(result);
|
|
1162
|
-
}
|
|
1163
|
-
catch (error) {
|
|
1164
|
-
return errorResult(error, workspaceId, autonomousFailureContext(error));
|
|
1165
|
-
}
|
|
1166
|
-
finally {
|
|
1167
|
-
releaseCaches();
|
|
1168
|
-
}
|
|
1169
|
-
});
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
export function registerFocusTools(server, _engine, _context) {
|
|
164
|
+
registerLegacyTaskTools(server, focusTaskTools);
|
|
1170
165
|
}
|