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.
Files changed (208) hide show
  1. package/.aiwf/config/language.json +1 -1
  2. package/.aiwf/metrics/metrics.json +18 -0
  3. package/.aiwf/personas/current.json +5 -0
  4. package/.aiwf/sessions/session_1752877859489_1t7n2fhdb.json +7 -0
  5. package/.aiwf/sessions/session_1752877859515_cwdv211ry.json +7 -0
  6. package/.aiwf/sessions/session_1752877860664_huziadmbo.json +7 -0
  7. package/.aiwf/sessions/session_1752877860666_kyr5u1np6.json +7 -0
  8. package/.aiwf/sessions/session_1752877861848_9yinepw3t.json +7 -0
  9. package/.aiwf/sessions/session_1752877861874_y32sputuc.json +7 -0
  10. package/.aiwf/sessions/session_1752877862685_ivxbl5rhz.json +7 -0
  11. package/.aiwf/sessions/session_1752877862820_wnq4glvxi.json +7 -0
  12. package/.aiwf/sessions/session_1752877864632_3ebnpaelb.json +7 -0
  13. package/.aiwf/sessions/session_1752877864879_b9zgjcb7d.json +7 -0
  14. package/.aiwf/sessions/session_1752877865533_x14m1vodi.json +7 -0
  15. package/.aiwf/sessions/session_1752877865562_gkoh19n7d.json +7 -0
  16. package/.aiwf/sessions/session_1752877866333_wp8qpdcoh.json +7 -0
  17. package/.aiwf/sessions/session_1752877866509_dawntfpab.json +7 -0
  18. package/.aiwf/sessions/session_1752877866890_plc5vfucl.json +7 -0
  19. package/.aiwf/sessions/session_1752877866895_ga8hmq3oi.json +7 -0
  20. package/.aiwf/sessions/session_1752877867761_ayjp4v5f5.json +7 -0
  21. package/.aiwf/sessions/session_1752877867892_lfz6jy252.json +7 -0
  22. package/.aiwf/sessions/session_1752877868139_7bqd9ubli.json +7 -0
  23. package/.aiwf/sessions/session_1752877868162_zq3ofjbsh.json +7 -0
  24. package/.aiwf/sessions/session_1752877869054_7rlgm9jt6.json +7 -0
  25. package/.aiwf/sessions/session_1752877869477_ax3nhbxzr.json +7 -0
  26. package/.aiwf/sessions/session_1752877870095_6hgp4vl1e.json +7 -0
  27. package/.aiwf/sessions/session_1752877870381_qfcrdg4li.json +7 -0
  28. package/.aiwf/sessions/session_1752877871895_pgd5vllhz.json +7 -0
  29. package/.aiwf/sessions/session_1752877872199_jbhv2g2da.json +7 -0
  30. package/.aiwf/sessions/session_1752877872680_x04wpffu6.json +7 -0
  31. package/.aiwf/sessions/session_1752877872895_3y4vbeauo.json +7 -0
  32. package/.aiwf/sessions/session_1752877873178_3maolzc6b.json +7 -0
  33. package/.aiwf/sessions/session_1752877873180_ytm83j07f.json +7 -0
  34. package/.aiwf/sessions/session_1752877874217_dp56c9t42.json +7 -0
  35. package/.aiwf/sessions/session_1752877874415_epwp1g1og.json +7 -0
  36. package/.aiwf/sessions/session_1752877874454_heicyuryb.json +7 -0
  37. package/.aiwf/sessions/session_1752877874869_1c2illeu5.json +7 -0
  38. package/.aiwf/sessions/session_1752877874879_k1vo4vxyx.json +7 -0
  39. package/.aiwf/sessions/session_1752877874913_bwgyiz9bp.json +7 -0
  40. package/.aiwf/sessions/session_1752877876218_44807kztz.json +7 -0
  41. package/.aiwf/sessions/session_1752877876515_qkhjv5kpw.json +7 -0
  42. package/.aiwf/sessions/session_1752877876543_p605aud1m.json +7 -0
  43. package/.aiwf/sessions/session_1752877876819_l65w7ifck.json +7 -0
  44. package/.aiwf/sessions/session_1752877876867_xrhzss4we.json +7 -0
  45. package/.aiwf/sessions/session_1752877876872_u7aso54qb.json +7 -0
  46. package/.aiwf/sessions/session_1752877877705_telnfg4o6.json +7 -0
  47. package/.aiwf/sessions/session_1752877878285_6s45im2ib.json +7 -0
  48. package/.aiwf/sessions/session_1752877878506_v0dvguqhz.json +7 -0
  49. package/.aiwf/sessions/session_1752877878581_3ri3yc66a.json +7 -0
  50. package/.aiwf/sessions/session_1752877878756_a82x9s2pd.json +7 -0
  51. package/.aiwf/sessions/session_1752877878886_20pjsfour.json +7 -0
  52. package/.aiwf/sessions/session_1752877880266_fm1ydjw0g.json +7 -0
  53. package/.aiwf/sessions/session_1752877881149_xbat4z03d.json +7 -0
  54. package/.aiwf/sessions/session_1752877881551_qy5sic9qs.json +7 -0
  55. package/.aiwf/sessions/session_1752877881554_24ugd96wu.json +7 -0
  56. package/.aiwf/sessions/session_1752877881797_e9mm25uh0.json +7 -0
  57. package/.aiwf/sessions/session_1752877881822_bxvjj8c9g.json +7 -0
  58. package/.aiwf/sessions/session_1752877881890_jv7l6jve3.json +7 -0
  59. package/.aiwf/sessions/session_1752877881991_f0qugdtcq.json +7 -0
  60. package/.aiwf/sessions/session_1752877883387_0bvw9rvnx.json +7 -0
  61. package/.aiwf/sessions/session_1752877883820_q6g2md2up.json +7 -0
  62. package/.aiwf/sessions/session_1752877884393_sqr7trvjl.json +7 -0
  63. package/.aiwf/sessions/session_1752877884500_whtypmzlf.json +7 -0
  64. package/.aiwf/sessions/session_1752877884645_y9mvvxli1.json +7 -0
  65. package/.aiwf/sessions/session_1752877884733_hk46eenel.json +7 -0
  66. package/.aiwf/sessions/session_1752877884738_c62j6214r.json +7 -0
  67. package/.aiwf/sessions/session_1752877884771_temhxehoz.json +7 -0
  68. package/.aiwf/sessions/session_1752877885643_6v5fcktc7.json +7 -0
  69. package/.aiwf/sessions/session_1752877886426_ee8di2fqh.json +7 -0
  70. package/.aiwf/sessions/session_1752877886549_3xj6tdoew.json +7 -0
  71. package/.aiwf/sessions/session_1752877886604_aj80yyfco.json +7 -0
  72. package/.aiwf/sessions/session_1752877886702_2b8841vyg.json +7 -0
  73. package/.aiwf/sessions/session_1752877886712_ntqc8b8bz.json +7 -0
  74. package/.aiwf/sessions/session_1752877886968_946m8tbw6.json +7 -0
  75. package/.aiwf/sessions/session_1752877887008_wg6zfg8dn.json +7 -0
  76. package/.aiwf/sessions/session_1752877888479_ciiu2qiqs.json +7 -0
  77. package/.aiwf/sessions/session_1752877889330_ravg8l66k.json +7 -0
  78. package/.aiwf/sessions/session_1752877889542_7zepkhjkc.json +7 -0
  79. package/.aiwf/sessions/session_1752877889638_ev4htqiu0.json +7 -0
  80. package/.aiwf/sessions/session_1752877889956_szphy2qma.json +7 -0
  81. package/.aiwf/sessions/session_1752877890220_o6aaa57tt.json +7 -0
  82. package/.aiwf/sessions/session_1752877890527_v2loy5vu1.json +7 -0
  83. package/.aiwf/sessions/session_1752877890572_cwq8fiuma.json +7 -0
  84. package/.aiwf/sessions/session_1752877892200_sgcwq1alt.json +7 -0
  85. package/.aiwf/sessions/session_1752877892268_v8kexpbc5.json +7 -0
  86. package/.aiwf/sessions/session_1752877892384_1gbbgrp4y.json +7 -0
  87. package/.aiwf/sessions/session_1752877893568_9p2n5p31a.json +7 -0
  88. package/.aiwf/sessions/session_1752877893727_un34a5tvp.json +7 -0
  89. package/.aiwf/sessions/session_1752877893778_01j79sw9i.json +7 -0
  90. package/.aiwf/sessions/session_1752877894053_jrhba6wko.json +7 -0
  91. package/.aiwf/sessions/session_1752877894082_x3bghawox.json +7 -0
  92. package/.aiwf/sessions/session_1752877894500_iytiav2ku.json +7 -0
  93. package/.aiwf/sessions/session_1752877894667_ucn0gc2ge.json +7 -0
  94. package/.aiwf/test-results-s02.json +4 -4
  95. package/.claude/commands/aiwf/aiwf_changelog.md +82 -0
  96. package/.claude/commands/aiwf/aiwf_code_review.md +88 -0
  97. package/.claude/commands/aiwf/aiwf_commit.md +160 -0
  98. package/.claude/commands/aiwf/aiwf_compress_aggressive_persona.md +53 -0
  99. package/.claude/commands/aiwf/aiwf_compress_context.md +116 -0
  100. package/.claude/commands/aiwf/aiwf_compress_minimal_persona.md +48 -0
  101. package/.claude/commands/aiwf/aiwf_compress_stats.md +79 -0
  102. package/.claude/commands/aiwf/aiwf_compress_with_persona.md +51 -0
  103. package/.claude/commands/aiwf/aiwf_create_feature_ledger.md +54 -0
  104. package/.claude/commands/aiwf/aiwf_create_general_task.md +147 -0
  105. package/.claude/commands/aiwf/aiwf_create_milestone_plan.md +194 -0
  106. package/.claude/commands/aiwf/aiwf_create_prd.md +280 -0
  107. package/.claude/commands/aiwf/aiwf_create_sprint_tasks.md +189 -0
  108. package/.claude/commands/aiwf/aiwf_create_sprints_from_milestone.md +121 -0
  109. package/.claude/commands/aiwf/aiwf_default_mode.md +44 -0
  110. package/.claude/commands/aiwf/aiwf_discuss_review.md +29 -0
  111. package/.claude/commands/aiwf/aiwf_do_task.md +109 -0
  112. package/.claude/commands/aiwf/aiwf_docs.md +225 -0
  113. package/.claude/commands/aiwf/aiwf_evaluate.md +92 -0
  114. package/.claude/commands/aiwf/aiwf_get_feature_details.md +78 -0
  115. package/.claude/commands/aiwf/aiwf_infinite.md +202 -0
  116. package/.claude/commands/aiwf/aiwf_initialize.md +134 -0
  117. package/.claude/commands/aiwf/aiwf_issue_create.md +65 -0
  118. package/.claude/commands/aiwf/aiwf_link_feature_to_milestone.md +57 -0
  119. package/.claude/commands/aiwf/aiwf_list_features.md +54 -0
  120. package/.claude/commands/aiwf/aiwf_mermaid.md +272 -0
  121. package/.claude/commands/aiwf/aiwf_persona_architect.md +48 -0
  122. package/.claude/commands/aiwf/aiwf_persona_backend.md +37 -0
  123. package/.claude/commands/aiwf/aiwf_persona_data_analyst.md +37 -0
  124. package/.claude/commands/aiwf/aiwf_persona_frontend.md +37 -0
  125. package/.claude/commands/aiwf/aiwf_persona_security.md +48 -0
  126. package/.claude/commands/aiwf/aiwf_persona_status.md +50 -0
  127. package/.claude/commands/aiwf/aiwf_pr_create.md +76 -0
  128. package/.claude/commands/aiwf/aiwf_prime.md +9 -0
  129. package/.claude/commands/aiwf/aiwf_project_review.md +261 -0
  130. package/.claude/commands/aiwf/aiwf_switch_language.md +160 -0
  131. package/.claude/commands/aiwf/aiwf_test.md +124 -0
  132. package/.claude/commands/aiwf/aiwf_testing_review.md +198 -0
  133. package/.claude/commands/aiwf/aiwf_tm-run-all-subtask.md +210 -0
  134. package/.claude/commands/aiwf/aiwf_ultrathink_code_advanced.md +460 -0
  135. package/.claude/commands/aiwf/aiwf_ultrathink_code_basic.md +165 -0
  136. package/.claude/commands/aiwf/aiwf_ultrathink_general.md +305 -0
  137. package/.claude/commands/aiwf/aiwf_update_docs_kr.md +25 -0
  138. package/.claude/commands/aiwf/aiwf_update_feature_status.md +53 -0
  139. package/.claude/commands/aiwf/aiwf_yolo.md +300 -0
  140. package/.eslintrc.json +26 -0
  141. package/.prettierrc +10 -0
  142. package/CHANGELOG.md +54 -5
  143. package/CLAUDE.md +154 -54
  144. package/README.ko.md +25 -0
  145. package/README.md +25 -0
  146. package/TEST_RECOVERY_PLAN.md +68 -0
  147. package/claude-code/aiwf/ko/commands/ai-persona.js +1 -1
  148. package/docs/CLEANUP_RECOMMENDATIONS.md +86 -0
  149. package/docs/CLI_USAGE_GUIDE.md +479 -0
  150. package/package.json +9 -9
  151. package/scripts/update-feature-status.js +70 -0
  152. package/{lib β†’ src/cli}/cache-cli.js +4 -4
  153. package/src/cli/index.js +206 -0
  154. package/{language-cli.js β†’ src/cli/language-cli.js} +11 -4
  155. package/src/commands/cache-templates.js +209 -0
  156. package/src/commands/create-offline.js +86 -0
  157. package/src/commands/create-project.js +151 -0
  158. package/src/commands/persona.js +288 -0
  159. package/src/lib/backup-manager.js +270 -0
  160. package/src/lib/file-downloader.js +208 -0
  161. package/src/lib/installer.js +676 -0
  162. package/src/lib/rollback-manager.js +418 -0
  163. package/src/lib/validator.js +561 -0
  164. package/src/utils/git-integration.js +173 -0
  165. package/{language-utils.js β†’ src/utils/language-utils.js} +2 -1
  166. package/src/utils/messages.js +136 -0
  167. package/src/utils/paths.js +130 -0
  168. package/tests/application.test.js +2 -2
  169. package/tests/installer.test.js +2 -2
  170. package/tests/integration/ai-persona-system.test.js +4 -4
  171. package/tests/integration/test-persona-system/docs/README.md +1 -0
  172. package/tests/integration/test-persona-system/package.json +1 -0
  173. package/tests/integration/test-persona-system/src/auth.js +1 -0
  174. package/tests/integration/test-persona-system/src/index.js +1 -0
  175. package/tests/integration/test-persona-system/test/test.js +1 -0
  176. package/tests/language-utils.test.js +1 -1
  177. package/tests/temp_invalid_json/invalid.json +1 -0
  178. package/tests/unit/ai-persona-manager.test.js +7 -7
  179. package/.aiwf/05_DOCUMENTATION/performance-report.md +0 -33
  180. package/.aiwf/test-cache/86c7f9e80251abb8bedd58953ab0d1779e46b4f43ea33b63bffdb778106373b0.json +0 -6
  181. package/.aiwf/test-cache/9e252a90b80fce0585d7ed0646e7a9b4343e109f661f42a3ae678ba1ec5e22c7.json +0 -7
  182. package/.aiwf/token-data/alerts.json +0 -293
  183. package/.aiwf/token-data/monitor-config.json +0 -12
  184. package/CHANGELOG_M02.md +0 -52
  185. package/claude-code/aiwf/ko/templates/web-app/template/package-lock.json +0 -10022
  186. package/claude-code/aiwf/ko/token-data/alerts.json +0 -293
  187. package/claude-code/aiwf/ko/token-data/monitor-config.json +0 -12
  188. package/docs/m02-dod-checklist.md +0 -125
  189. package/docs/m02-gap-analysis.md +0 -214
  190. package/docs/m02-milestone-completion-report.md +0 -186
  191. package/docs/m02-milestone-executive-summary.md +0 -68
  192. package/index.js +0 -1951
  193. package/lib/cache-integration.js +0 -401
  194. package/lib/file-batch-processor.js +0 -401
  195. package/lib/performance-benchmark.js +0 -621
  196. package/lib/template-manager.js +0 -310
  197. package/scripts/validate-commands.cjs +0 -338
  198. /package/{lib β†’ src/lib}/ai-persona-manager.js +0 -0
  199. /package/{lib β†’ src/lib}/cache-system.js +0 -0
  200. /package/{lib β†’ src/lib}/context-engine.js +0 -0
  201. /package/{lib β†’ src/lib}/memory-profiler.js +0 -0
  202. /package/{lib β†’ src/lib}/metrics-collector.js +0 -0
  203. /package/{lib β†’ src/lib}/offline-detector.js +0 -0
  204. /package/{lib β†’ src/lib}/task-analyzer.js +0 -0
  205. /package/{lib β†’ src/lib}/template-cache-system.js +0 -0
  206. /package/{lib β†’ src/lib}/template-downloader.js +0 -0
  207. /package/{lib β†’ src/lib}/template-version-manager.js +0 -0
  208. /package/{lib β†’ src/lib}/token-optimizer.js +0 -0
