LogoSkills

/dev-story — 스토리 하나를 끝까지 만드는 단계

스토리 하나를 요구사항 이해→구현→테스트→PR 까지 한 사이클로 끝내고 ZenHub 카드를 Review/QA 로 옮깁니다 — PR 은 열어두고 머지는 사람이 판단 (스토리 크기에 따라 1시간~3일).

/dev-story — 스토리 하나를 끝까지 만드는 단계#

항목내용
실행 명령/cc-product:dev-story

한마디로#

기획서에 적힌 작은 작업 단위(스토리) 하나를 요구사항 이해부터 코드 작성, 테스트, 검수, PR 올리기까지 한 번에 끝내주는 단계입니다. 요리로 치면 "주문서 한 장을 받아 재료 손질부터 조리, 맛보기, 접시에 담아 내보내기까지" 한 사이클을 도는 것과 같아요.

누가·언제 쓰나요#

  • 기획·설계가 끝나고 이제 실제로 만들 차례가 된 개발 담당자(또는 AI 개발자)
  • 스프린트 계획에 들어있는 스토리(작업 단위) 하나를 새로 시작하거나, 하던 걸 이어서 진행할 때
  • 규모와 상관없이 모든 프로젝트의 모든 스토리에서 사용합니다 (구현 단계의 표준 절차)

무엇을 해주나요#

  • 돌아가고 테스트까지 끝난 코드 — 백엔드/프론트엔드 구현 + 단위·통합 테스트
  • 상태가 갱신된 스토리.bmad/sprint-status.yaml 의 진행 상태가 "dev-complete" 로 바뀜
  • 리뷰용 PR — 스토리 브랜치에서 에픽 브랜치로 올라가는 Pull Request (자동 병합은 안 함, 사람이 검토)
  • ZenHub 파이프라인 자동 이동 — 시작 시 "In Progress", 완료 시 "Review/QA" 로 카드가 알아서 이동
  • 작업 시간은 스토리 크기에 따라 1시간 ~ 3일 정도 걸립니다

어떻게 쓰나요#

# 스토리 ID 하나를 지정해서 구현 시작
/dev-story STORY-008

# 끝난 뒤 안내에 따라 다음 스토리로 이어가기
/dev-story STORY-009

# 전체 스프린트 진행 상황을 보고 싶을 때
/sprint-status

지정한 스토리가 이미 "진행 중"이면 이어서, "완료됨"이면 "다시 만들까요, 더 확장할까요?"를 물어본 뒤 진행합니다.

안에서 무슨 일이 벌어지나요#

스토리 한 개를 받아 요구사항 → 구현 → 테스트 → 검수 → PR 순서로 차근차근 진행합니다.

  1. 준비 — 설정·스프린트 상태·스토리 문서·아키텍처를 불러오고, 작업할 브랜치 구조(에픽 → 스토리)를 정리합니다.
  2. 요구사항 이해 — "누가, 무엇을, 왜" 원하는지와 통과해야 할 합격 기준(Acceptance Criteria)을 읽고, ZenHub 카드를 "In Progress"로 옮깁니다.
  3. 작업 쪼개기 — 스토리를 5~15개의 기술 작업으로 나눠 할 일 목록(TodoWrite)으로 관리합니다.
  4. 브랜치 만들기 — main → 에픽 → 스토리로 이어지는 브랜치를 만들고 그 위에서 작업합니다.
  5. 구현 — 필요한 만큼 백엔드(데이터·로직·API)와 프론트엔드(화면·상호작용)를 만듭니다.
  6. 테스트 — 단위 테스트와 통합 테스트를 작성하고, 커버리지 80% 이상을 목표로 확인합니다.
  7. 검수 — 합격 기준을 하나씩 직접 통과 여부를 확인하고, 수동 테스트와 코드 품질 점검을 합니다.
  8. 마무리 — 커밋·푸시 후 스토리 → 에픽 PR을 올리고, 스프린트 상태와 ZenHub("Review/QA")를 갱신한 뒤, 다음에 할 일을 안내합니다.

⚙️ 상세 옵션·실행 명세 (개발자 / AI 에이전트용)

