@walwal-harness/cli 3.4.0 → 3.6.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.
@@ -143,8 +143,39 @@ pending → draft → reviewed → approved
143
143
  | 파일 | 역할 |
144
144
  |------|------|
145
145
  | `.harness/progress.json` | 기계 판독 상태 (현재 에이전트, 파이프라인, 실패 정보) |
146
- | `.harness/progress.log` | 사람 판독 히스토리 (append-only) |
147
- | `.harness/handoff.json` | 세션 전환 문서 (prompt, model, thinking_mode, artifacts, regression) |
146
+ | `.harness/progress.log` | 사람 판독 히스토리 (append-only, 전체) |
147
+ | `.harness/handoff.json` | 세션 전환 문서 (prompt, model, thinking_mode, artifacts) |
148
+ | `.harness/actions/audit.log` | Sprint cycle 단위 실행 추적 (Planner→Eval 통과) |
149
+
150
+ ### Audit Log
151
+
152
+ 1 sprint cycle(Planner/Dispatcher 시작 → Eval 통과) 단위의 상세 실행 추적입니다.
153
+
154
+ ```
155
+ # 예시:
156
+ TIMESTAMP | AGENT | ACTION | STATUS | TARGET | DETAIL
157
+ 2026-04-09T14:30:00Z | planner | plan | start | plan.md | Sprint 1 설계 시작
158
+ 2026-04-09T14:35:00Z | planner | plan | complete | api-contract.json | 3 endpoints, 2 services
159
+ 2026-04-09T14:35:01Z | planner | handoff | complete | →gen-backend |
160
+ 2026-04-09T14:36:00Z | system | gate | pass | pre-eval | gen-backend
161
+ 2026-04-09T14:40:00Z | gen-backend | develop | start | apps/service-user/ | User CRUD 구현
162
+ 2026-04-09T14:50:00Z | gen-backend | develop | complete | apps/service-user/ | 4 endpoints
163
+ 2026-04-09T14:50:01Z | gen-backend | handoff | complete | →eval-functional |
164
+ 2026-04-09T14:55:00Z | eval-func | review | start | POST /api/auth/register | AC-001~003 검증
165
+ 2026-04-09T14:58:00Z | eval-func | review | fail | POST /api/auth/register | 409→400 contract 불일치
166
+ 2026-04-09T14:58:01Z | eval-func | handoff | complete | →gen-backend | Re-Generate
167
+ ```
168
+
169
+ **라이프사이클**: 새 Planner/Dispatcher 사이클 시작 시 이전 로그는 archive로 이동, 새 로그 시작.
170
+
171
+ **에이전트 기록 의무**: 모든 에이전트는 세션 중 주요 작업의 시작/완료를 audit에 기록해야 합니다.
172
+
173
+ ```bash
174
+ # 에이전트 스킬에서 호출:
175
+ source scripts/lib/harness-audit.sh && init_audit .
176
+ audit_log "gen-backend" "develop" "start" "apps/service-user/" "User CRUD 구현"
177
+ audit_log "gen-backend" "develop" "complete" "apps/service-user/" "4 endpoints 완료"
178
+ ```
148
179
 
149
180
  ### 실행 방법
150
181
 
@@ -337,6 +337,23 @@
337
337
  "progress_state": ".harness/progress.json",
338
338
  "progress_log": ".harness/progress.log"
339
339
  },
