LogoSkills

zenhub:epic

ZenHub Epic and Story creation (based on feature requirements)

ํ•ญ๋ชฉ๋‚ด์šฉ
Categorypetmedi-workflow
Complexitymoderate
MCP Serverszenhub, sequential

/zenhub:epic#

Context Framework Note: This command creates ZenHub Epic/Story based on feature requirements.

Triggers#

  • When creating issues from feature requirements
  • When called from /figma:analyze Phase 4
  • When manually creating ZenHub issues

Context Trigger Pattern#

/zenhub:epic {feature_name} {entity_name} [--options]

Parameters#

Required Parameters#

ParameterDescriptionExample
feature_nameFeature name (snake_case)community
entity_nameEntity name (PascalCase)Post

Image Analysis Options (Optional)#

ParameterDescriptionExample
--images Image files to analyze (comma-separated) "list.png,detail.png,form.png"
--images-dirImage directory path./screenshots/
--attach-images Attach to Issue after committing to repo (default: false) true/false
--min-confidence Minimum confidence for screen type identification (default: 70%) 80
--skip-analysisSkip image analysistrue/false
--no-cacheIgnore cache and re-analyzetrue/false

Other Options (Optional)#

ParameterDescriptionExample
--requirements Requirements document path claudedocs/community/requirements.md
--bdd-dir BDD scenario directory claudedocs/community/bdd/
--labelsAdditional labels"sprint-1,mvp"
--screensScreens to generate"list,detail,form"
--pointsStory Point setting"3,3,5"
--sprintSprint assignment"current"

Workspace Info Query (required โ€” before Step 0)#

Issue Type ID, Repository ID, and Pipeline ID differ per workspace. Dynamically query based on the workspace configured in .mcp.json at the project root:

// 1. Repository ID + Pipeline ID
const workspace = await mcp__zenhub__getWorkspacePipelinesAndRepositories();
const repoId = workspace.repositories.find(r = >   /* GitHub ๋ ˆํฌ ์„ ํƒ */).id;

// 2. Issue Type ID
const issueTypes = await mcp__zenhub__getIssueTypes();
const epicTypeId = issueTypes.find(t = >   t.name ===  " Epic " ).id;
const featureTypeId = issueTypes.find(t = >   t.name ===  " Feature " ).id;
const subtaskTypeId = issueTypes.find(t = >   t.name ===  " Sub-task " ).id;

Behavioral Flow#

Step 0: Image Analysis (when --images provided)#

Executed when --images or --images-dir parameter is provided.

0.1 Image Load

Supported formats: PNG, JPG, JPEG, WEBP Recommended size: Max 5MB/file, 1920x1080 or smaller recommended

# Specify individual files
--images  " list.png,detail.png,form.png " 

 # Specify directory (auto-filter supported formats)
--images-dir ./screenshots/

0.2 Screen Type Identification

Identify each image via Claude multimodal analysis:

ImageScreen TypeConfidenceKey Components
list.pngList95%AppBar, ListView, FAB
detail.pngDetail90%SliverAppBar, ContentSection
form.pngForm92%Form, TextField, Button

Screen Type Identification Criteria:

  • List: Multiple repeating cards/items, scrollable layout, FAB
  • Detail: Full info of single item, large image/gallery, action buttons
  • Form: Input fields (TextField, Dropdown, etc.), save/cancel buttons

Confidence threshold (--min-confidence, default: 70%):

  • Screens below threshold request manual user confirmation
  • e.g.: With --min-confidence 80, confidence below 80% needs confirmation

0.3 Entity Field Extraction

Infer Entity fields from UI elements in images:

entity:
  name: {EntityName}
  fields:
    - name: title
      type: String
      required: true
      ui_component: TextField
      validation:
        max_length: 100
      source_screen: form.png

    - name: content
      type: String
      required: true
      ui_component: TextArea
      validation:
        max_length: 5000
      source_screen: form.png

    - name: category
      type:  " {EntityName}Category " 
       required: true
      ui_component: Dropdown
      options: [general, notice, event]
      source_screen: form.png

    - name: imageUrls
      type:  " List < String > ? " 
       required: false
      ui_component: ImagePicker
      validation:
        max_count: 5
      source_screen: form.png

