@claude-flow/cli 3.44.0 → 3.46.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 +2 -2
- package/README.md +2 -0
- package/bin/cli.js +13 -0
- package/bin/mcp-server.js +16 -0
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/doctor.d.ts +12 -0
- package/dist/src/commands/doctor.js +200 -3
- package/dist/src/commands/embeddings.js +50 -7
- package/dist/src/commands/init.js +5 -2
- package/dist/src/commands/mcp.js +27 -4
- package/dist/src/mcp-server.d.ts +50 -6
- package/dist/src/mcp-server.js +267 -33
- package/dist/src/mcp-tools/agent-execute-core.js +7 -0
- package/dist/src/mcp-tools/agentdb-tools.js +20 -8
- package/dist/src/mcp-tools/browser-tools.js +30 -23
- package/dist/src/mcp-tools/memory-tools.js +9 -4
- package/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/dist/src/mcp-tools/policy-enforcer.d.ts +4 -4
- package/dist/src/mcp-tools/policy-enforcer.js +4 -4
- package/dist/src/mcp-tools/terminal-tools.js +11 -1
- package/dist/src/mcp-tools/workflow-tools.js +1 -1
- package/dist/src/memory/ewc-consolidation.d.ts +16 -1
- package/dist/src/memory/ewc-consolidation.js +63 -13
- package/dist/src/memory/intelligence.d.ts +6 -2
- package/dist/src/memory/intelligence.js +18 -11
- package/dist/src/memory/memory-bridge.js +84 -0
- package/dist/src/ruvector/model-router.js +19 -1
- package/dist/src/services/distill-oracle.js +2 -1
- package/dist/src/services/git-workspace-identity.js +5 -8
- package/dist/src/services/policy-runtime.js +114 -4
- package/dist/src/services/worker-daemon.js +8 -3
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js +2 -6
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +14 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts +40 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts.map +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js +100 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js.map +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -0
- package/node_modules/@claude-flow/security/dist/safe-git.d.ts +22 -0
- package/node_modules/@claude-flow/security/dist/safe-git.d.ts.map +1 -0
- package/node_modules/@claude-flow/security/dist/safe-git.js +63 -0
- package/node_modules/@claude-flow/security/dist/safe-git.js.map +1 -0
- package/package.json +5 -5
- package/plugins/ruflo-metaharness/agents/metaharness-architect.md +1 -1
- package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +1 -1
- package/plugins/ruflo-metaharness/scripts/_darwin.mjs +84 -12
- package/plugins/ruflo-metaharness/scripts/_harness.mjs +10 -4
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +120 -6
- package/plugins/ruflo-metaharness/scripts/_redblue.mjs +33 -6
- package/plugins/ruflo-metaharness/scripts/audit-list.mjs +11 -10
- package/plugins/ruflo-metaharness/scripts/audit-trend.mjs +8 -7
- package/plugins/ruflo-metaharness/scripts/drift-from-history.mjs +2 -3
- package/plugins/ruflo-metaharness/scripts/oia-audit.mjs +8 -7
- package/plugins/ruflo-metaharness/scripts/similarity.mjs +9 -8
- package/plugins/ruflo-metaharness/scripts/smoke.sh +31 -0
- package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +319 -0
- package/plugins/ruflo-metaharness/scripts/test-pin-alignment.mjs +182 -0
- package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +4 -2
- package/plugins/ruflo-metaharness/skills/harness-gepa/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-security-bench/SKILL.md +3 -1
- package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.46.0",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
|
|
6
6
|
"hook-handler.cjs": "209d9fafe10e17d1be0866727f6f9cf9ac66f9a0793f1c793a4f58319e8e4583",
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"router.js": "b6998397e7883191b62229ccdc66fb87f1ccca1d5b2039ecb8c1e686d1ad81f8"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
-
"signature": "
|
|
12
|
+
"signature": "LwKXka3qAKGSIVpcv4vpg5vlv8CFHolvyCQwzW0hNvPIn7Un3+qpidc7drdoDzsrNyzx3gnZJBFEWeXxlTabDA==",
|
|
13
13
|
"algorithm": "ed25519"
|
|
14
14
|
}
|
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
# Ruflo
|
|
21
21
|
|
|
22
|
+
[English](README.md) · [简体中文](README.zh-CN.md)
|
|
23
|
+
|
|
22
24
|
**An agent meta-harness for Claude Code and Codex.**
|
|
23
25
|
|
|
24
26
|
[](docs/ruflo-explained.md)
|
package/bin/cli.js
CHANGED
|
@@ -164,6 +164,8 @@ const isMCPMode = !process.stdin.isTTY
|
|
|
164
164
|
if (isMCPMode) {
|
|
165
165
|
// Run MCP server mode
|
|
166
166
|
const { listMCPTools, callMCPTool, hasTool } = await import('../dist/src/mcp-client.js');
|
|
167
|
+
const { isPolicyEnforcementEnabled, loadMcpPolicy, evaluateToolCall } =
|
|
168
|
+
await import('../dist/src/mcp-tools/policy-enforcer.js');
|
|
167
169
|
|
|
168
170
|
const VERSION = '3.0.0';
|
|
169
171
|
const sessionId = `mcp-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
@@ -287,6 +289,17 @@ if (isMCPMode) {
|
|
|
287
289
|
};
|
|
288
290
|
}
|
|
289
291
|
|
|
292
|
+
if (isPolicyEnforcementEnabled()) {
|
|
293
|
+
const check = evaluateToolCall(loadMcpPolicy(), sessionId, toolName);
|
|
294
|
+
if (!check.allowed) {
|
|
295
|
+
return {
|
|
296
|
+
jsonrpc: '2.0',
|
|
297
|
+
id: message.id,
|
|
298
|
+
error: { code: -32001, message: `Policy denied: ${check.reason}` },
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
290
303
|
try {
|
|
291
304
|
const result = await callMCPTool(toolName, toolParams, { sessionId });
|
|
292
305
|
return {
|
package/bin/mcp-server.js
CHANGED
|
@@ -28,6 +28,11 @@ console.log = (...args) => {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
import { listMCPTools, callMCPTool, hasTool } from '../dist/src/mcp-client.js';
|
|
31
|
+
import {
|
|
32
|
+
isPolicyEnforcementEnabled,
|
|
33
|
+
loadMcpPolicy,
|
|
34
|
+
evaluateToolCall,
|
|
35
|
+
} from '../dist/src/mcp-tools/policy-enforcer.js';
|
|
31
36
|
|
|
32
37
|
const VERSION = '3.0.0';
|
|
33
38
|
const sessionId = `mcp-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
@@ -173,6 +178,17 @@ async function handleMessage(message) {
|
|
|
173
178
|
};
|
|
174
179
|
}
|
|
175
180
|
|
|
181
|
+
if (isPolicyEnforcementEnabled()) {
|
|
182
|
+
const check = evaluateToolCall(loadMcpPolicy(), sessionId, toolName);
|
|
183
|
+
if (!check.allowed) {
|
|
184
|
+
return {
|
|
185
|
+
jsonrpc: '2.0',
|
|
186
|
+
id: message.id,
|
|
187
|
+
error: { code: -32001, message: `Policy denied: ${check.reason}` },
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
176
192
|
try {
|
|
177
193
|
const result = await callMCPTool(toolName, toolParams, { sessionId });
|
|
178
194
|
return {
|
package/catalog-manifest.json
CHANGED
|
@@ -11,7 +11,19 @@ interface HealthCheck {
|
|
|
11
11
|
message: string;
|
|
12
12
|
fix?: string;
|
|
13
13
|
}
|
|
14
|
+
/** ADR-122 Phase 0: probe only the CLI version, without launching a browser. */
|
|
15
|
+
export declare function evaluateAgentBrowserVersion(rawOutput: string): HealthCheck;
|
|
16
|
+
export declare function checkAgentBrowserVersion(probe?: () => Promise<string>): Promise<HealthCheck>;
|
|
14
17
|
export declare function checkMemoryPersistenceDriver(): Promise<HealthCheck>;
|
|
18
|
+
/**
|
|
19
|
+
* #3392: pure verdict for "does the @claude-flow/memory the CLI loads satisfy
|
|
20
|
+
* the range the CLI declares?". `npx @claude-flow/cli@latest` reuses one npx
|
|
21
|
+
* cache directory across CLI versions, and npm keeps an already-installed
|
|
22
|
+
* dependency that still satisfies a caret range, so a stale memory could
|
|
23
|
+
* survive a CLI upgrade with no error. Exported for unit testing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function evaluateMemoryPackageVersion(declared: string | null, installed: string | null): HealthCheck;
|
|
26
|
+
export declare function checkMemoryPackageVersion(): Promise<HealthCheck>;
|
|
15
27
|
export declare const doctorCommand: Command;
|
|
16
28
|
export default doctorCommand;
|
|
17
29
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -9,13 +9,15 @@ import { existsSync, readFileSync, statSync, openSync, readSync, closeSync } fro
|
|
|
9
9
|
import { join, dirname } from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
|
-
import { execSync, exec } from 'child_process';
|
|
12
|
+
import { execSync, exec, execFile } from 'child_process';
|
|
13
13
|
import { promisify } from 'util';
|
|
14
14
|
import { decodeKey, isEncryptionEnabled } from '../encryption/vault.js';
|
|
15
15
|
import { isEncryptedBlob } from '../encryption/vault.js';
|
|
16
|
-
import
|
|
16
|
+
import * as semver from 'semver';
|
|
17
|
+
import { resolveMemoryPackageFromProject, resolveMemoryPackageFromCli, readMemoryPackageVersion, recordMemoryPackagePath, } from '../init/memory-package-resolver.js';
|
|
17
18
|
// Promisified exec with proper shell and env inheritance for cross-platform support
|
|
18
19
|
const execAsync = promisify(exec);
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
19
21
|
/**
|
|
20
22
|
* Execute command asynchronously with proper environment inheritance
|
|
21
23
|
* Critical for Windows where PATH may not be inherited properly
|
|
@@ -61,6 +63,39 @@ async function checkNpmVersion() {
|
|
|
61
63
|
return { name: 'npm Version', status: 'fail', message: 'npm not found', fix: 'Install Node.js from https://nodejs.org' };
|
|
62
64
|
}
|
|
63
65
|
}
|
|
66
|
+
/** ADR-122 Phase 0: probe only the CLI version, without launching a browser. */
|
|
67
|
+
export function evaluateAgentBrowserVersion(rawOutput) {
|
|
68
|
+
const name = 'agent-browser CLI (ADR-122)';
|
|
69
|
+
const fix = 'npm install -g agent-browser@latest';
|
|
70
|
+
const versionOutput = rawOutput.trim().replace(/\x1b\[[0-9;]*m/g, '');
|
|
71
|
+
const match = /^(?:agent-browser(?:\s+version)?\s+)?v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\s|$)/i.exec(versionOutput);
|
|
72
|
+
const version = match ? semver.parse(match[1]) : null;
|
|
73
|
+
if (!version) {
|
|
74
|
+
return { name, status: 'warn', message: `Unable to parse agent-browser version${versionOutput ? `: ${versionOutput.slice(0, 120)}` : ' (empty output)'}. Browser MCP tools may be unavailable.`, fix };
|
|
75
|
+
}
|
|
76
|
+
if (semver.lt(version, '0.27.0')) {
|
|
77
|
+
return { name, status: 'warn', message: `v${version.version} is below the ADR-122 v0.27.0 minimum for browser MCP tools`, fix };
|
|
78
|
+
}
|
|
79
|
+
return { name, status: 'pass', message: `v${version.version} meets the ADR-122 v0.27.0 minimum for browser MCP tools` };
|
|
80
|
+
}
|
|
81
|
+
export async function checkAgentBrowserVersion(probe = async () => {
|
|
82
|
+
const { stdout } = await execFileAsync('agent-browser', ['--version'], {
|
|
83
|
+
encoding: 'utf8', timeout: 3000, maxBuffer: 16 * 1024, windowsHide: true,
|
|
84
|
+
});
|
|
85
|
+
return String(stdout);
|
|
86
|
+
}) {
|
|
87
|
+
try {
|
|
88
|
+
return evaluateAgentBrowserVersion(await probe());
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const name = 'agent-browser CLI (ADR-122)';
|
|
92
|
+
const fix = 'npm install -g agent-browser@latest';
|
|
93
|
+
const code = error.code;
|
|
94
|
+
return code === 'ENOENT'
|
|
95
|
+
? { name, status: 'warn', message: 'Not found on PATH; @claude-flow/browser and browser MCP tools need agent-browser v0.27.0 or newer', fix }
|
|
96
|
+
: { name, status: 'warn', message: `Version check failed: ${error instanceof Error ? error.message : String(error)}`, fix };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
64
99
|
// Check config file
|
|
65
100
|
async function checkConfigFile() {
|
|
66
101
|
// JSON configs (parse-validated). The first three are LEGACY shapes from
|
|
@@ -305,6 +340,22 @@ async function resolveMemoryDbPath() {
|
|
|
305
340
|
return p;
|
|
306
341
|
return null;
|
|
307
342
|
}
|
|
343
|
+
/** Native AgentDB writes to a plaintext sibling of the sql.js memory file.
|
|
344
|
+
* Probe only stores that already exist: projects without the native bridge
|
|
345
|
+
* should not acquire an extra warning just by running doctor (#3195). */
|
|
346
|
+
async function existingNativeAgentDbPaths() {
|
|
347
|
+
const candidates = new Set();
|
|
348
|
+
try {
|
|
349
|
+
const { getMemoryRoot, resolveDbPath } = await import('../memory/memory-initializer.js');
|
|
350
|
+
candidates.add(join(getMemoryRoot(), 'agentdb-memory.db'));
|
|
351
|
+
// Explicit CLAUDE_FLOW_DB_PATH callers may put the sibling elsewhere.
|
|
352
|
+
candidates.add(join(dirname(resolveDbPath()), 'agentdb-memory.db'));
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
candidates.add(join(process.cwd(), '.swarm', 'agentdb-memory.db'));
|
|
356
|
+
}
|
|
357
|
+
return [...candidates].filter((path) => existsSync(path));
|
|
358
|
+
}
|
|
308
359
|
/** Open a sql.js Database over an on-disk file, returning null when the
|
|
309
360
|
* file can't be opened as a SQLite database (encrypted / corrupted / not
|
|
310
361
|
* a database). Callers decide whether that's warn or fail. */
|
|
@@ -506,6 +557,79 @@ async function checkMemoryStructuralIntegrity() {
|
|
|
506
557
|
catch { /* best-effort */ }
|
|
507
558
|
}
|
|
508
559
|
}
|
|
560
|
+
/** Check the AgentDB authority independently from the legacy memory.db.
|
|
561
|
+
* Opening read-only with SQLite keeps WAL commits visible without mutating
|
|
562
|
+
* either store; the existing legacy health checks retain their own scope. */
|
|
563
|
+
async function checkNativeAgentDbStructuralIntegrity(dbPath) {
|
|
564
|
+
const NAME = 'Native AgentDB Structural Integrity (quick_check)';
|
|
565
|
+
if (!existsSync(dbPath)) {
|
|
566
|
+
return { name: NAME, status: 'warn', message: `${dbPath} disappeared before the native check could run` };
|
|
567
|
+
}
|
|
568
|
+
if (isMemoryDbEncryptedAtRest(dbPath)) {
|
|
569
|
+
return {
|
|
570
|
+
name: NAME,
|
|
571
|
+
status: 'fail',
|
|
572
|
+
message: `${dbPath} — RFE1-encrypted; native AgentDB requires a plaintext SQLite file`,
|
|
573
|
+
fix: `back up ${dbPath} and restore a usable native AgentDB database`,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
let Database;
|
|
577
|
+
try {
|
|
578
|
+
Database = (await import('better-sqlite3')).default;
|
|
579
|
+
}
|
|
580
|
+
catch {
|
|
581
|
+
return {
|
|
582
|
+
name: NAME,
|
|
583
|
+
status: 'warn',
|
|
584
|
+
message: `${dbPath} — better-sqlite3 not installed; native AgentDB structural health is unverified`,
|
|
585
|
+
fix: 'install better-sqlite3 with npm install scripts enabled, then rerun this check',
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
let db;
|
|
589
|
+
try {
|
|
590
|
+
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
if (isNativeSqliteBindingUnavailable(error)) {
|
|
594
|
+
return nativeBindingUnavailableCheck(NAME, dbPath, error, '[native AgentDB structural health unverified]');
|
|
595
|
+
}
|
|
596
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
597
|
+
return {
|
|
598
|
+
name: NAME,
|
|
599
|
+
status: 'fail',
|
|
600
|
+
message: `${dbPath} — better-sqlite3 failed to open: ${message} [native AgentDB]`,
|
|
601
|
+
fix: `back up ${dbPath} and its WAL sidecars, then restore a known-good native AgentDB database`,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
const rows = db.pragma('quick_check');
|
|
606
|
+
const values = rows.map((row) => String(Object.values(row)[0]));
|
|
607
|
+
if (values.length === 1 && values[0] === 'ok') {
|
|
608
|
+
return { name: NAME, status: 'pass', message: `${dbPath} — PRAGMA quick_check: ok [native AgentDB, WAL-aware]` };
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
name: NAME,
|
|
612
|
+
status: 'fail',
|
|
613
|
+
message: `${dbPath} — PRAGMA quick_check: ${values.slice(0, 3).join('; ')}${values.length > 3 ? ` (+${values.length - 3} more)` : ''} [native AgentDB]`,
|
|
614
|
+
fix: `back up ${dbPath} and its WAL sidecars, then restore a known-good native AgentDB database`,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
619
|
+
return {
|
|
620
|
+
name: NAME,
|
|
621
|
+
status: 'fail',
|
|
622
|
+
message: `${dbPath} — quick_check probe threw: ${message} [native AgentDB]`,
|
|
623
|
+
fix: `back up ${dbPath} and its WAL sidecars, then restore a known-good native AgentDB database`,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
627
|
+
try {
|
|
628
|
+
db.close();
|
|
629
|
+
}
|
|
630
|
+
catch { /* best-effort */ }
|
|
631
|
+
}
|
|
632
|
+
}
|
|
509
633
|
// #2968/#3321 — read-only native SQLite capability probe. A skipped
|
|
510
634
|
// postinstall can leave the wrapper importable but its binding unavailable.
|
|
511
635
|
// Schema size cannot identify the runtime driver or the database's history:
|
|
@@ -1045,6 +1169,70 @@ async function checkLearningBridge() {
|
|
|
1045
1169
|
fix: 'npm i -D @claude-flow/memory (optional dep appears absent — likely --omit=optional install)',
|
|
1046
1170
|
};
|
|
1047
1171
|
}
|
|
1172
|
+
/**
|
|
1173
|
+
* #3392: pure verdict for "does the @claude-flow/memory the CLI loads satisfy
|
|
1174
|
+
* the range the CLI declares?". `npx @claude-flow/cli@latest` reuses one npx
|
|
1175
|
+
* cache directory across CLI versions, and npm keeps an already-installed
|
|
1176
|
+
* dependency that still satisfies a caret range, so a stale memory could
|
|
1177
|
+
* survive a CLI upgrade with no error. Exported for unit testing.
|
|
1178
|
+
*/
|
|
1179
|
+
export function evaluateMemoryPackageVersion(declared, installed) {
|
|
1180
|
+
const NAME = '@claude-flow/memory version';
|
|
1181
|
+
if (!declared) {
|
|
1182
|
+
return { name: NAME, status: 'warn', message: 'could not read the @claude-flow/memory range declared by @claude-flow/cli' };
|
|
1183
|
+
}
|
|
1184
|
+
if (!installed) {
|
|
1185
|
+
return {
|
|
1186
|
+
name: NAME,
|
|
1187
|
+
status: 'warn',
|
|
1188
|
+
message: `@claude-flow/memory is not resolvable from the CLI (declared ${declared}) — memory features fall back to degraded paths`,
|
|
1189
|
+
fix: `npm install @claude-flow/memory@${declared} --include=optional`,
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
if (!semver.validRange(declared) || !semver.valid(installed)) {
|
|
1193
|
+
return { name: NAME, status: 'warn', message: `cannot compare installed ${installed} against declared ${declared}` };
|
|
1194
|
+
}
|
|
1195
|
+
if (semver.satisfies(installed, declared, { includePrerelease: true })) {
|
|
1196
|
+
return { name: NAME, status: 'pass', message: `v${installed} satisfies declared ${declared}` };
|
|
1197
|
+
}
|
|
1198
|
+
// warn, not fail: a dev/hoisted layout can legitimately differ, and doctor's
|
|
1199
|
+
// exit code must not depend on which copy a package manager happened to hoist.
|
|
1200
|
+
return {
|
|
1201
|
+
name: NAME,
|
|
1202
|
+
status: 'warn',
|
|
1203
|
+
message: `installed v${installed} does not satisfy declared ${declared} — a stale cached copy is running, so fixes shipped in a newer @claude-flow/memory are silently absent`,
|
|
1204
|
+
fix: `rm -rf "$(npm config get cache)/_npx" && npx @claude-flow/cli@latest doctor # or: npm install @claude-flow/memory@${declared}`,
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
export async function checkMemoryPackageVersion() {
|
|
1208
|
+
try {
|
|
1209
|
+
// Walk up from this module to the CLI package root (npx cache, global,
|
|
1210
|
+
// project-local and monorepo dev all resolve the same way).
|
|
1211
|
+
let declared = null;
|
|
1212
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
1213
|
+
for (let i = 0; i < 8 && declared === null; i++) {
|
|
1214
|
+
const pj = join(dir, 'package.json');
|
|
1215
|
+
if (existsSync(pj)) {
|
|
1216
|
+
try {
|
|
1217
|
+
const pkg = JSON.parse(readFileSync(pj, 'utf-8'));
|
|
1218
|
+
if (pkg.name === '@claude-flow/cli') {
|
|
1219
|
+
declared = pkg.optionalDependencies?.['@claude-flow/memory'] ?? pkg.dependencies?.['@claude-flow/memory'] ?? null;
|
|
1220
|
+
break;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
catch { /* keep walking */ }
|
|
1224
|
+
}
|
|
1225
|
+
dir = dirname(dir);
|
|
1226
|
+
}
|
|
1227
|
+
// Resolve exactly as the CLI's own runtime does (its module context),
|
|
1228
|
+
// not from process.cwd() — that would report the project's copy instead.
|
|
1229
|
+
const distPath = resolveMemoryPackageFromCli();
|
|
1230
|
+
return evaluateMemoryPackageVersion(declared, distPath ? readMemoryPackageVersion(distPath) : null);
|
|
1231
|
+
}
|
|
1232
|
+
catch (err) {
|
|
1233
|
+
return { name: '@claude-flow/memory version', status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1048
1236
|
// Check API keys
|
|
1049
1237
|
async function checkApiKeys() {
|
|
1050
1238
|
const keys = ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY'];
|
|
@@ -2272,7 +2460,7 @@ export const doctorCommand = {
|
|
|
2272
2460
|
{
|
|
2273
2461
|
name: 'component',
|
|
2274
2462
|
short: 'c',
|
|
2275
|
-
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, metaharness)',
|
|
2463
|
+
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, browser, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, metaharness)',
|
|
2276
2464
|
type: 'string'
|
|
2277
2465
|
},
|
|
2278
2466
|
{
|
|
@@ -2379,11 +2567,14 @@ export const doctorCommand = {
|
|
|
2379
2567
|
output.writeln(output.dim('System diagnostics and health check'));
|
|
2380
2568
|
output.writeln(output.dim('─'.repeat(50)));
|
|
2381
2569
|
output.writeln();
|
|
2570
|
+
const nativeAgentDbChecks = (await existingNativeAgentDbPaths())
|
|
2571
|
+
.map((dbPath) => () => checkNativeAgentDbStructuralIntegrity(dbPath));
|
|
2382
2572
|
const allChecks = [
|
|
2383
2573
|
checkVersionFreshness,
|
|
2384
2574
|
checkNodeVersion,
|
|
2385
2575
|
checkNpmVersion,
|
|
2386
2576
|
checkClaudeCode,
|
|
2577
|
+
checkAgentBrowserVersion, // ADR-122 Phase 0 — browser runtime compatibility
|
|
2387
2578
|
checkGit,
|
|
2388
2579
|
checkGitRepo,
|
|
2389
2580
|
checkConfigFile,
|
|
@@ -2391,8 +2582,10 @@ export const doctorCommand = {
|
|
|
2391
2582
|
checkDaemonStatus,
|
|
2392
2583
|
checkMemoryDatabase,
|
|
2393
2584
|
checkMemoryStructuralIntegrity, // #2737 — bounded, native quick_check on every default run
|
|
2585
|
+
...nativeAgentDbChecks, // #3195 — verify each present native authority file
|
|
2394
2586
|
checkMemoryPersistenceDriver, // #2968/#3321 — read-only native capability probe
|
|
2395
2587
|
checkLearningBridge, // #2545 — can the auto-memory hook actually load @claude-flow/memory?
|
|
2588
|
+
checkMemoryPackageVersion, // #3392 — loaded @claude-flow/memory must satisfy the CLI's declared range
|
|
2396
2589
|
checkApiKeys,
|
|
2397
2590
|
checkMcpServers,
|
|
2398
2591
|
checkMcpSchemaOverhead, // #2726 — fixed tools/list prompt cost
|
|
@@ -2423,18 +2616,22 @@ export const doctorCommand = {
|
|
|
2423
2616
|
'node': checkNodeVersion,
|
|
2424
2617
|
'npm': checkNpmVersion,
|
|
2425
2618
|
'claude': checkClaudeCode,
|
|
2619
|
+
'browser': checkAgentBrowserVersion,
|
|
2426
2620
|
'config': checkConfigFile,
|
|
2427
2621
|
'stale-settings': checkStaleSettingsNpx, // #2448
|
|
2428
2622
|
'daemon': checkDaemonStatus,
|
|
2429
2623
|
'memory': [
|
|
2430
2624
|
checkMemoryDatabase, // existing: exists + statable (unchanged)
|
|
2431
2625
|
checkMemoryIntegrity, // #2677 check 1: sql.js open + PRAGMA integrity_check
|
|
2626
|
+
...nativeAgentDbChecks, // #3195: present AgentDB authority, checked independently
|
|
2432
2627
|
checkMemoryPersistenceDriver, // #2968/#3321: read-only native capability probe
|
|
2628
|
+
checkMemoryPackageVersion, // #3392: loaded memory package satisfies the declared range
|
|
2433
2629
|
checkMemoryContent, // #2677 check 2: memory_entries content coverage
|
|
2434
2630
|
checkMemoryEmbeddingCoverage, // #2677 check 3: vector coverage on populated rows
|
|
2435
2631
|
checkMemoryReflexionCoverage, // #2677 check 6: episodes are retrievable
|
|
2436
2632
|
checkMemoryCritiqueCoverage, // #2677 check 6: feedback carries lessons
|
|
2437
2633
|
],
|
|
2634
|
+
'memory-package': checkMemoryPackageVersion, // #3392
|
|
2438
2635
|
'learning': checkLearningBridge, // #2545
|
|
2439
2636
|
'learning-bridge': checkLearningBridge, // #2545
|
|
2440
2637
|
'api': checkApiKeys,
|
|
@@ -679,7 +679,12 @@ const initCommand = {
|
|
|
679
679
|
if (!fs.existsSync(modelDir)) {
|
|
680
680
|
fs.mkdirSync(modelDir, { recursive: true });
|
|
681
681
|
}
|
|
682
|
-
// Download model if requested
|
|
682
|
+
// Download model if requested. #3376: record what actually happened
|
|
683
|
+
// instead of assuming the download ran — `getEmbeddings()` returns null
|
|
684
|
+
// whenever the optional @claude-flow/embeddings package is absent, which
|
|
685
|
+
// is every plain `npm i -g ruflo` install.
|
|
686
|
+
let modelDownloaded = false;
|
|
687
|
+
let modelSkipReason = null;
|
|
683
688
|
if (download) {
|
|
684
689
|
spinner.setText(`Downloading ONNX model: ${model}...`);
|
|
685
690
|
const embeddings = await getEmbeddings();
|
|
@@ -687,13 +692,19 @@ const initCommand = {
|
|
|
687
692
|
await embeddings.downloadEmbeddingModel(model, modelDir, (p) => {
|
|
688
693
|
spinner.setText(`Downloading ${model}... ${p.percent.toFixed(0)}%`);
|
|
689
694
|
});
|
|
695
|
+
modelDownloaded = true;
|
|
690
696
|
}
|
|
691
697
|
else {
|
|
692
|
-
//
|
|
693
|
-
|
|
694
|
-
|
|
698
|
+
// No download happened. That is the outcome of this command, not an
|
|
699
|
+
// aside — reported below via spinner.fail + a warning, never as a
|
|
700
|
+
// dimmed note on a success path. No fake progress delay: there is
|
|
701
|
+
// nothing in progress.
|
|
702
|
+
modelSkipReason = '@claude-flow/embeddings is not installed';
|
|
695
703
|
}
|
|
696
704
|
}
|
|
705
|
+
else {
|
|
706
|
+
modelSkipReason = 'download disabled by --no-download';
|
|
707
|
+
}
|
|
697
708
|
// Write embeddings config
|
|
698
709
|
spinner.setText('Writing configuration...');
|
|
699
710
|
const dimension = model.includes('mpnet') ? 768 : 384;
|
|
@@ -702,6 +713,12 @@ const initCommand = {
|
|
|
702
713
|
modelPath: modelDir,
|
|
703
714
|
dimension,
|
|
704
715
|
cacheSize,
|
|
716
|
+
// #3376: additive, optional fields. `modelPath` keeps its old meaning
|
|
717
|
+
// (the directory the model belongs in), but it no longer implies a
|
|
718
|
+
// model is in it — `modelDownloaded` says whether one is. Readers that
|
|
719
|
+
// predate these keys are unaffected; they ignore unknown properties.
|
|
720
|
+
modelDownloaded,
|
|
721
|
+
...(modelSkipReason ? { modelSkipReason } : {}),
|
|
705
722
|
hyperbolic: {
|
|
706
723
|
enabled: hyperbolic,
|
|
707
724
|
curvature,
|
|
@@ -716,7 +733,15 @@ const initCommand = {
|
|
|
716
733
|
initialized: new Date().toISOString(),
|
|
717
734
|
};
|
|
718
735
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
719
|
-
|
|
736
|
+
// A download that was asked for and did not happen is a failed init:
|
|
737
|
+
// the only substantive step of `embeddings init` did not run. #3376.
|
|
738
|
+
const downloadSkipped = download && !modelDownloaded;
|
|
739
|
+
if (downloadSkipped) {
|
|
740
|
+
spinner.fail('Embedding model NOT downloaded — embedding subsystem is not ready');
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
spinner.succeed('Embedding subsystem initialized');
|
|
744
|
+
}
|
|
720
745
|
output.writeln();
|
|
721
746
|
output.printTable({
|
|
722
747
|
columns: [
|
|
@@ -725,6 +750,7 @@ const initCommand = {
|
|
|
725
750
|
],
|
|
726
751
|
data: [
|
|
727
752
|
{ setting: 'Model', value: model },
|
|
753
|
+
{ setting: 'Model Status', value: modelDownloaded ? output.success('Downloaded') : output.error('NOT downloaded') },
|
|
728
754
|
{ setting: 'Dimension', value: String(dimension) },
|
|
729
755
|
{ setting: 'Cache Size', value: String(cacheSize) + ' entries' },
|
|
730
756
|
{ setting: 'Hyperbolic', value: hyperbolic ? `${output.success('Enabled')} (c=${curvature})` : output.dim('Disabled') },
|
|
@@ -733,6 +759,20 @@ const initCommand = {
|
|
|
733
759
|
{ setting: 'Config', value: configPath },
|
|
734
760
|
],
|
|
735
761
|
});
|
|
762
|
+
if (downloadSkipped) {
|
|
763
|
+
output.writeln();
|
|
764
|
+
output.printWarning(`No embedding model was installed: ${modelSkipReason}.`);
|
|
765
|
+
output.printWarning('Until a real embedder is present, embeddings fall back to hash vectors, which carry no semantic meaning.');
|
|
766
|
+
output.printInfo(`Configuration was still written to ${configPath}, recording "modelDownloaded": false.`);
|
|
767
|
+
output.writeln();
|
|
768
|
+
output.writeln(output.dim('To finish initializing:'));
|
|
769
|
+
output.printList([
|
|
770
|
+
'npm install @claude-flow/embeddings - the package this command downloads with; then: embeddings init --force',
|
|
771
|
+
'npm install ruvector - the other real embedder ruflo accepts; needs no download step',
|
|
772
|
+
'embeddings init --no-download --force - keep this configuration without a model (exits 0)',
|
|
773
|
+
]);
|
|
774
|
+
return { success: false, exitCode: 1, data: config };
|
|
775
|
+
}
|
|
736
776
|
output.writeln();
|
|
737
777
|
if (hyperbolic) {
|
|
738
778
|
output.printBox([
|
|
@@ -1182,8 +1222,11 @@ const modelsCommand = {
|
|
|
1182
1222
|
}
|
|
1183
1223
|
}
|
|
1184
1224
|
else {
|
|
1185
|
-
|
|
1186
|
-
|
|
1225
|
+
// #3376, same class as `init`: nothing was downloaded, so this is a
|
|
1226
|
+
// failure, not a success with a caveat. No fake progress delay either.
|
|
1227
|
+
spinner.fail(`Download skipped — @claude-flow/embeddings is not installed`);
|
|
1228
|
+
output.printWarning(`"${download}" was not downloaded. Install @claude-flow/embeddings to enable model downloads.`);
|
|
1229
|
+
return { success: false, exitCode: 1 };
|
|
1187
1230
|
}
|
|
1188
1231
|
return { success: true };
|
|
1189
1232
|
}
|
|
@@ -113,7 +113,9 @@ export function runCodexInitializerCli(cwd, options) {
|
|
|
113
113
|
// init) and scripted `--format json` output.
|
|
114
114
|
async function maybeAutoDetectCodex(ctx, options) {
|
|
115
115
|
try {
|
|
116
|
-
|
|
116
|
+
// The CLI parser turns `--no-codex-detect` into `codexDetect: false`.
|
|
117
|
+
// Retain the literal key for programmatic CommandContext callers.
|
|
118
|
+
if (ctx.flags.codexDetect === false || ctx.flags['no-codex-detect'] === true)
|
|
117
119
|
return;
|
|
118
120
|
if (ctx.flags.format === 'json')
|
|
119
121
|
return; // scripted output stays pure
|
|
@@ -295,7 +297,8 @@ function isBloatedRufloSkillDir(dir) {
|
|
|
295
297
|
}
|
|
296
298
|
async function maybeInstallSkillsSh(ctx) {
|
|
297
299
|
try {
|
|
298
|
-
|
|
300
|
+
// The CLI parser turns `--no-skills-sh` into `skillsSh: false`.
|
|
301
|
+
if (ctx.flags.skillsSh === false || ctx.flags['no-skills-sh'] === true)
|
|
299
302
|
return;
|
|
300
303
|
if (ctx.flags.format === 'json')
|
|
301
304
|
return;
|
package/dist/src/commands/mcp.js
CHANGED
|
@@ -66,6 +66,11 @@ const startCommand = {
|
|
|
66
66
|
description: 'Tools to advertise (comma-separated categories, prefixes, exact names, or "all")',
|
|
67
67
|
type: 'string'
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
name: 'request-timeout-ms',
|
|
71
|
+
description: 'HTTP request timeout in milliseconds (default 30000; env RUFLO_MCP_REQUEST_TIMEOUT_MS)',
|
|
72
|
+
type: 'number'
|
|
73
|
+
},
|
|
69
74
|
{
|
|
70
75
|
name: 'daemon',
|
|
71
76
|
short: 'd',
|
|
@@ -84,6 +89,7 @@ const startCommand = {
|
|
|
84
89
|
examples: [
|
|
85
90
|
{ command: 'claude-flow mcp start', description: 'Start with defaults (stdio)' },
|
|
86
91
|
{ command: 'claude-flow mcp start -p 8080 -t http', description: 'Start HTTP server' },
|
|
92
|
+
{ command: 'claude-flow mcp start -t http --request-timeout-ms 120000', description: 'Allow slower cold-start HTTP tools' },
|
|
87
93
|
{ command: 'claude-flow mcp start -d', description: 'Start as daemon' },
|
|
88
94
|
{ command: 'claude-flow mcp start -f', description: 'Force restart (kill existing)' }
|
|
89
95
|
],
|
|
@@ -94,6 +100,15 @@ const startCommand = {
|
|
|
94
100
|
const tools = ctx.flags.tools
|
|
95
101
|
|| process.env.CLAUDE_FLOW_MCP_TOOLS
|
|
96
102
|
|| 'all';
|
|
103
|
+
const timeoutInput = ctx.flags.requestTimeoutMs
|
|
104
|
+
?? ctx.flags['request-timeout-ms']
|
|
105
|
+
?? process.env.RUFLO_MCP_REQUEST_TIMEOUT_MS;
|
|
106
|
+
const requestTimeoutMs = timeoutInput === undefined ? undefined : Number(timeoutInput);
|
|
107
|
+
if (requestTimeoutMs !== undefined &&
|
|
108
|
+
(!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1 || requestTimeoutMs > 3_600_000)) {
|
|
109
|
+
output.printError('Request timeout must be an integer from 1 to 3600000 milliseconds');
|
|
110
|
+
return { success: false, exitCode: 1 };
|
|
111
|
+
}
|
|
97
112
|
const daemon = ctx.flags.daemon ?? false;
|
|
98
113
|
const force = ctx.flags.force ?? false;
|
|
99
114
|
output.writeln();
|
|
@@ -104,10 +119,17 @@ const startCommand = {
|
|
|
104
119
|
// PID file exists, which would cause us to SIGKILL ourselves)
|
|
105
120
|
const existingStatus = await getMCPServerStatus();
|
|
106
121
|
const isSelfDetected = existingStatus.pid === process.pid;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
// #3364: only a port-bound transport (http/websocket) is single-instance.
|
|
123
|
+
// A stdio server belongs to the client that spawned it, over that client's
|
|
124
|
+
// own pipes, and any number run side by side. The PID file is a single
|
|
125
|
+
// slot per os.tmpdir(), so for stdio "already running" only ever meant
|
|
126
|
+
// "some other server is recorded" — and force-restarting SIGKILLed it
|
|
127
|
+
// (another terminal's stdio server, or a live http server). Stdio now
|
|
128
|
+
// kills a recorded server only when explicitly asked with --force.
|
|
129
|
+
const isSingleInstance = transport !== 'stdio';
|
|
130
|
+
if (existingStatus.running && !isSelfDetected && (isSingleInstance || force)) {
|
|
131
|
+
// --force always restarts; otherwise check health (http/websocket)
|
|
132
|
+
const shouldForceRestart = force;
|
|
111
133
|
if (!shouldForceRestart) {
|
|
112
134
|
// Verify the server is actually healthy/responsive
|
|
113
135
|
const manager = getServerManager();
|
|
@@ -143,6 +165,7 @@ const startCommand = {
|
|
|
143
165
|
host,
|
|
144
166
|
port,
|
|
145
167
|
tools: !tools || tools === 'all' ? 'all' : tools.split(','),
|
|
168
|
+
...(requestTimeoutMs === undefined ? {} : { requestTimeoutMs }),
|
|
146
169
|
daemonize: daemon,
|
|
147
170
|
};
|
|
148
171
|
try {
|