@wundr.io/cli 1.0.1 → 1.0.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 (249) hide show
  1. package/dist/ai/ai-service.d.ts +152 -0
  2. package/dist/ai/ai-service.d.ts.map +1 -0
  3. package/dist/ai/ai-service.js +430 -0
  4. package/dist/ai/ai-service.js.map +1 -0
  5. package/dist/ai/claude-client.d.ts +130 -0
  6. package/dist/ai/claude-client.d.ts.map +1 -0
  7. package/dist/ai/claude-client.js +340 -0
  8. package/dist/ai/claude-client.js.map +1 -0
  9. package/dist/ai/conversation-manager.d.ts +164 -0
  10. package/dist/ai/conversation-manager.d.ts.map +1 -0
  11. package/dist/ai/conversation-manager.js +614 -0
  12. package/dist/ai/conversation-manager.js.map +1 -0
  13. package/dist/ai/index.d.ts +5 -0
  14. package/dist/ai/index.d.ts.map +1 -0
  15. package/dist/ai/index.js +8 -0
  16. package/dist/ai/index.js.map +1 -0
  17. package/dist/cli.d.ts +36 -0
  18. package/dist/cli.d.ts.map +1 -0
  19. package/dist/cli.js +192 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/commands/ai.d.ts +89 -0
  22. package/dist/commands/ai.d.ts.map +1 -0
  23. package/dist/commands/ai.js +799 -0
  24. package/dist/commands/ai.js.map +1 -0
  25. package/dist/commands/alignment.d.ts +78 -0
  26. package/dist/commands/alignment.d.ts.map +1 -0
  27. package/dist/commands/alignment.js +817 -0
  28. package/dist/commands/alignment.js.map +1 -0
  29. package/dist/commands/analyze-optimized.d.ts +14 -0
  30. package/dist/commands/analyze-optimized.d.ts.map +1 -0
  31. package/dist/commands/analyze-optimized.js +600 -0
  32. package/dist/commands/analyze-optimized.js.map +1 -0
  33. package/dist/commands/analyze.d.ts +65 -0
  34. package/dist/commands/analyze.d.ts.map +1 -0
  35. package/dist/commands/analyze.js +435 -0
  36. package/dist/commands/analyze.js.map +1 -0
  37. package/dist/commands/batch.d.ts +71 -0
  38. package/dist/commands/batch.d.ts.map +1 -0
  39. package/dist/commands/batch.js +738 -0
  40. package/dist/commands/batch.js.map +1 -0
  41. package/dist/commands/chat.d.ts +71 -0
  42. package/dist/commands/chat.d.ts.map +1 -0
  43. package/dist/commands/chat.js +674 -0
  44. package/dist/commands/chat.js.map +1 -0
  45. package/dist/commands/claude-init.d.ts +28 -0
  46. package/dist/commands/claude-init.d.ts.map +1 -0
  47. package/dist/commands/claude-init.js +591 -0
  48. package/dist/commands/claude-init.js.map +1 -0
  49. package/dist/commands/claude-setup.d.ts +119 -0
  50. package/dist/commands/claude-setup.d.ts.map +1 -0
  51. package/dist/commands/claude-setup.js +1073 -0
  52. package/dist/commands/claude-setup.js.map +1 -0
  53. package/dist/commands/computer-setup-commands.d.ts +53 -0
  54. package/dist/commands/computer-setup-commands.d.ts.map +1 -0
  55. package/dist/commands/computer-setup-commands.js +705 -0
  56. package/dist/commands/computer-setup-commands.js.map +1 -0
  57. package/dist/commands/computer-setup.d.ts +7 -0
  58. package/dist/commands/computer-setup.d.ts.map +1 -0
  59. package/dist/commands/computer-setup.js +849 -0
  60. package/dist/commands/computer-setup.js.map +1 -0
  61. package/dist/commands/create-command.d.ts +7 -0
  62. package/dist/commands/create-command.d.ts.map +1 -0
  63. package/dist/commands/create-command.js +158 -0
  64. package/dist/commands/create-command.js.map +1 -0
  65. package/dist/commands/create.d.ts +74 -0
  66. package/dist/commands/create.d.ts.map +1 -0
  67. package/dist/commands/create.js +556 -0
  68. package/dist/commands/create.js.map +1 -0
  69. package/dist/commands/dashboard.d.ts +91 -0
  70. package/dist/commands/dashboard.d.ts.map +1 -0
  71. package/dist/commands/dashboard.js +538 -0
  72. package/dist/commands/dashboard.js.map +1 -0
  73. package/dist/commands/govern.d.ts +70 -0
  74. package/dist/commands/govern.d.ts.map +1 -0
  75. package/dist/commands/govern.js +481 -0
  76. package/dist/commands/govern.js.map +1 -0
  77. package/dist/commands/governance.d.ts +17 -0
  78. package/dist/commands/governance.d.ts.map +1 -0
  79. package/dist/commands/governance.js +703 -0
  80. package/dist/commands/governance.js.map +1 -0
  81. package/dist/commands/guardian.d.ts +20 -0
  82. package/dist/commands/guardian.d.ts.map +1 -0
  83. package/dist/commands/guardian.js +597 -0
  84. package/dist/commands/guardian.js.map +1 -0
  85. package/dist/commands/init.d.ts +59 -0
  86. package/dist/commands/init.d.ts.map +1 -0
  87. package/dist/commands/init.js +650 -0
  88. package/dist/commands/init.js.map +1 -0
  89. package/dist/commands/performance-optimizer.d.ts +30 -0
  90. package/dist/commands/performance-optimizer.d.ts.map +1 -0
  91. package/dist/commands/performance-optimizer.js +650 -0
  92. package/dist/commands/performance-optimizer.js.map +1 -0
  93. package/dist/commands/plugins.d.ts +87 -0
  94. package/dist/commands/plugins.d.ts.map +1 -0
  95. package/dist/commands/plugins.js +685 -0
  96. package/dist/commands/plugins.js.map +1 -0
  97. package/dist/commands/rag.d.ts +7 -0
  98. package/dist/commands/rag.d.ts.map +1 -0
  99. package/dist/commands/rag.js +748 -0
  100. package/dist/commands/rag.js.map +1 -0
  101. package/dist/commands/session.d.ts +41 -0
  102. package/dist/commands/session.d.ts.map +1 -0
  103. package/dist/commands/session.js +441 -0
  104. package/dist/commands/session.js.map +1 -0
  105. package/dist/commands/setup.d.ts +29 -0
  106. package/dist/commands/setup.d.ts.map +1 -0
  107. package/dist/commands/setup.js +397 -0
  108. package/dist/commands/setup.js.map +1 -0
  109. package/dist/commands/test-init.d.ts +9 -0
  110. package/dist/commands/test-init.d.ts.map +1 -0
  111. package/dist/commands/test-init.js +222 -0
  112. package/dist/commands/test-init.js.map +1 -0
  113. package/dist/commands/test.d.ts +25 -0
  114. package/dist/commands/test.d.ts.map +1 -0
  115. package/dist/commands/test.js +217 -0
  116. package/dist/commands/test.js.map +1 -0
  117. package/dist/commands/vp.d.ts +7 -0
  118. package/dist/commands/vp.d.ts.map +1 -0
  119. package/dist/commands/vp.js +571 -0
  120. package/dist/commands/vp.js.map +1 -0
  121. package/dist/commands/watch.d.ts +76 -0
  122. package/dist/commands/watch.d.ts.map +1 -0
  123. package/dist/commands/watch.js +613 -0
  124. package/dist/commands/watch.js.map +1 -0
  125. package/dist/commands/worktree.d.ts +63 -0
  126. package/dist/commands/worktree.d.ts.map +1 -0
  127. package/dist/commands/worktree.js +774 -0
  128. package/dist/commands/worktree.js.map +1 -0
  129. package/dist/context/context-manager.d.ts +155 -0
  130. package/dist/context/context-manager.d.ts.map +1 -0
  131. package/dist/context/context-manager.js +383 -0
  132. package/dist/context/context-manager.js.map +1 -0
  133. package/dist/context/index.d.ts +3 -0
  134. package/dist/context/index.d.ts.map +1 -0
  135. package/dist/context/index.js +6 -0
  136. package/dist/context/index.js.map +1 -0
  137. package/dist/context/session-manager.d.ts +207 -0
  138. package/dist/context/session-manager.d.ts.map +1 -0
  139. package/dist/context/session-manager.js +686 -0
  140. package/dist/context/session-manager.js.map +1 -0
  141. package/dist/index.d.ts +8 -0
  142. package/dist/index.d.ts.map +1 -0
  143. package/dist/index.js +51 -0
  144. package/dist/index.js.map +1 -0
  145. package/dist/interactive/interactive-mode.d.ts +76 -0
  146. package/dist/interactive/interactive-mode.d.ts.map +1 -0
  147. package/dist/interactive/interactive-mode.js +732 -0
  148. package/dist/interactive/interactive-mode.js.map +1 -0
  149. package/dist/nlp/command-mapper.d.ts +174 -0
  150. package/dist/nlp/command-mapper.d.ts.map +1 -0
  151. package/dist/nlp/command-mapper.js +624 -0
  152. package/dist/nlp/command-mapper.js.map +1 -0
  153. package/dist/nlp/command-parser.d.ts +106 -0
  154. package/dist/nlp/command-parser.d.ts.map +1 -0
  155. package/dist/nlp/command-parser.js +417 -0
  156. package/dist/nlp/command-parser.js.map +1 -0
  157. package/dist/nlp/index.d.ts +5 -0
  158. package/dist/nlp/index.d.ts.map +1 -0
  159. package/dist/nlp/index.js +8 -0
  160. package/dist/nlp/index.js.map +1 -0
  161. package/dist/nlp/intent-classifier.d.ts +59 -0
  162. package/dist/nlp/intent-classifier.d.ts.map +1 -0
  163. package/dist/nlp/intent-classifier.js +384 -0
  164. package/dist/nlp/intent-classifier.js.map +1 -0
  165. package/dist/nlp/intent-parser.d.ts +152 -0
  166. package/dist/nlp/intent-parser.d.ts.map +1 -0
  167. package/dist/nlp/intent-parser.js +744 -0
  168. package/dist/nlp/intent-parser.js.map +1 -0
  169. package/dist/plugins/plugin-manager.d.ts +120 -0
  170. package/dist/plugins/plugin-manager.d.ts.map +1 -0
  171. package/dist/plugins/plugin-manager.js +595 -0
  172. package/dist/plugins/plugin-manager.js.map +1 -0
  173. package/dist/types/index.d.ts +224 -0
  174. package/dist/types/index.d.ts.map +1 -0
  175. package/dist/types/index.js +3 -0
  176. package/dist/types/index.js.map +1 -0
  177. package/dist/utils/backup-rollback-manager.d.ts +72 -0
  178. package/dist/utils/backup-rollback-manager.d.ts.map +1 -0
  179. package/dist/utils/backup-rollback-manager.js +289 -0
  180. package/dist/utils/backup-rollback-manager.js.map +1 -0
  181. package/dist/utils/claude-config-installer.d.ts +94 -0
  182. package/dist/utils/claude-config-installer.d.ts.map +1 -0
  183. package/dist/utils/claude-config-installer.js +628 -0
  184. package/dist/utils/claude-config-installer.js.map +1 -0
  185. package/dist/utils/config-manager.d.ts +73 -0
  186. package/dist/utils/config-manager.d.ts.map +1 -0
  187. package/dist/utils/config-manager.js +339 -0
  188. package/dist/utils/config-manager.js.map +1 -0
  189. package/dist/utils/error-handler.d.ts +46 -0
  190. package/dist/utils/error-handler.d.ts.map +1 -0
  191. package/dist/utils/error-handler.js +169 -0
  192. package/dist/utils/error-handler.js.map +1 -0
  193. package/dist/utils/logger.d.ts +25 -0
  194. package/dist/utils/logger.d.ts.map +1 -0
  195. package/dist/utils/logger.js +105 -0
  196. package/dist/utils/logger.js.map +1 -0
  197. package/package.json +6 -4
  198. package/src/ai/ai-service.ts +22 -19
  199. package/src/ai/claude-client.ts +20 -16
  200. package/src/ai/conversation-manager.ts +37 -30
  201. package/src/cli.ts +46 -17
  202. package/src/commands/ai.ts +196 -88
  203. package/src/commands/alignment.ts +1212 -0
  204. package/src/commands/analyze-optimized.ts +394 -89
  205. package/src/commands/analyze.ts +22 -20
  206. package/src/commands/batch.ts +41 -38
  207. package/src/commands/chat.ts +37 -34
  208. package/src/commands/claude-init.ts +38 -30
  209. package/src/commands/claude-setup.ts +692 -97
  210. package/src/commands/computer-setup-commands.ts +45 -39
  211. package/src/commands/computer-setup.ts +490 -20
  212. package/src/commands/create-command.ts +7 -7
  213. package/src/commands/create.ts +36 -33
  214. package/src/commands/dashboard.ts +33 -28
  215. package/src/commands/govern.ts +34 -29
  216. package/src/commands/governance.ts +1005 -0
  217. package/src/commands/guardian.ts +887 -0
  218. package/src/commands/init.ts +112 -22
  219. package/src/commands/performance-optimizer.ts +48 -42
  220. package/src/commands/plugins.ts +35 -32
  221. package/src/commands/project-update.ts +1053 -0
  222. package/src/commands/rag.ts +904 -0
  223. package/src/commands/session.ts +631 -0
  224. package/src/commands/setup.ts +35 -31
  225. package/src/commands/test-init.ts +6 -5
  226. package/src/commands/test.ts +7 -6
  227. package/src/commands/vp.ts +762 -0
  228. package/src/commands/watch.ts +44 -33
  229. package/src/commands/worktree.ts +1057 -0
  230. package/src/context/context-manager.ts +15 -12
  231. package/src/context/session-manager.ts +51 -40
  232. package/src/index.ts +7 -6
  233. package/src/interactive/interactive-mode.ts +25 -18
  234. package/src/lib/conflict-resolution.ts +28 -0
  235. package/src/lib/merge-strategy.ts +28 -0
  236. package/src/lib/safety-mechanisms.ts +47 -0
  237. package/src/lib/state-detection.ts +28 -0
  238. package/src/nlp/command-mapper.ts +35 -30
  239. package/src/nlp/command-parser.ts +20 -17
  240. package/src/nlp/intent-classifier.ts +7 -7
  241. package/src/nlp/intent-parser.ts +61 -49
  242. package/src/plugins/plugin-manager.ts +27 -23
  243. package/src/types/index.ts +1 -1
  244. package/src/types/modules.d.ts +1 -0
  245. package/src/utils/backup-rollback-manager.ts +13 -11
  246. package/src/utils/claude-config-installer.ts +18 -16
  247. package/src/utils/config-manager.ts +12 -9
  248. package/src/utils/error-handler.ts +5 -3
  249. package/src/utils/logger.ts +35 -12
