@walwal-harness/cli 3.6.3 → 3.6.5

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.3",
3
+ "version": "3.6.5",
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,11 +227,15 @@ 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
 
234
+ # Strip all ANSI escape sequences from a string
235
+ strip_ansi() {
236
+ sed 's/\x1b\[[0-9;]*m//g; s/\x1b\[[0-9;]*[a-zA-Z]//g'
237
+ }
238
+
137
239
  render_failure_info() {
138
240
  if [ ! -f "$PROGRESS" ]; then return; fi
139
241
 
@@ -143,26 +245,31 @@ render_failure_info() {
143
245
  if [ -n "$failure_agent" ] && [ "$failure_agent" != "null" ]; then
144
246
  local failure_loc failure_msg
145
247
  failure_loc=$(jq -r '.failure.location // ""' "$PROGRESS")
146
- failure_msg=$(jq -r '.failure.message // ""' "$PROGRESS")
248
+ # Get raw message, strip ANSI codes, take first meaningful line
249
+ failure_msg=$(jq -r '.failure.message // ""' "$PROGRESS" | strip_ansi | tr '\n' ' ' | sed 's/ */ /g')
250
+
251
+ # Truncate to 80 chars
252
+ if [ ${#failure_msg} -gt 80 ]; then
253
+ failure_msg="${failure_msg:0:78}.."
254
+ fi
147
255
 
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}"
256
+ echo -e " ${RED}${BOLD}FAIL${RESET} ${RED}${failure_agent} → ${failure_loc}${RESET}"
257
+ if [ -n "$failure_msg" ]; then
258
+ echo -e " ${DIM}${failure_msg}${RESET}"
153
259
  fi
154
- echo -e " ${RED}${BOLD}╚════════════════════════════════════╝${RESET}"
155
260
  echo ""
156
261
  fi
157
262
  }
158
263
 
159
- # ── Render to buffer, then overwrite screen in one shot ──
264
+ # ── Render all sections ──
160
265
  render_all() {
161
266
  render_header
162
267
  render_sprint_overview
163
268
  render_build_status
164
269
  render_failure_info
165
- render_sprint_map
270
+
271
+ # Two-column: Sprint Map | Prompt History
272
+ render_two_columns
166
273
 
167
274
  # Feature progress (reuse existing lib)
168
275
  if [ -f "$FEATURES" ]; then
@@ -177,23 +284,15 @@ render_all() {
177
284
  }
178
285
 
179
286
  # ── Main Loop ──
180
- # Hide cursor during refresh to reduce flicker
181
- tput civis 2>/dev/null # hide cursor
287
+ tput civis 2>/dev/null
182
288
  trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
183
289
 
184
- # Initial clear
185
290
  clear
186
291
 
187
292
  while true; do
188
- # Capture full output into buffer
189
293
  buf=$(render_all 2>/dev/null)
190
-
191
- # Move cursor to top-left and overwrite (no clear = no flicker)
192
294
  tput cup 0 0 2>/dev/null
193
295
  echo "$buf"
194
-
195
- # Clear any leftover lines from previous render
196
296
  tput ed 2>/dev/null
197
-
198
297
  sleep 2
199
298
  done
@@ -4,7 +4,7 @@
4
4
  # Usage: bash scripts/harness-eval-watcher.sh [project-root] [--ai]
5
5
  # --ai evaluation 완료 시 claude -p 로 AI 요약 생성 (API 비용 발생)
6
6
 
7
- set -euo pipefail
7
+ set -uo pipefail
8
8
 
9
9
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
10
 
@@ -69,7 +69,7 @@ extract_eval_summary() {
69
69
 
70
70
  # Extract Verdict (PASS/FAIL)
71
71
  local verdict
72
- verdict=$(grep -i "verdict\|result\|판정" "$file" | head -3)
72
+ verdict=$(grep -i "verdict\|result\|판정" "$file" 2>/dev/null | head -3 || true)
73
73
  if [ -n "$verdict" ]; then
74
74
  if echo "$verdict" | grep -qi "PASS"; then
75
75
  echo -e " ${GREEN}${BOLD}VERDICT: PASS${RESET}"
@@ -83,7 +83,7 @@ extract_eval_summary() {
83
83
 
84
84
  # Extract Score
85
85
  local score_line
86
- score_line=$(grep -iE "score|점수|weighted|가중" "$file" | head -3)
86
+ score_line=$(grep -iE "score|점수|weighted|가중" "$file" 2>/dev/null | head -3 || true)
87
87
  if [ -n "$score_line" ]; then
88
88
  echo -e " ${BOLD}Score${RESET}"
89
89
  echo "$score_line" | while IFS= read -r line; do
@@ -107,7 +107,7 @@ extract_eval_summary() {
107
107
 
108
108
  # Extract individual rubric items (R1-R5 or V1-V5)
109
109
  local rubric_lines
110
- rubric_lines=$(grep -E "^[|#].*[RV][1-5]" "$file" | head -10)
110
+ rubric_lines=$(grep -E "^[|#].*[RV][1-5]" "$file" 2>/dev/null | head -10 || true)
111
111
  if [ -n "$rubric_lines" ]; then
112
112
  echo -e " ${BOLD}Rubric${RESET}"
113
113
  echo "$rubric_lines" | while IFS= read -r line; do
@@ -118,7 +118,7 @@ extract_eval_summary() {
118
118
 
119
119
  # Extract FAIL reasons
120
120
  local fail_lines
121
- fail_lines=$(grep -iE "fail|실패|regression|불일치|위반" "$file" | head -5)
121
+ fail_lines=$(grep -iE "fail|실패|regression|불일치|위반" "$file" 2>/dev/null | head -5 || true)
122
122
  if [ -n "$fail_lines" ]; then
123
123
  echo -e " ${RED}${BOLD}Issues${RESET}"
124
124
  echo "$fail_lines" | while IFS= read -r line; do
@@ -129,7 +129,7 @@ extract_eval_summary() {
129
129
 
130
130
  # Extract action items / recommendations
131
131
  local action_lines
132
- action_lines=$(grep -iE "recommend|action|수정|개선|필요|re-generate" "$file" | head -5)
132
+ action_lines=$(grep -iE "recommend|action|수정|개선|필요|re-generate" "$file" 2>/dev/null | head -5 || true)
133
133
  if [ -n "$action_lines" ]; then
134
134
  echo -e " ${YELLOW}${BOLD}Actions${RESET}"
135
135
  echo "$action_lines" | while IFS= read -r line; do
@@ -3,7 +3,7 @@
3
3
  # audit.log 스트리밍 + progress.json 변경 감지
4
4
  # Usage: bash scripts/harness-monitor.sh [project-root]
5
5
 
6
- set -euo pipefail
6
+ set -uo pipefail
7
7
 
8
8
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
9
 
@@ -68,22 +68,17 @@ tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
68
68
  tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50
69
69
 
70
70
  # Split right column (40% width)
71
- tmux split-window -h -l 80 -t "$SESSION_NAME" -c "$PROJECT_ROOT"
72
-
73
- # Pane 1 (top-right): Dashboard
74
- tmux send-keys -t "${SESSION_NAME}.1" "bash ${SCRIPT_DIR}/harness-dashboard.sh '${PROJECT_ROOT}'" Enter
71
+ # Use direct command execution (not send-keys) to avoid shell init noise (nvm warnings etc.)
72
+ tmux split-window -h -l 80 -t "$SESSION_NAME" -c "$PROJECT_ROOT" \
73
+ "exec bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"'"
75
74
 
76
75
  # Split pane 1 vertically for Monitor
77
- tmux split-window -v -t "${SESSION_NAME}.1" -c "$PROJECT_ROOT"
78
-
79
- # Pane 2 (mid-right): Monitor
80
- tmux send-keys -t "${SESSION_NAME}.2" "bash ${SCRIPT_DIR}/harness-monitor.sh '${PROJECT_ROOT}'" Enter
76
+ tmux split-window -v -t "${SESSION_NAME}.1" -c "$PROJECT_ROOT" \
77
+ "exec bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'"
81
78
 
82
79
  # Split pane 2 vertically for Eval
83
- tmux split-window -v -t "${SESSION_NAME}.2" -c "$PROJECT_ROOT"
84
-
85
- # Pane 3 (bottom-right): Eval Watcher
86
- tmux send-keys -t "${SESSION_NAME}.3" "bash ${SCRIPT_DIR}/harness-eval-watcher.sh '${PROJECT_ROOT}' ${USE_AI}" Enter
80
+ tmux split-window -v -t "${SESSION_NAME}.2" -c "$PROJECT_ROOT" \
81
+ "exec bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-eval-watcher.sh\" \"${PROJECT_ROOT}\" ${USE_AI}'"
87
82
 
88
83
  # ── Launch Claude in Main pane ──
89
84
  HANDOFF="$PROJECT_ROOT/.harness/handoff.json"