demo · git:20261015.abdb4f7 · 2026-10-15 · sha256 8cc58f7c2a42fe5c
demo git:20261015.abdb4f7A
Immutable. This exact content never changes and is served at /api/v1/blob/8cc58f7c2a42fe5c.
---
name: demo
description: Generate a demo of a web feature developed in this session — a page of test cases grouped by feature, each with its precondition, its own screen recording and its steps with screenshots, published as an artifact, driven by playwright-cli against the app the skill starts and stops itself. Use from dev-workflow's Update Rules gate (demo / no-demo) or standalone with /demo.
allowed-tools: Read, Write, Edit, Glob, Grep, Agent, Artifact, AskUserQuestion, Bash(playwright-cli *), Bash(node *), Bash(mkdir -p .demo/*), Bash(git check-ignore *), Bash(git diff *), Bash(git ls-files *), Bash(lsof -ti tcp:*), Bash(kill *), Bash(DEMO_PORT=* pnpm *), Bash(DEMO_PORT=* npm *), Bash(DEMO_PORT=* yarn *), Bash(DEMO_PORT=* bun *), Bash(DEMO_PORT=* make *), Bash(DEMO_PORT=* bundle exec *), Bash(DEMO_PORT=* rails *), Bash(DEMO_PORT=* python *), Bash(DEMO_PORT=* uv run *), Bash(DEMO_PORT=* docker compose *), Bash(DEMO_PORT=* go run *), Bash(DEMO_PORT=* cargo run *), Bash(DEMO_PORT=* node *)
---
# demo
```text
/demo [--plan <path>] [--base <ref>] [--lang ja|en] [--model <id>]
/demo --init
```
`--init` runs Setup mode only: it proposes and writes the settings, then stops with the return contract (`status: "ok"`, no capture). When a settings file already exists it stops with `status: "skipped"`, `reason: "settings exist"` and never overwrites. Without `--init`, a run that finds no settings file enters Setup mode by itself and continues into the demo.
Invocation contract, as fields in the invocation text (a field counts as provided when its value is non-empty): `Plan:` (the plan document the feature was built from; optional), `Base ref:` (git ref the diff is taken against; default `HEAD`), `Language:` (optional, see § Language), `Model:` (optional, see § Model). The command-line flags map onto the same fields.
Everything the run produces goes under `.demo/<slug>/` at the repository root: the screenshots, one recording per test case, saved values under `values/`, `steps.json`, `server.log` and `index.html`. The directory is meant to be gitignored and is never committed by this skill. Outside it, only Setup mode and § Procedure step 9 (Data guide gate) write.
## No-Stall Principle
The only paths that do not reach § Return contract with `status: "ok"`, each ending with the contract's JSON block: `playwright-cli --help video-chapter` exiting non-zero (`playwright-cli not installed`); a settings file that exists but cannot be read (`settings unreadable`); no `start_command` after the merge (`start_command unset`); `cancel` at the Setup gate (`setup declined`); `--init` with a settings file present (`settings exist`); `.claude/demo.md` appearing between the Setup gate and its write (`setup conflict`); neither a readable plan nor a diff (`nothing to demo`); `skip` at the start-failure gate (`dev server not ready`); `skip` at the step-table gate (`declined at step table`); `stop` at the publish gate (`declined at publish gate`); on the first capture, the capture agent's second unparseable reply (`no parseable capture result`) or a result that fails the schema check (`capture schema violation`); the renderer rejecting `steps.json` (`page rejected`). Once the app has been started, every one of these runs § Procedure step 7 (Stop the app) before emitting the block.
The capture agent's reply is a return value: judge it and issue the next tool call at once. A later capture (a re-capture, or a capture for the publish gate's `revise`) is one more dispatch within § Procedure step 6 (Capture); its result is judged like the first capture's, except that a second unparseable reply or a schema violation keeps the earlier results instead of stopping. A capture with `failed` steps is not a stopping point. The data check's browser is closed before the step-table gate on every path. Non-fatal outcomes are recorded and the run continues: a step marked `failed`, a later capture's (a re-capture's or a publish-gate `revise`'s) second unparseable reply or schema violation, a missing recording, a missing or failing `Artifact` tool, `.demo/` or `.playwright-cli/` not gitignored, a process still listening after the stop, a `changed-files.txt` that cannot be written (the run goes on without it), a `stop_command` that exits non-zero or times out, a start process that was not stopped because `<url>` is remote and `stop_command` is unset, a seed or fixture file the data guide names that cannot be read (the guide counts as silent on it), an `Edit` at § Procedure step 9 (Data guide gate) that fails.
## Settings
Two files: `.claude/demo.md` (shared by the team, committed) and `.claude/demo.local.md` (personal, gitignored). Each holds YAML frontmatter and, after its closing `---`, an optional Markdown body. For the keys, the local file overrides the shared one key by key; a `null` or empty value in the local file clears the key.
The bodies, the shared one followed by the local one, read as one text: the **data guide**, prose in the resolved language saying where the seed or fixture files are, which accounts (identifier and role) and which main records they hold, and how one shell command line inserts a record the seed lacks. The shared body names where a credential comes from (a seed file, the local body, `auth_state`); a credential itself, or a personal insertion command, goes only in the local body. Where the guide is silent, the run finds the fixtures or seed itself. `.claude/demo.md` is the guide's write target, `.claude/demo.local.md` when it alone exists.
| Key | Default | Meaning |
|---|---|---|
| `start_command` | required | Starts the app, in the background, with the environment below. A local app listens on `DEMO_PORT`; a remote target names its URL in `url` |
| `reset_command` | none | Runs once, in the foreground, before `start_command`, with the same environment. Resets a throwaway environment |
| `stop_command` | none | Runs once, in the foreground, at the start of § Procedure step 7 (Stop the app), with the same environment. Stops or cleans up what `start_command` created; the port kill follows. Bounded by `ready_timeout` seconds |
| `url` | none | Starting with `http://` or `https://`: the URL itself, with `${VAR}` expanded from the environment plus `DEMO_PORT`. Otherwise a command the wait re-runs, with the same environment, until it exits zero with a non-empty stdout line, within `ready_timeout`; that first non-empty line is the URL |
| `ready_timeout` | `120` | Seconds to wait for the URL to resolve and answer, at most 590 |
| `port` | none | When set, used as `DEMO_PORT` instead of a free port found at run time. Digits, or `${VAR}` expanding to digits: the port itself. Otherwise a command run before the start with the same environment and `DEMO_PORT=0`; the first line of its stdout is the port |
| `auth_state` | none | Storage state file passed to `playwright-cli state-load` after the browser opens |
| `error_cases` | `true` | `false`: derive no error test cases. Any other value is `true` |
| `video` | `full` | `normal`: error test cases are not recorded; their screenshots are still taken. Any other value is `full` |
**Environment.** Every command a key names runs as `DEMO_PORT=<port> DEMO_BASE_REF=<base> DEMO_CHANGED_FILES=<changed files> <command>` (the `port` command with `DEMO_PORT=0`). `<base>` is `Base ref` as resolved in § Procedure step 2 (Inputs); `<changed files>` is `.demo/<slug>/changed-files.txt`, one path per line, written at the start of § Procedure step 4 (Start the app). That prefix is `<environment>` in § Procedure.
When neither file exists, or `--init` was given, read `references/setup-mode.md` and follow it. When a file exists but `Read` fails for any other reason, or when it cannot be told whether the file is missing or unreadable, stop with `status: "error"`, `reason: "settings unreadable: <path>"` rather than entering Setup mode. When the merge leaves no `start_command`, stop with `status: "error"`, `reason: "start_command unset"`.
## Language
`Language:` or `--lang`, when given, is the language and nothing is read. Otherwise resolve it by reading only the `language` key from the YAML frontmatter of `~/.claude/dev-workflow.local.md`, `.claude/dev-workflow.md` and `.claude/dev-workflow.local.md`, in that order, later files overriding earlier ones; a `null` or empty value clears it, and a missing file, or one without frontmatter, contributes nothing. If none sets it, take `language` from `~/.claude/settings.json`; otherwise use `ja`. The page's fixed labels, the step table and every line written for the user follow the resolved language.
## Model
`Model:` or `--model` is applied as the `model` parameter of the capture `Agent` dispatch. It is valid only if it is one of the ids the current `Agent` tool's `model` parameter accepts; check the tool's live schema in this session. A full `claude-*` id is not among its aliases and is invalid. An absent or invalid value is treated as `sonnet`.
## Dispatch authorization
This skill's procedure dispatches subagents, so invoking the skill **is** the request to use that mechanism: an ambient instruction allowing subagent dispatch only when the user asked for it — a **permission-shaped restriction** — is already satisfied by this invocation. Do not ask the user to re-confirm the dispatch, and do not silently substitute inline execution for a dispatch this procedure specifies. Only two things justify that substitution: **technical availability** (the dispatch tool is not present and callable on the current tool surface), and an **explicit contract term from the caller** bounding this skill to its own thread. A permission-shaped restriction is neither.
## Procedure
`<base dir>` is this skill's directory as the harness reported it when the skill loaded.
1. **Preconditions.** Run `playwright-cli --help video-chapter`. A non-zero exit stops with `status: "error"`, `reason: "playwright-cli not installed"`. When its output starts with `playwright-cli video-chapter`, `<chapters>` = `true`; otherwise `<chapters>` = `false` and say in one line that this version records without title cards, cursor or action callouts. Then resolve the language per § Language and the model per § Model, then the settings per § Settings (Setup mode when neither file exists).
2. **Inputs.** With `Plan:`, `<slug>` is the file name without `.md`; `Read` the plan. When that `Read` fails, use the plan text when it is already in this conversation; otherwise say in one line that the steps come from the diff alone and continue. Without `Plan:`, `<slug>` is `<YYYYMMDD-HHMMSS>` of now. Read the change set against `Base ref` (default `HEAD`): `git diff <base>` and the content of files `git ls-files --others --exclude-standard` lists. No readable plan and an empty change set → `status: "error"`, `reason: "nothing to demo"`. Run `mkdir -p .demo/<slug>`.
3. **Derive the test cases.** From the data guide (read with the settings in step 1), `Read` the parts of the seed or fixture files it names that name accounts and records; when it names none, find them as `references/setup-mode.md` § Read the project does. Then, from the plan's After, Scope and Test plan and from the diff:
- **Features.** List the behaviors a person would check on screen, then group them into features. A feature is the set of actions a checker performs in a row for one purpose; it does not follow the count of tasks or commits. Group when the actions handle the same data for the same purpose, or when one action uses what an earlier one created or changed. Split when the purpose differs, when a part can be checked on its own, or when a different role performs it. When unsure, group. A single feature takes the same shape. Each feature gets a letter (`A` for the first, `B` for the second, and so on), a name stating what the user can do and a one- or two-sentence description.
- **Test cases.** Per feature, each test case has a title saying in one sentence what it checks, a precondition (the sign-in state and the data it needs), the `setup` actions that bring a fresh browser to that precondition, and its steps. A case's id is the feature letter plus its order within the feature (`A-1`, `A-2`). Normal cases: the feature's main flow, usually one case (more when the main flow branches); 3–10 normal steps across all features.
- **Error cases**, when `error_cases` is `true`. One test case per branch the diff implements (input validation, permissions, behavior that depends on state), each branch once; steps that prepare the branch belong to that case. Errors the diff does not implement (raised by the framework or by shared code) are not walked. There is no cap; when the error cases exceed 10, the gate shows their count and a time estimate (20–40 seconds per case) in one line. The expected result is the error text itself.
- **Preconditions.** Every case starts in a fresh, signed-out browser. A case that needs a signed-in user gets there through `auth_state` when it is set, otherwise through `setup` actions that sign in on the sign-in page with an account from the fixtures or seed as the data guide names them. Only when the diff changes sign-in itself (the code that creates or checks a session, the authentication settings, the account fields used to sign in, the sign-in page) does signing in become steps. A case that relies on data an earlier case created says so in its precondition, and the case order follows from it. An error case uses records that earlier cases do not change or delete; when they would, its data names a record of its own. A value that can be taken only once (a confirmation code consumed on use) is taken by the first step that needs it, with a script whose step `data` says `save as <name>`; each later step that needs it says `use <name>` in its `data`.
- **Order and numbers.** Every normal case, feature by feature, then every error case, feature by feature. A step's `n` runs through all cases; screenshots are named after it.
- **Each step** holds `n`, `case` (its case's id), the page path, the action (one imperative sentence), the expected result (one sentence), and the data it needs — covered by the project's fixtures or seed as the data guide names them, created on screen by an earlier step, or inserted by a script written here in the way the data guide says, in that order of preference. A script's full text goes into the step table so the gate shows it. A step that creates a record on screen (a sign-up, a new item) fills its unique fields with values carrying `<YYYYMMDD-HHMMSS>` of now (for example an email address), so a second capture does not collide with the first. Nothing is cleaned up afterwards.
4. **Start the app.** `Write` `.demo/<slug>/changed-files.txt`: the paths of the change set read in step 2 (Inputs), the changed files then the untracked ones, one per line, literal (an empty file when there are none). `<port>` is the `port` key resolved as § Settings says (a command's first stdout line must be an integer from 1 to 65535; a non-zero exit or any other line goes to the start-failure gate below, which then shows the command's output) or, when unset, the output of
`node -e 'const s=require("net").createServer();s.listen(0,()=>{console.log(s.address().port);s.close()})'`.
Say in one line which `reset_command` and `start_command` are about to run. When `reset_command` is set, run `<environment> <reset_command>` in the foreground; a non-zero exit goes to the start-failure gate below, which then shows the reset output instead of `server.log`. Run `<environment> <start_command> > .demo/<slug>/server.log 2>&1` with `run_in_background: true`. Resolve the URL and wait for it in one call:
- `<environment> node "<base dir>/scripts/wait-ready.mjs" --timeout <ready_timeout>` with one of the two options below, passing `timeout: (ready_timeout + 10) * 1000` on that Bash call. Any HTTP response counts as ready.
- `--url "<value>"` when the `url` key is a URL (its value expanded as § Settings says); when it is unset, the first `http://` or `https://` URL in `server.log`, else `http://localhost:<port>`.
- `--url-command '<value>'` when the `url` key is a command, in single quotes so the shell does not expand `$DEMO_PORT` before the script runs; a `'` inside the value becomes `'\''`.
- stdout `ready <url>` → `<url>` is that value. stdout `timeout` → the start-failure gate, which then also shows the script's stderr. Anything else (exit 2, a stdout of neither form, node itself failing) → the start-failure gate, which then shows the script's stdout and stderr.
- `<base url>` is `<url>` without query or fragment, with `/` appended unless it already ends with `/`. A path's URL is `<base url>` followed by the path without its leading `/`.
The **start-failure gate** (USER GATE): show the last 20 lines of `server.log` and `<port>`, and ask `retry` / `skip`. `retry` runs step 7 (Stop the app), then this step again from `reset_command`; there is no retry limit. `skip` runs step 7 and stops with `status: "skipped"`, `reason: "dev server not ready"`.
**Data check.** Before any page is opened, a record the steps rely on that the seed or fixture files the data guide names, or another persistent store the project keeps (an exported dataset), do not hold is a gap. Then, once the URL answers, in a separate browser (`playwright-cli -s=demo-<slug>-check`), once per distinct `setup` (no `setup` counts as one): `open`, `state-load <auth_state>` when set, perform that `setup`, then `goto` the URL of each distinct path of the cases that use it (read the snapshot it prints inline, or from the `.playwright-cli/` file it names), then `close`. A `setup` that fails is noted on every case that uses it. A page that shows an error, a not-found, or an empty list where its step needs records is a gap too. For each gap, write an insertion script into the earliest step that needs the data, as in step 3. A page that fails for a reason data cannot explain is noted on its step for the gate.
5. **Step-table gate** (USER GATE). Show `<url>`, whether `reset_command` ran, the features (letter, name, description), the test cases (id, kind, title, precondition, `setup`), the step table (number, case, path, action, expected result, data), the error-case count line when there is one, the `video` value, every data script in full, and what the data check changed or noted, in the resolved language. Say in one sentence that `revise` can drop error cases and that `error_cases: false` leaves them out altogether. Ask `approve` / `revise` / `skip`, and in the same question set ask whether a test case that fails for missing data or for how its steps were written may be corrected — its scripts, its `setup` or its actions — and captured again, at most twice (`yes` / `no`); keep that answer as `<recapture>`. `revise` takes the user's corrections to the URL, the cases, the steps, the data or a script, applies them and shows the tables again. `skip` runs step 7 and stops with `status: "skipped"`, `reason: "declined at step table"`. After `approve`, the only questions left are the publish gate in step 6, only when failures remain, and step 9 (Data guide gate), only when it has something to offer.
6. **Capture.** `<session>` = `demo-<slug>`. Before each dispatch, delete the `.demo/<slug>/<id>.webm` of every case it captures, in one `node -e` with `fs.rmSync(path, { force: true })`. Dispatch one `Agent` (`subagent_type: general-purpose`, `model` per § Model, `run_in_background: false`); it opens and closes a fresh browser and records each case itself. The prompt is three fenced sections: `--- TASK ---` holds the text of `references/capture-prompt.md` § Capture prompt after its `---` rule, verbatim, with `{{session}}`, `{{out_dir}}` (`.demo/<slug>`), `{{url}}` (`<base url>`), `{{chapters}}`, `{{video}}`, `{{auth_state}}` (`auth_state`, or `none`) and `{{language}}` filled in; `--- STEPS ---` holds the cases to capture as one JSON object `{ "cases": [ { "id", "kind", "title", "precondition", "setup" } ], "steps": [ { "n", "case", "path", "action", "expected", "data", "script" } ] }`; `--- RETURN ---` holds one sentence: the reply ends with a single fenced JSON block and nothing after it.
**Pre-invocation reminder**: the agent's reply is a return value, not a turn boundary. The next tool call after it returns is `playwright-cli -s=<session> close`, issued by this same thread. See § No-Stall Principle.
When the agent returns: run `playwright-cli -s=<session> close` (its output is ignored). Take the last fenced JSON block of the reply and parse it. No block, or a block that does not parse → dispatch once more with the sentence `Your previous reply carried no parseable JSON block.` appended to `--- RETURN ---`, then `close` again. A second failure on the first capture runs step 7 and stops with `status: "error"`, `reason: "no parseable capture result"`; on a later capture it is handled as **Later captures** says. Schema check, before anything is written: `steps` is an array holding each `n` of the steps passed in this dispatch exactly once; each entry has a `status` of `ok` or `failed`, a non-empty string `screenshot` (a bare `step-NN.png` file name; a path is reduced to its base name), a non-empty string `result`, an optional string `note`, and, on a `failed` entry, an optional `cause` of `data`, `steps` or `app`. A violation on the first capture runs step 7 and stops with `status: "error"`, `reason: "capture schema violation"`; on a later capture it is handled as **Later captures** says.
**Return-point no-stall reminder**: regardless of outcome — every step `ok`, some `failed`, any non-error result — the next action, `playwright-cli -s=<session> close`, is issued in the next tool call. After the first capture and after a re-capture, the re-capture decision follows, or the publish gate when **Later captures** sends the run there; after a capture for the publish gate's `revise`, the publish gate follows. See § No-Stall Principle.
**Later captures.** A later capture dispatches only the given cases and their steps, together with every case they rely on for data, with the app still running and no `reset_command`. Before it, the `<YYYYMMDD-HHMMSS>` in those cases' unique values is replaced with the current time. Its results replace those steps' earlier ones. A second unparseable reply or a schema violation keeps those cases' earlier results instead, and the run goes on to the publish gate.
**Re-capture.** `<recapture count>` is `0` on the first entry to this step. When `<recapture>` is `yes`, `<recapture count>` is below `2`, and a step is `failed` with `cause` `data` or `steps`: correct each case holding such a step — `data`: append an insertion script to the step's `script`; `steps`: read the step's `note` and the last 40 lines of `server.log`, then correct the case's `setup`, its actions, or the saved values it uses. Run a later capture of the corrected cases. When it returns a result that passes the schema check, add `1` to `<recapture count>` and decide again. When no re-capture is due (`<recapture>` is `no`, no `data` or `steps` failure is left, or `<recapture count>` is `2`), go to the publish gate.
**Publish gate** (USER GATE). When every step is `ok`, skip it and go to step 7 (Stop the app). Otherwise show, per test case with a failure, its id and title and each failed step's `n`, action, expected result, result, note, cause in words (missing data / how the steps were written / how the app behaved, in the resolved language) and screenshot path, and ask `publish` / `revise` / `stop`. `publish` goes to step 7 with the results as they are; the page shows those steps as not confirmed. `revise` takes the user's corrections to those cases, runs a later capture of only those cases, which does not count toward `<recapture count>`, and returns to this gate; there is no limit. `stop` runs step 7 and stops with `status: "skipped"`, `reason: "declined at publish gate"`.
7. **Stop the app.** Full stop sequence (applies to every case): when `stop_command` is set, say in one line that it runs, then run `<environment> <stop_command>` in the foreground, passing `timeout: (ready_timeout + 10) * 1000`; a non-zero exit or a timeout keeps one line for the report, `stop_command exited <code>: <reason>` or `stop_command timed out: <reason>`, where `<reason>` is the last non-empty stderr line cut to 80 chars, or `(no stderr)`, and the stop goes on. Then read the PIDs from `lsof -ti tcp:<stop port>`; when there are none, skip the kill. Otherwise run `kill <pids>`. Wait with `node -e "setTimeout(()=>{},2000)"`, read `lsof -ti tcp:<stop port>` again and `kill -9` what is left. If a PID still listens after that, keep one line for the report. The lines kept are those of the last run of this step. The three cases differ only in `<stop port>` and in one report line:
- `<url>` is not resolved (the wait returned no `ready`): `<stop port>` is `<port>`; no further line.
- `<url>`'s host is `localhost`, `127.0.0.1` or `::1`: `<stop port>` is the port named in `<url>`; no further line.
- any other host (a remote target): `<stop port>` is `<port>`. When `stop_command` is unset and the kill found no PID, keep one line for the report saying the start process was not stopped; otherwise keep no such line.
8. **Render and publish.** `Write` `.demo/<slug>/steps.json`: `title` (the change in two to four words), `dek` (the plan's After sentence, or one sentence from the diff), `url`, `features` (each approved feature that still has a case, with its `letter`, `name` and `description`), `cases` (each approved case's `id`, `feature` as its feature's position in that list, `kind`, `title` and `precondition`), and `steps` (each approved step with its `case` id, merged with the agent's `status`, `result`, `note` and `screenshot`). A case gets `video: "<id>.webm"` when that file exists under `.demo/<slug>/` and is 15MB or smaller; check every case in one `node -e` that lists each `.webm` with its size. Run
`node "<base dir>/scripts/render.mjs" --steps .demo/<slug>/steps.json --out .demo/<slug>/index.html --lang <language>`.
A non-zero exit stops with `status: "error"`, `reason: "page rejected: <first stderr line>"` (the app is already stopped). Call `Artifact` with `file_path` `.demo/<slug>/index.html`, `files` mapping each `step-NN.png` and each case video named in `steps.json` to its file under `.demo/<slug>/`, `icon` `camera`, and `description` one sentence naming the feature. When the files together exceed 64MB, publish again to the same URL with the remaining videos. Do not load the design skills: the page's layout and theming are fixed in the renderer. A missing or failing `Artifact` leaves `url` null; the page path still reports. Run `git check-ignore .demo .playwright-cli`; when its output lacks either path, keep one line for the report asking to add `.demo/` and `.playwright-cli/` to `.gitignore` (this skill does not edit it outside Setup mode).
9. **Data guide gate.** `<edited>` is `[]` on entry. Build two candidates. `<guide additions>`: for each item § Settings lists for the data guide, what this run used that the guide does not state yet, one or two lines each, in the resolved language, without this run's own values (the timestamps, the records this run created); a line with a credential-like value (a password, a token, a key) carries where the value comes from instead of the value. `<seed candidates>`: the records the scripts of the normal cases' `ok` steps inserted, only when the guide names a seed file (the first, when it names several); draft the addition in that file's own idiom as a diff, from the seed file as read in step 3 (`Read` it when step 3 did not). When both candidates are empty, show no gate and go to step 10 (Report). Otherwise (USER GATE), in one question set, show the lines to append verbatim and ask `yes` / `no`, and show the seed diff and ask `apply` / `decline`, each only when its candidate exists. `yes` appends the lines at the end of the guide's write target with `Edit`. `apply` applies the diff to the seed file with `Edit`. Nothing is committed. A failing `Edit` is noted for the report and the run continues. `<edited>` holds the paths whose `Edit` succeeded.
10. **Report.** In the resolved language: the artifact URL (or the page path), the test case count and how many test cases are not confirmed, one line per test case with its id, title and confirmed / not confirmed, how many re-captures ran, and for each case they corrected, every script added in full and its `setup` and actions as captured, the publish gate's answer when it was shown, each recording over 15MB left off the page, the stop result (the `stop_command` line when one was kept, and whether a process still listens), the gitignore line when there is one, the guide lines appended or that they were declined, and the seed file edited with a note that its change is uncommitted, or that it was declined. Then the return contract's JSON block.
## Return contract
Emit a single fenced JSON block at the end of the response, matching:
```json
{ "status": "ok" | "skipped" | "error", "reason": null | "<why>", "url": null | "<artifact url>", "page": null | "<path to index.html>", "cases": <n>, "steps": <n>, "failed": <n>, "edited": [ "<path>", ... ] }
```
`cases`, `steps` and `failed` are `0` when the capture did not run. `failed` counts steps. `edited` is always present: the paths § Procedure step 9 (Data guide gate) edited, `[]` on every path that does not reach it (Setup mode's own writes are reported by Setup mode, not here). Only one fenced JSON block appears in the response.
## Sub-skill caller directive
When invoked as a sub-skill (i.e. via `Skill(demo)` from an orchestrator), the fenced JSON verdict block this skill emits is the **structured return value** of the skill's procedure — it is **not** a deliverable to the user, and emitting it does **not** terminate the orchestrator's turn. The same agent that ran this skill must immediately issue the next tool call dictated by the orchestrator's flow. Do not insert a prose summary, an acknowledgment, or a "shall I proceed?" sentence between the JSON verdict and the next tool call. Only one fenced JSON block — the verdict block — appears in the response, so callers can locate it unambiguously. The skill's own procedure is over; the orchestrator's procedure continues without pause.
## Fallback
When the `Agent` tool is not on the tool surface, this thread runs the flow of `references/capture-prompt.md` itself, builds the same `steps` array, and continues at the schema check.