LogoSkills

zenhub-conventions

ZenHub issue/PR management conventions. Use when creating or managing ZenHub issues/PRs and pipelines.

ZenHub ์ปจ๋ฒค์…˜#

ํ•œ๋งˆ๋””๋กœ (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

ZenHub๋Š” ํ•  ์ผ(์ด์Šˆ)์„ ์นด๋“œ์ฒ˜๋Ÿผ ๊ด€๋ฆฌํ•˜๋Š” ์ž‘์—… ๋ณด๋“œ์ž…๋‹ˆ๋‹ค. ์ด ๋ฌธ์„œ๋Š” ๊ทธ ๋ณด๋“œ์— ์นด๋“œ๋ฅผ ๋งŒ๋“ค ๋•Œ ์ง€์ผœ์•ผ ํ•  "์ง‘์•ˆ ๊ทœ์น™"์„ ๋ชจ์•„ ๋‘” ์•ฝ์†์ง‘์ด์—์š”. ์˜ˆ๋ฅผ ๋“ค์–ด "ํฐ ๋ชฉํ‘œ๋Š” ๋นจ๊ฐ„ ์นด๋“œ, ์ž‘์€ ์ผ์€ ํŒŒ๋ž€ ์นด๋“œ"์ฒ˜๋Ÿผ, ์นด๋“œ๋ฅผ ์–ด๋–ค ์ข…๋ฅ˜๋กœยท์–ด๋–ค ์ด๋ฆ„์œผ๋กœยท์–ด๋””์— ๋ถ™์ผ์ง€๋ฅผ ํ†ต์ผํ•ด ๋‘๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋•๋ถ„์— ์‚ฌ๋žŒ๋„ AI๋„ ํ—ท๊ฐˆ๋ฆฌ์ง€ ์•Š๊ณ  ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ผ์„ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฌด์—‡์„ยท์–ธ์ œ (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

  • ๋ฌด์—‡์„ ํ•ด ์ฃผ๋‚˜์š”: ์ƒˆ ์ž‘์—… ์นด๋“œ(์ด์Šˆ)๋ฅผ ๋งŒ๋“ค ๋•Œ ์˜ฌ๋ฐ”๋ฅธ ์ข…๋ฅ˜ยท์ด๋ฆ„ยท์—ฐ๊ฒฐ ๋ฐฉ์‹์„ ์ž๋™์œผ๋กœ ์ง€ํ‚ค๊ฒŒ ํ•ด ์ค๋‹ˆ๋‹ค.
  • ํฐ ์ผ๊ณผ ์ž‘์€ ์ผ์„ ๋ถ€๋ชจ-์ž์‹์œผ๋กœ ๋ฌถ์–ด, ์ „์ฒด ๊ทธ๋ฆผ(Epic) ์•„๋ž˜์— ์„ธ๋ถ€ ์ž‘์—…(Story)์ด ๋งค๋‹ฌ๋ฆฌ๋„๋ก ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ์ผ์ •(์‹œ์ž‘์ผยท์ข…๋ฃŒ์ผ)๊ณผ ์ž‘์—…๋Ÿ‰ ์ ์ˆ˜(Story Point)๋ฅผ ์ •ํ•ด์ง„ ์ž๋ฆฌ์— ์ฑ„์›Œ ๋„ฃ์Šต๋‹ˆ๋‹ค.
  • ์–ธ์ œ ์ž‘๋™ํ•˜๋‚˜์š”: ZenHub์— ์ด์Šˆ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜, ์ž‘์—…์„ ๋‹จ๊ณ„๋ณ„ ๋ณด๋“œ(ํŒŒ์ดํ”„๋ผ์ธ)๋กœ ์˜ฎ๊ธฐ๊ฑฐ๋‚˜, ์ž‘์—… ๋ถ„๋Ÿ‰์„ ์ถ”์ •ํ•  ๋•Œ ์ด ๊ทœ์น™์ด ๊ธฐ์ค€์ด ๋ฉ๋‹ˆ๋‹ค.
  • ํ•ต์‹ฌ ์•ฝ์† ํ•˜๋‚˜: ์ด์Šˆ๋Š” ํ•ญ์ƒ GitHub ์ด์Šˆ๋กœ ๋งŒ๋“ค๊ณ , ZenHub ์ด์Šˆ๋Š” ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์ผ์ •ยท์ด๋™ ๊ธฐ๋Šฅ์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ).

ํ•ต์‹ฌ ์šฉ์–ด (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

์šฉ์–ด์‰ฌ์šด ์„ค๋ช…
์ด์Šˆ(Issue)ํ•ด์•ผ ํ•  ์ผ ํ•˜๋‚˜๋ฅผ ์ ์–ด ๋‘” ์ž‘์—… ์นด๋“œ
GitHub ์ด์Šˆ / ZenHub ์ด์Šˆ์นด๋“œ๋ฅผ ์–ด๋””์„œ ๋งŒ๋“ค์—ˆ๋Š”์ง€์— ๋”ฐ๋ฅธ ์ข…๋ฅ˜. ์—ฌ๊ธฐ์„œ๋Š” GitHub ์ด์Šˆ๋งŒ ์“ฐ๊ณ  ZenHub ์ด์Šˆ๋Š” ๊ธˆ์ง€
Epic์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋ฌถ์€ ํฐ ๊ธฐ๋Šฅ ๋ฉ์–ด๋ฆฌ(ํฐ ๋ชฉํ‘œ ์นด๋“œ)
StoryEpic ์•„๋ž˜์— ๋งค๋‹ฌ๋ฆฌ๋Š” ์‹ค์ œ ๊ฐœ๋ฐœ ์ž‘์—… ๋‹จ์œ„
Pipeline(ํŒŒ์ดํ”„๋ผ์ธ)"ํ•  ์ผ โ†’ ์ง„ํ–‰ ์ค‘ โ†’ ๋ฆฌ๋ทฐ"์ฒ˜๋Ÿผ ์ž‘์—…์ด ๊ฑฐ์ณ ๊ฐ€๋Š” ๋ณด๋“œ์˜ ๋‹จ๊ณ„
Repository(์ €์žฅ์†Œ)์ฝ”๋“œ๊ฐ€ ๋ณด๊ด€๋˜๋Š” ๊ณต๊ฐ„. ์–ด๋–ค ์ €์žฅ์†Œ์— ์ด์Šˆ๋ฅผ ๋งŒ๋“ค์ง€ ID๋กœ ์ง€์ •
parentIssueId์ž์‹ ์นด๋“œ๋ฅผ ๋ถ€๋ชจ ์นด๋“œ(Epic)์— ์—ฐ๊ฒฐํ•  ๋•Œ ์“ฐ๋Š” ํ‘œ์‹
Story Point๊ทธ ์ž‘์—…์ด ์–ผ๋งˆ๋‚˜ ํฐ์ง€ ๊ฐ€๋Š ํ•˜๋Š” ์ ์ˆ˜(์ž‘์—…๋Ÿ‰ ์ถ”์ •์น˜)
ํƒ€์ž„๋ผ์ธ์ž‘์—…์˜ ์‹œ์ž‘์ผ๊ณผ ์ข…๋ฃŒ์ผ ์ผ์ •
SWR ์บ์‹ฑ๋ฐ์ดํ„ฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ณด์—ฌ ์ฃผ๋ฉด์„œ ๋’ค์—์„œ ์ตœ์‹ ์œผ๋กœ ๊ฐฑ์‹ ํ•˜๋Š” ๋ฐฉ์‹(์˜ˆ์‹œ์— ๋“ฑ์žฅ)
MCPClaude๊ฐ€ 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()๋กœ ๋™์  ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค(ํ•˜๋“œ์ฝ”๋”ฉ ๊ธˆ์ง€). ๋ ˆ๋ฒจ(์ˆซ์ž๊ฐ€ ์ž‘์„์ˆ˜๋ก ์ƒ์œ„) ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

LevelType์šฉ๋„๋ถ€๋ชจ
1Initiative๋Œ€๊ทœ๋ชจ ์ „๋žต์  ๋ชฉํ‘œ (๋ถ„๊ธฐ ๋‹จ์œ„)โ€”
2Projectํ”„๋กœ์ ํŠธ/๋งˆ์ผ์Šคํ†ค ๋‹จ์œ„Initiative (์„ ํƒ)
3Epic๋Œ€๊ทœ๋ชจ ๊ธฐ๋Šฅ ๋ฌถ์ŒProject (์„ ํƒ)
4Feature / Bug / TaskStory(ํ™”๋ฉดยท์ž‘์—… ๋‹จ์œ„)Epic
5Sub-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 ์บ์‹ฑ ์ „๋žต ์ ์šฉ
Bugfix:fix: ๋กœ๊ทธ์ธ ํ† ํฐ ๊ฐฑ์‹  ์˜ค๋ฅ˜
Featurefeat:feat: ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ํŽ˜์ด์ง€ ์ถ”๊ฐ€
Taskchore:chore: ์˜์กด์„ฑ ์—…๋ฐ์ดํŠธ

์ฐธ๊ณ  ์‚ฌํ•ญ#

  1. ZenHub ์ด์Šˆ vs GitHub ์ด์Šˆ

    • ZenHub ์ด์Šˆ: ํŒŒ์ดํ”„๋ผ์ธ ์ด๋™/ํƒ€์ž„๋ผ์ธ ์„ค์ • ๋ถˆ๊ฐ€
    • GitHub ์ด์Šˆ: ๋ชจ๋“  ZenHub ๊ธฐ๋Šฅ ์ •์ƒ ์ž‘๋™
  2. ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„

    • parentIssueId ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ƒ์„ฑ ์‹œ ์—ฐ๊ฒฐ
    • ๋˜๋Š” setParentForIssues๋กœ ๋‚˜์ค‘์— ์—ฐ๊ฒฐ
    • ์ž์‹์˜ Issue Type level์€ ๋ถ€๋ชจ๋ณด๋‹ค ์ปค์•ผ ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: Epic(3) ์•„๋ž˜ Story(4) โœ…)
  3. ๊ฒ€์ƒ‰

    • searchLatestIssues: GitHub ์ด์Šˆ๋งŒ ๊ฒ€์ƒ‰๋จ
    • ZenHub ์ด์Šˆ๋Š” ๊ฒ€์ƒ‰ ๋ถˆ๊ฐ€
  4. ๋ธŒ๋žœ์น˜ ๊ณ„์ธต ์ •์ฑ… (#5424)

    • ์ด์Šˆ type์ด Epic์ด๊ฑฐ๋‚˜ parent๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๊ณ„์ธต ๋ธŒ๋žœ์น˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
    • ๊ตฌ์ฒด์ ์ธ prefixยทbase ๋ธŒ๋žœ์น˜ ๊ทœ์น™์€ branch-hierarchy ์Šคํ‚ฌ์„ ๋‹จ์ผ ์ถœ์ฒ˜๋กœ ์ฐธ์กฐํ•˜์„ธ์š”(์ด ๋ฌธ์„œ์—์„œ ์ค‘๋ณต ์„œ์ˆ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค โ€” drift ๋ฐฉ์ง€)