@walwal-harness/cli 4.0.0-alpha.9 → 4.0.0-beta.2
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 +235 -273
- package/assets/templates/config.json +1 -48
- package/assets/templates/gitignore-append.txt +1 -0
- package/bin/init.js +42 -18
- package/package.json +1 -1
- package/scripts/harness-dashboard-v4.sh +27 -88
- package/scripts/harness-next.sh +4 -15
- package/scripts/harness-user-prompt-submit.sh +10 -0
- package/skills/dispatcher/SKILL.md +7 -2
- package/skills/team-action/SKILL.md +26 -0
- package/skills/team-stop/SKILL.md +19 -0
- package/scripts/harness-control-v4.sh +0 -97
- package/scripts/harness-studio-v4.sh +0 -122
- package/scripts/harness-team-worker.sh +0 -415
- 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
|
@@ -133,62 +133,15 @@
|
|
|
133
133
|
]
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
|
-
"generator-frontend-flutter": {
|
|
137
|
-
"role": "Flutter 앱 개발 — Riverpod, integrated_data_layer(Retrofit), i18n(ARB), build_runner",
|
|
138
|
-
"skill": "harness-generator-frontend-flutter",
|
|
139
|
-
"inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
|
|
140
|
-
"outputs": ["code:flutter", "actions/sprint-contract.md"],
|
|
141
|
-
"order": 2,
|
|
142
|
-
"fe_stack": "flutter",
|
|
143
|
-
"model": "sonnet",
|
|
144
|
-
"thinking_mode": null
|
|
145
|
-
},
|
|
146
|
-
"evaluator-functional-flutter": {
|
|
147
|
-
"role": "Flutter 앱 검증 — flutter analyze, flutter test, build_runner 일관성, 안티패턴 정적 검증",
|
|
148
|
-
"skill": "harness-evaluator-functional-flutter",
|
|
149
|
-
"tools": ["bash:flutter", "bash:dart"],
|
|
150
|
-
"inputs": ["actions/sprint-contract.md"],
|
|
151
|
-
"outputs": ["actions/evaluation-functional.md"],
|
|
152
|
-
"fe_stack": "flutter",
|
|
153
|
-
"model": "opus",
|
|
154
|
-
"thinking_mode": "ultrathink"
|
|
155
|
-
}
|
|
156
136
|
},
|
|
157
137
|
"flow": {
|
|
158
138
|
"sequence": ["dispatcher", "planner", "generator-backend", "generator-frontend", "evaluator-functional", "evaluator-visual"],
|
|
159
139
|
"pipeline_selection": {
|
|
160
|
-
"comment": "Dispatcher가 pipeline.json으로 활성 에이전트를 결정.
|
|
140
|
+
"comment": "Dispatcher가 pipeline.json으로 활성 에이전트를 결정.",
|
|
161
141
|
"pipelines": {
|
|
162
142
|
"FULLSTACK": ["planner", "generator-backend", "generator-frontend", "evaluator-functional", "evaluator-visual"],
|
|
163
143
|
"FE-ONLY": ["planner:light", "generator-frontend", "evaluator-functional", "evaluator-visual"],
|
|
164
144
|
"BE-ONLY": ["planner", "generator-backend", "evaluator-functional:api-only"]
|
|
165
|
-
},
|
|
166
|
-
"fe_stack_substitution": {
|
|
167
|
-
"comment": "pipeline.json.fe_stack + fe_target 에 따라 FE 에이전트 치환. Flutter Web 은 React 와 동일한 Playwright 기반 evaluator 를 사용한다.",
|
|
168
|
-
"flutter": {
|
|
169
|
-
"_doc": "fe_target = web | mobile | desktop. by_target 으로 분기.",
|
|
170
|
-
"by_target": {
|
|
171
|
-
"web": {
|
|
172
|
-
"_doc": "Flutter Web — 컴파일 결과가 HTML+JS+CSS 이므로 Playwright evaluator 사용 가능",
|
|
173
|
-
"generator-frontend": "generator-frontend-flutter",
|
|
174
|
-
"evaluator-functional": "evaluator-functional",
|
|
175
|
-
"evaluator-visual": "evaluator-visual"
|
|
176
|
-
},
|
|
177
|
-
"mobile": {
|
|
178
|
-
"_doc": "Flutter Mobile (Android/iOS) — 브라우저 없음, 정적 분석 evaluator 사용",
|
|
179
|
-
"generator-frontend": "generator-frontend-flutter",
|
|
180
|
-
"evaluator-functional": "evaluator-functional-flutter",
|
|
181
|
-
"evaluator-visual": "__skip__"
|
|
182
|
-
},
|
|
183
|
-
"desktop": {
|
|
184
|
-
"_doc": "Flutter Desktop (macOS/Windows/Linux) — 브라우저 없음, 정적 분석 evaluator 사용",
|
|
185
|
-
"generator-frontend": "generator-frontend-flutter",
|
|
186
|
-
"evaluator-functional": "evaluator-functional-flutter",
|
|
187
|
-
"evaluator-visual": "__skip__"
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
"_default_target": "mobile"
|
|
191
|
-
}
|
|
192
145
|
}
|
|
193
146
|
},
|
|
194
147
|
"sprint_execution": {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# For consumer projects, add .worktrees/ to .gitignore
|
package/bin/init.js
CHANGED
|
@@ -226,11 +226,8 @@ function installScripts() {
|
|
|
226
226
|
'harness-eval-watcher.sh',
|
|
227
227
|
'harness-tmux.sh',
|
|
228
228
|
'harness-control.sh',
|
|
229
|
-
'harness-studio-v4.sh',
|
|
230
229
|
'harness-dashboard-v4.sh',
|
|
231
|
-
'harness-control-v4.sh',
|
|
232
230
|
'harness-queue-manager.sh',
|
|
233
|
-
'harness-team-worker.sh',
|
|
234
231
|
]);
|
|
235
232
|
|
|
236
233
|
if (fs.existsSync(scriptsSrc)) {
|
|
@@ -575,7 +572,7 @@ Usage:
|
|
|
575
572
|
npx walwal-harness --force Re-initialize (overwrites existing files)
|
|
576
573
|
npx walwal-harness studio Launch Harness Studio v3 (tmux 5-pane)
|
|
577
574
|
npx walwal-harness studio --ai Studio v3 + AI eval summary
|
|
578
|
-
npx walwal-harness v4
|
|
575
|
+
npx walwal-harness v4 Enable Agent Teams (set env var + init queue)
|
|
579
576
|
npx walwal-harness --help Show this help
|
|
580
577
|
|
|
581
578
|
What it does:
|
|
@@ -626,24 +623,51 @@ function runStudio() {
|
|
|
626
623
|
}
|
|
627
624
|
|
|
628
625
|
function runStudioV4() {
|
|
629
|
-
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
if (
|
|
633
|
-
|
|
634
|
-
process.exit(1);
|
|
626
|
+
// Enable Agent Teams in project settings
|
|
627
|
+
const settingsPath = path.join(PROJECT_ROOT, '.claude', 'settings.json');
|
|
628
|
+
let settings = {};
|
|
629
|
+
if (fileExists(settingsPath)) {
|
|
630
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (e) {}
|
|
635
631
|
}
|
|
636
632
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
633
|
+
if (!settings.env) settings.env = {};
|
|
634
|
+
settings.env['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] = '1';
|
|
635
|
+
ensureDir(path.dirname(settingsPath));
|
|
636
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
637
|
+
|
|
638
|
+
// Initialize feature queue if feature-list.json exists
|
|
639
|
+
const featureList = path.join(PROJECT_ROOT, '.harness', 'actions', 'feature-list.json');
|
|
640
|
+
const featureQueue = path.join(PROJECT_ROOT, '.harness', 'actions', 'feature-queue.json');
|
|
641
|
+
const queueMgr = path.join(PKG_ROOT, 'scripts', 'harness-queue-manager.sh');
|
|
642
|
+
|
|
643
|
+
if (fs.existsSync(featureList) && fs.existsSync(queueMgr)) {
|
|
644
|
+
if (!fs.existsSync(featureQueue)) {
|
|
645
|
+
log('Initializing feature queue...');
|
|
646
|
+
try { execSync(`bash "${queueMgr}" init "${PROJECT_ROOT}"`, { stdio: 'inherit' }); } catch (e) {}
|
|
647
|
+
} else {
|
|
648
|
+
log('Recovering feature queue...');
|
|
649
|
+
try { execSync(`bash "${queueMgr}" recover "${PROJECT_ROOT}"`, { stdio: 'inherit' }); } catch (e) {}
|
|
650
|
+
}
|
|
642
651
|
}
|
|
643
652
|
|
|
644
|
-
|
|
645
|
-
log('
|
|
646
|
-
|
|
653
|
+
console.log('');
|
|
654
|
+
log('╔═══════════════════════════════════════════════════════════╗');
|
|
655
|
+
log('║ Agent Teams v4 ENABLED ║');
|
|
656
|
+
log('╠═══════════════════════════════════════════════════════════╣');
|
|
657
|
+
log('║ ║');
|
|
658
|
+
log('║ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 set in ║');
|
|
659
|
+
log('║ .claude/settings.json ║');
|
|
660
|
+
log('║ ║');
|
|
661
|
+
log('║ Next steps: ║');
|
|
662
|
+
log('║ 1. Restart Claude Code (/exit → re-enter) ║');
|
|
663
|
+
log('║ 2. Run Planner: "하네스 엔지니어링 시작" ║');
|
|
664
|
+
log('║ 3. Start Teams: /harness-team-action ║');
|
|
665
|
+
log('║ ║');
|
|
666
|
+
log('║ Or use --teammate-mode tmux for split panes: ║');
|
|
667
|
+
log('║ $ claude --teammate-mode tmux ║');
|
|
668
|
+
log('║ ║');
|
|
669
|
+
log('╚═══════════════════════════════════════════════════════════╝');
|
|
670
|
+
console.log('');
|
|
647
671
|
}
|
|
648
672
|
|
|
649
673
|
function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-beta.2",
|
|
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"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-dashboard-v4.sh — v4 Dashboard
|
|
3
|
-
#
|
|
2
|
+
# harness-dashboard-v4.sh — v4 Dashboard 상단: Planner Progress
|
|
3
|
+
# Queue + Teams + Features 를 auto-refresh. 고정 영역, 스크롤 없음.
|
|
4
4
|
|
|
5
5
|
set -uo pipefail
|
|
6
6
|
|
|
@@ -30,42 +30,34 @@ render_header() {
|
|
|
30
30
|
now=$(date +"%H:%M:%S")
|
|
31
31
|
project_name=$(jq -r '.project_name // "Unknown"' "$PROGRESS" 2>/dev/null)
|
|
32
32
|
|
|
33
|
-
echo -e "${BOLD}
|
|
34
|
-
echo -e "${BOLD}║ HARNESS v4 — Parallel Agent Teams ║${RESET}"
|
|
35
|
-
echo -e "${BOLD}╚════════════════════════════════════════════════╝${RESET}"
|
|
36
|
-
echo -e " ${DIM}${project_name} | ${now}${RESET}"
|
|
37
|
-
echo ""
|
|
33
|
+
echo -e "${BOLD}HARNESS v4${RESET} ${DIM}${project_name} | ${now}${RESET}"
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
render_queue_summary() {
|
|
41
37
|
if [ ! -f "$QUEUE" ]; then
|
|
42
|
-
echo -e " ${DIM}(queue not initialized
|
|
38
|
+
echo -e " ${DIM}(queue not initialized)${RESET}"
|
|
43
39
|
return
|
|
44
40
|
fi
|
|
45
41
|
|
|
46
|
-
local ready blocked in_prog passed failed total
|
|
42
|
+
local ready blocked in_prog passed failed total
|
|
47
43
|
ready=$(jq '.queue.ready | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
48
44
|
blocked=$(jq '.queue.blocked | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
49
45
|
in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
50
46
|
passed=$(jq '.queue.passed | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
51
47
|
failed=$(jq '.queue.failed | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
52
|
-
concurrency=$(jq '.concurrency // 3' "$QUEUE" 2>/dev/null || echo 3)
|
|
53
48
|
ready=${ready:-0}; blocked=${blocked:-0}; in_prog=${in_prog:-0}; passed=${passed:-0}; failed=${failed:-0}
|
|
54
49
|
total=$((ready + blocked + in_prog + passed + failed))
|
|
55
50
|
|
|
56
|
-
# Progress bar
|
|
57
51
|
local pct=0
|
|
58
52
|
if [ "$total" -gt 0 ]; then pct=$(( passed * 100 / total )); fi
|
|
59
|
-
local bar_w=
|
|
53
|
+
local bar_w=16
|
|
60
54
|
local filled=$(( pct * bar_w / 100 ))
|
|
61
55
|
local empty=$(( bar_w - filled ))
|
|
62
56
|
local bar=""
|
|
63
57
|
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
64
58
|
for ((i=0; i<empty; i++)); do bar+="░"; done
|
|
65
59
|
|
|
66
|
-
echo -e " ${
|
|
67
|
-
echo -e " Ready:${GREEN}${ready}${RESET} Blocked:${YELLOW}${blocked}${RESET} Progress:${CYAN}${in_prog}${RESET} Pass:${GREEN}${passed}${RESET} Fail:${RED}${failed}${RESET}"
|
|
68
|
-
echo ""
|
|
60
|
+
echo -e " ${bar} ${passed}/${total} (${pct}%) R:${GREEN}${ready}${RESET} B:${YELLOW}${blocked}${RESET} P:${CYAN}${in_prog}${RESET} ${GREEN}✓${passed}${RESET} ${RED}✗${failed}${RESET}"
|
|
69
61
|
}
|
|
70
62
|
|
|
71
63
|
render_teams() {
|
|
@@ -75,49 +67,44 @@ render_teams() {
|
|
|
75
67
|
team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null)
|
|
76
68
|
if [ "${team_count:-0}" -eq 0 ]; then return; fi
|
|
77
69
|
|
|
78
|
-
echo -e " ${BOLD}Teams${RESET}"
|
|
79
|
-
|
|
80
70
|
for i in $(seq 1 "$team_count"); do
|
|
81
71
|
local t_status t_feature t_phase t_attempt
|
|
82
72
|
t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
|
|
83
73
|
t_feature=$(jq -r ".teams[\"$i\"].feature // \"—\"" "$QUEUE" 2>/dev/null)
|
|
84
74
|
|
|
85
|
-
# Get phase from in_progress
|
|
86
75
|
if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
|
|
87
76
|
t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
|
|
88
77
|
t_attempt=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].attempt // 1' "$QUEUE" 2>/dev/null)
|
|
89
78
|
else
|
|
90
|
-
t_phase="—"
|
|
91
|
-
t_attempt="—"
|
|
79
|
+
t_phase="—"; t_attempt=""
|
|
92
80
|
fi
|
|
93
81
|
|
|
94
82
|
local icon color
|
|
95
83
|
case "$t_status" in
|
|
96
|
-
busy) icon="▶"
|
|
97
|
-
idle) icon="○"
|
|
98
|
-
|
|
99
|
-
*) icon="?" ; color="$RESET" ;;
|
|
84
|
+
busy) icon="▶"; color="$GREEN" ;;
|
|
85
|
+
idle) icon="○"; color="$DIM" ;;
|
|
86
|
+
*) icon="?"; color="$RESET" ;;
|
|
100
87
|
esac
|
|
101
88
|
|
|
102
|
-
local
|
|
89
|
+
local phase_short=""
|
|
103
90
|
case "$t_phase" in
|
|
104
|
-
gen)
|
|
105
|
-
gate)
|
|
106
|
-
eval)
|
|
107
|
-
*)
|
|
91
|
+
gen) phase_short="${CYAN}G${RESET}" ;;
|
|
92
|
+
gate) phase_short="${YELLOW}K${RESET}" ;;
|
|
93
|
+
eval) phase_short="${MAGENTA}E${RESET}" ;;
|
|
94
|
+
*) phase_short="${DIM}-${RESET}" ;;
|
|
108
95
|
esac
|
|
109
96
|
|
|
110
|
-
printf " %b
|
|
97
|
+
printf " %b%b T%d %-7s %b" "$color" "$icon" "$i" "$t_feature" "$phase_short"
|
|
98
|
+
if [ -n "$t_attempt" ] && [ "$t_attempt" != "—" ]; then
|
|
99
|
+
printf " #%s" "$t_attempt"
|
|
100
|
+
fi
|
|
101
|
+
echo ""
|
|
111
102
|
done
|
|
112
|
-
echo ""
|
|
113
103
|
}
|
|
114
104
|
|
|
115
|
-
|
|
105
|
+
render_features() {
|
|
116
106
|
if [ ! -f "$QUEUE" ] || [ ! -f "$FEATURES" ]; then return; fi
|
|
117
107
|
|
|
118
|
-
echo -e " ${BOLD}Features${RESET}"
|
|
119
|
-
|
|
120
|
-
# Single jq call: merge feature-list + queue state → pre-formatted lines
|
|
121
108
|
jq -r --slurpfile q "$QUEUE" '
|
|
122
109
|
($q[0].queue.passed // []) as $passed |
|
|
123
110
|
($q[0].queue.failed // []) as $failed |
|
|
@@ -125,7 +112,7 @@ render_feature_list() {
|
|
|
125
112
|
($q[0].queue.in_progress // {}) as $prog |
|
|
126
113
|
.features[] |
|
|
127
114
|
.id as $fid |
|
|
128
|
-
(.name // .description // "?" | if length >
|
|
115
|
+
(.name // .description // "?" | if length > 18 then .[0:16] + ".." else . end) as $fname |
|
|
129
116
|
(if ($fid | IN($passed[])) then "P"
|
|
130
117
|
elif $prog[$fid] then "I|\($prog[$fid].team)|\($prog[$fid].phase)"
|
|
131
118
|
elif ($fid | IN($failed[])) then "F"
|
|
@@ -138,73 +125,25 @@ render_feature_list() {
|
|
|
138
125
|
F) printf " ${RED}✗${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
139
126
|
R) printf " ${YELLOW}○${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
140
127
|
B) printf " ${DIM}◌${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
141
|
-
I\|*)
|
|
142
|
-
team=$(echo "$st" | cut -d'|' -f2)
|
|
128
|
+
I\|*) team=$(echo "$st" | cut -d'|' -f2)
|
|
143
129
|
phase=$(echo "$st" | cut -d'|' -f3)
|
|
144
|
-
printf " ${CYAN}◐${RESET} %-6s %-
|
|
130
|
+
printf " ${CYAN}◐${RESET} %-6s %-14s T%s:%s\n" "$fid" "$fname" "$team" "$phase" ;;
|
|
145
131
|
*) printf " ? %-6s %s\n" "$fid" "$fname" ;;
|
|
146
132
|
esac
|
|
147
133
|
done
|
|
148
|
-
|
|
149
|
-
echo ""
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
render_prompt_history() {
|
|
153
|
-
local log_file="$PROJECT_ROOT/.harness/progress.log"
|
|
154
|
-
if [ ! -f "$log_file" ]; then return; fi
|
|
155
|
-
|
|
156
|
-
# Get terminal height to limit display
|
|
157
|
-
local term_h
|
|
158
|
-
term_h=$(tput lines 2>/dev/null || echo 50)
|
|
159
|
-
local max_lines=10 # show latest 10 entries
|
|
160
|
-
|
|
161
|
-
echo -e " ${BOLD}Prompt History${RESET} ${DIM}(newest first)${RESET}"
|
|
162
|
-
|
|
163
|
-
# Read non-comment lines, reverse (newest first), take max_lines
|
|
164
|
-
grep -v '^#' "$log_file" 2>/dev/null | grep -v '^$' | tail -r 2>/dev/null | head -"$max_lines" | \
|
|
165
|
-
while IFS= read -r line; do
|
|
166
|
-
# Parse: date | agent | action | detail
|
|
167
|
-
local ts agent action detail
|
|
168
|
-
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
169
|
-
agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
|
|
170
|
-
action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
|
|
171
|
-
detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
|
|
172
|
-
|
|
173
|
-
local short_ts icon color
|
|
174
|
-
short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
|
|
175
|
-
|
|
176
|
-
case "$agent" in
|
|
177
|
-
dispatcher*|dispatch) icon="▸"; color="$MAGENTA" ;;
|
|
178
|
-
team-*) icon="⚡"; color="$CYAN" ;;
|
|
179
|
-
manual|user) icon="★"; color="$BOLD" ;;
|
|
180
|
-
planner*) icon="□"; color="$YELLOW" ;;
|
|
181
|
-
generator*|gen*) icon="▶"; color="$GREEN" ;;
|
|
182
|
-
eval*) icon="✦"; color="$RED" ;;
|
|
183
|
-
system) icon="⚙"; color="$DIM" ;;
|
|
184
|
-
*) icon="·"; color="$DIM" ;;
|
|
185
|
-
esac
|
|
186
|
-
|
|
187
|
-
if [ ${#detail} -gt 45 ]; then detail="${detail:0:43}.."; fi
|
|
188
|
-
|
|
189
|
-
echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
|
|
190
|
-
done
|
|
191
|
-
|
|
192
|
-
echo ""
|
|
193
134
|
}
|
|
194
135
|
|
|
195
136
|
render_all() {
|
|
196
137
|
render_header
|
|
197
138
|
render_queue_summary
|
|
198
139
|
render_teams
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
echo -e " ${DIM}Refreshing every 3s${RESET}"
|
|
140
|
+
echo ""
|
|
141
|
+
render_features
|
|
202
142
|
}
|
|
203
143
|
|
|
204
144
|
# ── Main loop ──
|
|
205
145
|
tput civis 2>/dev/null
|
|
206
146
|
trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
|
|
207
|
-
|
|
208
147
|
clear
|
|
209
148
|
|
|
210
149
|
while true; do
|
package/scripts/harness-next.sh
CHANGED
|
@@ -59,21 +59,10 @@ if [ -f "$PIPELINE_JSON" ]; then
|
|
|
59
59
|
fi
|
|
60
60
|
|
|
61
61
|
# ─────────────────────────────────────────
|
|
62
|
-
# fe_stack
|
|
63
|
-
# pipeline_selection.pipelines 에서 읽은 에이전트명을 fe_stack/fe_target 에 따라 치환
|
|
64
|
-
# - react: 그대로
|
|
65
|
-
# - flutter+web: generator-frontend → generator-frontend-flutter 만 치환, eval 은 그대로 (Playwright 사용 가능)
|
|
66
|
-
# - flutter+mobile/desktop: eval 도 정적 분석용으로 치환, evaluator-visual 은 __skip__
|
|
62
|
+
# fe_stack 치환 (no-op — Flutter 지원 제거됨, 하위 호환용 stub)
|
|
67
63
|
# ─────────────────────────────────────────
|
|
68
64
|
substitute_fe_stack() {
|
|
69
|
-
|
|
70
|
-
if [ "$fe_stack" != "flutter" ]; then
|
|
71
|
-
echo "$agent"
|
|
72
|
-
return
|
|
73
|
-
fi
|
|
74
|
-
local sub
|
|
75
|
-
sub=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.${fe_stack}.by_target[\"${fe_target}\"][\"${agent}\"] // \"${agent}\"" "$CONFIG" 2>/dev/null)
|
|
76
|
-
echo "$sub"
|
|
65
|
+
echo "$1"
|
|
77
66
|
}
|
|
78
67
|
|
|
79
68
|
# ─────────────────────────────────────────
|
|
@@ -105,7 +94,7 @@ run_pre_eval_gate() {
|
|
|
105
94
|
# current_agent가 generator가 아닌 경우 (예: dispatcher로 리라우팅된 상태),
|
|
106
95
|
# completed_agents에서 마지막 generator를 찾는다
|
|
107
96
|
case "$source_agent" in
|
|
108
|
-
generator-frontend
|
|
97
|
+
generator-frontend)
|
|
109
98
|
location="frontend"
|
|
110
99
|
checks_key="frontend_checks"
|
|
111
100
|
;;
|
|
@@ -118,7 +107,7 @@ run_pre_eval_gate() {
|
|
|
118
107
|
local last_gen
|
|
119
108
|
last_gen=$(jq -r '.completed_agents // [] | map(select(startswith("generator-"))) | last // empty' "$PROGRESS" 2>/dev/null)
|
|
120
109
|
case "$last_gen" in
|
|
121
|
-
generator-frontend
|
|
110
|
+
generator-frontend)
|
|
122
111
|
location="frontend"
|
|
123
112
|
checks_key="frontend_checks"
|
|
124
113
|
;;
|
|
@@ -58,6 +58,16 @@ if [ -f "$FEATURE_QUEUE" ]; then
|
|
|
58
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
59
|
V4_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
60
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
|
+
|
|
61
71
|
cat <<EOF
|
|
62
72
|
[harness-v4] ${V4_PASSED}/${V4_TOTAL} features passed | ${V4_FAILED} failed
|
|
63
73
|
|
|
@@ -25,8 +25,13 @@ disable-model-invocation: false
|
|
|
25
25
|
- `pipeline` → 선택된 파이프라인 (FULLSTACK/FE-ONLY/BE-ONLY)
|
|
26
26
|
- `sprint.number` → `1`, `sprint.status` → `"in_progress"` (신규 파이프라인인 경우에만)
|
|
27
27
|
2. `.harness/progress.log`에 요약 한 줄 추가
|
|
28
|
-
3. **
|
|
29
|
-
|
|
28
|
+
3. **v4 모드 감지**: `.harness/actions/feature-queue.json` 파일이 존재하는지 확인
|
|
29
|
+
- **존재**: v4 Parallel Agent Teams 활성 상태. 별도 안내 없이 즉시 STOP.
|
|
30
|
+
출력: `"✓ Dispatcher 완료. Agent Teams가 자율 실행 중입니다."`
|
|
31
|
+
- **미존재**: v3 Classic 모드.
|
|
32
|
+
출력: `"✓ Dispatcher 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
|
|
33
|
+
4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
34
|
+
- v4 모드에서는 `/harness-team 실행하시겠습니까?` 같은 **질문을 하지 않는다**. Teams는 이미 실행 중이거나 사용자가 별도로 시작한다.
|
|
30
35
|
|
|
31
36
|
## Auto-Routing (UserPromptSubmit Hook)
|
|
32
37
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-team-action
|
|
3
|
+
description: "v4 Agent Teams 가동. feature-queue를 초기화하고 3개 Teammate를 생성하여 Feature 병렬 실행. 트리거: '/harness-team-action', 'team 시작', '팀 가동'"
|
|
4
|
+
disable-model-invocation: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /harness-team-action — Agent Teams 가동
|
|
8
|
+
|
|
9
|
+
## Step 1: Queue 초기화
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
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
|
+
```
|
|
14
|
+
|
|
15
|
+
## Step 2: Teammate 생성
|
|
16
|
+
|
|
17
|
+
Queue의 `ready` 배열에서 Feature를 읽고, **3명의 Teammate를 즉시 생성**하세요.
|
|
18
|
+
|
|
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에게 결과 보고
|
|
25
|
+
|
|
26
|
+
**Teammate 이름**: team-1, team-2, team-3
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-team-stop
|
|
3
|
+
description: "v4 Agent Teams 중지. 실행 중인 Teammate를 모두 해산한다. 트리거: '/harness-team-stop', 'team 중지', 'agent team 멈춰', '팀 멈춰', '팀 중지'"
|
|
4
|
+
disable-model-invocation: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /harness-team-stop — Agent Teams 중지
|
|
8
|
+
|
|
9
|
+
## 실행
|
|
10
|
+
|
|
11
|
+
모든 Teammate에게 현재 작업을 마무리하고 종료하라고 메시지를 보내세요.
|
|
12
|
+
|
|
13
|
+
Queue 상태는 보존되므로, `/harness-team-action`으로 다시 시작할 수 있습니다.
|
|
14
|
+
|
|
15
|
+
## 재시작
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
❯ /harness-team-action
|
|
19
|
+
```
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-control-v4.sh — v4 Control Center
|
|
3
|
-
#
|
|
4
|
-
# Commands:
|
|
5
|
-
# init Initialize feature queue
|
|
6
|
-
# start Launch all idle team workers
|
|
7
|
-
# pause <team> Pause team worker
|
|
8
|
-
# resume <team> Resume team worker
|
|
9
|
-
# assign <fid> <t> Force-assign feature to team
|
|
10
|
-
# requeue <fid> Move failed feature back to ready
|
|
11
|
-
# concurrency <N> Change parallel team count
|
|
12
|
-
# status / s Show queue status
|
|
13
|
-
# log <message> Add manual note
|
|
14
|
-
# help / h Show help
|
|
15
|
-
# quit / q Exit
|
|
16
|
-
|
|
17
|
-
set -uo pipefail
|
|
18
|
-
|
|
19
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
20
|
-
|
|
21
|
-
PROJECT_ROOT="${1:-}"
|
|
22
|
-
if [ -z "$PROJECT_ROOT" ]; then
|
|
23
|
-
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
24
|
-
PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[control] .harness/ not found."; exit 1; }
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
28
|
-
PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
|
|
29
|
-
QUEUE_MGR="$SCRIPT_DIR/harness-queue-manager.sh"
|
|
30
|
-
|
|
31
|
-
BOLD="\033[1m"
|
|
32
|
-
DIM="\033[2m"
|
|
33
|
-
GREEN="\033[32m"
|
|
34
|
-
YELLOW="\033[33m"
|
|
35
|
-
RED="\033[31m"
|
|
36
|
-
CYAN="\033[36m"
|
|
37
|
-
RESET="\033[0m"
|
|
38
|
-
|
|
39
|
-
cmd_init() {
|
|
40
|
-
bash "$QUEUE_MGR" init "$PROJECT_ROOT"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
cmd_status() {
|
|
44
|
-
bash "$QUEUE_MGR" status "$PROJECT_ROOT"
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
cmd_requeue() {
|
|
48
|
-
local fid="${1:-}"
|
|
49
|
-
if [ -z "$fid" ]; then echo -e " ${RED}Usage: requeue <feature_id>${RESET}"; return; fi
|
|
50
|
-
bash "$QUEUE_MGR" requeue "$fid" "$PROJECT_ROOT"
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
cmd_log() {
|
|
54
|
-
local msg="$1"
|
|
55
|
-
if [ -z "$msg" ]; then echo -e " ${RED}Usage: log <message>${RESET}"; return; fi
|
|
56
|
-
local ts
|
|
57
|
-
ts=$(date +"%Y-%m-%d")
|
|
58
|
-
echo "${ts} | manual | note | ${msg}" >> "$PROGRESS_LOG"
|
|
59
|
-
echo -e " ${GREEN}Logged:${RESET} ${msg}"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
show_help() {
|
|
63
|
-
echo ""
|
|
64
|
-
echo -e " ${BOLD}Harness v4 Control${RESET}"
|
|
65
|
-
echo -e " ${CYAN}init${RESET} Initialize feature queue from feature-list.json"
|
|
66
|
-
echo -e " ${CYAN}status${RESET} / ${CYAN}s${RESET} Show queue + team status"
|
|
67
|
-
echo -e " ${CYAN}requeue${RESET} <fid> Move failed feature back to ready"
|
|
68
|
-
echo -e " ${CYAN}log${RESET} <message> Add manual note to progress.log"
|
|
69
|
-
echo -e " ${CYAN}help${RESET} / ${CYAN}h${RESET} Show this help"
|
|
70
|
-
echo -e " ${CYAN}quit${RESET} / ${CYAN}q${RESET} Exit control"
|
|
71
|
-
echo ""
|
|
72
|
-
echo -e " ${DIM}Teams auto-start when studio launches.${RESET}"
|
|
73
|
-
echo -e " ${DIM}Workers auto-dequeue from the ready queue.${RESET}"
|
|
74
|
-
echo ""
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# ── Main ──
|
|
78
|
-
echo ""
|
|
79
|
-
echo -e " ${BOLD}Harness v4 Control${RESET} ${DIM}(type 'help' for commands)${RESET}"
|
|
80
|
-
echo ""
|
|
81
|
-
|
|
82
|
-
while true; do
|
|
83
|
-
echo -ne " ${BOLD}v4>${RESET} "
|
|
84
|
-
read -r input || exit 0
|
|
85
|
-
input=$(echo "$input" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
86
|
-
|
|
87
|
-
case "$input" in
|
|
88
|
-
init) cmd_init ;;
|
|
89
|
-
status|s) cmd_status ;;
|
|
90
|
-
requeue\ *) cmd_requeue "${input#requeue }" ;;
|
|
91
|
-
log\ *) cmd_log "${input#log }" ;;
|
|
92
|
-
help|h) show_help ;;
|
|
93
|
-
quit|q) echo -e " ${DIM}Goodbye.${RESET}"; exit 0 ;;
|
|
94
|
-
"") ;; # empty
|
|
95
|
-
*) echo -e " ${DIM}Unknown command. Type 'help'.${RESET}" ;;
|
|
96
|
-
esac
|
|
97
|
-
done
|