@walwal-harness/cli 3.7.4 → 4.0.0-alpha.10
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/assets/templates/config.json +11 -0
- package/bin/init.js +34 -2
- package/package.json +1 -1
- package/scripts/harness-control-v4.sh +97 -0
- package/scripts/harness-dashboard-v4.sh +243 -0
- package/scripts/harness-queue-manager.sh +367 -0
- package/scripts/harness-session-start.sh +18 -0
- package/scripts/harness-studio-v4.sh +122 -0
- package/scripts/harness-team-worker.sh +415 -0
- package/scripts/harness-user-prompt-submit.sh +31 -1
- package/skills/evaluator-functional/SKILL.md +26 -0
- package/skills/generator-frontend/SKILL.md +17 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-team-worker.sh — Team Worker: Feature-level Gen→Eval loop (v4.0)
|
|
3
|
+
#
|
|
4
|
+
# 1 Team = 1 프로세스. Feature Queue에서 feature를 꺼내
|
|
5
|
+
# Gen→Gate→Eval 루프를 claude -p 헤드리스로 자율 실행한다.
|
|
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 모델
|
|
14
|
+
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
|
|
17
|
+
TEAM_ID="${1:?Usage: harness-team-worker.sh <team_id> [project-root]}"
|
|
18
|
+
shift || true
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
21
|
+
|
|
22
|
+
# ── Resolve project root ──
|
|
23
|
+
PROJECT_ROOT="${1:-}"
|
|
24
|
+
if [ -z "$PROJECT_ROOT" ]; then
|
|
25
|
+
dir="$(pwd)"
|
|
26
|
+
while [ "$dir" != "/" ]; do
|
|
27
|
+
if [ -d "$dir/.harness" ]; then PROJECT_ROOT="$dir"; break; fi
|
|
28
|
+
dir="$(dirname "$dir")"
|
|
29
|
+
done
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
|
|
33
|
+
echo "[T${TEAM_ID}] .harness/ not found."
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
38
|
+
FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
39
|
+
CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
40
|
+
PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
|
|
41
|
+
QUEUE_MGR="$SCRIPT_DIR/harness-queue-manager.sh"
|
|
42
|
+
|
|
43
|
+
# ── Lock file for git operations (prevent race conditions between teams) ──
|
|
44
|
+
GIT_LOCK="$PROJECT_ROOT/.harness/.git-lock"
|
|
45
|
+
|
|
46
|
+
MAX_ATTEMPTS="${MAX_ATTEMPTS:-3}"
|
|
47
|
+
GEN_MODEL="${GEN_MODEL:-sonnet}"
|
|
48
|
+
EVAL_MODEL="${EVAL_MODEL:-opus}"
|
|
49
|
+
|
|
50
|
+
if [ -f "$CONFIG" ]; then
|
|
51
|
+
_gm=$(jq -r '.agents["generator-frontend"].model // empty' "$CONFIG" 2>/dev/null)
|
|
52
|
+
_em=$(jq -r '.agents["evaluator-functional"].model // empty' "$CONFIG" 2>/dev/null)
|
|
53
|
+
if [ -n "$_gm" ]; then GEN_MODEL="$_gm"; fi
|
|
54
|
+
if [ -n "$_em" ]; then EVAL_MODEL="$_em"; fi
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# ── ANSI helpers ──
|
|
58
|
+
BOLD="\033[1m"
|
|
59
|
+
DIM="\033[2m"
|
|
60
|
+
GREEN="\033[32m"
|
|
61
|
+
YELLOW="\033[33m"
|
|
62
|
+
RED="\033[31m"
|
|
63
|
+
CYAN="\033[36m"
|
|
64
|
+
RESET="\033[0m"
|
|
65
|
+
|
|
66
|
+
ts() { date +"%H:%M:%S"; }
|
|
67
|
+
|
|
68
|
+
log() {
|
|
69
|
+
echo -e "[$(ts)] ${BOLD}T${TEAM_ID}${RESET} $*"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
log_progress() {
|
|
73
|
+
echo "$(date +"%Y-%m-%d") | team-${TEAM_ID} | ${1} | ${2}" >> "$PROGRESS_LOG"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# ── Git lock — serialize git checkout/merge across teams ──
|
|
77
|
+
acquire_git_lock() {
|
|
78
|
+
local max_wait=60 waited=0
|
|
79
|
+
while [ -f "$GIT_LOCK" ]; do
|
|
80
|
+
sleep 1
|
|
81
|
+
waited=$((waited + 1))
|
|
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"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
release_git_lock() {
|
|
92
|
+
rm -f "$GIT_LOCK"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# ── Pre-eval gate ──
|
|
96
|
+
run_pre_eval_gate() {
|
|
97
|
+
local cwd="$PROJECT_ROOT"
|
|
98
|
+
|
|
99
|
+
if [ -f "$CONFIG" ]; then
|
|
100
|
+
_cwd=$(jq -r '.flow.pre_eval_gate.frontend_cwd // empty' "$CONFIG" 2>/dev/null)
|
|
101
|
+
if [ -n "$_cwd" ] && [ "$_cwd" != "null" ]; then
|
|
102
|
+
cwd="$PROJECT_ROOT/$_cwd"
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
local checks=()
|
|
107
|
+
if [ -f "$CONFIG" ]; then
|
|
108
|
+
mapfile -t checks < <(jq -r '.flow.pre_eval_gate.frontend_checks[]' "$CONFIG" 2>/dev/null)
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
if [ ${#checks[@]} -eq 0 ]; then
|
|
112
|
+
checks=("npx tsc --noEmit" "npx eslint src/")
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
local all_pass=true fail_cmds=""
|
|
116
|
+
for cmd in "${checks[@]}"; do
|
|
117
|
+
if (cd "$cwd" && timeout 120s bash -c "$cmd" >/dev/null 2>&1); then
|
|
118
|
+
log " ${GREEN}✓${RESET} $cmd"
|
|
119
|
+
else
|
|
120
|
+
log " ${RED}✗${RESET} $cmd"
|
|
121
|
+
all_pass=false
|
|
122
|
+
fail_cmds+="$cmd; "
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
|
|
126
|
+
[ "$all_pass" = true ]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# ── Build generator prompt ──
|
|
130
|
+
build_gen_prompt() {
|
|
131
|
+
local fid="$1" attempt="$2" feedback="${3:-}"
|
|
132
|
+
|
|
133
|
+
local fobj
|
|
134
|
+
fobj=$(jq --arg fid "$fid" '.features[] | select(.id == $fid)' "$FEATURES" 2>/dev/null)
|
|
135
|
+
local fname fdesc ac_json deps_json
|
|
136
|
+
fname=$(echo "$fobj" | jq -r '.name // .description // ""')
|
|
137
|
+
fdesc=$(echo "$fobj" | jq -r '.description // ""')
|
|
138
|
+
ac_json=$(echo "$fobj" | jq -c '.ac // []')
|
|
139
|
+
deps_json=$(echo "$fobj" | jq -c '.depends_on // []')
|
|
140
|
+
|
|
141
|
+
local project_name
|
|
142
|
+
project_name=$(jq -r '.project_name // ""' "$PROJECT_ROOT/.harness/progress.json" 2>/dev/null)
|
|
143
|
+
|
|
144
|
+
cat <<PROMPT
|
|
145
|
+
You are Generator-Frontend for a harness engineering project.
|
|
146
|
+
|
|
147
|
+
PROJECT: ${project_name}
|
|
148
|
+
CONVENTIONS: Read CONVENTIONS.md if it exists.
|
|
149
|
+
|
|
150
|
+
YOUR TASK: Implement ONLY feature ${fid}: ${fname}
|
|
151
|
+
Description: ${fdesc}
|
|
152
|
+
Dependencies (already implemented): ${deps_json}
|
|
153
|
+
Acceptance Criteria: ${ac_json}
|
|
154
|
+
|
|
155
|
+
Read these files for context:
|
|
156
|
+
- .harness/actions/feature-list.json (filter to ${fid})
|
|
157
|
+
- .harness/actions/api-contract.json (relevant endpoints)
|
|
158
|
+
- .harness/actions/plan.md (overall design)
|
|
159
|
+
|
|
160
|
+
RULES:
|
|
161
|
+
- Implement ONLY this single feature
|
|
162
|
+
- Do NOT modify code belonging to other features
|
|
163
|
+
- Follow existing code patterns and CONVENTIONS.md
|
|
164
|
+
- When done, stage and commit with: git add -A && git commit -m 'feat(${fid}): ${fname}'
|
|
165
|
+
PROMPT
|
|
166
|
+
|
|
167
|
+
if [ "$attempt" -gt 1 ] && [ -n "$feedback" ]; then
|
|
168
|
+
cat <<RETRY
|
|
169
|
+
|
|
170
|
+
PREVIOUS EVAL FEEDBACK (attempt ${attempt}):
|
|
171
|
+
${feedback}
|
|
172
|
+
|
|
173
|
+
Fix the issues above. Focus specifically on the failed criteria.
|
|
174
|
+
RETRY
|
|
175
|
+
fi
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# ── Build evaluator prompt ──
|
|
179
|
+
build_eval_prompt() {
|
|
180
|
+
local fid="$1"
|
|
181
|
+
|
|
182
|
+
local fobj
|
|
183
|
+
fobj=$(jq --arg fid "$fid" '.features[] | select(.id == $fid)' "$FEATURES" 2>/dev/null)
|
|
184
|
+
local fname ac_json
|
|
185
|
+
fname=$(echo "$fobj" | jq -r '.name // .description // ""')
|
|
186
|
+
ac_json=$(echo "$fobj" | jq -c '.ac // []')
|
|
187
|
+
|
|
188
|
+
local passed_list
|
|
189
|
+
passed_list=$(jq -r '.queue.passed // [] | join(", ")' "$QUEUE" 2>/dev/null)
|
|
190
|
+
|
|
191
|
+
cat <<PROMPT
|
|
192
|
+
You are Evaluator-Functional for a harness engineering project.
|
|
193
|
+
|
|
194
|
+
TASK: Evaluate feature ${fid}: ${fname}
|
|
195
|
+
|
|
196
|
+
Acceptance Criteria to verify:
|
|
197
|
+
${ac_json}
|
|
198
|
+
|
|
199
|
+
Previously passed features (regression check): [${passed_list}]
|
|
200
|
+
|
|
201
|
+
SCORING RUBRIC (R1-R5):
|
|
202
|
+
R1: API Contract compliance (25%)
|
|
203
|
+
R2: Acceptance Criteria full pass (25%)
|
|
204
|
+
R3: Negative tests (20%)
|
|
205
|
+
R4: E2E scenario (15%)
|
|
206
|
+
R5: Error handling & edge cases (15%)
|
|
207
|
+
|
|
208
|
+
PASS threshold: 2.80 / 3.00
|
|
209
|
+
FAIL: any AC not met, any regression failure
|
|
210
|
+
|
|
211
|
+
You MUST output this exact block (parseable by automation):
|
|
212
|
+
---EVAL-RESULT---
|
|
213
|
+
FEATURE: ${fid}
|
|
214
|
+
VERDICT: PASS or FAIL
|
|
215
|
+
SCORE: X.XX
|
|
216
|
+
FEEDBACK: one paragraph summary
|
|
217
|
+
---END-EVAL-RESULT---
|
|
218
|
+
PROMPT
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# ── Parse eval result (macOS-compatible, no grep -P) ──
|
|
222
|
+
parse_eval_result() {
|
|
223
|
+
local output="$1"
|
|
224
|
+
|
|
225
|
+
local verdict score feedback
|
|
226
|
+
verdict=$(echo "$output" | grep -E '^VERDICT:' | sed 's/VERDICT:[[:space:]]*//' | head -1)
|
|
227
|
+
score=$(echo "$output" | grep -E '^SCORE:' | sed 's/SCORE:[[:space:]]*//' | head -1)
|
|
228
|
+
feedback=$(echo "$output" | grep -E '^FEEDBACK:' | sed 's/FEEDBACK:[[:space:]]*//' | head -1)
|
|
229
|
+
|
|
230
|
+
echo "${verdict:-UNKNOWN}|${score:-0.00}|${feedback:-no feedback}"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# ══════════════════════════════════════════
|
|
234
|
+
# Main Worker Loop
|
|
235
|
+
# ══════════════════════════════════════════
|
|
236
|
+
log "${CYAN}Team ${TEAM_ID} started${RESET} (gen=${GEN_MODEL}, eval=${EVAL_MODEL}, max=${MAX_ATTEMPTS})"
|
|
237
|
+
log_progress "start" "Team ${TEAM_ID} worker started"
|
|
238
|
+
|
|
239
|
+
while true; do
|
|
240
|
+
# ── Dequeue next feature ──
|
|
241
|
+
feature_id=$(bash "$QUEUE_MGR" dequeue "$TEAM_ID" "$PROJECT_ROOT" 2>/dev/null)
|
|
242
|
+
|
|
243
|
+
if [ -z "$feature_id" ] || [[ "$feature_id" == "["* ]]; then
|
|
244
|
+
log "${DIM}No features ready. Waiting 10s...${RESET}"
|
|
245
|
+
sleep 10
|
|
246
|
+
|
|
247
|
+
# Check if completely done
|
|
248
|
+
remaining=$(jq '(.queue.ready | length) + (.queue.blocked | length) + (.queue.in_progress | length)' "$QUEUE" 2>/dev/null || echo "1")
|
|
249
|
+
if [ "${remaining}" -eq 0 ] 2>/dev/null; then
|
|
250
|
+
log "${GREEN}${BOLD}ALL FEATURES COMPLETE. Team ${TEAM_ID} exiting.${RESET}"
|
|
251
|
+
log_progress "complete" "All features done"
|
|
252
|
+
exit 0
|
|
253
|
+
fi
|
|
254
|
+
continue
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
log "${CYAN}▶ Dequeued ${feature_id}${RESET}"
|
|
258
|
+
log_progress "dequeue" "${feature_id}"
|
|
259
|
+
|
|
260
|
+
# ── Create feature branch (with lock) ──
|
|
261
|
+
branch="feature/${feature_id}"
|
|
262
|
+
acquire_git_lock
|
|
263
|
+
(cd "$PROJECT_ROOT" && git checkout main 2>/dev/null && git checkout -b "$branch" 2>/dev/null) || \
|
|
264
|
+
(cd "$PROJECT_ROOT" && git checkout "$branch" 2>/dev/null) || true
|
|
265
|
+
release_git_lock
|
|
266
|
+
log "Branch: ${branch}"
|
|
267
|
+
|
|
268
|
+
# ── Gen→Eval Loop ──
|
|
269
|
+
attempt=1
|
|
270
|
+
eval_feedback=""
|
|
271
|
+
passed=false
|
|
272
|
+
|
|
273
|
+
while [ "$attempt" -le "$MAX_ATTEMPTS" ]; do
|
|
274
|
+
log "${BOLD}── Attempt ${attempt}/${MAX_ATTEMPTS} ──${RESET}"
|
|
275
|
+
|
|
276
|
+
# ── Generate ──
|
|
277
|
+
log "Gen ${feature_id} (${GEN_MODEL})..."
|
|
278
|
+
bash "$QUEUE_MGR" update_phase "$feature_id" "gen" "$attempt" "$PROJECT_ROOT" 2>/dev/null
|
|
279
|
+
|
|
280
|
+
gen_prompt=$(build_gen_prompt "$feature_id" "$attempt" "$eval_feedback")
|
|
281
|
+
|
|
282
|
+
gen_start=$(date +%s)
|
|
283
|
+
log "${DIM} claude -p --dangerously-skip-permissions --model ${GEN_MODEL}${RESET}"
|
|
284
|
+
gen_output=$(cd "$PROJECT_ROOT" && claude -p "$gen_prompt" \
|
|
285
|
+
--dangerously-skip-permissions \
|
|
286
|
+
--model "$GEN_MODEL" \
|
|
287
|
+
--output-format text 2>&1 | tee /dev/stderr) 2>&1 || true
|
|
288
|
+
gen_elapsed=$(( $(date +%s) - gen_start ))
|
|
289
|
+
|
|
290
|
+
files_changed=$(cd "$PROJECT_ROOT" && git diff --name-only 2>/dev/null | wc -l | tr -d ' ')
|
|
291
|
+
log "Gen done (${gen_elapsed}s) — ${files_changed} files"
|
|
292
|
+
log_progress "gen" "${feature_id} attempt ${attempt}: ${files_changed} files, ${gen_elapsed}s"
|
|
293
|
+
|
|
294
|
+
# Auto-commit
|
|
295
|
+
(cd "$PROJECT_ROOT" && git add -A && git commit -m "feat(${feature_id}): gen attempt ${attempt}" --no-verify 2>/dev/null) || true
|
|
296
|
+
|
|
297
|
+
# ── Pre-eval gate ──
|
|
298
|
+
log "Pre-eval gate..."
|
|
299
|
+
bash "$QUEUE_MGR" update_phase "$feature_id" "gate" "$attempt" "$PROJECT_ROOT" 2>/dev/null
|
|
300
|
+
|
|
301
|
+
if ! run_pre_eval_gate "$feature_id"; then
|
|
302
|
+
log "${RED}Gate FAIL — retrying gen${RESET}"
|
|
303
|
+
eval_feedback="Pre-eval gate failed: type check or lint errors. Fix compilation and lint issues."
|
|
304
|
+
attempt=$((attempt + 1))
|
|
305
|
+
continue
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
# ── Evaluate ──
|
|
309
|
+
log "Eval ${feature_id} (${EVAL_MODEL})..."
|
|
310
|
+
bash "$QUEUE_MGR" update_phase "$feature_id" "eval" "$attempt" "$PROJECT_ROOT" 2>/dev/null
|
|
311
|
+
|
|
312
|
+
eval_prompt=$(build_eval_prompt "$feature_id")
|
|
313
|
+
|
|
314
|
+
eval_start=$(date +%s)
|
|
315
|
+
log "${DIM} claude -p --dangerously-skip-permissions --model ${EVAL_MODEL}${RESET}"
|
|
316
|
+
eval_output=$(cd "$PROJECT_ROOT" && claude -p "$eval_prompt" \
|
|
317
|
+
--dangerously-skip-permissions \
|
|
318
|
+
--model "$EVAL_MODEL" \
|
|
319
|
+
--output-format text 2>&1 | tee /dev/stderr) 2>&1 || true
|
|
320
|
+
eval_elapsed=$(( $(date +%s) - eval_start ))
|
|
321
|
+
|
|
322
|
+
# Parse result
|
|
323
|
+
result_line=$(parse_eval_result "$eval_output")
|
|
324
|
+
verdict=$(echo "$result_line" | cut -d'|' -f1)
|
|
325
|
+
score=$(echo "$result_line" | cut -d'|' -f2)
|
|
326
|
+
feedback=$(echo "$result_line" | cut -d'|' -f3-)
|
|
327
|
+
|
|
328
|
+
log_progress "eval" "${feature_id} attempt ${attempt}: ${verdict} (${score}) ${eval_elapsed}s"
|
|
329
|
+
|
|
330
|
+
if [ "$verdict" = "PASS" ]; then
|
|
331
|
+
log "${GREEN}${BOLD}✓ PASS${RESET} ${feature_id} — ${score}/3.00 (${eval_elapsed}s)"
|
|
332
|
+
passed=true
|
|
333
|
+
break
|
|
334
|
+
else
|
|
335
|
+
log "${RED}✗ FAIL${RESET} ${feature_id} — ${score}/3.00 (${eval_elapsed}s)"
|
|
336
|
+
log "${DIM} ${feedback}${RESET}"
|
|
337
|
+
eval_feedback="$feedback"
|
|
338
|
+
attempt=$((attempt + 1))
|
|
339
|
+
fi
|
|
340
|
+
done
|
|
341
|
+
|
|
342
|
+
# ══════════════════════════════════════════
|
|
343
|
+
# Phase 3: Branch merge with conflict handling
|
|
344
|
+
# ══════════════════════════════════════════
|
|
345
|
+
if [ "$passed" = true ]; then
|
|
346
|
+
log "Merging ${branch} → main..."
|
|
347
|
+
acquire_git_lock
|
|
348
|
+
|
|
349
|
+
merge_ok=false
|
|
350
|
+
|
|
351
|
+
# Attempt 1: straight merge
|
|
352
|
+
if (cd "$PROJECT_ROOT" && git checkout main 2>/dev/null && git merge --no-ff "$branch" -m "merge: ${feature_id} PASS" 2>/dev/null); then
|
|
353
|
+
merge_ok=true
|
|
354
|
+
else
|
|
355
|
+
# Attempt 2: abort failed merge, rebase, re-eval gate, then merge
|
|
356
|
+
log "${YELLOW}Conflict detected — rebasing ${branch} onto main...${RESET}"
|
|
357
|
+
(cd "$PROJECT_ROOT" && git merge --abort 2>/dev/null) || true
|
|
358
|
+
(cd "$PROJECT_ROOT" && git checkout "$branch" 2>/dev/null) || true
|
|
359
|
+
|
|
360
|
+
if (cd "$PROJECT_ROOT" && git rebase main 2>/dev/null); then
|
|
361
|
+
log "Rebase OK. Re-running gate..."
|
|
362
|
+
|
|
363
|
+
if run_pre_eval_gate "$feature_id"; then
|
|
364
|
+
log "Gate still PASS after rebase."
|
|
365
|
+
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
|
|
366
|
+
merge_ok=true
|
|
367
|
+
fi
|
|
368
|
+
else
|
|
369
|
+
log "${RED}Gate FAIL after rebase — needs re-gen${RESET}"
|
|
370
|
+
fi
|
|
371
|
+
else
|
|
372
|
+
log "${RED}Rebase failed — conflicts too complex${RESET}"
|
|
373
|
+
(cd "$PROJECT_ROOT" && git rebase --abort 2>/dev/null) || true
|
|
374
|
+
fi
|
|
375
|
+
fi
|
|
376
|
+
|
|
377
|
+
release_git_lock
|
|
378
|
+
|
|
379
|
+
if [ "$merge_ok" = true ]; then
|
|
380
|
+
# Clean up feature branch
|
|
381
|
+
(cd "$PROJECT_ROOT" && git branch -d "$branch" 2>/dev/null) || true
|
|
382
|
+
|
|
383
|
+
bash "$QUEUE_MGR" pass "$feature_id" "$PROJECT_ROOT" 2>/dev/null
|
|
384
|
+
log_progress "pass" "${feature_id} merged to main"
|
|
385
|
+
|
|
386
|
+
# Update feature-list.json passes
|
|
387
|
+
if [ -f "$FEATURES" ]; then
|
|
388
|
+
jq --arg fid "$feature_id" '
|
|
389
|
+
.features |= map(
|
|
390
|
+
if .id == $fid then
|
|
391
|
+
.passes = ((.passes // []) + ["generator-frontend", "evaluator-functional"] | unique)
|
|
392
|
+
else . end
|
|
393
|
+
)
|
|
394
|
+
' "$FEATURES" > "${FEATURES}.tmp" && mv "${FEATURES}.tmp" "$FEATURES"
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
log "${GREEN}${BOLD}✓ ${feature_id} DONE${RESET}"
|
|
398
|
+
else
|
|
399
|
+
log "${RED}${BOLD}Merge failed — ${feature_id} marked as failed${RESET}"
|
|
400
|
+
(cd "$PROJECT_ROOT" && git checkout main 2>/dev/null) || true
|
|
401
|
+
bash "$QUEUE_MGR" fail "$feature_id" "$PROJECT_ROOT" 2>/dev/null
|
|
402
|
+
log_progress "merge-fail" "${feature_id}"
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
else
|
|
406
|
+
log "${RED}${BOLD}✗ ${feature_id} FAILED after ${MAX_ATTEMPTS} attempts${RESET}"
|
|
407
|
+
acquire_git_lock
|
|
408
|
+
(cd "$PROJECT_ROOT" && git checkout main 2>/dev/null) || true
|
|
409
|
+
release_git_lock
|
|
410
|
+
bash "$QUEUE_MGR" fail "$feature_id" "$PROJECT_ROOT" 2>/dev/null
|
|
411
|
+
log_progress "fail" "${feature_id} after ${MAX_ATTEMPTS} attempts"
|
|
412
|
+
fi
|
|
413
|
+
|
|
414
|
+
sleep 2
|
|
415
|
+
done
|
|
@@ -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}
|
|
@@ -60,6 +60,32 @@ 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)
|
|
64
|
+
|
|
65
|
+
v4에서 Team Worker가 `claude -p`로 호출할 때, 프롬프트에 `FEATURE_ID`가 지정된다.
|
|
66
|
+
|
|
67
|
+
### Feature-Level Rules
|
|
68
|
+
- `feature-list.json`에서 **지정된 FEATURE_ID의 AC만** 검증
|
|
69
|
+
- Regression: `feature-queue.json`의 `passed` 목록에 있는 Feature들의 AC 재검증
|
|
70
|
+
- Cross-Validation: Feature 단위에서는 skip (Sprint-End에서 수행)
|
|
71
|
+
- Visual Evaluation: Feature 단위에서는 skip (Sprint-End에서 수행)
|
|
72
|
+
- 출력 형식: `---EVAL-RESULT---` 블록 (Worker가 파싱 가능)
|
|
73
|
+
|
|
74
|
+
### Feature-Level Scoring
|
|
75
|
+
- 동일한 R1-R5 루브릭 적용
|
|
76
|
+
- PASS 기준: 2.80/3.00 (변경 없음)
|
|
77
|
+
- 1건이라도 Regression 실패 시 FAIL (변경 없음)
|
|
78
|
+
|
|
79
|
+
### Output Format (Machine-Parseable)
|
|
80
|
+
```
|
|
81
|
+
---EVAL-RESULT---
|
|
82
|
+
FEATURE: F-XXX
|
|
83
|
+
VERDICT: PASS or FAIL
|
|
84
|
+
SCORE: X.XX
|
|
85
|
+
FEEDBACK: one paragraph summary
|
|
86
|
+
---END-EVAL-RESULT---
|
|
87
|
+
```
|
|
88
|
+
|
|
63
89
|
## Evaluation Steps
|
|
64
90
|
|
|
65
91
|
### Step 0: IA Structure Compliance (GATE)
|
|
@@ -43,6 +43,23 @@ disable-model-invocation: true
|
|
|
43
43
|
|
|
44
44
|
**Backend 통합 러너가 동작 중이어야 함.** Gateway 미응답 시 → STOP.
|
|
45
45
|
|
|
46
|
+
## v4 Feature-Level Mode (Parallel Agent Teams)
|
|
47
|
+
|
|
48
|
+
v4에서 Team Worker가 `claude -p`로 호출할 때, 프롬프트에 `FEATURE_ID`가 지정된다.
|
|
49
|
+
|
|
50
|
+
### Feature-Level Rules
|
|
51
|
+
- `feature-list.json`에서 **지정된 FEATURE_ID만** 필터하여 구현
|
|
52
|
+
- 다른 Feature의 코드를 수정하지 않음
|
|
53
|
+
- `depends_on`에 명시된 Feature는 이미 구현/머지 완료된 상태
|
|
54
|
+
- Feature branch (`feature/F-XXX`)에서 작업, 완료 시 commit
|
|
55
|
+
- Sprint Contract는 작성하지 않음 (v4에서는 Feature 단위로 관리)
|
|
56
|
+
|
|
57
|
+
### Feature-Level Prompt Template
|
|
58
|
+
Worker가 전달하는 프롬프트에는 다음이 포함됨:
|
|
59
|
+
- `FEATURE_ID`, `feature_name`, `description`, `ac` (Acceptance Criteria)
|
|
60
|
+
- `depends_on` (이미 완료된 의존 Feature 목록)
|
|
61
|
+
- Eval 재시도 시: 이전 Eval의 피드백
|
|
62
|
+
|
|
46
63
|
## Sprint Workflow
|
|
47
64
|
|
|
48
65
|
1. **Sprint Contract FE 섹션 추가** — 컴포넌트, API 연동, 성공 기준
|