@walwal-harness/cli 5.4.0 → 5.5.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.
@@ -1,4 +1,4 @@
1
- # 6-Agent Production Harness — 사용 가이드
1
+ # 7-Agent Production Harness — 사용 가이드
2
2
 
3
3
  > Anthropic 블로그 "Harness Design for Long-Running Application Development" 기반
4
4
  > Solo: 20min/$9 (broken) → Harness: 6hr/$200 (fully functional)
@@ -42,16 +42,37 @@ CONVENTIONS.md # 프로젝트 컨벤션 (사용자 작성,
42
42
  FULLSTACK FE-ONLY BE-ONLY
43
43
  ```
44
44
 
45
+ ### Evaluator Chain (공통)
46
+
47
+ Generator 이후는 **3-Evaluator 직렬 체인 + 조기 종료**:
48
+
49
+ ```
50
+ Generator
51
+ → Eval-Code-Quality (정적 · 저비용 · 브라우저 없음)
52
+ → Eval-Functional (동작 · 중비용 · Playwright/curl)
53
+ → Eval-Visual (렌더 · 고비용 · 스크린샷)
54
+ → Archive
55
+ ```
56
+
57
+ 앞단 FAIL 시 뒤 평가자는 실행하지 않고 즉시 Generator 재작업으로 리라우팅.
58
+ 구조가 깨진 코드에 동작/렌더 테스트를 낭비하지 않기 위함.
59
+
60
+ | 평가자 | 관심사 | 도구 |
61
+ |--------|--------|------|
62
+ | evaluator-code-quality | 유지보수성·레이어·타입 안정성·테스트 품질 (BE/FE/libs 공통) | Read/Grep + tsc/eslint |
63
+ | evaluator-functional | 엔드포인트·E2E 사용자 플로우·API 계약 준수 | Playwright(browser_*) 또는 curl(api-only) |
64
+ | evaluator-visual | 레이아웃·반응형·접근성·AI슬롭 | Playwright(screenshot/resize/snapshot) |
65
+
45
66
  ### FULLSTACK — 신규 PRD 기반 풀스택
46
67
 
47
68
  ```
48
- Planner → Gen-BE → Gen-FE → Eval-Func → Eval-Visual → Archive
69
+ Planner → Gen-BE → Gen-FE → Eval-Code-Quality → Eval-Func → Eval-Visual → Archive
49
70
  ```
50
71
 
51
72
  ### FE-ONLY — 기존 API + 프론트엔드 연동
52
73
 
53
74
  ```
54
- Planner(light) → Gen-FE → Eval-Func → Eval-Visual → Archive
75
+ Planner(light) → Gen-FE → Eval-Code-Quality → Eval-Func → Eval-Visual → Archive
55
76
  │
56
77
  └─ OpenAPI spec → api-contract.json 변환
57
78
  Gen-BE SKIP (외부 서버 사용)
@@ -60,7 +81,7 @@ Planner(light) → Gen-FE → Eval-Func → Eval-Visual → Archive
60
81
  ### BE-ONLY — 기존 서버 + 백엔드 기능 추가
61
82
 
62
83
  ```
63
- Planner → Gen-BE → Eval-Func(API-only) → Archive
84
+ Planner → Gen-BE → Eval-Code-Quality → Eval-Func(API-only) → Archive
64
85
  │
65
86
  └─ 기존 코드 분석 후 확장 설계
66
87
  Gen-FE SKIP, Eval-Visual SKIP
@@ -70,8 +91,9 @@ Planner → Gen-BE → Eval-Func(API-only) → Archive
70
91
  ### 공통 — 실패 시 루프
71
92
 
72
93
  ```
73
- Eval-Func FAIL → failure_location에 따라 Gen-BE 또는 Gen-FE 재작업 (max 5회)
74
- Eval-Visual FAIL → Gen-FE 재작업 (max 5회)
94
+ Eval-Code-Quality FAIL → failure.location 에 따라 Gen-BE 또는 Gen-FE 재작업 (뒤 평가자 실행 없음)
95
+ Eval-Func FAIL → 동일 규칙으로 재작업
96
+ Eval-Visual FAIL → Gen-FE 재작업
75
97
  3회 실패 → Planner 에스컬레이션 (scope 축소/접근 변경)
76
98
  5회 초과 → 사용자 개입 요청