Workflow Overview#

Goal: Implement a user story from start to completion

Phase: 4 - Implementation (Execution)

Agent: Developer

Inputs: Story ID, story document (if exists), sprint plan

Output: Working, tested code; updated story status; implementation notes

Duration: 1 hour to 3 days (varies by story size)

Required for: All stories in all project levels


Pre-Flight#

  1. Load context per helpers.md#Combined-Config-Load
  2. Load sprint status per helpers.md#Load-Sprint-Status
  3. Load story:
    • If story document exists: Read .bmad/stories/STORY-{ID}.md
    • Else: Find story in sprint plan .bmad/sprint-plan-*.md
  4. Load architecture (if Level 2+): Read .bmad/architecture-*.md
  5. Check story status:
    • If "not_started": Begin implementation
    • If "in_progress": Resume implementation
    • If "completed": Ask user if they want to re-implement or extend
  6. Load ZenHub context per helpers.md#Load-ZenHub-Context
  7. Check ZenHub story ID: If story has zh_issue_id in sprint status, store for pipeline management
  8. Resolve branch hierarchy: per helpers.md#Resolve-Branch-Names
    • Compute epic_branch, story_branch, task_prefix from sprint-status story entry
    • If story already has branch field in sprint-status, use that instead
  9. Check if branches exist: git branch -a to see which branches already exist
  10. Load sub-tasks (if zh_sub_tasks_enabled):
    • Check story entry in sprint-status.yaml for sub_tasks[] array
    • If sub-tasks exist, these will serve as implementation checklist (Part 2)
    • Track sub-task zh_issue_ids for pipeline management during implementation

Story Implementation Process#

Use TodoWrite to track implementation tasks (typically 5-15 tasks per story)

Approach: Practical, quality-focused, thorough.


Part 1: Understand Requirements#

Read and analyze story:

  1. User story: Who, what, why
  2. Acceptance criteria: All criteria that must pass
  3. Technical notes: Implementation guidance
  4. Dependencies: What must exist first
  5. Story points: Complexity estimate

Display to user:

Implementing STORY-{ID}: {Title}

User Story:
As a {type}, I want to {capability}, so that {benefit}

Acceptance Criteria: {count}
Story Points: {points}
Dependencies: {list}
Sub-tasks: {count} (from ZenHub)if sub-tasks loaded

I ' ll now plan the implementation...

Move to In Progress on ZenHub:

If zh_available and zh_story_id exists:
  Call helpers.md#Move-Pipeline-with-Context(zh_story_id,  " In Progress " )
  Log:  " ✓ ZenHub: #{issue_number} → In Progress " 

   # If this is the first story starting in this epic, move epic to In Progress too
  Check epic ' s stories in sprint-status:
    If ALL other stories are  " not_started " :
      # This is the first story — move epic to In Progress
      If zh_epic_id exists:
        Call helpers.md#Move-Pipeline-with-Context(zh_epic_id,  " In Progress " )
        Log:  " ✓ ZenHub: Epic #{epic_number} → In Progress (first story started) "

Check dependencies:

  • Are dependent stories complete?
  • Are external dependencies met?
  • If not, warn user and ask to proceed anyway or wait

Part 2: Plan Implementation Tasks#

Break story into technical tasks:

Based on story size and type, typical task breakdown:

Backend-heavy story (API, data, business logic):

  1. Define data models/schemas
  2. Create database migrations (if needed)
  3. Implement repository/data layer
  4. Implement business logic
  5. Create API endpoints/controllers
  6. Add input validation
  7. Add error handling
  8. Write unit tests
  9. Write integration tests
  10. Update API documentation

Frontend-heavy story (UI, components):

  1. Create component structure
  2. Implement UI layout
  3. Add state management
  4. Implement user interactions
  5. Add form validation (if forms)
  6. Integrate with backend APIs
  7. Add error handling and loading states
  8. Write component tests
  9. Test accessibility
  10. Test responsive design

Full-stack story:

  • Combine backend + frontend tasks
  • Add integration between layers

