Contributing¶
Ground rules¶
- Every change that affects behaviour comes with a unit test or a fixture.
- Command behaviour (options, exit codes, report files) is tested through
tests/Support/CommandRunner, which runs the plugin's commands via Composer's console application against a scratch copy of a fixture; add a case totests/Integration/RemediateCommandTest.phporDbCommandsTest.phpwhen you add or change an option. - PHPStan level 8 must pass:
ddev composer phpstan. - The layer rules in
deptrac.yamlmust pass:ddev composer deptrac. Plugin → Command → Output → Engine → Advisory, each layer depending only on those inside it; the advisory code (feed readers, database, providers) never reaches into planning, so a database can be built and served without a project. Third-party code is classified too (Semver, ComposerPlugin, ComposerApi, SymfonyConsole, SymfonyProcess, SymfonyYaml) and each project layer lists which of them it may use, so the report shows zero uncovered dependencies and the run fails on a dependency nobody has classified: when you pull in a new library or a new corner of Composer, add a layer for it and allow it where it is meant to be used. Deptrac needs PHP 8.2 or newer, so it is installed separately withddev composer --working-dir=tools/deptrac install(its lock is committed; the PHP 8.4 CI job runs it and, on pushes tomain, writes the README's architecture badge to thebadgesbranch: "passing", or "failing (n)" with the number of violations and uncovered dependencies).ddev composer checkruns PHPStan, Deptrac and the tests together. - Methods stay short enough to read in one screen. Commands parse options, assemble collaborators and emit the report in separate methods; the planner's search phases (candidates, expansion, descent, global combination) are separate methods or classes; renderers have one method per report section.
- Coverage:
ddev composer test:coverageprints a summary and writes HTML and Clover reports tobuild/coverage/(gitignored). The DDEV web image installs PCOV (.ddev/web-build/Dockerfile, settings in.ddev/php/custom.ini), the same driver the PHP 8.4 CI job uses, so local figures match the badge; PCOV adds little to the run time and needs no toggling. Keepddev xdebug offfor coverage runs: with both extensions loaded PHPUnit still picks PCOV, but Xdebug slows the suite. Outside DDEV, without PCOV, the script falls back to Xdebug (it setsXDEBUG_MODE=coverage), whose line counts differ from PCOV's by a fraction of a percent. In CI the PHP 8.4 job prints the summary on the run's summary page, uploads the HTML and Clover reports as thecoverageartifact and, on pushes tomain, writes the line-coverage badge data to thebadgesbranch that the README badge reads; the other matrix jobs run without a coverage driver. - The
ciworkflow does not run for commits that only touch hand-written pages underdocs/,mkdocs.yml, root Markdown files or the docs pipelines; thedocsworkflow builds and deploys those. The generated pages (docs/cli-reference.md,docs/case-studies.md) and the JSON schema do trigger it, because it checks them for staleness. It can also be started by hand from the Actions tab. - Documentation is part of the change. If a page in
docs/describes the code you touched, update it in the same commit;mkdocs build --strictruns in CI (locally:pipx run --spec mkdocs --pip-args=pymdown-extensions mkdocs build --strict). - Design decisions and their reasons are recorded in
docs/design-decisions.md. Add a section when you change one. docs/cli-reference.mdanddocs/case-studies.mdare generated: after changing a command, runddev composer cli-reference; after adding or rebuilding a fixture, runddev composer case-studies. CI fails when either is stale.
Commit messages¶
Conventional-commit style subjects (feat:, fix:, docs:, test:, chore:), imperative mood,
no trailer lines. Commits on main are signed (SSH signing keys work fine: git config gpg.format ssh
and register the public key on GitHub as a signing key).
Adding a fixture¶
See Test fixtures. Fixtures must be real historical project states with provenance
recorded in their README.md.