@walwal-harness/cli 6.1.4 → 6.1.6

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +43 -74
  3. package/assets/launchd/com.walwal.harness-wake.plist.template +32 -0
  4. package/assets/templates/CONVENTIONS.md +12 -0
  5. package/assets/templates/HARNESS.md +8 -8
  6. package/assets/templates/company-pipeline-manifest.json +71 -0
  7. package/assets/templates/config.json +29 -27
  8. package/assets/templates/memory.md +16 -2
  9. package/assets/templates/progress.json.template +6 -5
  10. package/bin/init.js +180 -90
  11. package/conventions/shared.md +34 -0
  12. package/gotchas/conductor.md +16 -0
  13. package/gotchas/dispatcher.md +16 -0
  14. package/gotchas/meeting-manager.md +8 -0
  15. package/gotchas/service-ops.md +8 -0
  16. package/package.json +4 -4
  17. package/scripts/conductor-tick.sh +157 -56
  18. package/scripts/harness-archive.sh +7 -5
  19. package/scripts/harness-dashboard-up.sh +11 -3
  20. package/scripts/harness-hourly-review.sh +582 -0
  21. package/scripts/harness-meeting-doc.sh +55 -16
  22. package/scripts/harness-next.sh +1 -1
  23. package/scripts/harness-parity-audit.sh +101 -0
  24. package/scripts/harness-queue-manager.sh +13 -6
  25. package/scripts/harness-service-ops-monitor.sh +273 -0
  26. package/scripts/harness-session-start.sh +60 -31
  27. package/scripts/harness-statusline.sh +8 -12
  28. package/scripts/harness-stop.sh +85 -0
  29. package/scripts/harness-user-prompt-submit.sh +11 -29
  30. package/scripts/harness-wake-install.sh +180 -0
  31. package/scripts/harness-wake.sh +350 -0
  32. package/scripts/harness-worker-dispatch.sh +188 -0
  33. package/scripts/lib/harness-agent-resolver.sh +127 -0
  34. package/scripts/lib/harness-progress-migrate.sh +6 -1
  35. package/scripts/lib/harness-render-progress.sh +3 -3
  36. package/skills/brainstorming/SKILL.md +2 -2
  37. package/skills/conductor/SKILL.md +67 -55
  38. package/skills/cqo/SKILL.md +1 -0
  39. package/skills/cto/SKILL.md +1 -0
  40. package/skills/dispatcher/SKILL.md +33 -14
  41. package/skills/evaluator-code-quality/SKILL.md +4 -4
  42. package/skills/evaluator-functional/SKILL.md +6 -6
  43. package/skills/evaluator-visual/SKILL.md +4 -4
  44. package/skills/generator-backend/SKILL.md +4 -4
  45. package/skills/generator-frontend/SKILL.md +5 -5
  46. package/skills/meeting-manager/SKILL.md +82 -12
  47. package/skills/planner/SKILL.md +3 -3
  48. package/skills/service-ops/SKILL.md +25 -0
  49. package/commands/harness-solo.md +0 -103
  50. package/commands/harness-stop.md +0 -53
  51. package/commands/harness-team.md +0 -530
  52. package/scripts/harness-dashboard.sh +0 -509
  53. package/scripts/harness-goal-init.sh +0 -72
  54. package/scripts/harness-goal-show.sh +0 -37
  55. package/scripts/harness-gotcha-memory.sh +0 -348
  56. package/scripts/harness-monitor.sh +0 -398
  57. package/scripts/harness-prompt-history.sh +0 -164
  58. package/scripts/harness-tmux.sh +0 -372
