@su-record/vibe 2.8.50 → 2.8.52

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.
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Stop Hook - 개발일지 자동 생성
3
+ *
4
+ * 매 커밋 후 카운터를 확인하여 interval(기본 10)에 도달하면
5
+ * LLM을 통해 개발일지를 생성하고 설정된 블로그 레포에 저장합니다.
6
+ *
7
+ * Config: .claude/vibe/config.json → devlog 섹션
8
+ */
9
+ import { execSync } from 'child_process';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { PROJECT_DIR, readProjectConfig } from './utils.js';
13
+
14
+ const DEFAULTS = {
15
+ targetDir: 'posts',
16
+ prefix: 'devlog',
17
+ interval: 10,
18
+ autoPush: false,
19
+ lang: 'ko',
20
+ category: 'dev-log',
21
+ tags: [],
22
+ };
23
+
24
+ function getDevlogConfig() {
25
+ const config = readProjectConfig();
26
+ const devlog = config.devlog;
27
+ if (!devlog || !devlog.enabled) return null;
28
+ if (!devlog.targetRepo) return null;
29
+ return { ...DEFAULTS, ...devlog };
30
+ }
31
+
32
+ function getProjectName() {
33
+ try {
34
+ const pkg = JSON.parse(fs.readFileSync(path.join(PROJECT_DIR, 'package.json'), 'utf-8'));
35
+ return pkg.name || path.basename(PROJECT_DIR);
36
+ } catch {
37
+ return path.basename(PROJECT_DIR);
38
+ }
39
+ }
40
+
41
+ function getGitAuthor() {
42
+ try {
43
+ return execSync('git config user.name', { cwd: PROJECT_DIR, encoding: 'utf-8' }).trim();
44
+ } catch {
45
+ return 'Unknown';
46
+ }
47
+ }
48
+
49
+ function findLastDevlogNumber(config) {
50
+ const dir = path.join(config.targetRepo, config.targetDir);
51
+ if (!fs.existsSync(dir)) return 0;
52
+
53
+ const files = fs.readdirSync(dir)
54
+ .filter(f => f.startsWith(config.prefix + '-') && f.endsWith('.md'))
55
+ .sort();
56
+
57
+ if (files.length === 0) return 0;
58
+
59
+ const last = files[files.length - 1];
60
+ const match = last.match(new RegExp(`${config.prefix}-(\\d+)\\.md$`));
61
+ return match ? parseInt(match[1], 10) : 0;
62
+ }
63
+
64
+ function findLastDevlogDate(config) {
65
+ const num = findLastDevlogNumber(config);
66
+ if (num === 0) return null;
67
+
68
+ const padded = String(num).padStart(4, '0');
69
+ const filePath = path.join(config.targetRepo, config.targetDir, `${config.prefix}-${padded}.md`);
70
+
71
+ try {
72
+ const content = fs.readFileSync(filePath, 'utf-8');
73
+ const dateMatch = content.match(/^date:\s*"?(\d{4}-\d{2}-\d{2})"?/m);
74
+ return dateMatch ? dateMatch[1] : null;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ function countCommitsSinceLastDevlog(config) {
81
+ const lastDate = findLastDevlogDate(config);
82
+ try {
83
+ const args = lastDate ? `--after="${lastDate}"` : '';
84
+ const log = execSync(`git log --oneline ${args}`, {
85
+ cwd: PROJECT_DIR,
86
+ encoding: 'utf-8',
87
+ }).trim();
88
+ return log ? log.split('\n').length : 0;
89
+ } catch {
90
+ return 0;
91
+ }
92
+ }
93
+
94
+ function getCommitsSinceLastDevlog(config) {
95
+ const lastDate = findLastDevlogDate(config);
96
+ const args = lastDate ? `--after="${lastDate}"` : '';
97
+ try {
98
+ return execSync(
99
+ `git log --oneline --reverse --format="%h %ad %s" --date=short ${args}`,
100
+ { cwd: PROJECT_DIR, encoding: 'utf-8' }
101
+ ).trim();
102
+ } catch {
103
+ return '';
104
+ }
105
+ }
106
+
107
+ function generateDevlogContent(config, commits, nextNumber) {
108
+ const projectName = getProjectName();
109
+ const author = config.author || getGitAuthor();
110
+ const today = new Date().toISOString().slice(0, 10);
111
+ const padded = String(nextNumber).padStart(4, '0');
112
+
113
+ const lines = commits.split('\n').filter(Boolean);
114
+ const interval = Math.min(lines.length, config.interval);
115
+
116
+ // 이번 배치의 커밋만 (최대 interval개)
117
+ const batch = lines.slice(0, interval);
118
+ const dates = batch.map(l => l.split(' ')[1]).filter(Boolean);
119
+ const startDate = dates[0] || today;
120
+ const endDate = dates[dates.length - 1] || today;
121
+
122
+ // 버전 범프 감지
123
+ const versions = batch
124
+ .map(l => l.split(' ').slice(2).join(' '))
125
+ .filter(msg => /^\d+\.\d+/.test(msg));
126
+ const startVersion = versions[0] || '';
127
+ const endVersion = versions[versions.length - 1] || '';
128
+
129
+ // 의미 있는 커밋 분류
130
+ const meaningful = batch
131
+ .map(l => {
132
+ const parts = l.split(' ');
133
+ return { hash: parts[0], date: parts[1], msg: parts.slice(2).join(' ') };
134
+ })
135
+ .filter(c => !/^\d+\.\d+/.test(c.msg)); // 버전 범프 제외
136
+
137
+ // 커밋 테이블 생성
138
+ const commitTable = meaningful.slice(0, 8).map(c => {
139
+ const shortMsg = c.msg.length > 60 ? c.msg.slice(0, 57) + '...' : c.msg;
140
+ return `| \`${shortMsg}\` | |`;
141
+ }).join('\n');
142
+
143
+ const versionInfo = startVersion && endVersion
144
+ ? `- **버전**: ${startVersion} → ${endVersion}`
145
+ : '';
146
+
147
+ return `---
148
+ title: "${projectName} 개발일지 #${nextNumber} - (${interval}개 커밋)"
149
+ date: "${today}"
150
+ category: "${config.category}"
151
+ description: ""
152
+ tags: [${[projectName, ...config.tags].map(t => `"${t}"`).join(', ')}]
153
+ author: "${author}"
154
+ lang: "${config.lang}"
155
+ ---
156
+
157
+ # ${projectName} 개발일지 #${nextNumber}
158
+
159
+ **작업 기간**: ${startDate}${startDate !== endDate ? ` ~ ${endDate}` : ''}
160
+
161
+ ## 이번 기간 작업 내용
162
+
163
+ ### ${interval}개 커밋
164
+
165
+ | 커밋 | 내용 |
166
+ |------|------|
167
+ ${commitTable}
168
+
169
+ ## 개발 현황
170
+
171
+ ${versionInfo}
172
+
173
+ ---
174
+
175
+ **다음 개발일지**: ${config.prefix}-${String(nextNumber + 1).padStart(4, '0')} (다음 ${config.interval}개 커밋 후)
176
+ `;
177
+ }
178
+
179
+ function writeDevlog(config, content, nextNumber) {
180
+ const padded = String(nextNumber).padStart(4, '0');
181
+ const targetDir = path.join(config.targetRepo, config.targetDir);
182
+ const filePath = path.join(targetDir, `${config.prefix}-${padded}.md`);
183
+
184
+ if (!fs.existsSync(targetDir)) {
185
+ fs.mkdirSync(targetDir, { recursive: true });
186
+ }
187
+
188
+ fs.writeFileSync(filePath, content, 'utf-8');
189
+ console.log(`[DEVLOG] Generated: ${filePath}`);
190
+
191
+ if (config.autoPush) {
192
+ try {
193
+ const relPath = path.join(config.targetDir, `${config.prefix}-${padded}.md`);
194
+ execSync(`git add "${relPath}"`, { cwd: config.targetRepo, stdio: 'ignore' });
195
+ execSync(`git commit -m "post: Add ${config.prefix} #${nextNumber}"`, {
196
+ cwd: config.targetRepo,
197
+ stdio: 'ignore',
198
+ });
199
+ execSync('git push', { cwd: config.targetRepo, stdio: 'ignore' });
200
+ console.log(`[DEVLOG] Pushed to ${config.targetRepo}`);
201
+ } catch (e) {
202
+ console.error(`[DEVLOG] Push failed: ${e.message}`);
203
+ }
204
+ }
205
+ }
206
+
207
+ try {
208
+ const config = getDevlogConfig();
209
+ if (!config) process.exit(0);
210
+
211
+ if (!fs.existsSync(config.targetRepo)) {
212
+ console.error(`[DEVLOG] targetRepo not found: ${config.targetRepo}`);
213
+ process.exit(0);
214
+ }
215
+
216
+ const commitCount = countCommitsSinceLastDevlog(config);
217
+ if (commitCount < config.interval) {
218
+ process.exit(0);
219
+ }
220
+
221
+ const nextNumber = findLastDevlogNumber(config) + 1;
222
+ const commits = getCommitsSinceLastDevlog(config);
223
+
224
+ if (!commits) process.exit(0);
225
+
226
+ const content = generateDevlogContent(config, commits, nextNumber);
227
+ writeDevlog(config, content, nextNumber);
228
+ } catch {
229
+ // devlog generation failure should never block
230
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "2.8.50",
3
+ "version": "2.8.52",
4
4
  "description": "AI Coding Framework for Claude Code — 56 agents, 45 skills, multi-LLM orchestration",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -17,7 +17,7 @@ Auto-generate app icons and favicons based on SPEC brand information.
17
17
 
18
18
  ## Prerequisites
19
19
 
20
- - Gemini API key configured (`vibe gemini auth`)
20
+ - Gemini API key configured (`vibe gemini key <key>`)
21
21
  - SPEC with brand context (app name, colors, style)
22
22
 
23
23
  ## Generated Assets
@@ -19,12 +19,6 @@ Training data의 지식 컷오프 문제를 해결합니다.
19
19
  | 웹 검색 → 노이즈 섞인 결과 | chub search → 큐레이션된 문서만 |
20
20
  | 세션마다 같은 실수 반복 | chub annotate → 학습 누적 |
21
21
 
22
- ## Prerequisites
23
-
24
- ```bash
25
- npm install -g @aisuite/chub
26
- ```
27
-
28
22
  ## When to Use
29
23
 
30
24
  | Situation | Example |
@@ -39,6 +33,8 @@ npm install -g @aisuite/chub
39
33
  ```
40
34
  외부 API/SDK 코드 작성 요청
41
35
 
36
+ Step 0: chub 설치 확인 (없으면 자동 설치)
37
+
42
38
  Step 1: chub search "<라이브러리명>"
43
39
 
44
40
  Step 2: chub get <id> --lang ts
@@ -48,6 +44,32 @@ Step 3: 문서 기반 코드 작성
48
44
  Step 4: gotcha 발견 시 chub annotate
49
45
  ```
50
46
 
47
+ ## Step 0 — 자동 설치 (Auto-install)
48
+
49
+ **스킬 실행 전 반드시 먼저 수행한다.**
50
+
51
+ ```bash
52
+ # 1. chub 존재 여부 확인
53
+ which chub || command -v chub
54
+ ```
55
+
56
+ chub이 없으면 자동 설치를 시도한다:
57
+
58
+ ```bash
59
+ npm install -g @aisuite/chub
60
+ ```
61
+
62
+ 설치 실패 시 `npx @aisuite/chub`로 대체 실행한다:
63
+
64
+ ```bash
65
+ # search 예시
66
+ npx @aisuite/chub search "stripe"
67
+ # get 예시
68
+ npx @aisuite/chub get stripe/api --lang ts
69
+ ```
70
+
71
+ `npx`도 실패하면 context7 또는 Web Search로 폴백한다 (Fallback Chain 참조).
72
+
51
73
  ## Usage
52
74
 
53
75
  ### Step 1 — 문서 검색
@@ -107,9 +129,11 @@ chub search # 인자 없이 실행하면 전체 목록 확인 가능
107
129
  ## Fallback Chain
108
130
 
109
131
  ```
110
- chub 설치 안 됨
132
+ which chub 실패
133
+
134
+ npm install -g @aisuite/chub (자동 시도)
111
135
 
112
- Prompt: npm install -g @aisuite/chub
136
+ 실패 npx @aisuite/chub <command> (임시 실행)
113
137
 
114
- If still unavailable: context7 또는 Web Search fallback
138
+ npx도 실패 context7 또는 Web Search fallback
115
139
  ```
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: devlog
3
+ tier: standard
4
+ description: "Auto-generate devlog posts from git commit history. Triggers every N commits, writes markdown to configured target repo."
5
+ triggers: [devlog, 개발일지, dev log, devlog 작성, 개발일지 작성]
6
+ priority: 60
7
+ ---
8
+ # Devlog Auto-Generator
9
+
10
+ Git 커밋 히스토리를 분석하여 개발일지를 자동 생성하고, 설정된 블로그 레포에 포스트로 저장합니다.
11
+
12
+ ## Config
13
+
14
+ `.claude/vibe/config.json`의 `devlog` 섹션:
15
+
16
+ ```json
17
+ {
18
+ "devlog": {
19
+ "enabled": true,
20
+ "targetRepo": "/absolute/path/to/blog-repo",
21
+ "targetDir": "posts",
22
+ "prefix": "devlog",
23
+ "interval": 10,
24
+ "autoPush": false,
25
+ "lang": "ko",
26
+ "author": "Su",
27
+ "category": "dev-log",
28
+ "tags": []
29
+ }
30
+ }
31
+ ```
32
+
33
+ | Key | Required | Default | Description |
34
+ |-----|----------|---------|-------------|
35
+ | `enabled` | Y | `false` | 활성화 여부 |
36
+ | `targetRepo` | Y | — | 블로그 레포 절대 경로 |
37
+ | `targetDir` | N | `"posts"` | 포스트 저장 디렉토리 |
38
+ | `prefix` | N | `"devlog"` | 파일명 프리픽스 (`{prefix}-{NNNN}.md`) |
39
+ | `interval` | N | `10` | 몇 커밋마다 생성할지 |
40
+ | `autoPush` | N | `false` | targetRepo에 자동 commit+push |
41
+ | `lang` | N | `"ko"` | 작성 언어 |
42
+ | `author` | N | git user.name | 작성자 |
43
+ | `category` | N | `"dev-log"` | frontmatter category |
44
+ | `tags` | N | `[]` | 기본 태그 (프로젝트명 등 자동 추가) |
45
+
46
+ ## Trigger Modes
47
+
48
+ ### 1. Auto (post-commit hook)
49
+
50
+ `devlog-gen.js` hook이 매 커밋마다 카운터를 확인하고, `interval`에 도달하면 `llm-orchestrate.js`를 통해 개발일지를 생성합니다.
51
+
52
+ ### 2. Manual
53
+
54
+ 사용자가 직접 `/devlog` 또는 `개발일지 작성` 키워드로 트리거. 마지막 개발일지 이후의 커밋을 모아 즉시 생성합니다.
55
+
56
+ ## Generation Process
57
+
58
+ ### Step 1: Collect Commits
59
+
60
+ 마지막 개발일지의 날짜 이후 커밋을 수집합니다:
61
+
62
+ ```bash
63
+ # 마지막 devlog 파일에서 날짜 추출
64
+ # targetRepo/targetDir/{prefix}-NNNN.md 의 frontmatter date
65
+ git log --oneline --after="{last_date}" --reverse
66
+ ```
67
+
68
+ ### Step 2: Analyze & Group
69
+
70
+ - 버전 범프 커밋 식별 (X.Y.Z 패턴)
71
+ - feat/fix/refactor/docs 분류
72
+ - 주요 커밋 선별 (의미 있는 변경)
73
+
74
+ ### Step 3: Generate Markdown
75
+
76
+ 다음 frontmatter 포맷으로 생성:
77
+
78
+ ```markdown
79
+ ---
80
+ title: "{project_name} 개발일지 #{next_number} - {summary_title} ({interval}개 커밋)"
81
+ date: "{today}"
82
+ category: "{category}"
83
+ description: "{one_line_description}"
84
+ tags: [{project_tags}, {auto_detected_tags}]
85
+ author: "{author}"
86
+ lang: "{lang}"
87
+ ---
88
+
89
+ # {title}
90
+
91
+ **작업 기간**: {start_date} ~ {end_date}
92
+
93
+ ## 이번 기간 작업 내용
94
+
95
+ ### {theme} ({interval}개 커밋)
96
+
97
+ {overview_paragraph}
98
+
99
+ | 커밋 | 내용 |
100
+ |------|------|
101
+ | `{meaningful_commit_message}` | **{highlight}** |
102
+ ...
103
+
104
+ ## 작업 하이라이트
105
+
106
+ {2-3 highlights with code blocks or diagrams}
107
+
108
+ ## 개발 현황
109
+
110
+ - **버전**: {start_version} → {end_version}
111
+ - **핵심**: {key_changes}
112
+
113
+ ---
114
+
115
+ **다음 개발일지**: {prefix}-{next+1} (다음 {interval}개 커밋 후)
116
+ ```
117
+
118
+ ### Step 4: Write File
119
+
120
+ ```
121
+ {targetRepo}/{targetDir}/{prefix}-{NNNN}.md
122
+ ```
123
+
124
+ 번호는 기존 파일에서 마지막 번호 + 1로 자동 결정.
125
+
126
+ ### Step 5: (Optional) Auto Push
127
+
128
+ `autoPush: true`이면:
129
+
130
+ ```bash
131
+ cd {targetRepo}
132
+ git add {targetDir}/{prefix}-{NNNN}.md
133
+ git commit -m "post: Add {prefix} #{NNNN}"
134
+ git push
135
+ ```
136
+
137
+ ## Rules
138
+
139
+ - **커밋 메시지 원문 보존** — 커밋 메시지를 그대로 인용, 임의 변경 금지
140
+ - **버전 범프 커밋 포함** — 테이블에는 포함하되 하이라이트에서 제외
141
+ - **하이라이트는 최대 3개** — 가장 영향력 있는 변경만 선별
142
+ - **코드 블록 활용** — Before/After 비교, 아키텍처 다이어그램 권장
143
+ - **숫자 강조** — 줄 수 변화(-N줄), 버전 범위, 파일 수 등