@walwal-harness/cli 5.6.3 → 5.6.5
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 +24 -1
- package/commands/harness-team.md +24 -17
- package/package.json +1 -1
- package/scripts/harness-dashboard.sh +13 -9
- package/scripts/harness-next.sh +1 -1
- package/scripts/harness-queue-manager.sh +75 -10
- package/scripts/lib/harness-guardrail.sh +3 -2
- package/scripts/lib/harness-render-progress.sh +2 -9
- package/skills/dispatcher/SKILL.md +7 -15
- package/skills/dispatcher/references/pipeline-definitions.md +13 -29
- package/skills/planner/SKILL.md +7 -6
- package/skills/planner/references/fe-stack-detection.md +27 -41
package/README.md
CHANGED
|
@@ -83,11 +83,34 @@ npx walwal-harness team
|
|
|
83
83
|
|
|
84
84
|
| 패널 | 내용 | 소스 |
|
|
85
85
|
|------|------|------|
|
|
86
|
-
| **Dashboard** | Pipeline · Sprint · Feature passes
|
|
86
|
+
| **Dashboard** | Pipeline · Sprint · Feature passes · Queue R:B:P · Retry | `harness-dashboard.sh` |
|
|
87
87
|
| **Gotcha & Memory** | 활성 에이전트의 누적 실수 + 나머지 요약 + 공유 메모리 | `harness-gotcha-memory.sh` |
|
|
88
88
|
| **TEAM 1–3** | 각 워커의 현재 feature · phase(Gen/Eval) · 실시간 stdout | `harness-queue-manager.sh` worker loop |
|
|
89
89
|
| **Archive Prompt** | 직전 완료 feature 요약 (다음 팀 컨텍스트 주입용) | archive 디렉토리 |
|
|
90
90
|
|
|
91
|
+
##### Feature 상태 아이콘 (v5.6.4+)
|
|
92
|
+
|
|
93
|
+
| 아이콘 | 상태 | 의미 |
|
|
94
|
+
|--------|------|------|
|
|
95
|
+
| `●` (녹색) | PASS | Evaluator 통과 · merge 완료 |
|
|
96
|
+
| `◐` (청색) | IN PROGRESS | 현재 team 배정됨 (phase 표기: T1:gen / T2:eval) |
|
|
97
|
+
| `○` (노랑) | READY | 의존성 해소됨 · idle team 배정 대기 |
|
|
98
|
+
| `◍` (자색) | **BLOCKED** | 선행 feature 대기 · deps 개수 함께 표시 |
|
|
99
|
+
| `◌` (어두움) | PENDING | 아직 큐 미등록 (sprint 미진입 or 사전 분석 단계) |
|
|
100
|
+
| `✗` (빨강) | FAILED | 재시도 한도 도달 · 사용자 개입 필요 |
|
|
101
|
+
|
|
102
|
+
##### Team Idle Auto-Dispatch (v5.6.4+)
|
|
103
|
+
|
|
104
|
+
Worker 완료 시 Lead 는 `auto-dispatch` 한 호출로 **모든 idle team ↔ ready feature** 쌍을 원자적으로 재배정합니다. 의존성 없는 작업은 병렬로 즉시 시작되어 team idle 시간은 "Agent 생성 소요 초" 로 수렴:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
bash scripts/harness-queue-manager.sh auto-dispatch .
|
|
108
|
+
# → [{"team":1,"feature":"F-001"},{"team":2,"feature":"F-002"}]
|
|
109
|
+
|
|
110
|
+
bash scripts/harness-queue-manager.sh idle-slots .
|
|
111
|
+
# → {"idle_teams":["3"], "ready_features":["F-003","F-004"], "dispatchable":1}
|
|
112
|
+
```
|
|
113
|
+
|
|
91
114
|
- 새로고침 주기: `HARNESS_REFRESH=5` (초). 환경변수로 조정.
|
|
92
115
|
- 단축키: `tmux prefix + 방향키` 로 패널 이동, `prefix + z` 로 확대/축소.
|
|
93
116
|
- 종료: `npx walwal-harness team --kill`.
|
package/commands/harness-team.md
CHANGED
|
@@ -59,17 +59,20 @@ bash scripts/harness-tmux.sh --team --force-tmux
|
|
|
59
59
|
|
|
60
60
|
**`--force-tmux` 필수**: iTerm2 감지 경로는 백그라운드에 iTerm2가 떠 있기만 해도 활성화되어 AppleScript 실패 시 팀 레이아웃이 조용히 사라짐. Team Mode는 항상 tmux로 강제하여 재현 가능한 레이아웃을 보장.
|
|
61
61
|
|
|
62
|
-
### Step 3: 초기 Worker 생성
|
|
62
|
+
### Step 3: 초기 Worker 생성 (Auto-Dispatch)
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
**v5.6.4+**: 개별 dequeue 대신 **`auto-dispatch`** 한 번으로 모든 idle team 에 ready feature 를 원자적으로 배정합니다. 의존성 없는 작업은 병렬로 즉시 시작됩니다.
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
#
|
|
68
|
-
bash scripts/harness-queue-manager.sh
|
|
69
|
-
|
|
70
|
-
bash scripts/harness-queue-manager.sh dequeue 3 .
|
|
67
|
+
# 모든 idle team ↔ ready feature 쌍을 한 번에 배정
|
|
68
|
+
bash scripts/harness-queue-manager.sh auto-dispatch .
|
|
69
|
+
# → [{"team":1,"feature":"F-001"},{"team":2,"feature":"F-002"},{"team":3,"feature":"F-003"}]
|
|
71
70
|
```
|
|
72
71
|
|
|
72
|
+
출력(JSON 배열)을 파싱해 각 `{team, feature}` 쌍마다 **background Agent** 를 생성합니다.
|
|
73
|
+
|
|
74
|
+
> **Fallback**: 단일 팀만 배정하려면 `dequeue <team_id>` 도 계속 사용 가능.
|
|
75
|
+
|
|
73
76
|
dequeue 성공한 피처마다 **background Agent**를 생성합니다:
|
|
74
77
|
|
|
75
78
|
```
|
|
@@ -99,25 +102,29 @@ ORCHESTRATION LOOP:
|
|
|
99
102
|
- FAIL (재시도 가능)인 경우 → Step 4b (Retry)
|
|
100
103
|
- ESCALATED인 경우 → 사용자에게 알림, 해당 팀 유휴
|
|
101
104
|
|
|
102
|
-
2.
|
|
103
|
-
|
|
105
|
+
2. **Auto-Dispatch (필수)** — worker 완료 직후 idle 이 된 팀뿐 아니라
|
|
106
|
+
모든 idle team 에 ready feature 를 즉시 재배정:
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
→ 새 Worker를 background Agent로 생성 (Step 3과 동일)
|
|
107
|
-
→ LOOP 계속
|
|
108
|
+
bash scripts/harness-queue-manager.sh auto-dispatch .
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
→ LOOP 계속
|
|
110
|
+
반환된 모든 (team, feature) 쌍에 대해 즉시 background Agent 생성.
|
|
111
|
+
한 작업의 merge 지연이 다른 idle team 을 놀게 두지 않는다.
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
3. Queue 상태 확인 (로그용):
|
|
114
|
+
bash scripts/harness-queue-manager.sh idle-slots .
|
|
115
|
+
bash scripts/harness-queue-manager.sh status .
|
|
116
|
+
|
|
117
|
+
4. auto-dispatch 가 pairs=[] (= ready 도 0, idle 도 0 혹은 idle 만 있고 ready 가 0) 이면:
|
|
118
|
+
→ ready=0 AND in_progress>0 → 다른 worker 완료 대기. LOOP 계속
|
|
119
|
+
→ ready=0 AND in_progress=0 → Sprint 전환 시도:
|
|
115
120
|
bash scripts/harness-queue-manager.sh next-sprint .
|
|
116
|
-
- "Advancing" →
|
|
121
|
+
- "Advancing" → auto-dispatch 다시 실행 (Step 2로 복귀)
|
|
117
122
|
- "ALL SPRINTS COMPLETE" → 최종 보고. LOOP 종료.
|
|
118
123
|
- "Cannot advance" → 실패 피처 사용자 개입 요청. LOOP 종료.
|
|
119
124
|
```
|
|
120
125
|
|
|
126
|
+
**핵심 원칙**: worker 완료 알림 → **즉시 auto-dispatch** → 반환된 모든 쌍에 대해 병렬 spawn. 팀 간 의존성이 없으면 idle 시간은 "Agent 생성에 걸리는 수초" 로 수렴한다.
|
|
127
|
+
|
|
121
128
|
#### Step 4a: Merge + Unblock (PASS 처리)
|
|
122
129
|
|
|
123
130
|
Worker가 PASS로 반환되면:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "5.6.
|
|
3
|
+
"version": "5.6.5",
|
|
4
4
|
"description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator chain (Code-Quality → Functional → Visual), optional Brainstormer. Supports React, Next.js, and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -269,6 +269,7 @@ render_team_features() {
|
|
|
269
269
|
($q[0].queue.failed // []) as $failed |
|
|
270
270
|
($q[0].queue.ready // []) as $ready |
|
|
271
271
|
($q[0].queue.in_progress // {}) as $prog |
|
|
272
|
+
($q[0].queue.blocked // {}) as $blocked |
|
|
272
273
|
.features[] |
|
|
273
274
|
.id as $fid |
|
|
274
275
|
(.name // .title // .description // "?" | if length > 18 then .[0:16] + ".." else . end) as $fname |
|
|
@@ -276,18 +277,21 @@ render_team_features() {
|
|
|
276
277
|
elif $prog[$fid] then "I|\($prog[$fid].team)|\($prog[$fid].phase)"
|
|
277
278
|
elif ($fid | IN($failed[])) then "F"
|
|
278
279
|
elif ($fid | IN($ready[])) then "R"
|
|
279
|
-
|
|
280
|
+
elif $blocked[$fid] then "BLK|\($blocked[$fid] | length)"
|
|
281
|
+
else "U" end) as $st |
|
|
280
282
|
"\($st)\t\($fid)\t\($fname)"
|
|
281
283
|
' "$FEATURES" 2>/dev/null | while IFS=$'\t' read -r st fid fname; do
|
|
282
284
|
case "$st" in
|
|
283
|
-
P)
|
|
284
|
-
F)
|
|
285
|
-
R)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
285
|
+
P) printf " ${GREEN}●${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
286
|
+
F) printf " ${RED}✗${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
287
|
+
R) printf " ${YELLOW}○${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
288
|
+
BLK\|*) deps=$(echo "$st" | cut -d'|' -f2)
|
|
289
|
+
printf " ${MAGENTA}◍${RESET} %-6s %-22s ${DIM}blocked (deps:%s)${RESET}\n" "$fid" "$fname" "$deps" ;;
|
|
290
|
+
U) printf " ${DIM}◌${RESET} %-6s %s\n" "$fid" "$fname" ;;
|
|
291
|
+
I\|*) team=$(echo "$st" | cut -d'|' -f2)
|
|
292
|
+
phase=$(echo "$st" | cut -d'|' -f3)
|
|
293
|
+
printf " ${CYAN}◐${RESET} %-6s %-22s T%s:%s\n" "$fid" "$fname" "$team" "$phase" ;;
|
|
294
|
+
*) printf " ? %-6s %s\n" "$fid" "$fname" ;;
|
|
291
295
|
esac
|
|
292
296
|
done
|
|
293
297
|
}
|
package/scripts/harness-next.sh
CHANGED
|
@@ -54,7 +54,7 @@ if [ -f "$PIPELINE_JSON" ]; then
|
|
|
54
54
|
fe_target=$(jq -r '.fe_target // empty' "$PIPELINE_JSON" 2>/dev/null || true)
|
|
55
55
|
if [ -z "$fe_target" ]; then
|
|
56
56
|
# pipeline.json 에 fe_target 미지정 시 config.json 의 _default_target 사용
|
|
57
|
-
fe_target
|
|
57
|
+
fe_target="web" # v5.6.5+: 치환 로직 제거. fe_target 은 pipeline.json 에서 명시하거나 web 기본.
|
|
58
58
|
fi
|
|
59
59
|
fi
|
|
60
60
|
|
|
@@ -424,19 +424,84 @@ cmd_recover() {
|
|
|
424
424
|
echo "[queue] Recovered ${stale_count} stale features back to ready queue."
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
# ══════════════════════════════════════════
|
|
428
|
+
# auto-dispatch — Pair all idle teams with ready features in one atomic step.
|
|
429
|
+
# Output: JSON array of {team, feature} on stdout.
|
|
430
|
+
# Atomically moves ready → in_progress + teams[tid] = busy for each pair.
|
|
431
|
+
# Dependencies are already honored (ready queue only holds unblocked features).
|
|
432
|
+
# ══════════════════════════════════════════
|
|
433
|
+
cmd_auto_dispatch() {
|
|
434
|
+
if [ ! -f "$QUEUE" ]; then echo "[queue] Run 'init' first." >&2; exit 1; fi
|
|
435
|
+
|
|
436
|
+
acquire_queue_lock
|
|
437
|
+
|
|
438
|
+
local result
|
|
439
|
+
result=$(jq '
|
|
440
|
+
. as $root |
|
|
441
|
+
($root.teams | to_entries | map(select(.value.status == "idle")) | map(.key)) as $idle |
|
|
442
|
+
($root.queue.ready) as $ready |
|
|
443
|
+
(if ($idle | length) < ($ready | length) then ($idle | length) else ($ready | length) end) as $n |
|
|
444
|
+
[range(0; $n) | { team: ($idle[.] | tonumber), feature: $ready[.] }] as $pairs |
|
|
445
|
+
(reduce $pairs[] as $p ($root;
|
|
446
|
+
.queue.ready -= [$p.feature] |
|
|
447
|
+
.queue.in_progress[$p.feature] = { team: $p.team, phase: "gen", attempt: 1 } |
|
|
448
|
+
.teams[($p.team | tostring)] = {
|
|
449
|
+
status: "busy",
|
|
450
|
+
feature: $p.feature,
|
|
451
|
+
branch: ("feature/" + $p.feature),
|
|
452
|
+
pid: null
|
|
453
|
+
}
|
|
454
|
+
)) as $updated |
|
|
455
|
+
{ state: $updated, pairs: $pairs }
|
|
456
|
+
' "$QUEUE")
|
|
457
|
+
|
|
458
|
+
local new_state pairs
|
|
459
|
+
new_state=$(echo "$result" | jq '.state')
|
|
460
|
+
pairs=$(echo "$result" | jq -c '.pairs')
|
|
461
|
+
|
|
462
|
+
# Write updated state
|
|
463
|
+
echo "$new_state" > "${QUEUE}.tmp" && mv "${QUEUE}.tmp" "$QUEUE"
|
|
464
|
+
|
|
465
|
+
release_queue_lock
|
|
466
|
+
|
|
467
|
+
# Emit pairs (single JSON line) for caller
|
|
468
|
+
echo "$pairs"
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
# ══════════════════════════════════════════
|
|
472
|
+
# idle-slots — Report how many teams are idle + how many ready features wait.
|
|
473
|
+
# No state mutation. Useful for dashboards / quick checks.
|
|
474
|
+
# ══════════════════════════════════════════
|
|
475
|
+
cmd_idle_slots() {
|
|
476
|
+
if [ ! -f "$QUEUE" ]; then echo "[queue] Run 'init' first." >&2; exit 1; fi
|
|
477
|
+
jq '{
|
|
478
|
+
idle_teams: [.teams | to_entries[] | select(.value.status == "idle") | .key],
|
|
479
|
+
ready_features: .queue.ready,
|
|
480
|
+
dispatchable: (
|
|
481
|
+
(if ([.teams | to_entries[] | select(.value.status == "idle") | .key] | length) <
|
|
482
|
+
(.queue.ready | length)
|
|
483
|
+
then [.teams | to_entries[] | select(.value.status == "idle") | .key] | length
|
|
484
|
+
else .queue.ready | length
|
|
485
|
+
end)
|
|
486
|
+
)
|
|
487
|
+
}' "$QUEUE"
|
|
488
|
+
}
|
|
489
|
+
|
|
427
490
|
# ── Dispatch ──
|
|
428
491
|
case "$CMD" in
|
|
429
|
-
init)
|
|
430
|
-
dequeue)
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
492
|
+
init) cmd_init "$@" ;;
|
|
493
|
+
dequeue) cmd_dequeue "$@" ;;
|
|
494
|
+
auto-dispatch) cmd_auto_dispatch ;;
|
|
495
|
+
idle-slots) cmd_idle_slots ;;
|
|
496
|
+
pass) cmd_pass "$@" ;;
|
|
497
|
+
fail) cmd_fail "$@" ;;
|
|
498
|
+
requeue) cmd_requeue "$@" ;;
|
|
499
|
+
update_phase) cmd_update_phase "$@" ;;
|
|
500
|
+
recover) cmd_recover ;;
|
|
501
|
+
next-sprint) cmd_next_sprint ;;
|
|
502
|
+
status) cmd_status ;;
|
|
438
503
|
*)
|
|
439
|
-
echo "Usage: harness-queue-manager.sh <init|dequeue|pass|fail|requeue|recover|next-sprint|update_phase|status> [args]"
|
|
504
|
+
echo "Usage: harness-queue-manager.sh <init|dequeue|auto-dispatch|idle-slots|pass|fail|requeue|recover|next-sprint|update_phase|status> [args]"
|
|
440
505
|
exit 1
|
|
441
506
|
;;
|
|
442
507
|
esac
|
|
@@ -29,9 +29,10 @@ get_allowed_paths() {
|
|
|
29
29
|
echo "tsconfig"
|
|
30
30
|
echo "docker-compose"
|
|
31
31
|
;;
|
|
32
|
-
generator-frontend
|
|
32
|
+
generator-frontend)
|
|
33
33
|
echo "apps/web/"
|
|
34
34
|
echo "apps/flutter/"
|
|
35
|
+
echo "lib/"
|
|
35
36
|
echo ".harness/actions/sprint-contract.md"
|
|
36
37
|
echo ".harness/actions/feature-list.json"
|
|
37
38
|
echo ".harness/progress.json"
|
|
@@ -43,7 +44,7 @@ get_allowed_paths() {
|
|
|
43
44
|
echo ".harness/actions/evaluation-code-quality.md"
|
|
44
45
|
echo ".harness/progress.json"
|
|
45
46
|
;;
|
|
46
|
-
evaluator-functional
|
|
47
|
+
evaluator-functional)
|
|
47
48
|
echo ".harness/actions/evaluation-functional.md"
|
|
48
49
|
echo ".harness/progress.json"
|
|
49
50
|
echo "tests/"
|
|
@@ -311,7 +311,7 @@ render_agent_bar() {
|
|
|
311
311
|
fe_stack=$(jq -r '.fe_stack // "react"' "$PIPELINE_JSON" 2>/dev/null || echo "react")
|
|
312
312
|
fe_target=$(jq -r '.fe_target // empty' "$PIPELINE_JSON" 2>/dev/null || true)
|
|
313
313
|
if [ -z "$fe_target" ]; then
|
|
314
|
-
fe_target
|
|
314
|
+
fe_target="web" # v5.6.5+: 치환 로직 제거. 기본 web.
|
|
315
315
|
fi
|
|
316
316
|
fi
|
|
317
317
|
|
|
@@ -325,14 +325,7 @@ render_agent_bar() {
|
|
|
325
325
|
|
|
326
326
|
while IFS= read -r agent; do
|
|
327
327
|
agent=$(echo "$agent" | sed 's/:.*//') # strip mode suffix like :light, :api-only
|
|
328
|
-
|
|
329
|
-
# fe_stack + fe_target 치환 적용
|
|
330
|
-
if [ "$fe_stack" = "flutter" ]; then
|
|
331
|
-
local sub
|
|
332
|
-
sub=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.flutter.by_target[\"${fe_target}\"][\"${agent}\"] // \"${agent}\"" "$CONFIG" 2>/dev/null)
|
|
333
|
-
if [ "$sub" = "__skip__" ]; then continue; fi
|
|
334
|
-
agent="$sub"
|
|
335
|
-
fi
|
|
328
|
+
# v5.6.5+: fe_stack 에이전트 치환 제거. 스택별 동작은 ref-docs 로 조절.
|
|
336
329
|
|
|
337
330
|
if [ "$first" = true ]; then
|
|
338
331
|
first=false
|
|
@@ -147,7 +147,7 @@ AGENTS.md 비하네스 → 기존 백업 + 리빌드
|
|
|
147
147
|
- FULLSTACK / FE-ONLY: `["evaluator-code-quality", "evaluator-functional", "evaluator-visual"]`
|
|
148
148
|
- BE-ONLY: `["evaluator-code-quality", "evaluator-functional"]` (functional 은 api-only 모드)
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
스택 특성(예: Flutter mobile 에서 Visual skip) 이 필요하면 해당 스택 ref-docs (`.harness/ref/fe-<stack>.md`) 의 `validation.visual.enabled` 를 false 로 두면 evaluator-visual 이 MANUAL_REQUIRED 로 우아하게 우회한다. 별도 치환 에이전트는 사용하지 않는다.
|
|
151
151
|
|
|
152
152
|
### Evaluator 체인 라우팅 규칙
|
|
153
153
|
|
|
@@ -163,11 +163,9 @@ Gotcha retry 시에도 체인 시작점은 chain[0] 부터 재실행.
|
|
|
163
163
|
|
|
164
164
|
FE-ONLY 또는 FULLSTACK 선택 시, `pipeline.json`에 **`fe_stack`** 필드를 포함해야 한다:
|
|
165
165
|
|
|
166
|
-
- `scan-result.json.tech_stack.fe_stack` 값을 기본으로 사용 (`react`
|
|
166
|
+
- `scan-result.json.tech_stack.fe_stack` 값을 기본으로 사용 (예: `react`, `nextjs`, `vue`, `flutter`, `swift` 등)
|
|
167
167
|
- 값이 없거나 불명확하면 Planner가 확정하도록 위임 (Dispatcher는 `"unknown"` 기록 + `notes` 에 메모)
|
|
168
|
-
-
|
|
169
|
-
- active: `generator-frontend-flutter`, `evaluator-functional-flutter`
|
|
170
|
-
- skipped: `generator-frontend`, `evaluator-functional`, `evaluator-visual`
|
|
168
|
+
- **에이전트 이름 치환은 하지 않는다** (v5.6.5+). 모든 FE 스택은 공통 `generator-frontend` / `evaluator-functional` / `evaluator-visual` 을 사용하고, 스택 특성은 `.harness/ref/fe-<stack>.md` (adaptive ref-docs) 에서 로드한다.
|
|
171
169
|
|
|
172
170
|
## 6. Brainstormer Routing Decision
|
|
173
171
|
|
|
@@ -222,7 +220,7 @@ Planner 를 호출해야 한다고 판단되면, **사용자에게 단 하나의
|
|
|
222
220
|
| 상황 | next_agent |
|
|
223
221
|
|------|-----------|
|
|
224
222
|
| "Eval, X 다시 검증해" | `evaluator-functional` (또는 `evaluator-visual`) |
|
|
225
|
-
| "Generator-FE, Y 버그 고쳐" | `generator-frontend`
|
|
223
|
+
| "Generator-FE, Y 버그 고쳐" | `generator-frontend` |
|
|
226
224
|
| "Generator-BE, API 재생성해" | `generator-backend` |
|
|
227
225
|
| Eval FAIL → retry | `failure.retry_target` |
|
|
228
226
|
| Gotcha 수정 | `failure.retry_target` 또는 현재 에이전트 |
|
|
@@ -240,14 +238,8 @@ Planner 를 호출해야 한다고 판단되면, **사용자에게 단 하나의
|
|
|
240
238
|
이 경우 기존 `.harness/actions/brainstorm-spec.md` 는 Brainstormer 의 On Start 에서
|
|
241
239
|
`.harness/archive/brainstorm-spec-<timestamp>.md` 로 백업된다.
|
|
242
240
|
|
|
243
|
-
## 7. Handoff 라우팅
|
|
241
|
+
## 7. Handoff 라우팅
|
|
244
242
|
|
|
245
|
-
Dispatcher가 `next_agent` 를 세팅할 때
|
|
243
|
+
Dispatcher 가 `next_agent` 를 세팅할 때 **스택별 에이전트 이름 치환은 하지 않는다** (v5.6.5+). 모든 FE 스택이 공통 `generator-frontend` / `evaluator-functional` / `evaluator-visual` 을 사용하고, 스택 특성은 adaptive ref-docs(`.harness/ref/fe-<stack>.md`) 에서 로드한다.
|
|
246
244
|
|
|
247
|
-
|
|
248
|
-
|-----------------|----------------|------------------|
|
|
249
|
-
| generator-frontend | generator-frontend | generator-frontend-flutter |
|
|
250
|
-
| evaluator-functional (FE 단계) | evaluator-functional | evaluator-functional-flutter |
|
|
251
|
-
| evaluator-visual | evaluator-visual | (skip → 다음 단계로 이동) |
|
|
252
|
-
|
|
253
|
-
**Brainstormer 는 fe_stack 치환 대상이 아니다** — 언어/스택 무관 공통 에이전트.
|
|
245
|
+
예외적 스킵 규칙은 ref-docs 의 `validation.visual.enabled` 플래그로 제어 — false 면 evaluator-visual 이 MANUAL_REQUIRED 로 우아하게 우회한다 (별도 에이전트 이름 변경 없음).
|
|
@@ -28,35 +28,19 @@ docmeta:
|
|
|
28
28
|
|
|
29
29
|
# Pipeline Definitions
|
|
30
30
|
|
|
31
|
-
> **FE Stack 차원**: 모든 FE 관련 파이프라인은 `fe_stack` 필드로
|
|
31
|
+
> **FE Stack 차원**: 모든 FE 관련 파이프라인은 `fe_stack` 필드로 스택을 기록한다.
|
|
32
32
|
> Planner가 scan-result.json(`tech_stack.fe_stack`) 또는 사용자 질문으로 확정한다.
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## 단일 에이전트 체인 (v5.6.5+)
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|----------|-----------|--------------|----------------|-------------|
|
|
38
|
-
| `react` | (n/a) | `generator-frontend` | `evaluator-functional` (Playwright) | `evaluator-visual` |
|
|
39
|
-
| `flutter`| **`web`** | `generator-frontend-flutter` | **`evaluator-functional`** (Playwright!) | **`evaluator-visual`** (Playwright!) |
|
|
40
|
-
| `flutter`| `mobile` | `generator-frontend-flutter` | `evaluator-functional-flutter` (정적 분석) | **SKIP** |
|
|
41
|
-
| `flutter`| `desktop` | `generator-frontend-flutter` | `evaluator-functional-flutter` (정적 분석) | **SKIP** |
|
|
36
|
+
**에이전트 이름 치환은 하지 않는다**. 모든 FE 스택(React, Next.js, Vue, Svelte, Flutter, Swift, 기타) 은 공통 `generator-frontend` / `evaluator-functional` / `evaluator-visual` 을 사용하며, 스택 특성(runner, paths, API, validation) 은 **adaptive ref-docs** (`.harness/ref/fe-<stack>.md`) 에서 로드한다.
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
| 스택별 스킵/우회 | 방법 |
|
|
39
|
+
|------------------|------|
|
|
40
|
+
| Visual 렌더 검증이 불가능한 네이티브 모바일/데스크톱 | ref-docs 의 `validation.visual.enabled = false` — `evaluator-visual` 이 MANUAL_REQUIRED 로 우아하게 우회 |
|
|
41
|
+
| Playwright 대신 스택 네이티브 E2E (예: XCUITest, Flutter `flutter_driver`) | ref-docs 의 `validation.functional_tests` 에 해당 명령 나열 — `evaluator-functional` 이 로드 실행 |
|
|
46
42
|
|
|
47
|
-
Dispatcher는 `
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
if pipeline.json.fe_stack == "flutter":
|
|
51
|
-
"generator-frontend" → "generator-frontend-flutter" (모든 fe_target 공통)
|
|
52
|
-
|
|
53
|
-
if fe_target == "web":
|
|
54
|
-
"evaluator-functional" → "evaluator-functional" (그대로, Playwright 사용)
|
|
55
|
-
"evaluator-visual" → "evaluator-visual" (그대로)
|
|
56
|
-
elif fe_target in ("mobile", "desktop"):
|
|
57
|
-
"evaluator-functional" → "evaluator-functional-flutter"
|
|
58
|
-
"evaluator-visual" → __skip__
|
|
59
|
-
```
|
|
43
|
+
Dispatcher 는 `fe_stack` 을 `pipeline.json` 에 기록만 하고, 에이전트 이름은 변경하지 않는다. Generator/Evaluator 는 세션 시작 시 스스로 ref-docs 를 로드해 스택에 맞는 동작을 한다.
|
|
60
44
|
|
|
61
45
|
## Evaluator Chain (공통)
|
|
62
46
|
|
|
@@ -83,10 +67,10 @@ agents:
|
|
|
83
67
|
- planner (light):
|
|
84
68
|
skip: MSA 서비스 설계, BE 기능 목록
|
|
85
69
|
do: OpenAPI → api-contract.json 변환, FE 컴포넌트 설계, feature-list (layer: frontend만), fe_stack 확정
|
|
86
|
-
- generator-frontend
|
|
70
|
+
- generator-frontend # 모든 스택 공통 (ref-docs 로드)
|
|
87
71
|
- evaluator-code-quality # 공통 — 브라우저 없음
|
|
88
|
-
- evaluator-functional
|
|
89
|
-
- evaluator-visual
|
|
72
|
+
- evaluator-functional # 모든 스택 공통
|
|
73
|
+
- evaluator-visual # ref.validation.visual.enabled=false 면 MANUAL_REQUIRED 로 우회
|
|
90
74
|
evaluator_chain:
|
|
91
75
|
- evaluator-code-quality
|
|
92
76
|
- evaluator-functional
|
|
@@ -95,9 +79,9 @@ skip:
|
|
|
95
79
|
- generator-backend
|
|
96
80
|
notes:
|
|
97
81
|
- api-contract.json은 OpenAPI에서 파생 (Planner가 변환)
|
|
98
|
-
- Eval-Func
|
|
82
|
+
- Eval-Func 의 API Health Check는 외부 서버 대상 (ref.api.base_url)
|
|
99
83
|
- AGENTS.md IA-MAP에 BE 경로 없음 (외부 서버)
|
|
100
|
-
-
|
|
84
|
+
- 네이티브 모바일/데스크톱 스택은 ref-docs 에서 visual.enabled=false 로 설정
|
|
101
85
|
```
|
|
102
86
|
|
|
103
87
|
## BE-ONLY
|
package/skills/planner/SKILL.md
CHANGED
|
@@ -76,14 +76,15 @@ jq '.agent_status = "completed" | .completed_agents += ["planner"]' .harness/p
|
|
|
76
76
|
|
|
77
77
|
## fe_stack (FE 파이프라인 분기)
|
|
78
78
|
|
|
79
|
-
`pipeline.json.fe_stack
|
|
79
|
+
`pipeline.json.fe_stack` 은 스택 기록용 메타데이터이며, **에이전트 이름 치환에는 사용되지 않는다** (v5.6.5+). 모든 스택이 공통 `generator-frontend` / `evaluator-functional` / `evaluator-visual` 을 사용하고, 스택별 동작(runner, paths, API, validation) 은 adaptive ref-docs(`.harness/ref/fe-<stack>.md`) 에서 로드한다.
|
|
80
80
|
|
|
81
|
-
|
|
|
82
|
-
|
|
83
|
-
| `react
|
|
84
|
-
| `flutter` |
|
|
81
|
+
| 스택 예 | ref-docs 경로 | 비고 |
|
|
82
|
+
|---------|--------------|------|
|
|
83
|
+
| `react`, `nextjs` | `.harness/ref/fe-react.md`, `.harness/ref/fe-nextjs.md` | Vercel/Next.js/Tailwind |
|
|
84
|
+
| `flutter` | `.harness/ref/fe-flutter.md` | Riverpod · validation.visual.enabled=false (모바일/데스크톱) |
|
|
85
|
+
| `vue`, `svelte`, `swift` 등 | `.harness/ref/fe-<stack>.md` | 각 스택 관례 |
|
|
85
86
|
|
|
86
|
-
**Planner는 `pipeline.json`에 `fe_stack`을 반드시 기록해야 한다.**
|
|
87
|
+
**Planner는 `pipeline.json`에 `fe_stack`을 반드시 기록해야 한다.** Generator/Evaluator 는 세션 시작 시 이 값으로 올바른 ref-docs 를 로드한다.
|
|
87
88
|
|
|
88
89
|
## Process
|
|
89
90
|
|
|
@@ -107,72 +107,58 @@ Flutter 프로젝트의 타깃을 확인합니다:
|
|
|
107
107
|
이번 스프린트의 타깃은? (A/B/C)
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
### fe_target
|
|
110
|
+
### fe_target 과 Eval 흐름 (v5.6.5+)
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|-----------|-----------|----------------|-------------|
|
|
114
|
-
| `web` | `generator-frontend-flutter` | `evaluator-functional` (Playwright!) | `evaluator-visual` (Playwright!) |
|
|
115
|
-
| `mobile` | `generator-frontend-flutter` | `evaluator-functional-flutter` (정적 분석) | SKIP |
|
|
116
|
-
| `desktop` | `generator-frontend-flutter` | `evaluator-functional-flutter` (정적 분석) | SKIP |
|
|
112
|
+
에이전트 이름 치환은 하지 않는다. 모든 스택이 공통 `generator-frontend` / `evaluator-functional` / `evaluator-visual` 을 사용하고, 스택 특성은 `.harness/ref/fe-<stack>.md` (adaptive ref-docs) 의 `validation` 블록으로 조절된다.
|
|
117
113
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
| fe_target | 전략 | 방법 |
|
|
115
|
+
|-----------|------|------|
|
|
116
|
+
| `web` (Flutter Web, Next.js 등) | 일반 Playwright 평가 | ref.validation.visual.enabled = true |
|
|
117
|
+
| `mobile` (iOS/Android 네이티브) | Visual 은 MANUAL_REQUIRED | ref.validation.visual.enabled = false + functional_tests 에 스택 네이티브 명령 |
|
|
118
|
+
| `desktop` (macOS/Windows/Linux 네이티브) | Visual MANUAL_REQUIRED | 동일 |
|
|
119
|
+
|
|
120
|
+
Generator 는 `ref.runner.dev_command` / `ref.paths.*` 를 로드해 스택별 빌드·테스트·구조를 따른다.
|
|
121
121
|
|
|
122
122
|
## 3. pipeline.json 갱신
|
|
123
123
|
|
|
124
|
-
`fe_stack` + `fe_target` 확정 후 `pipeline.json
|
|
124
|
+
`fe_stack` + `fe_target` 확정 후 `pipeline.json` 에 기록:
|
|
125
125
|
|
|
126
126
|
```json
|
|
127
127
|
{
|
|
128
128
|
"pipeline": "FULLSTACK",
|
|
129
129
|
"planner_mode": "full",
|
|
130
130
|
"fe_stack": "flutter",
|
|
131
|
-
"fe_target": "
|
|
131
|
+
"fe_target": "mobile",
|
|
132
132
|
"agents_active": [
|
|
133
133
|
"planner",
|
|
134
134
|
"generator-backend",
|
|
135
|
-
"generator-frontend-flutter",
|
|
136
|
-
"evaluator-functional",
|
|
137
|
-
"evaluator-visual"
|
|
138
|
-
],
|
|
139
|
-
"agents_skipped": [
|
|
140
135
|
"generator-frontend",
|
|
141
|
-
"evaluator-
|
|
136
|
+
"evaluator-code-quality",
|
|
137
|
+
"evaluator-functional"
|
|
142
138
|
],
|
|
143
|
-
"
|
|
144
|
-
"
|
|
139
|
+
"agents_skipped": [],
|
|
140
|
+
"evaluator_mode": "native",
|
|
141
|
+
"notes": "Flutter mobile — ref.validation.visual.enabled=false, evaluator-visual 은 MANUAL_REQUIRED 로 우회."
|
|
145
142
|
}
|
|
146
143
|
```
|
|
147
144
|
|
|
148
145
|
### fe_stack + fe_target → 파이프라인 매핑
|
|
149
146
|
|
|
150
|
-
| pipeline |
|
|
151
|
-
|
|
152
|
-
| FULLSTACK |
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
| FULLSTACK | flutter | desktop | planner, generator-backend, generator-frontend-flutter, evaluator-functional-flutter |
|
|
156
|
-
| FE-ONLY | react | (n/a) | planner, generator-frontend, evaluator-functional, evaluator-visual |
|
|
157
|
-
| FE-ONLY | flutter | **web** | planner, generator-frontend-flutter, evaluator-functional, evaluator-visual |
|
|
158
|
-
| FE-ONLY | flutter | mobile | planner, generator-frontend-flutter, evaluator-functional-flutter |
|
|
159
|
-
| BE-ONLY | (무관) | (n/a) | planner, generator-backend, evaluator-functional |
|
|
160
|
-
|
|
161
|
-
## 4. Flutter 선택 시 추가 작업
|
|
162
|
-
|
|
163
|
-
Flutter로 확정되면 Planner는:
|
|
164
|
-
|
|
165
|
-
1. **AGENTS.md IA-MAP** 의 `[FE]` 섹션을 Flutter 구조로 바꿔야 한다.
|
|
166
|
-
- `apps/web/` → `lib/ui/pages/`, `lib/ui/component/`
|
|
167
|
-
- `libs/shared-dto/` 대신 `integrated_data_layer/` 경로 등록
|
|
168
|
-
- 소유자: `→ Generator-Frontend-Flutter`
|
|
147
|
+
| pipeline | 에이전트 체인 (스택 무관) | ref-docs 로 조절되는 부분 |
|
|
148
|
+
|----------|--------------------------|---------------------------|
|
|
149
|
+
| FULLSTACK | planner → gen-be → gen-fe → eval-code-quality → eval-func → eval-visual | Gen/Eval 모두 ref.* 로드 |
|
|
150
|
+
| FE-ONLY | planner(light) → gen-fe → eval-code-quality → eval-func → eval-visual | 동일 |
|
|
151
|
+
| BE-ONLY | planner → gen-be → eval-code-quality → eval-func | visual 체인 없음 |
|
|
169
152
|
|
|
170
|
-
|
|
153
|
+
## 4. Flutter 등 네이티브 스택 선택 시 추가 작업
|
|
171
154
|
|
|
172
|
-
|
|
155
|
+
1. **AGENTS.md IA-MAP** 의 `[FE]` 섹션에 해당 스택 구조 반영 (예: Flutter 면 `lib/ui/pages/`, `integrated_data_layer/` 등).
|
|
156
|
+
2. **api-contract.json** 은 언어 중립 스키마 JSON — Generator 가 스택 타입으로 변환.
|
|
157
|
+
3. **feature-list.json** 의 `layer: "frontend"` feature 에 `fe_stack` 태그 유지 (필터링 용도).
|
|
158
|
+
4. **`.harness/ref/fe-<stack>.md`** 의 `validation` 블록을 스택에 맞게 채워 두기 — 이게 실제 동작을 바꾸는 유일한 지점.
|
|
173
159
|
|
|
174
160
|
## 5. 금지
|
|
175
161
|
|
|
176
162
|
- 파이프라인 실행 도중 `fe_stack` 변경 금지 — 스프린트 경계에서만 가능
|
|
177
|
-
-
|
|
163
|
+
- 서로 다른 스택 코드 혼재 생성 금지 — Generator 는 하나의 stack만 담당
|
|
178
164
|
- 사용자가 명시적으로 한 스택을 지시했는데 감지 결과로 다른 스택을 강제하지 말 것
|