77
99
  ```
@@ -282,6 +304,16 @@ jq . .harness/progress.json # 현재 상태 확인
282
304
  | FAIL 기준 | 2.79 이하 (예외 없음) |
283
305
  | Evidence 없는 항목 | Score = 0으로 강제 재계산 |
284
306
 
307
+ ### Evaluator-Code-Quality 채점 항목 (C1-C5)
308
+
309
+ | # | Criterion | Weight |
310
+ |---|-----------|--------|
311
+ | C1 | Layer & Boundary (IA-MAP/MSA/FE VM 경계) | 25% |
312
+ | C2 | Readability & Complexity | 15% |
313
+ | C3 | Reuse & DRY | 20% |
314
+ | C4 | Type Safety & Error Handling | 25% |
315
+ | C5 | Test Quality | 15% |
316
+
285
317
  ### Evaluator-Functional 채점 항목 (R1-R5)
286
318
 
287
319
  | # | Criterion | Weight |
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "harness": {
3
- "name": "6-Agent Production Harness",
4
- "version": "5.0.0",
5
- "description": "Solo/Team 통합 하네스 — Dispatcher + NestJS MSA + React/Next.js + Playwright",
3
+ "name": "7-Agent Production Harness",
4
+ "version": "5.5.0",
5
+ "description": "Solo/Team 통합 하네스 — Dispatcher + 3-Evaluator Chain (Code-Quality → Functional → Visual) + NestJS MSA + React/Next.js + Playwright",
6
6
  "source": "https://www.anthropic.com/engineering/harness-design-long-running-apps"
7
7
  },
8
8
  "agents": {
@@ -73,6 +73,36 @@
73
73
  "thinking_mode": null,
74
74
  "model_rationale": "코드 생성은 Sonnet이 비용 대비 효율적. 계획은 Planner가 이미 완료."
75
75
  },
76
+ "evaluator-code-quality": {
77
+ "role": "코드 자체의 유지보수성/아키텍처/Best Practice 검사 (BE/FE/libs 공통, 브라우저 없음)",
78
+ "skill": "harness-evaluator-code-quality",
79
+ "tools": ["Read", "Grep", "Glob", "Bash(tsc,eslint,biome,lint 스크립트 한정)"],
80
+ "inputs": ["actions/sprint-contract.md", "actions/feature-list.json", "actions/api-contract.json"],
81
+ "outputs": ["actions/evaluation-code-quality.md"],
82
+ "model": "opus",
83
+ "thinking_mode": "ultrathink",
84
+ "thinking_mode_description": "시니어 리뷰어 관점. 변경 파일 전수 정적 분석 + C1-C5 축 적대적 채점.",
85
+ "adversarial_rules": {
86
+ "comment": "Code-Quality Evaluator 적대적 행동 규칙 — rubber-stamping 금지",
87
+ "rules": [
88
+ "브라우저 없이 코드만으로 판정. '동작하니 PASS' 금지.",
89
+ "변경 파일(git diff)은 전수 검사. '시간 제약' 핑계 금지.",
90
+ "모든 Score 주장은 파일:라인 evidence 동반. Evidence 없는 Score = 0.",
91
+ "toolchain(tsc/eslint) 에러 1건 이상이면 나머지 축 채점 전 즉시 FAIL.",
92
+ "api-contract.json ↔ 실제 DTO/시그니처 불일치 1건 이상 = FAIL.",
93
+ "레이어 역방향 의존 / MSA 직접 DB 접근 / FE view→fetch 직결 1건 이상 = C1 Score 0.",
94
+ "unhandled promise 1건 이상 또는 비타당한 any 3건 이상 = C4 Score 0.",
95
+ "PASS를 주기 전에 자문하라: '내가 이 코드를 머지 승인할 수 있는가?' NO이면 FAIL.",
96
+ "반복 페널티: 같은 축에서 이전 스프린트 대비 악화 시 Score 최대 1."
97
+ ],
98
+ "forbidden": [
99
+ "'동작은 Functional 에서 확인됐으니 OK' 식의 책임 전가",
100
+ "'관용적 any', '사소한 중복' 같은 자기설득",
101
+ "'제안사항만 남기고 Pass' — 필수 개선은 FAIL",
102
+ "파일 수정/커밋 (이 평가자는 읽기 전용)"
103
+ ]
104
+ }
105
+ },
76
106
  "evaluator-functional": {
77
107
  "role": "Playwright로 E2E 기능 검증, API 응답/DB 상태 확인",
78
108
  "skill": "harness-evaluator-functional",
@@ -135,13 +165,26 @@
135
165
  }
136
166
  },
137
167
  "flow": {
138
- "sequence": ["dispatcher", "planner", "generator-backend", "generator-frontend", "evaluator-functional", "evaluator-visual"],
168
+ "sequence": ["dispatcher", "planner", "generator-backend", "generator-frontend", "evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
169
+ "evaluator_chain": {
170
+ "comment": "Generator 완료 후 실행되는 평가자 체인. 직렬 실행 + 조기 종료 — 앞단 FAIL 시 이후 평가자는 생략하고 Generator 로 리라우팅. 모두 PASS 여야 다음 스프린트로 이동.",
171
+ "execution": "serial_with_early_exit",
172
+ "order_rationale": "code-quality(정적, 저비용) → functional(동작, 중비용) → visual(렌더, 고비용). 구조가 깨진 코드에 동작 테스트를 낭비하지 않기 위함.",
173
+ "on_any_fail": "reroute_to_generator",
174
+ "on_all_pass": "advance_sprint"
175
+ },
139
176
  "pipeline_selection": {
140
177
  "comment": "Dispatcher가 pipeline.json으로 활성 에이전트를 결정.",
141
178
  "pipelines": {
142
- "FULLSTACK": ["planner", "generator-backend", "generator-frontend", "evaluator-functional", "evaluator-visual"],
143
- "FE-ONLY": ["planner:light", "generator-frontend", "evaluator-functional", "evaluator-visual"],
144
- "BE-ONLY": ["planner", "generator-backend", "evaluator-functional:api-only"]
179
+ "FULLSTACK": ["planner", "generator-backend", "generator-frontend", "evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
180
+ "FE-ONLY": ["planner:light", "generator-frontend", "evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
181
+ "BE-ONLY": ["planner", "generator-backend", "evaluator-code-quality", "evaluator-functional:api-only"]
182
+ },
183
+ "evaluator_chains": {
184
+ "comment": "파이프라인별 평가자 체인. Dispatcher가 pipeline.json.evaluator_chain 으로 기록.",
185
+ "FULLSTACK": ["evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
186
+ "FE-ONLY": ["evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
187
+ "BE-ONLY": ["evaluator-code-quality", "evaluator-functional"]
145
188
  }
146
189
  },
147
190
  "sprint_execution": {
@@ -198,7 +241,7 @@
198
241
  },
199
242
  "cross_validation": {
200
243
  "enabled": true,
201
- "comment": "evaluator-functional과 evaluator-visual이 서로의 결과를 참조하여 불일치를 감지. evaluation-*.md의 Cross-Validation Data JSON 블록을 기계적으로 파싱."
244
+ "comment": "evaluator-code-quality / evaluator-functional / evaluator-visual 세 평가자의 결과를 상호 참조하여 불일치를 감지. evaluation-*.md의 Cross-Validation Data JSON 블록을 기계적으로 파싱."
202
245
  },
203
246
  "acceptance_criteria_schema": {
204
247
  "comment": "Planner가 feature-list.json에 기능 정의 시 반드시 작성해야 하는 실행 가능한 검증 조건 스키마. Evaluator는 이 조건을 기계적으로 실행한다.",
@@ -267,6 +310,9 @@
267
310
  "api-contract.json": "draft",
268
311
  "feature-list.json": "draft"
269
312
  },
313
+ "evaluator-code-quality": {
314
+ "sprint-contract.md": "draft"
315
+ },
270
316
  "evaluator-functional": {
271
317
  "sprint-contract.md": "draft"
272
318
  },
@@ -0,0 +1,21 @@
1
+ ---
2
+ docmeta:
3
+ id: evaluator-code-quality-gotchas
4
+ title: Gotchas — Evaluator-Code-Quality
5
+ type: input
6
+ createdAt: 2026-04-22T00:00:00Z
7
+ updatedAt: 2026-04-22T00:00:00Z
8
+ source:
9
+ producer: user
10
+ skillId: harness-dispatcher
11
+ tags:
12
+ - gotchas
13
+ - evaluator
14
+ - code-quality
15
+ ---
16
+
17
+ # Gotchas — Evaluator-Code-Quality
18
+
19
+ > Dispatcher가 관리. Evaluator-Code-Quality는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
20
+
21
+ <!-- 항목이 추가되면 아래에 기록됩니다 -->
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "5.4.0",
4
- "description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer. Supports React, Next.js, and Flutter FE stacks.",
3
+ "version": "5.5.0",
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"
7
7
  },
@@ -382,7 +382,7 @@ render_dashboard() {
382
382
  render_team_status
383
383
  render_team_features
384
384
  render_team_bottleneck
385
- render_archive_prompts
385
+ # Archive Prompt는 별도 패널(harness-prompt-history.sh)에서 렌더링 — Dashboard 모니터링 영역 보호
386
386
  ;;
387
387
  *)
388
388
  render_solo_sprint_overview
@@ -393,7 +393,7 @@ render_dashboard() {
393
393
  fi
394
394
 
395
395
  render_solo_agent_info
396
- render_archive_prompts
396
+ # Archive Prompt는 별도 패널(harness-prompt-history.sh)에서 렌더링
397
397
  ;;
398
398
  esac
399
399
  }
@@ -442,7 +442,14 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
442
442
  ;;
443
443
  esac
444
444
 
445
- # ── Cross-Validation ──
445
+ # ── Cross-Validation (chain: code-quality → functional → visual) ──
446
+ local cross_validation_from_code_quality="null"
447
+ if [ "$next_agent" = "evaluator-functional" ] || [ "$next_agent" = "evaluator-visual" ]; then
448
+ local cq_eval="$PROJECT_ROOT/.harness/actions/evaluation-code-quality.md"
449
+ if [ -f "$cq_eval" ]; then
450
+ cross_validation_from_code_quality=$(sed -n '/```json/,/```/p' "$cq_eval" | tail -n +2 | head -n -1 | jq 'select(.evaluator == "code-quality" or .cross_validation_from_code_quality)' 2>/dev/null || echo "null")
451
+ fi
452
+ fi
446
453
  if [ "$next_agent" = "evaluator-visual" ]; then
447
454
  local func_eval="$PROJECT_ROOT/.harness/actions/evaluation-functional.md"
448
455
  if [ -f "$func_eval" ]; then
@@ -466,6 +473,7 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
466
473
  --argjson regression "$regression_source" \
467
474
  --argjson eval_config "$eval_config" \
468
475
  --argjson cross_val "$cross_validation_data" \
476
+ --argjson cross_val_cq "$cross_validation_from_code_quality" \
469
477
  --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
470
478
  '{
471
479
  from: $from,
@@ -482,6 +490,7 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
482
490
  regression: $regression,
483
491
  eval_config: $eval_config,
484
492
  cross_validation_from_functional: $cross_val,
493
+ cross_validation_from_code_quality: $cross_val_cq,
485
494
  warnings: [],
486
495
  timestamp: $timestamp
487
496
  }' > "$HANDOFF"
@@ -67,6 +67,7 @@ if [ -f "$FEATURE_LIST" ]; then
67
67
  total_features=$(jq '.features | length' "$FEATURE_LIST" 2>/dev/null || echo 0)
68
68
  completed_features=$(jq '[.features[]? | select(
69
69
  (.passes // []) | (
70
+ (map(select(. == "evaluator-code-quality")) | length > 0) and
70
71
  (map(select(. == "evaluator-functional")) | length > 0) and
71
72
  (map(select(. == "evaluator-visual")) | length > 0)
72
73
  )
@@ -4,13 +4,15 @@
4
4
  # Team Mode (--team):
5
5
  # ┌──────────────┬──────────────┬──────────────┐
6
6
  # │ │ │ TEAM 1 │
7
- # │ Dashboard │ Gotcha & │ │
8
- # │ (queue + │ Memory ├──────────────┤
9
- # │ status + │ │ TEAM 2 │
10
- # │ archive) │ │ │
11
- # │ │ ├──────────────┤
12
- # │ │ │ TEAM 3 │
7
+ # │ Dashboard │ │ │
8
+ # │ (queue + │ Gotcha & ├──────────────┤
9
+ # │ status + │ Memory │ TEAM 2 │
10
+ # │ features) │ │ │
11
+ # ├──────────────┤ ├──────────────┤
12
+ # │ Archive │ │ TEAM 3 │
13
+ # │ Prompt │ │ │
13
14
  # └──────────────┴──────────────┴──────────────┘
15
+ # (Archive Prompt는 별도 패널 — Dashboard 모니터링 영역 보호)
14
16
  #
15
17
  # Rendering strategy:
16
18
  # 1. iTerm2 detected → native split panes (no tmux needed)
@@ -104,6 +106,12 @@ launch_iterm2_team() {
104
106
  set name to "harness-studio"
105
107
  write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-dashboard.sh' '${PROJECT_ROOT}'"
106
108
 
109
+ -- Split down (bottom of Dashboard column) → Archive Prompt
110
+ set archivePane to (split horizontally with default profile)
111
+ tell archivePane
112
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-prompt-history.sh' '${PROJECT_ROOT}'"
113
+ end tell
114
+
107
115
  -- Split right → Gotcha & Memory
108
116
  set dashPane to (split vertically with default profile)
109
117
  tell dashPane
@@ -182,15 +190,19 @@ launch_tmux_team() {
182
190
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 2'")
183
191
  PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
184
192
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 3'")
193
+ # Dashboard 열을 상(Dashboard)/하(Archive Prompt)로 분할 — archive가 길어져도 모니터링 유지
194
+ PANE_ARCHIVE=$(tmux split-window -v -p 35 -t "$PANE_DASH" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
195
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
185
196
 
186
197
  tmux send-keys -t "$PANE_DASH" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
187
198
  tmux send-keys -t "$PANE_GOTCHA" "bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\"" Enter
188
199
 
189
- tmux select-pane -t "$PANE_DASH" -T "Dashboard"
190
- tmux select-pane -t "$PANE_GOTCHA" -T "Gotcha & Memory"
191
- tmux select-pane -t "$PANE_T1" -T "TEAM 1"
192
- tmux select-pane -t "$PANE_T2" -T "TEAM 2"
193
- tmux select-pane -t "$PANE_T3" -T "TEAM 3"
200
+ tmux select-pane -t "$PANE_DASH" -T "Dashboard"
201
+ tmux select-pane -t "$PANE_ARCHIVE" -T "Archive Prompt"
202
+ tmux select-pane -t "$PANE_GOTCHA" -T "Gotcha & Memory"
203
+ tmux select-pane -t "$PANE_T1" -T "TEAM 1"
204
+ tmux select-pane -t "$PANE_T2" -T "TEAM 2"
205
+ tmux select-pane -t "$PANE_T3" -T "TEAM 3"
194
206
  tmux select-pane -t "$PANE_DASH"
195
207
 
196
208
  tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
@@ -39,6 +39,10 @@ get_allowed_paths() {
39
39
  echo "tsconfig"
40
40
  echo "pubspec"
41
41
  ;;
42
+ evaluator-code-quality)
43
+ echo ".harness/actions/evaluation-code-quality.md"
44
+ echo ".harness/progress.json"
45
+ ;;
42
46
  evaluator-functional|evaluator-functional-flutter)
43
47
  echo ".harness/actions/evaluation-functional.md"
44
48
  echo ".harness/progress.json"
@@ -120,23 +120,29 @@ render_progress() {
120
120
  fi
121
121
 
122
122
  # Check passes
123
- be_pass="○"; fe_pass="○"; eval_func_pass="○"; eval_visual_pass="○"
123
+ be_pass="○"; fe_pass="○"; eval_cq_pass="○"; eval_func_pass="○"; eval_visual_pass="○"
124
124
  local eval_done=false
125
125
 
126
126
  while IFS= read -r p; do
127
127
  case "$p" in
128
- generator-backend) be_pass="✓" ;;
129
- generator-frontend) fe_pass="✓" ;;
130
- evaluator-functional) eval_func_pass="✓" ;;
131
- evaluator-visual) eval_visual_pass="✓" ;;
128
+ generator-backend) be_pass="✓" ;;
129
+ generator-frontend) fe_pass="✓" ;;
130
+ evaluator-code-quality) eval_cq_pass="✓" ;;
131
+ evaluator-functional) eval_func_pass="✓" ;;
132
+ evaluator-visual) eval_visual_pass="✓" ;;
132
133
  esac
133
134
  done <<< "$passes"
134
135
 
135
- # Eval column: both must pass
136
+ # Eval column: chain 내 존재하는 평가자 모두 PASS 여야 ✓
136
137
  local eval_pass="○"
137
- if [ "$eval_func_pass" = "✓" ] && [ "$eval_visual_pass" = "✓" ]; then
138
+ local eval_passed_count=0
139
+ local eval_total=3
140
+ [ "$eval_cq_pass" = "✓" ] && eval_passed_count=$((eval_passed_count + 1))
141
+ [ "$eval_func_pass" = "✓" ] && eval_passed_count=$((eval_passed_count + 1))
142
+ [ "$eval_visual_pass" = "✓" ] && eval_passed_count=$((eval_passed_count + 1))
143
+ if [ "$eval_passed_count" -eq "$eval_total" ]; then
138
144
  eval_pass="✓"
139
- elif [ "$eval_func_pass" = "✓" ] || [ "$eval_visual_pass" = "✓" ]; then
145
+ elif [ "$eval_passed_count" -gt 0 ]; then
140
146
  eval_pass="◐"
141
147
  fi
142
148
 
@@ -204,22 +210,19 @@ render_progress() {
204
210
  generator-frontend)
205
211
  if [ "$show_fe" = true ]; then done_steps=$((done_steps + 1)); fi
206
212
  ;;
207
- evaluator-functional|evaluator-visual)
208
- # Each eval counts as 0.5 of the eval step
209
- # We'll handle this by checking both at the end
213
+ evaluator-code-quality|evaluator-functional|evaluator-visual)
214
+ # Chain 내 평가자. 아래에서 통합 집계.
210
215
  ;;
211
216
  esac
212
217
  done <<< "$passes"
213
218
 
214
- # Check eval completion
215
- local ef ev
219
+ # Check eval completion — 체인상의 3개 평가자 모두 통과 시에만 1 step 인정
220
+ local ecq ef ev
221
+ ecq=$(jq -r ".features[$i].passes // [] | map(select(. == \"evaluator-code-quality\")) | length" "$FEATURES" 2>/dev/null)
216
222
  ef=$(jq -r ".features[$i].passes // [] | map(select(. == \"evaluator-functional\")) | length" "$FEATURES" 2>/dev/null)
217
223
  ev=$(jq -r ".features[$i].passes // [] | map(select(. == \"evaluator-visual\")) | length" "$FEATURES" 2>/dev/null)
218
- if [ "${ef:-0}" -gt 0 ] && [ "${ev:-0}" -gt 0 ]; then
224
+ if [ "${ecq:-0}" -gt 0 ] && [ "${ef:-0}" -gt 0 ] && [ "${ev:-0}" -gt 0 ]; then
219
225
  done_steps=$((done_steps + 1))
220
- elif [ "${ef:-0}" -gt 0 ] || [ "${ev:-0}" -gt 0 ]; then
221
- # partial eval — don't count
222
- :
223
226
  fi
224
227
 
225
228
  i=$((i + 1))
@@ -25,13 +25,8 @@ disable-model-invocation: false
25
25
  - `pipeline` → 선택된 파이프라인 (FULLSTACK/FE-ONLY/BE-ONLY)
26
26
  - `sprint.number` → `1`, `sprint.status` → `"in_progress"` (신규 파이프라인인 경우에만)
27
27
  2. `.harness/progress.log`에 요약 한 줄 추가
28
- 3. **모드 감지**: `.harness/progress.json`의 `mode` 필드 확인
29
- - **`"team"`**: Team Mode 활성. 별도 안내 없이 즉시 STOP.
30
- 출력: `"✓ Dispatcher 완료. Team Mode가 자율 실행 중입니다."`
31
- - **`"solo"` / `"paused"`**: Solo Mode.
32
- 출력: `"✓ Dispatcher 완료. 다음: /harness-solo 또는 프롬프트로 계속하세요."`
33
- 4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
34
- - Team 모드에서는 `/harness-team 실행하시겠습니까?` 같은 **질문을 하지 않는다**. Teams는 이미 실행 중이거나 사용자가 별도로 시작한다.
28
+ 3. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
29
+ 4. 출력: `"✓ Dispatcher 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
35
30
 
