@walwal-harness/cli 6.0.5 → 6.1.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/CHANGELOG.md +65 -0
- package/commands/harness-solo.md +20 -20
- package/commands/harness-team.md +1 -1
- package/gotchas/coo-developer.md +13 -0
- package/gotchas/documentationer.md +13 -0
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +416 -0
- package/scripts/harness-archive.sh +123 -0
- package/scripts/harness-dashboard-up.sh +72 -0
- package/scripts/harness-dashboard.sh +509 -0
- package/scripts/harness-goal-init.sh +72 -0
- package/scripts/harness-goal-show.sh +37 -0
- package/scripts/harness-gotcha-memory.sh +348 -0
- package/scripts/harness-gotcha-register.sh +393 -0
- package/scripts/harness-meeting-doc.sh +153 -0
- package/scripts/harness-monitor.sh +398 -0
- package/scripts/harness-next.sh +577 -0
- package/scripts/harness-progress-set.sh +41 -0
- package/scripts/harness-prompt-history.sh +164 -0
- package/scripts/harness-queue-manager.sh +694 -0
- package/scripts/harness-session-start.sh +140 -0
- package/scripts/harness-statusline.sh +139 -0
- package/scripts/harness-task-session.sh +48 -0
- package/scripts/harness-tmux.sh +372 -0
- package/scripts/harness-user-prompt-submit.sh +123 -0
- package/scripts/init-agents-md.sh +337 -0
- package/scripts/init-ref-docs.sh +262 -0
- package/scripts/lib/harness-audit.sh +113 -0
- package/scripts/lib/harness-guardrail.sh +148 -0
- package/scripts/lib/harness-keywait.sh +57 -0
- package/scripts/lib/harness-render-progress.sh +346 -0
- package/scripts/scan-project.sh +367 -0
- package/skills/brainstorming/SKILL.md +4 -4
- package/skills/conductor/SKILL.md +19 -16
- package/skills/coo-developer/SKILL.md +42 -0
- package/skills/cqo/SKILL.md +7 -0
- package/skills/cto/SKILL.md +7 -0
- package/skills/dispatcher/SKILL.md +17 -28
- package/skills/dispatcher/persona-ceo.md +2 -1
- package/skills/documentationer/SKILL.md +46 -0
- package/skills/evaluator-code-quality/SKILL.md +1 -1
- package/skills/evaluator-functional/SKILL.md +1 -1
- package/skills/evaluator-visual/SKILL.md +1 -1
- package/skills/generator-backend/SKILL.md +1 -1
- package/skills/generator-frontend/SKILL.md +1 -1
- package/skills/meeting-manager/SKILL.md +34 -1
- package/skills/planner/SKILL.md +23 -9
- package/skills/planner/persona-coo-hr.md +17 -0
- package/skills/service-ops/SKILL.md +19 -4
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# init-agents-md.sh — scan-result.json을 기반으로 AGENTS.md를 생성/리빌드
|
|
3
|
+
# 기존 CLAUDE.md의 사용자 규칙을 보존하면서 하네스 포맷으로 변환
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
PROJECT_ROOT="${1:-.}"
|
|
7
|
+
SCAN_RESULT="${PROJECT_ROOT}/.harness/actions/scan-result.json"
|
|
8
|
+
export SCAN_RESULT PROJECT_ROOT
|
|
9
|
+
AGENTS_FILE="${PROJECT_ROOT}/AGENTS.md"
|
|
10
|
+
CLAUDE_FILE="${PROJECT_ROOT}/CLAUDE.md"
|
|
11
|
+
BACKUP_DIR="${PROJECT_ROOT}/.harness/archive/pre-harness-backup"
|
|
12
|
+
|
|
13
|
+
if [ ! -f "$SCAN_RESULT" ]; then
|
|
14
|
+
echo "ERROR: scan-result.json not found. Run scan-project.sh first."
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# ─────────────────────────────────────────
|
|
19
|
+
# 1. Python으로 scan-result.json 파싱
|
|
20
|
+
# ─────────────────────────────────────────
|
|
21
|
+
eval "$(python3 << 'PYEOF'
|
|
22
|
+
import json, sys, os
|
|
23
|
+
|
|
24
|
+
with open(os.environ.get("SCAN_RESULT", ".harness/actions/scan-result.json")) as f:
|
|
25
|
+
scan = json.load(f)
|
|
26
|
+
|
|
27
|
+
docs = scan["existing_docs"]
|
|
28
|
+
tech = scan["tech_stack"]
|
|
29
|
+
rec = scan["recommendation"]
|
|
30
|
+
dirs = scan["structure"]["directories"]
|
|
31
|
+
configs = scan["structure"]["config_files"]
|
|
32
|
+
|
|
33
|
+
print(f'ACTION="{rec["agents_md_action"]}"')
|
|
34
|
+
print(f'PROJECT_TYPE="{rec["project_type"]}"')
|
|
35
|
+
print(f'TECH_BE="{tech["backend"]}"')
|
|
36
|
+
print(f'TECH_FE="{tech["frontend"]}"')
|
|
37
|
+
print(f'TECH_DB="{tech["database"]}"')
|
|
38
|
+
print(f'TECH_MONO="{tech["monorepo"]}"')
|
|
39
|
+
print(f'OPENAPI="{scan["api"]["openapi_spec"]}"')
|
|
40
|
+
print(f'HAS_EXISTING_CLAUDE="{docs["claude_md"]}"')
|
|
41
|
+
print(f'EXISTING_CONTENT={json.dumps(docs["existing_claude_md_content"])}')
|
|
42
|
+
PYEOF
|
|
43
|
+
)"
|
|
44
|
+
|
|
45
|
+
echo "=== AGENTS.md Initialization ==="
|
|
46
|
+
echo "Action: ${ACTION}"
|
|
47
|
+
echo "Project Type: ${PROJECT_TYPE}"
|
|
48
|
+
echo ""
|
|
49
|
+
|
|
50
|
+
# ─────────────────────────────────────────
|
|
51
|
+
# 2. 기존 문서 백업
|
|
52
|
+
# ─────────────────────────────────────────
|
|
53
|
+
if [ "$ACTION" = "migrate-claude-to-agents" ] || [ "$ACTION" = "rebuild-with-harness" ]; then
|
|
54
|
+
mkdir -p "$BACKUP_DIR"
|
|
55
|
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
56
|
+
|
|
57
|
+
if [ -f "$AGENTS_FILE" ] && [ ! -L "$AGENTS_FILE" ]; then
|
|
58
|
+
cp "$AGENTS_FILE" "${BACKUP_DIR}/AGENTS.md.${TIMESTAMP}.bak"
|
|
59
|
+
echo "Backed up: AGENTS.md → ${BACKUP_DIR}/AGENTS.md.${TIMESTAMP}.bak"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
if [ -f "$CLAUDE_FILE" ] && [ ! -L "$CLAUDE_FILE" ]; then
|
|
63
|
+
cp "$CLAUDE_FILE" "${BACKUP_DIR}/CLAUDE.md.${TIMESTAMP}.bak"
|
|
64
|
+
echo "Backed up: CLAUDE.md → ${BACKUP_DIR}/CLAUDE.md.${TIMESTAMP}.bak"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# ─────────────────────────────────────────
|
|
69
|
+
# 3. IA-MAP 자동 생성
|
|
70
|
+
# ─────────────────────────────────────────
|
|
71
|
+
generate_ia_map() {
|
|
72
|
+
python3 << 'PYEOF'
|
|
73
|
+
import json, os
|
|
74
|
+
|
|
75
|
+
with open(os.environ.get("SCAN_RESULT", ".harness/actions/scan-result.json")) as f:
|
|
76
|
+
scan = json.load(f)
|
|
77
|
+
|
|
78
|
+
dirs = scan["structure"]["directories"]
|
|
79
|
+
tech = scan["tech_stack"]
|
|
80
|
+
|
|
81
|
+
# 경로별 태그/설명 추론 규칙
|
|
82
|
+
tag_rules = {
|
|
83
|
+
# Backend patterns
|
|
84
|
+
"apps/gateway": ("[BE]", "API Gateway", "Generator-Backend"),
|
|
85
|
+
"apps/service": ("[BE]", "Microservice", "Generator-Backend"),
|
|
86
|
+
"src/controllers": ("[BE]", "API Controllers", "Generator-Backend"),
|
|
87
|
+
"src/services": ("[BE]", "Business Logic", "Generator-Backend"),
|
|
88
|
+
"src/models": ("[BE]", "Data Models", "Generator-Backend"),
|
|
89
|
+
"src/entities": ("[BE]", "DB Entities", "Generator-Backend"),
|
|
90
|
+
"src/modules": ("[BE]", "NestJS Modules", "Generator-Backend"),
|
|
91
|
+
"src/guards": ("[BE]", "Auth Guards", "Generator-Backend"),
|
|
92
|
+
"src/pipes": ("[BE]", "Validation Pipes", "Generator-Backend"),
|
|
93
|
+
"src/interceptors": ("[BE]", "Interceptors", "Generator-Backend"),
|
|
94
|
+
"src/filters": ("[BE]", "Exception Filters", "Generator-Backend"),
|
|
95
|
+
"routers": ("[BE]", "API Routers", "Generator-Backend"),
|
|
96
|
+
"routes": ("[BE]", "API Routes", "Generator-Backend"),
|
|
97
|
+
"middleware": ("[BE]", "Middleware", "Generator-Backend"),
|
|
98
|
+
"migrations": ("[BE]", "DB Migrations", "Generator-Backend"),
|
|
99
|
+
|
|
100
|
+
# Frontend patterns
|
|
101
|
+
"apps/web": ("[FE]", "Frontend App", "Generator-Frontend"),
|
|
102
|
+
"src/components": ("[FE]", "UI Components", "Generator-Frontend"),
|
|
103
|
+
"src/pages": ("[FE]", "Page Routes", "Generator-Frontend"),
|
|
104
|
+
"src/app": ("[FE]", "App Router", "Generator-Frontend"),
|
|
105
|
+
"src/hooks": ("[FE]", "Custom Hooks", "Generator-Frontend"),
|
|
106
|
+
"src/stores": ("[FE]", "State Management", "Generator-Frontend"),
|
|
107
|
+
"src/styles": ("[FE]", "Stylesheets", "Generator-Frontend"),
|
|
108
|
+
"src/api": ("[FE]", "API Client Layer", "Generator-Frontend"),
|
|
109
|
+
"src/lib": ("[FE]", "Utility Library", "Generator-Frontend"),
|
|
110
|
+
"public": ("[FE]", "Static Assets", "Generator-Frontend"),
|
|
111
|
+
|
|
112
|
+
# Shared / Libs
|
|
113
|
+
"libs/shared": ("[BE]", "Shared Libraries", "Generator-Backend"),
|
|
114
|
+
"libs/database": ("[BE]", "Database Module", "Generator-Backend"),
|
|
115
|
+
"libs/common": ("[BE]", "Common Utilities", "Generator-Backend"),
|
|
116
|
+
|
|
117
|
+
# Infra
|
|
118
|
+
"docker": ("[INFRA]", "Docker Configuration", "Planner"),
|
|
119
|
+
".github": ("[INFRA]", "CI/CD Workflows", "Planner"),
|
|
120
|
+
"deploy": ("[INFRA]", "Deployment Scripts", "Planner"),
|
|
121
|
+
"terraform": ("[INFRA]", "Infrastructure as Code", "Planner"),
|
|
122
|
+
"k8s": ("[INFRA]", "Kubernetes Manifests", "Planner"),
|
|
123
|
+
|
|
124
|
+
# Harness
|
|
125
|
+
".harness": ("[HARNESS]", "Harness System", "Planner"),
|
|
126
|
+
|
|
127
|
+
# Tests
|
|
128
|
+
"test": ("[TEST]", "Test Suite", "Evaluator"),
|
|
129
|
+
"tests": ("[TEST]", "Test Suite", "Evaluator"),
|
|
130
|
+
"e2e": ("[TEST]", "E2E Tests", "Evaluator"),
|
|
131
|
+
"__tests__": ("[TEST]", "Unit Tests", "Evaluator"),
|
|
132
|
+
|
|
133
|
+
# Native app patterns (Swift / Kotlin / Rust 등)
|
|
134
|
+
"Sources": ("[FE]", "Native Source Root", "Generator-Frontend"),
|
|
135
|
+
"Tests": ("[TEST]", "Native Test Suite", "Evaluator"),
|
|
136
|
+
"Resources": ("[FE]", "Native Resources", "Generator-Frontend"),
|
|
137
|
+
"android": ("[FE]", "Android Host", "Generator-Frontend"),
|
|
138
|
+
"ios": ("[FE]", "iOS Host", "Generator-Frontend"),
|
|
139
|
+
"macos": ("[FE]", "macOS Host", "Generator-Frontend"),
|
|
140
|
+
"Shared": ("[FE]", "Cross-platform Shared", "Generator-Frontend"),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# 디렉토리를 분석
|
|
144
|
+
ia_lines = []
|
|
145
|
+
for d in sorted(dirs):
|
|
146
|
+
if d == "." or not d:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
matched = False
|
|
150
|
+
for pattern, (tag, desc, owner) in tag_rules.items():
|
|
151
|
+
if pattern in d:
|
|
152
|
+
depth = d.count("/")
|
|
153
|
+
indent = "│ " * depth
|
|
154
|
+
name = d.split("/")[-1] + "/"
|
|
155
|
+
ia_lines.append(f"{indent}├── {name:<25} # {tag} {desc:<30} → {owner}")
|
|
156
|
+
matched = True
|
|
157
|
+
break
|
|
158
|
+
|
|
159
|
+
if not matched:
|
|
160
|
+
depth = d.count("/")
|
|
161
|
+
if depth <= 1: # 1depth만 미분류 표시
|
|
162
|
+
indent = "│ " * depth
|
|
163
|
+
name = d.split("/")[-1] + "/"
|
|
164
|
+
ia_lines.append(f"{indent}├── {name:<25} # [?] (Planner가 분류 필요) → TBD")
|
|
165
|
+
|
|
166
|
+
for line in ia_lines:
|
|
167
|
+
print(line)
|
|
168
|
+
PYEOF
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
IA_MAP=$(generate_ia_map)
|
|
172
|
+
|
|
173
|
+
# ─────────────────────────────────────────
|
|
174
|
+
# 4. 기존 사용자 규칙 추출 (CLAUDE.md에서)
|
|
175
|
+
# ─────────────────────────────────────────
|
|
176
|
+
extract_user_rules() {
|
|
177
|
+
if [ "$HAS_EXISTING_CLAUDE" = "exists" ]; then
|
|
178
|
+
python3 << 'PYEOF'
|
|
179
|
+
import json, os
|
|
180
|
+
|
|
181
|
+
with open(os.environ.get("SCAN_RESULT", ".harness/actions/scan-result.json")) as f:
|
|
182
|
+
scan = json.load(f)
|
|
183
|
+
|
|
184
|
+
content = scan["existing_docs"]["existing_claude_md_content"]
|
|
185
|
+
|
|
186
|
+
if not content or content.strip() == "":
|
|
187
|
+
print("(기존 규칙 없음)")
|
|
188
|
+
else:
|
|
189
|
+
# 사용자 정의 규칙 영역만 추출 (harness 관련 제외)
|
|
190
|
+
lines = content.split("\n")
|
|
191
|
+
user_section = []
|
|
192
|
+
skip = False
|
|
193
|
+
for line in lines:
|
|
194
|
+
# harness가 생성한 섹션은 제외
|
|
195
|
+
if any(kw in line.lower() for kw in ["ia-map", "ia map", "harness", "## tech stack"]):
|
|
196
|
+
skip = True
|
|
197
|
+
elif line.startswith("## ") and skip:
|
|
198
|
+
skip = False
|
|
199
|
+
|
|
200
|
+
if not skip:
|
|
201
|
+
user_section.append(line)
|
|
202
|
+
|
|
203
|
+
result = "\n".join(user_section).strip()
|
|
204
|
+
if result:
|
|
205
|
+
print(result)
|
|
206
|
+
else:
|
|
207
|
+
print("(기존 규칙 없음)")
|
|
208
|
+
PYEOF
|
|
209
|
+
else
|
|
210
|
+
echo "(기존 규칙 없음)"
|
|
211
|
+
fi
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
USER_RULES=$(extract_user_rules)
|
|
215
|
+
|
|
216
|
+
# ─────────────────────────────────────────
|
|
217
|
+
# 5. AGENTS.md 생성
|
|
218
|
+
# ─────────────────────────────────────────
|
|
219
|
+
cat > "$AGENTS_FILE" << AGENTSEOF
|
|
220
|
+
# AGENTS.md — Project Context for AI Agents
|
|
221
|
+
|
|
222
|
+
> 이 파일은 모든 AI 에이전트(Claude, Cursor, Copilot, Windsurf 등)의 공통 진입점입니다.
|
|
223
|
+
> CLAUDE.md는 이 파일의 심볼릭 링크입니다.
|
|
224
|
+
> Generated by walwal-harness ($(date +%Y-%m-%d))
|
|
225
|
+
|
|
226
|
+
## Project
|
|
227
|
+
|
|
228
|
+
- **Name**: (Planner가 설정)
|
|
229
|
+
- **Description**: (Planner가 설정)
|
|
230
|
+
- **Phase**: INIT
|
|
231
|
+
- **Type**: ${PROJECT_TYPE}
|
|
232
|
+
- **Harness**: \`.harness/HARNESS.md\` 참조
|
|
233
|
+
|
|
234
|
+
## Tech Stack (Auto-detected)
|
|
235
|
+
|
|
236
|
+
- Backend: ${TECH_BE}
|
|
237
|
+
- Frontend: ${TECH_FE}
|
|
238
|
+
- Database: ${TECH_DB}
|
|
239
|
+
- Monorepo: ${TECH_MONO}
|
|
240
|
+
- OpenAPI: ${OPENAPI}
|
|
241
|
+
|
|
242
|
+
## IA-MAP (Information Architecture)
|
|
243
|
+
|
|
244
|
+
> 자동 스캔으로 생성됨. Planner가 검토 후 확정해야 합니다.
|
|
245
|
+
> \`[?]\` 태그는 Planner가 분류해야 하는 미확인 경로입니다.
|
|
246
|
+
|
|
247
|
+
\`\`\`
|
|
248
|
+
/
|
|
249
|
+
${IA_MAP}
|
|
250
|
+
├── AGENTS.md # [META] 프로젝트 컨텍스트 → Planner
|
|
251
|
+
├── CLAUDE.md # [META] → AGENTS.md 심볼릭 링크
|
|
252
|
+
└── .harness/ # [HARNESS] 하네스 시스템 → Planner
|
|
253
|
+
\`\`\`
|
|
254
|
+
|
|
255
|
+
### IA-MAP 범례
|
|
256
|
+
|
|
257
|
+
| 태그 | 의미 | 소유 에이전트 |
|
|
258
|
+
|------|------|--------------|
|
|
259
|
+
| \`[BE]\` | Backend 영역 | Generator-Backend |
|
|
260
|
+
| \`[FE]\` | Frontend 영역 | Generator-Frontend |
|
|
261
|
+
| \`[HARNESS]\` | 하네스 시스템 | Planner / Evaluator |
|
|
262
|
+
| \`[META]\` | 프로젝트 메타 문서 | Planner |
|
|
263
|
+
| \`[INFRA]\` | 인프라/배포 설정 | Planner |
|
|
264
|
+
| \`[TEST]\` | 테스트 코드 | Evaluator / Generator |
|
|
265
|
+
| \`[?]\` | 미분류 (Planner 확인 필요) | TBD |
|
|
266
|
+
|
|
267
|
+
## Preserved Rules (기존 프로젝트에서 이관)
|
|
268
|
+
|
|
269
|
+
${USER_RULES}
|
|
270
|
+
|
|
271
|
+
## Rules (모든 에이전트 공통)
|
|
272
|
+
|
|
273
|
+
### 읽기/쓰기 권한
|
|
274
|
+
|
|
275
|
+
| 파일 | 읽기 | 쓰기 |
|
|
276
|
+
|------|------|------|
|
|
277
|
+
| AGENTS.md | 전체 | Planner만 |
|
|
278
|
+
| .harness/actions/api-contract.json | 전체 | Planner만 |
|
|
279
|
+
| .harness/actions/feature-list.json | 전체 | passes: Generator, 나머지: Planner |
|
|
280
|
+
| .harness/progress.json | 전체 | 전체 (Session Boundary Protocol에 따라 업데이트) |
|
|
281
|
+
| \`[BE]\` 소유 경로 | 전체 | Generator-Backend만 |
|
|
282
|
+
| \`[FE]\` 소유 경로 | 전체 | Generator-Frontend만 |
|
|
283
|
+
| .harness/archive/ | 전체 | 쓰기 금지 (불변) |
|
|
284
|
+
|
|
285
|
+
### 변경 요청 프로토콜
|
|
286
|
+
|
|
287
|
+
AGENTS.md 또는 api-contract.json 변경이 필요할 때:
|
|
288
|
+
1. sprint-contract.md에 \`## Change Request\` 섹션 추가
|
|
289
|
+
2. Planner가 다음 스프린트 전환 시 반영 여부 결정
|
|
290
|
+
|
|
291
|
+
### 금지 사항
|
|
292
|
+
|
|
293
|
+
- AGENTS.md를 Planner 외 에이전트가 수정
|
|
294
|
+
- 서비스 간 직접 DB 접근
|
|
295
|
+
- 테스트 삭제/약화
|
|
296
|
+
- archive/ 내 파일 수정
|
|
297
|
+
- 프로젝트를 조기 "완료" 선언
|
|
298
|
+
|
|
299
|
+
## Harness Quick Reference
|
|
300
|
+
|
|
301
|
+
| 명령 | 설명 |
|
|
302
|
+
|------|------|
|
|
303
|
+
| \`bash scripts/scan-project.sh\` | 프로젝트 구조 재스캔 |
|
|
304
|
+
| \`.harness/progress.json\` | 현재 진행 상태 (기계 판독) |
|
|
305
|
+
| \`.harness/actions/\` | 활성 스프린트 문서 |
|
|
306
|
+
| \`.harness/HARNESS.md\` | 하네스 상세 가이드 |
|
|
307
|
+
AGENTSEOF
|
|
308
|
+
|
|
309
|
+
echo ""
|
|
310
|
+
echo "=== AGENTS.md Generated ==="
|
|
311
|
+
echo "Path: ${AGENTS_FILE}"
|
|
312
|
+
|
|
313
|
+
# ─────────────────────────────────────────
|
|
314
|
+
# 6. CLAUDE.md 심볼릭 링크
|
|
315
|
+
# ─────────────────────────────────────────
|
|
316
|
+
if [ -f "$CLAUDE_FILE" ] && [ ! -L "$CLAUDE_FILE" ]; then
|
|
317
|
+
echo ""
|
|
318
|
+
echo "WARNING: CLAUDE.md가 일반 파일로 존재합니다."
|
|
319
|
+
echo "기존 내용은 AGENTS.md의 'Preserved Rules' 섹션에 이관되었습니다."
|
|
320
|
+
echo "백업: ${BACKUP_DIR}/"
|
|
321
|
+
rm "$CLAUDE_FILE"
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
if [ ! -L "$CLAUDE_FILE" ]; then
|
|
325
|
+
ln -sf AGENTS.md "$CLAUDE_FILE"
|
|
326
|
+
echo "Created symlink: CLAUDE.md → AGENTS.md"
|
|
327
|
+
else
|
|
328
|
+
echo "Symlink already exists: CLAUDE.md → AGENTS.md"
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
echo ""
|
|
332
|
+
echo "=== Initialization Complete ==="
|
|
333
|
+
echo ""
|
|
334
|
+
echo "Next steps:"
|
|
335
|
+
echo " 1. AGENTS.md의 [?] 태그를 확인하고 Planner에게 분류를 요청하세요"
|
|
336
|
+
echo " 2. 'Preserved Rules' 섹션에서 불필요한 규칙을 정리하세요"
|
|
337
|
+
echo " 3. '하네스 엔지니어링 시작'으로 Dispatcher를 실행하세요"
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# init-ref-docs.sh — 감지된 스택에 따라 .harness/ref/<role>-<stack>.md 를 준비한다.
|
|
3
|
+
#
|
|
4
|
+
# bash 혼자서는 Claude 의 WebSearch/WebFetch 를 호출할 수 없으므로 이 스크립트는:
|
|
5
|
+
# 1. scan-result.json 에서 감지된 스택 목록을 계산
|
|
6
|
+
# 2. --placeholder 모드: 최소 frontmatter + TODO 본문을 저장 (AC 검증용 · 파이프라인 unblock 용)
|
|
7
|
+
# 3. --claude-prompt 모드: Claude 세션이 실제 WebSearch + WebFetch + synthesis 로 채울 수 있도록 프롬프트 템플릿을 stdout 출력
|
|
8
|
+
# 4. --register 모드: Claude 가 실제 본문을 작성한 뒤 호출해 .generated.json 에 sources / generated_at 을 등록
|
|
9
|
+
#
|
|
10
|
+
# 사용 예:
|
|
11
|
+
# bash scripts/init-ref-docs.sh --dry-run .
|
|
12
|
+
# bash scripts/init-ref-docs.sh --yes --placeholder --stack swift --role fe .
|
|
13
|
+
# bash scripts/init-ref-docs.sh --claude-prompt --stack swift --role fe .
|
|
14
|
+
# bash scripts/init-ref-docs.sh --register --stack swift --role fe --sources "https://a,https://b" .
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
# ───── 옵션 파싱 ─────
|
|
18
|
+
MODE="interactive" # interactive | dry-run | placeholder | claude-prompt | register
|
|
19
|
+
YES=false
|
|
20
|
+
REFRESH=false
|
|
21
|
+
STACK=""
|
|
22
|
+
ROLE=""
|
|
23
|
+
SOURCES=""
|
|
24
|
+
PROJECT_ROOT="."
|
|
25
|
+
|
|
26
|
+
while [[ $# -gt 0 ]]; do
|
|
27
|
+
case "$1" in
|
|
28
|
+
--dry-run) MODE="dry-run"; shift ;;
|
|
29
|
+
--placeholder) MODE="placeholder"; shift ;;
|
|
30
|
+
--claude-prompt) MODE="claude-prompt"; shift ;;
|
|
31
|
+
--register) MODE="register"; shift ;;
|
|
32
|
+
--yes) YES=true; shift ;;
|
|
33
|
+
--refresh) REFRESH=true; shift ;;
|
|
34
|
+
--stack) STACK="$2"; shift 2 ;;
|
|
35
|
+
--role) ROLE="$2"; shift 2 ;;
|
|
36
|
+
--sources) SOURCES="$2"; shift 2 ;;
|
|
37
|
+
-h|--help)
|
|
38
|
+
sed -n '2,16p' "$0" | sed 's/^# //; s/^#//'
|
|
39
|
+
exit 0 ;;
|
|
40
|
+
*) PROJECT_ROOT="$1"; shift ;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
SCAN="${PROJECT_ROOT}/.harness/actions/scan-result.json"
|
|
45
|
+
REF_DIR="${PROJECT_ROOT}/.harness/ref"
|
|
46
|
+
META="${REF_DIR}/.generated.json"
|
|
47
|
+
ARCHIVE_DIR="${PROJECT_ROOT}/.harness/archive"
|
|
48
|
+
mkdir -p "$REF_DIR" "$ARCHIVE_DIR"
|
|
49
|
+
[ -f "$META" ] || echo "{}" > "$META"
|
|
50
|
+
|
|
51
|
+
# ───── 감지된 스택 목록 ─────
|
|
52
|
+
detect_stacks() {
|
|
53
|
+
if [ ! -f "$SCAN" ]; then
|
|
54
|
+
echo "ERROR: scan-result.json 이 없습니다. 먼저 bash scripts/scan-project.sh ${PROJECT_ROOT} 실행하세요." >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
local fe be native
|
|
58
|
+
fe=$(jq -r '.tech_stack.frontend' "$SCAN")
|
|
59
|
+
be=$(jq -r '.tech_stack.backend' "$SCAN")
|
|
60
|
+
native=$(jq -r '.tech_stack.is_native_app // false' "$SCAN")
|
|
61
|
+
|
|
62
|
+
# 네이티브 앱은 fe 만 기록 (be 는 null)
|
|
63
|
+
if [ "$fe" != "unknown" ] && [ "$fe" != "null" ]; then
|
|
64
|
+
echo "fe:$fe"
|
|
65
|
+
fi
|
|
66
|
+
if [ "$native" != "true" ] && [ "$be" != "unknown" ] && [ "$be" != "null" ]; then
|
|
67
|
+
echo "be:$be"
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# ───── placeholder 본문 ─────
|
|
72
|
+
write_placeholder() {
|
|
73
|
+
local role="$1" stack="$2" path="$3"
|
|
74
|
+
local ts
|
|
75
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
76
|
+
cat > "$path" <<REF
|
|
77
|
+
---
|
|
78
|
+
docmeta:
|
|
79
|
+
id: ref-${role}-${stack}
|
|
80
|
+
stack: ${stack}
|
|
81
|
+
role: ${role}
|
|
82
|
+
language: ${stack}
|
|
83
|
+
generated_at: ${ts}
|
|
84
|
+
generator: init-ref-docs.sh (placeholder)
|
|
85
|
+
sources: []
|
|
86
|
+
version: 0
|
|
87
|
+
runner:
|
|
88
|
+
dev_command: null
|
|
89
|
+
start_command: null
|
|
90
|
+
install_command: null
|
|
91
|
+
paths:
|
|
92
|
+
source_roots: []
|
|
93
|
+
test_roots: []
|
|
94
|
+
config_files: []
|
|
95
|
+
api:
|
|
96
|
+
base_url: null
|
|
97
|
+
gateway: null
|
|
98
|
+
validation:
|
|
99
|
+
pre_eval_gate: []
|
|
100
|
+
functional_tests: []
|
|
101
|
+
visual:
|
|
102
|
+
enabled: false
|
|
103
|
+
reason: "placeholder — not yet filled by Claude"
|
|
104
|
+
manual_check: "bash init.sh refresh-ref ${role} ${stack} 실행 후 Claude 세션에서 WebSearch + WebFetch 로 채우기"
|
|
105
|
+
anti_pattern_rules: []
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
# Ref — ${stack} (${role})
|
|
109
|
+
|
|
110
|
+
> **Status**: PLACEHOLDER — Claude 가 WebSearch + WebFetch 로 채워야 함.
|
|
111
|
+
> 실행: bash scripts/init-ref-docs.sh --claude-prompt --stack ${stack} --role ${role}
|
|
112
|
+
|
|
113
|
+
## 1. Runner
|
|
114
|
+
TODO
|
|
115
|
+
|
|
116
|
+
## 2. Paths / Source Layout
|
|
117
|
+
TODO
|
|
118
|
+
|
|
119
|
+
## 3. Best Practices
|
|
120
|
+
TODO
|
|
121
|
+
|
|
122
|
+
## 4. Anti-Patterns (→ gotchas 시드 후보)
|
|
123
|
+
TODO
|
|
124
|
+
REF
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# ───── Claude 용 프롬프트 ─────
|
|
128
|
+
# React 계열 스택 (react / nextjs / vite-react) 일 때는
|
|
129
|
+
# skills/generator-frontend/references/_web-react-legacy/ 의 4개 문서를
|
|
130
|
+
# 로컬 seed 로 프롬프트에 포함시킨다. 이 seed 는 새 ref-docs 작성 시
|
|
131
|
+
# "기존 웹 React 가이드" 참조용이며 WebSearch 결과와 병합된다.
|
|
132
|
+
LEGACY_SEED_DIR="$(dirname "$0")/../skills/generator-frontend/references/_web-react-legacy"
|
|
133
|
+
emit_claude_prompt() {
|
|
134
|
+
local role="$1" stack="$2" path="$3"
|
|
135
|
+
local legacy_hint=""
|
|
136
|
+
if [ "$role" = "fe" ] && [ -d "$LEGACY_SEED_DIR" ]; then
|
|
137
|
+
case "$stack" in
|
|
138
|
+
react|nextjs|vite-react|nuxt|vue|svelte|angular|*web*)
|
|
139
|
+
legacy_hint="
|
|
140
|
+
Local seed (병합 대상 · 이미 검증된 웹/React best practice):
|
|
141
|
+
$(ls "$LEGACY_SEED_DIR" 2>/dev/null | sed 's|^| - '"$LEGACY_SEED_DIR"'/|')
|
|
142
|
+
"
|
|
143
|
+
;;
|
|
144
|
+
esac
|
|
145
|
+
fi
|
|
146
|
+
cat <<PROMPT
|
|
147
|
+
───────────────────────────────────────────────
|
|
148
|
+
Claude 세션에 아래 프롬프트를 전달하세요
|
|
149
|
+
(또는 현재 세션이라면 그대로 실행):
|
|
150
|
+
───────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
목표: ${path} 를 생성한다.
|
|
153
|
+
|
|
154
|
+
1. WebSearch 로 다음 쿼리 실행: "${stack} best practices 2025 site:docs.*"
|
|
155
|
+
2. 상위 3개 공식 문서 URL 을 WebFetch 로 가져온다.
|
|
156
|
+
3. 결과를 종합해 다음 스키마로 YAML frontmatter + 본문을 작성한다:
|
|
157
|
+
- docmeta: { id, stack, role, language, generated_at, generator, sources, version }
|
|
158
|
+
- runner: { dev_command, start_command, install_command }
|
|
159
|
+
- paths: { source_roots, test_roots, config_files }
|
|
160
|
+
- api: { base_url, gateway }
|
|
161
|
+
- validation:
|
|
162
|
+
pre_eval_gate: [...]
|
|
163
|
+
functional_tests: [...]
|
|
164
|
+
visual: { enabled, reason, manual_check }
|
|
165
|
+
anti_pattern_rules: [{ id, pattern_type: "grep"|"lint_tool", pattern|tool+args, paths, severity }]
|
|
166
|
+
4. 본문에는 Runner / Paths / Best Practices / Anti-Patterns 섹션 작성.
|
|
167
|
+
5. 완료 후: bash scripts/init-ref-docs.sh --register --stack ${stack} --role ${role} --sources "<url1>,<url2>,<url3>" ${PROJECT_ROOT}
|
|
168
|
+
${legacy_hint}
|
|
169
|
+
───────────────────────────────────────────────
|
|
170
|
+
PROMPT
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# ───── register: 메타 기록 ─────
|
|
174
|
+
register_meta() {
|
|
175
|
+
local role="$1" stack="$2" sources_csv="$3"
|
|
176
|
+
local ts key sources_json
|
|
177
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
178
|
+
key="${role}-${stack}"
|
|
179
|
+
sources_json=$(echo "$sources_csv" | awk -F',' 'BEGIN{printf "["} {for(i=1;i<=NF;i++){printf (i>1?",":"") "\"" $i "\""}} END{printf "]"}')
|
|
180
|
+
[ -z "$sources_csv" ] && sources_json="[]"
|
|
181
|
+
jq --arg k "$key" --arg ts "$ts" --argjson s "$sources_json" '.[$k] = {"generated_at": $ts, "sources": $s, "status": "filled"}' "$META" > "${META}.tmp" && mv "${META}.tmp" "$META"
|
|
182
|
+
echo "registered: $key (sources=$(echo "$sources_json" | jq 'length'))"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# ───── 한 스택 처리 ─────
|
|
186
|
+
process_one() {
|
|
187
|
+
local role="$1" stack="$2"
|
|
188
|
+
local path="${REF_DIR}/${role}-${stack}.md"
|
|
189
|
+
|
|
190
|
+
if [ -f "$path" ] && ! $REFRESH; then
|
|
191
|
+
echo "skip: $path 이미 존재 (--refresh 로 강제 재생성)"
|
|
192
|
+
return 0
|
|
193
|
+
fi
|
|
194
|
+
if [ -f "$path" ] && $REFRESH; then
|
|
195
|
+
local backup="${ARCHIVE_DIR}/ref-${role}-${stack}-$(date +%Y%m%d%H%M%S).md"
|
|
196
|
+
cp "$path" "$backup"
|
|
197
|
+
echo "archived: $backup"
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
local answer="y"
|
|
201
|
+
if ! $YES; then
|
|
202
|
+
printf "Generate ref-docs for %s-%s? [y/N] " "$role" "$stack"
|
|
203
|
+
read -r answer </dev/tty || answer="n"
|
|
204
|
+
fi
|
|
205
|
+
case "$answer" in
|
|
206
|
+
y|Y|yes|YES) ;;
|
|
207
|
+
*) echo "skipped: $role-$stack"; return 3 ;;
|
|
208
|
+
esac
|
|
209
|
+
|
|
210
|
+
case "$MODE" in
|
|
211
|
+
placeholder)
|
|
212
|
+
write_placeholder "$role" "$stack" "$path"
|
|
213
|
+
local ts; ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
214
|
+
jq --arg k "${role}-${stack}" --arg ts "$ts" '.[$k] = {"generated_at": $ts, "sources": [], "status": "placeholder"}' "$META" > "${META}.tmp" && mv "${META}.tmp" "$META"
|
|
215
|
+
echo "wrote: $path (placeholder)"
|
|
216
|
+
;;
|
|
217
|
+
claude-prompt)
|
|
218
|
+
emit_claude_prompt "$role" "$stack" "$path"
|
|
219
|
+
;;
|
|
220
|
+
*)
|
|
221
|
+
echo "ERROR: interactive 모드는 --placeholder 또는 --claude-prompt 중 하나를 지정하세요." >&2
|
|
222
|
+
return 1
|
|
223
|
+
;;
|
|
224
|
+
esac
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# ───── main ─────
|
|
228
|
+
case "$MODE" in
|
|
229
|
+
dry-run)
|
|
230
|
+
echo "detected stacks:"
|
|
231
|
+
detect_stacks | sed 's/^/ - /'
|
|
232
|
+
echo ""
|
|
233
|
+
echo "ref files 예상 경로 (미존재 시 생성 대상):"
|
|
234
|
+
while IFS=':' read -r role stack; do
|
|
235
|
+
path="${REF_DIR}/${role}-${stack}.md"
|
|
236
|
+
if [ -f "$path" ]; then
|
|
237
|
+
echo " [exists] $path"
|
|
238
|
+
else
|
|
239
|
+
echo " [create] $path"
|
|
240
|
+
fi
|
|
241
|
+
done < <(detect_stacks)
|
|
242
|
+
;;
|
|
243
|
+
register)
|
|
244
|
+
[ -z "$STACK" ] || [ -z "$ROLE" ] && { echo "ERROR: --register 는 --stack --role --sources 필요" >&2; exit 1; }
|
|
245
|
+
register_meta "$ROLE" "$STACK" "$SOURCES"
|
|
246
|
+
;;
|
|
247
|
+
placeholder|claude-prompt)
|
|
248
|
+
if [ -n "$STACK" ] && [ -n "$ROLE" ]; then
|
|
249
|
+
process_one "$ROLE" "$STACK"
|
|
250
|
+
else
|
|
251
|
+
# 감지된 전체 스택을 순회
|
|
252
|
+
while IFS=':' read -r role stack; do
|
|
253
|
+
process_one "$role" "$stack"
|
|
254
|
+
done < <(detect_stacks)
|
|
255
|
+
fi
|
|
256
|
+
;;
|
|
257
|
+
interactive)
|
|
258
|
+
echo "ERROR: 모드 지정 필요 — --dry-run | --placeholder | --claude-prompt | --register" >&2
|
|
259
|
+
echo "자세한 사용법: bash $0 --help" >&2
|
|
260
|
+
exit 1
|
|
261
|
+
;;
|
|
262
|
+
esac
|