340
+ "audit": {
341
+ "comment": "Audit log — 1 sprint cycle 단위의 실행 추적. Planner/Dispatcher 시작 시 리셋.",
342
+ "file": ".harness/actions/audit.log",
343
+ "lifecycle": "planner_to_eval_pass",
344
+ "reset_on": ["planner", "dispatcher"],
345
+ "archive_previous": true,
346
+ "format": "TIMESTAMP | AGENT | ACTION | STATUS | TARGET | DETAIL",
347
+ "actions": {
348
+ "develop": "코드 생성/수정 (Generator)",
349
+ "review": "검수/평가 (Evaluator)",
350
+ "handoff": "에이전트 전환",
351
+ "plan": "설계/계획 (Planner)",
352
+ "gate": "자동 게이트 체크 (system)",
353
+ "escalate": "에스컬레이션 (system)"
354
+ },
355
+ "statuses": ["start", "complete", "fail", "pass", "skip", "warn"]
356
+ },
340
357
  "conventions": {
341
358
  "comment": "CONVENTIONS.md — 사용자가 자유 형식으로 작성하는 프로젝트 컨벤션. 모든 에이전트가 세션 시작 시 직접 읽음. Gotcha/AGENTS.md에 복사하지 않음.",
342
359
  "file": "CONVENTIONS.md",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "3.4.0",
3
+ "version": "3.6.0",
4
4
  "description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer (requirements refinement). Supports React and Flutter FE stacks.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -0,0 +1,179 @@
1
+ #!/bin/bash
2
+ # harness-dashboard.sh — Panel 1: 실시간 대시보드
3
+ # progress.json + feature-list.json 을 2초 간격으로 시각화
4
+ # Usage: bash scripts/harness-dashboard.sh [project-root]
5
+
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
10
+
11
+ PROJECT_ROOT="${1:-}"
12
+ if [ -z "$PROJECT_ROOT" ]; then
13
+ PROJECT_ROOT="$(resolve_harness_root ".")" || {
14
+ echo "[dashboard] .harness/ not found. Pass project root as argument."
15
+ exit 1
16
+ }
17
+ fi
18
+
19
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
20
+ FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
21
+
22
+ # ── ANSI helpers ──
23
+ BOLD="\033[1m"
24
+ DIM="\033[2m"
25
+ GREEN="\033[32m"
26
+ YELLOW="\033[33m"
27
+ RED="\033[31m"
28
+ CYAN="\033[36m"
29
+ RESET="\033[0m"
30
+
31
+ render_header() {
32
+ local now
33
+ now=$(date +"%H:%M:%S")
34
+ local project_name
35
+ project_name=$(jq -r '.project_name // "Unknown"' "$PROGRESS" 2>/dev/null)
36
+
37
+ echo -e "${BOLD}╔══════════════════════════════════════╗${RESET}"
38
+ echo -e "${BOLD}║ HARNESS DASHBOARD ║${RESET}"
39
+ echo -e "${BOLD}╚══════════════════════════════════════╝${RESET}"
40
+ echo -e " ${DIM}${project_name} | Updated: ${now}${RESET}"
41
+ echo ""
42
+ }
43
+
44
+ render_sprint_overview() {
45
+ if [ ! -f "$PROGRESS" ]; then
46
+ echo -e " ${DIM}(waiting for harness init...)${RESET}"
47
+ return
48
+ fi
49
+
50
+ local pipeline sprint_num sprint_status current_agent agent_status retry_count
51
+ pipeline=$(jq -r '.pipeline // "?"' "$PROGRESS")
52
+ sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS")
53
+ sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS")
54
+ current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS")
55
+ agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
56
+ retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
57
+
58
+ # Status color
59
+ local status_color="$RESET"
60
+ case "$agent_status" in
61
+ running) status_color="$GREEN" ;;
62
+ completed) status_color="$CYAN" ;;
63
+ failed) status_color="$RED" ;;
64
+ blocked) status_color="$RED" ;;
65
+ *) status_color="$YELLOW" ;;
66
+ esac
67
+
68
+ echo -e " ${BOLD}Pipeline${RESET} ${pipeline}"
69
+ echo -e " ${BOLD}Sprint${RESET} ${sprint_num} (${sprint_status})"
70
+ echo -e " ${BOLD}Agent${RESET} ${status_color}${current_agent} [${agent_status}]${RESET}"
71
+ if [ "$retry_count" -gt 0 ]; then
72
+ echo -e " ${BOLD}Retries${RESET} ${RED}${retry_count}${RESET}"
73
+ fi
74
+ echo ""
75
+ }
76
+
77
+ render_sprint_map() {
78
+ if [ ! -f "$PROGRESS" ]; then return; fi
79
+
80
+ local sprint_keys
81
+ sprint_keys=$(jq -r '.sprint_progress // {} | keys[]' "$PROGRESS" 2>/dev/null | sort -n)
82
+ if [ -z "$sprint_keys" ]; then return; fi
83
+
84
+ echo -e " ${BOLD}Sprint Map${RESET}"
85
+
86
+ while IFS= read -r s; do
87
+ local status features_csv notes
88
+ status=$(jq -r ".sprint_progress[\"${s}\"].status // \"pending\"" "$PROGRESS")
89
+ features_csv=$(jq -r ".sprint_progress[\"${s}\"].features // [] | join(\",\")" "$PROGRESS")
90
+ notes=$(jq -r ".sprint_progress[\"${s}\"].notes // \"\"" "$PROGRESS")
91
+
92
+ # Status icon
93
+ local icon="○"
94
+ case "$status" in
95
+ completed) icon="●" ;;
96
+ in_progress) icon="◐" ;;
97
+ scaffolded) icon="◑" ;;
98
+ pending) icon="○" ;;
99
+ esac
100
+
101
+ # Count features
102
+ local feat_count
103
+ feat_count=$(jq ".sprint_progress[\"${s}\"].features // [] | length" "$PROGRESS")
104
+
105
+ # Truncate notes
106
+ if [ ${#notes} -gt 40 ]; then
107
+ notes="${notes:0:38}.."
108
+ fi
109
+
110
+ printf " %s S%-2s %2d feat %-12s %s\n" "$icon" "$s" "$feat_count" "$status" "$notes"
111
+ done <<< "$sprint_keys"
112
+
113
+ echo ""
114
+ }
115
+
116
+ render_build_status() {
117
+ if [ ! -f "$PROGRESS" ]; then return; fi
118
+
119
+ local build_status tsc_status routes_total last_build
120
+ build_status=$(jq -r '.build.build_status // "unknown"' "$PROGRESS" 2>/dev/null)
121
+ tsc_status=$(jq -r '.build.tsc_status // "unknown"' "$PROGRESS" 2>/dev/null)
122
+ routes_total=$(jq -r '.build.routes_total // 0' "$PROGRESS" 2>/dev/null)
123
+ last_build=$(jq -r '.build.last_build // "n/a"' "$PROGRESS" 2>/dev/null)
124
+
125
+ if [ "$build_status" = "null" ] || [ "$build_status" = "unknown" ]; then return; fi
126
+
127
+ local build_color="$GREEN"
128
+ if [ "$build_status" != "passing" ]; then build_color="$RED"; fi
129
+ local tsc_color="$GREEN"
130
+ if [ "$tsc_status" != "clean" ]; then tsc_color="$RED"; fi
131
+
132
+ echo -e " ${BOLD}Build${RESET}"
133
+ echo -e " Build: ${build_color}${build_status}${RESET} | TSC: ${tsc_color}${tsc_status}${RESET} | Routes: ${routes_total}"
134
+ echo ""
135
+ }
136
+
137
+ render_failure_info() {
138
+ if [ ! -f "$PROGRESS" ]; then return; fi
139
+
140
+ local failure_agent
141
+ failure_agent=$(jq -r '.failure.agent // empty' "$PROGRESS" 2>/dev/null)
142
+
143
+ if [ -n "$failure_agent" ] && [ "$failure_agent" != "null" ]; then
144
+ local failure_loc failure_msg
145
+ failure_loc=$(jq -r '.failure.location // ""' "$PROGRESS")
146
+ failure_msg=$(jq -r '.failure.message // ""' "$PROGRESS")
147
+
148
+ echo -e " ${RED}${BOLD}╔═ FAILURE ═══════════════════════════╗${RESET}"
149
+ echo -e " ${RED} Agent: ${failure_agent}${RESET}"
150
+ echo -e " ${RED} Where: ${failure_loc}${RESET}"
151
+ if [ -n "$failure_msg" ] && [ "$failure_msg" != "null" ]; then
152
+ echo -e " ${RED} ${failure_msg}${RESET}"
153
+ fi
154
+ echo -e " ${RED}${BOLD}╚════════════════════════════════════╝${RESET}"
155
+ echo ""
156
+ fi
157
+ }
158
+
159
+ # ── Main Loop ──
160
+ while true; do
161
+ clear
162
+ render_header
163
+ render_sprint_overview
164
+ render_build_status
165
+ render_failure_info
166
+ render_sprint_map
167
+
168
+ # Feature progress (reuse existing lib)
169
+ if [ -f "$FEATURES" ]; then
170
+ render_progress "$PROJECT_ROOT" 2>/dev/null
171
+ fi
172
+
173
+ # Agent sequence bar
174
+ render_agent_bar "$PROJECT_ROOT" 2>/dev/null
175
+ echo ""
176
+
177
+ echo -e " ${DIM}Refreshing every 2s | Ctrl+C to exit${RESET}"
178
+ sleep 2
179
+ done
@@ -0,0 +1,230 @@
1
+ #!/bin/bash
2
+ # harness-eval-watcher.sh — Panel 3: Evaluation 결과 Summary 리뷰
3
+ # evaluation-*.md 파일 변경 감지 → 요약 출력 (또는 claude -p 호출)
4
+ # Usage: bash scripts/harness-eval-watcher.sh [project-root] [--ai]
5
+ # --ai evaluation 완료 시 claude -p 로 AI 요약 생성 (API 비용 발생)
6
+
7
+ set -euo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+
11
+ PROJECT_ROOT="${1:-}"
12
+ USE_AI=false
13
+ for arg in "$@"; do
14
+ if [ "$arg" = "--ai" ]; then USE_AI=true; fi
15
+ done
16
+
17
+ if [ -z "$PROJECT_ROOT" ] || [ "$PROJECT_ROOT" = "--ai" ]; then
18
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
19
+ PROJECT_ROOT="$(resolve_harness_root ".")" || {
20
+ echo "[eval-watcher] .harness/ not found. Pass project root as argument."
21
+ exit 1
22
+ }
23
+ fi
24
+
25
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
26
+ EVAL_FUNC="$PROJECT_ROOT/.harness/actions/evaluation-functional.md"
27
+ EVAL_VISUAL="$PROJECT_ROOT/.harness/actions/evaluation-visual.md"
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
+ RESET="\033[0m"
37
+
38
+ LAST_FUNC_HASH=""
39
+ LAST_VISUAL_HASH=""
40
+ EVAL_COUNT=0
41
+
42
+ print_header() {
43
+ echo -e "${BOLD}╔══════════════════════════════════════╗${RESET}"
44
+ echo -e "${BOLD}║ EVALUATION REVIEW ║${RESET}"
45
+ echo -e "${BOLD}╚══════════════════════════════════════╝${RESET}"
46
+ echo ""
47
+ }
48
+
49
+ file_hash() {
50
+ if [ -f "$1" ]; then
51
+ md5 -q "$1" 2>/dev/null || md5sum "$1" 2>/dev/null | awk '{print $1}' || echo ""
52
+ else
53
+ echo ""
54
+ fi
55
+ }
56
+
57
+ # ── Extract eval summary from markdown ──
58
+ extract_eval_summary() {
59
+ local file="$1" type="$2"
60
+
61
+ if [ ! -f "$file" ]; then return; fi
62
+
63
+ EVAL_COUNT=$((EVAL_COUNT + 1))
64
+ local now
65
+ now=$(date +"%H:%M:%S")
66
+
67
+ echo -e " ${BOLD}═══ ${type} Evaluation #${EVAL_COUNT} ═══${RESET} ${DIM}(${now})${RESET}"
68
+ echo ""
69
+
70
+ # Extract Verdict (PASS/FAIL)
71
+ local verdict
72
+ verdict=$(grep -i "verdict\|result\|판정" "$file" | head -3)
73
+ if [ -n "$verdict" ]; then
74
+ if echo "$verdict" | grep -qi "PASS"; then
75
+ echo -e " ${GREEN}${BOLD}VERDICT: PASS${RESET}"
76
+ elif echo "$verdict" | grep -qi "FAIL"; then
77
+ echo -e " ${RED}${BOLD}VERDICT: FAIL${RESET}"
78
+ else
79
+ echo -e " ${YELLOW}VERDICT: ${verdict}${RESET}"
80
+ fi
81
+ echo ""
82
+ fi
83
+
84
+ # Extract Score
85
+ local score_line
86
+ score_line=$(grep -iE "score|점수|weighted|가중" "$file" | head -3)
87
+ if [ -n "$score_line" ]; then
88
+ echo -e " ${BOLD}Score${RESET}"
89
+ echo "$score_line" | while IFS= read -r line; do
90
+ # Color by score value
91
+ local num
92
+ num=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+' | head -1)
93
+ if [ -n "$num" ]; then
94
+ local int_part
95
+ int_part=$(echo "$num" | cut -d. -f1)
96
+ if [ "${int_part:-0}" -ge 3 ] || ([ "${int_part:-0}" -eq 2 ] && [ "$(echo "$num >= 2.80" | bc 2>/dev/null)" = "1" ]); then
97
+ echo -e " ${GREEN}${line}${RESET}"
98
+ else
99
+ echo -e " ${RED}${line}${RESET}"
100
+ fi
101
+ else
102
+ echo " $line"
103
+ fi
104
+ done
105
+ echo ""
106
+ fi
107
+
108
+ # Extract individual rubric items (R1-R5 or V1-V5)
109
+ local rubric_lines
110
+ rubric_lines=$(grep -E "^[|#].*[RV][1-5]" "$file" | head -10)
111
+ if [ -n "$rubric_lines" ]; then
112
+ echo -e " ${BOLD}Rubric${RESET}"
113
+ echo "$rubric_lines" | while IFS= read -r line; do
114
+ echo " $line"
115
+ done
116
+ echo ""
117
+ fi
118
+
119
+ # Extract FAIL reasons
120
+ local fail_lines
121
+ fail_lines=$(grep -iE "fail|실패|regression|불일치|위반" "$file" | head -5)
122
+ if [ -n "$fail_lines" ]; then
123
+ echo -e " ${RED}${BOLD}Issues${RESET}"
124
+ echo "$fail_lines" | while IFS= read -r line; do
125
+ echo -e " ${RED}• ${line}${RESET}"
126
+ done
127
+ echo ""
128
+ fi
129
+
130
+ # Extract action items / recommendations
131
+ local action_lines
132
+ action_lines=$(grep -iE "recommend|action|수정|개선|필요|re-generate" "$file" | head -5)
133
+ if [ -n "$action_lines" ]; then
134
+ echo -e " ${YELLOW}${BOLD}Actions${RESET}"
135
+ echo "$action_lines" | while IFS= read -r line; do
136
+ echo -e " ${YELLOW}→ ${line}${RESET}"
137
+ done
138
+ echo ""
139
+ fi
140
+
141
+ echo -e " ${DIM}─────────────────────────────────────${RESET}"
142
+ echo ""
143
+ }
144
+
145
+ # ── AI Summary (optional) ──
146
+ generate_ai_summary() {
147
+ local file="$1" type="$2"
148
+
149
+ if [ "$USE_AI" != true ]; then return; fi
150
+ if ! command -v claude &>/dev/null; then
151
+ echo -e " ${DIM}(claude CLI not found — skipping AI summary)${RESET}"
152
+ return
153
+ fi
154
+
155
+ echo -e " ${CYAN}Generating AI summary...${RESET}"
156
+
157
+ local summary
158
+ summary=$(claude -p "다음 ${type} Evaluation 문서를 3줄 이내로 요약해줘. 핵심 결과(PASS/FAIL), 주요 문제점, 다음 액션을 포함:
159
+
160
+ $(cat "$file")" --output-format text 2>/dev/null || echo "(AI summary failed)")
161
+
162
+ echo -e " ${CYAN}${BOLD}AI Summary:${RESET}"
163
+ echo "$summary" | while IFS= read -r line; do
164
+ echo -e " ${CYAN}${line}${RESET}"
165
+ done
166
+ echo ""
167
+ }
168
+
169
+ # ── Main ──
170
+ print_header
171
+
172
+ # Show existing evals on startup
173
+ if [ -f "$EVAL_FUNC" ]; then
174
+ echo -e " ${DIM}Found existing functional evaluation${RESET}"
175
+ extract_eval_summary "$EVAL_FUNC" "Functional"
176
+ LAST_FUNC_HASH=$(file_hash "$EVAL_FUNC")
177
+ fi
178
+
179
+ if [ -f "$EVAL_VISUAL" ]; then
180
+ echo -e " ${DIM}Found existing visual evaluation${RESET}"
181
+ extract_eval_summary "$EVAL_VISUAL" "Visual"
182
+ LAST_VISUAL_HASH=$(file_hash "$EVAL_VISUAL")
183
+ fi
184
+
185
+ if [ -z "$LAST_FUNC_HASH" ] && [ -z "$LAST_VISUAL_HASH" ]; then
186
+ echo -e " ${DIM}Waiting for evaluation files...${RESET}"
187
+ echo -e " ${DIM} ${EVAL_FUNC}${RESET}"
188
+ echo -e " ${DIM} ${EVAL_VISUAL}${RESET}"
189
+ echo ""
190
+ fi
191
+
192
+ echo -e " ${DIM}Watching for changes every 3s | Ctrl+C to exit${RESET}"
193
+ if [ "$USE_AI" = true ]; then
194
+ echo -e " ${CYAN}AI summary enabled (claude -p)${RESET}"
195
+ fi
196
+ echo ""
197
+
198
+ # ── Poll for changes ──
199
+ while true; do
200
+ # Check functional eval
201
+ local_func_hash=$(file_hash "$EVAL_FUNC")
202
+ if [ "$local_func_hash" != "$LAST_FUNC_HASH" ] && [ -n "$local_func_hash" ]; then
203
+ LAST_FUNC_HASH="$local_func_hash"
204
+ extract_eval_summary "$EVAL_FUNC" "Functional"
205
+ generate_ai_summary "$EVAL_FUNC" "Functional"
206
+ fi
207
+
208
+ # Check visual eval
209
+ local_visual_hash=$(file_hash "$EVAL_VISUAL")
210
+ if [ "$local_visual_hash" != "$LAST_VISUAL_HASH" ] && [ -n "$local_visual_hash" ]; then
211
+ LAST_VISUAL_HASH="$local_visual_hash"
212
+ extract_eval_summary "$EVAL_VISUAL" "Visual"
213
+ generate_ai_summary "$EVAL_VISUAL" "Visual"
214
+ fi
215
+
216
+ # Check agent status for eval-related transitions
217
+ if [ -f "$PROGRESS" ]; then
218
+ local agent
219
+ agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
220
+ local status
221
+ status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
222
+
223
+ if [[ "$agent" == evaluator-* ]] && [ "$status" = "running" ]; then
224
+ # Show that evaluator is active
225
+ :
226
+ fi
227
+ fi
228
+
229
+ sleep 3
230
+ done
@@ -0,0 +1,193 @@
1
+ #!/bin/bash
2
+ # harness-monitor.sh — Panel 2: Task & Agent Lifecycle 모니터링
3
+ # audit.log 스트리밍 + progress.json 변경 감지
4
+ # Usage: bash scripts/harness-monitor.sh [project-root]
5
+
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+
10
+ PROJECT_ROOT="${1:-}"
11
+ if [ -z "$PROJECT_ROOT" ]; then
12
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
13
+ PROJECT_ROOT="$(resolve_harness_root ".")" || {
14
+ echo "[monitor] .harness/ not found. Pass project root as argument."
15
+ exit 1
16
+ }
17
+ fi
18
+
19
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
20
+ AUDIT_LOG="$PROJECT_ROOT/.harness/actions/audit.log"
21
+ HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
22
+
23
+ # ── ANSI helpers ──
24
+ BOLD="\033[1m"
25
+ DIM="\033[2m"
26
+ GREEN="\033[32m"
27
+ YELLOW="\033[33m"
28
+ RED="\033[31m"
29
+ CYAN="\033[36m"
30
+ MAGENTA="\033[35m"
31
+ RESET="\033[0m"
32
+
33
+ LAST_AGENT=""
34
+ LAST_STATUS=""
35
+ LAST_SPRINT=""
36
+ EVENT_COUNT=0
37
+
38
+ print_event() {
39
+ local ts="$1" icon="$2" color="$3" msg="$4"
40
+ EVENT_COUNT=$((EVENT_COUNT + 1))
41
+ echo -e " ${DIM}${ts}${RESET} ${color}${icon}${RESET} ${msg}"
42
+ }
43
+
44
+ # ── Header ──
45
+ print_header() {
46
+ echo -e "${BOLD}╔══════════════════════════════════════╗${RESET}"
47
+ echo -e "${BOLD}║ AGENT LIFECYCLE MONITOR ║${RESET}"
48
+ echo -e "${BOLD}╚══════════════════════════════════════╝${RESET}"
49
+ echo ""
50
+ }
51
+
52
+ # ── Detect agent transitions from progress.json ──
53
+ check_transitions() {
54
+ if [ ! -f "$PROGRESS" ]; then return; fi
55
+
56
+ local agent status sprint
57
+ agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
58
+ status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
59
+ sprint=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
60
+
61
+ local now
62
+ now=$(date +"%H:%M:%S")
63
+
64
+ # Sprint change
65
+ if [ "$sprint" != "$LAST_SPRINT" ] && [ -n "$LAST_SPRINT" ]; then
66
+ print_event "$now" ">>>" "$MAGENTA" "${BOLD}Sprint ${sprint} started${RESET}"
67
+ echo ""
68
+ fi
69
+
70
+ # Agent change
71
+ if [ "$agent" != "$LAST_AGENT" ] && [ -n "$LAST_AGENT" ]; then
72
+ if [ "$LAST_AGENT" != "none" ] && [ "$LAST_AGENT" != "null" ]; then
73
+ print_event "$now" " ✓ " "$GREEN" "${LAST_AGENT} → ${BOLD}done${RESET}"
74
+ fi
75
+ if [ "$agent" != "none" ] && [ "$agent" != "null" ]; then
76
+ print_event "$now" " ▶ " "$CYAN" "${BOLD}${agent}${RESET} started"
77
+
78
+ # Show handoff context if available
79
+ if [ -f "$HANDOFF" ]; then
80
+ local from focus
81
+ from=$(jq -r '.from // empty' "$HANDOFF" 2>/dev/null)
82
+ focus=$(jq -r '.focus_features // [] | join(", ")' "$HANDOFF" 2>/dev/null)
83
+ if [ -n "$from" ] && [ "$from" != "null" ]; then
84
+ echo -e " ${DIM}handoff from: ${from}${RESET}"
85
+ fi
86
+ if [ -n "$focus" ] && [ "$focus" != "null" ]; then
87
+ echo -e " ${DIM}focus: ${focus}${RESET}"
88
+ fi
89
+ fi
90
+ fi
91
+ fi
92
+
93
+ # Status change (same agent)
94
+ if [ "$agent" = "$LAST_AGENT" ] && [ "$status" != "$LAST_STATUS" ] && [ -n "$LAST_STATUS" ]; then
95
+ local status_color="$RESET"
96
+ local status_icon="•"
97
+ case "$status" in
98
+ running) status_color="$GREEN"; status_icon=" ▶ " ;;
99
+ completed) status_color="$CYAN"; status_icon=" ✓ " ;;
100
+ failed) status_color="$RED"; status_icon=" ✗ " ;;
101
+ blocked) status_color="$RED"; status_icon=" ! " ;;
102
+ pending) status_color="$YELLOW"; status_icon=" … " ;;
103
+ esac
104
+ print_event "$now" "$status_icon" "$status_color" "${agent} → ${BOLD}${status}${RESET}"
105
+
106
+ # Show failure detail
107
+ if [ "$status" = "failed" ]; then
108
+ local fail_msg
109
+ fail_msg=$(jq -r '.failure.message // empty' "$PROGRESS" 2>/dev/null)
110
+ if [ -n "$fail_msg" ] && [ "$fail_msg" != "null" ]; then
111
+ echo -e " ${RED}${fail_msg}${RESET}"
112
+ fi
113
+ fi
114
+ fi
115
+
116
+ LAST_AGENT="$agent"
117
+ LAST_STATUS="$status"
118
+ LAST_SPRINT="$sprint"
119
+ }
120
+
121
+ # ── Stream audit.log new lines ──
122
+ stream_audit() {
123
+ if [ ! -f "$AUDIT_LOG" ]; then return; fi
124
+
125
+ # Use tail -f in background, parse each new line
126
+ tail -n 0 -f "$AUDIT_LOG" 2>/dev/null | while IFS= read -r line; do
127
+ # Skip comments/headers
128
+ if [[ "$line" == "#"* ]] || [[ -z "$line" ]]; then continue; fi
129
+
130
+ # Parse: TIMESTAMP | AGENT | ACTION | STATUS | TARGET | DETAIL
131
+ local ts agent action status target detail
132
+ ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
133
+ agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
134
+ action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
135
+ status=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
136
+ target=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$5); print $5}')
137
+ detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$6); print $6}')
138
+
139
+ # Color by status
140
+ local color="$RESET" icon="•"
141
+ case "$status" in
142
+ start) color="$CYAN"; icon="▶" ;;
143
+ complete) color="$GREEN"; icon="✓" ;;
144
+ fail) color="$RED"; icon="✗" ;;
145
+ pass) color="$GREEN"; icon="✓" ;;
146
+ skip) color="$DIM"; icon="–" ;;
147
+ esac
148
+
149
+ # Short timestamp (HH:MM:SS from ISO)
150
+ local short_ts
151
+ short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "$ts")
152
+
153
+ echo -e " ${DIM}${short_ts}${RESET} ${color}${icon}${RESET} ${BOLD}${agent}${RESET} ${action} ${target} ${DIM}${detail}${RESET}"
154
+ done &
155
+ AUDIT_TAIL_PID=$!
156
+ }
157
+
158
+ # ── Cleanup ──
159
+ cleanup() {
160
+ if [ -n "${AUDIT_TAIL_PID:-}" ]; then
161
+ kill "$AUDIT_TAIL_PID" 2>/dev/null || true
162
+ fi
163
+ exit 0
164
+ }
165
+ trap cleanup EXIT INT TERM
166
+
167
+ # ── Main ──
168
+ print_header
169
+
170
+ # Initialize state
171
+ if [ -f "$PROGRESS" ]; then
172
+ LAST_AGENT=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
173
+ LAST_STATUS=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
174
+ LAST_SPRINT=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
175
+ fi
176
+
177
+ # Show existing history
178
+ if [ -f "$PROGRESS" ]; then
179
+ echo -e " ${BOLD}History${RESET}"
180
+ jq -r '.history // [] | .[] | " \(.timestamp) \(.agent) — \(.action): \(.detail)"' "$PROGRESS" 2>/dev/null
181
+ echo ""
182
+ echo -e " ${DIM}── Live events below ──${RESET}"
183
+ echo ""
184
+ fi
185
+
186
+ # Start audit log streaming
187
+ stream_audit
188
+
189
+ # Poll progress.json for transitions
190
+ while true; do
191
+ check_transitions
192
+ sleep 2
193
+ done
@@ -7,6 +7,7 @@ set -e
7
7
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
8
  source "$SCRIPT_DIR/lib/harness-render-progress.sh"
