@walwal-harness/cli 1.0.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 (35) hide show
  1. package/assets/templates/AGENTS.md.template +52 -0
  2. package/assets/templates/HARNESS.md +159 -0
  3. package/assets/templates/config.json +108 -0
  4. package/assets/templates/progress.txt.template +15 -0
  5. package/bin/init.js +281 -0
  6. package/gotchas/README.md +37 -0
  7. package/gotchas/evaluator-functional.md +5 -0
  8. package/gotchas/evaluator-visual.md +5 -0
  9. package/gotchas/generator-backend.md +5 -0
  10. package/gotchas/generator-frontend.md +5 -0
  11. package/gotchas/planner.md +5 -0
  12. package/package.json +35 -0
  13. package/scripts/init-agents-md.sh +327 -0
  14. package/scripts/scan-project.sh +269 -0
  15. package/skills/dispatcher/SKILL.md +53 -0
  16. package/skills/dispatcher/references/gotcha-flow.md +52 -0
  17. package/skills/dispatcher/references/initialization.md +53 -0
  18. package/skills/dispatcher/references/pipeline-definitions.md +76 -0
  19. package/skills/evaluator-functional/SKILL.md +58 -0
  20. package/skills/evaluator-functional/references/ia-compliance.md +37 -0
  21. package/skills/evaluator-functional/references/playwright-tools.md +43 -0
  22. package/skills/evaluator-functional/references/scoring-rubric.md +52 -0
  23. package/skills/evaluator-visual/SKILL.md +41 -0
  24. package/skills/evaluator-visual/references/responsive-checklist.md +44 -0
  25. package/skills/evaluator-visual/references/scoring-rubric.md +59 -0
  26. package/skills/generator-backend/SKILL.md +43 -0
  27. package/skills/generator-backend/references/nestjs-msa-patterns.md +69 -0
  28. package/skills/generator-backend/references/sprint-contract-be.md +32 -0
  29. package/skills/generator-frontend/SKILL.md +49 -0
  30. package/skills/generator-frontend/references/anti-slop-rules.md +21 -0
  31. package/skills/generator-frontend/references/component-patterns.md +52 -0
  32. package/skills/planner/SKILL.md +48 -0
  33. package/skills/planner/references/api-contract-schema.md +34 -0
  34. package/skills/planner/references/ia-map-guide.md +31 -0
  35. package/skills/planner/references/plan-template.md +51 -0
