LogoSkills

/team-dev — 여러 작업을 동시에 진행하는 "팀장"

의존성 없는 여러 스토리를 담당·파일 영역으로 나눠 동시에 구현하고 스토리별 PR 을 엽니다 — 브랜치와 워크트리는 하나를 공유하므로, 작업별 분리가 필요하면 `/cc-dev:batch` 를 쓰세요.

/team-dev — 여러 작업을 동시에 진행하는 "팀장"#

항목내용
실행 명령/cc-product:team-dev

한마디로#

서로 영향을 주지 않는 여러 업무(스토리)를 여러 명에게 동시에 나눠 맡겨 한꺼번에 처리하는 명령입니다. 한 명이 일을 순서대로 처리하는 대신, 팀장이 일을 겹치지 않게 나눠 주고 여러 직원이 동시에 일하는 것과 같아요.

누가·언제 쓰나요#

  • 한 스프린트 안에 서로 독립적인(서로 의존하지 않는) 작업이 2개 이상 준비되어 있을 때
  • 한 건씩 순서대로 처리하기엔 시간이 아까워, 동시에 빠르게 끝내고 싶을 때

👉 이 명령 대신 다른 걸 써야 하는 경우: 동시에 돌릴 만큼 독립적인 작업 묶음이 1개뿐이면 굳이 팀을 꾸리는 비용이 아깝습니다. 이 경우 명령이 한 건씩 처리하는 /dev-story를 권합니다.

👉 사전 준비: "Agent Teams"라는 실험적 기능이 켜져 있어야 합니다(CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1). 꺼져 있으면 명령이 멈추고 /dev-story를 안내합니다.

무엇을 해주나요#

여러 스토리에 대한 실제 동작하는 코드가 만들어지고, 각 작업마다 PR(코드 검토 요청) 이 열리며, 스프린트 현황이 자동으로 갱신됩니다.

  • 작업들이 겹치지 않도록 담당과 파일 영역을 나눈 배정표
  • 팀 전체가 함께 쓰는 브랜치 한 개(에픽 브랜치 아래 스토리 그룹 브랜치)
  • 전원이 끝난 뒤 한 번에 돌리는 품질 검사(lint, 타입 체크, 테스트) 통과 여부
  • ZenHub 보드의 상태 이동(진행 중 → 검토/QA)
  • 의존성 때문에 동시에 못 돌린 작업은 "순차 대기열" 로 따로 안내
  • 제한 시간까지 응답이 없는 담당자는 "멈춤(STALLED)" 으로 표시하고 집계에서 빼내 보고

👉 브랜치는 하나뿐입니다: 담당자들은 같은 작업 폴더(워크트리) 하나를 공유하므로 그 폴더에 체크아웃된 브랜치도 하나만 존재할 수 있습니다. 담당자마다 다른 브랜치를 주는 것은 이 구조에서 아예 표현되지 않고, 둘째 담당자가 브랜치를 바꾸는 순간 첫째 담당자가 아직 커밋하지 않은 작업이 사라집니다. 그래서 겹침은 브랜치가 아니라 파일 영역 분담으로 막고, PR은 전원이 끝난 뒤 팀장이 하나씩 차례로 올립니다. 작업마다 진짜로 분리된 브랜치·작업 폴더가 필요하면 이 명령이 아니라 /cc-dev:batch(작업별 워크트리 분리 + 머지 직렬화)를 쓰세요.

어떻게 쓰나요#

# 동시 개발 시작 (팀장 역할로 실행)
/team-dev

# 이미 승인된 배정표가 있을 때 (: /cc-product:develop --parallel 이 넘겨준 경우)
/team-dev --plan-approved .bmad/team-dev-plan.yaml

# 동시에 못 돌리는, 서로 의존하는 작업은 한 건씩 처리
/dev-story STORY-003

동시에 몇 명까지 일할지는 설정값 agent_teams.max_teammates(기본 3명)로 정해집니다. 배정 계획을 매번 확인 없이 자동 승인하려면 설정값 agent_teams.auto_approve_plans를 켜면 됩니다. --plan-approved를 주면 이미 승인받은 배정표를 그대로 씁니다 — 그룹 나누기를 다시 계산하지도, 같은 계획을 다시 확인받지도 않습니다.