0.4 BDD Scenario Generation

Apply default BDD scenario templates based on screen type.

Language: BDD scenarios are generated in Korean (project convention).

List screen:

  • List loading success
  • Pull to refresh
  • Infinite scroll
  • Card tap โ†’ navigate to detail
  • Apply filter
  • Empty list

Detail screen:

  • Display detail info
  • Like toggle
  • Edit/delete
  • Go back

Form screen:

  • Valid form submission
  • Required field missing
  • Image attachment
  • Cancel creation

0.5 Requirements Document Generation

Save analysis results as markdown:

Path: claudedocs/{feature_name}/requirements.md

# {Feature} Requirements Specification

 >   Auto-generated from image analysis

## Entity Definition

### {Entity}
| Field | Type | Required | Description |
|------|------|------|------|
| title | String | โœ… | Title |
| content | String | โœ… | Content |
...

## Screen Definition

### 1. List Screen (list.png)
- Components: AppBar, ListView, FAB
- Actions: Card tap โ†’ navigate to detail, FAB tap โ†’ create
...

0.6 Image Save and URL Generation (with --attach-images true)

Default: --attach-images is disabled (false) by default. You must explicitly specify --attach-images true to commit images to the repo.

Save images to repo and generate GitHub raw URLs:

โš ๏ธ Caution: Pushes directly to the current branch. May fail for protected branches (main/development).

User confirmation prompt:

๐Ÿ–ผ๏ธ ์ด๋ฏธ์ง€๋ฅผ repo์— commitํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

- ๋Œ€์ƒ ํŒŒ์ผ: list.png, detail.png, form.png
- ์ €์žฅ ๊ฒฝ๋กœ: claudedocs/{feature}/screenshots/
- ํ˜„์žฌ ๋ธŒ๋žœ์น˜: {current_branch}

**๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? (y/N)**
# 1. Copy images to claudedocs
cp {input_images} claudedocs/{feature}/screenshots/

# 2. Add to Git (current branch)
git add claudedocs/{feature}/screenshots/
git commit -m  " docs: ๐Ÿ“ธ {feature} ์Šคํฌ๋ฆฐ์ƒท ์ถ”๊ฐ€ " 
 git push origin HEAD

# 3. Generate GitHub raw URLs
# https://raw.githubusercontent.com/{owner}/{repo}/{branch}/claudedocs/{feature}/screenshots/{image}

Protected Branch Handling:

  • Recommend running from feature branch
  • If push fails, proceed without image URLs (use local paths)

0.7 User Confirmation

## ๐Ÿ“ธ ์ด๋ฏธ์ง€ ๋ถ„์„ ๊ฒฐ๊ณผ

### ์‹๋ณ„๋œ ํ™”๋ฉด
| # | ์ด๋ฏธ์ง€ | ํ™”๋ฉด ํƒ€์ž… | ์ปดํฌ๋„ŒํŠธ ์ˆ˜ |
|---|--------|----------|-----------|
| 1 | list.png | List | 5 |
| 2 | detail.png | Detail | 4 |
| 3 | form.png | Form | 6 |

### ์ถ”์ถœ๋œ Entity ํ•„๋“œ
| ํ•„๋“œ | ํƒ€์ž… | ํ•„์ˆ˜ | ์ถœ์ฒ˜ |
|------|------|------|------|
| title | String | โœ… | form.png |
| content | String | โœ… | form.png |
| category | Enum | โœ… | form.png |

### ์ƒ์„ฑํ•  BDD ์‹œ๋‚˜๋ฆฌ์˜ค
- ๋ชฉ๋ก: 8๊ฐœ
- ์ƒ์„ธ: 6๊ฐœ
- ํผ: 7๊ฐœ

