@walwal-harness/cli 2.0.1 → 2.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.
Files changed (29) hide show
  1. package/assets/templates/config.json +41 -1
  2. package/bin/init.js +65 -3
  3. package/package.json +5 -2
  4. package/scripts/harness-next.sh +36 -1
  5. package/scripts/harness-user-prompt-submit.sh +106 -0
  6. package/scripts/lib/harness-render-progress.sh +15 -1
  7. package/scripts/scan-project.sh +24 -2
  8. package/skills/brainstorming/SKILL.md +200 -0
  9. package/skills/brainstorming/references/attribution.md +109 -0
  10. package/skills/brainstorming/references/spec-document-reviewer-prompt.md +49 -0
  11. package/skills/brainstorming/references/visual-companion.md +287 -0
  12. package/skills/brainstorming/scripts/frame-template.html +214 -0
  13. package/skills/brainstorming/scripts/helper.js +88 -0
  14. package/skills/brainstorming/scripts/server.cjs +354 -0
  15. package/skills/brainstorming/scripts/start-server.sh +148 -0
  16. package/skills/brainstorming/scripts/stop-server.sh +56 -0
  17. package/skills/dispatcher/SKILL.md +114 -2
  18. package/skills/dispatcher/references/pipeline-definitions.md +53 -5
  19. package/skills/evaluator-functional-flutter/SKILL.md +198 -0
  20. package/skills/evaluator-functional-flutter/references/ia-compliance.md +77 -0
  21. package/skills/evaluator-functional-flutter/references/scoring-rubric.md +132 -0
  22. package/skills/evaluator-functional-flutter/references/static-check-rules.md +99 -0
  23. package/skills/generator-frontend-flutter/SKILL.md +138 -0
  24. package/skills/generator-frontend-flutter/references/anti-patterns.md +288 -0
  25. package/skills/generator-frontend-flutter/references/api-layer-pattern.md +233 -0
  26. package/skills/generator-frontend-flutter/references/i18n-pattern.md +102 -0
  27. package/skills/generator-frontend-flutter/references/riverpod-pattern.md +199 -0
  28. package/skills/planner/SKILL.md +23 -1
  29. package/skills/planner/references/fe-stack-detection.md +131 -0
@@ -12,9 +12,20 @@
12
12
  "outputs": ["actions/pipeline.json"],
13
13
  "always_first": true
14
14
  },
15
+ "brainstorming": {
16
+ "role": "사용자의 러프한 요구사항을 대화형으로 구체화하여 Planner가 바로 쓸 수 있는 brainstorm-spec.md로 변환. Dispatcher가 사용자에게 '브레인스토밍 필요?' 확인 후에만 호출되는 조건부 에이전트.",
17
+ "skill": "harness-brainstorming",
18
+ "inputs": ["(user conversation)"],
19
+ "outputs": ["actions/brainstorm-spec.md"],
20
+ "conditional": true,
21
+ "invoked_by": "dispatcher (user opt-in)",
22
+ "next_on_complete": "planner",
23
+ "attribution": "Derived from obra/superpowers skills/brainstorming (MIT License)"
24
+ },
15
25
  "planner": {
16
26
  "role": "제품 사양 + API 계약서 + 서비스 분할 설계",
17
27
  "skill": "harness-planner",
28
+ "inputs": ["actions/brainstorm-spec.md (optional)"],
18
29
  "outputs": [
19
30
  "actions/plan.md",
20
31
  "actions/feature-list.json",
@@ -48,16 +59,40 @@
48
59
  "tools": ["playwright:browser_take_screenshot", "playwright:browser_resize", "playwright:browser_snapshot"],
49
60
  "inputs": ["actions/sprint-contract.md", "actions/evaluation-functional.md"],
50
61
  "outputs": ["actions/evaluation-visual.md"]
62
+ },
63
+ "generator-frontend-flutter": {
64
+ "role": "Flutter 앱 개발 — Riverpod, integrated_data_layer(Retrofit), i18n(ARB), build_runner",
65
+ "skill": "harness-generator-frontend-flutter",
66
+ "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
67
+ "outputs": ["code:flutter", "actions/sprint-contract.md"],
68
+ "order": 2,
69
+ "fe_stack": "flutter"
70
+ },
71
+ "evaluator-functional-flutter": {
72
+ "role": "Flutter 앱 검증 — flutter analyze, flutter test, build_runner 일관성, 안티패턴 정적 검증",
73
+ "skill": "harness-evaluator-functional-flutter",
74
+ "tools": ["bash:flutter", "bash:dart"],
75
+ "inputs": ["actions/sprint-contract.md"],
76
+ "outputs": ["actions/evaluation-functional.md"],
77
+ "fe_stack": "flutter"
51
78
  }
52
79
  },
