@su-record/vibe 1.0.4 → 1.0.6

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 (2) hide show
  1. package/bin/vibe +290 -10
  2. package/package.json +1 -1
package/bin/vibe CHANGED
@@ -78,9 +78,10 @@ function copyDirRecursive(sourceDir, targetDir) {
78
78
  });
79
79
  }
80
80
 
81
- // 기술 스택 감지 (루트 + 1레벨 하위 폴더)
81
+ // 기술 스택 감지 (루트 + 1레벨 하위 폴더) - 실제 의존성 기반
82
82
  function detectTechStacks(projectRoot) {
83
83
  const stacks = [];
84
+ const details = { databases: [], stateManagement: [], hosting: [], cicd: [] };
84
85
 
85
86
  const detectInDir = (dir, prefix = '') => {
86
87
  const detected = [];
@@ -91,12 +92,35 @@ function detectTechStacks(projectRoot) {
91
92
  const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
92
93
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
93
94
 
95
+ // 프레임워크 감지
94
96
  if (deps['next']) detected.push({ type: 'typescript-nextjs', path: prefix });
95
97
  else if (deps['react-native']) detected.push({ type: 'typescript-react-native', path: prefix });
96
98
  else if (deps['react']) detected.push({ type: 'typescript-react', path: prefix });
97
99
  else if (deps['vue']) detected.push({ type: 'typescript-vue', path: prefix });
98
100
  else if (deps['express'] || deps['fastify'] || deps['koa']) detected.push({ type: 'typescript-node', path: prefix });
99
101
  else if (pkg.name) detected.push({ type: 'typescript-node', path: prefix });
102
+
103
+ // DB 감지
104
+ if (deps['pg'] || deps['postgres'] || deps['@prisma/client']) details.databases.push('PostgreSQL');
105
+ if (deps['mysql'] || deps['mysql2']) details.databases.push('MySQL');
106
+ if (deps['mongodb'] || deps['mongoose']) details.databases.push('MongoDB');
107
+ if (deps['redis'] || deps['ioredis']) details.databases.push('Redis');
108
+ if (deps['sqlite3'] || deps['better-sqlite3']) details.databases.push('SQLite');
109
+ if (deps['typeorm']) details.databases.push('TypeORM');
110
+ if (deps['prisma'] || deps['@prisma/client']) details.databases.push('Prisma');
111
+ if (deps['drizzle-orm']) details.databases.push('Drizzle');
112
+ if (deps['sequelize']) details.databases.push('Sequelize');
113
+
114
+ // 상태관리 감지
115
+ if (deps['redux'] || deps['@reduxjs/toolkit']) details.stateManagement.push('Redux');
116
+ if (deps['zustand']) details.stateManagement.push('Zustand');
117
+ if (deps['jotai']) details.stateManagement.push('Jotai');
118
+ if (deps['recoil']) details.stateManagement.push('Recoil');
119
+ if (deps['mobx']) details.stateManagement.push('MobX');
120
+ if (deps['@tanstack/react-query'] || deps['react-query']) details.stateManagement.push('React Query');
121
+ if (deps['swr']) details.stateManagement.push('SWR');
122
+ if (deps['pinia']) details.stateManagement.push('Pinia');
123
+ if (deps['vuex']) details.stateManagement.push('Vuex');
100
124
  } catch (e) {}
101
125
  }
102
126
 
@@ -107,6 +131,12 @@ function detectTechStacks(projectRoot) {
107
131
  if (content.includes('fastapi')) detected.push({ type: 'python-fastapi', path: prefix });
108
132
  else if (content.includes('django')) detected.push({ type: 'python-django', path: prefix });
109
133
  else detected.push({ type: 'python', path: prefix });
134
+
135
+ // Python DB
136
+ if (content.includes('psycopg') || content.includes('asyncpg')) details.databases.push('PostgreSQL');
137
+ if (content.includes('pymongo')) details.databases.push('MongoDB');
138
+ if (content.includes('sqlalchemy')) details.databases.push('SQLAlchemy');
139
+ if (content.includes('prisma')) details.databases.push('Prisma');
110
140
  } catch (e) {}
111
141
  } else if (fs.existsSync(path.join(dir, 'requirements.txt'))) {
112
142
  try {
@@ -114,22 +144,44 @@ function detectTechStacks(projectRoot) {
114
144
  if (content.includes('fastapi')) detected.push({ type: 'python-fastapi', path: prefix });
115
145
  else if (content.includes('django')) detected.push({ type: 'python-django', path: prefix });
116
146
  else detected.push({ type: 'python', path: prefix });
147
+
148
+ if (content.includes('psycopg') || content.includes('asyncpg')) details.databases.push('PostgreSQL');
149
+ if (content.includes('pymongo')) details.databases.push('MongoDB');
150
+ if (content.includes('sqlalchemy')) details.databases.push('SQLAlchemy');
117
151
  } catch (e) {}
118
152
  }
119
153
 
120
154
  // Flutter / Dart
121
155
  if (fs.existsSync(path.join(dir, 'pubspec.yaml'))) {
122
156
  detected.push({ type: 'dart-flutter', path: prefix });
157
+ try {
158
+ const content = fs.readFileSync(path.join(dir, 'pubspec.yaml'), 'utf-8');
159
+ if (content.includes('flutter_riverpod') || content.includes('riverpod')) details.stateManagement.push('Riverpod');
160
+ else if (content.includes('provider')) details.stateManagement.push('Provider');
161
+ if (content.includes('bloc')) details.stateManagement.push('BLoC');
162
+ if (content.includes('getx') || content.includes('get:')) details.stateManagement.push('GetX');
163
+ } catch (e) {}
123
164
  }
124
165
 
125
166
  // Go
126
167
  if (fs.existsSync(path.join(dir, 'go.mod'))) {
127
168
  detected.push({ type: 'go', path: prefix });
169
+ try {
170
+ const content = fs.readFileSync(path.join(dir, 'go.mod'), 'utf-8');
171
+ if (content.includes('pgx') || content.includes('pq')) details.databases.push('PostgreSQL');
172
+ if (content.includes('go-redis')) details.databases.push('Redis');
173
+ if (content.includes('mongo-driver')) details.databases.push('MongoDB');
174
+ } catch (e) {}
128
175
  }
129
176
 
130
177
  // Rust
131
178
  if (fs.existsSync(path.join(dir, 'Cargo.toml'))) {
132
179
  detected.push({ type: 'rust', path: prefix });
180
+ try {
181
+ const content = fs.readFileSync(path.join(dir, 'Cargo.toml'), 'utf-8');
182
+ if (content.includes('sqlx') || content.includes('diesel')) details.databases.push('PostgreSQL');
183
+ if (content.includes('mongodb')) details.databases.push('MongoDB');
184
+ } catch (e) {}
133
185
  }
134
186
 
135
187
  // Java / Kotlin
@@ -143,12 +195,19 @@ function detectTechStacks(projectRoot) {
143
195
  else if (content.includes('kotlin')) detected.push({ type: 'kotlin', path: prefix });
144
196
  else if (content.includes('spring')) detected.push({ type: 'java-spring', path: prefix });
145
197
  else detected.push({ type: 'java', path: prefix });
198
+
199
+ if (content.includes('postgresql')) details.databases.push('PostgreSQL');
200
+ if (content.includes('mysql')) details.databases.push('MySQL');
201
+ if (content.includes('jpa') || content.includes('hibernate')) details.databases.push('JPA/Hibernate');
146
202
  } catch (e) {}
147
203
  } else if (fs.existsSync(path.join(dir, 'pom.xml'))) {
148
204
  try {
149
205
  const content = fs.readFileSync(path.join(dir, 'pom.xml'), 'utf-8');
150
206
  if (content.includes('spring')) detected.push({ type: 'java-spring', path: prefix });
151
207
  else detected.push({ type: 'java', path: prefix });
208
+
209
+ if (content.includes('postgresql')) details.databases.push('PostgreSQL');
210
+ if (content.includes('mysql')) details.databases.push('MySQL');
152
211
  } catch (e) {}
153
212
  }