**๋ถ„์„ ๊ฒฐ๊ณผ๊ฐ€ ์ •ํ™•ํ•ฉ๋‹ˆ๊นŒ? (Y/n)**

0.8 Error Handling

SituationHandling
Image file not foundOutput โŒ File not found: {path} and exit
Unsupported format Only PNG, JPG, JPEG, WEBP supported. Other formats ignored with warning
Git push failureProceed without image URLs, use local paths (show warning)
Analysis confidence < threshold Request manual user confirmation: โš ๏ธ Confidence {n}% - please verify screen type
No images in directory Output โš ๏ธ No supported images in directory: {path} and exit
Entity field extraction failure Proceed with empty fields, request manual input from user

Step 1: Load Requirements#

Priority:

  1. Requirements generated in Step 0 (when using --images)
  2. File specified via --requirements
  3. Default path: claudedocs/{feature}/requirements.md
## ์š”๊ตฌ์‚ฌํ•ญ ๋กœ๋“œ

1. claudedocs/{feature}/requirements.md ์ฝ๊ธฐ
2. BDD ์‹œ๋‚˜๋ฆฌ์˜ค ํŒŒ์ผ๋“ค ์ฝ๊ธฐ (์žˆ๋Š” ๊ฒฝ์šฐ)
3. Entity ํ•„๋“œ ์ •์˜ ์ถ”์ถœ
4. ํ™”๋ฉด ์ •์˜ ์ถ”์ถœ

Step 2: Epic Creation#

MCP call example:

mcp__zenhub__createZenhubIssue({
  title:  " {feature} ๊ธฐ๋Šฅ ๊ตฌํ˜„ " ,
  body:  " {epic_body_template} " ,
  repositoryId: repoId,      // getWorkspacePipelinesAndRepositories()์—์„œ ์กฐํšŒ
  issueTypeId: epicTypeId,   // getIssueTypes()์—์„œ ์กฐํšŒ
  labels: [ " epic " ,  " feature " ,  " {feature_name} " ]
})
## Epic ์ƒ์„ฑ ํ™•์ธ

**์ œ๋ชฉ**: {feature} ๊ธฐ๋Šฅ ๊ตฌํ˜„

**๋‚ด์šฉ ๋ฏธ๋ฆฌ๋ณด๊ธฐ**:
---
# Epic: {Feature} ๊ธฐ๋Šฅ ๊ตฌํ˜„

## ๐Ÿ“‹ ๊ฐœ์š”
{feature_description}

## ๐Ÿ’ผ ๋น„์ฆˆ๋‹ˆ์Šค ๊ฐ€์น˜
**์‚ฌ์šฉ์ž๋กœ์„œ**, {capability}์„(๋ฅผ) ์›ํ•ฉ๋‹ˆ๋‹ค.
**๊ทธ๋ž˜์„œ** {value}์„(๋ฅผ) ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

## ๐Ÿ“Š ๋ฒ”์œ„
- โœ… ๋ชฉ๋ก ํ™”๋ฉด
- โœ… ์ƒ์„ธ ํ™”๋ฉด
- โœ… ํผ ํ™”๋ฉด

## ๐Ÿ› ๏ธ ๊ธฐ์ˆ  ๋…ธํŠธ
| ํ•ญ๋ชฉ | ๊ฐ’ |
|------|-----|
| Entity | `{EntityName}` |
| Backend | Serverpod endpoint |
| Caching | SWR |
---

**๋ผ๋ฒจ**: epic, feature, {feature_name}, petmedi

**์ƒ์„ฑํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? (Y/n)**

Step 3: Story Creation#

Create per-screen Stories after confirmation:

MCP call example (repeated per screen):

