@contentful/experience-design-system-cli 2.14.2 → 2.15.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/README.md +64 -2
- package/dist/package.json +3 -2
- package/dist/src/analyze/command.d.ts +11 -0
- package/dist/src/analyze/command.js +298 -3
- package/dist/src/analyze/composite-closure.d.ts +25 -0
- package/dist/src/analyze/composite-closure.js +0 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.d.ts +4 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.js +22 -0
- package/dist/src/analyze/composition/agent-parser/extract-parser.d.ts +8 -0
- package/dist/src/analyze/composition/agent-parser/extract-parser.js +44 -0
- package/dist/src/analyze/composition/agent-parser/load-prompt.d.ts +7 -0
- package/dist/src/analyze/composition/agent-parser/load-prompt.js +31 -0
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.d.ts +46 -0
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +88 -0
- package/dist/src/analyze/composition/agent-parser/sandbox.d.ts +45 -0
- package/dist/src/analyze/composition/agent-parser/sandbox.js +187 -0
- package/dist/src/analyze/composition/apply-mapping.d.ts +22 -0
- package/dist/src/analyze/composition/apply-mapping.js +67 -0
- package/dist/src/analyze/composition/candidate-critic-agent.d.ts +7 -0
- package/dist/src/analyze/composition/candidate-critic-agent.js +39 -0
- package/dist/src/analyze/composition/candidate-critic.d.ts +32 -0
- package/dist/src/analyze/composition/candidate-critic.js +63 -0
- package/dist/src/analyze/composition/candidate-files.d.ts +34 -0
- package/dist/src/analyze/composition/candidate-files.js +131 -0
- package/dist/src/analyze/composition/composition-cache-key.d.ts +19 -0
- package/dist/src/analyze/composition/composition-cache-key.js +19 -0
- package/dist/src/analyze/composition/interchange-schema.d.ts +37 -0
- package/dist/src/analyze/composition/interchange-schema.js +61 -0
- package/dist/src/analyze/composition/merge-edges.d.ts +19 -0
- package/dist/src/analyze/composition/merge-edges.js +50 -0
- package/dist/src/analyze/composition/parse-map-edges.d.ts +16 -0
- package/dist/src/analyze/composition/parse-map-edges.js +55 -0
- package/dist/src/analyze/composition/resolve-mapping-cli.d.ts +23 -0
- package/dist/src/analyze/composition/resolve-mapping-cli.js +29 -0
- package/dist/src/analyze/composition/resolve-mapping.d.ts +59 -0
- package/dist/src/analyze/composition/resolve-mapping.js +94 -0
- package/dist/src/analyze/cycle-detection.d.ts +27 -0
- package/dist/src/analyze/cycle-detection.js +234 -0
- package/dist/src/analyze/cycle-view.d.ts +7 -0
- package/dist/src/analyze/cycle-view.js +28 -0
- package/dist/src/analyze/fuzzy-search.d.ts +3 -0
- package/dist/src/analyze/fuzzy-search.js +55 -0
- package/dist/src/analyze/issue-inheritance.d.ts +12 -0
- package/dist/src/analyze/issue-inheritance.js +108 -0
- package/dist/src/analyze/lineage.d.ts +29 -0
- package/dist/src/analyze/lineage.js +0 -0
- package/dist/src/analyze/scope-gate-cascade.d.ts +11 -0
- package/dist/src/analyze/scope-gate-cascade.js +170 -0
- package/dist/src/analyze/search-neighborhood.d.ts +3 -0
- package/dist/src/analyze/search-neighborhood.js +56 -0
- package/dist/src/analyze/select/tui/App.js +5 -16
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.d.ts +0 -4
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +3 -15
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +29 -38
- package/dist/src/analyze/select/tui/components/FieldEditor.js +309 -185
- package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +23 -2
- package/dist/src/analyze/select/tui/components/GotoBanner.d.ts +31 -0
- package/dist/src/analyze/select/tui/components/GotoBanner.js +30 -0
- package/dist/src/analyze/select/tui/components/GroupedSidebar.d.ts +91 -0
- package/dist/src/analyze/select/tui/components/GroupedSidebar.js +497 -0
- package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +9 -3
- package/dist/src/analyze/select/tui/components/JsonEditor.js +3 -3
- package/dist/src/analyze/select/tui/components/LineagePanel.d.ts +14 -0
- package/dist/src/analyze/select/tui/components/LineagePanel.js +22 -0
- package/dist/src/analyze/select/tui/components/RationalePanel.d.ts +0 -7
- package/dist/src/analyze/select/tui/components/RationalePanel.js +3 -15
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +0 -6
- package/dist/src/analyze/select/tui/components/Sidebar.js +11 -27
- package/dist/src/analyze/select/tui/components/StatusBar.js +2 -1
- package/dist/src/analyze/select/tui/components/removed-components-text.d.ts +7 -0
- package/dist/src/analyze/select/tui/components/removed-components-text.js +11 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +1 -5
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +8 -10
- package/dist/src/analyze/select/tui/theme.d.ts +15 -0
- package/dist/src/analyze/select/tui/theme.js +28 -0
- package/dist/src/analyze/selection-cascade.d.ts +3 -0
- package/dist/src/analyze/selection-cascade.js +19 -0
- package/dist/src/analyze/slot-graph.d.ts +11 -0
- package/dist/src/analyze/slot-graph.js +17 -0
- package/dist/src/apply/api-client.d.ts +2 -1
- package/dist/src/apply/api-client.js +40 -6
- package/dist/src/apply/command.d.ts +17 -1
- package/dist/src/apply/command.js +69 -11
- package/dist/src/apply/error-parser.d.ts +16 -0
- package/dist/src/apply/error-parser.js +117 -0
- package/dist/src/apply/tui/ServerPreviewView.js +3 -1
- package/dist/src/credentials-store.d.ts +6 -0
- package/dist/src/credentials-store.js +5 -1
- package/dist/src/generate/agent-runner.d.ts +6 -0
- package/dist/src/generate/agent-runner.js +24 -8
- package/dist/src/import/command.js +69 -31
- package/dist/src/import/cycle-auto-reject.d.ts +4 -0
- package/dist/src/import/cycle-auto-reject.js +14 -0
- package/dist/src/import/orchestrator.d.ts +20 -7
- package/dist/src/import/orchestrator.js +139 -35
- package/dist/src/import/strip-allowed-components.d.ts +18 -0
- package/dist/src/import/strip-allowed-components.js +22 -0
- package/dist/src/import/tui/CustomPromptBanner.d.ts +0 -5
- package/dist/src/import/tui/CustomPromptBanner.js +2 -1
- package/dist/src/import/tui/WizardApp.d.ts +10 -74
- package/dist/src/import/tui/WizardApp.js +234 -246
- package/dist/src/import/tui/ai-flag.d.ts +5 -0
- package/dist/src/import/tui/ai-flag.js +6 -0
- package/dist/src/import/tui/autocomplete.d.ts +5 -0
- package/dist/src/import/tui/autocomplete.js +32 -0
- package/dist/src/import/tui/components/AutoFilterBanner.d.ts +10 -0
- package/dist/src/import/tui/components/AutoFilterBanner.js +19 -0
- package/dist/src/import/tui/components/CounterStrip.d.ts +12 -0
- package/dist/src/import/tui/components/CounterStrip.js +12 -0
- package/dist/src/import/tui/components/LegendEntry.d.ts +2 -0
- package/dist/src/import/tui/components/LegendEntry.js +6 -0
- package/dist/src/import/tui/cycle-panel-scroll.d.ts +8 -0
- package/dist/src/import/tui/cycle-panel-scroll.js +33 -0
- package/dist/src/import/tui/final-review-host.d.ts +3 -7
- package/dist/src/import/tui/final-review-host.js +9 -4
- package/dist/src/import/tui/group-collapse.d.ts +2 -0
- package/dist/src/import/tui/group-collapse.js +11 -0
- package/dist/src/import/tui/history.d.ts +22 -0
- package/dist/src/import/tui/history.js +53 -0
- package/dist/src/import/tui/hooks/useLineage.d.ts +32 -0
- package/dist/src/import/tui/hooks/useLineage.js +50 -0
- package/dist/src/import/tui/hooks/useOverlayPanel.d.ts +13 -0
- package/dist/src/import/tui/hooks/useOverlayPanel.js +27 -0
- package/dist/src/import/tui/lineage-layout.d.ts +75 -0
- package/dist/src/import/tui/lineage-layout.js +82 -0
- package/dist/src/import/tui/runLivePreview.d.ts +11 -0
- package/dist/src/import/tui/runLivePreview.js +7 -1
- package/dist/src/import/tui/scope-gate-columns.d.ts +31 -0
- package/dist/src/import/tui/scope-gate-columns.js +94 -0
- package/dist/src/import/tui/scope-gate-host.d.ts +3 -1
- package/dist/src/import/tui/scope-gate-host.js +11 -3
- package/dist/src/import/tui/sidebar-width.d.ts +1 -0
- package/dist/src/import/tui/sidebar-width.js +3 -0
- package/dist/src/import/tui/step-filters.d.ts +19 -0
- package/dist/src/import/tui/step-filters.js +42 -0
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.d.ts +42 -0
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +726 -0
- package/dist/src/import/tui/steps/AtomicScopeGateStep.d.ts +3 -0
- package/dist/src/import/tui/steps/AtomicScopeGateStep.js +211 -0
- package/dist/src/import/tui/steps/CredentialsStep.js +5 -18
- package/dist/src/import/tui/steps/DoneStep.d.ts +0 -2
- package/dist/src/import/tui/steps/DoneStep.js +3 -2
- package/dist/src/import/tui/steps/ErrorStep.js +2 -1
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +19 -25
- package/dist/src/import/tui/steps/GenerateReviewStep.js +1258 -229
- package/dist/src/import/tui/steps/PathValidationStep.js +3 -2
- package/dist/src/import/tui/steps/PreviewStep.js +2 -1
- package/dist/src/import/tui/steps/PushDecisionGateStep.d.ts +0 -7
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +3 -6
- package/dist/src/import/tui/steps/PushingStep.js +2 -1
- package/dist/src/import/tui/steps/RunningStep.d.ts +4 -1
- package/dist/src/import/tui/steps/RunningStep.js +3 -2
- package/dist/src/import/tui/steps/ScopeGateStep.d.ts +24 -0
- package/dist/src/import/tui/steps/ScopeGateStep.js +850 -193
- package/dist/src/import/tui/steps/TokenInputStep.js +2 -1
- package/dist/src/import/tui/steps/WelcomeStep.js +2 -1
- package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +6 -0
- package/dist/src/import/tui/steps/WizardPreviewStep.js +95 -56
- package/dist/src/import/tui/steps/auto-reject-decision.d.ts +7 -0
- package/dist/src/import/tui/steps/auto-reject-decision.js +9 -0
- package/dist/src/import/tui/steps/breaking-change-format.d.ts +2 -0
- package/dist/src/import/tui/steps/breaking-change-format.js +24 -0
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.d.ts +14 -0
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
- package/dist/src/import/tui/steps/preview-diff.js +48 -5
- package/dist/src/import/tui/useFinalizePreview.d.ts +34 -0
- package/dist/src/import/tui/useFinalizePreview.js +0 -0
- package/dist/src/import/tui/useLivePreview.d.ts +3 -0
- package/dist/src/import/tui/useLivePreview.js +1 -0
- package/dist/src/import/tui/wizard-state-transitions.d.ts +5 -59
- package/dist/src/import/tui/wizard-state-transitions.js +5 -60
- package/dist/src/lib/composition-mode.d.ts +28 -0
- package/dist/src/lib/composition-mode.js +37 -0
- package/dist/src/lib/prompt-overrides.d.ts +38 -0
- package/dist/src/lib/prompt-overrides.js +69 -0
- package/dist/src/print/command.js +19 -1
- package/dist/src/runs/modify-launcher.d.ts +3 -0
- package/dist/src/runs/modify-launcher.js +2 -0
- package/dist/src/runs/replay-helpers.js +1 -0
- package/dist/src/runs/run-picker.js +2 -18
- package/dist/src/runs/store.d.ts +4 -0
- package/dist/src/session/db.d.ts +23 -40
- package/dist/src/session/db.js +110 -138
- package/dist/src/setup/composition-mode-prompt.d.ts +14 -0
- package/dist/src/setup/composition-mode-prompt.js +37 -0
- package/package.json +5 -4
- package/prompts/composition-dir-critic.md +7 -0
- package/prompts/composition-edges.md +8 -0
- package/prompts/composition-parser-repair-empty.md +1 -0
- package/prompts/composition-parser-repair-error.md +2 -0
- package/prompts/composition-parser.md +42 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
function buildGraph(components) {
|
|
2
|
+
const nodes = [];
|
|
3
|
+
const seen = new Set();
|
|
4
|
+
for (const c of components) {
|
|
5
|
+
if (seen.has(c.name))
|
|
6
|
+
continue;
|
|
7
|
+
seen.add(c.name);
|
|
8
|
+
nodes.push(c.name);
|
|
9
|
+
}
|
|
10
|
+
const adjacency = new Map();
|
|
11
|
+
for (const node of nodes)
|
|
12
|
+
adjacency.set(node, []);
|
|
13
|
+
for (const comp of components) {
|
|
14
|
+
const outgoing = adjacency.get(comp.name);
|
|
15
|
+
if (!outgoing)
|
|
16
|
+
continue;
|
|
17
|
+
for (const slot of comp.slots) {
|
|
18
|
+
const allowed = slot.allowedComponents ?? [];
|
|
19
|
+
for (const target of allowed) {
|
|
20
|
+
if (!seen.has(target))
|
|
21
|
+
continue;
|
|
22
|
+
outgoing.push({ target, slotName: slot.name });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { nodes, adjacency };
|
|
27
|
+
}
|
|
28
|
+
function stronglyConnectedComponents(nodes, adjacency) {
|
|
29
|
+
const index = new Map();
|
|
30
|
+
const lowlink = new Map();
|
|
31
|
+
const onStack = new Set();
|
|
32
|
+
const stack = [];
|
|
33
|
+
const sccs = [];
|
|
34
|
+
let counter = 0;
|
|
35
|
+
const nodeSet = new Set(nodes);
|
|
36
|
+
function strongconnect(v) {
|
|
37
|
+
index.set(v, counter);
|
|
38
|
+
lowlink.set(v, counter);
|
|
39
|
+
counter += 1;
|
|
40
|
+
stack.push(v);
|
|
41
|
+
onStack.add(v);
|
|
42
|
+
const neighbours = adjacency.get(v) ?? [];
|
|
43
|
+
for (const { target } of neighbours) {
|
|
44
|
+
if (!nodeSet.has(target))
|
|
45
|
+
continue;
|
|
46
|
+
if (!index.has(target)) {
|
|
47
|
+
strongconnect(target);
|
|
48
|
+
lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(target)));
|
|
49
|
+
}
|
|
50
|
+
else if (onStack.has(target)) {
|
|
51
|
+
lowlink.set(v, Math.min(lowlink.get(v), index.get(target)));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (lowlink.get(v) === index.get(v)) {
|
|
55
|
+
const scc = [];
|
|
56
|
+
let w;
|
|
57
|
+
do {
|
|
58
|
+
w = stack.pop();
|
|
59
|
+
onStack.delete(w);
|
|
60
|
+
scc.push(w);
|
|
61
|
+
} while (w !== v);
|
|
62
|
+
sccs.push(scc);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const node of nodes) {
|
|
66
|
+
if (!index.has(node))
|
|
67
|
+
strongconnect(node);
|
|
68
|
+
}
|
|
69
|
+
return sccs;
|
|
70
|
+
}
|
|
71
|
+
function makeCycle(path, edges) {
|
|
72
|
+
return { path: [...path, path[0]], edges: [...edges] };
|
|
73
|
+
}
|
|
74
|
+
export function findSlotCycles(components) {
|
|
75
|
+
if (components.length === 0)
|
|
76
|
+
return [];
|
|
77
|
+
const { nodes, adjacency } = buildGraph(components);
|
|
78
|
+
if (nodes.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
const cycles = [];
|
|
81
|
+
for (const node of nodes) {
|
|
82
|
+
const outgoing = adjacency.get(node) ?? [];
|
|
83
|
+
for (const edge of outgoing) {
|
|
84
|
+
if (edge.target === node) {
|
|
85
|
+
cycles.push({
|
|
86
|
+
path: [node, node],
|
|
87
|
+
edges: [{ fromComponent: node, slotName: edge.slotName, toComponent: node }],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const nodeOrder = [...nodes].sort();
|
|
93
|
+
let remaining = [...nodeOrder];
|
|
94
|
+
while (remaining.length > 0) {
|
|
95
|
+
const subgraphNodes = remaining;
|
|
96
|
+
const sccs = stronglyConnectedComponents(subgraphNodes, adjacency).filter((scc) => {
|
|
97
|
+
if (scc.length > 1)
|
|
98
|
+
return true;
|
|
99
|
+
return false;
|
|
100
|
+
});
|
|
101
|
+
if (sccs.length === 0)
|
|
102
|
+
break;
|
|
103
|
+
let startNode = null;
|
|
104
|
+
let startScc = null;
|
|
105
|
+
for (const scc of sccs) {
|
|
106
|
+
const min = [...scc].sort()[0];
|
|
107
|
+
if (startNode === null || min < startNode) {
|
|
108
|
+
startNode = min;
|
|
109
|
+
startScc = scc;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (startNode === null || startScc === null)
|
|
113
|
+
break;
|
|
114
|
+
const sccSet = new Set(startScc);
|
|
115
|
+
const blocked = new Set();
|
|
116
|
+
const blockedMap = new Map();
|
|
117
|
+
const pathStack = [];
|
|
118
|
+
const edgeStack = [];
|
|
119
|
+
function unblock(u) {
|
|
120
|
+
blocked.delete(u);
|
|
121
|
+
const dependents = blockedMap.get(u);
|
|
122
|
+
if (!dependents)
|
|
123
|
+
return;
|
|
124
|
+
for (const w of dependents) {
|
|
125
|
+
if (blocked.has(w))
|
|
126
|
+
unblock(w);
|
|
127
|
+
}
|
|
128
|
+
dependents.clear();
|
|
129
|
+
}
|
|
130
|
+
function circuit(v, root) {
|
|
131
|
+
let foundCycle = false;
|
|
132
|
+
pathStack.push(v);
|
|
133
|
+
blocked.add(v);
|
|
134
|
+
const outgoing = adjacency.get(v) ?? [];
|
|
135
|
+
for (const { target: w, slotName } of outgoing) {
|
|
136
|
+
if (!sccSet.has(w))
|
|
137
|
+
continue;
|
|
138
|
+
edgeStack.push({ fromComponent: v, slotName, toComponent: w });
|
|
139
|
+
if (w === root) {
|
|
140
|
+
cycles.push(makeCycle(pathStack, edgeStack));
|
|
141
|
+
foundCycle = true;
|
|
142
|
+
}
|
|
143
|
+
else if (!blocked.has(w)) {
|
|
144
|
+
if (circuit(w, root))
|
|
145
|
+
foundCycle = true;
|
|
146
|
+
}
|
|
147
|
+
edgeStack.pop();
|
|
148
|
+
}
|
|
149
|
+
if (foundCycle) {
|
|
150
|
+
unblock(v);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
for (const { target: w } of outgoing) {
|
|
154
|
+
if (!sccSet.has(w))
|
|
155
|
+
continue;
|
|
156
|
+
let deps = blockedMap.get(w);
|
|
157
|
+
if (!deps) {
|
|
158
|
+
deps = new Set();
|
|
159
|
+
blockedMap.set(w, deps);
|
|
160
|
+
}
|
|
161
|
+
deps.add(v);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
pathStack.pop();
|
|
165
|
+
return foundCycle;
|
|
166
|
+
}
|
|
167
|
+
circuit(startNode, startNode);
|
|
168
|
+
remaining = remaining.filter((n) => n !== startNode);
|
|
169
|
+
}
|
|
170
|
+
return cycles;
|
|
171
|
+
}
|
|
172
|
+
export function suggestCycleBreakEdge(cycle, allCycles) {
|
|
173
|
+
if (cycle.edges.length === 0) {
|
|
174
|
+
throw new Error('suggestCycleBreakEdge: cycle has no edges');
|
|
175
|
+
}
|
|
176
|
+
const indegree = new Map();
|
|
177
|
+
for (const c of allCycles) {
|
|
178
|
+
for (const edge of c.edges) {
|
|
179
|
+
indegree.set(edge.toComponent, (indegree.get(edge.toComponent) ?? 0) + 1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
let best = cycle.edges[0];
|
|
183
|
+
let bestScore = indegree.get(best.toComponent) ?? 0;
|
|
184
|
+
for (const edge of cycle.edges) {
|
|
185
|
+
const score = indegree.get(edge.toComponent) ?? 0;
|
|
186
|
+
if (score > bestScore) {
|
|
187
|
+
best = edge;
|
|
188
|
+
bestScore = score;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return best;
|
|
192
|
+
}
|
|
193
|
+
export function formatCyclePath(cycle, maxHops = 8) {
|
|
194
|
+
const arrow = ' → ';
|
|
195
|
+
const parts = [];
|
|
196
|
+
for (let i = 0; i < cycle.edges.length; i += 1) {
|
|
197
|
+
parts.push(cycle.path[i]);
|
|
198
|
+
parts.push(cycle.edges[i].slotName);
|
|
199
|
+
}
|
|
200
|
+
parts.push(cycle.path[cycle.path.length - 1]);
|
|
201
|
+
if (cycle.edges.length <= maxHops) {
|
|
202
|
+
return parts.join(arrow);
|
|
203
|
+
}
|
|
204
|
+
const keepHops = Math.max(1, maxHops - 1);
|
|
205
|
+
const keepTokens = keepHops * 2;
|
|
206
|
+
const head = parts.slice(0, keepTokens).join(arrow);
|
|
207
|
+
const tail = parts[parts.length - 1];
|
|
208
|
+
return `${head}${arrow}…${arrow}${tail}`;
|
|
209
|
+
}
|
|
210
|
+
export function formatCyclePathSegments(cycle, maxHops = 8) {
|
|
211
|
+
const raw = [];
|
|
212
|
+
for (let i = 0; i < cycle.edges.length; i += 1) {
|
|
213
|
+
raw.push({ kind: 'component', text: cycle.path[i] });
|
|
214
|
+
raw.push({ kind: 'slot', text: `[${cycle.edges[i].slotName}]` });
|
|
215
|
+
}
|
|
216
|
+
raw.push({ kind: 'component', text: cycle.path[cycle.path.length - 1] });
|
|
217
|
+
const withArrows = (segs) => {
|
|
218
|
+
const out = [];
|
|
219
|
+
for (let i = 0; i < segs.length; i += 1) {
|
|
220
|
+
if (i > 0)
|
|
221
|
+
out.push({ kind: 'arrow', text: ' → ' });
|
|
222
|
+
out.push(segs[i]);
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
};
|
|
226
|
+
if (cycle.edges.length <= maxHops) {
|
|
227
|
+
return withArrows(raw);
|
|
228
|
+
}
|
|
229
|
+
const keepHops = Math.max(1, maxHops - 1);
|
|
230
|
+
const keepTokens = keepHops * 2;
|
|
231
|
+
const head = raw.slice(0, keepTokens);
|
|
232
|
+
const tail = raw[raw.length - 1];
|
|
233
|
+
return withArrows([...head, { kind: 'component', text: '…' }, tail]);
|
|
234
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SlotCycle } from './cycle-detection.js';
|
|
2
|
+
import { type SlotGraphInput } from './slot-graph.js';
|
|
3
|
+
export interface CycleView {
|
|
4
|
+
pushBlocking: SlotCycle[];
|
|
5
|
+
structural: Set<string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function computeCycleView(components: SlotGraphInput[]): CycleView;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { findSlotCycles } from './cycle-detection.js';
|
|
2
|
+
import { buildComponentGraph } from './slot-graph.js';
|
|
3
|
+
export function computeCycleView(components) {
|
|
4
|
+
const filtered = components.filter((c) => c.status !== 'rejected');
|
|
5
|
+
const filteredGraph = buildComponentGraph(filtered);
|
|
6
|
+
const unfilteredGraph = buildComponentGraph(components);
|
|
7
|
+
let pushBlocking = [];
|
|
8
|
+
const structural = new Set();
|
|
9
|
+
try {
|
|
10
|
+
pushBlocking = findSlotCycles(filteredGraph);
|
|
11
|
+
for (const cyc of pushBlocking)
|
|
12
|
+
for (const p of cyc.path)
|
|
13
|
+
structural.add(p);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// Malformed slot data — intentional swallow.
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const unfilteredCycles = findSlotCycles(unfilteredGraph);
|
|
20
|
+
for (const cyc of unfilteredCycles)
|
|
21
|
+
for (const p of cyc.path)
|
|
22
|
+
structural.add(p);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Malformed slot data — intentional swallow.
|
|
26
|
+
}
|
|
27
|
+
return { pushBlocking, structural };
|
|
28
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function match(query, text) {
|
|
2
|
+
if (query.length === 0)
|
|
3
|
+
return { score: 0 };
|
|
4
|
+
if (text.length === 0)
|
|
5
|
+
return null;
|
|
6
|
+
const queryLower = query.toLowerCase();
|
|
7
|
+
const textLower = text.toLowerCase();
|
|
8
|
+
let score = 0;
|
|
9
|
+
let qi = 0;
|
|
10
|
+
let lastMatchIndex = -2;
|
|
11
|
+
for (let ti = 0; ti < text.length && qi < query.length; ti++) {
|
|
12
|
+
if (textLower[ti] === queryLower[qi]) {
|
|
13
|
+
score += 10;
|
|
14
|
+
if (text[ti] === query[qi]) {
|
|
15
|
+
score += 5;
|
|
16
|
+
}
|
|
17
|
+
if (ti === 0 && qi === 0) {
|
|
18
|
+
score += 15;
|
|
19
|
+
}
|
|
20
|
+
if (ti === lastMatchIndex + 1) {
|
|
21
|
+
score += 5;
|
|
22
|
+
}
|
|
23
|
+
lastMatchIndex = ti;
|
|
24
|
+
qi++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (qi < query.length)
|
|
28
|
+
return null;
|
|
29
|
+
score -= text.length * 0.1;
|
|
30
|
+
return { score };
|
|
31
|
+
}
|
|
32
|
+
export function fuzzyMatches(query, text) {
|
|
33
|
+
return match(query, text) !== null;
|
|
34
|
+
}
|
|
35
|
+
export function fuzzyScore(query, text) {
|
|
36
|
+
const result = match(query, text);
|
|
37
|
+
return result === null ? null : result.score;
|
|
38
|
+
}
|
|
39
|
+
export function fuzzyFilter(query, candidates) {
|
|
40
|
+
if (query.length === 0)
|
|
41
|
+
return [...candidates];
|
|
42
|
+
const scored = candidates
|
|
43
|
+
.map((candidate, index) => ({
|
|
44
|
+
candidate,
|
|
45
|
+
index,
|
|
46
|
+
score: fuzzyScore(query, candidate),
|
|
47
|
+
}))
|
|
48
|
+
.filter((entry) => entry.score !== null);
|
|
49
|
+
scored.sort((a, b) => {
|
|
50
|
+
if (b.score !== a.score)
|
|
51
|
+
return b.score - a.score;
|
|
52
|
+
return a.index - b.index;
|
|
53
|
+
});
|
|
54
|
+
return scored.map((entry) => entry.candidate);
|
|
55
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Closure, NodeStatus } from './composite-closure.js';
|
|
2
|
+
export interface IssueLocation {
|
|
3
|
+
component: string;
|
|
4
|
+
status: NodeStatus;
|
|
5
|
+
}
|
|
6
|
+
export interface RenderStatus {
|
|
7
|
+
status: NodeStatus;
|
|
8
|
+
isOwn: boolean;
|
|
9
|
+
sourceComponents: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function computeRenderStatuses(closure: Closure, directIssues: Map<string, NodeStatus>): Map<string, RenderStatus>;
|
|
12
|
+
export declare function pickDrillTarget(ancestor: string, closure: Closure, directIssues: Map<string, NodeStatus>): string | null;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const STATUS_RANK = { ok: 0, warning: 1, error: 2 };
|
|
2
|
+
const RANK_STATUS = ['ok', 'warning', 'error'];
|
|
3
|
+
function worstOf(statuses) {
|
|
4
|
+
let worst = 0;
|
|
5
|
+
for (const s of statuses) {
|
|
6
|
+
const r = STATUS_RANK[s];
|
|
7
|
+
if (r > worst)
|
|
8
|
+
worst = r;
|
|
9
|
+
}
|
|
10
|
+
return RANK_STATUS[worst];
|
|
11
|
+
}
|
|
12
|
+
export function computeRenderStatuses(closure, directIssues) {
|
|
13
|
+
const out = new Map();
|
|
14
|
+
if (closure.nodes.length === 0)
|
|
15
|
+
return out;
|
|
16
|
+
const nodesByName = new Map();
|
|
17
|
+
for (const node of closure.nodes) {
|
|
18
|
+
nodesByName.set(node.name, { name: node.name, path: node.path });
|
|
19
|
+
}
|
|
20
|
+
// Descendant relationship: A is an ancestor of B iff A appears in B.path
|
|
21
|
+
// (which is the shortest path from root to B) at a position before B. Note
|
|
22
|
+
// this uses the ONE canonical shortest path. For pure-inheritance semantics
|
|
23
|
+
// — a node has an inherited issue iff any node reachable from it in the
|
|
24
|
+
// closure has an issue — we need transitive descendants. In this closure
|
|
25
|
+
// model, every node's `path` field gives its shortest-path ancestor chain,
|
|
26
|
+
// and every ancestor along that chain is transitively a "container" of that
|
|
27
|
+
// node. That is sufficient here because the closure is rooted and a node is
|
|
28
|
+
// reachable from the root iff it appears in the closure.
|
|
29
|
+
const own = new Map();
|
|
30
|
+
for (const node of closure.nodes) {
|
|
31
|
+
const s = directIssues.get(node.name);
|
|
32
|
+
if (s && s !== 'ok')
|
|
33
|
+
own.set(node.name, s);
|
|
34
|
+
}
|
|
35
|
+
const inheritedSources = new Map();
|
|
36
|
+
for (const node of closure.nodes) {
|
|
37
|
+
const ownStatus = own.get(node.name);
|
|
38
|
+
if (!ownStatus)
|
|
39
|
+
continue;
|
|
40
|
+
for (let i = 0; i < node.path.length - 1; i++) {
|
|
41
|
+
const ancestor = node.path[i];
|
|
42
|
+
if (!inheritedSources.has(ancestor))
|
|
43
|
+
inheritedSources.set(ancestor, new Map());
|
|
44
|
+
inheritedSources.get(ancestor).set(node.name, ownStatus);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const [name, status] of own.entries()) {
|
|
48
|
+
out.set(name, {
|
|
49
|
+
status,
|
|
50
|
+
isOwn: true,
|
|
51
|
+
sourceComponents: [name],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
for (const [ancestor, sources] of inheritedSources.entries()) {
|
|
55
|
+
if (out.has(ancestor))
|
|
56
|
+
continue;
|
|
57
|
+
const sourceNames = [...sources.keys()].sort();
|
|
58
|
+
const status = worstOf([...sources.values()]);
|
|
59
|
+
out.set(ancestor, {
|
|
60
|
+
status,
|
|
61
|
+
isOwn: false,
|
|
62
|
+
sourceComponents: sourceNames,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
export function pickDrillTarget(ancestor, closure, directIssues) {
|
|
68
|
+
const ancestorDirect = directIssues.get(ancestor);
|
|
69
|
+
if (ancestorDirect && ancestorDirect !== 'ok')
|
|
70
|
+
return null;
|
|
71
|
+
const nodesByName = new Map();
|
|
72
|
+
for (const node of closure.nodes)
|
|
73
|
+
nodesByName.set(node.name, node);
|
|
74
|
+
const ancestorNode = nodesByName.get(ancestor);
|
|
75
|
+
if (!ancestorNode)
|
|
76
|
+
return null;
|
|
77
|
+
const candidates = [];
|
|
78
|
+
const ancestorDepth = ancestorNode.path.length - 1;
|
|
79
|
+
for (const node of closure.nodes) {
|
|
80
|
+
if (node.name === ancestor)
|
|
81
|
+
continue;
|
|
82
|
+
const idx = node.path.indexOf(ancestor);
|
|
83
|
+
if (idx === -1)
|
|
84
|
+
continue;
|
|
85
|
+
const status = directIssues.get(node.name);
|
|
86
|
+
if (!status || status === 'ok')
|
|
87
|
+
continue;
|
|
88
|
+
// depthFromAncestor is number of edges between ancestor and node along
|
|
89
|
+
// the shortest path from root. Since `path` is the shortest path from
|
|
90
|
+
// root, path.length-1 == node.depth and idx == ancestor.depth along that
|
|
91
|
+
// path, so distance = (node.path.length - 1) - idx.
|
|
92
|
+
const depthFromAncestor = node.path.length - 1 - idx;
|
|
93
|
+
void ancestorDepth;
|
|
94
|
+
candidates.push({ name: node.name, status, depthFromAncestor });
|
|
95
|
+
}
|
|
96
|
+
if (candidates.length === 0)
|
|
97
|
+
return null;
|
|
98
|
+
candidates.sort((a, b) => {
|
|
99
|
+
const rDiff = STATUS_RANK[b.status] - STATUS_RANK[a.status];
|
|
100
|
+
if (rDiff !== 0)
|
|
101
|
+
return rDiff;
|
|
102
|
+
if (a.depthFromAncestor !== b.depthFromAncestor) {
|
|
103
|
+
return a.depthFromAncestor - b.depthFromAncestor;
|
|
104
|
+
}
|
|
105
|
+
return a.name.localeCompare(b.name);
|
|
106
|
+
});
|
|
107
|
+
return candidates[0].name;
|
|
108
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ComponentGraphNode } from './composite-closure.js';
|
|
2
|
+
export interface LineageParentEdge {
|
|
3
|
+
parent: string;
|
|
4
|
+
slotName: string;
|
|
5
|
+
}
|
|
6
|
+
export interface LineageEdge {
|
|
7
|
+
from: string;
|
|
8
|
+
slotName: string;
|
|
9
|
+
to: string;
|
|
10
|
+
}
|
|
11
|
+
export type LineageChain = LineageEdge[];
|
|
12
|
+
export declare function findDirectParents(target: string, components: ComponentGraphNode[]): LineageParentEdge[];
|
|
13
|
+
export declare function findAllAncestors(target: string, components: ComponentGraphNode[]): Set<string>;
|
|
14
|
+
export declare function findAllAncestorChains(target: string, components: ComponentGraphNode[]): LineageChain[];
|
|
15
|
+
export interface AncestorTreeNode {
|
|
16
|
+
name: string;
|
|
17
|
+
incomingSlotName?: string;
|
|
18
|
+
depth: number;
|
|
19
|
+
shared: boolean;
|
|
20
|
+
cycle: boolean;
|
|
21
|
+
parents: AncestorTreeNode[];
|
|
22
|
+
}
|
|
23
|
+
export declare function buildAncestorTree(target: string, components: ComponentGraphNode[]): AncestorTreeNode;
|
|
24
|
+
export interface AncestorTreeLine {
|
|
25
|
+
text: string;
|
|
26
|
+
jumpTarget?: string;
|
|
27
|
+
depth: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function renderAncestorTree(node: AncestorTreeNode): AncestorTreeLine[];
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComponentGraphNode } from './composite-closure.js';
|
|
2
|
+
import type { SlotCycle } from './cycle-detection.js';
|
|
3
|
+
export declare function buildCycleUnits(slotCycles: SlotCycle[]): Map<string, Set<string>>;
|
|
4
|
+
export declare function computeCycleAwareAcceptCascade(target: string, components: ComponentGraphNode[], cycleUnits: Map<string, Set<string>>): Set<string>;
|
|
5
|
+
export interface CycleAwareRejectResult {
|
|
6
|
+
toReject: Set<string>;
|
|
7
|
+
toDeselect: Set<string>;
|
|
8
|
+
cyclePartners: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function computeCycleAwareRejectCascade(target: string, components: ComponentGraphNode[], cycleUnits: Map<string, Set<string>>): CycleAwareRejectResult;
|
|
11
|
+
export declare function collectReachableCycleUnits(seeds: string[], components: ComponentGraphNode[], cycleUnits: Map<string, Set<string>>): Set<string>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { findDirectParents } from './lineage.js';
|
|
2
|
+
export function buildCycleUnits(slotCycles) {
|
|
3
|
+
const parent = new Map();
|
|
4
|
+
const find = (x) => {
|
|
5
|
+
let cur = x;
|
|
6
|
+
while (parent.get(cur) !== cur) {
|
|
7
|
+
cur = parent.get(cur);
|
|
8
|
+
}
|
|
9
|
+
let walk = x;
|
|
10
|
+
while (parent.get(walk) !== cur) {
|
|
11
|
+
const next = parent.get(walk);
|
|
12
|
+
parent.set(walk, cur);
|
|
13
|
+
walk = next;
|
|
14
|
+
}
|
|
15
|
+
return cur;
|
|
16
|
+
};
|
|
17
|
+
const union = (a, b) => {
|
|
18
|
+
const ra = find(a);
|
|
19
|
+
const rb = find(b);
|
|
20
|
+
if (ra !== rb)
|
|
21
|
+
parent.set(ra, rb);
|
|
22
|
+
};
|
|
23
|
+
for (const cycle of slotCycles) {
|
|
24
|
+
for (const node of cycle.path) {
|
|
25
|
+
if (!parent.has(node))
|
|
26
|
+
parent.set(node, node);
|
|
27
|
+
}
|
|
28
|
+
// `path` has the first node duplicated at the end; union with path[0] is
|
|
29
|
+
// enough to link every node in the cycle.
|
|
30
|
+
for (let i = 1; i < cycle.path.length; i++) {
|
|
31
|
+
union(cycle.path[0], cycle.path[i]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const groups = new Map();
|
|
35
|
+
for (const node of parent.keys()) {
|
|
36
|
+
const root = find(node);
|
|
37
|
+
let group = groups.get(root);
|
|
38
|
+
if (!group) {
|
|
39
|
+
group = new Set();
|
|
40
|
+
groups.set(root, group);
|
|
41
|
+
}
|
|
42
|
+
group.add(node);
|
|
43
|
+
}
|
|
44
|
+
const out = new Map();
|
|
45
|
+
for (const group of groups.values()) {
|
|
46
|
+
for (const node of group) {
|
|
47
|
+
out.set(node, group);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function slotTargets(node, byName) {
|
|
53
|
+
const c = byName.get(node);
|
|
54
|
+
if (!c)
|
|
55
|
+
return [];
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const slot of c.slots) {
|
|
59
|
+
for (const target of slot.allowedComponents ?? []) {
|
|
60
|
+
if (!byName.has(target))
|
|
61
|
+
continue;
|
|
62
|
+
if (seen.has(target))
|
|
63
|
+
continue;
|
|
64
|
+
seen.add(target);
|
|
65
|
+
out.push(target);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
export function computeCycleAwareAcceptCascade(target, components, cycleUnits) {
|
|
71
|
+
const byName = new Map(components.map((c) => [c.name, c]));
|
|
72
|
+
const visited = new Set();
|
|
73
|
+
const queue = [];
|
|
74
|
+
const enqueue = (name) => {
|
|
75
|
+
if (visited.has(name))
|
|
76
|
+
return;
|
|
77
|
+
visited.add(name);
|
|
78
|
+
queue.push(name);
|
|
79
|
+
// Cohesion: touching any cycle member pulls its whole unit along.
|
|
80
|
+
const unit = cycleUnits.get(name);
|
|
81
|
+
if (unit) {
|
|
82
|
+
for (const member of unit) {
|
|
83
|
+
if (!visited.has(member)) {
|
|
84
|
+
visited.add(member);
|
|
85
|
+
queue.push(member);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
enqueue(target);
|
|
91
|
+
while (queue.length > 0) {
|
|
92
|
+
const cur = queue.shift();
|
|
93
|
+
for (const next of slotTargets(cur, byName)) {
|
|
94
|
+
enqueue(next);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return visited;
|
|
98
|
+
}
|
|
99
|
+
export function computeCycleAwareRejectCascade(target, components, cycleUnits) {
|
|
100
|
+
const toReject = new Set();
|
|
101
|
+
const targetUnit = cycleUnits.get(target);
|
|
102
|
+
const initialSeeds = targetUnit ? [...targetUnit] : [target];
|
|
103
|
+
for (const seed of initialSeeds)
|
|
104
|
+
toReject.add(seed);
|
|
105
|
+
// Walk ancestors. Whenever a newly-added node happens to be a cycle
|
|
106
|
+
// member, splice in its whole cycle unit and keep walking from each unit
|
|
107
|
+
// member. Terminates because every add is idempotent against `toReject`.
|
|
108
|
+
const stack = [...toReject];
|
|
109
|
+
while (stack.length > 0) {
|
|
110
|
+
const cur = stack.pop();
|
|
111
|
+
for (const { parent } of findDirectParents(cur, components)) {
|
|
112
|
+
if (toReject.has(parent))
|
|
113
|
+
continue;
|
|
114
|
+
toReject.add(parent);
|
|
115
|
+
stack.push(parent);
|
|
116
|
+
const unit = cycleUnits.get(parent);
|
|
117
|
+
if (unit) {
|
|
118
|
+
for (const member of unit) {
|
|
119
|
+
if (!toReject.has(member)) {
|
|
120
|
+
toReject.add(member);
|
|
121
|
+
stack.push(member);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const acceptCascade = computeCycleAwareAcceptCascade(target, components, cycleUnits);
|
|
128
|
+
const toDeselect = new Set();
|
|
129
|
+
for (const n of acceptCascade) {
|
|
130
|
+
if (toReject.has(n))
|
|
131
|
+
continue;
|
|
132
|
+
toDeselect.add(n);
|
|
133
|
+
}
|
|
134
|
+
const cyclePartners = [];
|
|
135
|
+
if (targetUnit) {
|
|
136
|
+
for (const m of targetUnit) {
|
|
137
|
+
if (m !== target)
|
|
138
|
+
cyclePartners.push(m);
|
|
139
|
+
}
|
|
140
|
+
cyclePartners.sort();
|
|
141
|
+
}
|
|
142
|
+
return { toReject, toDeselect, cyclePartners };
|
|
143
|
+
}
|
|
144
|
+
export function collectReachableCycleUnits(seeds, components, cycleUnits) {
|
|
145
|
+
const out = new Set();
|
|
146
|
+
if (cycleUnits.size === 0)
|
|
147
|
+
return out;
|
|
148
|
+
const byName = new Map(components.map((c) => [c.name, c]));
|
|
149
|
+
const visited = new Set();
|
|
150
|
+
const queue = [...seeds];
|
|
151
|
+
while (queue.length > 0) {
|
|
152
|
+
const cur = queue.shift();
|
|
153
|
+
if (visited.has(cur))
|
|
154
|
+
continue;
|
|
155
|
+
visited.add(cur);
|
|
156
|
+
const unit = cycleUnits.get(cur);
|
|
157
|
+
if (unit) {
|
|
158
|
+
for (const member of unit) {
|
|
159
|
+
out.add(member);
|
|
160
|
+
if (!visited.has(member))
|
|
161
|
+
queue.push(member);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const next of slotTargets(cur, byName)) {
|
|
165
|
+
if (!visited.has(next))
|
|
166
|
+
queue.push(next);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ComponentGraphNode } from './composite-closure.js';
|
|
2
|
+
export declare function computeDirectNeighborhood(matches: Iterable<string>, graph: ComponentGraphNode[]): Set<string>;
|
|
3
|
+
export declare function findAllAncestors(target: string, graph: ComponentGraphNode[]): Set<string>;
|