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