@walwal-harness/cli 4.0.0-alpha.13 → 4.0.0-alpha.15

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 CHANGED
@@ -1,4 +1,4 @@
1
- # walwal-harness
1
+ # @walwal-harness/cli
2
2
 
3
3
  **AI 에이전트를 위한 프로덕션 하네스 엔지니어링 프레임워크**
4
4
 
@@ -9,332 +9,333 @@
9
9
 
10
10
  ---
11
11
 
12
+ ## 두 가지 모드
13
+
14
+ | 모드 | 설명 | 실행 방법 |
15
+ |------|------|----------|
16
+ | **Classic (v3)** | 순차 실행 — Planner → Generator → Evaluator 1세션씩 | `하네스 엔지니어링 시작` |
17
+ | **Agent Teams (v4)** | 병렬 실행 — 3 Team이 Feature 단위 Gen→Eval 루프 동시 실행 | `/harness-team` 또는 `npx walwal-harness v4` |
18
+
19
+ ---
20
+
12
21
  ## 설치
13
22
 
23
+ ### 첫 설치
24
+
14
25
  ```bash
26
+ cd your-project
15
27
  npm install @walwal-harness/cli
16
28
  ```
17
29
 
18
- `postinstall`이 자동으로 프로젝트 루트에 설치합니다:
19
-
20
- 1. `.harness/` 디렉토리 스캐폴딩 (actions, archive, gotchas, config)
21
- 2. `.claude/skills/` 에 에이전트 스킬 설치 (9개)
22
- 3. `scripts/` 오케스트레이션 헬퍼 스크립트 설치
23
- 4. `SessionStart` 등록 (세션 시작 시 진행 상태 표시)
24
- 5. `UserPromptSubmit` 훅 등록 (모든 프롬프트를 Dispatcher 경유 자동 라우팅)
25
- 6. `AGENTS.md` 생성 + `CLAUDE.md` 심볼릭 링크
26
- 7. 기존 프로젝트면 구조 스캔 → IA-MAP 자동 생성
30
+ `postinstall`이 자동으로:
31
+ 1. `.harness/` 디렉토리 스캐폴딩
32
+ 2. `.claude/skills/` 에이전트 스킬 설치 (8개)
33
+ 3. `scripts/` 에 오케스트레이션 스크립트 설치
34
+ 4. SessionStart / UserPromptSubmit 등록
35
+ 5. `AGENTS.md` + `CLAUDE.md` 심볼릭 링크 생성
27
36
 
28
37
  > **중요:** 설치 후 Claude Code 세션을 **재시작**해야 skills가 인식됩니다.
29
- > `/exit` → 디렉터리 재진입이 필요합니다.
30
38
 
31
- ### CLI 명령
39
+ ### 기존 설치자 업데이트
32
40
 
33
41
  ```bash
34
- npx walwal-harness # 초기화 (postinstall과 동일)
35
- npx walwal-harness --force # 강제 재초기화 (기존 파일 덮어쓰기)
36
- npx walwal-harness --help # 도움말
37
- ```
42
+ # latest (v3 안정판)
43
+ npm update @walwal-harness/cli
38
44
 
39
- ### 초기화에서 일어나는
45
+ # v4 alpha (Agent Teams)
46
+ npm install @walwal-harness/cli@next
40
47
 
48
+ # 스크립트 동기화 (코어 스크립트 자동 덮어쓰기)
49
+ npx walwal-harness
41
50
  ```
42
- your-project/
43
- ├── AGENTS.md # 에이전트 공통 컨텍스트 (Planner 관리)
44
- ├── CLAUDE.md → AGENTS.md # 심볼릭 링크
45
- ├── scripts/
46
- │ ├── harness-next.sh # 세션 오케스트레이터
47
- │ ├── harness-session-start.sh # SessionStart
48
- │ ├── harness-user-prompt-submit.sh # UserPromptSubmit (auto-routing)
49
- │ ├── scan-project.sh # 프로젝트 구조 스캔
50
- │ ├── init-agents-md.sh # AGENTS.md 생성/리빌드
51
- │ └── lib/
52
- │ └── harness-render-progress.sh # 프로그레스 바 렌더러
53
- ├── .harness/
54
- │ ├── config.json # 하네스 설정
55
- │ ├── HARNESS.md # 하네스 상세 가이드
56
- │ ├── progress.json # 세션 간 상태 (기계 판독)
57
- │ ├── gotchas/ # 에이전트별 실수 기록
58
- │ ├── actions/ # 활성 스프린트 문서
59
- │ └── archive/ # 완료 스프린트 보관 (불변)
60
- └── .claude/
61
- ├── settings.json # 훅 등록 (SessionStart + UserPromptSubmit)
62
- └── skills/ # Claude Code 스킬 (9개)
63
- ├── harness-dispatcher/
64
- ├── harness-brainstorming/
65
- ├── harness-planner/
66
- ├── harness-generator-backend/
67
- ├── harness-generator-frontend/
68
- ├── harness-generator-frontend-flutter/
69
- ├── harness-evaluator-functional/
70
- ├── harness-evaluator-functional-flutter/
71
- └── harness-evaluator-visual/
51
+
52
+ ### CLI 명령어
53
+
54
+ ```bash
55
+ npx walwal-harness # 초기화 / 스크립트 업데이트
56
+ npx walwal-harness --force # 강제 재초기화
57
+ npx walwal-harness studio # Harness Studio v3 (tmux 5-pane)
58
+ npx walwal-harness v4 # Harness Studio v4 (Agent Teams, tmux 6-pane)
59
+ npx walwal-harness --help # 도움말
72
60
  ```
73
61
 
74
62
  ---
75
63
 
76
- ## Quick Start
64
+ ## Quick Start — Classic Mode (v3)
77
65
 
78
66
  ### 1. 설치 + 재시작
79
67
 
80
68
  ```bash
81
69
  cd your-project
82
70
  npm install @walwal-harness/cli
83
- # Claude Code 세션 재시작 (/exit → 재진입)
71
+ # Claude Code 재시작 (/exit → 재진입)
84
72
  ```
85
73
 
86
74
  ### 2. 하네스 시작
87
75
 
88
- Claude Code에서 아무 요청이나 입력하면 **UserPromptSubmit 훅이 자동으로 Dispatcher를 호출**합니다. 또는 명시적으로:
89
-
90
76
  ```
91
- > 하네스 엔지니어링 시작
77
+ 하네스 엔지니어링 시작
92
78
  ```
93
79
 
94
- ### 3. Dispatcher 분류
80
+ 또는 Claude Code에서 아무 요청 → UserPromptSubmit 훅이 자동으로 Dispatcher 호출합니다.
81
+
82
+ ### 3. 파이프라인 자동 선택
83
+
84
+ Dispatcher가 요청을 분석하여 파이프라인을 결정합니다:
95
85
 
96
86
  ```
97
- 사용자 입력
98
-
99
- ├─ 신규 기능/제품 요청파이프라인 선택 (FULLSTACK / FE-ONLY / BE-ONLY)
100
- │ └─ "브레인스토밍 필요합니까?" → Y: Brainstormer → Planner
101
- │ → N: Planner 직행
102
- ├─ 실수 지적 → Gotcha 기록 → 해당 에이전트 재작업
103
- ├─ 특정 에이전트 명령 → 해당 에이전트 직접 라우팅
104
- └─ 메타/인사 → 일반 응답
87
+ FULLSTACK : Planner → Gen-BE → Gen-FE → Eval-Func → Eval-Visual
88
+ FE-ONLY : Planner(light) → Gen-FE → Eval-Func → Eval-Visual
89
+ BE-ONLY : Planner Gen-BEEval-Func(API-only)
105
90
  ```
106
91
 
107
- ### 4. 에이전트 순차 실행
92
+ ### 4. 순차 실행
108
93
 
109
- 각 에이전트가 완료되면 다음 프롬프트를 제안합니다:
94
+ 각 에이전트가 독립 Claude Code 세션에서 실행됩니다:
110
95
 
111
96
  ```
