Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copilot instructions — Theme Elementary

A custom WordPress **block theme** (PHP 8.2+) built on `rtcamp/wp-framework` (`rtCamp\WPFramework`, installed in gitignored `vendor/`: not visible at review).

Detailed, path-scoped rules live in `.github/instructions/`:
- `framework-php.instructions.md` (generated by `npm run sync-ai` from the framework; absent until then): all `**/*.php` framework architecture, security, testing, review flags.
- `structure.instructions.md`: theme layout and wiring.

## Stack

- PHP 8.2+, Composer, PSR-4 autoload (namespace === directory, filename === class).
- PHPUnit + `wp-phpunit` in `tests/php/` (mirrors `inc/`).
- PHPCS (WordPress-Core/Extra/Docs + VIPCS) and PHPStan: zero errors before merge.
- Block theme: `theme.json`, `templates/`, `parts/`, `patterns/`, `styles/`.

## Universal rules

- **TDD**: write the failing test first, then the implementation. Never ship code without a test.
- **Do not default to Singleton.** Hook WordPress through the framework `Loader` + `Registrable`; use `Shareable` only when an instance must be retrieved later. Full decision tree in `framework-php.instructions.md`.
- Prefer official WordPress / `@wordpress/*` APIs and block-theme mechanisms (`theme.json`, patterns, template parts) over custom PHP.
- Never modify WordPress core or anything under `vendor/`; extend via actions and filters.

## Commands

- `vendor/bin/phpunit -c phpunit.xml.dist` · `composer phpcs` · `composer phpcs:fix` · `composer phpstan`.

## Review conduct

These tune *how* you comment; the *what* lives in the path-scoped files. Balance matters: catch every real issue, but don't drown it in noise.

- **One comment per distinct issue, and never more than one per comment.** Every distinct problem gets its OWN separate comment at its own line, even when several issues sit on the same or adjacent lines (e.g. an unauthenticated `permission_callback`, raw `$_POST` use, and SQL injection in one method are THREE comments, not one). Do not merge unrelated findings. Conversely, when a *single* root cause spans several lines (e.g. the `Singleton` trait plus its `get_instance()` bootstrap), post it once at the clearest line and name the related lines; don't repeat the same finding.
- **Always post the missing-tests (TDD) finding** when a new/changed feature or class ships without a matching test in `tests/php/`. This is mandatory and exempt from dedupe, trimming, and any comment-budget; never drop it in favour of other findings.
- **Order the rest by impact:** security → architecture/contract (`Singleton`/`Shareable`/`Loader`/PSR-4/abstracts) → WordPress correctness → style. Lead with what breaks things.
- **Don't spend comments on what PHPCS/PHPStan already enforce.** A bare "add a `void` return type" or "add visibility" is noise; the linters catch it. Only raise a typing issue when it changes behavior or hides a real bug. If a non-linter-covered style note is worth raising, give it its own comment too.
- **On a correct, deliberate implementation, don't manufacture findings.** An idiomatic, defensible choice is not a bug. Prefer **zero comments** over low-value ones. A clean PR comes back clean. (A genuine correctness/security observation on otherwise-good code is still welcome; a stylistic preference is not.)
20 changes: 20 additions & 0 deletions .github/instructions/structure.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
applyTo: "inc/**"
description: "Theme structure. Merges with framework-php.instructions.md and copilot-instructions.md."
---

# Theme structure

