@walwal-harness/cli 2.4.0 → 2.5.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/README.md CHANGED
@@ -155,17 +155,18 @@ Brainstormer? → Planner → Generator-BE → Evaluator-Func(API-only)
155
155
 
156
156
  > `Brainstormer?` = Dispatcher가 사용자에게 확인 후 조건부 실행. 명확한 PRD/OpenAPI가 있으면 생략 가능.
157
157
 
158
- ### Flutter 프로젝트 자동 감지
158
+ ### Flutter 프로젝트 자동 감지 (Web / Mobile / Desktop)
159
159
 
160
- `pubspec.yaml` + `flutter:` 키가 감지되면 FE 에이전트가 **자동 치환**됩니다:
160
+ `pubspec.yaml` + `flutter:` 키가 감지되면 FE 에이전트가 **자동 치환**됩니다. **Flutter Web** 은 컴파일 결과가 HTML+JS+CSS 이므로 React 와 동일하게 Playwright 기반 evaluator 를 그대로 사용하고, **Mobile/Desktop** 만 정적 분석 evaluator 로 교체됩니다.
161
161
 
162
- | React (기본) | Flutter |
163
- |-------------|---------|
164
- | `generator-frontend` | `generator-frontend-flutter` |
165
- | `evaluator-functional` (Playwright) | `evaluator-functional-flutter` (flutter analyze/test) |
166
- | `evaluator-visual` | **SKIP** (브라우저 없음) |
162
+ | fe_stack | fe_target | Generator | Eval-Functional | Eval-Visual |
163
+ |----------|-----------|-----------|----------------|-------------|
164
+ | react | (n/a) | `generator-frontend` | `evaluator-functional` (Playwright) | `evaluator-visual` |
165
+ | **flutter** | **web** | `generator-frontend-flutter` | **`evaluator-functional`** (Playwright!) | **`evaluator-visual`** (Playwright!) |
166
+ | **flutter** | mobile | `generator-frontend-flutter` | `evaluator-functional-flutter` (flutter analyze/test) | **SKIP** |
167
+ | **flutter** | desktop | `generator-frontend-flutter` | `evaluator-functional-flutter` (flutter analyze/test) | **SKIP** |
167
168
 
168
- 치환은 `pipeline.json.fe_stack` 값에 의해 `harness-next.sh`가 자동 처리합니다. Planner가 프로젝트 스캔 결과를 보고 `fe_stack`을 확정합니다.
169
+ **감지 방식**: `scan-project.sh` 가 Flutter 프로젝트의 `web/index.html`, `android/`, `ios/`, `macos/`, `windows/`, `linux/` 디렉터리 존재 여부로 `fe_target` 을 자동 판정. 멀티 타겟 또는 모호한 경우 Planner 가 사용자에게 확인합니다. 치환은 `pipeline.json.fe_stack + fe_target` 조합에 의해 `harness-next.sh` 가 자동 처리합니다.
169
170
 
170
171
  ---
171
172
 
@@ -269,20 +270,24 @@ Evaluator는 기능 테스트 전에 AGENTS.md의 IA-MAP과 실제 폴더 구조
269
270
  | Evaluator-Visual | `harness-evaluator-visual` — 스크린샷 기반 디자인/접근성/반응형 검증 |
270
271
  | 레퍼런스 | Vercel Best Practices, Design System Rules, AI Forbidden Patterns, Component Patterns |
271
272
 
272
- ### Flutter (Dart)
273
+ ### Flutter (Dart) — Web / Mobile / Desktop
273
274
 
274
- | 항목 | 상세 |
275
- |------|------|
276
- | Generator | `harness-generator-frontend-flutter` — Riverpod + integrated_data_layer(Retrofit) + ARB i18n |
277
- | Evaluator-Func | `harness-evaluator-functional-flutter` — `flutter analyze` + `flutter test` + build_runner drift + anti-pattern grep (FL-01~FL-08) |
278
- | Evaluator-Visual | **SKIP** (Flutter 앱은 브라우저 기반 시각 검증 불가) |
279
- | 레퍼런스 | API Layer Pattern, Riverpod Pattern, i18n Pattern, Anti-Patterns (9개 금지 패턴 + 셀프 체크 스크립트) |
275
+ | 항목 | Web (`fe_target=web`) | Mobile/Desktop (`fe_target=mobile`/`desktop`) |
276
+ |------|----------------------|---------------------------------------------|
277
+ | Generator | `harness-generator-frontend-flutter` (Web 가이드 활성) | `harness-generator-frontend-flutter` (Mobile/Desktop 가이드) |
278
+ | Eval-Func | **`harness-evaluator-functional`** (Playwright MCP) | `harness-evaluator-functional-flutter` (`flutter analyze` + `flutter test` + build_runner drift + FL-01~FL-08 정적 검증) |
279
+ | Eval-Visual | **`harness-evaluator-visual`** (Playwright 스크린샷/반응형/접근성) | **SKIP** (브라우저 없음) |
280
+ | 레퍼런스 공통 | API Layer Pattern, Riverpod Pattern, i18n Pattern, Anti-Patterns | 동일 |
281
+ | 레퍼런스 추가 | **Flutter Web Pattern** (`dart:html`/`package:web` 허용, go_router, CORS, PWA, 빌드/호스팅) | — |
282
+ | dart:html / package:web | **허용** (가능하면 conditional import 권장) | **금지** (모바일/데스크톱 빌드 실패 유발) |
283
+ | 빌드 명령 | `flutter build web --release` / `flutter run -d chrome` | `flutter build apk` / `flutter build ios` / `flutter build macos` 등 |
280
284
 
281
- ### FE 스택 감지
285
+ ### FE 스택 + 타겟 감지
282
286
 
283
- 1. `scan-project.sh`가 `pubspec.yaml` + `flutter:` 키를 탐지
284
- 2. Planner가 `pipeline.json.fe_stack = "flutter"` 확정
285
- 3. `harness-next.sh`가 FE 에이전트를 자동 치환 (Agent Bar에도 반영)
287
+ 1. `scan-project.sh`가 `pubspec.yaml` + `flutter:` 키를 탐지하여 `fe_stack = "flutter"` 설정
288
+ 2. 같은 스캔이 `web/index.html`, `android/`, `ios/`, `macos/`/`windows/`/`linux/` 존재 여부로 `fe_target` 자동 판정 (`web` / `mobile` / `desktop`)
289
+ 3. Planner 가 모호한 경우(멀티 타겟, unknown) 사용자에게 단 한 번 확인하고 `pipeline.json.fe_target` 확정
290
+ 4. `harness-next.sh`가 `fe_stack + fe_target` 조합에 따라 FE 에이전트를 자동 치환 (Agent Bar에도 반영)
286
291
 
287
292
  ---
288
293
 
@@ -340,7 +345,7 @@ Evaluator-Functional / Evaluator-Visual이 브라우저 테스트를 수행하
340
345
  }
