@walwal-harness/cli 5.8.2 → 5.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/harness-team.md +10 -1
- package/package.json +1 -1
- package/scripts/harness-gotcha-memory.sh +107 -21
- package/scripts/harness-gotcha-register.sh +165 -31
- package/scripts/harness-next.sh +5 -3
- package/scripts/harness-tmux.sh +23 -6
- package/skills/_shared/dynamic-registration.md +120 -0
- package/skills/evaluator-code-quality/SKILL.md +10 -0
- package/skills/evaluator-functional/SKILL.md +65 -0
- package/skills/evaluator-visual/SKILL.md +10 -0
- package/skills/generator-backend/SKILL.md +32 -0
- package/skills/generator-frontend/SKILL.md +10 -0
package/commands/harness-team.md
CHANGED
|
@@ -197,7 +197,16 @@ bash scripts/harness-queue-manager.sh fail {FEATURE_ID} .
|
|
|
197
197
|
git merge {BRANCH_NAME} --no-edit
|
|
198
198
|
```
|
|
199
199
|
- 충돌 시: 자동 해결 시도 → 실패 시 사용자 개입 요청
|
|
200
|
-
3.
|
|
200
|
+
3. **동적 Gotcha/Convention 등록** (merge 직후 필수):
|
|
201
|
+
```bash
|
|
202
|
+
# worker 가 작성한 evaluation-*.md / gen-report-*.md 의
|
|
203
|
+
# gotcha_candidates / convention_candidates 블록을 모두 dedup append
|
|
204
|
+
bash scripts/harness-gotcha-register.sh . --scan-all
|
|
205
|
+
```
|
|
206
|
+
다음 worker spawn 전에 갱신된 gotchas/conventions 가 file system 에 반영되어야 함.
|
|
207
|
+
실수가 sprint 중에 등록되지 않으면 다음 worker 가 같은 실수 반복.
|
|
208
|
+
|
|
209
|
+
4. **Queue 업데이트** (unblock 포함):
|
|
201
210
|
```bash
|
|
202
211
|
bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .
|
|
203
212
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.1",
|
|
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"
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
# harness-gotcha-memory.sh — Gotcha · Memory · Conventions pane (index view)
|
|
3
3
|
# 활성 에이전트의 gotcha를 최상단으로, 나머지는 요약(최근 3항목+총개수)으로 표시
|
|
4
4
|
# + Shared Memory 요약 + Conventions 요약 (shared + 활성 에이전트별)
|
|
5
|
-
#
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bash scripts/harness-gotcha-memory.sh [project-root] # 전체 (legacy)
|
|
8
|
+
# bash scripts/harness-gotcha-memory.sh [project-root] --mode gotcha # gotcha 만
|
|
9
|
+
# bash scripts/harness-gotcha-memory.sh [project-root] --mode conventions # conventions 만
|
|
10
|
+
# bash scripts/harness-gotcha-memory.sh [project-root] --mode memory # memory 만
|
|
11
|
+
#
|
|
12
|
+
# tmux 분할 환경에서는 각 sub-pane 이 한 mode 만 렌더 → 좁은 창에서도 잘리지 않음.
|
|
13
|
+
# tmux copy-mode (prefix + [) 로 각 pane 독립 스크롤.
|
|
6
14
|
|
|
7
15
|
set -uo pipefail
|
|
8
16
|
|
|
@@ -10,11 +18,26 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
10
18
|
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
11
19
|
source "$SCRIPT_DIR/lib/harness-keywait.sh"
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
# Args parsing — first non-flag = project root, --mode = view mode
|
|
22
|
+
PROJECT_ROOT=""
|
|
23
|
+
MODE="all"
|
|
24
|
+
while [ $# -gt 0 ]; do
|
|
25
|
+
case "$1" in
|
|
26
|
+
--mode) MODE="$2"; shift 2 ;;
|
|
27
|
+
--mode=*) MODE="${1#--mode=}"; shift ;;
|
|
28
|
+
*) [ -z "$PROJECT_ROOT" ] && PROJECT_ROOT="$1"; shift ;;
|
|
29
|
+
esac
|
|
30
|
+
done
|
|
31
|
+
|
|
14
32
|
if [ -z "$PROJECT_ROOT" ]; then
|
|
15
33
|
PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[gotcha] .harness/ not found."; exit 1; }
|
|
16
34
|
fi
|
|
17
35
|
|
|
36
|
+
case "$MODE" in
|
|
37
|
+
all|gotcha|conventions|memory) ;;
|
|
38
|
+
*) echo "[gotcha] unknown mode: $MODE" >&2; exit 1 ;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
18
41
|
MEMORY_FILE="$PROJECT_ROOT/.harness/memory.md"
|
|
19
42
|
GOTCHAS_DIR="$PROJECT_ROOT/.harness/gotchas"
|
|
20
43
|
CONVENTIONS_DIR="$PROJECT_ROOT/.harness/conventions"
|
|
@@ -190,54 +213,117 @@ render_memory_summary() {
|
|
|
190
213
|
fi
|
|
191
214
|
}
|
|
192
215
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
printf '\033[H\033[2J\033[3J'
|
|
216
|
+
render_header() {
|
|
217
|
+
local title="$1"
|
|
196
218
|
local cols hr
|
|
197
219
|
cols=$(tput cols 2>/dev/null || echo 80)
|
|
198
220
|
hr=$(printf '─%.0s' $(seq 1 "$cols"))
|
|
221
|
+
echo -e "${BOLD}${MAGENTA}${title}${RESET} ${DIM}$(date +%H:%M:%S) · recent=${RECENT_N} · refresh ${REFRESH_SEC}s${RESET}"
|
|
222
|
+
local active="$1_active"
|
|
223
|
+
[ -n "${2:-}" ] && echo -e "${DIM}active agent:${RESET} ${BOLD}${YELLOW}$2${RESET}"
|
|
224
|
+
echo -e "${DIM}${hr}${RESET}"
|
|
225
|
+
}
|
|
199
226
|
|
|
200
|
-
|
|
201
|
-
active
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
echo -e "${BOLD}${MAGENTA}
|
|
227
|
+
render_gotcha_section() {
|
|
228
|
+
local active="$1" active_file="$2"
|
|
229
|
+
local cols hr
|
|
230
|
+
cols=$(tput cols 2>/dev/null || echo 80)
|
|
231
|
+
hr=$(printf '─%.0s' $(seq 1 "$cols"))
|
|
232
|
+
echo -e "${BOLD}${MAGENTA}▣ GOTCHAS${RESET} ${DIM}(.harness/gotchas) · $(date +%H:%M:%S) · refresh ${REFRESH_SEC}s${RESET}"
|
|
206
233
|
if [ -n "$active" ]; then
|
|
207
234
|
echo -e "${DIM}active agent:${RESET} ${BOLD}${YELLOW}${active}${RESET}"
|
|
208
|
-
else
|
|
209
|
-
echo -e "${DIM}active agent: (none)${RESET}"
|
|
210
235
|
fi
|
|
211
236
|
echo -e "${DIM}${hr}${RESET}"
|
|
212
237
|
|
|
213
|
-
# 활성 에이전트 gotcha 최상단
|
|
214
238
|
if [ -n "$active_file" ]; then
|
|
215
239
|
render_gotcha_entry "$active_file" "true"
|
|
216
240
|
echo ""
|
|
217
241
|
fi
|
|
218
|
-
|
|
219
|
-
# 나머지 gotcha
|
|
220
242
|
if [ -d "$GOTCHAS_DIR" ]; then
|
|
221
243
|
for f in "$GOTCHAS_DIR"/*.md; do
|
|
222
244
|
[ -e "$f" ] || continue
|
|
223
245
|
[ "$f" = "$active_file" ] && continue
|
|
224
|
-
local base
|
|
225
|
-
base=$(basename "$f" .md)
|
|
246
|
+
local base; base=$(basename "$f" .md)
|
|
226
247
|
[ "$base" = "README" ] && continue
|
|
227
248
|
render_gotcha_entry "$f" "false"
|
|
228
249
|
done
|
|
229
250
|
fi
|
|
251
|
+
}
|
|
230
252
|
|
|
231
|
-
|
|
253
|
+
render_conventions_section() {
|
|
254
|
+
local active="$1"
|
|
255
|
+
local cols hr
|
|
256
|
+
cols=$(tput cols 2>/dev/null || echo 80)
|
|
257
|
+
hr=$(printf '─%.0s' $(seq 1 "$cols"))
|
|
258
|
+
echo -e "${BOLD}${CYAN}▣ CONVENTIONS${RESET} ${DIM}(.harness/conventions) · $(date +%H:%M:%S) · refresh ${REFRESH_SEC}s${RESET}"
|
|
232
259
|
echo -e "${DIM}${hr}${RESET}"
|
|
233
|
-
echo -e "${BOLD}${CYAN}▣ CONVENTIONS${RESET} ${DIM}(.harness/conventions)${RESET}"
|
|
234
260
|
render_conventions_summary "$active"
|
|
261
|
+
}
|
|
235
262
|
|
|
236
|
-
|
|
263
|
+
render_memory_section() {
|
|
264
|
+
local cols hr
|
|
265
|
+
cols=$(tput cols 2>/dev/null || echo 80)
|
|
266
|
+
hr=$(printf '─%.0s' $(seq 1 "$cols"))
|
|
267
|
+
echo -e "${BOLD}${CYAN}▣ SHARED MEMORY${RESET} ${DIM}(memory.md) · $(date +%H:%M:%S) · refresh ${REFRESH_SEC}s${RESET}"
|
|
237
268
|
echo -e "${DIM}${hr}${RESET}"
|
|
238
269
|
render_memory_summary
|
|
239
270
|
}
|
|
240
271
|
|
|
272
|
+
render() {
|
|
273
|
+
# clear 대신 커서 홈 + 화면 끝까지 지우기 — 깜빡임 최소화
|
|
274
|
+
printf '\033[H\033[2J\033[3J'
|
|
275
|
+
|
|
276
|
+
local active active_file
|
|
277
|
+
active=$(get_active_agent)
|
|
278
|
+
active_file=""
|
|
279
|
+
[ -n "$active" ] && active_file=$(file_for_agent "$active")
|
|
280
|
+
|
|
281
|
+
case "$MODE" in
|
|
282
|
+
gotcha)
|
|
283
|
+
render_gotcha_section "$active" "$active_file"
|
|
284
|
+
;;
|
|
285
|
+
conventions)
|
|
286
|
+
render_conventions_section "$active"
|
|
287
|
+
;;
|
|
288
|
+
memory)
|
|
289
|
+
render_memory_section
|
|
290
|
+
;;
|
|
291
|
+
all)
|
|
292
|
+
local cols hr
|
|
293
|
+
cols=$(tput cols 2>/dev/null || echo 80)
|
|
294
|
+
hr=$(printf '─%.0s' $(seq 1 "$cols"))
|
|
295
|
+
echo -e "${BOLD}${MAGENTA}RULES INDEX${RESET} ${DIM}Gotcha · Memory · Conventions · $(date +%H:%M:%S) · recent=${RECENT_N} · refresh ${REFRESH_SEC}s${RESET}"
|
|
296
|
+
if [ -n "$active" ]; then
|
|
297
|
+
echo -e "${DIM}active agent:${RESET} ${BOLD}${YELLOW}${active}${RESET}"
|
|
298
|
+
else
|
|
299
|
+
echo -e "${DIM}active agent: (none)${RESET}"
|
|
300
|
+
fi
|
|
301
|
+
echo -e "${DIM}${hr}${RESET}"
|
|
302
|
+
|
|
303
|
+
if [ -n "$active_file" ]; then
|
|
304
|
+
render_gotcha_entry "$active_file" "true"
|
|
305
|
+
echo ""
|
|
306
|
+
fi
|
|
307
|
+
if [ -d "$GOTCHAS_DIR" ]; then
|
|
308
|
+
for f in "$GOTCHAS_DIR"/*.md; do
|
|
309
|
+
[ -e "$f" ] || continue
|
|
310
|
+
[ "$f" = "$active_file" ] && continue
|
|
311
|
+
local base; base=$(basename "$f" .md)
|
|
312
|
+
[ "$base" = "README" ] && continue
|
|
313
|
+
render_gotcha_entry "$f" "false"
|
|
314
|
+
done
|
|
315
|
+
fi
|
|
316
|
+
echo ""
|
|
317
|
+
echo -e "${DIM}${hr}${RESET}"
|
|
318
|
+
echo -e "${BOLD}${CYAN}▣ CONVENTIONS${RESET} ${DIM}(.harness/conventions)${RESET}"
|
|
319
|
+
render_conventions_summary "$active"
|
|
320
|
+
echo ""
|
|
321
|
+
echo -e "${DIM}${hr}${RESET}"
|
|
322
|
+
render_memory_summary
|
|
323
|
+
;;
|
|
324
|
+
esac
|
|
325
|
+
}
|
|
326
|
+
|
|
241
327
|
trap 'exit 0' INT TERM
|
|
242
328
|
|
|
243
329
|
while true; do
|
|
@@ -171,43 +171,173 @@ register_from_json() {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
# ─────────────────────────────────────────
|
|
174
|
-
#
|
|
174
|
+
# Convention 등록 — .harness/conventions/<scope>.md 에 [C-NNN] entry append
|
|
175
|
+
# $1: scope (shared, generator-backend, generator-frontend, ... — 파일명 base)
|
|
176
|
+
# $2: rule_id (dedup key)
|
|
177
|
+
# $3: title
|
|
178
|
+
# $4: rule (긍정 가이드 본문 — "X 는 항상 Y 로")
|
|
179
|
+
# $5: why
|
|
180
|
+
# $6: source
|
|
181
|
+
# ─────────────────────────────────────────
|
|
182
|
+
CONVENTIONS_DIR="$PROJECT_ROOT/.harness/conventions"
|
|
183
|
+
register_convention_one() {
|
|
184
|
+
local scope="$1" rule_id="$2" title="$3" rule="$4" why="$5" source="$6"
|
|
185
|
+
mkdir -p "$CONVENTIONS_DIR"
|
|
186
|
+
local file="$CONVENTIONS_DIR/${scope}.md"
|
|
187
|
+
|
|
188
|
+
if [ ! -f "$file" ]; then
|
|
189
|
+
cat > "$file" <<EOF
|
|
190
|
+
# Conventions — ${scope}
|
|
191
|
+
|
|
192
|
+
> Dispatcher가 관리. 긍정 가이드("~를 사용해", "항상 ~") 자동 등록.
|
|
193
|
+
|
|
194
|
+
EOF
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# dedup: 같은 rule_id 가 있으면 해당 [C-NNN] 블록의 Occurrences +1, Last-Seen 갱신
|
|
198
|
+
if grep -qE "<!-- rule_id: ${rule_id} -->" "$file" 2>/dev/null; then
|
|
199
|
+
awk -v rid="$rule_id" -v today="$TODAY" '
|
|
200
|
+
BEGIN { in_block = 0 }
|
|
201
|
+
/^### \[C-[0-9]+\].*<!-- rule_id: / {
|
|
202
|
+
in_block = ($0 ~ ("rule_id: " rid " -->"))
|
|
203
|
+
}
|
|
204
|
+
in_block && /^- \*\*Occurrences\*\*:/ {
|
|
205
|
+
n = $NF + 0
|
|
206
|
+
print "- **Occurrences**: " (n + 1)
|
|
207
|
+
next
|
|
208
|
+
}
|
|
209
|
+
in_block && /^- \*\*Last-Seen\*\*:/ {
|
|
210
|
+
print "- **Last-Seen**: " today
|
|
211
|
+
next
|
|
212
|
+
}
|
|
213
|
+
/^### / && !/<!-- rule_id: / { in_block = 0 }
|
|
214
|
+
{ print }
|
|
215
|
+
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
|
216
|
+
echo "[gotcha-register] convention/${scope}: dedup ${rule_id} (occurrence bumped)"
|
|
217
|
+
return 0
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# 신규 — 다음 [C-NNN] 번호 할당 (set -e 회피: || true)
|
|
221
|
+
local last_n
|
|
222
|
+
last_n=$(grep -oE '\[C-[0-9]+\]' "$file" 2>/dev/null | grep -oE '[0-9]+' | sort -n | tail -1 || true)
|
|
223
|
+
last_n=${last_n:-0}
|
|
224
|
+
local next_n=$(printf "%03d" $((last_n + 1)))
|
|
225
|
+
|
|
226
|
+
cat >> "$file" <<EOF
|
|
227
|
+
|
|
228
|
+
### [C-${next_n}] ${title} <!-- rule_id: ${rule_id} -->
|
|
229
|
+
- **Status**: unverified
|
|
230
|
+
- **Date**: ${TODAY}
|
|
231
|
+
- **Source**: ${source}
|
|
232
|
+
- **Rule**: ${rule}
|
|
233
|
+
- **Why**: ${why}
|
|
234
|
+
- **Occurrences**: 1
|
|
235
|
+
- **Last-Seen**: ${TODAY}
|
|
236
|
+
EOF
|
|
237
|
+
|
|
238
|
+
echo "[gotcha-register] convention/${scope}: registered C-${next_n} (${rule_id}) — unverified"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
register_conventions_from_json() {
|
|
242
|
+
local json="$1"
|
|
243
|
+
local count
|
|
244
|
+
count=$(echo "$json" | jq 'length' 2>/dev/null || echo 0)
|
|
245
|
+
[ "$count" -eq 0 ] && return 0
|
|
246
|
+
local i
|
|
247
|
+
for ((i=0; i<count; i++)); do
|
|
248
|
+
local sc r ti ru wh so
|
|
249
|
+
sc=$(echo "$json" | jq -r ".[$i].scope // \"shared\"")
|
|
250
|
+
r=$(echo "$json" | jq -r ".[$i].rule_id // empty")
|
|
251
|
+
ti=$(echo "$json" | jq -r ".[$i].title // empty")
|
|
252
|
+
ru=$(echo "$json" | jq -r ".[$i].rule // empty")
|
|
253
|
+
wh=$(echo "$json" | jq -r ".[$i].why // empty")
|
|
254
|
+
so=$(echo "$json" | jq -r ".[$i].source // \"agent:auto\"")
|
|
255
|
+
if [ -z "$r" ] || [ -z "$ti" ] || [ -z "$ru" ]; then
|
|
256
|
+
echo "[gotcha-register] convention skip: missing rule_id/title/rule at index $i" >&2
|
|
257
|
+
continue
|
|
258
|
+
fi
|
|
259
|
+
register_convention_one "$sc" "$r" "$ti" "$ru" "$wh" "$so"
|
|
260
|
+
done
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# ─────────────────────────────────────────
|
|
264
|
+
# 단일 파일에서 fenced block 추출 (block_tag 인자로 종류 지정)
|
|
265
|
+
# $1: file path · $2: block tag (gotcha_candidates 또는 convention_candidates)
|
|
266
|
+
# stdout 으로 각 block JSON 을 한 줄씩 출력 (\0 구분자)
|
|
267
|
+
# ─────────────────────────────────────────
|
|
268
|
+
extract_blocks() {
|
|
269
|
+
local file="$1" tag="$2"
|
|
270
|
+
local blocks_prefix
|
|
271
|
+
blocks_prefix=$(mktemp -d)
|
|
272
|
+
awk -v dir="$blocks_prefix" -v tag="$tag" '
|
|
273
|
+
BEGIN { idx=0; flag=0; buf="" }
|
|
274
|
+
$0 ~ ("^```" tag "[[:space:]]*$") { flag=1; buf=""; next }
|
|
275
|
+
/^```[[:space:]]*$/ && flag {
|
|
276
|
+
flag=0
|
|
277
|
+
idx++
|
|
278
|
+
outfile = sprintf("%s/block-%03d.json", dir, idx)
|
|
279
|
+
print buf > outfile
|
|
280
|
+
close(outfile)
|
|
281
|
+
buf=""
|
|
282
|
+
next
|
|
283
|
+
}
|
|
284
|
+
flag { buf = buf $0 "\n" }
|
|
285
|
+
' "$file"
|
|
286
|
+
echo "$blocks_prefix"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
process_blocks_in_file() {
|
|
290
|
+
local file="$1"
|
|
291
|
+
# gotcha_candidates
|
|
292
|
+
local g_dir
|
|
293
|
+
g_dir=$(extract_blocks "$file" "gotcha_candidates")
|
|
294
|
+
local b
|
|
295
|
+
for b in "$g_dir"/block-*.json; do
|
|
296
|
+
[ -f "$b" ] || continue
|
|
297
|
+
if jq empty "$b" 2>/dev/null; then
|
|
298
|
+
register_from_json "$(cat "$b")"
|
|
299
|
+
else
|
|
300
|
+
echo "[gotcha-register] skip: invalid gotcha_candidates JSON in $(basename "$file")" >&2
|
|
301
|
+
fi
|
|
302
|
+
done
|
|
303
|
+
rm -rf "$g_dir"
|
|
304
|
+
|
|
305
|
+
# convention_candidates
|
|
306
|
+
local c_dir
|
|
307
|
+
c_dir=$(extract_blocks "$file" "convention_candidates")
|
|
308
|
+
for b in "$c_dir"/block-*.json; do
|
|
309
|
+
[ -f "$b" ] || continue
|
|
310
|
+
if jq empty "$b" 2>/dev/null; then
|
|
311
|
+
register_conventions_from_json "$(cat "$b")"
|
|
312
|
+
else
|
|
313
|
+
echo "[gotcha-register] skip: invalid convention_candidates JSON in $(basename "$file")" >&2
|
|
314
|
+
fi
|
|
315
|
+
done
|
|
316
|
+
rm -rf "$c_dir"
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
# ─────────────────────────────────────────
|
|
320
|
+
# evaluation-*.md 스캔 — ```gotcha_candidates``` + ```convention_candidates``` 둘 다
|
|
175
321
|
# ─────────────────────────────────────────
|
|
176
322
|
scan_evaluations() {
|
|
177
|
-
|
|
323
|
+
[ -d "$ACTIONS_DIR" ] || return 0
|
|
178
324
|
local f
|
|
179
325
|
for f in "$ACTIONS_DIR"/evaluation-*.md; do
|
|
180
326
|
[ -f "$f" ] || continue
|
|
327
|
+
process_blocks_in_file "$f"
|
|
328
|
+
done
|
|
329
|
+
}
|
|
181
330
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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"
|
|
331
|
+
# ─────────────────────────────────────────
|
|
332
|
+
# 모든 worker report 스캔 — evaluation-*.md + gen-report-*.md + lead-report-*.md
|
|
333
|
+
# Generator 는 gen-report-{F-ID}.md 작성, Lead 는 lead-report-{date}.md 작성 가능
|
|
334
|
+
# ─────────────────────────────────────────
|
|
335
|
+
scan_all() {
|
|
336
|
+
[ -d "$ACTIONS_DIR" ] || return 0
|
|
337
|
+
local f
|
|
338
|
+
for f in "$ACTIONS_DIR"/evaluation-*.md "$ACTIONS_DIR"/gen-report-*.md "$ACTIONS_DIR"/lead-report-*.md; do
|
|
339
|
+
[ -f "$f" ] || continue
|
|
340
|
+
process_blocks_in_file "$f"
|
|
211
341
|
done
|
|
212
342
|
}
|
|
213
343
|
|
|
@@ -237,6 +367,7 @@ while [ $# -gt 0 ]; do
|
|
|
237
367
|
--source) SOURCE="$2"; shift 2 ;;
|
|
238
368
|
--from-json) MODE="json"; FROM_JSON="$2"; shift 2 ;;
|
|
239
369
|
--scan-evaluations) MODE="scan"; shift ;;
|
|
370
|
+
--scan-all) MODE="scan-all"; shift ;;
|
|
240
371
|
*) echo "[gotcha-register] unknown arg: $1" >&2; exit 1 ;;
|
|
241
372
|
esac
|
|
242
373
|
done
|
|
@@ -256,4 +387,7 @@ case "$MODE" in
|
|
|
256
387
|
scan)
|
|
257
388
|
scan_evaluations
|
|
258
389
|
;;
|
|
390
|
+
scan-all)
|
|
391
|
+
scan_all
|
|
392
|
+
;;
|
|
259
393
|
esac
|
package/scripts/harness-next.sh
CHANGED
|
@@ -320,12 +320,14 @@ fi
|
|
|
320
320
|
# .harness/gotchas/<target>.md 에 unverified 상태로 등록/dedup.
|
|
321
321
|
# ─────────────────────────────────────────
|
|
322
322
|
case "$current_agent" in
|
|
323
|
-
evaluator-*)
|
|
323
|
+
evaluator-*|generator-*)
|
|
324
324
|
if [ "$agent_status" = "completed" ] || [ "$agent_status" = "failed" ]; then
|
|
325
325
|
if [ -x "$SCRIPT_DIR/harness-gotcha-register.sh" ]; then
|
|
326
|
-
|
|
326
|
+
# --scan-all 로 evaluation-*.md + gen-report-*.md + lead-report-*.md 의
|
|
327
|
+
# gotcha_candidates / convention_candidates 블록 모두 동적 등록
|
|
328
|
+
bash "$SCRIPT_DIR/harness-gotcha-register.sh" "$PROJECT_ROOT" --scan-all 2>&1 \
|
|
327
329
|
| grep -E '^\[gotcha-register\]' || true
|
|
328
|
-
audit_gate "gotcha-register" "scan" "$current_agent"
|
|
330
|
+
audit_gate "gotcha-register" "scan-all" "$current_agent"
|
|
329
331
|
fi
|
|
330
332
|
fi
|
|
331
333
|
;;
|
package/scripts/harness-tmux.sh
CHANGED
|
@@ -112,7 +112,7 @@ launch_iterm2_team() {
|
|
|
112
112
|
write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-prompt-history.sh' '${PROJECT_ROOT}'"
|
|
113
113
|
end tell
|
|
114
114
|
|
|
115
|
-
-- Split right → Gotcha & Memory
|
|
115
|
+
-- Split right → Gotcha & Memory (combined view; tmux 경로는 3분할 사용)
|
|
116
116
|
set dashPane to (split vertically with default profile)
|
|
117
117
|
tell dashPane
|
|
118
118
|
write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-gotcha-memory.sh' '${PROJECT_ROOT}'"
|
|
@@ -163,7 +163,7 @@ launch_iterm2_solo() {
|
|
|
163
163
|
write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-prompt-history.sh' '${PROJECT_ROOT}'"
|
|
164
164
|
end tell
|
|
165
165
|
|
|
166
|
-
-- Split right → Gotcha · Memory · Conventions
|
|
166
|
+
-- Split right → Gotcha · Memory · Conventions (combined view; tmux 경로는 3분할)
|
|
167
167
|
set dashPane to (split vertically with default profile)
|
|
168
168
|
tell dashPane
|
|
169
169
|
write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-gotcha-memory.sh' '${PROJECT_ROOT}'"
|
|
@@ -198,12 +198,21 @@ launch_tmux_team() {
|
|
|
198
198
|
PANE_ARCHIVE=$(tmux split-window -v -p 35 -t "$PANE_DASH" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
|
|
199
199
|
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
|
|
200
200
|
|
|
201
|
+
# Gotcha 패널을 3 개 sub-pane 으로 수직 분할: Gotchas / Conventions / Memory
|
|
202
|
+
# 각 pane 은 tmux copy-mode (prefix + [) 로 독립 스크롤 가능
|
|
203
|
+
PANE_CONV=$(tmux split-window -v -p 60 -t "$PANE_GOTCHA" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
|
|
204
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode conventions'")
|
|
205
|
+
PANE_MEM=$(tmux split-window -v -p 50 -t "$PANE_CONV" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
|
|
206
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode memory'")
|
|
207
|
+
|
|
201
208
|
tmux send-keys -t "$PANE_DASH" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
|
|
202
|
-
tmux send-keys -t "$PANE_GOTCHA" "bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\"" Enter
|
|
209
|
+
tmux send-keys -t "$PANE_GOTCHA" "bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode gotcha" Enter
|
|
203
210
|
|
|
204
211
|
tmux select-pane -t "$PANE_DASH" -T "Dashboard"
|
|
205
212
|
tmux select-pane -t "$PANE_ARCHIVE" -T "Archive Prompt"
|
|
206
|
-
tmux select-pane -t "$PANE_GOTCHA" -T "
|
|
213
|
+
tmux select-pane -t "$PANE_GOTCHA" -T "Gotchas (scrollable: prefix + [ )"
|
|
214
|
+
tmux select-pane -t "$PANE_CONV" -T "Conventions (scrollable: prefix + [ )"
|
|
215
|
+
tmux select-pane -t "$PANE_MEM" -T "Shared Memory (scrollable: prefix + [ )"
|
|
207
216
|
tmux select-pane -t "$PANE_T1" -T "TEAM 1"
|
|
208
217
|
tmux select-pane -t "$PANE_T2" -T "TEAM 2"
|
|
209
218
|
tmux select-pane -t "$PANE_T3" -T "TEAM 3"
|
|
@@ -224,12 +233,20 @@ launch_tmux_solo() {
|
|
|
224
233
|
PANE_HISTORY=$(tmux split-window -v -p 35 -t "$PANE_DASH" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
|
|
225
234
|
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
|
|
226
235
|
|
|
236
|
+
# Gotcha 패널을 3 개 sub-pane 으로 수직 분할: Gotchas / Conventions / Memory
|
|
237
|
+
PANE_CONV=$(tmux split-window -v -p 60 -t "$PANE_GOTCHA" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
|
|
238
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode conventions'")
|
|
239
|
+
PANE_MEM=$(tmux split-window -v -p 50 -t "$PANE_CONV" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
|
|
240
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode memory'")
|
|
241
|
+
|
|
227
242
|
tmux send-keys -t "$PANE_DASH" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
|
|
228
|
-
tmux send-keys -t "$PANE_GOTCHA" "bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\"" Enter
|
|
243
|
+
tmux send-keys -t "$PANE_GOTCHA" "bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode gotcha" Enter
|
|
229
244
|
|
|
230
245
|
tmux select-pane -t "$PANE_DASH" -T "Dashboard"
|
|
231
246
|
tmux select-pane -t "$PANE_HISTORY" -T "Prompt History"
|
|
232
|
-
tmux select-pane -t "$PANE_GOTCHA" -T "
|
|
247
|
+
tmux select-pane -t "$PANE_GOTCHA" -T "Gotchas (scrollable: prefix + [ )"
|
|
248
|
+
tmux select-pane -t "$PANE_CONV" -T "Conventions (scrollable: prefix + [ )"
|
|
249
|
+
tmux select-pane -t "$PANE_MEM" -T "Shared Memory (scrollable: prefix + [ )"
|
|
233
250
|
tmux select-pane -t "$PANE_MONITOR" -T "Monitor"
|
|
234
251
|
tmux select-pane -t "$PANE_DASH"
|
|
235
252
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
docmeta:
|
|
3
|
+
id: dynamic-registration
|
|
4
|
+
title: 동적 Gotcha / Convention 등록 — 모든 worker mandatory
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-04-27T00:00:00Z
|
|
7
|
+
updatedAt: 2026-04-27T00:00:00Z
|
|
8
|
+
source:
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [harness, gotcha, convention, dynamic-registration, mandatory]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# 동적 Gotcha / Convention 등록 — 모든 worker mandatory
|
|
16
|
+
|
|
17
|
+
모든 Generator / Evaluator / Lead 는 자기 작업 결과물(`evaluation-*.md` / `gen-report-*.md` / `lead-report-*.md`) 끝에 **두 개의 fenced JSON 블록**을 반드시 포함한다. `harness-next.sh` 와 Team mode Lead 가 작업 직후 자동으로 `harness-gotcha-register.sh --scan-all` 을 호출하여 두 블록을 dedup append 한다.
|
|
18
|
+
|
|
19
|
+
**왜 mandatory 인가**: 사용자 명시 — "Gen/Eval 이 발견한 패턴은 메뉴얼로 시키기 전에 동적으로 등록되어야 한다. 자동으로 패턴화되는 이슈는 등록하라." 한 번 발견된 실수가 다음 sprint 에서 반복되지 않게 하려면 발견자가 그 자리에서 등록하는 것이 유일한 closure.
|
|
20
|
+
|
|
21
|
+
**금지**: 두 블록 중 하나라도 누락된 채로 작업 종료. 비어 있어도 `[]` 로 명시 (블록 자체 생략 금지).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1) gotcha_candidates — 부정 가이드 (실수 패턴)
|
|
26
|
+
|
|
27
|
+
**누가 등록**:
|
|
28
|
+
- Evaluator: 평가 중 발견한 generator 의 반복 가능한 결함
|
|
29
|
+
- Generator: 자기 작업 중 한 번 시도했다가 깨졌던 접근, 자체 fix 한 코드 실수
|
|
30
|
+
- Lead (Team mode): worker 보고서들에서 N 회 반복되는 패턴 (≥2 occurrences 권장)
|
|
31
|
+
|
|
32
|
+
**검출 기준** (하나라도 해당):
|
|
33
|
+
- 같은 sprint 내 다른 feature 에서도 재발할 가능성이 있는 결함
|
|
34
|
+
- generator 가 자주 빠뜨리는 케이스 (RSC↔CC 경계, schema 누락, missing not-found.tsx 등)
|
|
35
|
+
- spec/contract 위반 패턴
|
|
36
|
+
- AC 부분 통과 / Hard Gate 위반 사유
|
|
37
|
+
- lint/type 이 한 번에 안 잡힌 케이스 (정적 분석으로 못 잡는 결함)
|
|
38
|
+
|
|
39
|
+
**스키마**:
|
|
40
|
+
```gotcha_candidates
|
|
41
|
+
[
|
|
42
|
+
{
|
|
43
|
+
"target": "generator-frontend",
|
|
44
|
+
"rule_id": "rsc-cc-boundary-monitoring",
|
|
45
|
+
"title": "Server Component 에서 useState/useEffect 호출",
|
|
46
|
+
"wrong": "app/monitoring/page.tsx 에 'use client' 없이 hook 사용 → 빌드 통과해도 런타임에 깨짐",
|
|
47
|
+
"right": "client-side state 사용 시 파일 최상단에 'use client' 명시. 또는 server component 로 유지하면서 client 부분만 분리.",
|
|
48
|
+
"why": "Next.js App Router 의 RSC↔CC 경계는 정적 분석으로 100% 안 잡힘. F-209 에서 발견.",
|
|
49
|
+
"scope": "app/**/page.tsx, app/**/layout.tsx",
|
|
50
|
+
"source": "evaluator-functional:F-209"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
비어 있으면:
|
|
56
|
+
```gotcha_candidates
|
|
57
|
+
[]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
필수 필드: `target`, `rule_id`, `title`. 권장 필드: `wrong`, `right`, `why`, `scope`, `source`.
|
|
61
|
+
|
|
62
|
+
`target` 가능 값: `planner`, `dispatcher`, `brainstorming`, `generator-backend`, `generator-frontend`, `generator-frontend-flutter`, `evaluator-code-quality`, `evaluator-functional`, `evaluator-visual`, `evaluator-functional-flutter`.
|
|
63
|
+
|
|
64
|
+
`rule_id` 는 dedup key — 같은 rule_id 가 이미 있으면 Occurrences +1 만 증가하고 본문은 안 바뀜. **kebab-case 짧고 의미 있는 식별자**.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 2) convention_candidates — 긍정 가이드 (반복 가능한 best practice)
|
|
69
|
+
|
|
70
|
+
**누가 등록**:
|
|
71
|
+
- Evaluator: 평가 중 확립된 반복 가능한 모범 사례
|
|
72
|
+
- Generator: 작업 중 적용한 일관된 패턴 (다른 feature 에 모방되어야 하는)
|
|
73
|
+
- Lead: sprint 전반에서 합의된 룰
|
|
74
|
+
|
|
75
|
+
**검출 기준**:
|
|
76
|
+
- 같은 sprint 의 다른 feature 가 모방해야 할 패턴
|
|
77
|
+
- API 계약 / 폴더 구조 / 명명 규칙 관련 결정
|
|
78
|
+
- 사용자가 "이렇게 해" 라고 한 한 번의 발언이 평가/생성에서 일반화 가능한 경우
|
|
79
|
+
|
|
80
|
+
**스키마**:
|
|
81
|
+
```convention_candidates
|
|
82
|
+
[
|
|
83
|
+
{
|
|
84
|
+
"scope": "generator-frontend",
|
|
85
|
+
"rule_id": "route-segment-files",
|
|
86
|
+
"title": "App Router 세그먼트 필수 파일 세트",
|
|
87
|
+
"rule": "모든 app/**/page.tsx 는 같은 폴더에 not-found.tsx, error.tsx, loading.tsx 를 함께 배치한다.",
|
|
88
|
+
"why": "Next.js 의 segment-level 에러/로딩 처리. 누락 시 default 흰 화면 노출. F-209 평가에서 표준화 결정.",
|
|
89
|
+
"source": "evaluator-functional:F-209"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
비어 있으면:
|
|
95
|
+
```convention_candidates
|
|
96
|
+
[]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
필수 필드: `rule_id`, `title`, `rule`. 권장 필드: `scope`, `why`, `source`.
|
|
100
|
+
|
|
101
|
+
`scope` 가능 값: `shared` (전체 공통), `generator-backend`, `generator-frontend`, `evaluator-*`, `planner`. 미지정 시 `shared`.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 3) 등록 결과 확인
|
|
106
|
+
|
|
107
|
+
작업 완료 후 다음 명령으로 등록된 항목 확인 가능:
|
|
108
|
+
```bash
|
|
109
|
+
bash scripts/harness-gotcha-register.sh . --scan-all # 수동 재스캔
|
|
110
|
+
ls .harness/gotchas/ .harness/conventions/ # 누적 결과
|
|
111
|
+
tail -20 .harness/progress.log | grep gotcha-register # 등록 로그
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 4) Team mode 추가 룰
|
|
115
|
+
|
|
116
|
+
Team mode 에서 Lead 는 worker PASS/FAIL 처리 직후 (merge 전) 다음을 실행한다:
|
|
117
|
+
```bash
|
|
118
|
+
bash scripts/harness-gotcha-register.sh . --scan-all
|
|
119
|
+
```
|
|
120
|
+
이로써 worker 가 작성한 두 블록이 즉시 누적되며, 다음 worker spawn 시 새 worker 가 갱신된 gotchas/conventions 를 startup 에 읽어 같은 실수를 반복하지 않는다.
|
|
@@ -206,3 +206,13 @@ Cross-Validation 데이터 블록 포함 (Functional/Visual 이 참조):
|
|
|
206
206
|
|
|
207
207
|
- **PASS** → Session Boundary Protocol On Complete (PASS) 실행
|
|
208
208
|
- **FAIL** → Session Boundary Protocol On Fail 실행
|
|
209
|
+
|
|
210
|
+
## ⚠ MANDATORY — 동적 Gotcha / Convention 등록
|
|
211
|
+
|
|
212
|
+
evaluation-code-quality.md 의 끝에 **반드시 `gotcha_candidates` 와 `convention_candidates` fenced JSON 블록**을 작성한다 (비어 있으면 `[]`). harness-next.sh / Team Lead 가 자동 스캔하여 `.harness/gotchas/` `.harness/conventions/` 에 dedup append.
|
|
213
|
+
|
|
214
|
+
**Code-quality 특화 검출 대상**:
|
|
215
|
+
- 정적 분석으로 잡을 수 있는데 generator 가 빠뜨린 패턴 → `gotcha_candidates`
|
|
216
|
+
- 코드 구조/명명/import 정렬 등 일관 룰 → `convention_candidates`
|
|
217
|
+
|
|
218
|
+
상세 스키마 / 예시 / 필수 필드 → [공통 가이드 — dynamic-registration](../_shared/dynamic-registration.md)
|
|
@@ -204,3 +204,68 @@ Playwright 도구 → [도구 레퍼런스](references/playwright-tools.md)
|
|
|
204
204
|
|
|
205
205
|
- **PASS** → Session Boundary Protocol On Complete (PASS) 실행
|
|
206
206
|
- **FAIL** → Session Boundary Protocol On Fail 실행
|
|
207
|
+
|
|
208
|
+
## ⚠ MANDATORY — 동적 Gotcha / Convention 등록 (모든 평가에서 필수)
|
|
209
|
+
|
|
210
|
+
evaluation-functional.md 의 끝에 **반드시 두 개의 fenced JSON 블록**을 작성한다. 비어 있어도 `[]` 로 명시 (블록 자체를 생략 금지). harness-next.sh 가 평가 직후 자동으로 이 블록들을 파싱하여 `.harness/gotchas/<target>.md` 와 `.harness/conventions/<scope>.md` 에 dedup append 한다.
|
|
211
|
+
|
|
212
|
+
**왜 mandatory 인가**: 사용자 명시 — "Gen/Eval 이 발견한 패턴은 메뉴얼로 시키기 전에 동적으로 등록되어야 한다. 자동으로 패턴화되는 이슈는 등록하라." 한 번 발견된 실수가 다음 sprint 에서 반복되지 않게 하려면 평가자가 그 자리에서 등록하는 것이 유일한 closure.
|
|
213
|
+
|
|
214
|
+
### 1) gotcha_candidates — 부정 가이드 (실수 패턴)
|
|
215
|
+
|
|
216
|
+
평가 중 발견한 **반복 가능한 실수** (한 번이라도 동일 패턴이 다시 나올 위험이 있는 결함). 한 평가에서 0~N 개.
|
|
217
|
+
|
|
218
|
+
검출 기준 (하나라도 해당하면 등록):
|
|
219
|
+
- 같은 sprint 내 다른 feature 에서도 재발할 가능성이 있는 결함
|
|
220
|
+
- generator 가 자주 빠뜨리는 케이스 (e.g. RSC↔CC 경계, schema 누락)
|
|
221
|
+
- spec/contract 위반 패턴
|
|
222
|
+
- AC 부분 통과 / Hard Gate 위반 사유
|
|
223
|
+
|
|
224
|
+
```gotcha_candidates
|
|
225
|
+
[
|
|
226
|
+
{
|
|
227
|
+
"target": "generator-frontend",
|
|
228
|
+
"rule_id": "rsc-cc-boundary-monitoring",
|
|
229
|
+
"title": "Server Component 에서 useState/useEffect 호출",
|
|
230
|
+
"wrong": "app/monitoring/page.tsx 에 'use client' 없이 hook 사용 → 빌드 통과해도 런타임에 깨짐",
|
|
231
|
+
"right": "client-side state 사용 시 파일 최상단에 'use client' 명시. 또는 server component 로 유지하면서 client 부분만 분리.",
|
|
232
|
+
"why": "Next.js App Router 의 RSC↔CC 경계는 정적 분석으로 100% 안 잡힘. F-209 에서 발견.",
|
|
233
|
+
"scope": "app/**/page.tsx, app/**/layout.tsx",
|
|
234
|
+
"source": "evaluator-functional:F-209"
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
비어 있으면:
|
|
240
|
+
```gotcha_candidates
|
|
241
|
+
[]
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 2) convention_candidates — 긍정 가이드 (반복 가능한 best practice)
|
|
245
|
+
|
|
246
|
+
평가 중 확립된 **반복 가능한 모범 사례** (다른 feature 에서도 같은 방식으로 적용해야 하는 룰). 한 평가에서 0~N 개.
|
|
247
|
+
|
|
248
|
+
검출 기준:
|
|
249
|
+
- 같은 sprint 의 다른 feature 가 모방해야 할 패턴
|
|
250
|
+
- API 계약 / 폴더 구조 / 명명 규칙 관련 결정
|
|
251
|
+
- 사용자가 "이렇게 해" 라고 한 한 번의 발언이 평가에서 일반화 가능한 경우
|
|
252
|
+
|
|
253
|
+
```convention_candidates
|
|
254
|
+
[
|
|
255
|
+
{
|
|
256
|
+
"scope": "generator-frontend",
|
|
257
|
+
"rule_id": "route-segment-files",
|
|
258
|
+
"title": "App Router 세그먼트 필수 파일 세트",
|
|
259
|
+
"rule": "모든 app/**/page.tsx 는 같은 폴더에 not-found.tsx, error.tsx, loading.tsx 를 함께 배치한다.",
|
|
260
|
+
"why": "Next.js 의 segment-level 에러/로딩 처리. 누락 시 default 흰 화면 노출. F-209 평가에서 표준화 결정.",
|
|
261
|
+
"source": "evaluator-functional:F-209"
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
비어 있으면:
|
|
267
|
+
```convention_candidates
|
|
268
|
+
[]
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**금지**: 두 블록 중 하나라도 누락된 채로 평가 종료 → On Complete protocol 위반. harness-next.sh 의 audit gate 에서 누락 감지 시 경고.
|
|
@@ -153,3 +153,13 @@ jq '.agent_status = "completed" | .completed_agents += ["planner"]' .harness/p
|
|
|
153
153
|
|
|
154
154
|
- **PASS** → Session Boundary Protocol On Complete (PASS) 실행
|
|
155
155
|
- **FAIL** → Session Boundary Protocol On Fail 실행
|
|
156
|
+
|
|
157
|
+
## ⚠ MANDATORY — 동적 Gotcha / Convention 등록
|
|
158
|
+
|
|
159
|
+
evaluation-visual.md 의 끝에 **반드시 `gotcha_candidates` 와 `convention_candidates` fenced JSON 블록**을 작성한다 (비어 있으면 `[]`). harness-next.sh / Team Lead 가 자동 스캔하여 `.harness/gotchas/` `.harness/conventions/` 에 dedup append.
|
|
160
|
+
|
|
161
|
+
**Visual eval 특화 검출 대상**:
|
|
162
|
+
- 콘솔 에러/Hydration mismatch → `gotcha_candidates` (target=generator-frontend)
|
|
163
|
+
- 디자인 토큰 불일치, 일관된 spacing/font 룰 → `convention_candidates` (scope=generator-frontend)
|
|
164
|
+
|
|
165
|
+
상세 스키마 / 예시 / 필수 필드 → [공통 가이드 — dynamic-registration](../_shared/dynamic-registration.md)
|
|
@@ -119,3 +119,35 @@ api-contract.json 의 DTO 스키마는 해당 스택의 타입 표현(Pydantic /
|
|
|
119
119
|
|
|
120
120
|
- `ref-docs` 가 placeholder 상태 → 본격 구현 전에 `bash init.sh refresh-ref be <stack>` 으로 채우기 권고
|
|
121
121
|
- `scan-result.json.tech_stack_confidence == "unknown"` → 사용자에게 객관식 + 자유입력 fallback 로 스택 확인 요청
|
|
122
|
+
|
|
123
|
+
## ⚠ MANDATORY — 동적 Gotcha / Convention 등록 (Generator 도 mandatory)
|
|
124
|
+
|
|
125
|
+
작업 완료 시 `.harness/actions/gen-report-{FEATURE_ID}.md` 를 작성하고 끝에 **`gotcha_candidates` 와 `convention_candidates` fenced JSON 블록 두 개**를 포함한다 (비어 있으면 `[]`). harness-next.sh / Team Lead 가 자동 스캔하여 `.harness/gotchas/` `.harness/conventions/` 에 dedup append.
|
|
126
|
+
|
|
127
|
+
**Generator 가 등록해야 하는 패턴** (자기 발견):
|
|
128
|
+
- 한 번 시도했다가 깨졌던 접근, 자체 fix 한 코드 실수 → `gotcha_candidates` (target=generator-backend, source="generator-backend:F-XXX")
|
|
129
|
+
- 작업 중 적용한 일관된 코드 구조/패턴 (다른 feature 가 모방해야 할) → `convention_candidates` (scope=generator-backend)
|
|
130
|
+
|
|
131
|
+
`gen-report-{FEATURE_ID}.md` 최소 형식:
|
|
132
|
+
```markdown
|
|
133
|
+
# Generator-Backend Report — {FEATURE_ID}
|
|
134
|
+
|
|
135
|
+
## 변경 파일
|
|
136
|
+
- ...
|
|
137
|
+
|
|
138
|
+
## 자체 게이트 결과
|
|
139
|
+
- tsc: OK · eslint: 0 warn · jest: N/N
|
|
140
|
+
|
|
141
|
+
## 발견 패턴
|
|
142
|
+
(짧은 설명)
|
|
143
|
+
|
|
144
|
+
```gotcha_candidates
|
|
145
|
+
[]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```convention_candidates
|
|
149
|
+
[]
|
|
150
|
+
```
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
상세 스키마 → [공통 가이드 — dynamic-registration](../_shared/dynamic-registration.md)
|
|
@@ -123,3 +123,13 @@ Team Mode 에서 Team Worker 가 호출할 때, 프롬프트에 `FEATURE_ID` 가
|
|
|
123
123
|
|
|
124
124
|
- `ref-docs` 가 placeholder 상태(`generator: "init-ref-docs.sh (placeholder)"`) → 본격 구현 전에 Claude 세션에서 `bash init.sh refresh-ref fe <stack>` 후 프롬프트 실행으로 본문 채우기를 권고
|
|
125
125
|
- `scan-result.json.tech_stack_confidence == "unknown"` → 사용자에게 객관식(감지 후보 top 5 + 자유입력 fallback)으로 스택 확인 요청
|
|
126
|
+
|
|
127
|
+
## ⚠ MANDATORY — 동적 Gotcha / Convention 등록 (Generator 도 mandatory)
|
|
128
|
+
|
|
129
|
+
작업 완료 시 `.harness/actions/gen-report-{FEATURE_ID}.md` 를 작성하고 끝에 **`gotcha_candidates` 와 `convention_candidates` fenced JSON 블록 두 개**를 포함한다 (비어 있으면 `[]`). harness-next.sh / Team Lead 가 자동 스캔하여 `.harness/gotchas/` `.harness/conventions/` 에 dedup append.
|
|
130
|
+
|
|
131
|
+
**FE Generator 가 등록해야 하는 패턴** (자기 발견):
|
|
132
|
+
- RSC↔CC 경계 위반, missing 'use client', not-found.tsx/error.tsx 누락, typed-routes 미스매치 → `gotcha_candidates` (target=generator-frontend)
|
|
133
|
+
- 일관된 컴포넌트/훅/스타일 룰, 폴더 구조, shadcn 사용 패턴 → `convention_candidates` (scope=generator-frontend)
|
|
134
|
+
|
|
135
|
+
상세 스키마 / `gen-report-{FEATURE_ID}.md` 형식 → [공통 가이드 — dynamic-registration](../_shared/dynamic-registration.md)
|