@@ -0,0 +1,1053 @@
1
+ /**
2
+ * Project Update Command
3
+ *
4
+ * Main command for updating wundr projects to new versions.
5
+ * Orchestrates state detection, backup, merge, and conflict resolution.
6
+ */
7
+
8
+ import { existsSync } from 'fs';
9
+ import * as fs from 'fs/promises';
10
+ import * as path from 'path';
11
+
12
+ import chalk from 'chalk';
13
+ import { Command } from 'commander';
14
+ import inquirer from 'inquirer';
15
+ import ora from 'ora';
16
+
17
+ import {
18
+ createConflictResolver,
19
+ } from '../lib/conflict-resolution';
20
+ import {
21
+ MergeStrategyManager,
22
+ MergeResult,
23
+ threeWayMerge,
24
+ detectFileType,
25
+ } from '../lib/merge-strategy';
26
+ import {
27
+ createSafetyManager,
28
+ } from '../lib/safety-mechanisms';
29
+ import {
30
+ detectProjectState,
31
+ CustomizationInfo,
32
+ getStateSummary,
33
+ } from '../lib/state-detection';
34
+ import { errorHandler } from '../utils/error-handler';
35
+ import { logger } from '../utils/logger';
36
+
37
+ // Import lib modules
38
+ import type {
39
+ ConflictResolver,
40
+ UpdateConflict,
41
+ ConflictResolutionResult} from '../lib/conflict-resolution';
42
+ import type {
43
+ SafetyManager,
44
+ UpdateBackup,
45
+ UpdateTransaction} from '../lib/safety-mechanisms';
46
+ import type {
47
+ ProjectState} from '../lib/state-detection';
48
+ import type { PluginManager } from '../plugins/plugin-manager';
49
+ import type { ConfigManager } from '../utils/config-manager';
50
+
51
+
52
+ /**
53
+ * Update options from CLI flags
54
+ */
55
+ export interface ProjectUpdateOptions {
56
+ /** Dry run mode - show what would be done */
57
+ dryRun: boolean;
58
+ /** Force update without prompts */
59
+ force: boolean;
60
+ /** Skip backup creation */
61
+ skipBackup: boolean;
62
+ /** Specific components to update */
63
+ components: string[];
64
+ /** Target version to update to */
65
+ version: string | null;
66
+ /** Interactive mode */
67
+ interactive: boolean;
68
+ /** Verbose logging */
69
+ verbose: boolean;
70
+ /** Show diff during update */
71
+ showDiff: boolean;
72
+ /** Auto-resolve conflicts */
73
+ autoResolve: boolean;
74
+ /** Rollback on failure */
75
+ rollbackOnFailure: boolean;
76
+ }
77
+
78
+ /**
79
+ * Default update options
80
+ */
81
+ const DEFAULT_UPDATE_OPTIONS: ProjectUpdateOptions = {
82
+ dryRun: false,
83
+ force: false,
84
+ skipBackup: false,
85
+ components: [],
86
+ version: null,
87
+ interactive: true,
88
+ verbose: false,
89
+ showDiff: true,
90
+ autoResolve: false,
91
+ rollbackOnFailure: true,
92
+ };
93
+
94
+ /**
95
+ * Update result
96
+ */
97
+ export interface UpdateResult {
98
+ /** Whether update was successful */
99
+ success: boolean;
100
+ /** Updated from version */
101
+ fromVersion: string;
102
+ /** Updated to version */
103
+ toVersion: string;
104
+ /** Files updated */
105
+ filesUpdated: string[];
106
+ /** Files with conflicts */
107
+ conflicts: UpdateConflict[];
108
+ /** Backup created */
109
+ backup: UpdateBackup | null;
110
+ /** Errors encountered */
111
+ errors: string[];
112
+ /** Update summary */
113
+ summary: UpdateSummary;
114
+ }
115
+
116
+ /**
117
+ * Update summary
118
+ */
119
+ export interface UpdateSummary {
120
+ /** Components checked */
121
+ componentsChecked: number;
122
+ /** Components updated */
123
+ componentsUpdated: number;
124
+ /** Files checked */
125
+ filesChecked: number;
126
+ /** Files updated */
127
+ filesUpdated: number;
128
+ /** Conflicts resolved */
129
+ conflictsResolved: number;
130
+ /** Time taken in ms */
131
+ timeTaken: number;
132
+ }
133
+
134
+ /**
135
+ * Update log entry
136
+ */
137
+ interface UpdateLogEntry {
138
+ timestamp: string;
139
+ action: string;
140
+ target: string;
141
+ status: 'success' | 'failure' | 'skipped';
142
+ details?: string;
143
+ }
144
+
145
+ /**
146
+ * Component information for updates
147
+ */
148
+ interface UpdateComponent {
149
+ name: string;
150
+ files: string[];
151
+ needsUpdate: boolean;
152
+ }
153
+
154
+ /**
155
+ * Project Update Manager
156
+ */
157
+ export class ProjectUpdateManager {
158
+ private projectRoot: string;
159
+ private options: ProjectUpdateOptions;
160
+ private mergeManager: MergeStrategyManager;
161
+ private safetyManager: SafetyManager;
162
+ private conflictResolver: ConflictResolver;
163
+ private updateLog: UpdateLogEntry[] = [];
164
+ private spinner: ReturnType<typeof ora> | null = null;
165
+
166
+ constructor(projectRoot: string, options: Partial<ProjectUpdateOptions> = {}) {
167
+ this.projectRoot = projectRoot;
168
+ this.options = { ...DEFAULT_UPDATE_OPTIONS, ...options };
169
+
170
+ // Initialize managers
171
+ this.mergeManager = new MergeStrategyManager({
172
+ autoResolve: this.options.autoResolve,
173
+ preserveComments: true,
174
+ });
175
+ this.safetyManager = createSafetyManager({
176
+ projectRoot,
177
+ skipBackup: this.options.skipBackup,
178
+ dryRun: this.options.dryRun,
179
+ });
180
+ this.conflictResolver = createConflictResolver(projectRoot, {
181
+ interactive: this.options.interactive && !this.options.autoResolve,
182
+ autoResolveLow: true,
183
+ autoResolveMedium: this.options.autoResolve,
184
+ });
185
+ }
186
+
187
+ /**
188
+ * Run the project update
189
+ */
190
+ async run(): Promise<UpdateResult> {
191
+ const startTime = Date.now();
192
+
193
+ logger.info('Starting project update', {
194
+ projectRoot: this.projectRoot,
195
+ dryRun: this.options.dryRun,
196
+ force: this.options.force,
197
+ });
198
+
199
+ this.log('update', 'project', 'success', 'Starting project update');
200
+
201
+ try {
202
+ // Step 1: Detect current state
203
+ this.startSpinner('Detecting project state...');
204
+ const currentState = await detectProjectState(this.projectRoot, {
205
+ latestVersion: this.options.version || undefined,
206
+ });
207
+ this.stopSpinner();
208
+
209
+ const currentVersion = currentState.wundrVersion || '0.0.0';
210
+ this.log('detect', 'state', 'success', `Version: ${currentVersion}`);
211
+
212
+ // Step 2: Check if update is needed
213
+ const needsUpdate = currentState.isWundrOutdated ||
214
+ currentState.isPartialInstallation ||
215
+ this.options.force;
216
+
217
+ if (!needsUpdate && !this.options.force) {
218
+ console.log(chalk.green('\nProject is up to date!'));
219
+ return this.createResult(true, currentVersion, currentVersion, [], [], null, [], startTime);
220
+ }
221
+
222
+ // Step 3: Show update plan
223
+ await this.showUpdatePlan(currentState);
224
+
225
+ // Step 4: Confirm update (unless force mode)
226
+ if (!this.options.force && !this.options.dryRun) {
227
+ const confirmed = await this.confirmUpdate(currentState);
228
+ if (!confirmed) {
229
+ console.log(chalk.yellow('\nUpdate cancelled by user.'));
230
+ return this.createResult(false, currentVersion, currentVersion, [], [], null, ['Cancelled by user'], startTime);
231
+ }
232
+ }
233
+
234
+ // Step 5: Create backup
235
+ let backup: UpdateBackup | null = null;
236
+ if (!this.options.skipBackup && !this.options.dryRun) {
237
+ this.startSpinner('Creating backup...');
238
+ const filesToBackup = await this.getFilesToBackup(currentState);
239
+ backup = await this.safetyManager.createBackup(
240
+ filesToBackup,
241
+ 'Pre-update backup',
242
+ currentVersion,
243
+ this.options.version || 'latest',
244
+ );
245
+ this.stopSpinner();
246
+ console.log(chalk.green(`Backup created: ${backup.id}`));
247
+ this.log('backup', backup.path, 'success', `${backup.files.length} files backed up`);
248
+ }
249
+
250
+ // Step 6: Start transaction
251
+ const transaction = this.safetyManager.startTransaction('Project update');
252
+
253
+ try {
254
+ // Step 7: Perform updates
255
+ const components = this.extractComponents(currentState);
256
+ const { filesUpdated, conflicts, errors } = await this.performUpdates(
257
+ components,
258
+ currentState,
259
+ transaction,
260
+ );
261
+
262
+ // Step 8: Resolve conflicts
263
+ let resolvedConflicts: ConflictResolutionResult[] = [];
264
+ if (conflicts.length > 0) {
265
+ console.log(chalk.yellow(`\n${conflicts.length} conflict(s) found.`));
266
+ this.conflictResolver.startSession(conflicts);
267
+ resolvedConflicts = await this.conflictResolver.resolveAll();
268
+ }
269
+
270
+ // Step 9: Commit transaction
271
+ if (!this.options.dryRun) {
272
+ const committed = await transaction.commit();
273
+ if (!committed) {
274
+ throw new Error('Failed to commit transaction');
275
+ }
276
+ }
277
+
278
+ // Step 10: Write update log
279
+ await this.writeUpdateLog();
280
+
281
+ // Success
282
+ const toVersion = this.options.version || 'latest';
283
+ console.log(chalk.green(`\nProject updated successfully to ${toVersion}!`));
284
+
285
+ return this.createResult(
286
+ true,
287
+ currentVersion,
288
+ toVersion,
289
+ filesUpdated,
290
+ conflicts,
291
+ backup,
292
+ errors,
293
+ startTime,
294
+ );
295
+
296
+ } catch (error) {
297
+ // Rollback on failure
298
+ if (this.options.rollbackOnFailure && backup) {
299
+ console.log(chalk.yellow('\nRolling back changes...'));
300
+ await transaction.rollback();
301
+ await this.safetyManager.restoreFromBackup(backup);
302
+ console.log(chalk.green('Rollback completed.'));
303
+ }
304
+ throw error;
305
+ }
306
+
307
+ } catch (error: any) {
308
+ logger.error('Project update failed', error);
309
+ this.log('update', 'project', 'failure', error.message);
310
+ await this.writeUpdateLog();
311
+
312
+ return this.createResult(
313
+ false,
314
+ '0.0.0',
315
+ '0.0.0',
316
+ [],
317
+ [],
318
+ null,
319
+ [error.message],
320
+ startTime,
321
+ );
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Extract components from project state
327
+ */
328
+ private extractComponents(state: ProjectState): UpdateComponent[] {
329
+ const components: UpdateComponent[] = [];
330
+
331
+ // Claude config component
332
+ if (state.claudeConfigPath) {
333
+ components.push({
334
+ name: 'claude-config',
335
+ files: [state.claudeConfigPath],
336
+ needsUpdate: !state.hasClaudeConfig || state.isPartialInstallation,
337
+ });
338
+ }
339
+
340
+ // MCP config component
341
+ if (state.mcpConfigPath) {
342
+ components.push({
343
+ name: 'mcp-config',
344
+ files: [state.mcpConfigPath],
345
+ needsUpdate: !state.hasMCPConfig || state.isPartialInstallation,
346
+ });
347
+ }
348
+
349
+ // Wundr config component
350
+ if (state.wundrConfigPath) {
351
+ components.push({
352
+ name: 'wundr-config',
353
+ files: [state.wundrConfigPath],
354
+ needsUpdate: state.isWundrOutdated || false,
355
+ });
356
+ }
357
+
358
+ // Agent configs
359
+ if (state.agents.hasAgents) {
360
+ components.push({
361
+ name: 'agents',
362
+ files: state.agents.agents.map(a => a.configPath),
363
+ needsUpdate: state.agents.agents.some(a => !a.isValid),
364
+ });
365
+ }
366
+
367
+ // Hook configs
368
+ if (state.hooks.hasHooks) {
369
+ components.push({
370
+ name: 'hooks',
371
+ files: state.hooks.hooks.map(h => h.configPath),
372
+ needsUpdate: false,
373
+ });
374
+ }
375
+
376
+ return components;
377
+ }
378
+
379
+ /**
380
+ * Show update plan to user
381
+ */
382
+ private async showUpdatePlan(state: ProjectState): Promise<void> {
383
+ console.log(chalk.cyan('\n========== Update Plan ==========\n'));
384
+
385
+ console.log(chalk.white('Current Version:'), chalk.yellow(state.wundrVersion || 'Not installed'));
386
+ console.log(chalk.white('Target Version:'), chalk.green(this.options.version || state.latestWundrVersion || 'latest'));
387
+ console.log(chalk.white('Health Score:'), chalk.yellow(`${state.healthScore}/100`));
388
+
389
+ if (state.recommendations.length > 0) {
390
+ console.log(chalk.white('\nRecommendations:'));
391
+ for (const rec of state.recommendations.slice(0, 5)) {
392
+ console.log(chalk.gray(` - ${rec}`));
393
+ }
394
+ }
395
+
396
+ if (state.customizations.hasCustomizations) {
397
+ console.log(chalk.white('\nDetected Customizations:'));
398
+ for (const file of state.customizations.customizedFiles.slice(0, 5)) {
399
+ console.log(` - ${file}`);
400
+ }
401
+ if (state.customizations.customizedFiles.length > 5) {
402
+ console.log(chalk.gray(` ... and ${state.customizations.customizedFiles.length - 5} more`));
403
+ }
404
+ }
405
+
406
+ if (state.conflicts.hasConflicts) {
407
+ console.log(chalk.yellow('\nDetected Conflicts:'));
408
+ for (const conflict of state.conflicts.conflicts) {
409
+ const severityColor = conflict.severity === 'error' ? chalk.red :
410
+ conflict.severity === 'warning' ? chalk.yellow :
411
+ chalk.gray;
412
+ console.log(severityColor(` [${conflict.severity}] ${conflict.description}`));
413
+ }
414
+ }
415
+
416
+ console.log(chalk.cyan('\n=================================\n'));
417
+
418
+ if (this.options.dryRun) {
419
+ console.log(chalk.yellow('DRY RUN MODE - No changes will be made.\n'));
420
+ }
421
+ }
422
+
423
+ /**
424
+ * Confirm update with user
425
+ */
426
+ private async confirmUpdate(state: ProjectState): Promise<boolean> {
427
+ if (this.options.interactive) {
428
+ const { confirmed } = await inquirer.prompt([
429
+ {
430
+ type: 'confirm',
431
+ name: 'confirmed',
432
+ message: 'Proceed with update?',
433
+ default: true,
434
+ },
435
+ ]);
436
+ return confirmed;
437
+ }
438
+ return true;
439
+ }
440
+
441
+ /**
442
+ * Get files to backup
443
+ */
444
+ private async getFilesToBackup(state: ProjectState): Promise<string[]> {
445
+ const files: string[] = [];
446
+
447
+ // Add config files
448
+ if (state.claudeConfigPath) {
449
+ files.push(state.claudeConfigPath);
450
+ }
451
+ if (state.mcpConfigPath) {
452
+ files.push(state.mcpConfigPath);
453
+ }
454
+ if (state.wundrConfigPath) {
455
+ files.push(state.wundrConfigPath);
456
+ }
457
+
458
+ // Add agent config files
459
+ for (const agent of state.agents.agents) {
460
+ files.push(agent.configPath);
461
+ }
462
+
463
+ // Add hook config files
464
+ for (const hook of state.hooks.hooks) {
465
+ files.push(hook.configPath);
466
+ }
467
+
468
+ // Add customized files
469
+ for (const file of state.customizations.customizedFiles) {
470
+ const fullPath = path.join(state.projectPath, file);
471
+ if (existsSync(fullPath)) {
472
+ files.push(fullPath);
473
+ }
474
+ }
475
+
476
+ return files;
477
+ }
478
+
479
+ /**
480
+ * Perform the actual updates
481
+ */
482
+ private async performUpdates(
483
+ components: UpdateComponent[],
484
+ state: ProjectState,
485
+ transaction: UpdateTransaction,
486
+ ): Promise<{
487
+ filesUpdated: string[];
488
+ conflicts: UpdateConflict[];
489
+ errors: string[];
490
+ }> {
491
+ const filesUpdated: string[] = [];
492
+ const conflicts: UpdateConflict[] = [];
493
+ const errors: string[] = [];
494
+
495
+ // Filter components if specified
496
+ let componentsToUpdate = components;
497
+ if (this.options.components.length > 0) {
498
+ componentsToUpdate = components.filter(c =>
499
+ this.options.components.includes(c.name),
500
+ );
501
+ }
502
+
503
+ this.startSpinner(`Updating ${componentsToUpdate.length} component(s)...`);
504
+
505
+ for (const component of componentsToUpdate) {
506
+ if (!component.needsUpdate && !this.options.force) {
507
+ continue;
508
+ }
509
+
510
+ try {
511
+ const result = await this.updateComponent(component, state, transaction);
512
+
513
+ filesUpdated.push(...result.updated);
514
+ conflicts.push(...result.conflicts);
515
+
516
+ if (result.errors.length > 0) {
517
+ errors.push(...result.errors);
518
+ }
519
+
520
+ this.log('update', component.name, 'success', `${result.updated.length} files updated`);
521
+ } catch (error: any) {
522
+ errors.push(`Component ${component.name}: ${error.message}`);
523
+ this.log('update', component.name, 'failure', error.message);
524
+ }
525
+ }
526
+
527
+ this.stopSpinner();
528
+
529
+ return { filesUpdated, conflicts, errors };
530
+ }
531
+
532
+ /**
533
+ * Update a single component
534
+ */
535
+ private async updateComponent(
536
+ component: UpdateComponent,
537
+ state: ProjectState,
538
+ transaction: UpdateTransaction,
539
+ ): Promise<{
540
+ updated: string[];
541
+ conflicts: UpdateConflict[];
542
+ errors: string[];
543
+ }> {
544
+ const updated: string[] = [];
545
+ const conflicts: UpdateConflict[] = [];
546
+ const errors: string[] = [];
547
+
548
+ for (const filePath of component.files) {
549
+ if (!existsSync(filePath)) {
550
+ continue;
551
+ }
552
+
553
+ try {
554
+ // Record operation
555
+ transaction.recordOperation({
556
+ type: 'update',
557
+ path: filePath,
558
+ backupRef: null,
559
+ });
560
+
561
+ // Get current content
562
+ const currentContent = await fs.readFile(filePath, 'utf-8');
563
+
564
+ // Get base content (original version)
565
+ const baseContent = currentContent; // In real implementation, fetch from registry
566
+
567
+ // Get target content (new version)
568
+ const targetContent = await this.getTargetContent(filePath, component);
569
+
570
+ if (!targetContent) {
571
+ // No target content, skip
572
+ transaction.completeOperation(filePath);
573
+ continue;
574
+ }
575
+
576
+ // Perform merge
577
+ const fileType = detectFileType(filePath);
578
+ const mergeResult = await this.mergeManager.threeWayMerge({
579
+ base: baseContent,
580
+ user: currentContent,
581
+ target: targetContent,
582
+ filePath,
583
+ fileType,
584
+ });
585
+
586
+ if (mergeResult.success && mergeResult.content) {
587
+ if (!this.options.dryRun) {
588
+ await fs.writeFile(filePath, mergeResult.content);
589
+ }
590
+ updated.push(filePath);
591
+ transaction.completeOperation(filePath);
592
+
593
+ if (this.options.verbose) {
594
+ console.log(chalk.green(` Updated: ${filePath}`));
595
+ }
596
+ } else if (mergeResult.conflicts.length > 0) {
597
+ // Create update conflicts
598
+ for (const conflict of mergeResult.conflicts) {
599
+ conflicts.push(
600
+ this.conflictResolver.createUpdateConflict(conflict, filePath),
601
+ );
602
+ }
603
+ }
604
+
605
+ } catch (error: any) {
606
+ errors.push(`File ${filePath}: ${error.message}`);
607
+ transaction.failOperation(filePath, error.message);
608
+ }
609
+ }
610
+
611
+ return { updated, conflicts, errors };
612
+ }
613
+
614
+ /**
615
+ * Get target content (new version)
616
+ */
617
+ private async getTargetContent(
618
+ filePath: string,
619
+ component: UpdateComponent,
620
+ ): Promise<string | null> {
621
+ // In real implementation, would fetch from wundr registry
622
+ // For now, return null (no update available)
623
+ return null;
624
+ }
625
+
626
+ /**
627
+ * Create result object
628
+ */
629
+ private createResult(
630
+ success: boolean,
631
+ fromVersion: string,
632
+ toVersion: string,
633
+ filesUpdated: string[],
634
+ conflicts: UpdateConflict[],
635
+ backup: UpdateBackup | null,
636
+ errors: string[],
637
+ startTime: number,
638
+ ): UpdateResult {
639
+ return {
640
+ success,
641
+ fromVersion,
642
+ toVersion,
643
+ filesUpdated,
644
+ conflicts,
645
+ backup,
646
+ errors,
647
+ summary: {
648
+ componentsChecked: 0,
649
+ componentsUpdated: 0,
650
+ filesChecked: 0,
651
+ filesUpdated: filesUpdated.length,
652
+ conflictsResolved: 0,
653
+ timeTaken: Date.now() - startTime,
654
+ },
655
+ };
656
+ }
657
+
658
+ /**
659
+ * Log an update action
660
+ */
661
+ private log(action: string, target: string, status: 'success' | 'failure' | 'skipped', details?: string): void {
662
+ this.updateLog.push({
663
+ timestamp: new Date().toISOString(),
664
+ action,
665
+ target,
666
+ status,
667
+ details,
668
+ });
669
+ }
670
+
671
+ /**
672
+ * Write update log to file
673
+ */
674
+ private async writeUpdateLog(): Promise<void> {
675
+ const logPath = path.join(this.projectRoot, '.wundr-update.log');
676
+
677
+ try {
678
+ const content = this.updateLog.map(entry =>
679
+ `[${entry.timestamp}] ${entry.action.toUpperCase()} ${entry.target} - ${entry.status}${entry.details ? `: ${entry.details}` : ''}`,
680
+ ).join('\n');
681
+
682
+ await fs.writeFile(logPath, content);
683
+ } catch (error) {
684
+ logger.warn('Failed to write update log', error);
685
+ }
686
+ }
687
+
688
+ /**
689
+ * Start spinner
690
+ */
691
+ private startSpinner(text: string): void {
692
+ if (this.options.verbose || !this.options.interactive) {
693
+ console.log(text);
694
+ } else {
695
+ this.spinner = ora(text).start();
696
+ }
697
+ }
698
+
699
+ /**
700
+ * Stop spinner
701
+ */
702
+ private stopSpinner(success: boolean = true): void {
703
+ if (this.spinner) {
704
+ if (success) {
705
+ this.spinner.succeed();
706
+ } else {
707
+ this.spinner.fail();
708
+ }
709
+ this.spinner = null;
710
+ }
711
+ }
712
+ }
713
+
714
+ /**
715
+ * Project Update Commands Class
716
+ */
717
+ export class ProjectUpdateCommands {
718
+ constructor(
719
+ private program: Command,
720
+ private configManager: ConfigManager,
721
+ private pluginManager: PluginManager,
722
+ ) {
723
+ this.registerCommands();
724
+ }
725
+
726
+ private registerCommands(): void {
727
+ const updateCmd = this.program
728
+ .command('update')
729
+ .alias('upgrade')
730
+ .description('Update wundr project to a new version');
731
+
732
+ // Main update command
733
+ updateCmd
734
+ .command('project')
735
+ .description('Update the entire project')
736
+ .option('--dry-run', 'Show what would be done without making changes', false)
737
+ .option('-f, --force', 'Force update without prompts', false)
738
+ .option('--skip-backup', 'Skip creating backup before update', false)
739
+ .option('-c, --components <names>', 'Specific components to update (comma-separated)', '')
740
+ .option('-v, --version <version>', 'Target version to update to')
741
+ .option('--no-interactive', 'Disable interactive mode')
742
+ .option('--verbose', 'Enable verbose output', false)
743
+ .option('--show-diff', 'Show differences during update', true)
744
+ .option('--auto-resolve', 'Automatically resolve conflicts', false)
745
+ .option('--no-rollback', 'Disable rollback on failure')
746
+ .action(async (options) => {
747
+ await this.updateProject(options);
748
+ });
749
+
750
+ // Check for updates
751
+ updateCmd
752
+ .command('check')
753
+ .description('Check if updates are available')
754
+ .option('--verbose', 'Show detailed information')
755
+ .action(async (options) => {
756
+ await this.checkUpdates(options);
757
+ });
758
+
759
+ // Show update history
760
+ updateCmd
761
+ .command('history')
762
+ .description('Show update history')
763
+ .option('-n, --limit <number>', 'Number of entries to show', '10')
764
+ .action(async (options) => {
765
+ await this.showHistory(options);
766
+ });
767
+
768
+ // Rollback to previous state
769
+ updateCmd
770
+ .command('rollback [backupId]')
771
+ .description('Rollback to a previous state')
772
+ .option('--list', 'List available backups')
773
+ .action(async (backupId, options) => {
774
+ await this.rollback(backupId, options);
775
+ });
776
+
777
+ // Clean up old backups
778
+ updateCmd
779
+ .command('cleanup')
780
+ .description('Clean up old backups')
781
+ .option('-k, --keep <number>', 'Number of backups to keep', '5')
782
+ .action(async (options) => {
783
+ await this.cleanup(options);
784
+ });
785
+ }
786
+
787
+ /**
788
+ * Update project
789
+ */
790
+ private async updateProject(options: any): Promise<void> {
791
+ try {
792
+ const updateOptions: Partial<ProjectUpdateOptions> = {
793
+ dryRun: options.dryRun,
794
+ force: options.force,
795
+ skipBackup: options.skipBackup,
796
+ components: options.components ? options.components.split(',').map((c: string) => c.trim()) : [],
797
+ version: options.version || null,
798
+ interactive: options.interactive !== false,
799
+ verbose: options.verbose,
800
+ showDiff: options.showDiff,
801
+ autoResolve: options.autoResolve,
802
+ rollbackOnFailure: options.rollback !== false,
803
+ };
804
+
805
+ const manager = new ProjectUpdateManager(process.cwd(), updateOptions);
806
+ const result = await manager.run();
807
+
808
+ if (!result.success) {
809
+ process.exit(1);
810
+ }
811
+ } catch (error) {
812
+ throw errorHandler.createError(
813
+ 'WUNDR_UPDATE_FAILED',
814
+ 'Project update failed',
815
+ { options },
816
+ true,
817
+ );
818
+ }
819
+ }
820
+
821
+ /**
822
+ * Check for updates
823
+ */
824
+ private async checkUpdates(options: any): Promise<void> {
825
+ const spinner = ora('Checking for updates...').start();
826
+
827
+ try {
828
+ const state = await detectProjectState();
829
+
830
+ spinner.succeed('Update check complete');
831
+
832
+ console.log(chalk.cyan('\n========== Update Status ==========\n'));
833
+ console.log(chalk.white('Current Version:'), chalk.yellow(state.wundrVersion || 'Not installed'));
834
+ console.log(chalk.white('Health Score:'), chalk.yellow(`${state.healthScore}/100`));
835
+ console.log(chalk.white('Needs Update:'), state.isWundrOutdated ? chalk.red('Yes') : chalk.green('No'));
836
+
837
+ if (state.recommendations.length > 0) {
838
+ console.log(chalk.white('\nRecommendations:'));
839
+ for (const rec of state.recommendations) {
840
+ console.log(chalk.gray(` - ${rec}`));
841
+ }
842
+
843
+ console.log(chalk.cyan('\nRun `wundr update project` to apply updates.'));
844
+ }
845
+
846
+ console.log(chalk.cyan('\n====================================\n'));
847
+ } catch (error) {
848
+ spinner.fail('Update check failed');
849
+ throw error;
850
+ }
851
+ }
852
+
853
+ /**
854
+ * Show update history
855
+ */
856
+ private async showHistory(options: any): Promise<void> {
857
+ const logPath = path.join(process.cwd(), '.wundr-update.log');
858
+
859
+ if (!existsSync(logPath)) {
860
+ console.log(chalk.yellow('No update history found.'));
861
+ return;
862
+ }
863
+
864
+ const content = await fs.readFile(logPath, 'utf-8');
865
+ const lines = content.split('\n').slice(0, parseInt(options.limit, 10));
866
+
867
+ console.log(chalk.cyan('\n========== Update History ==========\n'));
868
+ for (const line of lines) {
869
+ if (line.includes('success')) {
870
+ console.log(chalk.green(line));
871
+ } else if (line.includes('failure')) {
872
+ console.log(chalk.red(line));
873
+ } else {
874
+ console.log(chalk.gray(line));
875
+ }
876
+ }
877
+ console.log(chalk.cyan('\n====================================\n'));
878
+ }
879
+
880
+ /**
881
+ * Rollback to previous state
882
+ */
883
+ private async rollback(backupId: string | undefined, options: any): Promise<void> {
884
+ const safetyManager = createSafetyManager({ projectRoot: process.cwd() });
885
+
886
+ if (options.list) {
887
+ const backups = await safetyManager.listBackups();
888
+
889
+ if (backups.length === 0) {
890
+ console.log(chalk.yellow('No backups available.'));
891
+ return;
892
+ }
893
+
894
+ console.log(chalk.cyan('\n========== Available Backups ==========\n'));
895
+ for (const backup of backups) {
896
+ console.log(
897
+ ` ${chalk.white(backup.id)} - ${chalk.gray(new Date(backup.timestamp).toLocaleString())} ` +
898
+ `(${backup.files.length} files)`,
899
+ );
900
+ }
901
+ console.log(chalk.cyan('\n=======================================\n'));
902
+ return;
903
+ }
904
+
905
+ let backup: UpdateBackup | null;
906
+
907
+ if (backupId) {
908
+ backup = await safetyManager.listBackups().then(
909
+ backups => backups.find(b => b.id === backupId) || null,
910
+ );
911
+ } else {
912
+ backup = await safetyManager.getLatestBackup();
913
+ }
914
+
915
+ if (!backup) {
916
+ console.log(chalk.red('Backup not found.'));
917
+ return;
918
+ }
919
+
920
+ const { confirmed } = await inquirer.prompt([
921
+ {
922
+ type: 'confirm',
923
+ name: 'confirmed',
924
+ message: `Rollback to ${backup.id}?`,
925
+ default: false,
926
+ },
927
+ ]);
928
+
929
+ if (!confirmed) {
930
+ console.log(chalk.yellow('Rollback cancelled.'));
931
+ return;
932
+ }
933
+
934
+ const spinner = ora('Rolling back...').start();
935
+ const success = await safetyManager.restoreFromBackup(backup);
936
+
937
+ if (success) {
938
+ spinner.succeed('Rollback completed successfully');
939
+ } else {
940
+ spinner.fail('Rollback failed');
941
+ }
942
+ }
943
+
944
+ /**
945
+ * Cleanup old backups
946
+ */
947
+ private async cleanup(options: any): Promise<void> {
948
+ const safetyManager = createSafetyManager({ projectRoot: process.cwd() });
949
+ const backups = await safetyManager.listBackups();
950
+ const keepCount = parseInt(options.keep, 10);
951
+
952
+ if (backups.length <= keepCount) {
953
+ console.log(chalk.green(`Only ${backups.length} backup(s) found. Nothing to clean up.`));
954
+ return;
955
+ }
956
+
957
+ const toDelete = backups.slice(keepCount);
958
+
959
+ const { confirmed } = await inquirer.prompt([
960
+ {
961
+ type: 'confirm',
962
+ name: 'confirmed',
963
+ message: `Delete ${toDelete.length} old backup(s)?`,
964
+ default: true,
965
+ },
966
+ ]);
967
+
968
+ if (!confirmed) {
969
+ return;
970
+ }
971
+
972
+ const spinner = ora('Cleaning up old backups...').start();
973
+
974
+ let deleted = 0;
975
+ for (const backup of toDelete) {
976
+ if (await safetyManager.deleteBackup(backup.id)) {
977
+ deleted++;
978
+ }
979
+ }
980
+
981
+ spinner.succeed(`Deleted ${deleted} backup(s)`);
982
+ }
983
+ }
984
+
985
+ /**
986
+ * Create and export the update command
987
+ */
988
+ export function createProjectUpdateCommand(): Command {
989
+ const cmd = new Command('update')
990
+ .alias('upgrade')
991
+ .description('Update wundr project to a new version');
992
+
993
+ // Add subcommands directly
994
+ cmd
995
+ .command('project')
996
+ .description('Update the entire project')
997
+ .option('--dry-run', 'Show what would be done without making changes', false)
998
+ .option('-f, --force', 'Force update without prompts', false)
999
+ .option('--skip-backup', 'Skip creating backup before update', false)
1000
+ .option('-c, --components <names>', 'Specific components to update (comma-separated)')
1001
+ .option('-v, --version <version>', 'Target version to update to')
1002
+ .option('--no-interactive', 'Disable interactive mode')
1003
+ .option('--verbose', 'Enable verbose output', false)
1004
+ .option('--auto-resolve', 'Automatically resolve conflicts', false)
1005
+ .action(async (options) => {
1006
+ const updateOptions: Partial<ProjectUpdateOptions> = {
1007
+ dryRun: options.dryRun,
1008
+ force: options.force,
1009
+ skipBackup: options.skipBackup,
1010
+ components: options.components ? options.components.split(',').map((c: string) => c.trim()) : [],
1011
+ version: options.version || null,
1012
+ interactive: options.interactive !== false,
1013
+ verbose: options.verbose,
1014
+ autoResolve: options.autoResolve,
1015
+ };
1016
+
1017
+ const manager = new ProjectUpdateManager(process.cwd(), updateOptions);
1018
+ const result = await manager.run();
1019
+
1020
+ if (!result.success) {
1021
+ process.exit(1);
1022
+ }
1023
+ });
1024
+
1025
+ cmd
1026
+ .command('check')
1027
+ .description('Check if updates are available')
1028
+ .action(async () => {
1029
+ const spinner = ora('Checking for updates...').start();
1030
+ try {
1031
+ const state = await detectProjectState();
1032
+ spinner.succeed();
1033
+
1034
+ console.log(chalk.white('\nCurrent Version:'), chalk.yellow(state.wundrVersion || 'Not installed'));
1035
+ console.log(chalk.white('Health Score:'), chalk.yellow(`${state.healthScore}/100`));
1036
+ console.log(chalk.white('Needs Update:'), state.isWundrOutdated ? chalk.red('Yes') : chalk.green('No'));
1037
+
1038
+ if (state.recommendations.length > 0) {
1039
+ console.log(chalk.white('\nRecommendations:'));
1040
+ for (const rec of state.recommendations.slice(0, 5)) {
1041
+ console.log(chalk.gray(` - ${rec}`));
1042
+ }
1043
+ }
1044
+ } catch (error) {
1045
+ spinner.fail('Check failed');
1046
+ throw error;
1047
+ }
1048
+ });
1049
+
1050
+ return cmd;
1051
+ }
1052
+
1053
+ export default ProjectUpdateCommands;