@walwal-harness/cli 5.4.1 → 5.5.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/assets/templates/HARNESS.md +38 -6
- package/assets/templates/config.json +54 -8
- package/gotchas/evaluator-code-quality.md +21 -0
- package/package.json +2 -2
- package/scripts/harness-dashboard.sh +1 -1
- package/scripts/harness-next.sh +10 -1
- package/scripts/harness-statusline.sh +1 -0
- package/scripts/lib/harness-guardrail.sh +4 -0
- package/scripts/lib/harness-render-progress.sh +21 -18
- package/skills/dispatcher/SKILL.md +36 -63
- package/skills/dispatcher/references/pipeline-definitions.md +34 -2
- package/skills/evaluator-code-quality/SKILL.md +161 -0
- package/skills/evaluator-code-quality/references/scoring-rubric.md +104 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
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-
|
|
74
|
-
Eval-
|
|
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": "
|
|
4
|
-
"version": "5.
|
|
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
|
|
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
|
-
"description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator(
|
|
3
|
+
"version": "5.5.1",
|
|
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
|
},
|
|
@@ -271,7 +271,7 @@ render_team_features() {
|
|
|
271
271
|
($q[0].queue.in_progress // {}) as $prog |
|
|
272
272
|
.features[] |
|
|
273
273
|
.id as $fid |
|
|
274
|
-
(.name // .description // "?" | if length > 18 then .[0:16] + ".." else . end) as $fname |
|
|
274
|
+
(.name // .title // .description // "?" | if length > 18 then .[0:16] + ".." else . end) as $fname |
|
|
275
275
|
(if ($fid | IN($passed[])) then "P"
|
|
276
276
|
elif $prog[$fid] then "I|\($prog[$fid].team)|\($prog[$fid].phase)"
|
|
277
277
|
elif ($fid | IN($failed[])) then "F"
|
package/scripts/harness-next.sh
CHANGED
|
@@ -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
|
)
|
|
@@ -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"
|
|
@@ -111,7 +111,7 @@ render_progress() {
|
|
|
111
111
|
while [ $i -lt "$total_features" ]; do
|
|
112
112
|
local fid fname passes be_pass fe_pass eval_func_pass eval_visual_pass
|
|
113
113
|
fid=$(jq -r ".features[$i].id // \"F-$((i+1))\"" "$FEATURES")
|
|
114
|
-
fname=$(jq -r ".features[$i].name // .features[$i].description // \"Feature $((i+1))\"" "$FEATURES")
|
|
114
|
+
fname=$(jq -r ".features[$i].name // .features[$i].title // .features[$i].description // \"Feature $((i+1))\"" "$FEATURES")
|
|
115
115
|
passes=$(jq -r ".features[$i].passes // [] | .[]" "$FEATURES" 2>/dev/null)
|
|
116
116
|
|
|
117
117
|
# Truncate name to 20 chars
|
|
@@ -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)
|
|
129
|
-
generator-frontend)
|
|
130
|
-
evaluator-
|
|
131
|
-
evaluator-
|
|
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:
|
|
136
|
+
# Eval column: chain 내 존재하는 평가자 모두 PASS 여야 ✓
|
|
136
137
|
local eval_pass="○"
|
|
137
|
-
|
|
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 [ "$
|
|
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
|
-
#
|
|
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,22 +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.
|
|
29
|
-
|
|
30
|
-
출력 (아래 3줄을 그대로 사용):
|
|
31
|
-
```
|
|
32
|
-
✓ Dispatcher 완료 — pipeline.json 생성됨 (next_agent=<값>).
|
|
33
|
-
→ 별도 tmux 창의 Team Worker들이 이 파일을 감지해 자동으로 다음 에이전트를 실행합니다. 사용자 입력은 필요 없습니다.
|
|
34
|
-
→ 진행 상황은 tmux Dashboard/Team 패널에서 확인하세요. 중단하려면 `bash scripts/harness-tmux.sh --kill`.
|
|
35
|
-
```
|
|
36
|
-
- **`"solo"` / `"paused"`**: Solo Mode. 자동 실행되는 것이 아무것도 없음 — 사용자 입력 대기.
|
|
37
|
-
출력 (아래 2줄을 그대로 사용):
|
|
38
|
-
```
|
|
39
|
-
✓ Dispatcher 완료 — pipeline.json 생성됨 (next_agent=<값>). 자동 실행은 시작되지 않았습니다.
|
|
40
|
-
→ 다음 에이전트를 돌리려면 `/harness-solo` 를 입력하거나, 그냥 후속 지시를 프롬프트로 주세요.
|
|
41
|
-
```
|
|
42
|
-
4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
43
|
-
- Team 모드에서는 `/harness-team 실행하시겠습니까?` 같은 **질문을 하지 않는다**. Teams는 이미 실행 중이거나 사용자가 별도로 시작한다.
|
|
28
|
+
3. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
29
|
+
4. 출력: `"✓ Dispatcher 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
|
|
44
30
|
|
|
45
31
|
## Auto-Routing (UserPromptSubmit Hook)
|
|
46
32
|
|
|
@@ -120,13 +106,34 @@ AGENTS.md 비하네스 → 기존 백업 + 리빌드
|
|
|
120
106
|
|
|
121
107
|
`.harness/actions/pipeline.json` 생성 → 사용자 확인 → Session Boundary Protocol On Complete 실행
|
|
122
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
|
+
|
|
123
128
|
### fe_stack 필드 (FE 파이프라인에서 필수)
|
|
124
129
|
|
|
125
|
-
FE-ONLY 또는 FULLSTACK 선택 시, `pipeline.json
|
|
130
|
+
FE-ONLY 또는 FULLSTACK 선택 시, `pipeline.json`에 **`fe_stack`** 필드를 포함해야 한다:
|
|
126
131
|
|
|
127
|
-
- `scan-result.json.tech_stack.fe_stack` (
|
|
128
|
-
- 값이 없거나 불명확하면 Planner
|
|
129
|
-
-
|
|
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`
|
|
130
137
|
|
|
131
138
|
## 6. Brainstormer Routing Decision
|
|
132
139
|
|
|
@@ -181,7 +188,7 @@ Planner 를 호출해야 한다고 판단되면, **사용자에게 단 하나의
|
|
|
181
188
|
| 상황 | next_agent |
|
|
182
189
|
|------|-----------|
|
|
183
190
|
| "Eval, X 다시 검증해" | `evaluator-functional` (또는 `evaluator-visual`) |
|
|
184
|
-
| "Generator-FE, Y 버그 고쳐" | `generator-frontend` |
|
|
191
|
+
| "Generator-FE, Y 버그 고쳐" | `generator-frontend` (또는 Flutter 변형) |
|
|
185
192
|
| "Generator-BE, API 재생성해" | `generator-backend` |
|
|
186
193
|
| Eval FAIL → retry | `failure.retry_target` |
|
|
187
194
|
| Gotcha 수정 | `failure.retry_target` 또는 현재 에이전트 |
|
|
@@ -199,48 +206,14 @@ Planner 를 호출해야 한다고 판단되면, **사용자에게 단 하나의
|
|
|
199
206
|
이 경우 기존 `.harness/actions/brainstorm-spec.md` 는 Brainstormer 의 On Start 에서
|
|
200
207
|
`.harness/archive/brainstorm-spec-<timestamp>.md` 로 백업된다.
|
|
201
208
|
|
|
202
|
-
## 7. Handoff 라우팅 (
|
|
203
|
-
|
|
204
|
-
v5.2 이후 에이전트 치환은 사용하지 않는다. Dispatcher 가 `next_agent` 를 세팅할 때 `pipeline.json.fe_stack` 은 **참고용 메타데이터**로만 기록되고, 실제 스택별 행동은 에이전트가 자기 On Start 에서 `.harness/ref/<role>-<stack>.md` 를 로드해 결정한다.
|
|
205
|
-
|
|
206
|
-
| 에이전트 | 스택 적응 메커니즘 |
|
|
207
|
-
|---------|------------------|
|
|
208
|
-
| `generator-frontend` | On Start 에서 `.harness/ref/fe-<stack>.md` 로드 |
|
|
209
|
-
| `generator-backend` | On Start 에서 `.harness/ref/be-<stack>.md` 로드 |
|
|
210
|
-
| `evaluator-functional` | `ref.validation.pre_eval_gate` / `functional_tests` / `anti_pattern_rules` 실행 |
|
|
211
|
-
| `evaluator-visual` | `ref.validation.visual.enabled == false` 면 MANUAL_REQUIRED 로 skip |
|
|
212
|
-
|
|
213
|
-
## 8. Auto Gotcha Registration (v5.2)
|
|
214
|
-
|
|
215
|
-
evaluator-functional / evaluator-visual 이 안티패턴을 발견하면 Dispatcher 경유로 자동 gotcha 파일에 등록한다. 이 섹션은 Dispatcher 가 등록 이벤트를 받았을 때의 행동을 정의한다.
|
|
216
|
-
|
|
217
|
-
### 8.1 수신 이벤트 페이로드
|
|
218
|
-
|
|
219
|
-
`api-contract.json.contracts["gotcha_register_interface"]` 참조. 필수 필드:
|
|
220
|
-
- `agent` (예: `"generator-frontend"`)
|
|
221
|
-
- `stack` (예: `"swift"`)
|
|
222
|
-
- `rule_id`, `severity`, `occurrences[] (file/line/snippet)`, `source_feature`
|
|
223
|
-
|
|
224
|
-
### 8.2 대상 파일 결정
|
|
225
|
-
|
|
226
|
-
`.harness/gotchas/<agent>-<stack>.md` (없으면 생성). 스택 특정 규칙이 아닌 공통 규칙은 `<agent>.md` 로 라우팅 (gotcha-flow.md 의 "라우팅 규칙" 참조).
|
|
227
|
-
|
|
228
|
-
### 8.3 등록 절차
|
|
229
|
-
|
|
230
|
-
```
|
|
231
|
-
1. 대상 파일 열기 (없으면 표준 헤더로 생성)
|
|
232
|
-
2. 기존 항목 중 같은 rule_id 검색:
|
|
233
|
-
- 있음 → Occurrences +1, Last seen 업데이트, snippet 최신으로 교체
|
|
234
|
-
- 없음 → 다음 G-NNN 번호로 새 항목 추가
|
|
235
|
-
3. progress.log 에 `"auto_gotcha_registered"` 이벤트 기록
|
|
236
|
-
4. evaluation-*.md 에 "Registered gotchas: <G-IDs>" 요약 기록
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
항목 포맷은 gotcha-flow.md 의 "Gotcha 항목 형식" 섹션과 동일.
|
|
209
|
+
## 7. Handoff 라우팅 (fe_stack 반영)
|
|
240
210
|
|
|
241
|
-
|
|
211
|
+
Dispatcher가 `next_agent` 를 세팅할 때 pipeline.json.fe_stack 을 참조해 치환:
|
|
242
212
|
|
|
243
|
-
|
|
244
|
-
|
|
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 → 다음 단계로 이동) |
|
|
245
218
|
|
|
246
|
-
**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점 재계산**.
|