112
- Dispatcher 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인.
97
+ /harness-planner # Plan 작성
98
+ ❯ /harness-generator-frontend # 코드 생성
99
+ ❯ /harness-evaluator-functional # E2E 테스트
113
100
  ```
114
101
 
115
- `bash scripts/harness-next.sh` 실행하면 프로그레스 + 다음 에이전트 안내:
102
+ 에이전트가 완료되면 **새 세션**을 시작하면 자동으로 다음 에이전트를 안내합니다.
116
103
 
117
- ```
118
- ═══ Sprint 1 / FULLSTACK ═══════════════════
104
+ ### 5. Eval FAIL → 자동 재작업
119
105
 
120
- Agents: planner✓generator-backend✓ [generator-frontend] evaluator-functional evaluator-visual
106
+ Evaluator FAIL 실패 원인 + 피드백과 함께 Generator로 자동 리라우팅 (최대 10회).
121
107
 
122
- /harness-generator-frontend 를 실행하세요.
123
- ```
108
+ ---
124
109
 
125
- ### 5. 반복
110
+ ## Quick Start — Agent Teams (v4)
126
111
 
127
- FAIL이 발생하면 Evaluator가 원인을 분석하고 해당 Generator로 자동 재작업 라우팅 (최대 10회). PASS하면 다음 Evaluator로 진행. 모든 Evaluator를 통과하면 스프린트 완료 → 아카이브.
112
+ > **v4는 alpha 단계입니다.** `npm install @walwal-harness/cli@next`로 설치하세요.
128
113
 
129
- ---
114
+ ### 개요
115
+
116
+ 3개 Agent Team이 **Feature 단위**로 Gen→Eval 루프를 **병렬 실행**합니다.
117
+
118
+ ```
119
+ 기존 (v3): Planner → Gen(F-001~028 전부) → Eval(전부)
120
+ v4: Planner → 3 Teams 병렬, 각 Team이 Feature 1개씩 Gen→Eval
121
+ ```
130
122
 
131
- ## 파이프라인
123
+ ### 1. 설치
132
124
 
133
- Dispatcher가 사용자 요청을 분석하여 3가지 파이프라인 중 하나를 자동 선택합니다:
125
+ ```bash
126
+ npm install @walwal-harness/cli@next
127
+ npx walwal-harness # 스크립트 동기화
128
+ ```
134
129
 
135
- ### FULLSTACK (신규 프로젝트)
130
+ ### 2. Planner 실행 (Classic으로)
131
+
132
+ v4에서도 Planner는 먼저 실행해야 합니다:
136
133
 
137
134
  ```
138
- Brainstormer? Planner → Generator-BE → Generator-FE → Evaluator-Func → Evaluator-Visual
139
- FAIL
140
- └──→ 재작업 (max 10회)
135
+ 하네스 엔지니어링 시작
136
+ # Dispatcher → Planner → feature-list.json 생성
141
137
  ```
142
138
 
143
- ### FE-ONLY (기존 API에 프론트엔드 연동)
139
+ ### 3. Agent Teams 실행
144
140
 
145
141
  ```
146
- Brainstormer? → Planner(light) → Generator-FE → Evaluator-Func → Evaluator-Visual
147
- └─ OpenAPI → api-contract.json 자동 변환
142
+ /harness-team
148
143
  ```
149
144
 
150
- ### BE-ONLY (기존 서버에 백엔드 기능 추가)
145
+ 또는:
151
146
 
147
+ ```bash
148
+ npx walwal-harness v4
152
149
  ```
153
- Brainstormer? → Planner → Generator-BE → Evaluator-Func(API-only)
150
+
151
+ ### 4. tmux 레이아웃
152
+
153
+ ```
154
+ ┌──────────────┬──────────────┬──────────────┐
155
+ │ │ Progress │ Team 1 │
156
+ │ │ (Queue, │ Gen→Eval │
157
+ │ Main │ Teams, ├──────────────┤
158
+ │ (Claude) │ Features) │ Team 2 │
159
+ │ ├──────────────┤ Gen→Eval │
160
+ │ │ Prompts ├──────────────┤
161
+ │ │ (History) │ Team 3 │
162
+ │ │ │ Gen→Eval │
163
+ └──────────────┴──────────────┴──────────────┘
154
164
  ```
155
165
 
156
- > `Brainstormer?` = Dispatcher가 사용자에게 확인 후 조건부 실행. 명확한 PRD/OpenAPI가 있으면 생략 가능.
166
+ | 패널 | 역할 |
167
+ |------|------|
168
+ | **Main** | 사용자 대화형 Claude — 오케스트레이터 역할 |
169
+ | **Progress** | Feature Queue + Team 상태 + Feature 목록 (자동 갱신) |
170
+ | **Prompts** | 사용자 매뉴얼 프롬프트 + Team 활동 로그 (newest first) |
171
+ | **Team 1~3** | `claude -p --dangerously-skip-permissions` headless worker |
157
172
 
158
- ### Flutter 프로젝트 자동 감지 (Web / Mobile / Desktop)
173
+ ### 5. 작동 원리
159
174
 
160
- `pubspec.yaml` + `flutter:` 키가 감지되면 FE 에이전트가 **자동 치환**됩니다. **Flutter Web** 은 컴파일 결과가 HTML+JS+CSS 이므로 React 와 동일하게 Playwright 기반 evaluator 를 그대로 사용하고, **Mobile/Desktop** 만 정적 분석 evaluator 로 교체됩니다.
175
+ ```
176
+ Feature Queue (dependency-aware topological sort)
177
+ Ready: [F-002, F-003, F-007] ← depends_on 충족된 것만
178
+ Blocked: [F-004, F-005, ...] ← 선행 Feature 미완료
161
179
 
