Skip to content

Commit bb9df47

Browse files
JUVOJustinJustin Vogt
andauthored
Add WordPress Abilities API Support (#155)
* Enhance upsert-translations documentation for clarity and detail * Rename upsert-translations documentation file to translations-upsert and update rule comparison instructions for clarity * Update commands-upsert documentation to clarify upstream command integration process * Update i18n documentation for clarity and command renaming * Update create-blocks documentation for clarity and accuracy * Update strauss-upsert documentation for accuracy in composer.json reference * Update ac-skills-upsert documentation to correct subtask flag and improve path clarity * Add WordPress Abilities API support with ability interface and registration * Add Ability Category support with interface and registration updates * Enhance documentation for WordPress Abilities API integration with detailed explanations and quick start guide * Refine type annotations for abilities and ability categories in Loader.php * Fix: Add validation for ability category classes in add_ability method * Chore: Add phpstan ignore comment for already narrowed type in add_ability method --------- Co-authored-by: Justin Vogt <mail@justin-vogt.de>
1 parent a4d3a0e commit bb9df47

14 files changed

Lines changed: 453 additions & 23 deletions

.opencode/command/a8c-skills-upsert.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
description: Upsert AC skills [skillnames], default to wp-interactivity-api, wp-block-development
3-
subtask: true
3+
subtask: false
44
---
55

66
**Goal:** Upsert skills provided by automattic https://github.com/Automattic/agent-skills into the current workspace.
@@ -25,7 +25,7 @@ Based on the input provided, determine which skills to sync: "$ARGUMENTS"
2525
- Always upsert wp-project-triage as it is a dependency for other skills.
2626
- Upsert the skills "$ARGUMENTS" if they exist in the archive. Let the user know about missing skills after syncing the valid ones.
2727

28-
* Compare existing skills in @.opencode/skill with the extracted skills.
28+
* Compare existing skills in @.opencode/skill/ with the extracted skills.
2929
* **Add** new skills that don’t exist in the workspace and should be added.
3030
* **Update** existing skills
3131
* Default to treating the upstream as the source of truth.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
---
2-
description: Add boilerplate commands to this workspace
2+
description: Add upstream commands to this workspace
33
---
44

55
ALL command files need to be valid markdown with proper `.md` file extension
66

77
1. Download https://github.com/JUVOJustin/wordpress-plugin-boilerplate/archive/refs/heads/main.zip inside of to the current workspace. ALWAYS remove the downloaded assets at end. The commands are stored inside `.opencode/command` folder of the archive.
8-
2. Compare the existing commands in `.opencode/command` with the upstream commands downloaded.
8+
2. Compare the existing commands in @.opencode/command/ with the upstream commands downloaded.
99
3. Add ALL new commands only present in the upstream
1010
4. Update commands present in both places. If the difference is too big, ask the user to confirm. Generally the upstream is the source of truth.
1111
5. Ask the user to confirm the removal of commands only present in the local project

.opencode/command/rules-upsert.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ description: Sync wordpress rules with wordpress-dev-llm-rules repo
1212

1313

1414
## **2. Compare and Sync Rules**
15-
* Compare existing rules in `.github/instructions/` with the extracted upstream rules.
15+
* Compare existing rules in @.github/instructions/ with the extracted upstream rules.
1616
* Rename each upstream rule to a Copilot-compatible format:
1717
`{filename}.instructions.md`
1818

1919
## **3. Add or Update Rules**
20-
* **Add** new upstream rules that don’t exist locally and that no overlapping skill exists for in `.opencode/skill`.
20+
* **Add** new upstream rules that don’t exist locally and that no overlapping skill exists for in @.opencode/skill.
2121
* **Update** existing rules when both sources have the same file.
2222

2323
* If differences are large, confirm with the user before overwriting.

.opencode/command/strauss-upsert.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
description: Update strauss prefixing package from boilerplate
33
---
44

5-
1. Get composer.json of the https://github.com/JUVOJustin/wordpress-plugin-boilerplate repository.
5+
1. Get @composer.json of the https://github.com/JUVOJustin/wordpress-plugin-boilerplate repository.
66
2. Check scripts.prefix-namespaces in the composer.json
77
3. Extract the url from the script. It typically looks like: `https://github.com/BrianHenryIE/strauss/releases/download/0.22.2/strauss.phar` or `https://github.com/BrianHenryIE/strauss/releases/latest/download/strauss.phar`
88
4. Compare the version with the local version.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ subtask: false
66
**Goal:** Update or create translations of the plugin based on the latest available strings.
77

88
1. Read @languages/ to validate which languages are already set up. This folder needs to contain all translation files.
9-
2. Run `composer run i18n:extract`. This generates a `.pot` file updates existing `.po` files.
9+
2. Run `composer run i18n:extract`. This generates a `.pot` file and updates existing `.po` files.
1010
3. Based on the input provided, determine what to translate: "$ARGUMENTS"
1111
* **No input (default)**: Read existing `.po` files and edit them to add missing or outdated translations.
1212
* Defined input: Create `.po` files for the languages specified in the input if they do not exist. Edit all `.po` files and add missing or outdated translations.
1313

14-
**Use one subtask/subagent per `.po` file**
14+
**Use one subtask/subagent per `.po` file to explore and edit**
1515
4. Run `composer run i18n:compile` to compile the `.po` files into `.mo`, `.json` and `.php` files. This step makes translations available to WordPress.

AGENTS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ private function define_admin_hooks() {
4545
To generate a new Gutenberg block, simply run `npm run create-block` and enter the required information when prompted.
4646
This will create a new block in the `src/Blocks/` folder. The block will be automatically registered and the assets enqueued.
4747

48+
### Abilities API
49+
To expose plugin functionality via the Abilities API:
50+
1. Create a category class in `src/Abilities/` implementing `Ability_Category_Interface`.
51+
2. Create an ability class in `src/Abilities/` implementing `Ability_Interface`.
52+
3. Register the ability in @Demo_Plugin.php
53+
54+
Categories are auto-registered when referenced by an ability. See @docs/abilities.md for full examples.
55+
4856
### i18n/Translations Support
4957
1. Run `npm run i18n:extract` to extract translatable strings into the `.pot` file located in the `languages/` directory. Strings in the PHP/JS need to use functions like `__()` or `_e()` with the plugin's text domain. Existing `.po` files will be updated automatically.
5058
2. From the `.pot` file, translate by creating `.po` files for each desired language.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ All plugin logic should go into the `src` folder. This separation helps maintain
5656

5757
```
5858
src/
59-
├── Blocks/ # Gutenberg Blocks
59+
├── Abilities/ # Abilitis and their categories
6060
├── API/ # Rest API-specific functionality
61+
├── Blocks/ # Gutenberg Blocks
6162
├── CLI/ # CLI commands
6263
└── Integrations/ # Core plugin functions and utilities
6364
└── BricksBuilder/ # Bricks Builder integration

demo-plugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* Description: This is a short description of what the plugin does. It's displayed in the WordPress admin area.
1717
* Version: 1.0.0
1818
* Requires PHP: 8.0
19-
* Requires at least: 6.8
19+
* Requires at least: 6.9
2020
* License: GPL-2.0+
2121
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
2222
* Text Domain: demo-plugin

docs/abilities.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# WordPress Abilities API
2+
3+
## Why This Boilerplate Includes Abilities Support
4+
5+
The project provides interfaces and Loader integration for the WordPress Abilities API to give you a head start when exposing your plugin's functionality.
6+
7+
**What this integration offers:**
8+
9+
- **Consistent structure** — The `Ability_Interface` enforces input/output schemas, permission checks, and annotations so every ability follows the same pattern.
10+
- **Automatic registration** — Categories are collected and registered automatically when you add an ability to the Loader.
11+
- **Type safety** — PHPStan and IDE autocompletion guide your implementation.
12+
- **No overhead when unused** — If you don't register abilities, no code runs. On WordPress < 6.9, the feature is silently skipped.
13+
14+
To understand *what* the Abilities API is or *why* you might use it see the [official documentation](https://developer.wordpress.org/news/2025/11/introducing-the-wordpress-abilities-api/).
15+
16+
---
17+
18+
19+
## Quick Start
20+
21+
### 1. Create Category Class (Optional)
22+
23+
Create `src/Abilities/Categories/Data_Retrieval.php`:
24+
25+
```php
26+
<?php
27+
28+
namespace Demo_Plugin\Abilities\Categories;
29+
30+
use Demo_Plugin\Abilities\Ability_Category_Interface;
31+
32+
/**
33+
* Category for abilities that retrieve data without modifications.
34+
*/
35+
class Data_Retrieval implements Ability_Category_Interface {
36+
37+
public static function get_slug(): string {
38+
return 'data-retrieval';
39+
}
40+
41+
public static function get_label(): string {
42+
return __( 'Data Retrieval', 'demo-plugin' );
43+
}
44+
45+
public static function get_description(): string {
46+
return __( 'Abilities that retrieve and return data from the WordPress site.', 'demo-plugin' );
47+
}
48+
49+
public static function get_meta(): array {
50+
return array();
51+
}
52+
}
53+
```
54+
55+
### 2. Create Ability Class
56+
57+
Create `src/Abilities/My_Ability.php`:
58+
59+
```php
60+
<?php
61+
62+
namespace Demo_Plugin\Abilities;
63+
64+
use Demo_Plugin\Abilities\Categories\Data_Retrieval;
65+
use WP_Error;
66+
67+
class My_Ability implements Ability_Interface {
68+
69+
public static function get_name(): string {
70+
return 'demo-plugin/my-ability';
71+
}
72+
73+
public static function get_label(): string {
74+
return __( 'My Ability', 'demo-plugin' );
75+
}
76+
77+
public static function get_description(): string {
78+
return __( 'Does something useful.', 'demo-plugin' );
79+
}
80+
81+
public static function get_category(): string {
82+
return Data_Retrieval::class;
83+
}
84+
85+
public static function get_input_schema(): array {
86+
return array(
87+
'type' => 'object',
88+
'properties' => array(
89+
'post_id' => array( 'type' => 'integer', 'minimum' => 1 ),
90+
),
91+
'required' => array( 'post_id' ),
92+
);
93+
}
94+
95+
public static function get_output_schema(): array {
96+
return array(
97+
'type' => 'object',
98+
'properties' => array(
99+
'success' => array( 'type' => 'boolean' ),
100+
'title' => array( 'type' => 'string' ),
101+
),
102+
);
103+
}
104+
105+
public static function get_annotations(): array {
106+
return array(
107+
'readonly' => true,
108+
'destructive' => false,
109+
'idempotent' => true,
110+
);
111+
}
112+
113+
public static function show_rest(): bool {
114+
return true;
115+
}
116+
117+
public static function check_permissions( mixed $input = null ): bool|WP_Error {
118+
return current_user_can( 'read' );
119+
}
120+
121+
public static function execute( mixed $input = null ): mixed {
122+
$post = get_post( $input['post_id'] );
123+
124+
if ( ! $post ) {
125+
return new WP_Error( 'not_found', 'Post not found.', array( 'status' => 404 ) );
126+
}
127+
128+
return array( 'success' => true, 'title' => $post->post_title );
129+
}
130+
}
131+
```
132+
133+
### 3. Register Ability
134+
135+
In `Demo_Plugin.php`:
136+
137+
```php
138+
$this->loader->add_ability( Abilities\My_Ability::class );
139+
```
140+
141+
Categories are automatically registered via the Loader when abilities reference them.
142+
143+
## Ability Interface Reference
144+
145+
| Method | Returns | Purpose |
146+
|--------|---------|---------|
147+
| `get_name()` | `string` | Unique ID: `namespace/ability-name` |
148+
| `get_label()` | `string` | Display name |
149+
| `get_description()` | `string` | What the ability does |
150+
| `get_category()` | `string` | Category class name |
151+
| `get_input_schema()` | `array` | JSON Schema for input |
152+
| `get_output_schema()` | `array` | JSON Schema for output |
153+
| `get_annotations()` | `array` | Behavioral hints (readonly, destructive, idempotent) |
154+
| `show_rest()` | `bool` | Expose via REST API |
155+
| `check_permissions($input)` | `bool\|WP_Error` | Permission check |
156+
| `execute($input)` | `mixed` | Main logic |
157+
158+
## Category Interface Reference
159+
160+
| Method | Returns | Purpose |
161+
|--------|---------|---------|
162+
| `get_slug()` | `string` | Unique slug (lowercase, hyphens only) |
163+
| `get_label()` | `string` | Display name |
164+
| `get_description()` | `string` | Category purpose |
165+
| `get_meta()` | `array` | Optional metadata |
166+
167+
## Usage
168+
169+
```php
170+
$ability = wp_get_ability( 'demo-plugin/my-ability' );
171+
$result = $ability->execute( array( 'post_id' => 123 ) );
172+
173+
if ( is_wp_error( $result ) ) {
174+
echo $result->get_error_message();
175+
}
176+
```

docs/create-blocks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Develop and Ship Blocks
22

3-
The boilerplate has builtin block support. This means it has an opinionated way to use blocks, but also automates a lot of the tedious tasks like registration and asset management.
3+
The project has builtin block support. This means it has an opinionated way to use blocks, but also automates a lot of the tedious tasks like registration and asset management.
44

55
## Create your first block
66

0 commit comments

Comments
 (0)