@walwal-harness/cli 4.0.0-alpha.2 → 4.0.0-alpha.21
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 +253 -270
- package/assets/templates/config.json +1 -48
- package/assets/templates/gitignore-append.txt +1 -0
- package/bin/init.js +1 -0
- package/package.json +1 -1
- package/scripts/harness-dashboard-v4.sh +58 -81
- package/scripts/harness-next.sh +4 -15
- package/scripts/harness-prompts-v4.sh +106 -0
- package/scripts/harness-queue-manager.sh +59 -5
- package/scripts/harness-session-start.sh +18 -0
- package/scripts/harness-studio-v4.sh +69 -69
- package/scripts/harness-team-worker.sh +136 -123
- package/scripts/harness-user-prompt-submit.sh +31 -1
- package/skills/dispatcher/SKILL.md +7 -2
- package/skills/team-action/SKILL.md +58 -0
- package/skills/team-stop/SKILL.md +31 -0
- package/skills/evaluator-functional-flutter/SKILL.md +0 -206
- package/skills/evaluator-functional-flutter/references/ia-compliance.md +0 -77
- package/skills/evaluator-functional-flutter/references/scoring-rubric.md +0 -132
- package/skills/evaluator-functional-flutter/references/static-check-rules.md +0 -99
- package/skills/generator-frontend-flutter/SKILL.md +0 -173
- package/skills/generator-frontend-flutter/references/anti-patterns.md +0 -320
- package/skills/generator-frontend-flutter/references/api-layer-pattern.md +0 -233
- package/skills/generator-frontend-flutter/references/flutter-web-pattern.md +0 -273
- package/skills/generator-frontend-flutter/references/i18n-pattern.md +0 -102
- package/skills/generator-frontend-flutter/references/riverpod-pattern.md +0 -199
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-studio-v4.sh — Harness Studio v4
|
|
2
|
+
# harness-studio-v4.sh — Harness Studio v4
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
# │
|
|
6
|
-
# │
|
|
7
|
-
# │
|
|
8
|
-
#
|
|
9
|
-
# │ Control ├─────────────────────────┤
|
|
10
|
-
# │ harness> _ │ Team 3 (worker log) │
|
|
11
|
-
# └──────────────────────┴─────────────────────────┘
|
|
12
|
-
#
|
|
13
|
-
# Usage:
|
|
14
|
-
# bash scripts/harness-studio-v4.sh [project-root]
|
|
15
|
-
# bash scripts/harness-studio-v4.sh --kill
|
|
4
|
+
# ┌──────────────┬──────────────┬──────────────┐
|
|
5
|
+
# │ │ Progress │ Team 1 │
|
|
6
|
+
# │ Main ├──────────────┤ Team 2 │
|
|
7
|
+
# │ (claude) │ Prompts │ Team 3 │
|
|
8
|
+
# └──────────────┴──────────────┴──────────────┘
|
|
16
9
|
|
|
17
10
|
set -euo pipefail
|
|
18
11
|
|
|
@@ -20,17 +13,10 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
20
13
|
SESSION_NAME="harness-v4"
|
|
21
14
|
|
|
22
15
|
PROJECT_ROOT=""
|
|
23
|
-
KILL=false
|
|
24
|
-
|
|
25
16
|
for arg in "$@"; do
|
|
26
17
|
case "$arg" in
|
|
27
|
-
--kill)
|
|
28
|
-
|
|
29
|
-
exit 0
|
|
30
|
-
;;
|
|
31
|
-
*)
|
|
32
|
-
if [ -d "$arg" ]; then PROJECT_ROOT="$arg"; fi
|
|
33
|
-
;;
|
|
18
|
+
--kill) tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "Killed." || echo "No session."; exit 0 ;;
|
|
19
|
+
*) if [ -d "$arg" ]; then PROJECT_ROOT="$arg"; fi ;;
|
|
34
20
|
esac
|
|
35
21
|
done
|
|
36
22
|
|
|
@@ -43,73 +29,87 @@ if [ -z "$PROJECT_ROOT" ]; then
|
|
|
43
29
|
fi
|
|
44
30
|
|
|
45
31
|
if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
|
|
46
|
-
echo "Error: .harness/ not found."
|
|
47
|
-
exit 1
|
|
32
|
+
echo "Error: .harness/ not found."; exit 1
|
|
48
33
|
fi
|
|
49
34
|
|
|
35
|
+
PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
|
|
50
36
|
echo "Project: $PROJECT_ROOT"
|
|
51
|
-
echo "Session: $SESSION_NAME"
|
|
52
37
|
|
|
38
|
+
tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
|
|
39
|
+
sleep 1
|
|
40
|
+
# Ensure no leftover session
|
|
53
41
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
|
|
54
42
|
|
|
55
|
-
# ──
|
|
43
|
+
# ── Queue ──
|
|
56
44
|
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
57
45
|
if [ ! -f "$QUEUE" ]; then
|
|
58
|
-
echo "Initializing feature queue..."
|
|
59
46
|
bash "$SCRIPT_DIR/harness-queue-manager.sh" init "$PROJECT_ROOT"
|
|
47
|
+
else
|
|
48
|
+
bash "$SCRIPT_DIR/harness-queue-manager.sh" recover "$PROJECT_ROOT"
|
|
60
49
|
fi
|
|
61
50
|
|
|
62
51
|
# ══════════════════════════════════════════
|
|
63
|
-
#
|
|
52
|
+
# Layout: use PANE IDs (not indices!) to avoid renumbering issues
|
|
53
|
+
#
|
|
54
|
+
# 1. MAIN
|
|
55
|
+
# 2. split-h MAIN → MAIN | RIGHT (RIGHT = pane ID captured)
|
|
56
|
+
# 3. split-h RIGHT → MID | RIGHT (use -P to capture MID ID, RIGHT stays)
|
|
57
|
+
# 4. split-v RIGHT → T1 | T2_AREA (RIGHT becomes T1, T2_AREA captured)
|
|
58
|
+
# 5. split-v T2_AREA → T2 | T3 (T2_AREA becomes T2, T3 captured)
|
|
59
|
+
# 6. split-v MID → PROGRESS | PROMPTS (MID becomes PROGRESS, PROMPTS captured)
|
|
64
60
|
# ══════════════════════════════════════════
|
|
65
61
|
|
|
66
|
-
# 1.
|
|
67
|
-
|
|
68
|
-
-P -F '#{pane_id}'
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
tmux
|
|
62
|
+
# 1. MAIN
|
|
63
|
+
P_MAIN=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
|
|
64
|
+
-P -F '#{pane_id}')
|
|
65
|
+
|
|
66
|
+
# 2. RIGHT column (66% of total)
|
|
67
|
+
P_RIGHT=$(tmux split-window -h -p 66 -t "$P_MAIN" -c "$PROJECT_ROOT" \
|
|
68
|
+
-P -F '#{pane_id}')
|
|
69
|
+
|
|
70
|
+
# 3. MID column — split RIGHT, new pane goes LEFT of RIGHT (using -b flag)
|
|
71
|
+
P_MID=$(tmux split-window -hb -p 50 -t "$P_RIGHT" -c "$PROJECT_ROOT" \
|
|
72
|
+
-P -F '#{pane_id}')
|
|
73
|
+
# P_MID = left half (mid column), P_RIGHT = right half (teams column)
|
|
74
|
+
|
|
75
|
+
# 4. T1/T2 area — split RIGHT vertically (66% goes to new pane below)
|
|
76
|
+
P_T2_AREA=$(tmux split-window -v -p 66 -t "$P_RIGHT" -c "$PROJECT_ROOT" \
|
|
77
|
+
-P -F '#{pane_id}')
|
|
78
|
+
P_T1="$P_RIGHT"
|
|
79
|
+
# P_T1 = top 34%, P_T2_AREA = bottom 66%
|
|
80
|
+
|
|
81
|
+
# 5. T2/T3 — split T2_AREA (50/50)
|
|
82
|
+
P_T3=$(tmux split-window -v -p 50 -t "$P_T2_AREA" -c "$PROJECT_ROOT" \
|
|
83
|
+
-P -F '#{pane_id}')
|
|
84
|
+
P_T2="$P_T2_AREA"
|
|
85
|
+
|
|
86
|
+
# 6. Progress/Prompts — split MID vertically
|
|
87
|
+
P_PROMPTS=$(tmux split-window -v -p 40 -t "$P_MID" -c "$PROJECT_ROOT" \
|
|
88
|
+
-P -F '#{pane_id}')
|
|
89
|
+
P_PROGRESS="$P_MID"
|
|
90
|
+
|
|
91
|
+
# ── Commands (using pane IDs — stable regardless of index renumbering) ──
|
|
92
|
+
tmux send-keys -t "$P_MAIN" "unset npm_config_prefix 2>/dev/null; clear && claude --dangerously-skip-permissions" Enter
|
|
93
|
+
tmux send-keys -t "$P_PROGRESS" "exec bash '${SCRIPT_DIR}/harness-dashboard-v4.sh' '${PROJECT_ROOT}'" Enter
|
|
94
|
+
tmux send-keys -t "$P_PROMPTS" "exec bash '${SCRIPT_DIR}/harness-prompts-v4.sh' '${PROJECT_ROOT}'" Enter
|
|
95
|
+
tmux send-keys -t "$P_T1" "exec bash '${SCRIPT_DIR}/harness-team-worker.sh' 1 '${PROJECT_ROOT}'" Enter
|
|
96
|
+
tmux send-keys -t "$P_T2" "exec bash '${SCRIPT_DIR}/harness-team-worker.sh' 2 '${PROJECT_ROOT}'" Enter
|
|
97
|
+
tmux send-keys -t "$P_T3" "exec bash '${SCRIPT_DIR}/harness-team-worker.sh' 3 '${PROJECT_ROOT}'" Enter
|
|
98
|
+
|
|
99
|
+
# ── Titles ──
|
|
100
|
+
tmux select-pane -t "$P_MAIN" -T "Main"
|
|
101
|
+
tmux select-pane -t "$P_PROGRESS" -T "Progress"
|
|
102
|
+
tmux select-pane -t "$P_PROMPTS" -T "Prompts"
|
|
103
|
+
tmux select-pane -t "$P_T1" -T "Team 1"
|
|
104
|
+
tmux select-pane -t "$P_T2" -T "Team 2"
|
|
105
|
+
tmux select-pane -t "$P_T3" -T "Team 3"
|
|
97
106
|
|
|
98
107
|
tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
|
|
99
108
|
tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
109
|
+
tmux select-pane -t "$P_MAIN"
|
|
100
110
|
|
|
101
|
-
# Focus Control
|
|
102
|
-
tmux select-pane -t "$PANE_CTRL"
|
|
103
|
-
|
|
104
|
-
# Attach
|
|
105
111
|
if [ -n "${TMUX:-}" ]; then
|
|
106
112
|
tmux switch-client -t "$SESSION_NAME"
|
|
107
113
|
else
|
|
108
|
-
echo ""
|
|
109
|
-
echo "Launching Harness Studio v4..."
|
|
110
|
-
echo " Dashboard (left↑) : Feature Queue + Team status"
|
|
111
|
-
echo " Control (left↓) : start/pause/assign/requeue"
|
|
112
|
-
echo " Team 1-3 (right) : Parallel worker logs"
|
|
113
|
-
echo ""
|
|
114
114
|
tmux attach -t "$SESSION_NAME"
|
|
115
115
|
fi
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-team-worker.sh — Team Worker:
|
|
2
|
+
# harness-team-worker.sh — Team Worker v4: git worktree 격리 실행
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# 각 Team이 독립 worktree에서 작업하여 git 충돌 없이 병렬 실행.
|
|
5
|
+
# Feature PASS → main merge → worktree 정리.
|
|
6
6
|
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# bash scripts/harness-team-worker.sh <team_id> [project-root]
|
|
9
|
-
#
|
|
10
|
-
# Environment:
|
|
11
|
-
# MAX_ATTEMPTS=3 Feature당 최대 Gen→Eval 시도 횟수
|
|
12
|
-
# GEN_MODEL=sonnet Generator 모델
|
|
13
|
-
# EVAL_MODEL=opus Evaluator 모델
|
|
7
|
+
# Usage: bash scripts/harness-team-worker.sh <team_id> [project-root]
|
|
14
8
|
|
|
15
9
|
set -uo pipefail
|
|
16
10
|
|
|
@@ -39,10 +33,11 @@ FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
|
39
33
|
CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
40
34
|
PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
|
|
41
35
|
QUEUE_MGR="$SCRIPT_DIR/harness-queue-manager.sh"
|
|
42
|
-
|
|
43
|
-
# ── Lock file for git operations (prevent race conditions between teams) ──
|
|
44
36
|
GIT_LOCK="$PROJECT_ROOT/.harness/.git-lock"
|
|
45
37
|
|
|
38
|
+
# Worktree base directory
|
|
39
|
+
WORKTREE_DIR="$PROJECT_ROOT/.worktrees/team-${TEAM_ID}"
|
|
40
|
+
|
|
46
41
|
MAX_ATTEMPTS="${MAX_ATTEMPTS:-3}"
|
|
47
42
|
GEN_MODEL="${GEN_MODEL:-sonnet}"
|
|
48
43
|
EVAL_MODEL="${EVAL_MODEL:-opus}"
|
|
@@ -54,7 +49,6 @@ if [ -f "$CONFIG" ]; then
|
|
|
54
49
|
if [ -n "$_em" ]; then EVAL_MODEL="$_em"; fi
|
|
55
50
|
fi
|
|
56
51
|
|
|
57
|
-
# ── ANSI helpers ──
|
|
58
52
|
BOLD="\033[1m"
|
|
59
53
|
DIM="\033[2m"
|
|
60
54
|
GREEN="\033[32m"
|
|
@@ -64,42 +58,101 @@ CYAN="\033[36m"
|
|
|
64
58
|
RESET="\033[0m"
|
|
65
59
|
|
|
66
60
|
ts() { date +"%H:%M:%S"; }
|
|
61
|
+
log() { echo -e "[$(ts)] ${BOLD}T${TEAM_ID}${RESET} $*"; }
|
|
62
|
+
log_progress() { echo "$(date +"%Y-%m-%d %H:%M") | team-${TEAM_ID} | ${1} | ${2}" >> "$PROGRESS_LOG"; }
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
# ── Git lock ──
|
|
65
|
+
acquire_git_lock() {
|
|
66
|
+
local waited=0
|
|
67
|
+
while ! mkdir "$GIT_LOCK" 2>/dev/null; do
|
|
68
|
+
sleep 0.2
|
|
69
|
+
waited=$((waited + 1))
|
|
70
|
+
if [ "$waited" -ge 150 ]; then rm -rf "$GIT_LOCK"; mkdir "$GIT_LOCK" 2>/dev/null || true; break; fi
|
|
71
|
+
done
|
|
70
72
|
}
|
|
73
|
+
release_git_lock() { rm -rf "$GIT_LOCK" 2>/dev/null || true; }
|
|
74
|
+
|
|
75
|
+
# ── Worktree management ──
|
|
76
|
+
setup_worktree() {
|
|
77
|
+
local branch="$1"
|
|
78
|
+
|
|
79
|
+
acquire_git_lock
|
|
80
|
+
|
|
81
|
+
# Clean previous worktree if exists
|
|
82
|
+
if [ -d "$WORKTREE_DIR" ]; then
|
|
83
|
+
(cd "$PROJECT_ROOT" && git worktree remove "$WORKTREE_DIR" --force 2>/dev/null) || rm -rf "$WORKTREE_DIR"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Create fresh worktree from main
|
|
87
|
+
(cd "$PROJECT_ROOT" && git worktree add "$WORKTREE_DIR" -b "$branch" main 2>/dev/null) || \
|
|
88
|
+
(cd "$PROJECT_ROOT" && git worktree add "$WORKTREE_DIR" "$branch" 2>/dev/null) || {
|
|
89
|
+
release_git_lock
|
|
90
|
+
log "${RED}Failed to create worktree${RESET}"
|
|
91
|
+
return 1
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
release_git_lock
|
|
71
95
|
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
# Copy .harness to worktree (symlink for shared state)
|
|
97
|
+
ln -sf "$PROJECT_ROOT/.harness" "$WORKTREE_DIR/.harness" 2>/dev/null || true
|
|
98
|
+
|
|
99
|
+
log "Worktree: ${WORKTREE_DIR}"
|
|
100
|
+
return 0
|
|
74
101
|
}
|
|
75
102
|
|
|
76
|
-
|
|
77
|
-
acquire_git_lock
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if [ "$waited" -ge "$max_wait" ]; then
|
|
83
|
-
log "${RED}Git lock timeout (${max_wait}s). Removing stale lock.${RESET}"
|
|
84
|
-
rm -f "$GIT_LOCK"
|
|
85
|
-
break
|
|
86
|
-
fi
|
|
87
|
-
done
|
|
88
|
-
echo "T${TEAM_ID}" > "$GIT_LOCK"
|
|
103
|
+
cleanup_worktree() {
|
|
104
|
+
acquire_git_lock
|
|
105
|
+
if [ -d "$WORKTREE_DIR" ]; then
|
|
106
|
+
(cd "$PROJECT_ROOT" && git worktree remove "$WORKTREE_DIR" --force 2>/dev/null) || rm -rf "$WORKTREE_DIR"
|
|
107
|
+
fi
|
|
108
|
+
release_git_lock
|
|
89
109
|
}
|
|
90
110
|
|
|
91
|
-
|
|
92
|
-
|
|
111
|
+
merge_to_main() {
|
|
112
|
+
local branch="$1"
|
|
113
|
+
|
|
114
|
+
acquire_git_lock
|
|
115
|
+
|
|
116
|
+
local merge_ok=false
|
|
117
|
+
|
|
118
|
+
# Try merge
|
|
119
|
+
if (cd "$PROJECT_ROOT" && git merge --no-ff "$branch" -m "merge: ${feature_id} PASS" 2>/dev/null); then
|
|
120
|
+
merge_ok=true
|
|
121
|
+
else
|
|
122
|
+
# Conflict → abort, then try rebase in worktree
|
|
123
|
+
(cd "$PROJECT_ROOT" && git merge --abort 2>/dev/null) || true
|
|
124
|
+
|
|
125
|
+
log "${YELLOW}Merge conflict — rebasing in worktree...${RESET}"
|
|
126
|
+
if (cd "$WORKTREE_DIR" && git rebase main 2>/dev/null); then
|
|
127
|
+
# Retry merge after rebase
|
|
128
|
+
if (cd "$PROJECT_ROOT" && git merge --no-ff "$branch" -m "merge: ${feature_id} PASS (rebased)" 2>/dev/null); then
|
|
129
|
+
merge_ok=true
|
|
130
|
+
fi
|
|
131
|
+
else
|
|
132
|
+
(cd "$WORKTREE_DIR" && git rebase --abort 2>/dev/null) || true
|
|
133
|
+
log "${RED}Rebase failed${RESET}"
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Clean up branch after merge
|
|
138
|
+
if [ "$merge_ok" = true ]; then
|
|
139
|
+
(cd "$PROJECT_ROOT" && git branch -d "$branch" 2>/dev/null) || true
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
release_git_lock
|
|
143
|
+
|
|
144
|
+
[ "$merge_ok" = true ]
|
|
93
145
|
}
|
|
94
146
|
|
|
95
|
-
# ── Pre-eval gate ──
|
|
147
|
+
# ── Pre-eval gate (runs in worktree) ──
|
|
96
148
|
run_pre_eval_gate() {
|
|
97
|
-
local
|
|
149
|
+
local work_dir="$WORKTREE_DIR"
|
|
98
150
|
|
|
151
|
+
# Resolve cwd within worktree
|
|
99
152
|
if [ -f "$CONFIG" ]; then
|
|
100
153
|
_cwd=$(jq -r '.flow.pre_eval_gate.frontend_cwd // empty' "$CONFIG" 2>/dev/null)
|
|
101
154
|
if [ -n "$_cwd" ] && [ "$_cwd" != "null" ]; then
|
|
102
|
-
|
|
155
|
+
work_dir="$WORKTREE_DIR/$_cwd"
|
|
103
156
|
fi
|
|
104
157
|
fi
|
|
105
158
|
|
|
@@ -107,26 +160,24 @@ run_pre_eval_gate() {
|
|
|
107
160
|
if [ -f "$CONFIG" ]; then
|
|
108
161
|
mapfile -t checks < <(jq -r '.flow.pre_eval_gate.frontend_checks[]' "$CONFIG" 2>/dev/null)
|
|
109
162
|
fi
|
|
110
|
-
|
|
111
163
|
if [ ${#checks[@]} -eq 0 ]; then
|
|
112
164
|
checks=("npx tsc --noEmit" "npx eslint src/")
|
|
113
165
|
fi
|
|
114
166
|
|
|
115
|
-
local all_pass=true
|
|
167
|
+
local all_pass=true
|
|
116
168
|
for cmd in "${checks[@]}"; do
|
|
117
|
-
if (cd "$
|
|
169
|
+
if (cd "$work_dir" && timeout 120s bash -c "$cmd" >/dev/null 2>&1); then
|
|
118
170
|
log " ${GREEN}✓${RESET} $cmd"
|
|
119
171
|
else
|
|
120
172
|
log " ${RED}✗${RESET} $cmd"
|
|
121
173
|
all_pass=false
|
|
122
|
-
fail_cmds+="$cmd; "
|
|
123
174
|
fi
|
|
124
175
|
done
|
|
125
176
|
|
|
126
177
|
[ "$all_pass" = true ]
|
|
127
178
|
}
|
|
128
179
|
|
|
129
|
-
# ── Build
|
|
180
|
+
# ── Build prompts ──
|
|
130
181
|
build_gen_prompt() {
|
|
131
182
|
local fid="$1" attempt="$2" feedback="${3:-}"
|
|
132
183
|
|
|
@@ -161,7 +212,7 @@ RULES:
|
|
|
161
212
|
- Implement ONLY this single feature
|
|
162
213
|
- Do NOT modify code belonging to other features
|
|
163
214
|
- Follow existing code patterns and CONVENTIONS.md
|
|
164
|
-
- When done, stage and commit
|
|
215
|
+
- When done, stage and commit: git add -A && git commit -m 'feat(${fid}): ${fname}'
|
|
165
216
|
PROMPT
|
|
166
217
|
|
|
167
218
|
if [ "$attempt" -gt 1 ] && [ -n "$feedback" ]; then
|
|
@@ -175,7 +226,6 @@ RETRY
|
|
|
175
226
|
fi
|
|
176
227
|
}
|
|
177
228
|
|
|
178
|
-
# ── Build evaluator prompt ──
|
|
179
229
|
build_eval_prompt() {
|
|
180
230
|
local fid="$1"
|
|
181
231
|
|
|
@@ -218,15 +268,12 @@ FEEDBACK: one paragraph summary
|
|
|
218
268
|
PROMPT
|
|
219
269
|
}
|
|
220
270
|
|
|
221
|
-
# ── Parse eval result (macOS-compatible, no grep -P) ──
|
|
222
271
|
parse_eval_result() {
|
|
223
272
|
local output="$1"
|
|
224
|
-
|
|
225
273
|
local verdict score feedback
|
|
226
274
|
verdict=$(echo "$output" | grep -E '^VERDICT:' | sed 's/VERDICT:[[:space:]]*//' | head -1)
|
|
227
275
|
score=$(echo "$output" | grep -E '^SCORE:' | sed 's/SCORE:[[:space:]]*//' | head -1)
|
|
228
276
|
feedback=$(echo "$output" | grep -E '^FEEDBACK:' | sed 's/FEEDBACK:[[:space:]]*//' | head -1)
|
|
229
|
-
|
|
230
277
|
echo "${verdict:-UNKNOWN}|${score:-0.00}|${feedback:-no feedback}"
|
|
231
278
|
}
|
|
232
279
|
|
|
@@ -237,14 +284,12 @@ log "${CYAN}Team ${TEAM_ID} started${RESET} (gen=${GEN_MODEL}, eval=${EVAL_MODEL
|
|
|
237
284
|
log_progress "start" "Team ${TEAM_ID} worker started"
|
|
238
285
|
|
|
239
286
|
while true; do
|
|
240
|
-
# ── Dequeue
|
|
287
|
+
# ── Dequeue ──
|
|
241
288
|
feature_id=$(bash "$QUEUE_MGR" dequeue "$TEAM_ID" "$PROJECT_ROOT" 2>/dev/null)
|
|
242
289
|
|
|
243
290
|
if [ -z "$feature_id" ] || [[ "$feature_id" == "["* ]]; then
|
|
244
291
|
log "${DIM}No features ready. Waiting 10s...${RESET}"
|
|
245
292
|
sleep 10
|
|
246
|
-
|
|
247
|
-
# Check if completely done
|
|
248
293
|
remaining=$(jq '(.queue.ready | length) + (.queue.blocked | length) + (.queue.in_progress | length)' "$QUEUE" 2>/dev/null || echo "1")
|
|
249
294
|
if [ "${remaining}" -eq 0 ] 2>/dev/null; then
|
|
250
295
|
log "${GREEN}${BOLD}ALL FEATURES COMPLETE. Team ${TEAM_ID} exiting.${RESET}"
|
|
@@ -254,16 +299,16 @@ while true; do
|
|
|
254
299
|
continue
|
|
255
300
|
fi
|
|
256
301
|
|
|
257
|
-
log "${CYAN}▶
|
|
302
|
+
log "${CYAN}▶ ${feature_id}${RESET}"
|
|
258
303
|
log_progress "dequeue" "${feature_id}"
|
|
259
304
|
|
|
260
|
-
# ──
|
|
305
|
+
# ── Setup worktree ──
|
|
261
306
|
branch="feature/${feature_id}"
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
307
|
+
if ! setup_worktree "$branch"; then
|
|
308
|
+
bash "$QUEUE_MGR" fail "$feature_id" "$PROJECT_ROOT" 2>/dev/null
|
|
309
|
+
log_progress "fail" "${feature_id} worktree setup failed"
|
|
310
|
+
continue
|
|
311
|
+
fi
|
|
267
312
|
|
|
268
313
|
# ── Gen→Eval Loop ──
|
|
269
314
|
attempt=1
|
|
@@ -271,111 +316,82 @@ while true; do
|
|
|
271
316
|
passed=false
|
|
272
317
|
|
|
273
318
|
while [ "$attempt" -le "$MAX_ATTEMPTS" ]; do
|
|
274
|
-
log "${BOLD}──
|
|
319
|
+
log "${BOLD}── ${feature_id} attempt ${attempt}/${MAX_ATTEMPTS} ──${RESET}"
|
|
275
320
|
|
|
276
|
-
# ── Generate ──
|
|
277
|
-
log "Gen
|
|
321
|
+
# ── Generate (in worktree) ──
|
|
322
|
+
log "Gen (${GEN_MODEL})..."
|
|
278
323
|
bash "$QUEUE_MGR" update_phase "$feature_id" "gen" "$attempt" "$PROJECT_ROOT" 2>/dev/null
|
|
279
324
|
|
|
280
325
|
gen_prompt=$(build_gen_prompt "$feature_id" "$attempt" "$eval_feedback")
|
|
281
326
|
|
|
282
327
|
gen_start=$(date +%s)
|
|
283
|
-
gen_output=$(cd "$
|
|
328
|
+
gen_output=$(cd "$WORKTREE_DIR" && claude -p "$gen_prompt" \
|
|
329
|
+
--dangerously-skip-permissions \
|
|
330
|
+
--model "$GEN_MODEL" \
|
|
331
|
+
--output-format text 2>&1 | tee /dev/stderr) 2>&1 || true
|
|
284
332
|
gen_elapsed=$(( $(date +%s) - gen_start ))
|
|
285
333
|
|
|
286
|
-
files_changed=$(cd "$
|
|
334
|
+
files_changed=$(cd "$WORKTREE_DIR" && git diff --name-only 2>/dev/null | wc -l | tr -d ' ')
|
|
287
335
|
log "Gen done (${gen_elapsed}s) — ${files_changed} files"
|
|
288
|
-
log_progress "gen" "${feature_id}
|
|
336
|
+
log_progress "gen" "${feature_id} #${attempt}: ${files_changed} files, ${gen_elapsed}s"
|
|
289
337
|
|
|
290
|
-
# Auto-commit
|
|
291
|
-
(cd "$
|
|
338
|
+
# Auto-commit in worktree
|
|
339
|
+
(cd "$WORKTREE_DIR" && git add -A && git commit -m "feat(${feature_id}): attempt ${attempt}" --no-verify 2>/dev/null) || true
|
|
292
340
|
|
|
293
|
-
# ── Pre-eval gate ──
|
|
294
|
-
log "
|
|
341
|
+
# ── Pre-eval gate (in worktree) ──
|
|
342
|
+
log "Gate..."
|
|
295
343
|
bash "$QUEUE_MGR" update_phase "$feature_id" "gate" "$attempt" "$PROJECT_ROOT" 2>/dev/null
|
|
296
344
|
|
|
297
|
-
if ! run_pre_eval_gate
|
|
298
|
-
log "${RED}Gate FAIL
|
|
299
|
-
eval_feedback="Pre-eval gate failed: type check or lint errors.
|
|
345
|
+
if ! run_pre_eval_gate; then
|
|
346
|
+
log "${RED}Gate FAIL${RESET}"
|
|
347
|
+
eval_feedback="Pre-eval gate failed: type check or lint errors."
|
|
300
348
|
attempt=$((attempt + 1))
|
|
301
349
|
continue
|
|
302
350
|
fi
|
|
303
351
|
|
|
304
|
-
# ── Evaluate ──
|
|
305
|
-
log "Eval
|
|
352
|
+
# ── Evaluate (in worktree) ──
|
|
353
|
+
log "Eval (${EVAL_MODEL})..."
|
|
306
354
|
bash "$QUEUE_MGR" update_phase "$feature_id" "eval" "$attempt" "$PROJECT_ROOT" 2>/dev/null
|
|
307
355
|
|
|
308
356
|
eval_prompt=$(build_eval_prompt "$feature_id")
|
|
309
357
|
|
|
310
358
|
eval_start=$(date +%s)
|
|
311
|
-
eval_output=$(cd "$
|
|
359
|
+
eval_output=$(cd "$WORKTREE_DIR" && claude -p "$eval_prompt" \
|
|
360
|
+
--dangerously-skip-permissions \
|
|
361
|
+
--model "$EVAL_MODEL" \
|
|
362
|
+
--output-format text 2>&1 | tee /dev/stderr) 2>&1 || true
|
|
312
363
|
eval_elapsed=$(( $(date +%s) - eval_start ))
|
|
313
364
|
|
|
314
|
-
# Parse result
|
|
315
365
|
result_line=$(parse_eval_result "$eval_output")
|
|
316
366
|
verdict=$(echo "$result_line" | cut -d'|' -f1)
|
|
317
367
|
score=$(echo "$result_line" | cut -d'|' -f2)
|
|
318
368
|
feedback=$(echo "$result_line" | cut -d'|' -f3-)
|
|
319
369
|
|
|
320
|
-
log_progress "eval" "${feature_id}
|
|
370
|
+
log_progress "eval" "${feature_id} #${attempt}: ${verdict} (${score}) ${eval_elapsed}s"
|
|
321
371
|
|
|
322
372
|
if [ "$verdict" = "PASS" ]; then
|
|
323
|
-
log "${GREEN}${BOLD}✓ PASS
|
|
373
|
+
log "${GREEN}${BOLD}✓ PASS ${score}/3.00${RESET} (${eval_elapsed}s)"
|
|
324
374
|
passed=true
|
|
325
375
|
break
|
|
326
376
|
else
|
|
327
|
-
log "${RED}✗ FAIL
|
|
377
|
+
log "${RED}✗ FAIL ${score}/3.00${RESET} (${eval_elapsed}s)"
|
|
328
378
|
log "${DIM} ${feedback}${RESET}"
|
|
329
379
|
eval_feedback="$feedback"
|
|
330
380
|
attempt=$((attempt + 1))
|
|
331
381
|
fi
|
|
332
382
|
done
|
|
333
383
|
|
|
334
|
-
#
|
|
335
|
-
# Phase 3: Branch merge with conflict handling
|
|
336
|
-
# ══════════════════════════════════════════
|
|
384
|
+
# ── Result ──
|
|
337
385
|
if [ "$passed" = true ]; then
|
|
338
|
-
log "Merging
|
|
339
|
-
acquire_git_lock
|
|
340
|
-
|
|
341
|
-
merge_ok=false
|
|
342
|
-
|
|
343
|
-
# Attempt 1: straight merge
|
|
344
|
-
if (cd "$PROJECT_ROOT" && git checkout main 2>/dev/null && git merge --no-ff "$branch" -m "merge: ${feature_id} PASS" 2>/dev/null); then
|
|
345
|
-
merge_ok=true
|
|
346
|
-
else
|
|
347
|
-
# Attempt 2: abort failed merge, rebase, re-eval gate, then merge
|
|
348
|
-
log "${YELLOW}Conflict detected — rebasing ${branch} onto main...${RESET}"
|
|
349
|
-
(cd "$PROJECT_ROOT" && git merge --abort 2>/dev/null) || true
|
|
350
|
-
(cd "$PROJECT_ROOT" && git checkout "$branch" 2>/dev/null) || true
|
|
351
|
-
|
|
352
|
-
if (cd "$PROJECT_ROOT" && git rebase main 2>/dev/null); then
|
|
353
|
-
log "Rebase OK. Re-running gate..."
|
|
354
|
-
|
|
355
|
-
if run_pre_eval_gate "$feature_id"; then
|
|
356
|
-
log "Gate still PASS after rebase."
|
|
357
|
-
if (cd "$PROJECT_ROOT" && git checkout main 2>/dev/null && git merge --no-ff "$branch" -m "merge: ${feature_id} PASS (rebased)" 2>/dev/null); then
|
|
358
|
-
merge_ok=true
|
|
359
|
-
fi
|
|
360
|
-
else
|
|
361
|
-
log "${RED}Gate FAIL after rebase — needs re-gen${RESET}"
|
|
362
|
-
fi
|
|
363
|
-
else
|
|
364
|
-
log "${RED}Rebase failed — conflicts too complex${RESET}"
|
|
365
|
-
(cd "$PROJECT_ROOT" && git rebase --abort 2>/dev/null) || true
|
|
366
|
-
fi
|
|
367
|
-
fi
|
|
368
|
-
|
|
369
|
-
release_git_lock
|
|
370
|
-
|
|
371
|
-
if [ "$merge_ok" = true ]; then
|
|
372
|
-
# Clean up feature branch
|
|
373
|
-
(cd "$PROJECT_ROOT" && git branch -d "$branch" 2>/dev/null) || true
|
|
386
|
+
log "Merging → main..."
|
|
374
387
|
|
|
388
|
+
if merge_to_main "$branch"; then
|
|
389
|
+
# Cleanup worktree after successful merge
|
|
390
|
+
cleanup_worktree
|
|
375
391
|
bash "$QUEUE_MGR" pass "$feature_id" "$PROJECT_ROOT" 2>/dev/null
|
|
376
|
-
log_progress "pass" "${feature_id} merged
|
|
392
|
+
log_progress "pass" "${feature_id} merged & cleaned"
|
|
377
393
|
|
|
378
|
-
# Update feature-list.json
|
|
394
|
+
# Update feature-list.json
|
|
379
395
|
if [ -f "$FEATURES" ]; then
|
|
380
396
|
jq --arg fid "$feature_id" '
|
|
381
397
|
.features |= map(
|
|
@@ -388,18 +404,15 @@ while true; do
|
|
|
388
404
|
|
|
389
405
|
log "${GREEN}${BOLD}✓ ${feature_id} DONE${RESET}"
|
|
390
406
|
else
|
|
391
|
-
|
|
392
|
-
(cd "$PROJECT_ROOT" && git checkout main 2>/dev/null) || true
|
|
407
|
+
cleanup_worktree
|
|
393
408
|
bash "$QUEUE_MGR" fail "$feature_id" "$PROJECT_ROOT" 2>/dev/null
|
|
409
|
+
log "${RED}Merge failed → ${feature_id} FAILED${RESET}"
|
|
394
410
|
log_progress "merge-fail" "${feature_id}"
|
|
395
411
|
fi
|
|
396
|
-
|
|
397
412
|
else
|
|
398
|
-
|
|
399
|
-
acquire_git_lock
|
|
400
|
-
(cd "$PROJECT_ROOT" && git checkout main 2>/dev/null) || true
|
|
401
|
-
release_git_lock
|
|
413
|
+
cleanup_worktree
|
|
402
414
|
bash "$QUEUE_MGR" fail "$feature_id" "$PROJECT_ROOT" 2>/dev/null
|
|
415
|
+
log "${RED}${BOLD}✗ ${feature_id} FAILED (${MAX_ATTEMPTS} attempts)${RESET}"
|
|
403
416
|
log_progress "fail" "${feature_id} after ${MAX_ATTEMPTS} attempts"
|
|
404
417
|
fi
|
|
405
418
|
|
|
@@ -51,7 +51,37 @@ current_agent=${CURRENT_AGENT} (running) 인데 /harness-${REQUESTED_SKILL} 호
|
|
|
51
51
|
fi
|
|
52
52
|
fi
|
|
53
53
|
|
|
54
|
-
# ──
|
|
54
|
+
# ── v4 Parallel Mode 감지 ──
|
|
55
|
+
FEATURE_QUEUE="$CWD/.harness/actions/feature-queue.json"
|
|
56
|
+
if [ -f "$FEATURE_QUEUE" ]; then
|
|
57
|
+
V4_PASSED=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
58
|
+
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
|
+
V4_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
60
|
+
|
|
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
|
+
cat <<EOF
|
|
72
|
+
[harness-v4] ${V4_PASSED}/${V4_TOTAL} features passed | ${V4_FAILED} failed
|
|
73
|
+
|
|
74
|
+
## v4 Parallel Mode Active
|
|
75
|
+
- 3 Agent Teams이 feature-queue에서 자율적으로 Gen→Eval 루프 실행 중
|
|
76
|
+
- 당신은 **오케스트레이터** 역할: 대시보드 모니터링, 실패 대응, 수동 개입
|
|
77
|
+
- /harness-generator-*, /harness-evaluator-* 스킬 호출 금지 (Teams가 처리)
|
|
78
|
+
- 할 수 있는 것: 코드 리뷰, 아키텍처 결정, failed feature 분석, requeue 판단
|
|
79
|
+
- skip: "harness skip" 시 일반 대화
|
|
80
|
+
EOF
|
|
81
|
+
exit 0
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ── Compact context 주입 (v3 mode) ──
|
|
55
85
|
cat <<EOF
|
|
56
86
|
[harness] S${SPRINT_NUM} | ${PIPELINE} | agent=${CURRENT_AGENT} (${AGENT_STATUS}) | next=${NEXT_AGENT}
|
|
57
87
|
${CONTEXT_WARNING}
|