Skip to main content
Work happens on a branch and reaches main through a pull request.

Branches

main is the only long-lived branch. Nothing is pushed to it directly, and it is expected to be releasable at every commit. There is no develop branch and no permanent feature branches. Branch protection is switched on wherever the hosting plan allows it; where it is not, the rule holds by convention and a direct push to main is treated as a mistake to be reverted, not a shortcut. Every change starts from the current main on a short-lived branch named after what it is: feat/webhook-ingress, fix/session-lock-race, chore/php-8.5. A branch lives for days, not weeks: rebase it on main if it falls behind, and split work that grows past a single reviewable change into several pull requests rather than one large branch. Behaviour that is not ready to be seen ships behind a feature flag, not on a branch that waits. Commit messages and pull request titles follow one format, described in Commit messages. The title matters more than the commits: after a squash merge it is the single commit on main, and release notes are generated from those titles.

Writing the pull request

The repository has a short pull request template; fill it in rather than deleting it. It asks two things of a reviewer who has not seen the branch:
  • What and why — what is different after the merge, in one or two sentences rather than a list of files, and the bug, missing behaviour or issue behind it. Link the issue with Closes #123 so it closes on merge.
  • How to check — the test that covers it, or the manual steps if the change is visual or operational.
Formatting, tests and architecture rules are not ticked off in the description: CI runs them on every pull request. One pull request carries one concern. A fix that was noticed while building a feature goes in its own pull request, so that it can be reviewed, released and, if needed, reverted on its own. If a change touches a public contract or needs an operator to do something on upgrade (a migration, a new environment variable), add an Upgrade notes section — the template has it commented out — because that paragraph is what ends up in the release notes. Open the pull request as a draft if it is not ready for review; convert it when it is. A draft is a fine place to get early feedback on a direction.

Merging

A pull request is squash-merged once CI is green and it has been reviewed. The branch is deleted on merge. Merge commits and rebase-merges are not used: one change on main is one commit, with the pull request number in its subject. Contributors without write access work the same way from a fork. Dependabot pull requests follow the same path. They are reviewed in batches before a release rather than as they arrive; an upgrade that moves a runtime major version (PHP, Node, Postgres) is treated as a normal change and gets a proper look, not a green-CI merge. How a merged change reaches operators is a separate step — see Releases and versioning.

Before opening one

A change that has not been run is not finished. If part of the work is incomplete or deliberately left out, say so in the pull request rather than leaving it to be discovered.

What a reviewer is looking for

The boundaries hold. Dependency direction, migration isolation, tenant-aware execution, worker safety. Only some of these have a mechanical check (PHPat covers migration isolation, flow runtime isolation, handler versioning, ID strategy and messaging boundaries; see Testing and architecture checks). Dependency direction and tenant-aware execution are caught in review, and rules cannot see intent, only shape. Business logic is in the right place. Controllers and jobs orchestrate; services and domain classes decide. The change is covered. A minimal relevant test, not an exhaustive suite. The code reads like its neighbours. Matching the surrounding conventions matters more than any individual preference — see Coding conventions.

Documentation that travels with the change

Documentation lives in this repository, so a change to a public contract and the change to the page describing it belong in the same pull request. That is the point of keeping them together: they cannot drift apart between releases. If a change alters something on the extension surface, it is a change to a public promise. Say so explicitly — see Stability policy.

Before it can be merged

A contribution needs the Contributor License Agreement accepted in a comment on the pull request — see Licence, CLA and trademark for the comment to post and what it grants.

Dependencies

Do not add a dependency without agreement. Raise it before the pull request rather than inside it.

Contributor License Agreement

On a first pull request the CLA check asks you to sign. Comment on the pull request with exactly:
The signature is recorded against your GitHub account and covers your later pull requests. You keep the copyright in your work. The agreement grants the project the rights it needs to distribute that work and confirms you are entitled to contribute it. If you are contributing on behalf of an employer, the entity signs once and covers every account it lists — see CLA.md in the repository root.