원클릭으로
approve
스펙을 승인하고 실행을 시작합니다. 사용자가 '승인', '진행해', 'OK 진행'을 말하거나 /mst:approve를 호출할 때 사용. Gran Maestro 워크플로우 내에서만 의미 있으며, 일반적인 확인 응답에는 사용하지 않음.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
스펙을 승인하고 실행을 시작합니다. 사용자가 '승인', '진행해', 'OK 진행'을 말하거나 /mst:approve를 호출할 때 사용. Gran Maestro 워크플로우 내에서만 의미 있으며, 일반적인 확인 응답에는 사용하지 않음.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
프로젝트 목표 + 설계 문서(objective.md)를 JTBD 기반 Q&A로 생성하고, 실행 전 검토 가능한 플래닝 세션을 초기화합니다.
프로젝트 목표를 제공하면 JTBD+프로젝트 DoD 기반 자율 실행을 수행합니다. Step 1은 agile-plan 서브스킬로 objective.md를 준비하고 Sprint 0 → Sprint N(프로젝트 건강 우선) 루프 → 스티어링 체크포인트를 반복합니다.
Claude provider 전용 native-first delegation entrypoint. 사용자가 '클로드로 실행', '클로드 서브에이전트'를 말하거나 /mst:claude를 호출할 때 사용한다. 같은 Claude host에서는 Task/Agent를 우선하고 route=external일 때만 managed wrapper를 사용한다.
Codex provider 작업을 native-first로 위임합니다. 사용자가 '코덱스 실행', '코덱스로', '코드 작업'을 말하거나 /mst:codex를 호출할 때 사용. 같은 Codex host에서는 collaboration agent를 우선하고, 중앙 route가 external일 때만 Codex CLI adapter를 사용합니다.
설정된 AI 에이전트들이 병렬로 버그를 조사하고 종합 리포트를 생성합니다. 사용자가 '디버그', '버그 찾아줘', '문제 분석'을 말하거나 /mst:debug를 호출할 때 사용. 1회성 의견 수집은 /mst:ideation을, 합의 토론은 /mst:discussion을 사용.
설정된 AI 팀원들이 합의에 도달할 때까지 반복 토론합니다. 사용자가 '토론', '합의', '디스커션'을 말하거나 /mst:discussion를 호출할 때 사용. 1회성 의견 수집은 /mst:ideation 사용.
| name | approve |
| description | 스펙을 승인하고 실행을 시작합니다. 사용자가 '승인', '진행해', 'OK 진행'을 말하거나 /mst:approve를 호출할 때 사용. Gran Maestro 워크플로우 내에서만 의미 있으며, 일반적인 확인 응답에는 사용하지 않음. |
| user-invocable | true |
| argument-hint | [-a|--auto] [REQ-ID...] [--stop-on-fail | --continue] [--parallel] [--priority <level>] |
PM이 작성한 구현 스펙을 승인하고 Phase 2 실행을 시작합니다. 단건/배치 승인 모두 지원. Phase 3 PASS 후 최종 수락은 workflow.auto_accept_result 설정에 따라 자동 실행되지만, request child accept는 session branch까지만 반영되며 original base merge는 session-level accept 또는 terminal_success evidence gate에 남겨둡니다.
[TRACE_SAVED] 등)를 종료 신호로 오해해 다음 Step 호출 없이 멈춘다.NEXT_ACTION 출력 직후 다음 Step 도구 호출을 실행한다.mst:request --resume ... -a를 즉시 호출한다.경로 규칙 (MANDATORY): 이 스킬의 모든
.gran-maestro/경로는 절대경로로 사용합니다. 스킬 실행 시작 시PROJECT_ROOT를 취득하고, 이후 모든 경로에{PROJECT_ROOT}/접두사를 붙입니다.PROJECT_ROOT=$(pwd)
{PLUGIN_ROOT}는 이 스킬의 "Base directory"에서skills/{스킬명}/을 제거한 절대경로입니다. 상대경로(.claude/...)는 절대 사용하지 않습니다.
이 프로토콜은 이 스킬 아래의 모든 provider 실행 예시보다 우선한다. provider 작업을 시작하기 전에 parent host가 route와 lifecycle evidence를 소유하고, child는 실제 할당 작업만 수행한다.
python3 {PLUGIN_ROOT}/scripts/mst.py host context --json을 실행하고 JSON의 host를 읽는다. 이 호출 실패, 잘못된 JSON, 알 수 없는 host는 임의 추정하지 말고 blocked로 종료한다.
이어서 반드시 아래 중앙 planner를 호출한다. {scope}는 현재 작업의 실제 scope(implementation, review, exploration, ideation, discussion, debug, analysis)이고, {provider}는 선택된 codex | claude | agy다.
python3 {PLUGIN_ROOT}/scripts/mst.py delegation route \
--host "{host}" \
--provider "{provider}" \
--scope "{scope}" \
--capability-status "{available|unknown|unavailable}"
route 결과 외의 근거로 transport를 바꾸지 않는다.
route=native_candidate: 같은 host/provider의 native bridge만 사용한다. handshake_required=true이면 실제 host tool 가용성을 확인한 뒤 진행한다.route=external: 이 경우에만 아래에 남아 있는 managed wrapper, dispatch build, provider CLI adapter 예시를 사용할 수 있다.route=blocked, CLI non-zero, lifecycle 응답의 status=blocked, 또는 현재 attempt의 phase=reconciling: 즉시 fail closed 한다. 같은 task/worktree에 새 agent나 external process를 시작하지 않는다.native_candidate 실행과 evidenceNative spawn 전 parent가 delegation start를 호출하고 반환된 attempt_id를 이후 모든 CAS 호출에 사용한다. start는 lifecycle 준비만 하며, 신규 응답이나 exact replay 모두 그 자체로 spawn 권한을 주지 않는다(spawn_allowed=false).
python3 {PLUGIN_ROOT}/scripts/mst.py delegation start \
--task-id "{task_id}" \
--idempotency-key "{task_id}:start:{stable_key}" \
--host "{host}" \
--provider "{provider}" \
--capability-status available \
--route-reason "{route.reason_code}" \
--worktree-dir "{worktree_path}" \
--model "{model}" \
--scope "{scope}" \
--prompt-file "{prompt_file}" \
--output-path "{output_path}"
analysis|review|exploration|ideation|discussion|debug가 실제 read-only 작업이고 별도 linked worktree를 쓰지 않는 경우에만 --read-only를 추가한다. 구현·수정 작업에는 이 예외를 사용하지 않는다.
그 다음 parent invocation별 고유한 {claimant_id}로 single-use spawn claim을 요청한다. 오직 이 호출에서 spawn_allowed=true와 non-empty private claim_token_file을 함께 받은 단 한 caller만 native host tool을 한 번 호출할 수 있다. raw bearer token은 CLI JSON, argv, process listing, tool transcript, child prompt에 노출하지 않는다.
python3 {PLUGIN_ROOT}/scripts/mst.py delegation claim-spawn \
--task-id "{task_id}" \
--attempt-id "{attempt_id}" \
--claimant-id "{claimant_id}" \
--idempotency-key "{task_id}:claim:{claimant_id}"
spawn_allowed=false, claim_status=claim_replay|already_claimed|reconciling|provider_task_in_flight|terminal, 빈 claim_token_file, 또는 claim 응답 유실/불명확 상태에서는 host tool을 호출하지 않는다. claim_replay|already_claimed는 winner의 claim lease가 살아 있는 동안 wait만 하며 recover/cancel로 ownership을 빼앗지 않는다. lease 만료 뒤에만 delegation recover로 reconcile하고, 그 외에는 next_action에 따라 기존 provider task에 attach/wait한다. claim exact replay는 bearer token/파일을 다시 발급하지 않는다. 따라서 claim 결과를 잃은 caller도 외부 fallback이나 중복 native spawn을 시도하지 않는다.
host=codex, provider=codex: Codex collaboration native tools를 사용한다. collaboration.spawn_agent로 spawn하고, host가 제공하는 attach/follow-up 수단으로 같은 task에 연결하며, collaboration.wait_agent로 대기한 뒤 전달된 completion result를 수집한다. 병렬 fan-out은 독립 task마다 native agent를 하나씩 spawn한다.host=claude, provider=claude: Claude의 Task(...) 또는 Agent(...) native tool로 spawn한다. background task는 host의 TaskOutput/resume 결과로 대기·수집한다.codex exec, claude CLI, mst.py run --provider {same_provider}, 같은 provider의 managed wrapper, 또는 nested /mst:claude//mst:codex를 호출하지 않는다.Native tool 응답마다 claim winner parent가 다음 순서로 evidence를 기록한다. {claim_token_file}은 winner 응답의 mode 0400 private one-shot handle이며 acknowledge 성공 시 삭제된다. 내용을 읽거나 복사하거나 child/user/log에 전달하지 않는다. 각 명령의 JSON 응답에서 status/phase를 확인하고 blocked/reconciling이면 더 진행하지 않는다.
delegation acknowledge --task-id "{task_id}" --attempt-id "{attempt_id}" --claim-token-file "{claim_token_file}" --spawn-status created_with_task_id --provider-task-id "{provider_task_id}" --idempotency-key "{task_id}:ack:{stable_key}"delegation attach --task-id "{task_id}" --attempt-id "{attempt_id}" --attach-status attached --idempotency-key "{task_id}:attach:{stable_key}"delegation heartbeat --task-id "{task_id}" --attempt-id "{attempt_id}" --provider-state running --idempotency-key "{task_id}:heartbeat:{sequence}"{output_path}의 sibling temp file에 먼저 쓰고 atomic replace한 뒤, fresh hash/size를 확인한다. child에게 이 파일 쓰기를 맡기거나 기존 파일을 재사용하지 않는다.delegation complete --task-id "{task_id}" --attempt-id "{attempt_id}" --completion-signal "{succeeded|failed|timeout|unknown}" --output-path "{output_path}" --idempotency-key "{task_id}:complete:{stable_key}"Native spawn이 task 생성 전에 명확히 실패한 경우에만 claim winner가 같은 --claim-token-file "{claim_token_file}"로 spawn-status=definitive_not_created를 acknowledge한 뒤 delegation fallback --expected-attempt-id "{attempt_id}" ...를 요청할 수 있다. 그 후 capability를 unavailable로 route planner에 다시 전달해 route=external을 받은 경우에만 external lane을 실행한다. claim 결과 유실, accepted, task ID 발급, attach 실패/timeout, child 실패, unknown/indeterminate 결과 뒤에는 external fallback을 금지하고 reconcile 상태를 유지한다.
route=external 판정만으로 provider command를 직접 만들지 않는다. Fresh headless/cross-provider external lane은 command 생성 전에 중앙 planner 결과를 state에 고정한다.
python3 {PLUGIN_ROOT}/scripts/mst.py dispatch authorize-external \
--provider "{provider}" \
--task-id "{task_id}" \
--prompt-file "{prompt_file}" \
--worktree-dir "{worktree_path}" \
--running-log-path "{running_log}" \
--trace-path "{trace_path}" \
--output-path "{output_path}" \
--model "{model}" \
--scope "{scope}" \
--idempotency-key "{task_id}:external-authorize:{stable_key}" \
{read_only_flag}
이 명령은 실제 host를 다시 확인하고 중앙 route가 여전히 external일 때만 current external attempt와 model/running/trace/output binding을 저장한다. 구현·수정 lane은 registered linked worktree를 사용하고 {read_only_flag}를 비운다. 실제 read-only scope만 --read-only를 사용한다. 반환된 attempt_id와 동일한 artifact binding을 external wrapper에 전달한다.
python3 {PLUGIN_ROOT}/scripts/mst.py dispatch build \
--provider "{provider}" \
--task-id "{task_id}" \
--prompt-file "{prompt_file}" \
--worktree-dir "{worktree_path}" \
--log-file "{running_log}" \
--model "{model}" \
--expected-attempt-id "{external_attempt_id}"
Native definitive non-creation fallback이면 새 authorization을 만들지 않고 delegation fallback이 반환한 external attempt_id를 --expected-attempt-id로 사용한다. Builder는 current attempt의 task/provider/resolved worktree/prompt hash/route를 재검증하므로 native, reconciling, stale attempt, 또는 mismatch 상태에서는 command를 만들지 않는다. Codex/Claude 보호 wrapper는 provider command나 split claim/finalize shell을 포함하지 않고 dispatch run-external 단일 감독자만 호출한다. 감독자는 먼저 side effect가 없는 anonymous exec gate를 띄워 PID/PGID/start identity를 CAS로 attach하고, 같은 task lock 안에서 취소보다 먼저 exec 권한이 확정된 경우에만 실제 provider를 release한다. claim에서 캡처한 정확한 prompt bytes를 stdin으로 전달하고, provider process group을 회수한 뒤 fresh single-link inode로 claim해 계속 보유한 non-following output descriptor로 결과를 게시한다. prompt/snapshot/running/trace/output alias와 MST state·lock·history reserved path alias는 provider spawn 전에 차단한다. claim-external/heartbeat-external/finalize-external을 별도로 호출하거나 prompt snapshot/output pathname을 shell에서 다시 열지 않는다. Prompt 본문·snapshot path·claim secret·descriptor number는 argv/state/history에 확장하지 않는다.
모든 native child prompt에는 다음 제약을 그대로 포함한다.
DELEGATION BOUNDARY (MANDATORY)
- Complete the assigned task yourself; do not delegate or spawn another provider agent.
- Do not invoke codex/claude provider CLIs, /mst:codex, /mst:claude, or a same-provider managed wrapper.
- Do not call `mst.py delegation` lifecycle commands and do not edit `.gran-maestro/run`, session, or history state; the parent owns routing and evidence.
- Work only in the assigned worktree/scope and return the result/evidence to the parent.
아래 skill별 dispatch 예시는 이 protocol의 route로 gate한다. Provider CLI/managed wrapper 예시는 오직 route=external일 때만 사용한다. Task/Agent/Codex collaboration 예시는 host와 provider가 일치하는 route=native_candidate일 때만 사용하고 child boundary와 native lifecycle evidence를 함께 적용한다.
State execution contract: state write commands inherit MST_SESSION_ID from the current session or receive equivalent structured context; do not inject process-scoped identity into canonical writes.
Parent session inheritance contract: child invocation, subprocess, and hook execution inherit parent MST_SESSION_ID; children must not issue arbitrary mst_session_id. Hook payload mst_session_id is allowed only when it matches the inherited parent MST_SESSION_ID.
DOD-007 canonical identity boundary: MST_SESSION_ID / mst_session_id만 canonical identity source다. Legacy-only input(MST_STATE_PPID, owner_ppid, owner_session_id, owner_pid, Claude hook session_id, transcript UUID, MST_SNAPSHOT_SESSION_ID, legacy aliases sessionId/session_id)은 diagnostic-only이며 canonical source, fallback, alias, migration requirement가 아니다. Legacy-only input은 session/state/history/snapshot/recovery/lock mutation 없이 structured non-success로 종료해야 한다. Canonical MST_SESSION_ID/mst_session_id와 legacy 값이 충돌하면 canonical identity가 우선하고 legacy 값은 override/repair/merge/persist source가 될 수 없다.
DOD-009 session identity glossary: mst_session_id is the canonical state machine identity payload/context field issued by mst.py as MST-{root_mst_id}-{started_at_compact}-{random}; it partitions .gran-maestro/state/{mst_session_id}/snapshot.json and .gran-maestro/sessions/{mst_session_id}/history.*. MST_SESSION_ID is the environment variable carrying the same canonical identity through child invocation, subprocess, and hook execution. A root resource ID such as AGI-030, PLN-638, or REQ-* can be the root component inside mst_session_id, but it is not the full canonical session identity. A process diagnostic ID such as owner_pid, MST_STATE_PPID, hook session_id, or transcript UUID is diagnostic-only; diagnostic output is allowed, but those values are not canonical source, fallback, alias, migration requirement. legacy aliases such as session_id, sessionId, or MST_SNAPSHOT_SESSION_ID are compatibility diagnostics and not canonical source, fallback, alias, migration requirement. source precedence is validated history ledger, validated state snapshot, then prompt summary as diagnostic-only context.
~/.claude/user-profile.json (User Input Boundary 컨텍스트, 비차단)~/.claude/user-profile.json을 Read한다.
user_profile_context = null로 처리하고 기존 동작을 유지한다 (graceful fallback).role (string)experience_level (string)domain_knowledge (string[])communication_style (string)user_profile_context = null로 처리한다 (워크플로우 차단 금지).communication_style을 최우선 반영한다.experience_level/domain_knowledge에 맞춰 용어 수준과 설명 깊이를 조절한다.외부 의존성(라이브러리/API/프레임워크/버전/프로토콜) 판단은 아래 공통 프로토콜을 따른다.
Bash(python3 {PLUGIN_ROOT}/scripts/mst.py config get reference.auto_search)로 reference.auto_search를 확인한다. true일 때만 자동 WebSearch를 허용한다. 설정 미존재 시 기본값은 cache_ttl_days=2, cutoff_threshold_months=0.5, max_searches_per_step=5, llm_auto_trigger=true, auto_fact_check=true.reference.llm_auto_trigger == true이면 PM이 최신 정보가 필요하다고 판단할 때도 WebSearch를 트리거한다. false이면 키워드 매칭 기반 동작만 유지한다..gran-maestro/references/ 캐시를 reference search --keyword "{keyword}" --json으로 확인, (b) searched_at + cache_ttl_days 기준 fresh/stale 판정, (c) 현재 시각 대비 cutoff_threshold_months 초과 시 expired 판정.stale/expired일 때만 검색한다. reference.auto_search == true일 때만 실행하고 Step당 max_searches_per_step을 넘지 않는다. reference.auto_fact_check == true이면 핵심 claim을 1회성 교차 WebSearch로 경량 검증한다.Bash로 mst.py reference add를 호출한다. 표/텍스트 결론 요약만으로는 저장 완료가 아니며 content.md는 raw 발췌(원문 근거) 중심으로 남긴다.
python3 {PLUGIN_ROOT}/scripts/mst.py reference add --topic "{topic}" --url "{url}" --summary "{summary}" --content "{raw 발췌 본문}"summary는 한 줄 인덱스 유지).> 인용: "{원문 핵심 문장}" (출처: {URL}, 날짜: {YYYY-MM-DD})| 열 | 값 | 형태의 raw markdown table과 출처 URL을 보존한다.content.md Read 필수): summary만으로 부족하거나 표/코드/원문 뉘앙스가 결론에 영향을 주면 반드시 content.md를 Read한다.[REFERENCE_CONTEXT]를 주입한다. 형식: current_date, model_cutoff, references: REF-001 (fresh|stale|expired) {topic} | {url}. 참조가 없으면 references: none으로 명시한다.$ARGUMENTS를 파싱하여 승인 대상 REQ 리스트를 결정합니다. 아래 규칙을 순서대로 적용합니다.
$ARGUMENTS가 단일 REQ 패턴(REQ-NNN)이면 단건 승인 프로토콜을 직접 실행합니다.
공백 구분 REQ 패턴이 2개 이상이면 토글 UI 없이 직접 배치 실행합니다.
콤마(,)나 범위(..) 포함 인자를 파싱합니다. 예시:
/mst:approve REQ-001,REQ-003,REQ-005 → [REQ-001, REQ-003, REQ-005]
/mst:approve REQ-001..005 → [REQ-001, REQ-002, REQ-003, REQ-004, REQ-005]
범위 지정 시 승인 가능 상태인 REQ만 결과 리스트에 포함.
--priority 필터링--priority <level> 플래그가 있으면 해당 우선순위의 승인 가능 REQ만 필터링합니다. request.json의 priority 필드 기준. 필드 없는 REQ는 normal로 취급. REQ 패턴/범위와 조합 가능.
$ARGUMENTS에 REQ 패턴이 없고 플래그만 있거나 완전히 비어 있는 경우:
스크립트 우선: python3 {PLUGIN_ROOT}/scripts/mst.py request filter --phase 1 --format json 실행 후 status가 phase1_analysis 또는 pending_dependency가 아닌 것 필터링. 실패 시 fallback.
Fallback:
{PROJECT_ROOT}/.gran-maestro/requests/ 디렉토리의 모든 request.json 스캔current_phase == 1 이고 status가 phase1_analysis 또는 pending_dependency가 아닌 것, 또는 status가 phase2_spec_review인 것--priority 필터 있으면 추가 적용, REQ 번호 오름차순 정렬| 승인 대기 REQ 수 | 환경 | 동작 |
|---|---|---|
| 0개 | — | "승인 대기 중인 요청이 없습니다" 메시지 후 종료 |
| 1개 | — | 기존 단건 동작 그대로 (스펙 요약 → 승인 → Phase 2) |
| 2개+ | 대화형 (TTY) | 토글 선택 UI 진입 (아래 참조) |
| 2개+ | 비대화형 | 기존 동작 유지 (첫 번째 REQ 자동 선택, 단건 실행) |
승인 대기 REQ가 2개 이상이고 대화형(TTY) 환경일 때:
배지 생성 규칙: dependencies.blockedBy → [←REQ-MMM], dependencies.blocks → [→REQ-PPP], 복합 [←MMM →PPP]. 없으면 생략.
AskUserQuestion의 multiSelect 옵션 사용:
label: "A. REQ-NNN {title 앞 18자} [←REQ-MMM →REQ-PPP]" (배지 있을 때)description: "[장점] 선택한 요청을 배치 실행에 포함합니다. [단점] 의존성이 있는 경우 선행 실패의 영향을 받습니다. [적합] Phase 1 완료, 태스크 N개 | 선행: REQ-MMM | 후행: REQ-PPP" (의존성 있을 때)REQ-NNN — {title} [배지] [태스크 M개] 형식"A. 전체 선택" 또는 "B. ID 직접 입력""A. 전체 선택": 전체 대기 REQ 배치 실행."B. ID 직접 입력": 2차 AskUserQuestion에서 REQ ID 자유 입력 → "콤마 구분 및 범위 지정" 파싱 로직으로 처리 → 배치 실행. 빈 입력/0건 → "선택된 요청이 없습니다" 후 종료.AUTO_MODE 초기화 (단건 프로토콜 진입 즉시):
--auto 또는 -a가 있으면 AUTO_MODE=true (최우선){PLUGIN_ROOT}/scripts 경유로 read_workflow_state_auto_mode("mst:approve", "{REQ-ID}") 호출
AUTO_MODE에 채택None이면 request.json.auto_approve == true 여부를 확인config.auto_mode.approve 확인AUTO_MODE=false우선순위: args > state(guarded, expected_source_id=REQ_ID) > request.json.auto_approve > config > false
이후 모든 Step에서 이 변수를 사용한다.
AUTO_MODE=true이면 단건 프로토콜 진입 직후 workflow state를 기록한다 (non-blocking):
# RESOLVED(PLN-509): agile_loop_active 보존 — plan/agile 맥락은 Step 4b 브리프 변수(PLAN_JSON_META/PAC_LIST/OBJECTIVE_SECTION)로 주입됨 (PLN-469 → PLN-509)
python3 {PLUGIN_ROOT}/scripts/mst.py state set-workflow \
--active true \
--skill mst:approve \
--req "{REQ-ID}" \
--next-skill mst:accept \
--next-source "{REQ-ID}" \
--source-skill mst:approve \
--auto true \
|| echo "[mst:approve] warning: failed to update workflow state" >&2
AUTO_MODE=false에서는 이 호출을 실행하지 않는다.
세션 중 자율 모드 전환: AskUserQuestion 대기 중 사용자가 "auto로 해줘", "자율 모드로", "-a로", "지금부터 자동으로" 등을 입력하면 즉시 AUTO_MODE=true로 전환합니다. 전환 즉시 [자율 모드 전환] 이제부터 -a 모드로 진행합니다. 출력 후 현재 Step부터 AUTO_MODE=true 적용하여 재개.
REQ 리스트가 1건이거나 명시적 단건 인자 호출 시 이 프로토콜을 실행합니다.
{PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/tasks/ 하위 spec.md 확인
dependencies.blocks 비어있지 않음 AND workflow.auto_approve_on_unblock == false):AUTO_MODE=true 또는 request.json.auto_approve=true이면 AskUserQuestion 없이 기본값("아니오, 각 단계마다 수동 approve") 적용 후 즉시 Step 2.5로 진행이 REQ가 완료되면 아래 REQ들이 순서대로 실행 가능해집니다:
REQ-NNN — {title} (대기 중)
REQ-MMM — {title} (대기 중) ← REQ-NNN 완료 후
(blocks 배열의 직접 후속 REQ만 표시; 재귀 조회는 1단계만)config.json의 workflow.auto_approve_on_unblock을 true로 업데이트
알림: "✓ 이후 모든 체인에서 의존성 해소 시 자동 approve가 실행됩니다. (/mst:settings workflow.auto_approve_on_unblock false로 되돌릴 수 있습니다)"구현을 시작하기 전 아래 검사를 수행한다:
"Test Scenarios (Pre-Impl)" 문자열 포함 검사(contains) — "## Test Scenarios (Pre-Impl)" (번호 없음) 또는 "## N.N Test Scenarios (Pre-Impl)" (번호 있음) 모두 허용.Test: 항목(실행 명령 또는 확인 방법) 기입 여부 확인통과 조건: 섹션 존재 + 모든 automatable AC에 Test 항목 기입 실패 시: 구현 착수 중단 → "Pre-Impl Test Scenarios 미작성" 오류 반환
예외: manual AC만 있는 spec은 Test Scenarios 섹션이 비어있어도 통과 허용
preflight 검사가 통과된 경우에만 아래 base 감지/protected 검사를 실행하고, 이 검사가 통과된 경우에만 Step 3(worktree 생성 및 구현 착수)로 진행.
Session parent base resolve + protected original guard (차단 검사, preflight 통과 이후 실행):
Bash(python3 {PLUGIN_ROOT}/scripts/mst.py worktree resolve-base --req {REQ-ID} --json)를 실행한다.
MST_SESSION_ID와 active/reused session metadata가 있는 정상 경로에서 stdout JSON의 base를 SESSION_BASE_BRANCH로 사용한다.{PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/request.json의 detected_base 필드는 session_branch와 같은 값으로 저장되어야 한다.parent_mst_session_id, parent_session_branch, parent_session_worktree_path, original_base_branch, original_base_sha가 포함되어야 한다.original_base_branch/original_base_sha reference로만 보존하며, final original merge trigger/scope는 DOD-005/DOD-013 범위로 남긴다.worktree.base_branch는 하위 호환 설정으로만 남기며 approve의 신규 child base 결정에는 사용하지 않는다.python3 {PLUGIN_ROOT}/scripts/mst.py request set-phase {REQ_ID} 2 phase2_execution; 실패 시 fallback으로 request.json의 current_phase=2, status=phase2_execution 직접 업데이트strategy.worktree_policy == "skip"이면 worktree 생성을 스킵하고 {PROJECT_ROOT}에서 직접 작업, 그렇지 않으면 각 태스크에 대해 git worktree 생성REQ 리스트가 2건 이상일 때, 배치 실행 루프 진입 전 선택된 REQ 집합의 의존성 위반을 검사합니다.
violations = []
for req_id in selected:
req = read_request_json(req_id)
for dep in req.dependencies.blockedBy:
if dep not in selected:
violations.append({ req: req_id, missing_prereq: dep })
if violations:
출력: "⚠️ 의존성 위반 감지:"
for v in violations:
출력: " - {v.req}은 {v.missing_prereq}이 먼저 완료되어야 하나 선택 목록에 없음"
AskUserQuestion:
- "누락된 선행 REQ 추가하여 전체 체인 실행" → 누락 REQ를 selected에 추가 후 재진행
- "후행 REQ 제외하고 선택된 것만 실행" → violations의 후행 REQ를 selected에서 제거 후 재진행
- "취소" → 종료
위반이 없거나 사용자 선택 후 재진행 시, 아래 배치 실행 루프로 진입합니다.
REQ 리스트가 2건 이상일 때 실행합니다.
| 플래그 | 동작 |
|---|---|
| (기본, 플래그 없음) | 순차 실행 — 각 REQ의 전체 라이프사이클(Phase 2 → 3 → 5) 완료 후 다음 REQ |
--parallel | 병렬 실행 — concurrency.batch_max_parallel_reqs만큼 REQ를 동시 실행 |
의존성 토폴로지 정렬을 수행하여 Wave 단위로 실행합니다. 의존성이 없는 REQ는 단일 Wave로 묶입니다.
topological_sort_into_waves 알고리즘:
def topological_sort_into_waves(req_ids):
in_degree = {r: 0 for r in req_ids}
for r in req_ids:
for dep in read_request_json(r).dependencies.blockedBy:
if dep in req_ids:
in_degree[r] += 1
waves = []
remaining = set(req_ids)
while remaining:
wave = [r for r in remaining if in_degree[r] == 0]
if not wave: # 사이클 감지
경고: "의존성 사이클 감지, 남은 REQ는 독립 실행"
wave = list(remaining)
waves.append(sorted(wave))
for r in wave:
remaining.remove(r)
for s in remaining:
if r in read_request_json(s).dependencies.blockedBy:
in_degree[s] -= 1
return waves
Wave 캐스케이드 실행:
waves = topological_sort_into_waves(req_list)
출력: "실행 계획:"
for i, wave in enumerate(waves):
출력: " Wave {i+1}: {wave} (순차 실행)"
all_results = []
outer: for wave_num, wave in enumerate(waves):
출력: "── Wave {wave_num+1}/{len(waves)} 시작 ──"
wave_results = []
for req_id in wave:
result = 단건 승인 프로토콜 실행(req_id, AUTO_MODE=현재 AUTO_MODE 값)
wave_results.append(result)
if result == FAILED:
오류 처리 규칙 적용 (§ 배치 오류 처리)
if 중단 결정:
남은 REQ (현재 Wave 미실행 + 이후 Wave 전체) → skipped
break outer
all_results.extend(wave_results)
failed_in_wave = [r.req_id for r in wave_results if r.status == FAILED]
if failed_in_wave:
이후 Wave에서 failed REQ를 blockedBy로 가진 REQ들 → 자동 Skip 마킹
출력: "의존 REQ N개를 Skip합니다" 알림
최종 요약 출력(all_results)
--parallel)--parallel 플래그 사용 시에도 Wave 경계는 준수합니다. Wave 내 REQ들은 병렬 실행하고, Wave 간에는 순차 유지.
config.concurrency.batch_max_parallel_reqs 값으로 동시 실행 REQ 수를 결정합니다.
max_concurrent = config.concurrency.batch_max_parallel_reqs # 기본 1
queue = req_list.copy()
running = {}
results = []
while queue 또는 running:
while len(running) < max_concurrent and queue:
req_id = queue.pop(0)
if has_failed_dependency(req_id, results):
results.append({req_id, status: "skipped", reason: "의존 REQ 실패"})
continue
출력: "[진행] {req_id} — 승인 시작..."
task = 비동기로 단건 승인 프로토콜 실행(req_id) # run_in_background
running[req_id] = task
for req_id, task in running:
if task.completed:
results.append(task.result)
running.remove(req_id)
출력: "[완료] {req_id} — {status}"
sleep(backoff)
최종 요약 출력(results)
슬롯 관리: 전역 동시 태스크 수는
min(batch_max_parallel_reqs × max_tasks_per_req, worktree.max_active)로 제한.
순차: [1/3] REQ-013 "JWT 미들웨어" — 승인 중... → 실행 중... → 완료
병렬: [병렬 2/3] REQ-013 시작 | REQ-014 시작
최종 요약:
═══ 배치 승인 완료 ═══
성공: 2 | 실패: 1 | 건너뜀: 0
REQ-015: Phase 2 사전검증 실패 (tsc error) → /mst:approve REQ-015 로 재시도
| 환경 | 기본 동작 | 세부 |
|---|---|---|
| 대화형 (TTY) | Prompt | Continue / Skip / Retry / Abort 4지선다 제시. 기본 커서 위치: Continue |
| 비대화형 (CI) | Continue | 실패 REQ는 failed 마킹 후 나머지 계속 진행. 최종 exit code: 실패 1건 이상이면 non-zero |
의존성 기반 예외: dependencies.blockedBy 관계에서 선행 REQ 실패 시 후속 REQ 자동 Skip (환경 불문). blockedBy 미기재 시 독립 REQ로 취급.
행동 수정자: --stop-on-fail — 첫 실패 시 즉시 중단. --continue — 실패 무시 후 계속. (의존성 Skip은 양쪽 모두 유지)
실패한 REQ의 status를 failed로 마킹. 재진입: /mst:approve REQ-NNN 단건 호출 또는 다음 배치 시 토글 UI 재선택.
OMX_AUTOPILOT = (config.omx.enabled == true && config.omx.autopilot == true) → config.omx 키 미존재 시 false로 처리 (fallback)
이 값을 Step 4c / Fix / Escalation에서 참조한다.
Phase 2에서 Claude(PM)는 절대 코드를 직접 작성하지 않습니다. 모든 구현은 /mst:codex 또는 /mst:agy로 외주합니다.
request.json.source_plan -> plan.json.type -> type-strategies.json 체인으로 실행 전략을 결정한다.
source_plan = request.json.source_plan
plan_type = "code"
if source_plan exists:
plan = Read({PROJECT_ROOT}/.gran-maestro/plans/{source_plan}/plan.json)
plan_type = plan.type if plan.type exists else "code"
type_strategies = Read({PLUGIN_ROOT}/templates/defaults/type-strategies.json)
strategy = type_strategies[plan_type] || type_strategies["code"]
if Read/parse/key lookup failed:
strategy = {
"template": "templates/impl-request.md",
"worktree_policy": "required",
"review_mode": "code",
"accept_mode": "squash-merge"
} # 하위 호환
strategy.worktree_policy == "skip"이면 DocExecutor 전략(문서 초안 생성 → 구조 검증 → 팩트체크)을 사용한다.각 태스크의 spec.md를 Read하기 전 경로 유효성을 확인합니다:
"spec.md 읽기 실패 (경로: {spec_path}) — 워크트리 구조 확인 필요" 오류를 반환하고 해당 태스크의 구현 착수를 차단합니다.모든 태스크의 spec.md를 일괄 검증합니다. 다음 항목이 명확한지 확인, 부족하면 보완:
Ideation 자동 트리거 (LLM 판단): 아래 상황 감지 시 /mst:ideation 호출하여 스펙 보완:
spec.md §7에서 blockedBy 배열 읽기blockedBy 비어있음 → 즉시 실행blockedBy 있음 → 선행 완료 후 실행Wave 1: {독립 태스크 목록} (병렬 실행)
Wave 2: {Wave 1 완료 후 실행 가능한 태스크} (병렬 실행)
spec.md 헤더의 Assigned Agent 필드를 읽어 에이전트를 결정합니다.
| 태스크 유형 | 에이전트 | capabilities |
|---|---|---|
| 백엔드, 리팩토링, 테스트 | codex-dev → /mst:codex | code, refactor, test |
신규 .ts 파일 생성, 단순 리팩토링·보일러플레이트, 독립 테스트 작성, 소규모 .ts 인라인 수정 | codex-dev → /mst:codex | code, refactor, test |
| 프론트엔드, 문서, 대용량 컨텍스트 | agy-dev → /mst:agy | frontend, docs, large-context |
.md 문서, .json/.env config, *.config.ts, 기존 .ts 인라인 수정(신규 .ts 생성 없음) | Codex-primary: codex-dev → /mst:codex; legacy Claude preset: claude-dev → /mst:claude | code, docs, config, small-inline |
경계 케이스 기본값: 태스크 유형이 모호한 경우 →
Bash(python3 {PLUGIN_ROOT}/scripts/mst.py config get workflow.default_agent)값 사용 (claude-dev하드코딩 금지). Route guard: shared routing protocol의host context와delegation route를 먼저 실행한다. Same-hostnative_candidate는 Codex collaboration 또는 Claude Task/Agent를 사용하며 provider CLI 설치 여부를 요구하지 않는다. CLI preflight와mst.py run/dispatch build는route=external일 때만 수행한다.
claude와 claude-dev는 동일하게 처리됩니다 (하위 호환).
Assigned Agent 필드 읽기: (1) 최종: 패턴이 있으면 최종: 이후 값을 에이전트명으로 사용. (2) 최종: 패턴이 없으면 필드 값 전체를 사용. (3) 필드가 없거나 비어있으면 workflow.default_agent를 fallback으로 사용.
Assigned Agent: claude/claude-dev인 경우: Step 4에서 Claude provider를 선택하고 shared routing protocol로 위임한다. Same-host는 Task/Agent, external route만 /mst:claude managed wrapper를 사용한다. PM은 직접 구현하지 않습니다.
REQ 브랜치 생성 (태스크 수와 무관한 공통 선행 단계):
SESSION_BASE_BRANCH="{Step 2.7에서 저장한 request.json.detected_base}"
REQ_BRANCH=$(python3 {PLUGIN_ROOT}/scripts/mst.py worktree branch-name --req REQ-NNN --base "$SESSION_BASE_BRANCH" --role integration --agi "${AGI_ID:-}")
INTEGRATION_WORKTREE=$(python3 {PLUGIN_ROOT}/scripts/mst.py worktree path --req REQ-NNN --role integration --agi "${AGI_ID:-}")
python3 {PLUGIN_ROOT}/scripts/mst.py worktree create --path "$INTEGRATION_WORKTREE" --branch "$REQ_BRANCH" --base "$SESSION_BASE_BRANCH"
REQ 브랜치명은 AGI_ID가 있으면 gran-maestro/{base_slug}/{AGI_ID}/REQ-NNN, 없으면 legacy fallback으로 gran-maestro/{base_slug}/REQ-NNN 형식이다. base_slug는 session branch base의 /만 -로 치환한다.
REQ 브랜치 checkout과 태스크 통합은 원본 PROJECT_ROOT 또는 original checkout이 아니라 INTEGRATION_WORKTREE에서만 수행한다.
accept 단계는 후속 수락 정책에서 request.json.detected_base와 original base reference를 구분해야 하며, final original branch merge trigger/scope는 DOD-005/DOD-013 범위로 남긴다.
단일 태스크 REQ에서도 반드시 integration worktree를 생성해야 accept의 3단계 플로우가 정상 작동한다.
태스크가 1개인 경우: 기존 순차 실행과 동일 처리.
실행 타입 분기 (if 1개, MANDATORY):
if strategy.worktree_policy == "skip":
{PROJECT_ROOT}에서 직접 작업한다.templates/doc-request.md 템플릿을 사용한다.else (strategy.worktree_policy != "skip"):
태스크가 2개 이상이고 독립 태스크가 존재하는 경우 (strategy.worktree_policy != "skip"):
독립 태스크들의 git worktree를 미리 생성합니다. 태스크 worktree는 integration worktree에서 준비된 REQ 브랜치를 기준으로 생성:
TASK_BRANCH=$(python3 {PLUGIN_ROOT}/scripts/mst.py worktree branch-name --req REQ-NNN --task T01 --base "$SESSION_BASE_BRANCH" --agi "${AGI_ID:-}")
python3 {PLUGIN_ROOT}/scripts/mst.py worktree create --path {worktree_path} --branch "$TASK_BRANCH" --base "$REQ_BRANCH"
독립 태스크들의 브리프 파일을 하나의 메시지에서 동시에 Write 호출합니다.
Write -> {PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/tasks/{NN}/prompts/phase2-impl.md
브리프는 templates/impl-request.md 템플릿 사용. (strategy.worktree_policy != "skip" 경로)
[CONTEXT_FILES]와 [WORK_CONTRACT] block이 모두 포함되어야 한다.[CONTEXT_FILES] 필수 항목: objective, objective_ids, plan, plan_json, plan_ids, spec, spec_context_manifest, previous_feedback[WORK_CONTRACT] 필수 항목: read_requirements, output_contract, verification_contract, failure_contractcompletion report, Read/inspection evidence, verify_cmd, expected_signal{{IMPL_CONTEXT}}: PM 작성 — 3~5줄 자유 형식 (무엇을, 왜, 어떻게 + 주의사항)
Reference Lookup Protocol을 먼저 실행하고, 생성된 [REFERENCE_CONTEXT] 블록을 {{IMPL_CONTEXT}} 끝에 주입한다.reference.auto_search != true이면 자동 WebSearch 없이 기존 REF 캐시 조회 결과만 주입한다.request.json에 linked_designs가 존재하고 비어있지 않으면, {{IMPL_CONTEXT}} 끝에 "spec.md §10의 Stitch HTML 파일을 참조하되 기술 스택에 맞게 구현하세요." 자동 추가.{{SPEC_PATH}}, {{WORKTREE_PATH}}, {{REQ_ID}}, {{TASK_ID}}: 자동 주입{{PLAN_PATH}}: request.json.source_plan 존재 시 {PROJECT_ROOT}/.gran-maestro/plans/{source_plan}/plan.md, 미존재 시 NO_SOURCE_PLAN{{PREV_FEEDBACK_PATH}}: 첫 실행 시 "N/A", 재실행 시 feedback 파일 경로{{PLAN_JSON_META}}: resolve 순서 request.json → plan.json → plan.ids.json → objective.md. request.json.source_plan이 존재하면 {PROJECT_ROOT}/.gran-maestro/plans/{source_plan}/plan.json을 Read하여 cynefin_domain, linked_objective, linked_intent, linked_captures 필드와 원본 경로를 3~5줄 요약으로 주입한다. linked_intent가 있으면 python3 {PLUGIN_ROOT}/scripts/mst.py intent get {INTENT_ID} --json으로 원본 intent를 조회하고, 반환된 원본 경로 또는 {PROJECT_ROOT}/.gran-maestro/intent/{INTENT_ID}*.md 조회 패턴과 확인 결과를 함께 주입한다. 미존재 시 warn 로그 + NO_PLAN_JSON, NO_LINKED_INTENT, missing_context, 또는 명시적 skip reason으로 치환한다.{{PAC_LIST}}: source_plan이 존재하면 {PROJECT_ROOT}/.gran-maestro/plans/{source_plan}/plan.ids.json을 Read하여 경로와 각 항목의 id, grade, tags, text 필드를 목록으로 주입한다. 미존재 시 warn 로그 + NO_PLAN_IDS, missing_context, 또는 명시적 skip reason으로 치환한다.{{OBJECTIVE_SECTION}}: plan.json.linked_objective가 존재하면 {PROJECT_ROOT}/.gran-maestro/agile/{AGI-NNN}/objective/objective.md와 {PROJECT_ROOT}/.gran-maestro/agile/{AGI-NNN}/objective/objective.ids.json(존재 시)을 Read하여 원본 경로, JTBD 요약, 프로젝트 DoD 항목, 성공 지표, objective anchor coverage evidence를 3~5줄 요약으로 주입한다. legacy plan에서 linked_objective/linked_intent/plan.ids.json 각각 미존재 시 warn 로그 + NO_LINKED_OBJECTIVE, NO_OBJECTIVE_IDS, NO_LINKED_INTENT, NO_PLAN_IDS, missing_context, 또는 명시적 skip reason으로 치환한다. agile-origin objective anchor metadata가 있는데 anchor manifest나 coverage evidence가 없으면 "N/A"로 숨기지 말고 brief에 누락 evidence를 남기고 review/accept가 확인할 수 있게 전달한다.spec_context_manifest는 항상 {{SPEC_PATH}}#§0-Context-Manifest로 전달하고, spec에 해당 섹션이 없으면 NO_CONTEXT_MANIFEST 또는 missing_context를 남긴다.previous_feedback는 첫 실행에만 N/A 허용, 그 외 재실행 경로에서는 feedback 파일 경로나 명시적 skip reason을 남긴다.plan.md, plan.json, plan.ids.json, linked objective/intent 원본, spec §0 Context Manifest 원본 파일을 구현 전 직접 Read/inspection하라는 지시와 완료 보고의 Read/inspection evidence 기록 요구를 반드시 포함한다. source_plan이 없는 legacy 요청은 이 요구를 hard fail로 적용하지 않고 NO_SOURCE_PLAN 또는 동등한 structured skip reason을 남긴다.plan:은 path-first required slot이므로 N/A로 렌더링하지 않는다. {{PLAN_PATH}}는 실제 plan.md 절대경로이거나 NO_SOURCE_PLAN이어야 하며, previous_feedback만 첫 실행에서 N/A를 유지할 수 있다.각 태스크에 shared routing protocol을 독립 적용한다. Same-host native fan-out은 host native background agent를 사용하고, run_in_background: true 기반 Bash/managed wrapper 예시는 해당 태스크의 route=external일 때만 사용한다.
{task_dir} = {PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/tasks/{TASK-NUM}/
agy-dev direct Bash exception은 병렬 dispatch(parallel dispatch)에서 Skill(mst:agy) 직렬 호출로 전환할 수 없는 경우에만 허용한다./mst:agy identity를 대체하지 않으며, prompt-file path와 context file path inspection 결과를 브리프와 completion report에 남겨야 한다.running.log, worktree path, trace label 또는 trace-equivalent id, final exit evidence, evidence path, evidence id를 포함한다.rate_limit, timeout, empty_result, nonzero_exit로 구분하고, 429/rate-limit/quota 신호는 rate_limit으로 기록한다.agy-dev → codex fallback 정책에 따라 structured failure_kind와 lifecycle evidence가 존재할 때만 실행 또는 갭 태스크로 기록한다.⚠️ agy-dev Bash 강제 (MANDATORY): agy-dev는 단건/병렬 무관하게 항상
Bash(run_in_background: true)로 실행한다.Skill(mst:agy)전환 불가. trace는running.log로 대체된다.
# route=external + codex-dev인 경우에만 (OMX_AUTOPILOT=true 시 \$autopilot 프리픽스 삽입)
Bash(
MODEL=$(python3 {PLUGIN_ROOT}/scripts/mst.py resolve-model codex default 2>/dev/null || echo "gpt-5.3-codex");
command: 'python3 {PLUGIN_ROOT}/scripts/mst.py run --task-id {REQ-ID}-T{TASK-NUM} --provider codex --model "$MODEL" --log-dir {task_dir} --trace {REQ-ID}/{TASK-NUM}/phase2-impl --require-worktree --worktree-dir {worktree_path} -- codex exec --full-auto -m "$MODEL" -C {worktree_path} "\$autopilot $(cat {prompt_file})" < /dev/null', # OMX_AUTOPILOT=true
# 또는:
command: 'python3 {PLUGIN_ROOT}/scripts/mst.py run --task-id {REQ-ID}-T{TASK-NUM} --provider codex --model "$MODEL" --log-dir {task_dir} --trace {REQ-ID}/{TASK-NUM}/phase2-impl --require-worktree --worktree-dir {worktree_path} -- codex exec --full-auto -m "$MODEL" -C {worktree_path} "$(cat {prompt_file})" < /dev/null', # OMX_AUTOPILOT=false
run_in_background: true,
timeout: {config.timeouts.cli_large_task_ms}
)
# agy-dev인 경우
Bash(
MODEL=$(python3 {PLUGIN_ROOT}/scripts/mst.py resolve-model agy default 2>/dev/null);
command: 'python3 {PLUGIN_ROOT}/scripts/mst.py run --task-id {REQ-ID}-T{TASK-NUM} --provider agy --model "$MODEL" --log-dir {task_dir} --trace {REQ-ID}/{TASK-NUM}/phase2-impl --require-worktree --worktree-dir {worktree_path} -- agy --print "$(cat {prompt_file})" --dangerously-skip-permissions --add-dir "{worktree_path}" < /dev/null',
run_in_background: true,
timeout: {config.timeouts.cli_large_task_ms}
)
# claude-dev (또는 claude)인 경우
if (route == "native_candidate" and host == "claude"):
Task(subagent_type: "general-purpose", prompt: {prompt_file 내용 + DELEGATION BOUNDARY}, run_in_background: true)
elif (route == "external"):
Skill(skill: "mst:claude", args: "--prompt-file {prompt_file} --dir {worktree_path} --trace {REQ-ID}/{TASK-NUM}/phase2-impl")
claude-dev external 단건 실행은 bare --trace만 넘기지 않는다. Native lane은 shared lifecycle sequence를 사용하고, external Phase 2 dispatch는 다음 항목을 함께 묶는다:
--prompt-file {prompt_file}, --dir {worktree_path}, --trace {REQ-ID}/{TASK-NUM}/phase2-implpython3 {PLUGIN_ROOT}/scripts/mst.py run, --task-id {REQ-ID}-T{TASK-NUM}, --provider claude, --model "$MODEL", --log-dir {task_dir}{task_dir}/running.log, trace path, session metadata, output/failure contract, exit-code propagation각 실행에서 background task_id를 받은 직후, 아래 실제 CLI를 즉시 호출해 dispatch attempt metadata를 request.json에 영구 저장:
python3 {PLUGIN_ROOT}/scripts/mst.py request record-phase2-dispatch-attempt {REQ_ID} \
--task-num {TASK_NUM} \
--task-id {bg_task_id} \
--attempt-id {attempt_id} \
--dispatched-at {UTC ISO8601} \
--agent {agent_slug} \
--worktree-path {worktree_path} \
--log-path {task_dir}/running.log \
--expected-task-status-before {dispatch 직전 task.status} \
--json
이 CLI는 내부적으로 record_phase2_dispatch_attempt(req_id, **kwargs) writer를 호출하며, 저장 결과는 아래 구조를 따라야 한다:
{
"background_task_ids": [
{
"task_id": "{bg_task_id}",
"task_num": "01",
"attempt_id": "{attempt_id}",
"dispatched_at": "{UTC ISO8601}",
"agent": "codex-dev",
"worktree_path": "{worktree_path}",
"log_path": "{task_dir}/running.log",
"expected_task_status_before": "{dispatch 직전 task.status}",
"status": "running"
}
],
"tasks": [
{
"id": "T01",
"attempts": [
{
"attempt_id": "{attempt_id}",
"task_id": "{bg_task_id}",
"task_num": "01",
"dispatched_at": "{UTC ISO8601}",
"agent": "codex-dev",
"worktree_path": "{worktree_path}",
"log_path": "{task_dir}/running.log",
"expected_task_status_before": "{dispatch 직전 task.status}",
"status": "running"
}
]
}
]
}
background_task_ids는 계속 배열 계약을 유지하고 additive metadata만 보강한다. tasks[].attempts[]는 같은 attempt를 task 관점에서 다시 관찰하는 용도이며, 같은 REQ 내 기존 attempt_id와 충돌하면 dispatch를 즉시 실패시켜야 한다.
세션 간 추적:
task_id와attempt_id를 기록하여 세션 전환, 재외주, append 로그 혼재 후에도 같은 시도를 식별한다. 필요 시TaskStop(task_id)로 취소.
⚠️ CONTINUATION GUARD: 서브스킬 반환 후 즉시 다음 Step 진행 (hook이 자동 강제). approve는 Phase 5 완료 시에만 종료.
모든 병렬 태스크가 완료될 때까지 폴링합니다.
backoff = 2초
max_backoff = 30초
while (실행 중인 태스크가 있음):
for task_id in running_tasks:
result = TaskOutput(task_id, block: false, timeout: 5000)
if result.status == 'completed':
remove task_id from running_tasks
if result.exit_code == 0:
Step 5로 이동(사전 검증)
else:
mark task status = failed
Step 4e 실패 전파 규칙 적용
Fallback 규칙 적용(필요 시 재실행)
해당 태스크가 선행인 후속태스크가 있으면 4e로 이동
if running_tasks 남아있음:
sleep(backoff)
backoff = min(backoff * 2, max_backoff)
선행 태스크 완료 시 blockedBy 해소:
blockedBy에서 완료 태스크 제거blockedBy 비면 4c에 추가해 병렬 실행failed이면 후속 태스크를 cancelled로 전이, 즉시 사용자 알림⚠️ NON-STOP EXECUTION RULE (CRITICAL): Step 5 ~ Step 6 ~ Phase 3 ~ 최종 수락까지 중간 멈춤 없이 연속 실행한다. 텍스트를 출력했으면 반드시 해당 단계의 도구 호출(Bash/Edit/Skill 등)을 즉시 실행한다. 서브스킬 반환 직후 강제 패턴: 서브스킬(mst:claude, mst:codex, mst:agy, mst:review 등)이 반환되면 반환 텍스트와 무관하게 즉시
NEXT_ACTION: <다음 Step 설명>패턴을 출력하고 해당 Step의 도구 호출을 실행한다. 서브스킬 반환은 종료가 아니라 다음 단계 전환 신호다. 컨텍스트 길이/대화 길이/토큰 소비량을 이유로 한 자발적 중단을 금지한다. Claude Code는 자동 대화 압축으로 실제 한계를 관리하므로, LLM이 이를 근거로 중단 여부를 직접 판단하지 않는다. 이 규칙은 이 approve 스킬의 모든 후속 Step에 적용된다.
각 태스크 완료 즉시 사전 검증 실행:
test_output 캡처 + exit code 확보)tsc_output 캡처 + exit code 확보)self_check 객체를 생성하고 request.json의 현재 태스크에 기록
self_check = {
tsc: (tsc_exit_code == 0 ? "PASS" : "FAIL"),
test: (test_exit_code == 0 ? "PASS" : "FAIL"),
ran_at: now_in_iso8601_utc(),
tsc_output: tsc_output,
test_output: test_output,
retry_round: (request_json.pre_check_retries or 0)
}
try:
req = Read({PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/request.json)
task = find(req.tasks, id == {TASK_ID})
if task exists:
task.self_check = self_check
Write(request.json, req)
else:
warn("[non-blocking] self_check 저장 대상 task를 찾지 못함: {TASK_ID}")
except err:
warn("[non-blocking] self_check 저장 실패: {err}")
저장 실패는 non-blocking: 경고만 출력하고 다음 분기로 진행.status → review → 즉시 Step 5.5 실행 (PM 커밋)status → pre_check_failed → 즉시 Step 5b 실행 (재외주)Step 5 PASS 후 PM이 직접 커밋합니다 (외주 에이전트의 index.lock 문제 방지).
이중 커밋 방지: git -C {worktree_path} status --porcelain → 출력 없으면 이미 clean. 이 경우에도 현재 branch/HEAD evidence를 저장한 뒤 status → committed 전환 후 Step 5.7 진행.
if [ -z "$(git -C {worktree_path} status --porcelain)" ]; then
COMMIT_HASH=$(git -C {worktree_path} rev-parse --verify HEAD)
COMMIT_MSG=$(git -C {worktree_path} log -1 --format="%s")
TASK_BRANCH=$(git -C {worktree_path} symbolic-ref --quiet --short HEAD)
python3 {PLUGIN_ROOT}/scripts/mst.py task set-commit {REQ_ID}-T{TASK_ID_PAD} "$COMMIT_HASH" "$COMMIT_MSG" \
--branch "$TASK_BRANCH" \
--worktree-path "{worktree_path}"
fi
전체 변경 스테이징: git -C {worktree_path} add -A
frontend/ 변경 자동 감지 후 빌드:
FRONTEND_CHANGED=$(git -C {worktree_path} diff --cached --name-only | grep "^frontend/" | head -1)
if [ -n "$FRONTEND_CHANGED" ]; then
cd {worktree_path}/frontend && npm install --prefer-offline && npm run build
git -C {worktree_path} add dist/
fi
PM이 커밋:
git -C {worktree_path} commit -m "[{REQ_ID}/{TASK_ID}] {spec §1 요약}
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
커밋 hash/message 저장 (실패 시 경고 후 계속):
COMMIT_HASH=$(git -C {worktree_path} log -1 --format="%H")
COMMIT_MSG=$(git -C {worktree_path} log -1 --format="%s")
TASK_BRANCH=$(git -C {worktree_path} symbolic-ref --quiet --short HEAD)
python3 {PLUGIN_ROOT}/scripts/mst.py task set-commit {REQ_ID}-T{TASK_ID_PAD} "$COMMIT_HASH" "$COMMIT_MSG" \
--branch "$TASK_BRANCH" \
--worktree-path "{worktree_path}"
태스크 status → committed, background_task_ids status → "completed" 업데이트 → Step 5.7 진행. Step 6의 readiness gate는 각 완료 task의 commit_hash와 branch/worktree_path가 현재 task branch HEAD와 일치하는지 검증하므로 status만 갱신해서는 Phase 3으로 전환되지 않는다.
이 Step은 Step 5 ~ Step 6 사이의 NON-STOP EXECUTION RULE 적용 범위 내부다. 검증 에이전트 반환 후 즉시 판정/보완/재검증 또는 Step 6 전환을 수행한다.
Read({PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/request.json)로 source_plan을 확인한다.Bash(python3 {PLUGIN_ROOT}/scripts/mst.py config get intent_verification review.auto_review workflow.auto_accept_result --json)를 실행하고, stdout JSON 배열을 phase3_config_items로 보관한다. 같은 preload에서 intent_verification, review.auto_review, workflow.auto_accept_result를 함께 추출한다. 파싱 실패, 빈 값, 누락 항목은 graceful fallback 처리한다.
intent_cfg: phase3_config_items에서 key == "intent_verification"인 항목의 value. 파싱 실패 또는 빈 값이면 {}로 취급한다.intent_enabled: intent_cfg.enabled가 boolean이면 그 값을 사용하고, 아니면 true.max_iterations: intent_cfg.max_iterations가 양의 정수이면 그 값을 사용하고, 아니면 5.review.auto_review, workflow.auto_accept_result도 같은 preload에서 함께 확보해 Step 6 Phase 3 리뷰 루프에서 재사용한다.request phase2-status로 수행하고, 전환 실행은 기존 advance-phase2-if-ready --json 경로에만 남긴다. 상태 전이를 수행하는 workflow gate-summary류 bundle 명령은 사용하지 않는다.Bash(python3 {PLUGIN_ROOT}/scripts/mst.py request phase2-status {REQ_ID} --json)를 실행하고, stdout JSON의 ready 값을 확인한다.
ready != true이면 아직 모든 Phase 2 태스크가 완료 상태가 아니므로 Step 6으로 이동해 전환 명령이 incomplete_tasks를 보고하게 한다.source_plan이 없으면 [Step 5.7 skip] source_plan 없음 (--plan 없는 REQ) → Step 6 진행을 출력하고 Step 6으로 이동한다.intent_enabled == false이면 [Step 5.7 skip] intent_verification.enabled=false → Step 6 진행을 출력하고 Step 6으로 이동한다.plan.md(AD 및 구조 명세 섹션 추출), plan.ids.json(PAC 목록 추출)intent-verification/각 iteration마다 아래 (a)~(d)를 순서대로 실행한다.
{PROJECT_ROOT}/templates/intent-verification.md{REQ_ID}, {PLN_ID}, {ITERATION}, {WORKTREE_PATH}, {AD_LIST}, {PAC_LIST}, {STRUCTURE_SPEC}intent-verification/prompt-iteration-{iteration}.mdintent-verification/iteration-{iteration}.md부분반영 + 미반영 == 0이면 수렴으로 간주하고 루프 종료: "[Step 5.7 converged] 미반영 항목 0건 → Step 6 진행"보완 필요 항목 목록을 기반으로 단일 보완 태스크를 생성한다.AUTO_MODE=true: PM 자율 판단으로 즉시 보완 디스패치AUTO_MODE=false: AskUserQuestion으로 미반영 목록 제시 → "보완하고 재검증" 또는 "남은 항목 무시하고 진행"보완 커밋 완료 즉시 iteration += 1 후 Step 5.7-2 (a)로 재진입한다. iteration > max_iterations이면 루프 종료.
부분반영 + 미반영 == 0 → 즉시 Step 6 진행iteration > max_iterations → 잔여 미반영이 있어도 Step 6 진행retry.max_cli_retries) 적용루프 종료 시 intent-verification/summary.md에 저장. 포함 항목: 총 iteration 수, 수렴 여부, 잔여 미반영 항목 목록.
Step 5.7 종료 직후 즉시 Step 6: Phase 3 전환으로 진행한다.
Step 5 FAIL 시, PM이 직접 코드를 수정하지 않고 외주 에이전트에게 에러 컨텍스트와 함께 재요청합니다. 최대 재시도 소진 후 PM 직접 개입.
실행 타입 분기 (if 1개, MANDATORY):
if strategy.worktree_policy == "skip":
2회, 루프는 팩트체크 실패 → 소스 재확인 프롬프트 생성 → 재작성 순서.직전 문서 검증 결과에서 실패 claim 목록(failed_claims)과 근거 부족 항목(unverified_claims)을 추출. 각 항목에 대해 "현재 서술 / 실패 사유 / 필요한 근거(source)"를 정리한다.
Write → {PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/tasks/{NN}/prompts/phase2-doc-fix-R{N}.md
포함 내용: spec.md §3 수락 조건, 실패/미검증 claim 목록 + 실패 사유, §0 Context Manifest 재확인 지시, "실패 claim 섹션만 재작성 후 구조 검증 + 팩트체크 다시 실행" 지시.
동일 에이전트로 재외주 실행. request.json에 doc_factcheck_retries(없으면 0)를 +1 저장. 재작성 완료 후 즉시 Step 5로 복귀.
doc_factcheck_retries >= 2이면 루프 종료. PM이 소스 원문을 재확인해 문서를 직접 보정한 뒤 검증만 재실행한다.
else (strategy.worktree_policy != "skip"):
5b-1 ~ 5b-5 기존 코드 경로를 그대로 수행한다. (변경 금지)5b-1의 트리밍된 에러 출력(TRIMMED_ERROR_OUTPUT)에 아래 포맷터 적용:
python3 {PLUGIN_ROOT}/scripts/format-precheck-errors.py파일경로(줄,열): error TSNNNN: 메시지 → 파일경로:줄 — TSNNNN — 메시지TRIMMED_ERROR_OUTPUT을 그대로 사용 (passthrough). 최종 출력 변수명: FORMATTED_ERROR_OUTPUTpre_check_retries 필드 확인 (없으면 0)config.retry.max_cli_retries (기본 2) 미만 → 5b-3 (재외주)Write → {PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/tasks/{NN}/prompts/phase2-fix-R{N}.md
포함 내용: spec.md §3 수락 조건, 포맷된 에러 출력(FORMATTED_ERROR_OUTPUT), "에러 수정 후 검증 명령어 실행 확인" 지침, spec §5 테스트/타입체크 명령어. <error_context>의 {ERROR_OUTPUT}에 FORMATTED_ERROR_OUTPUT 바인딩.
if OMX_AUTOPILOT:
fix_content = Read({PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/tasks/{NN}/prompts/phase2-fix-R{N}.md)
fix_omx_path = {PROJECT_ROOT}/.gran-maestro/requests/{REQ-ID}/tasks/{NN}/prompts/phase2-fix-omx-R{N}.md
Write(fix_omx_path, "$autopilot\n\n" + fix_content)
Skill(skill: "mst:codex", args: "--prompt-file {fix_omx_path} --dir {worktree_path} --trace {REQ-ID}/{TASK-NUM}/phase2-fix-R{N}")
else:
Skill(skill: "mst:codex", args: "--prompt-file {fix_path} --dir {worktree_path} --trace {REQ-ID}/{TASK-NUM}/phase2-fix-R{N}")
pre_check_retries +1, tasks[].retry_count +1, request.json 저장status → executing. 재외주 완료 후 즉시 Step 5 복귀max_cli_retries 소진 후, PM 직접 개입 전 Codex 에스컬레이션 1회 시도:
codex_fallback_retries 확인: >= 1이면 → 즉시 5b-5로 이동 (최대 1회 한도)git -C {worktree_path} stash, 에스컬레이션 프롬프트 준비 (phase2-fix-R{N}.md + ## 에스컬레이션 힌트 섹션). Step 4c와 동일 패턴으로 실행 (running-fallback.log 출력).codex_fallback_retries = 1 업데이트 → Step 5 재진입. 실패 시: stash pop → 5b-5 이동.background_task_ids에서 status: "running" 항목을 TaskStop(task_id)로 취소 → "cancelled" 업데이트.python3 {PLUGIN_ROOT}/scripts/mst.py dispatch validate-worktree --worktree-dir {worktree_path} --json를 실행한다. 실패하면 원본 checkout에서 수정하지 말고 status="blocked", reason="pm_direct_fix_worktree_guard_failed"를 기록한다.{worktree_path} 내부에서만 직접 코드 수정request.json.tasks[].pm_direct_fix에 true, worktree_path, verification_command, expected_signal, changed_files, rollback_command를 기록한다.status: review / 여전히 FAIL → git -C {worktree_path} checkout -- . rollback 후 사용자 개입 요청모든 Phase 2 태스크가 완료 상태(committed, completed, done, accepted)에 도달하면:
Bash(python3 {PLUGIN_ROOT}/scripts/mst.py request phase2-status {REQ_ID} --json)를 실행한다.ready == true이고 advanced == false인지 확인한다.ready == false이면 stdout JSON의 reason과 incomplete_tasks를 근거로 아직 Phase 3에 진입하지 않고 대기/수정 분기로 이동한다.Bash(python3 {PLUGIN_ROOT}/scripts/mst.py request advance-phase2-if-ready {REQ_ID} --json)를 실행한다.reason == "guard_blocked", advanced == false로 구조화되어야 한다.reason == "guard_blocked"이면 takeover/manual recovery로 분기하고, readiness failure와 혼동하지 않는다.ready == true이고 advanced == true인지 확인한다.current_phase=3, status=phase3_review이며, review_summary.status는 기존 passed/failed가 아닌 경우 pending_phase3_review로 보장된다.모든 Phase 2 태스크가 완료 상태에 도달하고 current_phase가 3으로 전환된 후:
review.auto_review / workflow.auto_accept_result 설정 확인:
phase3_config_items preload를 재사용한다.Bash(python3 {PLUGIN_ROOT}/scripts/mst.py config get intent_verification review.auto_review workflow.auto_accept_result --json)를 1회 실행해 phase3_config_items를 복구한다.AUTO_MODE는 단건 프로토콜 진입 시 단일 초기화된 값을 그대로 사용한다 (이중 판단 금지).false (기본): 아래 태스크 상태 검증 후 최종 수락 실행 (mst:review 미호출):
request.json.tasks 전체 확인: 모든 Phase 2 태스크가 완료 상태(committed, completed, done, accepted)인지 검증
phase3_config_items preload의 workflow.auto_accept_result 설정에 따라 즉시 실행:
true (기본): Skill(skill: "mst:accept", args: "{REQ_ID}") 호출 → accept 완료 후 DAG 연쇄 실행 판단false: Phase 3 리뷰 PASS로 간주하고 멈추고, 사용자에게 /mst:accept {REQ_ID} 수동 호출 안내true 또는 AUTO_MODE=true이면 mst:review 호출 진행iteration_num >= 2인 경우: iteration-decisions/iteration-{iteration_num - 1}.md Read → 컨텍스트 보관. 파일 없으면 skip.
AUTO_MODE=true -> Skill(skill: "mst:review", args: "{REQ_ID} --auto")
AUTO_MODE=false -> Skill(skill: "mst:review", args: "{REQ_ID}")
(AUTO_MODE=true에서는 review.auto_review=false이더라도 항상 호출)
⚠️ 반환 후 즉시 3번으로 진행 —
[TRACE_SAVED]텍스트 포함 여부 무관. approve는 Phase 5(mst:accept) 완료 시에만 종료.
mst:review 반환 후, review 결과 처리(3번) 진입 전에 실행. iteration-decisions/ 디렉토리 생성 후 iteration-{iteration_num}.md Write:
# Iteration {iteration_num} 결정 로그
## AC 상태
{각 AC에 대해: AC-NNN: PASS/FAIL + 판단 근거 1줄}
## 핵심 판단
{리뷰에서 발견된 주요 이슈에 대한 PM의 severity 동의/이의 + 결정 이유}
## 다음 iteration 방향
{다음 iteration에서 집중할 AC 목록 + 추가 태스크 방향}
Write 실패 시 warn만 출력하고 워크플로우를 차단하지 않는다 (graceful).
review 결과 처리:
review_issues_summary 로드: 최신 reviews/RV-NNN/review.json을 Read → review_issues_summary 파싱 (critical/major/minor 카운트 + auto_fixed/skipped 배열)
auto_accept_guard 메타 파싱:
skipped_minor_count = review_issues_summary.auto_accept_guard.skipped_minor_count (없으면 0)
protection_flags_count = review_issues_summary.auto_accept_guard.protection_flags_count (없으면 0)
guard_blocked = review_issues_summary.auto_accept_guard.blocked == true OR skipped_minor_count > 0 OR protection_flags_count > 0
auto_accept_guard.blocked_reasons가 있으면 차단 사유로 그대로 보고한다.
status: "passed": review_summary.status → "passed" 이후 아래 규칙으로 분기한다:
workflow.auto_accept_result == true AND guard_blocked == false:
AUTO_MODE=true -> Skill(skill: "mst:accept", args: "-a {REQ_ID}")
AUTO_MODE=false -> Skill(skill: "mst:accept", args: "{REQ_ID}")
workflow.auto_accept_result == true AND guard_blocked == true:
auto_accept_guard.blocked_reasons와 함께 보호 차단 상태를 보고하고, 사용자에게 /mst:accept {REQ_ID} 수동 호출 경로를 안내한다.workflow.auto_accept_result == false:
/mst:accept {REQ_ID}를 수동으로 호출하라고 안내한다. 설정 변경: /mst:settings workflow.auto_accept_result falseDAG 자동 연쇄 실행 (accept 완료 직후, auto_accept_result == true인 경우에만 실행):
아래 조건을 모두 충족하면 같은 plan의 후속 REQ를 자동 연쇄 실행한다.
(auto_accept_result == false인 경우의 DAG 연쇄 규칙은 mst:accept(Step 5.6)에서 실행)
실행 조건:
request.json에서 source_plan이 "PLN-NNN" 형태로 존재request.json에서 dag_auto_chain == truedone 또는 completed 또는 accepted하나라도 불충족이면 DAG 연쇄 실행 단계는 skip.
다음 REQ 탐색 규칙:
plan.json Read 후 linked_requests 전체를 plan 정의 순서대로 재평가pending_dependency, phase1_analysis, spec_ready)만 후보.blockedBy 해소 판정: 모든 선행 REQ가 done/completed/accepted이면 "실행 가능"으로 판단자동 연쇄 실행 루프:
컨텍스트 길이 기반 중단 금지 (MANDATORY): 아래 루프는 컨텍스트 길이/대화 길이/토큰 소비량을 이유로 중단하지 않는다. 유일한 예외는 사용자의 명시적 취소 지시다.
chain_results = [{ req_id: CURRENT_REQ_ID, status: "completed" }]
while true:
plan = Read({PROJECT_ROOT}/.gran-maestro/plans/{source_plan}/plan.json)
next_req = first runnable req from plan.linked_requests (full scan each loop)
if not next_req:
break
출력: "[DAG 연쇄] 다음 실행: {next_req.id} ({next_req.title})"
Skill(skill: "mst:request", args: "--plan {source_plan} --resume {next_req.id} -a")
refreshed = Read({PROJECT_ROOT}/.gran-maestro/requests/{next_req.id}/request.json)
if refreshed.status in ["done", "completed", "accepted"]:
chain_results.append({ req_id: next_req.id, status: "completed" })
continue
pending_tail = remaining non-terminal req ids in same plan
출력: "[DAG 연쇄 중단] {next_req.id} 실패. 후속 REQ: {pending_tail.join(', ')}"
종료
if all linked_requests are done/completed/accepted:
출력: "[DAG 연쇄 완료] {source_plan}의 모든 REQ가 완료되었습니다. ..."
else:
출력: "[DAG 연쇄 종료] 실행 가능한 다음 REQ가 없어 종료했습니다."
status: "gap_found":
a. CRITICAL 이슈 존재 시: CRITICAL은 PM 직접 수정 불가, 항상 재외주.
a-2. MINOR 이슈: review_issues_summary.skipped에 기록된 대로 스킵, 재외주 대상에 포함하지 않음.
b. MAJOR 이슈 — PM 직접 수정 분기:
CRITICAL은 PM 직접 수정 불가, 항상 재외주
MAJOR 이슈 중 아래 모든 조건을 충족하면 PM이 worktree에서 직접 수정:
config.review.severity_auto_fix.pm_direct_fix_enabled == truepm_direct_fix_max_filespm_direct_fix_max_diff_linesPM 직접 수정 조건 미충족 시 → 재외주 경로(아래 c.)로 전환.
PM 직접 수정 절차: MAJOR 이슈 중 조건 충족 이슈만 PM이 직접 수정하고 나머지는 재외주.
python3 {PLUGIN_ROOT}/scripts/mst.py dispatch validate-worktree --worktree-dir {worktree_path} --json를 실행해 {worktree_path}가 등록된 linked worktree이고 primary checkout이 아님을 확인한다. 실패하면 직접 수정 분기를 중단하고 재외주 경로(c.)로 전환한다.git -C {worktree_path} checkout -- .) → 해당 MAJOR 이슈 태스크를 request.json.tasks에 신규 생성(generated_by: "review", status: "pending") → c. 경로로 진입.review-report.md에 pm_direct_fix: true, worktree_path, 수정 파일 목록, 수정 내용 요약, 검증 명령, expected signal, commit 또는 rollback evidence를 기록.c. MAJOR 조건 미충족 또는 재외주 경로:
⚠️ AUTO_MODE=true일 때 재외주는 무정지 실행: AskUserQuestion 없이 즉시 아래 절차를 실행한다.
request.json.tasks에서 generated_by: "review" + status: "pending" 태스크만 선별current_phase → 3 재전환 → 이 루프 반복status: "pass_a_failed":
⚠️ CRITICAL:
pass-a-result.md스키마 필수 필드가 하나라도 누락되면 재외주 선별을 즉시 중단하고 review 재실행을 요구한다.
| 조건 | 동작 | 다음 단계 |
|---|---|---|
pass-a-result.md 스키마 검증 실패 (필수 필드 누락) | "스키마 불일치" 출력 + review 재실행 안내 | 재외주 선별 중단 |
스키마 통과 + covers_ac 비어있지 않은 태스크 있음 | failed_ac_ids ∩ covers_ac 교집합 기준 선별 | 선별 태스크로 재외주 |
스키마 통과 + 모든 committed 태스크의 covers_ac가 없거나 빈 배열 | 하위 호환 fallback — 전체 committed 태스크 선별 | 재외주 진행 |
스키마 통과 + covers_ac 있으나 교집합 없음 | fallback 없이 빈 선별 유지 | 재외주 대상 없음 |
스키마 통과 + 일부 태스크만 covers_ac 존재 | 교집합 기준 선별 + 나머지 fallback 포함 | 선별 태스크로 재외주 |
재외주 태스크 선별:
reviews/RV-NNN/pass-a-result.md Read → failed_ac_ids 파싱pass_a_result, failed_ac_ids, failure_class, evidence) 하나라도 누락 시 중단committed 태스크 중 covers_ac 비어있지 않은 태스크: failed_ac_ids ∩ covers_ac 교집합 >= 1인 태스크 선정covers_ac 없거나 빈 배열인 committed 태스크는 fallback으로 포함재외주 절차:
⚠️ AUTO_MODE=true일 때 재외주는 무정지 실행.
generated_by: "review", status: "pending")current_phase 3 재전환 → mst:review 재호출status: "limit_reached":
--auto 모드: review_summary.status = "limit_reached" 기록 후 workflow.auto_accept_result 설정에 따라 즉시 실행단, --auto 플래그 맥락: approve가 --auto로 실행된 경우 review 호출 시 컨텍스트로 전달됨.
/mst:inspect {REQ-ID}로 상태 조회/mst:inspect {REQ-ID}로 현재 Phase 확인/mst:accept 수동 호출하거나, workflow.auto_accept_result를 true로 설정/mst:approve REQ-NNN으로 실패한 REQ만 단건 재승인