kiro-spec-engine 1.2.2 → 1.3.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 (49) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +172 -0
  3. package/bin/kiro-spec-engine.js +62 -0
  4. package/docs/adoption-guide.md +506 -0
  5. package/docs/agent-hooks-analysis.md +815 -0
  6. package/docs/architecture.md +706 -0
  7. package/docs/cross-tool-guide.md +554 -0
  8. package/docs/developer-guide.md +615 -0
  9. package/docs/manual-workflows-guide.md +417 -0
  10. package/docs/steering-strategy-guide.md +196 -0
  11. package/docs/upgrade-guide.md +632 -0
  12. package/lib/adoption/detection-engine.js +14 -4
  13. package/lib/commands/adopt.js +117 -3
  14. package/lib/commands/context.js +99 -0
  15. package/lib/commands/prompt.js +105 -0
  16. package/lib/commands/status.js +225 -0
  17. package/lib/commands/task.js +199 -0
  18. package/lib/commands/watch.js +569 -0
  19. package/lib/commands/workflows.js +240 -0
  20. package/lib/commands/workspace.js +189 -0
  21. package/lib/context/context-exporter.js +378 -0
  22. package/lib/context/prompt-generator.js +482 -0
  23. package/lib/steering/adoption-config.js +164 -0
  24. package/lib/steering/steering-manager.js +289 -0
  25. package/lib/task/task-claimer.js +430 -0
  26. package/lib/utils/tool-detector.js +383 -0
  27. package/lib/watch/action-executor.js +458 -0
  28. package/lib/watch/event-debouncer.js +323 -0
  29. package/lib/watch/execution-logger.js +550 -0
  30. package/lib/watch/file-watcher.js +499 -0
  31. package/lib/watch/presets.js +266 -0
  32. package/lib/watch/watch-manager.js +533 -0
  33. package/lib/workspace/workspace-manager.js +370 -0
  34. package/lib/workspace/workspace-sync.js +356 -0
  35. package/package.json +4 -1
  36. package/template/.kiro/tools/backup_manager.py +295 -0
  37. package/template/.kiro/tools/configuration_manager.py +218 -0
  38. package/template/.kiro/tools/document_evaluator.py +550 -0
  39. package/template/.kiro/tools/enhancement_logger.py +168 -0
  40. package/template/.kiro/tools/error_handler.py +335 -0
  41. package/template/.kiro/tools/improvement_identifier.py +444 -0
  42. package/template/.kiro/tools/modification_applicator.py +737 -0
  43. package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
  44. package/template/.kiro/tools/quality_scorer.py +305 -0
  45. package/template/.kiro/tools/report_generator.py +154 -0
  46. package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
  47. package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
  48. package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
  49. package/template/.kiro/tools/workflow_quality_gate.py +100 -0
