@walwal-harness/cli 4.0.0-beta.8 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "4.0.0
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer (requirements refinement). Supports React and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -133,10 +133,55 @@ render_features() {
|
|
|
133
133
|
done
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
render_bottleneck_alert() {
|
|
137
|
+
if [ ! -f "$QUEUE" ] || [ ! -f "$FEATURES" ]; then return; fi
|
|
138
|
+
|
|
139
|
+
local failed_list blocked_count idle_teams in_prog
|
|
140
|
+
failed_list=$(jq -r '.queue.failed[]' "$QUEUE" 2>/dev/null)
|
|
141
|
+
blocked_count=$(jq '.queue.blocked | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
142
|
+
in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
143
|
+
idle_teams=0
|
|
144
|
+
|
|
145
|
+
# Count idle teams
|
|
146
|
+
local team_count
|
|
147
|
+
team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
148
|
+
for i in $(seq 1 "$team_count"); do
|
|
149
|
+
local ts
|
|
150
|
+
ts=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
|
|
151
|
+
if [ "$ts" = "idle" ]; then idle_teams=$((idle_teams + 1)); fi
|
|
152
|
+
done
|
|
153
|
+
|
|
154
|
+
if [ -z "$failed_list" ]; then return; fi
|
|
155
|
+
|
|
156
|
+
# Check each failed feature for blocking impact
|
|
157
|
+
while IFS= read -r fid; do
|
|
158
|
+
[ -z "$fid" ] && continue
|
|
159
|
+
# Count how many blocked features depend on this fid
|
|
160
|
+
local deps_on_this
|
|
161
|
+
deps_on_this=$(jq --arg fid "$fid" '[.queue.blocked | to_entries[] | select(.value | index($fid))] | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
162
|
+
|
|
163
|
+
if [ "$deps_on_this" -gt 0 ]; then
|
|
164
|
+
echo ""
|
|
165
|
+
echo -e " ${RED}${BOLD}⚠ BOTTLENECK${RESET} ${RED}${fid}${RESET} failed → ${YELLOW}${deps_on_this} features blocked${RESET}"
|
|
166
|
+
if [ "$idle_teams" -gt 0 ]; then
|
|
167
|
+
echo -e " ${BOLD}→ requeue:${RESET} bash scripts/harness-queue-manager.sh requeue ${fid} ."
|
|
168
|
+
fi
|
|
169
|
+
fi
|
|
170
|
+
done <<< "$failed_list"
|
|
171
|
+
|
|
172
|
+
# All teams idle + nothing in progress = stalled
|
|
173
|
+
if [ "$idle_teams" -eq "$team_count" ] && [ "$in_prog" -eq 0 ] && [ "$blocked_count" -gt 0 ]; then
|
|
174
|
+
echo ""
|
|
175
|
+
echo -e " ${RED}${BOLD}!! STALLED${RESET} — All teams idle, ${blocked_count} features blocked"
|
|
176
|
+
echo -e " ${BOLD}→ requeue failed features or check dependency graph${RESET}"
|
|
177
|
+
fi
|
|
178
|
+
}
|
|
179
|
+
|
|
136
180
|
render_all() {
|
|
137
181
|
render_header
|
|
138
182
|
render_queue_summary
|
|
139
183
|
render_teams
|
|
184
|
+
render_bottleneck_alert
|
|
140
185
|
echo ""
|
|
141
186
|
render_features
|
|
142
187
|
}
|
|
@@ -10,7 +10,19 @@ set -uo pipefail
|
|
|
10
10
|
|
|
11
11
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# ── Args ──
|
|
14
|
+
# Usage: harness-monitor.sh [project-root] [--team N]
|
|
15
|
+
# --team N → 단일 팀만 렌더 (tmux per-team pane 용도)
|
|
16
|
+
PROJECT_ROOT=""
|
|
17
|
+
TEAM_FILTER=""
|
|
18
|
+
while [ $# -gt 0 ]; do
|
|
19
|
+
case "$1" in
|
|
20
|
+
--team) TEAM_FILTER="$2"; shift 2 ;;
|
|
21
|
+
--team=*) TEAM_FILTER="${1#--team=}"; shift ;;
|
|
22
|
+
*) [ -z "$PROJECT_ROOT" ] && PROJECT_ROOT="$1"; shift ;;
|
|
23
|
+
esac
|
|
24
|
+
done
|
|
25
|
+
|
|
14
26
|
if [ -z "$PROJECT_ROOT" ]; then
|
|
15
27
|
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
16
28
|
PROJECT_ROOT="$(resolve_harness_root ".")" || {
|
|
@@ -58,8 +70,18 @@ render_v4_header() {
|
|
|
58
70
|
echo ""
|
|
59
71
|
}
|
|
60
72
|
|
|
73
|
+
# 고정폭 배너 구분선 (터미널 너비에 맞춰 채움)
|
|
74
|
+
banner_line() {
|
|
75
|
+
local cols
|
|
76
|
+
cols=$(tput cols 2>/dev/null || echo 78)
|
|
77
|
+
local line=""
|
|
78
|
+
for ((i=0; i<cols; i++)); do line+="━"; done
|
|
79
|
+
echo "$line"
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
render_team_section() {
|
|
62
83
|
local team_num="$1"
|
|
84
|
+
local log_lines="${2:-10}"
|
|
63
85
|
local color
|
|
64
86
|
color=$(team_color "$team_num")
|
|
65
87
|
|
|
@@ -90,89 +112,115 @@ render_team_section() {
|
|
|
90
112
|
*) phase_str="" ;;
|
|
91
113
|
esac
|
|
92
114
|
|
|
93
|
-
#
|
|
94
|
-
|
|
115
|
+
# ── 큰 배너 ──
|
|
116
|
+
local bar
|
|
117
|
+
bar=$(banner_line)
|
|
118
|
+
printf "%b%s%b\n" "$color" "$bar" "$RESET"
|
|
119
|
+
|
|
120
|
+
printf "%b%b TEAM %s%b" "$color$BOLD" "$icon" "$team_num" "$RESET"
|
|
95
121
|
if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
|
|
96
|
-
printf "%s " "$t_feature"
|
|
122
|
+
printf " %b%s%b" "$BOLD" "$t_feature" "$RESET"
|
|
97
123
|
if [ -n "$phase_str" ]; then
|
|
98
|
-
printf "%b%s%b" "$color" "$phase_str" "$RESET"
|
|
124
|
+
printf " %b[%s]%b" "$color" "$phase_str" "$RESET"
|
|
99
125
|
fi
|
|
100
126
|
if [ -n "$t_attempt" ] && [ "$t_attempt" != "—" ] && [ "$t_attempt" != "1" ]; then
|
|
101
|
-
printf " %
|
|
127
|
+
printf " %battempt #%s%b" "$YELLOW" "$t_attempt" "$RESET"
|
|
102
128
|
fi
|
|
103
129
|
else
|
|
104
|
-
printf "%bidle%b" "$DIM" "$RESET"
|
|
130
|
+
printf " %bidle%b" "$DIM" "$RESET"
|
|
105
131
|
fi
|
|
106
132
|
echo ""
|
|
133
|
+
printf "%b%s%b\n" "$color" "$bar" "$RESET"
|
|
107
134
|
|
|
108
|
-
# 팀 로그
|
|
135
|
+
# ── 팀 로그 ──
|
|
136
|
+
local have_logs=0
|
|
109
137
|
if [ -f "$PROGRESS_LOG" ]; then
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
local matched
|
|
139
|
+
matched=$(grep -E "team-${team_num}\b|team_${team_num}\b" "$PROGRESS_LOG" 2>/dev/null | tail -"$log_lines")
|
|
140
|
+
if [ -n "$matched" ]; then
|
|
141
|
+
have_logs=1
|
|
142
|
+
local cols
|
|
143
|
+
cols=$(tput cols 2>/dev/null || echo 78)
|
|
144
|
+
# 포맷: [ts] [icon agent] detail (너무 길면 터미널 너비에서 줄바꿈)
|
|
145
|
+
local prefix_w=18 # " HH:MM ✦ eval "
|
|
146
|
+
local detail_w=$(( cols - prefix_w ))
|
|
147
|
+
[ "$detail_w" -lt 20 ] && detail_w=20
|
|
148
|
+
|
|
149
|
+
echo "$matched" | while IFS= read -r line; do
|
|
150
|
+
local ts action detail
|
|
151
|
+
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
152
|
+
action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
|
|
153
|
+
detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
|
|
154
|
+
|
|
155
|
+
local short_ts
|
|
156
|
+
short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}' | tail -1 || echo "$ts")
|
|
157
|
+
|
|
158
|
+
local a_icon="·" a_color="$DIM" a_label="$action"
|
|
159
|
+
case "$action" in
|
|
160
|
+
gen|gen-start|gen-read|gen-write|gen-test|gen-done)
|
|
161
|
+
a_icon="▶"; a_color="$GREEN"; a_label="Gen" ;;
|
|
162
|
+
eval|eval-start|eval-check|eval-done)
|
|
163
|
+
a_icon="✦"; a_color="$BLUE"; a_label="Eval" ;;
|
|
164
|
+
result|pass)
|
|
165
|
+
a_icon="✓"; a_color="$GREEN"; a_label="Result" ;;
|
|
166
|
+
fail)
|
|
167
|
+
a_icon="✗"; a_color="$RED"; a_label="Result" ;;
|
|
168
|
+
dequeue) a_icon="→"; a_color="$CYAN"; a_label="Queue" ;;
|
|
169
|
+
gate) a_icon="◆"; a_color="$YELLOW"; a_label="Gate" ;;
|
|
170
|
+
*) a_icon="·"; a_color="$DIM"; a_label="$action" ;;
|
|
171
|
+
esac
|
|
172
|
+
|
|
173
|
+
# 첫 줄
|
|
174
|
+
local first="${detail:0:$detail_w}"
|
|
175
|
+
printf " %b%s%b %b%s%b %-6s %s\n" \
|
|
176
|
+
"$DIM" "$short_ts" "$RESET" \
|
|
177
|
+
"$a_color" "$a_icon" "$RESET" \
|
|
178
|
+
"$a_label" "$first"
|
|
179
|
+
# 이어질 줄 (긴 detail 은 접지 않고 들여쓰기로 이어서 출력)
|
|
180
|
+
local rest="${detail:$detail_w}"
|
|
181
|
+
while [ -n "$rest" ]; do
|
|
182
|
+
local chunk="${rest:0:$detail_w}"
|
|
183
|
+
rest="${rest:$detail_w}"
|
|
184
|
+
printf " %*s%s\n" "$prefix_w" "" "$chunk"
|
|
185
|
+
done
|
|
186
|
+
done
|
|
187
|
+
fi
|
|
188
|
+
fi
|
|
189
|
+
if [ "$have_logs" -eq 0 ]; then
|
|
190
|
+
printf " %b(no activity)%b\n" "$DIM" "$RESET"
|
|
138
191
|
fi
|
|
139
192
|
}
|
|
140
193
|
|
|
141
194
|
render_v4() {
|
|
195
|
+
# 단일 팀 모드 (tmux per-team pane)
|
|
196
|
+
if [ -n "$TEAM_FILTER" ]; then
|
|
197
|
+
local rows log_lines
|
|
198
|
+
rows=$(tput lines 2>/dev/null || echo 20)
|
|
199
|
+
log_lines=$(( rows - 5 ))
|
|
200
|
+
[ "$log_lines" -lt 3 ] && log_lines=3
|
|
201
|
+
render_team_section "$TEAM_FILTER" "$log_lines"
|
|
202
|
+
return
|
|
203
|
+
fi
|
|
204
|
+
|
|
142
205
|
render_v4_header
|
|
143
206
|
|
|
144
|
-
# 팀 수 확인
|
|
145
207
|
local team_count=3
|
|
146
208
|
if [ -f "$QUEUE" ]; then
|
|
147
209
|
team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null || echo 3)
|
|
148
210
|
fi
|
|
211
|
+
[ "$team_count" -lt 1 ] && team_count=3
|
|
212
|
+
|
|
213
|
+
local rows per_team log_lines
|
|
214
|
+
rows=$(tput lines 2>/dev/null || echo 40)
|
|
215
|
+
per_team=$(( (rows - 2) / team_count ))
|
|
216
|
+
log_lines=$(( per_team - 5 ))
|
|
217
|
+
if [ "$log_lines" -lt 3 ]; then log_lines=3; fi
|
|
218
|
+
if [ "$log_lines" -gt 15 ]; then log_lines=15; fi
|
|
149
219
|
|
|
150
220
|
for i in $(seq 1 "$team_count"); do
|
|
151
|
-
render_team_section "$i"
|
|
221
|
+
render_team_section "$i" "$log_lines"
|
|
152
222
|
echo ""
|
|
153
223
|
done
|
|
154
|
-
|
|
155
|
-
# Lead/시스템 이벤트 (team이 아닌 엔트리)
|
|
156
|
-
echo -e "${BOLD}SYSTEM${RESET}"
|
|
157
|
-
if [ -f "$PROGRESS_LOG" ]; then
|
|
158
|
-
grep -v 'team-[0-9]' "$PROGRESS_LOG" 2>/dev/null | grep -v '^#' | grep -v '^$' | tail -5 | while IFS= read -r line; do
|
|
159
|
-
local ts agent action detail
|
|
160
|
-
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
161
|
-
agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
|
|
162
|
-
action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
|
|
163
|
-
detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
|
|
164
|
-
|
|
165
|
-
local short_ts
|
|
166
|
-
short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}' | tail -1 || echo "$ts")
|
|
167
|
-
|
|
168
|
-
if [ ${#detail} -gt 35 ]; then detail="${detail:0:33}.."; fi
|
|
169
|
-
|
|
170
|
-
printf " %b%s%b %s %b%s%b\n" \
|
|
171
|
-
"$DIM" "$short_ts" "$RESET" \
|
|
172
|
-
"$agent" \
|
|
173
|
-
"$DIM" "$detail" "$RESET"
|
|
174
|
-
done
|
|
175
|
-
fi
|
|
176
224
|
}
|
|
177
225
|
|
|
178
226
|
# ══════════════════════════════════════════
|
|
@@ -292,8 +340,8 @@ clear
|
|
|
292
340
|
CURRENT_MODE=""
|
|
293
341
|
|
|
294
342
|
while true; do
|
|
295
|
-
# 동적 모드 감지
|
|
296
|
-
if [ -f "$QUEUE" ]; then
|
|
343
|
+
# 동적 모드 감지 (--team 지정 시 항상 v4)
|
|
344
|
+
if [ -n "$TEAM_FILTER" ] || [ -f "$QUEUE" ]; then
|
|
297
345
|
NEW_MODE="v4"
|
|
298
346
|
else
|
|
299
347
|
NEW_MODE="v3"
|
|
@@ -92,52 +92,48 @@ if [ -n "${TMUX:-}" ]; then
|
|
|
92
92
|
fi
|
|
93
93
|
|
|
94
94
|
# ══════════════════════════════════════════
|
|
95
|
-
# Case B: tmux 밖에 있음 → 새 세션 생성
|
|
95
|
+
# Case B: tmux 밖에 있음 → 새 세션 생성 + Terminal.app에서 자동 attach
|
|
96
96
|
# ══════════════════════════════════════════
|
|
97
97
|
|
|
98
|
-
# 이미 세션이 있으면 attach만
|
|
98
|
+
# 이미 세션이 있으면 Terminal.app에서 attach만
|
|
99
99
|
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|
100
|
-
echo "[studio] Session '$SESSION_NAME' already exists.
|
|
101
|
-
|
|
100
|
+
echo "[studio] Session '$SESSION_NAME' already exists. Opening Terminal..."
|
|
101
|
+
osascript -e "tell application \"Terminal\" to do script \"tmux attach -t $SESSION_NAME\"" 2>/dev/null \
|
|
102
|
+
|| echo "[studio] ATTACH_TMUX=$SESSION_NAME"
|
|
103
|
+
echo "[studio] OPENED_TERMINAL=true"
|
|
102
104
|
exit 0
|
|
103
105
|
fi
|
|
104
106
|
|
|
105
107
|
echo "[studio] Creating new tmux session with 3-column layout..."
|
|
106
108
|
|
|
107
|
-
# 1. 새 세션 → Left pane (Claude
|
|
108
|
-
|
|
109
|
-
-P -F '#{pane_id}')
|
|
110
|
-
|
|
111
|
-
# 2. Left 35% | Right 65%
|
|
112
|
-
PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_MAIN" -c "$PROJECT_ROOT" \
|
|
109
|
+
# 1. 새 세션 → Left pane (Dashboard 전용 — Claude는 현재 세션에서 계속)
|
|
110
|
+
PANE_DASH=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
|
|
113
111
|
-P -F '#{pane_id}' \
|
|
114
112
|
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
|
|
115
113
|
|
|
116
|
-
#
|
|
117
|
-
PANE_RIGHT=$(tmux split-window -h -p
|
|
114
|
+
# 2. Split: Dashboard | Monitor
|
|
115
|
+
PANE_RIGHT=$(tmux split-window -h -p 50 -t "$PANE_DASH" -c "$PROJECT_ROOT" \
|
|
118
116
|
-P -F '#{pane_id}' \
|
|
119
117
|
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
|
|
120
118
|
|
|
121
|
-
#
|
|
122
|
-
PANE_HISTORY=$(tmux split-window -v -p 55 -t "$
|
|
119
|
+
# 3. Split Dashboard vertically: Dashboard top | History bottom
|
|
120
|
+
PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_DASH" -c "$PROJECT_ROOT" \
|
|
123
121
|
-P -F '#{pane_id}' \
|
|
124
122
|
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
|
|
125
123
|
|
|
126
|
-
# 5. Left pane에서 Claude 자동 실행
|
|
127
|
-
tmux send-keys -t "$PANE_MAIN" "unset npm_config_prefix 2>/dev/null" Enter
|
|
128
|
-
tmux send-keys -t "$PANE_MAIN" "clear && $CLAUDE_CMD" Enter
|
|
129
|
-
|
|
130
124
|
# Pane titles
|
|
131
|
-
tmux select-pane -t "$
|
|
132
|
-
tmux select-pane -t "$PANE_MID" -T "Dashboard"
|
|
125
|
+
tmux select-pane -t "$PANE_DASH" -T "Dashboard"
|
|
133
126
|
tmux select-pane -t "$PANE_HISTORY" -T "Command History"
|
|
134
127
|
tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
|
|
135
128
|
|
|
136
129
|
tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
|
|
137
130
|
tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
138
131
|
|
|
139
|
-
#
|
|
140
|
-
|
|
132
|
+
# Terminal.app에서 자동으로 tmux attach
|
|
133
|
+
osascript -e "tell application \"Terminal\"
|
|
134
|
+
activate
|
|
135
|
+
do script \"tmux attach -t $SESSION_NAME\"
|
|
136
|
+
end tell" 2>/dev/null
|
|
141
137
|
|
|
142
138
|
echo "[studio] Session '$SESSION_NAME' created."
|
|
143
|
-
echo "[studio]
|
|
139
|
+
echo "[studio] OPENED_TERMINAL=true"
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
# harness-tmux-v4.sh — v4 Agent Teams: 원커맨드 실행
|
|
3
3
|
#
|
|
4
4
|
# ┌──────────────┬──────────────┬──────────────┐
|
|
5
|
-
# │ │ Dashboard │
|
|
6
|
-
# │ Main Claude │ (v4 queue)
|
|
7
|
-
# │ (Lead) ├──────────────┤
|
|
8
|
-
# │ │ Prompt
|
|
9
|
-
# │ │ History │
|
|
5
|
+
# │ │ Dashboard │ TEAM 1 │
|
|
6
|
+
# │ Main Claude │ (v4 queue) ├──────────────┤
|
|
7
|
+
# │ (Lead) ├──────────────┤ TEAM 2 │
|
|
8
|
+
# │ │ Prompt ├──────────────┤
|
|
9
|
+
# │ │ History │ TEAM 3 │
|
|
10
10
|
# └──────────────┴──────────────┴──────────────┘
|
|
11
11
|
#
|
|
12
12
|
# Usage:
|
|
@@ -80,10 +80,20 @@ PANE_MAIN=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y
|
|
|
80
80
|
PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_MAIN" -c "$PROJECT_ROOT" \
|
|
81
81
|
-P -F '#{pane_id}')
|
|
82
82
|
|
|
83
|
-
# 3. Split right section: Middle 45% | Right 55%
|
|
84
|
-
|
|
83
|
+
# 3. Split right section: Middle 45% | Right 55% — Right는 TEAM 1 으로 시작
|
|
84
|
+
PANE_T1=$(tmux split-window -h -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
|
|
85
85
|
-P -F '#{pane_id}' \
|
|
86
|
-
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
|
|
86
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 1'")
|
|
87
|
+
|
|
88
|
+
# 3b. Split TEAM 1 세로로 → TEAM 2 (하단 66%)
|
|
89
|
+
PANE_T2=$(tmux split-window -v -p 66 -t "$PANE_T1" -c "$PROJECT_ROOT" \
|
|
90
|
+
-P -F '#{pane_id}' \
|
|
91
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 2'")
|
|
92
|
+
|
|
93
|
+
# 3c. Split TEAM 2 세로로 → TEAM 3 (하단 50%)
|
|
94
|
+
PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" \
|
|
95
|
+
-P -F '#{pane_id}' \
|
|
96
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 3'")
|
|
87
97
|
|
|
88
98
|
# 4. Split middle pane vertically: Dashboard (top 45%) | Prompt History (bottom 55%)
|
|
89
99
|
PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
|
|
@@ -109,7 +119,9 @@ fi
|
|
|
109
119
|
tmux select-pane -t "$PANE_MAIN" -T "Lead (Main Claude)"
|
|
110
120
|
tmux select-pane -t "$PANE_MID" -T "Dashboard"
|
|
111
121
|
tmux select-pane -t "$PANE_HISTORY" -T "Prompt History"
|
|
112
|
-
tmux select-pane -t "$
|
|
122
|
+
tmux select-pane -t "$PANE_T1" -T "TEAM 1"
|
|
123
|
+
tmux select-pane -t "$PANE_T2" -T "TEAM 2"
|
|
124
|
+
tmux select-pane -t "$PANE_T3" -T "TEAM 3"
|
|
113
125
|
|
|
114
126
|
tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
|
|
115
127
|
tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
@@ -16,19 +16,8 @@ bash scripts/harness-studio-setup.sh .
|
|
|
16
16
|
|
|
17
17
|
스크립트 출력을 확인합니다:
|
|
18
18
|
|
|
19
|
-
- **`Layout ready`** →
|
|
20
|
-
- **`
|
|
21
|
-
사용자에게 아래 안내를 출력하고 **STOP**합니다:
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
Studio 레이아웃이 준비되었습니다!
|
|
25
|
-
다른 터미널에서 아래 명령을 실행하세요:
|
|
26
|
-
|
|
27
|
-
tmux attach -t harness-studio
|
|
28
|
-
|
|
29
|
-
새 창에서 Claude가 자동 실행됩니다. 거기서 "팀 가동"을 입력하면 Teams가 시작됩니다.
|
|
30
|
-
```
|
|
31
|
-
|
|
19
|
+
- **`Layout ready`** → tmux 안에서 split 완료. 바로 Step 1로.
|
|
20
|
+
- **`OPENED_TERMINAL=true`** → 새 Terminal.app 창에 Studio가 자동으로 열림. 바로 Step 1로.
|
|
32
21
|
- **`already set up`** → 이미 구축됨. 바로 Step 1로.
|
|
33
22
|
|
|
34
23
|
## Step 1: Queue 초기화
|
|
@@ -73,25 +62,99 @@ Agent({
|
|
|
73
62
|
## 할당된 Feature
|
|
74
63
|
- Feature ID: {FEATURE_ID}
|
|
75
64
|
- 프로젝트 루트: 현재 디렉토리 (worktree 복사본)
|
|
65
|
+
- 하네스 루트: 메인 프로젝트 루트 (worktree가 아닌 원본)
|
|
66
|
+
|
|
67
|
+
## 실시간 로깅 (필수)
|
|
68
|
+
|
|
69
|
+
Monitor 패널에서 **각 에이전트(Gen / Eval / Result)가 지금 무엇을 하고 있는지**가 보여야 합니다.
|
|
70
|
+
Phase 전환뿐 아니라 **내부 하위 단계**(파일 읽기, 파일 쓰기, 테스트 실행, AC 검증 등)까지
|
|
71
|
+
progress.log에 한 줄씩 남기세요. 대시보드는 3초마다 tail합니다.
|
|
72
|
+
|
|
73
|
+
**로깅 원칙**
|
|
74
|
+
- 의미 있는 동작마다 **한 줄씩** 즉시 기록 (파일 단위가 아니라 행위 단위)
|
|
75
|
+
- ACTION 토큰은 아래 표에서 선택 (Monitor가 아이콘/색을 매핑)
|
|
76
|
+
- DETAIL은 구체적으로 — 파일명, AC 번호, 에러 메시지 요약, 결정 사유
|
|
77
|
+
|
|
78
|
+
| ACTION | 사용 시점 | DETAIL 예시 |
|
|
79
|
+
|--------|-----------|-------------|
|
|
80
|
+
| `gen-start` | Gen Phase 시작 | `F-001 start — 6 AC` |
|
|
81
|
+
| `gen-read` | 소스/계약 읽기 | `read api-contract.json (POST /users)` |
|
|
82
|
+
| `gen-write` | 파일 생성/수정 | `write apps/service-user/src/user.controller.ts` |
|
|
83
|
+
| `gen-test` | 자체 게이트(tsc/eslint/jest) | `tsc OK · eslint 0 · jest 12/12` |
|
|
84
|
+
| `gen-done` | Gen Phase 종료 | `F-001 done — 5 files, 142 LOC` |
|
|
85
|
+
| `eval-start` | Evaluator Agent 호출 시작 | `F-001 spawning evaluator` |
|
|
86
|
+
| `eval-check` | AC 개별 검증 진행 | `AC-3 — verify POST /users returns 201` |
|
|
87
|
+
| `eval-done` | Eval 결과 수신 | `verdict=PASS score=2.95` |
|
|
88
|
+
| `result` / `pass` | PASS 확정 | `F-001 PASS — queue.pass` |
|
|
89
|
+
| `fail` | FAIL 확정(재시도/최종) | `FAIL #1 — AC-2 missing` |
|
|
90
|
+
|
|
91
|
+
**progress.log 기록** (하네스 루트의 progress.log에 append):
|
|
92
|
+
```bash
|
|
93
|
+
echo "$(date +'%Y-%m-%d %H:%M') | team-{N} | {ACTION} | {DETAIL}" >> {HARNESS_ROOT}/.harness/progress.log
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**queue phase 업데이트** (feature-queue.json의 팀 상태 갱신):
|
|
97
|
+
```bash
|
|
98
|
+
bash {HARNESS_ROOT}/scripts/harness-queue-manager.sh update_phase {FEATURE_ID} {PHASE} .
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
> {HARNESS_ROOT}는 worktree의 원본 프로젝트 경로입니다. `git worktree list` 첫 줄에서 확인 가능합니다.
|
|
102
|
+
> 워커 시작 시 먼저 실행: `HARNESS_ROOT=$(git worktree list | head -1 | awk '{print $1}')`
|
|
76
103
|
|
|
77
104
|
## Phase 1: Generator (코드 생성)
|
|
78
105
|
|
|
79
|
-
|
|
106
|
+
**시작 시 로깅:**
|
|
107
|
+
```bash
|
|
108
|
+
HARNESS_ROOT=$(git worktree list | head -1 | awk '{print $1}')
|
|
109
|
+
LOG="$HARNESS_ROOT/.harness/progress.log"
|
|
110
|
+
logev() { echo "$(date +'%Y-%m-%d %H:%M') | team-{N} | $1 | $2" >> "$LOG"; }
|
|
111
|
+
|
|
112
|
+
logev gen-start "{FEATURE_ID} start"
|
|
113
|
+
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" update_phase {FEATURE_ID} gen "$HARNESS_ROOT"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
1. Feature 정보 확인 — **각 읽기마다 로그**:
|
|
117
|
+
```bash
|
|
118
|
+
logev gen-read "feature-list.json → {FEATURE_ID}"
|
|
119
|
+
logev gen-read "api-contract.json → {관련 엔드포인트}"
|
|
120
|
+
```
|
|
80
121
|
- `jq '.features[] | select(.id == "{FEATURE_ID}")' .harness/actions/feature-list.json`
|
|
81
122
|
- `.harness/actions/api-contract.json`에서 관련 엔드포인트 확인
|
|
82
123
|
- AC(Acceptance Criteria) 목록을 정확히 파악
|
|
83
124
|
|
|
84
|
-
2. 코드
|
|
125
|
+
2. 코드 생성 — **파일 쓰기마다 로그**:
|
|
126
|
+
```bash
|
|
127
|
+
logev gen-write "apps/service-user/src/user.controller.ts"
|
|
128
|
+
logev gen-write "libs/shared-dto/src/user.dto.ts"
|
|
129
|
+
```
|
|
85
130
|
- AGENTS.md의 IA-MAP에 따라 올바른 디렉토리에 코드 작성
|
|
86
131
|
- AC의 모든 항목을 충족하도록 구현
|
|
87
132
|
|
|
88
|
-
3. Pre-eval 게이트 (자체)
|
|
133
|
+
3. Pre-eval 게이트 (자체) — **결과 로그**:
|
|
134
|
+
```bash
|
|
135
|
+
logev gen-test "tsc OK · eslint 0w 0e · jest 12/12"
|
|
136
|
+
```
|
|
89
137
|
- tsc (타입 체크) 실행
|
|
90
138
|
- eslint (린트) 실행
|
|
91
139
|
- 컴파일 에러가 있으면 직접 수정 (Eval에 넘기지 않음)
|
|
92
140
|
|
|
141
|
+
**Gen 완료 로깅:**
|
|
142
|
+
```bash
|
|
143
|
+
logev gen-done "{FEATURE_ID} done — {변경파일수} files, {LOC} LOC"
|
|
144
|
+
```
|
|
145
|
+
|
|
93
146
|
## Phase 2: Evaluator (독립 평가 — Agent 도구 사용)
|
|
94
147
|
|
|
148
|
+
**Eval 시작 로깅 (하위 단계 포함):**
|
|
149
|
+
```bash
|
|
150
|
+
logev eval-start "{FEATURE_ID} spawning evaluator"
|
|
151
|
+
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" update_phase {FEATURE_ID} eval "$HARNESS_ROOT"
|
|
152
|
+
# Evaluator가 AC를 하나씩 검증할 때마다:
|
|
153
|
+
# logev eval-check "AC-3 — POST /users returns 201"
|
|
154
|
+
# 최종:
|
|
155
|
+
# logev eval-done "verdict=PASS score=2.95"
|
|
156
|
+
```
|
|
157
|
+
|
|
95
158
|
코드 생성이 완료되면 **별도 Agent를 생성하여 평가**합니다.
|
|
96
159
|
이 Evaluator Agent는 당신(Generator)의 추론 과정을 모릅니다.
|
|
97
160
|
오직 코드와 AC만 보고 판단합니다.
|
|
@@ -137,15 +200,24 @@ FEEDBACK: (FAIL인 경우만) 구체적 수정 지시
|
|
|
137
200
|
Evaluator Agent 결과를 확인합니다:
|
|
138
201
|
|
|
139
202
|
### PASS인 경우 (VERDICT: PASS, SCORE ≥ 2.80):
|
|
140
|
-
|
|
141
|
-
|
|
203
|
+
```bash
|
|
204
|
+
logev result "{FEATURE_ID} PASS score={SCORE} — merging"
|
|
205
|
+
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" pass {FEATURE_ID} "$HARNESS_ROOT"
|
|
206
|
+
```
|
|
207
|
+
변경 파일 목록과 AC 충족 요약을 Lead에게 반환.
|
|
142
208
|
|
|
143
209
|
### FAIL인 경우:
|
|
210
|
+
```bash
|
|
211
|
+
logev fail "{FEATURE_ID} FAIL #{ATTEMPT} — {사유요약}"
|
|
212
|
+
```
|
|
144
213
|
1. Evaluator의 FEEDBACK을 읽고 코드를 수정 (Phase 1로 돌아감)
|
|
145
214
|
2. 수정 후 다시 Phase 2 (새 Evaluator Agent 생성 — 이전 Eval 컨텍스트 없음)
|
|
146
215
|
3. 최대 3회 시도. 3회 모두 FAIL이면:
|
|
147
|
-
|
|
148
|
-
|
|
216
|
+
```bash
|
|
217
|
+
logev fail "{FEATURE_ID} FINAL FAIL after 3 attempts"
|
|
218
|
+
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" fail {FEATURE_ID} "$HARNESS_ROOT"
|
|
219
|
+
```
|
|
220
|
+
실패 사유와 마지막 Eval 결과를 Lead에게 반환.
|
|
149
221
|
```
|
|
150
222
|
|
|
151
223
|
## Step 4: 결과 수집 및 다음 라운드
|
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-team-stop
|
|
3
|
-
description: "v4 Agent Teams
|
|
3
|
+
description: "v4 Agent Teams 중지 + Studio 정리. 실행 중인 Teammate 해산, tmux 세션 종료. 트리거: '/harness-team-stop', 'team 중지', 'agent team 멈춰', '팀 멈춰', '팀 중지'"
|
|
4
4
|
disable-model-invocation: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /harness-team-stop — Agent Teams 중지
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Step 1: Queue 상태 저장
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
현재 in_progress feature를 ready로 복구합니다 (재시작 시 이어서 가능):
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
```bash
|
|
14
|
+
bash scripts/harness-queue-manager.sh recover .
|
|
15
|
+
bash scripts/harness-queue-manager.sh status .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Step 2: tmux Studio 세션 정리
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
tmux kill-session -t harness-studio 2>/dev/null && echo "Studio session killed." || echo "No studio session."
|
|
22
|
+
```
|
|
14
23
|
|
|
15
|
-
##
|
|
24
|
+
## Step 3: 완료 안내
|
|
16
25
|
|
|
17
26
|
```
|
|
18
|
-
|
|
27
|
+
Teams 중지 완료.
|
|
28
|
+
- Queue 상태 보존됨 (in_progress → ready로 복구)
|
|
29
|
+
- 재시작: /harness-team-action
|
|
19
30
|
```
|