Block theme. Namespace `rtCamp\Theme\Elementary\` → `inc/`; tests `rtCamp\Theme\Elementary\Tests\` → `tests/php/`. Text domain `elementary-theme`. Entry: `functions.php` → `Autoloader` → `Main::get_instance()`. Block config in `theme.json`, `templates/`, `parts/`, `patterns/`, `styles/`.

Flatter than a plugin: `Main::CLASSES` lists Core/Module classes directly (no `AbstractModule` grouping yet; add one only when a domain grows several related classes). `inc/Core/` (`ThemeSetup`, `Menu`, `Assets`) implement `Registrable` directly; modules in `inc/Modules/`.

**Scaffolding:** the `inc/Modules/` classes are demos shipped to show the pattern. A real project removes unused ones and adds its own per requirements; do not assume a specific module file exists, nor flag one as "missing". `inc/Core/` infra is real and stays. Framework abstracts always exist in `vendor/`; which you extend is requirement-driven.

To add a feature: create the class implementing `Registrable` (or extending a framework abstract, e.g. an options page → `AbstractSettingsPage`), then add its `::class` to `Main::CLASSES`.

## Flag

- 🚩 New `Registrable` class not added to `Main::CLASSES` → it never loads.
- 🚩 Markup/styling hardcoded in PHP where `theme.json` / a block pattern / template part belongs.
- 🚩 Options page or other registration hand-rolled where a framework `Abstract*` fits.
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# AGENTS.md — Theme Elementary

Tool-agnostic brief for AI coding agents (Claude Code, Copilot coding agent, Codex). A custom WordPress **block theme** built on `rtcamp/wp-framework` (in `vendor/`).

## Authoritative rules

The review rules ARE the coding rules: the same files Copilot reviews against. Follow them when writing code; they hold the full detail:

- `.github/instructions/framework-php.instructions.md`: framework architecture, security, testing, and the do/don't flags. Shipped from `rtcamp/wp-framework`, generated locally by `npm run sync-ai` (absent until then).
- `.github/instructions/structure.instructions.md`: theme layout and wiring.
- `.github/copilot-instructions.md`: overview + conventions.

## Key principles (full detail in the files above)

- **TDD**: write the failing PHPUnit test first (`tests/php/` mirrors `inc/`), then code.
- **Don't default to Singleton.** Hook WordPress via the framework `Loader` + `Registrable`; use `Shareable` only when an instance must be retrieved later via `get_shared()`. `Singleton` is for `Main` only.
- **Extend the framework abstracts** (e.g. an options page → `AbstractSettingsPage`) instead of hand-rolling registration.
- **Prefer `theme.json` / block patterns / template parts** over hardcoded markup or styling in PHP.
- **WordPress security**: escape/sanitize, nonce + `current_user_can()` before mutations, `$wpdb->prepare()`, a real REST `permission_callback`.
- `declare( strict_types = 1 );`, full types, `@package`/`@since`, `static::` not `self::`, PSR-4 (namespace === dir).

## Structure

`inc/` (PSR-4 `rtCamp\Theme\Elementary\`): `Main.php` (boot), `Core/` (`ThemeSetup`/`Menu`/`Assets`, implement `Registrable`), `Modules/`. `Main::CLASSES` lists classes directly (no `AbstractModule` grouping yet). Block config in `theme.json`, `templates/`, `parts/`, `patterns/`, `styles/`. Text domain `elementary-theme`. `tests/php/` mirrors `inc/`; `inc/Modules/` classes are scaffolding; delete unused.

To add a feature: write the test, implement `Registrable` (or extend an abstract), add its `::class` to `Main::CLASSES`.
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# CLAUDE.md

Read **[AGENTS.md](AGENTS.md)**: it is the source of truth for this project's conventions, shared across all AI tools (Claude Code, Copilot, Codex). The detailed rules it points to live in `.github/instructions/`.

Claude-specific notes:
- _(none currently; keep Claude overrides here if they ever diverge from AGENTS.md)_
1 change: 0 additions & 1 deletion bin/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,6 @@ const initTheme = ( themeInfo ) => {
const getAllFiles = ( dir ) => {
const dirOrFilesIgnore = [
'.git',
'.github',
'node_modules',
'vendor',
];
Expand Down
23 changes: 23 additions & 0 deletions bin/sync-ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#! /usr/bin/env node

/* eslint no-console: 0 */

/**
* Thin wrapper around the framework's sync tool.
*
* Runs vendor/rtcamp/wp-framework/bin/sync-ai-instructions.js if the framework
* is installed, otherwise skips cleanly (for example before `composer install`
* has fetched it). Forwards any arguments, so `npm run sync-ai -- --check` works.
*/

const fs = require( 'fs' );
const { spawnSync } = require( 'child_process' );

const script = 'vendor/rtcamp/wp-framework/bin/sync-ai-instructions.js';

if ( ! fs.existsSync( script ) ) {
console.log( 'sync-ai: rtcamp/wp-framework is not installed yet; run `composer install` first. Skipping.' );
process.exit( 0 );
}

process.exit( spawnSync( 'node', [ script, ...process.argv.slice( 2 ) ], { stdio: 'inherit' } ).status ?? 0 );
16 changes: 8 additions & 8 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"scripts": {
"build:dev": "wp-scripts start --no-watch --experimental-modules",
"build:prod": "wp-scripts build --experimental-modules",
"init": "./bin/init.js",
"init": "./bin/init.js && npm run sync-ai",
"sync-ai": "node bin/sync-ai.js",
"lint:all": "npm-run-all --parallel lint:*",
"lint:css": "wp-scripts lint-style ./src",
"lint:css:fix": "npm run lint:css -- --fix",
Expand Down