53
80
  "flow": {
54
81
  "sequence": ["dispatcher", "planner", "generator-backend", "generator-frontend", "evaluator-functional", "evaluator-visual"],
55
82
  "pipeline_selection": {
56
- "comment": "Dispatcher가 pipeline.json으로 활성 에이전트를 결정. 이하 sequence에서 스킵 대상 제외.",
83
+ "comment": "Dispatcher가 pipeline.json으로 활성 에이전트를 결정. fe_stack 필드로 React/Flutter를 구분. harness-next.sh가 pipeline.json.fe_stack 을 읽어 generator-frontend/evaluator-functional 을 Flutter 변형으로 치환한다.",
57
84
  "pipelines": {
58
85
  "FULLSTACK": ["planner", "generator-backend", "generator-frontend", "evaluator-functional", "evaluator-visual"],
59
86
  "FE-ONLY": ["planner:light", "generator-frontend", "evaluator-functional", "evaluator-visual"],
60
87
  "BE-ONLY": ["planner", "generator-backend", "evaluator-functional:api-only"]
88
+ },
89
+ "fe_stack_substitution": {
90
+ "comment": "pipeline.json.fe_stack=flutter 이면 아래 치환을 적용. evaluator-visual은 skip.",
91
+ "flutter": {
92
+ "generator-frontend": "generator-frontend-flutter",
93
+ "evaluator-functional": "evaluator-functional-flutter",
94
+ "evaluator-visual": "__skip__"
95
+ }
61
96
  }
62
97
  },
63
98
  "sprint_execution": {
@@ -75,6 +110,11 @@
75
110
  "state_log": ".harness/progress.log",
76
111
  "next_prompt_file": ".harness/next-prompt.txt"
77
112
  },
113
+ "behavior": {
114
+ "comment": "하네스 동작 플래그. UserPromptSubmit 훅이 이 값을 읽어 자동 라우팅을 결정한다.",
115
+ "auto_route_dispatcher": true,
116
+ "auto_route_dispatcher_description": "true 이면 사용자의 모든 프롬프트가 harness-dispatcher 경유로 분류/라우팅 된다. 사용자가 'harness skip' 등을 말하면 단일 메시지 한정으로 건너뛴다."
117
+ },
78
118
  "paths": {
79
119
  "actions": ".harness/actions",
80
120
  "archive": ".harness/archive",
package/bin/init.js CHANGED
@@ -295,6 +295,60 @@ function installSessionHook() {
295
295
  }
296
296
  }
297
297
 
298
+ // ─────────────────────────────────────────
299
+ // 3c. UserPromptSubmit hook (auto dispatcher routing)
300
+ // ─────────────────────────────────────────
301
+ function installUserPromptSubmitHook() {
302
+ log('Installing UserPromptSubmit hook (auto dispatcher routing)...');
303
+
304
+ const settingsDir = path.join(PROJECT_ROOT, '.claude');
305
+ const settingsFile = path.join(settingsDir, 'settings.json');
306
+
307
+ ensureDir(settingsDir);
308
+
309
+ let settings = {};
310
+ if (fileExists(settingsFile)) {
311
+ try {
312
+ settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
313
+ } catch (e) {
314
+ log('WARNING: Could not parse existing .claude/settings.json, creating new');
315
+ }
316
+ }
317
+
318
+ if (!settings.hooks) settings.hooks = {};
319
+ if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
320
+
321
+ const hookCmd = 'bash scripts/harness-user-prompt-submit.sh';
322
+
323
+ // Detect existing harness UserPromptSubmit hook (any shape)
324
+ const alreadyInstalled = settings.hooks.UserPromptSubmit.some((entry) => {
325
+ if (!entry || typeof entry !== 'object') return false;
326
+ if (Array.isArray(entry.hooks)) {
327
+ return entry.hooks.some(
328
+ (h) => h && h.command && h.command.includes('harness-user-prompt-submit')
329
+ );
330
+ }
331
+ if (entry.type === 'command' && entry.command) {
332
+ return entry.command.includes('harness-user-prompt-submit');
333
+ }
334
+ return false;
335
+ });
336
+
337
+ if (!alreadyInstalled) {
338
+ settings.hooks.UserPromptSubmit.push({
339
+ matcher: '',
340
+ hooks: [{ type: 'command', command: hookCmd }]
341
+ });
342
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
343
+ log('UserPromptSubmit hook installed in .claude/settings.json');
344
+ log(' → All prompts will be routed through harness-dispatcher');
345
+ log(' → Opt-out per message: say "harness skip" or "without harness"');
346
+ log(' → Disable globally: set .harness/config.json behavior.auto_route_dispatcher = false');
347
+ } else {
348
+ log('UserPromptSubmit hook already installed');
349
+ }
350
+ }
351
+
298
352
  // ─────────────────────────────────────────
299
353
  // 4. AGENTS.md + CLAUDE.md
300
354
  // ─────────────────────────────────────────
@@ -453,9 +507,16 @@ What it does:
453
507
  1. Scaffolds .harness/ directory (actions, archive, gotchas, config)
454
508
  2. Installs skills to .claude/skills/ (dispatcher, planner, generators, evaluators)
455
509
  3. Copies helper scripts to scripts/
456
- 4. Creates AGENTS.md + CLAUDE.md symlink
457
- 5. Checks Playwright MCP configuration
458
- 6. Checks recommended external skills (Vercel, design skills)
510
+ 4. Registers SessionStart hook (boot-time progress render)
511
+ 5. Registers UserPromptSubmit hook (auto-route every prompt through harness-dispatcher)
512
+ 6. Creates AGENTS.md + CLAUDE.md symlink
513
+ 7. Checks Playwright MCP configuration
514
+ 8. Checks recommended external skills (Vercel, design skills)
515
+
516
+ Auto routing:
517
+ Every user prompt is routed through harness-dispatcher.
518
+ Per-message opt-out: say "harness skip" or "harness 없이".
519
+ Global disable: edit .harness/config.json → behavior.auto_route_dispatcher = false
459
520
 
460
521
  After init:
461
522
  1. Restart Claude Code session (exit and re-enter) for skills to load
@@ -487,6 +548,7 @@ function main() {
487
548
  installSkills();
488
549
  installScripts();
489
550
  installSessionHook();
551
+ installUserPromptSubmitHook();
490
552
  setupAgentsMd();
491
553
  checkPlaywrightMcp();
492
554
  checkRecommendedSkills();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "2.0.1",
4
- "description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual) with Gotcha management",
3
+ "version": "2.3.0",
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"
7
7
  },
@@ -16,11 +16,14 @@
16
16
  "ai-agent",
17
17
  "claude-code",
18
18
  "skill",
19
+ "brainstorming",
19
20
  "planner",
20
21
  "generator",
21
22
  "evaluator",
22
23
  "nestjs",
23
24
  "react",
25
+ "flutter",
26
+ "riverpod",
24
27
  "playwright"
25
28
  ],
