@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.
@@ -209,6 +209,17 @@
209
209
  "frontend_cwd": "",
210
210
  "timeout_seconds": 120,
211
211
  "on_fail": "reroute_to_generator"
212
+ },
213
+ "parallel": {
214
+ "comment": "v4 Parallel Agent Teams 설정. npx walwal-harness v4 로 실행.",
215
+ "enabled": false,
216
+ "concurrency": 3,
217
+ "max_attempts_per_feature": 3,
218
+ "gen_model": "sonnet",
219
+ "eval_model": "opus",
220
+ "branch_strategy": "feature-branch",
221
+ "merge_on_pass": true,
222
+ "rebase_on_conflict": true
212
223
  }
213
224
  },
214
225
  "evaluation": {
package/bin/init.js CHANGED
@@ -226,6 +226,11 @@ function installScripts() {
226
226
  'harness-eval-watcher.sh',
227
227
  'harness-tmux.sh',
228
228
  'harness-control.sh',
229
+ 'harness-studio-v4.sh',
230
+ 'harness-dashboard-v4.sh',
231
+ 'harness-control-v4.sh',
232
+ 'harness-queue-manager.sh',
233
+ 'harness-team-worker.sh',
229
234
  ]);
230
235
 
231
236
  if (fs.existsSync(scriptsSrc)) {
@@ -568,8 +573,9 @@ function showHelp() {
568
573
  Usage:
569
574
  npx walwal-harness Initialize project for harness engineering
570
575
  npx walwal-harness --force Re-initialize (overwrites existing files)
571
- npx walwal-harness studio Launch Harness Studio (tmux 4-pane monitor)
572
- npx walwal-harness studio --ai Studio + AI eval summary (API cost)
576
+ npx walwal-harness studio Launch Harness Studio v3 (tmux 5-pane)
577
+ npx walwal-harness studio --ai Studio v3 + AI eval summary
578
+ npx walwal-harness v4 Launch Studio v4 (3 Parallel Agent Teams)
573
579
  npx walwal-harness --help Show this help
574
580
 
575
581
  What it does:
@@ -619,6 +625,27 @@ function runStudio() {
619
625
  execSync(cmd, { stdio: 'inherit' });
620
626
  }
621
627
 
628
+ 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);
635
+ }
636
+
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);
642
+ }
643
+
644
+ const cmd = `bash "${tmuxScript}" "${PROJECT_ROOT}"`.trim();
645
+ log('Launching Harness Studio v4 (Parallel Agent Teams)...');
646
+ execSync(cmd, { stdio: 'inherit' });
647
+ }
648
+
622
649
  function main() {
623
650
  if (isHelp) {
624
651
  showHelp();
@@ -630,6 +657,11 @@ function main() {
630
657
  return;
631
658
  }
632
659
 
660
+ if (subcommand === 'studio-v4' || subcommand === 'v4') {
661
+ runStudioV4();
662
+ return;
663
+ }
664
+
633
665
  const pkg = require(path.join(PKG_ROOT, 'package.json'));
634
666
  console.log('');
635
667
  console.log('╔══════════════════════════════════════╗');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "3.7.4",
3
+ "version": "4.0.0-alpha.10",
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"
@@ -0,0 +1,97 @@
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
@@ -0,0 +1,243 @@
1
+ #!/bin/bash
2
+ # harness-dashboard-v4.sh — v4 Dashboard: Feature Queue + Team Status
3
+ # Auto-refresh 3초 간격. feature-queue.json + feature-list.json 시각화.
4
+
5
+ set -uo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
9
+
10
+ PROJECT_ROOT="${1:-}"
11
+ if [ -z "$PROJECT_ROOT" ]; then
12
+ PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[dash] .harness/ not found."; exit 1; }
13
+ fi
14
+
15
+ QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
16
+ FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
17
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
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
+ local now project_name
30
+ now=$(date +"%H:%M:%S")
31
+ project_name=$(jq -r '.project_name // "Unknown"' "$PROGRESS" 2>/dev/null)
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 ""
38
+ }
39
+
40
+ render_queue_summary() {
41
+ if [ ! -f "$QUEUE" ]; then
42
+ echo -e " ${DIM}(queue not initialized — run 'init' in Control)${RESET}"
43
+ return
44
+ fi
45
+
46
+ local ready blocked in_prog passed failed total concurrency
47
+ ready=$(jq '.queue.ready | length' "$QUEUE" 2>/dev/null || echo 0)
48
+ blocked=$(jq '.queue.blocked | length' "$QUEUE" 2>/dev/null || echo 0)
49
+ in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
50
+ passed=$(jq '.queue.passed | length' "$QUEUE" 2>/dev/null || echo 0)
51
+ failed=$(jq '.queue.failed | length' "$QUEUE" 2>/dev/null || echo 0)
52
+ concurrency=$(jq '.concurrency // 3' "$QUEUE" 2>/dev/null || echo 3)
53
+ ready=${ready:-0}; blocked=${blocked:-0}; in_prog=${in_prog:-0}; passed=${passed:-0}; failed=${failed:-0}
54
+ total=$((ready + blocked + in_prog + passed + failed))
55
+
56
+ # Progress bar
57
+ local pct=0
58
+ if [ "$total" -gt 0 ]; then pct=$(( passed * 100 / total )); fi
59
+ local bar_w=20
60
+ local filled=$(( pct * bar_w / 100 ))
61
+ local empty=$(( bar_w - filled ))
62
+ local bar=""
63
+ for ((i=0; i<filled; i++)); do bar+="█"; done
64
+ for ((i=0; i<empty; i++)); do bar+="░"; done
65
+
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 ""
69
+ }
70
+
71
+ render_teams() {
72
+ if [ ! -f "$QUEUE" ]; then return; fi
73
+
74
+ local team_count
75
+ team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null)
76
+ if [ "${team_count:-0}" -eq 0 ]; then return; fi
77
+
78
+ echo -e " ${BOLD}Teams${RESET}"
79
+
80
+ for i in $(seq 1 "$team_count"); do
81
+ local t_status t_feature t_phase t_attempt
82
+ t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
83
+ t_feature=$(jq -r ".teams[\"$i\"].feature // \"—\"" "$QUEUE" 2>/dev/null)
84
+
85
+ # Get phase from in_progress
86
+ if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
87
+ t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
88
+ t_attempt=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].attempt // 1' "$QUEUE" 2>/dev/null)
89
+ else
90
+ t_phase="—"
91
+ t_attempt="—"
92
+ fi
93
+
94
+ local icon color
95
+ case "$t_status" in
96
+ busy) icon="▶" ; color="$GREEN" ;;
97
+ idle) icon="○" ; color="$DIM" ;;
98
+ paused) icon="⏸" ; color="$YELLOW" ;;
99
+ *) icon="?" ; color="$RESET" ;;
100
+ esac
101
+
102
+ local phase_display=""
103
+ 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}" ;;
108
+ esac
109
+
110
+ printf " %b %b Team %d %-8s %b attempt %s\n" "$color" "$icon" "$i" "$t_feature" "$phase_display" "$t_attempt"
111
+ done
112
+ echo ""
113
+ }
114
+
115
+ render_feature_list() {
116
+ if [ ! -f "$QUEUE" ] || [ ! -f "$FEATURES" ]; then return; fi
117
+
118
+ echo -e " ${BOLD}Features${RESET}"
119
+
120
+ # Single jq call: merge feature-list + queue state → pre-formatted lines
121
+ jq -r --slurpfile q "$QUEUE" '
122
+ ($q[0].queue.passed // []) as $passed |
123
+ ($q[0].queue.failed // []) as $failed |
124
+ ($q[0].queue.ready // []) as $ready |
125
+ ($q[0].queue.in_progress // {}) as $prog |
126
+ .features[] |
127
+ .id as $fid |
128
+ (.name // .description // "?" | if length > 22 then .[0:20] + ".." else . end) as $fname |
129
+ (if ($fid | IN($passed[])) then "P"
130
+ elif $prog[$fid] then "I|\($prog[$fid].team)|\($prog[$fid].phase)"
131
+ elif ($fid | IN($failed[])) then "F"
132
+ elif ($fid | IN($ready[])) then "R"
133
+ else "B" end) as $st |
134
+ "\($st)\t\($fid)\t\($fname)"
135
+ ' "$FEATURES" 2>/dev/null | while IFS=$'\t' read -r st fid fname; do
136
+ case "$st" in
137
+ P) printf " ${GREEN}●${RESET} %-6s %s\n" "$fid" "$fname" ;;
138
+ F) printf " ${RED}✗${RESET} %-6s %s\n" "$fid" "$fname" ;;
139
+ R) printf " ${YELLOW}○${RESET} %-6s %s\n" "$fid" "$fname" ;;
140
+ B) printf " ${DIM}◌${RESET} %-6s %s\n" "$fid" "$fname" ;;
141
+ I\|*) # in_progress: extract team and phase
142
+ team=$(echo "$st" | cut -d'|' -f2)
143
+ phase=$(echo "$st" | cut -d'|' -f3)
144
+ printf " ${CYAN}◐${RESET} %-6s %-18s T%s:%s\n" "$fid" "$fname" "$team" "$phase" ;;
145
+ *) printf " ? %-6s %s\n" "$fid" "$fname" ;;
146
+ esac
147
+ done
148
+
149
+ echo ""
150
+ }
151
+
152
+ render_user_prompts() {
153
+ local log_file="$PROJECT_ROOT/.harness/progress.log"
154
+ if [ ! -f "$log_file" ]; then return; fi
155
+
156
+ # Extract user-prompt entries (newest first, max 5)
157
+ local user_lines
158
+ user_lines=$(grep 'user-prompt' "$log_file" 2>/dev/null | tail -r 2>/dev/null | head -5)
159
+
160
+ if [ -z "$user_lines" ]; then return; fi
161
+
162
+ echo -e " ${BOLD}Manual Prompts${RESET} ${DIM}(newest first)${RESET}"
163
+
164
+ echo "$user_lines" | while IFS= read -r line; do
165
+ local ts detail
166
+ ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
167
+ detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
168
+
169
+ # Short timestamp (HH:MM or MM-DD HH:MM)
170
+ local short_ts
171
+ short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
172
+
173
+ if [ ${#detail} -gt 55 ]; then detail="${detail:0:53}.."; fi
174
+
175
+ echo -e " ${BOLD}★${RESET} ${DIM}${short_ts}${RESET} ${detail}"
176
+ done
177
+
178
+ echo ""
179
+ }
180
+
181
+ render_team_activity() {
182
+ local log_file="$PROJECT_ROOT/.harness/progress.log"
183
+ if [ ! -f "$log_file" ]; then return; fi
184
+
185
+ local max_lines=8
186
+
187
+ echo -e " ${BOLD}Activity${RESET} ${DIM}(newest first)${RESET}"
188
+
189
+ # All non-user-prompt entries, newest first
190
+ grep -v '^#' "$log_file" 2>/dev/null | grep -v '^$' | grep -v 'user-prompt' | \
191
+ tail -r 2>/dev/null | head -"$max_lines" | \
192
+ while IFS= read -r line; do
193
+ local ts agent action detail
194
+ ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
195
+ agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
196
+ action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
197
+ detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
198
+
199
+ local short_ts icon color
200
+ short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
201
+
202
+ case "$agent" in
203
+ dispatcher*) icon="▸"; color="$MAGENTA" ;;
204
+ team-*) icon="⚡"; color="$CYAN" ;;
205
+ manual) icon="★"; color="$BOLD" ;;
206
+ planner*) icon="□"; color="$YELLOW" ;;
207
+ gen*) icon="▶"; color="$GREEN" ;;
208
+ eval*) icon="✦"; color="$RED" ;;
209
+ system) icon="⚙"; color="$DIM" ;;
210
+ *) icon="·"; color="$DIM" ;;
211
+ esac
212
+
213
+ if [ ${#detail} -gt 45 ]; then detail="${detail:0:43}.."; fi
214
+
215
+ echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
216
+ done
217
+
218
+ echo ""
219
+ }
220
+
221
+ render_all() {
222
+ render_header
223
+ render_queue_summary
224
+ render_teams
225
+ render_user_prompts
226
+ render_team_activity
227
+ render_feature_list
228
+ echo -e " ${DIM}Refreshing every 3s${RESET}"
229
+ }
230
+
231
+ # ── Main loop ──
232
+ tput civis 2>/dev/null
233
+ trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
234
+
235
+ clear
236
+
237
+ while true; do
238
+ buf=$(render_all 2>&1)
239
+ tput cup 0 0 2>/dev/null
240
+ echo "$buf"
241
+ tput ed 2>/dev/null
242
+ sleep 3
243
+ done