162
- | fe_stack | fe_target | Generator | Eval-Functional | Eval-Visual |
163
- |----------|-----------|-----------|----------------|-------------|
164
- | react | (n/a) | `generator-frontend` | `evaluator-functional` (Playwright) | `evaluator-visual` |
165
- | **flutter** | **web** | `generator-frontend-flutter` | **`evaluator-functional`** (Playwright!) | **`evaluator-visual`** (Playwright!) |
166
- | **flutter** | mobile | `generator-frontend-flutter` | `evaluator-functional-flutter` (flutter analyze/test) | **SKIP** |
167
- | **flutter** | desktop | `generator-frontend-flutter` | `evaluator-functional-flutter` (flutter analyze/test) | **SKIP** |
180
+ Team 1: dequeue F-002 worktree 생성 Gen → Gate → Eval
181
+ PASS → merge to main → worktree 정리 → unblock dependents → dequeue next
182
+ FAIL retry (max 3) 3회 실패 failed 처리
183
+ ```
168
184
 
169
- **감지 방식**: `scan-project.sh` Flutter 프로젝트의 `web/index.html`, `android/`, `ios/`, `macos/`, `windows/`, `linux/` 디렉터리 존재 여부로 `fe_target` 을 자동 판정. 멀티 타겟 또는 모호한 경우 Planner 가 사용자에게 확인합니다. 치환은 `pipeline.json.fe_stack + fe_target` 조합에 의해 `harness-next.sh` 가 자동 처리합니다.
185
+ - Team은 **git worktree**로 격리 실행 (파일 충돌 없음)
186
+ - Feature PASS 시 main으로 **auto-merge** + worktree 즉시 삭제
187
+ - Merge conflict 시 auto-rebase 시도
170
188
 
171
- ---
189
+ ### 6. Main에서 할 수 있는 것
172
190
 
173
- ## 에이전트
191
+ Main Claude는 **오케스트레이터**입니다:
192
+ - 대시보드 상태 확인 요청
193
+ - 실패한 Feature 분석/피드백
194
+ - 코드 리뷰, 아키텍처 판단
195
+ - Gotcha 등록 (실수 지적 → 이후 Team에 반영)
174
196
 
175
- ### 9개 에이전트 (조건부 포함)
197
+ Main에서는 `/harness-generator-*`, `/harness-evaluator-*` **직접 호출하지 않습니다** — Team이 처리합니다.
176
198
 
177
- | 에이전트 | 역할 | 호출 조건 |
178
- |----------|------|----------|
179
- | **Dispatcher** | 파이프라인 선택 + Gotcha 관리 + 라우팅 | 항상 (auto-routing 훅) |
180
- | **Brainstormer** | 러프한 요구사항을 대화형으로 구체화 | Dispatcher가 사용자에게 확인 후 |
181
- | **Planner** | 제품 사양 + API 계약서 + 서비스 분할 | 파이프라인 시작 시 |
182
- | **Generator-Backend** | NestJS MSA 구현 (Gateway + Microservices) | FULLSTACK, BE-ONLY |
183
- | **Generator-Frontend** | React/Next.js UI + API 연동 | FULLSTACK, FE-ONLY (React) |
184
- | **Generator-Frontend-Flutter** | Flutter (Riverpod + Retrofit + ARB i18n) | FULLSTACK, FE-ONLY (Flutter) |
185
- | **Evaluator-Functional** | Playwright E2E 기능 검증 | FULLSTACK, FE-ONLY (React) |
186
- | **Evaluator-Functional-Flutter** | flutter analyze/test + 정적 anti-pattern 검증 | FULLSTACK, FE-ONLY (Flutter) |
187
- | **Evaluator-Visual** | 디자인 일관성, 반응형, 접근성, AI슬롭 감지 | FULLSTACK, FE-ONLY (React만) |
199
+ ---
188
200
 
189
- ### Brainstormer (조건부)
201
+ ## 에이전트
190
202
 
191
- [obra/superpowers](https://github.com/obra/superpowers) (MIT License)의 brainstorming 방법론을 이식.
192
- 바이브코딩 수준의 러프한 요구사항을 **대화형 Q&A로 구체화**하여 Planner가 바로 소비할 수 있는 `brainstorm-spec.md`로 만듭니다.
203
+ | 에이전트 | 역할 | 모델 |
204
+ |----------|------|------|
205
+ | **Dispatcher** | 파이프라인 선택 + Gotcha 관리 | opus |
206
+ | **Brainstormer** | 러프한 요구사항 대화형 구체화 | opus |
207
+ | **Planner** | 제품 사양 + API 계약 + Feature 설계 | opus/ultraplan |
208
+ | **Generator-Backend** | NestJS MSA 구현 | sonnet |
209
+ | **Generator-Frontend** | React/Next.js UI 구현 | sonnet |
210
+ | **Evaluator-Functional** | Playwright E2E 기능 검증 | opus/ultrathink |
211
+ | **Evaluator-Visual** | 디자인, 반응형, 접근성 검증 | opus/ultrathink |
193
212
 
194
- **핵심 원칙:**
195
- - HARD-GATE: 사용자 승인 없이 구현 단계로 넘어갈 수 없음
196
- - 한 번에 한 질문, 객관식 우선
197
- - 2-3개 접근법 제시 후 사용자가 선택
198
- - Spec self-review + User Review Gate
213
+ ### Brainstormer (조건부)
199
214
 
200
- **실행 조건:** Dispatcher가 "브레인스토밍 과정이 필요합니까? (Y/N)" 라고 묻고 사용자가 Y 응답한 경우에만. 피드백/이터레이션/직접 명령에서는 자동 스킵.
215
+ [obra/superpowers](https://github.com/obra/superpowers) (MIT License) 기반. 바이브코딩 수준의 러프한 요구사항을 대화형 Q&A로 구체화합니다.
201
216
 
202
217
  ---
203
218
 
204
219
  ## 핵심 메커니즘
205
220
 
206
- ### Auto-Routing (UserPromptSubmit Hook)
221
+ ### Auto-Routing
207
222
 
208
- 설치 시 `.claude/settings.json`에 `UserPromptSubmit` 훅이 자동 등록됩니다. 모든 사용자 프롬프트가 Dispatcher를 경유하도록 컨텍스트를 주입합니다.
223
+ 모든 사용자 프롬프트가 Dispatcher를 자동 경유합니다.
209
224
 
210
- **per-message opt-out:**
211
- ```
212
- > harness skip 그냥 이것만 답해줘
213
- > harness 없이 질문 하나만
214
- > without harness ...
215
225
  ```
226
+ # per-message opt-out
227
+ ❯ harness skip 그냥 답해줘
216
228
 
217
- **전역 비활성:**
218
- ```json
219
- // .harness/config.json
220
- { "behavior": { "auto_route_dispatcher": false } }
229
+ # 전역 비활성
230
+ .harness/config.json → behavior.auto_route_dispatcher = false
221
231
  ```
222
232
 
223
233
  ### API Contract — 진실의 원천
224
234
 
225
- `api-contract.json`이 Frontend ↔ Gateway ↔ Microservices 간 유일한 계약서입니다. Planner만 수정 가능하며, Generator-BE는 이를 구현하고, Generator-FE는 이를 소비합니다.
226
-
227
- ### AGENTS.md — 에이전트 불문 범용 컨텍스트
235
+ `api-contract.json`이 FE ↔ Gateway ↔ Services 간 유일한 계약서입니다.
228
236
 
229
- 모든 AI 에이전트(Claude, Cursor, Copilot, Windsurf)가 읽을 수 있는 공통 진입점입니다. 1차원 IA-MAP으로 폴더별 책임과 소유 에이전트를 명시합니다.
237
+ ### Gotcha 시스템
230
238
 
231
- ```
232
- ├── apps/gateway/ # [BE] API Gateway → Generator-Backend
233
- ├── apps/service-a/ # [BE] Microservice → Generator-Backend
234
- ├── apps/web/ # [FE] Frontend App → Generator-Frontend
235
- └── .harness/ # [HARNESS] 하네스 시스템 → Planner
236
- ```
239
+ 사용자가 실수를 지적하면 Dispatcher가 자동 기록 → 이후 세션에서 반복 방지.
237
240
 
238
- ### Gotcha 시스템 — 실수를 반복하지 않는 에이전트
241
+ ### Pre-Eval Gate
239
242
 
240
- 사용자가 에이전트의 실수를 지적하면 Dispatcher가 자동으로 감지하여 해당 에이전트의 gotchas 파일에 기록합니다. 이후 세션에서 에이전트는 시작 자신의 gotchas를 읽고 같은 실수를 반복하지 않습니다.
243
+ Generator Evaluator 전환 결정론적 검증 (tsc, eslint, test). 실패Evaluator 세션을 열지 않고 Generator로 자동 리라우팅.
241
244
 
245
+ ```json
246
+ // .harness/config.json
247
+ "pre_eval_gate": {
248
+ "frontend_checks": ["npx tsc --noEmit", "npx eslint src/", "npx vitest run --bail 1"],
249
+ "frontend_cwd": "path/to/frontend" // 프로젝트 루트와 다른 경우
250
+ }
242
251
  ```
243
- 사용자: "API 응답에 created_at은 ISO 8601로 반환해야 해"
244
- → Dispatcher: generator-backend.md에 [G-001] 기록
245
- → Generator-BE 다음 세션: gotchas 읽기 → 같은 실수 방지
246
- ```
247
-
248
- ### IA Structure Compliance — Step 0 Gate
249
252
 
250
- Evaluator는 기능 테스트 전에 AGENTS.md의 IA-MAP과 실제 폴더 구조를 대조합니다. 경로 누락이나 소유권 침범이 발견되면 **기능 테스트 없이 즉시 FAIL**합니다.
253
+ ### Evaluation System
251
254
 
252
- ### 브라운필드 지원
253
-
254
- 기존 프로젝트에 설치하면:
255
- - `scan-project.sh`가 Tech Stack, 폴더 구조, 기존 CLAUDE.md를 자동 스캔
256
- - 기존 CLAUDE.md 규칙을 "Preserved Rules" 섹션으로 이관
257
- - 원본은 `.harness/archive/pre-harness-backup/`에 백업
258
- - Flutter 프로젝트 자동 감지 (`pubspec.yaml` + `flutter:`)
255
+ | 설정 | 값 |
256
+ |------|------|
257
+ | PASS 기준 | 2.80 / 3.00 이상 |
258
+ | FAIL 기준 | 2.79 이하 (예외 없음) |
259
+ | Evidence 없는 Score | 0점 강제 |
260
+ | Regression 실패 | 1건이라도 → 전체 FAIL |
259
261
 
