Generated banner for Wp Abilities Audit
Generated logo for Wp Abilities Audit

Wp Abilities Audit

WordPressOfficialPublished by WordPress

  • Skill
  • Workflow
  • Software development
  • GPL-2.0-or-later
  • No issues found

From the author

Audit a WordPress plugin's REST surface and produce a standardized audit document proposing Abilities API registrations. Produces a markdown doc with a YAML schema and prose sections that humans and agents can both consume when planning a registration rollout. Works on any WP plugin.
— the description by WordPress

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The wp-abilities-audit skill provides a systematic procedure for auditing a WordPress plugin's REST API surface. It guides the user through enumerating controllers, tracing capability gates, and applying semantic-intent grouping to propose Abilities API registrations. The output is a standardized markdown document containing a YAML schema and prose sections, serving as a comprehensive planning artifact for developers and AI agents to implement the Abilities API.

What it does

  • Enumerates all REST controller classes, routes, and methods within a plugin.
  • Traces permission callbacks to identify underlying capability gates.
  • Groups REST endpoints into semantic abilities based on real-world user intent.
  • Generates a structured audit document with YAML metadata and implementation notes.
  • Identifies gaps, risks, and deferred items for the MVP rollout.
  • Provides a standardized format for both human and agent-driven implementation.

When to use it

  • When planning to implement the Abilities API for a WordPress plugin.
  • When you need a shareable, standardized audit artifact for a multi-plugin rollout.
  • When assessing a plugin's readiness for agent-based interaction.
  • When a project manager needs to scope the implementation effort for REST surface integration.

Advantages

  • Ensures consistency across different plugin audits.
  • Reduces manual effort by providing a clear, step-by-step audit procedure.
  • Produces machine-readable YAML for downstream automation.
  • Separates internal plumbing from user-facing abilities via semantic grouping.
  • Captures critical implementation details like capability gates and side effects.

Usage examples

“Run the wp-abilities-audit on the plugin located in ./wp-content/plugins/my-custom-plugin and save the report to ./plans/audit-my-plugin.md”
The agent performs a full audit of the plugin's REST surface, generates the required YAML schema, and saves the structured markdown report to the specified path.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/wordpress/wp-abilities-audit/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip wp-abilities-audit.zip -d .agents/skills

Documentation by WordPress

WP Abilities Audit

Produce a standardized audit document for a WordPress plugin's REST surface, proposing a set of Abilities API registrations grouped by semantic intent. The audit doc is a planning artifact for implementers — humans, agents, or both — that captures the controller inventory, capability gates, and proposed ability shapes in a structured form. A reviewer reading the doc can scope the work without re-deriving the survey.

This skill works on any plugin that exposes a REST surface. Plugin classification (for purposes of the optional plugin_family annotation) is the user's call; the workflow itself is plugin-agnostic.

When to use

  • The task is "register Abilities API abilities for a WP plugin" and no audit doc exists yet.

  • Planning participation in a multi-plugin abilities rollout and need a shareable, standardized audit artifact.

  • Pre-flight checking a plugin's agent-readiness before implementing abilities.

  • A PM or non-implementer wants to scope the work before engineering picks it up.

Inputs required

  1. Plugin checkout path — working tree of the plugin to audit.

  2. Triage output — run wp-project-triage first if not already done. The audit consumes signals.usesAbilitiesApi, versions.wordpress, and project.kind from the report.

  3. Auditor identity — name and team or context, recorded in the audit's auditor field.

  4. Output path — where the audit doc should land. Default explicit over implicit; ask if not provided rather than writing into the plugin worktree.

Prerequisites

  • wp-project-triage has run successfully and classified the plugin.

  • The plugin has at least one REST controller. If enumeration finds zero controllers, the audit doesn't apply — see "Failure modes" below.

Procedure

1. Enumerate REST controllers

Read references/controller-enumeration.md now — it covers the two observed enumeration paths (glob for standard layouts, grep as the universal fallback) and when to use each.

Record every controller class + file + REST base + routes in a "Controller Inventory" table. The inventory is exhaustive even though only a subset becomes proposed abilities.

2. For each controller, extract the backing fields

For every controller found, extract the fields the audit schema requires: class, file, HTTP method, route, route-registration line number, callback name, callback line number, permission callback, whether the callback takes a WP_REST_Request argument or is zero-arg, and the return type.

