aiwf 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/.aiwf/config/language.json +1 -1
  2. package/.aiwf/sessions/session_1752877859489_1t7n2fhdb.json +7 -0
  3. package/.aiwf/sessions/session_1752877859515_cwdv211ry.json +7 -0
  4. package/.aiwf/sessions/session_1752877860664_huziadmbo.json +7 -0
  5. package/.aiwf/sessions/session_1752877860666_kyr5u1np6.json +7 -0
  6. package/.aiwf/sessions/session_1752877861848_9yinepw3t.json +7 -0
  7. package/.aiwf/sessions/session_1752877861874_y32sputuc.json +7 -0
  8. package/.aiwf/sessions/session_1752877862685_ivxbl5rhz.json +7 -0
  9. package/.aiwf/sessions/session_1752877862820_wnq4glvxi.json +7 -0
  10. package/.aiwf/sessions/session_1752877864632_3ebnpaelb.json +7 -0
  11. package/.aiwf/sessions/session_1752877864879_b9zgjcb7d.json +7 -0
  12. package/.aiwf/sessions/session_1752877865533_x14m1vodi.json +7 -0
  13. package/.aiwf/sessions/session_1752877865562_gkoh19n7d.json +7 -0
  14. package/.aiwf/sessions/session_1752877866333_wp8qpdcoh.json +7 -0
  15. package/.aiwf/sessions/session_1752877866509_dawntfpab.json +7 -0
  16. package/.aiwf/sessions/session_1752877866890_plc5vfucl.json +7 -0
  17. package/.aiwf/sessions/session_1752877866895_ga8hmq3oi.json +7 -0
  18. package/.aiwf/sessions/session_1752877867761_ayjp4v5f5.json +7 -0
  19. package/.aiwf/sessions/session_1752877867892_lfz6jy252.json +7 -0
  20. package/.aiwf/sessions/session_1752877868139_7bqd9ubli.json +7 -0
  21. package/.aiwf/sessions/session_1752877868162_zq3ofjbsh.json +7 -0
  22. package/.aiwf/sessions/session_1752877869054_7rlgm9jt6.json +7 -0
  23. package/.aiwf/sessions/session_1752877869477_ax3nhbxzr.json +7 -0
  24. package/.aiwf/sessions/session_1752877870095_6hgp4vl1e.json +7 -0
  25. package/.aiwf/sessions/session_1752877870381_qfcrdg4li.json +7 -0
  26. package/.aiwf/sessions/session_1752877871895_pgd5vllhz.json +7 -0
  27. package/.aiwf/sessions/session_1752877872199_jbhv2g2da.json +7 -0
  28. package/.aiwf/sessions/session_1752877872680_x04wpffu6.json +7 -0
  29. package/.aiwf/sessions/session_1752877872895_3y4vbeauo.json +7 -0
  30. package/.aiwf/sessions/session_1752877873178_3maolzc6b.json +7 -0
  31. package/.aiwf/sessions/session_1752877873180_ytm83j07f.json +7 -0
  32. package/.aiwf/sessions/session_1752877874217_dp56c9t42.json +7 -0
  33. package/.aiwf/sessions/session_1752877874415_epwp1g1og.json +7 -0
  34. package/.aiwf/sessions/session_1752877874454_heicyuryb.json +7 -0
  35. package/.aiwf/sessions/session_1752877874869_1c2illeu5.json +7 -0
  36. package/.aiwf/sessions/session_1752877874879_k1vo4vxyx.json +7 -0
  37. package/.aiwf/sessions/session_1752877874913_bwgyiz9bp.json +7 -0
  38. package/.aiwf/sessions/session_1752877876218_44807kztz.json +7 -0
  39. package/.aiwf/sessions/session_1752877876515_qkhjv5kpw.json +7 -0
  40. package/.aiwf/sessions/session_1752877876543_p605aud1m.json +7 -0
  41. package/.aiwf/sessions/session_1752877876819_l65w7ifck.json +7 -0
  42. package/.aiwf/sessions/session_1752877876867_xrhzss4we.json +7 -0
  43. package/.aiwf/sessions/session_1752877876872_u7aso54qb.json +7 -0
  44. package/.aiwf/sessions/session_1752877877705_telnfg4o6.json +7 -0
  45. package/.aiwf/sessions/session_1752877878285_6s45im2ib.json +7 -0
  46. package/.aiwf/sessions/session_1752877878506_v0dvguqhz.json +7 -0
  47. package/.aiwf/sessions/session_1752877878581_3ri3yc66a.json +7 -0
  48. package/.aiwf/sessions/session_1752877878756_a82x9s2pd.json +7 -0
  49. package/.aiwf/sessions/session_1752877878886_20pjsfour.json +7 -0
  50. package/.aiwf/sessions/session_1752877880266_fm1ydjw0g.json +7 -0
  51. package/.aiwf/sessions/session_1752877881149_xbat4z03d.json +7 -0
  52. package/.aiwf/sessions/session_1752877881551_qy5sic9qs.json +7 -0
  53. package/.aiwf/sessions/session_1752877881554_24ugd96wu.json +7 -0
  54. package/.aiwf/sessions/session_1752877881797_e9mm25uh0.json +7 -0
  55. package/.aiwf/sessions/session_1752877881822_bxvjj8c9g.json +7 -0
  56. package/.aiwf/sessions/session_1752877881890_jv7l6jve3.json +7 -0
  57. package/.aiwf/sessions/session_1752877881991_f0qugdtcq.json +7 -0
  58. package/.aiwf/sessions/session_1752877883387_0bvw9rvnx.json +7 -0
  59. package/.aiwf/sessions/session_1752877883820_q6g2md2up.json +7 -0
  60. package/.aiwf/sessions/session_1752877884393_sqr7trvjl.json +7 -0
  61. package/.aiwf/sessions/session_1752877884500_whtypmzlf.json +7 -0
  62. package/.aiwf/sessions/session_1752877884645_y9mvvxli1.json +7 -0
  63. package/.aiwf/sessions/session_1752877884733_hk46eenel.json +7 -0
  64. package/.aiwf/sessions/session_1752877884738_c62j6214r.json +7 -0
  65. package/.aiwf/sessions/session_1752877884771_temhxehoz.json +7 -0
  66. package/.aiwf/sessions/session_1752877885643_6v5fcktc7.json +7 -0
  67. package/.aiwf/sessions/session_1752877886426_ee8di2fqh.json +7 -0
  68. package/.aiwf/sessions/session_1752877886549_3xj6tdoew.json +7 -0
  69. package/.aiwf/sessions/session_1752877886604_aj80yyfco.json +7 -0
  70. package/.aiwf/sessions/session_1752877886702_2b8841vyg.json +7 -0
  71. package/.aiwf/sessions/session_1752877886712_ntqc8b8bz.json +7 -0
  72. package/.aiwf/sessions/session_1752877886968_946m8tbw6.json +7 -0
  73. package/.aiwf/sessions/session_1752877887008_wg6zfg8dn.json +7 -0
  74. package/.aiwf/sessions/session_1752877888479_ciiu2qiqs.json +7 -0
  75. package/.aiwf/sessions/session_1752877889330_ravg8l66k.json +7 -0
  76. package/.aiwf/sessions/session_1752877889542_7zepkhjkc.json +7 -0
  77. package/.aiwf/sessions/session_1752877889638_ev4htqiu0.json +7 -0
  78. package/.aiwf/sessions/session_1752877889956_szphy2qma.json +7 -0
  79. package/.aiwf/sessions/session_1752877890220_o6aaa57tt.json +7 -0
  80. package/.aiwf/sessions/session_1752877890527_v2loy5vu1.json +7 -0
  81. package/.aiwf/sessions/session_1752877890572_cwq8fiuma.json +7 -0
  82. package/.aiwf/sessions/session_1752877892200_sgcwq1alt.json +7 -0
  83. package/.aiwf/sessions/session_1752877892268_v8kexpbc5.json +7 -0
  84. package/.aiwf/sessions/session_1752877892384_1gbbgrp4y.json +7 -0
  85. package/.aiwf/sessions/session_1752877893568_9p2n5p31a.json +7 -0
  86. package/.aiwf/sessions/session_1752877893727_un34a5tvp.json +7 -0
  87. package/.aiwf/sessions/session_1752877893778_01j79sw9i.json +7 -0
  88. package/.aiwf/sessions/session_1752877894053_jrhba6wko.json +7 -0
  89. package/.aiwf/sessions/session_1752877894082_x3bghawox.json +7 -0
  90. package/.aiwf/sessions/session_1752877894500_iytiav2ku.json +7 -0
  91. package/.aiwf/sessions/session_1752877894667_ucn0gc2ge.json +7 -0
  92. package/.aiwf/test-results-s02.json +4 -4
  93. package/.claude/commands/aiwf/aiwf_changelog.md +82 -0
  94. package/.claude/commands/aiwf/aiwf_code_review.md +88 -0
  95. package/.claude/commands/aiwf/aiwf_commit.md +160 -0
  96. package/.claude/commands/aiwf/aiwf_compress_aggressive_persona.md +53 -0
  97. package/.claude/commands/aiwf/aiwf_compress_context.md +116 -0
  98. package/.claude/commands/aiwf/aiwf_compress_minimal_persona.md +48 -0
  99. package/.claude/commands/aiwf/aiwf_compress_stats.md +79 -0
  100. package/.claude/commands/aiwf/aiwf_compress_with_persona.md +51 -0
  101. package/.claude/commands/aiwf/aiwf_create_feature_ledger.md +54 -0
  102. package/.claude/commands/aiwf/aiwf_create_general_task.md +147 -0
  103. package/.claude/commands/aiwf/aiwf_create_milestone_plan.md +194 -0
  104. package/.claude/commands/aiwf/aiwf_create_prd.md +280 -0
  105. package/.claude/commands/aiwf/aiwf_create_sprint_tasks.md +189 -0
  106. package/.claude/commands/aiwf/aiwf_create_sprints_from_milestone.md +121 -0
  107. package/.claude/commands/aiwf/aiwf_default_mode.md +44 -0
  108. package/.claude/commands/aiwf/aiwf_discuss_review.md +29 -0
  109. package/.claude/commands/aiwf/aiwf_do_task.md +109 -0
  110. package/.claude/commands/aiwf/aiwf_docs.md +225 -0
  111. package/.claude/commands/aiwf/aiwf_evaluate.md +92 -0
  112. package/.claude/commands/aiwf/aiwf_get_feature_details.md +78 -0
  113. package/.claude/commands/aiwf/aiwf_infinite.md +202 -0
  114. package/.claude/commands/aiwf/aiwf_initialize.md +134 -0
  115. package/.claude/commands/aiwf/aiwf_issue_create.md +65 -0
  116. package/.claude/commands/aiwf/aiwf_link_feature_to_milestone.md +57 -0
  117. package/.claude/commands/aiwf/aiwf_list_features.md +54 -0
  118. package/.claude/commands/aiwf/aiwf_mermaid.md +272 -0
  119. package/.claude/commands/aiwf/aiwf_persona_architect.md +48 -0
  120. package/.claude/commands/aiwf/aiwf_persona_backend.md +37 -0
  121. package/.claude/commands/aiwf/aiwf_persona_data_analyst.md +37 -0
  122. package/.claude/commands/aiwf/aiwf_persona_frontend.md +37 -0
  123. package/.claude/commands/aiwf/aiwf_persona_security.md +48 -0
  124. package/.claude/commands/aiwf/aiwf_persona_status.md +50 -0
  125. package/.claude/commands/aiwf/aiwf_pr_create.md +76 -0
  126. package/.claude/commands/aiwf/aiwf_prime.md +9 -0
  127. package/.claude/commands/aiwf/aiwf_project_review.md +261 -0
  128. package/.claude/commands/aiwf/aiwf_switch_language.md +160 -0
  129. package/.claude/commands/aiwf/aiwf_test.md +124 -0
  130. package/.claude/commands/aiwf/aiwf_testing_review.md +198 -0
  131. package/.claude/commands/aiwf/aiwf_tm-run-all-subtask.md +210 -0
  132. package/.claude/commands/aiwf/aiwf_ultrathink_code_advanced.md +460 -0
  133. package/.claude/commands/aiwf/aiwf_ultrathink_code_basic.md +165 -0
  134. package/.claude/commands/aiwf/aiwf_ultrathink_general.md +305 -0
  135. package/.claude/commands/aiwf/aiwf_update_docs_kr.md +25 -0
  136. package/.claude/commands/aiwf/aiwf_update_feature_status.md +53 -0
  137. package/.claude/commands/aiwf/aiwf_yolo.md +300 -0
  138. package/.eslintrc.json +26 -0
  139. package/.prettierrc +10 -0
  140. package/CLAUDE.md +154 -54
  141. package/README.ko.md +25 -0
  142. package/README.md +25 -0
  143. package/TEST_RECOVERY_PLAN.md +68 -0
  144. package/docs/CLEANUP_RECOMMENDATIONS.md +86 -0
  145. package/docs/CLI_USAGE_GUIDE.md +479 -0
  146. package/package.json +9 -9
  147. package/scripts/update-feature-status.js +70 -0
  148. package/{lib → src/cli}/cache-cli.js +4 -4
  149. package/src/cli/index.js +165 -0
  150. package/{language-cli.js → src/cli/language-cli.js} +11 -4
  151. package/src/commands/cache-templates.js +209 -0
  152. package/src/commands/create-offline.js +86 -0
  153. package/src/commands/create-project.js +151 -0
  154. package/src/lib/backup-manager.js +270 -0
  155. package/src/lib/file-downloader.js +208 -0
  156. package/src/lib/installer.js +676 -0
  157. package/src/lib/rollback-manager.js +418 -0
  158. package/src/lib/validator.js +561 -0
  159. package/src/utils/git-integration.js +173 -0
  160. package/{language-utils.js → src/utils/language-utils.js} +2 -1
  161. package/src/utils/messages.js +136 -0
  162. package/src/utils/paths.js +130 -0
  163. package/tests/application.test.js +2 -2
  164. package/tests/installer.test.js +2 -2
  165. package/tests/integration/ai-persona-system.test.js +4 -4
  166. package/tests/integration/test-persona-system/docs/README.md +1 -0
  167. package/tests/integration/test-persona-system/package.json +1 -0
  168. package/tests/integration/test-persona-system/src/auth.js +1 -0
  169. package/tests/integration/test-persona-system/src/index.js +1 -0
  170. package/tests/integration/test-persona-system/test/test.js +1 -0
  171. package/tests/language-utils.test.js +1 -1
  172. package/tests/temp_invalid_json/invalid.json +1 -0
  173. package/tests/unit/ai-persona-manager.test.js +7 -7
  174. package/.aiwf/05_DOCUMENTATION/performance-report.md +0 -33
  175. package/.aiwf/test-cache/86c7f9e80251abb8bedd58953ab0d1779e46b4f43ea33b63bffdb778106373b0.json +0 -6
  176. package/.aiwf/test-cache/9e252a90b80fce0585d7ed0646e7a9b4343e109f661f42a3ae678ba1ec5e22c7.json +0 -7
  177. package/.aiwf/token-data/alerts.json +0 -293
  178. package/.aiwf/token-data/monitor-config.json +0 -12
  179. package/CHANGELOG_M02.md +0 -52
  180. package/claude-code/aiwf/ko/templates/web-app/template/package-lock.json +0 -10022
  181. package/claude-code/aiwf/ko/token-data/alerts.json +0 -293
  182. package/claude-code/aiwf/ko/token-data/monitor-config.json +0 -12
  183. package/docs/m02-dod-checklist.md +0 -125
  184. package/docs/m02-gap-analysis.md +0 -214
  185. package/docs/m02-milestone-completion-report.md +0 -186
  186. package/docs/m02-milestone-executive-summary.md +0 -68
  187. package/index.js +0 -1951
  188. package/lib/cache-integration.js +0 -401
  189. package/lib/file-batch-processor.js +0 -401
  190. package/lib/performance-benchmark.js +0 -621
  191. package/lib/template-manager.js +0 -310
  192. package/scripts/validate-commands.cjs +0 -338
  193. /package/{lib → src/lib}/ai-persona-manager.js +0 -0
  194. /package/{lib → src/lib}/cache-system.js +0 -0
  195. /package/{lib → src/lib}/context-engine.js +0 -0
  196. /package/{lib → src/lib}/memory-profiler.js +0 -0
  197. /package/{lib → src/lib}/metrics-collector.js +0 -0
  198. /package/{lib → src/lib}/offline-detector.js +0 -0
  199. /package/{lib → src/lib}/task-analyzer.js +0 -0
  200. /package/{lib → src/lib}/template-cache-system.js +0 -0
  201. /package/{lib → src/lib}/template-downloader.js +0 -0
  202. /package/{lib → src/lib}/template-version-manager.js +0 -0
  203. /package/{lib → src/lib}/token-optimizer.js +0 -0
