Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions dev/test/fixtures/skill-activation.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,55 @@
"Update WordPress core and all installed plugins.",
"Back up the WordPress database.",
"Configure redirects in the WordPress admin."
],
"routes": [
{
"prompt": "Invent a native Gutenberg feature section for this WordPress page.",
"route": "assemble",
"firstReference": "references/ASSEMBLE.md",
"artifact": "page post_content"
},
{
"prompt": "Convert this authored landing-page HTML and its CSS into editable WordPress blocks.",
"route": "convert",
"firstReference": "references/GUIDE.md#4-convert--someone-elses-html-into-page-post_content",
"artifact": "page post_content"
},
{
"prompt": "Create a static named Gutenberg block in source code for this existing project.",
"route": "author",
"firstReference": "references/AUTHORING.md",
"artifact": "registered block source"
},
{
"prompt": "Implement a custom PHP renderer and editor control in my existing WordPress plugin.",
"route": "project-owned",
"firstReference": "references/GUIDE.md#11-continue-project-owned-development-when-static-generation-does-not-fit",
"artifact": "project-owned implementation"
},
{
"prompt": "Repair this supplied Gutenberg markup before it is saved.",
"route": "validate-fix-validate",
"firstReference": "references/GUIDE.md#5-the-pre-flight-loop--before-page-markup-is-saved",
"artifact": "validated page post_content"
},
{
"prompt": "Convert the rendered frontend HTML I scraped into authored WordPress content.",
"route": "exclude",
"firstReference": null,
"artifact": "none"
},
{
"prompt": "Create a reusable WordPress section.",
"route": "clarify-artifact",
"firstReference": "references/GUIDE.md#0-understand-the-project-before-choosing-output",
"artifact": "choose page post_content or registered block source before writing"
},
{
"prompt": "Configure redirects in the WordPress admin.",
"route": "exclude",
"firstReference": null,
"artifact": "none"
}
]
}
39 changes: 34 additions & 5 deletions dev/test/skill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,45 @@ describe('canonical agent skill', () => {
expect(guide).toMatch(/custom PHP renderer or custom editor\s+behaviour/);
});

it('keeps a balanced activation regression set', async () => {
it('keeps activation coverage and explicit artifact routes', async () => {
const fixture = JSON.parse(
await readFile(new URL('./fixtures/skill-activation.json', import.meta.url), 'utf8'),
) as { shouldTrigger: string[]; shouldNotTrigger: string[] };
) as {
shouldTrigger: string[];
shouldNotTrigger: string[];
routes: Array<{
prompt: string;
route: 'assemble' | 'convert' | 'author' | 'project-owned' | 'validate-fix-validate' | 'exclude' | 'clarify-artifact';
firstReference: string | null;
artifact: string;
}>;
};

expect(fixture.shouldTrigger).toHaveLength(9);
expect(fixture.shouldNotTrigger).toHaveLength(8);
expect(new Set([...fixture.shouldTrigger, ...fixture.shouldNotTrigger]).size).toBe(17);
expect(fixture.shouldTrigger.length).toBeGreaterThan(0);
expect(fixture.shouldNotTrigger.length).toBeGreaterThan(0);
expect(new Set([...fixture.shouldTrigger, ...fixture.shouldNotTrigger]).size).toBe(
fixture.shouldTrigger.length + fixture.shouldNotTrigger.length,
);
expect(fixture.shouldTrigger.every((prompt) => /WordPress|Gutenberg|block/i.test(prompt))).toBe(true);
expect(fixture.shouldNotTrigger.some((prompt) => /non-WordPress|not use WordPress/i.test(prompt))).toBe(true);
expect(fixture.shouldTrigger).toContain('Create a reusable named Gutenberg block in my existing WordPress plugin.');

const routeNames = new Set(fixture.routes.map((entry) => entry.route));
expect(routeNames).toEqual(
new Set(['assemble', 'convert', 'author', 'project-owned', 'validate-fix-validate', 'exclude', 'clarify-artifact']),
);
expect(fixture.routes.every((entry) => entry.prompt.length > 0 && entry.artifact.length > 0)).toBe(true);
expect(
fixture.routes
.filter((entry) => entry.route !== 'exclude')
.every((entry) => entry.firstReference?.startsWith('references/')),
).toBe(true);
expect(fixture.routes.filter((entry) => entry.route === 'exclude').every((entry) => entry.firstReference === null)).toBe(true);
expect(fixture.routes.find((entry) => entry.route === 'clarify-artifact')?.artifact).toContain('before writing');

const skill = await readFile(new URL('../../skills/block-runner/SKILL.md', import.meta.url), 'utf8');
for (const reference of fixture.routes.flatMap((entry) => (entry.firstReference ? [entry.firstReference] : []))) {
expect(skill).toContain(reference.split('#', 1)[0]!);
}
});
});
52 changes: 25 additions & 27 deletions skills/block-runner/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ description: >-
Turn WordPress page content or authored design HTML into valid, native, editable Gutenberg blocks,
or plan a reusable named registered block for deterministic source generation. Use when creating
WordPress page content or sections, converting authored HTML or a design-tool export into block
markup, authoring a reusable named block in an existing WordPress project, understanding its
block integration before generation, validating or repairing Gutenberg
markup, authoring a reusable named block in an existing WordPress project, continuing an
in-scope Gutenberg component through custom PHP or editor code, understanding its block
integration before generation, validating or repairing Gutenberg
markup, or before writing blocks to WordPress. Do not use for general WordPress administration,
unrelated plugin or theme code, frontend-scraped HTML, or non-WordPress HTML.
license: GPL-2.0-or-later
Expand All @@ -14,36 +15,33 @@ compatibility: Requires Node.js ^20.19.0 || ^22.13.0 || >=24.0.0 and shell acces