341
346
  ```
342
347
 
343
- > Flutter 프로젝트에서는 Playwright가 필요 없습니다 — `evaluator-functional-flutter`는 `flutter analyze` + `flutter test` 기반입니다.
348
+ > **Flutter Web (`fe_target=web`) 프로젝트도 Playwright 가 필요합니다** — 컴파일 결과가 일반 웹앱과 동일하므로 React 와 같은 evaluator 를 재사용합니다. **Flutter Mobile/Desktop (`fe_target=mobile`/`desktop`) 프로젝트만** Playwright 없이 `flutter analyze` + `flutter test` 기반으로 동작합니다.
344
349
 
345
350
  ---
346
351
 
@@ -384,10 +389,29 @@ grep "flutter:" pubspec.yaml
384
389
 
385
390
  # 재스캔
386
391
  bash scripts/scan-project.sh .
387
- # pipeline.json의 fe_stack 값 확인
388
- cat .harness/actions/pipeline.json | jq '.fe_stack'
392
+ # pipeline.json의 fe_stack + fe_target 값 확인
393
+ cat .harness/actions/pipeline.json | jq '.fe_stack, .fe_target'
389
394
  ```
390
395
 
396
+ ### Flutter Web 프로젝트인데 evaluator-functional-flutter (정적 분석) 가 실행돼요
397
+
398
+ `fe_target` 이 `web` 으로 감지되지 않아서 발생합니다. 다음 두 가지를 확인:
399
+
400
+ ```bash
401
+ # 1) Flutter 프로젝트 루트에 web/index.html 이 존재하는가?
402
+ ls -la web/index.html
403
+ # 없으면 Flutter Web 활성화:
404
+ flutter config --enable-web
405
+ flutter create --platforms=web .
406
+
407
+ # 2) pipeline.json 의 fe_target 값 확인
408
+ jq '.fe_target' .harness/actions/pipeline.json
409
+ # "mobile" 또는 "unknown" 이면 수동 수정 또는 재스캔
410
+ bash scripts/scan-project.sh .
411
+ ```
412
+
413
+ `fe_target = web` 으로 확정되면 Playwright 기반 `evaluator-functional` + `evaluator-visual` 이 자동 사용됩니다.
414
+
391
415
  ### Auto-routing을 끄고 싶어요
392
416
 
