@wundr.io/cli 1.0.0

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 (213) hide show
  1. package/README.md +551 -0
  2. package/bin/wundr.js +39 -0
  3. package/dist/ai/ai-service.d.ts +152 -0
  4. package/dist/ai/ai-service.d.ts.map +1 -0
  5. package/dist/ai/ai-service.js +430 -0
  6. package/dist/ai/ai-service.js.map +1 -0
  7. package/dist/ai/claude-client.d.ts +130 -0
  8. package/dist/ai/claude-client.d.ts.map +1 -0
  9. package/dist/ai/claude-client.js +339 -0
  10. package/dist/ai/claude-client.js.map +1 -0
  11. package/dist/ai/conversation-manager.d.ts +164 -0
  12. package/dist/ai/conversation-manager.d.ts.map +1 -0
  13. package/dist/ai/conversation-manager.js +612 -0
  14. package/dist/ai/conversation-manager.js.map +1 -0
  15. package/dist/ai/index.d.ts +5 -0
  16. package/dist/ai/index.d.ts.map +1 -0
  17. package/dist/ai/index.js +8 -0
  18. package/dist/ai/index.js.map +1 -0
  19. package/dist/cli.d.ts +36 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +173 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/commands/ai.d.ts +89 -0
  24. package/dist/commands/ai.d.ts.map +1 -0
  25. package/dist/commands/ai.js +735 -0
  26. package/dist/commands/ai.js.map +1 -0
  27. package/dist/commands/analyze-optimized.d.ts +14 -0
  28. package/dist/commands/analyze-optimized.d.ts.map +1 -0
  29. package/dist/commands/analyze-optimized.js +437 -0
  30. package/dist/commands/analyze-optimized.js.map +1 -0
  31. package/dist/commands/analyze.d.ts +65 -0
  32. package/dist/commands/analyze.d.ts.map +1 -0
  33. package/dist/commands/analyze.js +435 -0
  34. package/dist/commands/analyze.js.map +1 -0
  35. package/dist/commands/batch.d.ts +71 -0
  36. package/dist/commands/batch.d.ts.map +1 -0
  37. package/dist/commands/batch.js +738 -0
  38. package/dist/commands/batch.js.map +1 -0
  39. package/dist/commands/chat.d.ts +71 -0
  40. package/dist/commands/chat.d.ts.map +1 -0
  41. package/dist/commands/chat.js +674 -0
  42. package/dist/commands/chat.js.map +1 -0
  43. package/dist/commands/claude-init.d.ts +28 -0
  44. package/dist/commands/claude-init.d.ts.map +1 -0
  45. package/dist/commands/claude-init.js +587 -0
  46. package/dist/commands/claude-init.js.map +1 -0
  47. package/dist/commands/claude-setup.d.ts +32 -0
  48. package/dist/commands/claude-setup.d.ts.map +1 -0
  49. package/dist/commands/claude-setup.js +570 -0
  50. package/dist/commands/claude-setup.js.map +1 -0
  51. package/dist/commands/computer-setup-commands.d.ts +39 -0
  52. package/dist/commands/computer-setup-commands.d.ts.map +1 -0
  53. package/dist/commands/computer-setup-commands.js +563 -0
  54. package/dist/commands/computer-setup-commands.js.map +1 -0
  55. package/dist/commands/computer-setup.d.ts +7 -0
  56. package/dist/commands/computer-setup.d.ts.map +1 -0
  57. package/dist/commands/computer-setup.js +481 -0
  58. package/dist/commands/computer-setup.js.map +1 -0
  59. package/dist/commands/create-command.d.ts +7 -0
  60. package/dist/commands/create-command.d.ts.map +1 -0
  61. package/dist/commands/create-command.js +158 -0
  62. package/dist/commands/create-command.js.map +1 -0
  63. package/dist/commands/create.d.ts +74 -0
  64. package/dist/commands/create.d.ts.map +1 -0
  65. package/dist/commands/create.js +556 -0
  66. package/dist/commands/create.js.map +1 -0
  67. package/dist/commands/dashboard.d.ts +91 -0
  68. package/dist/commands/dashboard.d.ts.map +1 -0
  69. package/dist/commands/dashboard.js +537 -0
  70. package/dist/commands/dashboard.js.map +1 -0
  71. package/dist/commands/govern.d.ts +70 -0
  72. package/dist/commands/govern.d.ts.map +1 -0
  73. package/dist/commands/govern.js +480 -0
  74. package/dist/commands/govern.js.map +1 -0
  75. package/dist/commands/init.d.ts +55 -0
  76. package/dist/commands/init.d.ts.map +1 -0
  77. package/dist/commands/init.js +584 -0
  78. package/dist/commands/init.js.map +1 -0
  79. package/dist/commands/performance-optimizer.d.ts +30 -0
  80. package/dist/commands/performance-optimizer.d.ts.map +1 -0
  81. package/dist/commands/performance-optimizer.js +649 -0
  82. package/dist/commands/performance-optimizer.js.map +1 -0
  83. package/dist/commands/plugins.d.ts +87 -0
  84. package/dist/commands/plugins.d.ts.map +1 -0
  85. package/dist/commands/plugins.js +685 -0
  86. package/dist/commands/plugins.js.map +1 -0
  87. package/dist/commands/setup.d.ts +29 -0
  88. package/dist/commands/setup.d.ts.map +1 -0
  89. package/dist/commands/setup.js +399 -0
  90. package/dist/commands/setup.js.map +1 -0
  91. package/dist/commands/test-init.d.ts +9 -0
  92. package/dist/commands/test-init.d.ts.map +1 -0
  93. package/dist/commands/test-init.js +222 -0
  94. package/dist/commands/test-init.js.map +1 -0
  95. package/dist/commands/test.d.ts +25 -0
  96. package/dist/commands/test.d.ts.map +1 -0
  97. package/dist/commands/test.js +217 -0
  98. package/dist/commands/test.js.map +1 -0
  99. package/dist/commands/watch.d.ts +76 -0
  100. package/dist/commands/watch.d.ts.map +1 -0
  101. package/dist/commands/watch.js +610 -0
  102. package/dist/commands/watch.js.map +1 -0
  103. package/dist/context/context-manager.d.ts +155 -0
  104. package/dist/context/context-manager.d.ts.map +1 -0
  105. package/dist/context/context-manager.js +383 -0
  106. package/dist/context/context-manager.js.map +1 -0
  107. package/dist/context/index.d.ts +3 -0
  108. package/dist/context/index.d.ts.map +1 -0
  109. package/dist/context/index.js +6 -0
  110. package/dist/context/index.js.map +1 -0
  111. package/dist/context/session-manager.d.ts +207 -0
  112. package/dist/context/session-manager.d.ts.map +1 -0
  113. package/dist/context/session-manager.js +682 -0
  114. package/dist/context/session-manager.js.map +1 -0
  115. package/dist/index.d.ts +8 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +51 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/interactive/interactive-mode.d.ts +76 -0
  120. package/dist/interactive/interactive-mode.d.ts.map +1 -0
  121. package/dist/interactive/interactive-mode.js +730 -0
  122. package/dist/interactive/interactive-mode.js.map +1 -0
  123. package/dist/nlp/command-mapper.d.ts +174 -0
  124. package/dist/nlp/command-mapper.d.ts.map +1 -0
  125. package/dist/nlp/command-mapper.js +623 -0
  126. package/dist/nlp/command-mapper.js.map +1 -0
  127. package/dist/nlp/command-parser.d.ts +106 -0
  128. package/dist/nlp/command-parser.d.ts.map +1 -0
  129. package/dist/nlp/command-parser.js +416 -0
  130. package/dist/nlp/command-parser.js.map +1 -0
  131. package/dist/nlp/index.d.ts +5 -0
  132. package/dist/nlp/index.d.ts.map +1 -0
  133. package/dist/nlp/index.js +8 -0
  134. package/dist/nlp/index.js.map +1 -0
  135. package/dist/nlp/intent-classifier.d.ts +59 -0
  136. package/dist/nlp/intent-classifier.d.ts.map +1 -0
  137. package/dist/nlp/intent-classifier.js +384 -0
  138. package/dist/nlp/intent-classifier.js.map +1 -0
  139. package/dist/nlp/intent-parser.d.ts +152 -0
  140. package/dist/nlp/intent-parser.d.ts.map +1 -0
  141. package/dist/nlp/intent-parser.js +739 -0
  142. package/dist/nlp/intent-parser.js.map +1 -0
  143. package/dist/plugins/plugin-manager.d.ts +120 -0
  144. package/dist/plugins/plugin-manager.d.ts.map +1 -0
  145. package/dist/plugins/plugin-manager.js +595 -0
  146. package/dist/plugins/plugin-manager.js.map +1 -0
  147. package/dist/types/index.d.ts +224 -0
  148. package/dist/types/index.d.ts.map +1 -0
  149. package/dist/types/index.js +3 -0
  150. package/dist/types/index.js.map +1 -0
  151. package/dist/utils/config-manager.d.ts +73 -0
  152. package/dist/utils/config-manager.d.ts.map +1 -0
  153. package/dist/utils/config-manager.js +339 -0
  154. package/dist/utils/config-manager.js.map +1 -0
  155. package/dist/utils/error-handler.d.ts +46 -0
  156. package/dist/utils/error-handler.d.ts.map +1 -0
  157. package/dist/utils/error-handler.js +169 -0
  158. package/dist/utils/error-handler.js.map +1 -0
  159. package/dist/utils/logger.d.ts +25 -0
  160. package/dist/utils/logger.d.ts.map +1 -0
  161. package/dist/utils/logger.js +94 -0
  162. package/dist/utils/logger.js.map +1 -0
  163. package/package.json +119 -0
  164. package/src/ai/ai-service.ts +595 -0
  165. package/src/ai/claude-client.ts +490 -0
  166. package/src/ai/conversation-manager.ts +907 -0
  167. package/src/ai/index.ts +8 -0
  168. package/src/cli.ts +202 -0
  169. package/src/commands/ai.ts +995 -0
  170. package/src/commands/analyze-optimized.ts +641 -0
  171. package/src/commands/analyze.ts +576 -0
  172. package/src/commands/batch.ts +935 -0
  173. package/src/commands/chat.ts +876 -0
  174. package/src/commands/claude-init.ts +715 -0
  175. package/src/commands/claude-setup.ts +697 -0
  176. package/src/commands/computer-setup-commands.ts +709 -0
  177. package/src/commands/computer-setup.ts +565 -0
  178. package/src/commands/create-command.ts +175 -0
  179. package/src/commands/create.ts +727 -0
  180. package/src/commands/dashboard.ts +691 -0
  181. package/src/commands/govern.ts +635 -0
  182. package/src/commands/init.ts +677 -0
  183. package/src/commands/performance-optimizer.ts +864 -0
  184. package/src/commands/plugins.ts +848 -0
  185. package/src/commands/setup.ts +508 -0
  186. package/src/commands/test-init.ts +242 -0
  187. package/src/commands/test.ts +264 -0
  188. package/src/commands/watch.ts +755 -0
  189. package/src/context/context-manager.ts +546 -0
  190. package/src/context/index.ts +9 -0
  191. package/src/context/session-manager.ts +1019 -0
  192. package/src/index.ts +64 -0
  193. package/src/interactive/interactive-mode.ts +830 -0
  194. package/src/nlp/command-mapper.ts +885 -0
  195. package/src/nlp/command-parser.ts +564 -0
  196. package/src/nlp/index.ts +4 -0
  197. package/src/nlp/intent-classifier.ts +458 -0
  198. package/src/nlp/intent-parser.ts +1101 -0
  199. package/src/plugins/plugin-manager.ts +744 -0
  200. package/src/types/index.ts +252 -0
  201. package/src/types/modules.d.ts +56 -0
  202. package/src/utils/config-manager.ts +391 -0
  203. package/src/utils/error-handler.ts +192 -0
  204. package/src/utils/logger.ts +104 -0
  205. package/templates/batch/ci-cd.yaml +62 -0
  206. package/templates/component/{{fileName}}.test.tsx +17 -0
  207. package/templates/component/{{fileName}}.tsx +21 -0
  208. package/templates/service/{{fileName}}.ts +98 -0
  209. package/templates/wundr-test.config.js +0 -0
  210. package/test-suites/api/health.spec.ts +134 -0
  211. package/test-suites/helpers/test-config.ts +84 -0
  212. package/test-suites/ui/accessibility.spec.ts +102 -0
  213. package/test-suites/ui/smoke.spec.ts +92 -0
@@ -0,0 +1,755 @@
1
+ import { Command } from 'commander';
2
+ import { watch, FSWatcher } from 'chokidar';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import chalk from 'chalk';
6
+ import YAML from 'yaml';
7
+ import { ConfigManager } from '../utils/config-manager';
8
+ import { PluginManager } from '../plugins/plugin-manager';
9
+ import { logger } from '../utils/logger';
10
+ import { errorHandler } from '../utils/error-handler';
11
+ import { WatchConfig, WatchCommand } from '../types';
12
+
13
+ /**
14
+ * Watch commands for real-time monitoring
15
+ */
16
+ export class WatchCommands {
17
+ private watchers: Map<string, FSWatcher> = new Map();
18
+ private activeWatches: Map<string, WatchConfig> = new Map();
19
+
20
+ constructor(
21
+ private program: Command,
22
+ private configManager: ConfigManager,
23
+ private pluginManager: PluginManager
24
+ ) {
25
+ this.registerCommands();
26
+ }
27
+
28
+ private registerCommands(): void {
29
+ const watchCmd = this.program
30
+ .command('watch')
31
+ .description('real-time file and project monitoring');
32
+
33
+ // Start watching
34
+ watchCmd
35
+ .command('start [patterns...]')
36
+ .description('start watching files or directories')
37
+ .option('--config <config>', 'watch configuration file')
38
+ .option('--ignore <patterns>', 'patterns to ignore (comma-separated)')
39
+ .option('--command <command>', 'command to run on changes')
40
+ .option('--debounce <ms>', 'debounce delay in milliseconds', '300')
41
+ .option('--recursive', 'watch subdirectories recursively')
42
+ .action(async (patterns, options) => {
43
+ await this.startWatching(patterns, options);
44
+ });
45
+
46
+ // Stop watching
47
+ watchCmd
48
+ .command('stop [name]')
49
+ .description('stop specific watch or all watches')
50
+ .action(async name => {
51
+ await this.stopWatching(name);
52
+ });
53
+
54
+ // List active watches
55
+ watchCmd
56
+ .command('list')
57
+ .alias('ls')
58
+ .description('list active watches')
59
+ .action(async () => {
60
+ await this.listWatches();
61
+ });
62
+
63
+ // Watch status
64
+ watchCmd
65
+ .command('status [name]')
66
+ .description('show watch status')
67
+ .action(async name => {
68
+ await this.showWatchStatus(name);
69
+ });
70
+
71
+ // Create watch config
72
+ watchCmd
73
+ .command('config create <name>')
74
+ .description('create watch configuration')
75
+ .option('--patterns <patterns>', 'file patterns to watch')
76
+ .option('--commands <commands>', 'commands to run on changes')
77
+ .option('--interactive', 'create configuration interactively')
78
+ .action(async (name, options) => {
79
+ await this.createWatchConfig(name, options);
80
+ });
81
+
82
+ // Load watch config
83
+ watchCmd
84
+ .command('config load <file>')
85
+ .description('load watch configuration from file')
86
+ .action(async file => {
87
+ await this.loadWatchConfig(file);
88
+ });
89
+
90
+ // Save watch config
91
+ watchCmd
92
+ .command('config save <name> [file]')
93
+ .description('save watch configuration to file')
94
+ .action(async (name, file) => {
95
+ await this.saveWatchConfig(name, file);
96
+ });
97
+
98
+ // Watch tests
99
+ watchCmd
100
+ .command('test')
101
+ .description('watch and run tests on changes')
102
+ .option(
103
+ '--framework <framework>',
104
+ 'test framework (jest, mocha, vitest)',
105
+ 'jest'
106
+ )
107
+ .option('--coverage', 'run with coverage')
108
+ .option('--changed-only', 'run tests for changed files only')
109
+ .action(async options => {
110
+ await this.watchTests(options);
111
+ });
112
+
113
+ // Watch build
114
+ watchCmd
115
+ .command('build')
116
+ .description('watch and build on changes')
117
+ .option('--target <target>', 'build target')
118
+ .option('--incremental', 'enable incremental builds')
119
+ .action(async options => {
120
+ await this.watchBuild(options);
121
+ });
122
+
123
+ // Watch lint
124
+ watchCmd
125
+ .command('lint')
126
+ .description('watch and lint on changes')
127
+ .option('--fix', 'automatically fix linting issues')
128
+ .option('--staged-only', 'lint staged files only')
129
+ .action(async options => {
130
+ await this.watchLint(options);
131
+ });
132
+
133
+ // Watch analysis
134
+ watchCmd
135
+ .command('analyze')
136
+ .description('watch and analyze code quality')
137
+ .option(
138
+ '--type <type>',
139
+ 'analysis type (quality, deps, security)',
140
+ 'quality'
141
+ )
142
+ .option('--threshold <threshold>', 'quality threshold')
143
+ .action(async options => {
144
+ await this.watchAnalysis(options);
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Start watching files or directories
150
+ */
151
+ private async startWatching(patterns: string[], options: any): Promise<void> {
152
+ try {
153
+ let watchConfig: WatchConfig;
154
+
155
+ if (options.config) {
156
+ watchConfig = await this.loadWatchConfigFile(options.config);
157
+ } else {
158
+ watchConfig = this.createWatchConfigFromOptions(patterns, options);
159
+ }
160
+
161
+ const watchName = `watch-${Date.now()}`;
162
+ logger.info(`Starting watch: ${chalk.cyan(watchName)}`);
163
+
164
+ const watcher = watch(watchConfig.patterns, {
165
+ ignored: watchConfig.ignore || [],
166
+ persistent: true,
167
+ ignoreInitial: true,
168
+ awaitWriteFinish: {
169
+ stabilityThreshold: parseInt(options.debounce) || 300,
170
+ pollInterval: 100,
171
+ },
172
+ });
173
+
174
+ // Set up event handlers
175
+ this.setupWatchHandlers(watcher, watchConfig, watchName);
176
+
177
+ // Store watcher and config
178
+ this.watchers.set(watchName, watcher);
179
+ this.activeWatches.set(watchName, watchConfig);
180
+
181
+ logger.success(`Watching ${watchConfig.patterns.join(', ')}`);
182
+ logger.info('Press Ctrl+C to stop watching');
183
+ } catch (error) {
184
+ throw errorHandler.createError(
185
+ 'WUNDR_WATCH_START_FAILED',
186
+ 'Failed to start watching',
187
+ { patterns, options },
188
+ true
189
+ );
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Stop watching
195
+ */
196
+ private async stopWatching(name?: string): Promise<void> {
197
+ try {
198
+ if (name) {
199
+ const watcher = this.watchers.get(name);
200
+ if (watcher) {
201
+ await watcher.close();
202
+ this.watchers.delete(name);
203
+ this.activeWatches.delete(name);
204
+ logger.success(`Stopped watch: ${name}`);
205
+ } else {
206
+ logger.warn(`Watch not found: ${name}`);
207
+ }
208
+ } else {
209
+ // Stop all watches
210
+ for (const [watchName, watcher] of this.watchers) {
211
+ await watcher.close();
212
+ logger.info(`Stopped watch: ${watchName}`);
213
+ }
214
+ this.watchers.clear();
215
+ this.activeWatches.clear();
216
+ logger.success('Stopped all watches');
217
+ }
218
+ } catch (error) {
219
+ throw errorHandler.createError(
220
+ 'WUNDR_WATCH_STOP_FAILED',
221
+ 'Failed to stop watching',
222
+ { name },
223
+ true
224
+ );
225
+ }
226
+ }
227
+
228
+ /**
229
+ * List active watches
230
+ */
231
+ private async listWatches(): Promise<void> {
232
+ try {
233
+ if (this.activeWatches.size === 0) {
234
+ logger.info('No active watches');
235
+ return;
236
+ }
237
+
238
+ logger.info(`Active watches (${this.activeWatches.size}):`);
239
+
240
+ const watchData = Array.from(this.activeWatches.entries()).map(
241
+ ([name, config]) => ({
242
+ Name: name,
243
+ Patterns: config.patterns.join(', '),
244
+ Commands: config.commands.length,
245
+ Debounce: `${config.debounce || 300}ms`,
246
+ })
247
+ );
248
+
249
+ console.table(watchData);
250
+ } catch (error) {
251
+ throw errorHandler.createError(
252
+ 'WUNDR_WATCH_LIST_FAILED',
253
+ 'Failed to list watches',
254
+ {},
255
+ true
256
+ );
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Show watch status
262
+ */
263
+ private async showWatchStatus(name?: string): Promise<void> {
264
+ try {
265
+ if (name) {
266
+ const config = this.activeWatches.get(name);
267
+ const watcher = this.watchers.get(name);
268
+
269
+ if (!config || !watcher) {
270
+ logger.warn(`Watch not found: ${name}`);
271
+ return;
272
+ }
273
+
274
+ console.log(chalk.blue(`\nWatch Status: ${name}`));
275
+ console.log(`Patterns: ${config.patterns.join(', ')}`);
276
+ console.log(`Commands: ${config.commands.length}`);
277
+ console.log(`Debounce: ${config.debounce || 300}ms`);
278
+ console.log(
279
+ `Watched Paths: ${Object.keys(watcher.getWatched()).length}`
280
+ );
281
+ } else {
282
+ console.log(chalk.blue('\nAll Watches Status:'));
283
+ console.log(`Active Watches: ${this.activeWatches.size}`);
284
+ console.log(`Total Watchers: ${this.watchers.size}`);
285
+
286
+ for (const [watchName] of this.activeWatches) {
287
+ await this.showWatchStatus(watchName);
288
+ }
289
+ }
290
+ } catch (error) {
291
+ throw errorHandler.createError(
292
+ 'WUNDR_WATCH_STATUS_FAILED',
293
+ 'Failed to show watch status',
294
+ { name },
295
+ true
296
+ );
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Create watch configuration
302
+ */
303
+ private async createWatchConfig(name: string, options: any): Promise<void> {
304
+ try {
305
+ let config: WatchConfig;
306
+
307
+ if (options.interactive) {
308
+ config = await this.createInteractiveWatchConfig();
309
+ } else {
310
+ config = {
311
+ patterns: options.patterns ? options.patterns.split(',') : ['**/*'],
312
+ commands: options.commands
313
+ ? this.parseWatchCommands(options.commands)
314
+ : [],
315
+ debounce: 300,
316
+ recursive: true,
317
+ };
318
+ }
319
+
320
+ const configPath = path.join(
321
+ process.cwd(),
322
+ '.wundr',
323
+ 'watch',
324
+ `${name}.yaml`
325
+ );
326
+ await fs.ensureDir(path.dirname(configPath));
327
+ await fs.writeFile(configPath, YAML.stringify(config));
328
+
329
+ logger.success(`Watch configuration created: ${configPath}`);
330
+ } catch (error) {
331
+ throw errorHandler.createError(
332
+ 'WUNDR_WATCH_CONFIG_CREATE_FAILED',
333
+ 'Failed to create watch configuration',
334
+ { name, options },
335
+ true
336
+ );
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Load watch configuration from file
342
+ */
343
+ private async loadWatchConfig(file: string): Promise<void> {
344
+ try {
345
+ const config = await this.loadWatchConfigFile(file);
346
+ const name = path.basename(file, path.extname(file));
347
+
348
+ await this.startWatchingWithConfig(name, config);
349
+ logger.success(`Loaded watch configuration: ${file}`);
350
+ } catch (error) {
351
+ throw errorHandler.createError(
352
+ 'WUNDR_WATCH_CONFIG_LOAD_FAILED',
353
+ 'Failed to load watch configuration',
354
+ { file },
355
+ true
356
+ );
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Save watch configuration to file
362
+ */
363
+ private async saveWatchConfig(name: string, file?: string): Promise<void> {
364
+ try {
365
+ const config = this.activeWatches.get(name);
366
+ if (!config) {
367
+ throw new Error(`Watch not found: ${name}`);
368
+ }
369
+
370
+ const outputPath =
371
+ file || path.join(process.cwd(), '.wundr', 'watch', `${name}.yaml`);
372
+ await fs.ensureDir(path.dirname(outputPath));
373
+ await fs.writeFile(outputPath, YAML.stringify(config));
374
+
375
+ logger.success(`Watch configuration saved: ${outputPath}`);
376
+ } catch (error) {
377
+ throw errorHandler.createError(
378
+ 'WUNDR_WATCH_CONFIG_SAVE_FAILED',
379
+ 'Failed to save watch configuration',
380
+ { name, file },
381
+ true
382
+ );
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Watch tests
388
+ */
389
+ private async watchTests(options: any): Promise<void> {
390
+ try {
391
+ logger.info('Starting test watcher...');
392
+
393
+ const testPatterns = this.getTestPatterns(options.framework);
394
+ const watchConfig: WatchConfig = {
395
+ patterns: testPatterns,
396
+ commands: [
397
+ {
398
+ trigger: 'change',
399
+ command: this.getTestCommand(options.framework, options),
400
+ condition: 'test-files',
401
+ },
402
+ ],
403
+ debounce: 1000,
404
+ };
405
+
406
+ await this.startWatchingWithConfig('test-watch', watchConfig);
407
+ } catch (error) {
408
+ throw errorHandler.createError(
409
+ 'WUNDR_WATCH_TEST_FAILED',
410
+ 'Failed to start test watcher',
411
+ { options },
412
+ true
413
+ );
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Watch build
419
+ */
420
+ private async watchBuild(options: any): Promise<void> {
421
+ try {
422
+ logger.info('Starting build watcher...');
423
+
424
+ const buildPatterns = ['src/**/*', 'lib/**/*', '*.config.*'];
425
+ const watchConfig: WatchConfig = {
426
+ patterns: buildPatterns,
427
+ commands: [
428
+ {
429
+ trigger: 'change',
430
+ command: this.getBuildCommand(options),
431
+ condition: 'source-files',
432
+ },
433
+ ],
434
+ debounce: 500,
435
+ };
436
+
437
+ await this.startWatchingWithConfig('build-watch', watchConfig);
438
+ } catch (error) {
439
+ throw errorHandler.createError(
440
+ 'WUNDR_WATCH_BUILD_FAILED',
441
+ 'Failed to start build watcher',
442
+ { options },
443
+ true
444
+ );
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Watch lint
450
+ */
451
+ private async watchLint(options: any): Promise<void> {
452
+ try {
453
+ logger.info('Starting lint watcher...');
454
+
455
+ const lintPatterns = ['**/*.{ts,tsx,js,jsx}', '!node_modules/**'];
456
+ const watchConfig: WatchConfig = {
457
+ patterns: lintPatterns,
458
+ commands: [
459
+ {
460
+ trigger: 'change',
461
+ command: this.getLintCommand(options),
462
+ condition: 'lint-files',
463
+ },
464
+ ],
465
+ debounce: 300,
466
+ };
467
+
468
+ await this.startWatchingWithConfig('lint-watch', watchConfig);
469
+ } catch (error) {
470
+ throw errorHandler.createError(
471
+ 'WUNDR_WATCH_LINT_FAILED',
472
+ 'Failed to start lint watcher',
473
+ { options },
474
+ true
475
+ );
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Watch analysis
481
+ */
482
+ private async watchAnalysis(options: any): Promise<void> {
483
+ try {
484
+ logger.info(`Starting ${options.type} analysis watcher...`);
485
+
486
+ const analysisPatterns = ['src/**/*', 'lib/**/*'];
487
+ const watchConfig: WatchConfig = {
488
+ patterns: analysisPatterns,
489
+ commands: [
490
+ {
491
+ trigger: 'change',
492
+ command: this.getAnalysisCommand(options),
493
+ condition: 'analysis-files',
494
+ },
495
+ ],
496
+ debounce: 2000,
497
+ };
498
+
499
+ await this.startWatchingWithConfig('analysis-watch', watchConfig);
500
+ } catch (error) {
501
+ throw errorHandler.createError(
502
+ 'WUNDR_WATCH_ANALYSIS_FAILED',
503
+ 'Failed to start analysis watcher',
504
+ { options },
505
+ true
506
+ );
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Helper methods
512
+ */
513
+ private setupWatchHandlers(
514
+ watcher: FSWatcher,
515
+ config: WatchConfig,
516
+ name: string
517
+ ): void {
518
+ const executeCommands = async (eventType: string, filePath: string) => {
519
+ const relevantCommands = config.commands.filter(
520
+ cmd => cmd.trigger === eventType || cmd.trigger === 'change'
521
+ );
522
+
523
+ for (const cmd of relevantCommands) {
524
+ if (this.shouldExecuteCommand(cmd, filePath)) {
525
+ await this.executeWatchCommand(cmd, filePath);
526
+ }
527
+ }
528
+ };
529
+
530
+ watcher.on('add', filePath => {
531
+ logger.debug(`File added: ${filePath}`);
532
+ executeCommands('add', filePath);
533
+ });
534
+
535
+ watcher.on('change', filePath => {
536
+ logger.debug(`File changed: ${filePath}`);
537
+ executeCommands('change', filePath);
538
+ });
539
+
540
+ watcher.on('unlink', filePath => {
541
+ logger.debug(`File deleted: ${filePath}`);
542
+ executeCommands('delete', filePath);
543
+ });
544
+
545
+ watcher.on('error', error => {
546
+ logger.error(`Watch error in ${name}:`, error);
547
+ });
548
+
549
+ watcher.on('ready', () => {
550
+ logger.debug(`Watch ready: ${name}`);
551
+ });
552
+ }
553
+
554
+ private shouldExecuteCommand(cmd: WatchCommand, filePath: string): boolean {
555
+ if (!cmd.condition) return true;
556
+
557
+ // Implement condition checking logic
558
+ switch (cmd.condition) {
559
+ case 'test-files':
560
+ return filePath.includes('.test.') || filePath.includes('.spec.');
561
+ case 'source-files':
562
+ return !filePath.includes('.test.') && !filePath.includes('.spec.');
563
+ case 'lint-files':
564
+ return /\.(ts|tsx|js|jsx)$/.test(filePath);
565
+ case 'analysis-files':
566
+ return /\.(ts|tsx|js|jsx)$/.test(filePath);
567
+ default:
568
+ return true;
569
+ }
570
+ }
571
+
572
+ private async executeWatchCommand(
573
+ cmd: WatchCommand,
574
+ filePath: string
575
+ ): Promise<void> {
576
+ try {
577
+ logger.info(`Executing: ${cmd.command}`);
578
+
579
+ // Replace placeholders in command
580
+ const command = cmd.command.replace('{{file}}', filePath);
581
+
582
+ // Execute command
583
+ const { spawn } = await import('child_process');
584
+ const [cmdName, ...args] = command.split(' ');
585
+
586
+ if (!cmdName) {
587
+ logger.error('Invalid command: empty command string');
588
+ return;
589
+ }
590
+
591
+ const child = spawn(cmdName, args, {
592
+ stdio: 'inherit',
593
+ shell: true,
594
+ });
595
+
596
+ child.on('exit', code => {
597
+ if (code === 0) {
598
+ logger.success(`Command completed: ${cmd.command}`);
599
+ } else {
600
+ logger.error(`Command failed with exit code ${code}: ${cmd.command}`);
601
+ }
602
+ });
603
+ } catch (error) {
604
+ logger.error(`Failed to execute command: ${cmd.command}`, error);
605
+ }
606
+ }
607
+
608
+ private createWatchConfigFromOptions(
609
+ patterns: string[],
610
+ options: any
611
+ ): WatchConfig {
612
+ return {
613
+ patterns: patterns.length > 0 ? patterns : ['**/*'],
614
+ ignore: options.ignore ? options.ignore.split(',') : [],
615
+ commands: options.command
616
+ ? [
617
+ {
618
+ trigger: 'change',
619
+ command: options.command,
620
+ },
621
+ ]
622
+ : [],
623
+ debounce: parseInt(options.debounce) || 300,
624
+ recursive: options.recursive || true,
625
+ };
626
+ }
627
+
628
+ private async loadWatchConfigFile(file: string): Promise<WatchConfig> {
629
+ if (!(await fs.pathExists(file))) {
630
+ throw new Error(`Configuration file not found: ${file}`);
631
+ }
632
+
633
+ const content = await fs.readFile(file, 'utf8');
634
+ const ext = path.extname(file).toLowerCase();
635
+
636
+ if (ext === '.yaml' || ext === '.yml') {
637
+ return YAML.parse(content);
638
+ } else if (ext === '.json') {
639
+ return JSON.parse(content);
640
+ } else {
641
+ throw new Error(`Unsupported configuration format: ${ext}`);
642
+ }
643
+ }
644
+
645
+ private async startWatchingWithConfig(
646
+ name: string,
647
+ config: WatchConfig
648
+ ): Promise<void> {
649
+ const watcher = watch(config.patterns, {
650
+ ignored: config.ignore || [],
651
+ persistent: true,
652
+ ignoreInitial: true,
653
+ awaitWriteFinish: {
654
+ stabilityThreshold: config.debounce || 300,
655
+ pollInterval: 100,
656
+ },
657
+ });
658
+
659
+ this.setupWatchHandlers(watcher, config, name);
660
+ this.watchers.set(name, watcher);
661
+ this.activeWatches.set(name, config);
662
+
663
+ logger.success(`Started watch: ${name}`);
664
+ }
665
+
666
+ private async createInteractiveWatchConfig(): Promise<WatchConfig> {
667
+ const inquirer = await import('inquirer');
668
+
669
+ const answers = await inquirer.default.prompt([
670
+ {
671
+ type: 'input',
672
+ name: 'patterns',
673
+ message: 'File patterns to watch (comma-separated):',
674
+ default: '**/*',
675
+ },
676
+ {
677
+ type: 'input',
678
+ name: 'ignore',
679
+ message: 'Patterns to ignore (comma-separated):',
680
+ default: 'node_modules/**,dist/**',
681
+ },
682
+ {
683
+ type: 'input',
684
+ name: 'command',
685
+ message: 'Command to run on changes:',
686
+ validate: input => input.length > 0 || 'Command is required',
687
+ },
688
+ {
689
+ type: 'number',
690
+ name: 'debounce',
691
+ message: 'Debounce delay (ms):',
692
+ default: 300,
693
+ },
694
+ ]);
695
+
696
+ return {
697
+ patterns: answers.patterns.split(',').map(p => p.trim()),
698
+ ignore: answers.ignore.split(',').map(p => p.trim()),
699
+ commands: [
700
+ {
701
+ trigger: 'change',
702
+ command: answers.command,
703
+ },
704
+ ],
705
+ debounce: answers.debounce,
706
+ recursive: true,
707
+ };
708
+ }
709
+
710
+ private parseWatchCommands(commandsStr: string): WatchCommand[] {
711
+ return commandsStr.split(',').map(cmd => ({
712
+ trigger: 'change',
713
+ command: cmd.trim(),
714
+ }));
715
+ }
716
+
717
+ private getTestPatterns(framework: string): string[] {
718
+ switch (framework) {
719
+ case 'jest':
720
+ return ['**/*.{test,spec}.{js,jsx,ts,tsx}', 'src/**/*'];
721
+ case 'mocha':
722
+ return ['test/**/*.js', 'src/**/*'];
723
+ case 'vitest':
724
+ return ['**/*.{test,spec}.{js,ts}', 'src/**/*'];
725
+ default:
726
+ return ['**/*.{test,spec}.*', 'src/**/*'];
727
+ }
728
+ }
729
+
730
+ private getTestCommand(framework: string, options: any): string {
731
+ const baseCmd = framework === 'npm' ? `npm test` : `npx ${framework}`;
732
+ const flags: string[] = [];
733
+
734
+ if (options.coverage) flags.push('--coverage');
735
+ if (options.changedOnly) flags.push('--changedSince=HEAD');
736
+
737
+ return `${baseCmd} ${flags.join(' ')}`;
738
+ }
739
+
740
+ private getBuildCommand(options: any): string {
741
+ const cmd = options.target
742
+ ? `npm run build:${options.target}`
743
+ : 'npm run build';
744
+ return options.incremental ? `${cmd} --incremental` : cmd;
745
+ }
746
+
747
+ private getLintCommand(options: any): string {
748
+ const cmd = 'npx eslint {{file}}';
749
+ return options.fix ? `${cmd} --fix` : cmd;
750
+ }
751
+
752
+ private getAnalysisCommand(options: any): string {
753
+ return `wundr analyze ${options.type}`;
754
+ }
755
+ }