@claude-flow/cli 3.71.4 → 3.72.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/.claude/helpers/helpers.manifest.json +4 -4
- package/.claude/helpers/hook-handler.cjs +23 -10
- package/.claude/helpers/statusline.cjs +29 -14
- package/README.md +18 -3
- package/bin/cli.js +13 -18
- package/bin/mcp-server.js +13 -18
- package/catalog-manifest.json +2 -2
- package/dist/src/appliance/rvfa-distribution.js +2 -2
- package/dist/src/benchmarks/gaia-critic.d.ts +1 -7
- package/dist/src/benchmarks/gaia-critic.js +1 -7
- package/dist/src/benchmarks/gaia-decomposer.d.ts +1 -8
- package/dist/src/benchmarks/gaia-decomposer.js +1 -8
- package/dist/src/commands/agent-wasm.js +2 -2
- package/dist/src/commands/analyze.js +1 -1
- package/dist/src/commands/daemon.js +1 -1
- package/dist/src/commands/doctor.js +3 -2
- package/dist/src/commands/guidance.js +9 -9
- package/dist/src/commands/hooks.js +13 -8
- package/dist/src/commands/init.js +14 -16
- package/dist/src/commands/neural.js +8 -6
- package/dist/src/commands/route.js +1 -1
- package/dist/src/commands/ruvector/backup.js +7 -5
- package/dist/src/commands/ruvector/benchmark.js +6 -1
- package/dist/src/commands/ruvector/import.js +19 -5
- package/dist/src/commands/ruvector/init.js +1 -1
- package/dist/src/commands/start.js +1 -1
- package/dist/src/commands/status.js +1 -1
- package/dist/src/commands/task.js +18 -1
- package/dist/src/commands/update.js +1 -1
- package/dist/src/funnel/local-signals.js +7 -2
- package/dist/src/mcp-protocol.d.ts +90 -0
- package/dist/src/mcp-protocol.js +179 -0
- package/dist/src/mcp-server.d.ts +1 -0
- package/dist/src/mcp-server.js +20 -22
- package/dist/src/mcp-tools/agent-execute-core.js +9 -1
- package/dist/src/mcp-tools/agent-tools.d.ts +13 -0
- package/dist/src/mcp-tools/agent-tools.js +59 -8
- package/dist/src/mcp-tools/browser-argv.d.ts +9 -0
- package/dist/src/mcp-tools/browser-argv.js +33 -0
- package/dist/src/mcp-tools/browser-session-tools.js +4 -0
- package/dist/src/mcp-tools/browser-tools.js +12 -2
- package/dist/src/mcp-tools/coordination-tools.js +5 -4
- package/dist/src/mcp-tools/embeddings-tools.js +12 -4
- package/dist/src/mcp-tools/glob-exclusions.d.ts +6 -0
- package/dist/src/mcp-tools/glob-exclusions.js +81 -0
- package/dist/src/mcp-tools/hive-mind-tools.js +104 -35
- package/dist/src/mcp-tools/hooks-tools.js +12 -5
- package/dist/src/mcp-tools/import-limits.d.ts +4 -0
- package/dist/src/mcp-tools/import-limits.js +28 -0
- package/dist/src/mcp-tools/memory-tools.js +21 -42
- package/dist/src/mcp-tools/performance-tools.js +7 -2
- package/dist/src/mcp-tools/record-store.d.ts +8 -0
- package/dist/src/mcp-tools/record-store.js +98 -1
- package/dist/src/mcp-tools/security-tools.d.ts +9 -0
- package/dist/src/mcp-tools/security-tools.js +39 -3
- package/dist/src/mcp-tools/session-tools.js +5 -3
- package/dist/src/mcp-tools/swarm-tools.js +35 -17
- package/dist/src/mcp-tools/task-tools.js +68 -5
- package/dist/src/mcp-tools/terminal-tools.js +9 -0
- package/dist/src/mcp-tools/workflow-tools.js +13 -12
- package/dist/src/mcp-tools/x-federation-join.js +55 -14
- package/dist/src/memory/intelligence.d.ts +11 -0
- package/dist/src/memory/intelligence.js +84 -6
- package/dist/src/memory/memory-initializer.d.ts +6 -2
- package/dist/src/memory/memory-initializer.js +15 -6
- package/dist/src/ruvector/model-router.d.ts +6 -1
- package/dist/src/ruvector/model-router.js +16 -4
- package/dist/src/services/worker-daemon.js +30 -12
- package/dist/src/transfer/storage/gcs.js +3 -2
- package/package.json +1 -1
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -0
|
@@ -337,6 +337,12 @@ class LocalReasoningBank {
|
|
|
337
337
|
persistenceEnabled;
|
|
338
338
|
dirty = false;
|
|
339
339
|
saveTimeout = null;
|
|
340
|
+
/** IDs changed locally since the last successful flush. */
|
|
341
|
+
dirtyIds = new Set();
|
|
342
|
+
/** IDs deleted locally; a disk copy must not be resurrected. */
|
|
343
|
+
deletedIds = new Set();
|
|
344
|
+
/** A local clear() must remove disk entries too. */
|
|
345
|
+
clearRequested = false;
|
|
340
346
|
constructor(options) {
|
|
341
347
|
this.maxSize = options.maxSize;
|
|
342
348
|
this.persistenceEnabled = options.persistence !== false;
|
|
@@ -359,6 +365,7 @@ class LocalReasoningBank {
|
|
|
359
365
|
const totalLoaded = data.length;
|
|
360
366
|
// Group by content to deduplicate
|
|
361
367
|
const byContent = new Map();
|
|
368
|
+
const removedIds = [];
|
|
362
369
|
for (const pattern of data) {
|
|
363
370
|
const key = pattern.content;
|
|
364
371
|
const existing = byContent.get(key);
|
|
@@ -372,10 +379,12 @@ class LocalReasoningBank {
|
|
|
372
379
|
(pattern.lastUsedAt ?? 0) > (existing.lastUsedAt ?? 0))) {
|
|
373
380
|
// Merge: adopt the higher usageCount sum
|
|
374
381
|
pattern.usageCount = (pattern.usageCount ?? 0) + (existing.usageCount ?? 0);
|
|
382
|
+
removedIds.push(existing.id);
|
|
375
383
|
byContent.set(key, pattern);
|
|
376
384
|
}
|
|
377
385
|
else {
|
|
378
386
|
existing.usageCount = (existing.usageCount ?? 0) + (pattern.usageCount ?? 0);
|
|
387
|
+
removedIds.push(pattern.id);
|
|
379
388
|
}
|
|
380
389
|
}
|
|
381
390
|
}
|
|
@@ -387,15 +396,28 @@ class LocalReasoningBank {
|
|
|
387
396
|
const removed = totalLoaded - byContent.size;
|
|
388
397
|
if (removed > 0) {
|
|
389
398
|
console.log(`Deduplicated ${removed} patterns (${byContent.size} unique)`);
|
|
390
|
-
// Persist the compacted set immediately so the file shrinks on disk
|
|
399
|
+
// Persist the compacted set immediately so the file shrinks on disk.
|
|
400
|
+
// Mark every kept row dirty so this compaction wins over the disk
|
|
401
|
+
// copies, and tombstone the discarded duplicates.
|
|
402
|
+
for (const pattern of byContent.values())
|
|
403
|
+
this.dirtyIds.add(pattern.id);
|
|
404
|
+
for (const id of removedIds)
|
|
405
|
+
this.deletedIds.add(id);
|
|
391
406
|
this.dirty = true;
|
|
392
407
|
this.flushToDisk();
|
|
393
408
|
}
|
|
394
409
|
}
|
|
395
410
|
}
|
|
396
411
|
}
|
|
397
|
-
catch {
|
|
398
|
-
//
|
|
412
|
+
catch (error) {
|
|
413
|
+
// A corrupt patterns file must be preserved, not silently replaced by
|
|
414
|
+
// an empty bank on the next flush (#4735).
|
|
415
|
+
try {
|
|
416
|
+
const corruptPath = `${getPatternsPath()}.corrupt-${Date.now()}`;
|
|
417
|
+
renameSync(getPatternsPath(), corruptPath);
|
|
418
|
+
console.warn(`[intelligence] Corrupt patterns file moved to ${corruptPath}`);
|
|
419
|
+
}
|
|
420
|
+
catch { /* already absent or rename failed — never overwrite it below */ }
|
|
399
421
|
}
|
|
400
422
|
}
|
|
401
423
|
/**
|
|
@@ -415,6 +437,11 @@ class LocalReasoningBank {
|
|
|
415
437
|
}
|
|
416
438
|
/**
|
|
417
439
|
* Immediately flush patterns to disk
|
|
440
|
+
*
|
|
441
|
+
* Re-reads the file under the cooperating-writer lock and merges only the
|
|
442
|
+
* patterns this process changed, so a long-lived daemon cannot erase patterns
|
|
443
|
+
* written by another hook/process (#4735). Deletions and clear() are carried
|
|
444
|
+
* as tombstones; the write itself is temp-file + rename.
|
|
418
445
|
*/
|
|
419
446
|
flushToDisk() {
|
|
420
447
|
if (!this.persistenceEnabled || !this.dirty)
|
|
@@ -422,11 +449,52 @@ class LocalReasoningBank {
|
|
|
422
449
|
try {
|
|
423
450
|
ensureDataDir();
|
|
424
451
|
const path = getPatternsPath();
|
|
425
|
-
|
|
426
|
-
|
|
452
|
+
withStatsFileLock(path, (canonicalPath) => {
|
|
453
|
+
let diskPatterns = [];
|
|
454
|
+
if (existsSync(canonicalPath)) {
|
|
455
|
+
try {
|
|
456
|
+
const parsed = JSON.parse(readFileSync(canonicalPath, 'utf-8'));
|
|
457
|
+
if (!Array.isArray(parsed))
|
|
458
|
+
throw new Error('patterns.json is not an array');
|
|
459
|
+
diskPatterns = parsed;
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// Preserve the corrupt original instead of overwriting it with an
|
|
463
|
+
// empty bank; the new file is written only after this rename.
|
|
464
|
+
try {
|
|
465
|
+
const corruptPath = `${canonicalPath}.corrupt-${Date.now()}`;
|
|
466
|
+
renameSync(canonicalPath, corruptPath);
|
|
467
|
+
console.warn(`[intelligence] Corrupt patterns file moved to ${corruptPath}`);
|
|
468
|
+
}
|
|
469
|
+
catch { /* leave the original in place */ }
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const merged = new Map();
|
|
473
|
+
if (!this.clearRequested) {
|
|
474
|
+
for (const pattern of diskPatterns) {
|
|
475
|
+
if (!this.deletedIds.has(pattern.id))
|
|
476
|
+
merged.set(pattern.id, pattern);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
for (const pattern of this.patternList) {
|
|
480
|
+
if (this.clearRequested || this.dirtyIds.has(pattern.id)) {
|
|
481
|
+
merged.set(pattern.id, pattern);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const list = [...merged.values()];
|
|
485
|
+
const tmp = `${canonicalPath}.${process.pid}.${Date.now()}.tmp`;
|
|
486
|
+
writeFileSync(tmp, JSON.stringify(list, null, 2), 'utf-8');
|
|
487
|
+
renameSync(tmp, canonicalPath);
|
|
488
|
+
this.patterns = new Map(list.map((pattern) => [pattern.id, pattern]));
|
|
489
|
+
this.patternList = list;
|
|
490
|
+
this.dirty = false;
|
|
491
|
+
this.dirtyIds.clear();
|
|
492
|
+
this.deletedIds.clear();
|
|
493
|
+
this.clearRequested = false;
|
|
494
|
+
});
|
|
427
495
|
}
|
|
428
496
|
catch (error) {
|
|
429
|
-
// Log but don't throw - persistence failures shouldn't break training
|
|
497
|
+
// Log but don't throw - persistence failures shouldn't break training.
|
|
430
498
|
console.error('Failed to persist patterns:', error);
|
|
431
499
|
}
|
|
432
500
|
}
|
|
@@ -459,6 +527,7 @@ class LocalReasoningBank {
|
|
|
459
527
|
this.patternList[idx] = stored;
|
|
460
528
|
}
|
|
461
529
|
this.patterns.set(pattern.id, stored);
|
|
530
|
+
this.dirtyIds.add(pattern.id);
|
|
462
531
|
this.saveToDisk();
|
|
463
532
|
return false;
|
|
464
533
|
}
|
|
@@ -474,6 +543,7 @@ class LocalReasoningBank {
|
|
|
474
543
|
}
|
|
475
544
|
// Keep the Map in sync with the mutated object
|
|
476
545
|
this.patterns.set(contentDupe.id, contentDupe);
|
|
546
|
+
this.dirtyIds.add(contentDupe.id);
|
|
477
547
|
this.saveToDisk();
|
|
478
548
|
return false;
|
|
479
549
|
}
|
|
@@ -482,11 +552,14 @@ class LocalReasoningBank {
|
|
|
482
552
|
const oldest = this.patternList.shift();
|
|
483
553
|
if (oldest) {
|
|
484
554
|
this.patterns.delete(oldest.id);
|
|
555
|
+
this.deletedIds.add(oldest.id);
|
|
556
|
+
this.dirtyIds.delete(oldest.id);
|
|
485
557
|
}
|
|
486
558
|
}
|
|
487
559
|
this.patternList.push(stored);
|
|
488
560
|
}
|
|
489
561
|
this.patterns.set(pattern.id, stored);
|
|
562
|
+
this.dirtyIds.add(stored.id);
|
|
490
563
|
// Trigger persistence (debounced)
|
|
491
564
|
this.saveToDisk();
|
|
492
565
|
return true;
|
|
@@ -583,6 +656,8 @@ class LocalReasoningBank {
|
|
|
583
656
|
if (idx >= 0) {
|
|
584
657
|
this.patternList.splice(idx, 1);
|
|
585
658
|
}
|
|
659
|
+
this.dirtyIds.delete(id);
|
|
660
|
+
this.deletedIds.add(id);
|
|
586
661
|
this.saveToDisk();
|
|
587
662
|
return true;
|
|
588
663
|
}
|
|
@@ -592,6 +667,9 @@ class LocalReasoningBank {
|
|
|
592
667
|
clear() {
|
|
593
668
|
this.patterns.clear();
|
|
594
669
|
this.patternList = [];
|
|
670
|
+
this.dirtyIds.clear();
|
|
671
|
+
this.deletedIds.clear();
|
|
672
|
+
this.clearRequested = true;
|
|
595
673
|
this.saveToDisk();
|
|
596
674
|
}
|
|
597
675
|
}
|
|
@@ -361,7 +361,9 @@ export declare function loadEmbeddingModel(options?: {
|
|
|
361
361
|
* own embedder is real or stubbed). Set `backend` truthfully by the path that
|
|
362
362
|
* actually produced the vector. Do NOT change the embedding math.
|
|
363
363
|
*/
|
|
364
|
-
export declare function generateEmbedding(text: string
|
|
364
|
+
export declare function generateEmbedding(text: string, options?: {
|
|
365
|
+
normalize?: boolean;
|
|
366
|
+
}): Promise<{
|
|
365
367
|
embedding: number[];
|
|
366
368
|
dimensions: number;
|
|
367
369
|
model: string;
|
|
@@ -382,7 +384,9 @@ export declare function generateEmbedding(text: string): Promise<{
|
|
|
382
384
|
*
|
|
383
385
|
* Keeping the local chain as its own export breaks that cycle structurally.
|
|
384
386
|
*/
|
|
385
|
-
export declare function generateLocalEmbedding(text: string
|
|
387
|
+
export declare function generateLocalEmbedding(text: string, options?: {
|
|
388
|
+
normalize?: boolean;
|
|
389
|
+
}): Promise<{
|
|
386
390
|
embedding: number[];
|
|
387
391
|
dimensions: number;
|
|
388
392
|
model: string;
|
|
@@ -2253,7 +2253,12 @@ async function loadLocalEmbeddingChain(verbose = false, startTime = Date.now())
|
|
|
2253
2253
|
* own embedder is real or stubbed). Set `backend` truthfully by the path that
|
|
2254
2254
|
* actually produced the vector. Do NOT change the embedding math.
|
|
2255
2255
|
*/
|
|
2256
|
-
export async function generateEmbedding(text) {
|
|
2256
|
+
export async function generateEmbedding(text, options) {
|
|
2257
|
+
// #4724: AgentDB embed() has no normalize switch and always returns a unit
|
|
2258
|
+
// vector. A raw request has to use the local chain, which can skip L2.
|
|
2259
|
+
if (options?.normalize === false) {
|
|
2260
|
+
return generateLocalEmbedding(text, { normalize: false });
|
|
2261
|
+
}
|
|
2257
2262
|
// ADR-053: Try AgentDB v3 bridge first
|
|
2258
2263
|
const bridge = await getBridge();
|
|
2259
2264
|
if (bridge) {
|
|
@@ -2283,7 +2288,9 @@ export async function generateEmbedding(text) {
|
|
|
2283
2288
|
*
|
|
2284
2289
|
* Keeping the local chain as its own export breaks that cycle structurally.
|
|
2285
2290
|
*/
|
|
2286
|
-
export async function generateLocalEmbedding(text) {
|
|
2291
|
+
export async function generateLocalEmbedding(text, options) {
|
|
2292
|
+
// Default true keeps the historical unit-norm embedding math.
|
|
2293
|
+
const normalize = options?.normalize !== false;
|
|
2287
2294
|
// Ensure the LOCAL model is loaded. #3375: this must not go through
|
|
2288
2295
|
// loadEmbeddingModel(), which is bridge-first — when the bridge answered
|
|
2289
2296
|
// there, no local model was ever loaded and this function always returned
|
|
@@ -2314,7 +2321,7 @@ export async function generateLocalEmbedding(text) {
|
|
|
2314
2321
|
// Use ONNX model if available
|
|
2315
2322
|
if (state.model && typeof state.model === 'function') {
|
|
2316
2323
|
try {
|
|
2317
|
-
const output = await state.model(text, { pooling: 'mean', normalize
|
|
2324
|
+
const output = await state.model(text, { pooling: 'mean', normalize });
|
|
2318
2325
|
// Handle @xenova/transformers (output.data), ruvector (plain array) and
|
|
2319
2326
|
// typed-array (ReasoningBank Float32Array, #3114) formats. DataView is an
|
|
2320
2327
|
// ArrayBuffer view without an iterator (Array.from gives []), so exclude it.
|
|
@@ -2338,7 +2345,7 @@ export async function generateLocalEmbedding(text) {
|
|
|
2338
2345
|
// Deterministic hash-based fallback (for testing/demo without ONNX).
|
|
2339
2346
|
// AUDIT #3: backend='mock' — these vectors do NOT carry real semantics.
|
|
2340
2347
|
(await import('./embedding-policy.js')).enforceNoStub('memory-initializer.generateLocalEmbedding'); // "no stubs" strict mode
|
|
2341
|
-
const embedding = generateHashEmbedding(text, state.dimensions);
|
|
2348
|
+
const embedding = generateHashEmbedding(text, state.dimensions, normalize);
|
|
2342
2349
|
return {
|
|
2343
2350
|
embedding,
|
|
2344
2351
|
dimensions: state.dimensions,
|
|
@@ -2401,7 +2408,7 @@ export async function generateBatchEmbeddings(texts, options) {
|
|
|
2401
2408
|
* Generate deterministic hash-based embedding
|
|
2402
2409
|
* Not semantic, but deterministic and useful for testing
|
|
2403
2410
|
*/
|
|
2404
|
-
function generateHashEmbedding(text, dimensions) {
|
|
2411
|
+
function generateHashEmbedding(text, dimensions, normalize = true) {
|
|
2405
2412
|
const embedding = new Array(dimensions).fill(0);
|
|
2406
2413
|
// Simple hash-based approach for reproducibility
|
|
2407
2414
|
const words = text.toLowerCase().split(/\s+/);
|
|
@@ -2413,7 +2420,9 @@ function generateHashEmbedding(text, dimensions) {
|
|
|
2413
2420
|
embedding[idx] += Math.sin(charCode * 0.1) * 0.1;
|
|
2414
2421
|
}
|
|
2415
2422
|
}
|
|
2416
|
-
// Normalize to unit vector
|
|
2423
|
+
// Normalize to unit vector unless the caller asked for the raw vector (#4724).
|
|
2424
|
+
if (!normalize)
|
|
2425
|
+
return embedding;
|
|
2417
2426
|
const magnitude = Math.sqrt(embedding.reduce((sum, v) => sum + v * v, 0)) || 1;
|
|
2418
2427
|
return embedding.map(v => v / magnitude);
|
|
2419
2428
|
}
|
|
@@ -90,6 +90,11 @@ export interface ModelRouterConfig {
|
|
|
90
90
|
*/
|
|
91
91
|
priorDecay: number;
|
|
92
92
|
}
|
|
93
|
+
/** Request-local preferences; omitted or false keeps the existing scoring. */
|
|
94
|
+
export interface ModelRoutingPreferences {
|
|
95
|
+
preferCost?: boolean;
|
|
96
|
+
preferSpeed?: boolean;
|
|
97
|
+
}
|
|
93
98
|
/**
|
|
94
99
|
* Routing decision result
|
|
95
100
|
*/
|
|
@@ -241,7 +246,7 @@ export declare class ModelRouter {
|
|
|
241
246
|
* 'bandit-fallback'` (neural was enabled but declined) or
|
|
242
247
|
* `'heuristic'` (neural was disabled).
|
|
243
248
|
*/
|
|
244
|
-
route(task: string, embedding?: number[]): Promise<ModelRoutingResult>;
|
|
249
|
+
route(task: string, embedding?: number[], preferences?: ModelRoutingPreferences): Promise<ModelRoutingResult>;
|
|
245
250
|
/**
|
|
246
251
|
* Analyze task complexity
|
|
247
252
|
*/
|
|
@@ -395,7 +395,7 @@ export class ModelRouter {
|
|
|
395
395
|
* 'bandit-fallback'` (neural was enabled but declined) or
|
|
396
396
|
* `'heuristic'` (neural was disabled).
|
|
397
397
|
*/
|
|
398
|
-
async route(task, embedding) {
|
|
398
|
+
async route(task, embedding, preferences = {}) {
|
|
399
399
|
const startTime = performance.now();
|
|
400
400
|
// Analyze task complexity
|
|
401
401
|
const complexity = this.analyzeComplexity(task, embedding);
|
|
@@ -473,7 +473,7 @@ export class ModelRouter {
|
|
|
473
473
|
// Silent — neural path is best-effort.
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
|
-
const scores = this.computeModelScores(complexity);
|
|
476
|
+
const scores = this.computeModelScores(complexity, preferences);
|
|
477
477
|
const adjustedScores = this.applyCircuitBreaker(scores);
|
|
478
478
|
// A/B mode: compute the pure-bandit pick alongside the hybrid pick so
|
|
479
479
|
// we can log disagreement. Both samples are drawn from the same Beta
|
|
@@ -717,17 +717,29 @@ export class ModelRouter {
|
|
|
717
717
|
/**
|
|
718
718
|
* Compute scores for each model
|
|
719
719
|
*/
|
|
720
|
-
computeModelScores(complexity) {
|
|
720
|
+
computeModelScores(complexity, preferences) {
|
|
721
721
|
const { score } = complexity;
|
|
722
722
|
// Base scoring: inverse relationship with complexity
|
|
723
723
|
// Low complexity → haiku scores high
|
|
724
724
|
// High complexity → opus scores high
|
|
725
|
-
|
|
725
|
+
const scores = {
|
|
726
726
|
haiku: Math.max(0, 1 - score * 2), // Drops off quickly as complexity rises
|
|
727
727
|
sonnet: 1 - Math.abs(score - 0.5) * 2, // Peaks at medium complexity
|
|
728
728
|
opus: Math.min(1, score * 1.5), // Rises with complexity
|
|
729
729
|
inherit: 0.1, // Low baseline unless explicitly needed
|
|
730
730
|
};
|
|
731
|
+
for (const model of ['haiku', 'sonnet', 'opus']) {
|
|
732
|
+
const capabilities = MODEL_CAPABILITIES[model];
|
|
733
|
+
// Preferences must not boost a tier beyond its task capability.
|
|
734
|
+
// Circuit-breaker penalties and uncertainty escalation still apply.
|
|
735
|
+
if (score > capabilities.maxComplexity)
|
|
736
|
+
continue;
|
|
737
|
+
if (preferences.preferCost)
|
|
738
|
+
scores[model] *= 2 - capabilities.costMultiplier;
|
|
739
|
+
if (preferences.preferSpeed)
|
|
740
|
+
scores[model] *= capabilities.speedMultiplier;
|
|
741
|
+
}
|
|
742
|
+
return scores;
|
|
731
743
|
}
|
|
732
744
|
/**
|
|
733
745
|
* Apply circuit breaker adjustments
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { ConfigFileManager } from './config-file-manager.js';
|
|
14
14
|
import { EventEmitter } from 'events';
|
|
15
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, unlinkSync } from 'fs';
|
|
15
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, unlinkSync, renameSync, statSync } from 'fs';
|
|
16
16
|
import { cpus, homedir } from 'os';
|
|
17
17
|
import { join } from 'path';
|
|
18
18
|
import { createRequire } from 'node:module';
|
|
@@ -31,6 +31,8 @@ import { getRepoSupervisorRegistry } from './repo-supervisor.js';
|
|
|
31
31
|
// Daemon config is read synchronously from the constructor. In an ESM build,
|
|
32
32
|
// bare `require` is undefined, so use a module-relative require for YAML.
|
|
33
33
|
const requireYaml = createRequire(import.meta.url);
|
|
34
|
+
const DAEMON_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
35
|
+
const DAEMON_LOG_BACKUPS = 3;
|
|
34
36
|
// Default worker configurations with improved intervals (P0 fix: map 5min -> 15min)
|
|
35
37
|
const DEFAULT_WORKERS = [
|
|
36
38
|
{ type: 'map', intervalMs: 15 * 60 * 1000, offsetMs: 0, priority: 'normal', description: 'Codebase mapping', enabled: true },
|
|
@@ -1543,7 +1545,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1543
1545
|
rawOutputPreview: typeof result.output === 'string' ? result.output.slice(0, 2000) : undefined,
|
|
1544
1546
|
rawOutputLength: typeof result.output === 'string' ? result.output.length : 0,
|
|
1545
1547
|
};
|
|
1546
|
-
|
|
1548
|
+
writeFileAtomic(metricsFile, Buffer.from(JSON.stringify(persisted, null, 2)));
|
|
1547
1549
|
}
|
|
1548
1550
|
// Worker implementations
|
|
1549
1551
|
async runMapWorker() {
|
|
@@ -1564,7 +1566,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1564
1566
|
},
|
|
1565
1567
|
scannedAt: Date.now(),
|
|
1566
1568
|
};
|
|
1567
|
-
|
|
1569
|
+
writeFileAtomic(metricsFile, Buffer.from(JSON.stringify(map, null, 2)));
|
|
1568
1570
|
return map;
|
|
1569
1571
|
}
|
|
1570
1572
|
/**
|
|
@@ -1596,7 +1598,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1596
1598
|
],
|
|
1597
1599
|
note: 'Local fallback checks file presence only; it does not scan source contents.',
|
|
1598
1600
|
};
|
|
1599
|
-
|
|
1601
|
+
writeFileAtomic(auditFile, Buffer.from(JSON.stringify(audit, null, 2)));
|
|
1600
1602
|
return audit;
|
|
1601
1603
|
}
|
|
1602
1604
|
/**
|
|
@@ -1620,7 +1622,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1620
1622
|
},
|
|
1621
1623
|
note: 'Install Claude Code CLI for AI-powered optimization suggestions',
|
|
1622
1624
|
};
|
|
1623
|
-
|
|
1625
|
+
writeFileAtomic(optimizeFile, Buffer.from(JSON.stringify(perf, null, 2)));
|
|
1624
1626
|
return perf;
|
|
1625
1627
|
}
|
|
1626
1628
|
/**
|
|
@@ -1662,7 +1664,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1662
1664
|
memoryCleaned: 0,
|
|
1663
1665
|
duplicatesRemoved: 0,
|
|
1664
1666
|
};
|
|
1665
|
-
|
|
1667
|
+
writeFileAtomic(consolidateFile, Buffer.from(JSON.stringify(disabledResult, null, 2)));
|
|
1666
1668
|
return disabledResult;
|
|
1667
1669
|
}
|
|
1668
1670
|
let report;
|
|
@@ -1689,7 +1691,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1689
1691
|
memoryCleaned: 0,
|
|
1690
1692
|
duplicatesRemoved: 0,
|
|
1691
1693
|
};
|
|
1692
|
-
|
|
1694
|
+
writeFileAtomic(consolidateFile, Buffer.from(JSON.stringify(errorResult, null, 2)));
|
|
1693
1695
|
return errorResult;
|
|
1694
1696
|
}
|
|
1695
1697
|
for (const store of stores) {
|
|
@@ -1733,7 +1735,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1733
1735
|
skipped: part.skipped,
|
|
1734
1736
|
})),
|
|
1735
1737
|
};
|
|
1736
|
-
|
|
1738
|
+
writeFileAtomic(consolidateFile, Buffer.from(JSON.stringify(result, null, 2)));
|
|
1737
1739
|
return result;
|
|
1738
1740
|
}
|
|
1739
1741
|
/**
|
|
@@ -1775,7 +1777,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1775
1777
|
this.log('warn', `Backup worker failed: ${message}`);
|
|
1776
1778
|
result = { timestamp: new Date().toISOString(), backedUp: false, error: message };
|
|
1777
1779
|
}
|
|
1778
|
-
|
|
1780
|
+
writeFileAtomic(backupFile, Buffer.from(JSON.stringify(result, null, 2)));
|
|
1779
1781
|
return result;
|
|
1780
1782
|
}
|
|
1781
1783
|
/**
|
|
@@ -1814,7 +1816,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1814
1816
|
this.log('warn', `Harness worker failed: ${message}`);
|
|
1815
1817
|
result = { timestamp: new Date().toISOString(), ran: false, error: message };
|
|
1816
1818
|
}
|
|
1817
|
-
|
|
1819
|
+
writeFileAtomic(harnessFile, Buffer.from(JSON.stringify(result, null, 2)));
|
|
1818
1820
|
return result;
|
|
1819
1821
|
}
|
|
1820
1822
|
/**
|
|
@@ -1835,7 +1837,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1835
1837
|
gaps: [],
|
|
1836
1838
|
note: 'Install Claude Code CLI for AI-powered test gap analysis',
|
|
1837
1839
|
};
|
|
1838
|
-
|
|
1840
|
+
writeFileAtomic(testGapsFile, Buffer.from(JSON.stringify(result, null, 2)));
|
|
1839
1841
|
return result;
|
|
1840
1842
|
}
|
|
1841
1843
|
/**
|
|
@@ -1916,7 +1918,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1916
1918
|
uptime: process.uptime(),
|
|
1917
1919
|
},
|
|
1918
1920
|
};
|
|
1919
|
-
|
|
1921
|
+
writeFileAtomic(benchmarkFile, Buffer.from(JSON.stringify(result, null, 2)));
|
|
1920
1922
|
return result;
|
|
1921
1923
|
}
|
|
1922
1924
|
/**
|
|
@@ -2020,6 +2022,22 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
2020
2022
|
// Also write to log file
|
|
2021
2023
|
try {
|
|
2022
2024
|
const logFile = join(this.config.logDir, 'daemon.log');
|
|
2025
|
+
// Keep three generations. Rotate before the next complete record
|
|
2026
|
+
// crosses 5 MiB; a single oversized record is preserved intact.
|
|
2027
|
+
if (existsSync(logFile)) {
|
|
2028
|
+
const size = statSync(logFile).size;
|
|
2029
|
+
if (size > 0 && size + Buffer.byteLength(logMessage + '\n') > DAEMON_LOG_MAX_BYTES) {
|
|
2030
|
+
const oldest = `${logFile}.${DAEMON_LOG_BACKUPS}`;
|
|
2031
|
+
if (existsSync(oldest))
|
|
2032
|
+
unlinkSync(oldest);
|
|
2033
|
+
for (let generation = DAEMON_LOG_BACKUPS - 1; generation >= 1; generation--) {
|
|
2034
|
+
const previous = `${logFile}.${generation}`;
|
|
2035
|
+
if (existsSync(previous))
|
|
2036
|
+
renameSync(previous, `${logFile}.${generation + 1}`);
|
|
2037
|
+
}
|
|
2038
|
+
renameSync(logFile, `${logFile}.1`);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2023
2041
|
appendFileSync(logFile, logMessage + '\n');
|
|
2024
2042
|
}
|
|
2025
2043
|
catch {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import * as crypto from 'crypto';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
10
11
|
import * as path from 'path';
|
|
11
12
|
import { execFileSync } from 'child_process';
|
|
12
13
|
/**
|
|
@@ -92,7 +93,7 @@ export async function uploadToGCS(content, options = {}) {
|
|
|
92
93
|
}
|
|
93
94
|
console.log(`[GCS] Uploading to gs://${config.bucket}/${objectPath}...`);
|
|
94
95
|
// Write content to temp file
|
|
95
|
-
const tempDir =
|
|
96
|
+
const tempDir = tmpdir();
|
|
96
97
|
const tempFile = path.join(tempDir, `claude-flow-upload-${Date.now()}.json`);
|
|
97
98
|
fs.writeFileSync(tempFile, content);
|
|
98
99
|
try {
|
|
@@ -151,7 +152,7 @@ export async function downloadFromGCS(uri, config) {
|
|
|
151
152
|
const cfg = config || getGCSConfig();
|
|
152
153
|
console.log(`[GCS] Downloading from ${uri}...`);
|
|
153
154
|
// Write to temp file first
|
|
154
|
-
const tempDir =
|
|
155
|
+
const tempDir = tmpdir();
|
|
155
156
|
const tempFile = path.join(tempDir, `claude-flow-download-${Date.now()}.json`);
|
|
156
157
|
try {
|
|
157
158
|
// Download using gcloud storage cp (array form prevents shell injection)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.72.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"name": "ruflo-metaharness",
|
|
3
3
|
"description": "MetaHarness integration for ruflo — surfaces score/genome/mint/mcp-scan/threat-model via skills; pairs with @metaharness/router (ADR-148/149) for cost-optimal model routing; honors ADR-150's architectural constraint that MetaHarness remains an optional augmentation, never a required runtime dependency. As a mod (ADR-445): confirms flywheel promotion, keeps secrets out of inputs, /metaharness-mod, and a status file the console shows",
|
|
4
4
|
"version": "0.4.0",
|
|
5
|
+
"repository": "https://github.com/ruvnet/ruflo",
|
|
5
6
|
"author": {
|
|
6
7
|
"name": "ruvnet",
|
|
7
8
|
"url": "https://github.com/ruvnet"
|