안에서 무슨 일이 벌어지나요#

  1. 준비 점검 — 설정과 스프린트 현황을 불러오고, 동시 작업 기능(Agent Teams)이 켜져 있는지 확인합니다. 꺼져 있으면 멈추고 한 건씩 처리하는 방법을 안내합니다. 시작할 수 있는 후보 작업들을 추립니다.
  2. 동시 처리 분석 — 작업을 에픽별로 묶고, 서로 의존하거나 같은 파일을 건드리는 작업을 골라냅니다. 겹치지 않는 독립 묶음만 동시에 돌리고, 나머지는 순차 대기열로 보냅니다. (--plan-approved가 있으면 이 단계는 건너뜁니다.)
  3. 배정 계획 제시 — 누가 어떤 작업과 파일을 맡을지 표로 보여 주고 사용자에게 진행 여부를 확인받습니다(자동 승인 설정 또는 --plan-approved 시 생략).
  4. 브랜치 생성 — 에픽 브랜치와 그 아래 스토리 그룹 브랜치 하나를 만듭니다. 담당자별 브랜치는 만들지 않습니다 — 작업이 겹치지 않게 하는 것은 브랜치가 아니라 파일 영역 분담입니다.
  5. 작업 분배·실행 — 공유 작업 목록을 만들고 담당자(teammate)들을 띄워 동시에 개발을 시작하며, ZenHub 보드를 "진행 중"으로 옮깁니다. 담당자들은 모두 같은 브랜치에 커밋하고, 브랜치를 바꾸지 않습니다.
  6. 진행 모니터링 — 정해진 간격으로 담당자들의 진행 상황을 확인합니다. 전체 제한 시간까지 끝나지 않은 담당자는 멈춤(STALLED) 으로 표시하고 집계에서 빼내 보고합니다 — 무한 대기는 없습니다.
  7. 전원 완료 후 품질 검사 — 모두 끝난 뒤 한 번에 lint·타입 체크·테스트를 돌립니다. 실패하면 해당 담당자에게 최대 2번까지 수정을 요청하고, 실패 건수가 줄지 않으면 그 작업만 "품질 차단"으로 표시해 보고합니다. 검사를 돌릴 수 없었던 경우도 통과가 아닙니다.
  8. 결과 정리 — 통과한 작업만 스토리별로 하나씩 차례로 PR을 열고, 완료 요약과 순차 대기열에 남은 작업을 정리해 보여 줍니다.

⚙️ 상세 옵션·실행 명세 (개발자 / AI 에이전트용)

Workflow Overview#

Goal: Develop multiple stories in parallel using Agent Teams

Phase: 4 - Implementation (Parallel Execution)

Agent: Team Lead

Inputs: Sprint plan with stories, architecture document, optionally a caller-approved group/ownership plan (--plan-approved {path})

Output: Working code for multiple stories, PRs created, sprint status updated

Duration: Varies by story count and complexity

Required for: Projects with 2+ independent stories ready for development

Prerequisite: Agent Teams must be enabled (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)

Substrate: Agent Teams — one shared worktree, fixed fan-out (3~5, role-scoped). Not migrated to Orca/Workflow: every added concurrency in this repo has needed a new compensating gate behind it, so this command makes the existing substrate honest instead of buying a heavier one. Substrate row, the branch prohibition, and the fan-out obligations (own: / tier: / serialize:) are defined once in plugins/cc-dev/rules/orchestration-graph.md → "Substrate Selection — 6행 매트릭스" and "Fan-out Obligations"; they are not restated here. Gate verdicts below are tri-state per that document's "Gate Contract — verdict 는 tri-state 다" (undet:fail unless a §3.1 case is declared on the node).

Not expressible here: per-teammate branches. One worktree has one HEAD, so N teammates cannot sit on N branches — git checkout is worktree-global and the second checkout carries or destroys the first teammate's uncommitted work. If true branch isolation is required, this command is the wrong substrate: route to plugins/cc-dev/commands/batch.md (Orca worktree dispatch — one worktree + terminal per sibling, serialize:1 merge tail, human approval before spawn).


