@walwal-harness/cli 4.0.0-alpha.13 → 4.0.0-alpha.14

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 CHANGED
@@ -231,6 +231,7 @@ function installScripts() {
231
231
  'harness-control-v4.sh',
232
232
  'harness-queue-manager.sh',
233
233
  'harness-team-worker.sh',
234
+ 'harness-prompts-v4.sh',
234
235
  ]);
235
236
 
236
237
  if (fs.existsSync(scriptsSrc)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "4.0.0-alpha.13",
3
+ "version": "4.0.0-alpha.14",
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,100 +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_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
134
  }
220
135
 
221
136
  render_all() {
222
137
  render_header
223
138
  render_queue_summary
224
139
  render_teams
225
- render_user_prompts
226
- render_team_activity
227
- render_feature_list
228
- echo -e " ${DIM}Refreshing every 3s${RESET}"
140
+ echo ""
141
+ render_features
229
142
  }
230
143
 
231
144
  # ── Main loop ──
232
145
  tput civis 2>/dev/null
233
146
  trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
234
-
235
147
  clear
236
148
 
237
149
  while true; do
@@ -0,0 +1,106 @@
1
+ #!/bin/bash
2
+ # harness-prompts-v4.sh — v4 Dashboard 하단: Manual Prompts + Activity
3
+ # progress.log에서 user-prompt와 team 활동을 newest-first로 표시.
4
+ # 스크롤 가능 영역 — 프롬프트가 많아져도 상단 Progress를 가리지 않음.
5
+
6
+ set -uo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+
10
+ PROJECT_ROOT="${1:-}"
11
+ if [ -z "$PROJECT_ROOT" ]; then
12
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
13
+ PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[prompts] .harness/ not found."; exit 1; }
14
+ fi
15
+
16
+ LOG_FILE="$PROJECT_ROOT/.harness/progress.log"
17
+
18
+ BOLD="\033[1m"
19
+ DIM="\033[2m"
20
+ GREEN="\033[32m"
21
+ YELLOW="\033[33m"
22
+ RED="\033[31m"
23
+ CYAN="\033[36m"
24
+ MAGENTA="\033[35m"
25
+ RESET="\033[0m"
26
+
27
+ LAST_LINE_COUNT=0
28
+
29
+ render_prompts() {
30
+ if [ ! -f "$LOG_FILE" ]; then
31
+ echo -e " ${DIM}(progress.log not found)${RESET}"
32
+ return
33
+ fi
34
+
35
+ local term_h
36
+ term_h=$(tput lines 2>/dev/null || echo 20)
37
+ local max_lines=$((term_h - 3)) # Leave room for header
38
+ if [ "$max_lines" -lt 5 ]; then max_lines=5; fi
39
+
40
+ echo -e "${BOLD}Prompts & Activity${RESET} ${DIM}(newest first)${RESET}"
41
+ echo ""
42
+
43
+ # Read all entries, reverse, limit to terminal height
44
+ grep -v '^#' "$LOG_FILE" 2>/dev/null | grep -v '^$' | \
45
+ tail -r 2>/dev/null | head -"$max_lines" | \
46
+ while IFS= read -r line; do
47
+ local ts agent action detail
48
+ ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
49
+ agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
50
+ action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
51
+ detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
52
+
53
+ local short_ts icon color
54
+ short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
55
+
56
+ case "$agent" in
57
+ user-prompt)
58
+ icon="★"; color="$BOLD"
59
+ # User prompts get full width, highlighted
60
+ if [ ${#detail} -gt 60 ]; then detail="${detail:0:58}.."; fi
61
+ echo -e " ${color}${icon} ${short_ts}${RESET} ${detail}"
62
+ ;;
63
+ dispatcher*)
64
+ icon="▸"; color="$MAGENTA"
65
+ if [ ${#detail} -gt 50 ]; then detail="${detail:0:48}.."; fi
66
+ echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
67
+ ;;
68
+ team-*)
69
+ icon="⚡"; color="$CYAN"
70
+ if [ ${#detail} -gt 50 ]; then detail="${detail:0:48}.."; fi
71
+ # Color by action type
72
+ case "$action" in
73
+ *pass*) color="$GREEN"; icon="✓" ;;
74
+ *fail*) color="$RED"; icon="✗" ;;
75
+ *eval*) color="$MAGENTA"; icon="✦" ;;
76
+ *gen*) color="$CYAN"; icon="▶" ;;
77
+ *merge*) color="$GREEN"; icon="⊕" ;;
78
+ esac
79
+ echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
80
+ ;;
81
+ manual)
82
+ icon="★"; color="$YELLOW"
83
+ if [ ${#detail} -gt 50 ]; then detail="${detail:0:48}.."; fi
84
+ echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${detail}"
85
+ ;;
86
+ *)
87
+ icon="·"; color="$DIM"
88
+ if [ ${#detail} -gt 50 ]; then detail="${detail:0:48}.."; fi
89
+ echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
90
+ ;;
91
+ esac
92
+ done
93
+ }
94
+
95
+ # ── Main loop ──
96
+ tput civis 2>/dev/null
97
+ trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
98
+ clear
99
+
100
+ while true; do
101
+ buf=$(render_prompts 2>&1)
102
+ tput cup 0 0 2>/dev/null
103
+ echo "$buf"
104
+ tput ed 2>/dev/null
105
+ sleep 3
106
+ done
@@ -72,11 +72,16 @@ fi
72
72
  PANE_MAIN=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
73
73
  -P -F '#{pane_id}')
74
74
 
75
- # Column 2: Dashboard (split right from Main, 66% remaining → 33% each of 3 cols)
75
+ # Column 2: Dashboard Progress (split right from Main, 66% remaining)
76
76
  PANE_DASH=$(tmux split-window -h -p 66 -t "$PANE_MAIN" -c "$PROJECT_ROOT" \
77
77
  -P -F '#{pane_id}' \
78
78
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
79
79
 
80
+ # Dashboard bottom: Prompts & Activity (split below Dashboard, 40% bottom)
81
+ PANE_PROMPTS=$(tmux split-window -v -p 40 -t "$PANE_DASH" -c "$PROJECT_ROOT" \
82
+ -P -F '#{pane_id}' \
83
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompts-v4.sh\" \"${PROJECT_ROOT}\"'")
84
+
80
85
  # Column 3: Team 1 (split right from Dashboard, 50% of remaining = 33% total)
81
86
  PANE_T1=$(tmux split-window -h -p 50 -t "$PANE_DASH" -c "$PROJECT_ROOT" \
82
87
  -P -F '#{pane_id}' \
@@ -96,11 +101,12 @@ PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" \
96
101
  tmux send-keys -t "$PANE_MAIN" "unset npm_config_prefix 2>/dev/null; clear && claude --dangerously-skip-permissions" Enter
97
102
 
98
103
  # ── Pane titles ──
99
- tmux select-pane -t "$PANE_MAIN" -T "Main"
100
- tmux select-pane -t "$PANE_DASH" -T "Dashboard"
101
- tmux select-pane -t "$PANE_T1" -T "Team 1"
102
- tmux select-pane -t "$PANE_T2" -T "Team 2"
103
- tmux select-pane -t "$PANE_T3" -T "Team 3"
104
+ tmux select-pane -t "$PANE_MAIN" -T "Main"
105
+ tmux select-pane -t "$PANE_DASH" -T "Progress"
106
+ tmux select-pane -t "$PANE_PROMPTS" -T "Prompts"
107
+ tmux select-pane -t "$PANE_T1" -T "Team 1"
108
+ tmux select-pane -t "$PANE_T2" -T "Team 2"
109
+ tmux select-pane -t "$PANE_T3" -T "Team 3"
104
110
 
105
111
  tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
106
112
  tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true