36
31
  ## Auto-Routing (UserPromptSubmit Hook)
37
32
 
@@ -111,13 +106,34 @@ AGENTS.md 비하네스 → 기존 백업 + 리빌드
111
106
 
112
107
  `.harness/actions/pipeline.json` 생성 → 사용자 확인 → Session Boundary Protocol On Complete 실행
113
108
 
109
+ ### evaluator_chain 필드 (모든 파이프라인 필수)
110
+
111
+ `pipeline.json` 에 **`evaluator_chain`** 배열을 기록한다. `config.json.flow.pipeline_selection.evaluator_chains.<pipeline>` 의 값을 복사:
112
+
113
+ - FULLSTACK / FE-ONLY: `["evaluator-code-quality", "evaluator-functional", "evaluator-visual"]`
114
+ - BE-ONLY: `["evaluator-code-quality", "evaluator-functional"]` (functional 은 api-only 모드)
115
+
116
+ Flutter 치환 규칙은 chain 배열의 각 원소에도 동일 적용. `fe_stack == "flutter"` + `fe_target in (mobile, desktop)` 이면 `evaluator-visual` 을 chain 에서 제거하고 `evaluator-functional` → `evaluator-functional-flutter` 로 치환한다. `evaluator-code-quality` 는 스택/타겟 무관 공통.
117
+
118
+ ### Evaluator 체인 라우팅 규칙
119
+
120
+ Generator 완료 후 `next_agent` 는 chain[0] (항상 `evaluator-code-quality`).
121
+
122
+ 각 평가자의 On Complete:
123
+ - **PASS**: chain 상 다음 평가자로 `next_agent` 설정. 마지막 평가자면 `archive`.
124
+ - **FAIL**: chain 나머지 **건너뛰고** `failure.retry_target` (해당 결함 위치의 Generator) 로 리라우팅.
125
+
126
+ Gotcha retry 시에도 체인 시작점은 chain[0] 부터 재실행.
127
+
114
128
  ### fe_stack 필드 (FE 파이프라인에서 필수)