26
29
  "author": "",
@@ -18,6 +18,7 @@ PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || {
18
18
 
19
19
  PROGRESS="$PROJECT_ROOT/.harness/progress.json"
20
20
  CONFIG="$PROJECT_ROOT/.harness/config.json"
21
+ PIPELINE_JSON="$PROJECT_ROOT/.harness/actions/pipeline.json"
21
22
  NEXT_PROMPT="$PROJECT_ROOT/.harness/next-prompt.txt"
22
23
 
23
24
  check_jq || exit 1
@@ -40,6 +41,29 @@ next_agent=$(jq -r '.next_agent // "null"' "$PROGRESS")
40
41
  retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
41
42
  max_retries=$(jq -r '.flow.max_retries_per_sprint // 10' "$CONFIG" 2>/dev/null || echo 10)
42
43
 
44
+ # fe_stack 치환 (Flutter 지원) — pipeline.json 에서 읽음
45
+ fe_stack="react"
46
+ if [ -f "$PIPELINE_JSON" ]; then
47
+ fe_stack=$(jq -r '.fe_stack // "react"' "$PIPELINE_JSON" 2>/dev/null || echo "react")
48
+ fi
49
+
50
+ # ─────────────────────────────────────────
51
+ # fe_stack 치환 헬퍼
52
+ # pipeline_selection.pipelines에서 읽은 에이전트명을 fe_stack에 따라 치환
53
+ # - react: 그대로
54
+ # - flutter: generator-frontend → generator-frontend-flutter 등, __skip__ 은 건너뜀
55
+ # ─────────────────────────────────────────
56
+ substitute_fe_stack() {
57
+ local agent="$1"
58
+ if [ "$fe_stack" != "flutter" ]; then
59
+ echo "$agent"
60
+ return
61
+ fi
62
+ local sub
63
+ sub=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.flutter[\"${agent}\"] // \"${agent}\"" "$CONFIG" 2>/dev/null)
64
+ echo "$sub"
65
+ }
66
+
43
67
  # ─────────────────────────────────────────
44
68
  # Determine next agent
45
69
  # ─────────────────────────────────────────
@@ -73,8 +97,18 @@ compute_next_agent() {
73
97
  return
74
98
  fi
75
99
 
100
+ local -a raw_agents
101
+ mapfile -t raw_agents < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null | sed 's/:.*//')
102
+
103
+ # fe_stack 치환 + __skip__ 필터링
76
104
  local -a agents
77
- mapfile -t agents < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null | sed 's/:.*//')
105
+ for a in "${raw_agents[@]}"; do
106
+ local sub
107
+ sub=$(substitute_fe_stack "$a")
108
+ if [ "$sub" != "__skip__" ]; then
109
+ agents+=("$sub")
110
+ fi
111
+ done
78
112
 
79
113
  local found=false
80
114
  for agent in "${agents[@]}"; do
