ZenHub ์ปจ๋ฒค์ #
ํ๋ง๋๋ก (๋น๊ฐ๋ฐ์์ฉ)#
ZenHub๋ ํ ์ผ(์ด์)์ ์นด๋์ฒ๋ผ ๊ด๋ฆฌํ๋ ์์ ๋ณด๋์ ๋๋ค. ์ด ๋ฌธ์๋ ๊ทธ ๋ณด๋์ ์นด๋๋ฅผ ๋ง๋ค ๋ ์ง์ผ์ผ ํ "์ง์ ๊ท์น"์ ๋ชจ์ ๋ ์ฝ์์ง์ด์์. ์๋ฅผ ๋ค์ด "ํฐ ๋ชฉํ๋ ๋นจ๊ฐ ์นด๋, ์์ ์ผ์ ํ๋ ์นด๋"์ฒ๋ผ, ์นด๋๋ฅผ ์ด๋ค ์ข ๋ฅ๋กยท์ด๋ค ์ด๋ฆ์ผ๋กยท์ด๋์ ๋ถ์ผ์ง๋ฅผ ํต์ผํด ๋๋ ๊ฒ์ ๋๋ค. ๋๋ถ์ ์ฌ๋๋ AI๋ ํท๊ฐ๋ฆฌ์ง ์๊ณ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ผ์ ์ ๋ฆฌํ ์ ์์ต๋๋ค.
๋ฌด์์ยท์ธ์ (๋น๊ฐ๋ฐ์์ฉ)#
- ๋ฌด์์ ํด ์ฃผ๋์: ์ ์์ ์นด๋(์ด์)๋ฅผ ๋ง๋ค ๋ ์ฌ๋ฐ๋ฅธ ์ข ๋ฅยท์ด๋ฆยท์ฐ๊ฒฐ ๋ฐฉ์์ ์๋์ผ๋ก ์งํค๊ฒ ํด ์ค๋๋ค.
- ํฐ ์ผ๊ณผ ์์ ์ผ์ ๋ถ๋ชจ-์์์ผ๋ก ๋ฌถ์ด, ์ ์ฒด ๊ทธ๋ฆผ(Epic) ์๋์ ์ธ๋ถ ์์ (Story)์ด ๋งค๋ฌ๋ฆฌ๋๋ก ์ ๋ฆฌํฉ๋๋ค.
- ์ผ์ (์์์ผยท์ข ๋ฃ์ผ)๊ณผ ์์ ๋ ์ ์(Story Point)๋ฅผ ์ ํด์ง ์๋ฆฌ์ ์ฑ์ ๋ฃ์ต๋๋ค.
- ์ธ์ ์๋ํ๋์: ZenHub์ ์ด์๋ฅผ ์๋ก ๋ง๋ค๊ฑฐ๋, ์์ ์ ๋จ๊ณ๋ณ ๋ณด๋(ํ์ดํ๋ผ์ธ)๋ก ์ฎ๊ธฐ๊ฑฐ๋, ์์ ๋ถ๋์ ์ถ์ ํ ๋ ์ด ๊ท์น์ด ๊ธฐ์ค์ด ๋ฉ๋๋ค.
- ํต์ฌ ์ฝ์ ํ๋: ์ด์๋ ํญ์ GitHub ์ด์๋ก ๋ง๋ค๊ณ , ZenHub ์ด์๋ ์ฐ์ง ์์ต๋๋ค(์ผ์ ยท์ด๋ ๊ธฐ๋ฅ์ด ์ ๋๋ก ๋์ํ๊ธฐ ๋๋ฌธ).
ํต์ฌ ์ฉ์ด (๋น๊ฐ๋ฐ์์ฉ)#
| ์ฉ์ด | ์ฌ์ด ์ค๋ช |
|---|---|
| ์ด์(Issue) | ํด์ผ ํ ์ผ ํ๋๋ฅผ ์ ์ด ๋ ์์ ์นด๋ |
| GitHub ์ด์ / ZenHub ์ด์ | ์นด๋๋ฅผ ์ด๋์ ๋ง๋ค์๋์ง์ ๋ฐ๋ฅธ ์ข ๋ฅ. ์ฌ๊ธฐ์๋ GitHub ์ด์๋ง ์ฐ๊ณ ZenHub ์ด์๋ ๊ธ์ง |
| Epic | ์ฌ๋ฌ ์์ ์ ๋ฌถ์ ํฐ ๊ธฐ๋ฅ ๋ฉ์ด๋ฆฌ(ํฐ ๋ชฉํ ์นด๋) |
| Story | Epic ์๋์ ๋งค๋ฌ๋ฆฌ๋ ์ค์ ๊ฐ๋ฐ ์์ ๋จ์ |
| Pipeline(ํ์ดํ๋ผ์ธ) | "ํ ์ผ โ ์งํ ์ค โ ๋ฆฌ๋ทฐ"์ฒ๋ผ ์์ ์ด ๊ฑฐ์ณ ๊ฐ๋ ๋ณด๋์ ๋จ๊ณ |
| Repository(์ ์ฅ์) | ์ฝ๋๊ฐ ๋ณด๊ด๋๋ ๊ณต๊ฐ. ์ด๋ค ์ ์ฅ์์ ์ด์๋ฅผ ๋ง๋ค์ง ID๋ก ์ง์ |
| parentIssueId | ์์ ์นด๋๋ฅผ ๋ถ๋ชจ ์นด๋(Epic)์ ์ฐ๊ฒฐํ ๋ ์ฐ๋ ํ์ |
| Story Point | ๊ทธ ์์ ์ด ์ผ๋ง๋ ํฐ์ง ๊ฐ๋ ํ๋ ์ ์(์์ ๋ ์ถ์ ์น) |
| ํ์๋ผ์ธ | ์์ ์ ์์์ผ๊ณผ ์ข ๋ฃ์ผ ์ผ์ |
| SWR ์บ์ฑ | ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ๋ณด์ฌ ์ฃผ๋ฉด์ ๋ค์์ ์ต์ ์ผ๋ก ๊ฐฑ์ ํ๋ ๋ฐฉ์(์์์ ๋ฑ์ฅ) |
| MCP | Claude๊ฐ ZenHub ๊ฐ์ ์ธ๋ถ ๋๊ตฌ๋ฅผ ์ง์ ์กฐ์ํ๋๋ก ์ฐ๊ฒฐํด ์ฃผ๋ ํต๋ก |
ZenHub๋ฅผ ์ฌ์ฉํ ์ด์ ๊ด๋ฆฌ ๊ท์น์ ๋๋ค.
โ ๏ธ ๋จ์ผ ์ถ์ฒ(Source of Truth): ์ด์ ์์ฑยทํ์ ๊ณ์ธตยทํ์ดํ๋ผ์ธ ๋ฐฐ์นยท์ข ๋ฃ ์ ์ฑ ์ ์์ธ ๊ท์น์
rules/zenhub-conventions.md์ ์์ต๋๋ค. ์ด SKILL์ ๊ทธ ์์ฝ์ด๋ฉฐ, ๋ ๋ฌธ์๊ฐ ๋ค๋ฅด๋ฉดrules/zenhub-conventions.md๊ฐ ์ฐ์ ํฉ๋๋ค. ๋ธ๋์น ์ ๋ต์branch-hierarchy์คํฌ์ ์ฐธ์กฐํ์ธ์.
์ด์ ์์ฑ ๊ท์น#
GitHub ์ด์ ์ฌ์ฉ (ํ์)#
๋ชจ๋ ์ด์๋ GitHub ์ด์๋ก ์์ฑํฉ๋๋ค (ZenHub ์ด์ ์ฌ์ฉ ๊ธ์ง)
// โ
CORRECT: GitHub ์ด์ ์์ฑ (ID๋ ํญ์ ๋์ ์กฐํ โ ์ํฌ์คํ์ด์ค๋ง๋ค ๋ค๋ฅด๋ฏ๋ก ํ๋์ฝ๋ฉ ๊ธ์ง)
const workspace = await mcp__zenhub__getWorkspacePipelinesAndRepositories();
const repoId = workspace.githubRepositories.find(r = > /* ๋์ GitHub ๋ ํฌ ์ ํ */).id;
const issueTypes = await mcp__zenhub__getIssueTypes({ repositoryId: repoId });
const epicTypeId = issueTypes.find(t = > t.name === " Epic " ).id;
mcp__zenhub__createGitHubIssue({
title: " ๊ธฐ๋ฅ ๊ฐ๋ฐ " , // Issue Type์ด Epic/Story๋ฅผ ์ด๋ฏธ ๊ตฌ๋ถํ๋ฏ๋ก ์ ๋์ฌ ๋ถํ์
repositoryId: repoId,
issueTypeId: epicTypeId,
})
// โ WRONG: ZenHub ์ด์ ์์ฑ (์ฌ์ฉ ๊ธ์ง)
mcp__zenhub__createZenhubIssue({...})
์ด์ :
- GitHub ์ด์๋ ํ์ดํ๋ผ์ธ ์ด๋ ๊ฐ๋ฅ
- ํ์๋ผ์ธ ์ค์ ์ ์ ์๋
- GitHub PR๊ณผ ์๋ ์ฐ๊ฒฐ
- ๊ฒ์ ๋ฐ ํํฐ๋ง ์ฉ์ด
Issue Type ๊ณ์ธต (5-๋ ๋ฒจ)#
Repository ID, Issue Type ID, Pipeline ID, Organization ID๋ ์ํฌ์คํ์ด์ค๋ง๋ค ๋ค๋ฅด๋ฏ๋ก ํญ์ getWorkspacePipelinesAndRepositories() / getIssueTypes()๋ก ๋์ ์กฐํํฉ๋๋ค(ํ๋์ฝ๋ฉ ๊ธ์ง). ๋ ๋ฒจ(์ซ์๊ฐ ์์์๋ก ์์) ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
| Level | Type | ์ฉ๋ | ๋ถ๋ชจ |
|---|---|---|---|
| 1 | Initiative | ๋๊ท๋ชจ ์ ๋ต์ ๋ชฉํ (๋ถ๊ธฐ ๋จ์) | โ |
| 2 | Project | ํ๋ก์ ํธ/๋ง์ผ์คํค ๋จ์ | Initiative (์ ํ) |
| 3 | Epic | ๋๊ท๋ชจ ๊ธฐ๋ฅ ๋ฌถ์ | Project (์ ํ) |
| 4 | Feature / Bug / Task | Story(ํ๋ฉดยท์์ ๋จ์) | Epic |
| 5 | Sub-task | ์ธ๋ถ ์์ | Feature/Bug/Task |
Initiative/Project ํ์ ์ ์ํฌ์คํ์ด์ค์ ์์ ์ ์์ผ๋ฏ๋ก
getIssueTypes()๋ก ๋จผ์ ํ์ธํ๊ณ , ์์ผ๋ฉด ๊ทธ ์๋ ๋ ๋ฒจ์ ์ต์์๋ก ๋์ฒดํ๋ฉฐ ์ฌ์ฉ์์๊ฒ ์๋ดํฉ๋๋ค.
์ด์ ์์ฑ ์์ (๋์ ์กฐํ ๊ธฐ๋ฐ)#
Epic ์์ฑ#
const workspace = await mcp__zenhub__getWorkspacePipelinesAndRepositories();
const repoId = workspace.githubRepositories.find(r = > /* ๋์ ๋ ํฌ ์ ํ */).id;
const issueTypes = await mcp__zenhub__getIssueTypes({ repositoryId: repoId });
const epicTypeId = issueTypes.find(t = > t.name === " Epic " ).id;
// 1. Epic ์์ฑ (GitHub ์ด์)
const epic = await mcp__zenhub__createGitHubIssue({
title: " ๊ธฐ๋ฅ๋ช
" ,
body: " Epic ์ค๋ช
... " ,
repositoryId: repoId,
issueTypeId: epicTypeId,
});
// 2. ํ์๋ผ์ธ ์ค์
await mcp__zenhub__setDatesForIssue({
issueId: epic.id,
startDate: " 2026-01-27 " ,
endDate: " 2026-02-17 " ,
zenhubOrganizationId: workspace.zenhubOrganization.id,
});
// 3. ํ์ดํ๋ผ์ธ ์ด๋
const backlog = workspace.pipelines.find(p = > p.name === " Product Backlog " );
await mcp__zenhub__moveIssueToPipeline({
issueId: epic.id,
pipelineId: backlog.id,
});
Story ์์ฑ#
const featureTypeId = issueTypes.find(t = > t.name === " Feature " ).id;
// Story ์์ฑ (Epic ํ์)
const story = await mcp__zenhub__createGitHubIssue({
title: " ๊ธฐ๋ฅ๋ช
" , // Issue Type(Feature)์ด Story์์ ์ด๋ฏธ ๊ตฌ๋ถํ๋ฏ๋ก ์ ๋์ฌ ๋ถํ์
body: " Story ์ค๋ช
... " ,
repositoryId: repoId,
issueTypeId: featureTypeId,
parentIssueId: epic.id, // Epic๊ณผ ์ฐ๊ฒฐ
});
// Story Point ์ค์
await mcp__zenhub__setIssueEstimate({
issueId: story.id,
estimate: 5,
});
Project/Initiative ์์ฑ, ์ฐ์ ์์ ๋ผ๋ฒจ(p0/p1/p2), ํ์ดํ๋ผ์ธ ๋ฐฐ์น ๋งคํธ๋ฆญ์ค ๋ฑ ์์ธ ๊ท์น์
rules/zenhub-conventions.md๋ฅผ ์ฐธ์กฐํ์ธ์.
์ด์ ์ ๋ชฉ ์ปจ๋ฒค์ #
Issue Type(Epic/Story/Sub-task ๋ฑ)์ด ์ด๋ฏธ ์ข
๋ฅ๋ฅผ ๊ตฌ๋ถํ๋ฏ๋ก, ์ ๋ชฉ์ [Epic]/[Story] ๊ฐ์ ์ ๋์ฌ๋ฅผ ๋ถ์ด์ง ์์ต๋๋ค.
| Type | ์ ๋์ฌ | ์์ |
|---|---|---|
| Epic / Project / Story | (์์ โ Issue Type์ผ๋ก ๊ตฌ๋ถ) | API ์ฐ๋ ๋ฐ SWR ์บ์ฑ ์ ๋ต ์ ์ฉ |
| Bug | fix: | fix: ๋ก๊ทธ์ธ ํ ํฐ ๊ฐฑ์ ์ค๋ฅ |
| Feature | feat: | feat: ์ฌ์ฉ์ ํ๋กํ ํ์ด์ง ์ถ๊ฐ |
| Task | chore: | chore: ์์กด์ฑ ์
๋ฐ์ดํธ |
์ฐธ๊ณ ์ฌํญ#
-
ZenHub ์ด์ vs GitHub ์ด์
- ZenHub ์ด์: ํ์ดํ๋ผ์ธ ์ด๋/ํ์๋ผ์ธ ์ค์ ๋ถ๊ฐ
- GitHub ์ด์: ๋ชจ๋ ZenHub ๊ธฐ๋ฅ ์ ์ ์๋
-
๋ถ๋ชจ-์์ ๊ด๊ณ
parentIssueIdํ๋ผ๋ฏธํฐ๋ก ์์ฑ ์ ์ฐ๊ฒฐ- ๋๋
setParentForIssues๋ก ๋์ค์ ์ฐ๊ฒฐ - ์์์ Issue Type level์ ๋ถ๋ชจ๋ณด๋ค ์ปค์ผ ํฉ๋๋ค (์: Epic(3) ์๋ Story(4) โ )
-
๊ฒ์
searchLatestIssues: GitHub ์ด์๋ง ๊ฒ์๋จ- ZenHub ์ด์๋ ๊ฒ์ ๋ถ๊ฐ
-
๋ธ๋์น ๊ณ์ธต ์ ์ฑ (#5424)
- ์ด์ type์ด Epic์ด๊ฑฐ๋ parent๊ฐ ์๋ ๊ฒฝ์ฐ ๊ณ์ธต ๋ธ๋์น๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค
- ๊ตฌ์ฒด์ ์ธ prefixยทbase ๋ธ๋์น ๊ท์น์
branch-hierarchy์คํฌ์ ๋จ์ผ ์ถ์ฒ๋ก ์ฐธ์กฐํ์ธ์(์ด ๋ฌธ์์์ ์ค๋ณต ์์ ํ์ง ์์ต๋๋ค โ drift ๋ฐฉ์ง)