@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.
- package/CHANGELOG.md +42 -0
- package/commands/harness-solo.md +20 -20
- package/commands/harness-team.md +1 -1
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +382 -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 +12 -16
- package/skills/cqo/SKILL.md +7 -0
- package/skills/cto/SKILL.md +7 -0
- package/skills/dispatcher/SKILL.md +17 -28
- 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 +33 -1
- package/skills/planner/SKILL.md +23 -9
- package/skills/service-ops/SKILL.md +19 -4
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-audit.sh — Audit log 헬퍼
|
|
3
|
+
#
|
|
4
|
+
# 라이프사이클: Planner 시작 → Eval 통과 (1 sprint cycle)
|
|
5
|
+
# 새 Planner/Dispatcher 사이클 시작 시 이전 로그는 archive로 이동.
|
|
6
|
+
#
|
|
7
|
+
# 사용법:
|
|
8
|
+
# source scripts/lib/harness-audit.sh
|
|
9
|
+
# audit_log "gen-backend" "develop" "start" "apps/service-user/" "User 서비스 CRUD 구현"
|
|
10
|
+
# audit_log "eval-func" "review" "complete" "POST /api/auth" "FAIL: 409→400 contract 불일치"
|
|
11
|
+
# audit_log "gen-backend" "handoff" "start" "→eval-functional" ""
|
|
12
|
+
|
|
13
|
+
AUDIT_LOG_FILE=""
|
|
14
|
+
|
|
15
|
+
# ─────────────────────────────────────────
|
|
16
|
+
# init_audit — audit.log 경로 설정 + 헤더 생성
|
|
17
|
+
# Args: $1 = project root
|
|
18
|
+
# ─────────────────────────────────────────
|
|
19
|
+
init_audit() {
|
|
20
|
+
local project_root="${1:-.}"
|
|
21
|
+
AUDIT_LOG_FILE="$project_root/.harness/actions/audit.log"
|
|
22
|
+
|
|
23
|
+
# 디렉토리 보장
|
|
24
|
+
mkdir -p "$(dirname "$AUDIT_LOG_FILE")"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# ─────────────────────────────────────────
|
|
28
|
+
# reset_audit — 새 사이클 시작 시 로그 초기화
|
|
29
|
+
# 이전 로그가 있으면 archive로 이동
|
|
30
|
+
# Args: $1 = project root, $2 = sprint number (optional)
|
|
31
|
+
# ─────────────────────────────────────────
|
|
32
|
+
reset_audit() {
|
|
33
|
+
local project_root="${1:-.}"
|
|
34
|
+
local sprint_num="${2:-0}"
|
|
35
|
+
init_audit "$project_root"
|
|
36
|
+
|
|
37
|
+
# 기존 로그가 있으면 archive로 이동
|
|
38
|
+
if [ -f "$AUDIT_LOG_FILE" ] && [ -s "$AUDIT_LOG_FILE" ]; then
|
|
39
|
+
local archive_dir="$project_root/.harness/archive"
|
|
40
|
+
mkdir -p "$archive_dir"
|
|
41
|
+
local ts
|
|
42
|
+
ts=$(date +%Y%m%d_%H%M%S)
|
|
43
|
+
mv "$AUDIT_LOG_FILE" "$archive_dir/audit_s${sprint_num}_${ts}.log"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# 새 로그 헤더
|
|
47
|
+
cat > "$AUDIT_LOG_FILE" <<HEADER
|
|
48
|
+
# Harness Audit Log
|
|
49
|
+
# Cycle started: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
50
|
+
# Format: TIMESTAMP | AGENT | ACTION | STATUS | TARGET | DETAIL
|
|
51
|
+
#─────────────────────────────────────────────────────────────────────────
|
|
52
|
+
HEADER
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ─────────────────────────────────────────
|
|
56
|
+
# audit_log — 로그 엔트리 추가
|
|
57
|
+
#
|
|
58
|
+
# Args:
|
|
59
|
+
# $1 = agent (planner, gen-backend, gen-frontend, eval-func, eval-visual)
|
|
60
|
+
# $2 = action (develop, review, handoff, plan, gate)
|
|
61
|
+
# $3 = status (start, complete, fail, skip)
|
|
62
|
+
# $4 = target (파일 경로, 엔드포인트, 에이전트명 등)
|
|
63
|
+
# $5 = detail (설명, 결과 등)
|
|
64
|
+
# ─────────────────────────────────────────
|
|
65
|
+
audit_log() {
|
|
66
|
+
if [ -z "$AUDIT_LOG_FILE" ] || [ ! -f "$AUDIT_LOG_FILE" ]; then
|
|
67
|
+
return 0 # audit 미초기화 시 silent pass
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
local agent="${1:-unknown}"
|
|
71
|
+
local action="${2:-unknown}"
|
|
72
|
+
local log_status="${3:-unknown}"
|
|
73
|
+
local target="${4:-}"
|
|
74
|
+
local detail="${5:-}"
|
|
75
|
+
local ts
|
|
76
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
77
|
+
|
|
78
|
+
# 고정폭 포맷
|
|
79
|
+
printf "%-20s | %-14s | %-8s | %-8s | %-30s | %s\n" \
|
|
80
|
+
"$ts" "$agent" "$action" "$log_status" "$target" "$detail" >> "$AUDIT_LOG_FILE"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# ─────────────────────────────────────────
|
|
84
|
+
# audit_handoff — 핸즈오프 기록 (편의 함수)
|
|
85
|
+
# Args: $1=from_agent, $2=to_agent, $3=status(start|complete)
|
|
86
|
+
# ─────────────────────────────────────────
|
|
87
|
+
audit_handoff() {
|
|
88
|
+
audit_log "$1" "handoff" "$3" "→${2}" ""
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# ─────────────────────────────────────────
|
|
92
|
+
# audit_gate — 게이트 체크 기록 (편의 함수)
|
|
93
|
+
# Args: $1=gate_name, $2=status(pass|fail), $3=detail
|
|
94
|
+
# ─────────────────────────────────────────
|
|
95
|
+
audit_gate() {
|
|
96
|
+
audit_log "system" "gate" "$2" "$1" "$3"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# ─────────────────────────────────────────
|
|
100
|
+
# render_audit — audit.log를 읽기 좋게 출력
|
|
101
|
+
# Args: $1 = project root (optional)
|
|
102
|
+
# ─────────────────────────────────────────
|
|
103
|
+
render_audit() {
|
|
104
|
+
local project_root="${1:-.}"
|
|
105
|
+
local log_file="$project_root/.harness/actions/audit.log"
|
|
106
|
+
|
|
107
|
+
if [ ! -f "$log_file" ]; then
|
|
108
|
+
echo " (audit log not yet created)"
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
cat "$log_file"
|
|
113
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-guardrail.sh — 런타임 파일 소유권 검증
|
|
3
|
+
# 에이전트 전환 시 이전 에이전트가 권한 밖 파일을 수정했는지 git diff로 검증.
|
|
4
|
+
#
|
|
5
|
+
# 사용법: source lib/harness-guardrail.sh && verify_file_ownership "$PROJECT_ROOT"
|
|
6
|
+
# 반환값: 0=통과, 1=위반 발견 (위반 목록을 stdout에 출력)
|
|
7
|
+
|
|
8
|
+
# ─────────────────────────────────────────
|
|
9
|
+
# Agent → 허용 경로 매핑
|
|
10
|
+
# ─────────────────────────────────────────
|
|
11
|
+
get_allowed_paths() {
|
|
12
|
+
local agent="$1"
|
|
13
|
+
case "$agent" in
|
|
14
|
+
planner)
|
|
15
|
+
echo "AGENTS.md"
|
|
16
|
+
echo ".harness/actions/"
|
|
17
|
+
echo ".harness/progress.json"
|
|
18
|
+
echo ".harness/progress.log"
|
|
19
|
+
;;
|
|
20
|
+
generator-backend)
|
|
21
|
+
echo "apps/gateway/"
|
|
22
|
+
echo "apps/service-"
|
|
23
|
+
echo "libs/"
|
|
24
|
+
echo ".harness/actions/sprint-contract.md"
|
|
25
|
+
echo ".harness/actions/feature-list.json"
|
|
26
|
+
echo ".harness/progress.json"
|
|
27
|
+
echo "package.json"
|
|
28
|
+
echo "nest-cli.json"
|
|
29
|
+
echo "tsconfig"
|
|
30
|
+
echo "docker-compose"
|
|
31
|
+
;;
|
|
32
|
+
generator-frontend)
|
|
33
|
+
echo "apps/web/"
|
|
34
|
+
echo "apps/flutter/"
|
|
35
|
+
echo "lib/"
|
|
36
|
+
echo ".harness/actions/sprint-contract.md"
|
|
37
|
+
echo ".harness/actions/feature-list.json"
|
|
38
|
+
echo ".harness/progress.json"
|
|
39
|
+
echo "package.json"
|
|
40
|
+
echo "tsconfig"
|
|
41
|
+
echo "pubspec"
|
|
42
|
+
;;
|
|
43
|
+
evaluator-code-quality)
|
|
44
|
+
echo ".harness/actions/evaluation-code-quality.md"
|
|
45
|
+
echo ".harness/progress.json"
|
|
46
|
+
;;
|
|
47
|
+
evaluator-functional)
|
|
48
|
+
echo ".harness/actions/evaluation-functional.md"
|
|
49
|
+
echo ".harness/progress.json"
|
|
50
|
+
echo "tests/"
|
|
51
|
+
echo "test/"
|
|
52
|
+
echo "e2e/"
|
|
53
|
+
echo "playwright"
|
|
54
|
+
;;
|
|
55
|
+
evaluator-visual)
|
|
56
|
+
echo ".harness/actions/evaluation-visual.md"
|
|
57
|
+
echo ".harness/progress.json"
|
|
58
|
+
;;
|
|
59
|
+
dispatcher)
|
|
60
|
+
echo ".harness/"
|
|
61
|
+
;;
|
|
62
|
+
*)
|
|
63
|
+
echo "**" # unknown agent — allow all (safe fallback)
|
|
64
|
+
;;
|
|
65
|
+
esac
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# ─────────────────────────────────────────
|
|
69
|
+
# verify_file_ownership — git diff 기반 검증
|
|
70
|
+
#
|
|
71
|
+
# Args:
|
|
72
|
+
# $1 — project root
|
|
73
|
+
#
|
|
74
|
+
# Reads:
|
|
75
|
+
# .harness/progress.json (current_agent)
|
|
76
|
+
#
|
|
77
|
+
# Returns: 0=pass, 1=violation found
|
|
78
|
+
# ─────────────────────────────────────────
|
|
79
|
+
verify_file_ownership() {
|
|
80
|
+
local PROJECT_ROOT="$1"
|
|
81
|
+
local PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
82
|
+
|
|
83
|
+
if [ ! -f "$PROGRESS" ]; then return 0; fi
|
|
84
|
+
|
|
85
|
+
local agent
|
|
86
|
+
agent=$(jq -r '.current_agent // "null"' "$PROGRESS" 2>/dev/null)
|
|
87
|
+
if [ "$agent" = "null" ] || [ -z "$agent" ]; then return 0; fi
|
|
88
|
+
|
|
89
|
+
# git이 없으면 skip
|
|
90
|
+
if ! command -v git &>/dev/null; then return 0; fi
|
|
91
|
+
|
|
92
|
+
# 마지막 커밋 이후 변경된 파일 목록
|
|
93
|
+
local -a changed_files
|
|
94
|
+
mapfile -t changed_files < <(cd "$PROJECT_ROOT" && git diff --name-only HEAD 2>/dev/null)
|
|
95
|
+
|
|
96
|
+
# staged 파일도 포함
|
|
97
|
+
local -a staged_files
|
|
98
|
+
mapfile -t staged_files < <(cd "$PROJECT_ROOT" && git diff --name-only --cached 2>/dev/null)
|
|
99
|
+
|
|
100
|
+
# 합치기 (중복 제거)
|
|
101
|
+
local -a all_files
|
|
102
|
+
mapfile -t all_files < <(printf '%s\n' "${changed_files[@]}" "${staged_files[@]}" | sort -u | grep -v '^$')
|
|
103
|
+
|
|
104
|
+
if [ ${#all_files[@]} -eq 0 ]; then return 0; fi
|
|
105
|
+
|
|
106
|
+
# 허용 경로 목록
|
|
107
|
+
local -a allowed
|
|
108
|
+
mapfile -t allowed < <(get_allowed_paths "$agent")
|
|
109
|
+
|
|
110
|
+
# 위반 검사
|
|
111
|
+
local -a violations=()
|
|
112
|
+
for file in "${all_files[@]}"; do
|
|
113
|
+
local is_allowed=false
|
|
114
|
+
for pattern in "${allowed[@]}"; do
|
|
115
|
+
if [[ "$file" == $pattern* ]]; then
|
|
116
|
+
is_allowed=true
|
|
117
|
+
break
|
|
118
|
+
fi
|
|
119
|
+
done
|
|
120
|
+
if [ "$is_allowed" = false ]; then
|
|
121
|
+
violations+=("$file")
|
|
122
|
+
fi
|
|
123
|
+
done
|
|
124
|
+
|
|
125
|
+
if [ ${#violations[@]} -eq 0 ]; then
|
|
126
|
+
return 0
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# 위반 보고
|
|
130
|
+
echo ""
|
|
131
|
+
echo " ── Guardrail Violation ────────────────"
|
|
132
|
+
echo " Agent '$agent' modified files outside its allowed paths:"
|
|
133
|
+
echo ""
|
|
134
|
+
for v in "${violations[@]}"; do
|
|
135
|
+
echo " ✗ $v"
|
|
136
|
+
done
|
|
137
|
+
echo ""
|
|
138
|
+
echo " Allowed paths for $agent:"
|
|
139
|
+
for p in "${allowed[@]}"; do
|
|
140
|
+
echo " ✓ $p*"
|
|
141
|
+
done
|
|
142
|
+
echo ""
|
|
143
|
+
echo " Action: Review these changes before proceeding."
|
|
144
|
+
echo " ─────────────────────────────────────────"
|
|
145
|
+
echo ""
|
|
146
|
+
|
|
147
|
+
return 1
|
|
148
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-keywait.sh — 패널 refresh/quit 키 대기 헬퍼
|
|
3
|
+
#
|
|
4
|
+
# 사용법:
|
|
5
|
+
# source scripts/lib/harness-keywait.sh
|
|
6
|
+
# while true; do
|
|
7
|
+
# render_panel
|
|
8
|
+
# echo " [r] refresh [q] quit"
|
|
9
|
+
# if wait_or_refresh 3; then
|
|
10
|
+
# # 타임아웃 → 일반 주기 리프레시
|
|
11
|
+
# :
|
|
12
|
+
# else
|
|
13
|
+
# # 'r' 키 즉시 리프레시 (반환값 != 0)
|
|
14
|
+
# :
|
|
15
|
+
# fi
|
|
16
|
+
# done
|
|
17
|
+
#
|
|
18
|
+
# 키 처리:
|
|
19
|
+
# 'r' / 'R' / Enter → 즉시 복귀 (return 1 — 강제 리프레시 신호)
|
|
20
|
+
# 'q' / 'Q' → exit 0 (패널 종료)
|
|
21
|
+
# 그 외 / 타임아웃 → return 0 (정상 주기 리프레시)
|
|
22
|
+
#
|
|
23
|
+
# TTY 가 아니면 plain sleep 으로 동작 (Claude Code Bash 도구 등에서도 안전).
|
|
24
|
+
|
|
25
|
+
wait_or_refresh() {
|
|
26
|
+
local secs="${1:-3}"
|
|
27
|
+
|
|
28
|
+
# Non-TTY fallback
|
|
29
|
+
if ! [ -t 0 ]; then
|
|
30
|
+
sleep "$secs"
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
local key=""
|
|
35
|
+
# -t: timeout, -n 1: 1글자, -s: 에코 안함
|
|
36
|
+
# read 는 Enter 에서도 타임아웃 전에 즉시 복귀 (빈 키)
|
|
37
|
+
if IFS= read -rsn 1 -t "$secs" key 2>/dev/null; then
|
|
38
|
+
case "$key" in
|
|
39
|
+
q|Q)
|
|
40
|
+
# 커서 복원 후 종료
|
|
41
|
+
tput cnorm 2>/dev/null || true
|
|
42
|
+
clear
|
|
43
|
+
exit 0
|
|
44
|
+
;;
|
|
45
|
+
r|R|"")
|
|
46
|
+
# 강제 리프레시 시그널
|
|
47
|
+
return 1
|
|
48
|
+
;;
|
|
49
|
+
*)
|
|
50
|
+
# 기타 키는 무시하고 일반 복귀
|
|
51
|
+
return 0
|
|
52
|
+
;;
|
|
53
|
+
esac
|
|
54
|
+
fi
|
|
55
|
+
# 타임아웃
|
|
56
|
+
return 0
|
|
57
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-render-progress.sh — Feature-level 프로그래스 렌더링 (공통 함수)
|
|
3
|
+
# source로 포함하여 사용: source "$(dirname "$0")/lib/harness-render-progress.sh"
|
|
4
|
+
|
|
5
|
+
# ─────────────────────────────────────────
|
|
6
|
+
# Dependencies: jq
|
|
7
|
+
# ─────────────────────────────────────────
|
|
8
|
+
check_jq() {
|
|
9
|
+
if ! command -v jq &>/dev/null; then
|
|
10
|
+
echo "[harness] ERROR: jq is required. Install with: brew install jq" >&2
|
|
11
|
+
return 1
|
|
12
|
+
fi
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# ─────────────────────────────────────────
|
|
16
|
+
# resolve_harness_root — .harness/ 디렉토리 찾기
|
|
17
|
+
# ─────────────────────────────────────────
|
|
18
|
+
resolve_harness_root() {
|
|
19
|
+
local dir="${1:-.}"
|
|
20
|
+
dir="$(cd "$dir" && pwd)"
|
|
21
|
+
while [ "$dir" != "/" ]; do
|
|
22
|
+
if [ -d "$dir/.harness" ]; then
|
|
23
|
+
echo "$dir"
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
dir="$(dirname "$dir")"
|
|
27
|
+
done
|
|
28
|
+
return 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# ─────────────────────────────────────────
|
|
32
|
+
# render_progress — Feature-level 프로그래스 출력
|
|
33
|
+
#
|
|
34
|
+
# Args:
|
|
35
|
+
# $1 — project root (optional, defaults to auto-detect)
|
|
36
|
+
#
|
|
37
|
+
# Reads:
|
|
38
|
+
# .harness/progress.json
|
|
39
|
+
# .harness/config.json
|
|
40
|
+
# .harness/actions/feature-list.json (if exists)
|
|
41
|
+
# ─────────────────────────────────────────
|
|
42
|
+
render_progress() {
|
|
43
|
+
check_jq || return 1
|
|
44
|
+
|
|
45
|
+
local PROJECT_ROOT
|
|
46
|
+
PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || {
|
|
47
|
+
echo "[harness] .harness/ directory not found" >&2
|
|
48
|
+
return 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
local PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
52
|
+
local CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
53
|
+
local FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
54
|
+
|
|
55
|
+
if [ ! -f "$PROGRESS" ]; then
|
|
56
|
+
return 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Read progress.json
|
|
60
|
+
local pipeline sprint_num sprint_status retry_count
|
|
61
|
+
local current_agent agent_status next_agent
|
|
62
|
+
local failure_agent failure_location failure_message
|
|
63
|
+
|
|
64
|
+
pipeline=$(jq -r '.pipeline // "unknown"' "$PROGRESS")
|
|
65
|
+
sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS")
|
|
66
|
+
sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS")
|
|
67
|
+
retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
|
|
68
|
+
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS")
|
|
69
|
+
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
|
|
70
|
+
next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS")
|
|
71
|
+
failure_agent=$(jq -r '.failure.agent // empty' "$PROGRESS")
|
|
72
|
+
failure_location=$(jq -r '.failure.location // empty' "$PROGRESS")
|
|
73
|
+
failure_message=$(jq -r '.failure.message // empty' "$PROGRESS")
|
|
74
|
+
|
|
75
|
+
# Read max retries from config
|
|
76
|
+
local max_retries=10
|
|
77
|
+
if [ -f "$CONFIG" ]; then
|
|
78
|
+
max_retries=$(jq -r '.flow.max_retries_per_sprint // 10' "$CONFIG")
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# Pipeline agents for progress bar
|
|
82
|
+
local -a pipeline_agents
|
|
83
|
+
if [ -f "$CONFIG" ] && [ "$pipeline" != "null" ] && [ "$pipeline" != "unknown" ]; then
|
|
84
|
+
mapfile -t pipeline_agents < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null | sed 's/:.*//')
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# ── Header ──
|
|
88
|
+
local header="Sprint ${sprint_num} / ${pipeline}"
|
|
89
|
+
local pad_len=$(( 40 - ${#header} ))
|
|
90
|
+
local padding=""
|
|
91
|
+
for ((i=0; i<pad_len; i++)); do padding+="═"; done
|
|
92
|
+
echo ""
|
|
93
|
+
echo " ═══ ${header} ${padding}"
|
|
94
|
+
echo ""
|
|
95
|
+
|
|
96
|
+
# ── Feature Progress ──
|
|
97
|
+
if [ -f "$FEATURES" ]; then
|
|
98
|
+
local total_features completed_features=0 in_progress_features=0
|
|
99
|
+
total_features=$(jq '.features | length' "$FEATURES" 2>/dev/null || echo 0)
|
|
100
|
+
|
|
101
|
+
if [ "$total_features" -gt 0 ]; then
|
|
102
|
+
# Determine columns based on pipeline
|
|
103
|
+
local show_be=true show_fe=true
|
|
104
|
+
case "$pipeline" in
|
|
105
|
+
BE-ONLY) show_fe=false ;;
|
|
106
|
+
FE-ONLY) show_be=false ;;
|
|
107
|
+
esac
|
|
108
|
+
|
|
109
|
+
# Build feature lines
|
|
110
|
+
local i=0
|
|
111
|
+
while [ $i -lt "$total_features" ]; do
|
|
112
|
+
local fid fname passes be_pass fe_pass eval_func_pass eval_visual_pass
|
|
113
|
+
fid=$(jq -r ".features[$i].id // \"F-$((i+1))\"" "$FEATURES")
|
|
114
|
+
fname=$(jq -r ".features[$i].name // .features[$i].title // .features[$i].description // \"Feature $((i+1))\"" "$FEATURES")
|
|
115
|
+
passes=$(jq -r ".features[$i].passes // [] | .[]" "$FEATURES" 2>/dev/null)
|
|
116
|
+
|
|
117
|
+
# Truncate name to 20 chars
|
|
118
|
+
if [ ${#fname} -gt 20 ]; then
|
|
119
|
+
fname="${fname:0:18}.."
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Check passes
|
|
123
|
+
be_pass="○"; fe_pass="○"; eval_cq_pass="○"; eval_func_pass="○"; eval_visual_pass="○"
|
|
124
|
+
local eval_done=false
|
|
125
|
+
|
|
126
|
+
while IFS= read -r p; do
|
|
127
|
+
case "$p" in
|
|
128
|
+
generator-backend) be_pass="✓" ;;
|
|
129
|
+
generator-frontend) fe_pass="✓" ;;
|
|
130
|
+
evaluator-code-quality) eval_cq_pass="✓" ;;
|
|
131
|
+
evaluator-functional) eval_func_pass="✓" ;;
|
|
132
|
+
evaluator-visual) eval_visual_pass="✓" ;;
|
|
133
|
+
esac
|
|
134
|
+
done <<< "$passes"
|
|
135
|
+
|
|
136
|
+
# Eval column: chain 내 존재하는 평가자 모두 PASS 여야 ✓
|
|
137
|
+
local eval_pass="○"
|
|
138
|
+
local eval_passed_count=0
|
|
139
|
+
local eval_total=3
|
|
140
|
+
[ "$eval_cq_pass" = "✓" ] && eval_passed_count=$((eval_passed_count + 1))
|
|
141
|
+
[ "$eval_func_pass" = "✓" ] && eval_passed_count=$((eval_passed_count + 1))
|
|
142
|
+
[ "$eval_visual_pass" = "✓" ] && eval_passed_count=$((eval_passed_count + 1))
|
|
143
|
+
if [ "$eval_passed_count" -eq "$eval_total" ]; then
|
|
144
|
+
eval_pass="✓"
|
|
145
|
+
elif [ "$eval_passed_count" -gt 0 ]; then
|
|
146
|
+
eval_pass="◐"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# Feature status icon
|
|
150
|
+
local icon="○"
|
|
151
|
+
local is_complete=false
|
|
152
|
+
if [ "$show_be" = true ] && [ "$show_fe" = true ]; then
|
|
153
|
+
# FULLSTACK: all must pass
|
|
154
|
+
if [ "$be_pass" = "✓" ] && [ "$fe_pass" = "✓" ] && [ "$eval_pass" = "✓" ]; then
|
|
155
|
+
icon="✓"; is_complete=true
|
|
156
|
+
elif [ "$be_pass" = "✓" ] || [ "$fe_pass" = "✓" ]; then
|
|
157
|
+
icon="◐"
|
|
158
|
+
fi
|
|
159
|
+
elif [ "$show_be" = true ]; then
|
|
160
|
+
# BE-ONLY
|
|
161
|
+
if [ "$be_pass" = "✓" ] && [ "$eval_pass" = "✓" ]; then
|
|
162
|
+
icon="✓"; is_complete=true
|
|
163
|
+
elif [ "$be_pass" = "✓" ]; then
|
|
164
|
+
icon="◐"
|
|
165
|
+
fi
|
|
166
|
+
else
|
|
167
|
+
# FE-ONLY
|
|
168
|
+
if [ "$fe_pass" = "✓" ] && [ "$eval_pass" = "✓" ]; then
|
|
169
|
+
icon="✓"; is_complete=true
|
|
170
|
+
elif [ "$fe_pass" = "✓" ]; then
|
|
171
|
+
icon="◐"
|
|
172
|
+
fi
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if [ "$is_complete" = true ]; then
|
|
176
|
+
completed_features=$((completed_features + 1))
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Build columns
|
|
180
|
+
local cols=""
|
|
181
|
+
if [ "$show_be" = true ]; then cols+="BE${be_pass} "; fi
|
|
182
|
+
if [ "$show_fe" = true ]; then cols+="FE${fe_pass} "; fi
|
|
183
|
+
cols+="Eval${eval_pass}"
|
|
184
|
+
|
|
185
|
+
printf " %s %-5s %-22s %s\n" "$icon" "$fid" "$fname" "$cols"
|
|
186
|
+
|
|
187
|
+
i=$((i + 1))
|
|
188
|
+
done
|
|
189
|
+
|
|
190
|
+
echo ""
|
|
191
|
+
|
|
192
|
+
# ── Progress Bar ──
|
|
193
|
+
local steps_per_feature=0
|
|
194
|
+
if [ "$show_be" = true ]; then steps_per_feature=$((steps_per_feature + 1)); fi
|
|
195
|
+
if [ "$show_fe" = true ]; then steps_per_feature=$((steps_per_feature + 1)); fi
|
|
196
|
+
steps_per_feature=$((steps_per_feature + 1)) # eval
|
|
197
|
+
|
|
198
|
+
local total_steps=$((total_features * steps_per_feature))
|
|
199
|
+
local done_steps=0
|
|
200
|
+
|
|
201
|
+
# Count individual passes across all features
|
|
202
|
+
i=0
|
|
203
|
+
while [ $i -lt "$total_features" ]; do
|
|
204
|
+
passes=$(jq -r ".features[$i].passes // [] | .[]" "$FEATURES" 2>/dev/null)
|
|
205
|
+
while IFS= read -r p; do
|
|
206
|
+
case "$p" in
|
|
207
|
+
generator-backend)
|
|
208
|
+
if [ "$show_be" = true ]; then done_steps=$((done_steps + 1)); fi
|
|
209
|
+
;;
|
|
210
|
+
generator-frontend)
|
|
211
|
+
if [ "$show_fe" = true ]; then done_steps=$((done_steps + 1)); fi
|
|
212
|
+
;;
|
|
213
|
+
evaluator-code-quality|evaluator-functional|evaluator-visual)
|
|
214
|
+
# Chain 내 평가자. 아래에서 통합 집계.
|
|
215
|
+
;;
|
|
216
|
+
esac
|
|
217
|
+
done <<< "$passes"
|
|
218
|
+
|
|
219
|
+
# Check eval completion — 체인상의 3개 평가자 모두 통과 시에만 1 step 인정
|
|
220
|
+
local ecq ef ev
|
|
221
|
+
ecq=$(jq -r ".features[$i].passes // [] | map(select(. == \"evaluator-code-quality\")) | length" "$FEATURES" 2>/dev/null)
|
|
222
|
+
ef=$(jq -r ".features[$i].passes // [] | map(select(. == \"evaluator-functional\")) | length" "$FEATURES" 2>/dev/null)
|
|
223
|
+
ev=$(jq -r ".features[$i].passes // [] | map(select(. == \"evaluator-visual\")) | length" "$FEATURES" 2>/dev/null)
|
|
224
|
+
if [ "${ecq:-0}" -gt 0 ] && [ "${ef:-0}" -gt 0 ] && [ "${ev:-0}" -gt 0 ]; then
|
|
225
|
+
done_steps=$((done_steps + 1))
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
i=$((i + 1))
|
|
229
|
+
done
|
|
230
|
+
|
|
231
|
+
local pct=0
|
|
232
|
+
if [ "$total_steps" -gt 0 ]; then
|
|
233
|
+
pct=$(( done_steps * 100 / total_steps ))
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
local bar_width=16
|
|
237
|
+
local filled=$(( pct * bar_width / 100 ))
|
|
238
|
+
local empty=$(( bar_width - filled ))
|
|
239
|
+
local bar=""
|
|
240
|
+
for ((j=0; j<filled; j++)); do bar+="█"; done
|
|
241
|
+
for ((j=0; j<empty; j++)); do bar+="░"; done
|
|
242
|
+
|
|
243
|
+
echo " Features: ${completed_features}/${total_features} completed"
|
|
244
|
+
echo " Pipeline: ${bar} ${pct}%"
|
|
245
|
+
fi
|
|
246
|
+
else
|
|
247
|
+
echo " (feature-list.json not yet created — run Planner first)"
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
echo ""
|
|
251
|
+
|
|
252
|
+
# ── Current Agent Status ──
|
|
253
|
+
if [ "$current_agent" != "none" ] && [ "$current_agent" != "null" ]; then
|
|
254
|
+
echo " Current: ${current_agent} [${agent_status}] (retry ${retry_count}/${max_retries})"
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
# ── Failure Info ──
|
|
258
|
+
if [ -n "$failure_agent" ] && [ "$failure_agent" != "null" ]; then
|
|
259
|
+
echo ""
|
|
260
|
+
echo " ⚠ FAIL: ${failure_agent} → ${failure_location}"
|
|
261
|
+
if [ -n "$failure_message" ] && [ "$failure_message" != "null" ]; then
|
|
262
|
+
echo " ${failure_message}"
|
|
263
|
+
fi
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
# ── Blocked ──
|
|
267
|
+
if [ "$agent_status" = "blocked" ]; then
|
|
268
|
+
echo ""
|
|
269
|
+
echo " ✖ BLOCKED: retry limit (${max_retries}) reached — user intervention required"
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
# ── Next Action ──
|
|
273
|
+
if [ "$next_agent" != "none" ] && [ "$next_agent" != "null" ] && [ "$agent_status" != "blocked" ]; then
|
|
274
|
+
# Read model & thinking mode from config
|
|
275
|
+
local next_model="opus"
|
|
276
|
+
local next_thinking="null"
|
|
277
|
+
if [ -f "$CONFIG" ]; then
|
|
278
|
+
next_model=$(jq -r ".agents[\"${next_agent}\"].model // \"opus\"" "$CONFIG" 2>/dev/null || echo "opus")
|
|
279
|
+
next_thinking=$(jq -r ".agents[\"${next_agent}\"].thinking_mode // \"null\"" "$CONFIG" 2>/dev/null || echo "null")
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
local mode_str=""
|
|
283
|
+
if [ "$next_thinking" != "null" ]; then
|
|
284
|
+
mode_str=" [/${next_thinking}]"
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
echo ""
|
|
288
|
+
echo " Next → /harness-${next_agent} (${next_model}${mode_str})"
|
|
289
|
+
echo " Tip → 새 세션을 시작하면 자동으로 안내됩니다"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
echo ""
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# ─────────────────────────────────────────
|
|
296
|
+
# render_agent_bar — 에이전트 시퀀스 바 (compact)
|
|
297
|
+
# ─────────────────────────────────────────
|
|
298
|
+
render_agent_bar() {
|
|
299
|
+
local PROGRESS="${1:-.}/.harness/progress.json"
|
|
300
|
+
local CONFIG="${1:-.}/.harness/config.json"
|
|
301
|
+
local PIPELINE_JSON="${1:-.}/.harness/actions/pipeline.json"
|
|
302
|
+
|
|
303
|
+
if [ ! -f "$PROGRESS" ] || [ ! -f "$CONFIG" ]; then return 1; fi
|
|
304
|
+
|
|
305
|
+
local pipeline current_agent fe_stack fe_target
|
|
306
|
+
pipeline=$(jq -r '.pipeline // "unknown"' "$PROGRESS")
|
|
307
|
+
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS")
|
|
308
|
+
fe_stack="react"
|
|
309
|
+
fe_target="web"
|
|
310
|
+
if [ -f "$PIPELINE_JSON" ]; then
|
|
311
|
+
fe_stack=$(jq -r '.fe_stack // "react"' "$PIPELINE_JSON" 2>/dev/null || echo "react")
|
|
312
|
+
fe_target=$(jq -r '.fe_target // empty' "$PIPELINE_JSON" 2>/dev/null || true)
|
|
313
|
+
if [ -z "$fe_target" ]; then
|
|
314
|
+
fe_target="web" # v5.6.5+: 치환 로직 제거. 기본 web.
|
|
315
|
+
fi
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
local completed_agents
|
|
319
|
+
completed_agents=$(jq -r '.completed_agents // [] | .[]' "$PROGRESS")
|
|
320
|
+
|
|
321
|
+
if [ "$pipeline" = "null" ] || [ "$pipeline" = "unknown" ]; then return 1; fi
|
|
322
|
+
|
|
323
|
+
local agents_line=" Agents: "
|
|
324
|
+
local first=true
|
|
325
|
+
|
|
326
|
+
while IFS= read -r agent; do
|
|
327
|
+
agent=$(echo "$agent" | sed 's/:.*//') # strip mode suffix like :light, :api-only
|
|
328
|
+
# v5.6.5+: fe_stack 에이전트 치환 제거. 스택별 동작은 ref-docs 로 조절.
|
|
329
|
+
|
|
330
|
+
if [ "$first" = true ]; then
|
|
331
|
+
first=false
|
|
332
|
+
else
|
|
333
|
+
agents_line+=" → "
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
if echo "$completed_agents" | grep -q "^${agent}$"; then
|
|
337
|
+
agents_line+="${agent}✓"
|
|
338
|
+
elif [ "$agent" = "$current_agent" ]; then
|
|
339
|
+
agents_line+="[${agent}]"
|
|
340
|
+
else
|
|
341
|
+
agents_line+="${agent}"
|
|
342
|
+
fi
|
|
343
|
+
done < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null)
|
|
344
|
+
|
|
345
|
+
echo "$agents_line"
|
|
346
|
+
}
|