Read references/audit-schema.md now for the exact field list and the shape of proposed_abilities entries. Line-number fields may be null for inherited callbacks — the schema allows this and pairs it with an optional inherited_from field.

3. Confirm capability gate(s)

Trace each controller's permission_callback to its current_user_can() call (or to the post-type capability machinery if the controller extends a post-type-backed base).

Read references/capability-gate-tracing.md now — it documents the two common mechanisms (direct check_permission() vs post-type-backed wc_rest_check_post_permissions()) and how to represent each in the schema. Note explicitly whether read and write gates differ: compound gates are represented as a {read, write} object, not a single string.

4. Propose abilities using semantic-intent grouping

Do NOT atomize one ability per HTTP method. Apply the semantic-intent grouping heuristic — it's the only grouping rule this skill uses.

Read ../wp-abilities-api/references/grouping-heuristic.md now — do NOT re-derive the rules here. Short version: one ability per real-world question or state transition, with filter parameters in input_schema collapsing N variants into 1.

Apply the use-case sanity check before populating any candidate. Per ../wp-abilities-api/references/domain-vs-projection.md's use-case-contract test: would a human or agent intentionally perform this behavior through a supported plugin workflow? If yes, the candidate is a real ability — proceed to fill in fields. If no, the route is internal transport plumbing (cache invalidation, scheduler ticks, bookkeeping endpoints, debug introspection) — keep it in the Controller Inventory section for completeness, but do NOT promote it to proposed_abilities. The route may be useful to inventory; the proposed ability must represent a real user/operator question or action.

For each proposed ability that passes the sanity check, fill in every field in the proposed_abilities schema: name, intent, backing, permission, return_type, effort (S/M/L), annotations (readonly/destructive/idempotent), notes, risks, use_case_fit, side_effects, seed_data_needs.

The last three are the implementation-readiness facts the implementer and the verify-mode tooling both need: which human/agent workflow this ability serves (use_case_fit), what the backing path emits on every call (side_effects — empty array is a fact, not a missing value), and what representative data must exist in the test environment for the ability to execute through the public boundary (seed_data_needs).

5. Surface gaps and deferred items

Three buckets:

  • excluded_from_mvp — candidates intentionally deferred for risk reasons (real-money writes, irreversible state changes, or prerequisite design work). Each entry gets a one-sentence reason.

  • surfaced_gaps — MVP candidates with no backing endpoint (ability with backing: null), plus high-value endpoints discovered during enumeration that aren't in the MVP list but would be easy future wins.

  • Risks per ability — anything about a backing endpoint that the implementer must handle (no idempotency key, two-phase behavior, state-transition caveats, zero-arg endpoints registered with permission_callback => '__return_true' that must NOT copy that into the ability registration).

6. Write the audit doc

Write to the explicit output path collected in "Inputs required". The document structure must match references/audit-schema.md exactly:

  1. Last updated: YYYY-MM-DD HH:MM header.

  2. YAML block with all required top-level metadata + proposed_abilities, excluded_from_mvp, surfaced_gaps.

  3. "Controller Inventory" table.

  4. "Notes and Surprises" prose section.

A copy-pasteable minimal example showing the full shape lives in references/audit-schema.md under "Minimal valid example" — start there when authoring a new audit.

7. (Optional) Designate a reference implementation ability

Set reference_ability: true on the first ability an implementer should land — typically the smallest, safest, highest-leverage read. This gives downstream workflows a deterministic starting point.

Verification

  • The audit conforms to references/audit-schema.md (all required top-level fields present, at least one entry in proposed_abilities, annotations complete on every ability).

  • capability_gate is a string for single-cap plugins or a {read, write} object for post-type-backed plugins.

  • Every ability with backing: null also appears in surfaced_gaps.

  • The doc round-trips through the validator in audit-schema.md "Known limitations" without errors.

