LogoSkills

dev:run

Full development cycle automation from issue creation to merge

ํ•ญ๋ชฉ๋‚ด์šฉ
Invoke/dev:run
Aliases/dr
Categorykobic-development
Complexityhigh
MCP Serverszenhub, 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๋‹จ๊ณ„๊ฐ€ ์ฐจ๋ก€๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

  1. ์ž‘์—… ๋‚ด์šฉ ๋ถ„์„ + PM ์š”๊ตฌ์‚ฌํ•ญ ์ •๋ฆฌ โ€” ๊ธ€์—์„œ ์ž‘์—… ์ข…๋ฅ˜(๊ธฐ๋Šฅ/์ˆ˜์ •/์ •๋ฆฌ), ๋ฒ”์œ„, ํ™”๋ฉด ์ข…๋ฅ˜, ์˜ˆ์ƒ ๊ทœ๋ชจ๋ฅผ ์ž๋™ ํŒ๋ณ„ํ•œ ๋’ค, PM ๊ด€์ ์—์„œ ์š”๊ตฌ์‚ฌํ•ญยท์™„๋ฃŒ ๊ธฐ์ค€ยท์Šค์ฝ”ํ”„(ํฌํ•จ/์ œ์™ธ)๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค(ํ”ผ๊ทธ๋งˆ ๋งํฌ๊ฐ€ ์žˆ์œผ๋ฉด ๋””์ž์ธ ๋‚ด์šฉ ๋ฐ˜์˜, --skip-pm์œผ๋กœ ์ƒ๋žต ๊ฐ€๋Šฅ). ์• ๋งคํ•œ ๋ถ€๋ถ„์€ ๋˜๋ฌป์ง€ ์•Š๊ณ  "๊ฐ€์ •"์œผ๋กœ ๋‚จ๊ธด ์ฑ„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ด์Šˆ ๋“ฑ๋ก (ZenHub) โ€” ์ •๋ฆฌ๋œ ๋‚ด์šฉ์œผ๋กœ ์ž‘์—… ๋“ฑ๋กํ‘œ๋ฅผ ๋งŒ๋“ค๊ณ  ํƒ€์ž…ยท๋ผ๋ฒจยท์˜ˆ์ƒ์น˜๋ฅผ ๋ถ™์ž…๋‹ˆ๋‹ค.
  3. ๋Œ€๊ธฐ์—ด๋กœ ์ด๋™ โ€” ๋“ฑ๋ก๋œ ์ž‘์—…์„ "ํ•  ์ผ(Product Backlog)" ์นธ์œผ๋กœ ์˜ฎ๊น๋‹ˆ๋‹ค.
  4. ์ž‘์—… ๊ณต๊ฐ„(๋ธŒ๋žœ์น˜) ์ƒ์„ฑ โ€” ๋‹ค๋ฅธ ์ฝ”๋“œ์™€ ์„ž์ด์ง€ ์•Š๋„๋ก ์ „์šฉ ๋ธŒ๋žœ์น˜๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. (๋ฉ”์ธ ์ฝ”๋“œ์— ์ง์ ‘ ์†๋Œ€์ง€ ์•Š๋„๋ก ๋ง‰๋Š” ์•ˆ์ „์žฅ์น˜ ํฌํ•จ)
  5. ์ง„ํ–‰ ์ค‘์œผ๋กœ ํ‘œ์‹œ โ€” ์ž‘์—…์„ "์ง„ํ–‰ ์ค‘(In Progress)" ์นธ์œผ๋กœ ์˜ฎ๊น๋‹ˆ๋‹ค.
  6. ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘์„ฑ โ€” ํ™”๋ฉด ์ž‘์—…์ด๋ฉด "์ด๋ ‡๊ฒŒ ๋™์ž‘ํ•ด์•ผ ํ•œ๋‹ค"๋Š” BDD ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ž๋™์œผ๋กœ ์”๋‹ˆ๋‹ค.
  7. ์‹ค์ œ ๊ตฌํ˜„ + ํ™”๋ฉด ๋””์ž์ธ ๊ฒ€์ฆ โ€” ์ด์Šˆ ๋‚ด์šฉ๋Œ€๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์กฐ๊ธˆ์”ฉ ์ €์žฅ(์ปค๋ฐ‹)ํ•ฉ๋‹ˆ๋‹ค. ํ™”๋ฉด ์ž‘์—…์ด๋ฉด ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐยท์‹ค๊ธฐ๊ธฐ์— ์‹ค์ œ๋กœ ์•ฑ์„ ๋„์›Œ Figma ์‹œ์•ˆ๊ณผ ์‚ฌ์ง„์œผ๋กœ ๋Œ€์กฐํ•˜๊ณ , ๋‹ค๋ฅด๋ฉด ๋˜‘๊ฐ™์•„์งˆ ๋•Œ๊นŒ์ง€ ์ž๋™์œผ๋กœ ๊ณ ์นฉ๋‹ˆ๋‹ค(๋„๊ตฌ/๊ธฐ๊ธฐ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๊ทธ๋ž˜๋„ ์•ˆ ๋งž์œผ๋ฉด ์‚ฌ๋žŒ์—๊ฒŒ ํ™•์ธ์„ ์š”์ฒญ โ€” ๋ฌด์Œ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค).
  8. ๋ฐฑ์—”๋“œ ์ฝ”๋“œ ์ƒ์„ฑยท๋กœ์ปฌ ํ†ตํ•ฉ ๊ฒ€์ฆ โ€” ์„œ๋ฒ„ ์ชฝ ๋ณ€๊ฒฝ์ด ์žˆ์œผ๋ฉด ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๋กœ์ปฌ์—์„œ ์„œ๋ฒ„+DB+์•ฑ์„ ๋„์›Œ ์‹ค์ œ๋กœ ์ž˜ ๋งž๋ฌผ๋ฆฌ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  9. ์—ฌ๋Ÿฌ ๊ฒน์˜ ํ’ˆ์งˆ ๊ฒ€์‚ฌ(๊ฒŒ์ดํŠธ) โ€” ํ…Œ์ŠคํŠธ ํ†ต๊ณผ, ์‹œ๋‚˜๋ฆฌ์˜ค ์ปค๋ฒ„๋ฆฌ์ง€ 100%, ์ฝ”๋“œ ์„œ์‹ยท์ •์  ๋ถ„์„ 0๊ฑด, ์ฝ”๋“œ ๋ฆฌ๋ทฐ์˜ ์น˜๋ช…์  ๋ฌธ์ œ 0๊ฑด์„ ์ฐจ๋ก€๋กœ ํ†ต๊ณผํ•ด์•ผ๋งŒ ๋‹ค์Œ์œผ๋กœ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค. ํ•˜๋‚˜๋ผ๋„ ๋ง‰ํžˆ๋ฉด ํ•ฉ์น˜๊ธฐ ์š”์ฒญ์„ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  10. ํ•ฉ์น˜๊ธฐ ์š”์ฒญ(PR) ์ƒ์„ฑ โ€” ๊ฒ€์‚ฌ๋ฅผ ๋ชจ๋‘ ํ†ต๊ณผํ•˜๋ฉด ๋ณ€๊ฒฝ ์š”์•ฝยทํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ๋‹ด์€ PR์„ ๋งŒ๋“ค๊ณ  ๊ฒ€์ˆ˜(Review/QA) ์นธ์œผ๋กœ ์˜ฎ๊น๋‹ˆ๋‹ค.
  11. ์ถ”๊ฐ€ ๋ฆฌ๋ทฐ ๋ฐ˜์˜ โ€” ๋‚จ์€ ๋ฆฌ๋ทฐ ์˜๊ฒฌ์„ ๋ฐ˜์˜ํ•˜๊ณ  ์™„๋ฃŒ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋ฅผ ๋Œ๋ฆฝ๋‹ˆ๋‹ค.
  12. ํ•ฉ์น˜๊ธฐ ์Šน์ธ ๋Œ€๊ธฐ โ†’ ํ•ฉ์น˜๊ธฐ โ€” ์‚ฌ๋žŒ์—๊ฒŒ "ํ•ฉ์ณ๋„ ๋ ๊นŒ์š”?"๋ฅผ ๋ฌป๊ณ , ์Šน์ธํ•˜๋ฉด ํ•˜๋‚˜๋กœ ํ•ฉ์นฉ๋‹ˆ๋‹ค(ํ•ฉ์น˜๊ธฐ = ์ž‘์—… ์™„๋ฃŒ).
  13. ๋งˆ๋ฌด๋ฆฌ ํ™•์ •ยท๋™๊ธฐํ™” โ€” ์ด์Šˆ๊ฐ€ ์ •๋ง "์™„๋ฃŒ"๋กœ ๋‹ซํ˜”๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๋‹ค์Œ ์ž‘์—…์„ ์œ„ํ•ด ๊ธฐ์ค€ ์ฝ”๋“œ๋ฅผ ์ตœ์‹  ์ƒํƒœ๋กœ ๋Œ์–ด์˜ต๋‹ˆ๋‹ค.

