Skip to content

feat(agents): add playground-verifier, the kit's first runtime gate - #3

Merged
pluginslab merged 1 commit into
mainfrom
feat/playground-verifier
Sep 21, 2026
Merged

pluginslab merged 1 commit into
mainfrom
feat/playground-verifier

Conversation

@pluginslab

Copy link
Copy Markdown
Owner

Why

Every gate in this kit is static. phpcs reads syntax, security-reviewer reads patterns, plan-reviewer reads markdown. Nothing boots WordPress.

That gap has a receipt. From the v1.0.2 changelog, fixing a scaffold that fataled the instant it was activated:

"None of the static gates caught it, because nothing in quality.sh boots WordPress; it only surfaced under a live wp-playground activation."

It passed phpcs. It passed the security review. It shipped in two releases.

What

playground-verifier — read-only plus the wp-playground MCP tools, no Edit, no Write. It boots the plugin in an ephemeral Playground instance at the WP and PHP versions the plugin's own header declares, mounts the working tree, then activates, reads the error log, confirms every register_rest_route call resolves at runtime, checks mutating routes reject unauthenticated requests, deactivates, uninstalls, and verifies nothing is left behind.

Design decisions worth reviewing:

  • Declared minimums, not latest. A plugin claiming Requires at least: 6.7 that calls a 6.8 function only fails on 6.7. Testing on latest hides exactly the bug the header exists to prevent.
  • Mounts rather than installs from a zip, so the thing under test is the thing on disk.
  • Routes verified at runtime, not by grep. A route hooked too late exists in source and 404s in practice.
  • Always tears the instance down. Only one runs at a time, so a leak breaks the next run.

Corrected against two live runs before shipping

This is the part I'd most want a second opinion on. Dispatching it at the kit's own pl-example template found 13 defects in its own playbook, two of which would have produced false passes:

  1. get_playground_logs does not surface PHP errors. It reports the Node process's output. The original playbook made it the primary evidence channel. In the first run it returned one npm warning while a real PHP Fatal error landed in wp-content/debug.log, invisible. An agent following those instructions literally would report "no errors" on a fataling plugin.
  2. The unauthenticated-REST check could not run. Playground's auto-login mu-plugin 302-redirects every cookie-less request; curl dies at exit 47. A Critical criterion that silently proved nothing.

A second run found a third: WP_DEBUG_DISPLAY is false by default, so the "notices in the page body" check was structurally incapable of failing.

The playbook now names debug.log as the evidence channel, requires a deliberate trigger_error probe to prove logging is on before an empty log means anything, carries working recipes for the commands the MCP bridge doesn't implement, and confirms booted versions from inside the instance — the CLI banner reported PHP 8.3 / WP latest for an instance serving 8.2.33 / 6.7.7.

It also gained one deliberate carve-out from "never edit": VFS-only scratch copies at non-mounted paths, with a mandatory git status --porcelain check afterwards. Forbidding it absolutely made the agent weaker for no safety gain — it's what let the agent prove the v1.0.2 autoload fix is real rather than masked by a Composer classmap.

Tests

tests/agents/test-agent-contracts.sh, 43 assertions. The read-only guarantee lives in each agent's tools: allowlist, not its prompt body — a prompt saying "you do not modify code" is advisory, an omitted Edit is enforced. These assert the structural half, and pin the three false-pass traps so a future edit that drops the debug.log warning or the auto-login recipe reintroduces a gate that cannot fail.

Verified by negative control: a fixture agent declaring Edit turns the suite red on exactly that assertion.

Suite: 56 → 99 assertions. ./scripts/quality.sh green.

Follow-up

A stacked PR fixes the two template bugs this agent found (vendor/autoload.php fataling on a fresh clone, and the PL_Example_Plugin substitution gap).

🤖 Generated with Claude Code

Every gate in this kit is static: phpcs reads syntax, security-reviewer
reads patterns, plan-reviewer reads markdown. Nothing boots WordPress.

That gap has a receipt. v1.0.2 fixed a scaffold that fataled the instant
it was activated, and the note said it plainly: "None of the static gates
caught it, because nothing in quality.sh boots WordPress." It passed
phpcs, passed the security review, and shipped in two releases.

playground-verifier boots the plugin in an ephemeral Playground instance
at the WP and PHP versions the plugin's own header declares, mounts the
working tree, activates, reads the error log, confirms every registered
REST route resolves at runtime, deactivates, uninstalls, and checks
nothing is left behind. Read-only plus the wp-playground MCP tools.

The definition was corrected against two live runs before shipping. The
first surfaced 13 defects in its own playbook, two of which would have
produced false passes: get_playground_logs surfaces the Node process's
output and NOT PHP errors, and the unauthenticated-REST check could not
run at all because Playground's auto-login mu-plugin redirects every
cookie-less request. The second found a third: WP_DEBUG_DISPLAY is false
by default, so the "notices in the page body" check was a no-op that
looked like a pass.

Contract tests pin those traps so a future edit that drops the debug.log
warning or the auto-login recipe reintroduces a gate that cannot fail.
The read-only guarantee is asserted structurally too: a prompt saying
"you do not modify code" is advisory, an omitted Edit is enforced.

Suite: 56 -> 99 assertions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pluginslab
pluginslab force-pushed the feat/playground-verifier branch from 73fad7c to c0d99a8 Compare September 21, 2026 16:40
@pluginslab
pluginslab merged commit f3f964a into main Sep 21, 2026
1 check passed
@pluginslab
pluginslab deleted the feat/playground-verifier branch September 21, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant