aiwf 0.1.0 → 0.3.1

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 (163) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/CLAUDE.md +19 -0
  3. package/LICENSE +1 -1
  4. package/README.ko.md +90 -62
  5. package/README.md +109 -62
  6. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/01_PROJECT_DOCS/ARCHITECTURE.md +1 -1
  7. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/M01_Backend_Setup/M01_milestone_meta.md +5 -1
  8. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/M01_Backend_Setup/PRD_AMEND_01_Auth_Flow_Update.md +5 -1
  9. package/claude-code/{simone/.simone/03_SPRINTS/CLAUDE.MD → aiwf/en/.aiwf/03_SPRINTS/CLAUDE.md} +28 -7
  10. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/03_SPRINTS/S01_M01_Initial_API/S01_sprint_meta.md +8 -1
  11. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/03_SPRINTS/S01_M01_Initial_API/T01_S01_Setup_Project_Structure.md +5 -1
  12. package/claude-code/aiwf/en/.aiwf/04_GENERAL_TASKS/CLAUDE.md +96 -0
  13. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/04_GENERAL_TASKS/T002_API_Rate_Limiting.md +7 -2
  14. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/04_GENERAL_TASKS/TX001_Refactor_Logging_Module.md +7 -2
  15. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/05_ARCHITECTURAL_DECISIONS/ADR001_Chosen_Database_System.md +7 -1
  16. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/05_ARCHITECTURAL_DECISIONS/ADR002_API_Authentication_Method.md +7 -1
  17. package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-12-12-00-needs-focus.md +139 -0
  18. package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-12-12-00-test-alignment.md +155 -0
  19. package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-13-00-50-solid-progress.md +122 -0
  20. package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-14-08-30-infrastructure-challenges.md +141 -0
  21. package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-16-21-48-critical-foundation-issues.md +177 -0
  22. package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-17-15-08-solid-progress.md +149 -0
  23. package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/index.json +5 -0
  24. package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/sync-index.js +78 -0
  25. package/claude-code/aiwf/en/.aiwf/98_PROMPTS/github_integration.md +130 -0
  26. package/claude-code/aiwf/en/.aiwf/98_PROMPTS/useful-prompts.md +31 -0
  27. package/claude-code/aiwf/en/.aiwf/98_PROMPTS/vibe-front-prompts.md +89 -0
  28. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/99_TEMPLATES/task_template.md +1 -0
  29. package/claude-code/{simone/.simone/CLAUDE.MD → aiwf/en/.aiwf/CLAUDE.md} +13 -4
  30. package/claude-code/{simone/.simone → aiwf/en/.aiwf}/README.md +6 -6
  31. package/claude-code/aiwf/en/.claude/CLAUDE_BE.md +276 -0
  32. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_changelog.md +82 -0
  33. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_code_review.md +88 -0
  34. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_commit.md +160 -0
  35. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_general_task.md +147 -0
  36. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_milestone_plan.md +194 -0
  37. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_prd.md +272 -0
  38. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_sprint_tasks.md +189 -0
  39. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_sprints_from_milestone.md +121 -0
  40. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_discuss_review.md +29 -0
  41. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_do_task.md +109 -0
  42. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_docs.md +225 -0
  43. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_infinite.md +202 -0
  44. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_initialize.md +134 -0
  45. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_issue_create.md +65 -0
  46. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_language_manager.md +287 -0
  47. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_language_status.md +246 -0
  48. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_mermaid.md +272 -0
  49. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_pr_create.md +76 -0
  50. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_prime.md +9 -0
  51. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_project_review.md +261 -0
  52. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_switch_language.md +160 -0
  53. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_test.md +125 -0
  54. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_testing_review.md +198 -0
  55. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_tm-run-all-subtask.md +206 -0
  56. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_ultrathink_code_advanced.md +460 -0
  57. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_ultrathink_code_basic.md +165 -0
  58. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_ultrathink_general.md +362 -0
  59. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_yolo.md +300 -0
  60. package/claude-code/aiwf/en/.claude/mcp.json +31 -0
  61. package/claude-code/aiwf/en/.claude/settings.json +102 -0
  62. package/claude-code/aiwf/ko/.aiwf/00_PROJECT_MANIFEST.md +49 -0
  63. package/claude-code/aiwf/ko/.aiwf/01_PROJECT_DOCS/ARCHITECTURE.md +55 -0
  64. package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/CLAUDE.md +78 -0
  65. package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/M01_Backend_Setup/M01_milestone_meta.md +42 -0
  66. package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/M01_Backend_Setup/PRD_AMEND_01_Auth_Flow_Update.md +73 -0
  67. package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/M01_Backend_Setup/PRD_Backend_Setup.md +98 -0
  68. package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/M01_Backend_Setup/SPECS_API_V1.md +232 -0
  69. package/claude-code/aiwf/ko/.aiwf/03_SPRINTS/CLAUDE.md +83 -0
  70. package/claude-code/aiwf/ko/.aiwf/03_SPRINTS/S01_M01_Initial_API/S01_sprint_meta.md +49 -0
  71. package/claude-code/aiwf/ko/.aiwf/03_SPRINTS/S01_M01_Initial_API/T01_S01_Setup_Project_Structure.md +60 -0
  72. package/claude-code/aiwf/ko/.aiwf/04_GENERAL_TASKS/CLAUDE.md +96 -0
  73. package/claude-code/aiwf/ko/.aiwf/04_GENERAL_TASKS/T002_API_Rate_Limiting.md +54 -0
  74. package/claude-code/aiwf/ko/.aiwf/04_GENERAL_TASKS/TX001_Refactor_Logging_Module.md +58 -0
  75. package/claude-code/aiwf/ko/.aiwf/05_ARCHITECTURAL_DECISIONS/ADR001_Chosen_Database_System.md +119 -0
  76. package/claude-code/aiwf/ko/.aiwf/05_ARCHITECTURAL_DECISIONS/ADR002_API_Authentication_Method.md +124 -0
  77. package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-12-12-00-needs-focus.md +139 -0
  78. package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-12-12-00-test-alignment.md +155 -0
  79. package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-13-00-50-solid-progress.md +122 -0
  80. package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-14-08-30-infrastructure-challenges.md +141 -0
  81. package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-16-21-48-critical-foundation-issues.md +177 -0
  82. package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-17-15-08-solid-progress.md +149 -0
  83. package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/index.json +5 -0
  84. package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/sync-index.js +78 -0
  85. package/claude-code/aiwf/ko/.aiwf/98_PROMPTS/github_integration.md +130 -0
  86. package/claude-code/aiwf/ko/.aiwf/98_PROMPTS/useful-prompts.md +31 -0
  87. package/claude-code/aiwf/ko/.aiwf/98_PROMPTS/vibe-front-prompts.md +89 -0
  88. package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/adr_template.md +49 -0
  89. package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/milestone_meta_template.md +25 -0
  90. package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/project_manifest_template.md +39 -0
  91. package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/sprint_meta_template.md +23 -0
  92. package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/task_template.md +36 -0
  93. package/claude-code/aiwf/ko/.aiwf/CLAUDE.md +76 -0
  94. package/claude-code/aiwf/ko/.aiwf/README.md +97 -0
  95. package/claude-code/aiwf/ko/.claude/CLAUDE_BE.md +276 -0
  96. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_changelog.md +82 -0
  97. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_code_review.md +88 -0
  98. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_commit.md +160 -0
  99. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_general_task.md +147 -0
  100. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_milestone_plan.md +194 -0
  101. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_prd.md +280 -0
  102. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_sprint_tasks.md +189 -0
  103. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_sprints_from_milestone.md +121 -0
  104. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_discuss_review.md +29 -0
  105. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_do_task.md +109 -0
  106. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_docs.md +225 -0
  107. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_infinite.md +202 -0
  108. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_initialize.md +134 -0
  109. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_issue_create.md +65 -0
  110. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_language_manager.md +287 -0
  111. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_language_status.md +246 -0
  112. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_mermaid.md +272 -0
  113. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_pr_create.md +76 -0
  114. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_prime.md +9 -0
  115. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_project_review.md +261 -0
  116. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_switch_language.md +160 -0
  117. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_test.md +124 -0
  118. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_testing_review.md +198 -0
  119. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_tm-run-all-subtask.md +210 -0
  120. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_ultrathink_code_advanced.md +460 -0
  121. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_ultrathink_code_basic.md +165 -0
  122. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_ultrathink_general.md +305 -0
  123. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_yolo.md +300 -0
  124. package/claude-code/aiwf/ko/.claude/mcp.json +31 -0
  125. package/claude-code/aiwf/ko/.claude/settings.json +102 -0
  126. package/{AI-WORKFLOW.md → docs/AI-WORKFLOW.ko.md} +38 -24
  127. package/docs/AI-WORKFLOW.md +299 -0
  128. package/docs/COMMANDS_GUIDE.ko.md +727 -0
  129. package/docs/COMMANDS_GUIDE.md +726 -0
  130. package/docs/CONTRIBUTING.md +406 -0
  131. package/docs/DEVELOPMENT_GUIDE.md +727 -0
  132. package/docs/Enhanced_Installation_Flow_Design.md +498 -0
  133. package/docs/PRD.ko.md +148 -0
  134. package/docs/PRD.md +150 -0
  135. package/docs/moonklabs-metadata-system-prd.md +127 -0
  136. package/index.js +1493 -122
  137. package/jest.config.js +4 -0
  138. package/language-cli.js +279 -0
  139. package/language-utils.js +330 -0
  140. package/package.json +23 -10
  141. package/scripts/validate-commands.cjs +338 -0
  142. package/scripts/validate-commands.js +254 -0
  143. package/tests/basic.test.js +62 -0
  144. package/tests/commands.test.js +127 -0
  145. package/tests/installer.test.js +129 -0
  146. package/tests/language-utils.test.js +271 -0
  147. package/COMMANDS_GUIDE.md +0 -462
  148. package/PRD.ko.md +0 -96
  149. package/PRD.md +0 -98
  150. package/claude-code/simone/.simone/04_GENERAL_TASKS/CLAUDE.MD +0 -51
  151. package/claude-code/simone/CHANGELOG.md +0 -71
  152. package/claude-code/simone/LICENSE +0 -21
  153. package/claude-code/simone/README.md +0 -219
  154. package/claude-code/simone/SYNC_GUIDE.md +0 -172
  155. package/claude-code/simone/sync-simone.sh +0 -138
  156. /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/00_PROJECT_MANIFEST.md +0 -0
  157. /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/CLAUDE.md +0 -0
  158. /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/M01_Backend_Setup/PRD_Backend_Setup.md +0 -0
  159. /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/M01_Backend_Setup/SPECS_API_V1.md +0 -0
  160. /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/99_TEMPLATES/adr_template.md +0 -0
  161. /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/99_TEMPLATES/milestone_meta_template.md +0 -0
  162. /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/99_TEMPLATES/project_manifest_template.md +0 -0
  163. /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/99_TEMPLATES/sprint_meta_template.md +0 -0
package/index.js CHANGED
@@ -10,18 +10,26 @@ import { fileURLToPath } from 'url';
10
10
  import https from 'https';
11
11
  import { createWriteStream } from 'fs';
12
12
  import { pipeline } from 'stream/promises';
13
+ import {
14
+ detectLanguage,
15
+ loadUserLanguageConfig,
16
+ saveUserLanguageConfig,
17
+ getInstallationLanguagePath,
18
+ SUPPORTED_LANGUAGES,
19
+ DEFAULT_LANGUAGE
20
+ } from './language-utils.js';
13
21
 
14
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
23
 
16
24
  const program = new Command();
17
25
 
18
26
  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';
27
+ const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/moonklabs/aiwf/main';
28
+ const GITHUB_CONTENT_PREFIX = 'claude-code/aiwf';
21
29
 
