@walwal-harness/cli 5.0.8 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/harness-team.md +171 -178
- package/package.json +1 -1
- package/scripts/harness-dashboard.sh +28 -14
- package/scripts/harness-monitor.sh +8 -2
- package/scripts/harness-prompt-history.sh +12 -22
- package/skills/planner/SKILL.md +49 -1
package/commands/harness-team.md
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
# /harness-team — Team Mode 시작/재개
|
|
2
2
|
|
|
3
|
-
Planner가 완료한 feature-list.json의 피처들을 3개 팀이 병렬로 Gen→Eval 사이클을 수행합니다.
|
|
3
|
+
Planner가 완료한 feature-list.json의 피처들을 최대 3개 팀이 병렬로 Gen→Eval 사이클을 수행합니다.
|
|
4
|
+
|
|
5
|
+
## 프로세스 설계 원칙
|
|
6
|
+
|
|
7
|
+
1. **Worker = 1 Feature Only**: 각 Worker는 단일 피처만 처리하고 반환. 자동 다음 피처 획득하지 않음.
|
|
8
|
+
2. **Lead = Orchestration Loop**: Lead가 Worker 완료 알림을 받고, merge → unblock 확인 → 새 Worker 생성을 반복.
|
|
9
|
+
3. **Background Agent**: Worker를 `run_in_background: true`로 생성하여 Lead가 개별 완료에 즉시 반응.
|
|
10
|
+
4. **Merge 후 재투입**: Worker PASS → Lead가 worktree merge → queue pass (unblock) → 새 Worker 생성.
|
|
4
11
|
|
|
5
12
|
## 실행 절차
|
|
6
13
|
|
|
7
14
|
### Step 0: 선행 조건 확인
|
|
8
15
|
|
|
9
16
|
```bash
|
|
10
|
-
# Planner 아티팩트 확인
|
|
11
17
|
[ -f .harness/actions/feature-list.json ] && [ -f .harness/actions/api-contract.json ] && echo "READY" || echo "NOT_READY"
|
|
12
18
|
```
|
|
13
19
|
|
|
14
|
-
- **NOT_READY** → "Planner가 먼저 완료되어야 합니다.
|
|
20
|
+
- **NOT_READY** → "Planner가 먼저 완료되어야 합니다." 안내 후 중단.
|
|
15
21
|
- **READY** → Step 1로 진행.
|
|
16
22
|
|
|
17
23
|
### Step 1: 모드 전환 + Queue 초기화
|
|
@@ -37,44 +43,110 @@ bash scripts/harness-queue-manager.sh status .
|
|
|
37
43
|
bash scripts/harness-tmux.sh --team
|
|
38
44
|
```
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
- **`Layout ready`** → tmux 레이아웃 구축 완료. Step 3로.
|
|
42
|
-
- **`OPENED_TERMINAL=true`** → 새 Terminal.app 창에 Studio 레이아웃이 자동 구축됨. Step 3로.
|
|
43
|
-
- **`already set up`** → 이미 구축됨. Step 3로.
|
|
44
|
-
|
|
45
|
-
### Step 3: Feature 할당 (dequeue)
|
|
46
|
+
### Step 3: 초기 Worker 생성
|
|
46
47
|
|
|
47
|
-
Queue
|
|
48
|
-
ready feature 수와 설정된 concurrency(기본 3) 중 작은 값만큼 팀을 생성합니다.
|
|
49
|
-
|
|
50
|
-
**각 팀마다** dequeue 명령으로 feature를 원자적으로 할당합니다:
|
|
48
|
+
Queue에서 ready 피처를 최대 3개 dequeue하고, **각각 background Agent로 생성**합니다.
|
|
51
49
|
|
|
52
50
|
```bash
|
|
53
|
-
|
|
51
|
+
# 각 팀마다 dequeue
|
|
52
|
+
bash scripts/harness-queue-manager.sh dequeue 1 .
|
|
53
|
+
bash scripts/harness-queue-manager.sh dequeue 2 .
|
|
54
|
+
bash scripts/harness-queue-manager.sh dequeue 3 .
|
|
54
55
|
```
|
|
55
56
|
|
|
56
|
-
dequeue
|
|
57
|
-
|
|
58
|
-
### Step 4: Agent 도구로 팀 생성 (Gen↔Eval 분리 사이클)
|
|
59
|
-
|
|
60
|
-
dequeue로 할당받은 feature마다 **Agent 도구**를 호출합니다.
|
|
61
|
-
**반드시 `isolation: "worktree"`를 사용**하여 각 팀이 독립된 코드 복사본에서 작업합니다.
|
|
62
|
-
|
|
63
|
-
**독립적인 팀들은 단일 메시지에서 병렬로 호출**하세요 (한 번의 응답에 여러 Agent 도구 호출).
|
|
57
|
+
dequeue 성공한 피처마다 **background Agent**를 생성합니다:
|
|
64
58
|
|
|
65
59
|
```
|
|
66
60
|
Agent({
|
|
67
61
|
description: "Team-{N}: {FEATURE_ID}",
|
|
68
62
|
isolation: "worktree",
|
|
63
|
+
run_in_background: true,
|
|
69
64
|
prompt: "<아래 Team Worker 프롬프트>"
|
|
70
65
|
})
|
|
71
66
|
```
|
|
72
67
|
|
|
73
|
-
|
|
68
|
+
**중요: `run_in_background: true`로 생성**하면 Lead가 블록되지 않고, 각 Worker 완료 시 알림을 받습니다.
|
|
69
|
+
|
|
70
|
+
초기 생성 후 **Step 4 (Orchestration Loop)**로 진입합니다.
|
|
71
|
+
|
|
72
|
+
### Step 4: Orchestration Loop (Lead 핵심 루프)
|
|
73
|
+
|
|
74
|
+
**이 루프가 Team Mode의 핵심입니다. 모든 Sprint가 완료될 때까지 반복합니다.**
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
ORCHESTRATION LOOP:
|
|
78
|
+
|
|
79
|
+
Background Agent 완료 알림을 받으면:
|
|
80
|
+
|
|
81
|
+
1. Worker 결과 분석:
|
|
82
|
+
- PASS인 경우 → Step 4a (Merge + Unblock)
|
|
83
|
+
- FAIL (재시도 가능)인 경우 → Step 4b (Retry)
|
|
84
|
+
- ESCALATED인 경우 → 사용자에게 알림, 해당 팀 유휴
|
|
85
|
+
|
|
86
|
+
2. Queue 상태 확인:
|
|
87
|
+
bash scripts/harness-queue-manager.sh status .
|
|
88
|
+
|
|
89
|
+
3. ready > 0 이면:
|
|
90
|
+
→ 새 Worker를 background Agent로 생성 (Step 3과 동일)
|
|
91
|
+
→ LOOP 계속
|
|
92
|
+
|
|
93
|
+
4. ready = 0 AND in_progress > 0 이면:
|
|
94
|
+
→ 다른 Worker 완료를 대기
|
|
95
|
+
→ LOOP 계속
|
|
96
|
+
|
|
97
|
+
5. ready = 0 AND in_progress = 0 이면:
|
|
98
|
+
→ Sprint 전환 시도:
|
|
99
|
+
bash scripts/harness-queue-manager.sh next-sprint .
|
|
100
|
+
- "Advancing" → Step 3으로 (새 Sprint 피처 dequeue + Worker 생성)
|
|
101
|
+
- "ALL SPRINTS COMPLETE" → 최종 보고. LOOP 종료.
|
|
102
|
+
- "Cannot advance" → 실패 피처 사용자 개입 요청. LOOP 종료.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Step 4a: Merge + Unblock (PASS 처리)
|
|
106
|
+
|
|
107
|
+
Worker가 PASS로 반환되면:
|
|
108
|
+
|
|
109
|
+
1. **Worktree branch 확인**: Agent 반환 결과에서 worktree path와 branch 확인
|
|
110
|
+
2. **Main에 merge**:
|
|
111
|
+
```bash
|
|
112
|
+
git merge {BRANCH_NAME} --no-edit
|
|
113
|
+
```
|
|
114
|
+
- 충돌 시: 자동 해결 시도 → 실패 시 사용자 개입 요청
|
|
115
|
+
3. **Queue 업데이트** (unblock 포함):
|
|
116
|
+
```bash
|
|
117
|
+
bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .
|
|
118
|
+
```
|
|
119
|
+
→ 의존 피처가 자동으로 blocked → ready로 전이
|
|
120
|
+
4. **진행 로그**:
|
|
121
|
+
```bash
|
|
122
|
+
echo "$(date +'%Y-%m-%d %H:%M') | lead | pass | {FEATURE_ID} merged + unblocked deps" >> .harness/progress.log
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### Step 4b: Retry (FAIL 처리)
|
|
126
|
+
|
|
127
|
+
Worker가 FAIL (재시도 가능)로 반환되면:
|
|
128
|
+
|
|
129
|
+
1. 시도 횟수 확인 (최대 5회)
|
|
130
|
+
2. 5회 미만:
|
|
131
|
+
```bash
|
|
132
|
+
bash scripts/harness-queue-manager.sh requeue {FEATURE_ID} .
|
|
133
|
+
bash scripts/harness-queue-manager.sh dequeue {TEAM_NUMBER} .
|
|
134
|
+
```
|
|
135
|
+
→ 새 background Agent Worker 생성 (이전 Eval feedback 포함)
|
|
136
|
+
3. 5회 도달:
|
|
137
|
+
```bash
|
|
138
|
+
bash scripts/harness-queue-manager.sh fail {FEATURE_ID} .
|
|
139
|
+
echo "$(date +'%Y-%m-%d %H:%M') | lead | escalate | {FEATURE_ID} ESCALATED after 5 attempts" >> .harness/progress.log
|
|
140
|
+
```
|
|
141
|
+
→ 사용자 개입 요청
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Team Worker 프롬프트
|
|
74
146
|
|
|
75
147
|
```
|
|
76
|
-
당신은 Harness Team-{N} 워커입니다.
|
|
77
|
-
완료 후
|
|
148
|
+
당신은 Harness Team-{N} 워커입니다. **단일 Feature**에 대해 Gen→Eval 사이클을 수행합니다.
|
|
149
|
+
완료 후 결과를 반환합니다. 다음 Feature는 Lead가 할당합니다.
|
|
78
150
|
|
|
79
151
|
## 할당된 Feature
|
|
80
152
|
- Feature ID: {FEATURE_ID}
|
|
@@ -83,224 +155,145 @@ Agent({
|
|
|
83
155
|
|
|
84
156
|
## 실시간 로깅 (필수)
|
|
85
157
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- ACTION 토큰은 아래 표에서 선택 (Monitor가 아이콘/색을 매핑)
|
|
93
|
-
- DETAIL은 구체적으로 — 파일명, AC 번호, 에러 메시지 요약, 결정 사유
|
|
158
|
+
**로깅 설정:**
|
|
159
|
+
```bash
|
|
160
|
+
HARNESS_ROOT=$(git worktree list | head -1 | awk '{print $1}')
|
|
161
|
+
LOG="$HARNESS_ROOT/.harness/progress.log"
|
|
162
|
+
logev() { echo "$(date +'%Y-%m-%d %H:%M') | team-{N} | $1 | $2" >> "$LOG"; }
|
|
163
|
+
```
|
|
94
164
|
|
|
95
165
|
| ACTION | 사용 시점 | DETAIL 예시 |
|
|
96
166
|
|--------|-----------|-------------|
|
|
97
167
|
| `gen-start` | Gen Phase 시작 | `F-001 start — 6 AC` |
|
|
98
168
|
| `gen-read` | 소스/계약 읽기 | `read api-contract.json (POST /users)` |
|
|
99
169
|
| `gen-write` | 파일 생성/수정 | `write apps/service-user/src/user.controller.ts` |
|
|
100
|
-
| `gen-test` | 자체 게이트
|
|
170
|
+
| `gen-test` | 자체 게이트 | `tsc OK · eslint 0 · jest 12/12` |
|
|
101
171
|
| `gen-done` | Gen Phase 종료 | `F-001 done — 5 files, 142 LOC` |
|
|
102
|
-
| `eval-start` | Evaluator
|
|
103
|
-
| `eval-check` | AC
|
|
104
|
-
| `eval-done` | Eval 결과
|
|
105
|
-
| `result`
|
|
106
|
-
| `fail` | FAIL 확정
|
|
107
|
-
| `escalate` | 5회 초과 실패 | `F-001 ESCALATED — user intervention required` |
|
|
108
|
-
|
|
109
|
-
**progress.log 기록** (하네스 루트의 progress.log에 append):
|
|
110
|
-
```bash
|
|
111
|
-
echo "$(date +'%Y-%m-%d %H:%M') | team-{N} | {ACTION} | {DETAIL}" >> {HARNESS_ROOT}/.harness/progress.log
|
|
112
|
-
```
|
|
172
|
+
| `eval-start` | Evaluator 시작 | `F-001 spawning evaluator` |
|
|
173
|
+
| `eval-check` | AC 검증 | `AC-3 — verify POST /users returns 201` |
|
|
174
|
+
| `eval-done` | Eval 결과 | `verdict=PASS score=2.95` |
|
|
175
|
+
| `result` | PASS 확정 | `F-001 PASS` |
|
|
176
|
+
| `fail` | FAIL 확정 | `FAIL #1 — AC-2 missing` |
|
|
113
177
|
|
|
114
|
-
**queue phase
|
|
178
|
+
**queue phase 업데이트:**
|
|
115
179
|
```bash
|
|
116
|
-
bash
|
|
180
|
+
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" update_phase {FEATURE_ID} {PHASE} "$HARNESS_ROOT"
|
|
117
181
|
```
|
|
118
182
|
|
|
119
|
-
> {HARNESS_ROOT}는 worktree의 원본 프로젝트 경로입니다. `git worktree list` 첫 줄에서 확인 가능합니다.
|
|
120
|
-
> 워커 시작 시 먼저 실행: `HARNESS_ROOT=$(git worktree list | head -1 | awk '{print $1}')`
|
|
121
|
-
|
|
122
183
|
## Phase 1: Generator (코드 생성)
|
|
123
184
|
|
|
124
|
-
**시작 시 로깅:**
|
|
125
185
|
```bash
|
|
126
|
-
HARNESS_ROOT=$(git worktree list | head -1 | awk '{print $1}')
|
|
127
|
-
LOG="$HARNESS_ROOT/.harness/progress.log"
|
|
128
|
-
logev() { echo "$(date +'%Y-%m-%d %H:%M') | team-{N} | $1 | $2" >> "$LOG"; }
|
|
129
|
-
|
|
130
186
|
logev gen-start "{FEATURE_ID} start"
|
|
131
187
|
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" update_phase {FEATURE_ID} gen "$HARNESS_ROOT"
|
|
132
188
|
```
|
|
133
189
|
|
|
134
|
-
1. Feature 정보 확인
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
logev gen-read "api-contract.json → {관련 엔드포인트}"
|
|
138
|
-
```
|
|
139
|
-
- `jq '.features[] | select(.id == "{FEATURE_ID}")' .harness/actions/feature-list.json`
|
|
140
|
-
- `.harness/actions/api-contract.json`에서 관련 엔드포인트 확인
|
|
141
|
-
- AC(Acceptance Criteria) 목록을 정확히 파악
|
|
142
|
-
|
|
143
|
-
2. 코드 생성 — **파일 쓰기마다 로그**:
|
|
144
|
-
```bash
|
|
145
|
-
logev gen-write "apps/service-user/src/user.controller.ts"
|
|
146
|
-
logev gen-write "libs/shared-dto/src/user.dto.ts"
|
|
147
|
-
```
|
|
148
|
-
- AGENTS.md의 IA-MAP에 따라 올바른 디렉토리에 코드 작성
|
|
149
|
-
- AC의 모든 항목을 충족하도록 구현
|
|
150
|
-
|
|
151
|
-
3. Pre-eval 게이트 (자체) — **결과 로그**:
|
|
152
|
-
```bash
|
|
153
|
-
logev gen-test "tsc OK · eslint 0w 0e · jest 12/12"
|
|
154
|
-
```
|
|
155
|
-
- tsc (타입 체크) 실행
|
|
156
|
-
- eslint (린트) 실행
|
|
157
|
-
- 컴파일 에러가 있으면 직접 수정 (Eval에 넘기지 않음)
|
|
190
|
+
1. Feature 정보 확인 (feature-list.json, api-contract.json)
|
|
191
|
+
2. 코드 생성 (AGENTS.md IA-MAP 준수, AC 전체 충족)
|
|
192
|
+
3. Pre-eval 게이트 (tsc, eslint — 에러 있으면 직접 수정)
|
|
158
193
|
|
|
159
|
-
**Gen 완료 로깅:**
|
|
160
194
|
```bash
|
|
161
|
-
logev gen-done "{FEATURE_ID} done — {
|
|
195
|
+
logev gen-done "{FEATURE_ID} done — {파일수} files"
|
|
162
196
|
```
|
|
163
197
|
|
|
164
|
-
## Phase 2: Evaluator (독립 평가
|
|
198
|
+
## Phase 2: Evaluator (독립 평가)
|
|
165
199
|
|
|
166
|
-
**Eval 시작 로깅:**
|
|
167
200
|
```bash
|
|
168
201
|
logev eval-start "{FEATURE_ID} spawning evaluator"
|
|
169
202
|
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" update_phase {FEATURE_ID} eval "$HARNESS_ROOT"
|
|
170
203
|
```
|
|
171
204
|
|
|
172
|
-
|
|
173
|
-
이 Evaluator Agent는 당신(Generator)의 추론 과정을 모릅니다.
|
|
174
|
-
오직 코드와 AC만 보고 판단합니다.
|
|
205
|
+
**별도 Agent 생성** (Generator의 추론 과정을 모르는 독립 평가):
|
|
175
206
|
|
|
176
207
|
```
|
|
177
208
|
Agent({
|
|
178
209
|
description: "Eval: {FEATURE_ID}",
|
|
179
|
-
prompt: "
|
|
180
|
-
|
|
181
|
-
```
|
|
210
|
+
prompt: "당신은 독립 Evaluator입니다. Generator가 작성한 코드를 AC 기준으로 냉정하게 평가합니다.
|
|
211
|
+
Generator의 의도나 추론 과정은 알 수 없습니다. 오직 코드와 결과만 봅니다.
|
|
182
212
|
|
|
183
|
-
|
|
213
|
+
## 실시간 로깅 (필수)
|
|
214
|
+
|
|
215
|
+
평가 진행 상황을 실시간으로 기록합니다. **각 AC 검증마다 반드시 logev를 호출**하세요.
|
|
184
216
|
|
|
217
|
+
```bash
|
|
218
|
+
HARNESS_ROOT=$(git worktree list | head -1 | awk '{print $1}')
|
|
219
|
+
LOG=\"$HARNESS_ROOT/.harness/progress.log\"
|
|
220
|
+
logev() { echo \"$(date +'%Y-%m-%d %H:%M') | team-{N} | $1 | $2\" >> \"$LOG\"; }
|
|
185
221
|
```
|
|
186
|
-
|
|
187
|
-
|
|
222
|
+
|
|
223
|
+
**로깅 시점:**
|
|
224
|
+
1. 평가 시작 즉시: `logev eval-start \"{FEATURE_ID} evaluating — {AC수} ACs\"`
|
|
225
|
+
2. 각 AC 검증 후: `logev eval-check \"{FEATURE_ID} AC-{N}: [PASS/FAIL] {근거 요약}\"`
|
|
226
|
+
3. tsc/eslint 검증 후: `logev eval-check \"{FEATURE_ID} gate: tsc {OK/FAIL}, eslint {OK/FAIL}\"`
|
|
227
|
+
4. 최종 판정: `logev eval-done \"{FEATURE_ID} VERDICT={PASS/FAIL} SCORE={X.XX}/3.00\"`
|
|
188
228
|
|
|
189
229
|
## 평가 대상
|
|
190
230
|
- Feature ID: {FEATURE_ID}
|
|
191
|
-
- AC
|
|
231
|
+
- AC: jq '.features[] | select(.id == \"{FEATURE_ID}\").acceptance_criteria' .harness/actions/feature-list.json
|
|
192
232
|
|
|
193
233
|
## 평가 기준
|
|
194
|
-
1. AC 100% 충족
|
|
195
|
-
2. api-contract.json
|
|
196
|
-
3. tsc
|
|
197
|
-
4.
|
|
198
|
-
5.
|
|
234
|
+
1. AC 100% 충족 (부분 통과 = FAIL)
|
|
235
|
+
2. api-contract.json 일치
|
|
236
|
+
3. tsc/eslint 통과
|
|
237
|
+
4. OWASP Top 10 보안
|
|
238
|
+
5. Regression 여부
|
|
199
239
|
|
|
200
240
|
## 출력 형식
|
|
201
|
-
반드시 아래 형식으로 결과를 반환하세요:
|
|
202
|
-
|
|
203
241
|
VERDICT: PASS 또는 FAIL
|
|
204
242
|
SCORE: X.XX / 3.00
|
|
205
243
|
EVIDENCE:
|
|
206
244
|
- AC-1: [PASS/FAIL] 근거
|
|
207
|
-
- AC-2: [PASS/FAIL] 근거
|
|
208
245
|
- ...
|
|
209
|
-
FEEDBACK: (FAIL
|
|
246
|
+
FEEDBACK: (FAIL만) 구체적 수정 지시"
|
|
247
|
+
})
|
|
210
248
|
```
|
|
211
249
|
|
|
212
|
-
## Phase 3: 결과 처리
|
|
213
|
-
|
|
214
|
-
Evaluator Agent 결과를 확인합니다:
|
|
250
|
+
## Phase 3: 결과 처리 + 반환
|
|
215
251
|
|
|
216
|
-
### PASS
|
|
252
|
+
### PASS (SCORE >= 2.80):
|
|
217
253
|
```bash
|
|
218
|
-
logev result "{FEATURE_ID} PASS score={SCORE}
|
|
219
|
-
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" pass {FEATURE_ID} "$HARNESS_ROOT"
|
|
254
|
+
logev result "{FEATURE_ID} PASS score={SCORE}"
|
|
220
255
|
```
|
|
221
|
-
|
|
256
|
+
Lead에게 반환: `PASS | {FEATURE_ID} | score={SCORE} | files={변경파일목록}`
|
|
222
257
|
|
|
223
|
-
### FAIL
|
|
258
|
+
### FAIL (재시도 가능):
|
|
224
259
|
```bash
|
|
225
|
-
logev fail "{FEATURE_ID} FAIL #{ATTEMPT} — {
|
|
260
|
+
logev fail "{FEATURE_ID} FAIL #{ATTEMPT} — {사유}"
|
|
226
261
|
```
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
```bash
|
|
231
|
-
logev escalate "{FEATURE_ID} ESCALATED after 5 attempts — user intervention required"
|
|
232
|
-
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" fail {FEATURE_ID} "$HARNESS_ROOT"
|
|
233
|
-
```
|
|
234
|
-
실패 사유와 마지막 Eval 결과를 Lead에게 반환. **사용자 개입을 요청**합니다.
|
|
235
|
-
|
|
236
|
-
## Phase 4: 자동 업무 획득
|
|
262
|
+
시도 횟수가 5회 미만이면:
|
|
263
|
+
- Evaluator FEEDBACK으로 코드 수정 → Phase 1로 돌아감 (같은 Worker 내에서 재시도)
|
|
264
|
+
- 새 Evaluator Agent 생성 (이전 Eval 기억 없음)
|
|
237
265
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
```bash
|
|
242
|
-
NEW_FEATURE=$(bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" dequeue {N} "$HARNESS_ROOT")
|
|
243
|
-
logev gen-start "$NEW_FEATURE auto-acquired"
|
|
244
|
-
```
|
|
245
|
-
3. `ready=0`이면 → next-sprint 시도:
|
|
246
|
-
```bash
|
|
247
|
-
NEXT=$(bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" next-sprint "$HARNESS_ROOT" 2>&1)
|
|
248
|
-
if echo "$NEXT" | grep -q "Advancing"; then
|
|
249
|
-
NEW_FEATURE=$(bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" dequeue {N} "$HARNESS_ROOT")
|
|
250
|
-
logev gen-start "$NEW_FEATURE auto-acquired (next sprint)"
|
|
251
|
-
else
|
|
252
|
-
logev result "Team-{N} 완료 — 모든 Sprint 처리됨"
|
|
253
|
-
fi
|
|
254
|
-
```
|
|
266
|
+
5회 모두 FAIL:
|
|
267
|
+
```bash
|
|
268
|
+
logev fail "{FEATURE_ID} FINAL FAIL after 5 attempts"
|
|
255
269
|
```
|
|
256
|
-
|
|
257
|
-
### Step 5: 결과 수집 + 재투입 루프 (반드시 반복)
|
|
258
|
-
|
|
259
|
-
**중요: 이 Step은 모든 Agent가 반환될 때마다 반복 실행해야 합니다.**
|
|
260
|
-
피처 PASS 시 blocked 피처가 unblock되므로, 유휴 팀에 즉시 새 피처를 할당해야 합니다.
|
|
261
|
-
|
|
270
|
+
Lead에게 반환: `ESCALATED | {FEATURE_ID} | attempts=5 | last_feedback={마지막_피드백}`
|
|
262
271
|
```
|
|
263
|
-
LOOP:
|
|
264
|
-
1. 현재 라운드의 모든 Team Agent 결과 수집 (PASS/FAIL 확인)
|
|
265
|
-
2. Queue 상태 재확인:
|
|
266
|
-
bash scripts/harness-queue-manager.sh status .
|
|
267
272
|
|
|
268
|
-
|
|
269
|
-
→ Step 3으로 돌아감 (새로 unblock된 피처를 dequeue하여 팀 생성)
|
|
270
|
-
→ 최대 3팀까지 병렬 Agent 재생성
|
|
271
|
-
→ 생성 후 다시 이 LOOP의 1번으로 돌아와 결과 대기
|
|
273
|
+
---
|
|
272
274
|
|
|
273
|
-
|
|
274
|
-
→ 아직 작업 중인 팀이 있음. 해당 Agent 결과를 기다림
|
|
275
|
-
→ 결과 수신 후 다시 이 LOOP의 1번으로 돌아감
|
|
275
|
+
## 핵심 원칙
|
|
276
276
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
- "Advancing to Sprint N" → Step 3으로 돌아가 팀 생성
|
|
282
|
-
- "ALL SPRINTS COMPLETE" → 전체 프로젝트 완료 보고. LOOP 종료.
|
|
283
|
-
- "Cannot advance: N failed" → 실패 피처 사용자 개입 요청. LOOP 종료.
|
|
284
|
-
```
|
|
277
|
+
### Worker = 1 Feature Only
|
|
278
|
+
- Worker는 할당된 단일 피처만 처리하고 반환
|
|
279
|
+
- 다음 피처 dequeue, next-sprint 시도는 **Lead만** 수행
|
|
280
|
+
- Worktree는 해당 피처 전용 — 다른 피처 작업 금지
|
|
285
281
|
|
|
286
|
-
|
|
282
|
+
### Lead = Merge + Orchestrate
|
|
283
|
+
- Worker PASS 시 Lead가 branch merge → queue pass → unblock
|
|
284
|
+
- 새로 ready된 피처에 즉시 Worker 재생성
|
|
285
|
+
- Sprint 전환도 Lead가 판단
|
|
287
286
|
|
|
288
|
-
|
|
287
|
+
### Background Agent로 비차단 실행
|
|
288
|
+
- `run_in_background: true`로 Worker 생성
|
|
289
|
+
- Lead가 각 Worker 완료에 즉시 반응
|
|
290
|
+
- 3팀이 서로 다른 속도로 작업해도 유휴 팀 즉시 재활용
|
|
289
291
|
|
|
290
292
|
### 자기 의식 편향 차단
|
|
291
|
-
- Generator
|
|
292
|
-
-
|
|
293
|
-
- FAIL 후 재시도 시에도 새 Evaluator를 생성 (이전 Eval 기억 없음)
|
|
294
|
-
|
|
295
|
-
### 중복 방지
|
|
296
|
-
- dequeue는 원자적 (lock 사용) — 같은 feature를 두 번 할당 불가
|
|
297
|
-
- ready가 0이면 팀을 생성하지 않음
|
|
298
|
-
|
|
299
|
-
### 격리
|
|
300
|
-
- 각 팀은 `isolation: "worktree"`로 독립 코드 복사본에서 작업
|
|
301
|
-
- 팀 간 코드 충돌 없음
|
|
293
|
+
- Evaluator는 항상 새 Agent (Generator의 추론 과정 모름)
|
|
294
|
+
- FAIL 후 재시도 시에도 새 Evaluator 생성
|
|
302
295
|
|
|
303
296
|
### 에스컬레이션 (5회 초과)
|
|
304
297
|
- 5회 연속 FAIL 시 사용자 개입 요청
|
|
305
|
-
-
|
|
306
|
-
-
|
|
298
|
+
- 해당 피처는 failed 상태로 남음
|
|
299
|
+
- 다른 피처는 계속 진행 (의존하지 않는 경우)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer. Supports React, Next.js, and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -40,7 +40,7 @@ strip_ansi() {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
get_term_width() {
|
|
43
|
-
tput cols 2>/dev/null || echo 80
|
|
43
|
+
echo "${_COLS:-$(tput cols 2>/dev/null || echo 80)}"
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
get_mode() {
|
|
@@ -150,14 +150,28 @@ render_solo_prompt_history() {
|
|
|
150
150
|
local short_ts icon color
|
|
151
151
|
short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
153
|
+
# action 기반으로 먼저 분류 (team 모드에서 agent=team-N이므로)
|
|
154
|
+
case "$action" in
|
|
155
|
+
eval-start|eval-check|eval-done|eval)
|
|
156
|
+
icon="✦" ; color="$MAGENTA" ;;
|
|
157
|
+
gen-start|gen-read|gen-write|gen-test|gen-done|gen)
|
|
158
|
+
icon="▶" ; color="$GREEN" ;;
|
|
159
|
+
result|pass)
|
|
160
|
+
icon="✓" ; color="$GREEN" ;;
|
|
161
|
+
fail)
|
|
162
|
+
icon="✗" ; color="$RED" ;;
|
|
163
|
+
*)
|
|
164
|
+
# fallback: agent 기반
|
|
165
|
+
case "$agent" in
|
|
166
|
+
dispatcher*) icon="▸" ; color="$MAGENTA" ;;
|
|
167
|
+
planner*) icon="□" ; color="$YELLOW" ;;
|
|
168
|
+
generator*) icon="▶" ; color="$GREEN" ;;
|
|
169
|
+
eval*) icon="✦" ; color="$MAGENTA" ;;
|
|
170
|
+
team-*) icon="◆" ; color="$CYAN" ;;
|
|
171
|
+
user*) icon="★" ; color="$BOLD" ;;
|
|
172
|
+
*) icon="·" ; color="$DIM" ;;
|
|
173
|
+
esac
|
|
174
|
+
;;
|
|
161
175
|
esac
|
|
162
176
|
|
|
163
177
|
if [ ${#detail} -gt 40 ]; then detail="${detail:0:38}.."; fi
|
|
@@ -331,9 +345,10 @@ render_archive_prompts() {
|
|
|
331
345
|
total_prompts=$(echo "$all_prompts" | wc -l | tr -d ' ')
|
|
332
346
|
|
|
333
347
|
# 마지막 프롬프트를 제외한 모든 프롬프트 = archived (처리 완료)
|
|
348
|
+
# newest first (tac), 전체 출력 (제한 없음)
|
|
334
349
|
local archived
|
|
335
350
|
if [ "$total_prompts" -le 1 ]; then return; fi
|
|
336
|
-
archived=$(echo "$all_prompts" | head -n $((total_prompts - 1)) |
|
|
351
|
+
archived=$(echo "$all_prompts" | head -n $((total_prompts - 1)) | tac)
|
|
337
352
|
|
|
338
353
|
echo ""
|
|
339
354
|
echo -e "${BOLD}Archive Prompt${RESET} ${DIM}(처리 완료)${RESET}"
|
|
@@ -350,10 +365,6 @@ render_archive_prompts() {
|
|
|
350
365
|
|
|
351
366
|
echo -e " ${GREEN}✓${RESET} ${DIM}${short_ts}${RESET} ${detail}"
|
|
352
367
|
done
|
|
353
|
-
|
|
354
|
-
if [ "$total_prompts" -gt 9 ]; then
|
|
355
|
-
echo -e " ${DIM}… +$((total_prompts - 9)) more${RESET}"
|
|
356
|
-
fi
|
|
357
368
|
}
|
|
358
369
|
|
|
359
370
|
# ══════════════════════════════════════════
|
|
@@ -396,6 +407,9 @@ trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
|
|
|
396
407
|
clear
|
|
397
408
|
|
|
398
409
|
while true; do
|
|
410
|
+
_ROWS=$(tput lines 2>/dev/null || echo 30)
|
|
411
|
+
_COLS=$(tput cols 2>/dev/null || echo 80)
|
|
412
|
+
export _ROWS _COLS
|
|
399
413
|
buf=$(render_dashboard 2>&1)
|
|
400
414
|
tput cup 0 0 2>/dev/null
|
|
401
415
|
echo "$buf"
|
|
@@ -159,8 +159,14 @@ render_team_section() {
|
|
|
159
159
|
case "$action" in
|
|
160
160
|
gen|gen-start|gen-read|gen-write|gen-test|gen-done)
|
|
161
161
|
a_icon="▶"; a_color="$GREEN"; a_label="Gen" ;;
|
|
162
|
-
eval
|
|
163
|
-
a_icon="✦"; a_color="$
|
|
162
|
+
eval-start)
|
|
163
|
+
a_icon="✦"; a_color="$MAGENTA"; a_label="Eval" ;;
|
|
164
|
+
eval-check)
|
|
165
|
+
a_icon="·"; a_color="$MAGENTA"; a_label="Eval" ;;
|
|
166
|
+
eval-done)
|
|
167
|
+
a_icon="✦"; a_color="${BOLD}${MAGENTA}"; a_label="Eval" ;;
|
|
168
|
+
eval)
|
|
169
|
+
a_icon="✦"; a_color="$MAGENTA"; a_label="Eval" ;;
|
|
164
170
|
result|pass)
|
|
165
171
|
a_icon="✓"; a_color="$GREEN"; a_label="Result" ;;
|
|
166
172
|
fail)
|
|
@@ -96,34 +96,30 @@ render_processing_status() {
|
|
|
96
96
|
echo ""
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
# 사용자 프롬프트만 필터하여 표시
|
|
99
|
+
# 사용자 프롬프트만 필터하여 표시 (newest first, 전체 출력)
|
|
100
100
|
render_user_prompts() {
|
|
101
101
|
if [ ! -f "$PROGRESS_LOG" ]; then
|
|
102
102
|
echo -e " ${DIM}(프롬프트 기록 없음)${RESET}"
|
|
103
103
|
return
|
|
104
104
|
fi
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
term_height=$(tput lines 2>/dev/null || echo 30)
|
|
108
|
-
local max_lines=$((term_height - 10))
|
|
109
|
-
if [ "$max_lines" -lt 5 ]; then max_lines=5; fi
|
|
110
|
-
|
|
111
|
-
# user-prompt 행만 필터
|
|
106
|
+
# user-prompt 행만 필터 → tac으로 newest first
|
|
112
107
|
local prompts
|
|
113
|
-
prompts=$(grep '| user-prompt |' "$PROGRESS_LOG" 2>/dev/null |
|
|
108
|
+
prompts=$(grep '| user-prompt |' "$PROGRESS_LOG" 2>/dev/null | tac)
|
|
114
109
|
|
|
115
110
|
if [ -z "$prompts" ]; then
|
|
116
111
|
echo -e " ${DIM}(사용자 프롬프트 없음)${RESET}"
|
|
117
112
|
return
|
|
118
113
|
fi
|
|
119
114
|
|
|
120
|
-
local line_num=0
|
|
121
115
|
local total_prompts
|
|
122
|
-
total_prompts=$(
|
|
116
|
+
total_prompts=$(echo "$prompts" | wc -l | tr -d ' ')
|
|
123
117
|
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
# 터미널 폭: main loop에서 측정한 _COLS 사용 (subshell 내 tput 불가 대비)
|
|
119
|
+
local max_width=$(( ${_COLS:-80} - 12 ))
|
|
120
|
+
if [ "$max_width" -lt 20 ]; then max_width=20; fi
|
|
126
121
|
|
|
122
|
+
echo "$prompts" | while IFS= read -r line; do
|
|
127
123
|
local ts detail
|
|
128
124
|
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
129
125
|
detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
|
|
@@ -131,20 +127,10 @@ render_user_prompts() {
|
|
|
131
127
|
local short_ts
|
|
132
128
|
short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}' | tail -1 || echo "$ts")
|
|
133
129
|
|
|
134
|
-
# 프롬프트 내용 truncate
|
|
135
|
-
local max_width
|
|
136
|
-
max_width=$(( $(tput cols 2>/dev/null || echo 40) - 12 ))
|
|
137
|
-
if [ "$max_width" -lt 20 ]; then max_width=20; fi
|
|
138
130
|
if [ ${#detail} -gt "$max_width" ]; then
|
|
139
131
|
detail="${detail:0:$((max_width - 2))}.."
|
|
140
132
|
fi
|
|
141
133
|
|
|
142
|
-
# 처리 상태 찾기: 이 프롬프트 이후의 첫 번째 에이전트 액션
|
|
143
|
-
local processed_by=""
|
|
144
|
-
local prompt_ts_epoch
|
|
145
|
-
prompt_ts_epoch=$(echo "$ts" | sed 's/[^0-9:]//g')
|
|
146
|
-
|
|
147
|
-
# 간단히: 마지막 프롬프트인지 확인 → 현재 처리 중 표시
|
|
148
134
|
echo -e " ${DIM}${short_ts}${RESET} ${WHITE}${detail}${RESET}"
|
|
149
135
|
done
|
|
150
136
|
|
|
@@ -164,6 +150,10 @@ trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
|
|
|
164
150
|
clear
|
|
165
151
|
|
|
166
152
|
while true; do
|
|
153
|
+
# subshell 내 tput 불가 → 미리 터미널 크기 측정
|
|
154
|
+
_ROWS=$(tput lines 2>/dev/null || echo 30)
|
|
155
|
+
_COLS=$(tput cols 2>/dev/null || echo 80)
|
|
156
|
+
export _ROWS _COLS
|
|
167
157
|
buf=$(render_all 2>&1)
|
|
168
158
|
tput cup 0 0 2>/dev/null
|
|
169
159
|
echo "$buf"
|
package/skills/planner/SKILL.md
CHANGED
|
@@ -75,10 +75,58 @@ disable-model-invocation: true
|
|
|
75
75
|
## Constraints
|
|
76
76
|
|
|
77
77
|
- 기술 구현 세부사항은 Generator에 위임
|
|
78
|
-
- Sprint당 기능 3-5개 권장
|
|
79
78
|
- 각 기능에 `layer`, `service`, `depends_on` 명시
|
|
80
79
|
- API 계약의 스키마는 Pydantic/class-validator로 직접 변환 가능한 수준
|
|
81
80
|
|
|
81
|
+
## Team 병렬 스케줄링 규칙 (필수)
|
|
82
|
+
|
|
83
|
+
Team Mode는 **최대 3팀이 동시 작업**한다. Planner는 feature-list.json 설계 시 다음 규칙을 반드시 준수한다.
|
|
84
|
+
|
|
85
|
+
### 핵심 원칙: Sprint 시작 시 ready ≥ 3
|
|
86
|
+
|
|
87
|
+
Sprint 시작 시점에 `depends_on`이 모두 충족된(또는 비어 있는) feature가 **최소 3개** 있어야 3팀이 즉시 가동된다. ready가 1~2개이면 나머지 팀은 유휴 상태가 된다.
|
|
88
|
+
|
|
89
|
+
### 의존성 그래프 형태
|
|
90
|
+
|
|
91
|
+
**금지 — 직렬 체인:**
|
|
92
|
+
```
|
|
93
|
+
F-001 → F-002 → F-003 → F-004 (ready=1, 1팀만 작업)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**권장 — 넓은 DAG (fan-out):**
|
|
97
|
+
```
|
|
98
|
+
┌→ F-002 (no deps)
|
|
99
|
+
Foundation → F-003 (no deps) (ready=3, 3팀 동시)
|
|
100
|
+
└→ F-004 (no deps)
|
|
101
|
+
↓
|
|
102
|
+
F-005 (depends_on: [F-002, F-003])
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Sprint 설계 체크리스트
|
|
106
|
+
|
|
107
|
+
1. **Sprint당 feature 수**: `팀수 × 2` 이상 (3팀 = 최소 6개)
|
|
108
|
+
- 3개는 즉시 시작, 나머지는 앞선 feature 완료 시 투입
|
|
109
|
+
- 팀이 PASS 후 대기하지 않고 바로 다음 feature를 가져감
|
|
110
|
+
2. **동시 ready 보장**: Sprint 내 feature 중 `depends_on: []`인 것이 ≥ 3개
|
|
111
|
+
3. **의존성 깊이(critical path) 최소화**: 같은 Sprint 내 체인 깊이 ≤ 2단계
|
|
112
|
+
4. **layer 분산**: 같은 Sprint에 backend-only, frontend-only, fullstack을 혼합하여 서로 독립적으로 작업 가능
|
|
113
|
+
5. **Feature 분할**: 하나의 큰 feature 대신 독립 AC 그룹으로 분할
|
|
114
|
+
- 예: "대시보드 전체" (1개) → "통계 카드" + "차트 영역" + "최근 활동" (3개, 동시 작업 가능)
|
|
115
|
+
|
|
116
|
+
### 검증: ready count 시뮬레이션
|
|
117
|
+
|
|
118
|
+
feature-list.json 완성 후, Sprint별 ready count를 머릿속으로 시뮬레이션한다:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
Sprint N 시작 → ready 목록 계산
|
|
122
|
+
ready ≥ 3 → OK (3팀 동시 가동)
|
|
123
|
+
ready = 2 → WARNING (1팀 유휴)
|
|
124
|
+
ready = 1 → FAIL → feature 분할 또는 의존성 제거 필요
|
|
125
|
+
ready = 0 → CRITICAL → 이전 Sprint 의존성 재설계 필요
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
ready가 3 미만인 Sprint가 있으면 **feature를 더 작게 분할하거나 의존성을 제거**하여 수정한다.
|
|
129
|
+
|
|
82
130
|
## After Completion
|
|
83
131
|
|
|
84
132
|
1. 사용자에게 plan.md + api-contract.json 리뷰 요청
|