Turns a production incident into a deterministic scenario file that
reproduces it. Takes an incident-report markdown OR direct fields
(stack trace, endpoint URL, HTTP method, request body), extracts the
minimal reproducer, writes `scenarios/<incident-id>.md` with
`linked_to_incident:` frontmatter, emits `wicked.qe.scenario.authored`
with `source: incident`, and queues a review task under
`assignee_skill: incident-to-scenario-synthesizer:review` so a human
confirms before the scenario is marke...
9 stars
0 votes
0 copies
0 views
Added October 18, 2026
ai-agentspythonrustgobashnodetestingapi
Works with
api
Security analysis
A92/100
mediumInstalls packages at runtime which could introduce malicious dependencies
Installs into .claude/skills of the current project.
Are you the author of Qe Incident To Scenario Synthesizer?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mikeparcewski-qe-incident-to-scenario-synthesizer)
---
name: wicked-garden-qe-incident-to-scenario-synthesizer
description: |
Turns a production incident into a deterministic scenario file that
reproduces it. Takes an incident-report markdown OR direct fields
(stack trace, endpoint URL, HTTP method, request body), extracts the
minimal reproducer, writes `scenarios/<incident-id>.md` with
`linked_to_incident:` frontmatter, emits `wicked.qe.scenario.authored`
with `source: incident`, and queues a review task under
`assignee_skill: incident-to-scenario-synthesizer:review` so a human
confirms before the scenario is marked active.
Use when: postmortem follow-up, "write a regression test for INC-123",
prod incident → scenario backport, error-class-to-test synthesis.
metadata:
role: worker
---
# Incident-to-Scenario Synthesizer
A prod incident is a free test case — nature already generated the input.
You extract it and lock it down as a reproducible scenario. You never
mark the scenario active; a human always verifies that the reproducer
actually reproduces before it joins the regression suite.
## Runtime
Script-backed steps use the `wicked-garden` launcher: `wicked-garden run <plugin-root-relative path, e.g. scripts/…> [args]`. It is on PATH after `npm i -g wicked-garden`; otherwise use `npx wicked-garden run …`; inside a wicked-crew run it is `"$WICKED_GARDEN_ROOT/scripts/wicked-garden"`.
If none of these is available, or Python 3 is missing, skip the script-backed step, say so, and follow the manual alternative where one is given next to it — never invent the script's output.
Relative paths in this skill are relative to the directory that contains this SKILL.md.
## 1. Inputs
Accept either path A (incident report) OR path B (direct fields). Reject
if neither is provided.
### Path A — incident-report markdown
```yaml
# input
incident_report: docs/postmortems/INC-4829.md
incident_id: INC-4829 # optional; inferred from filename if omitted
```
The report is expected to contain, at minimum:
- A stack trace (fenced code block tagged `stack` or `traceback`).
- An HTTP request or curl example (fenced block tagged `http` or `curl`).
- An "affected endpoint" line matching `^(endpoint|route|path):`.
- An error class line: `error: <Exception or message>`.
### Path B — direct fields
```yaml
# input
incident_id: INC-4829
stack_trace: |
TypeError: Cannot read property 'amount' of undefined
at applyCoupon (/app/src/pricing/coupon.ts:42:15)
at Checkout.finalize (/app/src/checkout.ts:188:7)
endpoint: POST /api/checkout
request_body: { "cart_id": "c_abc", "coupon": "SAVE10" }
expected_error_class: TypeError
```
- **`run_id`** — UUID of the current `runs` row; defines `EVIDENCE_DIR`.
- **`.wicked-qe/config.json`** — optional; used to pick the default
test framework for the synthesized steps.
- **Scenario output dir** — `scenarios/`. Must exist. One file per
incident id.
## 2. Extraction pipeline
```bash
# Path A: parse the markdown with a deterministic extractor that pulls
# the four required fenced/labelled blocks. No LLM judgement in the
# extraction itself — a missing block is ERR_INCIDENT_MALFORMED.
node {WT_LIB}/incident/extract.mjs \
--report "${INCIDENT_REPORT}" \
--out "${EVIDENCE_DIR}/extracted.json"
# Path B: normalize the inputs into the same extracted.json shape.
node {WT_LIB}/incident/normalize-direct.mjs \
--stack "${STACK_TRACE_FILE}" \
--endpoint "${ENDPOINT}" \
--method "${METHOD}" \
--body "${REQUEST_BODY_FILE}" \
--error-class "${ERROR_CLASS}" \
--out "${EVIDENCE_DIR}/extracted.json"
```
`extracted.json` shape:
```jsonc
{
"incident_id": "INC-4829",
"endpoint": { "method": "POST", "path": "/api/checkout" },
"request_body": { /* parsed JSON or raw string */ },
"expected_error_class": "TypeError",
"stack_summary": {
"top_frame_file": "src/pricing/coupon.ts",
"top_frame_line": 42,
"top_frame_function": "applyCoupon",
"depth": 6
},
"source_excerpt_path": "docs/postmortems/INC-4829.md"
}
```
## 3. Scenario synthesis
Write exactly one file: `scenarios/<incident_id>.md`. Use the
qe scenario format (see the `wicked-garden-qe` skill's `refs/scenario-format.md`).
```markdown
---
name: Regression — INC-4829 coupon-reuse 500
status: pending-review
source: incident
linked_to_incident: INC-4829
authored_by: wicked-garden-qe-incident-to-scenario-synthesizer
authored_at: 2026-04-21T14:02:11Z
target: POST /api/checkout
trust_level: sandbox
tools:
required: [curl, jq]
expected_error_class: TypeError
---
## Steps
1. POST to /api/checkout with the reproducer body below.
2. Expect a 5xx response OR an exception whose class matches
`TypeError: Cannot read property 'amount' of undefined`.
3. Capture the server stack trace; assert `applyCoupon` appears in the
top 3 frames.
## Reproducer
```bash
curl -sS -X POST "${TARGET_HOST}/api/checkout" \
-H "content-type: application/json" \
-d '{"cart_id":"c_abc","coupon":"SAVE10"}' \
-w '\n%{http_code}\n' \
> "${EVIDENCE_DIR}/response.txt"
```
## Evidence expected
- `response.txt` — HTTP response body + status code.
- `server-trace.txt` — captured exception trace (from log shipper).
- The exception class MUST be `TypeError` and the top frame MUST be
`applyCoupon` in `src/pricing/coupon.ts`.
## Source
Synthesized from docs/postmortems/INC-4829.md by
`wicked-garden-qe-incident-to-scenario-synthesizer`.
Human review required — scenario is `pending-review` until approved.
```
## 4. Evidence output
Under `.wicked-qe/evidence/<run_id>/`:
| File | manifest `kind` | Required |
|-----------------------------------|-----------------|----------|
| `extracted.json` | `misc` | Yes |
| `synthesized-scenario-path.txt` | `log` | Yes |
| `source-excerpt.md` | `log` | Yes (copy of the incident report) |
| `synthesis-log.md` | `log` | Yes |
`synthesized-scenario-path.txt` is a single line containing the
absolute path of the scenario file that was written — this is the
machine-readable handoff.
`synthesis-log.md` documents the extraction decisions: which block
was used for the stack trace, how the endpoint was parsed, which
fields came from the report vs defaults.
## 5. DomainStore + event bus
```js
// 1. Verdict — always PASS for authoring; verdict is "scenario authored".
// A FAIL here means extraction failed, not that the incident recurred.
store.create("verdicts", {
run_id: RUN_ID,
verdict: "PASS",
reviewer: "wicked-garden-qe-incident-to-scenario-synthesizer",
reason: `Authored scenarios/${incidentId}.md (pending-review) from ${sourcePath}.`,
evidence_path: `evidence/${RUN_ID}/`,
// Inside a governed wicked-crew run, name it, so crew attributes this verdict by stamp.
...(process.env.WICKED_RUN_ID ? { crew_run_id: process.env.WICKED_RUN_ID } : {}),
});
// 2. Task — human review is mandatory before the scenario goes active.
store.create("tasks", {
project_id: PROJECT_ID,
title: `Review synthesized scenario: ${incidentId}`,
status: "open",
assignee_skill: "incident-to-scenario-synthesizer:review",
body: JSON.stringify({
incident_id: incidentId,
scenario_path: scenarioPath,
extracted_endpoint: extracted.endpoint,
expected_error_class: extracted.expected_error_class,
top_frame: extracted.stack_summary,
action_on_approve: "set status: active in scenario frontmatter",
}),
});
// 3. Bus event so downstream listeners (e.g. a reviewer dashboard) can
// surface the authored scenario immediately.
bus.emit("wicked.qe.scenario.authored", {
scenario_path: scenarioPath,
incident_id: incidentId,
source: "incident",
status: "pending-review",
run_id: RUN_ID,
});
```
## 6. Failure modes
| code | meaning | class |
|-------------------------------|-----------------------------------------------------|--------|
| `ERR_NO_INPUT` | neither incident_report nor direct fields provided | user |
| `ERR_INCIDENT_MALFORMED` | report missing one of stack / endpoint / error class| user |
| `ERR_SCENARIO_EXISTS` | scenarios/<incident_id>.md already exists | user |
| `ERR_UNSAFE_ENDPOINT` | endpoint path matches a deny-list (prod-only URL) | user |
| `ERR_INCIDENT_ID_MISSING` | cannot infer incident_id from filename or inputs | user |
| `ERR_SCENARIO_WRITE_FAILED` | filesystem write to scenarios/ failed | system |
On `ERR_SCENARIO_EXISTS`: do NOT overwrite. Surface the existing path
and ask the caller to choose a new `incident_id` or delete the existing
file first. A scenario overwrite is a human decision.
## 7. Non-negotiable rules
- **`status: pending-review` is the only valid status on synthesis.**
Never mark a synthesized scenario `active`. A human confirms it
actually reproduces, then flips the status.
- **`linked_to_incident:` is required frontmatter.** Every synthesized
scenario must trace back to the incident id; orphaned regressions
are a source-control smell.
- **No destructive scenario steps.** The synthesized steps reproduce
the incident in the sandbox trust level; any destructive action
(DELETE, DROP, truncate) is omitted from the reproducer even if the
incident included it.
- **Reproducer is copy-pasteable.** The scenario's bash block must run
as-is against `${TARGET_HOST}` — a reviewer should execute it in
one paste and see the error class.
- **Evidence expectations are explicit.** The scenario states which
files the executor must produce and what the assertion is — do not
defer to "check it looks wrong".
## 8. Output
```
## Incident-to-Scenario: {incident_id}
source: {incident_report | direct-fields}
endpoint: {method} {path}
expected_error_class: {class}
top_frame: {file}:{line} ({function})
synthesized: scenarios/{incident_id}.md status: pending-review
emitted event: wicked.qe.scenario.authored source=incident run_id={RUN_ID}
queued task: incident-to-scenario-synthesizer:review ({incident_id})
VERDICT: PASS
REVIEWER: wicked-garden-qe-incident-to-scenario-synthesizer
RUN_ID: {RUN_ID}
```
## Helper resolution (`{WT_LIB}`)
`{WT_LIB}` is the plugin's own qe helper directory — the helper modules ship
in-catalog (`scripts/qe/lib/`, ported from the retired wicked-testing package <!-- historical -->
in Phase 6c). Resolve it (cross-platform):
```bash
WT_LIB="$(wicked-garden path scripts/qe/lib)"
```