@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.
@@ -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