@walwal-harness/cli 6.0.5 → 6.1.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/commands/harness-solo.md +20 -20
  3. package/commands/harness-team.md +1 -1
  4. package/package.json +1 -1
  5. package/scripts/conductor-tick.sh +382 -0
  6. package/scripts/harness-archive.sh +123 -0
  7. package/scripts/harness-dashboard-up.sh +72 -0
  8. package/scripts/harness-dashboard.sh +509 -0
  9. package/scripts/harness-goal-init.sh +72 -0
  10. package/scripts/harness-goal-show.sh +37 -0
  11. package/scripts/harness-gotcha-memory.sh +348 -0
  12. package/scripts/harness-gotcha-register.sh +393 -0
  13. package/scripts/harness-meeting-doc.sh +153 -0
  14. package/scripts/harness-monitor.sh +398 -0
  15. package/scripts/harness-next.sh +577 -0
  16. package/scripts/harness-progress-set.sh +41 -0
  17. package/scripts/harness-prompt-history.sh +164 -0
  18. package/scripts/harness-queue-manager.sh +694 -0
  19. package/scripts/harness-session-start.sh +140 -0
  20. package/scripts/harness-statusline.sh +139 -0
  21. package/scripts/harness-task-session.sh +48 -0
  22. package/scripts/harness-tmux.sh +372 -0
  23. package/scripts/harness-user-prompt-submit.sh +123 -0
  24. package/scripts/init-agents-md.sh +337 -0
  25. package/scripts/init-ref-docs.sh +262 -0
  26. package/scripts/lib/harness-audit.sh +113 -0
  27. package/scripts/lib/harness-guardrail.sh +148 -0
  28. package/scripts/lib/harness-keywait.sh +57 -0
  29. package/scripts/lib/harness-render-progress.sh +346 -0
  30. package/scripts/scan-project.sh +367 -0
  31. package/skills/brainstorming/SKILL.md +4 -4
  32. package/skills/conductor/SKILL.md +12 -16
  33. package/skills/cqo/SKILL.md +7 -0
  34. package/skills/cto/SKILL.md +7 -0
  35. package/skills/dispatcher/SKILL.md +17 -28
  36. package/skills/evaluator-code-quality/SKILL.md +1 -1
  37. package/skills/evaluator-functional/SKILL.md +1 -1
  38. package/skills/evaluator-visual/SKILL.md +1 -1
  39. package/skills/generator-backend/SKILL.md +1 -1
  40. package/skills/generator-frontend/SKILL.md +1 -1
  41. package/skills/meeting-manager/SKILL.md +33 -1
  42. package/skills/planner/SKILL.md +23 -9
  43. package/skills/service-ops/SKILL.md +19 -4
@@ -0,0 +1,398 @@
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
+