@walwal-harness/cli 6.0.4 → 6.0.5

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.
@@ -1,367 +0,0 @@
1
- #!/bin/bash
2
- # scan-project.sh — 기존 프로젝트 구조를 분석하여 하네스 초기화 정보를 생성
3
- # 출력: .harness/actions/scan-result.json
4
- set -e
5
-
6
- PROJECT_ROOT="${1:-.}"
7
- OUTPUT="${PROJECT_ROOT}/.harness/actions/scan-result.json"
8
-
9
- mkdir -p "$(dirname "$OUTPUT")"
10
-
11
- echo "=== Scanning project: ${PROJECT_ROOT} ==="
12
-
13
- # ─────────────────────────────────────────
14
- # 1. 기존 AGENTS.md / CLAUDE.md 감지
15
- # ─────────────────────────────────────────
16
- AGENTS_MD="none"
17
- CLAUDE_MD="none"
18
- CLAUDE_MD_IS_SYMLINK=false
19
- EXISTING_RULES=""
20
-
21
- if [ -f "${PROJECT_ROOT}/AGENTS.md" ]; then
22
- AGENTS_MD="exists"
23
- fi
24
-
25
- if [ -L "${PROJECT_ROOT}/CLAUDE.md" ]; then
26
- CLAUDE_MD="symlink"
27
- CLAUDE_MD_IS_SYMLINK=true
28
- elif [ -f "${PROJECT_ROOT}/CLAUDE.md" ]; then
29
- CLAUDE_MD="exists"
30
- # 기존 CLAUDE.md 내용 보존용 추출
31
- EXISTING_RULES=$(cat "${PROJECT_ROOT}/CLAUDE.md")
32
- fi
33
-
34
- # ─────────────────────────────────────────
35
- # 2. Tech Stack 감지
36
- # ─────────────────────────────────────────
37
- TECH_BACKEND="unknown"
38
- TECH_FRONTEND="unknown"
39
- TECH_DB="unknown"
40
- TECH_MONOREPO="none"
41
- TECH_LANG="unknown"
42
- IS_NATIVE_APP=false
43
-
44
- # Backend
45
- if [ -f "${PROJECT_ROOT}/nest-cli.json" ]; then
46
- TECH_BACKEND="nestjs"
47
- TECH_LANG="typescript"
48
- elif [ -f "${PROJECT_ROOT}/requirements.txt" ] || [ -f "${PROJECT_ROOT}/pyproject.toml" ]; then
49
- TECH_BACKEND="python"
50
- TECH_LANG="python"
51
- if grep -q "fastapi" "${PROJECT_ROOT}/requirements.txt" 2>/dev/null || grep -q "fastapi" "${PROJECT_ROOT}/pyproject.toml" 2>/dev/null; then
52
- TECH_BACKEND="fastapi"
53
- elif grep -q "django" "${PROJECT_ROOT}/requirements.txt" 2>/dev/null || grep -q "django" "${PROJECT_ROOT}/pyproject.toml" 2>/dev/null; then
54
- TECH_BACKEND="django"
55
- fi
56
- elif [ -f "${PROJECT_ROOT}/go.mod" ]; then
57
- TECH_BACKEND="go"
58
- TECH_LANG="go"
59
- elif [ -f "${PROJECT_ROOT}/pom.xml" ] || [ -f "${PROJECT_ROOT}/build.gradle" ]; then
60
- TECH_BACKEND="java"
61
- TECH_LANG="java"
62
- fi
63
-
64
- # Frontend
65
- # Flutter 우선 감지 — pubspec.yaml 존재하면 Flutter 프로젝트로 판정 (TECH_LANG도 보정)
66
- FE_STACK="react" # react | flutter (기본값은 react 계열)
67
- FE_TARGET="web" # web | mobile | desktop (Flutter 의 컴파일 타겟; React 는 항상 web)
68
- FLUTTER_ROOT="" # Flutter 프로젝트 루트 경로 (web/mobile/desktop 감지용)
69
-
70
- if [ -f "${PROJECT_ROOT}/pubspec.yaml" ] && grep -q "flutter:" "${PROJECT_ROOT}/pubspec.yaml" 2>/dev/null; then
71
- TECH_FRONTEND="flutter"
72
- TECH_LANG="dart"
73
- FE_STACK="flutter"
74
- FLUTTER_ROOT="${PROJECT_ROOT}"
75
- elif [ -f "${PROJECT_ROOT}/next.config.js" ] || [ -f "${PROJECT_ROOT}/next.config.ts" ] || [ -f "${PROJECT_ROOT}/next.config.mjs" ]; then
76
- TECH_FRONTEND="nextjs"
77
- elif [ -f "${PROJECT_ROOT}/vite.config.ts" ] || [ -f "${PROJECT_ROOT}/vite.config.js" ]; then
78
- TECH_FRONTEND="vite-react"
79
- elif [ -f "${PROJECT_ROOT}/angular.json" ]; then
80
- TECH_FRONTEND="angular"
81
- elif [ -f "${PROJECT_ROOT}/nuxt.config.ts" ]; then
82
- TECH_FRONTEND="nuxt"
83
- fi
84
-
85
- # Monorepo 내 Frontend 감지
86
- if [ -d "${PROJECT_ROOT}/apps/web" ]; then
87
- if [ -f "${PROJECT_ROOT}/apps/web/next.config.js" ] || [ -f "${PROJECT_ROOT}/apps/web/next.config.ts" ]; then
88
- TECH_FRONTEND="nextjs"
89
- FE_STACK="react"
90
- elif [ -f "${PROJECT_ROOT}/apps/web/vite.config.ts" ]; then
91
- TECH_FRONTEND="vite-react"
92
- FE_STACK="react"
93
- fi
94
- fi
95
-
96
- # Flutter 서브디렉토리 감지 (monorepo 또는 서브 프로젝트 케이스)
97
- if [ "$TECH_FRONTEND" = "unknown" ]; then
98
- # 대표적인 Flutter 서브폴더 이름을 얕게 탐색
99
- for d in apps/mobile apps/web mobile clue_mobile_app flutter_app; do
100
- if [ -f "${PROJECT_ROOT}/${d}/pubspec.yaml" ] && grep -q "flutter:" "${PROJECT_ROOT}/${d}/pubspec.yaml" 2>/dev/null; then
101
- TECH_FRONTEND="flutter"
102
- TECH_LANG="dart"
103
- FE_STACK="flutter"
104
- FLUTTER_ROOT="${PROJECT_ROOT}/${d}"
105
- break
106
- fi
107
- done
108
- fi
109
-
110
- # Flutter fe_target 감지 (web / mobile / desktop)
111
- # - web/index.html 존재 → web
112
- # - android/ 또는 ios/ 존재 + web/ 없음 → mobile
113
- # - macos/ 또는 windows/ 또는 linux/ 존재 + 위 둘 없음 → desktop
114
- # - 동시 존재 (멀티 타겟) → web 우선 (사용자가 Planner에서 변경 가능)
115
- if [ "$FE_STACK" = "flutter" ] && [ -n "$FLUTTER_ROOT" ]; then
116
- HAS_WEB=false
117
- HAS_MOBILE=false
118
- HAS_DESKTOP=false
119
- [ -f "${FLUTTER_ROOT}/web/index.html" ] && HAS_WEB=true
120
- { [ -d "${FLUTTER_ROOT}/android" ] || [ -d "${FLUTTER_ROOT}/ios" ]; } && HAS_MOBILE=true
121
- { [ -d "${FLUTTER_ROOT}/macos" ] || [ -d "${FLUTTER_ROOT}/windows" ] || [ -d "${FLUTTER_ROOT}/linux" ]; } && HAS_DESKTOP=true
122
-
123
- if [ "$HAS_WEB" = "true" ]; then
124
- FE_TARGET="web"
125
- elif [ "$HAS_MOBILE" = "true" ]; then
126
- FE_TARGET="mobile"
127
- elif [ "$HAS_DESKTOP" = "true" ]; then
128
- FE_TARGET="desktop"
129
- else
130
- FE_TARGET="unknown"
131
- fi
132
- fi
133
-
134
- # Swift (macOS / iOS 네이티브 앱) 감지 — Flutter 감지 이후
135
- if [ "$TECH_FRONTEND" = "unknown" ]; then
136
- SWIFT_DETECTED=false
137
- if [ -f "${PROJECT_ROOT}/Package.swift" ]; then
138
- SWIFT_DETECTED=true
139
- fi
140
- if ! $SWIFT_DETECTED; then
141
- for f in "${PROJECT_ROOT}"/*.xcodeproj "${PROJECT_ROOT}"/*.xcworkspace; do
142
- if [ -e "$f" ]; then
143
- SWIFT_DETECTED=true
144
- break
145
- fi
146
- done
147
- fi
148
- if ! $SWIFT_DETECTED && [ -f "${PROJECT_ROOT}/Podfile" ]; then
149
- SWIFT_DETECTED=true
150
- fi
151
-
152
- if $SWIFT_DETECTED; then
153
- TECH_LANG="swift"
154
- IS_NATIVE_APP=true
155
- FE_STACK="swift" # FE_STACK 기본값(react) 을 Swift 로 치환
156
- FE_TARGET="native" # web/mobile/desktop 대신 native
157
- # 서브타입 판별 — NSStatusBar 가 가장 특화적이므로 우선
158
- if grep -rq "NSStatusBar.system" "${PROJECT_ROOT}" --include="*.swift" 2>/dev/null; then
159
- TECH_FRONTEND="swift-macos-statusbar"
160
- elif grep -rq "import SwiftUI" "${PROJECT_ROOT}" --include="*.swift" 2>/dev/null; then
161
- TECH_FRONTEND="swift-swiftui"
162
- elif grep -rq "import UIKit" "${PROJECT_ROOT}" --include="*.swift" 2>/dev/null; then
163
- TECH_FRONTEND="swift-uikit"
164
- else
165
- TECH_FRONTEND="swift"
166
- fi
167
- fi
168
- fi
169
-
170
- # Database
171
- if grep -rq "typeorm\|prisma\|sequelize\|knex" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
172
- if grep -q "pg\|postgres" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
173
- TECH_DB="postgresql"
174
- elif grep -q "mysql" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
175
- TECH_DB="mysql"
176
- elif grep -q "sqlite" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
177
- TECH_DB="sqlite"
178
- else
179
- TECH_DB="orm-detected"
180
- fi
181
- elif grep -rq "mongoose\|mongodb" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
182
- TECH_DB="mongodb"
183
- fi
184
-
185
- # Monorepo
186
- if [ -f "${PROJECT_ROOT}/turbo.json" ]; then
187
- TECH_MONOREPO="turborepo"
188
- elif [ -f "${PROJECT_ROOT}/nx.json" ]; then
189
- TECH_MONOREPO="nx"
190
- elif [ -f "${PROJECT_ROOT}/lerna.json" ]; then
191
- TECH_MONOREPO="lerna"
192
- elif [ -f "${PROJECT_ROOT}/pnpm-workspace.yaml" ]; then
193
- TECH_MONOREPO="pnpm-workspace"
194
- elif [ -f "${PROJECT_ROOT}/nest-cli.json" ] && grep -q '"monorepo": true' "${PROJECT_ROOT}/nest-cli.json" 2>/dev/null; then
195
- TECH_MONOREPO="nestjs-monorepo"
196
- fi
197
-
198
- # ─────────────────────────────────────────
199
- # 3. 디렉토리 트리 스캔 (2 depth)
200
- # ─────────────────────────────────────────
201
- TREE=$(find "${PROJECT_ROOT}" -maxdepth 3 -type d \
202
- ! -path "*/node_modules/*" \
203
- ! -path "*/.git/*" \
204
- ! -path "*/.next/*" \
205
- ! -path "*/dist/*" \
206
- ! -path "*/__pycache__/*" \
207
- ! -path "*/.harness/archive/*" \
208
- ! -name "node_modules" \
209
- ! -name ".git" \
210
- ! -name ".next" \
211
- ! -name "dist" \
212
- 2>/dev/null | sort | sed "s|${PROJECT_ROOT}/||g" | sed '/^$/d')
213
-
214
- # ─────────────────────────────────────────
215
- # 4. 주요 설정 파일 목록
216
- # ─────────────────────────────────────────
217
- CONFIG_FILES=""
218
- for f in package.json tsconfig.json nest-cli.json next.config.js next.config.ts \
219
- vite.config.ts docker-compose.yml docker-compose.yaml Dockerfile \
220
- .env .env.example turbo.json nx.json pyproject.toml requirements.txt \
221
- go.mod pom.xml build.gradle Makefile; do
222
- if [ -f "${PROJECT_ROOT}/${f}" ]; then
223
- CONFIG_FILES="${CONFIG_FILES}\"${f}\","
224
- fi
225
- done
226
- CONFIG_FILES="[${CONFIG_FILES%,}]"
227
-
228
- # ─────────────────────────────────────────
229
- # 5. OpenAPI / Swagger 감지
230
- # ─────────────────────────────────────────
231
- OPENAPI="none"
232
- for f in openapi.json openapi.yaml swagger.json swagger.yaml api-spec.json api-spec.yaml; do
233
- if [ -f "${PROJECT_ROOT}/${f}" ] || [ -f "${PROJECT_ROOT}/docs/${f}" ]; then
234
- OPENAPI="${f}"
235
- break
236
- fi
237
- done
238
-
239
- # NestJS Swagger 모듈 감지
240
- if grep -rq "@nestjs/swagger" "${PROJECT_ROOT}/package.json" 2>/dev/null; then
241
- if [ "$OPENAPI" = "none" ]; then
242
- OPENAPI="nestjs-swagger-module (runtime-generated)"
243
- fi
244
- fi
245
-
246
- # ─────────────────────────────────────────
247
- # 6. Git 정보
248
- # ─────────────────────────────────────────
249
- GIT_INIT=false
250
- GIT_COMMITS=0
251
- GIT_BRANCH="none"
252
- if [ -d "${PROJECT_ROOT}/.git" ]; then
253
- GIT_INIT=true
254
- GIT_COMMITS=$(git -C "${PROJECT_ROOT}" rev-list --count HEAD 2>/dev/null || echo 0)
255
- GIT_BRANCH=$(git -C "${PROJECT_ROOT}" branch --show-current 2>/dev/null || echo "unknown")
256
- fi
257
-
258
- # ─────────────────────────────────────────
259
- # 7. 하네스 기존 상태
260
- # ─────────────────────────────────────────
261
- HARNESS_EXISTS=false
262
- HARNESS_VERSION="none"
263
- if [ -f "${PROJECT_ROOT}/.harness/config.json" ]; then
264
- HARNESS_EXISTS=true
265
- HARNESS_VERSION=$(grep '"version"' "${PROJECT_ROOT}/.harness/config.json" 2>/dev/null | head -1 | sed 's/.*: *"\(.*\)".*/\1/')
266
- fi
267
-
268
- # ─────────────────────────────────────────
269
- # 8. JSON 출력
270
- # ─────────────────────────────────────────
271
-
272
- # 디렉토리 트리를 JSON 배열로 변환
273
- TREE_JSON=$(echo "$TREE" | awk 'BEGIN{printf "["} NR>1{printf ","} {printf "\"%s\"", $0} END{printf "]"}')
274
-
275
- # 기존 CLAUDE.md 내용 (JSON escape)
276
- EXISTING_RULES_JSON=$(echo "$EXISTING_RULES" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null || echo '""')
277
-
278
- cat > "$OUTPUT" << JSONEOF
279
- {
280
- "scanned_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
281
- "project_root": "${PROJECT_ROOT}",
282
-
283
- "existing_docs": {
284
- "agents_md": "${AGENTS_MD}",
285
- "claude_md": "${CLAUDE_MD}",
286
- "claude_md_is_symlink": ${CLAUDE_MD_IS_SYMLINK},
287
- "existing_claude_md_content": ${EXISTING_RULES_JSON}
288
- },
289
-
290
- "tech_stack": {
291
- "backend": "${TECH_BACKEND}",
292
- "frontend": "${TECH_FRONTEND}",
293
- "fe_stack": "${FE_STACK}",
294
- "fe_target": "${FE_TARGET}",
295
- "database": "${TECH_DB}",
296
- "monorepo": "${TECH_MONOREPO}",
297
- "language": "${TECH_LANG}",
298
- "is_native_app": ${IS_NATIVE_APP}
299
- },
300
-
301
- "tech_stack_confidence": "$(
302
- if [ "$TECH_BACKEND" = "unknown" ] && [ "$TECH_FRONTEND" = "unknown" ]; then
303
- echo "unknown"
304
- else
305
- echo "detected"
306
- fi
307
- )",
308
-
309
- "structure": {
310
- "directories": ${TREE_JSON},
311
- "config_files": ${CONFIG_FILES}
312
- },
313
-
314
- "api": {
315
- "openapi_spec": "${OPENAPI}"
316
- },
317
-
318
- "git": {
319
- "initialized": ${GIT_INIT},
320
- "commits": ${GIT_COMMITS},
321
- "branch": "${GIT_BRANCH}"
322
- },
323
-
324
- "harness": {
325
- "exists": ${HARNESS_EXISTS},
326
- "version": "${HARNESS_VERSION}"
327
- },
328
-
329
- "recommendation": {
330
- "project_type": "$(
331
- if [ "$TECH_BACKEND" != "unknown" ] && [ "$TECH_FRONTEND" != "unknown" ]; then
332
- echo "fullstack"
333
- elif [ "$TECH_BACKEND" != "unknown" ]; then
334
- echo "backend-only"
335
- elif [ "$TECH_FRONTEND" != "unknown" ]; then
336
- echo "frontend-only"
337
- else
338
- echo "empty"
339
- fi
340
- )",
341
- "agents_md_action": "$(
342
- if [ "$AGENTS_MD" = "none" ] && [ "$CLAUDE_MD" = "none" ]; then
343
- echo "create-new"
344
- elif [ "$AGENTS_MD" = "none" ] && [ "$CLAUDE_MD" = "exists" ]; then
345
- echo "migrate-claude-to-agents"
346
- elif [ "$AGENTS_MD" = "exists" ]; then
347
- echo "rebuild-with-harness"
348
- else
349
- echo "create-new"
350
- fi
351
- )"
352
- }
353
- }
354
- JSONEOF
355
-
356
- echo ""
357
- echo "=== Scan Complete ==="
358
- echo "Output: ${OUTPUT}"
359
- echo ""
360
- echo "--- Summary ---"
361
- echo "Tech Stack: ${TECH_BACKEND} / ${TECH_FRONTEND} (fe_stack=${FE_STACK}, fe_target=${FE_TARGET}, native=${IS_NATIVE_APP}) / ${TECH_DB}"
362
- echo "Monorepo: ${TECH_MONOREPO}"
363
- echo "OpenAPI: ${OPENAPI}"
364
- echo "Git: ${GIT_INIT} (${GIT_COMMITS} commits, branch: ${GIT_BRANCH})"
365
- echo "Existing Docs: AGENTS.md=${AGENTS_MD}, CLAUDE.md=${CLAUDE_MD}"
366
- echo "Harness: ${HARNESS_EXISTS} (v${HARNESS_VERSION})"
367
- echo "Recommendation: $(cat "$OUTPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f\"type={d['recommendation']['project_type']}, agents_md={d['recommendation']['agents_md_action']}\")" 2>/dev/null)"