@walwal-harness/cli 5.8.1 → 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.
- package/commands/harness-team.md +39 -2
- package/gotchas/evaluator-code-quality.md +10 -0
- package/gotchas/evaluator-functional.md +37 -0
- package/gotchas/evaluator-visual.md +26 -0
- package/package.json +1 -1
- package/scripts/harness-gotcha-register.sh +165 -31
- package/scripts/harness-next.sh +5 -3
- 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
|
@@ -161,7 +161,35 @@ ORCHESTRATION LOOP:
|
|
|
161
161
|
|
|
162
162
|
#### Step 4a: Merge + Unblock (PASS 처리)
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
**⚠ HARD GATE — Runtime Bug 검출 시 PASS 처리 금지**
|
|
165
|
+
|
|
166
|
+
Worker 가 PASS 로 반환했더라도, 보고서에 다음 신호가 하나라도 포함되어 있으면 **PASS 처리하지 말고 즉시 FAIL 로 전환**한다. carry-over / 다음 sprint 미루기 절대 금지 — 견고한 기반 위에서만 다음 sprint 로 진행한다.
|
|
167
|
+
|
|
168
|
+
검출 시그널 (worker 반환 텍스트 검색):
|
|
169
|
+
- "runtime bug", "런타임 버그", "런타임 에러", "runtime error"
|
|
170
|
+
- "RSC", "Client Component" 경계 위반 (use client 누락 등)
|
|
171
|
+
- "not-found", "error.tsx", "loading.tsx" 누락/오류
|
|
172
|
+
- "typed-routes", "TypeScript route" 미스매치
|
|
173
|
+
- "console error", "uncaught", "Hydration error", "hydration mismatch"
|
|
174
|
+
- 그 외 worker 가 자신의 결과를 "carry-over", "다음 sprint 에서 처리", "추후 수정", "follow-up" 으로 표현하는 모든 케이스
|
|
175
|
+
|
|
176
|
+
위 신호 검출 시 처리:
|
|
177
|
+
```bash
|
|
178
|
+
# 1) PASS 가 아니라 FAIL 로 큐 업데이트
|
|
179
|
+
bash scripts/harness-queue-manager.sh fail {FEATURE_ID} .
|
|
180
|
+
|
|
181
|
+
# 2) 즉시 같은 feature 를 재투입 (worktree 재사용 또는 새 worker)
|
|
182
|
+
# Agent 프롬프트에 worker 가 보고한 버그 목록을 명시하고
|
|
183
|
+
# "이 sprint 안에서 fix 완료될 때까지 done 처리 금지" 강제
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
3) Sprint 전환 게이트: `next-sprint` 호출 전에 carry-over / known-bug 가 0 인지 반드시 확인. 1 건이라도 남아있으면 sprint advance 금지 — 같은 sprint 안에서 fix sprint 를 한 사이클 더 돈다.
|
|
187
|
+
|
|
188
|
+
이 룰을 위반한 사례: F-209 평가에서 "monitoring page RSC/CC, not-found.tsx, sidebar-nav typed-routes" 3 개 런타임 버그를 인지한 채 PASS 처리하고 Sprint 4 carry-over 로 미룬 적이 있다. 사용자 명시: "오류가 있는 것을 인지한 채로 스프린트를 넘어가는 행위는 절대 용납할 수 없다."
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
위 hard gate 를 통과한(=깨끗한 PASS) 경우에만:
|
|
165
193
|
|
|
166
194
|
1. **Worktree branch 확인**: Agent 반환 결과에서 worktree path와 branch 확인
|
|
167
195
|
2. **Main에 merge**:
|
|
@@ -169,7 +197,16 @@ Worker가 PASS로 반환되면:
|
|
|
169
197
|
git merge {BRANCH_NAME} --no-edit
|
|
170
198
|
```
|
|
171
199
|
- 충돌 시: 자동 해결 시도 → 실패 시 사용자 개입 요청
|
|
172
|
-
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 포함):
|
|
173
210
|
```bash
|
|
174
211
|
bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .
|
|
175
212
|
```
|
|
@@ -19,3 +19,13 @@ docmeta:
|
|
|
19
19
|
> Dispatcher가 관리. Evaluator-Code-Quality는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
20
20
|
|
|
21
21
|
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
|
22
|
+
|
|
23
|
+
## [G-001] 런타임 버그 인지 후 PASS 판정 금지 (verified · 2026-04-27)
|
|
24
|
+
|
|
25
|
+
**Status**: verified · **Severity**: HIGH
|
|
26
|
+
|
|
27
|
+
**규칙**: Code-quality eval 단계에서 SSR/RSC↔CC 경계 위반, 잘못된 라우트 타입, missing not-found/error.tsx 등 **런타임에 깨질 정적 결함**을 발견하면 즉시 FAIL. "스타일/구조 문제만 본다" 는 자기 합리화로 런타임 결함을 functional 단계로 넘기지 말 것.
|
|
28
|
+
|
|
29
|
+
**Why**: 사용자 명시 — "오류가 있는 것을 인지한 채로 스프린트를 넘어가는 행위는 절대 용납할 수 없다." Code-quality 가 정적 분석으로 잡을 수 있었던 결함을 통과시키면 evaluator chain 전체가 신뢰성을 잃는다.
|
|
30
|
+
|
|
31
|
+
**How to apply**: tsc/eslint 외에도 Next.js App Router 규칙 (use client 경계, typed routes), 라우트 세그먼트 필수 파일 (not-found/error/loading) 누락 여부를 정적으로 검사. 1 건이라도 발견 시 VERDICT=FAIL, retry_target=결함 위치 Generator.
|
|
@@ -1,5 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
docmeta:
|
|
3
|
+
id: evaluator-functional-gotchas
|
|
4
|
+
title: Gotchas — Evaluator-Functional
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-04-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-04-27T00:00:00Z
|
|
8
|
+
source:
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness-dispatcher
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [harness, gotcha, evaluator, evaluator-functional]
|
|
13
|
+
---
|
|
14
|
+
|
|
1
15
|
# Gotchas — Evaluator-Functional
|
|
2
16
|
|
|
3
17
|
> Dispatcher가 관리. Evaluator-Functional은 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
4
18
|
|
|
5
19
|
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
|
20
|
+
|
|
21
|
+
## [G-001] 런타임 버그 인지 후 PASS 판정 금지 (verified · 2026-04-27)
|
|
22
|
+
|
|
23
|
+
**Status**: verified
|
|
24
|
+
**Severity**: HIGH
|
|
25
|
+
**Occurrences**: 1+ (실제 사례: harness-clue Sprint 3 F-209)
|
|
26
|
+
|
|
27
|
+
**규칙**: Evaluator (functional / visual / code-quality 모두) 가 평가 중 런타임 버그를 발견했거나 worker 가 보고서에 런타임 버그를 명시한 경우, **PASS 판정 절대 금지**. carry-over / 다음 sprint 처리 / follow-up 으로 미루기도 금지. 같은 sprint 안에서 fix 완료 후에만 PASS.
|
|
28
|
+
|
|
29
|
+
**검출 시그널** (eval 자체 + worker report 둘 다 스캔):
|
|
30
|
+
- "runtime bug", "런타임 버그/에러", "runtime error"
|
|
31
|
+
- RSC ↔ Client Component 경계 위반 (`use client` 누락 등)
|
|
32
|
+
- `not-found.tsx`, `error.tsx`, `loading.tsx` 누락/오류
|
|
33
|
+
- typed-routes / TypeScript route 타입 미스매치
|
|
34
|
+
- Hydration error, Hydration mismatch
|
|
35
|
+
- 콘솔 uncaught error, unhandled promise rejection
|
|
36
|
+
- worker 가 자기 결과를 "carry-over", "다음 sprint", "추후", "follow-up" 으로 표현
|
|
37
|
+
|
|
38
|
+
**처리**: 위 시그널 1 건 이상 → VERDICT=FAIL, score 무관, retry_target = 결함 위치 Generator. 점수가 2.80+ 이어도 FAIL 강제 (Adversarial Rule).
|
|
39
|
+
|
|
40
|
+
**Why**: 사용자 명시 — "오류가 있는 것을 인지한 채로 스프린트를 넘어가는 행위는 절대 용납할 수 없다. 견고하게 기반을 다져나가야 한다."
|
|
41
|
+
|
|
42
|
+
**How to apply**: Eval 종료 직전 자기 보고서 + Generator 보고서를 위 키워드로 grep. 1 건이라도 매치하면 verdict 를 FAIL 로 강제 변환하고 evaluation-*.md 의 결론 섹션에 "Hard Gate G-001 위반: <검출 키워드> — 같은 sprint 내 fix 후 재평가 필요" 명시.
|
|
@@ -1,5 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
docmeta:
|
|
3
|
+
id: evaluator-visual-gotchas
|
|
4
|
+
title: Gotchas — Evaluator-Visual
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-04-08T00:00:00Z
|
|
7
|
+
updatedAt: 2026-04-27T00:00:00Z
|
|
8
|
+
source:
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness-dispatcher
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [harness, gotcha, evaluator, evaluator-visual]
|
|
13
|
+
---
|
|
14
|
+
|
|
1
15
|
# Gotchas — Evaluator-Visual
|
|
2
16
|
|
|
3
17
|
> Dispatcher가 관리. Evaluator-Visual은 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
4
18
|
|
|
5
19
|
<!-- 항목이 추가되면 아래에 기록됩니다 -->
|
|
20
|
+
|
|
21
|
+
## [G-001] 런타임 버그 인지 후 PASS 판정 금지 (verified · 2026-04-27)
|
|
22
|
+
|
|
23
|
+
**Status**: verified · **Severity**: HIGH
|
|
24
|
+
|
|
25
|
+
**규칙**: Visual eval 중 콘솔 에러/Hydration mismatch/RSC↔CC 경계 위반/렌더 실패가 한 번이라도 관찰되면 PASS 절대 금지. 스크린샷이 멀쩡해 보여도 console.error 가 1 건 이상이면 FAIL. carry-over/follow-up 으로 미루기 금지.
|
|
26
|
+
|
|
27
|
+
**검출 시그널**: console error/warning, Hydration error, "use client" 누락, not-found/error.tsx 미렌더, layout shift 비정상, 404/500 응답.
|
|
28
|
+
|
|
29
|
+
**처리**: VERDICT=FAIL, retry_target=generator-frontend, evaluation-visual.md 결론에 "Hard Gate G-001 위반" 명시. 같은 sprint 내 fix 완료 후에만 재평가.
|
|
30
|
+
|
|
31
|
+
**Why**: 사용자 명시 — "오류가 있는 것을 인지한 채로 스프린트를 넘어가는 행위는 절대 용납할 수 없다."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "5.
|
|
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
|
-
#
|
|
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
|
;;
|
|
@@ -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)
|