@walwal-harness/cli 5.5.3 → 5.6.1

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
@@ -92,7 +92,17 @@ npx walwal-harness team
92
92
  - 단축키: `tmux prefix + 방향키` 로 패널 이동, `prefix + z` 로 확대/축소.
93
93
  - 종료: `npx walwal-harness team --kill`.
94
94
 
95
- ### Step 5. Gotcha 등록 — 같은 실수 반복 막기
95
+ ### Step 5. Feedback 등록 — 학습 누적
96
+
97
+ 대화 중 사용자 피드백은 **성격에 따라 3개 저장소** 중 하나로 자동 분류됩니다.
98
+
99
+ | 유형 | 성격 | 시그널 | 저장 위치 | ID |
100
+ |------|------|--------|----------|-----|
101
+ | **Gotcha** | 에이전트 실수(부정) | "~하지 마", "잘못됐어" | `.harness/gotchas/<agent>.md` | `G-NNN` |
102
+ | **Convention** | 하우스 스타일(긍정) | "~해야 해", "이렇게 해줘" | `.harness/conventions/<scope>.md` | `C-NNN` |
103
+ | **Memory** | 전체 공통 교훈 | "모든 에이전트가~" | `.harness/memory.md` | `M-NNN` |
104
+
105
+ #### 5a. Gotcha — "이러면 안 돼"
96
106
 
97
107
  사용자가 에이전트의 실수를 지적하면 Dispatcher 가 해당 에이전트의 `.harness/gotchas/<agent>.md` 에 자동 append. 다음 세션부터 그 에이전트는 세션 시작 시 자기 gotcha 파일을 읽고 같은 실수를 피합니다.
98
108
 
@@ -122,7 +132,60 @@ Dispatcher 는 자동으로 분류:
122
132
  - **Applies to**: generator-backend
123
133
  ```
124
134
 
125
- #### 공유 메모리 (프로젝트 전체 규칙)
135
+ #### 5b. Convention — "이렇게 해줘"
136
+
137
+ 긍정 가이드는 하우스 스타일로 등록됩니다:
138
+
139
+ ```
140
+ API 응답 필드는 전부 snake_case 로 해야 해. FE TS 모델이
141
+ snake_case 로 정의돼 있어서 변환 레이어를 두기 싫어.
142
+ ```
143
+
144
+ Dispatcher 자동 분류:
145
+ - **스코프**: `generator-backend` (API 응답 → BE 스코프)
146
+ - **저장 위치**: `.harness/conventions/generator-backend.md`
147
+ - **ID 할당**: `[C-001]` (해당 파일의 기존 최댓값 + 1)
148
+
149
+ 기록 포맷 (자동 작성):
150
+
151
+ ```markdown
152
+ ### [C-001] API 응답 필드는 snake_case
153
+ - **Date**: 2026-04-22
154
+ - **Scope**: generator-backend
155
+ - **Rule**: 모든 API 응답 JSON 필드는 snake_case (created_at, user_id 등).
156
+ - **Rationale**: FE TS 모델이 snake_case 로 정의돼 있어 변환 레이어 불필요.
157
+ - **Applies to**: generator-backend, libs/shared-dto
158
+ - **Added from**: user prompt (2026-04-22 16:12)
159
+ ```
160
+
161
+ #### 스코프 판별 (Convention)
162
+
163
+ | 키워드 | 스코프 (파일) |
164
+ |--------|-------------|
165
+ | backend, API, controller, service, DTO, NestJS | `generator-backend.md` |
166
+ | frontend, React, Next.js, UI, component, hook | `generator-frontend.md` |
167
+ | plan, sprint, feature-list | `planner.md` |
168
+ | Playwright, E2E, browser | `evaluator-functional.md` |
169
+ | layout, screenshot, a11y, responsive | `evaluator-visual.md` |
170
+ | code quality, lint, architecture | `evaluator-code-quality.md` |
171
+ | 매칭 실패 + 에이전트 국한 | `shared.md` |
172
+ | 프로젝트 철학 (예: "우리는 TDD") | 루트 `CONVENTIONS.md` 권고 |
173
+
174
+ #### 에이전트 와이어링
175
+
176
+ 각 에이전트는 세션 시작 시 다음 순서로 읽고 적용:
177
+
178
+ ```
179
+ 1. CONVENTIONS.md (루트, 최상위 원칙)
180
+ 2. .harness/conventions/shared.md (공통)
181
+ 3. .harness/conventions/<self>.md (자기 스코프)
182
+ 4. .harness/gotchas/<self>.md (과거 실수)
183
+ 5. .harness/memory.md (공유 교훈)
184
+ ```
185
+
186
+ 충돌 시 우선순위: `<self>` > `shared` > 루트.
187
+
188
+ #### 5c. Memory — 프로젝트 전체 규칙
126
189
 
127
190
  한 에이전트 실수가 아니라 **모든 에이전트에 적용할 구조적 교훈** 이면 `.harness/memory.md` 로 승격:
128
191
 
@@ -136,7 +199,18 @@ Dispatcher 는 자동으로 분류:
136
199
 
137
200
  #### 주의 — 데이터 보존
138
201
 
139
- v5.5.2 부터 `npm install` postinstall 이 `### [G-NNN]` 엔트리가 있는 gotcha 파일을 **절대 덮어쓰지 않습니다**. 그 이전 버전에서는 업데이트 시 누적 기록이 템플릿으로 초기화되는 버그가 있었습니다 — 반드시 `5.5.2+` 를 사용하세요.
202
+ `npm install` postinstall 은 **누적 엔트리(`[G-NNN]` 또는 `[C-NNN]`)가 있는 파일을 절대 덮어쓰지 않습니다**. 스캐폴드 템플릿인 경우에만 갱신됩니다. v5.5.2 이전 버전은 gotchas 에 이 버그가 있었으므로 `5.6.0+` 사용을 권장합니다.
203
+
204
+ #### 기존 프로젝트 마이그레이션 (첫 설치 시 자동)
205
+
206
+ 기존 `CLAUDE.md` / `AGENTS.md` 에 Convention/Gotcha 성격의 섹션(`Conventions`, `Coding Standards`, `Best Practices`, `Gotchas`, `Don't`, `주의사항`, `금지사항` 등)이 있다면 첫 설치 시 자동으로 추출되어:
207
+
208
+ - **Convention 성격** → `.harness/conventions/<scope>.md` 에 `[C-NNN]` 으로 이관
209
+ - **Gotcha 성격** → `.harness/gotchas/<agent>.md` 에 `[G-NNN]` 으로 이관
210
+ - **원본** → `.harness/archive/pre-harness-*.md.bak` 에 백업
211
+ - **리포트** → `.harness/MIGRATION_REPORT.md` 에 이관 내역 + 수동 확인 요청 사항 기록
212
+
213
+ 마이그레이션은 heuristic(키워드 기반)이므로 리포트를 확인해 스코프 재배정이 필요한지 검토하세요. 이미 하네스 서명(`[BE]`/`[FE]`/`[HARNESS]`)이 있는 문서는 skip 됩니다.
140
214
 
