@walwal-harness/cli 4.0.0-alpha.9 → 4.0.0-beta.10
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/README.md +235 -273
- package/assets/templates/config.json +1 -48
- package/assets/templates/gitignore-append.txt +1 -0
- package/bin/init.js +139 -67
- package/package.json +4 -2
- package/scripts/harness-dashboard-v4.sh +66 -82
- package/scripts/harness-monitor.sh +202 -67
- package/scripts/harness-next.sh +4 -15
- package/scripts/harness-prompt-history.sh +126 -0
- package/scripts/harness-studio-setup.sh +143 -0
- package/scripts/harness-studio.sh +66 -0
- package/scripts/harness-tmux-v4.sh +136 -0
- package/scripts/harness-user-prompt-submit.sh +13 -0
- package/skills/dispatcher/SKILL.md +7 -2
- package/skills/team-action/SKILL.md +217 -0
- package/skills/team-stop/SKILL.md +19 -0
- package/scripts/harness-control-v4.sh +0 -97
- package/scripts/harness-studio-v4.sh +0 -122
- package/scripts/harness-team-worker.sh +0 -415
- package/skills/evaluator-functional-flutter/SKILL.md +0 -206
- package/skills/evaluator-functional-flutter/references/ia-compliance.md +0 -77
- package/skills/evaluator-functional-flutter/references/scoring-rubric.md +0 -132
- package/skills/evaluator-functional-flutter/references/static-check-rules.md +0 -99
- package/skills/generator-frontend-flutter/SKILL.md +0 -173
- package/skills/generator-frontend-flutter/references/anti-patterns.md +0 -320
- package/skills/generator-frontend-flutter/references/api-layer-pattern.md +0 -233
- package/skills/generator-frontend-flutter/references/flutter-web-pattern.md +0 -273
- package/skills/generator-frontend-flutter/references/i18n-pattern.md +0 -102
- package/skills/generator-frontend-flutter/references/riverpod-pattern.md +0 -199
|
@@ -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
|
-
|
|
288
|
+
# 매 루프마다 v4/v3 동적 감지 — 시작 후에도 모드 전환 가능
|
|
289
|
+
tput civis 2>/dev/null
|
|
290
|
+
clear
|
|
169
291
|
|
|
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)
|
|
175
|
-
fi
|
|
292
|
+
CURRENT_MODE=""
|
|
176
293
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
fi
|
|
294
|
+
while true; do
|
|
295
|
+
# 동적 모드 감지
|
|
296
|
+
if [ -f "$QUEUE" ]; then
|
|
297
|
+
NEW_MODE="v4"
|
|
298
|
+
else
|
|
299
|
+
NEW_MODE="v3"
|
|
300
|
+
fi
|
|
185
301
|
|
|
186
|
-
#
|
|
187
|
-
|
|
302
|
+
# 모드 전환 시 화면 초기화
|
|
303
|
+
if [ "$NEW_MODE" != "$CURRENT_MODE" ]; then
|
|
304
|
+
clear
|
|
305
|
+
CURRENT_MODE="$NEW_MODE"
|
|
306
|
+
if [ "$CURRENT_MODE" = "v3" ]; then
|
|
307
|
+
# v3 초기화
|
|
308
|
+
if [ -f "$PROGRESS" ]; then
|
|
309
|
+
LAST_AGENT=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
310
|
+
LAST_STATUS=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
|
|
311
|
+
LAST_SPRINT=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
|
|
312
|
+
fi
|
|
313
|
+
stream_audit
|
|
314
|
+
fi
|
|
315
|
+
fi
|
|
188
316
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
317
|
+
if [ "$CURRENT_MODE" = "v4" ]; then
|
|
318
|
+
buf=$(render_v4 2>&1)
|
|
319
|
+
tput cup 0 0 2>/dev/null
|
|
320
|
+
echo "$buf"
|
|
321
|
+
tput ed 2>/dev/null
|
|
322
|
+
else
|
|
323
|
+
check_transitions
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
sleep 3
|
|
193
327
|
done
|
|
328
|
+
|
package/scripts/harness-next.sh
CHANGED
|
@@ -59,21 +59,10 @@ if [ -f "$PIPELINE_JSON" ]; then
|
|
|
59
59
|
fi
|
|
60
60
|
|
|
61
61
|
# ─────────────────────────────────────────
|
|
62
|
-
# fe_stack
|
|
63
|
-
# pipeline_selection.pipelines 에서 읽은 에이전트명을 fe_stack/fe_target 에 따라 치환
|
|
64
|
-
# - react: 그대로
|
|
65
|
-
# - flutter+web: generator-frontend → generator-frontend-flutter 만 치환, eval 은 그대로 (Playwright 사용 가능)
|
|
66
|
-
# - flutter+mobile/desktop: eval 도 정적 분석용으로 치환, evaluator-visual 은 __skip__
|
|
62
|
+
# fe_stack 치환 (no-op — Flutter 지원 제거됨, 하위 호환용 stub)
|
|
67
63
|
# ─────────────────────────────────────────
|
|
68
64
|
substitute_fe_stack() {
|
|
69
|
-
|
|
70
|
-
if [ "$fe_stack" != "flutter" ]; then
|
|
71
|
-
echo "$agent"
|
|
72
|
-
return
|
|
73
|
-
fi
|
|
74
|
-
local sub
|
|
75
|
-
sub=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.${fe_stack}.by_target[\"${fe_target}\"][\"${agent}\"] // \"${agent}\"" "$CONFIG" 2>/dev/null)
|
|
76
|
-
echo "$sub"
|
|
65
|
+
echo "$1"
|
|
77
66
|
}
|
|
78
67
|
|
|
79
68
|
# ─────────────────────────────────────────
|
|
@@ -105,7 +94,7 @@ run_pre_eval_gate() {
|
|
|
105
94
|
# current_agent가 generator가 아닌 경우 (예: dispatcher로 리라우팅된 상태),
|
|
106
95
|
# completed_agents에서 마지막 generator를 찾는다
|
|
107
96
|
case "$source_agent" in
|
|
108
|
-
generator-frontend
|
|
97
|
+
generator-frontend)
|
|
109
98
|
location="frontend"
|
|
110
99
|
checks_key="frontend_checks"
|
|
111
100
|
;;
|
|
@@ -118,7 +107,7 @@ run_pre_eval_gate() {
|
|
|
118
107
|
local last_gen
|
|
119
108
|
last_gen=$(jq -r '.completed_agents // [] | map(select(startswith("generator-"))) | last // empty' "$PROGRESS" 2>/dev/null)
|
|
120
109
|
case "$last_gen" in
|
|
121
|
-
generator-frontend
|
|
110
|
+
generator-frontend)
|
|
122
111
|
location="frontend"
|
|
123
112
|
checks_key="frontend_checks"
|
|
124
113
|
;;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-prompt-history.sh — Prompt History pane for v4 tmux layout
|
|
3
|
+
# progress.log + audit.log에서 유저 프롬프트/에이전트 활동 히스토리를 표시
|
|
4
|
+
# Usage: bash scripts/harness-prompt-history.sh [project-root]
|
|
5
|
+
|
|
6
|
+
set -uo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
10
|
+
|
|
11
|
+
PROJECT_ROOT="${1:-}"
|
|
12
|
+
if [ -z "$PROJECT_ROOT" ]; then
|
|
13
|
+
PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[history] .harness/ not found."; exit 1; }
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
|
|
17
|
+
AUDIT_LOG="$PROJECT_ROOT/.harness/actions/audit.log"
|
|
18
|
+
|
|
19
|
+
BOLD="\033[1m"
|
|
20
|
+
DIM="\033[2m"
|
|
21
|
+
GREEN="\033[32m"
|
|
22
|
+
YELLOW="\033[33m"
|
|
23
|
+
RED="\033[31m"
|
|
24
|
+
CYAN="\033[36m"
|
|
25
|
+
MAGENTA="\033[35m"
|
|
26
|
+
RESET="\033[0m"
|
|
27
|
+
|
|
28
|
+
render_header() {
|
|
29
|
+
echo -e "${BOLD}PROMPT HISTORY${RESET} ${DIM}$(date +%H:%M:%S)${RESET}"
|
|
30
|
+
echo ""
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
render_progress_log() {
|
|
34
|
+
if [ ! -f "$PROGRESS_LOG" ]; then
|
|
35
|
+
echo -e " ${DIM}(no progress.log yet)${RESET}"
|
|
36
|
+
return
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
grep -v '^#' "$PROGRESS_LOG" 2>/dev/null | grep -v '^$' | tail -20 | while IFS= read -r line; do
|
|
40
|
+
local ts agent action detail
|
|
41
|
+
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
42
|
+
agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
|
|
43
|
+
action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
|
|
44
|
+
detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
|
|
45
|
+
|
|
46
|
+
local short_ts icon color
|
|
47
|
+
short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "$ts")
|
|
48
|
+
|
|
49
|
+
case "$agent" in
|
|
50
|
+
dispatcher*) icon="▸" ; color="$MAGENTA" ;;
|
|
51
|
+
brainstormer) icon="◇" ; color="$CYAN" ;;
|
|
52
|
+
planner*) icon="□" ; color="$YELLOW" ;;
|
|
53
|
+
generator*) icon="▶" ; color="$GREEN" ;;
|
|
54
|
+
eval*) icon="✦" ; color="$RED" ;;
|
|
55
|
+
user|manual) icon="★" ; color="$BOLD" ;;
|
|
56
|
+
team*) icon="⊕" ; color="$CYAN" ;;
|
|
57
|
+
*) icon="·" ; color="$DIM" ;;
|
|
58
|
+
esac
|
|
59
|
+
|
|
60
|
+
if [ ${#detail} -gt 40 ]; then detail="${detail:0:38}.."; fi
|
|
61
|
+
|
|
62
|
+
printf " %b%b%b %b%-8s%b %b%s%b %b%s%b\n" \
|
|
63
|
+
"$color" "$icon" "$RESET" \
|
|
64
|
+
"$DIM" "$short_ts" "$RESET" \
|
|
65
|
+
"$RESET" "$agent" "$RESET" \
|
|
66
|
+
"$DIM" "$action" "$RESET"
|
|
67
|
+
if [ -n "$detail" ] && [ "$detail" != " " ]; then
|
|
68
|
+
echo -e " ${DIM}${detail}${RESET}"
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
render_audit_tail() {
|
|
74
|
+
if [ ! -f "$AUDIT_LOG" ]; then return; fi
|
|
75
|
+
|
|
76
|
+
echo ""
|
|
77
|
+
echo -e "${BOLD}AUDIT${RESET}"
|
|
78
|
+
|
|
79
|
+
grep -v '^#' "$AUDIT_LOG" 2>/dev/null | grep -v '^$' | tail -10 | while IFS= read -r line; do
|
|
80
|
+
local ts agent action status target
|
|
81
|
+
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
82
|
+
agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
|
|
83
|
+
action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
|
|
84
|
+
status=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
|
|
85
|
+
target=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$5); print $5}')
|
|
86
|
+
|
|
87
|
+
local color="$RESET" icon="·"
|
|
88
|
+
case "$status" in
|
|
89
|
+
start) color="$CYAN"; icon="▶" ;;
|
|
90
|
+
complete) color="$GREEN"; icon="✓" ;;
|
|
91
|
+
fail) color="$RED"; icon="✗" ;;
|
|
92
|
+
pass) color="$GREEN"; icon="✓" ;;
|
|
93
|
+
skip) color="$DIM"; icon="–" ;;
|
|
94
|
+
esac
|
|
95
|
+
|
|
96
|
+
local short_ts
|
|
97
|
+
short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "$ts")
|
|
98
|
+
|
|
99
|
+
if [ ${#target} -gt 25 ]; then target="${target:0:23}.."; fi
|
|
100
|
+
|
|
101
|
+
printf " %b%b%b %b%s%b %-10s %b%s%b\n" \
|
|
102
|
+
"$color" "$icon" "$RESET" \
|
|
103
|
+
"$DIM" "$short_ts" "$RESET" \
|
|
104
|
+
"$agent" \
|
|
105
|
+
"$DIM" "$target" "$RESET"
|
|
106
|
+
done
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render_all() {
|
|
110
|
+
render_header
|
|
111
|
+
render_progress_log
|
|
112
|
+
render_audit_tail
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# ── Main loop ──
|
|
116
|
+
tput civis 2>/dev/null
|
|
117
|
+
trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
|
|
118
|
+
clear
|
|
119
|
+
|
|
120
|
+
while true; do
|
|
121
|
+
buf=$(render_all 2>&1)
|
|
122
|
+
tput cup 0 0 2>/dev/null
|
|
123
|
+
echo "$buf"
|
|
124
|
+
tput ed 2>/dev/null
|
|
125
|
+
sleep 3
|
|
126
|
+
done
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-studio-setup.sh — Claude 세션에서 3-column 레이아웃 자동 구축
|
|
3
|
+
#
|
|
4
|
+
# ┌──────────────┬──────────────┬──────────────┐
|
|
5
|
+
# │ │ Dashboard │ │
|
|
6
|
+
# │ Claude │ (v4 queue) │ Team Monitor│
|
|
7
|
+
# │ (Lead) ├──────────────┤ (lifecycle) │
|
|
8
|
+
# │ │ Command │ │
|
|
9
|
+
# │ │ History │ │
|
|
10
|
+
# └──────────────┴──────────────┴──────────────┘
|
|
11
|
+
#
|
|
12
|
+
# 두 가지 상황을 모두 처리:
|
|
13
|
+
# A) tmux 안에서 실행 → 현재 pane을 split하여 레이아웃 구축
|
|
14
|
+
# B) tmux 밖에서 실행 → 새 tmux 세션 생성, Claude를 좌측 pane에서 재실행
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# bash scripts/harness-studio-setup.sh [project-root]
|
|
18
|
+
#
|
|
19
|
+
# 이미 구축됐으면 skip (멱등성 보장)
|
|
20
|
+
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
24
|
+
SESSION_NAME="harness-studio"
|
|
25
|
+
|
|
26
|
+
PROJECT_ROOT="${1:-}"
|
|
27
|
+
if [ -z "$PROJECT_ROOT" ]; then
|
|
28
|
+
dir="$(pwd)"
|
|
29
|
+
while [ "$dir" != "/" ]; do
|
|
30
|
+
if [ -d "$dir/.harness" ]; then PROJECT_ROOT="$dir"; break; fi
|
|
31
|
+
dir="$(dirname "$dir")"
|
|
32
|
+
done
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
|
|
36
|
+
echo "[studio] .harness/ not found."
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# ── Resolve Claude command ──
|
|
41
|
+
CLAUDE_CMD="claude --dangerously-skip-permissions"
|
|
42
|
+
if [ -f "$PROJECT_ROOT/.harness/handoff.json" ]; then
|
|
43
|
+
_model=$(jq -r '.model // empty' "$PROJECT_ROOT/.harness/handoff.json" 2>/dev/null)
|
|
44
|
+
if [ -n "$_model" ] && [ "$_model" != "null" ]; then
|
|
45
|
+
CLAUDE_CMD="$CLAUDE_CMD --model $_model"
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# ══════════════════════════════════════════
|
|
50
|
+
# Case A: 이미 tmux 안에 있음 → pane split
|
|
51
|
+
# ══════════════════════════════════════════
|
|
52
|
+
if [ -n "${TMUX:-}" ]; then
|
|
53
|
+
# 멱등성: 이미 pane이 3개 이상이면 skip
|
|
54
|
+
PANE_COUNT=$(tmux list-panes | wc -l | tr -d ' ')
|
|
55
|
+
if [ "$PANE_COUNT" -ge 3 ]; then
|
|
56
|
+
echo "[studio] Layout already set up ($PANE_COUNT panes). Skipping."
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
PANE_CLAUDE=$(tmux display-message -p '#{pane_id}')
|
|
61
|
+
echo "[studio] Setting up 3-column layout (in-place split)..."
|
|
62
|
+
|
|
63
|
+
# Left 35% | Right 65%
|
|
64
|
+
PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_CLAUDE" -c "$PROJECT_ROOT" \
|
|
65
|
+
-P -F '#{pane_id}' \
|
|
66
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
|
|
67
|
+
|
|
68
|
+
# Middle 45% | Right 55%
|
|
69
|
+
PANE_RIGHT=$(tmux split-window -h -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
|
|
70
|
+
-P -F '#{pane_id}' \
|
|
71
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
|
|
72
|
+
|
|
73
|
+
# Dashboard top 45% | History bottom 55%
|
|
74
|
+
PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
|
|
75
|
+
-P -F '#{pane_id}' \
|
|
76
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
|
|
77
|
+
|
|
78
|
+
# Pane titles
|
|
79
|
+
tmux select-pane -t "$PANE_CLAUDE" -T "Lead (Claude)"
|
|
80
|
+
tmux select-pane -t "$PANE_MID" -T "Dashboard"
|
|
81
|
+
tmux select-pane -t "$PANE_HISTORY" -T "Command History"
|
|
82
|
+
tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
|
|
83
|
+
|
|
84
|
+
tmux set-option pane-border-status top 2>/dev/null || true
|
|
85
|
+
tmux set-option pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
86
|
+
|
|
87
|
+
# 포커스를 Claude pane으로 복귀
|
|
88
|
+
tmux select-pane -t "$PANE_CLAUDE"
|
|
89
|
+
|
|
90
|
+
echo "[studio] Layout ready (in-place)."
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# ══════════════════════════════════════════
|
|
95
|
+
# Case B: tmux 밖에 있음 → 새 세션 생성
|
|
96
|
+
# ══════════════════════════════════════════
|
|
97
|
+
|
|
98
|
+
# 이미 세션이 있으면 attach만
|
|
99
|
+
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|
100
|
+
echo "[studio] Session '$SESSION_NAME' already exists. Attaching..."
|
|
101
|
+
echo "[studio] ATTACH_TMUX=$SESSION_NAME"
|
|
102
|
+
exit 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
echo "[studio] Creating new tmux session with 3-column layout..."
|
|
106
|
+
|
|
107
|
+
# 1. 새 세션 → Left pane (Claude 실행)
|
|
108
|
+
PANE_MAIN=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
|
|
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" \
|
|
113
|
+
-P -F '#{pane_id}' \
|
|
114
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
|
|
115
|
+
|
|
116
|
+
# 3. Middle 45% | Right 55%
|
|
117
|
+
PANE_RIGHT=$(tmux split-window -h -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
|
|
118
|
+
-P -F '#{pane_id}' \
|
|
119
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
|
|
120
|
+
|
|
121
|
+
# 4. Dashboard top 45% | History bottom 55%
|
|
122
|
+
PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
|
|
123
|
+
-P -F '#{pane_id}' \
|
|
124
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
|
|
125
|
+
|
|
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
|
+
# Pane titles
|
|
131
|
+
tmux select-pane -t "$PANE_MAIN" -T "Lead (Claude)"
|
|
132
|
+
tmux select-pane -t "$PANE_MID" -T "Dashboard"
|
|
133
|
+
tmux select-pane -t "$PANE_HISTORY" -T "Command History"
|
|
134
|
+
tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
|
|
135
|
+
|
|
136
|
+
tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
|
|
137
|
+
tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
138
|
+
|
|
139
|
+
# Focus on Claude pane
|
|
140
|
+
tmux select-pane -t "$PANE_MAIN"
|
|
141
|
+
|
|
142
|
+
echo "[studio] Session '$SESSION_NAME' created."
|
|
143
|
+
echo "[studio] ATTACH_TMUX=$SESSION_NAME"
|