Infrastructure story:

  1. Design infrastructure
  2. Create IaC scripts
  3. Set up resources
  4. Configure networking/security
  5. Test infrastructure
  6. Document setup

If sub-tasks exist (from Pre-Flight step 10): Use sub-tasks as the primary implementation checklist:

Implementation Checklist (from ZenHub Sub-tasks):
- [ ] #{sub_num_1} {sub_task_title_1}
- [ ] #{sub_num_2} {sub_task_title_2}
- [ ] #{sub_num_3} {sub_task_title_3}
...

As each sub-task is completed, move its ZenHub pipeline:

If zh_available and sub_task has zh_issue_id:
  Call helpers.md#Move-Pipeline-with-Context(zh_sub_task_id,  " In Progress " )
  ... (after completing the sub-task)
  // sub-task complete: GitHub Close (no need to move to Done pipeline)
  mcp__zenhub__updateIssue({ issueId: zh_sub_task_id, state:  " CLOSED "   })
  Update sprint-status sub_task status →  " done "

If no sub-tasks, use TodoWrite to create task list for story

Example:

TodoWrite:
- [ ] Create password reset token model
- [ ] Add token fields to users table (migration)
- [ ] Implement token generation logic
- [ ] Create request-reset endpoint
- [ ] Create validate-token endpoint
- [ ] Create reset-password endpoint
- [ ] Add email sending for reset link
- [ ] Create frontend reset request page
- [ ] Create frontend reset form page
- [ ] Write backend unit tests
- [ ] Write API integration tests
- [ ] Write frontend component tests
- [ ] Manual testing
- [ ] Validate all acceptance criteria

Part 3: Set Up Branch Hierarchy#

Create hierarchical branch structure:

  1. Create epic and story branches per helpers.md#Create-Branch-Hierarchy:

        Call Create-Branch-Hierarchy(epic_branch, story_branch)
    - Creates epic branch from main (if not exists)
    - Creates story branch from epic (if not exists)
    - Switches to story branch
    
  2. Update sprint-status.yaml with branch info:

        # Story entry
    branch:  " story/STORY-{ID}-{slug} " 
     epic_branch:  " epic/EPIC-{number}-{slug} " 
    
     # Epic branches section (if new epic branch created)
    epic_branches:
      - epic_id:  " {zh_epic_id} " 
         epic_name:  " {epic_name} " 
         branch:  " epic/EPIC-{number}-{slug} " 
         created: true
    
  3. Display branch hierarchy:

        Branch Hierarchy:
    main
    └── {epic_branch}
        └── {story_branch}  ← current
    
  4. Check codebase structure

    • Use Glob/Grep to understand existing patterns
    • Identify where new code should live
    • Note existing naming conventions
  5. Verify development environment

    • Dependencies installed
    • Tests can run
  6. Note starting point:

        git status
    git log -1
    

Part 4: Implement - Backend (if applicable)#

Step 1: Data Layer

If story requires data changes:

  1. Define models/schemas

    • Create or update data models
    • Follow project's ORM/schema pattern
  2. Create migrations

        -- Example for password reset
    ALTER TABLE users ADD COLUMN reset_token VARCHAR(255);
    ALTER TABLE users ADD COLUMN reset_token_expiry TIMESTAMP;
    CREATE INDEX idx_reset_token ON users(reset_token);
    
  3. Test migration (run, verify, rollback, run again)

Step 2: Business Logic

  1. Create service/logic files

    • Follow project structure
    • Single responsibility functions
    • Clear function names
  2. Implement core logic

    • Start with happy path
    • Add error handling
    • Handle edge cases
  3. Add validation

    • Input validation
    • Business rule validation
    • Error messages

Example (Node.js/Express):

// services/password-reset.service.js
const crypto = require( ' crypto ' );
const bcrypt = require( ' bcrypt ' );

class PasswordResetService {
  generateResetToken() {
    return crypto.randomBytes(32).toString( ' hex ' );
  }