141
215
  ---
142
216
 
@@ -298,14 +372,23 @@ your-project/
298
372
  │ │ ├── evaluation-code-quality.md
299
373
  │ │ ├── evaluation-functional.md
300
374
  │ │ └── evaluation-visual.md
301
- │ ├── archive/ # 완료 스프린트 보관 (불변)
302
- │ └── gotchas/ # 에이전트 실수 기록 (누적 보존)
303
- │ ├── planner.md
304
- │ ├── generator-backend.md
305
- │ ├── generator-frontend.md
306
- │ ├── evaluator-code-quality.md
307
- │ ├── evaluator-functional.md
308
- │ └── evaluator-visual.md
375
+ │ ├── archive/ # 완료 스프린트 보관 (불변, 마이그레이션 백업도 여기)
376
+ │ ├── gotchas/ # 에이전트 실수 기록 [G-NNN] (누적 보존)
377
+ │ │ ├── planner.md
378
+ │ │ ├── generator-backend.md
379
+ │ │ ├── generator-frontend.md
380
+ │ │ ├── evaluator-code-quality.md
381
+ │ │ ├── evaluator-functional.md
382
+ │ │ └── evaluator-visual.md
383
+ │ ├── conventions/ # 하우스 스타일 [C-NNN] (v5.6+, 누적 보존)
384
+ │ │ ├── shared.md
385
+ │ │ ├── planner.md
386
+ │ │ ├── generator-backend.md
387
+ │ │ ├── generator-frontend.md
388
+ │ │ ├── evaluator-code-quality.md
389
+ │ │ ├── evaluator-functional.md
390
+ │ │ └── evaluator-visual.md
391
+ │ └── MIGRATION_REPORT.md # 첫 설치 시 기존 문서 이관 내역 (있을 때만)
309
392
  ├── .claude/
310
393
  │ ├── skills/harness-*/ # 에이전트 스킬 (8개)
311
394
  │ ├── commands/harness-*.md # 모드 제어 커맨드 (3개)
@@ -323,7 +406,8 @@ your-project/
323
406
  │ ├── harness-prompt-history.sh # 프롬프트 히스토리
324
407
  │ └── lib/ # 공유 라이브러리
325
408
  ├── AGENTS.md # 프로젝트 컨텍스트 (IA-MAP)
