@walwal-harness/cli 3.6.2 → 3.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "3.6.2",
3
+ "version": "3.6.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"
@@ -1,6 +1,14 @@
1
1
  #!/bin/bash
2
2
  # harness-dashboard.sh — Panel 1: 실시간 대시보드
3
- # progress.json + feature-list.json 을 2초 간격으로 시각화
3
+ # progress.json + feature-list.json + progress.log 를 2초 간격으로 시각화
4
+ #
5
+ # Layout:
6
+ # ┌─────────────────────┬──────────────────────┐
7
+ # │ Sprint Map │ Prompt History │
8
+ # ├─────────────────────┴──────────────────────┤
9
+ # │ Processing Status (features, agent bar) │
10
+ # └────────────────────────────────────────────┘
11
+ #
4
12
  # Usage: bash scripts/harness-dashboard.sh [project-root]
5
13
 
6
14
  set -euo pipefail
@@ -18,6 +26,7 @@ fi
18
26
 
19
27
  PROGRESS="$PROJECT_ROOT/.harness/progress.json"
20
28
  FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
29
+ PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
21
30
 
22
31
  # ── ANSI helpers ──
23
32
  BOLD="\033[1m"
@@ -26,8 +35,14 @@ GREEN="\033[32m"
26
35
  YELLOW="\033[33m"
27
36
  RED="\033[31m"
28
37
  CYAN="\033[36m"
38
+ MAGENTA="\033[35m"
29
39
  RESET="\033[0m"
30
40
 
41
+ # ── Get terminal width for column layout ──
42
+ get_term_width() {
43
+ tput cols 2>/dev/null || echo 80
44
+ }
45
+
31
46
  render_header() {
32
47
  local now
33
48
  now=$(date +"%H:%M:%S")
@@ -55,7 +70,6 @@ render_sprint_overview() {
55
70
  agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
56
71
  retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
57
72
 
58
- # Status color
59
73
  local status_color="$RESET"
60
74
  case "$agent_status" in
61
75
  running) status_color="$GREEN" ;;
@@ -65,62 +79,146 @@ render_sprint_overview() {
65
79
  *) status_color="$YELLOW" ;;
66
80
  esac
67
81
 
68
- echo -e " ${BOLD}Pipeline${RESET} ${pipeline}"
69
- echo -e " ${BOLD}Sprint${RESET} ${sprint_num} (${sprint_status})"
70
- echo -e " ${BOLD}Agent${RESET} ${status_color}${current_agent} [${agent_status}]${RESET}"
71
- if [ "$retry_count" -gt 0 ]; then
72
- echo -e " ${BOLD}Retries${RESET} ${RED}${retry_count}${RESET}"
73
- fi
82
+ echo -e " ${BOLD}Pipeline${RESET} ${pipeline} | ${BOLD}Sprint${RESET} ${sprint_num} (${sprint_status}) | ${BOLD}Agent${RESET} ${status_color}${current_agent} [${agent_status}]${RESET}$([ "$retry_count" -gt 0 ] && echo -e " | ${RED}R${retry_count}${RESET}")"
74
83
  echo ""
75
84
  }
76
85
 
77
- render_sprint_map() {
86
+ # ── Sprint Map column (left) ──
87
+ render_sprint_map_lines() {
78
88
  if [ ! -f "$PROGRESS" ]; then return; fi
79
89
 
80
90
  local sprint_keys
81
91
  sprint_keys=$(jq -r '.sprint_progress // {} | keys[]' "$PROGRESS" 2>/dev/null | sort -n)
82
92
  if [ -z "$sprint_keys" ]; then return; fi
83
93
 
84
- echo -e " ${BOLD}Sprint Map${RESET}"
94
+ echo -e "${BOLD}Sprint Map${RESET}"
85
95
 
86
96
  while IFS= read -r s; do
87
- local status features_csv notes
97
+ local status feat_count notes icon
88
98
  status=$(jq -r ".sprint_progress[\"${s}\"].status // \"pending\"" "$PROGRESS")
89
- features_csv=$(jq -r ".sprint_progress[\"${s}\"].features // [] | join(\",\")" "$PROGRESS")
99
+ feat_count=$(jq ".sprint_progress[\"${s}\"].features // [] | length" "$PROGRESS")
90
100
  notes=$(jq -r ".sprint_progress[\"${s}\"].notes // \"\"" "$PROGRESS")
91
101
 
92
- # Status icon
93
- local icon="○"
94
102
  case "$status" in
95
103
  completed) icon="●" ;;
96
104
  in_progress) icon="◐" ;;
97
105
  scaffolded) icon="◑" ;;
98
- pending) icon="○" ;;
106
+ *) icon="○" ;;
99
107
  esac
100
108
 
