@sigloch/contracts 10.14.0 → 12.0.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/analysis-freshness-rules.d.ts +37 -30
- package/dist/se/analysis-freshness-rules.js +41 -81
- package/dist/se/ao-rules.js +3 -3
- package/dist/se/chain-metrics.d.ts +116 -0
- package/dist/se/chain-metrics.js +311 -0
- package/dist/se/conformance-rules.d.ts +4 -0
- package/dist/se/conformance-rules.js +37 -32
- package/dist/se/cr-quality-rules.js +4 -76
- package/dist/se/evaluate-all.d.ts +7 -3
- package/dist/se/evaluate-all.js +3 -4
- package/dist/se/fchain-quality-rules.js +4 -4
- package/dist/se/fmea-rules.js +4 -4
- package/dist/se/format-e-parser.js +1 -1
- package/dist/se/grammar-snapshot.d.ts +6 -6
- package/dist/se/grammar-snapshot.js +90 -103
- package/dist/se/index.d.ts +4 -2
- package/dist/se/index.js +6 -2
- package/dist/se/metric-rules.d.ts +9 -5
- package/dist/se/metric-rules.js +10 -9
- package/dist/se/module-crossings.d.ts +10 -0
- package/dist/se/module-crossings.js +3 -1
- package/dist/se/near-duplicate-rules.d.ts +2 -0
- package/dist/se/near-duplicate-rules.js +2 -2
- package/dist/se/ontology.d.ts +23 -7
- package/dist/se/ontology.js +29 -26
- package/dist/se/quality-rules.js +5 -5
- package/dist/se/readiness.d.ts +41 -149
- package/dist/se/readiness.js +50 -280
- package/dist/se/rule-due.d.ts +26 -0
- package/dist/se/rule-due.js +72 -0
- package/dist/se/rule-help.d.ts +3 -1
- package/dist/se/rule-help.js +47 -31
- package/dist/se/rules.d.ts +23 -13
- package/dist/se/rules.js +112 -72
- package/dist/se/schema-quality-rules.d.ts +1 -1
- package/dist/se/schema-quality-rules.js +1 -1
- package/dist/se/similarity.js +4 -10
- package/dist/se/uc-quality-rules.js +4 -4
- package/dist/se/view-rules.js +6 -4
- package/package.json +1 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CR-SM-404 — Kennzahlen der Wirkkette: je FCHAIN eine Zahl, oder der Grund, warum es keine gibt.
|
|
3
|
+
*
|
|
4
|
+
* Die Leitlinie verlangt, die Realisierungsarchitektur AUF DER WIRKKETTE zu bewerten (graphcode
|
|
5
|
+
* `docs/graphcode_architektur_konzept.md`, „Kennzahlen der Wirkkette"). Bis hierher rechnete das
|
|
6
|
+
* nur ein Spike in graphcode (`scripts/spike-kettenkennzahlen.mjs`); von dort ist die Rechnung
|
|
7
|
+
* portiert, nicht neu erfunden. Gemessen 2026-10-09 ueber 10 Familie-Graphen: 76 Ketten, 31
|
|
8
|
+
* bewertbar.
|
|
9
|
+
*
|
|
10
|
+
* ## Diese Datei URTEILT NICHT
|
|
11
|
+
*
|
|
12
|
+
* Keine Regel, keine Schwelle, kein Katalogeintrag — dieselbe Trennung wie `moduleMetrics` und
|
|
13
|
+
* `functionCriticality`. Ob eine Laenge von 6 gut ist, sagt erst ein Profil.
|
|
14
|
+
*
|
|
15
|
+
* ## Nicht bewertbar heisst: KEINE Zahl
|
|
16
|
+
*
|
|
17
|
+
* Eine FCHAIN ist grammatisch eine Menge; die Reihenfolge entsteht erst aus Erzeuger ->
|
|
18
|
+
* Verbraucher (`FUNC -io-> FLOW -io-> FUNC`, beide Enden Glied). Zerfaellt die Kette, beschriebe
|
|
19
|
+
* jede Kennzahl etwas, das es nicht gibt. Eine solche Kette traegt deshalb ihre Gruende und kein
|
|
20
|
+
* einziges Zahlenfeld — das Schema laesst es nicht zu. Sie zaehlt im NENNER der Quote
|
|
21
|
+
* (`measurability`), die neben jedem Wert stehen muss wie die Bindungsquote neben RC-*.
|
|
22
|
+
*
|
|
23
|
+
* ## Wer urteilt, ob eine Kette eine Kette ist
|
|
24
|
+
*
|
|
25
|
+
* - `FC-05`: die Regel FC-05, aufgerufen, nicht nachgebaut. Ursache (fehlendes Glied, Sack) und
|
|
26
|
+
* die Nebenkomponenten stehen in ihrem Befund (`context.side_components`), nicht hier.
|
|
27
|
+
* - `loose-member`: ein Glied ohne io-Eingang oder -Ausgang. Das ist die Bedingung, unter der
|
|
28
|
+
* FC-05 sich ENTHAELT (R-31 meldet das Glied) — ohne diesen Grund haette eine solche Kette weder
|
|
29
|
+
* einen FC-05-Befund noch einen Grund und bekaeme Zahlen.
|
|
30
|
+
* - `no-entry` / `no-exit`: kein FLOW von bzw. zu einem Knoten AUSSERHALB der Kette. Weiter als
|
|
31
|
+
* FC-04, das nur einen ACTOR gelten laesst: eine Kette, die eine andere fortsetzt, ist messbar.
|
|
32
|
+
* - `empty`: keine FUNC (R-15).
|
|
33
|
+
*
|
|
34
|
+
* ## Ein Kettenbegriff, ein Modulbegriff
|
|
35
|
+
*
|
|
36
|
+
* Glied = direkte `FCHAIN -compose-> FUNC`-Kante, ueber `chainsByFunc` (R-30s Definition).
|
|
37
|
+
* Modul einer FUNC = direkte `allocate`-Kante, wie `module-crossings.ts` und `moduleMetrics`.
|
|
38
|
+
* Der Spike erbte das Modul ueber compose-Vorfahren; gemessen sind 326 von 326 Kettengliedern
|
|
39
|
+
* des Korpus direkt alloziert — die Vererbung kauft nichts und waere eine zweite Vorstellung von
|
|
40
|
+
* Zugehoerigkeit im selben Paket.
|
|
41
|
+
*
|
|
42
|
+
* ## CR-SM-406 — die ORTE je Kette, nur erkannt
|
|
43
|
+
*
|
|
44
|
+
* Neben jeder Zahl steht, WO sie herkommt: die Mitglieder je Rueckkopplung, die geteilten
|
|
45
|
+
* Funktionen, die Grenzen je gerichtetem Modulpaar mit ihren Vertraegen, dazu Zulauf, Importe und
|
|
46
|
+
* Uebergaben. Reine Zusatzfelder — die Zahlen aus CR-SM-404 bleiben und sind die Laenge ihrer
|
|
47
|
+
* Liste. Weiter kein Urteil.
|
|
48
|
+
*
|
|
49
|
+
* - Import und Uebergabe zaehlen nur an Gliedern, die ALLEIN dieser Kette gehoeren. Was an einer
|
|
50
|
+
* geteilten Funktion haengt, steht an der geteilten Funktion — sie ist ein eigener Ort. Ohne
|
|
51
|
+
* diese Grenze meldete jede Kette durch Gate und Store deren Zufluesse als ihre eigenen
|
|
52
|
+
* (gemessen am graphcode-Modell 2026-10-10: 333 statt 16).
|
|
53
|
+
* - Ein ACTOR als Erzeuger oder Verbraucher ist der Ein- und Ausgang der Kette, kein Ort.
|
|
54
|
+
* - Vertrag = `contractsOfFlow` aus `module-crossings.ts` (SCHEMA, sonst `UNBOUND:<flow>`), hier je
|
|
55
|
+
* Kettenkante gelesen. `moduleCrossings().pairs` selbst traegt die Vertraege des GANZEN Graphen
|
|
56
|
+
* je ungerichtetem Paar und kann „elf Kanten auf einem Vertrag" in EINER Kette nicht zeigen.
|
|
57
|
+
*
|
|
58
|
+
* @author andreas@siglochconsulting
|
|
59
|
+
*/
|
|
60
|
+
import { z } from 'zod/v4';
|
|
61
|
+
import { indexOf } from './graph-index.js';
|
|
62
|
+
import { chainsByFunc } from './function-criticality.js';
|
|
63
|
+
import { fc05ChainConnected } from './fchain-quality-rules.js';
|
|
64
|
+
import { contractsOfFlow } from './module-crossings.js';
|
|
65
|
+
const count = z.number().int().nonnegative();
|
|
66
|
+
const identity = { chainId: z.string(), chainName: z.string() };
|
|
67
|
+
/** CR-SM-406: ein Fluss an der Kettengrenze und das Glied der Kette, an dem er haengt. */
|
|
68
|
+
const FlowAtFunc = z.strictObject({ flow: z.string(), func: z.string() });
|
|
69
|
+
/**
|
|
70
|
+
* Eine bewertbare Kette: die fuenf heute rechenbaren Kennzahlen plus die Engstellen-Schranke,
|
|
71
|
+
* dazu die Orte (CR-SM-406). Jede Liste ist uid-sortiert.
|
|
72
|
+
*/
|
|
73
|
+
const MeasuredChain = z.strictObject({
|
|
74
|
+
...identity,
|
|
75
|
+
measurable: z.literal(true),
|
|
76
|
+
/** Gesamtlaenge: FUNC-Schritte auf dem laengsten Pfad; eine Schleife zaehlt als EIN Schritt. */
|
|
77
|
+
length: count,
|
|
78
|
+
/** Verzweigungsgrad: groesster Ausgangsgrad eines Glieds INNERHALB der Kette. */
|
|
79
|
+
branching: count,
|
|
80
|
+
/**
|
|
81
|
+
* Modulgrenzen: Kettenkanten, deren Enden in verschiedenen MOD liegen.
|
|
82
|
+
* `null`, sobald ein Glied kein Modul hat (R-22 meldet es) — keine geratene Zahl.
|
|
83
|
+
*/
|
|
84
|
+
moduleBoundaries: count.nullable(),
|
|
85
|
+
/** Rueckkopplungen: nicht-triviale starke Komponenten ueber die Kettenkanten. */
|
|
86
|
+
feedbackLoops: count,
|
|
87
|
+
/** Geteilte Knoten: Glieder, die in mehr als einer Kette liegen. */
|
|
88
|
+
sharedFuncs: count,
|
|
89
|
+
/**
|
|
90
|
+
* Engstellen — OBERE SCHRANKE: geteilte Glieder mit Ein- UND Ausgang in der Kette. Der
|
|
91
|
+
* Konzeptbegriff verlangt „synchron durchlaufen"; ohne `FLOW.sync` zaehlt jeder Durchgang.
|
|
92
|
+
*/
|
|
93
|
+
bottlenecks: count,
|
|
94
|
+
/** Synchrone Tiefe — in dieser Stufe nie berechnet: braucht den Leser von `FLOW.sync` (CR-SM-364). */
|
|
95
|
+
syncDepth: z.null(),
|
|
96
|
+
/** Fehlerpfad-Tiefe — nicht berechenbar: Fehlerfluesse traegt kein Attribut (CR-SM-364, `kind` verworfen). */
|
|
97
|
+
errorPathDepth: z.null(),
|
|
98
|
+
/** Zahl der Glieder. */
|
|
99
|
+
memberCount: count,
|
|
100
|
+
/** Zulauf: groesster Eingangsgrad eines Glieds INNERHALB der Kette — das Gegenstueck zu `branching`. */
|
|
101
|
+
fanIn: count,
|
|
102
|
+
/**
|
|
103
|
+
* Die Rueckkopplungen, die `feedbackLoops` zaehlt. `id` entsteht allein aus den Mitgliedern
|
|
104
|
+
* (uids mit `+` verbunden) — dieselbe Schleife traegt in jeder Kette dieselbe Kennung.
|
|
105
|
+
*/
|
|
106
|
+
loops: z.array(z.strictObject({ id: z.string(), members: z.array(z.string()).min(2) })),
|
|
107
|
+
/** Die geteilten Glieder, die `sharedFuncs` zaehlt. */
|
|
108
|
+
shared: z.array(z.string()),
|
|
109
|
+
/**
|
|
110
|
+
* Die Modulgrenzen je GERICHTETEM Modulpaar: `edges` Kettenkanten von `from` nach `to` (ihre
|
|
111
|
+
* Summe ist `moduleBoundaries`) auf den verschiedenen Vertraegen `contracts`. `null` wie dort.
|
|
112
|
+
*/
|
|
113
|
+
boundaries: z.array(z.strictObject({
|
|
114
|
+
from: z.string(), to: z.string(), edges: count.min(1), contracts: z.array(z.string()).min(1),
|
|
115
|
+
})).nullable(),
|
|
116
|
+
/**
|
|
117
|
+
* Importe: ein Glied, das NUR in dieser Kette liegt, verbraucht einen Fluss, den mindestens
|
|
118
|
+
* eine FUNC erzeugt und kein Glied der Kette.
|
|
119
|
+
*/
|
|
120
|
+
imports: z.array(FlowAtFunc),
|
|
121
|
+
/**
|
|
122
|
+
* Uebergaben, gespiegelt: ein Glied, das nur in dieser Kette liegt, erzeugt einen Fluss, den
|
|
123
|
+
* mindestens eine FUNC verbraucht und kein Glied der Kette.
|
|
124
|
+
*/
|
|
125
|
+
handovers: z.array(FlowAtFunc),
|
|
126
|
+
});
|
|
127
|
+
/** Eine nicht bewertbare Kette: der Grund statt der Zahl. Reihenfolge der Gruende ist fest. */
|
|
128
|
+
const UnmeasurableChain = z.strictObject({
|
|
129
|
+
...identity,
|
|
130
|
+
measurable: z.literal(false),
|
|
131
|
+
reasons: z.array(z.enum(['FC-05', 'loose-member', 'no-entry', 'no-exit', 'empty'])).min(1),
|
|
132
|
+
});
|
|
133
|
+
// CR-SM-361: ein Zod-Vertrag, kein Typ — der Erzeuger prueft sein Ergebnis.
|
|
134
|
+
export const ChainMetrics = z.object({
|
|
135
|
+
/** Eine Zeile je FCHAIN, in GRAPH-Reihenfolge — keine Rangfolge, die Zahl ist kein Urteil. */
|
|
136
|
+
chains: z.array(z.discriminatedUnion('measurable', [MeasuredChain, UnmeasurableChain])),
|
|
137
|
+
/** Die Reichweite der Aussage. `ratio` ist `null` ohne Ketten — kein Ersatzwert. */
|
|
138
|
+
measurability: z.object({ chains: count, measurable: count, ratio: z.number().min(0).max(1).nullable() }),
|
|
139
|
+
});
|
|
140
|
+
/** Die Kennzahlen aller FCHAINs dieses Graphen und die Quote der bewertbaren. */
|
|
141
|
+
export function chainMetrics(graph) {
|
|
142
|
+
const idx = indexOf(graph);
|
|
143
|
+
const chainsOf = chainsByFunc(graph);
|
|
144
|
+
// FC-05 ist der EINE Leser fuer „gerichtet zusammenhaengend" — hier zaehlt nur, ob er meldet.
|
|
145
|
+
const fc05 = new Set(fc05ChainConnected(graph).map((v) => v.element_id));
|
|
146
|
+
const flowsInto = (id) => idx.in(id, 'io').map((t) => t.source).filter((s) => idx.typeOf(s) === 'FLOW');
|
|
147
|
+
const flowsOutOf = (id) => idx.out(id, 'io').map((t) => t.target).filter((s) => idx.typeOf(s) === 'FLOW');
|
|
148
|
+
const moduleOf = (func) => idx.out(func, 'allocate').map((t) => t.target).find((m) => idx.typeOf(m) === 'MOD');
|
|
149
|
+
const chains = idx.elementsOfType('FCHAIN').map((fc) => {
|
|
150
|
+
const who = { chainId: fc.id, chainName: fc.name };
|
|
151
|
+
const members = [...new Set(idx.out(fc.id, 'compose').map((t) => t.target).filter((id) => idx.typeOf(id) === 'FUNC'))].sort();
|
|
152
|
+
if (members.length === 0)
|
|
153
|
+
return { ...who, measurable: false, reasons: ['empty'] };
|
|
154
|
+
const inChain = new Set(members);
|
|
155
|
+
// Kettenkanten Erzeuger -> Verbraucher; dazu, wo die Kette von aussen gespeist wird und wo sie hinausliefert.
|
|
156
|
+
const next = new Map(members.map((m) => [m, new Set()]));
|
|
157
|
+
const carriers = new Map(); // Kettenkante `a b` -> die FLOWs, die sie tragen
|
|
158
|
+
let entry = false;
|
|
159
|
+
let exit = false;
|
|
160
|
+
let loose = false;
|
|
161
|
+
for (const m of members) {
|
|
162
|
+
const ins = flowsInto(m);
|
|
163
|
+
const outs = flowsOutOf(m);
|
|
164
|
+
if (ins.length === 0 || outs.length === 0)
|
|
165
|
+
loose = true;
|
|
166
|
+
for (const flow of ins)
|
|
167
|
+
if (idx.in(flow, 'io').some((t) => !inChain.has(t.source)))
|
|
168
|
+
entry = true;
|
|
169
|
+
for (const flow of outs) {
|
|
170
|
+
for (const t of idx.out(flow, 'io')) {
|
|
171
|
+
if (t.target === m)
|
|
172
|
+
continue;
|
|
173
|
+
if (!inChain.has(t.target)) {
|
|
174
|
+
exit = true;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
next.get(m).add(t.target);
|
|
178
|
+
const edge = `${m} ${t.target}`;
|
|
179
|
+
(carriers.get(edge) ?? carriers.set(edge, new Set()).get(edge)).add(flow);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const reasons = [];
|
|
184
|
+
if (fc05.has(fc.id))
|
|
185
|
+
reasons.push('FC-05');
|
|
186
|
+
if (loose)
|
|
187
|
+
reasons.push('loose-member');
|
|
188
|
+
if (!entry)
|
|
189
|
+
reasons.push('no-entry');
|
|
190
|
+
if (!exit)
|
|
191
|
+
reasons.push('no-exit');
|
|
192
|
+
if (reasons.length > 0)
|
|
193
|
+
return { ...who, measurable: false, reasons };
|
|
194
|
+
// Rueckkopplungen: Tarjan — jede starke Komponente mit mehr als einem Glied ist eine Schleife.
|
|
195
|
+
const order = new Map();
|
|
196
|
+
const low = new Map();
|
|
197
|
+
const onStack = new Set();
|
|
198
|
+
const stack = [];
|
|
199
|
+
const sccOf = new Map();
|
|
200
|
+
const loops = [];
|
|
201
|
+
const visit = (v) => {
|
|
202
|
+
order.set(v, order.size);
|
|
203
|
+
low.set(v, order.get(v));
|
|
204
|
+
stack.push(v);
|
|
205
|
+
onStack.add(v);
|
|
206
|
+
for (const w of next.get(v)) {
|
|
207
|
+
if (!order.has(w)) {
|
|
208
|
+
visit(w);
|
|
209
|
+
low.set(v, Math.min(low.get(v), low.get(w)));
|
|
210
|
+
}
|
|
211
|
+
else if (onStack.has(w))
|
|
212
|
+
low.set(v, Math.min(low.get(v), order.get(w)));
|
|
213
|
+
}
|
|
214
|
+
if (low.get(v) !== order.get(v))
|
|
215
|
+
return;
|
|
216
|
+
const comp = [];
|
|
217
|
+
let w;
|
|
218
|
+
do {
|
|
219
|
+
w = stack.pop();
|
|
220
|
+
onStack.delete(w);
|
|
221
|
+
comp.push(w);
|
|
222
|
+
} while (w !== v);
|
|
223
|
+
if (comp.length > 1)
|
|
224
|
+
loops.push({ id: comp.sort().join('+'), members: comp });
|
|
225
|
+
for (const c of comp)
|
|
226
|
+
sccOf.set(c, v);
|
|
227
|
+
};
|
|
228
|
+
for (const m of members)
|
|
229
|
+
if (!order.has(m))
|
|
230
|
+
visit(m);
|
|
231
|
+
loops.sort((a, b) => (a.id < b.id ? -1 : 1));
|
|
232
|
+
// Gesamtlaenge: laengster Pfad auf dem Kondensat — eine Schleife ist dort ein Knoten.
|
|
233
|
+
const condensed = new Map();
|
|
234
|
+
for (const [a, targets] of next) {
|
|
235
|
+
for (const b of targets) {
|
|
236
|
+
const ca = sccOf.get(a);
|
|
237
|
+
const cb = sccOf.get(b);
|
|
238
|
+
if (ca !== cb)
|
|
239
|
+
(condensed.get(ca) ?? condensed.set(ca, new Set()).get(ca)).add(cb);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const longestFrom = new Map();
|
|
243
|
+
const longest = (c) => {
|
|
244
|
+
const known = longestFrom.get(c);
|
|
245
|
+
if (known !== undefined)
|
|
246
|
+
return known;
|
|
247
|
+
let best = 1;
|
|
248
|
+
for (const n of condensed.get(c) ?? [])
|
|
249
|
+
best = Math.max(best, 1 + longest(n));
|
|
250
|
+
longestFrom.set(c, best);
|
|
251
|
+
return best;
|
|
252
|
+
};
|
|
253
|
+
const length = Math.max(...members.map((m) => longest(sccOf.get(m))));
|
|
254
|
+
const allocated = members.every((m) => moduleOf(m) !== undefined);
|
|
255
|
+
let branching = 0;
|
|
256
|
+
const inDegree = new Map();
|
|
257
|
+
const pairs = new Map();
|
|
258
|
+
for (const [a, targets] of next) {
|
|
259
|
+
branching = Math.max(branching, targets.size);
|
|
260
|
+
for (const b of targets) {
|
|
261
|
+
inDegree.set(b, (inDegree.get(b) ?? 0) + 1);
|
|
262
|
+
const from = moduleOf(a);
|
|
263
|
+
const to = moduleOf(b);
|
|
264
|
+
if (from === undefined || to === undefined || from === to)
|
|
265
|
+
continue;
|
|
266
|
+
const key = `${from} ${to}`;
|
|
267
|
+
const pair = pairs.get(key) ?? pairs.set(key, { from, to, edges: 0, contracts: new Set() }).get(key);
|
|
268
|
+
pair.edges++;
|
|
269
|
+
for (const flow of carriers.get(`${a} ${b}`))
|
|
270
|
+
for (const c of contractsOfFlow(idx, flow))
|
|
271
|
+
pair.contracts.add(c);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const boundaries = [...pairs.entries()].sort(([a], [b]) => (a < b ? -1 : 1))
|
|
275
|
+
.map(([, p]) => ({ ...p, contracts: [...p.contracts].sort() }));
|
|
276
|
+
const shared = members.filter((m) => (chainsOf.get(m)?.size ?? 0) > 1);
|
|
277
|
+
// Importe und Uebergaben: nur an Gliedern, die allein dieser Kette gehoeren; die andere Seite
|
|
278
|
+
// des Flusses ist mindestens eine FUNC und kein Glied (ein ACTOR ist Ein-/Ausgang, kein Ort).
|
|
279
|
+
const own = members.filter((m) => !shared.includes(m));
|
|
280
|
+
const foreign = (ends) => {
|
|
281
|
+
const funcs = ends.filter((id) => idx.typeOf(id) === 'FUNC');
|
|
282
|
+
return funcs.length > 0 && !funcs.some((f) => inChain.has(f));
|
|
283
|
+
};
|
|
284
|
+
const at = (flows, farEnds) => own.flatMap((func) => [...new Set(flows(func))].sort()
|
|
285
|
+
.filter((flow) => foreign(farEnds(flow))).map((flow) => ({ flow, func })));
|
|
286
|
+
return {
|
|
287
|
+
...who,
|
|
288
|
+
measurable: true,
|
|
289
|
+
length,
|
|
290
|
+
branching,
|
|
291
|
+
moduleBoundaries: allocated ? boundaries.reduce((n, p) => n + p.edges, 0) : null,
|
|
292
|
+
feedbackLoops: loops.length,
|
|
293
|
+
sharedFuncs: shared.length,
|
|
294
|
+
bottlenecks: shared.filter((m) => inDegree.has(m) && next.get(m).size > 0).length,
|
|
295
|
+
syncDepth: null,
|
|
296
|
+
errorPathDepth: null,
|
|
297
|
+
memberCount: members.length,
|
|
298
|
+
fanIn: Math.max(0, ...inDegree.values()),
|
|
299
|
+
loops,
|
|
300
|
+
shared,
|
|
301
|
+
boundaries: allocated ? boundaries : null,
|
|
302
|
+
imports: at(flowsInto, (flow) => idx.in(flow, 'io').map((t) => t.source)),
|
|
303
|
+
handovers: at(flowsOutOf, (flow) => idx.out(flow, 'io').map((t) => t.target)),
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
const measurable = chains.filter((c) => c.measurable).length;
|
|
307
|
+
return ChainMetrics.parse({
|
|
308
|
+
chains,
|
|
309
|
+
measurability: { chains: chains.length, measurable, ratio: chains.length > 0 ? measurable / chains.length : null },
|
|
310
|
+
});
|
|
311
|
+
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { z } from 'zod/v4';
|
|
18
18
|
import type { OntologyGraph } from './ontology.js';
|
|
19
19
|
import type { RuleSeverity, RuleViolation } from './rules.js';
|
|
20
|
+
import type { RuleRole, RuleStage } from './readiness.js';
|
|
20
21
|
/** Parser facts about one source file (extracted by the executor). */
|
|
21
22
|
export declare const FileFactsSchema: z.ZodObject<{
|
|
22
23
|
exists: z.ZodBoolean;
|
|
@@ -67,6 +68,9 @@ export interface ConformanceRuleDefinition {
|
|
|
67
68
|
id: string;
|
|
68
69
|
name: string;
|
|
69
70
|
severity: RuleSeverity;
|
|
71
|
+
/** CR-SM-395: Stufe und Rolle wie an jeder Katalogregel (`RuleDefinition`). */
|
|
72
|
+
stage: RuleStage;
|
|
73
|
+
role?: RuleRole;
|
|
70
74
|
/**
|
|
71
75
|
* CR-SM-305: die Grundgesamtheit, ueber die die Regel meldet — dieselbe Bedeutung wie an
|
|
72
76
|
* jeder Katalogregel (CR-SM-235), damit RC in `ALL_RULE_DEFS` stehen kann, ohne dass ein
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { z } from 'zod/v4';
|
|
18
18
|
import { readTestRefs, readRealRef } from './ontology.js';
|
|
19
19
|
import { CLOSED_STATUS } from './cr-quality-rules.js';
|
|
20
|
+
import { whenDue } from './rule-due.js';
|
|
20
21
|
/** Parser facts about one source file (extracted by the executor). */
|
|
21
22
|
export const FileFactsSchema = z.object({
|
|
22
23
|
/** File exists on disk (repo-relative path). */
|
|
@@ -83,7 +84,7 @@ export const CodeFactsSchema = z.object({
|
|
|
83
84
|
});
|
|
84
85
|
const missingFile = (facts, file) => facts.files[file]?.exists !== true;
|
|
85
86
|
// RC-01: every valid FUNC realRef must resolve — file on disk, symbol declared in
|
|
86
|
-
// it. Presence is R-20's concern (incl.
|
|
87
|
+
// it. Presence is R-20's concern (incl. external/decomposition-parent
|
|
87
88
|
// exemptions, CR-GC-244); RC-01 only judges bindings that exist. `lang:'prompt'`
|
|
88
89
|
// is realized by a skill file — file-exists is the whole binding. A realRef with
|
|
89
90
|
// no `symbol` (CR-228) is also file-only: resolution stops at file-exists.
|
|
@@ -92,7 +93,7 @@ function codeRefMustResolve(graph, facts) {
|
|
|
92
93
|
for (const el of graph.elements) {
|
|
93
94
|
if (el.type !== 'FUNC')
|
|
94
95
|
continue;
|
|
95
|
-
if (el.attributes?.
|
|
96
|
+
if (el.attributes?.external === true)
|
|
96
97
|
continue;
|
|
97
98
|
const parsed = readRealRef(el.attributes);
|
|
98
99
|
if (parsed.state !== 'bound')
|
|
@@ -136,8 +137,6 @@ function testRefMustResolve(graph, facts) {
|
|
|
136
137
|
for (const el of graph.elements) {
|
|
137
138
|
if (el.type !== 'TEST')
|
|
138
139
|
continue;
|
|
139
|
-
if (el.attributes?.concept === true)
|
|
140
|
-
continue;
|
|
141
140
|
const parsed = readTestRefs(el.attributes);
|
|
142
141
|
if (parsed.state !== 'bound')
|
|
143
142
|
continue; // no/invalid binding → R-19 territory
|
|
@@ -172,7 +171,7 @@ function testRefMustResolve(graph, facts) {
|
|
|
172
171
|
}
|
|
173
172
|
// RC-03: every valid SCHEMA realRef must resolve — file on disk, symbol declared
|
|
174
173
|
// in it (CR-211, unified CR-228). Presence (a SCHEMA with NO realRef) is the
|
|
175
|
-
// concern of the R-26 presence rule, not RC-03;
|
|
174
|
+
// concern of the R-26 presence rule, not RC-03; external SCHEMAs are exempt
|
|
176
175
|
// there and here. Severity error, like RC-01: a bound-but-broken schema IS a defect.
|
|
177
176
|
// A symbol-less realRef stops at file-exists. Fires 0× until realRefs exist.
|
|
178
177
|
function schemaRefMustResolve(graph, facts) {
|
|
@@ -180,7 +179,7 @@ function schemaRefMustResolve(graph, facts) {
|
|
|
180
179
|
for (const el of graph.elements) {
|
|
181
180
|
if (el.type !== 'SCHEMA')
|
|
182
181
|
continue;
|
|
183
|
-
if (el.attributes?.
|
|
182
|
+
if (el.attributes?.external === true)
|
|
184
183
|
continue;
|
|
185
184
|
const parsed = readRealRef(el.attributes);
|
|
186
185
|
if (parsed.state !== 'bound')
|
|
@@ -274,7 +273,7 @@ function boundToNonZod(facts, ref) {
|
|
|
274
273
|
// but NONE of their realRef files import AND parse (`.parse`/`.safeParse`) the
|
|
275
274
|
// schema symbol, the modelled validation is missing. Severity warn (the parse may
|
|
276
275
|
// legitimately sit in a framework layer, not the FUNC's own file). Skips when the
|
|
277
|
-
// SCHEMA
|
|
276
|
+
// SCHEMA has no realRef (or a symbol-less one), or no io-connected
|
|
278
277
|
// realized FUNC. `external` is NOT exempt (CR-SM-318, ITEM-2026-064): an external SCHEMA
|
|
279
278
|
// with a realRef is a foreign-API contract held as a Zod schema in OUR code — the
|
|
280
279
|
// boundary to the foreign system is exactly where that parse belongs.
|
|
@@ -284,8 +283,6 @@ function schemaRefMustBeUsed(graph, facts) {
|
|
|
284
283
|
for (const el of graph.elements) {
|
|
285
284
|
if (el.type !== 'SCHEMA')
|
|
286
285
|
continue;
|
|
287
|
-
if (el.attributes?.concept === true)
|
|
288
|
-
continue;
|
|
289
286
|
const parsed = readRealRef(el.attributes);
|
|
290
287
|
if (parsed.state !== 'bound' || parsed.value.symbol === undefined)
|
|
291
288
|
continue;
|
|
@@ -308,7 +305,7 @@ function schemaRefMustBeUsed(graph, facts) {
|
|
|
308
305
|
(actual.length > 0 ? ` — it is parsed in: ${actual.join(', ')}` : ''),
|
|
309
306
|
fix_hint: actual.length > 0
|
|
310
307
|
? `The parse sits in ${actual.join(', ')}: point the FUNC realRef at that file, or move the parse into the modelled FUNC`
|
|
311
|
-
: `Import and call ${ref.symbol}.parse()/.safeParse() in one of the io-connected FUNC's code files
|
|
308
|
+
: `Import and call ${ref.symbol}.parse()/.safeParse() in one of the io-connected FUNC's code files`,
|
|
312
309
|
context: { element_type: el.type, element_name: el.name },
|
|
313
310
|
});
|
|
314
311
|
}
|
|
@@ -490,8 +487,7 @@ function externalRefMustNameDependency(graph, facts) {
|
|
|
490
487
|
const known = new Set(declared);
|
|
491
488
|
const violations = [];
|
|
492
489
|
for (const el of graph.elements) {
|
|
493
|
-
|
|
494
|
-
if (el.attributes?.external !== true || el.attributes?.concept === true)
|
|
490
|
+
if (el.attributes?.external !== true)
|
|
495
491
|
continue;
|
|
496
492
|
const parsed = readRealRef(el.attributes);
|
|
497
493
|
if (parsed.state !== 'bound')
|
|
@@ -523,8 +519,12 @@ function externalRefMustNameDependency(graph, facts) {
|
|
|
523
519
|
// directory;
|
|
524
520
|
// 2. an OPEN file with no node — an open CR the graph cannot see. The missing node has no
|
|
525
521
|
// element to carry the finding, so it anchors at the first SYS (sorted, Gate 5); without
|
|
526
|
-
// a SYS the branch is silent — R-
|
|
522
|
+
// a SYS the branch is silent — R-33 already reports that graph.
|
|
527
523
|
// A node WITHOUT a file is not reported: that is history (archived CRs, pre-docs/cr numbering).
|
|
524
|
+
//
|
|
525
|
+
// CR-SM-396: stage 10 (plan), not 12. The rule needs the ORDERS and the directory, not a binding —
|
|
526
|
+
// it holds always, also in a draft: an open CR file without a node must be reported before the
|
|
527
|
+
// build is opened, not after. Its findings hold the mark Bau like every finding of stages 10–12.
|
|
528
528
|
// ---------------------------------------------------------------------------
|
|
529
529
|
function crNodeMatchesFile(graph, facts) {
|
|
530
530
|
const crFiles = facts.crFiles;
|
|
@@ -585,8 +585,6 @@ function schemaRefMustBeZod(graph, facts) {
|
|
|
585
585
|
for (const el of graph.elements) {
|
|
586
586
|
if (el.type !== 'SCHEMA')
|
|
587
587
|
continue;
|
|
588
|
-
if (el.attributes?.concept === true)
|
|
589
|
-
continue;
|
|
590
588
|
const parsed = readRealRef(el.attributes);
|
|
591
589
|
if (parsed.state !== 'bound' || parsed.value.symbol === undefined)
|
|
592
590
|
continue;
|
|
@@ -620,8 +618,6 @@ function schemaParsedOnlyAtInterface(graph, facts) {
|
|
|
620
618
|
for (const el of graph.elements) {
|
|
621
619
|
if (el.type !== 'SCHEMA')
|
|
622
620
|
continue;
|
|
623
|
-
if (el.attributes?.concept === true)
|
|
624
|
-
continue;
|
|
625
621
|
const parsed = readRealRef(el.attributes);
|
|
626
622
|
if (parsed.state !== 'bound' || parsed.value.symbol === undefined)
|
|
627
623
|
continue;
|
|
@@ -660,7 +656,7 @@ function schemaParsedOnlyAtInterface(graph, facts) {
|
|
|
660
656
|
// boundary measurement are blind to it BY CONSTRUCTION. Measured at sigllm: 0 RC-05
|
|
661
657
|
// findings at 19 % import coverage, 8 at 81 % — the zero was the absence of a question.
|
|
662
658
|
// The cost in the message comes from `importCoverage` (the same resolution), not from a
|
|
663
|
-
// second count. `
|
|
659
|
+
// second count. `external` is exempt — the same exemption as R-20.
|
|
664
660
|
// ABSENT `importEdges` = silence, like RC-05: without an import graph RC-05 has nothing to
|
|
665
661
|
// judge, so there is no blindness to report — and the cost could not be named.
|
|
666
662
|
// ---------------------------------------------------------------------------
|
|
@@ -673,7 +669,7 @@ function modMustBeResolvable(graph, facts) {
|
|
|
673
669
|
.filter(t => t.type === 'allocate' && boundFuncs.has(t.source) && typeOf.get(t.target) === 'MOD')
|
|
674
670
|
.map(t => t.target));
|
|
675
671
|
const blind = graph.elements.filter(e => e.type === 'MOD' &&
|
|
676
|
-
e.attributes?.
|
|
672
|
+
e.attributes?.external !== true &&
|
|
677
673
|
!(typeof e.attributes?.path === 'string' && e.attributes.path.length > 0) &&
|
|
678
674
|
!resolvedByFunc.has(e.id));
|
|
679
675
|
if (blind.length === 0)
|
|
@@ -685,22 +681,31 @@ function modMustBeResolvable(graph, facts) {
|
|
|
685
681
|
severity: 'warning',
|
|
686
682
|
element_id: el.id,
|
|
687
683
|
message: `${el.id} has neither a path nor an allocated FUNC with a realRef — no file can resolve to it, RC-05 is blind here (${cost})`,
|
|
688
|
-
fix_hint: `Set the module's source directory via graph_mutate Format-E \`~ ${el.id} @path <dir>\` — or bind one of its allocated FUNCs (\`~ FUNC-x @realRef {"file":…,"symbol":…}\`)
|
|
684
|
+
fix_hint: `Set the module's source directory via graph_mutate Format-E \`~ ${el.id} @path <dir>\` — or bind one of its allocated FUNCs (\`~ FUNC-x @realRef {"file":…,"symbol":…}\`)`,
|
|
689
685
|
context: { element_type: el.type, element_name: el.name },
|
|
690
686
|
}));
|
|
691
687
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
{ id: 'RC-
|
|
703
|
-
]
|
|
688
|
+
// CR-SM-395: Stufe 12 — der Abgleich ist faellig, sobald der Bau eroeffnet ist (s. rule-due.ts).
|
|
689
|
+
// Zwei Regeln warten nicht auf den Bau und liegen deshalb auf Stufe 10, der letzten Stufe, deren
|
|
690
|
+
// Faelligkeit die `domain` ist; ihre Befunde halten weiter die Marke Bau:
|
|
691
|
+
// - RC-07 (CR-SM-396) braucht die Auftraege und `docs/cr`, keine Bindung.
|
|
692
|
+
// - RC-05 (CR-SM-397, Entscheidung des Autors 2026-10-06) braucht Module und den Import-Graphen.
|
|
693
|
+
// Ein Modul ist schon ueber `path` aufloesbar, ohne Bindung und ohne Auftrag. Die Stufenregel
|
|
694
|
+
// ergaebe 7 (Modul) — dort hielte eine Warnung KEINE Marke mehr (nur Fehler und Existenz-Regeln
|
|
695
|
+
// halten vor TRR). Stufe 10 ist die benannte Ausnahme: der Befund bleibt ein Befund des Baus.
|
|
696
|
+
// Ohne `importEdges` und ohne aufloesbares Modul schweigt die Regel von selbst.
|
|
697
|
+
export const CODE_CONFORMANCE_RULES = whenDue([
|
|
698
|
+
{ id: 'RC-01', name: 'FUNC realRef resolves to a declared symbol', severity: 'warning', stage: 12, domain: ['FUNC'], evaluate: codeRefMustResolve },
|
|
699
|
+
{ id: 'RC-02', name: 'testRefs entries resolve to runnable tests', severity: 'warning', stage: 12, domain: ['TEST'], evaluate: testRefMustResolve },
|
|
700
|
+
{ id: 'RC-03', name: 'SCHEMA realRef resolves to a declared export', severity: 'warning', stage: 12, domain: ['SCHEMA'], evaluate: schemaRefMustResolve },
|
|
701
|
+
{ id: 'RC-04', name: 'SCHEMA realRef is parsed at its interface', severity: 'warning', stage: 12, domain: ['SCHEMA'], evaluate: schemaRefMustBeUsed },
|
|
702
|
+
{ id: 'RC-05', name: 'cross-module import drift', severity: 'warning', stage: 10, domain: ['MOD'], evaluate: importDriftConformance },
|
|
703
|
+
{ id: 'RC-06', name: 'external realRef names a declared dependency', severity: 'warning', stage: 12, domain: ['FUNC', 'MOD', 'SCHEMA'], evaluate: externalRefMustNameDependency },
|
|
704
|
+
{ id: 'RC-07', name: 'CR node agrees with docs/cr', severity: 'warning', stage: 10, domain: ['CR', 'SYS'], evaluate: crNodeMatchesFile },
|
|
705
|
+
{ id: 'RC-08', name: 'SCHEMA realRef is a Zod schema', severity: 'warning', stage: 12, domain: ['SCHEMA'], evaluate: schemaRefMustBeZod },
|
|
706
|
+
{ id: 'RC-09', name: 'SCHEMA is parsed only at its modelled interface', severity: 'warning', stage: 12, domain: ['SCHEMA'], evaluate: schemaParsedOnlyAtInterface },
|
|
707
|
+
{ id: 'RC-10', name: 'MOD has resolvable files', severity: 'warning', stage: 12, domain: ['MOD'], evaluate: modMustBeResolvable },
|
|
708
|
+
]);
|
|
704
709
|
/** Run all RC rules against a graph + extracted code facts. */
|
|
705
710
|
export function evaluateConformanceRules(graph, facts) {
|
|
706
711
|
return CODE_CONFORMANCE_RULES.flatMap((rule) => rule.evaluate(graph, facts));
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readRealRef } from './ontology.js';
|
|
2
1
|
// ---------------------------------------------------------------------------
|
|
3
2
|
// CR-R01: ein offener CR nennt, WAS er aendert (CR-SM-295)
|
|
4
3
|
//
|
|
@@ -179,89 +178,18 @@ function crShouldHaveMilestone(graph) {
|
|
|
179
178
|
}));
|
|
180
179
|
}
|
|
181
180
|
// ---------------------------------------------------------------------------
|
|
182
|
-
// CR-R05: kein Blatt-REQ ohne Bauauftrag (CR-SM-343) — die REQ-Seite von CR-R01.
|
|
183
|
-
//
|
|
184
|
-
// RD-01 fragt nach einem Traeger, R-01 nach einer Verifikation; ob ein REQ einen BAUAUFTRAG
|
|
185
|
-
// hat, fragte niemand. Gemessen am Fremdlauf sigllm: `se-plan` meldete „20 von 20 geordnet",
|
|
186
|
-
// im selben Graphstand hatten 24 von 64 Blatt-REQ keinen Auftrag — genau die, die alle 16
|
|
187
|
-
// offenen FM-03-Risiken mildern.
|
|
188
|
-
//
|
|
189
|
-
// Beauftragt ist ein Blatt-REQ (kein `compose`→REQ-Kind), wenn ein CR eine `relation` (a)
|
|
190
|
-
// direkt darauf traegt oder (b) auf eine FUNC, die es `satisfy`t. **MOD und SYS zaehlen
|
|
191
|
-
// nicht:** ein Modul ist ein Behaelter — mitgerechnet las sigllm 55/64 statt 40/64 und sah
|
|
192
|
-
// gesund aus. Wer ein REQ an einem MOD-/SYS-Traeger beauftragt, zieht die `relation` direkt
|
|
193
|
-
// auf das REQ. FCHAIN ist kein Weg: `CR -relation-> FCHAIN` ist kein TRACE_PATTERN, eine
|
|
194
|
-
// solche Kante waere R-18 — ein FCHAIN-getragenes REQ braucht also ebenfalls die direkte Kante.
|
|
195
|
-
//
|
|
196
|
-
// Jeder CR zaehlt, auch ein abgeschlossener: gebaut ist beauftragt.
|
|
197
|
-
//
|
|
198
|
-
// **Eine gebaute FUNC deckt ohne CR** (CR-SM-375): traegt die FUNC eine gueltige `realRef`
|
|
199
|
-
// oder ist sie `external`, ist der Bauauftrag erledigt — Bestand vor der CR-Disziplin braucht
|
|
200
|
-
// keinen nachgetragenen Auftrag. `concept: true` ist nicht gebaut, auch mit realRef. Fuer
|
|
201
|
-
// MOD/SYS/FCHAIN aendert das nichts: dort deckt nur die direkte `CR -relation-> REQ`.
|
|
202
|
-
//
|
|
203
|
-
// **Ohne CR-Knoten schweigt die Regel** und zaehlt weder im Zaehler noch im Nenner
|
|
204
|
-
// (`RULE_PRECONDITION`, readiness.ts). Ein Repo ohne Plan im Graphen (`cr: docs`) bekaeme
|
|
205
|
-
// sonst auf jedem REQ einen Befund — die Regel wuerde abgeschaltet und waere danach ueberall
|
|
206
|
-
// still (Fail-open-Klasse ND-01/ND-02 vor CR-SM-286).
|
|
207
|
-
// ---------------------------------------------------------------------------
|
|
208
|
-
function funcIsBuilt(e) {
|
|
209
|
-
if (e.attributes?.concept === true)
|
|
210
|
-
return false;
|
|
211
|
-
return e.attributes?.external === true || readRealRef(e.attributes).state === 'bound';
|
|
212
|
-
}
|
|
213
|
-
function leafReqNeedsBuildOrder(graph) {
|
|
214
|
-
const crIds = new Set(graph.elements.filter(e => e.type === 'CR').map(e => e.id));
|
|
215
|
-
if (crIds.size === 0)
|
|
216
|
-
return [];
|
|
217
|
-
const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
|
|
218
|
-
const ordered = new Set(); // CR -relation-> X, oder eine bereits gebaute FUNC
|
|
219
|
-
for (const t of graph.traces) {
|
|
220
|
-
if (t.type === 'relation' && crIds.has(t.source))
|
|
221
|
-
ordered.add(t.target);
|
|
222
|
-
}
|
|
223
|
-
for (const e of graph.elements) {
|
|
224
|
-
if (e.type === 'FUNC' && funcIsBuilt(e))
|
|
225
|
-
ordered.add(e.id);
|
|
226
|
-
}
|
|
227
|
-
const covered = new Set();
|
|
228
|
-
const parents = new Set();
|
|
229
|
-
for (const t of graph.traces) {
|
|
230
|
-
if (t.type === 'relation' && crIds.has(t.source) && typeOf.get(t.target) === 'REQ')
|
|
231
|
-
covered.add(t.target);
|
|
232
|
-
if (t.type === 'satisfy' && typeOf.get(t.source) === 'FUNC' && ordered.has(t.source))
|
|
233
|
-
covered.add(t.target);
|
|
234
|
-
if (t.type === 'compose' && typeOf.get(t.source) === 'REQ' && typeOf.get(t.target) === 'REQ')
|
|
235
|
-
parents.add(t.source);
|
|
236
|
-
}
|
|
237
|
-
return graph.elements
|
|
238
|
-
.filter(e => e.type === 'REQ' && !parents.has(e.id) && !covered.has(e.id))
|
|
239
|
-
.map(req => ({
|
|
240
|
-
rule_id: 'CR-R05',
|
|
241
|
-
severity: 'warning',
|
|
242
|
-
element_id: req.id,
|
|
243
|
-
message: `${req.id} is a leaf REQ with no build order (no CR relation to it or to a FUNC that satisfies it, and no built FUNC satisfies it)`,
|
|
244
|
-
fix_hint: `Add a CR relation through graph_mutate Format-E \`CR-x -relation-> ${req.id}\` — or to the FUNC that satisfies it; a CR on its MOD/SYS carrier does not count`,
|
|
245
|
-
context: {
|
|
246
|
-
element_type: req.type,
|
|
247
|
-
element_name: req.name,
|
|
248
|
-
},
|
|
249
|
-
}));
|
|
250
|
-
}
|
|
251
|
-
// ---------------------------------------------------------------------------
|
|
252
181
|
// Exports
|
|
253
182
|
// ---------------------------------------------------------------------------
|
|
254
183
|
export const CR_RULES = [
|
|
255
|
-
{ id: 'CR-R01', name: 'CR must track', severity: 'warning', evaluate: crMustTrack, domain: ['CR'] },
|
|
256
|
-
{ id: 'CR-R02', name: 'Done requires commit', severity: 'warning', evaluate: crDoneRequiresCommit, domain: ['CR'] },
|
|
184
|
+
{ id: 'CR-R01', name: 'CR must track', severity: 'warning', stage: 10, evaluate: crMustTrack, domain: ['CR'] },
|
|
185
|
+
{ id: 'CR-R02', name: 'Done requires commit', severity: 'warning', stage: 10, evaluate: crDoneRequiresCommit, domain: ['CR'] },
|
|
257
186
|
// CR-SM-243: domain ist 'all', nicht CR — die Regel meldet am getrackten ZIELKNOTEN
|
|
258
187
|
// ("dieser Knoten wird von 2 offenen CRs angefasst"), und der kann jeden Typ haben. Eine
|
|
259
188
|
// Aufzaehlung waere eine Momentaufnahme der heute getrackten Typen, kein Modell; 'all'
|
|
260
189
|
// (= alle Elemente) ist die einzige Menge, aus der der Zielknoten garantiert stammt.
|
|
261
190
|
// Damit ist sie nach R-08/R-18 die dritte 'all'-Regel — bewusst, nicht vergessen.
|
|
262
|
-
{ id: 'CR-R03', name: 'No concurrent mutation', severity: 'warning', evaluate: noConcurrentMutation, domain: ['all'] },
|
|
263
|
-
{ id: 'MS-03', name: 'CR without milestone', severity: 'info', evaluate: crShouldHaveMilestone, domain: ['CR'] },
|
|
264
|
-
{ id: 'CR-R05', name: 'Leaf REQ has a build order', severity: 'warning', evaluate: leafReqNeedsBuildOrder, domain: ['REQ'] },
|
|
191
|
+
{ id: 'CR-R03', name: 'No concurrent mutation', severity: 'warning', stage: 'immer', evaluate: noConcurrentMutation, domain: ['all'] },
|
|
192
|
+
{ id: 'MS-03', name: 'CR without milestone', severity: 'info', stage: 10, evaluate: crShouldHaveMilestone, domain: ['CR'] },
|
|
265
193
|
];
|
|
266
194
|
// CR-SM-236: `policy` wird durchgereicht, auch wo diese Familie heute keine Schwelle hat —
|
|
267
195
|
// ein Sonderweg je Familie waere genau der zweite Pfad, den der Regelsatz verbietet.
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
import type { OntologyGraph } from './ontology.js';
|
|
6
6
|
import type { RuleViolation } from './rules.js';
|
|
7
7
|
import type { MetricPolicy } from './policy.js';
|
|
8
|
+
import { type MarkType, type RuleRole, type RuleStage } from './readiness.js';
|
|
8
9
|
/**
|
|
9
10
|
* All prescribed rule definitions (single source of truth for the catalog).
|
|
10
11
|
*
|
|
11
12
|
* CR-SM-235: `domain` reicht mit durch — die Grundgesamtheit, ueber die eine Regel feuert.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* wenn es keine zweite gibt.
|
|
13
|
+
* Die Faelligkeit (`isDue`) liest sie hier statt eine eigene Tabelle zu fuehren. Eine zweite
|
|
14
|
+
* Tabelle kann nicht hinterherhinken, wenn es keine zweite gibt.
|
|
15
15
|
*/
|
|
16
16
|
/** Profile groupings — see CATALOGS. */
|
|
17
17
|
export type ProfileId = 'default' | 'se' | 'coding' | 'conformance';
|
|
@@ -22,6 +22,10 @@ export declare const ALL_RULE_DEFS: ReadonlyArray<{
|
|
|
22
22
|
domain: readonly string[];
|
|
23
23
|
/** CR-SM-285: das Profil reist mit der Regel, statt aus ihrer ID geraten zu werden. */
|
|
24
24
|
profile: Exclude<ProfileId, 'default'>;
|
|
25
|
+
/** CR-SM-395: Stufe und Rolle von der Regeldefinition; `mark` ist daraus BERECHNET (`null` bei `'immer'`). */
|
|
26
|
+
stage: RuleStage;
|
|
27
|
+
role?: RuleRole;
|
|
28
|
+
mark: MarkType | null;
|
|
25
29
|
}>;
|
|
26
30
|
export declare function getRuleDefsForProfile(profile: ProfileId): typeof ALL_RULE_DEFS;
|
|
27
31
|
/**
|