@@ -82,6 +116,7 @@ compute_next_agent() {
82
116
  echo "$agent"
83
117
  return
84
118
  fi
119
+ # current 비교 시에도 치환된 이름으로 (Flutter 변형 에이전트가 실행되는 경우)
85
120
  if [ "$agent" = "$current" ]; then
86
121
  found=true
87
122
  fi
@@ -0,0 +1,106 @@
1
+ #!/bin/bash
2
+ # harness-user-prompt-submit.sh
3
+ # ─────────────────────────────────────────
4
+ # Claude Code UserPromptSubmit hook
5
+ # 모든 사용자 프롬프트를 harness-dispatcher 로 라우팅하도록 Claude 에게
6
+ # 지시하는 컨텍스트를 stdout 에 주입한다.
7
+ #
8
+ # 활성 조건:
9
+ # 1) 현재 cwd 에 .harness/config.json 존재
10
+ # 2) .harness/config.json 의 behavior.auto_route_dispatcher != false
11
+ #
12
+ # 비활성 상황에서는 아무것도 출력하지 않고 exit 0 (pass-through).
13
+ # ─────────────────────────────────────────
14
+ set -e
15
+
16
+ # stdin 의 JSON payload 읽기 (Claude Code 가 {prompt, cwd, session_id, ...} 전달)
17
+ INPUT=$(cat)
18
+
19
+ # cwd 추출 (payload 에 없으면 PWD fallback)
20
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)
21
+ if [ -z "$CWD" ]; then
22
+ CWD="$PWD"
23
+ fi
24
+
25
+ # 조건 1: 하네스 초기화 확인
26
+ if [ ! -f "$CWD/.harness/config.json" ]; then
27
+ exit 0
28
+ fi
29
+
30
+ # 조건 2: opt-out 플래그 확인 (기본값 true)
31
+ AUTO_ROUTE="true"
32
+ if command -v jq >/dev/null 2>&1; then
33
+ AUTO_ROUTE=$(jq -r '.behavior.auto_route_dispatcher // true' "$CWD/.harness/config.json" 2>/dev/null || echo "true")
34
+ fi
35
+ if [ "$AUTO_ROUTE" != "true" ]; then
36
+ exit 0
37
+ fi
38
+
39
+ # 프롬프트 내용 추출 (opt-out 문구 감지용)
40
+ PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null || true)
41
+ # 사용자가 명시적으로 건너뛰기 요청하면 pass-through
42
+ if echo "$PROMPT" | grep -qiE "harness\s*(skip|off|bypass|없이)|without\s*harness|just\s*(answer|chat|reply)"; then
43
+ exit 0
44
+ fi
45
+
46
+ # 현재 세션 상태 읽기
47
+ PIPELINE="none"
48
+ CURRENT_AGENT="none"
49
+ NEXT_AGENT="none"
50
+ SPRINT_NUM="0"
51
+ SPRINT_STATUS="init"
52
+ FE_STACK="react"
53
+
54
+ if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
55
+ PIPELINE=$(jq -r '.pipeline // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
56
+ CURRENT_AGENT=$(jq -r '.current_agent // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
57
+ NEXT_AGENT=$(jq -r '.next_agent // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
58
+ SPRINT_NUM=$(jq -r '.sprint.number // 0' "$CWD/.harness/progress.json" 2>/dev/null || echo "0")
59
+ SPRINT_STATUS=$(jq -r '.sprint.status // "init"' "$CWD/.harness/progress.json" 2>/dev/null || echo "init")
60
+ fi
61
+
62
+ if [ -f "$CWD/.harness/actions/pipeline.json" ] && command -v jq >/dev/null 2>&1; then
63
+ FE_STACK=$(jq -r '.fe_stack // "react"' "$CWD/.harness/actions/pipeline.json" 2>/dev/null || echo "react")
64
+ fi
65
+
66
+ # ─────────────────────────────────────────
67
+ # Context 주입 (stdout → Claude 컨텍스트)
68
+ # ─────────────────────────────────────────
69
+ cat <<EOF
70
+ [walwal-harness] Auto-routing is ACTIVE for this project.
71
+
72
+ 이 프로젝트는 walwal-harness 가 초기화되어 있으며, auto_route_dispatcher 플래그가
73
+ 켜져 있습니다. 사용자의 모든 프롬프트는 **harness-dispatcher** 스킬을 통해
74
+ 분류/라우팅된 뒤 처리되어야 합니다.
75
+
76
+ ## 분류 규칙 (Dispatcher Request Classification)
77
+ - **기능 요청** (만들어줘, 추가, 고쳐줘, PRD, OpenAPI) → Pipeline Flow
78
+ - **실수 지적** (아니, 잘못됐어, 그렇게 하면 안 돼, X로 해야지) → Gotcha Flow
79
+ - **혼합** → Gotcha 먼저 기록 → Pipeline 이어서
80
+ - **메타/인사/Claude 자체 질문** → 짧게 일반 응답 (dispatcher skip 허용)
81
+
82
+ ## 현재 harness 세션 상태
83
+ - pipeline: $PIPELINE
84
+ - current_agent: $CURRENT_AGENT
85
+ - next_agent: $NEXT_AGENT
86
+ - sprint: $SPRINT_NUM ($SPRINT_STATUS)
87
+ - fe_stack: $FE_STACK
88
+
89
+ ## 지시
90
+ 1. 먼저 위 상태를 읽고, 사용자 메시지가 어떤 분류에 해당하는지 판단
91
+ 2. **pipeline 이 'none' 또는 dispatcher 미실행 상태**면 harness-dispatcher 스킬 호출
92
+ 3. **pipeline 이 이미 활성**이면:
93
+ - 실수 지적이면 → dispatcher 로 gotcha 기록
94
+ - 기능 연속 작업이면 → next_agent 또는 current_agent 의 컨텍스트로 계속
95
+ 4. 응답 전 반드시 \`.harness/progress.json\` 을 읽고 세션 경계 프로토콜 준수
96
+
97
+ ## 이 라우팅을 건너뛰려면
98
+ - **단일 메시지**: 사용자가 "harness skip", "without harness", "harness 없이", "just answer" 등을 명시
99
+ - **전역 비활성**: \`.harness/config.json\` 에서 \`behavior.auto_route_dispatcher = false\` 설정
100
+
101
+ ## 참고
102
+ 이 안내는 \`scripts/harness-user-prompt-submit.sh\` UserPromptSubmit 훅에 의해
103
+ 매 프롬프트마다 자동 주입됩니다. 중복 안내처럼 보여도 무시하지 말고 위 규칙을 따르세요.
104
+ EOF
105
+
106
+ exit 0
@@ -282,12 +282,17 @@ render_progress() {
282
282
  render_agent_bar() {
283
283
  local PROGRESS="${1:-.}/.harness/progress.json"
284
284
  local CONFIG="${1:-.}/.harness/config.json"
285
+ local PIPELINE_JSON="${1:-.}/.harness/actions/pipeline.json"
285
286
 
286
287
  if [ ! -f "$PROGRESS" ] || [ ! -f "$CONFIG" ]; then return 1; fi
287
288
 
288
- local pipeline current_agent
289
+ local pipeline current_agent fe_stack
289
290
  pipeline=$(jq -r '.pipeline // "unknown"' "$PROGRESS")
290
291
  current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS")
292
+ fe_stack="react"
293
+ if [ -f "$PIPELINE_JSON" ]; then
294
+ fe_stack=$(jq -r '.fe_stack // "react"' "$PIPELINE_JSON" 2>/dev/null || echo "react")
295
+ fi
291
296
 
292
297
  local completed_agents
293
298
  completed_agents=$(jq -r '.completed_agents // [] | .[]' "$PROGRESS")
@@ -299,6 +304,15 @@ render_agent_bar() {
299
304
 
300
305
  while IFS= read -r agent; do
301
306
  agent=$(echo "$agent" | sed 's/:.*//') # strip mode suffix like :light, :api-only
307
+
308
+ # fe_stack 치환 적용
309
+ if [ "$fe_stack" = "flutter" ]; then
310
+ local sub
311
+ sub=$(jq -r ".flow.pipeline_selection.fe_stack_substitution.flutter[\"${agent}\"] // \"${agent}\"" "$CONFIG" 2>/dev/null)
312
+ if [ "$sub" = "__skip__" ]; then continue; fi
313
+ agent="$sub"
314
+ fi
315
+
302
316
  if [ "$first" = true ]; then
303
317
  first=false
304
318
  else
@@ -61,7 +61,13 @@ elif [ -f "${PROJECT_ROOT}/pom.xml" ] || [ -f "${PROJECT_ROOT}/build.gradle" ];
61
61
  fi
62
62
 
63
63
  # Frontend
64
- if [ -f "${PROJECT_ROOT}/next.config.js" ] || [ -f "${PROJECT_ROOT}/next.config.ts" ] || [ -f "${PROJECT_ROOT}/next.config.mjs" ]; then
64
+ # Flutter 우선 감지 — pubspec.yaml 존재하면 Flutter 프로젝트로 판정 (TECH_LANG도 보정)
65
+ FE_STACK="react" # react | flutter (기본값은 react 계열)
66
+ if [ -f "${PROJECT_ROOT}/pubspec.yaml" ] && grep -q "flutter:" "${PROJECT_ROOT}/pubspec.yaml" 2>/dev/null; then
67
+ TECH_FRONTEND="flutter"
68
+ TECH_LANG="dart"
69
+ FE_STACK="flutter"
70
+ elif [ -f "${PROJECT_ROOT}/next.config.js" ] || [ -f "${PROJECT_ROOT}/next.config.ts" ] || [ -f "${PROJECT_ROOT}/next.config.mjs" ]; then
65
71
  TECH_FRONTEND="nextjs"
66
72
  elif [ -f "${PROJECT_ROOT}/vite.config.ts" ] || [ -f "${PROJECT_ROOT}/vite.config.js" ]; then
67
73
  TECH_FRONTEND="vite-react"
@@ -75,11 +81,26 @@ fi
75
81
  if [ -d "${PROJECT_ROOT}/apps/web" ]; then
76
82
  if [ -f "${PROJECT_ROOT}/apps/web/next.config.js" ] || [ -f "${PROJECT_ROOT}/apps/web/next.config.ts" ]; then
77
83
  TECH_FRONTEND="nextjs"
84
+ FE_STACK="react"
78
85
  elif [ -f "${PROJECT_ROOT}/apps/web/vite.config.ts" ]; then
79
86
  TECH_FRONTEND="vite-react"
87
+ FE_STACK="react"
80
88
  fi
81
89
  fi
82
90
 
91
+ # Flutter 서브디렉토리 감지 (monorepo 또는 서브 프로젝트 케이스)
92
+ if [ "$TECH_FRONTEND" = "unknown" ]; then
93
+ # 대표적인 Flutter 서브폴더 이름을 얕게 탐색
94
+ for d in apps/mobile mobile clue_mobile_app flutter_app; do
95
+ if [ -f "${PROJECT_ROOT}/${d}/pubspec.yaml" ] && grep -q "flutter:" "${PROJECT_ROOT}/${d}/pubspec.yaml" 2>/dev/null; then
96
+ TECH_FRONTEND="flutter"
97
+ TECH_LANG="dart"
98
+ FE_STACK="flutter"
99
+ break
100
+ fi
101
+ done
102
+ fi
103
+
83
104
  # Database
84
105
  if grep -rq "typeorm\|prisma\|sequelize\|knex" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
85
106
  if grep -q "pg\|postgres" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
@@ -203,6 +224,7 @@ cat > "$OUTPUT" << JSONEOF
203
224
  "tech_stack": {
204
225
  "backend": "${TECH_BACKEND}",
205
226
  "frontend": "${TECH_FRONTEND}",
227
+ "fe_stack": "${FE_STACK}",
206
228
  "database": "${TECH_DB}",
207
229
  "monorepo": "${TECH_MONOREPO}",
208
230
  "language": "${TECH_LANG}"
@@ -260,7 +282,7 @@ echo "=== Scan Complete ==="
260
282
  echo "Output: ${OUTPUT}"
261
283
  echo ""
262
284
  echo "--- Summary ---"
263
- echo "Tech Stack: ${TECH_BACKEND} / ${TECH_FRONTEND} / ${TECH_DB}"
285
+ echo "Tech Stack: ${TECH_BACKEND} / ${TECH_FRONTEND} (fe_stack=${FE_STACK}) / ${TECH_DB}"
264
286
  echo "Monorepo: ${TECH_MONOREPO}"
265
287
  echo "OpenAPI: ${OPENAPI}"
266
288
  echo "Git: ${GIT_INIT} (${GIT_COMMITS} commits, branch: ${GIT_BRANCH})"
@@ -0,0 +1,200 @@
1
+ ---
2
+ name: harness-brainstorming
3
+ description: "사용자의 러프한(바이브코딩) 요구사항을 대화형으로 구체화하여 Planner가 바로 쓸 수 있는 디자인/스펙 문서(.harness/actions/brainstorm-spec.md)로 만든다. Dispatcher가 사용자에게 '브레인스토밍 필요?' 라고 확인한 뒤에만 호출된다. 원본: obra/superpowers MIT."
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Brainstormer — 요구사항 구체화 (obra/superpowers 파생)
8
+
9
+ > **Attribution**: 이 스킬의 방법론은 [obra/superpowers](https://github.com/obra/superpowers)
10
+ > (MIT License) 의 `skills/brainstorming` 을 walwal-harness 파이프라인에 맞게 이식한 것입니다.
11
+ > 원본 저자(J. Hubbard 등)에게 감사드립니다. 상세 원본 텍스트 + 라이센스는
12
+ > [references/attribution.md](references/attribution.md) 참조.
13
+
14
+ ## 역할
15
+
16
+ walwal-harness 에서 Brainstormer 의 유일한 목적은 **러프한 바이브코딩 아이디어를
17
+ Planner 가 곧바로 plan.md / feature-list.json / api-contract.json 으로 변환할 수 있는
18
+ 수준의 fit 한 요구사항 문서**로 구체화하는 것이다. 결과물은 단 하나:
19
+
20
+ ```
21
+ .harness/actions/brainstorm-spec.md
22
+ ```
23
+
24
+ 이 파일이 생성되면 Planner 는 이것을 PRD 대체 입력으로 읽는다.
25
+
26
+ ## 언제 호출되는가
27
+
28
+ **Dispatcher 가 사용자에게 명시적으로 "브레인스토밍 과정이 필요합니까?" 라고 묻고
29
+ 사용자가 승인한 경우에만 실행된다.** 이외의 모든 경로 (피드백, 이터레이션, 직접 에이전트
30
+ 명령, Gotcha 교정, 스프린트 retry) 에서는 호출되지 않는다. 사용자가 명확한 PRD 를
31
+ 제공하거나 브레인스토밍 불필요를 선언하면 Dispatcher 는 곧바로 `next_agent = planner`
32
+ 로 라우팅한다.
33
+
34
+ ## Session Boundary Protocol
35
+
36
+ ### On Start
37
+ 1. `.harness/progress.json` 읽기 — `next_agent == "brainstormer"` 인지 확인
38
+ - 아니면 즉시 STOP + "Dispatcher 를 먼저 실행하세요" 안내
39
+ 2. progress.json 업데이트: `current_agent` → `"brainstormer"`, `agent_status` → `"running"`, `updated_at` 갱신
40
+ 3. **Skip 조건 검사**: `.harness/actions/brainstorm-spec.md` 가 이미 존재하고
41
+ 사용자가 "새로 브레인스토밍" 을 명시하지 않았다면:
42
+ - 사용자에게 "기존 brainstorm-spec.md 가 있습니다. 재사용(Y) / 새로 작성(N)?" 질문
43
+ - Y → 즉시 On Complete 로 이동 (interactive 단계 전부 스킵)
44
+ - N → 기존 파일을 `.harness/archive/brainstorm-spec-<timestamp>.md` 로 이동 후 interactive 시작
45
+
46
+ ### On Complete
47
+ 1. `.harness/actions/brainstorm-spec.md` 가 존재하고 "User Review Gate" 를 통과했는지 확인
48
+ 2. progress.json 업데이트:
49
+ - `agent_status` → `"completed"`
50
+ - `completed_agents` 에 `"brainstormer"` 추가
51
+ - `next_agent` → `"planner"`
52
+ 3. `.harness/progress.log` 에 요약 추가: `Brainstormer → Planner (spec: <경로>)`
53
+ 4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
54
+ 5. 출력: `"✓ Brainstormer 완료. bash scripts/harness-next.sh 실행하여 Planner 단계로 진행."`
55
+
56
+ ### On Fail / Abort
57
+ 사용자가 중간에 "중단" / "취소" / "abort" 를 요청하면:
58
+ 1. progress.json 업데이트: `agent_status` → `"blocked"`, `failure.message` → `"user aborted brainstorming"`
59
+ 2. 작성 중이던 `brainstorm-spec.md` 는 `.harness/archive/brainstorm-spec-draft-<timestamp>.md` 로 이동
60
+ 3. **STOP**. 사용자에게 "재시작 원하면 'dispatcher 다시'" 안내
61
+
62
+ ## HARD-GATE (원본에서 계승)
63
+
64
+ > **Planner / Generator / 어떤 구현 에이전트도 호출하지 말라. 사용자가 "이 디자인 승인한다" 고
65
+ > 명시적으로 말하기 전까지는 brainstorm-spec.md 를 저장하지도 않는다.**
66
+
67
+ 아무리 간단해 보이는 프로젝트라도 이 게이트를 건너뛰지 않는다. "Simple" 한 프로젝트가
68
+ 오히려 검증되지 않은 가정 때문에 가장 많은 시간을 낭비시킨다.
69
+
70
+ ## 체크리스트 (Brainstormer 내부 워크플로우)
71
+
72
+ 각 항목을 **순서대로** 수행하고 각 단계 끝에서 사용자 확인을 받는다:
73
+
74
+ 1. **프로젝트 컨텍스트 탐색** — 기존 파일, docs, 최근 커밋, `AGENTS.md`, `.harness/actions/`
75
+ 2. **Visual Companion 제안** (시각 요소가 많을 예정이라면, 단일 메시지로) — [visual-companion 가이드](references/visual-companion.md)
76
+ 3. **명확화 질문** — 한 번에 **하나씩**, 목적/제약/성공 기준 파악 (가능하면 객관식)
77
+ 4. **스코프 점검** — 여러 독립 서브시스템이 섞여 있으면 먼저 분할 제안 (예: "플랫폼 A + 채팅 + 결제" → 먼저 A 만)
78
+ 5. **2~3 개 접근법 제시** — 각 접근의 장단점 + 추천안 + 추천 이유
79
+ 6. **디자인 섹션별 제시** — 복잡도에 맞춰 섹션별 길이 조절, 각 섹션 끝에 사용자 확인
80
+ - 아키텍처, 컴포넌트, 데이터 흐름, 에러 처리, 테스트
81
+ 7. **사용자 디자인 승인** 받기
82
+ 8. **brainstorm-spec.md 저장** — `.harness/actions/brainstorm-spec.md` 에 커밋 (아래 스펙 포맷)
83
+ 9. **Spec Self-Review** — 플레이스홀더, 모순, 애매성, 스코프, YAGNI 체크. 발견 시 inline 수정
84
+ 상세 → [spec-document-reviewer-prompt.md](references/spec-document-reviewer-prompt.md)
85
+ 10. **User Review Gate** — 사용자에게 작성된 파일을 리뷰 요청. 변경 요청 시 → 8번으로 복귀
86
+ 11. **Planner 로 핸드오프** — Session Boundary Protocol On Complete 실행
87
+
88
+ ## 핵심 원칙 (원본 계승)
89
+
90
+ - **한 번에 한 질문** — 질문 폭탄 금지
91
+ - **객관식 우선** — 가능하면 A/B/C/D 형태로
92
+ - **YAGNI** — 요청되지 않은 기능은 전부 제거
93
+ - **대안 탐색** — 결정하기 전에 항상 2-3 개 접근법 제시
94
+ - **점진적 검증** — 섹션별로 제시하고 승인 후 다음
95
+ - **유연성** — 중간에 "이건 안 맞는 것 같아" 가 나오면 언제든 이전 단계로 복귀
96
+
97
+ ## 출력 포맷: `.harness/actions/brainstorm-spec.md`
98
+
99
+ ```markdown
100
+ ---
101
+ docmeta:
102
+ id: brainstorm-spec
103
+ title: Brainstorm Spec — <프로젝트/기능 이름>
104
+ type: output
105
+ createdAt: <ISO 8601>
106
+ updatedAt: <ISO 8601>
107
+ source:
108
+ producer: agent
109
+ skillId: harness-brainstorming
110
+ inputs:
111
+ - documentId: user-conversation
112
+ uri: (inline — 사용자와의 대화 전체)
113
+ relation: output-from
114
+ tags: [brainstorming, spec, planner-input]
115
+ ---
116
+
117
+ # Brainstorm Spec — <프로젝트/기능 이름>
118
+
119
+ ## 1. 목적 (Purpose)
120
+ <무엇을, 왜 만드는가 — 1-2 문단>
121
+
122
+ ## 2. 성공 기준 (Success Criteria)
123
+ - [ ] ...
124
+ - [ ] ...
125
+
126
+ ## 3. 스코프 (Scope)
127
+ ### In
128
+ - ...
129
+ ### Out (명시적 제외)
130
+ - ...
131
+
132
+ ## 4. 제약 (Constraints)
133
+ - 기술 스택:
134
+ - 성능:
135
+ - 보안/컴플라이언스:
136
+ - 기타:
137
+
138
+ ## 5. 선택된 접근법 (Chosen Approach)
139
+ <2-3 개 대안 중 사용자가 승인한 것 + 이유>
140
+
141
+ ### 고려했던 대안
142
+ - **Alt A**: ... (장/단점)
143
+ - **Alt B**: ... (장/단점)
144
+
145
+ ## 6. 아키텍처 스케치
146
+ <구성요소 + 상호작용 — ASCII 다이어그램 또는 간단한 설명>
147
+
148
+ ## 7. 주요 컴포넌트 / 엔티티
149
+ - Component A — 책임:
150
+ - Entity X — 필드/관계:
151
+
152
+ ## 8. 데이터 흐름 (Data Flow)
153
+ <입력 → 처리 → 출력 → 저장 경로>
154
+
155
+ ## 9. 에러 처리 전략
156
+ - 경계 에러:
157
+ - 내부 에러:
158
+ - 사용자 노출 메시지:
159
+
160
+ ## 10. 테스트 전략 (High-level)
161
+ - Unit:
162
+ - Integration:
163
+ - E2E:
164
+
165
+ ## 11. Open Questions (Planner 가 확정할 것)
166
+ - Q1: ...
167
+ - Q2: ...
168
+
169
+ ## 12. 사용자 승인 로그
170
+ - YYYY-MM-DD HH:MM — 섹션 <N> 승인 ("네, 좋아요")
171
+ - YYYY-MM-DD HH:MM — 디자인 전체 승인
172
+ - YYYY-MM-DD HH:MM — 작성 스펙 파일 리뷰 통과
173
+ ```
174
+
175
+ ## Planner 와의 인터페이스
176
+
177
+ Planner 는 On Start 에 이 파일을 읽고:
178
+
179
+ 1. `1. 목적`, `2. 성공 기준`, `3. 스코프` → `plan.md` 의 사양서 도입부로 직접 반영
180
+ 2. `5. 선택된 접근법`, `6. 아키텍처 스케치` → MSA 서비스 분할 (full 모드) 또는 컴포넌트 설계의 베이스
181
+ 3. `7. 주요 컴포넌트 / 엔티티` → `feature-list.json` 초기 feature 목록 시드
182
+ 4. `11. Open Questions` → Planner 가 해소 (API 계약으로 확정)
183
+
184
+ Planner 는 brainstorm-spec.md 에 있는 **승인된 결정** 을 뒤엎지 않는다. 필요한 경우
185
+ `## Change Request` 섹션을 추가해 Dispatcher 를 통한 재논의 요청.
186
+
187
+ ## 금지 사항
188
+
189
+ - `brainstorm-spec.md` 승인 전에 `plan.md`, `feature-list.json`, `api-contract.json` 작성
190
+ - 코드 작성 / scaffolding / 파일 생성 (scripts/visual-companion 제외)
191
+ - 여러 질문을 한 메시지에 몰아 넣기
192
+ - 사용자 승인 없이 임의로 다음 섹션으로 진행
193
+ - `.harness/archive/` 쓰기 금지 원칙 위반 (skip/abort 시 draft 이동 외에는 금지)
194
+
195
+ ## 참고
196
+
197
+ - 원본 방법론 + 라이센스 → [references/attribution.md](references/attribution.md)
198
+ - Visual Companion (브라우저 기반 목업/다이어그램 서버) → [references/visual-companion.md](references/visual-companion.md)
199
+ - Spec self-review 상세 → [references/spec-document-reviewer-prompt.md](references/spec-document-reviewer-prompt.md)
200
+ - Visual Companion 실행 스크립트 → `scripts/start-server.sh` (실행은 선택)