jinhak-ai-standard 2.6.0 → 2.7.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.
- package/CHANGELOG.md +27 -1
- package/CLAUDE.md +3 -3
- package/PROMPT_LIBRARY_USAGE.md +1 -1
- package/QUICK_START_PROMPT.md +1 -1
- package/README.md +22 -26
- package/bin/cli.cjs +181 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ Claude Code의 `/session-start` 스킬이 이 파일을 참조하여 표준 업
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [2.7] - 2026-03-04
|
|
9
|
+
|
|
10
|
+
### CLI 기술 스택 자동 감지 + CLAUDE.md 완전 자동 생성
|
|
11
|
+
|
|
12
|
+
`npx jinhak-ai-standard` 실행 시 프로젝트의 기술 스택을 자동 감지하여 CLAUDE.md와 .ai/ 폴더를 의미있는 내용으로 생성합니다.
|
|
13
|
+
|
|
14
|
+
### 추가
|
|
15
|
+
- `detectTechStack()` — package.json에서 프레임워크, 언어, 패키지매니저, 상태관리, 스타일링, DB, ORM, 빌드도구, 테스트도구 자동 감지
|
|
16
|
+
- `applyStackToTemplate()` — templates/project-claude.md 템플릿의 `[대괄호]` 플레이스홀더를 감지된 기술 스택으로 자동 치환
|
|
17
|
+
- `generateFolderTree()` — 프로젝트 1-depth 폴더 트리 자동 생성
|
|
18
|
+
- `generateAiContent()` — .ai/ 폴더 파일에 감지된 기술 스택 기반 의미있는 초기 내용 생성 (ADR-001 포함)
|
|
19
|
+
|
|
20
|
+
### 변경
|
|
21
|
+
- `bin/cli.cjs`: apply() 흐름에 기술 스택 감지 → 템플릿 치환 → 의미있는 .ai/ 생성 단계 추가
|
|
22
|
+
- `package.json`: 버전 2.6.0 → 2.7.0
|
|
23
|
+
- `CLAUDE.md`: 버전 2.6 → 2.7
|
|
24
|
+
|
|
25
|
+
### Migration Guide (v2.6 → v2.7)
|
|
26
|
+
|
|
27
|
+
기존 v2.6 프로젝트에서 업데이트 시:
|
|
28
|
+
1. `npx jinhak-ai-standard` 재실행 — 기존 CLAUDE.md가 있으면 덮어쓰지 않고 버전만 안내
|
|
29
|
+
2. 새 프로젝트에서는 자동으로 기술 스택이 감지되어 CLAUDE.md에 반영됨
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
8
33
|
## [2.6] - 2026-03-02
|
|
9
34
|
|
|
10
35
|
### npm 패키지 + GitHub Releases 배포 시스템
|
|
@@ -17,9 +42,10 @@ Claude Code의 `/session-start` 스킬이 이 파일을 참조하여 표준 업
|
|
|
17
42
|
- `.github/workflows/release.yml` — 태그 push 시 GitHub Release + npm publish 자동화
|
|
18
43
|
|
|
19
44
|
### 변경
|
|
45
|
+
- `bin/cli.cjs`: CLAUDE.md 자동 생성(신규) 및 버전 메타데이터 업데이트(기존), prompts/ 디렉토리 복사 추가
|
|
20
46
|
- `.claude/skills/apply-standard/SKILL.md`: 0단계에 npm 패키지 → Release tarball → git clone 3단계 폴백 추가
|
|
21
47
|
- `QUICK_START_PROMPT.md`: 방법 1(npx), 방법 2(프롬프트), 방법 3(Release tarball) 구분
|
|
22
|
-
- `README.md`: v2.5 → v2.6, 적용 방법 1~4로 재정리, 문서 구조에 새 파일
|
|
48
|
+
- `README.md`: v2.5 → v2.6, 적용 방법 1~4로 재정리, 문서 구조에 새 파일 추가, npx 설명에 CLAUDE.md 자동 생성 반영
|
|
23
49
|
- `CLAUDE.md`: 버전 2.5 → 2.6, 프로젝트 구조에 package.json/bin/cli.cjs 추가
|
|
24
50
|
|
|
25
51
|
### Migration Guide (v2.5 → v2.6)
|
package/CLAUDE.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<!-- JINHAK Standard Metadata - 이 메타 정보는 자동 버전 관리에 사용됩니다. 삭제하지 마세요. -->
|
|
2
|
-
<!-- jinhak_standard_version: 2.
|
|
2
|
+
<!-- jinhak_standard_version: 2.7 -->
|
|
3
3
|
<!-- jinhak_standard_repo: https://github.com/JinhakStandard/ai-vibecoding -->
|
|
4
|
-
<!-- applied_date: 2026-03-
|
|
4
|
+
<!-- applied_date: 2026-03-04 -->
|
|
5
5
|
|
|
6
|
-
# JINHAK 전사 AI 개발 표준 v2.
|
|
6
|
+
# JINHAK 전사 AI 개발 표준 v2.7
|
|
7
7
|
|
|
8
8
|
이 문서는 JINHAK의 모든 프로젝트에서 AI(Claude Code / Claude.ai)와 협업할 때 따라야 하는 전사 표준입니다.
|
|
9
9
|
|
package/PROMPT_LIBRARY_USAGE.md
CHANGED
package/QUICK_START_PROMPT.md
CHANGED
|
@@ -99,7 +99,7 @@ CLAUDE.local.md
|
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
101
|
mkdir -p /tmp/jinhak-standards
|
|
102
|
-
curl -sL https://github.com/JinhakStandard/ai-vibecoding/archive/refs/tags/v2.
|
|
102
|
+
curl -sL https://github.com/JinhakStandard/ai-vibecoding/archive/refs/tags/v2.6.tar.gz \
|
|
103
103
|
| tar -xz -C /tmp/jinhak-standards --strip-components=1
|
|
104
104
|
```
|
|
105
105
|
|
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ cd my-project
|
|
|
13
13
|
npx jinhak-ai-standard
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
`.claude/`, `.ai/`, `security/` 등 표준 파일이 자동으로
|
|
17
|
-
|
|
16
|
+
`CLAUDE.md`, `.claude/`, `.ai/`, `security/`, `prompts/` 등 표준 파일이 자동으로 생성됩니다.
|
|
17
|
+
`CLAUDE.md`의 `[대괄호]` 내용을 프로젝트 정보로 수정한 뒤 Claude Code에서 `/session-start`로 시작하세요.
|
|
18
18
|
|
|
19
19
|
### 방법 2: Claude Code 프롬프트 (복사-붙여넣기)
|
|
20
20
|
|
|
@@ -105,9 +105,9 @@ claude
|
|
|
105
105
|
|
|
106
106
|
> 각 프로젝트의 CLAUDE.md에 다음 메타 정보가 기록되어 추적됩니다:
|
|
107
107
|
> ```html
|
|
108
|
-
> <!-- jinhak_standard_version: 2.
|
|
108
|
+
> <!-- jinhak_standard_version: 2.6 -->
|
|
109
109
|
> <!-- jinhak_standard_repo: https://github.com/JinhakStandard/ai-vibecoding -->
|
|
110
|
-
> <!-- applied_date: 2026-02
|
|
110
|
+
> <!-- applied_date: 2026-03-02 -->
|
|
111
111
|
> ```
|
|
112
112
|
|
|
113
113
|
---
|
|
@@ -138,9 +138,9 @@ JinhakStandard/
|
|
|
138
138
|
├── VIBE_CODING_GUIDE.md # 바이브 코딩 방법론 (비개발자 포함)
|
|
139
139
|
├── PROJECT_STRUCTURE.md # 표준 프로젝트 구조
|
|
140
140
|
├── SECURITY_ISMS.md # ISMS 보안 가이드
|
|
141
|
-
├── PROMPT-LIBRARY.md # 프롬프트 라이브러리 시스템 가이드
|
|
142
|
-
├── PROMPT_LIBRARY_USAGE.md # 프롬프트 라이브러리 사용법
|
|
143
|
-
├── prompts/ # 프롬프트 라이브러리
|
|
141
|
+
├── PROMPT-LIBRARY.md # 프롬프트 라이브러리 시스템 가이드
|
|
142
|
+
├── PROMPT_LIBRARY_USAGE.md # 프롬프트 라이브러리 사용법
|
|
143
|
+
├── prompts/ # 프롬프트 라이브러리
|
|
144
144
|
│ ├── _template/ # 새 프롬프트 작성용 템플릿
|
|
145
145
|
│ ├── code-gen/ # 코드 생성 프롬프트
|
|
146
146
|
│ ├── code-review/ # 코드 리뷰 프롬프트
|
|
@@ -167,7 +167,7 @@ JinhakStandard/
|
|
|
167
167
|
│ ├── settings.json # 권한, hooks 설정
|
|
168
168
|
│ ├── scripts/
|
|
169
169
|
│ │ └── session-briefing.cjs # 세션 자동 브리핑 Hook 스크립트
|
|
170
|
-
│ └── skills/ # 슬래시 명령어 (
|
|
170
|
+
│ └── skills/ # 슬래시 명령어 (14개)
|
|
171
171
|
│ ├── apply-standard/ # /apply-standard - 표준 적용/업데이트
|
|
172
172
|
│ ├── commit/ # /commit - 커밋 생성
|
|
173
173
|
│ ├── debug/ # /debug - 체계적 디버깅 (v2.3)
|
|
@@ -175,9 +175,10 @@ JinhakStandard/
|
|
|
175
175
|
│ ├── orchestrate/ # /orchestrate - Agent Teams + 2단계 검증 (v2.3)
|
|
176
176
|
│ ├── review-pr/ # /review-pr - PR 리뷰
|
|
177
177
|
│ ├── security-check/ # /security-check - 보안 점검 (v2.0)
|
|
178
|
-
│ ├── prompt-register/ # /prompt-register - 프롬프트 등록
|
|
179
|
-
│ ├── prompt-search/ # /prompt-search - 프롬프트 검색
|
|
180
|
-
│ ├── prompt-quality-check/ # /prompt-quality-check - 품질 검증
|
|
178
|
+
│ ├── prompt-register/ # /prompt-register - 프롬프트 등록
|
|
179
|
+
│ ├── prompt-search/ # /prompt-search - 프롬프트 검색
|
|
180
|
+
│ ├── prompt-quality-check/ # /prompt-quality-check - 품질 검증
|
|
181
|
+
│ ├── prompt-report/ # /prompt-report - 사용량 리포트 (v2.5)
|
|
181
182
|
│ ├── session-end/ # /session-end - 세션 종료 (v2.0.2)
|
|
182
183
|
│ ├── session-start/ # /session-start - 세션 시작
|
|
183
184
|
│ └── test/ # /test - 테스트 + Red-Green 검증 (v2.3)
|
|
@@ -213,26 +214,20 @@ JinhakStandard/
|
|
|
213
214
|
|
|
214
215
|
## 빠른 시작
|
|
215
216
|
|
|
216
|
-
> **자동 적용(권장):** [QUICK_START_PROMPT.md](./QUICK_START_PROMPT.md)의 프롬프트를 사용하세요.
|
|
217
|
+
> **자동 적용(권장):** `npx jinhak-ai-standard` 또는 [QUICK_START_PROMPT.md](./QUICK_START_PROMPT.md)의 프롬프트를 사용하세요.
|
|
217
218
|
|
|
218
219
|
### 수동 적용 (필요 시)
|
|
219
220
|
|
|
220
221
|
```bash
|
|
221
|
-
#
|
|
222
|
-
|
|
222
|
+
# 방법 A: npx로 한 번에 적용 (권장)
|
|
223
|
+
npx jinhak-ai-standard
|
|
223
224
|
|
|
224
|
-
#
|
|
225
|
+
# 방법 B: 저장소 클론 후 수동 복사
|
|
226
|
+
git clone https://github.com/JinhakStandard/ai-vibecoding.git /tmp/jinhak-standards
|
|
225
227
|
cp /tmp/jinhak-standards/templates/project-claude.md ./CLAUDE.md
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
mkdir -p .ai
|
|
229
|
-
touch .ai/SESSION_LOG.md .ai/CURRENT_SPRINT.md .ai/DECISIONS.md .ai/ARCHITECTURE.md .ai/CONVENTIONS.md
|
|
230
|
-
|
|
231
|
-
# 4. 스킬 파일 및 settings.json 복사
|
|
232
|
-
cp /tmp/jinhak-standards/.claude/skills/*/SKILL.md 각_스킬_폴더/
|
|
233
|
-
cp /tmp/jinhak-standards/.claude/settings.json .claude/
|
|
234
|
-
|
|
235
|
-
# 5. .gitignore에 추가
|
|
228
|
+
cp -r /tmp/jinhak-standards/.claude .
|
|
229
|
+
cp -r /tmp/jinhak-standards/security .
|
|
230
|
+
mkdir -p .ai
|
|
236
231
|
echo "CLAUDE.local.md" >> .gitignore
|
|
237
232
|
```
|
|
238
233
|
|
|
@@ -308,7 +303,8 @@ claude
|
|
|
308
303
|
|
|
309
304
|
| 버전 | 날짜 | 변경 내용 |
|
|
310
305
|
|------|------|----------|
|
|
311
|
-
| **2.
|
|
306
|
+
| **2.6** | **2026-03-02** | **npm 패키지 + GitHub Releases 배포: `npx jinhak-ai-standard` CLI (CLAUDE.md 자동 생성/업데이트, prompts/ 복사 포함)** |
|
|
307
|
+
| 2.5 | 2026-03-01 | 프롬프트 라이브러리 Phase 2: JABIS API 연동, `/prompt-report` 스킬, 사용량 추적 |
|
|
312
308
|
| 2.4 | 2026-02-28 | 프롬프트 라이브러리 Phase 1: 등록/검색/품질검증 시스템, 예시 프롬프트 3개, `/prompt-register` `/prompt-search` `/prompt-quality-check` 스킬 |
|
|
313
309
|
| 2.3 | 2026-02-28 | 적응적 추천 + skills.sh 모범사례 + 멀티 에이전트 패턴: 가중치 비평, C6 Hard Gate, State Contract, 스킬 조합 가이드, 2단계 검증, `/debug` 스킬, AI 합리화 방지 |
|
|
314
310
|
| 2.2 | 2026-02-28 | Planner-Critic 듀얼 에이전트 `/deep-plan` 스킬, Auto Memory 보강, memory-templates 추가 |
|
package/bin/cli.cjs
CHANGED
|
@@ -83,6 +83,143 @@ function ensureGitignoreEntries(entries) {
|
|
|
83
83
|
return added;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// ─────────────────────────────────────────
|
|
87
|
+
// 기술 스택 자동 감지
|
|
88
|
+
// ─────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
function detectTechStack() {
|
|
91
|
+
const stack = { projectName: path.basename(TARGET), description: '', framework: [], language: 'JavaScript', packageManager: 'npm', stateManagement: '', styling: '', database: '', orm: '', buildTool: '', testTool: '', scripts: {} };
|
|
92
|
+
let pkg = null;
|
|
93
|
+
try { pkg = JSON.parse(fs.readFileSync(path.join(TARGET, 'package.json'), 'utf8')); } catch { /* ignore */ }
|
|
94
|
+
|
|
95
|
+
if (pkg) {
|
|
96
|
+
if (pkg.name) stack.projectName = pkg.name;
|
|
97
|
+
if (pkg.description) stack.description = pkg.description;
|
|
98
|
+
if (pkg.scripts) stack.scripts = pkg.scripts;
|
|
99
|
+
const deps = Object.keys(pkg.dependencies || {}), devDeps = Object.keys(pkg.devDependencies || {}), allDeps = [...deps, ...devDeps];
|
|
100
|
+
|
|
101
|
+
if (deps.includes('next')) stack.framework.push('Next.js');
|
|
102
|
+
if (deps.includes('react') && !deps.includes('next')) stack.framework.push('React');
|
|
103
|
+
if (deps.includes('@nestjs/core')) stack.framework.push('NestJS');
|
|
104
|
+
if (deps.includes('express')) stack.framework.push('Express');
|
|
105
|
+
if (deps.includes('fastify')) stack.framework.push('Fastify');
|
|
106
|
+
if (deps.includes('nuxt')) stack.framework.push('Nuxt');
|
|
107
|
+
if (deps.includes('vue')) stack.framework.push('Vue');
|
|
108
|
+
|
|
109
|
+
if (allDeps.includes('typescript') || fs.existsSync(path.join(TARGET, 'tsconfig.json'))) stack.language = 'TypeScript';
|
|
110
|
+
|
|
111
|
+
if (deps.includes('zustand')) stack.stateManagement = 'Zustand';
|
|
112
|
+
else if (deps.includes('@reduxjs/toolkit') || deps.includes('redux')) stack.stateManagement = 'Redux';
|
|
113
|
+
else if (deps.includes('recoil')) stack.stateManagement = 'Recoil';
|
|
114
|
+
else if (deps.includes('jotai')) stack.stateManagement = 'Jotai';
|
|
115
|
+
|
|
116
|
+
if (allDeps.includes('tailwindcss')) stack.styling = 'Tailwind CSS';
|
|
117
|
+
else if (allDeps.includes('styled-components')) stack.styling = 'styled-components';
|
|
118
|
+
else if (allDeps.includes('@emotion/react')) stack.styling = 'Emotion';
|
|
119
|
+
else if (allDeps.includes('sass') || allDeps.includes('node-sass')) stack.styling = 'Sass/SCSS';
|
|
120
|
+
|
|
121
|
+
if (deps.includes('pg')) stack.database = 'PostgreSQL';
|
|
122
|
+
else if (deps.includes('mssql') || deps.includes('tedious')) stack.database = 'MSSQL';
|
|
123
|
+
else if (deps.includes('mysql2') || deps.includes('mysql')) stack.database = 'MySQL';
|
|
124
|
+
else if (deps.includes('mongodb') || deps.includes('mongoose')) stack.database = 'MongoDB';
|
|
125
|
+
|
|
126
|
+
if (deps.includes('@prisma/client') || devDeps.includes('prisma')) stack.orm = 'Prisma';
|
|
127
|
+
else if (deps.includes('drizzle-orm')) stack.orm = 'Drizzle';
|
|
128
|
+
else if (deps.includes('typeorm')) stack.orm = 'TypeORM';
|
|
129
|
+
|
|
130
|
+
if (allDeps.includes('vite')) stack.buildTool = 'Vite';
|
|
131
|
+
else if (allDeps.includes('webpack')) stack.buildTool = 'Webpack';
|
|
132
|
+
else if (allDeps.includes('esbuild')) stack.buildTool = 'esbuild';
|
|
133
|
+
|
|
134
|
+
if (allDeps.includes('vitest')) stack.testTool = 'Vitest';
|
|
135
|
+
else if (allDeps.includes('jest')) stack.testTool = 'Jest';
|
|
136
|
+
else if (allDeps.includes('mocha')) stack.testTool = 'Mocha';
|
|
137
|
+
else if (allDeps.includes('playwright')) stack.testTool = 'Playwright';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (fs.existsSync(path.join(TARGET, 'pnpm-lock.yaml'))) stack.packageManager = 'pnpm';
|
|
141
|
+
else if (fs.existsSync(path.join(TARGET, 'yarn.lock'))) stack.packageManager = 'yarn';
|
|
142
|
+
else if (fs.existsSync(path.join(TARGET, 'bun.lockb')) || fs.existsSync(path.join(TARGET, 'bun.lock'))) stack.packageManager = 'bun';
|
|
143
|
+
if (fs.existsSync(path.join(TARGET, 'tsconfig.json'))) stack.language = 'TypeScript';
|
|
144
|
+
|
|
145
|
+
return stack;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function generateFolderTree() {
|
|
149
|
+
try {
|
|
150
|
+
const entries = fs.readdirSync(TARGET, { withFileTypes: true })
|
|
151
|
+
.filter(e => !(e.name.startsWith('.') && e.name !== '.ai' && e.name !== '.claude') && e.name !== 'node_modules')
|
|
152
|
+
.sort((a, b) => a.isDirectory() === b.isDirectory() ? a.name.localeCompare(b.name) : a.isDirectory() ? -1 : 1);
|
|
153
|
+
return entries.map((e, i) => (i === entries.length - 1 ? '\u2514\u2500\u2500 ' : '\u251C\u2500\u2500 ') + e.name + (e.isDirectory() ? '/' : '')).join('\n');
|
|
154
|
+
} catch { return '[프로젝트 폴더 구조를 여기에 작성]'; }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function applyStackToTemplate(template, stack, version) {
|
|
158
|
+
const today = new Date().toISOString().split('T')[0];
|
|
159
|
+
template = template.replace(/\r\n/g, '\n');
|
|
160
|
+
const metaStart = template.indexOf('<!-- JINHAK Standard Metadata');
|
|
161
|
+
if (metaStart > 0) template = template.substring(metaStart);
|
|
162
|
+
|
|
163
|
+
template = template.replace(/jinhak_standard_version:\s*[\d.]+/, 'jinhak_standard_version: ' + version);
|
|
164
|
+
template = template.replace('[YYYY-MM-DD]', today);
|
|
165
|
+
template = template.replace(/\[표준 저장소 URL[^\]]*\]/, 'https://github.com/JinhakStandard/ai-vibecoding');
|
|
166
|
+
template = template.replace(/\[프로젝트명\]/g, stack.projectName);
|
|
167
|
+
template = template.replace(/\[프로젝트 한 줄 설명\]/, stack.description || '<!-- TODO: 프로젝트 설명을 작성하세요 -->');
|
|
168
|
+
template = template.replace(/\[프로젝트 폴더 구조를 여기에 작성\]/, generateFolderTree());
|
|
169
|
+
|
|
170
|
+
const fw = stack.framework.length > 0 ? stack.framework.join(' + ') : '-';
|
|
171
|
+
template = template.replace(/\[React \/ Next\.js \/ Express \/ NestJS 등\]/, fw);
|
|
172
|
+
template = template.replace(/\[JavaScript \/ TypeScript\]/, stack.language);
|
|
173
|
+
template = template.replace(/\[pnpm \/ npm \/ yarn\]/, stack.packageManager);
|
|
174
|
+
template = template.replace(/\[Zustand \/ Redux 등\]/, stack.stateManagement || '-');
|
|
175
|
+
template = template.replace(/\[Tailwind CSS \/ CSS Modules 등\]/, stack.styling || '-');
|
|
176
|
+
template = template.replace(/\[PostgreSQL \/ MySQL \/ MongoDB 등\]/, stack.database || '-');
|
|
177
|
+
template = template.replace(/\[Prisma \/ Drizzle \/ TypeORM 등\]/, stack.orm || '-');
|
|
178
|
+
template = template.replace(/\[Vite \/ Webpack \/ Turbopack 등\]/, stack.buildTool || '-');
|
|
179
|
+
|
|
180
|
+
const pm = stack.packageManager, s = stack.scripts, run = pm === 'npm' ? 'npm run' : pm;
|
|
181
|
+
template = template.replace(/\[pnpm install\]/g, pm === 'npm' ? 'npm install' : `${pm} install`);
|
|
182
|
+
template = template.replace(/\[pnpm dev\]/, s.dev ? `${run} dev` : `# ${run} dev (스크립트 미정의)`);
|
|
183
|
+
template = template.replace(/\[pnpm build\]/g, s.build ? `${run} build` : `# ${run} build (스크립트 미정의)`);
|
|
184
|
+
template = template.replace(/\[pnpm test\]/, s.test ? `${run} test` : `# ${run} test (스크립트 미정의)`);
|
|
185
|
+
template = template.replace(/\[pnpm typecheck\]/, s.typecheck ? `${run} typecheck` : (stack.language === 'TypeScript' ? 'npx tsc --noEmit' : `# TypeScript 미사용`));
|
|
186
|
+
template = template.replace(/\[pnpm lint\]/, s.lint ? `${run} lint` : `# ${run} lint (스크립트 미정의)`);
|
|
187
|
+
template = template.replace(/### 5\. \[프로젝트 특화 규칙\]\n- \[규칙 1 설명\]\n- \[규칙 2 설명\]\n- \[규칙 3 설명\]/, '### 5. 프로젝트 특화 규칙\n<!-- TODO: 프로젝트에 맞는 규칙을 추가하세요 -->\n- (규칙 추가 필요)');
|
|
188
|
+
template = template.replace(/\[apps\/\*\/node_modules packages\/\*\/node_modules\]/, '');
|
|
189
|
+
|
|
190
|
+
return template;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function generateAiContent(filename, stack, version) {
|
|
194
|
+
const today = new Date().toISOString().split('T')[0];
|
|
195
|
+
const fw = stack.framework.length > 0 ? stack.framework.join(' + ') : '미감지';
|
|
196
|
+
|
|
197
|
+
switch (filename) {
|
|
198
|
+
case 'SESSION_LOG.md':
|
|
199
|
+
return `# 세션 작업 기록\n\n> 세션 종료 시 반드시 업데이트하세요.\n\n---\n\n## ${today}\n\n### 세션 요약\n- JINHAK AI 개발 표준 v${version} 적용\n\n### 주요 변경\n- \`CLAUDE.md\` - AI 협업 설정 파일 생성\n- \`.claude/\` - Claude Code 설정 복사\n- \`.ai/\` - 프로젝트 문서화 폴더 초기화\n\n### 커밋\n- (표준 적용 커밋 필요)\n\n---\n`;
|
|
200
|
+
case 'CURRENT_SPRINT.md':
|
|
201
|
+
return `# 현재 진행 중인 작업\n\n> 마지막 업데이트: ${today}\n\n---\n\n## 진행 중 (In Progress)\n\n없음\n\n---\n\n## 대기 중 (Pending)\n\n### 우선순위 1: CLAUDE.md 프로젝트 특화 내용 보완\n- [ ] 프로젝트 설명 작성\n- [ ] 프로젝트 특화 규칙 추가\n\n---\n\n## 최근 완료\n\n### ${today}\n- [x] JINHAK AI 개발 표준 v${version} 적용\n\n---\n`;
|
|
202
|
+
case 'DECISIONS.md':
|
|
203
|
+
return `# 아키텍처 의사결정 기록 (ADR)\n\n---\n\n## ADR-001: 기술 스택 선정\n\n### 상태\n승인됨 (${today.substring(0, 7)})\n\n### 컨텍스트\n- ${stack.projectName} 프로젝트의 기술 스택 결정\n\n### 결정\n| 항목 | 선택 |\n|------|------|\n| 프레임워크 | ${fw} |\n| 언어 | ${stack.language} |\n| 패키지 매니저 | ${stack.packageManager} |${stack.stateManagement ? '\n| 상태관리 | ' + stack.stateManagement + ' |' : ''}${stack.database ? '\n| DB | ' + stack.database + ' |' : ''}${stack.orm ? '\n| ORM | ' + stack.orm + ' |' : ''}${stack.buildTool ? '\n| 빌드 | ' + stack.buildTool + ' |' : ''}${stack.testTool ? '\n| 테스트 | ' + stack.testTool + ' |' : ''}\n\n---\n\n## 의사결정 변경 이력\n\n| 날짜 | ADR | 변경 내용 |\n|------|-----|----------|\n| ${today} | ADR-001 | 초기 작성 (CLI 자동 감지) |\n`;
|
|
204
|
+
case 'ARCHITECTURE.md': {
|
|
205
|
+
const lines = [`# ${stack.projectName} 시스템 아키텍처\n\n> ${stack.description || '<!-- TODO: 프로젝트 설명 -->'}\n\n## 기술 스택\n`];
|
|
206
|
+
const front = stack.framework.filter(f => ['React', 'Next.js', 'Vue', 'Nuxt', 'Svelte'].includes(f));
|
|
207
|
+
const back = stack.framework.filter(f => ['Express', 'Fastify', 'NestJS'].includes(f));
|
|
208
|
+
if (front.length) lines.push(`### 프론트엔드\n- **프레임워크**: ${front.join(' + ')}${stack.styling ? '\n- **스타일링**: ' + stack.styling : ''}${stack.stateManagement ? '\n- **상태관리**: ' + stack.stateManagement : ''}\n`);
|
|
209
|
+
if (back.length) lines.push(`### 백엔드\n- **런타임**: Node.js\n- **프레임워크**: ${back.join(' + ')}\n`);
|
|
210
|
+
if (stack.database) lines.push(`### 데이터베이스\n- **DBMS**: ${stack.database}${stack.orm ? '\n- **ORM**: ' + stack.orm : ''}\n`);
|
|
211
|
+
lines.push(`### 빌드 & 개발\n- **언어**: ${stack.language}\n- **패키지 매니저**: ${stack.packageManager}${stack.buildTool ? '\n- **빌드 도구**: ' + stack.buildTool : ''}${stack.testTool ? '\n- **테스트**: ' + stack.testTool : ''}\n\n---\n`);
|
|
212
|
+
return lines.join('\n');
|
|
213
|
+
}
|
|
214
|
+
case 'CONVENTIONS.md': {
|
|
215
|
+
const ext = stack.language === 'TypeScript' ? '.tsx' : '.jsx';
|
|
216
|
+
return `# ${stack.projectName} 코딩 컨벤션\n\nJINHAK 전사 표준 기반.\n\n---\n\n## 네이밍 규칙\n\n| 유형 | 규칙 | 예시 |\n|------|------|------|\n| 컴포넌트 | PascalCase | \`MyComponent${ext}\` |\n| 함수/변수 | camelCase | \`handleSubmit\` |\n| 상수 | UPPER_SNAKE_CASE | \`MAX_RETRY\` |${stack.language === 'TypeScript' ? '\n| 타입 | PascalCase | `UserProfile` |' : ''}\n\n---\n\n## 금지 사항\n\n1. ${stack.language === 'TypeScript' ? '`any` 타입 사용 금지' : '불필요한 타입 강제 변환 금지'}\n2. \`console.log\` 프로덕션 코드 사용 금지\n3. 하드코딩된 URL/포트/비밀키 사용 금지\n4. PUT/PATCH/DELETE HTTP 메서드 사용 금지\n\n---\n`;
|
|
217
|
+
}
|
|
218
|
+
default:
|
|
219
|
+
return `# ${filename.replace('.md', '').replace(/_/g, ' ')}\n\n> 마지막 업데이트: ${today}\n\n---\n`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
86
223
|
// ─────────────────────────────────────────
|
|
87
224
|
// 메인 커맨드: apply
|
|
88
225
|
// ─────────────────────────────────────────
|
|
@@ -90,6 +227,7 @@ function ensureGitignoreEntries(entries) {
|
|
|
90
227
|
function apply() {
|
|
91
228
|
const version = getLatestVersion();
|
|
92
229
|
const current = getCurrentVersion();
|
|
230
|
+
const stack = detectTechStack();
|
|
93
231
|
|
|
94
232
|
log('');
|
|
95
233
|
log(c.bold('JINHAK AI 개발 표준 적용 도구'));
|
|
@@ -97,6 +235,18 @@ function apply() {
|
|
|
97
235
|
log(`대상 경로: ${c.cyan(TARGET)}`);
|
|
98
236
|
log('');
|
|
99
237
|
|
|
238
|
+
log(c.bold('감지된 기술 스택:'));
|
|
239
|
+
log(` 프로젝트: ${c.cyan(stack.projectName)}`);
|
|
240
|
+
if (stack.description) log(` 설명: ${stack.description}`);
|
|
241
|
+
log(` 프레임워크: ${stack.framework.length > 0 ? c.cyan(stack.framework.join(' + ')) : '-'}`);
|
|
242
|
+
log(` 언어: ${c.cyan(stack.language)} | 패키지 매니저: ${c.cyan(stack.packageManager)}`);
|
|
243
|
+
if (stack.stateManagement) log(` 상태관리: ${stack.stateManagement}`);
|
|
244
|
+
if (stack.styling) log(` 스타일링: ${stack.styling}`);
|
|
245
|
+
if (stack.database) log(` DB: ${stack.database}${stack.orm ? ' + ' + stack.orm : ''}`);
|
|
246
|
+
if (stack.buildTool) log(` 빌드: ${stack.buildTool}`);
|
|
247
|
+
if (stack.testTool) log(` 테스트: ${stack.testTool}`);
|
|
248
|
+
log('');
|
|
249
|
+
|
|
100
250
|
if (current) {
|
|
101
251
|
if (current === version) {
|
|
102
252
|
log(`이미 최신 버전 (v${current})이 적용되어 있습니다.`);
|
|
@@ -144,20 +294,14 @@ function apply() {
|
|
|
144
294
|
created.push('scripts/security-check-hook.cjs');
|
|
145
295
|
}
|
|
146
296
|
|
|
147
|
-
// 6. .ai/ 폴더
|
|
148
|
-
const aiFiles = [
|
|
149
|
-
'SESSION_LOG.md',
|
|
150
|
-
'CURRENT_SPRINT.md',
|
|
151
|
-
'DECISIONS.md',
|
|
152
|
-
'ARCHITECTURE.md',
|
|
153
|
-
'CONVENTIONS.md',
|
|
154
|
-
];
|
|
297
|
+
// 6. .ai/ 폴더 (기술 스택 기반 의미있는 초기 내용)
|
|
298
|
+
const aiFiles = ['SESSION_LOG.md', 'CURRENT_SPRINT.md', 'DECISIONS.md', 'ARCHITECTURE.md', 'CONVENTIONS.md'];
|
|
155
299
|
const aiDir = path.join(TARGET, '.ai');
|
|
156
300
|
fs.mkdirSync(aiDir, { recursive: true });
|
|
157
301
|
for (const f of aiFiles) {
|
|
158
302
|
const dest = path.join(aiDir, f);
|
|
159
303
|
if (!fs.existsSync(dest)) {
|
|
160
|
-
fs.writeFileSync(dest,
|
|
304
|
+
fs.writeFileSync(dest, generateAiContent(f, stack, version), 'utf8');
|
|
161
305
|
created.push(`.ai/${f}`);
|
|
162
306
|
}
|
|
163
307
|
}
|
|
@@ -175,6 +319,33 @@ function apply() {
|
|
|
175
319
|
created.push(`.gitignore (+${gitignoreAdded.length}개 항목)`);
|
|
176
320
|
}
|
|
177
321
|
|
|
322
|
+
// 8. CLAUDE.md (기술 스택 자동 치환)
|
|
323
|
+
const claudeMdDest = path.join(TARGET, 'CLAUDE.md');
|
|
324
|
+
const claudeTemplateSrc = path.join(STANDARD_ROOT, 'templates', 'project-claude.md');
|
|
325
|
+
if (!fs.existsSync(claudeMdDest)) {
|
|
326
|
+
if (fs.existsSync(claudeTemplateSrc)) {
|
|
327
|
+
let template = fs.readFileSync(claudeTemplateSrc, 'utf8');
|
|
328
|
+
template = applyStackToTemplate(template, stack, version);
|
|
329
|
+
fs.writeFileSync(claudeMdDest, template, 'utf8');
|
|
330
|
+
created.push('CLAUDE.md (기술 스택 자동 감지 적용)');
|
|
331
|
+
}
|
|
332
|
+
} else if (current && current !== version) {
|
|
333
|
+
let claudeContent = fs.readFileSync(claudeMdDest, 'utf8');
|
|
334
|
+
const today = new Date().toISOString().split('T')[0];
|
|
335
|
+
claudeContent = claudeContent.replace(/jinhak_standard_version:\s*[\d.]+/, 'jinhak_standard_version: ' + version);
|
|
336
|
+
claudeContent = claudeContent.replace(/applied_date:\s*[\d-]+/, 'applied_date: ' + today);
|
|
337
|
+
fs.writeFileSync(claudeMdDest, claudeContent, 'utf8');
|
|
338
|
+
created.push('CLAUDE.md (버전 ' + current + ' -> ' + version + ' 업데이트)');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 9. prompts/
|
|
342
|
+
const promptsSrc = path.join(STANDARD_ROOT, 'prompts');
|
|
343
|
+
const promptsDest = path.join(TARGET, 'prompts');
|
|
344
|
+
if (fs.existsSync(promptsSrc)) {
|
|
345
|
+
const promptCount = copyDir(promptsSrc, promptsDest);
|
|
346
|
+
if (promptCount > 0) created.push('prompts/ (' + promptCount + '개 파일)');
|
|
347
|
+
}
|
|
348
|
+
|
|
178
349
|
// 결과 출력
|
|
179
350
|
log('');
|
|
180
351
|
if (created.length > 0) {
|
|
@@ -188,7 +359,7 @@ function apply() {
|
|
|
188
359
|
|
|
189
360
|
log('');
|
|
190
361
|
log(c.bold('다음 단계:'));
|
|
191
|
-
log(' 1. CLAUDE.md
|
|
362
|
+
log(' 1. CLAUDE.md의 TODO 항목 확인 및 프로젝트에 맞게 수정');
|
|
192
363
|
log(' 2. Claude Code 세션 재시작 (settings.json 반영)');
|
|
193
364
|
log(' 3. /session-start 로 세션 시작');
|
|
194
365
|
log('');
|