@@ -0,0 +1,676 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ import prompts from 'prompts';
7
+ import {
8
+ fetchContent,
9
+ downloadFile,
10
+ downloadDirectory,
11
+ GITHUB_RAW_URL,
12
+ GITHUB_CONTENT_PREFIX
13
+ } from './file-downloader.js';
14
+ import {
15
+ backupCommandsAndDocs,
16
+ restoreFromBackup,
17
+ getCurrentBackupDir,
18
+ setBackupDir
19
+ } from './backup-manager.js';
20
+ import { getMessages } from '../utils/messages.js';
21
+ import {
22
+ validateInstallation,
23
+ displaySpecCompliantValidationResults
24
+ } from './validator.js';
25
+ import { rollbackTool } from './rollback-manager.js';
26
+ import {
27
+ detectLanguage,
28
+ loadUserLanguageConfig,
29
+ saveUserLanguageConfig,
30
+ getInstallationLanguagePath,
31
+ SUPPORTED_LANGUAGES,
32
+ DEFAULT_LANGUAGE
33
+ } from '../utils/language-utils.js';
34
+ import {
35
+ FILES,
36
+ TOOL_DIRS,
37
+ TEMP_DIRS,
38
+ AIWF_DIRS,
39
+ getAllAiwfDirectories,
40
+ getAllClaudeMdFiles
41
+ } from '../utils/paths.js';
42
+
43
+ /**
44
+ * Check if AIWF is already installed
45
+ * @returns {Promise<boolean>} Installation status
46
+ */
47
+ async function checkExistingInstallation() {
48
+ const flagExists = await fs
49
+ .access(FILES.INSTALLED_FLAG)
50
+ .then(() => true)
51
+ .catch(() => false);
52
+ return flagExists;
53
+ }
54
+
55
+ /**
56
+ * Log with spinner for debug mode
57
+ * @param {Object} spinner - Ora spinner instance
58
+ * @param {string} message - Message to log
59
+ * @param {boolean} debugLog - Debug mode flag
60
+ */
61
+ function logWithSpinner(spinner, message, debugLog) {
62
+ if (debugLog) console.log(chalk.gray(message));
63
+ if (spinner) spinner.text = message;
64
+ }
65
+
66
+ /**
67
+ * Select language for installation
68
+ * @param {Object} options - Installation options
69
+ * @returns {Promise<string>} Selected language
70
+ */
71
+ async function selectLanguage(options) {
72
+ let selectedLanguage = DEFAULT_LANGUAGE;
73
+
74
+ if (!options.force) {
75
+ // 1. Check existing configuration
76
+ const existingConfig = await loadUserLanguageConfig();
77
+
78
+ if (existingConfig && existingConfig.language) {
79
+ selectedLanguage = existingConfig.language;
80
+ console.log(
81
+ chalk.blue(
82
+ `🌐 κΈ°μ‘΄ μ–Έμ–΄ 섀정을 μ‚¬μš©ν•©λ‹ˆλ‹€ / Using existing language setting: ${selectedLanguage}`
83
+ )
84
+ );
85
+ } else {
86
+ // 2. Try auto-detection
87
+ const detectedLang = await detectLanguage();
88
+
89
+ console.log(
90
+ chalk.gray(`πŸ” μ‹œμŠ€ν…œ μ–Έμ–΄ 감지 / System language detected: ${detectedLang}`)
91
+ );
92
+
93
+ // 3. Prompt user for language selection
94
+ const languageResponse = await prompts({
95
+ type: 'select',
96
+ name: 'language',
97
+ message: 'Please select language / μ–Έμ–΄λ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”:',
98
+ choices: [
99
+ { title: 'English', value: 'en' },
100
+ { title: 'ν•œκ΅­μ–΄ (Korean)', value: 'ko' }
101
+ ],
102
+ initial:
103
+ SUPPORTED_LANGUAGES.indexOf(detectedLang) !== -1
104
+ ? SUPPORTED_LANGUAGES.indexOf(detectedLang)
105
+ : 0
106
+ });
107
+
108
+ if (languageResponse.language) {
109
+ selectedLanguage = languageResponse.language;
110
+ } else {
111
+ selectedLanguage = detectedLang;
112
+ }
113
+
114
+ // 4. Save selected language
115
+ try {
116
+ await saveUserLanguageConfig(selectedLanguage, {
117
+ autoDetect: true,
118
+ fallback: DEFAULT_LANGUAGE
119
+ });
120
+ console.log(
121
+ chalk.green(
122
+ `βœ… μ–Έμ–΄ 섀정이 μ €μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€ / Language preference saved: ${selectedLanguage}`
123
+ )
124
+ );
125
+ } catch (error) {
126
+ console.warn(
127
+ chalk.yellow(
128
+ `⚠️ μ–Έμ–΄ μ„€μ • μ €μž₯ μ‹€νŒ¨ / Failed to save language preference: ${error.message}`
129
+ )
130
+ );
131
+ }
132
+ }
133
+ } else {
134
+ // Force mode: try auto-detection
135
+ try {
136
+ selectedLanguage = await detectLanguage();
137
+ } catch (error) {
138
+ selectedLanguage = DEFAULT_LANGUAGE;
139
+ }
140
+ }
141
+
142
+ return selectedLanguage;
143
+ }
144
+
145
+ /**
146
+ * Handle existing installation
147
+ * @param {Object} msg - Localized messages
148
+ * @param {Object} options - Installation options
149
+ * @returns {Promise<boolean>} Continue with installation
150
+ */
151
+ async function handleExistingInstallation(msg, options) {
152
+ if (options.force) {
153
+ return true;
154
+ }
155
+
156
+ const response = await prompts({
157
+ type: 'select',
158
+ name: 'action',
159
+ message: msg.existingDetected,
160
+ choices: [
161
+ { title: msg.updateOption, value: 'update' },
162
+ { title: msg.skipOption, value: 'skip' },
163
+ { title: msg.cancelOption, value: 'cancel' }
164
+ ]
165
+ });
166
+
167
+ if (response.action === 'skip' || response.action === 'cancel') {
168
+ console.log(chalk.yellow(msg.installCancelled));
169
+ process.exit(0);
170
+ }
171
+
172
+ if (response.action === 'update') {
173
+ await backupCommandsAndDocs(msg);
174
+ }
175
+
176
+ return true;
177
+ }
178
+
179
+ /**
180
+ * Create AIWF directory structure
181
+ */
182
+ async function createDirectoryStructure() {
183
+ const aiwfDirs = getAllAiwfDirectories();
184
+
185
+ for (const dir of aiwfDirs) {
186
+ await fs.mkdir(dir, { recursive: true });
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Download manifest file
192
+ * @param {string} languagePath - Language path
193
+ * @param {Object} spinner - Ora spinner instance
194
+ * @param {Object} msg - Localized messages
195
+ * @param {boolean} debugLog - Debug mode flag
196
+ */
197
+ async function downloadManifest(languagePath, spinner, msg, debugLog) {
198
+ logWithSpinner(spinner, msg.downloading, debugLog);
199
+ try {
200
+ const manifestUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_PREFIX}/${languagePath}/.aiwf/00_PROJECT_MANIFEST.md`;
201
+ await downloadFile(manifestUrl, FILES.PROJECT_MANIFEST);
202
+ } catch (error) {
203
+ // If manifest doesn't exist, that's okay
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Download templates
209
+ * @param {string} languagePath - Language path
210
+ * @param {Object} spinner - Ora spinner instance
211
+ * @param {Object} msg - Localized messages
212
+ * @param {boolean} debugLog - Debug mode flag
213
+ */
214
+ async function downloadTemplates(languagePath, spinner, msg, debugLog) {
215
+ try {
216
+ logWithSpinner(spinner, msg.downloadingTemplates, debugLog);
217
+ await downloadDirectory(
218
+ `${GITHUB_CONTENT_PREFIX}/${languagePath}/.aiwf/98_PROMPTS`,
219
+ AIWF_DIRS.PROMPTS,
220
+ spinner,
221
+ msg
222
+ );
223
+ await downloadDirectory(
224
+ `${GITHUB_CONTENT_PREFIX}/${languagePath}/.aiwf/99_TEMPLATES`,
225
+ AIWF_DIRS.TEMPLATES,
226
+ spinner,
227
+ msg
228
+ );
229
+ } catch (error) {
230
+ logWithSpinner(spinner, msg.templatesNotFound, debugLog);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Update CLAUDE.md documentation files
236
+ * @param {string} languagePath - Language path
237
+ * @param {Object} spinner - Ora spinner instance
238
+ * @param {Object} msg - Localized messages
239
+ * @param {boolean} debugLog - Debug mode flag
240
+ */
241
+ async function updateDocumentation(languagePath, spinner, msg, debugLog) {
242
+ logWithSpinner(spinner, msg.updatingDocs, debugLog);
243
+ const claudeFiles = getAllClaudeMdFiles();
244
+
245
+ for (const claudeFile of claudeFiles) {
246
+ try {
247
+ const relativePath = claudeFile.replace(process.cwd() + '/', '');
248
+ const claudeUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_PREFIX}/${languagePath}/${relativePath}`;
249
+ await downloadFile(claudeUrl, claudeFile);
250
+ } catch (error) {
251
+ // If CLAUDE.md doesn't exist, that's okay
252
+ }
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Update commands
258
+ * @param {string} languagePath - Language path
259
+ * @param {Object} spinner - Ora spinner instance
260
+ * @param {Object} msg - Localized messages
261
+ * @param {boolean} debugLog - Debug mode flag
262
+ */
263
+ async function updateCommands(languagePath, spinner, msg, debugLog) {
264
+ // Delete existing commands folder if it exists
265
+ if (await fs.access(TOOL_DIRS.CLAUDE_COMMANDS).then(() => true).catch(() => false)) {
266
+ logWithSpinner(spinner, msg.deletingOldCommands, debugLog);
267
+ await fs.rm(TOOL_DIRS.CLAUDE_COMMANDS, { recursive: true, force: true });
268
+ }
269
+
270
+ await fs.mkdir(TOOL_DIRS.CLAUDE_COMMANDS, { recursive: true });
271
+
272
+ // Download commands
273
+ logWithSpinner(spinner, msg.downloadingCommands, debugLog);
274
+ try {
275
+ await downloadDirectory(
276
+ `${GITHUB_CONTENT_PREFIX}/${languagePath}/${TOOL_DIRS.CLAUDE_COMMANDS}`,
277
+ TOOL_DIRS.CLAUDE_COMMANDS,
278
+ spinner,
279
+ msg,
280
+ languagePath
281
+ );
282
+ } catch (error) {
283
+ logWithSpinner(spinner, msg.commandsNotFound, debugLog);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Download and process rules
289
+ * @param {Object} spinner - Ora spinner instance
290
+ * @param {Object} msg - Localized messages
291
+ * @param {boolean} debugLog - Debug mode flag
292
+ */
293
+ async function downloadAndProcessRules(spinner, msg, debugLog) {
294
+ // Try to download from GitHub first, fallback to local files
295
+ const tmpRulesGlobal = path.join(os.tmpdir(), 'aiwf-rules-global');
296
+ const tmpRulesManual = path.join(os.tmpdir(), 'aiwf-rules-manual');
297
+
298
+ let useLocalGlobal = false;
299
+ let useLocalManual = false;
300
+
301
+ // Try to download rules/global from GitHub
302
+ try {
303
+ await downloadDirectory(
304
+ `${GITHUB_CONTENT_PREFIX}/rules/global`,
305
+ tmpRulesGlobal,
306
+ spinner,
307
+ debugLog
308
+ );
309
+ logWithSpinner(spinner, `Downloaded rules/global from GitHub`, debugLog);
310
+ } catch (error) {
311
+ logWithSpinner(spinner, `Failed to download rules/global from GitHub, trying local files`, debugLog);
312
+ // Fallback to local files
313
+ const localRulesGlobal = 'rules/global';
314
+ try {
315
+ await fs.access(localRulesGlobal);
316
+ // Copy local files to temp directory
317
+ await fs.mkdir(tmpRulesGlobal, { recursive: true });
318
+ const files = await fs.readdir(localRulesGlobal);
319
+ for (const file of files) {
320
+ const srcPath = path.join(localRulesGlobal, file);
321
+ const destPath = path.join(tmpRulesGlobal, file);
322
+ await fs.copyFile(srcPath, destPath);
323
+ }
324
+ useLocalGlobal = true;
325
+ logWithSpinner(spinner, `Using local rules/global directory`, debugLog);
326
+ } catch (localError) {
327
+ logWithSpinner(spinner, msg.rulesGlobalNotFound, debugLog);
328
+ }
329
+ }
330
+
331
+ // Try to download rules/manual from GitHub
332
+ try {
333
+ await downloadDirectory(
334
+ `${GITHUB_CONTENT_PREFIX}/rules/manual`,
335
+ tmpRulesManual,
336
+ spinner,
337
+ debugLog
338
+ );
339
+ logWithSpinner(spinner, `Downloaded rules/manual from GitHub`, debugLog);
340
+ } catch (error) {
341
+ logWithSpinner(spinner, `Failed to download rules/manual from GitHub, trying local files`, debugLog);
342
+ // Fallback to local files
343
+ const localRulesManual = 'rules/manual';
344
+ try {
345
+ await fs.access(localRulesManual);
346
+ // Copy local files to temp directory
347
+ await fs.mkdir(tmpRulesManual, { recursive: true });
348
+ const files = await fs.readdir(localRulesManual);
349
+ for (const file of files) {
350
+ const srcPath = path.join(localRulesManual, file);
351
+ const destPath = path.join(tmpRulesManual, file);
352
+ await fs.copyFile(srcPath, destPath);
353
+ }
354
+ useLocalManual = true;
355
+ logWithSpinner(spinner, `Using local rules/manual directory`, debugLog);
356
+ } catch (localError) {
357
+ logWithSpinner(spinner, msg.rulesManualNotFound, debugLog);
358
+ }
359
+ }
360
+
361
+ // Process rules/global
362
+ try {
363
+ await fs.access(tmpRulesGlobal);
364
+ await fs.mkdir(TOOL_DIRS.CURSOR_RULES, { recursive: true });
365
+ await fs.mkdir(TOOL_DIRS.WINDSURF_RULES, { recursive: true });
366
+ const files = await fs.readdir(tmpRulesGlobal);
367
+ for (const file of files) {
368
+ const srcPath = path.join(tmpRulesGlobal, file);
369
+ const stat = await fs.stat(srcPath);
370
+ if (!stat.isFile()) continue;
371
+
372
+ // .cursor/rules: add .mdc extension and header
373
+ const base = path.parse(file).name;
374
+ const cursorTarget = path.join(TOOL_DIRS.CURSOR_RULES, base + '.mdc');
375
+ const content = await fs.readFile(srcPath, 'utf8');
376
+ const header = '---\ndescription: \nglobs: \nalwaysApply: true\n---\n';
377
+ await fs.writeFile(cursorTarget, header + content, 'utf8');
378
+ logWithSpinner(
379
+ spinner,
380
+ `[rules/global] ${file} -> ${TOOL_DIRS.CURSOR_RULES}/${base}.mdc`,
381
+ debugLog
382
+ );
383
+
384
+ // .windsurf/rules: keep extension, no header
385
+ const windsurfTarget = path.join(TOOL_DIRS.WINDSURF_RULES, file);
386
+ const windsurfContent = await fs.readFile(srcPath, 'utf8');
387
+ const windsurfHeader = '---\ntrigger: always_on\n---\n';
388
+ await fs.writeFile(windsurfTarget, windsurfHeader + windsurfContent, 'utf8');
389
+ logWithSpinner(
390
+ spinner,
391
+ `[rules/global] ${file} -> ${TOOL_DIRS.WINDSURF_RULES}/${file}`,
392
+ debugLog
393
+ );
394
+ }
395
+ // Clean up temp folder (only if not using local files)
396
+ if (!useLocalGlobal) {
397
+ await fs.rm(tmpRulesGlobal, { recursive: true, force: true });
398
+ }
399
+ } catch (error) {
400
+ logWithSpinner(spinner, `No rules/global directory found`, debugLog);
401
+ }
402
+
403
+ // Process rules/manual
404
+ try {
405
+ await fs.access(tmpRulesManual);
406
+ await fs.mkdir(TOOL_DIRS.CURSOR_RULES, { recursive: true });
407
+ await fs.mkdir(TOOL_DIRS.WINDSURF_RULES, { recursive: true });
408
+ const files = await fs.readdir(tmpRulesManual);
409
+ for (const file of files) {
410
+ const srcPath = path.join(tmpRulesManual, file);
411
+ const stat = await fs.stat(srcPath);
412
+ if (!stat.isFile()) continue;
413
+
414
+ // .cursor/rules: add .mdc extension and header (alwaysApply: false)
415
+ const base = path.parse(file).name;
416
+ const cursorTarget = path.join(TOOL_DIRS.CURSOR_RULES, base + '.mdc');
417
+ const content = await fs.readFile(srcPath, 'utf8');
418
+ const header = '---\ndescription: \nglobs: \nalwaysApply: false\n---\n';
419
+ await fs.writeFile(cursorTarget, header + content, 'utf8');
420
+ logWithSpinner(
421
+ spinner,
422
+ `[rules/manual] ${file} -> ${TOOL_DIRS.CURSOR_RULES}/${base}.mdc`,
423
+ debugLog
424
+ );
425
+
426
+ // .windsurf/rules: keep extension, no header
427
+ const windsurfTarget = path.join(TOOL_DIRS.WINDSURF_RULES, file);
428
+ const windsurfContent = await fs.readFile(srcPath, 'utf8');
429
+ const windsurfHeader = '---\ntrigger: manual\n---\n';
430
+ await fs.writeFile(windsurfTarget, windsurfHeader + windsurfContent, 'utf8');
431
+ logWithSpinner(
432
+ spinner,
433
+ `[rules/manual] ${file} -> ${TOOL_DIRS.WINDSURF_RULES}/${file}`,
434
+ debugLog
435
+ );
436
+ }
437
+ // Clean up temp folder (only if not using local files)
438
+ if (!useLocalManual) {
439
+ await fs.rm(tmpRulesManual, { recursive: true, force: true });
440
+ }
441
+ } catch (error) {
442
+ logWithSpinner(spinner, `No rules/manual directory found`, debugLog);
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Display installation summary
448
+ * @param {boolean} hasExisting - Has existing installation
449
+ * @param {Object} msg - Localized messages
450
+ */
451
+ function displaySummary(hasExisting, msg) {
452
+ if (hasExisting) {
453
+ console.log(chalk.blue(`\n${msg.updateHistory}`));
454
+ console.log(chalk.gray(` β€’ ${TOOL_DIRS.CLAUDE_COMMANDS}${msg.updatedCommands}`));
455
+ console.log(chalk.gray(` β€’ ${msg.updatedDocs}`));
456
+ console.log(chalk.green(`\n${msg.workPreserved}`));
457
+ console.log(chalk.gray(` β€’ ${msg.allFilesPreserved}`));
458
+ console.log(chalk.gray(` β€’ ${msg.backupCreated}`));
459
+ } else {
460
+ console.log(chalk.blue(`\n${msg.structureCreated}`));
461
+ console.log(chalk.gray(` ${AIWF_DIRS.ROOT}/ - ${msg.aiwfRoot}`));
462
+ console.log(chalk.gray(` ${TOOL_DIRS.CLAUDE_COMMANDS}/ - ${msg.claudeCommands}`));
463
+
464
+ console.log(chalk.green(`\n${msg.nextSteps}`));
465
+ console.log(chalk.white(` 1. ${msg.nextStep1}`));
466
+ console.log(chalk.white(` 2. ${msg.nextStep2}`));
467
+ console.log(chalk.white(` 3. ${msg.nextStep3}\n`));
468
+
469
+ console.log(chalk.blue(`\n${msg.gettingStarted}`));
470
+ console.log(chalk.gray(` 1. ${msg.startStep1}`));
471
+ console.log(chalk.gray(` 2. ${msg.startStep2}`));
472
+ console.log(chalk.gray(`\n${msg.checkDocs}`));
473
+ }
474
+ }
475
+
476
+ /**
477
+ * Offer reinstallation options
478
+ * @param {Object} validationResults - Validation results
479
+ * @param {string} language - Language code
480
+ * @returns {Promise<string>} Selected action
481
+ */
482
+ async function offerReinstallationOptions(validationResults, language) {
483
+ console.log(
484
+ chalk.yellow(
485
+ language === 'ko'
486
+ ? '\n⚠️ 일뢀 μ„€μΉ˜κ°€ μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. 무엇을 ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?'
487
+ : '\n⚠️ Some installations failed. What would you like to do?'
488
+ )
489
+ );
490
+
491
+ // List failed tools
492
+ const failedTools = validationResults.failed.map(({ tool }) => tool);
493
+
494
+ if (failedTools.length > 0) {
495
+ console.log(
496
+ chalk.red(
497
+ language === 'ko'
498
+ ? `μ‹€νŒ¨ν•œ 도ꡬ: ${failedTools.join(', ')}`
499
+ : `Failed tools: ${failedTools.join(', ')}`
500
+ )
501
+ );
502
+ }
503
+
504
+ const response = await prompts({
505
+ type: 'select',
506
+ name: 'action',
507
+ message:
508
+ language === 'ko' ? 'μ–΄λ–€ μž‘μ—…μ„ μˆ˜ν–‰ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?' : 'What would you like to do?',
509
+ choices: [
510
+ {
511
+ title: language === 'ko' ? '전체 μž¬μ„€μΉ˜' : 'Full reinstallation',
512
+ value: 'reinstall'
513
+ },
514
+ {
515
+ title: language === 'ko' ? 'μ‹€νŒ¨ν•œ λ„κ΅¬λ§Œ λ‘€λ°±' : 'Rollback failed tools only',
516
+ value: 'rollback'
517
+ },
518
+ {
519
+ title: language === 'ko' ? 'ν˜„μž¬ μƒνƒœ μœ μ§€' : 'Keep current state',
520
+ value: 'keep'
521
+ }
522
+ ]
523
+ });
524
+
525
+ return response.action;
526
+ }
527
+
528
+ /**
529
+ * Main installation function
530
+ * @param {Object} options - Installation options
531
+ */
532
+ export async function installAIWF(options = {}) {
533
+ const debugLog = options.debugLog || false;
534
+
535
+ // Select language
536
+ const selectedLanguage = await selectLanguage(options);
537
+ const msg = getMessages(selectedLanguage);
538
+
539
+ console.log(chalk.blue.bold(msg.welcome));
540
+ console.log(chalk.gray(msg.description));
541
+ console.log(chalk.gray(msg.optimized));
542
+
543
+ // Set language path
544
+ const languagePath = getInstallationLanguagePath(selectedLanguage);
545
+ const GITHUB_CONTENT_LANGUAGE_PREFIX = `${GITHUB_CONTENT_PREFIX}/${languagePath}`;
546
+
547
+ // Check existing installation
548
+ const hasExisting = await checkExistingInstallation();
549
+
550
+ if (hasExisting) {
551
+ const shouldContinue = await handleExistingInstallation(msg, options);
552
+ if (!shouldContinue) return;
553
+ }
554
+
555
+ const spinner = ora(msg.fetching).start();
556
+
557
+ try {
558
+ // Create directory structure
559
+ await createDirectoryStructure();
560
+
561
+ // Download manifest (fresh installs only)
562
+ if (!hasExisting) {
563
+ await downloadManifest(languagePath, spinner, msg, debugLog);
564
+ }
565
+
566
+ // Download templates
567
+ await downloadTemplates(languagePath, spinner, msg, debugLog);
568
+
569
+ // Update documentation
570
+ await updateDocumentation(languagePath, spinner, msg, debugLog);
571
+
572
+ // Update commands
573
+ await updateCommands(languagePath, spinner, msg, debugLog);
574
+
575
+ // Download and process rules
576
+ await downloadAndProcessRules(spinner, msg, debugLog);
577
+
578
+ // Success
579
+ if (hasExisting) {
580
+ spinner.succeed(chalk.green(msg.updateSuccess));
581
+ } else {
582
+ spinner.succeed(chalk.green(msg.installSuccess));
583
+ }
584
+
585
+ // Display summary
586
+ displaySummary(hasExisting, msg);
587
+
588
+ // Run validation
589
+ console.log(
590
+ chalk.blue('\nπŸ” Installation Validation / μ„€μΉ˜ 검증을 μ‹œμž‘ν•©λ‹ˆλ‹€...')
591
+ );
592
+ const selectedTools = ['claude-code', 'cursor', 'windsurf'];
593
+ const validationResults = await validateInstallation(
594
+ selectedTools,
595
+ selectedLanguage
596
+ );
597
+ displaySpecCompliantValidationResults(validationResults, selectedLanguage);
598
+
599
+ // Handle partial failures
600
+ if (validationResults.failed.length > 0) {
601
+ const action = await offerReinstallationOptions(
602
+ validationResults,
603
+ selectedLanguage
604
+ );
605
+
606
+ if (action === 'reinstall') {
607
+ console.log(
608
+ chalk.blue('\nπŸ”„ μž¬μ„€μΉ˜λ₯Ό μ‹œμž‘ν•©λ‹ˆλ‹€... / Starting reinstallation...')
609
+ );
610
+ // Retry installation once
611
+ if (!options.isRetry) {
612
+ return await installAIWF({ ...options, isRetry: true });
613
+ }
614
+ } else if (action === 'rollback') {
615
+ console.log(
616
+ chalk.blue('\n↩️ μ‹€νŒ¨ν•œ 도ꡬλ₯Ό λ‘€λ°±ν•©λ‹ˆλ‹€... / Rolling back failed tools...')
617
+ );
618
+
619
+ // Rollback failed tools
620
+ const failedTools = validationResults.failed.map(({ tool }) => tool);
621
+ const backupDir = getCurrentBackupDir();
622
+ let rollbackResults = {};
623
+
624
+ for (const tool of failedTools) {
625
+ if (backupDir) {
626
+ const rollbackResult = await rollbackTool(
627
+ tool,
628
+ backupDir,
629
+ selectedLanguage
630
+ );
631
+ rollbackResults[tool] = rollbackResult;
632
+ }
633
+ }
634
+
635
+ // Re-validate after rollback
636
+ console.log(
637
+ chalk.blue('\nπŸ” λ‘€λ°± ν›„ μž¬κ²€μ¦... / Re-validating after rollback...')
638
+ );
639
+ const postRollbackValidation = await validateInstallation(
640
+ selectedTools,
641
+ selectedLanguage
642
+ );
643
+ displaySpecCompliantValidationResults(
644
+ postRollbackValidation,
645
+ selectedLanguage
646
+ );
647
+
648
+ // Rollback summary
649
+ console.log(chalk.blue('\n=== Rollback Summary / λ‘€λ°± μš”μ•½ ==='));
650
+ for (const [tool, result] of Object.entries(rollbackResults)) {
651
+ if (result.success) {
652
+ console.log(
653
+ chalk.green(`βœ… ${tool}: ${result.restoredCount} files restored`)
654
+ );
655
+ } else {
656
+ console.log(chalk.red(`❌ ${tool}: ${result.error}`));
657
+ }
658
+ }
659
+ }
660
+ }
661
+
662
+ console.log(chalk.green(msg.enjoy));
663
+ } catch (error) {
664
+ if (hasExisting) {
665
+ spinner.fail(chalk.red(msg.updateFailed));
666
+ await restoreFromBackup(spinner, msg);
667
+ } else {
668
+ spinner.fail(chalk.red(msg.installFailed));
669
+ }
670
+ console.error(chalk.red(error.message));
671
+ process.exit(1);
672
+ }
673
+
674
+ // Create installation flag file
675
+ await fs.writeFile(FILES.INSTALLED_FLAG, 'installed', 'utf8');
676
+ }