This file defines NON-NEGOTIABLE rules for any AI-assisted work (Claude Code, ChatGPT, JetBrains AI Assistant, Cursor, etc.) in this repository.
Skills define behavioral guidance. AGENTS.md defines mandatory guardrails. If a conflict exists, AGENTS.md prevails.
These five rules apply to every agent, in every phase, before any skill-specific guidance loads.
- Surface assumptions before building. If the spec or codebase leaves something ambiguous, state the assumption explicitly before acting on it — don't silently guess.
- Stop when requirements conflict. If the issue, the spec, and the codebase contradict each other, stop and surface the conflict. Proceeding on a guess produces bugs that are hard to trace.
- Push back when warranted. If the simplest correct solution differs from the plan, say so. Prefer boring, obvious solutions over clever ones. An elegant approach that introduces risk is worse than a dull one that doesn't.
- Touch only what you are asked to touch. Scope discipline is the single biggest determinant of whether a PR is mergeable. Do not refactor adjacent code, rename unrelated identifiers, or "clean up while you're in the area."
- Verification is not optional. "Seems right" never closes a task. Every change must be confirmed by running tests, tools, or a manual scenario — not by reading the code and inferring it should work.
The objective is to keep WP Rocket:
- WordPress.org compliant
- Architecturally consistent
- Secure
- Maintainable
- Review-friendly
This document applies to ALL automated or AI-generated changes.
WP Rocket is a single-edition commercial WordPress caching plugin maintained by WP Media.
Core architectural patterns:
- Subscriber pattern — event-driven hooks via
Subscriber_Interface - League Container — dependency injection container
- ServiceProvider pattern — modules register their own bindings and subscribers
- PSR-4 autoloading —
WP_Rocket\namespace maps toinc/
When modifying architecture:
- Follow existing patterns (Subscriber → Container → ServiceProvider).
- Prefer adding new ServiceProviders over modifying existing ones.
- Keep infrastructure concerns out of Subscriber classes.
Source of truth:
- Composer scripts (
composer.json) - PHPCS rulesets (
phpcs.xml/phpcs.xml.dist/phpcs.baseline.xml) - PHPStan / Psalm configs if present (
phpstan.neon,phpstan-baseline.neon) - WordPress Plugin Check: https://github.com/WordPress/plugin-check/
- CI pipeline rules
WP Rocket must remain compatible with WordPress.org validation rules.
Any change affecting public APIs, output, security, metadata, or plugin bootstrap behavior must be evaluated against WordPress Plugin Check expectations.
AI MUST:
- Read
composer.jsonfirst and use defined scripts (e.g.lint,phpcs,phpcbf,test,phpstan) instead of inventing commands. - Auto-discover PHPCS configuration and follow it as the single source of truth.
Before making changes that affect standards or formatting, the agent MUST locate and respect the repository configuration files.
-
composer.json- Use scripts defined in
"scripts"whenever possible. - Prefer the exact commands used by CI.
- Do not invent lint/test commands.
- Use scripts defined in
-
PHPCS ruleset / baseline (first match wins, but consider all if referenced):
phpcs.xmlphpcs.xml.distphpcs.baseline.xml- Any PHPCS file referenced by composer scripts or CI
-
Static analysis configs (if present / referenced):
phpstan.neon,phpstan.neon.distphpstan-baseline.neon
- Do NOT hardcode PHPCS standards.
- Do NOT assume WordPress-Core or WordPress-Extra unless defined in the ruleset.
- If multiple PHPCS files exist, follow what is referenced by:
a) Composer scripts, then
b) CI configuration, then
c) Root-level
phpcs.xml(.dist)
If no PHPCS configuration exists, stop and ask.
WP Rocket ships four custom PHPStan rules. Every change must satisfy them:
| Rule | What it enforces |
|---|---|
DiscourageApplyFilters |
Use wpm_apply_filters_typed() instead of apply_filters() |
DiscourageWPOptionUsage |
Use injected Option objects instead of get_option() directly |
EnsureCallbackMethodsExistsInSubscribedEvents |
Every method name declared in get_subscribed_events() must exist in the class |
NoHooksInORM |
No WordPress hooks (add_action, add_filter, apply_filters) inside database Query/Table classes |
wpm_apply_filters_typed() is mandatory for all new filters:
// ❌ Never — flagged by DiscourageApplyFilters
$value = apply_filters( 'rocket_my_filter', $default );
// ✅ Always — type-safe, with required docblock
/**
* Filters the custom value.
*
* @param string $value The custom value.
* @return string
*/
$value = wpm_apply_filters_typed( 'string', 'rocket_my_filter', $default );Available types: 'string', 'integer', 'boolean', 'array', 'string[]'.
Option objects are mandatory for reading plugin settings:
// ❌ Never
$value = get_option( 'wp_rocket_settings' );
// ✅ Always — inject Options_Data via constructor
/** @var Options_Data */
private $options;
public function __construct( Options_Data $options ) {
$this->options = $options;
}
$value = $this->options->get( 'option_key', $default );AI must NOT:
- Introduce global state.
- Add new singletons without discussion.
- Bypass the League Container / dependency injection patterns used in the project.
- Couple UI logic to infrastructure logic.
- Modify
inc/Dependencies/without explicit instruction (vendored code). - Use
add_action/add_filterdirectly — always use Subscribers. - Use
apply_filters()directly — always usewpm_apply_filters_typed(). - Use
get_option( 'wp_rocket_settings' )directly — inject anOptions_Datainstance.
Follow existing patterns:
- Subscriber → implements
Subscriber_Interface, declaresget_subscribed_events() - ServiceProvider → extends
AbstractServiceProvider, binds services inregister() - Context classes →
inc/Engine/Feature/Context/Context.phpencapsulates "should this feature run?" logic; inject into Subscribers, never inline those checks - Container wiring → via ServiceProvider only, never manual
new ClassName() - Strict types where already used
- Namespacing:
WP_Rocket\Engine\*for engine features,WP_Rocket\Admin\*for admin
When adding a new feature module, follow this layout:
inc/Engine/MyFeature/
├── ServiceProvider.php # binds all services and declares $provides
├── Context/
│ └── Context.php # is this feature active? (injected into Subscriber)
├── Admin/
│ └── Subscriber.php # admin-only hooks
├── Frontend/
│ ├── Controller.php # business logic
│ └── Subscriber.php # frontend hooks
└── Database/ # only when custom tables are needed
├── Tables/MyFeature.php
├── Queries/MyFeature.php
├── Rows/MyFeature.php
└── Schemas/MyFeature.php
Table version format is YYYYMMDD. Migrations are declared in $upgrades:
protected $version = 20251006;
protected $upgrades = [
20251006 => 'add_new_column',
];
protected function add_new_column(): void { /* ALTER TABLE … */ }WP Rocket follows Test-Driven Development. Write tests before or alongside new code, not after.
| Type | Location | Command |
|---|---|---|
| Unit | tests/Unit/ |
composer test-unit |
| Integration | tests/Integration/ |
composer test-integration |
| Specific group | — | vendor/bin/phpunit --configuration tests/Integration/phpunit.xml.dist --group FeatureName |
Test files mirror the source structure: inc/Engine/Foo/Bar.php → tests/Unit/inc/Engine/Foo/Bar/methodName.php.
- Unit — business logic in isolation; mock all dependencies with Brain\Monkey / Mockery; no WordPress context needed.
- Integration — WordPress hooks, database operations, or hook interactions; extend the appropriate base class (
TestCase,AdminTestCase,AjaxTestCase).
Unit test with data provider:
class ProcessDataTest extends TestCase {
/** @dataProvider dataProvider */
public function testShouldReturnExpectedResult( $input, $expected ): void {
$result = ( new MyService() )->process( $input );
$this->assertSame( $expected, $result );
}
public function dataProvider(): array { return [ ... ]; }
}Integration test with fixture:
/** @group MyFeature */
class ProcessDataTest extends TestCase {
/** @dataProvider configTestData */
public function testShouldReturnExpectedResult( $config, $expected ): void { ... }
}
// fixture: tests/Fixtures/inc/Engine/MyFeature/…/processData.php → return [ 'scenario' => [ 'config' => …, 'expected' => … ] ];For every change:
- Run
composer phpcs-changedfirst (fast: checks only modified files), thencomposer phpcsbefore committing. - Run
composer run-stan— satisfy all four custom PHPStan rules (§2.2). - Run the relevant test suite; no regressions.
- Do not delete tests unless clearly obsolete.
If modifying templates:
- Validate escaping correctness.
- Ensure no functional regressions.
AI must work in small, incremental changes.
After each logical change set:
- explain what changed
- explain why
- list potential edge cases
AI must NOT:
- Perform massive automated refactors without approval.
- Reorganize files without explicit instruction.
- Rewrite entire classes when a minimal fix is sufficient.
By default, AI may only suggest commit messages and must not run git commit or git push.
Exception — Issue Workflow: When operating under the issue-workflow skill (triggered by /task <number>, issue <number>, or #<number>), the agent MAY:
- Run atomic
git commitcalls — one commit per logical, self-contained change set. - Run
git pushexactly once after all commits are ready, to publish the branch. - Create a GitHub Pull Request using the prepared PR draft.
- Monitor PR CI status checks until all pass or a failure is detected.
Atomic commit rules:
- Each commit must pass PHPCS and static analysis before being committed.
- Commit message format:
type(scope): short description(Conventional Commits). - Do not squash unrelated changes into a single commit.
- Do not amend commits that have already been pushed.
Changes must:
- Be minimal.
- Be scoped.
- Have clear intent.
- Avoid noise in diff.
- Avoid unrelated formatting changes.
Branches MUST follow these patterns:
- Bug fixes:
fix/{GitHub-issue-ID}-{description} - Enhancements:
enhancement/{GitHub-issue-ID}-{GitHub-issue-title} - Tests:
test/{GitHub-issue-ID}-{GitHub-issue-title}
Rules:
- Lowercase letters, hyphens for spaces.
- Always include the GitHub issue ID.
- Keep descriptions concise (first 4 words max).
Always assume:
- User input is untrusted.
- Remote API responses are untrusted.
- Stored values may be tampered with.
Never:
- Store sensitive values in plain text without review.
- Introduce unsafe serialization.
- Echo unescaped dynamic data.
Stop. Explain the ambiguity. Ask for clarification.
Architectural integrity is more important than speed.
The qa-engineer sub-agent validates PRs automatically after step 19 of the issue workflow.
It reads the PR spec, selects a validation strategy (API / Browser / Analysis), and produces
a structured test report.
Agent definition: .aiassistant/agents/qa-engineer.md.
The local WordPress environment at http://localhost:8888 (admin / password) is used for
browser validation via Playwright MCP. No containerised environment is required.
The repository defines AI Skills under /.aiassistant/skills.
Agents MUST activate the relevant skill depending on the task:
- Template or UI changes → WordPress Compliance Skill
- Structural or architectural changes → WP Rocket Architecture Skill
- Core service modifications → Both skills
- Codebase exploration / dependency tracing → Knowledge Graph Skill
A pre-built dependency graph is available at .aiassistant/graph/dependency-graph.json.
Before exploring the codebase structure (finding a class, tracing dependencies, checking namespace boundaries), read this file first. It contains:
nodes: per-file namespace, declared symbols, and imports.symbol_index: maps every fully-qualified PHP class/interface/trait/enum to its file.
Run node bin/build-knowledge-graph.js to refresh after structural changes (--full to force rebuild).
Canonical GitHub repository: wp-media/wp-rocket
Unless explicitly instructed otherwise, all GitHub issue, PR, and branch workflows must assume this repository.
The repository may define task-specific implementation specs under:
.aiassistant/specs/
Specs provide detailed guidance for recurring technical problems (e.g. PHPCS warnings, architecture migrations, WordPress compliance patterns).
When a relevant spec exists, agents must follow it in addition to:
• AGENTS.md • the applicable skills
When executing tasks, agents must prioritize:
- Security
- WordPress.org compliance
- Architectural integrity
- Backward compatibility
- Minimal diffs
- Performance
AGENTS.md remains the final authority.
Human-curated only. Never regenerate this section with an LLM — doing so degrades agent success rates. After each pipeline run, a human adds entries for findings that were surprising and are not already derivable from the code or other sections of this file.
Format per entry:
- **[YYYY-MM-DD] [module or area]**: What was surprising. What the correct approach is.
Agents MUST read this section. It takes precedence over any assumption derived from the spec or skill files when there is a conflict.
No entries yet. Add one after the first surprising pipeline finding.