@walwal-harness/cli 4.0.0-beta.6 → 4.0.0-beta.7
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 +203 -68
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.7",
|
|
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"
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-monitor.sh —
|
|
3
|
-
#
|
|
2
|
+
# harness-monitor.sh — Agent Lifecycle Monitor
|
|
3
|
+
#
|
|
4
|
+
# v3 모드: 단일 이벤트 스트림 (progress.json 변경 감지 + audit.log)
|
|
5
|
+
# v4 모드: 팀별 섹션 분리 (feature-queue.json + progress.log 기반)
|
|
6
|
+
#
|
|
4
7
|
# Usage: bash scripts/harness-monitor.sh [project-root]
|
|
5
8
|
|
|
6
9
|
set -uo pipefail
|
|
@@ -17,8 +20,9 @@ if [ -z "$PROJECT_ROOT" ]; then
|
|
|
17
20
|
fi
|
|
18
21
|
|
|
19
22
|
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
23
|
+
PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
|
|
20
24
|
AUDIT_LOG="$PROJECT_ROOT/.harness/actions/audit.log"
|
|
21
|
-
|
|
25
|
+
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
22
26
|
|
|
23
27
|
# ── ANSI helpers ──
|
|
24
28
|
BOLD="\033[1m"
|
|
@@ -28,28 +32,169 @@ YELLOW="\033[33m"
|
|
|
28
32
|
RED="\033[31m"
|
|
29
33
|
CYAN="\033[36m"
|
|
30
34
|
MAGENTA="\033[35m"
|
|
35
|
+
BLUE="\033[34m"
|
|
31
36
|
RESET="\033[0m"
|
|
32
37
|
|
|
38
|
+
# ── Team colors ──
|
|
39
|
+
T1_COLOR="$CYAN"
|
|
40
|
+
T2_COLOR="$MAGENTA"
|
|
41
|
+
T3_COLOR="$YELLOW"
|
|
42
|
+
|
|
43
|
+
team_color() {
|
|
44
|
+
case "$1" in
|
|
45
|
+
1|team-1) echo "$T1_COLOR" ;;
|
|
46
|
+
2|team-2) echo "$T2_COLOR" ;;
|
|
47
|
+
3|team-3) echo "$T3_COLOR" ;;
|
|
48
|
+
*) echo "$DIM" ;;
|
|
49
|
+
esac
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ══════════════════════════════════════════
|
|
53
|
+
# v4 모드: 팀별 섹션 렌더링
|
|
54
|
+
# ══════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
render_v4_header() {
|
|
57
|
+
echo -e "${BOLD}TEAM MONITOR${RESET} ${DIM}$(date +%H:%M:%S)${RESET}"
|
|
58
|
+
echo ""
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
render_team_section() {
|
|
62
|
+
local team_num="$1"
|
|
63
|
+
local color
|
|
64
|
+
color=$(team_color "$team_num")
|
|
65
|
+
|
|
66
|
+
# 팀 상태 from queue
|
|
67
|
+
local t_status="idle" t_feature="—" t_phase="—" t_attempt=""
|
|
68
|
+
if [ -f "$QUEUE" ]; then
|
|
69
|
+
t_status=$(jq -r ".teams[\"$team_num\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
|
|
70
|
+
t_feature=$(jq -r ".teams[\"$team_num\"].feature // \"—\"" "$QUEUE" 2>/dev/null)
|
|
71
|
+
if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
|
|
72
|
+
t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
|
|
73
|
+
t_attempt=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].attempt // 1' "$QUEUE" 2>/dev/null)
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# 상태 아이콘
|
|
78
|
+
local icon="○"
|
|
79
|
+
case "$t_status" in
|
|
80
|
+
busy) icon="▶" ;;
|
|
81
|
+
idle) icon="○" ;;
|
|
82
|
+
esac
|
|
83
|
+
|
|
84
|
+
# Phase 표시
|
|
85
|
+
local phase_str=""
|
|
86
|
+
case "$t_phase" in
|
|
87
|
+
gen) phase_str="Gen" ;;
|
|
88
|
+
gate) phase_str="Gate" ;;
|
|
89
|
+
eval) phase_str="Eval" ;;
|
|
90
|
+
*) phase_str="" ;;
|
|
91
|
+
esac
|
|
92
|
+
|
|
93
|
+
# 헤더 라인
|
|
94
|
+
printf "%b%b T%s%b " "$color" "$icon" "$team_num" "$RESET"
|
|
95
|
+
if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
|
|
96
|
+
printf "%s " "$t_feature"
|
|
97
|
+
if [ -n "$phase_str" ]; then
|
|
98
|
+
printf "%b%s%b" "$color" "$phase_str" "$RESET"
|
|
99
|
+
fi
|
|
100
|
+
if [ -n "$t_attempt" ] && [ "$t_attempt" != "—" ] && [ "$t_attempt" != "1" ]; then
|
|
101
|
+
printf " %b#%s%b" "$DIM" "$t_attempt" "$RESET"
|
|
102
|
+
fi
|
|
103
|
+
else
|
|
104
|
+
printf "%bidle%b" "$DIM" "$RESET"
|
|
105
|
+
fi
|
|
106
|
+
echo ""
|
|
107
|
+
|
|
108
|
+
# 팀 로그 (progress.log에서 team-N 엔트리만 추출)
|
|
109
|
+
if [ -f "$PROGRESS_LOG" ]; then
|
|
110
|
+
grep -i "team-${team_num}\|team_${team_num}" "$PROGRESS_LOG" 2>/dev/null | tail -5 | while IFS= read -r line; do
|
|
111
|
+
local ts action detail
|
|
112
|
+
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
113
|
+
action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
|
|
114
|
+
detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
|
|
115
|
+
|
|
116
|
+
local short_ts
|
|
117
|
+
short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}' | tail -1 || echo "$ts")
|
|
118
|
+
|
|
119
|
+
if [ ${#detail} -gt 35 ]; then detail="${detail:0:33}.."; fi
|
|
120
|
+
|
|
121
|
+
# 액션별 아이콘
|
|
122
|
+
local a_icon="·" a_color="$DIM"
|
|
123
|
+
case "$action" in
|
|
124
|
+
gen) a_icon="▶"; a_color="$GREEN" ;;
|
|
125
|
+
eval) a_icon="✦"; a_color="$BLUE" ;;
|
|
126
|
+
pass) a_icon="✓"; a_color="$GREEN" ;;
|
|
127
|
+
fail) a_icon="✗"; a_color="$RED" ;;
|
|
128
|
+
dequeue) a_icon="→"; a_color="$CYAN" ;;
|
|
129
|
+
gate) a_icon="◆"; a_color="$YELLOW" ;;
|
|
130
|
+
*) a_icon="·"; a_color="$DIM" ;;
|
|
131
|
+
esac
|
|
132
|
+
|
|
133
|
+
printf " %b%s%b %b%s%b %b%s%b\n" \
|
|
134
|
+
"$DIM" "$short_ts" "$RESET" \
|
|
135
|
+
"$a_color" "$a_icon" "$RESET" \
|
|
136
|
+
"$DIM" "$detail" "$RESET"
|
|
137
|
+
done
|
|
138
|
+
fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
render_v4() {
|
|
142
|
+
render_v4_header
|
|
143
|
+
|
|
144
|
+
# 팀 수 확인
|
|
145
|
+
local team_count=3
|
|
146
|
+
if [ -f "$QUEUE" ]; then
|
|
147
|
+
team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null || echo 3)
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
for i in $(seq 1 "$team_count"); do
|
|
151
|
+
render_team_section "$i"
|
|
152
|
+
echo ""
|
|
153
|
+
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
|
+
}
|
|
177
|
+
|
|
178
|
+
# ══════════════════════════════════════════
|
|
179
|
+
# v3 모드: 단일 스트림 (기존 동작)
|
|
180
|
+
# ══════════════════════════════════════════
|
|
181
|
+
|
|
33
182
|
LAST_AGENT=""
|
|
34
183
|
LAST_STATUS=""
|
|
35
184
|
LAST_SPRINT=""
|
|
36
|
-
EVENT_COUNT=0
|
|
37
185
|
|
|
38
186
|
print_event() {
|
|
39
187
|
local ts="$1" icon="$2" color="$3" msg="$4"
|
|
40
|
-
EVENT_COUNT=$((EVENT_COUNT + 1))
|
|
41
188
|
echo -e " ${DIM}${ts}${RESET} ${color}${icon}${RESET} ${msg}"
|
|
42
189
|
}
|
|
43
190
|
|
|
44
|
-
|
|
45
|
-
print_header() {
|
|
191
|
+
print_v3_header() {
|
|
46
192
|
echo -e "${BOLD}╔══════════════════════════════════════╗${RESET}"
|
|
47
193
|
echo -e "${BOLD}║ AGENT LIFECYCLE MONITOR ║${RESET}"
|
|
48
194
|
echo -e "${BOLD}╚══════════════════════════════════════╝${RESET}"
|
|
49
195
|
echo ""
|
|
50
196
|
}
|
|
51
197
|
|
|
52
|
-
# ── Detect agent transitions from progress.json ──
|
|
53
198
|
check_transitions() {
|
|
54
199
|
if [ ! -f "$PROGRESS" ]; then return; fi
|
|
55
200
|
|
|
@@ -61,39 +206,22 @@ check_transitions() {
|
|
|
61
206
|
local now
|
|
62
207
|
now=$(date +"%H:%M:%S")
|
|
63
208
|
|
|
64
|
-
# Sprint change
|
|
65
209
|
if [ "$sprint" != "$LAST_SPRINT" ] && [ -n "$LAST_SPRINT" ]; then
|
|
66
210
|
print_event "$now" ">>>" "$MAGENTA" "${BOLD}Sprint ${sprint} started${RESET}"
|
|
67
211
|
echo ""
|
|
68
212
|
fi
|
|
69
213
|
|
|
70
|
-
# Agent change
|
|
71
214
|
if [ "$agent" != "$LAST_AGENT" ] && [ -n "$LAST_AGENT" ]; then
|
|
72
215
|
if [ "$LAST_AGENT" != "none" ] && [ "$LAST_AGENT" != "null" ]; then
|
|
73
216
|
print_event "$now" " ✓ " "$GREEN" "${LAST_AGENT} → ${BOLD}done${RESET}"
|
|
74
217
|
fi
|
|
75
218
|
if [ "$agent" != "none" ] && [ "$agent" != "null" ]; then
|
|
76
219
|
print_event "$now" " ▶ " "$CYAN" "${BOLD}${agent}${RESET} started"
|
|
77
|
-
|
|
78
|
-
# Show handoff context if available
|
|
79
|
-
if [ -f "$HANDOFF" ]; then
|
|
80
|
-
local from focus
|
|
81
|
-
from=$(jq -r '.from // empty' "$HANDOFF" 2>/dev/null)
|
|
82
|
-
focus=$(jq -r '.focus_features // [] | join(", ")' "$HANDOFF" 2>/dev/null)
|
|
83
|
-
if [ -n "$from" ] && [ "$from" != "null" ]; then
|
|
84
|
-
echo -e " ${DIM}handoff from: ${from}${RESET}"
|
|
85
|
-
fi
|
|
86
|
-
if [ -n "$focus" ] && [ "$focus" != "null" ]; then
|
|
87
|
-
echo -e " ${DIM}focus: ${focus}${RESET}"
|
|
88
|
-
fi
|
|
89
|
-
fi
|
|
90
220
|
fi
|
|
91
221
|
fi
|
|
92
222
|
|
|
93
|
-
# Status change (same agent)
|
|
94
223
|
if [ "$agent" = "$LAST_AGENT" ] && [ "$status" != "$LAST_STATUS" ] && [ -n "$LAST_STATUS" ]; then
|
|
95
|
-
local status_color="$RESET"
|
|
96
|
-
local status_icon="•"
|
|
224
|
+
local status_color="$RESET" status_icon="•"
|
|
97
225
|
case "$status" in
|
|
98
226
|
running) status_color="$GREEN"; status_icon=" ▶ " ;;
|
|
99
227
|
completed) status_color="$CYAN"; status_icon=" ✓ " ;;
|
|
@@ -103,7 +231,6 @@ check_transitions() {
|
|
|
103
231
|
esac
|
|
104
232
|
print_event "$now" "$status_icon" "$status_color" "${agent} → ${BOLD}${status}${RESET}"
|
|
105
233
|
|
|
106
|
-
# Show failure detail
|
|
107
234
|
if [ "$status" = "failed" ]; then
|
|
108
235
|
local fail_msg
|
|
109
236
|
fail_msg=$(jq -r '.failure.message // empty' "$PROGRESS" 2>/dev/null)
|
|
@@ -118,76 +245,84 @@ check_transitions() {
|
|
|
118
245
|
LAST_SPRINT="$sprint"
|
|
119
246
|
}
|
|
120
247
|
|
|
121
|
-
# ── Stream audit.log new lines ──
|
|
122
248
|
stream_audit() {
|
|
123
249
|
if [ ! -f "$AUDIT_LOG" ]; then return; fi
|
|
124
|
-
|
|
125
|
-
# Use tail -f in background, parse each new line
|
|
126
250
|
tail -n 0 -f "$AUDIT_LOG" 2>/dev/null | while IFS= read -r line; do
|
|
127
|
-
# Skip comments/headers
|
|
128
251
|
if [[ "$line" == "#"* ]] || [[ -z "$line" ]]; then continue; fi
|
|
129
|
-
|
|
130
|
-
# Parse: TIMESTAMP | AGENT | ACTION | STATUS | TARGET | DETAIL
|
|
131
|
-
local ts agent action status target detail
|
|
252
|
+
local ts agent action status target
|
|
132
253
|
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
133
254
|
agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
|
|
134
255
|
action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
|
|
135
256
|
status=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
|
|
136
257
|
target=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$5); print $5}')
|
|
137
|
-
detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$6); print $6}')
|
|
138
258
|
|
|
139
|
-
# Color by status
|
|
140
259
|
local color="$RESET" icon="•"
|
|
141
260
|
case "$status" in
|
|
142
|
-
start) color="$CYAN";
|
|
143
|
-
complete) color="$GREEN";
|
|
144
|
-
fail) color="$RED";
|
|
145
|
-
pass) color="$GREEN";
|
|
146
|
-
skip) color="$DIM";
|
|
261
|
+
start) color="$CYAN"; icon="▶" ;;
|
|
262
|
+
complete) color="$GREEN"; icon="✓" ;;
|
|
263
|
+
fail) color="$RED"; icon="✗" ;;
|
|
264
|
+
pass) color="$GREEN"; icon="✓" ;;
|
|
265
|
+
skip) color="$DIM"; icon="–" ;;
|
|
147
266
|
esac
|
|
148
267
|
|
|
149
|
-
# Short timestamp (HH:MM:SS from ISO)
|
|
150
268
|
local short_ts
|
|
151
269
|
short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "$ts")
|
|
152
|
-
|
|
153
|
-
echo -e " ${DIM}${short_ts}${RESET} ${color}${icon}${RESET} ${BOLD}${agent}${RESET} ${action} ${target} ${DIM}${detail}${RESET}"
|
|
270
|
+
echo -e " ${DIM}${short_ts}${RESET} ${color}${icon}${RESET} ${BOLD}${agent}${RESET} ${action} ${target}"
|
|
154
271
|
done &
|
|
155
272
|
AUDIT_TAIL_PID=$!
|
|
156
273
|
}
|
|
157
274
|
|
|
158
|
-
#
|
|
275
|
+
# ══════════════════════════════════════════
|
|
276
|
+
# Mode detection & main loop
|
|
277
|
+
# ══════════════════════════════════════════
|
|
278
|
+
|
|
159
279
|
cleanup() {
|
|
160
280
|
if [ -n "${AUDIT_TAIL_PID:-}" ]; then
|
|
161
281
|
kill "$AUDIT_TAIL_PID" 2>/dev/null || true
|
|
162
282
|
fi
|
|
283
|
+
tput cnorm 2>/dev/null
|
|
163
284
|
exit 0
|
|
164
285
|
}
|
|
165
286
|
trap cleanup EXIT INT TERM
|
|
166
287
|
|
|
167
|
-
#
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if [ -f "$PROGRESS" ]; then
|
|
172
|
-
LAST_AGENT=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
173
|
-
LAST_STATUS=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
|
|
174
|
-
LAST_SPRINT=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
|
|
288
|
+
# v4 감지: feature-queue.json 존재 여부
|
|
289
|
+
IS_V4=false
|
|
290
|
+
if [ -f "$QUEUE" ]; then
|
|
291
|
+
IS_V4=true
|
|
175
292
|
fi
|
|
176
293
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
294
|
+
if [ "$IS_V4" = true ]; then
|
|
295
|
+
# ── v4: 팀별 섹션, auto-refresh ──
|
|
296
|
+
tput civis 2>/dev/null
|
|
297
|
+
clear
|
|
298
|
+
|
|
299
|
+
while true; do
|
|
300
|
+
buf=$(render_v4 2>&1)
|
|
301
|
+
tput cup 0 0 2>/dev/null
|
|
302
|
+
echo "$buf"
|
|
303
|
+
tput ed 2>/dev/null
|
|
304
|
+
sleep 3
|
|
305
|
+
done
|
|
306
|
+
else
|
|
307
|
+
# ── v3: 단일 스트림, audit tail ──
|
|
308
|
+
print_v3_header
|
|
185
309
|
|
|
186
|
-
|
|
187
|
-
|
|
310
|
+
if [ -f "$PROGRESS" ]; then
|
|
311
|
+
LAST_AGENT=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
312
|
+
LAST_STATUS=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
|
|
313
|
+
LAST_SPRINT=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
|
|
188
314
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
315
|
+
echo -e " ${BOLD}History${RESET}"
|
|
316
|
+
jq -r '.history // [] | .[] | " \(.timestamp) \(.agent) — \(.action): \(.detail)"' "$PROGRESS" 2>/dev/null
|
|
317
|
+
echo ""
|
|
318
|
+
echo -e " ${DIM}── Live events below ──${RESET}"
|
|
319
|
+
echo ""
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
stream_audit
|
|
323
|
+
|
|
324
|
+
while true; do
|
|
325
|
+
check_transitions
|
|
326
|
+
sleep 2
|
|
327
|
+
done
|
|
328
|
+
fi
|