Pre-Flight#

  1. Parse arguments:

    • --plan-approved {path} (optional, default path when the flag is given bare: .bmad/team-dev-plan.yaml)
    • Set plan_approved = true only if the file exists and parses with the required keys groups[].stories[], groups[].file_ownership[], groups[].epic, sequential_queue[], approved_by, approved_at. A missing/unparseable/incomplete file is not an approval: set plan_approved = false, warn, and fall through to Part 1 + Part 2 (never silently proceed on an unapproved plan — undet:fail, SoT "Gate Contract — verdict 는 tri-state 다").
    • The producer of this file is the caller (/cc-product:develop --parallel Phase 2 writes it after its own user approval). This command consumes it and does not re-derive or re-ask.
  2. Load context per helpers.md#Combined-Config-Load

  3. Load sprint status per helpers.md#Load-Sprint-Status

  4. Check Agent Teams per helpers.md#Check-Agent-Teams-Available

  5. If teams_available = false:

    • Output:
      Agent Teams not available.
      
      Agent Teams is an experimental feature that requires:
        CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
      
      Sequential alternative:
        Use /dev-story STORY-{ID} to develop stories one at a time.
      
    • Stop workflow (do not proceed)
  6. Load architecture (if Level 2+): Read .bmad/architecture-*.md

  7. Load ZenHub context per helpers.md#Load-ZenHub-Context

  8. Identify candidate stories:

    • Find all stories with status "not_started" or "defined" in sprint status
    • If no stories available: inform user and stop
    • If plan_approved (step 0): the candidate set is fixed by the plan file — do not widen it. Stories in the plan that are no longer in "not_started"/"defined" are dropped with a logged reason (a stale plan must not resurrect already-started work).
  9. Load max_teammates from config agent_teams.max_teammates (default: 3)

  10. Load sub-tasks (if zh_sub_tasks_enabled):

    • For each candidate story, check for sub_tasks[] in sprint status
    • Sub-tasks will be included in teammate context as implementation checklist

Part 1: Parallelization Analysis#

Skip this Part entirely when plan_approved = true. The groups, the file-ownership split and the sequential queue are then read verbatim from the plan file; re-deriving them is how the same decision got made twice with two different answers. Only the step-7 staleness drop and step 3' below still run.

Analyze stories for parallel execution:

  1. Group stories by epic:

        Epic 1: [STORY-001, STORY-002, STORY-003]
    Epic 2: [STORY-004, STORY-005]
    
  2. Check dependencies:

    • Read each story's dependencies from sprint status or story documents
    • Build dependency graph
    • Identify independent groups (no cross-dependencies)
  3. Calculate file ownership boundaries:

    • From architecture document, map epics to source directories
    • Ensure no overlap between teammate file sets
    • If overlap detected: move overlapping stories to sequential queue
    • Shared and generated files (lockfiles, *.g.dart, i18n bundles, sprint-status.yaml) are owned by the Lead only and appear in no teammate's set — with one branch, ownership is the only thing keeping two teammates off the same file

