| name | demo |
| description | The canonical forge demo capability — how forge runs a demo, what every demo must contain, and how the demo maps to the forge UI review page. A demo makes ONE behavioural delta (prior → new) visible, grounded in the initiative's acceptance criteria. The demo phase agent (skills/demo-agent, R4-07) composes this skill to author the structured demo.json and derive DEMO.md. The demo agent reads the project's generated demo skill (from skills/demo-design) to know what evidence to produce; this skill defines the contract that evidence must satisfy. Scale demo effort to the size of the change. |
| library | true |
| phase | developer-loop |
| surface | unattended |
| model | claude-sonnet-4-6 |
Demo — the forge demo capability (the consumer contract)
What this skill is
This is the single source of truth for the demo contract between forge and a
managed project. The review phase's job is to show the operator what changed
so they can make the merge decision; the demo is that artefact. This skill owns:
- What a demo must contain (the contract below).
- How forge runs a demo — read the project's generated demo skill, author
demo.json, derive DEMO.md.
- How the demo maps to the forge UI — the same
demo.json the review
screen renders (DemoComparison), so the PR artefact and the UI never drift.
The project-side demo machinery is generated by skills/demo-design when
the operator configures demoProcess in Studio. The generated skill (at
<artifactRoot>/skills/<slug>/SKILL.md in the project repo) tells the unifier
what evidence to produce. This skill defines the contract that evidence satisfies.
It supersedes the split that used to exist across skills/demo (a Playwright-spec
author), skills/demo-capture (media back-fill), the inline unifier prompt, and
ADR 016. The structured
artefact is the contract from ADR 021.
Required first action
- Load the project's generated demo skill — it is listed in the project's
skills array in .forge/project.json. Read it; it names the evidence form
and the exact commands to run.
- Invoke
brain-query against the project's brain before authoring:
- "How does this project demonstrate a change — test harness, UI, live API?"
- "Are there demo/recording conventions or gotchas for this project?"
The demo contract (what EVERY demo must contain)
A demo is one structured artefact: <demo-dir>/demo.json
(schema = DemoModel in cli/demo-model.ts,
validated by the pr_self_contained gate). The demo dir is resolved against the
project's artifactRoot: it is demo/<initiative-id>/ for a default-layout
project (artifactRoot: "."), or <artifactRoot>/history/<initiative-id>/demo/
when the project gathers its committed artifacts under a sub-root (e.g.
betterado's forge/, giving forge/history/<initiative-id>/demo/). The
iteration brief (PROMPT.md) names the exact dir — write there; forge demo render <initiative-id> resolves the same path from artifactRoot, so you never
pass --dir. It must carry:
Required core fields
title — the one-line essence of the change.
essence — 2–3 sentences: what behaviour changed and why it matters, framed
prior → new. State any non-visual delta here.
project, initiativeId, diffStat (git diff --stat main...HEAD).
checkpoints[] — ≥1, each { label, caption, kind?, beforeNote?, afterNote?, command?, beforeOutput?, afterOutput?, metrics?, beforeImage?, afterImage? }. Each
checkpoint shows ONE before→after pair, grounded in an acceptance criterion or a
concrete diff change.
- Prefer REAL captured output over a prose note. For a CLI tool, set
command to
the exact argv whose stdout IS the evidence (e.g. node dist/cli.js churn .); leave
beforeOutput/afterOutput empty — the orchestrator runs forge demo capture
after the agent's iteration (ADR 036), executing the command on main (before) AND
the branch HEAD (after) and back-filling the REAL terminal output, rendered
side-by-side. The agent authors WHAT to capture; forge produces the evidence —
hand-written beforeOutput/afterOutput for a command checkpoint is overwritten by
the orchestrator's own run. The operator wants to SEE the actual output, not a
description of it. Use beforeNote/afterNote only as a one-line caption, or when
no command can show the change (a pure type/internal refactor).
Rich structured sections (strongly recommended — prevents wall-of-text)
The operator's #1 complaint is demos that are walls of text or bare diffs.
Always author these sections when the diff warrants them. The renderer
collapses them gracefully when absent, but their absence means a less useful demo.
-
summary — { bullets: string[], prUrl?, commitSha?, branch? }.
3–5 concise bullets: what changed, why it matters, what the operator should
look for. Include the PR URL, branch, and commit SHA when available — these
appear in the header band of the rendered DEMO.md.
"summary": {
"bullets": [
"Adds a dark-mode toggle that follows the OS preference by default.",
"Persists the choice in localStorage so it survives page reload.",
"No behaviour change when OS preference is light."
],
"prUrl": "https://github.com/org/repo/pull/42",
"branch": "forge/INIT-2026-05-30-dark-mode",
"commitSha": "a1b2c3d"
}
-
apiDiff[] — Array of { name, change: "added"|"changed"|"removed", before?, after? }.
Each entry is one changed endpoint, function signature, config key, CLI flag, or
protocol message. before/after are the exact JSON shape or text — the renderer
syntax-highlights them. Include this section whenever the diff touches a public
API surface, a function signature, or an observable output format.
"apiDiff": [
{
"name": "GET /api/nodes/:id",
"change":
Three load-bearing disciplines
- One behavioural delta, not a tour. Pick the single delta that is the essence
of the initiative. A tight 3–5 checkpoint scenario beats an exhaustive walk.
- Baseline is a valid prior state, never "broken". Frame every checkpoint as
"prior behaviour → new behaviour", never "error → fixed". If the new feature did
not exist before, the baseline legitimately shows the prior screen/output —
describe it as prior behaviour in
beforeNote.
- Never fabricate a visual for a non-visual change. An internal refactor, an
API change, a type change has no screenshot. State the delta in
essence; do
not invent an on-screen overlay or re-shoot an unrelated screen. Use apiDiff
to show the before/after of the API surface instead.
How forge runs a demo
- Read the project's generated demo skill (from
<artifactRoot>/skills/<slug>/SKILL.md
in the project repo). Follow its evidence instructions — it names what commands
to run, what to capture, and what the evidence floor is.
- Author the demo.json at the dir named in PROMPT.md to the contract above.
Populate ALL applicable rich sections —
summary, apiDiff, testEvidence,
filesChanged — so the rendered DEMO.md is genuinely informative.
- Derive the artefact:
Bash forge demo render <initiative-id> → writes
DEMO.md from the JSON. Never hand-write DEMO.md — it is derived, so
the PR artefact and the UI render stay identical.
- Real before/after evidence is captured by the ORCHESTRATOR (ADR 036 —
orchestrator-owned gate execution). After the agent's iteration, forge itself
spawns
forge demo capture <initiative-id> in the worktree: for checkpoints
with a command, it captures the REAL stdout on main vs the branch HEAD
into beforeOutput/afterOutput; for browser checkpoints, screenshots. It
re-renders DEMO.md and commits the result. The agent must NOT run
forge demo capture and must never hand-write beforeOutput/afterOutput
— the orchestrator's run overwrites them. Environment failures (timeout /
non-zero exit) stay best-effort (recorded as a unifier.demo-capture event,
gate proceeds), with two N2 hardenings (plan item 2.6):
- Producibility: every checkpoint
command must be EXECUTABLE in the
project (binary on PATH / worktree path / defined npm script) — checked
by the orchestrator BEFORE the capture spawn. An unrunnable command
fails the pr_self_contained gate with the exact problem; fix the
command, don't paper over it with prose.
- Nonce binding: the orchestrator injects a per-run nonce
(
FORGE_CAPTURE_NONCE) into the capture child's environment — the agent
never sees it. A capture run that completes stamps it into demo.json as
capture: { nonce, capturedAt }, and the gate verifies the stamp:
evidence without this run's nonce (stale, replayed, or hand-written) is
rejected. Never author the capture field by hand — it cannot carry the
right nonce.
- Commit + (the bundle is born tracked — no
shadow).
Effort tiers — scale the demo to the change (load-bearing)
A trivial change must not trigger a heavyweight demo. This is a real cost sink
(observed: ~$11 / 15 unifier iterations packaging a one-file test change). Match
the effort to the diff:
| Diff shape | Demo effort |
|---|
| Trivial (≤~2 files, no behavioural surface — a test add, a constant, a doc) | Notes-only. One checkpoint, essence + beforeNote/afterNote. No media capture — a demo with no command/screenshot checkpoints makes the orchestrator skip its capture run entirely. |
| Standard (a feature or fix with a clear behavioural delta) | Full checkpoints grounded in ACs; summary + apiDiff + testEvidence when applicable; metrics if measurable; media only if the generated skill specifies screenshots. |
| Large / multi-feature | A checkpoint per distinct delta (still tight per checkpoint); summary bullets for each sub-change; apiDiff for each changed surface; a metrics table for harness evidence. |
When in doubt, prefer notes-only and let the operator ask for more. Media is an
enhancement, never a gate. Do author summary + apiDiff + testEvidence even
for notes-only demos when the diff touches a visible API or adds tests — these
sections make the rendered DEMO.md genuinely useful without requiring media capture.
Evidence forms (what the generated demo skill specifies)
The generated project demo skill (from skills/demo-design) specifies one of
these evidence forms based on what the project's code actually exposes:
- Portal/browser screenshot — a rendered UI. Author checkpoints; the
orchestrator's
forge demo capture run back-fills before/after screenshots.
kind: "screenshot" for a settled UI state; kind: "video" for
time-dependent behaviour.
- Harness metrics — behaviour measurable at the test layer. Run the project's
measurement command against baseline AND HEAD, scrape stable result lines,
encode them as a
kind: "harness" checkpoint with
metrics: [{ label, before, after, deltaPct, parity }]. Reuse the project's
test — never re-derive the measurement. Pair with testEvidence rows.
Parity vocabulary: match (exact same result), within (within tolerance),
diverged (regression), incomplete (no baseline available).
- Live external API round-trip — the change stands up a REAL resource in a
live external system. The evidence floor is a real REST round-trip, not a
test-name table: provision the resource, read it back via the system's API,
and persist that GET under
.forge/live-evidence/<label>.json; forge demo render
back-fills it into a checkpoint carrying liveEvidence.url. The demo MUST end
with such a checkpoint. Pair with testEvidence (the live acceptance result)
and, for a new capability, usage_example + impact. When credentials are
absent, fall back to notes-only and document the fallback in essence —
never fabricate the live read-back.
- JSON-diff / notes-only — no UI surface, no measurement command, no live
external calls. A single checkpoint whose caption +
afterNote is a rationale
block ("what changed and why it's correct"). Use summary bullets for the
rationale. apiDiff to show any changed API/config surface.
Media capture (the optional, best-effort step — orchestrator-run)
For projects with a renderable UI, after demo.json checkpoints are authored,
the orchestrator runs (ADR 036 — never the agent):
forge demo capture <initiative-id>
It materialises two git worktrees (baseline main vs changed HEAD), builds +
serves each, takes ONE screenshot per checkpoint label →
before/<label>.png + after/<label>.png (the capture engine calls
playwright screenshot per label — no agent-authored spec, no full test run),
then merges the captured images into demo.json checkpoints as inline data:
URIs (size-capped to 1.5 MB each) and re-renders DEMO.md.
Discipline:
- Best-effort, never fatal. No buildable app / missing Playwright / capture
error → logs and exits 0; the demo stays notes-only. Never blocks the gate.
- No remote media. Only inline
data: URIs are merged (validateDemoModel
rejects scheme-bearing refs). Video refs are relative sibling paths only.
- Does not re-author content. It fills only
beforeImage/afterImage —
never captions, notes, metrics, or the diffstat.
- Serve the built tree, not a stray dev server. A reused dev server latches
onto the main repo's build and captures stale screenshots (trafficGame
lesson). The capture engine builds + serves each worktree on its own port.
- Events:
demo-capture.start, demo-capture.merged, demo-capture.skipped.
How the demo maps to the forge UI page (the presentation half)
The structured demo.json is both the PR artefact (via the derived DEMO.md)
and the UI artefact: the review screen renders the same DemoModel natively via
DemoComparison.tsx. The schema is the template — what you put in demo.json
is exactly what the operator sees, so:
- The header band shows: title, cycle id, branch, commit SHA, PR link.
summary bullets appear in a dedicated Summary section above the checkpoints.
- Each checkpoint becomes a
CheckpointCard (caption + before/after media or
notes + a metrics table for harness evidence).
apiDiff renders as syntax-highlighted before/after code blocks.
testEvidence renders as a pass/fail table with coverage deltas.
filesChanged renders as an annotated file list with the diffstat collapsible.
- The screen mirrors structured state to
data-* (data-section="demo-comparison",
per-checkpoint data-checkpoint / data-checkpoint-kind) per the forge-ui
DOM-as-metrics convention.
snapshotCycleArtefacts copies demo.json + DEMO.md into
_logs/<cycleId>/artifacts/ for the bridge to serve.
If you change the demo's structure, change DemoComparison + its data-*
alongside it — the schema, the renderer, and the UI are one contract.
The project-side requirements (what makes a project demo-able)
For onboarding (the forge-onboard-project skill + forge preflight), a project
must have:
- A
demoProcess in .forge/project.json with ≥1 capture and ≥1 verify
step — validated by forge preflight DEMO clause.
- Generated demo machinery — the
skills/demo-design skill generates
<artifactRoot>/skills/<slug>/SKILL.md into the project repo; forge preflight
DEMO clause passes once this is present.
- A truthful test gate (contract C1) — the same gate that proves the change
is what the demo shows.
- A valid prior state — running the baseline must not error; the demo frames
it as prior behaviour.
Done when
demo/<initiative-id>/demo.json validates, DEMO.md is derived (not
hand-written) and committed alongside it, the effort matches the diff size,
every checkpoint is grounded in an AC or a real diff change and framed prior→new,
the summary / apiDiff / testEvidence / filesChanged sections are authored
where the diff warrants them, and (for visual projects) media capture has been
attempted best-effort.
Two faces: executed demo vs builder deliverable
The typed demoProcess descriptor (.forge/project.json) is the single shared
declaration of what a demo must capture/verify/present — but it has two married
faces, not two competing contracts (docs/forge-project-contract.md §DEMO).
Face A is the executed demo this skill defines: demo.json, composed at
cycle time by the demo agent (skills/demo-agent, R4-07). Face B is the demo-builder's per-initiative
HTML skill (skills/demo-builder, R1-03-F2): save-time authoring/preview of
what a demo could look like, not a cycle-time artefact. The descriptor is
shared; the deliverables intentionally differ.