| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /zenhub:epic |
| Category | kobic-workflow |
| Complexity | moderate |
| MCP Servers | zenhub, sequential |
/zenhub:epic โ ์๊ตฌ์ฌํญ์ ZenHub ์ผ๊ฐ์ผ๋ก ์๋ ์ ๋ฆฌ#
ํ๋ง๋๋ก#
๊ธฐ๋ฅ ์๊ตฌ์ฌํญ(๋๋ ํ๋ฉด ์บก์ฒ ์ด๋ฏธ์ง)์ ๋ฃ์ผ๋ฉด, ZenHub์ "ํฐ ์ผ๊ฐ โ ์์ ์ผ๊ฐ"์ ์๋์ผ๋ก ๋ง๋ค์ด ์ฃผ๋ ๋ช ๋ น์ ๋๋ค. ํฐ ๊ณต์ฌ ๊ณํ์๋ฅผ ๋ฐ์์ "ํ๋ก์ ํธ โ ๋จ๊ณ โ ์ธ๋ถ ์์ "์ผ๋ก ์ชผ๊ฐ๊ณ , ์ด๋ค ์ผ์ ๋จผ์ ํ ์ง ์ฐ์ ์์๊น์ง ๋งค๊ฒจ ์ผ์ ํ(๋ณด๋)์ ๊ฝ์ ์ฃผ๋ ๋น์๋ผ๊ณ ๋ณด๋ฉด ๋ฉ๋๋ค.
๋๊ฐยท์ธ์ ์ฐ๋์#
- ๊ธฐํ/์๊ตฌ์ฌํญ์ด ์ ๋ฆฌ๋ ์ํ์์ ZenHub์ ์ค์ ์ผ๊ฐ(์ด์)์ ๋ง๋ค์ด์ผ ํ ๋
- ํ๋ฉด ์บก์ฒ ์ด๋ฏธ์ง๋ง ์๊ณ , ๊ทธ๊ฑธ ๋ฐํ์ผ๋ก ์๊ตฌ์ฌํญ๊ณผ ์ผ๊ฐ์ ํ ๋ฒ์ ๋ฝ๊ณ ์ถ์ ๋
/figma:analyze(ํผ๊ทธ๋ง ๋ถ์)์ ๋ง์ง๋ง ๋จ๊ณ์์ ์๋์ผ๋ก ์ด์ด์ ํธ์ถ๋ ๋- ์ฌ๋ฌ Epic์ ๋ฌถ๋ ์์ Project ๋จ์ ์ผ๊ฐ์ ๋ง๋ค๊ณ ์ถ์ ๋ (
--project)
๋ฌด์์ ํด์ฃผ๋์#
- ZenHub ์ด์ ๊ณ์ธต์ ์๋ ์์ฑํฉ๋๋ค: (์ ํ) Project โ Epic(ํฐ ์ผ๊ฐ) โ Story(ํ๋ฉด๋ณ ์ผ๊ฐ) โ Sub-task(์ธ๋ถ ์์ )
- ๊ฐ ์ผ๊ฐ์ ์ฐ์ ์์(P0/P1/P2) ๋ฅผ ๋งค๊ธฐ๊ณ , ๊ทธ ๊ทผ๊ฑฐ ํ๋ฅผ Epic ๋ณธ๋ฌธ์ ๋ฃ์ด ์ค๋๋ค
- Epic์ ์ผ์ (์์์ผ~์ข ๋ฃ์ผ)๊ณผ Story Point(์์ ์์ ๋) ๋ฅผ ๋ฃ์ด ๋ก๋๋งต์ ๋ง๋๋ก ํ์๋๊ฒ ํฉ๋๋ค
- ๋ง๋ ์ด์๋ค์ ์ฐ์ ์์์ ๋ง๋ ํ์ดํ๋ผ์ธ(Product Backlog / Sprint Backlog / Icebox) ์ผ๋ก ๋ฐฐ์นํฉ๋๋ค
-
์ด๋ฏธ์ง ๋ถ์์ ์ฐ๋ฉด ์๊ตฌ์ฌํญยทBDD ์๋๋ฆฌ์ค ๋ฌธ์๊น์ง ์๋์ผ๋ก ๋ง๋ค์ด
.claude/docs/{๊ธฐ๋ฅ๋ช }/์๋์ ์ ์ฅํฉ๋๋ค (requirements.md, bdd/, zenhub/epic.md, priority.md ๋ฑ)
์ด๋ป๊ฒ ์ฐ๋์#
# ๊ฐ์ฅ ๊ธฐ๋ณธ โ community ๊ธฐ๋ฅ, Post ์ํฐํฐ๋ก Epic+Story ์์ฑ
/zenhub:epic community Post
# ์ฌ๋ฌ Epic์ ๋ฌถ๋ ์์ Project๊น์ง ํ ๋ฒ์ ๋ง๋ค๊ธฐ
/zenhub:epic --project " ์ด๋๋ฏผ ์ฝ์ v2 " --epics " auth,community,payment "
# ์ด๋ฏธ ์๋ Project ์ด์(์: 1024๋ฒ) ๋ฐ์ Epic ์ฐ๊ฒฐ
/zenhub:epic community Post --parent-project 1024
# ํ๋ฉด ์บก์ฒ ์ด๋ฏธ์ง๋ก๋ถํฐ ์๊ตฌ์ฌํญ+์ผ๊ฐ ์๋ ์์ฑ
/zenhub:epic community Post --images " list.png,detail.png,form.png "
# ์คํ๋ฆฐํธยท๋ผ๋ฒจ ์ง์
/zenhub:epic community Post --labels " sprint-1,mvp " --sprint current
# ์ผ์ ์ง์ ์ง์ (๋๋ --no-dates ๋ก ์ผ์ ๋น์ฐ๊ธฐ)
/zenhub:epic community Post --start-date 2026-06-16 --end-date 2026-07-14
์์ฃผ ์ฐ๋ ์ต์ ๋ช ๊ฐ์ง:
--images/--images-dir: ํ๋ฉด ์บก์ฒ๋ก ์๊ตฌ์ฌํญยทBDD๊น์ง ์๋ ์์ฑ--points "3,3,5": ํ๋ฉด๋ณ ์์ ์์ ๋(Story Point)์ ์ง์ ์ง์ --no-sort: ํ์ดํ๋ผ์ธ ์๋ ์ ๋ ฌ์ ๊ฑด๋๋--no-dates: Epic ์ผ์ ์ค์ ์ ๊ฑด๋๋
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
๋๋ต ์ด๋ฐ ์์๋ก ์งํ๋๊ณ , ์ค์ํ ๋ถ๊ธฐ๋ง๋ค "์ด๋๋ก ์งํํ ๊น์?"๋ผ๊ณ ์ฌ๋์๊ฒ ํ์ธ์ ๋ฐ์ต๋๋ค.
- (์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด) ํ๋ฉด ๋ถ์ โ ์บก์ฒ๊ฐ ๋ชฉ๋ก/์์ธ/ํผ ์ค ๋ฌด์์ธ์ง ์์๋ด๊ณ , ๊ฑฐ๊ธฐ์ Entity ํ๋์ BDD ์๋๋ฆฌ์ค, ์๊ตฌ์ฌํญ ๋ฌธ์๋ฅผ ์๋์ผ๋ก ๋ฝ์ต๋๋ค.
- ์๊ตฌ์ฌํญ ์ฝ๊ธฐ โ ๋ฐฉ๊ธ ๋ง๋ (๋๋ ๊ธฐ์กด) ์๊ตฌ์ฌํญยทBDD ๋ฌธ์๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
- ์ฐ์ ์์ ๊ฒํ โ ๋ง๋ค ์ผ๊ฐ๋ง๋ค ์์กด์ฑยท์ค์๋ยท์ํยท์์ ๋์ ๋ฐ์ ธ P0/P1/P2 ๋ฑ๊ธ์ ๋งค๊ธฐ๊ณ , ํ๋ก ๋ณด์ฌ ์ค ๋ค ํ์ธ์ ๋ฐ์ต๋๋ค. (๋ผ๋ฒจ์ ์์ฑ ํ ๋ชป ๋ฐ๊พธ๋ฏ๋ก ๋ฏธ๋ฆฌ ์ ํฉ๋๋ค.)
- ์ด์ ์์ฑ โ (์ ํ) Project โ Epic โ Story โ Sub-task ์์๋ก ZenHub ์ด์๋ฅผ ๋ง๋ค๊ณ ์๋ก ๋ถ๋ชจ-์์์ผ๋ก ์ฐ๊ฒฐํฉ๋๋ค.
- ์ผ์ ยท์์ ๋ ์ค์ โ Epic์ ์์/์ข ๋ฃ์ผ๊ณผ Story Point๋ฅผ ๋ฃ์ด ๋ก๋๋งต์ ํ์๋๊ฒ ํฉ๋๋ค. Story๋ค๋ Epic ๊ธฐ๊ฐ ์์์ ์์๋๋ก ๋๋ ๋ฐฐ์นํฉ๋๋ค.
- ํ์ดํ๋ผ์ธ ์ ๋ ฌ โ ์ฐ์ ์์์ ๋ฐ๋ผ ๊ฐ ์ด์๋ฅผ ์๋ง์ ๋ณด๋(ํ์ดํ๋ผ์ธ)๋ก ์ฎ๊ธฐ๊ณ , P0 Story๋ (์์ฒญ ์) ์คํ๋ฆฐํธ์ ๋ฃ์ต๋๋ค.
- ๊ฒฐ๊ณผ ๊ธฐ๋ก โ ๋ง๋ ๋ด์ฉ๊ณผ ์ฐ์ ์์ยท๋ฐฐ์น ๊ฒฐ๊ณผ๋ฅผ
.claude/docs/{๊ธฐ๋ฅ๋ช }/์๋ ๋ฌธ์๋ก ๋จ๊น๋๋ค.
โ๏ธ ์์ธ ์ต์ ยท์คํ ๋ช ์ธ (๊ฐ๋ฐ์ / AI ์์ด์ ํธ์ฉ)
Triggers#
- When creating issues from feature requirements
- When called from
/figma:analyzePhase 4 - When manually creating ZenHub issues
- When creating a Project-level issue that wraps multiple Epics (
--project)
Context Trigger Pattern#
/zenhub:epic {feature_name} {entity_name} [--options]Parameters#
Required Parameters#
| Parameter | Description | Example |
|---|---|---|
feature_name | Feature name (snake_case) | community |
entity_name | Entity name (PascalCase) | Post |
Image Analysis Options (Optional)#
| Parameter | Description | Example |
|---|---|---|
--images | Image files to analyze (comma-separated) | "list.png,detail.png,form.png" |
--images-dir | Image 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-analysis | Skip image analysis | true/false |
--no-cache | Ignore cache and re-analyze | true/false |
Hierarchy Options (Optional)#
| Parameter | Description | Example |
|---|---|---|
--initiative | Create an Initiative-level issue (wraps the Project/Epic(s) as children) | --initiative "2026 H2 ์ ๋ต ๋ชฉํ" |
--parent-initiative | Link the created Project (or top-level Epic) to an existing Initiative issue (issue number) | --parent-initiative 900 |
--project | Create a Project-level issue and link the Epic(s) as children | --project "์ด๋๋ฏผ ์ฝ์ v2" |
--parent-project | Link the Epic to an existing Project issue (issue number) | --parent-project 1024 |
--epics | Multiple Epic names under one Project (comma-separated; default: single Epic) | "auth,community,payment" |
--projectand--parent-projectare mutually exclusive; so are--initiativeand--parent-initiative. If none are given, the Epic is top-level (existing behavior).--initiative/--parent-initiativemay be combined with--project/--parent-project(Initiative wraps Project) or used alone (Initiative wraps Epic directly, when no Project level is requested).
Other Options (Optional)#
| Parameter | Description | Example |
|---|---|---|
--requirements | Requirements document path | .claude/docs/community/requirements.md |
--bdd-dir | BDD scenario directory | .claude/docs/community/bdd/ |
--labels | Additional labels | "sprint-1,mvp" |
--screens | Screens to generate | "list,detail,form" |
--points | Story Point setting | "3,3,5" |
--sprint | Sprint assignment (current=ํ์ฑ/next=๋ค์/์ซ์ยท์ด๋ฆ) | "current" |
--start-date | Epic timeline start date (ISO YYYY-MM-DD). Default: ํ์ฑ ์คํ๋ฆฐํธ ์์ (or today) | 2026-06-16 |
--end-date | Epic timeline end date (ISO YYYY-MM-DD). Default: start + computed duration | 2026-07-14 |
--duration | Timeline length in weeks (used when --end-date omitted). Default: auto from total points | 4 |
--no-sort | Skip the priority review/pipeline sorting step (Step 5) | true/false |
--no-dates | Skip timeline date setting (Epic created without a period) | true/false |
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.githubRepositories.find(r = > /* GitHub ๋ ํฌ ์ ํ */).id;
// 2. Issue Type ID (includes hierarchy level: lower = higher level)
const issueTypes = await mcp__zenhub__getIssueTypes({ repositoryId: repoId });
const initiativeType = issueTypes.find(t = > t.name === " Initiative " ); // may be absent
const projectType = issueTypes.find(t = > t.name === " Project " ); // may be absent
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;
// Initiative type fallback: if --initiative given but type absent,
// warn the user and create the Project (or Epic, if no --project) as top level instead
if (useInitiativeLevel & & !initiativeType) {
// โ ๏ธ " ์ด ์ํฌ์คํ์ด์ค์๋ Initiative Issue Type์ด ์์ต๋๋ค. Project(๋๋ Epic)๋ฅผ ์ต์์๋ก ์์ฑํฉ๋๋ค. "
}
// Project type fallback: if --project given but type absent,
// warn the user and create Epic(s) as top level instead
if (useProjectLevel & & !projectType) {
// โ ๏ธ " ์ด ์ํฌ์คํ์ด์ค์๋ Project Issue Type์ด ์์ต๋๋ค. Epic์ ์ต์์๋ก ์์ฑํฉ๋๋ค. "
}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:
| Image | Screen Type | Confidence | Key Components |
|---|---|---|---|
| list.png | List | 95% | AppBar, ListView, FAB |
| detail.png | Detail | 90% | SliverAppBar, ContentSection |
| form.png | Form | 92% | 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.png0.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: .claude/docs/{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-imagesis disabled (false) by default. You must explicitly specify--attach-images trueto 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
- ์ ์ฅ ๊ฒฝ๋ก: .claude/docs/{feature}/screenshots/
- ํ์ฌ ๋ธ๋์น: {current_branch}
**๊ณ์ํ์๊ฒ ์ต๋๊น? (y/N)**# 1. Copy images to .claude/docs
cp {input_images} .claude/docs/{feature}/screenshots/
# 2. Add to Git (current branch)
git add .claude/docs/{feature}/screenshots/
git commit -m " docs: ๐ธ {feature} ์คํฌ๋ฆฐ์ท ์ถ๊ฐ "
git push origin HEAD
# 3. Generate GitHub raw URLs
# https://raw.githubusercontent.com/{owner}/{repo}/{branch}/.claude/docs/{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
| Situation | Handling |
|---|---|
| Image file not found | Output โ File not found: {path} and exit |
| Unsupported format | Only PNG, JPG, JPEG, WEBP supported. Other formats ignored with warning |
| Git push failure | Proceed 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:
- Requirements generated in Step 0 (when using
--images) - File specified via
--requirements - Default path:
.claude/docs/{feature}/requirements.md
## ์๊ตฌ์ฌํญ ๋ก๋
1. .claude/docs/{feature}/requirements.md ์ฝ๊ธฐ
2. BDD ์๋๋ฆฌ์ค ํ์ผ๋ค ์ฝ๊ธฐ (์๋ ๊ฒฝ์ฐ)
3. Entity ํ๋ ์ ์ ์ถ์ถ
4. ํ๋ฉด ์ ์ ์ถ์ถStep 1.5: Priority Review (before creation)#
โ ๏ธ
updateIssuecannot change labels โ priority labels must be decided before issue creation.
Score every issue to be created (Epic/Story/Sub-task) and assign a tier per
rules/zenhub-conventions.md โ Priority Review and Pipeline Sorting:
| Axis | Question |
|---|---|
| Dependency | Do other Stories depend on this? (Entity/API foundation = P0) |
| Business value | Core user path of the Epic? |
| Risk | Technically uncertain โ earlier |
| Effort | Among equals, smaller points first |
Matrix(impactรeffort) ์ ํธ ์์ํ (C6): effort ๋
setIssueEstimate(Story Point)๋ก, impact ๋ ๋ณ๋ ๋ผ๋ฒจimpact-high|impact-med|impact-low๋ก ์ ์ฅํ๋ค(๋ผ๋ฒจ ๋ถ๋ณ โ ์์ฑ ์ ํ์ ). impact = Business value + Dependency + Risk (effort ์ ์ธ โ effort ๋ฅผ impact ์ ์์ผ๋ฉด ZenHub Matrix ์์ ์ด์ค ๊ณ์ฐ๋๋ค). P-tier ์ ๋ ๋ฆฝ๋ ์ถ์ด๋ฉฐ, ์ด๊ฑธ ์ฑ์์ผ Matrix Y์ถ์ด ์๋ฏธ๋ฅผ ๊ฐ๋๋ค.
## ๐ข ์ฐ์ ์์ ๊ฒํ ๊ฒฐ๊ณผ
| ์์ | ์ด์ | ํฐ์ด | impact | ๊ทผ๊ฑฐ |
|------|------|------|--------|------|
| 1 | {feature} ๋ชฉ๋ก ํ๋ฉด | P0 | high | ์์ธ/ํผ ํ๋ฉด์ด ๋ชฉ๋ก Entity์ ์์กด(value+dependency ๋์) |
| 2 | {feature} ์์ธ ํ๋ฉด | P1 | med | ํต์ฌ ๊ฒฝ๋ก, ์์กด ์ด์ ์์ |
| 3 | {feature} ํผ ํ๋ฉด | P1 | low | ํต์ฌ ๊ฒฝ๋ก |
**์ด ์ฐ์ ์์๋ก ์งํํ์๊ฒ ์ต๋๊น? (Y/n)**Confirmed tiers become p0/p1/p2 and impact-high|med|low labels passed to createGitHubIssue (both immutable post-creation), and the table is embedded in the Epic (or Project) body.
Step 1.6: Initiative Creation (with --initiative)#
Create the Initiative-level issue first, one level above Project; the Project (or, if no --project, the Epic directly) is linked as its child.
const initiative = await mcp__zenhub__createGitHubIssue({
title: " {initiative_title} " ,
body: " {initiative_body_template} " , // strategic goal, wrapped Project/Epic list
repositoryId: repoId,
issueTypeId: initiativeType.id,
labels: [ " initiative " , " p{priority} " ]
});With --parent-initiative {issue_number}, query the existing issue via searchLatestIssues and use its GraphQL ID instead of creating a new one.
Step 1.7: Project Creation (with --project)#
Create the Project-level issue first; Epics are linked as children.
const project = await mcp__zenhub__createGitHubIssue({
title: " {project_title} " ,
body: " {project_body_template} " , // goals, Epic list, priority table
repositoryId: repoId,
issueTypeId: projectType.id,
parentIssueId: initiative?.id, // when --initiative/--parent-initiative is used
labels: [ " project " , " p{priority} " ]
});With --parent-project {issue_number}, query the existing issue via searchLatestIssues and use its GraphQL ID as the parent instead of creating a new one.
When
--initiative/--parent-initiativeis used without--project, link the Epic directly to the Initiative instead (parentIssueId: initiative.idon the Epic in Step 2).
Step 2: Epic Creation#
MCP call example:
mcp__zenhub__createGitHubIssue({
title: " {feature} ๊ธฐ๋ฅ ๊ตฌํ " ,
body: " {epic_body_template} " ,
repositoryId: repoId, // getWorkspacePipelinesAndRepositories()์์ ์กฐํ
issueTypeId: epicTypeId, // getIssueTypes()์์ ์กฐํ
parentIssueId: project?.id ?? initiative?.id, // --project/--parent-project ์ฐ์ , ์์ผ๋ฉด --initiative/--parent-initiative ์ง์ ์ฐ๊ฒฐ
labels: [ " epic " , " feature " , " {feature_name} " , " p{priority} " ]
})Per
rules/zenhub-conventions.md, always usecreateGitHubIssueโcreateZenhubIssueis prohibited (ZenHub-native issues cannot move pipelines or set timelines).
Step 2.5: Epic Timeline + Estimate (required โ skipped only with --no-dates)
โ ๏ธ Without this step the Epic never appears on the Goals & Planning timeline.
setIssueEstimatecontrols SP;setDatesForIssuecontrols the timeline period. Setting SP alone (the previous behavior) leavesstartDate/endDatenull, so the Epic shows points but no bar on the roadmap. Always set both.
// 1) Organization ID (required by setDatesForIssue)
const orgId = workspace.zenhubOrganization.id;
// 2) Compute the timeline window
// ์์: ๋ช
์ ํ๋๊ทธ > --sprint ์
๋ ํฐ ์คํ๋ฆฐํธ ์์ > ์ค๋
// โ ๏ธ getUpcomingSprint ๋ ' ๋ค์ ' ์คํ๋ฆฐํธ๋ค. ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฐ๋ฉด Epic ๋ฐ๊ฐ ํ ์คํ๋ฆฐํธ ๋ฐ๋ฆฐ๋ค.
// ๊ธฐ๋ณธ(current)์ getSprint()(id ์์=ํ์ฑ)๋ฅผ ์ด๋ค.
const kickoffSprint = options.sprint === " next "
? await mcp__zenhub__getUpcomingSprint()
: await mcp__zenhub__getSprint(); // ๊ธฐ๋ณธ current = ํ์ฑ ์คํ๋ฆฐํธ
const startDate =
options.startDate ??
kickoffSprint?.startAt?.slice(0, 10) ??
todayISO(); // " YYYY-MM-DD "
// Default duration auto-derived from total Story Points when --duration/--end-date omitted.
// Heuristic: ~5 points โ one 2-week sprint (min 2 weeks, rounded up to whole weeks).
const totalPoints = stories.reduce((s, x) = > s + x.point, 0);
const weeks = options.duration ?? Math.max(2, Math.ceil(totalPoints / 5) * 2);
const endDate = options.endDate ?? addWeeksISO(startDate, weeks);
// 3) Set the Epic timeline (THIS is what was missing) โ unless --no-dates
if (!options.noDates) {
await mcp__zenhub__setDatesForIssue({
issueId: epic.id,
startDate, // " YYYY-MM-DD "
endDate, // " YYYY-MM-DD "
zenhubOrganizationId: orgId,
});
}
// 4) Set the Epic estimate (roll-up of child Story Points)
await mcp__zenhub__setIssueEstimate({
issueId: epic.id,
estimate: totalPoints,
});Verification: after the call, re-query the issue and confirm
startDate/endDateare non-null. IfsetDatesForIssuefails (e.g. missingzenhubOrganizationId), surface the error โ do not silently continue, or the Epic stays off the timeline.
## Epic ์์ฑ ํ์ธ
**์ ๋ชฉ**: {feature} ๊ธฐ๋ฅ ๊ตฌํ
**๋ด์ฉ ๋ฏธ๋ฆฌ๋ณด๊ธฐ**:
---
# Epic: {Feature} ๊ธฐ๋ฅ ๊ตฌํ
## ๐ ๊ฐ์
{feature_description}
## ๐ผ ๋น์ฆ๋์ค ๊ฐ์น
**์ฌ์ฉ์๋ก์**, {capability}์(๋ฅผ) ์ํฉ๋๋ค.
**๊ทธ๋์** {value}์(๋ฅผ) ์ป์ ์ ์์ต๋๋ค.
## ๐ ๋ฒ์
- โ
๋ชฉ๋ก ํ๋ฉด
- โ
์์ธ ํ๋ฉด
- โ
ํผ ํ๋ฉด
## ๐ ๏ธ ๊ธฐ์ ๋
ธํธ
| ํญ๋ชฉ | ๊ฐ |
|------|-----|
| Entity | `{EntityName}` |
| Backend | Serverpod endpoint |
| Caching | SWR |
---
**๋ผ๋ฒจ**: epic, feature, {feature_name}, kobic
**๐
ํ์๋ผ์ธ**: {start_date} ~ {end_date} ({weeks}์ฃผ) โ Goals & Planning ๋ก๋๋งต์ ํ์๋จ
**๐ Story Point (๋กค์
)**: {total_points}
**์์ฑํ์๊ฒ ์ต๋๊น? (Y/n)**ํ์๋ผ์ธ์ ๋น์๋๋ ค๋ฉด
--no-dates, ๊ธฐ๊ฐ์ ์ง์ ์ง์ ํ๋ ค๋ฉด--start-date/--end-date๋๋--duration์ฌ์ฉ.
Step 3: Story Creation#
Create per-screen Stories after confirmation:
MCP call example (repeated per screen):
mcp__zenhub__createGitHubIssue({
title: " {feature} {screen} ํ๋ฉด " ,
body: " {story_body_template} " ,
repositoryId: repoId, // getWorkspacePipelinesAndRepositories()์์ ์กฐํ
issueTypeId: featureTypeId, // getIssueTypes()์์ ์กฐํ
parentIssueId: " {epic_id} " , // Epic๊ณผ ์ฐ๊ฒฐ
labels: [ " story " , " {feature_name} " , " {screen_type}-view " , " p{priority} " , " impact-{impact} " ] // impact: high|med|low (Matrix Y์ถ, C6)
})
// effort ์ถ: Story Point ์ค์ (Matrix X์ถ)
await mcp__zenhub__setIssueEstimate({ issueId: story.id, estimate: story.point });
// โ ๏ธ Story ํ์๋ผ์ธ: setDatesForIssue ๋ ์์ ํ์
(Epic/Project) ์ ์ฉ์ด๋ฏ๋ก Story ์๋ ์ฐ์ง ์๋๋ค(off-spec).
// Roadmap Visibility Contract(rules/zenhub-conventions.md)๋๋ก Story ๋ **์คํ๋ฆฐํธ ๋ฉค๋ฒ์ญ**์ผ๋ก ๋
ธ์ถํ๋ค โ
// --sprint ๊ฐ ์ฃผ์ด์ง๋ฉด Step 5 ์์ addIssuesToSprints ๋ก ํด๋น ์คํ๋ฆฐํธ ๋ ์ธ์ ์ฌ๋ฆฐ๋ค.
// ์ฆ Story ์ ๋ก๋๋งต ๊ฐ์์ฑ = ๋ถ๋ชจ Epic ์ ๊ธฐ๊ฐ ๋ฐ + ์์ ์ ์คํ๋ฆฐํธ ๋ ์ธ.Story ๋ ๊ฐ๋ณ ๋ ์ง ๋์ ์คํ๋ฆฐํธ ๋ ์ธ์ผ๋ก ํ์๋ผ์ธ์ ๋ ธ์ถ๋๋ค. Epic ๋ง ๋ช ์์ ๊ธฐ๊ฐ(start/end)์ ๊ฐ๋๋ค. ์ ๋ฐ ์ผ์ ์ ZenHub ๋ณด๋์์ ๋๋๊ทธ๋ก ๋ฏธ์ธ ์กฐ์ ํ์ธ์.
## Story ์์ฑ
| # | Screen | Title | Point | Timeline | Acceptance Criteria Count |
|---|------|------|-------|----------|-------|
| 1 | List | {feature} ๋ชฉ๋ก ํ๋ฉด | 3 | {s1_start}~{s1_end} | 8 |
| 2 | Detail | {feature} ์์ธ ํ๋ฉด | 3 | {s2_start}~{s2_end} | 9 |
| 3 | Form | {feature} ํผ ํ๋ฉด | 5 | {s3_start}~{s3_end} | 10 |
**Total Stories**: 3
**Total Points**: 11
**Epic Timeline**: {epic_start} ~ {epic_end}Step 3.5: Sub-task Creation (Optional)#
Create detailed tasks as Sub-tasks under Stories.
MCP call example:
mcp__zenhub__createGitHubIssue({
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 Type | Sub-task List |
|---|---|
| List | Table column definition, search, filter, sort, pagination |
| Detail | Data 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: Hierarchy Linking (Project-Epic-Story)#
## ์ฐ๊ฒฐ ์๋ฃ
โ
Project #{project_number} ์์ฑ ์๋ฃ (--project ์ฌ์ฉ ์)
โ
Epic #{epic_number} ์์ฑ ์๋ฃ (Project์ ์ฐ๊ฒฐ๋จ)
โ
Story #{story_1} ์ฐ๊ฒฐ๋จ
โ
Story #{story_2} ์ฐ๊ฒฐ๋จ
โ
Story #{story_3} ์ฐ๊ฒฐ๋จ
### ์์ฑ๋ ์ด์ ๋งํฌ
- Project: https://github.com/cocode/kobic/issues/{project_number}
- Epic: https://github.com/cocode/kobic/issues/{epic_number}
- ๋ชฉ๋ก: https://github.com/cocode/kobic/issues/{story_1}
- ์์ธ: https://github.com/cocode/kobic/issues/{story_2}
- ํผ: https://github.com/cocode/kobic/issues/{story_3}Step 5: Pipeline Sorting (skipped with --no-sort)#
Place every created issue into its pipeline based on the Step 1.5 priority tiers.
Placement matrix follows rules/zenhub-conventions.md:
| Target | Pipeline |
|---|---|
| Project / Epic | Product Backlog |
Story P0 (with --sprint) | Sprint Backlog + addIssuesToSprints |
| Story P0/P1 (no sprint) | Product Backlog |
| Story P2 | Icebox |
| Sub-task | Follows parent Story (no separate move) |
// fail-closed ํด์: ์ด๋ฆ์ด ๋ผ์ด๋ธ ํ์ดํ๋ผ์ธ๊ณผ ๋ค๋ฅด๋ฉด throw (๋ถ๋ถ ๋ฐฐ์น ํ ๋ฌด์ ์ค๋จ ๋ฐฉ์ง)
const pipelineByName = (name) = > {
const p = workspace.pipelines.find(p = > p.name === name);
if (!p) throw new Error(`ํ์ดํ๋ผ์ธ ' ${name} ' ์์. ๋ผ์ด๋ธ: ${workspace.pipelines.map(p = > p.name).join( " , " )}`);
return p;
};
const backlog = pipelineByName( " Product Backlog " );
const sprintBacklog = pipelineByName( " Sprint Backlog " );
const icebox = pipelineByName( " Icebox " );
// โ ๏ธ moveIssueToPipeline has no position param โ
// move in DESCENDING priority order (P0 first) to approximate top-to-bottom ordering
for (const issue of issuesSortedByPriority) {
await mcp__zenhub__moveIssueToPipeline({
issueId: issue.id,
pipelineId: pipelineFor(issue), // matrix above
});
}
// --sprint ์ฌ์ฉ ์ P0 Story๋ฅผ ํด๋น ์คํ๋ฆฐํธ์ ์ถ๊ฐ
if (options.sprint) {
// ์
๋ ํฐ (rules/zenhub-conventions.md โ " Sprint Selector Resolution " ์ ๋์ผ ๊ท์น):
// current โ ํ์ฑ(getSprint, id ์์) ยท next โ ๋ค์(getUpcomingSprint) ยท ์ซ์/์ด๋ฆ โ listRecentSprints ๋งค์นญ
const targetSprint =
options.sprint === " next " ? await mcp__zenhub__getUpcomingSprint() :
options.sprint === " current " ? await mcp__zenhub__getSprint() :
(await mcp__zenhub__listRecentSprints()).openSprints
.find(s = > s.name.includes(String(options.sprint)));
if (!targetSprint?.id) throw new Error(`์คํ๋ฆฐํธ ' ${options.sprint} ' ํด์ ์คํจ โ current/next/๋ฒํธ๋ฅผ ํ์ธํ๋ผ`);
await mcp__zenhub__addIssuesToSprints({
issueIds: p0StoryIds,
sprintIds: [targetSprint.id],
});
}## ํ์ดํ๋ผ์ธ ์ ๋ ฌ ์๋ฃ
| ์ด์ | ํฐ์ด | ํ์ดํ๋ผ์ธ |
|------|------|-----------|
| Epic #{epic_number} | โ | Product Backlog |
| #{story_1} ๋ชฉ๋ก ํ๋ฉด | P0 | Sprint Backlog (Sprint 12) |
| #{story_2} ์์ธ ํ๋ฉด | P1 | Product Backlog |
| #{story_3} ํผ ํ๋ฉด | P1 | Product Backlog |
> ํ์ดํ๋ผ์ธ ๋ด ์ธ๋ถ ์์๋ MCP๊ฐ ์ง์ํ์ง ์์ต๋๋ค.
> ์ฐ์ ์์ ์์๋๋ก ์ด๋ํ์ผ๋ฉฐ, ๋ฏธ์ธ ์กฐ์ ์ ZenHub ๋ณด๋์์ ๋๋๊ทธ๋ก ์กฐ์ ํ์ธ์.Output Files#
.claude/docs/{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/
โโโ project.md # Project ์์ธ ์ ๋ณด (--project ์ฌ์ฉ ์)
โโโ epic.md # Epic ์์ธ ์ ๋ณด
โโโ priority.md # ์ฐ์ ์์ ๊ฒํ ๊ฒฐ๊ณผ + ํ์ดํ๋ผ์ธ ๋ฐฐ์น ๊ธฐ๋ก
โโโ 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` ์ฌ์ฉ ์ ์๋ ์์ฑ๋จ
### ๋ชฉ๋ก ํ๋ฉด

### ์์ธ ํ๋ฉด

### ํผ ํ๋ฉด

> ๋๋ 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}Given {precondition_1} When {action_1} Then {expected_result_1}
- **Priority**: {High/Medium/Low}
- **Tag**: @{tag}
### AC2: {scenario_2_name}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` ์ฌ์ฉ ์ ์๋ ์์ฑ๋จ

> ๋๋ Figma ๋งํฌ: {figma_link}
## ๐ ์์ Story Point
{point_value}
---
> ๐ค Generated by `/zenhub:epic`
> ๐ Epic: #{epic_number}MCP Integration#
| Task | MCP Server | Purpose |
|---|---|---|
| Issue creation | ZenHub | Epic/Story creation |
| Template structuring | Sequential | Systematic issue organization |
Examples#
Basic Usage#
/zenhub:epic community PostProject Level Creation (new)#
# Create new Project + Epics under it
/zenhub:epic community Post --project " ์ด๋๋ฏผ ์ฝ์ v2 "
# Multiple Epics under one Project
/zenhub:epic --project " ์ด๋๋ฏผ ์ฝ์ v2 " --epics " auth,community,payment "
# Link Epic to an existing Project issue
/zenhub:epic community Post --parent-project 1024Specify Requirements File#
/zenhub:epic community Post \
--requirements .claude/docs/community/requirements.md \
--bdd-dir .claude/docs/community/bdd/Specify Sprint and Labels#
/zenhub:epic community Post \
--labels " sprint-1,mvp " \
--sprint currentCustom 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 currentAnalysis Only Without Image Attachment#
/zenhub:epic community Post \
--images " list.png,detail.png,form.png " \
--attach-images falseReference Agent#
Detailed implementation rules: ${CLAUDE_PLUGIN_ROOT}/commands/agents/zenhub-integration-agent.md
Key Rules#
- Top-Down Creation: Create in hierarchy order โ Initiative (optional) โ Project (optional) โ Epic โ Story โ Sub-task
- Link Required: All Stories must be linked to Epic; Epics link to Project when
--project/--parent-projectis used (or directly to Initiative when--initiative/--parent-initiativeis used without a Project); Projects link to Initiative when both are used - GitHub Issues Only: Always use
createGitHubIssue(createZenhubIssueprohibited โ seerules/zenhub-conventions.md) - Priority Before Creation: Run the Step 1.5 priority review first โ labels (
p0/p1/p2) cannot be changed after creation - Timeline Required (Step 2.5): Always set both
setIssueEstimate(SP) andsetDatesForIssue(start/end) on the Epic โ SP alone leaves the Epic off the Goals & Planning roadmap. Skip only with--no-dates.setDatesForIssuerequireszenhubOrganizationId - Pipeline Sorting Required: After creation, always place issues per the priority โ pipeline matrix (Step 5), moving in descending priority order
- Include Acceptance Criteria: Include BDD-based Acceptance Criteria in each Story
- Label Consistency: Follow the defined label system
- Create After Confirmation: Create actual issues only after user confirmation
- Documentation: Save creation results to .claude/docs (including
priority.md) - Image Analysis: Detailed analysis via Claude multimodal when using
--images - Image Attachment: On
--attach-images true, commit to repo after user confirmation to attach URLs to Issues - BDD Language: Auto-generated BDD scenarios are written in Korean