  async requestReset(email) {
    const user = await User.findByEmail(email);
    if (!user) {
      // Don ' t reveal if email exists
      return { success: true };
    }

    const token = this.generateResetToken();
    const expiry = new Date(Date.now() + 3600000); // 1 hour

    await user.update({
      reset_token: await bcrypt.hash(token, 10),
      reset_token_expiry: expiry
    });

    await EmailService.sendPasswordResetEmail(user.email, token);

    return { success: true };
  }

  async validateToken(token) {
    const user = await User.findByResetToken(token);
    if (!user) {
      return { valid: false, error:  ' Invalid token '   };
    }

    if (user.reset_token_expiry  <   new Date()) {
      return { valid: false, error:  ' Token expired '   };
    }

    return { valid: true, userId: user.id };
  }

  async resetPassword(token, newPassword) {
    const validation = await this.validateToken(token);
    if (!validation.valid) {
      throw new Error(validation.error);
    }

    const user = await User.findById(validation.userId);
    const hashedPassword = await bcrypt.hash(newPassword, 10);

    await user.update({
      password: hashedPassword,
      reset_token: null,
      reset_token_expiry: null
    });

    return { success: true };
  }
}

module.exports = new PasswordResetService();

Step 3: API Endpoints

  1. Create routes/controllers
    • RESTful patterns
    • Proper HTTP methods and status codes
    • Error handling middleware

Example:

// routes/auth.routes.js
router.post( ' /request-password-reset ' ,
  validate(requestResetSchema),
  async (req, res, next) = >   {
    try {
      const { email } = req.body;
      await PasswordResetService.requestReset(email);
      res.json({ success: true, message:  ' If email exists, reset link sent '   });
    } catch (error) {
      next(error);
    }
  }
);

router.post( ' /reset-password ' ,
  validate(resetPasswordSchema),
  async (req, res, next) = >   {
    try {
      const { token, newPassword } = req.body;
      await PasswordResetService.resetPassword(token, newPassword);
      res.json({ success: true, message:  ' Password reset successful '   });
    } catch (error) {
      next(error);
    }
  }
);

Mark backend tasks complete in TodoWrite


Part 5: Implement - Frontend (if applicable)#

Step 1: Component Structure

  1. Create components following project structure
  2. Set up routing (if new pages)
  3. Add state management (if needed)

Step 2: UI Implementation

  1. Build layouts

    • HTML structure
    • CSS/styling
    • Responsive design
  2. Add interactivity

    • Form handling
    • API integration
    • Loading states
    • Error handling

Example (React):

// components/PasswordResetRequest.jsx
import { useState } from  ' react ' ;
import { requestPasswordReset } from  ' ../api/auth ' ;

export default function PasswordResetRequest() {
  const [email, setEmail] = useState( ' ' );
  const [loading, setLoading] = useState(false);
  const [message, setMessage] = useState( ' ' );
  const [error, setError] = useState( ' ' );

  const handleSubmit = async (e) = >   {
    e.preventDefault();
    setLoading(true);
    setError( ' ' );
    setMessage( ' ' );

    try {
      await requestPasswordReset(email);
      setMessage( ' Password reset link sent to your email ' );
      setEmail( ' ' );
    } catch (err) {
      setError( ' Failed to request password reset. Please try again. ' );
    } finally {
      setLoading(false);
    }
  };

  return (
     < div className= " password-reset-request " > 
       < h2 > Reset Your Password < /h2 > 
       < form onSubmit={handleSubmit} > 
         < div className= " form-group " > 
           < label htmlFor= " email " > Email Address < /label > 
           < input
            type= " email " 
             id= " email " 
             value={email}
            onChange={(e) = >   setEmail(e.target.value)}
            required
            disabled={loading}
          / > 
         < /div > 

         {error  & &   < div className= " alert alert-error " > {error} < /div > }
        {message  & &   < div className= " alert alert-success " > {message} < /div > }

         < button type= " submit "   disabled={loading} > 
           {loading ?  ' Sending... '   :  ' Send Reset Link ' }
         < /button > 
       < /form > 
     < /div > 
   );
}

Mark frontend tasks complete in TodoWrite


Part 6: Testing#

Step 1: Unit Tests

Write tests for individual functions/components:

Backend unit tests:

// services/password-reset.service.test.js
describe( ' PasswordResetService ' , () = >   {
  describe( ' generateResetToken ' , () = >   {
    it( ' should generate a random token ' , () = >   {
      const token1 = service.generateResetToken();
      const token2 = service.generateResetToken();

      expect(token1).toHaveLength(64);
      expect(token1).not.toEqual(token2);
    });
  });

  describe( ' requestReset ' , () = >   {
    it( ' should send reset email for existing user ' , async () = >   {
      const email =  ' test@example.com ' ;
      await service.requestReset(email);

      const user = await User.findByEmail(email);
      expect(user.reset_token).not.toBeNull();
      expect(user.reset_token_expiry).toBeGreaterThan(new Date());
    });

    it( ' should not reveal non-existent email ' , async () = >   {
      const result = await service.requestReset( ' fake@example.com ' );
      expect(result.success).toBe(true); // Generic response
    });
  });

  describe( ' validateToken ' , () = >   {
    it( ' should validate correct token ' , async () = >   {
      // Setup user with token
      const result = await service.validateToken(validToken);
      expect(result.valid).toBe(true);
    });

    it( ' should reject expired token ' , async () = >   {
      // Setup user with expired token
      const result = await service.validateToken(expiredToken);
      expect(result.valid).toBe(false);
      expect(result.error).toBe( ' Token expired ' );
    });
  });
});

Frontend component tests:

// components/PasswordResetRequest.test.jsx
import { render, screen, fireEvent, waitFor } from  ' @testing-library/react ' ;
import PasswordResetRequest from  ' ./PasswordResetRequest ' ;

describe( ' PasswordResetRequest ' , () = >   {
  it( ' renders reset form ' , () = >   {
    render( < PasswordResetRequest / > );
    expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
    expect(screen.getByText(/send reset link/i)).toBeInTheDocument();
  });

  it( ' submits email and shows success message ' , async () = >   {
    render( < PasswordResetRequest / > );

    const emailInput = screen.getByLabelText(/email/i);
    const submitButton = screen.getByText(/send reset link/i);

    fireEvent.change(emailInput, { target: { value:  ' test@example.com '   } });
    fireEvent.click(submitButton);

    await waitFor(() = >   {
      expect(screen.getByText(/reset link sent/i)).toBeInTheDocument();
    });
  });

  it( ' shows error message on failure ' , async () = >   {
    // Mock API to fail
    render( < PasswordResetRequest / > );

    fireEvent.change(screen.getByLabelText(/email/i), {
      target: { value:  ' test@example.com '   }
    });
    fireEvent.click(screen.getByText(/send reset link/i));

    await waitFor(() = >   {
      expect(screen.getByText(/failed to request/i)).toBeInTheDocument();
    });
  });
});

Run tests:

# Backend
npm test services/password-reset.service.test.js

# Frontend
npm test components/PasswordResetRequest.test.jsx

# All tests
npm test

Step 2: Integration Tests

Test complete flows:

// tests/integration/password-reset.test.js
describe( ' Password Reset Flow ' , () = >   {
  it( ' should complete full password reset ' , async () = >   {
    // 1. Request reset
    const res1 = await request(app)
      .post( ' /api/auth/request-password-reset ' )
      .send({ email:  ' test@example.com '   });
    expect(res1.status).toBe(200);

    // 2. Get token from database (in real scenario, from email)
    const user = await User.findByEmail( ' test@example.com ' );
    const token = user.reset_token;

    // 3. Validate token
    const res2 = await request(app)
      .get(`/api/auth/validate-reset-token/${token}`);
    expect(res2.body.valid).toBe(true);

    // 4. Reset password
    const newPassword =  ' NewSecure123! ' ;
    const res3 = await request(app)
      .post( ' /api/auth/reset-password ' )
      .send({ token, newPassword });
    expect(res3.status).toBe(200);

    // 5. Verify can login with new password
    const res4 = await request(app)
      .post( ' /api/auth/login ' )
      .send({ email:  ' test@example.com ' , password: newPassword });
    expect(res4.status).toBe(200);
  });
});