Failure modes / debugging

  • Plugin has no REST controllers — audit doesn't apply. Consider hooks/filters-based abilities (out of scope for this skill's current version) or skip abilities adoption for this plugin.

  • Plugin inherits controllers from another repo (common for plugins extending core post-type-backed controllers like WP_REST_Posts_Controller, or extension plugins built on a parent's REST classes) — capture with backing.inherited_from: "<parent FQCN>". Line-number fields may be null per the schema.

  • Compound capability gate (distinct read/write caps) — use the structured {read, write} form documented in references/capability-gate-tracing.md. Don't smuggle a /-separated string into a field typed as a single cap.

  • Ambiguous grouping — route to ../wp-abilities-api/references/grouping-heuristic.md. Do not invent alternative grouping rules in the audit doc.

  • Zero-arg endpoints with permission_callback => '__return_true' — legal at the REST layer, but the ability's own permission_callback must match the plugin's merchant gate. Never promote '__return_true' into an ability registration. Note this in the ability's risks.

  • Output path defaults to plugin worktree — always ask the user for an explicit output directory (e.g. their vault plans/). Writing the audit into the plugin's own git history pollutes the worktree and buries the artifact.

Escalation

  • If the plugin uses an enumeration convention not covered by references/controller-enumeration.md (neither the standard glob nor the grep fallback produces a complete inventory), update that reference with the new convention and open a PR so future audits cover it deterministically.

  • If capability tracing hits a mechanism not covered by references/capability-gate-tracing.md, extend that file rather than encoding the new case in the audit's "Notes and Surprises" only.

Files

references/capability-gate-tracing.md

Capability-Gate Tracing

How to resolve the actual capability (or capabilities) a plugin's REST controllers gate on. The audit's capability_gate field and each ability's permission.resolves_to field need to reflect reality, not what the controller docblock says.

Two common mechanisms cover most plugins. Document both explicitly so the auditor doesn't hard-code one plugin family's assumptions.

Mechanism A — Direct (check_permission() returning a single cap)

The base REST controller declares a check_permission() (or permissions_check()) method that calls current_user_can('<some_cap>') once. Every route in the controller uses that method as permission_callback.

Identifying signs
  • The base controller has a method like:

    public function check_permission() {
        return current_user_can( 'manage_options' );
    }
  • Controllers extend the plugin's own base, not a WordPress core post-type-backed class.

  • The grep grep -n 'current_user_can' <base-controller>.php yields one hit.

How to trace
# Locate the base controller (usually the parent of every REST controller).
grep -rn 'extends .*REST_Controller' includes/ | head

# Read its permission_callback implementation.
grep -n 'check_permission\|permissions_check' <base-controller>.php

Trace once: the single current_user_can() call is the plugin's gate.

How to represent in the audit
capability_gate: manage_options  # confirmed at includes/admin/class-<plugin>-rest-controller.php line 64

Plugin-specific capabilities (e.g. WooCommerce's manage_woocommerce for shop-aware contexts, Jetpack Forms' edit_pages) substitute for manage_options cleanly — the shape stays the same.

Mechanism B — Post-type-backed (core CPT capability machinery)

The controller extends a WordPress core post-type-backed class that dispatches to the post-type capability map. There is no local check_permission() — the permission callback resolves dynamically at request time based on the request context (read vs write) and the post type's cap object.

Identifying signs
  • The controller's base class is one of:

    • WP_REST_Posts_Controller — the core post-type REST base.

    • A subclass of it, in or out of this plugin's repo.

  • No local check_permission() — permission callbacks are inherited.

  • The post type is registered with capability_type => '<cpt_or_shadow>', and the cap map is resolved by core's map_meta_cap().

How to trace
# Find the post-type registration.
grep -rn "register_post_type\s*(\s*['\"]<cpt_name>['\"]" .

# Read the registration block. The relevant fields are:
#   - capability_type: the type whose cap map this post type uses.
#     A custom post type can either declare its own caps or shadow another
#     type's (e.g. capability_type => 'page' to reuse Pages' caps).
#   - capabilities: optional explicit cap-string overrides.
#   - map_meta_cap: whether meta caps (read_post, edit_post) get mapped to
#     primitive caps (read_private_<type>s, edit_others_<type>s).

Dynamic resolution typically lands at:

  • Read context (GET list / GET item): current_user_can('read_private_<type>s') or current_user_can('read_<type>', $id).

  • 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).

The two often differ — post-type-backed plugins routinely have distinct read and write caps.

How to represent in the audit

Use the structured {read, write} form from audit-schema.md:

capability_gate:
  read: read_private_pages
  write: edit_others_pages
  confirmed: true
  verified_at: "custom_post_type capability_type='page' → core map_meta_cap (wp-includes/post.php) → primitive page caps"

