From fc082e849ddb9f64ed1fd07498dde1499094428b Mon Sep 17 00:00:00 2001 From: Olivier Gorzalka Date: Wed, 28 Oct 2026 11:05:24 +0100 Subject: [PATCH] chore: require PHP 8.3, move to Pest 3 and adopt the Pollora harness --- .claude/settings.json | 27 +++++++++++++++++ .github/workflows/ci.yml | 4 +-- .github/workflows/harness.yml | 19 ++++++++++++ .gitignore | 2 ++ .harness.json | 16 ++++++++++ AGENTS.md | 56 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 7 +++++ README.md | 4 +-- api-surface.json | 4 +++ composer.json | 11 ++++--- phpstan.neon | 6 ++++ rector.php | 25 ++++------------ tests/Arch.php | 15 ++++++++++ 13 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 .claude/settings.json create mode 100644 .github/workflows/harness.yml create mode 100644 .harness.json create mode 100644 AGENTS.md create mode 100644 api-surface.json create mode 100644 phpstan.neon create mode 100644 tests/Arch.php diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..c8079f4 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,27 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/vendor/bin/pollora-qa hook pre-edit" } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/vendor/bin/pollora-qa hook post-edit" } + ] + } + ], + "Stop": [ + { + "hooks": [ + { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/vendor/bin/pollora-qa hook stop" } + ] + } + ] + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc5af16..a55dc66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,10 +10,10 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.2', '8.3', '8.4'] + php: ['8.3', '8.4'] dependencies: ['highest'] include: - - php: '8.2' + - php: '8.3' dependencies: 'lowest' steps: diff --git a/.github/workflows/harness.yml b/.github/workflows/harness.yml new file mode 100644 index 0000000..2c6db3d --- /dev/null +++ b/.github/workflows/harness.yml @@ -0,0 +1,19 @@ +name: Harness + +on: + push: + branches: [main] + pull_request: + # A label (api-change, breaking) changes what the public surface gate allows. + types: [opened, synchronize, reopened, labeled, unlabeled] + schedule: + - cron: '17 3 * * *' + workflow_dispatch: + +permissions: {} + +jobs: + harness: + uses: Pollora/harness/.github/workflows/php-package.yml@v0.1.11 + permissions: + contents: read diff --git a/.gitignore b/.gitignore index 17e605f..7b71d8a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ coverage.xml *.swp *.swo .vscode/ +# Pollora harness reports +/.harness/ diff --git a/.harness.json b/.harness.json new file mode 100644 index 0000000..fff4d85 --- /dev/null +++ b/.harness.json @@ -0,0 +1,16 @@ +{ + "profile": "core", + "mode": "enforce", + "downstream": [ + "framework" + ], + "ratchet": { + "phpstan_level": 5, + "phpstan_baseline": 0 + }, + "observed": [ + "Code style", + "Static analysis", + "Rector" + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..82fd517 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ +# Agent instructions + + +## Pollora harness + +This repository follows the Pollora harness (profile `core`, observe mode). The same gates run on your machine, in agent hooks and in CI; a pull request merges only when they pass. + +### Before you hand back + +- `composer qa:fast` runs the fast gates on what you changed (style, static analysis, architecture, dependencies). It must pass. +- `composer qa` runs every gate, as CI does. +- `vendor/bin/pollora-qa rules ` explains a rule quoted in a failure. + +### Never + +- Never edit `.harness.json`, `phpstan-baseline.neon`, `api-surface.json`: they hold the thresholds. If one is wrong, stop and explain why to the user. +- Never add `@phpstan-ignore`, a baseline entry or a `nosemgrep` comment without a written reason on the same line. +- Never weaken a test to make it pass, and never mark a test skipped to get a green run. +- Never break the public API (attributes, hooks fired, configuration keys, commands) in a minor version: deprecate first. + +### Always + +- A bug fix comes with a test that fails without the fix. +- Domain code depends on ports (interfaces), never on WordPress functions, Laravel or the Infrastructure layer. +- The common case of a public API needs no configuration; options are chained methods or named arguments. + +### Rules checked + +- **ARCH-01** Domain code imports neither Illuminate nor the Infrastructure or UI layers _(Pest arch, G1)_ +- **ARCH-02** Domain code calls no global WordPress function _(PHPStan rule pollora.noWordPressCallInDomain, G1)_ +- **ARCH-03** Application code depends on the Domain layer only _(Pest arch, G1)_ +- **ARCH-04** A core package requires neither illuminate/* nor laravel/*, unless listed in .harness.json _(pollora-qa deps, G1)_ +- **ARCH-05** The framework core references no third-party plugin (WooCommerce, ACF, Meta Box, WP Rocket) _(Semgrep, G1)_ +- **ARCH-06** Only integration packages, templates and projects depend on pollora/framework _(pollora-qa deps, G1)_ +- **ARCH-07** Every dependency used is declared, none declared is unused _(composer-dependency-analyser, G1)_ +- **ARCH-08** Attribute classes are final, readonly and declare their targets _(Pest arch, G1)_ +- **ARCH-09** declare(strict_types=1) in every file _(Pest arch + Pint, G1)_ +- **CONV-03** Laravel naming: ServiceProvider, Command, Exception, Repository, Factory suffixes _(Pest arch, G1)_ +- **CONV-04** Hooks fired by Pollora are named pollora// _(pollora-qa surface, G2)_ +- **CONV-05** Configuration lives under pollora.; env() is only called in config/ _(Pest arch, G1)_ +- **CONV-06** Commands are named pollora: or pollora:make- _(pollora-qa surface, G2)_ +- **CONV-07** Domain and Application code never calls app() or resolve(); it receives contracts _(Pest arch, G1)_ +- **CONV-10** One named exception per business case; never throw a bare \Exception _(Semgrep, G1)_ +- **CONV-11** Native types everywhere; PHPDoc only for generics, array shapes, @internal, @deprecated _(PHPStan + Pest type coverage, G1)_ +- **SEC-01** A REST route declared by attribute states its permission; a public route says so explicitly _(Semgrep, G1)_ +- **SEC-03** No $wpdb query built by interpolation without prepare() _(Semgrep, G1)_ +- **SEC-04** No unescaped Blade output {!! !!} outside a commented allowlist _(Semgrep, G1)_ +- **SEC-05** No unserialize, eval or extract _(Semgrep, G1)_ +- **SEC-08** No secret in the repository _(gitleaks, G1)_ +- **SEC-09** No dependency with a known vulnerability _(composer audit, G1)_ +- **QA-01** Ratchet: no metric gets worse (PHPStan level and baseline, coverage, mutation score) _(pollora-qa ratchet, G2)_ +- **QA-02** The harness files are protected: baseline, .harness.json and api-surface.json change only through a human _(Agent hooks + CODEOWNERS, G0)_ +- **QA-04** The public surface (attributes, hooks fired, configuration keys, commands) changes only with a label: api-change to add, breaking to remove or change _(pollora-qa surface, G2)_ +- **QA-05** No PHP signature breaks backward compatibility outside a major version _(roave/backward-compatibility-check, G2)_ +- **QA-03** A change to a core package keeps the framework green _(Downstream job, G2)_ + diff --git a/CHANGELOG.md b/CHANGELOG.md index fbdb39c..144179c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this package are documented here. The format follows [Kee Earlier releases (1.0 to 1.0.3) are listed on the [releases page](https://github.com/Pollora/Query/releases). +## [Unreleased] + +### Changed + +- **Requires PHP 8.3 or later** (PHP 8.2 is no longer maintained). CI runs on PHP 8.3 and 8.4. +- The development tools move to Pest 3 and the [Pollora harness](https://github.com/Pollora/harness), which replaces Rector 0.18 and PHPInsights. + ## [2.0.0] - 2026-10-15 A major release: Query now covers every query class of WordPress, and several arguments that were wrong or silently ignored are fixed. A few fixes change the results of a query that used to run, so read **[Upgrading](docs/upgrading.md)** before updating. diff --git a/README.md b/README.md index b69b44e..53503bf 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ It is for WordPress developers who are tired of looking up array keys and debugg composer require pollora/query ``` -Requires PHP 8.2+ and WordPress. If your WordPress site does not use Composer yet, see [this article on managing WordPress with Composer](https://deliciousbrains.com/storing-wordpress-in-git/). +Requires PHP 8.3+ and WordPress. If your WordPress site does not use Composer yet, see [this article on managing WordPress with Composer](https://deliciousbrains.com/storing-wordpress-in-git/). ## Quick start @@ -243,7 +243,7 @@ UPDATE_DOCS=1 vendor/bin/pest --group=docs # rewrite the outputs after a chang When you add a public method, mention it in the page of its class (the test lists the ones that are missing). -The code style is checked with [Pint](https://laravel.com/docs/pint): run `vendor/bin/pint` to fix it, `vendor/bin/pint --test` to only check it (this is what CI runs). The tests run on PHP 8.2, 8.3 and 8.4, with the highest and the lowest versions of the dependencies. +The code style is checked with [Pint](https://laravel.com/docs/pint): run `vendor/bin/pint` to fix it, `vendor/bin/pint --test` to only check it (this is what CI runs). The tests run on PHP 8.3 and 8.4, with the highest and the lowest versions of the dependencies. ## Contributing diff --git a/api-surface.json b/api-surface.json new file mode 100644 index 0000000..a226ee8 --- /dev/null +++ b/api-surface.json @@ -0,0 +1,4 @@ +{ + "version": 1, + "surface": [] +} diff --git a/composer.json b/composer.json index 0a2ceb4..6a6fa3e 100644 --- a/composer.json +++ b/composer.json @@ -15,19 +15,22 @@ } }, "require": { - "php": "^8.2", + "php": "^8.3", "pollora/wordpress-args": "^1.1" }, "require-dev": { "laravel/pint": "^1.30", - "pestphp/pest": "^2.19", - "rector/rector": "^0.18.4", - "nunomaduro/phpinsights": "^2.8" + "pestphp/pest": "^3.8", + "pollora/harness": "^0.1.11" }, "config": { "allow-plugins": { "pestphp/pest-plugin": true, "dealerdirect/phpcodesniffer-composer-installer": true } + }, + "scripts": { + "qa": "@php vendor/bin/pollora-qa check", + "qa:fast": "@php vendor/bin/pollora-qa check --fast" } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..e36f167 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +includes: + - vendor/pollora/harness/presets/phpstan/base.neon +parameters: + level: 5 + paths: + - src diff --git a/rector.php b/rector.php index ee8af93..46e828a 100644 --- a/rector.php +++ b/rector.php @@ -2,25 +2,10 @@ declare(strict_types=1); -use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; -use Rector\Config\RectorConfig; -use Rector\Set\ValueObject\LevelSetList; -use Rector\Set\ValueObject\SetList; +use Pollora\Harness\Profile; +use Pollora\Harness\Rector\Preset; -return static function (RectorConfig $rectorConfig): void { - $rectorConfig->paths([ - __DIR__.'/src', - __DIR__.'/tests', +return Preset::configure(__DIR__, Profile::Core) + ->withSkip([ + // Each skip says why, like the framework's rector.php. ]); - - $rectorConfig->importNames(); - - // register a single rule - $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); - - // define sets of rules - $rectorConfig->sets([ - LevelSetList::UP_TO_PHP_82, - SetList::DEAD_CODE, - ]); -}; diff --git a/tests/Arch.php b/tests/Arch.php new file mode 100644 index 0000000..3aa969e --- /dev/null +++ b/tests/Arch.php @@ -0,0 +1,15 @@ +register();