@sigloch/contracts 3.2.0 → 3.3.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/se/metric-rules.d.ts +34 -0
- package/dist/se/metric-rules.js +180 -162
- package/package.json +1 -1
|
@@ -52,6 +52,40 @@ export interface AllocationCohesion {
|
|
|
52
52
|
* Validation of this metric — and of MT-01/MT-02, which are thresholded the same way —
|
|
53
53
|
* is deferred (CR-SM-223).
|
|
54
54
|
*/
|
|
55
|
+
/** Per-module architecture metrics — the numbers behind MT-01/MT-02 (CR-SM-232). */
|
|
56
|
+
export interface ModuleMetrics {
|
|
57
|
+
moduleId: string;
|
|
58
|
+
moduleName: string;
|
|
59
|
+
/** FUNCs allocated to this module (`FUNC —allocate→ MOD`). */
|
|
60
|
+
allocatedFuncs: number;
|
|
61
|
+
/** MT-01 core: coupling. `instability = fanOut / (fanIn + fanOut)`. */
|
|
62
|
+
fanIn: number;
|
|
63
|
+
fanOut: number;
|
|
64
|
+
/** null when fanIn + fanOut === 0 — no signal, no substitute value. */
|
|
65
|
+
instability: number | null;
|
|
66
|
+
/** MT-02 core: connected-component count. null below 2 allocated FUNCs (not measurable). */
|
|
67
|
+
lcom4: number | null;
|
|
68
|
+
/** The CR-SM-223 measurement, deliberately threshold-free. null where contracts omits it. */
|
|
69
|
+
cohesion: {
|
|
70
|
+
internal: number;
|
|
71
|
+
external: number;
|
|
72
|
+
ratio: number;
|
|
73
|
+
} | null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Per-module architecture metrics as NUMBERS — one row per MOD, threshold or not
|
|
77
|
+
* (CR-SM-232).
|
|
78
|
+
*
|
|
79
|
+
* MT-01 only ever reported the modules above 70 %, MT-02 only those with ≥ 4
|
|
80
|
+
* components, and both only inside a prose `message`. For every module below the
|
|
81
|
+
* threshold there was no value, there was nothing — so a trend ("was 62 %, is 68 %"),
|
|
82
|
+
* the actual steering signal, was unobtainable. This exports what the rules already
|
|
83
|
+
* compute; it invents no metric and calibrates no threshold.
|
|
84
|
+
*
|
|
85
|
+
* Sorted worst cohesion first (as `allocationCohesion` does — the ranking IS the
|
|
86
|
+
* signal); modules without a cohesion measurement follow, stable by `moduleId`.
|
|
87
|
+
*/
|
|
88
|
+
export declare function moduleMetrics(graph: OntologyGraph): ModuleMetrics[];
|
|
55
89
|
export declare function allocationCohesion(graph: OntologyGraph): AllocationCohesion[];
|
|
56
90
|
export declare const MT_RULES: readonly [{
|
|
57
91
|
readonly id: "MT-01";
|
package/dist/se/metric-rules.js
CHANGED
|
@@ -8,39 +8,18 @@ const INSTABILITY_THRESHOLD = 0.7;
|
|
|
8
8
|
*/
|
|
9
9
|
export function mt01Instability(graph) {
|
|
10
10
|
const violations = [];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
.map(t => t.source));
|
|
17
|
-
// Direct MOD-level traces (MOD→MOD io/compose)
|
|
18
|
-
const directOut = graph.traces.filter(t => t.source === mod.id && (t.type === 'io' || t.type === 'compose') && t.target !== mod.id).length;
|
|
19
|
-
const directIn = graph.traces.filter(t => t.target === mod.id && (t.type === 'io' || t.type === 'compose' || t.type === 'allocate')).length;
|
|
20
|
-
// Indirect: traces from module FUNCs to outside elements (and vice versa)
|
|
21
|
-
let indirectOut = 0;
|
|
22
|
-
let indirectIn = 0;
|
|
23
|
-
for (const t of graph.traces) {
|
|
24
|
-
if (t.type === 'allocate')
|
|
25
|
-
continue; // allocate itself doesn't count as coupling
|
|
26
|
-
if (modFuncIds.has(t.source) && !modFuncIds.has(t.target) && t.target !== mod.id) {
|
|
27
|
-
indirectOut++;
|
|
28
|
-
}
|
|
29
|
-
if (modFuncIds.has(t.target) && !modFuncIds.has(t.source) && t.source !== mod.id) {
|
|
30
|
-
indirectIn++;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const fanOut = directOut + indirectOut;
|
|
34
|
-
const fanIn = directIn + indirectIn;
|
|
35
|
-
if (fanIn + fanOut === 0)
|
|
11
|
+
// CR-SM-232: EINE Rechnung, zwei Ausgaben. Die fan_in/fan_out-Formel steht in
|
|
12
|
+
// `measureModules` — hier wird nur noch geschwellt. Sonst stünde die Formel
|
|
13
|
+
// doppelt im selben File und der Export wäre eine zweite Implementierung.
|
|
14
|
+
for (const m of measureModules(graph)) {
|
|
15
|
+
if (m.instability === null)
|
|
36
16
|
continue;
|
|
37
|
-
|
|
38
|
-
if (instability > INSTABILITY_THRESHOLD) {
|
|
17
|
+
if (m.instability > INSTABILITY_THRESHOLD) {
|
|
39
18
|
violations.push({
|
|
40
19
|
rule_id: 'MT-01',
|
|
41
20
|
severity: 'warning',
|
|
42
|
-
element_id:
|
|
43
|
-
message: `${
|
|
21
|
+
element_id: m.moduleId,
|
|
22
|
+
message: `${m.moduleName} has instability ${Math.round(m.instability * 100)}% (>${INSTABILITY_THRESHOLD * 100}%). fan_in=${m.fanIn}, fan_out=${m.fanOut}`,
|
|
44
23
|
});
|
|
45
24
|
}
|
|
46
25
|
}
|
|
@@ -53,99 +32,16 @@ export function mt01Instability(graph) {
|
|
|
53
32
|
*/
|
|
54
33
|
export function mt02Lcom4(graph) {
|
|
55
34
|
const violations = [];
|
|
56
|
-
|
|
57
|
-
for (const
|
|
58
|
-
|
|
59
|
-
const allocTraces = graph.traces.filter(t => t.type === 'allocate' && t.target === mod.id);
|
|
60
|
-
const funcIds = allocTraces.map(t => t.source);
|
|
61
|
-
if (funcIds.length < 2)
|
|
35
|
+
// CR-SM-232: die Union-Find-Rechnung steht in `measureModules`; hier nur die Stufen.
|
|
36
|
+
for (const m of measureModules(graph)) {
|
|
37
|
+
if (m.lcom4 === null)
|
|
62
38
|
continue;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const targets = new Set();
|
|
67
|
-
for (const t of graph.traces) {
|
|
68
|
-
if (t.source === fid && (t.type === 'io' || t.type === 'satisfy')) {
|
|
69
|
-
targets.add(t.target);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
funcTargets.set(fid, targets);
|
|
39
|
+
const message = `${m.moduleName} has LCOM4=${m.lcom4} (${m.allocatedFuncs} FUNCs in ${m.lcom4} disconnected groups)`;
|
|
40
|
+
if (m.lcom4 >= 4 && m.lcom4 <= 5) {
|
|
41
|
+
violations.push({ rule_id: 'MT-02', severity: 'info', element_id: m.moduleId, message });
|
|
73
42
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
for (const fid of funcIds)
|
|
77
|
-
parent.set(fid, fid);
|
|
78
|
-
function find(x) {
|
|
79
|
-
while (parent.get(x) !== x) {
|
|
80
|
-
x = parent.get(x);
|
|
81
|
-
}
|
|
82
|
-
return x;
|
|
83
|
-
}
|
|
84
|
-
function union(a, b) {
|
|
85
|
-
const ra = find(a), rb = find(b);
|
|
86
|
-
if (ra !== rb)
|
|
87
|
-
parent.set(ra, rb);
|
|
88
|
-
}
|
|
89
|
-
for (let i = 0; i < funcIds.length; i++) {
|
|
90
|
-
for (let j = i + 1; j < funcIds.length; j++) {
|
|
91
|
-
const ti = funcTargets.get(funcIds[i]);
|
|
92
|
-
const tj = funcTargets.get(funcIds[j]);
|
|
93
|
-
// Check if they share any target
|
|
94
|
-
for (const t of ti) {
|
|
95
|
-
if (tj.has(t)) {
|
|
96
|
-
union(funcIds[i], funcIds[j]);
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// CR-165/CR-171: FUNCs sharing the same FLOW (via io in either direction) are connected
|
|
103
|
-
const funcIdSet = new Set(funcIds);
|
|
104
|
-
const flowToFuncs = new Map();
|
|
105
|
-
for (const t of graph.traces) {
|
|
106
|
-
if (t.type !== 'io')
|
|
107
|
-
continue;
|
|
108
|
-
// FLOW→FUNC direction
|
|
109
|
-
if (funcIdSet.has(t.target)) {
|
|
110
|
-
const srcEl = graph.elements.find(e => e.id === t.source);
|
|
111
|
-
if (srcEl?.type === 'FLOW') {
|
|
112
|
-
if (!flowToFuncs.has(t.source))
|
|
113
|
-
flowToFuncs.set(t.source, new Set());
|
|
114
|
-
flowToFuncs.get(t.source).add(t.target);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
// FUNC→FLOW direction (CR-171)
|
|
118
|
-
if (funcIdSet.has(t.source)) {
|
|
119
|
-
const tgtEl = graph.elements.find(e => e.id === t.target);
|
|
120
|
-
if (tgtEl?.type === 'FLOW') {
|
|
121
|
-
if (!flowToFuncs.has(t.target))
|
|
122
|
-
flowToFuncs.set(t.target, new Set());
|
|
123
|
-
flowToFuncs.get(t.target).add(t.source);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
for (const funcsInFlow of flowToFuncs.values()) {
|
|
128
|
-
const arr = [...funcsInFlow];
|
|
129
|
-
for (let i = 1; i < arr.length; i++)
|
|
130
|
-
union(arr[0], arr[i]);
|
|
131
|
-
}
|
|
132
|
-
const roots = new Set(funcIds.map(find));
|
|
133
|
-
const lcom4 = roots.size;
|
|
134
|
-
if (lcom4 >= 4 && lcom4 <= 5) {
|
|
135
|
-
violations.push({
|
|
136
|
-
rule_id: 'MT-02',
|
|
137
|
-
severity: 'info',
|
|
138
|
-
element_id: mod.id,
|
|
139
|
-
message: `${mod.name} has LCOM4=${lcom4} (${funcIds.length} FUNCs in ${lcom4} disconnected groups)`,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
else if (lcom4 > 5) {
|
|
143
|
-
violations.push({
|
|
144
|
-
rule_id: 'MT-02',
|
|
145
|
-
severity: 'warning',
|
|
146
|
-
element_id: mod.id,
|
|
147
|
-
message: `${mod.name} has LCOM4=${lcom4} (${funcIds.length} FUNCs in ${lcom4} disconnected groups)`,
|
|
148
|
-
});
|
|
43
|
+
else if (m.lcom4 > 5) {
|
|
44
|
+
violations.push({ rule_id: 'MT-02', severity: 'warning', element_id: m.moduleId, message });
|
|
149
45
|
}
|
|
150
46
|
}
|
|
151
47
|
return violations;
|
|
@@ -204,59 +100,181 @@ function connectionPairs(graph) {
|
|
|
204
100
|
return pairs;
|
|
205
101
|
}
|
|
206
102
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* It used to be MT-03 with an 80 % threshold, and it fired on nearly every module of
|
|
210
|
-
* every real graph: 6 of 7 on graphcode, 4 of 4 on graph-view-edit, 10 of 11 on the
|
|
211
|
-
* family graph. `CR-SM-221` first suspected the edge definition and made it
|
|
212
|
-
* FLOW-transitive — the hit rate did not move. The threshold was the miscalibration:
|
|
213
|
-
* in a flow-routed layered architecture, module interaction crosses boundaries by
|
|
214
|
-
* design, so "80 % of interaction is internal" describes a monolith, not a healthy
|
|
215
|
-
* module.
|
|
103
|
+
* The one per-module computation (CR-SM-232) — in GRAPH ORDER, unsorted.
|
|
216
104
|
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
105
|
+
* `mt01Instability` and `mt02Lcom4` consume this and only threshold; `moduleMetrics()`
|
|
106
|
+
* exports a sorted copy. Keeping the internal list in `graph.elements` order is what
|
|
107
|
+
* makes the violation ORDER of both rules byte-identical to the pre-CR implementation
|
|
108
|
+
* — the export's worst-first ranking must not leak into the rule output.
|
|
221
109
|
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* the `alloc` score permanently. A measurement must not masquerade as a defect.
|
|
225
|
-
*
|
|
226
|
-
* Validation of this metric — and of MT-01/MT-02, which are thresholded the same way —
|
|
227
|
-
* is deferred (CR-SM-223).
|
|
110
|
+
* Every value that is not measurable is `null`, never 0: a module with one allocated
|
|
111
|
+
* FUNC has no LCOM4, and a 1 there would be an invented statement about cohesion.
|
|
228
112
|
*/
|
|
229
|
-
|
|
230
|
-
const measurements = [];
|
|
113
|
+
function measureModules(graph) {
|
|
231
114
|
const mods = graph.elements.filter(e => e.type === 'MOD');
|
|
232
115
|
const pairs = [...connectionPairs(graph)].map(p => p.split('|'));
|
|
116
|
+
const rows = [];
|
|
233
117
|
for (const mod of mods) {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
118
|
+
const allocated = graph.traces
|
|
119
|
+
.filter(t => t.type === 'allocate' && t.target === mod.id)
|
|
120
|
+
.map(t => t.source);
|
|
121
|
+
const modFuncIds = new Set(allocated);
|
|
122
|
+
// --- MT-01: fan-in / fan-out (CR-165, indirect via the allocate path) ---
|
|
123
|
+
const directOut = graph.traces.filter(t => t.source === mod.id && (t.type === 'io' || t.type === 'compose') && t.target !== mod.id).length;
|
|
124
|
+
const directIn = graph.traces.filter(t => t.target === mod.id && (t.type === 'io' || t.type === 'compose' || t.type === 'allocate')).length;
|
|
125
|
+
let indirectOut = 0;
|
|
126
|
+
let indirectIn = 0;
|
|
127
|
+
for (const t of graph.traces) {
|
|
128
|
+
if (t.type === 'allocate')
|
|
129
|
+
continue; // allocate itself doesn't count as coupling
|
|
130
|
+
if (modFuncIds.has(t.source) && !modFuncIds.has(t.target) && t.target !== mod.id)
|
|
131
|
+
indirectOut++;
|
|
132
|
+
if (modFuncIds.has(t.target) && !modFuncIds.has(t.source) && t.source !== mod.id)
|
|
133
|
+
indirectIn++;
|
|
246
134
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
135
|
+
const fanOut = directOut + indirectOut;
|
|
136
|
+
const fanIn = directIn + indirectIn;
|
|
137
|
+
const instability = fanIn + fanOut === 0 ? null : fanOut / (fanIn + fanOut);
|
|
138
|
+
rows.push({
|
|
251
139
|
moduleId: mod.id,
|
|
252
140
|
moduleName: mod.name,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
141
|
+
allocatedFuncs: allocated.length,
|
|
142
|
+
fanIn,
|
|
143
|
+
fanOut,
|
|
144
|
+
instability,
|
|
145
|
+
lcom4: lcom4Of(graph, allocated),
|
|
146
|
+
cohesion: cohesionOf(pairs, modFuncIds),
|
|
256
147
|
});
|
|
257
148
|
}
|
|
258
|
-
|
|
259
|
-
|
|
149
|
+
return rows;
|
|
150
|
+
}
|
|
151
|
+
/** MT-02 core: connected components over the allocated FUNCs. null below 2 FUNCs. */
|
|
152
|
+
function lcom4Of(graph, funcIds) {
|
|
153
|
+
if (funcIds.length < 2)
|
|
154
|
+
return null;
|
|
155
|
+
// Two FUNCs are connected if they share a common io/satisfy target.
|
|
156
|
+
const funcTargets = new Map();
|
|
157
|
+
for (const fid of funcIds) {
|
|
158
|
+
const targets = new Set();
|
|
159
|
+
for (const t of graph.traces) {
|
|
160
|
+
if (t.source === fid && (t.type === 'io' || t.type === 'satisfy'))
|
|
161
|
+
targets.add(t.target);
|
|
162
|
+
}
|
|
163
|
+
funcTargets.set(fid, targets);
|
|
164
|
+
}
|
|
165
|
+
const parent = new Map();
|
|
166
|
+
for (const fid of funcIds)
|
|
167
|
+
parent.set(fid, fid);
|
|
168
|
+
function find(x) {
|
|
169
|
+
while (parent.get(x) !== x) {
|
|
170
|
+
x = parent.get(x);
|
|
171
|
+
}
|
|
172
|
+
return x;
|
|
173
|
+
}
|
|
174
|
+
function union(a, b) {
|
|
175
|
+
const ra = find(a), rb = find(b);
|
|
176
|
+
if (ra !== rb)
|
|
177
|
+
parent.set(ra, rb);
|
|
178
|
+
}
|
|
179
|
+
for (let i = 0; i < funcIds.length; i++) {
|
|
180
|
+
for (let j = i + 1; j < funcIds.length; j++) {
|
|
181
|
+
const ti = funcTargets.get(funcIds[i]);
|
|
182
|
+
const tj = funcTargets.get(funcIds[j]);
|
|
183
|
+
for (const t of ti) {
|
|
184
|
+
if (tj.has(t)) {
|
|
185
|
+
union(funcIds[i], funcIds[j]);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// CR-165/CR-171: FUNCs sharing the same FLOW (via io in either direction) are connected.
|
|
192
|
+
const funcIdSet = new Set(funcIds);
|
|
193
|
+
const flowToFuncs = new Map();
|
|
194
|
+
for (const t of graph.traces) {
|
|
195
|
+
if (t.type !== 'io')
|
|
196
|
+
continue;
|
|
197
|
+
if (funcIdSet.has(t.target)) {
|
|
198
|
+
const srcEl = graph.elements.find(e => e.id === t.source);
|
|
199
|
+
if (srcEl?.type === 'FLOW') {
|
|
200
|
+
if (!flowToFuncs.has(t.source))
|
|
201
|
+
flowToFuncs.set(t.source, new Set());
|
|
202
|
+
flowToFuncs.get(t.source).add(t.target);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (funcIdSet.has(t.source)) {
|
|
206
|
+
const tgtEl = graph.elements.find(e => e.id === t.target);
|
|
207
|
+
if (tgtEl?.type === 'FLOW') {
|
|
208
|
+
if (!flowToFuncs.has(t.target))
|
|
209
|
+
flowToFuncs.set(t.target, new Set());
|
|
210
|
+
flowToFuncs.get(t.target).add(t.source);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const funcsInFlow of flowToFuncs.values()) {
|
|
215
|
+
const arr = [...funcsInFlow];
|
|
216
|
+
for (let i = 1; i < arr.length; i++)
|
|
217
|
+
union(arr[0], arr[i]);
|
|
218
|
+
}
|
|
219
|
+
return new Set(funcIds.map(find)).size;
|
|
220
|
+
}
|
|
221
|
+
/** CR-SM-223 core: internal vs. external connection pairs. null where there is no signal. */
|
|
222
|
+
function cohesionOf(pairs, funcIds) {
|
|
223
|
+
if (funcIds.size < 2)
|
|
224
|
+
return null;
|
|
225
|
+
let internal = 0;
|
|
226
|
+
let external = 0;
|
|
227
|
+
for (const [a, b] of pairs) {
|
|
228
|
+
const aIn = funcIds.has(a);
|
|
229
|
+
const bIn = funcIds.has(b);
|
|
230
|
+
if (aIn && bIn)
|
|
231
|
+
internal++;
|
|
232
|
+
else if (aIn || bIn)
|
|
233
|
+
external++;
|
|
234
|
+
}
|
|
235
|
+
// No external connections → nothing to compare against, no signal.
|
|
236
|
+
if (external === 0)
|
|
237
|
+
return null;
|
|
238
|
+
return { internal, external, ratio: internal / (internal + external) };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Per-module architecture metrics as NUMBERS — one row per MOD, threshold or not
|
|
242
|
+
* (CR-SM-232).
|
|
243
|
+
*
|
|
244
|
+
* MT-01 only ever reported the modules above 70 %, MT-02 only those with ≥ 4
|
|
245
|
+
* components, and both only inside a prose `message`. For every module below the
|
|
246
|
+
* threshold there was no value, there was nothing — so a trend ("was 62 %, is 68 %"),
|
|
247
|
+
* the actual steering signal, was unobtainable. This exports what the rules already
|
|
248
|
+
* compute; it invents no metric and calibrates no threshold.
|
|
249
|
+
*
|
|
250
|
+
* Sorted worst cohesion first (as `allocationCohesion` does — the ranking IS the
|
|
251
|
+
* signal); modules without a cohesion measurement follow, stable by `moduleId`.
|
|
252
|
+
*/
|
|
253
|
+
export function moduleMetrics(graph) {
|
|
254
|
+
const byId = (a, b) => a.moduleId < b.moduleId ? -1 : a.moduleId > b.moduleId ? 1 : 0;
|
|
255
|
+
return measureModules(graph).sort((a, b) => {
|
|
256
|
+
if (a.cohesion && b.cohesion)
|
|
257
|
+
return a.cohesion.ratio - b.cohesion.ratio || byId(a, b);
|
|
258
|
+
if (a.cohesion)
|
|
259
|
+
return -1;
|
|
260
|
+
if (b.cohesion)
|
|
261
|
+
return 1;
|
|
262
|
+
return byId(a, b);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
export function allocationCohesion(graph) {
|
|
266
|
+
// CR-SM-232: dieselbe Rechnung wie `moduleMetrics` — hier nur die Projektion auf
|
|
267
|
+
// die Module MIT Messwert. Zwei Implementierungen derselben Kohäsion wären genau
|
|
268
|
+
// die Drift, gegen die dieser CR angetreten ist.
|
|
269
|
+
return moduleMetrics(graph)
|
|
270
|
+
.filter((m) => m.cohesion !== null)
|
|
271
|
+
.map((m) => ({
|
|
272
|
+
moduleId: m.moduleId,
|
|
273
|
+
moduleName: m.moduleName,
|
|
274
|
+
internal: m.cohesion.internal,
|
|
275
|
+
external: m.cohesion.external,
|
|
276
|
+
cohesion: m.cohesion.ratio,
|
|
277
|
+
}));
|
|
260
278
|
}
|
|
261
279
|
export const MT_RULES = [
|
|
262
280
|
{ id: 'MT-01', name: 'Module instability', severity: 'warning', evaluate: mt01Instability },
|