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.
- package/CHANGELOG.md +91 -0
- package/README.md +172 -0
- package/bin/kiro-spec-engine.js +62 -0
- package/docs/adoption-guide.md +506 -0
- package/docs/agent-hooks-analysis.md +815 -0
- package/docs/architecture.md +706 -0
- package/docs/cross-tool-guide.md +554 -0
- package/docs/developer-guide.md +615 -0
- package/docs/manual-workflows-guide.md +417 -0
- package/docs/steering-strategy-guide.md +196 -0
- package/docs/upgrade-guide.md +632 -0
- package/lib/adoption/detection-engine.js +14 -4
- package/lib/commands/adopt.js +117 -3
- package/lib/commands/context.js +99 -0
- package/lib/commands/prompt.js +105 -0
- package/lib/commands/status.js +225 -0
- package/lib/commands/task.js +199 -0
- package/lib/commands/watch.js +569 -0
- package/lib/commands/workflows.js +240 -0
- package/lib/commands/workspace.js +189 -0
- package/lib/context/context-exporter.js +378 -0
- package/lib/context/prompt-generator.js +482 -0
- package/lib/steering/adoption-config.js +164 -0
- package/lib/steering/steering-manager.js +289 -0
- package/lib/task/task-claimer.js +430 -0
- package/lib/utils/tool-detector.js +383 -0
- package/lib/watch/action-executor.js +458 -0
- package/lib/watch/event-debouncer.js +323 -0
- package/lib/watch/execution-logger.js +550 -0
- package/lib/watch/file-watcher.js +499 -0
- package/lib/watch/presets.js +266 -0
- package/lib/watch/watch-manager.js +533 -0
- package/lib/workspace/workspace-manager.js +370 -0
- package/lib/workspace/workspace-sync.js +356 -0
- package/package.json +4 -1
- package/template/.kiro/tools/backup_manager.py +295 -0
- package/template/.kiro/tools/configuration_manager.py +218 -0
- package/template/.kiro/tools/document_evaluator.py +550 -0
- package/template/.kiro/tools/enhancement_logger.py +168 -0
- package/template/.kiro/tools/error_handler.py +335 -0
- package/template/.kiro/tools/improvement_identifier.py +444 -0
- package/template/.kiro/tools/modification_applicator.py +737 -0
- package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
- package/template/.kiro/tools/quality_scorer.py +305 -0
- package/template/.kiro/tools/report_generator.py +154 -0
- package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
- package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
- package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
- 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;
|