| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /dev:run |
| Aliases | /dr |
| Category | petmedi-development |
| Complexity | high |
| MCP Servers | zenhub |
/dev:run#
Full development cycle automation from work content to issue creation through merge approval
Triggers#
- When starting new feature/bug/refactoring work
- When full cycle execution is needed from work content alone
- When batch processing issue creation โ implementation โ PR โ review โ merge
Usage#
Basic Usage#
# Start full cycle with work content
/dev:run " ์ ์ ๋ชฉ๋ก ํ๋ฉด ์ถ๊ฐ "
Start with Existing Issue (from Step 4)#
/dev:run 1810
# ๋๋
/dev:issue 1810
Parameters#
| Parameter | Required | Description | Example |
|---|---|---|---|
work_content |
โ * | Description of the work | "์ ์ ๋ชฉ๋ก ํ๋ฉด ์ถ๊ฐ" |
issue_number |
โ * | Existing issue number | 1810 |
*Either work content or issue number is required
Options#
Auto-Decision Options (defaults)#
When options are not specified, they are determined automatically:
| Option | Default | Auto-Decision Rule |
|---|---|---|
--type |
Auto-inferred | Keyword analysis: "add/create"โfeat, "fix/repair"โfix, "improve"โrefactor |
--scope |
Auto-inferred | Extract Entity/Feature name from work content, ask if unclear |
--point |
Auto-estimated | Complexity analysis: single file(1), multiple files(3), multiple layers(5), with BDD(8) |
--bdd |
On screen detection | List/detail/form keyword detection โ auto BDD scenario writing |
--base |
Auto-detected | Auto-detect parent issue branch (development if none) |
Explicit Options#
# Specify type
/dev:run --type feat " Add author list screen "
# Specify scope
/dev:run --scope console-author " Add author list screen "
# Specify point
/dev:run --point 5 " Add author list screen "
# Sprint assignment
/dev:run --sprint current " Add author list screen "
# PR only (no merge wait)
/dev:run --no-merge " Add author list screen "
# Explicit base branch (manual parent branch setting)
/dev:run --base epic/1810-author-feature 1812
# Skip review (urgent hotfix)
/dev:run --skip-review " Urgent fix "
# Force BDD scenarios (even for non-screen features)
/dev:run --bdd " API client refactoring "
# Skip BDD scenarios and BDD Coverage Gate (even for screen features)
/dev:run --skip-bdd " Simple list modification "
# Skip tests (urgent)
/dev:run --skip-tests " Urgent fix "
Execution Flow (13 Steps)#
Step-by-Step Prerequisites and Verification#
| Step | Phase | Prerequisites | Verification |
|---|---|---|---|
| 1 | Work content analysis | None | - |
| 2 | ZenHub issue creation | Step 1 complete | issue.id exists |
| 3 | Move to Product Backlog | Step 2 complete | pipeline verified |
| 4 | Branch creation | Steps 2,3 complete | branch exists |
| 5 | Move to In Progress | Step 4 complete | pipeline verified |
| 6 | BDD scenario writing | Step 5 complete, screen feature | .feature file exists |
| 7 | Implementation work | Step 5 complete | commits exist |
| 7.5 | Backend code generation | Step 7 complete, when Backend changes exist | generation success |
| 8 | Test writing/execution | Step 7 complete | tests passed |
| 8.3 | BDD Coverage Gate | Step 8 complete, .feature exists | step_coverage 100%, assertions exist |
| 8.5 | Pre-push verification + DCM format/lint 0-issue gate + DCM quality improvement | Step 8.3 complete | dcm format, dart analyze 0 issues, dcm analyze error 0 issues |
| 8.7 | Code Review Gate | Step 8.5 complete | 0 Critical issues |
| 9 | PR creation | Step 8.7 complete | PR URL exists |
| 10 | Move to Review/QA | Step 9 complete | pipeline verified |
| 11 | Apply additional review feedback | Step 10 complete | review complete |
| 12 | Wait for merge approval | Step 11 complete | user approval |
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ /dev:run " ์์
๋ด์ฉ " โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Step 1: ์์
๋ด์ฉ ๋ถ์ โ
โ โโโ ์์
์ ํ ์ถ๋ก (feat/fix/chore/refactor) โ
โ โโโ ์ค์ฝํ ์ถ์ถ (feature๋ช
) โ
โ โโโ ํ๋ฉด ํ์
๊ฐ์ง (๋ชฉ๋ก/์์ธ/ํผ) โ
โ โโโ ์์ ๋ณต์ก๋ ์ฐ์ (Story Point) โ
โ โ
โ Step 2: ZenHub ์ด์ ์์ฑ โ
โ โโโ mcp__zenhub__createGitHubIssue โ
โ โโโ ์ด์ ํ์
์๋ ์ค์ (Feature/Task/Bug) โ
โ โโโ ๋ผ๋ฒจ ์๋ ์ง์ โ
โ โโโ Estimate ์ค์ โ
โ โ
โ Step 3: Product Backlog ์ด๋ โ
โ โโโ mcp__zenhub__moveIssueToPipeline โ
โ โโโ Pipeline: Product Backlog โ
โ โ
โ Step 4: ๋ธ๋์น ์์ฑ โ ๏ธ ํ์ (๊ณ์ธต ๋ธ๋์น ์ ๋ต) โ
โ โโโ ๋ถ๋ชจ ์ด์ ๊ฐ์ง (ZenHub parentIssueId ์กฐํ) โ
โ โโโ base ๋ธ๋์น ๊ฒฐ์ : โ
โ โ โโโ Sub-task โ Story ๋ธ๋์น ๊ธฐ๋ฐ โ
โ โ โโโ Story โ Epic ๋ธ๋์น ๊ธฐ๋ฐ โ
โ โ โโโ Epic โ development ๊ธฐ๋ฐ โ
โ โ โโโ ๋
๋ฆฝ ์ด์ โ development ๊ธฐ๋ฐ โ
โ โโโ ๋ถ๋ชจ ๋ธ๋์น ๋ฏธ์กด์ฌ ์ ์๋ ์์ฑ โ
โ โโโ issue-branch-agent ํธ์ถ (base_branch ์ ๋ฌ) โ
โ โโโ {type}/{number}-{slug} ํ์ โ
โ โโโ โ ๏ธ development/main์ ์ง์ ์ปค๋ฐ ๊ธ์ง โ
โ โ
โ Step 5: In Progress ์ด๋ โ
โ โโโ issue-state-agent ํธ์ถ โ
โ โโโ Pipeline: In Progress โ
โ โ
โ Step 6: BDD ์๋๋ฆฌ์ค ์์ฑ (screen feature ์) โ
โ โโโ bdd-scenario-agent ํธ์ถ โ
โ โโโ Gherkin Feature ํ์ผ ์์ฑ โ
โ โโโ Step Definition ์์ฑ โ
โ โโโ ์ปค๋ฐ: " test({scope}): โ
BDD ์๋๋ฆฌ์ค ์์ฑ " โ
โ โ
โ Step 7: ๊ตฌํ ์์
โ
โ โโโ ์ด์ ๋ด์ฉ ๊ธฐ๋ฐ ๊ตฌํ โ
โ โโโ ์ฆ๋ถ ์ปค๋ฐ ์์ฑ โ
โ โ
โ Step 7.5: Backend ์ฝ๋ ์์ฑ (on Backend changes) โ ๏ธ โ
โ โโโ melos run backend:pod:generate โ
โ โโโ ์ปค๋ฐ: " chore(backend): ๐ง ์ฝ๋ ์์ฑ " โ
โ โ
โ Step 8: ํ
์คํธ ์์ฑ ๋ฐ ๊ฒ์ฆ (ํ์ ๊ฒ์ดํธ) โ ๏ธ โ
โ โโโ [ํ๋ก ํธ์๋ ํ
์คํธ - ํ์] โ
โ โ โโโ UseCase ๋จ์ ํ
์คํธ (unit-test-agent) โ
โ โ โโโ BLoC ๋จ์ ํ
์คํธ (bloc-test-agent) โ
โ โโโ [๋ฐฑ์๋ ํ
์คํธ - on Backend changes ํ์] โ
โ โ โโโ ์๋ํฌ์ธํธ ๋จ์ ํ
์คํธ (serverpod-test-agent) โ
โ โ โโโ ์๋น์ค ๋ก์ง ๋จ์ ํ
์คํธ (serverpod-test-agent) โ
โ โ โโโ ์๋ํฌ์ธํธ ํตํฉ ํ
์คํธ (serverpod-test-agent) โ
โ โโโ BDD Widget ํ
์คํธ (screen feature ์) โ
โ โโโ test-runner-agent ํธ์ถ (require_tests: true) โ
โ โโโ โ ๏ธ ๋ชจ๋ tests passed ํ์ (๊ฒ์ดํธ ์คํจ ์ PR ์์ฑ ์ฐจ๋จ) โ
โ โโโ ์ปค๋ฐ: " test({scope}): โ
ํ
์คํธ ์์ฑ " โ
โ โ
โ Step 8.3: BDD Coverage Gate โ ๏ธ (--skip-bdd ์ ์คํต) โ
โ โโโ .feature ํ์ผ ์ค์บ (ํ์ฌ feature ๋ฒ์) โ
โ โโโ Given/When/Then ์คํ
ํ์ฑ โ
โ โโโ step/ ํด๋์์ ๋งค์นญ step ํจ์ ๊ฒ์ฆ โ
โ โโโ ๊ฐ step ํจ์ ๋ด assertion (expect/verify) ์กด์ฌ ํ์ธ โ
โ โโโ โ ๏ธ step_coverage < 100% ์ PR ์์ฑ ์ฐจ๋จ โ
โ โโโ --skip-bdd ์ ๊ฒฝ๊ณ ๋ฉ์์ง์ ํจ๊ป ์คํต โ
โ โ
โ Step 8.5: Pre-push ๊ฒ์ฆ + DCM format/lint 0๊ฑด + DCM ํ์ง ๊ฐ์ โ ๏ธ ํ์โ
โ โโโ [Phase 1: DCM ํฌ๋งทํ
+ Dart ๋ฆฐํธ ์๋ ์์ + 0๊ฑด ๊ฒ์ดํธ] โ
โ โ โโโ melos run format (dcm format โ dart format ๋์ฒด) โ
โ โ โโโ dart fix --apply (๋ฆฐํธ ์๋ ์์ ) โ
โ โ โโโ melos run analyze:dart (Dart SDK ๋ถ์) โ
โ โ โโโ error/warning/info ์์กด ์ ์๋ ์์ (์ต๋ 2ํ ๋ฐ๋ณต) โ
โ โ โโโ โ ๏ธ 2ํ ์๋ ํ์๋ ์ด์ ์์กด ์ PR ์์ฑ ์ฐจ๋จ โ
โ โโโ [Phase 2: DCM ์ฝ๋ ํ์ง ๊ฐ์ ] โ
โ โ โโโ melos run analyze:dcm (DCM ๋ฆฐํธ + ๋ฉํธ๋ฆญ) โ
โ โ โโโ melos run fix:dcm (์๋ ์์ ๊ฐ๋ฅ ํญ๋ชฉ ์์ ) โ
โ โ โโโ melos run check:unused-code (๋ฏธ์ฌ์ฉ ์ฝ๋ ๊ฒ์ฌ) โ
โ โ โโโ melos run analyze:dcm (์์ ํ ์ฌ๊ฒ์ฆ) โ
โ โ โโโ โ ๏ธ error ์ฌ๊ฐ๋ ์ด์ ์์กด ์ PR ์์ฑ ์ฐจ๋จ โ
โ โโโ [Phase 3: ์ต์ข
๋ฆฐํธ ๊ฒ์ฆ] โ
โ โ โโโ melos run analyze:dart (DCM ์์ ํ Dart ์ฌํ์ธ) โ
โ โ โโโ melos run analyze:dcm (DCM ์ต์ข
์ฌํ์ธ) โ
โ โ โโโ โ ๏ธ ์ด์ 0๊ฑด ํ์ โ ์์กด ์ PR ์์ฑ ์ฐจ๋จ โ
โ โโโ ๊ฒ์ฆ ์คํจ ์ Step 9 ์งํ ๋ถ๊ฐ โ
โ โ
โ Step 8.7: Code Review Gate โ ๏ธ (gstack ํจํด) โ
โ โโโ /code-review --quick ์๋ ์คํ โ
โ โโโ 2-pass ๋ถ์: โ
โ โ โโโ Pass 1: Critical (์ฐจ๋จ) - ๋ณด์, ๋ฐ์ดํฐ ์์ ์ฑ, ๋ ์ด์ค์ปจ๋์
โ
โ โ โโโ Pass 2: Informational (PR body ํฌํจ) โ
โ โโโ Critical ๋ฐ๊ฒฌ ์: โ
โ โ โโโ ์๋ ์์ ์๋ (์ต๋ 2ํ) โ
โ โ โโโ ์์ ๋ถ๊ฐ ์ PR ์์ฑ ์ฐจ๋จ โ
โ โโโ Informational ํญ๋ชฉ โ PR description์ ์๋ ํฌํจ โ
โ โโโ --skip-review ์ ๊ฒฝ๊ณ ๋ฉ์์ง์ ํจ๊ป ์คํต โ
โ โ
โ Step 9: PR ์์ฑ (๊ฒ์ฆ ๊ฒ์ดํธ ํต๊ณผ ํ, ๊ณ์ธต ๋จธ์ง) โ
โ โโโ โ ๏ธ ๋ธ๋์น ํ์ ๊ฒ์ฆ ํ์ โ
โ โโโ PR base ๊ฒฐ์ (๊ณ์ธต ๋ธ๋์น ์ ๋ต): โ
โ โ โโโ Sub-task โ Story ๋ธ๋์น (base) โ
โ โ โโโ Story โ Epic ๋ธ๋์น (base) โ
โ โ โโโ Epic โ development (base) โ
โ โ โโโ ๋
๋ฆฝ ์ด์ โ development (base) โ
โ โโโ gh pr create --base {target_branch} โ
โ โโโ Closes #{issue_number} ์๋ ํฌํจ โ
โ โโโ ZenHub PR ์ฐ๊ฒฐ โ
โ โ
โ Step 10: Review/QA ์ด๋ โ
โ โโโ mcp__zenhub__moveIssueToPipeline โ
โ โโโ Pipeline: Review/QA โ
โ โ
โ Step 11: ์ถ๊ฐ ๋ฆฌ๋ทฐ ํผ๋๋ฐฑ ๋ฐ์ โ
โ โโโ Step 8.7์์ ์๋ ์ฝ๋ review complete๋จ โ
โ โโโ PR ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ ํ์ธ ๋ฐ ์ถ๊ฐ ํผ๋๋ฐฑ ๋ฐ์ โ
โ โโโ ์ฌ๊ฒํ ํ์ ์ ๋ฐ๋ณต โ
โ โโโ /checklist:feature-complete ์คํ โ
โ โ
โ Step 12: ๋จธ์ง ์น์ธ ๋๊ธฐ โ
โ โโโ user approval ์์ฒญ โ
โ โโโ ์น์ธ ์ ์ค์ฟผ์ ๋จธ์ง โ
โ โโโ GitHub " Closes # " ํค์๋๋ก ์ด์ ์๋ Close โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Verification Gates (Required)#
Step 4 Branch Verification (Required)#
Direct commits on development/main branch prohibited:
# ํ์ฌ ๋ธ๋์น ํ์ธ
BRANCH=$(git rev-parse --abbrev-ref HEAD)
# development/main ์ฒดํฌ
if [[ $BRANCH == " development " || $BRANCH == " main " ]]; then
echo " โ development/main์ ์ง์ ์ปค๋ฐํ ์ ์์ต๋๋ค "
echo " /dev:run ์คํฌ์ ๋ฐ๋์ feature ๋ธ๋์น์์ ์์
ํฉ๋๋ค "
echo " "
echo " ์ฌ๋ฐ๋ฅธ ์ฌ์ฉ๋ฒ: "
echo " 1. /dev:run \ " ์์
๋ด์ฉ\ " - ์ด์ ์์ฑ๋ถํฐ ์์ "
echo " 2. /dev:issue {number} - ๊ธฐ์กด ์ด์๋ก ์์ "
exit 1
fi
Step 9 Pre-PR Verification (Required)#
All of the following conditions must be met to create a PR:
-
Branch format verification
BRANCH=$(git rev-parse --abbrev-ref HEAD) if [[ ! $BRANCH =~ ^(epic|feature|fix|refactor|chore)/[0-9]+ ]]; then echo " โ ๋ธ๋์น ํ์ ์ค๋ฅ: $BRANCH " echo " ์ฌ๋ฐ๋ฅธ ํ์: feature/30-description ๋๋ epic/10-description " exit 1 fi -
Issue link verification
- Issue number extractable from branch name
- ZenHub issue in In Progress state
-
PR base verification (hierarchical branches)
# base ๋ธ๋์น๊ฐ ์ฌ๋ฐ๋ฅธ ๊ณ์ธต์ธ์ง ํ์ธ # Sub-task โ Story ๋ธ๋์น, Story โ Epic ๋ธ๋์น, Epic/๋ ๋ฆฝ โ development BASE_BRANCH= " ${baseBranch} " git rev-parse --verify " origin/${BASE_BRANCH} " 2 > /dev/null || { echo " โ base ๋ธ๋์น๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค: $BASE_BRANCH " exit 1 } -
Commit verification
# base ๋ธ๋์น ๋๋น ์ปค๋ฐ ๊ฒ์ฆ (๊ณ์ธต ๋ธ๋์น ์ง์) COMMITS=$(git rev-list --count origin/${BASE_BRANCH}..HEAD) if [[ $COMMITS -eq 0 ]]; then echo " โ ์ปค๋ฐ์ด ์์ต๋๋ค. Step 7์ ๋จผ์ ์๋ฃํด์ฃผ์ธ์ " exit 1 fi -
Pre-push verification + DCM format/lint 0-issue gate + DCM code quality improvement (Step 8.5)
# Phase 1: DCM ํฌ๋งทํ + Dart ๋ฆฐํธ ์๋ ์์ + 0๊ฑด ๊ฒ์ดํธ melos run format # dcm format (dart format ๋์ฒด) dart fix --apply # ์๋ ์์ ๊ฐ๋ฅํ ๋ฆฐํธ ์ด์ ์ผ๊ด ์์ melos run analyze:dart # Dart SDK ๋ถ์ ์คํ # โ error/warning/info ๋ชจ๋ 0๊ฑด ํ์ (๋จ์์์ผ๋ฉด ์๋ ์์ ํ ์ฌ๊ฒ์ฆ, ์ต๋ 2ํ) # โ 2ํ ์๋ ํ์๋ ์ด์ ์์กด ์ PR ์์ฑ ์ฐจ๋จ # Phase 2: DCM ์ฝ๋ ํ์ง ๊ฐ์ melos run analyze:dcm # DCM ๋ฆฐํธ + ๋ฉํธ๋ฆญ ๊ฒ์ฌ melos run fix:dcm # ์๋ ์์ (๋ฆฐํธ) melos run check:unused-code # ๋ฏธ์ฌ์ฉ ์ฝ๋ ๊ฒ์ฌ melos run analyze:dcm # ์์ ํ ์ฌ๊ฒ์ฆ # โ error ์ฌ๊ฐ๋ ์ด์ 0๊ฑด ํ์ # Phase 3: ์ต์ข ๋ฆฐํธ ๊ฒ์ฆ (DCM ์์ ์ด ์ ๋ฆฐํธ ์ด์๋ฅผ ๋ง๋ค์ง ์์๋์ง) melos run analyze:dart # Dart SDK ์ต์ข ํ์ธ melos run analyze:dcm # DCM ์ต์ข ํ์ธ
Step Skip Prevention#
Principle: Each step can only proceed after the previous step is complete
// ๋จ๊ณ ์ ์ ์กฐ๊ฑด ๊ฒ์ฆ
async function validateStepPrerequisites(stepNumber: number) {
const todos = await getTodoList();
// ์ด์ ๋จ๊ณ๋ค์ด ๋ชจ๋ completed์ธ์ง ํ์ธ
for (let i = 1; i < stepNumber; i++) {
const step = todos.find(t = > t.content.startsWith(`Step ${i}`));
if (step?.status !== " completed " & & step?.status !== " skipped " ) {
throw new Error(`
โ Step ${i}์ด(๊ฐ) ์๋ฃ๋์ง ์์์ต๋๋ค.
ํ์ฌ ์ํ: ${step?.status || ' unknown ' }
๋จผ์ Step ${i}์(๋ฅผ) ์๋ฃํด์ฃผ์ธ์.
`);
}
}
}
Detailed Implementation#
Step 1: Work Content Analysis#
// ํค์๋ ๊ธฐ๋ฐ ํ์
์๋ ์ถ๋ก
const typeKeywords = {
feat: [ " ์ถ๊ฐ " , " ๊ตฌํ " , " ์์ฑ " , " ๋ง๋ค๊ธฐ " , " ํ๋ฉด " ],
fix: [ " ์์ " , " ๊ณ ์น๊ธฐ " , " ๋ฒ๊ทธ " , " ์๋ฌ " , " ๋ฌธ์ " ],
refactor: [ " ๊ฐ์ " , " ๋ฆฌํฉํ ๋ง " , " ์ ๋ฆฌ " , " ์ต์ ํ " ],
chore: [ " ์ค์ " , " ๋น๋ " , " ํ๊ฒฝ " , " ๋ฐฐํฌ " ],
docs: [ " ๋ฌธ์ " , " README " , " ์ฃผ์ " ],
test: [ " ํ
์คํธ " , " test " , " ๊ฒ์ฆ " ],
};
// ํ๋ฉด ํ์
๊ฐ์ง
const screenKeywords = {
list: [ " ๋ชฉ๋ก " , " ๋ฆฌ์คํธ " , " list " , " ์กฐํ " , " ๊ฒ์ " ],
detail: [ " ์์ธ " , " detail " , " ๋ณด๊ธฐ " ],
form: [ " ์ถ๊ฐ " , " ์์ฑ " , " ์์ " , " ํธ์ง " , " form " , " ๋ฑ๋ก " ],
};
const analysis = {
type: inferType(workContent), // feat/fix/refactor...
scope: extractScope(workContent), // feature๋ช
screenType: detectScreen(workContent), // list/detail/form/null
point: estimatePoint(workContent), // 1/3/5/8
requiresBdd: screenType !== null, // screen feature์ด๋ฉด true
};
Step 2: ZenHub Issue Creation#
// ์ด์ ํ์
๋งคํ
const issueTypeMap = {
feat: " Feature " ,
fix: " Bug " ,
refactor: " Task " ,
chore: " Task " ,
docs: " Task " ,
test: " Task " ,
};
// GitHub ์ด์ ์์ฑ
const issue = await mcp__zenhub__createGitHubIssue({
repositoryId: githubRepoId,
title: `${gitmoji} ${analysis.scope}: ${workContent}`,
body: generateIssueBody(workContent, analysis),
issueTypeId: getIssueTypeId(issueTypeMap[analysis.type]),
labels: generateLabels(analysis),
});
// Estimate ์ค์
await mcp__zenhub__setIssueEstimate({
issueId: issue.id,
estimate: analysis.point,
});
Step 3-5: Pipeline Move & Branch Creation (hierarchical branch strategy)#
// Product Backlog ์ด๋
await mcp__zenhub__moveIssueToPipeline({
issueId: issue.id,
pipelineId: productBacklogPipelineId,
});
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Step 4: ๊ณ์ธต ๋ธ๋์น ์ ๋ต - base ๋ธ๋์น ๋์ ๊ฒฐ์
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// 4-1. ๋ถ๋ชจ ์ด์ ๊ฐ์ง
let baseBranch = options.base || " development " ;
let parentIssue = null;
if (!options.base) {
// ZenHub์์ ๋ถ๋ชจ ์ด์ ์กฐํ
const issueDetails = await mcp__zenhub__searchLatestIssues({
query: `#${issue.number}`,
});
const parentId = issueDetails[0]?.parentIssueId;
if (parentId) {
parentIssue = await mcp__zenhub__getIssue({ issueId: parentId });
const parentType = parentIssue.issueType; // " Epic " | " Feature " | ...
const parentNumber = parentIssue.number;
// 4-2. ๋ถ๋ชจ ๋ธ๋์น ์กด์ฌ ํ์ธ
const parentBranchPrefix = parentType === " Epic " ? " epic " : " feature " ;
const parentSlug = createSlug(parentIssue.title);
const expectedParentBranch = `${parentBranchPrefix}/${parentNumber}-${parentSlug}`;
const branchExists = await Bash(
`git rev-parse --verify origin/${expectedParentBranch} 2 > /dev/null & & echo " exists " || echo " missing " `
);
if (branchExists.trim() === " missing " ) {
// 4-3. ๋ถ๋ชจ ๋ธ๋์น ์๋ ์์ฑ (development ๊ธฐ๋ฐ)
// ๋ถ๋ชจ์ ๋ถ๋ชจ๊ฐ ์์ผ๋ฉด ์ฌ๊ท์ ์ผ๋ก base ๊ฒฐ์
const grandparentBranch = parentIssue.parentIssueId
? await resolveBaseBranch(parentIssue.parentIssueId)
: " development " ;
await Bash(`
git checkout ${grandparentBranch}
git pull origin ${grandparentBranch}
git checkout -b ${expectedParentBranch}
git push -u origin ${expectedParentBranch}
`);
console.log(`๐ฟ ๋ถ๋ชจ ๋ธ๋์น ์๋ ์์ฑ: ${expectedParentBranch}`);
}
baseBranch = expectedParentBranch;
console.log(`๐ ๊ณ์ธต ๋ธ๋์น: ${baseBranch} ๊ธฐ๋ฐ์ผ๋ก ๋ถ๊ธฐ`);
}
}
// 4-4. ๋ธ๋์น ์์ฑ (base ๋ธ๋์น์์ ๋ถ๊ธฐ)
await Task({
subagent_type: " issue-branch-agent " ,
prompt: `์ด์ #${issue.number} ๋ธ๋์น ์์ฑ (base: ${baseBranch})`,
// base_branch ํ๋ผ๋ฏธํฐ ์ ๋ฌ
});
// In Progress ์ด๋
await mcp__zenhub__moveIssueToPipeline({
issueId: issue.id,
pipelineId: inProgressPipelineId,
});
Base Branch Decision Function
// ์ฌ๊ท์ base ๋ธ๋์น ๊ฒฐ์
async function resolveBaseBranch(issueId: string): Promise < string > {
const issue = await mcp__zenhub__getIssue({ issueId });
const branchPrefix = issue.issueType === " Epic " ? " epic " : " feature " ;
const slug = createSlug(issue.title);
const branchName = `${branchPrefix}/${issue.number}-${slug}`;
// ๋ธ๋์น ์กด์ฌ ํ์ธ
const exists = await Bash(
`git rev-parse --verify origin/${branchName} 2 > /dev/null & & echo " exists " || echo " missing " `
);
if (exists.trim() === " exists " ) {
return branchName;
}
// ๋ถ๋ชจ๊ฐ ์์ผ๋ฉด ์ฌ๊ท, ์์ผ๋ฉด development
if (issue.parentIssueId) {
const parentBranch = await resolveBaseBranch(issue.parentIssueId);
await Bash(`
git checkout ${parentBranch} & & git pull origin ${parentBranch}
git checkout -b ${branchName}
git push -u origin ${branchName}
`);
return branchName;
}
// ์ต์์ โ development ๊ธฐ๋ฐ
await Bash(`
git checkout development & & git pull origin development
git checkout -b ${branchName}
git push -u origin ${branchName}
`);
return branchName;
}
Step 6: BDD Scenario Writing (for screen features)#
if (analysis.requiresBdd & & !options.skipBdd) {
await Task({
subagent_type: " bdd-scenario-agent " ,
prompt: `
feature_name: ${analysis.scope}
screen_type: ${analysis.screenType}
ํ๋ฉด ํ์
์ ๋ง๋ BDD ์๋๋ฆฌ์ค๋ฅผ ์์ฑํด์ฃผ์ธ์.
`,
});
// ์ปค๋ฐ
await Bash(`
git add .
git commit -m " test(${analysis.scope}): โ
BDD ์๋๋ฆฌ์ค ์์ฑ
- ${analysis.screenType} ํ๋ฉด ์๋๋ฆฌ์ค ์ถ๊ฐ
- Step Definition ์์ฑ
Co-Authored-By: Claude < noreply@anthropic.com > "
`);
}
Step 7-8: Implementation and Testing#
Agent Teams Parallel Mode (auto-detected)
When Agent Teams are available (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1), Steps 7 and 8 are parallelized.
When not available, automatically falls back to existing sequential processing.
์ฌ์ ํ์ธ (Step 7 ์์ ์ ):
1. CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS ํ๊ฒฝ๋ณ์ ํ์ธ
2. teams_available = true โ ๋ณ๋ ฌ ๋ชจ๋ ์ง์
3. teams_available = false โ ์์ฐจ ์คํ (์๋ " ์์ฐจ ์คํ ํ๋ฆ " ์น์
)
4. ๋ณ๋ ฌ ๋ชจ๋ ์ง์
์ ์ฌ์ฉ์์๊ฒ ํ ๊ตฌ์ฑ ๊ณํ ์ถ๋ ฅ ํ ์น์ธ ์์ฒญ
Step 7 Implementation Parallelization (frontend/backend simultaneous):
Agent Teams ์ฌ์ฉ ๊ฐ๋ฅ ์:
โโ Teammate 1: Backend ๊ตฌํ
โ โโ Serverpod model ์ ์
โ โโ Endpoint ๊ตฌํ
โ โโ Service ๋ก์ง
โ โโ melos run backend:pod:generate
โ
โโ Teammate 2: Frontend ๊ตฌํ
โโ Domain Layer (Entity, UseCase, Repository ์ธํฐํ์ด์ค)
โโ Data Layer (Repository ๊ตฌํ, DataSource)
โโ Presentation Layer (BLoC, Page, Widget)
โ ์๋ฃ ํ Lead๊ฐ ํตํฉ ๊ฒ์ฆ:
- ํ๋ก ํธ/๋ฐฑ์๋ ์ธํฐํ์ด์ค ์ผ์น ํ์ธ
- Import ์ฐธ์กฐ ์ ํฉ์ฑ ํ์ธ
Fallback (์์ฐจ):
Backend โ Frontend ์์ฐจ ์คํ
Step 8 Test Parallelization:
Agent Teams ์ฌ์ฉ ๊ฐ๋ฅ ์:
โโ Teammate 1: Frontend Unit + BLoC ํ
์คํธ
โ โโ UseCase ๋จ์ ํ
์คํธ (unit-test-agent)
โ โโ BLoC ๋จ์ ํ
์คํธ (bloc-test-agent)
โ
โโ Teammate 2: Backend Unit + Integration ํ
์คํธ
โ โโ ์๋ํฌ์ธํธ ๋จ์ ํ
์คํธ (serverpod-test-agent)
โ โโ ์๋น์ค ๋ก์ง ๋จ์ ํ
์คํธ
โ โโ ์๋ํฌ์ธํธ ํตํฉ ํ
์คํธ
โ
โโ Teammate 3: BDD Widget ํ
์คํธ (screen feature ์)
โโ Gherkin ์๋๋ฆฌ์ค ๊ฒ์ฆ
โโ Step Definition ์คํ
โ ์๋ฃ ํ Lead๊ฐ ๊ฒฐ๊ณผ ๋ณํฉ:
- ์ ์ฒด tests passed ์ฌ๋ถ ์ง๊ณ
- ์คํจ ์ PR ์์ฑ ์ฐจ๋จ
Fallback (์์ฐจ):
Unit โ BLoC โ Backend โ BDD ์์ฐจ ์คํ
Sequential Execution Flow (default / Fallback)
// ๊ตฌํ ์์
await Task({
subagent_type: " implementation-agent " ,
prompt: `์ด์ #${issue.number} ๊ตฌํ`,
});
// Backend ๋ณ๊ฒฝ ๊ฐ์ง
const hasBackendChanges = detectBackendChanges(issue);
// ํ
์คํธ ์์ฑ ๋ฐ ๊ฒ์ฆ (ํ์ ๊ฒ์ดํธ) โ ๏ธ
if (!options.skipTests) {
const testTypes = [ " unit " , " bloc " ];
if (analysis.requiresBdd) testTypes.push( " bdd " );
if (hasBackendChanges) testTypes.push( " backend_unit " , " backend_integration " );
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Phase 1: ํ
์คํธ ํ๋ ์์ฑ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// PR์ Test Plan ํญ๋ชฉ์ ๋จผ์ ์ ์ โ ๊ตฌํ ์ฝ๋์ ํต์ฌ ๋์ ์๋๋ฆฌ์ค
const testPlan = generateTestPlan({
workContent,
scope: analysis.scope,
testTypes,
hasBackendChanges,
requiresBdd: analysis.requiresBdd,
});
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Phase 2: ํ
์คํธ ํ๋ ๊ธฐ๋ฐ ํ
์คํธ ์ฝ๋ ์์ฑ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
await Task({
subagent_type: " test-runner-agent " ,
prompt: `
feature_name: ${analysis.scope}
test_types: ${JSON.stringify(testTypes)}
test_plan: ${JSON.stringify(testPlan)}
auto_fix: true
require_tests: true
write_tests_for_plan: true
`,
});
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Phase 3: ํ
์คํธ ์คํ ๋ฐ ๊ฒฐ๊ณผ ์์ง
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// 3-1. ์ ์ ๋ถ์ (dart analyze) โ ํ
์คํธ ์ฝ๋ ์ปดํ์ผ ๊ฒ์ฆ
const analyzeResult = await Bash(
`dart analyze ${testPlan.map(t = > t.file).join( ' ' )} 2 > & 1 | grep -c " error " `
);
const analyzeErrors = parseInt(analyzeResult.stdout.trim());
// 3-2. ํ
์คํธ ์คํ (์ ์ ๋ถ์ ํต๊ณผ ์)
let testResults = { totalPassed: 0, totalFailed: 0, items: [] };
if (analyzeErrors === 0) {
const testOutput = await Bash(
`flutter test ${testPlan.map(t = > t.file).filter(Boolean).join( ' ' )} 2 > & 1`
);
testResults = parseTestOutput(testOutput.stdout);
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Phase 4: ๊ฒฐ๊ณผ๋ฅผ PR Test Plan์ ๋ฐ์
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
prBodyExtras.testPlanResults = testPlan.map(item = > ({
...item,
analyzePass: analyzeErrors === 0,
testPass: testResults.items.find(r = > r.name === item.item)?.passed ?? null,
}));
prBodyExtras.testSummary = {
analyzeErrors,
totalPassed: testResults.totalPassed,
totalFailed: testResults.totalFailed,
};
// โ ๏ธ ๊ฒ์ดํธ: ์ ์ ๋ถ์ ์คํจ ์ PR ์์ฑ ์ฐจ๋จ
if (analyzeErrors > 0) {
throw new Error(`ํ
์คํธ ์ฝ๋ ์ ์ ๋ถ์ ์คํจ (${analyzeErrors}๊ฑด) - ์๋ฌ ์์ ํ ์ฌ์๋`);
}
// โ ๏ธ ๊ฒ์ดํธ: ํ
์คํธ ์คํจ ์ PR ์์ฑ ์ฐจ๋จ (๋ฐํ์ ์คํ ๊ฐ๋ฅํ๋ ๊ฒฝ์ฐ)
if (testResults.totalFailed > 0) {
throw new Error(`ํ
์คํธ ${testResults.totalFailed}๊ฑด ์คํจ - ์์ ํ ์ฌ์๋`);
}
} else {
// --skip-tests ์ฌ์ฉ ์ ์ฌ์ฉ์ ํ์ธ ํ์
const confirm = await AskUserQuestion({
questions: [{
header: " ํ
์คํธ ์คํต ํ์ธ " ,
question: " ํ
์คํธ๋ฅผ ์คํตํ๋ฉด ๊ฒ์ฆ ์์ด PR์ด ์์ฑ๋ฉ๋๋ค. ๊ณ์ํ์๊ฒ ์ต๋๊น? " ,
options: [
{ label: " ์คํต ํ์ธ " , description: " ํ
์คํธ ์์ด ์งํ (๊ธด๊ธ ํซํฝ์ค์ฉ) " },
{ label: " ํ
์คํธ ์คํ " , description: " ํ
์คํธ๋ฅผ ์คํํฉ๋๋ค " },
],
}],
});
}
Step 8.3: BDD Coverage Gate#
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Step 8.3: BDD Coverage Gate
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// .feature ํ์ผ์ ๋ชจ๋ Gherkin step์ด ๊ตฌํ๋ step ํจ์๋ฅผ ๊ฐ๊ณ ์๋์ง ๊ฒ์ฆ
// step ํจ์์ assertion์ด ํฌํจ๋์ด ์๋์ง ๊ฒ์ฆ
if (!options.skipBdd) {
// 1. .feature ํ์ผ ์ค์บ (ํ์ฌ feature ๋ฒ์)
const featureDir = findFeatureTestDir(scope); // test/src/bdd/ ํ์
const featureFiles = await Glob(`${featureDir}/**/*.feature`);
if (featureFiles.length === 0) {
console.warn(`โ ๏ธ .feature ํ์ผ ์์ (${featureDir}) โ BDD gate ์คํต`);
} else {
// 2. Given/When/Then ์คํ
ํ์ฑ
const allSteps = [];
for (const file of featureFiles) {
const content = await Read(file);
const steps = content.match(/^\s*(Given|When|Then|And|But)\s+(.+)$/gm) || [];
for (const step of steps) {
const keyword = step.match(/^\s*(Given|When|Then|And|But)/)[1];
const pattern = step
.replace(/^\s*(Given|When|Then|And|But)\s+/, ' ' )
.replace(/#.*$/, ' ' ) // ํ๊ธ ์ฃผ์ ์ ๊ฑฐ
.trim();
allSteps.push({ file, keyword, pattern });
}
}
// 3. step/ ํด๋์์ ๋งค์นญ step ํจ์ ๊ฒ์ฆ
const stepDir = `${featureDir}/step`;
const stepFiles = await Glob(`${stepDir}/**/*.dart`);
const stepContents = {};
for (const f of stepFiles) {
stepContents[f] = await Read(f);
}
const allStepCode = Object.values(stepContents).join( ' \n ' );
// step ํจํด โ camelCase ํจ์๋ช
๋ณํ ํ ์กด์ฌ ์ฌ๋ถ ํ์ธ
const missingSteps = allSteps.filter(step = > {
const funcName = stepPatternToFunctionName(step.pattern);
return !allStepCode.includes(funcName);
});
const stepCoverage = allSteps.length > 0
? (allSteps.length - missingSteps.length) / allSteps.length
: 1.0;
// 4. ๊ฐ step ํจ์ ๋ด assertion ์กด์ฌ ํ์ธ
const filesWithoutAssertions = stepFiles.filter(f = > {
const content = stepContents[f];
// UnimplementedError๋ stub โ assertion ๋ฏธ๊ตฌํ์ผ๋ก ๊ฐ์ฃผ
if (content.includes( ' UnimplementedError ' )) return true;
// expect, verify, expectVisible, expectTextVisible ๋ฑ ํ์ธ
return !content.match(/expect\(|verify\(|expectVisible|expectTextVisible|expectAsync/);
});
// 5. Gate ํ์
if (stepCoverage < 1.0) {
console.error(`โ BDD step coverage: ${(stepCoverage * 100).toFixed(1)}% (required: 100%)`);
missingSteps.forEach(s = >
console.error(` Missing: ${s.keyword} ${s.pattern} (${s.file})`)
);
console.error(`\n ๐ก step ํจ์ ์์ฑ: /bdd:generate ${scope} --only-steps true`);
throw new Error(`BDD Coverage Gate failed: step_coverage=${(stepCoverage * 100).toFixed(1)}%`);
}
if (filesWithoutAssertions.length > 0) {
console.error(`โ Assertion ์๋ step ํ์ผ ${filesWithoutAssertions.length}๊ฑด:`);
filesWithoutAssertions.forEach(f = > console.error(` ${f}`));
console.error(`\n ๐ก UnimplementedError stub์ ์ค์ assertion์ผ๋ก ๊ต์ฒดํ์ธ์`);
throw new Error(`BDD Coverage Gate failed: ${filesWithoutAssertions.length} step files missing assertions`);
}
console.log(`โ
Step 8.3 complete โ BDD coverage 100%, all ${allSteps.length} steps have assertions`);
}
} else {
console.warn(`โ ๏ธ BDD Coverage Gate skipped (--skip-bdd)`);
}
Step 8.5: Pre-push Verification + DCM Format/Lint 0-Issue Gate + DCM Code Quality Improvement#
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Phase 1: DCM ํฌ๋งทํ
+ Dart ๋ฆฐํธ ์๋ ์์ (0๊ฑด ๋ชฉํ)
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// โ ๏ธ ํฌ๋งคํฐ: dcm format์ผ๋ก ํต์ผ (dart format ์ฌ์ฉ ๊ธ์ง)
// melos run format โ ๋ด๋ถ์ ์ผ๋ก dcm format lib ์คํ
// 1-1. DCM ํฌ๋งทํ
์ ์ฉ
await Bash(`melos run format`); // dcm format (dart format ๋์ฒด)
// 1-2. dart fix --apply ๋ก ์๋ ์์ ๊ฐ๋ฅํ ๋ฆฐํธ ์ด์ ์ผ๊ด ์์
// (deprecated API ๋์ฒด, unnecessary_import ์ ๊ฑฐ, prefer_const ์ ์ฉ ๋ฑ)
await Bash(`dart fix --apply`);
// 1-3. ํฌ๋งทํ
+ fix ๋ณ๊ฒฝ ์ปค๋ฐ (๋ณ๊ฒฝ ์์ ๋๋ง)
const hasFixChanges = await Bash(`git diff --name-only`);
if (hasFixChanges.trim()) {
await Bash(`
git add .
git commit -m " style: ๐จ dart fix + format ์๋ ์์
- dart fix --apply ๋ฆฐํธ ์๋ ์์
- dcm format ์ฝ๋ ์คํ์ผ ์ ๋ฆฌ
Co-Authored-By: Claude < noreply@anthropic.com > "
`);
}
// 1-4. Dart SDK analyze ์คํ ๋ฐ ๊ฒฐ๊ณผ ํ์ฑ (dart analyze โ DCM๊ณผ ๋ณ๋)
const analyzeOutput = await Bash(`melos run analyze:dart 2 > & 1`);
const analyzeLines = analyzeOutput.split( ' \n ' )
.filter(line = > line.match(/\s+(info|warning|error)\s+[โขยท-]/));
const totalAnalyzeIssues = analyzeLines.length;
// 1-5. ๋ฆฐํธ ์ด์๊ฐ ๋จ์์์ผ๋ฉด ์๋ ์์ ์๋ (์ต๋ 2ํ ๋ฐ๋ณต)
if (totalAnalyzeIssues > 0) {
console.warn(`โ ๏ธ dart analyze ์ด์ ${totalAnalyzeIssues}๊ฑด ๋ฐ๊ฒฌ`);
for (let attempt = 0; attempt < 2; attempt++) {
// ๊ฐ ์ด์์ ์์ค ํ์ผ์ ์ฝ๊ณ ๋ฆฐํธ ๊ท์น์ ๋ง๊ฒ ์ง์ ์์
for (const issue of analyzeLines) {
// ํ์ผ ๊ฒฝ๋ก, ๋ผ์ธ, ๊ท์น๋ช
์ ํ์ฑํ์ฌ ํด๋น ์ฝ๋๋ฅผ ์์
await fixAnalyzeIssue(issue);
}
// ์์ ํ ์ฌํฌ๋งท + ์ฌ๋ถ์
await Bash(`dart fix --apply`);
await Bash(`melos run format`); // dcm format
const recheck = await Bash(`melos run analyze:dart 2 > & 1`);
const recheckLines = recheck.split( ' \n ' )
.filter(line = > line.match(/\s+(info|warning|error)\s+[โขยท-]/));
if (recheckLines.length === 0) {
console.log(`โ
dart analyze ์ด์ 0๊ฑด โ ๋ฆฐํธ ๊ฒ์ดํธ ํต๊ณผ`);
break;
}
if (attempt === 1 & & recheckLines.length > 0) {
// 2ํ ์๋ ํ์๋ ์ด์๊ฐ ๋จ์์์ผ๋ฉด ์์ธ ์ถ๋ ฅ + PR ์ฐจ๋จ
console.error(`โ dart analyze ์ด์ ${recheckLines.length}๊ฑด ๋ฏธํด๊ฒฐ โ PR ์์ฑ ์ฐจ๋จ`);
recheckLines.forEach(l = > console.error(` ${l.trim()}`));
throw new Error(`Dart lint gate blocked: ${recheckLines.length} issues remaining`);
}
}
// ์์ ๋ ํ์ผ ์ปค๋ฐ
const hasLintFixes = await Bash(`git diff --name-only`);
if (hasLintFixes.trim()) {
await Bash(`
git add .
git commit -m " fix: ๐ flutter analyze ๋ฆฐํธ ์ด์ ์์
- error/warning/info ์ด์ 0๊ฑด ๋ฌ์ฑ
- ์ ์ ๋ถ์ ํด๋ฆฐ ์ํ ํ๋ณด
Co-Authored-By: Claude < noreply@anthropic.com > "
`);
}
} else {
console.log(`โ
flutter analyze ์ด์ 0๊ฑด โ ๋ฆฐํธ ๊ฒ์ดํธ ํต๊ณผ`);
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Phase 2: DCM ์ฝ๋ ํ์ง ๊ฐ์ (dcm-code-quality ์คํฌ ํ์ฉ)
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// 2-1. DCM ๋ฆฐํธ + ๋ฉํธ๋ฆญ ๊ฒ์ฌ ์คํ
const dcmRunResult = await Bash(`melos run analyze:dcm 2 > & 1`);
// 2-2. ์๋ ์์ ๊ฐ๋ฅ ํญ๋ชฉ ์์
await Bash(`melos run fix:dcm`);
// 2-3. ๋ฏธ์ฌ์ฉ ์ฝ๋ ๊ฒ์ฌ
await Bash(`melos run check:unused-code`);
// 2-4. ์์ ํ ์ฌ๊ฒ์ฆ
const dcmVerifyResult = await Bash(`melos run analyze:dcm 2 > & 1`);
// 2-5. ์์ ๋ ํ์ผ ์ปค๋ฐ
const hasDcmChanges = await Bash(`git diff --name-only`);
if (hasDcmChanges.trim()) {
await Bash(`
git add .
git commit -m " refactor: โป๏ธ DCM ์ฝ๋ ํ์ง ์๋ ์์
- dcm fix . ์๋ ์์ ์ ์ฉ
- ๋ฏธ์ฌ์ฉ ์ฝ๋ ์ ๋ฆฌ
Co-Authored-By: Claude < noreply@anthropic.com > "
`);
}
// 2-6. ์์ฌ ์ด์ ์์ฝ โ PR body์ ํฌํจ
const remainingIssues = parseDcmOutput(dcmVerifyResult);
if (remainingIssues.length > 0) {
prBodyExtras.dcmFindings = remainingIssues;
console.log(`โน๏ธ DCM ์์ฌ ์ด์ ${remainingIssues.length}๊ฑด โ PR body์ ํฌํจ`);
}
// 2-7. error ์ฌ๊ฐ๋ ์ด์๊ฐ ๋จ์์์ผ๋ฉด ์ฐจ๋จ
const errorIssues = remainingIssues.filter(i = > i.severity === " error " );
if (errorIssues.length > 0) {
console.error(`โ DCM error ์ด์ ${errorIssues.length}๊ฑด ๋ฏธํด๊ฒฐ โ PR ์์ฑ ์ฐจ๋จ`);
throw new Error( " DCM quality gate blocked " );
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Phase 3: ์ต์ข
๋ฆฐํธ ๊ฒ์ฆ (DCM ์์ ์ด ์๋ก์ด ๋ฆฐํธ ์ด์๋ฅผ ๋ง๋ค์ง ์์๋์ง ํ์ธ)
// โ ๏ธ dart analyze + dcm analyze ๋ชจ๋ ์คํ (๊ฒ์ฌ ๋ฒ์๊ฐ ๋ค๋ฆ)
// - dart analyze: Dart SDK ๊ธฐ๋ณธ ๋ฆฐํธ + ํ์
์ฒดํฌ + ์ธ์ด ์์ค ์ค๋ฅ
// - dcm analyze: DCM ์ ์ฉ ๋ฆฐํธ ๊ท์น + ๋ฉํธ๋ฆญ + ์ํฐํจํด
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// 3-1. Dart SDK ์ต์ข
๊ฒ์ฆ
const finalDartAnalyze = await Bash(`melos run analyze:dart 2 > & 1`);
const finalDartIssues = finalDartAnalyze.split( ' \n ' )
.filter(line = > line.match(/\s+(info|warning|error)\s+[โขยท-]/));
if (finalDartIssues.length > 0) {
console.error(`โ ์ต์ข
๊ฒ์ฆ ์คํจ: dart analyze ์ด์ ${finalDartIssues.length}๊ฑด`);
finalDartIssues.forEach(l = > console.error(` ${l.trim()}`));
throw new Error(`Final dart lint gate blocked: ${finalDartIssues.length} issues remaining`);
}
// 3-2. DCM ์ต์ข
๊ฒ์ฆ
const finalDcmAnalyze = await Bash(`melos run analyze:dcm 2 > & 1`);
const finalDcmErrors = parseDcmOutput(finalDcmAnalyze).filter(i = > i.severity === " error " );
if (finalDcmErrors.length > 0) {
console.error(`โ ์ต์ข
๊ฒ์ฆ ์คํจ: dcm analyze error ${finalDcmErrors.length}๊ฑด`);
throw new Error(`Final DCM lint gate blocked: ${finalDcmErrors.length} error issues remaining`);
}
console.log(`โ
Step 8.5 complete โ dart analyze 0๊ฑด, dcm analyze error 0๊ฑด`);
Step 8.7: Code Review Gate (gstack pattern)#
Agent Teams Parallel Mode (auto-detected)
When Agent Teams are available, 8 review categories are distributed across 3 teams for parallel execution. (Agent Teams availability is already checked before Step 7 start)
Agent Teams ์ฌ์ฉ ๊ฐ๋ฅ ์:
โโ Teammate 1: ๋ณด์ + ์ฑ๋ฅ (Critical Focus)
โ โโ ๋ณด์ ์ทจ์ฝ์ (injection, XSS, ์ธ์ฆ ์ฐํ)
โ โโ ๋ฐ์ดํฐ ์์ ์ฑ (null safety, race condition)
โ โโ ์ฑ๋ฅ (N+1, ๋ฉ๋ชจ๋ฆฌ ๋์, ๋ถํ์ํ rebuild)
โ
โโ Teammate 2: ์ํคํ
์ฒ + ์ํ๊ด๋ฆฌ
โ โโ Clean Architecture ์ค์ (๋ ์ด์ด ์์กด์ฑ)
โ โโ BLoC ํจํด (์ํ ์ ์ด, ์ด๋ฒคํธ ์ฒ๋ฆฌ)
โ โโ DI / Repository ์ธํฐํ์ด์ค ์ค์
โ
โโ Teammate 3: ๊ฐ๋
์ฑ + i18n + ์ ๊ทผ์ฑ (Informational)
โโ ์ฝ๋ ๊ฐ๋
์ฑ (๋ค์ด๋ฐ, ๊ตฌ์กฐ, ๋ณต์ก๋)
โโ i18n (ํ๋์ฝ๋ฉ ๋ฌธ์์ด, ๋ฒ์ญ ํค)
โโ ์ ๊ทผ์ฑ (semanticLabel, ํฐ์น ํ๊ฒ)
โ Lead๊ฐ ๊ฒฐ๊ณผ ๋ณํฉ:
- Critical ์ด์ (Teammate 1, 2) โ Gate ์ฐจ๋จ ํ์
- Informational (Teammate 3) โ PR body์ ํฌํจ
- Critical ๋ฐ๊ฒฌ ์ ์๋ ์์ ์๋ (์ต๋ 2ํ)
Fallback (์์ฐจ):
8๊ฐ ์นดํ
๊ณ ๋ฆฌ ์์ฐจ ๊ฒ์ฌ
Sequential Execution Flow (default / Fallback)
if (!options.skipReview) {
// Pass 1: Critical ์ด์ ๊ฒ์ฌ
const reviewResult = await Skill({
skill: " code-review " ,
args: " --quick --gate-mode " ,
});
const criticalIssues = reviewResult.issues.filter(i = > i.severity === " critical " );
const informationalIssues = reviewResult.issues.filter(i = > i.severity !== " critical " );
if (criticalIssues.length > 0) {
// ์๋ ์์ ์๋ (์ต๋ 2ํ)
for (let attempt = 0; attempt < 2; attempt++) {
await autoFixCriticalIssues(criticalIssues);
const recheck = await Skill({ skill: " code-review " , args: " --quick --gate-mode " });
if (recheck.criticalCount === 0) break;
}
// ์ฌ๊ฒ์ฆ ํ์๋ Critical ๋จ์์์ผ๋ฉด ์ฐจ๋จ
if (finalCheck.criticalCount > 0) {
console.error(`โ Code Review Gate ์คํจ: ${finalCheck.criticalCount}๊ฑด์ Critical ์ด์`);
console.error( " --skip-review ์ต์
์ผ๋ก ์ฐํํ๊ฑฐ๋, ์๋ ์์ ํ ์ฌ์๋ํ์ธ์ " );
throw new Error( " Code Review Gate blocked " );
}
}
// Pass 2: Informational ํญ๋ชฉ์ PR body์ ํฌํจํ ๋ฐ์ดํฐ๋ก ์ ์ฅ
prBodyExtras.reviewFindings = informationalIssues;
} else {
console.warn( " โ ๏ธ Code Review Gate๋ฅผ ๊ฑด๋๋๋๋ค (--skip-review) " );
console.warn( " ํ๋ก๋์
๋ฐฐํฌ ์ ๋ฐ๋์ /code-review๋ฅผ ์คํํ์ธ์ " );
}
Step 9-10: PR Creation & Move to Review/QA (hierarchical merge)#
// PR base ๋ธ๋์น ๊ฒฐ์ (๊ณ์ธต ๋ธ๋์น ์ ๋ต)
// baseBranch๋ Step 4์์ ๊ฒฐ์ ๋ ๊ฐ ์ฌ์ฉ
const prBaseBranch = baseBranch; // epic/xxx, feature/xxx, ๋๋ development
// PR ์์ฑ
await Bash(`
gh pr create --base " ${prBaseBranch} " \
--title " ${gitmoji} ${analysis.scope}: ${workContent} " \
--body " $(cat < < ' EOF '
## Summary
- ${workContent}
## Test Plan
${prBodyExtras.testPlanResults?.map(t = > {
const check = t.testPass === true ? ' x ' : (t.testPass === false ? ' ' : ' ~ ' );
const status = t.analyzePass
? (t.testPass === true ? ' โ
' : (t.testPass === false ? ' โ ' : ' โ ๏ธ ๋ถ์๋ง ํต๊ณผ ' ))
: ' โ ๋ถ์ ์คํจ ' ;
return `- [${check}] ${t.item} (${status})`;
}).join( ' \n ' ) ?? `
- [ ] UseCase ๋จ์ tests passed
- [ ] BLoC ๋จ์ tests passed
${hasBackendChanges ? ' - [ ] ๋ฐฑ์๋ ์๋ํฌ์ธํธ ํตํฉ tests passed ' : ' ' }
${analysis.requiresBdd ? ' - [ ] BDD ์๋๋ฆฌ์ค ํต๊ณผ ' : ' ' }
`}
${prBodyExtras.testSummary ? `
> ํ
์คํธ ๊ฒฐ๊ณผ: ${prBodyExtras.testSummary.totalPassed}๊ฑด ํต๊ณผ` +
(prBodyExtras.testSummary.totalFailed > 0 ? `, ${prBodyExtras.testSummary.totalFailed}๊ฑด ์คํจ` : ' ' ) +
(prBodyExtras.testSummary.analyzeErrors > 0 ? ` | ์ ์ ๋ถ์ ์๋ฌ: ${prBodyExtras.testSummary.analyzeErrors}๊ฑด` : ' | ์ ์ ๋ถ์: โ
' )
: ' ' }
Closes #${issue.number}
${prBodyExtras.dcmFindings?.length > 0 ? `
## DCM Quality Report
${prBodyExtras.dcmFindings.map(f = > `- **[${f.severity}]** ${f.rule}: ${f.message} (${f.file}:${f.line})`).join( ' \n ' )}
` : ' ' }
${prBodyExtras.reviewFindings?.length > 0 ? `
## Review Findings (Informational)
${prBodyExtras.reviewFindings.map(f = > `- **[${f.category}]** ${f.message} (${f.file}:${f.line})`).join( ' \n ' )}
` : ' ' }
๐ค Generated with [Claude Code](https://claude.com/claude-code)
EOF
) "
`);
// Review/QA ์ด๋
await mcp__zenhub__moveIssueToPipeline({
issueId: issue.id,
pipelineId: reviewQaPipelineId,
});
Step 11: Apply Additional Review Feedback#
// Step 8.7์์ ์๋ ์ฝ๋ review complete๋จ
// Step 11์ PR ์์ฑ ํ ์ถ๊ฐ ๋ฆฌ๋ทฐ ํผ๋๋ฐฑ ๋ฐ์ ๋จ๊ณ
if (!options.skipReview) {
// PR ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ ํ์ธ ๋ฐ ์ถ๊ฐ ํผ๋๋ฐฑ ๋ฐ์
await Skill({ skill: " code-review " });
// ํผ๋๋ฐฑ ๋ฐ์ (์๋ ๊ฐ๋ฅํ ๊ฒ๋ง)
await Bash(`melos run format`); // dcm format
await Bash(`dart fix --apply`);
await Bash(`melos run analyze:dart`);
await Bash(`melos run analyze:dcm`);
// /checklist:feature-complete ์คํ
await Skill({ skill: " checklist:feature-complete " });
}
Step 12: Wait for Merge Approval#
// ์ต์ข
์ํ ์์ฝ
displayMergeSummary(issue, pr, testResults, reviewResults);
// user approval ์์ฒญ
const approval = await AskUserQuestion({
questions: [{
header: " ๋จธ์ง " ,
question: " PR์ ๋จธ์งํ์๊ฒ ์ต๋๊น? " ,
options: [
{ label: " ๋จธ์ง ์น์ธ " , description: " ์ค์ฟผ์ ๋จธ์ง ํ ์ด์ ํด๋ก์ฆ " },
{ label: " ์์ ํ์ " , description: " Step 7๋ก ๋์๊ฐ์ ์์ " },
{ label: " ์ทจ์ " , description: " ํ์ฌ ์ํ ์ ์ง " },
],
multiSelect: false,
}],
});
if (approval === " ๋จธ์ง ์น์ธ " ) {
// ์ค์ฟผ์ ๋จธ์ง โ GitHub " Closes # " ํค์๋๋ก ์ด์ ์๋ Close
// Done ํ์ดํ๋ผ์ธ ์ด๋ ๋ถํ์ (๋จธ์ง = ์๋ฃ)
await Bash(`gh pr merge --squash --delete-branch`);
// ๊ณ์ธต ๋ธ๋์น: ๋ถ๋ชจ ๋ธ๋์น๊ฐ Epic์ด๊ณ ๋ชจ๋ Story๊ฐ ์๋ฃ๋๋ฉด ์๋ด
if (parentIssue & & parentIssue.issueType === " Epic " ) {
const siblings = await mcp__zenhub__searchLatestIssues({
query: `parent:${parentIssue.id}`,
});
const openSiblings = siblings.filter(s = > s.state === " open " );
if (openSiblings.length === 0) {
console.log(`\n๐ Epic #${parentIssue.number}์ ๋ชจ๋ Story๊ฐ ์๋ฃ๋์์ต๋๋ค.`);
console.log(` Epic ๋ธ๋์น๋ฅผ development์ ๋จธ์งํ์ธ์:`);
console.log(` /dev:run ${parentIssue.number}`);
} else {
console.log(`\nโน๏ธ Epic #${parentIssue.number}: ${openSiblings.length}๊ฐ Story ๋จ์`);
}
}
}
Output Format#
Progress Display#
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ /dev:run Progress โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ โ
โ [โโโโโโโโโโโโโโโโโโโโ] 50% - Step 6/12 โ
โ โ
โ โ
Step 1: ์์
๋ด์ฉ ๋ถ์ ์๋ฃ (screen feature ๊ฐ์ง: List) โ
โ โ
Step 2: ์ด์ #1810 ์์ฑ ์๋ฃ โ
โ โ
Step 3: Product Backlog ์ด๋ โ
โ โ
Step 4: ๋ธ๋์น ์์ฑ ์๋ฃ โ
โ โ
Step 5: In Progress ์ด๋ โ
โ ๐ Step 6: BDD ์๋๋ฆฌ์ค ์์ฑ ์ค... โ
โ โณ Step 7: ๊ตฌํ ์์
๋๊ธฐ โ
โ โณ Step 8: ํ
์คํธ ์์ฑ/์คํ ๋๊ธฐ โ
โ โณ Step 9: PR ์์ฑ ๋๊ธฐ โ
โ โณ Step 10: Review/QA ์ด๋ ๋๊ธฐ โ
โ โณ Step 11: ์ฝ๋ ๋ฆฌ๋ทฐ ๋๊ธฐ โ
โ โณ Step 12: ๋จธ์ง ์น์ธ ๋๊ธฐ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
On Completion#
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Workflow Complete: #1810 โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ โ
โ ๐ Issue: #1810 - ์ ์ ๋ชฉ๋ก ํ๋ฉด ์ถ๊ฐ โ
โ ๐ PR: #1815 โ
โ ๐ฟ Branch: feature/1810-author-list (deleted) โ
โ โ
โ ๐ Changes: โ
โ - 12 files changed โ
โ - +520 / -30 lines โ
โ โ
โ โ
Tests: 35/35 passed โ
โ - UseCase Unit: 10/10 โ
โ - BLoC Unit: 8/8 โ
โ - Backend Unit: 6/6 (endpoint: 3, service: 3) โ
โ - Backend Integration: 4/4 โ
โ - BDD: 7/7 scenarios โ
โ โ
โ โ
Review: All issues resolved โ
โ โ
Checklist: 11/11 passed โ
โ โ
CI: All checks passed โ
โ โ
โ ๐ Duration: 22m 15s โ
โ ๐ Final State: CLOSED (by merge) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
TodoWrite Integration (Required)#
Rule: Immediately update TodoWrite on each step start/completion
Initialization (On Step 1 Start)#
TodoWrite([
{ content: " Step 1: ์์
๋ด์ฉ ๋ถ์ " , status: " in_progress " , activeForm: " ์์
๋ด์ฉ ๋ถ์ ์ค " },
{ content: " Step 2: ZenHub ์ด์ ์์ฑ " , status: " pending " , activeForm: " ์ด์ ์์ฑ ๋๊ธฐ " },
{ content: " Step 3: Product Backlog ์ด๋ " , status: " pending " , activeForm: " Pipeline ์ด๋ ๋๊ธฐ " },
{ content: " Step 4: ๋ธ๋์น ์์ฑ " , status: " pending " , activeForm: " ๋ธ๋์น ์์ฑ ๋๊ธฐ " },
{ content: " Step 5: In Progress ์ด๋ " , status: " pending " , activeForm: " Pipeline ์ด๋ ๋๊ธฐ " },
{ content: " Step 6: BDD ์๋๋ฆฌ์ค " , status: " pending " , activeForm: " BDD ์์ฑ ๋๊ธฐ " },
{ content: " Step 7: ๊ตฌํ ์์
" , status: " pending " , activeForm: " ๊ตฌํ ๋๊ธฐ " },
{ content: " Step 7.5: Backend ์ฝ๋ ์์ฑ " , status: " pending " , activeForm: " Backend ์์ฑ ๋๊ธฐ " },
{ content: " Step 8: ํ
์คํธ ์์ฑ ๋ฐ ๊ฒ์ฆ (ํ์ ๊ฒ์ดํธ) " , status: " pending " , activeForm: " ํ
์คํธ ๋๊ธฐ " },
{ content: " Step 8.3: BDD Coverage Gate " , status: " pending " , activeForm: " BDD ์ปค๋ฒ๋ฆฌ์ง ๊ฒ์ฆ ๋๊ธฐ " },
{ content: " Step 8.5: Pre-push ๊ฒ์ฆ + ๋ฆฐํธ 0๊ฑด + DCM ํ์ง ๊ฐ์ " , status: " pending " , activeForm: " ๊ฒ์ฆ ๋๊ธฐ " },
{ content: " Step 8.7: Code Review Gate " , status: " pending " , activeForm: " ๋ฆฌ๋ทฐ ๊ฒ์ดํธ ๋๊ธฐ " },
{ content: " Step 9: PR ์์ฑ " , status: " pending " , activeForm: " PR ์์ฑ ๋๊ธฐ " },
{ content: " Step 10-12: ๋ฆฌ๋ทฐ/๋จธ์ง " , status: " pending " , activeForm: " ๋ฆฌ๋ทฐ ๋๊ธฐ " },
]);
Update Immediately on Step Completion#
// Step 4 complete ํ ์์
TodoWrite([
{ content: " Step 1: ์์
๋ด์ฉ ๋ถ์ " , status: " completed " , activeForm: " ์์
๋ถ์ ์๋ฃ " },
{ content: " Step 2: ZenHub ์ด์ ์์ฑ " , status: " completed " , activeForm: " ์ด์ ์์ฑ ์๋ฃ " },
{ content: " Step 3: Product Backlog ์ด๋ " , status: " completed " , activeForm: " Pipeline ์ด๋ ์๋ฃ " },
{ content: " Step 4: ๋ธ๋์น ์์ฑ " , status: " completed " , activeForm: " ๋ธ๋์น ์์ฑ ์๋ฃ " },
{ content: " Step 5: In Progress ์ด๋ " , status: " in_progress " , activeForm: " Pipeline ์ด๋ ์ค " },
// ... ๋๋จธ์ง pending ์ ์ง
]);
State Tracking Rules#
| Rule | Description |
|---|---|
| Only 1 in_progress | Only 1 in_progress state allowed at a time |
| Immediate update | Change to completed immediately on step completion |
| Skip marking | Add "(skip)" to content for N/A steps |
| Keep on failure | Keep failed step as in_progress |
Auto-Inference Details#
Type Inference Examples#
/dev:run " ์ ์ ๋ชฉ๋ก ํ๋ฉด ์ถ๊ฐ "
โ ํค์๋ " ์ถ๊ฐ " , " ํ๋ฉด " ๊ฐ์ง โ feat
/dev:run " ๋ก๊ทธ์ธ ๋ฒ๊ทธ ์์ "
โ ํค์๋ " ๋ฒ๊ทธ " , " ์์ " ๊ฐ์ง โ fix
/dev:run " API ์๋ต ์บ์ฑ ๊ฐ์ "
โ ํค์๋ " ๊ฐ์ " ๊ฐ์ง โ refactor
Screen Type Detection Examples#
/dev:run " ์ ์ ๋ชฉ๋ก ํ๋ฉด ์ถ๊ฐ "
โ ํค์๋ " ๋ชฉ๋ก " ๊ฐ์ง โ List โ BDD ์๋ ์์ฑ
/dev:run " ๋์ ์์ธ ํ๋ฉด ๊ตฌํ "
โ ํค์๋ " ์์ธ " ๊ฐ์ง โ Detail โ BDD ์๋ ์์ฑ
/dev:run " ์ ์ ๋ฑ๋ก ํผ ์ถ๊ฐ "
โ ํค์๋ " ๋ฑ๋ก " ๊ฐ์ง โ Form โ BDD ์๋ ์์ฑ
/dev:run " API ์๋ต ์บ์ฑ ์ถ๊ฐ "
โ ํ๋ฉด ํค์๋ ์์ โ BDD ์คํต
Error Handling#
Step-by-Step Recovery Strategy#
| Failed Step | State | Recovery Method |
|---|---|---|
| Issue creation | No changes | Retry |
| Branch creation | Issue exists | Retry |
| BDD scenario | Branch exists | --skip-bdd or manual writing |
| Implementation | Branch exists | Continue work |
| Tests | Code complete | --skip-tests or manual fix |
| Dart lint gate | Code complete |
dart fix --apply
+
melos run format
(dcm format) + manual fix (up to 2 times) โ 0 issues required
|
| DCM quality gate | Code complete |
melos run fix:dcm
auto-fix โ
melos run analyze:dcm
โ manually fix remaining errors
|
| Code Review Gate | Code complete | --skip-review or manual fix |
| PR creation | Code complete | Run gh pr create manually |
| Code review | PR exists | --skip-review or manual application |
| Merge | PR exists | Wait for CI pass then manual merge |
Related Commands#
/dev:issue {number}- Progress cycle with existing issue/dev:bugfix- Bug fix dedicated cycle/bug-report- Bug report creation/code-review- Run code review/dcm:quality- DCM code quality analysis and auto-fix/checklist:feature-complete- Completion checklist
Related Agents#
issue-branch-agent- Branch creationissue-state-agent- Issue state managementbdd-scenario-agent- BDD scenario generationimplementation-agent- Code implementationtest-runner-agent- Test executionpr-lifecycle-agent- PR management