Skip to content

Contributing

phpClaw is MIT-licensed and developed in one monorepo, phpclaw-php/phpclaw-monorepo. Core, cloud, MCP and every adapter live under packages/.


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.

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.

  1. Fork the monorepo, clone your fork, and add the original as upstream:

    Terminal window
    git clone https://github.com/<your-username>/phpclaw-monorepo.git
    cd phpclaw-monorepo
    git remote add upstream https://github.com/phpclaw-php/phpclaw-monorepo.git
  2. Branch from the latest line. Replace N.x with the repository default branch:

    Terminal window
    git checkout N.x
    git pull upstream N.x
    git checkout -b fix/my-fix
  3. Make the change with tests.

  4. Inside the package you changed, run:

    Terminal window
    cd packages/<package>
    composer install
    composer test
    composer lint
    composer analyse

    composer lint runs Laravel Pint and reformats files in place; commit what it changes. composer analyse runs PHPStan at level 5.

  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:

    PrefixUse it forIn the release notes
    feat:a new featureunder Added
    fix:a bug fixunder Fixed
    security:a security fixunder Security
    chore:upkeep: dependencies, config, renamesleft out
    docs:documentation onlyleft out
    test:tests onlyleft out
    ci:GitHub Actions workflowsleft out
    build:build scripts and packagingleft out
    style:formatting only, no behaviour changeleft out
    anything elseunder Changed

    For example:

    feat: add DatabaseQueryTool with SELECT-only enforcement
    fix: guard bypass when user guards registered before defaults

    The prefix never changes the version number; a maintainer’s label does.

  6. 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:

Terminal window
git checkout N.x && git pull upstream N.x && git push origin N.x

A 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.

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.

LabelEffect
nonethe third number moves
release:minorthe second number moves
release:majorthe 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:major is refused unless the pull request changes something under packages/core/. An adapter, cloud or MCP change never moves its own first number.
  • release:major is refused on the current line N.x. A major opens the next line N+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.

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.


Every adapter, and core, cloud and MCP, runs the same seven jobs:

JobFails the pull request when
Unit Testsa test fails on any PHP version in the matrix
Static AnalysisPHPStan reports an error, or pint --test finds unformatted code
Docblock on every functiona named function has no docblock, or its docblock misses a @param or @return
Static Checksa blocking code-standard rule matches; the log names the rule. See Code Standards
Packagingthe package contains .DS_Store, Thumbs.db, *.bak, *.orig or *.swp, or, for an adapter, a config/settings.php
BenchmarksPHPBench fails, for packages with a benchmarks/ folder
Coveragenever; 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%.


  • 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 ArrayMemory instead of a real database.
  • Reset shared registries such as GuardRegistry and HookRegistry in setUp() and tearDown().
  • 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 Standards lists every rule, and marks which ones CI blocks on.

See Building an Adapter.

Do not open a public issue. Follow the security policy.

Be kind. Assume good intent, and keep discussion free of personal attacks and harassment.