scenario-design
SkillMediaDraft real-life test SCENARIOS (not smoke tests) from a change/feature spec. Derives edge-case, performance, frontend-quirk and error-handling scenarios with ISTQB techniques, routes each to a test level, and writes test-plan.md, emitting clarification questions on a spec gap. Use on "design test scenarios", "what should we test", "build a test plan", "find edge cases".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the scenario-design skill
What this skill tells your AI
The instructions your AI receives, as published by blackbelttechnology/pi-agent-dashboard in packages/eng-disciplines/.pi/skills/scenario-design/SKILL.md and read by ahel’s review.
Turn a change spec into adversarial, real-life test scenarios — designed to break the system, not confirm it works. A scenario is only as good as it is concrete and executable. If the spec can't supply the concrete bits, that is a spec defect, surfaced as a clarification — not a guess.
Input: A change/feature spec. --change <name> (OpenSpec), or infer from
context / point the skill at any spec doc.
Mode: --stage proposal|design|apply (default: infer — see Gate).
Output: a standalone test-plan.md catalog written to your change/spec's
test-plan location (OpenSpec: openspec/changes/<name>/test-plan.md).
Core mechanism — the Triple
Every scenario MUST resolve three concrete slots:
┌─────────────┬──────────────────────┬────────────────────────────┐
│ INPUT │ TRIGGER │ EXPECTED OBSERVABLE OUTCOME │
│ concrete │ the condition / │ a measurable, visible fact │
│ data / │ action that fires │ (status, value, latency, │
│ state │ the behaviour │ DOM, log line, exit code) │
└─────────────┴──────────────────────┴────────────────────────────┘
Rule (from spec-coding edge-case practice): if any slot is a verb without a noun, or an adjective instead of a number, the slot is unfillable → spec gap. "Handles errors gracefully" is not a Triple. "POST /api/restart while server already restarting (input) → second caller (trigger) → receives 409 within 500ms, no second orchestrator spawned (observable)" is.
Stance: falsify, don't confirm. For each requirement, the job is to find the input+trigger that makes the observable wrong. Happy path is table stakes; the scenario value is in the boundaries and failures.
Phase 1 — Read the spec, classify requirements
-
Read what exists (OpenSpec layout shown; in a non-OpenSpec project read whatever spec/design/task docs the user points at — do not fail on a missing
openspec/dir or CLI):openspec/changes/<name>/proposal.md(always)openspec/changes/<name>/design.md(if present — decisions, invariants)openspec/changes/<name>/specs/**/spec.md(requirement deltas)openspec/changes/<name>/tasks.md(if present — to align section numbers)
-
Extract every testable requirement (each
SHALL/MUST, each scenario block, each acceptance criterion). For each, tag its shape — this picks the technique:Requirement shape Technique to apply Scenario class Input range / numeric / size / count Equivalence Partitioning + Boundary Value Analysis edge-case Multiple boolean/enum flags combine Decision Table edge-case Lifecycle / status transitions / reconnect / restart State-Transition frontend-quirk + error-handling Async / WebSocket / polling / optimistic UI State-convergence + invariant assertions (not UI-visibility) frontend-quirk Latency / throughput / memory / long-run tail-latency (p95/p99) + soak + threshold performance Depends on network / disk / subprocess / other service fault injection (delay + abort) error-handling See
references/technique-cheatsheet.mdfor how to apply each.
Phase 2 — Generate scenarios via the Triple (or a gap)
For each requirement, walk its technique and try to emit one or more Triples.
- EP+BVA: emit min, just-below-min (invalid), nominal, just-below-max, max, just-above-max (invalid). Six Triples from one numeric requirement.
- Decision table: one Triple per reachable flag combination; mark impossible combos.
- State-transition: one Triple per legal edge AND per illegal edge (event fired in a state that shouldn't accept it).
- Async/convergence: assert the eventual invariant and the intermediate states, never "element is visible after N ms".
- Performance: state the workload, the metric (p95/p99/RSS), the threshold, and the measurement window. No threshold in spec → gap.
- Fault injection: for each dependency, a delay Triple and an abort Triple; assert retry/timeout/degradation behaviour.
When a slot won't fill → STOP generating that scenario. Record a gap with the unfillable slot named (see Gate). Do not invent the missing value.
Phase 3 — The clarification Gate (configurable)
Whether an unfillable Triple blocks or just annotates depends on stage:
stage = proposal | design → HARD gate
stage = apply → SOFT gate
(no --stage) → infer: tasks.md absent ⇒ proposal/design (hard)
tasks.md present ⇒ apply (soft)
- HARD gate: collect all gaps, then call
ask_userwith decision-forcing questions and STOP. Do not write test-plan.md until answered. The spec is not yet testable; clarify before locking scenarios. - SOFT gate: write the scenario row with a
[NEEDS CLARIFICATION: <slot> — <question>]marker, continue, and list all markers in a banner at the top of test-plan.md.
Decision-forcing question rules (from ambiguity-detection practice):
- Name the missing slot and why it blocks a scenario.
- Offer concrete candidate answers, never propose a solution/implementation.
- One question per genuine decision; do not pad.
Example: "Restart quiesce window: tasks say bridges 'suppress auto-start for the quiesce window'. To test the boundary I need the exact value — is it 5s (restart) / 60s (shutdown) per AGENTS.md, or spec-defined elsewhere? Without a number I cannot write the just-after-window re-spawn scenario."
Phase 4 — Route each scenario to a test level
Every scenario carries a level tag fixing where it would be authored, and a
disposition (automated | manual-only). Map each scenario's nature to
one of your project's actual test levels — the routing method is fixed; the
level names and paths are yours to fill. Do not assume a level/harness the
project lacks.
| Scenario nature | Route to the project level that is… |
|---|---|
| pure logic / boundary / decision table / pure state | the fast in-process unit tier |
| process / install / spawn / multi-OS runtime | the process/CLI smoke tier (NO rendered-UI asserts) |
| rendered UI / WS-driven view / convergence / quirk | the browser/e2e tier |
| micro perf (fn-level) | the unit tier, timed |
| process/load perf, soak | the smoke tier (or a dedicated perf harness) |
| aesthetics / hardware / "feels right" / subjective | manual-only → no fold, no test task (disposition=manual-only, level —) |
Keep the rendered-UI-vs-smoke boundary sacred: a UI-visible assertion never lives in a process/CLI smoke row.
Example — pi-agent-dashboard levels (this repo's concrete routing; other projects substitute their own). Honour the AGENTS.md hard rule: rendered-UI assertions are Playwright only; qa/ stays CLI/process smoke.
┌────────────────────────────┬──────────────────────────────────────────┐ │ Scenario nature │ Level → location │ ├────────────────────────────┼──────────────────────────────────────────┤ │ pure logic / boundary / │ L1 unit → packages/*/src/**/__tests__/ │ │ decision table / state pure│ *.test.ts (vitest) │ │ process / install / spawn │ L2 smoke → qa/tests/*.sh|*.ps1 │ │ / multi-OS runtime │ (NO rendered-UI asserts) │ │ rendered UI / WS-driven │ L3 e2e → tests/e2e/*.spec.ts │ │ view / convergence / quirk│ (Playwright vs docker harness port †) │ │ micro perf (fn-level) │ L1 unit (timed) │ │ process/load perf, soak │ L2 smoke (or dedicated harness) │ │ aesthetics / hardware / │ manual-only → no fold, no test task │ │ "feels right" / subjective │ (disposition=manual-only, level —) │ └────────────────────────────┴──────────────────────────────────────────┘† The docker e2e harness port is NOT a fixed
:18000—docker/test-up.shhash-derives a free port per worktree and records it in.pi-test-harness.json(dashboardPort). An L3 scenario's observable is read against that derived port; never hardcode:18000.
manual-only routing outcome (additive to L1/L2/L3): a scenario whose
expected observable is a human judgment with no automatable signal — visual
aesthetics, a hardware behaviour, "feels right / looks correct", subjective UX —
is NOT routed to a test level. Its manifest row records disposition: manual-only (level —), and no test task is folded for it; it is deferred to
post-merge manual verification by ship-change. Every routable scenario keeps
its L1/L2/L3 level and disposition: automated — this outcome only diverts the
truly un-automatable rows; existing L1/L2/L3 logic is unchanged.
If a scenario implies a brand-new level/harness, flag it in the plan's "New infra needed" section rather than silently assuming it exists.
Phase 5 — Write test-plan.md
Write the test-plan.md to your change/spec's test-plan location (OpenSpec:
openspec/changes/<name>/test-plan.md) using
references/test-plan-schema.md. It is a standalone catalog, separate from
tasks.md. Each scenario is a numbered row with: id, class, technique, level,
disposition (automated | manual-only), the full Triple, and (soft gate)
any clarification marker. The disposition column is mandatory on every row —
it is the manifest's source-of-truth signal that the fold step (in
plan-proposal) and the defer rule (in ship-change) both read.
End with a short offer (do not auto-act): "Want me to fold these into the
## Tests / ## Validate sections of tasks.md as checklist items?" — folding
is a separate, explicit step.
Guardrails
- Never invent a missing value to make a scenario "work" — that hides the spec gap this skill exists to expose.
- Never write app/test code here — this skill drafts the catalog. Authoring
the actual
*.test.ts/*.spec.tsis implementation (useimplement/openspec-apply-change). - Don't downgrade scenarios to smoke to make them easy. A scenario that only checks "it exists / exit 0" belongs in qa/ smoke already — this skill's output is the layer above that.
- Honour the level boundary — no rendered-UI assertion in a qa/ smoke row.
- Offer, don't auto-fold into tasks.md.
References
references/technique-cheatsheet.md— how to apply each ISTQB + resilience technique, with project-specific examples.references/test-plan-schema.md— exact test-plan.md layout.
Signals
- GitHub stars
- 283
- Forks
- 41
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
scenario-design- Source
- github.com/blackbelttechnology/pi-agent-dashboard