Step 3: Check Test Coverage

npm run test:coverage

# Target:80% coverage
# Focus on critical paths and business logic

Mark testing tasks complete in TodoWrite


Part 7: Validate Acceptance Criteria#

Go through each acceptance criterion:

Acceptance Criteria Checklist:
- [ ] User can request password reset from login page
      → Test manually: Click  " Forgot Password " , enter email, submit

- [ ] System sends email with reset link within 1 minute
      → Check email inbox, verify link received

- [ ] Reset link contains secure, expiring token (1-hour validity)Verify token in database, check expiry timestamp

- [ ] User can set new password meeting strength requirements
      → Test with weak password (should fail)Test with strong password (should succeed)

- [ ] System validates password confirmation matches
      → Test with mismatched passwords (should fail)

- [ ] Expired tokens show clear error message
      → Manually expire token in DB, try to use it

- [ ] Successful reset shows confirmation and redirects to login
      → Complete flow, verify redirect

- [ ] User can login with new password immediately
      → Login with new password

- [ ] Old password no longer works after reset
      → Try to login with old password (should fail)

Update status:

  • Mark each criterion as ✓ when validated
  • Note any issues or failures
  • Fix issues before proceeding

Part 8: Manual Testing & QA#

Functional testing:

  • Test happy path end-to-end
  • Test error cases (invalid email, expired token, weak password, etc.)
  • Test edge cases (special characters in email, very long passwords, etc.)
  • Test on different browsers (if frontend)
  • Test on mobile (if applicable)

UX testing:

  • Is the flow intuitive?
  • Are error messages clear?
  • Are loading states visible?
  • Is it accessible (keyboard navigation, screen readers)?

Security testing:

  • Are tokens secure (random, hashed)?
  • Is rate limiting in place?
  • Are inputs sanitized?
  • Is HTTPS enforced?

Mark manual testing complete in TodoWrite


Part 9: Code Quality Review#

Self-review checklist:

Code Style:

  • Follows project conventions
  • Consistent naming
  • No commented-out code
  • No console.logs or debug statements
  • Proper error handling

Functionality:

  • All acceptance criteria met
  • Edge cases handled
  • Error messages are user-friendly
  • No hardcoded values (use config)

Testing:

  • Test coverage ≥80%
  • All tests passing
  • Integration tests cover main flows
  • Edge cases are tested

Documentation:

  • Code comments for complex logic
  • API documentation updated (if API changes)
  • README updated (if setup changes)
  • CHANGELOG updated (if applicable)

Security:

  • No secrets in code
  • Input validation in place
  • SQL injection prevented (parameterized queries)
  • XSS prevented (sanitized outputs)
  • Authentication/authorization correct

Part 9.5: Task Branch Management (Optional)#

Decision heuristic — when to use task branches:

ConditionAction
Task touches 3+ filesCreate task branch
Task estimated 2+ hoursCreate task branch
Task is experimental/riskyCreate task branch
Task touches 1-2 files, <2 hoursCommit directly on story branch

If using task branch:

  1. Create task branch per helpers.md#Create-Task-Branch:

        Call Create-Task-Branch(story_branch,  " {task_slug} " )
    Example: task/STORY-008-fix-lint-rules
    
  2. Implement task on the task branch:

    • Write code, tests
    • Commit frequently on task branch
  3. Create PR and merge per helpers.md#Create-PR-and-Merge:

        Call Create-PR-and-Merge(
      source: task_branch,
      target: story_branch,
      title:  " fix: {task description} (STORY-{ID}) " ,
      merge_strategy:  " squash " 
     )
    
    • Squash merge keeps story branch history clean
    • Task branch is deleted after merge
  4. Repeat for each task that warrants a branch

If committing directly on story branch:

  • Make focused commits with clear messages
  • No PR needed — changes go directly to story branch

Display task branch activity:

Task Branches:
  task/STORY-008-fix-lint-rules → story (squash merged) ✓
  task/STORY-008-update-deps → story (squash merged)[direct commit] Add missing test cases ✓

Part 10: Complete Story — PR Flow and Status Update#

