(ywc) Use when generating a manual verification testsheet (developer + QA gate) from a PR, task directory, task directory range, git range, or diff, with Markdown or HTML output. Triggers: "generate test case", "testsheet", "QA checklist", "PR 테스트 케이스", "테스트시트", "テストシート作って", "manual test", "release 범위 test", "수기 검증". Do not use for writing automated unit/integration tests, code-level test generation, or in-IDE test scaffolding.
Installs into .claude/skills of the current project.
Are you the author of Ywc Gen Testcase?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/yongwoon-ywc-gen-testcase-ywc-agent-toolkit)
---
name: ywc-gen-testcase
description: >-
(ywc) Use when generating a manual verification testsheet (developer + QA gate) from a PR, task directory, task directory range, git range, or diff, with Markdown or HTML output. Triggers: "generate test case", "testsheet", "QA checklist", "PR 테스트 케이스", "테스트시트", "テストシート作って", "manual test", "release 범위 test", "수기 검증". Do not use for writing automated unit/integration tests, code-level test generation, or in-IDE test scaffolding.
---
# Generate Testsheet
**Announce at start:** "I'm using the ywc-gen-testcase skill to produce a dual-audience (developer + QA) testsheet."
Produce a **dual-audience** testsheet (Markdown by default, or interactive HTML with `--format html`) from a GitHub PR, an implemented task directory, a task directory range, a git range, or the current git diff. The testsheet cleanly separates what the **developer** does (DB queries, curl contracts, container logs) from what a **QA/PM/Product Owner** does in a browser, so both gates — pre-merge and pre-release — can be signed off independently and in parallel.
## Rationalization Defense
When tempted to skip a step, check this table first:
| Excuse | Reality |
|---|---|
| "Two input modes given, pick the more specific one silently" | Stop and ask. Inputs cover different scopes (a PR vs a diff). Wrong choice produces a testsheet with mismatched scope. |
| "Developer test cases are obvious, focus on QA cases" | Both audiences need explicit cases. Pre-merge gate (dev) and pre-release gate (QA) must be parallel-signable. |
| "Mix dev curl checks into the QA section for completeness" | Each audience must own its section. Cross-contamination breaks parallel sign-off. |
| "Testsheet output without prerequisites/setup is fine" | Prerequisites (env, credentials, DB state) must be explicit. A testsheet that cannot be started is not a testsheet. |
| "Negative test cases (failure paths) are out of scope" | Always include critical negative cases. Happy-path-only testsheets miss most regressions. |
| "Browser-dependent steps belong in dev section" | UI verification belongs to QA. Dev section stays in terminal/curl/DB. |
| "Expected: 'the feature works correctly' is clear enough" | Weak criteria require constant clarification. Expected must name concrete values: status codes, row counts, UI text, exact error messages. |
**Violating the letter of these rules is violating the spirit.** A testsheet that mixes audiences is sign-off theater.
## Inputs
Exactly one input mode must resolve. If more than one is given, stop and ask — they cover different scopes (a PR spans many commits; a diff spans only the current working tree), and silently picking one would produce a testsheet whose title, scope, and sign-off surface do not match the user's intent.
| Input mode | How to specify | Primary source |
|---|---|---|
| **PR** | PR URL or PR number | `gh pr view`, `gh pr diff` |
| **Task** | Task name or `<phase>-<sequence>` prefix | `<tasks-dir>/<task>/task.md` + `README.md` |
| **Task Range** | Positional `<task-start>..<task-end>` | Inclusive ordered task directories from `<tasks-dir>` |
| **Range** | Positional `<start>..<end>` (SHA / tag / branch / `HEAD~N`) or `--range <spec>` | `git log`, `git diff <start>..<end>` |
| **Diff** | `--from-diff` flag | Current `git diff HEAD` |
If no input is resolvable, stop and ask. Do not guess.
## Arguments
Parse `$ARGUMENTS`.
| Parameter | Format | Example | Description |
|---|---|---|---|
| PR identifier | URL or number | `250` | Fetches PR metadata + diff via `gh` |
| Task specifier | name or prefix | `000001-010` | Prefix match against `<tasks-dir>` |
| Task range | `<task-start>..<task-end>` | `000012-010..000019-010` | Inclusive range of ordered task directories. Resolve before treating positional `A..B` as a git range |
| `--from-diff` | flag | | Use `git diff HEAD` |
| `--range <spec>` | `<start>..<end>` | `--range v1.2..v1.3` | Explicit git range form. Two-dot only — three-dot `A...B` is rejected |
| `--output-dir <path>` | path | `--output-dir ./docs/qa` | Override `docs/test-case/` |
| `--lang <code>` | `ja`,`ko`,`en`,`zh`,`es` | `--lang zh` | Prose language. If omitted, resolve through shared YWC language policy. |
| `--filename <name>` | string | `--filename release-smoke` | Override derived filename (no `.md`) |
| `--tasks-dir <path>` | path | `--tasks-dir ./docs/tasks` | Tasks directory (Task and Task Range input only) |
| `--include-regression` | flag | | Add Regression section (B side) |
| `--audience <who>` | `dev`\|`qa`\|`both` | `--audience qa` | Generate only one audience. Default - `both` (single file, A+B sections) |
| `--split` | flag | | Force physical split into `<slug>-dev.md` + `<slug>-qa.md`. Mutually exclusive with `--force-single` |
| `--force-single` | flag | | Bypass the L-tier split suggestion and always produce one file |
| `--no-toc` | flag | | Suppress TOC auto-insertion for M/L tier |
| `--dry-run` | flag | | Show plan (tier, filenames, section counts) without writing |
| `--format` | `markdown`\|`html` | `--format html` | Output format. Default `markdown`. With `html`, writes an interactive HTML testsheet (see Step 5). |
**Flag conflicts**: `--split` and `--force-single` cannot coexist — stop and ask. `--audience dev|qa` implies a single-audience file; `--split` in that combination is redundant and is silently ignored. **Git range input** is mutually exclusive with PR, Task, Task Range, and `--from-diff` — stop and ask if more than one is given. Range accepts only the two-dot `A..B` form; three-dot `A...B` is rejected because its merge-base semantics silently change scope and surprise the tester.
## Context
- Repository: !`gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || echo "not a gh repo"`
- Current branch: !`git branch --show-current`
- Changed files (diff mode reference): !`git status --short`
- Default output directory: `docs/test-case/`
## Pre-flight
1. **Inside a repository** — `git rev-parse --show-toplevel` must succeed; run subsequent commands from the repo root.
2. **`gh` CLI authenticated** (PR input only) — `gh auth status` must succeed.
3. **Output directory writable** — `mkdir -p <output-dir>` must succeed.
4. **Exactly one input mode resolved** — if more than one input is present, stop and ask.
5. **Flag conflicts** — see Arguments note above.
## Step 1: Input Resolution
Resolve input mode in this order:
1. Explicit PR, task, `--from-diff`, and `--range` flags/phrases.
2. For positional two-dot `A..B`, reject `A...B` first.
3. For positional two-dot `A..B`, check whether either endpoint is task-like (for example, starts with `000012-010`) or resolves as a task prefix under `<tasks-dir>`. If yes, use **Task Range** mode. Do not run `git rev-parse` before this task-range check.
4. If the positional two-dot endpoints are not task-like and do not resolve as task prefixes, use **Git Range** mode.
`--range A..B` always means Git Range. Use it when a branch, tag, or SHA happens to look like a task prefix.
### 1a. PR input
```bash
gh pr view <N> --json number,title,body,author,headRefName,baseRefName,url,files,commits,labels
gh pr diff <N>
```
Capture PR URL, title, body, author, head/base branches, labels, changed files, commit messages. Lift any explicit "How to test" / "Acceptance Criteria" / "動作確認" block from the PR body into scenario goals verbatim.
### 1b. Task input
- Determine `<tasks-dir>` from `--tasks-dir`; if omitted, use the first existing directory among `tasks/`, `docs/tasks/`, `docs/specification/tasks/`, and `docs/specifications/tasks/`.
- Resolve the task directory via prefix match. Prefer `<tasks-dir>/completed/<name>/` over `<tasks-dir>/<name>/` — a testsheet after merge is the common case.
- Read `task.md` (Implementation Steps, Acceptance Criteria, Verify commands) and `README.md` in full.
### 1c. Task Range input
Use this mode for positional task ranges such as `000012-010..000019-010`.
- Resolve each endpoint with the same prefix-match rule as Task input.
- Build the ordered task list from directory basenames under `<tasks-dir>`, including common status buckets such as `completed/`. Sort by basename lexicographically; numbered task prefixes are designed to sort in execution order.
- Include every task from the resolved start task through the resolved end task, inclusive.
- If either endpoint is missing or ambiguous, report the missing/ambiguous task endpoint and stop. Do not fall back to Git Range for task-like endpoints.
- If the start task appears after the end task in the ordered list, stop and ask whether the user intended the reverse range. Do not silently swap endpoints.
- Read each selected task's `task.md` and `README.md` in full. Use task Acceptance Criteria and Verify commands as the primary scenario source, grouped by task in the Summary and Appendix when needed.
### 1d. Diff input
- `git diff HEAD` plus `git log <base>..HEAD --oneline` (auto-detect `<base>`: `develop` > `main` > `master`). Stop on empty diff.
### 1e. Git Range input
Positional `<start>..<end>` or `--range <spec>`. Each endpoint accepts SHA, tag, branch, or a relative ref like `HEAD~5`. Reject the three-dot form `A...B` explicitly.
Collect:
- `git rev-parse <start>` and `git rev-parse <end>` — fail fast on unknown refs
- `git log --oneline <start>..<end>` — commit list (used as scenario source)
- `git diff <start>..<end>` — the change set (used for surface classification)
- Stop immediately on an empty range
### 1f. Range quality checks (git range input only)
Range mode lacks PR body / Task Acceptance Criteria, so scenario quality is sensitive to commit-message hygiene and scope size. Three protective checks run before Step 2 to prevent low-quality testsheets from being generated silently:
1. **PR auto-suggest** — run `gh pr list --search <head-sha> --state all --json number,title,commits` on the range's HEAD commit. If an open or merged PR exists and its commit set covers ≥ 80% of the range, stop and ask:
```text
Range HEAD is part of PR #<NNN> "<title>" (covers N of M commits).
PR mode provides PR body, labels, and explicit "How to test" context —
scenario quality is meaningfully better.
[a] Switch to PR #<NNN> mode (recommended)
[b] Keep range mode (PR metadata will be ignored)
```
2. **Commit-message quality** — if ≥ 70% of commit headlines in the range are ≤ 10 characters (e.g. `wip`, `fix`, `tmp`, `update`), warn and ask whether to proceed. Low-signal headlines force scenario generation to rely on diff inference alone, which is materially weaker than structured acceptance criteria.
3. **Range size guard** — if the range contains > 50 commits, escalate directly to L-tier handling in Step 4 regardless of scenario count. Review burden grows with commit lineage length, not just with scenario count.
## Step 2: Audience and Surface Classification
Every scenario carries **two** tags: **audience** (who runs it) and **surface** (what it verifies). Classifying on both axes is what makes the final testsheet useful to different readers.
### 2a. Audience tagging
| Audience | Who | Tools | Gate |
|---|---|---|---|
| **A. Developer** | Backend / DB / DevOps engineer | `psql`, `gh`, `curl`, `docker`, `kubectl`, unit/integration test runners | **Pre-merge** — contracts, migrations, worker runners, container orchestration |
| **B. QA / Browser** | QA, PM, Product Owner, designer | Chrome + DevTools, admin UI, test HTML origin, mobile device/emulator | **Pre-release** — end-user experience and any browser-observable effect |
Scenarios that require both (e.g. "SDK loader works end-to-end") belong to **B**, with the data setup moved into A's Prerequisites. This prevents duplicate work.
### 2b. Surface classification
| Surface | File patterns (typical) | Scenarios produced | Typical audience |
|---|---|---|---|
| UI / Browser | `*.tsx`,`*.vue`,`*.svelte`,`app/**`,`pages/**`,`components/**`,`*.css`,`*.html` | Route/screen walkthrough, form validation, empty/loading/error states | B |
| Database / Table | `migrations/**`,`*.sql`,`prisma/schema.prisma`,`supabase/migrations/**` | Row visibility, constraint enforcement, index presence, rollback safety | A |
| API | `app/api/**`,`*.controller.*`,`**/route.ts`,`src/routes/**`,`api/**/*.py` | Status codes, response shape, auth boundary, validation errors | A primarily; B when endpoint is browser-consumed |
| Background job / Webhook | `workers/**`,`jobs/**`,`webhook*`,`cron*`,`*.worker.ts` | Trigger condition, retry, idempotency | A |
| Configuration | `*.env*`,`*.yaml`,`*.toml`,`Dockerfile`,`docker-compose*`,`.github/workflows/**` | Env presence, build/runtime, CI green | A |
| Docs only | `*.md`,`docs/**` | Docs review checklist | A or B depending on audience of the doc |
**Do not fabricate surfaces that do not exist in the diff.** An empty section tells the tester nothing and dilutes the real work.
### 2c. Browser-observable effects (even in backend-only PRs)
File-pattern matching alone is not sufficient to decide the B section. A backend-only PR can still have browser-observable effects that only a real browser can verify — curl cannot reliably reproduce CORS preflight, cookie partitioning, `<script>` tag loading, `Content-Type` sniffing, or downstream dashboard behaviour.
**Include a B (QA/Browser) section even when no frontend files changed, if any of the following are true:**
| Browser-observable effect | B-section scenario to include |
|---|---|
| Public endpoint consumed by a browser (`/config/:id`, SDK loader, webhook receiver) | "Fetch from DevTools Console" scenario |
| CORS / preflight behaviour changed | Preflight-from-real-browser scenario (curl does not fully simulate preflight) |
| `Content-Type` / CSP / cookie attributes changed | DevTools Network tab verification |
| Backend APIs consumed by an **existing unchanged** frontend in the same repo | Consumer-renders-successfully scenario (regression-style) |
| Script/asset delivered to end-user browsers (SDK, embed, widget) | "Load from test HTML page" scenario |
The test for whether to include a B section is not "did a `.tsx` file change" but **"can a human verify this change by opening a browser?"** When in doubt, include a minimal B section of 1–2 scenarios rather than omit it — a missing B section on a release that touches public endpoints is a common source of production surprises.
## Step 3: Scenario Generation
For each (audience, surface) pair that exists, generate 2–5 scenarios. A scenario is one testable behaviour, not one changed file.
Each scenario has:
- **Goal** — one sentence
- **Preconditions** — state the app must be in
- **Steps** — numbered, imperative, specific enough for a tester who did not write the code
- **Expected** — concrete values (`expect 3 rows with status = 'active'`), not "it should work"
- **Checkbox** — one `- [ ]` at the top
**Anti-pattern**: `- [ ] Test that the feature works`. That is not a scenario. If you cannot state *what to run/click and what to see*, the scenario is not ready.
Scenario sources, in priority order:
1. PR body / task Acceptance Criteria ("How to test", "動作確認", "Reviewer should verify")
2. Commit messages
3. Surface-driven scenarios (from 2b)
4. Diff edge cases (new `NOT NULL` → null-input scenario; new permission check → unauthorised-role scenario)
## Step 4: Tier Decision
Count scenarios after Step 3. Use the count to choose layout. This protects testsheets from becoming unread 1000-line walls.
| Tier | Scenarios | Layout |
|---|---|---|
| **S** | ≤ 20 | Single file, A+B sections, **no TOC**, **no collapsible details** |
| **M** | 21–40 | Single file, A+B sections, **auto-TOC** at top, **`<details>` collapsible** for Prerequisites + Edge Cases |
| **L** | > 40 scenarios **OR** range > 50 commits | **Ask user** before writing:<br>1. Single file with TOC + collapsible (as M)<br>2. `--split` into `<slug>-dev.md` + `<slug>-qa.md`<br>3. Split by Phase / feature (user names each file)<br>Proceed only after the user chooses. `--force-single` bypasses the prompt. |
Explicit flags override the tier default -
- `--split` → physical split, regardless of tier
- `--audience dev|qa` → single-audience file, skip the other section entirely
- `--force-single` → one file even if count > 40
- `--no-toc` → suppress TOC even in M/L
## Step 5: Write the Testsheet
### Output format
Default is a Markdown testsheet (`.md`) — keep this for the existing `-v<N>`
append and front-matter sign-off workflow. With `--format html`, write an
**interactive HTML testsheet** instead, following
[html-output.md](../references/html-output.md): each scenario checkbox is
clickable, sign-off state (tester name, Pass/Fail/Blocked, notes) persists in
the browser via `localStorage`, and a `Copy as Markdown` button exports the
current sign-off state back to the Markdown testsheet format so the result can
be pasted into a PR or committed. HTML mode is recommended when the testsheet
will be handed to a QA/PM who signs off in a browser; the filename follows the
Default filename table below with an `.html` extension. The Markdown surface
(front matter included) is preserved inside the file.
### Default filename
| Input | Single file (default) | `--split` mode |
|---|---|---|
| PR | `pr-<number>-<slug>.md` | `pr-<number>-<slug>-dev.md` + `...-qa.md` |
| Task | `task-<phase>-<sequence>-<slug>.md` | `...-dev.md` + `...-qa.md` |
| Task Range | `tasks-<start-prefix>-<end-prefix>-<slug>.md` | `...-dev.md` + `...-qa.md` |
| Range | `range-<short-start>-<short-end>-<slug>.md` (tag names used verbatim when both endpoints are tags, e.g. `range-v1.2-v1.3-<slug>.md`) | `...-dev.md` + `...-qa.md` |
| Diff | `<yyyymmdd-HHMM>-<branch-slug>.md` | `...-dev.md` + `...-qa.md` |
If the target file already exists, append `-v<N>` (never overwrite). Existing files may carry a tester's partial sign-off.
### Single-file template (default, A+B)
Write `<output-dir>/<filename>.md` using this skeleton. Keep front matter keys, section numbers, file names, code snippets, and template structure in English regardless of `--lang`; they are machine surfaces. M/L tier inserts a TOC block and wraps Prerequisites/Edge Cases in `<details>`; S tier skips both.
````markdown
---
source: <pr#N | task:<name> | task-range:<start>..<end> | diff | range>
pr_url: <url or "">
task_range_spec: <e.g. "000012-010..000019-010" or "">
task_count: <number or "">
range_spec: <e.g. "main..feature-x" or "">
range_start: <short SHA or tag, or "">
range_end: <short SHA or tag, or "">
branch: <head branch, or "" for ranges crossing branches>
base_branch: <base>
author: <username>
generated_at: <ISO 8601>
tier: <S | M | L>
dev_tester: ""
dev_status: pending
qa_tester: ""
qa_status: pending
---
# Test Case: <Title>
<!-- TOC block: M/L tier only -->
## Table of Contents
- [1. Summary](#1-summary)
- [2. Prerequisites](#2-prerequisites)
- [A. Developer Verification](#a-developer-verification)
- [A.1 Database](#a1-database--table)
- [A.2 API](#a2-api)
- [A.3 Background Jobs](#a3-background-jobs--workers)
- [A.4 Configuration](#a4-configuration)
- [A.5 Dev Edge Cases](#a5-dev-edge-cases)
- [A.6 Dev Sign-off](#a6-dev-sign-off)
- [B. QA / Browser Verification](#b-qa--browser-verification)
- [B.1 UI / Browser Scenarios](#b1-ui--browser-scenarios)
- [B.2 User-visible Edge Cases](#b2-user-visible-edge-cases)
- [B.3 Regression](#b3-regression)
- [B.4 QA Sign-off](#b4-qa-sign-off)
## 1. Summary
<2–4 sentences explaining what changed and why a human needs to verify it.>
## 2. Prerequisites
<!-- M/L tier: wrap in <details> tag, open by default -->
### 2.0 Common (both audiences)
- [ ] …
### 2.A Dev-only
- [ ] …
### 2.B QA-only
- [ ] …
## A. Developer Verification
> **Audience**: Backend / DB / DevOps engineer
> **Tools**: psql, gh, curl, docker
> **Gate**: pre-merge
### A.1 Database / Table
<only if surface exists>
#### A.1.1 <scenario name>
- [ ] **Goal**: …
- **Preconditions**: …
- **Steps**:
1. …
- **Expected**: …
### A.2 API
### A.3 Background Jobs / Workers
### A.4 Configuration
### A.5 Dev Edge Cases
<!-- M/L tier: wrap A.5 in <details> -->
- [ ] <boundary / null / error path>
### A.6 Dev Sign-off
- **Tester**:
- **Date**:
- **Result**: ☐ Pass ☐ Fail ☐ Blocked
- **Notes**:
## B. QA / Browser Verification
> **Audience**: QA / PM / Product Owner / Designer
> **Tools**: Chrome + DevTools, admin UI, test origin
> **Gate**: pre-release
### B.1 UI / Browser Scenarios
<only if a browser-observable surface exists>
### B.2 User-visible Edge Cases
<!-- M/L tier: wrap in <details> -->
### B.3 Regression
<only if --include-regression>
### B.4 QA Sign-off
- **Tester**:
- **Date**:
- **Result**: ☐ Pass ☐ Fail ☐ Blocked
- **Notes**:
## Appendix
<optional — long SQL, payload samples, troubleshooting>
````
### Split-file variants (`--split` or `--audience`)
Each file contains only that audience's Summary, Prerequisites, Scenarios, and Sign-off. Add a **Cross-reference** line at the bottom pointing to the counterpart (`Developer sheet: ./<slug>-dev.md`, `QA sheet: ./<slug>-qa.md`). Omit cross-reference when `--audience` was explicit and only one file is produced.
## Length Management Guidelines
Apply these regardless of tier to keep testsheets lean:
1. **Prerequisites common-prefix + audience-suffix** — shared rows live once in `## 2.0 Common`; dev-only / qa-only rows live in `## 2.A` / `## 2.B`. Never duplicate.
2. **Extract long verification material** — any SQL / script > 20 lines lives in `scripts/qa/*.sql` (or similar) and the scenario references the path only. The scenario body stays short.
3. **Regression by reference** — instead of repeating known-good regression flows, link to prior testsheets (e.g. `Stripe release testsheet: ./pr-230-stripe-release.md`).
4. **Appendix at the bottom** — overflow content (detailed troubleshooting, sample payloads) goes under `## Appendix` and is linked from the relevant scenario.
Applied consistently, these four rules keep most M-tier testsheets under ~800 lines and prevent L-tier by shifting volume out of the main flow.
## Step 6: Validate and Report
Before finishing:
1. File(s) exist and non-empty.
2. Checkbox count ≥ 3.
3. Every scenario has a concrete **Expected** result.
4. If M/L tier and `--no-toc` not set, TOC present at the top with anchors matching headings.
5. If split layout, cross-reference line present in each file.
Report:
```text
Testsheet generated: <absolute path(s)>
Source: <pr#N | task:<name> | task range:<task-start>..<task-end> | diff | git range:<start>..<end>>
Tier: <S | M | L>
Layout: <single | split>
Audiences: <A only | B only | A+B>
Scenarios by surface: UI (3), DB (1), API (2), Jobs (4), Config (2)
Scenarios by audience: Dev <n>, QA <m>
Total: <scenarios> scenarios, <checkboxes> checkboxes
Language: <detected-or-specified>
Next steps:
- Dev signs off on Section A (pre-merge gate)
- QA signs off on Section B (pre-release gate)
- Update front-matter `dev_status:` / `qa_status:` as work progresses
```
## Language Detection
When `--lang` is not specified, resolve testsheet prose language using the
shared Codex YWC policy:
[`../references/language-resolution.md`](../references/language-resolution.md).
The order is explicit `--lang` > project `.codex/ywc.json` > project guidance
(`AGENTS.md`, `CODEX.md`, `CLAUDE.md`) > user `~/.codex/ywc.json` > ask user.
Recent testsheets in the output directory and the project `README.md` may be
used only as non-authoritative context when asking the user. They do not create
a final fallback default.
Supported language codes are `ja`, `ko`, `en`, `zh`, and `es`. Treat aliases such as `japanese`, `korean`, `english`, `chinese`, `Chinese (Simplified)`, `中文`, `spanish`, `espanol`, and `español` as requests for the matching code; `zh` / `chinese` means Simplified Chinese unless the user explicitly asks for a different Chinese locale.
YAML front matter keys, section numbers, file names, code snippets, command names, and the template skeleton stay in English regardless of `--lang`. Only prose (Summary, Goal, Steps, Expected, Notes, Edge Cases, and audience-facing test descriptions) follows the chosen language. Technical terms that are part of the testsheet contract or tooling surface, such as Developer Verification, QA / Browser, PR, Task, Git Range, Expected, front matter, Status, and Sign-off, stay English in `zh` and `es` prose.
## Dry Run Mode
With `--dry-run`, perform Steps 1–4 as usual, then print:
- Resolved input mode and source
- Tier decision (S/M/L) and scenario count
- Target filename(s) and full output path(s)
- Surface classification (which sections will exist)
- Scenarios per (audience, surface) pair
- Detected language
Do **not** write any file. Exit after printing.
## Error Handling
| Situation | Behaviour |
|---|---|
| No input given | Stop, ask for PR / task / range / `--from-diff` |
| Multiple inputs given (PR + Range, Task + Range, Range + `--from-diff`, etc.) | Stop, ask which mode |
| Three-dot range `A...B` passed | Reject and ask for the two-dot `A..B` form instead |
| Task range endpoint missing or ambiguous | Report the endpoint and similar task names; stop without trying `git rev-parse` |
| Task range start appears after end | Stop and ask whether the user intended the reverse range |
| Range ref invalid (`git rev-parse` fails on `<start>` or `<end>`) | Report which ref failed; stop |
| Range empty (`<end>` has no commits absent from `<start>`) | Report "range is empty"; stop |
| Range > 50 commits | Escalate to L-tier handling (ask user how to proceed) |
| Range HEAD belongs to an open/merged PR (≥ 80% overlap) | Stop and ask whether to switch to PR mode |
| Range has ≥ 70% low-quality commit headlines | Warn and ask whether to proceed |
| `--split` and `--force-single` both set | Stop, ask which is intended |
| `gh` not authenticated (PR input or range PR auto-suggest) | Ask user to run `gh auth login`; stop |
| PR not found | Report the PR number and stop |
| Task not found | List similar tasks (fuzzy match) and stop |
| Empty diff (diff input) | Report "nothing to test" and stop |
| Output directory not writable | Report failing path and stop (no silent fallback) |
| Target file already exists | Append `-v<N>` suffix; report new filename |
| L tier detected and no explicit `--split`/`--force-single` | Ask user how to proceed before writing |
## Integration
Downstream of the implementation-oriented `ywc` skills:
- **ywc-sequential-executor** — generates the PR/Task this skill tests
- **ywc-parallel-executor** — same, for parallel execution
- **ywc-merge-dependabot** — merged dependency bumps that often warrant a smoke testsheet
Invoke this skill after those finish, when a human tester is ready to validate.
## Examples
For invocation examples across PR, task, task range, diff, and git range modes, read [references/examples.md](references/examples.md).
## Output Format
Include `Status: <DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT>`, the generated testsheet path or dry-run summary, detected scope, and any blocker.
## Validation
Before finalizing, verify that input mode is unambiguous, the diff/range is non-empty, output path handling is deterministic, and no file is written during `--dry-run`.