โš™๏ธ ์ƒ์„ธ ์˜ต์…˜ยท์‹คํ–‰ ๋ช…์„ธ (๊ฐœ๋ฐœ์ž / 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 1810

Parameters#

ParameterRequiredDescriptionExample
work_contentโœ…*Description of the work"์ €์ž ๋ชฉ๋ก ํ™”๋ฉด ์ถ”๊ฐ€"
issue_numberโœ…*Existing issue number1810

*Either work content or issue number is required


Options#

Auto-Decision Options (defaults)#

When options are not specified, they are determined automatically:

OptionDefaultAuto-Decision Rule
--typeAuto-inferredKeyword analysis: "add/create"โ†’feat, "fix/repair"โ†’fix, "improve"โ†’refactor
--scopeAuto-inferredExtract Entity/Feature name from work content, ask if unclear
--pointAuto-estimatedComplexity analysis: single file(1), multiple files(3), multiple layers(5), with BDD(8)
--bddOn screen detectionList/detail/form keyword detection โ†’ auto BDD scenario writing
--baseAuto-detectedAuto-detect parent issue branch (development if none)
--sprintcurrent (active sprint)currentโ†’ํ™œ์„ฑ ์Šคํ”„๋ฆฐํŠธ(getSprint, id ์—†์Œ=ํ™œ์„ฑ), nextโ†’๋‹ค์Œ ์Šคํ”„๋ฆฐํŠธ(getUpcomingSprint), ์ˆซ์ž/์ด๋ฆ„โ†’listRecentSprints ๋งค์นญ. ์ƒ์„ฑ ์ด์Šˆ๋ฅผ ํ•ด๋‹น ์Šคํ”„๋ฆฐํŠธ์— ๋„ฃ์–ด ๋กœ๋“œ๋งต/๋ฒˆ๋‹ค์šด์— ๋…ธ์ถœ
--no-sprintoff์Šคํ”„๋ฆฐํŠธ ๋ฐฐ์ • ์ƒ๋žต(์ด ๊ฒฝ์šฐ ์ €์ˆ˜์ค€ ์ด์Šˆ๋Š” ๋กœ๋“œ๋งต ํƒ€์ž„๋ผ์ธ์—์„œ ๋ณด์ด์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ)
--skip-pmoff (PM ์ •์ œ ํ•ญ์ƒ ์‹คํ–‰)Step 1.5 PM ์š”๊ตฌ์‚ฌํ•ญ ์ •์ œ(pm-spec-agent)๋ฅผ ๊ฑด๋„ˆ๋›ฐ๊ณ  Step 1์˜ ํ‚ค์›Œ๋“œ ์ถ”๋ก  ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋Œ€๋กœ ์ด์Šˆ ๋ณธ๋ฌธ์— ์‚ฌ์šฉ (๊ธด๊ธ‰/์‚ฌ์†Œํ•œ ํ•œ ์ค„ ์ž‘์—…์šฉ)
--skip-design-verifyoff (์š”๊ตฌ ์‹œ ํ™•์ธ ํ•„์ˆ˜)screen feature + Figma ์†Œ์Šค ์กด์žฌ ์‹œ Step 7.3 ๊ธฐ๋ณธ on. ๋„๊ตฌ/๊ธฐ๊ธฐ ๋ถ€์žฌ ์‹œ AskUserQuestion ๊ธฐ๋ณธ ๋‹ต๋งŒ "์Šคํ‚ต"์œผ๋กœ ๋ฐ”๊ฟ€ ๋ฟ, ํ™•์ธ ์ž์ฒด๋Š” ์ƒ๋žต ๋ถˆ๊ฐ€
--design-targetautopixel-loop/visual-verify๋กœ ์ „๋‹ฌํ•  ๋Œ€์ƒ ๊ธฐ๊ธฐ (ios/android/macos/auto)
--design-tolerancepixel=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#

StepPhasePrerequisitesVerification
1Work content analysisNone-
1.5PM requirements refinement (pm-spec-agent, --skip-pm ์‹œ ์Šคํ‚ต)Step 1 completeissue body์— FR/AC/์Šค์ฝ”ํ”„ ํฌํ•จ, ๊ฐ€์ • ๋ช…์‹œ
2ZenHub issue creationStep 1.5 complete (or Step 1 if --skip-pm)issue.id exists
3Move to Product BacklogStep 2 completepipeline verified
4Branch creation ({type}/{issue_number}-{slug})Steps 2,3 completebranch exists + ์ด์Šˆ๋ฒˆํ˜ธ ํฌํ•จ ๊ฒ€์ฆ
5Move to In ProgressStep 4 completepipeline verified
6BDD scenario writingStep 5 complete, screen feature.feature file exists
7Implementation workStep 5 completecommits exist
7.2CoUI ํŒจํ‚ค์ง€ ๋ณ€๊ฒฝ ๋ถ„๋ฆฌ โ€” 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.5Backend code generationStep 7 complete, when Backend changes existgeneration success
7.7๋กœ์ปฌ ํ’€์Šคํƒ ํ†ตํ•ฉ ๊ฒ€์ฆ (Serverpod 4 / ๋กœ์ปฌ ํ’€์Šคํƒ)Step 7.5 complete, on Backend or screen feature๋กœ์ปฌ ํ†ตํ•ฉํ…Œ์ŠคํŠธ PASS
8Test writing + /pr:preflight gate (๋ณ€๊ฒฝ ํŒจํ‚ค์ง€ unit+widget+integration)Step 7 complete/pr:preflight PASS
8.3BDD Coverage GateStep 8 complete, .feature existsstep_coverage 100%, assertions exist
8.5Pre-push verification + DCM format/lint 0-issue gate + DCM quality improvement (= Per-Push Gate, ์ดํ›„ ๋ชจ๋“  push ์ง์ „ ์žฌ์‹คํ–‰)Step 8.3 completedcm format, dart analyze 0 issues, dcm analyze error 0 issues
8.7Code Review GateStep 8.5 complete0 Critical issues
9PR creationStep 8.7 completePR URL exists
10Move to Review/QAStep 9 completepipeline verified
11Apply additional review feedbackStep 10 completereview complete
12Wait for merge approval โ†’ squash merge (merge = Close)Step 11 completeuser approval
12.5Issue Close ํ™•์ • โญ โ€” base๊ฐ€ ๊ธฐ๋ณธ(default) ๋ธŒ๋žœ์น˜๊ฐ€ ์•„๋‹ˆ๋ฉด(๊ณ„์ธต ๋จธ์ง€: story/ยทepic/) Closes # ์ž๋™ ๋‹ซํž˜์ด ์•ˆ ๋˜๋ฏ€๋กœ gh issue close๋กœ ๋ช…์‹œ์  ์ข…๋ฃŒ(ํด๋ฐฑ์ด ์•„๋‹ˆ๋ผ ๊ธฐ๋ณธ ๋™์ž‘). ์ด์–ด GitHub closed + ZenHub Closed ํ™•์ • โ†’ checkout PR base + git pullStep 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
fi

Step 9 Pre-PR Verification (Required)#

All of the following conditions must be met to create a PR:

  1. 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
    fi
    
  2. Issue link verification

    • Issue number extractable from branch name
    • ZenHub issue in In Progress state
  3. 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
    }
    
  4. Commit verification

        # base ๋ธŒ๋žœ์น˜ ๋Œ€๋น„ ์ปค๋ฐ‹ ๊ฒ€์ฆ (๊ณ„์ธต ๋ธŒ๋žœ์น˜ ์ง€์›)
    COMMITS=$(git rev-list --count origin/${BASE_BRANCH}..HEAD)
    if [[ $COMMITS -eq 0 ]]; then
      echo  " โŒ ์ปค๋ฐ‹์ด ์—†์Šต๋‹ˆ๋‹ค. Step 7์„ ๋จผ์ € ์™„๋ฃŒํ•ด์ฃผ์„ธ์š” " 
       exit 1
    fi
    
  5. CoUI 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
    fi
    
  6. Design 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
    fi
    
  7. Pre-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=repair 1ํšŒ๋กœ ์—์Šค์ปฌ๋ ˆ์ด์…˜ํ•œ๋‹ค.