260
262
  ---
261
263
 
262
- ## FE 스택별 지원
264
+ ## Harness Studio (tmux)
263
265
 
264
- ### React / Next.js (기본)
266
+ ### Studio v3 (Classic)
265
267
 
266
- | 항목 | 상세 |
267
- |------|------|
268
- | Generator | `harness-generator-frontend` — RSC, App Router, Tailwind CSS, Cache Components |
269
- | Evaluator-Func | `harness-evaluator-functional` — Playwright MCP (`browser_*`) E2E 테스트 |
270
- | Evaluator-Visual | `harness-evaluator-visual` — 스크린샷 기반 디자인/접근성/반응형 검증 |
271
- | 레퍼런스 | Vercel Best Practices, Design System Rules, AI Forbidden Patterns, Component Patterns |
272
-
273
- ### Flutter (Dart) — Web / Mobile / Desktop
274
-
275
- | 항목 | Web (`fe_target=web`) | Mobile/Desktop (`fe_target=mobile`/`desktop`) |
276
- |------|----------------------|---------------------------------------------|
277
- | Generator | `harness-generator-frontend-flutter` (Web 가이드 활성) | `harness-generator-frontend-flutter` (Mobile/Desktop 가이드) |
278
- | Eval-Func | **`harness-evaluator-functional`** (Playwright MCP) | `harness-evaluator-functional-flutter` (`flutter analyze` + `flutter test` + build_runner drift + FL-01~FL-08 정적 검증) |
279
- | Eval-Visual | **`harness-evaluator-visual`** (Playwright 스크린샷/반응형/접근성) | **SKIP** (브라우저 없음) |
280
- | 레퍼런스 공통 | API Layer Pattern, Riverpod Pattern, i18n Pattern, Anti-Patterns | 동일 |
281
- | 레퍼런스 추가 | **Flutter Web Pattern** (`dart:html`/`package:web` 허용, go_router, CORS, PWA, 빌드/호스팅) | — |
282
- | dart:html / package:web | **허용** (가능하면 conditional import 권장) | **금지** (모바일/데스크톱 빌드 실패 유발) |
283
- | 빌드 명령 | `flutter build web --release` / `flutter run -d chrome` | `flutter build apk` / `flutter build ios` / `flutter build macos` 등 |
284
-
285
- ### FE 스택 + 타겟 감지
286
-
287
- 1. `scan-project.sh`가 `pubspec.yaml` + `flutter:` 키를 탐지하여 `fe_stack = "flutter"` 설정
288
- 2. 같은 스캔이 `web/index.html`, `android/`, `ios/`, `macos/`/`windows/`/`linux/` 존재 여부로 `fe_target` 자동 판정 (`web` / `mobile` / `desktop`)
289
- 3. Planner 가 모호한 경우(멀티 타겟, unknown) 사용자에게 단 한 번 확인하고 `pipeline.json.fe_target` 확정
290
- 4. `harness-next.sh`가 `fe_stack + fe_target` 조합에 따라 FE 에이전트를 자동 치환 (Agent Bar에도 반영)
291
-
292
- ---
293
-
294
- ## 스크립트 레퍼런스
268
+ ```bash
269
+ npx walwal-harness studio
270
+ ```
295
271
 
296
- | 스크립트 | 설명 | 실행 방법 |
297
- |----------|------|----------|
298
- | `harness-next.sh` | 세션 오케스트레이터 — progress.json 읽고 다음 에이전트 결정, 프로그레스 바 출력 | `bash scripts/harness-next.sh` |
299
- | `scan-project.sh` | 프로젝트 구조 스캔 → `.harness/actions/scan-result.json` 출력 | `bash scripts/scan-project.sh .` |
300
- | `init-agents-md.sh` | scan-result.json 기반 AGENTS.md 생성/리빌드 | `bash scripts/init-agents-md.sh .` |
301
- | `harness-session-start.sh` | SessionStart 훅 — 세션 시작 시 progress 요약 출력 | (자동 — .claude/settings.json 훅) |
302
- | `harness-user-prompt-submit.sh` | UserPromptSubmit 훅 — 모든 프롬프트를 Dispatcher 경유 라우팅 | (자동 — .claude/settings.json 훅) |
272
+ ```
273
+ ┌──────────────┬──────────────┐
274
+ │ Dashboard │ Monitor │
275
+ │ (Progress) ├──────────────┤
276
+ ├──────────────┤ Agent Session
277
+ │ Control ├──────────────┤
278
+ │ (harness>) │ Eval Review │
279
+ └──────────────┴──────────────┘
280
+ ```
303
281
 
304
- ---
282
+ ### Studio v4 (Agent Teams)
305
283
 
306
- ## Tech Stack
284
+ ```bash
285
+ npx walwal-harness v4
286
+ ```
307
287
 
308
- | 영역 | 기술 |
309
- |------|------|
310
- | Backend | NestJS (TypeScript) + MSA |
311
- | Frontend (React) | React 또는 Next.js (TypeScript) + Tailwind CSS |
312
- | Frontend (Flutter) | Flutter (Dart) + Riverpod + Retrofit + ARB i18n |
313
- | E2E Testing (React) | Playwright MCP |
314
- | E2E Testing (Flutter) | flutter analyze + flutter test + 정적 검증 |
315
- | Unit Testing | Jest (BE) + Vitest (FE-React) + flutter test (FE-Flutter) |
316
- | Database | PostgreSQL / SQLite |
288
+ ```
289
+ ┌──────────────┬──────────────┬──────────────┐
290
+ │ │ Progress │ Team 1 │
291
+ │ Main ├──────────────┤ Team 2 │
292
+ (Claude) │ Prompts │ Team 3 │
293
+ └──────────────┴──────────────┴──────────────┘
294
+ ```
317
295
 
318
296
  ---
319
297
 
320
- ## 권장 외부 스킬
321
-
322
- 하네스는 자체 reference 파일로 기본 가이드를 제공하지만, 아래 외부 스킬을 설치하면 품질이 향상됩니다. 초기화 시 자동으로 설치 여부를 체크하고 안내합니다.
298
+ ## 프로젝트 구조
323
299
 
324
- | 스킬 | 설명 | 사용 에이전트 |
325
- |------|------|-------------|
326
- | **vercel** | Vercel/Next.js 배포, 성능 최적화, AI SDK | Generator-FE |
327
- | **web-design-guidelines** | UI/UX 접근성, Web Interface Guidelines | Evaluator-Visual, Generator-FE |
328
- | **taste-skill** | AI 생성 UI 디자인 품질, AI슬롭 감지 | Evaluator-Visual, Generator-FE |
329
- | **supanova-design-skill** | 디자인 시스템 규칙, 시각적 일관성 | Evaluator-Visual, Generator-FE |
300
+ ```
301
+ your-project/
302
+ ├── AGENTS.md # 에이전트 공통 컨텍스트
303
+ ├── CLAUDE.md AGENTS.md # 심볼릭 링크
304
+ ├── scripts/
305
+ │ ├── harness-next.sh # 세션 오케스트레이터
306
+ │ ├── harness-session-start.sh # SessionStart 훅
307
+ │ ├── harness-user-prompt-submit.sh # UserPromptSubmit 훅
308
+ │ ├── harness-queue-manager.sh # v4: Feature Queue 관리
309
+ │ ├── harness-team-worker.sh # v4: Team Worker (Gen→Eval 루프)
310
+ │ ├── harness-studio-v4.sh # v4: tmux 레이아웃
311
+ │ ├── harness-dashboard-v4.sh # v4: Progress 패널
312
+ │ ├── harness-prompts-v4.sh # v4: Prompts 패널
313
+ │ └── lib/
314
+ ├── .harness/
315
+ │ ├── config.json # 하네스 설정
316
+ │ ├── HARNESS.md # 상세 가이드
317
+ │ ├── progress.json # 세션 간 상태
318
+ │ ├── progress.log # append-only 히스토리
319
+ │ ├── gotchas/ # 에이전트별 실수 기록
320
+ │ ├── actions/
321
+ │ │ ├── feature-list.json # 기능 목록 + AC
322
+ │ │ ├── feature-queue.json # v4: Feature Queue 상태
323
+ │ │ ├── api-contract.json # API 계약서
324
+ │ │ └── plan.md # 제품 사양
325
+ │ └── archive/ # 완료 스프린트 보관
326
+ └── .claude/
327
+ ├── settings.json # 훅 등록
328
+ └── skills/ # Claude Code 스킬 (8개)
329
+ ```
330
330
 
