# 0.10.0: tools on request, your BC promise, and an agent that drives

*October 28, 2026 · Release · written for 0.10.0*

0.10.0 is about **proposing the right things, and fewer of them**. Some
tools are now set up only when you ask, the suite learns what your project
promises to the code outside it, and a run can be driven by another agent.
It has one breaking change, for a Pack of your own only: a Stage's `after`
is now `waits_for`. Everything else needs nothing from you; what you will
notice is listed at the end, in
[What changes for your project](#what-changes-for-your-project).

## Tools on request

Up to 0.9, every scan proposed every tool that was neither in place nor
declined. For some tools that was noise. Semgrep finds a lot in code that
grew for years without guardrails, and almost nothing in a young project
that had them from the start. Mutation testing runs your tests hundreds of
times, which only pays off once they are good enough to judge.

So a few tools are now **on request**: Semgrep, Psalm's taint analysis,
Secret Detection, and the new mutation testing with Infection. They are
never offered unasked and never block a Track. Every scan report names
what is available on request; name one in the call and it is proposed like
any other tool:

```text
/continuous-refactoring set up Semgrep
/continuous-refactoring scan including the tools on request
```

Two tools changed their place rather than their status:

- **Composer Audit is gone as a tool.** A pipeline check only fails on
  advisories known when the code changes; one published next month
  against the same lockfile is never seen. The first Housekeeping
  template now carries a recurring **dependency audit**, for any package
  manager, and audits on every cycle.
- **The secret scan no longer waits for a scanner in your project.**
  Housekeeping uses the one you have, or runs one of its own (gitleaks,
  for instance) without adding anything to your project. A forge's push
  protection counts as Secret Detection being in place.

The diagrams on the Pack pages now show a tool's status as the outline of
its steps, dashed for on request, dotted for recognized, with a legend
below. See [Pack php](https://continuous-refactoring.de/en/docs/pack-php.md).

## Your BC promise

Whether the suite may delete an old API depends on who else uses it. Up to
0.9 it guessed: where it could not tell, it assumed someone depends on it.
Now it asks. The **BC promise** is your project's own document saying what
stays stable for code outside the project, your package's users or your
application's addons, and how a breaking change is allowed: a new major
version under SemVer, or a deprecation period counted in releases and
months, as some applications promise their addons. The suite does not model either; it reads your document.

- **Onboarding asks for it**, and a project onboarded before is asked once
  at the start of its next run. The answer, a path or `none`, goes into
  `docs/agents/issue-tracker.md`.
- **Code the promise covers** is restructured with old and new side by
  side, and the old path is deprecated the way your promise says. Its
  removal is proposed later as an ordinary ticket, never made by a
  refactoring.
- **Under a SemVer promise with a tagged release**, the Safety Net gains
  Roave's Backward Compatibility Check, run in CI before the suite's
  restructurings start. Rector's type coverage waits for it.
- **Where your promise covers extending non-final classes**, or is not
  settled yet, Rector's type coverage takes a BC-safe path. Adding a
  parameter or return type to a method breaks every class outside your
  project that overrides it. So the suite switches off Rector's own
  type-declaration set and uses
  [`art4/rector-bc-library`](https://gitlab.com/Art4/rector-bc-library)
  instead, a library I maintain: the same rules, applied only where the
  change is not a BC break. It comes in as a dev dependency and
  supports the same adoption in levels.

The [FAQ](https://continuous-refactoring.de/en/docs/faq.md#what-is-the-bc-promise-and-why-does-the-suite-ask-about-it)
has the reasoning.

## Tests that say what they cover

A coverage report counts a class as covered when any test runs it, also a
test of another class that merely passes through. The suite reads those
numbers to find code without tests, so such a class looked tested when
nobody tested it. After the coverage floor, two more steps follow for PHP
projects:

- **Every test names the class it covers.**
- **Then, on PHPUnit 10 or newer**, also what it only uses, so a test that
  runs code it does not name fails. Integration and functional tests are
  marked as medium and large tests and need no such list. Pest has no way
  to name what a test uses, so it stops at the first step.

Counting only what a test means to test can lower the measured coverage.
The merge request writes the missing tests; where that does not fit, it
sets the floor once to the newly measured value and says so.

Two related changes: a restructuring that creates a new class 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 executes every
gate your CI runs over the whole project, such as the coverage floor, in
full, and names any it cannot run locally.

## Rector before PHPStan, and never above your PHP

- **PHPStan Level 4 waits for Rector's Dead Code set, Level 5 for Type
  Coverage.** Rector clears part of those levels' baselines mechanically,
  and every finding it clears is one nobody fixes by hand. Declining the
  Rector set releases the level.
- **Rector's PHP set stops at the PHP version your `composer.json`
  declares.** Rewriting the code for a newer PHP is the same promise as
  raising the minimum, and that is a product decision. The "PHP Version
  Floor" step is gone with it; the suite no longer raises your declared
  minimum.
- **Rector's PHPUnit set is a Guardrails Stage** and no longer holds the
  Safety Net back.

## An agent that drives the runs

`/continuous-refactoring` can now be started by an agent, as
`/continuous-housekeeping` already could. The new
[Driver playbook](https://continuous-refactoring.de/en/docs/driver.md) describes such an agent: it calls
one autonomous run after another, each in a subagent of its own so every
run starts with a fresh context, reviews each merge request in a second
subagent, merges what passes (or only comments, if you choose "review
only"), and asks you only where a decision is yours. The playbook ends
with a prompt to copy into your project.

Three changes make that work, and help any run:

- **A named ticket narrows the run.** "ticket 42" no longer searches every
  open ticket first, and reconcile checks only that ticket's merge request.
  A run started for one ticket writes nothing to the others.
- **An open question is answered by content, not by author.** The wait ends
  at the first comment that is neither the suite's plan nor another of its
  questions, whichever account wrote it, since agents often post with your
  own. An answer that declines the ticket is recorded as a rejection.
- **A tool whose judgement stays open** is filed with its question on the
  new ticket, so an autonomous run no longer skips it and raises it again
  on every scan.

[Known limitations](https://continuous-refactoring.de/en/docs/known-limitations.md) names the two catches:
an agent's comment on a waiting ticket counts as your answer, and a run
inside a subagent does its review steps in its own context.

## What changes for your project

One thing to do, only if you have a **Pack of your own**: rename each
Stage's `after` key to `waits_for`. A Pack that still says `after` is
refused with a message saying so. While you are there: a Tool of yours
that gains a second Stage gets a slug of its own; rename the Tool, not the
Stage, so your tickets and rejections keep their names.

What you will notice:

- **The next run asks about your BC promise**, once, and goes on.
- **An open "Composer Audit" or "PHP Version Floor" ticket** is offered for
  closing, with a note saying what took its place. A recorded rejection of
  the PHP Version Floor is simply ignored.
- **Your Housekeeping template gains the dependency audit** on its next
  cycle.
- **Open tickets for Semgrep, Psalm Taint Analysis or Secret Detection
  stay** and are worked as before; only new scans stop proposing them.
- **The next scan may propose new steps**: the coverage-metadata Stages,
  and the Backward Compatibility Check if your promise follows SemVer.

The [FAQ](https://continuous-refactoring.de/en/docs/faq.md) explains the reasoning, for example
[why some tools are only set up on request](https://continuous-refactoring.de/en/docs/faq.md#why-are-some-tools-only-set-up-when-i-ask-for-them)
and [why Rector stops at your PHP version](https://continuous-refactoring.de/en/docs/faq.md#why-does-rector-stop-at-the-php-version-my-project-declares).

If you let a Driver loose on a project of yours, I would love to read how
it went in
[GitHub Discussions (Show and tell)](https://github.com/Art4/continuous-refactoring/discussions/categories/show-and-tell).

## Changelog 0.10.0

The upstream changelog entries for 0.10.0, verbatim. Also on GitHub: [release 0.10.0](https://github.com/Art4/continuous-refactoring/releases/tag/0.10.0).

- 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-library` set 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.json` declares 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 `after` field in a Pack is now named `waits_for` ("B waits for A"), and the generated tooling tables call the column "Waits for". A Pack of your own that still says `after` is 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-refactoring` by itself, as it already could `continuous-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.md` lists 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.

---

Questions or comments on this post? Welcome in [GitHub Discussions (Q&A)](https://github.com/Art4/continuous-refactoring/discussions/categories/q-a).