์Šคํ‚ต/์‹คํŒจ ์‹œ ๋ฌธ์ œ:

  • ๋ฌด์Œ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๋ฉด ํ™”๋ฉด์ด ๋””์ž์ธ๊ณผ ๋‹ค๋ฅด๊ฒŒ ๋‚˜๊ฐ€๋Š” ์ง€๊ธˆ๊นŒ์ง€์˜ ๋ฌธ์ œ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋ฐ˜๋ณต๋œ๋‹ค
  • ๋””์ž์ด๋„ˆ๊ฐ€ 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-lease re-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#

RuleDescription
Only 1 in_progressOnly 1 in_progress state allowed at a time
Immediate updateChange to completed immediately on step completion
Skip markingAdd "(skip)" to content for N/A steps
Keep on failureKeep 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 StepStateRecovery Method
PM requirements refinement (Step 1.5)No changespm-spec-agent ์‹คํŒจ ์‹œ Step 1 ํ‚ค์›Œ๋“œ ์ถ”๋ก  ๊ฒฐ๊ณผ๋กœ ์ž๋™ ํด๋ฐฑ + ๊ฒฝ๊ณ  (ํŒŒ์ดํ”„๋ผ์ธ ๊ณ„์† ์ง„ํ–‰); ์™„์ „ํžˆ ์›์น˜ ์•Š์œผ๋ฉด --skip-pm
Issue creationNo changesRetry
Branch creationIssue existsRetry
BDD scenarioBranch exists--skip-bdd or manual writing
ImplementationBranch existsContinue work
CoUI change separation (Step 7.2)CoUI PR open/mergedCoUI 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ํšŒ ํ›„ ์‚ฌ์šฉ์ž ํ™•์ธ
TestsCode complete--skip-tests or manual fix
Local full-stack integration (Step 7.7)Code completeserverpod start (๋‚ด์žฅ Postgres) ์žฌ๊ธฐ๋™ ํ›„ dart test -t integration ์žฌ์‹คํ–‰ (ํ•ซ๋ฆฌ๋กœ๋“œ ๋ง‰ํžˆ๋ฉด R ํ‚ค๋กœ ์žฌ์‹œ์ž‘) ยท 3.x ํด๋ฐฑ: docker compose up -d + dart run bin/main.dart ยท ๋˜๋Š” --skip-local-integration
Dart lint gateCode completedart fix --apply + melos run format (dcm format) + manual fix (up to 2 times) โ†’ 0 issues required
DCM quality gateCode completemelos run dcm:fix auto-fix โ†’ melos run dcm:analyze โ†’ manually fix remaining errors
Code Review GateCode complete--skip-review or manual fix
PR creationCode completeRun gh pr create manually
Code reviewPR exists--skip-review or manual application
MergePR existsWait for CI pass then manual merge
Issue Close ๊ฒ€์ฆ (Step 12.5)MergedGitHub ๋ฏธclose ์‹œ gh issue close {N} --reason completed; ZenHub ๋ฏธ๋™๊ธฐํ™” ์‹œ updateIssue state:CLOSED

  • /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
  • cc-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/๋ผ์šฐํŠธ ์ž๋™ ์ถ”๋ก  ๋ฐ ์ˆ˜๋™ ์žฌ๊ฒ€์ฆ์šฉ ์ง€๋ฆ„๊ธธ
  • pm-spec-agent - PM requirements refinement (Step 1.5, non-interactive)
  • issue-branch-agent - Branch creation
  • issue-state-agent - Issue state management
  • bdd-scenario-agent - BDD scenario generation (cc-flutter-dev ์ œ๊ณต โ€” cross-plugin ์„ ํƒ ์˜์กด, ๋ฏธ์„ค์น˜ ์‹œ BDD ๋‹จ๊ณ„ skip)
  • implementation-agent - Code implementation
  • test-runner-agent - Test execution
  • pr-lifecycle-agent - PR management