@walwal-harness/cli 5.0.9 → 5.2.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.
@@ -210,6 +210,22 @@ Agent({
210
210
  prompt: "당신은 독립 Evaluator입니다. Generator가 작성한 코드를 AC 기준으로 냉정하게 평가합니다.
211
211
  Generator의 의도나 추론 과정은 알 수 없습니다. 오직 코드와 결과만 봅니다.
212
212
 
213
+ ## 실시간 로깅 (필수)
214
+
215
+ 평가 진행 상황을 실시간으로 기록합니다. **각 AC 검증마다 반드시 logev를 호출**하세요.
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\"; }
221
+ ```
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\"`
228
+
213
229
  ## 평가 대상
214
230
  - Feature ID: {FEATURE_ID}
215
231
  - AC: jq '.features[] | select(.id == \"{FEATURE_ID}\").acceptance_criteria' .harness/actions/feature-list.json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "5.0.9",
3
+ "version": "5.2.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
- case "$agent" in
154
- dispatcher*) icon="▸" ; color="$MAGENTA" ;;
155
- planner*) icon="□" ; color="$YELLOW" ;;
156
- generator*) icon="▶" ; color="$GREEN" ;;
157
- eval*) icon="✦" ; color="$RED" ;;
158
- team-*) icon="◆" ; color="$CYAN" ;;
159
- user*) icon="★" ; color="$BOLD" ;;
160
- *) icon="·" ; color="$DIM" ;;
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)) | tail -8)
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|eval-start|eval-check|eval-done)
163
- a_icon="✦"; a_color="$BLUE"; a_label="Eval" ;;
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
- local term_height
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 | tail -"$max_lines")
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=$(grep -c '| user-prompt |' "$PROGRESS_LOG" 2>/dev/null || echo 0)
116
+ total_prompts=$(echo "$prompts" | wc -l | tr -d ' ')
123
117
 
124
- echo "$prompts" | while IFS= read -r line; do
125
- line_num=$((line_num + 1))
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"
@@ -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
@@ -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`에 **`fe_stack`** 필드를 포함해야 한다:
116
+ FE-ONLY 또는 FULLSTACK 선택 시, `pipeline.json` 에 **`fe_stack`** 필드를 포함해야 한다:
117
117
 
118
- - `scan-result.json.tech_stack.fe_stack` 값을 기본으로 사용 (`react` | `flutter`)
119
- - 값이 없거나 불명확하면 Planner가 확정하도록 위임 (Dispatcher는 `"unknown"` 기록 + `notes` 에 메모)
120
- - Flutter 선택 시 `agents_active`/`agents_skipped`에 치환된 에이전트명을 기록
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` (또는 Flutter 변형) |
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 라우팅 (fe_stack 반영)
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
- | 원본 next_agent | fe_stack=react | fe_stack=flutter |
200
- |-----------------|----------------|------------------|
201
- | generator-frontend | generator-frontend | generator-frontend-flutter |
202
- | evaluator-functional (FE 단계) | evaluator-functional | evaluator-functional-flutter |
203
- | evaluator-visual | evaluator-visual | (skip → 다음 단계로 이동) |
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
- **Brainstormer 는 fe_stack 치환 대상이 아니다** — 언어/스택 무관 공통 에이전트.
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. Frontend `http://localhost:5173` 실행 확인
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. NestJS MSA 모노레포로 API Gateway, Microservices, DB 스키마를 구현한다. api-contract.json이 스펙이며, Sprint Contract의 Backend 섹션을 작성 후 구현한다."
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 — NestJS MSA
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`가 `"generator-backend"`인지 확인
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인 경우 `evaluation-functional.md`의 실패 사유 우선 읽기
14
+ 3. `failure` 필드 확인 — retry 인 경우 평가 문서의 실패 사유 우선 읽기
15
15
 
16
16
  ### On Complete
17
17
  1. progress.json 업데이트:
18
18
  - `agent_status` → `"completed"`
19
- - `completed_agents`에 `"generator-backend"` 추가
20
- - `next_agent` → 파이프라인에 따라 결정 (FULLSTACK: `"generator-frontend"`, BE-ONLY: `"evaluator-functional"`)
19
+ - `completed_agents` 에 `"generator-backend"` 추가
20
+ - `next_agent` → 파이프라인에 따라 (`"generator-frontend"` 또는 `"evaluator-functional"`)
21
21
  - `failure` 필드 초기화 (retry 성공 시)
22
- 2. `feature-list.json`의 해당 feature `passes`에 `"generator-backend"` 추가
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/gotchas/generator-backend.md` 읽기 — **과거 실수 반복 금지**
31
- 3. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
32
- 4. `pwd` + `.harness/progress.json` + `git log --oneline -20`
33
- 5. `.harness/actions/api-contract.json` 읽기 — **이것이 스펙**
34
- 6. `.harness/actions/feature-list.json` — `layer: "backend"` 필터
35
- 7. 통합 러너: `npm run dev`
36
- 8. Gateway 헬스체크: `curl http://localhost:3000/health`
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` 소유 경로만 쓰기 가능. 구조 변경 필요 시 sprint-contract.md에 `## Change Request`.
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 섹션 작성** — 엔드포인트, DB 변경, message patterns, 성공 기준
45
- 2. **구현** — Gateway 컨트롤러 + Microservice 핸들러 + Shared DTO
46
- 3. **Self-Verification** — Jest + curl 테스트
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
- NestJS MSA 패턴 → [NestJS MSA 참조](references/nestjs-msa-patterns.md)
50
- Sprint Contract 형식 → [Sprint Contract BE 템플릿](references/sprint-contract-be.md)
83
+ - api-contract.json 에 없는 엔드포인트 **구현 금지**
84
+ - 로깅 / 에러 핸들링 / 트랜잭션 경계 필수
85
+ - 보안: OWASP Top 10 (인증·인가·입력 검증·SQL Injection 등) 기본 준수
86
+ - 테스트: `ref.validation.functional_tests` 에 나열된 명령이 전부 통과해야 PASS
51
87
 