331
331
  ---
332
332
 
333
- ## Playwright MCP 설정 (React 프로젝트)
333
+ ## Playwright MCP 설정
334
334
 
335
- Evaluator-Functional / Evaluator-Visual이 브라우저 테스트를 수행하려면 Playwright MCP가 필요합니다. `~/.mcp.json`에 추가:
335
+ Evaluator 브라우저 테스트를 수행하려면 Playwright MCP가 필요합니다:
336
336
 
337
337
  ```json
338
+ // ~/.mcp.json
338
339
  {
339
340
  "mcpServers": {
340
341
  "playwright": {
@@ -345,8 +346,6 @@ Evaluator-Functional / Evaluator-Visual이 브라우저 테스트를 수행하
345
346
  }
346
347
  ```
347
348
 
348
- > **Flutter Web (`fe_target=web`) 프로젝트도 Playwright 가 필요합니다** — 컴파일 결과가 일반 웹앱과 동일하므로 React 와 같은 evaluator 를 재사용합니다. **Flutter Mobile/Desktop (`fe_target=mobile`/`desktop`) 프로젝트만** Playwright 없이 `flutter analyze` + `flutter test` 기반으로 동작합니다.
349
-
350
349
  ---
351
350
 
352
351
  ## 트러블슈팅
@@ -354,76 +353,30 @@ Evaluator-Functional / Evaluator-Visual이 브라우저 테스트를 수행하
354
353
  ### Skills가 로드되지 않아요
355
354
 
356
355
  ```bash
357
- # Claude Code 세션을 완전히 종료하고 다시 시작
358
- /exit
359
- # 프로젝트 디렉토리에서 재진입 후 확인
360
- ```
361
-
362
- Claude Code는 세션 시작 시 `.claude/skills/`를 스캔합니다. `npm install` 이후 반드시 세션 재시작이 필요합니다.
363
-
364
- ### Dispatcher가 자동 호출되지 않아요
365
-
366
- `UserPromptSubmit` 훅이 정상적으로 등록되었는지 확인:
367
-
368
- ```bash
369
- cat .claude/settings.json
370
- # hooks.UserPromptSubmit 배열에 harness-user-prompt-submit.sh가 있어야 함
356
+ /exit # Claude Code 세션 종료
357
+ # 프로젝트 디렉토리에서 재진입
371
358
  ```
372
359
 
373
- 등록이 되었다면:
360
+ ### 스크립트가 구버전이에요
374
361
 
375
362
  ```bash
376
- npx walwal-harness --force
377
- # 재시작
363
+ npx walwal-harness # 코어 스크립트 자동 덮어쓰기
378
364
  ```
379
365
 
380
- ### 브레인스토밍이 매번 실행돼서 피로해요
381
-
382
- Brainstormer는 Dispatcher가 "브레인스토밍 필요합니까?" 물을 때만 실행됩니다. **N**으로 답하면 Planner로 직행합니다. 피드백/이터레이션/특정 에이전트 명령에서는 질문 자체가 나오지 않습니다.
383
-
384
- ### Flutter 프로젝트인데 React 에이전트가 실행돼요
366
+ ### v4 Team이 "No features ready" 반복
385
367
 
386
368
  ```bash
387
- # 프로젝트 루트에 pubspec.yaml이 있고 flutter: 키가 존재하는지 확인
388
- grep "flutter:" pubspec.yaml
389
-
390
- # 재스캔
391
- bash scripts/scan-project.sh .
392
- # pipeline.json의 fe_stack + fe_target 값 확인
393
- cat .harness/actions/pipeline.json | jq '.fe_stack, .fe_target'
369
+ # Studio를 재시작하면 자동으로 stale in_progress 복구
370
+ npx walwal-harness v4
394
371
  ```
395
372
 
396
- ### Flutter Web 프로젝트인데 evaluator-functional-flutter (정적 분석) 가 실행돼요
397
-
398
- `fe_target` 이 `web` 으로 감지되지 않아서 발생합니다. 다음 두 가지를 확인:
399
-
400
- ```bash
401
- # 1) Flutter 프로젝트 루트에 web/index.html 이 존재하는가?
402
- ls -la web/index.html
403
- # 없으면 Flutter Web 활성화:
404
- flutter config --enable-web
405
- flutter create --platforms=web .
373
+ ### Auto-routing 끄기
406
374
 
407
- # 2) pipeline.json 의 fe_target 값 확인
408
- jq '.fe_target' .harness/actions/pipeline.json
409
- # "mobile" 또는 "unknown" 이면 수동 수정 또는 재스캔
410
- bash scripts/scan-project.sh .
411
375
  ```
412
-
413
- `fe_target = web` 으로 확정되면 Playwright 기반 `evaluator-functional` + `evaluator-visual` 이 자동 사용됩니다.
414
-
415
- ### Auto-routing을 끄고 싶어요
416
-
417
- ```json
418
- // .harness/config.json
419
- { "behavior": { "auto_route_dispatcher": false } }
376
+ ❯ harness skip 답만 해줘
420
377
  ```
421
378
 
422
- 또는 개별 메시지에서:
423
-
424
- ```
425
- > harness skip 그냥 이것만 답해줘
426
- ```
379
+ 또는 `.harness/config.json`에서 `behavior.auto_route_dispatcher = false`
427
380
 
428
381
  ---
429
382
 
@@ -432,7 +385,7 @@ bash scripts/scan-project.sh .
432
385
  - [Anthropic: Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents)