mcp__zenhub__createZenhubIssue({
  title:  " {feature} {screen} ํ™”๋ฉด " ,
  body:  " {story_body_template} " ,
  repositoryId: repoId,         // getWorkspacePipelinesAndRepositories()์—์„œ ์กฐํšŒ
  issueTypeId: featureTypeId,   // getIssueTypes()์—์„œ ์กฐํšŒ
  parentIssueId:  " {epic_id} " ,                                    // Epic๊ณผ ์—ฐ๊ฒฐ
  labels: [ " story " ,  " {feature_name} " ,  " {screen_type}-view " ]
})
## Story ์ƒ์„ฑ

| # | Screen | Title | Point | Acceptance Criteria Count |
|---|------|------|-------|-------|
| 1 | List | {feature} ๋ชฉ๋ก ํ™”๋ฉด | 3 | 8 |
| 2 | Detail | {feature} ์ƒ์„ธ ํ™”๋ฉด | 3 | 9 |
| 3 | Form | {feature} ํผ ํ™”๋ฉด | 5 | 10 |

**Total Stories**: 3
**Total Points**: 11

Step 3.5: Sub-task Creation (Optional)#

Create detailed tasks as Sub-tasks under Stories.

MCP call example:

mcp__zenhub__createZenhubIssue({
  title:  " [{PREFIX}-00X-0Y] {task_description} " ,
  body:  " {subtask_body} " ,
  repositoryId: repoId,         // getWorkspacePipelinesAndRepositories()์—์„œ ์กฐํšŒ
  issueTypeId: subtaskTypeId,   // getIssueTypes()์—์„œ ์กฐํšŒ
  parentIssueId:  " {story_id} " ,                                   // Story์™€ ์—ฐ๊ฒฐ
  labels: [ " subtask " ,  " {feature_name} " ,  " p{priority} " ]
})

Auto-generated Sub-tasks by screen type:

Screen TypeSub-task List
ListTable column definition, search, filter, sort, pagination
DetailData display, action buttons, edit/delete confirmation
Form Field validation, form submission, cancel handling, image upload

Sub-task title format:

  • [{PREFIX}-001-01] : First Sub-task of Story 001
  • [{PREFIX}-001-02] : Second Sub-task of Story 001
  • PREFIX is based on feature_name (e.g., AUTH, INST, BOOK)

Step 4: Epic-Story Linking#

## ์—ฐ๊ฒฐ ์™„๋ฃŒ

โœ… Epic #{epic_number} ์ƒ์„ฑ ์™„๋ฃŒ
โœ… Story #{story_1} ์—ฐ๊ฒฐ๋จ
โœ… Story #{story_2} ์—ฐ๊ฒฐ๋จ
โœ… Story #{story_3} ์—ฐ๊ฒฐ๋จ

### ์ƒ์„ฑ๋œ ์ด์Šˆ ๋งํฌ
- Epic: https://github.com/cocode/petmedi/issues/{epic_number}
- ๋ชฉ๋ก: https://github.com/cocode/petmedi/issues/{story_1}
- ์ƒ์„ธ: https://github.com/cocode/petmedi/issues/{story_2}
- ํผ: https://github.com/cocode/petmedi/issues/{story_3}

Output Files#

claudedocs/{feature_name}/
โ”œโ”€โ”€ screenshots/              # ์ด๋ฏธ์ง€ ๋ถ„์„ ์‹œ ์ €์žฅ (--images ์‚ฌ์šฉ ์‹œ)
โ”‚   โ”œโ”€โ”€ list.png
โ”‚   โ”œโ”€โ”€ detail.png
โ”‚   โ””โ”€โ”€ form.png
โ”œโ”€โ”€ image_analysis.md         # ์ด๋ฏธ์ง€ ๋ถ„์„ ๊ฒฐ๊ณผ (--images ์‚ฌ์šฉ ์‹œ)
โ”œโ”€โ”€ requirements.md           # ์š”๊ตฌ์‚ฌํ•ญ (์ž๋™ ์ƒ์„ฑ ๋˜๋Š” ๊ธฐ์กด ํŒŒ์ผ)
โ”œโ”€โ”€ bdd/                      # BDD ์‹œ๋‚˜๋ฆฌ์˜ค (์ž๋™ ์ƒ์„ฑ)
โ”‚   โ”œโ”€โ”€ {feature}_list.feature
โ”‚   โ”œโ”€โ”€ {feature}_detail.feature
โ”‚   โ””โ”€โ”€ {feature}_form.feature
โ””โ”€โ”€ zenhub/
    โ”œโ”€โ”€ epic.md               # Epic ์ƒ์„ธ ์ •๋ณด
    โ””โ”€โ”€ stories/
        โ”œโ”€โ”€ list_story.md     # ๋ชฉ๋ก Story ์ •๋ณด
        โ”œโ”€โ”€ detail_story.md   # ์ƒ์„ธ Story ์ •๋ณด
        โ””โ”€โ”€ form_story.md     # ํผ Story ์ •๋ณด