115
129
 
116
- FE-ONLY 또는 FULLSTACK 선택 시, `pipeline.json` 에 **`fe_stack`** 필드를 포함해야 한다:
130
+ FE-ONLY 또는 FULLSTACK 선택 시, `pipeline.json`에 **`fe_stack`** 필드를 포함해야 한다:
117
131
 
118
- - `scan-result.json.tech_stack.fe_stack` (또는 `tech_stack.frontend`) 값을 기본으로 사용
119
- - 값이 없거나 불명확하면 Planner 가 확정하도록 위임 (Dispatcher 는 `"unknown"` 기록 + `notes` 에 메모)
120
- - v5.2 이후: **에이전트 분기 없음** — 동일한 `generator-frontend` / `evaluator-functional` 에이전트가 `.harness/ref/fe-<fe_stack>.md` 를 로드해 스택 적응적으로 동작
132
+ - `scan-result.json.tech_stack.fe_stack` 값을 기본으로 사용 (`react` | `flutter`)
133
+ - 값이 없거나 불명확하면 Planner가 확정하도록 위임 (Dispatcher는 `"unknown"` 기록 + `notes` 에 메모)
134
+ - Flutter 선택 시 `agents_active`/`agents_skipped`에 치환된 에이전트명을 기록
135
+ - active: `generator-frontend-flutter`, `evaluator-functional-flutter`
136
+ - skipped: `generator-frontend`, `evaluator-functional`, `evaluator-visual`
121
137
 