3'. Single-branch reachability check (runs even when plan_approved = true):

  • All teammates share one worktree, so the whole invocation runs on one branch. Groups whose epic differs from the first group's epic therefore cannot run in this invocation.
  • verdict: distinct epics among selected groups === 1 · undet:fail
  • On fail: keep group 1, move every other epic's groups to the sequential queue, and report them by name. Silent truncation is forbidden — SoT "Fan-out Obligations" (width: is a budget; capped items go to a deferred log).
  1. Determine optimal teammate count:

    • Count independent story groups
    • Cap at max_teammates
    • If only 1 group: suggest /dev-story instead and ask user
  2. Display analysis:

        Parallelization Analysis:
    
    Independent Groups: {count}
    Total Stories: {count}
    Sequential Queue: {count} (due to dependencies or file overlap)
    
    Group 1: Epic  " {name} " 
       Stories: STORY-001, STORY-002
      Files: src/auth/*, src/middleware/auth/*
      Points: {total}
    
    Group 2: Epic  " {name} " 
       Stories: STORY-004, STORY-005
      Files: src/catalog/*, src/api/products/*
      Points: {total}
    
    Teammates needed: {count}
    

Part 2: Assignment Plan#

If plan_approved = true (Pre-Flight step 0): do not present and do not ask. Echo the plan read-only (same layout as below, header Assignment Plan (approved by {approved_by} at {approved_at})), then go straight to Part 3. Re-asking here is the failure this flag exists to remove: develop.md Phase 2 already showed this exact plan to this exact user and got a y.

Otherwise, present assignment plan to user:

Proposed Teammate Assignments:

Team Branch (shared by ALL teammates): story/STORY-001-{slug}
  Base: epic/EPIC-{num}-{slug}   ← single epic per invocation (Part 1 step 3 ' )

Teammate 1 (Developer):
  Stories: STORY-001, STORY-002
  Epic: {epic_name}
  File Ownership: src/auth/*, tests/auth/*
  Commits to: story/STORY-001-{slug} (the team branch — never checks out another branch)
  Sub-tasks: {count} (as implementation checklist)  ← if sub-tasks exist

Teammate 2 (Developer):
  Stories: STORY-004, STORY-005
  Epic: {epic_name}
  File Ownership: src/catalog/*, tests/catalog/*
  Commits to: story/STORY-001-{slug} (the team branch — never checks out another branch)
  Sub-tasks: {count} (as implementation checklist)  ← if sub-tasks exist

Lead-owned files (no teammate may touch): lockfiles, *.g.dart, i18n bundles, sprint-status.yaml

Sequential Queue (after parallel phase):
  STORY-003 (depends on STORY-001)

Proceed with this plan? (y/n)

Wait for user confirmation before proceeding.

If config.agent_teams.auto_approve_plans = true, skip confirmation.


Part 3: Branch Creation#

Ensure exactly two branches exist: the epic branch and one shared team branch.

helpers.md#Create-Branch-Hierarchy ends with a git checkout. Calling it once per story in this worktree means the last call wins and every earlier teammate's uncommitted work is carried onto the wrong branch or lost. So it is called exactly once, before any teammate is spawned:

  1. Resolve branch names per helpers.md#Resolve-Branch-Names for the group's first story → epic_branch, story_branch
  2. Create branch hierarchy per helpers.md#Create-Branch-Hierarchyone call
    • Create epic branch from main (if not exists)
    • Create story_branch from the epic branch and leave it checked out
    • team_branch = story_branch. Every story in every group of this invocation lands here.
  3. Freeze the branch for the run: after this point, and until the Part 6.5 barrier reports, no git checkout / git switch / git worktree add is issued by anyone — Lead or teammate. verdict: git rev-parse --abbrev-ref HEAD === team_branch · undet:fail · on fail: HALT before spawning (a wrong HEAD here mislabels every commit of the run).

Display branch structure:

Branch Hierarchy (one worktree → one HEAD → one branch):
main
└── epic/EPIC-001-user-auth
    └── story/STORY-001-registration   ← team branch: Teammate 1 AND Teammate 2 commit here
                                          (separation is by file ownership, not by branch)

Per-story branches, if wanted at all, are cut after the barrier by the Lead alone, one at a time (Part 7 step 3). Need genuinely separate branches during implementation? That is plugins/cc-dev/commands/batch.md (Orca worktree dispatch), not this command.


Part 4: Task List & Teammate Spawn#

Step 1: Create shared task list per helpers.md#Create-Team-Task-List:

For each teammate assignment:

TaskCreate:
  subject:  " Developer: Implement {story_ids} ({epic_name}) " 
   description: Full context including stories, file ownership, and the shared team branch
  activeForm:  " Implementing {story_ids} "

Set up dependencies if any stories are sequential.

Step 2: Spawn teammates per helpers.md#Spawn-BMAD-Teammate:

For each teammate:

  • role: "developer"
  • context: Story documents, architecture summary, file ownership, team_branch name
  • own: — the teammate's file-ownership globs, verbatim from the plan. This is the only write isolation that exists in this substrate; declaring it is mandatory per SoT "Fan-out Obligations" (a write fan-out without per-branch own: or isolate:worktree is a violation).
  • tier:standard for developer teammates unless the plan says otherwise. Never leave it unset: unset is silent inherit, and at fan-out N that is an N× multiplier, not a default (SoT "Fan-out Obligations" → Effort Routing Convention).

Hard constraints handed to every teammate (state them verbatim in the prompt):

  • You are in a shared worktree. Do not run git checkout, git switch, git worktree add, git stash, or git rebase — they move or hide the HEAD that your teammates are also using.
  • Commit to the branch that is already checked out ({team_branch}). Commit often; uncommitted work in a shared worktree is the thing that gets lost.
  • Every commit subject must carry its story id (STORY-00X: …) — after the barrier that id is the only way the Lead can split one branch into per-story PRs.
  • Write only inside your own: globs. Anything outside — lockfiles, generated files, sprint-status.yaml — is Lead-owned; report the need instead of editing.
  • Runtime slots (ports, DB names, simulators) must not be derived from the worktree path or branch: all teammates share both and would compute the same slot. See SoT "런타임 격리는 6행 전부와 직교한다".

Step 3: Move stories to In Progress on ZenHub:

If zh_available:
  For each story being developed:
    Call helpers.md#Move-Pipeline-with-Context(zh_story_id,  " In Progress " )
  For each epic with first story starting:
    Call helpers.md#Move-Pipeline-with-Context(zh_epic_id,  " In Progress " )

Note on Sub-tasks: When spawning teammates, include sub-task information in the context:

  • Sub-task list serves as implementation checklist for the teammate
  • Teammate should move sub-task pipelines as they complete each one
  • Include sub-task zh_issue_ids so teammates can call Move-Pipeline-with-Context
  • After teammate completes: Lead batch-updates sub_task statuses in sprint-status.yaml

Part 5: Plan Approval#

Review teammate implementation plans:

Each teammate produces an implementation plan before coding.

If config.agent_teams.auto_approve_plans = false (default):

  1. Collect plans from teammates
  2. Present each plan to user for review
  3. User approves or requests changes
  4. On approval: teammates proceed with implementation
  5. On rejection: teammate revises plan — bounded, see L-TD-plan below

If config.agent_teams.auto_approve_plans = true:

  • Teammates proceed directly after planning (budget: 0 rounds)

Loop contract L-TD-plan (team-dev.plan-approval-revise) — 7 fields per plugins/cc-dev/rules/orchestration-graph.md → "Loop Contract — 필수 7 필드". Values live here, not there.

inv:      구현은 승인된 계획으로만 시작한다 · 각 라운드 종료 시 계획에 적힌 경로가
          그 teammate 의 own: 글롭 안에 전부 들어 있다
prog:     unresolvedObjections(teammate) = 사용자가 아직 해소되지 않았다고 표시한 항목 수,
          라운드마다 강한 감소
          no-prog: 같은 지적이 두 라운드 연속 남으면 남은 예산을 같은 계획에 더 쓰지 않고
          exhaust 로 내려간다 (sequential-workflow.md Rung 2 상당)
term:     모든 활성 teammate 에서 unresolvedObjections == 0
budget:   teammate 당 2 rounds (auto_approve_plans=true0 rounds) / outer 재진입 없음
exhaust:  그 teammate 를 스폰 취소하고 담당 스토리를 순차 대기열로 이동 + 사용자에게 보고.
          미승인 계획으로 구현 진행 금지, 남은 teammate 는 계속 (형제 중단 금지)
resume:   TaskList 의 각 teammate task 상태 + sprint-status.yaml 의 story status 재조회로
          위치 판정 (대화 카운터는 예산이 아니다)
log:      라운드당 한 줄  " plan#2: T1 objections 3→1 · T2 approved "   + 탈락시킨
          teammate/스토리를 sprint-status.yaml 에 내구 기록

Part 6: Progress Monitoring#

Poll teammate progress — bounded. all done is not the only exit.

  1. Check task status (every interval, not continuously):

        Call TaskList
    Display: {completed}/{total} teammates done · elapsed {t} / cap 90m
    
  2. On teammate completion: a. Record dev-reported for that teammate's stories in memory and note the commit range. Do not run the quality gate here — it runs once, after the barrier (Part 6.5). Running it per teammate means checking out and validating a branch that the still-running teammates are concurrently committing to, so its verdict expires the moment it is produced.

    b. Do not write sprint-status.yaml yet. Story status → dev-complete, completed_points, sub_task statuses and sub_tasks_completed are all written once by the Lead after the barrier passes (Part 6.5 step 4) — this is the "Lead-only, batched" rule in Notes for LLMs.

    c. Do not move the ZenHub pipeline yet. A story reaches Review/QA only after the Part 6.5 gate passes for it.

  3. On teammate failure:

    • Log the issue durably (sprint-status.yaml + issue comment); console alone is not durable
    • Continue monitoring remaining teammates (one teammate must not halt the group)
    • Mark those stories dev-failed, reflect the block on the board per plugins/cc-dev/rules/zenhub-conventions.md → "Blocked Issue Contract" (createBlockage + move to the holding column — never leave it in In Progress), and report
  4. Display progress:

        Team Progress (poll #7, t+7m / cap 90m):
    
    Teammate 1:Reported complete (STORY-001, STORY-002) — quality gate pending (Part 6.5)
    
    Teammate 2: 🔄 In Progress (STORY-004, STORY-005)
      Current: Implementing STORY-004... (last activity t+6m)
    
    Teammate 3:STALLED (STORY-007) — no activity for 20m, excluded from this run
    
    Overall: 2/4 stories reported · 1 stalled
    

Loop contract L-TD-poll (team-dev.progress-poll) — 7 fields per plugins/cc-dev/rules/orchestration-graph.md → "Loop Contract — 필수 7 필드". Values live here, not there.

inv:      매 폴 진입·종료 시 sprint-status.yaml 을 쓰는 주체는 Lead 하나 ·
          STALLED 로 표시된 teammate 는 이후 폴에서 다시 pending 으로 세지 않는다
prog:     remainingWallClock = cap - elapsed, 폴마다 강한 감소 ·
          pendingTeammates 는 비증가 (teammate 를 추가 스폰하지 않는다)
          no-prog: 한 teammate 의 TaskList 상태·커밋·아티팩트가 stallWindow 동안 변화 0 이면
          같은 대기에 예산을 더 쓰지 않고 그 teammate 를 STALLED 로 확정한다
term:     pendingTeammates == 0 — 즉 전원이 done | dev-failed | STALLED (셋 다 종단)
budget:   interval 60s · per-teammate stallWindow 20 min · 실행 전체 wall-clock cap 90 min
          (산정 근거는 plugins/cc-dev/skills/job-timeout-budget/SKILL.md — 실측 최대 × 2)
          outer 재진입 없음: 재실행은 새 실행이며 예산도 새로 받는다
exhaust:  cap 도달 시 남아 있는 teammate 전원을 STALLED 로 표시하고 집계에서 제외한 뒤,
          그 스토리를 순차 대기열로 옮기고 BLOCKED( ' teammate_stalled ' ) 로 보드에 반영하고
          (zenhub-conventions.md  " Blocked Issue Contract " ) 사용자에게 보고한다.
          조용한 무한 대기 금지 · 미검증 작업을 dev-complete 로 승격하는 것도 금지.
          단계 사다리는 plugins/cc-dev/agents/sequential-workflow.md  " Stall Budget — 3-Rung Ladder " 
           를 그대로 쓴다 (새 사다리를 만들지 않는다)
resume:   TaskList + sprint-status.yaml + `git log {team_branch}` 재실측으로 위치 판정
          (이미 STALLED 인 teammate 는 되살리지 않는다)
log:      폴당 한 줄  " poll#7 (t+7m): reported 1/3 pending 1 stalled 1 "   +
          탈락시킨 teammate·스토리와 그 이유를 sprint-status.yaml 에 내구 기록

Part 6.5: All-Teammates Barrier & Quality Gate#

mode:barrier · because: 품질 판정은 팀 브랜치 하나의 최종 상태에 대한 교차 항목이다 — 한 teammate 가 아직 같은 브랜치에 커밋하는 중에 내린 판정은 산출되는 순간 만료된다. barrier 정당화 요건은 plugins/cc-dev/rules/orchestration-graph.md → "Fan-out Obligations" / "barrier(= mode:barrier)가 정당한 자리" 참조.

Do not dismiss teammates before this Part reports. The single on-failure action below needs the owning teammate alive.

  1. Barrier: wait until L-TD-poll terminates (every teammate is done | dev-failed | STALLED). Nothing in this Part runs earlier.

  2. Collect results per helpers.md#Collect-Team-Results.

  3. Run the quality gate once, on the team branch per helpers.md#Team-Quality-Gate:

        Call Team-Quality-Gate(team_branch)      ← once per run, not once per teammate
    
    verdict per story:
      pass  = qualityGate.passed === true
               & &   openChildrenStatus(zh_story_id).status ===  " none "     # sub-tasks all closed
      fail  = qualityGate.passed === false
      undet = a check could not be run (lint/typecheck/test tool missing, branch unreadable)
              or openChildrenStatus(...).status ===  " unknown " 
     undet:fail   ← 판정 불가 ≠ 통과
    
    • openChildrenStatus() is called, not reimplemented — the tri-state definition lives in plugins/cc-dev/rules/zenhub-conventions.md → "Child Enumeration Contract". A Story is a container type, so unknown blocks (only Sub-task gets the leaf degradation there).
    • The only undet:warn allowed is the tool-absent case of plugins/cc-dev/rules/orchestration-graph.md → "3.1 warn 으로 완화할 수 있는 세 경우", and only with the reason recorded durably in sprint-status.yaml and in the PR body. A missing linter recorded to the console only is a silent skip (fail-open type silent skip).
  4. On pass: for each passing story, in one batched write —

    • helpers.md#Update-Sprint-Status: status → dev-complete, set branch = team_branch and epic_branch, increment completed_points, batch-update sub_task statuses, increment sub_tasks_completed
    • helpers.md#Move-Pipeline-with-Context(zh_story_id, "Review/QA") (best-effort: warn and continue)
  5. On fail — one action, and only this one: request a fix from the owning teammate. This is the normative on-quality-gate-failure action for this flow. The wordings in plugins/cc-dev/skills/agent-teams/SKILL.md ("Request fix from respective Teammate on failure") and in helpers.md#Team-Quality-Gate ("do NOT auto-fix — report to Lead for decision") describe the helper's own return value, not this decision; where they read differently, this site wins (SoT "파생 뷰는 규범이 아니다"). Neither the Lead nor another teammate fixes someone else's files.

Loop contract L-TD-qgate (team-dev.progress-poll-quality-gate) — 7 fields per plugins/cc-dev/rules/orchestration-graph.md → "Loop Contract — 필수 7 필드". Values live here, not there. (The id keeps the progress-poll- prefix from when this gate ran inside the poll; it now runs after the barrier.)

inv:      매 라운드 진입·종료 시 팀 브랜치가 commit-clean · 수정은 그 스토리를 소유한
          teammate 의 own: 글롭 안에서만 일어난다 · `// ignore:` 삽입이나 훅 비활성화로
          0건을 달성하는 것 금지
prog:     failureCount = lintFailures + typecheckFailures + testFailures, 라운드마다
          **강한 감소**(같거나 늘면 진행 아님)
          no-prog: failureCount 가 감소하지 않은 라운드는 예산을 반환하지 않고 즉시 exhaust
term:     모든 스토리에서 verdict == pass (undetermined 는 term 이 아니다)
budget:   스토리당 inner 2 rounds · outer 재진입 없음 (재실행은 새 실행)
exhaust:  그 스토리를 `quality-blocked` 로 표시하고 sprint-status.yaml + 이슈 코멘트에
          실패 요약을 남기고, 보드는  " Blocked Issue Contract "   대로 holding 컬럼으로 옮긴다.
          그 스토리의 PR 은 만들지 않고 형제 스토리는 계속 진행한다.
          소유 teammate 가 STALLED/부재면 라운드를 쓰지 않고 곧바로 이 상태로 간다 —
          Lead 대리 수정 금지(소유권 위반), 사용자에게 자동 승격도 금지.
resume:   `Team-Quality-Gate(team_branch)` 재실측으로 위치 판정 (직전 라운드의 결과를
          믿지 않는다) + sprint-status.yaml 의 story status
log:      라운드당 한 줄  " qgate#2 STORY-004: fail 7→2 (lint 5→0, test 2→2) "   +
          quality-blocked 로 탈락시킨 스토리와 남은 실패 요약을 내구 기록.
          0**주장된 0** 이 아니라 실측 0 일 때만 0 으로 출력한다

Part 7: Results Summary#

After the Part 6.5 barrier and quality gate report:

  1. Collect results — already collected at Part 6.5 step 2; reuse that result, do not re-run helpers.md#Collect-Team-Results (it would race nothing now, but a second collection produces a second set of numbers to reconcile)

  2. Handle sequential queue (if any):

    • Inform user about remaining dependent stories
    • Also list here: stories deferred by the Part 1 step 3' single-epic cap, stories dropped by L-TD-plan exhaust, dev-failed stories, STALLED teammates' stories, and quality-blocked stories — each with its reason. Nothing leaves this run unaccounted for.
    • Suggest /dev-story STORY-{ID} for each
  3. Create story → epic PRs, one at a time per helpers.md#Create-PR-and-Merge. Teammates are dismissed first; only then does the Lead touch HEAD. serialize:1 — the worktree has one HEAD, so these are sequential by construction, never a fan-out:

        For each story with verdict == pass, in plan order (serialize:1):
      If the run produced one story only:
        source = team_branch
      Else:
        git checkout -b story/STORY-{ID}-{slug} {epic_branch}   # Lead only, teammates dismissed
        cherry-pick that story ' s commits from team_branch (matched by the  " STORY-{ID}: "   subject)
        source = story/STORY-{ID}-{slug}
        If the commits are not separable (a commit spans two stories, or a cherry-pick conflicts):
          abort the split, open ONE team_branch → epic_branch PR listing every passing story,
          and say so in the PR body. Do not open a PR that claims a scope it does not contain.
      Call Create-PR-and-Merge(
        source: source,
        target: epic_branch,
        title:  " feat: STORY-{ID} {title} " ,
        merge_strategy:  " merge " 
       )
    

    Stories with verdict fail / undetermined (quality-blocked) get no PR.

  4. Display summary:

    Team Development Complete!
    
    Team Branch: story/STORY-001-{slug} (base epic/EPIC-001-{slug}) — shared by all teammates
    Stories Completed: {count}/{total}
    Total Points: {points}
    
    Results:
      STORY-001:Dev Complete, PR open
        Quality: lint ✓, typecheck ✓, tests ✓ ({count} tests)
        PR: {pr_url}
    
      STORY-002:Dev Complete, PR open
        Quality: lint ✓, typecheck ✓, tests ✓ ({count} tests)
        PR: {pr_url}
    
      STORY-004: ⛔ quality-blocked after 2 fix rounds (test 2 failures remain) — no PR
    
    ZenHub Pipeline:
      STORY-001Review/QA
      STORY-002Review/QA
      STORY-004Sprint Backlog (blocked)
    
    Not run / carried over:
      STORY-003 (depends on STORY-001)Run /dev-story STORY-003
      STORY-007 (Teammate 3 STALLED at cap)Run /dev-story STORY-007
      STORY-009 (other epic, single-epic cap)Run /team-dev again for that epic
    
    Sprint Progress: {completed}/{total} points
    

Helper References#

  • Load config: helpers.md#Combined-Config-Load
  • Load sprint status: helpers.md#Load-Sprint-Status
  • Update sprint status: helpers.md#Update-Sprint-Status
  • Check Agent Teams: helpers.md#Check-Agent-Teams-Available
  • Spawn teammate: helpers.md#Spawn-BMAD-Teammate
  • Create team tasks: helpers.md#Create-Team-Task-List
  • Collect results: helpers.md#Collect-Team-Results
  • Quality gate: helpers.md#Team-Quality-Gate
  • Resolve branches: helpers.md#Resolve-Branch-Names
  • Create branch hierarchy: helpers.md#Create-Branch-Hierarchy
  • Create PR: helpers.md#Create-PR-and-Merge
  • ZenHub context: helpers.md#Load-ZenHub-Context
  • Move pipeline: helpers.md#Move-Pipeline-with-Context

Notes for LLMs#

  • ALWAYS check Agent Teams availability first (Pre-Flight step 3-4)
  • If teams not available, suggest /dev-story and STOP — do not attempt workarounds
  • Present the parallelization plan and wait for confirmation — unless --plan-approved supplied a caller-approved plan, in which case echo it and proceed (Part 1 skipped, Part 2 does not ask)
  • Sprint-status.yaml is Lead-only: batch all updates after the Part 6.5 barrier, not per teammate
  • File ownership boundaries must be explicit and non-overlapping — with a single shared branch this is the only write isolation that exists, so declare own: on every teammate and keep shared/generated files Lead-owned
  • The quality gate runs once, after the all-teammates barrier, on the team branch — never per teammate while others are still committing. Undetermined (tool missing, openChildrenStatusunknown) is not a pass
  • On quality-gate failure there is exactly one action: request a fix from the owning teammate, max 2 rounds, and each round must strictly decrease failureCount; otherwise the story becomes quality-blocked with no PR. Do not auto-fix on the teammate's behalf and do not escalate to the user as a substitute for the bound
  • ZenHub pipeline moves are best-effort (warn and continue on failure); blocks are not best-effort — reflect them on the board per zenhub-conventions.md → "Blocked Issue Contract"
  • All teammates share one worktree, therefore one branch. Every teammate commits to the single team branch (story/STORY-{first}-{slug}) under the file-ownership split, and no teammate runs git checkout / switch / worktree add / stash / rebase. Per-teammate branches are not a missing feature — one HEAD makes them inexpressible, and the second checkout takes the first teammate's uncommitted work with it. For real branch isolation, use plugins/cc-dev/commands/batch.md (Orca worktree dispatch) instead of this command
  • Story → epic PRs are created by the Lead after teammates are dismissed, one at a time (serialize:1); if commits cannot be split per story, open one team-branch PR and say so
  • The progress poll is bounded (interval 60s, 20m per-teammate stall window, 90m run cap). At the cap, mark remaining teammates STALLED, exclude them, and report — "all done" is not the only exit
  • Sequential queue handles stories with dependencies (run after parallel phase), plus everything this run deferred: single-epic cap, plan-approval exhaust, dev-failed, STALLED, quality-blocked
  • If gh CLI unavailable, output manual PR instructions (do not block)
  • Notation, loop-contract fields, gate tri-state, substrate matrix and fan-out obligations are defined once in plugins/cc-dev/rules/orchestration-graph.md — cite it, do not restate it. The per-loop values (L-TD-plan, L-TD-poll, L-TD-qgate) live in this file, at each loop's own site

Remember: The value of /team-dev is parallelization. If there's only 1 independent story group, recommend /dev-story instead. The overhead of team coordination should be justified by parallel execution gains.