# 0.9.0: tooling that fits your project – roles, ratchets, Packs

*October 26, 2026 · Release · written for 0.9.0*

0.9.0 is about **which tools the suite proposes**. The tooling tree is
rebuilt so that it understands tools that do the same job, other
languages, and tools of your own. It is not a breaking release. A project
on 0.7 or 0.8 has nothing to do before it calls the suite again; what you
will notice is listed at the end, in
[What changes for your project](#what-changes-for-your-project).

## Why the tooling tree had to change

Up to 0.8 the tooling tree was a graph of adoption steps, built for PHP.
Using it on real projects showed where it did not hold:

- **A tooling tree of your own was hard to build.** Adding a tool meant
  changes in several places, sometimes in the parser itself. A project
  could not bring its own tools without changing the suite.
- **A tool could not be tightened later.** A tool came in with the Safety
  Net, and from then on its node was either done or not. There was no way
  to introduce it early and tighten it step by step afterwards, which is
  what a **ratchet** does: no new violations, the old ones shrink, in one
  direction only.

The second point led to a contradiction. Structural work, the Investigation
Track, always waits for the Safety Net. But some ratchets could only be
tightened with a restructuring. So the Safety Net waited for structural
work, and structural work waited for the Safety Net.

Two smaller problems came on top:

- **Tools that do the same job.** PHPStan or Psalm took several helper
  nodes and special cases, and Laravel Pint had no place at all.
- **Anything that is not PHP.** On a project in another language, the
  Safety Net could never be fulfilled, because nearly all of its tools
  were PHP tools.

## Roles, Tools, Stages and Packs

0.9.0 replaces the graph with four terms:

- A **Role** is what a tool is for: Static Analysis, Code Style, Testing.
- A **Tool** plays one or more Roles. It is either **recommended**
  (proposed when its Role is empty) or **recognized** (counted when it is
  there, never proposed).
- A **Stage** is one step of a Tool, such as a PHPStan level or a Rector
  set. Tickets are filed per Stage, as before per node, and their names
  stay the same.
- A **Pack** is a set of Tools with a sentence saying when it applies.
  There is a generic Pack, a PHP Pack and, new, a Twig Pack.

Beside these, the **ratchet** is now a property of a Tool: the PHPStan
baseline, the test coverage floor, PHPMD's limits.

The graph itself is gone. With every Stage as a node and every dependency
as an edge, it would have become too complicated to understand. Each Pack
is now a JSON file, `pack.json`, which the suite reads, and the prose for
each Tool sits beside it in a Markdown file.

What this changes in practice:

- **Introduce early, tighten later.** A Tool comes in with the Safety Net;
  tightening it is a later Stage in Guardrails, which may need structural
  work and no longer holds the Safety Net back. Psalm Taint Analysis moved
  to Guardrails for exactly this reason: its findings can need a
  restructuring to fix. PHP CS Fixer and PHPMD were split into two Stages
  the same way (see below).
- **A filled Role is respected.** A project on Psalm has Static Analysis
  filled and is not offered PHPStan. A project on Laravel Pint, Easy Coding
  Standard or PHP_CodeSniffer has Code Style filled and is not offered PHP
  CS Fixer.
- **You can decline more precisely.** A rejection can name a single
  Stage, a whole Tool, or a whole Role. Declining a Tool leaves its Role
  open for another one.
- **A project that is not PHP** can now have a fulfilled Safety Net from
  the generic Tools: a CI pipeline and `.editorconfig`.
- **Twig templates** in a PHP project get their own Tools: Twig-CS-Fixer
  for code style and TwigStan for static analysis. They stand beside the
  PHP Tools, because a Role is filled per language: a code-style tool for
  Twig does not take the place of one for PHP.

For humans, pages are generated from the Packs: the
[tooling overview](https://continuous-refactoring.de/en/docs/tooling.md) lists the Roles and Packs, and
each Pack has a page with a diagram, for example
[PHP](https://continuous-refactoring.de/en/docs/pack-php.md).

## A Pack of your own

A tool the suite does not know no longer needs a change to the suite. Your
project can keep a **Pack of its own**: a `pack.json` with the Tool and its
Stages, and a Markdown file per Tool saying when a Stage is fulfilled.
Name its folder in a new optional **Tooling Pack** bullet in your
`## Refactoring operations` section, and the suite proposes your Stages
like its own.

Your Pack can add Tools, Stages and Roles, and wait for the suite's Stages.
It cannot redefine them: a clash ends the run's tooling step with an error
that names both places. A Tool of yours that does the job of one of the
suite's stands beside it; decline the suite's if you prefer yours. The
[playbook](https://continuous-refactoring.de/en/docs/own-tool.md) has a worked example with Deptrac.

The format of `pack.json` is described by a JSON Schema,
[`pack.schema.json`](https://github.com/Art4/continuous-refactoring/blob/0.9.0/skills/continuous-refactoring/references/pack.schema.json),
which ships with the suite. Point your file's `"$schema"` line at it in
your installed skills, and your editor checks and completes the file as
you write it.

Note that the Pack format is still in development and will change. A Pack
of your own may need adjusting after a later release; the
[changelog](https://github.com/Art4/continuous-refactoring/blob/main/CHANGELOG.md)
will say what changes.

## Large projects: PHP CS Fixer and PHPMD in two Stages

On a project with thousands of files, one formatting pass is a merge
request nobody can review. So PHP CS Fixer now comes in two Stages:

- The **adoption** pins a PER Coding Style version, proposes extra rules
  you may decline, and takes in as many rules as one merge request can
  carry. The others are switched off with a marker. Rector waits only for
  this first Stage.
- The **rollout**, a Guardrails Stage, takes in the remaining rules one at
  a time, path by path, through a second config file.

PHPMD gets the same split, and loses its baseline. A baseline only lists
rule, file and method, with no value and no reason, so a method that gets
worse after it was baselined stays quiet. Instead, each metric limit is set
to the **worst value the code has today**, with a comment, and lowered
whenever the work removes that value. The limits themselves are the record
of the debt. A deliberate exception is suppressed at the code, with its
reason beside it.

## Other agents, and marking a ticket ready

- The suite no longer names Claude Code's question tool. It asks for the
  multiple-choice question tool of whatever agent runs it, and falls back
  to numbered options in chat. So questions now show up properly in Cursor
  and OpenCode too.
- A new optional **Ready after plan** bullet in your tracker file says how
  your project marks a ticket ready for implementation, for example a
  label or a board column. The suite runs it right after it posts a plan.
  Without the bullet, nothing changes. The suite still never sets a status
  when work starts.

## What changes for your project

Nothing you have to do. What you will notice:

- **Your existing tickets and rejections stay valid.** Stages keep the
  names of the old nodes.
- **The next scan may propose new things**: Stages it has not judged on
  your project before, such as the PHP CS Fixer and PHPMD rollouts, or the
  Twig Tools if your project uses Twig.
- **A project on Psalm, Pint, ECS or PHP_CodeSniffer** is no longer offered
  the competing tool.
- **A project with a PHPMD baseline** gets one merge request that replaces
  it with limits and suppressions.

Optional, if you want them: a **Tooling Pack** for your own tools, and
**Ready after plan** for your tracker's workflow.

The [FAQ](https://continuous-refactoring.de/en/docs/faq.md) explains the reasoning, for example
[why the suite thinks in roles and tools](https://continuous-refactoring.de/en/docs/faq.md#why-does-the-suite-think-in-roles-and-tools-not-just-a-list-of-steps)
and [why PHPMD has no baseline](https://continuous-refactoring.de/en/docs/faq.md#why-does-phpmd-have-no-baseline).

If you build a Pack of your own, or improve something with what 0.9 makes
possible, I would love to read about it in
[GitHub Discussions (Show and tell)](https://github.com/Art4/continuous-refactoring/discussions/categories/show-and-tell).

## Changelog 0.9.0

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

- The tooling tree is now Roles, Tools and Stages delivered in Packs: a project on Psalm is no longer offered PHPStan, a project that is not PHP can have a fulfilled Safety Net from the language-neutral tools, a rejection can name a whole tool or role, and a tool the suite learns about later is picked up on the next run.
- The tools the suite knows are now drawn for humans: `docs/tooling/` lists the Roles and, per Pack, the Tools and Stages with a diagram of what requires what. The pages are generated from the Packs, and the validator now fails on a Pack that breaks its schema, is not in the fixed form, has two recommended tools for one Role, names a missing details file, or whose page is out of date.
- The prose for a tool now sits beside its Pack: the generic Pack's tools, the suite's two prerequisites and the smaller PHP tools moved to `packs/<pack>/<tool>.md`, without the history of how they used to be wired. The PHP Pack also knows Laravel Pint and Easy Coding Standard now: a project on either has its code style settled, and PHP CS Fixer is not proposed to it.
- The last tool files (PHPStan, Psalm, Rector) moved into the PHP Pack and lost the description of how the tools used to be wired; the two tooling-tree documents are gone. Every tool now has one file beside its Pack, and the Pack pages under `docs/tooling/` link to it.
- A project can keep a Pack of its own for tools the suite does not know: a `pack.json` and a file per tool, named in a new optional **Tooling Pack** operation of its tracker file. Its steps are proposed like the suite's own; it can add tools, steps and roles, never redefine the suite's, and a clash ends the run's tooling step with an error that names both places. A Pack now names a scope (PHP, Twig, …): tools take each other's place only within a scope, so a code-style tool for Twig no longer competes with one for PHP. See the new playbook on adding a tool of your own.
- A new Pack for Twig in PHP projects: it applies when the PHP project requires `twig/twig` (the file extension of the templates does not decide) and brings Twig-CS-Fixer for the Code Style of the templates and TwigStan for their Static Analysis. Both stand beside the PHP Tools and do not take their place.
- PHP_CodeSniffer is now a recognized Tool for the Role Code Style: a target that runs `phpcs`/`phpcbf` has the Role filled, so PHP CS Fixer is no longer proposed on top of it.
- The PHP Pack Stage "PHP Minimum Version" is now called "PHP Version Floor", matching its Role Runtime Floor and the terms Floor correction and Floor raise; its slug `php-minimal-version` is unchanged.
- Psalm Taint Analysis is now a Guardrails Stage instead of a Safety Net one: its findings can need a restructuring to fix, and that restructuring waits for the Safety Net, so the Stage no longer holds the Safety Net back and no longer has to be skipped to pass it.
- Twig-CS-Fixer is now a Guardrails Stage instead of a Safety Net one: a style pass over Twig templates no longer holds the Safety Net, and with it every PHP Guardrails Stage, back. TwigStan still waits for it.
- In the PHP Pack diagram, the Static Analysis Role now sits inside the Safety Net group instead of outside it.
- PHP CS Fixer now comes in two Stages, so a project with thousands of files gets its code style in merge requests a person can review. The adoption (`php-cs-fixer-adoption`, renamed from `php-cs-fixer`) pins a PER Coding Style version, proposes a list of extra rules you may decline, checks the project's lowest PHP version, and takes in as many rules as one merge request can carry; it alone releases Rector. The new Guardrails Stage `php-cs-fixer-rollout` takes in the rules left `Not yet:` one at a time, path by path, through a second config file, as the ticket "PHP CS Fixer: take in the remaining rules". A Housekeeping line looks for a newer PER Coding Style version.
- PHPMD no longer starts with a baseline: the limits of its metric rules are set to the worst value the code has today, with a comment, and fall whenever a refactoring removes that worst value. Deliberate exceptions are suppressed at the code with a reason, the five rule sets (now with `design`) are the default, and a project that already has a PHPMD baseline gets one merge request that replaces it.
- PHPMD now comes in two Stages, like PHP CS Fixer. The adoption (`phpmd-adoption`, renamed from `phpmd`) installs the tool, sets the limits, takes in the rules one merge request can carry and adds the CI job; rules left `Not yet:` no longer keep it unfulfilled. The new Guardrails Stage `phpmd-rollout` takes in those rules one at a time, as the ticket "PHPMD: take in the remaining rules". A rule too large for one merge request is taken in directory by directory through a second ruleset, `phpmd.rollout.xml`, which CI runs over the directories already done, so they cannot slip back before the rule is in everywhere.
- A Stage's `after` may now name a Role as well as a Stage. The Rector Stages wait for the Role Code Style instead of PHP CS Fixer's adoption, so the tooling diagram shows PHP CS Fixer, Laravel Pint, Easy Coding Standard and PHP_CodeSniffer feeding one Code Style node that Rector waits on. When Rector is released does not change: a Role counts as decided once it is filled, or once every Tool that could fill it is decided. A target's own Pack can use the same form.
- The suite no longer names one agent's question tool: it asks for the environment's multiple-choice question tool (Claude Code, Cursor and OpenCode each have one under a different name) and falls back to numbered options in chat when there is none, so questions are shown in Cursor and OpenCode too. A tool reply that no human gave counts as unanswered.
- A new optional operation, **Ready after plan**, lets a target mark a ticket ready for implementation (for example a label or a board column) right after the suite posts a plan on it. Without the bullet nothing changes.

---

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