Step 1: Final commit and push on story branch

  1. Ensure on story branch:
        git checkout {story_branch}
    
  2. Stage, commit, and push:
        git add .
    git commit -m  " feat({component}): implement {story title} (STORY-{ID})
    
    - {summary of changes}
    
    Closes STORY-{ID} " 
     git push origin {story_branch}
    

Step 2: Create story → epic PR per helpers.md#Create-PR-and-Merge:

Call Create-PR-and-Merge(
  source: story_branch,
  target: epic_branch,
  title:  " feat: STORY-{ID} {title} " ,
  body:  " ## Summary\n- {changes}\n\n## Acceptance Criteria\n- [x] All criteria met\n\nCloses STORY-{ID} " ,
  merge_strategy:  " merge "     # preserve story commit history
)
  • Do NOT auto-merge — PR stays open for review
  • Store pr_url for status tracking

Step 3: Move to Review/QA on ZenHub:

If zh_available and zh_story_id exists:
  Call helpers.md#Move-Pipeline-with-Context(zh_story_id,  " Review/QA " )
  Log:  " ✓ ZenHub: #{issue_number} → Review/QA "

Step 4: Update sprint status:

Per helpers.md#Update-Sprint-Status:

  1. Find STORY-{ID} in sprint status YAML
  2. Update status to "dev-complete"
  3. Update branch, epic_branch fields (if not already set)
  4. Update pr_url, pr_status: "open"
  5. Add completion_date
  6. Add actual_points (if different from estimate)
  7. Increment sprint completed_points
  8. Save status file

Step 5: Post-review guidance (displayed to user):

PR Review and Merge Instructions:

1. Review PR: {pr_url}
2. After PR is approved and merged (story → epic):
   - GitHub Close: Automatic close via  " Closes # "   keyword in PR
   - sprint-status.yaml: Update pr_status to  " merged " , status to  " done " 

 3. When ALL stories in this epic are closed:
   - Create epic → main PR:
     gh pr create --base main --head {epic_branch} \
       --title  " feat: {epic_name} "   --body  " ... " 
    - Merge with merge commit (preserve epic history)
   - GitHub Close: Epic #{epic_number}

Update story document (if exists):

## Progress Tracking

**Status History:**
- {date}: Created
- {date}: Started (branch: {story_branch})
- {date}: Code complete, PR created
- {date}: PR merged → Closed

**Branch:** {story_branch}
**PR:** {pr_url}
**Actual Effort:** {points} points

Display Summary to User#

Show completion summary:

Story Development Complete!

STORY-{ID}: {Title}
Status: Dev CompletePR Open
Story Points: {points}
Actual Effort: {points} points

Implementation:
- Files created/modified: {count}
- Tests: {count} tests, {coverage}% coverage
- All acceptance criteria validated ✓

Branch Hierarchy:
main
└── {epic_branch}
    └── {story_branch}PR open
        ├── task/{slug-1} (squash merged) ✓   # if task branches used
        └── task/{slug-2} (squash merged)PR: {pr_url} (story → epic, merge commit)

Sub-tasks: {completed}/{total} done     (if sub-tasks exist)

ZenHub Pipeline History:        (if zh_available)
  Sprint BacklogIn ProgressReview/QA
  Epic: #{epic_number} In Progress

Next Steps:
1. Review and merge PR: {pr_url}
2. After merge: Automatic close via GitHub  " Closes # "   keyword
3. Continue with next story or create epic → main PR

Recommend Next Steps#

If more stories in sprint:

Story STORY-{ID} PR open for review!

Sprint Progress: {completed}/{total} points

Pending PR: {pr_url} (story → epic)

Next stories in Sprint:
- STORY-{next}: {title} ({points} points)

Run /dev-story STORY-{next} to continue
Or run /sprint-status to see full sprint progress

If last story in epic:

All stories in Epic complete!

All story PRs merged to {epic_branch}:
- STORY-{A}: ✓ merged
- STORY-{B}: ✓ merged

Next: Create epic → main PR
  gh pr create --base main --head {epic_branch} --title  " feat: {epic_name} " 
   After merge: GitHub Close Epic #{epic_number}