@@ -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
+ };
@@ -0,0 +1,208 @@
1
+ import https from 'https';
2
+ import { createWriteStream } from 'fs';
3
+ import { pipeline } from 'stream/promises';
4
+ import path from 'path';
5
+
6
+ const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/moonklabs/aiwf/main';
7
+ const GITHUB_CONTENT_PREFIX = 'claude-code/aiwf';
8
+
9
+ // Pre-defined list of command files
10
+ const COMMAND_FILES = [
11
+ 'aiwf_changelog.md',
12
+ 'aiwf_code_review.md',
13
+ 'aiwf_commit.md',
14
+ 'aiwf_compress_aggressive_persona.md',
15
+ 'aiwf_compress_context.md',
16
+ 'aiwf_compress_minimal_persona.md',
17
+ 'aiwf_compress_stats.md',
18
+ 'aiwf_compress_with_persona.md',
19
+ 'aiwf_create_feature_ledger.md',
20
+ 'aiwf_create_general_task.md',
21
+ 'aiwf_create_milestone_plan.md',
22
+ 'aiwf_create_prd.md',
23
+ 'aiwf_create_sprint_tasks.md',
24
+ 'aiwf_create_sprints_from_milestone.md',
25
+ 'aiwf_default_mode.md',
26
+ 'aiwf_discuss_review.md',
27
+ 'aiwf_do_task.md',
28
+ 'aiwf_docs.md',
29
+ 'aiwf_evaluate.md',
30
+ 'aiwf_get_feature_details.md',
31
+ 'aiwf_infinite.md',
32
+ 'aiwf_initialize.md',
33
+ 'aiwf_issue_create.md',
34
+ 'aiwf_link_feature_to_milestone.md',
35
+ 'aiwf_list_features.md',
36
+ 'aiwf_mermaid.md',
37
+ 'aiwf_persona_architect.md',
38
+ 'aiwf_persona_backend.md',
39
+ 'aiwf_persona_data_analyst.md',
40
+ 'aiwf_persona_frontend.md',
41
+ 'aiwf_persona_security.md',
42
+ 'aiwf_persona_status.md',
43
+ 'aiwf_pr_create.md',
44
+ 'aiwf_prime.md',
45
+ 'aiwf_project_review.md',
46
+ 'aiwf_switch_language.md',
47
+ 'aiwf_test.md',
48
+ 'aiwf_testing_review.md',
49
+ 'aiwf_tm-run-all-subtask.md',
50
+ 'aiwf_ultrathink_code_advanced.md',
51
+ 'aiwf_ultrathink_code_basic.md',
52
+ 'aiwf_ultrathink_general.md',
53
+ 'aiwf_update_docs_kr.md',
54
+ 'aiwf_update_feature_status.md',
55
+ 'aiwf_yolo.md'
56
+ ];
57
+
58
+ // Pre-defined list of global rules files
59
+ const GLOBAL_RULES_FILES = [
60
+ 'code-style-guide.md',
61
+ 'coding-principles.md',
62
+ 'development-process.md',
63
+ 'global-rules.md'
64
+ ];
65
+
66
+ // Pre-defined list of manual rules files
67
+ const MANUAL_RULES_FILES = [
68
+ 'generate-plan-docs.md'
69
+ ];
70
+
71
+ // Pre-defined list of template files (these might not exist in the repo)
72
+ const TEMPLATE_FILES = [
73
+ // Add template files here when they are available
74
+ ];
75
+
76
+ // Pre-defined list of prompt files (these might not exist in the repo)
77
+ const PROMPT_FILES = [
78
+ // Add prompt files here when they are available
79
+ ];
80
+
81
+ /**
82
+ * Fetch content from URL
83
+ * @param {string} url - The URL to fetch from
84
+ * @returns {Promise<string>} The response content
85
+ */
86
+ async function fetchContent(url) {
87
+ return new Promise((resolve, reject) => {
88
+ https.get(url, { headers: { 'User-Agent': 'aiwf' } }, (res) => {
89
+ let data = '';
90
+ res.on('data', chunk => data += chunk);
91
+ res.on('end', () => {
92
+ if (res.statusCode === 200) {
93
+ resolve(data);
94
+ } else {
95
+ reject(new Error(`Failed to fetch ${url}: ${res.statusCode}`));
96
+ }
97
+ res.destroy();
98
+ });
99
+ }).on('error', (err) => {
100
+ reject(err);
101
+ });
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Download a file from URL to destination path
107
+ * @param {string} url - The URL to download from
108
+ * @param {string} destPath - The destination file path
109
+ * @returns {Promise<void>}
110
+ */
111
+ async function downloadFile(url, destPath) {
112
+ return new Promise((resolve, reject) => {
113
+ https.get(url, { headers: { 'User-Agent': 'aiwf' } }, (response) => {
114
+ if (response.statusCode !== 200) {
115
+ response.destroy();
116
+ reject(new Error(`Failed to download ${url}: ${response.statusCode}`));
117
+ return;
118
+ }
119
+ const file = createWriteStream(destPath);
120
+ pipeline(response, file)
121
+ .then(() => {
122
+ resolve();
123
+ })
124
+ .catch((error) => {
125
+ reject(error);
126
+ });
127
+ }).on('error', (err) => {
128
+ reject(err);
129
+ });
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Download files from a predefined list
135
+ * @param {string} githubPath - GitHub repository path
136
+ * @param {string} localPath - Local destination path
137
+ * @param {Array<string>} fileList - List of files to download
138
+ * @param {Object} spinner - Ora spinner instance
139
+ * @param {Object} messages - Localized messages
140
+ * @returns {Promise<void>}
141
+ */
142
+ async function downloadFileList(githubPath, localPath, fileList, spinner, messages) {
143
+ for (const fileName of fileList) {
144
+ try {
145
+ const destPath = path.join(localPath, fileName);
146
+ const downloadUrl = `${GITHUB_RAW_URL}/${githubPath}/${fileName}`;
147
+
148
+ await downloadFile(downloadUrl, destPath);
149
+ if (spinner) {
150
+ spinner.text = `${messages.downloading}: ${fileName}`;
151
+ }
152
+ } catch (error) {
153
+ // Skip files that don't exist, but log the error
154
+ if (spinner) {
155
+ spinner.text = `${messages.skipping || 'Skipping'}: ${fileName}`;
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Download directory contents using predefined file lists
163
+ * @param {string} githubPath - GitHub repository path
164
+ * @param {string} localPath - Local destination path
165
+ * @param {Object} spinner - Ora spinner instance
166
+ * @param {Object} messages - Localized messages
167
+ * @param {string} [selectedLanguage='en'] - Selected language
168
+ * @returns {Promise<void>}
169
+ */
170
+ async function downloadDirectory(githubPath, localPath, spinner, messages, selectedLanguage = 'en') {
171
+ try {
172
+ // Determine which file list to use based on the path
173
+ let fileList = [];
174
+
175
+ if (githubPath.includes('.claude/commands/aiwf')) {
176
+ fileList = COMMAND_FILES;
177
+ } else if (githubPath.includes('rules/global')) {
178
+ fileList = GLOBAL_RULES_FILES;
179
+ } else if (githubPath.includes('rules/manual')) {
180
+ fileList = MANUAL_RULES_FILES;
181
+ } else if (githubPath.includes('98_PROMPTS')) {
182
+ fileList = PROMPT_FILES;
183
+ } else if (githubPath.includes('99_TEMPLATES')) {
184
+ fileList = TEMPLATE_FILES;
185
+ } else {
186
+ // For unknown directories, try to download common files
187
+ fileList = ['README.md', 'index.md'];
188
+ }
189
+
190
+ await downloadFileList(githubPath, localPath, fileList, spinner, messages);
191
+ } catch (error) {
192
+ throw new Error(`Failed to download directory ${githubPath}: ${error.message}`);
193
+ }
194
+ }
195
+
196
+ export {
197
+ fetchContent,
198
+ downloadFile,
199
+ downloadFileList,
200
+ downloadDirectory,
201
+ GITHUB_RAW_URL,
202
+ GITHUB_CONTENT_PREFIX,
203
+ COMMAND_FILES,
204
+ GLOBAL_RULES_FILES,
205
+ MANUAL_RULES_FILES,
206
+ TEMPLATE_FILES,
207
+ PROMPT_FILES
208
+ };