@walwal-harness/cli 6.0.3 → 6.0.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.
@@ -1,509 +0,0 @@
1
- #!/bin/bash
2
- # harness-dashboard.sh — Unified Dashboard (Solo + Team mode)
3
- #
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
7
- #
8
- # Usage: bash scripts/harness-dashboard.sh [project-root]
9
-
10
- set -uo pipefail
11
-
12
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
- source "$SCRIPT_DIR/lib/harness-render-progress.sh"
14
- source "$SCRIPT_DIR/lib/harness-keywait.sh"
15
-
16
- PROJECT_ROOT="${1:-}"
17
- if [ -z "$PROJECT_ROOT" ]; then
18
- PROJECT_ROOT="$(resolve_harness_root ".")" || {
19
- echo "[dashboard] .harness/ not found. Pass project root as argument."
20
- exit 1
21
- }
22
- fi
23
-
24
- PROGRESS="$PROJECT_ROOT/.harness/progress.json"
25
- FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
26
- QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
27
- PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
28
-
29
- # ── ANSI helpers ──
30
- BOLD="\033[1m"
31
- DIM="\033[2m"
32
- GREEN="\033[32m"
33
- YELLOW="\033[33m"
34
- RED="\033[31m"
35
- CYAN="\033[36m"
36
- MAGENTA="\033[35m"
37
- RESET="\033[0m"
38
-
39
- strip_ansi() {
40
- sed 's/\x1b\[[0-9;]*m//g; s/\x1b\[[0-9;]*[a-zA-Z]//g'
41
- }
42
-
43
- get_term_width() {
44
- echo "${_COLS:-$(tput cols 2>/dev/null || echo 80)}"
45
- }
46
-
47
- get_mode() {
48
- if [ -f "$PROGRESS" ] && command -v jq &>/dev/null; then
49
- local raw_mode queue active
50
- raw_mode=$(jq -r '.mode // "solo"' "$PROGRESS" 2>/dev/null)
51
- queue="$(dirname "$PROGRESS")/actions/feature-queue.json"
52
- if [ "$raw_mode" != "team" ] && [ -f "$queue" ]; then
53
- active=$(jq -r '(.queue.in_progress | length) // 0' "$queue" 2>/dev/null || echo 0)
54
- if [ "${active:-0}" -gt 0 ]; then
55
- local heal_teams=$active
56
- [ "$heal_teams" -gt 3 ] && heal_teams=3
57
- local SCRIPT_DIR_LOCAL
58
- SCRIPT_DIR_LOCAL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
59
- bash "$SCRIPT_DIR_LOCAL/harness-progress-set.sh" "$(dirname "$(dirname "$PROGRESS")")" \
60
- ".mode = \"team\" | .team_state.active_teams = ${heal_teams} | .team_state.paused_at = null" \
61
- 2>/dev/null
62
- if [ -f "$(dirname "$PROGRESS")/progress.log" ]; then
63
- echo "$(date +'%Y-%m-%d %H:%M') | system | heal | mode | dashboard reset ${raw_mode}→team (queue had ${active} in_progress)" >> "$(dirname "$PROGRESS")/progress.log"
64
- fi
65
- echo "team"
66
- return
67
- fi
68
- fi
69
- echo "$raw_mode"
70
- else
71
- echo "solo"
72
- fi
73
- }
74
-
75
- # ══════════════════════════════════════════
76
- # Common header
77
- # ══════════════════════════════════════════
78
- render_header() {
79
- local now project_name mode
80
- now=$(date +"%H:%M:%S")
81
- project_name=$(jq -r '.project_name // "Unknown"' "$PROGRESS" 2>/dev/null)
82
- mode=$(get_mode)
83
-
84
- local mode_label
85
- case "$mode" in
86
- team) mode_label="${GREEN}TEAM${RESET}" ;;
87
- paused) mode_label="${YELLOW}PAUSED${RESET}" ;;
88
- *) mode_label="${CYAN}SOLO${RESET}" ;;
89
- esac
90
-
91
- echo -e "${BOLD}HARNESS DASHBOARD${RESET} ${mode_label} ${DIM}${project_name} | ${now}${RESET}"
92
- echo ""
93
- }
94
-
95
- # ══════════════════════════════════════════
96
- # Solo Mode rendering
97
- # ══════════════════════════════════════════
98
- render_solo_sprint_overview() {
99
- if [ ! -f "$PROGRESS" ]; then
100
- echo -e " ${DIM}(waiting for harness init...)${RESET}"
101
- return
102
- fi
103
-
104
- local pipeline sprint_num sprint_status current_agent agent_status retry_count
105
- # progress.json 이 깨졌을 때 jq 가 stderr 로 에러를 뿜고 stdout 은 빈 문자열을 반환.
106
- # 모든 jq 호출에 2>/dev/null + bash fallback 으로 변수 기본값 보장.
107
- if ! jq empty "$PROGRESS" 2>/dev/null; then
108
- echo -e " ${RED}⚠ .harness/progress.json 이 손상되었습니다 (invalid JSON).${RESET}"
109
- echo -e " ${DIM}복구: cp .harness/progress.json{,.bak} && jq . .harness/progress.json.bak${RESET}"
110
- echo -e " ${DIM}또는: npx @walwal-harness/cli@latest --force (전체 재초기화)${RESET}"
111
- echo ""
112
- return
113
- fi
114
- pipeline=$(jq -r '.pipeline // "?"' "$PROGRESS" 2>/dev/null)
115
- sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
116
- sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null)
117
- current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
118
- agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
119
- retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS" 2>/dev/null)
120
- # 빈 문자열 → 기본값 (jq 가 silently fail 한 잔여 케이스 대비)
121
- pipeline="${pipeline:-?}"
122
- sprint_num="${sprint_num:-0}"
123
- sprint_status="${sprint_status:-init}"
124
- current_agent="${current_agent:-none}"
125
- agent_status="${agent_status:-pending}"
126
- retry_count="${retry_count:-0}"
127
-
128
- local status_color="$RESET"
129
- case "$agent_status" in
130
- running) status_color="$GREEN" ;;
131
- completed) status_color="$CYAN" ;;
132
- failed) status_color="$RED" ;;
133
- blocked) status_color="$RED" ;;
134
- *) status_color="$YELLOW" ;;
135
- esac
136
-
137
- 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}")"
138
- echo ""
139
- }
140
-
141
- render_solo_failure_info() {
142
- if [ ! -f "$PROGRESS" ]; then return; fi
143
-
144
- local failure_agent
145
- failure_agent=$(jq -r '.failure.agent // empty' "$PROGRESS" 2>/dev/null)
146
-
147
- if [ -n "$failure_agent" ] && [ "$failure_agent" != "null" ]; then
148
- local failure_loc failure_msg
149
- failure_loc=$(jq -r '.failure.location // ""' "$PROGRESS")
150
- failure_msg=$(jq -r '.failure.message // ""' "$PROGRESS" | strip_ansi | tr '\n' ' ' | sed 's/ */ /g')
151
- if [ ${#failure_msg} -gt 80 ]; then failure_msg="${failure_msg:0:78}.."; fi
152
-
153
- echo -e " ${RED}${BOLD}FAIL${RESET} ${RED}${failure_agent} → ${failure_loc}${RESET}"
154
- if [ -n "$failure_msg" ]; then
155
- echo -e " ${DIM}${failure_msg}${RESET}"
156
- fi
157
- echo ""
158
- fi
159
- }
160
-
161
- render_solo_agent_info() {
162
- if [ ! -f "$PROGRESS" ]; then return; fi
163
-
164
- local next_agent agent_status
165
- next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
166
- agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
167
-
168
- if [ "$next_agent" != "none" ] && [ "$next_agent" != "null" ] && [ "$agent_status" != "blocked" ]; then
169
- echo -e " ${CYAN}Next → /harness-${next_agent}${RESET}"
170
- fi
171
-
172
- render_agent_bar "$PROJECT_ROOT" 2>/dev/null
173
- echo ""
174
- }
175
-
176
- render_solo_prompt_history() {
177
- echo -e "${BOLD}Recent Activity${RESET}"
178
-
179
- if [ -f "$PROGRESS_LOG" ]; then
180
- grep -v '^#' "$PROGRESS_LOG" 2>/dev/null | grep -v '^$' | tail -10 | while IFS= read -r line; do
181
- local ts agent action detail
182
- ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
183
- agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
184
- action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
185
- detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
186
-
187
- local short_ts icon color
188
- short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
189
-
190
- # action 기반으로 먼저 분류 (team 모드에서 agent=team-N이므로)
191
- case "$action" in
192
- eval-start|eval-check|eval-done|eval)
193
- icon="✦" ; color="$MAGENTA" ;;
194
- gen-start|gen-read|gen-write|gen-test|gen-done|gen)
195
- icon="▶" ; color="$GREEN" ;;
196
- result|pass)
197
- icon="✓" ; color="$GREEN" ;;
198
- fail)
199
- icon="✗" ; color="$RED" ;;
200
- *)
201
- # fallback: agent 기반
202
- case "$agent" in
203
- dispatcher*) icon="▸" ; color="$MAGENTA" ;;
204
- planner*) icon="□" ; color="$YELLOW" ;;
205
- generator*) icon="▶" ; color="$GREEN" ;;
206
- eval*) icon="✦" ; color="$MAGENTA" ;;
207
- team-*) icon="◆" ; color="$CYAN" ;;
208
- user*) icon="★" ; color="$BOLD" ;;
209
- *) icon="·" ; color="$DIM" ;;
210
- esac
211
- ;;
212
- esac
213
-
214
- if [ ${#detail} -gt 40 ]; then detail="${detail:0:38}.."; fi
215
-
216
- echo -e "${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
217
- done
218
- else
219
- echo -e "${DIM}(no progress.log yet)${RESET}"
220
- fi
221
- }
222
-
223
- # ══════════════════════════════════════════
224
- # Team Mode rendering
225
- # ══════════════════════════════════════════
226
- render_team_queue_summary() {
227
- if [ ! -f "$QUEUE" ]; then
228
- echo -e " ${DIM}(queue not initialized — run /harness-team)${RESET}"
229
- return
230
- fi
231
-
232
- local ready blocked in_prog passed failed total
233
- ready=$(jq '.queue.ready | length' "$QUEUE" 2>/dev/null || echo 0)
234
- blocked=$(jq '.queue.blocked | length' "$QUEUE" 2>/dev/null || echo 0)
235
- in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
236
- failed=$(jq '.queue.failed | length' "$QUEUE" 2>/dev/null || echo 0)
237
-
238
- # passed 는 queue.passed ∪ feature-list.json 의 self-passed 를 dedup 합집합으로 계산
239
- # total 도 feature-list IDs ∪ queue 전체 IDs 합집합으로 계산 — 새 sprint feature 가
240
- # queue 에만 추가되고 feature-list 에 누락되어도 누락 없이 카운트 (예: F-800+ Sprint 8)
241
- if [ -f "$FEATURES" ]; then
242
- passed=$(jq -r --slurpfile q "$QUEUE" '
243
- ($q[0].queue.passed // []) as $qp |
244
- ([.features[] | select((.passes // []) | any(. == "evaluator-functional" or . == "evaluator-visual" or . == "evaluator-code-quality")) | .id]) as $sp |
245
- ($qp + $sp | unique | length)
246
- ' "$FEATURES" 2>/dev/null || echo 0)
247
- total=$(jq -r --slurpfile q "$QUEUE" '
248
- ([.features[].id]) as $fl |
249
- (($q[0].queue.ready // []) +
250
- ($q[0].queue.passed // []) +
251
- (($q[0].queue.failed // []) | if type == "object" then keys else . end) +
252
- (($q[0].queue.in_progress // {}) | keys) +
253
- (($q[0].queue.blocked // {}) | keys)) as $qids |
254
- ($fl + $qids | unique | length)
255
- ' "$FEATURES" 2>/dev/null || echo 0)
256
- else
257
- passed=$(jq '.queue.passed | length' "$QUEUE" 2>/dev/null || echo 0)
258
- total=$((ready + blocked + in_prog + passed + failed))
259
- fi
260
- ready=${ready:-0}; blocked=${blocked:-0}; in_prog=${in_prog:-0}; passed=${passed:-0}; failed=${failed:-0}; total=${total:-0}
261
-
262
- local pct=0
263
- if [ "$total" -gt 0 ]; then pct=$(( passed * 100 / total )); fi
264
- local bar_w=16
265
- local filled=$(( pct * bar_w / 100 ))
266
- local empty=$(( bar_w - filled ))
267
- local bar=""
268
- for ((i=0; i<filled; i++)); do bar+="█"; done
269
- for ((i=0; i<empty; i++)); do bar+="░"; done
270
-
271
- 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}"
272
-
273
- # Integrity warn — 큐에 있지만 feature-list 에 없는 orphan feature 가 있으면 경고.
274
- # 발생 원인: Lead 가 jq 로 큐 직접 편집 (canonical path = enqueue 명령 사용).
275
- if [ -f "$FEATURES" ]; then
276
- local orphan_count
277
- orphan_count=$(jq -r --slurpfile q "$QUEUE" '
278
- ([.features[].id]) as $fl |
279
- (($q[0].queue.ready // []) +
280
- ($q[0].queue.passed // []) +
281
- (($q[0].queue.failed // []) | if type == "object" then keys else . end) +
282
- (($q[0].queue.in_progress // {}) | keys) +
283
- (($q[0].queue.blocked // {}) | keys)) as $qids |
284
- ($qids - $fl) | unique | length
285
- ' "$FEATURES" 2>/dev/null || echo 0)
286
- if [ "${orphan_count:-0}" -gt 0 ]; then
287
- echo -e " ${YELLOW}⚠ ${orphan_count} feature(s) in queue but not in feature-list.json${RESET} ${DIM}(use: queue-manager enqueue)${RESET}"
288
- fi
289
- fi
290
- }
291
-
292
- render_team_status() {
293
- if [ ! -f "$QUEUE" ]; then return; fi
294
-
295
- local team_count
296
- team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null)
297
- if [ "${team_count:-0}" -eq 0 ]; then return; fi
298
-
299
- echo ""
300
- echo -e "${BOLD}Teams${RESET}"
301
-
302
- for i in $(seq 1 "$team_count"); do
303
- local t_status t_feature t_phase t_attempt
304
- t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
305
- t_feature=$(jq -r ".teams[\"$i\"].feature // \"—\"" "$QUEUE" 2>/dev/null)
306
-
307
- if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
308
- t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
309
- t_attempt=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].attempt // 1' "$QUEUE" 2>/dev/null)
310
- else
311
- t_phase="—"; t_attempt=""
312
- fi
313
-
314
- local icon color
315
- case "$t_status" in
316
- busy) icon="▶"; color="$GREEN" ;;
317
- idle) icon="○"; color="$DIM" ;;
318
- *) icon="?"; color="$RESET" ;;
319
- esac
320
-
321
- local phase_short=""
322
- case "$t_phase" in
323
- gen) phase_short="${CYAN}Gen${RESET}" ;;
324
- gate) phase_short="${YELLOW}Gate${RESET}" ;;
325
- eval) phase_short="${MAGENTA}Eval${RESET}" ;;
326
- *) phase_short="${DIM}-${RESET}" ;;
327
- esac
328
-
329
- printf " %b%b T%d %-7s %b" "$color" "$icon" "$i" "$t_feature" "$phase_short"
330
- if [ -n "$t_attempt" ] && [ "$t_attempt" != "—" ]; then
331
- printf " #%s/5" "$t_attempt"
332
- fi
333
- echo ""
334
- done
335
- }
336
-
337
- render_team_features() {
338
- if [ ! -f "$QUEUE" ] || [ ! -f "$FEATURES" ]; then return; fi
339
-
340
- echo ""
341
- echo -e "${BOLD}Features${RESET}"
342
-
343
- # feature-list IDs ∪ queue 전체 IDs 합집합을 iterate.
344
- # feature-list 에 등록 안 된 queue-only feature 는 name="(queue)" 로 표시.
345
- jq -r --slurpfile q "$QUEUE" '
346
- ($q[0].queue.passed // []) as $passed |
347
- ($q[0].queue.failed // []) as $failed_raw |
348
- (if ($failed_raw | type) == "object" then ($failed_raw | keys) else $failed_raw end) as $failed |
349
- ($q[0].queue.ready // []) as $ready |
350
- ($q[0].queue.in_progress // {}) as $prog |
351
- ($q[0].queue.blocked // {}) as $blocked |
352
- ([.features[] | {id: .id, name: (.name // .title // .description // "?"), passes: (.passes // [])}]) as $fl |
353
- ([$fl[] | {(.id): .}] | add // {}) as $fl_map |
354
- (([$fl[].id]) + $ready + $passed + $failed + ($prog | keys) + ($blocked | keys) | unique) as $all_ids |
355
- $all_ids[] |
356
- . as $fid |
357
- ($fl_map[$fid] // {name: "(queue)", passes: []}) as $f |
358
- ($f.name | if length > 18 then .[0:16] + ".." else . end) as $fname |
359
- (($f.passes // []) | any(. == "evaluator-functional" or . == "evaluator-visual" or . == "evaluator-code-quality")) as $self_passed |
360
- (if ($fid | IN($passed[])) or $self_passed then "P"
361
- elif $prog[$fid] then "I|\($prog[$fid].team)|\($prog[$fid].phase)"
362
- elif ($fid | IN($failed[])) then "F"
363
- elif ($fid | IN($ready[])) then "R"
364
- elif $blocked[$fid] then "BLK|\($blocked[$fid] | length)"
365
- else "U" end) as $st |
366
- "\($st)\t\($fid)\t\($fname)"
367
- ' "$FEATURES" 2>/dev/null | while IFS=$'\t' read -r st fid fname; do
368
- case "$st" in
369
- P) printf " ${GREEN}●${RESET} %-6s %s\n" "$fid" "$fname" ;;
370
- F) printf " ${RED}✗${RESET} %-6s %s\n" "$fid" "$fname" ;;
371
- R) printf " ${YELLOW}○${RESET} %-6s %s\n" "$fid" "$fname" ;;
372
- BLK\|*) deps=$(echo "$st" | cut -d'|' -f2)
373
- printf " ${MAGENTA}◍${RESET} %-6s %-22s ${DIM}blocked (deps:%s)${RESET}\n" "$fid" "$fname" "$deps" ;;
374
- U) printf " ${DIM}◌${RESET} %-6s %s\n" "$fid" "$fname" ;;
375
- I\|*) team=$(echo "$st" | cut -d'|' -f2)
376
- phase=$(echo "$st" | cut -d'|' -f3)
377
- printf " ${CYAN}◐${RESET} %-6s %-22s T%s:%s\n" "$fid" "$fname" "$team" "$phase" ;;
378
- *) printf " ? %-6s %s\n" "$fid" "$fname" ;;
379
- esac
380
- done
381
- }
382
-
383
- render_team_bottleneck() {
384
- if [ ! -f "$QUEUE" ] || [ ! -f "$FEATURES" ]; then return; fi
385
-
386
- local failed_list
387
- failed_list=$(jq -r '.queue.failed[]' "$QUEUE" 2>/dev/null)
388
- if [ -z "$failed_list" ]; then return; fi
389
-
390
- local team_count idle_teams=0 blocked_count in_prog
391
- team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null || echo 0)
392
- blocked_count=$(jq '.queue.blocked | length' "$QUEUE" 2>/dev/null || echo 0)
393
- in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
394
-
395
- for i in $(seq 1 "$team_count"); do
396
- local ts
397
- ts=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
398
- if [ "$ts" = "idle" ]; then idle_teams=$((idle_teams + 1)); fi
399
- done
400
-
401
- while IFS= read -r fid; do
402
- [ -z "$fid" ] && continue
403
- local deps_on_this
404
- deps_on_this=$(jq --arg fid "$fid" '[.queue.blocked | to_entries[] | select(.value | index($fid))] | length' "$QUEUE" 2>/dev/null || echo 0)
405
-
406
- if [ "$deps_on_this" -gt 0 ]; then
407
- echo ""
408
- echo -e " ${RED}${BOLD}⚠ BOTTLENECK${RESET} ${RED}${fid}${RESET} failed → ${YELLOW}${deps_on_this} features blocked${RESET}"
409
- if [ "$idle_teams" -gt 0 ]; then
410
- echo -e " ${BOLD}→ requeue:${RESET} bash scripts/harness-queue-manager.sh requeue ${fid} ."
411
- fi
412
- fi
413
- done <<< "$failed_list"
414
-
415
- if [ "$idle_teams" -eq "$team_count" ] && [ "$in_prog" -eq 0 ] && [ "$blocked_count" -gt 0 ]; then
416
- echo ""
417
- echo -e " ${RED}${BOLD}!! STALLED${RESET} — All teams idle, ${blocked_count} features blocked"
418
- fi
419
- }
420
-
421
- # ══════════════════════════════════════════
422
- # Archive Prompt — 처리 완료된 프롬프트 목록
423
- # ══════════════════════════════════════════
424
- render_archive_prompts() {
425
- if [ ! -f "$PROGRESS_LOG" ]; then return; fi
426
-
427
- # user-prompt 엔트리 중 이후에 result/pass/completed 액션이 있는 것 = 처리 완료
428
- local all_prompts
429
- all_prompts=$(grep '| user-prompt |' "$PROGRESS_LOG" 2>/dev/null)
430
- if [ -z "$all_prompts" ]; then return; fi
431
-
432
- local total_prompts
433
- total_prompts=$(echo "$all_prompts" | wc -l | tr -d ' ')
434
-
435
- # 마지막 프롬프트를 제외한 모든 프롬프트 = archived (처리 완료)
436
- # newest first (tac), 전체 출력 (제한 없음)
437
- local archived
438
- if [ "$total_prompts" -le 1 ]; then return; fi
439
- archived=$(echo "$all_prompts" | head -n $((total_prompts - 1)) | tac)
440
-
441
- echo ""
442
- echo -e "${BOLD}Archive Prompt${RESET} ${DIM}(처리 완료)${RESET}"
443
-
444
- echo "$archived" | while IFS= read -r line; do
445
- local ts detail
446
- ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
447
- detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
448
-
449
- local short_ts
450
- short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}' | tail -1 || echo "$ts")
451
-
452
- if [ ${#detail} -gt 45 ]; then detail="${detail:0:43}.."; fi
453
-
454
- echo -e " ${GREEN}✓${RESET} ${DIM}${short_ts}${RESET} ${detail}"
455
- done
456
- }
457
-
458
- # ══════════════════════════════════════════
459
- # Render dispatch
460
- # ══════════════════════════════════════════
461
- render_dashboard() {
462
- local mode
463
- mode=$(get_mode)
464
-
465
- render_header
466
-
467
- case "$mode" in
468
- team|paused)
469
- render_team_queue_summary
470
- render_team_status
471
- render_team_features
472
- render_team_bottleneck
473
- # Archive Prompt는 별도 패널(harness-prompt-history.sh)에서 렌더링 — Dashboard 모니터링 영역 보호
474
- ;;
475
- *)
476
- render_solo_sprint_overview
477
- render_solo_failure_info
478
-
479
- if [ -f "$FEATURES" ]; then
480
- render_progress "$PROJECT_ROOT" 2>/dev/null
481
- fi
482
-
483
- render_solo_agent_info
484
- # Archive Prompt는 별도 패널(harness-prompt-history.sh)에서 렌더링
485
- ;;
486
- esac
487
- }
488
-
489
- # ══════════════════════════════════════════
490
- # Main — Auto-refresh loop
491
- # ══════════════════════════════════════════
492
- tput civis 2>/dev/null
493
- trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
494
-
495
- clear
496
-
497
- while true; do
498
- _ROWS=$(tput lines 2>/dev/null || echo 30)
499
- _COLS=$(tput cols 2>/dev/null || echo 80)
500
- export _ROWS _COLS
501
- buf=$(render_dashboard 2>&1)
502
- tput cup 0 0 2>/dev/null
503
- # Append ESC[K after every line so each row clears its right-side residue
504
- # from any previous frame (fixes wrapped shell-prompt bleed-through).
505
- printf '%s\n' "$buf" | awk '{printf "%s\033[K\n", $0}'
506
- tput ed 2>/dev/null
507
- printf "${DIM} [r] refresh [q] quit${RESET}\033[K\n"
508
- wait_or_refresh 3 || true
509
- done
@@ -1,72 +0,0 @@
1
- #!/usr/bin/env bash
2
- # harness-goal-init.sh — 새 GOAL 발급 (CEO=Dispatcher 전용)
3
- # 사용법: bash scripts/harness-goal-init.sh "<title>"
4
- #
5
- # 동작:
6
- # 1. .harness/actions/goals.md 가 없으면 template로 생성
7
- # 2. 다음 GOAL ID(G-N) 발급 → progress.json.goals.list 에 등록
8
- # 3. progress.json.goals.active_id 갱신
9
- # 4. CTO 협의 미완료 상태(cto_feasibility=null, owner_confirmed=false)
10
- #
11
- # 후속:
12
- # - CTO는 cto_feasibility 의견을 협의 후 같은 항목에 채워줌
13
- # - Owner 최종 확인 시 dispatcher 가 owner_confirmed=true 로 갱신
14
- set -euo pipefail
15
-
16
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17
- GOAL_FILE="$ROOT/.harness/actions/goals.md"
18
- TEMPLATE="$ROOT/.harness/actions/goals.md.template"
19
- PROGRESS="$ROOT/.harness/progress.json"
20
-
21
- TITLE="${1:-}"
22
- if [ -z "$TITLE" ]; then
23
- echo "usage: $0 \"<title>\"" >&2
24
- exit 1
25
- fi
26
-
27
- mkdir -p "$ROOT/.harness/actions"
28
-
29
- # 1. 파일이 없으면 template 복사 후 ISO 시각 채움
30
- if [ ! -f "$GOAL_FILE" ]; then
31
- if [ ! -f "$TEMPLATE" ]; then
32
- echo "template missing: $TEMPLATE" >&2; exit 1
33
- fi
34
- ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
35
- sed "s|<ISO>|$ISO|g" "$TEMPLATE" > "$GOAL_FILE"
36
- fi
37
-
38
- # 2. 다음 GOAL ID 계산
39
- LAST_N=$(jq -r '.goals.list | map(.id // "G-0") | map(sub("G-"; "")) | map(tonumber? // 0) | max // 0' "$PROGRESS" 2>/dev/null || echo 0)
40
- NEXT_N=$((LAST_N + 1))
41
- GOAL_ID="G-${NEXT_N}"
42
- ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
43
-
44
- # 3. progress.json 에 등록 (partial update)
45
- jq --arg id "$GOAL_ID" --arg title "$TITLE" --arg iso "$ISO" '
46
- .goals.list += [{
47
- "id": $id,
48
- "title": $title,
49
- "status": "draft",
50
- "owner_confirmed": false,
51
- "cto_feasibility": null,
52
- "created_at": $iso
53
- }] |
54
- .goals.active_id = $id |
55
- .updated_at = $iso
56
- ' "$PROGRESS" > "$PROGRESS.tmp" && mv "$PROGRESS.tmp" "$PROGRESS"
57
-
58
- # 4. goals.md 에 GOAL 섹션 append (CEO가 이후 본문 채움)
59
- cat >> "$GOAL_FILE" <<EOF
60
-
61
- ## $GOAL_ID — $TITLE
62
- status: draft
63
- owner_confirmed: false
64
- cto_feasibility: null
65
- created_at: $ISO
66
-
67
- (CEO가 success_metrics·deadline·kpis·runbook 채움)
68
- EOF
69
-
70
- echo "$GOAL_ID 발급 완료: $TITLE"
71
- echo "→ $GOAL_FILE 에서 본문 작성"
72
- echo "→ CTO 협의 후 owner 확정 시 owner_confirmed=true"
@@ -1,37 +0,0 @@
1
- #!/usr/bin/env bash
2
- # harness-goal-show.sh — 현재 active GOAL + 적합도 요약 출력
3
- set -euo pipefail
4
-
5
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
6
- PROGRESS="$ROOT/.harness/progress.json"
7
- GOAL_FILE="$ROOT/.harness/actions/goals.md"
8
-
9
- if [ ! -f "$PROGRESS" ]; then
10
- echo "progress.json 없음. bash init.sh 먼저 실행." >&2; exit 1
11
- fi
12
-
13
- ACTIVE_ID=$(jq -r '.goals.active_id // ""' "$PROGRESS")
14
- ADHERENCE=$(jq -r '.goals.current_adherence // "n/a"' "$PROGRESS")
15
-
16
- if [ -z "$ACTIVE_ID" ]; then
17
- echo "Active GOAL 없음. CEO(Dispatcher)가 첫 발화에서 발급."
18
- echo " bash scripts/harness-goal-init.sh \"<title>\""
19
- exit 0
20
- fi
21
-
22
- echo "Active GOAL: $ACTIVE_ID"
23
- echo "Adherence: $ADHERENCE"
24
- echo ""
25
-
26
- # progress.json 의 GOAL 메타 출력
27
- jq -r --arg id "$ACTIVE_ID" '
28
- .goals.list[] | select(.id == $id) |
29
- "title: \(.title)",
30
- "status: \(.status)",
31
- "owner_confirmed: \(.owner_confirmed)",
32
- "cto_feasibility: \(.cto_feasibility // "pending")",
33
- "created_at: \(.created_at // "?")"
34
- ' "$PROGRESS"
35
-
36
- echo ""
37
- echo "본문: $GOAL_FILE 에서 ## $ACTIVE_ID 섹션 참조"