| 1 |
# Imagify — project instructions for AI agents |
| 2 |
|
| 3 |
Project Configuration values below override a pipeline's derived defaults. Behaviour — how agents |
| 4 |
run, dispatch, or report — belongs to the pipeline, not to this file. |
| 5 |
|
| 6 |
--- |
| 7 |
|
| 8 |
## Project Configuration |
| 9 |
|
| 10 |
| Variable | Value | |
| 11 |
|---|---| |
| 12 |
| `REPO` | `wp-media/imagify-plugin` | |
| 13 |
| `SLUG` / `TEXT_DOMAIN` | `imagify` | |
| 14 |
| `BASE_BRANCH` | `develop` | |
| 15 |
| Protected branches | `develop`, `master` — never commit directly | |
| 16 |
| Branch naming | `<prefix>/<issue>-<slug>`; prefix `fix` (bugs), `enhancement` (features, incl. `feat` commits), `test` | |
| 17 |
| Test (full / unit / integration) | `composer run-tests` / `composer test-unit` / `composer test-integration` | |
| 18 |
| Test (one group) | `vendor/bin/phpunit --configuration Tests/Integration/phpunit.xml.dist --group <Name>` | |
| 19 |
| Lint / auto-fix | `composer phpcs` / `composer phpcbf` | |
| 20 |
| Static analysis | `composer run-stan` | |
| 21 |
| Build frontend | `npm run build` | |
| 22 |
| Local env up / down / seed | `bash bin/dev-up.sh` / `bash bin/dev-down.sh` / `bash bin/dev-seed.sh` | |
| 23 |
| E2E runner | `bash bin/test-e2e.sh` (`--headed`, `--ui`, or a spec path) | |
| 24 |
| Local URL | `http://localhost:8888` — admin `admin` / `password` | |
| 25 |
| Settings page | `/wp-admin/options-general.php?page=imagify` | |
| 26 |
| Key option | `imagify_settings` (API key + config) | |
| 27 |
| PR template | `.github/PULL_REQUEST_TEMPLATE.md` | |
| 28 |
|
| 29 |
`.env.local` at the repo root (gitignored) holds `IMAGIFY_TESTS_API_KEY=<key>`. The same value must |
| 30 |
exist as a GitHub secret for CI optimization tests. |
| 31 |
|
| 32 |
--- |
| 33 |
|
| 34 |
## Traps |
| 35 |
|
| 36 |
Things that fail in ways you will not notice. |
| 37 |
|
| 38 |
- **Run `composer install` before any test or lint command.** Strauss namespace-prefixing is a |
| 39 |
Composer post-install hook. Without it, PHPCS and PHPUnit cannot resolve `Imagify\Dependencies\*` |
| 40 |
and fail with misleading autoload errors. |
| 41 |
- **`WordPress.Security.NonceVerification.Missing` and `.Recommended` are deliberately suppressed** |
| 42 |
in `phpcs.xml`. Do not add `phpcs:ignore` for them and do not restructure code to satisfy them. |
| 43 |
- **`assets/` is build output.** Edit `_dev/` and rebuild, or the next build discards your work. |
| 44 |
- **There is no JavaScript unit test runner** (no Jest, no Vitest). Never invent `npm test`. Frontend |
| 45 |
verification is `npm run build` plus Playwright; otherwise report JS tests as `N/A`. |
| 46 |
- **Optimization behaviour sits behind license, API-key and quota guards.** Before reporting that a |
| 47 |
feature works or is broken, run `bash bin/dev-seed.sh` and confirm the key landed: |
| 48 |
`npx @wordpress/env run cli wp option get imagify_settings --format=json | grep -c '"api_key":"[^"]\+'` |
| 49 |
Only once the key is present (result `1`) may you treat a guard as a genuine blocker. Never report a |
| 50 |
behavioural pass or failure *through* a blocked guard — report "cannot verify" with the guard's |
| 51 |
`file:line`. Guard locations are in the `e2e` skill. |
| 52 |
- **A PR's number is not its issue number.** Read it back from the `gh pr create` output. |
| 53 |
- **PHP-rendered admin UI is a frontend change.** `wp_admin_notice()`, |
| 54 |
`add_action( 'admin_notices', ... )` and `add_settings_error()` need frontend scoping and testing |
| 55 |
despite living in PHP. |
| 56 |
|
| 57 |
--- |
| 58 |
|
| 59 |
## Architecture |
| 60 |
|
| 61 |
Single-edition plugin — no FREE/PRO split. Namespace root `Imagify\`, PSR-4 root `classes/`. |
| 62 |
|
| 63 |
- `classes/` — modern PSR-4, `declare(strict_types=1)` required. **New features go here.** |
| 64 |
- `inc/classes/` — legacy classmap, `Imagify_` prefix, migrating toward `classes/`. **Add nothing here.** |
| 65 |
|
| 66 |
In new `classes/` code: |
| 67 |
|
| 68 |
- No new singletons or `InstanceGetterTrait` usage. The trait is still used in `classes/Plugin.php`, |
| 69 |
`classes/Bulk/Bulk.php` and elsewhere — that is legacy precedent, not a pattern to copy. |
| 70 |
- No bare `add_action()` / `add_filter()`. Register hooks through a `Subscriber` implementing |
| 71 |
`SubscriberInterface`, listed in `ServiceProvider::get_subscribers()`. |
| 72 |
- Service providers live at `classes/*/ServiceProvider.php` and are registered in |
| 73 |
`config/providers.php`. |
| 74 |
- No global state, and no UI logic coupled to infrastructure logic. The legacy `inc/` layer is |
| 75 |
procedural and full of both — do not pattern-match from it. |
| 76 |
|
| 77 |
Nonce action naming: `imagify_<feature>_<action>`. |
| 78 |
|
| 79 |
DI container is Strauss-prefixed: `Imagify\Dependencies\League\Container\Container` — a plain |
| 80 |
`use League\Container\Container;` is wrong here. Async work uses ActionScheduler, not wp-cron. |
| 81 |
|
| 82 |
Frontend source is `_dev/` (Grunt, `gruntfile.js`); `_dev/` also carries its own `bud.config.js`. |
| 83 |
|
| 84 |
--- |
| 85 |
|
| 86 |
## Coding standards |
| 87 |
|
| 88 |
Source of truth: `composer.json` scripts, `phpcs.xml`, `phpstan.neon.dist`, CI. Never hardcode PHPCS |
| 89 |
standards or invent lint commands. |
| 90 |
|
| 91 |
Imagify must stay WordPress.org-compatible |
| 92 |
([](https://github.com/WordPress/plugin-check/Plugin Check](https://github.com/WordPress/plugin-check/](https://github.com/WordPress/plugin-check/)). Evaluate any change to public APIs, |
| 93 |
output, security, metadata, or bootstrap behaviour against it. |
| 94 |
|
| 95 |
- PHP 7.4+, strict types in all new `classes/` files. |
| 96 |
- Text domain `imagify`: `esc_html__( 'Label', 'imagify' )`. |
| 97 |
- Authorization: use the project's registered capability (`'bulk-optimize'`, `'manage'`, …) in |
| 98 |
`current_user_can()`, never the coarse `manage_options`. |
| 99 |
- No jQuery in new or modified JavaScript — native DOM APIs only, no inline handlers, no unsafe |
| 100 |
`innerHTML`. Pass nonces via `wp_localize_script`. |
| 101 |
- Remote responses from the Imagify API are untrusted input and are this plugin's main attack |
| 102 |
surface. Validate and escape everything that comes back before storing or echoing it. |
| 103 |
|
| 104 |
--- |
| 105 |
|
| 106 |
## Testing |
| 107 |
|
| 108 |
Prefer **integration tests** (`Tests/Integration/`, annotated `@group FeatureName`) — they exercise |
| 109 |
real WordPress context, DI wiring and hook execution. Unit tests only for pure logic with no WP |
| 110 |
globals, container or hooks. |
| 111 |
|
| 112 |
Scale scope to risk: LOW = the relevant `--group`; MEDIUM = `composer test-unit` plus that group; |
| 113 |
HIGH = `composer run-tests`. |
| 114 |
|
| 115 |
Not scope violations: generated files (`*.min.js`, `*.min.css`), lockfiles, tests mirroring changed |
| 116 |
source, auto-formatter output. |
| 117 |
|
| 118 |
--- |
| 119 |
|
| 120 |
## Commits and PRs |
| 121 |
|
| 122 |
- Conventional Commits: `type(scope): short description`. Each commit passes PHPCS and static |
| 123 |
analysis before being made. |
| 124 |
- Pipeline-authored commits carry a `Co-Authored-By: <model> <[email protected]>` trailer so |
| 125 |
automated work stays auditable. Human-authored commits do not. |
| 126 |
- Do not squash unrelated changes. Do not amend commits already pushed. |
| 127 |
- Outside a pipeline working a specific ticket, only *suggest* commits — do not run `git commit` or |
| 128 |
`git push`. |
| 129 |
- PR title: `Closes #<N>: <short title>` — never a Conventional-Commit prefix; that is for commits. |
| 130 |
- The PR body needs a standalone `Closes #<N>` line, not buried in prose — that is what auto-closes. |
| 131 |
- Apply the `Made by AI` label to every AI-created PR and issue. |
| 132 |
- Split work vertically: each PR one complete behaviour including its tests. Never a backend PR plus |
| 133 |
a separate frontend PR for one feature, even when separate agents did the work. |
| 134 |
- Do not run large automated refactors or reorganize files without being asked. The legacy-to-modern |
| 135 |
migration is ongoing; "cleaning up while I'm here" is not in scope. |
| 136 |
|
| 137 |
--- |
| 138 |
|
| 139 |
## On-demand references |
| 140 |
|
| 141 |
- **Browser testing** — admin URLs, page objects, environment variables, local running: |
| 142 |
[](docs/E2E_TESTING.md`docs/E2E_TESTING.md`](docs/E2E_TESTING.md](docs/E2E_TESTING.md) |
| 143 |
- **QA procedure** — tiers, spec conventions, guard locations: the `e2e` skill (`.claude/skills/e2e/`) |
| 144 |
- **Code structure** — use the installed pipeline's knowledge-graph skill if it provides one. A local |
| 145 |
builder also exists: `node bin/build-knowledge-graph.js` writes `.aiassistant/graph/` (gitignored). |
| 146 |
Do not mix the two graphs in one session; if neither is current, use grep rather than stopping. |
| 147 |
|
| 148 |
--- |
| 149 |
|
| 150 |
## Priority order |
| 151 |
|
| 152 |
When these conflict, earlier wins: |
| 153 |
|
| 154 |
1. Security |
| 155 |
2. WordPress.org compliance |
| 156 |
3. Architectural integrity |
| 157 |
4. Backward compatibility |
| 158 |
5. Minimal diffs |
| 159 |
6. Performance |
| 160 |
|