@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 +1 -1
- package/scripts/harness-dashboard.sh +139 -44
- package/scripts/harness-tmux.sh +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "3.6.
|
|
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
|
|
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
|
-
|
|
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 "
|
|
94
|
+
echo -e "${BOLD}Sprint Map${RESET}"
|
|
85
95
|
|
|
86
96
|
while IFS= read -r s; do
|
|
87
|
-
local status
|
|
97
|
+
local status feat_count notes icon
|
|
88
98
|
status=$(jq -r ".sprint_progress[\"${s}\"].status // \"pending\"" "$PROGRESS")
|
|
89
|
-
|
|
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
|
-
|
|
106
|
+
*) icon="○" ;;
|
|
99
107
|
esac
|
|
100
108
|
|
|
101
|
-
#
|
|
102
|
-
|
|
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 "
|
|
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
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
echo
|
|
151
|
-
if [
|
|
152
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/scripts/harness-tmux.sh
CHANGED
|
@@ -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"
|