aiwf 0.3.3 → 0.3.5
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/metrics/metrics.json +18 -0
- package/.aiwf/personas/current.json +5 -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_aggressive_persona.md +53 -0
- package/.claude/commands/aiwf/aiwf_compress_context.md +116 -0
- package/.claude/commands/aiwf/aiwf_compress_minimal_persona.md +48 -0
- package/.claude/commands/aiwf/aiwf_compress_stats.md +79 -0
- package/.claude/commands/aiwf/aiwf_compress_with_persona.md +51 -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_evaluate.md +92 -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/CHANGELOG.md +54 -5
- package/CLAUDE.md +154 -54
- package/README.ko.md +25 -0
- package/README.md +25 -0
- package/TEST_RECOVERY_PLAN.md +68 -0
- package/claude-code/aiwf/ko/commands/ai-persona.js +1 -1
- 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 +206 -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/commands/persona.js +288 -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/templates/web-app/template/package-lock.json +0 -10022
- 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 -1951
- 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/{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
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI 페르소나 명령어
|
|
5
|
+
* AI 행동 페르소나 관리 및 전환
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AIPersonaManager } from '../lib/ai-persona-manager.js';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { dirname } from 'path';
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
|
|
17
|
+
class PersonaCommand {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.personaManager = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 페르소나 매니저 초기화
|
|
24
|
+
*/
|
|
25
|
+
async initPersonaManager() {
|
|
26
|
+
if (!this.personaManager) {
|
|
27
|
+
this.personaManager = new AIPersonaManager({
|
|
28
|
+
personaConfigPath: path.join(process.cwd(), '.aiwf', 'personas'),
|
|
29
|
+
metricsPath: path.join(process.cwd(), '.aiwf', 'metrics'),
|
|
30
|
+
metricsEnabled: true,
|
|
31
|
+
autoDetectionEnabled: true
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await this.personaManager.init();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return this.personaManager;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 페르소나 상태 표시
|
|
42
|
+
*/
|
|
43
|
+
async showStatus() {
|
|
44
|
+
try {
|
|
45
|
+
// 파일에서 현재 페르소나 읽기
|
|
46
|
+
const fs = await import('fs/promises');
|
|
47
|
+
const path = await import('path');
|
|
48
|
+
const personaFile = path.join(process.cwd(), '.aiwf', 'personas', 'current.json');
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const content = await fs.readFile(personaFile, 'utf-8');
|
|
52
|
+
const currentPersona = JSON.parse(content);
|
|
53
|
+
|
|
54
|
+
console.log(chalk.cyan('🎭 현재 AI 페르소나 상태'));
|
|
55
|
+
console.log(chalk.gray('-'.repeat(50)));
|
|
56
|
+
console.log(`현재 페르소나: ${chalk.yellow(currentPersona.name)}`);
|
|
57
|
+
console.log(`설명: ${currentPersona.description}`);
|
|
58
|
+
console.log(`활성화 시간: ${new Date(currentPersona.activatedAt).toLocaleString('ko-KR')}`);
|
|
59
|
+
|
|
60
|
+
const behaviors = this.getKoreanBehaviors(currentPersona.name);
|
|
61
|
+
if (behaviors.length > 0) {
|
|
62
|
+
console.log('\n주요 동작:');
|
|
63
|
+
behaviors.forEach(behavior => {
|
|
64
|
+
console.log(` • ${behavior}`);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(chalk.gray('\n' + '-'.repeat(50)));
|
|
69
|
+
console.log(chalk.green('✅ 페르소나가 활성화되어 있습니다.'));
|
|
70
|
+
|
|
71
|
+
} catch (fileError) {
|
|
72
|
+
console.log(chalk.yellow('⚠️ 현재 활성화된 페르소나가 없습니다.'));
|
|
73
|
+
console.log(chalk.gray('사용 가능한 페르소나 목록을 보려면: aiwf persona list'));
|
|
74
|
+
console.log(chalk.gray('페르소나를 설정하려면: aiwf persona set <페르소나명>'));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(chalk.red('❌ 페르소나 상태 확인 중 오류가 발생했습니다:'), error.message);
|
|
79
|
+
console.error(error.stack);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 페르소나 목록 표시
|
|
85
|
+
*/
|
|
86
|
+
async listPersonas() {
|
|
87
|
+
try {
|
|
88
|
+
console.log(chalk.cyan('🎭 사용 가능한 AI 페르소나'));
|
|
89
|
+
console.log(chalk.gray('-'.repeat(50)));
|
|
90
|
+
|
|
91
|
+
// 기본 페르소나 목록을 하드코딩으로 표시 (초기화 문제 회피)
|
|
92
|
+
const defaultPersonas = [
|
|
93
|
+
{ name: 'architect', description: '시스템 설계 및 아키텍처 전문가' },
|
|
94
|
+
{ name: 'developer', description: '일반 개발자 (기본값)' },
|
|
95
|
+
{ name: 'reviewer', description: '코드 리뷰 및 품질 관리 전문가' },
|
|
96
|
+
{ name: 'debugger', description: '디버깅 및 문제 해결 전문가' },
|
|
97
|
+
{ name: 'optimizer', description: '성능 최적화 전문가' },
|
|
98
|
+
{ name: 'security', description: '보안 및 취약점 분석 전문가' },
|
|
99
|
+
{ name: 'documenter', description: '문서화 및 기술 작성 전문가' }
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
defaultPersonas.forEach(persona => {
|
|
103
|
+
const displayName = chalk.yellow(persona.name);
|
|
104
|
+
console.log(` ${displayName} - ${persona.description}`);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log(chalk.gray('\n' + '-'.repeat(50)));
|
|
108
|
+
console.log(chalk.gray('사용법: aiwf persona set <페르소나명>'));
|
|
109
|
+
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(chalk.red('❌ 페르소나 목록 조회 중 오류가 발생했습니다:'), error.message);
|
|
112
|
+
console.error(error.stack);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 페르소나 전환
|
|
118
|
+
*/
|
|
119
|
+
async setPersona(personaName) {
|
|
120
|
+
if (!personaName) {
|
|
121
|
+
console.error(chalk.red('❌ 페르소나 이름을 지정해주세요.'));
|
|
122
|
+
console.log(chalk.gray('사용법: aiwf persona set <페르소나명>'));
|
|
123
|
+
console.log(chalk.gray('사용 가능한 페르소나: aiwf persona list'));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
// 유효한 페르소나인지 확인
|
|
129
|
+
const validPersonas = ['architect', 'developer', 'reviewer', 'debugger', 'optimizer', 'security', 'documenter'];
|
|
130
|
+
|
|
131
|
+
if (!validPersonas.includes(personaName)) {
|
|
132
|
+
console.error(chalk.red(`❌ 페르소나 '${personaName}'를 찾을 수 없습니다.`));
|
|
133
|
+
console.log(chalk.gray('사용 가능한 페르소나: aiwf persona list'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 간단한 페르소나 설정 (파일 기반)
|
|
138
|
+
const fs = await import('fs/promises');
|
|
139
|
+
const path = await import('path');
|
|
140
|
+
const personaDir = path.join(process.cwd(), '.aiwf', 'personas');
|
|
141
|
+
|
|
142
|
+
// 디렉토리 생성
|
|
143
|
+
await fs.mkdir(personaDir, { recursive: true });
|
|
144
|
+
|
|
145
|
+
// 현재 페르소나 저장
|
|
146
|
+
const personaConfig = {
|
|
147
|
+
name: personaName,
|
|
148
|
+
description: this.getKoreanDescription(personaName),
|
|
149
|
+
activatedAt: new Date().toISOString()
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
await fs.writeFile(
|
|
153
|
+
path.join(personaDir, 'current.json'),
|
|
154
|
+
JSON.stringify(personaConfig, null, 2)
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
console.log(chalk.green('🎭 AI 페르소나 활성화 완료'));
|
|
158
|
+
console.log(chalk.gray('-'.repeat(50)));
|
|
159
|
+
console.log(`현재 페르소나: ${chalk.yellow(personaName)}`);
|
|
160
|
+
console.log(`전문 분야: ${this.getKoreanDescription(personaName)}`);
|
|
161
|
+
console.log(chalk.gray('-'.repeat(50)));
|
|
162
|
+
console.log(chalk.green('✅ 페르소나가 성공적으로 활성화되었습니다.'));
|
|
163
|
+
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error(chalk.red('❌ 페르소나 전환 중 오류가 발생했습니다:'), error.message);
|
|
166
|
+
console.error(error.stack);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 페르소나 리셋
|
|
172
|
+
*/
|
|
173
|
+
async resetPersona() {
|
|
174
|
+
try {
|
|
175
|
+
// 기본 페르소나(developer)로 리셋
|
|
176
|
+
await this.setPersona('developer');
|
|
177
|
+
console.log(chalk.green('🔄 페르소나가 기본값(developer)으로 리셋되었습니다.'));
|
|
178
|
+
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(chalk.red('❌ 페르소나 리셋 중 오류가 발생했습니다:'), error.message);
|
|
181
|
+
console.error(error.stack);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 명령어 실행
|
|
187
|
+
*/
|
|
188
|
+
async execute(args) {
|
|
189
|
+
const [subcommand, ...restArgs] = args;
|
|
190
|
+
|
|
191
|
+
switch (subcommand) {
|
|
192
|
+
case 'status':
|
|
193
|
+
await this.showStatus();
|
|
194
|
+
break;
|
|
195
|
+
case 'list':
|
|
196
|
+
await this.listPersonas();
|
|
197
|
+
break;
|
|
198
|
+
case 'set':
|
|
199
|
+
await this.setPersona(restArgs[0]);
|
|
200
|
+
break;
|
|
201
|
+
case 'reset':
|
|
202
|
+
await this.resetPersona();
|
|
203
|
+
break;
|
|
204
|
+
default:
|
|
205
|
+
this.showHelp();
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 도움말 표시
|
|
212
|
+
*/
|
|
213
|
+
showHelp() {
|
|
214
|
+
console.log(chalk.cyan('🎭 AIWF 페르소나 관리'));
|
|
215
|
+
console.log(chalk.gray('-'.repeat(50)));
|
|
216
|
+
console.log('사용법: aiwf persona <명령어> [옵션]');
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log('명령어:');
|
|
219
|
+
console.log(' status 현재 페르소나 상태 표시');
|
|
220
|
+
console.log(' list 사용 가능한 페르소나 목록');
|
|
221
|
+
console.log(' set <페르소나명> 특정 페르소나로 전환');
|
|
222
|
+
console.log(' reset 기본 페르소나로 리셋');
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log('예시:');
|
|
225
|
+
console.log(' aiwf persona status');
|
|
226
|
+
console.log(' aiwf persona list');
|
|
227
|
+
console.log(' aiwf persona set architect');
|
|
228
|
+
console.log(' aiwf persona reset');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* 한국어 설명 가져오기
|
|
233
|
+
*/
|
|
234
|
+
getKoreanDescription(persona) {
|
|
235
|
+
const descriptions = {
|
|
236
|
+
architect: '시스템 설계 및 아키텍처 전문가',
|
|
237
|
+
developer: '일반 개발자 (기본값)',
|
|
238
|
+
reviewer: '코드 리뷰 및 품질 관리 전문가',
|
|
239
|
+
debugger: '디버깅 및 문제 해결 전문가',
|
|
240
|
+
optimizer: '성능 최적화 전문가',
|
|
241
|
+
security: '보안 및 취약점 분석 전문가',
|
|
242
|
+
devops: 'DevOps 및 인프라 관리 전문가',
|
|
243
|
+
tester: '테스트 및 QA 전문가',
|
|
244
|
+
documenter: '문서화 및 기술 작성 전문가',
|
|
245
|
+
mentor: '멘토링 및 교육 전문가'
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return descriptions[persona] || '설명 없음';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 한국어 동작 설명 가져오기
|
|
253
|
+
*/
|
|
254
|
+
getKoreanBehaviors(persona) {
|
|
255
|
+
const behaviors = {
|
|
256
|
+
architect: [
|
|
257
|
+
'큰 그림과 전체 시스템 구조에 집중',
|
|
258
|
+
'확장성과 유지보수성 우선시',
|
|
259
|
+
'디자인 패턴과 아키텍처 원칙 적용',
|
|
260
|
+
'통합 지점과 인터페이스 고려'
|
|
261
|
+
],
|
|
262
|
+
developer: [
|
|
263
|
+
'실용적이고 효율적인 코드 작성',
|
|
264
|
+
'베스트 프랙티스 적용',
|
|
265
|
+
'가독성과 유지보수성 중시'
|
|
266
|
+
],
|
|
267
|
+
reviewer: [
|
|
268
|
+
'코드 품질과 표준 준수 확인',
|
|
269
|
+
'잠재적 버그와 개선점 식별',
|
|
270
|
+
'성능과 보안 측면 검토'
|
|
271
|
+
],
|
|
272
|
+
debugger: [
|
|
273
|
+
'문제의 근본 원인 분석',
|
|
274
|
+
'체계적인 디버깅 접근',
|
|
275
|
+
'재현 가능한 테스트 케이스 생성'
|
|
276
|
+
],
|
|
277
|
+
optimizer: [
|
|
278
|
+
'성능 병목 지점 식별',
|
|
279
|
+
'메모리 사용량 최적화',
|
|
280
|
+
'알고리즘 효율성 개선'
|
|
281
|
+
]
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
return behaviors[persona] || ['기본 동작'];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export default PersonaCommand;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import {
|
|
6
|
+
TOOL_DIRS,
|
|
7
|
+
AIWF_DIRS,
|
|
8
|
+
FILES,
|
|
9
|
+
getBackupDirName as getBackupDirNamePath
|
|
10
|
+
} from '../utils/paths.js';
|
|
11
|
+
|
|
12
|
+
let BACKUP_DIR = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate backup directory name with timestamp
|
|
16
|
+
* @returns {string} Backup directory name
|
|
17
|
+
*/
|
|
18
|
+
function getBackupDirName() {
|
|
19
|
+
return getBackupDirNamePath();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Backup a single file with proper naming convention
|
|
24
|
+
* @param {string} filePath - File to backup
|
|
25
|
+
* @param {string} backupDir - Backup directory path
|
|
26
|
+
* @returns {Promise<string|null>} Backup file path or null
|
|
27
|
+
*/
|
|
28
|
+
async function backupFile(filePath, backupDir) {
|
|
29
|
+
try {
|
|
30
|
+
const exists = await fs.access(filePath).then(() => true).catch(() => false);
|
|
31
|
+
if (exists) {
|
|
32
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
33
|
+
const backupPath = `${filePath}.bak`;
|
|
34
|
+
await fs.copyFile(filePath, backupPath);
|
|
35
|
+
|
|
36
|
+
// Tool-specific backup file naming rules
|
|
37
|
+
let bakFileName;
|
|
38
|
+
if (filePath.startsWith(TOOL_DIRS.CLAUDE_COMMANDS + '/')) {
|
|
39
|
+
// Claude Code command files
|
|
40
|
+
const relativePath = filePath.substring(TOOL_DIRS.CLAUDE_COMMANDS.length + 1);
|
|
41
|
+
bakFileName = `claude-commands-aiwf-${relativePath.replace(/\//g, '-')}.bak`;
|
|
42
|
+
} else if (filePath.startsWith(TOOL_DIRS.GEMINI_PROMPTS + '/')) {
|
|
43
|
+
// Gemini CLI prompt files
|
|
44
|
+
const relativePath = filePath.substring(TOOL_DIRS.GEMINI_PROMPTS.length + 1);
|
|
45
|
+
bakFileName = `gemini-prompts-aiwf-${relativePath.replace(/\//g, '-')}.bak`;
|
|
46
|
+
} else if (filePath.startsWith(TOOL_DIRS.CURSOR_RULES + '/')) {
|
|
47
|
+
// Cursor rule files
|
|
48
|
+
const relativePath = filePath.substring(TOOL_DIRS.CURSOR_RULES.length + 1);
|
|
49
|
+
bakFileName = `cursor-rules-${relativePath.replace(/\//g, '-')}.bak`;
|
|
50
|
+
} else if (filePath.startsWith(TOOL_DIRS.WINDSURF_RULES + '/')) {
|
|
51
|
+
// Windsurf rule files
|
|
52
|
+
const relativePath = filePath.substring(TOOL_DIRS.WINDSURF_RULES.length + 1);
|
|
53
|
+
bakFileName = `windsurf-rules-${relativePath.replace(/\//g, '-')}.bak`;
|
|
54
|
+
} else if (filePath.startsWith(AIWF_DIRS.ROOT + '/')) {
|
|
55
|
+
// AIWF core files
|
|
56
|
+
const relativePath = filePath.substring(AIWF_DIRS.ROOT.length + 1);
|
|
57
|
+
bakFileName = `aiwf-${relativePath.replace(/\//g, '-')}.bak`;
|
|
58
|
+
} else {
|
|
59
|
+
// Other files
|
|
60
|
+
bakFileName = path.basename(filePath) + '.bak';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const destPath = path.join(backupDir, bakFileName);
|
|
64
|
+
await fs.rename(backupPath, destPath);
|
|
65
|
+
return destPath;
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// Backup failed, but continue
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Backup commands and documentation files
|
|
75
|
+
* @param {Object} messages - Localized messages
|
|
76
|
+
* @param {string} [commandsDir] - Commands directory
|
|
77
|
+
* @returns {Promise<Array<string>>} Array of backed up file paths
|
|
78
|
+
*/
|
|
79
|
+
async function backupCommandsAndDocs(messages, commandsDir = TOOL_DIRS.CLAUDE_COMMANDS) {
|
|
80
|
+
if (!BACKUP_DIR) BACKUP_DIR = getBackupDirName();
|
|
81
|
+
const spinner = ora(messages.backingUp).start();
|
|
82
|
+
const backedUpFiles = [];
|
|
83
|
+
await fs.mkdir(BACKUP_DIR, { recursive: true });
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Files that will be updated and need backup
|
|
87
|
+
const filesToBackup = Object.values(FILES.CLAUDE_MD);
|
|
88
|
+
|
|
89
|
+
// Backup CLAUDE.md files
|
|
90
|
+
for (const file of filesToBackup) {
|
|
91
|
+
const backupPath = await backupFile(file, BACKUP_DIR);
|
|
92
|
+
if (backupPath) {
|
|
93
|
+
backedUpFiles.push(backupPath);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Backup all command files
|
|
98
|
+
const commandsExist = await fs.access(commandsDir).then(() => true).catch(() => false);
|
|
99
|
+
if (commandsExist) {
|
|
100
|
+
try {
|
|
101
|
+
const commandFiles = await fs.readdir(commandsDir);
|
|
102
|
+
for (const file of commandFiles) {
|
|
103
|
+
const filePath = path.join(commandsDir, file);
|
|
104
|
+
const stat = await fs.stat(filePath);
|
|
105
|
+
if (stat.isFile() && file.endsWith('.md')) {
|
|
106
|
+
const backupPath = await backupFile(filePath, BACKUP_DIR);
|
|
107
|
+
if (backupPath) {
|
|
108
|
+
backedUpFiles.push(backupPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
// Commands directory might be empty or have issues
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Backup IDE rule files (Cursor)
|
|
118
|
+
const cursorRulesExist = await fs.access(TOOL_DIRS.CURSOR_RULES).then(() => true).catch(() => false);
|
|
119
|
+
if (cursorRulesExist) {
|
|
120
|
+
try {
|
|
121
|
+
const cursorFiles = await fs.readdir(TOOL_DIRS.CURSOR_RULES);
|
|
122
|
+
for (const file of cursorFiles) {
|
|
123
|
+
if (file.endsWith('.mdc')) {
|
|
124
|
+
const filePath = path.join(TOOL_DIRS.CURSOR_RULES, file);
|
|
125
|
+
const backupPath = await backupFile(filePath, BACKUP_DIR);
|
|
126
|
+
if (backupPath) {
|
|
127
|
+
backedUpFiles.push(backupPath);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// Cursor rules directory might have issues
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Backup IDE rule files (Windsurf)
|
|
137
|
+
const windsurfRulesExist = await fs.access(TOOL_DIRS.WINDSURF_RULES).then(() => true).catch(() => false);
|
|
138
|
+
if (windsurfRulesExist) {
|
|
139
|
+
try {
|
|
140
|
+
const windsurfFiles = await fs.readdir(TOOL_DIRS.WINDSURF_RULES);
|
|
141
|
+
for (const file of windsurfFiles) {
|
|
142
|
+
if (file.endsWith('.md')) {
|
|
143
|
+
const filePath = path.join(TOOL_DIRS.WINDSURF_RULES, file);
|
|
144
|
+
const backupPath = await backupFile(filePath, BACKUP_DIR);
|
|
145
|
+
if (backupPath) {
|
|
146
|
+
backedUpFiles.push(backupPath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// Windsurf rules directory might have issues
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (backedUpFiles.length > 0) {
|
|
156
|
+
spinner.succeed(chalk.green(messages.backupComplete.replace('{count}', backedUpFiles.length)));
|
|
157
|
+
} else {
|
|
158
|
+
spinner.succeed(chalk.gray(messages.noFilesToBackup));
|
|
159
|
+
}
|
|
160
|
+
return backedUpFiles;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
spinner.fail(chalk.red(messages.backupFailed));
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Restore files from backup
|
|
169
|
+
* @param {Object} spinner - Ora spinner instance
|
|
170
|
+
* @param {Object} messages - Localized messages
|
|
171
|
+
* @returns {Promise<void>}
|
|
172
|
+
*/
|
|
173
|
+
async function restoreFromBackup(spinner, messages) {
|
|
174
|
+
if (!BACKUP_DIR) return;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
spinner.text = messages.restoringBackup;
|
|
178
|
+
|
|
179
|
+
// Get all backup files
|
|
180
|
+
const backupFiles = await fs.readdir(BACKUP_DIR);
|
|
181
|
+
let restoredCount = 0;
|
|
182
|
+
|
|
183
|
+
for (const bakFile of backupFiles) {
|
|
184
|
+
if (bakFile.endsWith('.bak')) {
|
|
185
|
+
const backupPath = path.join(BACKUP_DIR, bakFile);
|
|
186
|
+
let originalPath;
|
|
187
|
+
|
|
188
|
+
// Determine original path from backup filename
|
|
189
|
+
if (bakFile.startsWith('claude-commands-aiwf-')) {
|
|
190
|
+
const relativePath = bakFile
|
|
191
|
+
.replace('claude-commands-aiwf-', '')
|
|
192
|
+
.replace('.bak', '')
|
|
193
|
+
.replace(/-/g, '/');
|
|
194
|
+
originalPath = path.join(TOOL_DIRS.CLAUDE_COMMANDS, relativePath);
|
|
195
|
+
} else if (bakFile.startsWith('gemini-prompts-aiwf-')) {
|
|
196
|
+
const relativePath = bakFile
|
|
197
|
+
.replace('gemini-prompts-aiwf-', '')
|
|
198
|
+
.replace('.bak', '')
|
|
199
|
+
.replace(/-/g, '/');
|
|
200
|
+
originalPath = path.join(TOOL_DIRS.GEMINI_PROMPTS, relativePath);
|
|
201
|
+
} else if (bakFile.startsWith('cursor-rules-')) {
|
|
202
|
+
const relativePath = bakFile
|
|
203
|
+
.replace('cursor-rules-', '')
|
|
204
|
+
.replace('.bak', '')
|
|
205
|
+
.replace(/-/g, '/');
|
|
206
|
+
originalPath = path.join(TOOL_DIRS.CURSOR_RULES, relativePath);
|
|
207
|
+
} else if (bakFile.startsWith('windsurf-rules-')) {
|
|
208
|
+
const relativePath = bakFile
|
|
209
|
+
.replace('windsurf-rules-', '')
|
|
210
|
+
.replace('.bak', '')
|
|
211
|
+
.replace(/-/g, '/');
|
|
212
|
+
originalPath = path.join(TOOL_DIRS.WINDSURF_RULES, relativePath);
|
|
213
|
+
} else if (bakFile.startsWith('aiwf-')) {
|
|
214
|
+
const relativePath = bakFile
|
|
215
|
+
.replace('aiwf-', '')
|
|
216
|
+
.replace('.bak', '')
|
|
217
|
+
.replace(/-/g, '/');
|
|
218
|
+
originalPath = path.join(AIWF_DIRS.ROOT, relativePath);
|
|
219
|
+
} else {
|
|
220
|
+
// Simple filename.ext.bak format
|
|
221
|
+
originalPath = bakFile.replace('.bak', '');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
// Ensure directory exists
|
|
226
|
+
await fs.mkdir(path.dirname(originalPath), { recursive: true });
|
|
227
|
+
// Copy backup to original location
|
|
228
|
+
await fs.copyFile(backupPath, originalPath);
|
|
229
|
+
restoredCount++;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
// Individual file restore failed, continue with others
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Clean up backup directory
|
|
237
|
+
await fs.rm(BACKUP_DIR, { recursive: true, force: true });
|
|
238
|
+
BACKUP_DIR = null;
|
|
239
|
+
|
|
240
|
+
spinner.text = messages.backupRestored.replace('{count}', restoredCount);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
spinner.fail(chalk.red(messages.restoreFailed));
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get current backup directory
|
|
249
|
+
* @returns {string|null} Current backup directory path
|
|
250
|
+
*/
|
|
251
|
+
function getCurrentBackupDir() {
|
|
252
|
+
return BACKUP_DIR;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Set backup directory
|
|
257
|
+
* @param {string} dir - Backup directory path
|
|
258
|
+
*/
|
|
259
|
+
function setBackupDir(dir) {
|
|
260
|
+
BACKUP_DIR = dir;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export {
|
|
264
|
+
getBackupDirName,
|
|
265
|
+
backupFile,
|
|
266
|
+
backupCommandsAndDocs,
|
|
267
|
+
restoreFromBackup,
|
|
268
|
+
getCurrentBackupDir,
|
|
269
|
+
setBackupDir
|
|
270
|
+
};
|