diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..46db3c2 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "php-coding", + "version": "0.1.0", + "description": "Idiomatic PHP coding standards for AI assistants — PHP 8.4+, PER Coding Style, PHPStan level 8.", + "author": { + "name": "Cadasto B.V.", + "url": "https://github.com/Cadasto" + }, + "license": "MIT", + "repository": "https://github.com/Cadasto/php-coding-plugin", + "keywords": ["php", "coding-standards", "per", "phpstan", "phpunit"] +} diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..29625a6 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,5 @@ +# CLAUDE.md + +Project instructions live in the root-level AGENTS.md: + +@../AGENTS.md diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json new file mode 100644 index 0000000..40a09e2 --- /dev/null +++ b/.cursor-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "name": "php-coding", + "version": "0.1.0", + "description": "Idiomatic PHP coding standards for AI assistants — PHP 8.4+, PER Coding Style, PHPStan level 8.", + "author": { + "name": "Cadasto B.V.", + "url": "https://github.com/Cadasto" + }, + "license": "MIT", + "skills": "skills", + "agents": "agents", + "rules": "rules", + "hooks": "hooks/cursor-hooks.json" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..47aca82 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Exclude from `git archive` (and similar export) bundles — maintainer / repo infra, not runtime plugin UX +AGENTS.md export-ignore +.github/** export-ignore diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2368d3c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## Summary + + + +## Test plan + +- [ ] `./scripts/validate.sh` (checks index links, the Cursor rule mirror, and reference names) +- [ ] `claude plugin validate .` shows no warnings +- [ ] No reference restates a rule `@PER-CS`, PHPStan level 8, or `composer audit` already enforces +- [ ] Every new rule cites a primary source +- [ ] A new or changed reference has an eval case under `evals/` diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..d722d05 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,18 @@ +name: validate + +on: + push: + branches: [main] + pull_request: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + # CI is strict and deterministic: Python is guaranteed here, so run the + # validator directly (the scripts/validate.sh graceful skip is for local use). + - run: python3 scripts/validate.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc568c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Environment files +.env +.env.local +.env.*.local + +# IDE +.idea/ +.vscode/ +.claude/settings.local.json +*.swp +*.swo +*~ + +# Python (validation harness) +__pycache__/ +*.py[cod] + +# OS +.DS_Store +Thumbs.db + +# Temporary files +*.tmp +*.temp + +# Local working notes (planning/research kept out of the published plugin) +docs/plans/ +docs/research/ + +# Claude Code local project memory (machine-specific) +CLAUDE.local.md + +# PHP tool caches, if someone runs the reference config in a working copy +vendor/ +var/ +.php-cs-fixer.cache +.phpunit.cache + +# Plugin eval run output +evals/results/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7ab44fb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,140 @@ +# AI Guidelines: PHP Coding Plugin + +This file provides guidance to AI coding assistants (Claude Code, Cursor, and compatible tools that read `AGENTS.md`) working in this repository. It is the **canonical** instruction set. `.claude/CLAUDE.md` imports it with `@../AGENTS.md`, and any host-specific instruction file defers to it. + +## Project Overview + +The **PHP Coding Plugin** is an AI plugin by Cadasto B.V. that teaches AI coding assistants **idiomatic PHP**: the PER rules a fixer does not settle, types, exceptions, modern idioms, PHPUnit, security, the PSR interfaces, and the libraries PHP services lean on (Guzzle, Monolog, OpenTelemetry, Slim, standalone Symfony components). It targets **both Claude Code and Cursor** from a single shared component set. + +> **Current status: v0.1.0, not yet published.** One knowledge skill, `php-coding`, is an index over eleven reference files. Beside it are the `/php-lint-setup` command skill, the report-only `php-reviewer` agent, the `rules/php-context.mdc` Cursor rule, `session-start` and `format-on-save` hooks, the reference lint config, and an eval suite. Everything passes `./scripts/validate.sh`. Do not assume a file is present because it is documented here. Check first. + +## Domain Context + +Guidance is grounded in sources read on 2026-09-30, not in memory: + +- **Language pin — PHP 8.4.** The Cadasto template-repo requires `"php": "^8.4"`. PHP 8.4 is in active support until 31 Dec 2026 (latest patch that day: 8.4.26). — +- **Current stable — PHP 8.5.11** (24 Sep 2026). Features that need 8.5 are hints until a repo requires them. PHP 8.6 was a release candidate and is not a target. — +- **PER Coding Style 3.1** (tagged 13 Aug 2026) is the living standard. It extends and replaces PSR-12, which stays the frozen baseline PER still accepts. — , +- **php-cs-fixer `@PER-CS`** aliases the newest set the fixer ships, `@PER-CS3x0` (PER 3.0), in php-cs-fixer 3.95.27. There is no 3.1 set yet, so `style.md` lists the 3.1 additions to follow by hand. — +- **PHPStan level 8** (cumulative; level 8 adds nullable access). `phpVersion: 80400` is PHP 8.4. PHPStan has no taint analysis. — +- **PHPUnit 12.5**, the line the template-repo requires. PHPUnit 12.0 removed doc-comment metadata, so only attributes count. Pest is out of scope. — , +- **Compatibility**: SemVer 2.0.0 and Symfony's backward compatibility promise define a break; `roave/backward-compatibility-check` 8.22.0 detects one. — +- **Libraries** (latest stable that day): Guzzle **8.2.0**, with 7.15.x still patched; Monolog **3.12.1**; OpenTelemetry PHP api 1.10.0 and sdk 1.15.0, with traces, metrics, and logs all stable; Slim **4.15.3**, with no Slim 5 release; Symfony **8.1**, whose components require PHP **8.4.1**, and 7.4 LTS on PHP 8.2+; `psr/http-message` 2.0 and `psr/log` 3.0. Each reference cites its own release tag. +- **Skill format** follows Anthropic's skill authoring best practices: an always-on description of at most 1,024 characters, a short SKILL.md, and reference files one level deep, with a table of contents past 100 lines. — + +Decisions taken from the template-repo, and the ones this plugin does not copy: + +- Kept: PHP `^8.4`, `declare(strict_types=1)` on every file, PHPStan level 8, PHPUnit 12. +- Not copied: PHPCS with a PSR-12 ruleset (`@PER-CS` already includes `@PSR12`). `treatPhpDocTypesAsCertain: false` (it relaxes checks). Rector `deadCode` and `codeQuality` as a default (Rector is optional, pinned to PHP 8.4, and the fixer runs after it). + +Scope: framework-neutral PHP, the PSR interfaces, Guzzle, Monolog, OpenTelemetry, Slim, and Symfony components used standalone. Out of scope: Laravel framework code ([Laravel Boost](https://github.com/laravel/boost) covers it), full-stack Symfony configuration, WordPress, Drupal, and Pest. + +When a recommendation derives from a source, attribute it. A rule the fixer, PHPStan, or `composer audit` already fails the build for is a command, not a paragraph. + +### Refreshing a dated fact + +The facts above are copied into the README, the testing doc, the index description, the references, the agent, the Cursor rule, the session-start line, and the reference config comments. When one changes, re-read the source, then list every copy: + +```bash +grep -rnE '8\.5\.[0-9]+|8\.4\.[0-9]+|current stable|PER Coding Style 3|PER-CS3x0|12\.5|Guzzle [78]|Monolog 3|Slim 4|Symfony [78]|2026-09-30' --exclude-dir=.git --exclude-dir=evals --exclude=CHANGELOG.md . +``` + +Update each hit. Keep release numbers and "checked" dates in `## Sources` sections and in this file, not in rule text. Leave released CHANGELOG sections as they are. + +## Repository Layout + +- **Claude manifest**: `.claude-plugin/plugin.json` — `name`, `version`, `description`, `author` (an **object** `{name, url}`), `license`, `repository`, `keywords`. Claude Code discovers `skills/`, `agents/`, and `hooks/` automatically. +- **Cursor manifest**: `.cursor-plugin/plugin.json` — the same metadata **plus** explicit path keys (`skills`, `rules`, `agents`, `hooks`). No `mcpServers`. Keep `name`, `version`, `description`, and `author` identical to the Claude manifest. +- **Knowledge skill**: `skills/php-coding/SKILL.md` is the index: principles, a routing table from "what the change touches" to a command and a reference, and a minimum checklist. Its references are `skills/php-coding/references/`: `style`, `idioms`, `testing`, `security`, `compatibility`, `psr`, `guzzle`, `monolog`, `opentelemetry`, `slim`, `symfony-components`. +- **Command skill**: `skills/php-lint-setup/SKILL.md` (`/php-lint-setup`), with `argument-hint` and `allowed-tools`. It is separate because it is user-invoked. The legacy `commands/` folder is not used. +- **Agent**: `agents/php-reviewer.md`. Report-only, no sub-agent dispatch. Its `tools:` include `Skill` so it can load `php-coding`. +- **Cursor rule**: `rules/php-context.mdc` (`globs: ["**/*.php"]`) mirrors the index table. +- **Claude hooks**: `hooks/hooks.json` wires `SessionStart` and `PostToolUse` (`matcher: "Write|Edit"`) to the shared scripts as `bash "${CLAUDE_PLUGIN_ROOT}/hooks/