154
213
 
@@ -161,6 +220,43 @@ function detectTechStacks(projectRoot) {
161
220
  return detected;
162
221
  };
163
222
 
223
+ // CI/CD 감지
224
+ if (fs.existsSync(path.join(projectRoot, '.github', 'workflows'))) {
225
+ details.cicd.push('GitHub Actions');
226
+ }
227
+ if (fs.existsSync(path.join(projectRoot, '.gitlab-ci.yml'))) {
228
+ details.cicd.push('GitLab CI');
229
+ }
230
+ if (fs.existsSync(path.join(projectRoot, 'Jenkinsfile'))) {
231
+ details.cicd.push('Jenkins');
232
+ }
233
+ if (fs.existsSync(path.join(projectRoot, '.circleci'))) {
234
+ details.cicd.push('CircleCI');
235
+ }
236
+
237
+ // Hosting 감지 (설정 파일 기반)
238
+ if (fs.existsSync(path.join(projectRoot, 'vercel.json')) ||
239
+ fs.existsSync(path.join(projectRoot, '.vercel'))) {
240
+ details.hosting.push('Vercel');
241
+ }
242
+ if (fs.existsSync(path.join(projectRoot, 'netlify.toml'))) {
243
+ details.hosting.push('Netlify');
244
+ }
245
+ if (fs.existsSync(path.join(projectRoot, 'app.yaml')) ||
246
+ fs.existsSync(path.join(projectRoot, 'cloudbuild.yaml'))) {
247
+ details.hosting.push('Google Cloud');
248
+ }
249
+ if (fs.existsSync(path.join(projectRoot, 'Dockerfile')) ||
250
+ fs.existsSync(path.join(projectRoot, 'docker-compose.yml'))) {
251
+ details.hosting.push('Docker');
252
+ }
253
+ if (fs.existsSync(path.join(projectRoot, 'fly.toml'))) {
254
+ details.hosting.push('Fly.io');
255
+ }
256
+ if (fs.existsSync(path.join(projectRoot, 'railway.json'))) {
257
+ details.hosting.push('Railway');
258
+ }
259
+
164
260
  // 루트 디렉토리 검사