326
- └── CLAUDE.md → AGENTS.md # 심볼릭 링크
409
+ ├── CLAUDE.md → AGENTS.md # 심볼릭 링크
410
+ └── CONVENTIONS.md # 최상위 원칙 (사용자 자유 기술, 하위는 .harness/conventions/)
327
411
  ```
328
412
 
329
413
  ---
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "harness": {
3
3
  "name": "7-Agent Production Harness",
4
- "version": "5.5.0",
5
- "description": "Solo/Team 통합 하네스 — Dispatcher + 3-Evaluator Chain (Code-Quality → Functional → Visual) + NestJS MSA + React/Next.js + Playwright",
4
+ "version": "5.6.0",
5
+ "description": "Solo/Team 통합 하네스 — Dispatcher + 3-Evaluator Chain (Code-Quality → Functional → Visual) + Hierarchical Conventions/Gotchas + NestJS MSA + React/Next.js + Playwright",
6
6
  "source": "https://www.anthropic.com/engineering/harness-design-long-running-apps"
7
7
  },
8
8
  "agents": {
@@ -368,11 +368,17 @@
368
368
  "statuses": ["start", "complete", "fail", "pass", "skip", "warn"]
369
369
  },
370
370
  "conventions": {
371
- "comment": "CONVENTIONS.md — 사용자가 자유 형식으로 작성하는 프로젝트 컨벤션. 모든 에이전트가 세션 시작 시 직접 읽음. Gotcha/AGENTS.md에 복사하지 않음.",
372
- "file": "CONVENTIONS.md",
371
+ "comment": "House-style registry. 루트 CONVENTIONS.md(사용자 자유 기술) + .harness/conventions/<scope>.md(Dispatcher 자동 누적). 모든 에이전트가 세션 시작 시 읽고 적용한다. Gotcha 의 긍정 대칭판.",
372
+ "root_file": "CONVENTIONS.md",
373
+ "scoped_dir": ".harness/conventions/",
374
+ "scopes": ["shared", "planner", "generator-backend", "generator-frontend", "evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
375
+ "entry_id_prefix": "C",
373
376
  "read_by": "all_agents",
374
- "write_by": "user_only",
375
- "read_timing": "session_start"
377
+ "read_order": ["CONVENTIONS.md", ".harness/conventions/shared.md", ".harness/conventions/<self>.md", ".harness/gotchas/<self>.md", ".harness/memory.md"],
378
+ "conflict_priority": "self > shared > root",
379
+ "write_by": "dispatcher (auto-append C-NNN) or user_only (manual edit)",
380
+ "read_timing": "session_start",
381
+ "preserve_on_postinstall": true
376
382
  },
377
383
  "recommended_skills": {
378
384
  "comment": "Claude Code에 설치하면 하네스 품질이 향상되는 외부 스킬 목록",
package/bin/init.js CHANGED
@@ -90,16 +90,162 @@ function log(msg) {
90
90
  console.log(`[walwal-harness] ${msg}`);
91
91
  }
92
92
 
93
+ // ─────────────────────────────────────────
94
+ // First-install migration — extract Convention/Gotcha-shaped sections from
95
+ // existing CLAUDE.md / AGENTS.md into .harness/conventions and .harness/gotchas.
96
+ // Conservative: only triggers when these docs are NOT already harness-scaffolded
97
+ // (detected by IA-MAP tags like "[BE]" or "[HARNESS]").
98
+ // ─────────────────────────────────────────
99
+ function migrateExistingDocs() {
100
+ // Match heading titles. Use (?=\s|$) instead of \b — Korean chars are
101
+ // not "word" in JS regex, so \b produces inconsistent matches.
102
+ const CONVENTION_HEADINGS = /^#{1,4}\s+(Conventions?|Coding Standards?|Style Guide|Rules|Guidelines|Best Practices|Do's and Don'ts|규칙|하우스 스타일|명명 규칙|코딩 규칙|코드 스타일)(?=[\s:]|$)/im;
103
+ const GOTCHA_HEADINGS = /^#{1,4}\s+(Gotchas?|Anti[- ]?patterns?|Don'?ts?|Avoid|Pitfalls?|주의사항|금지사항|실수|함정|안티[- ]?패턴)(?=[\s:]|$)/im;
104
+ const HARNESS_SIGNATURE = /\[(BE|FE|HARNESS|META|INFRA|ROOT)\]|walwal-harness|harness-dispatcher/;
105
+
106
+ const candidates = [
107
+ path.join(PROJECT_ROOT, 'CLAUDE.md'),
108
+ path.join(PROJECT_ROOT, 'AGENTS.md')
109
+ ];
110
+
111
+ const report = [];
112
+ const extractedConv = { counter: 0, byScope: {} };
113
+ const extractedGotcha = { counter: 0, byAgent: {} };
114
+
115
+ const scopeFor = (body) => {
116
+ const b = body.toLowerCase();
117
+ if (/\b(backend|api|nestjs|controller|dto|service|msa|repository)\b/.test(b)) return 'generator-backend';
118
+ if (/\b(frontend|react|next\.?js|ui|component|tsx|tailwind|hook)\b/.test(b)) return 'generator-frontend';
119
+ if (/\b(planner|plan\.md|sprint|feature-list|api-contract)\b/.test(b)) return 'planner';
120
+ if (/\b(playwright|e2e|functional test)\b/.test(b)) return 'evaluator-functional';
121
+ if (/\b(visual|layout|screenshot|a11y|accessibility|responsive)\b/.test(b)) return 'evaluator-visual';
122
+ if (/\b(code quality|lint|tsc|architecture|typescript strict)\b/.test(b)) return 'evaluator-code-quality';
123
+ return 'shared';
124
+ };
125
+
126
+ const appendEntry = (filePath, id, kind, title, body, source) => {
127
+ const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
128
+ const entry = [
129
+ ``,
130
+ `### [${id}] ${title}`,
131
+ `- **Date**: ${new Date().toISOString().split('T')[0]}`,
132
+ `- **Source**: ${source} (migrated)`,
133
+ ``,
134
+ body.trim(),
135
+ ``
136
+ ].join('\n');
137
+ fs.writeFileSync(filePath, existing.replace(/\s*$/, '') + '\n' + entry + '\n');
138
+ };
139
+
140
+ for (const docPath of candidates) {
141
+ if (!fileExists(docPath)) continue;
142
+ const content = fs.readFileSync(docPath, 'utf8');
143
+ if (HARNESS_SIGNATURE.test(content)) {
144
+ // Already a harness-managed doc — skip
145
+ continue;
146
+ }
147
+
148
+ // Backup
149
+ const backupPath = path.join(HARNESS_DIR, 'archive', `pre-harness-${path.basename(docPath)}.bak`);
150
+ fs.writeFileSync(backupPath, content);
151
+ report.push(`Backed up: ${docPath} → ${backupPath}`);
152
+
153
+ // Split by top-level and H2 headings to get sections
154
+ // Simple approach: find heading lines, slice until next heading of same-or-higher level
155
+ const lines = content.split('\n');
156
+ const sections = [];
157
+ let current = null;
158
+ lines.forEach((line, idx) => {
159
+ const m = /^(#{1,4})\s+(.+?)\s*$/.exec(line);
160
+ if (m) {
161
+ if (current) sections.push(current);
162
+ current = { level: m[1].length, title: m[2], startLine: idx + 1, endLine: idx + 1, body: [] };
163
+ } else if (current) {
164
+ current.body.push(line);
165
+ current.endLine = idx + 1;
166
+ }
167
+ });
168
+ if (current) sections.push(current);
169
+
170
+ for (const sec of sections) {
171
+ const header = `${'#'.repeat(sec.level)} ${sec.title}`;
172
+ const isConv = CONVENTION_HEADINGS.test(header);
173
+ const isGotcha = GOTCHA_HEADINGS.test(header);
174
+ if (!isConv && !isGotcha) continue;
175
+ const body = sec.body.join('\n').trim();
176
+ if (!body) continue;
177
+
178
+ const sourceRef = `${path.basename(docPath)}:${sec.startLine}-${sec.endLine}`;
179
+
180
+ if (isConv) {
181
+ const scope = scopeFor(sec.title + '\n' + body);
182
+ extractedConv.counter += 1;
183
+ const id = `C-${String(extractedConv.counter).padStart(3, '0')}`;
184
+ const target = path.join(HARNESS_DIR, 'conventions', `${scope}.md`);
185
+ appendEntry(target, id, 'convention', sec.title, body, sourceRef);
186
+ extractedConv.byScope[scope] = (extractedConv.byScope[scope] || 0) + 1;
187
+ report.push(`[${id}] "${sec.title}" → conventions/${scope}.md (from ${sourceRef})`);
188
+ } else {
189
+ const scope = scopeFor(sec.title + '\n' + body);
190
+ const agent = scope === 'shared' ? 'planner' : scope; // default shared-gotchas to planner
191
+ extractedGotcha.counter += 1;
192
+ const id = `G-${String(extractedGotcha.counter).padStart(3, '0')}`;
193
+ const target = path.join(HARNESS_DIR, 'gotchas', `${agent}.md`);
194
+ appendEntry(target, id, 'gotcha', sec.title, body, sourceRef);
195
+ extractedGotcha.byAgent[agent] = (extractedGotcha.byAgent[agent] || 0) + 1;
196
+ report.push(`[${id}] "${sec.title}" → gotchas/${agent}.md (from ${sourceRef})`);
197
+ }
198
+ }
199
+ }
200
+
201
+ if (report.length === 0) return;
202
+
203
+ const reportPath = path.join(HARNESS_DIR, 'MIGRATION_REPORT.md');
204
+ const reportContent = [
205
+ `# Walwal-Harness Migration Report`,
206
+ ``,
207
+ `Generated on first install at ${new Date().toISOString()}.`,
208
+ ``,
209
+ `## Summary`,
210
+ ``,
211
+ `- Conventions extracted: ${extractedConv.counter}`,
212
+ `- Gotchas extracted: ${extractedGotcha.counter}`,
213
+ ``,
214
+ `## Manual Review Required`,
215
+ ``,
216
+ `Migration is heuristic (keyword-based). Please review each extracted entry:`,
217
+ `- Verify scope assignment is correct`,
218
+ `- Split entries into smaller atomic rules if appropriate`,
219
+ `- Adjust wording to positive-rule form for conventions, negative/anti-pattern form for gotchas`,
220
+ ``,
221
+ `## Entries`,
222
+ ``,
223
+ ...report.map(r => `- ${r}`),
224
+ ``,
225
+ `## Backups`,
226
+ ``,
227
+ `Original documents were preserved in \`.harness/archive/pre-harness-*.md.bak\`.`,
228
+ ``
229
+ ].join('\n');
230
+ fs.writeFileSync(reportPath, reportContent);
231
+ log(`Migration: ${extractedConv.counter} convention(s), ${extractedGotcha.counter} gotcha(s) extracted.`);
232
+ log(`Migration report: ${reportPath}`);
233
+ }
234
+
93
235
  // ─────────────────────────────────────────
