aiwf 0.1.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.
Files changed (43) hide show
  1. package/AI-WORKFLOW.md +285 -0
  2. package/CHANGELOG.md +1 -0
  3. package/COMMANDS_GUIDE.md +462 -0
  4. package/LICENSE +21 -0
  5. package/PRD.ko.md +96 -0
  6. package/PRD.md +98 -0
  7. package/README.ko.md +115 -0
  8. package/README.md +117 -0
  9. package/claude-code/docker/Dockerfile +117 -0
  10. package/claude-code/simone/.simone/00_PROJECT_MANIFEST.md +49 -0
  11. package/claude-code/simone/.simone/01_PROJECT_DOCS/ARCHITECTURE.md +55 -0
  12. package/claude-code/simone/.simone/02_REQUIREMENTS/CLAUDE.md +78 -0
  13. package/claude-code/simone/.simone/02_REQUIREMENTS/M01_Backend_Setup/M01_milestone_meta.md +38 -0
  14. package/claude-code/simone/.simone/02_REQUIREMENTS/M01_Backend_Setup/PRD_AMEND_01_Auth_Flow_Update.md +69 -0
  15. package/claude-code/simone/.simone/02_REQUIREMENTS/M01_Backend_Setup/PRD_Backend_Setup.md +98 -0
  16. package/claude-code/simone/.simone/02_REQUIREMENTS/M01_Backend_Setup/SPECS_API_V1.md +232 -0
  17. package/claude-code/simone/.simone/03_SPRINTS/CLAUDE.MD +62 -0
  18. package/claude-code/simone/.simone/03_SPRINTS/S01_M01_Initial_API/S01_sprint_meta.md +42 -0
  19. package/claude-code/simone/.simone/03_SPRINTS/S01_M01_Initial_API/T01_S01_Setup_Project_Structure.md +56 -0
  20. package/claude-code/simone/.simone/04_GENERAL_TASKS/CLAUDE.MD +51 -0
  21. package/claude-code/simone/.simone/04_GENERAL_TASKS/T002_API_Rate_Limiting.md +49 -0
  22. package/claude-code/simone/.simone/04_GENERAL_TASKS/TX001_Refactor_Logging_Module.md +53 -0
  23. package/claude-code/simone/.simone/05_ARCHITECTURAL_DECISIONS/ADR001_Chosen_Database_System.md +113 -0
  24. package/claude-code/simone/.simone/05_ARCHITECTURAL_DECISIONS/ADR002_API_Authentication_Method.md +118 -0
  25. package/claude-code/simone/.simone/99_TEMPLATES/adr_template.md +49 -0
  26. package/claude-code/simone/.simone/99_TEMPLATES/milestone_meta_template.md +25 -0
  27. package/claude-code/simone/.simone/99_TEMPLATES/project_manifest_template.md +39 -0
  28. package/claude-code/simone/.simone/99_TEMPLATES/sprint_meta_template.md +23 -0
  29. package/claude-code/simone/.simone/99_TEMPLATES/task_template.md +35 -0
  30. package/claude-code/simone/.simone/CLAUDE.MD +65 -0
  31. package/claude-code/simone/.simone/README.md +97 -0
  32. package/claude-code/simone/CHANGELOG.md +71 -0
  33. package/claude-code/simone/LICENSE +21 -0
  34. package/claude-code/simone/README.md +219 -0
  35. package/claude-code/simone/SYNC_GUIDE.md +172 -0
  36. package/claude-code/simone/sync-simone.sh +138 -0
  37. package/index.js +468 -0
  38. package/package.json +38 -0
  39. package/rules/global/code-style-guide.md +30 -0
  40. package/rules/global/coding-principles.md +33 -0
  41. package/rules/global/development-process.md +41 -0
  42. package/rules/global/global-rules.md +84 -0
  43. package/rules/manual/generate-plan-docs.md +280 -0