52
88
  ## 금지 사항
53
89
 
54
- - api-contract.json에 없는 엔드포인트 추가
55
- - Frontend(apps/web/) 코드 수정
56
- - feature-list.json에서 `passes` 외 필드 수정
57
- - 서비스 간 직접 DB 접근 (반드시 메시지 패턴)
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
- ## On Evaluator Feedback
95
+ ## 디버깅 / Fallback
61
96
 
62
- `evaluation-functional.md` → `failure_location: "backend"` 필터 → 수정 → Jest 재실행 → 핸드오프
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. React/Next.js + TypeScript로 프리미엄 UI를 구현한다. Vercel best practices(RSC, App Router, Cache Components) + taste-skill 디자인 철학(AI슬롭 금지, 프리미엄 타이포·컬러·모션) 기반. api-contract.json으로 Gateway만 바라본다."
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 — React/Next.js + Premium Design
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`가 `"generator-frontend"`인지 확인
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`에 `"generator-frontend"` 추가
19
+ - `completed_agents` 에 `"generator-frontend"` 추가
20
20
  - `next_agent` → `"evaluator-functional"`
21
21
  - `failure` 필드 초기화 (retry 성공 시)
22
- 2. `feature-list.json`의 해당 feature `passes`에 `"generator-frontend"` 추가
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/gotchas/generator-frontend.md` 읽기 — **과거 실수 반복 금지**
31
- 3. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
32
- 4. `pwd` + `.harness/progress.json` + `git log --oneline -20`
33
- 5. `.harness/actions/api-contract.json` 읽기 — **Gateway가 유일한 API 인터페이스**
34
- 6. `.harness/actions/feature-list.json` — `layer: "frontend"` 필터
35
- 7. Gateway 확인: `curl -s http://localhost:3000/health`
36
- 8. Frontend 시작: `cd apps/web && npm run dev`
37
-
38
- ## AGENTS.md — 읽기 전용
39
-
40
- `[FE]` + `→ Generator-Frontend` 소유 경로만 쓰기 가능.
41
-
42
- ## Prerequisites
43
-
44
- **Backend 통합 러너가 동작 중이어야 함.** Gateway 미응답 시 → STOP.
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`에서 **지정된 FEATURE_ID만** 필터하여 구현
52
- - 다른 Feature의 코드를 수정하지 않음
53
- - `depends_on`에 명시된 Feature는 이미 구현/머지 완료된 상태
54
- - Feature branch (`feature/F-XXX`)에서 작업, 완료 시 commit
55
- - Sprint Contract는 작성하지 않음 (v4에서는 Feature 단위로 관리)
56
-
57
- ### Feature-Level Prompt Template
58
- Worker가 전달하는 프롬프트에는 다음이 포함됨:
59
- - `FEATURE_ID`, `feature_name`, `description`, `ac` (Acceptance Criteria)
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 섹션 추가** — 컴포넌트, API 연동, 성공 기준
66
- 2. **구현** — 아래 3개 레퍼런스를 반드시 참조
67
- 3. **Self-Verification** — tsc + Vitest + 브라우저 확인
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
- | [Vercel Best Practices](references/vercel-best-practices.md) | RSC, App Router, 성능, 렌더링 전략 | 프로젝트 스캐폴딩 시 |
75
- | [Design System Rules](references/design-system-rules.md) | 타이포, 컬러, 레이아웃, 모션, 한국어 규칙 | 컴포넌트 구현 시 |
76
- | [AI Forbidden Patterns](references/ai-forbidden-patterns.md) | AI슬롭 감지/금지 패턴 전체 목록 | 구현 완료 후 셀프 체크 |
77
- | [Component Patterns](references/component-patterns.md) | 프로젝트 구조, API 타입, 상태관리 | 파일 생성 시 |
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
- - api-contract.json → `src/api/types.ts` 1:1 변환
82
- - API base URL: Gateway만 (`http://localhost:3000`)
83
- - **RSC 우선**: `'use client'`는 인터랙션이 필요한 곳만
84
- - 로딩/에러/빈 상태 3가지 필수 처리
85
- - 시맨틱 HTML + 키보드 네비게이션 + WCAG AA
86
- - Tailwind CSS + `cubic-bezier(0.16, 1, 0.3, 1)` 모션 기본값
85
+ ## 핵심 규칙 (스택 무관)
86
+
87
+ - api-contract.json 이 있으면 그에 정의된 엔드포인트만 호출/매핑
88
+ - 로딩·에러·빈 상태 3가지 필수 처리 (UI 가 있는 스택에서)
89
+ - 접근성·키보드 네비게이션 기본 고려 (ref.validation.visual 설정에 따름)
90
+ - 로케일·i18n 은 ref-docs 본문 가이드 준수
87
91
 
88
92
  ## 금지 사항
89
93
 
90
- - Backend 코드 수정, Gateway 내부 서비스 직접 호출
91
- - api-contract.json에 없는 엔드포인트 호출
92
- - AI Forbidden Patterns 목록의 모든 항목
93
- - `h-screen` 사용 (`min-h-[100dvh]`로 대체)
94
- - `window.addEventListener('scroll')` (IntersectionObserver 사용)
95
- - `linear` / `ease-in-out` 트랜지션 (커스텀 cubic-bezier 사용)
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)으로 스택 확인 요청
@@ -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 리뷰 요청