@sigloch/se-engine 1.2.0 → 1.4.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/dist/fix-templates.d.ts +117 -1
- package/dist/fix-templates.js +380 -22
- package/dist/metrics.d.ts +3 -2
- package/dist/metrics.js +4 -2
- package/dist/readiness-compute.d.ts +35 -1
- package/dist/readiness-compute.js +65 -4
- package/dist/rule-apply.js +10 -2
- package/dist/rule-classify.js +4 -13
- package/dist/steer.d.ts +104 -0
- package/dist/steer.js +89 -0
- package/dist/suggest.d.ts +21 -8
- package/dist/suggest.js +100 -23
- package/package.json +3 -3
package/dist/fix-templates.d.ts
CHANGED
|
@@ -1,11 +1,63 @@
|
|
|
1
1
|
import type { OntologyGraph, RuleViolation, TraceType, ElementType } from '@sigloch/contracts/se';
|
|
2
2
|
export interface SuggestedEdit {
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* `'add-trace'` — die Kante `source -type-> target` wird angehängt (ggf. mit
|
|
5
|
+
* `retire`, s. u.).
|
|
6
|
+
*
|
|
7
|
+
* `'merge-nodes'` — CR-GC-444, der Konsolidierungs-Operator: `target`
|
|
8
|
+
* ABSORBIERT `source` (Semantik des gleichnamigen `MutateCommand`), `source`
|
|
9
|
+
* verschwindet. `type` benennt dann die Trace, über die die Kopplung läuft
|
|
10
|
+
* (heute `relation`, der Vertrag `FLOW -relation-> SCHEMA`); die gekoppelten
|
|
11
|
+
* Zusatz-Merges stehen in `merges`.
|
|
12
|
+
*/
|
|
13
|
+
op: 'add-trace' | 'merge-nodes';
|
|
4
14
|
source: string;
|
|
5
15
|
target: string;
|
|
6
16
|
type: TraceType;
|
|
7
17
|
/** Warum genau dieses Ziel — Fundstelle im Elementtext bzw. Eindeutigkeit. */
|
|
8
18
|
rationale: string;
|
|
19
|
+
/**
|
|
20
|
+
* CR-GC-435 — die EINE Kante, die weichen muss, damit der Edit die
|
|
21
|
+
* Kardinalitäts-Obergrenze des Meta-Modells (R-18, zweites Bein) einhält.
|
|
22
|
+
* Fehlt = reines Anhängen. Anwendung IMMER als EIN Batch
|
|
23
|
+
* [delete(retire), add(edit)] durchs Gate — nacheinander wäre der
|
|
24
|
+
* Zwischenzustand je nach Reihenfolge illegal oder unternormiert.
|
|
25
|
+
*/
|
|
26
|
+
retire?: {
|
|
27
|
+
source: string;
|
|
28
|
+
target: string;
|
|
29
|
+
type: TraceType;
|
|
30
|
+
rationale: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* CR-GC-435 — gesetzt NUR beim Umhängen (retire) einer realisierten Quelle
|
|
34
|
+
* (realRef/codeRef): die Allokation speist den Datei→Modul-Resolver
|
|
35
|
+
* (buildModResolver, RC-05/importCoverage) — der Graph-Edit zieht dann
|
|
36
|
+
* Code-Arbeit nach sich. Ohne Realisierung fehlt das Feld (kein Rauschen).
|
|
37
|
+
*/
|
|
38
|
+
codeImpact?: {
|
|
39
|
+
file: string;
|
|
40
|
+
targetModule: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* CR-GC-444 — die GEKOPPELTEN Knoten-Merges, die zusammen mit dem primären
|
|
44
|
+
* Merge (`source` → `target`) in EINEM Batch laufen MÜSSEN, damit der
|
|
45
|
+
* Endzustand die Kardinalitäts-Obergrenzen des Meta-Modells einhält.
|
|
46
|
+
*
|
|
47
|
+
* Die Kopplung ist keine Konvention, sondern Gate-Realität: `FLOW -relation->
|
|
48
|
+
* SCHEMA` ist `1..1` (contracts 10.0.0, drittes R-18-Bein). Werden zwei FLOWs
|
|
49
|
+
* zusammengelegt, trägt der überlebende FLOW zwei SCHEMAs — und wird
|
|
50
|
+
* abgewiesen, solange der SCHEMA-Merge nicht im selben Batch steht (in
|
|
51
|
+
* CR-GC-438 Kill 2 gemessen: 5 FLOWs mit > 1 SCHEMA, Zug tot).
|
|
52
|
+
*
|
|
53
|
+
* Reihenfolge: primärer Merge zuerst, dann diese Liste. Fehlt das Feld, ist
|
|
54
|
+
* keine Kopplung nötig (die Zusammengelegten teilen den Vertrag bereits).
|
|
55
|
+
*/
|
|
56
|
+
merges?: {
|
|
57
|
+
source: string;
|
|
58
|
+
target: string;
|
|
59
|
+
rationale: string;
|
|
60
|
+
}[];
|
|
9
61
|
}
|
|
10
62
|
type Element = OntologyGraph['elements'][number];
|
|
11
63
|
type FixTemplate = (v: RuleViolation, g: OntologyGraph) => SuggestedEdit | null;
|
|
@@ -21,4 +73,68 @@ export declare const FIX_TEMPLATES: Record<string, FixTemplate>;
|
|
|
21
73
|
* Deterministisch; nie der generische applyRule-Trace.
|
|
22
74
|
*/
|
|
23
75
|
export declare function fixFor(v: RuleViolation, g: OntologyGraph): SuggestedEdit | null;
|
|
76
|
+
/**
|
|
77
|
+
* Die Kennung der Konsolidierungs-Vorschläge im `Suggestion.ruleId`-Feld.
|
|
78
|
+
*
|
|
79
|
+
* AUSDRÜCKLICH KEINE contracts-Regel-ID. Der Operator hängt an keiner Violation,
|
|
80
|
+
* weil ein Merge keine Regel repariert — er bewegt die Metrik (Vertrags-
|
|
81
|
+
* konzentration, in CR-GC-438 als dieselbe Größe wie `flowEfficiency` gemessen,
|
|
82
|
+
* r = 0,89). Ein Fund wäre erfunden; die Kennung sagt stattdessen, woher der
|
|
83
|
+
* Vorschlag kommt.
|
|
84
|
+
*/
|
|
85
|
+
export declare const MERGE_OPERATOR_ID = "OP-MERGE";
|
|
86
|
+
/**
|
|
87
|
+
* Ab welcher ND-02-Ähnlichkeit zwei VERSCHIEDENE SCHEMAs als DERSELBE Vertrag
|
|
88
|
+
* gelten (CR-GC-444).
|
|
89
|
+
*
|
|
90
|
+
* Bewusst ND-02s eigene DUPLIKAT-Schwelle (0.85) und nicht die lockere
|
|
91
|
+
* Overlap-Schwelle 0.5 aus AO-D01 (AO-D03 ist mit CR-SM-283 entfallen):
|
|
92
|
+
* ein Merge ist destruktiv, „überlappt"
|
|
93
|
+
* ist nicht „ist derselbe Vertrag". Die Zahl steht hier, weil contracts sie
|
|
94
|
+
* modul-lokal hält (`near-duplicate-rules.ts`) — sie wird NICHT umdefiniert.
|
|
95
|
+
*/
|
|
96
|
+
export declare const MERGE_SIMILARITY_THRESHOLD = 0.85;
|
|
97
|
+
/**
|
|
98
|
+
* Tragen zwei SCHEMA-Knoten denselben Datenvertrag?
|
|
99
|
+
*
|
|
100
|
+
* Genau die Definition, die contracts schon fährt: identischer Knoten = 100 %,
|
|
101
|
+
* sonst ND-02s eigene Rechnung (`similarity.ts`, seit CR-SM-286 dort statt in
|
|
102
|
+
* einer injizierten Matrix). Kein zweites Ähnlichkeitsmaß, keine eigene Formel,
|
|
103
|
+
* kein ND-02-Fork.
|
|
104
|
+
*
|
|
105
|
+
* **CR-SM-289 — der Ähnlichkeits-Zweig ist heute UNERREICHBAR, nicht nur still.**
|
|
106
|
+
* ND-02s `usage_overlap` läuft über die direkten Partner eines SCHEMA, und
|
|
107
|
+
* `FLOW -relation-> SCHEMA [1..1]` ist das einzige Pattern, das SCHEMA berührt:
|
|
108
|
+
* zwei VERSCHIEDENE SCHEMAs hängen an zwei verschiedenen FLOWs, ihre
|
|
109
|
+
* Partnermengen sind disjunkt, `usage_overlap` ist 0. Deckel damit
|
|
110
|
+
* `0,50 + 0,30 = 0,80` gegen die Schwelle `0,85` — gemessen, nicht gerechnet.
|
|
111
|
+
* Bis CR-SM-286 war das ein „ohne Matrix"-Vorbehalt; seit die Injektion weg ist,
|
|
112
|
+
* gilt es immer.
|
|
113
|
+
*
|
|
114
|
+
* Der Zweig BLEIBT: er ist die Sicherung gegen eine echte
|
|
115
|
+
* R-18-Kardinalitätsverletzung und wird scharf, sobald Formel, Schwelle oder
|
|
116
|
+
* SCHEMA-Pattern wandern. `suggest.test.ts` nagelt beide Zahlen fest und schlägt
|
|
117
|
+
* dann an. Das zu reparieren ist eine contracts-Entscheidung, kein Fork hier.
|
|
118
|
+
*/
|
|
119
|
+
export declare function contractSimilarity(g: OntologyGraph, a: string, b: string): number;
|
|
120
|
+
/**
|
|
121
|
+
* Konsolidierungs-Kandidaten: FLOW-Paare, die DENSELBEN Datenvertrag tragen
|
|
122
|
+
* (CR-GC-444, Zielbild „graphcode als Regelkreis", FLOWs 62 → 27).
|
|
123
|
+
*
|
|
124
|
+
* „Derselbe Vertrag" = `contractSimilarity` (identischer SCHEMA-Knoten oder
|
|
125
|
+
* ND-02-Duplikat) — EINE Definition, keine neue Zahl. Ein FLOW ohne oder mit
|
|
126
|
+
* mehr als einem SCHEMA nimmt nicht teil: er ist schon grammatikalisch kaputt
|
|
127
|
+
* und gehört R-18, nicht dem Optimierer.
|
|
128
|
+
*
|
|
129
|
+
* Pro Kandidaten-GRUPPE genau EIN Vorschlag (ein Greedy-Schritt, wie der Rest
|
|
130
|
+
* des Optimierers): der io-stärkste FLOW absorbiert den nächststärksten.
|
|
131
|
+
* Deterministisch — io-Grad absteigend, Tiebreak id aufsteigend.
|
|
132
|
+
*/
|
|
133
|
+
export declare function mergeCandidates(g: OntologyGraph): SuggestedEdit[];
|
|
134
|
+
/**
|
|
135
|
+
* Den Merge-Verbund eines `SuggestedEdit` IN MEMORY anwenden — die Δm-Sonde des
|
|
136
|
+
* Operators (`suggestEdits`). Kein Ersatz fürs Gate: hier wird gemessen, dort
|
|
137
|
+
* geurteilt. Reihenfolge wie beim Batch: primärer Merge zuerst, dann `merges`.
|
|
138
|
+
*/
|
|
139
|
+
export declare function applyMergeEdit(g: OntologyGraph, edit: SuggestedEdit): OntologyGraph;
|
|
24
140
|
export {};
|
package/dist/fix-templates.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Ausgeliefert wird ein Edit nur, wenn ein rule-spezifisches Template ihn aus
|
|
8
8
|
* dem Elementtext DETERMINISTISCH herleiten kann (Fundstelle = Begründung):
|
|
9
9
|
*
|
|
10
|
-
* - CR-R01: CR ohne
|
|
10
|
+
* - CR-R01: CR ohne Umfangsbezug → relation zu einem IM CR-TEXT genannten
|
|
11
11
|
* UC/REQ/FUNC/MOD (Spike: "Relation zu im CR-Text genannten Elementen")
|
|
12
12
|
* - CR-R04: CR ohne FUNC → relation zu einer im CR-Text genannten FUNC
|
|
13
13
|
* - MS-03 : CR ohne Milestone → relation zur im Text genannten MS,
|
|
@@ -23,15 +23,18 @@
|
|
|
23
23
|
* sonst zum einzigen MOD im Graphen (eindeutig ⇒ herleitbar)
|
|
24
24
|
* - R-23 : MOD ohne allozierte FUNC → allocate von der im MOD-TEXT
|
|
25
25
|
* genannten FUNC (kein Eindeutigkeits-Fallback, s. dort)
|
|
26
|
-
* - SC-02
|
|
27
|
-
*
|
|
26
|
+
* - SC-02: fehlende SCHEMA-Nutzung → relation zum im TEXT genannten
|
|
27
|
+
* SCHEMA, sonst zum einzigen SCHEMA
|
|
28
|
+
* - R-18 : NUR das Untergrenzen-Bein (CR-SM-302) → die vom Meta-Modell
|
|
29
|
+
* geforderte Kante zum im TEXT genannten Ziel, sonst zum einzigen
|
|
30
|
+
* (heute `FLOW -relation-> SCHEMA [1..1]`, aus REQUIRED_PATTERNS gelesen)
|
|
28
31
|
*
|
|
29
32
|
* Regeln ohne Template (oder Template ohne Fund) liefern null → die Suggestion
|
|
30
33
|
* bleibt Fund-Ebene (Violation + Richtung + Δm), ohne Edit. Regeln, deren Fix
|
|
31
34
|
* Element-Erzeugung braucht (FCHAIN, REQ-Pre/Postcondition, TEST), sind mit
|
|
32
35
|
* additiven Kanten prinzipiell nicht ausdrückbar — bewusst kein Template.
|
|
33
36
|
*/
|
|
34
|
-
import { isValidTrace } from '@sigloch/contracts/se';
|
|
37
|
+
import { isValidTrace, BOUNDED_PATTERNS, REQUIRED_PATTERNS, maxOccurs, schemaSimilarity } from '@sigloch/contracts/se';
|
|
35
38
|
function byId(g, id) {
|
|
36
39
|
return g.elements.find((e) => e.id === id);
|
|
37
40
|
}
|
|
@@ -39,6 +42,46 @@ function hasTrace(g, source, target, type) {
|
|
|
39
42
|
return g.traces.some((t) => t.source === source && t.target === target && t.type === type);
|
|
40
43
|
}
|
|
41
44
|
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
45
|
+
/** Datei-Bindung einer Quelle — dieselbe Zugriffsfolge wie weightNodes (layer.ts). */
|
|
46
|
+
function realFileOf(e) {
|
|
47
|
+
const el = e;
|
|
48
|
+
return el.realRef?.file ?? el.codeRef?.file ?? el.attributes?.realRef?.file ?? el.attributes?.codeRef?.file;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* CR-GC-435 — das Kardinalitäts-Bewusstsein der Templates. `isValidTrace` prüft
|
|
52
|
+
* die PAAR-Legalität; die MENGEN-Bedingung (BOUNDED_PATTERNS, z.B.
|
|
53
|
+
* `FUNC -allocate-> MOD [0..1]`) sah bis dahin kein Template — es schlug Edits
|
|
54
|
+
* vor, die das Gate über R-18s zweites Bein sicher abweist (der verifizierte
|
|
55
|
+
* Repo-Befund: KEIN Architektur-Vorschlag anwendbar, sobald alloziert ist).
|
|
56
|
+
*
|
|
57
|
+
* Rückgabe: `undefined` = kein retire nötig (Anhängen bleibt Anhängen);
|
|
58
|
+
* eine Kante = GENAU DIE muss weichen, damit der Edit legal wird (Umhängen);
|
|
59
|
+
* `'blocked'` = kein EINZELNES retire macht den Edit legal (die Quelle verletzt
|
|
60
|
+
* die Obergrenze schon jetzt) — dann Option C: kein Edit statt eines sicher
|
|
61
|
+
* scheiternden (der Aufrufer probiert den nächsten Kandidaten bzw. liefert null).
|
|
62
|
+
*/
|
|
63
|
+
function retireFor(g, source, target, type) {
|
|
64
|
+
const pattern = BOUNDED_PATTERNS.find((p) => p.source === source.type && p.target === target.type && p.type === type);
|
|
65
|
+
if (!pattern)
|
|
66
|
+
return undefined;
|
|
67
|
+
const max = maxOccurs(pattern.cardinality);
|
|
68
|
+
const existing = g.traces.filter((t) => t.type === type &&
|
|
69
|
+
t.source === source.id &&
|
|
70
|
+
t.target !== target.id &&
|
|
71
|
+
byId(g, t.target)?.type === pattern.target);
|
|
72
|
+
if (existing.length + 1 <= max)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (existing.length + 1 - max > 1)
|
|
75
|
+
return 'blocked';
|
|
76
|
+
const old = existing[0];
|
|
77
|
+
return {
|
|
78
|
+
source: old.source,
|
|
79
|
+
target: old.target,
|
|
80
|
+
type,
|
|
81
|
+
rationale: `${source.id} trägt bereits ${type} → ${old.target}; das Meta-Modell erlaubt höchstens ` +
|
|
82
|
+
`${max} (${pattern.cardinality}) — diese Kante muss weichen, damit ${target.id} legal wird`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
42
85
|
/**
|
|
43
86
|
* Elemente der gegebenen Typen, deren id ODER name (Wortgrenze, ≥3 Zeichen,
|
|
44
87
|
* case-insensitive) im Text vorkommt. Deterministische Rangfolge:
|
|
@@ -69,7 +112,10 @@ function relationToMentioned(types) {
|
|
|
69
112
|
continue;
|
|
70
113
|
if (hasTrace(g, el.id, target.id, 'relation'))
|
|
71
114
|
continue;
|
|
72
|
-
if (!isValidTrace({
|
|
115
|
+
if (!isValidTrace({
|
|
116
|
+
source: el.type, target: target.type, type: 'relation',
|
|
117
|
+
sourceKinds: el.kinds, targetKinds: target.kinds,
|
|
118
|
+
}))
|
|
73
119
|
continue;
|
|
74
120
|
return {
|
|
75
121
|
op: 'add-trace',
|
|
@@ -109,8 +155,19 @@ function edgeToMentioned(opts) {
|
|
|
109
155
|
const target = opts.direction === 'out' ? other : el;
|
|
110
156
|
if (hasTrace(g, source.id, target.id, opts.traceType))
|
|
111
157
|
continue;
|
|
112
|
-
if (!isValidTrace({
|
|
158
|
+
if (!isValidTrace({
|
|
159
|
+
source: source.type, target: target.type, type: opts.traceType,
|
|
160
|
+
sourceKinds: source.kinds, targetKinds: target.kinds,
|
|
161
|
+
}))
|
|
162
|
+
continue;
|
|
163
|
+
// CR-GC-435: Paar legal ≠ Menge legal — die Kardinalitäts-Obergrenze
|
|
164
|
+
// entscheidet, ob der Edit ein Anhängen (kein retire), ein Umhängen
|
|
165
|
+
// (genau ein retire) oder nicht ausdrückbar ist ('blocked' → nächster
|
|
166
|
+
// Kandidat; Option C statt eines Edits, den das Gate sicher abweist).
|
|
167
|
+
const retire = retireFor(g, source, target, opts.traceType);
|
|
168
|
+
if (retire === 'blocked')
|
|
113
169
|
continue;
|
|
170
|
+
const file = retire ? realFileOf(source) : undefined;
|
|
114
171
|
return {
|
|
115
172
|
op: 'add-trace',
|
|
116
173
|
source: source.id,
|
|
@@ -119,14 +176,17 @@ function edgeToMentioned(opts) {
|
|
|
119
176
|
rationale: mentioned.length > 0
|
|
120
177
|
? `${other.id} ist im Text von ${el.id} genannt`
|
|
121
178
|
: `${other.id} ist das einzige ${other.type} im Graphen`,
|
|
179
|
+
...(retire ? { retire } : {}),
|
|
180
|
+
...(retire && file ? { codeImpact: { file, targetModule: target.id } } : {}),
|
|
122
181
|
};
|
|
123
182
|
}
|
|
124
183
|
return null;
|
|
125
184
|
};
|
|
126
185
|
}
|
|
127
186
|
export const FIX_TEMPLATES = {
|
|
128
|
-
|
|
129
|
-
|
|
187
|
+
// CR-SM-295: SCHEMA gehoert zum Umfang — ein CR, der nur einen Datenvertrag aendert,
|
|
188
|
+
// ist ein vollstaendiger CR. Die Liste ist woertlich SCOPE_TYPES aus cr-quality-rules.
|
|
189
|
+
'CR-R01': relationToMentioned(['UC', 'REQ', 'FUNC', 'MOD', 'SCHEMA']),
|
|
130
190
|
// --- Architektur-Operatoren (CR-SM-241) ------------------------------------
|
|
131
191
|
// Ohne sie hat auf `layer: 'arch'` — graphcodes DEFAULT-Messebene — jeder
|
|
132
192
|
// Template-Edit Δm = 0, weil CR/MS/UC gar nicht im Teilgraphen liegen.
|
|
@@ -138,9 +198,46 @@ export const FIX_TEMPLATES = {
|
|
|
138
198
|
// irgendwohin), „das einzige FUNC" ist keine — ein FUNC darf legitim einem
|
|
139
199
|
// anderen Modul gehören, und dieses hier hätte dann einfach noch keins.
|
|
140
200
|
'R-23': edgeToMentioned({ types: ['FUNC'], traceType: 'allocate', direction: 'in' }),
|
|
141
|
-
//
|
|
201
|
+
// SCHEMA-Nutzung → das im Text genannte SCHEMA, sonst das einzige.
|
|
142
202
|
'SC-02': edgeToMentioned({ types: ['SCHEMA'], traceType: 'relation', direction: 'out', uniqueFallback: true }),
|
|
143
|
-
|
|
203
|
+
/**
|
|
204
|
+
* CR-SM-302 — das UNTERGRENZEN-Bein von R-18, und ausschliesslich dieses.
|
|
205
|
+
*
|
|
206
|
+
* CR-SM-271 hat SC-04 gestrichen und den Fall zur Grammatik gemacht; mit der Regel fiel
|
|
207
|
+
* ihr Template, mit der Begruendung "R-18 meldet zu viele verschiedene Faelle". Der Satz
|
|
208
|
+
* stimmt fuer drei der vier Beine — und genau die drei sind hier ausgeschlossen:
|
|
209
|
+
*
|
|
210
|
+
* illegales Paar · Kardinalitaets-OBERgrenze · compose-Baum
|
|
211
|
+
*
|
|
212
|
+
* Ihr Fix ist ein LOESCHEN, und ein additives Template drueckt das nicht aus. Das vierte
|
|
213
|
+
* Bein ist das Gegenteil: es fehlt genau EINE Kante, deren Typ und Ziel-Elementtyp das
|
|
214
|
+
* Meta-Modell vorgibt. Erkennungsmerkmal ist `candidate_targets` im Kontext — das setzt
|
|
215
|
+
* nur `cardinalityViolations`' Untergrenzen-Schleife (contracts `rules.ts`).
|
|
216
|
+
*
|
|
217
|
+
* Gemessen (CR-SM-302): an einem Graphen IM BAU (graphcodes DIVERGENCE_FIXTURE, 5 FLOWs)
|
|
218
|
+
* sind 3 von 4 Befunden herleitbar — der FLOW-Text nennt sein SCHEMA. An den migrierten
|
|
219
|
+
* Selbstmodellen sind es 0 von 6, aber diese Zahl misst Ueberlebende: ein R-18-Befund ist
|
|
220
|
+
* ein `error`, ein Graph, der sein Gate besteht, hat per Konstruktion keinen.
|
|
221
|
+
*
|
|
222
|
+
* Das Muster kommt aus REQUIRED_PATTERNS, nicht aus einer Konstanten: kommt eine zweite
|
|
223
|
+
* durchgesetzte Untergrenze dazu, traegt dieses Template sie ohne Aenderung.
|
|
224
|
+
*/
|
|
225
|
+
'R-18': (v, g) => {
|
|
226
|
+
if (!Array.isArray(v.context?.candidate_targets))
|
|
227
|
+
return null;
|
|
228
|
+
const el = byId(g, v.element_id);
|
|
229
|
+
if (!el)
|
|
230
|
+
return null;
|
|
231
|
+
const pattern = REQUIRED_PATTERNS.find((p) => p.source === el.type);
|
|
232
|
+
if (!pattern)
|
|
233
|
+
return null;
|
|
234
|
+
return edgeToMentioned({
|
|
235
|
+
types: [pattern.target],
|
|
236
|
+
traceType: pattern.type,
|
|
237
|
+
direction: 'out',
|
|
238
|
+
uniqueFallback: true,
|
|
239
|
+
})(v, g);
|
|
240
|
+
},
|
|
144
241
|
'MS-03': (v, g) => {
|
|
145
242
|
const cr = byId(g, v.element_id);
|
|
146
243
|
if (!cr)
|
|
@@ -150,7 +247,10 @@ export const FIX_TEMPLATES = {
|
|
|
150
247
|
const ms = mentioned[0] ?? (milestones.length === 1 ? milestones[0] : undefined);
|
|
151
248
|
if (!ms || hasTrace(g, cr.id, ms.id, 'relation'))
|
|
152
249
|
return null;
|
|
153
|
-
if (!isValidTrace({
|
|
250
|
+
if (!isValidTrace({
|
|
251
|
+
source: cr.type, target: ms.type, type: 'relation',
|
|
252
|
+
sourceKinds: cr.kinds, targetKinds: ms.kinds,
|
|
253
|
+
}))
|
|
154
254
|
return null;
|
|
155
255
|
return {
|
|
156
256
|
op: 'add-trace',
|
|
@@ -162,23 +262,50 @@ export const FIX_TEMPLATES = {
|
|
|
162
262
|
: `${ms.id} ist der einzige Milestone im Graphen`,
|
|
163
263
|
};
|
|
164
264
|
},
|
|
265
|
+
/**
|
|
266
|
+
* CR-SM-266 D1: der Vorschlag geht an den FLOW, nicht mehr an den UC.
|
|
267
|
+
*
|
|
268
|
+
* Die alte Fassung schlug `ACTOR -io-> UC` vor. Mit dem Wegfall des Patterns waere sie
|
|
269
|
+
* still gestorben — `isValidTrace` haette jeden Kandidaten verworfen, die Schleife immer
|
|
270
|
+
* `null` geliefert, und UC-02 haette einen Fix-Template-Eintrag OHNE Wirkung behalten: ein
|
|
271
|
+
* Vorschlagspfad, der nichts mehr vorschlaegt, ist schlimmer als keiner, weil niemand ihn
|
|
272
|
+
* vermisst.
|
|
273
|
+
*
|
|
274
|
+
* Der tragende Pfad ist jetzt `ACTOR -io-> FLOW`, wobei der FLOW ein Kettenglied des UC
|
|
275
|
+
* speisen muss — sonst ist die Kante zwar gueltig, macht UC-02 aber nicht gruen und der
|
|
276
|
+
* Vorschlag waere eine Kante ins Leere. Deshalb wird der FLOW nicht geraten, sondern aus
|
|
277
|
+
* dem Graphen HERGELEITET: die FLOWs, die in eine FUNC einer FCHAIN dieses UC laufen.
|
|
278
|
+
* Ist keiner da, gibt das Template `null` — der fehlende FLOW ist dann die eigentliche
|
|
279
|
+
* Luecke und gehoert R-31, nicht einer erfundenen Actor-Kante.
|
|
280
|
+
*/
|
|
165
281
|
'UC-02': (v, g) => {
|
|
166
282
|
const uc = byId(g, v.element_id);
|
|
167
283
|
if (!uc)
|
|
168
284
|
return null;
|
|
285
|
+
const chainMembers = new Set(g.traces
|
|
286
|
+
.filter((t) => t.type === 'compose' && t.source === uc.id)
|
|
287
|
+
.flatMap((chain) => g.traces.filter((t) => t.type === 'compose' && t.source === chain.target).map((t) => t.target)));
|
|
288
|
+
const feedingFlows = g.traces
|
|
289
|
+
.filter((t) => t.type === 'io' && chainMembers.has(t.target))
|
|
290
|
+
.map((t) => byId(g, t.source))
|
|
291
|
+
.filter((e) => e?.type === 'FLOW');
|
|
292
|
+
if (feedingFlows.length === 0)
|
|
293
|
+
return null;
|
|
169
294
|
const actors = mentionedElements(`${uc.name} ${uc.description ?? ''}`, g, ['ACTOR']);
|
|
170
295
|
for (const actor of actors) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
296
|
+
for (const flow of feedingFlows) {
|
|
297
|
+
if (hasTrace(g, actor.id, flow.id, 'io'))
|
|
298
|
+
continue;
|
|
299
|
+
if (!isValidTrace({ source: actor.type, target: flow.type, type: 'io' }))
|
|
300
|
+
continue;
|
|
301
|
+
return {
|
|
302
|
+
op: 'add-trace',
|
|
303
|
+
source: actor.id,
|
|
304
|
+
target: flow.id,
|
|
305
|
+
type: 'io',
|
|
306
|
+
rationale: `${actor.id} ist im Text von ${uc.id} genannt, und ${flow.id} speist dessen Wirkkette`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
182
309
|
}
|
|
183
310
|
return null;
|
|
184
311
|
},
|
|
@@ -190,3 +317,234 @@ export const FIX_TEMPLATES = {
|
|
|
190
317
|
export function fixFor(v, g) {
|
|
191
318
|
return FIX_TEMPLATES[v.rule_id]?.(v, g) ?? null;
|
|
192
319
|
}
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// CR-GC-444 — der Konsolidierungs-Operator (Merge)
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
/**
|
|
324
|
+
* Die Kennung der Konsolidierungs-Vorschläge im `Suggestion.ruleId`-Feld.
|
|
325
|
+
*
|
|
326
|
+
* AUSDRÜCKLICH KEINE contracts-Regel-ID. Der Operator hängt an keiner Violation,
|
|
327
|
+
* weil ein Merge keine Regel repariert — er bewegt die Metrik (Vertrags-
|
|
328
|
+
* konzentration, in CR-GC-438 als dieselbe Größe wie `flowEfficiency` gemessen,
|
|
329
|
+
* r = 0,89). Ein Fund wäre erfunden; die Kennung sagt stattdessen, woher der
|
|
330
|
+
* Vorschlag kommt.
|
|
331
|
+
*/
|
|
332
|
+
export const MERGE_OPERATOR_ID = 'OP-MERGE';
|
|
333
|
+
/**
|
|
334
|
+
* Ab welcher ND-02-Ähnlichkeit zwei VERSCHIEDENE SCHEMAs als DERSELBE Vertrag
|
|
335
|
+
* gelten (CR-GC-444).
|
|
336
|
+
*
|
|
337
|
+
* Bewusst ND-02s eigene DUPLIKAT-Schwelle (0.85) und nicht die lockere
|
|
338
|
+
* Overlap-Schwelle 0.5 aus AO-D01 (AO-D03 ist mit CR-SM-283 entfallen):
|
|
339
|
+
* ein Merge ist destruktiv, „überlappt"
|
|
340
|
+
* ist nicht „ist derselbe Vertrag". Die Zahl steht hier, weil contracts sie
|
|
341
|
+
* modul-lokal hält (`near-duplicate-rules.ts`) — sie wird NICHT umdefiniert.
|
|
342
|
+
*/
|
|
343
|
+
export const MERGE_SIMILARITY_THRESHOLD = 0.85;
|
|
344
|
+
/**
|
|
345
|
+
* Tragen zwei SCHEMA-Knoten denselben Datenvertrag?
|
|
346
|
+
*
|
|
347
|
+
* Genau die Definition, die contracts schon fährt: identischer Knoten = 100 %,
|
|
348
|
+
* sonst ND-02s eigene Rechnung (`similarity.ts`, seit CR-SM-286 dort statt in
|
|
349
|
+
* einer injizierten Matrix). Kein zweites Ähnlichkeitsmaß, keine eigene Formel,
|
|
350
|
+
* kein ND-02-Fork.
|
|
351
|
+
*
|
|
352
|
+
* **CR-SM-289 — der Ähnlichkeits-Zweig ist heute UNERREICHBAR, nicht nur still.**
|
|
353
|
+
* ND-02s `usage_overlap` läuft über die direkten Partner eines SCHEMA, und
|
|
354
|
+
* `FLOW -relation-> SCHEMA [1..1]` ist das einzige Pattern, das SCHEMA berührt:
|
|
355
|
+
* zwei VERSCHIEDENE SCHEMAs hängen an zwei verschiedenen FLOWs, ihre
|
|
356
|
+
* Partnermengen sind disjunkt, `usage_overlap` ist 0. Deckel damit
|
|
357
|
+
* `0,50 + 0,30 = 0,80` gegen die Schwelle `0,85` — gemessen, nicht gerechnet.
|
|
358
|
+
* Bis CR-SM-286 war das ein „ohne Matrix"-Vorbehalt; seit die Injektion weg ist,
|
|
359
|
+
* gilt es immer.
|
|
360
|
+
*
|
|
361
|
+
* Der Zweig BLEIBT: er ist die Sicherung gegen eine echte
|
|
362
|
+
* R-18-Kardinalitätsverletzung und wird scharf, sobald Formel, Schwelle oder
|
|
363
|
+
* SCHEMA-Pattern wandern. `suggest.test.ts` nagelt beide Zahlen fest und schlägt
|
|
364
|
+
* dann an. Das zu reparieren ist eine contracts-Entscheidung, kein Fork hier.
|
|
365
|
+
*/
|
|
366
|
+
export function contractSimilarity(g, a, b) {
|
|
367
|
+
if (a === b)
|
|
368
|
+
return 1;
|
|
369
|
+
// CR-SM-286: der Graph ist jetzt Parameter. Vorher las die Funktion den contracts-Modulzustand
|
|
370
|
+
// und gab ohne Injektion 0 — also "nicht aehnlich" statt "nicht beurteilbar", und das an einer
|
|
371
|
+
// Stelle, die ueber einen destruktiven Merge entscheidet.
|
|
372
|
+
const sim = schemaSimilarity(g);
|
|
373
|
+
const i = sim.ids.indexOf(a);
|
|
374
|
+
const j = sim.ids.indexOf(b);
|
|
375
|
+
if (i < 0 || j < 0)
|
|
376
|
+
return 0;
|
|
377
|
+
return sim.matrix[i]?.[j] ?? 0;
|
|
378
|
+
}
|
|
379
|
+
/** Eindeutige Ziele einer Kante `id -type-> <targetType>`, in Graph-Reihenfolge. */
|
|
380
|
+
function outTargets(g, id, type, targetType) {
|
|
381
|
+
const out = [];
|
|
382
|
+
for (const t of g.traces) {
|
|
383
|
+
if (t.type !== type || t.source !== id)
|
|
384
|
+
continue;
|
|
385
|
+
if (byId(g, t.target)?.type !== targetType)
|
|
386
|
+
continue;
|
|
387
|
+
if (!out.includes(t.target))
|
|
388
|
+
out.push(t.target);
|
|
389
|
+
}
|
|
390
|
+
return out;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Die Merges, die MITLAUFEN müssen, wenn `drop` in `keep` aufgeht — hergeleitet
|
|
394
|
+
* aus der GRAMMATIK, nicht konventioniert.
|
|
395
|
+
*
|
|
396
|
+
* Für jedes `BOUNDED_PATTERNS`-Muster, dessen QUELLE der gemergte Typ ist
|
|
397
|
+
* (Kardinalität zählt ausgehend, wie R-18s zweites Bein), wird geprüft, ob die
|
|
398
|
+
* Vereinigung der Ziele von `keep` und `drop` die Obergrenze reißt. Genau dann
|
|
399
|
+
* fällt ein gekoppelter Merge an. Heute trifft das exakt einen Fall:
|
|
400
|
+
* `FLOW -relation-> SCHEMA [1..1]`.
|
|
401
|
+
*
|
|
402
|
+
* `null` = nicht eindeutig auflösbar ⇒ KEIN Vorschlag (Option C aus CR-GC-435:
|
|
403
|
+
* lieber kein Edit als einer, den das Gate sicher abweist). Das gilt, wenn die
|
|
404
|
+
* Obergrenze > 1 ist, wenn `keep` selbst schon überzählt, oder wenn die weichenden
|
|
405
|
+
* Ziele nicht nachweislich derselbe Vertrag sind.
|
|
406
|
+
*/
|
|
407
|
+
function coupledMerges(g, drop, keep) {
|
|
408
|
+
const out = [];
|
|
409
|
+
for (const p of BOUNDED_PATTERNS) {
|
|
410
|
+
if (p.source !== drop.type || p.target === '*')
|
|
411
|
+
continue;
|
|
412
|
+
const targetType = p.target;
|
|
413
|
+
const keepTargets = outTargets(g, keep.id, p.type, targetType);
|
|
414
|
+
const dropTargets = outTargets(g, drop.id, p.type, targetType);
|
|
415
|
+
const union = [...keepTargets, ...dropTargets.filter((t) => !keepTargets.includes(t))];
|
|
416
|
+
if (union.length <= maxOccurs(p.cardinality))
|
|
417
|
+
continue;
|
|
418
|
+
if (maxOccurs(p.cardinality) !== 1 || keepTargets.length !== 1)
|
|
419
|
+
return null;
|
|
420
|
+
const survivor = keepTargets[0];
|
|
421
|
+
for (const extra of union) {
|
|
422
|
+
if (extra === survivor)
|
|
423
|
+
continue;
|
|
424
|
+
// Nur SCHEMA lässt sich hier belegen — für alles andere fehlt der Nachweis,
|
|
425
|
+
// dass die beiden Ziele dasselbe sind, also gibt es keinen Vorschlag.
|
|
426
|
+
if (targetType !== 'SCHEMA' || contractSimilarity(g, extra, survivor) < MERGE_SIMILARITY_THRESHOLD)
|
|
427
|
+
return null;
|
|
428
|
+
out.push({
|
|
429
|
+
source: extra,
|
|
430
|
+
target: survivor,
|
|
431
|
+
rationale: `${keep.id} trüge nach dem Merge ${union.length} ${targetType} über ${p.type}, ` +
|
|
432
|
+
`die Grammatik erlaubt ${p.cardinality} — ${extra} muss im SELBEN Batch in ${survivor} aufgehen`,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return out;
|
|
437
|
+
}
|
|
438
|
+
/** io-Grad eines Knotens — der besser verdrahtete Vertrag überlebt den Merge. */
|
|
439
|
+
function ioDegree(g, id) {
|
|
440
|
+
return g.traces.filter((t) => t.type === 'io' && (t.source === id || t.target === id)).length;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Konsolidierungs-Kandidaten: FLOW-Paare, die DENSELBEN Datenvertrag tragen
|
|
444
|
+
* (CR-GC-444, Zielbild „graphcode als Regelkreis", FLOWs 62 → 27).
|
|
445
|
+
*
|
|
446
|
+
* „Derselbe Vertrag" = `contractSimilarity` (identischer SCHEMA-Knoten oder
|
|
447
|
+
* ND-02-Duplikat) — EINE Definition, keine neue Zahl. Ein FLOW ohne oder mit
|
|
448
|
+
* mehr als einem SCHEMA nimmt nicht teil: er ist schon grammatikalisch kaputt
|
|
449
|
+
* und gehört R-18, nicht dem Optimierer.
|
|
450
|
+
*
|
|
451
|
+
* Pro Kandidaten-GRUPPE genau EIN Vorschlag (ein Greedy-Schritt, wie der Rest
|
|
452
|
+
* des Optimierers): der io-stärkste FLOW absorbiert den nächststärksten.
|
|
453
|
+
* Deterministisch — io-Grad absteigend, Tiebreak id aufsteigend.
|
|
454
|
+
*/
|
|
455
|
+
export function mergeCandidates(g) {
|
|
456
|
+
const flows = g.elements.filter((e) => e.type === 'FLOW');
|
|
457
|
+
// Gruppen zunächst nach identischem SCHEMA-Knoten …
|
|
458
|
+
const bySchema = new Map();
|
|
459
|
+
for (const f of flows) {
|
|
460
|
+
const schemas = outTargets(g, f.id, 'relation', 'SCHEMA');
|
|
461
|
+
if (schemas.length !== 1)
|
|
462
|
+
continue;
|
|
463
|
+
const group = bySchema.get(schemas[0]);
|
|
464
|
+
if (group)
|
|
465
|
+
group.push(f);
|
|
466
|
+
else
|
|
467
|
+
bySchema.set(schemas[0], [f]);
|
|
468
|
+
}
|
|
469
|
+
// … dann die Gruppen verschmelzen, deren SCHEMAs ND-02-Duplikate sind.
|
|
470
|
+
const schemaIds = [...bySchema.keys()].sort();
|
|
471
|
+
const groupOf = new Map(schemaIds.map((s) => [s, s]));
|
|
472
|
+
const root = (s) => {
|
|
473
|
+
let x = s;
|
|
474
|
+
for (let n = 0; n < 50 && groupOf.get(x) !== x; n += 1)
|
|
475
|
+
x = groupOf.get(x);
|
|
476
|
+
return x;
|
|
477
|
+
};
|
|
478
|
+
for (let i = 0; i < schemaIds.length; i += 1) {
|
|
479
|
+
for (let j = i + 1; j < schemaIds.length; j += 1) {
|
|
480
|
+
if (contractSimilarity(g, schemaIds[i], schemaIds[j]) < MERGE_SIMILARITY_THRESHOLD)
|
|
481
|
+
continue;
|
|
482
|
+
const a = root(schemaIds[i]);
|
|
483
|
+
const b = root(schemaIds[j]);
|
|
484
|
+
if (a !== b)
|
|
485
|
+
groupOf.set(b, a);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const grouped = new Map();
|
|
489
|
+
for (const s of schemaIds) {
|
|
490
|
+
const key = root(s);
|
|
491
|
+
grouped.set(key, [...(grouped.get(key) ?? []), ...bySchema.get(s)]);
|
|
492
|
+
}
|
|
493
|
+
const edits = [];
|
|
494
|
+
for (const key of [...grouped.keys()].sort()) {
|
|
495
|
+
const members = grouped.get(key)
|
|
496
|
+
.slice()
|
|
497
|
+
.sort((a, b) => ioDegree(g, b.id) - ioDegree(g, a.id) || a.id.localeCompare(b.id));
|
|
498
|
+
if (members.length < 2)
|
|
499
|
+
continue;
|
|
500
|
+
const [keep, drop] = members;
|
|
501
|
+
const merges = coupledMerges(g, drop, keep);
|
|
502
|
+
if (merges === null)
|
|
503
|
+
continue;
|
|
504
|
+
const keepSchema = outTargets(g, keep.id, 'relation', 'SCHEMA')[0];
|
|
505
|
+
const dropSchema = outTargets(g, drop.id, 'relation', 'SCHEMA')[0];
|
|
506
|
+
edits.push({
|
|
507
|
+
op: 'merge-nodes',
|
|
508
|
+
source: drop.id,
|
|
509
|
+
target: keep.id,
|
|
510
|
+
type: 'relation',
|
|
511
|
+
rationale: keepSchema === dropSchema
|
|
512
|
+
? `${drop.id} und ${keep.id} tragen denselben Vertrag ${keepSchema} — ${keep.id} absorbiert ${drop.id}`
|
|
513
|
+
: `${drop.id} und ${keep.id} tragen mit ${dropSchema} / ${keepSchema} ein ND-02-Duplikat ` +
|
|
514
|
+
`(${Math.round(contractSimilarity(g, dropSchema, keepSchema) * 100)} %) — ${keep.id} absorbiert ${drop.id}`,
|
|
515
|
+
...(merges.length ? { merges } : {}),
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
return edits;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Den Merge-Verbund eines `SuggestedEdit` IN MEMORY anwenden — die Δm-Sonde des
|
|
522
|
+
* Operators (`suggestEdits`). Kein Ersatz fürs Gate: hier wird gemessen, dort
|
|
523
|
+
* geurteilt. Reihenfolge wie beim Batch: primärer Merge zuerst, dann `merges`.
|
|
524
|
+
*/
|
|
525
|
+
export function applyMergeEdit(g, edit) {
|
|
526
|
+
const absorbedBy = new Map([
|
|
527
|
+
[edit.source, edit.target],
|
|
528
|
+
...(edit.merges ?? []).map((m) => [m.source, m.target]),
|
|
529
|
+
]);
|
|
530
|
+
const resolve = (id) => {
|
|
531
|
+
let x = id;
|
|
532
|
+
for (let n = 0; n < 50 && absorbedBy.has(x); n += 1)
|
|
533
|
+
x = absorbedBy.get(x);
|
|
534
|
+
return x;
|
|
535
|
+
};
|
|
536
|
+
const seen = new Set();
|
|
537
|
+
const traces = [];
|
|
538
|
+
for (const t of g.traces) {
|
|
539
|
+
const source = resolve(t.source);
|
|
540
|
+
const target = resolve(t.target);
|
|
541
|
+
if (source === target)
|
|
542
|
+
continue;
|
|
543
|
+
const key = `${source}>${t.type}>${target}`;
|
|
544
|
+
if (seen.has(key))
|
|
545
|
+
continue;
|
|
546
|
+
seen.add(key);
|
|
547
|
+
traces.push({ ...t, source, target });
|
|
548
|
+
}
|
|
549
|
+
return { ...g, elements: g.elements.filter((e) => !absorbedBy.has(e.id)), traces };
|
|
550
|
+
}
|
package/dist/metrics.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export declare function metrics(graph: OntologyGraph, opts?: MetricsOptions): Me
|
|
|
48
48
|
export { projectLayer, weightNodes, ARCH_TYPES, type MetricLayer } from './layer.js';
|
|
49
49
|
export { CLASS_MAP, classOf, classifyAll, classificationStats, type RuleClass, type Classification, type ClassifiedRule, type ClassificationStats, } from './rule-classify.js';
|
|
50
50
|
export { applyRule, markEmptyDelta, type ApplyResult, type DeltaMark } from './rule-apply.js';
|
|
51
|
-
export {
|
|
52
|
-
export {
|
|
51
|
+
export { suggestEdits, type Suggestion, type SuggestOptions } from './suggest.js';
|
|
52
|
+
export { steerScore, beatsBySteer, STEER_RULES, EPS_AUGMENT, type SteerScore, type SteerCandidate } from './steer.js';
|
|
53
|
+
export { fixFor, mentionedElements, FIX_TEMPLATES, mergeCandidates, applyMergeEdit, contractSimilarity, MERGE_OPERATOR_ID, MERGE_SIMILARITY_THRESHOLD, type SuggestedEdit, } from './fix-templates.js';
|
|
53
54
|
export { buildAdjacency, betweenness, maxBetweenness, detectCommunities, modularityOf, modularityQ, redundancyDensity, intraEdgeFraction, components, componentSizes, sourceSinkPaths, type Adjacency, } from './topology.js';
|
package/dist/metrics.js
CHANGED
|
@@ -92,6 +92,8 @@ export function metrics(graph, opts = {}) {
|
|
|
92
92
|
export { projectLayer, weightNodes, ARCH_TYPES } from './layer.js';
|
|
93
93
|
export { CLASS_MAP, classOf, classifyAll, classificationStats, } from './rule-classify.js';
|
|
94
94
|
export { applyRule, markEmptyDelta } from './rule-apply.js';
|
|
95
|
-
export {
|
|
96
|
-
|
|
95
|
+
export { suggestEdits } from './suggest.js';
|
|
96
|
+
// CR-SM-292: der Ranking-Score. `targetFor` ist mit dem ℝ⁶-Ranking ersatzlos entfallen.
|
|
97
|
+
export { steerScore, beatsBySteer, STEER_RULES, EPS_AUGMENT } from './steer.js';
|
|
98
|
+
export { fixFor, mentionedElements, FIX_TEMPLATES, mergeCandidates, applyMergeEdit, contractSimilarity, MERGE_OPERATOR_ID, MERGE_SIMILARITY_THRESHOLD, } from './fix-templates.js';
|
|
97
99
|
export { buildAdjacency, betweenness, maxBetweenness, detectCommunities, modularityOf, modularityQ, redundancyDensity, intraEdgeFraction, components, componentSizes, sourceSinkPaths, } from './topology.js';
|