@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.
Files changed (38) hide show
  1. package/.agentic-flow/intelligence.json +4 -4
  2. package/.claude-flow/daemon-state.json +42 -35
  3. package/.claude-flow/daemon-test.log +0 -0
  4. package/.claude-flow/daemon.log +0 -0
  5. package/.claude-flow/daemon2.log +0 -0
  6. package/.claude-flow/daemon3.log +0 -0
  7. package/.claude-flow/metrics/codebase-map.json +2 -2
  8. package/.claude-flow/metrics/consolidation.json +1 -1
  9. package/.claude-flow/metrics/performance.json +12 -84
  10. package/.claude-flow/metrics/security-audit.json +1 -1
  11. package/.claude-flow/metrics/task-metrics.json +3 -3
  12. package/.claude-flow/metrics/test-gaps.json +1 -1
  13. package/agents/architect.yaml +1 -1
  14. package/agents/coder.yaml +1 -1
  15. package/agents/reviewer.yaml +1 -1
  16. package/agents/security-architect.yaml +1 -1
  17. package/agents/tester.yaml +1 -1
  18. package/dist/src/commands/daemon.d.ts.map +1 -1
  19. package/dist/src/commands/daemon.js +182 -8
  20. package/dist/src/commands/daemon.js.map +1 -1
  21. package/dist/src/index.d.ts +1 -1
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/dist/src/index.js +21 -0
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/output.d.ts +16 -0
  26. package/dist/src/output.d.ts.map +1 -1
  27. package/dist/src/output.js +42 -0
  28. package/dist/src/output.js.map +1 -1
  29. package/dist/src/services/worker-daemon.d.ts +29 -2
  30. package/dist/src/services/worker-daemon.d.ts.map +1 -1
  31. package/dist/src/services/worker-daemon.js +123 -20
  32. package/dist/src/services/worker-daemon.js.map +1 -1
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +1 -1
  35. package/src/commands/daemon.ts +211 -8
  36. package/src/index.ts +25 -1
  37. package/src/output.ts +47 -1
  38. 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
- // Default worker configurations with intervals
77
- const DEFAULT_WORKERS: WorkerConfig[] = [
78
- { type: 'map', intervalMs: 5 * 60 * 1000, priority: 'normal', description: 'Codebase mapping', enabled: true },
79
- { type: 'audit', intervalMs: 10 * 60 * 1000, priority: 'critical', description: 'Security analysis', enabled: true },
80
- { type: 'optimize', intervalMs: 15 * 60 * 1000, priority: 'high', description: 'Performance optimization', enabled: true },
81
- { type: 'consolidate', intervalMs: 30 * 60 * 1000, priority: 'low', description: 'Memory consolidation', enabled: true },
82
- { type: 'testgaps', intervalMs: 20 * 60 * 1000, priority: 'normal', description: 'Test coverage analysis', enabled: true },
83
- { type: 'predict', intervalMs: 2 * 60 * 1000, priority: 'low', description: 'Predictive preloading', enabled: false },
84
- { type: 'document', intervalMs: 60 * 60 * 1000, priority: 'low', description: 'Auto-documentation', enabled: false },
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 ?? true,
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 ?? 3,
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 (run immediately if never run, otherwise respect interval)
231
- let initialDelay = 0;
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(0, workerConfig.intervalMs - timeSinceLastRun);
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
- await this.executeWorker(workerConfig);
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.runWorkerLogic(workerConfig);
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
  */