@claude-flow/cli 3.56.9 → 3.56.10
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-version +1 -1
- package/.claude/helpers/helpers.manifest.json +3 -3
- package/.claude/helpers/intelligence.cjs +51 -9
- package/.claude/helpers/router.cjs +0 -0
- package/.claude/skills/verification-quality/SKILL.md +14 -11
- package/README.md +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/autopilot-state.d.ts +2 -0
- package/dist/src/autopilot-state.js +36 -8
- package/dist/src/command-resolution.d.ts +15 -0
- package/dist/src/command-resolution.js +25 -0
- package/dist/src/commands/agent.js +17 -17
- package/dist/src/commands/claims.js +6 -8
- package/dist/src/commands/daemon.js +86 -31
- package/dist/src/commands/memory.js +14 -2
- package/dist/src/commands/security-code-patterns.d.ts +2 -2
- package/dist/src/commands/security-code-patterns.js +61 -3
- package/dist/src/commands/security.d.ts +24 -0
- package/dist/src/commands/security.js +81 -35
- package/dist/src/commands/tui.d.ts +9 -0
- package/dist/src/commands/tui.js +29 -3
- package/dist/src/config/proven-config-refresh.d.ts +9 -0
- package/dist/src/config/proven-config-refresh.js +24 -3
- package/dist/src/funnel/local-signals.js +2 -1
- package/dist/src/index.js +6 -42
- package/dist/src/init/types.js +1 -1
- package/dist/src/mcp-tools/agent-execute-core.d.ts +2 -0
- package/dist/src/mcp-tools/agent-execute-core.js +15 -0
- package/dist/src/mcp-tools/agent-health-metrics.d.ts +22 -0
- package/dist/src/mcp-tools/agent-health-metrics.js +33 -0
- package/dist/src/mcp-tools/agent-tools.js +45 -22
- package/dist/src/mcp-tools/agentbbs-federation.d.ts +1 -1
- package/dist/src/mcp-tools/agentbbs-federation.js +7 -2
- package/dist/src/mcp-tools/agentdb-tools.js +25 -2
- package/dist/src/mcp-tools/aidefence-known-misses.d.ts +22 -0
- package/dist/src/mcp-tools/aidefence-known-misses.js +57 -0
- package/dist/src/mcp-tools/analyze-tools.js +10 -11
- package/dist/src/mcp-tools/autopilot-tools.js +2 -2
- package/dist/src/mcp-tools/browser-intent-tools.js +6 -2
- package/dist/src/mcp-tools/embeddings-tools.js +29 -7
- package/dist/src/mcp-tools/hooks-tools.js +4 -0
- package/dist/src/mcp-tools/memory-tools.js +70 -20
- package/dist/src/mcp-tools/neural-tools.js +28 -4
- package/dist/src/mcp-tools/ruvllm-tools.js +6 -0
- package/dist/src/mcp-tools/security-tools.js +55 -17
- package/dist/src/mcp-tools/workflow-tools.js +66 -17
- package/dist/src/memory/intelligence.d.ts +5 -1
- package/dist/src/memory/intelligence.js +105 -29
- package/dist/src/memory/memory-bridge.js +10 -3
- package/dist/src/memory/memory-initializer.js +2 -1
- package/dist/src/memory/search-defaults.d.ts +54 -0
- package/dist/src/memory/search-defaults.js +37 -0
- package/dist/src/memory/sona-optimizer.d.ts +28 -0
- package/dist/src/memory/sona-optimizer.js +59 -2
- package/dist/src/mods/command-registry/schema.d.ts +24 -24
- package/dist/src/ruvector/agent-wasm.d.ts +10 -0
- package/dist/src/ruvector/agent-wasm.js +27 -1
- package/dist/src/ruvector/diff-classifier.d.ts +2 -4
- package/dist/src/ruvector/diff-classifier.js +202 -69
- package/dist/src/ruvector/ruvllm-wasm.d.ts +4 -0
- package/dist/src/ruvector/ruvllm-wasm.js +12 -1
- package/dist/src/services/policy-runtime.d.ts +1 -1
- package/dist/src/services/policy-runtime.js +7 -1
- package/dist/src/services/worker-daemon.d.ts +11 -0
- package/dist/src/services/worker-daemon.js +37 -0
- package/dist/src/transfer/store/discovery.js +16 -17
- package/dist/src/transfer/store/types.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
- package/package.json +4 -4
- package/plugins/ruflo-console/dist/tui.mjs +162 -161
- package/plugins/ruflo-metaharness/.claude-flow/daemon-state.json +179 -0
- package/plugins/ruflo-metaharness/.claude-flow/daemon.pid +1 -0
- package/plugins/ruflo-metaharness/.claude-flow/logs/daemon.log +32 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/backup.json +23 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/codebase-map.json +11 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/consolidation.json +16 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/harness-loop.json +91 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/performance.json +17 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/security-audit.json +12 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/test-gaps.json +8 -0
- package/plugins/ruflo-metaharness/.claude-flow/neural/patterns.json +2809 -0
- package/plugins/ruflo-metaharness/.claude-flow/neural/stats.json +6 -0
- package/plugins/ruflo-metaharness/.claude-flow/policy/state.json +11 -0
- package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db-shm +0 -0
- package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db-wal +0 -0
- package/plugins/ruflo-metaharness/.swarm/backups/memory-2026-10-14T16-09-38-164Z.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/backups/store-agentdb-memory.db-2026-10-14T16-09-38-166Z.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/ewc-fisher.json +397 -0
- package/plugins/ruflo-metaharness/.swarm/memory.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/memory.db-shm +0 -0
- package/plugins/ruflo-metaharness/.swarm/memory.db-wal +0 -0
- package/plugins/ruflo-metaharness/agentdb.rvf +0 -0
- package/plugins/ruflo-metaharness/agentdb.rvf.lock +0 -0
- package/plugins/ruflo-metaharness/ruvector.db +0 -0
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module v3/cli/intelligence
|
|
12
12
|
*/
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import { createRequire } from 'node:module';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
17
|
import { resolveTrainingBackend } from '../ruvector/lora-adapter.js';
|
|
18
|
+
import { getProjectCwd } from '../mcp-tools/types.js';
|
|
18
19
|
// ============================================================================
|
|
19
20
|
// Persistence Configuration
|
|
20
21
|
// ============================================================================
|
|
@@ -24,7 +25,11 @@ import { resolveTrainingBackend } from '../ruvector/lora-adapter.js';
|
|
|
24
25
|
* falling back to home directory
|
|
25
26
|
*/
|
|
26
27
|
function getDataDir() {
|
|
27
|
-
|
|
28
|
+
// Every MCP tool resolves `.claude-flow/` against the project root
|
|
29
|
+
// (CLAUDE_FLOW_CWD when set), so the neural stats/patterns must too —
|
|
30
|
+
// otherwise an MCP server whose process cwd differs from the project
|
|
31
|
+
// writes learning state where nothing reads it.
|
|
32
|
+
const cwd = getProjectCwd();
|
|
28
33
|
const localDir = join(cwd, '.claude-flow', 'neural');
|
|
29
34
|
const homeDir = join(homedir(), '.claude-flow', 'neural');
|
|
30
35
|
// Prefer local directory if .claude-flow exists
|
|
@@ -429,6 +434,10 @@ class LocalReasoningBank {
|
|
|
429
434
|
* Deduplicates by content: if a pattern with the same content already
|
|
430
435
|
* exists, the existing entry is updated (bumped usageCount, higher
|
|
431
436
|
* confidence wins, refreshed lastUsedAt) instead of adding a duplicate.
|
|
437
|
+
*
|
|
438
|
+
* Returns true only when a NEW pattern was added (#3979 item 4): callers that
|
|
439
|
+
* keep a "patterns learned" counter must not count a merge or an update of an
|
|
440
|
+
* existing pattern as a newly learned one.
|
|
432
441
|
*/
|
|
433
442
|
store(pattern) {
|
|
434
443
|
const now = Date.now();
|
|
@@ -448,6 +457,9 @@ class LocalReasoningBank {
|
|
|
448
457
|
if (idx >= 0) {
|
|
449
458
|
this.patternList[idx] = stored;
|
|
450
459
|
}
|
|
460
|
+
this.patterns.set(pattern.id, stored);
|
|
461
|
+
this.saveToDisk();
|
|
462
|
+
return false;
|
|
451
463
|
}
|
|
452
464
|
else {
|
|
453
465
|
// Check for content-duplicate before inserting a new entry
|
|
@@ -462,7 +474,7 @@ class LocalReasoningBank {
|
|
|
462
474
|
// Keep the Map in sync with the mutated object
|
|
463
475
|
this.patterns.set(contentDupe.id, contentDupe);
|
|
464
476
|
this.saveToDisk();
|
|
465
|
-
return;
|
|
477
|
+
return false;
|
|
466
478
|
}
|
|
467
479
|
// Evict oldest if at capacity
|
|
468
480
|
if (this.patterns.size >= this.maxSize) {
|
|
@@ -476,6 +488,7 @@ class LocalReasoningBank {
|
|
|
476
488
|
this.patterns.set(pattern.id, stored);
|
|
477
489
|
// Trigger persistence (debounced)
|
|
478
490
|
this.saveToDisk();
|
|
491
|
+
return true;
|
|
479
492
|
}
|
|
480
493
|
/**
|
|
481
494
|
* Find similar patterns by embedding.
|
|
@@ -629,43 +642,92 @@ let globalStats = {
|
|
|
629
642
|
signalsProcessed: 0,
|
|
630
643
|
lastAdaptation: null
|
|
631
644
|
};
|
|
632
|
-
// ============================================================================
|
|
633
|
-
// Stats Persistence
|
|
634
|
-
// ============================================================================
|
|
635
645
|
/**
|
|
636
|
-
*
|
|
646
|
+
* Increments made by THIS process that have not yet been merged into
|
|
647
|
+
* stats.json. Persistence is delta-based: several processes (MCP server,
|
|
648
|
+
* hook one-shots, the worker daemon) share one stats.json, and each holds
|
|
649
|
+
* its own module-level globalStats. Writing the local snapshot verbatim let
|
|
650
|
+
* any writer erase every other writer's progress — notably the
|
|
651
|
+
* memory-bridge `recordSignalProcessed()` path, which persists without ever
|
|
652
|
+
* loading the file, reset `trajectoriesRecorded` to 0 on disk every 16
|
|
653
|
+
* writes. Merging deltas makes the counters monotonic across processes.
|
|
637
654
|
*/
|
|
638
|
-
|
|
655
|
+
let pendingDelta = {
|
|
656
|
+
trajectoriesRecorded: 0,
|
|
657
|
+
patternsLearned: 0,
|
|
658
|
+
signalsProcessed: 0,
|
|
659
|
+
};
|
|
660
|
+
let persistedStatsLoaded = false;
|
|
661
|
+
function bumpStat(key, n = 1) {
|
|
662
|
+
globalStats[key] = (globalStats[key] ?? 0) + n;
|
|
663
|
+
pendingDelta[key] += n;
|
|
664
|
+
}
|
|
665
|
+
function readStatsFile() {
|
|
639
666
|
try {
|
|
640
667
|
const path = getStatsPath();
|
|
641
|
-
if (existsSync(path))
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
// and signalsProcessed reset to zero on every restart, masking real
|
|
646
|
-
// learning progress in the dashboards.
|
|
647
|
-
globalStats.trajectoriesRecorded = data.trajectoriesRecorded ?? 0;
|
|
648
|
-
globalStats.patternsLearned = data.patternsLearned ?? 0;
|
|
649
|
-
globalStats.signalsProcessed = data.signalsProcessed ?? 0;
|
|
650
|
-
globalStats.lastAdaptation = data.lastAdaptation ?? null;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
668
|
+
if (!existsSync(path))
|
|
669
|
+
return null;
|
|
670
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
671
|
+
return data && typeof data === 'object' ? data : null;
|
|
653
672
|
}
|
|
654
673
|
catch {
|
|
655
|
-
|
|
674
|
+
return null;
|
|
656
675
|
}
|
|
657
676
|
}
|
|
677
|
+
// ============================================================================
|
|
678
|
+
// Stats Persistence
|
|
679
|
+
// ============================================================================
|
|
680
|
+
/**
|
|
681
|
+
* Load persisted stats from disk
|
|
682
|
+
*/
|
|
683
|
+
function loadPersistedStats() {
|
|
684
|
+
persistedStatsLoaded = true;
|
|
685
|
+
const data = readStatsFile();
|
|
686
|
+
if (!data)
|
|
687
|
+
return;
|
|
688
|
+
// #2245: previously only restored trajectoriesRecorded — patternsLearned
|
|
689
|
+
// and signalsProcessed reset to zero on every restart, masking real
|
|
690
|
+
// learning progress in the dashboards. Unsaved local increments are
|
|
691
|
+
// re-applied on top so a late load never drops them.
|
|
692
|
+
globalStats.trajectoriesRecorded = (data.trajectoriesRecorded ?? 0) + pendingDelta.trajectoriesRecorded;
|
|
693
|
+
globalStats.patternsLearned = (data.patternsLearned ?? 0) + pendingDelta.patternsLearned;
|
|
694
|
+
globalStats.signalsProcessed = (data.signalsProcessed ?? 0) + pendingDelta.signalsProcessed;
|
|
695
|
+
globalStats.lastAdaptation = maxTimestamp(data.lastAdaptation ?? null, globalStats.lastAdaptation);
|
|
696
|
+
}
|
|
697
|
+
function maxTimestamp(a, b) {
|
|
698
|
+
if (a == null)
|
|
699
|
+
return b;
|
|
700
|
+
if (b == null)
|
|
701
|
+
return a;
|
|
702
|
+
return Math.max(a, b);
|
|
703
|
+
}
|
|
658
704
|
/**
|
|
659
|
-
* Save stats to disk
|
|
705
|
+
* Save stats to disk: merge this process's unsaved deltas into whatever is
|
|
706
|
+
* currently on disk (read-merge-write, atomic rename), then adopt the merged
|
|
707
|
+
* totals locally so this process also sees other writers' progress.
|
|
660
708
|
*/
|
|
661
709
|
function savePersistedStats() {
|
|
662
710
|
try {
|
|
663
711
|
ensureDataDir();
|
|
664
712
|
const path = getStatsPath();
|
|
665
|
-
|
|
713
|
+
const disk = readStatsFile();
|
|
714
|
+
const merged = disk
|
|
715
|
+
? {
|
|
716
|
+
trajectoriesRecorded: (disk.trajectoriesRecorded ?? 0) + pendingDelta.trajectoriesRecorded,
|
|
717
|
+
patternsLearned: (disk.patternsLearned ?? 0) + pendingDelta.patternsLearned,
|
|
718
|
+
signalsProcessed: (disk.signalsProcessed ?? 0) + pendingDelta.signalsProcessed,
|
|
719
|
+
lastAdaptation: maxTimestamp(disk.lastAdaptation ?? null, globalStats.lastAdaptation),
|
|
720
|
+
}
|
|
721
|
+
: { ...globalStats };
|
|
722
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
723
|
+
writeFileSync(tmp, JSON.stringify(merged, null, 2), 'utf-8');
|
|
724
|
+
renameSync(tmp, path);
|
|
725
|
+
globalStats = merged;
|
|
726
|
+
pendingDelta = { trajectoriesRecorded: 0, patternsLearned: 0, signalsProcessed: 0 };
|
|
727
|
+
persistedStatsLoaded = true;
|
|
666
728
|
}
|
|
667
729
|
catch {
|
|
668
|
-
// Ignore save errors
|
|
730
|
+
// Ignore save errors (deltas are retained for the next save)
|
|
669
731
|
}
|
|
670
732
|
}
|
|
671
733
|
/**
|
|
@@ -679,7 +741,7 @@ function savePersistedStats() {
|
|
|
679
741
|
let signalsSinceLastSave = 0;
|
|
680
742
|
const SIGNAL_PERSIST_EVERY = 16;
|
|
681
743
|
export function recordSignalProcessed() {
|
|
682
|
-
|
|
744
|
+
bumpStat('signalsProcessed');
|
|
683
745
|
signalsSinceLastSave++;
|
|
684
746
|
if (signalsSinceLastSave >= SIGNAL_PERSIST_EVERY) {
|
|
685
747
|
savePersistedStats();
|
|
@@ -881,7 +943,7 @@ export async function recordStep(step) {
|
|
|
881
943
|
await sonaCoordinator.distillLearning(reasoningBank);
|
|
882
944
|
globalStats.lastAdaptation = Date.now();
|
|
883
945
|
}
|
|
884
|
-
|
|
946
|
+
bumpStat('trajectoriesRecorded');
|
|
885
947
|
savePersistedStats();
|
|
886
948
|
return true;
|
|
887
949
|
}
|
|
@@ -928,7 +990,7 @@ export async function recordTrajectory(steps, verdict) {
|
|
|
928
990
|
if (verdict === 'success') {
|
|
929
991
|
for (const step of enrichedSteps) {
|
|
930
992
|
if (step.embedding && step.embedding.length > 0) {
|
|
931
|
-
reasoningBank.store({
|
|
993
|
+
const added = reasoningBank.store({
|
|
932
994
|
id: `pattern-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
933
995
|
type: step.type,
|
|
934
996
|
content: step.content,
|
|
@@ -937,7 +999,11 @@ export async function recordTrajectory(steps, verdict) {
|
|
|
937
999
|
metadata: step.metadata || {},
|
|
938
1000
|
createdAt: Date.now(),
|
|
939
1001
|
});
|
|
940
|
-
|
|
1002
|
+
// #3979 item 4: count only patterns that were actually added. A step
|
|
1003
|
+
// whose content already exists is merged into that pattern, so the
|
|
1004
|
+
// counter no longer climbs once per successful step.
|
|
1005
|
+
if (added)
|
|
1006
|
+
bumpStat('patternsLearned');
|
|
941
1007
|
}
|
|
942
1008
|
}
|
|
943
1009
|
}
|
|
@@ -962,7 +1028,7 @@ export async function recordTrajectory(steps, verdict) {
|
|
|
962
1028
|
// ruvllm recording failed silently
|
|
963
1029
|
}
|
|
964
1030
|
}
|
|
965
|
-
|
|
1031
|
+
bumpStat('trajectoriesRecorded');
|
|
966
1032
|
globalStats.lastAdaptation = Date.now();
|
|
967
1033
|
savePersistedStats();
|
|
968
1034
|
return true;
|
|
@@ -1034,6 +1100,12 @@ export function getIntelligenceStats() {
|
|
|
1034
1100
|
if (!ruvllmLoaded) {
|
|
1035
1101
|
loadRuvllmCoordinatorSync();
|
|
1036
1102
|
}
|
|
1103
|
+
// A read-only status process (ruvllm_status, `neural status`, a fresh MCP
|
|
1104
|
+
// server) has never run initializeIntelligence(); without this it reported
|
|
1105
|
+
// 0 trajectories no matter what other processes had persisted.
|
|
1106
|
+
if (!persistedStatsLoaded) {
|
|
1107
|
+
loadPersistedStats();
|
|
1108
|
+
}
|
|
1037
1109
|
const ruvllmStats = ruvllmCoordinator?.stats?.() || null;
|
|
1038
1110
|
// Fetch cross-module stats for unified reporting.
|
|
1039
1111
|
//
|
|
@@ -1145,6 +1217,10 @@ export function clearIntelligence() {
|
|
|
1145
1217
|
signalsProcessed: 0,
|
|
1146
1218
|
lastAdaptation: null
|
|
1147
1219
|
};
|
|
1220
|
+
pendingDelta = { trajectoriesRecorded: 0, patternsLearned: 0, signalsProcessed: 0 };
|
|
1221
|
+
// In-memory reset only: stay "loaded" so the next stats read does not
|
|
1222
|
+
// immediately re-hydrate the counters this call just cleared.
|
|
1223
|
+
persistedStatsLoaded = true;
|
|
1148
1224
|
}
|
|
1149
1225
|
/**
|
|
1150
1226
|
* Benchmark SONA adaptation time
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
import { AppendConditionFailed, assertAppendConditions, validateAppendConditions } from './append-conditions.js';
|
|
20
20
|
import { liveMemoryRowSql } from './live-memory-row.js';
|
|
21
21
|
import { resolveAgentdbBetterSqlite3 } from './shared-sqlite.js';
|
|
22
|
+
import { DEFAULT_SEARCH_THRESHOLD } from './search-defaults.js';
|
|
22
23
|
import { encodeEmbeddingQ8, MAX_LIST_EMBEDDINGS } from './embedding-q8.js';
|
|
23
24
|
import * as path from 'path';
|
|
24
25
|
import * as crypto from 'crypto';
|
|
25
|
-
import { realpathSync } from 'node:fs';
|
|
26
|
+
import { mkdirSync, realpathSync } from 'node:fs';
|
|
26
27
|
import { createRequire } from 'node:module';
|
|
27
28
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
28
29
|
import { validateFeedbackPatterns } from './feedback-patterns.js';
|
|
@@ -265,7 +266,13 @@ async function getRegistry(dbPath) {
|
|
|
265
266
|
return;
|
|
266
267
|
origLog.apply(console, args);
|
|
267
268
|
};
|
|
269
|
+
// #4132: better-sqlite3 cannot create a missing parent directory. In a
|
|
270
|
+
// fresh checkout or worktree `.swarm/` does not exist yet, so AgentDB
|
|
271
|
+
// failed to open, the degraded registry was cached for the server's
|
|
272
|
+
// lifetime and hierarchical memory silently ran volatile.
|
|
268
273
|
try {
|
|
274
|
+
if (resolvedPath !== ':memory:')
|
|
275
|
+
mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
269
276
|
await registry.initialize({
|
|
270
277
|
// #2786: use agentdb-memory.db (plaintext) so native better-sqlite3 doesn't hit the encrypted memory.db.
|
|
271
278
|
dbPath: resolvedPath,
|
|
@@ -1299,7 +1306,7 @@ export async function bridgeSearchEntries(options) {
|
|
|
1299
1306
|
if (!ctx)
|
|
1300
1307
|
return null;
|
|
1301
1308
|
try {
|
|
1302
|
-
const { query: queryStr, namespace, limit = 10, threshold =
|
|
1309
|
+
const { query: queryStr, namespace, limit = 10, threshold = DEFAULT_SEARCH_THRESHOLD, provenanceFilter } = options;
|
|
1303
1310
|
const effectiveNamespace = namespace || 'all';
|
|
1304
1311
|
const startTime = Date.now();
|
|
1305
1312
|
// Generate query embedding — same agentdb-then-local chain as the write
|
|
@@ -1870,7 +1877,7 @@ export async function bridgeSearchBruteForceCosine(queryEmbedding, options, dbPa
|
|
|
1870
1877
|
return null;
|
|
1871
1878
|
try {
|
|
1872
1879
|
const k = options?.k ?? 10;
|
|
1873
|
-
const threshold = options?.threshold ??
|
|
1880
|
+
const threshold = options?.threshold ?? DEFAULT_SEARCH_THRESHOLD;
|
|
1874
1881
|
const nsFilter = options?.namespace && options.namespace !== 'all'
|
|
1875
1882
|
? `AND namespace = ?`
|
|
1876
1883
|
: '';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadBetterSqlite3 } from './shared-sqlite.js';
|
|
2
2
|
import { resolveMemoryRoot } from './memory-root.js';
|
|
3
|
+
import { DEFAULT_SEARCH_THRESHOLD } from './search-defaults.js';
|
|
3
4
|
import { liveMemoryRowSql } from './live-memory-row.js';
|
|
4
5
|
import { encodeEmbeddingQ8, MAX_LIST_EMBEDDINGS } from './embedding-q8.js';
|
|
5
6
|
import * as fs from 'fs';
|
|
@@ -2745,7 +2746,7 @@ export async function searchEntries(options) {
|
|
|
2745
2746
|
return bridgeResult;
|
|
2746
2747
|
}
|
|
2747
2748
|
// Fallback: raw sql.js
|
|
2748
|
-
const { query, namespace, limit = 10, threshold =
|
|
2749
|
+
const { query, namespace, limit = 10, threshold = DEFAULT_SEARCH_THRESHOLD, dbPath: customPath, provenanceFilter } = options;
|
|
2749
2750
|
const effectiveNamespace = namespace || 'all';
|
|
2750
2751
|
const swarmDir = getMemoryRoot();
|
|
2751
2752
|
const dbPath = customPath ? path.resolve(customPath) : path.join(swarmDir, 'memory.db');
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default relevance floor for memory search (#4129).
|
|
3
|
+
*
|
|
4
|
+
* The bundled 384-dim embedding model scores a correct paraphrase match at
|
|
5
|
+
* roughly 0.25-0.5 and unrelated text at 0.0-0.15, so the old 0.3 floor sent
|
|
6
|
+
* back an empty list for natural-language queries whose right answer was
|
|
7
|
+
* clearly the best match (0.28 in the report). 0.2 sits between the two
|
|
8
|
+
* populations. An explicit `threshold` always wins, including 0.
|
|
9
|
+
*
|
|
10
|
+
* #2558 constraint stays: the fused recall score of a full-coverage exact
|
|
11
|
+
* keyword hit with semantic cosine <= 0 tops out at 0.40, so this must stay
|
|
12
|
+
* well below 0.4.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_SEARCH_THRESHOLD = 0.2;
|
|
15
|
+
/** How many below-threshold matches an empty default search reports as hints. */
|
|
16
|
+
export declare const NEAR_MISS_LIMIT = 3;
|
|
17
|
+
interface NearMissSearch {
|
|
18
|
+
(req: {
|
|
19
|
+
query: string;
|
|
20
|
+
namespace?: string;
|
|
21
|
+
limit?: number;
|
|
22
|
+
threshold?: number;
|
|
23
|
+
provenanceFilter?: string[];
|
|
24
|
+
dbPath?: string;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
success: boolean;
|
|
27
|
+
results?: Array<{
|
|
28
|
+
key: string;
|
|
29
|
+
namespace: string;
|
|
30
|
+
score: number;
|
|
31
|
+
}>;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* When a search that used the DEFAULT threshold returns nothing, say what was
|
|
36
|
+
* close instead of leaving the caller to assume the store has no answer
|
|
37
|
+
* (#4129). One extra, cheap query at threshold 0; never throws; absent when
|
|
38
|
+
* nothing at all matched.
|
|
39
|
+
*/
|
|
40
|
+
export declare function nearMisses(search: NearMissSearch, req: {
|
|
41
|
+
query: string;
|
|
42
|
+
namespace?: string;
|
|
43
|
+
provenanceFilter?: string[];
|
|
44
|
+
dbPath?: string;
|
|
45
|
+
}): Promise<{
|
|
46
|
+
nearMisses: Array<{
|
|
47
|
+
key: string;
|
|
48
|
+
namespace: string;
|
|
49
|
+
similarity: number;
|
|
50
|
+
}>;
|
|
51
|
+
hint: string;
|
|
52
|
+
} | undefined>;
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=search-defaults.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default relevance floor for memory search (#4129).
|
|
3
|
+
*
|
|
4
|
+
* The bundled 384-dim embedding model scores a correct paraphrase match at
|
|
5
|
+
* roughly 0.25-0.5 and unrelated text at 0.0-0.15, so the old 0.3 floor sent
|
|
6
|
+
* back an empty list for natural-language queries whose right answer was
|
|
7
|
+
* clearly the best match (0.28 in the report). 0.2 sits between the two
|
|
8
|
+
* populations. An explicit `threshold` always wins, including 0.
|
|
9
|
+
*
|
|
10
|
+
* #2558 constraint stays: the fused recall score of a full-coverage exact
|
|
11
|
+
* keyword hit with semantic cosine <= 0 tops out at 0.40, so this must stay
|
|
12
|
+
* well below 0.4.
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_SEARCH_THRESHOLD = 0.2;
|
|
15
|
+
/** How many below-threshold matches an empty default search reports as hints. */
|
|
16
|
+
export const NEAR_MISS_LIMIT = 3;
|
|
17
|
+
/**
|
|
18
|
+
* When a search that used the DEFAULT threshold returns nothing, say what was
|
|
19
|
+
* close instead of leaving the caller to assume the store has no answer
|
|
20
|
+
* (#4129). One extra, cheap query at threshold 0; never throws; absent when
|
|
21
|
+
* nothing at all matched.
|
|
22
|
+
*/
|
|
23
|
+
export async function nearMisses(search, req) {
|
|
24
|
+
try {
|
|
25
|
+
const r = await search({ ...req, limit: NEAR_MISS_LIMIT, threshold: 0 });
|
|
26
|
+
if (!r?.success || !r.results?.length)
|
|
27
|
+
return undefined;
|
|
28
|
+
return {
|
|
29
|
+
nearMisses: r.results.map(e => ({ key: e.key, namespace: e.namespace, similarity: e.score })),
|
|
30
|
+
hint: `No result reached the default threshold (${DEFAULT_SEARCH_THRESHOLD}); the closest matches are in nearMisses. Pass threshold: 0 to include them in results.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=search-defaults.js.map
|
|
@@ -93,6 +93,8 @@ export interface SONAStats {
|
|
|
93
93
|
agents: number;
|
|
94
94
|
} | 'unavailable';
|
|
95
95
|
}
|
|
96
|
+
/** The routing gate in getRoutingSuggestion: confidence x matchRatio must reach this for a learned pattern to be used. */
|
|
97
|
+
export declare const SONA_ROUTING_GATE = 0.6;
|
|
96
98
|
/** Agent roles supported by the SONA routing optimizer. */
|
|
97
99
|
export declare function isSupportedSonaAgent(agent: string): boolean;
|
|
98
100
|
/**
|
|
@@ -101,6 +103,17 @@ export declare function isSupportedSonaAgent(agent: string): boolean;
|
|
|
101
103
|
* Learns from past task outcomes to improve future routing decisions.
|
|
102
104
|
* Integrates with Q-learning router for hybrid routing strategy.
|
|
103
105
|
*/
|
|
106
|
+
/**
|
|
107
|
+
* How close a pattern's confidence is to the routing gate. `successesNeeded`
|
|
108
|
+
* assumes a perfect keyword match (matchRatio 1) and the 0.1 success increment;
|
|
109
|
+
* a partial match needs more.
|
|
110
|
+
*/
|
|
111
|
+
export declare function routingGateStatus(confidence: number): {
|
|
112
|
+
threshold: number;
|
|
113
|
+
confidence: number;
|
|
114
|
+
reached: boolean;
|
|
115
|
+
successesNeeded: number;
|
|
116
|
+
};
|
|
104
117
|
export declare class SONAOptimizer {
|
|
105
118
|
private patterns;
|
|
106
119
|
private trajectoriesProcessed;
|
|
@@ -140,6 +153,15 @@ export declare class SONAOptimizer {
|
|
|
140
153
|
patternKey: string;
|
|
141
154
|
confidence: number;
|
|
142
155
|
keywordsExtracted: string[];
|
|
156
|
+
/** True when this trajectory reinforced an existing near-identical pattern instead of creating a new one (#4134). */
|
|
157
|
+
reinforcedNeighbour?: boolean;
|
|
158
|
+
/** How far the pattern is from influencing routing, so a user can see learning has not kicked in yet (#4134). */
|
|
159
|
+
routingGate?: {
|
|
160
|
+
threshold: number;
|
|
161
|
+
confidence: number;
|
|
162
|
+
reached: boolean;
|
|
163
|
+
successesNeeded: number;
|
|
164
|
+
};
|
|
143
165
|
};
|
|
144
166
|
/**
|
|
145
167
|
* Get routing suggestion based on learned patterns.
|
|
@@ -202,6 +224,12 @@ export declare class SONAOptimizer {
|
|
|
202
224
|
* Create a unique pattern key from keywords and agent
|
|
203
225
|
*/
|
|
204
226
|
private createPatternKey;
|
|
227
|
+
/**
|
|
228
|
+
* The key of the existing pattern for `agent` whose keyword set overlaps
|
|
229
|
+
* `keywords` the most, if that overlap (Jaccard) reaches NEIGHBOUR_MIN_JACCARD.
|
|
230
|
+
* Ties go to the more confident pattern. Linear in the pattern count (<= 1000).
|
|
231
|
+
*/
|
|
232
|
+
private findNeighbourPattern;
|
|
205
233
|
/**
|
|
206
234
|
* Find the best matching pattern for given keywords
|
|
207
235
|
*/
|
|
@@ -26,6 +26,10 @@ const CONFIDENCE_INCREMENT = 0.1;
|
|
|
26
26
|
const CONFIDENCE_DECREMENT = 0.15;
|
|
27
27
|
const DECAY_RATE = 0.01; // Per day
|
|
28
28
|
const MAX_PATTERNS = 1000;
|
|
29
|
+
/** #4134: a trajectory reinforces an existing pattern of the same agent whose keyword set overlaps at least this much (Jaccard). */
|
|
30
|
+
const NEIGHBOUR_MIN_JACCARD = 0.6;
|
|
31
|
+
/** The routing gate in getRoutingSuggestion: confidence x matchRatio must reach this for a learned pattern to be used. */
|
|
32
|
+
export const SONA_ROUTING_GATE = 0.6;
|
|
29
33
|
// ============================================================================
|
|
30
34
|
// Contrastive Trainer (lazy-loaded from @ruvector/ruvllm)
|
|
31
35
|
// ============================================================================
|
|
@@ -119,6 +123,18 @@ const KEYWORD_CATEGORIES = {
|
|
|
119
123
|
* Learns from past task outcomes to improve future routing decisions.
|
|
120
124
|
* Integrates with Q-learning router for hybrid routing strategy.
|
|
121
125
|
*/
|
|
126
|
+
/**
|
|
127
|
+
* How close a pattern's confidence is to the routing gate. `successesNeeded`
|
|
128
|
+
* assumes a perfect keyword match (matchRatio 1) and the 0.1 success increment;
|
|
129
|
+
* a partial match needs more.
|
|
130
|
+
*/
|
|
131
|
+
export function routingGateStatus(confidence) {
|
|
132
|
+
const reached = confidence >= SONA_ROUTING_GATE;
|
|
133
|
+
const successesNeeded = reached || confidence >= MAX_CONFIDENCE
|
|
134
|
+
? 0
|
|
135
|
+
: Math.ceil(Math.log((1 - SONA_ROUTING_GATE) / (1 - confidence)) / Math.log(1 - CONFIDENCE_INCREMENT));
|
|
136
|
+
return { threshold: SONA_ROUTING_GATE, confidence, reached, successesNeeded };
|
|
137
|
+
}
|
|
122
138
|
export class SONAOptimizer {
|
|
123
139
|
patterns = new Map();
|
|
124
140
|
trajectoriesProcessed = 0;
|
|
@@ -210,7 +226,18 @@ export class SONAOptimizer {
|
|
|
210
226
|
};
|
|
211
227
|
}
|
|
212
228
|
// Create pattern key from sorted keywords
|
|
213
|
-
|
|
229
|
+
let patternKey = this.createPatternKey(keywords, agent);
|
|
230
|
+
// #4134: near-identical tasks ("Verify the X plugin ..." / "Verify the Y plugin ...")
|
|
231
|
+
// used to create a separate pattern each at confidence 0.5, so none ever reached
|
|
232
|
+
// the routing gate. Reinforce the closest existing pattern of the same agent instead.
|
|
233
|
+
let reinforcedNeighbour = false;
|
|
234
|
+
if (!this.patterns.has(patternKey)) {
|
|
235
|
+
const neighbour = this.findNeighbourPattern(keywords, agent);
|
|
236
|
+
if (neighbour) {
|
|
237
|
+
patternKey = neighbour;
|
|
238
|
+
reinforcedNeighbour = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
214
241
|
// Get or create pattern
|
|
215
242
|
let pattern = this.patterns.get(patternKey);
|
|
216
243
|
if (!pattern) {
|
|
@@ -264,6 +291,8 @@ export class SONAOptimizer {
|
|
|
264
291
|
patternKey,
|
|
265
292
|
confidence: pattern.confidence,
|
|
266
293
|
keywordsExtracted: keywords,
|
|
294
|
+
reinforcedNeighbour,
|
|
295
|
+
routingGate: routingGateStatus(pattern.confidence),
|
|
267
296
|
};
|
|
268
297
|
}
|
|
269
298
|
/**
|
|
@@ -305,7 +334,7 @@ export class SONAOptimizer {
|
|
|
305
334
|
const keywords = this.extractKeywords(task);
|
|
306
335
|
// Priority 2: Try SONA learned pattern matching
|
|
307
336
|
const sonaResult = this.findBestPatternMatch(keywords);
|
|
308
|
-
if (sonaResult && sonaResult.confidence >=
|
|
337
|
+
if (sonaResult && sonaResult.confidence >= SONA_ROUTING_GATE) {
|
|
309
338
|
return {
|
|
310
339
|
agent: sonaResult.agent,
|
|
311
340
|
confidence: sonaResult.confidence,
|
|
@@ -532,6 +561,34 @@ export class SONAOptimizer {
|
|
|
532
561
|
const sortedKeywords = [...keywords].sort();
|
|
533
562
|
return `${agent}:${sortedKeywords.join('+')}`;
|
|
534
563
|
}
|
|
564
|
+
/**
|
|
565
|
+
* The key of the existing pattern for `agent` whose keyword set overlaps
|
|
566
|
+
* `keywords` the most, if that overlap (Jaccard) reaches NEIGHBOUR_MIN_JACCARD.
|
|
567
|
+
* Ties go to the more confident pattern. Linear in the pattern count (<= 1000).
|
|
568
|
+
*/
|
|
569
|
+
findNeighbourPattern(keywords, agent) {
|
|
570
|
+
const wanted = new Set(keywords);
|
|
571
|
+
let bestKey = null;
|
|
572
|
+
let bestJaccard = 0;
|
|
573
|
+
let bestConfidence = -1;
|
|
574
|
+
for (const [key, pattern] of this.patterns) {
|
|
575
|
+
if (pattern.agent !== agent)
|
|
576
|
+
continue;
|
|
577
|
+
let shared = 0;
|
|
578
|
+
for (const k of pattern.keywords)
|
|
579
|
+
if (wanted.has(k))
|
|
580
|
+
shared++;
|
|
581
|
+
const union = wanted.size + pattern.keywords.length - shared;
|
|
582
|
+
const jaccard = union === 0 ? 0 : shared / union;
|
|
583
|
+
if (jaccard >= NEIGHBOUR_MIN_JACCARD &&
|
|
584
|
+
(jaccard > bestJaccard || (jaccard === bestJaccard && pattern.confidence > bestConfidence))) {
|
|
585
|
+
bestKey = key;
|
|
586
|
+
bestJaccard = jaccard;
|
|
587
|
+
bestConfidence = pattern.confidence;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return bestKey;
|
|
591
|
+
}
|
|
535
592
|
/**
|
|
536
593
|
* Find the best matching pattern for given keywords
|
|
537
594
|
*/
|