Zum Inhalt springen

req-coverage

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 einen nearestAnchoredNeighbour: 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 speist req_draft beim 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:

  1. codecharter req coverage --write-baseline schreibt 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.
  2. Ab dann rendert eine in der Baseline stehende Method als tolerated; eine neue unjustified Method, eine, die nicht in der Baseline steht, rendert als new und beendet den Lauf, sobald das Gate aktiv ist, mit einem Fehler-Exit.
  3. Das Gate selbst (Exit ungleich 0 bei jeder new-Method) ist Opt-in, hinter req.coverage.gate.enabled: true in .codecharter/config.yml (Default false). 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.