@@ -0,0 +1,52 @@
1
+ # AGENTS.md — Project Context for AI Agents
2
+
3
+ > 이 파일은 모든 AI 에이전트(Claude, Cursor, Copilot, Windsurf 등)의 공통 진입점입니다.
4
+ > CLAUDE.md는 이 파일의 심볼릭 링크입니다.
5
+ > Generated by walwal-harness ({{DATE}})
6
+
7
+ ## Project
8
+
9
+ - **Name**: (Planner가 설정)
10
+ - **Description**: (Planner가 설정)
11
+ - **Phase**: INIT
12
+ - **Harness**: `.harness/HARNESS.md` 참조
13
+
14
+ ## Tech Stack
15
+
16
+ - Backend: (scan-project.sh가 감지)
17
+ - Frontend: (scan-project.sh가 감지)
18
+ - Database: (scan-project.sh가 감지)
19
+
20
+ ## IA-MAP (Information Architecture)
21
+
22
+ > Planner가 프로젝트 시작 시 확정합니다.
23
+ > `[?]` 태그는 Planner가 분류해야 하는 미확인 경로입니다.
24
+
25
+ ```
26
+ /
27
+ ├── AGENTS.md # [META] 프로젝트 컨텍스트 → Planner
28
+ ├── CLAUDE.md # [META] → AGENTS.md 심볼릭 링크
29
+ └── .harness/ # [HARNESS] 하네스 시스템 → Planner
30
+ ```
31
+
32
+ ## Rules (모든 에이전트 공통)
33
+
34
+ ### 읽기/쓰기 권한
35
+
36
+ | 파일 | 읽기 | 쓰기 |
37
+ |------|------|------|
38
+ | AGENTS.md | 전체 | Planner만 |
39
+ | .harness/actions/api-contract.json | 전체 | Planner만 |
40
+ | .harness/actions/feature-list.json | 전체 | passes: Generator, 나머지: Planner |
41
+ | .harness/progress.txt | 전체 | 전체 (세션 종료 시 업데이트 의무) |
42
+ | `[BE]` 소유 경로 | 전체 | Generator-Backend만 |
43
+ | `[FE]` 소유 경로 | 전체 | Generator-Frontend만 |
44
+ | .harness/archive/ | 전체 | 쓰기 금지 (불변) |
45
+ | .harness/gotchas/ | 전체 | Dispatcher만 |
46
+
47
+ ### 금지 사항
48
+
49
+ - AGENTS.md를 Planner 외 에이전트가 수정
50
+ - 서비스 간 직접 DB 접근
51
+ - 테스트 삭제/약화
52
+ - archive/ 내 파일 수정
@@ -0,0 +1,159 @@
1
+ # 6-Agent Production Harness — 사용 가이드
2
+
3
+ > Anthropic 블로그 "Harness Design for Long-Running Application Development" 기반
4
+ > Solo: 20min/$9 (broken) → Harness: 6hr/$200 (fully functional)
5
+ > Stack: NestJS MSA + React/Next.js + Playwright MCP
6
+
7
+ ## 디렉토리 구조
8
+
9
+ ```
10
+ .harness/
11
+ ├── HARNESS.md # 이 파일
12
+ ├── config.json # 하네스 설정
13
+ ├── progress.txt # 세션 간 상태 전달 로그
14
+ ├── prompts/ # 에이전트 프롬프트 (6개)
15
+ │ ├── dispatcher.md # Dispatcher — 파이프라인 선택
16
+ │ ├── planner.md # Planner — 제품 사양 + MSA 설계
17
+ │ ├── generator-backend.md # Generator-BE — NestJS MSA 구현
18
+ │ ├── generator-frontend.md # Generator-FE — React/Next.js 구현
19
+ │ ├── evaluator-functional.md # Evaluator-기능 — Playwright E2E
20
+ │ └── evaluator-visual.md # Evaluator-비주얼 — 디자인/접근성
21
+ ├── actions/ # 현재 활성 문서
22
+ │ ├── pipeline.json # Dispatcher 결정 (어떤 파이프라인인지)
23
+ │ ├── plan.md # 제품 사양
24
+ │ ├── feature-list.json # 기능 추적 (layer + service 필드)
25
+ │ ├── api-contract.json # API 계약서
26
+ │ ├── sprint-contract.md # 현재 스프린트 계약
27
+ │ ├── evaluation-functional.md
28
+ │ └── evaluation-visual.md
29
+ └── archive/ # 완료 스프린트 보관 (불변)
30
+ └── sprint-NNN/
31
+ ```
32
+
33
+ ## 실행 흐름
34
+
35
+ ```
36
+ 사용자: 프로젝트 요청 (자유 형식)
37
+ │
38
+ ▼
39
+ ┌──────────────────┐
40
+ │ 0. DISPATCHER │ 요청 분석 → pipeline.json 생성
41
+ │ (파이프라인 선택) │ 사용자 확인
42
+ └────────┬─────────┘
43
+ │
44
+ ┌────┴────┬──────────┐
45
+ ▼ ▼ ▼
46
+ FULLSTACK FE-ONLY BE-ONLY
47
+ ```
48
+
49
+ ### FULLSTACK — 신규 PRD 기반 풀스택
50
+
51
+ ```
52
+ Planner → Gen-BE → Gen-FE → Eval-Func → Eval-Visual → Archive
53
+ ```
54
+
55
+ ### FE-ONLY — 기존 API + 프론트엔드 연동
56
+
57
+ ```
58
+ Planner(light) → Gen-FE → Eval-Func → Eval-Visual → Archive
59
+ │
60
+ └─ OpenAPI spec → api-contract.json 변환
61
+ Gen-BE SKIP (외부 서버 사용)
62
+ ```
63
+
64
+ ### BE-ONLY — 기존 서버 + 백엔드 기능 추가
65
+
66
+ ```
67
+ Planner → Gen-BE → Eval-Func(API-only) → Archive
68
+ │
69
+ └─ 기존 코드 분석 후 확장 설계
70
+ Gen-FE SKIP, Eval-Visual SKIP
71
+ Eval-Func: Playwright 대신 curl/httpie API 테스트
72
+ ```
73
+
74
+ ### 공통 — 실패 시 루프
75
+
76
+ ```
77
+ Eval-Func FAIL → failure_location에 따라 Gen-BE 또는 Gen-FE 재작업 (max 3회)
78
+ Eval-Visual FAIL → Gen-FE 재작업 (max 3회)
79
+ 3회 초과 → 사용자 개입 요청
80
+ ```
81
+
82
+ ## 수동 프롬프트 실행 방법
83
+
84
+ ### Step 0: Dispatcher (항상 최초 실행)
85
+ ```
86
+ ".harness/prompts/dispatcher.md를 읽고 Dispatcher로 동작하세요.
87
+ [사용자의 자유 형식 요청]"
88
+ ```
89
+
90
+ ### Step 1: Planner
91
+ ```
92
+ ".harness/prompts/planner.md를 읽고 Planner 에이전트로 동작하세요.
93
+ pipeline.json을 참조하여 [light/full] 모드로 실행합니다."
94
+ ```
95
+
96
+ ### Step 2: Generator-Backend (FULLSTACK, BE-ONLY)
97
+ ```
98
+ ".harness/prompts/generator-backend.md를 읽고 Generator-Backend으로 동작하세요.
99
+ Sprint [N]을 시작합니다."
100
+ ```
101
+
102
+ ### Step 3: Generator-Frontend (FULLSTACK, FE-ONLY)
103
+ ```
104
+ ".harness/prompts/generator-frontend.md를 읽고 Generator-Frontend으로 동작하세요.
105
+ Sprint [N]의 프론트엔드를 구현합니다."
106
+ ```
107
+
108
+ ### Step 4: Evaluator-Functional
109
+ ```
110
+ ".harness/prompts/evaluator-functional.md를 읽고 Evaluator-Functional로 동작하세요.
111
+ Sprint [N]을 평가합니다. Playwright MCP를 사용하세요."
112
+ ```
113
+
114
+ ### Step 5: Evaluator-Visual
115
+ ```
116
+ ".harness/prompts/evaluator-visual.md를 읽고 Evaluator-Visual로 동작하세요.
117
+ Sprint [N]의 비주얼을 평가합니다."
118
+ ```
119
+
120
+ ### Archive (Eval-Visual PASS 후)
121
+ ```
122
+ "Sprint [N] 문서를 아카이브 하세요.
123
+ .harness/actions/의 스프린트 문서를 .harness/archive/sprint-NNN/으로 이동합니다."
124
+ ```
125
+
126
+ ## 핵심 원칙
127
+
128
+ 1. **Backend First** — API가 안정된 후 Frontend 연동 (없는 API 호출 방지)
129
+ 2. **api-contract.json이 진실의 원천** — FE↔Gateway↔Services 간 유일한 계약
130
+ 3. **한 세션에 1 스프린트** — 컨텍스트 소진 방지
131
+ 4. **feature-list.json의 passes만 수정** — 기능 정의는 Planner만 변경
132
+ 5. **테스트 삭제/약화 금지** — 테스트는 계약이다
133
+ 6. **Evaluator는 회의적** — 자기 설득 금지, 기준 미달 = FAIL
134
+ 7. **아카이브 불변** — 완료 문서 수정 금지
135
+ 8. **MSA 경계 존수** — 서비스 간 직접 DB 접근 금지, 반드시 메시지 패턴
136
+
137
+ ## Tech Stack
138
+
139
+ | 영역 | 기술 |
140
+ |------|------|
141
+ | Backend Framework | NestJS (TypeScript) |
142
+ | Architecture | MSA (Microservice Architecture) |
143
+ | Monorepo | NestJS 내장 monorepo (nest-cli.json) |
144
+ | Runner | 통합 러너 (`npm run dev` = concurrently) |
145
+ | Transport | TCP (dev) / RabbitMQ·NATS (prod) |
146
+ | Frontend | React 또는 Next.js (TypeScript) |
147
+ | Styling | Tailwind CSS |
148
+ | State | TanStack Query + Zustand |
149
+ | E2E Testing | Playwright MCP |
150
+ | Unit Testing | Jest (backend) + Vitest (frontend) |
151
+ | Database | PostgreSQL (dev: SQLite 가능) |
152
+
153
+ ## MCP 도구
154
+
155
+ Playwright MCP (`@playwright/mcp`) — headless + vision 모드:
156
+ - `browser_navigate`, `browser_click`, `browser_fill`
157
+ - `browser_take_screenshot`, `browser_snapshot`
158
+ - `browser_console_messages`, `browser_network_requests`
159
+ - `browser_resize`, `browser_press_key`, `browser_wait`
@@ -0,0 +1,108 @@
1
+ {
2
+ "harness": {
3
+ "name": "6-Agent Production Harness",
4
+ "version": "3.0.0",
5
+ "description": "Dispatcher + NestJS MSA + React/Next.js + Playwright 기반 실무 하네스",
6
+ "source": "https://www.anthropic.com/engineering/harness-design-long-running-apps"
7
+ },
8
+ "agents": {
9
+ "dispatcher": {
10
+ "role": "사용자 요청 분석 → 파이프라인 결정 (FE-ONLY / BE-ONLY / FULLSTACK)",
11
+ "prompt_file": "prompts/dispatcher.md",
12
+ "outputs": ["actions/pipeline.json"],
13
+ "triggers_next": "planner",
14
+ "always_first": true
15
+ },
16
+ "planner": {
17
+ "role": "제품 사양 + API 계약서 + 서비스 분할 설계",
18
+ "prompt_file": "prompts/planner.md",
19
+ "outputs": [
20
+ "actions/plan.md",
21
+ "actions/feature-list.json",
22
+ "actions/api-contract.json"
23
+ ],
24
+ "triggers_next": ["generator-backend", "generator-frontend"]
25
+ },
26
+ "generator-backend": {
27
+ "role": "NestJS MSA 서비스 구현, API 엔드포인트, DB 스키마",
28
+ "prompt_file": "prompts/generator-backend.md",
29
+ "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
30
+ "outputs": ["code:backend", "actions/sprint-contract.md"],
31
+ "triggers_next": "generator-frontend",
32
+ "order": 1
33
+ },
34
+ "generator-frontend": {
35
+ "role": "React/Next.js UI 컴포넌트, 상태관리, API 연동",
36
+ "prompt_file": "prompts/generator-frontend.md",
37
+ "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
38
+ "outputs": ["code:frontend", "actions/sprint-contract.md"],
39
+ "triggers_next": "evaluator-functional",
40
+ "order": 2
41
+ },
42
+ "evaluator-functional": {
43
+ "role": "Playwright로 E2E 기능 검증, API 응답/DB 상태 확인",
44
+ "prompt_file": "prompts/evaluator-functional.md",
45
+ "tools": ["playwright:browser_*"],
46
+ "inputs": ["actions/sprint-contract.md"],
47
+ "outputs": ["actions/evaluation-functional.md"],
48
+ "on_fail": "generator-backend OR generator-frontend (결함 위치에 따라)",
49
+ "on_pass": "evaluator-visual"
50
+ },
51
+ "evaluator-visual": {
52
+ "role": "디자인 일관성, 반응형, 접근성, AI슬롭 감지",
53
+ "prompt_file": "prompts/evaluator-visual.md",
54
+ "tools": ["playwright:browser_take_screenshot", "playwright:browser_resize", "playwright:browser_snapshot"],
55
+ "inputs": ["actions/sprint-contract.md", "actions/evaluation-functional.md"],
56
+ "outputs": ["actions/evaluation-visual.md"],
57
+ "on_fail": "generator-frontend",
58
+ "on_pass": "archive_and_next_sprint"
59
+ }
60
+ },
61
+ "flow": {
62
+ "sequence": ["dispatcher", "planner", "generator-backend", "generator-frontend", "evaluator-functional", "evaluator-visual"],
63
+ "pipeline_selection": {
64
+ "comment": "Dispatcher가 pipeline.json으로 활성 에이전트를 결정. 이하 sequence에서 스킵 대상 제외.",
65
+ "pipelines": {
66
+ "FULLSTACK": ["planner", "generator-backend", "generator-frontend", "evaluator-functional", "evaluator-visual"],
67
+ "FE-ONLY": ["planner:light", "generator-frontend", "evaluator-functional", "evaluator-visual"],
68
+ "BE-ONLY": ["planner", "generator-backend", "evaluator-functional:api-only"]
69
+ }
70
+ },
71
+ "sprint_execution": {
72
+ "comment": "Backend 먼저 → API 안정 후 Frontend 연동 → 기능 QA → 비주얼 QA (FULLSTACK 기준)",
73
+ "backend_first": true,
74
+ "reason": "프론트가 존재하지 않는 API를 호출하는 실패 방지"
75
+ },
76
+ "retry_on_fail": true,
77
+ "max_retries_per_sprint": 3,
78
+ "archive_on_sprint_complete": true
79
+ },
80
+ "paths": {
81
+ "actions": ".harness/actions",
82
+ "archive": ".harness/archive",
83
+ "prompts": ".harness/prompts",
84
+ "gotchas": ".harness/gotchas",
85
+ "progress": ".harness/progress.txt"
86
+ },
87
+ "tech_stack": {
88
+ "backend": {
89
+ "framework": "NestJS (TypeScript)",
90
+ "architecture": "MSA (Microservice Architecture)",
91
+ "monorepo": "Nx 또는 Turborepo",
92
+ "runner": "통합 러너 (단일 명령으로 전체 서비스 기동/종료)",
93
+ "database": "PostgreSQL (dev: SQLite fallback 가능)",
94
+ "api_gateway": "NestJS API Gateway 패턴",
95
+ "transport": "TCP (dev) / RabbitMQ 또는 NATS (prod)"
96
+ },
97
+ "frontend": {
98
+ "framework": "React 또는 Next.js (TypeScript)",
99
+ "state": "Zustand 또는 TanStack Query",
100
+ "styling": "Tailwind CSS"
101
+ },
102
+ "testing": {
103
+ "e2e": "Playwright MCP (browser_* tools)",
104
+ "backend_unit": "Jest (NestJS 내장)",
105
+ "frontend_unit": "Vitest"
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,15 @@
1
+ # Harness Progress Log
2
+ # 세션 간 상태 전달을 위한 진행 기록 파일
3
+ # ─────────────────────────────────────────────────
4
+
5
+ ## [{{DATE}}] Harness Initialized
6
+ - walwal-harness 설치 완료
7
+ - 6-Agent 아키텍처 (Dispatcher → Planner → Gen-BE → Gen-FE → Eval-Func → Eval-Visual)
8
+ - Gotcha 관리 시스템 활성화
9
+
10
+ ## Current State
11
+ - Phase: INIT (Dispatcher 실행 대기)
12
+ - Active Sprint: none
13
+ - Features Completed: 0 / 0
14
+ - Blocking Issues: none
15
+ - Next Action: "하네스 엔지니어링 시작"으로 Dispatcher 실행
package/bin/init.js ADDED
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * walwal-harness initializer
5
+ *
6
+ * Usage:
7
+ * npx walwal-harness # Interactive init
8
+ * npx walwal-harness --auto # Auto init (postinstall)
9
+ * npx walwal-harness --force # Force reinit
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { execSync } = require('child_process');
15
+
16
+ const PROJECT_ROOT = process.cwd();
17
+ const PKG_ROOT = path.resolve(__dirname, '..');
18
+ const HARNESS_DIR = path.join(PROJECT_ROOT, '.harness');
19
+ const CLAUDE_SKILLS_DIR = path.join(PROJECT_ROOT, '.claude', 'skills');
20
+
21
+ const isAuto = process.argv.includes('--auto');
22
+ const isForce = process.argv.includes('--force');
23
+
24
+ // ─────────────────────────────────────────
25
+ // Utility
26
+ // ─────────────────────────────────────────
27
+ function ensureDir(dir) {
28
+ if (!fs.existsSync(dir)) {
29
+ fs.mkdirSync(dir, { recursive: true });
30
+ }
31
+ }
32
+
33
+ function copyFile(src, dest) {
34
+ ensureDir(path.dirname(dest));
35
+ fs.copyFileSync(src, dest);
36
+ }
37
+
38
+ function copyDir(src, dest) {
39
+ ensureDir(dest);
40
+ const entries = fs.readdirSync(src, { withFileTypes: true });
41
+ for (const entry of entries) {
42
+ const srcPath = path.join(src, entry.name);
43
+ const destPath = path.join(dest, entry.name);
44
+ if (entry.isDirectory()) {
45
+ copyDir(srcPath, destPath);
46
+ } else {
47
+ copyFile(srcPath, destPath);
48
+ }
49
+ }
50
+ }
51
+
52
+ function fileExists(p) {
53
+ return fs.existsSync(p);
54
+ }
55
+
56
+ function log(msg) {
57
+ console.log(`[walwal-harness] ${msg}`);
58
+ }
59
+
60
+ // ─────────────────────────────────────────
61
+ // 1. .harness/ scaffolding
62
+ // ─────────────────────────────────────────
63
+ function scaffoldHarness() {
64
+ log('Scaffolding .harness/ directory...');
65
+
66
+ // Core directories
67
+ ensureDir(path.join(HARNESS_DIR, 'actions'));
68
+ ensureDir(path.join(HARNESS_DIR, 'archive'));
69
+ ensureDir(path.join(HARNESS_DIR, 'gotchas'));
70
+
71
+ // Copy gotchas initial files
72
+ const gotchasSrc = path.join(PKG_ROOT, 'gotchas');
73
+ if (fs.existsSync(gotchasSrc)) {
74
+ const files = fs.readdirSync(gotchasSrc);
75
+ for (const file of files) {
76
+ const dest = path.join(HARNESS_DIR, 'gotchas', file);
77
+ if (!fileExists(dest) || isForce) {
78
+ copyFile(path.join(gotchasSrc, file), dest);
79
+ }
80
+ }
81
+ }
82
+
83
+ // Copy templates as initial files
84
+ const templateMap = {
85
+ 'progress.txt.template': path.join(HARNESS_DIR, 'progress.txt'),
86
+ };
87
+
88
+ const templatesDir = path.join(PKG_ROOT, 'assets', 'templates');
89
+ if (fs.existsSync(templatesDir)) {
90
+ for (const [template, dest] of Object.entries(templateMap)) {
91
+ const src = path.join(templatesDir, template);
92
+ if (fs.existsSync(src) && (!fileExists(dest) || isForce)) {
93
+ let content = fs.readFileSync(src, 'utf8');
94
+ content = content.replace(/\{\{DATE\}\}/g, new Date().toISOString().split('T')[0]);
95
+ fs.writeFileSync(dest, content);
96
+ }
97
+ }
98
+ }
99
+
100
+ // Copy config.json
101
+ const configSrc = path.join(PKG_ROOT, 'assets', 'templates', 'config.json');
102
+ const configDest = path.join(HARNESS_DIR, 'config.json');
103
+ if (fs.existsSync(configSrc) && (!fileExists(configDest) || isForce)) {
104
+ copyFile(configSrc, configDest);
105
+ }
106
+
107
+ // Copy HARNESS.md
108
+ const harnessMdSrc = path.join(PKG_ROOT, 'assets', 'templates', 'HARNESS.md');
109
+ const harnessMdDest = path.join(HARNESS_DIR, 'HARNESS.md');
110
+ if (fs.existsSync(harnessMdSrc) && (!fileExists(harnessMdDest) || isForce)) {
111
+ copyFile(harnessMdSrc, harnessMdDest);
112
+ }
113
+
114
+ log('.harness/ scaffolding complete');
115
+ }
116
+
117
+ // ─────────────────────────────────────────
118
+ // 2. Skills → .claude/skills/
119
+ // ─────────────────────────────────────────
120
+ function installSkills() {
121
+ log('Installing skills to .claude/skills/...');
122
+
123
+ const skillsSrc = path.join(PKG_ROOT, 'skills');
124
+ if (!fs.existsSync(skillsSrc)) {
125
+ log('WARNING: skills/ directory not found in package');
126
+ return;
127
+ }
128
+
129
+ const skills = fs.readdirSync(skillsSrc, { withFileTypes: true })
130
+ .filter(d => d.isDirectory())
131
+ .map(d => d.name);
132
+
133
+ for (const skill of skills) {
134
+ const src = path.join(skillsSrc, skill);
135
+ const dest = path.join(CLAUDE_SKILLS_DIR, `harness-${skill}`);
136
+
137
+ if (!fileExists(dest) || isForce) {
138
+ copyDir(src, dest);
139
+ log(` Installed: harness-${skill}`);
140
+ } else {
141
+ log(` Skipped (exists): harness-${skill}`);
142
+ }
143
+ }
144
+
145
+ log('Skills installation complete');
146
+ }
147
+
148
+ // ─────────────────────────────────────────
149
+ // 3. Scripts
150
+ // ─────────────────────────────────────────
151
+ function installScripts() {
152
+ log('Installing scripts...');
153
+
154
+ const scriptsSrc = path.join(PKG_ROOT, 'scripts');
155
+ const scriptsDest = path.join(PROJECT_ROOT, 'scripts');
156
+
157
+ if (fs.existsSync(scriptsSrc)) {
158
+ ensureDir(scriptsDest);
159
+ const files = fs.readdirSync(scriptsSrc);
160
+ for (const file of files) {
161
+ const dest = path.join(scriptsDest, file);
162
+ if (!fileExists(dest) || isForce) {
163
+ copyFile(path.join(scriptsSrc, file), dest);
164
+ try { fs.chmodSync(dest, '755'); } catch (e) {}
165
+ }
166
+ }
167
+ }
168
+
169
+ log('Scripts installation complete');
170
+ }
171
+
172
+ // ─────────────────────────────────────────
173
+ // 4. AGENTS.md + CLAUDE.md
174
+ // ─────────────────────────────────────────
175
+ function setupAgentsMd() {
176
+ const agentsMd = path.join(PROJECT_ROOT, 'AGENTS.md');
177
+ const claudeMd = path.join(PROJECT_ROOT, 'CLAUDE.md');
178
+
179
+ // Run scan if no AGENTS.md
180
+ if (!fileExists(agentsMd) || isForce) {
181
+ log('Running project scan...');
182
+ try {
183
+ execSync(`bash "${path.join(PKG_ROOT, 'scripts', 'scan-project.sh')}" "${PROJECT_ROOT}"`, {
184
+ stdio: 'inherit'
185
+ });
186
+ execSync(`bash "${path.join(PKG_ROOT, 'scripts', 'init-agents-md.sh')}" "${PROJECT_ROOT}"`, {
187
+ stdio: 'inherit'
188
+ });
189
+ } catch (e) {
190
+ log('WARNING: Auto-scan failed. Run manually: bash scripts/scan-project.sh .');
191
+ // Create minimal AGENTS.md
192
+ const templateSrc = path.join(PKG_ROOT, 'assets', 'templates', 'AGENTS.md.template');
193
+ if (fs.existsSync(templateSrc)) {
194
+ let content = fs.readFileSync(templateSrc, 'utf8');
195
+ content = content.replace(/\{\{DATE\}\}/g, new Date().toISOString().split('T')[0]);
196
+ fs.writeFileSync(agentsMd, content);
197
+ }
198
+ }
199
+ }
200
+
201
+ // Ensure CLAUDE.md symlink
202
+ if (fileExists(agentsMd)) {
203
+ try {
204
+ const stat = fs.lstatSync(claudeMd);
205
+ if (!stat.isSymbolicLink()) {
206
+ // Backup existing CLAUDE.md
207
+ const backupDir = path.join(HARNESS_DIR, 'archive', 'pre-harness-backup');
208
+ ensureDir(backupDir);
209
+ fs.copyFileSync(claudeMd, path.join(backupDir, `CLAUDE.md.${Date.now()}.bak`));
210
+ fs.unlinkSync(claudeMd);
211
+ fs.symlinkSync('AGENTS.md', claudeMd);
212
+ log('CLAUDE.md backed up and replaced with symlink → AGENTS.md');
213
+ }
214
+ } catch (e) {
215
+ // CLAUDE.md doesn't exist
216
+ try {
217
+ fs.symlinkSync('AGENTS.md', claudeMd);
218
+ log('Created symlink: CLAUDE.md → AGENTS.md');
219
+ } catch (e2) {}
220
+ }
221
+ }
222
+ }
223
+
224
+ // ─────────────────────────────────────────
225
+ // 5. Playwright MCP check
226
+ // ─────────────────────────────────────────
227
+ function checkPlaywrightMcp() {
228
+ const mcpJson = path.join(require('os').homedir(), '.mcp.json');
229
+
230
+ if (fileExists(mcpJson)) {
231
+ try {
232
+ const config = JSON.parse(fs.readFileSync(mcpJson, 'utf8'));
233
+ if (config.mcpServers && config.mcpServers.playwright) {
234
+ log('Playwright MCP: already configured');
235
+ return;
236
+ }
237
+ } catch (e) {}
238
+ }
239
+
240
+ log('');
241
+ log('NOTE: Playwright MCP is not configured.');
242
+ log('Evaluator agents require Playwright MCP for browser testing.');
243
+ log('Add to ~/.mcp.json:');
244
+ log('');
245
+ log(' {');
246
+ log(' "mcpServers": {');
247
+ log(' "playwright": {');
248
+ log(' "command": "npx",');
249
+ log(' "args": ["-y", "@playwright/mcp@latest", "--headless", "--caps", "vision"]');
250
+ log(' }');
251
+ log(' }');
252
+ log(' }');
253
+ log('');
254
+ }
255
+
256
+ // ─────────────────────────────────────────
257
+ // Main
258
+ // ─────────────────────────────────────────
259
+ function main() {
260
+ console.log('');
261
+ console.log('╔══════════════════════════════════════╗');
262
+ console.log('║ walwal-harness v1.0.0 ║');
263
+ console.log('║ AI Agent Harness Engineering ║');
264
+ console.log('╚══════════════════════════════════════╝');
265
+ console.log('');
266
+
267
+ scaffoldHarness();
268
+ installSkills();
269
+ installScripts();
270
+ setupAgentsMd();
271
+ checkPlaywrightMcp();
272
+
273
+ console.log('');
274
+ log('═══ Initialization Complete ═══');
275
+ log('');
276
+ log('Start with: "하네스 엔지니어링 시작"');
277
+ log('Or invoke: /harness-dispatcher');
278
+ console.log('');
279
+ }
280
+
281
+ main();
@@ -0,0 +1,37 @@
1
+ # Gotchas — Agent Mistake Registry
2
+
3
+ > 이 디렉토리는 각 에이전트가 반복하는 실수를 누적 기록합니다.
4
+ > Dispatcher가 사용자의 실수 지적을 감지하면 해당 에이전트의 gotchas 파일에 추가합니다.
5
+ > 각 에이전트는 세션 시작 시 자신의 gotchas 파일을 읽고 같은 실수를 반복하지 않습니다.
6
+
7
+ ## 파일 구조
8
+
9
+ ```
10
+ .harness/gotchas/
11
+ ├── README.md # 이 파일
12
+ ├── planner.md # Planner의 반복 실수
13
+ ├── generator-backend.md # Generator-Backend의 반복 실수
14
+ ├── generator-frontend.md # Generator-Frontend의 반복 실수
15
+ ├── evaluator-functional.md # Evaluator-Functional의 반복 실수
16
+ └── evaluator-visual.md # Evaluator-Visual의 반복 실수
17
+ ```
18
+
19
+ ## 항목 형식
20
+
21
+ ```markdown
22
+ ### [G-NNN] 간결한 제목
23
+ - **Date**: YYYY-MM-DD
24
+ - **Trigger**: 사용자가 한 말 (원문 요약)
25
+ - **Wrong**: 에이전트가 했던 잘못된 행동
26
+ - **Right**: 올바른 행동
27
+ - **Why**: 왜 잘못인지 근거
28
+ - **Scope**: 이 규칙이 적용되는 조건/범위
29
+ ```
30
+
31
+ ## 관리 규칙
32
+
33
+ - Dispatcher만 gotchas 파일에 쓰기 가능
34
+ - 각 에이전트는 자신의 gotchas 파일을 읽기 전용으로 참조
35
+ - 중복 항목 금지 — 같은 실수는 기존 항목에 횟수 증가
36
+ - 해결된 항목은 삭제하지 않고 `[RESOLVED]` 태그 추가
37
+ - 항목이 20개 초과 시 가장 오래된 RESOLVED 항목부터 정리
@@ -0,0 +1,5 @@
1
+ # Gotchas — Evaluator-Functional
2
+
3
+ > Dispatcher가 관리. Evaluator-Functional은 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
4
+
5
+ <!-- 항목이 추가되면 아래에 기록됩니다 -->
@@ -0,0 +1,5 @@
1
+ # Gotchas — Evaluator-Visual
2
+
3
+ > Dispatcher가 관리. Evaluator-Visual은 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
4
+
5
+ <!-- 항목이 추가되면 아래에 기록됩니다 -->
@@ -0,0 +1,5 @@
1
+ # Gotchas — Generator-Backend
2
+
3
+ > Dispatcher가 관리. Generator-Backend는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
4
+
5
+ <!-- 항목이 추가되면 아래에 기록됩니다 -->
@@ -0,0 +1,5 @@
1
+ # Gotchas — Generator-Frontend
2
+
3
+ > Dispatcher가 관리. Generator-Frontend는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
4
+
5
+ <!-- 항목이 추가되면 아래에 기록됩니다 -->