If last story in sprint:

Sprint Complete!

Velocity: {points} points

Next: Start Sprint 2 or run sprint retrospective

If Level 0 (single story):

Story Complete!

Project complete! Single story implemented and tested.

Next: Deploy to production or continue with enhancements

Helper References#

  • Load config: helpers.md#Combined-Config-Load
  • Load sprint status: helpers.md#Load-Sprint-Status
  • Update sprint status: helpers.md#Update-Sprint-Status
  • Save document: helpers.md#Save-Output-Document
  • ZenHub context: helpers.md#Load-ZenHub-Context
  • Resolve branches: helpers.md#Resolve-Branch-Names
  • Create branch hierarchy: helpers.md#Create-Branch-Hierarchy
  • Create task branch: helpers.md#Create-Task-Branch
  • Create PR: helpers.md#Create-PR-and-Merge
  • Move pipeline: helpers.md#Move-Pipeline-with-Context

Tips for Effective Implementation#

Start Small:

  • Break story into smallest possible tasks
  • Complete one task fully before moving to next
  • Commit frequently

Test as You Go:

  • Don't wait until end to test
  • Write tests alongside code
  • Fix issues immediately

Ask Questions:

  • If acceptance criteria unclear, ask user
  • If technical approach uncertain, propose options
  • Don't make assumptions

Quality Over Speed:

  • Working correctly > finishing quickly
  • Good tests > high coverage number
  • Clean code > clever code

Notes for LLMs#

  • Maintain approach (practical, quality-focused, thorough)

  • Use TodoWrite to track implementation tasks (very important!)

  • Always start by reading acceptance criteria

  • Load architecture to understand system design

  • Write tests throughout implementation (not at end)

  • Validate each acceptance criterion explicitly

  • Use Read/Write/Edit tools for code changes

  • Use Bash tool for running tests, git commands

  • Update sprint status when story complete

  • Ask user for clarification when requirements ambiguous

  • Show progress throughout implementation (don't go silent)

  • On story start (Part 1), move ZenHub issue to "In Progress" pipeline if zh_available and zh_story_id exists

  • If first story in epic, also move epic to "In Progress"

  • On story completion (Part 10), move ZenHub issue to "Review/QA" pipeline

  • Pipeline moves are best-effort: if ZenHub MCP fails, warn and continue

  • Read zh_story_id from sprint-status.yaml (populated by sprint-planning or create-story)

  • If sub-tasks exist (Pre-Flight step 10), use them as implementation checklist in Part 2

  • Move sub-task pipelines during implementation: "In Progress" when starting, GitHub Close when completed

  • Update sprint-status sub_task entries as they complete (status, zh_pipeline)

Hierarchical Branch Strategy:

  • Always create epic → story branch hierarchy (Part 3)
  • Use helpers.md#Resolve-Branch-Names to compute names, helpers.md#Create-Branch-Hierarchy to create
  • Epic branches are lazy-created (on first story start, not during sprint planning)
  • If branch creation fails, fall back to feature/STORY-{ID} flat branch
  • Store branch names in sprint-status.yaml for cross-session consistency

Task Branch Heuristic (Part 9.5):

  • 3+ files OR 2+ hours → use task branch (squash merge to story)
  • Otherwise → commit directly on story branch
  • Task branches are optional — no penalty for skipping

PR Flow (Part 10):

  • story → epic: merge commit (preserves story history) — PR stays open for review
  • After PR is reviewed and merged → Automatic close via GitHub "Closes #" keyword
  • When all stories in epic are closed → create epic → main PR (merge commit)
  • If gh CLI unavailable, output manual PR instructions (do not block)

Fallback Strategy:

  • If hierarchical branches fail → use flat feature/STORY-{ID} branch
  • If ZenHub MCP unavailable → skip pipeline moves, continue with git workflow
  • If gh CLI unavailable → provide manual PR instructions
  • Never block the developer workflow due to tooling issues

Remember: You are implementing working software. Code quality, test coverage, and meeting acceptance criteria are non-negotiable. Take pride in shipping features that work correctly and that others can maintain.