Skip to main content
Product Management/story-write

Story Write

You need a complete story with acceptance criteria — optionally generate a UI prototype, review with stakeholders, and push to Linear, Jira, Asana, or Notion.

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:

  1. What feature or capability does this story cover? (Describe what the user should be able to do.)
  2. 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-create first 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?"
  3. Why does this matter? (What value does this deliver to the user or business?)
  4. Any known technical constraints or dependencies? (APIs, third-party services, design system requirements)
  5. Any designs or wireframes to reference? (Figma links, screenshots, or descriptions)
  6. 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.)
  7. 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
  8. 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:

  1. A revised, clarified description of what the story should cover
  2. 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-design to expand boundaries and partitions, or /edge-case-hunt to 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:

  1. 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)?
  2. 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
  3. 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.

  4. 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:

  1. Show the story — the full formatted story text from Step 4
  2. Show the prototype (if generated) — the live preview or screenshot from Step 6
  3. 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:

  1. Use search_mcp_registry with relevant keywords (e.g., ["jira", "atlassian"] or ["linear"])
  2. Use suggest_connectors to present the connection option to the user
  3. 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_attachment to 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:

  1. Run search_mcp_registry with keywords ["jira", "atlassian"]
  2. Run suggest_connectors with the returned UUID
  3. Tell the user: "To push stories to Jira, connect the Atlassian connector. Click Connect above, then I'll push your stories."
  4. 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_registry with keywords ["asana", "tasks"] and suggest_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:

  1. Show a summary before pushing:

    Ready to push 4 stories to Linear (team: Platform). Confirm?

    1. User can filter dashboard by date range
    2. User can export dashboard as PDF
    3. User receives email notification on threshold breach
    4. Admin can configure notification thresholds
  2. Push all stories sequentially after user confirms — don't re-ask per story.

  3. 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".

  4. 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

FormatLocationNotes
Story textConversationAlways produced. Source of truth until pushed.
Prototype./prototypes/(story-slug)/index.htmlOptional. Branded HTML mockup.
Linear issueLinear workspaceVia Linear MCP. Prototype attached if generated.
Jira issueJira projectVia Atlassian MCP (if connected).
Asana taskAsana projectVia Asana MCP (if connected).
Notion pageNotion workspaceVia 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 type inventory.updated carries sku_id, facility_id, quantity_on_hand
  • Compare quantity_on_hand against reorder_thresholds table (Postgres) on every event; index on (sku_id, facility_id)
  • Implement a alert_suppression table keyed on (sku_id, facility_id, manager_id) with a last_sent_at timestamp; suppress if last_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.internal for QA testing
  • PagerDuty integration for failed alerts uses the existing ops-critical service key

Testability Audit ✓

ScenarioObservable 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 definedn/a
5 — Twilio failure✅ Retry counts + backoff values✅ 30/60/120s explicit
6 — No phone number✅ email_fallback in logn/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?