122
138
  ## 6. Brainstormer Routing Decision
123
139
 
@@ -172,7 +188,7 @@ Planner 를 호출해야 한다고 판단되면, **사용자에게 단 하나의
172
188
  | 상황 | next_agent |
173
189
  |------|-----------|
174
190
  | "Eval, X 다시 검증해" | `evaluator-functional` (또는 `evaluator-visual`) |
175
- | "Generator-FE, Y 버그 고쳐" | `generator-frontend` |
191
+ | "Generator-FE, Y 버그 고쳐" | `generator-frontend` (또는 Flutter 변형) |
176
192
  | "Generator-BE, API 재생성해" | `generator-backend` |
177
193
  | Eval FAIL → retry | `failure.retry_target` |
178
194
  | Gotcha 수정 | `failure.retry_target` 또는 현재 에이전트 |
@@ -190,48 +206,14 @@ Planner 를 호출해야 한다고 판단되면, **사용자에게 단 하나의
190
206
  이 경우 기존 `.harness/actions/brainstorm-spec.md` 는 Brainstormer 의 On Start 에서
191
207
  `.harness/archive/brainstorm-spec-<timestamp>.md` 로 백업된다.
192
208
 
193
- ## 7. Handoff 라우팅 (v5.2 — 스택 치환 없음)
194
-
195
- v5.2 이후 에이전트 치환은 사용하지 않는다. Dispatcher 가 `next_agent` 를 세팅할 때 `pipeline.json.fe_stack` 은 **참고용 메타데이터**로만 기록되고, 실제 스택별 행동은 에이전트가 자기 On Start 에서 `.harness/ref/<role>-<stack>.md` 를 로드해 결정한다.
196
-
197
- | 에이전트 | 스택 적응 메커니즘 |
198
- |---------|------------------|
199
- | `generator-frontend` | On Start 에서 `.harness/ref/fe-<stack>.md` 로드 |
200
- | `generator-backend` | On Start 에서 `.harness/ref/be-<stack>.md` 로드 |
201
- | `evaluator-functional` | `ref.validation.pre_eval_gate` / `functional_tests` / `anti_pattern_rules` 실행 |
202
- | `evaluator-visual` | `ref.validation.visual.enabled == false` 면 MANUAL_REQUIRED 로 skip |
203
-
204
- ## 8. Auto Gotcha Registration (v5.2)
205
-
206
- evaluator-functional / evaluator-visual 이 안티패턴을 발견하면 Dispatcher 경유로 자동 gotcha 파일에 등록한다. 이 섹션은 Dispatcher 가 등록 이벤트를 받았을 때의 행동을 정의한다.
207
-
208
- ### 8.1 수신 이벤트 페이로드
209
-
210
- `api-contract.json.contracts["gotcha_register_interface"]` 참조. 필수 필드:
211
- - `agent` (예: `"generator-frontend"`)
212
- - `stack` (예: `"swift"`)
213
- - `rule_id`, `severity`, `occurrences[] (file/line/snippet)`, `source_feature`
214
-
215
- ### 8.2 대상 파일 결정
216
-
217
- `.harness/gotchas/<agent>-<stack>.md` (없으면 생성). 스택 특정 규칙이 아닌 공통 규칙은 `<agent>.md` 로 라우팅 (gotcha-flow.md 의 "라우팅 규칙" 참조).
218
-
219
- ### 8.3 등록 절차
220
-
221
- ```
222
- 1. 대상 파일 열기 (없으면 표준 헤더로 생성)
223
- 2. 기존 항목 중 같은 rule_id 검색:
224
- - 있음 → Occurrences +1, Last seen 업데이트, snippet 최신으로 교체
225
- - 없음 → 다음 G-NNN 번호로 새 항목 추가
226
- 3. progress.log 에 `"auto_gotcha_registered"` 이벤트 기록
227
- 4. evaluation-*.md 에 "Registered gotchas: <G-IDs>" 요약 기록
228
- ```
229
-
230
- 항목 포맷은 gotcha-flow.md 의 "Gotcha 항목 형식" 섹션과 동일.
209
+ ## 7. Handoff 라우팅 (fe_stack 반영)
231
210
 
232
- ### 8.4 멱등성 / 중복 방지
211
+ Dispatcher가 `next_agent` 를 세팅할 때 pipeline.json.fe_stack 을 참조해 치환:
233
212
 