9
9
  source "$SCRIPT_DIR/lib/harness-guardrail.sh"
10
+ source "$SCRIPT_DIR/lib/harness-audit.sh"
10
11
 
11
12
  # ─────────────────────────────────────────
12
13
  # Resolve project root
@@ -24,6 +25,9 @@ HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
24
25
 
25
26
  check_jq || exit 1
26
27
 
28
+ # Initialize audit log
29
+ init_audit "$PROJECT_ROOT"
30
+
27
31
  if [ ! -f "$PROGRESS" ]; then
28
32
  echo "[harness] ERROR: progress.json not found" >&2
29
33
  echo " Run 'npx walwal-harness --force' to reinitialize." >&2
@@ -277,15 +281,25 @@ verify_artifact_prerequisites() {
277
281
  # Runtime Guardrail — 파일 소유권 검증
278
282
  # ─────────────────────────────────────────
279
283
  if [ "$agent_status" = "completed" ]; then
280
- verify_file_ownership "$PROJECT_ROOT" || true
284
+ audit_log "$current_agent" "develop" "complete" "" "agent completed"
285
+ verify_file_ownership "$PROJECT_ROOT" || audit_gate "file-ownership" "warn" "boundary violation detected"
281
286
  fi
282
287
 
283
288
  # ─────────────────────────────────────────
284
289
  # Run Pre-Eval Gate (if applicable)
285
290
  # ─────────────────────────────────────────
286
291
  if [ "$agent_status" = "completed" ]; then
287
- run_pre_eval_gate "$next_agent" || true
288
- verify_artifact_prerequisites "$next_agent" || true
292
+ audit_gate "pre-eval" "start" "$next_agent"
293
+ if run_pre_eval_gate "$next_agent"; then
294
+ audit_gate "pre-eval" "pass" "$next_agent"
295
+ else
296
+ audit_gate "pre-eval" "fail" "rerouted to $current_agent"
297
+ fi
298
+ if verify_artifact_prerequisites "$next_agent"; then
299
+ audit_gate "artifact-prereq" "pass" "$next_agent"
300
+ else
301
+ audit_gate "artifact-prereq" "fail" "$next_agent"
302
+ fi
289
303
  fi
290
304
 
291
305
  # ─────────────────────────────────────────
@@ -303,6 +317,7 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
303
317
  escalate_after=$(jq -r '.flow.escalate_to_planner_after // 3' "$CONFIG" 2>/dev/null || echo 3)
304
318
  if [ "$retry_count" -ge "$escalate_after" ] && [ "$sprint_status" = "failed" ] && [ "$next_agent" != "planner" ]; then
305
319
  echo " ⚠ Escalation: ${retry_count}회 실패 — Planner에게 scope 축소/접근 변경 요청"
320
+ audit_log "system" "escalate" "start" "→planner" "retry ${retry_count}회 초과"
306
321
  next_agent="planner"
307
322
  jq --arg msg "Escalated after ${retry_count} failures. Planner must review scope or approach." \
308
323
  '.next_agent = "planner" |
@@ -442,7 +457,10 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
442
457
  timestamp: $timestamp
443
458
  }' > "$HANDOFF"
