surfaces

This skill should be used when end to end or visual regression has to be set up for the current project, for example "set up e2e", "set up visual regression", "register a surface", "remove a surface",

SKILL.md

GitHub

Surfaces

Set up the surfaces review runs: end to end and visual regression. The framework decides how, and that answer lives in a recipe outside this plugin. Setup takes no task folder, the way the tool skill does. With no task active, it runs at the code path. Run from inside a task's worktree, which is outside the code path, it runs there instead, so the setup ships with the task's branch. The review skill runs the surfaces; this skill only sets them up. The guards live in the script: --confirmed and decline refuse unattended. This skill waits for a plain yes before install. So a stage's offer may invoke it.

Every call below runs surfaces-actions.sh, named in this skill's own grant, so it runs without asking. Read the exit code first, never the text alone. Exit 1 means no project owns this directory: say so, name the project skill, and stop.

Determine the run mode

Read the active task's run mode. A task that states none is interactive, and so is a call with no task active. Pass --run-mode autonomous on every call only when the task states it, or states light. Decide this once, at the start. A mode that names stages in brackets covers this call only when it names the stage this call runs inside. Exit 70 means a person's answer was passed with nobody present.

Read the state first

"${CLAUDE_PLUGIN_ROOT}"/skills/surfaces/scripts/surfaces-actions.sh read

It prints the project, the surfaces field, and the surface file with its state. It prints one line per surface, and a version 5 registry.yml when one sits beside the file. That registry is left in place. Its ids and URLs are candidates for discovery, below.

When the surface file is missing, read prints surface-branch:, the branch that holds the file, or none. When it names a branch, offer no setup, and install refuses. A new file would collide with that branch's file. Say the file reaches this tree once that branch lands on the trunk. If that branch is abandoned, deleting it, local and remote, lets setup run.

A stage's offer names the kind in its invocation. Run by hand with no kind, ask which kind after read, naming each kind's state, and then work that kind only.

A command as the end to end surface

A project with no page to drive, such as a command-line tool, has no setup recipe for e2e. Its end to end surface is a command. It needs no recipe and no install:

"${CLAUDE_PLUGIN_ROOT}"/skills/surfaces/scripts/surfaces-actions.sh --run-mode <mode> register <id> --command "<line>" --kind e2e --reason "<why>" --critical --enable

The line is the command that walks the path, such as a script in the code repository. Exit 0 means the path works. The line is split at spaces and runs without a shell. So a quote or a shell character is refused at 3. --reason is required, as for every e2e surface below. With no surface file, register writes one and turns end to end on in the project record, and commits both. Review runs the command in the task's tree, and nobody walks it. Exit 3 also means the file holds an e2e page surface already, because a project uses one form.

The command runs with the session's PATH. So it must reach this tree's own build, never an installed copy, for example through a path inside the tree. Register it before the build starts: on the trunk before tasks are cut, or in the first task's tree. A register during review moves the code under review, and review refuses that. A task cut before the trunk had the file cannot run it. Review reads a command unknown when the range changed it, or deleted its files, or changed their mode.

Resolve the recipes

Run this once, with one --point flag per point and one --framework flag per framework the project records:

"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh recipe-point "<project folder>" --point <phase> --framework <framework>

The points are e2e-setup for the e2e kind, visual-regression for the visual-regression kind, and review when a baseline is taken. A point is a point of AIDA's process, never a keyword search. Pass each answer= in its own word, one flag per framework. A path on disk is --recipe <framework>=<path>. A failed lookup is --lookup-failed <framework>=<word>, with no-recipe, listing-unreachable or fetch-failed. Only the first word says anything about the framework. Never retype a command out of a recipe. The script reads the blocks.

Before install or baseline, invoke the tool skill with require <path> for each recipe path. Its section "Tools a recipe names" says what each answer means. It always exits 0. Name each tool it reads absent or unknown, and go on.

Show, then install

"${CLAUDE_PLUGIN_ROOT}"/skills/surfaces/scripts/surfaces-actions.sh show <kind> <recipe flags>
"${CLAUDE_PLUGIN_ROOT}"/skills/surfaces/scripts/surfaces-actions.sh --run-mode <mode> install <kind> <recipe flags>