94
236
  // 1. .harness/ scaffolding
95
237
  // ─────────────────────────────────────────
96
238
  function scaffoldHarness() {
97
239
  log('Scaffolding .harness/ directory...');
98
240
 
241
+ // Detect first install BEFORE ensureDir creates the root
242
+ const isFirstInstall = !fs.existsSync(HARNESS_DIR);
243
+
99
244
  // Core directories
100
245
  ensureDir(path.join(HARNESS_DIR, 'actions'));
101
246
  ensureDir(path.join(HARNESS_DIR, 'archive'));
102
247
  ensureDir(path.join(HARNESS_DIR, 'gotchas'));
248
+ ensureDir(path.join(HARNESS_DIR, 'conventions'));
103
249
 
104
250
  // Copy gotchas — preserve any existing file that has accumulated entries.
105
251
  // Dispatcher appends `### [G-NNN] ...` entries directly; we must NEVER overwrite
@@ -136,6 +282,39 @@ function scaffoldHarness() {
136
282
  }
137
283
  }
138
284
 
285
+ // Copy conventions — mirror gotchas preservation: never overwrite files with
286
+ // accumulated `### [C-NNN]` entries.
287
+ const conventionsSrc = path.join(PKG_ROOT, 'conventions');
288
+ if (fs.existsSync(conventionsSrc)) {
289
+ const CONV_ENTRY = /^### \[C-\d+\]/m;
290
+ const files = fs.readdirSync(conventionsSrc);
291
+ for (const file of files) {
292
+ const destPath = path.join(HARNESS_DIR, 'conventions', file);
293
+ const srcPath = path.join(conventionsSrc, file);
294
+ if (!fileExists(destPath)) {
295
+ copyFile(srcPath, destPath);
296
+ continue;
297
+ }
298
+ if (!file.endsWith('.md')) continue;
299
+ const existing = fs.readFileSync(destPath, 'utf8');
300
+ if (CONV_ENTRY.test(existing)) {
301
+ if (file === 'README.md') copyFile(srcPath, destPath);
302
+ continue;
303
+ }
304
+ copyFile(srcPath, destPath);
305
+ }
306
+ }
307
+
308
+ // First-install migration: extract Convention/Gotcha-shaped sections from
309
+ // existing CLAUDE.md / AGENTS.md and copy into the hierarchical stores.
310
+ if (isFirstInstall) {
311
+ try {
312
+ migrateExistingDocs();
313
+ } catch (e) {
314
+ log('WARNING: migration failed — ' + e.message);
315
+ }
316
+ }
317
+
139
318
  // Copy templates as initial files
