kiro-spec-engine 1.2.3 → 1.4.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 (78) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/README.md +239 -213
  3. package/README.zh.md +0 -330
  4. package/bin/kiro-spec-engine.js +62 -0
  5. package/docs/README.md +223 -0
  6. package/docs/agent-hooks-analysis.md +815 -0
  7. package/docs/command-reference.md +252 -0
  8. package/docs/cross-tool-guide.md +554 -0
  9. package/docs/examples/add-export-command/design.md +194 -0
  10. package/docs/examples/add-export-command/requirements.md +110 -0
  11. package/docs/examples/add-export-command/tasks.md +88 -0
  12. package/docs/examples/add-rest-api/design.md +855 -0
  13. package/docs/examples/add-rest-api/requirements.md +323 -0
  14. package/docs/examples/add-rest-api/tasks.md +355 -0
  15. package/docs/examples/add-user-dashboard/design.md +192 -0
  16. package/docs/examples/add-user-dashboard/requirements.md +143 -0
  17. package/docs/examples/add-user-dashboard/tasks.md +91 -0
  18. package/docs/faq.md +696 -0
  19. package/docs/integration-modes.md +525 -0
  20. package/docs/integration-philosophy.md +313 -0
  21. package/docs/manual-workflows-guide.md +417 -0
  22. package/docs/quick-start-with-ai-tools.md +374 -0
  23. package/docs/quick-start.md +711 -0
  24. package/docs/spec-workflow.md +453 -0
  25. package/docs/steering-strategy-guide.md +196 -0
  26. package/docs/tools/claude-guide.md +653 -0
  27. package/docs/tools/cursor-guide.md +705 -0
  28. package/docs/tools/generic-guide.md +445 -0
  29. package/docs/tools/kiro-guide.md +308 -0
  30. package/docs/tools/vscode-guide.md +444 -0
  31. package/docs/tools/windsurf-guide.md +390 -0
  32. package/docs/troubleshooting.md +795 -0
  33. package/docs/zh/README.md +275 -0
  34. package/docs/zh/quick-start.md +711 -0
  35. package/docs/zh/tools/claude-guide.md +348 -0
  36. package/docs/zh/tools/cursor-guide.md +280 -0
  37. package/docs/zh/tools/generic-guide.md +498 -0
  38. package/docs/zh/tools/kiro-guide.md +342 -0
  39. package/docs/zh/tools/vscode-guide.md +448 -0
  40. package/docs/zh/tools/windsurf-guide.md +377 -0
  41. package/lib/adoption/detection-engine.js +14 -4
  42. package/lib/commands/adopt.js +117 -3
  43. package/lib/commands/context.js +99 -0
  44. package/lib/commands/prompt.js +105 -0
  45. package/lib/commands/status.js +225 -0
  46. package/lib/commands/task.js +199 -0
  47. package/lib/commands/watch.js +569 -0
  48. package/lib/commands/workflows.js +240 -0
  49. package/lib/commands/workspace.js +189 -0
  50. package/lib/context/context-exporter.js +378 -0
  51. package/lib/context/prompt-generator.js +482 -0
  52. package/lib/steering/adoption-config.js +164 -0
  53. package/lib/steering/steering-manager.js +289 -0
  54. package/lib/task/task-claimer.js +430 -0
  55. package/lib/utils/tool-detector.js +383 -0
  56. package/lib/watch/action-executor.js +458 -0
  57. package/lib/watch/event-debouncer.js +323 -0
  58. package/lib/watch/execution-logger.js +550 -0
  59. package/lib/watch/file-watcher.js +499 -0
  60. package/lib/watch/presets.js +266 -0
  61. package/lib/watch/watch-manager.js +533 -0
  62. package/lib/workspace/workspace-manager.js +370 -0
  63. package/lib/workspace/workspace-sync.js +356 -0
  64. package/package.json +3 -1
  65. package/template/.kiro/tools/backup_manager.py +295 -0
  66. package/template/.kiro/tools/configuration_manager.py +218 -0
  67. package/template/.kiro/tools/document_evaluator.py +550 -0
  68. package/template/.kiro/tools/enhancement_logger.py +168 -0
  69. package/template/.kiro/tools/error_handler.py +335 -0
  70. package/template/.kiro/tools/improvement_identifier.py +444 -0
  71. package/template/.kiro/tools/modification_applicator.py +737 -0
  72. package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
  73. package/template/.kiro/tools/quality_scorer.py +305 -0
  74. package/template/.kiro/tools/report_generator.py +154 -0
  75. package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
  76. package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
  77. package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
  78. package/template/.kiro/tools/workflow_quality_gate.py +100 -0
