Repository context. Gather first
Collect these with individual Bash calls, one command per call, never combined into a single invocation:
- Current branch,
git branch --show-current - Working tree status (empty = clean),
git status --porcelain | head -10
The pipe is the bound and belongs in the command. A read-time cap ("read only the first 10 entries") bounds nothing: the Bash tool returns the command's complete output into context before there is anything to decide about.
Treat a failure (not a repository, git unavailable) as an unknown value and carry on. Keep these as separate body Bash calls rather than pre-compute lines: the harness runs a skill's whole pre-compute block as one shell invocation, and a worktree-isolated session refuses a compound command that contains git.
Pre-computed context
Project ecosystems: !${CLAUDE_SKILL_DIR}/scripts/detect-ecosystems.sh 2>/dev/null || echo "none detected"
Variables
Arguments: $ARGUMENTS
Purpose
A disposable interactive shell driving a state model by hand, a terminal app by default, a shareable HTML demo when the audience calls for it. Use when the question is about business logic, state transitions, or data shape. Things that look reasonable on paper but only feel wrong once pushed through real cases.
The shared throwaway rules, the auto-invoke gate, and how to capture the answer live in
${CLAUDE_PLUGIN_ROOT}/context/discipline.md. Read it before you
start. This file covers only the logic facet.
If the question is "what should this look like". Wrong facet. Invoke /prototype:explore-directions via the Skill tool.
When this is the right shape
- "Does this state machine handle the edge case where X then Y?"
- "Does this data model represent the case where...?"
- "What should the API surface look like before writing it?"
- Anything where pressing buttons and watching state change reveals the answer
Two shells. TUI default
Two disposable shells can front the same portable logic module (step 3 below), the terminal app (the default) and a self-contained HTML demo. Both run the same process; only the shell swaps. Route by audience:
- The driver is a developer with a terminal → the TUI (the default below).
- The driver is a non-developer, a designer, PM, or domain expert pressing the buttons, or no terminal fits the handoff → the HTML demo shell (next section): one file, nothing to install, opened by double-click.
- Explicit override wins both ways. Ask for a TUI or an HTML demo directly and that beats the default routing.
HTML demo shell
When the audience routing selects it, the shell is one self-contained file:// HTML page over the
same pure logic module, the module lives in a single inline <script> block written so it could
be lifted out unchanged; the page calls into it, and nothing flows the other direction. It
replaces steps 4–5 below; the rest of the process holds (in step 6, the run command is the page's
file:// path).
Lay the page out top to bottom, in domain language. Every button and field reads like the business, not the reducer, because the driver is not reading code:
- Title and one-line explanation, the question from step 1, visible on the page.
- State panel, the full relevant state as a readable labelled panel (not a raw JSON dump), re-rendered after every click so the change is visible.
- Free-play buttons. One button per action, always available, so the driver can poke at the model in any order.
- Guided walkthroughs, a few scenarios worth demonstrating: the happy path, a tricky edge case, an attempt at something that should be illegal. Each is a short plain-language description plus the ordered buttons to press; starting a walkthrough resets to a known initial state so the scenario runs the same way every time.
- Validation answer set, the questions this demo exists to answer, each as a forced choice with a small authored option set, every option naming its cost in plain language (what picking it gives up), plus a free-text escape hatch for the answer the options missed. The driver's picks are the demo's real output; pair the set with a copy-out control that lifts the filled answers back out as text to paste into the session.
- Fake-data disclosure footer, one visible line stating the page is synthetic end to end, that nothing on it reads from or writes to the real app, and, when decided, where the real wiring lives (or will live) and behind which flag; when integration is not yet decided, or no flag is planned, the footer says so explicitly rather than inventing production details. The driver is not reading code; the footer is what keeps a convincing mock from being mistaken for the wired feature.
Constraints (the same set as explore-directions' HTML mockup substrate):
- Synthetic data only. A throwaway prototype binds synthetic data, never real or captured values.
- No remote fetch by construction. Vendor everything inline so the page opens straight from
file://. No external scripts, fonts, or data fetches. Enforce this rather than trusting it: emit a restrictive CSP meta tag in the page<head>so the browser blocks any remote resource:<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:">. Inline<style>and inline<script>stay allowed (the logic module and the button handlers need inline script); only remote origins, CDNs, web fonts,fetch/XHR, are forbidden. - Ephemeral placement. Generate the page via the platform's temp primitive, never a tracked
path and never inside the repo. On Unix/Linux/Git Bash, create a private run directory and
write the page inside it, echoing the directory in the same call:
d=$(mktemp -d "${TMPDIR:-/tmp}/pressure-test-XXXXXX"); echo "$d", then write to<echoed dir>/pressure-test.html. Echo it because shell state does not survive between Bash calls. Keep the temp root in the positional template with theXXXXXXtrailing, and give the page a fixed name inside the generated directory, the mktemp dialect traps behind those two rules (GNU vs BSD-p/-tdivergence, trailing-X-only substitution on BSD/macOS) are unpacked inexplore-directions's HTML mockup substrate section; the same rules apply verbatim here. On Windows, a user-scoped temp under%LOCALAPPDATA%\Temp. One file per run. The path is handed to the user to open fromfile://, so do not delete it. It must still be readable when they open it. - Markdown captures the answer. Copy what the demo taught into your durable answer (per the shared discipline), then discard the page like any other shell, the validated logic module is the only artifact that outlives the prototype (lifted into production per step 7).
Process
1. State the question
Write down what state model and what question you're prototyping. One paragraph, comment at top of file. A logic prototype answering the wrong question is pure waste. Make the question explicit.
2. Pick the language
Use whatever the host project uses. Match existing conventions for tooling. Don't add a new runtime for a prototype.
3. Isolate logic in a portable module
Put the logic, the bit answering the question, behind a small, pure interface that could be lifted into the real codebase later. The shell around it (TUI or HTML page) is throwaway; the logic module should not be.
Pick the shape that fits the question:
| Shape | When |
|---|---|
Pure reducer. (state, action) => state |
Actions are discrete events, state is a single value |
| State machine. Explicit states and transitions | "Which actions are even legal right now" is part of the question |
| Pure functions over a plain data type | No implicit current state. Just transformations |
| Class/module with clear method surface | Logic genuinely owns ongoing internal state |
Keep it pure: no I/O, no terminal code, no DOM access, no console output for control flow. The shell imports and calls into the logic module; nothing flows the other direction.
This is what makes the prototype useful past its own lifetime. When the question's been answered, the validated module can be lifted into production code, the shell gets deleted.
4. Build the smallest TUI that exposes state
(TUI default. When the audience routing selected the HTML demo shell, that section replaces this step and step 5.)
On every tick, clear the screen and re-render the whole frame. The user sees one stable view, not growing scrollback.
Each frame has two parts:
- Current state. Pretty-printed, one field per line or formatted output. Bold field names, dim less-important context (timestamps, IDs, derived values).
- Actions. Listed at bottom:
[a] add item [d] delete item [t] tick clock [q] quit.
Behavior loop:
- Initialize state, a single in-memory object. Render the first frame on start.
- Read one keystroke (or one line), dispatch to a handler that mutates state via the logic module.
- Re-render the full frame after every action. Replace, don't append.
- Loop until quit.
The whole frame should fit on one screen.
5. Make it runnable in one command
Add a script to the project's existing task runner. The user runs the equivalent of
dotnet run --project <path> or pnpm run <name>, never needs to remember a file path.
If there's no task runner, put the command at the top of a prototype README.
6. Hand it over
Give the user the run command (for the HTML demo shell, the page's file:// path: send them the
file or open it for them). Interesting moments, "wait, that shouldn't be possible" or "huh, I
assumed X would be different", are bugs in the IDEA. Add new actions as the user requests them.
Prototypes evolve.
7. Capture the answer
When done, capture what the prototype taught (per the shared discipline). For the HTML demo shell, carry the filled validation answer set into the durable answer verbatim: the chosen option per question, with the cost the driver accepted, is the record of what was actually decided. The logic module behind the shell is often worth keeping; the shell, TUI or HTML page, is not: lift the validated module into production and delete the shell.
Next
- The model holds and the logic module is ready to lift:
/implementation:implement. - The prototype invalidates the model:
/planning:design.
Anti-patterns
- Adding tests. A prototype needing tests is no longer a prototype.
- Wiring to a real database. In-memory store unless the question IS about persistence.
- Generalizing. No "what if we wanted to support X later."
- Blurring logic and shell. If the reducer/state machine references console output, terminal codes, or the DOM, it's no longer portable. The shell (TUI or page) is a thin layer over a pure module.
- Shipping the shell to production. The shell, TUI or HTML page, is optimized for hand-driving. The logic module behind it is the reusable bit.
- Reaching for a framework, bundler, or server in the HTML demo shell. One file the driver double-clicks; a dev server defeats "nothing to install".