22
30
  async function fetchGitHubContent(url) {
23
31
  return new Promise((resolve, reject) => {
24
- https.get(url, { headers: { 'User-Agent': 'hello-moonklabs' } }, (res) => {
32
+ https.get(url, { headers: { 'User-Agent': 'aiwf' } }, (res) => {
25
33
  let data = '';
26
34
  res.on('data', chunk => data += chunk);
27
35
  res.on('end', () => {
@@ -40,7 +48,7 @@ async function fetchGitHubContent(url) {
40
48
 
41
49
  async function downloadFile(url, destPath) {
42
50
  return new Promise((resolve, reject) => {
43
- https.get(url, { headers: { 'User-Agent': 'hello-moonklabs' } }, (response) => {
51
+ https.get(url, { headers: { 'User-Agent': 'aiwf' } }, (response) => {
44
52
  if (response.statusCode !== 200) {
45
53
  response.destroy();
46
54
  reject(new Error(`Failed to download ${url}: ${response.statusCode}`));
@@ -69,10 +77,12 @@ async function getDirectoryStructure(path = '') {
69
77
  return JSON.parse(content);
70
78
  }
71
79
 
80
+ const COMMANDS_DIR = '.claude/commands/aiwf';
81
+
72
82
  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;
83
+ const aiwfExists = await fs.access('.aiwf').then(() => true).catch(() => false);
84
+ const claudeCommandsExists = await fs.access(COMMANDS_DIR).then(() => true).catch(() => false);
85
+ return aiwfExists || claudeCommandsExists;
76
86
  }
77
87
 
78
88
  // 백업 폴더명 생성 함수
@@ -85,7 +95,7 @@ function getBackupDirName() {
85
95
  const h = pad(now.getHours());
86
96
  const min = pad(now.getMinutes());
87
97
  const s = pad(now.getSeconds());
88
- return `.moonklabs/backup_${y}${m}${d}_${h}${min}${s}`;
98
+ return `.aiwf/backup_${y}${m}${d}_${h}${min}${s}`;
89
99
  }
90
100
 
91
101
  let BACKUP_DIR = null;
@@ -97,15 +107,34 @@ async function backupFile(filePath, backupDir) {
97
107
  await fs.mkdir(backupDir, { recursive: true });
98
108
  const backupPath = `${filePath}.bak`;
99
109
  await fs.copyFile(filePath, backupPath);
100
- // 상대경로 보존: .claude/commands/moonklabs/foo.md -> backupDir/claude-commands-moonklabs-foo.md.bak
110
+
111
+ // 도구별 백업 파일 명명 규칙 개선
101
112
  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';
113
+ if (filePath.startsWith('.claude/commands/aiwf/')) {
114
+ // Claude Code 명령어 파일
115
+ const relativePath = filePath.substring('.claude/commands/aiwf/'.length);
116
+ bakFileName = `claude-commands-aiwf-${relativePath.replace(/\//g, '-')}.bak`;
117
+ } else if (filePath.startsWith('.gemini/prompts/aiwf/')) {
118
+ // Gemini CLI 프롬프트 파일
119
+ const relativePath = filePath.substring('.gemini/prompts/aiwf/'.length);
120
+ bakFileName = `gemini-prompts-aiwf-${relativePath.replace(/\//g, '-')}.bak`;
121
+ } else if (filePath.startsWith('.cursor/rules/')) {
122
+ // Cursor 규칙 파일
123
+ const relativePath = filePath.substring('.cursor/rules/'.length);
124
+ bakFileName = `cursor-rules-${relativePath.replace(/\//g, '-')}.bak`;
125
+ } else if (filePath.startsWith('.windsurf/rules/')) {
126
+ // Windsurf 규칙 파일
127
+ const relativePath = filePath.substring('.windsurf/rules/'.length);
128
+ bakFileName = `windsurf-rules-${relativePath.replace(/\//g, '-')}.bak`;
129
+ } else if (filePath.startsWith('.aiwf/')) {
130
+ // AIWF 핵심 파일
131
+ const relativePath = filePath.substring('.aiwf/'.length);
132
+ bakFileName = `aiwf-${relativePath.replace(/\//g, '-')}.bak`;
106
133
  } else {
134
+ // 기타 파일
107
135
  bakFileName = path.basename(filePath) + '.bak';
108
136
  }
137
+
109
138
  const destPath = path.join(backupDir, bakFileName);
110
139
  await fs.rename(backupPath, destPath);
111
140
  return destPath;
@@ -116,19 +145,20 @@ async function backupFile(filePath, backupDir) {
116
145
  return null;
117
146
  }
118
147
 
119
- async function backupCommandsAndDocs() {
148
+ async function backupCommandsAndDocs(msg) {
120
149
  if (!BACKUP_DIR) BACKUP_DIR = getBackupDirName();
121
- const spinner = ora('기존 명령어 및 문서 백업 중...').start();
150
+ const spinner = ora(msg.backingUp).start();
122
151
  const backedUpFiles = [];
123
152
  await fs.mkdir(BACKUP_DIR, { recursive: true });
124
153
  try {
125
154
  // Files that will be updated and need backup
126
155
  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'
156
+ '.aiwf/CLAUDE.md',
157
+ '.aiwf/02_REQUIREMENTS/CLAUDE.md',
158
+ '.aiwf/03_SPRINTS/CLAUDE.md',
159
+ '.aiwf/04_GENERAL_TASKS/CLAUDE.md'
131
160
  ];
161
+
132
162
  // Backup CLAUDE.md files
133
163
  for (const file of filesToBackup) {
134
164
  const backupPath = await backupFile(file, BACKUP_DIR);
@@ -136,14 +166,14 @@ async function backupCommandsAndDocs() {
136
166
  backedUpFiles.push(backupPath);
137
167
  }
138
168
  }
169
+
139
170
  // Backup all command files
140
- const commandsDir = '.claude/commands/moonklabs';
141
- const commandsExist = await fs.access(commandsDir).then(() => true).catch(() => false);
171
+ const commandsExist = await fs.access(COMMANDS_DIR).then(() => true).catch(() => false);
142
172
  if (commandsExist) {
143
173
  try {
144
- const commandFiles = await fs.readdir(commandsDir);
174
+ const commandFiles = await fs.readdir(COMMANDS_DIR);
145
175
  for (const file of commandFiles) {
146
- const filePath = path.join(commandsDir, file);
176
+ const filePath = path.join(COMMANDS_DIR, file);
147
177
  const stat = await fs.stat(filePath);
148
178
  if (stat.isFile() && file.endsWith('.md')) {
149
179
  const backupPath = await backupFile(filePath, BACKUP_DIR);
@@ -156,19 +186,58 @@ async function backupCommandsAndDocs() {
156
186
  // Commands directory might be empty or have issues
157
187
  }
158
188
  }
189
+
190
+ // Backup IDE rule files (Cursor)
191
+ const cursorRulesExist = await fs.access('.cursor/rules').then(() => true).catch(() => false);
192
+ if (cursorRulesExist) {
193
+ try {
194
+ const cursorFiles = await fs.readdir('.cursor/rules');
195
+ for (const file of cursorFiles) {
196
+ if (file.endsWith('.mdc')) {
197
+ const filePath = path.join('.cursor/rules', file);
198
+ const backupPath = await backupFile(filePath, BACKUP_DIR);
199
+ if (backupPath) {
200
+ backedUpFiles.push(backupPath);
201
+ }
202
+ }
203
+ }
204
+ } catch (error) {
205
+ // Cursor rules directory might have issues
206
+ }
207
+ }
208
+
209
+ // Backup IDE rule files (Windsurf)
210
+ const windsurfRulesExist = await fs.access('.windsurf/rules').then(() => true).catch(() => false);
211
+ if (windsurfRulesExist) {
212
+ try {
213
+ const windsurfFiles = await fs.readdir('.windsurf/rules');
214
+ for (const file of windsurfFiles) {
215
+ if (file.endsWith('.md')) {
216
+ const filePath = path.join('.windsurf/rules', file);
217
+ const backupPath = await backupFile(filePath, BACKUP_DIR);
218
+ if (backupPath) {
219
+ backedUpFiles.push(backupPath);
220
+ }
221
+ }
222
+ }
223
+ } catch (error) {
224
+ // Windsurf rules directory might have issues
225
+ }
226
+ }
227
+
159
228
  if (backedUpFiles.length > 0) {
160
- spinner.succeed(chalk.green(`${backedUpFiles.length}개 파일 백업 완료 (*.bak)`));
229
+ spinner.succeed(chalk.green(msg.backupComplete.replace('{count}', backedUpFiles.length)));
161
230
  } else {
162
- spinner.succeed(chalk.gray('백업할 기존 파일이 없습니다'));
231
+ spinner.succeed(chalk.gray(msg.noFilesToBackup));
163
232
  }
164
233
  return backedUpFiles;
165
234
  } catch (error) {
166
- spinner.fail(chalk.red('백업 실패'));
235
+ spinner.fail(chalk.red(msg.backupFailed));
167
236
  throw error;
168
237
  }
169
238
  }
170
239
 
171
- async function downloadDirectory(githubPath, localPath, spinner) {
240
+ async function downloadDirectory(githubPath, localPath, spinner, msg) {
172
241
  await fs.mkdir(localPath, { recursive: true });
173
242
 
174
243
  const items = await getDirectoryStructure(githubPath);
@@ -177,9 +246,9 @@ async function downloadDirectory(githubPath, localPath, spinner) {
177
246
  const itemLocalPath = path.join(localPath, item.name);
178
247
 
179
248
  if (item.type === 'dir') {
180
- await downloadDirectory(item.path, itemLocalPath, spinner);
249
+ await downloadDirectory(item.path, itemLocalPath, spinner, msg);
181
250
  } else if (item.type === 'file') {
182
- spinner.text = `${item.path} 다운로드 중...`;
251
+ spinner.text = msg.downloadingFile.replace('{path}', item.path);
183
252
  await downloadFile(item.download_url, itemLocalPath);
184
253
  }
185
254
  }
@@ -191,11 +260,1258 @@ function logWithSpinner(spinner, message, debugLog) {
191
260
  if (spinner) spinner.text = message;
192
261
  }
193
262
 
194
- async function installMoonklabs(options = {}) {
263
+ // 언어별 메시지 정의
264
+ const messages = {
265
+ ko: {
266
+ welcome: '\n🎉 AIWF에 오신 것을 환영합니다!\n',
267
+ description: '이 설치 프로그램은 AIWF AI 프롬프트 프레임워크를 설정합니다',
268
+ optimized: '특별히 Claude Code 에 최적화 되어있습니다.\n',
269
+ selectLanguage: 'Please select language / 언어를 선택해주세요:',
270
+ korean: '한국어 (Korean)',
271
+ english: 'English',
272
+ existingDetected: '기존 AIWF 설치가 감지되었습니다. 무엇을 하시겠습니까?',
273
+ updateOption: '업데이트 (명령어와 문서만 업데이트하고 작업 내용은 보존)',
274
+ skipOption: '설치 건너뛰기',
275
+ cancelOption: '취소',
276
+ installCancelled: '\n설치가 취소되었습니다.',
277
+ backingUp: '기존 명령어 및 문서 백업 중...',
278
+ fetching: 'GitHub에서 AIWF 프레임워크를 가져오는 중...',
279
+ downloading: 'AIWF 프레임워크 파일을 다운로드하는 중...',
280
+ updatingDocs: '문서를 업데이트하는 중...',
281
+ downloadingCommands: 'AIWF 명령어를 새로 다운로드하는 중...',
282
+ updateSuccess: '✅ AIWF 프레임워크가 성공적으로 업데이트되었습니다!',
283
+ installSuccess: '✅ AIWF 프레임워크가 성공적으로 설치되었습니다!',
284
+ enjoy: '\nEnjoy AIWF! 🚀\n',
285
+ backupComplete: '{count}개 파일 백업 완료 (*.bak)',
286
+ noFilesToBackup: '백업할 기존 파일이 없습니다',
287
+ backupFailed: '백업 실패',
288
+ downloadingFile: '{path} 다운로드 중...',
289
+ downloadingTemplates: '템플릿 디렉토리를 다운로드 중...',
290
+ templatesNotFound: '템플릿 디렉토리를 찾을 수 없어 건너뜁니다...',
291
+ deletingOldCommands: '기존 AIWF 명령어 폴더를 삭제하는 중...',
292
+ commandsNotFound: '명령어 디렉토리를 찾을 수 없어 건너뜁니다...',
293
+ downloadingRulesGlobal: 'rules/global 폴더를 다운로드 중...',
294
+ rulesGlobalNotFound: 'rules/global 폴더를 찾을 수 없어 건너뜁니다...',
295
+ downloadingRulesManual: 'rules/manual 폴더를 다운로드 중...',
296
+ rulesManualNotFound: 'rules/manual 폴더를 찾을 수 없어 건너뜁니다...',
297
+ updateHistory: '🔄 업데이트 내역:',
298
+ updatedCommands: '내의 명령어',
299
+ updatedDocs: '문서 (CLAUDE.md 파일)',
300
+ workPreserved: '💾 작업 내용은 보존되었습니다:',
301
+ allFilesPreserved: '모든 작업, 스프린트, 및 프로젝트 파일이 변경되지 않음',
302
+ backupCreated: '백업은 *.bak 파일로 만들어짐',
303
+ structureCreated: '📁 생성된 구조:',
304
+ aiwfRoot: '프로젝트 관리 루트',
305
+ claudeCommands: 'Claude 사용자 명령어',
306
+ nextSteps: '🚀 다음 단계:',
307
+ nextStep1: 'Claude Code에서 이 프로젝트를 엽니다',
308
+ nextStep2: '/aiwf_<command> 명령어를 사용하여 프로젝트를 관리하세요',
309
+ nextStep3: '/aiwf_initialize를 실행하여 프로젝트를 설정하세요',
310
+ gettingStarted: '✨ 시작하려면:',
311
+ startStep1: '새 터미널을 열거나 쉘 프로필을 소싱하세요 (예: source ~/.zshrc)',
312
+ startStep2: '다음을 실행하세요: claude 를 실행하여 사용 가능한 명령어를 확인하세요.',
313
+ checkDocs: '자세한 내용은 .aiwf 디렉토리의 문서를 확인하세요.',
314
+ updateFailed: '업데이트 실패',
315
+ installFailed: '설치 실패',
316
+ noBackupFound: '복원할 백업 폴더가 없습니다.',
317
+ restoringFromBackup: '백업에서 복원 중...',
318
+ restoreSuccess: '성공적으로 복원되었습니다.',
319
+ restoreFailed: '복원에 실패했습니다.',
320
+ cannotRestore: '파일을 복원할 수 없습니다. 수동 확인이 필요합니다.'
321
+ },
322
+ en: {
323
+ welcome: '\n🎉 Welcome to AIWF!\n',
324
+ description: 'This installer sets up the AIWF AI prompt framework',
325
+ optimized: 'Specially optimized for Claude Code.\n',
326
+ selectLanguage: 'Please select language / 언어를 선택해주세요:',
327
+ korean: '한국어 (Korean)',
328
+ english: 'English',
329
+ existingDetected: 'Existing AIWF installation detected. What would you like to do?',
330
+ updateOption: 'Update (Update commands and docs only, preserve work content)',
331
+ skipOption: 'Skip installation',
332
+ cancelOption: 'Cancel',
333
+ installCancelled: '\nInstallation cancelled.',
334
+ backingUp: 'Backing up existing commands and documents...',
335
+ fetching: 'Fetching AIWF framework from GitHub...',
336
+ downloading: 'Downloading AIWF framework files...',
337
+ updatingDocs: 'Updating documentation...',
338
+ downloadingCommands: 'Downloading AIWF commands...',
339
+ updateSuccess: '✅ AIWF framework updated successfully!',
340
+ installSuccess: '✅ AIWF framework installed successfully!',
341
+ enjoy: '\nEnjoy AIWF! 🚀\n',
342
+ backupComplete: '{count} files backed up (*.bak)',
343
+ noFilesToBackup: 'No existing files to backup',
344
+ backupFailed: 'Backup failed',
345
+ downloadingFile: 'Downloading {path}...',
346
+ downloadingTemplates: 'Downloading template directories...',
347
+ templatesNotFound: 'Template directories not found, skipping...',
348
+ deletingOldCommands: 'Deleting existing AIWF commands folder...',
349
+ commandsNotFound: 'Commands directory not found, skipping...',
350
+ downloadingRulesGlobal: 'Downloading rules/global folder...',
351
+ rulesGlobalNotFound: 'rules/global folder not found, skipping...',
352
+ downloadingRulesManual: 'Downloading rules/manual folder...',
353
+ rulesManualNotFound: 'rules/manual folder not found, skipping...',
354
+ updateHistory: '🔄 Update History:',
355
+ updatedCommands: '/ commands',
356
+ updatedDocs: 'Documentation (CLAUDE.md files)',
357
+ workPreserved: '💾 Work preserved:',
358
+ allFilesPreserved: 'All work, sprints, and project files unchanged',
359
+ backupCreated: 'Backups created as *.bak files',
360
+ structureCreated: '📁 Structure created:',
361
+ aiwfRoot: 'Project management root',
362
+ claudeCommands: 'Claude user commands',
363
+ nextSteps: '🚀 Next steps:',
364
+ nextStep1: 'Open this project in Claude Code',
365
+ nextStep2: 'Use /aiwf_<command> commands to manage your project',
366
+ nextStep3: 'Run /aiwf_initialize to set up your project',
367
+ gettingStarted: '✨ Getting started:',
368
+ startStep1: 'Open a new terminal or source your shell profile (e.g. source ~/.zshrc)',
369
+ startStep2: 'Run: claude to see available commands.',
370
+ checkDocs: 'For more details, check the documentation in the .aiwf directory.',
371
+ updateFailed: 'Update failed',
372
+ installFailed: 'Installation failed',
373
+ noBackupFound: 'No backup folder found to restore.',
374
+ restoringFromBackup: 'Restoring from backup...',
375
+ restoreSuccess: 'Successfully restored.',
376
+ restoreFailed: 'Restore failed.',
377
+ cannotRestore: 'Cannot restore file. Manual verification required.'
378
+ }
379
+ };
380
+
381
+ // =============================================================================
382
+ // INSTALLATION VALIDATION SYSTEM
383
+ // =============================================================================
384
+
385
+ // 각 도구별 필수 파일 목록 정의
386
+ const ESSENTIAL_FILES = {
387
+ claudeCode: {
388
+ baseDir: '.claude/commands/aiwf',
389
+ requiredFiles: [
390
+ 'aiwf_initialize.md',
391
+ 'aiwf_do_task.md',
392
+ 'aiwf_commit.md',
393
+ 'aiwf_create_sprint_tasks.md',
394
+ 'aiwf_code_review.md'
395
+ ],
396
+ minFileCount: 5,
397
+ description: 'Claude Code Commands'
398
+ },
399
+ geminiCLI: {
400
+ baseDir: '.gemini/prompts/aiwf',
401
+ requiredFiles: [
402
+ 'README.md'
403
+ ],
404
+ minFileCount: 1,
405
+ description: 'Gemini-CLI Prompts'
406
+ },
407
+ cursor: {
408
+ baseDir: '.cursor/rules',
409
+ requiredFiles: [],
410
+ minFileCount: 2,
411
+ fileExtension: '.mdc',
412
+ description: 'Cursor IDE Rules'
413
+ },
414
+ windsurf: {
415
+ baseDir: '.windsurf/rules',
416
+ requiredFiles: [],
417
+ minFileCount: 2,
418
+ fileExtension: '.md',
419
+ description: 'Windsurf IDE Rules'
420
+ },
421
+ aiwf: {
422
+ baseDir: '.aiwf',
423
+ requiredFiles: [
424
+ 'CLAUDE.md',
425
+ '00_PROJECT_MANIFEST.md'
426
+ ],
427
+ minFileCount: 2,
428
+ description: 'AIWF Core Structure'
429
+ }
430
+ };
431
+
432
+ // 범용 검증 함수 (모든 도구에서 사용)
433
+ async function validateToolInstallation(config) {
434
+ const results = {
435
+ success: true,
436
+ description: config.description,
437
+ baseDir: config.baseDir,
438
+ checks: {
439
+ directoryExists: false,
440
+ fileCount: 0,
441
+ requiredFiles: [],
442
+ totalSize: 0,
443
+ issues: []
444
+ }
445
+ };
446
+
447
+ try {
448
+ // 1. 디렉토리 존재 확인
449
+ await fs.access(config.baseDir);
450
+ const dirStat = await fs.stat(config.baseDir);
451
+ if (dirStat.isDirectory()) {
452
+ results.checks.directoryExists = true;
453
+ } else {
454
+ results.checks.issues.push(`${config.baseDir} is not a directory`);
455
+ results.success = false;
456
+ return results;
457
+ }
458
+
459
+ // 2. 파일 목록 가져오기
460
+ const actualFiles = await fs.readdir(config.baseDir);
461
+ results.checks.fileCount = actualFiles.length;
462
+
463
+ // 3. 필수 파일 존재 확인
464
+ for (const requiredFile of config.requiredFiles || []) {
465
+ const filePath = path.join(config.baseDir, requiredFile);
466
+ const fileCheck = {
467
+ file: requiredFile,
468
+ exists: false,
469
+ size: 0,
470
+ valid: false
471
+ };
472
+
473
+ if (actualFiles.includes(requiredFile)) {
474
+ fileCheck.exists = true;
475
+ try {
476
+ const stats = await fs.stat(filePath);
477
+ fileCheck.size = stats.size;
478
+ results.checks.totalSize += stats.size;
479
+
480
+ // 파일 크기 검증 (최소 10바이트)
481
+ if (stats.size < 10) {
482
+ results.checks.issues.push(`File ${requiredFile} is too small (${stats.size} bytes)`);
483
+ results.success = false;
484
+ } else {
485
+ fileCheck.valid = true;
486
+ }
487
+ } catch (error) {
488
+ results.checks.issues.push(`Cannot read stats for ${requiredFile}: ${error.message}`);
489
+ results.success = false;
490
+ }
491
+ } else {
492
+ results.checks.issues.push(`Required file ${requiredFile} is missing`);
493
+ results.success = false;
494
+ }
495
+
496
+ results.checks.requiredFiles.push(fileCheck);
497
+ }
498
+
499
+ // 4. 파일 확장자 기반 검증 (Cursor/Windsurf)
500
+ if (config.fileExtension) {
501
+ const matchingFiles = actualFiles.filter(f => f.endsWith(config.fileExtension));
502
+ if (matchingFiles.length < config.minFileCount) {
503
+ results.checks.issues.push(`Expected at least ${config.minFileCount} ${config.fileExtension} files, found ${matchingFiles.length}`);
504
+ results.success = false;
505
+ }
506
+
507
+ // 확장자별 파일 크기 검증
508
+ for (const file of matchingFiles) {
509
+ const filePath = path.join(config.baseDir, file);
510
+ try {
511
+ const stats = await fs.stat(filePath);
512
+ results.checks.totalSize += stats.size;
513
+
514
+ if (stats.size < 50) { // Rules 파일은 최소 50바이트
515
+ results.checks.issues.push(`Rules file ${file} is too small (${stats.size} bytes)`);
516
+ results.success = false;
517
+ }
518
+ } catch (error) {
519
+ results.checks.issues.push(`Cannot read ${file}: ${error.message}`);
520
+ results.success = false;
521
+ }
522
+ }
523
+ }
524
+
525
+ // 5. 최소 파일 개수 검증
526
+ if (results.checks.fileCount < config.minFileCount) {
527
+ results.checks.issues.push(`Expected at least ${config.minFileCount} files, found ${results.checks.fileCount}`);
528
+ results.success = false;
529
+ }
530
+
531
+ } catch (error) {
532
+ results.success = false;
533
+ results.checks.issues.push(`Validation error: ${error.message}`);
534
+ }
535
+
536
+ return results;
537
+ }
538
+
539
+ // 개별 도구 검증 함수들
540
+ async function validateClaudeInstall() {
541
+ return await validateToolInstallation(ESSENTIAL_FILES.claudeCode);
542
+ }
543
+
544
+ async function validateGeminiInstall() {
545
+ return await validateToolInstallation(ESSENTIAL_FILES.geminiCLI);
546
+ }
547
+
548
+ async function validateCursorInstall() {
549
+ return await validateToolInstallation(ESSENTIAL_FILES.cursor);
550
+ }
551
+
552
+ async function validateWindsurfInstall() {
553
+ return await validateToolInstallation(ESSENTIAL_FILES.windsurf);
554
+ }
555
+
556
+ async function validateAIWFCore() {
557
+ return await validateToolInstallation(ESSENTIAL_FILES.aiwf);
558
+ }
559
+
560
+ // Legacy detailed validation (for backward compatibility)
561
+ async function validateInstallationDetailed() {
562
+ const validationResults = {
563
+ overall: {
564
+ success: true,
565
+ toolsChecked: 0,
566
+ toolsPassed: 0,
567
+ toolsFailed: 0,
568
+ totalIssues: 0
569
+ },
570
+ tools: {}
571
+ };
572
+
573
+ // AIWF 코어는 항상 검증
574
+ const aiwfResult = await validateAIWFCore();
575
+ validationResults.tools.aiwf = aiwfResult;
576
+ validationResults.overall.toolsChecked++;
577
+
578
+ if (aiwfResult.success) {
579
+ validationResults.overall.toolsPassed++;
580
+ } else {
581
+ validationResults.overall.toolsFailed++;
582
+ validationResults.overall.success = false;
583
+ validationResults.overall.totalIssues += aiwfResult.checks.issues.length;
584
+ }
585
+
586
+ // 모든 도구 검증 (이 간단한 버전에서는 항상 모든 도구 설치)
587
+ const toolValidators = {
588
+ claudeCode: validateClaudeInstall,
589
+ geminiCLI: validateGeminiInstall,
590
+ cursor: validateCursorInstall,
591
+ windsurf: validateWindsurfInstall
592
+ };
593
+
594
+ for (const [toolName, validator] of Object.entries(toolValidators)) {
595
+ const result = await validator();
596
+ validationResults.tools[toolName] = result;
597
+ validationResults.overall.toolsChecked++;
598
+
599
+ if (result.success) {
600
+ validationResults.overall.toolsPassed++;
601
+ } else {
602
+ validationResults.overall.toolsFailed++;
603
+ validationResults.overall.success = false;
604
+ validationResults.overall.totalIssues += result.checks.issues.length;
605
+ }
606
+ }
607
+
608
+ return validationResults;
609
+ }
610
+
611
+ // 향상된 설치 검증 함수 (선택적 도구 지원)
612
+ async function validateInstallationEnhanced(selectedTools = null, language = 'en') {
613
+ const validationResults = {
614
+ overall: {
615
+ success: true,
616
+ toolsChecked: 0,
617
+ toolsPassed: 0,
618
+ toolsFailed: 0,
619
+ totalIssues: 0
620
+ },
621
+ tools: {}
622
+ };
623
+
624
+ // AIWF 코어는 항상 검증
625
+ const aiwfResult = await validateAIWFCore();
626
+ validationResults.tools.aiwf = aiwfResult;
627
+ validationResults.overall.toolsChecked++;
628
+
629
+ if (aiwfResult.success) {
630
+ validationResults.overall.toolsPassed++;
631
+ } else {
632
+ validationResults.overall.toolsFailed++;
633
+ validationResults.overall.success = false;
634
+ validationResults.overall.totalIssues += aiwfResult.checks.issues.length;
635
+ }
636
+
637
+ // 도구 목록 결정 (선택된 도구 또는 모든 도구)
638
+ const toolsToValidate = selectedTools || ['claudeCode', 'geminiCLI', 'cursor', 'windsurf'];
639
+
640
+ const toolValidators = {
641
+ claudeCode: validateClaudeInstall,
642
+ geminiCLI: validateGeminiInstall,
643
+ cursor: validateCursorInstall,
644
+ windsurf: validateWindsurfInstall
645
+ };
646
+
647
+ for (const toolName of toolsToValidate) {
648
+ if (toolValidators[toolName]) {
649
+ const result = await toolValidators[toolName]();
650
+ validationResults.tools[toolName] = result;
651
+ validationResults.overall.toolsChecked++;
652
+
653
+ if (result.success) {
654
+ validationResults.overall.toolsPassed++;
655
+ } else {
656
+ validationResults.overall.toolsFailed++;
657
+ validationResults.overall.success = false;
658
+ validationResults.overall.totalIssues += result.checks.issues.length;
659
+ }
660
+ }
661
+ }
662
+
663
+ return validationResults;
664
+ }
665
+
666
+ // Display validation results for specification-compliant format
667
+ function displaySpecCompliantValidationResults(validationResults, language) {
668
+ const msg = messages[language];
669
+
670
+ console.log(chalk.blue('\n=== Installation Validation Results ==='));
671
+
672
+ // Success summary
673
+ if (validationResults.success.length > 0) {
674
+ console.log(chalk.green(`\n✅ Successfully Validated Tools (${validationResults.success.length}):`));
675
+ validationResults.success.forEach(tool => {
676
+ console.log(chalk.green(` ✓ ${tool}`));
677
+ });
678
+ }
679
+
680
+ // Failed summary
681
+ if (validationResults.failed.length > 0) {
682
+ console.log(chalk.red(`\n❌ Failed Validations (${validationResults.failed.length}):`));
683
+ validationResults.failed.forEach(({tool, reason}) => {
684
+ console.log(chalk.red(` ✗ ${tool}: ${reason}`));
685
+ });
686
+ }
687
+
688
+ // Warnings summary
689
+ if (validationResults.warnings.length > 0) {
690
+ console.log(chalk.yellow(`\n⚠️ Warnings (${validationResults.warnings.length}):`));
691
+ validationResults.warnings.forEach(warning => {
692
+ console.log(chalk.yellow(` ⚠ ${warning}`));
693
+ });
694
+ }
695
+
696
+ // Overall status
697
+ const isSuccess = validationResults.failed.length === 0;
698
+ const statusIcon = isSuccess ? '✅' : '❌';
699
+ const statusColor = isSuccess ? chalk.green : chalk.red;
700
+ const statusText = isSuccess ? 'PASSED' : 'FAILED';
701
+
702
+ console.log(statusColor(`\n${statusIcon} Overall Validation: ${statusText}`));
703
+
704
+ if (isSuccess) {
705
+ console.log(chalk.green('🎉 All selected tools are properly installed and validated!'));
706
+ } else {
707
+ console.log(chalk.yellow('⚠️ Some installations failed. See details above.'));
708
+ }
709
+ }
710
+
711
+ // Enhanced validation function with simplified interface for T05_S02
712
+ async function validateInstallation(selectedTools, language) {
713
+ const results = {
714
+ success: [],
715
+ failed: [],
716
+ warnings: []
717
+ };
718
+
719
+ // Always validate common files (AIWF core)
720
+ const commonValid = await validateCommonFiles();
721
+ if (!commonValid.success) {
722
+ results.failed.push({ tool: 'aiwf', reason: commonValid.reason });
723
+ } else {
724
+ results.success.push('aiwf');
725
+ }
726
+
727
+ // Validate selected tools
728
+ if (selectedTools && selectedTools.length > 0) {
729
+ for (const tool of selectedTools) {
730
+ const validation = await validateTool(tool);
731
+ if (validation.success) {
732
+ results.success.push(tool);
733
+ } else {
734
+ results.failed.push({ tool, reason: validation.reason });
735
+ }
736
+ }
737
+ } else {
738
+ // Default: validate all tools if no selection provided
739
+ const allTools = ['claudeCode', 'geminiCLI', 'cursor', 'windsurf'];
740
+ for (const tool of allTools) {
741
+ const validation = await validateTool(tool);
742
+ if (validation.success) {
743
+ results.success.push(tool);
744
+ } else {
745
+ results.failed.push({ tool, reason: validation.reason });
746
+ }
747
+ }
748
+ }
749
+
750
+ return results;
751
+ }
752
+
753
+ // Helper function to validate common/core files
754
+ async function validateCommonFiles() {
755
+ try {
756
+ const requiredFiles = [
757
+ '.aiwf/CLAUDE.md',
758
+ '.aiwf/00_PROJECT_MANIFEST.md'
759
+ ];
760
+
761
+ for (const file of requiredFiles) {
762
+ const exists = await fs.access(file).then(() => true).catch(() => false);
763
+ if (!exists) {
764
+ return { success: false, reason: `Missing core file: ${file}` };
765
+ }
766
+ }
767
+
768
+ return { success: true };
769
+ } catch (error) {
770
+ return { success: false, reason: `Core validation error: ${error.message}` };
771
+ }
772
+ }
773
+
774
+ // Helper function to validate specific tools with simplified interface
775
+ async function validateTool(tool) {
776
+ switch (tool) {
777
+ case 'claudeCode':
778
+ case 'claude-code':
779
+ return validateClaudeCode();
780
+ case 'geminiCLI':
781
+ case 'gemini-cli':
782
+ return validateGeminiCLI();
783
+ case 'cursor':
784
+ return validateCursorTool();
785
+ case 'windsurf':
786
+ return validateWindsurfTool();
787
+ case 'aiwf':
788
+ return validateCommonFiles();
789
+ default:
790
+ return { success: false, reason: `Unknown tool: ${tool}` };
791
+ }
792
+ }
793
+
794
+ // Simplified tool validation functions
795
+ async function validateClaudeCode() {
796
+ try {
797
+ const requiredFiles = [
798
+ '.claude/commands/aiwf/aiwf_initialize.md',
799
+ '.claude/commands/aiwf/aiwf_do_task.md',
800
+ '.claude/commands/aiwf/aiwf_commit.md',
801
+ '.claude/commands/aiwf/aiwf_code_review.md'
802
+ ];
803
+
804
+ for (const file of requiredFiles) {
805
+ const exists = await fs.access(file).then(() => true).catch(() => false);
806
+ if (!exists) {
807
+ return { success: false, reason: `Missing file: ${file}` };
808
+ }
809
+ }
810
+
811
+ return { success: true };
812
+ } catch (error) {
813
+ return { success: false, reason: `Claude Code validation error: ${error.message}` };
814
+ }
815
+ }
816
+
817
+ async function validateGeminiCLI() {
818
+ try {
819
+ // Check if .gemini/prompts/aiwf directory exists
820
+ const dirExists = await fs.access('.gemini/prompts/aiwf').then(() => true).catch(() => false);
821
+ if (!dirExists) {
822
+ return { success: false, reason: 'Missing Gemini-CLI prompts directory: .gemini/prompts/aiwf' };
823
+ }
824
+
825
+ return { success: true };
826
+ } catch (error) {
827
+ return { success: false, reason: `Gemini-CLI validation error: ${error.message}` };
828
+ }
829
+ }
830
+
831
+ async function validateCursorTool() {
832
+ try {
833
+ const dirExists = await fs.access('.cursor/rules').then(() => true).catch(() => false);
834
+ if (!dirExists) {
835
+ return { success: false, reason: 'Missing Cursor rules directory: .cursor/rules' };
836
+ }
837
+
838
+ return { success: true };
839
+ } catch (error) {
840
+ return { success: false, reason: `Cursor validation error: ${error.message}` };
841
+ }
842
+ }
843
+
844
+ async function validateWindsurfTool() {
845
+ try {
846
+ const dirExists = await fs.access('.windsurf/rules').then(() => true).catch(() => false);
847
+ if (!dirExists) {
848
+ return { success: false, reason: 'Missing Windsurf rules directory: .windsurf/rules' };
849
+ }
850
+
851
+ return { success: true };
852
+ } catch (error) {
853
+ return { success: false, reason: `Windsurf validation error: ${error.message}` };
854
+ }
855
+ }
856
+
857
+ // Enhanced rollback function for T05_S02
858
+ async function rollbackToolEnhanced(tool, backupDir, language = 'ko') {
859
+ const spinner = ora(`Rolling back ${tool}...`).start();
860
+
861
+ try {
862
+ // Get tool directory mapping
863
+ const toolDir = getToolDirectory(tool);
864
+ if (!toolDir) {
865
+ spinner.fail(`Unknown tool: ${tool}`);
866
+ return false;
867
+ }
868
+
869
+ // Remove installed directory
870
+ try {
871
+ await fs.access(toolDir);
872
+ await fs.rm(toolDir, { recursive: true, force: true });
873
+ spinner.text = `Removed ${toolDir}`;
874
+ } catch (error) {
875
+ // Directory doesn't exist, skip
876
+ }
877
+
878
+ // Restore from backup if available
879
+ if (backupDir) {
880
+ const backupFiles = await getBackupFilesForTool(tool, backupDir);
881
+ for (const backup of backupFiles) {
882
+ await restoreBackupFile(backup);
883
+ spinner.text = `Restored ${backup.originalPath}`;
884
+ }
885
+ }
886
+
887
+ spinner.succeed(`${tool} rolled back successfully`);
888
+ return true;
889
+ } catch (error) {
890
+ spinner.fail(`Failed to rollback ${tool}: ${error.message}`);
891
+ return false;
892
+ }
893
+ }
894
+
895
+ // Helper function to get tool directories
896
+ function getToolDirectory(tool) {
897
+ const toolDirs = {
898
+ claudeCode: '.claude/commands/aiwf',
899
+ 'claude-code': '.claude/commands/aiwf',
900
+ geminiCLI: '.gemini/prompts/aiwf',
901
+ 'gemini-cli': '.gemini/prompts/aiwf',
902
+ cursor: '.cursor/rules',
903
+ windsurf: '.windsurf/rules',
904
+ aiwf: '.aiwf'
905
+ };
906
+
907
+ return toolDirs[tool] || null;
908
+ }
909
+
910
+ // Helper function to restore backup file
911
+ async function restoreBackupFile(backup) {
912
+ try {
913
+ // Ensure target directory exists
914
+ const targetDir = path.dirname(backup.originalPath);
915
+ await fs.mkdir(targetDir, { recursive: true });
916
+
917
+ // Copy backup file to original location
918
+ await fs.copyFile(backup.backupFile, backup.originalPath);
919
+ } catch (error) {
920
+ throw new Error(`Failed to restore ${backup.originalPath}: ${error.message}`);
921
+ }
922
+ }
923
+
924
+ // Enhanced installation report generation function for T05_S02
925
+ function generateInstallationReportEnhanced(results, language) {
926
+ const msg = messages[language];
927
+
928
+ console.log('\n' + chalk.bold(language === 'ko' ? '📋 설치 리포트' : '📋 Installation Report'));
929
+ console.log('─'.repeat(50));
930
+
931
+ if (results.success.length > 0) {
932
+ console.log(chalk.green(language === 'ko' ? '✅ 성공적으로 설치된 도구:' : '✅ Successfully Installed Tools:'));
933
+ results.success.forEach(tool => {
934
+ console.log(chalk.green(` ✓ ${getToolName(tool, language)}`));
935
+ });
936
+ }
937
+
938
+ if (results.failed.length > 0) {
939
+ console.log(chalk.red(language === 'ko' ? '\n❌ 설치 실패한 도구:' : '\n❌ Failed to Install:'));
940
+ results.failed.forEach(({ tool, reason }) => {
941
+ console.log(chalk.red(` ✗ ${getToolName(tool, language)}: ${reason}`));
942
+ });
943
+ }
944
+
945
+ if (results.warnings.length > 0) {
946
+ console.log(chalk.yellow(language === 'ko' ? '\n⚠️ 경고:' : '\n⚠️ Warnings:'));
947
+ results.warnings.forEach(warning => {
948
+ console.log(chalk.yellow(` ⚠ ${warning}`));
949
+ });
950
+ }
951
+
952
+ console.log('─'.repeat(50));
953
+ }
954
+
955
+ // Helper function to get localized tool names
956
+ function getToolName(tool, language) {
957
+ const toolNames = {
958
+ ko: {
959
+ aiwf: 'AIWF 코어 구조',
960
+ claudeCode: 'Claude Code 명령어',
961
+ 'claude-code': 'Claude Code 명령어',
962
+ geminiCLI: 'Gemini-CLI 프롬프트',
963
+ 'gemini-cli': 'Gemini-CLI 프롬프트',
964
+ cursor: 'Cursor IDE 규칙',
965
+ windsurf: 'Windsurf IDE 규칙'
966
+ },
967
+ en: {
968
+ aiwf: 'AIWF Core Structure',
969
+ claudeCode: 'Claude Code Commands',
970
+ 'claude-code': 'Claude Code Commands',
971
+ geminiCLI: 'Gemini-CLI Prompts',
972
+ 'gemini-cli': 'Gemini-CLI Prompts',
973
+ cursor: 'Cursor IDE Rules',
974
+ windsurf: 'Windsurf IDE Rules'
975
+ }
976
+ };
977
+
978
+ return toolNames[language]?.[tool] || tool;
979
+ }
980
+
981
+ // Enhanced installation flow with validation and rollback for T05_S02
982
+ async function performInstallationStepEnhanced(language, selectedTools) {
983
+ try {
984
+ // ... installation performed by existing code ...
985
+ // This function would be integrated into the main installation flow
986
+
987
+ // After installation, perform validation
988
+ const validation = await validateInstallation(selectedTools, language);
989
+
990
+ // Handle failed tools with rollback
991
+ if (validation.failed.length > 0) {
992
+ console.log(chalk.yellow('\n⚠️ Some installations failed. Initiating rollback...'));
993
+
994
+ for (const { tool } of validation.failed) {
995
+ console.log(chalk.yellow(`Rolling back ${tool}...`));
996
+ await rollbackToolEnhanced(tool, BACKUP_DIR, language);
997
+ }
998
+ }
999
+
1000
+ // Generate installation report
1001
+ generateInstallationReportEnhanced(validation, language);
1002
+
1003
+ // Throw error if complete failure
1004
+ if (validation.success.length === 0) {
1005
+ const errorMsg = language === 'ko' ?
1006
+ '모든 설치가 실패했습니다.' :
1007
+ 'All installations failed.';
1008
+ throw new Error(errorMsg);
1009
+ }
1010
+
1011
+ return validation;
1012
+ } catch (error) {
1013
+ console.error(chalk.red(`Installation step failed: ${error.message}`));
1014
+ throw error;
1015
+ }
1016
+ }
1017
+
1018
+ // 설치 리포트 생성 함수 (통합된 버전)
1019
+ function generateInstallationReport(validationResults, language, installationType = 'install') {
1020
+ const msg = messages[language];
1021
+
1022
+ // 상단 헤더
1023
+ console.log(chalk.blue.bold('\n' + '='.repeat(60)));
1024
+ console.log(chalk.blue.bold(
1025
+ language === 'ko' ?
1026
+ '🔍 AIWF 설치 검증 및 리포트' :
1027
+ '🔍 AIWF Installation Validation & Report'
1028
+ ));
1029
+ console.log(chalk.blue.bold('='.repeat(60)));
1030
+
1031
+ // 설치 유형 표시
1032
+ const installTypeText = {
1033
+ install: language === 'ko' ? '새 설치' : 'Fresh Installation',
1034
+ update: language === 'ko' ? '업데이트' : 'Update',
1035
+ reinstall: language === 'ko' ? '재설치' : 'Reinstallation'
1036
+ };
1037
+ console.log(chalk.gray(`설치 유형 / Installation Type: ${installTypeText[installationType] || installTypeText.install}`));
1038
+ console.log(chalk.gray(`검증 시간 / Validation Time: ${new Date().toLocaleString()}`));
1039
+
1040
+ // 전체 요약
1041
+ const { overall } = validationResults;
1042
+ const statusIcon = overall.success ? '✅' : '❌';
1043
+ const statusColor = overall.success ? chalk.green : chalk.red;
1044
+
1045
+ console.log(statusColor(`\n${statusIcon} 전체 상태 / Overall Status: ${overall.success ? 'PASSED' : 'FAILED'}`));
1046
+ console.log(chalk.gray(` 검증한 도구 / Tools Checked: ${overall.toolsChecked}`));
1047
+ console.log(chalk.gray(` 성공한 도구 / Tools Passed: ${overall.toolsPassed}`));
1048
+
1049
+ if (overall.toolsFailed > 0) {
1050
+ console.log(chalk.red(` 실패한 도구 / Tools Failed: ${overall.toolsFailed}`));
1051
+ console.log(chalk.red(` 총 이슈 수 / Total Issues: ${overall.totalIssues}`));
1052
+ }
1053
+
1054
+ // 개별 도구 결과
1055
+ console.log(chalk.blue('\n=== 개별 도구 결과 / Individual Tool Results ==='));
1056
+
1057
+ for (const [toolName, result] of Object.entries(validationResults.tools)) {
1058
+ const toolIcon = result.success ? '✅' : '❌';
1059
+ const toolColor = result.success ? chalk.green : chalk.red;
1060
+
1061
+ console.log(toolColor(`\n${toolIcon} ${result.description} (${toolName})`));
1062
+ console.log(chalk.gray(` 디렉토리 / Directory: ${result.baseDir}`));
1063
+ console.log(chalk.gray(` 파일 수 / Files Found: ${result.checks.fileCount}`));
1064
+ console.log(chalk.gray(` 총 크기 / Total Size: ${(result.checks.totalSize / 1024).toFixed(2)} KB`));
1065
+
1066
+ if (result.checks.issues.length > 0) {
1067
+ console.log(chalk.red(` 이슈 / Issues:`));
1068
+ for (const issue of result.checks.issues) {
1069
+ console.log(chalk.red(` • ${issue}`));
1070
+ }
1071
+ }
1072
+
1073
+ if (result.checks.requiredFiles.length > 0) {
1074
+ console.log(chalk.gray(` 필수 파일 / Required Files:`));
1075
+ for (const fileCheck of result.checks.requiredFiles) {
1076
+ const fileIcon = fileCheck.valid ? '✅' : '❌';
1077
+ const fileColor = fileCheck.valid ? chalk.gray : chalk.red;
1078
+ console.log(fileColor(` ${fileIcon} ${fileCheck.file} (${(fileCheck.size / 1024).toFixed(2)} KB)`));
1079
+ }
1080
+ }
1081
+ }
1082
+
1083
+ // 권장사항 섹션
1084
+ console.log(chalk.blue('\n=== 권장사항 / Recommendations ==='));
1085
+ if (overall.success) {
1086
+ console.log(chalk.green('🎉 모든 도구가 올바르게 설치되어 검증되었습니다!'));
1087
+ console.log(chalk.green('🎉 All tools are properly installed and validated!'));
1088
+ console.log(chalk.blue('\n다음 단계 / Next Steps:'));
1089
+ console.log(chalk.white(' 1. Claude Code에서 프로젝트를 열어보세요'));
1090
+ console.log(chalk.white(' 2. /project:aiwf:initialize 명령어로 프로젝트를 초기화하세요'));
1091
+ console.log(chalk.white(' 3. 프로젝트 관리를 시작하세요!'));
1092
+ } else {
1093
+ console.log(chalk.yellow('⚠️ 일부 이슈가 발견되었습니다. 위의 세부사항을 확인하세요.'));
1094
+ console.log(chalk.yellow('⚠️ Some issues were found. See details above.'));
1095
+
1096
+ // 실패한 도구별 해결 방안 제시
1097
+ const failedTools = Object.entries(validationResults.tools)
1098
+ .filter(([_, result]) => !result.success)
1099
+ .map(([tool, _]) => tool);
1100
+
1101
+ if (failedTools.length > 0) {
1102
+ console.log(chalk.blue('\n해결 방안 / Solutions:'));
1103
+ console.log(chalk.yellow(' • 전체 재설치를 시도해보세요'));
1104
+ console.log(chalk.yellow(' • 실패한 도구만 롤백 후 다시 설치하세요'));
1105
+ console.log(chalk.yellow(' • 네트워크 연결을 확인하고 다시 시도하세요'));
1106
+ }
1107
+ }
1108
+
1109
+ console.log(chalk.blue.bold('\n' + '='.repeat(60)));
1110
+ }
1111
+
1112
+ // 검증 결과 표시 함수 (기존 함수는 유지, 새로운 리포트 함수와 함께 사용)
1113
+ function displayValidationResults(validationResults, language) {
1114
+ return generateInstallationReport(validationResults, language, 'install');
1115
+ }
1116
+
1117
+ // 도구별 디렉토리 매핑 함수 (중복 제거됨 - 위의 함수 사용)
1118
+
1119
+ // 특정 도구의 백업 파일 목록 가져오기
1120
+ async function getBackupFilesForTool(tool, backupDir) {
1121
+ const toolBackupFiles = [];
1122
+
1123
+ try {
1124
+ const backupFiles = await fs.readdir(backupDir);
1125
+ const toolDir = getToolDirectory(tool);
1126
+
1127
+ if (!toolDir) return toolBackupFiles;
1128
+
1129
+ // 도구별 백업 파일 패턴 매칭
1130
+ const patterns = {
1131
+ claudeCode: /^claude-commands-aiwf-.*\.bak$/,
1132
+ geminiCLI: /^gemini-prompts-aiwf-.*\.bak$/,
1133
+ cursor: /^cursor-rules-.*\.bak$/,
1134
+ windsurf: /^windsurf-rules-.*\.bak$/,
1135
+ aiwf: /^aiwf-.*\.bak$/
1136
+ };
1137
+
1138
+ const pattern = patterns[tool];
1139
+ if (pattern) {
1140
+ const matchingFiles = backupFiles.filter(file => pattern.test(file));
1141
+ for (const file of matchingFiles) {
1142
+ toolBackupFiles.push({
1143
+ backupFile: path.join(backupDir, file),
1144
+ originalPath: restoreOriginalPath(file, tool)
1145
+ });
1146
+ }
1147
+ }
1148
+
1149
+ } catch (error) {
1150
+ // 백업 디렉토리가 없거나 읽을 수 없는 경우
1151
+ }
1152
+
1153
+ return toolBackupFiles;
1154
+ }
1155
+
1156
+ // 백업 파일명에서 원본 경로 복원
1157
+ function restoreOriginalPath(backupFileName, tool) {
1158
+ const toolDirs = {
1159
+ claudeCode: '.claude/commands/aiwf',
1160
+ geminiCLI: '.gemini/prompts/aiwf',
1161
+ cursor: '.cursor/rules',
1162
+ windsurf: '.windsurf/rules',
1163
+ aiwf: '.aiwf'
1164
+ };
1165
+
1166
+ const baseDir = toolDirs[tool];
1167
+ if (!baseDir) return null;
1168
+
1169
+ // 백업 파일명에서 원본 파일명 추출
1170
+ let originalFileName = backupFileName;
1171
+
1172
+ // 도구별 프리픽스 제거
1173
+ const prefixes = {
1174
+ claudeCode: 'claude-commands-aiwf-',
1175
+ geminiCLI: 'gemini-prompts-aiwf-',
1176
+ cursor: 'cursor-rules-',
1177
+ windsurf: 'windsurf-rules-',
1178
+ aiwf: 'aiwf-'
1179
+ };
1180
+
1181
+ const prefix = prefixes[tool];
1182
+ if (prefix && originalFileName.startsWith(prefix)) {
1183
+ originalFileName = originalFileName.substring(prefix.length);
1184
+ }
1185
+
1186
+ // .bak 확장자 제거
1187
+ if (originalFileName.endsWith('.bak')) {
1188
+ originalFileName = originalFileName.substring(0, originalFileName.length - 4);
1189
+ }
1190
+
1191
+ // 하이픈을 슬래시로 복원 (하위 디렉토리 구조)
1192
+ originalFileName = originalFileName.replace(/-/g, '/');
1193
+
1194
+ return path.join(baseDir, originalFileName);
1195
+ }
1196
+
1197
+ // 개별 백업 파일 복원 (중복 제거됨 - 위의 함수 사용)
1198
+
1199
+ // 특정 도구 롤백 함수
1200
+ async function rollbackTool(tool, backupDir, language = 'ko') {
1201
+ const msg = messages[language];
1202
+ const spinner = ora(
1203
+ language === 'ko' ?
1204
+ `${tool} 도구를 롤백하는 중...` :
1205
+ `Rolling back ${tool}...`
1206
+ ).start();
1207
+
1208
+ try {
1209
+ // 1. 해당 도구의 디렉토리 삭제
1210
+ const toolDir = getToolDirectory(tool);
1211
+ if (toolDir) {
1212
+ const dirExists = await fs.access(toolDir).then(() => true).catch(() => false);
1213
+ if (dirExists) {
1214
+ await fs.rm(toolDir, { recursive: true, force: true });
1215
+ spinner.text = language === 'ko' ?
1216
+ `${tool} 디렉토리 삭제 완료` :
1217
+ `${tool} directory removed`;
1218
+ }
1219
+ }
1220
+
1221
+ // 2. 백업에서 복원
1222
+ const backupFiles = await getBackupFilesForTool(tool, backupDir);
1223
+ let restoredCount = 0;
1224
+
1225
+ for (const backup of backupFiles) {
1226
+ const restored = await restoreBackupFile(backup);
1227
+ if (restored) {
1228
+ restoredCount++;
1229
+ }
1230
+ }
1231
+
1232
+ if (restoredCount > 0) {
1233
+ spinner.succeed(chalk.green(
1234
+ language === 'ko' ?
1235
+ `${tool} 롤백 완료 (${restoredCount}개 파일 복원)` :
1236
+ `${tool} rolled back successfully (${restoredCount} files restored)`
1237
+ ));
1238
+ return { success: true, restoredCount };
1239
+ } else {
1240
+ spinner.warn(chalk.yellow(
1241
+ language === 'ko' ?
1242
+ `${tool} 롤백: 복원할 백업 파일이 없음` :
1243
+ `${tool} rollback: No backup files to restore`
1244
+ ));
1245
+ return { success: true, restoredCount: 0 };
1246
+ }
1247
+
1248
+ } catch (error) {
1249
+ spinner.fail(chalk.red(
1250
+ language === 'ko' ?
1251
+ `${tool} 롤백 실패: ${error.message}` :
1252
+ `Failed to rollback ${tool}: ${error.message}`
1253
+ ));
1254
+ return { success: false, error: error.message };
1255
+ }
1256
+ }
1257
+
1258
+ // =============================================================================
1259
+ // ROLLBACK SYSTEM IMPLEMENTATION
1260
+ // =============================================================================
1261
+
1262
+ // 도구별 디렉토리 경로 가져오기 (중복 제거됨 - 위의 getToolDirectory 함수 사용)
1263
+
1264
+ // 백업에서 특정 도구의 파일들 찾기 (중복 제거됨 - 위의 함수 사용)
1265
+
1266
+ // 백업 파일명에서 원래 경로 복원
1267
+ function restoreBackupPath(backupFileName, tool) {
1268
+ const toolDir = getToolDirectory(tool);
1269
+ if (!toolDir) return null;
1270
+
1271
+ // 백업 파일명 패턴: claude-commands-aiwf-foo.md.bak -> .claude/commands/aiwf/foo.md
1272
+ if (backupFileName.startsWith('claude-commands-aiwf-')) {
1273
+ const relativePath = backupFileName
1274
+ .replace('claude-commands-aiwf-', '')
1275
+ .replace('.bak', '')
1276
+ .replace(/-/g, '/');
1277
+ return path.join('.claude/commands/aiwf', relativePath);
1278
+ }
1279
+
1280
+ // 백업 파일명 패턴: aiwf-foo-bar.md.bak -> .aiwf/foo/bar.md
1281
+ if (backupFileName.startsWith('aiwf-')) {
1282
+ const relativePath = backupFileName
1283
+ .replace('aiwf-', '')
1284
+ .replace('.bak', '')
1285
+ .replace(/-/g, '/');
1286
+ return path.join('.aiwf', relativePath);
1287
+ }
1288
+
1289
+ // 기타 패턴
1290
+ return backupFileName.replace('.bak', '');
1291
+ }
1292
+
1293
+ // 개별 백업 파일 복원 (중복 제거됨 - 위의 함수 사용)
1294
+
1295
+ // 특정 도구 롤백 함수
1296
+
1297
+ // 다중 도구 롤백 함수
1298
+ async function rollbackMultipleTools(failedTools, backupDir, language = 'en') {
1299
+ const results = {
1300
+ success: [],
1301
+ failed: [],
1302
+ partial: []
1303
+ };
1304
+
1305
+ console.log(chalk.blue('\n🔄 Starting rollback process...'));
1306
+
1307
+ for (const toolInfo of failedTools) {
1308
+ const toolName = typeof toolInfo === 'string' ? toolInfo : toolInfo.tool;
1309
+ const rollbackResult = await rollbackTool(toolName, backupDir, language);
1310
+
1311
+ if (rollbackResult === true) {
1312
+ results.success.push(toolName);
1313
+ } else if (rollbackResult === false) {
1314
+ results.failed.push(toolName);
1315
+ } else {
1316
+ results.partial.push(toolName);
1317
+ }
1318
+ }
1319
+
1320
+ // 롤백 결과 요약
1321
+ console.log(chalk.blue('\n📋 Rollback Summary:'));
1322
+ if (results.success.length > 0) {
1323
+ console.log(chalk.green(`✅ Successfully rolled back: ${results.success.join(', ')}`));
1324
+ }
1325
+ if (results.partial.length > 0) {
1326
+ console.log(chalk.yellow(`⚠️ Partially rolled back: ${results.partial.join(', ')}`));
1327
+ }
1328
+ if (results.failed.length > 0) {
1329
+ console.log(chalk.red(`❌ Failed to rollback: ${results.failed.join(', ')}`));
1330
+ }
1331
+
1332
+ return results;
1333
+ }
1334
+
1335
+ // 개선된 재설치 옵션 제공 함수
1336
+ async function offerReinstallationOptions(validationResults, language) {
1337
+ console.log(chalk.yellow(
1338
+ language === 'ko' ?
1339
+ '\n⚠️ 일부 설치가 실패했습니다. 무엇을 하시겠습니까?' :
1340
+ '\n⚠️ Some installations failed. What would you like to do?'
1341
+ ));
1342
+
1343
+ // 실패한 도구들 나열 - 새로운 형식에 맞게 수정
1344
+ const failedTools = validationResults.failed.map(({tool}) => tool);
1345
+
1346
+ if (failedTools.length > 0) {
1347
+ console.log(chalk.red(
1348
+ language === 'ko' ?
1349
+ `실패한 도구: ${failedTools.join(', ')}` :
1350
+ `Failed tools: ${failedTools.join(', ')}`
1351
+ ));
1352
+ }
1353
+
1354
+ const response = await prompts({
1355
+ type: 'select',
1356
+ name: 'action',
1357
+ message: language === 'ko' ? '어떤 작업을 수행하시겠습니까?' : 'What would you like to do?',
1358
+ choices: [
1359
+ {
1360
+ title: language === 'ko' ? '전체 재설치' : 'Full reinstallation',
1361
+ value: 'reinstall'
1362
+ },
1363
+ {
1364
+ title: language === 'ko' ? '실패한 도구만 롤백' : 'Rollback failed tools only',
1365
+ value: 'rollback'
1366
+ },
1367
+ {
1368
+ title: language === 'ko' ? '현재 상태 유지' : 'Keep current state',
1369
+ value: 'keep'
1370
+ }
1371
+ ]
1372
+ });
1373
+
1374
+ return response.action;
1375
+ }
1376
+
1377
+ // =============================================================================
1378
+ // ROLLBACK SYSTEM
1379
+ // =============================================================================
1380
+
1381
+ // 도구별 롤백 함수
1382
+ // 도구별 백업 복원 함수
1383
+ async function restoreToolFromBackup(tool, backupDir) {
1384
+ try {
1385
+ const backupFiles = await fs.readdir(backupDir);
1386
+ const toolBackups = backupFiles.filter(f => f.endsWith('.bak'));
1387
+
1388
+ for (const backup of toolBackups) {
1389
+ let shouldRestore = false;
1390
+ let originalPath = '';
1391
+
1392
+ // 도구별 백업 파일 패턴 매칭
1393
+ switch (tool) {
1394
+ case 'claudeCode':
1395
+ if (backup.startsWith('claude-commands-aiwf-')) {
1396
+ shouldRestore = true;
1397
+ const fileName = backup.replace('claude-commands-aiwf-', '').replace('.bak', '');
1398
+ originalPath = path.join('.claude/commands/aiwf', fileName);
1399
+ }
1400
+ break;
1401
+ case 'aiwf':
1402
+ if (backup.startsWith('aiwf-')) {
1403
+ shouldRestore = true;
1404
+ const relativePath = backup.replace('aiwf-', '').replace(/-/g, '/').replace('.bak', '');
1405
+ originalPath = '.aiwf/' + relativePath;
1406
+ }
1407
+ break;
1408
+ // cursor와 windsurf는 현재 백업하지 않음 (rules 파일은 동적 생성)
1409
+ }
1410
+
1411
+ if (shouldRestore) {
1412
+ const backupPath = path.join(backupDir, backup);
1413
+ await fs.mkdir(path.dirname(originalPath), { recursive: true });
1414
+ await fs.copyFile(backupPath, originalPath);
1415
+ }
1416
+ }
1417
+ } catch (error) {
1418
+ // 백업 복원 실패는 심각하지 않음
1419
+ console.warn(`Warning: Could not restore ${tool} from backup: ${error.message}`);
1420
+ }
1421
+ }
1422
+
1423
+ // 실패한 도구들에 대한 일괄 롤백
1424
+ async function rollbackFailedTools(failedTools, language) {
1425
+ const results = {
1426
+ success: [],
1427
+ failed: []
1428
+ };
1429
+
1430
+ for (const toolInfo of failedTools) {
1431
+ const tool = typeof toolInfo === 'object' ? toolInfo.tool : toolInfo;
1432
+ const rollbackSuccess = await rollbackTool(tool, language);
1433
+
1434
+ if (rollbackSuccess) {
1435
+ results.success.push(tool);
1436
+ } else {
1437
+ results.failed.push(tool);
1438
+ }
1439
+ }
1440
+
1441
+ return results;
1442
+ }
1443
+
1444
+ // =============================================================================
1445
+ // MAIN INSTALLATION FUNCTION
1446
+ // =============================================================================
1447
+
1448
+ async function installAIWF(options = {}) {
195
1449
  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'));
1450
+
1451
+ // 지능적 언어 선택 시스템
1452
+ let selectedLanguage = DEFAULT_LANGUAGE;
1453
+
1454
+ if (!options.force) {
1455
+ // 1. 기존 설정이 있는지 확인
1456
+ const existingConfig = await loadUserLanguageConfig();
1457
+
1458
+ if (existingConfig && existingConfig.language) {
1459
+ selectedLanguage = existingConfig.language;
1460
+ console.log(chalk.blue(`🌐 기존 언어 설정을 사용합니다 / Using existing language setting: ${selectedLanguage}`));
1461
+ } else {
1462
+ // 2. 자동 언어 감지 시도
1463
+ const detectedLang = await detectLanguage();
1464
+
1465
+ console.log(chalk.gray(`🔍 시스템 언어 감지 / System language detected: ${detectedLang}`));
1466
+
1467
+ // 3. 사용자에게 언어 선택 제공 (감지된 언어를 기본값으로)
1468
+ const languageResponse = await prompts({
1469
+ type: 'select',
1470
+ name: 'language',
1471
+ message: 'Please select language / 언어를 선택해주세요:',
1472
+ choices: [
1473
+ { title: 'English', value: 'en' },
1474
+ { title: '한국어 (Korean)', value: 'ko' }
1475
+ ],
1476
+ initial: SUPPORTED_LANGUAGES.indexOf(detectedLang) !== -1 ?
1477
+ SUPPORTED_LANGUAGES.indexOf(detectedLang) : 0
1478
+ });
1479
+
1480
+ if (languageResponse.language) {
1481
+ selectedLanguage = languageResponse.language;
1482
+ } else {
1483
+ selectedLanguage = detectedLang;
1484
+ }
1485
+
1486
+ // 4. 선택된 언어를 설정으로 저장
1487
+ try {
1488
+ await saveUserLanguageConfig(selectedLanguage, {
1489
+ autoDetect: true,
1490
+ fallback: DEFAULT_LANGUAGE
1491
+ });
1492
+ console.log(chalk.green(`✅ 언어 설정이 저장되었습니다 / Language preference saved: ${selectedLanguage}`));
1493
+ } catch (error) {
1494
+ console.warn(chalk.yellow(`⚠️ 언어 설정 저장 실패 / Failed to save language preference: ${error.message}`));
1495
+ }
1496
+ }
1497
+ } else {
1498
+ // Force 모드일 때도 자동 감지 시도
1499
+ try {
1500
+ selectedLanguage = await detectLanguage();
1501
+ } catch (error) {
1502
+ selectedLanguage = DEFAULT_LANGUAGE;
1503
+ }
1504
+ }
1505
+
1506
+ const msg = messages[selectedLanguage];
1507
+
1508
+ console.log(chalk.blue.bold(msg.welcome));
1509
+ console.log(chalk.gray(msg.description));
1510
+ console.log(chalk.gray(msg.optimized));
1511
+
1512
+ // 언어별 경로 설정 (언어 유틸리티 사용)
1513
+ const languagePath = getInstallationLanguagePath(selectedLanguage);
1514
+ const GITHUB_CONTENT_LANGUAGE_PREFIX = `${GITHUB_CONTENT_PREFIX}/${languagePath}`;
199
1515
 
200
1516
  const hasExisting = await checkExistingInstallation();
201
1517
 
@@ -203,51 +1519,51 @@ async function installMoonklabs(options = {}) {
203
1519
  const response = await prompts({
204
1520
  type: 'select',
205
1521
  name: 'action',
206
- message: '기존 Moonklabs 설치가 감지되었습니다. 무엇을 하시겠습니까?',
1522
+ message: msg.existingDetected,
207
1523
  choices: [
208
- { title: '업데이트 (명령어와 문서만 업데이트하고 작업 내용은 보존)', value: 'update' },
209
- { title: '설치 건너뛰기', value: 'skip' },
210
- { title: '취소', value: 'cancel' }
1524
+ { title: msg.updateOption, value: 'update' },
1525
+ { title: msg.skipOption, value: 'skip' },
1526
+ { title: msg.cancelOption, value: 'cancel' }
211
1527
  ]
212
1528
  });
213
1529
 
214
1530
  if (response.action === 'skip' || response.action === 'cancel') {
215
- console.log(chalk.yellow('\n설치가 취소되었습니다.'));
1531
+ console.log(chalk.yellow(msg.installCancelled));
216
1532
  process.exit(0);
217
1533
  }
218
1534
 
219
1535
  if (response.action === 'update') {
220
- await backupCommandsAndDocs();
1536
+ await backupCommandsAndDocs(msg);
221
1537
  }
222
1538
  }
223
1539
 
224
- const spinner = ora('GitHub에서 Moonklabs 프레임워크를 가져오는 중...').start();
1540
+ const spinner = ora(msg.fetching).start();
225
1541
 
226
1542
  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'
1543
+ // Create .aiwf directory structure
1544
+ const aiwfDirs = [
1545
+ '.aiwf',
1546
+ '.aiwf/01_PROJECT_DOCS',
1547
+ '.aiwf/02_REQUIREMENTS',
1548
+ '.aiwf/03_SPRINTS',
1549
+ '.aiwf/04_GENERAL_TASKS',
1550
+ '.aiwf/05_ARCHITECTURE_DECISIONS',
1551
+ '.aiwf/10_STATE_OF_PROJECT',
1552
+ '.aiwf/98_PROMPTS',
1553
+ '.aiwf/99_TEMPLATES'
238
1554
  ];
239
1555
 
240
- for (const dir of moonklabsDirs) {
1556
+ for (const dir of aiwfDirs) {
241
1557
  await fs.mkdir(dir, { recursive: true });
242
1558
  }
243
1559
 
244
1560
  // Only download manifest on fresh installs
245
1561
  if (!hasExisting) {
246
- logWithSpinner(spinner, 'Moonklabs 프레임워크 파일을 다운로드하는 중...', debugLog);
1562
+ logWithSpinner(spinner, msg.downloading, debugLog);
247
1563
  // Get the root manifest
248
1564
  try {
249
- const manifestUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_PREFIX}/.moonklabs/00_PROJECT_MANIFEST.md`;
250
- await downloadFile(manifestUrl, '.moonklabs/00_PROJECT_MANIFEST.md');
1565
+ const manifestUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_LANGUAGE_PREFIX}/.aiwf/00_PROJECT_MANIFEST.md`;
1566
+ await downloadFile(manifestUrl, '.aiwf/00_PROJECT_MANIFEST.md');
251
1567
  } catch (error) {
252
1568
  // If manifest doesn't exist, that's okay
253
1569
  }
@@ -255,59 +1571,66 @@ async function installMoonklabs(options = {}) {
255
1571
 
256
1572
  // Download templates on fresh install
257
1573
  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);
1574
+ logWithSpinner(spinner, msg.downloadingTemplates, debugLog);
1575
+ await downloadDirectory(`${GITHUB_CONTENT_LANGUAGE_PREFIX}/.aiwf/98_PROMPTS`, '.aiwf/98_PROMPTS', spinner, msg);
1576
+ await downloadDirectory(`${GITHUB_CONTENT_LANGUAGE_PREFIX}/.aiwf/99_TEMPLATES`, '.aiwf/99_TEMPLATES', spinner, msg);
261
1577
  } catch (error) {
262
- logWithSpinner(spinner, '템플릿 디렉토리를 찾을 수 없어 건너뜁니다...', debugLog);
1578
+ logWithSpinner(spinner, msg.templatesNotFound, debugLog);
263
1579
  }
264
1580
 
265
1581
  // Always update CLAUDE.md documentation files
266
- logWithSpinner(spinner, '문서를 업데이트하는 중...', debugLog);
1582
+ logWithSpinner(spinner, msg.updatingDocs, debugLog);
267
1583
  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'
1584
+ '.aiwf/CLAUDE.md',
1585
+ '.aiwf/02_REQUIREMENTS/CLAUDE.md',
1586
+ '.aiwf/03_SPRINTS/CLAUDE.md',
1587
+ '.aiwf/04_GENERAL_TASKS/CLAUDE.md'
272
1588
  ];
273
1589
 
274
1590
  for (const claudeFile of claudeFiles) {
275
1591
  try {
276
- const claudeUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_PREFIX}/${claudeFile}`;
1592
+ const claudeUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_LANGUAGE_PREFIX}/${claudeFile}`;
277
1593
  await downloadFile(claudeUrl, claudeFile);
278
1594
  } catch (error) {
279
1595
  // If CLAUDE.md doesn't exist, that's okay
280
1596
  }
281
1597
  }
282
1598
 
283
- // Create .claude/commands/moonklabs directory
284
- await fs.mkdir('.claude/commands/moonklabs', { recursive: true });
1599
+ // Delete and recreate commands directory
1600
+ const commandsExist = await fs.access(COMMANDS_DIR).then(() => true).catch(() => false);
1601
+
1602
+ if (commandsExist) {
1603
+ logWithSpinner(spinner, msg.deletingOldCommands, debugLog);
1604
+ await fs.rm(COMMANDS_DIR, { recursive: true, force: true });
1605
+ }
1606
+
1607
+ await fs.mkdir(COMMANDS_DIR, { recursive: true });
285
1608
 
286
- // Always update commands
287
- logWithSpinner(spinner, 'Moonklabs 명령어를 업데이트하는 중...', debugLog);
1609
+ // Always update commands (clean download)
1610
+ logWithSpinner(spinner, msg.downloadingCommands, debugLog);
288
1611
  try {
289
- await downloadDirectory(`${GITHUB_CONTENT_PREFIX}/.claude/commands/moonklabs`, '.claude/commands/moonklabs', spinner);
1612
+ await downloadDirectory(`${GITHUB_CONTENT_LANGUAGE_PREFIX}/${COMMANDS_DIR}`, COMMANDS_DIR, spinner, msg);
290
1613
  } catch (error) {
291
- logWithSpinner(spinner, '명령어 디렉토리를 찾을 수 없어 건너뜁니다...', debugLog);
1614
+ logWithSpinner(spinner, msg.commandsNotFound, debugLog);
292
1615
  }
293
1616
 
294
1617
  // 2. rules/global 폴더 다운로드 (임시 폴더)
295
- let tmpRulesGlobal = '.moonklabs/_tmp_rules_global';
1618
+ let tmpRulesGlobal = '.aiwf/_tmp_rules_global';
296
1619
  try {
297
- logWithSpinner(spinner, 'rules/global 폴더를 다운로드 중...', debugLog);
298
- await downloadDirectory(`rules/global`, tmpRulesGlobal, spinner);
1620
+ logWithSpinner(spinner, msg.downloadingRulesGlobal, debugLog);
1621
+ await downloadDirectory(`rules/global`, tmpRulesGlobal, spinner, msg);
299
1622
  } catch (error) {
300
- logWithSpinner(spinner, 'rules/global 폴더를 찾을 수 없어 건너뜁니다...', debugLog);
1623
+ logWithSpinner(spinner, msg.rulesGlobalNotFound, debugLog);
301
1624
  tmpRulesGlobal = null;
302
1625
  }
303
1626
 
304
1627
  // 2-1. rules/manual 폴더 다운로드 (임시 폴더)
305
- let tmpRulesManual = '.moonklabs/_tmp_rules_manual';
1628
+ let tmpRulesManual = '.aiwf/_tmp_rules_manual';
306
1629
  try {
307
- logWithSpinner(spinner, 'rules/manual 폴더를 다운로드 중...', debugLog);
308
- await downloadDirectory(`rules/manual`, tmpRulesManual, spinner);
1630
+ logWithSpinner(spinner, msg.downloadingRulesManual, debugLog);
1631
+ await downloadDirectory(`rules/manual`, tmpRulesManual, spinner, msg);
309
1632
  } catch (error) {
310
- logWithSpinner(spinner, 'rules/manual 폴더를 찾을 수 없어 건너뜁니다...', debugLog);
1633
+ logWithSpinner(spinner, msg.rulesManualNotFound, debugLog);
311
1634
  tmpRulesManual = null;
312
1635
  }
313
1636
 
@@ -372,57 +1695,105 @@ async function installMoonklabs(options = {}) {
372
1695
  }
373
1696
 
374
1697
  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 파일로 만들어짐'));
1698
+ spinner.succeed(chalk.green(msg.updateSuccess));
1699
+ console.log(chalk.blue(`\n${msg.updateHistory}`));
1700
+ console.log(chalk.gray(` • ${COMMANDS_DIR}${msg.updatedCommands}`));
1701
+ console.log(chalk.gray(` • ${msg.updatedDocs}`));
1702
+ console.log(chalk.green(`\n${msg.workPreserved}`));
1703
+ console.log(chalk.gray(` • ${msg.allFilesPreserved}`));
1704
+ console.log(chalk.gray(` • ${msg.backupCreated}`));
382
1705
  } 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 사용자 명령어'));
1706
+ spinner.succeed(chalk.green(msg.installSuccess));
1707
+ console.log(chalk.blue(`\n${msg.structureCreated}`));
1708
+ console.log(chalk.gray(` .aiwf/ - ${msg.aiwfRoot}`));
1709
+ console.log(chalk.gray(` .claude/commands/ - ${msg.claudeCommands}`));
1710
+
1711
+ console.log(chalk.green(`\n${msg.nextSteps}`));
1712
+ console.log(chalk.white(` 1. ${msg.nextStep1}`));
1713
+ console.log(chalk.white(` 2. ${msg.nextStep2}`));
1714
+ console.log(chalk.white(` 3. ${msg.nextStep3}\n`));
387
1715
 
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'));
1716
+ console.log(chalk.blue(`\n${msg.gettingStarted}`));
1717
+ console.log(chalk.gray(` 1. ${msg.startStep1}`));
1718
+ console.log(chalk.gray(` 2. ${msg.startStep2}`));
1719
+ console.log(chalk.gray(`\n${msg.checkDocs}`));
1720
+ }
392
1721
 
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 디렉토리의 문서를 확인하세요.'));
1722
+ // 설치 검증 실행
1723
+ console.log(chalk.blue('\n🔍 Installation Validation / 설치 검증을 시작합니다...'));
1724
+ // Use specification-compliant validation function
1725
+ const selectedTools = ['claude-code', 'cursor', 'windsurf']; // Default tools for this version
1726
+ const validationResults = await validateInstallation(selectedTools, selectedLanguage);
1727
+ displaySpecCompliantValidationResults(validationResults, selectedLanguage);
1728
+
1729
+ // 부분 실패 시 재설치/롤백 옵션 제공
1730
+ if (validationResults.failed.length > 0) {
1731
+ const action = await offerReinstallationOptions(validationResults, selectedLanguage);
1732
+
1733
+ if (action === 'reinstall') {
1734
+ console.log(chalk.blue('\n🔄 재설치를 시작합니다... / Starting reinstallation...'));
1735
+ // 재귀적으로 설치를 다시 시도 (한 번만)
1736
+ if (!options.isRetry) {
1737
+ return await installAIWF({ ...options, isRetry: true });
1738
+ }
1739
+ } else if (action === 'rollback') {
1740
+ console.log(chalk.blue('\n↩️ 실패한 도구를 롤백합니다... / Rolling back failed tools...'));
1741
+
1742
+ // 실패한 도구들만 롤백 - 새로운 형식에 맞게 수정
1743
+ const failedTools = validationResults.failed.map(({tool}) => tool);
1744
+
1745
+ let rollbackResults = {};
1746
+ for (const tool of failedTools) {
1747
+ if (BACKUP_DIR) {
1748
+ const rollbackResult = await rollbackTool(tool, BACKUP_DIR, selectedLanguage);
1749
+ rollbackResults[tool] = rollbackResult;
1750
+ }
1751
+ }
1752
+
1753
+ // 롤백 후 다시 검증
1754
+ console.log(chalk.blue('\n🔍 롤백 후 재검증... / Re-validating after rollback...'));
1755
+ const postRollbackValidation = await validateInstallation(selectedTools, selectedLanguage);
1756
+ displaySpecCompliantValidationResults(postRollbackValidation, selectedLanguage);
1757
+
1758
+ // 롤백 결과 요약
1759
+ console.log(chalk.blue('\n=== Rollback Summary / 롤백 요약 ==='));
1760
+ for (const [tool, result] of Object.entries(rollbackResults)) {
1761
+ if (result.success) {
1762
+ console.log(chalk.green(`✅ ${tool}: ${result.restoredCount} files restored`));
1763
+ } else {
1764
+ console.log(chalk.red(`❌ ${tool}: ${result.error}`));
1765
+ }
1766
+ }
1767
+ }
397
1768
  }
398
1769
 
399
- console.log(chalk.green('\nEnjoy Moonklabs! 🚀\n'));
1770
+ console.log(chalk.green(msg.enjoy));
400
1771
 
401
1772
  } catch (error) {
402
1773
  if (hasExisting) {
403
- spinner.fail(chalk.red('업데이트 실패'));
404
- await restoreFromBackup(spinner);
1774
+ spinner.fail(chalk.red(msg.updateFailed));
1775
+ await restoreFromBackup(spinner, msg);
405
1776
  } else {
406
- spinner.fail(chalk.red('설치 실패'));
1777
+ spinner.fail(chalk.red(msg.installFailed));
407
1778
  }
408
1779
  console.error(chalk.red(error.message));
409
1780
  process.exit(1);
410
1781
  }
411
1782
  }
412
1783
 
413
- async function restoreFromBackup(spinner) {
1784
+ async function restoreFromBackup(spinner, msg) {
414
1785
  if (!BACKUP_DIR) {
415
1786
  // 가장 최근 backup 폴더 사용
416
- const moonklabsDir = '.moonklabs';
417
- const dirs = (await fs.readdir(moonklabsDir)).filter(f => f.startsWith('backup_'));
1787
+ const aiwfDir = '.aiwf';
1788
+ const dirs = (await fs.readdir(aiwfDir)).filter(f => f.startsWith('backup_'));
418
1789
  if (dirs.length === 0) {
419
- spinner.fail(chalk.red('복원할 백업 폴더가 없습니다.'));
1790
+ spinner.fail(chalk.red(msg.noBackupFound));
420
1791
  return;
421
1792
  }
422
1793
  dirs.sort();
423
- BACKUP_DIR = path.join(moonklabsDir, dirs[dirs.length - 1]);
1794
+ BACKUP_DIR = path.join(aiwfDir, dirs[dirs.length - 1]);
424
1795
  }
425
- spinner.start(chalk.yellow('백업에서 복원 중... '));
1796
+ spinner.start(chalk.yellow(msg.restoringFromBackup));
426
1797
  try {
427
1798
  let backupFiles = [];
428
1799
  try {
@@ -431,16 +1802,16 @@ async function restoreFromBackup(spinner) {
431
1802
  backupFiles = [];
432
1803
  }
433
1804
  for (const backup of backupFiles) {
434
- // 원래 위치 추정: claude-commands-moonklabs-foo.md.bak -> .claude/commands/moonklabs/foo.md
1805
+ // 원래 위치 추정: claude-commands-aiwf-foo.md.bak -> .claude/commands/aiwf/foo.md
435
1806
  let originalFile;
436
- if (backup.startsWith('moonklabs-')) {
437
- // .moonklabs/ 하위
1807
+ if (backup.startsWith('aiwf-')) {
1808
+ // .aiwf/ 하위
438
1809
  const rel = backup.replace(/-/g, '/').replace('.bak', '');
439
1810
  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);
1811
+ } else if (backup.startsWith('claude-commands-aiwf-')) {
1812
+ // .claude/commands/aiwf/ 하위
1813
+ const rel = backup.replace('claude-commands-aiwf-', '').replace(/-/g, '/').replace('.bak', '');
1814
+ originalFile = path.join(COMMANDS_DIR, rel);
444
1815
  } else {
445
1816
  // 기타
446
1817
  originalFile = backup.replace('.bak', '');
@@ -448,21 +1819,21 @@ async function restoreFromBackup(spinner) {
448
1819
  try {
449
1820
  await fs.rename(path.join(BACKUP_DIR, backup), originalFile);
450
1821
  } catch (e) {
451
- console.warn(chalk.yellow(`'${backup}' 파일을 복원할 수 없습니다. 수동 확인이 필요합니다.`));
1822
+ console.warn(chalk.yellow(`'${backup}' ${msg.cannotRestore}`));
452
1823
  }
453
1824
  }
454
- spinner.succeed(chalk.green('성공적으로 복원되었습니다.'));
1825
+ spinner.succeed(chalk.green(msg.restoreSuccess));
455
1826
  } catch (error) {
456
- spinner.fail(chalk.red('복원에 실패했습니다.'));
1827
+ spinner.fail(chalk.red(msg.restoreFailed));
457
1828
  console.error(error);
458
1829
  }
459
1830
  }
460
1831
 
461
1832
  program
462
- .name('hello-moonklabs')
1833
+ .name('aiwf')
463
1834
  .version('1.0.1')
464
- .description('Moonklabs 프레임워크 설치 프로그램')
465
- .option('-f, --force', '프롬프트 없이 강제 설치')
466
- .action((options) => installMoonklabs({ ...options, debugLog: true }));
1835
+ .description('AIWF Installer')
1836
+ .option('-f, --force', 'Force install without prompts')
1837
+ .action((options) => installAIWF({ ...options, debugLog: true }));
467
1838
 
468
1839
  program.parse(process.argv);