Contributing
phpClaw is MIT-licensed and developed in one monorepo,
phpclaw-php/phpclaw-monorepo. Core, cloud, MCP and
every adapter live under packages/.
Bug reports and feature requests
Section titled “Bug reports and feature requests”A bug report should have a clear title, a description, and a code sample that reproduces the problem. A pull request with a failing test is even better.
Propose a feature by opening an issue. Be ready to help build it.
For a wrong PHPDoc comment or a static-analysis warning, open a pull request rather than an issue.
Branches
Section titled “Branches”Development happens on a version line branch named after the current major, written N.x below. It
is the repository default, so a new pull request targets it automatically, and the CI and release
workflows run on pushes to it.
Patch and minor releases are merged into the current line. A breaking change opens the next line,
N+1.x, which then becomes the default; you still open your pull request against the default
branch. The lines before it stay on GitHub for reference.
The single-package repositories that Composer installs from are read-only mirrors of the monorepo. Send changes to the monorepo, not to a mirror.
Workflow
Section titled “Workflow”-
Fork the monorepo, clone your fork, and add the original as
upstream:Terminal window git clone https://github.com/<your-username>/phpclaw-monorepo.gitcd phpclaw-monorepogit remote add upstream https://github.com/phpclaw-php/phpclaw-monorepo.git -
Branch from the latest line. Replace
N.xwith the repository default branch:Terminal window git checkout N.xgit pull upstream N.xgit checkout -b fix/my-fix -
Make the change with tests.
-
Inside the package you changed, run:
Terminal window cd packages/<package>composer installcomposer testcomposer lintcomposer analysecomposer lintruns Laravel Pint and reformats files in place; commit what it changes.composer analyseruns PHPStan at level 5. -
Give your pull request a title with a short prefix that says what kind of change it is. The title, without the prefix, becomes the line in the release notes:
Prefix Use it for In the release notes feat:a new feature under Added fix:a bug fix under Fixed security:a security fix under Security chore:upkeep: dependencies, config, renames left out docs:documentation only left out test:tests only left out ci:GitHub Actions workflows left out build:build scripts and packaging left out style:formatting only, no behaviour change left out anything else under Changed For example:
feat: add DatabaseQueryTool with SELECT-only enforcementfix: guard bypass when user guards registered before defaultsThe prefix never changes the version number; a maintainer’s label does.
-
Push to your fork and open a pull request against the default branch, saying what changed and why.
Keep a pull request to one package: it changes files under a single packages/<name>/ folder. Each
of the eleven packages counts on its own, core, cloud and MCP included. Two packages means two pull
requests.
When you fix a bug, look for the same pattern in the other adapters and providers, and fix every copy, one pull request per package, linking them to each other.
Before your next pull request, bring your fork up to date:
git checkout N.x && git pull upstream N.x && git push origin N.xA maintainer reviews and merges. Merging your pull request publishes nothing on its own. A bot keeps
one release pull request up to date that collects every merged change into each package’s
CHANGELOG.md, and merging that pull request is the release. That single merge publishes both sides:
the four CMS plugins as ZIPs, and the seven Composer packages to their mirrors and to Packagist.
How the version number is decided
Section titled “How the version number is decided”The bump is patch unless a maintainer labels the pull request. Labels can only be applied by people with write access, so nothing in your title or commit messages changes the version.
| Label | Effect |
|---|---|
| none | the third number moves |
release:minor | the second number moves |
release:major | the first number moves, on a new line only |
Two rules are enforced by the release workflows, so a mislabelled pull request fails loudly instead of publishing a wrong version:
release:majoris refused unless the pull request changes something underpackages/core/. An adapter, cloud or MCP change never moves its own first number.release:majoris refused on the current lineN.x. A major opens the next lineN+1.x, where core, cloud, MCP and all eight adapters move to that major together.
Each failure names the pull request and what to do next: remove the label and re-run the workflow, or open the next line.
Before 1.0
Section titled “Before 1.0”While the line is 0.x, only patches are released: both release:minor and release:major are
refused. Below 1.0 the minor is the breaking digit, so ^0.1 stops accepting 0.2, and moving it
would mean rewriting every package’s requirements in the same release. New features therefore ship
as patches, and the changelog still lists them under Added. The first minor is 1.0.0 itself,
which opens the 1.x line.
What CI runs
Section titled “What CI runs”Every adapter, and core, cloud and MCP, runs the same seven jobs:
| Job | Fails the pull request when |
|---|---|
| Unit Tests | a test fails on any PHP version in the matrix |
| Static Analysis | PHPStan reports an error, or pint --test finds unformatted code |
| Docblock on every function | a named function has no docblock, or its docblock misses a @param or @return |
| Static Checks | a blocking code-standard rule matches; the log names the rule. See Code Standards |
| Packaging | the package contains .DS_Store, Thumbs.db, *.bak, *.orig or *.swp, or, for an adapter, a config/settings.php |
| Benchmarks | PHPBench fails, for packages with a benchmarks/ folder |
| Coverage | never; the 80% threshold is reported only |
Two steps report without failing: composer audit in Static Analysis, and the coverage threshold.
Core’s workflow also runs its own coverage job, which fails below 80%.
Testing
Section titled “Testing”- Tests must run the code and check what it returns or does. Do not read a source file in a test.
- Do not call real providers: mock
RawHttpClient. - Use
ArrayMemoryinstead of a real database. - Reset shared registries such as
GuardRegistryandHookRegistryinsetUp()andtearDown(). - Name tests after the behaviour, for example
test_it_refuses_a_path_outside_the_workspace().
Security classes, such as the guards, ShellTool and the file tools, need the most thorough tests.
Code style
Section titled “Code style”Code Standards lists every rule, and marks which ones CI blocks on.
Building an adapter
Section titled “Building an adapter”See Building an Adapter.
Security issues
Section titled “Security issues”Do not open a public issue. Follow the security policy.
Code of conduct
Section titled “Code of conduct”Be kind. Assume good intent, and keep discussion free of personal attacks and harassment.