@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,8 +1,8 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-statusline.sh — Claude Code statusline hook
|
|
2
|
+
# harness-statusline.sh — Claude Code statusline hook (v5 unified)
|
|
3
3
|
# 터미널 하단에 항상 고정되는 1줄 compact 상태 표시.
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# Solo: [S1] FULL | solo | >backend | sonnet | 2/5 feat | ctx 45% | $1.23
|
|
5
|
+
# Team: [S1] FULL | team | T1:gen T2:eval T3:idle | 2/5 feat | ctx 45% | $1.23
|
|
6
6
|
|
|
7
7
|
# Read Claude Code session data from stdin
|
|
8
8
|
input=$(cat)
|
|
@@ -39,6 +39,7 @@ current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
|
39
39
|
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
|
|
40
40
|
next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
41
41
|
retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS" 2>/dev/null)
|
|
42
|
+
harness_mode=$(jq -r '.mode // "solo"' "$PROGRESS" 2>/dev/null)
|
|
42
43
|
|
|
43
44
|
# Pipeline short name
|
|
44
45
|
case "$pipeline" in
|
|
@@ -106,10 +107,32 @@ if [ -f "$CONFIG" ] && [ "$active_agent" != "none" ] && [ "$active_agent" != "nu
|
|
|
106
107
|
fi
|
|
107
108
|
fi
|
|
108
109
|
|
|
109
|
-
# Build compact status line
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
# Build compact status line based on mode
|
|
111
|
+
FEATURE_QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
112
|
+
|
|
113
|
+
if [ "$harness_mode" = "team" ] && [ -f "$FEATURE_QUEUE" ]; then
|
|
114
|
+
# Team mode: show team status instead of single agent
|
|
115
|
+
team_info=""
|
|
116
|
+
for i in 1 2 3; do
|
|
117
|
+
t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$FEATURE_QUEUE" 2>/dev/null)
|
|
118
|
+
t_feature=$(jq -r ".teams[\"$i\"].feature // \"\"" "$FEATURE_QUEUE" 2>/dev/null)
|
|
119
|
+
if [ "$t_status" = "busy" ] && [ -n "$t_feature" ] && [ "$t_feature" != "null" ]; then
|
|
120
|
+
t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$FEATURE_QUEUE" 2>/dev/null)
|
|
121
|
+
team_info+="T${i}:${t_phase} "
|
|
122
|
+
else
|
|
123
|
+
team_info+="T${i}:- "
|
|
124
|
+
fi
|
|
125
|
+
done
|
|
126
|
+
t_passed=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
127
|
+
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)
|
|
128
|
+
|
|
129
|
+
echo "[S${sprint_num}] ${pl} | team | ${team_info}| ${t_passed}/${t_total} feat | ctx ${context_pct}% | \$${cost}"
|
|
113
130
|
else
|
|
114
|
-
|
|
131
|
+
# Solo/paused mode: show single agent status
|
|
132
|
+
mode_tag="${harness_mode}"
|
|
133
|
+
if [ -n "$model_short" ]; then
|
|
134
|
+
echo "[S${sprint_num}] ${pl} | ${mode_tag} | ${status_icon}${agent_short}${retry_str} | ${model_short} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
|
|
135
|
+
else
|
|
136
|
+
echo "[S${sprint_num}] ${pl} | ${mode_tag} | ${status_icon}${agent_short}${retry_str} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
|
|
137
|
+
fi
|
|
115
138
|
fi
|
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,137 @@ 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"
|
|
180
|
+
elif ! tty -s 2>/dev/null; then
|
|
181
|
+
# Non-terminal environment (e.g., Claude Code agent session)
|
|
182
|
+
# Try opening Terminal.app on macOS, otherwise detach silently
|
|
183
|
+
if [ "$(uname)" = "Darwin" ]; then
|
|
184
|
+
osascript -e "tell application \"Terminal\" to do script \"tmux attach -t $SESSION_NAME\"" 2>/dev/null && \
|
|
185
|
+
echo "OPENED_TERMINAL=true" || echo "Layout ready"
|
|
186
|
+
else
|
|
187
|
+
echo "Session created (non-terminal). Attach: tmux attach -t $SESSION_NAME"
|
|
188
|
+
echo "Layout ready"
|
|
189
|
+
fi
|
|
123
190
|
else
|
|
124
191
|
if [ -n "${TMUX:-}" ]; then
|
|
125
192
|
tmux switch-client -t "$SESSION_NAME"
|
|
126
193
|
else
|
|
127
194
|
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"
|
|
195
|
+
echo "Launching Harness Studio ($MODE mode)..."
|
|
196
|
+
echo " Stop: bash scripts/harness-tmux.sh --kill"
|
|
134
197
|
echo ""
|
|
135
198
|
tmux attach -t "$SESSION_NAME"
|
|
136
199
|
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
|