Requirements-Coverage
codecharter req coverage und das
MCP-Tool req_coverage
melden ein Coverage-Modell aus beiden Richtungen der Traceability,
berechnet von einem einzigen gemeinsamen Aggregator, damit die beiden
Oberflächen bei keiner Zahl auseinanderlaufen können:
- Requirement → Code: Hält die Evidenz jedes Requirements gerade noch?
- Code → Requirement: Welche Methoden tragen überhaupt keine
[Satisfies]-Rechtfertigung, und warum?
Beide Hälften nutzen dieselben Evaluatoren und Indizes, die schon Requirements und Requirements über MCP beschreiben, kein Criterion, Verdict oder Justification-Walk wird für diesen Report neu implementiert.
Wofür die Zahl da ist und wer sie liest
Coverage beantwortet zwei Fragen, die im Projektalltag von verschiedenen Personen gestellt werden. Die Projektleitung und der Auftraggeber fragen nach dem Stand der Zusagen: hält jedes Requirement noch, und wo hält es nicht mehr. Das Entwicklungsteam fragt nach der Gegenrichtung: welcher Teil des Codes ist überhaupt an eine Zusage gebunden und welcher steht unbegründet daneben.
Beide Fragen aus einem Lauf zu beantworten ist der eigentliche Zweck. Eine Traceability-Matrix, die nur die erste Richtung kennt, sieht immer gut aus, solange man nur die verankerten Stellen anschaut. Erst die Gegenrichtung zeigt, wie viel Code gar nicht erst vorkommt.
Der Befehl ist zuerst ein Report. Er lässt euren Build nicht scheitern, bis ihr das ausdrücklich einschaltet, siehe Das Tolerated-Legacy-Gate weiter unten.
Ein Beispielkopf, gelesen
req coverage — scope: solution
requirements: 12, criteria: 27, anchors: 9 (anchored requirements: 8)
code: 340 methods — justified 41.18% (140), accounted 46.47% (158 incl. 18 infrastructure), unjustified 182.
Von zwölf Requirements tragen acht überhaupt einen Anchor, vier sind also noch nirgends im Code gebunden. Von 340 Methoden sind 140 über einen Justification-Walk von einem Requirement aus erreichbar, weitere 18 sind bewusst als Infrastruktur eingeordnet, und 182 stehen ohne Begründung da.
Was diese Zahlen nicht sagen: dass die 140 Methoden das Richtige tun. Sie sagen, dass für sie jemand eine Bindung eingetragen hat, die ein Reviewer im Diff gesehen hat. Ein niedriger Wert am Anfang ist normal und kein Mangel des Codes, er ist der ehrliche Startpunkt.
Requirement → Code: Rollups
Jedes Criterion eines Requirements wird genau einmal ausgewertet, mit
demselben Pin-Evaluator, den req check --with-code und req_verify
verwenden, und dann zu einem einzigen Worst-of-Verdict aufgerollt, nach
dieser festen Rangfolge:
| Rang | Verdict | Warum dieser Rang |
|---|---|---|
| 5 (schlechtest) | violated |
Nachweislich gebrochen gegen den aktuellen Code. |
| 4 | stale |
Eine verfallene positive Aussage: hat einmal gehalten, die gepinnte Evidenz hat sich seitdem geändert, in einer alten Traceability-Matrix aktiv irreführend, deshalb rangiert es vor einer ehrlichen Nicht-Antwort. |
| 3 | unknown |
Auswertung konnte sich nicht entscheiden. Eine ehrliche Nicht-Antwort, kein Zeichen für Zustand oder Verfall. |
| 2 | open |
Eine deklarierte Lücke, das am wenigsten alarmierende nicht-grüne Ergebnis, weil bewusst statt entdeckt. |
| 1 (best) | holds |
Jedes Criterion hält. |
Nie unverdient grün. Ein Requirement ohne Criteria rollt nicht zu
holds auf, es rollt zu open mit einem unbound-Grund von
no-criteria auf. Dasselbe gilt für null Anchors (no-anchors) und
Anchors, die alle nicht aufgelöst werden konnten (anchors-unresolved).
Dasselbe Prinzip "nie Holds melden, ohne geprüft zu haben", das die
Criterion-Evaluatoren schon anwenden, nur auf Requirement-Ebene erweitert.
Der Frontmatter-Key status fließt nie in das Rollup ein, er wird
daneben gemeldet. Ein Requirement, dessen deklarierter Status vom Rollup
abweicht (status: implemented bei einem Rollup ungleich holds, oder
status: open bei einem Rollup von holds), wird als Status-Divergenz
gemeldet: zur Sichtbarkeit, nie um das Rollup zu ändern.
Code → Requirement: klassifizierte Methoden
Jede Methode im analysierten Codemodell fällt in genau einen von drei disjunkten Buckets, gemeldet als zwei Kopfzahlen, die nie zu einer einzigen mehrdeutigen Zahl zusammengeführt werden:
justified: von mindestens einem Justification-Walk eines Nicht-Infrastructure-Requirements erreicht.infrastructure-declared: siehe Infrastructure-Exemption.- unjustified, weiter unterteilt in:
dead-candidate: kein Caller ist für den Justification-Walk sichtbar, und die Methode ist auch kein Dispatch-Ziel (weder eine Interface-Implementierung noch ein Override noch eine Implementing-Method erreicht sie). Nie als bewiesen ungenutzt zu verstehen, nur als nicht erreichbar über die sichtbaren Kanten dieses Walks (keine Reflection, kein Delegate-Aufruf, kein dynamischer Dispatch wird verfolgt). Der Report sagt immer "candidate", nie "dead" allein.undocumented-requirement-candidate: hat sichtbare Caller, aber kein Anchor erreicht sie über seinen Justification-Walk. Trägt einennearestAnchoredNeighbour: die nächste justified Method, gefunden durch eine begrenzte Vorwärtssuche über die eigenen ausgehenden Aufrufe der Kandidatenmethode (nie über Caller, auf vier Hops gedeckelt), zusammen mit den Requirement-Ids, die sie rechtfertigen,null, wenn die Grenze erreicht wird, nie eine Vermutung. Genau dieser Hinweis speistreq_draftbeim Entwurf eines neuen Requirements.
justified_percent = justified / total_methods;
accounted_percent = (justified + infrastructure_declared) / total_methods.
Hat der Corpus überhaupt keine [Satisfies]-Anchors, melden beide
Prozentwerte 0 mit einem expliziten Hinweis, und es wird keine
klassifizierte Liste erzeugt, ein Repository, das Anchoring noch nicht
eingeführt hat, soll beim ersten Lauf keine zehntausend "dead candidates"
bekommen.
Infrastructure-Exemption
Ein Requirement mit kind: infrastructure befreit genau die Methoden, die
seine [Satisfies]-Anchors direkt benennen, ein Method-Anchor befreit
diese Method, ein Type-Anchor befreit alle Methods dieses Typs. Diese
Befreiung ist deklariert, nie erraten: Es gibt keine Heuristik, die von
sich aus ableitet, "das sieht nach Glue-Code aus", eine fälschlich als
Infrastructure eingestufte Fachlogik ist deshalb immer eine sichtbare
Attribut-Änderung in einem Diff, nie eine stille Klassifikator-Entscheidung.
Sie ist außerdem bewusst nicht transitiv. Ein Anchor auf Program oder
eine DI-Registrierungsklasse befreit nicht alles, was diese Methoden
aufrufen, nur die direkt verankerten Deklarationen selbst. Eine transitive
Befreiung würde ein Attribut am Composition-Root die halbe Solution
befreien lassen, genau die Art Fehletikettierung, die dieser Report
sichtbar halten soll. Erreicht der Justification-Walk eines
Nicht-Infrastructure-Requirements eine Methode, die auch direkt als
Infrastructure befreit ist, gibt der Report einen Info-Hinweis
req-infrastructure-overreach aus, ein Signal, dass das
Infrastructure-Label möglicherweise auf Code angewendet wurde, von dem
Fachlogik tatsächlich abhängt.
Diff-Scope
--git-ref/git_ref (oder rohes diff-Text über MCP) schränkt die
klassifizierte Methoden- und die Rollup-Liste auf das ein, was der Diff
berührt. Jede Summe und jeder Prozentwert, einschließlich
justified_percent und accounted_percent, bleibt corpus-weit, nur die
beiden Listen schrumpfen, mit einem scope: diff-Hinweis, der das benennt.
Dasselbe Muster wie graph_hubss eigenes scope-Feld: eine
diff-eingeschränkte Ansicht verengt nie still, was eine Summenzahl zu
umfassen behauptet.
Das Tolerated-Legacy-Gate
Coverage meldet eine Zahl ab dem Moment der Einführung, selbst auf einer
großen, nur teilweise verankerten Codebase, aber diese Zahl muss nicht erst
100% sein, um nützlich zu sein, und sie soll auch nicht still zurückfallen.
req coverage liefert deshalb ein Einweg-Ratchet, dasselbe Muster wie
die Findings-Baseline:
codecharter req coverage --write-baselineschreibt einmalig die Identität jeder aktuell unjustified Method (Roslyn-Deklarations-Id, mit Fallback auf einen Graph-Key, wenn der Compiler keine erzeugt hat, ein Rename macht einen Fallback-Eintrag absichtlich ungültig) nach.codecharter/req/coverage-baseline.json.- Ab dann rendert eine in der Baseline stehende Method als
tolerated; eine neue unjustified Method, eine, die nicht in der Baseline steht, rendert alsnewund beendet den Lauf, sobald das Gate aktiv ist, mit einem Fehler-Exit. - Das Gate selbst (Exit ungleich 0 bei jeder
new-Method) ist Opt-in, hinterreq.coverage.gate.enabled: truein.codecharter/config.yml(Defaultfalse). Die Mechanik und der fehlschlagende Exit-Code existieren und sind getestet, unabhängig davon, ob ein Repository bereits opted-in hat.
Als Infrastructure deklarierte Methoden sind bewusst befreit und zählen nie
als Baseline-Schuld, nur dead-candidate- und
undocumented-requirement-candidate-Methoden werden verfolgt. Die Baseline
weitet sich nie von selbst aus: Nur --write-baseline schreibt sie, und
wiederholtes Schreiben derselben Menge ist bytegenau identisch (sortiert,
dedupliziert, ohne Zeitstempel).
Zuerst Report, dann Gate: ein Goodhart-Hinweis
[Satisfies]-Attribute sind im Diff sichtbar und werden im Review getragen. Die Kennzahl wird erst zum Ziel, wenn das Gate existiert.
[Satisfies]-Anchors landen im Pull-Request-Diff und werden dort gereviewt
wie jede andere Codeänderung auch. Coverage macht daraus eine Zahl, die
sichtbar ist und, sobald ihr das Gate einschaltet, auch nicht mehr still
zurückfallen kann. Direkt auf die Prozentzahl zu optimieren, etwa
Requirements wahllos an Code zu ankern, nur um justified_percent zu
erhöhen, verfehlt den Zweck: Die Zahl ist nur so vertrauenswürdig wie die
Anchors, die ein Mensch tatsächlich reviewt hat.