433
386
  - [Anthropic: Harness Design for Long-Running Application Development](https://www.anthropic.com/engineering/harness-design-long-running-apps)
434
387
  - [Claude Code Skills Documentation](https://code.claude.com/docs/en/skills)
435
- - [obra/superpowers](https://github.com/obra/superpowers) — Brainstorming skill 원본 (MIT License)
388
+ - [obra/superpowers](https://github.com/obra/superpowers) — Brainstorming skill (MIT License)
436
389
 
437
390
  ## License
438
391
 
package/bin/init.js CHANGED
@@ -231,6 +231,7 @@ function installScripts() {
231
231
  'harness-control-v4.sh',
232
232
  'harness-queue-manager.sh',
233
233
  'harness-team-worker.sh',
234
+ 'harness-prompts-v4.sh',
234
235
  ]);
235
236
 
236
237
  if (fs.existsSync(scriptsSrc)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "4.0.0-alpha.13",
3
+ "version": "4.0.0-alpha.15",
4
4
  "description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer (requirements refinement). Supports React and Flutter FE stacks.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
- # harness-dashboard-v4.sh — v4 Dashboard: Feature Queue + Team Status
3
- # Auto-refresh 3초 간격. feature-queue.json + feature-list.json 시각화.
2
+ # harness-dashboard-v4.sh — v4 Dashboard 상단: Planner Progress
3
+ # Queue + Teams + Features 를 auto-refresh. 고정 영역, 스크롤 없음.
4
4
 
5
5
  set -uo pipefail
6
6
 
@@ -30,42 +30,34 @@ render_header() {
30
30
  now=$(date +"%H:%M:%S")
31
31
  project_name=$(jq -r '.project_name // "Unknown"' "$PROGRESS" 2>/dev/null)
32
32
 
33
- echo -e "${BOLD}╔════════════════════════════════════════════════╗${RESET}"
34
- echo -e "${BOLD}║ HARNESS v4 — Parallel Agent Teams ║${RESET}"
35
- echo -e "${BOLD}╚════════════════════════════════════════════════╝${RESET}"
36
- echo -e " ${DIM}${project_name} | ${now}${RESET}"
37
- echo ""
33
+ echo -e "${BOLD}HARNESS v4${RESET} ${DIM}${project_name} | ${now}${RESET}"
38
34
  }
39
35
 
40
36
  render_queue_summary() {
41
37
  if [ ! -f "$QUEUE" ]; then
42
- echo -e " ${DIM}(queue not initialized — run 'init' in Control)${RESET}"
38
+ echo -e " ${DIM}(queue not initialized)${RESET}"
43
39
  return
44
40
  fi
45
41
 
46
- local ready blocked in_prog passed failed total concurrency
42
+ local ready blocked in_prog passed failed total
47
43
  ready=$(jq '.queue.ready | length' "$QUEUE" 2>/dev/null || echo 0)
48
44
  blocked=$(jq '.queue.blocked | length' "$QUEUE" 2>/dev/null || echo 0)
49
45
  in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
50
46
  passed=$(jq '.queue.passed | length' "$QUEUE" 2>/dev/null || echo 0)
51
47
  failed=$(jq '.queue.failed | length' "$QUEUE" 2>/dev/null || echo 0)
52
- concurrency=$(jq '.concurrency // 3' "$QUEUE" 2>/dev/null || echo 3)
53
48
  ready=${ready:-0}; blocked=${blocked:-0}; in_prog=${in_prog:-0}; passed=${passed:-0}; failed=${failed:-0}
54
49
  total=$((ready + blocked + in_prog + passed + failed))
55
50
 
56
- # Progress bar
57
51
  local pct=0
58
52
  if [ "$total" -gt 0 ]; then pct=$(( passed * 100 / total )); fi
59
- local bar_w=20
53
+ local bar_w=16
60
54
  local filled=$(( pct * bar_w / 100 ))
61
55
  local empty=$(( bar_w - filled ))
62
56
  local bar=""
63
57
  for ((i=0; i<filled; i++)); do bar+="█"; done
64
58
  for ((i=0; i<empty; i++)); do bar+="░"; done
65
59
 
66
- echo -e " ${BOLD}Queue${RESET} ${bar} ${passed}/${total} (${pct}%) ${DIM}concurrency=${concurrency}${RESET}"
67
- echo -e " Ready:${GREEN}${ready}${RESET} Blocked:${YELLOW}${blocked}${RESET} Progress:${CYAN}${in_prog}${RESET} Pass:${GREEN}${passed}${RESET} Fail:${RED}${failed}${RESET}"
68
- echo ""
60
+ echo -e " ${bar} ${passed}/${total} (${pct}%) R:${GREEN}${ready}${RESET} B:${YELLOW}${blocked}${RESET} P:${CYAN}${in_prog}${RESET} ${GREEN}✓${passed}${RESET} ${RED}✗${failed}${RESET}"
69
61
  }
70
62
 
71
63
  render_teams() {
@@ -75,49 +67,44 @@ render_teams() {
75
67
  team_count=$(jq '.teams | length' "$QUEUE" 2>/dev/null)
76
68
  if [ "${team_count:-0}" -eq 0 ]; then return; fi
77
69
 
78
- echo -e " ${BOLD}Teams${RESET}"
79
-
80
70
  for i in $(seq 1 "$team_count"); do
81
71
  local t_status t_feature t_phase t_attempt
82
72
  t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
83
73
  t_feature=$(jq -r ".teams[\"$i\"].feature // \"—\"" "$QUEUE" 2>/dev/null)
84
74
 
85
- # Get phase from in_progress
86
75
  if [ "$t_feature" != "—" ] && [ "$t_feature" != "null" ]; then
87
76
  t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
88
77
  t_attempt=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].attempt // 1' "$QUEUE" 2>/dev/null)
89
78
  else
90
- t_phase="—"
91
- t_attempt="—"
79
+ t_phase="—"; t_attempt=""
92
80
  fi
93
81
 
94
82
  local icon color
95
83
  case "$t_status" in
96
- busy) icon="▶" ; color="$GREEN" ;;
97
- idle) icon="○" ; color="$DIM" ;;
98
- paused) icon="" ; color="$YELLOW" ;;
99
- *) icon="?" ; color="$RESET" ;;
84
+ busy) icon="▶"; color="$GREEN" ;;
85
+ idle) icon="○"; color="$DIM" ;;
86
+ *) icon="?"; color="$RESET" ;;
100
87
  esac
101
88
 
102
- local phase_display=""
89
+ local phase_short=""
103
90
  case "$t_phase" in
104
- gen) phase_display="${CYAN}GEN${RESET}" ;;
105
- gate) phase_display="${YELLOW}GATE${RESET}" ;;
106
- eval) phase_display="${MAGENTA}EVAL${RESET}" ;;
107
- *) phase_display="${DIM}${t_phase}${RESET}" ;;
91
+ gen) phase_short="${CYAN}G${RESET}" ;;
92
+ gate) phase_short="${YELLOW}K${RESET}" ;;
93
+ eval) phase_short="${MAGENTA}E${RESET}" ;;
94
+ *) phase_short="${DIM}-${RESET}" ;;
108
95
  esac
109
96
 
110
- printf " %b %b Team %d %-8s %b attempt %s\n" "$color" "$icon" "$i" "$t_feature" "$phase_display" "$t_attempt"
97
+ printf " %b%b T%d %-7s %b" "$color" "$icon" "$i" "$t_feature" "$phase_short"
98
+ if [ -n "$t_attempt" ] && [ "$t_attempt" != "—" ]; then
99
+ printf " #%s" "$t_attempt"
100
+ fi
101
+ echo ""
111
102
  done
112
- echo ""
113
103
  }
114
104
 