393
417
  ```json
@@ -87,11 +87,30 @@
87
87
  "BE-ONLY": ["planner", "generator-backend", "evaluator-functional:api-only"]
88
88
  },
89
89
  "fe_stack_substitution": {
90
- "comment": "pipeline.json.fe_stack=flutter 이면 아래 치환을 적용. evaluator-visual은 skip.",
90
+ "comment": "pipeline.json.fe_stack + fe_target 에 따라 FE 에이전트 치환. Flutter Web 은 React 와 동일한 Playwright 기반 evaluator 를 사용한다.",
91
91
  "flutter": {
92
- "generator-frontend": "generator-frontend-flutter",
93
- "evaluator-functional": "evaluator-functional-flutter",
94
- "evaluator-visual": "__skip__"
92
+ "_doc": "fe_target = web | mobile | desktop. by_target 으로 분기.",
93
+ "by_target": {
94
+ "web": {
95
+ "_doc": "Flutter Web — 컴파일 결과가 HTML+JS+CSS 이므로 Playwright evaluator 사용 가능",
96
+ "generator-frontend": "generator-frontend-flutter",
97
+ "evaluator-functional": "evaluator-functional",
98
+ "evaluator-visual": "evaluator-visual"
99
+ },
100
+ "mobile": {
101
+ "_doc": "Flutter Mobile (Android/iOS) — 브라우저 없음, 정적 분석 evaluator 사용",
102
+ "generator-frontend": "generator-frontend-flutter",
103
+ "evaluator-functional": "evaluator-functional-flutter",
104
+ "evaluator-visual": "__skip__"
105
+ },
106
+ "desktop": {
107
+ "_doc": "Flutter Desktop (macOS/Windows/Linux) — 브라우저 없음, 정적 분석 evaluator 사용",
108
+ "generator-frontend": "generator-frontend-flutter",
109
+ "evaluator-functional": "evaluator-functional-flutter",
110
+ "evaluator-visual": "__skip__"
111
+ }
112
+ },
113
+ "_default_target": "mobile"
95
114
  }
96
115
  }
97
116
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer (requirements refinement). Supports React and Flutter FE stacks.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -41,17 +41,24 @@ next_agent=$(jq -r '.next_agent // "null"' "$PROGRESS")
41
41
  retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
42
42
  max_retries=$(jq -r '.flow.max_retries_per_sprint // 10' "$CONFIG" 2>/dev/null || echo 10)
43
43
 
44
- # fe_stack 치환 (Flutter 지원) — pipeline.json 에서 읽음
44
+ # fe_stack + fe_target 치환 (Flutter Web/Mobile/Desktop 지원) — pipeline.json 에서 읽음
45
45
  fe_stack="react"
46
+ fe_target="web"
46
47
  if [ -f "$PIPELINE_JSON" ]; then
47
48
  fe_stack=$(jq -r '.fe_stack // "react"' "$PIPELINE_JSON" 2>/dev/null || echo "react")
49
+ fe_target=$(jq -r '.fe_target // empty' "$PIPELINE_JSON" 2>/dev/null || true)
50
+ if [ -z "$fe_target" ]; then
51
+ # pipeline.json 에 fe_target 미지정 시 config.json 의 _default_target 사용
52
+ fe_target=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.${fe_stack}._default_target // \"web\"" "$CONFIG" 2>/dev/null || echo "web")
53
+ fi
48
54
  fi
49
55
 
50
56
  # ─────────────────────────────────────────
51
- # fe_stack 치환 헬퍼
52
- # pipeline_selection.pipelines에서 읽은 에이전트명을 fe_stack에 따라 치환
57
+ # fe_stack + fe_target 치환 헬퍼
58
+ # pipeline_selection.pipelines 에서 읽은 에이전트명을 fe_stack/fe_target 에 따라 치환
53
59
  # - react: 그대로
54
- # - flutter: generator-frontend → generator-frontend-flutter 등, __skip__ 은 건너뜀
60
+ # - flutter+web: generator-frontend → generator-frontend-flutter 만 치환, eval 은 그대로 (Playwright 사용 가능)
61
+ # - flutter+mobile/desktop: eval 도 정적 분석용으로 치환, evaluator-visual 은 __skip__
55
62
  # ─────────────────────────────────────────
56
63
  substitute_fe_stack() {
57
64
  local agent="$1"
@@ -60,7 +67,7 @@ substitute_fe_stack() {
60
67
  return
61
68
  fi
62
69
  local sub
63
- sub=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.flutter[\"${agent}\"] // \"${agent}\"" "$CONFIG" 2>/dev/null)
70
+ sub=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.${fe_stack}.by_target[\"${fe_target}\"][\"${agent}\"] // \"${agent}\"" "$CONFIG" 2>/dev/null)
64
71
  echo "$sub"
65
72
  }
66
73
 
@@ -286,12 +286,17 @@ render_agent_bar() {
286
286
 
287
287
  if [ ! -f "$PROGRESS" ] || [ ! -f "$CONFIG" ]; then return 1; fi
288
288
 
289
- local pipeline current_agent fe_stack
289
+ local pipeline current_agent fe_stack fe_target
290
290
  pipeline=$(jq -r '.pipeline // "unknown"' "$PROGRESS")
291
291
  current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS")
292
292
  fe_stack="react"
293
+ fe_target="web"
293
294
  if [ -f "$PIPELINE_JSON" ]; then
294
295
  fe_stack=$(jq -r '.fe_stack // "react"' "$PIPELINE_JSON" 2>/dev/null || echo "react")
296
+ fe_target=$(jq -r '.fe_target // empty' "$PIPELINE_JSON" 2>/dev/null || true)
297
+ if [ -z "$fe_target" ]; then
298
+ fe_target=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.${fe_stack}._default_target // \"web\"" "$CONFIG" 2>/dev/null || echo "web")
299
+ fi
295
300
  fi
296
301
 
297
302
  local completed_agents
@@ -305,10 +310,10 @@ render_agent_bar() {
305
310
  while IFS= read -r agent; do
306
311
  agent=$(echo "$agent" | sed 's/:.*//') # strip mode suffix like :light, :api-only
307
312
 
308
- # fe_stack 치환 적용
313
+ # fe_stack + fe_target 치환 적용
309
314
  if [ "$fe_stack" = "flutter" ]; then
310
315
  local sub
311
- sub=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.flutter[\"${agent}\"] // \"${agent}\"" "$CONFIG" 2>/dev/null)
316
+ sub=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.flutter.by_target[\"${fe_target}\"][\"${agent}\"] // \"${agent}\"" "$CONFIG" 2>/dev/null)
312
317
  if [ "$sub" = "__skip__" ]; then continue; fi
313
318
  agent="$sub"
314
319
  fi
@@ -62,11 +62,15 @@ fi
62
62
 
63
63
  # Frontend
64
64
  # Flutter 우선 감지 — pubspec.yaml 존재하면 Flutter 프로젝트로 판정 (TECH_LANG도 보정)
65
- FE_STACK="react" # react | flutter (기본값은 react 계열)
65
+ FE_STACK="react" # react | flutter (기본값은 react 계열)
66
+ FE_TARGET="web" # web | mobile | desktop (Flutter 의 컴파일 타겟; React 는 항상 web)
67
+ FLUTTER_ROOT="" # Flutter 프로젝트 루트 경로 (web/mobile/desktop 감지용)
68
+
66
69
  if [ -f "${PROJECT_ROOT}/pubspec.yaml" ] && grep -q "flutter:" "${PROJECT_ROOT}/pubspec.yaml" 2>/dev/null; then
67
70
  TECH_FRONTEND="flutter"
68
71
  TECH_LANG="dart"
69
72
  FE_STACK="flutter"
73
+ FLUTTER_ROOT="${PROJECT_ROOT}"
70
74
  elif [ -f "${PROJECT_ROOT}/next.config.js" ] || [ -f "${PROJECT_ROOT}/next.config.ts" ] || [ -f "${PROJECT_ROOT}/next.config.mjs" ]; then
71
75
  TECH_FRONTEND="nextjs"
72
76
  elif [ -f "${PROJECT_ROOT}/vite.config.ts" ] || [ -f "${PROJECT_ROOT}/vite.config.js" ]; then
@@ -91,16 +95,41 @@ fi
91
95
  # Flutter 서브디렉토리 감지 (monorepo 또는 서브 프로젝트 케이스)
92
96
  if [ "$TECH_FRONTEND" = "unknown" ]; then
93
97
  # 대표적인 Flutter 서브폴더 이름을 얕게 탐색
94
- for d in apps/mobile mobile clue_mobile_app flutter_app; do
98
+ for d in apps/mobile apps/web mobile clue_mobile_app flutter_app; do
95
99
  if [ -f "${PROJECT_ROOT}/${d}/pubspec.yaml" ] && grep -q "flutter:" "${PROJECT_ROOT}/${d}/pubspec.yaml" 2>/dev/null; then
96
100
  TECH_FRONTEND="flutter"
97
101
  TECH_LANG="dart"
98
102
  FE_STACK="flutter"
103
+ FLUTTER_ROOT="${PROJECT_ROOT}/${d}"
99
104
  break
100
105
  fi
101
106
  done
102
107
  fi
103
108
 
109
+ # Flutter fe_target 감지 (web / mobile / desktop)
110
+ # - web/index.html 존재 → web
111
+ # - android/ 또는 ios/ 존재 + web/ 없음 → mobile
112
+ # - macos/ 또는 windows/ 또는 linux/ 존재 + 위 둘 없음 → desktop
113
+ # - 동시 존재 (멀티 타겟) → web 우선 (사용자가 Planner에서 변경 가능)
114
+ if [ "$FE_STACK" = "flutter" ] && [ -n "$FLUTTER_ROOT" ]; then
115
+ HAS_WEB=false
116
+ HAS_MOBILE=false
117
+ HAS_DESKTOP=false
118
+ [ -f "${FLUTTER_ROOT}/web/index.html" ] && HAS_WEB=true
119
+ { [ -d "${FLUTTER_ROOT}/android" ] || [ -d "${FLUTTER_ROOT}/ios" ]; } && HAS_MOBILE=true
120
+ { [ -d "${FLUTTER_ROOT}/macos" ] || [ -d "${FLUTTER_ROOT}/windows" ] || [ -d "${FLUTTER_ROOT}/linux" ]; } && HAS_DESKTOP=true
121
+
122
+ if [ "$HAS_WEB" = "true" ]; then
123
+ FE_TARGET="web"
124
+ elif [ "$HAS_MOBILE" = "true" ]; then
125
+ FE_TARGET="mobile"
126
+ elif [ "$HAS_DESKTOP" = "true" ]; then
127
+ FE_TARGET="desktop"
128
+ else
129
+ FE_TARGET="unknown"
130
+ fi
131
+ fi
132
+
104
133
  # Database
105
134
  if grep -rq "typeorm\|prisma\|sequelize\|knex" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
106
135
  if grep -q "pg\|postgres" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
@@ -225,6 +254,7 @@ cat > "$OUTPUT" << JSONEOF
225
254
  "backend": "${TECH_BACKEND}",
226
255
  "frontend": "${TECH_FRONTEND}",
227
256
  "fe_stack": "${FE_STACK}",
257
+ "fe_target": "${FE_TARGET}",
228
258
  "database": "${TECH_DB}",
229
259
  "monorepo": "${TECH_MONOREPO}",
230
260
  "language": "${TECH_LANG}"
@@ -282,7 +312,7 @@ echo "=== Scan Complete ==="
282
312
  echo "Output: ${OUTPUT}"
283
313
  echo ""
284
314
  echo "--- Summary ---"
285
- echo "Tech Stack: ${TECH_BACKEND} / ${TECH_FRONTEND} (fe_stack=${FE_STACK}) / ${TECH_DB}"
315
+ echo "Tech Stack: ${TECH_BACKEND} / ${TECH_FRONTEND} (fe_stack=${FE_STACK}, fe_target=${FE_TARGET}) / ${TECH_DB}"
286
316
  echo "Monorepo: ${TECH_MONOREPO}"
287
317
  echo "OpenAPI: ${OPENAPI}"
288
318
  echo "Git: ${GIT_INIT} (${GIT_COMMITS} commits, branch: ${GIT_BRANCH})"
@@ -31,20 +31,31 @@ docmeta:
31
31
  > **FE Stack 차원**: 모든 FE 관련 파이프라인은 `fe_stack` 필드로 React/Flutter를 구분한다.
32
32
  > Planner가 scan-result.json(`tech_stack.fe_stack`) 또는 사용자 질문으로 확정한다.
33
33
 
34
- ## fe_stack 스위치 매트릭스
34
+ ## fe_stack + fe_target 스위치 매트릭스
35
35
 
36
- | fe_stack | FE Generator | FE Evaluator(Functional) | Evaluator-Visual |
37
- |----------|--------------|--------------------------|------------------|
38
- | `react` | `generator-frontend` | `evaluator-functional` (Playwright MCP) | `evaluator-visual` |
39
- | `flutter`| `generator-frontend-flutter` | `evaluator-functional-flutter` (flutter analyze/test) | **SKIP** (브라우저 없음) |
36
+ | fe_stack | fe_target | FE Generator | Eval-Functional | Eval-Visual |
37
+ |----------|-----------|--------------|----------------|-------------|
38
+ | `react` | (n/a) | `generator-frontend` | `evaluator-functional` (Playwright) | `evaluator-visual` |
39
+ | `flutter`| **`web`** | `generator-frontend-flutter` | **`evaluator-functional`** (Playwright!) | **`evaluator-visual`** (Playwright!) |
40
+ | `flutter`| `mobile` | `generator-frontend-flutter` | `evaluator-functional-flutter` (정적 분석) | **SKIP** |
41
+ | `flutter`| `desktop` | `generator-frontend-flutter` | `evaluator-functional-flutter` (정적 분석) | **SKIP** |
40
42
 
41
- Dispatcher는 `next_agent`를 설정할 때 다음 규칙으로 치환한다:
43
+ > **Flutter Web**: 컴파일 결과가 HTML+JS+CSS 이므로 React 경로의 Playwright evaluator 가 정상 동작.
44
+ > Generator-Frontend-Flutter 의 Self-Verification 에서 `flutter analyze` / `flutter test` /
45
+ > `flutter build web --release` 가 이미 통과한 상태로 handoff 됨이 전제.
46
+
47
+ Dispatcher는 `next_agent`를 설정할 때 `pipeline.json` 의 `fe_stack` + `fe_target` 두 값을 보고 치환한다:
42
48
 
43
49
  ```
