kiro-spec-engine 1.2.3 → 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 (45) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +172 -0
  3. package/bin/kiro-spec-engine.js +62 -0
  4. package/docs/agent-hooks-analysis.md +815 -0
  5. package/docs/cross-tool-guide.md +554 -0
  6. package/docs/manual-workflows-guide.md +417 -0
  7. package/docs/steering-strategy-guide.md +196 -0
  8. package/lib/adoption/detection-engine.js +14 -4
  9. package/lib/commands/adopt.js +117 -3
  10. package/lib/commands/context.js +99 -0
  11. package/lib/commands/prompt.js +105 -0
  12. package/lib/commands/status.js +225 -0
  13. package/lib/commands/task.js +199 -0
  14. package/lib/commands/watch.js +569 -0
  15. package/lib/commands/workflows.js +240 -0
  16. package/lib/commands/workspace.js +189 -0
  17. package/lib/context/context-exporter.js +378 -0
  18. package/lib/context/prompt-generator.js +482 -0
  19. package/lib/steering/adoption-config.js +164 -0
  20. package/lib/steering/steering-manager.js +289 -0
  21. package/lib/task/task-claimer.js +430 -0
  22. package/lib/utils/tool-detector.js +383 -0
  23. package/lib/watch/action-executor.js +458 -0
  24. package/lib/watch/event-debouncer.js +323 -0
  25. package/lib/watch/execution-logger.js +550 -0
  26. package/lib/watch/file-watcher.js +499 -0
  27. package/lib/watch/presets.js +266 -0
  28. package/lib/watch/watch-manager.js +533 -0
  29. package/lib/workspace/workspace-manager.js +370 -0
  30. package/lib/workspace/workspace-sync.js +356 -0
  31. package/package.json +3 -1
  32. package/template/.kiro/tools/backup_manager.py +295 -0
  33. package/template/.kiro/tools/configuration_manager.py +218 -0
  34. package/template/.kiro/tools/document_evaluator.py +550 -0
  35. package/template/.kiro/tools/enhancement_logger.py +168 -0
  36. package/template/.kiro/tools/error_handler.py +335 -0
  37. package/template/.kiro/tools/improvement_identifier.py +444 -0
  38. package/template/.kiro/tools/modification_applicator.py +737 -0
  39. package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
  40. package/template/.kiro/tools/quality_scorer.py +305 -0
  41. package/template/.kiro/tools/report_generator.py +154 -0
  42. package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
  43. package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
  44. package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
  45. package/template/.kiro/tools/workflow_quality_gate.py +100 -0
