@claude-flow/cli 3.0.0-alpha.7 → 3.0.0-alpha.9
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/.agentic-flow/intelligence.json +4 -4
- package/.claude-flow/daemon-state.json +42 -35
- package/.claude-flow/daemon-test.log +0 -0
- package/.claude-flow/daemon.log +0 -0
- package/.claude-flow/daemon2.log +0 -0
- package/.claude-flow/daemon3.log +0 -0
- package/.claude-flow/metrics/codebase-map.json +2 -2
- package/.claude-flow/metrics/consolidation.json +1 -1
- package/.claude-flow/metrics/performance.json +12 -84
- package/.claude-flow/metrics/security-audit.json +1 -1
- package/.claude-flow/metrics/task-metrics.json +3 -3
- package/.claude-flow/metrics/test-gaps.json +1 -1
- package/agents/architect.yaml +1 -1
- package/agents/coder.yaml +1 -1
- package/agents/reviewer.yaml +1 -1
- package/agents/security-architect.yaml +1 -1
- package/agents/tester.yaml +1 -1
- package/dist/src/commands/daemon.d.ts.map +1 -1
- package/dist/src/commands/daemon.js +182 -8
- package/dist/src/commands/daemon.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/output.d.ts +16 -0
- package/dist/src/output.d.ts.map +1 -1
- package/dist/src/output.js +42 -0
- package/dist/src/output.js.map +1 -1
- package/dist/src/services/worker-daemon.d.ts +29 -2
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +123 -20
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/commands/daemon.ts +211 -8
- package/src/index.ts +25 -1
- package/src/output.ts +47 -1
- package/src/services/worker-daemon.ts +153 -21
|
@@ -70,20 +70,33 @@ interface DaemonConfig {
|
|
|
70
70
|
logDir: string;
|
|
71
71
|
stateFile: string;
|
|
72
72
|
maxConcurrent: number;
|
|
73
|
+
workerTimeoutMs: number;
|
|
74
|
+
resourceThresholds: {
|
|
75
|
+
maxCpuLoad: number;
|
|
76
|
+
minFreeMemoryPercent: number;
|
|
77
|
+
};
|
|
73
78
|
workers: WorkerConfig[];
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{ type: '
|
|
84
|
-
{ type: '
|
|
81
|
+
// Worker configuration with staggered offsets to prevent overlap
|
|
82
|
+
interface WorkerConfigInternal extends WorkerConfig {
|
|
83
|
+
offsetMs: number; // Stagger start time
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Default worker configurations with improved intervals (P0 fix: map 5min -> 15min)
|
|
87
|
+
const DEFAULT_WORKERS: WorkerConfigInternal[] = [
|
|
88
|
+
{ type: 'map', intervalMs: 15 * 60 * 1000, offsetMs: 0, priority: 'normal', description: 'Codebase mapping', enabled: true },
|
|
89
|
+
{ type: 'audit', intervalMs: 10 * 60 * 1000, offsetMs: 2 * 60 * 1000, priority: 'critical', description: 'Security analysis', enabled: true },
|
|
90
|
+
{ type: 'optimize', intervalMs: 15 * 60 * 1000, offsetMs: 4 * 60 * 1000, priority: 'high', description: 'Performance optimization', enabled: true },
|
|
91
|
+
{ type: 'consolidate', intervalMs: 30 * 60 * 1000, offsetMs: 6 * 60 * 1000, priority: 'low', description: 'Memory consolidation', enabled: true },
|
|
92
|
+
{ type: 'testgaps', intervalMs: 20 * 60 * 1000, offsetMs: 8 * 60 * 1000, priority: 'normal', description: 'Test coverage analysis', enabled: true },
|
|
93
|
+
{ type: 'predict', intervalMs: 10 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Predictive preloading', enabled: false },
|
|
94
|
+
{ type: 'document', intervalMs: 60 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Auto-documentation', enabled: false },
|
|
85
95
|
];
|
|
86
96
|
|
|
97
|
+
// Worker timeout (5 minutes max per worker)
|
|
98
|
+
const DEFAULT_WORKER_TIMEOUT_MS = 5 * 60 * 1000;
|
|
99
|
+
|
|
87
100
|
/**
|
|
88
101
|
* Worker Daemon - Manages background workers with Node.js
|
|
89
102
|
*/
|
|
@@ -94,6 +107,8 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
94
107
|
private running = false;
|
|
95
108
|
private startedAt?: Date;
|
|
96
109
|
private projectRoot: string;
|
|
110
|
+
private runningWorkers: Set<WorkerType> = new Set(); // Track concurrent workers
|
|
111
|
+
private pendingWorkers: WorkerType[] = []; // Queue for deferred workers
|
|
97
112
|
|
|
98
113
|
constructor(projectRoot: string, config?: Partial<DaemonConfig>) {
|
|
99
114
|
super();
|
|
@@ -102,13 +117,21 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
102
117
|
const claudeFlowDir = join(projectRoot, '.claude-flow');
|
|
103
118
|
|
|
104
119
|
this.config = {
|
|
105
|
-
autoStart: config?.autoStart ??
|
|
120
|
+
autoStart: config?.autoStart ?? false, // P1 fix: Default to false for explicit consent
|
|
106
121
|
logDir: config?.logDir ?? join(claudeFlowDir, 'logs'),
|
|
107
122
|
stateFile: config?.stateFile ?? join(claudeFlowDir, 'daemon-state.json'),
|
|
108
|
-
maxConcurrent: config?.maxConcurrent ??
|
|
123
|
+
maxConcurrent: config?.maxConcurrent ?? 2, // P0 fix: Limit concurrent workers
|
|
124
|
+
workerTimeoutMs: config?.workerTimeoutMs ?? DEFAULT_WORKER_TIMEOUT_MS,
|
|
125
|
+
resourceThresholds: config?.resourceThresholds ?? {
|
|
126
|
+
maxCpuLoad: 2.0,
|
|
127
|
+
minFreeMemoryPercent: 20,
|
|
128
|
+
},
|
|
109
129
|
workers: config?.workers ?? DEFAULT_WORKERS,
|
|
110
130
|
};
|
|
111
131
|
|
|
132
|
+
// Setup graceful shutdown handlers
|
|
133
|
+
this.setupShutdownHandlers();
|
|
134
|
+
|
|
112
135
|
// Ensure directories exist
|
|
113
136
|
if (!existsSync(claudeFlowDir)) {
|
|
114
137
|
mkdirSync(claudeFlowDir, { recursive: true });
|
|
@@ -121,6 +144,53 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
121
144
|
this.initializeWorkerStates();
|
|
122
145
|
}
|
|
123
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Setup graceful shutdown handlers
|
|
149
|
+
*/
|
|
150
|
+
private setupShutdownHandlers(): void {
|
|
151
|
+
const shutdown = async () => {
|
|
152
|
+
this.log('info', 'Received shutdown signal, stopping daemon...');
|
|
153
|
+
await this.stop();
|
|
154
|
+
process.exit(0);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
process.on('SIGTERM', shutdown);
|
|
158
|
+
process.on('SIGINT', shutdown);
|
|
159
|
+
process.on('SIGHUP', shutdown);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Check if system resources allow worker execution
|
|
164
|
+
*/
|
|
165
|
+
private async canRunWorker(): Promise<{ allowed: boolean; reason?: string }> {
|
|
166
|
+
const os = await import('os');
|
|
167
|
+
const cpuLoad = os.loadavg()[0];
|
|
168
|
+
const totalMem = os.totalmem();
|
|
169
|
+
const freeMem = os.freemem();
|
|
170
|
+
const freePercent = (freeMem / totalMem) * 100;
|
|
171
|
+
|
|
172
|
+
if (cpuLoad > this.config.resourceThresholds.maxCpuLoad) {
|
|
173
|
+
return { allowed: false, reason: `CPU load too high: ${cpuLoad.toFixed(2)}` };
|
|
174
|
+
}
|
|
175
|
+
if (freePercent < this.config.resourceThresholds.minFreeMemoryPercent) {
|
|
176
|
+
return { allowed: false, reason: `Memory too low: ${freePercent.toFixed(1)}% free` };
|
|
177
|
+
}
|
|
178
|
+
return { allowed: true };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Process pending workers queue
|
|
183
|
+
*/
|
|
184
|
+
private async processPendingWorkers(): Promise<void> {
|
|
185
|
+
while (this.pendingWorkers.length > 0 && this.runningWorkers.size < this.config.maxConcurrent) {
|
|
186
|
+
const workerType = this.pendingWorkers.shift()!;
|
|
187
|
+
const workerConfig = this.config.workers.find(w => w.type === workerType);
|
|
188
|
+
if (workerConfig) {
|
|
189
|
+
await this.executeWorkerWithConcurrencyControl(workerConfig);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
124
194
|
private initializeWorkerStates(): void {
|
|
125
195
|
// Try to restore state from file
|
|
126
196
|
if (existsSync(this.config.stateFile)) {
|
|
@@ -222,16 +292,18 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
222
292
|
}
|
|
223
293
|
|
|
224
294
|
/**
|
|
225
|
-
* Schedule a worker to run at intervals
|
|
295
|
+
* Schedule a worker to run at intervals with staggered start
|
|
226
296
|
*/
|
|
227
297
|
private scheduleWorker(workerConfig: WorkerConfig): void {
|
|
228
298
|
const state = this.workers.get(workerConfig.type)!;
|
|
299
|
+
const internalConfig = workerConfig as WorkerConfigInternal;
|
|
300
|
+
const staggerOffset = internalConfig.offsetMs || 0;
|
|
229
301
|
|
|
230
|
-
// Calculate initial delay
|
|
231
|
-
let initialDelay =
|
|
302
|
+
// Calculate initial delay with stagger offset
|
|
303
|
+
let initialDelay = staggerOffset;
|
|
232
304
|
if (state.lastRun) {
|
|
233
305
|
const timeSinceLastRun = Date.now() - state.lastRun.getTime();
|
|
234
|
-
initialDelay = Math.max(
|
|
306
|
+
initialDelay = Math.max(staggerOffset, workerConfig.intervalMs - timeSinceLastRun);
|
|
235
307
|
}
|
|
236
308
|
|
|
237
309
|
state.nextRun = new Date(Date.now() + initialDelay);
|
|
@@ -239,7 +311,8 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
239
311
|
const runAndReschedule = async () => {
|
|
240
312
|
if (!this.running) return;
|
|
241
313
|
|
|
242
|
-
|
|
314
|
+
// Use concurrency-controlled execution (P0 fix)
|
|
315
|
+
await this.executeWorkerWithConcurrencyControl(workerConfig);
|
|
243
316
|
|
|
244
317
|
// Reschedule
|
|
245
318
|
if (this.running) {
|
|
@@ -249,7 +322,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
249
322
|
}
|
|
250
323
|
};
|
|
251
324
|
|
|
252
|
-
// Schedule first run
|
|
325
|
+
// Schedule first run with stagger offset
|
|
253
326
|
const timer = setTimeout(runAndReschedule, initialDelay);
|
|
254
327
|
this.timers.set(workerConfig.type, timer);
|
|
255
328
|
|
|
@@ -257,20 +330,50 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
257
330
|
}
|
|
258
331
|
|
|
259
332
|
/**
|
|
260
|
-
* Execute a worker
|
|
333
|
+
* Execute a worker with concurrency control (P0 fix)
|
|
334
|
+
*/
|
|
335
|
+
private async executeWorkerWithConcurrencyControl(workerConfig: WorkerConfig): Promise<WorkerResult | null> {
|
|
336
|
+
// Check concurrency limit
|
|
337
|
+
if (this.runningWorkers.size >= this.config.maxConcurrent) {
|
|
338
|
+
this.log('info', `Worker ${workerConfig.type} deferred: max concurrent (${this.config.maxConcurrent}) reached`);
|
|
339
|
+
this.pendingWorkers.push(workerConfig.type);
|
|
340
|
+
this.emit('worker:deferred', { type: workerConfig.type, reason: 'max_concurrent' });
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Check resource availability
|
|
345
|
+
const resourceCheck = await this.canRunWorker();
|
|
346
|
+
if (!resourceCheck.allowed) {
|
|
347
|
+
this.log('info', `Worker ${workerConfig.type} deferred: ${resourceCheck.reason}`);
|
|
348
|
+
this.pendingWorkers.push(workerConfig.type);
|
|
349
|
+
this.emit('worker:deferred', { type: workerConfig.type, reason: resourceCheck.reason });
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return this.executeWorker(workerConfig);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Execute a worker with timeout protection
|
|
261
358
|
*/
|
|
262
359
|
private async executeWorker(workerConfig: WorkerConfig): Promise<WorkerResult> {
|
|
263
360
|
const state = this.workers.get(workerConfig.type)!;
|
|
264
361
|
const workerId = `${workerConfig.type}_${Date.now()}`;
|
|
265
362
|
const startTime = Date.now();
|
|
266
363
|
|
|
364
|
+
// Track running worker
|
|
365
|
+
this.runningWorkers.add(workerConfig.type);
|
|
267
366
|
state.isRunning = true;
|
|
268
367
|
this.emit('worker:start', { workerId, type: workerConfig.type });
|
|
269
|
-
this.log('info', `Starting worker: ${workerConfig.type}`);
|
|
368
|
+
this.log('info', `Starting worker: ${workerConfig.type} (${this.runningWorkers.size}/${this.config.maxConcurrent} concurrent)`);
|
|
270
369
|
|
|
271
370
|
try {
|
|
272
|
-
// Execute worker logic
|
|
273
|
-
const output = await this.
|
|
371
|
+
// Execute worker logic with timeout (P1 fix)
|
|
372
|
+
const output = await this.runWithTimeout(
|
|
373
|
+
() => this.runWorkerLogic(workerConfig),
|
|
374
|
+
this.config.workerTimeoutMs,
|
|
375
|
+
`Worker ${workerConfig.type} timed out after ${this.config.workerTimeoutMs / 1000}s`
|
|
376
|
+
);
|
|
274
377
|
const durationMs = Date.now() - startTime;
|
|
275
378
|
|
|
276
379
|
// Update state
|
|
@@ -316,9 +419,38 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
316
419
|
this.saveState();
|
|
317
420
|
|
|
318
421
|
return result;
|
|
422
|
+
} finally {
|
|
423
|
+
// Remove from running set and process queue
|
|
424
|
+
this.runningWorkers.delete(workerConfig.type);
|
|
425
|
+
this.processPendingWorkers();
|
|
319
426
|
}
|
|
320
427
|
}
|
|
321
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Run a function with timeout (P1 fix)
|
|
431
|
+
*/
|
|
432
|
+
private async runWithTimeout<T>(
|
|
433
|
+
fn: () => Promise<T>,
|
|
434
|
+
timeoutMs: number,
|
|
435
|
+
timeoutMessage: string
|
|
436
|
+
): Promise<T> {
|
|
437
|
+
return new Promise<T>((resolve, reject) => {
|
|
438
|
+
const timer = setTimeout(() => {
|
|
439
|
+
reject(new Error(timeoutMessage));
|
|
440
|
+
}, timeoutMs);
|
|
441
|
+
|
|
442
|
+
fn()
|
|
443
|
+
.then((result) => {
|
|
444
|
+
clearTimeout(timer);
|
|
445
|
+
resolve(result);
|
|
446
|
+
})
|
|
447
|
+
.catch((error) => {
|
|
448
|
+
clearTimeout(timer);
|
|
449
|
+
reject(error);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
322
454
|
/**
|
|
323
455
|
* Run the actual worker logic
|
|
324
456
|
*/
|