@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.
@@ -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으로 활성 에이전트를 결정. fe_stack 필드로 React/Flutter를 구분. harness-next.sh가 pipeline.json.fe_stack 을 읽어 generator-frontend/evaluator-functional 을 Flutter 변형으로 치환한다.",
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 Launch Studio v4 (3 Parallel Agent Teams)
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
- const scriptsDir = path.join(PKG_ROOT, 'scripts');
630
- const tmuxScript = path.join(scriptsDir, 'harness-studio-v4.sh');
631
-
632
- if (!fs.existsSync(tmuxScript)) {
633
- log('ERROR: harness-studio-v4.sh not found. Update @walwal-harness/cli to >= 4.0.0');
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
- try {
638
- execSync('which tmux', { stdio: 'ignore' });
639
- } catch {
640
- log('ERROR: tmux is required. Install with: brew install tmux');
641
- process.exit(1);
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
- const cmd = `bash "${tmuxScript}" "${PROJECT_ROOT}"`.trim();
645
- log('Launching Harness Studio v4 (Parallel Agent Teams)...');
646
- execSync(cmd, { stdio: 'inherit' });
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-alpha.9",
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: Feature Queue + Team Status
3
- # Auto-refresh 3초 간격. feature-queue.json + feature-list.json 시각화.
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}╔════════════════════════════════════════════════╗${RESET}"
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 — run 'init' in Control)${RESET}"
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 concurrency
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=20
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 " ${BOLD}Queue${RESET} ${bar} ${passed}/${total} (${pct}%) ${DIM}concurrency=${concurrency}${RESET}"
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="▶" ; color="$GREEN" ;;
97
- idle) icon="○" ; color="$DIM" ;;
98
- paused) icon="" ; color="$YELLOW" ;;
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 phase_display=""
89
+ local phase_short=""
103
90
  case "$t_phase" in
104
- gen) phase_display="${CYAN}GEN${RESET}" ;;
105
- gate) phase_display="${YELLOW}GATE${RESET}" ;;
106
- eval) phase_display="${MAGENTA}EVAL${RESET}" ;;
107
- *) phase_display="${DIM}${t_phase}${RESET}" ;;
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 %b Team %d %-8s %b attempt %s\n" "$color" "$icon" "$i" "$t_feature" "$phase_display" "$t_attempt"
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
- render_feature_list() {
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 > 22 then .[0:20] + ".." else . end) as $fname |
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\|*) # in_progress: extract team and phase
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 %-18s T%s:%s\n" "$fid" "$fname" "$team" "$phase" ;;
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
- render_prompt_history
200
- render_feature_list
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
@@ -59,21 +59,10 @@ if [ -f "$PIPELINE_JSON" ]; then
59
59
  fi
60
60
 
61
61
  # ─────────────────────────────────────────
62
- # fe_stack + fe_target 치환 헬퍼
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
- local agent="$1"
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|generator-frontend-flutter)
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|generator-frontend-flutter)
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. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
29
- 4. 출력: `"✓ Dispatcher 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
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