@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
package/scripts/harness-tmux.sh
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-tmux.sh — Harness Studio tmux
|
|
2
|
+
# harness-tmux.sh — Unified Harness Studio tmux layout
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# │
|
|
7
|
-
# │
|
|
8
|
-
#
|
|
9
|
-
# │
|
|
10
|
-
# │
|
|
11
|
-
#
|
|
4
|
+
# Team Mode (--team):
|
|
5
|
+
# ┌──────────────┬──────────────┬──────────────┐
|
|
6
|
+
# │ Prompt │ Dashboard │ TEAM 1 │
|
|
7
|
+
# │ History │ (queue + │ Gen | Eval │
|
|
8
|
+
# │ │ status) ├──────────────┤
|
|
9
|
+
# ├──────────────┤ │ TEAM 2 │
|
|
10
|
+
# │ Controller │ │ Gen | Eval │
|
|
11
|
+
# │ (Claude / │ ├──────────────┤
|
|
12
|
+
# │ Codex) │ │ TEAM 3 │
|
|
13
|
+
# └──────────────┴──────────────┴──────────────┘
|
|
12
14
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
15
|
+
# Solo Mode (--solo):
|
|
16
|
+
# ┌──────────────┬──────────────┬──────────────┐
|
|
17
|
+
# │ Dashboard │ Monitor │ Agent │
|
|
18
|
+
# │ │ (compact) │ Session │
|
|
19
|
+
# ├──────────────┤ │ │
|
|
20
|
+
# │ Prompt │ │ │
|
|
21
|
+
# │ History │ │ │
|
|
22
|
+
# └──────────────┴──────────────┴──────────────┘
|
|
16
23
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
24
|
+
# Usage:
|
|
25
|
+
# bash scripts/harness-tmux.sh [project-root] --team
|
|
26
|
+
# bash scripts/harness-tmux.sh [project-root] --solo
|
|
27
|
+
# bash scripts/harness-tmux.sh --kill
|
|
21
28
|
|
|
22
29
|
set -euo pipefail
|
|
23
30
|
|
|
@@ -25,12 +32,13 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
25
32
|
SESSION_NAME="harness-studio"
|
|
26
33
|
|
|
27
34
|
PROJECT_ROOT=""
|
|
28
|
-
|
|
35
|
+
MODE=""
|
|
29
36
|
DETACH=false
|
|
30
37
|
|
|
31
38
|
for arg in "$@"; do
|
|
32
39
|
case "$arg" in
|
|
33
|
-
--
|
|
40
|
+
--team) MODE="team" ;;
|
|
41
|
+
--solo) MODE="solo" ;;
|
|
34
42
|
--detach) DETACH=true ;;
|
|
35
43
|
--kill)
|
|
36
44
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "Killed." || echo "No session."
|
|
@@ -55,82 +63,127 @@ if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
|
|
|
55
63
|
exit 1
|
|
56
64
|
fi
|
|
57
65
|
|
|
66
|
+
# Auto-detect mode from progress.json if not specified
|
|
67
|
+
if [ -z "$MODE" ]; then
|
|
68
|
+
if command -v jq &>/dev/null && [ -f "$PROJECT_ROOT/.harness/progress.json" ]; then
|
|
69
|
+
MODE=$(jq -r '.mode // "solo"' "$PROJECT_ROOT/.harness/progress.json" 2>/dev/null)
|
|
70
|
+
fi
|
|
71
|
+
MODE="${MODE:-solo}"
|
|
72
|
+
fi
|
|
73
|
+
|
|
58
74
|
echo "Project: $PROJECT_ROOT"
|
|
59
75
|
echo "Session: $SESSION_NAME"
|
|
76
|
+
echo "Mode: $MODE"
|
|
60
77
|
|
|
61
78
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
|
|
62
79
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
# ══════════════════════════════════════════
|
|
81
|
+
# Team Mode: 3-column layout (6 panes)
|
|
82
|
+
# ══════════════════════════════════════════
|
|
83
|
+
if [ "$MODE" = "team" ]; then
|
|
84
|
+
|
|
85
|
+
# 1. Create session → Left pane (will become Prompt History)
|
|
86
|
+
PANE_LEFT=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
|
|
87
|
+
-P -F '#{pane_id}')
|
|
88
|
+
|
|
89
|
+
# 2. Split horizontally: Left 30% | Right 70%
|
|
90
|
+
PANE_MID=$(tmux split-window -h -p 70 -t "$PANE_LEFT" -c "$PROJECT_ROOT" \
|
|
91
|
+
-P -F '#{pane_id}')
|
|
92
|
+
|
|
93
|
+
# 3. Split right section: Middle 40% | Right 60% (Teams)
|
|
94
|
+
PANE_T1=$(tmux split-window -h -p 60 -t "$PANE_MID" -c "$PROJECT_ROOT" \
|
|
95
|
+
-P -F '#{pane_id}' \
|
|
96
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 1'")
|
|
97
|
+
|
|
98
|
+
# 4. Split Team 1 vertically → Team 2 (bottom 66%)
|
|
99
|
+
PANE_T2=$(tmux split-window -v -p 66 -t "$PANE_T1" -c "$PROJECT_ROOT" \
|
|
100
|
+
-P -F '#{pane_id}' \
|
|
101
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 2'")
|
|
102
|
+
|
|
103
|
+
# 5. Split Team 2 vertically → Team 3 (bottom 50%)
|
|
104
|
+
PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" \
|
|
105
|
+
-P -F '#{pane_id}' \
|
|
106
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 3'")
|
|
107
|
+
|
|
108
|
+
# 6. Split left pane vertically: Prompt History (top 45%) | Controller (bottom 55%)
|
|
109
|
+
PANE_CTRL=$(tmux split-window -v -p 55 -t "$PANE_LEFT" -c "$PROJECT_ROOT" \
|
|
110
|
+
-P -F '#{pane_id}')
|
|
111
|
+
|
|
112
|
+
# 7. Start Prompt History in left-top
|
|
113
|
+
tmux send-keys -t "$PANE_LEFT" "bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"" Enter
|
|
114
|
+
|
|
115
|
+
# 8. Start Dashboard in middle pane
|
|
116
|
+
tmux send-keys -t "$PANE_MID" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
|
|
117
|
+
|
|
118
|
+
# 9. Controller pane — ready for Claude/Codex
|
|
119
|
+
tmux send-keys -t "$PANE_CTRL" "unset npm_config_prefix 2>/dev/null" Enter
|
|
120
|
+
tmux send-keys -t "$PANE_CTRL" "clear" Enter
|
|
121
|
+
|
|
122
|
+
# Pane titles
|
|
123
|
+
tmux select-pane -t "$PANE_LEFT" -T "Prompt History"
|
|
124
|
+
tmux select-pane -t "$PANE_CTRL" -T "Controller"
|
|
125
|
+
tmux select-pane -t "$PANE_MID" -T "Dashboard"
|
|
126
|
+
tmux select-pane -t "$PANE_T1" -T "TEAM 1"
|
|
127
|
+
tmux select-pane -t "$PANE_T2" -T "TEAM 2"
|
|
128
|
+
tmux select-pane -t "$PANE_T3" -T "TEAM 3"
|
|
129
|
+
|
|
130
|
+
# Focus on Controller
|
|
131
|
+
tmux select-pane -t "$PANE_CTRL"
|
|
72
132
|
|
|
73
133
|
# ══════════════════════════════════════════
|
|
74
|
-
#
|
|
134
|
+
# Solo Mode: 3-column layout (5 panes)
|
|
75
135
|
# ══════════════════════════════════════════
|
|
136
|
+
else
|
|
76
137
|
|
|
77
|
-
# 1. Create session →
|
|
78
|
-
PANE_DASHBOARD=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50 \
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# 2. Split
|
|
83
|
-
PANE_MONITOR=$(tmux split-window -h -p
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# 3. Split Dashboard vertically (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# 4. Split Monitor
|
|
93
|
-
PANE_AGENT=$(tmux split-window -v -p 85 -t "$PANE_MONITOR" -c "$PROJECT_ROOT" \
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# 5.
|
|
97
|
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
tmux
|
|
104
|
-
tmux
|
|
105
|
-
|
|
106
|
-
#
|
|
107
|
-
tmux select-pane -t "$
|
|
108
|
-
|
|
109
|
-
tmux select-pane -t "$PANE_CONTROL" -T "Control"
|
|
110
|
-
tmux select-pane -t "$PANE_AGENT" -T "Agent Session"
|
|
111
|
-
tmux select-pane -t "$PANE_EVAL" -T "Eval Review"
|
|
138
|
+
# 1. Create session → Dashboard pane
|
|
139
|
+
PANE_DASHBOARD=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50 \
|
|
140
|
+
-P -F '#{pane_id}' \
|
|
141
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"'")
|
|
142
|
+
|
|
143
|
+
# 2. Split horizontally: Left 40% | Right 60%
|
|
144
|
+
PANE_MONITOR=$(tmux split-window -h -p 60 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" \
|
|
145
|
+
-P -F '#{pane_id}' \
|
|
146
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
|
|
147
|
+
|
|
148
|
+
# 3. Split Dashboard vertically: Dashboard (top 70%) | Prompt History (bottom 30%)
|
|
149
|
+
PANE_HISTORY=$(tmux split-window -v -p 30 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" \
|
|
150
|
+
-P -F '#{pane_id}' \
|
|
151
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
|
|
152
|
+
|
|
153
|
+
# 4. Split Monitor: Monitor (top 15%) | Agent Session (bottom 85%)
|
|
154
|
+
PANE_AGENT=$(tmux split-window -v -p 85 -t "$PANE_MONITOR" -c "$PROJECT_ROOT" \
|
|
155
|
+
-P -F '#{pane_id}')
|
|
156
|
+
|
|
157
|
+
# 5. Agent Session — empty shell ready for claude
|
|
158
|
+
tmux send-keys -t "$PANE_AGENT" "unset npm_config_prefix 2>/dev/null" Enter
|
|
159
|
+
tmux send-keys -t "$PANE_AGENT" "clear" Enter
|
|
160
|
+
|
|
161
|
+
# Pane titles
|
|
162
|
+
tmux select-pane -t "$PANE_DASHBOARD" -T "Dashboard"
|
|
163
|
+
tmux select-pane -t "$PANE_MONITOR" -T "Monitor"
|
|
164
|
+
tmux select-pane -t "$PANE_HISTORY" -T "Prompt History"
|
|
165
|
+
tmux select-pane -t "$PANE_AGENT" -T "Agent Session"
|
|
166
|
+
|
|
167
|
+
# Focus on Agent Session
|
|
168
|
+
tmux select-pane -t "$PANE_AGENT"
|
|
169
|
+
fi
|
|
112
170
|
|
|
171
|
+
# ── Common settings ──
|
|
113
172
|
tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
|
|
114
173
|
tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
115
174
|
|
|
116
|
-
# ── Focus on Control pane (where user types) ──
|
|
117
|
-
tmux select-pane -t "$PANE_CONTROL"
|
|
118
|
-
|
|
119
175
|
# ── Attach ──
|
|
120
176
|
if [ "$DETACH" = true ]; then
|
|
121
177
|
echo ""
|
|
122
178
|
echo "Session created. Attach: tmux attach -t $SESSION_NAME"
|
|
179
|
+
echo "Layout ready"
|
|
123
180
|
else
|
|
124
181
|
if [ -n "${TMUX:-}" ]; then
|
|
125
182
|
tmux switch-client -t "$SESSION_NAME"
|
|
126
183
|
else
|
|
127
184
|
echo ""
|
|
128
|
-
echo "Launching
|
|
129
|
-
echo "
|
|
130
|
-
echo " Control (left↓) : Manual commands (next/retry/stop/log)"
|
|
131
|
-
echo " Monitor (right↑) : Agent lifecycle (compact)"
|
|
132
|
-
echo " Agent Session (right↔): claude --dangerously-skip-permissions"
|
|
133
|
-
echo " Eval Review (right↓): Evaluation summaries"
|
|
185
|
+
echo "Launching Harness Studio ($MODE mode)..."
|
|
186
|
+
echo " Stop: bash scripts/harness-tmux.sh --kill"
|
|
134
187
|
echo ""
|
|
135
188
|
tmux attach -t "$SESSION_NAME"
|
|
136
189
|
fi
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-user-prompt-submit.sh — UserPromptSubmit hook (
|
|
3
|
-
# 핵심 상태 + 라우팅 지시만 주입.
|
|
2
|
+
# harness-user-prompt-submit.sh — UserPromptSubmit hook (v5 unified)
|
|
3
|
+
# 핵심 상태 + 라우팅 지시만 주입. Solo/Team 통합.
|
|
4
4
|
set -e
|
|
5
5
|
|
|
6
6
|
INPUT=$(cat)
|
|
@@ -64,27 +64,34 @@ current_agent=${CURRENT_AGENT} (running) 인데 /harness-${REQUESTED_SKILL} 호
|
|
|
64
64
|
fi
|
|
65
65
|
fi
|
|
66
66
|
|
|
67
|
-
# ──
|
|
68
|
-
|
|
69
|
-
if [ -f "$
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
# ── Mode 기반 분기 ──
|
|
68
|
+
MODE="solo"
|
|
69
|
+
if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
|
|
70
|
+
MODE=$(jq -r '.mode // "solo"' "$CWD/.harness/progress.json" 2>/dev/null || echo "solo")
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if [ "$MODE" = "team" ]; then
|
|
74
|
+
FEATURE_QUEUE="$CWD/.harness/actions/feature-queue.json"
|
|
75
|
+
T_PASSED=0; T_TOTAL=0; T_FAILED=0
|
|
76
|
+
if [ -f "$FEATURE_QUEUE" ]; then
|
|
77
|
+
T_PASSED=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
78
|
+
T_TOTAL=$(jq '[.queue.ready, (.queue.blocked | keys), (.queue.in_progress | keys), .queue.passed, .queue.failed] | flatten | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
79
|
+
T_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
80
|
+
fi
|
|
73
81
|
|
|
74
82
|
cat <<EOF
|
|
75
|
-
[harness
|
|
83
|
+
[harness] team | ${T_PASSED}/${T_TOTAL} passed | ${T_FAILED} failed
|
|
76
84
|
|
|
77
|
-
##
|
|
78
|
-
- 3 Agent Teams이
|
|
79
|
-
-
|
|
80
|
-
- /harness-
|
|
81
|
-
- 할 수 있는 것: 코드 리뷰, 아키텍처 결정, failed feature 분석, requeue 판단
|
|
85
|
+
## Team Mode Active
|
|
86
|
+
- 3 Agent Teams이 자율적으로 Gen→Eval 루프 실행 중 (max 5 retries)
|
|
87
|
+
- 역할: 모니터링, 실패 대응, 수동 개입
|
|
88
|
+
- /harness-stop → 중단 | /harness-solo → Solo 전환
|
|
82
89
|
- skip: "harness skip" 시 일반 대화
|
|
83
90
|
EOF
|
|
84
91
|
exit 0
|
|
85
92
|
fi
|
|
86
93
|
|
|
87
|
-
# ── Compact context 주입 (
|
|
94
|
+
# ── Compact context 주입 (solo/paused mode) ──
|
|
88
95
|
cat <<EOF
|
|
89
96
|
[harness] S${SPRINT_NUM} | ${PIPELINE} | agent=${CURRENT_AGENT} (${AGENT_STATUS}) | next=${NEXT_AGENT}
|
|
90
97
|
${CONTEXT_WARNING}
|
|
@@ -25,13 +25,13 @@ disable-model-invocation: false
|
|
|
25
25
|
- `pipeline` → 선택된 파이프라인 (FULLSTACK/FE-ONLY/BE-ONLY)
|
|
26
26
|
- `sprint.number` → `1`, `sprint.status` → `"in_progress"` (신규 파이프라인인 경우에만)
|
|
27
27
|
2. `.harness/progress.log`에 요약 한 줄 추가
|
|
28
|
-
3.
|
|
29
|
-
-
|
|
30
|
-
출력: `"✓ Dispatcher 완료.
|
|
31
|
-
-
|
|
32
|
-
출력: `"✓ Dispatcher 완료.
|
|
28
|
+
3. **모드 감지**: `.harness/progress.json`의 `mode` 필드 확인
|
|
29
|
+
- **`"team"`**: Team Mode 활성. 별도 안내 없이 즉시 STOP.
|
|
30
|
+
출력: `"✓ Dispatcher 완료. Team Mode가 자율 실행 중입니다."`
|
|
31
|
+
- **`"solo"` / `"paused"`**: Solo Mode.
|
|
32
|
+
출력: `"✓ Dispatcher 완료. 다음: /harness-solo 또는 프롬프트로 계속하세요."`
|
|
33
33
|
4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
34
|
-
-
|
|
34
|
+
- Team 모드에서는 `/harness-team 실행하시겠습니까?` 같은 **질문을 하지 않는다**. Teams는 이미 실행 중이거나 사용자가 별도로 시작한다.
|
|
35
35
|
|
|
36
36
|
## Auto-Routing (UserPromptSubmit Hook)
|
|
37
37
|
|
|
@@ -60,9 +60,9 @@ disable-model-invocation: true
|
|
|
60
60
|
5. `actions/api-contract.json` — 기대 API 동작
|
|
61
61
|
6. `.harness/progress.json`
|
|
62
62
|
|
|
63
|
-
##
|
|
63
|
+
## Feature-Level Mode (Team Mode)
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
Team Mode에서 Team Worker가 호출할 때, 프롬프트에 `FEATURE_ID`가 지정된다.
|
|
66
66
|
|
|
67
67
|
### Feature-Level Rules
|
|
68
68
|
- `feature-list.json`에서 **지정된 FEATURE_ID의 AC만** 검증
|
|
@@ -43,9 +43,9 @@ disable-model-invocation: true
|
|
|
43
43
|
|
|
44
44
|
**Backend 통합 러너가 동작 중이어야 함.** Gateway 미응답 시 → STOP.
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## Feature-Level Mode (Team Mode)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
Team Mode에서 Team Worker가 호출할 때, 프롬프트에 `FEATURE_ID`가 지정된다.
|
|
49
49
|
|
|
50
50
|
### Feature-Level Rules
|
|
51
51
|
- `feature-list.json`에서 **지정된 FEATURE_ID만** 필터하여 구현
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-control.sh — Control Center 프롬프트 (하단)
|
|
3
|
-
# 사용자 명령을 받아 Agent Session 관리 + progress.log 기록
|
|
4
|
-
#
|
|
5
|
-
# Commands:
|
|
6
|
-
# next / n 다음 에이전트 세션 시작
|
|
7
|
-
# retry / r 현재 에이전트 재실행
|
|
8
|
-
# stop 에이전트 세션 중지
|
|
9
|
-
# log <message> progress.log에 수동 메모 추가
|
|
10
|
-
# clear-fail failure 상태 클리어
|
|
11
|
-
# status / s 대시보드 즉시 갱신 (상단 pane)
|
|
12
|
-
# help / h 도움말
|
|
13
|
-
# quit / q 종료
|
|
14
|
-
#
|
|
15
|
-
# Usage: bash scripts/harness-control.sh [project-root]
|
|
16
|
-
|
|
17
|
-
set -uo pipefail
|
|
18
|
-
|
|
19
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
20
|
-
|
|
21
|
-
PROJECT_ROOT="${1:-}"
|
|
22
|
-
if [ -z "$PROJECT_ROOT" ]; then
|
|
23
|
-
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
24
|
-
PROJECT_ROOT="$(resolve_harness_root ".")" || {
|
|
25
|
-
echo "[control] .harness/ not found."
|
|
26
|
-
exit 1
|
|
27
|
-
}
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
31
|
-
PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
|
|
32
|
-
|
|
33
|
-
BOLD="\033[1m"
|
|
34
|
-
DIM="\033[2m"
|
|
35
|
-
GREEN="\033[32m"
|
|
36
|
-
YELLOW="\033[33m"
|
|
37
|
-
RED="\033[31m"
|
|
38
|
-
CYAN="\033[36m"
|
|
39
|
-
RESET="\033[0m"
|
|
40
|
-
|
|
41
|
-
# ── Agent Session pane target (find by title) ──
|
|
42
|
-
find_agent_pane() {
|
|
43
|
-
tmux list-panes -t harness-studio -F '#{pane_id} #{pane_title}' 2>/dev/null \
|
|
44
|
-
| grep "Agent Session" | awk '{print $1}' | head -1
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
48
|
-
|
|
49
|
-
# Build claude command from config
|
|
50
|
-
build_claude_cmd() {
|
|
51
|
-
local agent="$1"
|
|
52
|
-
local model="opus"
|
|
53
|
-
if [ -f "$CONFIG" ]; then
|
|
54
|
-
model=$(jq -r ".agents[\"${agent}\"].model // \"opus\"" "$CONFIG" 2>/dev/null)
|
|
55
|
-
fi
|
|
56
|
-
echo "claude --dangerously-skip-permissions --model ${model}"
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
cmd_log() {
|
|
60
|
-
local msg="$1"
|
|
61
|
-
if [ -z "$msg" ]; then
|
|
62
|
-
echo -e " ${RED}Usage: log <message>${RESET}"
|
|
63
|
-
return
|
|
64
|
-
fi
|
|
65
|
-
local ts
|
|
66
|
-
ts=$(date +"%Y-%m-%d")
|
|
67
|
-
echo "${ts} | manual | note | ${msg}" >> "$PROGRESS_LOG"
|
|
68
|
-
echo -e " ${GREEN}Logged:${RESET} ${msg}"
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
cmd_clear_fail() {
|
|
72
|
-
if [ ! -f "$PROGRESS" ]; then return; fi
|
|
73
|
-
jq '.failure = {agent: null, location: null, message: null, retry_target: null} |
|
|
74
|
-
.sprint.status = "in_progress" |
|
|
75
|
-
.sprint.retry_count = 0 |
|
|
76
|
-
.agent_status = "completed"' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
77
|
-
echo -e " ${GREEN}Failure state cleared.${RESET}"
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
cmd_next() {
|
|
81
|
-
local pane
|
|
82
|
-
pane=$(find_agent_pane)
|
|
83
|
-
if [ -z "$pane" ]; then
|
|
84
|
-
echo -e " ${RED}Agent Session pane not found.${RESET}"
|
|
85
|
-
return
|
|
86
|
-
fi
|
|
87
|
-
|
|
88
|
-
local next_agent
|
|
89
|
-
next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
90
|
-
if [ "$next_agent" = "none" ] || [ "$next_agent" = "null" ]; then
|
|
91
|
-
echo -e " ${YELLOW}No next agent in queue.${RESET}"
|
|
92
|
-
return
|
|
93
|
-
fi
|
|
94
|
-
|
|
95
|
-
local claude_cmd
|
|
96
|
-
claude_cmd=$(build_claude_cmd "$next_agent")
|
|
97
|
-
|
|
98
|
-
echo -e " ${CYAN}Starting ${next_agent}...${RESET}"
|
|
99
|
-
echo -e " ${DIM}${claude_cmd}${RESET}"
|
|
100
|
-
|
|
101
|
-
local ts
|
|
102
|
-
ts=$(date +"%Y-%m-%d")
|
|
103
|
-
echo "${ts} | manual | start-agent | ${next_agent}" >> "$PROGRESS_LOG"
|
|
104
|
-
|
|
105
|
-
# Launch claude in Agent Session pane with the agent's prompt
|
|
106
|
-
tmux send-keys -t "$pane" "${claude_cmd} -p '하네스 엔지니어링 시작'" Enter
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
cmd_retry() {
|
|
110
|
-
local pane
|
|
111
|
-
pane=$(find_agent_pane)
|
|
112
|
-
if [ -z "$pane" ]; then
|
|
113
|
-
echo -e " ${RED}Agent Session pane not found.${RESET}"
|
|
114
|
-
return
|
|
115
|
-
fi
|
|
116
|
-
|
|
117
|
-
local current_agent
|
|
118
|
-
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
119
|
-
if [ "$current_agent" = "none" ] || [ "$current_agent" = "null" ]; then
|
|
120
|
-
echo -e " ${YELLOW}No current agent to retry.${RESET}"
|
|
121
|
-
return
|
|
122
|
-
fi
|
|
123
|
-
|
|
124
|
-
local claude_cmd
|
|
125
|
-
claude_cmd=$(build_claude_cmd "$current_agent")
|
|
126
|
-
|
|
127
|
-
echo -e " ${CYAN}Retrying ${current_agent}...${RESET}"
|
|
128
|
-
local ts
|
|
129
|
-
ts=$(date +"%Y-%m-%d")
|
|
130
|
-
echo "${ts} | manual | retry | ${current_agent}" >> "$PROGRESS_LOG"
|
|
131
|
-
tmux send-keys -t "$pane" "${claude_cmd} -p '하네스 엔지니어링 시작'" Enter
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
cmd_stop() {
|
|
135
|
-
local pane
|
|
136
|
-
pane=$(find_agent_pane)
|
|
137
|
-
if [ -z "$pane" ]; then
|
|
138
|
-
echo -e " ${RED}Agent Session pane not found.${RESET}"
|
|
139
|
-
return
|
|
140
|
-
fi
|
|
141
|
-
|
|
142
|
-
echo -e " ${YELLOW}Sending Ctrl+C to Agent Session...${RESET}"
|
|
143
|
-
tmux send-keys -t "$pane" C-c
|
|
144
|
-
local ts
|
|
145
|
-
ts=$(date +"%Y-%m-%d")
|
|
146
|
-
echo "${ts} | manual | stop | user-initiated" >> "$PROGRESS_LOG"
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
show_help() {
|
|
150
|
-
echo ""
|
|
151
|
-
echo -e " ${BOLD}Commands${RESET}"
|
|
152
|
-
echo -e " ${CYAN}next${RESET} / ${CYAN}n${RESET} Start next agent"
|
|
153
|
-
echo -e " ${CYAN}retry${RESET} / ${CYAN}r${RESET} Retry current agent"
|
|
154
|
-
echo -e " ${CYAN}stop${RESET} Send Ctrl+C to agent"
|
|
155
|
-
echo -e " ${CYAN}log${RESET} <message> Add note to progress.log"
|
|
156
|
-
echo -e " ${CYAN}clear-fail${RESET} Clear failure state"
|
|
157
|
-
echo -e " ${CYAN}help${RESET} / ${CYAN}h${RESET} Show this help"
|
|
158
|
-
echo -e " ${CYAN}quit${RESET} / ${CYAN}q${RESET} Exit"
|
|
159
|
-
echo ""
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
# ── Main ──
|
|
163
|
-
echo ""
|
|
164
|
-
echo -e " ${BOLD}Harness Control${RESET} ${DIM}(type 'help' for commands)${RESET}"
|
|
165
|
-
echo ""
|
|
166
|
-
|
|
167
|
-
while true; do
|
|
168
|
-
echo -ne " ${BOLD}harness>${RESET} "
|
|
169
|
-
read -r input || exit 0
|
|
170
|
-
input=$(echo "$input" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
171
|
-
|
|
172
|
-
case "$input" in
|
|
173
|
-
next|n) cmd_next ;;
|
|
174
|
-
retry|r) cmd_retry ;;
|
|
175
|
-
stop) cmd_stop ;;
|
|
176
|
-
log\ *) cmd_log "${input#log }" ;;
|
|
177
|
-
clear-fail) cmd_clear_fail ;;
|
|
178
|
-
help|h) show_help ;;
|
|
179
|
-
quit|q) echo -e " ${DIM}Goodbye.${RESET}"; exit 0 ;;
|
|
180
|
-
"") ;; # empty — just re-prompt
|
|
181
|
-
*) echo -e " ${DIM}Logging as note.${RESET}"; cmd_log "$input" ;;
|
|
182
|
-
esac
|
|
183
|
-
done
|