444
459
 
460
+ audit_handoff "${current_agent:-dispatcher}" "$next_agent" "complete"
461
+
445
462
  elif [ "$next_agent" = "archive" ]; then
463
+ audit_log "system" "archive" "start" "sprint-${sprint_num}" "sprint cycle complete"
446
464
  jq -n \
447
465
  --arg from "${current_agent:-evaluator}" \
448
466
  --argjson sprint "$sprint_num" \
@@ -1,17 +1,17 @@
1
1
  #!/bin/bash
2
2
  # harness-session-start.sh — SessionStart 훅
3
3
  # 새 세션 시작 시 자동으로:
4
- # 1) 이전 에이전트가 completed이면 harness-next.sh를 실행하여 게이트 체크 + handoff 생성
5
- # 2) handoff.json이 있으면 다음 에이전트 안내
6
- # 3) statusline이 상시 상태를 표시하므로 여기서는 핵심 안내만 출력
7
- #
8
- # 사용자가 수동으로 harness-next.sh를 실행할 필요가 없도록 자동화.
4
+ # 1) 이전 에이전트가 completed이면 harness-next.sh 실행 (게이트 + handoff)
5
+ # 2) Planner/Dispatcher 사이클이면 audit 리셋
6
+ # 3) 다음 에이전트 안내 출력
9
7
 
