@walwal-harness/cli 4.0.0-beta.11 → 4.0.0-beta.13
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 +1 -1
- package/scripts/harness-monitor.sh +110 -62
- package/scripts/harness-studio-setup.sh +19 -23
- package/scripts/harness-tmux-v4.sh +21 -9
- package/skills/team-action/SKILL.md +51 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.13",
|
|
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"
|
|
@@ -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 초기화
|
|
@@ -77,7 +66,27 @@ Agent({
|
|
|
77
66
|
|
|
78
67
|
## 실시간 로깅 (필수)
|
|
79
68
|
|
|
80
|
-
|
|
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` |
|
|
81
90
|
|
|
82
91
|
**progress.log 기록** (하네스 루트의 progress.log에 append):
|
|
83
92
|
```bash
|
|
@@ -97,35 +106,53 @@ bash {HARNESS_ROOT}/scripts/harness-queue-manager.sh update_phase {FEATURE_ID} {
|
|
|
97
106
|
**시작 시 로깅:**
|
|
98
107
|
```bash
|
|
99
108
|
HARNESS_ROOT=$(git worktree list | head -1 | awk '{print $1}')
|
|
100
|
-
|
|
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"
|
|
101
113
|
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" update_phase {FEATURE_ID} gen "$HARNESS_ROOT"
|
|
102
114
|
```
|
|
103
115
|
|
|
104
|
-
1. Feature 정보
|
|
116
|
+
1. Feature 정보 확인 — **각 읽기마다 로그**:
|
|
117
|
+
```bash
|
|
118
|
+
logev gen-read "feature-list.json → {FEATURE_ID}"
|
|
119
|
+
logev gen-read "api-contract.json → {관련 엔드포인트}"
|
|
120
|
+
```
|
|
105
121
|
- `jq '.features[] | select(.id == "{FEATURE_ID}")' .harness/actions/feature-list.json`
|
|
106
122
|
- `.harness/actions/api-contract.json`에서 관련 엔드포인트 확인
|
|
107
123
|
- AC(Acceptance Criteria) 목록을 정확히 파악
|
|
108
124
|
|
|
109
|
-
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
|
+
```
|
|
110
130
|
- AGENTS.md의 IA-MAP에 따라 올바른 디렉토리에 코드 작성
|
|
111
131
|
- AC의 모든 항목을 충족하도록 구현
|
|
112
132
|
|
|
113
|
-
3. Pre-eval 게이트 (자체)
|
|
133
|
+
3. Pre-eval 게이트 (자체) — **결과 로그**:
|
|
134
|
+
```bash
|
|
135
|
+
logev gen-test "tsc OK · eslint 0w 0e · jest 12/12"
|
|
136
|
+
```
|
|
114
137
|
- tsc (타입 체크) 실행
|
|
115
138
|
- eslint (린트) 실행
|
|
116
139
|
- 컴파일 에러가 있으면 직접 수정 (Eval에 넘기지 않음)
|
|
117
140
|
|
|
118
141
|
**Gen 완료 로깅:**
|
|
119
142
|
```bash
|
|
120
|
-
|
|
143
|
+
logev gen-done "{FEATURE_ID} done — {변경파일수} files, {LOC} LOC"
|
|
121
144
|
```
|
|
122
145
|
|
|
123
146
|
## Phase 2: Evaluator (독립 평가 — Agent 도구 사용)
|
|
124
147
|
|
|
125
|
-
**Eval 시작
|
|
148
|
+
**Eval 시작 로깅 (하위 단계 포함):**
|
|
126
149
|
```bash
|
|
127
|
-
|
|
150
|
+
logev eval-start "{FEATURE_ID} spawning evaluator"
|
|
128
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"
|
|
129
156
|
```
|
|
130
157
|
|
|
131
158
|
코드 생성이 완료되면 **별도 Agent를 생성하여 평가**합니다.
|
|
@@ -174,20 +201,20 @@ Evaluator Agent 결과를 확인합니다:
|
|
|
174
201
|
|
|
175
202
|
### PASS인 경우 (VERDICT: PASS, SCORE ≥ 2.80):
|
|
176
203
|
```bash
|
|
177
|
-
|
|
204
|
+
logev result "{FEATURE_ID} PASS score={SCORE} — merging"
|
|
178
205
|
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" pass {FEATURE_ID} "$HARNESS_ROOT"
|
|
179
206
|
```
|
|
180
207
|
변경 파일 목록과 AC 충족 요약을 Lead에게 반환.
|
|
181
208
|
|
|
182
209
|
### FAIL인 경우:
|
|
183
210
|
```bash
|
|
184
|
-
|
|
211
|
+
logev fail "{FEATURE_ID} FAIL #{ATTEMPT} — {사유요약}"
|
|
185
212
|
```
|
|
186
213
|
1. Evaluator의 FEEDBACK을 읽고 코드를 수정 (Phase 1로 돌아감)
|
|
187
214
|
2. 수정 후 다시 Phase 2 (새 Evaluator Agent 생성 — 이전 Eval 컨텍스트 없음)
|
|
188
215
|
3. 최대 3회 시도. 3회 모두 FAIL이면:
|
|
189
216
|
```bash
|
|
190
|
-
|
|
217
|
+
logev fail "{FEATURE_ID} FINAL FAIL after 3 attempts"
|
|
191
218
|
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" fail {FEATURE_ID} "$HARNESS_ROOT"
|
|
192
219
|
```
|
|
193
220
|
실패 사유와 마지막 Eval 결과를 Lead에게 반환.
|