44
50
  if pipeline.json.fe_stack == "flutter":
45
- "generator-frontend" → "generator-frontend-flutter"
46
- "evaluator-functional" (FE 단계) → "evaluator-functional-flutter"
47
- "evaluator-visual" → skip (agents_skipped로 이동)
51
+ "generator-frontend" → "generator-frontend-flutter" (모든 fe_target 공통)
52
+
53
+ if fe_target == "web":
54
+ "evaluator-functional" → "evaluator-functional" (그대로, Playwright 사용)
55
+ "evaluator-visual" → "evaluator-visual" (그대로)
56
+ elif fe_target in ("mobile", "desktop"):
57
+ "evaluator-functional" → "evaluator-functional-flutter"
58
+ "evaluator-visual" → __skip__
48
59
  ```
49
60
 
50
61
  ## FE-ONLY
@@ -6,9 +6,16 @@ disable-model-invocation: true
6
6
 
7
7
  # Evaluator-Functional-Flutter — Dart/Flutter 정적·동적 검증
8
8
 
9
- > **주의**: Flutter 앱은 브라우저가 아니므로 Playwright MCP(`browser_*`)를 쓸 수 없다.
10
- > 이 에이전트는 `flutter analyze`, `flutter test`, `dart format`, 정적 grep 검증, 생성 파일 일관성으로 평가한다.
11
- > 실기기/시뮬레이터 UI 검증은 사람 QA 또는 별도 파이프라인으로 위임.
9
+ > **이 에이전트는 `fe_target = mobile` 또는 `desktop` 일 때만 사용된다.**
10
+ >
11
+ > Flutter Mobile/Desktop 앱은 브라우저가 아니므로 Playwright MCP(`browser_*`)를 쓸 수 없다.
12
+ > 이 에이전트는 `flutter analyze`, `flutter test`, `dart format`, 정적 grep 검증, 생성 파일
13
+ > 일관성으로 평가한다. 실기기/시뮬레이터 UI 검증은 사람 QA 또는 별도 파이프라인으로 위임.
14
+ >
15
+ > **`fe_target = web` 인 Flutter Web 프로젝트는 이 에이전트가 아니라 일반 `evaluator-functional`
16
+ > 과 `evaluator-visual` 이 Playwright 로 검증한다** (컴파일 결과가 HTML+JS+CSS 이므로).
17
+ > 이 경우 Generator-Frontend-Flutter 의 Self-Verification 단계에서 `flutter analyze` /
18
+ > `flutter test` / `flutter build web --release` 가 이미 통과했음이 전제된다.
12
19
 
13
20
  ## Session Boundary Protocol
14
21
 
@@ -33,7 +33,10 @@ disable-model-invocation: true
33
33
  4. `pwd` + `.harness/progress.json` + `git log --oneline -20`
34
34
  5. `.harness/actions/api-contract.json` 읽기 — **서버 API 계약이 Source of Truth**
35
35
  6. `.harness/actions/feature-list.json` — `layer: "frontend"` 필터
36
- 7. `pubspec.yaml` 확인 — Flutter 버전, Riverpod/Retrofit/json_serializable 의존성 존재 확인
36
+ 7. `.harness/actions/pipeline.json` 의 **`fe_target`** 확인 (`web` | `mobile` | `desktop`)
37
+ - 이 값에 따라 허용되는 API, 빌드 명령, 안티패턴이 달라진다 (아래 "fe_target 분기" 섹션 참조)
38
+ 8. `pubspec.yaml` 확인 — Flutter 버전, Riverpod/Retrofit/json_serializable 의존성 존재 확인
39
+ - `fe_target = web` 인 경우 `flutter config --enable-web` 활성화 + `web/index.html` 존재 확인
37
40
 
38
41
  ## AGENTS.md — 읽기 전용
39
42
 
@@ -60,7 +63,38 @@ Backend 코드, `.harness/`, `AGENTS.md` 수정 금지.
60
63
  | [API Layer Pattern](references/api-layer-pattern.md) | integrated_data_layer 구조, Request/Response, Retrofit | API 연동 시 |
61
64
  | [Riverpod Pattern](references/riverpod-pattern.md) | Page+VM 쌍, NotifierProvider, family 패턴 | 페이지/위젯 구현 시 |
62
65
  | [i18n Pattern](references/i18n-pattern.md) | ARB 파일, LocaleAssist, 키 네이밍 | 문자열 추가 시 |
63
- | [Anti-Patterns](references/anti-patterns.md) | 금지 API, 하드코딩, bridges/, StatefulWidget 직접 호출 | 구현 완료 후 셀프 체크 |
66
+ | [Anti-Patterns](references/anti-patterns.md) | 금지 API, 하드코딩, bridges/, StatefulWidget 직접 호출 (fe_target 별 차이 포함) | 구현 완료 후 셀프 체크 |
67
+ | [Flutter Web Pattern](references/flutter-web-pattern.md) | Web 전용 — `dart:html`/`package:web` 허용, 라우팅, 빌드, hosting | `fe_target = web` 일 때만 |
68
+
69
+ ## fe_target 분기
70
+
71
+ `pipeline.json.fe_target` 에 따라 적용되는 규칙이 다르다:
72
+
73
+ ### `fe_target = web` (Flutter Web)
74
+
75
+ - **빌드**: `flutter build web --release` / 개발 서버: `flutter run -d chrome`
76
+ - **HTML/JS 인터롭 허용**: `dart:html`, `package:web`, `dart:js_interop` 사용 가능
77
+ - 단, 가능하면 cross-platform 코드를 우선하고 web 전용 코드는 `if (kIsWeb)` 가드 또는 conditional import
78
+ - **라우팅**: `go_router` 권장 (URL 동기화). `Navigator 1.0` 의 `MaterialApp.routes` 도 OK 지만 hash routing 주의
79
+ - **자산**: `web/index.html`, `web/manifest.json`, `web/icons/` 관리. SEO 가 필요하면 `<meta>` 태그 명시
80
+ - **CORS**: 백엔드 API 가 `localhost:포트` 에서 다른 origin 일 수 있으므로 CORS 설정 확인
81
+ - **Eval 흐름**: Playwright 기반 `evaluator-functional` + `evaluator-visual` 이 정상 작동 (브라우저 E2E + 시각 검증)
82
+ - 상세 → [Flutter Web Pattern](references/flutter-web-pattern.md)
83
+
84
+ ### `fe_target = mobile` (Android / iOS)
85
+
86
+ - **빌드**: `flutter build apk` / `flutter build ios` / 개발: `flutter run -d <device>`
87
+ - **HTML/JS 인터롭 금지**: `dart:html`, `package:web`, `universal_html` 직접 참조 → 빌드 실패
88
+ - **플랫폼 채널**: 네이티브 기능 사용 시 `MethodChannel` 또는 검증된 plugin 사용
89
+ - **권한**: `AndroidManifest.xml` / `Info.plist` 에 권한 명시
90
+ - **Eval 흐름**: 정적 분석 기반 `evaluator-functional-flutter` 사용 (Playwright 불가)
91
+
92
+ ### `fe_target = desktop` (macOS / Windows / Linux)
93
+
94
+ - **빌드**: `flutter build macos` / `flutter build windows` / `flutter build linux`
95
+ - **HTML/JS 인터롭 금지**: 모바일과 동일
96
+ - **윈도우 관리**: `window_manager` 등 플러그인으로 윈도우 크기/위치 제어
97
+ - **Eval 흐름**: 정적 분석 기반 `evaluator-functional-flutter` 사용
64
98
 
65
99
  ## 핵심 규칙
66
100
 
@@ -33,26 +33,49 @@ docmeta:
33
33
 
34
34
  각 항목은 **패턴 → 이유 → 대체 방법** 형태.
35
35
 
36
- ## 1. 웹 전용 API 직접 참조
36
+ ## 1. 웹 전용 API 직접 참조 (fe_target 별 차이)
37
37
 
38
- ### 금지
38
+ ### `fe_target = mobile` 또는 `desktop` — 금지
39
39
  ```dart
