@walwal-harness/cli 4.0.0-beta.9 → 5.0.0
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/README.md +118 -294
- package/assets/templates/config.json +8 -7
- package/assets/templates/progress.json.template +7 -1
- package/bin/init.js +115 -84
- package/commands/harness-solo.md +68 -0
- package/commands/harness-stop.md +53 -0
- package/{skills/team-action/SKILL.md → commands/harness-team.md} +100 -43
- package/package.json +9 -10
- package/scripts/harness-dashboard.sh +219 -154
- package/scripts/harness-monitor.sh +126 -73
- package/scripts/harness-prompt-history.sh +1 -1
- package/scripts/harness-queue-manager.sh +1 -1
- package/scripts/harness-session-start.sh +24 -13
- package/scripts/harness-statusline.sh +31 -8
- package/scripts/harness-tmux.sh +125 -72
- package/scripts/harness-user-prompt-submit.sh +22 -15
- package/skills/dispatcher/SKILL.md +6 -6
- package/skills/evaluator-functional/SKILL.md +2 -2
- package/skills/generator-frontend/SKILL.md +2 -2
- package/scripts/harness-control.sh +0 -183
- package/scripts/harness-dashboard-v4.sh +0 -155
- package/scripts/harness-eval-watcher.sh +0 -228
- package/scripts/harness-studio-setup.sh +0 -143
- package/scripts/harness-studio.sh +0 -66
- package/scripts/harness-tmux-v4.sh +0 -136
- package/skills/team-stop/SKILL.md +0 -19
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer. Supports React, Next.js, and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
7
7
|
},
|
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
"postinstall": "node bin/init.js --auto",
|
|
10
10
|
"init": "node bin/init.js",
|
|
11
11
|
"scan": "bash scripts/scan-project.sh .",
|
|
12
|
-
"reset": "node bin/init.js --force"
|
|
13
|
-
"studio": "bash scripts/harness-studio.sh",
|
|
14
|
-
"studio:kill": "bash scripts/harness-studio.sh --kill"
|
|
12
|
+
"reset": "node bin/init.js --force"
|
|
15
13
|
},
|
|
16
14
|
"keywords": [
|
|
17
15
|
"harness-engineering",
|
|
18
16
|
"ai-agent",
|
|
19
17
|
"claude-code",
|
|
18
|
+
"codex",
|
|
20
19
|
"skill",
|
|
20
|
+
"command",
|
|
21
|
+
"solo-mode",
|
|
22
|
+
"team-mode",
|
|
21
23
|
"brainstorming",
|
|
22
24
|
"planner",
|
|
23
25
|
"generator",
|
|
@@ -25,7 +27,6 @@
|
|
|
25
27
|
"nestjs",
|
|
26
28
|
"react",
|
|
27
29
|
"flutter",
|
|
28
|
-
"riverpod",
|
|
29
30
|
"playwright"
|
|
30
31
|
],
|
|
31
32
|
"author": "",
|
|
@@ -34,10 +35,8 @@
|
|
|
34
35
|
"bin/",
|
|
35
36
|
"skills/",
|
|
36
37
|
"scripts/",
|
|
38
|
+
"commands/",
|
|
37
39
|
"assets/",
|
|
38
40
|
"gotchas/"
|
|
39
|
-
]
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"@walwal-harness/cli": "^3.3.0"
|
|
42
|
-
}
|
|
41
|
+
]
|
|
43
42
|
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-dashboard.sh —
|
|
2
|
+
# harness-dashboard.sh — Unified Dashboard (Solo + Team mode)
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# Commands:
|
|
8
|
-
# status / s 대시보드 즉시 갱신
|
|
9
|
-
# next / n 다음 에이전트 세션 시작 (Agent Session pane으로 전달)
|
|
10
|
-
# retry / r 현재 에이전트 재실행
|
|
11
|
-
# stop 에이전트 세션 중지
|
|
12
|
-
# log <message> progress.log에 수동 메모 추가
|
|
13
|
-
# clear-fail failure 상태 클리어
|
|
14
|
-
# quit / q 대시보드 종료
|
|
4
|
+
# Reads progress.json.mode to decide rendering:
|
|
5
|
+
# solo: Sequential pipeline progress (sprint map, agent bar, prompt history)
|
|
6
|
+
# team: Queue summary, team status, feature list, bottleneck alerts
|
|
15
7
|
#
|
|
16
8
|
# Usage: bash scripts/harness-dashboard.sh [project-root]
|
|
17
9
|
|
|
@@ -30,6 +22,7 @@ fi
|
|
|
30
22
|
|
|
31
23
|
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
32
24
|
FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
25
|
+
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
33
26
|
PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
|
|
34
27
|
|
|
35
28
|
# ── ANSI helpers ──
|
|
@@ -42,7 +35,6 @@ CYAN="\033[36m"
|
|
|
42
35
|
MAGENTA="\033[35m"
|
|
43
36
|
RESET="\033[0m"
|
|
44
37
|
|
|
45
|
-
# ── Strip ANSI escape sequences ──
|
|
46
38
|
strip_ansi() {
|
|
47
39
|
sed 's/\x1b\[[0-9;]*m//g; s/\x1b\[[0-9;]*[a-zA-Z]//g'
|
|
48
40
|
}
|
|
@@ -51,23 +43,38 @@ get_term_width() {
|
|
|
51
43
|
tput cols 2>/dev/null || echo 80
|
|
52
44
|
}
|
|
53
45
|
|
|
46
|
+
get_mode() {
|
|
47
|
+
if [ -f "$PROGRESS" ] && command -v jq &>/dev/null; then
|
|
48
|
+
jq -r '.mode // "solo"' "$PROGRESS" 2>/dev/null
|
|
49
|
+
else
|
|
50
|
+
echo "solo"
|
|
51
|
+
fi
|
|
52
|
+
}
|
|
53
|
+
|
|
54
54
|
# ══════════════════════════════════════════
|
|
55
|
-
#
|
|
55
|
+
# Common header
|
|
56
56
|
# ══════════════════════════════════════════
|
|
57
|
-
|
|
58
57
|
render_header() {
|
|
59
|
-
local now project_name
|
|
58
|
+
local now project_name mode
|
|
60
59
|
now=$(date +"%H:%M:%S")
|
|
61
60
|
project_name=$(jq -r '.project_name // "Unknown"' "$PROGRESS" 2>/dev/null)
|
|
61
|
+
mode=$(get_mode)
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
local mode_label
|
|
64
|
+
case "$mode" in
|
|
65
|
+
team) mode_label="${GREEN}TEAM${RESET}" ;;
|
|
66
|
+
paused) mode_label="${YELLOW}PAUSED${RESET}" ;;
|
|
67
|
+
*) mode_label="${CYAN}SOLO${RESET}" ;;
|
|
68
|
+
esac
|
|
69
|
+
|
|
70
|
+
echo -e "${BOLD}HARNESS DASHBOARD${RESET} ${mode_label} ${DIM}${project_name} | ${now}${RESET}"
|
|
67
71
|
echo ""
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
|
|
74
|
+
# ══════════════════════════════════════════
|
|
75
|
+
# Solo Mode rendering
|
|
76
|
+
# ══════════════════════════════════════════
|
|
77
|
+
render_solo_sprint_overview() {
|
|
71
78
|
if [ ! -f "$PROGRESS" ]; then
|
|
72
79
|
echo -e " ${DIM}(waiting for harness init...)${RESET}"
|
|
73
80
|
return
|
|
@@ -94,39 +101,46 @@ render_sprint_overview() {
|
|
|
94
101
|
echo ""
|
|
95
102
|
}
|
|
96
103
|
|
|
97
|
-
|
|
104
|
+
render_solo_failure_info() {
|
|
98
105
|
if [ ! -f "$PROGRESS" ]; then return; fi
|
|
99
106
|
|
|
100
|
-
local
|
|
101
|
-
|
|
102
|
-
if [ -z "$sprint_keys" ]; then return; fi
|
|
107
|
+
local failure_agent
|
|
108
|
+
failure_agent=$(jq -r '.failure.agent // empty' "$PROGRESS" 2>/dev/null)
|
|
103
109
|
|
|
104
|
-
|
|
110
|
+
if [ -n "$failure_agent" ] && [ "$failure_agent" != "null" ]; then
|
|
111
|
+
local failure_loc failure_msg
|
|
112
|
+
failure_loc=$(jq -r '.failure.location // ""' "$PROGRESS")
|
|
113
|
+
failure_msg=$(jq -r '.failure.message // ""' "$PROGRESS" | strip_ansi | tr '\n' ' ' | sed 's/ */ /g')
|
|
114
|
+
if [ ${#failure_msg} -gt 80 ]; then failure_msg="${failure_msg:0:78}.."; fi
|
|
115
|
+
|
|
116
|
+
echo -e " ${RED}${BOLD}FAIL${RESET} ${RED}${failure_agent} → ${failure_loc}${RESET}"
|
|
117
|
+
if [ -n "$failure_msg" ]; then
|
|
118
|
+
echo -e " ${DIM}${failure_msg}${RESET}"
|
|
119
|
+
fi
|
|
120
|
+
echo ""
|
|
121
|
+
fi
|
|
122
|
+
}
|
|
105
123
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
status=$(jq -r ".sprint_progress[\"${s}\"].status // \"pending\"" "$PROGRESS")
|
|
109
|
-
feat_count=$(jq ".sprint_progress[\"${s}\"].features // [] | length" "$PROGRESS")
|
|
110
|
-
notes=$(jq -r ".sprint_progress[\"${s}\"].notes // \"\"" "$PROGRESS")
|
|
124
|
+
render_solo_agent_info() {
|
|
125
|
+
if [ ! -f "$PROGRESS" ]; then return; fi
|
|
111
126
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
scaffolded) icon="◑" ;;
|
|
116
|
-
*) icon="○" ;;
|
|
117
|
-
esac
|
|
127
|
+
local next_agent agent_status
|
|
128
|
+
next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
129
|
+
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
|
|
118
130
|
|
|
119
|
-
|
|
131
|
+
if [ "$next_agent" != "none" ] && [ "$next_agent" != "null" ] && [ "$agent_status" != "blocked" ]; then
|
|
132
|
+
echo -e " ${CYAN}Next → /harness-${next_agent}${RESET}"
|
|
133
|
+
fi
|
|
120
134
|
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
render_agent_bar "$PROJECT_ROOT" 2>/dev/null
|
|
136
|
+
echo ""
|
|
123
137
|
}
|
|
124
138
|
|
|
125
|
-
|
|
126
|
-
echo -e "${BOLD}
|
|
139
|
+
render_solo_prompt_history() {
|
|
140
|
+
echo -e "${BOLD}Recent Activity${RESET}"
|
|
127
141
|
|
|
128
142
|
if [ -f "$PROGRESS_LOG" ]; then
|
|
129
|
-
grep -v '^#' "$PROGRESS_LOG" 2>/dev/null | grep -v '^$' | tail -
|
|
143
|
+
grep -v '^#' "$PROGRESS_LOG" 2>/dev/null | grep -v '^$' | tail -10 | while IFS= read -r line; do
|
|
130
144
|
local ts agent action detail
|
|
131
145
|
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
132
146
|
agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
|
|
@@ -138,155 +152,203 @@ render_prompt_history_lines() {
|
|
|
138
152
|
|
|
139
153
|
case "$agent" in
|
|
140
154
|
dispatcher*) icon="▸" ; color="$MAGENTA" ;;
|
|
141
|
-
brainstormer) icon="◇" ; color="$CYAN" ;;
|
|
142
155
|
planner*) icon="□" ; color="$YELLOW" ;;
|
|
143
156
|
generator*) icon="▶" ; color="$GREEN" ;;
|
|
144
157
|
eval*) icon="✦" ; color="$RED" ;;
|
|
145
|
-
|
|
158
|
+
team-*) icon="◆" ; color="$CYAN" ;;
|
|
159
|
+
user*) icon="★" ; color="$BOLD" ;;
|
|
146
160
|
*) icon="·" ; color="$DIM" ;;
|
|
147
161
|
esac
|
|
148
162
|
|
|
149
|
-
if [ ${#detail} -gt
|
|
163
|
+
if [ ${#detail} -gt 40 ]; then detail="${detail:0:38}.."; fi
|
|
150
164
|
|
|
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
|
|
165
|
+
echo -e "${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
|
|
155
166
|
done
|
|
156
167
|
else
|
|
157
168
|
echo -e "${DIM}(no progress.log yet)${RESET}"
|
|
158
169
|
fi
|
|
159
170
|
}
|
|
160
171
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if [ "$
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
while IFS= read -r line; do
|
|
171
|
-
left_lines+=("$line")
|
|
172
|
-
done < <(render_sprint_map_lines 2>/dev/null)
|
|
173
|
-
|
|
174
|
-
while IFS= read -r line; do
|
|
175
|
-
right_lines+=("$line")
|
|
176
|
-
done < <(render_prompt_history_lines 2>/dev/null)
|
|
177
|
-
|
|
178
|
-
local left_count=${#left_lines[@]}
|
|
179
|
-
local right_count=${#right_lines[@]}
|
|
180
|
-
local max_rows=$left_count
|
|
181
|
-
if [ "$right_count" -gt "$max_rows" ]; then max_rows=$right_count; fi
|
|
182
|
-
|
|
183
|
-
local sep=""
|
|
184
|
-
for ((i=0; i<term_width-4; i++)); do sep+="─"; done
|
|
185
|
-
echo -e " ${DIM}${sep}${RESET}"
|
|
186
|
-
|
|
187
|
-
for ((i=0; i<max_rows; i++)); do
|
|
188
|
-
local left_text="${left_lines[$i]:-}"
|
|
189
|
-
local right_text="${right_lines[$i]:-}"
|
|
190
|
-
|
|
191
|
-
local left_plain
|
|
192
|
-
left_plain=$(echo -e "$left_text" | strip_ansi)
|
|
193
|
-
local left_len=${#left_plain}
|
|
194
|
-
local pad_needed=$(( col_width - left_len ))
|
|
195
|
-
if [ "$pad_needed" -lt 0 ]; then pad_needed=0; fi
|
|
196
|
-
local padding=""
|
|
197
|
-
for ((p=0; p<pad_needed; p++)); do padding+=" "; done
|
|
198
|
-
|
|
199
|
-
echo -e " ${left_text}${padding} ${DIM}│${RESET} ${right_text}"
|
|
200
|
-
done
|
|
172
|
+
# ══════════════════════════════════════════
|
|
173
|
+
# Team Mode rendering
|
|
174
|
+
# ══════════════════════════════════════════
|
|
175
|
+
render_team_queue_summary() {
|
|
176
|
+
if [ ! -f "$QUEUE" ]; then
|
|
177
|
+
echo -e " ${DIM}(queue not initialized — run /harness-team)${RESET}"
|
|
178
|
+
return
|
|
179
|
+
fi
|
|
201
180
|
|
|
202
|
-
|
|
203
|
-
|
|
181
|
+
local ready blocked in_prog passed failed total
|
|
182
|
+
ready=$(jq '.queue.ready | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
183
|
+
blocked=$(jq '.queue.blocked | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
184
|
+
in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
185
|
+
passed=$(jq '.queue.passed | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
186
|
+
failed=$(jq '.queue.failed | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
187
|
+
ready=${ready:-0}; blocked=${blocked:-0}; in_prog=${in_prog:-0}; passed=${passed:-0}; failed=${failed:-0}
|
|
188
|
+
total=$((ready + blocked + in_prog + passed + failed))
|
|
189
|
+
|
|
190
|
+
local pct=0
|
|
191
|
+
if [ "$total" -gt 0 ]; then pct=$(( passed * 100 / total )); fi
|
|
192
|
+
local bar_w=16
|
|
193
|
+
local filled=$(( pct * bar_w / 100 ))
|
|
194
|
+
local empty=$(( bar_w - filled ))
|
|
195
|
+
local bar=""
|
|
196
|
+
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
197
|
+
for ((i=0; i<empty; i++)); do bar+="░"; done
|
|
198
|
+
|
|
199
|
+
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}"
|
|
204
200
|
}
|
|
205
201
|
|
|
206
|
-
|
|
207
|
-
if [ ! -f "$
|
|
202
|
+
render_team_status() {
|
|
203
|
+
if [ ! -f "$QUEUE" ]; then return; fi
|
|
204
|
+
|
|
205
|
+
local team_count
|
|
206
|
+
team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null)
|
|
207
|
+
if [ "${team_count:-0}" -eq 0 ]; then return; fi
|
|
208
|
+
|
|
209
|
+
echo ""
|
|
210
|
+
echo -e "${BOLD}Teams${RESET}"
|
|
211
|
+
|
|
212
|
+
for i in $(seq 1 "$team_count"); do
|
|
213
|
+
local t_status t_feature t_phase t_attempt
|
|
214
|
+
t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
|
|
215
|
+
t_feature=$(jq -r ".teams[\"$i\"].feature // \"—\"" "$QUEUE" 2>/dev/null)
|
|
216
|
+
|
|
217
|
+
if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
|
|
218
|
+
t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
|
|
219
|
+
t_attempt=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].attempt // 1' "$QUEUE" 2>/dev/null)
|
|
220
|
+
else
|
|
221
|
+
t_phase="—"; t_attempt=""
|
|
222
|
+
fi
|
|
208
223
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
224
|
+
local icon color
|
|
225
|
+
case "$t_status" in
|
|
226
|
+
busy) icon="▶"; color="$GREEN" ;;
|
|
227
|
+
idle) icon="○"; color="$DIM" ;;
|
|
228
|
+
*) icon="?"; color="$RESET" ;;
|
|
229
|
+
esac
|
|
230
|
+
|
|
231
|
+
local phase_short=""
|
|
232
|
+
case "$t_phase" in
|
|
233
|
+
gen) phase_short="${CYAN}Gen${RESET}" ;;
|
|
234
|
+
gate) phase_short="${YELLOW}Gate${RESET}" ;;
|
|
235
|
+
eval) phase_short="${MAGENTA}Eval${RESET}" ;;
|
|
236
|
+
*) phase_short="${DIM}-${RESET}" ;;
|
|
237
|
+
esac
|
|
213
238
|
|
|
214
|
-
|
|
239
|
+
printf " %b%b T%d %-7s %b" "$color" "$icon" "$i" "$t_feature" "$phase_short"
|
|
240
|
+
if [ -n "$t_attempt" ] && [ "$t_attempt" != "—" ]; then
|
|
241
|
+
printf " #%s/5" "$t_attempt"
|
|
242
|
+
fi
|
|
243
|
+
echo ""
|
|
244
|
+
done
|
|
245
|
+
}
|
|
215
246
|
|
|
216
|
-
|
|
217
|
-
if [ "$
|
|
218
|
-
local tsc_color="$GREEN"
|
|
219
|
-
if [ "$tsc_status" != "clean" ]; then tsc_color="$RED"; fi
|
|
247
|
+
render_team_features() {
|
|
248
|
+
if [ ! -f "$QUEUE" ] || [ ! -f "$FEATURES" ]; then return; fi
|
|
220
249
|
|
|
221
|
-
echo -e " ${BOLD}Build${RESET} ${build_color}${build_status}${RESET} | TSC: ${tsc_color}${tsc_status}${RESET} | Routes: ${routes_total}"
|
|
222
250
|
echo ""
|
|
251
|
+
echo -e "${BOLD}Features${RESET}"
|
|
252
|
+
|
|
253
|
+
jq -r --slurpfile q "$QUEUE" '
|
|
254
|
+
($q[0].queue.passed // []) as $passed |
|
|
255
|
+
($q[0].queue.failed // []) as $failed |
|
|
256
|
+
($q[0].queue.ready // []) as $ready |
|
|
257
|
+
($q[0].queue.in_progress // {}) as $prog |
|
|
258
|
+
.features[] |
|
|
259
|
+
.id as $fid |
|
|
260
|
+
(.name // .description // "?" | if length > 18 then .[0:16] + ".." else . end) as $fname |
|
|
261
|
+
(if ($fid | IN($passed[])) then "P"
|
|
262
|
+
elif $prog[$fid] then "I|\($prog[$fid].team)|\($prog[$fid].phase)"
|
|
263
|
+
elif ($fid | IN($failed[])) then "F"
|
|
264
|
+
elif ($fid | IN($ready[])) then "R"
|
|
265
|
+
else "B" end) as $st |
|
|
266
|
+
"\($st)\t\($fid)\t\($fname)"
|
|
267
|
+
' "$FEATURES" 2>/dev/null | while IFS=$'\t' read -r st fid fname; do
|
|
268
|
+
case "$st" in
|
|
269
|
+
P) printf " ${GREEN}●${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
270
|
+
F) printf " ${RED}✗${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
271
|
+
R) printf " ${YELLOW}○${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
272
|
+
B) printf " ${DIM}◌${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
273
|
+
I\|*) team=$(echo "$st" | cut -d'|' -f2)
|
|
274
|
+
phase=$(echo "$st" | cut -d'|' -f3)
|
|
275
|
+
printf " ${CYAN}◐${RESET} %-6s %-14s T%s:%s\n" "$fid" "$fname" "$team" "$phase" ;;
|
|
276
|
+
*) printf " ? %-6s %s\n" "$fid" "$fname" ;;
|
|
277
|
+
esac
|
|
278
|
+
done
|
|
223
279
|
}
|
|
224
280
|
|
|
225
|
-
|
|
226
|
-
if [ ! -f "$
|
|
281
|
+
render_team_bottleneck() {
|
|
282
|
+
if [ ! -f "$QUEUE" ] || [ ! -f "$FEATURES" ]; then return; fi
|
|
227
283
|
|
|
228
|
-
local
|
|
229
|
-
|
|
284
|
+
local failed_list
|
|
285
|
+
failed_list=$(jq -r '.queue.failed[]' "$QUEUE" 2>/dev/null)
|
|
286
|
+
if [ -z "$failed_list" ]; then return; fi
|
|
230
287
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if [ ${#failure_msg} -gt 80 ]; then failure_msg="${failure_msg:0:78}.."; fi
|
|
288
|
+
local team_count idle_teams=0 blocked_count in_prog
|
|
289
|
+
team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
290
|
+
blocked_count=$(jq '.queue.blocked | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
291
|
+
in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
236
292
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
fi
|
|
241
|
-
|
|
242
|
-
fi
|
|
243
|
-
}
|
|
293
|
+
for i in $(seq 1 "$team_count"); do
|
|
294
|
+
local ts
|
|
295
|
+
ts=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
|
|
296
|
+
if [ "$ts" = "idle" ]; then idle_teams=$((idle_teams + 1)); fi
|
|
297
|
+
done
|
|
244
298
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
299
|
+
while IFS= read -r fid; do
|
|
300
|
+
[ -z "$fid" ] && continue
|
|
301
|
+
local deps_on_this
|
|
302
|
+
deps_on_this=$(jq --arg fid "$fid" '[.queue.blocked | to_entries[] | select(.value | index($fid))] | length' "$QUEUE" 2>/dev/null || echo 0)
|
|
248
303
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
304
|
+
if [ "$deps_on_this" -gt 0 ]; then
|
|
305
|
+
echo ""
|
|
306
|
+
echo -e " ${RED}${BOLD}⚠ BOTTLENECK${RESET} ${RED}${fid}${RESET} failed → ${YELLOW}${deps_on_this} features blocked${RESET}"
|
|
307
|
+
if [ "$idle_teams" -gt 0 ]; then
|
|
308
|
+
echo -e " ${BOLD}→ requeue:${RESET} bash scripts/harness-queue-manager.sh requeue ${fid} ."
|
|
309
|
+
fi
|
|
310
|
+
fi
|
|
311
|
+
done <<< "$failed_list"
|
|
252
312
|
|
|
253
|
-
if [ "$
|
|
254
|
-
echo
|
|
313
|
+
if [ "$idle_teams" -eq "$team_count" ] && [ "$in_prog" -eq 0 ] && [ "$blocked_count" -gt 0 ]; then
|
|
314
|
+
echo ""
|
|
315
|
+
echo -e " ${RED}${BOLD}!! STALLED${RESET} — All teams idle, ${blocked_count} features blocked"
|
|
255
316
|
fi
|
|
256
|
-
|
|
257
|
-
# Agent bar
|
|
258
|
-
render_agent_bar "$PROJECT_ROOT" 2>/dev/null
|
|
259
|
-
echo ""
|
|
260
317
|
}
|
|
261
318
|
|
|
262
319
|
# ══════════════════════════════════════════
|
|
263
|
-
#
|
|
320
|
+
# Render dispatch
|
|
264
321
|
# ══════════════════════════════════════════
|
|
265
322
|
render_dashboard() {
|
|
266
|
-
local
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
323
|
+
local mode
|
|
324
|
+
mode=$(get_mode)
|
|
325
|
+
|
|
326
|
+
render_header
|
|
327
|
+
|
|
328
|
+
case "$mode" in
|
|
329
|
+
team|paused)
|
|
330
|
+
render_team_queue_summary
|
|
331
|
+
render_team_status
|
|
332
|
+
render_team_features
|
|
333
|
+
render_team_bottleneck
|
|
334
|
+
;;
|
|
335
|
+
*)
|
|
336
|
+
render_solo_sprint_overview
|
|
337
|
+
render_solo_failure_info
|
|
338
|
+
|
|
339
|
+
if [ -f "$FEATURES" ]; then
|
|
340
|
+
render_progress "$PROJECT_ROOT" 2>/dev/null
|
|
341
|
+
fi
|
|
280
342
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
343
|
+
render_solo_agent_info
|
|
344
|
+
echo ""
|
|
345
|
+
render_solo_prompt_history
|
|
346
|
+
;;
|
|
347
|
+
esac
|
|
286
348
|
}
|
|
287
349
|
|
|
288
350
|
# ══════════════════════════════════════════
|
|
289
|
-
# Main — Auto-refresh
|
|
351
|
+
# Main — Auto-refresh loop
|
|
290
352
|
# ══════════════════════════════════════════
|
|
291
353
|
tput civis 2>/dev/null
|
|
292
354
|
trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
|
|
@@ -294,6 +356,9 @@ trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
|
|
|
294
356
|
clear
|
|
295
357
|
|
|
296
358
|
while true; do
|
|
297
|
-
render_dashboard
|
|
359
|
+
buf=$(render_dashboard 2>&1)
|
|
360
|
+
tput cup 0 0 2>/dev/null
|
|
361
|
+
echo "$buf"
|
|
362
|
+
tput ed 2>/dev/null
|
|
298
363
|
sleep 3
|
|
299
364
|
done
|