| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /dev:run |
| Aliases | /dr |
| Category | kobic-development |
| Complexity | high |
| MCP Servers | zenhub, figma |
/dev:run โ ์์ ํ ์ค์ด๋ฉด ๋๊น์ง ๊ตด๋ ค์ฃผ๋ "๊ฐ๋ฐ ์๋ ์ปจ๋ฒ ์ด์ด"#
ํ์ดํ๋ผ์ธ์ ๊ฐ๋ฐ ์คํ ๋จ๊ณ. "๋ฌด์์ ๋ง๋ค์ง" ํ ์ค๋ง ์ฃผ๋ฉด ์ด์ ๋ฑ๋ก๋ถํฐ ์ฝ๋ ์์ฑ, ํ ์คํธ, ๊ฒ์, ๋จธ์ง๊น์ง 13๋จ๊ณ๋ฅผ ์๋์ผ๋ก ์งํํฉ๋๋ค.
ํ๋ง๋๋ก#
"์ด๋ฐ ๊ฑฐ ๋ง๋ค์ด์ค" ํ ์ค์ ๋ฃ์ผ๋ฉด, ํ ์ผ ๋ฑ๋ก โ ์ฝ๋ ์์ฑ โ ํ์ง ๊ฒ์ฌ โ ์ต์ข ํฉ์น๊ธฐ๊น์ง ์์์ ์งํ๋๋ ์๋ ์ปจ๋ฒ ์ด์ด ๋ฒจํธ์ ๋๋ค. ๊ณต์ฅ ๋ผ์ธ์ ์์์ฌ(์์ ๋ด์ฉ)๋ฅผ ์ฌ๋ ค๋๋ฉด ์์ฑํ(ํฉ์ณ์ง ์ฝ๋)์ด ๋์ค๋ ๊ฒ๊ณผ ๊ฐ์์. ๋จ, ๋ง์ง๋ง "ํฉ์ณ๋ ๋ ๊น์?"๋ ์ฌ๋์ด ์ง์ ์น์ธํฉ๋๋ค.
๋๊ฐยท์ธ์ ์ฐ๋์#
- ์ ๊ธฐ๋ฅ ์ถ๊ฐ, ๋ฒ๊ทธ ์์ , ์ฝ๋ ์ ๋ฆฌ(๋ฆฌํฉํ ๋ง)๋ฅผ ์ฒ์๋ถํฐ ๋๊น์ง ํ ๋ฒ์ ์งํํ๊ณ ์ถ์ ๋
- "๋ฌด์จ ์์ ์ ํ ์ง" ์ค๋ช ๋ง ์๊ณ , ๊ทธ๊ฑธ ์ด์๋ก ๋ง๋ค๊ณ ์ฝ๋๊น์ง ์ด์ด๊ฐ๊ณ ์ถ์ ๋
- ์ด๋ฏธ ๋ฑ๋ก๋ ์์ ๋ฒํธ(์ด์)๊ฐ ์์ด์, ๊ทธ ๋ฒํธ๋ก ๋ฐ๋ก ๊ฐ๋ฐ์ ์์ํ๊ณ ์ถ์ ๋
๐ ํ ์ค ์ค๋ช ์ผ๋ก ์์ํ๋ฉด ์์ ๋ฒํธ(์ด์)๋ถํฐ ์๋ก ๋ง๋ค์ด ์ฃผ๊ณ , ์ด๋ฏธ ๋ฒํธ๊ฐ ์์ผ๋ฉด ๊ทธ ๋ฒํธ๋ก ๊ณง์ฅ ์ด์ด๊ฐ๋๋ค.
๋ฌด์์ ํด์ฃผ๋์#
์์ ํ๋๊ฐ ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฌผ๋ก ์ ๋ฆฌ๋ฉ๋๋ค. (๊ดํธ ์์ ์ค์ ๋ก ๋จ๋ ์ฐ์ถ๋ฌผ)
- ์์ ๋ฑ๋กํ(ZenHub ์ด์) โ PM ๊ด์ ์์ ์๊ตฌ์ฌํญยท์๋ฃ ๊ธฐ์คยท์ค์ฝํ๋ฅผ ํ ๋ฒ ๋ค๋ฌ์ ๋ค(ํผ๊ทธ๋ง ๋งํฌ๊ฐ ์์ผ๋ฉด ๋์์ธ ๋ด์ฉ๋ ๋ฐ์), ์์ ์ข ๋ฅยท๋ฒ์ยท์์ ๊ท๋ชจ๊ฐ ์๋์ผ๋ก ๋งค๊ฒจ์ง ์ฑ๋ก ๋ฑ๋ก
- ์ ์ฉ ์์
๊ณต๊ฐ(๋ธ๋์น) โ
{์ข ๋ฅ}/{์ด์๋ฒํธ}-{์ด๋ฆ}ํ์์ ๊ฒฉ๋ฆฌ๋ ์ฝ๋ ์์ ๊ณต๊ฐ - ์๋๋ฆฌ์ค ๋ช
์ธ(
.featureํ์ผ) โ ํ๋ฉด ์์ ์ด๋ฉด "์ด๋ป๊ฒ ๋์ํด์ผ ํ๋์ง" ํ ์คํธ ์๋๋ฆฌ์ค ์๋ ์์ฑ - ๋์์ธ ๋์กฐ ์ฆ๋น(์คํฌ๋ฆฐ์ท+๋น๊ต ๋ฆฌํฌํธ) โ ํ๋ฉด ์์ ์ด๋ฉด ์ค๊ธฐ๊ธฐยท์๋ฎฌ๋ ์ดํฐ์ ๋์ด ํ๋ฉด์ Figma ์์๊ณผ ๋์กฐํ ์ฌ์ง๊ณผ ๋ฆฌํฌํธ
- ๊ฒ์ฆ๋ ์ฝ๋์ ํ ์คํธ โ ๋จ์/์์ ฏ/ํตํฉ ํ ์คํธ๊ฐ ๋ชจ๋ ํต๊ณผํ ์ํ์ ์ปค๋ฐ๋ค
- ํฉ์น๊ธฐ ์์ฒญ์(PR) โ ๋ณ๊ฒฝ ์์ฝยทํ ์คํธ ๊ฒฐ๊ณผยท์ฝ๋ ๋ฆฌ๋ทฐ ๊ฒฐ๊ณผ๊ฐ ๋ด๊ธด Pull Request
- ์๋ ์ ๋ฆฌ โ ํฉ์ณ์ง๋ฉด ์ด์๊ฐ ์๋์ผ๋ก "์๋ฃ(Closed)" ์ฒ๋ฆฌ๋๊ณ , ๋ค์ ์์ ์ ์ํด ๊ธฐ์ค ์ฝ๋๊ฐ ์ต์ ์ํ๋ก ๊ฐฑ์
์ด๋ป๊ฒ ์ฐ๋์#
# ๊ฐ์ฅ ๊ธฐ๋ณธ โ ์์
๋ด์ฉ ํ ์ค๋ก ์ฒ์๋ถํฐ ๋๊น์ง
/dev:run " ์ ์ ๋ชฉ๋ก ํ๋ฉด ์ถ๊ฐ "
# ์ด๋ฏธ ๋ฑ๋ก๋ ์์
๋ฒํธ(์ด์)๋ก ์์
/dev:run 1810
# ์์
์ข
๋ฅ๋ฅผ ์ง์ ์ง์ (feat=๊ธฐ๋ฅ / fix=์์ / refactor=์ ๋ฆฌ)
/dev:run --type feat " Add author list screen "
# ์ด๋ฒ ์คํ๋ฆฐํธ์ ๋ฐฐ์
/dev:run --sprint current " Add author list screen "
# ํฉ์น๊ธฐ(๋จธ์ง)๊น์ง๋ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ PR๋ง ์์ฑ
/dev:run --no-merge " Add author list screen "
# ๋ฆฌ๋ทฐ ๊ฑด๋๋ฐ๊ธฐ (๊ธด๊ธ ํซํฝ์ค)
/dev:run --skip-review " Urgent fix "
# ํ
์คํธ ๊ฑด๋๋ฐ๊ธฐ (๊ธด๊ธ)
/dev:run --skip-tests " Urgent fix "
์ฐธ๊ณ : ๊ธฐํ ๋ช ์ธ(
docs/seed-spec-*.md, ํ์ /LOCKED)๊ฐ ์๋ ์ ์ฅ์์์ ์ฌ๋ฌ ์ด์๋ฅผ ํ ๋ฒ์ ๋๋/dev:batch๋ ์์ยท๋ง๋ฌด๋ฆฌ ์์ ์ "์์ ์ด ์๋ ๊ธฐํ ๋ชฉํ์ ๊ฐ์ ๋ฐฉํฅ์ธ๊ฐ"๋ฅผ ์๋ฏธ ๊ธฐ๋ฐ์ผ๋ก ํ ๋ฒ์ฉ ๋์กฐํฉ๋๋ค(์ฝ๊ธฐ ์ ์ฉ,/spec:status์ฌ์ฌ์ฉ). ๋จ์ผ ์ด์์ฉ/dev:run์๋ ์ด ์ ๊ฒ ๋์ , ์ด์ ์์ฑ ์ง์ (Step 1.5)์pm-spec-agent๊ฐ ํ ์ค ์์ฒญ์ FR/ACยท์ค์ฝํยทStory Point๋ก ์ ์ ํฉ๋๋ค โ LOCKED seed-spec์ด ์์ผ๋ฉด ๊ทธ๊ฑธ ์ฐ์ ์ฐธ๊ณ ํ๊ณ , ์์ด๋ ๋งค๋ฒ ๋์ํฉ๋๋ค(--skip-pm์ผ๋ก ์๋ต ๊ฐ๋ฅ).
์ต์
์ ๋ฐ๋ก ์ฃผ์ง ์์ผ๋ฉด ์์
์ข
๋ฅยท๋ฒ์ยท๊ท๋ชจยทBDD ํ์ ์ฌ๋ถ ๋ฑ์ ๋ด์ฉ์์ ์์์ ์ถ๋ก ํ๊ณ , ์ด์๋ฅผ ๋ง๋ค๊ธฐ ์ ์ PM ๊ด์ ์ ์๊ตฌ์ฌํญ ์ ๋ฆฌ(Step 1.5)๊น์ง ์๋์ผ๋ก ๊ฑฐ์นฉ๋๋ค.
์ด ๋ฐ์๋ --scope(๋ฒ์ ์ง์ ), --point(๊ท๋ชจ ์ง์ ), --base(๊ธฐ์ค ๋ธ๋์น ์ง์ ),
--bdd/--skip-bdd(์๋๋ฆฌ์ค ๊ฐ์ /์๋ต), --skip-pm(PM ์๊ตฌ์ฌํญ ์ ๋ฆฌ ์๋ต), --skip-local-integration(๋ก์ปฌ ํตํฉ ๊ฒ์ฆ ์๋ต),
--skip-design-verify(ํ๋ฉด ๋์์ธ ๊ฒ์ฆ ์๋ต, ํ์ธ ํ์), --design-target/--design-tolerance(๊ฒ์ฆ ๊ธฐ๊ธฐยทํ์ฉ์ค์ฐจ ์ง์ ) ๊ฐ์ ์ธ๋ถ ์ต์
์ด ์์ต๋๋ค. ์ ํํ ๋์์ ์๋ ์์ธ ๋ช
์ธ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
ํฌ๊ฒ "๋ฑ๋ก โ ์์ ๊ณต๊ฐ ์ค๋น โ ๋ง๋ค๊ธฐ โ ์ฌ๋ฌ ๊ฒน์ ํ์ง ๊ฒ์ฌ โ ํฉ์น๊ธฐ" ์์๋ก 13๋จ๊ณ๊ฐ ์ฐจ๋ก๋ก ์งํ๋ฉ๋๋ค.
-
์์
๋ด์ฉ ๋ถ์ + PM ์๊ตฌ์ฌํญ ์ ๋ฆฌ โ ๊ธ์์ ์์
์ข
๋ฅ(๊ธฐ๋ฅ/์์ /์ ๋ฆฌ), ๋ฒ์, ํ๋ฉด ์ข
๋ฅ, ์์ ๊ท๋ชจ๋ฅผ ์๋ ํ๋ณํ ๋ค, PM ๊ด์ ์์ ์๊ตฌ์ฌํญยท์๋ฃ ๊ธฐ์คยท์ค์ฝํ(ํฌํจ/์ ์ธ)๋ฅผ ์ ๋ฆฌํฉ๋๋ค(ํผ๊ทธ๋ง ๋งํฌ๊ฐ ์์ผ๋ฉด ๋์์ธ ๋ด์ฉ ๋ฐ์,
--skip-pm์ผ๋ก ์๋ต ๊ฐ๋ฅ). ์ ๋งคํ ๋ถ๋ถ์ ๋๋ฌป์ง ์๊ณ "๊ฐ์ "์ผ๋ก ๋จ๊ธด ์ฑ ์งํํฉ๋๋ค. - ์ด์ ๋ฑ๋ก (ZenHub) โ ์ ๋ฆฌ๋ ๋ด์ฉ์ผ๋ก ์์ ๋ฑ๋กํ๋ฅผ ๋ง๋ค๊ณ ํ์ ยท๋ผ๋ฒจยท์์์น๋ฅผ ๋ถ์ ๋๋ค.
- ๋๊ธฐ์ด๋ก ์ด๋ โ ๋ฑ๋ก๋ ์์ ์ "ํ ์ผ(Product Backlog)" ์นธ์ผ๋ก ์ฎ๊น๋๋ค.
- ์์ ๊ณต๊ฐ(๋ธ๋์น) ์์ฑ โ ๋ค๋ฅธ ์ฝ๋์ ์์ด์ง ์๋๋ก ์ ์ฉ ๋ธ๋์น๋ฅผ ๋ง๋ญ๋๋ค. (๋ฉ์ธ ์ฝ๋์ ์ง์ ์๋์ง ์๋๋ก ๋ง๋ ์์ ์ฅ์น ํฌํจ)
- ์งํ ์ค์ผ๋ก ํ์ โ ์์ ์ "์งํ ์ค(In Progress)" ์นธ์ผ๋ก ์ฎ๊น๋๋ค.
- ์๋๋ฆฌ์ค ์์ฑ โ ํ๋ฉด ์์ ์ด๋ฉด "์ด๋ ๊ฒ ๋์ํด์ผ ํ๋ค"๋ BDD ์๋๋ฆฌ์ค๋ฅผ ์๋์ผ๋ก ์๋๋ค.
- ์ค์ ๊ตฌํ + ํ๋ฉด ๋์์ธ ๊ฒ์ฆ โ ์ด์ ๋ด์ฉ๋๋ก ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์กฐ๊ธ์ฉ ์ ์ฅ(์ปค๋ฐ)ํฉ๋๋ค. ํ๋ฉด ์์ ์ด๋ฉด ์๋ฎฌ๋ ์ดํฐยท์ค๊ธฐ๊ธฐ์ ์ค์ ๋ก ์ฑ์ ๋์ Figma ์์๊ณผ ์ฌ์ง์ผ๋ก ๋์กฐํ๊ณ , ๋ค๋ฅด๋ฉด ๋๊ฐ์์ง ๋๊น์ง ์๋์ผ๋ก ๊ณ ์นฉ๋๋ค(๋๊ตฌ/๊ธฐ๊ธฐ๊ฐ ์๊ฑฐ๋ ๊ทธ๋๋ ์ ๋ง์ผ๋ฉด ์ฌ๋์๊ฒ ํ์ธ์ ์์ฒญ โ ๋ฌด์์ผ๋ก ๊ฑด๋๋ฐ์ง ์์ต๋๋ค).
- ๋ฐฑ์๋ ์ฝ๋ ์์ฑยท๋ก์ปฌ ํตํฉ ๊ฒ์ฆ โ ์๋ฒ ์ชฝ ๋ณ๊ฒฝ์ด ์์ผ๋ฉด ์ฝ๋๋ฅผ ์์ฑํ๊ณ , ๋ก์ปฌ์์ ์๋ฒ+DB+์ฑ์ ๋์ ์ค์ ๋ก ์ ๋ง๋ฌผ๋ฆฌ๋์ง ํ์ธํฉ๋๋ค.
- ์ฌ๋ฌ ๊ฒน์ ํ์ง ๊ฒ์ฌ(๊ฒ์ดํธ) โ ํ ์คํธ ํต๊ณผ, ์๋๋ฆฌ์ค ์ปค๋ฒ๋ฆฌ์ง 100%, ์ฝ๋ ์์ยท์ ์ ๋ถ์ 0๊ฑด, ์ฝ๋ ๋ฆฌ๋ทฐ์ ์น๋ช ์ ๋ฌธ์ 0๊ฑด์ ์ฐจ๋ก๋ก ํต๊ณผํด์ผ๋ง ๋ค์์ผ๋ก ๋์ด๊ฐ๋๋ค. ํ๋๋ผ๋ ๋งํ๋ฉด ํฉ์น๊ธฐ ์์ฒญ์ ๋ง๋ค์ง ์์ต๋๋ค.
- ํฉ์น๊ธฐ ์์ฒญ(PR) ์์ฑ โ ๊ฒ์ฌ๋ฅผ ๋ชจ๋ ํต๊ณผํ๋ฉด ๋ณ๊ฒฝ ์์ฝยทํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ๋ด์ PR์ ๋ง๋ค๊ณ ๊ฒ์(Review/QA) ์นธ์ผ๋ก ์ฎ๊น๋๋ค.
- ์ถ๊ฐ ๋ฆฌ๋ทฐ ๋ฐ์ โ ๋จ์ ๋ฆฌ๋ทฐ ์๊ฒฌ์ ๋ฐ์ํ๊ณ ์๋ฃ ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ๋๋ฆฝ๋๋ค.
- ํฉ์น๊ธฐ ์น์ธ ๋๊ธฐ โ ํฉ์น๊ธฐ โ ์ฌ๋์๊ฒ "ํฉ์ณ๋ ๋ ๊น์?"๋ฅผ ๋ฌป๊ณ , ์น์ธํ๋ฉด ํ๋๋ก ํฉ์นฉ๋๋ค(ํฉ์น๊ธฐ = ์์ ์๋ฃ).
- ๋ง๋ฌด๋ฆฌ ํ์ ยท๋๊ธฐํ โ ์ด์๊ฐ ์ ๋ง "์๋ฃ"๋ก ๋ซํ๋์ง ํ์ธํ๊ณ , ๋ค์ ์์ ์ ์ํด ๊ธฐ์ค ์ฝ๋๋ฅผ ์ต์ ์ํ๋ก ๋์ด์ต๋๋ค.
โ๏ธ ์์ธ ์ต์ ยท์คํ ๋ช ์ธ (๊ฐ๋ฐ์ / AI ์์ด์ ํธ์ฉ)
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 1810Parameters#
| 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) |
--sprint | current (active sprint) | currentโํ์ฑ ์คํ๋ฆฐํธ(getSprint, id ์์=ํ์ฑ), nextโ๋ค์ ์คํ๋ฆฐํธ(getUpcomingSprint), ์ซ์/์ด๋ฆโlistRecentSprints ๋งค์นญ. ์์ฑ ์ด์๋ฅผ ํด๋น ์คํ๋ฆฐํธ์ ๋ฃ์ด ๋ก๋๋งต/๋ฒ๋ค์ด์ ๋
ธ์ถ |
--no-sprint | off | ์คํ๋ฆฐํธ ๋ฐฐ์ ์๋ต(์ด ๊ฒฝ์ฐ ์ ์์ค ์ด์๋ ๋ก๋๋งต ํ์๋ผ์ธ์์ ๋ณด์ด์ง ์์ ์ ์์) |
--skip-pm | off (PM ์ ์ ํญ์ ์คํ) | Step 1.5 PM ์๊ตฌ์ฌํญ ์ ์ (pm-spec-agent)๋ฅผ ๊ฑด๋๋ฐ๊ณ Step 1์ ํค์๋ ์ถ๋ก ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋๋ก ์ด์ ๋ณธ๋ฌธ์ ์ฌ์ฉ (๊ธด๊ธ/์ฌ์ํ ํ ์ค ์์
์ฉ) |
--skip-design-verify | off (์๊ตฌ ์ ํ์ธ ํ์) | screen feature + Figma ์์ค ์กด์ฌ ์ Step 7.3 ๊ธฐ๋ณธ on. ๋๊ตฌ/๊ธฐ๊ธฐ ๋ถ์ฌ ์ AskUserQuestion ๊ธฐ๋ณธ ๋ต๋ง "์คํต"์ผ๋ก ๋ฐ๊ฟ ๋ฟ, ํ์ธ ์์ฒด๋ ์๋ต ๋ถ๊ฐ |
--design-target | auto | pixel-loop/visual-verify๋ก ์ ๋ฌํ ๋์ ๊ธฐ๊ธฐ (ios/android/macos/auto) |
--design-tolerance | pixel=2,color_delta_e=3 (create) / pixel=1,color_delta_e=2 (repair) | pixel-loop ์์ฒด ๊ธฐ๋ณธ๊ฐ๊ณผ ๋์ผํ ์์น โ create(์ต์ด ์์ฑ)๋ ๋ค์ ์ํ, repair(์ฌ์์ )๋ ์๊ฒฉ |
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 "
# Skip local full-stack integration verification (Step 7.7, default on)
# ๋ก์ปฌ ํ์คํ(serverpod start, ๋ด์ฅ Postgres) ํตํฉ ๊ฒ์ฆ์ ๊ฑด๋๋
/dev:run --skip-local-integration " Backend-only config tweak "
# Skip PM requirements refinement (Step 1.5, default on)
# ์ด์๋ฅผ ๋ง๋ค๊ธฐ ์ PM ์๊ตฌ์ฌํญ ์ ์ ๋จ๊ณ๋ฅผ ๊ฑด๋๋ฐ๊ณ Step 1์ ํค์๋ ์ถ๋ก ๋ง ์ฌ์ฉ
/dev:run --skip-pm " Urgent one-liner "
# Skip design verification (Step 7.3, default on for screen features) โ still requires AskUserQuestion confirmation
/dev:run --skip-design-verify " Urgent hotfix without a simulator handy "
# Explicit design-verification target/tolerance
/dev:run --design-target=android --design-tolerance pixel=1,color_delta_e=2 " ์ ์ ์์ธ ํ๋ฉด ์ถ๊ฐ "Execution Flow (13 Steps)#
Step 0: Tooling Preflight & Degradation Contract โ ๏ธ (GD-01)#
13๋จ๊ณ ์ง์ ์ ์ ์ธ๋ถ ๋๊ตฌ ๊ฐ์ฉ์ฑ์ ํ ๋ฒ์ ์ ๊ฒํ๊ณ capability ๋งต์ ๋ง๋ ๋ค. ์ดํ ๊ฐ ๊ฒ์ดํธ๋ ์ด ๋งต์ ์ฐธ์กฐํ๋ค โ ์ค์น ์ ๋ ๋๊ตฌ๋ฅผ bare ํธ์ถํด throw ์ํค์ง ์๋๋ค.
# loop/๋จ๊ณ ์ง์
์ 1ํ โ ๊ฒฐ๊ณผ๋ฅผ capability ๋งต์ผ๋ก ๋ณด๊ด
for tool in melos serverpod dart flutter gh; do
command -v " $tool " > /dev/null 2 > & 1 & & echo " $tool: ok " || echo " $tool: MISSING "
done
# ๋์์ธ ๊ฒ์ฆ ๋๊ตฌ(Step 7.3 ์ ์ฉ) โ ์๋ " ์์ธ " ์ฐธ๊ณ , melos/serverpod ๋ฑ๊ณผ ์ทจ๊ธ์ด ๋ค๋ฅด๋ค
claude mcp list 2 > /dev/null | grep -Eq " figma.*connected " & & echo " figma-mcp: ok " || echo " figma-mcp: MISSING "
command -v maestro > /dev/null 2 > & 1 & & echo " maestro: ok " || echo " maestro: MISSING "
claude mcp list 2 > /dev/null | grep -Eq " dart.*connected " & & echo " dart-mcp: ok " || echo " dart-mcp: MISSING "
flutter devices 2 > /dev/null | grep -Eq " โข " & & echo " device: ok " || echo " device: MISSING "Degradation contract (๋ฐ๋์ ์ค์):
- ์์ (ํ์ง) ์๋ฐ โ ์ฐจ๋จ(throw): ํ ์คํธ ์คํจ / lint ์ด์ / ๋ฆฌ๋ทฐ Critical ๋ฑ์ hard fail.
- ๋๊ตฌ ๋ถ์ฌ โ degrade + ๊ฒฝ๊ณ (throw ์๋): ๋น๋ ๋๊ตฌ ๋ฏธ์ค์น๋ ์์ ์๋ฐ์ด ์๋๋ค. ์ฐํํ๊ณ ๊ฒฝ๊ณ ๋ง ๋จ๊ธด๋ค.
melos์์ผ๋ฉด โflutter test/dart analyze/dart format๋ฅผ ์ง์ ํธ์ถ(=preflight.mdํด๋ฐฑ๊ณผ ๋์ผ),melos run backend:pod:generate๋ skip + ๊ฒฝ๊ณ .serverpod์์ผ๋ฉด โ Step 7.7(๋ก์ปฌ ํ์คํ ํตํฉ ๊ฒ์ฆ) skip + ๊ฒฝ๊ณ (--skip-local-integration๊ณผ ๋์ผ ์ทจ๊ธ).gh์์/๋ฏธ์ธ์ฆ โ PR ๋จ๊ณ(Step 9~) ์ง์ ์ ์ ์กฐ๊ธฐ ์๋ด ํ ์ค๋จ(PR์ด ํต์ฌ ์ฐ์ถ๋ฌผ์ด๋ผ ์งํ ์๋ฏธ ์์; ์์ ์ ๋ค ํ ๋ค Step 9์์ throw ํ์ง ๋ง ๊ฒ).
- ๊ธ์ง: ์ค์น ์ ๋ ๋๊ตฌ๋ฅผ ๊ฐ๋ ์์ด bare
await Bash(...)๋ก ํธ์ถํด throw ์ํค๋ ๊ฒ. ์ ๋งต์ ๋จผ์ ํ์ธํ๊ณ ๋ถ๊ธฐํ๋ผ. - โ ๏ธ ์์ธ (figma-mcp/maestro/dart-mcp/device): ์ด 4๊ฐ๋ ์ "๋๊ตฌ ๋ถ์ฌ โ ๋ฌด์ degrade" ๊ณ์ฝ์ ๋ฐ๋ฅด์ง ์๋๋ค. Step 7.3์ด ์ค์ ๋ก ํธ๋ฆฌ๊ฑฐ๋๋ screen feature์์ ์ด ์ค ํ๋๋ผ๋ ์์ผ๋ฉด, ๋ฌด์์ผ๋ก ๊ฑด๋๋ฐ์ง ๋ง๊ณ ๋ฐ๋์
AskUserQuestion์ผ๋ก ๋ช ์ ํ์ธ์ ๋ฐ์ ๋ค(--skip-tests์ ๋์ผํ UX), PR body์ ์คํต ์ฌ์ค์ ์๊ตฌ ๊ธฐ๋กํ๋ค. ๋ฌด์ ์คํต์ด "ํ๋ฉด์ด ๋งค๋ฒ ๋์์ธ๊ณผ ๋ฌ๋ผ์ง๋" ๋ฌธ์ ๊ฐ ์ง๊ธ๊น์ง ๋ฐฉ์น๋ ์์ธ์ด์๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐฐ๊ฒฝ: ๊ณผ๊ฑฐ
/dev:run์ melos/serverpod/dart ๋ช ๋ น์ ์กด์ฌ ๊ฐ๋ ์์ด bare ํธ์ถํด, ๋๊ตฌ 1๊ฐ ๋ถ์ฌ๋ก ์ ์ฒด๊ฐ ํ๋ํ์ผํ๋ค(GD-01).scripts/check_skill_drift.py์ FAILOPEN warn ์ด ์ด ํจํด์ ๊ฐ์งํ๋ค.
Step-by-Step Prerequisites and Verification#
| Step | Phase | Prerequisites | Verification |
|---|---|---|---|
| 1 | Work content analysis | None | - |
| 1.5 | PM requirements refinement (pm-spec-agent, --skip-pm ์ ์คํต) | Step 1 complete | issue body์ FR/AC/์ค์ฝํ ํฌํจ, ๊ฐ์ ๋ช ์ |
| 2 | ZenHub issue creation | Step 1.5 complete (or Step 1 if --skip-pm) | issue.id exists |
| 3 | Move to Product Backlog | Step 2 complete | pipeline verified |
| 4 | Branch creation ({type}/{issue_number}-{slug}) | 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.2 | CoUI ํจํค์ง ๋ณ๊ฒฝ ๋ถ๋ฆฌ โ CoUI ์ปดํฌ๋ํธ ์์ /ํ์ฅ ํ์ ์ CoUI ์ ์ฅ์ ๋ณ๋ PR ์ ํ ๋จธ์ง โ ํ๋ก์ ํธ๋ ์์กด์ฑ ๋ฐ์ ํ ํ๋ฉด ์กฐ๋ฆฝ ์ฌ๊ฐ | Step 7 ์งํ ์ค ๊ฐ์ง (composition ladder rung 4) | CoUI PR merged + ์์กด์ฑ ๊ฐฑ์ , ํ๋ฉด diff์ CoUI ์์ค ์์ |
| 7.3 | ๋์์ธ ๊ฒ์ฆ ๊ฒ์ดํธ (Figma โ ์ค๊ธฐ๊ธฐ/์๋ฎฌ๋ ์ดํฐ) โ screen feature + Figma ์์ค ์กด์ฌ ์, ์ค์ ๋ก ์ฑ์ ๋์ ํฝ์ ๋จ์๋ก ๋์กฐ | Step 7.2 ์๋ฃ (ํด๋น ์) | ์๊ฐ ๋์กฐ PASS ๋๋ ๋ช
์์ ์คํต ํ์ธ ๊ธฐ๋ก (prBodyExtras.designVerification ์กด์ฌ) |
| 7.5 | Backend code generation | Step 7 complete, when Backend changes exist | generation success |
| 7.7 | ๋ก์ปฌ ํ์คํ ํตํฉ ๊ฒ์ฆ (Serverpod 4 / ๋ก์ปฌ ํ์คํ) | Step 7.5 complete, on Backend or screen feature | ๋ก์ปฌ ํตํฉํ ์คํธ PASS |
| 8 | Test writing + /pr:preflight gate (๋ณ๊ฒฝ ํจํค์ง unit+widget+integration) | Step 7 complete | /pr:preflight PASS |
| 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 (= Per-Push Gate, ์ดํ ๋ชจ๋ push ์ง์ ์ฌ์คํ) | 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 โ squash merge (merge = Close) | Step 11 complete | user approval |
| 12.5 | Issue Close ํ์ โญ โ base๊ฐ ๊ธฐ๋ณธ(default) ๋ธ๋์น๊ฐ ์๋๋ฉด(๊ณ์ธต ๋จธ์ง: story/ยทepic/) Closes # ์๋ ๋ซํ์ด ์ ๋๋ฏ๋ก gh issue close๋ก ๋ช
์์ ์ข
๋ฃ(ํด๋ฐฑ์ด ์๋๋ผ ๊ธฐ๋ณธ ๋์). ์ด์ด GitHub closed + ZenHub Closed ํ์ โ checkout PR base + git pull | Step 12 merge ์๋ฃ | GitHub+ZenHub ๋ชจ๋ Closed, base ์ต์ ํ |
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ /dev:run " ์์
๋ด์ฉ " โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Step 1: ์์
๋ด์ฉ ๋ถ์ โ
โ โโโ ์์
์ ํ ์ถ๋ก (feat/fix/chore/refactor) โ
โ โโโ ์ค์ฝํ ์ถ์ถ (feature๋ช
) โ
โ โโโ ํ๋ฉด ํ์
๊ฐ์ง (๋ชฉ๋ก/์์ธ/ํผ) โ
โ โโโ ์์ ๋ณต์ก๋ ์ฐ์ (Story Point) โ
โ โ
โ Step 1.5: PM ์๊ตฌ์ฌํญ ์ ์ โ ๏ธ (--skip-pm ์ ์คํต) โ
โ โโโ pm-spec-agent ํธ์ถ โ
โ โโโ (ํผ๊ทธ๋ง ๋งํฌ ์์ผ๋ฉด) get_design_context/get_screenshot ์กฐํ โ
โ โโโ LOCKED seed-spec ์์ผ๋ฉด ์ฐ์ ์ ํฉ โ
โ โโโ FR/AC, ์ค์ฝํ In/Out, Story Point ์ฌ์ฐ์ โ
โ โโโ ๋ช
์์ --type/--scope/--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.2: CoUI ํจํค์ง ๋ณ๊ฒฝ ๋ถ๋ฆฌ (on CoUI ์์ /ํ์ฅ ํ์) โ ๏ธ โ
โ โโโ composition ladder ์ ํ (as-isโstyleโcomposeโextend) โ
โ โโโ extend ํ์ ํ์ ์: CoUI ์ ์ฅ์ ๋ณ๋ ์ด์/๋ธ๋์น/PR โ
โ โ (additive ยท coui_flutter + coui_web ์์ชฝ ยท ๋ฒ์ ๋ฒํ) โ
โ โโโ CoUI PR ์ ํ ๋จธ์ง โ ํ๋ก์ ํธ ์์กด์ฑ ๊ฐฑ์ โ
โ โโโ โ ๏ธ ํ๋ฉด PR diff์ CoUI ์์ค ๋ณ๊ฒฝ ํฌํจ ๊ธ์ง (Step 9 ๊ฒ์ดํธ) โ
โ โ
โ Step 7.3: ๋์์ธ ๊ฒ์ฆ ๊ฒ์ดํธ (screen feature + Figma ์์ค ์) โ ๏ธ โ
โ โโโ (๋๊ตฌ/๊ธฐ๊ธฐ ๋ถ์ฌ ๋๋ --skip-design-verify ์ AskUserQuestion ํ์ธ)โ
โ โโโ flutter run -d {target} --print-dtd (๋ฐฑ๊ทธ๋ผ์ด๋, ๋ณ๋ ํ๋ก์ธ์ค) โ
โ โโโ [์ ํยท๋น์ฐจ๋จ] marionette:smoke โ ์กฐ๋ฆฝ/๋ฐ์ ์ฌ๋ถ๋ง ๋น ๋ฅด๊ฒ ํ์ธ โ
โ โโโ pixel-loop:loop < figma-url > --mode=create|repair --route=... โ
โ โโโ inspector:visual-verify --mode=regression (์ฝ๊ธฐ์ ์ฉ ์ต์ข
ํ์ ) โ
โ โโโ ์คํจ ์ pixel-loop repair 1ํ ์ฌ์๋ โ ๊ทธ๋๋ ์คํจ๋ฉด ์ฌ์ฉ์ ํ์ธโ
โ โโโ โ ๏ธ ๋ฌด์ ์คํต ๊ธ์ง โ PR body์ ์ํ(pass/skip/๋ฏธํด๊ฒฐ) ์๊ตฌ ๊ธฐ๋ก โ
โ โ
โ Step 7.5: Backend ์ฝ๋ ์์ฑ (on Backend changes) โ ๏ธ โ
โ โโโ melos run backend:pod:generate โ
โ โโโ ์ปค๋ฐ: " chore(backend): ๐ง ์ฝ๋ ์์ฑ " โ
โ โ
โ Step 7.7: ๋ก์ปฌ ํ์คํ ํตํฉ ๊ฒ์ฆ (Backend/screen feature ์) โ ๏ธ โ
โ โโโ (--skip-local-integration ์ ์คํต, ๊ธฐ๋ณธ on) โ
โ โโโ serverpod start (๋ฐฑ์๋+๋ด์ฅ Postgres+์ฑ ํ์คํ ๊ธฐ๋) โ
โ โโโ [Backend ๋ณ๊ฒฝ] dart test -t integration (๋ก์ปฌ, Docker ๋ถํ์) โ
โ โโโ [screen feature] ํ๋ก ํธ๋ฅผ ๋ก์ปฌ ๋ฐฑ์๋ ๋์ ํตํฉ ์ค๋ชจํฌ โ
โ โโโ ๋ง์ด๊ทธ๋ ์ด์
/๋ก๊ทธ โ Serverpod MCP ํ์ฉ โ
โ โโโ ๊ฒ์ฆ ํ serverpod start ์ข
๋ฃ โ
โ โโโ (3.x ํด๋ฐฑ: docker compose up -d + dart run bin/main.dart) โ
โ โโโ โ ๏ธ ๊ฒ์ดํธ ์คํจ ์ Step 8 ์งํ ์ฐจ๋จ โ
โ โ
โ Step 8: ํ
์คํธ ์์ฑ ๋ฐ ๊ฒ์ฆ (ํ์ ๊ฒ์ดํธ) โ ๏ธ โ
โ โโโ [Phase 8a: ํ
์คํธ ์ฝ๋ ์์ฑ] โ
โ โ โโโ [ํ๋ก ํธ์๋ - ํ์] โ
โ โ โ โโโ UseCase ๋จ์ ํ
์คํธ (unit-test-agent) โ
โ โ โ โโโ BLoC ๋จ์ ํ
์คํธ (bloc-test-agent) โ
โ โ โโโ [๋ฐฑ์๋ - on Backend changes ํ์] โ
โ โ โ โโโ ์๋ํฌ์ธํธ/์๋น์ค ๋จ์ ํ
์คํธ (serverpod-test-agent) โ
โ โ โ โโโ ์๋ํฌ์ธํธ ํตํฉ ํ
์คํธ (serverpod-test-agent) โ
โ โ โโโ BDD Widget ํ
์คํธ (screen feature ์, bdd_test_gen ํ์ฉ) โ
โ โ โโโ ์ปค๋ฐ: " test({scope}): โ
ํ
์คํธ ์์ฑ " โ
โ โโโ [Phase 8b: PR Preflight ๊ฒ์ดํธ] /pr:preflight --auto-scope โ
โ โ โโโ ๋ณ๊ฒฝ ํจํค์ง ์๋ ๊ฐ์ง (git diff {base}..HEAD) โ
โ โ โโโ Unit ๋ ์ด์ด (๋ณ๊ฒฝ ํจํค์ง) โ ์คํจ ์ ์ฆ์ ์ค๋จ โ
โ โ โโโ Widget ๋ ์ด์ด (๋ณ๊ฒฝ ํจํค์ง, BDD widget_test ํฌํจ) โ
โ โ โโโ Integration @smoke (๋๋ฐ์ด์ค ์์ผ๋ฉด ์กฐ๊ฑด๋ถ ์คํ) โ
โ โโโ โ ๏ธ ๊ฒ์ดํธ ์คํจ ์ Step 8.3 ์งํ ๋ถ๊ฐ โ PR ์์ฑ ์ฐจ๋จ โ
โ ์ ์ฑ
: cc-dev-cycle/rules/pr-preflight-policy.md โ
โ โ
โ 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 SDK ๋ถ์) โ
โ โ โโโ error/warning/info ์์กด ์ ์๋ ์์ (์ต๋ 2ํ ๋ฐ๋ณต) โ
โ โ โโโ โ ๏ธ 2ํ ์๋ ํ์๋ ์ด์ ์์กด ์ PR ์์ฑ ์ฐจ๋จ โ
โ โโโ [Phase 2: DCM ์ฝ๋ ํ์ง ๊ฐ์ ] โ
โ โ โโโ melos run dcm:analyze (DCM ๋ฆฐํธ + ๋ฉํธ๋ฆญ) โ
โ โ โโโ melos run dcm:fix (์๋ ์์ ๊ฐ๋ฅ ํญ๋ชฉ ์์ ) โ
โ โ โโโ melos run dcm:unused-code (๋ฏธ์ฌ์ฉ ์ฝ๋ ๊ฒ์ฌ) โ
โ โ โโโ melos run dcm:analyze (์์ ํ ์ฌ๊ฒ์ฆ) โ
โ โ โโโ โ ๏ธ error ์ฌ๊ฐ๋ ์ด์ ์์กด ์ PR ์์ฑ ์ฐจ๋จ โ
โ โโโ [Phase 3: ์ต์ข
๋ฆฐํธ ๊ฒ์ฆ] โ
โ โ โโโ melos run analyze (DCM ์์ ํ Dart ์ฌํ์ธ) โ
โ โ โโโ melos run dcm:analyze (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: ๋จธ์ง ์น์ธ ๋๊ธฐ (๋จธ์ง = Close) โ
โ โโโ user approval ์์ฒญ โ
โ โโโ ์น์ธ ์ ์ค์ฟผ์ ๋จธ์ง โ
โ โโโ GitHub " Closes # " ํค์๋๋ก ์ด์ ์๋ Close ์๋ โ
โ โ
โ Step 12.5: ์ด์ Close ๊ฒ์ฆ + ํด๋ฐฑ โญ (ZenHub 2-์ํ ๋ชจ๋ธ) โ ๏ธ โ
โ โโโ GitHub state ํ์ธ (open/closed์ source of truth) โ
โ โ โโโ ๋ฏธclose ์ gh issue close ๋ก ๋ช
์์ ์ข
๋ฃ โ
โ โโโ ZenHub Closed ๋๊ธฐํ ํ์ธ (GitHub closed = Closed 1:1) โ
โ โ โโโ ๋๊ธฐํ ์ง์ฐ ์ updateIssue state:CLOSED ๊ฐ์ โ
โ โโโ PR base ๋ธ๋์น๋ก ์ด๋ + git pull (์ต์ ํ) โญ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ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
# โญ ์ด์๋ฒํธ ํฌํจ ๊ฒ์ฆ (์ด์ ์ถ์ ์ฉ์ด): {type}/{issue_number}-{slug}
# ๋ธ๋์น ์์ฑ ์งํ ์กฐ๊ธฐ ๊ฒ์ดํธ โ ๋ฒํธ ๋๋ฝ์ Step 9(PR ์ง์ )๊น์ง ๋ฏธ๋ฃจ์ง ์๊ณ ์ฆ์ ์ฐจ๋จ
if [[ ! $BRANCH =~ ^(epic|story|task|feature|fix|refactor|chore)/[0-9]+- ]]; then
echo " โ ๋ธ๋์น๋ช
์ ์ด์๋ฒํธ๊ฐ ์์ต๋๋ค: $BRANCH "
echo " ํ์: {type}/{issue_number}-{slug} (์: feature/1810-author-list) "
echo " ์ด์ ์ถ์ ์ ์ํด ๋ธ๋์น๋ช
์ ๋ฐ๋์ ์ด์๋ฒํธ๊ฐ ํฌํจ๋ผ์ผ ํฉ๋๋ค. "
echo " /dev:run ์ ์ด์ ์์ฑ(Step 2) ํ ์๋์ผ๋ก ๋ฒํธ๋ฅผ ํฌํจํด ๋ธ๋์น๋ฅผ ๋ง๋ญ๋๋ค. "
exit 1
fiStep 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|story|task|feature|fix|refactor|chore)/[0-9]+ ]]; then echo " โ ๋ธ๋์น ํ์ ์ค๋ฅ: $BRANCH " echo " ์ฌ๋ฐ๋ฅธ ํ์: feature/30-description ๋๋ epic/10-description " exit 1 fiIssue 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 fiCoUI source contamination check (screen feature ์ โ Step 7.2 ๊ฒ์ดํธ)
# ํ๋ฉด PR diff์ CoUI ํจํค์ง ์์ค ๋ณ๊ฒฝ์ด ํฌํจ๋๋ฉด ์ฐจ๋จ (์์กด์ฑ ๋ฒํ๋ง ํ์ฉ) COUI_TOUCHED=$(git diff --name-only origin/${BASE_BRANCH}..HEAD | grep -E ' (^|/)(packages/)?coui(_flutter|_web|_core)?/ ' || true) if [[ -n " $COUI_TOUCHED " ]]; then echo " โ ํ๋ฉด PR์ CoUI ์์ค ๋ณ๊ฒฝ ํฌํจ โ CoUI ์ ์ฅ์ ๋ณ๋ PR๋ก ๋ถ๋ฆฌ ํ ์ ํ ๋จธ์งํ์ธ์ (Step 7.2) " exit 1 fiDesign verification recorded (screen feature ์ โ Step 7.3 ๊ฒ์ดํธ)
# screen feature + Figma ์์ค๊ฐ ์์๋๋ฐ prBodyExtras.designVerification ๊ฐ ๋น์ด ์์ผ๋ฉด ์ฐจ๋จ # (๋ฏธ๊ฒ์ฆ ์ํ๋ฅผ pass ๋ก ๊ฐ์ฃผํ์ง ์๋๋ค โ Step 8์ FO-04 ์์น๊ณผ ๋์ผ) if [[ " $REQUIRES_BDD " == " true " & & -f " .claude/docs/${SCOPE}/figma_analysis.md " & & -z " $DESIGN_VERIFICATION_STATUS " ]]; then echo " โ Step 7.3 ๋์์ธ ๊ฒ์ฆ ๊ฒฐ๊ณผ๊ฐ ๊ธฐ๋ก๋์ง ์์์ต๋๋ค โ PR ์์ฑ ์ฐจ๋จ " exit 1 fiPre-push verification + DCM format/lint 0-issue gate + DCM code quality improvement (Step 8.5 โ Per-Push Gate: ์ต์ด push๋ฟ ์๋๋ผ ์ดํ ๋ชจ๋ push ์ง์ ์ ์ฌ์คํ)
# Phase 1: DCM ํฌ๋งทํ + Dart ๋ฆฐํธ ์๋ ์์ + 0๊ฑด ๊ฒ์ดํธ melos run format # dcm format (dart format ๋์ฒด) dart fix --apply # ์๋ ์์ ๊ฐ๋ฅํ ๋ฆฐํธ ์ด์ ์ผ๊ด ์์ melos run analyze # Dart SDK ๋ถ์ ์คํ # โ error/warning/info ๋ชจ๋ 0๊ฑด ํ์ (๋จ์์์ผ๋ฉด ์๋ ์์ ํ ์ฌ๊ฒ์ฆ, ์ต๋ 2ํ) # โ 2ํ ์๋ ํ์๋ ์ด์ ์์กด ์ PR ์์ฑ ์ฐจ๋จ # Phase 2: DCM ์ฝ๋ ํ์ง ๊ฐ์ melos run dcm:analyze # DCM ๋ฆฐํธ + ๋ฉํธ๋ฆญ ๊ฒ์ฌ melos run dcm:fix # ์๋ ์์ (๋ฆฐํธ) melos run dcm:unused-code # ๋ฏธ์ฌ์ฉ ์ฝ๋ ๊ฒ์ฌ melos run dcm:analyze # ์์ ํ ์ฌ๊ฒ์ฆ # โ error ์ฌ๊ฐ๋ ์ด์ 0๊ฑด ํ์ # Phase 3: ์ต์ข ๋ฆฐํธ ๊ฒ์ฆ (DCM ์์ ์ด ์ ๋ฆฐํธ ์ด์๋ฅผ ๋ง๋ค์ง ์์๋์ง) melos run analyze # Dart SDK ์ต์ข ํ์ธ melos run dcm:analyze # 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 1.5: PM Requirements Refinement (--skip-pm ์ ์คํต)#
Step 1์ ํค์๋ ์ถ๋ก ์ ์ด์๋ฅผ ๋ง๋ค๊ธฐ์ ๋๋ฌด ์๋ค โ ํนํ "ํ๋ฉด"/"๊ด๋ฆฌ์"๋ฅ ์์ฒญ์ ์ค์ ๋ก๋ CRUD ๋ฒ์, ๋
ธ์ถ ์กฐ๊ฑด, ๋์์ธ ์ํ ๊ฐ์ ์ธ๋ถ์ฌํญ์ด ์จ์ด ์๋ค. ์ด ๋จ๊ณ๋ pm-spec-agent๋ฅผ ํธ์ถํด Step 1 ๊ฒฐ๊ณผ๋ฅผ PM ๊ด์ ์ ์ค์ ์คํ์ผ๋ก ์น๊ฒฉ์ํจ ๋ค Step 2์ ๋๊ธด๋ค.
if (!options.skipPm) {
// work_content์์ URL ์ถ์ถ (ํผ๊ทธ๋ง ๋ฑ)
const figmaUrl = extractFigmaUrl(workContent);
// ๊ธฐ์กด ํ์ (LOCKED) ๊ธฐํ ๋ช
์ธ๊ฐ ์์ผ๋ฉด ์ฐ์ ์ฌ์ฉ
const lockedSeedSpec = await findLockedSeedSpec(analysis.scope); // docs/seed-spec-*.md
let pmSpec;
try {
pmSpec = await Task({
subagent_type: " pm-spec-agent " ,
prompt: `
work_content: ${workContent}
initial_analysis: ${JSON.stringify(analysis)}
explicit_options: ${JSON.stringify({ type: options.type, scope: options.scope, point: options.point })}
figma_url: ${figmaUrl ?? " none " }
locked_seed_spec: ${lockedSeedSpec ?? " none " }
์ด ์์
์์ฒญ์ PM ๊ด์ (FR/AC, ์ค์ฝํ In/Out, Fibonacci Story Point)์ผ๋ก ์ ๋ฆฌํด์ค.
์ ๋งคํ ๋ถ๋ถ์ ๋ฌป์ง ๋ง๊ณ ํฉ๋ฆฌ์ ์ผ๋ก ๊ฐ์ ํ ๋ค " Assumptions " ์ ๋ช
์ํ๊ณ ์งํํด.
`,
});
} catch (e) {
console.warn( " โ ๏ธ PM ์๊ตฌ์ฌํญ ์ ์ ์คํจ โ Step 1 ํค์๋ ์ถ๋ก ๊ฒฐ๊ณผ๋ก ํด๋ฐฑ: " , e?.message ?? e);
pmSpec = null; // degrade, don ' t throw (GD-01๊ณผ ๋์ผํ ๊ณ์ฝ)
}
if (pmSpec) {
// ๋ช
์์ ์ต์
์ ๊ทธ๋๋ก ์ ์ง, ๋๋จธ์ง๋ pmSpec์ผ๋ก ์น๊ฒฉ
analysis.type = options.type ?? pmSpec.type;
analysis.scope = options.scope ?? pmSpec.scope;
analysis.point = options.point ?? pmSpec.point;
analysis.requiresBdd = pmSpec.screenTypes.length > 0;
analysis.issueBody = pmSpec.body; // Step 2๊ฐ ๊ทธ๋๋ก ์ฌ์ฉ
}
} else {
console.warn( " โ ๏ธ PM ์๊ตฌ์ฌํญ ์ ์ ๋ฅผ ๊ฑด๋๋๋๋ค (--skip-pm) " );
}Degradation (GD-01 ์ค์): figma MCP ๋ฏธ์ค์น/๋งํฌ ์ ๊ทผ ์คํจ โ ๋์์ธ ์ปจํ
์คํธ ์์ด ํ
์คํธ๋ง์ผ๋ก ์งํ + ๊ฒฝ๊ณ (throw ์๋). pm-spec-agent ์์ฒด๊ฐ ์คํจํ๋ฉด Step 1์ ํค์๋ ์ถ๋ก ๊ฒฐ๊ณผ๋ก ํด๋ฐฑ + ๊ฒฝ๊ณ โ PM ์ ์ ์คํจ๊ฐ ์ ์ฒด ํ์ดํ๋ผ์ธ์ ๋ง์ง ์๋๋ค.
Skip: --skip-pm ์ต์
์ ์ด ๋จ๊ณ ์ ์ฒด๋ฅผ ๊ฑด๋๋ฐ๊ณ Step 1์ ํค์๋ ์ถ๋ก ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋๋ก Step 2์ ์ ๋ฌํ๋ค (๊ฒฝ๊ณ ๋ฉ์์ง ์ถ๋ ฅ).
Step 2: ZenHub/GitHub Issue Creation#
โ ๏ธ ์ด์ ์์ฑ์ ZenHub(GitHub)์๋ง ํฉ๋๋ค (๋จ์ผ ์ถ์ฒ). Jira์๋ ์ ๋ ์ด์๋ฅผ ๋ง๋ค์ง ์์ต๋๋ค โ
createJiraIssue/editJiraIssue/transitionJiraIssueํธ์ถ ๊ธ์ง. Jira๋ ์์ฑ๋ ์ด์๋ฅผ ์กฐํยทํ์ธํ๋ ์ฝ๊ธฐ ์ ์ฉ ์ฉ๋๋ก๋ง ์๋๋ค(getJiraIssue,searchJiraIssuesUsingJql). ์์ธํ ์ ์ฑ ์ SKILL.md โ "Issue Tracker Policy" ์ฐธ๊ณ .
// ์ด์ ํ์
๋งคํ
const issueTypeMap = {
feat: " Feature " ,
fix: " Bug " ,
refactor: " Task " ,
chore: " Task " ,
docs: " Task " ,
test: " Task " ,
};
// GitHub ์ด์ ์์ฑ
// analysis.issueBody๋ Step 1.5(pm-spec-agent)๊ฐ ์ฑ์ด PM ์ ์ ๋ณธ โ --skip-pm ์ด๊ฑฐ๋ ์ ์ ์คํจ ์ ํด๋ฐฑ
const issue = await mcp__zenhub__createGitHubIssue({
repositoryId: githubRepoId,
title: `${gitmoji} ${analysis.scope}: ${workContent}`,
body: analysis.issueBody ?? generateIssueBody(workContent, analysis),
issueTypeId: getIssueTypeId(issueTypeMap[analysis.type]),
labels: generateLabels(analysis),
});
// Estimate ์ค์
await mcp__zenhub__setIssueEstimate({
issueId: issue.id,
estimate: analysis.point,
});Step 2.6 (optional): Jira read-only verification
์ด์๋ ์ Step 2์์ ZenHub(GitHub)์ ์ด๋ฏธ ์์ฑ๋จ. Jira๋ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ์ด ์ด์๊ฐ ๋ณด์ด๋์ง ์กฐํยทํ์ธ๋ง ํ๋ค(์์ฑ/์์ ๊ธ์ง). ์กฐํ ์คํจ๋ ์ํฌํ๋ก๋ฅผ ๋ง์ง ์๋๋ค(non-blocking).
// ์ฝ๊ธฐ ์ ์ฉ: ์์ฑ๋ ์ด์๊ฐ Jira์์๋ ์กฐํ๋๋์ง ํ์ธ (์: GitHub ์ด์ ๋ฒํธ๋ก ๊ฒ์)
// โ createJiraIssue / editJiraIssue / transitionJiraIssue ๊ธ์ง โ ์กฐํ ๋๊ตฌ๋ง ์ฌ์ฉ
try {
const found = await mcp__atlassian__searchJiraIssuesUsingJql({
jql: `text ~ " #${issue.number} " ORDER BY created DESC`,
maxResults: 5,
});
if (found?.issues?.length) {
console.log(`๐ Jira ํ์ธ: ${found.issues.map(i = > i.key).join( " , " )}`);
} else {
console.log( " ๐ Jira ํ์ธ: ๋งค์นญ ์ด์ ์์ (์ฝ๊ธฐ ์ ์ฉ ํ์ธ, ์ํฌํ๋ก ๊ณ์) " );
}
} catch (e) {
console.warn( " ๐ Jira ์กฐํ ๊ฑด๋๋ (์ฝ๊ธฐ ์ ์ฉ, non-blocking): " , e?.message ?? e);
}Step 3-5: Pipeline Move & Branch Creation (hierarchical branch strategy)#
// โโ ํ์ดํ๋ผ์ธ ID ์ผ๊ด ํด์ (fail-closed) โโโโโโโโโโโโโโโโโโโโโโโโโ
// โ ๏ธ ๊ณผ๊ฑฐ์ productBacklogPipelineId/inProgressPipelineId/reviewQaPipelineId ๊ฐ
// ์ด๋์์๋ ํด์๋์ง ์์ move ๊ฐ pipelineId=undefined ๋ก silent no-op ์๋ค(C3).
// ์ด๋ฆ์ด ๋ผ์ด๋ธ ํ์ดํ๋ผ์ธ๊ณผ ๋ค๋ฅด๋ฉด throw โ ๋ฌด์ ๋๋ฝ ๊ธ์ง.
const workspace = await mcp__zenhub__getWorkspacePipelinesAndRepositories();
const pipelineId = (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.id;
};
const productBacklogPipelineId = pipelineId( " Product Backlog " );
const inProgressPipelineId = pipelineId( " In Progress " );
const reviewQaPipelineId = pipelineId( " Review/QA " );
// Product Backlog ์ด๋
await mcp__zenhub__moveIssueToPipeline({
issueId: issue.id,
pipelineId: productBacklogPipelineId,
});
// โโ Step 2.5: ์คํ๋ฆฐํธ ๋ฐฐ์ (ํ์๋ผ์ธ ๊ฐ์์ฑ) โโโโโโโโโโโโโโโโโโโโโ
// ์ ์์ค ํ์
(Feature/Bug/Task)์ setDatesForIssue(์์ ํ์
์ ์ฉ)๋ฅผ ์ฐ์ง ์๊ณ
// ํ์ฑ ์คํ๋ฆฐํธ์ ๋ฃ์ด ๋ก๋๋งต/๋ฒ๋ค์ด์ ๋
ธ์ถํ๋ค. ๊ธฐ๋ณธ on, --no-sprint ๋ก opt-out.
if (!options.noSprint) {
// --sprint ์
๋ ํฐ: next โ ๋ค์(getUpcomingSprint), ๊ทธ ์ธ(๊ธฐ๋ณธ current) โ ํ์ฑ(getSprint)
const sprint = options.sprint === " next "
? await mcp__zenhub__getUpcomingSprint()
: await mcp__zenhub__getSprint(); // id ์์ = ํ์ฑ ์คํ๋ฆฐํธ
if (sprint?.id) {
await mcp__zenhub__addIssuesToSprints({ issueIds: [issue.id], sprintIds: [sprint.id] });
console.log(`๐
์คํ๋ฆฐํธ ๋ฐฐ์ : ${sprint.name ?? sprint.id}`);
} else {
console.warn( " โ ๏ธ ํ์ฑ ์คํ๋ฆฐํธ ์์ โ ์คํ๋ฆฐํธ ๋ฏธ๋ฐฐ์ (๋ก๋๋งต ๋น๊ฐ์ ๊ฐ๋ฅ) " );
}
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Step 4: ๊ณ์ธต ๋ธ๋์น ์ ๋ต - base ๋ธ๋์น ๋์ ๊ฒฐ์
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// 4-1. ๋ถ๋ชจ ์ด์ ๊ฐ์ง
let baseBranch = options.base || " development " ;
let parentIssue = null;
if (!options.base) {
// โ ๏ธ ZenHub MCP ์๋ id ๋ก ๋จ๊ฑด ์ด์๋ฅผ ๊ฐ์ ธ์ค๋ ๋๊ตฌ๊ฐ ์๋ค(getIssue/getChildrenOfParent ๋ฏธ์กด์ฌ).
// ์ด์์ ๊ทธ ๋ถ๋ชจ๋ searchLatestIssues ์ nested parentIssue ๋ก ์กฐํํ๋ค.
const issueDetails = await mcp__zenhub__searchLatestIssues({
query: `#${issue.number}`,
});
const self = issueDetails.find(i = > i.number === issue.number);
parentIssue = self?.parentIssue ?? null; // { id, number, title, issueType }
if (parentIssue) {
// ๋ถ๋ชจ ๋ธ๋์น๋ฅผ ํ๋ณด(์์ผ๋ฉด ์กฐ๋ถ๋ชจ ๊ธฐ๋ฐ์ผ๋ก ์ฌ๊ท ์์ฑ)ํ๊ณ ๊ทธ ์์์ ๋ถ๊ธฐํ๋ค.
// resolveBaseBranch ๊ฐ searchLatestIssues ๋ก ๊ณ์ธต์ ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ๋ฉฐ ๋ธ๋์น๋ฅผ ๋ณด์ฅํ๋ค.
baseBranch = await resolveBaseBranch(parentIssue.number);
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(issueNumber: number): Promise < string > {
// โ ๏ธ getIssue ๋ฏธ์กด์ฌ โ searchLatestIssues ๋ก ์ด์ + nested parentIssue ์กฐํ
const _found = await mcp__zenhub__searchLatestIssues({ query: `#${issueNumber}` });
const issue = _found.find(i = > i.number === issueNumber);
if (!issue) throw new Error(`์ด์ #${issueNumber} ์กฐํ ์คํจ โ base ๋ธ๋์น ํด์ ๋ถ๊ฐ`);
// ๊ณ์ธต ๋ธ๋์น prefix (branch-hierarchy ์คํฌ ๊ธฐ์ค):
// Epic โ epic/ ยท Sub-task โ task/ ยท parent ์๋ Feature/Bug/Task(=Story) โ story/
// parent ์๋ ๋จ๋
์ด์ โ ๊ธฐ์กด feature/ (๊ธฐ์กด ๋์ ์ ์ง, ์ด ํจ์๋ ๊ณ์ธต ์ผ์ด์ค ์ ์ฉ)
const branchPrefix =
issue.issueType === " Epic " ? " epic " :
issue.issueType === " Sub-task " ? " task " :
issue.parentIssue ? " story " :
" feature " ;
// createSlug romanizes non-ASCII and never returns ' ' (see issue-branch-agent.md). Pass type word for the empty-guard fallback.
const slug = createSlug(issue.title, branchPrefix);
const branchName = `${branchPrefix}/${issue.number}-${slug}`;
// Defense-in-depth (LANG-05): a non-romanized Korean-only title must not yield " feature/25- " .
if (!slug || branchName.endsWith( " - " )) throw new Error(`๋น slug โ ๋ธ๋์น๋ช
๋น์ ์(${branchName}). ์ ๋ชฉ ๋ก๋ง์ ๋ณํ ํ์ธ ํ์`);
// ๋ธ๋์น ์กด์ฌ ํ์ธ
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.parentIssue?.number) {
const parentBranch = await resolveBaseBranch(issue.parentIssue.number);
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) {
// โ ๏ธ bdd-scenario-agent ๋ cc-flutter-dev ๊ฐ ์ ๊ณตํ๋ cross-plugin ์ ํ ์์กด์ด๋ค(F3).
// ๋ฏธ์ค์น๋ฉด throw ํ์ง ๋ง๊ณ BDD ๋จ๊ณ๋ฅผ skip + ๊ฒฝ๊ณ ํ๋ผ(๋ฏธ์กด์ฌ dispatch ๋ฅผ PASS ๋ก ๊ฐ์ฃผ ๊ธ์ง):
// if (!isAgentAvailable( " bdd-scenario-agent " )) { console.warn( " โ ๏ธ BDD skip โ cc-flutter-dev ๋ฏธ์ค์น " ); /* skip BDD */ }
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) โ ํ
์คํธ ์ฝ๋ ์ปดํ์ผ ๊ฒ์ฆ
// FO-10: ํ์ดํ `| grep -c` ๋ grep ์ exit code ๋ง ๋จ๊ฒจ dart analyze ์ ์คํจ(๋ฏธ์ค์น/ํฌ๋์)๋ฅผ
// 0( " clean " )์ผ๋ก ๊ฐ๋ฆฐ๋ค. analyze ๋ฅผ ์ง์ ์คํํด exit code ๋ก ํ์ ํ๊ณ ,
// ๋ช
๋ น ๋ฏธ์คํ/๋น์ ์ ์ข
๋ฃ๋ 0 ์ด ์๋๋ผ ๋ช
์ FAIL ๋ก ๋๋ค(๊ฒ์ฆ ๋ถ๊ฐ โ ํต๊ณผ).
const analyzeRaw = await Bash(
`dart analyze ${testPlan.map(t = > t.file).join( ' ' )} 2 > & 1`
);
if (analyzeRaw.exitCode === 127 /* command not found */) {
throw new Error( " dart analyze ์คํ ์คํจ(๋๊ตฌ/์คํฌ๋ฆฝํธ ๋ถ์ฌ) โ ๊ฒ์ฆ ๋ถ๊ฐ = ์ฐจ๋จ (Step 0 ๋๊ตฌ ์ ๊ฒ ์ฐธ์กฐ) " );
}
// dart analyze: exit 0 = ์ด์ ์์, ๊ทธ ์ธ = ์ด์ ์กด์ฌ(๋๋ ์คํ ์ค๋ฅ โ ์ฐจ๋จ)
const analyzeErrors = analyzeRaw.exitCode === 0
? 0
: ((analyzeRaw.stdout.match(/^\s*error\b/gim) || []).length || 1);
// 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}๊ฑด ์คํจ - ์์ ํ ์ฌ์๋`);
}
// โ ๏ธ ๊ฒ์ดํธ(FO-04): ์คํ๋์ง ์์(๋ฏธ๊ฒ์ฆ) ํ
์คํธ๋ PASS ๋ก ๊ฐ์ฃผํ์ง ์๋๋ค.
// testPass === null ์ " ์คํจ ์๋ " ์ด ์๋๋ผ " ๊ฒ์ฆ ์ ๋จ " โ ์ฐจ๋จ ์ํ.
const unverified = prBodyExtras.testPlanResults.filter(r = > r.testPass === null);
if (unverified.length > 0) {
throw new Error(`ํ
์คํธ ๋ฏธ์คํ ${unverified.length}๊ฑด โ ๊ฒ์ฆ๋์ง ์์ ํญ๋ชฉ์ PASS ๋ก ๊ฐ์ฃผํ์ง ์์(์ฐจ๋จ)`);
}
} else {
// --skip-tests ์ฌ์ฉ ์ ์ฌ์ฉ์ ํ์ธ ํ์
const confirm = await AskUserQuestion({
questions: [{
header: " ํ
์คํธ ์คํต ํ์ธ " ,
question: " ํ
์คํธ๋ฅผ ์คํตํ๋ฉด ๊ฒ์ฆ ์์ด PR์ด ์์ฑ๋ฉ๋๋ค. ๊ณ์ํ์๊ฒ ์ต๋๊น? " ,
options: [
{ label: " ์คํต ํ์ธ " , description: " ํ
์คํธ ์์ด ์งํ (๊ธด๊ธ ํซํฝ์ค์ฉ) " },
{ label: " ํ
์คํธ ์คํ " , description: " ํ
์คํธ๋ฅผ ์คํํฉ๋๋ค " },
],
}],
});
}Step 7.2: CoUI Package Change Separation (on CoUI component modification/extension) โ ๏ธ#
ํ๋ฉด ๊ตฌํ(Step 7) ์ค CoUI ์ปดํฌ๋ํธ ์์ฒด์ ์์ /ํ์ฅ์ด ํ์ํด์ง๋ ๊ฒฝ์ฐ์ ๋ถ๋ฆฌ ์ ์ฐจ. CoUI๋ ์ฌ๋ฌ ํ๋ก์ ํธ๊ฐ ๊ณต์ ํ๋ฏ๋ก CoUI ์์ค ๋ณ๊ฒฝ์ ํ๋ฉด PR์ ์ ๋ ์์ง ์๋๋ค.
๊ฐ์ง ์กฐ๊ฑด (composition ladder ์ ํ ํ์):
1. CoUI ์ปดํฌ๋ํธ as-is ์ฌ์ฉ โ ํด๊ฒฐ๋๋ฉด Step 7.2 ๋ถํ์
2. Core < X > Style / typed param ์ค๋ฒ๋ผ์ด๋ โ ํด๊ฒฐ๋๋ฉด Step 7.2 ๋ถํ์
3. Composition (CoUI behavior + ์ปค์คํ
content) โ ํด๊ฒฐ๋๋ฉด Step 7.2 ๋ถํ์ (ํ๋ก์ ํธ ๋ด ํด๊ฒฐ)
4. CoUI ํ์ฅ ํ์ ํ์ โ Step 7.2 ์ง์
โ ๏ธladder 1~3์ ์ ๋ถ ํ๋ก์ ํธ ์์์ ํด๊ฒฐ๋๋ฉฐ CoUI PR์ด ํ์ ์๋ค. rung 4 ํ์ ์์๋ง ๋ถ๋ฆฌ ์ ์ฐจ๋ฅผ ๋ฐ๋๋ค. ์์ธ:
coui-composition-and-extension์คํฌ.
๋ถ๋ฆฌ ์ ์ฐจ:
// Step 7 ๊ตฌํ ์ค CoUI ํ์ฅ ํ์ ๊ฐ์ง ์
if (needsCouiExtension) {
// 1. ํ๋ฉด ์์
์ผ์ ์ ์ง โ composition์ผ๋ก ์งํ ๊ฐ๋ฅํ ๋ถ๋ถ์ ๊ณ์
// (CoUI ์ ๊ท capability์ ๋งํ ๋ถ๋ถ๋ง ๋ณด๋ฅ)
// 2. CoUI ์ ์ฅ์(coco-de/coui)์์ ๋ณ๋ dev ์ฌ์ดํด ์คํ
// - ๋ณ๋ ์ด์/๋ธ๋์น/PR (ํ๋ฉด ์ด์์ ์ํธ ๋งํฌ)
// - additive ยท backward-compatible ํ์ฅ๋ง (๊ธฐ์กด ๊ธฐ๋ณธ๊ฐ/๋์ ๋ณด์กด)
// - โ ๏ธ coui_core ๊ณ์ฝ + coui_flutter + coui_web ์์ชฝ ๋ชจ๋ ๋ฐ์ (ํ์ชฝ๋ง ๊ธ์ง)
// - Widgetbook use-case + minor ๋ฒ์ ๋ฒํ + CHANGELOG
// 3. CoUI PR ์ ํ ๋จธ์ง (CoUI ์ ์ฅ์ ์์ฒด ๊ฒ์ดํธ/๋ฆฌ๋ทฐ ํต๊ณผ)
// 4. ํ๋ก์ ํธ๋ก ๋ณต๊ท: CoUI ์์กด์ฑ ๊ฐฑ์ ํ ํ๋ฉด ์กฐ๋ฆฝ ์ฌ๊ฐ
// - pubspec ๋ฒ์ ๋ฒํ / vendored copy ๊ฐฑ์ / submodule ํฌ์ธํฐ ์
๋ฐ์ดํธ
// - ์ปค๋ฐ: " chore(deps): ๐ง coui ๋ฒ์ ๋ฒํ (vX.Y.Z) "
// - ์ดํ ํ๋ฉด ๋ธ๋์น์์ ์ปดํฌ๋ํธ ์กฐ๋ฆฝ(assembly)๋ง ๊ณ์
}Step 9 ๊ฒ์ดํธ โ ํ๋ฉด PR์ CoUI ์์ค ์ค์ผ ์ฐจ๋จ:
# ํ๋ฉด PR diff์ CoUI ํจํค์ง ์์ค ๋ณ๊ฒฝ์ด ํฌํจ๋๋ฉด PR ์์ฑ ์ฐจ๋จ
COUI_TOUCHED=$(git diff --name-only ${BASE_BRANCH}..HEAD | grep -E ' (^|/)(packages/)?coui(_flutter|_web|_core)?/ ' || true)
if [ -n " $COUI_TOUCHED " ]; then
echo " โ ํ๋ฉด PR์ CoUI ์์ค ๋ณ๊ฒฝ ํฌํจ โ Step 7.2 ์ ์ฐจ๋ก CoUI ์ ์ฅ์ ๋ณ๋ PR๋ก ๋ถ๋ฆฌํ์ธ์ "
echo " $COUI_TOUCHED "
exit 1
fi
# ํ์ฉ: pubspec.yaml/lockfile ๋ฒ์ ๋ฒํ, submodule ํฌ์ธํฐ ์ปค๋ฐ (์์กด์ฑ ๋ฐ์)์คํต ์ ๋ฌธ์ :
- CoUI ์์ฒด ๋ฆฌ๋ทฐ/ํ์ํธํ ๊ฒ์ดํธ๋ฅผ ์ฐํ โ ๋ค๋ฅธ ์๋น ํ๋ก์ ํธ๊ฐ ์กฐ์ฉํ ๊นจ์ง
coui_flutter๋ง ๊ณ ์น๊ณcoui_web์ ๋น ๋จ๋ฆฌ๋ฉด(๋๋ ๋ฐ๋) ํฌ๋ก์คํ๋ซํผ API ๊ณ์ฝ desync- ํ๋ฉด PR๊ณผ ๋์์ธ์์คํ ๋ณ๊ฒฝ์ด ๊ฒฐํฉ๋์ด ๋ ๋ฆฝ ๋จธ์ง/๋ฆฌ๋ฒํธ ๋ถ๊ฐ
Step 7.3: Design Verification Gate (Figma โ Runtime) โ ๏ธ#
๋ฐฐ๊ฒฝ: ์ง๊ธ๊น์ง ํ๋ฉด ๊ธฐ๋ฅ์
/figma:analyze๊ฐ ๋ฝ์ ์ ์ ํ ์คํธ/ํ ํฐ ์คํ๋ง ๋ณด๊ณ ๊ตฌํ๋ ๋ค ๊ณง์ฅ ํ ์คํธ/๋ฆฐํธ/๋ฆฌ๋ทฐ๋ก ๋์ด๊ฐ๋ค โ ์๋ฎฌ๋ ์ดํฐยท์ค๊ธฐ๊ธฐ์ ์ค์ ๋ก ์ฑ์ ๋์ Figma ์์๊ณผ ํฝ์ ๋จ์๋ก ๋น๊ตํ๋ ๋จ๊ณ๊ฐ ์ด๋์๋ ์์๋ค. ์ด ๋จ๊ณ๊ฐ "๋งค๋ฒ ๋์ถฉ ๋ง๋ค์ด ๋์์ธ์ด ๋ฌ๋ผ์ง๋ค"๋ ๋ฌธ์ ์ ์ง์ ์ ์ธ ์์ธ์ด์๋ค. Step 7.3์ ์ด๋ฏธ ์กด์ฌํ๋ 4-layer ๊ฒ์ฆ ์คํ(cc-flutter-pixel-loop๊ฐ native-external/Maestro ๋ ์ด์ด) ์ค ์ ์(pixel-loop, ์ฝ๋๋ฅผ ์ค์ ๋ก ๊ณ ์นจ)๊ณผ ๊ฒ์ดํธ(visual-verify, ์ฝ๊ธฐ ์ ์ฉ ํ์ )๋ฅผ ์ด ์ํฌํ๋ก์ ์ฒ์์ผ๋ก ์ฐ๊ฒฐํ๋ค.Step 7.1(Design Reference,
cc-designer-skills)๊ณผ๋ ๋ค๋ฅธ ์ธต์๋ค: Step 7.1์ ํ๋ฉด์ "์์ฑํ๋ ๋์" ์์ยทํ์ดํฌยท๋ชจ์ ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ์ฐธ๊ณ ํ๋ ๋น์ฐจ๋จ ์ฐธ๊ณ ๋จ๊ณ์ด๊ณ , Step 7.3์ ํ๋ฉด์ "๋ค ์์ฑํ ๋ค" ์ค์ ์ค๊ธฐ๊ธฐ ๋ ๋๋ฅผ Figma ์์๊ณผ ํฝ์ ๋จ์๋ก ๋์กฐํ๋ ์ฐจ๋จ ๊ฒ์ฆ ๊ฒ์ดํธ๋ค. ๋์ ๊ฒฝ์ํ์ง ์๊ณ ๋ณด์ํ๋ค.
๊ฐ์ง ์กฐ๊ฑด: analysis.requiresBdd(screen feature)์ด๋ฉด์ .claude/docs/${analysis.scope}/figma_analysis.md์ Figma URL์ด ์์ ๋๋ง ์คํ. ํ๋ฉด ๊ธฐ๋ฅ์ธ๋ฐ Figma ์์ค ์์ฒด๊ฐ ์์ผ๋ฉด(์์ ํ
์คํธ ์ค๋ช
) ๋น๊ต ๋์์ด ์์ผ๋ฏ๋ก not-applicable๋ก ์กฐ์ฉํ skip.
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Step 7.3: ๋์์ธ ๊ฒ์ฆ ๊ฒ์ดํธ (Figma โ ์ค๊ธฐ๊ธฐ/์๋ฎฌ๋ ์ดํฐ)
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const figmaDocPath = `.claude/docs/${analysis.scope}/figma_analysis.md`;
const figmaDocExists = (await Bash(`test -f " ${figmaDocPath} " & & echo yes || echo no`)).trim() === " yes " ;
const hasDesignSource = analysis.requiresBdd & & figmaDocExists;
if (!hasDesignSource) {
console.log( " โน๏ธ Step 7.3 skip โ screen feature ์๋ ๋๋ Figma ์์ค ์์ (๋์กฐ ๋์ ์์) " );
} else if (analysis.platform === " web " ) {
// Maestro๋ Flutter Web ๋ฏธ์ง์ โ cc-flutter-dev:figma:analyze ์ Playwright ํด๋ฐฑ ๊ฒฝ๋ก ์๋ด
console.warn( " โ ๏ธ Flutter Web ๋์ โ pixel-loop/visual-verify ๋ฏธ์ง์. Playwright ํด๋ฐฑ์ /figma:analyze ์ฐธ๊ณ " );
prBodyExtras.designVerification = { status: " skipped " , reason: " flutter-web (see /figma:analyze Playwright fallback) " };
} else {
// โโ 1. ๋๊ตฌ/๊ธฐ๊ธฐ ๊ฐ์ฉ์ฑ ํ์ธ (Step 0 capability map ํ์ฅ๋ถ ์ฐธ์กฐ) โโ
// โ ๏ธ Step 0์ " ๋ฏธ์ค์นโdegrade+๊ฒฝ๊ณ (๋ฌด์) " ๊ธฐ๋ณธ ๊ณ์ฝ์ ์์ธ: ์ฌ๊ธฐ์๋ ๋ฌด์ ์คํต ๊ธ์ง.
const designToolingOk =
capabilityMap.figmaMcp & & capabilityMap.maestro & & capabilityMap.dartMcp & & capabilityMap.device;
if (!designToolingOk || options.skipDesignVerify) {
const confirm = await AskUserQuestion({
questions: [{
header: " ๋์์ธ ๊ฒ์ฆ ์คํต ํ์ธ " ,
question: !designToolingOk
? " figma/maestro/dart MCP ๋๋ ์๋ฎฌ๋ ์ดํฐยท๊ธฐ๊ธฐ๊ฐ ์ค๋น๋์ง ์์์ต๋๋ค. ๋์์ธ ๊ฒ์ฆ ์์ด ์งํํ ๊น์? "
: " ํ๋ฉด ์ค๊ธฐ๊ธฐ ๊ฒ์ฆ ์์ด ์งํํ๋ฉด ๋์์ธ ๋๋ฆฌํํธ๊ฐ PR์ ๊ทธ๋๋ก ์ค๋ฆด ์ ์์ต๋๋ค. ๊ณ์ํ ๊น์? " ,
options: [
{ label: " ์คํต ํ์ธ " , description: " ๋์์ด๋ ์๋ ํ์ธ ํ์ โ PR์ ๊ฒฝ๊ณ ์๊ตฌ ๊ธฐ๋ก " },
{ label: " ๊ฒ์ฆ ์คํ " , description: " ์ง๊ธ ๋๊ตฌ๋ฅผ ๊ฐ์ถ๊ณ ๊ฒ์ฆ์ ์คํํฉ๋๋ค " },
],
}],
});
if (confirm !== " ๊ฒ์ฆ ์คํ " ) {
console.warn( " โ ๏ธ Step 7.3 ์คํต ํ์ธ๋จ โ PR body์ ' ๋์์ธ ๊ฒ์ฆ ๋ฏธ์คํ ' ๊ฒฝ๊ณ ๊ธฐ๋ก " );
prBodyExtras.designVerification = {
status: " skipped " ,
reason: !designToolingOk ? " tooling/device unavailable " : " --skip-design-verify (user confirmed) " ,
};
}
}
if (!prBodyExtras.designVerification) {
// โโ 2. ์ฑ ๊ธฐ๋ โ ๋ณ๋ ํ๋ก์ธ์ค (Step 7.7์ serverpod ๋ก์ปฌ ์ฑ๊ณผ ๊ณต์ ํ์ง ์์, 7.3์ด 7.7๋ณด๋ค ๋จผ์ ์คํ) โโ
const target = options.designTarget || " auto " ;
const boot = await Bash(`flutter run -d ${target} --print-dtd 2 > & 1 & `, { run_in_background: true });
const dtdUri = extractDtdUri(boot.stdout); // " ws://127.0.0.1:PORT/token " ํจํด ํ์ฑ
const figmaUrl = extractFigmaUrl(await Read(figmaDocPath)); // pixel-loop-handoff ์ ๋์ผ ์ถ์ถ ๊ท์น, ์ฌ๊ตฌํ ๊ธ์ง
const resolvedRoute = resolveRouteForScope(analysis.scope); // lib/router.dart TypedRoute ๋๋ *_route.dart ๋งค์นญ
const mode = isReworkPass ? " repair " : " create " ;
const tolerance =
options.designTolerance || (mode === " create " ? " pixel=2,color_delta_e=3 " : " pixel=1,color_delta_e=2 " );
// โโ 3. (์ ํยท๋น์ฐจ๋จ) Marionette ์ค๋ชจํฌ โ " ๋ณด์ด์ง๋ง ์ ๋๋ฆฌ๋ " ๋ฒ๊ทธ๋ฅผ ์กฐ๊ธฐ ํฌ์ฐฉ โโ
await Skill({ skill: " cc-flutter-marionette:marionette:smoke " , args: `--routes=${resolvedRoute}` })
.catch(e = > console.warn(`โ ๏ธ marionette smoke ์คํจ(informational, ๋น์ฐจ๋จ): ${e?.message ?? e}`));
// โโ 4. pixel-loop โ ์ค์ ์ ์/์๋ ด (์ฝ๋๋ฅผ ๊ณ ์นจ) โโ
await Skill({
skill: " cc-flutter-pixel-loop:pixel-loop:loop " ,
args: `${figmaUrl} --mode=${mode} --route=${resolvedRoute} --target=${target} --tolerance ${tolerance}`,
});
// โโ 5. visual-verify โ ์ฝ๊ธฐ ์ ์ฉ ์ต์ข
ํ์ (์ด ํ๋ฉด์ด ๊ฑด๋๋ฆฐ ๋ชจ๋ ๋ผ์ฐํธ) โโ
let verify = await Skill({
skill: " cc-flutter-inspector:visual-verify " ,
args: `--figma=${figmaUrl} --routes=${touchedRoutesForScope(analysis.scope).join( " , " )} --mode=regression --target=${target} --tolerance ${tolerance}`,
});
if (!verify.pass) {
// 1ํ repair ์ฌ์๋ (pixel-loop ์์ฒด ์ง๋-๊ฐ์ง์ ๋ณ๊ฐ๋ก, ์ฌ๊ธฐ์๋ ๋ฌด์ ํต๊ณผ๋ ๊ธ์ง)
await Skill({
skill: " cc-flutter-pixel-loop:pixel-loop:loop " ,
args: `${figmaUrl} --mode=repair --route=${resolvedRoute} --target=${target} --tolerance ${tolerance}`,
});
verify = await Skill({
skill: " cc-flutter-inspector:visual-verify " ,
args: `--figma=${figmaUrl} --routes=${touchedRoutesForScope(analysis.scope).join( " , " )} --mode=regression --target=${target} --tolerance ${tolerance}`,
});
}
await Bash(`kill %1 2 > /dev/null || true`); // flutter run ํ๋ก์ธ์ค ์ข
๋ฃ
if (!verify.pass) {
// โ ๏ธ FO-04์ ๋์ผ ์์น: ๋ฏธํด๊ฒฐ diff๋ฅผ ๋ฌด์์ผ๋ก ํต๊ณผ์ํค์ง ์๋๋ค
const decision = await AskUserQuestion({
questions: [{
header: " ๋์์ธ ๊ฒ์ฆ ์คํจ " ,
question: `์ฌ์๋ ํ์๋ ๋์์ธ ๋ถ์ผ์น๊ฐ ๋จ์ ์์ต๋๋ค(${verify.diffCount}๊ฑด). ์ด๋ป๊ฒ ํ ๊น์?`,
options: [
{ label: " Step 7๋ก ๋ณต๊ท " , description: " ์ฝ๋๋ฅผ ๋ ๊ณ ์น ํ ์ฌ์๋ (๊ถ์ฅ) " },
{ label: " ๊ทธ๋๋ก ์งํ " , description: " ๋์์ด๋ ์๋ ํ์ธ ํ์ โ PR์ ๋ฏธํด๊ฒฐ๋ก ๊ธฐ๋ก " },
],
}],
});
if (decision !== " ๊ทธ๋๋ก ์งํ " ) {
throw new Error(`๋์์ธ ๊ฒ์ฆ ์คํจ โ ์์กด diff ${verify.diffCount}๊ฑด, Step 7 ์ฌ์์
ํ์`);
}
prBodyExtras.designVerification = { status: " fail-acknowledged " , tolerance, target, ...verify };
} else {
prBodyExtras.designVerification = { status: " pass " , tolerance, target, ...verify };
}
}
}
console.log(`โ
Step 7.3 complete โ ${prBodyExtras.designVerification?.status ?? " not-applicable " }`);โป๏ธ Per-push ์ฌ์คํ ์ ์ฑ (Step 8.5์ ๋ค๋ฆ): Step 7.3์ Step 8.5(๋ฆฐํธ ๊ฒ์ดํธ)์ฒ๋ผ ๋งค push๋ง๋ค ์ ์ฒด๋ฅผ ๋ค์ ๋๋ฆฌ์ง ์๋๋ค โ ๊ธฐ๊ธฐ ๋ถํ + Figma/Maestro MCP ์๋ณต ๋น์ฉ์ด ํฌ๊ธฐ ๋๋ฌธ. ๋์ Step 11 ๋ฆฌ๋ทฐ ํผ๋๋ฐฑ push๊ฐ ํ๋ ์ ํ ์ด์ ๋ ์ด์ด ํ์ผ(
**/*_page.dart,**/*_widget.dart,**/*_view.dart,**/*_screen.dart,**/presentation/**/*.dartโcc-flutter-dev/rules/dcm-flutter.md์ ์ด๋ฏธ ์ ์๋ glob ๊ทธ๋๋ก ์ฌ์ฌ์ฉ)์ ๊ฑด๋๋ฆด ๋๋งcc-flutter-inspector:visual-verify(์ฝ๊ธฐ ์ ์ฉ)๋ง ๊ฐ๋ณ๊ฒ ์ฌ์คํํ๊ณ , ํ๊ท๊ฐ ๋ฐ๊ฒฌ๋ ๋๋งcc-flutter-pixel-loop:pixel-loop:loop --mode=repair1ํ๋ก ์์ค์ปฌ๋ ์ด์ ํ๋ค.
์คํต/์คํจ ์ ๋ฌธ์ :
- ๋ฌด์์ผ๋ก ๊ฑด๋๋ฐ๋ฉด ํ๋ฉด์ด ๋์์ธ๊ณผ ๋ค๋ฅด๊ฒ ๋๊ฐ๋ ์ง๊ธ๊น์ง์ ๋ฌธ์ ๊ฐ ๊ทธ๋๋ก ๋ฐ๋ณต๋๋ค
- ๋์์ด๋๊ฐ PR์์ ํ์ธํ ๊ฐ๊ด์ ์ฆ๋น(์คํฌ๋ฆฐ์ทยทdiff ๋ฆฌํฌํธ)์ด ์์ผ๋ฉด "๋๋ค"๋ ์ฃผ์ฅ๋ง ๋จ๊ณ ์ค์ ๊ฒ์ฆ์ ์ ๋จ
- Figma ์์ค๊ฐ ์๋๋ฐ
prBodyExtras.designVerification๊ฐ ๋น์ด ์์ผ๋ฉด Step 9 ๊ฒ์ดํธ์์ ์ฐจ๋จ๋จ (์๋ Verification Gates ์ฐธ๊ณ )
Step 7.7: Local Full-Stack Integration Verification (Serverpod 4 / ๋ก์ปฌ ํ์คํ)#
์กฐ๊ฑด๋ถ ์คํ: Backend ๋ณ๊ฒฝ(
hasBackendChanges) ๋๋ screen feature(analysis.requiresBdd)์ผ ๋๋ง ์คํ. ๊ธฐ๋ณธ on,--skip-local-integration์ต์ ์ผ๋ก ์คํต ๊ฐ๋ฅ. ๊ฒ์ดํธ ์คํจ ์ Step 8 ์งํ ์ฐจ๋จ. ๋ฐฑ์๋ ํตํฉ/E2E ๊ฒ์ฆ์ ๊ธฐ๋ณธ ๋ฐฑ์๋ = ๋ก์ปฌ ํ์คํ(serverpod start, ๋ด์ฅ Postgres). Staging์ ํด๋ฐฑ. ์์ธ ๊ฐ์ด๋:serverpod-local-fullstack,serverpod-mcp-guide์คํฌ ์ฐธ์กฐ.
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Step 7.7: ๋ก์ปฌ ํ์คํ ํตํฉ ๊ฒ์ฆ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Serverpod 4 (tech preview): ' serverpod start ' ํ๋๋ก ๋ฐฑ์๋ ์๋ฒ +
// ๋ด์ฅ Postgres + Flutter ์ฑ์ ํจ๊ป ๊ธฐ๋. Docker/docker-compose ๋ถํ์.
// ์ฌ์ปดํ์ผยท์ฌ์์ ์์ด ์๋ฒยทDBยท์นยท์ฑ ์ ์ฒด stateful ํซ๋ฆฌ๋ก๋.
// (kobic ํ๋ก๋์
์ 3.x โ ์๋ 3.x ํด๋ฐฑ ๊ฒฝ๋ก ์ฐธ์กฐ)
const needsLocalIntegration = hasBackendChanges || analysis.requiresBdd;
if (needsLocalIntegration & & !options.skipLocalIntegration) {
// 7.7-1. ๋ก์ปฌ ํ์คํ ๊ธฐ๋ (๋ฐฑ๊ทธ๋ผ์ด๋)
// Serverpod 4: ๋ด์ฅ Postgres ํฌํจ โ ์์ ๋ก์ปฌ, Docker ๋ถํ์
// ํซ๋ฆฌ๋ก๋๊ฐ ๋งํ๋ฉด ' serverpod start ' ํฐ๋ฏธ๋์์ R ํค๋ก ์๋ฒ+์ฑ ๊ฐ์ ์ฌ์์
await Bash(`serverpod start`, { run_in_background: true });
// โ ์๋ฒ ๋ถํ
๋๊ธฐ (http://localhost:8080/ ํฌ์ค ํ์ธ)
// โโ 3.x / legacy ํด๋ฐฑ (๋ก์ปฌ ๋ถ๊ฐ / nightly) โโโโโโโโโโโโโโ
// v3 ๊ฒฝ๋ก๋ ๊ทธ๋๋ก ๋ณด์กด:
// docker compose up -d # Postgres ๋ฑ ์์กด ์๋น์ค ๊ธฐ๋
// dart run bin/main.dart # ๋ฐฑ์๋ ์๋ฒ ๊ธฐ๋
// Staging ๋ฐฑ์๋๋ ๋ก์ปฌ ํ์คํ ๋ถ๊ฐ ์ ํด๋ฐฑ์ผ๋ก ์ฌ์ฉ.
let localIntegrationPass = true;
// 7.7-2. [Backend ๋ณ๊ฒฝ ์] ๋ก์ปฌ ํตํฉ ํ
์คํธ
// withServerpod ํฌํผ + ' dart test -t integration ' (v3์ ๋์ผ API).
// Serverpod 4 ๋ด์ฅ Postgres ๋๋ถ์ Docker ์์ด ์์ ๋ก์ปฌ ์คํ.
if (hasBackendChanges) {
const integrationOut = await Bash(`dart test -t integration 2 > & 1`);
const integrationResult = parseTestOutput(integrationOut.stdout);
if (integrationResult.totalFailed > 0) localIntegrationPass = false;
prBodyExtras.localIntegration = integrationResult;
}
// 7.7-3. [screen feature ์] ํ๋ก ํธ๋ฅผ ๋ก์ปฌ ๋ฐฑ์๋ ๋์์ผ๋ก ํตํฉ ์ค๋ชจํฌ
// ์ฑ์ dev flavor์์ ๋ก์ปฌ ์๋ฒ(http://localhost:8080/)๋ก ์ฐ๊ฒฐ.
// Staging์ ํด๋ฐฑ. ์ฑ์ ๊ธฐ์กด client/OpenApiService/flavor ์ค์ ์ ๋ง์ถค
// โ base-URL ํ๋์ฝ๋ฉ ๊ธ์ง.
if (analysis.requiresBdd) {
const smokeOut = await Bash(
`flutter test integration_test --dart-define=flavor=dev 2 > & 1`
);
const smokeResult = parseTestOutput(smokeOut.stdout);
if (smokeResult.totalFailed > 0) localIntegrationPass = false;
prBodyExtras.localSmoke = smokeResult;
}
// 7.7-4. ๋ง์ด๊ทธ๋ ์ด์
/ ์๋ฒ ๋ก๊ทธ๋ Serverpod MCP ํ์ฉ
// Serverpod MCP ์๋ฒ(๋ฒ๋ค)๋ก ์คํ ์ค ๋ก์ปฌ ์๋ฒ์ ์ฐ๊ฒฐ โ
// DB ๋ง์ด๊ทธ๋ ์ด์
์์ฑยท์ ์ฉ, ์๋ฒ ๋ก๊ทธ ์ฝ๊ธฐ, ์ฑ ๊ตฌ๋+์คํฌ๋ฆฐ์ท ๊ฒํ .
// (tool ID๋ preview๋ผ ๋ณ๋ ๊ฐ๋ฅ โ ๋ฅ๋ ฅ ๊ธฐ์ค์ผ๋ก ์ฌ์ฉ. serverpod-mcp-guide ์ฐธ์กฐ)
// 7.7-5. ๊ฒ์ฆ ํ ๋ก์ปฌ ํ์คํ ์ข
๋ฃ
// ' serverpod start ' ํ๋ก์ธ์ค ์ข
๋ฃ (3.x ํด๋ฐฑ ์ docker compose down)
await stopLocalFullstack();
// โ ๏ธ ๊ฒ์ดํธ: ๋ก์ปฌ ํตํฉ ๊ฒ์ฆ ์คํจ ์ Step 8 ์งํ ์ฐจ๋จ
if (!localIntegrationPass) {
throw new Error(
" ๋ก์ปฌ ํ์คํ ํตํฉ ๊ฒ์ฆ ์คํจ - ์์ ํ ์ฌ์๋ " +
" (๋๋ --skip-local-integration ์ผ๋ก ์คํต) "
);
}
console.log(`โ
Step 7.7 complete โ ๋ก์ปฌ ํ์คํ ํตํฉ ๊ฒ์ฆ PASS`);
} else if (options.skipLocalIntegration) {
console.warn(`โ ๏ธ ๋ก์ปฌ ํ์คํ ํตํฉ ๊ฒ์ฆ ์คํต (--skip-local-integration)`);
} else {
console.log(`โน๏ธ Step 7.7 skip โ Backend ๋ณ๊ฒฝ/screen feature ์๋`);
}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) {
// FO-03: screen feature(BDD ํ์)์ธ๋ฐ .feature ๊ฐ 0๊ฐ๋ฉด " ์คํต " ์ด ์๋๋ผ FAIL โ ๊ฒ์ฆ ๋ถ๊ฐ โ ํต๊ณผ.
if (analysis.requiresBdd) {
throw new Error(`BDD Coverage Gate failed: screen feature ์ธ๋ฐ .feature ํ์ผ์ด 0๊ฐ (๊ฒ์ฆ ๋ถ๊ฐ โ ํต๊ณผ). /bdd:generate ${scope} ๋ก ์์ฑ`);
}
console.warn(`โ ๏ธ .feature ํ์ผ ์์ (${featureDir}) โ BDD ๋น๋์ scope, gate not-applicable`);
} 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);
});
// FO-03: BDD ํ์ scope ์์ step 0๊ฐ๋ " 100% ์ปค๋ฒ๋ฆฌ์ง " ๊ฐ ์๋๋ผ FAIL(๊ฒ์ฆํ ๊ฒ ์์ โ ํต๊ณผ).
const stepCoverage = allSteps.length > 0
? (allSteps.length - missingSteps.length) / allSteps.length
: (analysis.requiresBdd ? 0.0 : 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#
โป๏ธ Per-Push Verification Gate (์ฌ์ฌ์ฉ): ์๋ ๊ฒ์ฆ ์ํ์ค๋ฅผ
runPrePushGate()๋ผ ๋ถ๋ฅธ๋ค. ์ต์ด push(Step 9 ์ง์ ) 1ํ๋ก ๋๋์ง ์๊ณ , PR ๋ธ๋์น๋ก push๊ฐ ๋ฐ์ํ ๋๋ง๋ค โ Step 11 ์ถ๊ฐ ๋ฆฌ๋ทฐ ํผ๋๋ฐฑ ๋ฐ์,changes requested์ฌ์์ , CI ์คํจ ์์ , conflict ํด์ ํ--force-with-leasere-push ํฌํจ โ push ์ง์ ์ ๋์ผํ๊ฒ ์ฌ์คํํ๋ค. ๊ฒ์ดํธ ์คํจ ์ ํด๋น push๋ ์ฐจ๋จ๋๋ค (LEFTHOOK=0์ฐํ ๊ธ์ง).
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// 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 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 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 dcm:analyze 2 > & 1`);
// 2-2. ์๋ ์์ ๊ฐ๋ฅ ํญ๋ชฉ ์์
await Bash(`melos run dcm:fix`);
// 2-3. ๋ฏธ์ฌ์ฉ ์ฝ๋ ๊ฒ์ฌ
await Bash(`melos run dcm:unused-code`);
// 2-4. ์์ ํ ์ฌ๊ฒ์ฆ
const dcmVerifyResult = await Bash(`melos run dcm:analyze 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 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 dcm:analyze 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๊ฑด`);
// โป๏ธ ์ Phase 1~3 ์ ์ฒด๊ฐ runPrePushGate() โ Step 11 ๋ฑ ์ดํ์ ๋ชจ๋ push ์ง์ ์ ์ฌํธ์ถ๋๋ค.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, story/xxx, task/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}๊ฑด` : ' | ์ ์ ๋ถ์: โ
' )
: ' ' }
${prBodyExtras.designVerification ? `
## Design Verification
${prBodyExtras.designVerification.status === " pass " ? `โ
PASS โ tolerance ${prBodyExtras.designVerification.tolerance}, target ${prBodyExtras.designVerification.target}` : ' ' }
${prBodyExtras.designVerification.status === " skipped " ? `โ ๏ธ ๋์์ธ ๊ฒ์ฆ ๋ฏธ์คํ (${prBodyExtras.designVerification.reason}) โ ๋์์ด๋ ์๋ ํ์ธ ํ์` : ' ' }
${prBodyExtras.designVerification.status === " fail-acknowledged " ? `โ ๏ธ ์์กด ๋ถ์ผ์น ${prBodyExtras.designVerification.diffCount}๊ฑด โ ์ฌ์ฉ์ ํ์ธ ํ ์งํ (์๋ ๊ฒํ ํ์)` : ' ' }
${prBodyExtras.designVerification.artifactPath ? `- Artifacts: \`${prBodyExtras.designVerification.artifactPath}\`` : ' ' }
` : ' ' }
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 " });
// ํผ๋๋ฐฑ ๋ฐ์ ์ปค๋ฐ (๋ณ๊ฒฝ ์์ ๋๋ง)
const hasFeedbackChanges = await Bash(`git status --porcelain`);
if (hasFeedbackChanges.trim()) {
await Bash(`
git add .
git commit -m " refactor: โป๏ธ PR ๋ฆฌ๋ทฐ ํผ๋๋ฐฑ ๋ฐ์
Co-Authored-By: Claude < noreply@anthropic.com > "
`);
// โ ๏ธ Per-Push Verification Gate: push ์ง์ Step 8.5 ๊ฒ์ดํธ ์ ์ฒด ์ฌ์คํ
// (format โ dart fix โ analyze 0๊ฑด โ dcm:analyze error 0๊ฑด)
// PR์ด ์ด๋ฏธ ์กด์ฌํด๋ ์์ธ ์์ โ ์คํจ ์ throw๋ก push ์ฐจ๋จ
await runPrePushGate(); // = Step 8.5 Phase 1~3
await Bash(`git push`);
}
// /checklist:feature-complete ์คํ
await Skill({ skill: " checklist:feature-complete " });
}Step 12 & 12.5: Merge Approval (merge = Close) + Issue Closure Verification#
// ์ต์ข
์ํ ์์ฝ
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
// ์ ์ฑ
: " ๋จธ์ง = Close " (AI agent๊ฐ ๋จธ์ง ์ ํ์คํ E2E/๋ฆฌ๋ทฐ ์๋ฃ). Done ํ์ดํ๋ผ์ธ ๋ฏธ์ฌ์ฉ.
await Bash(`gh pr merge --squash --delete-branch`);
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Step 12.5: ์ด์ Close ๊ฒ์ฆ + ํด๋ฐฑ โ ๏ธ (ZenHub 2-์ํ ๋ชจ๋ธ)
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// ZenHub๋ ๋ ์ํ๋ฅผ ๋ฐ๋ก ๊ด๋ฆฌํ๋ค:
// - Pipeline(๋ณด๋ ์นผ๋ผ): Done์ผ๋ก ์ฎ๊ฒจ๋ GitHub ์ด์๋ open ๊ทธ๋๋ก
// - GitHub state(open/closed): Closed ํ์ดํ๋ผ์ธ = GitHub closed์ 1:1
// ๋ฐ๋ผ์ " ๋ซํ๋ค " ์ ์ง์ค์ pipeline์ด ์๋๋ผ GitHub state๋ก ํ๋จํ๋ค.
// `Closes #N` ์๋ close๊ฐ ๋๋ฝ/์ง์ฐ๋ ์ ์์ผ๋ฏ๋ก ๋จธ์ง ํ ๋ฐ๋์ ๊ฒ์ฆยทํด๋ฐฑํ๋ค.
// 12.5-1. GitHub ์ด์ ์ํ ํ์ธ (open/closed์ source of truth)
const ghState = (await Bash(`gh issue view ${issue.number} --json state -q .state`)).trim();
if (ghState !== " CLOSED " ) {
// `Closes #N`์ด ๋ฐํํ์ง ์์(ํค์๋ ๋๋ฝ/ํฌ๋ก์ค๋ฆฌํฌ ๋งํฌ ๋ฑ) โ ๋ช
์์ close
console.warn(`โ ๏ธ #${issue.number} ์๋ close ์ ๋จ โ ๋ช
์์ ์ผ๋ก closeํฉ๋๋ค`);
await Bash(`gh issue close ${issue.number} --reason completed`);
}
// 12.5-2. ZenHub๊ฐ Closed ํ์ดํ๋ผ์ธ์ผ๋ก ๋๊ธฐํ๋๋์ง ํ์ธ (GitHub close โ ์๋ ๋๊ธฐํ)
// ๋๊ธฐํ ์ง์ฐ ์ ZenHub state๋ฅผ ์ง์ CLOSED๋ก ๊ฐ์
const closedOnZh = await mcp__zenhub__searchClosedIssues({ query: `#${issue.number}` });
if (!closedOnZh.find(i = > i.number === issue.number)) {
console.warn(`โ ๏ธ ZenHub Closed ๋๊ธฐํ ์ง์ฐ โ state๋ฅผ CLOSED๋ก ๊ฐ์ `);
await mcp__zenhub__updateIssue({ issueId: issue.id, state: " CLOSED " });
}
console.log(`โ
Step 12.5 โ #${issue.number} GitHub + ZenHub ๋ชจ๋ Closed ํ์ `);
// โญ ๊ณ์ธต ์ฌ๊ท ํ์ธ: ๋ฐฉ๊ธ ๋ซํ ์ด์๊ฐ Epic์ด๋ฉด, ๊ทธ ์ Project(โ Initiative)๊น์ง
// ๋ชจ๋ ์์์ด ๋ซํ๋์ง ์ฌ๊ท ํ์ธ ํ ์๋ ์ข
๋ฃํ๋ค. Story ๋ ๋ฒจ close๋ ์ฌ๊ธฐ์
// ์ฌ๊ท๋ฅผ ํ์ง ์๋๋ค โ Epic ์์ ์ ์๋ " ๊ณ์ธต ๋ธ๋์น " ๋ธ๋ก์ ์๋ด๋ฅผ ๊ฑฐ์ณ ์ฌ๋์ด
// Epic PR์ ๋จธ์งํด์ผ ๋ซํ๋ฏ๋ก, Story close๋ง์ผ๋ก Epic์ ์์ง๋ฌ ๋ซ์ง ์๋๋ค.
if (issue.issueType === " Epic " ) {
await checkAndCloseParent(issue.number); // see commands/agents/dev/issue-state-agent.md
}
// โญ ๋จธ์ง ํ: PR base ๋ธ๋์น๋ก ์ด๋ + ์ต์ ์ํ๋ก ๋๊ธฐํ + ์๋ธ๋ชจ๋ ์ต์ ํ (ํ์)
// - gh pr merge --delete-branch ๋ base ๋ธ๋์น๋ฅผ ์ฒดํฌ์์ํ์ง๋ง pull ํ์ง ์๋๋ค.
// - release ์๋ํ๊ฐ ๋จธ์ง ์งํ chore(release) ์ปค๋ฐ์ origin์ ์ถ๊ฐํ๋ฏ๋ก,
// base ๋ธ๋์น๋ฅผ ๋ช
์์ ์ผ๋ก ์ต์ ํํ์ฌ ๋ก์ปฌ์ ํญ์ ์ต์ ์ํ๋ก ๋๋ค.
// - git pull ์ superproject๊ฐ ๊ธฐ๋กํ ์๋ธ๋ชจ๋ ํฌ์ธํฐ๋ง ๊ฐฑ์ ํ ๋ฟ ์๋ธ๋ชจ๋ ์ํนํธ๋ฆฌ๋
// ์ฒดํฌ์์ํ์ง ์๋๋ค. git submodule update ์์ด๋ ์๋ธ๋ชจ๋์ด ๋จธ์ง ์ด์ ๋ฆฌ๋น์ ์ ๋จธ๋ฌผ๋ฌ
// Serverpod/Melos ๋ชจ๋
ธ๋ ํฌ์์ ๋น๋/์ฝ๋์ drift๊ฐ ๋ฐ์ํ๋ค.
// git submodule sync ๋ก .gitmodules URL ๋ณ๊ฒฝ์ ๋จผ์ ๋ฐ์ํ ๋ค update ํ๋ค.
// ์๋ธ๋ชจ๋์ด ์๋ ๋ฆฌํฌ์งํ ๋ฆฌ์์๋ ๋ ๋ช
๋ น ๋ชจ๋ no-op ์ด๋ฏ๋ก ํญ์ ์คํํด๋ ์์ ํ๋ค.
// - baseBranch ๋ Step 4์์ ๊ฒฐ์ ๋ PR base (๊ณ์ธต: ๋ถ๋ชจ ๋ธ๋์น, ๋
๋ฆฝ: development).
await Bash(`
git checkout ${baseBranch}
git pull --ff-only origin ${baseBranch}
git submodule sync --recursive
git submodule update --init --recursive
`);
console.log(`โ
๋จธ์ง ํ ${baseBranch} ๋ธ๋์น๋ก ์ด๋ + ์ต์ ํ + ์๋ธ๋ชจ๋ ๋๊ธฐํ ์๋ฃ`);
// ๊ณ์ธต ๋ธ๋์น: ๋ถ๋ชจ ๋ธ๋์น๊ฐ Epic์ด๊ณ ๋ชจ๋ Story๊ฐ ์๋ฃ๋๋ฉด ์๋ด
if (parentIssue & & parentIssue.issueType === " Epic " ) {
const siblings = await mcp__zenhub__searchLatestIssues({
query: `parent:${parentIssue.id}`,
});
// ์ํ ๋น๊ต๋ ๋์๋ฌธ์ ์ ๊ทํ (ZenHub ๋ " OPEN " / " CLOSED " ๋๋ฌธ์ ๋ฐํ โ :1398 ๊ณผ ์ผ๊ด)
const openSiblings = siblings.filter(s = > (s.state || " " ).toUpperCase() !== " CLOSED " );
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 โ
โ โ
โ โ
Design Verify: PASS (pixelโค2, ฮEโค3) โ
โ โ
โ โ
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 1.5: PM ์๊ตฌ์ฌํญ ์ ์ (--skip-pm ์ skip) " , status: " pending " , activeForm: " PM ์๊ตฌ์ฌํญ ์ ์ ๋๊ธฐ " },
{ 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.2: CoUI ํจํค์ง ๋ณ๊ฒฝ ๋ถ๋ฆฌ (ํ์ ์) " , status: " pending " , activeForm: " CoUI ๋ถ๋ฆฌ ๋๊ธฐ " },
{ content: " Step 7.3: ๋์์ธ ๊ฒ์ฆ ๊ฒ์ดํธ (screen feature ์) " , status: " pending " , activeForm: " ๋์์ธ ๊ฒ์ฆ ๋๊ธฐ " },
{ content: " Step 7.5: Backend ์ฝ๋ ์์ฑ " , status: " pending " , activeForm: " Backend ์์ฑ ๋๊ธฐ " },
{ content: " Step 7.7: ๋ก์ปฌ ํ์คํ ํตํฉ ๊ฒ์ฆ " , status: " pending " , activeForm: " ๋ก์ปฌ ํตํฉ ๊ฒ์ฆ ๋๊ธฐ " },
{ 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: ๋ฆฌ๋ทฐ/๋จธ์ง (๋จธ์ง = Close) " , status: " pending " , activeForm: " ๋ฆฌ๋ทฐ ๋๊ธฐ " },
{ content: " Step 12.5: ์ด์ Close ๊ฒ์ฆ + ํด๋ฐฑ โ base ๋ธ๋์น ์ต์ ํ " , status: " pending " , activeForm: " Close ๊ฒ์ฆ ๋๊ธฐ " },
]);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 ์๋ต ์บ์ฑ ๊ฐ์ "
โ ํค์๋ " ๊ฐ์ " ๊ฐ์ง โ refactorScreen 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 |
|---|---|---|
| PM requirements refinement (Step 1.5) | No changes | pm-spec-agent ์คํจ ์ Step 1 ํค์๋ ์ถ๋ก ๊ฒฐ๊ณผ๋ก ์๋ ํด๋ฐฑ + ๊ฒฝ๊ณ (ํ์ดํ๋ผ์ธ ๊ณ์ ์งํ); ์์ ํ ์์น ์์ผ๋ฉด --skip-pm |
| Issue creation | No changes | Retry |
| Branch creation | Issue exists | Retry |
| BDD scenario | Branch exists | --skip-bdd or manual writing |
| Implementation | Branch exists | Continue work |
| CoUI change separation (Step 7.2) | CoUI PR open/merged | CoUI PR ๋จธ์ง ๋๊ธฐ ํ ์์กด์ฑ ๋ฒํ ์ฌ์๋; ํ๋ฉด diff์ CoUI ์์ค๊ฐ ์์์ผ๋ฉด ํด๋น ์ปค๋ฐ์ CoUI ์ ์ฅ์ ๋ธ๋์น๋ก ๋ถ๋ฆฌ (git checkout -p / cherry-pick) ํ ํ๋ฉด ๋ธ๋์น์์ ์ ๊ฑฐ |
| Design verification (Step 7.3) | Code complete | ๋๊ตฌ/๊ธฐ๊ธฐ(figma-mcp/maestro/dart-mcp/device) ํ์ธ ํ ์ฌ์คํ, ๋๋ --skip-design-verify(์ฌ์ ํ AskUserQuestion ํ์ธ ํ์) โ ์์กด diff๋ pixel-loop:loop --mode=repair 1ํ ํ ์ฌ์ฉ์ ํ์ธ |
| Tests | Code complete | --skip-tests or manual fix |
| Local full-stack integration (Step 7.7) | Code complete | serverpod start (๋ด์ฅ Postgres) ์ฌ๊ธฐ๋ ํ dart test -t integration ์ฌ์คํ (ํซ๋ฆฌ๋ก๋ ๋งํ๋ฉด R ํค๋ก ์ฌ์์) ยท 3.x ํด๋ฐฑ: docker compose up -d + dart run bin/main.dart ยท ๋๋ --skip-local-integration |
| 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 dcm:fix auto-fix โ melos run dcm:analyze โ 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 |
| Issue Close ๊ฒ์ฆ (Step 12.5) | Merged | GitHub ๋ฏธclose ์ gh issue close {N} --reason completed; ZenHub ๋ฏธ๋๊ธฐํ ์ updateIssue state:CLOSED |
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 checklistcc-flutter-pixel-loop:pixel-loop:loop- Step 7.3์ ์ ์/์๋ ด ์์ง (FigmaโFlutter ํฝ์ ๋์กฐ ๋ฐ๋ณต)cc-flutter-inspector:visual-verify- Step 7.3์ ์ฝ๊ธฐ ์ ์ฉ ์ต์ข ํ์ ๋๊ตฌcc-flutter-marionette:marionette:smoke- Step 7.3์ ์ ํ์ ์์ ฏ ์กฐ๋ฆฝ ์ค๋ชจํฌ ์ฒดํฌcc-flutter-dev:pixel-loop-handoff(/flutter-dev:pixel-loop) - Figma URL/๋ผ์ฐํธ ์๋ ์ถ๋ก ๋ฐ ์๋ ์ฌ๊ฒ์ฆ์ฉ ์ง๋ฆ๊ธธ
Related Agents#
pm-spec-agent- PM requirements refinement (Step 1.5, non-interactive)issue-branch-agent- Branch creationissue-state-agent- Issue state managementbdd-scenario-agent- BDD scenario generation (cc-flutter-dev ์ ๊ณต โ cross-plugin ์ ํ ์์กด, ๋ฏธ์ค์น ์ BDD ๋จ๊ณ skip)implementation-agent- Code implementationtest-runner-agent- Test executionpr-lifecycle-agent- PR management