40
- import 'dart:html';
41
- import 'package:universal_html/html.dart';
40
+ import 'dart:html'; // ✗
41
+ import 'package:universal_html/html.dart'; // ✗
42
+ import 'package:web/web.dart'; // ✗ (가드 없으면)
42
43
  ```
43
44
 
44
45
  ### 이유
45
- Flutter 앱은 크로스 플랫폼 — 모바일 빌드에서 `dart:html` 참조는 즉시 빌드 실패한다.
46
- Web 빌드 전용 코드가 필요하면 조건부 import 패턴을 사용한다.
46
+ Flutter 모바일/데스크톱 빌드에서 `dart:html` 참조는 즉시 빌드 실패한다.
47
+ 가드 없이 import 하면 cross-platform 코드가 깨진다.
47
48
 
48
49
  ### 대체
49
- `package:web` 또는 `kIsWeb` 분기로 플랫폼 체크 후 조건부 import.
50
+ - `kIsWeb` 분기로 플랫폼 체크 후 조건부 import
51
+ - `if (kIsWeb) { ... }` 가드 안에서만 web API 사용
52
+ - 또는 conditional import (`stub.dart` / `web.dart` / `io.dart`)
53
+
54
+ ### `fe_target = web` — 허용 (단, 권장 패턴 준수)
55
+ ```dart
56
+ import 'package:web/web.dart' as web; // ✓ 권장 (modern)
57
+ import 'dart:js_interop'; // ✓ JS interop
58
+ import 'dart:html'; // ✓ (legacy, 신규 코드는 package:web 권장)
59
+ ```
60
+
61
+ ### Web 권장 패턴
62
+ - 새 코드는 `package:web` + `dart:js_interop` 사용 (Flutter 3.7+ 에서 stable)
63
+ - `dart:html` 은 legacy — 마이그레이션 대상이지만 즉시 금지는 아님
64
+ - 가능하면 web 전용 코드를 별도 파일로 분리하고 conditional import:
65
+ ```dart
66
+ // _web_helper.dart 또는 conditional import
67
+ import 'platform_helper.dart'
68
+ if (dart.library.html) 'platform_helper_web.dart'
69
+ if (dart.library.io) 'platform_helper_io.dart';
70
+ ```
50
71
 
51
72
  ### 검증
52
73
  ```bash