234
- - 동일 feature 의 동일 rule_id 는 한 번의 평가 안에서 최대 1회만 Occurrences 증가
235
- - Retry 시에는 이전 Occurrences 유지 (재평가이므로 중복 카운팅 금지)
213
+ | 원본 next_agent | fe_stack=react | fe_stack=flutter |
214
+ |-----------------|----------------|------------------|
215
+ | generator-frontend | generator-frontend | generator-frontend-flutter |
216
+ | evaluator-functional (FE 단계) | evaluator-functional | evaluator-functional-flutter |
217
+ | evaluator-visual | evaluator-visual | (skip → 다음 단계로 이동) |
236
218
 
237
- **Brainstormer 는 스택 치환 대상이 아니다** — 언어/스택 무관 공통 에이전트.
219
+ **Brainstormer 는 fe_stack 치환 대상이 아니다** — 언어/스택 무관 공통 에이전트.
@@ -58,6 +58,23 @@ if pipeline.json.fe_stack == "flutter":
58
58
  "evaluator-visual" → __skip__
59
59
  ```
60
60
 
61
+ ## Evaluator Chain (공통)
62
+
63
+ 모든 파이프라인의 Generator 이후 단계는 **평가자 체인**으로 처리한다. 직렬 실행 + 조기 종료:
64
+
65
+ ```
66
+ Generator → evaluator-code-quality → evaluator-functional → evaluator-visual → archive
67
+ │ │ │
68
+ └─ FAIL ─ reroute ────────┴──────────────────────┘
69
+ → Generator (failure.retry_target)
70
+ ```
71
+
72
+ - **code-quality** (정적, 저비용, 브라우저 없음): BE/FE/libs 공통 코드 품질
73
+ - **functional** (동작, 중비용): API·E2E 행동 검증
74
+ - **visual** (렌더, 고비용): 레이아웃/접근성
75
+
76
+ 앞단 FAIL 시 뒷단은 실행하지 않는다. 구조가 깨진 코드에 동작 테스트 낭비 방지.
77
+
61
78
  ## FE-ONLY
62
79
 
63
80
  ```yaml
@@ -67,15 +84,20 @@ agents:
67
84
  skip: MSA 서비스 설계, BE 기능 목록
68
85
  do: OpenAPI → api-contract.json 변환, FE 컴포넌트 설계, feature-list (layer: frontend만), fe_stack 확정
69
86
  - generator-frontend OR generator-frontend-flutter # fe_stack에 따라
87
+ - evaluator-code-quality # 공통 — 브라우저 없음
70
88
  - evaluator-functional OR evaluator-functional-flutter
71
- - evaluator-visual # fe_stack == "flutter" 이면 SKIP
89
+ - evaluator-visual # fe_stack == "flutter" + fe_target in (mobile,desktop) 이면 SKIP
90
+ evaluator_chain:
91
+ - evaluator-code-quality
92
+ - evaluator-functional
93
+ - evaluator-visual
72
94
  skip:
73
95
  - generator-backend
74
96
  notes:
75
97
  - api-contract.json은 OpenAPI에서 파생 (Planner가 변환)
76
98
  - Eval-Func(React)의 API Health Check는 외부 서버 대상
77
99
  - AGENTS.md IA-MAP에 BE 경로 없음 (외부 서버)
78
- - fe_stack=flutter 인 경우 evaluator-visual 생략
100
+ - fe_stack=flutter (mobile/desktop) 인 경우 evaluator-visual 생략
79
101
  ```
80
102
 
81
103
  ## BE-ONLY
@@ -87,15 +109,20 @@ agents:
87
109
  skip: FE 컴포넌트 설계, 비주얼 요구사항
88
110
  do: 기존 코드 분석, 신규 API 설계, api-contract.json 확장
89
111
  - generator-backend
112
+ - evaluator-code-quality # 공통
90
113
  - evaluator-functional:
91
114
  mode: api-only
92
115
  skip: browser 테스트
93
116
  do: curl/httpie로 API 엔드포인트 직접 검증
117
+ evaluator_chain:
118
+ - evaluator-code-quality
119
+ - evaluator-functional
94
120
  skip:
95
121
  - generator-frontend
96
122
  - evaluator-visual
97
123
  notes:
98
124
  - Eval-Func는 Playwright 대신 CLI 기반 API 테스트
125
+ - Code-Quality 는 BE 코드의 레이어/DI/DTO/에러 전파/테스트 품질 감사
99
126
  ```
100
127
 
101
128
  ## FULLSTACK
@@ -106,6 +133,11 @@ agents:
106
133
  - planner (full)
107
134
  - generator-backend
108
135
  - generator-frontend
136
+ - evaluator-code-quality
137
+ - evaluator-functional
138
+ - evaluator-visual
139
+ evaluator_chain:
140
+ - evaluator-code-quality
109
141
  - evaluator-functional
110
142
  - evaluator-visual
