@walwal-harness/cli 5.8.2 → 5.9.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.
@@ -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. **Queue 업데이트** (unblock 포함):
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.8.2",
3
+ "version": "5.9.0",
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"
@@ -171,43 +171,173 @@ register_from_json() {
171
171
  }
172
172
 
173
173
  # ─────────────────────────────────────────
174
- # evaluation-*.md 스캔 — ```gotcha_candidates ... ``` JSON fenced block 추출
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
- if [ ! -d "$ACTIONS_DIR" ]; then return 0; fi
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
- # 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"
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
@@ -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
- bash "$SCRIPT_DIR/harness-gotcha-register.sh" "$PROJECT_ROOT" --scan-evaluations 2>&1 \
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
  ;;
@@ -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)