Use this when a PM, designer, or engineer needs to write a user story. The skill guides you through an interactive Q&A to produce a well-structured story that meets INVEST criteria, with Gherkin acceptance criteria covering happy paths, error paths, and edge cases. Optionally generate a UI prototype and push the story directly to your project tracker.
Full guide: See the Story Writing guide for multiple entry points and the Story Writing guide for the full practice.
Related resources:
user-story-guide.md-- 4-step Quick Start Guide for writing user stories (standalone, printable).
Process
Step 1: Gather context
Ask the user:
- What feature or capability does this story cover? (Describe what the user should be able to do.)
- Who is the user persona? Check for existing personas first:
- Look in the
personas/directory — if persona files exist, list them and ask: "I found these personas: (list). Which one fits this story, or should we define a new one?" - If no personas exist, ask: "Describe the user — their role, what they care about, and what a typical day looks like. Or we can run
/persona-createfirst to build a proper persona." - If the user gives a generic answer like "admin" or "user," push for specifics: "What kind of admin? What are they trying to get done? What frustrates them today?"
- Look in the
- Why does this matter? (What value does this deliver to the user or business?)
- Any known technical constraints or dependencies? (APIs, third-party services, design system requirements)
- Any designs or wireframes to reference? (Figma links, screenshots, or descriptions)
- Would you like a UI prototype generated for this story? (A quick branded HTML mockup of the feature — useful for stakeholder demos, design validation, and visual acceptance criteria.)
- What story format do you need?
- Default (our standard) — As a / I want / So that + Gherkin AC + Technical Details + Out of Scope + Added Context (recommended)
- Jira-optimized — Summary, Description, Acceptance Criteria (Gherkin), Story Points estimate, Labels
- Linear-optimized — Title + markdown body with clean headers, priority, labels
- Scenario (narrative) — A plain-language scenario describing a user accomplishing a goal. No Gherkin, no acceptance criteria. Best for design-phase exploration when the team needs to communicate intent, not test cases.
- Custom — describe your team's template and I'll adapt the output to match
- Where should stories go for this session? This choice applies to all stories we write together:
- Conversation only (default) — copy into your tracker manually
- Linear — push directly via Linear MCP
- Jira — push directly via Atlassian MCP
- Asana — push via Asana MCP (if connected)
- Notion — create as a Notion page
- Other — check available connectors via
search_mcp_registry
Collect answers before proceeding. If the user provides a brief description instead of answering individually, extract what you can and ask follow-up questions for gaps.
Session-level tracker choice: Once the user picks a tracker, remember it for all stories in this conversation. Don't re-ask for each story.
Step 2: Clarify and fill gaps
Based on the user's input, generate:
- A revised, clarified description of what the story should cover
- Follow-up questions to fill gaps — explain why each question matters
Iterate: the user answers your questions, you refine the story description. Repeat until you have enough detail. If the user says they're done answering questions, proceed to Step 3.
Step 3: Check story size
Evaluate whether the story can be completed in 1-3 days. If it's too large:
- Propose splitting into smaller, vertically sliced stories
- Each split must deliver end-to-end value independently
- Never split by technical layer (no "frontend story" and "backend story")
- Get user confirmation on the split before writing
Step 4: Write the story
Use the format the user chose in Step 1. If they chose "Default" or didn't specify, use the standard structure below. For Jira-optimized, Linear-optimized, or Custom formats, adapt the output to match while preserving the Gherkin acceptance criteria and all essential content.
Default format
For each story, output using this structure:
## Title
(Brief description of what the user can do)
## Description
As a (user persona), I want to be able to (action), so that (purpose/value).
## Acceptance Criteria
Scenario 1: (Scenario name — happy path)
Given (initial context / system state)
When (user action or event)
Then (expected observable outcome)
And (additional outcome if needed)
Scenario 2: (Scenario name — error path)
Given (initial context)
When (action that triggers error)
Then (error handling behavior)
Scenario 3: (Scenario name — edge case)
Given (edge case setup)
When (user action)
Then (expected behavior)
(Continue numbering scenarios as needed)
## Technical Details
- (Bullet points for engineers, designers, or data scientists)
- (Include unknowns)
## Links to Designs
- (Figma links, wireframes, or "None yet")
## Out of Scope
- (What is NOT included in this story)
- (Reference related stories that cover excluded items)
## Added Context
- (Environment notes, dependencies, or implementation considerations)
Jira-optimized format
## Summary
(Brief description — becomes the Jira issue summary)
## Description
As a (user persona), I want to be able to (action), so that (purpose/value).
(Additional context paragraph if needed)
## Acceptance Criteria
Scenario 1: (Scenario name — happy path)
Given (initial context / system state)
When (user action or event)
Then (expected observable outcome)
Scenario 2: (Scenario name — error path)
Given (initial context)
When (action that triggers error)
Then (error handling behavior)
(Continue numbering scenarios as needed)
## Story Points
(Estimate: 1, 2, 3, 5, 8, 13 — or leave for team to estimate)
## Labels
(Comma-separated labels for Jira)
## Links to Designs
- (Figma links, wireframes, or "None yet")
Linear-optimized format
## (Story title — becomes the Linear issue title)
As a (user persona), I want to be able to (action), so that (purpose/value).
### Acceptance Criteria
**Scenario 1: (Scenario name — happy path)**
- Given (initial context / system state)
- When (user action or event)
- Then (expected observable outcome)
**Scenario 2: (Scenario name — error path)**
- Given (initial context)
- When (action that triggers error)
- Then (error handling behavior)
### Technical Details
- (Bullet points for engineers)
### Out of Scope
- (What is NOT included)
### Context
- (Dependencies, related issues)
Scenario (narrative) format
## {{scenario_title}}
**User:** {{persona name and brief description}}
**Context:** {{what situation the user is in, what they care about}}
### The scenario
{{Narrative paragraph describing the user's goal, the steps they take, what they see, what they experience. Written in present tense, second or third person. Focus on the user's mental model and emotional state, not UI mechanics.}}
### What success looks like
{{1-2 sentences describing the end state from the user's perspective.}}
### Open questions
- {{Design questions this scenario raises}}
Custom format
If the user chose "Custom," ask them to describe or paste their team's template. Adapt the story content to fit their structure while preserving Gherkin acceptance criteria. If their template doesn't include AC, add a Gherkin section anyway and explain why.
Step 5: Review and iterate
Present the story and ask:
- Does the description capture the right user need?
- Are there missing scenarios in the acceptance criteria? (Think: what can go wrong? What are the boundary conditions?)
- Is the scope right — not too big, not too small?
- Anything to add to technical details or out of scope?
Make edits as needed. Continue until the user is satisfied with the story text.
Step 5.5: Testability check
Before moving to prototype or tracker push, run a quick testability audit on the acceptance criteria:
For each scenario, check:
- Could this generate a failing e2e test? (If not, the Then clause needs to be more specific.)
- Are boundary values specified? ("minimum 3 characters" not "valid input")
- Are error messages exact? ("Email is required" not "an error message")
- Are Given conditions observable state, not internal system state?
- Are Then outcomes observable (UI text, navigation, visual state), not database internals?
If gaps are found:
- Fix obvious specificity issues inline (replace "valid" with actual constraints)
- For deeper expansion, suggest: "Want me to run
/test-case-designto expand boundaries and partitions, or/edge-case-huntto find persona-driven edge cases?"
This check takes 30 seconds and prevents stories from entering iteration with untestable AC.
Step 6: Generate UI prototype (optional)
If the user requested a prototype in Step 1 (or requests one now), generate a quick UI mockup using the /artium-prototype approach:
-
Determine what to prototype. Extract the key UI elements from the story:
- What screens or views does the user interact with?
- What are the primary actions (buttons, forms, navigation)?
- What states need to be shown (empty, loading, success, error)?
-
Generate the HTML prototype. Create a branded HTML file using the prototype scaffold:
- Save to
./prototypes/(story-slug)/index.html - Use Tailwind CSS + brand tokens
- Include the key interactions from the acceptance criteria
- Focus on the happy path — this is a communication tool, not production code
- Save to
-
Preview the prototype. Start a live preview so the user can see it in their browser. If Claude Preview is not available, tell the user to open the HTML file directly.
-
Capture a screenshot. Take a screenshot of the prototype for use in Step 8 (attaching to the project tracker).
When to recommend a prototype:
- The story involves a new UI screen or significant UI change
- Stakeholders need to "see it" before committing
- The acceptance criteria describe specific layout or interaction patterns
- The team is discussing visual design alongside functional requirements
When to skip:
- The story is purely backend/API work
- The story is a bug fix with no UI changes
- Designs already exist in Figma (link those instead)
Step 7: Human-in-the-loop review
This is the approval gate before pushing anything to a project tracker. Present the complete package:
- Show the story — the full formatted story text from Step 4
- Show the prototype (if generated) — the live preview or screenshot from Step 6
- Ask for explicit approval:
Review checkpoint: Here's the complete story package. Before I push this to your tracker:
- Is the story text final? Any last changes?
- Does the prototype match your expectations? (if applicable)
- Ready to push to (Linear / Jira / Asana / Notion)? Or keep in conversation only?
Do not proceed to Step 8 until the user explicitly approves. If the user wants changes, loop back to Step 5 (for story text) or Step 6 (for prototype).
Step 8: Push to project tracker (optional)
If the user approved pushing to a tracker in Step 7, create the story in their chosen tool. Use the session-level tracker choice from Step 1 — don't re-ask.
Before pushing, check connectivity: For any tracker, verify the MCP connector is available. If not:
- Use
search_mcp_registrywith relevant keywords (e.g.,["jira", "atlassian"]or["linear"]) - Use
suggest_connectorsto present the connection option to the user - Wait for the user to connect, then retry
Linear (via MCP)
Tool: save_issue
Parameters:
title: (Story title)
description: (Full story markdown — description, AC, technical details, links, out of scope, added context)
team: (ask user if not known — remember for subsequent stories)
priority: (ask or infer: 1=Urgent, 2=High, 3=Normal, 4=Low)
labels: (if applicable)
project: (if applicable)
If a prototype was generated:
- Include the prototype file path in the description:
**Prototype:** ./prototypes/(story-slug)/index.html - Use
create_attachmentto attach a screenshot of the prototype to the issue
After pushing: Confirm success and share the Linear issue URL. Example: "Created LIN-123: (Story title) — view in Linear: (issue URL)"
Jira (via Atlassian MCP)
Tool: jira_create_issue (via Atlassian MCP)
Parameters:
project: (ask user if not known — remember for subsequent stories)
issuetype: Story
summary: (Story title)
description: (Full story markdown — use Jira-optimized format if selected in Step 1)
labels: (if applicable, comma-separated)
priority: (ask or infer: Highest, High, Medium, Low, Lowest)
If a prototype was generated:
- Include the prototype link in the description
- Attach prototype screenshot if the Jira MCP supports attachments
After pushing: Confirm success and share the Jira issue URL. Example: "Created PROJ-456: (Story title) — view in Jira: (issue URL)"
If Atlassian MCP is not connected:
- Run
search_mcp_registrywith keywords["jira", "atlassian"] - Run
suggest_connectorswith the returned UUID - Tell the user: "To push stories to Jira, connect the Atlassian connector. Click Connect above, then I'll push your stories."
- Once connected, retry the push
Asana (via MCP)
If the Asana MCP connector is available:
- Create a task with the story text as the description
- Attach prototype screenshot if available
- If not connected, use
search_mcp_registrywith keywords["asana", "tasks"]andsuggest_connectors
Notion (via MCP)
Tool: notion-create-pages
Parameters:
pages: [{
properties: { title: "(Story title)" },
content: (Full story markdown)
}]
parent: (ask user for page or database — remember for subsequent stories)
If a prototype was generated, embed the prototype screenshot or link in the page content.
Fallback: conversation only
If no PM tool is selected or available, the story stays in the conversation. Remind the user they can copy it into their tracker manually.
Batch push (multiple stories in a session)
If multiple stories were written during the session, push them together after the final review:
-
Show a summary before pushing:
Ready to push 4 stories to Linear (team: Platform). Confirm?
- User can filter dashboard by date range
- User can export dashboard as PDF
- User receives email notification on threshold breach
- Admin can configure notification thresholds
-
Push all stories sequentially after user confirms — don't re-ask per story.
-
Report results as a batch:
Pushed 4 stories to Linear:
- LIN-123: User can filter dashboard by date range
- LIN-124: User can export dashboard as PDF
- LIN-125: User receives email notification on threshold breach
- LIN-126: Admin can configure notification thresholds
All stories created in project "Dashboard v2", team "Platform".
-
If any story fails, report which ones succeeded and which failed. Offer to retry failed stories individually.
Session-level settings to remember: Once the user provides team, project, board, or parent page for the first story push, reuse those settings for all subsequent stories in the session. Don't re-ask.
Related skills
/story-workshop— facilitated multi-step flow: persona → acceptance criteria → story → export (skill set)/artium-prototype— standalone branded prototype creation (used by Step 6)/story-split— break large stories into smaller slices/story-review— review existing stories for quality/persona-create— create personas to reference in story descriptions/jtbd-analysis— connect stories to jobs-to-be-done/test-case-design— expand AC with BVA, equivalence partitioning, and decision tables/edge-case-hunt— find persona-driven edge cases to add to AC/e2e-test-gen— generate failing Playwright e2e tests from the Gherkin AC
Output locations
| Format | Location | Notes |
|---|---|---|
| Story text | Conversation | Always produced. Source of truth until pushed. |
| Prototype | ./prototypes/(story-slug)/index.html | Optional. Branded HTML mockup. |
| Linear issue | Linear workspace | Via Linear MCP. Prototype attached if generated. |
| Jira issue | Jira project | Via Atlassian MCP (if connected). |
| Asana task | Asana project | Via Asana MCP (if connected). |
| Notion page | Notion workspace | Via Notion MCP. |
Example Output
Input
- Feature: A warehouse manager at Meridian Logistics needs to receive real-time low-stock alerts when any SKU in their assigned facility drops below a configurable reorder threshold
- Persona: Jordan, a warehouse operations manager — oversees 3 facilities, monitors 2,000+ SKUs daily, currently catches stockouts only during manual cycle counts
- Business value: Reduce emergency purchase orders (currently costing ~$40K/quarter in rush shipping fees) and prevent line stoppages
- Technical constraints: Alerts must integrate with the existing Twilio SMS gateway; inventory levels are updated via a nightly batch job and a real-time webhook from the WMS (WarehouseIQ); threshold config stored in Postgres
- Format: Default (standard)
- Tracker: Linear (team: Inventory Platform, project: WMS Integration Q3)
Output (abbreviated)
Title
Warehouse manager receives real-time low-stock alert when SKU drops below reorder threshold
Description
As a warehouse operations manager, I want to receive an SMS alert when any SKU in my assigned facility falls below its configured reorder threshold, so that I can initiate a purchase order before a stockout causes a line stoppage.
Acceptance Criteria
Scenario 1: Alert triggered on real-time webhook update (happy path)
Given Jordan is the assigned manager for Facility 7
And SKU "WH-8821-BLK" has a reorder threshold of 50 units
And current inventory is 52 units
When the WarehouseIQ webhook posts an inventory update reducing stock to 48 units
Then Twilio sends an SMS to Jordan's registered mobile number within 60 seconds
And the message reads: "LOW STOCK ALERT — Facility 7: WH-8821-BLK has 48 units remaining (threshold: 50). Reorder now: [link]"
And the alert is logged in the alerts table with status "sent" and a UTC timestamp
Scenario 2: Alert triggered on nightly batch job update
Given SKU "WH-3302-WHT" has a reorder threshold of 100 units
And the nightly batch job processes inventory reconciliation at 02:00 UTC
When the batch updates stock for WH-3302-WHT to 91 units
Then an SMS alert is dispatched to the facility manager within 5 minutes of batch completion
And the alert payload matches the same format as real-time alerts
Scenario 3: No duplicate alert within 24-hour suppression window
Given Jordan received a low-stock alert for SKU "WH-8821-BLK" at 09:15 UTC today
When a second inventory update reduces stock further to 40 units at 11:30 UTC
Then no additional SMS is sent for that SKU within the 24-hour suppression window
And the suppressed alert is logged with status "suppressed" and reason "duplicate_within_window"
Scenario 4: Threshold not yet configured for SKU (edge case)
Given SKU "WH-9001-GRY" exists in inventory but has no reorder threshold set
When an inventory update reduces stock to any quantity
Then no alert is triggered
And the system logs a warning: "No threshold configured for SKU WH-9001-GRY — alert skipped"
Scenario 5: Twilio delivery failure (error path)
Given a low-stock condition is detected for SKU "WH-8821-BLK"
When Twilio returns a 5xx error on the SMS send attempt
Then the system retries delivery up to 3 times with exponential backoff (30s, 60s, 120s)
And if all retries fail, the alert status is set to "failed" and a PagerDuty incident is raised
And Jordan does not receive a partial or malformed message
Scenario 6: Manager has no registered mobile number (error path)
Given Facility 7's assigned manager has no mobile number in the user profile
When a low-stock condition is detected
Then the alert falls back to email delivery using the manager's registered email
And the alert log records delivery_channel as "email_fallback" with a note explaining why
Technical Details
- Subscribe to the WarehouseIQ real-time webhook at
/api/v2/inventory-events; event typeinventory.updatedcarriessku_id,facility_id,quantity_on_hand - Compare
quantity_on_handagainstreorder_thresholdstable (Postgres) on every event; index on(sku_id, facility_id) - Implement a
alert_suppressiontable keyed on(sku_id, facility_id, manager_id)with alast_sent_attimestamp; suppress iflast_sent_at > NOW() - INTERVAL '24 hours' - Twilio SMS via existing gateway service (
notifications-svc); do not call Twilio directly from inventory service - The deep-link in the SMS should route to the PO creation flow in the Meridian portal (
/purchase-orders/new?sku=&facility=) - Nightly batch job runs via Airflow DAG
wms_inventory_reconciliation; alert logic must also fire in the post-reconciliation step - Unknown: Confirm with WarehouseIQ whether webhook events are guaranteed-delivery or fire-and-forget — affects retry architecture
Links to Designs
- Figma: None yet — UI prototype generated (see
./prototypes/low-stock-alert/index.html) - Alert message format agreed in Slack thread #wms-integration (2024-01-18)
Out of Scope
- Email-only alert channel (covered in story: "Manager receives low-stock alert via email" — INV-88)
- Threshold configuration UI — Jordan setting reorder levels per SKU (separate story: INV-91)
- Multi-manager escalation (e.g., if no one acknowledges within N hours) — post-MVP
- Push notifications via the Meridian mobile app — roadmap item
Added Context
- Suppression window (24h) is a product decision; eng should make it configurable via env var so it can be tuned without a deploy
- WarehouseIQ sandbox available at
wms-sandbox.meridianlogistics.internalfor QA testing - PagerDuty integration for failed alerts uses the existing
ops-criticalservice key
Testability Audit ✓
| Scenario | Observable Then? | Boundary values specified? | Error messages exact? |
|---|---|---|---|
| 1 — Happy path webhook | ✅ SMS content defined verbatim | ✅ 60s SLA explicit | ✅ |
| 2 — Batch job | ✅ 5-min window | ✅ 02:00 UTC trigger | ✅ |
| 3 — Suppression | ✅ Log status "suppressed" | ✅ 24h window named | ✅ |
| 4 — No threshold | ✅ Warning log message defined | n/a | ✅ |
| 5 — Twilio failure | ✅ Retry counts + backoff values | ✅ 30/60/120s explicit | ✅ |
| 6 — No phone number | ✅ email_fallback in log | n/a | ✅ |
Review checkpoint: Story is ready. Prototype generated at
./prototypes/low-stock-alert/index.html(shows the alert SMS preview and the suppression log admin view).Shall I push this to Linear — team Inventory Platform, project WMS Integration Q3? Any final changes first?