From 77727aac1bc56c40a38c75360374608285e32067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 17 Feb 2026 23:27:46 +0100 Subject: [PATCH 1/4] Add agent-readiness setup: AGENTS.md, Docker sandbox, Dev Containers AI coding agents need three things to work well in a repo: knowledge of the project conventions, the ability to build and test on their own, and a sandboxed environment where mistakes don't escape. AGENTS.md captures the hard-to-discover knowledge: the PHP 7.2 constraint, the zero-dependency rule, the classmap autoloading convention, the single-class architecture, and the common pitfalls that trip up both humans and agents. CLAUDE.md points to it so Claude Code picks it up automatically. The Dockerfile and docker-compose.yml provide a locked-down sandbox (no network, read-only root, all capabilities dropped) where agents can run tests and lints without touching the host. The Dev Container spec reuses the same Dockerfile for VS Code, Codespaces, and any editor that supports the standard. .claude/settings.json pre-approves the build, test, lint, and Docker commands so agents can self-verify without prompting on every run. --- .claude/settings.json | 16 +++ .devcontainer/devcontainer.json | 25 ++++ .dockerignore | 10 ++ AGENTS.md | 215 ++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + Dockerfile | 35 ++++++ README.md | 41 +++++- docker-compose.yml | 22 ++++ 8 files changed, 361 insertions(+), 4 deletions(-) create mode 100644 .claude/settings.json create mode 100644 .devcontainer/devcontainer.json create mode 100644 .dockerignore create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..95504af2 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,16 @@ +{ + "permissions": { + "allow": [ + "Bash(composer install:*)", + "Bash(composer test:*)", + "Bash(composer lint:*)", + "Bash(composer lint-fix:*)", + "Bash(vendor/bin/phpunit:*)", + "Bash(vendor/bin/phpcs:*)", + "Bash(vendor/bin/phpcbf:*)", + "Bash(php bin/regenerate_composer.json.php:*)", + "Bash(docker compose build:*)", + "Bash(docker compose run --rm sandbox:*)" + ] + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..14aa0254 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,25 @@ +{ + "name": "PHP Toolkit", + "build": { + "dockerfile": "../Dockerfile" + }, + "postCreateCommand": "composer install --no-interaction --no-progress", + "customizations": { + "vscode": { + "extensions": [ + "bmewburn.vscode-intelephense-client", + "shevaua.phpcs" + ], + "settings": { + "php.validate.executablePath": "/usr/local/bin/php", + "phpcs.executablePath": "vendor/bin/phpcs", + "editor.insertSpaces": false, + "editor.tabSize": 4 + } + } + }, + "containerEnv": { + "LC_ALL": "en_US.UTF-8", + "LANG": "en_US.UTF-8" + } +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..66814037 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +vendor/ +node_modules/ +dist/ +untracked/ +.idea/ +.vscode/ +.cursor/ +.claude/ +.git/ +*.phar diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..be9564e3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,215 @@ +# AGENTS.md – PHP Toolkit + +## What this is + +A monorepo of standalone PHP libraries for WordPress and general PHP projects. +Each component lives in `components//` and is independently publishable +to Packagist under `wp-php-toolkit/`. Some components are also bundled +into WordPress plugins under `plugins/`. + +Upstream: https://github.com/WordPress/php-toolkit +Branch: `trunk` (not `main`) + +## Commands + +```bash +# Install dependencies (required before anything else) +composer install + +# Run all tests – two suites: fast component tests first, slow Blueprints tests second +composer test + +# Run tests for a single component +vendor/bin/phpunit components/Zip/Tests/ + +# Run a single test class +vendor/bin/phpunit components/Zip/Tests/ZipEncoderTest.php + +# Lint +composer lint + +# Auto-fix lint errors +composer lint-fix + +# Regenerate root composer.json after changing a component's composer.json +php bin/regenerate_composer.json.php +``` + +CRITICAL: After any code change, run lint AND the relevant component tests at +minimum. If your change touches shared code (Encoding, Polyfill, ByteStream, +Filesystem), run the full test suite. + +## Component structure + +Every component follows the same layout: + +``` +components// + class-.php # WordPress naming: class-lowercase-with-dashes.php + functions.php # Optional standalone functions + composer.json # Per-component, with inter-component dependencies + Tests/ + Test.php # PHPUnit test classes (no WordPress naming here) + fixtures/ # Test data + vendor-patched/ # Vendored dependencies (rare, checked in) +``` + +Namespace: `WordPress\` (e.g., `WordPress\Zip\ZipEncoder`). +Autoloading: classmap-based, NOT PSR-4 (despite using namespaces). + +## PHP requirements – CRITICAL + +This project MUST run on PHP 7.2 through 8.3. This means: + +- No typed properties (PHP 7.4+) +- No union types (PHP 8.0+) +- No named arguments (PHP 8.0+) +- No enums (PHP 8.1+) +- No `readonly` (PHP 8.1+) +- No `match` expressions (PHP 8.0+) +- No arrow functions `fn()` (PHP 7.4+) +- No null coalescing assignment `??=` (PHP 7.4+) +- Use `array()` or `[]` — both are fine, but existing code mostly uses `array()` + for multi-line and `[]` for inline + +If you aren't sure whether a feature is available in PHP 7.2, don't use it. +The CI matrix tests PHP 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, and 8.3 on Linux, +macOS, and Windows. + +## Zero external dependencies + +This is a hard constraint. Components MUST NOT require PHP extensions beyond +`json` and `mbstring`. No `libxml2`, no `curl`, no `libzip`, no `sqlite3` +(sqlite3 is a dev-only dependency for tests). No Composer packages beyond +other `wp-php-toolkit/*` components, except rare vendored exceptions already +checked into `vendor-patched/`. + +If you need functionality provided by a PHP extension, implement it in pure PHP. +That's the whole point of this project. + +## Coding conventions + +WordPress coding standards with strategic divergences: + +- **Namespaces**: all components use `namespace WordPress\` +- **File naming**: `class-lowercase-with-dashes.php` (WordPress convention) +- **Spacing**: tabs for indentation, spaces inside parentheses per WordPress + style: `function_name( $arg1, $arg2 )` +- **Variables**: `$snake_case` (NOT `$camelCase`) +- **Methods and functions**: `snake_case()` (NOT `camelCase()`) +- **Classes**: `PascalCase` +- **Constants**: `UPPER_SNAKE_CASE` +- **Test classes**: `PascalCaseTest extends TestCase` (PHPUnit convention, not + WordPress naming) + +The linter enforces most of this. When in doubt, match the surrounding code. + +## Architecture – the single-class philosophy + +The architectural role model is `WP_HTML_Processor` — a single class that does +one thing well. Avoid deep class hierarchies, abstract factories, or patterns +like AbstractSingletonFactoryProxy. When a component needs multiple classes, +that's fine, but keep the API surface small and the class count low. + +Components are designed to be re-entrant: they can start, stop, and resume +processing. Many operate as streaming processors where you feed data in and +get results incrementally. + +## Common pitfalls + +- **Do not add `declare(strict_types=1)`** to component source files. The test + files and config files use it, but the library code deliberately doesn't + because WordPress core doesn't. +- **Do not add type declarations** on parameters or return types that would + break PHP 7.2 compatibility. `?Type` nullable syntax is fine (PHP 7.1+), + but `Type|null` union syntax is not (PHP 8.0+). +- **Do not restructure autoloading to PSR-4.** The classmap approach is + intentional — it matches WordPress core conventions and avoids directory + depth requirements. +- **Do not add Composer dependencies.** If you think you need a package, + implement the functionality yourself or discuss it first. +- **Do not use `\n` in expected test output on disk.** The `.gitattributes` + file normalizes fixtures to LF, but constructing expected output in test + code should use `"\n"`, not `PHP_EOL`. +- **Do not edit files in `vendor-patched/`.** These are manually vendored and + patched third-party code. Changes there require careful consideration. +- **Root `composer.json` is generated.** Don't edit it directly for + dependencies or autoloading — edit the component's `composer.json` and run + `php bin/regenerate_composer.json.php`. +- **Paths MUST use forward slashes** even on Windows. The Filesystem component + deliberately uses Unix-style separators everywhere. See README.md for details. +- **Keep tests fast.** The Blueprints test suite is intentionally separated + because it's slow. Component tests should be quick and self-contained. +- **Do not modify the `plugins/` directory** unless specifically asked. Plugins + are built from components and have their own build step. + +## Test conventions + +- Tests live in `components//Tests/` +- Test classes extend `PHPUnit\Framework\TestCase` +- Use `@before` and `@after` annotations for setup/teardown (not `setUp`/ + `tearDown` methods, following the existing pattern) +- Test fixtures go in `components//Tests/fixtures/` +- Tests MUST pass on PHP 7.2 — use `yoast/phpunit-polyfills` for compatibility + between PHPUnit versions + +## Verification + +To verify changes work end-to-end (not just unit tests), use WordPress +Playground. The `/wordpress-playground` skill starts a local WordPress instance +where you can install plugins from this repo and test behavior from a user +perspective. This is especially useful for the `plugins/` directory. + +## Sandbox + +A Docker-based sandbox isolates agent-built code from the host. The container +runs with no network access, a read-only root filesystem, and all Linux +capabilities dropped — code can only write to the mounted project directory +and `/tmp`. + +```bash +# Build the sandbox image (first time only, or after Dockerfile changes) +docker compose build + +# Run the full test suite inside the sandbox +docker compose run --rm sandbox + +# Run tests for a single component +docker compose run --rm sandbox vendor/bin/phpunit components/Zip/Tests/ + +# Run the linter +docker compose run --rm sandbox vendor/bin/phpcs -d memory_limit=1G . + +# Auto-fix lint errors +docker compose run --rm sandbox vendor/bin/phpcbf -d memory_limit=1G . + +# Open a shell for interactive debugging +docker compose run --rm sandbox bash +``` + +The container uses PHP 8.1 (matching the lint CI). Source files are bind-mounted +so edits on the host are immediately visible inside the container. + +Without Docker, the test suite still works directly on the host — it's fully +self-contained (no database, no web server, no external services). Tests create +temp files in `sys_get_temp_dir()` and clean up after themselves. + +For WordPress-level integration testing, use the WordPress Playground skill +which spins up an isolated WordPress instance in-memory. + +### Dev Containers + +The repo includes a [Dev Containers](https://containers.dev/) spec in +`.devcontainer/devcontainer.json`. This provides a consistent, pre-configured +development environment that works in VS Code, GitHub Codespaces, and any +editor that supports the Dev Containers standard. + +To use it: + +- **VS Code**: Install the Dev Containers extension, then "Reopen in Container" +- **GitHub Codespaces**: Click "Code > Codespaces > New codespace" on GitHub +- **CLI**: `devcontainer up --workspace-folder .` + +The container is built from the same `Dockerfile` used by the sandbox. Composer +dependencies are installed automatically on creation. PHP IntelliSense and +PHPCS linting are pre-configured. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..132269fb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +FROM php:8.1-cli + +# Match CI locale settings +ENV LC_ALL=en_US.UTF-8 +ENV LANG=en_US.UTF-8 + +# Install system dependencies for PHP extensions and locale +RUN apt-get update && apt-get install -y --no-install-recommends \ + libzip-dev \ + libsqlite3-dev \ + libonig-dev \ + locales \ + unzip \ + git \ + && sed -i 's/# en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen \ + && locale-gen \ + && docker-php-ext-install zip pdo pdo_sqlite mbstring \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Composer +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +WORKDIR /app + +# Install dependencies first (cached layer for faster rebuilds) +COPY composer.json composer.lock* ./ +RUN composer install --no-interaction --no-progress --optimize-autoloader 2>/dev/null || true + +# Copy the rest of the source +COPY . . + +# Re-run install in case the cached layer was stale +RUN composer install --no-interaction --no-progress --optimize-autoloader + +CMD ["vendor/bin/phpunit", "-c", "phpunit.xml"] diff --git a/README.md b/README.md index 59f275f5..cb2b6ccc 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,44 @@ For convenience, a standalone Blueprints runner and other tools from this reposi ### Development -#### Testing +#### Dev Container (recommended) + +The quickest way to get a working environment is the [Dev Container](https://containers.dev/) +spec in `.devcontainer/`. It gives you PHP 8.1 with all the required extensions, +Composer, and editor tooling pre-configured. + +- **VS Code**: Install the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), then run "Reopen in Container". +- **GitHub Codespaces**: Click "Code > Codespaces > New codespace" on GitHub — it picks up the spec automatically. +- **CLI**: `devcontainer up --workspace-folder .` + +#### Docker sandbox + +For running tests and lints in an isolated container without a full dev +environment, use the Docker Compose sandbox: + +```sh +# Build once +docker compose build + +# Run all tests +docker compose run --rm sandbox -To run the PHPUnit test suite, run: +# Run tests for one component +docker compose run --rm sandbox vendor/bin/phpunit components/Zip/Tests/ + +# Lint +docker compose run --rm sandbox vendor/bin/phpcs -d memory_limit=1G . +``` + +The sandbox has no network access, a read-only root filesystem, and all Linux +capabilities dropped — the only writable areas are the project mount and `/tmp`. + +#### Without Docker + +The test suite works directly on the host too — no database, no web server, +no external services needed. You just need PHP 7.2+ with `json` and `mbstring`. + +#### Testing ```sh composer test @@ -67,8 +102,6 @@ composer test #### Linting -To run the PHP_CodeSniffer linting suite, run: - ```sh composer lint ``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..104a4e5b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +services: + sandbox: + build: . + volumes: + - .:/app + # Keep container's vendor dir separate so host PHP version + # doesn't conflict with container's compiled extensions. + - vendor_data:/app/vendor + working_dir: /app + # Drop all capabilities, read-only root filesystem. + # The agent can still write to /app (mounted volume) and /tmp. + read_only: true + tmpfs: + - /tmp + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + network_mode: none + +volumes: + vendor_data: From 5e39ebdfbe12d86f2f02f64b0de347a39f9451d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 17 Feb 2026 23:41:10 +0100 Subject: [PATCH 2/4] Update verification section in AGENTS.md Clarify verification instructions for end-to-end testing. --- AGENTS.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index be9564e3..cfcc1aee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -155,10 +155,7 @@ get results incrementally. ## Verification -To verify changes work end-to-end (not just unit tests), use WordPress -Playground. The `/wordpress-playground` skill starts a local WordPress instance -where you can install plugins from this repo and test behavior from a user -perspective. This is especially useful for the `plugins/` directory. +To verify changes work end-to-end (not just unit tests). ## Sandbox From cfbed0c6bee36d1263bad1acf47b00875b491caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 17 Feb 2026 23:42:45 +0100 Subject: [PATCH 3/4] Tone down Dev Container section in README Present it as one option among others, not a recommendation. --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cb2b6ccc..3a7f30a4 100644 --- a/README.md +++ b/README.md @@ -57,15 +57,12 @@ For convenience, a standalone Blueprints runner and other tools from this reposi ### Development -#### Dev Container (recommended) +#### Dev Container -The quickest way to get a working environment is the [Dev Container](https://containers.dev/) -spec in `.devcontainer/`. It gives you PHP 8.1 with all the required extensions, -Composer, and editor tooling pre-configured. - -- **VS Code**: Install the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), then run "Reopen in Container". -- **GitHub Codespaces**: Click "Code > Codespaces > New codespace" on GitHub — it picks up the spec automatically. -- **CLI**: `devcontainer up --workspace-folder .` +A [Dev Container](https://containers.dev/) spec is included in `.devcontainer/`. +It provides PHP 8.1 with all the required extensions, Composer, and editor +tooling pre-configured. Works with VS Code ("Reopen in Container"), GitHub +Codespaces, or `devcontainer up --workspace-folder .` from the CLI. #### Docker sandbox From 6b0e26413f47d5f933db01be23be2819a501a60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 17 Feb 2026 23:44:13 +0100 Subject: [PATCH 4/4] Default sandbox CMD to bash so it can run any PHP, not just tests The container now drops into a shell by default. Tests, lints, and arbitrary PHP scripts are all passed as explicit commands. This makes the sandbox useful for experimenting with library code, not only for running the test suite. --- AGENTS.md | 7 +++++-- Dockerfile | 2 +- README.md | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cfcc1aee..e43a2703 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -169,11 +169,14 @@ and `/tmp`. docker compose build # Run the full test suite inside the sandbox -docker compose run --rm sandbox +docker compose run --rm sandbox vendor/bin/phpunit -c phpunit.xml # Run tests for a single component docker compose run --rm sandbox vendor/bin/phpunit components/Zip/Tests/ +# Run a PHP script +docker compose run --rm sandbox php my-script.php + # Run the linter docker compose run --rm sandbox vendor/bin/phpcs -d memory_limit=1G . @@ -181,7 +184,7 @@ docker compose run --rm sandbox vendor/bin/phpcs -d memory_limit=1G . docker compose run --rm sandbox vendor/bin/phpcbf -d memory_limit=1G . # Open a shell for interactive debugging -docker compose run --rm sandbox bash +docker compose run --rm sandbox ``` The container uses PHP 8.1 (matching the lint CI). Source files are bind-mounted diff --git a/Dockerfile b/Dockerfile index 132269fb..12170472 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,4 +32,4 @@ COPY . . # Re-run install in case the cached layer was stale RUN composer install --no-interaction --no-progress --optimize-autoloader -CMD ["vendor/bin/phpunit", "-c", "phpunit.xml"] +CMD ["bash"] diff --git a/README.md b/README.md index 3a7f30a4..959f69ee 100644 --- a/README.md +++ b/README.md @@ -74,11 +74,14 @@ environment, use the Docker Compose sandbox: docker compose build # Run all tests -docker compose run --rm sandbox +docker compose run --rm sandbox vendor/bin/phpunit -c phpunit.xml # Run tests for one component docker compose run --rm sandbox vendor/bin/phpunit components/Zip/Tests/ +# Run a PHP script +docker compose run --rm sandbox php my-script.php + # Lint docker compose run --rm sandbox vendor/bin/phpcs -d memory_limit=1G . ```