# Block Runner

## Start with the user's project
## Pick the artifact first

For a component that belongs in an existing project, read `references/GUIDE.md` §0 before
choosing an output, and `references/CONSTRUCTION-PATTERNS.md` to choose an implementation route
and inspect its contracts. Use relevant source and site context to recommend reuse, supported
generation or a developer handoff. Recognizing a route does not add generator support. Ask only
for choices that the request and repository do not answer; use structured questions if your
harness supports them, otherwise ask in plain language. Skip discovery for a self-contained
markup check or when the relevant project facts are already established.
Choose the visible result before discovering a project. Read the named first reference, use the
condition, and stop at its stated boundary. Do not load a construction taxonomy for a
self-contained page-content or markup-repair task.

## The short version
| Requested artifact | First reference | Use when | Completion boundary |
| --- | --- | --- | --- |
| Native page or post structure | [ASSEMBLE.md](references/ASSEMBLE.md), then [GUIDE.md §5–6](references/GUIDE.md) | You are inventing a page section and have no authored HTML whose styling must survive. Run `npx -y block-runner@latest assemble - --json` with an intent tree. | Valid native page `post_content`, delivered to the agreed destination. |
| Page or post content from authored design HTML | [GUIDE.md §4](references/GUIDE.md) | The supplied source is authored HTML; use `npx -y block-runner@latest convert - --json` when its CSS matters. | Valid native page `post_content` plus reported fallbacks and styling limits. Never treat frontend-scraped rendered HTML as authored input. |
| Reusable, named static registered block source | [AUTHORING.md](references/AUTHORING.md) | The requested result is a static `namespace/slug` block in code. | Confirmed source at its exact retained destination; build, integration, and WordPress proof remain separate. The complete plan in [AUTHORING-PLAN.md](references/AUTHORING-PLAN.md) is an advanced route, not a prerequisite. |
| Custom PHP renderer or editor behaviour in an existing project | [GUIDE.md §1.1](references/GUIDE.md) | Static generation cannot satisfy the requested component. | Continue in project-owned code; retain a useful native subtree only when it genuinely helps. Inspect [CONSTRUCTION-PATTERNS.md](references/CONSTRUCTION-PATTERNS.md) only when project facts needed for that implementation are missing. |
| Supplied block markup to repair or check | [GUIDE.md §5](references/GUIDE.md) | Markup already exists and must be safe to save. | `validate` → `fix` → `validate`; do not save a hard-invalid result. |

Pick by the requested artifact:
If “reusable section” does not say whether it is page `post_content` or a named registered source
block, ask that one artifact question before writing. Do not use this skill for unrelated WordPress
administration, non-WordPress HTML, or frontend-scraped HTML presented as source.

- **You need a reusable, named static registered block in code** → read `references/AUTHORING.md`.
For authored HTML, submit a declarative `AuthorOptions.proposal`, then run `author preview`, show
its literal tree and warnings, obtain confirmation for its exact hash, and run `author write`.
The deterministic generator writes executable source; follow the agreed source-only or plugin
delivery route and name the proof it has not established. The complete `AuthorOptions.plan` is an
advanced compatibility route.
## Discover only missing project facts

- **You are inventing the structure** → do not write HTML. Emit an intent tree (JSON
describing which blocks and how they nest) and pipe it to
`npx -y block-runner@latest assemble - --json`. Deterministic code builds the markup, so it
cannot come out invalid. This is the best path and the one to reach for by default.
- **You have authored source HTML** → `npx -y block-runner@latest convert - --json`. The only
path that carries CSS; use it when the styling matters (`--styling relaxed` is the default).
Its result is page `post_content`, not a reusable source package.
- **You have block markup to check** → `validate` → `fix` → `validate`. Never save markup that
is still invalid after `fix`.
For a component that belongs in an existing project, use [GUIDE.md §0](references/GUIDE.md) after
the artifact route is clear. Reuse established facts and inspect only the relevant source and site
context needed for this change. Existing-plugin integration needs inspection; a self-contained
paragraph repair does not. Use relevant facts to recommend reuse, supported generation or a
developer handoff. Recognizing a route does not add generator support. Ask only for choices that
the request and repository do not answer; use structured questions if your harness supports them,
otherwise ask in plain language.

## Generator boundary and continuation

Expand Down
Loading