claude-flow 3.17.0 → 3.18.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 +12 -6
- package/v3/@claude-flow/cli/dist/src/commands/neural.js +16 -6
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +52 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-session-tools.d.ts +5 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-session-tools.js +92 -18
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +131 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +119 -84
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +20 -5
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +15 -2
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +64 -10
- package/v3/@claude-flow/cli/dist/src/ruvector/lora-adapter.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/lora-adapter.js +21 -3
- package/v3/@claude-flow/cli/dist/src/ruvector/output-verifier.d.ts +83 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/output-verifier.js +277 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/trajectory-tree.d.ts +113 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/trajectory-tree.js +237 -0
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -15,99 +15,120 @@ import { getProjectCwd } from './types.js';
|
|
|
15
15
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
16
16
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
|
-
//
|
|
19
|
-
//
|
|
18
|
+
// Real embeddings — resolved LAZILY on first use.
|
|
19
|
+
// Perf (measured 2026-07): the previous top-level-await version of this block
|
|
20
|
+
// ran `await import('ruvector')` + `initOnnxEmbedder()` + a probe embed at
|
|
21
|
+
// module-import time, adding ~450ms (warm) to ~2800ms (cold) to EVERY CLI
|
|
22
|
+
// command, because mcp-client statically imports this module. ensureEmbeddings()
|
|
23
|
+
// keeps the exact same tier chain and degraded/fallback semantics, but only
|
|
24
|
+
// pays the cost when a handler actually needs an embedding.
|
|
25
|
+
// Tier 0 (ADR-089): ruvector@0.2.27 bundled ONNX (no sharp dep, fixes ADR-086's
|
|
20
26
|
// silent-fallback bug at source; closes the chain described in ruvnet/ruvector#523).
|
|
21
27
|
// Tier 1: agentic-flow v3 ReasoningBank (was Tier 1 — broken on darwin-arm64 without sharp)
|
|
22
28
|
// Tier 2-3: @claude-flow/embeddings
|
|
23
29
|
let realEmbeddings = null;
|
|
24
30
|
let embeddingServiceName = 'none';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
let embeddingsPromise = null;
|
|
32
|
+
/**
|
|
33
|
+
* Memoized lazy initialiser for the embedding provider chain. Safe to call
|
|
34
|
+
* concurrently (single shared promise). If every tier fails to import or
|
|
35
|
+
* probe, `realEmbeddings` stays null and callers use the explicit
|
|
36
|
+
* hash-fallback path — identical degraded semantics to the old eager block.
|
|
37
|
+
*/
|
|
38
|
+
function ensureEmbeddings() {
|
|
39
|
+
if (embeddingsPromise)
|
|
40
|
+
return embeddingsPromise;
|
|
41
|
+
embeddingsPromise = (async () => {
|
|
33
42
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// Tier 2: @claude-flow/embeddings with agentic-flow provider
|
|
66
|
-
if (!realEmbeddings) {
|
|
67
|
-
const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
|
|
68
|
-
if (embeddingsModule?.createEmbeddingService) {
|
|
69
|
-
try {
|
|
70
|
-
const service = embeddingsModule.createEmbeddingService({ provider: 'agentic-flow' });
|
|
71
|
-
realEmbeddings = {
|
|
72
|
-
embed: async (text) => {
|
|
73
|
-
const result = await service.embed(text);
|
|
74
|
-
return Array.from(result.embedding);
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
embeddingServiceName = 'agentic-flow';
|
|
43
|
+
// Tier 0: ruvector@0.2.27 — bundled all-MiniLM-L6-v2 + parallel worker pool.
|
|
44
|
+
// Probe with isOnnxAvailable() and verify an actual embed succeeds (avoids
|
|
45
|
+
// the type-load-success-but-runtime-fails trap from ADR-086). The probe now
|
|
46
|
+
// runs on first embed request instead of at import time.
|
|
47
|
+
// NOTE: ruvector's embed() returns `{embedding, dimension, timeMs}` — we
|
|
48
|
+
// unwrap to plain number[] for the shared interface.
|
|
49
|
+
const rv = await import('ruvector').catch(() => null);
|
|
50
|
+
if (rv?.embed && typeof rv.embed === 'function' && rv.isOnnxAvailable?.()) {
|
|
51
|
+
try {
|
|
52
|
+
if (typeof rv.initOnnxEmbedder === 'function')
|
|
53
|
+
await rv.initOnnxEmbedder();
|
|
54
|
+
const probe = await rv.embed('probe');
|
|
55
|
+
// Handle both shapes: ruvector wraps as {embedding, dimension, timeMs};
|
|
56
|
+
// some versions returned raw Float32Array.
|
|
57
|
+
const probeVec = probe?.embedding ?? probe;
|
|
58
|
+
if (probeVec && (Array.isArray(probeVec) || probeVec.length > 0)) {
|
|
59
|
+
realEmbeddings = {
|
|
60
|
+
embed: async (text) => {
|
|
61
|
+
const r = await rv.embed(text);
|
|
62
|
+
const v = r?.embedding ?? r;
|
|
63
|
+
return Array.isArray(v) ? v : Array.from(v);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
embeddingServiceName = 'ruvector@0.2.27 (bundled all-MiniLM-L6-v2)';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// ruvector embed failed at runtime; fall through to next tier
|
|
71
|
+
}
|
|
78
72
|
}
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
// Tier 1: agentic-flow v3 ReasoningBank (kept for backward-compat; may
|
|
74
|
+
// silently fall back on darwin-arm64 without sharp — that's the bug
|
|
75
|
+
// Tier 0 was added to bypass).
|
|
76
|
+
if (!realEmbeddings) {
|
|
77
|
+
const rb = await import('agentic-flow/reasoningbank').catch(() => null);
|
|
78
|
+
if (rb?.computeEmbedding) {
|
|
79
|
+
realEmbeddings = { embed: (text) => rb.computeEmbedding(text) };
|
|
80
|
+
embeddingServiceName = 'agentic-flow/reasoningbank';
|
|
81
|
+
}
|
|
81
82
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
// Tier 2: @claude-flow/embeddings with agentic-flow provider
|
|
84
|
+
if (!realEmbeddings) {
|
|
85
|
+
const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
|
|
86
|
+
if (embeddingsModule?.createEmbeddingService) {
|
|
87
|
+
try {
|
|
88
|
+
const service = embeddingsModule.createEmbeddingService({ provider: 'agentic-flow' });
|
|
89
|
+
realEmbeddings = {
|
|
90
|
+
embed: async (text) => {
|
|
91
|
+
const result = await service.embed(text);
|
|
92
|
+
return Array.from(result.embedding);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
embeddingServiceName = 'agentic-flow';
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// agentic-flow provider not available, try ONNX
|
|
99
|
+
}
|
|
100
|
+
}
|
|
97
101
|
}
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
// Tier 3: @claude-flow/embeddings with ONNX provider
|
|
103
|
+
if (!realEmbeddings) {
|
|
104
|
+
const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
|
|
105
|
+
if (embeddingsModule?.createEmbeddingService) {
|
|
106
|
+
try {
|
|
107
|
+
const service = embeddingsModule.createEmbeddingService({ provider: 'onnx' });
|
|
108
|
+
realEmbeddings = {
|
|
109
|
+
embed: async (text) => {
|
|
110
|
+
const result = await service.embed(text);
|
|
111
|
+
return Array.from(result.embedding);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
embeddingServiceName = 'onnx';
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// ONNX provider not available, fall through to mock
|
|
118
|
+
}
|
|
119
|
+
}
|
|
100
120
|
}
|
|
121
|
+
// No Tier 4 mock fallback. If all real-embedder tiers fail to import or
|
|
122
|
+
// probe, leave realEmbeddings null and let downstream code use the
|
|
123
|
+
// explicit hash-fallback path with a clear _embeddingNote in stats.
|
|
124
|
+
// Silently substituting mock embeddings would hide a missing production
|
|
125
|
+
// dependency from callers — that's the bug ADR-086 was about.
|
|
101
126
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// dependency from callers — that's the bug ADR-086 was about.
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
// No embedding provider available, will use fallback
|
|
127
|
+
catch {
|
|
128
|
+
// No embedding provider available, will use fallback
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
return embeddingsPromise;
|
|
111
132
|
}
|
|
112
133
|
// Storage paths
|
|
113
134
|
const STORAGE_DIR = '.claude-flow';
|
|
@@ -173,8 +194,9 @@ export function getNeuralStoreStats() {
|
|
|
173
194
|
export async function storeNeuralPatterns(items) {
|
|
174
195
|
if (!items || items.length === 0)
|
|
175
196
|
return { stored: 0, total: 0 };
|
|
176
|
-
// realEmbeddings is initialised by
|
|
177
|
-
// generateEmbedding() falls back to a hash-based embedding if
|
|
197
|
+
// realEmbeddings is initialised lazily by ensureEmbeddings() (invoked from
|
|
198
|
+
// generateEmbedding()); it falls back to a hash-based embedding if no
|
|
199
|
+
// provider is available.
|
|
178
200
|
const store = loadNeuralStore();
|
|
179
201
|
let stored = 0;
|
|
180
202
|
for (const item of items) {
|
|
@@ -200,6 +222,10 @@ export async function storeNeuralPatterns(items) {
|
|
|
200
222
|
}
|
|
201
223
|
// Generate embedding - uses real ML embeddings if available, falls back to deterministic hash
|
|
202
224
|
async function generateEmbedding(text, dims = 384) {
|
|
225
|
+
// Lazily resolve the embedding provider on first real use (perf: keeps
|
|
226
|
+
// ONNX init off the CLI startup path — see ensureEmbeddings()).
|
|
227
|
+
if (text)
|
|
228
|
+
await ensureEmbeddings();
|
|
203
229
|
// If real embeddings available and text provided, use them
|
|
204
230
|
if (realEmbeddings && text) {
|
|
205
231
|
try {
|
|
@@ -747,6 +773,9 @@ export const neuralTools = [
|
|
|
747
773
|
if (!v.valid)
|
|
748
774
|
return { success: false, error: v.error };
|
|
749
775
|
}
|
|
776
|
+
// Resolve provider so embeddingProvider in the result matches the old
|
|
777
|
+
// eager-init reporting.
|
|
778
|
+
await ensureEmbeddings();
|
|
750
779
|
const store = loadNeuralStore();
|
|
751
780
|
const method = input.method || 'quantize';
|
|
752
781
|
const targetReduction = input.targetSize || 0.5;
|
|
@@ -860,6 +889,9 @@ export const neuralTools = [
|
|
|
860
889
|
if (!v.valid)
|
|
861
890
|
return { success: false, error: v.error };
|
|
862
891
|
}
|
|
892
|
+
// Resolve provider so _realEmbeddings/embeddingProvider report the same
|
|
893
|
+
// values the old eager-init version produced.
|
|
894
|
+
await ensureEmbeddings();
|
|
863
895
|
const store = loadNeuralStore();
|
|
864
896
|
if (input.modelId) {
|
|
865
897
|
const model = store.models[input.modelId];
|
|
@@ -928,6 +960,9 @@ export const neuralTools = [
|
|
|
928
960
|
if (!v.valid)
|
|
929
961
|
return { success: false, error: v.error };
|
|
930
962
|
}
|
|
963
|
+
// Resolve provider so embeddingProvider in the result matches the old
|
|
964
|
+
// eager-init reporting.
|
|
965
|
+
await ensureEmbeddings();
|
|
931
966
|
const store = loadNeuralStore();
|
|
932
967
|
const target = input.target || 'balanced';
|
|
933
968
|
const patterns = Object.values(store.patterns);
|
|
@@ -14,6 +14,7 @@ import { existsSync, mkdirSync, readFileSync, 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
|
+
import { resolveTrainingBackend } from '../ruvector/lora-adapter.js';
|
|
17
18
|
// ============================================================================
|
|
18
19
|
// Persistence Configuration
|
|
19
20
|
// ============================================================================
|
|
@@ -1006,15 +1007,29 @@ export function getIntelligenceStats() {
|
|
|
1006
1007
|
loadRuvllmCoordinatorSync();
|
|
1007
1008
|
}
|
|
1008
1009
|
const ruvllmStats = ruvllmCoordinator?.stats?.() || null;
|
|
1009
|
-
// Fetch cross-module stats for unified reporting
|
|
1010
|
+
// Fetch cross-module stats for unified reporting.
|
|
1011
|
+
//
|
|
1012
|
+
// #2549 — two prior defects here: `trainingBackend` was declared and
|
|
1013
|
+
// returned but never assigned (always 'unavailable'), and contrastive
|
|
1014
|
+
// availability was read ONLY from an in-process global that a fresh
|
|
1015
|
+
// read-only `neural status` process never populates. Both made the
|
|
1016
|
+
// native @ruvector/ruvllm path invisible even when installed. Backend
|
|
1017
|
+
// now comes from the lora-adapter's capability probe; the global still
|
|
1018
|
+
// wins when present because it carries live in-process session counts.
|
|
1010
1019
|
let contrastiveTrainer = 'unavailable';
|
|
1011
1020
|
let trainingBackend = 'unavailable';
|
|
1012
1021
|
try {
|
|
1013
|
-
|
|
1014
|
-
|
|
1022
|
+
trainingBackend = resolveTrainingBackend();
|
|
1023
|
+
}
|
|
1024
|
+
catch { /* module absent — stay 'unavailable' */ }
|
|
1025
|
+
try {
|
|
1015
1026
|
const sonaModule = globalThis.__claudeFlowSonaStats;
|
|
1016
|
-
if (sonaModule) {
|
|
1017
|
-
contrastiveTrainer = sonaModule._contrastiveTrainer
|
|
1027
|
+
if (sonaModule?._contrastiveTrainer) {
|
|
1028
|
+
contrastiveTrainer = sonaModule._contrastiveTrainer;
|
|
1029
|
+
}
|
|
1030
|
+
else if (trainingBackend === 'ruvllm') {
|
|
1031
|
+
// Module resolves but no in-process session — available, idle.
|
|
1032
|
+
contrastiveTrainer = 'available';
|
|
1018
1033
|
}
|
|
1019
1034
|
}
|
|
1020
1035
|
catch { /* not available */ }
|
|
@@ -419,14 +419,26 @@ export declare function bridgeHealthCheck(dbPath?: string): Promise<{
|
|
|
419
419
|
*
|
|
420
420
|
* Real HierarchicalMemory API (agentdb alpha.10+):
|
|
421
421
|
* store(content, importance?, tier?, options?) → Promise<string>
|
|
422
|
-
*
|
|
423
|
-
* store(key, value, tier) — synchronous
|
|
422
|
+
* Fallback API (@claude-flow/memory TieredMemoryStore):
|
|
423
|
+
* store(key, value, tier, temporalOptions?) — synchronous, returns
|
|
424
|
+
* { id, key, tier, superseded? }
|
|
425
|
+
*
|
|
426
|
+
* Temporal validity (Zep/Graphiti-style, impl/memory-sota):
|
|
427
|
+
* - validFrom / validUntil (ISO) travel with the entry.
|
|
428
|
+
* - supersedes=<entryId|key> INVALIDATES the old entry (validUntil=now +
|
|
429
|
+
* supersededBy=newId) instead of deleting it. Natively supported by the
|
|
430
|
+
* TieredMemoryStore fallback; on the real agentdb HierarchicalMemory the
|
|
431
|
+
* temporal fields are stored in metadata, and supersede is reported as
|
|
432
|
+
* unsupported (no public update API) rather than silently dropped.
|
|
424
433
|
*/
|
|
425
434
|
export declare function bridgeHierarchicalStore(params: {
|
|
426
435
|
key: string;
|
|
427
436
|
value: string;
|
|
428
437
|
tier?: string;
|
|
429
438
|
importance?: number;
|
|
439
|
+
validFrom?: string;
|
|
440
|
+
validUntil?: string;
|
|
441
|
+
supersedes?: string;
|
|
430
442
|
}): Promise<any>;
|
|
431
443
|
/**
|
|
432
444
|
* Recall from hierarchical memory.
|
|
@@ -441,6 +453,7 @@ export declare function bridgeHierarchicalRecall(params: {
|
|
|
441
453
|
query: string;
|
|
442
454
|
tier?: string;
|
|
443
455
|
topK?: number;
|
|
456
|
+
includeExpired?: boolean;
|
|
444
457
|
}): Promise<any>;
|
|
445
458
|
/**
|
|
446
459
|
* Run memory consolidation.
|
|
@@ -1970,8 +1970,17 @@ export async function bridgeHealthCheck(dbPath) {
|
|
|
1970
1970
|
*
|
|
1971
1971
|
* Real HierarchicalMemory API (agentdb alpha.10+):
|
|
1972
1972
|
* store(content, importance?, tier?, options?) → Promise<string>
|
|
1973
|
-
*
|
|
1974
|
-
* store(key, value, tier) — synchronous
|
|
1973
|
+
* Fallback API (@claude-flow/memory TieredMemoryStore):
|
|
1974
|
+
* store(key, value, tier, temporalOptions?) — synchronous, returns
|
|
1975
|
+
* { id, key, tier, superseded? }
|
|
1976
|
+
*
|
|
1977
|
+
* Temporal validity (Zep/Graphiti-style, impl/memory-sota):
|
|
1978
|
+
* - validFrom / validUntil (ISO) travel with the entry.
|
|
1979
|
+
* - supersedes=<entryId|key> INVALIDATES the old entry (validUntil=now +
|
|
1980
|
+
* supersededBy=newId) instead of deleting it. Natively supported by the
|
|
1981
|
+
* TieredMemoryStore fallback; on the real agentdb HierarchicalMemory the
|
|
1982
|
+
* temporal fields are stored in metadata, and supersede is reported as
|
|
1983
|
+
* unsupported (no public update API) rather than silently dropped.
|
|
1975
1984
|
*/
|
|
1976
1985
|
export async function bridgeHierarchicalStore(params) {
|
|
1977
1986
|
const registry = await getRegistry();
|
|
@@ -1984,15 +1993,36 @@ export async function bridgeHierarchicalStore(params) {
|
|
|
1984
1993
|
const tier = params.tier || 'working';
|
|
1985
1994
|
// Detect real HierarchicalMemory (has async store returning id) vs stub
|
|
1986
1995
|
if (typeof hm.getStats === 'function' && typeof hm.promote === 'function') {
|
|
1987
|
-
// Real agentdb HierarchicalMemory
|
|
1996
|
+
// Real agentdb HierarchicalMemory — temporal fields ride in metadata
|
|
1997
|
+
// so bridgeHierarchicalRecall can filter on them.
|
|
1998
|
+
const metadata = { key: params.key };
|
|
1999
|
+
if (params.validFrom)
|
|
2000
|
+
metadata.validFrom = params.validFrom;
|
|
2001
|
+
if (params.validUntil)
|
|
2002
|
+
metadata.validUntil = params.validUntil;
|
|
1988
2003
|
const id = await hm.store(params.value, params.importance || 0.5, tier, {
|
|
1989
|
-
metadata
|
|
2004
|
+
metadata,
|
|
1990
2005
|
tags: [params.key],
|
|
1991
2006
|
});
|
|
1992
|
-
|
|
2007
|
+
const result = { success: true, id, key: params.key, tier };
|
|
2008
|
+
if (params.supersedes) {
|
|
2009
|
+
// No public update/invalidate API on agentdb HierarchicalMemory —
|
|
2010
|
+
// surface the limitation honestly instead of silently dropping it.
|
|
2011
|
+
result.superseded = null;
|
|
2012
|
+
result.warning = 'supersedes is not supported by the native agentdb HierarchicalMemory backend (no update API); the referenced entry was left untouched';
|
|
2013
|
+
}
|
|
2014
|
+
return result;
|
|
2015
|
+
}
|
|
2016
|
+
// TieredMemoryStore fallback (temporal-aware) / legacy stub
|
|
2017
|
+
const storeResult = hm.store(params.key, params.value, tier, {
|
|
2018
|
+
validFrom: params.validFrom,
|
|
2019
|
+
validUntil: params.validUntil,
|
|
2020
|
+
supersedes: params.supersedes,
|
|
2021
|
+
});
|
|
2022
|
+
if (storeResult && typeof storeResult === 'object') {
|
|
2023
|
+
return { success: true, ...storeResult };
|
|
1993
2024
|
}
|
|
1994
|
-
//
|
|
1995
|
-
hm.store(params.key, params.value, tier);
|
|
2025
|
+
// Legacy stub (returns void) — temporal options were ignored
|
|
1996
2026
|
return { success: true, key: params.key, tier };
|
|
1997
2027
|
}
|
|
1998
2028
|
catch (e) {
|
|
@@ -2027,10 +2057,34 @@ export async function bridgeHierarchicalRecall(params) {
|
|
|
2027
2057
|
memoryQuery.tier = params.tier;
|
|
2028
2058
|
}
|
|
2029
2059
|
const results = await hm.recall(memoryQuery);
|
|
2030
|
-
|
|
2060
|
+
// Temporal filter over metadata (validFrom/validUntil stamped by
|
|
2061
|
+
// bridgeHierarchicalStore). Entries without the fields are always
|
|
2062
|
+
// valid — legacy behavior unchanged.
|
|
2063
|
+
const filtered = params.includeExpired
|
|
2064
|
+
? (results || [])
|
|
2065
|
+
: (results || []).filter((r) => {
|
|
2066
|
+
const meta = r?.metadata || {};
|
|
2067
|
+
const now = Date.now();
|
|
2068
|
+
if (meta.validFrom) {
|
|
2069
|
+
const from = Date.parse(String(meta.validFrom));
|
|
2070
|
+
if (!Number.isNaN(from) && from > now)
|
|
2071
|
+
return false;
|
|
2072
|
+
}
|
|
2073
|
+
if (meta.validUntil) {
|
|
2074
|
+
const until = Date.parse(String(meta.validUntil));
|
|
2075
|
+
if (!Number.isNaN(until) && until <= now)
|
|
2076
|
+
return false;
|
|
2077
|
+
}
|
|
2078
|
+
return true;
|
|
2079
|
+
});
|
|
2080
|
+
return { results: filtered, controller: 'hierarchicalMemory' };
|
|
2031
2081
|
}
|
|
2032
|
-
//
|
|
2033
|
-
|
|
2082
|
+
// TieredMemoryStore fallback — recall(string, number, options?).
|
|
2083
|
+
// Temporal filtering happens inside the store; the legacy stub simply
|
|
2084
|
+
// ignores the extra argument.
|
|
2085
|
+
const results = hm.recall(params.query, params.topK || 5, {
|
|
2086
|
+
includeExpired: params.includeExpired === true,
|
|
2087
|
+
});
|
|
2034
2088
|
const filtered = params.tier
|
|
2035
2089
|
? results.filter((r) => r.tier === params.tier)
|
|
2036
2090
|
: results;
|
|
@@ -97,6 +97,15 @@ export interface LoRAStats {
|
|
|
97
97
|
/** Training backend in use ('ruvllm' | 'js-fallback') */
|
|
98
98
|
_trainingBackend?: string;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Which training backend a train call would use — WITHOUT loading the
|
|
102
|
+
* pipeline (#2549). Before this existed, status surfaces read module state
|
|
103
|
+
* that only a prior in-process train populates, so a fresh read-only
|
|
104
|
+
* process always reported 'js-fallback'/'unavailable' even with
|
|
105
|
+
* @ruvector/ruvllm installed. The pipeline stays lazy: this only probes
|
|
106
|
+
* module resolution.
|
|
107
|
+
*/
|
|
108
|
+
export declare function resolveTrainingBackend(): 'ruvllm' | 'js-fallback';
|
|
100
109
|
/**
|
|
101
110
|
* Low-Rank Adaptation module for efficient embedding fine-tuning
|
|
102
111
|
*/
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
21
21
|
import { dirname, join } from 'path';
|
|
22
|
+
import { createRequire } from 'module';
|
|
22
23
|
// ============================================================================
|
|
23
24
|
// Types & Constants
|
|
24
25
|
// ============================================================================
|
|
@@ -57,6 +58,25 @@ const DEFAULT_CONFIG = {
|
|
|
57
58
|
// ============================================================================
|
|
58
59
|
let ruvllmPipeline = null;
|
|
59
60
|
let pipelineLoaded = false;
|
|
61
|
+
/**
|
|
62
|
+
* Which training backend a train call would use — WITHOUT loading the
|
|
63
|
+
* pipeline (#2549). Before this existed, status surfaces read module state
|
|
64
|
+
* that only a prior in-process train populates, so a fresh read-only
|
|
65
|
+
* process always reported 'js-fallback'/'unavailable' even with
|
|
66
|
+
* @ruvector/ruvllm installed. The pipeline stays lazy: this only probes
|
|
67
|
+
* module resolution.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveTrainingBackend() {
|
|
70
|
+
if (pipelineLoaded)
|
|
71
|
+
return ruvllmPipeline ? 'ruvllm' : 'js-fallback';
|
|
72
|
+
try {
|
|
73
|
+
createRequire(import.meta.url).resolve('@ruvector/ruvllm');
|
|
74
|
+
return 'ruvllm';
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return 'js-fallback';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
60
80
|
async function loadTrainingPipeline(adapter) {
|
|
61
81
|
if (pipelineLoaded)
|
|
62
82
|
return ruvllmPipeline;
|
|
@@ -295,9 +315,7 @@ export class LoRAAdapter {
|
|
|
295
315
|
? this.adaptationNormSum / this.totalAdaptations
|
|
296
316
|
: 0,
|
|
297
317
|
lastUpdate: this.lastUpdate,
|
|
298
|
-
_trainingBackend:
|
|
299
|
-
? (ruvllmPipeline ? 'ruvllm' : 'js-fallback')
|
|
300
|
-
: 'js-fallback',
|
|
318
|
+
_trainingBackend: resolveTrainingBackend(),
|
|
301
319
|
};
|
|
302
320
|
}
|
|
303
321
|
/**
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* output-verifier.ts — Confidence-gated tier escalation (post-generation).
|
|
3
|
+
*
|
|
4
|
+
* ADR-026/143 route tasks to a tier BEFORE generation. 2026 SOTA cascade
|
|
5
|
+
* routing adds a post-generation gate: attempt the cheap tier, run a CHEAP
|
|
6
|
+
* verifier over the produced output, and escalate to the next tier only when
|
|
7
|
+
* the verifier is not confident. This module is that verifier.
|
|
8
|
+
*
|
|
9
|
+
* Design constraints (load-bearing):
|
|
10
|
+
* - $0 by default — NO LLM call. Every signal is a structural / lexical
|
|
11
|
+
* check: emptiness, refusal patterns, truncation, delimiter balance,
|
|
12
|
+
* degenerate repetition, and (for code tasks) a real syntax parse via the
|
|
13
|
+
* TypeScript compiler (lazy-imported; degrades to delimiter checks when
|
|
14
|
+
* typescript is not installed) or JSON.parse for JSON output.
|
|
15
|
+
* - Pure with respect to router state — recording the verdict into the
|
|
16
|
+
* bandit's learning stream is the CALLER's job (the hooks_model-verify
|
|
17
|
+
* MCP tool does it via ModelRouter.recordOutcome), keeping this module
|
|
18
|
+
* trivially unit-testable.
|
|
19
|
+
* - Escalation ladder mirrors the tier table: tier 2 (haiku) → tier 3
|
|
20
|
+
* (sonnet), sonnet → opus, opus has no bump (escalate=false even when the
|
|
21
|
+
* verdict is not confident — the caller should retry or surface instead).
|
|
22
|
+
*
|
|
23
|
+
* Verdict semantics: `confident=false` means "cheap signals say this output
|
|
24
|
+
* is likely unusable"; it is NOT a semantic-quality judgment. False
|
|
25
|
+
* negatives (bad output that parses fine) are expected — this gate trades
|
|
26
|
+
* recall for being free.
|
|
27
|
+
*
|
|
28
|
+
* @module ruvector/output-verifier
|
|
29
|
+
*/
|
|
30
|
+
export type VerifyTier = 1 | 2 | 3;
|
|
31
|
+
export type VerifyModel = 'haiku' | 'sonnet' | 'opus';
|
|
32
|
+
export type VerifyTaskKind = 'code' | 'json' | 'text' | 'auto';
|
|
33
|
+
export interface VerifyInput {
|
|
34
|
+
/** The task the output was generated for. */
|
|
35
|
+
task: string;
|
|
36
|
+
/** The generated output to verify. */
|
|
37
|
+
output: string;
|
|
38
|
+
/** Model that produced the output (drives the escalation ladder). */
|
|
39
|
+
model?: VerifyModel;
|
|
40
|
+
/** Tier that produced the output; derived from `model` when absent. */
|
|
41
|
+
tierUsed?: VerifyTier;
|
|
42
|
+
/** Force the task kind; 'auto' (default) detects from task + output. */
|
|
43
|
+
taskKind?: VerifyTaskKind;
|
|
44
|
+
/** Minimum trimmed output length considered plausible (default 20). */
|
|
45
|
+
minLength?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface VerifySignal {
|
|
48
|
+
name: string;
|
|
49
|
+
ok: boolean;
|
|
50
|
+
detail?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface VerifyVerdict {
|
|
53
|
+
confident: boolean;
|
|
54
|
+
/** Fraction of signals that passed (0..1). */
|
|
55
|
+
score: number;
|
|
56
|
+
/** Human-readable failure reasons; empty when confident. */
|
|
57
|
+
reasons: string[];
|
|
58
|
+
/** Every signal evaluated, pass or fail. */
|
|
59
|
+
signals: VerifySignal[];
|
|
60
|
+
/** Tier the caller should use next: unchanged when confident, bumped 2→3 on failure. */
|
|
61
|
+
suggestedTier: VerifyTier;
|
|
62
|
+
/** Concrete next model on the ladder (haiku→sonnet→opus); null when no bump exists. */
|
|
63
|
+
suggestedModel: VerifyModel | null;
|
|
64
|
+
/** True when not confident AND a higher tier exists to escalate to. */
|
|
65
|
+
escalate: boolean;
|
|
66
|
+
/** Detected task kind after 'auto' resolution. */
|
|
67
|
+
taskKind: Exclude<VerifyTaskKind, 'auto'>;
|
|
68
|
+
}
|
|
69
|
+
interface ExtractedCode {
|
|
70
|
+
lang: string;
|
|
71
|
+
code: string;
|
|
72
|
+
}
|
|
73
|
+
/** Pull fenced code blocks out of markdown-ish output. */
|
|
74
|
+
export declare function extractCodeBlocks(output: string): ExtractedCode[];
|
|
75
|
+
/** Cheap stack-based bracket balance check that skips string/comment-ish content. */
|
|
76
|
+
export declare function bracketsBalanced(code: string): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Compute a confidence verdict for `output` from cheap structural signals,
|
|
79
|
+
* and suggest an escalation target when not confident.
|
|
80
|
+
*/
|
|
81
|
+
export declare function verifyAndEscalate(input: VerifyInput): Promise<VerifyVerdict>;
|
|
82
|
+
export default verifyAndEscalate;
|
|
83
|
+
//# sourceMappingURL=output-verifier.d.ts.map
|