140
319
  const templateMap = {
141
320
  'progress.json.template': path.join(HARNESS_DIR, 'progress.json'),
@@ -232,12 +411,12 @@ function scaffoldHarness() {
232
411
  copyFile(memorySrc, memoryDest);
233
412
  }
234
413
 
235
- // Copy CONVENTIONS.md to project root
236
- const conventionsSrc = path.join(PKG_ROOT, 'assets', 'templates', 'CONVENTIONS.md');
237
- const conventionsDest = path.join(PROJECT_ROOT, 'CONVENTIONS.md');
238
- if (fs.existsSync(conventionsSrc) && (!fileExists(conventionsDest) || isForce)) {
239
- copyFile(conventionsSrc, conventionsDest);
240
- log('CONVENTIONS.md created — edit to define your project conventions');
414
+ // Copy CONVENTIONS.md to project root (legacy — root still supported)
415
+ const rootConvSrc = path.join(PKG_ROOT, 'assets', 'templates', 'CONVENTIONS.md');
416
+ const rootConvDest = path.join(PROJECT_ROOT, 'CONVENTIONS.md');
417
+ if (fs.existsSync(rootConvSrc) && (!fileExists(rootConvDest) || isForce)) {
418
+ copyFile(rootConvSrc, rootConvDest);
419
+ log('CONVENTIONS.md created — edit to define top-level project conventions');
241
420
  }
242
421
 
243
422
  // Create progress.log
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "5.5.3",
3
+ "version": "5.6.1",
4
4
  "description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator chain (Code-Quality → Functional → Visual), optional Brainstormer. Supports React, Next.js, and Flutter FE stacks.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -31,6 +31,14 @@
31
31
  ],
32
32
  "author": "",
33
33
  "license": "MIT",
34
+ "homepage": "https://github.com/30eggis/walwal-harness#readme",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/30eggis/walwal-harness.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/30eggis/walwal-harness/issues"
41
+ },
34
42
  "files": [
35
43
  "bin/",
36
44
  "skills/",
@@ -46,19 +46,25 @@ Claude 는 기본적으로 Dispatcher 경유로 분류/라우팅해야 한다.
46
46
 
47
47
  사용자 입력을 먼저 분류합니다:
48
48
 
49
- - **실수 지적** ("아니", "잘못", "그렇게 하면 안 돼", "X로 해야지") → **Gotcha Flow**
49
+ - **실수 지적 (부정)** ("아니", "잘못", "그렇게 하면 안 돼", "~하지 마") → **Gotcha Flow**
50
+ - **긍정 규범** ("~해야 해", "~이렇게 해줘", "항상 ~", "우리는 ~ 방식") → **Convention Flow**
50
51
  - **기능 요청** ("만들어", "추가", "시작", PRD, OpenAPI) → **Pipeline Flow**
51
- - **혼합** → Gotcha 먼저 기록 → Pipeline 이어서
52
+ - **혼합** → Gotcha/Convention 먼저 기록 → Pipeline 이어서
52
53
  - **메타/인사/Claude 자체 질문** → Dispatcher skip, 짧은 일반 응답 허용
53
54
 
54
- ## 2. Gotcha Flow vs Memory Flow
55
+ **부정 vs 긍정 구분법**: "X 하지 마 / X 가 틀렸어 / 그렇게 하면 안 돼" 는 **Gotcha**. "X 를 해야 해 / X 로 해줘 / 항상 X" 는 **Convention**. 동일 주제도 시그널에 따라 저장 위치가 달라집니다.
55
56
 
56
- 사용자의 교정/피드백을 받으면 **먼저 분류**:
57
+ ## 2. Feedback Taxonomy — Gotcha / Convention / Memory
57
58
 
58
- | 유형 | 저장 위치 | 예시 |
59
- |------|----------|------|
60
- | **에이전트별 실수** (일회성 교정) | `.harness/gotchas/[agent].md` | "API 응답에 created_at은 ISO 8601로" |
61
- | **프로젝트 공유 규칙** (구조적/반복적) | `.harness/memory.md` | "Playwright 스크린샷은 단계 완료 후 항상 삭제" |
59
+ 사용자의 교정/가이드를 받으면 **먼저 분류**:
60
+
61
+ | 유형 | 성격 | 저장 위치 | ID | 예시 |
62
+ |------|------|----------|-----|------|
63
+ | **Gotcha** | 특정 에이전트의 **일회성 실수(사고)** 기록 (negative) | `.harness/gotchas/<agent>.md` | `[G-NNN]` | "Generator-BE 가 MockServer 무시하고 실 DB 붙지 마" |
64
+ | **Convention** | 에이전트/스코프의 **하우스 스타일(norm)** (positive) | `.harness/conventions/<scope>.md` | `[C-NNN]` | "API 응답 필드는 snake_case" |
65
+ | **Memory** | **모든 에이전트** 공통 구조적 교훈 | `.harness/memory.md` | `[M-NNN]` | "Playwright 스크린샷은 단계 완료 후 항상 삭제" |
66
+
67
+ Scope 가 특정 에이전트를 넘어서면 Memory. 특정 에이전트에 해당하면 Gotcha(부정) 혹은 Convention(긍정).
62
68
 
63
69
  ### Gotcha Flow (에이전트별 실수)
64
70
 
@@ -67,7 +73,17 @@ Claude 는 기본적으로 Dispatcher 경유로 분류/라우팅해야 한다.
67
73
  핵심:
68
74
  1. 교정 시그널 감지 (HIGH/MEDIUM만 기록)
69
75
  2. 도메인 분석 → 대상 에이전트 판별
70
- 3. `.harness/gotchas/[agent].md`에 항목 추가 (중복 시 Occurrences 증가)
76
+ 3. `.harness/gotchas/[agent].md`에 `[G-NNN]` 추가 (중복 시 Occurrences 증가)
77
+ 4. 사용자에게 기록 확인
78
+
79
+ ### Convention Flow (에이전트별 하우스 스타일)
80
+
81
+ 긍정 가이드 감지 시 → [Convention 상세 가이드](references/convention-flow.md)
82
+
83
+ 핵심:
84
+ 1. 긍정 시그널 감지 ("해야 해", "이렇게 해줘", "항상" 등)
85
+ 2. 스코프 판별: 특정 에이전트(`generator-backend` 등) / `shared` / 프로젝트 전체(루트 `CONVENTIONS.md`)
86
+ 3. `.harness/conventions/<scope>.md` 에 `[C-NNN]` 추가
71
87
  4. 사용자에게 기록 확인
72
88
 
73
89
  ### Memory Flow (프로젝트 공유 규칙)
@@ -0,0 +1,111 @@
1
+ ---
2
+ docmeta:
3
+ id: convention-flow
4
+ title: Convention Flow — Positive Guide Classifier
5
+ type: output
6
+ createdAt: 2026-04-22T00:00:00Z
7
+ updatedAt: 2026-04-22T00:00:00Z
8
+ source:
9
+ producer: agent
10
+ skillId: harness-dispatcher
11
+ inputs:
12
+ - documentId: harness-dispatcher-skill
13
+ uri: ../SKILL.md
14
+ relation: output-from
15
+ sections:
16
+ - sourceRange:
17
+ startLine: 45
18
+ endLine: 100
19
+ targetRange:
20
+ startLine: 30
21
+ endLine: 140
22
+ tags:
23
+ - dispatcher
24
+ - convention
25
+ - positive-guidance
26
+ ---
27
+
28
+ # Convention Flow — Positive Guide Classifier
29
+
30
+ 긍정 가이드("~해야 해", "항상 ~", "이렇게 해줘")를 감지하면
31
+ 해당 스코프의 `.harness/conventions/<scope>.md` 에 `[C-NNN]` 엔트리로 append.
32
+
33
+ ## 1. 긍정 시그널 감지
34
+
35
+ 다음 패턴 중 하나라도 포함되면 Convention 후보:
36
+
37
+ - 명령형: "해야 해", "~로 해줘", "이렇게 만들어", "~를 사용해"
38
+ - 원칙 선언: "항상 ~", "모든 ~ 는", "우리는 ~ 방식", "표준은 ~"
39
+ - 하우스 스타일: "이 프로젝트에서는 ~", "컨벤션상 ~", "규칙은 ~"
40
+ - 영어: "always", "must", "should", "we use", "prefer", "standard is"
41
+
42
+ **부정 시그널과 충돌 시 부정 우선** (Gotcha 로 라우팅). 예: "~ 하지 말고 ~ 해줘" 는 Gotcha.
43
+
44
+ ## 2. Scope 판별
45
+
46
+ 엔트리의 적용 범위를 다음 순서로 판별:
47
+
48
+ 1. **특정 에이전트 명시** → 해당 에이전트 파일
49
+ - 사용자가 명시적으로 이름 언급 ("Generator-BE 는 ~") 또는 문맥상 확실한 키워드
50
+ 2. **도메인 키워드 매칭** → 대응 에이전트
51
+ | 키워드 | 스코프 |
52
+ |--------|-------|
53
+ | backend, API, controller, service, DTO, NestJS, MSA | `generator-backend` |
54
+ | frontend, React, Next.js, UI, component, hook, Tailwind | `generator-frontend` |
55
+ | plan, sprint, feature-list, api-contract, roadmap | `planner` |
56
+ | Playwright, E2E, browser, functional test | `evaluator-functional` |
57
+ | layout, screenshot, a11y, responsive, viewport, AI slop | `evaluator-visual` |
58
+ | code quality, lint, tsc, architecture, type safety | `evaluator-code-quality` |
59
+ 3. **매칭 실패 + 여전히 에이전트 국한** → `shared.md`
60
+ 4. **프로젝트 전체 철학/원칙** (예: "우리는 TDD 한다", "보안 우선") → 루트 `CONVENTIONS.md` 에 사용자 권고 (Dispatcher 직접 수정 금지)
61
+
62
+ ## 3. 중복 감지
63
+
64
+ 대상 파일에서 기존 `[C-NNN]` 엔트리를 읽어:
65
+ - **완전 중복** (같은 rule) → append 하지 않고 기존 엔트리의 Date 를 갱신
66
+ - **부분 중복** (관련 주제) → 새 엔트리로 추가하되 기존 엔트리 ID 를 `Related:` 필드로 참조
67
+
68
+ ## 4. 엔트리 포맷
69
+
70
+ ```markdown
71
+ ### [C-NNN] 간결한 제목 (긍정형, 70자 이내)
72
+ - **Date**: YYYY-MM-DD
73
+ - **Scope**: <agent> | shared
74
+ - **Rule**: 사용자가 말한 내용을 긍정 규칙으로 정제. 명령형 문장.
75
+ - **Rationale**: 사용자가 설명한 이유 (없으면 "미지정" 표기, 추정 금지)
76
+ - **Applies to**: 적용 대상 상세 (특정 파일 경로, 특정 상황, 엔드포인트 등)
77
+ - **Added from**: 사용자 프롬프트 (YYYY-MM-DD HH:MM) | migration | manual
78
+ - **Related**: C-XXX, C-YYY (선택)
79
+ ```
80
+
81
+ ### ID 할당
82
+
83
+ 대상 파일의 기존 `[C-NNN]` 최댓값 + 1 을 3자리 zero-pad. 예: 기존에 C-001, C-003 이 있으면 다음은 C-004 (비어있는 번호는 재사용하지 않음).
84
+
85
+ ## 5. 루트 CONVENTIONS.md 처리
86
+
87
+ 루트 `CONVENTIONS.md` 는 **사용자가 자유 기술하는 최상위 원칙 파일**. Dispatcher 가 직접 수정하지 않고, 사용자에게 안내:
88
+
89
+ ```
90
+ 이 규칙은 프로젝트 전체 철학에 가까워 보여서 CONVENTIONS.md(루트) 에
91
+ 직접 추가하시는 게 좋겠습니다. 추가 후 모든 에이전트가 세션 시작 시 읽습니다.
92
+ ```
93
+
94
+ ## 6. 사용자 확인 메시지 포맷
95
+
96
+ ```
97
+ Convention 등록 완료:
98
+ - ID: [C-004]
99
+ - Scope: generator-backend
100
+ - Rule: API 응답 필드는 snake_case
101
+ - 저장 위치: .harness/conventions/generator-backend.md
102
+
103
+ Generator-Backend 는 다음 세션 시작 시 이 규칙을 읽고 적용합니다.
104
+ ```
105
+
106
+ ## 7. 금지 사항
107
+
108
+ - Convention 파일의 기존 엔트리 삭제/수정 (사용자만 가능)
109
+ - `CONVENTIONS.md` (루트) 직접 수정
110
+ - 추정성 rationale 작성 (근거 없으면 "미지정")
111
+ - 에이전트 SKILL.md 자체 수정 (구조적 변경은 사용자 권고)
@@ -63,13 +63,14 @@ disable-model-invocation: true
63
63
  ## Startup
64
64
 
65
65
  1. `AGENTS.md` 읽기 — IA-MAP (레이어 경계)
66
- 2. `CONVENTIONS.md` 읽기 — 프로젝트 컨벤션 (있을 때만)
67
- 3. `.harness/gotchas/evaluator-code-quality.md` 읽기 — **과거 실수 반복 금지**
68
- 4. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
69
- 5. `actions/sprint-contract.md` — 이번 스프린트 변경 범위
70
- 6. `actions/feature-list.json` — 기능 정의
71
- 7. `actions/api-contract.json` — DTO 형태 (계약 vs 구현 일치 확인용)
72
- 8. `.harness/progress.json`
66
+ 2. `CONVENTIONS.md` (루트) 읽기 — 프로젝트 최상위 원칙 (있을 때만)
67
+ 3. `.harness/conventions/shared.md` + `.harness/conventions/evaluator-code-quality.md` — **긍정 하우스 스타일 (C-NNN) — PASS 판정의 기준이 된다**
68
+ 4. `.harness/gotchas/evaluator-code-quality.md` 읽기 — **과거 실수 반복 금지**
69
+ 5. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
70
+ 6. `actions/sprint-contract.md` — 이번 스프린트 변경 범위
71
+ 7. `actions/feature-list.json` — 기능 정의
72
+ 8. `actions/api-contract.json` — DTO 형태 (계약 vs 구현 일치 확인용)
73
+ 9. `.harness/progress.json`
73
74
 
74
75
  ## Evaluation Steps
75
76
 
@@ -63,12 +63,14 @@ disable-model-invocation: true
63
63
  ## Startup
64
64
 
65
65
  1. `AGENTS.md` 읽기 — IA-MAP
66
- 2. `.harness/gotchas/evaluator-functional.md` 읽기 — **과거 실수 반복 금지**
67
- 3. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
68
- 4. `actions/sprint-contract.md` — BE + FE 성공 기준 전체
69
- 4. `actions/feature-list.json` — 이번 스프린트 범위
70
- 5. `actions/api-contract.json` — 기대 API 동작
71
- 6. `.harness/progress.json`
66
+ 2. `CONVENTIONS.md` (루트) 읽기 — 프로젝트 최상위 원칙 (있을 때만)
67
+ 3. `.harness/conventions/shared.md` + `.harness/conventions/evaluator-functional.md` — **긍정 하우스 스타일 적용**
68
+ 4. `.harness/gotchas/evaluator-functional.md` 읽기 — **과거 실수 반복 금지**
69
+ 5. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
70
+ 6. `actions/sprint-contract.md` — BE + FE 성공 기준 전체
71
+ 7. `actions/feature-list.json` — 이번 스프린트 범위
72
+ 8. `actions/api-contract.json` — 기대 API 동작
73
+ 9. `.harness/progress.json`
72
74
 
73
75
  ## Feature-Level Mode (Team Mode)
74
76
 
@@ -56,10 +56,12 @@ disable-model-invocation: true
56
56
  ## Startup
57
57
 
58
58
  1. `AGENTS.md` 읽기
59
- 2. `.harness/gotchas/evaluator-visual.md` 읽기 — **과거 실수 반복 금지**
60
- 3. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
61
- 4. `actions/evaluation-functional.md` — Verdict: PASS 확인
62
- 5. **Stack-Adaptive Gate** (v5.2) — `scan-result.json.tech_stack` 으로 스택 확인 후 `.harness/ref/fe-<stack>.md` 의 `validation.visual` 파싱:
59
+ 2. `CONVENTIONS.md` (루트) 읽기 — 프로젝트 최상위 원칙 (있을 때만)
60
+ 3. `.harness/conventions/shared.md` + `.harness/conventions/evaluator-visual.md` — **긍정 하우스 스타일 적용**
61
+ 4. `.harness/gotchas/evaluator-visual.md` 읽기 — **과거 실수 반복 금지**
62
+ 5. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
63
+ 6. `actions/evaluation-functional.md` — Verdict: PASS 확인
64
+ 7. **Stack-Adaptive Gate** (v5.2) — `scan-result.json.tech_stack` 으로 스택 확인 후 `.harness/ref/fe-<stack>.md` 의 `validation.visual` 파싱:
63
65
  - `visual.enabled == false`: 즉시 **MANUAL_REQUIRED 모드** 로 전환 — 아래 "Visual Skip Flow" 수행 후 종료
64
66
  - `visual.enabled == true` (또는 ref-docs 없이 웹 전통 스택): 계속 진행, ref 에 `visual.base_url` 이 있으면 그 URL 로, 없으면 `ref.runner.dev_command` 로 서버 기동 후 Playwright 접속
65
67
 
@@ -27,18 +27,23 @@ disable-model-invocation: true
27
27
  ## Startup (Adaptive Loading)
28
28
 
29
29
  1. `AGENTS.md` 읽기 — IA-MAP, 권한 확인
30
- 2. `.harness/actions/scan-result.json` 읽기 → `tech_stack.backend` 또는 `tech_stack.language` 로 현재 스택 확정 (이하 `<stack>`)
31
- 3. **Ref-docs 로드** — `.harness/ref/be-<stack>.md`
30
+ 2. `CONVENTIONS.md` (루트) 읽기 — 프로젝트 최상위 원칙 (있을 때만)
31
+ 3. **Conventions 로드** — 세 파일 모두 (있는 것만):
32
+ - `.harness/conventions/shared.md` (모든 에이전트 공통)
33
+ - `.harness/conventions/generator-backend.md` (BE 스코프)
34
+ - `.harness/conventions/generator-backend-<stack>.md` (스택별, 선택)
35
+ 4. `.harness/actions/scan-result.json` 읽기 → `tech_stack.backend` 또는 `tech_stack.language` 로 현재 스택 확정 (이하 `<stack>`)
36
+ 5. **Ref-docs 로드** — `.harness/ref/be-<stack>.md`
32
37
  - 파일 없음 → STOP + 안내: `"ref-docs 가 없습니다. bash init.sh init 실행 또는 bash scripts/init-ref-docs.sh --claude-prompt --stack <stack> --role be . 실행하세요."`
33
38
  - frontmatter 파싱 실패 → 경고 출력 + 기본값으로 degrade
34
- 4. **Gotchas 로드** — 두 파일 모두 (있는 것만):
39
+ 6. **Gotchas 로드** — 두 파일 모두 (있는 것만):
35
40
  - `.harness/gotchas/generator-backend.md` (공통)
36
41
  - `.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
+ 7. `.harness/memory.md` 읽기 — 프로젝트 공유 학습 규칙
43
+ 8. `pwd` + `.harness/progress.json` + `git log --oneline -20`
44
+ 9. `.harness/actions/api-contract.json` 읽기 — **이 계약이 유일한 BE 외부 인터페이스**
45
+ 10. `.harness/actions/feature-list.json` — 지정된 `FEATURE_ID` 또는 `layer: "backend"` 필터
46
+ 11. **DB / 외부 의존성 부트스트랩**:
42
47
  - `ref.runner.install_command` 가 있으면 1회 실행
43
48
  - `ref.runner.dev_command` 를 백그라운드 실행 (있는 경우)
44
49
 
@@ -27,21 +27,26 @@ disable-model-invocation: true
27
27
  ## Startup (Adaptive Loading)
28
28
 
29
29
  1. `AGENTS.md` 읽기 — IA-MAP, 권한 확인
30
- 2. `.harness/actions/scan-result.json` 읽기 → `tech_stack.fe_stack` 또는 `tech_stack.frontend` 로 현재 스택 확정 (이하 `<stack>`)
31
- 3. **Ref-docs 로드** — `.harness/ref/fe-<stack>.md`
30
+ 2. `CONVENTIONS.md` (루트) 읽기 — 프로젝트 최상위 원칙 (있을 때만)
31
+ 3. **Conventions 로드** — 세 파일 모두 (있는 것만):
32
+ - `.harness/conventions/shared.md` (모든 에이전트 공통)
33
+ - `.harness/conventions/generator-frontend.md` (FE 스코프)
34
+ - `.harness/conventions/generator-frontend-<stack>.md` (스택별, 선택)
35
+ 4. `.harness/actions/scan-result.json` 읽기 → `tech_stack.fe_stack` 또는 `tech_stack.frontend` 로 현재 스택 확정 (이하 `<stack>`)
36
+ 5. **Ref-docs 로드** — `.harness/ref/fe-<stack>.md`
32
37
  - 파일 없음 → STOP + 안내: `"ref-docs 가 없습니다. bash init.sh init 실행 또는 bash scripts/init-ref-docs.sh --claude-prompt --stack <stack> --role fe . 실행하세요."`
33
38
  - frontmatter 파싱 실패 → 경고 출력 + 기본값(runner/paths/api 모두 null)으로 degrade
34
- 4. **Gotchas 로드** — 두 파일 모두 (있는 것만):
39
+ 6. **Gotchas 로드** — 두 파일 모두 (있는 것만):
35
40
  - `.harness/gotchas/generator-frontend.md` (공통)
36
41
  - `.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 체크**:
42
+ 7. `.harness/memory.md` 읽기 — 프로젝트 공유 학습 규칙
43
+ 8. `pwd` + `.harness/progress.json` + `git log --oneline -20`
44
+ 9. `.harness/actions/api-contract.json` 읽기
45
+ 10. `.harness/actions/feature-list.json` — 지정된 `FEATURE_ID` 또는 `layer: "frontend"` 필터
46
+ 11. **개발 서버 기동**:
47
+ - `ref.runner.dev_command` 가 `null` 이 아니면 해당 명령 백그라운드 실행
48
+ - `null` 이면 "개발 서버 기동은 스택 특성상 생략" 로그만 남김
49
+ 12. **API Gateway 체크**:
45
50
  - `ref.api.base_url` 이 `null` 이 아니면 `curl -s <base_url>/health` 로 헬스체크
46
51
  - `null` (네이티브 앱 등) 이면 체크 스킵
47
52
 
@@ -24,18 +24,20 @@ disable-model-invocation: true
24
24
  ## Startup
25
25
 
26
26
  1. `AGENTS.md` 읽기
27
- 2. `.harness/gotchas/planner.md` 읽기 — **과거 실수 반복 금지**
28
- 3. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
29
- 4. `.harness/progress.json` 읽기
30
- 5. `.harness/actions/pipeline.json` 읽기 — `planner_mode`, `fe_stack` 확인
31
- 6. `.harness/actions/scan-result.json` 읽기 — `tech_stack.fe_stack` 확인 (없으면 `react` 기본)
32
- 7. **Brainstorm Spec 우선 로드** — `.harness/actions/brainstorm-spec.md` 가 존재하면
27
+ 2. `CONVENTIONS.md` (루트) 읽기 — 프로젝트 최상위 원칙 (있을 때만)
28
+ 3. `.harness/conventions/shared.md` + `.harness/conventions/planner.md` — **긍정 하우스 스타일 적용 (feature 분할/AC 작성 시)**
29
+ 4. `.harness/gotchas/planner.md` 읽기 — **과거 실수 반복 금지**
30
+ 5. `.harness/memory.md` 읽기 — **프로젝트 공유 학습 규칙 적용**
31
+ 6. `.harness/progress.json` 읽기
32
+ 7. `.harness/actions/pipeline.json` 읽기 — `planner_mode`, `fe_stack` 확인
33
+ 8. `.harness/actions/scan-result.json` 읽기 — `tech_stack.fe_stack` 확인 (없으면 `react` 기본)
34
+ 9. **Brainstorm Spec 우선 로드** — `.harness/actions/brainstorm-spec.md` 가 존재하면
33
35
  **이 파일이 PRD 대체 입력**. Brainstormer 가 이미 사용자와 대화하여 확정한
34
36
  결과이므로 **승인된 결정을 뒤엎지 않는다**. 없으면 사용자의 원본 요청 텍스트를 입력으로 사용.
35
37
  - brainstorm-spec.md 에 `## Open Questions` 섹션이 있으면 Planner 가 해소 (API 계약으로 확정)
36
38
  - brainstorm-spec.md 의 `## 7. 주요 컴포넌트 / 엔티티` → `feature-list.json` 초기 feature 목록 시드
37
39
  - brainstorm-spec.md 의 `## 5. 선택된 접근법` / `## 6. 아키텍처 스케치` → MSA 서비스 분할 베이스
38
- 8. **FE Stack 확정** → [FE Stack 결정 가이드](references/fe-stack-detection.md)
40
+ 10. **FE Stack 확정** → [FE Stack 결정 가이드](references/fe-stack-detection.md)
39
41
  - `pubspec.yaml` + `flutter:` 키 → `fe_stack = "flutter"`
40
42
  - 혼재/불명확 → 사용자에게 단 한 번 질문
41
43
  - 확정 후 `pipeline.json.fe_stack` 갱신 (없으면 생성)