feat(patterns): create_pattern — synced-pattern creation with sync-status control - #57
Conversation
WalkthroughThe PR adds ChangesPattern creation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant MCPTool
participant WordPressBlockClient
participant RESTController
participant PatternManager
participant WordPress
MCPTool->>WordPressBlockClient: createPattern(request)
WordPressBlockClient->>RESTController: POST /patterns
RESTController->>PatternManager: create_pattern(args)
PatternManager->>WordPress: create wp_block and sync metadata
WordPress-->>PatternManager: pattern identifiers and content
PatternManager-->>RESTController: reference and warnings
RESTController-->>WordPressBlockClient: created pattern response
WordPressBlockClient-->>MCPTool: pattern result
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
src/types.ts (1)
473-481: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winEncode the
content/blocksXOR in the public type.The interface permits both fields or neither, despite documenting “exactly one.” Prefer a union with one required branch and the other
never; retain runtime validation for untyped callers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/types.ts` around lines 473 - 481, Update CreatePatternRequest to a discriminated union with one branch requiring content and forbidding blocks via never, and another requiring blocks and forbidding content via never; preserve the existing shared fields and runtime validation for untyped callers.src/__tests__/tools/patterns/create_pattern.test.ts (1)
1-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocblock claims "unknown tool still throws" coverage that isn't in this file.
No test in this suite dispatches an unrecognized tool name through
handlePatternTool. Either add the regression test or trim the claim from the docblock.✅ Example regression test to add
it('throws for an unknown tool', async () => { await expect( handlePatternTool('not_a_real_tool' as any, {}, client as any) ).rejects.toThrow(); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/__tests__/tools/patterns/create_pattern.test.ts` around lines 1 - 11, Update the create_pattern test docblock to remove the “Unknown tool still throws” coverage claim, since this suite does not currently dispatch an unrecognized tool through handlePatternTool. Keep the remaining coverage descriptions unchanged.wordpress-plugin/gk-block-mcp/tests/REST/CreatePatternTest.php (1)
121-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering the remaining validation branches.
invalid_sync_status,invalid_status,block_depth_exceeded, andinvalid_blockare new dedicated error codes inPattern_Manager::create_pattern()with no direct test here (only the XOR and legacy-tier branches are covered).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wordpress-plugin/gk-block-mcp/tests/REST/CreatePatternTest.php` around lines 121 - 141, Extend the CreatePatternTest coverage with direct cases for Pattern_Manager::create_pattern() returning each new error code: invalid_sync_status, invalid_status, block_depth_exceeded, and invalid_block. Add assertions that each response is a WP_Error with the expected dedicated code, while preserving the existing XOR and legacy-tier tests.wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php (1)
745-754: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInline compound condition — extract to a named variable for consistency.
Two lines later, the content/blocks XOR check is correctly assigned to named
$has_blocks/$has_contentbefore theif. The title check inlines a two-predicate compound expression directly instead.As per coding guidelines,
wordpress-plugin/gk-block-mcp/**/*.php: "Assign checks to named variables beforeif,while, or ternary conditionals instead of inlining function calls or compound expressions, except load-bearing short-circuit guards."♻️ Suggested fix
$title = isset( $args['title'] ) ? $args['title'] : null; - if ( ! is_string( $title ) || '' === $title ) { + $title_is_invalid = ! is_string( $title ) || '' === $title; + if ( $title_is_invalid ) { return new \WP_Error( 'missing_title', __( 'A non-empty "title" is required.', 'gk-block-mcp' ), array( 'status' => 400 ) ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php` around lines 745 - 754, In PatternManager::create_pattern, extract the title validation expression into a named boolean variable before the conditional, then have the if check that variable. Preserve the existing behavior: non-string titles and the empty string, including title "0" as valid, must return the same WP_Error.Source: Coding guidelines
wordpress-plugin/gk-block-mcp/tests/Abilities/AbilitiesRegistryTest.php (1)
61-65: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert the new tool name, not only the count.
The count change can remain green if
create_patternis omitted and another tool replaces it. AddassertContains( 'create_pattern', $names )to verify the new registration directly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wordpress-plugin/gk-block-mcp/tests/Abilities/AbilitiesRegistryTest.php` around lines 61 - 65, Add a direct assertion for create_pattern to the tool-name checks in AbilitiesRegistryTest, alongside the existing get_page_blocks, edit_block_tree, and site_editor_context assertions. Keep the manifest count assertion unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/client.ts`:
- Around line 397-404: Add the required `@since` 2.1.0 annotation to the JSDoc for
the new public pattern-creation method, alongside its existing documentation
tags.
- Around line 409-412: The client and MCP layers must use identical validation
and normalization for create_pattern inputs. In src/client.ts lines 409-412,
ensure empty blocks are not posted alongside valid content and reject inputs
that do not contain exactly one non-empty value; apply the same presence and
validity rules in src/tools/patterns.ts lines 103-106 before the REST call,
preserving acceptance of either valid content or non-empty blocks but never
both.
In `@src/tools/patterns.ts`:
- Around line 23-49: Update the input schema in the pattern tool to enforce the
runtime contract: require a non-blank title, require exactly one of content or
blocks, reject empty content, and require blocks to contain at least one item.
Use the schema’s supported conditional and minimum constraints while preserving
the existing BLOCK_INPUT_SCHEMA validation and optional fields.
- Around line 109-115: Validate the optional sync_status and status values
against their allowed enums, and validate slug is a string when provided, before
the client.createPattern call in the pattern handler. Reject invalid arguments
before constructing or delegating the request, then retain the existing defaults
and pass only validated values instead of relying on type assertions.
In `@wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs`:
- Around line 52805-52830: Update the inputSchema for the pattern tool to encode
validation constraints: require a non-whitespace title, and add oneOf
alternatives so exactly one of content or blocks is provided. Set content to
minLength 1, blocks to minItems 1, and preserve their existing property
definitions and descriptions.
- Around line 40266-40273: The documentation for the pattern-creation operation
incorrectly implies all patterns are synced. Update the source docblocks for
this operation, including the additional description referenced near the related
symbol, to state that it creates patterns with configurable synced or unsynced
status while preserving the existing parameter and return documentation; then
regenerate the bundled index.cjs output.
- Around line 40274-40282: Update createPattern to validate that data is a
non-null object before accessing its properties, then explicitly require
data.title to be a string before trimming or checking emptiness. Preserve the
existing validation errors for missing or invalid titles and the exactly-one-of
content/blocks rule.
In `@wordpress-plugin/gk-block-mcp/includes/abilities/tools.manifest.json`:
- Line 1192: Update the create_pattern ability permission flow: add a
create_pattern case in Abilities_Registry::check_tool_permission() that uses
check_create_pattern_permissions() and enforces the wp_block create_posts
capability, then change the manifest permission from edit_post to create_pattern
and add a contributor regression test covering the required authorization
behavior.
In `@wordpress-plugin/gk-block-mcp/includes/class-rest-controller.php`:
- Around line 1492-1499: Add an `@since` 2.2.0 annotation to the docblock for the
public REST handler create_pattern(), matching the version used by its sibling
methods and the current release context.
---
Nitpick comments:
In `@src/__tests__/tools/patterns/create_pattern.test.ts`:
- Around line 1-11: Update the create_pattern test docblock to remove the
“Unknown tool still throws” coverage claim, since this suite does not currently
dispatch an unrecognized tool through handlePatternTool. Keep the remaining
coverage descriptions unchanged.
In `@src/types.ts`:
- Around line 473-481: Update CreatePatternRequest to a discriminated union with
one branch requiring content and forbidding blocks via never, and another
requiring blocks and forbidding content via never; preserve the existing shared
fields and runtime validation for untyped callers.
In `@wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php`:
- Around line 745-754: In PatternManager::create_pattern, extract the title
validation expression into a named boolean variable before the conditional, then
have the if check that variable. Preserve the existing behavior: non-string
titles and the empty string, including title "0" as valid, must return the same
WP_Error.
In `@wordpress-plugin/gk-block-mcp/tests/Abilities/AbilitiesRegistryTest.php`:
- Around line 61-65: Add a direct assertion for create_pattern to the tool-name
checks in AbilitiesRegistryTest, alongside the existing get_page_blocks,
edit_block_tree, and site_editor_context assertions. Keep the manifest count
assertion unchanged.
In `@wordpress-plugin/gk-block-mcp/tests/REST/CreatePatternTest.php`:
- Around line 121-141: Extend the CreatePatternTest coverage with direct cases
for Pattern_Manager::create_pattern() returning each new error code:
invalid_sync_status, invalid_status, block_depth_exceeded, and invalid_block.
Add assertions that each response is a WP_Error with the expected dedicated
code, while preserving the existing XOR and legacy-tier tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b31bf996-ff70-4680-9332-1950441b3931
📒 Files selected for processing (19)
README.mdsrc/__tests__/helpers/mock-client.tssrc/__tests__/tools/patterns/create_pattern.test.tssrc/agent-guide.tssrc/client.tssrc/tools/patterns.tssrc/types.tswordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjswordpress-plugin/gk-block-mcp/gk-block-mcp.phpwordpress-plugin/gk-block-mcp/includes/abilities/tools.manifest.jsonwordpress-plugin/gk-block-mcp/includes/class-pattern-manager.phpwordpress-plugin/gk-block-mcp/includes/class-rest-controller.phpwordpress-plugin/gk-block-mcp/tests/Abilities/AbilitiesRegistryTest.phpwordpress-plugin/gk-block-mcp/tests/Block/PatternReferenceCountsTest.phpwordpress-plugin/gk-block-mcp/tests/REST/CreatePatternTest.phpwordpress-plugin/gk-block-mcp/tests/REST/RestSummaryTest.phpwordpress-plugin/gk-block-mcp/tests/REST/WriteHandlerErrorEnvelopeTest.phpwordpress-plugin/gk-block-mcp/tests/RestControllerTestCase.phpwordpress-plugin/gk-block-mcp/tests/Stress/PatternRecursionStressTest.php
| /** | ||
| * Create a synced pattern (a `wp_block` post). Exactly one of | ||
| * `content`/`blocks` is required; structured `blocks` go through the same | ||
| * registry/tier/dual-storage validation as `create_post`. | ||
| * | ||
| * @param data - Pattern title plus exactly one of content/blocks, sync_status, slug, status | ||
| * @returns The created pattern's id, slug, sync_status, edit_url, and a ready-to-insert `reference` snippet | ||
| */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add the required @since annotation.
This new public method should include @since 2.1.0 until the release version is known.
As per coding guidelines, new public production methods require @since {version} annotations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/client.ts` around lines 397 - 404, Add the required `@since` 2.1.0
annotation to the JSDoc for the new public pattern-creation method, alongside
its existing documentation tags.
Source: Coding guidelines
| async createPattern(data) { | ||
| if (!data.title || data.title.trim() === "") { | ||
| throw new Error('create_pattern: a non-empty "title" is required'); | ||
| } | ||
| const hasContent = typeof data.content === "string" && data.content !== ""; | ||
| const hasBlocks = Array.isArray(data.blocks) && data.blocks.length > 0; | ||
| if (hasContent === hasBlocks) { | ||
| throw new Error('create_pattern: provide exactly one of "content" or "blocks"'); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Guard the client method against malformed input.
A missing data object or a truthy non-string data.title causes a TypeError before the intended validation error. Validate the object and title type explicitly:
Proposed fix
- if (!data.title || data.title.trim() === "") {
+ if (!data || typeof data.title !== "string" || data.title.trim() === "") {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs` around lines 40274
- 40282, Update createPattern to validate that data is a non-null object before
accessing its properties, then explicitly require data.title to be a string
before trimming or checking emptiness. Preserve the existing validation errors
for missing or invalid titles and the exactly-one-of content/blocks rule.
| "output_schema": { | ||
| "type": "object" | ||
| }, | ||
| "permission": "edit_post", |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C 8 "check_tool_permission|permissionFor|check_create_pattern_permissions|create_pattern" \
wordpress-plugin/gk-block-mcp/includes \
wordpress-plugin/gk-block-mcp/testsRepository: GravityKit/block-mcp
Length of output: 39931
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the manifest entry and the permission switch used by the abilities registry.
sed -n '1180,1210p' wordpress-plugin/gk-block-mcp/includes/abilities/tools.manifest.json
printf '\n---\n'
sed -n '263,340p' wordpress-plugin/gk-block-mcp/includes/class-abilities-registry.php
printf '\n---\n'
sed -n '1,220p' wordpress-plugin/gk-block-mcp/includes/class-pattern-controller.phpRepository: GravityKit/block-mcp
Length of output: 3685
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -a "class-.*pattern.*\.php|.*pattern.*controller.*\.php|.*pattern.*\.php" wordpress-plugin/gk-block-mcp/includesRepository: GravityKit/block-mcp
Length of output: 242
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 4 "function check_edit_permissions|check_edit_permissions\(" wordpress-plugin/gk-block-mcp/includesRepository: GravityKit/block-mcp
Length of output: 1077
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 4 "check_edit_permissions|check_create_permissions|create_pattern" wordpress-plugin/gk-block-mcp/includesRepository: GravityKit/block-mcp
Length of output: 24706
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1136,1165p' wordpress-plugin/gk-block-mcp/includes/class-rest-controller.phpRepository: GravityKit/block-mcp
Length of output: 1109
Route create_pattern through the dedicated create-pattern permission. The manifest still gates this ability with edit_post, but POST /patterns requires check_create_pattern_permissions() and the wp_block create_posts cap. Add a create_pattern case in Abilities_Registry::check_tool_permission() and a contributor regression test.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wordpress-plugin/gk-block-mcp/includes/abilities/tools.manifest.json` at line
1192, Update the create_pattern ability permission flow: add a create_pattern
case in Abilities_Registry::check_tool_permission() that uses
check_create_pattern_permissions() and enforces the wp_block create_posts
capability, then change the manifest permission from edit_post to create_pattern
and add a contributor regression test covering the required authorization
behavior.
| /** | ||
| * POST /patterns — create a synced pattern (a `wp_block` post). | ||
| * | ||
| * @param \WP_REST_Request $request Request object. | ||
| * | ||
| * @return \WP_REST_Response|\WP_Error | ||
| */ | ||
| public function create_pattern( $request ) { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Missing @since annotation on the new public REST handler.
The sibling methods added in this same PR (check_create_pattern_permissions(), Pattern_Manager::create_pattern()) both carry @since 2.2.0; this new public handler doesn't.
As per coding guidelines, wordpress-plugin/gk-block-mcp/**/*.php: "Add @since {version} annotations to shipped public production classes, methods, hooks, and REST routes; new code uses @since 2.1.0 until the release version is known."
📝 Suggested fix
/**
* POST /patterns — create a synced pattern (a `wp_block` post).
*
+ * `@since` 2.2.0
+ *
* `@param` \WP_REST_Request $request Request object.
*
* `@return` \WP_REST_Response|\WP_Error
*/
public function create_pattern( $request ) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * POST /patterns — create a synced pattern (a `wp_block` post). | |
| * | |
| * @param \WP_REST_Request $request Request object. | |
| * | |
| * @return \WP_REST_Response|\WP_Error | |
| */ | |
| public function create_pattern( $request ) { | |
| /** | |
| * POST /patterns — create a synced pattern (a `wp_block` post). | |
| * | |
| * `@since` 2.2.0 | |
| * | |
| * `@param` \WP_REST_Request $request Request object. | |
| * | |
| * `@return` \WP_REST_Response|\WP_Error | |
| */ | |
| public function create_pattern( $request ) { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wordpress-plugin/gk-block-mcp/includes/class-rest-controller.php` around
lines 1492 - 1499, Add an `@since` 2.2.0 annotation to the docblock for the public
REST handler create_pattern(), matching the version used by its sibling methods
and the current release context.
Source: Coding guidelines
Failing-first PHPUnit coverage for POST /patterns (create_pattern), written against the not-yet-implemented REST_Controller::create_pattern() and check_create_pattern_permissions(). All 7 assertions currently error with "Call to undefined method" — confirmed red before implementation lands in the next commit(s). Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
…assthrough [red] Failing-first Vitest coverage for the not-yet-implemented create_pattern tool (handlePatternTool has no case for it yet). All 8 assertions currently fail with "Unknown pattern tool: create_pattern" — confirmed red before implementation lands in the next commit(s). Adds a createPattern mock stub to the shared mock client as test scaffolding. Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
Pattern_Manager::create_pattern() creates a wp_block post: structured blocks go through Block_CRUD::build_block_from_def() (the same registry/tier/dual-storage validation create_post uses), raw content through wp_kses_post(). sync_status defaults to synced (meta absent); 'unsynced' sets core's own wp_pattern_sync_status meta key. format_synced_pattern() now reports sync_status too. POST /patterns registers CREATABLE alongside the existing GET, gated by check_create_pattern_permissions() (edit_posts + the wp_block post type's create_posts cap, which maps to publish_posts). Pattern_Manager's constructor now takes a Block_CRUD; gk-block-mcp.php reorders construction so block_crud is built before pattern_manager. CreatePatternTest and the create_pattern Vitest suite (committed red in prior commits) now pass. Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
client.createPattern() → POST /patterns; new create_pattern tool in PATTERN_TOOLS validates title + the content/blocks XOR client-side (mirroring create_post), defaults sync_status to synced and status to publish, and forwards structured blocks via the shared BLOCK_INPUT_SCHEMA. New CreatePatternRequest/CreatePatternResponse types; Pattern gains an optional sync_status field. The create_pattern Vitest suite (committed red in a prior commit) now passes. Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
… regenerate manifest + bundle Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
0caf4dc to
d593847
Compare
…_Executor (#59) * test(abilities): list_binding_sources + create_pattern execution/gate parity [red] Writes the failing tests for BLOCK-35 first, before any production code change. Same class of gap Agent A flagged after #57/#55 merged and I fixed for the templates group in #58: both tools are already in the manifest and register as abilities (registration is manifest-driven, independent of Tool_Executor), but neither has a Tool_Executor::execute() case, so both 400 "Unknown Block MCP tool" over the Abilities/MCP-Adapter transport regardless of the caller's permissions. New tests/Abilities/BindingSourcesAbilityTest.php: registration presence, execution returns the {sources:[...]} shape, subscriber denial (read permission parity with the rest of the discovery group), readonly annotation. New tests/Abilities/CreatePatternAbilityTest.php: registration presence, execution creates a real wp_block post for an editor, subscriber denial (base edit_posts check), and the actual gate-parity bug this issue exists to fix — a Contributor (has edit_posts, lacks publish_posts) must be denied by the ability exactly as REST_Controller::check_create_pattern_permissions() denies the identical request over REST, because that permission callback checks wp_block's create_posts capability (-> publish_posts) on top of the base edit_posts check. A manifest permission of plain 'edit_post' would let this actor through the ability while REST denies them. Also pins check_create_pattern_permissions() denying the same Contributor directly, and the create-pattern annotation (not readonly, not destructive). tests/abilities-manifest.test.ts gains two assertions: list_binding_sources already maps to 'read' (confirms no TS-side change needed there — the gap is PHP-execution-only for this tool) and create_pattern must map to a new 'create_pattern' permission distinct from 'edit_post' (genuinely red). Confirmed red: $ vendor/bin/phpunit -c tests/phpunit.xml tests/Abilities/ Tests: 163, Assertions: 403, Failures: 3. (list_binding_sources execution: "Unknown Block MCP tool"; create_pattern execution: "Unknown Block MCP tool"; create_pattern Contributor-denial: got 'unknown_tool' instead of 'ability_invalid_permissions' -- the wrong 'edit_post' permission branch let the Contributor through, then execution 400'd instead of the permission callback denying it) $ npm test -- tests/abilities-manifest.test.ts Tests 1 failed | 6 passed (7) (create_pattern permission: expected 'create_pattern', received 'edit_post'; list_binding_sources permission already correctly 'read') Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3 * feat(abilities): wire list_binding_sources + create_pattern execution/gate parity [green] Makes the BLOCK-35 red tests pass. scripts/export-abilities-manifest.mjs: new CREATE_PATTERN permission bucket mapping create_pattern to a 'create_pattern' permission key (checked before the edit_post fallback). list_binding_sources needed no change -- its readOnlyHint annotation already resolved to 'read'. Regenerated tools.manifest.json (still 33 tools; only create_pattern's permission value changes). Abilities_Registry::check_tool_permission() gets a 'create_pattern' case that delegates to REST_Controller::check_create_pattern_permissions() -- the same dedicated callback POST /patterns uses, which checks edit_posts AND wp_block's create_posts capability (publish_posts). No gate logic is re-implemented. Tool_Executor::execute() gains dispatch cases + execute_list_binding_sources() (delegates to REST_Controller::get_binding_sources()) and execute_create_pattern() (delegates to REST_Controller::create_pattern(), passing input through as the JSON body -- Pattern_Manager::create_pattern() already validates title/content-blocks-XOR/sync_status/status, so no duplicate validation belongs here), both via the existing call_controller() pattern. Also fixes a bug in the red commit's own test: create_pattern's response key is `pattern_id`, not `id` (Pattern_Manager::create_pattern()'s actual return shape) -- caught by an "Undefined array key" error on the first green run, not a silent false pass. Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
What changed
New
create_patterntool creates a synced pattern (awp_blockpost) with sync-status control — the first-class path for "turn this section into a reusable pattern."Pattern_Manager::create_pattern( array $args )(wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php): validatestitleand thecontent/blocksXOR; structuredblocksgo throughBlock_CRUD::build_block_from_def()per top-level block — the same registry/tier/dual-storage validationcreate_postuses; rawcontentthroughwp_kses_post().wp_insert_post()withpost_type => 'wp_block'.sync_status: "unsynced"sets core's ownwp_pattern_sync_statuspost meta;"synced"(default) leaves it absent. Response:{pattern_id, title, slug, sync_status, edit_url, reference: {blockName:'core/block', attrs:{ref}}, warnings}.format_synced_pattern()now reportssync_statuson every synced pattern (existingGET /patternsresponses gain the field too).Block_CRUD;gk-block-mcp.php's service-graph construction is reordered soblock_crudbuilds beforepattern_manager.POST /patterns(WP_REST_Server::CREATABLE, same route as the existingGET), gated by a newcheck_create_pattern_permissions()—edit_posts(base) +wp_block'screate_postscapability (maps topublish_posts, which the agent role holds).src/client.tscreatePattern();src/tools/patterns.tsnewcreate_patterntool inPATTERN_TOOLS(schema:titlerequired,blocks/contentXOR viaBLOCK_INPUT_SCHEMAfromwrite.js,sync_statusenum defaultsynced,slug?,status?enum defaultpublish); handler validates the XOR client-side and defaultssync_status/statusbefore calling the client (mirrorsinsert_pattern's client-sidesynceddefault). NewCreatePatternRequest/CreatePatternResponsetypes;Patterngains optionalsync_status.tools.manifest.json(33 tools) +assets/mcp-server/index.cjs.Strict TDD — commit sequence is the proof
Per an explicit requirement for this issue, tests were written and committed FIRST, confirmed red, then implemented to green in later commits — see the commit list on this PR:
test(patterns): create_pattern sync-status + XOR + cap checks [red]— PHP, 7 tests, all erroring "Call to undefined method" before implementationtest(patterns): create_pattern XOR validation + dispatch + response passthrough [red]— TS, 8 tests, all failing "Unknown pattern tool: create_pattern"feat(patterns): implement create_pattern [green]— PHP implementation, same 7 tests now passtest(patterns): update Pattern_Manager call sites for the new Block_CRUD param— fixture updates for the constructor signature changefeat(patterns): create_pattern MCP tool [green]— TS implementation, same 8 tests now passtest(patterns): bump tool-count fixtures to 33 for create_patterndocs(patterns): README row + agent-guide sentence; regenerate manifest + bundleNo local squashing — this sequence is pushed as-is.
Acceptance criteria
get_post_meta(...,'wp_pattern_sync_status',true) === '')'unsynced'GET /patternswith correctsync_statuslegacy_blockerror code)publish_posts) → 403referenceGate output
Siteminter smoke
Site
blockmcp-a(Docker/Siteminter,http://localhost:8960, gk-block-mcp mounted from this worktree), real HTTP requests with WP Application Passwords, against the actual merged code:Test fixtures (posts 4/5/6, the contributor user) were deleted after verification.
Fixes BLOCK-31
https://linear.app/gravitykit/issue/BLOCK-31
https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
Summary by CodeRabbit
New Features
create_patterntool for creating reusable WordPress patterns from structured blocks or raw content.Documentation
Tests
💾 Build file (d593847).