@@ -0,0 +1,458 @@
1
+ const EventEmitter = require('events');
2
+ const { exec } = require('child_process');
3
+ const { promisify } = require('util');
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ /**
8
+ * ActionExecutor - 动作执行器
9
+ *
10
+ * 执行基于文件变化的命令,支持重试、超时和命令验证
11
+ */
12
+ class ActionExecutor extends EventEmitter {
13
+ constructor(config = {}) {
14
+ super();
15
+
16
+ this.config = {
17
+ maxRetries: config.maxRetries || 3,
18
+ retryDelay: config.retryDelay || 1000,
19
+ retryBackoff: config.retryBackoff || 'exponential', // 'linear' or 'exponential'
20
+ timeout: config.timeout || 30000, // 30 seconds
21
+ allowedCommands: config.allowedCommands || null, // null = allow all
22
+ cwd: config.cwd || process.cwd(),
23
+ ...config
24
+ };
25
+
26
+ // 执行历史
27
+ this.executionHistory = [];
28
+ this.maxHistorySize = config.maxHistorySize || 100;
29
+
30
+ // 统计信息
31
+ this.stats = {
32
+ totalExecutions: 0,
33
+ successfulExecutions: 0,
34
+ failedExecutions: 0,
35
+ retriedExecutions: 0,
36
+ timeoutExecutions: 0
37
+ };
38
+ }
39
+
40
+ /**
41
+ * 执行命令
42
+ *
43
+ * @param {Object} action - 动作配置
44
+ * @param {Object} context - 执行上下文
45
+ * @returns {Promise<Object>} 执行结果
46
+ */
47
+ async execute(action, context = {}) {
48
+ if (!action || action.command === undefined || action.command === null) {
49
+ throw new Error('Action must have a command property');
50
+ }
51
+
52
+ if (typeof action.command !== 'string' || action.command.trim().length === 0) {
53
+ throw new Error('Action command cannot be empty');
54
+ }
55
+
56
+ const startTime = Date.now();
57
+ const executionId = `exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
58
+
59
+ try {
60
+ // 1. 验证命令
61
+ this._validateAction(action);
62
+
63
+ // 2. 插值命令
64
+ const command = this._interpolateCommand(action.command, context);
65
+
66
+ // 3. 验证命令安全性
67
+ this._validateCommand(command);
68
+
69
+ this.emit('execution:start', {
70
+ executionId,
71
+ command,
72
+ context,
73
+ timestamp: new Date()
74
+ });
75
+
76
+ // 4. 执行命令
77
+ const result = await this._executeCommand(command, action);
78
+
79
+ const duration = Date.now() - startTime;
80
+
81
+ // 5. 记录成功
82
+ this.stats.totalExecutions++;
83
+ this.stats.successfulExecutions++;
84
+
85
+ const executionRecord = {
86
+ executionId,
87
+ command,
88
+ context,
89
+ result: {
90
+ success: true,
91
+ stdout: result.stdout,
92
+ stderr: result.stderr,
93
+ exitCode: 0
94
+ },
95
+ duration,
96
+ timestamp: new Date(),
97
+ retries: 0
98
+ };
99
+
100
+ this._addToHistory(executionRecord);
101
+
102
+ this.emit('execution:success', executionRecord);
103
+
104
+ return executionRecord.result;
105
+
106
+ } catch (error) {
107
+ const duration = Date.now() - startTime;
108
+
109
+ // 6. 处理错误
110
+ this.stats.totalExecutions++;
111
+ this.stats.failedExecutions++;
112
+
113
+ const executionRecord = {
114
+ executionId,
115
+ command: action.command,
116
+ context,
117
+ result: {
118
+ success: false,
119
+ error: error.message,
120
+ stdout: error.stdout || '',
121
+ stderr: error.stderr || '',
122
+ exitCode: error.code || -1
123
+ },
124
+ duration,
125
+ timestamp: new Date(),
126
+ retries: 0
127
+ };
128
+
129
+ this._addToHistory(executionRecord);
130
+
131
+ this.emit('execution:error', {
132
+ ...executionRecord,
133
+ error
134
+ });
135
+
136
+ // 7. 重试(如果配置)
137
+ if (action.retry !== false && this.config.maxRetries > 0) {
138
+ return await this._retryExecution(action, context, error, executionId);
139
+ }
140
+
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * 执行命令(内部方法)
147
+ *
148
+ * @private
149
+ * @param {string} command - 命令
150
+ * @param {Object} action - 动作配置
151
+ * @returns {Promise<Object>} 执行结果
152
+ */
153
+ async _executeCommand(command, action) {
154
+ const timeout = action.timeout || this.config.timeout;
155
+
156
+ try {
157
+ const result = await execAsync(command, {
158
+ cwd: this.config.cwd,
159
+ timeout,
160
+ maxBuffer: 1024 * 1024 * 10 // 10MB
161
+ });
162
+
163
+ return result;
164
+ } catch (error) {
165
+ // 检查是否超时
166
+ if (error.killed && error.signal === 'SIGTERM') {
167
+ this.stats.timeoutExecutions++;
168
+ this.emit('execution:timeout', {
169
+ command,
170
+ timeout,
171
+ timestamp: new Date()
172
+ });
173
+ }
174
+
175
+ throw error;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * 重试执行
181
+ *
182
+ * @private
183
+ * @param {Object} action - 动作配置
184
+ * @param {Object} context - 执行上下文
185
+ * @param {Error} originalError - 原始错误
186
+ * @param {string} executionId - 执行ID
187
+ * @returns {Promise<Object>} 执行结果
188
+ */
189
+ async _retryExecution(action, context, originalError, executionId) {
190
+ const maxRetries = action.maxRetries || this.config.maxRetries;
191
+
192
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
193
+ // 计算延迟
194
+ const delay = this._calculateRetryDelay(attempt);
195
+
196
+ this.emit('execution:retry', {
197
+ executionId,
198
+ attempt,
199
+ maxRetries,
200
+ delay,
201
+ error: originalError.message,
202
+ timestamp: new Date()
203
+ });
204
+
205
+ // 等待
206
+ await new Promise(resolve => setTimeout(resolve, delay));
207
+
208
+ try {
209
+ // 重新执行
210
+ const command = this._interpolateCommand(action.command, context);
211
+ const result = await this._executeCommand(command, action);
212
+
213
+ // 成功
214
+ this.stats.retriedExecutions++;
215
+
216
+ const executionRecord = {
217
+ executionId,
218
+ command,
219
+ context,
220
+ result: {
221
+ success: true,
222
+ stdout: result.stdout,
223
+ stderr: result.stderr,
224
+ exitCode: 0
225
+ },
226
+ duration: 0,
227
+ timestamp: new Date(),
228
+ retries: attempt
229
+ };
230
+
231
+ this._addToHistory(executionRecord);
232
+
233
+ this.emit('execution:retry:success', {
234
+ ...executionRecord,
235
+ attempt
236
+ });
237
+
238
+ return executionRecord.result;
239
+
240
+ } catch (error) {
241
+ // 继续重试
242
+ if (attempt === maxRetries) {
243
+ // 最后一次重试失败
244
+ this.emit('execution:retry:failed', {
245
+ executionId,
246
+ attempts: maxRetries,
247
+ error: error.message,
248
+ timestamp: new Date()
249
+ });
250
+
251
+ throw error;
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * 计算重试延迟
259
+ *
260
+ * @private
261
+ * @param {number} attempt - 尝试次数
262
+ * @returns {number} 延迟时间(毫秒)
263
+ */
264
+ _calculateRetryDelay(attempt) {
265
+ const baseDelay = this.config.retryDelay;
266
+
267
+ if (this.config.retryBackoff === 'exponential') {
268
+ return baseDelay * Math.pow(2, attempt - 1);
269
+ } else {
270
+ return baseDelay * attempt;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * 验证动作
276
+ *
277
+ * @private
278
+ * @param {Object} action - 动作配置
279
+ */
280
+ _validateAction(action) {
281
+ if (!action.command || typeof action.command !== 'string') {
282
+ throw new Error('Action command must be a non-empty string');
283
+ }
284
+
285
+ if (action.command.trim().length === 0) {
286
+ throw new Error('Action command cannot be empty');
287
+ }
288
+ }
289
+
290
+ /**
291
+ * 插值命令
292
+ *
293
+ * @private
294
+ * @param {string} command - 命令模板
295
+ * @param {Object} context - 上下文数据
296
+ * @returns {string} 插值后的命令
297
+ */
298
+ _interpolateCommand(command, context) {
299
+ let result = command;
300
+
301
+ // 替换 ${variable} 格式的变量
302
+ const matches = command.match(/\$\{([^}]+)\}/g);
303
+
304
+ if (matches) {
305
+ matches.forEach(match => {
306
+ const key = match.slice(2, -1); // 移除 ${ 和 }
307
+ const value = this._getNestedValue(context, key);
308
+
309
+ if (value !== undefined) {
310
+ result = result.replace(match, value);
311
+ }
312
+ });
313
+ }
314
+
315
+ return result;
316
+ }
317
+
318
+ /**
319
+ * 获取嵌套值
320
+ *
321
+ * @private
322
+ * @param {Object} obj - 对象
323
+ * @param {string} path - 路径(如 'a.b.c')
324
+ * @returns {*} 值
325
+ */
326
+ _getNestedValue(obj, path) {
327
+ return path.split('.').reduce((current, key) => {
328
+ return current && current[key] !== undefined ? current[key] : undefined;
329
+ }, obj);
330
+ }
331
+
332
+ /**
333
+ * 验证命令安全性
334
+ *
335
+ * @private
336
+ * @param {string} command - 命令
337
+ */
338
+ _validateCommand(command) {
339
+ // 如果配置了允许的命令列表
340
+ if (this.config.allowedCommands && Array.isArray(this.config.allowedCommands)) {
341
+ const isAllowed = this.config.allowedCommands.some(pattern => {
342
+ if (typeof pattern === 'string') {
343
+ return command.startsWith(pattern);
344
+ } else if (pattern instanceof RegExp) {
345
+ return pattern.test(command);
346
+ }
347
+ return false;
348
+ });
349
+
350
+ if (!isAllowed) {
351
+ throw new Error(`Command not allowed: ${command}`);
352
+ }
353
+ }
354
+
355
+ // 检查危险命令
356
+ const dangerousPatterns = [
357
+ /rm\s+-rf\s+\//, // rm -rf /
358
+ /:\(\)\{.*\}:/, // Fork bomb
359
+ />\s*\/dev\/sd/ // Write to disk device
360
+ ];
361
+
362
+ for (const pattern of dangerousPatterns) {
363
+ if (pattern.test(command)) {
364
+ throw new Error(`Dangerous command detected: ${command}`);
365
+ }
366
+ }
367
+ }
368
+
369
+ /**
370
+ * 添加到历史记录
371
+ *
372
+ * @private
373
+ * @param {Object} record - 执行记录
374
+ */
375
+ _addToHistory(record) {
376
+ this.executionHistory.push(record);
377
+
378
+ // 限制历史记录大小
379
+ if (this.executionHistory.length > this.maxHistorySize) {
380
+ this.executionHistory.shift();
381
+ }
382
+ }
383
+
384
+ /**
385
+ * 获取执行历史
386
+ *
387
+ * @param {number} limit - 限制数量
388
+ * @returns {Array} 执行历史
389
+ */
390
+ getExecutionHistory(limit = null) {
391
+ if (limit) {
392
+ return this.executionHistory.slice(-limit);
393
+ }
394
+ return [...this.executionHistory];
395
+ }
396
+
397
+ /**
398
+ * 清除执行历史
399
+ */
400
+ clearHistory() {
401
+ this.executionHistory = [];
402
+ this.emit('history:cleared');
403
+ }
404
+
405
+ /**
406
+ * 获取统计信息
407
+ *
408
+ * @returns {Object} 统计信息
409
+ */
410
+ getStats() {
411
+ return {
412
+ ...this.stats,
413
+ successRate: this.stats.totalExecutions > 0
414
+ ? (this.stats.successfulExecutions / this.stats.totalExecutions * 100).toFixed(2) + '%'
415
+ : '0%'
416
+ };
417
+ }
418
+
419
+ /**
420
+ * 重置统计信息
421
+ */
422
+ resetStats() {
423
+ this.stats = {
424
+ totalExecutions: 0,
425
+ successfulExecutions: 0,
426
+ failedExecutions: 0,
427
+ retriedExecutions: 0,
428
+ timeoutExecutions: 0
429
+ };
430
+
431
+ this.emit('stats:reset');
432
+ }
433
+
434
+ /**
435
+ * 设置允许的命令
436
+ *
437
+ * @param {Array} commands - 允许的命令列表
438
+ */
439
+ setAllowedCommands(commands) {
440
+ if (!Array.isArray(commands)) {
441
+ throw new Error('Allowed commands must be an array');
442
+ }
443
+
444
+ this.config.allowedCommands = commands;
445
+ this.emit('config:updated', { allowedCommands: commands });
446
+ }
447
+
448
+ /**
449
+ * 获取配置
450
+ *
451
+ * @returns {Object} 配置
452
+ */
453
+ getConfig() {
454
+ return { ...this.config };
455
+ }
456
+ }
457
+
458
+ module.exports = ActionExecutor;