Epic Template#

# Epic: {Feature} ๊ธฐ๋Šฅ ๊ตฌํ˜„

## ๐Ÿ“‹ ๊ฐœ์š”
{Feature์— ๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ์„ค๋ช…}

## ๐Ÿ’ผ ๋น„์ฆˆ๋‹ˆ์Šค ๊ฐ€์น˜
**์‚ฌ์šฉ์ž๋กœ์„œ**, {feature_capability}์„(๋ฅผ) ์›ํ•ฉ๋‹ˆ๋‹ค.
**๊ทธ๋ž˜์„œ** {business_value}์„(๋ฅผ) ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

## ๐Ÿ“Š ๋ฒ”์œ„

### ํฌํ•จ
- โœ… {screen_1} ํ™”๋ฉด
- โœ… {screen_2} ํ™”๋ฉด
- โœ… {screen_3} ํ™”๋ฉด

### ์ œ์™ธ
- โŒ (ํ•„์š”์‹œ ๋ช…์‹œ)

## ๐Ÿ› ๏ธ ๊ธฐ์ˆ  ๋…ธํŠธ
| ํ•ญ๋ชฉ | ๊ฐ’ |
|------|-----|
| Entity | `{EntityName}` |
| Backend | Serverpod endpoint |
| Caching | {SWR / Cache-First} |
| Location | `feature/{location}/{feature_name}` |

## ๐ŸŽจ ๋””์ž์ธ ์Šคํฌ๋ฆฐ์ƒท

 >   `--images` ์‚ฌ์šฉ ์‹œ ์ž๋™ ์ƒ์„ฑ๋จ

