aiwf 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/.aiwf/00_PROJECT_MANIFEST.md +29 -16
  2. package/.aiwf/02_REQUIREMENTS/M03_Simone_Integration_Enhancement/M03_milestone_meta.md +55 -0
  3. package/.aiwf/02_REQUIREMENTS/M03_Simone_Integration_Enhancement/SPECS_AIWF_Simplified_Plan.md +221 -0
  4. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/S03_M02_sprint_meta.md +0 -1
  5. package/.aiwf/metrics/metrics.json +18 -0
  6. package/.aiwf/personas/current.json +5 -0
  7. package/.aiwf/test-results-s02.json +5 -5
  8. package/.claude/commands/aiwf/aiwf_add_task_to_sprint.md +283 -0
  9. package/.claude/commands/aiwf/aiwf_install_git_hooks.md +63 -0
  10. package/.claude/commands/aiwf/aiwf_link_feature_branch.md +43 -0
  11. package/.claude/commands/aiwf/aiwf_link_feature_commit.md +46 -0
  12. package/.claude/commands/aiwf/aiwf_persona_architect.md +1 -1
  13. package/.claude/commands/aiwf/aiwf_persona_backend.md +1 -1
  14. package/.claude/commands/aiwf/aiwf_persona_data_analyst.md +1 -1
  15. package/.claude/commands/aiwf/aiwf_persona_frontend.md +1 -1
  16. package/.claude/commands/aiwf/aiwf_persona_security.md +1 -1
  17. package/.claude/commands/aiwf/aiwf_persona_status.md +1 -1
  18. package/CHANGELOG.md +54 -5
  19. package/claude-code/aiwf/ko/commands/ai-persona.js +1 -1
  20. package/package.json +1 -1
  21. package/src/cli/index.js +58 -2
  22. package/src/commands/create-project.js +10 -7
  23. package/src/commands/persona.js +288 -0
  24. package/src/commands/sprint-task.js +262 -0
  25. package/src/lib/file-downloader.js +5 -0
  26. package/src/lib/installer.js +14 -4
  27. package/tests/integration/test-persona-system/.aiwf/metrics/metrics.json +833 -0
  28. package/tests/unit/sprint-task.test.js +172 -0
  29. package/.aiwf/sessions/session_1752877859489_1t7n2fhdb.json +0 -7
  30. package/.aiwf/sessions/session_1752877859515_cwdv211ry.json +0 -7
  31. package/.aiwf/sessions/session_1752877860664_huziadmbo.json +0 -7
  32. package/.aiwf/sessions/session_1752877860666_kyr5u1np6.json +0 -7
  33. package/.aiwf/sessions/session_1752877861848_9yinepw3t.json +0 -7
  34. package/.aiwf/sessions/session_1752877861874_y32sputuc.json +0 -7
  35. package/.aiwf/sessions/session_1752877862685_ivxbl5rhz.json +0 -7
  36. package/.aiwf/sessions/session_1752877862820_wnq4glvxi.json +0 -7
  37. package/.aiwf/sessions/session_1752877864632_3ebnpaelb.json +0 -7
  38. package/.aiwf/sessions/session_1752877864879_b9zgjcb7d.json +0 -7
  39. package/.aiwf/sessions/session_1752877865533_x14m1vodi.json +0 -7
  40. package/.aiwf/sessions/session_1752877865562_gkoh19n7d.json +0 -7
  41. package/.aiwf/sessions/session_1752877866333_wp8qpdcoh.json +0 -7
  42. package/.aiwf/sessions/session_1752877866509_dawntfpab.json +0 -7
  43. package/.aiwf/sessions/session_1752877866890_plc5vfucl.json +0 -7
  44. package/.aiwf/sessions/session_1752877866895_ga8hmq3oi.json +0 -7
  45. package/.aiwf/sessions/session_1752877867761_ayjp4v5f5.json +0 -7
  46. package/.aiwf/sessions/session_1752877867892_lfz6jy252.json +0 -7
  47. package/.aiwf/sessions/session_1752877868139_7bqd9ubli.json +0 -7
  48. package/.aiwf/sessions/session_1752877868162_zq3ofjbsh.json +0 -7
  49. package/.aiwf/sessions/session_1752877869054_7rlgm9jt6.json +0 -7
  50. package/.aiwf/sessions/session_1752877869477_ax3nhbxzr.json +0 -7
  51. package/.aiwf/sessions/session_1752877870095_6hgp4vl1e.json +0 -7
  52. package/.aiwf/sessions/session_1752877870381_qfcrdg4li.json +0 -7
  53. package/.aiwf/sessions/session_1752877871895_pgd5vllhz.json +0 -7
  54. package/.aiwf/sessions/session_1752877872199_jbhv2g2da.json +0 -7
  55. package/.aiwf/sessions/session_1752877872680_x04wpffu6.json +0 -7
  56. package/.aiwf/sessions/session_1752877872895_3y4vbeauo.json +0 -7
  57. package/.aiwf/sessions/session_1752877873178_3maolzc6b.json +0 -7
  58. package/.aiwf/sessions/session_1752877873180_ytm83j07f.json +0 -7
  59. package/.aiwf/sessions/session_1752877874217_dp56c9t42.json +0 -7
  60. package/.aiwf/sessions/session_1752877874415_epwp1g1og.json +0 -7
  61. package/.aiwf/sessions/session_1752877874454_heicyuryb.json +0 -7
  62. package/.aiwf/sessions/session_1752877874869_1c2illeu5.json +0 -7
  63. package/.aiwf/sessions/session_1752877874879_k1vo4vxyx.json +0 -7
  64. package/.aiwf/sessions/session_1752877874913_bwgyiz9bp.json +0 -7
  65. package/.aiwf/sessions/session_1752877876218_44807kztz.json +0 -7
  66. package/.aiwf/sessions/session_1752877876515_qkhjv5kpw.json +0 -7
  67. package/.aiwf/sessions/session_1752877876543_p605aud1m.json +0 -7
  68. package/.aiwf/sessions/session_1752877876819_l65w7ifck.json +0 -7
  69. package/.aiwf/sessions/session_1752877876867_xrhzss4we.json +0 -7
  70. package/.aiwf/sessions/session_1752877876872_u7aso54qb.json +0 -7
  71. package/.aiwf/sessions/session_1752877877705_telnfg4o6.json +0 -7
  72. package/.aiwf/sessions/session_1752877878285_6s45im2ib.json +0 -7
  73. package/.aiwf/sessions/session_1752877878506_v0dvguqhz.json +0 -7
  74. package/.aiwf/sessions/session_1752877878581_3ri3yc66a.json +0 -7
  75. package/.aiwf/sessions/session_1752877878756_a82x9s2pd.json +0 -7
  76. package/.aiwf/sessions/session_1752877878886_20pjsfour.json +0 -7
  77. package/.aiwf/sessions/session_1752877880266_fm1ydjw0g.json +0 -7
  78. package/.aiwf/sessions/session_1752877881149_xbat4z03d.json +0 -7
  79. package/.aiwf/sessions/session_1752877881551_qy5sic9qs.json +0 -7
  80. package/.aiwf/sessions/session_1752877881554_24ugd96wu.json +0 -7
  81. package/.aiwf/sessions/session_1752877881797_e9mm25uh0.json +0 -7
  82. package/.aiwf/sessions/session_1752877881822_bxvjj8c9g.json +0 -7
  83. package/.aiwf/sessions/session_1752877881890_jv7l6jve3.json +0 -7
  84. package/.aiwf/sessions/session_1752877881991_f0qugdtcq.json +0 -7
  85. package/.aiwf/sessions/session_1752877883387_0bvw9rvnx.json +0 -7
  86. package/.aiwf/sessions/session_1752877883820_q6g2md2up.json +0 -7
  87. package/.aiwf/sessions/session_1752877884393_sqr7trvjl.json +0 -7
  88. package/.aiwf/sessions/session_1752877884500_whtypmzlf.json +0 -7
  89. package/.aiwf/sessions/session_1752877884645_y9mvvxli1.json +0 -7
  90. package/.aiwf/sessions/session_1752877884733_hk46eenel.json +0 -7
  91. package/.aiwf/sessions/session_1752877884738_c62j6214r.json +0 -7
  92. package/.aiwf/sessions/session_1752877884771_temhxehoz.json +0 -7
  93. package/.aiwf/sessions/session_1752877885643_6v5fcktc7.json +0 -7
  94. package/.aiwf/sessions/session_1752877886426_ee8di2fqh.json +0 -7
  95. package/.aiwf/sessions/session_1752877886549_3xj6tdoew.json +0 -7
  96. package/.aiwf/sessions/session_1752877886604_aj80yyfco.json +0 -7
  97. package/.aiwf/sessions/session_1752877886702_2b8841vyg.json +0 -7
  98. package/.aiwf/sessions/session_1752877886712_ntqc8b8bz.json +0 -7
  99. package/.aiwf/sessions/session_1752877886968_946m8tbw6.json +0 -7
  100. package/.aiwf/sessions/session_1752877887008_wg6zfg8dn.json +0 -7
  101. package/.aiwf/sessions/session_1752877888479_ciiu2qiqs.json +0 -7
  102. package/.aiwf/sessions/session_1752877889330_ravg8l66k.json +0 -7
  103. package/.aiwf/sessions/session_1752877889542_7zepkhjkc.json +0 -7
  104. package/.aiwf/sessions/session_1752877889638_ev4htqiu0.json +0 -7
  105. package/.aiwf/sessions/session_1752877889956_szphy2qma.json +0 -7
  106. package/.aiwf/sessions/session_1752877890220_o6aaa57tt.json +0 -7
  107. package/.aiwf/sessions/session_1752877890527_v2loy5vu1.json +0 -7
  108. package/.aiwf/sessions/session_1752877890572_cwq8fiuma.json +0 -7
  109. package/.aiwf/sessions/session_1752877892200_sgcwq1alt.json +0 -7
  110. package/.aiwf/sessions/session_1752877892268_v8kexpbc5.json +0 -7
  111. package/.aiwf/sessions/session_1752877892384_1gbbgrp4y.json +0 -7
  112. package/.aiwf/sessions/session_1752877893568_9p2n5p31a.json +0 -7
  113. package/.aiwf/sessions/session_1752877893727_un34a5tvp.json +0 -7
  114. package/.aiwf/sessions/session_1752877893778_01j79sw9i.json +0 -7
  115. package/.aiwf/sessions/session_1752877894053_jrhba6wko.json +0 -7
  116. package/.aiwf/sessions/session_1752877894082_x3bghawox.json +0 -7
  117. package/.aiwf/sessions/session_1752877894500_iytiav2ku.json +0 -7
  118. package/.aiwf/sessions/session_1752877894667_ucn0gc2ge.json +0 -7
  119. package/test-token-system.js +0 -88
  120. package/tests/temp_invalid_json/invalid.json +0 -1
  121. /package/{.aiwf/templates → templates}/README.md +0 -0
  122. /package/{.aiwf/templates → templates}/api-server/config.json +0 -0
  123. /package/{.aiwf/templates → templates}/api-server/template/.aiwf/config.json +0 -0
  124. /package/{.aiwf/templates → templates}/api-server/template/.aiwf/feature-ledger.json +0 -0
  125. /package/{.aiwf/templates → templates}/api-server/template/.aiwf/personas/backend-engineer.json +0 -0
  126. /package/{.aiwf/templates → templates}/api-server/template/.aiwf/scripts/cli.js +0 -0
  127. /package/{.aiwf/templates → templates}/api-server/template/.env.example +0 -0
  128. /package/{.aiwf/templates → templates}/api-server/template/.eslintrc.json +0 -0
  129. /package/{.aiwf/templates → templates}/api-server/template/README.md +0 -0
  130. /package/{.aiwf/templates → templates}/api-server/template/jest.config.js +0 -0
  131. /package/{.aiwf/templates → templates}/api-server/template/nodemon.json +0 -0
  132. /package/{.aiwf/templates → templates}/api-server/template/package.json +0 -0
  133. /package/{.aiwf/templates → templates}/api-server/template/src/app.ts +0 -0
  134. /package/{.aiwf/templates → templates}/api-server/template/src/config/swagger.ts +0 -0
  135. /package/{.aiwf/templates → templates}/api-server/template/src/controllers/aiwfController.ts +0 -0
  136. /package/{.aiwf/templates → templates}/api-server/template/src/controllers/authController.ts +0 -0
  137. /package/{.aiwf/templates → templates}/api-server/template/src/controllers/statusController.ts +0 -0
  138. /package/{.aiwf/templates → templates}/api-server/template/src/index.ts +0 -0
  139. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/aiwfMiddleware.ts +0 -0
  140. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/authMiddleware.ts +0 -0
  141. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/errorHandler.ts +0 -0
  142. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/notFoundHandler.ts +0 -0
  143. /package/{.aiwf/templates → templates}/api-server/template/src/middleware/requestLogger.ts +0 -0
  144. /package/{.aiwf/templates → templates}/api-server/template/src/routes/aiwf.ts +0 -0
  145. /package/{.aiwf/templates → templates}/api-server/template/src/routes/index.ts +0 -0
  146. /package/{.aiwf/templates → templates}/api-server/template/src/routes/v1/index.ts +0 -0
  147. /package/{.aiwf/templates → templates}/api-server/template/src/utils/logger.ts +0 -0
  148. /package/{.aiwf/templates → templates}/api-server/template/tests/app.test.ts +0 -0
  149. /package/{.aiwf/templates → templates}/api-server/template/tsconfig.json +0 -0
  150. /package/{.aiwf/templates → templates}/npm-library/config.json +0 -0
  151. /package/{.aiwf/templates → templates}/npm-library/template/LICENSE +0 -0
  152. /package/{.aiwf/templates → templates}/npm-library/template/README.md +0 -0
  153. /package/{.aiwf/templates → templates}/npm-library/template/package.json +0 -0
  154. /package/{.aiwf/templates → templates}/web-app/config.json +0 -0
  155. /package/{.aiwf/templates → templates}/web-app/template/.aiwf/config.json +0 -0
  156. /package/{.aiwf/templates → templates}/web-app/template/.aiwf/feature-ledger.json +0 -0
  157. /package/{.aiwf/templates → templates}/web-app/template/.aiwf/personas/fullstack-developer.json +0 -0
  158. /package/{.aiwf/templates → templates}/web-app/template/.aiwf/scripts/cli.js +0 -0
  159. /package/{.aiwf/templates → templates}/web-app/template/.eslintrc.cjs +0 -0
  160. /package/{.aiwf/templates → templates}/web-app/template/README.md +0 -0
  161. /package/{.aiwf/templates → templates}/web-app/template/index.html +0 -0
  162. /package/{.aiwf/templates → templates}/web-app/template/package.json +0 -0
  163. /package/{.aiwf/templates → templates}/web-app/template/postcss.config.js +0 -0
  164. /package/{.aiwf/templates → templates}/web-app/template/public/vite.svg +0 -0
  165. /package/{.aiwf/templates → templates}/web-app/template/src/App.tsx +0 -0
  166. /package/{.aiwf/templates → templates}/web-app/template/src/components/Layout.tsx +0 -0
  167. /package/{.aiwf/templates → templates}/web-app/template/src/components/aiwf/ContextStatus.tsx +0 -0
  168. /package/{.aiwf/templates → templates}/web-app/template/src/components/aiwf/FeatureLedger.tsx +0 -0
  169. /package/{.aiwf/templates → templates}/web-app/template/src/components/aiwf/TokenUsage.tsx +0 -0
  170. /package/{.aiwf/templates → templates}/web-app/template/src/index.css +0 -0
  171. /package/{.aiwf/templates → templates}/web-app/template/src/main.tsx +0 -0
  172. /package/{.aiwf/templates → templates}/web-app/template/src/pages/AiwfDashboard.tsx +0 -0
  173. /package/{.aiwf/templates → templates}/web-app/template/src/pages/HomePage.tsx +0 -0
  174. /package/{.aiwf/templates → templates}/web-app/template/src/pages/NotFound.tsx +0 -0
  175. /package/{.aiwf/templates → templates}/web-app/template/src/stores/aiwfStore.ts +0 -0
  176. /package/{.aiwf/templates → templates}/web-app/template/src/types/global.d.ts +0 -0
  177. /package/{.aiwf/templates → templates}/web-app/template/src/vite-env.d.ts +0 -0
  178. /package/{.aiwf/templates → templates}/web-app/template/tailwind.config.js +0 -0
  179. /package/{.aiwf/templates → templates}/web-app/template/tsconfig.json +0 -0
  180. /package/{.aiwf/templates → templates}/web-app/template/tsconfig.node.json +0 -0
  181. /package/{.aiwf/templates → templates}/web-app/template/vite.config.ts +0 -0
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs-extra');
4
- const path = require('path');
5
- const prompts = require('prompts');
6
- const chalk = require('chalk');
7
- const ora = require('ora');
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import prompts from 'prompts';
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
8
9
 
