@walwal-harness/cli 4.0.0-beta.9 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -294
- package/assets/templates/config.json +8 -7
- 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} +100 -43
- package/package.json +9 -10
- package/scripts/harness-dashboard.sh +219 -154
- package/scripts/harness-monitor.sh +126 -73
- 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 +125 -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 -155
- package/scripts/harness-eval-watcher.sh +0 -228
- package/scripts/harness-studio-setup.sh +0 -143
- package/scripts/harness-studio.sh +0 -66
- package/scripts/harness-tmux-v4.sh +0 -136
- package/skills/team-stop/SKILL.md +0 -19
|
@@ -1,155 +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_all() {
|
|
137
|
-
render_header
|
|
138
|
-
render_queue_summary
|
|
139
|
-
render_teams
|
|
140
|
-
echo ""
|
|
141
|
-
render_features
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
# ── Main loop ──
|
|
145
|
-
tput civis 2>/dev/null
|
|
146
|
-
trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
|
|
147
|
-
clear
|
|
148
|
-
|
|
149
|
-
while true; do
|
|
150
|
-
buf=$(render_all 2>&1)
|
|
151
|
-
tput cup 0 0 2>/dev/null
|
|
152
|
-
echo "$buf"
|
|
153
|
-
tput ed 2>/dev/null
|
|
154
|
-
sleep 3
|
|
155
|
-
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,143 +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 밖에 있음 → 새 세션 생성
|
|
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"
|
|
@@ -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
|