10
8
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
11
9
  LIB="$SCRIPT_DIR/lib/harness-render-progress.sh"
10
+ AUDIT_LIB="$SCRIPT_DIR/lib/harness-audit.sh"
12
11
 
13
12
  if [ ! -f "$LIB" ]; then exit 0; fi
14
13
  source "$LIB"
14
+ [ -f "$AUDIT_LIB" ] && source "$AUDIT_LIB"
15
15
  command -v jq &>/dev/null || exit 0
16
16
 
17
17
  PROJECT_ROOT="$(resolve_harness_root "." 2>/dev/null)" || exit 0
@@ -21,6 +21,7 @@ HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
21
21
  [ -f "$PROGRESS" ] || exit 0
22
22
 
23
23
  sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null)
24
+ sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
24
25
  current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
25
26
  next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
26
27
  agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
@@ -34,14 +35,21 @@ if [ "$sprint_status" = "init" ]; then
34
35
  fi
35
36
 
36
37
  # ─────────────────────────────────────────
37
- # 이전 에이전트가 completed → 자동으로 harness-next 실행
38
- # (게이트 체크, 아티팩트 검증, handoff.json 생성)
38
+ # Audit lifecycle: Planner/Dispatcher 시작 시 리셋
39
+ # ─────────────────────────────────────────
40
+ init_audit "$PROJECT_ROOT"
41
+ if [ "$next_agent" = "planner" ] || [ "$next_agent" = "dispatcher" ]; then
42
+ # 새 사이클 — 이전 audit을 archive로 이동하고 새로 시작
43
+ reset_audit "$PROJECT_ROOT" "$sprint_num"
44
+ audit_log "system" "cycle" "start" "sprint-${sprint_num}" "new plan/dispatch cycle"
45
+ fi
46
+
47
+ # ─────────────────────────────────────────
48
+ # 이전 에이전트가 completed/failed → 자동 전환
39
49
  # ─────────────────────────────────────────
