@walwal-harness/cli 5.7.0 → 5.7.2
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/bin/init.js +3 -4
- package/gotchas/README.md +23 -6
- package/package.json +1 -1
- package/scripts/harness-gotcha-memory.sh +64 -2
- package/scripts/harness-gotcha-register.sh +259 -0
- package/scripts/harness-next.sh +17 -0
- package/scripts/harness-session-start.sh +1 -0
- package/scripts/harness-tmux.sh +1 -1
- package/skills/dispatcher/SKILL.md +22 -0
- package/skills/evaluator-code-quality/SKILL.md +28 -0
- package/skills/evaluator-functional/SKILL.md +29 -7
- package/skills/evaluator-visual/SKILL.md +28 -0
package/bin/init.js
CHANGED
|
@@ -1017,15 +1017,14 @@ function main() {
|
|
|
1017
1017
|
log('║ Then say: "하네스 엔지니어링 시작" ║');
|
|
1018
1018
|
log('║ Or invoke: /harness-dispatcher ║');
|
|
1019
1019
|
log('║ ║');
|
|
1020
|
-
log('║
|
|
1021
|
-
log('║
|
|
1022
|
-
log('║ /harness-solo → Solo mode (prompting sequential) ║');
|
|
1020
|
+
log('║ 기본은 Solo 모드 (순차 진행) — 입력 불필요. ║');
|
|
1021
|
+
log('║ 병렬 3팀 실행을 원하면 파이프라인 확정 후 /harness-team ║');
|
|
1023
1022
|
log('╚═══════════════════════════════════════════════════════════╝');
|
|
1024
1023
|
} else {
|
|
1025
1024
|
log('Next steps:');
|
|
1026
1025
|
log(' 1. Restart Claude Code session (/exit → re-enter directory)');
|
|
1027
1026
|
log(' 2. Say "하네스 엔지니어링 시작" or /harness-dispatcher');
|
|
1028
|
-
log(' 3.
|
|
1027
|
+
log(' 3. Solo 모드는 자동. Team 모드 원할 때만 파이프라인 확정 후 /harness-team');
|
|
1029
1028
|
}
|
|
1030
1029
|
console.log('');
|
|
1031
1030
|
}
|
package/gotchas/README.md
CHANGED
|
@@ -19,19 +19,36 @@
|
|
|
19
19
|
## 항목 형식
|
|
20
20
|
|
|
21
21
|
```markdown
|
|
22
|
-
### [G-NNN] 간결한 제목
|
|
22
|
+
### [G-NNN] 간결한 제목 <!-- rule_id: <unique-key> -->
|
|
23
|
+
- **Status**: unverified | verified | resolved
|
|
23
24
|
- **Date**: YYYY-MM-DD
|
|
24
|
-
- **
|
|
25
|
+
- **Source**: <작성 주체> (예: "evaluator-functional:F-003" / "dispatcher:manual")
|
|
26
|
+
- **Trigger**: 사용자가 한 말 또는 "Eval 자동 감지"
|
|
25
27
|
- **Wrong**: 에이전트가 했던 잘못된 행동
|
|
26
28
|
- **Right**: 올바른 행동
|
|
27
29
|
- **Why**: 왜 잘못인지 근거
|
|
28
30
|
- **Scope**: 이 규칙이 적용되는 조건/범위
|
|
31
|
+
- **Occurrences**: 1
|
|
32
|
+
- **Last-Seen**: YYYY-MM-DD
|
|
29
33
|
```
|
|
30
34
|
|
|
35
|
+
## 작성 주체
|
|
36
|
+
|
|
37
|
+
- **Dispatcher**: 사용자의 명시적 실수 지적을 Gotcha Flow 로 기록
|
|
38
|
+
- **Evaluator (code-quality / functional / visual)**: evaluation-*.md 의 `gotcha_candidates` JSON 블록을 통해 자동 등록 (v5.7.1+). `harness-next.sh` 가 Evaluator 완료 직후 `scripts/harness-gotcha-register.sh --scan-evaluations` 로 처리.
|
|
39
|
+
|
|
40
|
+
## Status 라이프사이클 (v5.7.1+)
|
|
41
|
+
|
|
42
|
+
- **unverified**: 신규 자동/수동 등록 기본값. Generator 는 참조하지만 페널티 강도 낮음.
|
|
43
|
+
- **verified**: Planner/사용자 리뷰 후 승격. 이후 위반 시 하드 페널티.
|
|
44
|
+
- **resolved**: 근본 원인이 코드/스킬/컨벤션에 반영되어 더 이상 재발하지 않는 항목. 삭제 대신 태그 유지.
|
|
45
|
+
|
|
46
|
+
Planner 는 스프린트 전환 시 `unverified` 항목을 검토해 `verified` 또는 제거 판정을 내린다 (AGENTS.md "메모리 오염 방어" 섹션).
|
|
47
|
+
|
|
31
48
|
## 관리 규칙
|
|
32
49
|
|
|
33
|
-
- Dispatcher만 gotchas 파일에 쓰기 가능
|
|
50
|
+
- Dispatcher + Evaluator 만 gotchas 파일에 쓰기 가능 (자동 등록 포함)
|
|
34
51
|
- 각 에이전트는 자신의 gotchas 파일을 읽기 전용으로 참조
|
|
35
|
-
- 중복 항목 금지 —
|
|
36
|
-
- 해결된
|
|
37
|
-
- 항목이 20개 초과 시 가장 오래된
|
|
52
|
+
- 중복 항목 금지 — 동일 `rule_id` 는 `Occurrences` + `Last-Seen` 만 갱신
|
|
53
|
+
- 해결된 항목: `Status: resolved` 또는 제목 뒤 `[RESOLVED]` 태그
|
|
54
|
+
- 항목이 20개 초과 시 가장 오래된 resolved 항목부터 정리
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "5.7.
|
|
3
|
+
"version": "5.7.2",
|
|
4
4
|
"description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator chain (Code-Quality → Functional → Visual), optional Brainstormer. Supports React, Next.js, and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-gotcha-memory.sh — Gotcha
|
|
2
|
+
# harness-gotcha-memory.sh — Gotcha · Memory · Conventions pane (index view)
|
|
3
3
|
# 활성 에이전트의 gotcha를 최상단으로, 나머지는 요약(최근 3항목+총개수)으로 표시
|
|
4
|
+
# + Shared Memory 요약 + Conventions 요약 (shared + 활성 에이전트별)
|
|
4
5
|
# Usage: bash scripts/harness-gotcha-memory.sh [project-root]
|
|
5
6
|
|
|
6
7
|
set -uo pipefail
|
|
@@ -16,6 +17,7 @@ fi
|
|
|
16
17
|
|
|
17
18
|
MEMORY_FILE="$PROJECT_ROOT/.harness/memory.md"
|
|
18
19
|
GOTCHAS_DIR="$PROJECT_ROOT/.harness/gotchas"
|
|
20
|
+
CONVENTIONS_DIR="$PROJECT_ROOT/.harness/conventions"
|
|
19
21
|
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
20
22
|
|
|
21
23
|
BOLD="\033[1m"
|
|
@@ -45,6 +47,12 @@ compute_signature() {
|
|
|
45
47
|
stat -f '%N %m %z' "$f" 2>/dev/null
|
|
46
48
|
done
|
|
47
49
|
fi
|
|
50
|
+
if [ -d "$CONVENTIONS_DIR" ]; then
|
|
51
|
+
for f in "$CONVENTIONS_DIR"/*.md; do
|
|
52
|
+
[ -e "$f" ] || continue
|
|
53
|
+
stat -f '%N %m %z' "$f" 2>/dev/null
|
|
54
|
+
done
|
|
55
|
+
fi
|
|
48
56
|
} | md5 -q 2>/dev/null || echo "$RANDOM"
|
|
49
57
|
}
|
|
50
58
|
|
|
@@ -115,6 +123,55 @@ render_gotcha_entry() {
|
|
|
115
123
|
fi
|
|
116
124
|
}
|
|
117
125
|
|
|
126
|
+
render_conventions_summary() {
|
|
127
|
+
if [ ! -d "$CONVENTIONS_DIR" ]; then
|
|
128
|
+
echo -e "${DIM}(conventions/ 없음)${RESET}"
|
|
129
|
+
return
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
local active="$1"
|
|
133
|
+
local any=false
|
|
134
|
+
|
|
135
|
+
# Shared 먼저, 그다음 활성 에이전트, 그다음 나머지
|
|
136
|
+
local ordered_files=()
|
|
137
|
+
[ -f "$CONVENTIONS_DIR/shared.md" ] && ordered_files+=("$CONVENTIONS_DIR/shared.md")
|
|
138
|
+
if [ -n "$active" ] && [ -f "$CONVENTIONS_DIR/${active}.md" ]; then
|
|
139
|
+
ordered_files+=("$CONVENTIONS_DIR/${active}.md")
|
|
140
|
+
fi
|
|
141
|
+
for cand in "$CONVENTIONS_DIR"/*.md; do
|
|
142
|
+
[ -e "$cand" ] || continue
|
|
143
|
+
local base; base=$(basename "$cand" .md)
|
|
144
|
+
[ "$base" = "README" ] && continue
|
|
145
|
+
[ "$cand" = "$CONVENTIONS_DIR/shared.md" ] && continue
|
|
146
|
+
[ -n "$active" ] && [ "$cand" = "$CONVENTIONS_DIR/${active}.md" ] && continue
|
|
147
|
+
ordered_files+=("$cand")
|
|
148
|
+
done
|
|
149
|
+
|
|
150
|
+
for f in "${ordered_files[@]}"; do
|
|
151
|
+
[ -e "$f" ] || continue
|
|
152
|
+
local name count
|
|
153
|
+
name=$(basename "$f" .md)
|
|
154
|
+
count=$(count_items "$f")
|
|
155
|
+
if [ "$name" = "shared" ]; then
|
|
156
|
+
echo -e "${BOLD}${CYAN}▸ ${name}${RESET} ${DIM}(${count} items · shared)${RESET}"
|
|
157
|
+
elif [ -n "$active" ] && [ "$name" = "$active" ]; then
|
|
158
|
+
echo -e "${BOLD}${BG_YELLOW} ▸ ${name} ${RESET} ${DIM}(${count} items · ACTIVE)${RESET}"
|
|
159
|
+
else
|
|
160
|
+
echo -e "${BOLD}${GREEN}▸ ${name}${RESET} ${DIM}(${count} items)${RESET}"
|
|
161
|
+
fi
|
|
162
|
+
if [ "$count" -eq 0 ]; then
|
|
163
|
+
echo -e " ${DIM}(항목 없음)${RESET}"
|
|
164
|
+
else
|
|
165
|
+
recent_titles "$f" "$RECENT_N" | while IFS= read -r t; do
|
|
166
|
+
echo -e " ${DIM}·${RESET} $t"
|
|
167
|
+
done
|
|
168
|
+
fi
|
|
169
|
+
any=true
|
|
170
|
+
done
|
|
171
|
+
|
|
172
|
+
[ "$any" = "false" ] && echo -e "${DIM}(항목 없음)${RESET}"
|
|
173
|
+
}
|
|
174
|
+
|
|
118
175
|
render_memory_summary() {
|
|
119
176
|
if [ ! -f "$MEMORY_FILE" ]; then
|
|
120
177
|
echo -e "${DIM}(memory.md 없음)${RESET}"
|
|
@@ -145,7 +202,7 @@ render() {
|
|
|
145
202
|
active_file=""
|
|
146
203
|
[ -n "$active" ] && active_file=$(file_for_agent "$active")
|
|
147
204
|
|
|
148
|
-
echo -e "${BOLD}${MAGENTA}
|
|
205
|
+
echo -e "${BOLD}${MAGENTA}RULES INDEX${RESET} ${DIM}Gotcha · Memory · Conventions · $(date +%H:%M:%S) · recent=${RECENT_N} · refresh ${REFRESH_SEC}s${RESET}"
|
|
149
206
|
if [ -n "$active" ]; then
|
|
150
207
|
echo -e "${DIM}active agent:${RESET} ${BOLD}${YELLOW}${active}${RESET}"
|
|
151
208
|
else
|
|
@@ -171,6 +228,11 @@ render() {
|
|
|
171
228
|
done
|
|
172
229
|
fi
|
|
173
230
|
|
|
231
|
+
echo ""
|
|
232
|
+
echo -e "${DIM}${hr}${RESET}"
|
|
233
|
+
echo -e "${BOLD}${CYAN}▣ CONVENTIONS${RESET} ${DIM}(.harness/conventions)${RESET}"
|
|
234
|
+
render_conventions_summary "$active"
|
|
235
|
+
|
|
174
236
|
echo ""
|
|
175
237
|
echo -e "${DIM}${hr}${RESET}"
|
|
176
238
|
render_memory_summary
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-gotcha-register.sh — Evaluator → Gotcha 자동 등록 파이프라인
|
|
3
|
+
#
|
|
4
|
+
# 사용법:
|
|
5
|
+
# 1) 단일 항목 등록 (수동/스크립트):
|
|
6
|
+
# bash harness-gotcha-register.sh <project-root> \
|
|
7
|
+
# --target <agent> \
|
|
8
|
+
# --rule-id <rule-id> \
|
|
9
|
+
# --title "제목" \
|
|
10
|
+
# --wrong "잘못된 행동" \
|
|
11
|
+
# --right "올바른 행동" \
|
|
12
|
+
# --why "근거" \
|
|
13
|
+
# --scope "적용 범위" \
|
|
14
|
+
# --source "evaluator-functional:F-003"
|
|
15
|
+
#
|
|
16
|
+
# 2) 일괄 등록 (JSON stdin/파일):
|
|
17
|
+
# bash harness-gotcha-register.sh <project-root> --from-json <path>
|
|
18
|
+
# bash harness-gotcha-register.sh <project-root> --scan-evaluations
|
|
19
|
+
# └ .harness/actions/evaluation-*.md 의 ```gotcha_candidates``` 블록 전부 스캔
|
|
20
|
+
#
|
|
21
|
+
# 동작:
|
|
22
|
+
# - 대상 파일: .harness/gotchas/<target>.md (없으면 생성)
|
|
23
|
+
# - 중복 감지: 동일 rule_id 가 있으면 Occurrences +1, last_seen 갱신 (본문 수정 없음)
|
|
24
|
+
# - 신규: 다음 G-NNN 할당, Status: unverified 로 기록
|
|
25
|
+
# - 등록 성공 로그: .harness/progress.log 에 한 줄 append
|
|
26
|
+
#
|
|
27
|
+
# 등록 형식:
|
|
28
|
+
# ### [G-NNN] <title> <!-- rule_id: <rule-id> -->
|
|
29
|
+
# - **Status**: unverified
|
|
30
|
+
# - **Date**: YYYY-MM-DD
|
|
31
|
+
# - **Source**: <source> (예: "evaluator-functional:F-003")
|
|
32
|
+
# - **Trigger**: Eval 자동 감지
|
|
33
|
+
# - **Wrong**: <wrong>
|
|
34
|
+
# - **Right**: <right>
|
|
35
|
+
# - **Why**: <why>
|
|
36
|
+
# - **Scope**: <scope>
|
|
37
|
+
# - **Occurrences**: 1
|
|
38
|
+
# - **Last-Seen**: YYYY-MM-DD
|
|
39
|
+
|
|
40
|
+
set -euo pipefail
|
|
41
|
+
|
|
42
|
+
if [ $# -lt 1 ]; then
|
|
43
|
+
echo "usage: harness-gotcha-register.sh <project-root> [options]" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
PROJECT_ROOT="$1"
|
|
48
|
+
shift
|
|
49
|
+
PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
|
|
50
|
+
|
|
51
|
+
command -v jq >/dev/null 2>&1 || { echo "[gotcha-register] ERROR: jq required" >&2; exit 1; }
|
|
52
|
+
|
|
53
|
+
GOTCHAS_DIR="$PROJECT_ROOT/.harness/gotchas"
|
|
54
|
+
ACTIONS_DIR="$PROJECT_ROOT/.harness/actions"
|
|
55
|
+
mkdir -p "$GOTCHAS_DIR"
|
|
56
|
+
|
|
57
|
+
TODAY="$(date +%Y-%m-%d)"
|
|
58
|
+
|
|
59
|
+
# ─────────────────────────────────────────
|
|
60
|
+
# 단일 항목을 .harness/gotchas/<target>.md 에 append
|
|
61
|
+
# $1: target agent (generator-backend, generator-frontend, evaluator-functional, ...)
|
|
62
|
+
# $2: rule_id (dedup key)
|
|
63
|
+
# $3: title
|
|
64
|
+
# $4: wrong
|
|
65
|
+
# $5: right
|
|
66
|
+
# $6: why
|
|
67
|
+
# $7: scope
|
|
68
|
+
# $8: source
|
|
69
|
+
# ─────────────────────────────────────────
|
|
70
|
+
register_one() {
|
|
71
|
+
local target="$1" rule_id="$2" title="$3" wrong="$4" right="$5" why="$6" scope="$7" source="$8"
|
|
72
|
+
local file="$GOTCHAS_DIR/${target}.md"
|
|
73
|
+
|
|
74
|
+
# Ensure file exists with header
|
|
75
|
+
if [ ! -f "$file" ]; then
|
|
76
|
+
cat > "$file" <<EOF
|
|
77
|
+
# Gotchas — $target
|
|
78
|
+
|
|
79
|
+
> Dispatcher + Evaluator 가 관리. $target 은 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
80
|
+
> 신규 항목은 \`Status: unverified\` 로 기록되고, Planner 리뷰 후 \`verified\` 로 승격됩니다.
|
|
81
|
+
|
|
82
|
+
EOF
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Dedup by rule_id marker comment
|
|
86
|
+
if grep -qE "<!-- rule_id: ${rule_id} -->" "$file" 2>/dev/null; then
|
|
87
|
+
# Bump Occurrences + Last-Seen for matching block
|
|
88
|
+
awk -v rid="$rule_id" -v today="$TODAY" '
|
|
89
|
+
BEGIN { in_block = 0 }
|
|
90
|
+
/^### \[G-[0-9]+\].*<!-- rule_id: / {
|
|
91
|
+
in_block = (index($0, "rule_id: " rid " ") > 0 || index($0, "rule_id: " rid "\n") > 0 || $0 ~ ("rule_id: " rid " -->"))
|
|
92
|
+
}
|
|
93
|
+
in_block && /^- \*\*Occurrences\*\*:/ {
|
|
94
|
+
n = $NF + 0
|
|
95
|
+
print "- **Occurrences**: " (n + 1)
|
|
96
|
+
next
|
|
97
|
+
}
|
|
98
|
+
in_block && /^- \*\*Last-Seen\*\*:/ {
|
|
99
|
+
print "- **Last-Seen**: " today
|
|
100
|
+
next
|
|
101
|
+
}
|
|
102
|
+
/^### / && !/<!-- rule_id: / { in_block = 0 }
|
|
103
|
+
{ print }
|
|
104
|
+
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
|
105
|
+
echo "[gotcha-register] $target: dedup $rule_id (occurrence bumped)"
|
|
106
|
+
return 0
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Allocate next G-NNN (|| true — pipefail-safe when no existing entries)
|
|
110
|
+
local next_num=""
|
|
111
|
+
next_num=$(grep -oE '^### \[G-[0-9]+\]' "$file" 2>/dev/null | grep -oE '[0-9]+' | sort -n | tail -1 || true)
|
|
112
|
+
next_num=$((${next_num:-0} + 1))
|
|
113
|
+
local g_id
|
|
114
|
+
g_id=$(printf 'G-%03d' "$next_num")
|
|
115
|
+
|
|
116
|
+
# Append new block
|
|
117
|
+
{
|
|
118
|
+
echo ""
|
|
119
|
+
echo "### [$g_id] $title <!-- rule_id: $rule_id -->"
|
|
120
|
+
echo "- **Status**: unverified"
|
|
121
|
+
echo "- **Date**: $TODAY"
|
|
122
|
+
echo "- **Source**: $source"
|
|
123
|
+
echo "- **Trigger**: Eval 자동 감지"
|
|
124
|
+
echo "- **Wrong**: $wrong"
|
|
125
|
+
echo "- **Right**: $right"
|
|
126
|
+
echo "- **Why**: $why"
|
|
127
|
+
echo "- **Scope**: $scope"
|
|
128
|
+
echo "- **Occurrences**: 1"
|
|
129
|
+
echo "- **Last-Seen**: $TODAY"
|
|
130
|
+
} >> "$file"
|
|
131
|
+
|
|
132
|
+
echo "[gotcha-register] $target: registered $g_id ($rule_id) — unverified"
|
|
133
|
+
|
|
134
|
+
# Log to progress.log if present
|
|
135
|
+
local progress_log="$PROJECT_ROOT/.harness/progress.log"
|
|
136
|
+
if [ -f "$progress_log" ]; then
|
|
137
|
+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) [gotcha] $target $g_id unverified — $title (source: $source)" >> "$progress_log"
|
|
138
|
+
fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# ─────────────────────────────────────────
|
|
142
|
+
# JSON 배열에서 일괄 등록
|
|
143
|
+
# schema: [{ target, rule_id, title, wrong, right, why, scope, source }]
|
|
144
|
+
# ─────────────────────────────────────────
|
|
145
|
+
register_from_json() {
|
|
146
|
+
local json="$1"
|
|
147
|
+
local count
|
|
148
|
+
count=$(echo "$json" | jq 'length' 2>/dev/null || echo 0)
|
|
149
|
+
if [ "$count" -eq 0 ]; then
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
local i
|
|
154
|
+
for ((i=0; i<count; i++)); do
|
|
155
|
+
local t r ti w ri wh sc so
|
|
156
|
+
t=$(echo "$json" | jq -r ".[$i].target // empty")
|
|
157
|
+
r=$(echo "$json" | jq -r ".[$i].rule_id // empty")
|
|
158
|
+
ti=$(echo "$json" | jq -r ".[$i].title // empty")
|
|
159
|
+
w=$(echo "$json" | jq -r ".[$i].wrong // empty")
|
|
160
|
+
ri=$(echo "$json" | jq -r ".[$i].right // empty")
|
|
161
|
+
wh=$(echo "$json" | jq -r ".[$i].why // empty")
|
|
162
|
+
sc=$(echo "$json" | jq -r ".[$i].scope // \"항상\"")
|
|
163
|
+
so=$(echo "$json" | jq -r ".[$i].source // \"evaluator:auto\"")
|
|
164
|
+
|
|
165
|
+
if [ -z "$t" ] || [ -z "$r" ] || [ -z "$ti" ]; then
|
|
166
|
+
echo "[gotcha-register] skip: missing target/rule_id/title at index $i" >&2
|
|
167
|
+
continue
|
|
168
|
+
fi
|
|
169
|
+
register_one "$t" "$r" "$ti" "$w" "$ri" "$wh" "$sc" "$so"
|
|
170
|
+
done
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# ─────────────────────────────────────────
|
|
174
|
+
# evaluation-*.md 스캔 — ```gotcha_candidates ... ``` JSON fenced block 추출
|
|
175
|
+
# ─────────────────────────────────────────
|
|
176
|
+
scan_evaluations() {
|
|
177
|
+
if [ ! -d "$ACTIONS_DIR" ]; then return 0; fi
|
|
178
|
+
local f
|
|
179
|
+
for f in "$ACTIONS_DIR"/evaluation-*.md; do
|
|
180
|
+
[ -f "$f" ] || continue
|
|
181
|
+
|
|
182
|
+
# Enumerate blocks — simple per-file awk that prints each block into a
|
|
183
|
+
# uniquely-named temp file. Avoids macOS awk \0 quirks.
|
|
184
|
+
local blocks_prefix
|
|
185
|
+
blocks_prefix=$(mktemp -d)
|
|
186
|
+
awk -v dir="$blocks_prefix" '
|
|
187
|
+
BEGIN { idx=0 }
|
|
188
|
+
/^```gotcha_candidates[[:space:]]*$/ { flag=1; buf=""; next }
|
|
189
|
+
/^```[[:space:]]*$/ && flag {
|
|
190
|
+
flag=0
|
|
191
|
+
idx++
|
|
192
|
+
outfile = sprintf("%s/block-%03d.json", dir, idx)
|
|
193
|
+
print buf > outfile
|
|
194
|
+
close(outfile)
|
|
195
|
+
buf=""
|
|
196
|
+
next
|
|
197
|
+
}
|
|
198
|
+
flag { buf = buf $0 "\n" }
|
|
199
|
+
' "$f"
|
|
200
|
+
|
|
201
|
+
local blockfile
|
|
202
|
+
for blockfile in "$blocks_prefix"/block-*.json; do
|
|
203
|
+
[ -f "$blockfile" ] || continue
|
|
204
|
+
if jq empty "$blockfile" 2>/dev/null; then
|
|
205
|
+
register_from_json "$(cat "$blockfile")"
|
|
206
|
+
else
|
|
207
|
+
echo "[gotcha-register] skip: invalid JSON block in $(basename "$f")" >&2
|
|
208
|
+
fi
|
|
209
|
+
done
|
|
210
|
+
rm -rf "$blocks_prefix"
|
|
211
|
+
done
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# ─────────────────────────────────────────
|
|
215
|
+
# Parse CLI
|
|
216
|
+
# ─────────────────────────────────────────
|
|
217
|
+
TARGET=""
|
|
218
|
+
RULE_ID=""
|
|
219
|
+
TITLE=""
|
|
220
|
+
WRONG=""
|
|
221
|
+
RIGHT=""
|
|
222
|
+
WHY=""
|
|
223
|
+
SCOPE="항상"
|
|
224
|
+
SOURCE="evaluator:auto"
|
|
225
|
+
MODE="single"
|
|
226
|
+
FROM_JSON=""
|
|
227
|
+
|
|
228
|
+
while [ $# -gt 0 ]; do
|
|
229
|
+
case "$1" in
|
|
230
|
+
--target) TARGET="$2"; shift 2 ;;
|
|
231
|
+
--rule-id) RULE_ID="$2"; shift 2 ;;
|
|
232
|
+
--title) TITLE="$2"; shift 2 ;;
|
|
233
|
+
--wrong) WRONG="$2"; shift 2 ;;
|
|
234
|
+
--right) RIGHT="$2"; shift 2 ;;
|
|
235
|
+
--why) WHY="$2"; shift 2 ;;
|
|
236
|
+
--scope) SCOPE="$2"; shift 2 ;;
|
|
237
|
+
--source) SOURCE="$2"; shift 2 ;;
|
|
238
|
+
--from-json) MODE="json"; FROM_JSON="$2"; shift 2 ;;
|
|
239
|
+
--scan-evaluations) MODE="scan"; shift ;;
|
|
240
|
+
*) echo "[gotcha-register] unknown arg: $1" >&2; exit 1 ;;
|
|
241
|
+
esac
|
|
242
|
+
done
|
|
243
|
+
|
|
244
|
+
case "$MODE" in
|
|
245
|
+
single)
|
|
246
|
+
if [ -z "$TARGET" ] || [ -z "$RULE_ID" ] || [ -z "$TITLE" ]; then
|
|
247
|
+
echo "[gotcha-register] usage: --target X --rule-id Y --title Z [...]" >&2
|
|
248
|
+
exit 1
|
|
249
|
+
fi
|
|
250
|
+
register_one "$TARGET" "$RULE_ID" "$TITLE" "$WRONG" "$RIGHT" "$WHY" "$SCOPE" "$SOURCE"
|
|
251
|
+
;;
|
|
252
|
+
json)
|
|
253
|
+
if [ ! -f "$FROM_JSON" ]; then echo "[gotcha-register] file not found: $FROM_JSON" >&2; exit 1; fi
|
|
254
|
+
register_from_json "$(cat "$FROM_JSON")"
|
|
255
|
+
;;
|
|
256
|
+
scan)
|
|
257
|
+
scan_evaluations
|
|
258
|
+
;;
|
|
259
|
+
esac
|
package/scripts/harness-next.sh
CHANGED
|
@@ -314,6 +314,23 @@ if [ "$agent_status" = "completed" ]; then
|
|
|
314
314
|
verify_file_ownership "$PROJECT_ROOT" || audit_gate "file-ownership" "warn" "boundary violation detected"
|
|
315
315
|
fi
|
|
316
316
|
|
|
317
|
+
# ─────────────────────────────────────────
|
|
318
|
+
# Auto Gotcha Registration — Evaluator 완료 직후 evaluation-*.md 스캔
|
|
319
|
+
# evaluation-*.md 내부의 ```gotcha_candidates``` JSON 블록을 읽어
|
|
320
|
+
# .harness/gotchas/<target>.md 에 unverified 상태로 등록/dedup.
|
|
321
|
+
# ─────────────────────────────────────────
|
|
322
|
+
case "$current_agent" in
|
|
323
|
+
evaluator-*)
|
|
324
|
+
if [ "$agent_status" = "completed" ] || [ "$agent_status" = "failed" ]; then
|
|
325
|
+
if [ -x "$SCRIPT_DIR/harness-gotcha-register.sh" ]; then
|
|
326
|
+
bash "$SCRIPT_DIR/harness-gotcha-register.sh" "$PROJECT_ROOT" --scan-evaluations 2>&1 \
|
|
327
|
+
| grep -E '^\[gotcha-register\]' || true
|
|
328
|
+
audit_gate "gotcha-register" "scan" "$current_agent"
|
|
329
|
+
fi
|
|
330
|
+
fi
|
|
331
|
+
;;
|
|
332
|
+
esac
|
|
333
|
+
|
|
317
334
|
# ─────────────────────────────────────────
|
|
318
335
|
# Run Pre-Eval Gate (if applicable)
|
|
319
336
|
# ─────────────────────────────────────────
|
package/scripts/harness-tmux.sh
CHANGED
|
@@ -199,7 +199,7 @@ launch_tmux_team() {
|
|
|
199
199
|
|
|
200
200
|
tmux select-pane -t "$PANE_DASH" -T "Dashboard"
|
|
201
201
|
tmux select-pane -t "$PANE_ARCHIVE" -T "Archive Prompt"
|
|
202
|
-
tmux select-pane -t "$PANE_GOTCHA" -T "Gotcha
|
|
202
|
+
tmux select-pane -t "$PANE_GOTCHA" -T "Gotcha · Memory · Conventions"
|
|
203
203
|
tmux select-pane -t "$PANE_T1" -T "TEAM 1"
|
|
204
204
|
tmux select-pane -t "$PANE_T2" -T "TEAM 2"
|
|
205
205
|
tmux select-pane -t "$PANE_T3" -T "TEAM 3"
|
|
@@ -152,6 +152,28 @@ AGENTS.md 비하네스 → 기존 백업 + 리빌드
|
|
|
152
152
|
|
|
153
153
|
`.harness/actions/pipeline.json` 생성 → 사용자 확인 → Session Boundary Protocol On Complete 실행
|
|
154
154
|
|
|
155
|
+
### Mode Recommendation (v5.7.1+)
|
|
156
|
+
|
|
157
|
+
⚠️ **Dispatcher → Planner 전환 시에는 mode 질문을 하지 않는다.** Planner 는 mode 와 무관한 단일 실행이다. 과거의 "harness-solo 를 입력하세요" 안내는 제거.
|
|
158
|
+
|
|
159
|
+
파이프라인이 확정되면 (Planner 호출 직전) **단 한 문단** 으로 Mode 추천을 출력하되, 응답을 기다리지 않고 **default=solo 로 그대로 진행**한다. 사용자가 team 을 원하면 언제든 `/harness-team` 으로 전환 가능.
|
|
160
|
+
|
|
161
|
+
추천 로직:
|
|
162
|
+
- Planner 가 feature-list.json 을 확정한 뒤 `features.length >= 3` 이고 서로 의존성이 낮으면 → "Team 모드 권장" 안내
|
|
163
|
+
- `features.length < 3` 또는 단일 feature 연속 작업 → "Solo 모드 권장" 안내
|
|
164
|
+
- Dispatcher 단계에서는 feature 수를 모를 수 있으므로 **기본은 Solo 진행**, Planner 완료 후 자동으로 재평가
|
|
165
|
+
|
|
166
|
+
출력 예:
|
|
167
|
+
```
|
|
168
|
+
Pipeline: FULLSTACK 확정. Solo 모드로 자동 진행합니다.
|
|
169
|
+
(병렬 3팀 실행을 원하면 Planner 완료 후 /harness-team 입력)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**금지**:
|
|
173
|
+
- "solo 입력하세요 / team 입력하세요" 식의 선택 강요
|
|
174
|
+
- mode 결정을 기다리며 Planner 호출을 보류하는 것
|
|
175
|
+
- 이미 mode 가 설정된 상태(`progress.json.mode` 존재)에서 재질문하는 것
|
|
176
|
+
|
|
155
177
|
### evaluator_chain 필드 (모든 파이프라인 필수)
|
|
156
178
|
|
|
157
179
|
`pipeline.json` 에 **`evaluator_chain`** 배열을 기록한다. `config.json.flow.pipeline_selection.evaluator_chains.<pipeline>` 의 값을 복사:
|
|
@@ -158,6 +158,34 @@ Cross-Validation 데이터 블록 포함 (Functional/Visual 이 참조):
|
|
|
158
158
|
}
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
## Auto Gotcha Registration — v5.7.1+
|
|
162
|
+
|
|
163
|
+
**필수 emission**: `actions/evaluation-code-quality.md` 끝부분에 `gotcha_candidates` JSON 블록을 반드시 포함 (후보 없으면 `[]`). `harness-next.sh` 가 Evaluator 완료 직후 이 블록을 스캔해 자동 등록한다.
|
|
164
|
+
|
|
165
|
+
````
|
|
166
|
+
```gotcha_candidates
|
|
167
|
+
[
|
|
168
|
+
{
|
|
169
|
+
"target": "generator-backend",
|
|
170
|
+
"rule_id": "be-any-type-leak",
|
|
171
|
+
"title": "서비스 레이어 any 남용",
|
|
172
|
+
"wrong": "UserService.findAll() 반환 타입을 any[] 로 선언",
|
|
173
|
+
"right": "api-contract.json 의 DTO 타입을 재사용하거나 shared-dto 에 정의",
|
|
174
|
+
"why": "C4 Type Safety 축은 25% 가중. evidence 없는 any 는 Score 0.",
|
|
175
|
+
"scope": "모든 BE service/controller 레이어",
|
|
176
|
+
"source": "evaluator-code-quality:F-002"
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
```
|
|
180
|
+
````
|
|
181
|
+
|
|
182
|
+
등록 규칙:
|
|
183
|
+
- `target`: 실수를 반복할 대상 에이전트 (`generator-backend`, `generator-frontend`, `planner` 등).
|
|
184
|
+
- `rule_id`: dedup 키 (동일 rule_id 는 Occurrences +1, 본문 미변경).
|
|
185
|
+
- 신규 항목은 `Status: unverified`. Planner 리뷰 후 `verified` 승격.
|
|
186
|
+
- **FAIL 시**: 실패 근본 원인 1건 이상을 반드시 등록.
|
|
187
|
+
- **PASS 시**: 발견된 경미한 위반이 있으면 등록 (스코어 미반영이지만 반복 방지).
|
|
188
|
+
|
|
161
189
|
## Adversarial Rules
|
|
162
190
|
|
|
163
191
|
- "동작하니 PASS" 금지. 여기서는 구조를 본다.
|
|
@@ -137,16 +137,38 @@ Evaluator 는 스택마다 다른 검증 도구를 가진다. `scan-result.json.
|
|
|
137
137
|
- false → evaluation-functional.md 에 "MANUAL_REQUIRED: {manual_check}" 기록, Visual 은 __skip__
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
### Auto Gotcha Registration (안티패턴 자동 등록)
|
|
140
|
+
### Auto Gotcha Registration (안티패턴 + 평가 실패 자동 등록) — v5.7.1+
|
|
141
|
+
|
|
142
|
+
**필수 emission**: `actions/evaluation-functional.md` 끝부분에 아래 fenced JSON 블록을 반드시 포함한다 (후보 없으면 빈 배열 `[]`). `harness-next.sh` 가 Evaluator 완료 직후 이 블록을 스캔해 `scripts/harness-gotcha-register.sh` 로 자동 등록한다.
|
|
143
|
+
|
|
144
|
+
````
|
|
145
|
+
```gotcha_candidates
|
|
146
|
+
[
|
|
147
|
+
{
|
|
148
|
+
"target": "generator-frontend",
|
|
149
|
+
"rule_id": "fe-console-error-ignored",
|
|
150
|
+
"title": "콘솔 JS 에러 방치",
|
|
151
|
+
"wrong": "렌더 직후 발생하는 TypeError 를 수정하지 않고 PASS 주장",
|
|
152
|
+
"right": "콘솔 JS 에러 0 이 될 때까지 수정 후 재제출",
|
|
153
|
+
"why": "Evaluator-Functional 콘솔 청결 축은 15% 가중 하드 임계 (0개)",
|
|
154
|
+
"scope": "모든 FE Feature",
|
|
155
|
+
"source": "evaluator-functional:F-003"
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
```
|
|
159
|
+
````
|
|
141
160
|
|
|
142
|
-
|
|
161
|
+
등록 규칙:
|
|
162
|
+
- `target`: 실수를 반복할 **대상 에이전트** (예: `generator-frontend`, `generator-backend`, `planner`). 본인(`evaluator-*`) 대상도 가능.
|
|
163
|
+
- `rule_id`: 전역 유일 식별자. 동일 rule_id 는 Occurrences 증가 + Last-Seen 갱신 (본문 미변경).
|
|
164
|
+
- `source`: 출처 — `<agent>:<feature-id>` 형식 권장.
|
|
165
|
+
- 신규 항목은 `Status: unverified` 로 기록. Planner 리뷰 후 수동으로 `verified` 승격.
|
|
166
|
+
- 대상 파일: `.harness/gotchas/<target>.md` (스택별 파일 필요 시 `<target>-<stack>.md` 를 `target` 에 명시).
|
|
143
167
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
- 중복 rule_id: Occurrences 카운터 +1 + last_seen 업데이트
|
|
147
|
-
- 상세 계약: `api-contract.json.contracts["gotcha_register_interface"]`
|
|
168
|
+
**FAIL 시**: 실패 근본 원인 1건 이상을 반드시 candidate 로 등록 (중복 실수 방지가 목적).
|
|
169
|
+
**PASS 시**: 발견된 안티패턴/경미한 위반이 있으면 등록 (스코어에는 반영 안 됐지만 반복 방지).
|
|
148
170
|
|
|
149
|
-
|
|
171
|
+
Dispatcher 경유 수동 등록은 여전히 가능하지만, **이 자동 파이프라인이 기본 경로**다.
|
|
150
172
|
|
|
151
173
|
## Evaluation Steps
|
|
152
174
|
|
|
@@ -121,6 +121,34 @@ jq '.agent_status = "completed" | .completed_agents += ["planner"]' .harness/p
|
|
|
121
121
|
|
|
122
122
|
**어떤 차원이든 하드 임계값 미달 → FAIL**
|
|
123
123
|
|
|
124
|
+
## Auto Gotcha Registration — v5.7.1+
|
|
125
|
+
|
|
126
|
+
**필수 emission**: `actions/evaluation-visual.md` 끝부분에 `gotcha_candidates` JSON 블록을 반드시 포함 (후보 없으면 `[]`). `harness-next.sh` 가 Evaluator 완료 직후 이 블록을 스캔해 자동 등록한다.
|
|
127
|
+
|
|
128
|
+
````
|
|
129
|
+
```gotcha_candidates
|
|
130
|
+
[
|
|
131
|
+
{
|
|
132
|
+
"target": "generator-frontend",
|
|
133
|
+
"rule_id": "fe-contrast-fail",
|
|
134
|
+
"title": "텍스트 대비 WCAG AA 미달",
|
|
135
|
+
"wrong": "버튼 #888 on #aaa — 3.0:1 (AA 4.5:1 미만)",
|
|
136
|
+
"right": "primary 버튼은 body 컬러 대비 4.5:1 이상 보장. Tailwind tokens 활용.",
|
|
137
|
+
"why": "접근성은 Evaluator-Visual 하드 임계",
|
|
138
|
+
"scope": "모든 인터랙티브 엘리먼트",
|
|
139
|
+
"source": "evaluator-visual:F-004"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
```
|
|
143
|
+
````
|
|
144
|
+
|
|
145
|
+
등록 규칙:
|
|
146
|
+
- `target`: 실수를 반복할 대상 에이전트 (일반적으로 `generator-frontend`, 디자인 토큰 결함은 `planner`).
|
|
147
|
+
- `rule_id`: dedup 키.
|
|
148
|
+
- 신규 항목은 `Status: unverified`. Planner 리뷰 후 `verified` 승격.
|
|
149
|
+
- **FAIL 시**: 실패 근본 원인 1건 이상 반드시 등록.
|
|
150
|
+
- **PASS 시**: 감지된 경미한 디자인 편차가 있으면 등록 (반복 방지).
|
|
151
|
+
|
|
124
152
|
## After Evaluation
|
|
125
153
|
|
|
126
154
|
- **PASS** → Session Boundary Protocol On Complete (PASS) 실행
|