In each ability's permission block, spell out both calls:

permission:
  callback: get_items_permissions_check
  resolves_to: "WP_REST_Posts_Controller::get_items_permissions_check (inherited) → current_user_can('read_private_pages')"
  confirmed: true

Example A — generic plugin shadowing core Pages caps. A custom post type registered with capability_type='page' inherits the Pages cap map, so reads gate on read_private_pages and writes gate on edit_others_pages.

Example B — WooCommerce-style sidebar. WooCommerce's shop_subscription is registered with capability_type='shop_order', so reads gate on read_private_shop_orders and writes gate on edit_shop_orders. Mechanically identical to Example A; the cap names are project-specific. WooCommerce also exposes a helper wc_rest_check_post_permissions() that wraps the same core machinery — the helper is convenience; the underlying mechanism is core's map_meta_cap().

Compound-string form (accepted, not preferred)

Some earlier audits encoded compound gates as a single string with a / separator:

capability_gate: read_private_pages / edit_others_pages

This is accepted for backwards compatibility, but:

  • Downstream consumers have to heuristically split on /.

  • YAML comments after the string are silently dropped by strict parsers, so provenance gets lost.

  • The {read, write} object form is machine-parseable and carries confirmed and verified_at in-band.

Prefer the structured form for any new audit.

Procedure — trace the permission source for each proposed behavior

The ability's permission should match the plugin's intended gate for the proposed behavior, not necessarily the REST route. Often the REST controller's permission_callback is the right source of truth, but in some plugins the canonical permission lives elsewhere — an admin-action handler with its own check_admin_referer + current_user_can block, a service / helper method that performs the check before doing the work, a domain-policy / authorization layer, or a post-type cap shadow resolved through core's map_meta_cap. The audit should preserve where the permission canonically lives so the implementer doesn't silently drift to whichever source the REST layer happens to expose.

For each proposed ability, walk the chain once:

  1. Identify the behavior the ability surfaces, then locate where the plugin enforces the cap for that behavior. Check the REST controller's permission_callback first; if the REST callback is '__return_true', delegates entirely, or doesn't match the behavior's intended gate, look for the canonical source in an admin handler, a shared service method, a domain-policy class, or a post-type cap map.

  2. Record where the gate lives in the ability's permission.source field per audit-schema.md: one of rest_controller, admin_action, service, domain_policy, post_type_map, none. Default rest_controller; pick another value when the canonical source is elsewhere.

  3. Determine whether the gate is Mechanism A (local method, single cap) or Mechanism B (inherited, post-type-backed, dynamic).

  4. Resolve to the actual current_user_can() call(s). For Mechanism B, resolve BOTH read and write if the ability crosses contexts.

  5. Record in the ability's permission.resolves_to field verbatim — the string should read as an actual trace, not a best-guess summary.

  6. Add a risk note when the canonical permission source diverges from the REST controller's callback: the ability's permission_callback must consult the canonical source (or replicate its check), not copy the REST callback by reflex.

  7. If every behavior in the plugin resolves to the same cap (or same {read, write} pair) at the same source, hoist it into the top-level capability_gate. If any behavior diverges in cap OR in source, record the divergence in "Notes and Surprises".

Common pitfall — permission_callback => '__return_true'

Zero-arg public endpoints sometimes declare permission_callback => '__return_true' at the REST layer (e.g. status lookups, enumerated lists that are safe to expose). The audit still needs a gate:

  • Record the REST-layer value as-is (resolves_to: "__return_true (public)") so the auditor isn't hiding reality.

  • Add a risk note: the ability registration must NOT copy '__return_true' — the ability's own permission_callback must match the plugin's intended user gate (e.g. manage_options, edit_pages, or whatever your plugin uses). The ability layer is the agent-facing surface and needs that gate even when the underlying REST route is public.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

The author says: Targets WordPress 7.0+ (PHP 7.4.0+). Filesystem-based agent with bash + node. Requires access to the plugin checkout; some workflows benefit from WP-CLI but don't require it.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
WordPress
Publisher
WordPress (Official)
Source
WordPress/agent-skills · skills/wp-abilities-audit · commit 7374e6f
Licence
GNU General Public License v2.0 or later (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by WordPress or WordPress.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download wp-abilities-audit.zip