show prints the recipe, its commands, the files it writes, its viewports and its seed surfaces, and runs nothing. A recipe with no install block exits 3 from show too, so the exit code says what install would do. Interactive: run show first, print the commands and the files, and wait for a plain yes before install. Autonomous: halt here and say the install needs a person, because an install changes the project and nobody is there to approve it.

install runs every command in order and writes each file only when absent. It writes the surface file when absent, and turns the kind on in the project record. It runs again safely. Other uncommitted work in the tree prints a warning: line naming it. The commit takes only the files this install changed, with the reason in the message, the way baseline does. A file already changed before the install stays out. It commits the project record too.

Exit code Meaning What to do
0 Every step ran, or not-applicable: no framework has a recipe. Nothing more for this kind. Name templates/process-recipe-setup.md in this plugin as the shape a catalog recipe follows.
3 A recipe with no install block, a refused command, a file present with content no version of the recipe declares, or unknown: nobody looked. Show the text and stop. It names the recipe or the file, which is where the fix belongs.
4 A step failed. The first: line quotes its first line of output. Show it; do not install by hand.

--viewport <name>=<w>x<h> replaces the recipe's viewport list. Pass it only when a person gave it. A surface file another branch holds prints a warning: line, and the install goes on.

Discover the surfaces, then a person confirms

After install, open the discovery step through the script and follow it:

"${CLAUDE_PLUGIN_ROOT}"/skills/surfaces/scripts/surfaces-actions.sh step discover

A Read rule naming the plugin root does not reliably expand, which is why the step is read this way. Discovery ends with one register call per surface the person confirmed:

"${CLAUDE_PLUGIN_ROOT}"/skills/surfaces/scripts/surfaces-actions.sh --run-mode <mode> register <id> --url <url> --kind <kind>... [--mask <css>]... [--path <glob>]... [--critical] [--reason "<why>"] --enable

An end to end test is added on purpose, so a surface with the e2e kind needs --reason. The reason is the person's words: the core journey it walks, or the behaviour no lower test can reach. With no reason, register refuses at 3 and writes nothing. The surface file keeps the reason. Pass --enable only for a surface the person confirmed. Each --path is a glob, relative to the code tree, naming files that render the surface. Review runs the surface only when the diff touches one of them. --critical makes it run on every review. A surface with no --path runs on every review too. Each register commits the surface file alone, so other uncommitted work in the tree neither stops it nor goes into the commit. Exit 3 names an id already registered with different fields, or says install has not run.

Remove a surface

"${CLAUDE_PLUGIN_ROOT}"/skills/surfaces/scripts/surfaces-actions.sh unregister <id>

Removes one surface from the surface file and commits it. Run it only when a person decided the surface goes, such as at test cleanup when a lower test covers it. The baselines of that surface stay on disk. It commits the surface file alone. Exit 3 means no surface has that id, or there is no surface file.

Take the first baselines

"${CLAUDE_PLUGIN_ROOT}"/skills/surfaces/scripts/surfaces-actions.sh --run-mode <mode> baseline [<id>]... --check-recipe <framework>=<path> [--value base-url=<address>]

Without --confirmed it prints the plan: the ids given, or every enabled visual regression surface, at every viewport. Show the plan and take the person's yes. Then run it again with --confirmed. It runs the review recipe's accept row over those ids only, and commits the baselines with the reason in the message. Never write a baseline unattended, and never by hand.

Ask for the base URL once, interactive, and pass it as --value base-url=<address>. Nothing stores it. When the task record has environment.address, pass that as --value base-url= and do not ask. Exit 3 means the recipe carries no accept row. Other uncommitted work prints a warning: line, and the commit takes only the files the accept row wrote.

Decline

"${CLAUDE_PLUGIN_ROOT}"/skills/surfaces/scripts/surfaces-actions.sh decline <kind>

Records that the person declined that kind's setup. Every stage's offer, at scope, design and review, reads it and does not ask again for that kind. It is a person's answer, so it refuses unattended at 70. No kind, or an unknown one, refuses at 3, naming e2e and visual-regression. It commits the project record too.

What this skill never does

It never edits a recipe, and it never runs a command a recipe supplied through a shell.