# 0.8.0: Refactoring, das sich prüfen lässt – festhalten, belegen oder fragen

*25. Oktober 2026 · Release · geschrieben für 0.8.0*

Bei 0.8.0 geht es darum, **wie die Suite Code ändert**: was sie vor,
während und nach einem Umbau prüft, und wann sie aufhört. Es ist kein
Breaking Release. Ein Projekt auf 0.7 muss nichts tun, bevor es die Suite
wieder aufruft; was dir auffallen wird, steht am Ende unter
[Was sich für dein Projekt ändert](#was-sich-für-dein-projekt-ändert).

## Woher dieses Release kommt

Der größte Teil von 0.8.0 begann mit einem Blick auf
[pstack](https://github.com/cursor/plugins/tree/main/pstack), ein Plugin
von Lauren Tan für die Arbeit mit Agenten in einer Codebasis. Es ist anders
gebaut: interaktiv, ein Router-Skill mit vielen Playbooks und Skills für
je ein Prinzip, während diese Suite eine Kette von Entscheidungspunkten
ist, die dein Tracker antreibt. Aber viele seiner Ideen zum Refactoring
lassen sich übertragen: Verhalten festhalten, bevor sich Struktur bewegt,
eine Änderung daran messen, wie viel leichter der Code zu lesen ist, sagen,
warum eine Änderung sicher ist, aufhören, wenn man festhängt, statt es
ewig weiter zu versuchen.

Ich bin diese Ideen durchgegangen, habe jede gegen die Skills der Suite
geprüft und die passenden in die eigene Planung, Umsetzung und das Review
der Suite eingebaut. Die meisten Abschnitte unten sind eine davon.

Eines vorweg: Seit 0.7 nutzt ein Run die eigenen Skills deines Projekts
fürs Planen und Umsetzen, wo es welche hat, und die Verfahren der Suite
nur als Rückfall. Was dieser Beitrag beschreibt, ist, was diese Verfahren
tun. Plant und setzt dein Projekt mit eigenen Skills um, siehst du weniger
davon.

Derselbe Vorrang sollte es erlauben, pstack und diese Suite nebeneinander
zu nutzen: die Skills von pstack für die Planung und die Änderung, diese
Suite, um die Arbeit zu finden und bis zum Merge Request zu bringen.
Ausprobiert habe ich diese Kombination noch nicht.

## Ein Test, bevor sich etwas bewegt

Ein Refactoring verspricht, dass sich das Verhalten nicht ändert. Das lässt
sich nur gegen Verhalten prüfen, das **vor** der Änderung festgehalten
wurde. Deshalb beginnt ein strukturelles Ticket jetzt mit einem **Pinning
Test**: einem Test, der festhält, was der Code heute tut, und grün ist,
bevor sich irgendeine Struktur bewegt. Der Plan nennt ihn, die Umsetzung
committet ihn zuerst, das Review prüft, dass er zuerst kam, und der Merge
Request berichtet, dass er vor der Änderung grün war. Wo es schon Tests
gibt, die den Code erreichen, sind diese der Pin.

Die Idee ist Michael Feathers' **Characterization Test** aus *Working
Effectively with Legacy Code*: ein Test, der festhält, was der Code
tatsächlich tut, nicht was er tun sollte
([Überblick auf Wikipedia, englisch](https://en.wikipedia.org/wiki/Characterization_test)).

Ein Typcheck oder ein Linter ist kein Pin. Wo sich ein Pin nicht mit
vertretbarem Aufwand schreiben lässt, baut der Run nicht ungeprüft um: Er
fragt dich.

Für einen Umbau, der über mehrere Schritte reicht und etwas Vergleichbares
erzeugt, etwa Text, eine Datei oder Daten, kann der Pin ein
**Vergleichsskript** sein. Es lässt den alten und den neuen Code auf den
eigenen Beispieleingaben deines Projekts laufen (Fixtures, Beispieldateien,
Eingaben deiner Tests, nie Produktionsdaten) und vergleicht die Ausgaben.
Der Merge Request sagt, bei wie vielen Eingaben sie übereinstimmten.

Ein Tooling-Ticket bekommt keinen neuen Test: Dort ändert ein Tool den
Code, und deine bestehende Testsuite ist das, was einen Unterschied
bemerkt.

## Leichter nachvollziehbar, oder gar nicht

Eine Änderung kann korrekt sein und trotzdem nichts verbessern. 0.8.0 führt
dafür ein Maß ein: die **Leselast**, den Aufwand, den es kostet, dem Code
zu folgen. Der Plan nennt die Reibung, um die es im Ticket geht; das Review
prüft jeden Teil der Änderung dagegen. Ein Teil, der die Leselast nirgends
senkt, wird nicht nachgebessert, sondern **herausgenommen**. Bleibt nichts
übrig, endet der Run ohne Merge Request und hinterlässt auf dem Ticket
einen Kommentar, der sagt, warum.

Die Leselast folgt Artem Zakirullins
[Cognitive load is what matters](https://minds.md/zakirullin/cognitive)
(englisch): Ein Teil des Aufwands kommt aus dem Problem selbst und bleibt;
der Teil, der aus dem Aufbau des Codes kommt, etwa Schichten, denen man
folgen muss, und Zustand, den man im Kopf behalten muss, ist der Teil, den
ein Refactoring senken kann.

Dahinter steht eine Aufteilung der Vorgaben der Suite in **Regeln** und
**Prinzipien**. Regeln sind wenige und gelten ohne Ausnahme: Ein
Refactoring erhält das Verhalten, ein Test hält es zuerst fest, Arbeit
passiert auf einem eigenen Branch. Prinzipien helfen bei Abwägungen, die
vom Code abhängen, und der Plan nennt jedes Prinzip, das eine Entscheidung
verändert hat. Zwei davon:

- Eine API, von der etwas **außerhalb** des Projekts abhängt, wird Stück
  für Stück ersetzt, Alt und Neu nebeneinander. Eine **interne** wird in
  einem Schritt ersetzt: Alle Aufrufer ziehen um und die alte API wird
  gelöscht, sodass keine Kompatibilitätsschicht zurückbleibt. Kann die
  Suite es nicht sagen, nimmt sie an, dass jemand außerhalb davon abhängt.
- Toter Code wird vor dem Umbau entfernt, und nach jeder Umbenennung wird
  in Strings, Docs und Konfiguration gesucht, nicht nur dort, wo ein Tool
  sie findet.

## Warum die Änderung sicher ist

Der Pin deckt ab, was ein Nutzer des Codes sieht. Eine Umbenennung, eine
Verschiebung oder eine Löschung kann trotzdem etwas brechen, das er nie
erreicht: einen Namen in einem String, eine Konfigurationsdatei, einen
Container-Eintrag, gespeicherte Daten. Wo eine Änderung einen solchen Namen
berührt, nennt der Merge Request jetzt **die eine Tatsache, derentwegen die
Änderung sicher ist**, und wie weit das belegt wurde: im Quellcode
nachgelesen, gezeigt, dass der schlechte Fall nicht eintreten kann,
ausgeführt oder in der Anwendung nachgestellt. Was sich nicht belegen ließ,
steht als unbelegt da, damit du weißt, wo du hinschauen musst. Betrifft
eine solche Tatsache Sicherheit, Berechtigungen, eine Migration oder
Nebenläufigkeit und bleibt unbelegt, fragt der Run, bevor er weitermacht.

## Merge Requests, die ein Mensch reviewen kann

[0.7 hat es möglich gemacht](https://continuous-refactoring.de/de/blog/release-0-7-0.md#die-grenzen-fallen-weg),
ein Ticket in mehreren Merge Requests zu liefern. 0.8 nutzt das für
Tooling-Tickets: Der Plan **schätzt zuerst die Größe** der Änderung, mit
dem Probelauf des Tools oder einer Zählung der Dateien. Wäre ein Merge
Request zu viel zum Reviewen, wird der Plan an einer Naht von Art oder
Risiko in Teile geschnitten: Umschreibungen, die den Code nur anders
ausdrücken, auf der einen Seite, Regeln, die ändern, was er tut, auf der
anderen. Jeder Teil lohnt sich allein zu mergen, und der nächste Run nimmt
den ersten Teil, der noch nicht gemergt ist. Es gibt keine feste
Dateianzahl, denn dieselbe Anzahl kann leicht oder schwer zu reviewen sein.

## Was das Review findet, und was ein Bot sagt

Das eigene Review der Suite **sortiert** jetzt seine Befunde:

- **beheben**: Die Arbeit geht dafür zurück. Der Normalfall.
- **vermerken**: stimmt, geht aber über den Plan hinaus. Der Befund wird
  im Merge Request genannt und liegen gelassen, damit die Änderung die
  Größe behält, der du zugestimmt hast.
- **verwerfen**: doch kein Befund, mit konkreter Begründung. Verworfene
  Befunde stehen im Abschlussbericht, sodass du einen davon kippen kannst.

Ein Befund gegen den Plan, oder eine Änderung an dem, was die Anwendung
tut, wird nie verworfen.

Dieselbe Sorgfalt gilt für Kommentare auf einem bestehenden Merge Request.
Nimmt ein Run ein Ticket auf, dessen Merge Request Review-Kommentare hat,
behebt er nicht mehr einfach alle:

- Der Kommentar eines **Bots** wird zuerst geprüft, indem der Test oder
  Befehl ausgeführt wird, den er nennt, wo das billig ist. Dann wird er
  behoben, mit Begründung verworfen oder erfragt. Alles zu Sicherheit,
  Daten, Berechtigungen, Migrationen oder Nebenläufigkeit wird immer
  erfragt.
- Der Kommentar eines **Menschen** wird behoben oder beantwortet, nie
  verworfen.

Jeder bearbeitete Kommentar bekommt eine kurze Antwort. Threads als gelöst
markieren bleibt Sache des Reviewers.

## Aufhören, wenn es nicht weitergeht

Ein Run hört jetzt auf, wenn **derselbe** rote Check oder Review-Befund
zwei Fix-Versuche übersteht. Keine Anzahl von Runden entscheidet das, nur
dass der Run nicht weiterkommt. Der Branch bleibt lokal und wird nicht
gepusht; das Ticket bekommt eine offene Frage in klaren Worten, die sagt,
was versucht wurde und wo es scheitert. Kein Check wird gelöscht, gelockert
oder ignoriert, um ihn grün zu bekommen.

## Ein Abschlussbericht, der mit dir beginnt

Der Abschlussbericht eines Runs beginnt jetzt damit, **wie viele Dinge auf
dich warten**, oder dass keines wartet. Zu jedem sagt er, was er empfiehlt
und was passiert, wenn du nicht antwortest. Und wo sich eine Frage zu
Verhalten, Ausgabe oder Timing klären lässt, indem man etwas Harmloses
ausführt, führt der Entwurf das jetzt aus, statt dich zu fragen.

## Kleinere Änderungen

- Der Merge Request eines PHPStan-Baseline-Tickets darf jetzt einen neuen
  Baseline-Eintrag hinzufügen, solange die Baseline insgesamt kürzer wird;
  er nennt den Eintrag. Ein Befund, der dauerhaft ignoriert werden soll,
  kommt erst in die `phpstan.neon`, wenn du es erlaubst.

## Was sich für dein Projekt ändert

Du musst nichts tun. Was dir auffallen wird:

- **Merge Requests sagen mehr**: den Pin und dass er vor der Änderung grün
  war, wo der Code leichter nachvollziehbar wurde, die Vermerke des Reviews
  und bei Umbenennungen und Verschiebungen, warum die Änderung sicher ist.
- **Große Tooling-Änderungen kommen in Teilen**, jeder als eigener Merge
  Request.
- **Manche Runs enden ohne Merge Request**: wenn das Review findet, dass
  kein Teil der Änderung den Code leichter nachvollziehbar macht, oder wenn
  ein Fix nicht hält. Das Ticket trägt dann einen Kommentar oder eine
  offene Frage.
- **Review-Kommentare bekommen Antworten**, und Bot-Kommentare werden
  unter Umständen mit Begründung verworfen statt behoben.

Die [FAQ](https://continuous-refactoring.de/de/docs/faq.md) erklärt die Gründe hinter den meisten dieser
Änderungen, zum Beispiel
[warum ein strukturelles Ticket mit einem Test beginnt](https://continuous-refactoring.de/de/docs/faq.md#warum-beginnt-ein-strukturelles-ticket-mit-einem-test-ein-tooling-ticket-aber-nicht)
und
[warum die Suite nicht einfach tut, was ein Review-Bot verlangt](https://continuous-refactoring.de/de/docs/faq.md#warum-tut-die-suite-nicht-einfach-was-ein-review-bot-verlangt).

## Changelog 0.8.0

Die Einträge aus dem Upstream-Changelog zu 0.8.0, unverändert auf Englisch. Auch auf GitHub: [Release 0.8.0](https://github.com/Art4/continuous-refactoring/releases/tag/0.8.0).

- A structural refactoring ticket now starts with a Pinning Test — a test that holds today's behaviour and passes before any restructuring — which the plan names, the review checks, and the merge request reports; where none can be written at a reasonable cost, the run asks instead of restructuring unchecked.
- For a restructuring that reaches over several steps behind one edge, the plan can pin the behaviour with a script that runs the old and the new code on the project's own sample inputs and compares the outputs; the merge request says on how many inputs they agreed.
- A structural refactoring now has to make the code easier to follow: the review takes off any change that lowers the reader's effort nowhere, and the run ends without a merge request when none is left. Judgement aids — reader load, the Strangler Fig, the technique vocabulary — now live in their own principles reference, apart from the few binding rules.
- Plans now tell apart an API someone outside depends on (replaced piece by piece, old and new side by side) from an internal one (all callers moved and the old API deleted in one step), remove dead code before reshaping, and every rename is searched for across strings, docs and configuration.
- When a restructuring renames, moves or deletes something that can be referred to from outside the tested interface, the merge request now states the one fact the change is safe because of and how far it was proved — or that it is not proved.
- A plan for a tooling ticket now estimates how big its merge request would be and, where a person could not review it, is cut into parts at a seam of kind or risk, each worth merging alone.
- The suite's own review now sorts its findings into fix, note and dismiss: what the plan cannot fix is named in the merge request, a dismissal carries its reason in the closing report, and a finding against the plan is never dismissed.
- When a run works a ticket that already has a merge request, it now sorts the review comments instead of fixing all of them: a bot's comment is checked and then fixed, dismissed with a reason, or asked about (security, data and permissions are always asked); a person's comment is fixed or answered, never dismissed; each handled comment gets a short reply, and threads are left for the reviewer to resolve.
- A run now stops when the same failing check or review finding survives two fix attempts: the branch stays local, the ticket gets an open question in plain words, and no check is deleted, loosened or ignored to get green.
- The design now settles a question about behaviour, output or timing by running something harmless instead of asking you, and a run's closing report opens with how many things wait for you and says what is recommended and what happens if you do not answer.
- A PHPStan baseline ticket's merge request may now add a new baseline entry as long as the baseline gets shorter overall, and names it; a finding to ignore for good, or a baseline that would not get shorter, becomes an ignore rule in `phpstan.neon` only after the human allows it.
- Contributors are told to turn a rule that is broken twice into a check, and the validator now checks that README and docs cite no ADR or PR numbers and no scratch files.

---

Fragen oder Anmerkungen zu diesem Beitrag? Gern in den [GitHub Discussions (Q&A)](https://github.com/Art4/continuous-refactoring/discussions/categories/q-a).
