claude-flow 3.21.0 → 3.21.1
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/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +56 -0
- package/v3/@claude-flow/cli/dist/src/init/executor.js +17 -0
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +26 -2
- package/v3/@claude-flow/cli/dist/src/init/memory-package-resolver.d.ts +53 -0
- package/v3/@claude-flow/cli/dist/src/init/memory-package-resolver.js +118 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +49 -8
- package/v3/@claude-flow/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.21.
|
|
3
|
+
"version": "3.21.1",
|
|
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",
|
|
@@ -13,6 +13,7 @@ import { execSync, exec } 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 { resolveMemoryPackageFromProject, readMemoryPackageVersion, recordMemoryPackagePath, } from '../init/memory-package-resolver.js';
|
|
16
17
|
// Promisified exec with proper shell and env inheritance for cross-platform support
|
|
17
18
|
const execAsync = promisify(exec);
|
|
18
19
|
/**
|
|
@@ -244,6 +245,37 @@ async function checkMemoryDatabase() {
|
|
|
244
245
|
}
|
|
245
246
|
return { name: 'Memory Database', status: 'warn', message: 'Not initialized', fix: 'claude-flow memory configure --backend hybrid' };
|
|
246
247
|
}
|
|
248
|
+
// #2545: Check that the self-learning bridge can actually load @claude-flow/memory
|
|
249
|
+
// the SAME way the SessionStart auto-memory hook does. On the documented `npx ruflo`
|
|
250
|
+
// path the package lands in the npx cache — unreachable from the project — so the
|
|
251
|
+
// hook silently no-op'd with no signal anywhere. This surfaces it.
|
|
252
|
+
async function checkLearningBridge() {
|
|
253
|
+
const cwd = process.cwd();
|
|
254
|
+
const hookPath = join(cwd, '.claude', 'helpers', 'auto-memory-hook.mjs');
|
|
255
|
+
// Only relevant once init has deployed the hook; otherwise stay quiet.
|
|
256
|
+
if (!existsSync(hookPath)) {
|
|
257
|
+
return {
|
|
258
|
+
name: 'Learning Bridge',
|
|
259
|
+
status: 'pass',
|
|
260
|
+
message: 'auto-memory hook not installed (run: npx ruflo@latest init)',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const distPath = resolveMemoryPackageFromProject(cwd);
|
|
264
|
+
if (distPath) {
|
|
265
|
+
const version = readMemoryPackageVersion(distPath);
|
|
266
|
+
return {
|
|
267
|
+
name: 'Learning Bridge',
|
|
268
|
+
status: 'pass',
|
|
269
|
+
message: `@claude-flow/memory resolvable${version ? ` (v${version})` : ''}`,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
name: 'Learning Bridge',
|
|
274
|
+
status: 'fail',
|
|
275
|
+
message: '@claude-flow/memory NOT resolvable — SessionStart self-learning imports are a silent no-op',
|
|
276
|
+
fix: 'npx ruflo@latest doctor --fix (records resolver sidecar) — or: npm i -D @claude-flow/memory',
|
|
277
|
+
};
|
|
278
|
+
}
|
|
247
279
|
// Check API keys
|
|
248
280
|
async function checkApiKeys() {
|
|
249
281
|
const keys = ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY'];
|
|
@@ -1036,6 +1068,7 @@ export const doctorCommand = {
|
|
|
1036
1068
|
checkStaleSettingsNpx, // #2448 — runaway `npx @latest` in statusLine/hooks
|
|
1037
1069
|
checkDaemonStatus,
|
|
1038
1070
|
checkMemoryDatabase,
|
|
1071
|
+
checkLearningBridge, // #2545 — can the auto-memory hook actually load @claude-flow/memory?
|
|
1039
1072
|
checkApiKeys,
|
|
1040
1073
|
checkMcpServers,
|
|
1041
1074
|
checkAIDefence, // #1807
|
|
@@ -1057,6 +1090,8 @@ export const doctorCommand = {
|
|
|
1057
1090
|
'stale-settings': checkStaleSettingsNpx, // #2448
|
|
1058
1091
|
'daemon': checkDaemonStatus,
|
|
1059
1092
|
'memory': checkMemoryDatabase,
|
|
1093
|
+
'learning': checkLearningBridge, // #2545
|
|
1094
|
+
'learning-bridge': checkLearningBridge, // #2545
|
|
1060
1095
|
'api': checkApiKeys,
|
|
1061
1096
|
'git': checkGit,
|
|
1062
1097
|
'mcp': checkMcpServers,
|
|
@@ -1107,6 +1142,27 @@ export const doctorCommand = {
|
|
|
1107
1142
|
spinner.stop();
|
|
1108
1143
|
output.writeln(output.error('Failed to run health checks'));
|
|
1109
1144
|
}
|
|
1145
|
+
// #2545: --fix / --install can actually repair the Learning Bridge by
|
|
1146
|
+
// recording the resolver sidecar. When doctor runs via `npx ruflo`, the CLI
|
|
1147
|
+
// CAN resolve its optional @claude-flow/memory dep (it is in the same npx
|
|
1148
|
+
// cache), so writing the sidecar makes the SessionStart hook find it.
|
|
1149
|
+
if ((showFix || autoInstall)) {
|
|
1150
|
+
const lbResult = results.find(r => r.name === 'Learning Bridge');
|
|
1151
|
+
if (lbResult && lbResult.status === 'fail') {
|
|
1152
|
+
const record = recordMemoryPackagePath(process.cwd(), 'doctor');
|
|
1153
|
+
if (record) {
|
|
1154
|
+
const newCheck = await checkLearningBridge();
|
|
1155
|
+
const idx = results.findIndex(r => r.name === 'Learning Bridge');
|
|
1156
|
+
if (idx !== -1)
|
|
1157
|
+
results[idx] = newCheck;
|
|
1158
|
+
const fixIdx = fixes.findIndex(f => f.startsWith('Learning Bridge:'));
|
|
1159
|
+
if (fixIdx !== -1 && newCheck.status === 'pass')
|
|
1160
|
+
fixes.splice(fixIdx, 1);
|
|
1161
|
+
output.writeln(output.success(`Repaired Learning Bridge — wrote .claude-flow/memory-package.json → ${record.distPath}`));
|
|
1162
|
+
output.writeln(formatCheck(newCheck));
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1110
1166
|
// Auto-install missing dependencies if requested
|
|
1111
1167
|
if (autoInstall) {
|
|
1112
1168
|
const claudeCodeResult = results.find(r => r.name === 'Claude Code CLI');
|
|
@@ -16,6 +16,7 @@ import { generateMCPJson } from './mcp-generator.js';
|
|
|
16
16
|
import { generateStatuslineScript } from './statusline-generator.js';
|
|
17
17
|
import { generatePreCommitHook, generatePostCommitHook, generateSessionManager, generateAgentRouter, generateMemoryHelper, generateHookHandler, generateIntelligenceStub, generateAutoMemoryHook, generateRufloHookCjs, } from './helpers-generator.js';
|
|
18
18
|
import { generateClaudeMd } from './claudemd-generator.js';
|
|
19
|
+
import { recordMemoryPackagePath } from './memory-package-resolver.js';
|
|
19
20
|
/**
|
|
20
21
|
* Skills to copy based on configuration
|
|
21
22
|
*/
|
|
@@ -196,6 +197,16 @@ export async function executeInit(options) {
|
|
|
196
197
|
// Generate helpers
|
|
197
198
|
if (options.components.helpers) {
|
|
198
199
|
await writeHelpers(targetDir, options, result);
|
|
200
|
+
// #2545: The auto-memory hook needs @claude-flow/memory, which is an
|
|
201
|
+
// optionalDependency of the CLI and lands in the npx cache — unreachable
|
|
202
|
+
// by a node_modules walk-up from the user's project. Resolve it from the
|
|
203
|
+
// CLI's own context now (where it IS present) and record the absolute
|
|
204
|
+
// path in a machine-local sidecar the hook reads first. Best-effort:
|
|
205
|
+
// when the optional dep is absent the hook fails loud and doctor flags it.
|
|
206
|
+
const memRecord = recordMemoryPackagePath(targetDir, 'init');
|
|
207
|
+
if (memRecord) {
|
|
208
|
+
result.created.files.push('.claude-flow/memory-package.json');
|
|
209
|
+
}
|
|
199
210
|
}
|
|
200
211
|
// Generate statusline
|
|
201
212
|
if (options.components.statusline) {
|
|
@@ -472,6 +483,12 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
472
483
|
catch { }
|
|
473
484
|
}
|
|
474
485
|
}
|
|
486
|
+
// #2545: (re)record the resolved @claude-flow/memory path so the auto-memory
|
|
487
|
+
// hook can find it on the npx install path. Best-effort — see executeInit.
|
|
488
|
+
const memRecord = recordMemoryPackagePath(targetDir, 'upgrade');
|
|
489
|
+
if (memRecord) {
|
|
490
|
+
result.updated.push('.claude-flow/memory-package.json');
|
|
491
|
+
}
|
|
475
492
|
// 1. ALWAYS update statusline helper (force overwrite)
|
|
476
493
|
const statuslinePath = path.join(targetDir, '.claude', 'helpers', 'statusline.cjs');
|
|
477
494
|
// Use default options with statusline config
|
|
@@ -863,13 +863,37 @@ const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
|
|
|
863
863
|
const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
|
|
864
864
|
|
|
865
865
|
const DIM = '\\x1b[2m';
|
|
866
|
+
const YELLOW = '\\x1b[0;33m';
|
|
866
867
|
const RESET = '\\x1b[0m';
|
|
867
868
|
const dim = (msg) => console.log(\` \${DIM}\${msg}\${RESET}\`);
|
|
868
869
|
|
|
870
|
+
// #2545: fail LOUD instead of a silent dim skip when @claude-flow/memory is
|
|
871
|
+
// unresolvable — self-learning imports are a no-op and the user must be told.
|
|
872
|
+
function warnMemoryUnavailable() {
|
|
873
|
+
const l1 = \`[AutoMemory] @claude-flow/memory not resolvable from \${PROJECT_ROOT} — self-learning imports are DISABLED.\`;
|
|
874
|
+
const l2 = ' Fix: npm i -D @claude-flow/memory (or re-run: npx ruflo@latest init, then npx ruflo@latest doctor --fix)';
|
|
875
|
+
console.log(\`\${YELLOW}\${l1}\${RESET}\`);
|
|
876
|
+
console.log(\`\${YELLOW}\${l2}\${RESET}\`);
|
|
877
|
+
process.stderr.write(\`\${l1}\\n\${l2}\\n\`);
|
|
878
|
+
}
|
|
879
|
+
|
|
869
880
|
// Ensure data dir
|
|
870
881
|
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
|
871
882
|
|
|
872
883
|
async function loadMemoryPackage() {
|
|
884
|
+
// Strategy 0 (#2545): sidecar recorded by \`init\` / \`doctor --fix\`. On the npx
|
|
885
|
+
// path @claude-flow/memory lands in the npx cache (unreachable by walk-up), so
|
|
886
|
+
// init records its absolute path here — the only strategy that works there.
|
|
887
|
+
try {
|
|
888
|
+
const sidecar = join(PROJECT_ROOT, '.claude-flow', 'memory-package.json');
|
|
889
|
+
if (existsSync(sidecar)) {
|
|
890
|
+
const rec = JSON.parse(readFileSync(sidecar, 'utf-8'));
|
|
891
|
+
if (rec && rec.distPath && existsSync(rec.distPath)) {
|
|
892
|
+
return await import(\`file://\${rec.distPath}\`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
} catch { /* fall through */ }
|
|
896
|
+
|
|
873
897
|
// Strategy 1: Use createRequire for CJS-style resolution (handles nested node_modules
|
|
874
898
|
// when installed as a transitive dependency via npx ruflo / npx claude-flow)
|
|
875
899
|
try {
|
|
@@ -899,7 +923,7 @@ async function doImport() {
|
|
|
899
923
|
const memPkg = await loadMemoryPackage();
|
|
900
924
|
|
|
901
925
|
if (!memPkg || !memPkg.AutoMemoryBridge) {
|
|
902
|
-
|
|
926
|
+
warnMemoryUnavailable();
|
|
903
927
|
return;
|
|
904
928
|
}
|
|
905
929
|
|
|
@@ -916,7 +940,7 @@ async function doSync() {
|
|
|
916
940
|
const memPkg = await loadMemoryPackage();
|
|
917
941
|
|
|
918
942
|
if (!memPkg || !memPkg.AutoMemoryBridge) {
|
|
919
|
-
|
|
943
|
+
warnMemoryUnavailable();
|
|
920
944
|
return;
|
|
921
945
|
}
|
|
922
946
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Package Resolver (#2545)
|
|
3
|
+
*
|
|
4
|
+
* `@claude-flow/memory` is an *optionalDependency* of `@claude-flow/cli`. On the
|
|
5
|
+
* documented `npx ruflo` install path it lands in the npx cache
|
|
6
|
+
* (`~/.npm/_npx/<hash>/node_modules`), which is NOT on the node_modules walk-up
|
|
7
|
+
* path from the user's project. The SessionStart auto-memory hook therefore
|
|
8
|
+
* could never resolve it and self-learning silently no-op'd.
|
|
9
|
+
*
|
|
10
|
+
* At init time, however, the CLI *can* resolve the package from its own module
|
|
11
|
+
* context (it is installed alongside the CLI in that same npx cache). We resolve
|
|
12
|
+
* it once and record the absolute path in a machine-local project sidecar
|
|
13
|
+
* (`.claude-flow/memory-package.json`). The hook reads this sidecar first, so it
|
|
14
|
+
* reuses the copy npx already downloaded — no second install, no vendoring.
|
|
15
|
+
*
|
|
16
|
+
* This module is deliberately dependency-free and best-effort: nothing here ever
|
|
17
|
+
* throws into the init/doctor flow.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MEMORY_PACKAGE = "@claude-flow/memory";
|
|
20
|
+
/** Project-relative path of the resolver sidecar written by init / doctor --fix. */
|
|
21
|
+
export declare const MEMORY_SIDECAR_REL: string;
|
|
22
|
+
export interface MemoryPackageRecord {
|
|
23
|
+
/** Absolute path to the package's main entry (dist/index.js). */
|
|
24
|
+
distPath: string;
|
|
25
|
+
/** Resolved package version, if readable. */
|
|
26
|
+
version: string | null;
|
|
27
|
+
/** What produced this record ("init" | "doctor" | "upgrade"). */
|
|
28
|
+
resolvedBy: string;
|
|
29
|
+
/** ISO timestamp of resolution. */
|
|
30
|
+
resolvedAt: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve `@claude-flow/memory`'s main entry from the CLI's own module context.
|
|
34
|
+
* Returns the absolute dist path, or null when the optional dependency is absent
|
|
35
|
+
* (e.g. installed with `--omit=optional`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveMemoryPackageFromCli(): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve `@claude-flow/memory` the same way the runtime hook does, from a target
|
|
40
|
+
* project directory: sidecar → project package.json → node_modules walk-up.
|
|
41
|
+
* Used by `doctor` so its verdict matches what the hook will actually experience.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveMemoryPackageFromProject(targetDir: string): string | null;
|
|
44
|
+
/** Read the version of the resolved memory package (dist/index.js → ../package.json). */
|
|
45
|
+
export declare function readMemoryPackageVersion(distPath: string): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Best-effort: resolve `@claude-flow/memory` from the CLI and record its path in
|
|
48
|
+
* the project sidecar so the runtime hook can find it on the npx install path.
|
|
49
|
+
* Returns the written record, or null when the package is not resolvable from the
|
|
50
|
+
* CLI (in which case the hook fails loud and `doctor` flags it).
|
|
51
|
+
*/
|
|
52
|
+
export declare function recordMemoryPackagePath(targetDir: string, resolvedBy?: string): MemoryPackageRecord | null;
|
|
53
|
+
//# sourceMappingURL=memory-package-resolver.d.ts.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Package Resolver (#2545)
|
|
3
|
+
*
|
|
4
|
+
* `@claude-flow/memory` is an *optionalDependency* of `@claude-flow/cli`. On the
|
|
5
|
+
* documented `npx ruflo` install path it lands in the npx cache
|
|
6
|
+
* (`~/.npm/_npx/<hash>/node_modules`), which is NOT on the node_modules walk-up
|
|
7
|
+
* path from the user's project. The SessionStart auto-memory hook therefore
|
|
8
|
+
* could never resolve it and self-learning silently no-op'd.
|
|
9
|
+
*
|
|
10
|
+
* At init time, however, the CLI *can* resolve the package from its own module
|
|
11
|
+
* context (it is installed alongside the CLI in that same npx cache). We resolve
|
|
12
|
+
* it once and record the absolute path in a machine-local project sidecar
|
|
13
|
+
* (`.claude-flow/memory-package.json`). The hook reads this sidecar first, so it
|
|
14
|
+
* reuses the copy npx already downloaded — no second install, no vendoring.
|
|
15
|
+
*
|
|
16
|
+
* This module is deliberately dependency-free and best-effort: nothing here ever
|
|
17
|
+
* throws into the init/doctor flow.
|
|
18
|
+
*/
|
|
19
|
+
import { createRequire } from 'module';
|
|
20
|
+
import * as fs from 'fs';
|
|
21
|
+
import * as path from 'path';
|
|
22
|
+
export const MEMORY_PACKAGE = '@claude-flow/memory';
|
|
23
|
+
/** Project-relative path of the resolver sidecar written by init / doctor --fix. */
|
|
24
|
+
export const MEMORY_SIDECAR_REL = path.join('.claude-flow', 'memory-package.json');
|
|
25
|
+
/**
|
|
26
|
+
* Resolve `@claude-flow/memory`'s main entry from the CLI's own module context.
|
|
27
|
+
* Returns the absolute dist path, or null when the optional dependency is absent
|
|
28
|
+
* (e.g. installed with `--omit=optional`).
|
|
29
|
+
*/
|
|
30
|
+
export function resolveMemoryPackageFromCli() {
|
|
31
|
+
try {
|
|
32
|
+
const require = createRequire(import.meta.url);
|
|
33
|
+
return require.resolve(MEMORY_PACKAGE);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve `@claude-flow/memory` the same way the runtime hook does, from a target
|
|
41
|
+
* project directory: sidecar → project package.json → node_modules walk-up.
|
|
42
|
+
* Used by `doctor` so its verdict matches what the hook will actually experience.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveMemoryPackageFromProject(targetDir) {
|
|
45
|
+
// 1. Sidecar written by a previous init / doctor --fix
|
|
46
|
+
try {
|
|
47
|
+
const sidecar = path.join(targetDir, MEMORY_SIDECAR_REL);
|
|
48
|
+
if (fs.existsSync(sidecar)) {
|
|
49
|
+
const rec = JSON.parse(fs.readFileSync(sidecar, 'utf-8'));
|
|
50
|
+
if (rec?.distPath && fs.existsSync(rec.distPath))
|
|
51
|
+
return rec.distPath;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* fall through */
|
|
56
|
+
}
|
|
57
|
+
// 2. createRequire from the project's package.json (direct/transitive dep)
|
|
58
|
+
try {
|
|
59
|
+
const require = createRequire(path.join(targetDir, 'package.json'));
|
|
60
|
+
return require.resolve(MEMORY_PACKAGE);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* fall through */
|
|
64
|
+
}
|
|
65
|
+
// 3. node_modules walk-up from the project root
|
|
66
|
+
let dir = path.resolve(targetDir);
|
|
67
|
+
// eslint-disable-next-line no-constant-condition
|
|
68
|
+
while (true) {
|
|
69
|
+
const candidate = path.join(dir, 'node_modules', '@claude-flow', 'memory', 'dist', 'index.js');
|
|
70
|
+
if (fs.existsSync(candidate))
|
|
71
|
+
return candidate;
|
|
72
|
+
const parent = path.dirname(dir);
|
|
73
|
+
if (parent === dir)
|
|
74
|
+
break;
|
|
75
|
+
dir = parent;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/** Read the version of the resolved memory package (dist/index.js → ../package.json). */
|
|
80
|
+
export function readMemoryPackageVersion(distPath) {
|
|
81
|
+
try {
|
|
82
|
+
const pkgJson = path.resolve(path.dirname(distPath), '..', 'package.json');
|
|
83
|
+
if (fs.existsSync(pkgJson)) {
|
|
84
|
+
const parsed = JSON.parse(fs.readFileSync(pkgJson, 'utf-8'));
|
|
85
|
+
return parsed.version ?? null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* ignore */
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Best-effort: resolve `@claude-flow/memory` from the CLI and record its path in
|
|
95
|
+
* the project sidecar so the runtime hook can find it on the npx install path.
|
|
96
|
+
* Returns the written record, or null when the package is not resolvable from the
|
|
97
|
+
* CLI (in which case the hook fails loud and `doctor` flags it).
|
|
98
|
+
*/
|
|
99
|
+
export function recordMemoryPackagePath(targetDir, resolvedBy = 'init') {
|
|
100
|
+
const distPath = resolveMemoryPackageFromCli();
|
|
101
|
+
if (!distPath)
|
|
102
|
+
return null;
|
|
103
|
+
const record = {
|
|
104
|
+
distPath,
|
|
105
|
+
version: readMemoryPackageVersion(distPath),
|
|
106
|
+
resolvedBy,
|
|
107
|
+
resolvedAt: new Date().toISOString(),
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
fs.mkdirSync(path.join(targetDir, '.claude-flow'), { recursive: true });
|
|
111
|
+
fs.writeFileSync(path.join(targetDir, MEMORY_SIDECAR_REL), JSON.stringify(record, null, 2), 'utf-8');
|
|
112
|
+
return record;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=memory-package-resolver.js.map
|
|
@@ -680,6 +680,31 @@ export async function bridgeStoreEntry(options) {
|
|
|
680
680
|
catch { /* vector_indexes may not exist on legacy DBs — fall through */ }
|
|
681
681
|
const stmt = ctx.db.prepare(insertSql);
|
|
682
682
|
stmt.run(id, key, namespace, value, embeddingJson, dimensions || null, model, tags.length > 0 ? JSON.stringify(tags) : null, '{}', now, now, ttl ? now + (ttl * 1000) : null);
|
|
683
|
+
// #2558: keep `vector_indexes.total_vectors` accurate so status/tooling
|
|
684
|
+
// stop reporting "HNSW index: 0 vectors" while embedded entries exist.
|
|
685
|
+
try {
|
|
686
|
+
ctx.db
|
|
687
|
+
.prepare(`UPDATE vector_indexes SET
|
|
688
|
+
total_vectors = (SELECT COUNT(*) FROM memory_entries
|
|
689
|
+
WHERE namespace = ? AND embedding IS NOT NULL),
|
|
690
|
+
updated_at = ?
|
|
691
|
+
WHERE name = ?`)
|
|
692
|
+
.run(namespace, now, namespace);
|
|
693
|
+
}
|
|
694
|
+
catch { /* vector_indexes may not exist on legacy DBs — non-fatal */ }
|
|
695
|
+
// #2558: better-sqlite3 opens the DB in WAL mode and, for the small write
|
|
696
|
+
// volumes typical of CLI usage, may never reach the auto-checkpoint
|
|
697
|
+
// threshold — leaving committed rows only in the -wal file. WAL-blind
|
|
698
|
+
// readers (the sql.js fallback search path; the statusline's read-only
|
|
699
|
+
// `sqlite3` vector count) then see a stale/empty main DB file and report
|
|
700
|
+
// "0 vectors" / empty search. A PASSIVE checkpoint flushes committed pages
|
|
701
|
+
// into the main file without blocking writers. Best-effort, never fatal.
|
|
702
|
+
try {
|
|
703
|
+
if (typeof ctx.db.pragma === 'function') {
|
|
704
|
+
ctx.db.pragma('wal_checkpoint(PASSIVE)');
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch { /* non-WAL, busy, or unsupported — non-fatal */ }
|
|
683
708
|
// Phase 2: Write-through to TieredCache
|
|
684
709
|
const safeNs = String(namespace).replace(/:/g, '_');
|
|
685
710
|
const safeKey = String(key).replace(/:/g, '_');
|
|
@@ -770,17 +795,33 @@ export async function bridgeSearchEntries(options) {
|
|
|
770
795
|
// Normalize BM25 to 0-1 range (cap at 10 for normalization)
|
|
771
796
|
bm25ScoreVal = Math.min(bm25ScoreVal / 10, 1.0);
|
|
772
797
|
}
|
|
773
|
-
//
|
|
774
|
-
//
|
|
775
|
-
//
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
798
|
+
// #2558: keyword-coverage floor for the lexical signal.
|
|
799
|
+
// BM25's IDF collapses toward zero when a term appears in most/all
|
|
800
|
+
// documents (routine on small memory corpora), and the /10 normalization
|
|
801
|
+
// crushed exact-keyword hits well below the default 0.3 threshold — so
|
|
802
|
+
// `memory search` recalled NOTHING even when the content literally
|
|
803
|
+
// contained the query term (issue #2558: "keyword recall random"). The
|
|
804
|
+
// pre-BM25 fallback guaranteed keyword recall via matchCount/words*0.5;
|
|
805
|
+
// this restores that guarantee. `coverage` is the fraction of query
|
|
806
|
+
// terms present in the document — a full-coverage hit must always be
|
|
807
|
+
// recallable regardless of IDF.
|
|
808
|
+
const contentLower = String(row.content || '').toLowerCase();
|
|
809
|
+
const matchedTerms = queryTerms.filter(t => contentLower.includes(t)).length;
|
|
810
|
+
const coverage = queryTerms.length > 0 ? matchedTerms / queryTerms.length : 0;
|
|
811
|
+
const lexicalScore = Math.max(bm25ScoreVal, coverage);
|
|
812
|
+
// Recall-friendly fusion: a strong semantic OR lexical signal alone must
|
|
813
|
+
// clear the threshold. `blended` (0.6 semantic + 0.4 lexical) drives
|
|
814
|
+
// ranking; taking max() with the raw semantic score means (a) a genuinely
|
|
815
|
+
// similar entry is never dropped just because it lacks the query's exact
|
|
816
|
+
// words, and (b) a full-coverage keyword hit (lexical=1 → blended≥0.4) is
|
|
817
|
+
// never dropped just because its embedding cosine is low or negative.
|
|
818
|
+
const blended = 0.6 * Math.max(0, semanticScore) + 0.4 * lexicalScore;
|
|
819
|
+
const score = Math.max(blended, semanticScore);
|
|
779
820
|
if (score >= threshold) {
|
|
780
821
|
// Phase 4: ExplainableRecall provenance
|
|
781
822
|
const provenance = queryEmbedding
|
|
782
|
-
? `semantic:${semanticScore.toFixed(3)}+
|
|
783
|
-
: `
|
|
823
|
+
? `semantic:${semanticScore.toFixed(3)}+lexical:${lexicalScore.toFixed(3)}`
|
|
824
|
+
: `lexical:${lexicalScore.toFixed(3)}`;
|
|
784
825
|
results.push({
|
|
785
826
|
id: String(row.id).substring(0, 12),
|
|
786
827
|
key: row.key || String(row.id).substring(0, 15),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.21.
|
|
3
|
+
"version": "3.21.1",
|
|
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",
|