/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를 주면 이미 승인받은 배정표를 그대로 씁니다 — 그룹 나누기를 다시 계산하지도, 같은 계획을 다시 확인받지도 않습니다.
안에서 무슨 일이 벌어지나요#
- 준비 점검 — 설정과 스프린트 현황을 불러오고, 동시 작업 기능(Agent Teams)이 켜져 있는지 확인합니다. 꺼져 있으면 멈추고 한 건씩 처리하는 방법을 안내합니다. 시작할 수 있는 후보 작업들을 추립니다.
-
동시 처리 분석 — 작업을 에픽별로 묶고, 서로 의존하거나 같은 파일을 건드리는 작업을 골라냅니다. 겹치지 않는 독립 묶음만 동시에 돌리고, 나머지는 순차 대기열로 보냅니다. (
--plan-approved가 있으면 이 단계는 건너뜁니다.) -
배정 계획 제시 — 누가 어떤 작업과 파일을 맡을지 표로 보여 주고 사용자에게 진행 여부를 확인받습니다(자동 승인 설정 또는
--plan-approved시 생략). - 브랜치 생성 — 에픽 브랜치와 그 아래 스토리 그룹 브랜치 하나를 만듭니다. 담당자별 브랜치는 만들지 않습니다 — 작업이 겹치지 않게 하는 것은 브랜치가 아니라 파일 영역 분담입니다.
- 작업 분배·실행 — 공유 작업 목록을 만들고 담당자(teammate)들을 띄워 동시에 개발을 시작하며, ZenHub 보드를 "진행 중"으로 옮깁니다. 담당자들은 모두 같은 브랜치에 커밋하고, 브랜치를 바꾸지 않습니다.
- 진행 모니터링 — 정해진 간격으로 담당자들의 진행 상황을 확인합니다. 전체 제한 시간까지 끝나지 않은 담당자는 멈춤(STALLED) 으로 표시하고 집계에서 빼내 보고합니다 — 무한 대기는 없습니다.
- 전원 완료 후 품질 검사 — 모두 끝난 뒤 한 번에 lint·타입 체크·테스트를 돌립니다. 실패하면 해당 담당자에게 최대 2번까지 수정을 요청하고, 실패 건수가 줄지 않으면 그 작업만 "품질 차단"으로 표시해 보고합니다. 검사를 돌릴 수 없었던 경우도 통과가 아닙니다.
- 결과 정리 — 통과한 작업만 스토리별로 하나씩 차례로 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#
Parse arguments:
--plan-approved {path}(optional, default path when the flag is given bare:.bmad/team-dev-plan.yaml)- Set
plan_approved = trueonly if the file exists and parses with the required keysgroups[].stories[],groups[].file_ownership[],groups[].epic,sequential_queue[],approved_by,approved_at. A missing/unparseable/incomplete file is not an approval: setplan_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 --parallelPhase 2 writes it after its own user approval). This command consumes it and does not re-derive or re-ask.
Load context per
helpers.md#Combined-Config-LoadLoad sprint status per
helpers.md#Load-Sprint-StatusCheck Agent Teams per
helpers.md#Check-Agent-Teams-AvailableIf 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)
- Output:
Load architecture (if Level 2+): Read
.bmad/architecture-*.mdLoad ZenHub context per
helpers.md#Load-ZenHub-ContextIdentify 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).
Load max_teammates from config
agent_teams.max_teammates(default: 3)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
- For each candidate story, check for
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:
Group stories by epic:
Epic 1: [STORY-001, STORY-002, STORY-003] Epic 2: [STORY-004, STORY-005]Check dependencies:
- Read each story's dependencies from sprint status or story documents
- Build dependency graph
- Identify independent groups (no cross-dependencies)
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).
Determine optimal teammate count:
- Count independent story groups
- Cap at max_teammates
- If only 1 group: suggest
/dev-storyinstead and ask user
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:
- Resolve branch names per
helpers.md#Resolve-Branch-Namesfor the group's first story →epic_branch,story_branch - Create branch hierarchy per
helpers.md#Create-Branch-Hierarchy— one call- Create epic branch from main (if not exists)
- Create
story_branchfrom the epic branch and leave it checked out team_branch = story_branch. Every story in every group of this invocation lands here.
- Freeze the branch for the run: after this point, and until the Part 6.5 barrier reports, no
git checkout/git switch/git worktree addis 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_branchname 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-branchown:orisolate:worktreeis a violation).tier:—standardfor 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, orgit 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):
- Collect plans from teammates
- Present each plan to user for review
- User approves or requests changes
- On approval: teammates proceed with implementation
- On rejection: teammate revises plan — bounded, see
L-TD-planbelow
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=true 면 0 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.
Check task status (every
interval, not continuously):Call TaskList Display: {completed}/{total} teammates done · elapsed {t} / cap 90mOn teammate completion: a. Record
dev-reportedfor 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.yamlyet. Story status →dev-complete,completed_points, sub_task statuses andsub_tasks_completedare 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/QAonly after the Part 6.5 gate passes for it.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 perplugins/cc-dev/rules/zenhub-conventions.md→ "Blocked Issue Contract" (createBlockage+ move to the holding column — never leave it inIn Progress), and report
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.
Barrier: wait until
L-TD-pollterminates (every teammate isdone | dev-failed | STALLED). Nothing in this Part runs earlier.Collect results per
helpers.md#Collect-Team-Results.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 inplugins/cc-dev/rules/zenhub-conventions.md→ "Child Enumeration Contract". A Story is a container type, sounknownblocks (onlySub-taskgets the leaf degradation there).- The only
undet:warnallowed is the tool-absent case ofplugins/cc-dev/rules/orchestration-graph.md→ "3.1warn으로 완화할 수 있는 세 경우", and only with the reason recorded durably insprint-status.yamland in the PR body. A missing linter recorded to the console only is a silent skip (fail-open typesilent skip).
On pass: for each passing story, in one batched write —
helpers.md#Update-Sprint-Status: status →dev-complete, setbranch=team_branchandepic_branch, incrementcompleted_points, batch-updatesub_taskstatuses, incrementsub_tasks_completedhelpers.md#Move-Pipeline-with-Context(zh_story_id, "Review/QA")(best-effort: warn and continue)
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 inhelpers.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:
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)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-planexhaust,dev-failedstories,STALLEDteammates' stories, andquality-blockedstories — each with its reason. Nothing leaves this run unaccounted for. - Suggest
/dev-story STORY-{ID}for each
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.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-001 → Review/QA STORY-002 → Review/QA STORY-004 → Sprint 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-storyand STOP — do not attempt workarounds - Present the parallelization plan and wait for confirmation — unless
--plan-approvedsupplied 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,
openChildrenStatus→unknown) 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 becomesquality-blockedwith 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 runsgit 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, useplugins/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
ghCLI 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.