@walwal-harness/cli 4.0.0 → 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.
@@ -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
- # stdin: Claude Code JSON payload (model, context_window, cost 등)
5
- # stdout: 상태 문자열 (Claude Code가 터미널 하단에 렌더링)
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
- # Format: [S1] FULL | >backend | sonnet | 2/5 feat | ctx 45% | $1.23
111
- if [ -n "$model_short" ]; then
112
- echo "[S${sprint_num}] ${pl} | ${status_icon}${agent_short}${retry_str} | ${model_short} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
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
- echo "[S${sprint_num}] ${pl} | ${status_icon}${agent_short}${retry_str} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
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
@@ -1,23 +1,30 @@
1
1
  #!/bin/bash
2
- # harness-tmux.sh — Harness Studio tmux 5-pane 레이아웃
2
+ # harness-tmux.sh — Unified Harness Studio tmux layout
3
3
  #
4
- # ┌─────────────────────────┬────────────────────────┐
5
- # │ Dashboard (auto-refresh)│ Monitor (compact) │
6
- # │ ├────────────────────────┤
7
- # │ │ Agent Session (claude) │
8
- # ├─────────────────────────┤ │
9
- # │ Control (harness> _) ├────────────────────────┤
10
- # │ │ Eval Review │
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
- # Usage:
14
- # bash scripts/harness-tmux.sh [project-root]
15
- # bash scripts/harness-tmux.sh /path/to/project --ai
15
+ # Solo Mode (--solo):
16
+ # ┌──────────────┬──────────────┬──────────────┐
17
+ # │ Dashboard │ Monitor │ Agent │
18
+ # │ │ (compact) │ Session │
19
+ # ├──────────────┤ │ │
20
+ # │ Prompt │ │ │
21
+ # │ History │ │ │
22
+ # └──────────────┴──────────────┴──────────────┘
16
23
  #
17
- # Options:
18
- # --ai Eval Review에서 claude -p AI 요약
19
- # --detach 세션 생성 후 attach 하지 않음
20
- # --kill 기존 harness-studio 세션 종료
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
- USE_AI=""
35
+ MODE=""
29
36
  DETACH=false
30
37
 
31
38
  for arg in "$@"; do
32
39
  case "$arg" in
33
- --ai) USE_AI="--ai" ;;
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
- # ── Resolve Claude command ──
64
- HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
65
- CLAUDE_CMD="claude --dangerously-skip-permissions"
66
- if [ -f "$HANDOFF" ]; then
67
- _model=$(jq -r '.model // empty' "$HANDOFF" 2>/dev/null)
68
- if [ -n "$_model" ] && [ "$_model" != "null" ]; then
69
- CLAUDE_CMD="$CLAUDE_CMD --model $_model"
70
- fi
71
- fi
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
- # Build layout using explicit pane IDs
134
+ # Solo Mode: 3-column layout (5 panes)
75
135
  # ══════════════════════════════════════════
136
+ else
76
137
 
77
- # 1. Create session → capture Dashboard pane ID
78
- PANE_DASHBOARD=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50 \
79
- -P -F '#{pane_id}' \
80
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"'")
81
-
82
- # 2. Split Dashboard horizontally (55% left / 45% right) → Monitor pane
83
- PANE_MONITOR=$(tmux split-window -h -p 45 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" \
84
- -P -F '#{pane_id}' \
85
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
86
-
87
- # 3. Split Dashboard vertically (75% top / 25% bottom) → Control pane
88
- PANE_CONTROL=$(tmux split-window -v -p 25 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" \
89
- -P -F '#{pane_id}' \
90
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-control.sh\" \"${PROJECT_ROOT}\"'")
91
-
92
- # 4. Split Monitor vertically (15% top / 85% bottom) → Agent Session pane
93
- PANE_AGENT=$(tmux split-window -v -p 85 -t "$PANE_MONITOR" -c "$PROJECT_ROOT" \
94
- -P -F '#{pane_id}')
95
-
96
- # 5. Split Agent Session vertically (55% top / 45% bottom) → Eval Review pane
97
- PANE_EVAL=$(tmux split-window -v -p 45 -t "$PANE_AGENT" -c "$PROJECT_ROOT" \
98
- -P -F '#{pane_id}' \
99
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-eval-watcher.sh\" \"${PROJECT_ROOT}\" ${USE_AI}'")
100
-
101
- # 6. Agent Session — leave as empty shell, ready for Control to launch claude
102
- # Pre-unset nvm conflict variable
103
- tmux send-keys -t "$PANE_AGENT" "unset npm_config_prefix 2>/dev/null" Enter
104
- tmux send-keys -t "$PANE_AGENT" "clear" Enter
105
-
106
- # ── Pane titles ──
107
- tmux select-pane -t "$PANE_DASHBOARD" -T "Dashboard"
108
- tmux select-pane -t "$PANE_MONITOR" -T "Monitor"
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 harness-studio..."
129
- echo " Dashboard (left↑) : Auto-refresh progress"
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 (compact v3.2)
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
- # ── v4 Parallel Mode 감지 ──
68
- FEATURE_QUEUE="$CWD/.harness/actions/feature-queue.json"
69
- if [ -f "$FEATURE_QUEUE" ]; then
70
- V4_PASSED=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
71
- V4_TOTAL=$(jq '[.queue.ready, (.queue.blocked | keys), (.queue.in_progress | keys), .queue.passed, .queue.failed] | flatten | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
72
- V4_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
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-v4] ${V4_PASSED}/${V4_TOTAL} features passed | ${V4_FAILED} failed
83
+ [harness] team | ${T_PASSED}/${T_TOTAL} passed | ${T_FAILED} failed
76
84
 
77
- ## v4 Parallel Mode Active
78
- - 3 Agent Teams이 feature-queue에서 자율적으로 Gen→Eval 루프 실행 중
79
- - 당신은 **오케스트레이터** 역할: 대시보드 모니터링, 실패 대응, 수동 개입
80
- - /harness-generator-*, /harness-evaluator-* 스킬 호출 금지 (Teams가 처리)
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 주입 (v3 mode) ──
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. **v4 모드 감지**: `.harness/actions/feature-queue.json` 파일이 존재하는지 확인
29
- - **존재**: v4 Parallel Agent Teams 활성 상태. 별도 안내 없이 즉시 STOP.
30
- 출력: `"✓ Dispatcher 완료. Agent Teams가 자율 실행 중입니다."`
31
- - **미존재**: v3 Classic 모드.
32
- 출력: `"✓ Dispatcher 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
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
- - v4 모드에서는 `/harness-team 실행하시겠습니까?` 같은 **질문을 하지 않는다**. Teams는 이미 실행 중이거나 사용자가 별도로 시작한다.
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
- ## v4 Feature-Level Mode (Parallel Agent Teams)
63
+ ## Feature-Level Mode (Team Mode)
64
64
 
65
- v4에서 Team Worker가 `claude -p`로 호출할 때, 프롬프트에 `FEATURE_ID`가 지정된다.
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
- ## v4 Feature-Level Mode (Parallel Agent Teams)
46
+ ## Feature-Level Mode (Team Mode)
47
47
 
48
- v4에서 Team Worker가 `claude -p`로 호출할 때, 프롬프트에 `FEATURE_ID`가 지정된다.
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