@walwal-harness/cli 5.1.0 → 5.3.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 +39 -13
- package/package.json +1 -1
- package/scripts/harness-gotcha-memory.sh +165 -0
- package/scripts/harness-tmux.sh +30 -18
- package/scripts/init-agents-md.sh +10 -0
- package/scripts/init-ref-docs.sh +262 -0
- package/scripts/scan-project.sh +48 -2
- package/skills/dispatcher/SKILL.md +47 -15
- package/skills/dispatcher/references/gotcha-flow.md +28 -1
- package/skills/evaluator-functional/SKILL.md +32 -0
- package/skills/evaluator-visual/SKILL.md +19 -1
- package/skills/generator-backend/SKILL.md +66 -30
- package/skills/generator-frontend/SKILL.md +65 -58
- /package/skills/generator-frontend/references/{ai-forbidden-patterns.md → _web-react-legacy/ai-forbidden-patterns.md} +0 -0
- /package/skills/generator-frontend/references/{component-patterns.md → _web-react-legacy/component-patterns.md} +0 -0
- /package/skills/generator-frontend/references/{design-system-rules.md → _web-react-legacy/design-system-rules.md} +0 -0
- /package/skills/generator-frontend/references/{vercel-best-practices.md → _web-react-legacy/vercel-best-practices.md} +0 -0
package/commands/harness-team.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
docmeta:
|
|
3
|
+
id: harness-team
|
|
4
|
+
title: /harness-team — Team Mode 시작/재개
|
|
5
|
+
type: input
|
|
6
|
+
createdAt: 2026-04-20T00:00:00Z
|
|
7
|
+
updatedAt: 2026-04-20T00:00:00Z
|
|
8
|
+
source:
|
|
9
|
+
producer: user
|
|
10
|
+
skillId: harness
|
|
11
|
+
inputs: []
|
|
12
|
+
tags: [harness, team-mode, tmux, command]
|
|
13
|
+
---
|
|
14
|
+
|
|
1
15
|
# /harness-team — Team Mode 시작/재개
|
|
2
16
|
|
|
3
17
|
Planner가 완료한 feature-list.json의 피처들을 최대 3개 팀이 병렬로 Gen→Eval 사이클을 수행합니다.
|
|
@@ -40,9 +54,11 @@ bash scripts/harness-queue-manager.sh status .
|
|
|
40
54
|
### Step 2: tmux Studio 레이아웃 구축
|
|
41
55
|
|
|
42
56
|
```bash
|
|
43
|
-
bash scripts/harness-tmux.sh --team
|
|
57
|
+
bash scripts/harness-tmux.sh --team --force-tmux
|
|
44
58
|
```
|
|
45
59
|
|
|
60
|
+
**`--force-tmux` 필수**: iTerm2 감지 경로는 백그라운드에 iTerm2가 떠 있기만 해도 활성화되어 AppleScript 실패 시 팀 레이아웃이 조용히 사라짐. Team Mode는 항상 tmux로 강제하여 재현 가능한 레이아웃을 보장.
|
|
61
|
+
|
|
46
62
|
### Step 3: 초기 Worker 생성
|
|
47
63
|
|
|
48
64
|
Queue에서 ready 피처를 최대 3개 dequeue하고, **각각 background Agent로 생성**합니다.
|
|
@@ -162,18 +178,28 @@ LOG="$HARNESS_ROOT/.harness/progress.log"
|
|
|
162
178
|
logev() { echo "$(date +'%Y-%m-%d %H:%M') | team-{N} | $1 | $2" >> "$LOG"; }
|
|
163
179
|
```
|
|
164
180
|
|
|
165
|
-
| ACTION | 사용 시점 | DETAIL 예시 |
|
|
166
|
-
|
|
167
|
-
| `gen-start` | Gen Phase 시작 | `F-001 start — 6 AC` |
|
|
168
|
-
| `gen-
|
|
169
|
-
| `gen-
|
|
170
|
-
| `gen-
|
|
171
|
-
| `gen-
|
|
172
|
-
| `
|
|
173
|
-
| `eval-
|
|
174
|
-
| `eval-
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
181
|
+
| ACTION | 사용 시점 | DETAIL 예시 (필수 포함 정보) |
|
|
182
|
+
|--------|-----------|------------------------------|
|
|
183
|
+
| `gen-start` | Gen Phase 시작 (1회) | `F-001 "사용자 회원가입 API" start — goal=POST /users, 6 AC` — **Feature 제목+목표** 포함 필수 |
|
|
184
|
+
| `gen-plan` | 작업 계획 공표 (1회) | `plan: create controller+service+dto, wire module, add 3 unit tests` |
|
|
185
|
+
| `gen-read` | 소스/계약 읽기 (매 파일) | `read api-contract.json#/paths/~1users` |
|
|
186
|
+
| `gen-write` | 파일 생성/수정 (**매 파일**) | `write apps/service-user/src/user.controller.ts (+82 LOC, create)` — **경로+LOC+action(create/edit/delete)** 필수 |
|
|
187
|
+
| `gen-test` | 자체 게이트 | `tsc OK · eslint 0 warn · jest 12/12 pass` |
|
|
188
|
+
| `gen-done` | Gen Phase 종료 | `F-001 done — 5 files: controller.ts, service.ts, dto.ts, module.ts, spec.ts (total +142 LOC)` — **변경 파일 전체 나열** 필수 |
|
|
189
|
+
| `eval-start` | Evaluator 시작 | `F-001 evaluating — 6 ACs + regression + security` — **AC 개수+검증 축** 필수 |
|
|
190
|
+
| `eval-ac` | AC 본문 선언 (**매 AC 시작 시 1회**) | `AC-3: "POST /users returns 201 with created user id"` — **AC 원문** 필수 |
|
|
191
|
+
| `eval-check` | AC 검증 수행/증거 | `AC-3 [PASS] — curl POST /users → 201, body.id matches` — **판정+증거** 필수 |
|
|
192
|
+
| `eval-gate` | 자동 게이트 | `gate: tsc OK, eslint OK, security scan 0 high` |
|
|
193
|
+
| `eval-done` | Eval 결과 | `F-001 VERDICT=PASS SCORE=2.95/3.00 (AC 6/6, gates OK)` |
|
|
194
|
+
| `result` | PASS 확정 (**SCORE ≥ 2.80**) | `F-001 PASS score=2.95` |
|
|
195
|
+
| `fail` | FAIL 확정 | `F-001 FAIL #1 — AC-2 "email uniqueness" missing DB constraint` |
|
|
196
|
+
|
|
197
|
+
**중요: 로깅 가독성 규칙 (필수)**
|
|
198
|
+
1. `gen-start`에는 반드시 Feature **제목과 목표**를 함께 기록 (무슨 일을 시작하는가 명확히).
|
|
199
|
+
2. `gen-write`는 **변경되는 파일마다 1건씩** 기록. 묶어서 요약 금지 ("2 files edit" 같은 표기 금지).
|
|
200
|
+
3. `gen-done`은 **변경 파일 전체 목록**을 나열. "(2 files)" 같은 개수만 기록 금지.
|
|
201
|
+
4. `eval-ac`로 **AC 원문을 먼저 선언**한 뒤 `eval-check`로 증거/판정 기록. "AC-1 count=0" 같은 수치 단독 기록 금지.
|
|
202
|
+
5. `result` PASS는 **SCORE ≥ 2.80** 인 경우에만 기록. score=1.00인데 PASS로 기록하는 실수 금지.
|
|
177
203
|
|
|
178
204
|
**queue phase 업데이트:**
|
|
179
205
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.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"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-gotcha-memory.sh — Gotcha & Memory pane (index view)
|
|
3
|
+
# 활성 에이전트의 gotcha를 최상단으로, 나머지는 요약(최근 3항목+총개수)으로 표시
|
|
4
|
+
# Usage: bash scripts/harness-gotcha-memory.sh [project-root]
|
|
5
|
+
|
|
6
|
+
set -uo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
10
|
+
|
|
11
|
+
PROJECT_ROOT="${1:-}"
|
|
12
|
+
if [ -z "$PROJECT_ROOT" ]; then
|
|
13
|
+
PROJECT_ROOT="$(resolve_harness_root ".")" || { echo "[gotcha] .harness/ not found."; exit 1; }
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
MEMORY_FILE="$PROJECT_ROOT/.harness/memory.md"
|
|
17
|
+
GOTCHAS_DIR="$PROJECT_ROOT/.harness/gotchas"
|
|
18
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
19
|
+
|
|
20
|
+
BOLD="\033[1m"
|
|
21
|
+
DIM="\033[2m"
|
|
22
|
+
GREEN="\033[32m"
|
|
23
|
+
YELLOW="\033[33m"
|
|
24
|
+
RED="\033[31m"
|
|
25
|
+
CYAN="\033[36m"
|
|
26
|
+
MAGENTA="\033[35m"
|
|
27
|
+
RESET="\033[0m"
|
|
28
|
+
BG_YELLOW="\033[43m"
|
|
29
|
+
|
|
30
|
+
REFRESH_SEC="${HARNESS_REFRESH:-5}"
|
|
31
|
+
RECENT_N="${HARNESS_GOTCHA_RECENT:-3}"
|
|
32
|
+
|
|
33
|
+
# 활성 에이전트 결정: current_agent > next_agent
|
|
34
|
+
get_active_agent() {
|
|
35
|
+
[ -f "$PROGRESS" ] || { echo ""; return; }
|
|
36
|
+
command -v jq &>/dev/null || { echo ""; return; }
|
|
37
|
+
local cur nxt
|
|
38
|
+
cur=$(jq -r '.current_agent // ""' "$PROGRESS" 2>/dev/null)
|
|
39
|
+
nxt=$(jq -r '.next_agent // ""' "$PROGRESS" 2>/dev/null)
|
|
40
|
+
if [ -n "$cur" ] && [ "$cur" != "null" ] && [ "$cur" != "none" ]; then
|
|
41
|
+
echo "$cur"
|
|
42
|
+
elif [ -n "$nxt" ] && [ "$nxt" != "null" ] && [ "$nxt" != "none" ]; then
|
|
43
|
+
echo "$nxt"
|
|
44
|
+
else
|
|
45
|
+
echo ""
|
|
46
|
+
fi
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# gotcha 파일에서 '### ' 헤딩 개수 = 항목 총수
|
|
50
|
+
count_items() {
|
|
51
|
+
local f="$1"
|
|
52
|
+
local n
|
|
53
|
+
n=$(grep -c '^### ' "$f" 2>/dev/null)
|
|
54
|
+
echo "${n:-0}"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# 최근 N개 항목 제목 추출 (### 헤딩 — 파일 하단부가 최신이라 tail)
|
|
58
|
+
recent_titles() {
|
|
59
|
+
local f="$1" n="$2"
|
|
60
|
+
grep '^### ' "$f" 2>/dev/null | tail -n "$n" | sed 's/^### //'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# agent 이름 → 파일 basename 매핑 (정확/접두 일치)
|
|
64
|
+
file_for_agent() {
|
|
65
|
+
local agent="$1"
|
|
66
|
+
local f="$GOTCHAS_DIR/${agent}.md"
|
|
67
|
+
[ -f "$f" ] && { echo "$f"; return; }
|
|
68
|
+
# 접두 일치 (예: current_agent=generator → generator-backend/frontend 중 하나)
|
|
69
|
+
for cand in "$GOTCHAS_DIR"/*.md; do
|
|
70
|
+
[ -e "$cand" ] || continue
|
|
71
|
+
local base
|
|
72
|
+
base=$(basename "$cand" .md)
|
|
73
|
+
[ "$base" = "README" ] && continue
|
|
74
|
+
case "$base" in
|
|
75
|
+
"$agent"*|*"$agent"*) echo "$cand"; return ;;
|
|
76
|
+
esac
|
|
77
|
+
done
|
|
78
|
+
echo ""
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
render_gotcha_entry() {
|
|
82
|
+
local f="$1" active="$2"
|
|
83
|
+
local name count
|
|
84
|
+
name=$(basename "$f" .md)
|
|
85
|
+
count=$(count_items "$f")
|
|
86
|
+
if [ "$active" = "true" ]; then
|
|
87
|
+
echo -e "${BOLD}${BG_YELLOW} ▸ ${name} ${RESET} ${DIM}(${count} items · ACTIVE)${RESET}"
|
|
88
|
+
else
|
|
89
|
+
echo -e "${BOLD}${GREEN}▸ ${name}${RESET} ${DIM}(${count} items)${RESET}"
|
|
90
|
+
fi
|
|
91
|
+
if [ "$count" -eq 0 ]; then
|
|
92
|
+
echo -e " ${DIM}(항목 없음)${RESET}"
|
|
93
|
+
else
|
|
94
|
+
recent_titles "$f" "$RECENT_N" | while IFS= read -r t; do
|
|
95
|
+
echo -e " ${DIM}·${RESET} $t"
|
|
96
|
+
done
|
|
97
|
+
fi
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
render_memory_summary() {
|
|
101
|
+
if [ ! -f "$MEMORY_FILE" ]; then
|
|
102
|
+
echo -e "${DIM}(memory.md 없음)${RESET}"
|
|
103
|
+
return
|
|
104
|
+
fi
|
|
105
|
+
local count
|
|
106
|
+
count=$(grep -c '^### ' "$MEMORY_FILE" 2>/dev/null)
|
|
107
|
+
count="${count:-0}"
|
|
108
|
+
echo -e "${BOLD}${CYAN}▣ SHARED MEMORY${RESET} ${DIM}(${count} items · memory.md)${RESET}"
|
|
109
|
+
if [ "$count" -eq 0 ]; then
|
|
110
|
+
echo -e " ${DIM}(항목 없음)${RESET}"
|
|
111
|
+
else
|
|
112
|
+
grep '^### ' "$MEMORY_FILE" | tail -n "$RECENT_N" | sed 's/^### //' | while IFS= read -r t; do
|
|
113
|
+
echo -e " ${DIM}·${RESET} $t"
|
|
114
|
+
done
|
|
115
|
+
fi
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
render() {
|
|
119
|
+
clear
|
|
120
|
+
local cols hr
|
|
121
|
+
cols=$(tput cols 2>/dev/null || echo 80)
|
|
122
|
+
hr=$(printf '─%.0s' $(seq 1 "$cols"))
|
|
123
|
+
|
|
124
|
+
local active active_file
|
|
125
|
+
active=$(get_active_agent)
|
|
126
|
+
active_file=""
|
|
127
|
+
[ -n "$active" ] && active_file=$(file_for_agent "$active")
|
|
128
|
+
|
|
129
|
+
echo -e "${BOLD}${MAGENTA}GOTCHA INDEX${RESET} ${DIM}$(date +%H:%M:%S) · recent=${RECENT_N} · refresh ${REFRESH_SEC}s${RESET}"
|
|
130
|
+
if [ -n "$active" ]; then
|
|
131
|
+
echo -e "${DIM}active agent:${RESET} ${BOLD}${YELLOW}${active}${RESET}"
|
|
132
|
+
else
|
|
133
|
+
echo -e "${DIM}active agent: (none)${RESET}"
|
|
134
|
+
fi
|
|
135
|
+
echo -e "${DIM}${hr}${RESET}"
|
|
136
|
+
|
|
137
|
+
# 활성 에이전트 gotcha 최상단
|
|
138
|
+
if [ -n "$active_file" ]; then
|
|
139
|
+
render_gotcha_entry "$active_file" "true"
|
|
140
|
+
echo ""
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# 나머지 gotcha
|
|
144
|
+
if [ -d "$GOTCHAS_DIR" ]; then
|
|
145
|
+
for f in "$GOTCHAS_DIR"/*.md; do
|
|
146
|
+
[ -e "$f" ] || continue
|
|
147
|
+
[ "$f" = "$active_file" ] && continue
|
|
148
|
+
local base
|
|
149
|
+
base=$(basename "$f" .md)
|
|
150
|
+
[ "$base" = "README" ] && continue
|
|
151
|
+
render_gotcha_entry "$f" "false"
|
|
152
|
+
done
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
echo ""
|
|
156
|
+
echo -e "${DIM}${hr}${RESET}"
|
|
157
|
+
render_memory_summary
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
trap 'exit 0' INT TERM
|
|
161
|
+
|
|
162
|
+
while true; do
|
|
163
|
+
render
|
|
164
|
+
sleep "$REFRESH_SEC"
|
|
165
|
+
done
|
package/scripts/harness-tmux.sh
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
# Team Mode (--team):
|
|
5
5
|
# ┌──────────────┬──────────────┬──────────────┐
|
|
6
6
|
# │ │ │ TEAM 1 │
|
|
7
|
-
# │
|
|
8
|
-
# │
|
|
9
|
-
# │
|
|
10
|
-
# │
|
|
7
|
+
# │ Dashboard │ Gotcha & │ │
|
|
8
|
+
# │ (queue + │ Memory ├──────────────┤
|
|
9
|
+
# │ status + │ │ TEAM 2 │
|
|
10
|
+
# │ archive) │ │ │
|
|
11
11
|
# │ │ ├──────────────┤
|
|
12
12
|
# │ │ │ TEAM 3 │
|
|
13
13
|
# └──────────────┴──────────────┴──────────────┘
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
# Usage:
|
|
21
21
|
# bash scripts/harness-tmux.sh [project-root] --team
|
|
22
22
|
# bash scripts/harness-tmux.sh [project-root] --solo
|
|
23
|
+
# bash scripts/harness-tmux.sh [project-root] --team --force-tmux # skip iTerm2, use tmux
|
|
23
24
|
# bash scripts/harness-tmux.sh --kill
|
|
24
25
|
|
|
25
26
|
set -euo pipefail
|
|
@@ -30,12 +31,14 @@ SESSION_NAME="harness-studio"
|
|
|
30
31
|
PROJECT_ROOT=""
|
|
31
32
|
MODE=""
|
|
32
33
|
DETACH=false
|
|
34
|
+
FORCE_TMUX=false
|
|
33
35
|
|
|
34
36
|
for arg in "$@"; do
|
|
35
37
|
case "$arg" in
|
|
36
38
|
--team) MODE="team" ;;
|
|
37
39
|
--solo) MODE="solo" ;;
|
|
38
40
|
--detach) DETACH=true ;;
|
|
41
|
+
--force-tmux) FORCE_TMUX=true ;;
|
|
39
42
|
--kill)
|
|
40
43
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "tmux session killed." || true
|
|
41
44
|
# Also try to close iTerm2 studio tab if exists
|
|
@@ -99,12 +102,12 @@ launch_iterm2_team() {
|
|
|
99
102
|
tell studioWindow
|
|
100
103
|
tell current session of current tab
|
|
101
104
|
set name to "harness-studio"
|
|
102
|
-
write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-
|
|
105
|
+
write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-dashboard.sh' '${PROJECT_ROOT}'"
|
|
103
106
|
|
|
104
|
-
-- Split right →
|
|
107
|
+
-- Split right → Gotcha & Memory
|
|
105
108
|
set dashPane to (split vertically with default profile)
|
|
106
109
|
tell dashPane
|
|
107
|
-
write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-
|
|
110
|
+
write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-gotcha-memory.sh' '${PROJECT_ROOT}'"
|
|
108
111
|
|
|
109
112
|
-- Split right → Team 1
|
|
110
113
|
set t1Pane to (split vertically with default profile)
|
|
@@ -171,24 +174,24 @@ APPLESCRIPT
|
|
|
171
174
|
launch_tmux_team() {
|
|
172
175
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
|
|
173
176
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
PANE_T1=$(tmux split-window -h -p 60 -t "$
|
|
177
|
+
PANE_DASH=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 -P -F '#{pane_id}')
|
|
178
|
+
PANE_GOTCHA=$(tmux split-window -h -p 70 -t "$PANE_DASH" -c "$PROJECT_ROOT" -P -F '#{pane_id}')
|
|
179
|
+
PANE_T1=$(tmux split-window -h -p 60 -t "$PANE_GOTCHA" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
|
|
177
180
|
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 1'")
|
|
178
181
|
PANE_T2=$(tmux split-window -v -p 66 -t "$PANE_T1" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
|
|
179
182
|
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 2'")
|
|
180
183
|
PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
|
|
181
184
|
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 3'")
|
|
182
185
|
|
|
183
|
-
tmux send-keys -t "$
|
|
184
|
-
tmux send-keys -t "$
|
|
186
|
+
tmux send-keys -t "$PANE_DASH" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
|
|
187
|
+
tmux send-keys -t "$PANE_GOTCHA" "bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\"" Enter
|
|
185
188
|
|
|
186
|
-
tmux select-pane -t "$
|
|
187
|
-
tmux select-pane -t "$
|
|
188
|
-
tmux select-pane -t "$PANE_T1"
|
|
189
|
-
tmux select-pane -t "$PANE_T2"
|
|
190
|
-
tmux select-pane -t "$PANE_T3"
|
|
191
|
-
tmux select-pane -t "$
|
|
189
|
+
tmux select-pane -t "$PANE_DASH" -T "Dashboard"
|
|
190
|
+
tmux select-pane -t "$PANE_GOTCHA" -T "Gotcha & Memory"
|
|
191
|
+
tmux select-pane -t "$PANE_T1" -T "TEAM 1"
|
|
192
|
+
tmux select-pane -t "$PANE_T2" -T "TEAM 2"
|
|
193
|
+
tmux select-pane -t "$PANE_T3" -T "TEAM 3"
|
|
194
|
+
tmux select-pane -t "$PANE_DASH"
|
|
192
195
|
|
|
193
196
|
tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
|
|
194
197
|
tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
@@ -237,6 +240,15 @@ if command -v tmux &>/dev/null; then
|
|
|
237
240
|
HAS_TMUX=true
|
|
238
241
|
fi
|
|
239
242
|
|
|
243
|
+
# --force-tmux overrides iTerm2 detection
|
|
244
|
+
if [ "$FORCE_TMUX" = true ]; then
|
|
245
|
+
if [ "$HAS_TMUX" != true ]; then
|
|
246
|
+
echo "ERROR: --force-tmux specified but tmux not installed. Run: brew install tmux"
|
|
247
|
+
exit 1
|
|
248
|
+
fi
|
|
249
|
+
IS_ITERM=false
|
|
250
|
+
fi
|
|
251
|
+
|
|
240
252
|
if [ "$IS_ITERM" = true ]; then
|
|
241
253
|
echo "Strategy: iTerm2 native split panes"
|
|
242
254
|
if [ "$MODE" = "team" ]; then
|
|
@@ -5,6 +5,7 @@ set -e
|
|
|
5
5
|
|
|
6
6
|
PROJECT_ROOT="${1:-.}"
|
|
7
7
|
SCAN_RESULT="${PROJECT_ROOT}/.harness/actions/scan-result.json"
|
|
8
|
+
export SCAN_RESULT PROJECT_ROOT
|
|
8
9
|
AGENTS_FILE="${PROJECT_ROOT}/AGENTS.md"
|
|
9
10
|
CLAUDE_FILE="${PROJECT_ROOT}/CLAUDE.md"
|
|
10
11
|
BACKUP_DIR="${PROJECT_ROOT}/.harness/archive/pre-harness-backup"
|
|
@@ -128,6 +129,15 @@ tag_rules = {
|
|
|
128
129
|
"tests": ("[TEST]", "Test Suite", "Evaluator"),
|
|
129
130
|
"e2e": ("[TEST]", "E2E Tests", "Evaluator"),
|
|
130
131
|
"__tests__": ("[TEST]", "Unit Tests", "Evaluator"),
|
|
132
|
+
|
|
133
|
+
# Native app patterns (Swift / Kotlin / Rust 등)
|
|
134
|
+
"Sources": ("[FE]", "Native Source Root", "Generator-Frontend"),
|
|
135
|
+
"Tests": ("[TEST]", "Native Test Suite", "Evaluator"),
|
|
136
|
+
"Resources": ("[FE]", "Native Resources", "Generator-Frontend"),
|
|
137
|
+
"android": ("[FE]", "Android Host", "Generator-Frontend"),
|
|
138
|
+
"ios": ("[FE]", "iOS Host", "Generator-Frontend"),
|
|
139
|
+
"macos": ("[FE]", "macOS Host", "Generator-Frontend"),
|
|
140
|
+
"Shared": ("[FE]", "Cross-platform Shared", "Generator-Frontend"),
|
|
131
141
|
}
|
|
132
142
|
|
|
133
143
|
# 디렉토리를 분석
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# init-ref-docs.sh — 감지된 스택에 따라 .harness/ref/<role>-<stack>.md 를 준비한다.
|
|
3
|
+
#
|
|
4
|
+
# bash 혼자서는 Claude 의 WebSearch/WebFetch 를 호출할 수 없으므로 이 스크립트는:
|
|
5
|
+
# 1. scan-result.json 에서 감지된 스택 목록을 계산
|
|
6
|
+
# 2. --placeholder 모드: 최소 frontmatter + TODO 본문을 저장 (AC 검증용 · 파이프라인 unblock 용)
|
|
7
|
+
# 3. --claude-prompt 모드: Claude 세션이 실제 WebSearch + WebFetch + synthesis 로 채울 수 있도록 프롬프트 템플릿을 stdout 출력
|
|
8
|
+
# 4. --register 모드: Claude 가 실제 본문을 작성한 뒤 호출해 .generated.json 에 sources / generated_at 을 등록
|
|
9
|
+
#
|
|
10
|
+
# 사용 예:
|
|
11
|
+
# bash scripts/init-ref-docs.sh --dry-run .
|
|
12
|
+
# bash scripts/init-ref-docs.sh --yes --placeholder --stack swift --role fe .
|
|
13
|
+
# bash scripts/init-ref-docs.sh --claude-prompt --stack swift --role fe .
|
|
14
|
+
# bash scripts/init-ref-docs.sh --register --stack swift --role fe --sources "https://a,https://b" .
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
# ───── 옵션 파싱 ─────
|
|
18
|
+
MODE="interactive" # interactive | dry-run | placeholder | claude-prompt | register
|
|
19
|
+
YES=false
|
|
20
|
+
REFRESH=false
|
|
21
|
+
STACK=""
|
|
22
|
+
ROLE=""
|
|
23
|
+
SOURCES=""
|
|
24
|
+
PROJECT_ROOT="."
|
|
25
|
+
|
|
26
|
+
while [[ $# -gt 0 ]]; do
|
|
27
|
+
case "$1" in
|
|
28
|
+
--dry-run) MODE="dry-run"; shift ;;
|
|
29
|
+
--placeholder) MODE="placeholder"; shift ;;
|
|
30
|
+
--claude-prompt) MODE="claude-prompt"; shift ;;
|
|
31
|
+
--register) MODE="register"; shift ;;
|
|
32
|
+
--yes) YES=true; shift ;;
|
|
33
|
+
--refresh) REFRESH=true; shift ;;
|
|
34
|
+
--stack) STACK="$2"; shift 2 ;;
|
|
35
|
+
--role) ROLE="$2"; shift 2 ;;
|
|
36
|
+
--sources) SOURCES="$2"; shift 2 ;;
|
|
37
|
+
-h|--help)
|
|
38
|
+
sed -n '2,16p' "$0" | sed 's/^# //; s/^#//'
|
|
39
|
+
exit 0 ;;
|
|
40
|
+
*) PROJECT_ROOT="$1"; shift ;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
SCAN="${PROJECT_ROOT}/.harness/actions/scan-result.json"
|
|
45
|
+
REF_DIR="${PROJECT_ROOT}/.harness/ref"
|
|
46
|
+
META="${REF_DIR}/.generated.json"
|
|
47
|
+
ARCHIVE_DIR="${PROJECT_ROOT}/.harness/archive"
|
|
48
|
+
mkdir -p "$REF_DIR" "$ARCHIVE_DIR"
|
|
49
|
+
[ -f "$META" ] || echo "{}" > "$META"
|
|
50
|
+
|
|
51
|
+
# ───── 감지된 스택 목록 ─────
|
|
52
|
+
detect_stacks() {
|
|
53
|
+
if [ ! -f "$SCAN" ]; then
|
|
54
|
+
echo "ERROR: scan-result.json 이 없습니다. 먼저 bash scripts/scan-project.sh ${PROJECT_ROOT} 실행하세요." >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
local fe be native
|
|
58
|
+
fe=$(jq -r '.tech_stack.frontend' "$SCAN")
|
|
59
|
+
be=$(jq -r '.tech_stack.backend' "$SCAN")
|
|
60
|
+
native=$(jq -r '.tech_stack.is_native_app // false' "$SCAN")
|
|
61
|
+
|
|
62
|
+
# 네이티브 앱은 fe 만 기록 (be 는 null)
|
|
63
|
+
if [ "$fe" != "unknown" ] && [ "$fe" != "null" ]; then
|
|
64
|
+
echo "fe:$fe"
|
|
65
|
+
fi
|
|
66
|
+
if [ "$native" != "true" ] && [ "$be" != "unknown" ] && [ "$be" != "null" ]; then
|
|
67
|
+
echo "be:$be"
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# ───── placeholder 본문 ─────
|
|
72
|
+
write_placeholder() {
|
|
73
|
+
local role="$1" stack="$2" path="$3"
|
|
74
|
+
local ts
|
|
75
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
76
|
+
cat > "$path" <<REF
|
|
77
|
+
---
|
|
78
|
+
docmeta:
|
|
79
|
+
id: ref-${role}-${stack}
|
|
80
|
+
stack: ${stack}
|
|
81
|
+
role: ${role}
|
|
82
|
+
language: ${stack}
|
|
83
|
+
generated_at: ${ts}
|
|
84
|
+
generator: init-ref-docs.sh (placeholder)
|
|
85
|
+
sources: []
|
|
86
|
+
version: 0
|
|
87
|
+
runner:
|
|
88
|
+
dev_command: null
|
|
89
|
+
start_command: null
|
|
90
|
+
install_command: null
|
|
91
|
+
paths:
|
|
92
|
+
source_roots: []
|
|
93
|
+
test_roots: []
|
|
94
|
+
config_files: []
|
|
95
|
+
api:
|
|
96
|
+
base_url: null
|
|
97
|
+
gateway: null
|
|
98
|
+
validation:
|
|
99
|
+
pre_eval_gate: []
|
|
100
|
+
functional_tests: []
|
|
101
|
+
visual:
|
|
102
|
+
enabled: false
|
|
103
|
+
reason: "placeholder — not yet filled by Claude"
|
|
104
|
+
manual_check: "bash init.sh refresh-ref ${role} ${stack} 실행 후 Claude 세션에서 WebSearch + WebFetch 로 채우기"
|
|
105
|
+
anti_pattern_rules: []
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
# Ref — ${stack} (${role})
|
|
109
|
+
|
|
110
|
+
> **Status**: PLACEHOLDER — Claude 가 WebSearch + WebFetch 로 채워야 함.
|
|
111
|
+
> 실행: bash scripts/init-ref-docs.sh --claude-prompt --stack ${stack} --role ${role}
|
|
112
|
+
|
|
113
|
+
## 1. Runner
|
|
114
|
+
TODO
|
|
115
|
+
|
|
116
|
+
## 2. Paths / Source Layout
|
|
117
|
+
TODO
|
|
118
|
+
|
|
119
|
+
## 3. Best Practices
|
|
120
|
+
TODO
|
|
121
|
+
|
|
122
|
+
## 4. Anti-Patterns (→ gotchas 시드 후보)
|
|
123
|
+
TODO
|
|
124
|
+
REF
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# ───── Claude 용 프롬프트 ─────
|
|
128
|
+
# React 계열 스택 (react / nextjs / vite-react) 일 때는
|
|
129
|
+
# skills/generator-frontend/references/_web-react-legacy/ 의 4개 문서를
|
|
130
|
+
# 로컬 seed 로 프롬프트에 포함시킨다. 이 seed 는 새 ref-docs 작성 시
|
|
131
|
+
# "기존 웹 React 가이드" 참조용이며 WebSearch 결과와 병합된다.
|
|
132
|
+
LEGACY_SEED_DIR="$(dirname "$0")/../skills/generator-frontend/references/_web-react-legacy"
|
|
133
|
+
emit_claude_prompt() {
|
|
134
|
+
local role="$1" stack="$2" path="$3"
|
|
135
|
+
local legacy_hint=""
|
|
136
|
+
if [ "$role" = "fe" ] && [ -d "$LEGACY_SEED_DIR" ]; then
|
|
137
|
+
case "$stack" in
|
|
138
|
+
react|nextjs|vite-react|nuxt|vue|svelte|angular|*web*)
|
|
139
|
+
legacy_hint="
|
|
140
|
+
Local seed (병합 대상 · 이미 검증된 웹/React best practice):
|
|
141
|
+
$(ls "$LEGACY_SEED_DIR" 2>/dev/null | sed 's|^| - '"$LEGACY_SEED_DIR"'/|')
|
|
142
|
+
"
|
|
143
|
+
;;
|
|
144
|
+
esac
|
|
145
|
+
fi
|
|
146
|
+
cat <<PROMPT
|
|
147
|
+
───────────────────────────────────────────────
|
|
148
|
+
Claude 세션에 아래 프롬프트를 전달하세요
|
|
149
|
+
(또는 현재 세션이라면 그대로 실행):
|
|
150
|
+
───────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
목표: ${path} 를 생성한다.
|
|
153
|
+
|
|
154
|
+
1. WebSearch 로 다음 쿼리 실행: "${stack} best practices 2025 site:docs.*"
|
|
155
|
+
2. 상위 3개 공식 문서 URL 을 WebFetch 로 가져온다.
|
|
156
|
+
3. 결과를 종합해 다음 스키마로 YAML frontmatter + 본문을 작성한다:
|
|
157
|
+
- docmeta: { id, stack, role, language, generated_at, generator, sources, version }
|
|
158
|
+
- runner: { dev_command, start_command, install_command }
|
|
159
|
+
- paths: { source_roots, test_roots, config_files }
|
|
160
|
+
- api: { base_url, gateway }
|
|
161
|
+
- validation:
|
|
162
|
+
pre_eval_gate: [...]
|
|
163
|
+
functional_tests: [...]
|
|
164
|
+
visual: { enabled, reason, manual_check }
|
|
165
|
+
anti_pattern_rules: [{ id, pattern_type: "grep"|"lint_tool", pattern|tool+args, paths, severity }]
|
|
166
|
+
4. 본문에는 Runner / Paths / Best Practices / Anti-Patterns 섹션 작성.
|
|
167
|
+
5. 완료 후: bash scripts/init-ref-docs.sh --register --stack ${stack} --role ${role} --sources "<url1>,<url2>,<url3>" ${PROJECT_ROOT}
|
|
168
|
+
${legacy_hint}
|
|
169
|
+
───────────────────────────────────────────────
|
|
170
|
+
PROMPT
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# ───── register: 메타 기록 ─────
|
|
174
|
+
register_meta() {
|
|
175
|
+
local role="$1" stack="$2" sources_csv="$3"
|
|
176
|
+
local ts key sources_json
|
|
177
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
178
|
+
key="${role}-${stack}"
|
|
179
|
+
sources_json=$(echo "$sources_csv" | awk -F',' 'BEGIN{printf "["} {for(i=1;i<=NF;i++){printf (i>1?",":"") "\"" $i "\""}} END{printf "]"}')
|
|
180
|
+
[ -z "$sources_csv" ] && sources_json="[]"
|
|
181
|
+
jq --arg k "$key" --arg ts "$ts" --argjson s "$sources_json" '.[$k] = {"generated_at": $ts, "sources": $s, "status": "filled"}' "$META" > "${META}.tmp" && mv "${META}.tmp" "$META"
|
|
182
|
+
echo "registered: $key (sources=$(echo "$sources_json" | jq 'length'))"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# ───── 한 스택 처리 ─────
|
|
186
|
+
process_one() {
|
|
187
|
+
local role="$1" stack="$2"
|
|
188
|
+
local path="${REF_DIR}/${role}-${stack}.md"
|
|
189
|
+
|
|
190
|
+
if [ -f "$path" ] && ! $REFRESH; then
|
|
191
|
+
echo "skip: $path 이미 존재 (--refresh 로 강제 재생성)"
|
|
192
|
+
return 0
|
|
193
|
+
fi
|
|
194
|
+
if [ -f "$path" ] && $REFRESH; then
|
|
195
|
+
local backup="${ARCHIVE_DIR}/ref-${role}-${stack}-$(date +%Y%m%d%H%M%S).md"
|
|
196
|
+
cp "$path" "$backup"
|
|
197
|
+
echo "archived: $backup"
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
local answer="y"
|
|
201
|
+
if ! $YES; then
|
|
202
|
+
printf "Generate ref-docs for %s-%s? [y/N] " "$role" "$stack"
|
|
203
|
+
read -r answer </dev/tty || answer="n"
|
|
204
|
+
fi
|
|
205
|
+
case "$answer" in
|
|
206
|
+
y|Y|yes|YES) ;;
|
|
207
|
+
*) echo "skipped: $role-$stack"; return 3 ;;
|
|
208
|
+
esac
|
|
209
|
+
|
|
210
|
+
case "$MODE" in
|
|
211
|
+
placeholder)
|
|
212
|
+
write_placeholder "$role" "$stack" "$path"
|
|
213
|
+
local ts; ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
214
|
+
jq --arg k "${role}-${stack}" --arg ts "$ts" '.[$k] = {"generated_at": $ts, "sources": [], "status": "placeholder"}' "$META" > "${META}.tmp" && mv "${META}.tmp" "$META"
|
|
215
|
+
echo "wrote: $path (placeholder)"
|
|
216
|
+
;;
|
|
217
|
+
claude-prompt)
|
|
218
|
+
emit_claude_prompt "$role" "$stack" "$path"
|
|
219
|
+
;;
|
|
220
|
+
*)
|
|
221
|
+
echo "ERROR: interactive 모드는 --placeholder 또는 --claude-prompt 중 하나를 지정하세요." >&2
|
|
222
|
+
return 1
|
|
223
|
+
;;
|
|
224
|
+
esac
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# ───── main ─────
|
|
228
|
+
case "$MODE" in
|
|
229
|
+
dry-run)
|
|
230
|
+
echo "detected stacks:"
|
|
231
|
+
detect_stacks | sed 's/^/ - /'
|
|
232
|
+
echo ""
|
|
233
|
+
echo "ref files 예상 경로 (미존재 시 생성 대상):"
|
|
234
|
+
while IFS=':' read -r role stack; do
|
|
235
|
+
path="${REF_DIR}/${role}-${stack}.md"
|
|
236
|
+
if [ -f "$path" ]; then
|
|
237
|
+
echo " [exists] $path"
|
|
238
|
+
else
|
|
239
|
+
echo " [create] $path"
|
|
240
|
+
fi
|
|
241
|
+
done < <(detect_stacks)
|
|
242
|
+
;;
|
|
243
|
+
register)
|
|
244
|
+
[ -z "$STACK" ] || [ -z "$ROLE" ] && { echo "ERROR: --register 는 --stack --role --sources 필요" >&2; exit 1; }
|
|
245
|
+
register_meta "$ROLE" "$STACK" "$SOURCES"
|
|
246
|
+
;;
|
|
247
|
+
placeholder|claude-prompt)
|
|
248
|
+
if [ -n "$STACK" ] && [ -n "$ROLE" ]; then
|
|
249
|
+
process_one "$ROLE" "$STACK"
|
|
250
|
+
else
|
|
251
|
+
# 감지된 전체 스택을 순회
|
|
252
|
+
while IFS=':' read -r role stack; do
|
|
253
|
+
process_one "$role" "$stack"
|
|
254
|
+
done < <(detect_stacks)
|
|
255
|
+
fi
|
|
256
|
+
;;
|
|
257
|
+
interactive)
|
|
258
|
+
echo "ERROR: 모드 지정 필요 — --dry-run | --placeholder | --claude-prompt | --register" >&2
|
|
259
|
+
echo "자세한 사용법: bash $0 --help" >&2
|
|
260
|
+
exit 1
|
|
261
|
+
;;
|
|
262
|
+
esac
|
package/scripts/scan-project.sh
CHANGED
|
@@ -39,6 +39,7 @@ TECH_FRONTEND="unknown"
|
|
|
39
39
|
TECH_DB="unknown"
|
|
40
40
|
TECH_MONOREPO="none"
|
|
41
41
|
TECH_LANG="unknown"
|
|
42
|
+
IS_NATIVE_APP=false
|
|
42
43
|
|
|
43
44
|
# Backend
|
|
44
45
|
if [ -f "${PROJECT_ROOT}/nest-cli.json" ]; then
|
|
@@ -130,6 +131,42 @@ if [ "$FE_STACK" = "flutter" ] && [ -n "$FLUTTER_ROOT" ]; then
|
|
|
130
131
|
fi
|
|
131
132
|
fi
|
|
132
133
|
|
|
134
|
+
# Swift (macOS / iOS 네이티브 앱) 감지 — Flutter 감지 이후
|
|
135
|
+
if [ "$TECH_FRONTEND" = "unknown" ]; then
|
|
136
|
+
SWIFT_DETECTED=false
|
|
137
|
+
if [ -f "${PROJECT_ROOT}/Package.swift" ]; then
|
|
138
|
+
SWIFT_DETECTED=true
|
|
139
|
+
fi
|
|
140
|
+
if ! $SWIFT_DETECTED; then
|
|
141
|
+
for f in "${PROJECT_ROOT}"/*.xcodeproj "${PROJECT_ROOT}"/*.xcworkspace; do
|
|
142
|
+
if [ -e "$f" ]; then
|
|
143
|
+
SWIFT_DETECTED=true
|
|
144
|
+
break
|
|
145
|
+
fi
|
|
146
|
+
done
|
|
147
|
+
fi
|
|
148
|
+
if ! $SWIFT_DETECTED && [ -f "${PROJECT_ROOT}/Podfile" ]; then
|
|
149
|
+
SWIFT_DETECTED=true
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
if $SWIFT_DETECTED; then
|
|
153
|
+
TECH_LANG="swift"
|
|
154
|
+
IS_NATIVE_APP=true
|
|
155
|
+
FE_STACK="swift" # FE_STACK 기본값(react) 을 Swift 로 치환
|
|
156
|
+
FE_TARGET="native" # web/mobile/desktop 대신 native
|
|
157
|
+
# 서브타입 판별 — NSStatusBar 가 가장 특화적이므로 우선
|
|
158
|
+
if grep -rq "NSStatusBar.system" "${PROJECT_ROOT}" --include="*.swift" 2>/dev/null; then
|
|
159
|
+
TECH_FRONTEND="swift-macos-statusbar"
|
|
160
|
+
elif grep -rq "import SwiftUI" "${PROJECT_ROOT}" --include="*.swift" 2>/dev/null; then
|
|
161
|
+
TECH_FRONTEND="swift-swiftui"
|
|
162
|
+
elif grep -rq "import UIKit" "${PROJECT_ROOT}" --include="*.swift" 2>/dev/null; then
|
|
163
|
+
TECH_FRONTEND="swift-uikit"
|
|
164
|
+
else
|
|
165
|
+
TECH_FRONTEND="swift"
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
|
|
133
170
|
# Database
|
|
134
171
|
if grep -rq "typeorm\|prisma\|sequelize\|knex" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
|
|
135
172
|
if grep -q "pg\|postgres" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
|
|
@@ -257,9 +294,18 @@ cat > "$OUTPUT" << JSONEOF
|
|
|
257
294
|
"fe_target": "${FE_TARGET}",
|
|
258
295
|
"database": "${TECH_DB}",
|
|
259
296
|
"monorepo": "${TECH_MONOREPO}",
|
|
260
|
-
"language": "${TECH_LANG}"
|
|
297
|
+
"language": "${TECH_LANG}",
|
|
298
|
+
"is_native_app": ${IS_NATIVE_APP}
|
|
261
299
|
},
|
|
262
300
|
|
|
301
|
+
"tech_stack_confidence": "$(
|
|
302
|
+
if [ "$TECH_BACKEND" = "unknown" ] && [ "$TECH_FRONTEND" = "unknown" ]; then
|
|
303
|
+
echo "unknown"
|
|
304
|
+
else
|
|
305
|
+
echo "detected"
|
|
306
|
+
fi
|
|
307
|
+
)",
|
|
308
|
+
|
|
263
309
|
"structure": {
|
|
264
310
|
"directories": ${TREE_JSON},
|
|
265
311
|
"config_files": ${CONFIG_FILES}
|
|
@@ -312,7 +358,7 @@ echo "=== Scan Complete ==="
|
|
|
312
358
|
echo "Output: ${OUTPUT}"
|
|
313
359
|
echo ""
|
|
314
360
|
echo "--- Summary ---"
|
|
315
|
-
echo "Tech Stack: ${TECH_BACKEND} / ${TECH_FRONTEND} (fe_stack=${FE_STACK}, fe_target=${FE_TARGET}) / ${TECH_DB}"
|
|
361
|
+
echo "Tech Stack: ${TECH_BACKEND} / ${TECH_FRONTEND} (fe_stack=${FE_STACK}, fe_target=${FE_TARGET}, native=${IS_NATIVE_APP}) / ${TECH_DB}"
|
|
316
362
|
echo "Monorepo: ${TECH_MONOREPO}"
|
|
317
363
|
echo "OpenAPI: ${OPENAPI}"
|
|
318
364
|
echo "Git: ${GIT_INIT} (${GIT_COMMITS} commits, branch: ${GIT_BRANCH})"
|
|
@@ -113,13 +113,11 @@ AGENTS.md 비하네스 → 기존 백업 + 리빌드
|
|
|
113
113
|
|
|
114
114
|
### fe_stack 필드 (FE 파이프라인에서 필수)
|
|
115
115
|
|
|
116
|
-
FE-ONLY 또는 FULLSTACK 선택 시, `pipeline.json
|
|
116
|
+
FE-ONLY 또는 FULLSTACK 선택 시, `pipeline.json` 에 **`fe_stack`** 필드를 포함해야 한다:
|
|
117
117
|
|
|
118
|
-
- `scan-result.json.tech_stack.fe_stack` 값을 기본으로 사용
|
|
119
|
-
- 값이 없거나 불명확하면 Planner가 확정하도록 위임 (Dispatcher는 `"unknown"` 기록 + `notes` 에 메모)
|
|
120
|
-
-
|
|
121
|
-
- active: `generator-frontend-flutter`, `evaluator-functional-flutter`
|
|
122
|
-
- skipped: `generator-frontend`, `evaluator-functional`, `evaluator-visual`
|
|
118
|
+
- `scan-result.json.tech_stack.fe_stack` (또는 `tech_stack.frontend`) 값을 기본으로 사용
|
|
119
|
+
- 값이 없거나 불명확하면 Planner 가 확정하도록 위임 (Dispatcher 는 `"unknown"` 기록 + `notes` 에 메모)
|
|
120
|
+
- v5.2 이후: **에이전트 분기 없음** — 동일한 `generator-frontend` / `evaluator-functional` 에이전트가 `.harness/ref/fe-<fe_stack>.md` 를 로드해 스택 적응적으로 동작
|
|
123
121
|
|
|
124
122
|
## 6. Brainstormer Routing Decision
|
|
125
123
|
|
|
@@ -174,7 +172,7 @@ Planner 를 호출해야 한다고 판단되면, **사용자에게 단 하나의
|
|
|
174
172
|
| 상황 | next_agent |
|
|
175
173
|
|------|-----------|
|
|
176
174
|
| "Eval, X 다시 검증해" | `evaluator-functional` (또는 `evaluator-visual`) |
|
|
177
|
-
| "Generator-FE, Y 버그 고쳐" | `generator-frontend`
|
|
175
|
+
| "Generator-FE, Y 버그 고쳐" | `generator-frontend` |
|
|
178
176
|
| "Generator-BE, API 재생성해" | `generator-backend` |
|
|
179
177
|
| Eval FAIL → retry | `failure.retry_target` |
|
|
180
178
|
| Gotcha 수정 | `failure.retry_target` 또는 현재 에이전트 |
|
|
@@ -192,14 +190,48 @@ Planner 를 호출해야 한다고 판단되면, **사용자에게 단 하나의
|
|
|
192
190
|
이 경우 기존 `.harness/actions/brainstorm-spec.md` 는 Brainstormer 의 On Start 에서
|
|
193
191
|
`.harness/archive/brainstorm-spec-<timestamp>.md` 로 백업된다.
|
|
194
192
|
|
|
195
|
-
## 7. Handoff 라우팅 (
|
|
193
|
+
## 7. Handoff 라우팅 (v5.2 — 스택 치환 없음)
|
|
196
194
|
|
|
197
|
-
Dispatcher가 `next_agent` 를 세팅할 때 pipeline.json.fe_stack
|
|
195
|
+
v5.2 이후 에이전트 치환은 사용하지 않는다. Dispatcher 가 `next_agent` 를 세팅할 때 `pipeline.json.fe_stack` 은 **참고용 메타데이터**로만 기록되고, 실제 스택별 행동은 에이전트가 자기 On Start 에서 `.harness/ref/<role>-<stack>.md` 를 로드해 결정한다.
|
|
198
196
|
|
|
199
|
-
|
|
|
200
|
-
|
|
201
|
-
| generator-frontend |
|
|
202
|
-
|
|
|
203
|
-
| evaluator-
|
|
197
|
+
| 에이전트 | 스택 적응 메커니즘 |
|
|
198
|
+
|---------|------------------|
|
|
199
|
+
| `generator-frontend` | On Start 에서 `.harness/ref/fe-<stack>.md` 로드 |
|
|
200
|
+
| `generator-backend` | On Start 에서 `.harness/ref/be-<stack>.md` 로드 |
|
|
201
|
+
| `evaluator-functional` | `ref.validation.pre_eval_gate` / `functional_tests` / `anti_pattern_rules` 실행 |
|
|
202
|
+
| `evaluator-visual` | `ref.validation.visual.enabled == false` 면 MANUAL_REQUIRED 로 skip |
|
|
204
203
|
|
|
205
|
-
|
|
204
|
+
## 8. Auto Gotcha Registration (v5.2)
|
|
205
|
+
|
|
206
|
+
evaluator-functional / evaluator-visual 이 안티패턴을 발견하면 Dispatcher 경유로 자동 gotcha 파일에 등록한다. 이 섹션은 Dispatcher 가 등록 이벤트를 받았을 때의 행동을 정의한다.
|
|
207
|
+
|
|
208
|
+
### 8.1 수신 이벤트 페이로드
|
|
209
|
+
|
|
210
|
+
`api-contract.json.contracts["gotcha_register_interface"]` 참조. 필수 필드:
|
|
211
|
+
- `agent` (예: `"generator-frontend"`)
|
|
212
|
+
- `stack` (예: `"swift"`)
|
|
213
|
+
- `rule_id`, `severity`, `occurrences[] (file/line/snippet)`, `source_feature`
|
|
214
|
+
|
|
215
|
+
### 8.2 대상 파일 결정
|
|
216
|
+
|
|
217
|
+
`.harness/gotchas/<agent>-<stack>.md` (없으면 생성). 스택 특정 규칙이 아닌 공통 규칙은 `<agent>.md` 로 라우팅 (gotcha-flow.md 의 "라우팅 규칙" 참조).
|
|
218
|
+
|
|
219
|
+
### 8.3 등록 절차
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
1. 대상 파일 열기 (없으면 표준 헤더로 생성)
|
|
223
|
+
2. 기존 항목 중 같은 rule_id 검색:
|
|
224
|
+
- 있음 → Occurrences +1, Last seen 업데이트, snippet 최신으로 교체
|
|
225
|
+
- 없음 → 다음 G-NNN 번호로 새 항목 추가
|
|
226
|
+
3. progress.log 에 `"auto_gotcha_registered"` 이벤트 기록
|
|
227
|
+
4. evaluation-*.md 에 "Registered gotchas: <G-IDs>" 요약 기록
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
항목 포맷은 gotcha-flow.md 의 "Gotcha 항목 형식" 섹션과 동일.
|
|
231
|
+
|
|
232
|
+
### 8.4 멱등성 / 중복 방지
|
|
233
|
+
|
|
234
|
+
- 동일 feature 의 동일 rule_id 는 한 번의 평가 안에서 최대 1회만 Occurrences 증가
|
|
235
|
+
- Retry 시에는 이전 Occurrences 유지 (재평가이므로 중복 카운팅 금지)
|
|
236
|
+
|
|
237
|
+
**Brainstormer 는 스택 치환 대상이 아니다** — 언어/스택 무관 공통 에이전트.
|
|
@@ -24,9 +24,36 @@
|
|
|
24
24
|
| 설계, 아키텍처, 기획, 기능 목록, IA, 서비스 분할 | `planner` |
|
|
25
25
|
| 불명확 | 사용자에게 질문 |
|
|
26
26
|
|
|
27
|
+
## 스택별 파일 네이밍 규칙 (v5.2 — 적응형 하네스)
|
|
28
|
+
|
|
29
|
+
에이전트별로 **공통 파일 + 스택별 파일** 2트랙:
|
|
30
|
+
|
|
31
|
+
| 파일 | 용도 |
|
|
32
|
+
|------|------|
|
|
33
|
+
| `.harness/gotchas/<agent>.md` | 스택 무관 공통 실수 (예: "PASS 남발", "Evidence 없는 Score") |
|
|
34
|
+
| `.harness/gotchas/<agent>-<stack>.md` | 특정 스택에서만 적용되는 실수 (예: `generator-frontend-swift.md` — force unwrap 금지) |
|
|
35
|
+
|
|
36
|
+
### 라우팅 규칙
|
|
37
|
+
|
|
38
|
+
교정 시그널을 기록할 때:
|
|
39
|
+
1. `scan-result.json.tech_stack.fe_stack` / `be_stack` 조회
|
|
40
|
+
2. 시그널 내용이 스택 특정 기술(import, API, 프레임워크 함수명 등)을 언급 → `<agent>-<stack>.md`
|
|
41
|
+
3. 스택 무관 일반 규칙(문서화, 테스트 태도, 평가 기준) → `<agent>.md` (공통)
|
|
42
|
+
4. 판단 애매 → **공통 파일 우선** (후속 발생 시 스택별로 이관)
|
|
43
|
+
|
|
44
|
+
### 에이전트 On Start 로딩
|
|
45
|
+
|
|
46
|
+
적응형 에이전트는 세션 시작 시 **두 파일을 모두 로드**:
|
|
47
|
+
```
|
|
48
|
+
.harness/gotchas/<agent>.md # 공통
|
|
49
|
+
.harness/gotchas/<agent>-<current_stack>.md # 스택별 (없으면 skip)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
27
54
|
## Gotcha 항목 형식
|
|
28
55
|
|
|
29
|
-
`.harness/gotchas/[agent-name].md
|
|
56
|
+
`.harness/gotchas/[agent-name].md` 또는 `<agent-name>-<stack>.md` 에 추가:
|
|
30
57
|
|
|
31
58
|
```markdown
|
|
32
59
|
### [G-NNN] 간결한 제목
|
|
@@ -86,6 +86,38 @@ FEEDBACK: one paragraph summary
|
|
|
86
86
|
---END-EVAL-RESULT---
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
## Stack-Adaptive Validation (v5.2)
|
|
90
|
+
|
|
91
|
+
Evaluator 는 스택마다 다른 검증 도구를 가진다. `scan-result.json.tech_stack` 에서 현재 스택을 확인한 뒤 `.harness/ref/<role>-<stack>.md` 의 `validation` 블록을 로드해 순차 실행한다.
|
|
92
|
+
|
|
93
|
+
### Validation 블록 파싱
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
1. ref-docs YAML frontmatter 파싱 → validation 객체 추출
|
|
97
|
+
2. validation.pre_eval_gate 의 모든 명령을 순차 실행
|
|
98
|
+
- 실패 시 → FAIL + generator 로 retry (Pre-Eval Gate)
|
|
99
|
+
3. validation.functional_tests 의 모든 명령을 순차 실행
|
|
100
|
+
- 실패 시 → FAIL 항목 기록
|
|
101
|
+
4. validation.anti_pattern_rules 순회:
|
|
102
|
+
- pattern_type == "grep": `grep -rE "<pattern>" <paths>` 로 스캔
|
|
103
|
+
- pattern_type == "lint_tool": `<tool> <args>` 로 호출 + JSON 출력 파싱
|
|
104
|
+
- 위반 발견 시 → Auto Gotcha Registration (아래)
|
|
105
|
+
5. validation.visual.enabled:
|
|
106
|
+
- true → evaluator-visual 에 Playwright 검증 위임
|
|
107
|
+
- false → evaluation-functional.md 에 "MANUAL_REQUIRED: {manual_check}" 기록, Visual 은 __skip__
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Auto Gotcha Registration (안티패턴 자동 등록)
|
|
111
|
+
|
|
112
|
+
`validation.anti_pattern_rules` 실행에서 위반 1건 이상 발견 시 — Dispatcher 경유로 자동 gotcha 등록:
|
|
113
|
+
|
|
114
|
+
- 대상 파일: `.harness/gotchas/generator-<role>-<stack>.md` (없으면 생성)
|
|
115
|
+
- 항목 포맷: `### [G-NNN] <rule_id>` / severity / occurrences / last_seen(file:line) / snippet / source feature
|
|
116
|
+
- 중복 rule_id: Occurrences 카운터 +1 + last_seen 업데이트
|
|
117
|
+
- 상세 계약: `api-contract.json.contracts["gotcha_register_interface"]`
|
|
118
|
+
|
|
119
|
+
이 메커니즘이 작동하려면 Dispatcher 의 "Auto Gotcha Registration" 섹션을 참고하라.
|
|
120
|
+
|
|
89
121
|
## Evaluation Steps
|
|
90
122
|
|
|
91
123
|
### Step 0: IA Structure Compliance (GATE)
|
|
@@ -49,7 +49,25 @@ disable-model-invocation: true
|
|
|
49
49
|
2. `.harness/gotchas/evaluator-visual.md` 읽기 — **과거 실수 반복 금지**
|
|
50
50
|
3. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
|
|
51
51
|
4. `actions/evaluation-functional.md` — Verdict: PASS 확인
|
|
52
|
-
5.
|
|
52
|
+
5. **Stack-Adaptive Gate** (v5.2) — `scan-result.json.tech_stack` 으로 스택 확인 후 `.harness/ref/fe-<stack>.md` 의 `validation.visual` 파싱:
|
|
53
|
+
- `visual.enabled == false`: 즉시 **MANUAL_REQUIRED 모드** 로 전환 — 아래 "Visual Skip Flow" 수행 후 종료
|
|
54
|
+
- `visual.enabled == true` (또는 ref-docs 없이 웹 전통 스택): 계속 진행, ref 에 `visual.base_url` 이 있으면 그 URL 로, 없으면 `ref.runner.dev_command` 로 서버 기동 후 Playwright 접속
|
|
55
|
+
|
|
56
|
+
## Visual Skip Flow (네이티브 앱 / 비-브라우저 스택)
|
|
57
|
+
|
|
58
|
+
`validation.visual.enabled == false` (예: Swift macOS, Flutter mobile, CLI 앱) 인 경우:
|
|
59
|
+
|
|
60
|
+
1. Playwright 스크린샷·axe-core·AI슬롭 감지 **전부 skip**
|
|
61
|
+
2. `.harness/actions/evaluation-visual.md` 에 다음을 기록:
|
|
62
|
+
```
|
|
63
|
+
VERDICT: MANUAL_REQUIRED
|
|
64
|
+
STACK: <stack>
|
|
65
|
+
REASON: {ref.validation.visual.reason}
|
|
66
|
+
MANUAL_CHECK: {ref.validation.visual.manual_check}
|
|
67
|
+
```
|
|
68
|
+
3. progress.json: `agent_status = "completed"`, `next_agent = "archive"` (PASS 경로와 동일 라우팅)
|
|
69
|
+
4. `progress.log` 에 `"visual skipped (manual required)"` 이벤트 기록
|
|
70
|
+
5. 사용자에게 `manual_check` 문자열 출력 + 확인 요청
|
|
53
71
|
|
|
54
72
|
## Evaluation Steps
|
|
55
73
|
|
|
@@ -1,62 +1,98 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-generator-backend
|
|
3
|
-
description: "하네스 Backend Generator.
|
|
3
|
+
description: "하네스 Backend Generator. 스택 독립(adaptive) — scan-result.json 의 be_stack / language 에 따라 .harness/ref/be-<stack>.md 를 로드해 runner/paths/api/validation 을 따른다. 모든 BE 스택(FastAPI / Django / Go / Rails / Phoenix / Spring / Express 등) 대응."
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Generator-Backend —
|
|
7
|
+
# Generator-Backend — Adaptive (Stack-Agnostic)
|
|
8
8
|
|
|
9
9
|
## Session Boundary Protocol
|
|
10
10
|
|
|
11
11
|
### On Start
|
|
12
|
-
1. `.harness/progress.json` 읽기 — `next_agent
|
|
12
|
+
1. `.harness/progress.json` 읽기 — `next_agent` 가 `"generator-backend"` 인지 확인
|
|
13
13
|
2. progress.json 업데이트: `current_agent` → `"generator-backend"`, `agent_status` → `"running"`, `updated_at` 갱신
|
|
14
|
-
3. `failure` 필드 확인 — retry인 경우
|
|
14
|
+
3. `failure` 필드 확인 — retry 인 경우 평가 문서의 실패 사유 우선 읽기
|
|
15
15
|
|
|
16
16
|
### On Complete
|
|
17
17
|
1. progress.json 업데이트:
|
|
18
18
|
- `agent_status` → `"completed"`
|
|
19
|
-
- `completed_agents
|
|
20
|
-
- `next_agent` → 파이프라인에 따라
|
|
19
|
+
- `completed_agents` 에 `"generator-backend"` 추가
|
|
20
|
+
- `next_agent` → 파이프라인에 따라 (`"generator-frontend"` 또는 `"evaluator-functional"`)
|
|
21
21
|
- `failure` 필드 초기화 (retry 성공 시)
|
|
22
|
-
2. `feature-list.json
|
|
23
|
-
3. `.harness/progress.log
|
|
22
|
+
2. `feature-list.json` 의 해당 feature `passes` 에 `"generator-backend"` 추가
|
|
23
|
+
3. `.harness/progress.log` 에 요약 추가
|
|
24
24
|
4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
25
25
|
5. 출력: `"✓ Generator-Backend 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
|
|
26
26
|
|
|
27
|
-
## Startup
|
|
27
|
+
## Startup (Adaptive Loading)
|
|
28
28
|
|
|
29
29
|
1. `AGENTS.md` 읽기 — IA-MAP, 권한 확인
|
|
30
|
-
2. `.harness/
|
|
31
|
-
3. `.harness/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
2. `.harness/actions/scan-result.json` 읽기 → `tech_stack.backend` 또는 `tech_stack.language` 로 현재 스택 확정 (이하 `<stack>`)
|
|
31
|
+
3. **Ref-docs 로드** — `.harness/ref/be-<stack>.md`
|
|
32
|
+
- 파일 없음 → STOP + 안내: `"ref-docs 가 없습니다. bash init.sh init 실행 또는 bash scripts/init-ref-docs.sh --claude-prompt --stack <stack> --role be . 실행하세요."`
|
|
33
|
+
- frontmatter 파싱 실패 → 경고 출력 + 기본값으로 degrade
|
|
34
|
+
4. **Gotchas 로드** — 두 파일 모두 (있는 것만):
|
|
35
|
+
- `.harness/gotchas/generator-backend.md` (공통)
|
|
36
|
+
- `.harness/gotchas/generator-backend-<stack>.md` (스택별)
|
|
37
|
+
5. `.harness/memory.md` 읽기 — 프로젝트 공유 학습 규칙
|
|
38
|
+
6. `pwd` + `.harness/progress.json` + `git log --oneline -20`
|
|
39
|
+
7. `.harness/actions/api-contract.json` 읽기 — **이 계약이 유일한 BE 외부 인터페이스**
|
|
40
|
+
8. `.harness/actions/feature-list.json` — 지정된 `FEATURE_ID` 또는 `layer: "backend"` 필터
|
|
41
|
+
9. **DB / 외부 의존성 부트스트랩**:
|
|
42
|
+
- `ref.runner.install_command` 가 있으면 1회 실행
|
|
43
|
+
- `ref.runner.dev_command` 를 백그라운드 실행 (있는 경우)
|
|
44
|
+
|
|
45
|
+
## Feature-Level Mode (Team Mode)
|
|
46
|
+
|
|
47
|
+
Team Worker 가 호출할 때 프롬프트에 `FEATURE_ID` 가 지정된다. Feature branch 에서 단일 feature 만 구현.
|
|
37
48
|
|
|
38
49
|
## AGENTS.md — 읽기 전용
|
|
39
50
|
|
|
40
|
-
`[BE]` + `→ Generator-Backend` 소유 경로만 쓰기 가능.
|
|
51
|
+
`[BE]` + `→ Generator-Backend` 소유 경로만 쓰기 가능. 스택별 실제 소스 경로는 `ref.paths.source_roots` 가 권위 있는 출처 (예: FastAPI `app/`, Go `cmd/` + `internal/`, Rails `app/`).
|
|
41
52
|
|
|
42
53
|
## Sprint Workflow
|
|
43
54
|
|
|
44
|
-
1. **Sprint Contract BE 섹션
|
|
45
|
-
2. **구현** —
|
|
46
|
-
3. **Self-Verification** —
|
|
47
|
-
4. **Handoff** → Generator-Frontend
|
|
55
|
+
1. **Sprint Contract BE 섹션 추가** — 엔드포인트 / DB 스키마 / 서비스 분할 / 성공 기준
|
|
56
|
+
2. **구현** — api-contract.json ↔ 해당 스택 타입 시스템 1:1 매핑
|
|
57
|
+
3. **Self-Verification** — `ref.validation.pre_eval_gate` 의 모든 명령 실행
|
|
58
|
+
4. **Handoff** → Evaluator-Functional (또는 Generator-Frontend, 파이프라인에 따라)
|
|
59
|
+
|
|
60
|
+
## 스택 치환 규칙 (Adaptive Core)
|
|
61
|
+
|
|
62
|
+
구현 시 **모든 스택 의존 값은 ref-docs 에서 치환**한다:
|
|
63
|
+
|
|
64
|
+
| 치환 키 | 출처 | 예시 |
|
|
65
|
+
|---------|------|------|
|
|
66
|
+
| `<source_roots>` | `ref.paths.source_roots` | FastAPI: `app/` · Go: `cmd/`, `internal/` · Rails: `app/` |
|
|
67
|
+
| `<test_roots>` | `ref.paths.test_roots` | FastAPI: `tests/` · Go: `_test.go` 동거 · Rails: `spec/` |
|
|
68
|
+
| `<dev_command>` | `ref.runner.dev_command` | FastAPI: `uvicorn app.main:app` · Go: `go run ./cmd/server` |
|
|
69
|
+
| `<api_base_url>` | `ref.api.base_url` | 로컬 개발 baseurl (ref-docs 참조) |
|
|
70
|
+
| `<pre_eval_gate>` | `ref.validation.pre_eval_gate` | FastAPI: `[ruff, mypy, pytest]` · Go: `[go vet, go test ./...]` |
|
|
71
|
+
| `<anti_patterns>` | `ref.validation.anti_pattern_rules` | 스택별 grep/lint 규칙 |
|
|
72
|
+
|
|
73
|
+
api-contract.json 의 DTO 스키마는 해당 스택의 타입 표현(Pydantic / struct / class-validator / ActiveRecord 등)으로 직접 매핑한다.
|
|
74
|
+
|
|
75
|
+
## 서비스 간 통신 규칙
|
|
76
|
+
|
|
77
|
+
- MSA 환경이면 서비스 간 **직접 DB 접근 금지** (메시지 큐 / 이벤트 / RPC 만)
|
|
78
|
+
- 모놀리스 환경이면 modules/packages 경계 준수
|
|
79
|
+
- 어느 쪽인지 `ref.paths.source_roots` 구조와 `ref` 본문의 "Architecture" 섹션으로 판단
|
|
80
|
+
|
|
81
|
+
## 핵심 규칙 (스택 무관)
|
|
48
82
|
|
|
49
|
-
|
|
50
|
-
|
|
83
|
+
- api-contract.json 에 없는 엔드포인트 **구현 금지**
|
|
84
|
+
- 로깅 / 에러 핸들링 / 트랜잭션 경계 필수
|
|
85
|
+
- 보안: OWASP Top 10 (인증·인가·입력 검증·SQL Injection 등) 기본 준수
|
|
86
|
+
- 테스트: `ref.validation.functional_tests` 에 나열된 명령이 전부 통과해야 PASS
|
|
51
87
|
|
|
52
88
|
## 금지 사항
|
|
53
89
|
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
- AGENTS.md 수정
|
|
90
|
+
- **ref.paths.source_roots 밖의 프로덕션 코드 수정** (FE/HARNESS 영역 침범 금지)
|
|
91
|
+
- api-contract.json 에 없는 엔드포인트 신설
|
|
92
|
+
- `.harness/ref/` 직접 편집 (refresh 는 `bash init.sh refresh-ref` 경유)
|
|
93
|
+
- 공통 `generator-backend.md` / 스택별 `generator-backend-<stack>.md` gotcha 에 적힌 실수 반복
|
|
59
94
|
|
|
60
|
-
##
|
|
95
|
+
## 디버깅 / Fallback
|
|
61
96
|
|
|
62
|
-
`
|
|
97
|
+
- `ref-docs` 가 placeholder 상태 → 본격 구현 전에 `bash init.sh refresh-ref be <stack>` 으로 채우기 권고
|
|
98
|
+
- `scan-result.json.tech_stack_confidence == "unknown"` → 사용자에게 객관식 + 자유입력 fallback 로 스택 확인 요청
|
|
@@ -1,95 +1,102 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-generator-frontend
|
|
3
|
-
description: "하네스 Frontend Generator.
|
|
3
|
+
description: "하네스 Frontend Generator. 스택 독립(adaptive) — scan-result.json 의 fe_stack 에 따라 .harness/ref/fe-<stack>.md 를 로드해 해당 스택의 runner/paths/api/validation 을 따른다. 모든 FE 스택(Swift / Flutter / Vue / Svelte / Angular / 웹 SSR 등) 대응."
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Generator-Frontend —
|
|
7
|
+
# Generator-Frontend — Adaptive (Stack-Agnostic)
|
|
8
8
|
|
|
9
9
|
## Session Boundary Protocol
|
|
10
10
|
|
|
11
11
|
### On Start
|
|
12
|
-
1. `.harness/progress.json` 읽기 — `next_agent
|
|
12
|
+
1. `.harness/progress.json` 읽기 — `next_agent` 가 `"generator-frontend"` 인지 확인
|
|
13
13
|
2. progress.json 업데이트: `current_agent` → `"generator-frontend"`, `agent_status` → `"running"`, `updated_at` 갱신
|
|
14
|
-
3. `failure` 필드 확인 — retry인 경우 평가 문서의 실패 사유 우선 읽기
|
|
14
|
+
3. `failure` 필드 확인 — retry 인 경우 평가 문서의 실패 사유 우선 읽기
|
|
15
15
|
|
|
16
16
|
### On Complete
|
|
17
17
|
1. progress.json 업데이트:
|
|
18
18
|
- `agent_status` → `"completed"`
|
|
19
|
-
- `completed_agents
|
|
19
|
+
- `completed_agents` 에 `"generator-frontend"` 추가
|
|
20
20
|
- `next_agent` → `"evaluator-functional"`
|
|
21
21
|
- `failure` 필드 초기화 (retry 성공 시)
|
|
22
|
-
2. `feature-list.json
|
|
23
|
-
3. `.harness/progress.log
|
|
22
|
+
2. `feature-list.json` 의 해당 feature `passes` 에 `"generator-frontend"` 추가
|
|
23
|
+
3. `.harness/progress.log` 에 요약 추가
|
|
24
24
|
4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
|
|
25
25
|
5. 출력: `"✓ Generator-Frontend 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
|
|
26
26
|
|
|
27
|
-
## Startup
|
|
27
|
+
## Startup (Adaptive Loading)
|
|
28
28
|
|
|
29
29
|
1. `AGENTS.md` 읽기 — IA-MAP, 권한 확인
|
|
30
|
-
2. `.harness/
|
|
31
|
-
3. `.harness/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
**
|
|
30
|
+
2. `.harness/actions/scan-result.json` 읽기 → `tech_stack.fe_stack` 또는 `tech_stack.frontend` 로 현재 스택 확정 (이하 `<stack>`)
|
|
31
|
+
3. **Ref-docs 로드** — `.harness/ref/fe-<stack>.md`
|
|
32
|
+
- 파일 없음 → STOP + 안내: `"ref-docs 가 없습니다. bash init.sh init 실행 또는 bash scripts/init-ref-docs.sh --claude-prompt --stack <stack> --role fe . 실행하세요."`
|
|
33
|
+
- frontmatter 파싱 실패 → 경고 출력 + 기본값(runner/paths/api 모두 null)으로 degrade
|
|
34
|
+
4. **Gotchas 로드** — 두 파일 모두 (있는 것만):
|
|
35
|
+
- `.harness/gotchas/generator-frontend.md` (공통)
|
|
36
|
+
- `.harness/gotchas/generator-frontend-<stack>.md` (스택별)
|
|
37
|
+
5. `.harness/memory.md` 읽기 — 프로젝트 공유 학습 규칙
|
|
38
|
+
6. `pwd` + `.harness/progress.json` + `git log --oneline -20`
|
|
39
|
+
7. `.harness/actions/api-contract.json` 읽기
|
|
40
|
+
8. `.harness/actions/feature-list.json` — 지정된 `FEATURE_ID` 또는 `layer: "frontend"` 필터
|
|
41
|
+
9. **개발 서버 기동**:
|
|
42
|
+
- `ref.runner.dev_command` 가 `null` 이 아니면 해당 명령 백그라운드 실행
|
|
43
|
+
- `null` 이면 "개발 서버 기동은 스택 특성상 생략" 로그만 남김
|
|
44
|
+
10. **API Gateway 체크**:
|
|
45
|
+
- `ref.api.base_url` 이 `null` 이 아니면 `curl -s <base_url>/health` 로 헬스체크
|
|
46
|
+
- `null` (네이티브 앱 등) 이면 체크 스킵
|
|
45
47
|
|
|
46
48
|
## Feature-Level Mode (Team Mode)
|
|
47
49
|
|
|
48
|
-
Team Mode에서 Team Worker가 호출할 때, 프롬프트에 `FEATURE_ID
|
|
50
|
+
Team Mode 에서 Team Worker 가 호출할 때, 프롬프트에 `FEATURE_ID` 가 지정된다.
|
|
49
51
|
|
|
50
52
|
### Feature-Level Rules
|
|
51
|
-
- `feature-list.json
|
|
52
|
-
- 다른 Feature의 코드를 수정하지 않음
|
|
53
|
-
- `depends_on
|
|
54
|
-
- Feature branch (`feature/F-XXX`)에서 작업, 완료 시 commit
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- `depends_on` (이미 완료된 의존 Feature 목록)
|
|
61
|
-
- Eval 재시도 시: 이전 Eval의 피드백
|
|
53
|
+
- `feature-list.json` 에서 **지정된 FEATURE_ID 만** 필터하여 구현
|
|
54
|
+
- 다른 Feature 의 코드를 수정하지 않음
|
|
55
|
+
- `depends_on` 에 명시된 Feature 는 이미 구현/머지 완료 상태
|
|
56
|
+
- Feature branch (`feature/F-XXX`) 에서 작업, 완료 시 commit
|
|
57
|
+
|
|
58
|
+
## AGENTS.md — 읽기 전용
|
|
59
|
+
|
|
60
|
+
`[FE]` + `→ Generator-Frontend` 소유 경로만 쓰기 가능.
|
|
61
|
+
스택별 실제 소스 경로는 `ref.paths.source_roots` 를 권위 있는 출처로 삼는다 (예: Swift `Sources/`, Flutter `lib/`, Vue `src/`).
|
|
62
62
|
|
|
63
63
|
## Sprint Workflow
|
|
64
64
|
|
|
65
|
-
1. **Sprint Contract FE 섹션 추가** —
|
|
66
|
-
2. **구현** — 아래
|
|
67
|
-
3. **Self-Verification** —
|
|
65
|
+
1. **Sprint Contract FE 섹션 추가** — 컴포넌트 / API 연동 / 성공 기준
|
|
66
|
+
2. **구현** — 아래 "스택 치환 규칙" 엄수
|
|
67
|
+
3. **Self-Verification** — `ref.validation.pre_eval_gate` 에 나열된 명령 전부 실행
|
|
68
68
|
4. **Handoff** → Evaluator-Functional
|
|
69
69
|
|
|
70
|
-
##
|
|
70
|
+
## 스택 치환 규칙 (Adaptive Core)
|
|
71
|
+
|
|
72
|
+
구현 시 **모든 스택 의존 값은 ref-docs 에서 치환**한다:
|
|
71
73
|
|
|
72
|
-
|
|
|
73
|
-
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
|
77
|
-
|
|
|
74
|
+
| 치환 키 | 출처 | 예시 값 |
|
|
75
|
+
|---------|------|---------|
|
|
76
|
+
| `<source_roots>` | `ref.paths.source_roots` | Swift: `Sources/` · Flutter: `lib/` · Vue: `src/` |
|
|
77
|
+
| `<test_roots>` | `ref.paths.test_roots` | Swift: `Tests/` · Flutter: `test/` · 일반 웹: `tests/` |
|
|
78
|
+
| `<dev_command>` | `ref.runner.dev_command` | Swift: `xcodebuild -scheme X build` · 기타: ref-docs 참조 |
|
|
79
|
+
| `<api_base_url>` | `ref.api.base_url` | 네이티브 앱: `null` (무시) · 웹: ref-docs 참조 |
|
|
80
|
+
| `<pre_eval_gate>` | `ref.validation.pre_eval_gate` | Swift: `[swift build, swiftlint]` |
|
|
81
|
+
| `<anti_patterns>` | `ref.validation.anti_pattern_rules` | 스택별 grep/lint 규칙 |
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
코드 생성 시 특정 프레임워크 전용 지시(예: "컴포넌트를 X 스타일로 만들어라")는 하지 않는다. 대신 ref-docs 본문의 "Best Practices" 섹션을 존중하여 해당 스택 이디엄으로 작성한다.
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
85
|
+
## 핵심 규칙 (스택 무관)
|
|
86
|
+
|
|
87
|
+
- api-contract.json 이 있으면 그에 정의된 엔드포인트만 호출/매핑
|
|
88
|
+
- 로딩·에러·빈 상태 3가지 필수 처리 (UI 가 있는 스택에서)
|
|
89
|
+
- 접근성·키보드 네비게이션 기본 고려 (ref.validation.visual 설정에 따름)
|
|
90
|
+
- 로케일·i18n 은 ref-docs 본문 가이드 준수
|
|
87
91
|
|
|
88
92
|
## 금지 사항
|
|
89
93
|
|
|
90
|
-
-
|
|
91
|
-
- api-contract.json에 없는 엔드포인트 호출
|
|
92
|
-
-
|
|
93
|
-
- `
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
- **ref.paths.source_roots 밖의 프로덕션 코드 수정** (BE/HARNESS 영역 침범 금지)
|
|
95
|
+
- api-contract.json 에 없는 엔드포인트 호출 (base_url 이 있는 경우)
|
|
96
|
+
- `.harness/ref/` 직접 편집 (refresh 는 `bash init.sh refresh-ref` 경유)
|
|
97
|
+
- 공통 `generator-frontend.md` / 스택별 `generator-frontend-<stack>.md` gotcha 에 적힌 실수 반복
|
|
98
|
+
|
|
99
|
+
## 디버깅 / Fallback
|
|
100
|
+
|
|
101
|
+
- `ref-docs` 가 placeholder 상태(`generator: "init-ref-docs.sh (placeholder)"`) → 본격 구현 전에 Claude 세션에서 `bash init.sh refresh-ref fe <stack>` 후 프롬프트 실행으로 본문 채우기를 권고
|
|
102
|
+
- `scan-result.json.tech_stack_confidence == "unknown"` → 사용자에게 객관식(감지 후보 top 5 + 자유입력 fallback)으로 스택 확인 요청
|
|
File without changes
|
|
File without changes
|
|
File without changes
|