wordpress-agent-kit 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/wp-bootstrap/SKILL.md +314 -0
- package/.agents/skills/wp-bootstrap/references/composer-setup.md +275 -0
- package/.agents/skills/wp-bootstrap/references/monorepo-patterns.md +184 -0
- package/.agents/skills/wp-bootstrap/scripts/bootstrap.sh +151 -0
- package/.agents/skills/wp-bootstrap/scripts/detect-structure.mjs +466 -0
- package/.agents/skills/wp-bootstrap/scripts/package-wp.sh +173 -0
- package/.agents/skills/wp-bootstrap/scripts/playground-start.sh +148 -0
- package/.agents/skills/wp-bootstrap/scripts/playground-verify.sh +165 -0
- package/.agents/skills/wp-bootstrap/scripts/setup-github.sh +417 -0
- package/.agents/skills/wp-wpengine/SKILL.md +76 -12
- package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +16 -9
- package/README.md +1 -1
- package/dist/cli.js +2 -0
- package/dist/commands/bootstrap.js +105 -0
- package/dist/lib/api.js +1 -0
- package/dist/lib/bootstrap.js +352 -0
- package/extensions/wp-agent-kit/index.ts +143 -3
- package/package.json +1 -1
- package/skills-custom/wp-bootstrap/SKILL.md +314 -0
- package/skills-custom/wp-bootstrap/references/composer-setup.md +275 -0
- package/skills-custom/wp-bootstrap/references/monorepo-patterns.md +184 -0
- package/skills-custom/wp-bootstrap/scripts/bootstrap.sh +151 -0
- package/skills-custom/wp-bootstrap/scripts/detect-structure.mjs +466 -0
- package/skills-custom/wp-bootstrap/scripts/package-wp.sh +173 -0
- package/skills-custom/wp-bootstrap/scripts/playground-start.sh +148 -0
- package/skills-custom/wp-bootstrap/scripts/playground-verify.sh +165 -0
- package/skills-custom/wp-bootstrap/scripts/setup-github.sh +417 -0
- package/skills-custom/wp-wpengine/SKILL.md +76 -12
- package/skills-custom/wp-wpengine/references/github-actions-deploy.md +16 -9
- package/.github/skills/blueprint/SKILL.md +0 -418
- package/.github/skills/wordpress-router/SKILL.md +0 -52
- package/.github/skills/wordpress-router/references/decision-tree.md +0 -55
- package/.github/skills/wp-abilities-api/SKILL.md +0 -108
- package/.github/skills/wp-abilities-api/references/delegate-helper-pattern.md +0 -241
- package/.github/skills/wp-abilities-api/references/domain-vs-projection.md +0 -113
- package/.github/skills/wp-abilities-api/references/error-code-vocabulary.md +0 -123
- package/.github/skills/wp-abilities-api/references/grouping-heuristic.md +0 -89
- package/.github/skills/wp-abilities-api/references/input-schema-gotchas.md +0 -265
- package/.github/skills/wp-abilities-api/references/php-registration.md +0 -94
- package/.github/skills/wp-abilities-api/references/plugin-family-patterns.md +0 -233
- package/.github/skills/wp-abilities-api/references/rest-api.md +0 -13
- package/.github/skills/wp-abilities-api/references/shared-core-service.md +0 -184
- package/.github/skills/wp-abilities-audit/SKILL.md +0 -199
- package/.github/skills/wp-abilities-audit/references/audit-schema.md +0 -300
- package/.github/skills/wp-abilities-audit/references/capability-gate-tracing.md +0 -197
- package/.github/skills/wp-abilities-audit/references/controller-enumeration.md +0 -116
- package/.github/skills/wp-abilities-verify/SKILL.md +0 -215
- package/.github/skills/wp-abilities-verify/references/annotation-correctness.md +0 -154
- package/.github/skills/wp-abilities-verify/references/audit-schema-validation.md +0 -131
- package/.github/skills/wp-abilities-verify/references/permission-roundtrip.md +0 -190
- package/.github/skills/wp-abilities-verify/references/runtime-harness.md +0 -462
- package/.github/skills/wp-abilities-verify/references/schema-lints.md +0 -118
- package/.github/skills/wp-abilities-verify/references/static-enumeration.md +0 -126
- package/.github/skills/wp-block-development/SKILL.md +0 -175
- package/.github/skills/wp-block-development/references/attributes-and-serialization.md +0 -22
- package/.github/skills/wp-block-development/references/block-json.md +0 -49
- package/.github/skills/wp-block-development/references/creating-new-blocks.md +0 -46
- package/.github/skills/wp-block-development/references/debugging.md +0 -36
- package/.github/skills/wp-block-development/references/deprecations.md +0 -24
- package/.github/skills/wp-block-development/references/dynamic-rendering.md +0 -23
- package/.github/skills/wp-block-development/references/inner-blocks.md +0 -25
- package/.github/skills/wp-block-development/references/registration.md +0 -30
- package/.github/skills/wp-block-development/references/supports-and-wrappers.md +0 -18
- package/.github/skills/wp-block-development/references/tooling-and-testing.md +0 -21
- package/.github/skills/wp-block-development/scripts/list_blocks.mjs +0 -121
- package/.github/skills/wp-block-themes/SKILL.md +0 -117
- package/.github/skills/wp-block-themes/references/creating-new-block-theme.md +0 -37
- package/.github/skills/wp-block-themes/references/debugging.md +0 -24
- package/.github/skills/wp-block-themes/references/patterns.md +0 -18
- package/.github/skills/wp-block-themes/references/style-variations.md +0 -14
- package/.github/skills/wp-block-themes/references/templates-and-parts.md +0 -16
- package/.github/skills/wp-block-themes/references/theme-json.md +0 -59
- package/.github/skills/wp-block-themes/scripts/detect_block_themes.mjs +0 -117
- package/.github/skills/wp-interactivity-api/SKILL.md +0 -180
- package/.github/skills/wp-interactivity-api/references/debugging.md +0 -29
- package/.github/skills/wp-interactivity-api/references/directives-quickref.md +0 -30
- package/.github/skills/wp-interactivity-api/references/server-side-rendering.md +0 -310
- package/.github/skills/wp-performance/SKILL.md +0 -147
- package/.github/skills/wp-performance/references/autoload-options.md +0 -24
- package/.github/skills/wp-performance/references/cron.md +0 -20
- package/.github/skills/wp-performance/references/database.md +0 -20
- package/.github/skills/wp-performance/references/http-api.md +0 -15
- package/.github/skills/wp-performance/references/measurement.md +0 -21
- package/.github/skills/wp-performance/references/object-cache.md +0 -24
- package/.github/skills/wp-performance/references/query-monitor-headless.md +0 -38
- package/.github/skills/wp-performance/references/server-timing.md +0 -22
- package/.github/skills/wp-performance/references/wp-cli-doctor.md +0 -24
- package/.github/skills/wp-performance/references/wp-cli-profile.md +0 -32
- package/.github/skills/wp-performance/scripts/perf_inspect.mjs +0 -128
- package/.github/skills/wp-phpstan/SKILL.md +0 -98
- package/.github/skills/wp-phpstan/references/configuration.md +0 -52
- package/.github/skills/wp-phpstan/references/third-party-classes.md +0 -76
- package/.github/skills/wp-phpstan/references/wordpress-annotations.md +0 -124
- package/.github/skills/wp-phpstan/scripts/phpstan_inspect.mjs +0 -263
- package/.github/skills/wp-playground/SKILL.md +0 -233
- package/.github/skills/wp-playground/references/blueprints.md +0 -36
- package/.github/skills/wp-playground/references/cli-commands.md +0 -39
- package/.github/skills/wp-playground/references/debugging.md +0 -16
- package/.github/skills/wp-playground/references/e2e-playwright.md +0 -115
- package/.github/skills/wp-plugin-development/SKILL.md +0 -113
- package/.github/skills/wp-plugin-development/references/data-and-cron.md +0 -19
- package/.github/skills/wp-plugin-development/references/debugging.md +0 -19
- package/.github/skills/wp-plugin-development/references/lifecycle.md +0 -33
- package/.github/skills/wp-plugin-development/references/security.md +0 -29
- package/.github/skills/wp-plugin-development/references/settings-api.md +0 -22
- package/.github/skills/wp-plugin-development/references/structure.md +0 -16
- package/.github/skills/wp-plugin-development/scripts/detect_plugins.mjs +0 -122
- package/.github/skills/wp-plugin-directory-guidelines/SKILL.md +0 -133
- package/.github/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +0 -217
- package/.github/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +0 -592
- package/.github/skills/wp-plugin-directory-guidelines/references/naming-rules.md +0 -121
- package/.github/skills/wp-project-triage/SKILL.md +0 -39
- package/.github/skills/wp-project-triage/references/triage.schema.json +0 -143
- package/.github/skills/wp-project-triage/scripts/detect_wp_project.mjs +0 -610
- package/.github/skills/wp-rest-api/SKILL.md +0 -115
- package/.github/skills/wp-rest-api/references/authentication.md +0 -18
- package/.github/skills/wp-rest-api/references/custom-content-types.md +0 -20
- package/.github/skills/wp-rest-api/references/discovery-and-params.md +0 -20
- package/.github/skills/wp-rest-api/references/responses-and-fields.md +0 -30
- package/.github/skills/wp-rest-api/references/routes-and-endpoints.md +0 -36
- package/.github/skills/wp-rest-api/references/schema.md +0 -22
- package/.github/skills/wp-wpcli-and-ops/SKILL.md +0 -124
- package/.github/skills/wp-wpcli-and-ops/references/automation.md +0 -30
- package/.github/skills/wp-wpcli-and-ops/references/cron-and-cache.md +0 -23
- package/.github/skills/wp-wpcli-and-ops/references/debugging.md +0 -17
- package/.github/skills/wp-wpcli-and-ops/references/multisite.md +0 -22
- package/.github/skills/wp-wpcli-and-ops/references/packages-and-updates.md +0 -22
- package/.github/skills/wp-wpcli-and-ops/references/safety.md +0 -30
- package/.github/skills/wp-wpcli-and-ops/references/search-replace.md +0 -40
- package/.github/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +0 -90
- package/.github/skills/wp-wpengine/SKILL.md +0 -127
- package/.github/skills/wpds/SKILL.md +0 -59
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
# Audit Document Schema
|
|
2
|
-
|
|
3
|
-
The canonical schema for an abilities audit doc. Every audit produced by
|
|
4
|
-
`wp-abilities-audit` must conform to this schema so downstream tooling
|
|
5
|
-
(humans reviewing, agents implementing, or validators like
|
|
6
|
-
`wp-abilities-verify`) can consume it without parsing surprises.
|
|
7
|
-
|
|
8
|
-
A copy-pasteable minimal example with both a read ability and a write
|
|
9
|
-
ability lives under "Minimal valid example" below.
|
|
10
|
-
|
|
11
|
-
## File layout
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
<output-dir>/<YYYY-MM-DD>-abilities-audit-<plugin-slug>.md
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
`<output-dir>` is explicit — collected from the user, not inferred. Typical
|
|
18
|
-
values are the user's vault `plans/` directory or a dedicated audit repo.
|
|
19
|
-
Writing into the plugin worktree is discouraged (pollutes git history).
|
|
20
|
-
|
|
21
|
-
The body has two parts:
|
|
22
|
-
|
|
23
|
-
1. A fenced ` ```yaml ` block holding structured fields (top-level metadata
|
|
24
|
-
+ `proposed_abilities`, `excluded_from_mvp`, `surfaced_gaps`).
|
|
25
|
-
2. Prose sections below: "Controller Inventory" table + "Notes and Surprises".
|
|
26
|
-
|
|
27
|
-
A `Last updated: YYYY-MM-DD HH:MM` header sits above everything.
|
|
28
|
-
|
|
29
|
-
## Top-level fields (all required)
|
|
30
|
-
|
|
31
|
-
| Field | Type | Description |
|
|
32
|
-
|---|---|---|
|
|
33
|
-
| `plugin` | string | Plugin slug (e.g. `my-plugin`, `tasks-plugin`, `notifications`). |
|
|
34
|
-
| `repo` | string | `Owner/Repository`. |
|
|
35
|
-
| `branch_audited` | string | Git branch the audit was run against. |
|
|
36
|
-
| `audited_at` | string | ISO date (YYYY-MM-DD). |
|
|
37
|
-
| `auditor` | string | Human auditor name + team or context (e.g. `Your Name (Your Team)`). |
|
|
38
|
-
| `baseline_abilities` | integer | Count of abilities already registered by the plugin at audit time. Usually 0. |
|
|
39
|
-
| `capability_gate` | string OR object | The capability gate the base controller resolves to. Accept either a single string (single-cap plugins) OR a `{read, write}` object (post-type-backed or otherwise compound gates). See `capability-gate-tracing.md` for the mechanisms. |
|
|
40
|
-
| `plugin_family` | string (optional) | Free-form classification when useful to downstream readers (e.g. `core-post-type`, `forms-engine`, or a project-specific family name). Optional and user-supplied — no canonical enum. Downstream consumers treat unknown values as opaque rather than erroring. |
|
|
41
|
-
|
|
42
|
-
### `capability_gate` representations
|
|
43
|
-
|
|
44
|
-
Two legal shapes, both consumed by downstream tooling:
|
|
45
|
-
|
|
46
|
-
```yaml
|
|
47
|
-
# Single-cap plugin (one capability across every controller)
|
|
48
|
-
capability_gate: manage_options # confirmed at includes/admin/class-my-plugin-rest-controller.php line 64
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
```yaml
|
|
52
|
-
# Compound read/write (post-type-backed plugins typically need this shape;
|
|
53
|
-
# read and write resolve to different capabilities)
|
|
54
|
-
capability_gate:
|
|
55
|
-
read: read_private_pages
|
|
56
|
-
write: edit_others_pages
|
|
57
|
-
confirmed: true
|
|
58
|
-
verified_at: "custom_post_type capability_type='page' → core post-type cap map (wp-includes/post.php map_meta_cap)"
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Plugin-specific capabilities (e.g. WooCommerce's `manage_woocommerce`,
|
|
62
|
-
`edit_shop_orders`) are equally valid — substitute your plugin's caps. The
|
|
63
|
-
shape is the contract; the literal cap names are project-specific.
|
|
64
|
-
|
|
65
|
-
A legacy compound-string form exists in the wild (`"<read_cap> / <write_cap>"`)
|
|
66
|
-
and is accepted for backwards compatibility, but the structured form above is
|
|
67
|
-
the preferred representation for new audits.
|
|
68
|
-
|
|
69
|
-
## `proposed_abilities` — array
|
|
70
|
-
|
|
71
|
-
Each entry:
|
|
72
|
-
|
|
73
|
-
| Field | Type | Description |
|
|
74
|
-
|---|---|---|
|
|
75
|
-
| `name` | string | Kebab-case `<plugin-slug>/<ability>`. |
|
|
76
|
-
| `intent` | string | One sentence, user-question framed. |
|
|
77
|
-
| `backing` | object or `null` | See below. `null` marks an ability with no backing endpoint (a known gap). |
|
|
78
|
-
| `permission` | object or `null` | See below. `null` when `backing` is null. |
|
|
79
|
-
| `return_type` | string | Short description (e.g. `WP_REST_Response (wrapping array)`). Hint-only; not machine-parsed. |
|
|
80
|
-
| `effort` | enum | `S`, `M`, or `L`. |
|
|
81
|
-
| `annotations` | object | `{ readonly: bool, destructive: bool, idempotent: bool }`. All three required. |
|
|
82
|
-
| `notes` | array of strings | Implementer-facing detail (filter params, edge cases, alternative backings). |
|
|
83
|
-
| `risks` | array of strings | Anything the implementer must handle (missing idempotency key, two-phase behavior, `permission_callback => '__return_true'` at the REST layer that must not copy into the ability, etc.). |
|
|
84
|
-
| `use_case_fit` | string | One sentence naming the human or agent workflow this ability serves. The use-case-contract check (see `wp-abilities-api/references/domain-vs-projection.md`): if no human would intentionally do this through a supported UI or workflow, the entry probably belongs in `excluded_from_mvp` instead. |
|
|
85
|
-
| `side_effects` | array of strings | Side effects the backing path emits on every call: telemetry hooks, audit-log rows, notifications, cache writes. One short line per effect. Empty array (`[]`) when the backing is a pure data-fetch — that is *itself* a load-bearing fact: it is what unlocks the conditional delegation shortcut in `wp-abilities-api/references/shared-core-service.md`. A non-empty array tells the implementer (and downstream verify-mode tooling) that this ability needs the shared-service shape, not the delegate-through-REST shortcut. |
|
|
86
|
-
| `seed_data_needs` | string OR `null` | One line describing what representative data must exist in the test environment for the ability to execute through the public boundary and return something meaningful (e.g. `"at least one entity in the plugin's primary table"`, `"no seed required"`). `null` when the auditor has not yet identified the seed shape; downstream verify-mode tooling treats `null` as "ask the implementer" rather than guessing. |
|
|
87
|
-
| `reference_ability` | bool (optional) | If `true`, marks this ability as the reference implementation — the first one an implementer should land (smallest, safest, highest-leverage read). Exactly zero or one ability per audit may set this. |
|
|
88
|
-
|
|
89
|
-
### `backing: null` semantics
|
|
90
|
-
|
|
91
|
-
An ability with `backing: null` is a known gap (the auditor identified a
|
|
92
|
-
valuable ability that has no backing endpoint yet). The schema permits this
|
|
93
|
-
as a warning, not an error:
|
|
94
|
-
|
|
95
|
-
- The ability MUST also appear in `surfaced_gaps` with a one-line rationale.
|
|
96
|
-
- Implementers pause for resolution rather than guessing a backing.
|
|
97
|
-
- The audit is still valid; `backing: null` is intentional output, not
|
|
98
|
-
missing data.
|
|
99
|
-
|
|
100
|
-
### `backing` object
|
|
101
|
-
|
|
102
|
-
| Field | Type | Description |
|
|
103
|
-
|---|---|---|
|
|
104
|
-
| `kind` | enum (optional, default `rest_controller`) | The implementation path the ability should use or inspect. One of `rest_controller`, `service`, `helper`, `data_store`. When omitted, defaults to `rest_controller` for backwards compatibility with audits authored before this field landed. The kind tells downstream tooling whether the delegation pattern from `wp-abilities-api/references/shared-core-service.md` applies (only `rest_controller` is a candidate; the others select the shared-service shape from the start). |
|
|
105
|
-
| `class` | string | Fully-qualified PHP class name (controller class for `rest_controller`; service / helper / data-store class for the other kinds). May be omitted when `kind: data_store` and the backing is a bare option key, post-meta key, or table without an owning class. |
|
|
106
|
-
| `file` | string | Path relative to plugin root. |
|
|
107
|
-
| `method` | enum (required when `kind: rest_controller`) | HTTP method: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`. Not applicable when `kind` is `service`, `helper`, or `data_store`. |
|
|
108
|
-
| `route` | string (required when `kind: rest_controller`) | Full REST route path. Not applicable to non-REST kinds. |
|
|
109
|
-
| `route_registration_line` | integer OR `null` | For `kind: rest_controller`: line number of the `register_rest_route(` call, or `null` when inherited. Omit for other kinds. |
|
|
110
|
-
| `callback` | string | For `kind: rest_controller`: controller method name that handles the route. For `kind: service` / `helper`: method name on the service / helper class. For `kind: data_store`: the operation name (`get_option`, `get_post_meta`) or the table-read pattern; may be omitted. |
|
|
111
|
-
| `callback_line` | integer OR `null` | Line number of the callback or method definition, or `null` when inherited or not applicable. |
|
|
112
|
-
| `inherited_from` | string (optional) | Fully-qualified parent class name when the route and/or callback is inherited from a class outside this plugin's repo (e.g. `WP_REST_Posts_Controller` from WordPress core, or another plugin's REST base class for extension plugins). Pair with `null` line numbers. Lets downstream tooling skip the re-grep step cleanly. Primarily relevant for `kind: rest_controller`. |
|
|
113
|
-
|
|
114
|
-
### `permission` object
|
|
115
|
-
|
|
116
|
-
| Field | Type | Description |
|
|
117
|
-
|---|---|---|
|
|
118
|
-
| `source` | enum (optional, default `rest_controller`) | Where the canonical permission for this behavior lives — not always the REST controller's `permission_callback`. One of `rest_controller`, `admin_action`, `service`, `domain_policy`, `post_type_map`, `none`. When omitted, defaults to `rest_controller` for backwards compatibility. `admin_action` for behaviors gated by `check_admin_referer` / `current_user_can` on an admin handler; `service` when a shared method enforces the cap; `domain_policy` for plugins with a policy / authorization layer; `post_type_map` for capabilities resolved through `map_meta_cap` on a post-type cap shadow; `none` for genuinely public behavior. Tells the implementer whether the ability's `permission_callback` can mirror the REST callback or must consult a different source of truth. |
|
|
119
|
-
| `callback` | string | The method or function name that enforces the cap at the recorded `source`. For `source: rest_controller`, this is the `permission_callback` value. For `source: admin_action`, the admin handler function or method. For `source: service`, the service method that performs the cap check. |
|
|
120
|
-
| `resolves_to` | string | The `current_user_can()` call(s) it ultimately resolves to. For compound gates, include both (e.g. `"current_user_can('read_private_pages')` for read; `current_user_can('edit_others_pages')` for write"). |
|
|
121
|
-
| `confirmed` | bool | `true` if verified against source; `false` if inferred. |
|
|
122
|
-
|
|
123
|
-
## `excluded_from_mvp` — array
|
|
124
|
-
|
|
125
|
-
Abilities intentionally deferred for risk reasons. Each entry:
|
|
126
|
-
|
|
127
|
-
| Field | Type | Description |
|
|
128
|
-
|---|---|---|
|
|
129
|
-
| `name` | string | Proposed ability name (kebab-case). |
|
|
130
|
-
| `reason` | string | One sentence why it's deferred. |
|
|
131
|
-
|
|
132
|
-
## `surfaced_gaps` — array
|
|
133
|
-
|
|
134
|
-
MVP candidates with no backing endpoint (paired with `backing: null` above),
|
|
135
|
-
plus high-value endpoints discovered during enumeration that aren't in MVP
|
|
136
|
-
but would make future follow-up work. Each entry:
|
|
137
|
-
|
|
138
|
-
| Field | Type | Description |
|
|
139
|
-
|---|---|---|
|
|
140
|
-
| `name` | string | Proposed ability name. |
|
|
141
|
-
| `one_line_rationale` | string | Why it would be high-leverage. |
|
|
142
|
-
|
|
143
|
-
## Prose sections (required)
|
|
144
|
-
|
|
145
|
-
### Controller Inventory
|
|
146
|
-
|
|
147
|
-
A Markdown table with columns `Class | File | REST Base | Routes`. Must list
|
|
148
|
-
every controller enumeration found, even ones that aren't backing any MVP
|
|
149
|
-
ability. This gives reviewers a full picture and catches "why isn't X in the
|
|
150
|
-
MVP?" questions.
|
|
151
|
-
|
|
152
|
-
### Notes and Surprises
|
|
153
|
-
|
|
154
|
-
Free-form prose capturing anything that didn't fit the structured schema:
|
|
155
|
-
capability-gate mismatches between controllers, hardcoded route paths, dual
|
|
156
|
-
controllers with different output shapes, two-phase endpoint semantics, and
|
|
157
|
-
any judgment calls the auditor made.
|
|
158
|
-
|
|
159
|
-
## Minimal valid example
|
|
160
|
-
|
|
161
|
-
Copy-pasteable starting point for a new audit:
|
|
162
|
-
|
|
163
|
-
````markdown
|
|
164
|
-
---
|
|
165
|
-
Last updated: 2026-04-20 14:30
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
# Example Plugin Abilities — Phase 1 Audit
|
|
169
|
-
|
|
170
|
-
```yaml
|
|
171
|
-
plugin: example-plugin
|
|
172
|
-
repo: Owner/example-plugin
|
|
173
|
-
branch_audited: feat/abilities-example-plugin
|
|
174
|
-
audited_at: 2026-04-20
|
|
175
|
-
auditor: Your Name (Your Team)
|
|
176
|
-
baseline_abilities: 0
|
|
177
|
-
capability_gate: manage_options # confirmed at includes/rest-api/class-example-rest-controller.php line 32
|
|
178
|
-
|
|
179
|
-
proposed_abilities:
|
|
180
|
-
|
|
181
|
-
- name: example-plugin/get-items
|
|
182
|
-
intent: "List items with filters (status, owner, date range) so an agent can answer 'which items need attention?' in one call."
|
|
183
|
-
backing:
|
|
184
|
-
kind: rest_controller
|
|
185
|
-
class: Example_REST_Items_Controller
|
|
186
|
-
file: includes/rest-api/class-example-rest-items-controller.php
|
|
187
|
-
method: GET
|
|
188
|
-
route: /example/v1/items
|
|
189
|
-
route_registration_line: 26
|
|
190
|
-
callback: get_items
|
|
191
|
-
callback_line: 52
|
|
192
|
-
permission:
|
|
193
|
-
source: rest_controller
|
|
194
|
-
callback: check_permission
|
|
195
|
-
resolves_to: "current_user_can('manage_options')"
|
|
196
|
-
confirmed: true
|
|
197
|
-
return_type: "WP_REST_Response (wrapping array)"
|
|
198
|
-
effort: S
|
|
199
|
-
annotations: { readonly: true, destructive: false, idempotent: true }
|
|
200
|
-
notes:
|
|
201
|
-
- "get_items(WP_REST_Request $request) requires a WP_REST_Request; construct one in the ability execute_callback."
|
|
202
|
-
risks: []
|
|
203
|
-
use_case_fit: "Agent answers 'which items need attention right now?' in a single call without paging through a UI."
|
|
204
|
-
side_effects: []
|
|
205
|
-
seed_data_needs: "at least one item exists in any non-trashed status"
|
|
206
|
-
reference_ability: true
|
|
207
|
-
|
|
208
|
-
- name: example-plugin/close-item
|
|
209
|
-
intent: "Close a single item — terminal state transition, non-reversible."
|
|
210
|
-
backing:
|
|
211
|
-
kind: service
|
|
212
|
-
class: Example_Items_Service
|
|
213
|
-
file: src/Service/class-items-service.php
|
|
214
|
-
callback: close
|
|
215
|
-
callback_line: 88
|
|
216
|
-
permission:
|
|
217
|
-
source: service
|
|
218
|
-
callback: Example_Items_Service::assert_can_close
|
|
219
|
-
resolves_to: "current_user_can('manage_options')"
|
|
220
|
-
confirmed: true
|
|
221
|
-
return_type: "WP_REST_Response (updated item object)"
|
|
222
|
-
effort: M
|
|
223
|
-
annotations: { readonly: false, destructive: true, idempotent: false }
|
|
224
|
-
notes:
|
|
225
|
-
- "Close is terminal — no reopen endpoint exists."
|
|
226
|
-
risks:
|
|
227
|
-
- "No idempotency key on the backing endpoint; duplicate POSTs may produce inconsistent audit trails."
|
|
228
|
-
use_case_fit: "User or agent closes a stale item from a workflow that surfaces stale items (admin list view, daily-digest agent)."
|
|
229
|
-
side_effects:
|
|
230
|
-
- "fires action `example_plugin/item_closed` (downstream listeners may dispatch email)"
|
|
231
|
-
- "writes audit-log row to `example_plugin_audit_log`"
|
|
232
|
-
seed_data_needs: "one open item to close; the test must capture the item id before invocation"
|
|
233
|
-
|
|
234
|
-
excluded_from_mvp:
|
|
235
|
-
- name: example-plugin/delete-item
|
|
236
|
-
reason: "Hard delete is irreversible and lacks an undo endpoint; defer until soft-delete is designed."
|
|
237
|
-
|
|
238
|
-
surfaced_gaps:
|
|
239
|
-
- name: example-plugin/get-overview
|
|
240
|
-
one_line_rationale: "A zero-arg overview endpoint answering 'what's the current state of all items?' would be the highest-leverage ability but no backing endpoint exists yet."
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## Controller Inventory
|
|
244
|
-
|
|
245
|
-
| Class | File | REST Base | Routes |
|
|
246
|
-
|---|---|---|---|
|
|
247
|
-
| Example_REST_Items_Controller | includes/rest-api/class-example-rest-items-controller.php | example/v1/items | GET /example/v1/items, POST /example/v1/items/{id}/close |
|
|
248
|
-
|
|
249
|
-
## Notes and Surprises
|
|
250
|
-
|
|
251
|
-
### Capability gate is uniform
|
|
252
|
-
Every controller inherits `Example_Base_REST_Controller` and uses
|
|
253
|
-
`check_permission` verbatim as the `permission_callback`. No per-route
|
|
254
|
-
overrides. Safe to treat `manage_options` as the single gate.
|
|
255
|
-
````
|
|
256
|
-
|
|
257
|
-
## Known limitations
|
|
258
|
-
|
|
259
|
-
Documented so downstream skills have an explicit contract:
|
|
260
|
-
|
|
261
|
-
- **`capability_gate` string-with-inline-comment form** loses data when parsed
|
|
262
|
-
by strict YAML parsers (comments are dropped). The structured object form is
|
|
263
|
-
preferred; string form is accepted for backwards compatibility.
|
|
264
|
-
- **Legacy compound-string `capability_gate`** — the `"<read_cap> / <write_cap>"`
|
|
265
|
-
form predates the structured `{read, write}` object and is still accepted
|
|
266
|
-
for backwards compatibility. Validators (e.g. `wp-abilities-verify`)
|
|
267
|
-
emit WARN on this form to nudge migration to the structured shape;
|
|
268
|
-
they do NOT FAIL. New audits should use the object form.
|
|
269
|
-
- **`return_type` is hint-only.** Prose for the human auditor; not
|
|
270
|
-
machine-parseable. Downstream skills use runtime `is_wp_error(...)` and
|
|
271
|
-
`instanceof WP_REST_Response` checks regardless of what this field says.
|
|
272
|
-
- **Line numbers drift.** `route_registration_line` and `callback_line` are
|
|
273
|
-
captured at audit time and may bit-rot. Downstream skills re-locate routes
|
|
274
|
-
by `(class, callback)` and do not rely on exact line numbers.
|
|
275
|
-
- **`inherited_from` + `null` line numbers** are the canonical way to
|
|
276
|
-
represent routes/callbacks defined in a parent class that lives outside
|
|
277
|
-
the plugin repo.
|
|
278
|
-
- **`backing: null` invariant.** Abilities with `backing: null` are intentional
|
|
279
|
-
gaps and MUST also appear in `surfaced_gaps` by `name`. Validators FAIL
|
|
280
|
-
audits where this invariant is violated (a `null` backing without a
|
|
281
|
-
matching `surfaced_gaps` entry indicates inconsistent audit output).
|
|
282
|
-
- **Implementation-readiness fields added 2026-05-21.** `use_case_fit`,
|
|
283
|
-
`side_effects`, and `seed_data_needs` are required in the per-ability
|
|
284
|
-
schema as of this date. Audits authored against an earlier revision of
|
|
285
|
-
this schema will be missing the three fields. Validators (e.g.
|
|
286
|
-
`wp-abilities-verify`) emit WARN on missing implementation-readiness
|
|
287
|
-
fields to nudge backfill the next time the audit is touched; they do
|
|
288
|
-
NOT FAIL, mirroring the legacy `capability_gate` posture above. New
|
|
289
|
-
audits MUST populate all three.
|
|
290
|
-
- **`backing.kind` and `permission.source` added 2026-05-21.** Both
|
|
291
|
-
are optional with default `rest_controller` so older audits validate
|
|
292
|
-
as-is. New audits SHOULD populate both explicitly — `backing.kind`
|
|
293
|
-
to record whether the ability backs a REST controller, a shared
|
|
294
|
-
service, a helper, or a data store (because the answer drives the
|
|
295
|
-
delegate-vs-extract-service decision in `wp-abilities-api/references/
|
|
296
|
-
shared-core-service.md`); `permission.source` to record where the
|
|
297
|
-
canonical permission lives (REST callback is the common case, but
|
|
298
|
-
admin actions, service methods, domain policies, and post-type cap
|
|
299
|
-
maps each happen). Validators treat a missing field as the default,
|
|
300
|
-
not as an error.
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# Capability-Gate Tracing
|
|
2
|
-
|
|
3
|
-
How to resolve the actual capability (or capabilities) a plugin's REST
|
|
4
|
-
controllers gate on. The audit's `capability_gate` field and each ability's
|
|
5
|
-
`permission.resolves_to` field need to reflect reality, not what the
|
|
6
|
-
controller docblock says.
|
|
7
|
-
|
|
8
|
-
Two common mechanisms cover most plugins. Document both explicitly so the
|
|
9
|
-
auditor doesn't hard-code one plugin family's assumptions.
|
|
10
|
-
|
|
11
|
-
## Mechanism A — Direct (`check_permission()` returning a single cap)
|
|
12
|
-
|
|
13
|
-
The base REST controller declares a `check_permission()` (or
|
|
14
|
-
`permissions_check()`) method that calls `current_user_can('<some_cap>')`
|
|
15
|
-
once. Every route in the controller uses that method as
|
|
16
|
-
`permission_callback`.
|
|
17
|
-
|
|
18
|
-
### Identifying signs
|
|
19
|
-
|
|
20
|
-
- The base controller has a method like:
|
|
21
|
-
```php
|
|
22
|
-
public function check_permission() {
|
|
23
|
-
return current_user_can( 'manage_options' );
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
- Controllers extend the plugin's own base, not a WordPress core
|
|
27
|
-
post-type-backed class.
|
|
28
|
-
- The grep `grep -n 'current_user_can' <base-controller>.php` yields one hit.
|
|
29
|
-
|
|
30
|
-
### How to trace
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
# Locate the base controller (usually the parent of every REST controller).
|
|
34
|
-
grep -rn 'extends .*REST_Controller' includes/ | head
|
|
35
|
-
|
|
36
|
-
# Read its permission_callback implementation.
|
|
37
|
-
grep -n 'check_permission\|permissions_check' <base-controller>.php
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Trace once: the single `current_user_can()` call is the plugin's gate.
|
|
41
|
-
|
|
42
|
-
### How to represent in the audit
|
|
43
|
-
|
|
44
|
-
```yaml
|
|
45
|
-
capability_gate: manage_options # confirmed at includes/admin/class-<plugin>-rest-controller.php line 64
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Plugin-specific capabilities (e.g. WooCommerce's `manage_woocommerce` for
|
|
49
|
-
shop-aware contexts, Jetpack Forms' `edit_pages`) substitute for
|
|
50
|
-
`manage_options` cleanly — the shape stays the same.
|
|
51
|
-
|
|
52
|
-
## Mechanism B — Post-type-backed (core CPT capability machinery)
|
|
53
|
-
|
|
54
|
-
The controller extends a WordPress core post-type-backed class that
|
|
55
|
-
dispatches to the post-type capability map. There is no local
|
|
56
|
-
`check_permission()` — the permission callback resolves dynamically at
|
|
57
|
-
request time based on the request context (read vs write) and the post
|
|
58
|
-
type's `cap` object.
|
|
59
|
-
|
|
60
|
-
### Identifying signs
|
|
61
|
-
|
|
62
|
-
- The controller's base class is one of:
|
|
63
|
-
- `WP_REST_Posts_Controller` — the core post-type REST base.
|
|
64
|
-
- A subclass of it, in or out of this plugin's repo.
|
|
65
|
-
- No local `check_permission()` — permission callbacks are inherited.
|
|
66
|
-
- The post type is registered with `capability_type => '<cpt_or_shadow>'`,
|
|
67
|
-
and the cap map is resolved by core's `map_meta_cap()`.
|
|
68
|
-
|
|
69
|
-
### How to trace
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
# Find the post-type registration.
|
|
73
|
-
grep -rn "register_post_type\s*(\s*['\"]<cpt_name>['\"]" .
|
|
74
|
-
|
|
75
|
-
# Read the registration block. The relevant fields are:
|
|
76
|
-
# - capability_type: the type whose cap map this post type uses.
|
|
77
|
-
# A custom post type can either declare its own caps or shadow another
|
|
78
|
-
# type's (e.g. capability_type => 'page' to reuse Pages' caps).
|
|
79
|
-
# - capabilities: optional explicit cap-string overrides.
|
|
80
|
-
# - map_meta_cap: whether meta caps (read_post, edit_post) get mapped to
|
|
81
|
-
# primitive caps (read_private_<type>s, edit_others_<type>s).
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Dynamic resolution typically lands at:
|
|
85
|
-
|
|
86
|
-
- **Read context** (GET list / GET item): `current_user_can('read_private_<type>s')` or `current_user_can('read_<type>', $id)`.
|
|
87
|
-
- **Write context** (POST / PUT / DELETE): `current_user_can('edit_<type>s')`, `current_user_can('edit_others_<type>s')`, or `current_user_can('delete_<type>s', $id)`.
|
|
88
|
-
|
|
89
|
-
The two often differ — post-type-backed plugins routinely have distinct read
|
|
90
|
-
and write caps.
|
|
91
|
-
|
|
92
|
-
### How to represent in the audit
|
|
93
|
-
|
|
94
|
-
Use the structured `{read, write}` form from `audit-schema.md`:
|
|
95
|
-
|
|
96
|
-
```yaml
|
|
97
|
-
capability_gate:
|
|
98
|
-
read: read_private_pages
|
|
99
|
-
write: edit_others_pages
|
|
100
|
-
confirmed: true
|
|
101
|
-
verified_at: "custom_post_type capability_type='page' → core map_meta_cap (wp-includes/post.php) → primitive page caps"
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
In each ability's `permission` block, spell out both calls:
|
|
105
|
-
|
|
106
|
-
```yaml
|
|
107
|
-
permission:
|
|
108
|
-
callback: get_items_permissions_check
|
|
109
|
-
resolves_to: "WP_REST_Posts_Controller::get_items_permissions_check (inherited) → current_user_can('read_private_pages')"
|
|
110
|
-
confirmed: true
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Example A — generic plugin shadowing core Pages caps. A custom post type
|
|
114
|
-
registered with `capability_type='page'` inherits the Pages cap map, so
|
|
115
|
-
reads gate on `read_private_pages` and writes gate on `edit_others_pages`.
|
|
116
|
-
|
|
117
|
-
Example B — WooCommerce-style sidebar. WooCommerce's `shop_subscription` is
|
|
118
|
-
registered with `capability_type='shop_order'`, so reads gate on
|
|
119
|
-
`read_private_shop_orders` and writes gate on `edit_shop_orders`.
|
|
120
|
-
Mechanically identical to Example A; the cap names are project-specific.
|
|
121
|
-
WooCommerce also exposes a helper `wc_rest_check_post_permissions()` that
|
|
122
|
-
wraps the same core machinery — the helper is convenience; the underlying
|
|
123
|
-
mechanism is core's `map_meta_cap()`.
|
|
124
|
-
|
|
125
|
-
## Compound-string form (accepted, not preferred)
|
|
126
|
-
|
|
127
|
-
Some earlier audits encoded compound gates as a single string with a `/`
|
|
128
|
-
separator:
|
|
129
|
-
|
|
130
|
-
```yaml
|
|
131
|
-
capability_gate: read_private_pages / edit_others_pages
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
This is accepted for backwards compatibility, but:
|
|
135
|
-
|
|
136
|
-
- Downstream consumers have to heuristically split on `/`.
|
|
137
|
-
- YAML comments after the string are silently dropped by strict parsers, so
|
|
138
|
-
provenance gets lost.
|
|
139
|
-
- The `{read, write}` object form is machine-parseable and carries
|
|
140
|
-
`confirmed` and `verified_at` in-band.
|
|
141
|
-
|
|
142
|
-
Prefer the structured form for any new audit.
|
|
143
|
-
|
|
144
|
-
## Procedure — trace the permission source for each proposed behavior
|
|
145
|
-
|
|
146
|
-
The ability's permission should match the plugin's intended gate for the
|
|
147
|
-
proposed *behavior*, not necessarily the REST route. Often the REST
|
|
148
|
-
controller's `permission_callback` is the right source of truth, but in
|
|
149
|
-
some plugins the canonical permission lives elsewhere — an admin-action
|
|
150
|
-
handler with its own `check_admin_referer` + `current_user_can` block, a
|
|
151
|
-
service / helper method that performs the check before doing the work, a
|
|
152
|
-
domain-policy / authorization layer, or a post-type cap shadow resolved
|
|
153
|
-
through core's `map_meta_cap`. The audit should preserve where the
|
|
154
|
-
permission canonically lives so the implementer doesn't silently drift
|
|
155
|
-
to whichever source the REST layer happens to expose.
|
|
156
|
-
|
|
157
|
-
For each proposed ability, walk the chain once:
|
|
158
|
-
|
|
159
|
-
1. Identify the *behavior* the ability surfaces, then locate where the
|
|
160
|
-
plugin enforces the cap for that behavior. Check the REST controller's
|
|
161
|
-
`permission_callback` first; if the REST callback is `'__return_true'`,
|
|
162
|
-
delegates entirely, or doesn't match the behavior's intended gate,
|
|
163
|
-
look for the canonical source in an admin handler, a shared service
|
|
164
|
-
method, a domain-policy class, or a post-type cap map.
|
|
165
|
-
2. Record where the gate lives in the ability's `permission.source` field
|
|
166
|
-
per `audit-schema.md`: one of `rest_controller`, `admin_action`,
|
|
167
|
-
`service`, `domain_policy`, `post_type_map`, `none`. Default
|
|
168
|
-
`rest_controller`; pick another value when the canonical source is
|
|
169
|
-
elsewhere.
|
|
170
|
-
3. Determine whether the gate is Mechanism A (local method, single cap)
|
|
171
|
-
or Mechanism B (inherited, post-type-backed, dynamic).
|
|
172
|
-
4. Resolve to the actual `current_user_can()` call(s). For Mechanism B,
|
|
173
|
-
resolve BOTH read and write if the ability crosses contexts.
|
|
174
|
-
5. Record in the ability's `permission.resolves_to` field verbatim — the
|
|
175
|
-
string should read as an actual trace, not a best-guess summary.
|
|
176
|
-
6. Add a risk note when the canonical permission source diverges from
|
|
177
|
-
the REST controller's callback: the ability's `permission_callback`
|
|
178
|
-
must consult the canonical source (or replicate its check), not
|
|
179
|
-
copy the REST callback by reflex.
|
|
180
|
-
7. If every behavior in the plugin resolves to the same cap (or same
|
|
181
|
-
`{read, write}` pair) at the same source, hoist it into the top-level
|
|
182
|
-
`capability_gate`. If any behavior diverges in cap OR in source,
|
|
183
|
-
record the divergence in "Notes and Surprises".
|
|
184
|
-
|
|
185
|
-
## Common pitfall — `permission_callback => '__return_true'`
|
|
186
|
-
|
|
187
|
-
Zero-arg public endpoints sometimes declare `permission_callback =>
|
|
188
|
-
'__return_true'` at the REST layer (e.g. status lookups, enumerated lists
|
|
189
|
-
that are safe to expose). The audit still needs a gate:
|
|
190
|
-
|
|
191
|
-
- Record the REST-layer value as-is (`resolves_to:
|
|
192
|
-
"__return_true (public)"`) so the auditor isn't hiding reality.
|
|
193
|
-
- Add a risk note: the **ability** registration must NOT copy
|
|
194
|
-
`'__return_true'` — the ability's own `permission_callback` must match
|
|
195
|
-
the plugin's intended user gate (e.g. `manage_options`, `edit_pages`, or
|
|
196
|
-
whatever your plugin uses). The ability layer is the agent-facing surface
|
|
197
|
-
and needs that gate even when the underlying REST route is public.
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
# Controller Enumeration
|
|
2
|
-
|
|
3
|
-
How to produce an exhaustive list of a plugin's REST controllers — the first
|
|
4
|
-
step of every audit. Plugin family classification is handled separately by
|
|
5
|
-
`wp-project-triage`; this reference covers the mechanics of finding controller
|
|
6
|
-
classes inside whatever layout the plugin happens to use.
|
|
7
|
-
|
|
8
|
-
## Two enumeration paths
|
|
9
|
-
|
|
10
|
-
Observed across the plugins audited to date, there are exactly two paths that
|
|
11
|
-
together cover every layout seen in the wild:
|
|
12
|
-
|
|
13
|
-
| Path | When it works | How it works |
|
|
14
|
-
|---|---|---|
|
|
15
|
-
| **Glob** | Plugins that follow the standard `includes/admin/class-*-rest-*-controller.php` layout (WooCommerce core extensions, classic WooPayments). | Fast, deterministic, easy to script. Returns a complete list in one shell call. |
|
|
16
|
-
| **Grep** | Any non-standard layout — `includes/api/`, `includes/rest-api/`, `src/rest/`, monorepo package directories, or anything else. | Universal fallback: grep every PHP file under the plugin root for `register_rest_route(` call sites, then collect the enclosing class for each hit. |
|
|
17
|
-
|
|
18
|
-
### Default order
|
|
19
|
-
|
|
20
|
-
1. **Try glob first** — it's faster and produces a cleaner inventory.
|
|
21
|
-
2. **Fall back to grep** if glob returns zero hits (or clearly undercounts
|
|
22
|
-
against what you see in the plugin's public documentation / admin UI).
|
|
23
|
-
|
|
24
|
-
Running both and de-duplicating is legal; it catches monorepos that have
|
|
25
|
-
*some* controllers under the standard layout and *others* under a package
|
|
26
|
-
directory.
|
|
27
|
-
|
|
28
|
-
## Glob — standard layout
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
# From the plugin root:
|
|
32
|
-
ls includes/admin/class-*-rest-*-controller.php 2>/dev/null
|
|
33
|
-
ls includes/reports/class-*-rest-*-controller.php 2>/dev/null
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
What you'll see in repos that match this convention:
|
|
37
|
-
|
|
38
|
-
- WooPayments (`Automattic/woocommerce-payments`) — every controller under
|
|
39
|
-
`includes/admin/class-wc-rest-payments-*-controller.php` plus some under
|
|
40
|
-
`includes/reports/`.
|
|
41
|
-
- WooCommerce core's internal REST controllers use the same pattern.
|
|
42
|
-
|
|
43
|
-
If glob returns 5+ hits, it's almost always the complete inventory. If it
|
|
44
|
-
returns 0-2, fall through to grep.
|
|
45
|
-
|
|
46
|
-
## Grep — universal fallback
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
# From the plugin root:
|
|
50
|
-
grep -rn --include='*.php' 'register_rest_route(' .
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
For each hit:
|
|
54
|
-
|
|
55
|
-
1. Open the file.
|
|
56
|
-
2. Walk up to the enclosing class declaration.
|
|
57
|
-
3. Record `(class, file, route, callback, permission_callback)`.
|
|
58
|
-
|
|
59
|
-
This path matters because it's the only one that finds controllers in
|
|
60
|
-
non-standard locations:
|
|
61
|
-
|
|
62
|
-
- **WooCommerce Subscriptions** — controllers live under `includes/api/` and
|
|
63
|
-
`includes/api/legacy/`. The standard WooPayments glob returns zero; grep is
|
|
64
|
-
mandatory.
|
|
65
|
-
- **Jetpack Forms** (and most Jetpack packages) — controllers live under
|
|
66
|
-
`projects/packages/<name>/src/` with no conventional filename. Grep is
|
|
67
|
-
again mandatory.
|
|
68
|
-
- **Custom plugin layouts** — anything with `src/Rest/`, `lib/rest/`,
|
|
69
|
-
`api/v1/`, etc. Grep catches them all.
|
|
70
|
-
|
|
71
|
-
## Inherited routes
|
|
72
|
-
|
|
73
|
-
A controller can extend a base class in a different repo — typically the
|
|
74
|
-
parent plugin (for extensions built on top of another plugin) or WordPress
|
|
75
|
-
core itself (for plugins extending `WP_REST_Posts_Controller` or other core
|
|
76
|
-
REST bases). The `parent::register_routes()` dispatch appears in the
|
|
77
|
-
extending plugin's source, but the literal `register_rest_route(` call lives
|
|
78
|
-
in the parent. WooCommerce extensions extending
|
|
79
|
-
`WC_REST_Orders_Controller`, plugins built on Jetpack package REST classes,
|
|
80
|
-
and CPT plugins inheriting from `WP_REST_Posts_Controller` all hit this
|
|
81
|
-
pattern.
|
|
82
|
-
|
|
83
|
-
Handling:
|
|
84
|
-
|
|
85
|
-
- Record the route on the child class (that's where the plugin's REST surface
|
|
86
|
-
actually exposes it).
|
|
87
|
-
- Set `backing.route_registration_line: null` and
|
|
88
|
-
`backing.callback_line: null` in the audit schema.
|
|
89
|
-
- Add `backing.inherited_from: "<parent FQCN>"` so downstream skills can tell
|
|
90
|
-
the inheritance case from a plain missing line number.
|
|
91
|
-
- Consider running grep against the parent repo too when you need to confirm
|
|
92
|
-
the callback's request handling — inherited callbacks behave as whatever
|
|
93
|
-
the parent defines, not what the plugin repo documents.
|
|
94
|
-
|
|
95
|
-
See `audit-schema.md` for the exact field shapes.
|
|
96
|
-
|
|
97
|
-
## Exhaustiveness is the goal
|
|
98
|
-
|
|
99
|
-
The "Controller Inventory" table in the audit doc must list every controller
|
|
100
|
-
the enumeration found — not just ones backing proposed abilities. A reviewer
|
|
101
|
-
asking "why isn't controller X in the MVP?" should be able to point at the
|
|
102
|
-
inventory and see the explicit answer (usually: "excluded from MVP because…"
|
|
103
|
-
or "surfaced as a gap because…").
|
|
104
|
-
|
|
105
|
-
If your inventory has 3 entries and the plugin clearly exposes more, either
|
|
106
|
-
the enumeration is incomplete (re-run grep with broader patterns) or you're
|
|
107
|
-
filtering the inventory instead of the proposal list. Fix the inventory
|
|
108
|
-
first; filter after.
|
|
109
|
-
|
|
110
|
-
## Escalation
|
|
111
|
-
|
|
112
|
-
If neither glob nor grep produces a complete inventory — for example a
|
|
113
|
-
plugin that registers routes dynamically from config or via a factory that
|
|
114
|
-
does not contain a literal `register_rest_route(` string — document the
|
|
115
|
-
enumeration gap in "Notes and Surprises", and extend this reference with the
|
|
116
|
-
new pattern once understood.
|