@walwal-harness/cli 4.0.0-beta.3 → 4.0.0-beta.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "4.0.0-beta.3",
3
+ "version": "4.0.0-beta.5",
4
4
  "description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer (requirements refinement). Supports React and Flutter FE stacks.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -9,7 +9,9 @@
9
9
  "postinstall": "node bin/init.js --auto",
10
10
  "init": "node bin/init.js",
11
11
  "scan": "bash scripts/scan-project.sh .",
12
- "reset": "node bin/init.js --force"
12
+ "reset": "node bin/init.js --force",
13
+ "studio": "bash scripts/harness-studio.sh",
14
+ "studio:kill": "bash scripts/harness-studio.sh --kill"
13
15
  },
14
16
  "keywords": [
15
17
  "harness-engineering",
@@ -0,0 +1,126 @@
1
+ #!/bin/bash
2
+ # harness-prompt-history.sh — Prompt History pane for v4 tmux layout
3
+ # progress.log + audit.log에서 유저 프롬프트/에이전트 활동 히스토리를 표시
4
+ # Usage: bash scripts/harness-prompt-history.sh [project-root]
5
+
6
+ set -uo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
10
+
11
+ PROJECT_ROOT="${1:-}"
12
+ if [ -z "$PROJECT_ROOT" ]; then
13
+ PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[history] .harness/ not found."; exit 1; }
14
+ fi
15
+
16
+ PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
17
+ AUDIT_LOG="$PROJECT_ROOT/.harness/actions/audit.log"
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
+ echo -e "${BOLD}PROMPT HISTORY${RESET} ${DIM}$(date +%H:%M:%S)${RESET}"
30
+ echo ""
31
+ }
32
+
33
+ render_progress_log() {
34
+ if [ ! -f "$PROGRESS_LOG" ]; then
35
+ echo -e " ${DIM}(no progress.log yet)${RESET}"
36
+ return
37
+ fi
38
+
39
+ grep -v '^#' "$PROGRESS_LOG" 2>/dev/null | grep -v '^$' | tail -20 | while IFS= read -r line; do
40
+ local ts agent action detail
41
+ ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
42
+ agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
43
+ action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
44
+ detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
45
+
46
+ local short_ts icon color
47
+ short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "$ts")
48
+
49
+ case "$agent" in
50
+ dispatcher*) icon="▸" ; color="$MAGENTA" ;;
51
+ brainstormer) icon="◇" ; color="$CYAN" ;;
52
+ planner*) icon="□" ; color="$YELLOW" ;;
53
+ generator*) icon="▶" ; color="$GREEN" ;;
54
+ eval*) icon="✦" ; color="$RED" ;;
55
+ user|manual) icon="★" ; color="$BOLD" ;;
56
+ team*) icon="⊕" ; color="$CYAN" ;;
57
+ *) icon="·" ; color="$DIM" ;;
58
+ esac
59
+
60
+ if [ ${#detail} -gt 40 ]; then detail="${detail:0:38}.."; fi
61
+
62
+ printf " %b%b%b %b%-8s%b %b%s%b %b%s%b\n" \
63
+ "$color" "$icon" "$RESET" \
64
+ "$DIM" "$short_ts" "$RESET" \
65
+ "$RESET" "$agent" "$RESET" \
66
+ "$DIM" "$action" "$RESET"
67
+ if [ -n "$detail" ] && [ "$detail" != " " ]; then
68
+ echo -e " ${DIM}${detail}${RESET}"
69
+ fi
70
+ done
71
+ }
72
+
73
+ render_audit_tail() {
74
+ if [ ! -f "$AUDIT_LOG" ]; then return; fi
75
+
76
+ echo ""
77
+ echo -e "${BOLD}AUDIT${RESET}"
78
+
79
+ grep -v '^#' "$AUDIT_LOG" 2>/dev/null | grep -v '^$' | tail -10 | while IFS= read -r line; do
80
+ local ts agent action status target
81
+ ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
82
+ agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
83
+ action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
84
+ status=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
85
+ target=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$5); print $5}')
86
+
87
+ local color="$RESET" icon="·"
88
+ case "$status" in
89
+ start) color="$CYAN"; icon="▶" ;;
90
+ complete) color="$GREEN"; icon="✓" ;;
91
+ fail) color="$RED"; icon="✗" ;;
92
+ pass) color="$GREEN"; icon="✓" ;;
93
+ skip) color="$DIM"; icon="–" ;;
94
+ esac
95
+
96
+ local short_ts
97
+ short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "$ts")
98
+
99
+ if [ ${#target} -gt 25 ]; then target="${target:0:23}.."; fi
100
+
101
+ printf " %b%b%b %b%s%b %-10s %b%s%b\n" \
102
+ "$color" "$icon" "$RESET" \
103
+ "$DIM" "$short_ts" "$RESET" \
104
+ "$agent" \
105
+ "$DIM" "$target" "$RESET"
106
+ done
107
+ }
108
+
109
+ render_all() {
110
+ render_header
111
+ render_progress_log
112
+ render_audit_tail
113
+ }
114
+
115
+ # ── Main loop ──
116
+ tput civis 2>/dev/null
117
+ trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
118
+ clear
119
+
120
+ while true; do
121
+ buf=$(render_all 2>&1)
122
+ tput cup 0 0 2>/dev/null
123
+ echo "$buf"
124
+ tput ed 2>/dev/null
125
+ sleep 3
126
+ done
@@ -0,0 +1,143 @@
1
+ #!/bin/bash
2
+ # harness-studio-setup.sh — Claude 세션에서 3-column 레이아웃 자동 구축
3
+ #
4
+ # ┌──────────────┬──────────────┬──────────────┐
5
+ # │ │ Dashboard │ │
6
+ # │ Claude │ (v4 queue) │ Team Monitor│
7
+ # │ (Lead) ├──────────────┤ (lifecycle) │
8
+ # │ │ Command │ │
9
+ # │ │ History │ │
10
+ # └──────────────┴──────────────┴──────────────┘
11
+ #
12
+ # 두 가지 상황을 모두 처리:
13
+ # A) tmux 안에서 실행 → 현재 pane을 split하여 레이아웃 구축
14
+ # B) tmux 밖에서 실행 → 새 tmux 세션 생성, Claude를 좌측 pane에서 재실행
15
+ #
16
+ # Usage:
17
+ # bash scripts/harness-studio-setup.sh [project-root]
18
+ #
19
+ # 이미 구축됐으면 skip (멱등성 보장)
20
+
21
+ set -euo pipefail
22
+
23
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
24
+ SESSION_NAME="harness-studio"
25
+
26
+ PROJECT_ROOT="${1:-}"
27
+ if [ -z "$PROJECT_ROOT" ]; then
28
+ dir="$(pwd)"
29
+ while [ "$dir" != "/" ]; do
30
+ if [ -d "$dir/.harness" ]; then PROJECT_ROOT="$dir"; break; fi
31
+ dir="$(dirname "$dir")"
32
+ done
33
+ fi
34
+
35
+ if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
36
+ echo "[studio] .harness/ not found."
37
+ exit 1
38
+ fi
39
+
40
+ # ── Resolve Claude command ──
41
+ CLAUDE_CMD="claude --dangerously-skip-permissions"
42
+ if [ -f "$PROJECT_ROOT/.harness/handoff.json" ]; then
43
+ _model=$(jq -r '.model // empty' "$PROJECT_ROOT/.harness/handoff.json" 2>/dev/null)
44
+ if [ -n "$_model" ] && [ "$_model" != "null" ]; then
45
+ CLAUDE_CMD="$CLAUDE_CMD --model $_model"
46
+ fi
47
+ fi
48
+
49
+ # ══════════════════════════════════════════
50
+ # Case A: 이미 tmux 안에 있음 → pane split
51
+ # ══════════════════════════════════════════
52
+ if [ -n "${TMUX:-}" ]; then
53
+ # 멱등성: 이미 pane이 3개 이상이면 skip
54
+ PANE_COUNT=$(tmux list-panes | wc -l | tr -d ' ')
55
+ if [ "$PANE_COUNT" -ge 3 ]; then
56
+ echo "[studio] Layout already set up ($PANE_COUNT panes). Skipping."
57
+ exit 0
58
+ fi
59
+
60
+ PANE_CLAUDE=$(tmux display-message -p '#{pane_id}')
61
+ echo "[studio] Setting up 3-column layout (in-place split)..."
62
+
63
+ # Left 35% | Right 65%
64
+ PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_CLAUDE" -c "$PROJECT_ROOT" \
65
+ -P -F '#{pane_id}' \
66
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
67
+
68
+ # Middle 45% | Right 55%
69
+ PANE_RIGHT=$(tmux split-window -h -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
70
+ -P -F '#{pane_id}' \
71
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
72
+
73
+ # Dashboard top 45% | History bottom 55%
74
+ PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
75
+ -P -F '#{pane_id}' \
76
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
77
+
78
+ # Pane titles
79
+ tmux select-pane -t "$PANE_CLAUDE" -T "Lead (Claude)"
80
+ tmux select-pane -t "$PANE_MID" -T "Dashboard"
81
+ tmux select-pane -t "$PANE_HISTORY" -T "Command History"
82
+ tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
83
+
84
+ tmux set-option pane-border-status top 2>/dev/null || true
85
+ tmux set-option pane-border-format " #{pane_title} " 2>/dev/null || true
86
+
87
+ # 포커스를 Claude pane으로 복귀
88
+ tmux select-pane -t "$PANE_CLAUDE"
89
+
90
+ echo "[studio] Layout ready (in-place)."
91
+ exit 0
92
+ fi
93
+
94
+ # ══════════════════════════════════════════
95
+ # Case B: tmux 밖에 있음 → 새 세션 생성
96
+ # ══════════════════════════════════════════
97
+
98
+ # 이미 세션이 있으면 attach만
99
+ if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
100
+ echo "[studio] Session '$SESSION_NAME' already exists. Attaching..."
101
+ echo "[studio] ATTACH_TMUX=$SESSION_NAME"
102
+ exit 0
103
+ fi
104
+
105
+ echo "[studio] Creating new tmux session with 3-column layout..."
106
+
107
+ # 1. 새 세션 → Left pane (Claude 실행)
108
+ PANE_MAIN=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
109
+ -P -F '#{pane_id}')
110
+
111
+ # 2. Left 35% | Right 65%
112
+ PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_MAIN" -c "$PROJECT_ROOT" \
113
+ -P -F '#{pane_id}' \
114
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
115
+
116
+ # 3. Middle 45% | Right 55%
117
+ PANE_RIGHT=$(tmux split-window -h -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
118
+ -P -F '#{pane_id}' \
119
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
120
+
121
+ # 4. Dashboard top 45% | History bottom 55%
122
+ PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
123
+ -P -F '#{pane_id}' \
124
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
125
+
126
+ # 5. Left pane에서 Claude 자동 실행
127
+ tmux send-keys -t "$PANE_MAIN" "unset npm_config_prefix 2>/dev/null" Enter
128
+ tmux send-keys -t "$PANE_MAIN" "clear && $CLAUDE_CMD" Enter
129
+
130
+ # Pane titles
131
+ tmux select-pane -t "$PANE_MAIN" -T "Lead (Claude)"
132
+ tmux select-pane -t "$PANE_MID" -T "Dashboard"
133
+ tmux select-pane -t "$PANE_HISTORY" -T "Command History"
134
+ tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
135
+
136
+ tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
137
+ tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
138
+
139
+ # Focus on Claude pane
140
+ tmux select-pane -t "$PANE_MAIN"
141
+
142
+ echo "[studio] Session '$SESSION_NAME' created."
143
+ echo "[studio] ATTACH_TMUX=$SESSION_NAME"
@@ -0,0 +1,66 @@
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
@@ -0,0 +1,136 @@
1
+ #!/bin/bash
2
+ # harness-tmux-v4.sh — v4 Agent Teams: 원커맨드 실행
3
+ #
4
+ # ┌──────────────┬──────────────┬──────────────┐
5
+ # │ │ Dashboard │ │
6
+ # │ Main Claude │ (v4 queue) │ Team Monitor│
7
+ # │ (Lead) ├──────────────┤ (lifecycle) │
8
+ # │ │ Prompt │ │
9
+ # │ │ History │ │
10
+ # └──────────────┴──────────────┴──────────────┘
11
+ #
12
+ # Usage:
13
+ # bash scripts/harness-tmux-v4.sh # 레이아웃 + Claude 자동 실행 + team-action 자동 시작
14
+ # bash scripts/harness-tmux-v4.sh --no-auto # 레이아웃만 (Claude 수동 실행)
15
+ # bash scripts/harness-tmux-v4.sh --kill # 세션 종료
16
+ #
17
+ # 이것만 기억하세요:
18
+ # bash scripts/harness-tmux-v4.sh
19
+
20
+ set -euo pipefail
21
+
22
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
23
+ SESSION_NAME="harness-v4"
24
+
25
+ PROJECT_ROOT=""
26
+ DETACH=false
27
+ AUTO_START=true
28
+
29
+ for arg in "$@"; do
30
+ case "$arg" in
31
+ --detach) DETACH=true ;;
32
+ --no-auto) AUTO_START=false ;;
33
+ --kill)
34
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "Killed." || echo "No session."
35
+ exit 0
36
+ ;;
37
+ *)
38
+ if [ -d "$arg" ]; then PROJECT_ROOT="$arg"; fi
39
+ ;;
40
+ esac
41
+ done
42
+
43
+ if [ -z "$PROJECT_ROOT" ]; then
44
+ dir="$(pwd)"
45
+ while [ "$dir" != "/" ]; do
46
+ if [ -d "$dir/.harness" ]; then PROJECT_ROOT="$dir"; break; fi
47
+ dir="$(dirname "$dir")"
48
+ done
49
+ fi
50
+
51
+ if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
52
+ echo "Error: .harness/ not found."
53
+ exit 1
54
+ fi
55
+
56
+ echo "Project: $PROJECT_ROOT"
57
+ echo "Session: $SESSION_NAME"
58
+
59
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
60
+
61
+ # ── Resolve Claude command ──
62
+ HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
63
+ CLAUDE_CMD="claude --dangerously-skip-permissions"
64
+ if [ -f "$HANDOFF" ]; then
65
+ _model=$(jq -r '.model // empty' "$HANDOFF" 2>/dev/null)
66
+ if [ -n "$_model" ] && [ "$_model" != "null" ]; then
67
+ CLAUDE_CMD="$CLAUDE_CMD --model $_model"
68
+ fi
69
+ fi
70
+
71
+ # ══════════════════════════════════════════
72
+ # Build 3-column layout
73
+ # ══════════════════════════════════════════
74
+
75
+ # 1. Create session → Left pane (Main Claude Lead)
76
+ PANE_MAIN=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
77
+ -P -F '#{pane_id}')
78
+
79
+ # 2. Split horizontally: Left 35% | Right 65%
80
+ PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_MAIN" -c "$PROJECT_ROOT" \
81
+ -P -F '#{pane_id}')
82
+
83
+ # 3. Split right section: Middle 45% | Right 55%
84
+ PANE_RIGHT=$(tmux split-window -h -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
85
+ -P -F '#{pane_id}' \
86
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
87
+
88
+ # 4. Split middle pane vertically: Dashboard (top 45%) | Prompt History (bottom 55%)
89
+ PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
90
+ -P -F '#{pane_id}' \
91
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
92
+
93
+ # 5. Start dashboard in the middle-top pane
94
+ tmux send-keys -t "$PANE_MID" "bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"" Enter
95
+
96
+ # 6. Main pane — Claude 자동 실행
97
+ tmux send-keys -t "$PANE_MAIN" "unset npm_config_prefix 2>/dev/null" Enter
98
+ tmux send-keys -t "$PANE_MAIN" "clear" Enter
99
+
100
+ if [ "$AUTO_START" = true ]; then
101
+ # Claude 실행 → 시작 후 자동으로 /harness-team-action 전송
102
+ tmux send-keys -t "$PANE_MAIN" "$CLAUDE_CMD" Enter
103
+ # Claude가 초기화될 시간을 준 뒤 team-action 명령 전송
104
+ sleep 3
105
+ tmux send-keys -t "$PANE_MAIN" "/harness-team-action" Enter
106
+ fi
107
+
108
+ # ── Pane titles ──
109
+ tmux select-pane -t "$PANE_MAIN" -T "Lead (Main Claude)"
110
+ tmux select-pane -t "$PANE_MID" -T "Dashboard"
111
+ tmux select-pane -t "$PANE_HISTORY" -T "Prompt History"
112
+ tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
113
+
114
+ tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
115
+ tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
116
+
117
+ # ── Focus on Main pane ──
118
+ tmux select-pane -t "$PANE_MAIN"
119
+
120
+ # ── Attach ──
121
+ if [ "$DETACH" = true ]; then
122
+ echo ""
123
+ echo "Session created. Attach: tmux attach -t $SESSION_NAME"
124
+ else
125
+ if [ -n "${TMUX:-}" ]; then
126
+ tmux switch-client -t "$SESSION_NAME"
127
+ else
128
+ echo ""
129
+ echo "harness-v4 starting..."
130
+ echo ""
131
+ echo " All automatic. Just watch."
132
+ echo " Stop: bash scripts/harness-tmux-v4.sh --kill"
133
+ echo ""
134
+ tmux attach -t "$SESSION_NAME"
135
+ fi
136
+ fi
@@ -36,6 +36,19 @@ if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
36
36
  AGENT_STATUS=$(jq -r '.agent_status // "pending"' "$CWD/.harness/progress.json" 2>/dev/null || echo "pending")
37
37
  fi
38
38
 
39
+ # ── 명령 히스토리 기록 (모든 모드 공통) ──
40
+ PROGRESS_LOG="$CWD/.harness/progress.log"
41
+ if [ -n "$PROMPT" ] && [ -d "$CWD/.harness" ]; then
42
+ # progress.log가 없으면 생성
43
+ if [ ! -f "$PROGRESS_LOG" ]; then
44
+ echo "# Harness Command History — $(date +%Y-%m-%d)" > "$PROGRESS_LOG"
45
+ fi
46
+ PROMPT_SHORT=$(echo "$PROMPT" | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-80)
47
+ if [ ${#PROMPT_SHORT} -gt 2 ]; then
48
+ echo "$(date +"%Y-%m-%d %H:%M") | user-prompt | input | ${PROMPT_SHORT}" >> "$PROGRESS_LOG"
49
+ fi
50
+ fi
51
+
39
52
  # ── 컨텍스트 분리 가드레일 ──
40
53
  # 현재 에이전트가 활성인데 다른 에이전트 스킬을 호출하려는 경우 경고
41
54
  CONTEXT_WARNING=""
@@ -58,16 +71,6 @@ if [ -f "$FEATURE_QUEUE" ]; then
58
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)
59
72
  V4_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
60
73
 
61
- # Log user prompt to progress.log (truncated to 80 chars)
62
- PROGRESS_LOG="$CWD/.harness/progress.log"
63
- if [ -n "$PROMPT" ] && [ -f "$PROGRESS_LOG" ]; then
64
- PROMPT_SHORT=$(echo "$PROMPT" | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-80)
65
- # Skip logging for empty or very short prompts
66
- if [ ${#PROMPT_SHORT} -gt 2 ]; then
67
- echo "$(date +"%Y-%m-%d %H:%M") | user-prompt | input | ${PROMPT_SHORT}" >> "$PROGRESS_LOG"
68
- fi
69
- fi
70
-
71
74
  cat <<EOF
72
75
  [harness-v4] ${V4_PASSED}/${V4_TOTAL} features passed | ${V4_FAILED} failed
73
76
 
@@ -1,26 +1,172 @@
1
1
  ---
2
2
  name: harness-team-action
3
- description: "v4 Agent Teams 가동. feature-queue를 초기화하고 3개 Teammate를 생성하여 Feature 병렬 실행. 트리거: '/harness-team-action', 'team 시작', '팀 가동'"
3
+ description: "v4 Agent Teams 가동. Studio 레이아웃 구축 Queue 초기화 → Gen↔Eval 분리 사이클 팀 병렬 실행. 트리거: '/harness-team-action', 'team 시작', '팀 가동'"
4
4
  disable-model-invocation: false
5
5
  ---
6
6
 
7
7
  # /harness-team-action — Agent Teams 가동
8
8
 
9
+ ## Step 0: Studio 레이아웃 자동 구축
10
+
11
+ 3-column 대시보드 레이아웃을 자동 구축합니다:
12
+
13
+ ```bash
14
+ bash scripts/harness-studio-setup.sh .
15
+ ```
16
+
17
+ 스크립트 출력을 확인합니다:
18
+
19
+ - **`Layout ready`** → 현재 터미널에 split 완료. 바로 Step 1로.
20
+ - **`ATTACH_TMUX=harness-studio`** → 새 tmux 세션이 생성됨 (tmux 밖에서 실행한 경우).
21
+ 사용자에게 아래 안내를 출력하고 **STOP**합니다:
22
+
23
+ ```
24
+ Studio 레이아웃이 준비되었습니다!
25
+ 다른 터미널에서 아래 명령을 실행하세요:
26
+
27
+ tmux attach -t harness-studio
28
+
29
+ 새 창에서 Claude가 자동 실행됩니다. 거기서 "팀 가동"을 입력하면 Teams가 시작됩니다.
30
+ ```
31
+
32
+ - **`already set up`** → 이미 구축됨. 바로 Step 1로.
33
+
9
34
  ## Step 1: Queue 초기화
10
35
 
11
36
  ```bash
12
37
  if [ ! -f .harness/actions/feature-queue.json ]; then bash scripts/harness-queue-manager.sh init .; else bash scripts/harness-queue-manager.sh recover .; fi && bash scripts/harness-queue-manager.sh status .
13
38
  ```
14
39
 
15
- ## Step 2: Teammate 생성
40
+ ## Step 2: Feature 할당 (dequeue)
41
+
42
+ Queue status 결과를 확인하여 `ready` 큐에 feature가 있는지 확인합니다.
43
+ ready feature 수와 설정된 concurrency 중 작은 값만큼 팀을 생성합니다 (최대 3).
44
+
45
+ **각 팀마다** dequeue 명령으로 feature를 원자적으로 할당합니다:
46
+
47
+ ```bash
48
+ bash scripts/harness-queue-manager.sh dequeue {TEAM_NUMBER} .
49
+ ```
50
+
51
+ dequeue 결과로 feature ID가 반환됩니다. 빈 결과면 해당 팀은 생성하지 않습니다.
52
+
53
+ ## Step 3: Agent 도구로 팀 생성 (Gen↔Eval 분리 사이클)
54
+
55
+ dequeue로 할당받은 feature마다 **Agent 도구**를 호출합니다.
56
+ **반드시 `isolation: "worktree"`를 사용**하여 각 팀이 독립된 코드 복사본에서 작업합니다.
57
+
58
+ **독립적인 팀들은 단일 메시지에서 병렬로 호출**하세요 (한 번의 응답에 여러 Agent 도구 호출).
59
+
60
+ ```
61
+ Agent({
62
+ description: "Team-{N}: {FEATURE_ID}",
63
+ isolation: "worktree",
64
+ prompt: "<아래 Team Worker 프롬프트>"
65
+ })
66
+ ```
67
+
68
+ ### Team Worker 프롬프트
69
+
70
+ ```
71
+ 당신은 Harness Team-{N} 워커입니다. 하나의 Feature에 대해 Gen→Eval 사이클을 수행합니다.
72
+
73
+ ## 할당된 Feature
74
+ - Feature ID: {FEATURE_ID}
75
+ - 프로젝트 루트: 현재 디렉토리 (worktree 복사본)
76
+
77
+ ## Phase 1: Generator (코드 생성)
78
+
79
+ 1. Feature 정보 확인:
80
+ - `jq '.features[] | select(.id == "{FEATURE_ID}")' .harness/actions/feature-list.json`
81
+ - `.harness/actions/api-contract.json`에서 관련 엔드포인트 확인
82
+ - AC(Acceptance Criteria) 목록을 정확히 파악
83
+
84
+ 2. 코드 생성:
85
+ - AGENTS.md의 IA-MAP에 따라 올바른 디렉토리에 코드 작성
86
+ - AC의 모든 항목을 충족하도록 구현
87
+
88
+ 3. Pre-eval 게이트 (자체):
89
+ - tsc (타입 체크) 실행
90
+ - eslint (린트) 실행
91
+ - 컴파일 에러가 있으면 직접 수정 (Eval에 넘기지 않음)
92
+
93
+ ## Phase 2: Evaluator (독립 평가 — Agent 도구 사용)
94
+
95
+ 코드 생성이 완료되면 **별도 Agent를 생성하여 평가**합니다.
96
+ 이 Evaluator Agent는 당신(Generator)의 추론 과정을 모릅니다.
97
+ 오직 코드와 AC만 보고 판단합니다.
98
+
99
+ ```
100
+ Agent({
101
+ description: "Eval: {FEATURE_ID}",
102
+ prompt: "<아래 Evaluator 프롬프트>"
103
+ })
104
+ ```
105
+
106
+ #### Evaluator 프롬프트
107
+
108
+ ```
109
+ 당신은 독립 Evaluator입니다. Generator가 작성한 코드를 AC 기준으로 냉정하게 평가합니다.
110
+ Generator의 의도나 추론 과정은 알 수 없습니다. 오직 코드와 결과만 봅니다.
111
+
112
+ ## 평가 대상
113
+ - Feature ID: {FEATURE_ID}
114
+ - AC 확인: `jq '.features[] | select(.id == "{FEATURE_ID}").acceptance_criteria' .harness/actions/feature-list.json`
115
+
116
+ ## 평가 기준
117
+ 1. AC 100% 충족 여부 (부분 통과 = FAIL)
118
+ 2. api-contract.json과의 일치 여부 (엔드포인트, 요청/응답 스키마)
119
+ 3. tsc, eslint 통과 여부
120
+ 4. 보안 취약점 여부 (OWASP Top 10)
121
+ 5. 기존 코드와의 regression 여부
122
+
123
+ ## 출력 형식
124
+ 반드시 아래 형식으로 결과를 반환하세요:
125
+
126
+ VERDICT: PASS 또는 FAIL
127
+ SCORE: X.XX / 3.00
128
+ EVIDENCE:
129
+ - AC-1: [PASS/FAIL] 근거
130
+ - AC-2: [PASS/FAIL] 근거
131
+ - ...
132
+ FEEDBACK: (FAIL인 경우만) 구체적 수정 지시
133
+ ```
134
+
135
+ ## Phase 3: 결과 처리
136
+
137
+ Evaluator Agent 결과를 확인합니다:
138
+
139
+ ### PASS인 경우 (VERDICT: PASS, SCORE ≥ 2.80):
140
+ 1. `bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .` 실행
141
+ 2. 변경 파일 목록과 AC 충족 요약을 Lead에게 반환
142
+
143
+ ### FAIL인 경우:
144
+ 1. Evaluator의 FEEDBACK을 읽고 코드를 수정 (Phase 1로 돌아감)
145
+ 2. 수정 후 다시 Phase 2 (새 Evaluator Agent 생성 — 이전 Eval 컨텍스트 없음)
146
+ 3. 최대 3회 시도. 3회 모두 FAIL이면:
147
+ - `bash scripts/harness-queue-manager.sh fail {FEATURE_ID} .` 실행
148
+ - 실패 사유와 마지막 Eval 결과를 Lead에게 반환
149
+ ```
150
+
151
+ ## Step 4: 결과 수집 및 다음 라운드
152
+
153
+ 모든 Team Agent가 완료되면:
154
+ 1. 각 Agent 반환 메시지에서 PASS/FAIL 확인
155
+ 2. Queue 상태 재확인: `bash scripts/harness-queue-manager.sh status .`
156
+ 3. ready 큐에 새로 unblock된 feature가 있으면 → **Step 2로 돌아가서** 추가 팀 생성
157
+ 4. ready=0, in_progress=0이면 → 최종 결과 보고
158
+
159
+ ## 핵심 원칙
16
160
 
17
- Queue의 `ready` 배열에서 Feature를 읽고, **3명의 Teammate를 즉시 생성**하세요.
161
+ ### 자기 의식 편향 차단
162
+ - Generator가 자기 코드를 평가하지 않음
163
+ - Evaluator는 항상 새 Agent (Generator의 추론 과정을 모름)
164
+ - FAIL 후 재시도 시에도 새 Evaluator를 생성 (이전 Eval 기억 없음)
18
165
 
19
- Teammate에게 전달할 지시:
20
- - `.harness/actions/feature-queue.json`의 ready에서 Feature 1개를 담당
21
- - 해당 Feature의 코드를 생성하고, AC(Acceptance Criteria)를 기준으로 자체 검증
22
- - PASS 시: `bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .` 실행
23
- - FAIL 시: 재시도 (최대 3회), 3회 실패 시 `bash scripts/harness-queue-manager.sh fail {FEATURE_ID} .`
24
- - 완료 후 Lead에게 결과 보고
166
+ ### 중복 방지
167
+ - dequeue는 원자적 (lock 사용) — 같은 feature 할당 불가
168
+ - ready가 0이면 팀을 생성하지 않음
25
169
 
26
- **Teammate 이름**: team-1, team-2, team-3
170
+ ### 격리
171
+ - 각 팀은 `isolation: "worktree"`로 독립 코드 복사본에서 작업
172
+ - 팀 간 코드 충돌 없음