@walwal-harness/cli 4.0.0-beta.2 → 4.0.0-beta.4
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/bin/init.js +95 -11
- package/package.json +4 -2
- package/scripts/harness-prompt-history.sh +126 -0
- package/scripts/harness-studio-setup.sh +88 -0
- package/scripts/harness-studio.sh +66 -0
- package/scripts/harness-tmux-v4.sh +136 -0
- package/scripts/harness-user-prompt-submit.sh +13 -10
- package/skills/team-action/SKILL.md +142 -10
package/bin/init.js
CHANGED
|
@@ -129,17 +129,56 @@ function scaffoldHarness() {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// config.json — ALWAYS update (harness system file, not user data)
|
|
133
|
+
// But preserve user's custom settings (pre_eval_gate.frontend_cwd, behavior, etc.)
|
|
133
134
|
const configSrc = path.join(PKG_ROOT, 'assets', 'templates', 'config.json');
|
|
134
135
|
const configDest = path.join(HARNESS_DIR, 'config.json');
|
|
135
|
-
if (fs.existsSync(configSrc)
|
|
136
|
-
|
|
136
|
+
if (fs.existsSync(configSrc)) {
|
|
137
|
+
if (fileExists(configDest) && !isForce) {
|
|
138
|
+
// Merge: keep user's customizations, update harness structure
|
|
139
|
+
try {
|
|
140
|
+
const existing = JSON.parse(fs.readFileSync(configDest, 'utf8'));
|
|
141
|
+
const template = JSON.parse(fs.readFileSync(configSrc, 'utf8'));
|
|
142
|
+
// Preserve user customizations
|
|
143
|
+
const userPreserve = {
|
|
144
|
+
behavior: existing.behavior,
|
|
145
|
+
'flow.pre_eval_gate.frontend_cwd': existing?.flow?.pre_eval_gate?.frontend_cwd,
|
|
146
|
+
'flow.pre_eval_gate.backend_cwd': existing?.flow?.pre_eval_gate?.backend_cwd,
|
|
147
|
+
'flow.pre_eval_gate.frontend_checks': existing?.flow?.pre_eval_gate?.frontend_checks,
|
|
148
|
+
'flow.pre_eval_gate.backend_checks': existing?.flow?.pre_eval_gate?.backend_checks,
|
|
149
|
+
};
|
|
150
|
+
// Write template, then re-apply user settings
|
|
151
|
+
fs.writeFileSync(configDest, JSON.stringify(template, null, 2) + '\n');
|
|
152
|
+
// Re-apply preserved user settings
|
|
153
|
+
const merged = JSON.parse(fs.readFileSync(configDest, 'utf8'));
|
|
154
|
+
if (userPreserve.behavior) merged.behavior = userPreserve.behavior;
|
|
155
|
+
if (userPreserve['flow.pre_eval_gate.frontend_cwd']) {
|
|
156
|
+
merged.flow.pre_eval_gate.frontend_cwd = userPreserve['flow.pre_eval_gate.frontend_cwd'];
|
|
157
|
+
}
|
|
158
|
+
if (userPreserve['flow.pre_eval_gate.backend_cwd']) {
|
|
159
|
+
merged.flow.pre_eval_gate.backend_cwd = userPreserve['flow.pre_eval_gate.backend_cwd'];
|
|
160
|
+
}
|
|
161
|
+
if (userPreserve['flow.pre_eval_gate.frontend_checks']) {
|
|
162
|
+
merged.flow.pre_eval_gate.frontend_checks = userPreserve['flow.pre_eval_gate.frontend_checks'];
|
|
163
|
+
}
|
|
164
|
+
if (userPreserve['flow.pre_eval_gate.backend_checks']) {
|
|
165
|
+
merged.flow.pre_eval_gate.backend_checks = userPreserve['flow.pre_eval_gate.backend_checks'];
|
|
166
|
+
}
|
|
167
|
+
fs.writeFileSync(configDest, JSON.stringify(merged, null, 2) + '\n');
|
|
168
|
+
log('config.json updated (user settings preserved)');
|
|
169
|
+
} catch (e) {
|
|
170
|
+
copyFile(configSrc, configDest);
|
|
171
|
+
log('config.json replaced (merge failed)');
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
copyFile(configSrc, configDest);
|
|
175
|
+
}
|
|
137
176
|
}
|
|
138
177
|
|
|
139
|
-
//
|
|
178
|
+
// HARNESS.md — ALWAYS update
|
|
140
179
|
const harnessMdSrc = path.join(PKG_ROOT, 'assets', 'templates', 'HARNESS.md');
|
|
141
180
|
const harnessMdDest = path.join(HARNESS_DIR, 'HARNESS.md');
|
|
142
|
-
if (fs.existsSync(harnessMdSrc)
|
|
181
|
+
if (fs.existsSync(harnessMdSrc)) {
|
|
143
182
|
copyFile(harnessMdSrc, harnessMdDest);
|
|
144
183
|
}
|
|
145
184
|
|
|
@@ -190,16 +229,23 @@ function installSkills() {
|
|
|
190
229
|
.filter(d => d.isDirectory())
|
|
191
230
|
.map(d => d.name);
|
|
192
231
|
|
|
232
|
+
// Remove obsolete skills (cleaned up in v4)
|
|
233
|
+
const obsoleteSkills = ['harness-generator-frontend-flutter', 'harness-evaluator-functional-flutter', 'harness-team'];
|
|
234
|
+
for (const obs of obsoleteSkills) {
|
|
235
|
+
const obsPath = path.join(CLAUDE_SKILLS_DIR, obs);
|
|
236
|
+
if (fs.existsSync(obsPath)) {
|
|
237
|
+
fs.rmSync(obsPath, { recursive: true, force: true });
|
|
238
|
+
log(` Removed obsolete: ${obs}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
193
242
|
for (const skill of skills) {
|
|
194
243
|
const src = path.join(skillsSrc, skill);
|
|
195
244
|
const dest = path.join(CLAUDE_SKILLS_DIR, `harness-${skill}`);
|
|
196
245
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
} else {
|
|
201
|
-
log(` Skipped (exists): harness-${skill}`);
|
|
202
|
-
}
|
|
246
|
+
// Skills are ALWAYS overwritten — they are harness-managed, not user-editable
|
|
247
|
+
copyDir(src, dest);
|
|
248
|
+
log(` Installed: harness-${skill}`);
|
|
203
249
|
}
|
|
204
250
|
|
|
205
251
|
log('Skills installation complete');
|
|
@@ -214,6 +260,21 @@ function installScripts() {
|
|
|
214
260
|
const scriptsSrc = path.join(PKG_ROOT, 'scripts');
|
|
215
261
|
const scriptsDest = path.join(PROJECT_ROOT, 'scripts');
|
|
216
262
|
|
|
263
|
+
// Remove obsolete scripts from previous versions
|
|
264
|
+
const obsoleteScripts = [
|
|
265
|
+
'harness-studio-v4.sh',
|
|
266
|
+
'harness-control-v4.sh',
|
|
267
|
+
'harness-prompts-v4.sh',
|
|
268
|
+
'harness-team-worker.sh',
|
|
269
|
+
];
|
|
270
|
+
for (const obs of obsoleteScripts) {
|
|
271
|
+
const obsPath = path.join(scriptsDest, obs);
|
|
272
|
+
if (fs.existsSync(obsPath)) {
|
|
273
|
+
fs.unlinkSync(obsPath);
|
|
274
|
+
log(` Removed obsolete: ${obs}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
217
278
|
// Core scripts are ALWAYS overwritten on update (not user-editable)
|
|
218
279
|
// These contain harness logic that must stay in sync with the CLI version
|
|
219
280
|
const coreScripts = new Set([
|
|
@@ -421,6 +482,28 @@ function installUserPromptSubmitHook() {
|
|
|
421
482
|
// ─────────────────────────────────────────
|
|
422
483
|
// 4. AGENTS.md + CLAUDE.md
|
|
423
484
|
// ─────────────────────────────────────────
|
|
485
|
+
// ─────────────────────────────────────────
|
|
486
|
+
// 3d. Agent Teams env var
|
|
487
|
+
// ─────────────────────────────────────────
|
|
488
|
+
function installAgentTeamsEnv() {
|
|
489
|
+
const settingsPath = path.join(PROJECT_ROOT, '.claude', 'settings.json');
|
|
490
|
+
let settings = {};
|
|
491
|
+
if (fileExists(settingsPath)) {
|
|
492
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (e) {}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!settings.env) settings.env = {};
|
|
496
|
+
|
|
497
|
+
if (settings.env['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] !== '1') {
|
|
498
|
+
settings.env['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] = '1';
|
|
499
|
+
ensureDir(path.dirname(settingsPath));
|
|
500
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
501
|
+
log('Agent Teams enabled (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)');
|
|
502
|
+
} else {
|
|
503
|
+
log('Agent Teams already enabled');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
424
507
|
function setupAgentsMd() {
|
|
425
508
|
const agentsMd = path.join(PROJECT_ROOT, 'AGENTS.md');
|
|
426
509
|
const claudeMd = path.join(PROJECT_ROOT, 'CLAUDE.md');
|
|
@@ -703,6 +786,7 @@ function main() {
|
|
|
703
786
|
installSessionHook();
|
|
704
787
|
installStatusline();
|
|
705
788
|
installUserPromptSubmitHook();
|
|
789
|
+
installAgentTeamsEnv();
|
|
706
790
|
setupAgentsMd();
|
|
707
791
|
checkPlaywrightMcp();
|
|
708
792
|
checkRecommendedSkills();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.4",
|
|
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,88 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-studio-setup.sh — Claude 세션 내부에서 실행하여 3-column 레이아웃 자동 구축
|
|
3
|
+
#
|
|
4
|
+
# Claude가 이미 실행 중인 tmux pane에서 호출됨.
|
|
5
|
+
# 현재 pane(Claude가 있는 곳)을 Left로 유지하고,
|
|
6
|
+
# Center(Dashboard + History)와 Right(Monitor)를 split으로 생성.
|
|
7
|
+
#
|
|
8
|
+
# ┌──────────────┬──────────────┬──────────────┐
|
|
9
|
+
# │ │ Dashboard │ │
|
|
10
|
+
# │ 여기서 │ (v4 queue) │ Team Monitor│
|
|
11
|
+
# │ Claude 실행중├──────────────┤ (lifecycle) │
|
|
12
|
+
# │ │ Command │ │
|
|
13
|
+
# │ │ History │ │
|
|
14
|
+
# └──────────────┴──────────────┴──────────────┘
|
|
15
|
+
#
|
|
16
|
+
# Usage (Claude가 bash 도구로 호출):
|
|
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
|
+
|
|
25
|
+
PROJECT_ROOT="${1:-}"
|
|
26
|
+
if [ -z "$PROJECT_ROOT" ]; then
|
|
27
|
+
dir="$(pwd)"
|
|
28
|
+
while [ "$dir" != "/" ]; do
|
|
29
|
+
if [ -d "$dir/.harness" ]; then PROJECT_ROOT="$dir"; break; fi
|
|
30
|
+
dir="$(dirname "$dir")"
|
|
31
|
+
done
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
|
|
35
|
+
echo "[studio] .harness/ not found."
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# ── tmux 내부인지 확인 ──
|
|
40
|
+
if [ -z "${TMUX:-}" ]; then
|
|
41
|
+
echo "[studio] Not inside tmux. Layout requires tmux."
|
|
42
|
+
echo "[studio] Start with: tmux new-session -s harness"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# ── 멱등성: 이미 pane이 3개 이상이면 skip ──
|
|
47
|
+
PANE_COUNT=$(tmux list-panes | wc -l | tr -d ' ')
|
|
48
|
+
if [ "$PANE_COUNT" -ge 3 ]; then
|
|
49
|
+
echo "[studio] Layout already set up ($PANE_COUNT panes). Skipping."
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# ── 현재 pane = Claude (Left) ──
|
|
54
|
+
PANE_CLAUDE=$(tmux display-message -p '#{pane_id}')
|
|
55
|
+
|
|
56
|
+
echo "[studio] Setting up 3-column layout..."
|
|
57
|
+
|
|
58
|
+
# ── Split: Left 35% | Right 65% ──
|
|
59
|
+
PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_CLAUDE" -c "$PROJECT_ROOT" \
|
|
60
|
+
-P -F '#{pane_id}' \
|
|
61
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
|
|
62
|
+
|
|
63
|
+
# ── Split right: Middle 45% | Right 55% ──
|
|
64
|
+
PANE_RIGHT=$(tmux split-window -h -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
|
|
65
|
+
-P -F '#{pane_id}' \
|
|
66
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
|
|
67
|
+
|
|
68
|
+
# ── Split middle vertically: Dashboard (top 45%) | History (bottom 55%) ──
|
|
69
|
+
PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
|
|
70
|
+
-P -F '#{pane_id}' \
|
|
71
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
|
|
72
|
+
|
|
73
|
+
# ── Pane titles ──
|
|
74
|
+
tmux select-pane -t "$PANE_CLAUDE" -T "Lead (Claude)"
|
|
75
|
+
tmux select-pane -t "$PANE_MID" -T "Dashboard"
|
|
76
|
+
tmux select-pane -t "$PANE_HISTORY" -T "Command History"
|
|
77
|
+
tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
|
|
78
|
+
|
|
79
|
+
tmux set-option pane-border-status top 2>/dev/null || true
|
|
80
|
+
tmux set-option pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
81
|
+
|
|
82
|
+
# ── 포커스를 Claude pane으로 돌림 ──
|
|
83
|
+
tmux select-pane -t "$PANE_CLAUDE"
|
|
84
|
+
|
|
85
|
+
echo "[studio] Layout ready."
|
|
86
|
+
echo "[studio] Left : Lead (Claude) — you are here"
|
|
87
|
+
echo "[studio] Center : Dashboard + Command History"
|
|
88
|
+
echo "[studio] Right : Team Monitor"
|
|
@@ -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,158 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-team-action
|
|
3
|
-
description: "v4 Agent Teams 가동.
|
|
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
|
+
tmux 내부에서 실행 중이면, 3-column 레이아웃을 자동 구축합니다:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bash scripts/harness-studio-setup.sh .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
> 이미 구축됐으면 자동 skip (멱등).
|
|
18
|
+
> tmux 밖이면 경고만 출력하고 계속 진행 (필수는 아님).
|
|
19
|
+
|
|
9
20
|
## Step 1: Queue 초기화
|
|
10
21
|
|
|
11
22
|
```bash
|
|
12
23
|
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
24
|
```
|
|
14
25
|
|
|
15
|
-
## Step 2:
|
|
26
|
+
## Step 2: Feature 할당 (dequeue)
|
|
27
|
+
|
|
28
|
+
Queue status 결과를 확인하여 `ready` 큐에 feature가 있는지 확인합니다.
|
|
29
|
+
ready feature 수와 설정된 concurrency 중 작은 값만큼 팀을 생성합니다 (최대 3).
|
|
30
|
+
|
|
31
|
+
**각 팀마다** dequeue 명령으로 feature를 원자적으로 할당합니다:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bash scripts/harness-queue-manager.sh dequeue {TEAM_NUMBER} .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
dequeue 결과로 feature ID가 반환됩니다. 빈 결과면 해당 팀은 생성하지 않습니다.
|
|
38
|
+
|
|
39
|
+
## Step 3: Agent 도구로 팀 생성 (Gen↔Eval 분리 사이클)
|
|
40
|
+
|
|
41
|
+
dequeue로 할당받은 feature마다 **Agent 도구**를 호출합니다.
|
|
42
|
+
**반드시 `isolation: "worktree"`를 사용**하여 각 팀이 독립된 코드 복사본에서 작업합니다.
|
|
43
|
+
|
|
44
|
+
**독립적인 팀들은 단일 메시지에서 병렬로 호출**하세요 (한 번의 응답에 여러 Agent 도구 호출).
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Agent({
|
|
48
|
+
description: "Team-{N}: {FEATURE_ID}",
|
|
49
|
+
isolation: "worktree",
|
|
50
|
+
prompt: "<아래 Team Worker 프롬프트>"
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Team Worker 프롬프트
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
당신은 Harness Team-{N} 워커입니다. 하나의 Feature에 대해 Gen→Eval 사이클을 수행합니다.
|
|
58
|
+
|
|
59
|
+
## 할당된 Feature
|
|
60
|
+
- Feature ID: {FEATURE_ID}
|
|
61
|
+
- 프로젝트 루트: 현재 디렉토리 (worktree 복사본)
|
|
62
|
+
|
|
63
|
+
## Phase 1: Generator (코드 생성)
|
|
64
|
+
|
|
65
|
+
1. Feature 정보 확인:
|
|
66
|
+
- `jq '.features[] | select(.id == "{FEATURE_ID}")' .harness/actions/feature-list.json`
|
|
67
|
+
- `.harness/actions/api-contract.json`에서 관련 엔드포인트 확인
|
|
68
|
+
- AC(Acceptance Criteria) 목록을 정확히 파악
|
|
69
|
+
|
|
70
|
+
2. 코드 생성:
|
|
71
|
+
- AGENTS.md의 IA-MAP에 따라 올바른 디렉토리에 코드 작성
|
|
72
|
+
- AC의 모든 항목을 충족하도록 구현
|
|
73
|
+
|
|
74
|
+
3. Pre-eval 게이트 (자체):
|
|
75
|
+
- tsc (타입 체크) 실행
|
|
76
|
+
- eslint (린트) 실행
|
|
77
|
+
- 컴파일 에러가 있으면 직접 수정 (Eval에 넘기지 않음)
|
|
78
|
+
|
|
79
|
+
## Phase 2: Evaluator (독립 평가 — Agent 도구 사용)
|
|
80
|
+
|
|
81
|
+
코드 생성이 완료되면 **별도 Agent를 생성하여 평가**합니다.
|
|
82
|
+
이 Evaluator Agent는 당신(Generator)의 추론 과정을 모릅니다.
|
|
83
|
+
오직 코드와 AC만 보고 판단합니다.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Agent({
|
|
87
|
+
description: "Eval: {FEATURE_ID}",
|
|
88
|
+
prompt: "<아래 Evaluator 프롬프트>"
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Evaluator 프롬프트
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
당신은 독립 Evaluator입니다. Generator가 작성한 코드를 AC 기준으로 냉정하게 평가합니다.
|
|
96
|
+
Generator의 의도나 추론 과정은 알 수 없습니다. 오직 코드와 결과만 봅니다.
|
|
97
|
+
|
|
98
|
+
## 평가 대상
|
|
99
|
+
- Feature ID: {FEATURE_ID}
|
|
100
|
+
- AC 확인: `jq '.features[] | select(.id == "{FEATURE_ID}").acceptance_criteria' .harness/actions/feature-list.json`
|
|
101
|
+
|
|
102
|
+
## 평가 기준
|
|
103
|
+
1. AC 100% 충족 여부 (부분 통과 = FAIL)
|
|
104
|
+
2. api-contract.json과의 일치 여부 (엔드포인트, 요청/응답 스키마)
|
|
105
|
+
3. tsc, eslint 통과 여부
|
|
106
|
+
4. 보안 취약점 여부 (OWASP Top 10)
|
|
107
|
+
5. 기존 코드와의 regression 여부
|
|
108
|
+
|
|
109
|
+
## 출력 형식
|
|
110
|
+
반드시 아래 형식으로 결과를 반환하세요:
|
|
111
|
+
|
|
112
|
+
VERDICT: PASS 또는 FAIL
|
|
113
|
+
SCORE: X.XX / 3.00
|
|
114
|
+
EVIDENCE:
|
|
115
|
+
- AC-1: [PASS/FAIL] 근거
|
|
116
|
+
- AC-2: [PASS/FAIL] 근거
|
|
117
|
+
- ...
|
|
118
|
+
FEEDBACK: (FAIL인 경우만) 구체적 수정 지시
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Phase 3: 결과 처리
|
|
122
|
+
|
|
123
|
+
Evaluator Agent 결과를 확인합니다:
|
|
124
|
+
|
|
125
|
+
### PASS인 경우 (VERDICT: PASS, SCORE ≥ 2.80):
|
|
126
|
+
1. `bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .` 실행
|
|
127
|
+
2. 변경 파일 목록과 AC 충족 요약을 Lead에게 반환
|
|
128
|
+
|
|
129
|
+
### FAIL인 경우:
|
|
130
|
+
1. Evaluator의 FEEDBACK을 읽고 코드를 수정 (Phase 1로 돌아감)
|
|
131
|
+
2. 수정 후 다시 Phase 2 (새 Evaluator Agent 생성 — 이전 Eval 컨텍스트 없음)
|
|
132
|
+
3. 최대 3회 시도. 3회 모두 FAIL이면:
|
|
133
|
+
- `bash scripts/harness-queue-manager.sh fail {FEATURE_ID} .` 실행
|
|
134
|
+
- 실패 사유와 마지막 Eval 결과를 Lead에게 반환
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Step 4: 결과 수집 및 다음 라운드
|
|
138
|
+
|
|
139
|
+
모든 Team Agent가 완료되면:
|
|
140
|
+
1. 각 Agent 반환 메시지에서 PASS/FAIL 확인
|
|
141
|
+
2. Queue 상태 재확인: `bash scripts/harness-queue-manager.sh status .`
|
|
142
|
+
3. ready 큐에 새로 unblock된 feature가 있으면 → **Step 2로 돌아가서** 추가 팀 생성
|
|
143
|
+
4. ready=0, in_progress=0이면 → 최종 결과 보고
|
|
144
|
+
|
|
145
|
+
## 핵심 원칙
|
|
16
146
|
|
|
17
|
-
|
|
147
|
+
### 자기 의식 편향 차단
|
|
148
|
+
- Generator가 자기 코드를 평가하지 않음
|
|
149
|
+
- Evaluator는 항상 새 Agent (Generator의 추론 과정을 모름)
|
|
150
|
+
- FAIL 후 재시도 시에도 새 Evaluator를 생성 (이전 Eval 기억 없음)
|
|
18
151
|
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
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에게 결과 보고
|
|
152
|
+
### 중복 방지
|
|
153
|
+
- dequeue는 원자적 (lock 사용) — 같은 feature를 두 번 할당 불가
|
|
154
|
+
- ready가 0이면 팀을 생성하지 않음
|
|
25
155
|
|
|
26
|
-
|
|
156
|
+
### 격리
|
|
157
|
+
- 각 팀은 `isolation: "worktree"`로 독립 코드 복사본에서 작업
|
|
158
|
+
- 팀 간 코드 충돌 없음
|