@@ -0,0 +1,533 @@
1
+ const EventEmitter = require('events');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const FileWatcher = require('./file-watcher');
5
+ const EventDebouncer = require('./event-debouncer');
6
+ const ActionExecutor = require('./action-executor');
7
+ const ExecutionLogger = require('./execution-logger');
8
+
9
+ /**
10
+ * WatchManager - Watch 模式管理器
11
+ *
12
+ * 协调所有 watch 组件,管理生命周期
13
+ */
14
+ class WatchManager extends EventEmitter {
15
+ constructor(config = {}) {
16
+ super();
17
+
18
+ this.config = {
19
+ configFile: config.configFile || '.kiro/watch-config.json',
20
+ basePath: config.basePath || process.cwd(),
21
+ autoStart: config.autoStart !== false,
22
+ ...config
23
+ };
24
+
25
+ // 组件实例
26
+ this.fileWatcher = null;
27
+ this.debouncer = null;
28
+ this.executor = null;
29
+ this.logger = null;
30
+
31
+ // 状态
32
+ this.isRunning = false;
33
+ this.watchConfig = null;
34
+
35
+ // 统计信息
36
+ this.stats = {
37
+ startedAt: null,
38
+ stoppedAt: null,
39
+ filesWatched: 0,
40
+ eventsProcessed: 0,
41
+ actionsExecuted: 0
42
+ };
43
+ }
44
+
45
+ /**
46
+ * 启动 watch 模式
47
+ *
48
+ * @param {Object} config - 配置(可选,如果不提供则从文件加载)
49
+ * @returns {Promise<void>}
50
+ */
51
+ async start(config = null) {
52
+ if (this.isRunning) {
53
+ throw new Error('WatchManager is already running');
54
+ }
55
+
56
+ try {
57
+ // 1. 加载配置
58
+ if (config) {
59
+ this.watchConfig = config;
60
+ } else {
61
+ this.watchConfig = await this.loadConfig();
62
+ }
63
+
64
+ // 2. 验证配置
65
+ this._validateConfig(this.watchConfig);
66
+
67
+ // 3. 初始化组件
68
+ await this._initializeComponents();
69
+
70
+ // 4. 启动文件监控
71
+ await this._startWatching();
72
+
73
+ // 5. 更新状态
74
+ this.isRunning = true;
75
+ this.stats.startedAt = new Date();
76
+
77
+ this.emit('started', {
78
+ config: this.watchConfig,
79
+ timestamp: new Date()
80
+ });
81
+
82
+ } catch (error) {
83
+ this.isRunning = false;
84
+ this.emit('error', {
85
+ message: 'Failed to start watch mode',
86
+ error,
87
+ timestamp: new Date()
88
+ });
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * 停止 watch 模式
95
+ *
96
+ * @returns {Promise<void>}
97
+ */
98
+ async stop() {
99
+ if (!this.isRunning) {
100
+ return;
101
+ }
102
+
103
+ try {
104
+ // 1. 停止文件监控
105
+ if (this.fileWatcher) {
106
+ await this.fileWatcher.stop();
107
+ }
108
+
109
+ // 2. 清理 debouncer
110
+ if (this.debouncer) {
111
+ this.debouncer.clear();
112
+ }
113
+
114
+ // 3. 更新状态
115
+ this.isRunning = false;
116
+ this.stats.stoppedAt = new Date();
117
+
118
+ this.emit('stopped', {
119
+ stats: this.getStats(),
120
+ timestamp: new Date()
121
+ });
122
+
123
+ } catch (error) {
124
+ this.emit('error', {
125
+ message: 'Failed to stop watch mode',
126
+ error,
127
+ timestamp: new Date()
128
+ });
129
+ throw error;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * 重启 watch 模式
135
+ *
136
+ * @returns {Promise<void>}
137
+ */
138
+ async restart() {
139
+ await this.stop();
140
+ await new Promise(resolve => setTimeout(resolve, 1000));
141
+ await this.start();
142
+ }
143
+
144
+ /**
145
+ * 初始化组件
146
+ *
147
+ * @private
148
+ * @returns {Promise<void>}
149
+ */
150
+ async _initializeComponents() {
151
+ // 1. 初始化 logger
152
+ this.logger = new ExecutionLogger({
153
+ logDir: path.join(this.config.basePath, '.kiro/watch/logs'),
154
+ logLevel: this.watchConfig.logging?.level || 'info',
155
+ maxLogSize: this._parseSize(this.watchConfig.logging?.maxSize || '10MB'),
156
+ enableRotation: this.watchConfig.logging?.rotation !== false
157
+ });
158
+
159
+ // 2. 初始化 executor
160
+ this.executor = new ActionExecutor({
161
+ maxRetries: this.watchConfig.retry?.maxAttempts || 3,
162
+ retryBackoff: this.watchConfig.retry?.backoff || 'exponential',
163
+ cwd: this.config.basePath
164
+ });
165
+
166
+ // 监听执行事件
167
+ this.executor.on('execution:success', (data) => {
168
+ this.logger.info('execution:success', data);
169
+ this.stats.actionsExecuted++;
170
+ });
171
+
172
+ this.executor.on('execution:error', (data) => {
173
+ this.logger.error('execution:error', data);
174
+ });
175
+
176
+ // 3. 初始化 debouncer
177
+ this.debouncer = new EventDebouncer({
178
+ defaultDelay: this.watchConfig.debounce?.default || 2000
179
+ });
180
+
181
+ // 4. 初始化 file watcher
182
+ this.fileWatcher = new FileWatcher({
183
+ patterns: this.watchConfig.patterns || [],
184
+ ignored: this.watchConfig.ignored || [],
185
+ persistent: true
186
+ });
187
+
188
+ // 监听文件事件
189
+ this.fileWatcher.on('file:changed', (data) => this._handleFileEvent('changed', data));
190
+ this.fileWatcher.on('file:added', (data) => this._handleFileEvent('added', data));
191
+ this.fileWatcher.on('file:deleted', (data) => this._handleFileEvent('deleted', data));
192
+ }
193
+
194
+ /**
195
+ * 启动文件监控
196
+ *
197
+ * @private
198
+ * @returns {Promise<void>}
199
+ */
200
+ async _startWatching() {
201
+ await this.fileWatcher.start(this.config.basePath);
202
+ this.stats.filesWatched = this.fileWatcher.getWatchedFiles().length;
203
+ }
204
+
205
+ /**
206
+ * 处理文件事件
207
+ *
208
+ * @private
209
+ * @param {string} eventType - 事件类型
210
+ * @param {Object} data - 事件数据
211
+ */
212
+ _handleFileEvent(eventType, data) {
213
+ this.stats.eventsProcessed++;
214
+
215
+ // 查找匹配的动作
216
+ const action = this._findMatchingAction(data.path);
217
+
218
+ if (!action) {
219
+ this.logger.debug('no_action_found', {
220
+ file: data.path,
221
+ eventType
222
+ });
223
+ return;
224
+ }
225
+
226
+ // 获取 debounce 延迟
227
+ const delay = action.debounce ||
228
+ this.watchConfig.debounce?.perPattern?.[data.path] ||
229
+ this.watchConfig.debounce?.default ||
230
+ 2000;
231
+
232
+ // 使用 debouncer 处理
233
+ this.debouncer.debounce(
234
+ data.path,
235
+ async () => {
236
+ await this._executeAction(action, data);
237
+ },
238
+ delay
239
+ );
240
+ }
241
+
242
+ /**
243
+ * 查找匹配的动作
244
+ *
245
+ * @private
246
+ * @param {string} filePath - 文件路径
247
+ * @returns {Object|null} 动作配置
248
+ */
249
+ _findMatchingAction(filePath) {
250
+ if (!this.watchConfig.actions) {
251
+ return null;
252
+ }
253
+
254
+ // 规范化路径
255
+ const normalizedPath = filePath.replace(/\\/g, '/');
256
+
257
+ // 查找匹配的模式
258
+ for (const [pattern, action] of Object.entries(this.watchConfig.actions)) {
259
+ // 使用 FileWatcher 的 matchesPattern 方法
260
+ // 但需要创建一个临时的 FileWatcher 来测试模式
261
+ const { minimatch } = require('minimatch');
262
+
263
+ if (minimatch(normalizedPath, pattern, { dot: true })) {
264
+ return action;
265
+ }
266
+ }
267
+
268
+ return null;
269
+ }
270
+
271
+ /**
272
+ * 执行动作
273
+ *
274
+ * @private
275
+ * @param {Object} action - 动作配置
276
+ * @param {Object} fileData - 文件数据
277
+ * @returns {Promise<void>}
278
+ */
279
+ async _executeAction(action, fileData) {
280
+ try {
281
+ const context = {
282
+ file: fileData.path,
283
+ event: fileData.event,
284
+ timestamp: fileData.timestamp
285
+ };
286
+
287
+ await this.executor.execute(action, context);
288
+
289
+ } catch (error) {
290
+ this.emit('action:error', {
291
+ action,
292
+ file: fileData.path,
293
+ error,
294
+ timestamp: new Date()
295
+ });
296
+ }
297
+ }
298
+
299
+ /**
300
+ * 验证配置
301
+ *
302
+ * @private
303
+ * @param {Object} config - 配置
304
+ */
305
+ _validateConfig(config) {
306
+ if (!config) {
307
+ throw new Error('Configuration is required');
308
+ }
309
+
310
+ if (!config.patterns || !Array.isArray(config.patterns) || config.patterns.length === 0) {
311
+ throw new Error('At least one pattern is required');
312
+ }
313
+
314
+ if (config.actions && typeof config.actions !== 'object') {
315
+ throw new Error('Actions must be an object');
316
+ }
317
+ }
318
+
319
+ /**
320
+ * 解析大小字符串
321
+ *
322
+ * @private
323
+ * @param {string} sizeStr - 大小字符串(如 '10MB')
324
+ * @returns {number} 字节数
325
+ */
326
+ _parseSize(sizeStr) {
327
+ const units = {
328
+ B: 1,
329
+ KB: 1024,
330
+ MB: 1024 * 1024,
331
+ GB: 1024 * 1024 * 1024
332
+ };
333
+
334
+ const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*([A-Z]+)$/i);
335
+ if (!match) {
336
+ throw new Error(`Invalid size format: ${sizeStr}`);
337
+ }
338
+
339
+ const value = parseFloat(match[1]);
340
+ const unit = match[2].toUpperCase();
341
+
342
+ if (!units[unit]) {
343
+ throw new Error(`Unknown size unit: ${unit}`);
344
+ }
345
+
346
+ return Math.floor(value * units[unit]);
347
+ }
348
+
349
+ /**
350
+ * 加载配置
351
+ *
352
+ * @returns {Promise<Object>} 配置对象
353
+ */
354
+ async loadConfig() {
355
+ const configPath = path.join(this.config.basePath, this.config.configFile);
356
+
357
+ try {
358
+ if (!await fs.pathExists(configPath)) {
359
+ // 返回默认配置
360
+ return this._getDefaultConfig();
361
+ }
362
+
363
+ const config = await fs.readJson(configPath);
364
+
365
+ this.emit('config:loaded', {
366
+ configPath,
367
+ timestamp: new Date()
368
+ });
369
+
370
+ return config;
371
+
372
+ } catch (error) {
373
+ this.emit('error', {
374
+ message: 'Failed to load configuration',
375
+ error,
376
+ configPath,
377
+ timestamp: new Date()
378
+ });
379
+ throw error;
380
+ }
381
+ }
382
+
383
+ /**
384
+ * 保存配置
385
+ *
386
+ * @param {Object} config - 配置对象
387
+ * @returns {Promise<void>}
388
+ */
389
+ async saveConfig(config) {
390
+ const configPath = path.join(this.config.basePath, this.config.configFile);
391
+
392
+ try {
393
+ // 验证配置
394
+ this._validateConfig(config);
395
+
396
+ // 确保目录存在
397
+ await fs.ensureDir(path.dirname(configPath));
398
+
399
+ // 保存配置
400
+ await fs.writeJson(configPath, config, { spaces: 2 });
401
+
402
+ this.emit('config:saved', {
403
+ configPath,
404
+ timestamp: new Date()
405
+ });
406
+
407
+ } catch (error) {
408
+ this.emit('error', {
409
+ message: 'Failed to save configuration',
410
+ error,
411
+ configPath,
412
+ timestamp: new Date()
413
+ });
414
+ throw error;
415
+ }
416
+ }
417
+
418
+ /**
419
+ * 获取默认配置
420
+ *
421
+ * @private
422
+ * @returns {Object} 默认配置
423
+ */
424
+ _getDefaultConfig() {
425
+ return {
426
+ enabled: true,
427
+ patterns: ['**/*.md'],
428
+ ignored: ['**/node_modules/**', '**/.git/**'],
429
+ actions: {},
430
+ debounce: {
431
+ default: 2000
432
+ },
433
+ logging: {
434
+ enabled: true,
435
+ level: 'info',
436
+ maxSize: '10MB',
437
+ rotation: true
438
+ },
439
+ retry: {
440
+ enabled: true,
441
+ maxAttempts: 3,
442
+ backoff: 'exponential'
443
+ }
444
+ };
445
+ }
446
+
447
+ /**
448
+ * 获取状态
449
+ *
450
+ * @returns {Object} 状态信息
451
+ */
452
+ getStatus() {
453
+ return {
454
+ isRunning: this.isRunning,
455
+ config: this.watchConfig,
456
+ stats: this.getStats(),
457
+ components: {
458
+ fileWatcher: this.fileWatcher ? this.fileWatcher.getStatus() : null,
459
+ debouncer: this.debouncer ? this.debouncer.getStats() : null,
460
+ executor: this.executor ? this.executor.getStats() : null,
461
+ logger: this.logger ? this.logger.getMetrics() : null
462
+ }
463
+ };
464
+ }
465
+
466
+ /**
467
+ * 获取统计信息
468
+ *
469
+ * @returns {Object} 统计信息
470
+ */
471
+ getStats() {
472
+ const stats = { ...this.stats };
473
+
474
+ if (stats.startedAt && this.isRunning) {
475
+ stats.uptime = Date.now() - stats.startedAt.getTime();
476
+ }
477
+
478
+ return stats;
479
+ }
480
+
481
+ /**
482
+ * 获取日志
483
+ *
484
+ * @param {number} lines - 行数
485
+ * @returns {Promise<Array>} 日志条目
486
+ */
487
+ async getLogs(lines = 100) {
488
+ if (!this.logger) {
489
+ return [];
490
+ }
491
+
492
+ return await this.logger.readLogs(lines);
493
+ }
494
+
495
+ /**
496
+ * 获取指标
497
+ *
498
+ * @returns {Object} 指标数据
499
+ */
500
+ getMetrics() {
501
+ if (!this.logger) {
502
+ return {
503
+ totalExecutions: 0,
504
+ successfulExecutions: 0,
505
+ failedExecutions: 0,
506
+ successRate: 0,
507
+ averageDuration: 0,
508
+ timeSaved: 0,
509
+ byAction: {},
510
+ errors: []
511
+ };
512
+ }
513
+
514
+ return this.logger.getMetrics();
515
+ }
516
+
517
+ /**
518
+ * 导出指标
519
+ *
520
+ * @param {string} format - 格式
521
+ * @param {string} outputPath - 输出路径
522
+ * @returns {Promise<string>} 输出文件路径
523
+ */
524
+ async exportMetrics(format = 'json', outputPath = null) {
525
+ if (!this.logger) {
526
+ throw new Error('Logger not initialized');
527
+ }
528
+
529
+ return await this.logger.exportMetrics(format, outputPath);
530
+ }
531
+ }
532
+
533
+ module.exports = WatchManager;