mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-10-23 03:47:00 +09:00
* fix: #2219 #2221 #2222 #2215 #2226 — memory/routing/statusline bug cluster (3.10.6) Five reproducible bugs reported by external contributors (pacphi, casparml, HF-teamdev), each verified against source and covered by a regression test. #2219 (silent write loss, Node 24/26) — agentdb pins better-sqlite3 as an OPTIONAL dep at ^11.8.1, which has no Node 24/25/26 prebuild; the native build fails silently and AgentDB drops to a non-persistent backend. Override better-sqlite3 to >=12.8.0 (ships Node 20–26 prebuilds) in BOTH the root umbrella and the ruflo wrapper (#2112: root overrides don't reach the wrapper). New CI guard audit-better-sqlite3-override.mjs prevents regression. #2226 (pattern store/search backend mismatch) — agentdb_pattern-store and agentdb_pattern-search hit disjoint backends, so stored patterns were never found. Two paths fixed: (a) when the ReasoningBank controller is present, bridgeSearchPatterns now reads LocalReasoningBank.findSimilar/getAll (the same backend store writes to); (b) when the controller is absent (the common real-world state), the memory-store-fallback search Tier-2 now hydrates entry content via getEntry — listEntries returns metadata only (see #2014), so the substring scan previously matched nothing. #2222 (route feedback no-op) — `route feedback` applied the Q-update in memory but the CLI exits before the Q-learner's autoSaveInterval fires, so learning never persisted. Add an explicit awaited router.saveModel() after feedback. #2221 (statusline shows V3.6 on global installs) — getPkgVersion() never probed the global npm root, so `npm i -g ruflo` fell back to the hard-coded default. Derive the global node_modules dir from process.execPath (no npm spawn) and probe it; covers nvm/mise and Windows layouts. Regenerated statusline.cjs. #2215 (flashAttention contradictory state) — system_info emitted a hard-coded `flashAttention: false` while hooks_intelligence reported the live probe. system_info now runs the same getFlashAttention() probe as the authoritative path so the two tools can't disagree. Tests: __tests__/bug-cluster-2219-2226.test.ts (5/5), statusline drift guard still byte-identical (8/8), tsc clean, targeted sweep 120/120. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(deps): #2219 — sync package-lock to better-sqlite3@12.10.0 override npm ci validates lockfile against package.json; the override bumped the resolved version but the committed lockfile still pinned 11.10.0, failing 'npm ci' in CI. Regenerated with --package-lock-only. Co-Authored-By: RuFlo <ruv@ruv.net>
This commit is contained in:
13 files changed
+333
-27
No files matched your search
@@ -345,6 +345,20 @@ function getPkgVersion() {
|
|||||||
path.join(CWD, 'node_modules', 'ruflo', 'package.json'),
|
path.join(CWD, 'node_modules', 'ruflo', 'package.json'),
|
||||||
path.join(CWD, 'v3', '@claude-flow', 'cli', 'package.json'),
|
path.join(CWD, 'v3', '@claude-flow', 'cli', 'package.json'),
|
||||||
];
|
];
|
||||||
|
// #2221: global installs (npm i -g ruflo) live outside CWD/node_modules, so the
|
||||||
|
// probes above all miss and the version falls back to the hard-coded default.
|
||||||
|
// Derive the global node_modules dir from the running node binary (no npm spawn —
|
||||||
|
// statusline renders often). Covers nvm/mise (bin/../lib/node_modules) and Windows
|
||||||
|
// (bin/node_modules) layouts.
|
||||||
|
try {
|
||||||
|
const binDir = path.dirname(process.execPath);
|
||||||
|
for (const gm of [path.join(binDir, '..', 'lib', 'node_modules'), path.join(binDir, 'node_modules')]) {
|
||||||
|
pkgPaths.push(
|
||||||
|
path.join(gm, 'ruflo', 'package.json'),
|
||||||
|
path.join(gm, '@claude-flow', 'cli', 'package.json'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch { /* ignore */ }
|
||||||
for (const p of pkgPaths) {
|
for (const p of pkgPaths) {
|
||||||
if (!fs.existsSync(p)) continue;
|
if (!fs.existsSync(p)) continue;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1618,6 +1618,17 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: node scripts/audit-umbrella-version-lockstep.mjs
|
run: node scripts/audit-umbrella-version-lockstep.mjs
|
||||||
|
|
||||||
|
- name: Run better-sqlite3 override audit (#2219)
|
||||||
|
# agentdb pins better-sqlite3 as an OPTIONAL dep at ^11.8.1, which has
|
||||||
|
# no Node 24/25/26 prebuild → the optional native build fails silently
|
||||||
|
# on those runtimes and AgentDB drops to a non-persistent backend
|
||||||
|
# (silent write loss). Asserts both the root umbrella and the ruflo
|
||||||
|
# wrapper override better-sqlite3 to >=12.8.0 (which ships Node 20–26
|
||||||
|
# prebuilds). Root overrides do NOT reach the published wrapper (#2112),
|
||||||
|
# so both must carry it.
|
||||||
|
shell: bash
|
||||||
|
run: node scripts/audit-better-sqlite3-override.mjs
|
||||||
|
|
||||||
- name: Run plugin-hooks cross-platform audit (#2132)
|
- name: Run plugin-hooks cross-platform audit (#2132)
|
||||||
# Catches /bin/bash literals, POSIX-only pipelines (jq, xargs, tr),
|
# Catches /bin/bash literals, POSIX-only pipelines (jq, xargs, tr),
|
||||||
# and .sh script invocations in plugin hooks.json files — patterns
|
# and .sh script invocations in plugin hooks.json files — patterns
|
||||||
|
|||||||
Generated
+8
-20
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-flow",
|
"name": "claude-flow",
|
||||||
"version": "3.7.0-alpha.80",
|
"version": "3.10.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "claude-flow",
|
"name": "claude-flow",
|
||||||
"version": "3.7.0-alpha.80",
|
"version": "3.10.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@claude-flow/cli-core": "^3.7.0-alpha.5",
|
"@claude-flow/cli-core": "^3.7.0-alpha.5",
|
||||||
@@ -230,21 +230,6 @@
|
|||||||
"better-sqlite3": "^12.9.0"
|
"better-sqlite3": "^12.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@claude-flow/memory/node_modules/better-sqlite3": {
|
|
||||||
"version": "12.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz",
|
|
||||||
"integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"bindings": "^1.5.0",
|
|
||||||
"prebuild-install": "^7.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@claude-flow/neural": {
|
"node_modules/@claude-flow/neural": {
|
||||||
"version": "3.0.0-alpha.9",
|
"version": "3.0.0-alpha.9",
|
||||||
"resolved": "https://registry.npmjs.org/@claude-flow/neural/-/neural-3.0.0-alpha.9.tgz",
|
"resolved": "https://registry.npmjs.org/@claude-flow/neural/-/neural-3.0.0-alpha.9.tgz",
|
||||||
@@ -4503,15 +4488,18 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/better-sqlite3": {
|
"node_modules/better-sqlite3": {
|
||||||
"version": "11.10.0",
|
"version": "12.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz",
|
||||||
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
|
"integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bindings": "^1.5.0",
|
"bindings": "^1.5.0",
|
||||||
"prebuild-install": "^7.1.1"
|
"prebuild-install": "^7.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bignumber.js": {
|
"node_modules/bignumber.js": {
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-flow",
|
"name": "claude-flow",
|
||||||
"version": "3.10.5",
|
"version": "3.10.6",
|
||||||
"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",
|
"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",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -75,6 +75,7 @@
|
|||||||
"agentic-flow": "^2.0.13"
|
"agentic-flow": "^2.0.13"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
"better-sqlite3": ">=12.8.0",
|
||||||
"hono": ">=4.11.4",
|
"hono": ">=4.11.4",
|
||||||
"@ruvector/rvf-wasm": "0.1.5",
|
"@ruvector/rvf-wasm": "0.1.5",
|
||||||
"@hono/node-server": ">=1.19.10",
|
"@hono/node-server": ">=1.19.10",
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ruflo",
|
"name": "ruflo",
|
||||||
"version": "3.10.5",
|
"version": "3.10.6",
|
||||||
"description": "Ruflo - Enterprise AI agent orchestration platform. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
"description": "Ruflo - Enterprise AI agent orchestration platform. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
||||||
"main": "bin/ruflo.js",
|
"main": "bin/ruflo.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
"@claude-flow/cli": "^3.10.3"
|
"@claude-flow/cli": "^3.10.3"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
"better-sqlite3": ">=12.8.0",
|
||||||
"@ruvector/rvf-wasm": "0.1.5",
|
"@ruvector/rvf-wasm": "0.1.5",
|
||||||
"@hono/node-server": ">=1.19.10",
|
"@hono/node-server": ">=1.19.10",
|
||||||
"flatted": ">=3.4.0",
|
"flatted": ">=3.4.0",
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Static guard for ruvnet/ruflo#2219 — keep the `better-sqlite3` override
|
||||||
|
* pinned to a version that ships Node 24/25/26 prebuilds.
|
||||||
|
*
|
||||||
|
* Why: `agentdb` declares `better-sqlite3` as an OPTIONAL dependency at
|
||||||
|
* `^11.8.1`. better-sqlite3 11.8.1 has no prebuilt binary for Node 24/25/26,
|
||||||
|
* so on those runtimes the optional native build fails *silently* (optional
|
||||||
|
* deps never error) and AgentDB falls back to a non-persistent backend —
|
||||||
|
* users on Node 24/26 saw stores succeed but never persist (silent write
|
||||||
|
* loss). better-sqlite3 >=12.8.0 ships prebuilds for Node 20–26.
|
||||||
|
*
|
||||||
|
* Per the #2112 lesson, root overrides do NOT propagate to the published
|
||||||
|
* `ruflo` wrapper — the override MUST be present in BOTH the root umbrella
|
||||||
|
* (`package.json`) and the wrapper (`ruflo/package.json`). This guard asserts
|
||||||
|
* both carry `better-sqlite3 >= 12.8.0`. Wired into v3-ci.yml.
|
||||||
|
*
|
||||||
|
* Exit codes:
|
||||||
|
* 0 — both overrides present and satisfy >=12.8.0
|
||||||
|
* 1 — missing override, or a range that admits a Node-24/26-broken version
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFileSync, existsSync } from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { dirname, join, resolve } from 'node:path';
|
||||||
|
import semver from 'semver';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const REPO_ROOT = resolve(__dirname, '..');
|
||||||
|
|
||||||
|
// The floor that has Node 24/25/26 prebuilds. Any override that could resolve
|
||||||
|
// below this re-opens the silent-write-loss on new Node majors.
|
||||||
|
const MIN_SAFE = '12.8.0';
|
||||||
|
|
||||||
|
const TARGETS = [
|
||||||
|
{ label: 'claude-flow (root umbrella)', path: 'package.json' },
|
||||||
|
{ label: 'ruflo (published wrapper)', path: 'ruflo/package.json' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let failed = false;
|
||||||
|
|
||||||
|
for (const { label, path: rel } of TARGETS) {
|
||||||
|
const p = join(REPO_ROOT, rel);
|
||||||
|
if (!existsSync(p)) {
|
||||||
|
console.error(`✗ ${label}: ${rel} not found`);
|
||||||
|
failed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let pkg;
|
||||||
|
try {
|
||||||
|
pkg = JSON.parse(readFileSync(p, 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`✗ ${label}: ${rel} is not valid JSON — ${e.message}`);
|
||||||
|
failed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = pkg.overrides?.['better-sqlite3'];
|
||||||
|
if (!range) {
|
||||||
|
console.error(
|
||||||
|
`✗ ${label}: missing overrides["better-sqlite3"]. ` +
|
||||||
|
`Add "better-sqlite3": ">=${MIN_SAFE}" so agentdb's optional native ` +
|
||||||
|
`dep resolves to a version with Node 24/25/26 prebuilds (#2219).`,
|
||||||
|
);
|
||||||
|
failed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The minimum version the range can resolve to must be >= MIN_SAFE, so the
|
||||||
|
// override can never drop back to a Node-24/26-broken better-sqlite3.
|
||||||
|
const minOfRange = semver.minVersion(range);
|
||||||
|
if (!minOfRange || semver.lt(minOfRange, MIN_SAFE)) {
|
||||||
|
console.error(
|
||||||
|
`✗ ${label}: overrides["better-sqlite3"] = "${range}" can resolve below ` +
|
||||||
|
`${MIN_SAFE} (min ${minOfRange ?? 'unparseable'}). Node 24/25/26 need ` +
|
||||||
|
`>=${MIN_SAFE} prebuilds (#2219).`,
|
||||||
|
);
|
||||||
|
failed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✓ ${label}: better-sqlite3 override "${range}" (min ${minOfRange}) ≥ ${MIN_SAFE}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed) {
|
||||||
|
console.error('\nbetter-sqlite3 override guard FAILED (#2219). See messages above.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log('\n✓ better-sqlite3 override guard passed — Node 24/25/26 persistence protected.');
|
||||||
|
process.exit(0);
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* Regression tests for the 2026-05-29 bug cluster (issues #2215, #2221, #2222, #2226).
|
||||||
|
*
|
||||||
|
* Each test reproduces a reported defect and asserts the fix holds:
|
||||||
|
* #2215 — system_info and hooks_intelligence must agree on flashAttention state.
|
||||||
|
* #2221 — the generated statusline probes the global npm install for its version.
|
||||||
|
* #2222 — `route feedback` persists the Q-table update to disk (no longer a no-op).
|
||||||
|
* #2226 — agentdb_pattern-store and agentdb_pattern-search share a backend
|
||||||
|
* (a stored pattern is findable by search).
|
||||||
|
*
|
||||||
|
* Backend-dependent tests degrade gracefully (skip the assertion) when the
|
||||||
|
* runtime backend cannot initialize in isolation, matching the existing
|
||||||
|
* statusline drift-guard convention.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { mkdtempSync, existsSync, readFileSync, rmSync } from 'fs';
|
||||||
|
import { tmpdir } from 'os';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
import { generateStatuslineScript } from '../src/init/statusline-generator.js';
|
||||||
|
import { DEFAULT_INIT_OPTIONS } from '../src/init/types.js';
|
||||||
|
import { systemTools } from '../src/mcp-tools/system-tools.js';
|
||||||
|
import { hooksTools } from '../src/mcp-tools/hooks-tools.js';
|
||||||
|
import { agentdbPatternStore, agentdbPatternSearch } from '../src/mcp-tools/agentdb-tools.js';
|
||||||
|
import { createQLearningRouter } from '../src/ruvector/q-learning-router.js';
|
||||||
|
|
||||||
|
function findTool(tools: any[], name: string) {
|
||||||
|
const t = tools.find((x) => x.name === name);
|
||||||
|
if (!t) throw new Error(`tool ${name} not found`);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('#2221 — statusline version probe covers global npm installs', () => {
|
||||||
|
const SCRIPT = generateStatuslineScript(DEFAULT_INIT_OPTIONS);
|
||||||
|
|
||||||
|
it('derives the global node_modules dir from process.execPath (no npm spawn)', () => {
|
||||||
|
expect(SCRIPT).toContain('process.execPath');
|
||||||
|
expect(SCRIPT).toContain("'lib', 'node_modules'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still keeps the project-local and plugin-marketplace probes', () => {
|
||||||
|
expect(SCRIPT).toContain("'marketplaces', 'ruflo', 'package.json'");
|
||||||
|
expect(SCRIPT).toContain("'node_modules', 'ruflo', 'package.json'");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#2215 — system_info and hooks_intelligence agree on flashAttention', () => {
|
||||||
|
it('reports the SAME boolean for flashAttention', async () => {
|
||||||
|
const sysInfo = await findTool(systemTools, 'system_info').handler({});
|
||||||
|
const intel = await findTool(hooksTools, 'hooks_intelligence').handler({ showStatus: true });
|
||||||
|
|
||||||
|
const sysFlash = sysInfo.features.flashAttention;
|
||||||
|
expect(typeof sysFlash).toBe('boolean');
|
||||||
|
|
||||||
|
// hooks_intelligence exposes the authoritative runtime status; both must match.
|
||||||
|
const comp = intel?.components?.flashAttention;
|
||||||
|
if (!comp) return; // intelligence registry unavailable in isolation — skip
|
||||||
|
const intelFlash = comp.status === 'active';
|
||||||
|
expect(sysFlash).toBe(intelFlash);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#2222 — route feedback persists the Q-table to disk', () => {
|
||||||
|
it('writes the model after a single update + explicit save', async () => {
|
||||||
|
const dir = mkdtempSync(path.join(tmpdir(), 'ruflo-qlearn-'));
|
||||||
|
const modelPath = path.join(dir, 'q-learning-model.json');
|
||||||
|
try {
|
||||||
|
const router = createQLearningRouter({ modelPath, autoSaveInterval: 100 });
|
||||||
|
await router.initialize();
|
||||||
|
expect(existsSync(modelPath)).toBe(false); // nothing saved yet
|
||||||
|
|
||||||
|
// Mirror the FIXED feedback handler: one update, then explicit awaited save.
|
||||||
|
router.update('implement auth', 'coder', -1);
|
||||||
|
const persisted = await router.saveModel();
|
||||||
|
|
||||||
|
expect(persisted).toBe(true);
|
||||||
|
expect(existsSync(modelPath)).toBe(true);
|
||||||
|
const model = JSON.parse(readFileSync(modelPath, 'utf-8'));
|
||||||
|
expect(model.stats.updateCount).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(Object.keys(model.qTable).length).toBeGreaterThan(0);
|
||||||
|
} finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#2226 — pattern store and search share a backend', () => {
|
||||||
|
it('stores a pattern and finds it via search', async () => {
|
||||||
|
const marker = `oauth-refresh-token-rotation-${process.pid}-${process.hrtime.bigint()}`;
|
||||||
|
// Backend init (registry + ONNX embeddings) can be slow on first load.
|
||||||
|
|
||||||
|
const stored = await agentdbPatternStore.handler({
|
||||||
|
pattern: `Use ${marker} for secure session renewal`,
|
||||||
|
type: 'auth-pattern',
|
||||||
|
confidence: 0.9,
|
||||||
|
});
|
||||||
|
if (!stored || stored.success !== true) return; // backend unavailable in isolation — skip
|
||||||
|
|
||||||
|
const found = await agentdbPatternSearch.handler({
|
||||||
|
query: marker,
|
||||||
|
topK: 5,
|
||||||
|
minConfidence: 0.1,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(Array.isArray(found.results)).toBe(true);
|
||||||
|
// Result text lives in `content` (reasoningBank path) or `pattern` (fallback path).
|
||||||
|
const hit = found.results.find((r: any) => {
|
||||||
|
const text = typeof r.content === 'string' ? r.content
|
||||||
|
: typeof r.pattern === 'string' ? r.pattern : '';
|
||||||
|
return text.includes(marker);
|
||||||
|
});
|
||||||
|
expect(hit).toBeDefined();
|
||||||
|
}, 60_000);
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@claude-flow/cli",
|
"name": "@claude-flow/cli",
|
||||||
"version": "3.10.5",
|
"version": "3.10.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
|
|||||||
@@ -438,7 +438,17 @@ const feedbackCommand: Command = {
|
|||||||
const clampedReward = Math.max(-1, Math.min(1, reward));
|
const clampedReward = Math.max(-1, Math.min(1, reward));
|
||||||
const tdError = router.update(taskDescription, agentId, clampedReward, nextTask);
|
const tdError = router.update(taskDescription, agentId, clampedReward, nextTask);
|
||||||
|
|
||||||
|
// #2222: persist immediately. The Q-learner only auto-saves every
|
||||||
|
// autoSaveInterval updates, but the CLI process exits after this single
|
||||||
|
// feedback call, so without an explicit flush the learned update is lost
|
||||||
|
// and route-learning never improves across invocations. saveModel()
|
||||||
|
// swallows its own errors (returns false), so this await cannot throw.
|
||||||
|
const persisted = await router.saveModel();
|
||||||
|
|
||||||
output.printSuccess(`Feedback recorded for agent "${agent.name}"`);
|
output.printSuccess(`Feedback recorded for agent "${agent.name}"`);
|
||||||
|
if (!persisted) {
|
||||||
|
output.printWarning('Feedback applied but the model could not be persisted to disk.');
|
||||||
|
}
|
||||||
output.writeln();
|
output.writeln();
|
||||||
output.printBox([
|
output.printBox([
|
||||||
`Task: ${taskDescription}`,
|
`Task: ${taskDescription}`,
|
||||||
|
|||||||
@@ -372,6 +372,20 @@ function getPkgVersion() {
|
|||||||
path.join(CWD, 'node_modules', 'ruflo', 'package.json'),
|
path.join(CWD, 'node_modules', 'ruflo', 'package.json'),
|
||||||
path.join(CWD, 'v3', '@claude-flow', 'cli', 'package.json'),
|
path.join(CWD, 'v3', '@claude-flow', 'cli', 'package.json'),
|
||||||
];
|
];
|
||||||
|
// #2221: global installs (npm i -g ruflo) live outside CWD/node_modules, so the
|
||||||
|
// probes above all miss and the version falls back to the hard-coded default.
|
||||||
|
// Derive the global node_modules dir from the running node binary (no npm spawn —
|
||||||
|
// statusline renders often). Covers nvm/mise (bin/../lib/node_modules) and Windows
|
||||||
|
// (bin/node_modules) layouts.
|
||||||
|
try {
|
||||||
|
const binDir = path.dirname(process.execPath);
|
||||||
|
for (const gm of [path.join(binDir, '..', 'lib', 'node_modules'), path.join(binDir, 'node_modules')]) {
|
||||||
|
pkgPaths.push(
|
||||||
|
path.join(gm, 'ruflo', 'package.json'),
|
||||||
|
path.join(gm, '@claude-flow', 'cli', 'package.json'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch { /* ignore */ }
|
||||||
for (const p of pkgPaths) {
|
for (const p of pkgPaths) {
|
||||||
if (!fs.existsSync(p)) continue;
|
if (!fs.existsSync(p)) continue;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export const agentdbPatternSearch: MCPTool = {
|
|||||||
// against each entry's pattern text. Deterministic; survives
|
// against each entry's pattern text. Deterministic; survives
|
||||||
// embedding-index latency and threshold tuning.
|
// embedding-index latency and threshold tuning.
|
||||||
try {
|
try {
|
||||||
const { searchEntries, listEntries } = await import('../memory/memory-initializer.js');
|
const { searchEntries, listEntries, getEntry } = await import('../memory/memory-initializer.js');
|
||||||
|
|
||||||
const parseEntry = (e: Record<string, unknown>): Record<string, unknown> | null => {
|
const parseEntry = (e: Record<string, unknown>): Record<string, unknown> | null => {
|
||||||
const raw = typeof e.content === 'string' ? e.content : (e as { value?: unknown }).value;
|
const raw = typeof e.content === 'string' ? e.content : (e as { value?: unknown }).value;
|
||||||
@@ -236,14 +236,29 @@ export const agentdbPatternSearch: MCPTool = {
|
|||||||
.filter((r): r is Record<string, unknown> => r !== null);
|
.filter((r): r is Record<string, unknown> => r !== null);
|
||||||
} catch { /* fall through to tier 2 */ }
|
} catch { /* fall through to tier 2 */ }
|
||||||
|
|
||||||
// Tier 2 — substring scan (catches just-written entries before HNSW indexes them)
|
// Tier 2 — substring scan (catches just-written entries before HNSW indexes them).
|
||||||
|
// #2226: listEntries returns metadata only (no content/value — see open #2014),
|
||||||
|
// so parseEntry would always null out here. Fetch each entry's content by key via
|
||||||
|
// getEntry (which DOES return content) before parsing/matching. This is the path
|
||||||
|
// that actually runs when the ReasoningBank controller is unavailable (the common
|
||||||
|
// real-world state), so a stored pattern is now findable by search.
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
tier = 'substring';
|
tier = 'substring';
|
||||||
const all = await listEntries({ namespace: 'pattern', limit: 200 });
|
const all = await listEntries({ namespace: 'pattern', limit: 200 });
|
||||||
const qLower = query.toLowerCase();
|
const qLower = query.toLowerCase();
|
||||||
const matched: Array<Record<string, unknown>> = [];
|
const matched: Array<Record<string, unknown>> = [];
|
||||||
for (const e of (all?.entries ?? [])) {
|
for (const e of (all?.entries ?? [])) {
|
||||||
const parsed = parseEntry(e as Record<string, unknown>);
|
const meta = e as Record<string, unknown>;
|
||||||
|
let entry: Record<string, unknown> = meta;
|
||||||
|
// If the listing lacks content, hydrate it from the keyed store.
|
||||||
|
if (typeof meta.content !== 'string' && typeof (meta as { value?: unknown }).value !== 'string') {
|
||||||
|
const key = typeof meta.key === 'string' ? meta.key : (typeof meta.id === 'string' ? meta.id : null);
|
||||||
|
if (!key) continue;
|
||||||
|
const got = await getEntry({ key, namespace: 'pattern' });
|
||||||
|
if (!got?.found || !got.entry) continue;
|
||||||
|
entry = got.entry as unknown as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
const parsed = parseEntry(entry);
|
||||||
if (!parsed) continue;
|
if (!parsed) continue;
|
||||||
const text = typeof parsed.pattern === 'string' ? parsed.pattern.toLowerCase() : '';
|
const text = typeof parsed.pattern === 'string' ? parsed.pattern.toLowerCase() : '';
|
||||||
if (text.includes(qLower)) matched.push(parsed);
|
if (text.includes(qLower)) matched.push(parsed);
|
||||||
|
|||||||
@@ -497,6 +497,17 @@ export const systemTools: MCPTool[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
|
// #2215: flashAttention must reflect the runtime probe, not a stale literal.
|
||||||
|
// Same source-of-truth as hooks_intelligence / neural_status so the tools
|
||||||
|
// can never report contradictory state for the same daemon.
|
||||||
|
let flashAttentionAvailable = false;
|
||||||
|
try {
|
||||||
|
const { getFlashAttention } = await import('@claude-flow/neural');
|
||||||
|
flashAttentionAvailable = getFlashAttention() !== null;
|
||||||
|
} catch {
|
||||||
|
flashAttentionAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version: PKG_VERSION,
|
version: PKG_VERSION,
|
||||||
nodeVersion: process.version,
|
nodeVersion: process.version,
|
||||||
@@ -511,7 +522,7 @@ export const systemTools: MCPTool[] = [
|
|||||||
neural: true,
|
neural: true,
|
||||||
hnsw: true,
|
hnsw: true,
|
||||||
quantization: true,
|
quantization: true,
|
||||||
flashAttention: false,
|
flashAttention: flashAttentionAvailable,
|
||||||
},
|
},
|
||||||
limits: {
|
limits: {
|
||||||
maxAgents: 100,
|
maxAgents: 100,
|
||||||
|
|||||||
@@ -1473,6 +1473,42 @@ export async function bridgeSearchPatterns(options: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #2226 — the wired-in LocalReasoningBank implements store() + findSimilar()/getAll()
|
||||||
|
// but NOT searchPatterns()/search(). bridgeStorePattern commits patterns to its
|
||||||
|
// store(), so search MUST read the SAME backend or stored patterns are never found
|
||||||
|
// (previously search fell through to the disjoint sql.js 'pattern' namespace, which
|
||||||
|
// the store never wrote to → always-empty results). Adapt findSimilar (semantic) with
|
||||||
|
// a getAll() substring fallback so freshly-stored patterns are visible. This mirrors
|
||||||
|
// what hooks_intelligence_pattern-search already does against the same backend.
|
||||||
|
if (reasoningBank && typeof reasoningBank.findSimilar === 'function') {
|
||||||
|
const k = options.topK || 5;
|
||||||
|
const threshold = options.minConfidence ?? 0.3;
|
||||||
|
let mapped: Array<{ id: string; content: string; score: number }> = [];
|
||||||
|
try {
|
||||||
|
const { generateEmbedding } = await import('./memory-initializer.js');
|
||||||
|
const qEmb = await generateEmbedding(options.query);
|
||||||
|
if (qEmb && Array.isArray(qEmb.embedding) && qEmb.embedding.length > 0) {
|
||||||
|
const hits = reasoningBank.findSimilar(qEmb.embedding, { k, threshold });
|
||||||
|
mapped = (Array.isArray(hits) ? hits : []).map((r: any) => ({
|
||||||
|
id: r.id ?? '',
|
||||||
|
content: r.content ?? '',
|
||||||
|
score: r.confidence ?? r.score ?? 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch { /* embedding unavailable — fall through to substring scan */ }
|
||||||
|
|
||||||
|
// Deterministic substring fallback over the same in-memory store.
|
||||||
|
if (mapped.length === 0 && typeof reasoningBank.getAll === 'function') {
|
||||||
|
const q = options.query.toLowerCase();
|
||||||
|
mapped = (reasoningBank.getAll() as any[])
|
||||||
|
.filter((p: any) => typeof p.content === 'string' && p.content.toLowerCase().includes(q))
|
||||||
|
.slice(0, k)
|
||||||
|
.map((p: any) => ({ id: p.id ?? '', content: p.content ?? '', score: p.confidence ?? 0 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { results: mapped, controller: 'reasoningBank' };
|
||||||
|
}
|
||||||
|
|
||||||
// Fallback: search via bridge
|
// Fallback: search via bridge
|
||||||
const result = await bridgeSearchEntries({
|
const result = await bridgeSearchEntries({
|
||||||
query: options.query,
|
query: options.query,
|
||||||
|
|||||||
Reference in new issue
Block a user