111
143
  skip: none
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: harness-evaluator-code-quality
3
+ description: "하네스 Code-Quality Evaluator. 브라우저 없이 코드 자체를 읽어 유지보수성·모범사례 준수·아키텍처 건전성을 검사한다. BE/FE/lib 전 영역 적용. C1-C5 축으로 적대적 채점. 기준 미달 = FAIL."
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Evaluator-Code-Quality — Static Code Audit (No Browser)
8
+
9
+ > Functional/Visual 평가자보다 **먼저** 실행된다. 코드가 구조적으로 망가졌으면
10
+ > 동작 테스트는 의미 없다. 이 게이트에서 FAIL 이면 Functional/Visual 은 아예 시작도 하지 않는다.
11
+
12
+ ## Session Boundary Protocol
13
+
14
+ ### On Start
15
+ 1. `.harness/progress.json` 읽기 — `next_agent`가 `"evaluator-code-quality"`인지 확인
16
+ 2. progress.json 업데이트: `current_agent` → `"evaluator-code-quality"`, `agent_status` → `"running"`, `updated_at` 갱신
17
+
18
+ ### On Complete (PASS)
19
+ 1. progress.json 업데이트:
20
+ - `agent_status` → `"completed"`
21
+ - `completed_agents`에 `"evaluator-code-quality"` 추가
22
+ - `next_agent` → **evaluator chain의 다음 노드** (일반적으로 `"evaluator-functional"`; chain 이 `evaluator-code-quality` 단독이면 `"archive"`)
23
+ - `failure` 필드 초기화
24
+ 2. `feature-list.json`의 통과 feature `passes`에 `"evaluator-code-quality"` 추가
25
+ 3. `.harness/progress.log`에 PASS 요약 추가
26
+ 4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
27
+ 5. 출력: `"✓ Evaluator-Code-Quality PASS. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
28
+
29
+ ### On Fail
30
+ 1. progress.json 업데이트:
31
+ - `agent_status` → `"failed"`
32
+ - `failure.agent` → `"evaluator-code-quality"`
33
+ - `failure.location` → `"backend"` / `"frontend"` / `"shared"` (결함 위치; 혼재 시 주 결함 위치)
34
+ - `failure.message` → 실패 요약 (1줄)
35
+ - `failure.retry_target` → 결함 위치에 대응하는 Generator (`"generator-backend"` 또는 `"generator-frontend"`)
36
+ - `next_agent` → `failure.retry_target`
37
+ - `sprint.retry_count` 증가
38
+ 2. `sprint.retry_count >= 10`이면 `agent_status` → `"blocked"`, 사용자 개입 요청
39
+ 3. `.harness/progress.log`에 FAIL 요약 추가
40
+ 4. **STOP.**
41
+ 5. 출력: `"✖ Evaluator-Code-Quality FAIL. bash scripts/harness-next.sh 실행하여 재작업 대상 확인."`
42
+
43
+ ## Critical Mindset
44
+
45
+ - **회의적 시니어 리뷰어.** Generator가 "깔끔하다"고 주장해도 직접 읽는다.
46
+ - 실행·렌더링 없이 판정. 브라우저/서버 기동 금지 (불필요한 비용).
47
+ - 파일 수정 금지. 오직 읽기 + 정적 분석기 실행.
48
+ - "동작하니 통과"는 Functional 의 일. 여기서는 **동작해도 구조가 나쁘면 FAIL**.
49
+ - Best Practice 위반을 "사소하다"고 자기설득 금지.
50
+
51
+ ## Scope — "시각과 무관한 코드 품질 전부"
52
+
53
+ 이 평가자는 **도메인(BE/FE)을 구분하지 않는다.**
54
+
55
+ - Backend: controller/service/repo 레이어링, DI, DTO, 에러 전파, 트랜잭션 경계, MSA 메시지 패턴
56
+ - Frontend 비주얼-외 로직: 상태관리(store/VM/hooks), 데이터 페칭, 라우팅, API 어댑터, 유틸
57
+ - Shared libs: 공유 DTO/유틸/타입 정의, 순환 의존, 과도한 export
58
+
59
+ **제외 영역** (다른 평가자 담당):
60
+ - 렌더링 결과, 레이아웃, 스크린샷 → Evaluator-Visual
61
+ - 엔드포인트 응답값·사용자 플로우 동작 → Evaluator-Functional
62
+
63
+ ## Startup
64
+
65
+ 1. `AGENTS.md` 읽기 — IA-MAP (레이어 경계)
66
+ 2. `CONVENTIONS.md` 읽기 — 프로젝트 컨벤션 (있을 때만)
67
+ 3. `.harness/gotchas/evaluator-code-quality.md` 읽기 — **과거 실수 반복 금지**
68
+ 4. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
69
+ 5. `actions/sprint-contract.md` — 이번 스프린트 변경 범위
70
+ 6. `actions/feature-list.json` — 기능 정의
71
+ 7. `actions/api-contract.json` — DTO 형태 (계약 vs 구현 일치 확인용)
72
+ 8. `.harness/progress.json`
73
+
74
+ ## Evaluation Steps
75
+
76
+ ### Step 0: Diff Scope Extraction
77
+
78
+ 이번 스프린트에서 **실제로 수정된 파일만** 검사한다:
79
+
80
+ ```bash
81
+ git diff --name-only <sprint_base>..HEAD
82
+ ```
83
+
84
+ - 수정 파일이 없으면 즉시 FAIL (스프린트 자체가 빈 상태).
85
+ - 수정 파일 수가 너무 많으면 (>50 변경) Planner 에스컬레이션 고려.
86
+
87
+ ### Step 1: Static Toolchain Execution
88
+
89
+ 결정론적 검사를 먼저 돌려 근본적 문제를 배제:
90
+
91
+ - `tsc --noEmit` (전체 or 영향 패키지)
92
+ - `eslint <변경 파일>` (biome 있으면 `biome check`)
93
+ - 프로젝트가 제공하는 lint/type check 스크립트 (`npm run lint:quality` 등)
94
+
95
+ **하나라도 error 레벨 실패 → 즉시 FAIL** (나머지 축 채점 전 조기 종료).
96
+
97
+ ### Step 2: C1-C5 축별 코드 리딩
98
+
99
+ 변경 파일을 각 축으로 읽어 evidence 수집. 상세 루브릭 → [scoring-rubric](references/scoring-rubric.md)
100
+
101
+ | # | 축 | 무엇을 보는가 | Weight |
102
+ |---|----|--------------|--------|
103
+ | C1 | Layer & Boundary | IA-MAP/MSA 경계 준수, 레이어 역방향 의존 없음, FE VM↔View 경계 | 25% |
104
+ | C2 | Readability & Complexity | 네이밍, 함수 길이/중첩, 매직 넘버, 주석 남용/부재, dead code | 15% |
105
+ | C3 | Reuse & DRY | 기존 util/hook/dto 활용, 중복 로직 없음, 조기 추상화 아님 | 20% |
106
+ | C4 | Type Safety & Error Handling | `any` 남용, null 처리, 예외 전파 경로, 경계 입력 검증 | 25% |
107
+ | C5 | Test Quality | 행동 기반 (not 구현 결합), mock 남용 없음, AC 매핑, 커버리지 의미 | 15% |
108
+
109
+ ### Step 3: Cross-Reference With Contracts
110
+
111
+ - `api-contract.json` ↔ 실제 DTO/controller 시그니처 불일치 감지
112
+ - `feature-list.json` 의 feature 경계 ↔ 구현 파일 위치 IA-MAP 준수
113
+
114
+ ### Step 4: Verdict
115
+
116
+ - 가중 점수 < 2.80 → FAIL
117
+ - 축 하나라도 Score 0 → FAIL (evidence 없는 Score 는 0 강제)
118
+ - Step 1 toolchain 실패 → FAIL
119
+ - Contract 불일치 1건 이상 → FAIL
120
+
121
+ ### Step 5: Output
122
+
123
+ `actions/evaluation-code-quality.md` 작성:
124
+
125
+ - 축별 Score(0-3), 근거 evidence (파일:라인), 개선 제안
126
+ - Toolchain 실행 로그 요약
127
+ - 수정 파일 목록 + diff 통계
128
+ - 최종 Verdict + 재작업 대상 (retry_target)
129
+
130
+ Cross-Validation 데이터 블록 포함 (Functional/Visual 이 참조):
131
+
132
+ ```json
133
+ {
134
+ "cross_validation_from_code_quality": {
135
+ "layer_violations": [...],
136
+ "type_holes": [...],
137
+ "contract_divergence": [...]
138
+ }
139
+ }
140
+ ```
141
+
142
+ ## Adversarial Rules
143
+
144
+ - "동작하니 PASS" 금지. 여기서는 구조를 본다.
145
+ - "이번 스프린트 외 코드라 건너뜀" 금지. 변경 파일 범위 안에서는 전수 검사.
146
+ - Evidence (파일:라인) 없는 Score = 0.
147
+ - "사소한 중복", "관용적 any" 같은 자기설득 금지.
148
+ - Generator가 이미 같은 패턴을 반복 제출 중이면 **반복 페널티** (같은 축에서 이전 스프린트 대비 악화 → Score 최대 1).
149
+ - 리팩토링 제안만 하고 Pass 주는 건 금지 — 제안이 필수 개선이면 FAIL.
150
+
151
+ ## Forbidden
152
+
153
+ - 브라우저/서버 기동
154
+ - 코드 수정/커밋
155
+ - "Functional 에서 확인하세요" 식의 책임 전가 (여기서 코드로 확인 가능한 것은 여기서 한다)
156
+ - "시간 제약" 핑계 — 변경 범위는 유한하다
157
+
158
+ ## After Evaluation
159
+
160
+ - **PASS** → Session Boundary Protocol On Complete (PASS) 실행
161
+ - **FAIL** → Session Boundary Protocol On Fail 실행
@@ -0,0 +1,104 @@
1
+ ---
2
+ docmeta:
3
+ id: scoring-rubric
4
+ title: Code-Quality Scoring Rubric
5
+ type: output
6
+ createdAt: 2026-04-22T00:00:00Z
7
+ updatedAt: 2026-04-22T00:00:00Z
8
+ source:
9
+ producer: agent
10
+ skillId: harness-evaluator-code-quality
11
+ inputs:
12
+ - documentId: harness-evaluator-code-quality-skill
13
+ uri: ../SKILL.md
14
+ relation: output-from
15
+ sections:
16
+ - sourceRange:
17
+ startLine: 76
18
+ endLine: 84
19
+ targetRange:
20
+ startLine: 20
21
+ endLine: 85
22
+ tags:
23
+ - evaluator
24
+ - code-quality
25
+ - rubric
26
+ ---
27
+
28
+ # Code-Quality Scoring Rubric (C1-C5)
29
+
30
+ Score 척도: 0 (Evidence 없음 / Critical 위반) · 1 (Major 위반) · 2 (Minor 위반) · 3 (위반 없음 + 긍정 evidence)
31
+
32
+ ## C1. Layer & Boundary (25%)
33
+
34
+ **본다:**
35
+ - AGENTS.md IA-MAP 을 위반하는 import (예: FE에서 BE internal 참조)
36
+ - MSA 서비스 간 직접 DB 접근 (메시지 패턴이 원칙)
37
+ - Backend: controller → service → repo 단방향. 역방향/건너뜀 금지.
38
+ - Frontend: view 가 fetch/store 를 직접 조작하지 않고 VM/hook 경유
39
+ - libs/shared-dto 가 apps/ 를 역참조하지 않음
40
+
41
+ **0점 조건:** 레이어 역방향 의존 1건 이상, 또는 MSA 경계 위반.
42
+
43
+ ## C2. Readability & Complexity (15%)
44
+
45
+ **본다:**
46
+ - 함수/메서드 50줄 초과, 중첩 4단 초과
47
+ - 매직 넘버/문자열 (상수화 없음)
48
+ - 네이밍: 역할을 말하지 않는 이름 (`data`, `tmp`, `handle`)
49
+ - 주석이 what을 설명 (코드로 말해야 함) / 왜를 빠뜨림
50
+ - dead code, 주석 처리된 블록
51
+
52
+ **0점 조건:** 300줄 이상 단일 함수, 또는 다수의 dead code 블록 방치.
53
+
54
+ ## C3. Reuse & DRY (20%)
55
+
56
+ **본다:**
57
+ - 이미 존재하는 util/hook/service 를 무시하고 재구현
58
+ - 3회 이상 반복되는 동일 로직
59
+ - 조기 추상화 (단 1회 사용하는 generic factory 등) — 반대 방향 위반
60
+ - lib/shared-dto 확장 없이 로컬 DTO 중복 선언
61
+
62
+ **0점 조건:** 기존 util 무시하고 동일 로직 3건+ 재구현.
63
+
64
+ ## C4. Type Safety & Error Handling (25%)
65
+
66
+ **본다:**
67
+ - `any`, `as any`, `@ts-ignore`, `@ts-expect-error` — 각 건 근거 필수
68
+ - nullable 무시, non-null 단언(`!`) 남용
69
+ - try/catch 가 오류를 삼킴 (`catch {}`, `catch(e) { console.log }`)
70
+ - 경계(API/외부 입력)에서 zod/class-validator 등 검증 부재
71
+ - 비동기 race / unhandled promise
72
+
73
+ **0점 조건:** unhandled promise 1건 이상, 또는 비타당한 `any` 3건 이상.
74
+
75
+ ## C5. Test Quality (15%)
76
+
77
+ **본다:**
78
+ - 테스트가 구현 세부(내부 함수 호출 순서)에 결합
79
+ - mock 남용 — 실제 계약이 아닌 스텁 세계를 검증
80
+ - AC 와 테스트 케이스 매핑 부재
81
+ - 행복 경로만, 엣지/에러 케이스 없음
82
+ - 커버리지 숫자 채우기용 (`expect(x).toBeDefined()`)
83
+
84
+ **0점 조건:** 신규 기능에 테스트 0건, 또는 AC와 매핑되는 테스트 0건.
85
+
86
+ ## Weighted Verdict
87
+
88
+ ```
89
+ Weighted = C1*0.25 + C2*0.15 + C3*0.20 + C4*0.25 + C5*0.15
90
+ PASS : Weighted >= 2.80 AND 모든 축 Score > 0
91
+ FAIL : Weighted < 2.80 OR any axis = 0 OR toolchain error OR contract divergence
92
+ ```
93
+
94
+ ## Evidence 형식
95
+
96
+ 모든 Score 주장은 아래 형식의 evidence 를 동반해야 한다:
97
+
98
+ ```
99
+ [C4:1] apps/service-user/src/user.service.ts:42
100
+ - `result as any` 로 타입 우회. DTO 정의 존재(libs/shared-dto/user.dto.ts:15)에도
101
+ 불구하고 강제 캐스팅. 타당한 사유 없음.
102
+ ```
103
+
104
+ Evidence 없는 Score 는 **자동 0점 재계산**.