53
- grep -rn "dart:html\|universal_html" lib/ integrated_data_layer/lib/
74
+ # fe_target = mobile/desktop: 0개여야 함
75
+ grep -rn "dart:html\|universal_html\|package:web" lib/ integrated_data_layer/lib/
76
+
77
+ # fe_target = web: 매치 OK, 단 'kIsWeb' 가드 또는 conditional import 와 함께 쓰는지 인접 라인 확인
54
78
  ```
55
- 결과 0개여야 함.
56
79
 
57
80
  ---
58
81
 
@@ -263,11 +286,20 @@ grep -rEn "(api[_-]?key|secret|token)\s*=\s*['\"][A-Za-z0-9]{16,}['\"]" lib/ int
263
286
 
264
287
  ## 셀프 체크 스크립트
265
288
 
266
- Generator는 handoff 전 다음 명령을 모두 실행하고 결과를 sprint-contract.md에 기록한다:
289
+ Generator는 handoff 전 다음 명령을 모두 실행하고 결과를 sprint-contract.md에 기록한다.
290
+ **`fe_target` 에 따라 1번 룰의 적용 여부가 달라진다.**
267
291
 
268
292
  ```bash
269
- # 1. 웹 API 직접 참조
270
- grep -rn "dart:html\|universal_html" lib/ integrated_data_layer/lib/ || echo "OK"
293
+ # fe_target 읽기
294
+ FE_TARGET=$(jq -r '.fe_target // "web"' .harness/actions/pipeline.json 2>/dev/null || echo "web")
295
+
296
+ # 1. 웹 API 직접 참조 — fe_target=web 이 아닐 때만 검사
297
+ if [ "$FE_TARGET" != "web" ]; then
298
+ grep -rn "dart:html\|universal_html\|package:web" lib/ integrated_data_layer/lib/ || echo "OK (FL-01 mobile/desktop)"
299
+ else
300
+ # web 타겟: 가드 없는 사용만 경고 (수동 검토)
301
+ grep -rn "dart:html\|package:web" lib/ | grep -v "kIsWeb\|conditional import" || echo "OK (FL-01 web — manual review)"
302
+ fi
271
303
 
272
304
  # 2. print 남발
273
305
  grep -rn "^\s*print(" lib/ integrated_data_layer/lib/ || echo "OK"
