@walwal-harness/cli 1.2.0 → 2.0.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/README.md +12 -1
- package/assets/templates/AGENTS.md.template +1 -1
- package/assets/templates/HARNESS.md +41 -41
- package/assets/templates/config.json +44 -19
- package/assets/templates/progress.json.template +22 -0
- package/bin/init.js +136 -7
- package/package.json +1 -1
- package/scripts/harness-next.sh +140 -0
- package/scripts/harness-session-start.sh +31 -0
- package/scripts/init-agents-md.sh +2 -2
- package/scripts/lib/harness-render-progress.sh +318 -0
- package/skills/dispatcher/SKILL.md +18 -1
- package/skills/evaluator-functional/SKILL.md +34 -3
- package/skills/evaluator-visual/SKILL.md +33 -2
- package/skills/generator-backend/SKILL.md +19 -1
- package/skills/generator-frontend/SKILL.md +19 -1
- package/skills/planner/SKILL.md +18 -4
- package/assets/templates/progress.txt.template +0 -15
package/README.md
CHANGED
|
@@ -135,7 +135,7 @@ your-project/
|
|
|
135
135
|
├── CLAUDE.md → AGENTS.md # 심볼릭 링크
|
|
136
136
|
├── .harness/
|
|
137
137
|
│ ├── config.json # 하네스 설정
|
|
138
|
-
│ ├── progress.
|
|
138
|
+
│ ├── progress.json # 세션 간 상태 (기계 판독)
|
|
139
139
|
│ ├── gotchas/ # 에이전트별 실수 기록
|
|
140
140
|
│ │ ├── planner.md
|
|
141
141
|
│ │ ├── generator-backend.md
|
|
@@ -170,6 +170,17 @@ your-project/
|
|
|
170
170
|
| Unit Testing | Jest (BE) + Vitest (FE) |
|
|
171
171
|
| Database | PostgreSQL / SQLite |
|
|
172
172
|
|
|
173
|
+
## 권장 외부 스킬
|
|
174
|
+
|
|
175
|
+
하네스는 자체 reference 파일로 기본 가이드를 제공하지만, 아래 외부 스킬을 설치하면 품질이 향상됩니다. 초기화 시 자동으로 설치 여부를 체크하고 안내합니다.
|
|
176
|
+
|
|
177
|
+
| 스킬 | 설명 | 사용 에이전트 |
|
|
178
|
+
|------|------|-------------|
|
|
179
|
+
| **vercel** | Vercel/Next.js 배포, 성능 최적화, AI SDK | Generator-FE |
|
|
180
|
+
| **web-design-guidelines** | UI/UX 접근성, Web Interface Guidelines | Evaluator-Visual, Generator-FE |
|
|
181
|
+
| **taste-skill** | AI 생성 UI 디자인 품질, AI슬롭 감지 | Evaluator-Visual, Generator-FE |
|
|
182
|
+
| **supanova-design-skill** | 디자인 시스템 규칙, 시각적 일관성 | Evaluator-Visual, Generator-FE |
|
|
183
|
+
|
|
173
184
|
## Playwright MCP 설정
|
|
174
185
|
|
|
175
186
|
Evaluator가 브라우저 테스트를 수행하려면 Playwright MCP가 필요합니다. `~/.mcp.json`에 추가:
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
| AGENTS.md | 전체 | Planner만 |
|
|
39
39
|
| .harness/actions/api-contract.json | 전체 | Planner만 |
|
|
40
40
|
| .harness/actions/feature-list.json | 전체 | passes: Generator, 나머지: Planner |
|
|
41
|
-
| .harness/progress.
|
|
41
|
+
| .harness/progress.json | 전체 | 전체 (Session Boundary Protocol에 따라 업데이트) |
|
|
42
42
|
| `[BE]` 소유 경로 | 전체 | Generator-Backend만 |
|
|
43
43
|
| `[FE]` 소유 경로 | 전체 | Generator-Frontend만 |
|
|
44
44
|
| .harness/archive/ | 전체 | 쓰기 금지 (불변) |
|
|
@@ -10,14 +10,9 @@
|
|
|
10
10
|
.harness/
|
|
11
11
|
├── HARNESS.md # 이 파일
|
|
12
12
|
├── config.json # 하네스 설정
|
|
13
|
-
├── progress.
|
|
14
|
-
├──
|
|
15
|
-
|
|
16
|
-
│ ├── planner.md # Planner — 제품 사양 + MSA 설계
|
|
17
|
-
│ ├── generator-backend.md # Generator-BE — NestJS MSA 구현
|
|
18
|
-
│ ├── generator-frontend.md # Generator-FE — React/Next.js 구현
|
|
19
|
-
│ ├── evaluator-functional.md # Evaluator-기능 — Playwright E2E
|
|
20
|
-
│ └── evaluator-visual.md # Evaluator-비주얼 — 디자인/접근성
|
|
13
|
+
├── progress.json # 기계 판독 상태 (세션 오케스트레이션)
|
|
14
|
+
├── progress.log # 사람 판독 히스토리 (append-only)
|
|
15
|
+
├── next-prompt.txt # 다음 세션용 프롬프트 (claude CLI 파이프용)
|
|
21
16
|
├── actions/ # 현재 활성 문서
|
|
22
17
|
│ ├── pipeline.json # Dispatcher 결정 (어떤 파이프라인인지)
|
|
23
18
|
│ ├── plan.md # 제품 사양
|
|
@@ -79,55 +74,60 @@ Eval-Visual FAIL → Gen-FE 재작업 (max 10회)
|
|
|
79
74
|
10회 초과 → 사용자 개입 요청
|
|
80
75
|
```
|
|
81
76
|
|
|
82
|
-
##
|
|
77
|
+
## 세션 오케스트레이션
|
|
83
78
|
|
|
84
|
-
###
|
|
85
|
-
```
|
|
86
|
-
".harness/prompts/dispatcher.md를 읽고 Dispatcher로 동작하세요.
|
|
87
|
-
[사용자의 자유 형식 요청]"
|
|
88
|
-
```
|
|
79
|
+
### 핵심: 한 세션에 1 에이전트 단계
|
|
89
80
|
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
".harness/prompts/planner.md를 읽고 Planner 에이전트로 동작하세요.
|
|
93
|
-
pipeline.json을 참조하여 [light/full] 모드로 실행합니다."
|
|
94
|
-
```
|
|
81
|
+
각 에이전트는 독립 Claude Code 세션에서 실행됩니다. 컨텍스트 소진을 방지하고 품질을 유지합니다.
|
|
95
82
|
|
|
96
|
-
###
|
|
97
|
-
```
|
|
98
|
-
".harness/prompts/generator-backend.md를 읽고 Generator-Backend으로 동작하세요.
|
|
99
|
-
Sprint [N]을 시작합니다."
|
|
100
|
-
```
|
|
83
|
+
### 상태 관리
|
|
101
84
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
85
|
+
| 파일 | 역할 |
|
|
86
|
+
|------|------|
|
|
87
|
+
| `.harness/progress.json` | 기계 판독 상태 (현재 에이전트, 파이프라인, 실패 정보) |
|
|
88
|
+
| `.harness/progress.log` | 사람 판독 히스토리 (append-only) |
|
|
89
|
+
| `.harness/next-prompt.txt` | 다음 세션용 프롬프트 (claude CLI 파이프용) |
|
|
107
90
|
|
|
108
|
-
###
|
|
109
|
-
```
|
|
110
|
-
".harness/prompts/evaluator-functional.md를 읽고 Evaluator-Functional로 동작하세요.
|
|
111
|
-
Sprint [N]을 평가합니다. Playwright MCP를 사용하세요."
|
|
112
|
-
```
|
|
91
|
+
### 실행 방법
|
|
113
92
|
|
|
114
|
-
|
|
93
|
+
#### 1. 첫 세션: Dispatcher
|
|
115
94
|
```
|
|
116
|
-
"
|
|
117
|
-
Sprint [N]의 비주얼을 평가합니다."
|
|
95
|
+
"하네스 엔지니어링 시작" 또는 /harness-dispatcher
|
|
118
96
|
```
|
|
119
97
|
|
|
120
|
-
|
|
98
|
+
#### 2. 이후 세션: harness-next.sh로 안내받기
|
|
99
|
+
```bash
|
|
100
|
+
bash scripts/harness-next.sh
|
|
121
101
|
```
|
|
122
|
-
|
|
123
|
-
|
|
102
|
+
Feature-level 프로그래스를 출력하고 다음 에이전트를 안내합니다.
|
|
103
|
+
|
|
104
|
+
#### 3. 다음 세션 시작
|
|
105
|
+
```bash
|
|
106
|
+
# 방법 A: 새 세션에서 스킬 직접 호출
|
|
107
|
+
/harness-generator-backend
|
|
108
|
+
|
|
109
|
+
# 방법 B: claude CLI 자동 실행
|
|
110
|
+
claude --prompt "$(cat .harness/next-prompt.txt)"
|
|
124
111
|
```
|
|
125
112
|
|
|
113
|
+
#### 4. SessionStart 훅 (자동)
|
|
114
|
+
세션 시작 시 `.claude/settings.json` 훅이 자동으로 현재 프로그래스를 출력합니다.
|
|
115
|
+
|
|
116
|
+
### Session Boundary Protocol
|
|
117
|
+
|
|
118
|
+
모든 에이전트 스킬에 내장된 프로토콜:
|
|
119
|
+
|
|
120
|
+
- **On Start**: `progress.json` 읽기 → `agent_status: "running"` 설정
|
|
121
|
+
- **On Complete**: `progress.json` 업데이트 → `next_agent` 계산 → **STOP**
|
|
122
|
+
- **On Fail** (Evaluator): `failure` 정보 기록 → `retry_target` 설정 → **STOP**
|
|
123
|
+
|
|
124
|
+
에이전트는 절대 다음 에이전트를 직접 호출하지 않습니다.
|
|
125
|
+
|
|
126
126
|
## 핵심 원칙
|
|
127
127
|
|
|
128
128
|
1. **Backend First** — API가 안정된 후 Frontend 연동 (없는 API 호출 방지)
|
|
129
129
|
2. **api-contract.json이 진실의 원천** — FE↔Gateway↔Services 간 유일한 계약
|
|
130
|
-
3. **한 세션에 1
|
|
130
|
+
3. **한 세션에 1 에이전트 단계** — 컨텍스트 소진 방지, Session Boundary Protocol 준수
|
|
131
131
|
4. **feature-list.json의 passes만 수정** — 기능 정의는 Planner만 변경
|
|
132
132
|
5. **테스트 삭제/약화 금지** — 테스트는 계약이다
|
|
133
133
|
6. **Evaluator는 회의적** — 자기 설득 금지, 기준 미달 = FAIL
|
|
@@ -8,54 +8,46 @@
|
|
|
8
8
|
"agents": {
|
|
9
9
|
"dispatcher": {
|
|
10
10
|
"role": "사용자 요청 분석 → 파이프라인 결정 (FE-ONLY / BE-ONLY / FULLSTACK)",
|
|
11
|
-
"
|
|
11
|
+
"skill": "harness-dispatcher",
|
|
12
12
|
"outputs": ["actions/pipeline.json"],
|
|
13
|
-
"triggers_next": "planner",
|
|
14
13
|
"always_first": true
|
|
15
14
|
},
|
|
16
15
|
"planner": {
|
|
17
16
|
"role": "제품 사양 + API 계약서 + 서비스 분할 설계",
|
|
18
|
-
"
|
|
17
|
+
"skill": "harness-planner",
|
|
19
18
|
"outputs": [
|
|
20
19
|
"actions/plan.md",
|
|
21
20
|
"actions/feature-list.json",
|
|
22
21
|
"actions/api-contract.json"
|
|
23
|
-
]
|
|
24
|
-
"triggers_next": ["generator-backend", "generator-frontend"]
|
|
22
|
+
]
|
|
25
23
|
},
|
|
26
24
|
"generator-backend": {
|
|
27
25
|
"role": "NestJS MSA 서비스 구현, API 엔드포인트, DB 스키마",
|
|
28
|
-
"
|
|
26
|
+
"skill": "harness-generator-backend",
|
|
29
27
|
"inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
|
|
30
28
|
"outputs": ["code:backend", "actions/sprint-contract.md"],
|
|
31
|
-
"triggers_next": "generator-frontend",
|
|
32
29
|
"order": 1
|
|
33
30
|
},
|
|
34
31
|
"generator-frontend": {
|
|
35
32
|
"role": "React/Next.js UI 컴포넌트, 상태관리, API 연동",
|
|
36
|
-
"
|
|
33
|
+
"skill": "harness-generator-frontend",
|
|
37
34
|
"inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
|
|
38
35
|
"outputs": ["code:frontend", "actions/sprint-contract.md"],
|
|
39
|
-
"triggers_next": "evaluator-functional",
|
|
40
36
|
"order": 2
|
|
41
37
|
},
|
|
42
38
|
"evaluator-functional": {
|
|
43
39
|
"role": "Playwright로 E2E 기능 검증, API 응답/DB 상태 확인",
|
|
44
|
-
"
|
|
40
|
+
"skill": "harness-evaluator-functional",
|
|
45
41
|
"tools": ["playwright:browser_*"],
|
|
46
42
|
"inputs": ["actions/sprint-contract.md"],
|
|
47
|
-
"outputs": ["actions/evaluation-functional.md"]
|
|
48
|
-
"on_fail": "generator-backend OR generator-frontend (결함 위치에 따라)",
|
|
49
|
-
"on_pass": "evaluator-visual"
|
|
43
|
+
"outputs": ["actions/evaluation-functional.md"]
|
|
50
44
|
},
|
|
51
45
|
"evaluator-visual": {
|
|
52
46
|
"role": "디자인 일관성, 반응형, 접근성, AI슬롭 감지",
|
|
53
|
-
"
|
|
47
|
+
"skill": "harness-evaluator-visual",
|
|
54
48
|
"tools": ["playwright:browser_take_screenshot", "playwright:browser_resize", "playwright:browser_snapshot"],
|
|
55
49
|
"inputs": ["actions/sprint-contract.md", "actions/evaluation-functional.md"],
|
|
56
|
-
"outputs": ["actions/evaluation-visual.md"]
|
|
57
|
-
"on_fail": "generator-frontend",
|
|
58
|
-
"on_pass": "archive_and_next_sprint"
|
|
50
|
+
"outputs": ["actions/evaluation-visual.md"]
|
|
59
51
|
}
|
|
60
52
|
},
|
|
61
53
|
"flow": {
|
|
@@ -77,12 +69,45 @@
|
|
|
77
69
|
"max_retries_per_sprint": 10,
|
|
78
70
|
"archive_on_sprint_complete": true
|
|
79
71
|
},
|
|
72
|
+
"session": {
|
|
73
|
+
"isolation": true,
|
|
74
|
+
"state_file": ".harness/progress.json",
|
|
75
|
+
"state_log": ".harness/progress.log",
|
|
76
|
+
"next_prompt_file": ".harness/next-prompt.txt"
|
|
77
|
+
},
|
|
80
78
|
"paths": {
|
|
81
79
|
"actions": ".harness/actions",
|
|
82
80
|
"archive": ".harness/archive",
|
|
83
|
-
"prompts": ".harness/prompts",
|
|
84
81
|
"gotchas": ".harness/gotchas",
|
|
85
|
-
"
|
|
82
|
+
"progress_state": ".harness/progress.json",
|
|
83
|
+
"progress_log": ".harness/progress.log"
|
|
84
|
+
},
|
|
85
|
+
"recommended_skills": {
|
|
86
|
+
"comment": "Claude Code에 설치하면 하네스 품질이 향상되는 외부 스킬 목록",
|
|
87
|
+
"vercel": {
|
|
88
|
+
"description": "Vercel/Next.js 배포, 성능 최적화, AI SDK 가이드",
|
|
89
|
+
"used_by": ["generator-frontend"],
|
|
90
|
+
"install": "claude skill install vercel",
|
|
91
|
+
"check_path": ".claude/skills/vercel"
|
|
92
|
+
},
|
|
93
|
+
"web-design-guidelines": {
|
|
94
|
+
"description": "UI/UX 접근성, 디자인 리뷰, Web Interface Guidelines",
|
|
95
|
+
"used_by": ["evaluator-visual", "generator-frontend"],
|
|
96
|
+
"install": "claude skill install web-design-guidelines",
|
|
97
|
+
"check_path": ".claude/skills/web-design-guidelines"
|
|
98
|
+
},
|
|
99
|
+
"taste-skill": {
|
|
100
|
+
"description": "AI 생성 UI의 디자인 품질 검증, AI슬롭 패턴 감지",
|
|
101
|
+
"used_by": ["evaluator-visual", "generator-frontend"],
|
|
102
|
+
"install": "npx claude-skill install leonxlnx/taste-skill",
|
|
103
|
+
"check_path": ".claude/skills/taste-skill"
|
|
104
|
+
},
|
|
105
|
+
"supanova-design-skill": {
|
|
106
|
+
"description": "디자인 시스템 규칙, 컴포넌트 패턴, 시각적 일관성",
|
|
107
|
+
"used_by": ["evaluator-visual", "generator-frontend"],
|
|
108
|
+
"install": "npx claude-skill install uxjoseph/supanova-design-skill",
|
|
109
|
+
"check_path": ".claude/skills/supanova-design-skill"
|
|
110
|
+
}
|
|
86
111
|
},
|
|
87
112
|
"tech_stack": {
|
|
88
113
|
"backend": {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"project_name": "",
|
|
4
|
+
"pipeline": null,
|
|
5
|
+
"sprint": {
|
|
6
|
+
"number": 0,
|
|
7
|
+
"status": "init",
|
|
8
|
+
"retry_count": 0
|
|
9
|
+
},
|
|
10
|
+
"current_agent": null,
|
|
11
|
+
"agent_status": "pending",
|
|
12
|
+
"completed_agents": [],
|
|
13
|
+
"next_agent": "dispatcher",
|
|
14
|
+
"failure": {
|
|
15
|
+
"agent": null,
|
|
16
|
+
"location": null,
|
|
17
|
+
"message": null,
|
|
18
|
+
"retry_target": null
|
|
19
|
+
},
|
|
20
|
+
"updated_at": "{{DATE}}",
|
|
21
|
+
"history": []
|
|
22
|
+
}
|
package/bin/init.js
CHANGED
|
@@ -112,7 +112,7 @@ function scaffoldHarness() {
|
|
|
112
112
|
|
|
113
113
|
// Copy templates as initial files
|
|
114
114
|
const templateMap = {
|
|
115
|
-
'progress.
|
|
115
|
+
'progress.json.template': path.join(HARNESS_DIR, 'progress.json'),
|
|
116
116
|
};
|
|
117
117
|
|
|
118
118
|
const templatesDir = path.join(PKG_ROOT, 'assets', 'templates');
|
|
@@ -141,6 +141,19 @@ function scaffoldHarness() {
|
|
|
141
141
|
copyFile(harnessMdSrc, harnessMdDest);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// Create progress.log
|
|
145
|
+
const progressLog = path.join(HARNESS_DIR, 'progress.log');
|
|
146
|
+
if (!fileExists(progressLog) || isForce) {
|
|
147
|
+
const date = new Date().toISOString().split('T')[0];
|
|
148
|
+
fs.writeFileSync(progressLog, `# Harness Progress Log\n# ${date} — Initialized\n`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Create next-prompt.txt placeholder
|
|
152
|
+
const nextPrompt = path.join(HARNESS_DIR, 'next-prompt.txt');
|
|
153
|
+
if (!fileExists(nextPrompt) || isForce) {
|
|
154
|
+
fs.writeFileSync(nextPrompt, '');
|
|
155
|
+
}
|
|
156
|
+
|
|
144
157
|
log('.harness/ scaffolding complete');
|
|
145
158
|
}
|
|
146
159
|
|
|
@@ -186,12 +199,29 @@ function installScripts() {
|
|
|
186
199
|
|
|
187
200
|
if (fs.existsSync(scriptsSrc)) {
|
|
188
201
|
ensureDir(scriptsDest);
|
|
189
|
-
const
|
|
190
|
-
for (const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
202
|
+
const entries = fs.readdirSync(scriptsSrc, { withFileTypes: true });
|
|
203
|
+
for (const entry of entries) {
|
|
204
|
+
const srcPath = path.join(scriptsSrc, entry.name);
|
|
205
|
+
const destPath = path.join(scriptsDest, entry.name);
|
|
206
|
+
if (entry.isDirectory()) {
|
|
207
|
+
// Copy subdirectories (e.g., lib/)
|
|
208
|
+
if (!fileExists(destPath) || isForce) {
|
|
209
|
+
copyDir(srcPath, destPath);
|
|
210
|
+
// Make scripts in subdirs executable
|
|
211
|
+
try {
|
|
212
|
+
const subFiles = fs.readdirSync(destPath);
|
|
213
|
+
for (const f of subFiles) {
|
|
214
|
+
if (f.endsWith('.sh')) {
|
|
215
|
+
fs.chmodSync(path.join(destPath, f), '755');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (e) {}
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
if (!fileExists(destPath) || isForce) {
|
|
222
|
+
copyFile(srcPath, destPath);
|
|
223
|
+
try { fs.chmodSync(destPath, '755'); } catch (e) {}
|
|
224
|
+
}
|
|
195
225
|
}
|
|
196
226
|
}
|
|
197
227
|
}
|
|
@@ -199,6 +229,48 @@ function installScripts() {
|
|
|
199
229
|
log('Scripts installation complete');
|
|
200
230
|
}
|
|
201
231
|
|
|
232
|
+
// ─────────────────────────────────────────
|
|
233
|
+
// 3b. SessionStart hook
|
|
234
|
+
// ─────────────────────────────────────────
|
|
235
|
+
function installSessionHook() {
|
|
236
|
+
log('Installing SessionStart hook...');
|
|
237
|
+
|
|
238
|
+
const settingsDir = path.join(PROJECT_ROOT, '.claude');
|
|
239
|
+
const settingsFile = path.join(settingsDir, 'settings.json');
|
|
240
|
+
|
|
241
|
+
ensureDir(settingsDir);
|
|
242
|
+
|
|
243
|
+
let settings = {};
|
|
244
|
+
if (fileExists(settingsFile)) {
|
|
245
|
+
try {
|
|
246
|
+
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
247
|
+
} catch (e) {
|
|
248
|
+
log('WARNING: Could not parse existing .claude/settings.json, creating new');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Ensure hooks.SessionStart array exists
|
|
253
|
+
if (!settings.hooks) settings.hooks = {};
|
|
254
|
+
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
|
|
255
|
+
|
|
256
|
+
// Check if our hook is already installed
|
|
257
|
+
const hookCmd = 'bash scripts/harness-session-start.sh';
|
|
258
|
+
const alreadyInstalled = settings.hooks.SessionStart.some(
|
|
259
|
+
h => h.command && h.command.includes('harness-session-start')
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (!alreadyInstalled) {
|
|
263
|
+
settings.hooks.SessionStart.push({
|
|
264
|
+
type: 'command',
|
|
265
|
+
command: hookCmd
|
|
266
|
+
});
|
|
267
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
|
|
268
|
+
log('SessionStart hook installed in .claude/settings.json');
|
|
269
|
+
} else {
|
|
270
|
+
log('SessionStart hook already installed');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
202
274
|
// ─────────────────────────────────────────
|
|
203
275
|
// 4. AGENTS.md + CLAUDE.md
|
|
204
276
|
// ─────────────────────────────────────────
|
|
@@ -283,6 +355,60 @@ function checkPlaywrightMcp() {
|
|
|
283
355
|
log('');
|
|
284
356
|
}
|
|
285
357
|
|
|
358
|
+
// ─────────────────────────────────────────
|
|
359
|
+
// 6. Recommended skills check
|
|
360
|
+
// ─────────────────────────────────────────
|
|
361
|
+
function checkRecommendedSkills() {
|
|
362
|
+
const configPath = path.join(HARNESS_DIR, 'config.json');
|
|
363
|
+
if (!fileExists(configPath)) return;
|
|
364
|
+
|
|
365
|
+
let config;
|
|
366
|
+
try {
|
|
367
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
368
|
+
} catch (e) { return; }
|
|
369
|
+
|
|
370
|
+
const skills = config.recommended_skills;
|
|
371
|
+
if (!skills) return;
|
|
372
|
+
|
|
373
|
+
const missing = [];
|
|
374
|
+
const found = [];
|
|
375
|
+
|
|
376
|
+
for (const [name, info] of Object.entries(skills)) {
|
|
377
|
+
if (name === 'comment') continue;
|
|
378
|
+
const checkPath = path.join(PROJECT_ROOT, info.check_path);
|
|
379
|
+
if (fileExists(checkPath)) {
|
|
380
|
+
found.push(name);
|
|
381
|
+
} else {
|
|
382
|
+
missing.push({ name, ...info });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (found.length > 0) {
|
|
387
|
+
log(`Recommended skills installed: ${found.join(', ')}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (missing.length > 0) {
|
|
391
|
+
console.log('');
|
|
392
|
+
log('╔═══════════════════════════════════════════════════════════╗');
|
|
393
|
+
log('║ Recommended skills (not installed) ║');
|
|
394
|
+
log('╠═══════════════════════════════════════════════════════════╣');
|
|
395
|
+
for (const skill of missing) {
|
|
396
|
+
const agents = skill.used_by.join(', ');
|
|
397
|
+
log(`║ ${skill.name}`);
|
|
398
|
+
log(`║ ${skill.description}`);
|
|
399
|
+
log(`║ Used by: ${agents}`);
|
|
400
|
+
log(`║ Install: ${skill.install}`);
|
|
401
|
+
log('║');
|
|
402
|
+
}
|
|
403
|
+
log('║ These skills are optional but improve output quality. ║');
|
|
404
|
+
log('║ Harness reference files provide baseline guidance ║');
|
|
405
|
+
log('║ even without these skills installed. ║');
|
|
406
|
+
log('╚═══════════════════════════════════════════════════════════╝');
|
|
407
|
+
} else if (found.length > 0) {
|
|
408
|
+
log('All recommended skills are installed!');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
286
412
|
// ─────────────────────────────────────────
|
|
287
413
|
// Help
|
|
288
414
|
// ─────────────────────────────────────────
|
|
@@ -305,6 +431,7 @@ What it does:
|
|
|
305
431
|
3. Copies helper scripts to scripts/
|
|
306
432
|
4. Creates AGENTS.md + CLAUDE.md symlink
|
|
307
433
|
5. Checks Playwright MCP configuration
|
|
434
|
+
6. Checks recommended external skills (Vercel, design skills)
|
|
308
435
|
|
|
309
436
|
After init:
|
|
310
437
|
1. Restart Claude Code session (exit and re-enter) for skills to load
|
|
@@ -335,8 +462,10 @@ function main() {
|
|
|
335
462
|
scaffoldHarness();
|
|
336
463
|
installSkills();
|
|
337
464
|
installScripts();
|
|
465
|
+
installSessionHook();
|
|
338
466
|
setupAgentsMd();
|
|
339
467
|
checkPlaywrightMcp();
|
|
468
|
+
checkRecommendedSkills();
|
|
340
469
|
|
|
341
470
|
console.log('');
|
|
342
471
|
log('═══ Initialization Complete ═══');
|
package/package.json
CHANGED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-next.sh — 세션 오케스트레이터
|
|
3
|
+
# 현재 progress.json 상태를 읽고 다음 에이전트를 결정한다.
|
|
4
|
+
# Feature-level 프로그래스를 출력하고 next-prompt.txt를 생성한다.
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
9
|
+
|
|
10
|
+
# ─────────────────────────────────────────
|
|
11
|
+
# Resolve project root
|
|
12
|
+
# ─────────────────────────────────────────
|
|
13
|
+
PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || {
|
|
14
|
+
echo "[harness] ERROR: .harness/ directory not found" >&2
|
|
15
|
+
echo " Run 'npx walwal-harness' to initialize." >&2
|
|
16
|
+
exit 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
20
|
+
CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
21
|
+
NEXT_PROMPT="$PROJECT_ROOT/.harness/next-prompt.txt"
|
|
22
|
+
|
|
23
|
+
check_jq || exit 1
|
|
24
|
+
|
|
25
|
+
if [ ! -f "$PROGRESS" ]; then
|
|
26
|
+
echo "[harness] ERROR: progress.json not found" >&2
|
|
27
|
+
echo " Run 'npx walwal-harness --force' to reinitialize." >&2
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# ─────────────────────────────────────────
|
|
32
|
+
# Read current state
|
|
33
|
+
# ─────────────────────────────────────────
|
|
34
|
+
pipeline=$(jq -r '.pipeline // "null"' "$PROGRESS")
|
|
35
|
+
sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS")
|
|
36
|
+
sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS")
|
|
37
|
+
current_agent=$(jq -r '.current_agent // "null"' "$PROGRESS")
|
|
38
|
+
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
|
|
39
|
+
next_agent=$(jq -r '.next_agent // "null"' "$PROGRESS")
|
|
40
|
+
retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
|
|
41
|
+
max_retries=$(jq -r '.flow.max_retries_per_sprint // 10' "$CONFIG" 2>/dev/null || echo 10)
|
|
42
|
+
|
|
43
|
+
# ─────────────────────────────────────────
|
|
44
|
+
# Determine next agent
|
|
45
|
+
# ─────────────────────────────────────────
|
|
46
|
+
compute_next_agent() {
|
|
47
|
+
local current="$1"
|
|
48
|
+
local status="$2"
|
|
49
|
+
|
|
50
|
+
# If blocked, no next
|
|
51
|
+
if [ "$status" = "blocked" ]; then
|
|
52
|
+
echo "null"
|
|
53
|
+
return
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# If failure with retry target, use that
|
|
57
|
+
local retry_target
|
|
58
|
+
retry_target=$(jq -r '.failure.retry_target // "null"' "$PROGRESS")
|
|
59
|
+
if [ "$retry_target" != "null" ] && [ "$status" = "failed" ]; then
|
|
60
|
+
echo "$retry_target"
|
|
61
|
+
return
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# If next_agent is already set in progress.json, use it
|
|
65
|
+
if [ "$next_agent" != "null" ]; then
|
|
66
|
+
echo "$next_agent"
|
|
67
|
+
return
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Compute from pipeline sequence
|
|
71
|
+
if [ "$pipeline" = "null" ] || [ ! -f "$CONFIG" ]; then
|
|
72
|
+
echo "dispatcher"
|
|
73
|
+
return
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
local -a agents
|
|
77
|
+
mapfile -t agents < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null | sed 's/:.*//')
|
|
78
|
+
|
|
79
|
+
local found=false
|
|
80
|
+
for agent in "${agents[@]}"; do
|
|
81
|
+
if [ "$found" = true ]; then
|
|
82
|
+
echo "$agent"
|
|
83
|
+
return
|
|
84
|
+
fi
|
|
85
|
+
if [ "$agent" = "$current" ]; then
|
|
86
|
+
found=true
|
|
87
|
+
fi
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
# Current is last agent → sprint complete
|
|
91
|
+
echo "archive"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# If current agent completed, compute next
|
|
95
|
+
if [ "$agent_status" = "completed" ] && [ "$next_agent" = "null" ]; then
|
|
96
|
+
next_agent=$(compute_next_agent "$current_agent" "$agent_status")
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# ─────────────────────────────────────────
|
|
100
|
+
# Render progress
|
|
101
|
+
# ─────────────────────────────────────────
|
|
102
|
+
render_progress "$PROJECT_ROOT"
|
|
103
|
+
render_agent_bar "$PROJECT_ROOT"
|
|
104
|
+
echo ""
|
|
105
|
+
|
|
106
|
+
# ─────────────────────────────────────────
|
|
107
|
+
# Generate next-prompt.txt
|
|
108
|
+
# ─────────────────────────────────────────
|
|
109
|
+
if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_status" != "blocked" ]; then
|
|
110
|
+
# Build prompt
|
|
111
|
+
prompt="/harness-${next_agent} 를 실행하세요."
|
|
112
|
+
|
|
113
|
+
if [ "$sprint_num" -gt 0 ]; then
|
|
114
|
+
prompt+=$'\n'"Sprint ${sprint_num}"
|
|
115
|
+
if [ "$sprint_status" = "failed" ]; then
|
|
116
|
+
prompt+=" (retry ${retry_count}/${max_retries})"
|
|
117
|
+
fi
|
|
118
|
+
prompt+="을 진행합니다."
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
prompt+=$'\n'".harness/progress.json을 읽고 현재 상태를 확인하세요."
|
|
122
|
+
|
|
123
|
+
# Add failure context if retrying
|
|
124
|
+
failure_msg=$(jq -r '.failure.message // empty' "$PROGRESS")
|
|
125
|
+
if [ -n "$failure_msg" ] && [ "$failure_msg" != "null" ]; then
|
|
126
|
+
prompt+=$'\n\n'"이전 실패 사유: ${failure_msg}"
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
echo "$prompt" > "$NEXT_PROMPT"
|
|
130
|
+
|
|
131
|
+
elif [ "$next_agent" = "archive" ]; then
|
|
132
|
+
cat > "$NEXT_PROMPT" <<'PROMPT'
|
|
133
|
+
Sprint 문서를 아카이브 하세요.
|
|
134
|
+
.harness/actions/의 스프린트 문서를 .harness/archive/sprint-NNN/으로 이동합니다.
|
|
135
|
+
.harness/progress.json을 읽고 sprint 번호를 확인하세요.
|
|
136
|
+
PROMPT
|
|
137
|
+
|
|
138
|
+
else
|
|
139
|
+
echo "" > "$NEXT_PROMPT"
|
|
140
|
+
fi
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-session-start.sh — SessionStart 훅
|
|
3
|
+
# Claude Code 세션 시작 시 자동 실행되어 현재 하네스 상태를 출력한다.
|
|
4
|
+
# .claude/settings.json의 SessionStart 훅으로 등록된다.
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
LIB="$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
8
|
+
|
|
9
|
+
# lib이 없으면 silent exit (훅이므로 에러 출력하지 않음)
|
|
10
|
+
if [ ! -f "$LIB" ]; then exit 0; fi
|
|
11
|
+
source "$LIB"
|
|
12
|
+
|
|
13
|
+
# jq 없으면 silent exit
|
|
14
|
+
command -v jq &>/dev/null || exit 0
|
|
15
|
+
|
|
16
|
+
# .harness/ 찾기
|
|
17
|
+
PROJECT_ROOT="$(resolve_harness_root "." 2>/dev/null)" || exit 0
|
|
18
|
+
|
|
19
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
20
|
+
[ -f "$PROGRESS" ] || exit 0
|
|
21
|
+
|
|
22
|
+
# init 상태면 간단 안내만
|
|
23
|
+
sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null)
|
|
24
|
+
if [ "$sprint_status" = "init" ]; then
|
|
25
|
+
echo "# Harness ready — say \"하네스 엔지니어링 시작\" or /harness-dispatcher"
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Feature-level 프로그래스 출력
|
|
30
|
+
render_progress "$PROJECT_ROOT"
|
|
31
|
+
render_agent_bar "$PROJECT_ROOT"
|
|
@@ -267,7 +267,7 @@ ${USER_RULES}
|
|
|
267
267
|
| AGENTS.md | 전체 | Planner만 |
|
|
268
268
|
| .harness/actions/api-contract.json | 전체 | Planner만 |
|
|
269
269
|
| .harness/actions/feature-list.json | 전체 | passes: Generator, 나머지: Planner |
|
|
270
|
-
| .harness/progress.
|
|
270
|
+
| .harness/progress.json | 전체 | 전체 (Session Boundary Protocol에 따라 업데이트) |
|
|
271
271
|
| \`[BE]\` 소유 경로 | 전체 | Generator-Backend만 |
|
|
272
272
|
| \`[FE]\` 소유 경로 | 전체 | Generator-Frontend만 |
|
|
273
273
|
| .harness/archive/ | 전체 | 쓰기 금지 (불변) |
|
|
@@ -291,7 +291,7 @@ AGENTS.md 또는 api-contract.json 변경이 필요할 때:
|
|
|
291
291
|
| 명령 | 설명 |
|
|
292
292
|
|------|------|
|
|
293
293
|
| \`bash scripts/scan-project.sh\` | 프로젝트 구조 재스캔 |
|
|
294
|
-
| \`.harness/progress.
|
|
294
|
+
| \`.harness/progress.json\` | 현재 진행 상태 (기계 판독) |
|
|
295
295
|
| \`.harness/actions/\` | 활성 스프린트 문서 |
|
|
296
296
|
| \`.harness/HARNESS.md\` | 하네스 상세 가이드 |
|
|
297
297
|
AGENTSEOF
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-render-progress.sh — Feature-level 프로그래스 렌더링 (공통 함수)
|
|
3
|
+
# source로 포함하여 사용: source "$(dirname "$0")/lib/harness-render-progress.sh"
|
|
4
|
+
|
|
5
|
+
# ─────────────────────────────────────────
|
|
6
|
+
# Dependencies: jq
|
|
7
|
+
# ─────────────────────────────────────────
|
|
8
|
+
check_jq() {
|
|
9
|
+
if ! command -v jq &>/dev/null; then
|
|
10
|
+
echo "[harness] ERROR: jq is required. Install with: brew install jq" >&2
|
|
11
|
+
return 1
|
|
12
|
+
fi
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# ─────────────────────────────────────────
|
|
16
|
+
# resolve_harness_root — .harness/ 디렉토리 찾기
|
|
17
|
+
# ─────────────────────────────────────────
|
|
18
|
+
resolve_harness_root() {
|
|
19
|
+
local dir="${1:-.}"
|
|
20
|
+
dir="$(cd "$dir" && pwd)"
|
|
21
|
+
while [ "$dir" != "/" ]; do
|
|
22
|
+
if [ -d "$dir/.harness" ]; then
|
|
23
|
+
echo "$dir"
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
dir="$(dirname "$dir")"
|
|
27
|
+
done
|
|
28
|
+
return 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# ─────────────────────────────────────────
|
|
32
|
+
# render_progress — Feature-level 프로그래스 출력
|
|
33
|
+
#
|
|
34
|
+
# Args:
|
|
35
|
+
# $1 — project root (optional, defaults to auto-detect)
|
|
36
|
+
#
|
|
37
|
+
# Reads:
|
|
38
|
+
# .harness/progress.json
|
|
39
|
+
# .harness/config.json
|
|
40
|
+
# .harness/actions/feature-list.json (if exists)
|
|
41
|
+
# ─────────────────────────────────────────
|
|
42
|
+
render_progress() {
|
|
43
|
+
check_jq || return 1
|
|
44
|
+
|
|
45
|
+
local PROJECT_ROOT
|
|
46
|
+
PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || {
|
|
47
|
+
echo "[harness] .harness/ directory not found" >&2
|
|
48
|
+
return 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
local PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
52
|
+
local CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
53
|
+
local FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
54
|
+
|
|
55
|
+
if [ ! -f "$PROGRESS" ]; then
|
|
56
|
+
return 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Read progress.json
|
|
60
|
+
local pipeline sprint_num sprint_status retry_count
|
|
61
|
+
local current_agent agent_status next_agent
|
|
62
|
+
local failure_agent failure_location failure_message
|
|
63
|
+
|
|
64
|
+
pipeline=$(jq -r '.pipeline // "unknown"' "$PROGRESS")
|
|
65
|
+
sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS")
|
|
66
|
+
sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS")
|
|
67
|
+
retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
|
|
68
|
+
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS")
|
|
69
|
+
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
|
|
70
|
+
next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS")
|
|
71
|
+
failure_agent=$(jq -r '.failure.agent // empty' "$PROGRESS")
|
|
72
|
+
failure_location=$(jq -r '.failure.location // empty' "$PROGRESS")
|
|
73
|
+
failure_message=$(jq -r '.failure.message // empty' "$PROGRESS")
|
|
74
|
+
|
|
75
|
+
# Read max retries from config
|
|
76
|
+
local max_retries=10
|
|
77
|
+
if [ -f "$CONFIG" ]; then
|
|
78
|
+
max_retries=$(jq -r '.flow.max_retries_per_sprint // 10' "$CONFIG")
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# Pipeline agents for progress bar
|
|
82
|
+
local -a pipeline_agents
|
|
83
|
+
if [ -f "$CONFIG" ] && [ "$pipeline" != "null" ] && [ "$pipeline" != "unknown" ]; then
|
|
84
|
+
mapfile -t pipeline_agents < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null | sed 's/:.*//')
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# ── Header ──
|
|
88
|
+
local header="Sprint ${sprint_num} / ${pipeline}"
|
|
89
|
+
local pad_len=$(( 40 - ${#header} ))
|
|
90
|
+
local padding=""
|
|
91
|
+
for ((i=0; i<pad_len; i++)); do padding+="═"; done
|
|
92
|
+
echo ""
|
|
93
|
+
echo " ═══ ${header} ${padding}"
|
|
94
|
+
echo ""
|
|
95
|
+
|
|
96
|
+
# ── Feature Progress ──
|
|
97
|
+
if [ -f "$FEATURES" ]; then
|
|
98
|
+
local total_features completed_features=0 in_progress_features=0
|
|
99
|
+
total_features=$(jq '.features | length' "$FEATURES" 2>/dev/null || echo 0)
|
|
100
|
+
|
|
101
|
+
if [ "$total_features" -gt 0 ]; then
|
|
102
|
+
# Determine columns based on pipeline
|
|
103
|
+
local show_be=true show_fe=true
|
|
104
|
+
case "$pipeline" in
|
|
105
|
+
BE-ONLY) show_fe=false ;;
|
|
106
|
+
FE-ONLY) show_be=false ;;
|
|
107
|
+
esac
|
|
108
|
+
|
|
109
|
+
# Build feature lines
|
|
110
|
+
local i=0
|
|
111
|
+
while [ $i -lt "$total_features" ]; do
|
|
112
|
+
local fid fname passes be_pass fe_pass eval_func_pass eval_visual_pass
|
|
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")
|
|
115
|
+
passes=$(jq -r ".features[$i].passes // [] | .[]" "$FEATURES" 2>/dev/null)
|
|
116
|
+
|
|
117
|
+
# Truncate name to 20 chars
|
|
118
|
+
if [ ${#fname} -gt 20 ]; then
|
|
119
|
+
fname="${fname:0:18}.."
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Check passes
|
|
123
|
+
be_pass="○"; fe_pass="○"; eval_func_pass="○"; eval_visual_pass="○"
|
|
124
|
+
local eval_done=false
|
|
125
|
+
|
|
126
|
+
while IFS= read -r p; do
|
|
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="✓" ;;
|
|
132
|
+
esac
|
|
133
|
+
done <<< "$passes"
|
|
134
|
+
|
|
135
|
+
# Eval column: both must pass
|
|
136
|
+
local eval_pass="○"
|
|
137
|
+
if [ "$eval_func_pass" = "✓" ] && [ "$eval_visual_pass" = "✓" ]; then
|
|
138
|
+
eval_pass="✓"
|
|
139
|
+
elif [ "$eval_func_pass" = "✓" ] || [ "$eval_visual_pass" = "✓" ]; then
|
|
140
|
+
eval_pass="◐"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Feature status icon
|
|
144
|
+
local icon="○"
|
|
145
|
+
local is_complete=false
|
|
146
|
+
if [ "$show_be" = true ] && [ "$show_fe" = true ]; then
|
|
147
|
+
# FULLSTACK: all must pass
|
|
148
|
+
if [ "$be_pass" = "✓" ] && [ "$fe_pass" = "✓" ] && [ "$eval_pass" = "✓" ]; then
|
|
149
|
+
icon="✓"; is_complete=true
|
|
150
|
+
elif [ "$be_pass" = "✓" ] || [ "$fe_pass" = "✓" ]; then
|
|
151
|
+
icon="◐"
|
|
152
|
+
fi
|
|
153
|
+
elif [ "$show_be" = true ]; then
|
|
154
|
+
# BE-ONLY
|
|
155
|
+
if [ "$be_pass" = "✓" ] && [ "$eval_pass" = "✓" ]; then
|
|
156
|
+
icon="✓"; is_complete=true
|
|
157
|
+
elif [ "$be_pass" = "✓" ]; then
|
|
158
|
+
icon="◐"
|
|
159
|
+
fi
|
|
160
|
+
else
|
|
161
|
+
# FE-ONLY
|
|
162
|
+
if [ "$fe_pass" = "✓" ] && [ "$eval_pass" = "✓" ]; then
|
|
163
|
+
icon="✓"; is_complete=true
|
|
164
|
+
elif [ "$fe_pass" = "✓" ]; then
|
|
165
|
+
icon="◐"
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
if [ "$is_complete" = true ]; then
|
|
170
|
+
completed_features=$((completed_features + 1))
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Build columns
|
|
174
|
+
local cols=""
|
|
175
|
+
if [ "$show_be" = true ]; then cols+="BE${be_pass} "; fi
|
|
176
|
+
if [ "$show_fe" = true ]; then cols+="FE${fe_pass} "; fi
|
|
177
|
+
cols+="Eval${eval_pass}"
|
|
178
|
+
|
|
179
|
+
printf " %s %-5s %-22s %s\n" "$icon" "$fid" "$fname" "$cols"
|
|
180
|
+
|
|
181
|
+
i=$((i + 1))
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
echo ""
|
|
185
|
+
|
|
186
|
+
# ── Progress Bar ──
|
|
187
|
+
local steps_per_feature=0
|
|
188
|
+
if [ "$show_be" = true ]; then steps_per_feature=$((steps_per_feature + 1)); fi
|
|
189
|
+
if [ "$show_fe" = true ]; then steps_per_feature=$((steps_per_feature + 1)); fi
|
|
190
|
+
steps_per_feature=$((steps_per_feature + 1)) # eval
|
|
191
|
+
|
|
192
|
+
local total_steps=$((total_features * steps_per_feature))
|
|
193
|
+
local done_steps=0
|
|
194
|
+
|
|
195
|
+
# Count individual passes across all features
|
|
196
|
+
i=0
|
|
197
|
+
while [ $i -lt "$total_features" ]; do
|
|
198
|
+
passes=$(jq -r ".features[$i].passes // [] | .[]" "$FEATURES" 2>/dev/null)
|
|
199
|
+
while IFS= read -r p; do
|
|
200
|
+
case "$p" in
|
|
201
|
+
generator-backend)
|
|
202
|
+
if [ "$show_be" = true ]; then done_steps=$((done_steps + 1)); fi
|
|
203
|
+
;;
|
|
204
|
+
generator-frontend)
|
|
205
|
+
if [ "$show_fe" = true ]; then done_steps=$((done_steps + 1)); fi
|
|
206
|
+
;;
|
|
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
|
|
210
|
+
;;
|
|
211
|
+
esac
|
|
212
|
+
done <<< "$passes"
|
|
213
|
+
|
|
214
|
+
# Check eval completion
|
|
215
|
+
local ef ev
|
|
216
|
+
ef=$(jq -r ".features[$i].passes // [] | map(select(. == \"evaluator-functional\")) | length" "$FEATURES" 2>/dev/null)
|
|
217
|
+
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
|
|
219
|
+
done_steps=$((done_steps + 1))
|
|
220
|
+
elif [ "${ef:-0}" -gt 0 ] || [ "${ev:-0}" -gt 0 ]; then
|
|
221
|
+
# partial eval — don't count
|
|
222
|
+
:
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
i=$((i + 1))
|
|
226
|
+
done
|
|
227
|
+
|
|
228
|
+
local pct=0
|
|
229
|
+
if [ "$total_steps" -gt 0 ]; then
|
|
230
|
+
pct=$(( done_steps * 100 / total_steps ))
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
local bar_width=16
|
|
234
|
+
local filled=$(( pct * bar_width / 100 ))
|
|
235
|
+
local empty=$(( bar_width - filled ))
|
|
236
|
+
local bar=""
|
|
237
|
+
for ((j=0; j<filled; j++)); do bar+="█"; done
|
|
238
|
+
for ((j=0; j<empty; j++)); do bar+="░"; done
|
|
239
|
+
|
|
240
|
+
echo " Features: ${completed_features}/${total_features} completed"
|
|
241
|
+
echo " Pipeline: ${bar} ${pct}%"
|
|
242
|
+
fi
|
|
243
|
+
else
|
|
244
|
+
echo " (feature-list.json not yet created — run Planner first)"
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
echo ""
|
|
248
|
+
|
|
249
|
+
# ── Current Agent Status ──
|
|
250
|
+
if [ "$current_agent" != "none" ] && [ "$current_agent" != "null" ]; then
|
|
251
|
+
echo " Current: ${current_agent} [${agent_status}] (retry ${retry_count}/${max_retries})"
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
# ── Failure Info ──
|
|
255
|
+
if [ -n "$failure_agent" ] && [ "$failure_agent" != "null" ]; then
|
|
256
|
+
echo ""
|
|
257
|
+
echo " ⚠ FAIL: ${failure_agent} → ${failure_location}"
|
|
258
|
+
if [ -n "$failure_message" ] && [ "$failure_message" != "null" ]; then
|
|
259
|
+
echo " ${failure_message}"
|
|
260
|
+
fi
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# ── Blocked ──
|
|
264
|
+
if [ "$agent_status" = "blocked" ]; then
|
|
265
|
+
echo ""
|
|
266
|
+
echo " ✖ BLOCKED: retry limit (${max_retries}) reached — user intervention required"
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
# ── Next Action ──
|
|
270
|
+
if [ "$next_agent" != "none" ] && [ "$next_agent" != "null" ] && [ "$agent_status" != "blocked" ]; then
|
|
271
|
+
echo ""
|
|
272
|
+
echo " Next → /harness-${next_agent}"
|
|
273
|
+
echo " Auto → claude --prompt \"\$(cat .harness/next-prompt.txt)\""
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
echo ""
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# ─────────────────────────────────────────
|
|
280
|
+
# render_agent_bar — 에이전트 시퀀스 바 (compact)
|
|
281
|
+
# ─────────────────────────────────────────
|
|
282
|
+
render_agent_bar() {
|
|
283
|
+
local PROGRESS="${1:-.}/.harness/progress.json"
|
|
284
|
+
local CONFIG="${1:-.}/.harness/config.json"
|
|
285
|
+
|
|
286
|
+
if [ ! -f "$PROGRESS" ] || [ ! -f "$CONFIG" ]; then return 1; fi
|
|
287
|
+
|
|
288
|
+
local pipeline current_agent
|
|
289
|
+
pipeline=$(jq -r '.pipeline // "unknown"' "$PROGRESS")
|
|
290
|
+
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS")
|
|
291
|
+
|
|
292
|
+
local completed_agents
|
|
293
|
+
completed_agents=$(jq -r '.completed_agents // [] | .[]' "$PROGRESS")
|
|
294
|
+
|
|
295
|
+
if [ "$pipeline" = "null" ] || [ "$pipeline" = "unknown" ]; then return 1; fi
|
|
296
|
+
|
|
297
|
+
local agents_line=" Agents: "
|
|
298
|
+
local first=true
|
|
299
|
+
|
|
300
|
+
while IFS= read -r agent; do
|
|
301
|
+
agent=$(echo "$agent" | sed 's/:.*//') # strip mode suffix like :light, :api-only
|
|
302
|
+
if [ "$first" = true ]; then
|
|
303
|
+
first=false
|
|
304
|
+
else
|
|
305
|
+
agents_line+=" → "
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
if echo "$completed_agents" | grep -q "^${agent}$"; then
|
|
309
|
+
agents_line+="${agent}✓"
|
|
310
|
+
elif [ "$agent" = "$current_agent" ]; then
|
|
311
|
+
agents_line+="[${agent}]"
|
|
312
|
+
else
|
|
313
|
+
agents_line+="${agent}"
|
|
314
|
+
fi
|
|
315
|
+
done < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null)
|
|
316
|
+
|
|
317
|
+
echo "$agents_line"
|
|
318
|
+
}
|
|
@@ -6,6 +6,23 @@ disable-model-invocation: false
|
|
|
6
6
|
|
|
7
7
|
# Dispatcher — Pipeline Selector + Gotcha Manager
|
|
8
8
|
|
|
9
|
+
## Session Boundary Protocol
|
|
10
|
+
|
|
11
|
+
### On Start
|
|
12
|
+
1. `.harness/progress.json` 읽기 — `next_agent`가 `"dispatcher"`인지 확인
|
|
13
|
+
2. progress.json 업데이트: `current_agent` → `"dispatcher"`, `agent_status` → `"running"`, `updated_at` 갱신
|
|
14
|
+
|
|
15
|
+
### On Complete
|
|
16
|
+
1. progress.json 업데이트:
|
|
17
|
+
- `agent_status` → `"completed"`
|
|
18
|
+
- `completed_agents`에 `"dispatcher"` 추가
|
|
19
|
+
- `next_agent` → `"planner"`
|
|
20
|
+
- `pipeline` → 선택된 파이프라인 (FULLSTACK/FE-ONLY/BE-ONLY)
|
|
21
|
+
- `sprint.number` → `1`, `sprint.status` → `"in_progress"`
|
|
22
|
+
2. `.harness/progress.log`에 요약 한 줄 추가
|
|
23
|
+
3. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
24
|
+
4. 출력: `"✓ Dispatcher 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
|
|
25
|
+
|
|
9
26
|
## 1. Request Classification (최우선)
|
|
10
27
|
|
|
11
28
|
사용자 입력을 먼저 분류합니다:
|
|
@@ -50,4 +67,4 @@ AGENTS.md 비하네스 → 기존 백업 + 리빌드
|
|
|
50
67
|
|
|
51
68
|
## 5. Output
|
|
52
69
|
|
|
53
|
-
`.harness/actions/pipeline.json` 생성 → 사용자 확인 →
|
|
70
|
+
`.harness/actions/pipeline.json` 생성 → 사용자 확인 → Session Boundary Protocol On Complete 실행
|
|
@@ -6,6 +6,37 @@ disable-model-invocation: true
|
|
|
6
6
|
|
|
7
7
|
# Evaluator-Functional — Playwright MCP
|
|
8
8
|
|
|
9
|
+
## Session Boundary Protocol
|
|
10
|
+
|
|
11
|
+
### On Start
|
|
12
|
+
1. `.harness/progress.json` 읽기 — `next_agent`가 `"evaluator-functional"`인지 확인
|
|
13
|
+
2. progress.json 업데이트: `current_agent` → `"evaluator-functional"`, `agent_status` → `"running"`, `updated_at` 갱신
|
|
14
|
+
|
|
15
|
+
### On Complete (PASS)
|
|
16
|
+
1. progress.json 업데이트:
|
|
17
|
+
- `agent_status` → `"completed"`
|
|
18
|
+
- `completed_agents`에 `"evaluator-functional"` 추가
|
|
19
|
+
- `next_agent` → 파이프라인에 따라 결정 (FULLSTACK/FE-ONLY: `"evaluator-visual"`, BE-ONLY: `"archive"`)
|
|
20
|
+
- `failure` 필드 초기화
|
|
21
|
+
2. `feature-list.json`의 통과 feature `passes`에 `"evaluator-functional"` 추가
|
|
22
|
+
3. `.harness/progress.log`에 PASS 요약 추가
|
|
23
|
+
4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
24
|
+
5. 출력: `"✓ Evaluator-Functional PASS. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
|
|
25
|
+
|
|
26
|
+
### On Fail
|
|
27
|
+
1. progress.json 업데이트:
|
|
28
|
+
- `agent_status` → `"failed"`
|
|
29
|
+
- `failure.agent` → `"evaluator-functional"`
|
|
30
|
+
- `failure.location` → `"backend"` 또는 `"frontend"` (결함 위치)
|
|
31
|
+
- `failure.message` → 실패 요약 (1줄)
|
|
32
|
+
- `failure.retry_target` → `"generator-backend"` 또는 `"generator-frontend"`
|
|
33
|
+
- `next_agent` → `failure.retry_target`과 동일
|
|
34
|
+
- `sprint.retry_count` 증가
|
|
35
|
+
2. `sprint.retry_count >= 10`이면 `agent_status` → `"blocked"`, 사용자 개입 요청
|
|
36
|
+
3. `.harness/progress.log`에 FAIL 요약 추가
|
|
37
|
+
4. **STOP.**
|
|
38
|
+
5. 출력: `"✖ Evaluator-Functional FAIL. bash scripts/harness-next.sh 실행하여 재작업 대상 확인."`
|
|
39
|
+
|
|
9
40
|
## Critical Mindset
|
|
10
41
|
|
|
11
42
|
- **회의적 평가자**. Generator의 자체 평가를 신뢰하지 마세요.
|
|
@@ -20,7 +51,7 @@ disable-model-invocation: true
|
|
|
20
51
|
3. `actions/sprint-contract.md` — BE + FE 성공 기준 전체
|
|
21
52
|
4. `actions/feature-list.json` — 이번 스프린트 범위
|
|
22
53
|
5. `actions/api-contract.json` — 기대 API 동작
|
|
23
|
-
6.
|
|
54
|
+
6. `.harness/progress.json`
|
|
24
55
|
|
|
25
56
|
## Evaluation Steps
|
|
26
57
|
|
|
@@ -54,5 +85,5 @@ Playwright 도구 → [도구 레퍼런스](references/playwright-tools.md)
|
|
|
54
85
|
|
|
55
86
|
## After Evaluation
|
|
56
87
|
|
|
57
|
-
- **PASS** →
|
|
58
|
-
- **FAIL** →
|
|
88
|
+
- **PASS** → Session Boundary Protocol On Complete (PASS) 실행
|
|
89
|
+
- **FAIL** → Session Boundary Protocol On Fail 실행
|
|
@@ -6,6 +6,37 @@ disable-model-invocation: true
|
|
|
6
6
|
|
|
7
7
|
# Evaluator-Visual — Design & Accessibility
|
|
8
8
|
|
|
9
|
+
## Session Boundary Protocol
|
|
10
|
+
|
|
11
|
+
### On Start
|
|
12
|
+
1. `.harness/progress.json` 읽기 — `next_agent`가 `"evaluator-visual"`인지 확인
|
|
13
|
+
2. progress.json 업데이트: `current_agent` → `"evaluator-visual"`, `agent_status` → `"running"`, `updated_at` 갱신
|
|
14
|
+
|
|
15
|
+
### On Complete (PASS)
|
|
16
|
+
1. progress.json 업데이트:
|
|
17
|
+
- `agent_status` → `"completed"`
|
|
18
|
+
- `completed_agents`에 `"evaluator-visual"` 추가
|
|
19
|
+
- `next_agent` → `"archive"`
|
|
20
|
+
- `failure` 필드 초기화
|
|
21
|
+
2. `feature-list.json`의 통과 feature `passes`에 `"evaluator-visual"` 추가
|
|
22
|
+
3. `.harness/progress.log`에 PASS 요약 추가
|
|
23
|
+
4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
24
|
+
5. 출력: `"✓ Evaluator-Visual PASS. bash scripts/harness-next.sh 실행하여 아카이브 진행."`
|
|
25
|
+
|
|
26
|
+
### On Fail
|
|
27
|
+
1. progress.json 업데이트:
|
|
28
|
+
- `agent_status` → `"failed"`
|
|
29
|
+
- `failure.agent` → `"evaluator-visual"`
|
|
30
|
+
- `failure.location` → `"frontend"` (비주얼 = 항상 프론트)
|
|
31
|
+
- `failure.message` → 실패 요약 (1줄)
|
|
32
|
+
- `failure.retry_target` → `"generator-frontend"`
|
|
33
|
+
- `next_agent` → `"generator-frontend"`
|
|
34
|
+
- `sprint.retry_count` 증가
|
|
35
|
+
2. `sprint.retry_count >= 10`이면 `agent_status` → `"blocked"`, 사용자 개입 요청
|
|
36
|
+
3. `.harness/progress.log`에 FAIL 요약 추가
|
|
37
|
+
4. **STOP.**
|
|
38
|
+
5. 출력: `"✖ Evaluator-Visual FAIL. bash scripts/harness-next.sh 실행하여 재작업 대상 확인."`
|
|
39
|
+
|
|
9
40
|
## Startup
|
|
10
41
|
|
|
11
42
|
1. `AGENTS.md` 읽기
|
|
@@ -37,5 +68,5 @@ disable-model-invocation: true
|
|
|
37
68
|
|
|
38
69
|
## After Evaluation
|
|
39
70
|
|
|
40
|
-
- **PASS** →
|
|
41
|
-
- **FAIL** →
|
|
71
|
+
- **PASS** → Session Boundary Protocol On Complete (PASS) 실행
|
|
72
|
+
- **FAIL** → Session Boundary Protocol On Fail 실행
|
|
@@ -6,11 +6,29 @@ disable-model-invocation: true
|
|
|
6
6
|
|
|
7
7
|
# Generator-Backend — NestJS MSA
|
|
8
8
|
|
|
9
|
+
## Session Boundary Protocol
|
|
10
|
+
|
|
11
|
+
### On Start
|
|
12
|
+
1. `.harness/progress.json` 읽기 — `next_agent`가 `"generator-backend"`인지 확인
|
|
13
|
+
2. progress.json 업데이트: `current_agent` → `"generator-backend"`, `agent_status` → `"running"`, `updated_at` 갱신
|
|
14
|
+
3. `failure` 필드 확인 — retry인 경우 `evaluation-functional.md`의 실패 사유 우선 읽기
|
|
15
|
+
|
|
16
|
+
### On Complete
|
|
17
|
+
1. progress.json 업데이트:
|
|
18
|
+
- `agent_status` → `"completed"`
|
|
19
|
+
- `completed_agents`에 `"generator-backend"` 추가
|
|
20
|
+
- `next_agent` → 파이프라인에 따라 결정 (FULLSTACK: `"generator-frontend"`, BE-ONLY: `"evaluator-functional"`)
|
|
21
|
+
- `failure` 필드 초기화 (retry 성공 시)
|
|
22
|
+
2. `feature-list.json`의 해당 feature `passes`에 `"generator-backend"` 추가
|
|
23
|
+
3. `.harness/progress.log`에 요약 추가
|
|
24
|
+
4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
25
|
+
5. 출력: `"✓ Generator-Backend 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
|
|
26
|
+
|
|
9
27
|
## Startup
|
|
10
28
|
|
|
11
29
|
1. `AGENTS.md` 읽기 — IA-MAP, 권한 확인
|
|
12
30
|
2. `.harness/gotchas/generator-backend.md` 읽기 — **과거 실수 반복 금지**
|
|
13
|
-
3. `pwd` + `.harness/progress.
|
|
31
|
+
3. `pwd` + `.harness/progress.json` + `git log --oneline -20`
|
|
14
32
|
4. `.harness/actions/api-contract.json` 읽기 — **이것이 스펙**
|
|
15
33
|
5. `.harness/actions/feature-list.json` — `layer: "backend"` 필터
|
|
16
34
|
6. 통합 러너: `npm run dev`
|
|
@@ -6,11 +6,29 @@ disable-model-invocation: true
|
|
|
6
6
|
|
|
7
7
|
# Generator-Frontend — React/Next.js + Premium Design
|
|
8
8
|
|
|
9
|
+
## Session Boundary Protocol
|
|
10
|
+
|
|
11
|
+
### On Start
|
|
12
|
+
1. `.harness/progress.json` 읽기 — `next_agent`가 `"generator-frontend"`인지 확인
|
|
13
|
+
2. progress.json 업데이트: `current_agent` → `"generator-frontend"`, `agent_status` → `"running"`, `updated_at` 갱신
|
|
14
|
+
3. `failure` 필드 확인 — retry인 경우 평가 문서의 실패 사유 우선 읽기
|
|
15
|
+
|
|
16
|
+
### On Complete
|
|
17
|
+
1. progress.json 업데이트:
|
|
18
|
+
- `agent_status` → `"completed"`
|
|
19
|
+
- `completed_agents`에 `"generator-frontend"` 추가
|
|
20
|
+
- `next_agent` → `"evaluator-functional"`
|
|
21
|
+
- `failure` 필드 초기화 (retry 성공 시)
|
|
22
|
+
2. `feature-list.json`의 해당 feature `passes`에 `"generator-frontend"` 추가
|
|
23
|
+
3. `.harness/progress.log`에 요약 추가
|
|
24
|
+
4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
25
|
+
5. 출력: `"✓ Generator-Frontend 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
|
|
26
|
+
|
|
9
27
|
## Startup
|
|
10
28
|
|
|
11
29
|
1. `AGENTS.md` 읽기 — IA-MAP, 권한 확인
|
|
12
30
|
2. `.harness/gotchas/generator-frontend.md` 읽기 — **과거 실수 반복 금지**
|
|
13
|
-
3. `pwd` + `.harness/progress.
|
|
31
|
+
3. `pwd` + `.harness/progress.json` + `git log --oneline -20`
|
|
14
32
|
4. `.harness/actions/api-contract.json` 읽기 — **Gateway가 유일한 API 인터페이스**
|
|
15
33
|
5. `.harness/actions/feature-list.json` — `layer: "frontend"` 필터
|
|
16
34
|
6. Gateway 확인: `curl -s http://localhost:3000/health`
|
package/skills/planner/SKILL.md
CHANGED
|
@@ -6,11 +6,26 @@ disable-model-invocation: true
|
|
|
6
6
|
|
|
7
7
|
# Planner Agent
|
|
8
8
|
|
|
9
|
+
## Session Boundary Protocol
|
|
10
|
+
|
|
11
|
+
### On Start
|
|
12
|
+
1. `.harness/progress.json` 읽기 — `next_agent`가 `"planner"`인지 확인
|
|
13
|
+
2. progress.json 업데이트: `current_agent` → `"planner"`, `agent_status` → `"running"`, `updated_at` 갱신
|
|
14
|
+
|
|
15
|
+
### On Complete
|
|
16
|
+
1. progress.json 업데이트:
|
|
17
|
+
- `agent_status` → `"completed"`
|
|
18
|
+
- `completed_agents`에 `"planner"` 추가
|
|
19
|
+
- `next_agent` → 파이프라인에 따라 결정 (FULLSTACK/BE-ONLY: `"generator-backend"`, FE-ONLY: `"generator-frontend"`)
|
|
20
|
+
2. `.harness/progress.log`에 요약 추가
|
|
21
|
+
3. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
22
|
+
4. 출력: `"✓ Planner 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
|
|
23
|
+
|
|
9
24
|
## Startup
|
|
10
25
|
|
|
11
26
|
1. `AGENTS.md` 읽기
|
|
12
27
|
2. `.harness/gotchas/planner.md` 읽기 — **과거 실수 반복 금지**
|
|
13
|
-
3. `.harness/progress.
|
|
28
|
+
3. `.harness/progress.json` 읽기
|
|
14
29
|
4. `.harness/actions/pipeline.json` 읽기 — `planner_mode` 확인
|
|
15
30
|
|
|
16
31
|
## Outputs (4개)
|
|
@@ -43,6 +58,5 @@ disable-model-invocation: true
|
|
|
43
58
|
|
|
44
59
|
## After Completion
|
|
45
60
|
|
|
46
|
-
1.
|
|
47
|
-
2.
|
|
48
|
-
3. 승인 후 → 다음 에이전트 (pipeline.json 참조)
|
|
61
|
+
1. 사용자에게 plan.md + api-contract.json 리뷰 요청
|
|
62
|
+
2. 승인 후 → Session Boundary Protocol On Complete 실행
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Harness Progress Log
|
|
2
|
-
# 세션 간 상태 전달을 위한 진행 기록 파일
|
|
3
|
-
# ─────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
## [{{DATE}}] Harness Initialized
|
|
6
|
-
- walwal-harness 설치 완료
|
|
7
|
-
- 6-Agent 아키텍처 (Dispatcher → Planner → Gen-BE → Gen-FE → Eval-Func → Eval-Visual)
|
|
8
|
-
- Gotcha 관리 시스템 활성화
|
|
9
|
-
|
|
10
|
-
## Current State
|
|
11
|
-
- Phase: INIT (Dispatcher 실행 대기)
|
|
12
|
-
- Active Sprint: none
|
|
13
|
-
- Features Completed: 0 / 0
|
|
14
|
-
- Blocking Issues: none
|
|
15
|
-
- Next Action: "하네스 엔지니어링 시작"으로 Dispatcher 실행
|