0.10.0: Tools auf Anfrage, dein BC-Versprechen und ein Agent, der steuert
Bei 0.10.0 geht es darum, das Richtige vorzuschlagen, und weniger
davon. Manche Tools werden jetzt nur noch eingerichtet, wenn du danach
fragst, die Suite erfährt, was dein Projekt dem Code außerhalb davon
verspricht, und ein Run lässt sich von einem anderen Agenten steuern. Es
gibt eine inkompatible Änderung, nur für ein eigenes Pack: after einer
Stufe heißt jetzt waits_for. Alles andere braucht nichts von dir; was dir
auffallen wird, steht am Ende unter
Was sich für dein Projekt ändert.
Tools auf Anfrage
Bis 0.9 schlug jeder Scan jedes Tool vor, das weder eingerichtet noch abgelehnt war. Bei manchen Tools war das Rauschen. Semgrep findet viel in Code, der jahrelang ohne Guardrails gewachsen ist, und fast nichts in einem jungen Projekt, das sie von Anfang an hatte. Mutation Testing führt deine Tests hunderte Male aus, was sich erst lohnt, wenn sie gut genug sind, um beurteilt zu werden.
Deshalb gibt es ein paar Tools jetzt auf Anfrage: Semgrep, die Taint-Analyse von Psalm, Secret Detection und das neue Mutation Testing mit Infection. Sie werden nie ungefragt angeboten und blockieren nie einen Track. Jeder Scan-Bericht nennt, was auf Anfrage verfügbar ist; nenne eines im Aufruf, und es wird vorgeschlagen wie jedes andere Tool:
/continuous-refactoring richte Semgrep ein
/continuous-refactoring scanne inklusive der Tools auf Anfrage
Zwei Tools haben eher ihren Platz gewechselt als ihren Status:
- Composer Audit gibt es nicht mehr als Tool. Ein Pipeline-Check schlägt nur bei Advisories fehl, die bekannt sind, wenn sich der Code ändert; eines, das nächsten Monat gegen dasselbe Lockfile erscheint, wird nie gesehen. Die erste Housekeeping-Vorlage enthält jetzt ein wiederkehrendes Dependency-Audit, für jeden Paketmanager, und auditiert in jedem Zyklus.
- Der Secret-Scan wartet nicht mehr auf einen Scanner in deinem Projekt. Housekeeping nutzt den, den du hast, oder führt einen eigenen aus (etwa gitleaks), ohne deinem Projekt etwas hinzuzufügen. Der Push-Schutz einer Forge zählt als eingerichtete Secret Detection.
Die Diagramme auf den Pack-Seiten zeigen den Status eines Tools jetzt am Rahmen seiner Stufen, gestrichelt für auf Anfrage, gepunktet für erkannt, mit einer Legende darunter. Siehe Pack php.
Dein BC-Versprechen
Ob die Suite eine alte API löschen darf, hängt davon ab, wer sie sonst noch nutzt. Bis 0.9 hat sie geraten: Wo sie es nicht sagen konnte, nahm sie an, dass jemand davon abhängt. Jetzt fragt sie. Das BC-Versprechen ist das eigene Dokument deines Projekts, das sagt, was für Code außerhalb des Projekts stabil bleibt, die Nutzer deines Pakets oder die Addons deiner Anwendung, und wie eine inkompatible Änderung erlaubt ist: eine neue Major-Version unter SemVer oder eine Deprecation-Frist, gezählt in Releases und Monaten, wie manche Anwendungen sie ihren Addons versprechen. Die Suite modelliert keins von beiden; sie liest dein Dokument.
- Das Onboarding fragt danach, und ein Projekt, das schon vorher
onboardet wurde, wird einmal zu Beginn seines nächsten Runs gefragt. Die
Antwort, ein Pfad oder
none, kommt indocs/agents/issue-tracker.md. - Code, den das Versprechen abdeckt, wird mit Alt und Neu nebeneinander restrukturiert, und der alte Weg wird so als deprecated markiert, wie dein Versprechen es sagt. Sein Entfernen wird später als gewöhnliches Ticket vorgeschlagen, nie von einem Refactoring vorgenommen.
- Unter einem SemVer-Versprechen mit getaggtem Release bekommt das Safety Net Roaves Backward Compatibility Check, der in der CI läuft, bevor die Restrukturierungen der Suite beginnen. Rectors Type Coverage wartet darauf.
- Wo dein Versprechen das Erweitern nicht finaler Klassen abdeckt oder
noch nicht geklärt ist, nimmt Rectors Type Coverage einen BC-sicheren
Weg. Einer Methode einen Parameter- oder Rückgabetyp hinzuzufügen, bricht
jede Klasse außerhalb deines Projekts, die sie überschreibt. Deshalb
schaltet die Suite Rectors eigenes Type-Declaration-Set ab und nimmt
stattdessen
art4/rector-bc-library, eine Library, die ich maintaine: dieselben Regeln, aber nur dort angewendet, wo die Änderung keinen BC-Bruch bedeutet. Das Paket kommt als Dev-Abhängigkeit dazu und unterstützt dieselbe Einführung in Levels.
Die FAQ erklärt die Gründe.
Tests, die sagen, was sie abdecken
Ein Coverage-Bericht zählt eine Klasse als abgedeckt, wenn irgendein Test sie ausführt, auch der Test einer anderen Klasse, der nur durch sie hindurchläuft. Die Suite liest diese Zahlen, um Code ohne Tests zu finden, also sah eine solche Klasse getestet aus, obwohl niemand sie testete. Nach dem Coverage-Floor folgen für PHP-Projekte zwei weitere Stufen:
- Jeder Test nennt die Klasse, die er abdeckt.
- Dann, auf PHPUnit 10 oder neuer, auch, was er nur nutzt, sodass ein Test fehlschlägt, der Code ausführt, den er nicht nennt. Integrations- und funktionale Tests werden als mittlere und große Tests markiert und brauchen keine solche Liste. Pest bietet keine Möglichkeit zu nennen, was ein Test nutzt, also endet es bei der ersten Stufe.
Nur zu zählen, was ein Test testen will, kann die gemessene Coverage senken. Der Merge Request schreibt die fehlenden Tests; wo das nicht passt, setzt er den Floor einmal auf den neu gemessenen Wert und sagt das.
Zwei verwandte Änderungen: Eine Restrukturierung, die eine neue Klasse anlegt, gibt ihr jetzt einen eigenen Test, zuerst geschrieben, statt sich darauf zu verlassen, dass der Test des alten Codes grün bleibt. Und bevor ein Run einen Merge Request öffnet, führt er jedes Gate, das deine CI über das ganze Projekt ausführt, etwa den Coverage-Floor, vollständig aus und nennt jedes, das er lokal nicht ausführen kann.
Rector vor PHPStan, und nie über dein PHP hinaus
- PHPStan Level 4 wartet auf Rectors Dead-Code-Set, Level 5 auf Type Coverage. Rector räumt einen Teil der Baselines dieser Levels mechanisch ab, und jeder Befund, den es abräumt, ist einer, den niemand von Hand behebt. Das Rector-Set abzulehnen gibt das Level frei.
- Rectors PHP-Set hört bei der PHP-Version auf, die deine
composer.jsondeklariert. Den Code für ein neueres PHP umzuschreiben ist dasselbe Versprechen wie das Minimum anzuheben, und das ist eine Produktentscheidung. Die Stufe „PHP Version Floor“ ist damit weg; die Suite hebt dein deklariertes Minimum nicht mehr an. - Rectors PHPUnit-Set ist eine Guardrails-Stufe und hält das Safety Net nicht mehr auf.
Ein Agent, der die Runs steuert
/continuous-refactoring kann jetzt von einem Agenten gestartet werden,
wie es /continuous-housekeeping schon konnte. Das neue
Driver-Playbook beschreibt einen solchen Agenten: Er
ruft einen autonomen Run nach dem anderen auf, jeden in einem eigenen
Subagenten, sodass jeder Run mit frischem Kontext beginnt, prüft jeden
Merge Request in einem zweiten Subagenten, mergt, was besteht (oder
kommentiert nur, wenn du „nur Review“ wählst), und fragt dich nur, wo die
Entscheidung bei dir liegt. Am Ende des Playbooks steht ein Prompt zum
Kopieren in dein Projekt.
Drei Änderungen machen das möglich und helfen jedem Run:
- Ein genanntes Ticket grenzt den Run ein. „Ticket 42“ durchsucht nicht mehr erst alle offenen Tickets, und der Abgleich prüft nur den Merge Request dieses Tickets. Ein Run, der für ein Ticket gestartet wurde, schreibt nichts an die anderen.
- Eine offene Frage wird nach Inhalt beantwortet, nicht nach Autor. Das Warten endet beim ersten Kommentar, der weder der Plan der Suite noch eine weitere ihrer Fragen ist, egal von welchem Account, denn Agenten posten oft mit deinem. Eine Antwort, die das Ticket ablehnt, wird als Ablehnung festgehalten.
- Ein Tool, dessen Beurteilung offen bleibt, wird mit seiner Frage am neuen Ticket angelegt, sodass ein autonomer Run es nicht mehr auslässt und bei jedem Scan erneut aufbringt.
Die bekannten Einschränkungen nennen die zwei Haken: Der Kommentar eines Agenten an einem wartenden Ticket zählt als deine Antwort, und ein Run in einem Subagenten macht seine Review-Schritte im eigenen Kontext.
Was sich für dein Projekt ändert
Eine Sache gibt es zu tun, nur wenn du ein eigenes Pack hast: Benenne den
Schlüssel after jeder Stufe in waits_for um. Ein Pack, das noch after
sagt, wird mit einer Meldung abgelehnt, die genau das sagt. Wenn du schon
dabei bist: Ein Tool von dir, das eine zweite Stufe bekommt, braucht einen
eigenen Slug; benenne das Tool um, nicht die Stufe, damit deine Tickets und
Ablehnungen ihre Namen behalten.
Was dir auffallen wird:
- Der nächste Run fragt nach deinem BC-Versprechen, einmal, und macht weiter.
- Ein offenes Ticket „Composer Audit“ oder „PHP Version Floor“ wird zum Schließen angeboten, mit einer Notiz, was an seine Stelle getreten ist. Eine festgehaltene Ablehnung dieser Stufe wird einfach ignoriert.
- Deine Housekeeping-Vorlage bekommt das Dependency-Audit im nächsten Zyklus.
- Offene Tickets für Semgrep, Psalm Taint Analysis oder Secret Detection bleiben und werden bearbeitet wie bisher; nur neue Scans schlagen sie nicht mehr vor.
- Der nächste Scan schlägt vielleicht neue Stufen vor: die Coverage-Metadaten-Stufen und den Backward Compatibility Check, wenn dein Versprechen SemVer folgt.
Die FAQ erklärt die Gründe, zum Beispiel warum manche Tools nur auf Anfrage eingerichtet werden und warum Rector bei deiner PHP-Version aufhört.
Wenn du einen Driver auf eines deiner Projekte loslässt, freue ich mich, in den GitHub Discussions (Show and tell) zu lesen, wie es lief.
Changelog 0.10.0
Die Einträge aus dem Upstream-Changelog zu 0.10.0, unverändert auf Englisch. Auch auf GitHub: Release 0.10.0.
Changelog-Einträge anzeigen
- Semgrep and Psalm's taint analysis are no longer proposed to every project: they are now set up only when you ask for them, by naming one in the call ("set up Semgrep") or by asking for a scan including the tools on request, and each scan report names what is available. Composer Audit is no longer a tool to set up: the first Housekeeping template now carries a recurring dependency audit for any package manager, an existing template gains it on its next cycle, and an open "Composer Audit" or "PHP Version Floor" ticket is offered for closing with a note saying what took its place. The diagrams under docs/tooling now show a tool's status as the outline of its steps — dashed for on request, dotted for recognized — with a legend below.
- Secret Detection is now set up only on request, and the secret scan over the Git history no longer waits for it: Housekeeping uses your project's own secret scanner, or runs one of its own (gitleaks, for instance) without adding anything to your project. A forge's push protection for secrets now counts as Secret Detection being in place, and the scanner's findings no longer feed the Security signal of restructuring candidates.
- Mutation testing joins the tooling, on request: ask for Infection and the suite sets it up after your coverage floor, with a minimum score that only rises, a pipeline check on the changed files and a full run in each Housekeeping cycle; the faults your tests miss then point the search for restructurings at code that is run but not really tested. A project that already runs Pest's own mutation testing has it in place. A tool on request that needs a newer PHP than your project allows is no longer reported as out of reach until you ask for it.
- The suite now asks whether your project promises stability to code outside it — your package's users, your application's addons — and records where that promise is written (or that there is none) in
docs/agents/issue-tracker.md. Onboarding asks it; a project onboarded before is asked once at the start of its next run. Code the promise covers is restructured with old and new side by side and the old path deprecated the way your promise says; its removal is proposed as an ordinary ticket, never made by a refactoring. - A project whose BC promise follows SemVer and that has tagged a release now gets Roave's Backward Compatibility Check in its Safety Net, run in CI from its Docker image, before the suite's restructurings start. Rector's type coverage waits for it, and where your promise covers extending non-final classes it takes the BC-safe
art4/rector-bc-libraryset instead of Rector's own, so an added type does not break a subclass in someone else's code. - After the coverage floor, the suite now proposes two more steps for PHP projects: every test says which class it covers, and then — on PHPUnit 10 or newer, not on Pest — also what it only uses, so coverage counts only what a test means to test and a class without a test of its own shows up as untested. Integration and functional tests are marked as medium and large tests and need no list of what they use. The merge request writes the tests a class lacks; where that does not fit, it sets the coverage floor once to the newly measured value and says so. A Pack of your own whose Tool gains a second Stage is now told to rename the Tool, not the Stage, so tickets and rejections on record keep their name.
- Rector's PHP set now goes up to the PHP version your
composer.jsondeclares and never above it, and the "PHP Version Floor" step is gone: the suite no longer raises your declared minimum PHP after rewriting the code for a newer one. A rejection of that step you recorded earlier is ignored. - PHPStan Level 4 now waits until Rector Dead Code is decided, and Level 5 until Rector Type Coverage is, so the rewrites shrink those levels' baselines before they are fixed by hand; Rector's PHPUnit Set is now a Guardrails Stage and no longer holds the Safety Net back.
- A Stage's
afterfield in a Pack is now namedwaits_for("B waits for A"), and the generated tooling tables call the column "Waits for". A Pack of your own that still saysafteris refused with a message asking you to rename the key. - A structural change that creates a new class or other type reachable from outside now gives it a test of its own, written first, instead of relying on the old code's test staying green; and before opening a merge request a run now executes every gate your CI applies to the whole project, such as a coverage floor, in full, naming any it cannot run locally.
- A run whose call names a ticket now looks at that ticket alone: it no longer searches every open ticket first, and reconcile checks only that ticket's merge request, so it writes nothing to tickets you did not name.
- An open question on a ticket now ends at the first comment that is neither the suite's plan nor another of its questions, whichever account wrote it, and an answer that declines the ticket is recorded as a rejection.
- A proposed tool whose judgement stays open is now filed with its question as an open question on the new ticket, so an autonomous run no longer leaves it unfiled and raises it again on every scan.
- An agent may now start
continuous-refactoringby itself, as it already couldcontinuous-housekeeping— for example one that hands each run to a subagent and reviews its merge request before calling the next. - A new Driver playbook describes an agent that calls runs one after another, each in a subagent of its own, reviews and merges each merge request in between, and asks you only where a decision is yours — with a prompt to copy into your project.
- Known limitations now name two effects of letting an agent drive runs: its comments on a waiting ticket count as your answer, and a run inside a subagent does every step in its own context.
docs/adr/README.mdlists every decision with its status — in force, amended, superseded in part or superseded — generated from the ADRs' banners, which the validator now keeps consistent on both sides of every relation.