@@ -0,0 +1,273 @@
1
+ ---
2
+ docmeta:
3
+ id: flutter-web-pattern
4
+ title: Flutter Web 패턴 — fe_target=web 전용
5
+ type: output
6
+ createdAt: 2026-04-09T00:00:00Z
7
+ updatedAt: 2026-04-09T00:00:00Z
8
+ source:
9
+ producer: agent
10
+ skillId: harness-generator-frontend-flutter
11
+ inputs:
12
+ - documentId: harness-generator-frontend-flutter-skill
13
+ uri: ../SKILL.md
14
+ relation: output-from
15
+ sections:
16
+ - sourceRange:
17
+ startLine: 56
18
+ endLine: 90
19
+ targetRange:
20
+ startLine: 21
21
+ endLine: 240
22
+ - documentId: flutter-anti-patterns
23
+ uri: ./anti-patterns.md
24
+ relation: output-from
25
+ sections:
26
+ - sourceRange:
27
+ startLine: 22
28
+ endLine: 75
29
+ targetRange:
30
+ startLine: 90
31
+ endLine: 165
32
+ tags:
33
+ - flutter
34
+ - flutter-web
35
+ - dart-web
36
+ - go-router
37
+ ---
38
+
39
+ # Flutter Web 패턴 (`fe_target = web` 전용)
40
+
41
+ `pipeline.json.fe_target == "web"` 일 때만 적용. Mobile/Desktop 타겟에서는 이 문서의 규칙을 무시한다.
42
+
43
+ ## 1. 프로젝트 활성화
44
+
45
+ 기존 Flutter 프로젝트에 Web 타겟이 없으면:
46
+
47
+ ```bash
48
+ flutter config --enable-web
49
+ flutter create --platforms=web .
50
+ ```
51
+
52
+ `web/index.html`, `web/manifest.json`, `web/favicon.png`, `web/icons/` 가 생성된다.
53
+
54
+ ## 2. 빌드 및 개발 서버
55
+
56
+ | 명령 | 용도 |
57
+ |------|------|
58
+ | `flutter run -d chrome` | 개발 서버 (HMR 포함) |
59
+ | `flutter run -d chrome --web-port 8080` | 포트 고정 |
60
+ | `flutter build web --release` | 프로덕션 빌드 (`build/web/`) |
61
+ | `flutter build web --release --web-renderer canvaskit` | CanvasKit 렌더러 (성능 우선) |
62
+ | `flutter build web --release --web-renderer html` | HTML 렌더러 (호환성 우선, 작은 번들) |
63
+
64
+ > **렌더러 선택**: 모바일 사파리 호환성이 중요하면 `html`, 데스크톱/Chrome 중심이면 `canvaskit` (또는 auto).
65
+
66
+ ## 3. 라우팅 — `go_router` 권장
67
+
68
+ URL 동기화 + 브라우저 history 지원을 위해 `go_router` 사용.
69
+
70
+ ```yaml
71
+ # pubspec.yaml
72
+ dependencies:
73
+ go_router: ^14.0.0
74
+ ```
75
+
76
+ ```dart
77
+ // lib/router/app_router.dart
78
+ import 'package:go_router/go_router.dart';
79
+
80
+ final pAppRouterProvider = Provider<GoRouter>((ref) {
81
+ return GoRouter(
82
+ initialLocation: '/',
83
+ routes: [
84
+ GoRoute(path: '/', builder: (ctx, state) => const HomePage()),
85
+ GoRoute(path: '/login', builder: (ctx, state) => const LoginPage()),
86
+ GoRoute(
87
+ path: '/items/:id',
88
+ builder: (ctx, state) => ItemPage(id: state.pathParameters['id']!),
89
+ ),
90
+ ],
91
+ );
92
+ });
93
+ ```
94
+
95
+ ```dart
96
+ // main.dart
97
+ class App extends ConsumerWidget {
98
+ @override
99
+ Widget build(BuildContext context, WidgetRef ref) {
100
+ final router = ref.watch(pAppRouterProvider);
101
+ return MaterialApp.router(
102
+ routerConfig: router,
103
+ // ...
104
+ );
105
+ }
106
+ }
107
+ ```
108
+
109
+ **Hash routing vs Path routing**:
110
+ - 기본은 hash routing (`/#/login`)
111
+ - Path routing 으로 바꾸려면 `web/index.html` 의 `<base href="/">` 설정 + 서버 fallback (모든 경로 → `index.html`)
112
+
113
+ ## 4. JS Interop (필요할 때만)
114
+
115
+ 크로스플랫폼 코드를 우선하되, Web 전용 API 가 필요하면 `package:web` + `dart:js_interop` 사용.
116
+
117
+ ```dart
118
+ import 'package:web/web.dart' as web;
119
+ import 'dart:js_interop';
120
+
121
+ void copyToClipboard(String text) {
122
+ web.window.navigator.clipboard.writeText(text.toJS);
123
+ }
124
+
125
+ String getUserAgent() => web.window.navigator.userAgent;
126
+ ```
127
+
128
+ **Conditional import 패턴** (cross-platform 코드를 web/io 로 분기):
129
+
130
+ ```dart
131
+ // platform_helper.dart (interface)
132
+ abstract class PlatformHelper {
133
+ String get platformName;
134
+ }
135
+
136
+ PlatformHelper getPlatformHelper() => throw UnimplementedError();
137
+ ```
138
+
139
+ ```dart
140
+ // platform_helper_web.dart
141
+ import 'package:web/web.dart' as web;
142
+ import 'platform_helper.dart';
143
+
144
+ class WebPlatformHelper implements PlatformHelper {
145
+ @override
146
+ String get platformName => 'web (${web.window.navigator.userAgent})';
147
+ }
148
+
149
+ PlatformHelper getPlatformHelper() => WebPlatformHelper();
150
+ ```
151
+
152
+ ```dart
153
+ // platform_helper_io.dart
154
+ import 'dart:io';
155
+ import 'platform_helper.dart';
156
+
157
+ class IoPlatformHelper implements PlatformHelper {
158
+ @override
159
+ String get platformName => 'io (${Platform.operatingSystem})';
160
+ }
161
+
162
+ PlatformHelper getPlatformHelper() => IoPlatformHelper();
163
+ ```
164
+
165
+ ```dart
166
+ // 사용처
167
+ import 'platform_helper.dart'
168
+ if (dart.library.html) 'platform_helper_web.dart'
169
+ if (dart.library.io) 'platform_helper_io.dart';
170
+
171
+ final helper = getPlatformHelper();
172
+ print(helper.platformName);
173
+ ```
174
+
175
+ ## 5. CORS 와 백엔드 연동
176
+
177
+ Flutter Web 은 브라우저에서 실행되므로 백엔드 API 호출 시 **CORS** 가 적용된다.
178
+
179
+ - 개발 단계: 백엔드 Gateway 의 `Access-Control-Allow-Origin` 에 `http://localhost:8080` (또는 `*`) 추가
180
+ - 프로덕션: 동일 origin 또는 명시적 CORS 화이트리스트
181
+ - API 키/인증 토큰은 **HttpOnly cookie** 또는 메모리 보관 (localStorage 는 XSS 위험)
182
+
183
+ `Dio` 인터셉터로 CSRF 토큰 등을 헤더에 추가:
184
+
185
+ ```dart
186
+ final dio = Dio(BaseOptions(
187
+ baseUrl: 'https://api.example.com',
188
+ headers: {'Content-Type': 'application/json'},
189
+ ));
190
+
191
+ dio.interceptors.add(InterceptorsWrapper(
192
+ onRequest: (options, handler) {
193
+ final token = ref.read(pAuthProvider).accessToken;
194
+ if (token != null) {
195
+ options.headers['Authorization'] = 'Bearer $token';
196
+ }
197
+ return handler.next(options);
198
+ },
199
+ ));
200
+ ```
201
+
202
+ ## 6. SEO / Meta Tags
203
+
204
+ `web/index.html` 의 `<head>` 에 SEO 메타 태그 추가:
205
+
206
+ ```html
207
+ <meta name="description" content="Suprema CLUe — Smart Access Control">
208
+ <meta property="og:title" content="CLUe">
209
+ <meta property="og:description" content="...">
210
+ <meta property="og:image" content="/icons/og-image.png">
211
+ <link rel="canonical" href="https://app.example.com">
212
+ ```
213
+
214
+ > SPA 의 SEO 한계: Flutter Web 은 client-side rendering 이므로 검색엔진이 동적 콘텐츠를 인덱싱하지 못할 수 있다. SSR 이 필요하면 Next.js + REST API 패턴을 고려.
215
+
216
+ ## 7. 자산 최적화
217
+
218
+ - 이미지: `assets/images/` 에 두고 `pubspec.yaml` 의 `flutter.assets` 에 등록
219
+ - 폰트: `web/fonts/` 또는 `assets/fonts/` + `pubspec.yaml` 의 `flutter.fonts`
220
+ - 큰 이미지: webp 사용 권장 (`flutter_image_compress` 또는 사전 변환)
221
+ - Tree-shaking: `flutter build web --tree-shake-icons` 로 사용하지 않는 Material 아이콘 제거
222
+
223
+ ## 8. PWA (Progressive Web App)
224
+
225
+ `flutter create --platforms=web` 시 자동 생성되는 `web/manifest.json` 을 채워서 PWA 로 동작:
226
+
227
+ ```json
228
+ {
229
+ "name": "CLUe",
230
+ "short_name": "CLUe",
231
+ "start_url": "/",
232
+ "display": "standalone",
233
+ "background_color": "#FFFFFF",
234
+ "theme_color": "#6682FF",
235
+ "icons": [
236
+ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" },
237
+ { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" }
238
+ ]
239
+ }
240
+ ```
241
+
242
+ Service worker 는 `flutter build web` 시 자동 생성된다 (`flutter_service_worker.js`).
243
+
244
+ ## 9. 디버깅 — Chrome DevTools
245
+
246
+ - `flutter run -d chrome` 후 Chrome 자체 DevTools 열기 (F12)
247
+ - Console 에서 `print()` 출력 확인 가능 (단, 프로덕션 빌드에서는 `print` 금지)
248
+ - Source map 활성화: `--web-renderer html --source-maps` (디버그 빌드 기본)
249
+ - Network 탭에서 API 호출 직접 검사 가능 → Playwright 기반 `evaluator-functional` 도 동일하게 동작
250
+
251
+ ## 10. Eval 인터페이스
252
+
253
+ `fe_target = web` 인 경우 `harness-next.sh` 가 다음과 같이 자동 라우팅:
254
+
255
+ ```
256
+ generator-frontend-flutter (Self-Verification: flutter analyze + flutter test)
257
+ → evaluator-functional (Playwright MCP — http://localhost:포트 E2E)
258
+ → evaluator-visual (Playwright MCP — 스크린샷 + 반응형 + 접근성)
259
+ ```
260
+
261
+ **Generator 의 Self-Verification 단계에서 `flutter build web --release` 가 성공하는지 확인** 후 handoff.
262
+
263
+ ## 11. 호스팅 (참고)
264
+
265
+ | 호스팅 | 빌드 산출물 |
266
+ |--------|------------|
267
+ | Vercel | `build/web/` 디렉토리를 정적 호스팅 |
268
+ | Netlify | 동일 |
269
+ | Firebase Hosting | `firebase deploy --only hosting` (firebase.json 의 public 을 `build/web` 으로) |
270
+ | GitHub Pages | `build/web/` 을 gh-pages 브랜치에 푸시 |
271
+ | 자체 서버 | nginx 로 정적 파일 서빙 + SPA fallback (`try_files $uri /index.html`) |
272
+
273
+ 호스팅 결정은 Planner / 사용자가 함. Generator 는 빌드 산출물만 보장.
@@ -76,40 +76,87 @@ Planner는 Sprint 1 착수 전 반드시 `fe_stack`을 확정하고 `pipeline.js
76
76
 
77
77
  혼재 프로젝트(Web + Flutter mobile)라면 사용자에게 **현 스프린트의 타깃**을 묻는다 — 파이프라인은 한 번에 하나의 `fe_stack`만 취급한다.
78
78
 
79
+ ## 2.5 fe_target 확정 (Flutter 전용)
80
+
81
+ `fe_stack == "flutter"` 인 경우, **반드시** `fe_target` 도 함께 확정해야 한다 (`web` | `mobile` | `desktop`).
82
+ 이는 Flutter Web vs Mobile/Desktop 에서 사용 가능한 API, 빌드 명령, evaluator 가 달라지기 때문이다.
83
+
84
+ ### 자동 감지 (scan-result.json 의 `tech_stack.fe_target`)
85
+
86
+ | 시그널 | fe_target |
87
+ |--------|-----------|
88
+ | `<flutter_root>/web/index.html` 존재 | `web` |
89
+ | `<flutter_root>/android/` 또는 `ios/` 존재, web/ 없음 | `mobile` |
90
+ | `<flutter_root>/macos/`, `windows/`, `linux/` 존재 (mobile/web 없음) | `desktop` |
91
+ | 없음 | `unknown` |
92
+
93
+ ### 사용자 질문 (불명확하거나 multi-target)
94
+
95
+ ```
96
+ Flutter 프로젝트의 타깃을 확인합니다:
97
+
98
+ (A) Web — 브라우저 (Chrome/Safari/Firefox), 컴파일 결과는 HTML+JS+CSS
99
+ (B) Mobile — Android / iOS 네이티브 빌드
100
+ (C) Desktop — macOS / Windows / Linux 네이티브 빌드
101
+
102
+ 감지된 시그널:
103
+ - web/index.html: [있음/없음]
104
+ - android|ios/: [있음/없음]
105
+ - macos|windows|linux/: [있음/없음]
106
+
107
+ 이번 스프린트의 타깃은? (A/B/C)
108
+ ```
109
+
110
+ ### fe_target → Eval 흐름
111
+
112
+ | fe_target | Generator | Eval-Functional | Eval-Visual |
113
+ |-----------|-----------|----------------|-------------|
114
+ | `web` | `generator-frontend-flutter` | `evaluator-functional` (Playwright!) | `evaluator-visual` (Playwright!) |
115
+ | `mobile` | `generator-frontend-flutter` | `evaluator-functional-flutter` (정적 분석) | SKIP |
116
+ | `desktop` | `generator-frontend-flutter` | `evaluator-functional-flutter` (정적 분석) | SKIP |
117
+
118
+ **핵심**: Flutter Web 의 빌드 결과물(HTML/JS/CSS)은 일반 웹앱과 동일하므로 Playwright 기반
119
+ React 경로의 evaluator 를 그대로 재사용한다. Generator-Frontend-Flutter 의 Self-Verification
120
+ 단계에서 `flutter analyze`/`flutter test`/`flutter build web` 정적 검증이 이미 통과한 상태로 handoff 된다.
121
+
79
122
  ## 3. pipeline.json 갱신
80
123
 
81
- `fe_stack` 확정 후 `pipeline.json`에 반드시 추가:
124
+ `fe_stack` + `fe_target` 확정 후 `pipeline.json`에 반드시 추가:
82
125
 
83
126
  ```json
