@walwal-harness/cli 4.0.0 → 5.0.1
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 +118 -294
- package/assets/templates/config.json +10 -9
- package/assets/templates/progress.json.template +7 -1
- package/bin/init.js +115 -84
- package/commands/harness-solo.md +68 -0
- package/commands/harness-stop.md +53 -0
- package/{skills/team-action/SKILL.md → commands/harness-team.md} +59 -29
- package/package.json +9 -10
- package/scripts/harness-dashboard.sh +219 -154
- package/scripts/harness-monitor.sh +17 -12
- package/scripts/harness-prompt-history.sh +1 -1
- package/scripts/harness-queue-manager.sh +1 -1
- package/scripts/harness-session-start.sh +24 -13
- package/scripts/harness-statusline.sh +31 -8
- package/scripts/harness-tmux.sh +135 -72
- package/scripts/harness-user-prompt-submit.sh +22 -15
- package/skills/dispatcher/SKILL.md +6 -6
- package/skills/evaluator-functional/SKILL.md +2 -2
- package/skills/generator-frontend/SKILL.md +2 -2
- package/scripts/harness-control.sh +0 -183
- package/scripts/harness-dashboard-v4.sh +0 -200
- package/scripts/harness-eval-watcher.sh +0 -228
- package/scripts/harness-studio-setup.sh +0 -139
- package/scripts/harness-studio.sh +0 -66
- package/scripts/harness-tmux-v4.sh +0 -148
- package/skills/team-stop/SKILL.md +0 -30
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-dashboard-v4.sh — v4 Dashboard 상단: Planner Progress
|
|
3
|
-
# Queue + Teams + Features 를 auto-refresh. 고정 영역, 스크롤 없음.
|
|
4
|
-
|
|
5
|
-
set -uo pipefail
|
|
6
|
-
|
|
7
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
9
|
-
|
|
10
|
-
PROJECT_ROOT="${1:-}"
|
|
11
|
-
if [ -z "$PROJECT_ROOT" ]; then
|
|
12
|
-
PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[dash] .harness/ not found."; exit 1; }
|
|
13
|
-
fi
|
|
14
|
-
|
|
15
|
-
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
16
|
-
FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
17
|
-
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
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
|
-
local now project_name
|
|
30
|
-
now=$(date +"%H:%M:%S")
|
|
31
|
-
project_name=$(jq -r '.project_name // "Unknown"' "$PROGRESS" 2>/dev/null)
|
|
32
|
-
|
|
33
|
-
echo -e "${BOLD}HARNESS v4${RESET} ${DIM}${project_name} | ${now}${RESET}"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
render_queue_summary() {
|
|
37
|
-
if [ ! -f "$QUEUE" ]; then
|
|
38
|
-
echo -e " ${DIM}(queue not initialized)${RESET}"
|
|
39
|
-
return
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
local ready blocked in_prog passed failed total
|
|
43
|
-
ready=$(jq '.queue.ready | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
44
|
-
blocked=$(jq '.queue.blocked | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
45
|
-
in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
46
|
-
passed=$(jq '.queue.passed | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
47
|
-
failed=$(jq '.queue.failed | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
48
|
-
ready=${ready:-0}; blocked=${blocked:-0}; in_prog=${in_prog:-0}; passed=${passed:-0}; failed=${failed:-0}
|
|
49
|
-
total=$((ready + blocked + in_prog + passed + failed))
|
|
50
|
-
|
|
51
|
-
local pct=0
|
|
52
|
-
if [ "$total" -gt 0 ]; then pct=$(( passed * 100 / total )); fi
|
|
53
|
-
local bar_w=16
|
|
54
|
-
local filled=$(( pct * bar_w / 100 ))
|
|
55
|
-
local empty=$(( bar_w - filled ))
|
|
56
|
-
local bar=""
|
|
57
|
-
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
58
|
-
for ((i=0; i<empty; i++)); do bar+="░"; done
|
|
59
|
-
|
|
60
|
-
echo -e " ${bar} ${passed}/${total} (${pct}%) R:${GREEN}${ready}${RESET} B:${YELLOW}${blocked}${RESET} P:${CYAN}${in_prog}${RESET} ${GREEN}✓${passed}${RESET} ${RED}✗${failed}${RESET}"
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
render_teams() {
|
|
64
|
-
if [ ! -f "$QUEUE" ]; then return; fi
|
|
65
|
-
|
|
66
|
-
local team_count
|
|
67
|
-
team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null)
|
|
68
|
-
if [ "${team_count:-0}" -eq 0 ]; then return; fi
|
|
69
|
-
|
|
70
|
-
for i in $(seq 1 "$team_count"); do
|
|
71
|
-
local t_status t_feature t_phase t_attempt
|
|
72
|
-
t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
|
|
73
|
-
t_feature=$(jq -r ".teams[\"$i\"].feature // \"—\"" "$QUEUE" 2>/dev/null)
|
|
74
|
-
|
|
75
|
-
if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
|
|
76
|
-
t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
|
|
77
|
-
t_attempt=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].attempt // 1' "$QUEUE" 2>/dev/null)
|
|
78
|
-
else
|
|
79
|
-
t_phase="—"; t_attempt=""
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
local icon color
|
|
83
|
-
case "$t_status" in
|
|
84
|
-
busy) icon="▶"; color="$GREEN" ;;
|
|
85
|
-
idle) icon="○"; color="$DIM" ;;
|
|
86
|
-
*) icon="?"; color="$RESET" ;;
|
|
87
|
-
esac
|
|
88
|
-
|
|
89
|
-
local phase_short=""
|
|
90
|
-
case "$t_phase" in
|
|
91
|
-
gen) phase_short="${CYAN}G${RESET}" ;;
|
|
92
|
-
gate) phase_short="${YELLOW}K${RESET}" ;;
|
|
93
|
-
eval) phase_short="${MAGENTA}E${RESET}" ;;
|
|
94
|
-
*) phase_short="${DIM}-${RESET}" ;;
|
|
95
|
-
esac
|
|
96
|
-
|
|
97
|
-
printf " %b%b T%d %-7s %b" "$color" "$icon" "$i" "$t_feature" "$phase_short"
|
|
98
|
-
if [ -n "$t_attempt" ] && [ "$t_attempt" != "—" ]; then
|
|
99
|
-
printf " #%s" "$t_attempt"
|
|
100
|
-
fi
|
|
101
|
-
echo ""
|
|
102
|
-
done
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
render_features() {
|
|
106
|
-
if [ ! -f "$QUEUE" ] || [ ! -f "$FEATURES" ]; then return; fi
|
|
107
|
-
|
|
108
|
-
jq -r --slurpfile q "$QUEUE" '
|
|
109
|
-
($q[0].queue.passed // []) as $passed |
|
|
110
|
-
($q[0].queue.failed // []) as $failed |
|
|
111
|
-
($q[0].queue.ready // []) as $ready |
|
|
112
|
-
($q[0].queue.in_progress // {}) as $prog |
|
|
113
|
-
.features[] |
|
|
114
|
-
.id as $fid |
|
|
115
|
-
(.name // .description // "?" | if length > 18 then .[0:16] + ".." else . end) as $fname |
|
|
116
|
-
(if ($fid | IN($passed[])) then "P"
|
|
117
|
-
elif $prog[$fid] then "I|\($prog[$fid].team)|\($prog[$fid].phase)"
|
|
118
|
-
elif ($fid | IN($failed[])) then "F"
|
|
119
|
-
elif ($fid | IN($ready[])) then "R"
|
|
120
|
-
else "B" end) as $st |
|
|
121
|
-
"\($st)\t\($fid)\t\($fname)"
|
|
122
|
-
' "$FEATURES" 2>/dev/null | while IFS=$'\t' read -r st fid fname; do
|
|
123
|
-
case "$st" in
|
|
124
|
-
P) printf " ${GREEN}●${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
125
|
-
F) printf " ${RED}✗${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
126
|
-
R) printf " ${YELLOW}○${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
127
|
-
B) printf " ${DIM}◌${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
128
|
-
I\|*) team=$(echo "$st" | cut -d'|' -f2)
|
|
129
|
-
phase=$(echo "$st" | cut -d'|' -f3)
|
|
130
|
-
printf " ${CYAN}◐${RESET} %-6s %-14s T%s:%s\n" "$fid" "$fname" "$team" "$phase" ;;
|
|
131
|
-
*) printf " ? %-6s %s\n" "$fid" "$fname" ;;
|
|
132
|
-
esac
|
|
133
|
-
done
|
|
134
|
-
}
|
|
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
|
-
|
|
180
|
-
render_all() {
|
|
181
|
-
render_header
|
|
182
|
-
render_queue_summary
|
|
183
|
-
render_teams
|
|
184
|
-
render_bottleneck_alert
|
|
185
|
-
echo ""
|
|
186
|
-
render_features
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
# ── Main loop ──
|
|
190
|
-
tput civis 2>/dev/null
|
|
191
|
-
trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
|
|
192
|
-
clear
|
|
193
|
-
|
|
194
|
-
while true; do
|
|
195
|
-
buf=$(render_all 2>&1)
|
|
196
|
-
tput cup 0 0 2>/dev/null
|
|
197
|
-
echo "$buf"
|
|
198
|
-
tput ed 2>/dev/null
|
|
199
|
-
sleep 3
|
|
200
|
-
done
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-eval-watcher.sh — Panel 3: Evaluation 결과 Summary 리뷰
|
|
3
|
-
# evaluation-*.md 파일 변경 감지 → 요약 출력 (또는 claude -p 호출)
|
|
4
|
-
# Usage: bash scripts/harness-eval-watcher.sh [project-root] [--ai]
|
|
5
|
-
# --ai evaluation 완료 시 claude -p 로 AI 요약 생성 (API 비용 발생)
|
|
6
|
-
|
|
7
|
-
set -uo pipefail
|
|
8
|
-
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
-
|
|
11
|
-
PROJECT_ROOT="${1:-}"
|
|
12
|
-
USE_AI=false
|
|
13
|
-
for arg in "$@"; do
|
|
14
|
-
if [ "$arg" = "--ai" ]; then USE_AI=true; fi
|
|
15
|
-
done
|
|
16
|
-
|
|
17
|
-
if [ -z "$PROJECT_ROOT" ] || [ "$PROJECT_ROOT" = "--ai" ]; then
|
|
18
|
-
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
19
|
-
PROJECT_ROOT="$(resolve_harness_root ".")" || {
|
|
20
|
-
echo "[eval-watcher] .harness/ not found. Pass project root as argument."
|
|
21
|
-
exit 1
|
|
22
|
-
}
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
26
|
-
EVAL_FUNC="$PROJECT_ROOT/.harness/actions/evaluation-functional.md"
|
|
27
|
-
EVAL_VISUAL="$PROJECT_ROOT/.harness/actions/evaluation-visual.md"
|
|
28
|
-
|
|
29
|
-
# ── ANSI helpers ──
|
|
30
|
-
BOLD="\033[1m"
|
|
31
|
-
DIM="\033[2m"
|
|
32
|
-
GREEN="\033[32m"
|
|
33
|
-
YELLOW="\033[33m"
|
|
34
|
-
RED="\033[31m"
|
|
35
|
-
CYAN="\033[36m"
|
|
36
|
-
RESET="\033[0m"
|
|
37
|
-
|
|
38
|
-
LAST_FUNC_HASH=""
|
|
39
|
-
LAST_VISUAL_HASH=""
|
|
40
|
-
EVAL_COUNT=0
|
|
41
|
-
|
|
42
|
-
print_header() {
|
|
43
|
-
echo -e "${BOLD}╔══════════════════════════════════════╗${RESET}"
|
|
44
|
-
echo -e "${BOLD}║ EVALUATION REVIEW ║${RESET}"
|
|
45
|
-
echo -e "${BOLD}╚══════════════════════════════════════╝${RESET}"
|
|
46
|
-
echo ""
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
file_hash() {
|
|
50
|
-
if [ -f "$1" ]; then
|
|
51
|
-
md5 -q "$1" 2>/dev/null || md5sum "$1" 2>/dev/null | awk '{print $1}' || echo ""
|
|
52
|
-
else
|
|
53
|
-
echo ""
|
|
54
|
-
fi
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
# ── Extract eval summary from markdown ──
|
|
58
|
-
extract_eval_summary() {
|
|
59
|
-
local file="$1" type="$2"
|
|
60
|
-
|
|
61
|
-
if [ ! -f "$file" ]; then return; fi
|
|
62
|
-
|
|
63
|
-
EVAL_COUNT=$((EVAL_COUNT + 1))
|
|
64
|
-
local now
|
|
65
|
-
now=$(date +"%H:%M:%S")
|
|
66
|
-
|
|
67
|
-
echo -e " ${BOLD}═══ ${type} Evaluation #${EVAL_COUNT} ═══${RESET} ${DIM}(${now})${RESET}"
|
|
68
|
-
echo ""
|
|
69
|
-
|
|
70
|
-
# Extract Verdict (PASS/FAIL)
|
|
71
|
-
local verdict
|
|
72
|
-
verdict=$(grep -i "verdict\|result\|판정" "$file" 2>/dev/null | head -3 || true)
|
|
73
|
-
if [ -n "$verdict" ]; then
|
|
74
|
-
if echo "$verdict" | grep -qi "PASS"; then
|
|
75
|
-
echo -e " ${GREEN}${BOLD}VERDICT: PASS${RESET}"
|
|
76
|
-
elif echo "$verdict" | grep -qi "FAIL"; then
|
|
77
|
-
echo -e " ${RED}${BOLD}VERDICT: FAIL${RESET}"
|
|
78
|
-
else
|
|
79
|
-
echo -e " ${YELLOW}VERDICT: ${verdict}${RESET}"
|
|
80
|
-
fi
|
|
81
|
-
echo ""
|
|
82
|
-
fi
|
|
83
|
-
|
|
84
|
-
# Extract Score
|
|
85
|
-
local score_line
|
|
86
|
-
score_line=$(grep -iE "score|점수|weighted|가중" "$file" 2>/dev/null | head -3 || true)
|
|
87
|
-
if [ -n "$score_line" ]; then
|
|
88
|
-
echo -e " ${BOLD}Score${RESET}"
|
|
89
|
-
echo "$score_line" | while IFS= read -r line; do
|
|
90
|
-
# Color by score value
|
|
91
|
-
local num
|
|
92
|
-
num=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+' | head -1)
|
|
93
|
-
if [ -n "$num" ]; then
|
|
94
|
-
local int_part
|
|
95
|
-
int_part=$(echo "$num" | cut -d. -f1)
|
|
96
|
-
if [ "${int_part:-0}" -ge 3 ] || ([ "${int_part:-0}" -eq 2 ] && [ "$(echo "$num >= 2.80" | bc 2>/dev/null)" = "1" ]); then
|
|
97
|
-
echo -e " ${GREEN}${line}${RESET}"
|
|
98
|
-
else
|
|
99
|
-
echo -e " ${RED}${line}${RESET}"
|
|
100
|
-
fi
|
|
101
|
-
else
|
|
102
|
-
echo " $line"
|
|
103
|
-
fi
|
|
104
|
-
done
|
|
105
|
-
echo ""
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
# Extract individual rubric items (R1-R5 or V1-V5)
|
|
109
|
-
local rubric_lines
|
|
110
|
-
rubric_lines=$(grep -E "^[|#].*[RV][1-5]" "$file" 2>/dev/null | head -10 || true)
|
|
111
|
-
if [ -n "$rubric_lines" ]; then
|
|
112
|
-
echo -e " ${BOLD}Rubric${RESET}"
|
|
113
|
-
echo "$rubric_lines" | while IFS= read -r line; do
|
|
114
|
-
echo " $line"
|
|
115
|
-
done
|
|
116
|
-
echo ""
|
|
117
|
-
fi
|
|
118
|
-
|
|
119
|
-
# Extract FAIL reasons
|
|
120
|
-
local fail_lines
|
|
121
|
-
fail_lines=$(grep -iE "fail|실패|regression|불일치|위반" "$file" 2>/dev/null | head -5 || true)
|
|
122
|
-
if [ -n "$fail_lines" ]; then
|
|
123
|
-
echo -e " ${RED}${BOLD}Issues${RESET}"
|
|
124
|
-
echo "$fail_lines" | while IFS= read -r line; do
|
|
125
|
-
echo -e " ${RED}• ${line}${RESET}"
|
|
126
|
-
done
|
|
127
|
-
echo ""
|
|
128
|
-
fi
|
|
129
|
-
|
|
130
|
-
# Extract action items / recommendations
|
|
131
|
-
local action_lines
|
|
132
|
-
action_lines=$(grep -iE "recommend|action|수정|개선|필요|re-generate" "$file" 2>/dev/null | head -5 || true)
|
|
133
|
-
if [ -n "$action_lines" ]; then
|
|
134
|
-
echo -e " ${YELLOW}${BOLD}Actions${RESET}"
|
|
135
|
-
echo "$action_lines" | while IFS= read -r line; do
|
|
136
|
-
echo -e " ${YELLOW}→ ${line}${RESET}"
|
|
137
|
-
done
|
|
138
|
-
echo ""
|
|
139
|
-
fi
|
|
140
|
-
|
|
141
|
-
echo -e " ${DIM}─────────────────────────────────────${RESET}"
|
|
142
|
-
echo ""
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
# ── AI Summary (optional) ──
|
|
146
|
-
generate_ai_summary() {
|
|
147
|
-
local file="$1" type="$2"
|
|
148
|
-
|
|
149
|
-
if [ "$USE_AI" != true ]; then return; fi
|
|
150
|
-
if ! command -v claude &>/dev/null; then
|
|
151
|
-
echo -e " ${DIM}(claude CLI not found — skipping AI summary)${RESET}"
|
|
152
|
-
return
|
|
153
|
-
fi
|
|
154
|
-
|
|
155
|
-
echo -e " ${CYAN}Generating AI summary...${RESET}"
|
|
156
|
-
|
|
157
|
-
local summary
|
|
158
|
-
summary=$(claude -p "다음 ${type} Evaluation 문서를 3줄 이내로 요약해줘. 핵심 결과(PASS/FAIL), 주요 문제점, 다음 액션을 포함:
|
|
159
|
-
|
|
160
|
-
$(cat "$file")" --output-format text 2>/dev/null || echo "(AI summary failed)")
|
|
161
|
-
|
|
162
|
-
echo -e " ${CYAN}${BOLD}AI Summary:${RESET}"
|
|
163
|
-
echo "$summary" | while IFS= read -r line; do
|
|
164
|
-
echo -e " ${CYAN}${line}${RESET}"
|
|
165
|
-
done
|
|
166
|
-
echo ""
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
# ── Main ──
|
|
170
|
-
print_header
|
|
171
|
-
|
|
172
|
-
# Show existing evals on startup
|
|
173
|
-
if [ -f "$EVAL_FUNC" ]; then
|
|
174
|
-
echo -e " ${DIM}Found existing functional evaluation${RESET}"
|
|
175
|
-
extract_eval_summary "$EVAL_FUNC" "Functional"
|
|
176
|
-
LAST_FUNC_HASH=$(file_hash "$EVAL_FUNC")
|
|
177
|
-
fi
|
|
178
|
-
|
|
179
|
-
if [ -f "$EVAL_VISUAL" ]; then
|
|
180
|
-
echo -e " ${DIM}Found existing visual evaluation${RESET}"
|
|
181
|
-
extract_eval_summary "$EVAL_VISUAL" "Visual"
|
|
182
|
-
LAST_VISUAL_HASH=$(file_hash "$EVAL_VISUAL")
|
|
183
|
-
fi
|
|
184
|
-
|
|
185
|
-
if [ -z "$LAST_FUNC_HASH" ] && [ -z "$LAST_VISUAL_HASH" ]; then
|
|
186
|
-
echo -e " ${DIM}Waiting for evaluation files...${RESET}"
|
|
187
|
-
echo -e " ${DIM} ${EVAL_FUNC}${RESET}"
|
|
188
|
-
echo -e " ${DIM} ${EVAL_VISUAL}${RESET}"
|
|
189
|
-
echo ""
|
|
190
|
-
fi
|
|
191
|
-
|
|
192
|
-
echo -e " ${DIM}Watching for changes every 3s | Ctrl+C to exit${RESET}"
|
|
193
|
-
if [ "$USE_AI" = true ]; then
|
|
194
|
-
echo -e " ${CYAN}AI summary enabled (claude -p)${RESET}"
|
|
195
|
-
fi
|
|
196
|
-
echo ""
|
|
197
|
-
|
|
198
|
-
# ── Poll for changes ──
|
|
199
|
-
while true; do
|
|
200
|
-
# Check functional eval
|
|
201
|
-
local_func_hash=$(file_hash "$EVAL_FUNC")
|
|
202
|
-
if [ "$local_func_hash" != "$LAST_FUNC_HASH" ] && [ -n "$local_func_hash" ]; then
|
|
203
|
-
LAST_FUNC_HASH="$local_func_hash"
|
|
204
|
-
extract_eval_summary "$EVAL_FUNC" "Functional"
|
|
205
|
-
generate_ai_summary "$EVAL_FUNC" "Functional"
|
|
206
|
-
fi
|
|
207
|
-
|
|
208
|
-
# Check visual eval
|
|
209
|
-
local_visual_hash=$(file_hash "$EVAL_VISUAL")
|
|
210
|
-
if [ "$local_visual_hash" != "$LAST_VISUAL_HASH" ] && [ -n "$local_visual_hash" ]; then
|
|
211
|
-
LAST_VISUAL_HASH="$local_visual_hash"
|
|
212
|
-
extract_eval_summary "$EVAL_VISUAL" "Visual"
|
|
213
|
-
generate_ai_summary "$EVAL_VISUAL" "Visual"
|
|
214
|
-
fi
|
|
215
|
-
|
|
216
|
-
# Check agent status for eval-related transitions
|
|
217
|
-
if [ -f "$PROGRESS" ]; then
|
|
218
|
-
_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
219
|
-
_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
|
|
220
|
-
|
|
221
|
-
if [[ "$_agent" == evaluator-* ]] && [ "$_status" = "running" ]; then
|
|
222
|
-
# Show that evaluator is active
|
|
223
|
-
:
|
|
224
|
-
fi
|
|
225
|
-
fi
|
|
226
|
-
|
|
227
|
-
sleep 3
|
|
228
|
-
done
|
|
@@ -1,139 +0,0 @@
|
|
|
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 밖에 있음 → 새 세션 생성 + Terminal.app에서 자동 attach
|
|
96
|
-
# ══════════════════════════════════════════
|
|
97
|
-
|
|
98
|
-
# 이미 세션이 있으면 Terminal.app에서 attach만
|
|
99
|
-
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|
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"
|
|
104
|
-
exit 0
|
|
105
|
-
fi
|
|
106
|
-
|
|
107
|
-
echo "[studio] Creating new tmux session with 3-column layout..."
|
|
108
|
-
|
|
109
|
-
# 1. 새 세션 → Left pane (Dashboard 전용 — Claude는 현재 세션에서 계속)
|
|
110
|
-
PANE_DASH=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
|
|
111
|
-
-P -F '#{pane_id}' \
|
|
112
|
-
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
|
|
113
|
-
|
|
114
|
-
# 2. Split: Dashboard | Monitor
|
|
115
|
-
PANE_RIGHT=$(tmux split-window -h -p 50 -t "$PANE_DASH" -c "$PROJECT_ROOT" \
|
|
116
|
-
-P -F '#{pane_id}' \
|
|
117
|
-
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
|
|
118
|
-
|
|
119
|
-
# 3. Split Dashboard vertically: Dashboard top | History bottom
|
|
120
|
-
PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_DASH" -c "$PROJECT_ROOT" \
|
|
121
|
-
-P -F '#{pane_id}' \
|
|
122
|
-
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
|
|
123
|
-
|
|
124
|
-
# Pane titles
|
|
125
|
-
tmux select-pane -t "$PANE_DASH" -T "Dashboard"
|
|
126
|
-
tmux select-pane -t "$PANE_HISTORY" -T "Command History"
|
|
127
|
-
tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
|
|
128
|
-
|
|
129
|
-
tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
|
|
130
|
-
tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
131
|
-
|
|
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
|
|
137
|
-
|
|
138
|
-
echo "[studio] Session '$SESSION_NAME' created."
|
|
139
|
-
echo "[studio] OPENED_TERMINAL=true"
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-studio.sh — 하네스 통합 진입점
|
|
3
|
-
#
|
|
4
|
-
# 사용자가 기억할 명령: 이것 하나.
|
|
5
|
-
#
|
|
6
|
-
# bash scripts/harness-studio.sh # 자동 감지: v3 or v4
|
|
7
|
-
# bash scripts/harness-studio.sh --kill # 세션 종료
|
|
8
|
-
# bash scripts/harness-studio.sh --v3 # v3 강제
|
|
9
|
-
# bash scripts/harness-studio.sh --v4 # v4 강제
|
|
10
|
-
#
|
|
11
|
-
# 자동 감지 기준:
|
|
12
|
-
# feature-queue.json 존재 → v4 (Agent Teams 병렬 모드)
|
|
13
|
-
# 없으면 → v3 (순차 파이프라인 모드)
|
|
14
|
-
|
|
15
|
-
set -euo pipefail
|
|
16
|
-
|
|
17
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
-
FORCE_MODE=""
|
|
19
|
-
|
|
20
|
-
# ── Parse args (pass through to sub-script) ──
|
|
21
|
-
PASSTHROUGH_ARGS=()
|
|
22
|
-
for arg in "$@"; do
|
|
23
|
-
case "$arg" in
|
|
24
|
-
--v3) FORCE_MODE="v3" ;;
|
|
25
|
-
--v4) FORCE_MODE="v4" ;;
|
|
26
|
-
--kill)
|
|
27
|
-
tmux kill-session -t "harness-studio" 2>/dev/null && echo "v3 killed." || true
|
|
28
|
-
tmux kill-session -t "harness-v4" 2>/dev/null && echo "v4 killed." || true
|
|
29
|
-
exit 0
|
|
30
|
-
;;
|
|
31
|
-
*)
|
|
32
|
-
PASSTHROUGH_ARGS+=("$arg")
|
|
33
|
-
;;
|
|
34
|
-
esac
|
|
35
|
-
done
|
|
36
|
-
|
|
37
|
-
# ── Auto-detect mode ──
|
|
38
|
-
detect_mode() {
|
|
39
|
-
local dir="${1:-.}"
|
|
40
|
-
while [ "$dir" != "/" ]; do
|
|
41
|
-
if [ -d "$dir/.harness" ]; then
|
|
42
|
-
if [ -f "$dir/.harness/actions/feature-queue.json" ]; then
|
|
43
|
-
echo "v4"
|
|
44
|
-
else
|
|
45
|
-
echo "v3"
|
|
46
|
-
fi
|
|
47
|
-
return
|
|
48
|
-
fi
|
|
49
|
-
dir="$(dirname "$dir")"
|
|
50
|
-
done
|
|
51
|
-
echo "v3" # default
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
MODE="${FORCE_MODE:-$(detect_mode "$(pwd)")}"
|
|
55
|
-
|
|
56
|
-
echo "Harness Studio — mode: $MODE"
|
|
57
|
-
echo ""
|
|
58
|
-
|
|
59
|
-
case "$MODE" in
|
|
60
|
-
v4)
|
|
61
|
-
exec bash "$SCRIPT_DIR/harness-tmux-v4.sh" "${PASSTHROUGH_ARGS[@]}"
|
|
62
|
-
;;
|
|
63
|
-
v3)
|
|
64
|
-
exec bash "$SCRIPT_DIR/harness-tmux.sh" "${PASSTHROUGH_ARGS[@]}"
|
|
65
|
-
;;
|
|
66
|
-
esac
|