diff --git a/docs/skill-set-v1.md b/docs/skill-set-v1.md index cfd5617..3dad861 100644 --- a/docs/skill-set-v1.md +++ b/docs/skill-set-v1.md @@ -6,6 +6,7 @@ This repo currently includes: - `wp-project-triage` - `wp-block-development` - `wp-block-themes` +- `wp-docs` - `wp-plugin-development` - `wp-rest-api` - `wp-interactivity-api` diff --git a/eval/scenarios/wp-docs-review-phpdoc.json b/eval/scenarios/wp-docs-review-phpdoc.json new file mode 100644 index 0000000..20dc91a --- /dev/null +++ b/eval/scenarios/wp-docs-review-phpdoc.json @@ -0,0 +1,27 @@ +{ + "name": "Review PHPDoc in a WordPress plugin", + "skills": ["wordpress-router", "wp-project-triage", "wp-docs"], + "query": "Review the PHPDoc in this plugin and fix any violations of WordPress documentation standards", + "expected_behavior": [ + "Step 1: Run wordpress-router to classify repo kind", + "Step 2: Run wp-project-triage script to detect plugin structure", + "Step 3: Route to wp-docs based on documentation task", + "Step 4: Enter wp-docs review mode for existing documentation", + "Step 5: Scan PHP files for PHPDoc blocks", + "Step 6: Check summaries use third-person singular verb form (e.g. 'Retrieves' not 'Retrieve')", + "Step 7: Check @param tags follow 'type $name Description.' format", + "Step 8: Check @return tags include type and description (not bare @return)", + "Step 9: Check @since tags are present on all public and protected members", + "Step 10: Check hook documentation has PHPDoc blocks before do_action() and apply_filters() calls with @since and @param tags", + "Step 11: Report findings with specific file locations and suggested fixes" + ], + "success_criteria": [ + "Checks that summaries use third-person singular verb form", + "Validates @param format includes type, variable name, and description", + "Flags bare @return tags missing type or description", + "Verifies @since tags are present on public and protected members", + "Checks hook documentation is complete for do_action and apply_filters calls", + "Reports findings with file paths and line references", + "Follows WordPress PHP Documentation Standards" + ] +} diff --git a/eval/scenarios/wp-docs-write-guide.json b/eval/scenarios/wp-docs-write-guide.json new file mode 100644 index 0000000..20709b8 --- /dev/null +++ b/eval/scenarios/wp-docs-write-guide.json @@ -0,0 +1,27 @@ +{ + "name": "Write a getting started guide for a WordPress plugin", + "skills": ["wordpress-router", "wp-project-triage", "wp-docs"], + "query": "Write a getting started guide for this plugin following WordPress documentation standards", + "expected_behavior": [ + "Step 1: Run wordpress-router to classify repo kind", + "Step 2: Run wp-project-triage script to detect plugin structure", + "Step 3: Route to wp-docs based on documentation task", + "Step 4: Enter wp-docs write mode for new documentation", + "Step 5: Determine document type as user-facing guide", + "Step 6: Research plugin source code to understand features and usage", + "Step 7: Write guide with sentence case headings", + "Step 8: Use second person voice throughout (you/your)", + "Step 9: Include verified code examples from actual plugin functionality", + "Step 10: Ensure all links and references resolve to valid targets", + "Step 11: Self-review the guide against WordPress documentation standards" + ], + "success_criteria": [ + "Headings use sentence case (not Title Case)", + "Uses second person voice consistently (you/your)", + "Code examples are accurate and verified against plugin source", + "All links and cross-references resolve to valid targets", + "Guide follows a logical progression from installation to usage", + "Follows WordPress documentation standards for structure and formatting", + "Includes prerequisites and requirements section" + ] +} diff --git a/skills/wordpress-router/references/decision-tree.md b/skills/wordpress-router/references/decision-tree.md index 6d72cbb..d441953 100644 --- a/skills/wordpress-router/references/decision-tree.md +++ b/skills/wordpress-router/references/decision-tree.md @@ -37,6 +37,8 @@ Route by intent even if repo kind is broad (like `wp-site`): - Route → `wp-rest-api`. - **WP-CLI / wp-cli.yml / commands** - Route → `wp-wpcli-and-ops`. +- **Documentation / PHPDoc / write docs / review docs / documentation standards / inline documentation / @since** + - Route → `wp-docs`. - **Build tooling / @wordpress/scripts / webpack / Vite / npm scripts** - Route → `wp-build-tooling` (planned). - **Testing / PHPUnit / wp-env / Playwright** diff --git a/skills/wp-docs/SKILL.md b/skills/wp-docs/SKILL.md new file mode 100644 index 0000000..f82fb13 --- /dev/null +++ b/skills/wp-docs/SKILL.md @@ -0,0 +1,109 @@ +--- +name: wp-docs +description: "Use when writing or reviewing WordPress documentation: wordpress docs, WP docs standards, review docs, PHPDoc standards, documentation standards. Covers user-facing markdown docs and inline PHP documentation (PHPDoc). Two modes: write (create/update) and review (audit + optional --fix)." +compatibility: "Targets WordPress 6.9+ (PHP 7.2.24+). Filesystem-based agent with bash + node." +--- + +# WP Documentation Standards + +## When to use + +Use this skill when you need to: + +- write or update user-facing documentation (guides, README, migration guides, architecture docs) +- write or review inline PHP documentation (PHPDoc blocks) +- audit existing docs against WordPress documentation standards +- document hooks (actions and filters) following WordPress patterns +- create documentation for REST API endpoints or capabilities +- enforce sentence case headings, second person voice, and WordPress formatting conventions + +## Inputs required + +- Repo root + target file(s) or directory. +- Mode: `write` or `review`. Infer from context if not explicit ("check", "audit" = review; "create", "update" = write). +- Flags (optional): + - `--fix` — auto-fix FIX-severity violations (review mode only) + - `--phpdoc` — focus on PHP inline documentation only + - `--markdown` — focus on user-facing markdown docs only + +## Procedure + +### Write mode + +1. **Determine doc type.** Based on the target and context, decide: + - New file or updating existing? + - Diataxis type: tutorial, how-to, reference, or explanation? + - Audience: end user, plugin developer, or contributor? + +2. **Research source code.** Before writing: + - Read the actual source code for features being documented. + - Read existing docs for style and conventions. + - Check for related docs that need cross-linking. + +3. **Write following standards.** Apply all rules from the references: + - For markdown docs, follow `references/markdown-standards.md` (sentence case headings, second person, active voice, verified code examples). + - For PHPDoc, follow `references/phpdoc-standards.md` (third-person singular verbs, @param/@return/@since/@throws). + - For WordPress-specific patterns (hooks, REST endpoints, capabilities, migrations), follow `references/wordpress-patterns.md`. + +4. **Self-review.** Run the review checks (below) against your own output. Fix any violations before presenting to the user. + +### Review mode + +1. **Collect files.** Based on the target: + - File path: review that file. + - Directory: review all `.md` and/or `.php` files in it. + - `--phpdoc`: review `.php` files in `includes/` or `src/`. + - `--markdown`: review `.md` files in `docs/` + root `README.md`. + - No target: review everything. + +2. **Run checks.** For each file, check all applicable rules: + - **Markdown categories:** headings, language/tone, code blocks, links, formatting, structure, WordPress patterns. Full checklist in `references/markdown-standards.md`. + - **PHP categories:** summaries, @param, @return, @since, @throws, hook documentation. Full checklist in `references/phpdoc-standards.md`. + - **WordPress patterns:** hook docs, REST endpoint docs, capability docs, migration guides. Full checklist in `references/wordpress-patterns.md`. + +3. **Report findings.** Group by severity: + - **FIX** — violates a standard, must be corrected (e.g., missing @since, skipped heading level). + - **IMPROVE** — not a violation but could be better (e.g., passive voice, vague heading). + - **PASS** — checked and compliant. + +4. **Auto-fix (if --fix).** Apply fixes for FIX-severity findings only. Present IMPROVE findings for user review. Re-run checks after fixing to confirm. + +## Verification + +After writing or fixing documentation: + +- Re-run the review checks against all modified files. +- Confirm zero FIX-severity findings remain. +- Verify all code examples match actual source code. +- Verify all internal links resolve to existing files. + +Quick checklist: + +- [ ] Sentence case headings throughout +- [ ] No skipped heading levels +- [ ] All code examples verified against actual source +- [ ] Code examples follow WordPress coding standards +- [ ] All links resolve to existing files +- [ ] Second person ("you") in guides +- [ ] Active voice preferred +- [ ] No dismissed complexity ("simply", "just", "easy", "obvious") +- [ ] Hooks documented with type, @since, parameters, example +- [ ] PHPDoc uses third-person singular verbs +- [ ] @param tags have type + name + description +- [ ] @return tags are never bare (always include description) +- [ ] @since present on all public/protected members +- [ ] Proper nouns capitalized, technical terms consistent + +## Failure modes / debugging + +- **No docs/ directory:** handle gracefully; suggest creating one with appropriate structure. +- **Mixed file types in target:** detect file extensions and apply the correct standards (markdown vs PHP) per file. +- **Missing @since tags:** common issue; use `@since n.e.x.t` for unreleased plugin versions, or `@since Unknown` when the version truly cannot be determined (WordPress core convention). +- **Heading case disagreements:** follow sentence case strictly; only proper nouns and acronyms are capitalized. +- **Code examples out of date:** always verify against current source; flag stale examples as FIX severity. + +## Escalation + +- When WordPress documentation standards conflict with project-specific conventions (e.g., `AGENTS.md`, `CLAUDE.md` overrides), project conventions win. Note the deviation. +- When a project uses custom documentation tooling (Docusaurus, MkDocs), adapt the standards to the tooling's constraints but preserve WordPress voice and formatting rules. +- When unsure whether a PHPDoc issue is a documentation concern (wp-docs) or a type annotation concern (wp-phpstan), apply this rule: wp-docs covers summaries, descriptions, @since, and prose quality; wp-phpstan covers type correctness for static analysis. diff --git a/skills/wp-docs/references/markdown-standards.md b/skills/wp-docs/references/markdown-standards.md new file mode 100644 index 0000000..f182e24 --- /dev/null +++ b/skills/wp-docs/references/markdown-standards.md @@ -0,0 +1,164 @@ +# Markdown documentation standards + +Upstream reference: + +- https://make.wordpress.org/docs/handbook/documentation-team-handbook/handbooks-style-and-formatting-guide/ + +Use this file when writing or reviewing user-facing markdown documentation for WordPress plugins and themes. All rules below align with the WordPress Documentation Style Guide. + +--- + +## Documentation types (Diataxis) + +Before writing, determine which type of document you are creating. Each type has a different purpose and structure. + +| Type | Purpose | Voice | Example | +|------|---------|-------|---------| +| Tutorial | Learning-oriented — guide the reader through steps to build something | Second person, encouraging | "Getting started with your first block" | +| How-to | Task-oriented — solve a specific problem | Second person, direct | "How to register a custom post type" | +| Reference | Information-oriented — describe the system accurately | Third person, precise | "REST API endpoint reference" | +| Explanation | Understanding-oriented — clarify concepts and decisions | Second person or third person, conversational | "How the plugin lifecycle works" | + +Choose the type before writing. Do not mix types in a single document. + +--- + +## Language and tone + +| Rule | Standard | Example | +|------|----------|---------| +| Person | Second person ("you") for tutorials and how-to guides; third person for reference | "You can register a hook" not "We can register a hook" | +| Voice | Active voice preferred | "The plugin registers hooks" not "Hooks are registered by the plugin" | +| Tense | Present tense | "This function returns" not "This function will return" | +| Contractions | Allowed and encouraged | "don't", "you'll", "it's" | +| Tone | Conversational but professional | Like a knowledgeable colleague, not a textbook | +| Dismissed complexity | Never use "simply", "just", "easy", "obvious" | "Run the command" not "Simply run the command" | + +--- + +## Headings + +| Rule | Standard | Example | +|------|----------|---------| +| Case | **Sentence case** — capitalize only the first word and proper nouns | "System architecture" not "System Architecture" | +| Hierarchy | Never skip levels (H1 then H2 then H3, not H1 then H3) | Required | +| Content | Descriptive and specific | "Configure the database connection" not "Configuration" | +| Formatting | No links, code, or special formatting inside headings | Required | +| Punctuation | No periods at end of headings | Required | +| H1 | One per document (the title) | Required | + +### Sentence case rules + +Capitalize only the first word and proper nouns. Everything else is lowercase. + +**Proper nouns in WordPress context:** + +| Term | Capitalization | Notes | +|------|---------------|-------| +| WordPress | WordPress | Never "Wordpress" or "wordpress" | +| PHP | PHP | Always uppercase | +| JavaScript | JavaScript | Capital J, capital S | +| REST API | REST API | Both words uppercase | +| WP-CLI | WP-CLI | Hyphenated, all caps | +| GitHub | GitHub | Capital G, capital H | +| Gutenberg | Gutenberg | Proper noun | +| JSON | JSON | Acronym, all caps | + +**General capitalization rules:** + +| Category | Rule | Example | +|----------|------|---------| +| Acronyms | Stay uppercase | HTTP, DTO, API, URI, CLI, AJAX, CSRF | +| Technical names | Stay as-is (preserve original casing) | `WP_Query`, `register_post_type()`, `wp-env` | +| Plugin/theme names | Use the official capitalization | WooCommerce, Jetpack, Yoast SEO | +| WordPress features | Lowercase unless a proper noun | block editor, customizer, widgets | + +--- + +## Code examples + +| Rule | Standard | +|------|----------| +| Fenced blocks | Always use triple backticks with a language identifier (`php`, `bash`, `json`, `javascript`) | +| Placeholders | Use `example.com` for URLs, `yoursite.com` for user URLs, `your-plugin` for slugs | +| WordPress standards | Code examples must follow WordPress coding standards (tabs for indentation, spaces inside parentheses, Yoda conditions) | +| Comments | Add comments for non-obvious parts only | +| Completeness | Show enough context to be copy-pasteable — include `use` statements, function signatures, and surrounding code when needed | +| Verification | Every code example must be verified against actual source code | + +--- + +## Formatting + +| Element | Format | Example | +|---------|--------|---------| +| File paths | Inline code | `includes/Domain/Tools/McpTool.php` | +| Function/method names | Inline code | `McpTool::fromArray()` | +| Hook names | Inline code | `mcp_adapter_init` | +| UI elements | Bold | Click **Save** | +| First use of a term | Define it immediately | "a DTO (Data Transfer Object)" | +| Parameters | Inline code | The `$ability` parameter | +| Emphasis | Italic for terms, bold for important warnings | *schema layer*, **Breaking change** | +| Notes/warnings | Blockquote with bold label | `> **Note:** Additional context here.` | + +### Notes and warnings format + +Use blockquotes with a bold label for callouts: + +```markdown +> **Note:** This applies only to multisite installations. + +> **Warning:** This action cannot be undone. + +> **Tip:** You can use WP-CLI to speed up this process. +``` + +--- + +## Lists + +| Rule | Standard | +|------|----------| +| Sequential steps | Numbered lists | +| Non-sequential items | Bulleted lists | +| Parallel structure | All items must use the same grammatical form | +| Capitalization | Start each item with a capital letter | +| Punctuation | Period at end only if items are full sentences | + +--- + +## Links + +| Rule | Standard | +|------|----------| +| Link text | Descriptive — never "click here" or "read more" | +| Internal links | Use relative paths for links within the same repository | +| External links | Include full URL, prefer HTTPS | +| Verification | All links must resolve to existing files or live URLs | + +**Good:** `See the [authentication guide](../guides/authentication.md) for details.` + +**Bad:** `For details, [click here](../guides/authentication.md).` + +--- + +## Review checklist + +When reviewing markdown documentation, verify each of these: + +- [ ] Sentence case headings throughout +- [ ] No skipped heading levels +- [ ] One H1 per document +- [ ] All code blocks have a language identifier +- [ ] Code examples follow WordPress coding standards +- [ ] Code examples are verified against actual source +- [ ] All links resolve to existing files or live URLs +- [ ] Link text is descriptive +- [ ] Second person ("you") in tutorials and how-to guides +- [ ] Active voice preferred throughout +- [ ] No dismissed complexity ("simply", "just", "easy", "obvious") +- [ ] Proper nouns capitalized correctly +- [ ] Notes and warnings use blockquote format with bold label +- [ ] Lists use parallel structure +- [ ] File paths and function names in inline code +- [ ] UI elements in bold diff --git a/skills/wp-docs/references/phpdoc-standards.md b/skills/wp-docs/references/phpdoc-standards.md new file mode 100644 index 0000000..dfb789b --- /dev/null +++ b/skills/wp-docs/references/phpdoc-standards.md @@ -0,0 +1,429 @@ +# WordPress PHP documentation standards (PHPDoc) + +> **Upstream:** [WordPress PHP Documentation Standards](https://developer.wordpress.org/coding-standards/inline-documentation-standards/php/) +> +> This reference provides agent-first checklists and rules extracted from the official WordPress PHP Documentation Standards. Always defer to the upstream source for edge cases. + +--- + +## Function and method documentation + +Every public and protected function or method requires a PHPDoc block directly preceding it with no intervening code. + +### Summary line + +| Rule | Standard | +|------|----------| +| Verb form | Third-person singular ("Retrieves", "Registers", "Deletes") | +| Length | One sentence, maximum two lines | +| Punctuation | Ends with a period | +| Markup | No HTML or Markdown in summaries — write "image tag" not `` `` `` | +| Mental model | Prefix with "It" to verify: "It retrieves the post title." | + +```php +/** + * Retrieves the post title for the given post ID. + * + * @since 1.0.0 + * + * @param int $post_id The post ID. + * @return string The post title. + */ +function get_post_title( int $post_id ): string { + // ... +} +``` + +### Long description + +Separate the long description from the summary with a blank line. Use it for complex methods that need additional context. Markdown is allowed in descriptions (but never HTML outside code examples). + +```php +/** + * Registers a custom post type with the given arguments. + * + * This method validates the provided arguments against the WordPress + * post type schema before registration. Invalid arguments trigger a + * `_doing_it_wrong()` notice and fall back to defaults. + * + * @since 1.2.0 + * + * @param string $post_type Post type key. Must not exceed 20 characters. + * @param array $args Optional. Post type arguments. Default empty array. + * @return WP_Post_Type|WP_Error The registered post type object, or WP_Error on failure. + */ +function register_custom_post_type( string $post_type, array $args = array() ) { + // ... +} +``` + +--- + +## `@since` tag + +| Rule | Standard | +|------|----------| +| Required | On all public and protected members (functions, methods, classes, properties, constants, hooks) | +| Format | Three-digit version: `@since 1.0.0` | +| Unreleased | Use `@since n.e.x.t` for unreleased versions in plugin projects; WordPress core uses `@since Unknown` when the version cannot be determined | +| Multiple entries | Add a new `@since` when behavior changes significantly (new parameter, changed default, deprecated) | +| MU exception | `@since MU (3.0.0)` for features originating in WordPress MU | + +```php +/** + * Retrieves the formatted date string. + * + * @since 1.0.0 + * @since 1.3.0 Added the `$gmt` parameter. + * @since 2.0.0 The `$format` parameter now accepts 'relative'. + * + * @param string $format Date format string. + * @param bool $gmt Optional. Whether to use GMT. Default false. + * @return string The formatted date. + */ +function get_formatted_date( string $format, bool $gmt = false ): string { + // ... +} +``` + +--- + +## `@param` tag + +| Rule | Standard | +|------|----------| +| Format | `@param type $name Description.` | +| Required params | Type, variable name, description ending with a period | +| Optional params | Description starts with "Optional." and ends with "Default X." | +| Accepted values | List with "Accepts 'value1', 'value2'." before the default | +| Alignment | Align type, variable name, and description columns when multiple params exist | + +### Required parameter + +```php +/** + * Deletes a user by ID. + * + * @since 1.0.0 + * + * @param int $user_id The ID of the user to delete. + * @return bool True on success, false on failure. + */ +function delete_user( int $user_id ): bool { + // ... +} +``` + +### Optional parameter + +```php +/** + * Retrieves posts matching the given criteria. + * + * @since 1.0.0 + * + * @param string $post_type Optional. Post type slug. Default 'post'. + * @param string $status Optional. Post status. Accepts 'publish', 'draft', + * 'pending', 'trash'. Default 'publish'. + * @param int $limit Optional. Maximum number of posts to return. + * Default 10. + * @return WP_Post[] Array of post objects. + */ +function get_matching_posts( + string $post_type = 'post', + string $status = 'publish', + int $limit = 10 +): array { + // ... +} +``` + +### Array shape parameters + +Use `@param array $args { ... @type ... }` syntax to document the expected keys of associative arrays. Each key uses `@type` in the originating function. Note: this is the WordPress convention — do not confuse with the PHPStan `@param array{key: type}` syntax used for static analysis. When consuming the same array in a called function, use `@see` to reference the original documentation rather than duplicating it. + +```php +/** + * Registers a custom sidebar with the given arguments. + * + * @since 1.0.0 + * + * @param array $args { + * Arguments for registering the sidebar. + * + * @type string $id Required. Sidebar ID. + * @type string $name Required. Sidebar display name. + * @type string $description Optional. Sidebar description. Default empty string. + * @type string $class Optional. CSS class. Default empty string. + * @type string $before_widget Optional. HTML before each widget. Default + * `
  • `. + * @type string $after_widget Optional. HTML after each widget. Default `
  • `. + * @type string $before_title Optional. HTML before the title. Default `

    `. + * @type string $after_title Optional. HTML after the title. Default `

    `. + * } + * @return string The sidebar ID. + */ +function register_custom_sidebar( array $args ): string { + // ... +} +``` + +--- + +## `@return` tag + +| Rule | Standard | +|------|----------| +| Format | `@return type Description.` | +| Never bare | Always include a description — never just `@return string` | +| Multiple types | Separate with pipe: `@return string|false` | +| Void | Omit `@return` entirely when a function returns nothing — do not use `@return void` | +| Description | Ends with a period | + +```php +/** + * Finds a user by email address. + * + * @since 1.0.0 + * + * @param string $email The email address to search for. + * @return WP_User|false The user object if found, false otherwise. + */ +function find_user_by_email( string $email ) { + // ... +} +``` + +--- + +## `@throws` tag + +| Rule | Standard | +|------|----------| +| Required | Document every exception a public method can throw | +| Format | `@throws ExceptionClass Description of when this is thrown.` | +| Placement | After `@return`; consistency within a project matters most | + +```php +/** + * Processes a payment for the given order. + * + * @since 2.0.0 + * + * @param int $order_id The order ID. + * @param float $amount The payment amount. + * @return string The transaction ID. + * + * @throws InvalidArgumentException When the amount is negative or zero. + * @throws PaymentGatewayException When the payment gateway is unreachable. + */ +function process_payment( int $order_id, float $amount ): string { + if ( $amount <= 0 ) { + throw new InvalidArgumentException( 'Payment amount must be positive.' ); + } + + // ... +} +``` + +--- + +## Class, interface, and trait documentation + +Every class, interface, and trait requires a PHPDoc block with a summary, optional long description, `@since`, and `@package`. + +| Tag | Required | Notes | +|-----|----------|-------| +| Summary | Yes | Third-person singular, one sentence, ends with period | +| Long description | No | Separated by blank line from summary | +| `@since` | Yes | Version the class was introduced | +| `@package` | Yes | Namespace or logical package grouping | + +```php +/** + * Manages webhook subscriptions and delivery scheduling. + * + * Handles registration of webhook endpoints, validates payload + * signatures, and schedules delivery via Action Scheduler. Failed + * deliveries are retried with exponential backoff. + * + * @since 1.0.0 + * @package MyPlugin\Webhooks + */ +class Webhook_Manager { + // ... +} +``` + +```php +/** + * Defines the contract for cache storage backends. + * + * @since 1.2.0 + * @package MyPlugin\Contracts + */ +interface Cache_Interface { + // ... +} +``` + +```php +/** + * Provides shared logging capabilities for service classes. + * + * @since 1.0.0 + * @package MyPlugin\Concerns + */ +trait Has_Logging { + // ... +} +``` + +--- + +## Property and constant documentation + +### Class properties + +```php +/** + * The maximum number of retry attempts. + * + * @since 1.0.0 + * @var int + */ +private int $max_retries = 3; +``` + +### Class constants + +```php +/** + * The cache key prefix for all transients. + * + * @since 1.0.0 + * @var string + */ +const CACHE_PREFIX = 'myplugin_'; +``` + +--- + +## Hook documentation (inline) + +PHPDoc blocks for `apply_filters()` and `do_action()` are placed immediately before the call. They document what the hook does, when it fires, and all parameters passed to callbacks. + +### Filter documentation (`apply_filters`) + +| Rule | Standard | +|------|----------| +| Placement | Directly before the `apply_filters()` call | +| Summary | Describes what is being filtered | +| `@since` | Required | +| `@param` | One for each argument passed; first `@param` is the value being filtered | + +```php +/** + * Filters the list of allowed file extensions for upload. + * + * Allows plugins to add or remove file extensions from the + * whitelist used during media upload validation. + * + * @since 1.0.0 + * @since 1.4.0 Added the `$user_id` parameter. + * + * @param string[] $extensions Array of allowed file extensions. + * @param int $user_id The ID of the uploading user. + */ +$extensions = apply_filters( 'myplugin_allowed_extensions', $extensions, $user_id ); +``` + +### Action documentation (`do_action`) + +| Rule | Standard | +|------|----------| +| Placement | Directly before the `do_action()` call | +| Summary | Describes when the action fires | +| `@since` | Required | +| `@param` | One for each argument passed to callbacks | + +```php +/** + * Fires after a webhook has been successfully delivered. + * + * @since 1.0.0 + * + * @param int $webhook_id The webhook subscription ID. + * @param string $endpoint The delivery endpoint URL. + * @param array $payload The delivered payload data. + * @param int $response_code The HTTP response code from the endpoint. + */ +do_action( 'myplugin_webhook_delivered', $webhook_id, $endpoint, $payload, $response_code ); +``` + +--- + +## File-level documentation + +File-level PHPDoc blocks should be included whenever possible. They are especially important for files with procedural functions, main plugin files, and config files. For files containing a single class, the class-level docblock can serve as file documentation, but a separate file-level block is still recommended. Place the block at the top of the file immediately after the opening ` **Upstream sources:** +> - [WordPress Plugin Hooks](https://developer.wordpress.org/plugins/hooks/) +> - [WordPress REST API Reference](https://developer.wordpress.org/rest-api/) +> +> This reference provides copy-pasteable templates for documenting WordPress-specific constructs: hooks, REST API endpoints, capabilities, and migration guides. Always defer to the upstream sources for edge cases. + +--- + +## Hook documentation pattern + +Hooks (filters and actions) are a core WordPress extension mechanism. Every hook your plugin exposes is a public API contract and must be documented in two places: + +1. **User-facing markdown** (in `docs/` or a developer guide) for discoverability +2. **Inline PHPDoc** (in the source file) for developers reading the code + +### Markdown format + +Use this template in user-facing documentation to describe a hook. Wrap the hook name in backticks as an H3 heading. Include type, version, description, parameters, default value (for filters), and a working code example. + +````markdown +### `your_plugin_hook_name` + +**Type:** Filter +**Since:** 1.0.0 + +Description of what the hook does and when it fires. Explain the use case +in plain language so a developer knows whether they need this hook. + +**Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `$value` | `string` | The value being filtered. | +| `$context` | `array` | Additional context about the current request. | + +**Default:** `'default_value'` + +**Example:** + +```php +add_filter( 'your_plugin_hook_name', function ( string $value, array $context ): string { + if ( 'special_case' === $context['type'] ) { + return 'custom_value'; + } + + return $value; +}, 10, 2 ); +``` +```` + +For actions, omit the **Default** line since actions do not return values: + +````markdown +### `your_plugin_after_process` + +**Type:** Action +**Since:** 1.2.0 + +Fires after the processing step completes. Use this hook to run +side effects such as logging, cache invalidation, or notifications. + +**Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `$item_id` | `int` | The ID of the processed item. | +| `$result` | `array` | The processing result data. | + +**Example:** + +```php +add_action( 'your_plugin_after_process', function ( int $item_id, array $result ): void { + error_log( sprintf( 'Processed item %d with status %s', $item_id, $result['status'] ) ); +}, 10, 2 ); +``` +```` + +### Inline PHPDoc format + +Use this template in source code directly above the `apply_filters()` or `do_action()` call. The PHPDoc block documents what the hook does, when it fires, the version it was introduced, and all parameters passed to callbacks. + +**Filter (inline PHPDoc):** + +```php +/** + * Filters the output format before rendering. + * + * Allows plugins to modify the output format string used when + * rendering items in the admin list table. + * + * @since 1.0.0 + * @since 1.3.0 Added the `$context` parameter. + * + * @param string $format The output format string. + * @param int $item_id The current item ID. + * @param array $context Additional rendering context. + */ +$format = apply_filters( 'your_plugin_output_format', $format, $item_id, $context ); +``` + +**Action (inline PHPDoc):** + +```php +/** + * Fires after a subscription has been activated. + * + * Use this action to trigger welcome emails, provision resources, + * or update external systems when a subscription becomes active. + * + * @since 1.0.0 + * + * @param int $subscription_id The subscription ID. + * @param string $plan_slug The plan slug (e.g., 'pro', 'enterprise'). + * @param array $meta Subscription metadata. + */ +do_action( 'your_plugin_subscription_activated', $subscription_id, $plan_slug, $meta ); +``` + +### Key rules for hook documentation + +| Rule | Standard | +|------|----------| +| Placement | PHPDoc directly before the `apply_filters()` / `do_action()` call | +| First `@param` (filters) | Always the value being filtered | +| `@since` | Required; add new entries when parameters change | +| Summary (filters) | "Filters the..." | +| Summary (actions) | "Fires after/before/when..." | +| Markdown heading | Hook name in backticks as H3 | + +--- + +## REST API endpoint documentation pattern + +Use this template to document each REST API endpoint your plugin registers. Include the route, HTTP method, authentication requirements, required capability, request parameters, a successful response example, and error responses. + +````markdown +### Create item + +**Route:** `POST /wp-json/your-plugin/v1/items` +**Authentication:** Required (application password, cookie with nonce, or OAuth) +**Capability:** `edit_posts` + +Creates a new item in the system. The item is created in `draft` status +until you explicitly publish it via the update endpoint. + +**Request body:** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | `string` | Yes | The item name. Maximum 200 characters. | +| `type` | `string` | Yes | The item type. Accepts `'standard'`, `'premium'`. | +| `metadata` | `object` | No | Key-value pairs of additional metadata. | + +**Response:** `201 Created` + +```json +{ + "id": 42, + "name": "Example item", + "type": "standard", + "status": "draft", + "metadata": {}, + "created_at": "2026-03-17T12:00:00Z", + "_links": { + "self": [ + { + "href": "https://example.com/wp-json/your-plugin/v1/items/42" + } + ] + } +} +``` + +**Error responses:** + +| Code | HTTP status | Condition | +|------|-------------|-----------| +| `rest_missing_param` | `400` | A required parameter is missing. | +| `rest_invalid_param` | `400` | A parameter value is invalid (see `data.params` for details). | +| `rest_forbidden` | `403` | The authenticated user lacks the required capability. | +| `rest_not_logged_in` | `401` | No authentication credentials provided. | +```` + +### Key rules for REST API documentation + +| Rule | Standard | +|------|----------| +| Route format | Full path including `/wp-json/` prefix and namespace | +| HTTP method | Included in the route line | +| Authentication | State whether required and which methods are accepted | +| Capability | Always document the required capability | +| Parameters | Type, required/optional, description with constraints | +| Response | Include the HTTP status code and a realistic JSON example | +| Error responses | Cover validation errors, auth failures, and not-found cases | + +--- + +## Capability documentation pattern + +Every privileged operation in a WordPress plugin must document its capability requirement. This tells developers what permission is needed, how to customize it, and what happens when the check fails. + +### Inline format + +Use this one-line format when documenting a capability within a broader section (such as an endpoint or admin page description): + +````markdown +**Required capability:** `edit_posts` (filterable via `your_plugin_required_capability`) +```` + +### Expanded format + +Use the expanded format when a capability needs more context, such as in a permissions reference page or when the capability logic is complex: + +````markdown +### Item management permissions + +**Required capability:** `manage_options` +**Filter:** `your_plugin_manage_items_capability` +**Failure behavior:** Returns `WP_Error` with code `rest_forbidden` and HTTP 403 status. + +By default, only administrators can manage items. You can customize this +with the `your_plugin_manage_items_capability` filter: + +```php +add_filter( 'your_plugin_manage_items_capability', function ( string $capability ): string { + return 'edit_others_posts'; +} ); +``` + +> **Note:** Changing the required capability affects all item management +> operations including create, update, and delete. +```` + +### Key rules for capability documentation + +| Rule | Standard | +|------|----------| +| Always document | Which capability is required | +| Always document | Which filter customizes the capability (if filterable) | +| Always document | What happens when the check fails (error code, HTTP status, user-facing behavior) | +| Code example | Show how to change the capability via the filter | + +--- + +## Migration guide pattern + +Use this template when releasing a version that includes breaking changes, significant new features, or internal API changes. Lead with reassurance about what does not break, then cover new features, and finally detail changes that affect advanced users. + +````markdown +# Migration guide: version X.Y.Z + +Brief summary of what changed and why. One to two sentences that give +readers the high-level picture before diving into details. + +## For most users: seamless upgrade + +List the public APIs and behaviors that remain unchanged. Lead with +what does NOT break to reassure the majority of users who depend on +stable interfaces. + +- The `your_plugin_hook_name` filter continues to work as before. +- All REST API endpoints maintain backward compatibility. +- Configuration stored in `wp_options` is automatically migrated. + +## New features + +Describe new capabilities introduced in this version. Focus on what +users can now do that they could not do before. + +### Feature name + +Description of the feature and how to use it. + +```php +// Example showing the new feature in action. +$result = your_plugin_new_function( 'example' ); +``` + +## Advanced: internal API changes + +> This section only applies if you have extended the plugin's internal +> classes, overridden protected methods, or directly called internal APIs +> marked with `@internal` or `@access private`. + +### Change title + +**What changed:** One-sentence description of the change. + +**Who is affected:** Developers who [specific condition]. + +**Before (vX.Y-1.Z):** + +```php +// Old pattern that no longer works. +$old_service = new Internal_Service(); +$old_service->process( $data ); +``` + +**After (vX.Y.Z):** + +```php +// New pattern replacing the old approach. +$new_service = your_plugin_get_service( 'processor' ); +$new_service->handle( $data ); +``` + +**Migration steps:** + +1. Replace `new Internal_Service()` with `your_plugin_get_service( 'processor' )`. +2. Replace `->process()` calls with `->handle()`. +3. Update any type hints from `Internal_Service` to `Processor_Interface`. +```` + +### Key rules for migration guides + +| Rule | Standard | +|------|----------| +| Title | Includes the version number | +| Lead with stability | Show what does NOT break before showing what changed | +| Before/after code | Always show both the old and new patterns side by side | +| Migration steps | Numbered list of concrete actions the developer must take | +| Scope qualifier | The "Advanced" section starts with a note about who is affected | +| Tone | Reassuring, not alarming; factual, not apologetic | + +--- + +## Quick-reference checklist + +Use this checklist when writing or reviewing WordPress-specific documentation: + +- [ ] Every public hook has both markdown documentation and inline PHPDoc +- [ ] Hook markdown includes type, since, parameters table, and code example +- [ ] Hook PHPDoc is placed directly before `apply_filters()` / `do_action()` +- [ ] Filter PHPDoc lists the filtered value as the first `@param` +- [ ] REST endpoints document route, method, auth, capability, request, response, and errors +- [ ] Every privileged operation documents its required capability +- [ ] Capability documentation includes the customization filter and failure behavior +- [ ] Migration guides lead with what does NOT break +- [ ] Migration guides include before/after code blocks for every breaking change +- [ ] Migration guides include numbered migration steps