84
127
  {
85
128
  "pipeline": "FULLSTACK",
86
129
  "planner_mode": "full",
87
130
  "fe_stack": "flutter",
131
+ "fe_target": "web",
88
132
  "agents_active": [
89
133
  "planner",
90
134
  "generator-backend",
91
135
  "generator-frontend-flutter",
92
- "evaluator-functional-flutter"
136
+ "evaluator-functional",
137
+ "evaluator-visual"
93
138
  ],
94
139
  "agents_skipped": [
95
140
  "generator-frontend",
96
- "evaluator-functional",
97
- "evaluator-visual"
141
+ "evaluator-functional-flutter"
98
142
  ],
99
- "evaluator_mode": "flutter-native",
100
- "notes": "Flutter 앱 — Playwright 대신 flutter analyze/test + 정적 anti-pattern 검증"
143
+ "evaluator_mode": "playwright-web",
144
+ "notes": "Flutter Web — 컴파일 결과가 HTML+JS+CSS 이므로 React 경로의 Playwright evaluator 사용. Generator 의 Self-Verification 에서 flutter analyze/test 통과 전제."
101
145
  }
102
146
  ```
103
147
 
104
- ### fe_stack → 파이프라인 매핑
105
-
106
- | pipeline | fe_stack | agents_active 예시 |
107
- |----------|----------|-------------------|
108
- | FULLSTACK | react | planner, generator-backend, generator-frontend, evaluator-functional, evaluator-visual |
109
- | FULLSTACK | flutter | planner, generator-backend, generator-frontend-flutter, evaluator-functional-flutter |
110
- | FE-ONLY | react | planner, generator-frontend, evaluator-functional, evaluator-visual |
111
- | FE-ONLY | flutter | planner, generator-frontend-flutter, evaluator-functional-flutter |
112
- | BE-ONLY | (무관) | planner, generator-backend, evaluator-functional |
148
+ ### fe_stack + fe_target → 파이프라인 매핑
149
+
150
+ | pipeline | fe_stack | fe_target | agents_active 예시 |
151
+ |----------|----------|-----------|-------------------|
152
+ | FULLSTACK | react | (n/a) | planner, generator-backend, generator-frontend, evaluator-functional, evaluator-visual |
153
+ | FULLSTACK | flutter | **web** | planner, generator-backend, generator-frontend-flutter, evaluator-functional, evaluator-visual |
154
+ | FULLSTACK | flutter | mobile | planner, generator-backend, generator-frontend-flutter, evaluator-functional-flutter |
155
+ | FULLSTACK | flutter | desktop | planner, generator-backend, generator-frontend-flutter, evaluator-functional-flutter |
156
+ | FE-ONLY | react | (n/a) | planner, generator-frontend, evaluator-functional, evaluator-visual |
157
+ | FE-ONLY | flutter | **web** | planner, generator-frontend-flutter, evaluator-functional, evaluator-visual |
158
+ | FE-ONLY | flutter | mobile | planner, generator-frontend-flutter, evaluator-functional-flutter |
159
+ | BE-ONLY | (무관) | (n/a) | planner, generator-backend, evaluator-functional |
113
160
 
114
161
  ## 4. Flutter 선택 시 추가 작업
115
162