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.
- package/CHANGELOG.md +46 -0
- package/CLAUDE.md +19 -0
- package/LICENSE +1 -1
- package/README.ko.md +90 -62
- package/README.md +109 -62
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/01_PROJECT_DOCS/ARCHITECTURE.md +1 -1
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/M01_Backend_Setup/M01_milestone_meta.md +5 -1
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/M01_Backend_Setup/PRD_AMEND_01_Auth_Flow_Update.md +5 -1
- package/claude-code/{simone/.simone/03_SPRINTS/CLAUDE.MD → aiwf/en/.aiwf/03_SPRINTS/CLAUDE.md} +28 -7
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/03_SPRINTS/S01_M01_Initial_API/S01_sprint_meta.md +8 -1
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/03_SPRINTS/S01_M01_Initial_API/T01_S01_Setup_Project_Structure.md +5 -1
- package/claude-code/aiwf/en/.aiwf/04_GENERAL_TASKS/CLAUDE.md +96 -0
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/04_GENERAL_TASKS/T002_API_Rate_Limiting.md +7 -2
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/04_GENERAL_TASKS/TX001_Refactor_Logging_Module.md +7 -2
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/05_ARCHITECTURAL_DECISIONS/ADR001_Chosen_Database_System.md +7 -1
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/05_ARCHITECTURAL_DECISIONS/ADR002_API_Authentication_Method.md +7 -1
- package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-12-12-00-needs-focus.md +139 -0
- package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-12-12-00-test-alignment.md +155 -0
- package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-13-00-50-solid-progress.md +122 -0
- package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-14-08-30-infrastructure-challenges.md +141 -0
- package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-16-21-48-critical-foundation-issues.md +177 -0
- package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/2025-06-17-15-08-solid-progress.md +149 -0
- package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/index.json +5 -0
- package/claude-code/aiwf/en/.aiwf/10_STATE_OF_PROJECT/sync-index.js +78 -0
- package/claude-code/aiwf/en/.aiwf/98_PROMPTS/github_integration.md +130 -0
- package/claude-code/aiwf/en/.aiwf/98_PROMPTS/useful-prompts.md +31 -0
- package/claude-code/aiwf/en/.aiwf/98_PROMPTS/vibe-front-prompts.md +89 -0
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/99_TEMPLATES/task_template.md +1 -0
- package/claude-code/{simone/.simone/CLAUDE.MD → aiwf/en/.aiwf/CLAUDE.md} +13 -4
- package/claude-code/{simone/.simone → aiwf/en/.aiwf}/README.md +6 -6
- package/claude-code/aiwf/en/.claude/CLAUDE_BE.md +276 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_changelog.md +82 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_code_review.md +88 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_commit.md +160 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_general_task.md +147 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_milestone_plan.md +194 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_prd.md +272 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_sprint_tasks.md +189 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_sprints_from_milestone.md +121 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_discuss_review.md +29 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_do_task.md +109 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_docs.md +225 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_infinite.md +202 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_initialize.md +134 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_issue_create.md +65 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_language_manager.md +287 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_language_status.md +246 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_mermaid.md +272 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_pr_create.md +76 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_prime.md +9 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_project_review.md +261 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_switch_language.md +160 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_test.md +125 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_testing_review.md +198 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_tm-run-all-subtask.md +206 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_ultrathink_code_advanced.md +460 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_ultrathink_code_basic.md +165 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_ultrathink_general.md +362 -0
- package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_yolo.md +300 -0
- package/claude-code/aiwf/en/.claude/mcp.json +31 -0
- package/claude-code/aiwf/en/.claude/settings.json +102 -0
- package/claude-code/aiwf/ko/.aiwf/00_PROJECT_MANIFEST.md +49 -0
- package/claude-code/aiwf/ko/.aiwf/01_PROJECT_DOCS/ARCHITECTURE.md +55 -0
- package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/CLAUDE.md +78 -0
- package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/M01_Backend_Setup/M01_milestone_meta.md +42 -0
- package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/M01_Backend_Setup/PRD_AMEND_01_Auth_Flow_Update.md +73 -0
- package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/M01_Backend_Setup/PRD_Backend_Setup.md +98 -0
- package/claude-code/aiwf/ko/.aiwf/02_REQUIREMENTS/M01_Backend_Setup/SPECS_API_V1.md +232 -0
- package/claude-code/aiwf/ko/.aiwf/03_SPRINTS/CLAUDE.md +83 -0
- package/claude-code/aiwf/ko/.aiwf/03_SPRINTS/S01_M01_Initial_API/S01_sprint_meta.md +49 -0
- package/claude-code/aiwf/ko/.aiwf/03_SPRINTS/S01_M01_Initial_API/T01_S01_Setup_Project_Structure.md +60 -0
- package/claude-code/aiwf/ko/.aiwf/04_GENERAL_TASKS/CLAUDE.md +96 -0
- package/claude-code/aiwf/ko/.aiwf/04_GENERAL_TASKS/T002_API_Rate_Limiting.md +54 -0
- package/claude-code/aiwf/ko/.aiwf/04_GENERAL_TASKS/TX001_Refactor_Logging_Module.md +58 -0
- package/claude-code/aiwf/ko/.aiwf/05_ARCHITECTURAL_DECISIONS/ADR001_Chosen_Database_System.md +119 -0
- package/claude-code/aiwf/ko/.aiwf/05_ARCHITECTURAL_DECISIONS/ADR002_API_Authentication_Method.md +124 -0
- package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-12-12-00-needs-focus.md +139 -0
- package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-12-12-00-test-alignment.md +155 -0
- package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-13-00-50-solid-progress.md +122 -0
- package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-14-08-30-infrastructure-challenges.md +141 -0
- package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-16-21-48-critical-foundation-issues.md +177 -0
- package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/2025-06-17-15-08-solid-progress.md +149 -0
- package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/index.json +5 -0
- package/claude-code/aiwf/ko/.aiwf/10_STATE_OF_PROJECT/sync-index.js +78 -0
- package/claude-code/aiwf/ko/.aiwf/98_PROMPTS/github_integration.md +130 -0
- package/claude-code/aiwf/ko/.aiwf/98_PROMPTS/useful-prompts.md +31 -0
- package/claude-code/aiwf/ko/.aiwf/98_PROMPTS/vibe-front-prompts.md +89 -0
- package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/adr_template.md +49 -0
- package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/milestone_meta_template.md +25 -0
- package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/project_manifest_template.md +39 -0
- package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/sprint_meta_template.md +23 -0
- package/claude-code/aiwf/ko/.aiwf/99_TEMPLATES/task_template.md +36 -0
- package/claude-code/aiwf/ko/.aiwf/CLAUDE.md +76 -0
- package/claude-code/aiwf/ko/.aiwf/README.md +97 -0
- package/claude-code/aiwf/ko/.claude/CLAUDE_BE.md +276 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_changelog.md +82 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_code_review.md +88 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_commit.md +160 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_general_task.md +147 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_milestone_plan.md +194 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_prd.md +280 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_sprint_tasks.md +189 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_sprints_from_milestone.md +121 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_discuss_review.md +29 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_do_task.md +109 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_docs.md +225 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_infinite.md +202 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_initialize.md +134 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_issue_create.md +65 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_language_manager.md +287 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_language_status.md +246 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_mermaid.md +272 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_pr_create.md +76 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_prime.md +9 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_project_review.md +261 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_switch_language.md +160 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_test.md +124 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_testing_review.md +198 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_tm-run-all-subtask.md +210 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_ultrathink_code_advanced.md +460 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_ultrathink_code_basic.md +165 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_ultrathink_general.md +305 -0
- package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_yolo.md +300 -0
- package/claude-code/aiwf/ko/.claude/mcp.json +31 -0
- package/claude-code/aiwf/ko/.claude/settings.json +102 -0
- package/{AI-WORKFLOW.md → docs/AI-WORKFLOW.ko.md} +38 -24
- package/docs/AI-WORKFLOW.md +299 -0
- package/docs/COMMANDS_GUIDE.ko.md +727 -0
- package/docs/COMMANDS_GUIDE.md +726 -0
- package/docs/CONTRIBUTING.md +406 -0
- package/docs/DEVELOPMENT_GUIDE.md +727 -0
- package/docs/Enhanced_Installation_Flow_Design.md +498 -0
- package/docs/PRD.ko.md +148 -0
- package/docs/PRD.md +150 -0
- package/docs/moonklabs-metadata-system-prd.md +127 -0
- package/index.js +1493 -122
- package/jest.config.js +4 -0
- package/language-cli.js +279 -0
- package/language-utils.js +330 -0
- package/package.json +23 -10
- package/scripts/validate-commands.cjs +338 -0
- package/scripts/validate-commands.js +254 -0
- package/tests/basic.test.js +62 -0
- package/tests/commands.test.js +127 -0
- package/tests/installer.test.js +129 -0
- package/tests/language-utils.test.js +271 -0
- package/COMMANDS_GUIDE.md +0 -462
- package/PRD.ko.md +0 -96
- package/PRD.md +0 -98
- package/claude-code/simone/.simone/04_GENERAL_TASKS/CLAUDE.MD +0 -51
- package/claude-code/simone/CHANGELOG.md +0 -71
- package/claude-code/simone/LICENSE +0 -21
- package/claude-code/simone/README.md +0 -219
- package/claude-code/simone/SYNC_GUIDE.md +0 -172
- package/claude-code/simone/sync-simone.sh +0 -138
- /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/00_PROJECT_MANIFEST.md +0 -0
- /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/CLAUDE.md +0 -0
- /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/M01_Backend_Setup/PRD_Backend_Setup.md +0 -0
- /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/02_REQUIREMENTS/M01_Backend_Setup/SPECS_API_V1.md +0 -0
- /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/99_TEMPLATES/adr_template.md +0 -0
- /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/99_TEMPLATES/milestone_meta_template.md +0 -0
- /package/claude-code/{simone/.simone → aiwf/en/.aiwf}/99_TEMPLATES/project_manifest_template.md +0 -0
- /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/
|
|
20
|
-
const GITHUB_CONTENT_PREFIX = 'claude-code/
|
|
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': '
|
|
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': '
|
|
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
|
|
74
|
-
const claudeCommandsExists = await fs.access(
|
|
75
|
-
return
|
|
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 `.
|
|
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
|
-
|
|
110
|
+
|
|
111
|
+
// 도구별 백업 파일 명명 규칙 개선
|
|
101
112
|
let bakFileName;
|
|
102
|
-
if (filePath.startsWith('.claude/commands/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
bakFileName =
|
|
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(
|
|
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
|
-
'.
|
|
128
|
-
'.
|
|
129
|
-
'.
|
|
130
|
-
'.
|
|
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
|
|
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(
|
|
174
|
+
const commandFiles = await fs.readdir(COMMANDS_DIR);
|
|
145
175
|
for (const file of commandFiles) {
|
|
146
|
-
const filePath = path.join(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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:
|
|
1522
|
+
message: msg.existingDetected,
|
|
207
1523
|
choices: [
|
|
208
|
-
{ title:
|
|
209
|
-
{ title:
|
|
210
|
-
{ title:
|
|
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(
|
|
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(
|
|
1540
|
+
const spinner = ora(msg.fetching).start();
|
|
225
1541
|
|
|
226
1542
|
try {
|
|
227
|
-
// Create .
|
|
228
|
-
const
|
|
229
|
-
'.
|
|
230
|
-
'.
|
|
231
|
-
'.
|
|
232
|
-
'.
|
|
233
|
-
'.
|
|
234
|
-
'.
|
|
235
|
-
'.
|
|
236
|
-
'.
|
|
237
|
-
'.
|
|
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
|
|
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,
|
|
1562
|
+
logWithSpinner(spinner, msg.downloading, debugLog);
|
|
247
1563
|
// Get the root manifest
|
|
248
1564
|
try {
|
|
249
|
-
const manifestUrl = `${GITHUB_RAW_URL}/${
|
|
250
|
-
await downloadFile(manifestUrl, '.
|
|
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,
|
|
259
|
-
await downloadDirectory(`${
|
|
260
|
-
await downloadDirectory(`${
|
|
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,
|
|
1578
|
+
logWithSpinner(spinner, msg.templatesNotFound, debugLog);
|
|
263
1579
|
}
|
|
264
1580
|
|
|
265
1581
|
// Always update CLAUDE.md documentation files
|
|
266
|
-
logWithSpinner(spinner,
|
|
1582
|
+
logWithSpinner(spinner, msg.updatingDocs, debugLog);
|
|
267
1583
|
const claudeFiles = [
|
|
268
|
-
'.
|
|
269
|
-
'.
|
|
270
|
-
'.
|
|
271
|
-
'.
|
|
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}/${
|
|
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
|
-
//
|
|
284
|
-
await fs.
|
|
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,
|
|
1609
|
+
// Always update commands (clean download)
|
|
1610
|
+
logWithSpinner(spinner, msg.downloadingCommands, debugLog);
|
|
288
1611
|
try {
|
|
289
|
-
await downloadDirectory(`${
|
|
1612
|
+
await downloadDirectory(`${GITHUB_CONTENT_LANGUAGE_PREFIX}/${COMMANDS_DIR}`, COMMANDS_DIR, spinner, msg);
|
|
290
1613
|
} catch (error) {
|
|
291
|
-
logWithSpinner(spinner,
|
|
1614
|
+
logWithSpinner(spinner, msg.commandsNotFound, debugLog);
|
|
292
1615
|
}
|
|
293
1616
|
|
|
294
1617
|
// 2. rules/global 폴더 다운로드 (임시 폴더)
|
|
295
|
-
let tmpRulesGlobal = '.
|
|
1618
|
+
let tmpRulesGlobal = '.aiwf/_tmp_rules_global';
|
|
296
1619
|
try {
|
|
297
|
-
logWithSpinner(spinner,
|
|
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,
|
|
1623
|
+
logWithSpinner(spinner, msg.rulesGlobalNotFound, debugLog);
|
|
301
1624
|
tmpRulesGlobal = null;
|
|
302
1625
|
}
|
|
303
1626
|
|
|
304
1627
|
// 2-1. rules/manual 폴더 다운로드 (임시 폴더)
|
|
305
|
-
let tmpRulesManual = '.
|
|
1628
|
+
let tmpRulesManual = '.aiwf/_tmp_rules_manual';
|
|
306
1629
|
try {
|
|
307
|
-
logWithSpinner(spinner,
|
|
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,
|
|
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(
|
|
376
|
-
console.log(chalk.blue(
|
|
377
|
-
console.log(chalk.gray(
|
|
378
|
-
console.log(chalk.gray(
|
|
379
|
-
console.log(chalk.green(
|
|
380
|
-
console.log(chalk.gray(
|
|
381
|
-
console.log(chalk.gray(
|
|
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(
|
|
384
|
-
console.log(chalk.blue(
|
|
385
|
-
console.log(chalk.gray(
|
|
386
|
-
console.log(chalk.gray(
|
|
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.
|
|
389
|
-
console.log(chalk.
|
|
390
|
-
console.log(chalk.
|
|
391
|
-
console.log(chalk.
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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(
|
|
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
|
|
417
|
-
const dirs = (await fs.readdir(
|
|
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(
|
|
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-
|
|
1805
|
+
// 원래 위치 추정: claude-commands-aiwf-foo.md.bak -> .claude/commands/aiwf/foo.md
|
|
435
1806
|
let originalFile;
|
|
436
|
-
if (backup.startsWith('
|
|
437
|
-
// .
|
|
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-
|
|
441
|
-
// .claude/commands/
|
|
442
|
-
const rel = backup.replace('claude-commands-
|
|
443
|
-
originalFile = path.join(
|
|
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('
|
|
1833
|
+
.name('aiwf')
|
|
463
1834
|
.version('1.0.1')
|
|
464
|
-
.description('
|
|
465
|
-
.option('-f, --force', '
|
|
466
|
-
.action((options) =>
|
|
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);
|