
Job Stories
- 91 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
job-stories is a skill that writes Jobs-to-Be-Done job stories in the When/Want/So format to define product requirements around user situations instead of personas.
About
This skill helps write product requirements as Jobs-to-Be-Done job stories using the format 'When [situation], I want to [motivation], so I can [outcome].' It provides a discovery canvas that maps customer jobs, pains, and gains into story components. Product managers and teams use it during feature definition and backlog creation to keep requirements grounded in user context rather than personas.
- Writes Jobs-to-Be-Done stories in the When/Want/So format instead of persona-based user stories
- Includes a JTBD discovery canvas mapping customer jobs, pains, and gains to story components
- Focuses requirements on user situation and motivation rather than role
Job Stories by the numbers
- 91 all-time installs (skills.sh)
- Ranked #1,405 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
job-stories capabilities & compatibility
- Capabilities
- product planning · requirements writing · backlog grooming
- Use cases
- planning · project management
- Pricing
- Free
What job-stories says it does
Jobs-to-Be-Done story writing that focuses on user situations and motivations rather than personas.
When [situation], I want to [motivation], so I can [outcome].
npx skills add https://github.com/borghei/claude-skills --skill job-storiesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 91 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Write JTBD job stories for feature definition and backlog creation grounded in user situation and motivation.
Who is it for?
Product managers defining features or building a backlog who want requirements grounded in user context.
Skip if: Purely technical work with no user-facing situation, or teams needing strategic backlog items with business context.
When should I use this skill?
You need to articulate what to build and why using the Jobs-to-Be-Done framework.
What you get
Produces job stories in When/Want/So format grounded in observable, testable user situations.
- Job stories
- JTBD discovery canvas
- Prioritized opportunity statements
By the numbers
- Job story has three components: situation, motivation, outcome
- Discovery canvas has four sections: customer jobs, pains, gains, assumptions
Files
Job Stories Expert
Overview
Write job stories using the Jobs-to-Be-Done (JTBD) framework. Unlike traditional user stories that focus on roles ("As a user..."), job stories focus on the situation, motivation, and desired outcome. This shift produces requirements that are more grounded in real user context and less likely to encode assumptions about who the user is.
When to Use
- Feature definition -- When you need to articulate what to build and why, grounded in user context.
- Backlog creation -- When populating a backlog with work items that stay focused on user outcomes.
- Requirement workshops -- When collaborating with stakeholders to define what "done" looks like.
- Design briefs -- When giving designers context about the situation and motivation behind a feature.
When NOT to Use
- When you need strategic backlog items with business context -- use
wwas/instead. - When you need lightweight stories for a team already fluent in user story format.
- When the work is purely technical with no direct user-facing situation.
JTBD Discovery Canvas
Before writing job stories, run a Jobs-to-Be-Done discovery canvas to understand what customers are trying to achieve, where they struggle, and what outcomes they value most. This canvas produces the situational context that feeds directly into job story writing.
When to Run Discovery First
- Starting a new product area with no existing user research.
- Entering a new market segment or persona.
- Backlog items feel disconnected from real user needs.
- Team debates "what to build" without grounding in customer jobs.
Discovery Canvas Template
1. Customer Jobs
Functional Jobs:
- [Tasks customers need to perform — keep each bullet 4-8 words]
Social Jobs:
- [Ways customers want to be perceived socially]
Emotional Jobs:
- [Emotional states customers seek to achieve or avoid]
2. Pains
Challenges:
- [Obstacles customers face when performing their jobs]
Costliness:
- [What customers find too costly in time, money, or effort]
Common Mistakes:
- [Frequent errors customers make that could be prevented]
Unresolved Problems:
- [Problems not solved by current solutions]
3. Gains
Expectations:
- [Ways current solutions fail to meet expectations]
Savings:
- [Ways savings in time, money, or effort would delight customers]
Adoption Factors:
- [Factors that would increase the likelihood of adoption]
Life Improvement:
- [Ways a solution could make customers' lives easier or more enjoyable]
4. Assumptions to Validate
- [Assumption 1]
- [Assumption 2]
- [Assumption 3]
From Canvas to Job Stories
Each row in the discovery canvas maps to job story components:
| Canvas Section | Maps To | Example |
|---|---|---|
| Customer Jobs (Functional) | Motivation (I want to...) | "I want to reconcile my accounts" |
| Pains (Challenges) | Situation (When...) | "When I discover a discrepancy during month-end close" |
| Gains (Expectations) | Outcome (So I can...) | "So I can submit accurate reports before the deadline" |
Process: 1. Run the discovery canvas with stakeholders or from research data. 2. Identify the highest-impact job-pain-gain clusters. 3. Write one job story per cluster using the When/Want/So format below. 4. Validate assumptions before committing to build.
Next Steps After Discovery
1. Generate prioritized opportunity statements from the canvas (Recommended) 2. Convert the canvas into a value proposition draft 3. Generate interview questions to validate top assumptions 4. Generate a hypothesis backlog for rapid experiments
---
The Job Story Format
When [situation], I want to [motivation], so I can [outcome].| Component | Focus | Question It Answers |
|---|---|---|
| When [situation] | The context or trigger | What is happening when the user needs this? |
| I want to [motivation] | The action or capability desired | What does the user want to do in this moment? |
| So I can [outcome] | The expected result or benefit | What does success look like for the user? |
Key Principle: Focus on the Job, Not the Role
User stories say "As a [role]..." which anchors the requirement to a persona. Job stories remove the role and instead describe the situation -- the real-world context that creates the need. This matters because:
- The same person may have different needs in different situations.
- Different people in the same situation often have the same need.
- Situations are observable and testable; roles are abstract labels.
Example comparison:
| User Story | Job Story |
|---|---|
| As a budget manager, I want to see a spending report so I can track expenses. | When I am preparing my weekly budget, I want to see spending so far this period, so I can adjust before overspending. |
| As an admin, I want to export user data so I can comply with data requests. | When I receive a data subject access request, I want to export all data associated with that person, so I can respond within the 30-day legal deadline. |
The job story version is more specific, more testable, and provides better design guidance.
Writing Good Job Stories
Situations (When...)
Good situations are:
- Specific and observable -- You could watch someone be in this situation.
- Contextual -- They describe what is happening, not who the person is.
- Triggering -- They explain what creates the need right now.
| Weak Situation | Strong Situation |
|---|---|
| When I use the app | When I open the app for the first time after signing up |
| When I need data | When I am in a client meeting and need to reference last quarter's results |
| When I manage my team | When a team member submits a time-off request that overlaps with a project deadline |
Motivations (I want to...)
Good motivations are:
- Action-oriented -- They describe doing something, not having something.
- Solution-agnostic -- They describe the capability, not the implementation.
- Singular -- One motivation per story.
| Weak Motivation | Strong Motivation |
|---|---|
| I want a dashboard | I want to see my team's progress at a glance |
| I want better notifications | I want to be alerted only when something requires my action |
| I want to manage users and permissions | I want to grant a new team member access to the project (split into two if needed) |
Outcomes (So I can...)
Good outcomes are:
- Benefit-focused -- They describe the end result, not the means.
- Measurable or observable -- You can tell if the outcome was achieved.
- Meaningful -- They connect to something the user genuinely cares about.
| Weak Outcome | Strong Outcome |
|---|---|
| So I can use the feature | So I can complete my weekly report in under 10 minutes |
| So I can be productive | So I can identify which tasks are blocked before standup |
| So I can do my job | So I can respond to the customer within our 4-hour SLA |
INVEST Quality Criteria
Apply INVEST to every job story before it enters the backlog:
| Criterion | Question | Red Flag |
|---|---|---|
| Independent | Can this story be delivered without depending on another story? | "This only works after story X is done" |
| Negotiable | Is the implementation open to discussion, or is it prescribing a solution? | Motivation says "I want a dropdown menu" instead of "I want to select from available options" |
| Valuable | Does the outcome deliver clear value to the user? | Outcome is vague ("so I can use it") or internal ("so the database is normalized") |
| Estimable | Can the team estimate the effort? | Situation is too vague to understand scope |
| Small | Can this be completed in one sprint? | Story covers multiple distinct situations or motivations |
| Testable | Can you write acceptance criteria that verify the outcome? | Outcome is subjective ("so I feel confident") |
Story Template
For each job story, produce a card with the following structure:
### [Title]
**Job Story:**
When [situation], I want to [motivation], so I can [outcome].
**Design:** [Link to design file or "TBD"]
**Acceptance Criteria:**
1. [ ] [Observable outcome that verifies the story is complete]
2. [ ] [Observable outcome]
3. [ ] [Observable outcome]
4. [ ] [Observable outcome]
5. [ ] [Observable outcome]
6. [ ] [Observable outcome]Acceptance Criteria Guidelines
- Write 6-8 acceptance criteria per story.
- Focus on outcomes, not implementation steps.
- Each criterion should be independently verifiable.
- Use the pattern: "[Thing] [does/shows/enables] [expected behavior] [under condition]."
- Include edge cases and error states, not just the happy path.
Good acceptance criteria examples:
1. The spending summary shows all transactions from the current period, grouped by category. 2. Transactions from previous periods are excluded from the current period total. 3. The summary updates within 5 seconds of a new transaction being recorded. 4. If no transactions exist for the current period, a message explains that no spending has been recorded yet. 5. The summary is accessible on mobile screens without horizontal scrolling. 6. Category totals match the individual transaction amounts (no rounding discrepancies).
Bad acceptance criteria examples (avoid):
- The API returns a 200 status code (implementation detail)
- The React component renders correctly (implementation detail)
- It works (not testable)
- The user is happy (not observable)
Worked Example
Context: A budgeting application for personal finance.
Weekly Budget Check
Job Story: When I am preparing my weekly budget on Sunday evening, I want to see how much I have spent so far this month by category, so I can decide where to cut back before the month ends.
Design: [Link to Figma mock]
Acceptance Criteria:
1. [ ] The spending view shows the current month's transactions grouped by category (e.g., groceries, dining, transport). 2. [ ] Each category displays the total spent and the remaining budget for that category. 3. [ ] Categories that have exceeded their budget are visually distinguished from those within budget. 4. [ ] Tapping a category shows the individual transactions within it. 5. [ ] The view loads within 2 seconds on a standard mobile connection. 6. [ ] If no budget has been set for a category, the category still appears with total spent but no remaining budget indicator. 7. [ ] The date range is fixed to the current calendar month and is clearly displayed. 8. [ ] A "last updated" timestamp shows when transaction data was last synced.
Integration with Other Skills
- Use
summarize-meeting/to capture the discovery conversations that inform job stories. - Use
wwas/when you need to add strategic business context (the "Why") to backlog items. - Feed completed job stories into
../jira-expert/for ticket creation. - Use
brainstorm-okrs/to connect job stories back to team objectives.
References
- See
references/jtbd-guide.mdfor Jobs-to-Be-Done theory, comparison with user stories, and story splitting techniques. - See
assets/job_story_template.mdfor ready-to-use templates.
Troubleshooting
| Problem | Likely Cause | Resolution |
|---|---|---|
| Team writes situations that are too vague ("When I use the app...") | Insufficient user research; situations invented at desk rather than observed | Require each situation to reference a specific interview quote, support ticket, or analytics event; use the "could you video this?" test |
| Motivations prescribe a specific solution ("I want a dropdown...") | Team conflates solution with capability; negotiability criterion failing | Rewrite using "I want to [verb] [object]" pattern without naming UI elements; apply the INVEST-N check before acceptance |
| Outcomes are not measurable ("So I can be productive") | Outcome too abstract; not grounded in observable behavior | Ask "How would you know the user achieved this?" -- if you cannot describe an observable signal, the outcome needs rewriting |
| Job stories are too large for a single sprint | Multiple situations or motivations packed into one story | Split by situation (different contexts become separate stories) or by outcome (different success criteria become separate stories) |
| Team defaults to user story format despite training | Habit and muscle memory; Jira templates still use "As a..." format | Update Jira issue templates to use JTBD format; run a conversion workshop with 5 real user stories rewritten as job stories |
| Acceptance criteria describe implementation steps instead of outcomes | Engineering team writing criteria from their perspective rather than the user's perspective | Apply the "would the user care about this?" filter; replace API/database criteria with observable behavior statements |
| Job stories lack connection to strategic objectives | JTBD format focuses on user context but does not inherently include business "why" | Pair each job story with a WWAS "Why" statement from wwas/; or add an optional "Supports:" field linking to an OKR |
Success Criteria
- 100% of job stories in the backlog follow the "When / I want to / So I can" format correctly
- All situations reference observable, specific contexts (pass the "could you video this?" test)
- All motivations are solution-agnostic (no UI element names or implementation details)
- Each story has 6-8 acceptance criteria focused on observable outcomes, not implementation
- Every job story passes all 6 INVEST criteria before entering a sprint
- Defect rate on stories written in JTBD format is 20%+ lower than stories written in traditional format (measured over 3 months)
- Team members can articulate the difference between a job story and a user story and choose the appropriate format for the context
Scope & Limitations
In Scope: Writing job stories using JTBD "When/Want/So" format, applying INVEST quality criteria, writing outcome-focused acceptance criteria, converting existing user stories to job stories, facilitating story-writing workshops, integrating job stories with Jira backlog items.
Out of Scope: Strategic backlog items with business context (hand off to wwas/), product ideation and opportunity discovery (hand off to discovery/brainstorm-ideas/), detailed technical specifications, UX research and user interviewing methodology.
Limitations: Job stories work best when the team has access to real user research (interviews, observation, support data). Without user context, teams will invent situations that may not reflect reality. The format is less natural for purely technical or infrastructure work where there is no direct user situation. Job stories and user stories are complementary -- some teams use both formats for different types of work.
Integration Points
| Integration | Direction | What Flows |
|---|---|---|
wwas/ | Complementary | WWAS adds strategic "Why" context; job stories add situational "When" context. Use both when needed |
summarize-meeting/ | Meetings -> Stories | Discovery conversations and refinement sessions produce the situations that inform job stories |
../jira-expert/ | Stories -> Jira | Completed job stories become Jira tickets with structured descriptions |
discovery/brainstorm-ideas/ | Ideas -> Stories | Validated product ideas decompose into job stories for the backlog |
execution/brainstorm-okrs/ | OKRs -> Stories | Team objectives define the outcomes that job stories should connect to |
execution/prioritization-frameworks/ | Stories -> Prioritization | Job stories scored via RICE or other frameworks for sprint planning |
Job Story Templates
Single Job Story Card
[Title -- Short, Descriptive Name]
Job Story: When [specific situation or trigger], I want to [motivation -- what the user wants to do], so I can [outcome -- the benefit or result the user achieves].
Design: [Link to Figma / Sketch / wireframe, or "TBD"]
Acceptance Criteria:
1. [ ] [Observable outcome that verifies the story is complete] 2. [ ] [Observable outcome under a specific condition] 3. [ ] [Edge case or error state handling] 4. [ ] [Performance or responsiveness criterion] 5. [ ] [Accessibility or cross-platform criterion] 6. [ ] [Data accuracy or consistency criterion]
INVEST Check:
| Criterion | Pass? | Notes |
|---|---|---|
| Independent | [ ] | Can be delivered without other stories |
| Negotiable | [ ] | Implementation is open to discussion |
| Valuable | [ ] | Outcome is meaningful to the user |
| Estimable | [ ] | Team can estimate the effort |
| Small | [ ] | Completable in one sprint |
| Testable | [ ] | Acceptance criteria are verifiable |
---
Batch Job Story Worksheet
Use this worksheet to draft multiple job stories in a single session. Fill in the table first, then expand the best candidates into full story cards.
| # | Situation (When...) | Motivation (I want to...) | Outcome (So I can...) | Priority | Notes |
|---|---|---|---|---|---|
| 1 | |||||
| 2 | |||||
| 3 | |||||
| 4 | |||||
| 5 | |||||
| 6 | |||||
| 7 | |||||
| 8 |
Batch Review Checklist
After drafting, review each story against these questions:
- [ ] Is the situation specific and observable (not "when I use the app")?
- [ ] Is the motivation solution-agnostic (not naming a UI element)?
- [ ] Is the outcome meaningful and measurable (not "so I can use it")?
- [ ] Is each story independent enough to deliver on its own?
- [ ] Are any stories too large and need splitting?
- [ ] Are there duplicate or overlapping stories to merge?
---
Acceptance Criteria Template (Given/When/Then Adapted for JTBD)
For teams that prefer structured acceptance criteria, adapt the Given/When/Then format to align with the job story:
Given [the situation from the job story],
When [the user takes the action from the motivation],
Then [the expected outcome is achieved].Example
Job Story: When I am preparing my weekly budget on Sunday evening, I want to see how much I have spent so far this month by category, so I can decide where to cut back before the month ends.
Acceptance Criteria:
Given I am viewing the budget screen during an active month,
When the spending summary loads,
Then I see all transactions grouped by category with totals for each.
Given a category has exceeded its budget,
When the spending summary is displayed,
Then that category is visually highlighted as over-budget.
Given no transactions exist for the current month,
When the spending summary loads,
Then a message explains that no spending has been recorded yet.
Given I tap on a specific category,
When the category detail view opens,
Then I see all individual transactions within that category for the current month.
Given I am on a mobile device,
When the spending summary is displayed,
Then all content is readable without horizontal scrolling.
Given a new transaction is recorded,
When I refresh the spending summary,
Then the updated totals reflect the new transaction within 5 seconds.---
Story Mapping Layout
Use this layout to organize job stories into a story map for release planning:
Activities (user goals):
[Goal A] [Goal B] [Goal C]
Steps (situations):
[Situation A1] [A2] [A3] [Situation B1] [B2] [Situation C1] [C2] [C3]
Stories (per situation, ordered by priority):
A1-Story1 A2-Story1 B1-Story1 B2-Story1 C1-Story1
A1-Story2 A2-Story2 B1-Story2 C1-Story2
A2-Story3
------- Release 1 line -------
A1-Story3 B1-Story3 B2-Story2 C1-Story3 C3-Story1
------- Release 2 line -------Each row below the release line represents lower-priority stories for future releases.
Example: 15 Job Stories for Wayfinder Onboarding
Real-world scenario showing how to write JTBD job stories end-to-end.
Context
Wayfinder is a Series-B B2B project-management SaaS. The Onboarding & Activation squad is redesigning the new-workspace onboarding flow. The previous "As a user, I want..." stories produced generic requirements ("the user wants a fast setup") that designers couldn't act on. The PM (Sam) is switching to JTBD job stories for this work.
Sam has 14 customer interviews from the last 6 weeks plus session-replay data from 200+ recent signups. She'll run a JTBD discovery canvas with the squad on 2026-05-22, then write 15 job stories covering the onboarding flow.
Inputs
- 14 customer interviews (recorded + transcribed)
- 200 session replays from Pendo
- Squad: 1 PM, 1 EM, 3 engineers, 1 designer, 1 CSM
- Goal: 15 job stories that cover signup -> first project -> first invite -> first task -> activation
- 90-minute workshop
Applying the skill
1. Ran the JTBD discovery canvas with the squad for 30 minutes. Surfaced 7 functional jobs, 5 pains, 5 gains. 2. Mapped canvas rows to job-story components: Functional jobs -> Motivation; Pains -> Situation; Gains -> Outcome. 3. Drafted 15 job stories in the When / I want / So I can format, covering signup through first-week activation. 4. Reviewed each for INVEST applicability. Job stories pass V (valuable) and T (testable) by construction if the outcome is named; verified S (small) by sizing each at <= 5 days. 5. Cross-referenced stories against the canvas to ensure every pain and gain is addressed by at least one story.
Key decision quoted: "We do not write 'As a user, I want X' anywhere in this backlog. The format pretends to be user-centered but it lets us avoid naming the situation that triggers the need."
The artifact
````markdown
Wayfinder Onboarding Job Stories (v1)
PM: Sam (Onboarding & Activation) Workshop date: 2026-05-22 Source: 14 interviews + 200 session replays + squad canvas Format: When ... / I want ... / So I can ...
Discovery canvas summary (condensed)
Functional jobs
- Get my team set up so we can start tracking work
- Migrate work-in-progress from spreadsheets/Trello/Asana
- Demonstrate Wayfinder's value to my manager in week 1
- Configure permissions so the right people see the right work
- Connect Wayfinder to Slack so the team doesn't have to leave Slack
- Avoid losing visibility on work already in flight
Pains
- "I'm setting this up at 9pm; my team needs it by tomorrow's standup"
- "I have a 60-task spreadsheet I can't lose"
- "I'm worried about looking incompetent if I pick the wrong setup"
- "Standups happen even when the tool isn't fully set up"
- "Old tool had everything wired to Slack; switching breaks our flow"
Gains
- "Setup takes < 20 min start to finish"
- "I can import what I have in 1 click"
- "Templates that match how my team actually works"
- "Slack notifications working before standup tomorrow"
The 15 job stories
Signup phase
JS-1 -- Workspace owner: setup-time anxiety
When I sign up for Wayfinder at the end of my work day
knowing my team needs it operational tomorrow,
I want to complete the essential setup in under 20 minutes,
So I can stop worrying and start standup tomorrow with the tool ready.Source: Interview #4 (Maya, head of analytics, signed up at 21:14 the night before a sprint) Acceptance:
- Time-to-first-project < 5 min
- Time-to-first-invite < 2 min
- Total wizard flow < 20 min p85
- Wizard is resumable across sessions
Sizing: 5 days
JS-2 -- Workspace owner: workspace identity
When I create a new Wayfinder workspace for my company,
I want to name it and brand it with our logo and color,
So I can quickly orient teammates that this is "our" tool, not generic SaaS.Source: 6/14 interviews mentioned wanting to see company name in the header on first login Acceptance:
- Workspace name + logo + accent color set in step 1
- Header reflects branding from first page-load post-signup
Sizing: 3 days
JS-3 -- Workspace owner: choosing a template
When I'm new to project-management tools and unsure how to structure work,
I want to pick from templates matched to my team type (engineering / marketing / ops / design),
So I can avoid blank-page anxiety and start from something credible.Source: Interview #11 (first-time PM tool buyer); 4 other interviews showed similar Acceptance:
- 4 starter templates visible in wizard step 2
- Each template is one-click; backed by 3-5 example tasks + boards
- User can edit or discard template after creation
Sizing: 5 days
First-project phase
JS-4 -- Workspace owner: import from spreadsheet
When I have an active 60-task spreadsheet and I'm switching to Wayfinder,
I want to paste or upload the spreadsheet and have it become a project,
So I can start using Wayfinder without losing my work-in-flight.Source: 9/14 interviews; the #1 reason customers stalled in past onboarding Acceptance:
- CSV upload or paste-from-clipboard import
- Auto-detect columns (title, owner, status, priority)
- Preview before commit
- Imports complete in < 30s for files up to 500 rows
Sizing: 8 days (split into 4a CSV upload, 4b paste import)
JS-5 -- Workspace owner: import from Trello
When my team has been on Trello for 18 months and we are switching,
I want a 1-click import that pulls boards, cards, comments, and assignees,
So I can preserve the team's institutional memory.Source: Interview #2, #8, #13 -- migrating from Trello Acceptance:
- OAuth flow to Trello
- Import maps Boards -> Projects, Cards -> Tasks, Lists -> Statuses
- Comments and assignees preserved
- Imports < 5 min for boards up to 500 cards
Sizing: 8 days (defer to next sprint)
Invite phase
JS-6 -- Workspace owner: bulk invite teammates
When I have 8 teammates who need access by tomorrow standup,
I want to paste 8 emails and have invites sent in one action,
So I can invite the whole team in under a minute.Source: 8/14 interviews; existing in-app feedback widget item Acceptance:
- Multi-email input field with validation
- Invites send within 30s
- Visible "8 of 8 sent" confirmation
Sizing: 3 days
JS-7 -- Workspace owner: optional invite step
When I'm onboarding solo and just want to evaluate the tool first,
I want to skip the team-invite step without feeling guilty about it,
So I can explore the product before committing to invite my team.Source: Session replays: 32% of solo signups bounce at the invite step in the old flow Acceptance:
- "Skip for now" CTA at the invite step
- Skipping does not penalize the user (no nag for 7 days)
- Skipped state is captured as
wizard_invite_skippedevent for analysis
Sizing: 2 days
JS-8 -- Invited teammate: first login experience
When I receive an invite email from my workspace owner,
I want to sign in and immediately see what work is mine,
So I can contribute on day 1 instead of poking around to find my place.Source: Interview #6 (invited teammate, week 1 perspective) Acceptance:
- Invite link goes directly to "My tasks" view
- View pre-filtered to tasks assigned to me
- Onboarding tour appears once, dismissable
Sizing: 3 days
First-task phase
JS-9 -- Workspace owner: create first task with assignee
When I'm setting up my first project and want to feel like work is happening,
I want to create a task with a title, owner, and due date in <30 seconds,
So I can stop configuring and start managing.Source: Interview #9; session replays show 78% of time in old wizard spent on task creation Acceptance:
- Single-line creation supports natural language ("@maya design the auth flow due Friday")
- Auto-parses owner and date; user confirms with one keypress
- Task visible in board immediately
Sizing: 5 days
JS-10 -- Workspace owner: first task feels like a milestone
When I finish creating my first real task in Wayfinder,
I want some signal that something meaningful just happened,
So I can confirm I am doing this right and feel motivated to continue.Source: 5 interviews mentioned "I never knew if I was on the right path" Acceptance:
- First-task creation triggers a celebratory confetti + "First task created!" toast
- Celebration is dismissable; does not re-fire for subsequent tasks
- Telemetry confirms first-task event fired
Sizing: 2 days
Integration phase
JS-11 -- Workspace owner: connect Slack before standup
When I'm onboarding and standup is tomorrow,
I want Slack notifications working without leaving the wizard,
So I can keep our standup ritual intact through the switch.Source: Interview #3, #5, #10; this is the make-or-break for several teams Acceptance:
- Slack OAuth from wizard step 4
- Sends a test message within 30s to confirm
- Default subscriptions sensible (task created, task completed, blocker added)
Sizing: 5 days
JS-12 -- Workspace owner: defer Slack to later
When I have a workspace using Microsoft Teams instead of Slack,
I want to skip the Slack step without feeling like onboarding is broken,
So I can finish onboarding and connect Teams later.Source: Interview #14 (Teams-only org) Acceptance:
- "I use Teams" CTA visible at Slack step
- "Skip integration" CTA also visible
- Skipped state does not block wizard completion
- Future: Teams integration deferred to v1.1 (out of scope here)
Sizing: 1 day
Activation phase (post-wizard)
JS-13 -- Workspace owner: see the value in week 1
When I'm in my first week of Wayfinder usage,
I want to see a dashboard that shows what my team got done this week,
So I can prove value to my manager and convince myself this was a good choice.Source: 7/14 interviews; activation literature on "aha moments" Acceptance:
- Dashboard available from main nav after first task created
- Shows count of tasks created, completed, in flight
- Updates in real time
Sizing: 5 days (defer to next sprint)
JS-14 -- Workspace owner: get a nudge if I stall
When I have not opened Wayfinder for 4 days after signup,
I want a friendly nudge with something specific I can do,
So I can re-engage instead of forgetting I have an account.Source: 38% of new workspaces show no activity 4 days post-signup Acceptance:
- Email sent on day 4 if no
user_activeevent since signup - Email subject and body A/B-tested
- Click-through to a specific in-app action (e.g., "create your first task")
- Unsubscribe honored
Sizing: 3 days
JS-15 -- Workspace owner: dismiss onboarding when I'm done
When I have completed the essentials and don't need the checklist anymore,
I want to dismiss the onboarding UI permanently,
So I can use the product without feeling babied.Source: Interview #1, #7 (both expert users; old flow nagged forever) Acceptance:
- Dismiss CTA on the onboarding checklist
- Dismissed state persists across sessions and devices
- Telemetry confirms dismissal event
Sizing: 1 day
Canvas coverage check
| Canvas item | Covered by job stories |
|---|---|
| Functional: get team set up | JS-1, JS-2, JS-6 |
| Functional: migrate work | JS-4, JS-5 |
| Functional: demonstrate value | JS-13 |
| Functional: configure permissions | (deferred to next sprint -- not in 15) |
| Functional: connect Slack | JS-11, JS-12 |
| Pain: setup time anxiety | JS-1 |
| Pain: 60-task spreadsheet | JS-4 |
| Pain: standup tomorrow | JS-11 |
| Pain: looking incompetent | JS-3, JS-10 |
| Gain: <20 min setup | JS-1 |
| Gain: 1-click import | JS-4, JS-5 |
| Gain: templates that match | JS-3 |
| Gain: Slack working before standup | JS-11 |
Coverage gap: workspace-level permissions setup. Captured as backlog item to address in the next workshop.
Sizing roll-up
| Story | Sizing (days) |
|---|---|
| JS-1 | 5 |
| JS-2 | 3 |
| JS-3 | 5 |
| JS-4 | 8 (split) |
| JS-5 | 8 (defer) |
| JS-6 | 3 |
| JS-7 | 2 |
| JS-8 | 3 |
| JS-9 | 5 |
| JS-10 | 2 |
| JS-11 | 5 |
| JS-12 | 1 |
| JS-13 | 5 (defer) |
| JS-14 | 3 |
| JS-15 | 1 |
| Total | 59 days |
Squad capacity ~22 days per sprint. Top-RICE ordered. JS-5, JS-13 deferred to next sprint.
Job-story format rules used here
1. Situation, not persona. Every story names a When that names a real situation, not a generic "as a user". 2. Outcome, not output. Every So I can names a desired outcome that the customer can verify (not the same as the engineering output). 3. Source-tagged. Every story cites the interview or replay it came from. No story is "obvious to PM". 4. Sized. Every story has a sizing estimate; INVEST-S is enforced. 5. Acceptance criteria are observable. No "the system is fast"; instead "p85 < 20 min". ````
Why this works
- Every story names the situation (When) -- 9pm with standup tomorrow, 60-task spreadsheet I can't lose. The persona is built into the situation, not abstracted as "user".
- Every story has a source citation -- interview number or session-replay statistic. Pure invention is excluded.
- The canvas coverage check catches the gap (permissions) instead of letting it surface in week 2 of build.
- Acceptance criteria are observable and quantitative ("p85 < 20 min", "30s import for 500 rows"), so QA and PM can both verify done.
- JS-15 (dismiss onboarding) is included -- the "expert user" perspective is captured, preventing the common onboarding-nags-everyone failure.
What's next
- Feed top stories into ../backlog-refinement/ for INVEST scoring before sprint planning.
- Pair with ../story-splitting/ for JS-4 (CSV + paste split) and JS-5 (Trello).
- Use ../prioritization-frameworks/ for RICE scoring across the 15.
- Use ../create-prd/ if any story grows into a feature initiative (e.g., a Trello migration tool).
- Feed activation metrics from JS-13, JS-14 into ../activation-funnel/.
Jobs-to-Be-Done (JTBD) Guide
Theory Overview
Jobs-to-Be-Done is a framework for understanding why people use products. The core insight: people do not buy products -- they "hire" products to do a job for them. When you understand the job, you build better products.
Key concepts:
- A job is a progress a person is trying to make in a particular circumstance.
- Jobs are stable over time. People have wanted to "get from A to B quickly" for centuries. The solutions change (horse, car, rideshare), but the job stays the same.
- Jobs have functional, emotional, and social dimensions. The functional job is the task. The emotional job is how the person wants to feel. The social job is how they want to be perceived.
Origin: Clayton Christensen popularized JTBD in The Innovator's Solution (2003). The framework has been further developed by Tony Ulwick (Outcome-Driven Innovation) and Alan Klement (job stories for product design).
Job Stories vs User Stories
User Story Format
As a [role], I want to [action], so that [benefit].Strengths:
- Simple and widely understood.
- Easy to map to personas.
- Good for teams with established persona research.
Weaknesses:
- Anchors thinking to roles/personas, which may not reflect real usage patterns.
- The "role" component often becomes a placeholder ("As a user...").
- Can lead to building features for personas instead of real situations.
Job Story Format
When [situation], I want to [motivation], so I can [outcome].Strengths:
- Focuses on context and trigger, which are observable and testable.
- Removes persona assumptions -- the same job may apply to multiple roles.
- Provides stronger design direction because the situation constrains the solution space.
- Naturally encourages empathy with the user's real circumstances.
Weaknesses:
- Requires deeper understanding of user context (more research investment).
- Less familiar to teams trained on user stories.
- Can feel more complex for simple CRUD features.
When to Use Each
| Scenario | Recommended Format |
|---|---|
| Team is new to agile and needs simplicity | User stories |
| Strong persona research exists | User stories |
| Product has diverse users in varied situations | Job stories |
| Building for a new market or unknown users | Job stories |
| Design-heavy work where context matters | Job stories |
| Internal tools with well-defined roles | User stories |
| Mixed team with varying experience | Start with user stories, evolve to job stories |
Writing Good Situations
The situation is the most important part of a job story. It provides the context that drives design decisions.
Techniques for Discovering Situations
1. Customer interviews -- Ask "Walk me through the last time you needed to [do the job]." Listen for the trigger and context. 2. Support ticket analysis -- Look at what users were trying to do when they hit problems. 3. Session recordings -- Watch where users hesitate, backtrack, or abandon flows. 4. "Five Whys" on feature requests -- When a customer requests a feature, ask why five times to find the underlying situation.
Situation Quality Checklist
- [ ] Describes a real-world moment, not an abstract need
- [ ] Includes temporal context (when does this happen?)
- [ ] Includes environmental context (where, with what constraints?)
- [ ] Is specific enough to guide design but not so specific it excludes valid use cases
- [ ] Does not mention the product or a specific solution
Writing Good Motivations
The motivation describes what the user wants to do in the moment. It should be solution-agnostic.
Common Mistakes
| Mistake | Example | Fix |
|---|---|---|
| Naming a specific UI element | "I want to click the export button" | "I want to get my data out of the system" |
| Combining multiple motivations | "I want to review and approve the request" | Split into two stories |
| Being too vague | "I want to manage things" | "I want to see which tasks are overdue" |
| Describing the system's behavior | "I want the system to send a notification" | "I want to be alerted when something needs my attention" |
Writing Good Outcomes
The outcome is the user's definition of success. It should be meaningful and measurable.
Outcome Hierarchy
Outcomes can operate at different levels:
1. Immediate outcome -- What happens right now. "So I can see my balance." 2. End outcome -- What the immediate outcome enables. "So I can decide whether to make this purchase." 3. Life outcome -- The broader life goal. "So I can stay within my financial plan."
For job stories, aim for the end outcome level. Immediate outcomes are too small (they describe the UI). Life outcomes are too broad (they do not constrain design).
INVEST Criteria Applied to Job Stories
Independent
Each job story should be deliverable on its own. If story A requires story B to be done first, either combine them or ensure B is scheduled first and A is written to work on its own once B exists.
Test: Can the team demo this story in isolation?
Negotiable
The story describes the what and why, not the how. The implementation is open to discussion between product, design, and engineering.
Test: Could the team solve this story in more than one way?
Valuable
The outcome describes clear value to the user. Stories that only deliver value to the business or the system (not the user) should be reframed or classified as technical tasks.
Test: Would a user care if this story were done?
Estimable
The situation is clear enough that the team can estimate the effort. If the team cannot estimate, the story needs more research or should be split.
Test: Can the team give a confidence-level estimate (even a range)?
Small
A story should be completable within one sprint. If it spans multiple sprints, split it by situation, by outcome, or by scope (happy path vs. edge cases).
Test: Can the team finish this in one sprint?
Testable
The acceptance criteria are specific enough to write test cases. If you cannot describe how to verify the outcome, the story is not ready.
Test: Can QA write a test plan from the acceptance criteria?
Story Splitting Techniques
When a job story is too large, split it using one of these strategies:
Split by Situation
If the story covers multiple situations, create one story per situation.
Before: "When I need to communicate with my team, I want to send messages, so I can collaborate."
After:
- "When I need a quick answer from a teammate, I want to send a direct message, so I can get unblocked without scheduling a meeting."
- "When I need to share an update with the whole team, I want to post to a channel, so everyone sees it without me messaging each person."
Split by Outcome
If the story has multiple outcomes, create one story per outcome.
Before: "When I review the monthly report, I want to see revenue and churn data, so I can assess business health."
After:
- "When I review the monthly report, I want to see revenue trends by product line, so I can identify which products are growing."
- "When I review the monthly report, I want to see churn by customer segment, so I can prioritize retention efforts."
Split by Complexity
Deliver the simple version first, then enhance.
Before: "When I search for a customer, I want to find them by any attribute, so I can quickly pull up their record."
After:
- "When I search for a customer, I want to find them by name or email, so I can quickly pull up their record."
- "When name or email search does not find the customer, I want to search by phone number or account ID, so I can still locate their record."
References
- Clayton Christensen, The Innovator's Solution (2003)
- Tony Ulwick, Jobs to Be Done: Theory to Practice (2016)
- Alan Klement, When Coffee and Kale Compete (2018)
- Bob Moesta and Chris Spiek, Demand-Side Sales 101 (2020)
- intercom.com/blog/using-job-stories-design-features-ui-ux -- Original job stories blog post
Red Flags: Job Stories
Common ways this skill's output goes wrong — concrete examples, why they're bad, and how to fix them.
How to use this document
Scan job stories before they enter sprint planning. Each red flag shows the bad version next to the good version, anchored to the JTBD "When/Want/So" format (Klement, Christensen) and Bill Wake's INVEST.
---
Red Flag 1: Job story is a feature request in disguise
Symptom. Story reads "When I want to use saved searches, I want a saved-search button, so I can save searches."
Why it's bad. The Situation is a tautology ("when I want to use feature X"), the Motivation names the implementation ("button"), and the Outcome restates the feature. The story has been wrapped in JTBD framing without changing the team's solution-first thinking.
Bad example:
"When I want to use saved searches, I want a saved-search button on the toolbar, so I can save searches."
Good example:
"When I am exploring listings late at night and find one I want to revisit tomorrow, I want to bookmark the listing without losing my current search context, so I can resume my search later without re-running it."
How to catch it. Strip the Want clause. Does the remaining "When ... so I can ..." describe a recognizable real-world moment? If the situation tautologically references the feature, the story is feature-in-disguise.
---
Red Flag 2: Vague situation ("when I use the app")
Symptom. Situation reads "When I open the app" or "When I need data" or "When I am working".
Why it's bad. Vague situations are unfalsifiable. The team cannot disprove them; they cannot design for them. Every user "opens the app" — that's not the trigger that creates the need. The specific moment is what informs the design.
Bad example:
"When I open the app, I want to see my data, so I can be informed."
Good example:
"When I am about to walk into my Monday 9:30am pipeline review and I have not opened the app since Friday, I want to see the deals that moved or stalled in those 3 days, so I can focus the review on the changes."
How to catch it. Apply the "could you video this?" test. If a film crew could not capture the moment described, the situation is too vague.
---
Red Flag 3: Motivation prescribes the UI
Symptom. Motivation reads "I want a dropdown" or "I want a button" or "I want a modal".
Why it's bad. INVEST-N (negotiable) fails. The story has locked in an implementation before design has explored alternatives. Engineering builds the dropdown; six months later, research shows users wanted a search-as-you-type input.
Bad example:
"When I am filtering my list, I want a dropdown of all available filters, so I can pick one."
Good example:
"When I am filtering my list of 200+ items, I want to narrow it by status and assignee, so I can quickly find the items I need to act on."
How to catch it. Search the Motivation for: dropdown, button, modal, picker, panel, sidebar, popup. Each is an implementation lock.
---
Red Flag 4: Outcome is not measurable
Symptom. Outcome reads "so I can be productive" or "so I can feel confident" or "so I can do my job".
Why it's bad. Unmeasurable outcomes give the team no way to validate the story shipped its value. The acceptance criteria collapse into "feature exists"; the team produces output instead of outcome.
Bad example:
"When I review my dashboard, I want to see my key metrics, so I can be productive."
Good example:
"When I review my dashboard on Monday morning, I want to see my 3 key team metrics with week-over-week change, so I can decide where to focus my 1:1s this week (and skip the 'how is the team doing' question entirely)."
How to catch it. Ask: "How would you know the user achieved this?" If no observable signal exists, rewrite.
---
Red Flag 5: Multiple situations packed into one story
Symptom. Story has "When I am creating a new project OR editing an existing project OR cloning a template, I want to set permissions..."
Why it's bad. Multiple situations = multiple stories. Each context has different design needs, different acceptance criteria, different complexity. Packing them violates INVEST-S (small).
Bad example:
"When I am creating a new project, editing an existing project, or cloning a project template, I want to configure access permissions, so I can ensure the right people can collaborate."
Good example:
"Split into 3:
1. When I am creating a new project from scratch, I want to invite the first collaborators by email, so they receive an invitation immediately.
2. When I am editing an existing project's team, I want to add or remove members and see what changed, so I can audit access.
3. When I am cloning a project template, I want to choose whether to bring the original collaborators or start clean, so I can match the new project's team."
How to catch it. Look for "OR" or commas listing situations in the When clause. Each is a separate story.
---
Red Flag 6: ACs describe implementation, not behavior
Symptom. Acceptance criteria include "API returns 200", "React component renders", "DB column populates".
Why it's bad. Implementation ACs cannot be observed by users; they cannot be tested by QA; they lock in technical choices. They produce "done" stories that fail user value.
Bad example:
"AC1: POST /saved-searches returns 201
AC2: SavedSearchPanel React component renders
AC3: saved_searches table row inserted
AC4: Redux store updates"
Good example:
"AC1: After clicking 'Save', the search appears in the user's Saved list within 1 second.
AC2: The saved search can be re-run, producing the same results.
AC3: The saved search persists across logout/login.
AC4: If the user has 25 saved searches, the 26th save shows a paywall with upgrade CTA."
How to catch it. Read each AC. Does it mention an API, an endpoint, a database table, HTTP status code, or a UI framework? If yes, rewrite as observable behavior.
---
Red Flag 7: Story has no acceptance criteria
Symptom. Story is well-written (When/Want/So) but has 0 acceptance criteria. "We'll figure out the details in implementation."
Why it's bad. INVEST-T (testable) fails. Without ACs, the team cannot agree on "done". The story will be marked done when code merges; QA will reject; engineering will redo. Sprint flow is destroyed.
Bad example:
"Story: When I am preparing for a meeting, I want to see the agenda, so I can come prepared. Acceptance: (empty.)"
Good example:
"Story: When I am preparing for a meeting, I want to see the agenda, so I can come prepared.
AC1: Agenda visible in the meeting card on dashboard, no extra click.
AC2: Agenda shows time + topic for each item.
AC3: If no agenda is set, a 'add agenda' prompt is visible.
AC4: Agenda updates within 30 sec of being changed in the calendar tool.
AC5: Agenda is accessible on mobile without scrolling.
AC6: Items the user owns are visually distinguished."
How to catch it. Each story must have 6-8 ACs. < 4 means the team has not thought through behavior.
---
Red Flag 8: Outcome is internal (not user-facing)
Symptom. Outcome reads "so the database is normalized" or "so the API is consistent" or "so the architecture is clean".
Why it's bad. INVEST-V (valuable) fails. The user does not benefit from a normalized database; they benefit from the user-facing capability that normalization enables. The story buries the user value in technical language.
Bad example:
"When I want to use the system, I want consistent data types, so the schema is clean."
Good example:
"When I export my dashboard to CSV for my exec review, I want every numeric column to use the same decimal format, so I do not have to reformat 40 cells before pasting into the slides."
How to catch it. Read the outcome aloud. If it describes internal system properties, rewrite to describe what the user can now do that they could not before.
---
Red Flag 9: Story is too small (a task pretending to be a story)
Symptom. Story reads "When I want to change my password, I want a password field, so I can change my password." Acceptance: 1 AC. Estimate: 30 min.
Why it's bad. Stories should be in the 1-5 day range (INVEST-S). Tasks roll up into stories; stories do not need to be split into tasks-pretending-to-be-stories. A 30-min item is a task — track it in the parent story, not as a separate story.
Bad example:
"STORY-401: Add password change field (30 min, 1 AC).
STORY-402: Add password change submit button (30 min, 1 AC).
STORY-403: Add password change confirmation message (30 min, 1 AC)."
Good example:
"STORY-401: When I am securing my account, I want to change my password without contacting support, so I can rotate credentials per my company's policy. (6 ACs covering field, validation, submit, confirmation, audit log, email notification. Estimate: 2 days.)"
How to catch it. Filter the backlog for estimates < 1 day. > 20% of stories under 1 day = tasks dressed as stories.
---
Red Flag 10: No JTBD discovery behind the story
Symptom. Story arrives at refinement with no link to a customer interview, support ticket, or observed behavior. The PM wrote it from desk imagination.
Why it's bad. Job stories work because they ground in real user context. Without research, the team invents situations. Invented situations don't match reality; engineering builds for fictional users.
Bad example:
"STORY-501: When I am using the app, I want a quick way to find help, so I can solve my problem. (Source: 'we should probably have help.')"
Good example:
"STORY-501: When I have just signed up and I am on the empty dashboard with no data, I want a guided next-step prompt, so I do not bounce out of the product confused. (Source: 5 user research interviews with new signups; support tickets [link] showing 23% of first-week tickets are 'I don't know what to do next'; analytics showing 41% bounce rate from empty dashboard.)"
How to catch it. For each story, ask "what is the source?" If no interview quote, ticket, or analytics signal, the situation is invented.
---
Red Flag 11: ACs ignore edge cases
Symptom. All 6 ACs describe the happy path. None describe error states, empty states, or edge cases.
Why it's bad. Production users hit edge cases first. A happy-path-only AC set ships features that work in demos and break for the first user who has no data, low connectivity, or weird input. QA finds the gaps late; engineering reopens the story.
Bad example:
"AC1: User clicks save and search appears in list.
AC2: User can re-run saved search.
AC3: User can delete saved search.
AC4: User can rename saved search.
AC5: Search list shows newest first.
AC6: Each search shows name and date saved."
Good example:
"AC1: Happy path: user clicks save → appears in list in 1 sec.
AC2: Happy path: user re-runs → same results as save time.
AC3: Edge: user at 25-search limit → save attempt shows paywall.
AC4: Edge: search produces no results → save still works; re-run shows 'no results' empty state.
AC5: Edge: search criteria reference a now-deleted field → re-run shows error with 'edit search' CTA.
AC6: Empty state: user has 0 saved searches → list shows 'Save your first search' prompt with link.
AC7: Error: save fails due to network → user sees inline error with retry button.
AC8: A11y: all interactions keyboard-accessible; screen reader announces save success."
How to catch it. Count ACs by category. < 30% covering edge cases / empty states / errors / a11y = happy path only.
---
Red Flag 12: Job story without strategic linkage
Symptom. Story is well-formed but the team cannot answer "why is this in this sprint?"
Why it's bad. A good job story is locally coherent (matches a user job) but may not be strategically coherent (matches the team's quarterly OKR). Without a "Supports:" linkage, the backlog fills with disconnected user-value stories that don't aggregate into impact.
Bad example:
"STORY-601: When I am reviewing my team's performance, I want to see metric trends, so I can spot issues early. (PM asked 'why this sprint?' — no answer.)"
Good example:
"STORY-601: When I am reviewing my team's performance ... so I can spot issues early.
Supports: KR2 'D30 retention from 58% to 70%' (Q3 OKR). Hypothesis: managers who spot issues early intervene earlier; teams with engaged managers retain 23% better (customer-feedback-triage/ cluster C-014, 18 distinct customers).Outcome metric: '% of users who view this view weekly' (target 60% within 30 days of launch)."
How to catch it. For each story, ask: "Which OKR or strategic outcome does this serve?" If the answer is "general improvement", the story may be wrong-prioritized.
---
Red Flag Quick Reference
| # | Anti-pattern | One-line check |
|---|---|---|
| 1 | Feature request in disguise | Strip the Want clause — is there still a real moment? |
| 2 | Vague situation | Apply the "could you video this?" test |
| 3 | Motivation prescribes UI | Search for: dropdown, button, modal, picker, panel |
| 4 | Outcome not measurable | How would you know the user achieved this? |
| 5 | Multiple situations packed | "OR" or commas in the When clause? |
| 6 | ACs describe implementation | Do ACs reference API, DB, HTTP, framework? |
| 7 | Story has no ACs | < 4 ACs? |
| 8 | Outcome is internal | Does Outcome describe what the user can now do? |
| 9 | Story too small | Estimate < 1 day? |
| 10 | No JTBD discovery behind story | What is the source: interview, ticket, analytics? |
| 11 | ACs ignore edge cases | % of ACs covering errors / empty / a11y? |
| 12 | No strategic linkage | Which OKR does this serve? |
Related Reading
- SKILL.md Troubleshooting
- references/jtbd-guide.md
wwas/(Why-What-Acceptance for stories needing business context)backlog-refinement/(INVEST grading and DoR)discovery/jtbd-workshop/(sourcing real jobs from customer research)discovery/interview-synthesis/(interview → opportunities → job stories)- Alan Klement, When Coffee and Kale Compete (2016) — JTBD framing
- Clayton Christensen, Competing Against Luck (2016) — JTBD theory
#!/usr/bin/env python3
"""INVEST Validator - Validate backlog items against INVEST criteria.
Reads stories/items and evaluates each against the 6 INVEST criteria
(Independent, Negotiable, Valuable, Estimable, Small, Testable).
Usage:
python invest_validator.py --items items.json
python invest_validator.py --items items.json --json
python invest_validator.py --example
"""
import argparse
import json
import re
import sys
def load_data(path: str) -> dict:
with open(path, "r") as f:
return json.load(f)
def validate_invest(item: dict) -> dict:
title = item.get("title", "Untitled")
description = item.get("description", "")
acceptance_criteria = item.get("acceptance_criteria", [])
dependencies = item.get("dependencies", [])
story_points = item.get("story_points")
sprint_fit = item.get("fits_in_sprint", None)
checks = {}
# Independent
has_deps = len(dependencies) > 0
checks["independent"] = {
"passed": not has_deps,
"detail": f"Has {len(dependencies)} dependency(ies): {', '.join(dependencies[:3])}" if has_deps else "No blocking dependencies",
"fix": "Reorder backlog or combine dependent items" if has_deps else None,
}
# Negotiable
impl_words = [r"\buse React\b", r"\buse API\b", r"\buse SQL\b", r"\bimplement with\b", r"\bmust use\b", r"\brequires \w+ library\b"]
is_prescriptive = any(re.search(p, description, re.IGNORECASE) for p in impl_words)
checks["negotiable"] = {
"passed": not is_prescriptive,
"detail": "Description prescribes implementation approach" if is_prescriptive else "Implementation approach is negotiable",
"fix": "Remove implementation details from description -- describe the What, not the How" if is_prescriptive else None,
}
# Valuable
has_outcome = bool(item.get("outcome") or item.get("so_that") or item.get("why"))
value_words = [r"\breduce\b", r"\bincrease\b", r"\bimprove\b", r"\benable\b", r"\bsave\b", r"\beliminate\b"]
has_value_language = any(re.search(p, description, re.IGNORECASE) for p in value_words)
checks["valuable"] = {
"passed": has_outcome or has_value_language,
"detail": "Clear value proposition" if (has_outcome or has_value_language) else "No clear user or business value stated",
"fix": "Add a Why or outcome statement connecting to user benefit or business objective" if not (has_outcome or has_value_language) else None,
}
# Estimable
desc_length = len(description)
has_ac = len(acceptance_criteria) >= 2
checks["estimable"] = {
"passed": desc_length >= 50 and has_ac,
"detail": f"Description: {desc_length} chars, {len(acceptance_criteria)} AC" if desc_length >= 50 and has_ac else "Insufficient detail for estimation",
"fix": "Add more context to description and at least 4 acceptance criteria" if not (desc_length >= 50 and has_ac) else None,
}
# Small
if sprint_fit is not None:
is_small = sprint_fit
elif story_points is not None:
is_small = story_points <= 8
else:
word_count = len(description.split())
is_small = word_count < 200 and len(acceptance_criteria) <= 10
checks["small"] = {
"passed": is_small,
"detail": f"Story points: {story_points}" if story_points else ("Fits in sprint" if is_small else "May be too large for one sprint"),
"fix": "Split by user segment, scenario, or outcome to fit in one sprint" if not is_small else None,
}
# Testable
testable_ac = sum(1 for ac in acceptance_criteria if any(re.search(p, ac.lower()) for p in [r"\bshows\b", r"\bdisplays\b", r"\benables\b", r"\bprevents\b", r"\bwithin\b", r"\bwhen\b", r"\bif\b"]))
has_testable_ac = testable_ac >= 2 or len(acceptance_criteria) >= 4
checks["testable"] = {
"passed": has_testable_ac,
"detail": f"{testable_ac}/{len(acceptance_criteria)} criteria are clearly testable" if acceptance_criteria else "No acceptance criteria",
"fix": "Rewrite criteria as observable outcomes: '[Thing] [does] [expected behavior] [under condition]'" if not has_testable_ac else None,
}
passed = sum(1 for c in checks.values() if c["passed"])
total = len(checks)
score = round(passed / total * 100)
if passed == 6:
rating = "Sprint Ready"
elif passed >= 4:
rating = "Needs Refinement"
else:
rating = "Not Ready"
fixes = [{"criterion": k, "action": v["fix"]} for k, v in checks.items() if v.get("fix")]
return {
"title": title,
"score": score,
"rating": rating,
"passed": passed,
"total": total,
"checks": checks,
"fixes": fixes,
}
def analyze_items(data: dict) -> dict:
items = data.get("items", [])
results = [validate_invest(item) for item in items]
ready = sum(1 for r in results if r["rating"] == "Sprint Ready")
needs_work = sum(1 for r in results if r["rating"] == "Needs Refinement")
not_ready = sum(1 for r in results if r["rating"] == "Not Ready")
return {
"total_items": len(results),
"sprint_ready": ready,
"needs_refinement": needs_work,
"not_ready": not_ready,
"items": results,
}
def print_report(result: dict) -> None:
print(f"\nINVEST Validation")
print(f"Items: {result['total_items']}")
print("=" * 60)
print(f"Sprint Ready: {result['sprint_ready']} | Needs Refinement: {result['needs_refinement']} | Not Ready: {result['not_ready']}")
for item in result["items"]:
print(f"\n [{item['rating']}] {item['title']} ({item['passed']}/{item['total']} INVEST)")
for criterion, check in item["checks"].items():
status = "PASS" if check["passed"] else "FAIL"
print(f" [{status}] {criterion.upper()}: {check['detail']}")
if item["fixes"]:
print(f" Fixes needed:")
for fix in item["fixes"]:
print(f" - {fix['criterion'].upper()}: {fix['action']}")
print()
def print_example() -> None:
example = {
"items": [
{
"title": "Weekly Budget Summary",
"description": "When preparing my weekly budget, I want to see spending by category so I can adjust before overspending. The view should group transactions and highlight over-budget categories.",
"outcome": "Reduce time spent on budget review from 30 minutes to 5 minutes",
"acceptance_criteria": [
"Shows current month transactions grouped by category",
"Displays total spent and remaining budget per category",
"Over-budget categories are visually highlighted",
"Tapping a category shows individual transactions",
"Loads within 2 seconds on mobile",
],
"dependencies": [],
"story_points": 5,
},
{
"title": "Complete Platform Redesign",
"description": "Redesign the entire platform. Use React and implement with a new API.",
"acceptance_criteria": ["It works"],
"dependencies": ["Database migration", "API v2 release"],
"story_points": 40,
},
],
}
print(json.dumps(example, indent=2))
def main():
parser = argparse.ArgumentParser(description="Validate items against INVEST criteria.")
parser.add_argument("--items", type=str, help="Path to items JSON file")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--example", action="store_true", help="Print example and exit")
args = parser.parse_args()
if args.example:
print_example()
return
if not args.items:
parser.error("--items is required")
data = load_data(args.items)
result = analyze_items(data)
if args.json:
print(json.dumps(result, indent=2))
else:
print_report(result)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Story Converter - Convert between user stories and job stories.
Takes user stories in "As a / I want / So that" format and converts them
to JTBD "When / I want / So I can" format with quality guidance.
Usage:
python story_converter.py --stories stories.json
python story_converter.py --stories stories.json --json
python story_converter.py --example
"""
import argparse
import json
import re
import sys
def load_data(path: str) -> dict:
with open(path, "r") as f:
return json.load(f)
def convert_story(story: dict) -> dict:
role = story.get("role", "").strip()
want = story.get("want", "").strip()
so_that = story.get("so_that", "").strip()
title = story.get("title", "Untitled")
# Original user story
original = f"As a {role}, I want to {want}, so that {so_that}."
# Conversion guidance
situation_hints = []
if role:
situation_hints.append(f"Think about when a {role} would need this capability. What is the specific context or trigger?")
# Generate a template conversion
situation_suggestion = f"[When a {role} is {_infer_context(want)}]"
motivation_suggestion = want
outcome_suggestion = so_that
# Quality warnings
warnings = []
if not so_that or len(so_that) < 10:
warnings.append("The 'so that' is vague or missing. Strengthen the outcome with measurable criteria.")
if role and role.lower() in ("user", "admin", "manager"):
warnings.append(f"Role '{role}' is generic. The job story should describe the specific situation instead.")
if any(word in want.lower() for word in ["button", "dropdown", "modal", "api"]):
warnings.append("The 'I want' contains solution details. Rewrite to describe the capability, not the UI element.")
converted = {
"title": title,
"original_user_story": original,
"converted_job_story": {
"situation": situation_suggestion,
"motivation": motivation_suggestion,
"outcome": outcome_suggestion,
},
"template": f"When {situation_suggestion}, I want to {motivation_suggestion}, so I can {outcome_suggestion}.",
"conversion_notes": situation_hints,
"warnings": warnings,
"needs_refinement": len(warnings) > 0,
}
return converted
def _infer_context(want: str) -> str:
"""Infer a situational context from the want statement."""
action_patterns = {
r"see|view|check|monitor": "reviewing information",
r"create|add|make": "creating something new",
r"edit|update|change|modify": "updating existing data",
r"delete|remove": "cleaning up or removing items",
r"export|download|share": "sharing or extracting data",
r"search|find|filter": "looking for specific information",
r"configure|set up|customize": "setting up or customizing the system",
}
for pattern, context in action_patterns.items():
if re.search(pattern, want.lower()):
return context
return "performing a specific task"
def batch_convert(data: dict) -> dict:
stories = data.get("stories", [])
results = [convert_story(s) for s in stories]
needs_refinement = sum(1 for r in results if r["needs_refinement"])
return {
"total_stories": len(results),
"converted": len(results),
"needs_refinement": needs_refinement,
"stories": results,
}
def print_report(result: dict) -> None:
print(f"\nStory Conversion: User Stories -> Job Stories")
print(f"Converted: {result['converted']} | Needs Refinement: {result['needs_refinement']}")
print("=" * 65)
for s in result["stories"]:
print(f"\n {s['title']}")
print(f" BEFORE: {s['original_user_story']}")
print(f" AFTER: {s['template']}")
if s["warnings"]:
for w in s["warnings"]:
print(f" ! {w}")
if s["conversion_notes"]:
for n in s["conversion_notes"]:
print(f" > {n}")
print()
def print_example() -> None:
example = {
"stories": [
{
"title": "Export User Data",
"role": "admin",
"want": "export user data as a CSV file",
"so_that": "I can comply with data subject access requests",
},
{
"title": "View Dashboard",
"role": "user",
"want": "see a dashboard with my key metrics",
"so_that": "I can track my progress",
},
{
"title": "Click Button",
"role": "manager",
"want": "click the approve button on the modal",
"so_that": "the request is processed",
},
],
}
print(json.dumps(example, indent=2))
def main():
parser = argparse.ArgumentParser(description="Convert user stories to job stories.")
parser.add_argument("--stories", type=str, help="Path to user stories JSON file")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--example", action="store_true", help="Print example and exit")
args = parser.parse_args()
if args.example:
print_example()
return
if not args.stories:
parser.error("--stories is required")
data = load_data(args.stories)
result = batch_convert(data)
if args.json:
print(json.dumps(result, indent=2))
else:
print_report(result)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Story Quality Checker - Validate job stories against JTBD and INVEST criteria.
Reads job stories and checks each for format compliance, specificity,
and quality against best practices.
Usage:
python story_quality_checker.py --stories stories.json
python story_quality_checker.py --stories stories.json --json
python story_quality_checker.py --example
"""
import argparse
import json
import re
import sys
VAGUE_SITUATIONS = [
r"^when i use",
r"^when i need",
r"^when i am a",
r"^when i want",
r"^when using the",
]
SOLUTION_WORDS = [
r"\bdropdown\b", r"\bbutton\b", r"\bmodal\b", r"\bpopup\b",
r"\bcheckbox\b", r"\btextbox\b", r"\bAPI\b", r"\bendpoint\b",
r"\bdatabase\b", r"\bCSS\b", r"\bReact\b", r"\bcomponent\b",
]
VAGUE_OUTCOMES = [
r"so i can use it",
r"so i can be productive",
r"so i can do my job",
r"so i can be happy",
r"so i can feel",
r"so it works",
]
def load_data(path: str) -> dict:
with open(path, "r") as f:
return json.load(f)
def check_story(story: dict) -> dict:
title = story.get("title", "Untitled")
situation = story.get("situation", "").strip()
motivation = story.get("motivation", "").strip()
outcome = story.get("outcome", "").strip()
acceptance_criteria = story.get("acceptance_criteria", [])
checks = []
score = 100
# Format: has all three parts
if not situation:
checks.append({"check": "has_situation", "passed": False, "detail": "Missing 'When [situation]'"})
score -= 20
else:
checks.append({"check": "has_situation", "passed": True})
if not motivation:
checks.append({"check": "has_motivation", "passed": False, "detail": "Missing 'I want to [motivation]'"})
score -= 20
else:
checks.append({"check": "has_motivation", "passed": True})
if not outcome:
checks.append({"check": "has_outcome", "passed": False, "detail": "Missing 'So I can [outcome]'"})
score -= 20
else:
checks.append({"check": "has_outcome", "passed": True})
# Situation quality
if situation:
is_vague = any(re.search(p, situation.lower()) for p in VAGUE_SITUATIONS)
checks.append({
"check": "specific_situation",
"passed": not is_vague,
"detail": "Situation is too vague -- add specific context or trigger" if is_vague else "Situation is specific",
})
if is_vague:
score -= 10
# Motivation: not solution-prescriptive
if motivation:
has_solution = any(re.search(p, motivation, re.IGNORECASE) for p in SOLUTION_WORDS)
checks.append({
"check": "solution_agnostic",
"passed": not has_solution,
"detail": "Motivation prescribes a solution -- describe the capability instead" if has_solution else "Motivation is solution-agnostic",
})
if has_solution:
score -= 10
# Outcome quality
if outcome:
is_vague = any(re.search(p, outcome.lower()) for p in VAGUE_OUTCOMES)
checks.append({
"check": "measurable_outcome",
"passed": not is_vague,
"detail": "Outcome is too vague -- add observable/measurable criteria" if is_vague else "Outcome is measurable",
})
if is_vague:
score -= 10
# Acceptance criteria
ac_count = len(acceptance_criteria)
has_enough_ac = ac_count >= 4
checks.append({
"check": "acceptance_criteria_count",
"passed": has_enough_ac,
"detail": f"{ac_count} criteria (minimum: 4)" if not has_enough_ac else f"{ac_count} criteria",
})
if not has_enough_ac:
score -= 10
# AC quality: check for implementation details
impl_detail_count = 0
for ac in acceptance_criteria:
if any(re.search(p, ac, re.IGNORECASE) for p in [r"\bAPI\b", r"\b200\b", r"\brender\b", r"\bunit test\b", r"\bdatabase\b"]):
impl_detail_count += 1
if impl_detail_count > 0:
checks.append({
"check": "ac_outcome_focused",
"passed": False,
"detail": f"{impl_detail_count} criteria reference implementation details -- rewrite as user-observable outcomes",
})
score -= 5 * impl_detail_count
else:
checks.append({"check": "ac_outcome_focused", "passed": True, "detail": "Criteria are outcome-focused"})
score = max(0, score)
if score >= 85:
rating = "Ready"
elif score >= 65:
rating = "Needs Refinement"
else:
rating = "Not Ready"
return {
"title": title,
"score": score,
"rating": rating,
"checks_passed": sum(1 for c in checks if c["passed"]),
"checks_total": len(checks),
"checks": checks,
"full_story": f"When {situation}, I want to {motivation}, so I can {outcome}." if all([situation, motivation, outcome]) else "Incomplete",
}
def analyze_stories(data: dict) -> dict:
stories = data.get("stories", [])
results = [check_story(s) for s in stories]
scores = [r["score"] for r in results]
avg = round(sum(scores) / len(scores), 1) if scores else 0
ready = sum(1 for r in results if r["rating"] == "Ready")
needs_work = sum(1 for r in results if r["rating"] == "Needs Refinement")
not_ready = sum(1 for r in results if r["rating"] == "Not Ready")
return {
"total_stories": len(results),
"average_score": avg,
"ready": ready,
"needs_refinement": needs_work,
"not_ready": not_ready,
"stories": results,
}
def print_report(result: dict) -> None:
print(f"\nJob Story Quality Check")
print(f"Stories: {result['total_stories']} | Avg Score: {result['average_score']:.0f}/100")
print("=" * 65)
print(f"Ready: {result['ready']} | Needs Refinement: {result['needs_refinement']} | Not Ready: {result['not_ready']}")
for s in sorted(result["stories"], key=lambda x: x["score"]):
print(f"\n [{s['rating']}] {s['title']} (Score: {s['score']})")
if s["full_story"] != "Incomplete":
print(f" {s['full_story'][:100]}")
for c in s["checks"]:
if not c["passed"]:
print(f" ! {c.get('detail', c['check'])}")
print()
def print_example() -> None:
example = {
"stories": [
{
"title": "Weekly Budget Check",
"situation": "I am preparing my weekly budget on Sunday evening",
"motivation": "see how much I have spent so far this month by category",
"outcome": "decide where to cut back before the month ends",
"acceptance_criteria": [
"Spending view shows current month transactions grouped by category",
"Each category displays total spent and remaining budget",
"Categories exceeding budget are visually distinguished",
"Tapping a category shows individual transactions",
"View loads within 2 seconds on mobile",
"Date range is fixed to current calendar month",
],
},
{
"title": "Bad Example Story",
"situation": "I use the app",
"motivation": "click the dropdown to select a report",
"outcome": "be productive",
"acceptance_criteria": ["API returns 200", "Component renders"],
},
],
}
print(json.dumps(example, indent=2))
def main():
parser = argparse.ArgumentParser(description="Validate job stories against JTBD and INVEST criteria.")
parser.add_argument("--stories", type=str, help="Path to stories JSON file")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--example", action="store_true", help="Print example and exit")
args = parser.parse_args()
if args.example:
print_example()
return
if not args.stories:
parser.error("--stories is required")
data = load_data(args.stories)
result = analyze_stories(data)
if args.json:
print(json.dumps(result, indent=2))
else:
print_report(result)
if __name__ == "__main__":
main()
Related skills
FAQ
How is a job story different from a user story?
A job story removes the role and describes the situation using When/Want/So, while a user story anchors to a persona with 'As a [role].'
What is the job story format?
When [situation], I want to [motivation], so I can [outcome].