| name | taskmaestro |
| description | Use when orchestrating parallel Claude Code instances across tmux panes with git worktree isolation โ managing multiple concurrent development tasks visually |
| argument-hint | <start|assign|status|watch|send|stop> [args...] |
| user-invocable | true |
TaskMaestro โ tmux ํจ๋ ์ค์ผ์คํธ๋ ์ดํฐ
ํ์ฌ tmux ์ธ์
์ ๊ธฐ์กด ํจ๋๋ค์์ Claude Code๋ฅผ ๋ณ๋ ฌ๋ก ์คํํ๊ณ , ์ด ํจ๋(์งํ์)์์ ์ ์ดํ๋ค.
๊ฐ ํจ๋์ ๋
๋ฆฝ๋ git worktree์์ ์์
ํ๋ฉฐ, ์งํ์๊ฐ ์์
์ง์, ์ํ ๊ฐ์๋ฅผ ์ํํ๋ค.
์ ์ ์กฐ๊ฑด: ์งํ์(์ด Claude Code)๊ฐ tmux ํจ๋ ์ค ํ๋์์ ์คํ ์ค์ด์ด์ผ ํ๋ฉฐ, ๋๋จธ์ง ํจ๋๋ค์ด ์ด๋ฏธ ์ด๋ ค์์ด์ผ ํ๋ค.
Arguments ํ์ฑ
$ARGUMENTS๋ฅผ ํ์ฑํ์ฌ ์๋ธ์ปค๋งจ๋์ ์ธ์๋ฅผ ๋ถ๋ฆฌํ๋ค:
start [--repo <path>] [--base <branch>] [--panes <1,2,3>] [--review-pane] [--no-review-pane]
assign <ํจ๋๋ฒํธ> "<์์
์ง์>"
status
watch
send <ํจ๋๋ฒํธ> "<ํ
์คํธ>"
stop [ํจ๋๋ฒํธ|all]
์๋ธ์ปค๋งจ๋์ ํด๋นํ๋ ์น์
์ผ๋ก ์ด๋ํ๋ค.
ํ์ฌ tmux ํ๊ฒฝ ๊ฐ์ง
๋ชจ๋ ์๋ธ์ปค๋งจ๋์์ ๋จผ์ ์คํ:
TMUX_SOCKET=$(tmux display-message -p '#{socket_path}')
SOCKET_NAME=$(basename "$TMUX_SOCKET")
SESSION=$(tmux display-message -p '#{session_name}')
WIN_IDX=$(tmux display-message -p '#{window_index}')
MY_PANE=$(tmux display-message -p '#{pane_index}')
tmux ๋ช
๋ น์ด์์ -L ํ๋๊ทธ๋ ์์ผ ์ด๋ฆ์ ์ฌ์ฉํ๋ค. tmux -L "$SOCKET_NAME" send-keys ... ํํ๋ก ์ฌ์ฉ.
Worker Completion Signaling (RESULT.json)
์์ปค๊ฐ ์์
์ ์๋ฃํ๋ฉด .taskmaestro/wt-N/RESULT.json ํ์ผ์ ์์ฑํ์ฌ ์งํ์์๊ฒ ๊ฒฐ๊ณผ๋ฅผ ์๋ฆฐ๋ค.
์ด ํ์ผ์ watch ๋ชจ๋์ 1์ฐจ ๊ฐ์ง ์๋จ์ด๋ฉฐ, capture-pane์ fallback์ผ๋ก๋ง ์ฌ์ฉํ๋ค.
RESULT.json ์คํค๋ง
{
"status": "success | failure | error | review_pending | review_addressed | approved",
"issue": "#767",
"pr_number": 123,
"pr_url": "https://github.com/org/repo/pull/123",
"timestamp": "2026-03-22T01:13:00.000Z",
"cost": "$0.45",
"error": null,
"review_cycle": 0,
"review_comments": []
}
| ํ๋ | ํ์
| ํ์ | ์ค๋ช
|
|---|
status | "success" | "failure" | "error" | "review_pending" | "review_addressed" | "approved" | โ
| ์์
๊ฒฐ๊ณผ. success: PR ์์ฑ ์๋ฃ (๋ฆฌ๋ทฐ ๋๊ธฐ), failure: ์์
์คํจ (ํ
์คํธ ์คํจ ๋ฑ), error: ์๊ธฐ์น ๋ชปํ ์ค๋ฅ, review_pending: ์งํ์ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ ์์ฑ ์๋ฃ (์์ปค ์๋ต ๋๊ธฐ), review_addressed: ์์ปค๊ฐ ๋ฆฌ๋ทฐ ๋ฐ์ ์๋ฃ (์ฌ๋ฆฌ๋ทฐ ๋๊ธฐ), approved: ์งํ์ ์ต์ข
์น์ธ (์ง์ง ์๋ฃ) |
issue | string | null | โ | ๊ด๋ จ ์ด์ ๋ฒํธ (์: "#767") |
pr_number | number | null | โ | ์์ฑ๋ PR ๋ฒํธ |
pr_url | string | null | โ | ์์ฑ๋ PR URL |
timestamp | string | โ
| ISO 8601 ์๋ฃ ์๊ฐ |
cost | string | null | โ | ์ธ์
๋น์ฉ (์: "$0.45") |
error | string | null | โ | ์๋ฌ ๋ฉ์์ง (status๊ฐ "error"์ผ ๋) |
review_cycle | number | โ | ํ์ฌ ๋ฆฌ๋ทฐ ์ฌ์ดํด ํ์ (๊ธฐ๋ณธ๊ฐ: 0) |
review_comments | string[] | โ | ์งํ์๊ฐ ๋จ๊ธด ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ ์์ฝ ๋ชฉ๋ก |
ํ์ผ ๊ฒฝ๋ก ๊ท์น
- ์์น:
<repo>/.taskmaestro/wt-<N>/RESULT.json (๊ฐ ์์ปค์ worktree ๋ฃจํธ)
- ์์ปค๋ ์์
์๋ฃ ์ง์ (์ปค๋ฐ/PR ์์ฑ ํ, ์ธ์
์ข
๋ฃ ์ )์ ์ด ํ์ผ์ ์์ฑํ๋ค
- ์งํ์๋ ์ด ํ์ผ์ ์กด์ฌ ์ฌ๋ถ๋ก ์์ปค ์๋ฃ๋ฅผ ๊ฐ์งํ๋ค
Review Cycle Protocol
์์ปค๊ฐ RESULT.json์ status: "success"๋ฅผ ๊ธฐ๋กํ๋ฉด ๋ฆฌ๋ทฐ ์ฌ์ดํด์ด ์์๋๊ณ , status: "approved"๊ฐ ๋ ๋๊น์ง ๋ฐ๋ณต๋๋ค. ์น์ธ์ด "์ง์ง ์๋ฃ"๋ค.
Canonical source: ๋ฆฌ๋ทฐ ์ฌ์ดํด ํ๋กํ ์ฝ์ ๋จ์ผ ์์ค๋ packages/rules/.ai-rules/rules/pr-review-cycle.md์ด๋ค.
์๋ ์น์
์ taskmaestro ์ ์ฉ ๊ตฌํ ์ธ๋ถ์ฌํญ(tmux ํธ๋ฆฌ๊ฑฐ, taskmaestro-state.json ๊ตฌ์กฐ)๋ง ๋จ๊ธฐ๊ณ , ํ๋กํ ์ฝ ์ ์ฒด๋ canonical ๋ฌธ์๋ฅผ ์ฐธ์กฐํ๋ค.
Trigger (taskmaestro ๊ด์ )
์งํ์์ watch ๋ชจ๋๋ .taskmaestro/wt-N/RESULT.json์ status ํ๋๋ฅผ ๊ฐ์งํ๋ค:
status | ์งํ์ ๋์ |
|---|
success | ๋ฆฌ๋ทฐ ์ฌ์ดํด ์์ (done์ผ๋ก ๋ณด๊ณ ํ์ง ์์) |
failure / error | ์ฌ์ฉ์์๊ฒ ๋ณด๊ณ , ๋ฆฌ๋ทฐ ๋ฏธ์งํ |
review_pending | ์์ปค ์๋ต ๋๊ธฐ |
review_addressed | ์ฌ๋ฆฌ๋ทฐ ์์ |
approved | ์ง์ง ์๋ฃ โ
|
Review Routing
review_pane์ด ์ค์ ๋์ง ์์์ผ๋ฉด (๊ธฐ๋ณธ) โ Conductor Review ์คํ
review_pane์ด ์ค์ ๋์์ผ๋ฉด โ ํด๋น ํจ๋์ Review Agent๋ก ์์
๋ ๊ฒฝ์ฐ ๋ชจ๋ ์คํํ๋ ํ๋กํ ์ฝ(CI gate โ local verify โ diff โ code quality โ spec โ tests โ write review โ approve)์ canonical ๋ฌธ์๋ฅผ ๋ฐ๋ฅธ๋ค. Severity ๋ฑ๊ธ(critical/high/medium/low), approval ์กฐ๊ฑด, commit hygiene(amend + force-with-lease), ์ต๋ 3ํ ์ฌ์ดํด ์ํ๋ canonical ๋ฌธ์๊ฐ ์ ์ํ๋ค.
Conductor Review๊ฐ ๊ธฐ๋ณธ ๋์์ธ ์ด์ : ์งํ์๋ PLAN ์ปจํ
์คํธ๋ฅผ ๋ณด์ ํ์ง๋ง, ์์ปค ๋ฆฌ๋ทฐ์ด๋ ๊ทธ๋ ์ง ์๋ค.
taskmaestro ๊ณ ์ : ํ์ผ ๊ธฐ๋ฐ ๋ฆฌ๋ทฐ ํธ๋ฆฌ๊ฑฐ
ํ๋กํ ์ฝ์ "์์ปค์๊ฒ ๋ฆฌ๋ทฐ ๋ฐ์ ์ง์" ๋จ๊ณ๋ฅผ taskmaestro๋ file-based trigger๋ก ๊ตฌํํ๋ค. ๊ธด ํ๋กฌํํธ๋ฅผ send-keys๋ก ์ง์ ์ ์กํ๋ฉด ์๋ฆด ์ ์์ผ๋ฏ๋ก:
-
Review Fix TASK.md๋ฅผ ์์ปค worktree ๋ฃจํธ($REPO/.taskmaestro/wt-$PANE_IDX/TASK.md)์ ์์ฑํ๋ค. ๋ด์ฉ์ ์๋ Review Fix TASK.md ํ
ํ๋ฆฟ์ ๋ฐ๋ฅธ๋ค.
-
์งง์ ํธ๋ฆฌ๊ฑฐ๋ง send-keys๋ก ์ ์กํ๋ค:
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$PANE_IDX" \
"Read TASK.md and execute all steps." Enter
๋ฆฌ๋ทฐ ์์ด์ ํธ ํจ๋($REVIEW_PANE)์ ๋ฆฌ๋ทฐ ํ๋กฌํํธ๋ฅผ ์ ์กํ ๋๋ ๋์ผํ ํ์ผ ๊ธฐ๋ฐ ํจํด์ ์ฌ์ฉํ๋ค.
Review Fix TASK.md ํ
ํ๋ฆฟ
์์ปค worktree์ ์์ฑํ๋ TASK.md์ ๋ด์ฉ:
# Review Fix Task
See rules/pr-review-cycle.md for the canonical worker response protocol.
## Steps
1. `gh pr view <PR_NUMBER> --comments` ๋ก ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ๋ฅผ ํ์ธํ๋ค.
2. ๊ฐ ์ฝ๋ฉํธ์ ๋์: ์์ฉ ์ ์ฝ๋ ์์ , ๊ฑฐ๋ถ ์ ๋ฐ๋ฐ ์ฝ๋ฉํธ, `Resolved: ...` ํ์ .
3. MANDATORY ์ฌ์ ์ฒดํฌ: `yarn prettier --write . && yarn lint --fix && yarn type-check && yarn test` โ ๋ชจ๋ ํต๊ณผํด์ผ push ํ๋ค.
4. ์ปค๋ฐ ์์: `git commit --amend --no-edit && git push --force-with-lease` (fix ์ปค๋ฐ์ ์์ง ๋ง ๊ฒ).
5. `RESULT.json`์ ์
๋ฐ์ดํธํ๋ค: `{ "status": "review_addressed", "review_cycle": <current_cycle> }`
[CONTINUOUS] DO NOT stop. Only stop AFTER updating RESULT.json to "review_addressed".
ํต์ฌ: ์์ปค๋ ๋ฆฌ๋ทฐ ๋ฐ์ ํ ๋ฐ๋์ RESULT.json์ status๋ฅผ review_addressed๋ก ์
๋ฐ์ดํธํด์ผ watch cron์ด ์ฌ๋ฆฌ๋ทฐ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ค.
taskmaestro ๊ณ ์ : ์ํ ์คํค๋ง
๋ฆฌ๋ทฐ ์ฌ์ดํด ์ค ๊ฐ ํจ๋์ taskmaestro-state.json ํ๋:
{
"index": 1,
"role": "worker",
"worktree": "...",
"branch": "...",
"task": "...",
"status": "review_pending",
"result": { "...RESULT.json ๋ด์ฉ..." },
"review_cycle": 1,
"pr_number": 42
}
๋ฆฌ๋ทฐ ์์ด์ ํธ ํจ๋์ role: "reviewer", status: "reviewing"์ ์ฌ์ฉํ๋ค. ๋ฆฌ๋ทฐ ์์ด์ ํธ์ ๊ฒฐ๊ณผ ํ์ผ(<repo>/.taskmaestro/wt-<review_pane>/RESULT.json)์ ๋ค์ ์คํค๋ง๋ฅผ ๋ฐ๋ฅธ๋ค:
{
"status": "success",
"review_result": "approve | changes_requested",
"issues_found": 3,
"critical_count": 0,
"high_count": 1
}
Severity ์นด์ดํธ ํ๋(critical_count, high_count)๋ rules/severity-classification.md์ Code Review Severity ์ค์ผ์ผ์ ๋ฐ๋ฅธ๋ค.
์งํ์๋ ๋ฆฌ๋ทฐ ์์ด์ ํธ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ณ :
review_result: "approve" โ ์์ปค RESULT.json์ approved๋ก ์
๋ฐ์ดํธ, ์น์ธ ์ฝ๋ฉํธ ๊ฒ์, ์ฌ์ฉ์์๊ฒ ์๋ฃ ๋ณด๊ณ
review_result: "changes_requested" โ ์์ปค RESULT.json์ review_pending์ผ๋ก ์
๋ฐ์ดํธํ๊ณ review_cycle ์ฆ๊ฐ, file-based ๋ฆฌ๋ทฐ ์์ ํธ๋ฆฌ๊ฑฐ ์ ์ก
- ๋ฆฌ๋ทฐ ์์ด์ ํธ
RESULT.json์ ์ญ์ ํด ๋ค์ ๋ฆฌ๋ทฐ๋ฅผ ์ํด ์ด๊ธฐํ
status ๊ฐ ๋งคํ:
working โ ์์ปค ์์
์ค
reviewing โ ๋ฆฌ๋ทฐ ์์ด์ ํธ๊ฐ PR ๋ฆฌ๋ทฐ ์ค (reviewer ํจ๋ ์ ์ฉ)
review_pending โ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ ์์ฑ ์๋ฃ, ์์ปค ์๋ต ๋๊ธฐ
review_addressed โ ์์ปค ๋ฆฌ๋ทฐ ๋ฐ์ ์๋ฃ, ์ฌ๋ฆฌ๋ทฐ ๋๊ธฐ
approved โ ์ต์ข
์น์ธ ์๋ฃ (์ง์ง done)
done โ ๋ฆฌ๋ทฐ ์์ด ์๋ฃ (failure/error)
Merge Policy (MANDATORY)
The conductor MUST NEVER merge PRs or modify the master/main branch.
Prohibited commands:
gh pr merge (all flags: --squash, --merge, --rebase, --admin, --auto)
git merge
git pull origin master / git pull origin main (includes merge)
- Any command that modifies the remote default branch
Allowed:
git fetch origin (read-only)
gh pr create (create PR, not merge)
gh pr view (read-only)
Protocol:
- Worker creates PR via
/ship โ reports PR URL
- Conductor reports PR URL to user
- User merges PR at their discretion
- User notifies conductor: "๋จธ์งํ์" / "merged"
- Conductor runs
git fetch origin to verify, then proceeds
Wave transition:
- After all workers in a wave create PRs, report to user: "Wave N complete. PRs: #X, #Y, #Z. ๋จธ์ง ํ ์๋ ค์ฃผ์ธ์."
- Wait for user confirmation before creating next wave's worktrees
Wave Analysis: Shared File Prediction
Before assigning issues to a wave, predict the FULL file footprint per issue:
- Explicit files: Listed in issue body
- Implicit shared files (always check):
package.json โ dependency changes
config.schema.ts / *.config.* โ schema/config updates
index.ts / barrel exports โ new module exports
README.md โ documentation updates
.gitignore โ new artifacts
- MCP server issues: Always flag
config.schema.ts as potential overlap
MCP Server Issues โ Mandatory Overlap Check
ANY issue that creates a new MCP handler MUST flag these files as modified:
apps/mcp-server/src/mcp/handlers/index.ts
apps/mcp-server/src/mcp/mcp.module.ts
ANY issue that creates a new NestJS module MUST flag:
apps/mcp-server/src/app.module.ts
ANY issue that adds to an existing module MUST flag that module file.
Two issues touching the same barrel/module file โ MUST be in different waves.
Overlap check:
For each pair of issues in the wave:
shared_files = issue_A.files โฉ issue_B.files
if shared_files is not empty:
โ Move one issue to next wave OR make sequential in same pane
Reference incidents:
- Wave A PR #876/#879 โ config.schema.ts modified by two issues not listed in issue body, causing merge conflict.
- Wave 1 PR #1142 โ handlers/index.ts and mcp.module.ts modified by two MCP handler issues (#1120, #1122) not listed in issue body, causing merge conflict.
Shell Script Safety Rules
Array Indexing Ban
NEVER use array indexing in monitoring/watch scripts. zsh is 1-indexed, bash is 0-indexed. This causes wrong issue mapping.
EXPECTED=("#888" "#811")
echo ${EXPECTED[$IDX]}
for PANE_INFO in "1:#888" "2:#811" "3:#813"; do
N="${PANE_INFO%%:*}"
EXP="${PANE_INFO##*:}"
done
Reference incident: zsh 1-based indexing caused pane 3 and 4's valid RESULT.json to be deleted.
Session Management
Compact Cadence
For long taskMaestro sessions (multiple waves):
- Compact after every 2 waves to prevent context degradation
- Compact before starting a new batch of waves
- Save critical state to
docs/codingbuddy/context.md before compact (via update_context)
- After compact: re-read taskmaestro-state.json to restore wave context
Recommended flow:
Wave 1 โ Wave 2 โ compact โ Wave 3 โ Wave 4 โ compact โ ...
Warning signs that compact is needed:
- Session cost exceeds $2
- Context usage exceeds 50%
- Response quality noticeably degrading
- Session duration exceeds 2 hours
Subcommand: start
/taskmaestro start [--repo <path>] [--base <branch>] [--panes <1,2,3>] [--review-pane] [--no-review-pane]
ํ์ฌ ์ธ์
์ ๊ธฐ์กด ํจ๋๋ค์ worktree + Claude Code๋ฅผ ์ธํ
ํ๋ค.
Step 1: ์ธ์ ํ์ฑ ๋ฐ ๊ธฐ๋ณธ๊ฐ ์ค์
--repo: ๋์ ๋ ํฌ ๊ฒฝ๋ก (๊ธฐ๋ณธ๊ฐ: ํ์ฌ ์์
๋๋ ํ ๋ฆฌ)
--base: ๋ฒ ์ด์ค ๋ธ๋์น (๊ธฐ๋ณธ๊ฐ: ํ์ฌ ๋ธ๋์น)
--panes: ์ฌ์ฉํ ํจ๋ ๋ฒํธ ๋ชฉ๋ก (๊ธฐ๋ณธ๊ฐ: ์งํ์ ํจ๋์ ์ ์ธํ ๋ชจ๋ ํจ๋)
--review-pane: ๋ฆฌ๋ทฐ ์ ์ฉ ํจ๋ ํ ๋น (๊ธฐ๋ณธ๊ฐ: ๋นํ์ฑํ). ํ์ฑํํ๋ ค๋ฉด --review-pane ๋ช
์ ์ฌ์ฉ
- Rationale: Conductor has PLAN context; worker reviewers do not. Conductor Review is the primary and default review method.
Step 2: ์ฌ์ ๊ฒ์ฆ
git -C "$REPO" rev-parse --git-dir
์๋ฌ ์ "๋์ ๊ฒฝ๋ก๊ฐ git ๋ ํฌ์งํ ๋ฆฌ๊ฐ ์๋๋๋ค" ์ถ๋ ฅ ํ ์ค๋จ.
tmux -L "$SOCKET_NAME" list-panes -t "$SESSION:$WIN_IDX" -F '#{pane_index} #{pane_current_command}'
์งํ์ ํจ๋($MY_PANE)์ ์ ์ธํ ํจ๋๋ค์ด ์์ปค ๋์์ด ๋๋ค. --panes๋ก ์ง์ ํ์ง ์์ผ๋ฉด ๋ชจ๋ ๋นํ์ฑ ํจ๋์ ์ฌ์ฉํ๋ค.
๋ฆฌ๋ทฐ ํจ๋ ํ ๋น (--review-pane, ๊ธฐ๋ณธ ๋นํ์ฑํ):
--review-pane ํ๋๊ทธ๋ฅผ ๋ช
์ํ๋ฉด ์ฌ์ฉ ๊ฐ๋ฅํ ํจ๋ ์ค ๋ง์ง๋ง ํจ๋์ ๋ฆฌ๋ทฐ ์์ด์ ํธ ์ ์ฉ์ผ๋ก ํ ๋นํ๋ค.
๊ธฐ๋ณธ๊ฐ์ ๋นํ์ฑํ์ด๋ฉฐ, ์งํ์๊ฐ ์ง์ ๋ฆฌ๋ทฐ๋ฅผ ์ํํ๋ค (Conductor Review).
Rationale: Conductor has PLAN context; worker reviewers do not.
if [ "$REVIEW_PANE_ENABLED" = true ]; then
REVIEW_PANE=$(echo "$AVAILABLE_PANES" | tail -1)
WORKER_PANES=$(echo "$AVAILABLE_PANES" | head -n -1)
if [ -z "$WORKER_PANES" ]; then
echo "ERROR: ๋ฆฌ๋ทฐ ํจ๋์ ํ ๋นํ๋ฉด ์์ปค ํจ๋์ด ์์ต๋๋ค. --no-review-pane์ ์ฌ์ฉํ๊ฑฐ๋ ํจ๋์ ์ถ๊ฐํ์ธ์."
exit 1
fi
else
REVIEW_PANE=""
WORKER_PANES="$AVAILABLE_PANES"
fi
์ต์ ํจ๋ ์๊ตฌ: --review-pane ์ฌ์ฉ ์ ์งํ์ + ์์ปค 1๊ฐ + ๋ฆฌ๋ทฐ์ด 1๊ฐ = ์ต์ 3๊ฐ ํจ๋์ด ํ์ํ๋ค.
์์ปค ํจ๋์ด ์๋ ๊ฒฝ์ฐ: ์งํ์ ํจ๋๋ง ์กด์ฌํ๋ฉด, ์ฒซ ๋ฒ์งธ ์ซ์ ์ธ์(๊ธฐ๋ณธ๊ฐ 3)๋งํผ ์ ํจ๋์ ์์ฑํ๋ค:
PANE_COUNT=${1:-3}
DIR=$(cd "$REPO" && pwd)
for ((i = 0; i < PANE_COUNT; i++)); do
tmux -L "$SOCKET_NAME" split-window -t "$SESSION:$WIN_IDX" -c "$DIR"
tmux -L "$SOCKET_NAME" select-layout -t "$SESSION:$WIN_IDX" tiled
done
tmux -L "$SOCKET_NAME" select-pane -t "$SESSION:$WIN_IDX.$MY_PANE"
์์ฑ ํ ๋ค์ ํจ๋ ๋ชฉ๋ก์ ์กฐํํ์ฌ ์์ปค ํจ๋์ ๊ฒฐ์ ํ๋ค.
Step 3: ๊ธฐ์กด ์ํ ํ์ผ ํ์ธ
cat ~/.claude/taskmaestro-state.json 2>/dev/null
์ด๋ฏธ ํ์ฑ ์ธ์
์ด ์กด์ฌํ๋ฉด AskUserQuestion์ผ๋ก ์ฌ์ฉ์์๊ฒ ์ง๋ฌธ:
- "๊ธฐ์กด ์ธ์
์ ์ฐ๊ฒฐ" โ ๊ธฐ์กด ์ธ์
์ ๋ณด๋ฅผ ๋ก๋ํ๊ณ ์ข
๋ฃ
- "๊ธฐ์กด ์ธ์
์ ๋ฆฌ ํ ์๋ก ์์ฑ" โ ๊ธฐ์กด worktree/์ธ์
์ ์ ๋ฆฌํ๊ณ ๊ณ์ ์งํ
Step 4: worktree ์์ฑ
TIMESTAMP=$(date +%s)
DIR=$(cd "$REPO" && pwd)
BASE_BRANCH="${BASE:-$(git -C "$DIR" branch --show-current)}"
mkdir -p "$DIR/.taskmaestro"
grep -qxF '.taskmaestro/' "$DIR/.gitignore" 2>/dev/null || echo '.taskmaestro/' >> "$DIR/.gitignore"
ALL_PANES="$WORKER_PANES"
[ -n "$REVIEW_PANE" ] && ALL_PANES="$ALL_PANES $REVIEW_PANE"
for PANE_IDX in $ALL_PANES; do
BRANCH="taskmaestro/$TIMESTAMP/pane-$PANE_IDX"
WT_PATH="$DIR/.taskmaestro/wt-$PANE_IDX"
if [ -d "$WT_PATH" ]; then
if git -C "$DIR" worktree list | grep -q "$WT_PATH"; then
git -C "$DIR" worktree remove "$WT_PATH" --force
else
rm -rf "$WT_PATH"
fi
fi
git -C "$DIR" worktree add "$WT_PATH" -b "$BRANCH" "$BASE_BRANCH"
done
Step 4.5: Worker Pre-Flight Verification
Before launching Claude Code, verify each worktree:
ALL_PANES="$WORKER_PANES"
[ -n "$REVIEW_PANE" ] && ALL_PANES="$ALL_PANES $REVIEW_PANE"
for PANE_IDX in $ALL_PANES; do
WT_PATH="$DIR/.taskmaestro/wt-$PANE_IDX"
rm -f "$WT_PATH/RESULT.json" "$WT_PATH/TASK.md"
if [ -f "$REPO/.claude/settings.local.json" ]; then
mkdir -p "$WT_PATH/.claude"
cp "$REPO/.claude/settings.local.json" "$WT_PATH/.claude/settings.local.json"
fi
echo "Installing dependencies in wt-$PANE_IDX..."
(cd "$WT_PATH" && yarn install --immutable 2>/dev/null) || echo "WARNING: yarn install failed in wt-$PANE_IDX"
if ! git -C "$WT_PATH" diff --quiet 2>/dev/null; then
echo "WARNING: wt-$PANE_IDX has uncommitted changes"
fi
done
This prevents:
- Stale RESULT.json from previous waves being detected as "completed"
- Workers unable to run local CI checks due to missing node_modules (root cause of repeated CI failures)
- Permission prompt interruptions blocking worker progress (#905)
- Dirty worktree state causing unexpected behavior
Step 5: ๊ฐ ํจ๋์ Claude Code ์คํ
for PANE_IDX in $WORKER_PANES; do
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$PANE_IDX" \
"unset CLAUDECODE && cd $DIR/.taskmaestro/wt-$PANE_IDX && claude --dangerously-skip-permissions" Enter
done
if [ -n "$REVIEW_PANE" ]; then
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$REVIEW_PANE" \
"unset CLAUDECODE && cd $DIR/.taskmaestro/wt-$REVIEW_PANE && claude --dangerously-skip-permissions" Enter
fi
์ค์: ์งํ์ Claude Code ์ธ์
์ tmux ํจ๋์ CLAUDECODE ํ๊ฒฝ๋ณ์๋ฅผ ์์๋ฐ์ ์ค์ฒฉ ์คํ์ด ์ฐจ๋จ๋๋ค. unset CLAUDECODE๋ก ํด์ ํด์ผ ํ๋ค.
Step 6: ์ด๊ธฐ ํ๋กฌํํธ ์๋ ์ฒ๋ฆฌ
Claude Code ์์ ์ trust ํ๋กฌํํธ์ ์ฝ๊ด ๋์ ํ๋กฌํํธ๊ฐ ๋ํ๋ ์ ์๋ค:
sleep 10
for PANE_IDX in $WORKER_PANES; do
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$PANE_IDX" Enter
done
sleep 3
for PANE_IDX in $WORKER_PANES; do
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$PANE_IDX" Down
done
sleep 1
for PANE_IDX in $WORKER_PANES; do
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$PANE_IDX" Enter
done
์ด๋ฏธ ๋์ํ ํ๊ฒฝ์์๋ ์ด ํ๋กฌํํธ๊ฐ ๋ํ๋์ง ์์ ์ ์๋ค. send-keys๋ ๋ฌดํดํ๊ฒ ๋ฌด์๋๋ค.
10์ด ๋๊ธฐ ํ ๊ฐ ํจ๋์ Claude Code๊ฐ ํ๋กฌํํธ(โฏ) ์ํ์ธ์ง capture-pane์ผ๋ก ํ์ธํ๋ค.
Step 7: ์ํ ํ์ผ ๊ธฐ๋ก
~/.claude/taskmaestro-state.json์ ์ธ์
์ ๋ณด๋ฅผ ๊ธฐ๋กํ๋ค.
{
"socket_name": "workspace-1",
"session": "workspace-1",
"window_index": 0,
"conductor_pane": 0,
"review_pane": 3,
"repo": "์ ๋๊ฒฝ๋ก",
"base_branch": "๋ธ๋์น",
"created_at": "ISO timestamp",
"watch_cron_id": null,
"panes": [
{"index": 1, "role": "worker", "worktree": "์ ๋๊ฒฝ๋ก/.taskmaestro/wt-1", "branch": "taskmaestro/ts/pane-1", "task": null, "status": "idle", "result": null},
{"index": 2, "role": "worker", "worktree": "์ ๋๊ฒฝ๋ก/.taskmaestro/wt-2", "branch": "taskmaestro/ts/pane-2", "task": null, "status": "idle", "result": null},
{"index": 3, "role": "reviewer", "worktree": "์ ๋๊ฒฝ๋ก/.taskmaestro/wt-3", "branch": "taskmaestro/ts/pane-3", "task": null, "status": "idle", "result": null}
]
}
review_pane: ๋ฆฌ๋ทฐ ์์ด์ ํธ ํจ๋ ์ธ๋ฑ์ค. --no-review-pane ์ฌ์ฉ ์ null
role: ๊ฐ ํจ๋์ ์ญํ . "worker" ๋๋ "reviewer"
- ๋ฆฌ๋ทฐ ํจ๋์ด ์๋ ๊ฒฝ์ฐ(
review_pane: null), ๋ชจ๋ ํจ๋์ role์ "worker"
Step 8: watch ์๋ ์์
CronCreate๋ก 30์ด ์ฃผ๊ธฐ cron job์ ์์ฑํ์ฌ watch ๋ชจ๋๋ฅผ ์๋ ์์ํ๋ค.
์์ฑ๋ cron job ID๋ฅผ ์ํ ํ์ผ์ watch_cron_id์ ๊ธฐ๋กํ๋ค.
ํ๋กฌํํธ: ๊ฐ ์์ปค ํจ๋์ ๋ํด 1์ฐจ: RESULT.json ํ์ผ ํ์ธ โ 2์ฐจ: capture-pane fallback ์์๋ก ์ํ๋ฅผ ๊ฐ์งํ๋ค. ์์ปค์ "success" ๊ฐ์ง ์ Review Routing (review_pane ์กด์ฌ ์ ๋ฆฌ๋ทฐ ์์ด์ ํธ์ ์์). ๋ฆฌ๋ทฐ ์์ด์ ํธ ํจ๋์ RESULT.json๋ ํ์ธํ์ฌ ๋ฆฌ๋ทฐ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ. ์๋ฃ/์๋ฌ ์ํ ํจ๋์ด ์์ผ๋ฉด ์ฌ์ฉ์์๊ฒ ๋ณด๊ณ ํ๋ค.
Step 9: ๊ฒฐ๊ณผ ๋ณด๊ณ
์ฌ์ฉ์์๊ฒ ๋ณด๊ณ :
TaskMaestro ์์ ์๋ฃ!
์ธ์
: workspace-1
๋ ํฌ: /path/to/repo
์งํ์: ํจ๋ 0
์์ปค: ํจ๋ 1, 2
๋ฆฌ๋ทฐ์ด: ํจ๋ 3
๋ฒ ์ด์ค: main
/taskmaestro assign <๋ฒํธ> "์์
" ์ผ๋ก ์์
์ ๋ฐฐ์ ํ์ธ์.
/taskmaestro status ๋ก ์ํ๋ฅผ ํ์ธํ์ธ์.
๋ฆฌ๋ทฐ ํจ๋์ด ์๋ ๊ฒฝ์ฐ (--no-review-pane): "๋ฆฌ๋ทฐ์ด" ์ค์ ์๋ตํ๊ณ ๋ชจ๋ ํจ๋์ ์์ปค๋ก ํ์ํ๋ค.
Subcommand: assign
/taskmaestro assign <ํจ๋ ๋ฒํธ> "<์์
์ง์>"
ํน์ ํจ๋์ Claude Code์ ์ ์์
์ ๋ฐฐ์ ํ๋ค. ์ํ ํ์ผ์ task/status๋ฅผ ์
๋ฐ์ดํธํ๋ค.
Step 1: ์ํ ํ์ผ ๋ก๋ ๋ฐ ๊ฒ์ฆ
cat ~/.claude/taskmaestro-state.json
์์ผ/์ธ์
/์๋์ฐ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค.
ํจ๋ ๋ฒํธ๊ฐ ์์ปค ํจ๋ ๋ชฉ๋ก์ ํฌํจ๋๋์ง ๊ฒ์ฆํ๋ค. ์งํ์ ํจ๋์ด๊ฑฐ๋ ์กด์ฌํ์ง ์๋ ํจ๋์ด๋ฉด ์๋ฌ ์ถ๋ ฅ ํ ์ค๋จ.
Step 2: ์์
์ง์ ํ์ผ ์์ฑ ๋ฐ ํธ๋ฆฌ๊ฑฐ ์ ์ก
์์ปค์๊ฒ ์์
์ง์์ ํจ๊ป RESULT.json ์์ฑ ์ง์๋ฅผ ํฌํจํ ํ๋กฌํํธ๋ฅผ TASK.md ํ์ผ๋ก ์ ๋ฌํ๋ค.
์ค์: ๊ธด ํ๋กฌํํธ๋ฅผ send-keys๋ก ์ง์ ์ ์กํ๋ฉด ์๋ฆด ์ ์๋ค. ๋ฐ๋์ TASK.md ํ์ผ์ ์์ฑํ๊ณ ์งง์ ํธ๋ฆฌ๊ฑฐ๋ง send-keys๋ก ์ ์กํ๋ค.
Step 2a: TASK.md ํ์ผ ์์ฑ
์์ปค worktree ๋ฃจํธ์ TASK.md๋ฅผ ์์ฑํ๋ค:
WT_PATH="$REPO/.taskmaestro/wt-$PANE"
cat > "$WT_PATH/TASK.md" << 'TASKEOF'
<์์
์ง์ ์ ์ฒด ๋ด์ฉ>
---
[MANDATORY PR RULES]
When creating PR with gh pr create, ALWAYS include --add-label with appropriate labels (feat, chore, fix, etc.).
[MANDATORY PRE-PUSH] yarn prettier --write . && yarn lint --fix && yarn type-check && yarn test โ ALL must pass before push.
[COMPLETION] Write RESULT.json: {"status":"success|failure|error","issue":"#NNNN","pr_number":N,"pr_url":"URL","timestamp":"ISO","cost":null,"error":null}
[CONTINUOUS] DO NOT stop. Complete ALL steps. Only stop AFTER RESULT.json.
TASKEOF
Step 2b: ์งง์ ํธ๋ฆฌ๊ฑฐ ์ ์ก
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$PANE" \
"Read TASK.md and execute all steps. Follow TDD. Create PR with correct labels. Write RESULT.json when done. " Enter
์ฐธ๊ณ : ํธ๋ฆฌ๊ฑฐ ๋ฉ์์ง์ ํต์ฌ ์ง์๋ฅผ ํ ์ค๋ก ์์ฝํ์ฌ ํฌํจํ๋ค. ์์ธ ์ง์๋ TASK.md์์ ์ฝ๊ฒ ๋๋ค.
Step 3: ์ํ ํ์ผ ์
๋ฐ์ดํธ
ํด๋น ํจ๋์ task์ status๋ฅผ ์
๋ฐ์ดํธํ๋ค:
task: ์์
์ง์ ๋ด์ฉ
status: "working"
Worker Completion & Review Fix Protocol
์์ปค๋ ์์
์๋ฃ ํ RESULT.json์ ์์ฑํ๋ค. ์ดํ ๋ฆฌ๋ทฐ ์ฌ์ดํด์ด ์์๋๋ฉด:
- ์งํ์(๋๋ watch cron)๊ฐ Review Fix TASK.md๋ฅผ ์์ปค worktree์ ์์ฑํ๋ค
- ์์ปค๋
"Read TASK.md and execute all steps." ํธ๋ฆฌ๊ฑฐ๋ฅผ ๋ฐ๊ณ TASK.md๋ฅผ ์ฝ์ด ์คํํ๋ค
- ์์ปค๊ฐ ๋ฆฌ๋ทฐ ๋ฐ์์ ์๋ฃํ๋ฉด:
- ์ฝ๋ ์์ โ pre-push ๊ฒ์ฆ โ push
- RESULT.json์
status๋ฅผ "review_addressed"๋ก ์
๋ฐ์ดํธ (ํ์)
- watch cron์ด
"review_addressed"๋ฅผ ๊ฐ์งํ์ฌ ์ฌ๋ฆฌ๋ทฐ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ค
ํต์ฌ: ์์ปค๊ฐ ๋ฆฌ๋ทฐ ๋ฐ์ ํ RESULT.json์ "review_addressed"๋ก ์
๋ฐ์ดํธํ์ง ์์ผ๋ฉด, watch cron์ด ์ฌ๋ฆฌ๋ทฐ๋ฅผ ์์ํ ์ ์๋ค. TASK.md ํ
ํ๋ฆฟ์ ์ด ์ง์๊ฐ ํฌํจ๋์ด ์๋ค.
Step 4: ๊ฒฐ๊ณผ ๋ณด๊ณ
ํจ๋ $PANE์ ์์
์ ๋ฐฐ์ ํ์ต๋๋ค: "<์์
์ง์>"
Subcommand: send
/taskmaestro send <ํจ๋ ๋ฒํธ> "<ํ
์คํธ>"
ํน์ ํจ๋์ ํ
์คํธ๋ฅผ ์ง์ ์ ์กํ๋ค.
์ด๋ฏธ ์งํ ์ค์ธ ์์
์ ํ์ ์ง์๋ ์๋ต์ ๋ณด๋ผ ๋ ์ฌ์ฉํ๋ค.
assign๊ณผ ๋ฌ๋ฆฌ ์ํ ํ์ผ์ task/status๋ฅผ ๋ณ๊ฒฝํ์ง ์๋๋ค.
Step 1: ์ํ ํ์ผ ๋ก๋ ๋ฐ ๊ฒ์ฆ
์์ผ/์ธ์
/์๋์ฐ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค.
ํจ๋ ๋ฒํธ ์ ํจ์ฑ ๊ฒ์ฆ.
Step 2: ํ
์คํธ ์ ์ก
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$PANE" "<ํ
์คํธ>" Enter
Step 3: ๊ฒฐ๊ณผ ๋ณด๊ณ
ํจ๋ $PANE์ ์ ์ก ์๋ฃ: "<ํ
์คํธ>"
Subcommand: status
/taskmaestro status
๋ชจ๋ ์์ปค ํจ๋์ ํ์ฌ ์ํ๋ฅผ ์์งํ์ฌ ์์ฝ ๋ณด๊ณ ํ๋ค.
Step 1: ์ํ ํ์ผ ๋ก๋
cat ~/.claude/taskmaestro-state.json
Step 2: RESULT.json ํ์ธ (1์ฐจ ๊ฐ์ง)
๊ฐ ์์ปค ํจ๋์ worktree์์ RESULT.json ์กด์ฌ ์ฌ๋ถ๋ฅผ ๋จผ์ ํ์ธํ๋ค:
for PANE_IDX in $WORKER_PANES; do
RESULT_FILE="$REPO/.taskmaestro/wt-$PANE_IDX/RESULT.json"
if [ -f "$RESULT_FILE" ]; then
STATUS=$(cat "$RESULT_FILE" | jq -r '.status')
PR_NUMBER=$(cat "$RESULT_FILE" | jq -r '.pr_number // empty')
REVIEW_CYCLE=$(cat "$RESULT_FILE" | jq -r '.review_cycle // 0')
fi
done
RESULT.json์ด ์กด์ฌํ๋ ํจ๋์ status ๊ฐ์ ๋ฐ๋ผ ๋ถ๊ธฐํ์ฌ ํ์ํ๋ค:
"success" โ ๋ฆฌ๋ทฐ ๋๊ธฐ (PR ์์ฑ ์๋ฃ)
"review_pending" โ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ ์์ฑ ์๋ฃ, ์์ปค ์๋ต ๋๊ธฐ
"review_addressed" โ ์์ปค ๋ฆฌ๋ทฐ ๋ฐ์ ์๋ฃ, ์ฌ๋ฆฌ๋ทฐ ๋๊ธฐ
"approved" โ ์ต์ข
์น์ธ ์๋ฃ (์ง์ง done)
"failure" / "error" โ ์คํจ/์๋ฌ (๊ธฐ์กด ๋์)
Step 3: capture-pane fallback (2์ฐจ ๊ฐ์ง)
RESULT.json์ด ์๋ ํจ๋์ ๋ํด์๋ง capture-pane์ผ๋ก ์ํ๋ฅผ ํ์ธํ๋ค:
for PANE_IDX in $NO_RESULT_PANES; do
tmux -L "$SOCKET_NAME" capture-pane -t "$SESSION:$WIN_IDX.$PANE_IDX" -p -S -30 | tail -15
done
์บก์ฒ๋ ๋ด์ฉ์ ๋ถ์:
- ๋ง์ง๋ง ์ค์ Claude Code ํ๋กฌํํธ (
โฏ ๋๋ > ๋ก ์์ํ๋ ์
๋ ฅ ๋๊ธฐ) โ "idle"
- ๋๊ตฌ ์คํ ์ค (์คํผ๋, ์ฝ๋ ์ถ๋ ฅ ์งํ ์ค) โ "working"
- ์๋ฌ ํจํด (
Error, error, failed, FAIL) โ "error"
- ํ๋ก์ธ์ค ์์ (zsh ํ๋กฌํํธ๋ง ๋ณด์) โ "crashed"
Step 4: ์์ฝ ๋ณด๊ณ
์ํ ํ์ผ์ task ์ ๋ณด์ ๊ฒฐํฉํ์ฌ ํ
์ด๋ธ๋ก ์ถ๋ ฅ:
TaskMaestro ์ํ (workspace-1):
ํจ๋ 1: โ
approved - "API ์๋ํฌ์ธํธ ๊ตฌํ" โ PR #42 (review complete) (worker)
ํจ๋ 2: ๐ reviewing - PR #43 (reviewer)
ํจ๋ 3: ๐ review_addressed - "๋ชจ๋ ๋ฆฌํฉํ ๋ง" โ PR #44 (awaiting re-review) (worker)
ํจ๋ 4: ๐ working - "์ธ์ฆ ๊ธฐ๋ฅ ์ถ๊ฐ" (worker)
ํจ๋ 5: โณ review_pending - "์บ์ ๋ ์ด์ด" โ PR #45 (awaiting worker response) (worker)
ํจ๋ 6: โ error - "๋ชจ๋์ ์ฐพ์ ์ ์์" (worker)
์ํ๋ณ ์์ด์ฝ:
| status | ์์ด์ฝ | ์ค๋ช
|
|---|
approved | โ
| ์ต์ข
์น์ธ ์๋ฃ (์ง์ง done) |
success (๋ฆฌ๋ทฐ ์์ ์ ) | ๐ | ๋ฆฌ๋ทฐ ์ฌ์ดํด ์งํ ์ค |
reviewing | ๐ | ๋ฆฌ๋ทฐ ์์ด์ ํธ๊ฐ PR ๋ฆฌ๋ทฐ ์ค (reviewer ํจ๋ ์ ์ฉ) |
review_pending | โณ | ๋ฆฌ๋ทฐ ์๋ฃ, ์์ปค ์๋ต ๋๊ธฐ |
review_addressed | ๐ | ์์ปค ๋ฐ์ ์๋ฃ, ์ฌ๋ฆฌ๋ทฐ ๋๊ธฐ |
working | ๐ | ์์
์ค |
failure | โ | ์์
์คํจ |
error | โ | ์๊ธฐ์น ๋ชปํ ์ค๋ฅ |
approved ํจ๋: RESULT.json์์ pr_url, cost, review_cycle ๋ฑ์ ํจ๊ป ํ์
- ๋ฆฌ๋ทฐ ์งํ ์ค ํจ๋: PR ๋ฒํธ์ ํ์ฌ ๋ฆฌ๋ทฐ ์ฌ์ดํด ํ์ (N/3) ํ์
error/crashed ํจ๋: capture-pane ๋ด์ฉ ๋ง์ง๋ง 10์ค๋ ํจ๊ป ํ์
Status Report Format (with evidence)
Every status line MUST include parenthetical evidence:
ํจ๋ 1: โ
approved - "task" โ PR #N (RESULT.json: approved, review cycles: 2, pane idle โ) (worker)
ํจ๋ 2: ๐ working - "task" (active: โฝ Implementingโฆ ยท โ 5.3k tokens) (worker)
ํจ๋ 3: ๐ reviewing - PR #N (RESULT.json: reviewing PR #N for #M, pane active โ) (reviewer)
ํจ๋ 4: โณ review_pending - "task" โ PR #N (RESULT.json: review_pending, cycle 1/3, awaiting worker) (worker)
ํจ๋ 5: ๐ review_addressed - "task" โ PR #N (RESULT.json: review_addressed, cycle 2/3, awaiting re-review) (worker)
ํจ๋ 6: โ ๏ธ uncertain - "task" (RESULT.json exists BUT pane still active โ cross-verify needed) (worker)
ํจ๋ 7: โ error - "task" (error in last 10 lines: "ModuleNotFoundError") (worker)
Rules:
- Never report bare โ
without evidence
- RESULT.json
approved + pane idle = confirmed done
- RESULT.json
success = start review cycle (NOT done)
- RESULT.json
review_pending = awaiting worker response
- RESULT.json
review_addressed = awaiting conductor re-review
- RESULT.json + pane active = UNCERTAIN (investigate)
- No RESULT.json + pane idle = needs nudge
- Active spinner (โฆ + โ tokens) = confirmed working
- "thinking" alone โ working โ check token count changes
Subcommand: watch
/taskmaestro watch
์ฃผ๊ธฐ์ ๊ฐ์ ๋ชจ๋๋ฅผ ํ ๊ธํ๋ค (์์/์ค์ง).
๊ฐ์ง ์ ๋ต (2๋จ๊ณ)
watch ๋ชจ๋๋ RESULT.json ํ์ผ ๊ธฐ๋ฐ ๊ฐ์ง๋ฅผ 1์ฐจ ์๋จ์ผ๋ก ์ฌ์ฉํ๊ณ , capture-pane์ fallback์ผ๋ก๋ง ์ฌ์ฉํ๋ค.
1์ฐจ: RESULT.json ํ์ผ ๊ฐ์ง (๋น ๋ฅด๊ณ ์ ํ)
for PANE_IDX in $WORKER_PANES; do
RESULT_FILE="$REPO/.taskmaestro/wt-$PANE_IDX/RESULT.json"
if [ -f "$RESULT_FILE" ]; then
STATUS=$(cat "$RESULT_FILE" | jq -r '.status')
case "$STATUS" in
"success")
;;
"failure"|"error")
;;
"review_pending")
;;
"review_addressed")
;;
"approved")
;;
esac
fi
done
if [ -n "$REVIEW_PANE" ]; then
REVIEW_RESULT_FILE="$REPO/.taskmaestro/wt-$REVIEW_PANE/RESULT.json"
if [ -f "$REVIEW_RESULT_FILE" ]; then
REVIEW_STATUS=$(cat "$REVIEW_RESULT_FILE" | jq -r '.status')
REVIEW_RESULT=$(cat "$REVIEW_RESULT_FILE" | jq -r '.review_result')
if [ "$REVIEW_STATUS" = "success" ]; then
rm -f "$REVIEW_RESULT_FILE"
fi
fi
fi
RESULT.json status ๋งคํ:
| status | watch ๋์ |
|---|
"success" | ๋ฆฌ๋ทฐ ์ฌ์ดํด ์์ ("done"์ด ์๋) |
"failure" / "error" | ์ฌ์ฉ์์๊ฒ ๋ณด๊ณ (๊ธฐ์กด ๋์ ์ ์ง) |
"review_pending" | ์์ปค ์๋ต ๋๊ธฐ ์ค |
"review_addressed" | ์งํ์ ์ฌ๋ฆฌ๋ทฐ ์์ |
"approved" | ์ง์ง ์๋ฃ โ
|
- RESULT.json์ด ์กด์ฌํ๋ฉด: status ๊ฐ์ ๋ฐ๋ผ ์ ๋งคํ๋๋ก ๋ถ๊ธฐํ๋ค.
- RESULT.json์ด ์์ผ๋ฉด: ์์ง ์์
์ค์ด๊ฑฐ๋ ํ์ผ ๋ฏธ์์ฑ. fallback์ผ๋ก ์งํํ๋ค.
2์ฐจ (fallback): capture-pane ๊ธฐ๋ฐ ๊ฐ์ง
RESULT.json์ด ์๋ ํจ๋์ ๋ํด์๋ง capture-pane์ผ๋ก ์ํ๋ฅผ ํ์ธํ๋ค:
for PANE_IDX in $NO_RESULT_PANES; do
CAPTURED=$(tmux -L "$SOCKET_NAME" capture-pane -t "$SESSION:$WIN_IDX.$PANE_IDX" -p -S -30 | tail -15)
done
์์ (watch_cron_id๊ฐ null์ธ ๊ฒฝ์ฐ)
-
์ํ ํ์ผ์์ ์ ๋ณด ๋ก๋
-
CronCreate๋ก 30์ด ์ฃผ๊ธฐ cron job ์์ฑ. ํ๋กฌํํธ์ ์๋ ์ ์ฒด ๋ก์ง์ ํฌํจํ๋ค:
์์ปค ํจ๋ ์ํ (1์ฐจ: RESULT.json โ 2์ฐจ: capture-pane fallback):
๊ฐ ์์ปค ํจ๋์ ๋ํด $REPO/.taskmaestro/wt-$PANE_IDX/RESULT.json์ ํ์ธํ๊ณ , status์ ๋ฐ๋ผ ๋ถ๊ธฐํ๋ค:
-
"success" ๊ฐ์ง (๋ฆฌ๋ทฐ ์ฌ์ดํด ์์):
- ์ํ ํ์ผ์์
review_pane ํ์ธ
review_pane ์กด์ฌ ์ โ Review Agent Protocol์ ๋ฐ๋ผ ๋ฆฌ๋ทฐ ์์ด์ ํธ์ ์์
review_pane ์์ผ๋ฉด โ Conductor Review ์คํ:
a. gh pr diff <PR_NUMBER> ๋ก PR ๋ณ๊ฒฝ์ฌํญ ์ฝ๊ธฐ
b. codingbuddy generate_checklist ๋๊ตฌ๋ก ์ฒดํฌ๋ฆฌ์คํธ ์์ฑ
c. gh pr review <PR_NUMBER> --comment --body "<๋ฆฌ๋ทฐ>" ๋ก ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ ์์ฑ
d. ์์ปค์ RESULT.json ์
๋ฐ์ดํธ: status: "review_pending", review_cycle ์ฆ๊ฐ, review_comments ์ถ๊ฐ
e. ์์ปค worktree์ Review Fix TASK.md ์์ฑ (์๋ ํ
ํ๋ฆฟ ์ฐธ์กฐ):
cat > "$REPO/.taskmaestro/wt-$PANE_IDX/TASK.md" << 'TASKEOF'
... (Review Fix TASK.md Template ์ฐธ์กฐ)
TASKEOF
f. ์์ปค์๊ฒ ์งง์ ํธ๋ฆฌ๊ฑฐ ์ ์ก:
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$PANE_IDX" \
"Read TASK.md and execute all steps." Enter
-
"review_addressed" ๊ฐ์ง (์ฌ๋ฆฌ๋ทฐ):
review_cycle ํ์ธ. 3 ์ด์์ด๋ฉด ์ฌ์ฉ์์๊ฒ ๋ณด๊ณ ํ๊ณ ํด๋น ํจ๋ ๋ฆฌ๋ทฐ ์ค๋จ
- ์ํ ํ์ผ์์
review_pane ํ์ธ
review_pane ์กด์ฌ ์ โ ๋ฆฌ๋ทฐ ์์ด์ ํธ์ ์ฌ๋ฆฌ๋ทฐ ์์
review_pane ์์ผ๋ฉด โ Conductor Review ์ฌ์คํ:
a. gh pr diff <PR_NUMBER> ์ฌํ์ธ
b. ๋ฏธํด๊ฒฐ ์ฝ๋ฉํธ ํ์ธ (gh pr view <PR_NUMBER> --comments)
c. ์ถฉ๋ถํ ๊ฐ์ โ status: "approved", PR approve ์ฝ๋ฉํธ ์์ฑ, ์ฌ์ฉ์์๊ฒ ์๋ฃ ๋ณด๊ณ
d. ์์ง ๋ถ์กฑ โ ์ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ ์์ฑ ํ Review Fix TASK.md๋ฅผ ๋ค์ ์์ปค์๊ฒ ์ ๋ฌ
-
"approved" ๊ฐ์ง: ์ง์ง ์๋ฃ โ
. ์ํ๋ฅผ "done"์ผ๋ก ์
๋ฐ์ดํธํ๊ณ ์ฌ์ฉ์์๊ฒ ๋ณด๊ณ
-
"failure" / "error" ๊ฐ์ง: ์ฌ์ฉ์์๊ฒ ์๋ฌ/์คํจ ๋ณด๊ณ (๊ธฐ์กด ๋์ ์ ์ง)
-
"review_pending" ๊ฐ์ง: ์์ปค ์๋ต ๋๊ธฐ ์ค. ์งํ์๋ ๋๊ธฐํ๋ค
๋ฆฌ๋ทฐ ์์ด์ ํธ ํจ๋ ํ์ธ (review_pane์ด ์ค์ ๋ ๊ฒฝ์ฐ):
$REPO/.taskmaestro/wt-$REVIEW_PANE/RESULT.json ํ์ธ:
review_result: "approve" โ ์์ปค์ RESULT.json์ status: "approved"๋ก ์
๋ฐ์ดํธ, PR approve ์ฝ๋ฉํธ ์์ฑ, ๋ฆฌ๋ทฐ ์์ด์ ํธ RESULT.json ์ญ์
review_result: "changes_requested" โ ์์ปค์ RESULT.json์ status: "review_pending"์ผ๋ก ์
๋ฐ์ดํธ (review_cycle ์ฆ๊ฐ), ์์ปค worktree์ Review Fix TASK.md ์์ฑ, ์์ปค์๊ฒ "Read TASK.md and execute all steps." ํธ๋ฆฌ๊ฑฐ ์ ์ก, ๋ฆฌ๋ทฐ ์์ด์ ํธ RESULT.json ์ญ์
Fallback (RESULT.json ์๋ ํจ๋):
capture-pane์ผ๋ก ์ํ ํ์ธ. idle์ธ๋ฐ status๊ฐ "working"์ด๋ฉด Auto-Nudge Protocol ์คํ.
Claude Code๊ฐ ๋น์ ์ ์ข
๋ฃ๋ ํจ๋์ด ์์ผ๋ฉด ์ฌ์์ ์ฌ๋ถ๋ฅผ ์ฌ์ฉ์์๊ฒ ์ง๋ฌธํ๋ค.
-
cron job ID๋ฅผ ์ํ ํ์ผ์ watch_cron_id์ ๊ธฐ๋ก
-
๋ณด๊ณ : "Watch ๋ชจ๋๋ฅผ ์์ํฉ๋๋ค (30์ด ์ฃผ๊ธฐ). ์ค์งํ๋ ค๋ฉด /taskmaestro watch๋ฅผ ๋ค์ ์คํํ์ธ์."
์ค์ง (watch_cron_id๊ฐ ์กด์ฌํ๋ ๊ฒฝ์ฐ)
- ์ํ ํ์ผ์์
watch_cron_id ํ์ธ
- CronDelete๋ก ํด๋น cron job ์ ๊ฑฐ
watch_cron_id๋ฅผ null๋ก ์
๋ฐ์ดํธ
- ๋ณด๊ณ : "Watch ๋ชจ๋๋ฅผ ์ค์งํ์ต๋๋ค."
Auto-Nudge Protocol
When a pane is detected as idle (โฏ prompt visible) but its task status is "working":
- First idle detection: Send
tmux send-keys "continue" Enter
- Second consecutive idle: Send
tmux send-keys "continue with the next incomplete task" Enter
- Third consecutive idle: Send explicit instruction with task context
- Track nudge count per pane in the watch report
Include in watch cron prompt:
If pane is idle (โฏ visible) and status is "working":
nudge_count[pane] += 1
if nudge_count <= 1: send "continue"
elif nudge_count == 2: send "continue with the next incomplete task"
else: send explicit instruction
Report: "Pane N: IDLE โ auto-nudged (#count)"
์ ํ ์ฌํญ
CronCreate job์ ํ์ฌ Claude Code ์ธ์
์ ์ข
์๋๋ฉฐ, 7์ผ ํ ์๋ ๋ง๋ฃ๋๋ค.
์ธ์
์ด ์ข
๋ฃ๋๊ฑฐ๋ ๋ง๋ฃ ์ watch๋ ์ค๋จ๋๋ค. ์ฌ์์ํ๋ ค๋ฉด /taskmaestro watch๋ฅผ ๋ค์ ์คํํ๋ค.
Subcommand: stop
/taskmaestro stop [ํจ๋ ๋ฒํธ|all]
Claude Code๋ฅผ ์ข
๋ฃํ๊ณ worktree๋ฅผ ์ ๋ฆฌํ๋ค.
์ธ์ ํ์ฑ
- ์ธ์ ์์ ๋๋
all: ๋ชจ๋ ์์ปค ํจ๋ ์ข
๋ฃ
- ์ซ์: ํด๋น ํจ๋๋ง ์ข
๋ฃ
๋จ์ผ ํจ๋ ์ข
๋ฃ
- ํจ๋ ๋ฒํธ๊ฐ ์์ปค ํจ๋์ธ์ง ๊ฒ์ฆ. ์๋๋ฉด ์๋ฌ ์ถ๋ ฅ ํ ์ค๋จ.
- ํด๋น ํจ๋์ Claude Code ์ข
๋ฃ ์ ์ก:
tmux -L "$SOCKET_NAME" send-keys -t "$SESSION:$WIN_IDX.$PANE" "/exit" Enter
- 5์ด ๋๊ธฐ ํ worktree ์ ๋ฆฌ:
if git -C "$WORKTREE_PATH" diff --quiet && git -C "$WORKTREE_PATH" diff --cached --quiet; then
git -C "$REPO" worktree remove "$WORKTREE_PATH"
else
fi
- ํจ๋ ๋ซ๊ธฐ:
tmux -L "$SOCKET_NAME" kill-pane -t "$SESSION:$WIN_IDX.$PANE"
- ์ํ ํ์ผ์์ ํด๋น ํจ๋ ์ ๋ณด ์ ๊ฑฐ
- ๋ณด๊ณ :
ํจ๋ N์ ์ข
๋ฃํ์ต๋๋ค. (๋ธ๋์น taskmaestro/xxx/pane-N ์ ์ง๋จ)
์ ์ฒด ์ข
๋ฃ (all)
- watch cron ์ค์ง (ํ์ฑ ์ํ์ธ ๊ฒฝ์ฐ CronDelete)
- ๊ฐ ์์ปค ํจ๋์
/exit ์ ์ก
- 5์ด ๋๊ธฐ
- ๊ฐ worktree ์ ๋ฆฌ (uncommitted changes ํ์ธ ํฌํจ)
- ์์ปค ํจ๋๋ค ๋ซ๊ธฐ (์ญ์์ผ๋ก):
for PANE_IDX in $(echo "$WORKER_PANES" | sort -rn); do
tmux -L "$SOCKET_NAME" kill-pane -t "$SESSION:$WIN_IDX.$PANE_IDX"
done
.taskmaestro/ ๋๋ ํ ๋ฆฌ๊ฐ ๋น์์ผ๋ฉด ์ญ์ :
rmdir "$REPO/.taskmaestro" 2>/dev/null
- ์ํ ํ์ผ ์ด๊ธฐํ
- ๋ณด๊ณ :
TaskMaestro ์ข
๋ฃ ์๋ฃ.
์ ๋ฆฌ๋ ์์ปค: N๊ฐ
์ ์ง๋ ๋ธ๋์น: taskmaestro/xxx/pane-1, pane-2, ...
์ฃผ์: stop all์ tmux ์ธ์
์ ์ข
๋ฃํ์ง ์๋๋ค. ํจ๋๋ค์ ๊ทธ๋๋ก ๋จ์์์ผ๋ฉฐ zsh ์ํ๋ก ๋์๊ฐ๋ค.