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,216 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { hashCanonical } from './canonical.js';
|
|
3
|
+
import { ServiceError } from './result.js';
|
|
4
|
+
const ordinal = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
5
|
+
export const dependencyPageCursorSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
version: z.literal(1),
|
|
8
|
+
key: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
9
|
+
root: ordinal,
|
|
10
|
+
steps: ordinal,
|
|
11
|
+
emitted: ordinal,
|
|
12
|
+
cycles: ordinal,
|
|
13
|
+
depthStops: ordinal,
|
|
14
|
+
frames: z
|
|
15
|
+
.array(z.object({ edge: ordinal, next: ordinal, entered: z.boolean() }).strict())
|
|
16
|
+
.max(256),
|
|
17
|
+
visited: z.array(z.string().min(1)).max(1_000_000),
|
|
18
|
+
})
|
|
19
|
+
.strict();
|
|
20
|
+
/** Bounded DFS pages: retain only active ancestors and, for node policy, this root's visited nodes. */
|
|
21
|
+
export class DependencyPages {
|
|
22
|
+
input;
|
|
23
|
+
key;
|
|
24
|
+
state;
|
|
25
|
+
roots;
|
|
26
|
+
outgoing = new Map();
|
|
27
|
+
branches;
|
|
28
|
+
visited;
|
|
29
|
+
constructor(input, restored) {
|
|
30
|
+
this.input = input;
|
|
31
|
+
if (!Number.isSafeInteger(input.maximumDepth) ||
|
|
32
|
+
input.maximumDepth < 1 ||
|
|
33
|
+
input.maximumDepth > 256 ||
|
|
34
|
+
input.edges.length > 1_000_000)
|
|
35
|
+
throw new ServiceError('DEPENDENCY_PAGE_INPUT_INVALID', 'Dependency pagination requires bounded edge and depth inventories');
|
|
36
|
+
const byId = new Map(input.edges.map(({ id }, index) => [id, index]));
|
|
37
|
+
if (byId.size !== input.edges.length ||
|
|
38
|
+
input.roots.some((id) => !byId.has(id)) ||
|
|
39
|
+
new Set(input.roots).size !== input.roots.length)
|
|
40
|
+
throw new ServiceError('DEPENDENCY_PAGE_INPUT_INVALID', 'Dependency edge and root identities must be unique and all roots must exist');
|
|
41
|
+
this.key = hashCanonical({ algorithm: 'dependency-pages.v1', ...input });
|
|
42
|
+
this.roots = input.roots.map((id) => byId.get(id));
|
|
43
|
+
this.branches = new Set(input.branches);
|
|
44
|
+
for (const [index, { from }] of input.edges.entries()) {
|
|
45
|
+
const values = this.outgoing.get(from) ?? [];
|
|
46
|
+
values.push(index);
|
|
47
|
+
this.outgoing.set(from, values);
|
|
48
|
+
}
|
|
49
|
+
if (input.reverseEdges)
|
|
50
|
+
for (const values of this.outgoing.values())
|
|
51
|
+
values.reverse();
|
|
52
|
+
if (restored === undefined)
|
|
53
|
+
this.state = {
|
|
54
|
+
version: 1,
|
|
55
|
+
key: this.key,
|
|
56
|
+
root: 0,
|
|
57
|
+
steps: 0,
|
|
58
|
+
emitted: 0,
|
|
59
|
+
cycles: 0,
|
|
60
|
+
depthStops: 0,
|
|
61
|
+
frames: [],
|
|
62
|
+
visited: [],
|
|
63
|
+
};
|
|
64
|
+
else {
|
|
65
|
+
const parsed = dependencyPageCursorSchema.safeParse(restored);
|
|
66
|
+
if (!parsed.success)
|
|
67
|
+
throw new ServiceError('DEPENDENCY_CURSOR_INVALID', 'Dependency continuation has an invalid state schema');
|
|
68
|
+
this.state = parsed.data;
|
|
69
|
+
}
|
|
70
|
+
if (this.state.key !== this.key)
|
|
71
|
+
throw new ServiceError('DEPENDENCY_CURSOR_STALE', 'Dependency continuation does not match the exact source revision and traversal contract');
|
|
72
|
+
this.validate();
|
|
73
|
+
this.visited = new Set(this.state.visited);
|
|
74
|
+
}
|
|
75
|
+
validate() {
|
|
76
|
+
const invalid = () => {
|
|
77
|
+
throw new ServiceError('DEPENDENCY_CURSOR_INVALID', 'Dependency continuation has inconsistent counters or DFS ancestry');
|
|
78
|
+
};
|
|
79
|
+
const { root, frames, visited, steps, emitted, cycles, depthStops } = this.state;
|
|
80
|
+
if (root > this.roots.length ||
|
|
81
|
+
frames.length > this.input.maximumDepth ||
|
|
82
|
+
(root === this.roots.length && frames.length !== 0) ||
|
|
83
|
+
emitted > steps ||
|
|
84
|
+
cycles + depthStops > emitted ||
|
|
85
|
+
new Set(visited).size !== visited.length ||
|
|
86
|
+
(frames.length === 0 && visited.length !== 0) ||
|
|
87
|
+
(this.input.visitPolicy === 'edge_path' && visited.length !== 0))
|
|
88
|
+
invalid();
|
|
89
|
+
const nodes = new Set(this.input.edges.map(({ to }) => to));
|
|
90
|
+
if (visited.some((node) => !nodes.has(node)))
|
|
91
|
+
invalid();
|
|
92
|
+
const ancestors = new Set();
|
|
93
|
+
for (const [index, frame] of frames.entries()) {
|
|
94
|
+
const edge = this.input.edges[frame.edge];
|
|
95
|
+
if (edge === undefined || ancestors.has(edge.to))
|
|
96
|
+
invalid();
|
|
97
|
+
ancestors.add(edge.to);
|
|
98
|
+
if (frame.next > (this.outgoing.get(edge.to)?.length ?? 0) ||
|
|
99
|
+
(!frame.entered && (index !== frames.length - 1 || frame.next !== 0)))
|
|
100
|
+
invalid();
|
|
101
|
+
if (index === 0) {
|
|
102
|
+
if (frame.edge !== this.roots[root])
|
|
103
|
+
invalid();
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const parent = frames[index - 1];
|
|
107
|
+
const previous = this.input.edges[parent.edge];
|
|
108
|
+
if (!parent.entered ||
|
|
109
|
+
parent.next === 0 ||
|
|
110
|
+
this.outgoing.get(previous.to)?.[parent.next - 1] !== frame.edge ||
|
|
111
|
+
previous.to !== edge.from ||
|
|
112
|
+
!this.branches.has(edge.to))
|
|
113
|
+
invalid();
|
|
114
|
+
}
|
|
115
|
+
if (this.input.visitPolicy === 'node' && !visited.includes(edge.to))
|
|
116
|
+
invalid();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
cursor() {
|
|
120
|
+
return {
|
|
121
|
+
...this.state,
|
|
122
|
+
frames: this.state.frames.map((frame) => ({ ...frame })),
|
|
123
|
+
visited: [...this.visited],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
page(maximumRecords, maximumWork, signal) {
|
|
127
|
+
if (!Number.isSafeInteger(maximumRecords) ||
|
|
128
|
+
maximumRecords < 1 ||
|
|
129
|
+
maximumRecords > 5_000 ||
|
|
130
|
+
!Number.isSafeInteger(maximumWork) ||
|
|
131
|
+
maximumWork < 1 ||
|
|
132
|
+
maximumWork > 100_000)
|
|
133
|
+
throw new ServiceError('DEPENDENCY_PAGE_BOUND_INVALID', 'Dependency pages require 1–5,000 records and 1–100,000 work transitions');
|
|
134
|
+
const records = [];
|
|
135
|
+
const startedAt = this.state.steps;
|
|
136
|
+
const emit = (kind, extra) => {
|
|
137
|
+
records.push({
|
|
138
|
+
kind,
|
|
139
|
+
root: this.state.root,
|
|
140
|
+
path: [
|
|
141
|
+
...this.state.frames.map(({ edge }) => edge),
|
|
142
|
+
...(extra === undefined ? [] : [extra]),
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
this.state.emitted++;
|
|
146
|
+
if (kind === 'cycle')
|
|
147
|
+
this.state.cycles++;
|
|
148
|
+
if (kind === 'depth')
|
|
149
|
+
this.state.depthStops++;
|
|
150
|
+
};
|
|
151
|
+
while (this.state.root < this.roots.length && this.state.steps - startedAt < maximumWork) {
|
|
152
|
+
signal?.throwIfAborted();
|
|
153
|
+
if (this.state.steps === Number.MAX_SAFE_INTEGER)
|
|
154
|
+
throw new ServiceError('DEPENDENCY_PAGE_COUNTER_LIMIT', 'Dependency pagination exhausted its exact integer work counter');
|
|
155
|
+
const frame = this.state.frames.at(-1);
|
|
156
|
+
if (frame === undefined) {
|
|
157
|
+
const edge = this.roots[this.state.root];
|
|
158
|
+
this.state.frames.push({ edge, next: 0, entered: false });
|
|
159
|
+
this.visited.clear();
|
|
160
|
+
if (this.input.visitPolicy === 'node')
|
|
161
|
+
this.visited.add(this.input.edges[edge].to);
|
|
162
|
+
}
|
|
163
|
+
else if (!frame.entered) {
|
|
164
|
+
if (records.length >= maximumRecords)
|
|
165
|
+
break;
|
|
166
|
+
emit('visit');
|
|
167
|
+
frame.entered = true;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const node = this.input.edges[frame.edge].to;
|
|
171
|
+
const candidate = this.outgoing.get(node)?.[frame.next];
|
|
172
|
+
if (candidate === undefined) {
|
|
173
|
+
this.state.frames.pop();
|
|
174
|
+
if (this.state.frames.length === 0) {
|
|
175
|
+
this.state.root++;
|
|
176
|
+
this.visited.clear();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const edge = this.input.edges[candidate];
|
|
181
|
+
const cycle = this.state.frames.some((ancestor) => this.input.edges[ancestor.edge].to === edge.to);
|
|
182
|
+
const kind = !this.branches.has(edge.to)
|
|
183
|
+
? 'terminal'
|
|
184
|
+
: cycle
|
|
185
|
+
? 'cycle'
|
|
186
|
+
: this.state.frames.length >= this.input.maximumDepth
|
|
187
|
+
? 'depth'
|
|
188
|
+
: undefined;
|
|
189
|
+
if (kind !== undefined) {
|
|
190
|
+
if (records.length >= maximumRecords)
|
|
191
|
+
break;
|
|
192
|
+
emit(kind, candidate);
|
|
193
|
+
frame.next++;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
frame.next++;
|
|
197
|
+
if (this.input.visitPolicy === 'edge_path' || !this.visited.has(edge.to)) {
|
|
198
|
+
if (this.input.visitPolicy === 'node')
|
|
199
|
+
this.visited.add(edge.to);
|
|
200
|
+
this.state.frames.push({ edge: candidate, next: 0, entered: false });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.state.steps++;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
records,
|
|
209
|
+
finished: this.state.root === this.roots.length,
|
|
210
|
+
completedRoots: this.state.root,
|
|
211
|
+
totalRoots: this.roots.length,
|
|
212
|
+
work: this.state.steps - startedAt,
|
|
213
|
+
cursor: this.cursor(),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { hashCanonical } from './canonical.js';
|
|
3
|
+
import { currentAnalysisCheckpoints } from './analysis-checkpoints.js';
|
|
4
|
+
import { ServiceError } from './result.js';
|
|
5
|
+
const ordinal = z.number().int().nonnegative();
|
|
6
|
+
const pathSchema = z.object({ edge: ordinal, parent: ordinal.nullable() }).strict();
|
|
7
|
+
const recordSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
kind: z.enum(['visit', 'terminal', 'cycle', 'depth']),
|
|
10
|
+
root: ordinal,
|
|
11
|
+
path: ordinal,
|
|
12
|
+
})
|
|
13
|
+
.strict();
|
|
14
|
+
const stateSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
version: z.literal(1),
|
|
17
|
+
key: z.string(),
|
|
18
|
+
root: ordinal,
|
|
19
|
+
steps: ordinal,
|
|
20
|
+
paths: z.array(pathSchema),
|
|
21
|
+
records: z.array(recordSchema),
|
|
22
|
+
stack: z.array(ordinal),
|
|
23
|
+
visited: z.array(z.string()),
|
|
24
|
+
current: z.object({ path: ordinal, edge: ordinal }).strict().nullable(),
|
|
25
|
+
started: z.boolean(),
|
|
26
|
+
complete: z.boolean(),
|
|
27
|
+
})
|
|
28
|
+
.strict();
|
|
29
|
+
/** A bounded, serializable DFS frontier; paths share prefixes instead of copying source data. */
|
|
30
|
+
export class DependencyWalk {
|
|
31
|
+
input;
|
|
32
|
+
state;
|
|
33
|
+
key;
|
|
34
|
+
edges;
|
|
35
|
+
outgoing = new Map();
|
|
36
|
+
branches;
|
|
37
|
+
visited;
|
|
38
|
+
constructor(input, restored) {
|
|
39
|
+
this.input = input;
|
|
40
|
+
this.key = hashCanonical({ algorithm: 'dependency-walk.v1', ...input });
|
|
41
|
+
this.edges = new Map(input.edges.map(({ id }, index) => [id, index]));
|
|
42
|
+
if (this.edges.size !== input.edges.length || input.roots.some((id) => !this.edges.has(id)))
|
|
43
|
+
throw new ServiceError('DEPENDENCY_WALK_INPUT_INVALID', 'Dependency edge identities must be unique and roots must exist');
|
|
44
|
+
if (!Number.isSafeInteger(input.maximumDepth) ||
|
|
45
|
+
input.maximumDepth < 1 ||
|
|
46
|
+
!Number.isSafeInteger(input.maximumSteps) ||
|
|
47
|
+
input.maximumSteps < 1)
|
|
48
|
+
throw new ServiceError('DEPENDENCY_WALK_INPUT_INVALID', 'Dependency traversal requires finite positive bounds');
|
|
49
|
+
this.branches = new Set(input.branches);
|
|
50
|
+
for (const [index, edge] of input.edges.entries()) {
|
|
51
|
+
const values = this.outgoing.get(edge.from) ?? [];
|
|
52
|
+
values.push(index);
|
|
53
|
+
this.outgoing.set(edge.from, values);
|
|
54
|
+
}
|
|
55
|
+
if (input.reverseEdges)
|
|
56
|
+
for (const values of this.outgoing.values())
|
|
57
|
+
values.reverse();
|
|
58
|
+
const decoded = restored === undefined ? undefined : stateSchema.safeParse(restored);
|
|
59
|
+
if (decoded !== undefined && !decoded.success)
|
|
60
|
+
throw new ServiceError('DEPENDENCY_CHECKPOINT_INVALID', 'Dependency checkpoint has an invalid state schema');
|
|
61
|
+
this.state = decoded?.data ?? {
|
|
62
|
+
version: 1,
|
|
63
|
+
key: this.key,
|
|
64
|
+
root: 0,
|
|
65
|
+
steps: 0,
|
|
66
|
+
paths: [],
|
|
67
|
+
records: [],
|
|
68
|
+
stack: [],
|
|
69
|
+
visited: [],
|
|
70
|
+
current: null,
|
|
71
|
+
started: false,
|
|
72
|
+
complete: input.roots.length === 0,
|
|
73
|
+
};
|
|
74
|
+
if (this.state.key !== this.key ||
|
|
75
|
+
this.state.root > input.roots.length ||
|
|
76
|
+
this.state.steps > input.maximumSteps)
|
|
77
|
+
throw new ServiceError('DEPENDENCY_CHECKPOINT_STALE', 'Dependency checkpoint does not match this exact source graph and traversal contract');
|
|
78
|
+
this.validateState();
|
|
79
|
+
this.visited = new Set(this.state.visited);
|
|
80
|
+
}
|
|
81
|
+
validateState() {
|
|
82
|
+
const invalid = (message) => {
|
|
83
|
+
throw new ServiceError('DEPENDENCY_CHECKPOINT_INVALID', message);
|
|
84
|
+
};
|
|
85
|
+
if (this.state.complete !== (this.state.root === this.input.roots.length) ||
|
|
86
|
+
(!this.state.started && (this.state.current !== null || this.state.stack.length !== 0)) ||
|
|
87
|
+
(this.state.complete && this.state.started) ||
|
|
88
|
+
this.state.paths.length > this.state.steps ||
|
|
89
|
+
this.state.records.length > this.state.steps ||
|
|
90
|
+
new Set(this.state.stack).size !== this.state.stack.length ||
|
|
91
|
+
new Set(this.state.visited).size !== this.state.visited.length)
|
|
92
|
+
invalid('Dependency checkpoint has inconsistent lifecycle or retained work');
|
|
93
|
+
const roots = [];
|
|
94
|
+
const rootEdges = new Set(this.input.roots.map((id) => this.edges.get(id)));
|
|
95
|
+
for (const [index, path] of this.state.paths.entries()) {
|
|
96
|
+
if (path.edge >= this.input.edges.length || (path.parent !== null && path.parent >= index))
|
|
97
|
+
throw new ServiceError('DEPENDENCY_CHECKPOINT_INVALID', 'Dependency checkpoint contains an invalid path');
|
|
98
|
+
if (path.parent === null) {
|
|
99
|
+
if (!rootEdges.has(path.edge))
|
|
100
|
+
invalid('Dependency path starts outside the requested roots');
|
|
101
|
+
roots.push(path.edge);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const previous = this.input.edges[this.state.paths[path.parent].edge];
|
|
105
|
+
if (previous.to !== this.input.edges[path.edge].from)
|
|
106
|
+
invalid('Dependency checkpoint path is disconnected from its parent edge');
|
|
107
|
+
roots.push(roots[path.parent]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const index of [
|
|
111
|
+
...this.state.stack,
|
|
112
|
+
...(this.state.current === null ? [] : [this.state.current.path]),
|
|
113
|
+
])
|
|
114
|
+
if (index >= this.state.paths.length ||
|
|
115
|
+
this.input.edges[roots[index]].id !== this.input.roots[this.state.root])
|
|
116
|
+
throw new ServiceError('DEPENDENCY_CHECKPOINT_INVALID', 'Dependency frontier points outside its retained paths');
|
|
117
|
+
if (this.state.current !== null) {
|
|
118
|
+
const { path, edge } = this.state.current;
|
|
119
|
+
const node = this.input.edges[this.state.paths[path].edge].to;
|
|
120
|
+
if (edge > (this.outgoing.get(node)?.length ?? 0) || this.state.stack.includes(path))
|
|
121
|
+
invalid('Dependency adjacency cursor is outside its current node or duplicates the frontier');
|
|
122
|
+
}
|
|
123
|
+
for (const record of this.state.records)
|
|
124
|
+
if (record.root >= this.input.roots.length ||
|
|
125
|
+
record.path >= this.state.paths.length ||
|
|
126
|
+
this.input.edges[roots[record.path]].id !== this.input.roots[record.root])
|
|
127
|
+
throw new ServiceError('DEPENDENCY_CHECKPOINT_INVALID', 'Dependency evidence points outside its retained graph');
|
|
128
|
+
}
|
|
129
|
+
path(index) {
|
|
130
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= this.state.paths.length)
|
|
131
|
+
throw new ServiceError('DEPENDENCY_PATH_INVALID', 'Dependency path ordinal is outside the retained frontier');
|
|
132
|
+
const values = [];
|
|
133
|
+
let cursor = index;
|
|
134
|
+
while (cursor !== null) {
|
|
135
|
+
const path = this.state.paths[cursor];
|
|
136
|
+
values.push(this.input.edges[path.edge]);
|
|
137
|
+
cursor = path.parent;
|
|
138
|
+
}
|
|
139
|
+
return values.reverse();
|
|
140
|
+
}
|
|
141
|
+
addPath(edge, parent) {
|
|
142
|
+
this.state.paths.push({ edge, parent });
|
|
143
|
+
return this.state.paths.length - 1;
|
|
144
|
+
}
|
|
145
|
+
record(kind, path) {
|
|
146
|
+
this.state.records.push({ kind, path, root: this.state.root });
|
|
147
|
+
}
|
|
148
|
+
/** Advance at most this many elementary transitions, including wide adjacency lists. */
|
|
149
|
+
advance(maximumSteps, signal) {
|
|
150
|
+
if (!Number.isSafeInteger(maximumSteps) || maximumSteps < 1)
|
|
151
|
+
throw new ServiceError('DEPENDENCY_BATCH_INVALID', 'Dependency batch size must be positive');
|
|
152
|
+
const end = this.state.steps + maximumSteps;
|
|
153
|
+
while (!this.state.complete && this.state.steps < end) {
|
|
154
|
+
signal?.throwIfAborted();
|
|
155
|
+
if (this.state.steps >= this.input.maximumSteps)
|
|
156
|
+
throw new ServiceError('DEPENDENCY_WORK_LIMIT', 'Dependency expansion reached its explicit work ceiling', {
|
|
157
|
+
maximum: this.input.maximumSteps,
|
|
158
|
+
completedRoots: this.state.root,
|
|
159
|
+
totalRoots: this.input.roots.length,
|
|
160
|
+
});
|
|
161
|
+
this.state.steps++;
|
|
162
|
+
if (!this.state.started) {
|
|
163
|
+
const edge = this.edges.get(this.input.roots[this.state.root]);
|
|
164
|
+
const rootPath = this.addPath(edge, null);
|
|
165
|
+
this.state.stack.push(rootPath);
|
|
166
|
+
this.visited.clear();
|
|
167
|
+
if (this.input.visitPolicy === 'node')
|
|
168
|
+
this.visited.add(this.input.edges[edge].to);
|
|
169
|
+
this.state.started = true;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (this.state.current === null) {
|
|
173
|
+
const path = this.state.stack.pop();
|
|
174
|
+
if (path === undefined) {
|
|
175
|
+
this.state.root++;
|
|
176
|
+
this.state.started = false;
|
|
177
|
+
this.state.complete = this.state.root === this.input.roots.length;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (this.input.visitPolicy === 'path') {
|
|
181
|
+
const key = this.path(path)
|
|
182
|
+
.map(({ to }) => to)
|
|
183
|
+
.join('\0');
|
|
184
|
+
if (this.visited.has(key))
|
|
185
|
+
continue;
|
|
186
|
+
this.visited.add(key);
|
|
187
|
+
}
|
|
188
|
+
this.record('visit', path);
|
|
189
|
+
this.state.current = { path, edge: 0 };
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const current = this.state.current;
|
|
193
|
+
const node = this.input.edges[this.state.paths[current.path].edge].to;
|
|
194
|
+
const candidates = this.outgoing.get(node) ?? [];
|
|
195
|
+
const edgeIndex = candidates[current.edge++];
|
|
196
|
+
if (edgeIndex === undefined) {
|
|
197
|
+
this.state.current = null;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const edge = this.input.edges[edgeIndex];
|
|
201
|
+
if (!this.branches.has(edge.to)) {
|
|
202
|
+
this.record('terminal', this.addPath(edgeIndex, current.path));
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const ancestors = this.path(current.path).map(({ to }) => to);
|
|
206
|
+
if (ancestors.includes(edge.to)) {
|
|
207
|
+
this.record('cycle', this.addPath(edgeIndex, current.path));
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (ancestors.length >= this.input.maximumDepth) {
|
|
211
|
+
this.record('depth', this.addPath(edgeIndex, current.path));
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const visitKey = this.input.visitPolicy === 'node' ? edge.to : [...ancestors, edge.to].join('\0');
|
|
215
|
+
if (this.input.visitPolicy === 'node') {
|
|
216
|
+
if (this.visited.has(visitKey))
|
|
217
|
+
continue;
|
|
218
|
+
this.visited.add(visitKey);
|
|
219
|
+
}
|
|
220
|
+
this.state.stack.push(this.addPath(edgeIndex, current.path));
|
|
221
|
+
}
|
|
222
|
+
return this.state.complete;
|
|
223
|
+
}
|
|
224
|
+
checkpoint() {
|
|
225
|
+
this.state.visited = [...this.visited];
|
|
226
|
+
return this.state;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export function walkDependencies(input, signal) {
|
|
230
|
+
const walk = new DependencyWalk(input);
|
|
231
|
+
while (!walk.state.complete)
|
|
232
|
+
walk.advance(256, signal);
|
|
233
|
+
return walk;
|
|
234
|
+
}
|
|
235
|
+
export async function walkDependenciesAsync(input, signal, checkpointSteps = 2_048) {
|
|
236
|
+
signal?.throwIfAborted();
|
|
237
|
+
if (!Number.isSafeInteger(checkpointSteps) || checkpointSteps < 1)
|
|
238
|
+
throw new ServiceError('DEPENDENCY_BATCH_INVALID', 'Checkpoint interval must be positive');
|
|
239
|
+
const checkpoints = currentAnalysisCheckpoints();
|
|
240
|
+
const key = hashCanonical({ algorithm: 'dependency-walk.v1', ...input });
|
|
241
|
+
const restored = await checkpoints?.load(key, input.sourceRevision, stateSchema);
|
|
242
|
+
const walk = new DependencyWalk(input, restored);
|
|
243
|
+
let savedAt = walk.state.steps;
|
|
244
|
+
while (!walk.state.complete) {
|
|
245
|
+
const complete = walk.advance(256, signal);
|
|
246
|
+
if ((complete && savedAt > 0) || walk.state.steps - savedAt >= checkpointSteps) {
|
|
247
|
+
await checkpoints?.save(key, input.sourceRevision, walk.checkpoint(), walk.state.root, input.roots.length);
|
|
248
|
+
savedAt = walk.state.steps;
|
|
249
|
+
}
|
|
250
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
251
|
+
signal?.throwIfAborted();
|
|
252
|
+
}
|
|
253
|
+
return walk;
|
|
254
|
+
}
|
|
255
|
+
export function runDependencyAnalysis(analysis, signal) {
|
|
256
|
+
let next = analysis.next();
|
|
257
|
+
while (!next.done)
|
|
258
|
+
next = analysis.next(walkDependencies(next.value, signal));
|
|
259
|
+
return next.value;
|
|
260
|
+
}
|
|
261
|
+
export async function runDependencyAnalysisAsync(analysis, signal) {
|
|
262
|
+
let next = analysis.next();
|
|
263
|
+
while (!next.done)
|
|
264
|
+
next = analysis.next(await walkDependenciesAsync(next.value, signal));
|
|
265
|
+
return next.value;
|
|
266
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { boundedSourceHashEvidence, publicArtifactLink } from './artifacts.js';
|
|
2
|
+
import { canonicalJson } from './canonical.js';
|
|
3
|
+
import { exactChangedFilePatterns, focusDomainScanPatterns } from './domain-scan-patterns.js';
|
|
4
|
+
import { parseClausewitz } from './source/index.js';
|
|
5
|
+
import { TRANSACTION_MAX_DIAGNOSTICS } from './transaction-limits.js';
|
|
6
|
+
import { importContinuousFocusPalettes, importFocusTrees, layoutFocusTreeAsync, lintContinuousFocusPalette, lintFocusTree, } from '../focus/index.js';
|
|
7
|
+
import { ScriptedGuiStudio } from '../gui/index.js';
|
|
8
|
+
import { AgentNudger, attributeMapValidationDiagnostics } from '../map/index.js';
|
|
9
|
+
import { PACKAGE_VERSION } from '../version.js';
|
|
10
|
+
function sourcePathComparisonKey(value) {
|
|
11
|
+
const normalized = value.replaceAll('\\', '/');
|
|
12
|
+
return process.platform === 'win32' ? normalized.toLocaleLowerCase('en-US') : normalized;
|
|
13
|
+
}
|
|
14
|
+
function focusReferences(engineSnapshot) {
|
|
15
|
+
const active = (kind) => engineSnapshot.index
|
|
16
|
+
.findAll(kind)
|
|
17
|
+
.filter(({ overridden }) => !overridden)
|
|
18
|
+
.map(({ id }) => id);
|
|
19
|
+
return {
|
|
20
|
+
decision: active('decision'),
|
|
21
|
+
decision_category: active('decision_category'),
|
|
22
|
+
event: active('event'),
|
|
23
|
+
idea: active('idea'),
|
|
24
|
+
leader: active('leader'),
|
|
25
|
+
formable: active('formable'),
|
|
26
|
+
helper: active('scripted_effect'),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export async function postValidateTransaction(engine, transaction, principal, signal) {
|
|
30
|
+
engine.invalidate(transaction.workspaceId);
|
|
31
|
+
const workspace = engine.resolver.get(transaction.workspaceId, principal);
|
|
32
|
+
const focusTransaction = transaction.operationKind === 'focus-plan-changes' ||
|
|
33
|
+
transaction.operationKind === 'continuous-focus-plan-changes';
|
|
34
|
+
const snapshot = await engine.scan(transaction.workspaceId, {
|
|
35
|
+
patterns: focusTransaction
|
|
36
|
+
? focusDomainScanPatterns(workspace)
|
|
37
|
+
: exactChangedFilePatterns(transaction.files.map(({ relativePath }) => relativePath)),
|
|
38
|
+
}, principal, signal);
|
|
39
|
+
const changed = new Set(transaction.files.map(({ relativePath }) => sourcePathComparisonKey(relativePath)));
|
|
40
|
+
const diagnostics = snapshot.diagnostics.filter(({ location }) => {
|
|
41
|
+
if (location === undefined)
|
|
42
|
+
return false;
|
|
43
|
+
const sourcePath = sourcePathComparisonKey(location.path);
|
|
44
|
+
return [...changed].some((relativePath) => sourcePath.endsWith(`:${relativePath}`));
|
|
45
|
+
});
|
|
46
|
+
const checks = [];
|
|
47
|
+
if (transaction.operationKind === 'focus-plan-changes') {
|
|
48
|
+
const catalog = focusReferences(snapshot);
|
|
49
|
+
for (const file of snapshot.files.filter(({ shadowedBy, relativePath }) => shadowedBy === undefined &&
|
|
50
|
+
relativePath.toLowerCase().endsWith('.txt') &&
|
|
51
|
+
changed.has(sourcePathComparisonKey(relativePath)))) {
|
|
52
|
+
const document = parseClausewitz(file.bytes, file.displayPath);
|
|
53
|
+
const imported = importFocusTrees(document, { references: catalog });
|
|
54
|
+
diagnostics.push(...imported.diagnostics);
|
|
55
|
+
for (const plan of imported.plans) {
|
|
56
|
+
const layout = await layoutFocusTreeAsync(plan, signal === undefined ? {} : { signal });
|
|
57
|
+
diagnostics.push(...lintFocusTree(plan, {
|
|
58
|
+
index: snapshot.index,
|
|
59
|
+
references: catalog,
|
|
60
|
+
layout,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const passed = !diagnostics.some(({ severity }) => severity === 'error' || severity === 'blocker');
|
|
65
|
+
checks.push({
|
|
66
|
+
id: 'post-write-focus',
|
|
67
|
+
passed,
|
|
68
|
+
message: passed
|
|
69
|
+
? 'The focus result re-imported, laid out, and linted successfully'
|
|
70
|
+
: 'The focus result failed post-write import or lint',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else if (transaction.operationKind === 'continuous-focus-plan-changes') {
|
|
74
|
+
for (const file of snapshot.files.filter(({ shadowedBy, relativePath }) => shadowedBy === undefined &&
|
|
75
|
+
relativePath.toLowerCase().endsWith('.txt') &&
|
|
76
|
+
changed.has(sourcePathComparisonKey(relativePath)))) {
|
|
77
|
+
const imported = importContinuousFocusPalettes(parseClausewitz(file.bytes, file.displayPath));
|
|
78
|
+
diagnostics.push(...imported.diagnostics);
|
|
79
|
+
for (const plan of imported.continuousFocusPalettes) {
|
|
80
|
+
diagnostics.push(...lintContinuousFocusPalette(plan));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const passed = !diagnostics.some(({ severity }) => severity === 'error' || severity === 'blocker');
|
|
84
|
+
checks.push({
|
|
85
|
+
id: 'post-write-continuous-focus',
|
|
86
|
+
passed,
|
|
87
|
+
message: passed
|
|
88
|
+
? 'The continuous focus result re-imported and linted successfully'
|
|
89
|
+
: 'The continuous focus result failed post-write import or lint',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else if (transaction.operationKind === 'gui-source-change' ||
|
|
93
|
+
transaction.operationKind === 'gui-helper-compilation') {
|
|
94
|
+
const studio = new ScriptedGuiStudio(engine);
|
|
95
|
+
const scanned = await studio.scan(transaction.workspaceId, principal, signal);
|
|
96
|
+
diagnostics.push(...scanned.graph.diagnostics);
|
|
97
|
+
const passed = !scanned.graph.diagnostics.some(({ severity }) => severity === 'error' || severity === 'blocker');
|
|
98
|
+
checks.push({
|
|
99
|
+
id: 'post-write-gui-graph',
|
|
100
|
+
passed,
|
|
101
|
+
message: passed
|
|
102
|
+
? 'The GUI graph rebuilt successfully'
|
|
103
|
+
: 'The GUI graph has blocking diagnostics',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (transaction.operationKind === 'agent-nudger-map-changes') {
|
|
107
|
+
const nudger = new AgentNudger(engine);
|
|
108
|
+
const validated = await nudger.validate(transaction.workspaceId, principal, signal);
|
|
109
|
+
const mapDiagnostics = attributeMapValidationDiagnostics({
|
|
110
|
+
diagnostics: [...diagnostics, ...validated.validation.diagnostics],
|
|
111
|
+
operations: transaction.operations,
|
|
112
|
+
changes: transaction.files,
|
|
113
|
+
});
|
|
114
|
+
diagnostics.length = 0;
|
|
115
|
+
diagnostics.push(...mapDiagnostics);
|
|
116
|
+
checks.push(...validated.validation.checks);
|
|
117
|
+
checks.push({
|
|
118
|
+
id: 'post-write-map',
|
|
119
|
+
passed: validated.validation.passed,
|
|
120
|
+
message: validated.validation.passed
|
|
121
|
+
? 'The map result rescanned and validated successfully'
|
|
122
|
+
: 'The map result failed post-write validation',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const passed = !diagnostics.some(({ severity }) => severity === 'error' || severity === 'blocker');
|
|
126
|
+
checks.unshift({
|
|
127
|
+
id: 'post-write-shared-index',
|
|
128
|
+
passed,
|
|
129
|
+
message: passed
|
|
130
|
+
? 'Changed files reparsed and reindexed successfully'
|
|
131
|
+
: 'Changed files have blocking post-write diagnostics',
|
|
132
|
+
});
|
|
133
|
+
const availableDiagnostics = Math.max(0, TRANSACTION_MAX_DIAGNOSTICS - transaction.diagnostics.length);
|
|
134
|
+
if (diagnostics.length > availableDiagnostics) {
|
|
135
|
+
const workspace = engine.resolver.get(transaction.workspaceId, principal);
|
|
136
|
+
const sourceEvidence = boundedSourceHashEvidence(Object.fromEntries(transaction.files.flatMap(({ relativePath, afterSha256 }) => afterSha256 === null ? [] : [[relativePath, afterSha256]])));
|
|
137
|
+
const artifact = await engine.artifacts.putChunked(workspace, `${transaction.operationKind}.post-validation.json`, 'application/json', `${canonicalJson({
|
|
138
|
+
schemaVersion: 1,
|
|
139
|
+
operationKind: transaction.operationKind,
|
|
140
|
+
diagnosticCount: diagnostics.length,
|
|
141
|
+
checks,
|
|
142
|
+
diagnostics,
|
|
143
|
+
})}\n`, {
|
|
144
|
+
kind: 'rewrite-post-validation',
|
|
145
|
+
toolVersion: PACKAGE_VERSION,
|
|
146
|
+
schemaVersion: 'rewrite-post-validation.v1',
|
|
147
|
+
sourceHashes: sourceEvidence.sourceHashes,
|
|
148
|
+
metadata: { sourceHashInventory: sourceEvidence.inventory },
|
|
149
|
+
}, 'Complete diagnostics from post-write validation', signal);
|
|
150
|
+
const retainedLimit = Math.max(0, availableDiagnostics - 1);
|
|
151
|
+
const hard = diagnostics.filter(({ severity }) => severity === 'error' || severity === 'blocker');
|
|
152
|
+
const retainedHard = hard.slice(0, retainedLimit);
|
|
153
|
+
const retainedSoft = diagnostics
|
|
154
|
+
.filter(({ severity }) => severity !== 'error' && severity !== 'blocker')
|
|
155
|
+
.slice(0, retainedLimit - retainedHard.length);
|
|
156
|
+
const omittedHard = hard.length - retainedHard.length;
|
|
157
|
+
const artifactLink = publicArtifactLink(artifact);
|
|
158
|
+
checks.push({
|
|
159
|
+
id: 'post-write-diagnostics-artifact',
|
|
160
|
+
passed: omittedHard === 0,
|
|
161
|
+
message: `Complete post-write diagnostics are stored at ${artifactLink.uri}`,
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
diagnostics: availableDiagnostics === 0
|
|
165
|
+
? []
|
|
166
|
+
: [
|
|
167
|
+
...retainedHard,
|
|
168
|
+
...retainedSoft,
|
|
169
|
+
{
|
|
170
|
+
code: 'POST_VALIDATION_DIAGNOSTICS_IN_ARTIFACT',
|
|
171
|
+
severity: omittedHard > 0 ? 'blocker' : 'warning',
|
|
172
|
+
category: 'validation',
|
|
173
|
+
message: `Complete post-write validation contains ${diagnostics.length} diagnostics and is stored as an artifact`,
|
|
174
|
+
details: {
|
|
175
|
+
total: diagnostics.length,
|
|
176
|
+
returned: availableDiagnostics,
|
|
177
|
+
omitted: diagnostics.length - retainedLimit,
|
|
178
|
+
omittedHard,
|
|
179
|
+
artifact: artifactLink,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
checks,
|
|
184
|
+
artifacts: [artifactLink],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return { diagnostics, checks, artifacts: [] };
|
|
188
|
+
}
|