package/index.js ADDED
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import prompts from 'prompts';
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import https from 'https';
11
+ import { createWriteStream } from 'fs';
12
+ import { pipeline } from 'stream/promises';
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+
16
+ const program = new Command();
17
+
18
+ const GITHUB_API_URL = 'https://api.github.com/repos/moonklabs/aiwf';
19
+ const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/moonklabs/aiwf/master';
20
+ const GITHUB_CONTENT_PREFIX = 'claude-code/moonklabs';
21
+
22
+ async function fetchGitHubContent(url) {
23
+ return new Promise((resolve, reject) => {
24
+ https.get(url, { headers: { 'User-Agent': 'hello-moonklabs' } }, (res) => {
25
+ let data = '';
26
+ res.on('data', chunk => data += chunk);
27
+ res.on('end', () => {
28
+ if (res.statusCode === 200) {
29
+ resolve(data);
30
+ } else {
31
+ reject(new Error(`Failed to fetch ${url}: ${res.statusCode}`));
32
+ }
33
+ res.destroy();
34
+ });
35
+ }).on('error', (err) => {
36
+ reject(err);
37
+ });
38
+ });
39
+ }
40
+
41
+ async function downloadFile(url, destPath) {
42
+ return new Promise((resolve, reject) => {
43
+ https.get(url, { headers: { 'User-Agent': 'hello-moonklabs' } }, (response) => {
44
+ if (response.statusCode !== 200) {
45
+ response.destroy();
46
+ reject(new Error(`Failed to download ${url}: ${response.statusCode}`));
47
+ return;
48
+ }
49
+ const file = createWriteStream(destPath);
50
+ pipeline(response, file)
51
+ .then(() => {
52
+ resolve();
53
+ })
54
+ .catch((err) => {
55
+ reject(err);
56
+ })
57
+ .finally(() => {
58
+ response.destroy();
59
+ });
60
+ }).on('error', (err) => {
61
+ reject(err);
62
+ });
63
+ });
64
+ }
65
+
66
+ async function getDirectoryStructure(path = '') {
67
+ const url = `${GITHUB_API_URL}/contents/${path}`;
68
+ const content = await fetchGitHubContent(url);
69
+ return JSON.parse(content);
70
+ }
71
+
72
+ async function checkExistingInstallation() {
73
+ const moonklabsExists = await fs.access('.moonklabs').then(() => true).catch(() => false);
74
+ const claudeCommandsExists = await fs.access('.claude/commands/moonklabs').then(() => true).catch(() => false);
75
+ return moonklabsExists || claudeCommandsExists;
76
+ }
77
+
78
+ // 백업 폴더명 생성 함수
79
+ function getBackupDirName() {
80
+ const now = new Date();
81
+ const pad = n => n.toString().padStart(2, '0');
82
+ const y = now.getFullYear();
83
+ const m = pad(now.getMonth() + 1);
84
+ const d = pad(now.getDate());
85
+ const h = pad(now.getHours());
86
+ const min = pad(now.getMinutes());
87
+ const s = pad(now.getSeconds());
88
+ return `.moonklabs/backup_${y}${m}${d}_${h}${min}${s}`;
89
+ }
90
+
91
+ let BACKUP_DIR = null;
92
+
93
+ async function backupFile(filePath, backupDir) {
94
+ try {
95
+ const exists = await fs.access(filePath).then(() => true).catch(() => false);
96
+ if (exists) {
97
+ await fs.mkdir(backupDir, { recursive: true });
98
+ const backupPath = `${filePath}.bak`;
99
+ await fs.copyFile(filePath, backupPath);
100
+ // 상대경로 보존: .claude/commands/moonklabs/foo.md -> backupDir/claude-commands-moonklabs-foo.md.bak
101
+ let bakFileName;
102
+ if (filePath.startsWith('.claude/commands/moonklabs/')) {
103
+ bakFileName = filePath.replace(/\//g, '-').replace(/^\./, '') + '.bak';
104
+ } else if (filePath.startsWith('.moonklabs/')) {
105
+ bakFileName = filePath.replace(/\//g, '-').replace(/^\./, '') + '.bak';
106
+ } else {
107
+ bakFileName = path.basename(filePath) + '.bak';
108
+ }
109
+ const destPath = path.join(backupDir, bakFileName);
110
+ await fs.rename(backupPath, destPath);
111
+ return destPath;
112
+ }
113
+ } catch (error) {
114
+ // Backup failed, but continue
115
+ }
116
+ return null;
117
+ }
118
+
119
+ async function backupCommandsAndDocs() {
120
+ if (!BACKUP_DIR) BACKUP_DIR = getBackupDirName();
121
+ const spinner = ora('기존 명령어 및 문서 백업 중...').start();
122
+ const backedUpFiles = [];
123
+ await fs.mkdir(BACKUP_DIR, { recursive: true });
124
+ try {
125
+ // Files that will be updated and need backup
126
+ const filesToBackup = [
127
+ '.moonklabs/CLAUDE.md',
128
+ '.moonklabs/02_REQUIREMENTS/CLAUDE.md',
129
+ '.moonklabs/03_SPRINTS/CLAUDE.md',
130
+ '.moonklabs/04_GENERAL_TASKS/CLAUDE.md'
131
+ ];
132
+ // Backup CLAUDE.md files
133
+ for (const file of filesToBackup) {
134
+ const backupPath = await backupFile(file, BACKUP_DIR);
135
+ if (backupPath) {
136
+ backedUpFiles.push(backupPath);
137
+ }
138
+ }
139
+ // Backup all command files
140
+ const commandsDir = '.claude/commands/moonklabs';
141
+ const commandsExist = await fs.access(commandsDir).then(() => true).catch(() => false);
142
+ if (commandsExist) {
143
+ try {
144
+ const commandFiles = await fs.readdir(commandsDir);
145
+ for (const file of commandFiles) {
146
+ const filePath = path.join(commandsDir, file);
147
+ const stat = await fs.stat(filePath);
148
+ if (stat.isFile() && file.endsWith('.md')) {
149
+ const backupPath = await backupFile(filePath, BACKUP_DIR);
150
+ if (backupPath) {
151
+ backedUpFiles.push(backupPath);
152
+ }
153
+ }
154
+ }
155
+ } catch (error) {
156
+ // Commands directory might be empty or have issues
157
+ }
158
+ }
159
+ if (backedUpFiles.length > 0) {
160
+ spinner.succeed(chalk.green(`${backedUpFiles.length}개 파일 백업 완료 (*.bak)`));
161
+ } else {
162
+ spinner.succeed(chalk.gray('백업할 기존 파일이 없습니다'));
163
+ }
164
+ return backedUpFiles;
165
+ } catch (error) {
166
+ spinner.fail(chalk.red('백업 실패'));
167
+ throw error;
168
+ }
169
+ }
170
+
171
+ async function downloadDirectory(githubPath, localPath, spinner) {
172
+ await fs.mkdir(localPath, { recursive: true });
173
+
174
+ const items = await getDirectoryStructure(githubPath);
175
+
176
+ for (const item of items) {
177
+ const itemLocalPath = path.join(localPath, item.name);
178
+
179
+ if (item.type === 'dir') {
180
+ await downloadDirectory(item.path, itemLocalPath, spinner);
181
+ } else if (item.type === 'file') {
182
+ spinner.text = `${item.path} 다운로드 중...`;
183
+ await downloadFile(item.download_url, itemLocalPath);
184
+ }
185
+ }
186
+ }
187
+
188
+ // 스피너와 로그를 동시에 처리하는 함수
189
+ function logWithSpinner(spinner, message, debugLog) {
190
+ if (debugLog) console.log(chalk.gray(message));
191
+ if (spinner) spinner.text = message;
192
+ }
193
+
194
+ async function installMoonklabs(options = {}) {
195
+ const debugLog = options.debugLog || false;
196
+ console.log(chalk.blue.bold('\n🎉 Hello Moonklabs에 오신 것을 환영합니다!\n'));
197
+ console.log(chalk.gray('이 설치 프로그램은 Moonklabs AI 프롬프트 프레임워크를 설정합니다'));
198
+ console.log(chalk.gray('특별히 Claude Code 에 최적화 되어있습니다.\n'));
199
+
200
+ const hasExisting = await checkExistingInstallation();
201
+
202
+ if (hasExisting && !options.force) {
203
+ const response = await prompts({
204
+ type: 'select',
205
+ name: 'action',
206
+ message: '기존 Moonklabs 설치가 감지되었습니다. 무엇을 하시겠습니까?',
207
+ choices: [
208
+ { title: '업데이트 (명령어와 문서만 업데이트하고 작업 내용은 보존)', value: 'update' },
209
+ { title: '설치 건너뛰기', value: 'skip' },
210
+ { title: '취소', value: 'cancel' }
211
+ ]
212
+ });
213
+
214
+ if (response.action === 'skip' || response.action === 'cancel') {
215
+ console.log(chalk.yellow('\n설치가 취소되었습니다.'));
216
+ process.exit(0);
217
+ }
218
+
219
+ if (response.action === 'update') {
220
+ await backupCommandsAndDocs();
221
+ }
222
+ }
223
+
224
+ const spinner = ora('GitHub에서 Moonklabs 프레임워크를 가져오는 중...').start();
225
+
226
+ try {
227
+ // Create .moonklabs directory structure
228
+ const moonklabsDirs = [
229
+ '.moonklabs',
230
+ '.moonklabs/01_PROJECT_DOCS',
231
+ '.moonklabs/02_REQUIREMENTS',
232
+ '.moonklabs/03_SPRINTS',
233
+ '.moonklabs/04_GENERAL_TASKS',
234
+ '.moonklabs/05_ARCHITECTURE_DECISIONS',
235
+ '.moonklabs/10_STATE_OF_PROJECT',
236
+ '.moonklabs/98_PROMPTS',
237
+ '.moonklabs/99_TEMPLATES'
238
+ ];
239
+
240
+ for (const dir of moonklabsDirs) {
241
+ await fs.mkdir(dir, { recursive: true });
242
+ }
243
+
244
+ // Only download manifest on fresh installs
245
+ if (!hasExisting) {
246
+ logWithSpinner(spinner, 'Moonklabs 프레임워크 파일을 다운로드하는 중...', debugLog);
247
+ // Get the root manifest
248
+ try {
249
+ const manifestUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_PREFIX}/.moonklabs/00_PROJECT_MANIFEST.md`;
250
+ await downloadFile(manifestUrl, '.moonklabs/00_PROJECT_MANIFEST.md');
251
+ } catch (error) {
252
+ // If manifest doesn't exist, that's okay
253
+ }
254
+ }
255
+
256
+ // Download templates on fresh install
257
+ try {
258
+ logWithSpinner(spinner, '템플릿 디렉토리를 다운로드 중...', debugLog);
259
+ await downloadDirectory(`${GITHUB_CONTENT_PREFIX}/.moonklabs/98_PROMPTS`, '.moonklabs/98_PROMPTS', spinner);
260
+ await downloadDirectory(`${GITHUB_CONTENT_PREFIX}/.moonklabs/99_TEMPLATES`, '.moonklabs/99_TEMPLATES', spinner);
261
+ } catch (error) {
262
+ logWithSpinner(spinner, '템플릿 디렉토리를 찾을 수 없어 건너뜁니다...', debugLog);
263
+ }
264
+
265
+ // Always update CLAUDE.md documentation files
266
+ logWithSpinner(spinner, '문서를 업데이트하는 중...', debugLog);
267
+ const claudeFiles = [
268
+ '.moonklabs/CLAUDE.md',
269
+ '.moonklabs/02_REQUIREMENTS/CLAUDE.md',
270
+ '.moonklabs/03_SPRINTS/CLAUDE.md',
271
+ '.moonklabs/04_GENERAL_TASKS/CLAUDE.md'
272
+ ];
273
+
274
+ for (const claudeFile of claudeFiles) {
275
+ try {
276
+ const claudeUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_PREFIX}/${claudeFile}`;
277
+ await downloadFile(claudeUrl, claudeFile);
278
+ } catch (error) {
279
+ // If CLAUDE.md doesn't exist, that's okay
280
+ }
281
+ }
282
+
283
+ // Create .claude/commands/moonklabs directory
284
+ await fs.mkdir('.claude/commands/moonklabs', { recursive: true });
285
+
286
+ // Always update commands
287
+ logWithSpinner(spinner, 'Moonklabs 명령어를 업데이트하는 중...', debugLog);
288
+ try {
289
+ await downloadDirectory(`${GITHUB_CONTENT_PREFIX}/.claude/commands/moonklabs`, '.claude/commands/moonklabs', spinner);
290
+ } catch (error) {
291
+ logWithSpinner(spinner, '명령어 디렉토리를 찾을 수 없어 건너뜁니다...', debugLog);
292
+ }
293
+
294
+ // 2. rules/global 폴더 다운로드 (임시 폴더)
295
+ let tmpRulesGlobal = '.moonklabs/_tmp_rules_global';
296
+ try {
297
+ logWithSpinner(spinner, 'rules/global 폴더를 다운로드 중...', debugLog);
298
+ await downloadDirectory(`rules/global`, tmpRulesGlobal, spinner);
299
+ } catch (error) {
300
+ logWithSpinner(spinner, 'rules/global 폴더를 찾을 수 없어 건너뜁니다...', debugLog);
301
+ tmpRulesGlobal = null;
302
+ }
303
+
304
+ // 2-1. rules/manual 폴더 다운로드 (임시 폴더)
305
+ let tmpRulesManual = '.moonklabs/_tmp_rules_manual';
306
+ try {
307
+ logWithSpinner(spinner, 'rules/manual 폴더를 다운로드 중...', debugLog);
308
+ await downloadDirectory(`rules/manual`, tmpRulesManual, spinner);
309
+ } catch (error) {
310
+ logWithSpinner(spinner, 'rules/manual 폴더를 찾을 수 없어 건너뜁니다...', debugLog);
311
+ tmpRulesManual = null;
312
+ }
313
+
314
+ // 3. rules/global -> .cursor/rules(.mdc+헤더), .windsurf/rules(확장자 유지) 복사
315
+ if (tmpRulesGlobal) {
316
+ const cursorRulesDir = '.cursor/rules';
317
+ const windsurfRulesDir = '.windsurf/rules';
318
+ await fs.mkdir(cursorRulesDir, { recursive: true });
319
+ await fs.mkdir(windsurfRulesDir, { recursive: true });
320
+ const files = await fs.readdir(tmpRulesGlobal);
321
+ for (const file of files) {
322
+ const srcPath = path.join(tmpRulesGlobal, file);
323
+ const stat = await fs.stat(srcPath);
324
+ if (!stat.isFile()) continue;
325
+ // .cursor/rules: 확장자 mdc, 헤더 추가 (alwaysApply: true)
326
+ const base = path.parse(file).name;
327
+ const cursorTarget = path.join(cursorRulesDir, base + '.mdc');
328
+ const content = await fs.readFile(srcPath, 'utf8');
329
+ const header = '---\ndescription: \nglobs: \nalwaysApply: true\n---\n';
330
+ await fs.writeFile(cursorTarget, header + content, 'utf8');
331
+ logWithSpinner(spinner, `[rules/global] ${file} -> .cursor/rules/${base}.mdc`, debugLog);
332
+ // .windsurf/rules: 확장자 유지, 헤더 없음
333
+ const windsurfTarget = path.join(windsurfRulesDir, file);
334
+ await fs.copyFile(srcPath, windsurfTarget);
335
+ logWithSpinner(spinner, `[rules/global] ${file} -> .windsurf/rules/${file}`, debugLog);
336
+ }
337
+ // 임시 폴더 삭제
338
+ for (const file of files) {
339
+ await fs.unlink(path.join(tmpRulesGlobal, file));
340
+ }
341
+ await fs.rmdir(tmpRulesGlobal);
342
+ }
343
+
344
+ // 3-1. rules/manual -> .cursor/rules(.mdc+헤더), .windsurf/rules(확장자 유지) 복사
345
+ if (tmpRulesManual) {
346
+ const cursorRulesDir = '.cursor/rules';
347
+ const windsurfRulesDir = '.windsurf/rules';
348
+ await fs.mkdir(cursorRulesDir, { recursive: true });
349
+ await fs.mkdir(windsurfRulesDir, { recursive: true });
350
+ const files = await fs.readdir(tmpRulesManual);
351
+ for (const file of files) {
352
+ const srcPath = path.join(tmpRulesManual, file);
353
+ const stat = await fs.stat(srcPath);
354
+ if (!stat.isFile()) continue;
355
+ // .cursor/rules: 확장자 mdc, 헤더 추가 (alwaysApply: false)
356
+ const base = path.parse(file).name;
357
+ const cursorTarget = path.join(cursorRulesDir, base + '.mdc');
358
+ const content = await fs.readFile(srcPath, 'utf8');
359
+ const header = '---\ndescription: \nglobs: \nalwaysApply: false\n---\n';
360
+ await fs.writeFile(cursorTarget, header + content, 'utf8');
361
+ logWithSpinner(spinner, `[rules/manual] ${file} -> .cursor/rules/${base}.mdc`, debugLog);
362
+ // .windsurf/rules: 확장자 유지, 헤더 없음
363
+ const windsurfTarget = path.join(windsurfRulesDir, file);
364
+ await fs.copyFile(srcPath, windsurfTarget);
365
+ logWithSpinner(spinner, `[rules/manual] ${file} -> .windsurf/rules/${file}`, debugLog);
366
+ }
367
+ // 임시 폴더 삭제
368
+ for (const file of files) {
369
+ await fs.unlink(path.join(tmpRulesManual, file));
370
+ }
371
+ await fs.rmdir(tmpRulesManual);
372
+ }
373
+
374
+ if (hasExisting) {
375
+ spinner.succeed(chalk.green('✅ Moonklabs 프레임워크가 성공적으로 업데이트되었습니다!'));
376
+ console.log(chalk.blue('\n🔄 업데이트 내역:'));
377
+ console.log(chalk.gray(' • .claude/commands/moonklabs/ 내의 명령어'));
378
+ console.log(chalk.gray(' • 문서 (CLAUDE.md 파일)'));
379
+ console.log(chalk.green('\n💾 작업 내용은 보존되었습니다:'));
380
+ console.log(chalk.gray(' • 모든 작업, 스프린트, 및 프로젝트 파일이 변경되지 않음'));
381
+ console.log(chalk.gray(' • 백업은 *.bak 파일로 만들어짐'));
382
+ } else {
383
+ spinner.succeed(chalk.green('✅ Moonklabs 프레임워크가 성공적으로 설치되었습니다!'));
384
+ console.log(chalk.blue('\n📁 생성된 구조:'));
385
+ console.log(chalk.gray(' .moonklabs/ - 프로젝트 관리 루트'));
386
+ console.log(chalk.gray(' .claude/commands/ - Claude 사용자 명령어'));
387
+
388
+ console.log(chalk.green('\n🚀 다음 단계:'));
389
+ console.log(chalk.white(' 1. Claude Code에서 이 프로젝트를 엽니다'));
390
+ console.log(chalk.white(' 2. /project:moonklabs 명령어를 사용하여 프로젝트를 관리하세요'));
391
+ console.log(chalk.white(' 3. /project:moonklabs:initialize를 실행하여 프로젝트를 설정하세요\n'));
392
+
393
+ console.log(chalk.blue('\n✨ 시작하려면:'));
394
+ console.log(chalk.gray(' 1. 새 터미널을 열거나 쉘 프로필을 소싱하세요 (예: source ~/.zshrc)'));
395
+ console.log(chalk.gray(` 2. 다음을 실행하세요: ${chalk.cyan('claude')} 를 실행하여 사용 가능한 명령어를 확인하세요.`));
396
+ console.log(chalk.gray('\n자세한 내용은 .moonklabs 디렉토리의 문서를 확인하세요.'));
397
+ }
398
+
399
+ console.log(chalk.green('\nEnjoy Moonklabs! 🚀\n'));
400
+
401
+ } catch (error) {
402
+ if (hasExisting) {
403
+ spinner.fail(chalk.red('업데이트 실패'));
404
+ await restoreFromBackup(spinner);
405
+ } else {
406
+ spinner.fail(chalk.red('설치 실패'));
407
+ }
408
+ console.error(chalk.red(error.message));
409
+ process.exit(1);
410
+ }
411
+ }
412
+
413
+ async function restoreFromBackup(spinner) {
414
+ if (!BACKUP_DIR) {
415
+ // 가장 최근 backup 폴더 사용
416
+ const moonklabsDir = '.moonklabs';
417
+ const dirs = (await fs.readdir(moonklabsDir)).filter(f => f.startsWith('backup_'));
418
+ if (dirs.length === 0) {
419
+ spinner.fail(chalk.red('복원할 백업 폴더가 없습니다.'));
420
+ return;
421
+ }
422
+ dirs.sort();
423
+ BACKUP_DIR = path.join(moonklabsDir, dirs[dirs.length - 1]);
424
+ }
425
+ spinner.start(chalk.yellow('백업에서 복원 중... '));
426
+ try {
427
+ let backupFiles = [];
428
+ try {
429
+ backupFiles = (await fs.readdir(BACKUP_DIR)).filter(f => f.endsWith('.bak'));
430
+ } catch (e) {
431
+ backupFiles = [];
432
+ }
433
+ for (const backup of backupFiles) {
434
+ // 원래 위치 추정: claude-commands-moonklabs-foo.md.bak -> .claude/commands/moonklabs/foo.md
435
+ let originalFile;
436
+ if (backup.startsWith('moonklabs-')) {
437
+ // .moonklabs/ 하위
438
+ const rel = backup.replace(/-/g, '/').replace('.bak', '');
439
+ originalFile = '.' + rel;
440
+ } else if (backup.startsWith('claude-commands-moonklabs-')) {
441
+ // .claude/commands/moonklabs/ 하위
442
+ const rel = backup.replace('claude-commands-moonklabs-', '').replace(/-/g, '/').replace('.bak', '');
443
+ originalFile = path.join('.claude', 'commands', 'moonklabs', rel);
444
+ } else {
445
+ // 기타
446
+ originalFile = backup.replace('.bak', '');
447
+ }
448
+ try {
449
+ await fs.rename(path.join(BACKUP_DIR, backup), originalFile);
450
+ } catch (e) {
451
+ console.warn(chalk.yellow(`'${backup}' 파일을 복원할 수 없습니다. 수동 확인이 필요합니다.`));
452
+ }
453
+ }
454
+ spinner.succeed(chalk.green('성공적으로 복원되었습니다.'));
455
+ } catch (error) {
456
+ spinner.fail(chalk.red('복원에 실패했습니다.'));
457
+ console.error(error);
458
+ }
459
+ }
460
+
461
+ program
462
+ .name('hello-moonklabs')
463
+ .version('1.0.1')
464
+ .description('Moonklabs 프레임워크 설치 프로그램')
465
+ .option('-f, --force', '프롬프트 없이 강제 설치')
466
+ .action((options) => installMoonklabs({ ...options, debugLog: true }));
467
+
468
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "aiwf",
3
+ "version": "0.1.0",
4
+ "description": "AI Workflow Framework for Moonklabs",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "aiwf": "./index.js"
9
+ },
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [
14
+ "moonklabs",
15
+ "ai",
16
+ "workflow",
17
+ "framework"
18
+ ],
19
+ "author": "moonklabs",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/moonklabs/aiwf.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/moonklabs/aiwf/issues"
27
+ },
28
+ "homepage": "https://github.com/moonklabs/aiwf#readme",
29
+ "engines": {
30
+ "node": ">=14.0.0"
31
+ },
32
+ "dependencies": {
33
+ "commander": "^11.1.0",
34
+ "chalk": "^5.3.0",
35
+ "ora": "^8.0.1",
36
+ "prompts": "^2.4.2"
37
+ }
38
+ }
@@ -0,0 +1,30 @@
1
+ # Code Style Guide
2
+
3
+ Detailed code style rules for naming and TypeScript usage.
4
+
5
+ ## Naming Conventions
6
+
7
+ **Do:**
8
+
9
+ - Use descriptive names with auxiliary verbs for booleans (e.g., `isLoading`, `hasError`).
10
+ - Prefix event handlers with "handle" (e.g., `handleClick`, `handleSubmit`).
11
+ - Use lowercase and dashes for directory names (kebab-case) (e.g., `components/todo-list`).
12
+ - Prefer named exports for components and modules.
13
+
14
+ **Don't:**
15
+
16
+ - Don't use ambiguous or single-letter variable names unless their scope is very small and clear (e.g., loop counters).
17
+ - Don't use `snake_case` or `PascalCase` for file or directory names.
18
+
19
+ ## TypeScript Usage
20
+
21
+ **Do:**
22
+
23
+ - Prefer `interface` over `type` for defining object shapes, but use `type` for unions, intersections, or primitives.
24
+ - Use `const` assertions or simple objects instead of `enum`.
25
+ - Use the `satisfies` operator for type validation without changing the underlying type.
26
+
27
+ **Don't:**
28
+
29
+ - Don't use the `any` type unless absolutely necessary and its usage is well-justified.
30
+ - Don't ignore type errors or suppress them with `// @ts-ignore` without a clear explanation.
@@ -0,0 +1,33 @@
1
+ # Coding Principles Guide
2
+
3
+ High-level architectural principles for the project.
4
+
5
+ ## Architecture Principles
6
+
7
+ **Do:**
8
+
9
+ - Apply Clean Architecture principles to create decoupled, testable, and maintainable systems.
10
+ - Follow SOLID principles (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion).
11
+ - Explicitly define architectural boundaries and module responsibilities.
12
+ - Use dependency inversion to decouple high-level and low-level modules.
13
+
14
+ **Don't:**
15
+
16
+ - Don't create tightly coupled modules that are hard to change or test independently.
17
+ - Don't violate core architectural boundaries or mix unrelated concerns.
18
+ - Don't ignore the separation of business logic and infrastructure.
19
+
20
+ ## 2. General Coding Philosophy
21
+
22
+ **Do:**
23
+
24
+ - Write concise, highly readable TypeScript code.
25
+ - Use functional and declarative programming patterns where appropriate.
26
+ - Apply the DRY (Don't Repeat Yourself) principle to avoid code duplication.
27
+ - Use early return statements to improve readability and reduce nesting.
28
+ - Organize components logically: exports, subcomponents, helpers, types.
29
+
30
+ **Don't:**
31
+
32
+ - Don't write overly complex or deeply nested logic.
33
+ - Don't sacrifice clarity for cleverness.
@@ -0,0 +1,41 @@
1
+ # Development Process Guide
2
+
3
+ Advanced development process rules for Git, code review, and prompt engineering.
4
+
5
+ ## Advanced Git Workflow
6
+
7
+ **Do:**
8
+
9
+ - Rebase your feature branches on the main branch before merging to maintain a clean history.
10
+ - Use pull requests for all changes, even minor ones, to ensure peer review.
11
+ - Use conventional commit messages for automated changelogs and CI/CD integration.
12
+
13
+ **Don't:**
14
+
15
+ - Don't create messy commit histories with "fixup" or "wip" messages in the final pull request.
16
+ - Don't merge your own pull requests without at least one other reviewer's approval.
17
+
18
+ ## Code Review Best Practices
19
+
20
+ **Do:**
21
+
22
+ - Provide constructive, actionable feedback focused on code quality, maintainability, and security.
23
+ - Ensure all new code is accompanied by proper documentation and tests.
24
+ - Use checklists for reviewing large or complex pull requests.
25
+
26
+ **Don't:**
27
+
28
+ - Don't approve pull requests that lack sufficient tests or documentation.
29
+ - Don't ignore potential security vulnerabilities or performance bottlenecks.
30
+
31
+ ## Prompt Engineering (AI/LLM Usage)
32
+
33
+ **Do:**
34
+
35
+ - Provide few-shot examples when the task is complex or nuanced.
36
+ - Specify the desired output format, constraints, and structure.
37
+ - Request step-by-step reasoning or a chain of thought for complex problem-solving.
38
+
39
+ **Don't:**
40
+
41
+ - Don't request multiple unrelated tasks in a single prompt.