원클릭으로
doey-worktree
Isolate a team window in a git worktree, or return it.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Isolate a team window in a git worktree, or return it.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Plan-first task creation — research, breakdown, risk analysis, then create tasks from approved plan. Usage: /doey-planned-task <goal>
Request a respawn of the current worker pane. Use when you need to "respawn me", "restart myself", "fresh context", or "reset my pane". Writes an atomic respawn request that kills the current Claude process and relaunches with a fresh context.
Discover scaffolding patterns in this project — structural fingerprints, accretion files, and refactoring patterns from git history. Returns candidates worth turning into templates.
Spawn the doey-scaffy MCP server so workers can drive the Scaffy template engine via tool calls. Loaded automatically when a task needs scaffy_run / scaffy_validate / scaffy_list / scaffy_audit / scaffy_discover / scaffy_new / scaffy_fmt or any scaffy:// resource.
Create a new scaffy template from existing files (--from-files) or interactively. Outputs to .doey/scaffy/templates/<name>.scaffy.
Manage persistent project tasks — list, add, transition, show, subtasks, decisions, notes. Tasks stored in .doey/tasks/ (survives reboot). Use when you need to "show tasks", "add a task", "mark a task done", "what are we working on", or "create a task".
| name | doey-worktree |
| description | Isolate a team window in a git worktree, or return it. |
cat $(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)/session.env 2>/dev/null || truefor f in $(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)/team_*.env; do [ -f "$f" ] && echo "--- $(basename $f) ---" && cat "$f"; done || trueW=$(tmux show-environment DOEY_WINDOW_INDEX 2>/dev/null | cut -d= -f2-); for f in $(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)/status/*_${W}_*.status; do [ -f "$f" ] && echo "--- $(basename $f) ---" && cat "$f"; done 2>/dev/null || true/doey-worktree [W] (isolate) | [W] --back (return). No confirmation.
RD=$(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)
TARGET_WIN="${DOEY_WINDOW_INDEX:-1}"; BACK_MODE=false # parse --back from args
[ "$TARGET_WIN" = "0" ] && { echo "ERROR: Cannot transform Dashboard"; exit 1; }
TEAM_ENV="${RD}/team_${TARGET_WIN}.env"
[ ! -f "$TEAM_ENV" ] && { echo "ERROR: No team env for window ${TARGET_WIN}"; exit 1; }
SESSION_NAME=$(grep '^SESSION_NAME=' "${RD}/session.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"')
tmux list-windows -t "$SESSION_NAME" -F '#{window_index}' 2>/dev/null | grep -qx "$TARGET_WIN" || { echo "ERROR: Window ${TARGET_WIN} not found"; exit 1; }
PROJECT_DIR=$(grep '^PROJECT_DIR=' "${RD}/session.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"')
PROJECT_NAME=$(grep '^PROJECT_NAME=' "${RD}/session.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"')
_tv() { grep "^$1=" "$TEAM_ENV" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'; }
WORKER_PANES=$(_tv WORKER_PANES); WORKTREE_DIR=$(_tv WORKTREE_DIR); WORKTREE_BRANCH=$(_tv WORKTREE_BRANCH)
WPL=$(echo "$WORKER_PANES" | tr ',' ' '); SESSION_SAFE=$(echo "$SESSION_NAME" | tr ':.-' '_')
BUSY=""
for i in $WPL; do
SF="${RD}/status/${SESSION_SAFE}_${TARGET_WIN}_${i}.status"
[ -f "$SF" ] && grep -q '^STATUS: *BUSY' "$SF" && BUSY="$BUSY ${TARGET_WIN}.${i}"
done
[ -n "$BUSY" ] && { echo "ERROR: Busy workers:${BUSY} — wait or /doey-stop first"; exit 1; }
# Validate mode vs state
if [ "$BACK_MODE" = "true" ]; then [ -z "$WORKTREE_DIR" ] && { echo "ERROR: Not in a worktree"; exit 1; }
else [ -n "$WORKTREE_DIR" ] && { echo "ERROR: Already in worktree — use --back first"; exit 1; }; fi
BRANCH="doey/team-${TARGET_WIN}-$(date +%m%d-%H%M)"
WT_DIR="/tmp/doey/${PROJECT_NAME}/worktrees/team-${TARGET_WIN}"
if [ -d "$WT_DIR" ]; then
if git -C "$WT_DIR" status --porcelain 2>/dev/null | grep -q '^'; then
echo "ERROR: Worktree $WT_DIR has uncommitted changes. Commit or stash first."
exit 1
fi
git -C "$PROJECT_DIR" worktree remove "$WT_DIR" --force 2>/dev/null || true
fi
mkdir -p "/tmp/doey/${PROJECT_NAME}/worktrees"
WT_OUTPUT=$(git -C "$PROJECT_DIR" worktree add "$WT_DIR" -b "$BRANCH" 2>&1) || { echo "ERROR: $WT_OUTPUT"; exit 1; }
[ -f "${PROJECT_DIR}/.claude/settings.local.json" ] && mkdir -p "${WT_DIR}/.claude" && cp "${PROJECT_DIR}/.claude/settings.local.json" "${WT_DIR}/.claude/"
TMPENV=$(mktemp "${RD}/team_${TARGET_WIN}.env.tmp_XXXXXX")
cat "$TEAM_ENV" > "$TMPENV"; printf 'WORKTREE_DIR=%s\nWORKTREE_BRANCH=%s\n' "$WT_DIR" "$BRANCH" >> "$TMPENV"
mv "$TMPENV" "$TEAM_ENV"; TARGET_DIR="$WT_DIR"
echo "export DOEY_ALLOW_AGENT_WORKTREE=1" >> "$TEAM_ENV"
DIRTY=$(git -C "$WORKTREE_DIR" status --porcelain 2>/dev/null)
if [ -n "$DIRTY" ]; then
# Write auto-save audit log
AUTO_SAVE_DIR="${RD}/auto-saves"
mkdir -p "$AUTO_SAVE_DIR"
WT_BRANCH=$(git -C "$WORKTREE_DIR" branch --show-current 2>/dev/null || echo "unknown")
{
printf 'AUTO-SAVE: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
printf 'WORKTREE: %s\n' "$WORKTREE_DIR"
printf 'BRANCH: %s\n' "$WT_BRANCH"
printf 'FILES:\n'
git -C "$WORKTREE_DIR" status --porcelain 2>/dev/null || true
} > "${AUTO_SAVE_DIR}/${WT_BRANCH}_$(date +%s).log" 2>/dev/null || true
git -C "$WORKTREE_DIR" add -A && git -C "$WORKTREE_DIR" commit -m "doey: WIP from team ${TARGET_WIN} worktree"
fi
echo "Commits on branch ${WORKTREE_BRANCH}:"
git -C "$WORKTREE_DIR" log --oneline "$(git -C "$PROJECT_DIR" rev-parse HEAD)..HEAD" 2>/dev/null || echo " (none)"
git -C "$PROJECT_DIR" worktree remove "$WORKTREE_DIR" --force 2>&1 || echo "WARNING: Manual removal needed"
TMPENV=$(mktemp "${RD}/team_${TARGET_WIN}.env.tmp_XXXXXX")
grep -v '^WORKTREE_DIR=\|^WORKTREE_BRANCH=' "$TEAM_ENV" > "$TMPENV"; mv "$TMPENV" "$TEAM_ENV"
TARGET_DIR="$PROJECT_DIR"
for i in $WPL; do
CHILD=$(pgrep -P "$(tmux display-message -t "${SESSION_NAME}:${TARGET_WIN}.${i}" -p '#{pane_pid}' 2>/dev/null)" 2>/dev/null)
[ -n "$CHILD" ] && kill "$CHILD" 2>/dev/null
done; sleep 3
for i in $WPL; do
CHILD=$(pgrep -P "$(tmux display-message -t "${SESSION_NAME}:${TARGET_WIN}.${i}" -p '#{pane_pid}' 2>/dev/null)" 2>/dev/null)
[ -n "$CHILD" ] && kill -9 "$CHILD" 2>/dev/null
done; sleep 1
for i in $WPL; do
tmux copy-mode -q -t "${SESSION_NAME}:${TARGET_WIN}.${i}" 2>/dev/null
source "$HOME/.local/bin/doey-send.sh" 2>/dev/null || true
doey_send_command "${SESSION_NAME}:${TARGET_WIN}.${i}" "clear"
done; sleep 1
for i in $WPL; do
WP=$(grep -l "pane ${TARGET_WIN}\.${i} " "${RD}/worker-system-prompt-"*.md 2>/dev/null | head -1 || true)
CMD="cd \"${TARGET_DIR}\" && claude --dangerously-skip-permissions --model opus --name \"T${TARGET_WIN} W${i}\""
[ -n "$WP" ] && CMD="${CMD} --append-system-prompt-file \"${WP}\""
doey_send_launch "${SESSION_NAME}:${TARGET_WIN}.${i}" "$CMD"; sleep 0.5
done
[ "$BACK_MODE" = "true" ] && tmux rename-window -t "${SESSION_NAME}:${TARGET_WIN}" "T${TARGET_WIN}" || tmux rename-window -t "${SESSION_NAME}:${TARGET_WIN}" "T${TARGET_WIN} [worktree]"
for attempt in 1 2 3 4 5; do
NOT_READY=0; DOWN=""
for i in $WPL; do
tmux capture-pane -t "${SESSION_NAME}:${TARGET_WIN}.${i}" -p 2>/dev/null | grep -q "bypass permissions" || { NOT_READY=$((NOT_READY + 1)); DOWN="$DOWN ${TARGET_WIN}.$i"; }
done
[ "$NOT_READY" -eq 0 ] && break; sleep 5
done
Output: mode, window, branch, directory, boot count. Never transform window 0.