@@ -0,0 +1,550 @@
1
+ const EventEmitter = require('events');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+
5
+ /**
6
+ * ExecutionLogger - 执行日志记录器
7
+ *
8
+ * 记录所有自动化执行,跟踪指标,管理日志轮转
9
+ */
10
+ class ExecutionLogger extends EventEmitter {
11
+ constructor(config = {}) {
12
+ super();
13
+
14
+ this.config = {
15
+ logDir: config.logDir || '.kiro/watch/logs',
16
+ logFile: config.logFile || 'execution.log',
17
+ maxLogSize: config.maxLogSize || 10 * 1024 * 1024, // 10MB
18
+ maxLogFiles: config.maxLogFiles || 5,
19
+ logLevel: config.logLevel || 'info', // 'debug', 'info', 'warn', 'error'
20
+ enableRotation: config.enableRotation !== false,
21
+ enableMetrics: config.enableMetrics !== false,
22
+ ...config
23
+ };
24
+
25
+ // 日志级别优先级
26
+ this.logLevels = {
27
+ debug: 0,
28
+ info: 1,
29
+ warn: 2,
30
+ error: 3
31
+ };
32
+
33
+ // 指标数据
34
+ this.metrics = {
35
+ totalExecutions: 0,
36
+ successfulExecutions: 0,
37
+ failedExecutions: 0,
38
+ totalDuration: 0,
39
+ averageDuration: 0,
40
+ timeSaved: 0, // 估算节省的时间
41
+ lastExecution: null,
42
+ executionsByAction: {},
43
+ errorsByType: {}
44
+ };
45
+
46
+ // 错误记录
47
+ this.errors = [];
48
+ this.maxErrors = config.maxErrors || 100;
49
+
50
+ // 确保日志目录存在
51
+ this._ensureLogDir();
52
+ }
53
+
54
+ /**
55
+ * 确保日志目录存在
56
+ *
57
+ * @private
58
+ */
59
+ _ensureLogDir() {
60
+ try {
61
+ fs.ensureDirSync(this.config.logDir);
62
+ } catch (error) {
63
+ this.emit('error', {
64
+ message: 'Failed to create log directory',
65
+ error,
66
+ timestamp: new Date()
67
+ });
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 记录日志
73
+ *
74
+ * @param {string} level - 日志级别
75
+ * @param {string} event - 事件类型
76
+ * @param {Object} data - 日志数据
77
+ */
78
+ log(level, event, data = {}) {
79
+ // 检查日志级别
80
+ if (!this._shouldLog(level)) {
81
+ return;
82
+ }
83
+
84
+ const logEntry = {
85
+ timestamp: new Date().toISOString(),
86
+ level,
87
+ event,
88
+ ...data
89
+ };
90
+
91
+ // 写入日志文件
92
+ this._writeLog(logEntry);
93
+
94
+ // 更新指标
95
+ if (this.config.enableMetrics) {
96
+ this._updateMetrics(event, data);
97
+ }
98
+
99
+ // 记录错误
100
+ if (level === 'error') {
101
+ this._recordError(logEntry);
102
+ }
103
+
104
+ // 触发事件
105
+ this.emit('log', logEntry);
106
+ this.emit(`log:${level}`, logEntry);
107
+
108
+ // 检查是否需要轮转
109
+ if (this.config.enableRotation) {
110
+ this._checkRotation();
111
+ }
112
+ }
113
+
114
+ /**
115
+ * 记录调试信息
116
+ *
117
+ * @param {string} event - 事件类型
118
+ * @param {Object} data - 数据
119
+ */
120
+ debug(event, data) {
121
+ this.log('debug', event, data);
122
+ }
123
+
124
+ /**
125
+ * 记录信息
126
+ *
127
+ * @param {string} event - 事件类型
128
+ * @param {Object} data - 数据
129
+ */
130
+ info(event, data) {
131
+ this.log('info', event, data);
132
+ }
133
+
134
+ /**
135
+ * 记录警告
136
+ *
137
+ * @param {string} event - 事件类型
138
+ * @param {Object} data - 数据
139
+ */
140
+ warn(event, data) {
141
+ this.log('warn', event, data);
142
+ }
143
+
144
+ /**
145
+ * 记录错误
146
+ *
147
+ * @param {string} event - 事件类型
148
+ * @param {Object} data - 数据
149
+ */
150
+ error(event, data) {
151
+ this.log('error', event, data);
152
+ }
153
+
154
+ /**
155
+ * 检查是否应该记录
156
+ *
157
+ * @private
158
+ * @param {string} level - 日志级别
159
+ * @returns {boolean} 是否应该记录
160
+ */
161
+ _shouldLog(level) {
162
+ const currentLevel = this.logLevels[this.config.logLevel] || 1;
163
+ const messageLevel = this.logLevels[level] || 1;
164
+ return messageLevel >= currentLevel;
165
+ }
166
+
167
+ /**
168
+ * 写入日志
169
+ *
170
+ * @private
171
+ * @param {Object} logEntry - 日志条目
172
+ */
173
+ _writeLog(logEntry) {
174
+ try {
175
+ const logPath = path.join(this.config.logDir, this.config.logFile);
176
+ const logLine = JSON.stringify(logEntry) + '\n';
177
+
178
+ fs.appendFileSync(logPath, logLine, 'utf8');
179
+ } catch (error) {
180
+ this.emit('error', {
181
+ message: 'Failed to write log',
182
+ error,
183
+ timestamp: new Date()
184
+ });
185
+ }
186
+ }
187
+
188
+ /**
189
+ * 更新指标
190
+ *
191
+ * @private
192
+ * @param {string} event - 事件类型
193
+ * @param {Object} data - 数据
194
+ */
195
+ _updateMetrics(event, data) {
196
+ // 更新执行计数
197
+ if (event === 'execution:success' || event === 'execution:error') {
198
+ this.metrics.totalExecutions++;
199
+ this.metrics.lastExecution = new Date();
200
+
201
+ if (event === 'execution:success') {
202
+ this.metrics.successfulExecutions++;
203
+ } else {
204
+ this.metrics.failedExecutions++;
205
+ }
206
+
207
+ // 更新持续时间
208
+ if (data.duration) {
209
+ this.metrics.totalDuration += data.duration;
210
+ this.metrics.averageDuration =
211
+ this.metrics.totalDuration / this.metrics.totalExecutions;
212
+ }
213
+
214
+ // 按动作统计
215
+ if (data.command) {
216
+ const action = data.command.split(' ')[0]; // 获取命令的第一部分
217
+ if (!this.metrics.executionsByAction[action]) {
218
+ this.metrics.executionsByAction[action] = {
219
+ count: 0,
220
+ success: 0,
221
+ failed: 0
222
+ };
223
+ }
224
+ this.metrics.executionsByAction[action].count++;
225
+ if (event === 'execution:success') {
226
+ this.metrics.executionsByAction[action].success++;
227
+ } else {
228
+ this.metrics.executionsByAction[action].failed++;
229
+ }
230
+ }
231
+
232
+ // 估算节省的时间(假设手动执行需要30秒)
233
+ if (event === 'execution:success') {
234
+ this.metrics.timeSaved += 30000; // 30 seconds in ms
235
+ }
236
+ }
237
+
238
+ // 按错误类型统计
239
+ if (event === 'execution:error' && data.error) {
240
+ const errorType = data.error.split(':')[0] || 'Unknown';
241
+ this.metrics.errorsByType[errorType] =
242
+ (this.metrics.errorsByType[errorType] || 0) + 1;
243
+ }
244
+ }
245
+
246
+ /**
247
+ * 记录错误
248
+ *
249
+ * @private
250
+ * @param {Object} logEntry - 日志条目
251
+ */
252
+ _recordError(logEntry) {
253
+ this.errors.push(logEntry);
254
+
255
+ // 限制错误记录数量
256
+ if (this.errors.length > this.maxErrors) {
257
+ this.errors.shift();
258
+ }
259
+ }
260
+
261
+ /**
262
+ * 检查是否需要轮转
263
+ *
264
+ * @private
265
+ */
266
+ _checkRotation() {
267
+ try {
268
+ const logPath = path.join(this.config.logDir, this.config.logFile);
269
+
270
+ if (!fs.existsSync(logPath)) {
271
+ return;
272
+ }
273
+
274
+ const stats = fs.statSync(logPath);
275
+
276
+ if (stats.size >= this.config.maxLogSize) {
277
+ this.rotate();
278
+ }
279
+ } catch (error) {
280
+ this.emit('error', {
281
+ message: 'Failed to check log rotation',
282
+ error,
283
+ timestamp: new Date()
284
+ });
285
+ }
286
+ }
287
+
288
+ /**
289
+ * 轮转日志
290
+ */
291
+ rotate() {
292
+ try {
293
+ const logPath = path.join(this.config.logDir, this.config.logFile);
294
+
295
+ if (!fs.existsSync(logPath)) {
296
+ return;
297
+ }
298
+
299
+ // 删除最老的日志(如果存在)
300
+ const oldestPath = path.join(
301
+ this.config.logDir,
302
+ `${this.config.logFile}.${this.config.maxLogFiles}`
303
+ );
304
+ if (fs.existsSync(oldestPath)) {
305
+ fs.removeSync(oldestPath);
306
+ }
307
+
308
+ // 轮转现有日志文件
309
+ for (let i = this.config.maxLogFiles - 1; i >= 1; i--) {
310
+ const oldPath = path.join(
311
+ this.config.logDir,
312
+ `${this.config.logFile}.${i}`
313
+ );
314
+ const newPath = path.join(
315
+ this.config.logDir,
316
+ `${this.config.logFile}.${i + 1}`
317
+ );
318
+
319
+ if (fs.existsSync(oldPath)) {
320
+ fs.moveSync(oldPath, newPath, { overwrite: true });
321
+ }
322
+ }
323
+
324
+ // 移动当前日志
325
+ const rotatedPath = path.join(
326
+ this.config.logDir,
327
+ `${this.config.logFile}.1`
328
+ );
329
+ fs.moveSync(logPath, rotatedPath, { overwrite: true });
330
+
331
+ this.emit('rotated', {
332
+ timestamp: new Date(),
333
+ rotatedFile: rotatedPath
334
+ });
335
+
336
+ } catch (error) {
337
+ this.emit('error', {
338
+ message: 'Failed to rotate logs',
339
+ error,
340
+ timestamp: new Date()
341
+ });
342
+ }
343
+ }
344
+
345
+ /**
346
+ * 获取指标
347
+ *
348
+ * @returns {Object} 指标数据
349
+ */
350
+ getMetrics() {
351
+ return {
352
+ ...this.metrics,
353
+ successRate: this.metrics.totalExecutions > 0
354
+ ? (this.metrics.successfulExecutions / this.metrics.totalExecutions * 100).toFixed(2) + '%'
355
+ : '0%',
356
+ timeSavedFormatted: this._formatDuration(this.metrics.timeSaved)
357
+ };
358
+ }
359
+
360
+ /**
361
+ * 获取错误列表
362
+ *
363
+ * @param {number} limit - 限制数量
364
+ * @returns {Array} 错误列表
365
+ */
366
+ getErrors(limit = null) {
367
+ if (limit) {
368
+ return this.errors.slice(-limit);
369
+ }
370
+ return [...this.errors];
371
+ }
372
+
373
+ /**
374
+ * 清除错误
375
+ */
376
+ clearErrors() {
377
+ this.errors = [];
378
+ this.emit('errors:cleared');
379
+ }
380
+
381
+ /**
382
+ * 重置指标
383
+ */
384
+ resetMetrics() {
385
+ this.metrics = {
386
+ totalExecutions: 0,
387
+ successfulExecutions: 0,
388
+ failedExecutions: 0,
389
+ totalDuration: 0,
390
+ averageDuration: 0,
391
+ timeSaved: 0,
392
+ lastExecution: null,
393
+ executionsByAction: {},
394
+ errorsByType: {}
395
+ };
396
+
397
+ this.emit('metrics:reset');
398
+ }
399
+
400
+ /**
401
+ * 导出指标到文件
402
+ *
403
+ * @param {string} format - 格式 ('json' 或 'csv')
404
+ * @param {string} outputPath - 输出路径
405
+ * @returns {Promise<void>}
406
+ */
407
+ async exportMetrics(format = 'json', outputPath = null) {
408
+ const metrics = this.getMetrics();
409
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
410
+
411
+ if (!outputPath) {
412
+ outputPath = path.join(
413
+ this.config.logDir,
414
+ `metrics-${timestamp}.${format}`
415
+ );
416
+ }
417
+
418
+ try {
419
+ if (format === 'json') {
420
+ await fs.writeJson(outputPath, metrics, { spaces: 2 });
421
+ } else if (format === 'csv') {
422
+ const csv = this._metricsToCSV(metrics);
423
+ await fs.writeFile(outputPath, csv, 'utf8');
424
+ } else {
425
+ throw new Error(`Unsupported format: ${format}`);
426
+ }
427
+
428
+ this.emit('metrics:exported', {
429
+ format,
430
+ outputPath,
431
+ timestamp: new Date()
432
+ });
433
+
434
+ return outputPath;
435
+ } catch (error) {
436
+ this.emit('error', {
437
+ message: 'Failed to export metrics',
438
+ error,
439
+ timestamp: new Date()
440
+ });
441
+ throw error;
442
+ }
443
+ }
444
+
445
+ /**
446
+ * 将指标转换为 CSV
447
+ *
448
+ * @private
449
+ * @param {Object} metrics - 指标数据
450
+ * @returns {string} CSV 字符串
451
+ */
452
+ _metricsToCSV(metrics) {
453
+ const lines = [];
454
+
455
+ // 基本指标
456
+ lines.push('Metric,Value');
457
+ lines.push(`Total Executions,${metrics.totalExecutions}`);
458
+ lines.push(`Successful Executions,${metrics.successfulExecutions}`);
459
+ lines.push(`Failed Executions,${metrics.failedExecutions}`);
460
+ lines.push(`Success Rate,${metrics.successRate}`);
461
+ lines.push(`Average Duration,${metrics.averageDuration.toFixed(2)}ms`);
462
+ lines.push(`Time Saved,${metrics.timeSavedFormatted}`);
463
+
464
+ return lines.join('\n');
465
+ }
466
+
467
+ /**
468
+ * 格式化持续时间
469
+ *
470
+ * @private
471
+ * @param {number} ms - 毫秒
472
+ * @returns {string} 格式化的持续时间
473
+ */
474
+ _formatDuration(ms) {
475
+ const seconds = Math.floor(ms / 1000);
476
+ const minutes = Math.floor(seconds / 60);
477
+ const hours = Math.floor(minutes / 60);
478
+
479
+ if (hours > 0) {
480
+ return `${hours}h ${minutes % 60}m`;
481
+ } else if (minutes > 0) {
482
+ return `${minutes}m ${seconds % 60}s`;
483
+ } else {
484
+ return `${seconds}s`;
485
+ }
486
+ }
487
+
488
+ /**
489
+ * 读取日志
490
+ *
491
+ * @param {number} lines - 读取的行数
492
+ * @returns {Promise<Array>} 日志条目数组
493
+ */
494
+ async readLogs(lines = 100) {
495
+ try {
496
+ const logPath = path.join(this.config.logDir, this.config.logFile);
497
+
498
+ if (!fs.existsSync(logPath)) {
499
+ return [];
500
+ }
501
+
502
+ const content = await fs.readFile(logPath, 'utf8');
503
+ const allLines = content.trim().split('\n').filter(line => line);
504
+
505
+ // 获取最后 N 行
506
+ const selectedLines = allLines.slice(-lines);
507
+
508
+ // 解析 JSON
509
+ return selectedLines.map(line => {
510
+ try {
511
+ return JSON.parse(line);
512
+ } catch (error) {
513
+ return { error: 'Failed to parse log line', line };
514
+ }
515
+ });
516
+ } catch (error) {
517
+ this.emit('error', {
518
+ message: 'Failed to read logs',
519
+ error,
520
+ timestamp: new Date()
521
+ });
522
+ return [];
523
+ }
524
+ }
525
+
526
+ /**
527
+ * 获取配置
528
+ *
529
+ * @returns {Object} 配置
530
+ */
531
+ getConfig() {
532
+ return { ...this.config };
533
+ }
534
+
535
+ /**
536
+ * 设置日志级别
537
+ *
538
+ * @param {string} level - 日志级别
539
+ */
540
+ setLogLevel(level) {
541
+ if (!this.logLevels.hasOwnProperty(level)) {
542
+ throw new Error(`Invalid log level: ${level}`);
543
+ }
544
+
545
+ this.config.logLevel = level;
546
+ this.emit('config:updated', { logLevel: level });
547
+ }
548
+ }
549
+
550
+ module.exports = ExecutionLogger;