@@ -1,398 +0,0 @@
1
- #!/bin/bash
2
- # harness-monitor.sh — Agent Lifecycle Monitor (v5 unified)
3
- #
4
- # Solo 모드: 단일 이벤트 스트림 (progress.json 변경 감지 + audit.log)
5
- # Team 모드: 팀별 섹션 분리 (feature-queue.json + progress.log 기반)
6
- #
7
- # Usage: bash scripts/harness-monitor.sh [project-root]
8
-
9
- set -uo pipefail
10
-
11
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
- source "$SCRIPT_DIR/lib/harness-keywait.sh"
13
-
14
- # ── Args ──
15
- # Usage: harness-monitor.sh [project-root] [--team N]
16
- # --team N → 단일 팀만 렌더 (tmux per-team pane 용도)
17
- PROJECT_ROOT=""
18
- TEAM_FILTER=""
19
- while [ $# -gt 0 ]; do
20
- case "$1" in
21
- --team) TEAM_FILTER="$2"; shift 2 ;;
22
- --team=*) TEAM_FILTER="${1#--team=}"; shift ;;
23
- *) [ -z "$PROJECT_ROOT" ] && PROJECT_ROOT="$1"; shift ;;
24
- esac
25
- done
26
-
27
- if [ -z "$PROJECT_ROOT" ]; then
28
- source "$SCRIPT_DIR/lib/harness-render-progress.sh"
29
- PROJECT_ROOT="$(resolve_harness_root ".")" || {
30
- echo "[monitor] .harness/ not found. Pass project root as argument."
31
- exit 1
32
- }
33
- fi
34
-
35
- PROGRESS="$PROJECT_ROOT/.harness/progress.json"
36
- PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
37
- AUDIT_LOG="$PROJECT_ROOT/.harness/actions/audit.log"
38
- QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
39
-
40
- # ── ANSI helpers ──
41
- BOLD="\033[1m"
42
- DIM="\033[2m"
43
- GREEN="\033[32m"
44
- YELLOW="\033[33m"
45
- RED="\033[31m"
46
- CYAN="\033[36m"
47
- MAGENTA="\033[35m"
48
- BLUE="\033[34m"
49
- RESET="\033[0m"
50
-
51
- # ── Team colors ──
52
- T1_COLOR="$CYAN"
53
- T2_COLOR="$MAGENTA"
54
- T3_COLOR="$YELLOW"
55
-
56
- team_color() {
57
- case "$1" in
58
- 1|team-1) echo "$T1_COLOR" ;;
59
- 2|team-2) echo "$T2_COLOR" ;;
60
- 3|team-3) echo "$T3_COLOR" ;;
61
- *) echo "$DIM" ;;
62
- esac
63
- }
64
-
65
- # ══════════════════════════════════════════
66
- # Team 모드: 팀별 섹션 렌더링
67
- # ══════════════════════════════════════════
68
-
69
- render_v4_header() {
70
- echo -e "${BOLD}TEAM MONITOR${RESET} ${DIM}$(date +%H:%M:%S)${RESET}"
71
- echo ""
72
- }
73
-
74
- # 고정폭 배너 구분선 (터미널 너비에 맞춰 채움)
75
- banner_line() {
76
- local cols
77
- cols=$(tput cols 2>/dev/null || echo 78)
78
- local line=""
79
- for ((i=0; i<cols; i++)); do line+="━"; done
80
- echo "$line"
81
- }
82
-
83
- render_team_section() {
84
- local team_num="$1"
85
- local log_lines="${2:-10}"
86
- local color
87
- color=$(team_color "$team_num")
88
-
89
- # 팀 상태 from queue
90
- local t_status="idle" t_feature="—" t_phase="—" t_attempt=""
91
- if [ -f "$QUEUE" ]; then
92
- t_status=$(jq -r ".teams[\"$team_num\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
93
- t_feature=$(jq -r ".teams[\"$team_num\"].feature // \"—\"" "$QUEUE" 2>/dev/null)
94
- if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
95
- t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
96
- t_attempt=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].attempt // 1' "$QUEUE" 2>/dev/null)
97
- fi
98
- fi
99
-
100
- # 상태 아이콘
101
- local icon="○"
102
- case "$t_status" in
103
- busy) icon="▶" ;;
104
- idle) icon="○" ;;
105
- esac
106
-
107
- # Phase 표시
108
- local phase_str=""
109
- case "$t_phase" in
110
- gen) phase_str="Gen" ;;
111
- gate) phase_str="Gate" ;;
112
- eval) phase_str="Eval" ;;
113
- *) phase_str="" ;;
114
- esac
115
-
116
- # ── 큰 배너 ──
117
- local bar
118
- bar=$(banner_line)
119
- printf "%b%s%b\n" "$color" "$bar" "$RESET"
120
-
121
- printf "%b%b TEAM %s%b" "$color$BOLD" "$icon" "$team_num" "$RESET"
122
- if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
123
- printf " %b%s%b" "$BOLD" "$t_feature" "$RESET"
124
- if [ -n "$phase_str" ]; then
125
- printf " %b[%s]%b" "$color" "$phase_str" "$RESET"
126
- fi
127
- if [ -n "$t_attempt" ] && [ "$t_attempt" != "—" ] && [ "$t_attempt" != "1" ]; then
128
- printf " %battempt #%s%b" "$YELLOW" "$t_attempt" "$RESET"
129
- fi
130
- else
131
- printf " %bidle%b" "$DIM" "$RESET"
132
- fi
133
- echo ""
134
- printf "%b%s%b\n" "$color" "$bar" "$RESET"
135
-
136
- # ── 팀 로그 ──
137
- local have_logs=0
138
- if [ -f "$PROGRESS_LOG" ]; then
139
- local matched
140
- # Team panel 필터: agent 필드($2)가 해당 team 번호를 포함하는 모든 prefix 매칭.
141
- # 허용: team-N, team_N, eval-N, eval_N, gen-N, gen-fe-N, worker-N, lead-N 등
142
- # (워커가 evaluator 분리를 위해 eval-N 으로 로깅해도 누락되지 않게 한다)
143
- matched=$(awk -F'|' -v n="${team_num}" '
144
- {
145
- # $2 = agent (앞뒤 공백 trim)
146
- a=$2; gsub(/^ +| +$/,"",a)
147
- # agent 토큰이 [-_]N\b 로 끝나거나 -N- / _N_ 처럼 N이 토큰 경계에 포함되면 매치
148
- if (match(a, "(^|[-_])" n "([-_]|$)")) print
149
- }' "$PROGRESS_LOG" 2>/dev/null | tail -"$log_lines")
150
- if [ -n "$matched" ]; then
151
- have_logs=1
152
- local cols
153
- cols=$(tput cols 2>/dev/null || echo 78)
154
- # 포맷: [ts] [icon agent] detail (너무 길면 터미널 너비에서 줄바꿈)
155
- local prefix_w=18 # " HH:MM ✦ eval "
156
- local detail_w=$(( cols - prefix_w ))
157
- [ "$detail_w" -lt 20 ] && detail_w=20
158
-
159
- echo "$matched" | while IFS= read -r line; do
160
- local ts action detail
161
- ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
162
- action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
163
- detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
164
-
165
- local short_ts
166
- short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}' | tail -1 || echo "$ts")
167
-
168
- local a_icon="·" a_color="$DIM" a_label="$action"
169
- case "$action" in
170
- gen|gen-start|gen-read|gen-write|gen-test|gen-done)
171
- a_icon="▶"; a_color="$GREEN"; a_label="Gen" ;;
172
- eval-start)
173
- a_icon="✦"; a_color="$MAGENTA"; a_label="Eval" ;;
174
- eval-check)
175
- a_icon="·"; a_color="$MAGENTA"; a_label="Eval" ;;
176
- eval-done)
177
- a_icon="✦"; a_color="${BOLD}${MAGENTA}"; a_label="Eval" ;;
178
- eval)
179
- a_icon="✦"; a_color="$MAGENTA"; a_label="Eval" ;;
180
- result|pass)
181
- a_icon="✓"; a_color="$GREEN"; a_label="Result" ;;
182
- fail)
183
- a_icon="✗"; a_color="$RED"; a_label="Result" ;;
184
- dequeue) a_icon="→"; a_color="$CYAN"; a_label="Queue" ;;
185
- gate) a_icon="◆"; a_color="$YELLOW"; a_label="Gate" ;;
186
- *) a_icon="·"; a_color="$DIM"; a_label="$action" ;;
187
- esac
188
-
189
- # 첫 줄
190
- local first="${detail:0:$detail_w}"
191
- printf " %b%s%b %b%s%b %-6s %s\n" \
192
- "$DIM" "$short_ts" "$RESET" \
193
- "$a_color" "$a_icon" "$RESET" \
194
- "$a_label" "$first"
195
- # 이어질 줄 (긴 detail 은 접지 않고 들여쓰기로 이어서 출력)
196
- local rest="${detail:$detail_w}"
197
- while [ -n "$rest" ]; do
198
- local chunk="${rest:0:$detail_w}"
199
- rest="${rest:$detail_w}"
200
- printf " %*s%s\n" "$prefix_w" "" "$chunk"
201
- done
202
- done
203
- fi
204
- fi
205
- if [ "$have_logs" -eq 0 ]; then
206
- printf " %b(no activity)%b\n" "$DIM" "$RESET"
207
- fi
208
- }
209
-
210
- render_v4() {
211
- # 단일 팀 모드 (tmux per-team pane)
212
- if [ -n "$TEAM_FILTER" ]; then
213
- local rows log_lines
214
- rows=$(tput lines 2>/dev/null || echo 20)
215
- log_lines=$(( rows - 5 ))
216
- [ "$log_lines" -lt 3 ] && log_lines=3
217
- render_team_section "$TEAM_FILTER" "$log_lines"
218
- return
219
- fi
220
-
221
- render_v4_header
222
-
223
- local team_count=3
224
- if [ -f "$QUEUE" ]; then
225
- team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null || echo 3)
226
- fi
227
- [ "$team_count" -lt 1 ] && team_count=3
228
-
229
- local rows per_team log_lines
230
- rows=$(tput lines 2>/dev/null || echo 40)
231
- per_team=$(( (rows - 2) / team_count ))
232
- log_lines=$(( per_team - 5 ))
233
- if [ "$log_lines" -lt 3 ]; then log_lines=3; fi
234
- if [ "$log_lines" -gt 15 ]; then log_lines=15; fi
235
-
236
- for i in $(seq 1 "$team_count"); do
237
- render_team_section "$i" "$log_lines"
238
- echo ""
239
- done
240
- }
241
-
242
- # ══════════════════════════════════════════
243
- # Solo 모드: 단일 스트림 (기존 동작)
244
- # ══════════════════════════════════════════
245
-
246
- LAST_AGENT=""
247
- LAST_STATUS=""
248
- LAST_SPRINT=""
249
-
250
- print_event() {
251
- local ts="$1" icon="$2" color="$3" msg="$4"
252
- echo -e " ${DIM}${ts}${RESET} ${color}${icon}${RESET} ${msg}"
253
- }
254
-
255
- print_v3_header() {
256
- echo -e "${BOLD}╔══════════════════════════════════════╗${RESET}"
257
- echo -e "${BOLD}║ AGENT LIFECYCLE MONITOR ║${RESET}"
258
- echo -e "${BOLD}╚══════════════════════════════════════╝${RESET}"
259
- echo ""
260
- }
261
-
262
- check_transitions() {
263
- if [ ! -f "$PROGRESS" ]; then return; fi
264
-
265
- local agent status sprint
266
- agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
267
- status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
268
- sprint=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
269
-
270
- local now
271
- now=$(date +"%H:%M:%S")
272
-
273
- if [ "$sprint" != "$LAST_SPRINT" ] && [ -n "$LAST_SPRINT" ]; then
274
- print_event "$now" ">>>" "$MAGENTA" "${BOLD}Sprint ${sprint} started${RESET}"
275
- echo ""
276
- fi
277
-
278
- if [ "$agent" != "$LAST_AGENT" ] && [ -n "$LAST_AGENT" ]; then
279
- if [ "$LAST_AGENT" != "none" ] && [ "$LAST_AGENT" != "null" ]; then
280
- print_event "$now" " ✓ " "$GREEN" "${LAST_AGENT} → ${BOLD}done${RESET}"
281
- fi
282
- if [ "$agent" != "none" ] && [ "$agent" != "null" ]; then
283
- print_event "$now" " ▶ " "$CYAN" "${BOLD}${agent}${RESET} started"
284
- fi
285
- fi
286
-
287
- if [ "$agent" = "$LAST_AGENT" ] && [ "$status" != "$LAST_STATUS" ] && [ -n "$LAST_STATUS" ]; then
288
- local status_color="$RESET" status_icon="•"
289
- case "$status" in
290
- running) status_color="$GREEN"; status_icon=" ▶ " ;;
291
- completed) status_color="$CYAN"; status_icon=" ✓ " ;;
292
- failed) status_color="$RED"; status_icon=" ✗ " ;;
293
- blocked) status_color="$RED"; status_icon=" ! " ;;
294
- pending) status_color="$YELLOW"; status_icon=" … " ;;
295
- esac
296
- print_event "$now" "$status_icon" "$status_color" "${agent} → ${BOLD}${status}${RESET}"
297
-
298
- if [ "$status" = "failed" ]; then
299
- local fail_msg
300
- fail_msg=$(jq -r '.failure.message // empty' "$PROGRESS" 2>/dev/null)
301
- if [ -n "$fail_msg" ] && [ "$fail_msg" != "null" ]; then
302
- echo -e " ${RED}${fail_msg}${RESET}"
303
- fi
304
- fi
305
- fi
306
-
307
- LAST_AGENT="$agent"
308
- LAST_STATUS="$status"
309
- LAST_SPRINT="$sprint"
310
- }
311
-
312
- stream_audit() {
313
- if [ ! -f "$AUDIT_LOG" ]; then return; fi
314
- tail -n 0 -f "$AUDIT_LOG" 2>/dev/null | while IFS= read -r line; do
315
- if [[ "$line" == "#"* ]] || [[ -z "$line" ]]; then continue; fi
316
- local ts agent action status target
317
- ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
318
- agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
319
- action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
320
- status=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
321
- target=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$5); print $5}')
322
-
323
- local color="$RESET" icon="•"
324
- case "$status" in
325
- start) color="$CYAN"; icon="▶" ;;
326
- complete) color="$GREEN"; icon="✓" ;;
327
- fail) color="$RED"; icon="✗" ;;
328
- pass) color="$GREEN"; icon="✓" ;;
329
- skip) color="$DIM"; icon="–" ;;
330
- esac
331
-
332
- local short_ts
333
- short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "$ts")
334
- echo -e " ${DIM}${short_ts}${RESET} ${color}${icon}${RESET} ${BOLD}${agent}${RESET} ${action} ${target}"
335
- done &
336
- AUDIT_TAIL_PID=$!
337
- }
338
-
339
- # ══════════════════════════════════════════
340
- # Mode detection & main loop
341
- # ══════════════════════════════════════════
342
-
343
- cleanup() {
344
- if [ -n "${AUDIT_TAIL_PID:-}" ]; then
345
- kill "$AUDIT_TAIL_PID" 2>/dev/null || true
346
- fi
347
- tput cnorm 2>/dev/null
348
- exit 0
349
- }
350
- trap cleanup EXIT INT TERM
351
-
352
- # 매 루프마다 mode 동적 감지 — 시작 후에도 모드 전환 가능
353
- tput civis 2>/dev/null
354
- clear
355
-
356
- CURRENT_MODE=""
357
-
358
- while true; do
359
- # 동적 모드 감지 (--team 지정 시 항상 team, 그 외 progress.json.mode 참조)
360
- if [ -n "$TEAM_FILTER" ] || [ -f "$QUEUE" ]; then
361
- NEW_MODE="team"
362
- else
363
- if [ -f "$PROGRESS" ] && command -v jq &>/dev/null; then
364
- NEW_MODE=$(jq -r '.mode // "solo"' "$PROGRESS" 2>/dev/null)
365
- [ "$NEW_MODE" = "team" ] || NEW_MODE="solo"
366
- else
367
- NEW_MODE="solo"
368
- fi
369
- fi
370
-
371
- # 모드 전환 시 화면 초기화
372
- if [ "$NEW_MODE" != "$CURRENT_MODE" ]; then
373
- clear
374
- CURRENT_MODE="$NEW_MODE"
375
- if [ "$CURRENT_MODE" = "solo" ]; then
376
- # solo 초기화
377
- if [ -f "$PROGRESS" ]; then
378
- LAST_AGENT=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
379
- LAST_STATUS=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
380
- LAST_SPRINT=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
381
- fi
382
- stream_audit
383
- fi
384
- fi
385
-
386
- if [ "$CURRENT_MODE" = "team" ]; then
387
- buf=$(render_v4 2>&1)
388
- tput cup 0 0 2>/dev/null
389
- echo "$buf"
390
- tput ed 2>/dev/null
391
- else
392
- check_transitions
393
- fi
394
-
395
- printf "${DIM} [r] refresh [q] quit${RESET}\033[K\n"
396
- wait_or_refresh 3 || true
397
- done
398
-
@@ -1,164 +0,0 @@
1
- #!/bin/bash
2
- # harness-prompt-history.sh — User Prompt History pane
3
- # 사용자가 입력한 프롬프트만 표시 + 현재 처리 단계(Planner/Team) 표시
4
- # Usage: bash scripts/harness-prompt-history.sh [project-root]
5
-
6
- set -uo pipefail
7
-
8
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
- source "$SCRIPT_DIR/lib/harness-render-progress.sh"
10
- source "$SCRIPT_DIR/lib/harness-keywait.sh"
11
-
12
- PROJECT_ROOT="${1:-}"
13
- if [ -z "$PROJECT_ROOT" ]; then
14
- PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[history] .harness/ not found."; exit 1; }
15
- fi
16
-
17
- PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
18
- PROGRESS="$PROJECT_ROOT/.harness/progress.json"
19
- QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
20
-
21
- BOLD="\033[1m"
22
- DIM="\033[2m"
23
- GREEN="\033[32m"
24
- YELLOW="\033[33m"
25
- RED="\033[31m"
26
- CYAN="\033[36m"
27
- MAGENTA="\033[35m"
28
- WHITE="\033[37m"
29
- RESET="\033[0m"
30
- BG_BLUE="\033[44m"
31
- BG_GREEN="\033[42m"
32
- BG_YELLOW="\033[43m"
33
-
34
- render_header() {
35
- echo -e "${BOLD}USER PROMPTS${RESET} ${DIM}$(date +%H:%M:%S)${RESET}"
36
- echo ""
37
- }
38
-
39
- # 현재 처리 상태 배지
40
- render_processing_status() {
41
- local mode="solo" current_agent="none" agent_status="pending"
42
-
43
- if [ -f "$PROGRESS" ] && command -v jq &>/dev/null; then
44
- mode=$(jq -r '.mode // "solo"' "$PROGRESS" 2>/dev/null)
45
- current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
46
- agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
47
- fi
48
-
49
- local badge=""
50
- case "$mode" in
51
- team)
52
- # Team 모드: 각 팀의 현재 상태 표시
53
- badge="${BG_BLUE}${WHITE}${BOLD} TEAM MODE ${RESET}"
54
- if [ -f "$QUEUE" ]; then
55
- local team_info=""
56
- for i in 1 2 3; do
57
- local t_status t_feature t_phase
58
- t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
59
- t_feature=$(jq -r ".teams[\"$i\"].feature // \"\"" "$QUEUE" 2>/dev/null)
60
- if [ "$t_status" = "busy" ] && [ -n "$t_feature" ] && [ "$t_feature" != "null" ]; then
61
- t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
62
- team_info+=" ${CYAN}T${i}${RESET}:${t_feature}(${t_phase})"
63
- else
64
- team_info+=" ${DIM}T${i}:idle${RESET}"
65
- fi
66
- done
67
- echo -e " ${badge}${team_info}"
68
- else
69
- echo -e " ${badge}"
70
- fi
71
- ;;
72
- paused)
73
- echo -e " ${BG_YELLOW}${WHITE}${BOLD} PAUSED ${RESET} ${DIM}/harness-team 또는 /harness-solo${RESET}"
74
- ;;
75
- *)
76
- # Solo 모드: 현재 에이전트 표시
77
- if [ "$current_agent" != "none" ] && [ "$current_agent" != "null" ]; then
78
- local agent_color="$RESET"
79
- case "$current_agent" in
80
- planner*) agent_color="$YELLOW" ;;
81
- generator*) agent_color="$GREEN" ;;
82
- eval*) agent_color="$RED" ;;
83
- dispatcher*) agent_color="$MAGENTA" ;;
84
- esac
85
- local status_icon="·"
86
- case "$agent_status" in
87
- running) status_icon="▶" ;;
88
- completed) status_icon="✓" ;;
89
- failed) status_icon="✗" ;;
90
- esac
91
- echo -e " ${BG_GREEN}${WHITE}${BOLD} SOLO ${RESET} ${agent_color}${status_icon} ${current_agent}${RESET}"
92
- else
93
- echo -e " ${BG_GREEN}${WHITE}${BOLD} SOLO ${RESET} ${DIM}대기 중${RESET}"
94
- fi
95
- ;;
96
- esac
97
- echo ""
98
- }
99
-
100
- # 사용자 프롬프트만 필터하여 표시 (newest first, 전체 출력)
101
- render_user_prompts() {
102
- if [ ! -f "$PROGRESS_LOG" ]; then
103
- echo -e " ${DIM}(프롬프트 기록 없음)${RESET}"
104
- return
105
- fi
106
-
107
- # user-prompt 행만 필터 → tac으로 newest first
108
- local prompts
109
- prompts=$(grep '| user-prompt |' "$PROGRESS_LOG" 2>/dev/null | tac)
110
-
111
- if [ -z "$prompts" ]; then
112
- echo -e " ${DIM}(사용자 프롬프트 없음)${RESET}"
113
- return
114
- fi
115
-
116
- local total_prompts
117
- total_prompts=$(echo "$prompts" | wc -l | tr -d ' ')
118
-
119
- # 터미널 폭: main loop에서 측정한 _COLS 사용 (subshell 내 tput 불가 대비)
120
- local max_width=$(( ${_COLS:-80} - 12 ))
121
- if [ "$max_width" -lt 20 ]; then max_width=20; fi
122
-
123
- echo "$prompts" | while IFS= read -r line; do
124
- local ts detail
125
- ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
126
- detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
127
-
128
- local short_ts
129
- short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}' | tail -1 || echo "$ts")
130
-
131
- if [ ${#detail} -gt "$max_width" ]; then
132
- detail="${detail:0:$((max_width - 2))}.."
133
- fi
134
-
135
- echo -e " ${DIM}${short_ts}${RESET} ${WHITE}${detail}${RESET}"
136
- done
137
-
138
- echo ""
139
- echo -e " ${DIM}── ${total_prompts} prompts total ──${RESET}"
140
- }
141
-
142
- render_all() {
143
- render_header
144
- render_processing_status
145
- render_user_prompts
146
- }
147
-
148
- # ── Main loop ──
149
- tput civis 2>/dev/null
150
- trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
151
- clear
152
-
153
- while true; do
154
- # subshell 내 tput 불가 → 미리 터미널 크기 측정
155
- _ROWS=$(tput lines 2>/dev/null || echo 30)
156
- _COLS=$(tput cols 2>/dev/null || echo 80)
157
- export _ROWS _COLS
158
- buf=$(render_all 2>&1)
159
- tput cup 0 0 2>/dev/null
160
- echo "$buf"
161
- tput ed 2>/dev/null
162
- printf "${DIM} [r] refresh [q] quit${RESET}\033[K\n"
163
- wait_or_refresh 3 || true
164
- done