115
- render_feature_list() {
105
+ render_features() {
116
106
  if [ ! -f "$QUEUE" ] || [ ! -f "$FEATURES" ]; then return; fi
117
107
 
118
- echo -e " ${BOLD}Features${RESET}"
119
-
120
- # Single jq call: merge feature-list + queue state → pre-formatted lines
121
108
  jq -r --slurpfile q "$QUEUE" '
122
109
  ($q[0].queue.passed // []) as $passed |
123
110
  ($q[0].queue.failed // []) as $failed |
@@ -125,7 +112,7 @@ render_feature_list() {
125
112
  ($q[0].queue.in_progress // {}) as $prog |
126
113
  .features[] |
127
114
  .id as $fid |
128
- (.name // .description // "?" | if length > 22 then .[0:20] + ".." else . end) as $fname |
115
+ (.name // .description // "?" | if length > 18 then .[0:16] + ".." else . end) as $fname |
129
116
  (if ($fid | IN($passed[])) then "P"
130
117
  elif $prog[$fid] then "I|\($prog[$fid].team)|\($prog[$fid].phase)"
131
118
  elif ($fid | IN($failed[])) then "F"
@@ -138,100 +125,25 @@ render_feature_list() {
138
125
  F) printf " ${RED}✗${RESET} %-6s %s\n" "$fid" "$fname" ;;
139
126
  R) printf " ${YELLOW}○${RESET} %-6s %s\n" "$fid" "$fname" ;;
140
127
  B) printf " ${DIM}◌${RESET} %-6s %s\n" "$fid" "$fname" ;;
141
- I\|*) # in_progress: extract team and phase
142
- team=$(echo "$st" | cut -d'|' -f2)
128
+ I\|*) team=$(echo "$st" | cut -d'|' -f2)
143
129
  phase=$(echo "$st" | cut -d'|' -f3)
144
- printf " ${CYAN}◐${RESET} %-6s %-18s T%s:%s\n" "$fid" "$fname" "$team" "$phase" ;;
130
+ printf " ${CYAN}◐${RESET} %-6s %-14s T%s:%s\n" "$fid" "$fname" "$team" "$phase" ;;
145
131
  *) printf " ? %-6s %s\n" "$fid" "$fname" ;;
146
132
  esac
147
133
  done
148
-
149
- echo ""
150
- }
151
-
152
- render_user_prompts() {
153
- local log_file="$PROJECT_ROOT/.harness/progress.log"
154
- if [ ! -f "$log_file" ]; then return; fi
155
-
156
- # Extract user-prompt entries (newest first, max 5)
157
- local user_lines
158
- user_lines=$(grep 'user-prompt' "$log_file" 2>/dev/null | tail -r 2>/dev/null | head -5)
159
-
160
- if [ -z "$user_lines" ]; then return; fi
161
-
162
- echo -e " ${BOLD}Manual Prompts${RESET} ${DIM}(newest first)${RESET}"
163
-
164
- echo "$user_lines" | while IFS= read -r line; do
165
- local ts detail
166
- ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
167
- detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
168
-
169
- # Short timestamp (HH:MM or MM-DD HH:MM)
170
- local short_ts
171
- short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
172
-
173
- if [ ${#detail} -gt 55 ]; then detail="${detail:0:53}.."; fi
174
-
175
- echo -e " ${BOLD}★${RESET} ${DIM}${short_ts}${RESET} ${detail}"
176
- done
177
-
178
- echo ""
179
- }
180
-
181
- render_team_activity() {
182
- local log_file="$PROJECT_ROOT/.harness/progress.log"
183
- if [ ! -f "$log_file" ]; then return; fi
184
-
185
- local max_lines=8
186
-
187
- echo -e " ${BOLD}Activity${RESET} ${DIM}(newest first)${RESET}"
188
-
189
- # All non-user-prompt entries, newest first
190
- grep -v '^#' "$log_file" 2>/dev/null | grep -v '^$' | grep -v 'user-prompt' | \
191
- tail -r 2>/dev/null | head -"$max_lines" | \
192
- while IFS= read -r line; do
193
- local ts agent action detail
194
- ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
195
- agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
196
- action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
197
- detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
198
-
199
- local short_ts icon color
200
- short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
201
-
202
- case "$agent" in
203
- dispatcher*) icon="▸"; color="$MAGENTA" ;;
204
- team-*) icon="⚡"; color="$CYAN" ;;
205
- manual) icon="★"; color="$BOLD" ;;
206
- planner*) icon="□"; color="$YELLOW" ;;
207
- gen*) icon="▶"; color="$GREEN" ;;
208
- eval*) icon="✦"; color="$RED" ;;
209
- system) icon="⚙"; color="$DIM" ;;
210
- *) icon="·"; color="$DIM" ;;
211
- esac
212
-
213
- if [ ${#detail} -gt 45 ]; then detail="${detail:0:43}.."; fi
214
-
215
- echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
216
- done
217
-
218
- echo ""
219
134
  }
220
135
 
221
136
  render_all() {
222
137
  render_header
223
138
  render_queue_summary
224
139
  render_teams
225
- render_user_prompts
226
- render_team_activity
227
- render_feature_list
228
- echo -e " ${DIM}Refreshing every 3s${RESET}"
140
+ echo ""
141
+ render_features
229
142
  }
230
143
 
231
144
  # ── Main loop ──
232
145
  tput civis 2>/dev/null
233
146
  trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
234
-
235
147
  clear
236
148
 
237
149
  while true; do
@@ -0,0 +1,106 @@
1
+ #!/bin/bash
2
+ # harness-prompts-v4.sh — v4 Dashboard 하단: Manual Prompts + Activity
3
+ # progress.log에서 user-prompt와 team 활동을 newest-first로 표시.
4
+ # 스크롤 가능 영역 — 프롬프트가 많아져도 상단 Progress를 가리지 않음.
5
+
6
+ set -uo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+
10
+ PROJECT_ROOT="${1:-}"
11
+ if [ -z "$PROJECT_ROOT" ]; then
12
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
13
+ PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[prompts] .harness/ not found."; exit 1; }
14
+ fi
15
+
16
+ LOG_FILE="$PROJECT_ROOT/.harness/progress.log"
17
+
18
+ BOLD="\033[1m"
19
+ DIM="\033[2m"
20
+ GREEN="\033[32m"
21
+ YELLOW="\033[33m"
22
+ RED="\033[31m"
23
+ CYAN="\033[36m"
24
+ MAGENTA="\033[35m"
25
+ RESET="\033[0m"
26
+
27
+ LAST_LINE_COUNT=0
28
+
29
+ render_prompts() {
30
+ if [ ! -f "$LOG_FILE" ]; then
31
+ echo -e " ${DIM}(progress.log not found)${RESET}"
32
+ return
33
+ fi
34
+
35
+ local term_h
36
+ term_h=$(tput lines 2>/dev/null || echo 20)
37
+ local max_lines=$((term_h - 3)) # Leave room for header
38
+ if [ "$max_lines" -lt 5 ]; then max_lines=5; fi
39
+
40
+ echo -e "${BOLD}Prompts & Activity${RESET} ${DIM}(newest first)${RESET}"
41
+ echo ""
42
+
43
+ # Read all entries, reverse, limit to terminal height
44
+ grep -v '^#' "$LOG_FILE" 2>/dev/null | grep -v '^$' | \
45
+ tail -r 2>/dev/null | head -"$max_lines" | \
46
+ while IFS= read -r line; do
47
+ local ts agent action detail
48
+ ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
49
+ agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
50
+ action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
51
+ detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
52
+
53
+ local short_ts icon color
54
+ short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
55
+
56
+ case "$agent" in
57
+ user-prompt)
58
+ icon="★"; color="$BOLD"
59
+ # User prompts get full width, highlighted
60
+ if [ ${#detail} -gt 60 ]; then detail="${detail:0:58}.."; fi
61
+ echo -e " ${color}${icon} ${short_ts}${RESET} ${detail}"
62
+ ;;
63
+ dispatcher*)
64
+ icon="▸"; color="$MAGENTA"
65
+ if [ ${#detail} -gt 50 ]; then detail="${detail:0:48}.."; fi
66
+ echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
67
+ ;;
68
+ team-*)
69
+ icon="⚡"; color="$CYAN"
70
+ if [ ${#detail} -gt 50 ]; then detail="${detail:0:48}.."; fi
71
+ # Color by action type
72
+ case "$action" in
73
+ *pass*) color="$GREEN"; icon="✓" ;;
74
+ *fail*) color="$RED"; icon="✗" ;;
75
+ *eval*) color="$MAGENTA"; icon="✦" ;;
76
+ *gen*) color="$CYAN"; icon="▶" ;;
77
+ *merge*) color="$GREEN"; icon="⊕" ;;
78
+ esac
79
+ echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
80
+ ;;
81
+ manual)
82
+ icon="★"; color="$YELLOW"
83
+ if [ ${#detail} -gt 50 ]; then detail="${detail:0:48}.."; fi
84
+ echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${detail}"
85
+ ;;
86
+ *)
87
+ icon="·"; color="$DIM"
88
+ if [ ${#detail} -gt 50 ]; then detail="${detail:0:48}.."; fi
89
+ echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
90
+ ;;
91
+ esac
92
+ done
93
+ }
94
+
95
+ # ── Main loop ──
96
+ tput civis 2>/dev/null
97
+ trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
98
+ clear
99
+
100
+ while true; do
101
+ buf=$(render_prompts 2>&1)
102
+ tput cup 0 0 2>/dev/null
103
+ echo "$buf"
104
+ tput ed 2>/dev/null
105
+ sleep 3
106
+ done
@@ -1,21 +1,13 @@
1
1
  #!/bin/bash
2
- # harness-studio-v4.sh — Harness Studio v4: 3-Column Layout
2
+ # harness-studio-v4.sh — Harness Studio v4: 3-Column + Dashboard Split
3
3
  #
4
4
  # ┌──────────────┬──────────────┬──────────────┐
5
- # │ │ │ Team 1 │
6
- # │ │ ├──────────────┤
7
- # │ Main Dashboard │ Team 2 │
8
- # │ (Claude) │ (read-only) ├──────────────┤
9
- # │ │ │ Team 3 │
5
+ # │ │ Progress │ Team 1 │
6
+ # │ │ (fixed) ├──────────────┤
7
+ # │ Main ├──────────────┤ Team 2 │
8
+ # │ (claude) │ Prompts ├──────────────┤
9
+ # │ │ (scroll) │ Team 3 │
10
10
  # └──────────────┴──────────────┴──────────────┘
11
- #
12
- # Main: 사용자가 직접 Claude Code를 실행하는 대화형 세션
13
- # Dashboard: feature-queue + team status 자동 갱신 (입력 불가)
14
- # Team 1~3: claude -p headless worker (입력 불가, 로그만 표시)
15
- #
16
- # Usage:
17
- # bash scripts/harness-studio-v4.sh [project-root]
18
- # bash scripts/harness-studio-v4.sh --kill
19
11
 
20
12
  set -euo pipefail
21
13
 
@@ -54,7 +46,7 @@ echo "Session: $SESSION_NAME"
54
46
 
55
47
  tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
56
48
 
57
- # ── Initialize or recover queue ──
49
+ # ── Init/recover queue ──
58
50
  QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
59
51
  if [ ! -f "$QUEUE" ]; then
60
52
  echo "Initializing feature queue..."
@@ -65,47 +57,78 @@ else
65
57
  fi
66
58
 
67
59
  # ══════════════════════════════════════════
68
- # 3-Column Layout (Main | Dashboard | Teams)
60
+ # Layout: 3 columns first, then split middle column
61
+ #
62
+ # Step 1: [Main]
63
+ # Step 2: [Main | Right] — h split 66%
64
+ # Step 3: [Main | Mid | Right] — h split Mid from Right at 50%
65
+ # Step 4: [Main | Progress / Prompts | Right] — v split Mid
66
+ # Step 5: [Main | Progress / Prompts | T1 / T2 / T3] — v split Right
69
67
  # ══════════════════════════════════════════
70
68
 
71
- # Column 1: Main (interactive shell — user runs claude here)
69
+ # 1. Main
72
70
  PANE_MAIN=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
73
71
  -P -F '#{pane_id}')
74
72
 
75
- # Column 2: Dashboard (split right from Main, 66% remaining → 33% each of 3 cols)
76
- PANE_DASH=$(tmux split-window -h -p 66 -t "$PANE_MAIN" -c "$PROJECT_ROOT" \
77
- -P -F '#{pane_id}' \
78
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
73
+ # 2. Right column (will become Team area)
74
+ PANE_RIGHT=$(tmux split-window -h -p 66 -t "$PANE_MAIN" -c "$PROJECT_ROOT" \
75
+ -P -F '#{pane_id}')
79
76
 
80
- # Column 3: Team 1 (split right from Dashboard, 50% of remaining = 33% total)
81
- PANE_T1=$(tmux split-window -h -p 50 -t "$PANE_DASH" -c "$PROJECT_ROOT" \
77
+ # 3. Middle column (split from Right, 50% = Mid gets left half, Right keeps right half)
78
+ PANE_MID=$(tmux split-window -h -p 50 -t "$PANE_RIGHT" -c "$PROJECT_ROOT" \
79
+ -P -F '#{pane_id}')
80
+ # Now: PANE_MID is the NEW pane (right side of split), PANE_RIGHT stays left
81
+ # Wait — tmux split-window -h on PANE_RIGHT creates new pane to the RIGHT of PANE_RIGHT
82
+ # So PANE_MID ends up on the right, and PANE_RIGHT is in the middle
83
+ # We need to swap: split PANE_MAIN's right, then the rightmost becomes teams
84
+
85
+ # Actually let me reconsider. After step 2:
86
+ # [Main 33%] [Right 66%]
87
+ # After step 3 (split Right horizontally at 50%):
88
+ # [Main 33%] [Right_left 33%] [Right_right 33%]
89
+ # Right_left = PANE_RIGHT (original), Right_right = PANE_MID (new)
90
+ # We want: Right_left = Dashboard area, Right_right = Team area
91
+ # So PANE_RIGHT becomes Dashboard, PANE_MID becomes Teams. But pane IDs...
92
+ # Actually: split-window creates the NEW pane. -h splits horizontally.
93
+ # The new pane goes to the right. So:
94
+ # PANE_RIGHT (original) = middle column (Dashboard)
95
+ # PANE_MID (new) = right column (Teams)
96
+ # Perfect!
97
+
98
+ # 4. Split middle column (PANE_RIGHT = Dashboard area) into Progress + Prompts
99
+ # Kill the shell in PANE_RIGHT first, replace with Progress
100
+ tmux send-keys -t "$PANE_RIGHT" "exec bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'" Enter
101
+
102
+ PANE_PROMPTS=$(tmux split-window -v -p 40 -t "$PANE_RIGHT" -c "$PROJECT_ROOT" \
82
103
  -P -F '#{pane_id}' \
83
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-team-worker.sh\" 1 \"${PROJECT_ROOT}\"'")
104
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompts-v4.sh\" \"${PROJECT_ROOT}\"'")
105
+
106
+ # 5. Split right column (PANE_MID = Team area) into Team 1/2/3
107
+ tmux send-keys -t "$PANE_MID" "exec bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-team-worker.sh\" 1 \"${PROJECT_ROOT}\"'" Enter
84
108
 
85
- # Team 2 (split below Team 1)
86
- PANE_T2=$(tmux split-window -v -p 66 -t "$PANE_T1" -c "$PROJECT_ROOT" \
109
+ PANE_T2=$(tmux split-window -v -p 66 -t "$PANE_MID" -c "$PROJECT_ROOT" \
87
110
  -P -F '#{pane_id}' \
88
111
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-team-worker.sh\" 2 \"${PROJECT_ROOT}\"'")
89
112
 
90
- # Team 3 (split below Team 2)
91
113
  PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" \
92
114
  -P -F '#{pane_id}' \
93
115
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-team-worker.sh\" 3 \"${PROJECT_ROOT}\"'")
94
116
 
95
- # ── Launch Claude in Main pane ──
117
+ # ── Launch Claude in Main ──
96
118
  tmux send-keys -t "$PANE_MAIN" "unset npm_config_prefix 2>/dev/null; clear && claude --dangerously-skip-permissions" Enter
97
119
 
98
120
  # ── Pane titles ──
99
- tmux select-pane -t "$PANE_MAIN" -T "Main"
100
- tmux select-pane -t "$PANE_DASH" -T "Dashboard"
101
- tmux select-pane -t "$PANE_T1" -T "Team 1"
102
- tmux select-pane -t "$PANE_T2" -T "Team 2"
103
- tmux select-pane -t "$PANE_T3" -T "Team 3"
121
+ tmux select-pane -t "$PANE_MAIN" -T "Main"
122
+ tmux select-pane -t "$PANE_RIGHT" -T "Progress"
123
+ tmux select-pane -t "$PANE_PROMPTS" -T "Prompts"
124
+ tmux select-pane -t "$PANE_MID" -T "Team 1"
125
+ tmux select-pane -t "$PANE_T2" -T "Team 2"
126
+ tmux select-pane -t "$PANE_T3" -T "Team 3"
104
127
 
105
128
  tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
106
129
  tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
107
130
 
108
- # ── Focus Main pane ──
131
+ # ── Focus Main ──
109
132
  tmux select-pane -t "$PANE_MAIN"
110
133
 
111
134
  # ── Attach ──
@@ -114,9 +137,10 @@ if [ -n "${TMUX:-}" ]; then
114
137
  else
115
138
  echo ""
116
139
  echo "Launching Harness Studio v4..."
117
- echo " Main (left) : Interactive — run 'claude' here"
118
- echo " Dashboard (mid) : Feature Queue + Team status (auto-refresh)"
119
- echo " Team 1-3 (right) : Parallel workers (headless, log only)"
140
+ echo " Main (left) : Claude interactive"
141
+ echo " Progress (mid) : Queue + Teams + Features"
142
+ echo " Prompts (mid↓) : Manual prompts + activity"
143
+ echo " Team 1-3 (right) : Parallel workers"
120
144
  echo ""
121
145
  tmux attach -t "$SESSION_NAME"
122
146
  fi