9
- const TEMPLATES_DIR = path.join(__dirname, '../templates');
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const TEMPLATES_DIR = path.join(__dirname, '../../templates');
10
13
 
11
14
  async function createProject() {
12
15
  console.log(chalk.cyan('\n🚀 AIWF 프로젝트 생성기\n'));
@@ -148,4 +151,4 @@ if (require.main === module) {
148
151
  });
149
152
  }
150
153
 
151
- module.exports = { createProject };
154
+ export { createProject };
@@ -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,262 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * 스프린트 태스크 관리 명령어
5
+ * 스프린트에 개별 태스크를 추가하고 관리하는 기능
6
+ */
7
+
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import chalk from 'chalk';
11
+ import ora from 'ora';
12
+
13
+ // 프로젝트 루트에서 .aiwf 디렉토리 찾기
14
+ async function findProjectRoot(startDir = process.cwd()) {
15
+ let currentDir = startDir;
16
+
17
+ while (currentDir !== path.parse(currentDir).root) {
18
+ try {
19
+ const aiwfPath = path.join(currentDir, '.aiwf');
20
+ await fs.access(aiwfPath);
21
+ return currentDir;
22
+ } catch {
23
+ currentDir = path.dirname(currentDir);
24
+ }
25
+ }
26
+
27
+ throw new Error('.aiwf 디렉토리를 찾을 수 없습니다. AIWF 프로젝트 내에서 실행해주세요.');
28
+ }
29
+
30
+ // 스프린트 존재 확인
31
+ async function verifySprintExists(projectRoot, sprintId) {
32
+ const sprintsDir = path.join(projectRoot, '.aiwf', '03_SPRINTS');
33
+
34
+ try {
35
+ const entries = await fs.readdir(sprintsDir);
36
+ const sprintDir = entries.find(entry => entry.startsWith(sprintId + '_'));
37
+
38
+ if (!sprintDir) {
39
+ throw new Error(`스프린트 ${sprintId}를 찾을 수 없습니다.`);
40
+ }
41
+
42
+ return path.join(sprintsDir, sprintDir);
43
+ } catch (error) {
44
+ throw new Error(`스프린트 디렉토리 읽기 실패: ${error.message}`);
45
+ }
46
+ }
47
+
48
+ // 다음 태스크 번호 결정
49
+ async function getNextTaskNumber(sprintPath, sprintId) {
50
+ try {
51
+ const files = await fs.readdir(sprintPath);
52
+ const taskFiles = files.filter(f => f.match(/^T\d+_/));
53
+
54
+ if (taskFiles.length === 0) {
55
+ return 'T01';
56
+ }
57
+
58
+ const numbers = taskFiles.map(f => {
59
+ const match = f.match(/^T(\d+)_/);
60
+ return match ? parseInt(match[1]) : 0;
61
+ });
62
+
63
+ const maxNumber = Math.max(...numbers);
64
+ return `T${String(maxNumber + 1).padStart(2, '0')}`;
65
+ } catch {
66
+ return 'T01';
67
+ }
68
+ }
69
+
70
+ // 태스크 파일 생성
71
+ async function createTaskFile(sprintPath, taskNumber, sprintId, taskTitle) {
72
+ const timestamp = new Date().toISOString().slice(0, 16).replace('T', ' ');
73
+ const fileName = `${taskNumber}_${sprintId}_${taskTitle.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_가-힣]/g, '')}.md`;
74
+ const filePath = path.join(sprintPath, fileName);
75
+
76
+ const content = `# ${taskNumber}: ${taskTitle}
77
+
78
+ **스프린트**: ${sprintId}
79
+ **생성일**: ${timestamp}
80
+ **상태**: pending
81
+ **우선순위**: medium
82
+ **예상 시간**: TBD
83
+
84
+ ## 개요
85
+
86
+ [태스크에 대한 간단한 설명을 작성하세요]
87
+
88
+ ## 요구사항
89
+
90
+ - [ ] [구체적인 요구사항 1]
91
+ - [ ] [구체적인 요구사항 2]
92
+ - [ ] [구체적인 요구사항 3]
93
+
94
+ ## 승인 기준
95
+
96
+ - [ ] [완료 조건 1]
97
+ - [ ] [완료 조건 2]
98
+ - [ ] [완료 조건 3]
99
+
100
+ ## 기술 가이드라인
101
+
102
+ - [관련 아키텍처 결정 참조]
103
+ - [기존 코드베이스 패턴 참조]
104
+ - [사용할 기술 스택]
105
+
106
+ ## 구현 노트
107
+
108
+ - [구현 시 고려사항]
109
+ - [잠재적 리스크]
110
+ - [테스트 전략]
111
+
112
+ ## 참조
113
+
114
+ - [관련 문서나 코드 링크]
115
+ `;
116
+
117
+ await fs.writeFile(filePath, content);
118
+ return { filePath, fileName, taskNumber };
119
+ }
120
+
121
+ // 스프린트 메타 파일 업데이트
122
+ async function updateSprintMeta(sprintPath, taskNumber, taskTitle) {
123
+ const files = await fs.readdir(sprintPath);
124
+ const metaFile = files.find(f => f.includes('sprint_meta.md'));
125
+
126
+ if (!metaFile) {
127
+ console.warn(chalk.yellow('⚠️ 스프린트 메타 파일을 찾을 수 없습니다.'));
128
+ return;
129
+ }
130
+
131
+ const metaPath = path.join(sprintPath, metaFile);
132
+ let content = await fs.readFile(metaPath, 'utf-8');
133
+
134
+ // 태스크 섹션 찾기 및 업데이트
135
+ const taskSectionRegex = /## 태스크 목록[\s\S]*?(?=##|$)/;
136
+ const taskMatch = content.match(taskSectionRegex);
137
+
138
+ if (taskMatch) {
139
+ const newTaskLine = `- [ ] ${taskNumber}: ${taskTitle} - 상태: pending\n`;
140
+ const updatedSection = taskMatch[0] + newTaskLine;
141
+ content = content.replace(taskSectionRegex, updatedSection);
142
+ } else {
143
+ // 태스크 섹션이 없으면 추가
144
+ content += `\n## 태스크 목록\n\n- [ ] ${taskNumber}: ${taskTitle} - 상태: pending\n`;
145
+ }
146
+
147
+ await fs.writeFile(metaPath, content);
148
+ }
149
+
150
+ // 매니페스트 업데이트
151
+ async function updateManifest(projectRoot, sprintId, taskNumber, taskTitle) {
152
+ const manifestPath = path.join(projectRoot, '.aiwf', '00_PROJECT_MANIFEST.md');
153
+
154
+ try {
155
+ let content = await fs.readFile(manifestPath, 'utf-8');
156
+
157
+ // 최종 업데이트 날짜 변경
158
+ const dateRegex = /\*\*마지막 업데이트\*\*: \d{4}-\d{2}-\d{2}/;
159
+ const today = new Date().toISOString().slice(0, 10);
160
+ content = content.replace(dateRegex, `**마지막 업데이트**: ${today}`);
161
+
162
+ // 활성 태스크 섹션에 추가
163
+ const activeTasksRegex = /## 활성 태스크[\s\S]*?### 계획됨/;
164
+ const match = content.match(activeTasksRegex);
165
+
166
+ if (match) {
167
+ const newTaskLine = `\n- **${taskNumber}_${sprintId}**: ${taskTitle} (pending → ${today} 생성)\n`;
168
+ const updatedSection = match[0].replace('### 계획됨', newTaskLine + '### 계획됨');
169
+ content = content.replace(activeTasksRegex, updatedSection);
170
+ }
171
+
172
+ // 태스크 통계 업데이트
173
+ const statsRegex = /- \*\*총 태스크\*\*: (\d+)/;
174
+ const statsMatch = content.match(statsRegex);
175
+ if (statsMatch) {
176
+ const total = parseInt(statsMatch[1]) + 1;
177
+ content = content.replace(statsRegex, `- **총 태스크**: ${total}`);
178
+ }
179
+
180
+ await fs.writeFile(manifestPath, content);
181
+ } catch (error) {
182
+ console.warn(chalk.yellow(`⚠️ 매니페스트 업데이트 실패: ${error.message}`));
183
+ }
184
+ }
185
+
186
+ // 메인 함수
187
+ export async function addTaskToSprint(sprintId, taskTitle) {
188
+ const spinner = ora('스프린트에 태스크 추가 중...').start();
189
+
190
+ try {
191
+ // 1. 프로젝트 루트 찾기
192
+ const projectRoot = await findProjectRoot();
193
+
194
+ // 2. 스프린트 존재 확인
195
+ spinner.text = '스프린트 확인 중...';
196
+ const sprintPath = await verifySprintExists(projectRoot, sprintId);
197
+
198
+ // 3. 다음 태스크 번호 결정
199
+ spinner.text = '태스크 번호 결정 중...';
200
+ const taskNumber = await getNextTaskNumber(sprintPath, sprintId);
201
+
202
+ // 4. 태스크 파일 생성
203
+ spinner.text = '태스크 파일 생성 중...';
204
+ const { filePath, fileName, taskNumber: finalTaskNumber } = await createTaskFile(
205
+ sprintPath,
206
+ taskNumber,
207
+ sprintId,
208
+ taskTitle
209
+ );
210
+
211
+ // 5. 스프린트 메타 업데이트
212
+ spinner.text = '스프린트 메타 업데이트 중...';
213
+ await updateSprintMeta(sprintPath, finalTaskNumber, taskTitle);
214
+
215
+ // 6. 매니페스트 업데이트
216
+ spinner.text = '프로젝트 매니페스트 업데이트 중...';
217
+ await updateManifest(projectRoot, sprintId, finalTaskNumber, taskTitle);
218
+
219
+ spinner.succeed('태스크 추가 완료!');
220
+
221
+ // 성공 메시지
222
+ console.log('\n' + chalk.green('✅ 태스크 추가 완료!'));
223
+ console.log('\n📄 생성된 파일: ' + chalk.cyan(fileName));
224
+ console.log('🏃 스프린트: ' + chalk.yellow(sprintId));
225
+ console.log('📋 태스크: ' + chalk.yellow(`${finalTaskNumber} - ${taskTitle}`));
226
+ console.log('⏱️ 상태: ' + chalk.blue('pending'));
227
+
228
+ console.log('\n' + chalk.bold('📝 다음 단계:'));
229
+ console.log('1. 태스크 파일을 열어 세부사항 작성');
230
+ console.log(`2. ${chalk.cyan(`/aiwf:do_task ${finalTaskNumber}`)}로 작업 시작`);
231
+ console.log(`3. 완료 후 ${chalk.cyan(`/aiwf:update_task_status ${finalTaskNumber} completed`)} 실행`);
232
+
233
+ console.log('\n💡 팁: 태스크 목록 확인은 ' + chalk.cyan(`/aiwf:list_sprint_tasks ${sprintId}`));
234
+
235
+ return { success: true, taskNumber: finalTaskNumber, filePath };
236
+
237
+ } catch (error) {
238
+ spinner.fail(`태스크 추가 실패: ${error.message}`);
239
+ throw error;
240
+ }
241
+ }
242
+
243
+ // CLI 실행
244
+ if (import.meta.url === `file://${process.argv[1]}`) {
245
+ const args = process.argv.slice(2);
246
+
247
+ if (args.length < 2) {
248
+ console.error(chalk.red('사용법: aiwf-sprint-task <스프린트ID> <태스크 제목>'));
249
+ console.error(chalk.gray('예시: aiwf-sprint-task S03 "API 성능 최적화"'));
250
+ process.exit(1);
251
+ }
252
+
253
+ const [sprintId, ...titleParts] = args;
254
+ const taskTitle = titleParts.join(' ').replace(/^["']|["']$/g, '');
255
+
256
+ addTaskToSprint(sprintId, taskTitle).catch(error => {
257
+ console.error(chalk.red(`오류: ${error.message}`));
258
+ process.exit(1);
259
+ });
260
+ }
261
+
262
+ export default addTaskToSprint;
@@ -1,5 +1,6 @@
1
1
  import https from 'https';
2
2
  import { createWriteStream } from 'fs';
3
+ import fs from 'fs/promises';
3
4
  import { pipeline } from 'stream/promises';
4
5
  import path from 'path';
5
6
 
@@ -109,6 +110,10 @@ async function fetchContent(url) {
109
110
  * @returns {Promise<void>}
110
111
  */
111
112
  async function downloadFile(url, destPath) {
113
+ // Ensure destination directory exists
114
+ const destDir = path.dirname(destPath);
115
+ await fs.mkdir(destDir, { recursive: true });
116
+
112
117
  return new Promise((resolve, reject) => {
113
118
  https.get(url, { headers: { 'User-Agent': 'aiwf' } }, (response) => {
114
119
  if (response.statusCode !== 200) {
@@ -182,9 +182,14 @@ async function handleExistingInstallation(msg, options) {
182
182
  async function createDirectoryStructure() {
183
183
  const aiwfDirs = getAllAiwfDirectories();
184
184
 
185
+ // Create AIWF directories
185
186
  for (const dir of aiwfDirs) {
186
187
  await fs.mkdir(dir, { recursive: true });
187
188
  }
189
+
190
+ // Create tool-specific directories
191
+ await fs.mkdir(TOOL_DIRS.CURSOR_RULES, { recursive: true });
192
+ await fs.mkdir(TOOL_DIRS.WINDSURF_RULES, { recursive: true });
188
193
  }
189
194
 
190
195
  /**
@@ -301,7 +306,7 @@ async function downloadAndProcessRules(spinner, msg, debugLog) {
301
306
  // Try to download rules/global from GitHub
302
307
  try {
303
308
  await downloadDirectory(
304
- `${GITHUB_CONTENT_PREFIX}/rules/global`,
309
+ `rules/global`,
305
310
  tmpRulesGlobal,
306
311
  spinner,
307
312
  debugLog
@@ -310,20 +315,25 @@ async function downloadAndProcessRules(spinner, msg, debugLog) {
310
315
  } catch (error) {
311
316
  logWithSpinner(spinner, `Failed to download rules/global from GitHub, trying local files`, debugLog);
312
317
  // Fallback to local files
313
- const localRulesGlobal = 'rules/global';
318
+ const localRulesGlobal = path.join(process.cwd(), 'rules/global');
314
319
  try {
320
+ logWithSpinner(spinner, `Trying to access: ${localRulesGlobal}`, debugLog);
315
321
  await fs.access(localRulesGlobal);
322
+ logWithSpinner(spinner, `Successfully accessed local rules/global`, debugLog);
316
323
  // Copy local files to temp directory
317
324
  await fs.mkdir(tmpRulesGlobal, { recursive: true });
318
325
  const files = await fs.readdir(localRulesGlobal);
326
+ logWithSpinner(spinner, `Found ${files.length} files in rules/global: ${files.join(', ')}`, debugLog);
319
327
  for (const file of files) {
320
328
  const srcPath = path.join(localRulesGlobal, file);
321
329
  const destPath = path.join(tmpRulesGlobal, file);
322
330
  await fs.copyFile(srcPath, destPath);
331
+ logWithSpinner(spinner, `Copied: ${file}`, debugLog);
323
332
  }
324
333
  useLocalGlobal = true;
325
334
  logWithSpinner(spinner, `Using local rules/global directory`, debugLog);
326
335
  } catch (localError) {
336
+ logWithSpinner(spinner, `Local rules/global error: ${localError.message}`, debugLog);
327
337
  logWithSpinner(spinner, msg.rulesGlobalNotFound, debugLog);
328
338
  }
329
339
  }
@@ -331,7 +341,7 @@ async function downloadAndProcessRules(spinner, msg, debugLog) {
331
341
  // Try to download rules/manual from GitHub
332
342
  try {
333
343
  await downloadDirectory(
334
- `${GITHUB_CONTENT_PREFIX}/rules/manual`,
344
+ `rules/manual`,
335
345
  tmpRulesManual,
336
346
  spinner,
337
347
  debugLog
@@ -340,7 +350,7 @@ async function downloadAndProcessRules(spinner, msg, debugLog) {
340
350
  } catch (error) {
341
351
  logWithSpinner(spinner, `Failed to download rules/manual from GitHub, trying local files`, debugLog);
342
352
  // Fallback to local files
343
- const localRulesManual = 'rules/manual';
353
+ const localRulesManual = path.join(process.cwd(), 'rules/manual');
344
354
  try {
345
355
  await fs.access(localRulesManual);
346
356
  // Copy local files to temp directory