40
50
  if [ "$agent_status" = "completed" ] || [ "$agent_status" = "failed" ]; then
41
- # harness-next.sh를 백그라운드로 실행하면 안 됨 — 결과가 필요
42
51
  bash "$SCRIPT_DIR/harness-next.sh" "$PROJECT_ROOT" 2>/dev/null
43
52
 
44
- # harness-next.sh가 progress.json과 handoff.json을 업데이트했으므로 다시 읽기
45
53
  next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
46
54
  agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
47
55
  fi
@@ -53,7 +61,6 @@ if [ "$agent_status" = "blocked" ]; then
53
61
  echo "# Harness BLOCKED — retry limit reached, user intervention required"
54
62
 
55
63
  elif [ -f "$HANDOFF" ] && [ "$next_agent" != "none" ] && [ "$next_agent" != "null" ]; then
56
- # handoff.json에서 모델/모드 정보 읽기
57
64
  handoff_model=$(jq -r '.model // "opus"' "$HANDOFF" 2>/dev/null)
58
65
  handoff_thinking=$(jq -r '.thinking_mode // empty' "$HANDOFF" 2>/dev/null)
59
66
 
@@ -0,0 +1,121 @@
1
+ #!/bin/bash
2
+ # harness-tmux.sh — tmux 4-pane 레이아웃 자동 구성
3
+ #
4
+ # ┌──────────────────────┬──────────────────────┐
5
+ # │ │ Panel 1: Dashboard │
6
+ # │ Main Terminal ├──────────────────────┤
7
+ # │ (claude 대화형) │ Panel 2: Monitor │
8
+ # │ ├──────────────────────┤
9
+ # │ │ Panel 3: Eval Review │
10
+ # └──────────────────────┴──────────────────────┘
11
+ #
12
+ # Usage:
13
+ # bash scripts/harness-tmux.sh [project-root]
14
+ # bash scripts/harness-tmux.sh /path/to/project --ai (AI eval summary 활성화)
15
+ #
16
+ # Options:
17
+ # --ai Panel 3에서 claude -p 로 AI 요약 생성 (API 비용 발생)
18
+ # --detach 세션 생성 후 attach 하지 않음
19
+ # --kill 기존 harness-studio 세션 종료
20
+
21
+ set -euo pipefail
22
+
23
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
24
+ SESSION_NAME="harness-studio"
25
+
26
+ PROJECT_ROOT=""
27
+ USE_AI=""
28
+ DETACH=false
29
+ KILL=false
30
+
31
+ for arg in "$@"; do
32
+ case "$arg" in
33
+ --ai) USE_AI="--ai" ;;
34
+ --detach) DETACH=true ;;
35
+ --kill)
36
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "Session '$SESSION_NAME' killed." || echo "No session to kill."
37
+ exit 0
38
+ ;;
39
+ *)
40
+ if [ -d "$arg" ]; then PROJECT_ROOT="$arg"; fi
41
+ ;;
42
+ esac
43
+ done
44
+
45
+ # Auto-detect project root
46
+ if [ -z "$PROJECT_ROOT" ]; then
47
+ dir="$(pwd)"
48
+ while [ "$dir" != "/" ]; do
49
+ if [ -d "$dir/.harness" ]; then PROJECT_ROOT="$dir"; break; fi
50
+ dir="$(dirname "$dir")"
51
+ done
52
+ fi
53
+
54
+ if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
55
+ echo "Error: .harness/ directory not found."
56
+ echo "Usage: bash scripts/harness-tmux.sh [project-root]"
57
+ exit 1
58
+ fi
59
+
60
+ echo "Project: $PROJECT_ROOT"
61
+ echo "Session: $SESSION_NAME"
62
+
63
+ # Kill existing session if any
64
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
65
+
66
+ # ── Create tmux session ──
67
+ # Pane 0 (left, Main): interactive shell at project root
68
+ tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50
69
+
70
+ # Split right column (40% width)
71
+ tmux split-window -h -l 80 -t "$SESSION_NAME" -c "$PROJECT_ROOT"
72
+
73
+ # Pane 1 (top-right): Dashboard
74
+ tmux send-keys -t "${SESSION_NAME}.1" "bash ${SCRIPT_DIR}/harness-dashboard.sh '${PROJECT_ROOT}'" Enter
75
+
76
+ # Split pane 1 vertically for Monitor
77
+ tmux split-window -v -t "${SESSION_NAME}.1" -c "$PROJECT_ROOT"
78
+
79
+ # Pane 2 (mid-right): Monitor
80
+ tmux send-keys -t "${SESSION_NAME}.2" "bash ${SCRIPT_DIR}/harness-monitor.sh '${PROJECT_ROOT}'" Enter
81
+
82
+ # Split pane 2 vertically for Eval
83
+ tmux split-window -v -t "${SESSION_NAME}.2" -c "$PROJECT_ROOT"
84
+
85
+ # Pane 3 (bottom-right): Eval Watcher
86
+ tmux send-keys -t "${SESSION_NAME}.3" "bash ${SCRIPT_DIR}/harness-eval-watcher.sh '${PROJECT_ROOT}' ${USE_AI}" Enter
87
+
88
+ # ── Layout adjustments ──
89
+ # Select left pane (Main) — this is where the user works
90
+ tmux select-pane -t "${SESSION_NAME}.0"
91
+
92
+ # Set pane titles (requires tmux >= 3.2 with pane-border-format)
93
+ tmux select-pane -t "${SESSION_NAME}.0" -T "Main"
94
+ tmux select-pane -t "${SESSION_NAME}.1" -T "Dashboard"
95
+ tmux select-pane -t "${SESSION_NAME}.2" -T "Monitor"
96
+ tmux select-pane -t "${SESSION_NAME}.3" -T "Eval Review"
97
+
98
+ # Enable pane border titles
99
+ tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
100
+ tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
101
+
102
+ # ── Attach or print info ──
103
+ if [ "$DETACH" = true ]; then
104
+ echo ""
105
+ echo "Session created. Attach with:"
106
+ echo " tmux attach -t $SESSION_NAME"
107
+ else
108
+ # If already in tmux, switch; otherwise attach
109
+ if [ -n "${TMUX:-}" ]; then
110
+ tmux switch-client -t "$SESSION_NAME"
111
+ else
112
+ echo ""
113
+ echo "Launching harness-studio..."
114
+ echo " Main (left) : Interactive terminal — run 'claude' here"
115
+ echo " Dashboard (right↑) : Real-time progress"
116
+ echo " Monitor (right↔) : Agent lifecycle events"
117
+ echo " Eval (right↓) : Evaluation summaries"
118
+ echo ""
119
+ tmux attach -t "$SESSION_NAME"
120
+ fi
121
+ fi
@@ -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
+ }