claude-flow 3.5.22 → 3.5.24
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/README.md +9 -7
- package/package.json +1 -1
- package/v3/@claude-flow/cli/README.md +9 -7
- package/v3/@claude-flow/cli/dist/src/commands/daemon.js +54 -7
- package/v3/@claude-flow/cli/dist/src/commands/init.js +3 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +17 -17
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +10 -10
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +150 -7
- package/v3/@claude-flow/cli/dist/src/services/index.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/ruvector-training.js +11 -4
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +24 -3
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +123 -12
- package/v3/@claude-flow/cli/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
|
-
> **Why Ruflo?** Claude Flow is now Ruflo — named by Ruv, who loves Rust, flow states, and building things that feel inevitable. The "Ru" is the Ruv. The "flo" is the flow. Underneath, WASM kernels written in Rust power the policy engine, embeddings, and proof system. 5,
|
|
28
|
+
> **Why Ruflo?** Claude Flow is now Ruflo — named by Ruv, who loves Rust, flow states, and building things that feel inevitable. The "Ru" is the Ruv. The "flo" is the flow. Underneath, WASM kernels written in Rust power the policy engine, embeddings, and proof system. 5,900+ commits later, the alpha is over. This is v3.5.
|
|
29
29
|
|
|
30
30
|
## Getting into the Flow
|
|
31
31
|
|
|
@@ -161,6 +161,8 @@ curl -fsSL https://cdn.jsdelivr.net/gh/ruvnet/claude-flow@main/scripts/install.s
|
|
|
161
161
|
npx ruflo@latest init --wizard
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
> **New to Ruflo?** You don't need to learn 259 MCP tools or 26 CLI commands. After running `init`, just use Claude Code normally — the hooks system automatically routes tasks to the right agents, learns from successful patterns, and coordinates multi-agent work in the background. The advanced tools exist for fine-grained control when you need it.
|
|
165
|
+
|
|
164
166
|
---
|
|
165
167
|
### Key Capabilities
|
|
166
168
|
|
|
@@ -697,12 +699,12 @@ claude mcp add ruflo -- npx -y ruflo@latest mcp start
|
|
|
697
699
|
claude mcp list
|
|
698
700
|
```
|
|
699
701
|
|
|
700
|
-
Once added, Claude Code can use all
|
|
702
|
+
Once added, Claude Code can use all 259 ruflo MCP tools directly:
|
|
701
703
|
- `swarm_init` - Initialize agent swarms
|
|
702
704
|
- `agent_spawn` - Spawn specialized agents
|
|
703
705
|
- `memory_search` - Search patterns with HNSW vector search
|
|
704
706
|
- `hooks_route` - Intelligent task routing
|
|
705
|
-
- And
|
|
707
|
+
- And 255+ more tools...
|
|
706
708
|
|
|
707
709
|
---
|
|
708
710
|
## What is it exactly? Agents that learn, build and work perpetually.
|
|
@@ -752,7 +754,7 @@ Ruflo v3 introduces **self-learning neural capabilities** that no other agent or
|
|
|
752
754
|
|
|
753
755
|
| Feature | Ruflo v3 | CrewAI | LangGraph | AutoGen | Manus |
|
|
754
756
|
|---------|----------------|--------|-----------|---------|-------|
|
|
755
|
-
| **MCP Integration** | ✅ Native (
|
|
757
|
+
| **MCP Integration** | ✅ Native (259 tools) | ⛔ | ⛔ | ⛔ | ⛔ |
|
|
756
758
|
| **Skills System** | ✅ 42+ pre-built | ⛔ | ⛔ | ⛔ | Limited |
|
|
757
759
|
| **Stream Pipelines** | ✅ JSON chains | ⛔ | Via code | ⛔ | ⛔ |
|
|
758
760
|
| **Pair Programming** | ✅ Driver/Navigator | ⛔ | ⛔ | ⛔ | ⛔ |
|
|
@@ -2158,7 +2160,7 @@ npx ruflo@v3alpha worker status
|
|
|
2158
2160
|
| `agentConfigs` | 15 V3 agent configurations | Agent testing |
|
|
2159
2161
|
| `memoryEntries` | Patterns, rules, embeddings | Memory testing |
|
|
2160
2162
|
| `swarmConfigs` | V3 default, minimal, mesh, hierarchical | Swarm testing |
|
|
2161
|
-
| `mcpTools` |
|
|
2163
|
+
| `mcpTools` | 259 tool definitions | MCP testing |
|
|
2162
2164
|
|
|
2163
2165
|
</details>
|
|
2164
2166
|
|
|
@@ -5018,9 +5020,9 @@ npx agentic-flow mcp stdio
|
|
|
5018
5020
|
</details>
|
|
5019
5021
|
|
|
5020
5022
|
<details>
|
|
5021
|
-
<summary>🔧 <strong>MCP Tools</strong> —
|
|
5023
|
+
<summary>🔧 <strong>MCP Tools</strong> — 259 Integration Tools</summary>
|
|
5022
5024
|
|
|
5023
|
-
Agentic-flow exposes
|
|
5025
|
+
Agentic-flow exposes 259 MCP tools for integration:
|
|
5024
5026
|
|
|
5025
5027
|
| Category | Tools | Examples |
|
|
5026
5028
|
|----------|-------|----------|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.24",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
|
-
> **Why Ruflo?** Claude Flow is now Ruflo — named by Ruv, who loves Rust, flow states, and building things that feel inevitable. The "Ru" is the Ruv. The "flo" is the flow. Underneath, WASM kernels written in Rust power the policy engine, embeddings, and proof system. 5,
|
|
28
|
+
> **Why Ruflo?** Claude Flow is now Ruflo — named by Ruv, who loves Rust, flow states, and building things that feel inevitable. The "Ru" is the Ruv. The "flo" is the flow. Underneath, WASM kernels written in Rust power the policy engine, embeddings, and proof system. 5,900+ commits later, the alpha is over. This is v3.5.
|
|
29
29
|
|
|
30
30
|
## Getting into the Flow
|
|
31
31
|
|
|
@@ -161,6 +161,8 @@ curl -fsSL https://cdn.jsdelivr.net/gh/ruvnet/claude-flow@main/scripts/install.s
|
|
|
161
161
|
npx ruflo@latest init --wizard
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
> **New to Ruflo?** You don't need to learn 259 MCP tools or 26 CLI commands. After running `init`, just use Claude Code normally — the hooks system automatically routes tasks to the right agents, learns from successful patterns, and coordinates multi-agent work in the background. The advanced tools exist for fine-grained control when you need it.
|
|
165
|
+
|
|
164
166
|
---
|
|
165
167
|
### Key Capabilities
|
|
166
168
|
|
|
@@ -697,12 +699,12 @@ claude mcp add ruflo -- npx -y ruflo@latest mcp start
|
|
|
697
699
|
claude mcp list
|
|
698
700
|
```
|
|
699
701
|
|
|
700
|
-
Once added, Claude Code can use all
|
|
702
|
+
Once added, Claude Code can use all 259 ruflo MCP tools directly:
|
|
701
703
|
- `swarm_init` - Initialize agent swarms
|
|
702
704
|
- `agent_spawn` - Spawn specialized agents
|
|
703
705
|
- `memory_search` - Search patterns with HNSW vector search
|
|
704
706
|
- `hooks_route` - Intelligent task routing
|
|
705
|
-
- And
|
|
707
|
+
- And 255+ more tools...
|
|
706
708
|
|
|
707
709
|
---
|
|
708
710
|
## What is it exactly? Agents that learn, build and work perpetually.
|
|
@@ -752,7 +754,7 @@ Ruflo v3 introduces **self-learning neural capabilities** that no other agent or
|
|
|
752
754
|
|
|
753
755
|
| Feature | Ruflo v3 | CrewAI | LangGraph | AutoGen | Manus |
|
|
754
756
|
|---------|----------------|--------|-----------|---------|-------|
|
|
755
|
-
| **MCP Integration** | ✅ Native (
|
|
757
|
+
| **MCP Integration** | ✅ Native (259 tools) | ⛔ | ⛔ | ⛔ | ⛔ |
|
|
756
758
|
| **Skills System** | ✅ 42+ pre-built | ⛔ | ⛔ | ⛔ | Limited |
|
|
757
759
|
| **Stream Pipelines** | ✅ JSON chains | ⛔ | Via code | ⛔ | ⛔ |
|
|
758
760
|
| **Pair Programming** | ✅ Driver/Navigator | ⛔ | ⛔ | ⛔ | ⛔ |
|
|
@@ -2158,7 +2160,7 @@ npx ruflo@v3alpha worker status
|
|
|
2158
2160
|
| `agentConfigs` | 15 V3 agent configurations | Agent testing |
|
|
2159
2161
|
| `memoryEntries` | Patterns, rules, embeddings | Memory testing |
|
|
2160
2162
|
| `swarmConfigs` | V3 default, minimal, mesh, hierarchical | Swarm testing |
|
|
2161
|
-
| `mcpTools` |
|
|
2163
|
+
| `mcpTools` | 259 tool definitions | MCP testing |
|
|
2162
2164
|
|
|
2163
2165
|
</details>
|
|
2164
2166
|
|
|
@@ -5018,9 +5020,9 @@ npx agentic-flow mcp stdio
|
|
|
5018
5020
|
</details>
|
|
5019
5021
|
|
|
5020
5022
|
<details>
|
|
5021
|
-
<summary>🔧 <strong>MCP Tools</strong> —
|
|
5023
|
+
<summary>🔧 <strong>MCP Tools</strong> — 259 Integration Tools</summary>
|
|
5022
5024
|
|
|
5023
|
-
Agentic-flow exposes
|
|
5025
|
+
Agentic-flow exposes 259 MCP tools for integration:
|
|
5024
5026
|
|
|
5025
5027
|
| Category | Tools | Examples |
|
|
5026
5028
|
|----------|-------|----------|
|
|
@@ -19,6 +19,8 @@ const startCommand = {
|
|
|
19
19
|
{ name: 'foreground', short: 'f', type: 'boolean', description: 'Run daemon in foreground (blocks terminal)' },
|
|
20
20
|
{ name: 'headless', type: 'boolean', description: 'Enable headless worker execution (E2B sandbox)' },
|
|
21
21
|
{ name: 'sandbox', type: 'string', description: 'Default sandbox mode for headless workers', choices: ['strict', 'permissive', 'disabled'] },
|
|
22
|
+
{ name: 'max-cpu-load', type: 'string', description: 'Override maxCpuLoad resource threshold (e.g. 4.0)' },
|
|
23
|
+
{ name: 'min-free-memory', type: 'string', description: 'Override minFreeMemoryPercent resource threshold (e.g. 15)' },
|
|
22
24
|
],
|
|
23
25
|
examples: [
|
|
24
26
|
{ command: 'claude-flow daemon start', description: 'Start daemon in background (default)' },
|
|
@@ -31,6 +33,37 @@ const startCommand = {
|
|
|
31
33
|
const foreground = ctx.flags.foreground;
|
|
32
34
|
const projectRoot = process.cwd();
|
|
33
35
|
const isDaemonProcess = process.env.CLAUDE_FLOW_DAEMON === '1';
|
|
36
|
+
// Parse resource threshold overrides from CLI flags
|
|
37
|
+
const config = {};
|
|
38
|
+
const rawMaxCpu = ctx.flags['max-cpu-load'];
|
|
39
|
+
const rawMinMem = ctx.flags['min-free-memory'];
|
|
40
|
+
// Strict numeric pattern to prevent command injection when forwarding to subprocess (S1)
|
|
41
|
+
const NUMERIC_RE = /^\d+(\.\d+)?$/;
|
|
42
|
+
const sanitize = (s) => s.replace(/[\x00-\x1f\x7f-\x9f]/g, '');
|
|
43
|
+
if (rawMaxCpu || rawMinMem) {
|
|
44
|
+
const thresholds = {};
|
|
45
|
+
if (rawMaxCpu) {
|
|
46
|
+
const val = parseFloat(rawMaxCpu);
|
|
47
|
+
if (NUMERIC_RE.test(rawMaxCpu) && isFinite(val) && val > 0 && val <= 1000) {
|
|
48
|
+
thresholds.maxCpuLoad = val;
|
|
49
|
+
}
|
|
50
|
+
else if (!quiet) {
|
|
51
|
+
output.printWarning(`Ignoring invalid --max-cpu-load value: ${sanitize(rawMaxCpu)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (rawMinMem) {
|
|
55
|
+
const val = parseFloat(rawMinMem);
|
|
56
|
+
if (NUMERIC_RE.test(rawMinMem) && isFinite(val) && val >= 0 && val <= 100) {
|
|
57
|
+
thresholds.minFreeMemoryPercent = val;
|
|
58
|
+
}
|
|
59
|
+
else if (!quiet) {
|
|
60
|
+
output.printWarning(`Ignoring invalid --min-free-memory value: ${sanitize(rawMinMem)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (thresholds.maxCpuLoad !== undefined || thresholds.minFreeMemoryPercent !== undefined) {
|
|
64
|
+
config.resourceThresholds = thresholds;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
34
67
|
// Check if background daemon already running (skip if we ARE the daemon process)
|
|
35
68
|
if (!isDaemonProcess) {
|
|
36
69
|
const bgPid = getBackgroundDaemonPid(projectRoot);
|
|
@@ -43,7 +76,7 @@ const startCommand = {
|
|
|
43
76
|
}
|
|
44
77
|
// Background mode (default): fork a detached process
|
|
45
78
|
if (!foreground) {
|
|
46
|
-
return startBackgroundDaemon(projectRoot, quiet);
|
|
79
|
+
return startBackgroundDaemon(projectRoot, quiet, rawMaxCpu, rawMinMem);
|
|
47
80
|
}
|
|
48
81
|
// Foreground mode: run in current process (blocks terminal)
|
|
49
82
|
try {
|
|
@@ -74,7 +107,7 @@ const startCommand = {
|
|
|
74
107
|
if (!quiet) {
|
|
75
108
|
const spinner = output.createSpinner({ text: 'Starting worker daemon...', spinner: 'dots' });
|
|
76
109
|
spinner.start();
|
|
77
|
-
const daemon = await startDaemon(projectRoot);
|
|
110
|
+
const daemon = await startDaemon(projectRoot, config);
|
|
78
111
|
const status = daemon.getStatus();
|
|
79
112
|
spinner.succeed('Worker daemon started (foreground mode)');
|
|
80
113
|
output.writeln();
|
|
@@ -83,6 +116,8 @@ const startCommand = {
|
|
|
83
116
|
`Started: ${status.startedAt?.toISOString()}`,
|
|
84
117
|
`Workers: ${status.config.workers.filter(w => w.enabled).length} enabled`,
|
|
85
118
|
`Max Concurrent: ${status.config.maxConcurrent}`,
|
|
119
|
+
`Max CPU Load: ${status.config.resourceThresholds.maxCpuLoad}`,
|
|
120
|
+
`Min Free Memory: ${status.config.resourceThresholds.minFreeMemoryPercent}%`,
|
|
86
121
|
].join('\n'), 'Daemon Status');
|
|
87
122
|
output.writeln();
|
|
88
123
|
output.writeln(output.bold('Scheduled Workers'));
|
|
@@ -120,7 +155,7 @@ const startCommand = {
|
|
|
120
155
|
await new Promise(() => { }); // Never resolves - daemon runs until killed
|
|
121
156
|
}
|
|
122
157
|
else {
|
|
123
|
-
await startDaemon(projectRoot);
|
|
158
|
+
await startDaemon(projectRoot, config);
|
|
124
159
|
await new Promise(() => { }); // Keep alive
|
|
125
160
|
}
|
|
126
161
|
return { success: true };
|
|
@@ -157,7 +192,7 @@ function validatePath(path, label) {
|
|
|
157
192
|
/**
|
|
158
193
|
* Start daemon as a detached background process
|
|
159
194
|
*/
|
|
160
|
-
async function startBackgroundDaemon(projectRoot, quiet) {
|
|
195
|
+
async function startBackgroundDaemon(projectRoot, quiet, maxCpuLoad, minFreeMemory) {
|
|
161
196
|
// Validate and resolve project root
|
|
162
197
|
const resolvedRoot = resolve(projectRoot);
|
|
163
198
|
validatePath(resolvedRoot, 'Project root');
|
|
@@ -199,10 +234,20 @@ async function startBackgroundDaemon(projectRoot, quiet) {
|
|
|
199
234
|
};
|
|
200
235
|
// Use spawn with explicit arguments instead of shell string interpolation
|
|
201
236
|
// This prevents command injection via paths
|
|
202
|
-
const
|
|
237
|
+
const spawnArgs = [
|
|
203
238
|
cliPath,
|
|
204
|
-
'daemon', 'start', '--foreground', '--quiet'
|
|
205
|
-
]
|
|
239
|
+
'daemon', 'start', '--foreground', '--quiet',
|
|
240
|
+
];
|
|
241
|
+
// Forward resource threshold flags to the foreground child process
|
|
242
|
+
// Validate with strict numeric pattern to prevent shell injection on Windows (S1)
|
|
243
|
+
const SPAWN_NUMERIC_RE = /^\d+(\.\d+)?$/;
|
|
244
|
+
if (maxCpuLoad && SPAWN_NUMERIC_RE.test(maxCpuLoad)) {
|
|
245
|
+
spawnArgs.push('--max-cpu-load', maxCpuLoad);
|
|
246
|
+
}
|
|
247
|
+
if (minFreeMemory && SPAWN_NUMERIC_RE.test(minFreeMemory)) {
|
|
248
|
+
spawnArgs.push('--min-free-memory', minFreeMemory);
|
|
249
|
+
}
|
|
250
|
+
const child = spawn(process.execPath, spawnArgs, spawnOpts);
|
|
206
251
|
// Get PID from spawned process directly (no shell echo needed)
|
|
207
252
|
const pid = child.pid;
|
|
208
253
|
if (!pid || pid <= 0) {
|
|
@@ -371,6 +416,8 @@ const statusCommand = {
|
|
|
371
416
|
status.startedAt ? `Started: ${status.startedAt.toISOString()}` : '',
|
|
372
417
|
`Workers Enabled: ${status.config.workers.filter(w => w.enabled).length}`,
|
|
373
418
|
`Max Concurrent: ${status.config.maxConcurrent}`,
|
|
419
|
+
`Max CPU Load: ${status.config.resourceThresholds.maxCpuLoad}`,
|
|
420
|
+
`Min Free Memory: ${status.config.resourceThresholds.minFreeMemoryPercent}%`,
|
|
374
421
|
].filter(Boolean).join('\n'), 'RuFlo Daemon');
|
|
375
422
|
output.writeln();
|
|
376
423
|
output.writeln(output.bold('Worker Status'));
|
|
@@ -21,9 +21,11 @@ async function initCodexAction(ctx, options) {
|
|
|
21
21
|
// Dynamic import of the Codex initializer with lazy loading fallback
|
|
22
22
|
let CodexInitializer;
|
|
23
23
|
// Try multiple resolution strategies for the @claude-flow/codex package
|
|
24
|
+
// Use a variable to prevent TypeScript from statically resolving the optional module
|
|
25
|
+
const codexModuleId = '@claude-flow/codex';
|
|
24
26
|
const resolutionStrategies = [
|
|
25
27
|
// Strategy 1: Direct import (works if installed as CLI dependency)
|
|
26
|
-
async () => (await import(
|
|
28
|
+
async () => (await import(codexModuleId)).CodexInitializer,
|
|
27
29
|
// Strategy 2: Project node_modules (works if installed in user's project)
|
|
28
30
|
async () => {
|
|
29
31
|
const projectPath = path.join(ctx.cwd, 'node_modules', '@claude-flow', 'codex', 'dist', 'index.js');
|
|
@@ -430,7 +430,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
430
430
|
ddd: { progress: 0, modules: 0, totalFiles: 0, totalLines: 0 },
|
|
431
431
|
swarm: { activeAgents: 0, maxAgents: 15, topology: 'hierarchical-mesh' },
|
|
432
432
|
learning: { status: 'READY', patternsLearned: 0, sessionsCompleted: 0 },
|
|
433
|
-
_note: 'Metrics will update as you use
|
|
433
|
+
_note: 'Metrics will update as you use Ruflo'
|
|
434
434
|
};
|
|
435
435
|
fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2), 'utf-8');
|
|
436
436
|
result.created.push('.claude-flow/metrics/v3-progress.json');
|
|
@@ -462,7 +462,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
462
462
|
routing: { accuracy: 0, decisions: 0 },
|
|
463
463
|
patterns: { shortTerm: 0, longTerm: 0, quality: 0 },
|
|
464
464
|
sessions: { total: 0, current: null },
|
|
465
|
-
_note: 'Intelligence grows as you use
|
|
465
|
+
_note: 'Intelligence grows as you use Ruflo'
|
|
466
466
|
};
|
|
467
467
|
fs.writeFileSync(learningPath, JSON.stringify(learning, null, 2), 'utf-8');
|
|
468
468
|
result.created.push('.claude-flow/metrics/learning.json');
|
|
@@ -1129,7 +1129,7 @@ async function writeInitialMetrics(targetDir, options, result) {
|
|
|
1129
1129
|
patternsLearned: 0,
|
|
1130
1130
|
sessionsCompleted: 0
|
|
1131
1131
|
},
|
|
1132
|
-
_note: 'Metrics will update as you use
|
|
1132
|
+
_note: 'Metrics will update as you use Ruflo. Run: npx ruflo@latest daemon start'
|
|
1133
1133
|
};
|
|
1134
1134
|
fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2), 'utf-8');
|
|
1135
1135
|
result.created.files.push('.claude-flow/metrics/v3-progress.json');
|
|
@@ -1176,7 +1176,7 @@ async function writeInitialMetrics(targetDir, options, result) {
|
|
|
1176
1176
|
total: 0,
|
|
1177
1177
|
current: null
|
|
1178
1178
|
},
|
|
1179
|
-
_note: 'Intelligence grows as you use
|
|
1179
|
+
_note: 'Intelligence grows as you use Ruflo'
|
|
1180
1180
|
};
|
|
1181
1181
|
fs.writeFileSync(learningPath, JSON.stringify(learning, null, 2), 'utf-8');
|
|
1182
1182
|
result.created.files.push('.claude-flow/metrics/learning.json');
|
|
@@ -1197,7 +1197,7 @@ async function writeInitialMetrics(targetDir, options, result) {
|
|
|
1197
1197
|
}
|
|
1198
1198
|
}
|
|
1199
1199
|
/**
|
|
1200
|
-
* Write CAPABILITIES.md - comprehensive overview of all
|
|
1200
|
+
* Write CAPABILITIES.md - comprehensive overview of all Ruflo features
|
|
1201
1201
|
*/
|
|
1202
1202
|
async function writeCapabilitiesDoc(targetDir, options, result) {
|
|
1203
1203
|
const capabilitiesPath = path.join(targetDir, '.claude-flow', 'CAPABILITIES.md');
|
|
@@ -1556,8 +1556,8 @@ npx @claude-flow/cli@latest hive-mind consensus --propose "task"
|
|
|
1556
1556
|
|
|
1557
1557
|
### MCP Server Setup
|
|
1558
1558
|
\`\`\`bash
|
|
1559
|
-
# Add
|
|
1560
|
-
claude mcp add
|
|
1559
|
+
# Add Ruflo MCP
|
|
1560
|
+
claude mcp add ruflo -- npx -y ruflo@latest
|
|
1561
1561
|
|
|
1562
1562
|
# Optional servers
|
|
1563
1563
|
claude mcp add ruv-swarm -- npx -y ruv-swarm mcp start
|
|
@@ -1571,24 +1571,24 @@ claude mcp add flow-nexus -- npx -y flow-nexus@latest mcp start
|
|
|
1571
1571
|
### Essential Commands
|
|
1572
1572
|
\`\`\`bash
|
|
1573
1573
|
# Setup
|
|
1574
|
-
npx @
|
|
1575
|
-
npx @
|
|
1576
|
-
npx @
|
|
1574
|
+
npx ruflo@latest init --wizard
|
|
1575
|
+
npx ruflo@latest daemon start
|
|
1576
|
+
npx ruflo@latest doctor --fix
|
|
1577
1577
|
|
|
1578
1578
|
# Swarm
|
|
1579
|
-
npx @
|
|
1580
|
-
npx @
|
|
1579
|
+
npx ruflo@latest swarm init --topology hierarchical --max-agents 8
|
|
1580
|
+
npx ruflo@latest swarm status
|
|
1581
1581
|
|
|
1582
1582
|
# Agents
|
|
1583
|
-
npx @
|
|
1584
|
-
npx @
|
|
1583
|
+
npx ruflo@latest agent spawn -t coder
|
|
1584
|
+
npx ruflo@latest agent list
|
|
1585
1585
|
|
|
1586
1586
|
# Memory
|
|
1587
|
-
npx @
|
|
1587
|
+
npx ruflo@latest memory search --query "patterns"
|
|
1588
1588
|
|
|
1589
1589
|
# Hooks
|
|
1590
|
-
npx @
|
|
1591
|
-
npx @
|
|
1590
|
+
npx ruflo@latest hooks pre-task --description "task"
|
|
1591
|
+
npx ruflo@latest hooks worker dispatch --trigger optimize
|
|
1592
1592
|
\`\`\`
|
|
1593
1593
|
|
|
1594
1594
|
### File Structure
|
|
@@ -8,12 +8,12 @@ import { generateStatuslineScript, generateStatuslineHook } from './statusline-g
|
|
|
8
8
|
*/
|
|
9
9
|
export function generatePreCommitHook() {
|
|
10
10
|
return `#!/bin/bash
|
|
11
|
-
#
|
|
11
|
+
# Ruflo Pre-Commit Hook
|
|
12
12
|
# Validates code quality before commit
|
|
13
13
|
|
|
14
14
|
set -e
|
|
15
15
|
|
|
16
|
-
echo "🔍 Running
|
|
16
|
+
echo "🔍 Running Ruflo pre-commit checks..."
|
|
17
17
|
|
|
18
18
|
# Get staged files
|
|
19
19
|
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
|
|
@@ -40,7 +40,7 @@ echo "✅ Pre-commit checks complete"
|
|
|
40
40
|
*/
|
|
41
41
|
export function generatePostCommitHook() {
|
|
42
42
|
return `#!/bin/bash
|
|
43
|
-
#
|
|
43
|
+
# Ruflo Post-Commit Hook
|
|
44
44
|
# Records commit metrics and trains patterns
|
|
45
45
|
|
|
46
46
|
COMMIT_HASH=$(git rev-parse HEAD)
|
|
@@ -48,8 +48,8 @@ COMMIT_MSG=$(git log -1 --pretty=%B)
|
|
|
48
48
|
|
|
49
49
|
echo "📊 Recording commit metrics..."
|
|
50
50
|
|
|
51
|
-
# Notify
|
|
52
|
-
npx @
|
|
51
|
+
# Notify ruflo of commit
|
|
52
|
+
npx ruflo@latest hooks notify \\
|
|
53
53
|
--message "Commit: $COMMIT_MSG" \\
|
|
54
54
|
--level info \\
|
|
55
55
|
--metadata '{"hash": "'$COMMIT_HASH'"}' 2>/dev/null || true
|
|
@@ -63,7 +63,7 @@ echo "✅ Commit recorded"
|
|
|
63
63
|
export function generateSessionManager() {
|
|
64
64
|
return `#!/usr/bin/env node
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Ruflo Session Manager
|
|
67
67
|
* Handles session lifecycle: start, restore, end
|
|
68
68
|
*/
|
|
69
69
|
|
|
@@ -196,7 +196,7 @@ module.exports = commands;
|
|
|
196
196
|
export function generateAgentRouter() {
|
|
197
197
|
return `#!/usr/bin/env node
|
|
198
198
|
/**
|
|
199
|
-
*
|
|
199
|
+
* Ruflo Agent Router
|
|
200
200
|
* Routes tasks to optimal agents based on learned patterns
|
|
201
201
|
*/
|
|
202
202
|
|
|
@@ -268,7 +268,7 @@ module.exports = { routeTask, AGENT_CAPABILITIES, TASK_PATTERNS };
|
|
|
268
268
|
export function generateMemoryHelper() {
|
|
269
269
|
return `#!/usr/bin/env node
|
|
270
270
|
/**
|
|
271
|
-
*
|
|
271
|
+
* Ruflo Memory Helper
|
|
272
272
|
* Simple key-value memory for cross-session context
|
|
273
273
|
*/
|
|
274
274
|
|
|
@@ -361,7 +361,7 @@ export function generateHookHandler() {
|
|
|
361
361
|
const lines = [
|
|
362
362
|
'#!/usr/bin/env node',
|
|
363
363
|
'/**',
|
|
364
|
-
' *
|
|
364
|
+
' * Ruflo Hook Handler (Cross-Platform)',
|
|
365
365
|
' * Dispatches hook events to the appropriate helper modules.',
|
|
366
366
|
' */',
|
|
367
367
|
'',
|
|
@@ -1026,7 +1026,7 @@ PowerShell -ExecutionPolicy Bypass -File "%~dp0daemon-manager.ps1" %*
|
|
|
1026
1026
|
export function generateCrossPlatformSessionManager() {
|
|
1027
1027
|
return `#!/usr/bin/env node
|
|
1028
1028
|
/**
|
|
1029
|
-
*
|
|
1029
|
+
* Ruflo Cross-Platform Session Manager
|
|
1030
1030
|
* Works on Windows, macOS, and Linux
|
|
1031
1031
|
*/
|
|
1032
1032
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Provides intelligent hooks functionality via MCP protocol
|
|
4
4
|
*/
|
|
5
5
|
import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync } from 'fs';
|
|
6
|
-
import { join, resolve } from 'path';
|
|
6
|
+
import { dirname, join, resolve } from 'path';
|
|
7
7
|
// Real vector search functions - lazy loaded to avoid circular imports
|
|
8
8
|
let searchEntriesFn = null;
|
|
9
9
|
async function getRealSearchFunction() {
|
|
@@ -121,7 +121,88 @@ function generateSimpleEmbedding(text, dimension = 384) {
|
|
|
121
121
|
}
|
|
122
122
|
return embedding;
|
|
123
123
|
}
|
|
124
|
-
//
|
|
124
|
+
// ── Runtime routing outcome persistence ──────────────────────────────
|
|
125
|
+
// Closes the learning loop: post-task records outcomes → route loads them.
|
|
126
|
+
const ROUTING_OUTCOMES_PATH = join(resolve('.'), '.claude-flow/routing-outcomes.json');
|
|
127
|
+
const ROUTING_STOPWORDS = new Set([
|
|
128
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
|
|
129
|
+
'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'shall', 'can',
|
|
130
|
+
'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through', 'during',
|
|
131
|
+
'before', 'after', 'above', 'below', 'between', 'under', 'again', 'further', 'then', 'once',
|
|
132
|
+
'it', 'its', 'this', 'that', 'these', 'those', 'i', 'me', 'my', 'we', 'our', 'you', 'your',
|
|
133
|
+
'he', 'she', 'they', 'them', 'and', 'but', 'or', 'nor', 'not', 'no', 'so', 'if', 'when', 'than',
|
|
134
|
+
'very', 'just', 'also', 'only', 'both', 'each', 'all', 'any', 'few', 'more', 'most', 'other',
|
|
135
|
+
'some', 'such', 'same', 'new', 'now', 'here', 'there', 'where', 'how', 'what', 'which', 'who',
|
|
136
|
+
]);
|
|
137
|
+
function extractKeywords(text) {
|
|
138
|
+
if (!text)
|
|
139
|
+
return [];
|
|
140
|
+
return text.toLowerCase()
|
|
141
|
+
.replace(/[^a-z0-9\s-]/g, ' ')
|
|
142
|
+
.split(/\s+/)
|
|
143
|
+
.filter(w => w.length > 2 && !ROUTING_STOPWORDS.has(w));
|
|
144
|
+
}
|
|
145
|
+
function loadRoutingOutcomes() {
|
|
146
|
+
try {
|
|
147
|
+
if (existsSync(ROUTING_OUTCOMES_PATH)) {
|
|
148
|
+
const data = JSON.parse(readFileSync(ROUTING_OUTCOMES_PATH, 'utf-8'));
|
|
149
|
+
return data.outcomes || [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch { /* corrupt file, start fresh */ }
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
function saveRoutingOutcomes(outcomes) {
|
|
156
|
+
try {
|
|
157
|
+
const dir = dirname(ROUTING_OUTCOMES_PATH);
|
|
158
|
+
if (!existsSync(dir))
|
|
159
|
+
mkdirSync(dir, { recursive: true });
|
|
160
|
+
// Cap at 500 entries to bound file size
|
|
161
|
+
const capped = outcomes.slice(-500);
|
|
162
|
+
writeFileSync(ROUTING_OUTCOMES_PATH, JSON.stringify({ outcomes: capped }, null, 2));
|
|
163
|
+
}
|
|
164
|
+
catch { /* non-critical */ }
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Build learned routing patterns from successful task outcomes.
|
|
168
|
+
* Returns patterns in the same shape as TASK_PATTERNS so they can be
|
|
169
|
+
* merged into both the native HNSW and pure-JS semantic routers.
|
|
170
|
+
*/
|
|
171
|
+
function loadLearnedPatterns() {
|
|
172
|
+
const outcomes = loadRoutingOutcomes();
|
|
173
|
+
const byAgent = {};
|
|
174
|
+
for (const o of outcomes) {
|
|
175
|
+
if (!o.success || !o.agent || !o.keywords?.length)
|
|
176
|
+
continue;
|
|
177
|
+
if (!byAgent[o.agent])
|
|
178
|
+
byAgent[o.agent] = new Set();
|
|
179
|
+
for (const kw of o.keywords)
|
|
180
|
+
byAgent[o.agent].add(kw);
|
|
181
|
+
}
|
|
182
|
+
const patterns = {};
|
|
183
|
+
for (const [agent, kwSet] of Object.entries(byAgent)) {
|
|
184
|
+
patterns[`learned-${agent}`] = {
|
|
185
|
+
keywords: [...kwSet].slice(0, 50),
|
|
186
|
+
agents: [agent],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return patterns;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Merge static TASK_PATTERNS with runtime-learned patterns.
|
|
193
|
+
* Static patterns take precedence (learned patterns won't overwrite them).
|
|
194
|
+
*/
|
|
195
|
+
function getMergedTaskPatterns() {
|
|
196
|
+
const merged = { ...TASK_PATTERNS };
|
|
197
|
+
const learned = loadLearnedPatterns();
|
|
198
|
+
for (const [key, pattern] of Object.entries(learned)) {
|
|
199
|
+
if (!merged[key]) {
|
|
200
|
+
merged[key] = pattern;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return merged;
|
|
204
|
+
}
|
|
205
|
+
// ── Static task patterns (used by both native and pure-JS routers) ───
|
|
125
206
|
const TASK_PATTERNS = {
|
|
126
207
|
'security-task': {
|
|
127
208
|
keywords: ['authentication', 'security', 'auth', 'password', 'encryption', 'vulnerability', 'cve', 'audit'],
|
|
@@ -198,8 +279,8 @@ async function getSemanticRouter() {
|
|
|
198
279
|
hnswEfConstruction: 200,
|
|
199
280
|
hnswEfSearch: 100,
|
|
200
281
|
});
|
|
201
|
-
// Initialize with task patterns
|
|
202
|
-
for (const [patternName, { keywords }] of Object.entries(
|
|
282
|
+
// Initialize with static + runtime-learned task patterns
|
|
283
|
+
for (const [patternName, { keywords }] of Object.entries(getMergedTaskPatterns())) {
|
|
203
284
|
for (const keyword of keywords) {
|
|
204
285
|
const embedding = generateSimpleEmbedding(keyword);
|
|
205
286
|
db.insert(`${patternName}:${keyword}`, embedding);
|
|
@@ -220,7 +301,7 @@ async function getSemanticRouter() {
|
|
|
220
301
|
try {
|
|
221
302
|
const { SemanticRouter } = await import('../ruvector/semantic-router.js');
|
|
222
303
|
semanticRouter = new SemanticRouter({ dimension: 384 });
|
|
223
|
-
for (const [patternName, { keywords, agents }] of Object.entries(
|
|
304
|
+
for (const [patternName, { keywords, agents }] of Object.entries(getMergedTaskPatterns())) {
|
|
224
305
|
const embeddings = keywords.map(kw => generateSimpleEmbedding(kw));
|
|
225
306
|
semanticRouter.addIntentWithEmbeddings(patternName, embeddings, { agents, keywords });
|
|
226
307
|
// Cache embeddings for keywords
|
|
@@ -414,11 +495,32 @@ function suggestAgentsForFile(filePath) {
|
|
|
414
495
|
}
|
|
415
496
|
function suggestAgentsForTask(task) {
|
|
416
497
|
const taskLower = task.toLowerCase();
|
|
498
|
+
// Check static keyword patterns first
|
|
417
499
|
for (const [pattern, result] of Object.entries(KEYWORD_PATTERNS)) {
|
|
418
500
|
if (taskLower.includes(pattern)) {
|
|
419
501
|
return result;
|
|
420
502
|
}
|
|
421
503
|
}
|
|
504
|
+
// Check runtime-learned patterns from successful task outcomes
|
|
505
|
+
const taskKeywords = extractKeywords(task);
|
|
506
|
+
if (taskKeywords.length > 0) {
|
|
507
|
+
const outcomes = loadRoutingOutcomes();
|
|
508
|
+
let bestAgent = '';
|
|
509
|
+
let bestOverlap = 0;
|
|
510
|
+
for (const outcome of outcomes) {
|
|
511
|
+
if (!outcome.success || !outcome.agent || !outcome.keywords?.length)
|
|
512
|
+
continue;
|
|
513
|
+
const overlap = taskKeywords.filter(kw => outcome.keywords.includes(kw)).length;
|
|
514
|
+
if (overlap > bestOverlap) {
|
|
515
|
+
bestOverlap = overlap;
|
|
516
|
+
bestAgent = outcome.agent;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// Require at least 2 keyword overlap to prevent false positives
|
|
520
|
+
if (bestAgent && bestOverlap >= 2) {
|
|
521
|
+
return { agents: [bestAgent], confidence: Math.min(0.6 + bestOverlap * 0.05, 0.85) };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
422
524
|
// Default fallback
|
|
423
525
|
return { agents: ['coder', 'researcher', 'tester'], confidence: 0.7 };
|
|
424
526
|
}
|
|
@@ -674,13 +776,16 @@ export const hooksRoute = {
|
|
|
674
776
|
routingMethod = 'semantic-native';
|
|
675
777
|
backendInfo = 'native VectorDb (HNSW)';
|
|
676
778
|
// Convert results to semantic format
|
|
779
|
+
const mergedPatterns = getMergedTaskPatterns();
|
|
677
780
|
semanticResult = results.map((r) => {
|
|
678
781
|
const [patternName] = r.id.split(':');
|
|
679
|
-
const pattern =
|
|
782
|
+
const pattern = mergedPatterns[patternName];
|
|
680
783
|
return {
|
|
681
784
|
intent: patternName,
|
|
682
785
|
score: 1 - r.score, // Native uses distance (lower is better), convert to similarity
|
|
683
|
-
metadata: {
|
|
786
|
+
metadata: {
|
|
787
|
+
agents: pattern?.agents || (patternName.startsWith('learned-') ? [patternName.slice(8)] : ['coder']),
|
|
788
|
+
},
|
|
684
789
|
};
|
|
685
790
|
});
|
|
686
791
|
}
|
|
@@ -941,6 +1046,8 @@ export const hooksPostTask = {
|
|
|
941
1046
|
success: { type: 'boolean', description: 'Whether task was successful' },
|
|
942
1047
|
agent: { type: 'string', description: 'Agent that completed the task' },
|
|
943
1048
|
quality: { type: 'number', description: 'Quality score (0-1)' },
|
|
1049
|
+
task: { type: 'string', description: 'Task description text (used for learning keyword extraction)' },
|
|
1050
|
+
storeDecisions: { type: 'boolean', description: 'Also store routing decision in memory DB' },
|
|
944
1051
|
},
|
|
945
1052
|
required: ['taskId'],
|
|
946
1053
|
},
|
|
@@ -979,6 +1086,41 @@ export const hooksPostTask = {
|
|
|
979
1086
|
catch {
|
|
980
1087
|
// Non-fatal
|
|
981
1088
|
}
|
|
1089
|
+
// Persist routing outcome for runtime learning (file-based, always reliable)
|
|
1090
|
+
const taskText = params.task || '';
|
|
1091
|
+
const outcomeKeywords = extractKeywords(taskText);
|
|
1092
|
+
let outcomePersisted = false;
|
|
1093
|
+
if (taskText && agent && agent.length <= 100 && /^[a-zA-Z0-9_-]+$/.test(agent)) {
|
|
1094
|
+
try {
|
|
1095
|
+
const outcomes = loadRoutingOutcomes();
|
|
1096
|
+
outcomes.push({
|
|
1097
|
+
task: taskText,
|
|
1098
|
+
agent,
|
|
1099
|
+
success,
|
|
1100
|
+
quality,
|
|
1101
|
+
keywords: outcomeKeywords,
|
|
1102
|
+
timestamp: new Date().toISOString(),
|
|
1103
|
+
});
|
|
1104
|
+
saveRoutingOutcomes(outcomes);
|
|
1105
|
+
outcomePersisted = true;
|
|
1106
|
+
}
|
|
1107
|
+
catch { /* non-critical */ }
|
|
1108
|
+
}
|
|
1109
|
+
// Optionally store in memory DB for cross-session vector retrieval
|
|
1110
|
+
if (params.storeDecisions && taskText && agent) {
|
|
1111
|
+
try {
|
|
1112
|
+
const storeFn = await getRealStoreFunction();
|
|
1113
|
+
if (storeFn) {
|
|
1114
|
+
await storeFn({
|
|
1115
|
+
key: `routing-decision:${taskId}`,
|
|
1116
|
+
namespace: 'patterns',
|
|
1117
|
+
value: JSON.stringify({ task: taskText, agent, success, quality, keywords: outcomeKeywords }),
|
|
1118
|
+
tags: ['routing-decision'],
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
catch { /* non-critical */ }
|
|
1123
|
+
}
|
|
982
1124
|
const duration = Date.now() - startTime;
|
|
983
1125
|
return {
|
|
984
1126
|
taskId,
|
|
@@ -989,6 +1131,7 @@ export const hooksPostTask = {
|
|
|
989
1131
|
newPatterns: success ? 1 : 0,
|
|
990
1132
|
trajectoryId: `traj-${Date.now()}`,
|
|
991
1133
|
controller: feedbackResult?.controller || 'none',
|
|
1134
|
+
outcomePersisted,
|
|
992
1135
|
},
|
|
993
1136
|
quality,
|
|
994
1137
|
feedback: feedbackResult ? {
|
|
@@ -6,7 +6,7 @@ export { WorkerDaemon, getDaemon, startDaemon, stopDaemon, type WorkerType, } fr
|
|
|
6
6
|
export { HeadlessWorkerExecutor, HEADLESS_WORKER_TYPES, HEADLESS_WORKER_CONFIGS, LOCAL_WORKER_TYPES, LOCAL_WORKER_CONFIGS, ALL_WORKER_CONFIGS, isHeadlessWorker, isLocalWorker, getModelId, getWorkerConfig, type HeadlessWorkerType, type LocalWorkerType, type HeadlessWorkerConfig, type HeadlessExecutionResult, type HeadlessExecutorConfig, type HeadlessOptions, type PoolStatus, type SandboxMode, type ModelType, type OutputFormat, type ExecutionMode, type WorkerPriority, type WorkerConfig, } from './headless-worker-executor.js';
|
|
7
7
|
export { ContainerWorkerPool, type ContainerInfo, type ContainerPoolConfig, type ContainerExecutionOptions, type ContainerPoolStatus, type ContainerState, } from './container-worker-pool.js';
|
|
8
8
|
export { WorkerQueue, type QueueTask, type WorkerQueueConfig, type QueueStats, type WorkerRegistration, type TaskStatus, } from './worker-queue.js';
|
|
9
|
-
export type { default as WorkerDaemonType } from './worker-daemon.js';
|
|
9
|
+
export type { default as WorkerDaemonType, DaemonConfig } from './worker-daemon.js';
|
|
10
10
|
export type { default as HeadlessWorkerExecutorType } from './headless-worker-executor.js';
|
|
11
11
|
export type { default as ContainerWorkerPoolType } from './container-worker-pool.js';
|
|
12
12
|
export type { default as WorkerQueueType } from './worker-queue.js';
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Created with ❤️ by ruv.io
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* ESM/CJS interop helper — handles `.default` for CJS modules.
|
|
18
|
+
* Uses `'default' in mod` check which is safer than `mod.default || mod`.
|
|
19
|
+
*/
|
|
20
|
+
async function importWithInterop(packageName) {
|
|
21
|
+
const mod = await import(packageName);
|
|
22
|
+
return ('default' in mod) ? mod.default : mod;
|
|
23
|
+
}
|
|
16
24
|
// Lazy-loaded WASM modules
|
|
17
25
|
let microLoRA = null;
|
|
18
26
|
let scopedLoRA = null;
|
|
@@ -252,7 +260,7 @@ export async function initializeTraining(config = {}) {
|
|
|
252
260
|
}
|
|
253
261
|
// --- Attention mechanisms (optional, independent of WASM) ---
|
|
254
262
|
try {
|
|
255
|
-
const attention = await
|
|
263
|
+
const attention = await importWithInterop('@ruvector/attention');
|
|
256
264
|
if (config.useFlashAttention !== false) {
|
|
257
265
|
flashAttention = new attention.FlashAttention(dim, 64);
|
|
258
266
|
features.push('FlashAttention');
|
|
@@ -289,9 +297,8 @@ export async function initializeTraining(config = {}) {
|
|
|
289
297
|
// --- SONA (optional, backward compatible) ---
|
|
290
298
|
if (config.useSona !== false) {
|
|
291
299
|
try {
|
|
292
|
-
const sona = await
|
|
300
|
+
const sona = await importWithInterop('@ruvector/sona');
|
|
293
301
|
const sonaRank = config.sonaRank || 4;
|
|
294
|
-
// @ts-expect-error - SonaEngine accepts 4 positional args but types say 1
|
|
295
302
|
sonaEngine = new sona.SonaEngine(dim, sonaRank, alpha, lr);
|
|
296
303
|
sonaAvailable = true;
|
|
297
304
|
features.push(`SONA (${dim}-dim, rank-${sonaRank}, 624k learn/s)`);
|
|
@@ -473,7 +480,7 @@ export function mineHardNegatives(anchor, candidates) {
|
|
|
473
480
|
* Benchmark the training system
|
|
474
481
|
*/
|
|
475
482
|
export async function benchmarkTraining(dim, iterations) {
|
|
476
|
-
const attention = await
|
|
483
|
+
const attention = await importWithInterop('@ruvector/attention');
|
|
477
484
|
lastBenchmark = attention.benchmarkAttention(dim || 256, 100, iterations || 1000);
|
|
478
485
|
return lastBenchmark ?? [];
|
|
479
486
|
}
|
|
@@ -44,7 +44,7 @@ interface DaemonStatus {
|
|
|
44
44
|
workers: Map<WorkerType, WorkerState>;
|
|
45
45
|
config: DaemonConfig;
|
|
46
46
|
}
|
|
47
|
-
interface DaemonConfig {
|
|
47
|
+
export interface DaemonConfig {
|
|
48
48
|
autoStart: boolean;
|
|
49
49
|
logDir: string;
|
|
50
50
|
stateFile: string;
|
|
@@ -70,6 +70,7 @@ export declare class WorkerDaemon extends EventEmitter {
|
|
|
70
70
|
private pendingWorkers;
|
|
71
71
|
private headlessExecutor;
|
|
72
72
|
private headlessAvailable;
|
|
73
|
+
private originalConfig?;
|
|
73
74
|
constructor(projectRoot: string, config?: Partial<DaemonConfig>);
|
|
74
75
|
/**
|
|
75
76
|
* Initialize headless executor if Claude Code is available
|
|
@@ -83,6 +84,20 @@ export declare class WorkerDaemon extends EventEmitter {
|
|
|
83
84
|
* Get headless executor instance
|
|
84
85
|
*/
|
|
85
86
|
getHeadlessExecutor(): HeadlessWorkerExecutor | null;
|
|
87
|
+
/**
|
|
88
|
+
* Detect effective CPU count for the current environment.
|
|
89
|
+
*
|
|
90
|
+
* Inside Docker / K8s containers, os.cpus().length reports the HOST cpu
|
|
91
|
+
* count, not the container limit (Node.js #28762 — wontfix). We read
|
|
92
|
+
* cgroup v2 / v1 quota files first so the maxCpuLoad threshold stays
|
|
93
|
+
* meaningful under resource-limited containers.
|
|
94
|
+
*/
|
|
95
|
+
static getEffectiveCpuCount(): number;
|
|
96
|
+
/**
|
|
97
|
+
* Read daemon-specific config from .claude-flow/config.json
|
|
98
|
+
* Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'
|
|
99
|
+
*/
|
|
100
|
+
private readDaemonConfigFromFile;
|
|
86
101
|
/**
|
|
87
102
|
* Setup graceful shutdown handlers
|
|
88
103
|
*/
|
|
@@ -93,6 +108,12 @@ export declare class WorkerDaemon extends EventEmitter {
|
|
|
93
108
|
private canRunWorker;
|
|
94
109
|
/**
|
|
95
110
|
* Process pending workers queue
|
|
111
|
+
*
|
|
112
|
+
* When executeWorkerWithConcurrencyControl defers a worker (returns null),
|
|
113
|
+
* we break immediately to avoid a busy-wait loop — the deferred worker is
|
|
114
|
+
* already back on the pendingWorkers queue by that point. If no workers are
|
|
115
|
+
* currently running when we break, we schedule a backoff retry so the queue
|
|
116
|
+
* does not get permanently stuck.
|
|
96
117
|
*/
|
|
97
118
|
private processPendingWorkers;
|
|
98
119
|
private initializeWorkerStates;
|
|
@@ -194,11 +215,11 @@ export declare class WorkerDaemon extends EventEmitter {
|
|
|
194
215
|
/**
|
|
195
216
|
* Get or create daemon instance
|
|
196
217
|
*/
|
|
197
|
-
export declare function getDaemon(projectRoot?: string): WorkerDaemon;
|
|
218
|
+
export declare function getDaemon(projectRoot?: string, config?: Partial<DaemonConfig>): WorkerDaemon;
|
|
198
219
|
/**
|
|
199
220
|
* Start daemon (for use in session-start hook)
|
|
200
221
|
*/
|
|
201
|
-
export declare function startDaemon(projectRoot: string): Promise<WorkerDaemon>;
|
|
222
|
+
export declare function startDaemon(projectRoot: string, config?: Partial<DaemonConfig>): Promise<WorkerDaemon>;
|
|
202
223
|
/**
|
|
203
224
|
* Stop daemon
|
|
204
225
|
*/
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { EventEmitter } from 'events';
|
|
13
13
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'fs';
|
|
14
|
+
import { cpus } from 'os';
|
|
14
15
|
import { join } from 'path';
|
|
15
16
|
import { HeadlessWorkerExecutor, isHeadlessWorker, } from './headless-worker-executor.js';
|
|
16
17
|
// Default worker configurations with improved intervals (P0 fix: map 5min -> 15min)
|
|
@@ -40,19 +41,35 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
40
41
|
// Headless execution support
|
|
41
42
|
headlessExecutor = null;
|
|
42
43
|
headlessAvailable = false;
|
|
44
|
+
// Preserve the original constructor config so we can detect explicit overrides
|
|
45
|
+
// during state restoration (R1: constructor config takes priority over stale state)
|
|
46
|
+
originalConfig;
|
|
43
47
|
constructor(projectRoot, config) {
|
|
44
48
|
super();
|
|
45
49
|
this.projectRoot = projectRoot;
|
|
50
|
+
this.originalConfig = config;
|
|
46
51
|
const claudeFlowDir = join(projectRoot, '.claude-flow');
|
|
52
|
+
// Read daemon config from .claude-flow/config.json (Layer B)
|
|
53
|
+
const fileConfig = this.readDaemonConfigFromFile(claudeFlowDir);
|
|
54
|
+
// CPU-proportional smart default instead of hardcoded 2.0
|
|
55
|
+
const cpuCount = WorkerDaemon.getEffectiveCpuCount();
|
|
56
|
+
const smartMaxCpuLoad = Math.max(cpuCount * 0.8, 2.0); // Floor of 2.0 for single-CPU machines
|
|
57
|
+
// Platform-aware default: macOS os.freemem() excludes reclaimable file cache,
|
|
58
|
+
// so reported "free" is much lower than actually available memory.
|
|
59
|
+
// Linux reports available memory (including reclaimable cache) more accurately.
|
|
60
|
+
const defaultMinFreeMemory = process.platform === 'darwin' ? 5 : 10;
|
|
61
|
+
// Priority: constructor arg > config.json > smart default
|
|
62
|
+
// For resourceThresholds, merge field-by-field so partial overrides
|
|
63
|
+
// (e.g. only --max-cpu-load) still pick up defaults for other fields.
|
|
47
64
|
this.config = {
|
|
48
|
-
autoStart: config?.autoStart ??
|
|
65
|
+
autoStart: config?.autoStart ?? fileConfig.autoStart ?? false,
|
|
49
66
|
logDir: config?.logDir ?? join(claudeFlowDir, 'logs'),
|
|
50
67
|
stateFile: config?.stateFile ?? join(claudeFlowDir, 'daemon-state.json'),
|
|
51
|
-
maxConcurrent: config?.maxConcurrent ?? 2,
|
|
52
|
-
workerTimeoutMs: config?.workerTimeoutMs ?? DEFAULT_WORKER_TIMEOUT_MS,
|
|
53
|
-
resourceThresholds:
|
|
54
|
-
maxCpuLoad:
|
|
55
|
-
minFreeMemoryPercent:
|
|
68
|
+
maxConcurrent: config?.maxConcurrent ?? fileConfig.maxConcurrent ?? 2,
|
|
69
|
+
workerTimeoutMs: config?.workerTimeoutMs ?? fileConfig.workerTimeoutMs ?? DEFAULT_WORKER_TIMEOUT_MS,
|
|
70
|
+
resourceThresholds: {
|
|
71
|
+
maxCpuLoad: config?.resourceThresholds?.maxCpuLoad ?? fileConfig.maxCpuLoad ?? smartMaxCpuLoad,
|
|
72
|
+
minFreeMemoryPercent: config?.resourceThresholds?.minFreeMemoryPercent ?? fileConfig.minFreeMemoryPercent ?? defaultMinFreeMemory,
|
|
56
73
|
},
|
|
57
74
|
workers: config?.workers ?? DEFAULT_WORKERS,
|
|
58
75
|
};
|
|
@@ -118,6 +135,66 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
118
135
|
getHeadlessExecutor() {
|
|
119
136
|
return this.headlessExecutor;
|
|
120
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Detect effective CPU count for the current environment.
|
|
140
|
+
*
|
|
141
|
+
* Inside Docker / K8s containers, os.cpus().length reports the HOST cpu
|
|
142
|
+
* count, not the container limit (Node.js #28762 — wontfix). We read
|
|
143
|
+
* cgroup v2 / v1 quota files first so the maxCpuLoad threshold stays
|
|
144
|
+
* meaningful under resource-limited containers.
|
|
145
|
+
*/
|
|
146
|
+
static getEffectiveCpuCount() {
|
|
147
|
+
// 1. Try cgroup v2: /sys/fs/cgroup/cpu.max
|
|
148
|
+
try {
|
|
149
|
+
const cpuMax = readFileSync('/sys/fs/cgroup/cpu.max', 'utf8').trim();
|
|
150
|
+
const [quotaStr, periodStr] = cpuMax.split(' ');
|
|
151
|
+
if (quotaStr !== 'max') {
|
|
152
|
+
const quota = parseInt(quotaStr, 10);
|
|
153
|
+
const period = parseInt(periodStr, 10);
|
|
154
|
+
if (quota > 0 && period > 0)
|
|
155
|
+
return Math.ceil(quota / period);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch { /* not in cgroup v2 */ }
|
|
159
|
+
// 2. Try cgroup v1: /sys/fs/cgroup/cpu/cpu.cfs_quota_us
|
|
160
|
+
try {
|
|
161
|
+
const quota = parseInt(readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_quota_us', 'utf8').trim(), 10);
|
|
162
|
+
const period = parseInt(readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_period_us', 'utf8').trim(), 10);
|
|
163
|
+
if (quota > 0 && period > 0)
|
|
164
|
+
return Math.ceil(quota / period);
|
|
165
|
+
}
|
|
166
|
+
catch { /* not in cgroup v1 */ }
|
|
167
|
+
// 3. Fallback to os.cpus().length
|
|
168
|
+
return cpus().length || 1;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Read daemon-specific config from .claude-flow/config.json
|
|
172
|
+
* Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'
|
|
173
|
+
*/
|
|
174
|
+
readDaemonConfigFromFile(claudeFlowDir) {
|
|
175
|
+
const configPath = join(claudeFlowDir, 'config.json');
|
|
176
|
+
if (!existsSync(configPath))
|
|
177
|
+
return {};
|
|
178
|
+
try {
|
|
179
|
+
const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
180
|
+
// Support both flat keys at root and nested under scopes.project
|
|
181
|
+
const cfg = raw?.scopes?.project ?? raw;
|
|
182
|
+
const rawCpuLoad = cfg['daemon.resourceThresholds.maxCpuLoad'] ?? raw['daemon.resourceThresholds.maxCpuLoad'];
|
|
183
|
+
const rawMinMem = cfg['daemon.resourceThresholds.minFreeMemoryPercent'] ?? raw['daemon.resourceThresholds.minFreeMemoryPercent'];
|
|
184
|
+
const rawMaxConcurrent = cfg['daemon.maxConcurrent'] ?? raw['daemon.maxConcurrent'];
|
|
185
|
+
const rawTimeout = cfg['daemon.workerTimeoutMs'] ?? raw['daemon.workerTimeoutMs'];
|
|
186
|
+
return {
|
|
187
|
+
autoStart: typeof raw['daemon.autoStart'] === 'boolean' ? raw['daemon.autoStart'] : undefined,
|
|
188
|
+
maxConcurrent: (typeof rawMaxConcurrent === 'number' && rawMaxConcurrent > 0) ? rawMaxConcurrent : undefined,
|
|
189
|
+
workerTimeoutMs: (typeof rawTimeout === 'number' && rawTimeout > 0) ? rawTimeout : undefined,
|
|
190
|
+
maxCpuLoad: (typeof rawCpuLoad === 'number' && rawCpuLoad > 0 && rawCpuLoad < 1000) ? rawCpuLoad : undefined,
|
|
191
|
+
minFreeMemoryPercent: (typeof rawMinMem === 'number' && rawMinMem >= 0 && rawMinMem <= 100) ? rawMinMem : undefined,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return {};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
121
198
|
/**
|
|
122
199
|
* Setup graceful shutdown handlers
|
|
123
200
|
*/
|
|
@@ -150,13 +227,30 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
150
227
|
}
|
|
151
228
|
/**
|
|
152
229
|
* Process pending workers queue
|
|
230
|
+
*
|
|
231
|
+
* When executeWorkerWithConcurrencyControl defers a worker (returns null),
|
|
232
|
+
* we break immediately to avoid a busy-wait loop — the deferred worker is
|
|
233
|
+
* already back on the pendingWorkers queue by that point. If no workers are
|
|
234
|
+
* currently running when we break, we schedule a backoff retry so the queue
|
|
235
|
+
* does not get permanently stuck.
|
|
153
236
|
*/
|
|
154
237
|
async processPendingWorkers() {
|
|
155
238
|
while (this.pendingWorkers.length > 0 && this.runningWorkers.size < this.config.maxConcurrent) {
|
|
156
239
|
const workerType = this.pendingWorkers.shift();
|
|
157
240
|
const workerConfig = this.config.workers.find(w => w.type === workerType);
|
|
158
241
|
if (workerConfig) {
|
|
159
|
-
await this.executeWorkerWithConcurrencyControl(workerConfig);
|
|
242
|
+
const result = await this.executeWorkerWithConcurrencyControl(workerConfig);
|
|
243
|
+
if (result === null) {
|
|
244
|
+
// Worker was deferred (resource pressure or concurrency limit).
|
|
245
|
+
// Break to avoid tight-looping — the next executeWorker() completion
|
|
246
|
+
// will call processPendingWorkers() again via the finally block.
|
|
247
|
+
if (this.runningWorkers.size === 0) {
|
|
248
|
+
// No workers running means nobody will trigger the finally-block
|
|
249
|
+
// callback, so schedule a backoff retry to avoid a stuck queue.
|
|
250
|
+
setTimeout(() => this.processPendingWorkers(), 30_000).unref();
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
160
254
|
}
|
|
161
255
|
}
|
|
162
256
|
}
|
|
@@ -175,6 +269,23 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
175
269
|
}
|
|
176
270
|
}
|
|
177
271
|
}
|
|
272
|
+
// Restore resourceThresholds, maxConcurrent, workerTimeoutMs from saved state
|
|
273
|
+
// Only restore if valid numeric values within sane ranges
|
|
274
|
+
if (saved.config?.resourceThresholds && !this.originalConfig?.resourceThresholds) {
|
|
275
|
+
const rt = saved.config.resourceThresholds;
|
|
276
|
+
if (typeof rt.maxCpuLoad === 'number' && rt.maxCpuLoad > 0 && rt.maxCpuLoad < 1000) {
|
|
277
|
+
this.config.resourceThresholds.maxCpuLoad = rt.maxCpuLoad;
|
|
278
|
+
}
|
|
279
|
+
if (typeof rt.minFreeMemoryPercent === 'number' && rt.minFreeMemoryPercent >= 0 && rt.minFreeMemoryPercent <= 100) {
|
|
280
|
+
this.config.resourceThresholds.minFreeMemoryPercent = rt.minFreeMemoryPercent;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (typeof saved.config?.maxConcurrent === 'number' && saved.config.maxConcurrent > 0) {
|
|
284
|
+
this.config.maxConcurrent = saved.config.maxConcurrent;
|
|
285
|
+
}
|
|
286
|
+
if (typeof saved.config?.workerTimeoutMs === 'number' && saved.config.workerTimeoutMs > 0) {
|
|
287
|
+
this.config.workerTimeoutMs = saved.config.workerTimeoutMs;
|
|
288
|
+
}
|
|
178
289
|
// Restore worker runtime states (runCount, successCount, etc.)
|
|
179
290
|
if (saved.workers) {
|
|
180
291
|
for (const [type, state] of Object.entries(saved.workers)) {
|
|
@@ -228,7 +339,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
228
339
|
}
|
|
229
340
|
// Save state
|
|
230
341
|
this.saveState();
|
|
231
|
-
this.log('info', `Daemon started
|
|
342
|
+
this.log('info', `Daemon started (PID: ${process.pid}, CPUs: ${cpus().length}, workers: ${this.config.workers.filter(w => w.enabled).length}, maxCpuLoad: ${this.config.resourceThresholds.maxCpuLoad}, minFreeMemoryPercent: ${this.config.resourceThresholds.minFreeMemoryPercent}%)`);
|
|
232
343
|
}
|
|
233
344
|
/**
|
|
234
345
|
* Stop the daemon and all workers
|
|
@@ -755,9 +866,9 @@ let daemonInstance = null;
|
|
|
755
866
|
/**
|
|
756
867
|
* Get or create daemon instance
|
|
757
868
|
*/
|
|
758
|
-
export function getDaemon(projectRoot) {
|
|
869
|
+
export function getDaemon(projectRoot, config) {
|
|
759
870
|
if (!daemonInstance && projectRoot) {
|
|
760
|
-
daemonInstance = new WorkerDaemon(projectRoot);
|
|
871
|
+
daemonInstance = new WorkerDaemon(projectRoot, config);
|
|
761
872
|
}
|
|
762
873
|
if (!daemonInstance) {
|
|
763
874
|
throw new Error('Daemon not initialized. Provide projectRoot on first call.');
|
|
@@ -767,8 +878,8 @@ export function getDaemon(projectRoot) {
|
|
|
767
878
|
/**
|
|
768
879
|
* Start daemon (for use in session-start hook)
|
|
769
880
|
*/
|
|
770
|
-
export async function startDaemon(projectRoot) {
|
|
771
|
-
const daemon = getDaemon(projectRoot);
|
|
881
|
+
export async function startDaemon(projectRoot, config) {
|
|
882
|
+
const daemon = getDaemon(projectRoot, config);
|
|
772
883
|
await daemon.start();
|
|
773
884
|
return daemon;
|
|
774
885
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.24",
|
|
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",
|