165
261
  stacks.push(...detectInDir(projectRoot));
166
262
 
@@ -187,7 +283,13 @@ function detectTechStacks(projectRoot) {
187
283
  }
188
284
  }
189
285
 
190
- return stacks;
286
+ // 중복 제거
287
+ details.databases = [...new Set(details.databases)];
288
+ details.stateManagement = [...new Set(details.stateManagement)];
289
+ details.hosting = [...new Set(details.hosting)];
290
+ details.cicd = [...new Set(details.cicd)];
291
+
292
+ return { stacks, details };
191
293
  }
192
294
 
193
295
  // 협업자 자동 설치 설정
@@ -352,16 +454,22 @@ async function init(projectName) {
352
454
  copyDirContents(sourceDir, commandsDir);
353
455
  log(' ✅ 슬래시 커맨드 설치 완료 (7개)\n');
354
456
 
355
- // 기술 스택 감지
356
- const detectedStacks = detectTechStacks(projectRoot);
457
+ // 기술 스택 감지 (실제 의존성 기반)
458
+ const { stacks: detectedStacks, details: stackDetails } = detectTechStacks(projectRoot);
357
459
  if (detectedStacks.length > 0) {
358
460
  log(` 🔍 감지된 기술 스택:\n`);
359
461
  detectedStacks.forEach(s => {
360
462
  log(` - ${s.type}${s.path ? ` (${s.path}/)` : ''}\n`);
361
463
  });
464
+ if (stackDetails.databases.length > 0) {
465
+ log(` - DB: ${stackDetails.databases.join(', ')}\n`);
466
+ }
467
+ if (stackDetails.stateManagement.length > 0) {
468
+ log(` - State: ${stackDetails.stateManagement.join(', ')}\n`);
469
+ }
362
470
  }
363
471
 
364
- // constitution.md 생성 (감지된 스택으로 플레이스홀더 업데이트)
472
+ // constitution.md 생성 (실제 감지된 스택으로 플레이스홀더 업데이트)
365
473
  const templatePath = path.join(__dirname, '../templates/constitution-template.md');
366
474
  const constitutionPath = path.join(vibeDir, 'constitution.md');
367
475
  if (fs.existsSync(templatePath)) {
@@ -377,7 +485,7 @@ async function init(projectName) {
377
485
  s.type.includes('flutter') || s.type.includes('swift') || s.type.includes('android')
378
486
  );
379
487
 
380
- // 스택 이름 매핑
488
+ // 스택 이름 매핑 (기본값)
381
489
  const stackNames = {
382
490
  'python-fastapi': { lang: 'Python 3.11+', framework: 'FastAPI' },
383
491
  'python-django': { lang: 'Python 3.11+', framework: 'Django' },
@@ -395,7 +503,7 @@ async function init(projectName) {
395
503
  'swift-ios': { lang: 'Swift', framework: 'iOS/SwiftUI' }
396
504
  };
397
505
 
398
- // 플레이스홀더 업데이트
506
+ // 플레이스홀더 업데이트 - Backend
399
507
  if (backendStack && stackNames[backendStack.type]) {
400
508
  const info = stackNames[backendStack.type];
401
509
  constitution = constitution.replace(
@@ -408,6 +516,7 @@ async function init(projectName) {
408
516
  );
409
517
  }
410
518
 
519
+ // 플레이스홀더 업데이트 - Frontend
411
520
  if (frontendStack && stackNames[frontendStack.type]) {
412
521
  const info = stackNames[frontendStack.type];
413
522
  constitution = constitution.replace(
@@ -416,6 +525,58 @@ async function init(projectName) {
416
525
  );
417
526
  }
418
527
 
528
+ // 실제 감지된 DB로 플레이스홀더 업데이트
529
+ if (stackDetails.databases.length > 0) {
530
+ constitution = constitution.replace(
531
+ '- Database: {PostgreSQL / MongoDB / etc.}',
532
+ `- Database: ${stackDetails.databases.join(', ')}`
533
+ );
534
+ } else {
535
+ constitution = constitution.replace(
536
+ '- Database: {PostgreSQL / MongoDB / etc.}',
537
+ '- Database: (프로젝트에 맞게 설정)'
538
+ );
539
+ }
540
+
541
+ // 실제 감지된 상태관리로 플레이스홀더 업데이트
542
+ if (stackDetails.stateManagement.length > 0) {
543
+ constitution = constitution.replace(
544
+ '- State Management: {Provider / Redux / etc.}',
545
+ `- State Management: ${stackDetails.stateManagement.join(', ')}`
546
+ );
547
+ } else {
548
+ constitution = constitution.replace(
549
+ '- State Management: {Provider / Redux / etc.}',
550
+ '- State Management: (프로젝트에 맞게 설정)'
551
+ );
552
+ }
553
+
554
+ // 실제 감지된 Hosting으로 플레이스홀더 업데이트
555
+ if (stackDetails.hosting.length > 0) {
556
+ constitution = constitution.replace(
557
+ '- Hosting: {Cloud Run / Vercel / etc.}',
558
+ `- Hosting: ${stackDetails.hosting.join(', ')}`
559
+ );
560
+ } else {
561
+ constitution = constitution.replace(
562
+ '- Hosting: {Cloud Run / Vercel / etc.}',
563
+ '- Hosting: (프로젝트에 맞게 설정)'
564
+ );
565
+ }
566
+
567
+ // 실제 감지된 CI/CD로 플레이스홀더 업데이트
568
+ if (stackDetails.cicd.length > 0) {
569
+ constitution = constitution.replace(
570
+ '- CI/CD: {GitHub Actions / etc.}',
571
+ `- CI/CD: ${stackDetails.cicd.join(', ')}`
572
+ );
573
+ } else {
574
+ constitution = constitution.replace(
575
+ '- CI/CD: {GitHub Actions / etc.}',
576
+ '- CI/CD: (프로젝트에 맞게 설정)'
577
+ );
578
+ }
579
+
419
580
  // 날짜 업데이트
420
581
  const today = new Date().toISOString().split('T')[0];
421
582
  constitution = constitution.replace('{날짜}', today);
@@ -428,7 +589,8 @@ async function init(projectName) {
428
589
  const config = {
429
590
  language: 'ko',
430
591
  quality: { strict: true, autoVerify: true },
431
- stacks: detectedStacks
592
+ stacks: detectedStacks,
593
+ details: stackDetails
432
594
  };
433
595
  fs.writeFileSync(path.join(vibeDir, 'config.json'), JSON.stringify(config, null, 2));
434
596
 
@@ -704,10 +866,128 @@ async function update() {
704
866
  copyDirContents(sourceDir, commandsDir);
705
867
  log(' ✅ 슬래시 커맨드 업데이트 완료 (7개)\n');
706
868
 
707
- // .vibe/rules/ 업데이트
869
+ // 기술 스택 감지 (update에서도 실제 의존성 기반)
870
+ const { stacks: detectedStacks, details: stackDetails } = detectTechStacks(projectRoot);
871
+
872
+ // config.json 업데이트 (stacks + details 정보 추가)
873
+ const configPath = path.join(vibeDir, 'config.json');
874
+ if (fs.existsSync(configPath)) {
875
+ try {
876
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
877
+ config.stacks = detectedStacks;
878
+ config.details = stackDetails;
879
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
880
+ } catch (e) {}
881
+ }
882
+
883
+ // constitution.md 업데이트 (실제 감지된 스택으로)
884
+ const templatePath = path.join(__dirname, '../templates/constitution-template.md');
885
+ const constitutionPath = path.join(vibeDir, 'constitution.md');
886
+ if (fs.existsSync(templatePath)) {
887
+ let constitution = fs.readFileSync(templatePath, 'utf-8');
888
+
889
+ const backendStack = detectedStacks.find(s =>
890
+ s.type.includes('python') || s.type.includes('node') ||
891
+ s.type.includes('go') || s.type.includes('java') || s.type.includes('rust')
892
+ );
893
+ const frontendStack = detectedStacks.find(s =>
894
+ s.type.includes('react') || s.type.includes('vue') ||
895
+ s.type.includes('flutter') || s.type.includes('swift') || s.type.includes('android')
896
+ );
897
+
898
+ const stackNames = {
899
+ 'python-fastapi': { lang: 'Python 3.11+', framework: 'FastAPI' },
900
+ 'python-django': { lang: 'Python 3.11+', framework: 'Django' },
901
+ 'python': { lang: 'Python 3.11+', framework: '-' },
902
+ 'typescript-node': { lang: 'TypeScript/Node.js', framework: 'Express/Fastify' },
903
+ 'typescript-nextjs': { lang: 'TypeScript', framework: 'Next.js' },
904
+ 'typescript-react': { lang: 'TypeScript', framework: 'React' },
905
+ 'typescript-vue': { lang: 'TypeScript', framework: 'Vue.js' },
906
+ 'typescript-react-native': { lang: 'TypeScript', framework: 'React Native' },
907
+ 'dart-flutter': { lang: 'Dart', framework: 'Flutter' },
908
+ 'go': { lang: 'Go', framework: '-' },
909
+ 'rust': { lang: 'Rust', framework: '-' },
910
+ 'java-spring': { lang: 'Java 17+', framework: 'Spring Boot' },
911
+ 'kotlin-android': { lang: 'Kotlin', framework: 'Android' },
912
+ 'swift-ios': { lang: 'Swift', framework: 'iOS/SwiftUI' }
913
+ };
914
+
915
+ if (backendStack && stackNames[backendStack.type]) {
916
+ const info = stackNames[backendStack.type];
917
+ constitution = constitution.replace('- Language: {Python 3.11+ / Node.js / etc.}', `- Language: ${info.lang}`);
918
+ constitution = constitution.replace('- Framework: {FastAPI / Express / etc.}', `- Framework: ${info.framework}`);
919
+ }
920
+
921
+ if (frontendStack && stackNames[frontendStack.type]) {
922
+ const info = stackNames[frontendStack.type];
923
+ constitution = constitution.replace('- Framework: {Flutter / React / etc.}', `- Framework: ${info.framework}`);
924
+ }
925
+
926
+ // 실제 감지된 값으로 업데이트
927
+ constitution = constitution.replace(
928
+ '- Database: {PostgreSQL / MongoDB / etc.}',
929
+ stackDetails.databases.length > 0 ? `- Database: ${stackDetails.databases.join(', ')}` : '- Database: (프로젝트에 맞게 설정)'
930
+ );
931
+ constitution = constitution.replace(
932
+ '- State Management: {Provider / Redux / etc.}',
933
+ stackDetails.stateManagement.length > 0 ? `- State Management: ${stackDetails.stateManagement.join(', ')}` : '- State Management: (프로젝트에 맞게 설정)'
934
+ );
935
+ constitution = constitution.replace(
936
+ '- Hosting: {Cloud Run / Vercel / etc.}',
937
+ stackDetails.hosting.length > 0 ? `- Hosting: ${stackDetails.hosting.join(', ')}` : '- Hosting: (프로젝트에 맞게 설정)'
938
+ );
939
+ constitution = constitution.replace(
940
+ '- CI/CD: {GitHub Actions / etc.}',
941
+ stackDetails.cicd.length > 0 ? `- CI/CD: ${stackDetails.cicd.join(', ')}` : '- CI/CD: (프로젝트에 맞게 설정)'
942
+ );
943
+
944
+ const today = new Date().toISOString().split('T')[0];
945
+ constitution = constitution.replace('{날짜}', today);
946
+ constitution = constitution.replace('{이름}', 'vibe update');
947
+ constitution = constitution.replace('토큰 유효 기간: {기간}', '토큰 유효 기간: 1시간');
948
+
949
+ fs.writeFileSync(constitutionPath, constitution);
950
+ log(' ✅ constitution.md 업데이트 완료\n');
951
+ }
952
+
953
+ // .vibe/rules/ 업데이트 (감지된 스택에 해당하는 언어 규칙만)
708
954
  const rulesSource = path.join(__dirname, '../.vibe/rules');
709
955
  const rulesTarget = path.join(vibeDir, 'rules');
710
- copyDirRecursive(rulesSource, rulesTarget);
956
+
957
+ // core, quality, standards, tools는 전체 복사
958
+ const coreDirs = ['core', 'quality', 'standards', 'tools'];
959
+ coreDirs.forEach(dir => {
960
+ const src = path.join(rulesSource, dir);
961
+ const dst = path.join(rulesTarget, dir);
962
+ if (fs.existsSync(src)) {
963
+ copyDirRecursive(src, dst);
964
+ }
965
+ });
966
+
967
+ // languages는 감지된 스택만 복사 (기존 불필요 파일 제거)
968
+ const langSource = path.join(rulesSource, 'languages');
969
+ const langTarget = path.join(rulesTarget, 'languages');
970
+
971
+ // 기존 languages 폴더 정리 후 필요한 것만 복사
972
+ if (fs.existsSync(langTarget)) {
973
+ removeDirRecursive(langTarget);
974
+ }
975
+ ensureDir(langTarget);
976
+
977
+ const detectedTypes = detectedStacks.map(s => s.type);
978
+ if (fs.existsSync(langSource)) {
979
+ const langFiles = fs.readdirSync(langSource);
980
+ langFiles.forEach(file => {
981
+ const langType = file.replace('.md', '');
982
+ if (detectedTypes.includes(langType)) {
983
+ fs.copyFileSync(path.join(langSource, file), path.join(langTarget, file));
984
+ }
985
+ });
986
+ }
987
+
988
+ if (detectedStacks.length > 0) {
989
+ log(` 🔍 감지된 기술 스택: ${detectedTypes.join(', ')}\n`);
990
+ }
711
991
  log(' ✅ 코딩 규칙 업데이트 완료 (.vibe/rules/)\n');
712
992
 
713
993
  // .claude/agents/ 업데이트
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Vibe - Claude Code exclusive SPEC-driven AI coding framework",
5
5
  "bin": {
6
6
  "vibe": "./bin/vibe"