@webpieces/nx-webpieces-rules 0.4.912 → 0.4.914
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/README.md +11 -0
- package/executors.json +5 -0
- package/package.json +7 -7
- package/src/executors/help/executor.js +2 -0
- package/src/executors/help/executor.js.map +1 -1
- package/src/executors/visualize-runtime/executor.d.ts +3 -1
- package/src/executors/visualize-runtime/executor.js +9 -3
- package/src/executors/visualize-runtime/executor.js.map +1 -1
- package/src/executors/visualize-runtime-goal/executor.d.ts +27 -0
- package/src/executors/visualize-runtime-goal/executor.js +98 -0
- package/src/executors/visualize-runtime-goal/executor.js.map +1 -0
- package/src/executors/visualize-runtime-goal/schema.json +13 -0
- package/src/lib/graph-color-modes.d.ts +8 -1
- package/src/lib/graph-color-modes.js +11 -1
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-filter.client.d.ts +9 -2
- package/src/lib/graph-filter.client.js +15 -0
- package/src/lib/graph-filter.client.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +9 -2
- package/src/lib/graph-mode-state.client.js +17 -4
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-page-shell.js +6 -5
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-render-model.d.ts +41 -0
- package/src/lib/graph-render-model.js +45 -1
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +7 -1
- package/src/lib/graph-visualizer.client.js +37 -4
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/runtime-goal-comparison.d.ts +55 -0
- package/src/lib/runtime-goal-comparison.js +209 -0
- package/src/lib/runtime-goal-comparison.js.map +1 -0
- package/src/lib/runtime-goal-details.d.ts +17 -0
- package/src/lib/runtime-goal-details.js +118 -0
- package/src/lib/runtime-goal-details.js.map +1 -0
- package/src/lib/runtime-goal-file.d.ts +72 -0
- package/src/lib/runtime-goal-file.js +201 -0
- package/src/lib/runtime-goal-file.js.map +1 -0
- package/src/lib/runtime-goal-styles.d.ts +10 -0
- package/src/lib/runtime-goal-styles.js +25 -0
- package/src/lib/runtime-goal-styles.js.map +1 -0
- package/src/lib/runtime-goal-validator.d.ts +39 -0
- package/src/lib/runtime-goal-validator.js +236 -0
- package/src/lib/runtime-goal-validator.js.map +1 -0
- package/src/lib/runtime-goal-view.d.ts +22 -0
- package/src/lib/runtime-goal-view.js +64 -0
- package/src/lib/runtime-goal-view.js.map +1 -0
- package/src/lib/runtime-graph-diff.d.ts +107 -0
- package/src/lib/runtime-graph-diff.js +346 -0
- package/src/lib/runtime-graph-diff.js.map +1 -0
- package/src/lib/runtime-html-page.d.ts +2 -0
- package/src/lib/runtime-html-page.js +28 -3
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-layout.d.ts +2 -0
- package/src/lib/runtime-layout.js +4 -0
- package/src/lib/runtime-layout.js.map +1 -1
- package/src/lib/runtime-union-graph.d.ts +20 -0
- package/src/lib/runtime-union-graph.js +94 -0
- package/src/lib/runtime-union-graph.js.map +1 -0
- package/src/lib/runtime-visualizer.client.js +7 -3
- package/src/lib/runtime-visualizer.client.js.map +1 -1
- package/src/lib/runtime-visualizer.d.ts +8 -3
- package/src/lib/runtime-visualizer.js +18 -10
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-options.d.ts +28 -1
- package/src/lib/runtime-viz-options.js +36 -2
- package/src/lib/runtime-viz-options.js.map +1 -1
- package/src/plugin.js +2 -2
- package/src/plugin.js.map +1 -1
- package/src/runtime-targets.d.ts +6 -0
- package/src/runtime-targets.js +17 -0
- package/src/runtime-targets.js.map +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Goal vs Current (#1239): turns the semantic diff of the two saved runtime files into the overlay the
|
|
4
|
+
* runtime viewer's "Goal vs Current" color mode draws.
|
|
5
|
+
*
|
|
6
|
+
* The overlay is rendered by the SAME shared runtime renderer as both pages (generateRuntimeRenderModel
|
|
7
|
+
* over RuntimeUnionGraph), so queue boxes, self-queue sub-rows, level bands and non-constraining
|
|
8
|
+
* feedback edges behave exactly as on the normal pages. Only the statements are restyled afterwards:
|
|
9
|
+
*
|
|
10
|
+
* added in goal green
|
|
11
|
+
* removed from goal red, dashed box / dotted arrow — still drawn, never dropped
|
|
12
|
+
* changed amber; a moved level reads `L4 → L2`
|
|
13
|
+
* unchanged subdued grey, kept for context
|
|
14
|
+
*
|
|
15
|
+
* Every drawn box and arrow maps back to the diff's FACT identities (never to display names or edge
|
|
16
|
+
* ids), and its before/after lines travel in the page for the details panel.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.RuntimeGoalComparison = exports.RuntimeComparisonStyler = exports.RuntimeComparisonIds = void 0;
|
|
20
|
+
const dot_syntax_1 = require("./dot-syntax");
|
|
21
|
+
const graph_render_model_1 = require("./graph-render-model");
|
|
22
|
+
const runtime_graph_diff_1 = require("./runtime-graph-diff");
|
|
23
|
+
const runtime_union_graph_1 = require("./runtime-union-graph");
|
|
24
|
+
const runtime_viz_options_1 = require("./runtime-viz-options");
|
|
25
|
+
const runtime_visualizer_1 = require("./runtime-visualizer");
|
|
26
|
+
const runtime_layout_1 = require("./runtime-layout");
|
|
27
|
+
const remote_service_node_id_1 = require("./remote-service-node-id");
|
|
28
|
+
const runtime_external_triggers_1 = require("./runtime-external-triggers");
|
|
29
|
+
const runtime_goal_styles_1 = require("./runtime-goal-styles");
|
|
30
|
+
/** Render-node / render-edge → the diff fact identities it draws. */
|
|
31
|
+
class RuntimeComparisonIds {
|
|
32
|
+
graph;
|
|
33
|
+
byNode = new Map();
|
|
34
|
+
triggerService = new Map();
|
|
35
|
+
constructor(graph) {
|
|
36
|
+
this.graph = graph;
|
|
37
|
+
const safe = (raw) => raw.replace(/[^A-Za-z0-9_]/g, '_');
|
|
38
|
+
for (const name of Object.keys(graph.services))
|
|
39
|
+
this.put(name, `service:${name}`);
|
|
40
|
+
for (const id of Object.keys(graph.externalSystems ?? {}))
|
|
41
|
+
this.put(`system__${safe(id)}`, `external-system:${id}`);
|
|
42
|
+
for (const name of Object.keys(graph.remoteServices ?? {}))
|
|
43
|
+
this.put(new remote_service_node_id_1.RemoteServiceNodeId().of(name), `remote-service:${name}`);
|
|
44
|
+
for (const use of graph.unresolvedUses) {
|
|
45
|
+
const owner = graph.apis[use.api]?.owner ?? use.api;
|
|
46
|
+
if (graph.apis[use.api]?.externalSystem === undefined)
|
|
47
|
+
this.put(`external__${owner}`, `external-library:${owner}`);
|
|
48
|
+
}
|
|
49
|
+
for (const trigger of graph.triggers) {
|
|
50
|
+
const id = `trigger:${trigger.kind}:${trigger.api}.${trigger.method}@${trigger.service}`;
|
|
51
|
+
this.triggerService.set(id, trigger.service);
|
|
52
|
+
if (trigger.kind === 'cron')
|
|
53
|
+
this.put(`cron__${safe(`${trigger.api}_${trigger.method}`)}`, id);
|
|
54
|
+
}
|
|
55
|
+
for (const group of new runtime_external_triggers_1.RuntimeExternalTriggers(graph.triggers).groups) {
|
|
56
|
+
for (const trigger of group.triggers)
|
|
57
|
+
this.put(group.from, `trigger:external:${trigger.api}.${trigger.method}@${trigger.service}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
node(node) {
|
|
61
|
+
if (node.queueKeys.length > 0)
|
|
62
|
+
return this.queues(node);
|
|
63
|
+
return this.byNode.get(node.id) ?? [];
|
|
64
|
+
}
|
|
65
|
+
edge(edge, nodes) {
|
|
66
|
+
const from = nodes.get(edge.from);
|
|
67
|
+
const to = nodes.get(edge.to);
|
|
68
|
+
if (from !== undefined && from.queueKeys.length > 0)
|
|
69
|
+
return this.queues(from).map((queue) => `deliver:${queue}->${edge.to}`);
|
|
70
|
+
if (to !== undefined && to.queueKeys.length > 0)
|
|
71
|
+
return this.queues(to).map((queue) => `enqueue:${edge.from}->${queue}`);
|
|
72
|
+
if (this.graph.services[edge.from] === undefined)
|
|
73
|
+
return (this.byNode.get(edge.from) ?? []).filter((id) => this.triggerService.get(id) === edge.to);
|
|
74
|
+
if (this.graph.services[edge.to] !== undefined)
|
|
75
|
+
return [`rpc:${edge.from}->${edge.to}`];
|
|
76
|
+
return (this.byNode.get(edge.to) ?? []).flatMap((target) => this.towards(edge, target));
|
|
77
|
+
}
|
|
78
|
+
/** A service's arrow into a context node: an external system, a remote service or a vendor library. */
|
|
79
|
+
towards(edge, target) {
|
|
80
|
+
if (target.startsWith('external-system:'))
|
|
81
|
+
return [`external-use:${edge.from}->${target}`];
|
|
82
|
+
if (target.startsWith('remote-service:'))
|
|
83
|
+
return [`remote-call:${edge.from}->${target}#${edge.dot.includes('enqueue') ? 'pubsub' : 'rpc'}`];
|
|
84
|
+
const owner = target.slice('external-library:'.length);
|
|
85
|
+
return this.graph.unresolvedUses
|
|
86
|
+
.filter((use) => use.service === edge.from && (this.graph.apis[use.api]?.owner ?? use.api) === owner)
|
|
87
|
+
.map((use) => `unresolved-use:${use.service}->${use.api}`);
|
|
88
|
+
}
|
|
89
|
+
queues(node) {
|
|
90
|
+
return node.queueKeys.map((key) => (key.startsWith('queue:') ? `contract-queue:${key.slice(6)}` : `queue:${key}`));
|
|
91
|
+
}
|
|
92
|
+
put(renderId, factId) {
|
|
93
|
+
if (!this.byNode.has(renderId))
|
|
94
|
+
this.byNode.set(renderId, []);
|
|
95
|
+
if (!this.byNode.get(renderId).includes(factId))
|
|
96
|
+
this.byNode.get(renderId).push(factId);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.RuntimeComparisonIds = RuntimeComparisonIds;
|
|
100
|
+
/** Restyles one rendered statement for its diff status. */
|
|
101
|
+
class RuntimeComparisonStyler {
|
|
102
|
+
status(changes) {
|
|
103
|
+
const statuses = new Set(changes.map((change) => change.status));
|
|
104
|
+
if (statuses.size === 0)
|
|
105
|
+
return runtime_graph_diff_1.RuntimeDiffStatus.UNCHANGED;
|
|
106
|
+
if (statuses.size === 1)
|
|
107
|
+
return [...statuses][0];
|
|
108
|
+
return runtime_graph_diff_1.RuntimeDiffStatus.CHANGED;
|
|
109
|
+
}
|
|
110
|
+
/** A service box: rebuilt (its HTML label carries the level badge and the status in words). */
|
|
111
|
+
service(node, status, changes) {
|
|
112
|
+
const style = runtime_goal_styles_1.DIFF_STYLES[status];
|
|
113
|
+
const change = changes[0];
|
|
114
|
+
const level = this.levels(change);
|
|
115
|
+
const changed = status === runtime_graph_diff_1.RuntimeDiffStatus.CHANGED && change !== undefined ? `: ${change.changedFields.join(', ')}` : '';
|
|
116
|
+
const label = `<FONT COLOR="${style.text}99">${(0, dot_syntax_1.htmlLabelText)(level)}</FONT>  <B>${(0, dot_syntax_1.htmlLabelText)(node.id.split('/').pop() ?? node.id)}</B>` +
|
|
117
|
+
`<BR/>${(0, dot_syntax_1.htmlLabelText)(node.tags?.role ?? 'unknown')}<BR/>${(0, dot_syntax_1.htmlLabelText)(`${style.word}${changed}`)}`;
|
|
118
|
+
const boxStyle = status === runtime_graph_diff_1.RuntimeDiffStatus.REMOVED ? 'filled,rounded,dashed' : 'filled,rounded';
|
|
119
|
+
return (` "${(0, dot_syntax_1.dotValue)(node.id)}" [style="${boxStyle}", fillcolor="${style.fill}", color="${style.border}", ` +
|
|
120
|
+
`penwidth=2, fontcolor="${style.text}", label=<${label}>];\n`);
|
|
121
|
+
}
|
|
122
|
+
/** A context box (queue, datastore, clock, vendor): kind shape kept, status colors laid over it. */
|
|
123
|
+
context(node, status, changes) {
|
|
124
|
+
const style = runtime_goal_styles_1.DIFF_STYLES[status];
|
|
125
|
+
let dot = node.dot;
|
|
126
|
+
const moved = changes.find((change) => change.changedFields.includes('level'));
|
|
127
|
+
if (node.queueKeys.length > 0 && moved !== undefined)
|
|
128
|
+
dot = dot.replace(/(label=" \|)L\d+ {2}/, `$1${this.levels(moved).replace(/>/g, '\\>')} `);
|
|
129
|
+
const base = /style="([^"]*)"/.exec(dot)?.[1] ?? 'filled';
|
|
130
|
+
const boxStyle = status === runtime_graph_diff_1.RuntimeDiffStatus.REMOVED && !base.includes('dashed') ? `${base},dashed` : base;
|
|
131
|
+
return `${dot.slice(0, -2)} [style="${boxStyle}", fillcolor="${style.fill}", color="${style.border}", penwidth=2, fontcolor="${style.text}"];\n`;
|
|
132
|
+
}
|
|
133
|
+
/** An arrow: its line style says rpc/event as always; color (and dots for a removal) says the diff. */
|
|
134
|
+
edge(dot, status) {
|
|
135
|
+
const style = runtime_goal_styles_1.DIFF_STYLES[status];
|
|
136
|
+
const removed = status === runtime_graph_diff_1.RuntimeDiffStatus.REMOVED ? ', style=dotted' : '';
|
|
137
|
+
const width = status === runtime_graph_diff_1.RuntimeDiffStatus.UNCHANGED ? 1 : 2.2;
|
|
138
|
+
return `${dot.slice(0, -2)} [color="${style.border}", fontcolor="${style.border}", penwidth=${width}${removed}];\n`;
|
|
139
|
+
}
|
|
140
|
+
/** `L4`, or `L4 → L2` when the level moved. */
|
|
141
|
+
levels(change) {
|
|
142
|
+
const before = change?.before?.['level'];
|
|
143
|
+
const after = change?.after?.['level'];
|
|
144
|
+
if (before !== undefined && after !== undefined && before !== after)
|
|
145
|
+
return `${before} → ${after}`;
|
|
146
|
+
return after ?? before ?? '';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
exports.RuntimeComparisonStyler = RuntimeComparisonStyler;
|
|
150
|
+
/** Builds the Goal vs Current view both runtime pages carry. */
|
|
151
|
+
class RuntimeGoalComparison {
|
|
152
|
+
styler = new RuntimeComparisonStyler();
|
|
153
|
+
build(current, goal, options) {
|
|
154
|
+
const diff = new runtime_graph_diff_1.RuntimeGraphDiff().compare(current, goal);
|
|
155
|
+
const union = new runtime_union_graph_1.RuntimeUnionGraph().build(current, goal);
|
|
156
|
+
const tags = {};
|
|
157
|
+
for (const key of Object.keys(union.queues))
|
|
158
|
+
tags[key] = diff.get(`queue:${key}`)?.status ?? '';
|
|
159
|
+
const model = (0, runtime_visualizer_1.generateRuntimeRenderModel)(union, 'Goal vs Current', options.withPage(runtime_viz_options_1.RuntimePageKind.COMPARISON, tags));
|
|
160
|
+
const nodeDetails = {};
|
|
161
|
+
const edgeDetails = {};
|
|
162
|
+
const ids = new RuntimeComparisonIds(union);
|
|
163
|
+
this.restyleNodes(model, ids, diff, nodeDetails);
|
|
164
|
+
this.restyleEdges(model, ids, diff, edgeDetails);
|
|
165
|
+
model.fullDot = new runtime_layout_1.RuntimeLayout().assemble(model);
|
|
166
|
+
(0, dot_syntax_1.assertValidDot)(model.fullDot, 'runtime-architecture goal comparison');
|
|
167
|
+
return new graph_render_model_1.RuntimeComparisonView(true, '', 'Current → Goal', this.summary(diff), model, nodeDetails, edgeDetails, diff.differences().map((change) => this.line(change)));
|
|
168
|
+
}
|
|
169
|
+
restyleNodes(model, ids, diff, details) {
|
|
170
|
+
for (let index = 0; index < model.nodes.length; index++) {
|
|
171
|
+
const node = model.nodes[index];
|
|
172
|
+
const changes = this.changes(ids.node(node), diff);
|
|
173
|
+
const status = this.styler.status(changes);
|
|
174
|
+
const dot = node.tags !== null ? this.styler.service(node, status, changes) : this.styler.context(node, status, changes);
|
|
175
|
+
model.nodes[index] = new graph_render_model_1.RenderNode(node.id, dot, null, node.tags, node.products, node.queueKeys);
|
|
176
|
+
details[node.id] = changes.flatMap((change) => change.lines());
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
restyleEdges(model, ids, diff, details) {
|
|
180
|
+
const nodes = new Map();
|
|
181
|
+
for (const node of model.nodes)
|
|
182
|
+
nodes.set(node.id, node);
|
|
183
|
+
for (let index = 0; index < model.edges.length; index++) {
|
|
184
|
+
const edge = model.edges[index];
|
|
185
|
+
const changes = this.changes(ids.edge(edge, nodes), diff);
|
|
186
|
+
const status = this.styler.status(changes);
|
|
187
|
+
model.edges[index] = new graph_render_model_1.RenderEdge(edge.from, edge.to, this.styler.edge(edge.dot, status), edge.id, null);
|
|
188
|
+
details[edge.id] = [`${edge.from} → ${edge.to}`, ...changes.flatMap((change) => change.lines())];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
changes(factIds, diff) {
|
|
192
|
+
return factIds
|
|
193
|
+
.map((id) => diff.get(id))
|
|
194
|
+
.filter((change) => change !== undefined);
|
|
195
|
+
}
|
|
196
|
+
summary(diff) {
|
|
197
|
+
if (diff.identical())
|
|
198
|
+
return 'No differences: the goal matches the current runtime graph.';
|
|
199
|
+
const counts = diff.counts();
|
|
200
|
+
const levels = diff.maxLevelBefore === diff.maxLevelAfter ? '' : ` · max level L${diff.maxLevelBefore} → L${diff.maxLevelAfter}`;
|
|
201
|
+
return `${counts.added} added · ${counts.removed} removed · ${counts.changed} changed · ${counts.unchanged} unchanged${levels}`;
|
|
202
|
+
}
|
|
203
|
+
line(change) {
|
|
204
|
+
const fields = change.status === runtime_graph_diff_1.RuntimeDiffStatus.CHANGED ? ` (${change.changedFields.join(', ')})` : '';
|
|
205
|
+
return `${change.status}: ${change.label}${fields}`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
exports.RuntimeGoalComparison = RuntimeGoalComparison;
|
|
209
|
+
//# sourceMappingURL=runtime-goal-comparison.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-goal-comparison.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-goal-comparison.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Goal vs Current (#1239): turns the semantic diff of the two saved runtime files into the overlay the\n * runtime viewer's \"Goal vs Current\" color mode draws.\n *\n * The overlay is rendered by the SAME shared runtime renderer as both pages (generateRuntimeRenderModel\n * over RuntimeUnionGraph), so queue boxes, self-queue sub-rows, level bands and non-constraining\n * feedback edges behave exactly as on the normal pages. Only the statements are restyled afterwards:\n *\n * added in goal green\n * removed from goal red, dashed box / dotted arrow — still drawn, never dropped\n * changed amber; a moved level reads `L4 → L2`\n * unchanged subdued grey, kept for context\n *\n * Every drawn box and arrow maps back to the diff's FACT identities (never to display names or edge\n * ids), and its before/after lines travel in the page for the details panel.\n */\n\nimport { dotValue, htmlLabelText, assertValidDot } from './dot-syntax';\nimport { GraphRenderModel, RenderEdge, RenderNode, RuntimeComparisonView } from './graph-render-model';\nimport type { RuntimeGraph, RuntimeUnresolved } from './runtime-graph-model';\nimport { RuntimeGraphDiff, RuntimeDiffStatus, RuntimeFactChange, RuntimeGraphDiffResult } from './runtime-graph-diff';\nimport { RuntimeUnionGraph } from './runtime-union-graph';\nimport { RuntimeVizOptions, RuntimePageKind } from './runtime-viz-options';\nimport { generateRuntimeRenderModel } from './runtime-visualizer';\nimport { RuntimeLayout } from './runtime-layout';\nimport { RemoteServiceNodeId } from './remote-service-node-id';\nimport { RuntimeExternalTriggers } from './runtime-external-triggers';\nimport { DIFF_STYLES } from './runtime-goal-styles';\n\n/** Render-node / render-edge → the diff fact identities it draws. */\nexport class RuntimeComparisonIds {\n private readonly byNode = new Map<string, string[]>();\n private readonly triggerService = new Map<string, string>();\n\n constructor(private readonly graph: RuntimeGraph) {\n const safe = (raw: string): string => raw.replace(/[^A-Za-z0-9_]/g, '_');\n for (const name of Object.keys(graph.services)) this.put(name, `service:${name}`);\n for (const id of Object.keys(graph.externalSystems ?? {})) this.put(`system__${safe(id)}`, `external-system:${id}`);\n for (const name of Object.keys(graph.remoteServices ?? {})) this.put(new RemoteServiceNodeId().of(name), `remote-service:${name}`);\n for (const use of graph.unresolvedUses) {\n const owner = graph.apis[use.api]?.owner ?? use.api;\n if (graph.apis[use.api]?.externalSystem === undefined) this.put(`external__${owner}`, `external-library:${owner}`);\n }\n for (const trigger of graph.triggers) {\n const id = `trigger:${trigger.kind}:${trigger.api}.${trigger.method}@${trigger.service}`;\n this.triggerService.set(id, trigger.service);\n if (trigger.kind === 'cron') this.put(`cron__${safe(`${trigger.api}_${trigger.method}`)}`, id);\n }\n for (const group of new RuntimeExternalTriggers(graph.triggers).groups) {\n for (const trigger of group.triggers) this.put(group.from, `trigger:external:${trigger.api}.${trigger.method}@${trigger.service}`);\n }\n }\n\n node(node: RenderNode): string[] {\n if (node.queueKeys.length > 0) return this.queues(node);\n return this.byNode.get(node.id) ?? [];\n }\n\n edge(edge: RenderEdge, nodes: Map<string, RenderNode>): string[] {\n const from = nodes.get(edge.from);\n const to = nodes.get(edge.to);\n if (from !== undefined && from.queueKeys.length > 0) return this.queues(from).map((queue: string) => `deliver:${queue}->${edge.to}`);\n if (to !== undefined && to.queueKeys.length > 0) return this.queues(to).map((queue: string) => `enqueue:${edge.from}->${queue}`);\n if (this.graph.services[edge.from] === undefined)\n return (this.byNode.get(edge.from) ?? []).filter((id: string) => this.triggerService.get(id) === edge.to);\n if (this.graph.services[edge.to] !== undefined) return [`rpc:${edge.from}->${edge.to}`];\n return (this.byNode.get(edge.to) ?? []).flatMap((target: string) => this.towards(edge, target));\n }\n\n /** A service's arrow into a context node: an external system, a remote service or a vendor library. */\n private towards(edge: RenderEdge, target: string): string[] {\n if (target.startsWith('external-system:')) return [`external-use:${edge.from}->${target}`];\n if (target.startsWith('remote-service:'))\n return [`remote-call:${edge.from}->${target}#${edge.dot.includes('enqueue') ? 'pubsub' : 'rpc'}`];\n const owner = target.slice('external-library:'.length);\n return this.graph.unresolvedUses\n .filter((use: RuntimeUnresolved) => use.service === edge.from && (this.graph.apis[use.api]?.owner ?? use.api) === owner)\n .map((use: RuntimeUnresolved) => `unresolved-use:${use.service}->${use.api}`);\n }\n\n private queues(node: RenderNode): string[] {\n return node.queueKeys.map((key: string) => (key.startsWith('queue:') ? `contract-queue:${key.slice(6)}` : `queue:${key}`));\n }\n\n private put(renderId: string, factId: string): void {\n if (!this.byNode.has(renderId)) this.byNode.set(renderId, []);\n if (!this.byNode.get(renderId)!.includes(factId)) this.byNode.get(renderId)!.push(factId);\n }\n}\n\n/** Restyles one rendered statement for its diff status. */\nexport class RuntimeComparisonStyler {\n status(changes: RuntimeFactChange[]): RuntimeDiffStatus {\n const statuses = new Set(changes.map((change: RuntimeFactChange) => change.status));\n if (statuses.size === 0) return RuntimeDiffStatus.UNCHANGED;\n if (statuses.size === 1) return [...statuses][0];\n return RuntimeDiffStatus.CHANGED;\n }\n\n /** A service box: rebuilt (its HTML label carries the level badge and the status in words). */\n service(node: RenderNode, status: RuntimeDiffStatus, changes: RuntimeFactChange[]): string {\n const style = DIFF_STYLES[status];\n const change = changes[0];\n const level = this.levels(change);\n const changed = status === RuntimeDiffStatus.CHANGED && change !== undefined ? `: ${change.changedFields.join(', ')}` : '';\n const label =\n `<FONT COLOR=\"${style.text}99\">${htmlLabelText(level)}</FONT>  <B>${htmlLabelText(node.id.split('/').pop() ?? node.id)}</B>` +\n `<BR/>${htmlLabelText(node.tags?.role ?? 'unknown')}<BR/>${htmlLabelText(`${style.word}${changed}`)}`;\n const boxStyle = status === RuntimeDiffStatus.REMOVED ? 'filled,rounded,dashed' : 'filled,rounded';\n return (\n ` \"${dotValue(node.id)}\" [style=\"${boxStyle}\", fillcolor=\"${style.fill}\", color=\"${style.border}\", ` +\n `penwidth=2, fontcolor=\"${style.text}\", label=<${label}>];\\n`\n );\n }\n\n /** A context box (queue, datastore, clock, vendor): kind shape kept, status colors laid over it. */\n context(node: RenderNode, status: RuntimeDiffStatus, changes: RuntimeFactChange[]): string {\n const style = DIFF_STYLES[status];\n let dot = node.dot;\n const moved = changes.find((change: RuntimeFactChange) => change.changedFields.includes('level'));\n if (node.queueKeys.length > 0 && moved !== undefined)\n dot = dot.replace(/(label=\" \\|)L\\d+ {2}/, `$1${this.levels(moved).replace(/>/g, '\\\\>')} `);\n const base = /style=\"([^\"]*)\"/.exec(dot)?.[1] ?? 'filled';\n const boxStyle = status === RuntimeDiffStatus.REMOVED && !base.includes('dashed') ? `${base},dashed` : base;\n return `${dot.slice(0, -2)} [style=\"${boxStyle}\", fillcolor=\"${style.fill}\", color=\"${style.border}\", penwidth=2, fontcolor=\"${style.text}\"];\\n`;\n }\n\n /** An arrow: its line style says rpc/event as always; color (and dots for a removal) says the diff. */\n edge(dot: string, status: RuntimeDiffStatus): string {\n const style = DIFF_STYLES[status];\n const removed = status === RuntimeDiffStatus.REMOVED ? ', style=dotted' : '';\n const width = status === RuntimeDiffStatus.UNCHANGED ? 1 : 2.2;\n return `${dot.slice(0, -2)} [color=\"${style.border}\", fontcolor=\"${style.border}\", penwidth=${width}${removed}];\\n`;\n }\n\n /** `L4`, or `L4 → L2` when the level moved. */\n levels(change: RuntimeFactChange | undefined): string {\n const before = change?.before?.['level'];\n const after = change?.after?.['level'];\n if (before !== undefined && after !== undefined && before !== after) return `${before} → ${after}`;\n return after ?? before ?? '';\n }\n}\n\n/** Builds the Goal vs Current view both runtime pages carry. */\nexport class RuntimeGoalComparison {\n private readonly styler = new RuntimeComparisonStyler();\n\n build(current: RuntimeGraph, goal: RuntimeGraph, options: RuntimeVizOptions): RuntimeComparisonView {\n const diff = new RuntimeGraphDiff().compare(current, goal);\n const union = new RuntimeUnionGraph().build(current, goal);\n const tags: Record<string, string> = {};\n for (const key of Object.keys(union.queues)) tags[key] = diff.get(`queue:${key}`)?.status ?? '';\n const model = generateRuntimeRenderModel(union, 'Goal vs Current', options.withPage(RuntimePageKind.COMPARISON, tags));\n const nodeDetails: Record<string, string[]> = {};\n const edgeDetails: Record<string, string[]> = {};\n const ids = new RuntimeComparisonIds(union);\n this.restyleNodes(model, ids, diff, nodeDetails);\n this.restyleEdges(model, ids, diff, edgeDetails);\n model.fullDot = new RuntimeLayout().assemble(model);\n assertValidDot(model.fullDot, 'runtime-architecture goal comparison');\n return new RuntimeComparisonView(\n true,\n '',\n 'Current → Goal',\n this.summary(diff),\n model,\n nodeDetails,\n edgeDetails,\n diff.differences().map((change: RuntimeFactChange) => this.line(change)),\n );\n }\n\n private restyleNodes(model: GraphRenderModel, ids: RuntimeComparisonIds, diff: RuntimeGraphDiffResult, details: Record<string, string[]>): void {\n for (let index = 0; index < model.nodes.length; index++) {\n const node = model.nodes[index];\n const changes = this.changes(ids.node(node), diff);\n const status = this.styler.status(changes);\n const dot = node.tags !== null ? this.styler.service(node, status, changes) : this.styler.context(node, status, changes);\n model.nodes[index] = new RenderNode(node.id, dot, null, node.tags, node.products, node.queueKeys);\n details[node.id] = changes.flatMap((change: RuntimeFactChange) => change.lines());\n }\n }\n\n private restyleEdges(model: GraphRenderModel, ids: RuntimeComparisonIds, diff: RuntimeGraphDiffResult, details: Record<string, string[]>): void {\n const nodes = new Map<string, RenderNode>();\n for (const node of model.nodes) nodes.set(node.id, node);\n for (let index = 0; index < model.edges.length; index++) {\n const edge = model.edges[index];\n const changes = this.changes(ids.edge(edge, nodes), diff);\n const status = this.styler.status(changes);\n model.edges[index] = new RenderEdge(edge.from, edge.to, this.styler.edge(edge.dot, status), edge.id, null);\n details[edge.id] = [`${edge.from} → ${edge.to}`, ...changes.flatMap((change: RuntimeFactChange) => change.lines())];\n }\n }\n\n private changes(factIds: string[], diff: RuntimeGraphDiffResult): RuntimeFactChange[] {\n return factIds\n .map((id: string) => diff.get(id))\n .filter((change: RuntimeFactChange | undefined): change is RuntimeFactChange => change !== undefined);\n }\n\n private summary(diff: RuntimeGraphDiffResult): string {\n if (diff.identical()) return 'No differences: the goal matches the current runtime graph.';\n const counts = diff.counts();\n const levels = diff.maxLevelBefore === diff.maxLevelAfter ? '' : ` · max level L${diff.maxLevelBefore} → L${diff.maxLevelAfter}`;\n return `${counts.added} added · ${counts.removed} removed · ${counts.changed} changed · ${counts.unchanged} unchanged${levels}`;\n }\n\n private line(change: RuntimeFactChange): string {\n const fields = change.status === RuntimeDiffStatus.CHANGED ? ` (${change.changedFields.join(', ')})` : '';\n return `${change.status}: ${change.label}${fields}`;\n }\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RuntimeComparisonView } from './graph-render-model';
|
|
2
|
+
/** The before/after lines the page's Goal vs Current details panel shows, keyed by overlay id. */
|
|
3
|
+
export declare class RuntimeGoalDetailsData {
|
|
4
|
+
readonly nodes: Record<string, string[]>;
|
|
5
|
+
readonly edges: Record<string, string[]>;
|
|
6
|
+
constructor(nodes: Record<string, string[]>, edges: Record<string, string[]>);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Goal vs Current details (#1239): the page script that opens before/after lines for a clicked box
|
|
10
|
+
* (a "Goal vs Current" item in the SHARED node menu) or a clicked arrow, while that mode is on.
|
|
11
|
+
* Inlined with toString, like RuntimeDetails, so the page works from `file://` with no fetch.
|
|
12
|
+
*/
|
|
13
|
+
export declare class RuntimeGoalDetails {
|
|
14
|
+
private readonly comparison;
|
|
15
|
+
constructor(comparison: RuntimeComparisonView | null);
|
|
16
|
+
script(): string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeGoalDetails = exports.RuntimeGoalDetailsData = void 0;
|
|
4
|
+
/** The before/after lines the page's Goal vs Current details panel shows, keyed by overlay id. */
|
|
5
|
+
class RuntimeGoalDetailsData {
|
|
6
|
+
nodes;
|
|
7
|
+
edges;
|
|
8
|
+
constructor(nodes, edges) {
|
|
9
|
+
this.nodes = nodes;
|
|
10
|
+
this.edges = edges;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.RuntimeGoalDetailsData = RuntimeGoalDetailsData;
|
|
14
|
+
/**
|
|
15
|
+
* Goal vs Current details (#1239): the page script that opens before/after lines for a clicked box
|
|
16
|
+
* (a "Goal vs Current" item in the SHARED node menu) or a clicked arrow, while that mode is on.
|
|
17
|
+
* Inlined with toString, like RuntimeDetails, so the page works from `file://` with no fetch.
|
|
18
|
+
*/
|
|
19
|
+
class RuntimeGoalDetails {
|
|
20
|
+
comparison;
|
|
21
|
+
constructor(comparison) {
|
|
22
|
+
this.comparison = comparison;
|
|
23
|
+
}
|
|
24
|
+
script() {
|
|
25
|
+
const data = new RuntimeGoalDetailsData(this.comparison?.nodeDetails ?? {}, this.comparison?.edgeDetails ?? {});
|
|
26
|
+
const encoded = JSON.stringify(data).replace(/</g, '\\u003c');
|
|
27
|
+
return `window.__WP_GOAL__ = new (${RuntimeGoalDetailsController.toString()})(${encoded});`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.RuntimeGoalDetails = RuntimeGoalDetails;
|
|
31
|
+
/** Browser controller: one floating panel, rebound to each redrawn overlay SVG. */
|
|
32
|
+
class RuntimeGoalDetailsController {
|
|
33
|
+
data;
|
|
34
|
+
host = document.getElementById('graph');
|
|
35
|
+
panel = document.createElement('div');
|
|
36
|
+
anchor = null;
|
|
37
|
+
constructor(data) {
|
|
38
|
+
this.data = data;
|
|
39
|
+
this.panel.className = 'wp-goal-details';
|
|
40
|
+
this.panel.hidden = true;
|
|
41
|
+
this.panel.setAttribute('role', 'dialog');
|
|
42
|
+
this.panel.setAttribute('aria-label', 'Goal vs Current details');
|
|
43
|
+
document.body.append(this.panel);
|
|
44
|
+
if (this.host !== null)
|
|
45
|
+
new MutationObserver(() => this.attach()).observe(this.host, { childList: true });
|
|
46
|
+
document.addEventListener('keydown', (event) => {
|
|
47
|
+
if (event.key === 'Escape' && !this.panel.hidden) {
|
|
48
|
+
event.stopPropagation();
|
|
49
|
+
this.close();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
document.addEventListener('click', (event) => {
|
|
53
|
+
if (event.target instanceof Node && !this.panel.contains(event.target) && !this.anchor?.contains(event.target))
|
|
54
|
+
this.close();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** The node menu's item, only while Goal vs Current is the active mode and the box has details. */
|
|
58
|
+
item(name, node) {
|
|
59
|
+
if (!this.active() || this.data.nodes[name] === undefined)
|
|
60
|
+
return null;
|
|
61
|
+
return new WpNodeMenuItem('Goal vs Current details', () => this.open(node, this.data.nodes[name]));
|
|
62
|
+
}
|
|
63
|
+
active() {
|
|
64
|
+
return document.body.dataset['wpMode'] === 'goal';
|
|
65
|
+
}
|
|
66
|
+
/** Every overlay arrow opens its relationship's before/after lines (capture: ahead of the Uses popup). */
|
|
67
|
+
attach() {
|
|
68
|
+
this.close();
|
|
69
|
+
if (!this.active() || this.host === null)
|
|
70
|
+
return;
|
|
71
|
+
for (const group of Array.from(this.host.querySelectorAll('g.edge'))) {
|
|
72
|
+
const lines = this.data.edges[group.id];
|
|
73
|
+
if (lines === undefined || group.dataset['wpGoal'] === 'true')
|
|
74
|
+
continue;
|
|
75
|
+
group.dataset['wpGoal'] = 'true';
|
|
76
|
+
group.classList.add('wp-goal-edge');
|
|
77
|
+
group.setAttribute('tabindex', '0');
|
|
78
|
+
group.setAttribute('role', 'button');
|
|
79
|
+
group.setAttribute('aria-label', `Goal vs Current: ${lines[0]}`);
|
|
80
|
+
group.addEventListener('click', (event) => {
|
|
81
|
+
event.stopPropagation();
|
|
82
|
+
this.open(group, lines);
|
|
83
|
+
}, true);
|
|
84
|
+
group.addEventListener('keydown', (event) => {
|
|
85
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
86
|
+
return;
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
this.open(group, lines);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
open(anchor, lines) {
|
|
93
|
+
this.anchor = anchor;
|
|
94
|
+
const close = document.createElement('button');
|
|
95
|
+
close.type = 'button';
|
|
96
|
+
close.textContent = 'Close';
|
|
97
|
+
close.onclick = () => this.close();
|
|
98
|
+
const title = document.createElement('p');
|
|
99
|
+
title.textContent = 'Current → Goal';
|
|
100
|
+
const list = document.createElement('ul');
|
|
101
|
+
for (const line of lines.length === 0 ? ['Unchanged: present in both saved files with the same facts.'] : lines) {
|
|
102
|
+
const item = document.createElement('li');
|
|
103
|
+
item.textContent = line;
|
|
104
|
+
list.append(item);
|
|
105
|
+
}
|
|
106
|
+
this.panel.replaceChildren(close, title, list);
|
|
107
|
+
this.panel.hidden = false;
|
|
108
|
+
const rect = anchor.getBoundingClientRect();
|
|
109
|
+
this.panel.style.left = `${Math.max(12, Math.min(rect.left, innerWidth - this.panel.offsetWidth - 12))}px`;
|
|
110
|
+
this.panel.style.top = `${Math.max(12, Math.min(rect.bottom + 6, innerHeight - this.panel.offsetHeight - 12))}px`;
|
|
111
|
+
close.focus({ preventScroll: true });
|
|
112
|
+
}
|
|
113
|
+
close() {
|
|
114
|
+
this.panel.hidden = true;
|
|
115
|
+
this.anchor = null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=runtime-goal-details.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-goal-details.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-goal-details.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RuntimeComparisonView } from './graph-render-model';\n\n/** The before/after lines the page's Goal vs Current details panel shows, keyed by overlay id. */\nexport class RuntimeGoalDetailsData {\n constructor(\n public readonly nodes: Record<string, string[]>,\n public readonly edges: Record<string, string[]>,\n ) {}\n}\n\n/**\n * Goal vs Current details (#1239): the page script that opens before/after lines for a clicked box\n * (a \"Goal vs Current\" item in the SHARED node menu) or a clicked arrow, while that mode is on.\n * Inlined with toString, like RuntimeDetails, so the page works from `file://` with no fetch.\n */\nexport class RuntimeGoalDetails {\n constructor(private readonly comparison: RuntimeComparisonView | null) {}\n\n script(): string {\n const data = new RuntimeGoalDetailsData(this.comparison?.nodeDetails ?? {}, this.comparison?.edgeDetails ?? {});\n const encoded = JSON.stringify(data).replace(/</g, '\\\\u003c');\n return `window.__WP_GOAL__ = new (${RuntimeGoalDetailsController.toString()})(${encoded});`;\n }\n}\n\n/** Browser controller: one floating panel, rebound to each redrawn overlay SVG. */\nclass RuntimeGoalDetailsController {\n private readonly host = document.getElementById('graph');\n private readonly panel = document.createElement('div');\n private anchor: Element | null = null;\n\n constructor(private readonly data: RuntimeGoalDetailsData) {\n this.panel.className = 'wp-goal-details';\n this.panel.hidden = true;\n this.panel.setAttribute('role', 'dialog');\n this.panel.setAttribute('aria-label', 'Goal vs Current details');\n document.body.append(this.panel);\n if (this.host !== null) new MutationObserver(() => this.attach()).observe(this.host, { childList: true });\n document.addEventListener('keydown', (event: KeyboardEvent) => {\n if (event.key === 'Escape' && !this.panel.hidden) {\n event.stopPropagation();\n this.close();\n }\n });\n document.addEventListener('click', (event: MouseEvent) => {\n if (event.target instanceof Node && !this.panel.contains(event.target) && !this.anchor?.contains(event.target)) this.close();\n });\n }\n\n /** The node menu's item, only while Goal vs Current is the active mode and the box has details. */\n item(name: string, node: SVGGElement): WpNodeMenuItem | null {\n if (!this.active() || this.data.nodes[name] === undefined) return null;\n return new WpNodeMenuItem('Goal vs Current details', () => this.open(node, this.data.nodes[name]));\n }\n\n private active(): boolean {\n return document.body.dataset['wpMode'] === 'goal';\n }\n\n /** Every overlay arrow opens its relationship's before/after lines (capture: ahead of the Uses popup). */\n private attach(): void {\n this.close();\n if (!this.active() || this.host === null) return;\n for (const group of Array.from(this.host.querySelectorAll<SVGGElement>('g.edge'))) {\n const lines = this.data.edges[group.id];\n if (lines === undefined || group.dataset['wpGoal'] === 'true') continue;\n group.dataset['wpGoal'] = 'true';\n group.classList.add('wp-goal-edge');\n group.setAttribute('tabindex', '0');\n group.setAttribute('role', 'button');\n group.setAttribute('aria-label', `Goal vs Current: ${lines[0]}`);\n group.addEventListener('click', (event: MouseEvent) => {\n event.stopPropagation();\n this.open(group, lines);\n }, true);\n group.addEventListener('keydown', (event: KeyboardEvent) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n this.open(group, lines);\n });\n }\n }\n\n private open(anchor: Element, lines: string[]): void {\n this.anchor = anchor;\n const close = document.createElement('button');\n close.type = 'button';\n close.textContent = 'Close';\n close.onclick = () => this.close();\n const title = document.createElement('p');\n title.textContent = 'Current → Goal';\n const list = document.createElement('ul');\n for (const line of lines.length === 0 ? ['Unchanged: present in both saved files with the same facts.'] : lines) {\n const item = document.createElement('li');\n item.textContent = line;\n list.append(item);\n }\n this.panel.replaceChildren(close, title, list);\n this.panel.hidden = false;\n const rect = anchor.getBoundingClientRect();\n this.panel.style.left = `${Math.max(12, Math.min(rect.left, innerWidth - this.panel.offsetWidth - 12))}px`;\n this.panel.style.top = `${Math.max(12, Math.min(rect.bottom + 6, innerHeight - this.panel.offsetHeight - 12))}px`;\n close.focus({ preventScroll: true });\n }\n\n private close(): void {\n this.panel.hidden = true;\n this.anchor = null;\n }\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime architecture GOAL (#1239): `architecture/runtime-dependencies-goal.json`.
|
|
3
|
+
*
|
|
4
|
+
* A proposal, not a fact. `architecture/runtime-dependencies.json` is GENERATED from source and its
|
|
5
|
+
* unchanged-validator rightly treats any hand edit as drift, so a proposal needs its own durable,
|
|
6
|
+
* reviewable file in the same RuntimeGraph shape. Nothing in the generator reads, writes or deletes
|
|
7
|
+
* this file: only a person (or an AI) edits it, and only the explicit `--prepare` step below rewrites
|
|
8
|
+
* its derived fields.
|
|
9
|
+
*
|
|
10
|
+
* Errors here never tell anyone to regenerate from source — a goal has no source. They name the goal
|
|
11
|
+
* path, the broken element, and the edit or the explicit preparation command that repairs it.
|
|
12
|
+
*/
|
|
13
|
+
import { Option } from '@webpieces/rules-config';
|
|
14
|
+
import type { RuntimeGraph } from './runtime-graph-model';
|
|
15
|
+
export declare const DEFAULT_RUNTIME_GOAL_PATH = "architecture/runtime-dependencies-goal.json";
|
|
16
|
+
export declare const RUNTIME_GOAL_RULE_NAME = "runtime-architecture-goal";
|
|
17
|
+
export declare const RUNTIME_GOAL_PREPARE_COMMAND = "pnpm nx run architecture:visualize-runtime-goal --prepare";
|
|
18
|
+
export declare const RUNTIME_GOAL_INIT_COMMAND = "cp architecture/runtime-dependencies.json architecture/runtime-dependencies-goal.json";
|
|
19
|
+
/** The derived fields `--prepare` rewrote, as `field: before → after` lines; empty when none moved. */
|
|
20
|
+
export declare class RuntimeGoalPreparation {
|
|
21
|
+
readonly graph: RuntimeGraph;
|
|
22
|
+
readonly changes: string[];
|
|
23
|
+
/** Whether the goal file was rewritten (also true when only explanatory levelBecause text moved). */
|
|
24
|
+
readonly written: boolean;
|
|
25
|
+
constructor(graph: RuntimeGraph, changes: string[],
|
|
26
|
+
/** Whether the goal file was rewritten (also true when only explanatory levelBecause text moved). */
|
|
27
|
+
written: boolean);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* What viewing found about the goal's derived facts: a CYCLE (only an edit of runtimeEdges fixes it,
|
|
31
|
+
* `--prepare` refuses it too) or field DIFFERENCES that `--prepare` would rewrite.
|
|
32
|
+
*/
|
|
33
|
+
export declare class RuntimeGoalDrift {
|
|
34
|
+
readonly cycle: string | null;
|
|
35
|
+
readonly differences: string[];
|
|
36
|
+
constructor(cycle: string | null, differences: string[]);
|
|
37
|
+
}
|
|
38
|
+
/** Loads, checks and (only on request) prepares the hand-authored runtime goal. */
|
|
39
|
+
export declare class RuntimeGoalFile {
|
|
40
|
+
readonly goalPath: string;
|
|
41
|
+
private readonly validator;
|
|
42
|
+
constructor(goalPath?: string);
|
|
43
|
+
exists(workspaceRoot: string): boolean;
|
|
44
|
+
/** The goal as saved, after its shape, references and derived facts check out. Never writes anything. */
|
|
45
|
+
load(workspaceRoot: string): RuntimeGraph;
|
|
46
|
+
/**
|
|
47
|
+
* Where the saved derived facts disagree with what the existing runtime level machinery
|
|
48
|
+
* (RuntimeNodeLevels — the generator's own) computes from the goal's relationships. Reported, not
|
|
49
|
+
* fixed: viewing is read-only, and `--prepare` is the explicit step that rewrites them.
|
|
50
|
+
*/
|
|
51
|
+
drift(graph: RuntimeGraph): RuntimeGoalDrift;
|
|
52
|
+
/**
|
|
53
|
+
* Recompute levels, self-queue flags, contract queues and maxLevel, and WRITE the goal. Gated on
|
|
54
|
+
* STRUCTURAL problems only: missing or stale derived facts are exactly what this step computes.
|
|
55
|
+
*/
|
|
56
|
+
prepare(workspaceRoot: string): RuntimeGoalPreparation;
|
|
57
|
+
/** The edit that clears a cycle verdict — `--prepare` cannot. */
|
|
58
|
+
cycleOption(): Option;
|
|
59
|
+
private structurallyValid;
|
|
60
|
+
/** A deep copy with the generator's level machinery applied, or the cycle it refused. */
|
|
61
|
+
private recompute;
|
|
62
|
+
private differences;
|
|
63
|
+
private contractDifferences;
|
|
64
|
+
private missing;
|
|
65
|
+
private editOption;
|
|
66
|
+
private failure;
|
|
67
|
+
}
|
|
68
|
+
/** What a goal page says it is: a hand-authored proposal, not a generated snapshot (#1239). */
|
|
69
|
+
export declare class RuntimeGoalProvenance {
|
|
70
|
+
message(): string;
|
|
71
|
+
html(): string;
|
|
72
|
+
}
|