### ๋ชฉ๋ก ํ™”๋ฉด
![๋ชฉ๋ก ํ™”๋ฉด](https://raw.githubusercontent.com/{owner}/{repo}/{branch}/claudedocs/{feature}/screenshots/list.png)

### ์ƒ์„ธ ํ™”๋ฉด
![์ƒ์„ธ ํ™”๋ฉด](https://raw.githubusercontent.com/{owner}/{repo}/{branch}/claudedocs/{feature}/screenshots/detail.png)

### ํผ ํ™”๋ฉด
![ํผ ํ™”๋ฉด](https://raw.githubusercontent.com/{owner}/{repo}/{branch}/claudedocs/{feature}/screenshots/form.png)

 >   ๋˜๋Š” Figma ๋งํฌ: {figma_urls}

## ๐Ÿ“Ž ๊ด€๋ จ Story
- [ ] #{story_1_number} - ๋ชฉ๋ก ํ™”๋ฉด
- [ ] #{story_2_number} - ์ƒ์„ธ ํ™”๋ฉด
- [ ] #{story_3_number} - ํผ ํ™”๋ฉด

---
 >   ๐Ÿค– Generated by `/zenhub:epic`

Story Template#

# Story: {Screen Name}

## ๐Ÿ“‹ ์‚ฌ์šฉ์ž ์Šคํ† ๋ฆฌ
**{user_type}๋กœ์„œ**, {screen_capability}์„(๋ฅผ) ์›ํ•ฉ๋‹ˆ๋‹ค.
**๊ทธ๋ž˜์„œ** {value}์„(๋ฅผ) ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

## โœ… ์ธ์ˆ˜ ๊ธฐ์ค€ (Acceptance Criteria)

### AC1: {scenario_1_name}
gherkin

Given {precondition_1} When {action_1} Then {expected_result_1}

- **Priority**: {High/Medium/Low}
- **Tag**: @{tag}

### AC2: {scenario_2_name}
gherkin

Given {precondition_2} When {action_2} Then {expected_result_2}

- **Priority**: {High/Medium/Low}
- **Tag**: @{tag}

## ๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์ž‘์—…

### Backend
- [ ] Serverpod endpoint ๊ตฌํ˜„
- [ ] DTO ์ •์˜

### Domain
- [ ] Entity ์ •์˜
- [ ] Repository Interface ์ •์˜
- [ ] UseCase ๊ตฌํ˜„
- [ ] UseCase ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

### Data
- [ ] Repository ๊ตฌํ˜„
- [ ] Serverpod Mixin ๊ตฌํ˜„
- [ ] ์บ์‹ฑ ์ „๋žต ์ ์šฉ

### Presentation
- [ ] BLoC ๊ตฌํ˜„ (Event/State)
- [ ] Page ์œ„์ ฏ ๊ตฌํ˜„
- [ ] Widget ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„
- [ ] Route ๋“ฑ๋ก
- [ ] BDD ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

### Widgetbook
- [ ] Component story ์ถ”๊ฐ€

## ๐ŸŽจ ๋””์ž์ธ ์ฐธ์กฐ

 >   `--images` ์‚ฌ์šฉ ์‹œ ์ž๋™ ์ƒ์„ฑ๋จ

![{screen_name}](https://raw.githubusercontent.com/{owner}/{repo}/{branch}/claudedocs/{feature}/screenshots/{screen}.png)

 >   ๋˜๋Š” Figma ๋งํฌ: {figma_link}

## ๐Ÿ“ ์˜ˆ์ƒ Story Point
{point_value}

---
 >   ๐Ÿค– Generated by `/zenhub:epic`
 >   ๐Ÿ“Ž Epic: #{epic_number}

MCP Integration#

TaskMCP ServerPurpose
Issue creationZenHubEpic/Story creation
Template structuring Sequential Systematic issue organization

Examples#

Basic Usage#

/zenhub:epic community Post

Specify Requirements File#

/zenhub:epic community Post \
  --requirements claudedocs/community/requirements.md \
  --bdd-dir claudedocs/community/bdd/

Specify Sprint and Labels#

/zenhub:epic community Post \
  --labels  " sprint-1,mvp "   \
  --sprint current

Custom Story Points#

/zenhub:epic community Post \
  --screens  " list,detail,form "   \
  --points  " 5,3,8 "

Create Epic from Image Files (new)#

/zenhub:epic community Post \
  --images  " list.png,detail.png,form.png "

Create Epic from Image Directory#

/zenhub:epic community Post \
  --images-dir ./screenshots/community/

Images + Additional Options#

/zenhub:epic community Post \
  --images  " list.png,detail.png,form.png "   \
  --labels  " sprint-1,mvp "   \
  --sprint current

Analysis Only Without Image Attachment#

/zenhub:epic community Post \
  --images  " list.png,detail.png,form.png "   \
  --attach-images false

Reference Agent#

Detailed implementation rules: ~/.claude/commands/agents/zenhub-integration-agent.md

Key Rules#

  1. Epic First: Create Epic before Stories
  2. Link Required: All Stories must be linked to Epic
  3. Include Acceptance Criteria: Include BDD-based Acceptance Criteria in each Story
  4. Label Consistency: Follow the defined label system
  5. Create After Confirmation: Create actual issues only after user confirmation
  6. Documentation: Save creation results to claudedocs
  7. Image Analysis: Detailed analysis via Claude multimodal when using --images
  8. Image Attachment: On --attach-images true, commit to repo after user confirmation to attach URLs to Issues
  9. BDD Language: Auto-generated BDD scenarios are written in Korean