101
- # Count features
102
- local feat_count
103
- feat_count=$(jq ".sprint_progress[\"${s}\"].features // [] | length" "$PROGRESS")
104
-
105
- # Truncate notes
106
- if [ ${#notes} -gt 40 ]; then
107
- notes="${notes:0:38}.."
109
+ if [ ${#notes} -gt 28 ]; then
110
+ notes="${notes:0:26}.."
108
111
  fi
109
112
 
110
- printf " %s S%-2s %2d feat %-12s %s\n" "$icon" "$s" "$feat_count" "$status" "$notes"
113
+ printf "%s S%-2s %2df %-11s %s\n" "$icon" "$s" "$feat_count" "$status" "$notes"
111
114
  done <<< "$sprint_keys"
115
+ }
116
+
117
+ # ── Prompt History column (right) ──
118
+ render_prompt_history_lines() {
119
+ echo -e "${BOLD}Prompt History${RESET}"
120
+
121
+ # Source 1: progress.log (user/dispatcher interventions)
122
+ if [ -f "$PROGRESS_LOG" ]; then
123
+ grep -v '^#' "$PROGRESS_LOG" | grep -v '^$' | tail -15 | while IFS= read -r line; do
124
+ # Format: 2026-04-13 | agent | action | detail
125
+ local ts agent action detail
126
+ ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
127
+ agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
128
+ action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
129
+ detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
130
+
131
+ # Short date (MM-DD)
132
+ local short_ts
133
+ short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
134
+
135
+ # Icon by agent/action
136
+ local icon color
137
+ case "$agent" in
138
+ dispatcher*) icon="▸" ; color="$MAGENTA" ;;
139
+ brainstormer) icon="◇" ; color="$CYAN" ;;
140
+ planner*) icon="□" ; color="$YELLOW" ;;
141
+ generator*) icon="▶" ; color="$GREEN" ;;
142
+ eval*) icon="✦" ; color="$RED" ;;
143
+ *) icon="·" ; color="$DIM" ;;
144
+ esac
145
+
146
+ # Truncate detail
147
+ if [ ${#detail} -gt 35 ]; then
148
+ detail="${detail:0:33}.."
149
+ fi
150
+
151
+ echo -e "${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET}"
152
+ if [ -n "$detail" ]; then
153
+ echo -e " ${DIM}${detail}${RESET}"
154
+ fi
155
+ done
156
+ else
157
+ echo -e "${DIM}(no progress.log yet)${RESET}"
158
+ fi
159
+ }
112
160
 
161
+ # ── Side-by-side rendering ──
162
+ render_two_columns() {
163
+ local term_width
164
+ term_width=$(get_term_width)
165
+ local col_width=$(( (term_width - 7) / 2 )) # 7 = indent(4) + separator(3)
166
+ if [ "$col_width" -lt 30 ]; then col_width=30; fi
167
+ if [ "$col_width" -gt 60 ]; then col_width=60; fi
168
+
169
+ # Capture each column into temp arrays
170
+ local left_lines=()
171
+ local right_lines=()
172
+
173
+ while IFS= read -r line; do
174
+ left_lines+=("$line")
175
+ done < <(render_sprint_map_lines 2>/dev/null)
176
+
177
+ while IFS= read -r line; do
178
+ right_lines+=("$line")
179
+ done < <(render_prompt_history_lines 2>/dev/null)
180
+
181
+ # Determine max rows
182
+ local left_count=${#left_lines[@]}
183
+ local right_count=${#right_lines[@]}
184
+ local max_rows=$left_count
185
+ if [ "$right_count" -gt "$max_rows" ]; then max_rows=$right_count; fi
186
+
187
+ # Print separator
188
+ local sep=""
189
+ for ((i=0; i<term_width-4; i++)); do sep+="─"; done
190
+ echo -e " ${DIM}${sep}${RESET}"
191
+
192
+ # Print rows side by side
193
+ for ((i=0; i<max_rows; i++)); do
194
+ local left_text="${left_lines[$i]:-}"
195
+ local right_text="${right_lines[$i]:-}"
196
+
197
+ # Strip ANSI for length calculation
198
+ local left_plain
199
+ left_plain=$(echo -e "$left_text" | sed 's/\x1b\[[0-9;]*m//g')
200
+ local left_len=${#left_plain}
201
+
202
+ # Pad left column
203
+ local pad_needed=$(( col_width - left_len ))
204
+ if [ "$pad_needed" -lt 0 ]; then pad_needed=0; fi
205
+ local padding=""
206
+ for ((p=0; p<pad_needed; p++)); do padding+=" "; done
207
+
208
+ echo -e " ${left_text}${padding} ${DIM}│${RESET} ${right_text}"
209
+ done
210
+
211
+ echo -e " ${DIM}${sep}${RESET}"
113
212
  echo ""
114
213
  }
115
214
 
116
215
  render_build_status() {
117
216
  if [ ! -f "$PROGRESS" ]; then return; fi
118
217
 
119
- local build_status tsc_status routes_total last_build
218
+ local build_status tsc_status routes_total
120
219
  build_status=$(jq -r '.build.build_status // "unknown"' "$PROGRESS" 2>/dev/null)
121
220
  tsc_status=$(jq -r '.build.tsc_status // "unknown"' "$PROGRESS" 2>/dev/null)
122
221
  routes_total=$(jq -r '.build.routes_total // 0' "$PROGRESS" 2>/dev/null)
123
- last_build=$(jq -r '.build.last_build // "n/a"' "$PROGRESS" 2>/dev/null)
124
222
 
125
223
  if [ "$build_status" = "null" ] || [ "$build_status" = "unknown" ]; then return; fi
126
224
 
@@ -129,8 +227,7 @@ render_build_status() {
129
227
  local tsc_color="$GREEN"
130
228
  if [ "$tsc_status" != "clean" ]; then tsc_color="$RED"; fi
131
229
 
132
- echo -e " ${BOLD}Build${RESET}"
133
- echo -e " Build: ${build_color}${build_status}${RESET} | TSC: ${tsc_color}${tsc_status}${RESET} | Routes: ${routes_total}"
230
+ echo -e " ${BOLD}Build${RESET} ${build_color}${build_status}${RESET} | TSC: ${tsc_color}${tsc_status}${RESET} | Routes: ${routes_total}"
134
231
  echo ""
135
232
  }
136
233
 
@@ -145,24 +242,30 @@ render_failure_info() {
145
242
  failure_loc=$(jq -r '.failure.location // ""' "$PROGRESS")
146
243
  failure_msg=$(jq -r '.failure.message // ""' "$PROGRESS")
147
244
 
148
- echo -e " ${RED}${BOLD}╔═ FAILURE ═══════════════════════════╗${RESET}"
149
- echo -e " ${RED} Agent: ${failure_agent}${RESET}"
150
- echo -e " ${RED} Where: ${failure_loc}${RESET}"
151
- if [ -n "$failure_msg" ] && [ "$failure_msg" != "null" ]; then
152
- echo -e " ${RED} ${failure_msg}${RESET}"
245
+ # Truncate long failure messages (take first 3 lines)
246
+ local short_msg
247
+ short_msg=$(echo "$failure_msg" | head -3 | tr '\n' ' ')
248
+ if [ ${#short_msg} -gt 80 ]; then
249
+ short_msg="${short_msg:0:78}.."
250
+ fi
251
+
252
+ echo -e " ${RED}${BOLD}FAIL${RESET} ${RED}${failure_agent} → ${failure_loc}${RESET}"
253
+ if [ -n "$short_msg" ]; then
254
+ echo -e " ${DIM}${short_msg}${RESET}"
153
255
  fi
154
- echo -e " ${RED}${BOLD}╚════════════════════════════════════╝${RESET}"
155
256
  echo ""
156
257
  fi
157
258
  }
158
259
 
159
- # ── Render to buffer, then overwrite screen in one shot ──
260
+ # ── Render all sections ──
160
261
  render_all() {
161
262
  render_header
162
263
  render_sprint_overview
163
264
  render_build_status
164
265
  render_failure_info
165
- render_sprint_map
266
+
267
+ # Two-column: Sprint Map | Prompt History
268
+ render_two_columns
166
269
 
167
270
  # Feature progress (reuse existing lib)
168
271
  if [ -f "$FEATURES" ]; then
@@ -177,23 +280,15 @@ render_all() {
177
280
  }
178
281
 
179
282
  # ── Main Loop ──
180
- # Hide cursor during refresh to reduce flicker
181
- tput civis 2>/dev/null # hide cursor
283
+ tput civis 2>/dev/null
182
284
  trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
183
285
 
184
- # Initial clear
185
286
  clear
186
287
 
187
288
  while true; do
188
- # Capture full output into buffer
189
289
  buf=$(render_all 2>/dev/null)
190
-
191
- # Move cursor to top-left and overwrite (no clear = no flicker)
192
290
  tput cup 0 0 2>/dev/null
193
291
  echo "$buf"
194
-
195
- # Clear any leftover lines from previous render
196
292
  tput ed 2>/dev/null
197
-
198
293
  sleep 2
199
294
  done
@@ -85,6 +85,20 @@ tmux split-window -v -t "${SESSION_NAME}.2" -c "$PROJECT_ROOT"
85
85
  # Pane 3 (bottom-right): Eval Watcher
86
86
  tmux send-keys -t "${SESSION_NAME}.3" "bash ${SCRIPT_DIR}/harness-eval-watcher.sh '${PROJECT_ROOT}' ${USE_AI}" Enter
87
87
 
88
+ # ── Launch Claude in Main pane ──
89
+ HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
90
+ CLAUDE_CMD="claude --dangerously-skip-permissions"
91
+
92
+ # If handoff.json exists, pick up model from it
93
+ if [ -f "$HANDOFF" ]; then
94
+ _model=$(jq -r '.model // empty' "$HANDOFF" 2>/dev/null)
95
+ if [ -n "$_model" ] && [ "$_model" != "null" ]; then
96
+ CLAUDE_CMD="$CLAUDE_CMD --model $_model"
97
+ fi
98
+ fi
99
+
100
+ tmux send-keys -t "${SESSION_NAME}.0" "$CLAUDE_CMD" Enter
101
+
88
102
  # ── Layout adjustments ──
89
103
  # Select left pane (Main) — this is where the user works
90
104
  tmux select-pane -t "${SESSION_NAME}.0"