@walwal-harness/cli 6.0.5 → 6.1.1
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/CHANGELOG.md +65 -0
- package/commands/harness-solo.md +20 -20
- package/commands/harness-team.md +1 -1
- package/gotchas/coo-developer.md +13 -0
- package/gotchas/documentationer.md +13 -0
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +416 -0
- package/scripts/harness-archive.sh +123 -0
- package/scripts/harness-dashboard-up.sh +72 -0
- package/scripts/harness-dashboard.sh +509 -0
- package/scripts/harness-goal-init.sh +72 -0
- package/scripts/harness-goal-show.sh +37 -0
- package/scripts/harness-gotcha-memory.sh +348 -0
- package/scripts/harness-gotcha-register.sh +393 -0
- package/scripts/harness-meeting-doc.sh +153 -0
- package/scripts/harness-monitor.sh +398 -0
- package/scripts/harness-next.sh +577 -0
- package/scripts/harness-progress-set.sh +41 -0
- package/scripts/harness-prompt-history.sh +164 -0
- package/scripts/harness-queue-manager.sh +694 -0
- package/scripts/harness-session-start.sh +140 -0
- package/scripts/harness-statusline.sh +139 -0
- package/scripts/harness-task-session.sh +48 -0
- package/scripts/harness-tmux.sh +372 -0
- package/scripts/harness-user-prompt-submit.sh +123 -0
- package/scripts/init-agents-md.sh +337 -0
- package/scripts/init-ref-docs.sh +262 -0
- package/scripts/lib/harness-audit.sh +113 -0
- package/scripts/lib/harness-guardrail.sh +148 -0
- package/scripts/lib/harness-keywait.sh +57 -0
- package/scripts/lib/harness-render-progress.sh +346 -0
- package/scripts/scan-project.sh +367 -0
- package/skills/brainstorming/SKILL.md +4 -4
- package/skills/conductor/SKILL.md +19 -16
- package/skills/coo-developer/SKILL.md +42 -0
- package/skills/cqo/SKILL.md +7 -0
- package/skills/cto/SKILL.md +7 -0
- package/skills/dispatcher/SKILL.md +17 -28
- package/skills/dispatcher/persona-ceo.md +2 -1
- package/skills/documentationer/SKILL.md +46 -0
- package/skills/evaluator-code-quality/SKILL.md +1 -1
- package/skills/evaluator-functional/SKILL.md +1 -1
- package/skills/evaluator-visual/SKILL.md +1 -1
- package/skills/generator-backend/SKILL.md +1 -1
- package/skills/generator-frontend/SKILL.md +1 -1
- package/skills/meeting-manager/SKILL.md +34 -1
- package/skills/planner/SKILL.md +23 -9
- package/skills/planner/persona-coo-hr.md +17 -0
- package/skills/service-ops/SKILL.md +19 -4
|
@@ -0,0 +1,509 @@
|
|
|
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
|
|
@@ -0,0 +1,72 @@
|
|
|
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"
|
|
@@ -0,0 +1,37 @@
|
|
|
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 섹션 참조"
|