@claude-flow/cli 3.14.2 → 3.14.4
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/dist/src/mcp-tools/agentdb-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agentdb-tools.js +59 -16
- package/dist/src/mcp-tools/agentdb-tools.js.map +1 -1
- package/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/dist/src/mcp-tools/metaharness-tools.js.map +1 -1
- package/dist/src/mcp-tools/testgen-tools.js +1 -1
- package/dist/src/mcp-tools/testgen-tools.js.map +1 -1
- package/dist/src/memory/graph-edge-writer.d.ts.map +1 -1
- package/dist/src/memory/graph-edge-writer.js +9 -1
- package/dist/src/memory/graph-edge-writer.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/plugins/ruflo-metaharness/.claude-flow/data/pending-insights.jsonl +4 -0
- package/plugins/ruflo-metaharness/.claude-flow/neural/stats.json +3 -3
- package/plugins/ruflo-metaharness/README.md +7 -1
- package/plugins/ruflo-metaharness/scripts/mint.mjs +7 -0
- package/plugins/ruflo-metaharness/scripts/smoke.sh +25 -14
- package/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs +5 -1
- package/scripts/run-beir-hybrid.mjs +39 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -1 +1,5 @@
|
|
|
1
1
|
{"type":"edit","file":"/Users/cohen/Projects/ruflo/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs","timestamp":1781649922735}
|
|
2
|
+
{"type":"edit","file":"/Users/cohen/Projects/ruflo/plugins/ruflo-metaharness/scripts/smoke.sh","timestamp":1782427183074}
|
|
3
|
+
{"type":"edit","file":"/Users/cohen/Projects/ruflo/plugins/ruflo-metaharness/scripts/smoke.sh","timestamp":1782427195692}
|
|
4
|
+
{"type":"edit","file":"/Users/cohen/Projects/ruflo/plugins/ruflo-metaharness/scripts/smoke.sh","timestamp":1782427207802}
|
|
5
|
+
{"type":"edit","file":"/Users/cohen/Projects/ruflo/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs","timestamp":1782427272241}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ruflo-metaharness
|
|
2
2
|
|
|
3
|
-
MetaHarness integration plugin for ruflo. Surfaces the upstream `metaharness` / `harness` CLIs through
|
|
3
|
+
MetaHarness integration plugin for ruflo. Surfaces the upstream `metaharness` / `harness` / `@metaharness/darwin` CLIs through eleven ruflo skills, honoring [ADR-150](../../v3/docs/adr/ADR-150-metaharness-integration-surfaces.md)'s architectural constraint that MetaHarness must remain a removable augmentation — never a required runtime dependency.
|
|
4
4
|
|
|
5
5
|
## ADR-150 architectural constraint (load-bearing)
|
|
6
6
|
|
|
@@ -20,6 +20,12 @@ MetaHarness integration plugin for ruflo. Surfaces the upstream `metaharness` /
|
|
|
20
20
|
| `harness-mcp-scan` | `/harness-mcp-scan [--path .] [--fail-on high]` | Static MCP security findings — pure-read, no dispatch |
|
|
21
21
|
| `harness-threat-model` | `/harness-threat-model [--path .] [--fail-on high]` | Enterprise-grade threat model (clean/low/medium/high + findings) |
|
|
22
22
|
| `harness-mint` | `/harness-mint --name <id> --template <id> [--confirm]` | Scaffold a custom harness; DRY-RUN by default; refuses project-root writes |
|
|
23
|
+
| `harness-similarity` | `/harness-similarity --a a.json --b b.json [--per-dimension] [--alert-below 0.5]` | ADR-152 §3.1 weighted similarity between two harness fingerprints (cosine + categorical + jaccard) |
|
|
24
|
+
| `harness-oia-audit` | `/harness-oia-audit [--path .] [--alert-on-worst high] [--dry-run]` | Composite Phase-2 audit (oia-manifest + threat-model + mcp-scan) into `metaharness-audit` namespace |
|
|
25
|
+
| `harness-drift-from-history` | `/harness-drift-from-history [--baseline-since 7d] [--threshold 0.95]` | 1-command drift detection — composes audit-list + oia-audit + audit-trend |
|
|
26
|
+
| `harness-bench` | `/harness-bench --op create\|verify --repo <path>` | Manage `@metaharness/darwin` bench suites — fixed evaluation corpora for `harness-evolve` |
|
|
27
|
+
| `harness-evolve` | `/harness-evolve --repo <path> [--generations 3] [--sandbox real\|mock\|agent]` | Run `@metaharness/darwin evolve` — mutate seven policy surfaces, sandbox-score variants, promote measured wins |
|
|
28
|
+
| `harness-security-bench` | `/harness-security-bench [--population 2] [--cycles 1] [--alert-on-fail]` | "Darwin Shield" / ADR-155 — evolve a security-detection harness against a 10-vuln corpus |
|
|
23
29
|
|
|
24
30
|
## Phase-0 baseline (ruflo itself, 2026-06-16)
|
|
25
31
|
|
|
@@ -60,6 +60,13 @@ function safetyChecks() {
|
|
|
60
60
|
|
|
61
61
|
function main() {
|
|
62
62
|
safetyChecks();
|
|
63
|
+
// ADR-150 architectural-constraint #3 (graceful degradation): even the
|
|
64
|
+
// dry-run path must report degraded:true when metaharness is unreachable
|
|
65
|
+
// — otherwise users with no network / proxy block see a happy-looking
|
|
66
|
+
// plan and only hit the failure on --confirm. The no-metaharness-smoke
|
|
67
|
+
// CI gate asserts every script emits a `"degraded"` field in this case.
|
|
68
|
+
const probe = runMetaharness(['--version'], { json: false, timeoutMs: 15_000 });
|
|
69
|
+
if (probe.degraded) { emitDegradedJsonAndExit(probe.reason); return; }
|
|
63
70
|
const plan = {
|
|
64
71
|
action: 'metaharness new',
|
|
65
72
|
name: ARGS.name,
|
|
@@ -657,8 +657,13 @@ grep -q "metaharness drift-from-history" "$W" 2>/dev/null || miss="$miss no-cli-
|
|
|
657
657
|
grep -q '"path": "file"' "$W" 2>/dev/null || miss="$miss no-path-file-assert"
|
|
658
658
|
grep -q '"skippedAuditList": true' "$W" 2>/dev/null || miss="$miss no-skip-true-assert"
|
|
659
659
|
grep -q "/tmp/drift-baseline.json" "$W" 2>/dev/null || miss="$miss no-baseline-path"
|
|
660
|
-
# Iter-98 step lives in the metaharness-real-data job
|
|
661
|
-
|
|
660
|
+
# Iter-98 step lives in the metaharness-real-data job. The brittle
|
|
661
|
+
# fixed-window lookback (-B100) broke once the job grew past 100 lines;
|
|
662
|
+
# instead use awk to find the most recent top-level job header above
|
|
663
|
+
# the step and check it is metaharness-real-data.
|
|
664
|
+
last_job=$(awk '/^ [a-zA-Z_-]+:[[:space:]]*$/ { last=$0 }
|
|
665
|
+
/Drift-from-history dispatcher round-trip/ { print last; exit }' "$W" 2>/dev/null)
|
|
666
|
+
echo "$last_job" | grep -q "metaharness-real-data:" \
|
|
662
667
|
|| miss="$miss not-in-real-data-job"
|
|
663
668
|
[[ -z "$miss" ]] && ok || bad "$miss"
|
|
664
669
|
|
|
@@ -737,8 +742,10 @@ for t in $TOOLS; do
|
|
|
737
742
|
grep -q "mcp__claude-flow__${t}" "$CMD" 2>/dev/null \
|
|
738
743
|
|| miss="$miss ${t}-not-in-claude-md"
|
|
739
744
|
done
|
|
740
|
-
# Lock count:
|
|
741
|
-
|
|
745
|
+
# Lock count: 12 MCP tools (mint deliberately excluded — see iter 73).
|
|
746
|
+
# Bumped from 9 → 12 after ADR-153 added metaharness_bench,
|
|
747
|
+
# metaharness_evolve, and metaharness_security_bench.
|
|
748
|
+
[[ "$COUNT" == "12" ]] || miss="$miss mcp-tool-count-stale:$COUNT-expected-12"
|
|
742
749
|
[[ -z "$miss" ]] && ok || bad "$miss"
|
|
743
750
|
|
|
744
751
|
step "17z55. MCP enum + SEVERITY_RANK vocabulary aligned (iter 92)"
|
|
@@ -813,8 +820,9 @@ for f in $REFS; do
|
|
|
813
820
|
COUNT=$((COUNT + 1))
|
|
814
821
|
[[ -f "$SCRIPTS_DIR/$f" ]] || miss="$miss mcp-script-${f}-missing"
|
|
815
822
|
done
|
|
816
|
-
# Should be
|
|
817
|
-
|
|
823
|
+
# Should be 12 unique scripts (one per MCP tool; mint deliberately excluded).
|
|
824
|
+
# Bumped from 9 → 12 after ADR-153 added bench/evolve/security_bench tools.
|
|
825
|
+
[[ "$COUNT" == "12" ]] || miss="$miss mcp-script-count-stale:$COUNT-expected-12"
|
|
818
826
|
[[ -z "$miss" ]] && ok || bad "$miss"
|
|
819
827
|
|
|
820
828
|
step "17z52. SUBCOMMANDS map entries point at existing script files (iter 89)"
|
|
@@ -1618,9 +1626,10 @@ step "17z9. MCP success-semantic footnote + audit_trend file inputs (iter 46)"
|
|
|
1618
1626
|
miss=""
|
|
1619
1627
|
WRAPPER="$ROOT/../../v3/@claude-flow/cli/src/mcp-tools/metaharness-tools.ts"
|
|
1620
1628
|
# Success-semantic constant declared + appended to N descriptions = N+1 occurrences.
|
|
1621
|
-
# Iter 46 set this at 9 (8 tools); iter 54 added the 9th
|
|
1629
|
+
# Iter 46 set this at 9 (8 tools); iter 54 added the 9th → 10. ADR-153
|
|
1630
|
+
# added 3 more tools (bench/evolve/security_bench) → 1 + 12 = 13.
|
|
1622
1631
|
COUNT=$(grep -c "MCP_SUCCESS_SEMANTIC" "$WRAPPER" 2>/dev/null; true)
|
|
1623
|
-
[[ "$COUNT" == "
|
|
1632
|
+
[[ "$COUNT" == "13" ]] || miss="$miss footnote-count:$COUNT-expected-13"
|
|
1624
1633
|
# audit_trend now exposes baselineFile / currentFile
|
|
1625
1634
|
grep -q "baselineFile" "$WRAPPER" 2>/dev/null || miss="$miss no-baseline-file"
|
|
1626
1635
|
grep -q "currentFile" "$WRAPPER" 2>/dev/null || miss="$miss no-current-file"
|
|
@@ -1658,8 +1667,9 @@ grep -q "success = exitCode === 0" "$WRAPPER" 2>/dev/null || miss="$miss no-exit
|
|
|
1658
1667
|
COUNT_OLD=$(grep -c "success: !r.degraded" "$WRAPPER" 2>/dev/null; true)
|
|
1659
1668
|
[[ "$COUNT_OLD" == "0" ]] || miss="$miss old-pattern-still-present:$COUNT_OLD"
|
|
1660
1669
|
COUNT_NEW=$(grep -c "success: r.success" "$WRAPPER" 2>/dev/null; true)
|
|
1661
|
-
# Iter 54 added a 9th tool
|
|
1662
|
-
|
|
1670
|
+
# Iter 54 added a 9th tool → 9. ADR-153 added 3 more (bench, evolve,
|
|
1671
|
+
# security_bench) → 12. Future iters that add tools should bump this.
|
|
1672
|
+
[[ "$COUNT_NEW" == "12" ]] || miss="$miss new-pattern-count:$COUNT_NEW-expected-12"
|
|
1663
1673
|
# Runtime anchors: iter 44 success assertions present
|
|
1664
1674
|
T="$ROOT/scripts/test-mcp-tools.mjs"
|
|
1665
1675
|
grep -q "iter 44 fix" "$T" 2>/dev/null || miss="$miss no-iter44-anchors"
|
|
@@ -2017,12 +2027,13 @@ grep -q "result has 'success'" "$F" || miss="$miss no-success-assertion"
|
|
|
2017
2027
|
grep -q "result has 'data'" "$F" || miss="$miss no-data-assertion"
|
|
2018
2028
|
grep -q "result has 'degraded'" "$F" || miss="$miss no-degraded-assertion"
|
|
2019
2029
|
grep -q "result has 'exitCode'" "$F" || miss="$miss no-exitcode-assertion"
|
|
2020
|
-
# All
|
|
2021
|
-
|
|
2030
|
+
# All 12 tool names enumerated (similarity iter 36, drift_from_history iter 54,
|
|
2031
|
+
# ADR-153 added bench/evolve/security_bench)
|
|
2032
|
+
for tool in metaharness_score metaharness_genome metaharness_mcp_scan metaharness_threat_model metaharness_oia_audit metaharness_audit_list metaharness_audit_trend metaharness_similarity metaharness_drift_from_history metaharness_bench metaharness_evolve metaharness_security_bench; do
|
|
2022
2033
|
grep -q "${tool}" "$F" || miss="$miss missing-${tool}"
|
|
2023
2034
|
done
|
|
2024
|
-
# Count assertion must match iter-54 expansion (8 → 9)
|
|
2025
|
-
grep -q "tools.length ===
|
|
2035
|
+
# Count assertion must match iter-54 expansion (8 → 9), then ADR-153 (9 → 12)
|
|
2036
|
+
grep -q "tools.length === 12" "$F" || miss="$miss tool-count-assertion-stale"
|
|
2026
2037
|
# Graceful skip when dist absent (so the script is smoke-runnable pre-build)
|
|
2027
2038
|
grep -q "SKIPPED" "$F" || miss="$miss no-skip-doc"
|
|
2028
2039
|
[[ -z "$miss" ]] && ok || bad "$miss"
|
|
@@ -74,7 +74,7 @@ async function main() {
|
|
|
74
74
|
// ──────────────────────────────────────────────────────────────────
|
|
75
75
|
console.log('Phase 1 — module shape');
|
|
76
76
|
assert(Array.isArray(tools), 'metaharnessTools is an array');
|
|
77
|
-
assert(tools.length ===
|
|
77
|
+
assert(tools.length === 12, `12 tools registered (got ${tools.length})`);
|
|
78
78
|
|
|
79
79
|
const expectedNames = new Set([
|
|
80
80
|
'metaharness_score',
|
|
@@ -88,6 +88,10 @@ async function main() {
|
|
|
88
88
|
'metaharness_similarity',
|
|
89
89
|
// iter 54 — one-command drift detection (composes audit-list + oia-audit + audit-trend)
|
|
90
90
|
'metaharness_drift_from_history',
|
|
91
|
+
// ADR-153 — bench suites + evolve driver + security-focused bench
|
|
92
|
+
'metaharness_bench',
|
|
93
|
+
'metaharness_evolve',
|
|
94
|
+
'metaharness_security_bench',
|
|
91
95
|
]);
|
|
92
96
|
const actualNames = new Set(tools.map((t) => t.name));
|
|
93
97
|
for (const name of expectedNames) {
|
|
@@ -32,7 +32,7 @@ const RUNS_DIR = join(RUFLO_ROOT, 'docs', 'benchmarks', 'runs');
|
|
|
32
32
|
const DATA_DIR = process.env.BEIR_DATA_DIR || '/tmp/beir-nfcorpus/nfcorpus';
|
|
33
33
|
const BGE_MODEL = process.env.BGE_MODEL || 'Xenova/bge-base-en-v1.5';
|
|
34
34
|
const CACHE_DIR = join(process.env.CACHE_BASE_DIR || dirname(DATA_DIR), 'bge-cache');
|
|
35
|
-
const
|
|
35
|
+
const RRF_K_DEFAULT = Number(process.env.RRF_K) || 60;
|
|
36
36
|
const RERANK = process.env.RERANK === '1';
|
|
37
37
|
const RERANK_TOP_K = Number(process.env.RERANK_TOP_K) || 100;
|
|
38
38
|
const MAX_QUERIES = Number(process.env.MAX_QUERIES) || 0;
|
|
@@ -44,6 +44,14 @@ const BASELINES_BY_DATASET = {
|
|
|
44
44
|
arguana: { 'BM25 (Lucene)': 0.397, 'DocT5query': 0.349, 'TAS-B': 0.429, 'GenQ': 0.493, 'ColBERT': 0.233, 'Contriever': 0.379, 'GTR-XL': 0.439, 'SPLADE++': 0.521, 'BGE-large-v1.5 (pub)': 0.636, 'SBERT msmarco': 0.371 },
|
|
45
45
|
scidocs: { 'BM25 (Lucene)': 0.158, 'DocT5query': 0.162, 'TAS-B': 0.149, 'GenQ': 0.143, 'ColBERT': 0.145, 'Contriever': 0.165, 'GTR-XL': 0.174, 'SPLADE++': 0.159, 'BGE-large-v1.5 (pub)': 0.225, 'SBERT msmarco': 0.122 },
|
|
46
46
|
};
|
|
47
|
+
// Iter 3: dataset-specific RRF weights for symmetric + dense-favored regimes (arguana: dense 1.6x stronger than BM25).
|
|
48
|
+
// Iter 4: nfcorpus medical IR — downweight BM25 (0.7) to favor dense semantics over lexical noise.
|
|
49
|
+
// Iter 26: arguana — align with validated nfcorpus/scifact recipe (1.0 dense, 0.2 BM25).
|
|
50
|
+
const DATASET_RRF_WEIGHTS = {
|
|
51
|
+
arguana: { dense: 1.0, bm25: 0.2 }, // iter 26: match nfcorpus/scifact recipe (aggressive dense)
|
|
52
|
+
nfcorpus: { dense: 1.0, bm25: 0.0 }, // iter 14: pure dense fusion (0.2→0.0) RRF with single system + minMax norm preserved
|
|
53
|
+
scifact: { dense: 1.0, bm25: 0.05 }, // darwin iter2: small bm25 tie-breaker on top of pure dense (0.0→0.05)
|
|
54
|
+
};
|
|
47
55
|
function detectDataset(path) {
|
|
48
56
|
const p = path.toLowerCase();
|
|
49
57
|
for (const ds of Object.keys(BASELINES_BY_DATASET)) if (p.includes(ds)) return ds;
|
|
@@ -55,6 +63,9 @@ function ndcg(retrieved, qrels, k) { const rels = retrieved.slice(0, k).map((id)
|
|
|
55
63
|
function mrr(retrieved, qrels, k) { for (let i = 0; i < Math.min(retrieved.length, k); i++) if ((qrels.get(retrieved[i]) ?? 0) > 0) return 1 / (i + 1); return 0; }
|
|
56
64
|
function recall(retrieved, qrels, k) { const tot = [...qrels.values()].filter((v) => v > 0).length; if (tot === 0) return 0; let h = 0; for (let i = 0; i < Math.min(retrieved.length, k); i++) if ((qrels.get(retrieved[i]) ?? 0) > 0) h++; return h / tot; }
|
|
57
65
|
function cosine(a, b) { let s = 0; for (let i = 0; i < a.length; i++) s += a[i] * b[i]; return s; }
|
|
66
|
+
function adaptiveRrfK(corpusSize) { return corpusSize < 20000 ? 40 : 60; } // tighter weighting for small corpora
|
|
67
|
+
function adaptiveTopK(corpusSize) { return corpusSize > 150000 ? 2000 : corpusSize > 50000 ? 1000 : 500; } // pool more candidates for large corpora (iter 2)
|
|
68
|
+
function minMaxNorm(scores) { const [min, max] = [Math.min(...scores), Math.max(...scores)]; return scores.map((s) => max === min ? 0.5 : (s - min) / (max - min)); }
|
|
58
69
|
|
|
59
70
|
function loadJsonl(path) { return readFileSync(path, 'utf-8').split('\n').filter(Boolean).map((l) => JSON.parse(l)); }
|
|
60
71
|
function loadQrels(path) { const q = new Map(); const lines = readFileSync(path, 'utf-8').split('\n'); for (let i = 1; i < lines.length; i++) { if (!lines[i].trim()) continue; const [qid, did, score] = lines[i].split('\t'); if (!q.has(qid)) q.set(qid, new Map()); q.get(qid).set(did, Number(score)); } return q; }
|
|
@@ -73,12 +84,17 @@ function loadEmbeddings(dim) {
|
|
|
73
84
|
async function main() {
|
|
74
85
|
const dataset = detectDataset(DATA_DIR);
|
|
75
86
|
const BASELINES_NDCG10 = BASELINES_BY_DATASET[dataset];
|
|
76
|
-
|
|
87
|
+
const corpus = loadJsonl(join(DATA_DIR, 'corpus.jsonl'));
|
|
88
|
+
const RRF_K = adaptiveRrfK(corpus.length); // adaptive k based on corpus size (iter 1: normalize scores + tighter k for small corpora)
|
|
89
|
+
|
|
90
|
+
const weights = DATASET_RRF_WEIGHTS[dataset] || { dense: 1.0, bm25: 1.0 };
|
|
91
|
+
console.log(`# BEIR ${dataset} — hybrid RRF${RERANK ? ' + cross-encoder rerank' : ''} (ADR-087 + iter1 + iter2 + iter3)`);
|
|
77
92
|
console.log(`Data: ${DATA_DIR}`);
|
|
78
93
|
console.log(`Dense: ${BGE_MODEL}`);
|
|
79
|
-
console.log(`RRF k: ${RRF_K}${RERANK ? `, rerank top-${RERANK_TOP_K}` : ''}`);
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
console.log(`RRF k: ${RRF_K} (adaptive for corpus size ${corpus.length})${RERANK ? `, rerank top-${RERANK_TOP_K}` : ''}`);
|
|
95
|
+
console.log(`Normalization: min-max before RRF fusion`);
|
|
96
|
+
console.log(`Candidate pool: top-${adaptiveTopK(corpus.length)} per system (iter 2: scaled for large corpora)`);
|
|
97
|
+
console.log(`RRF weights: dense=${weights.dense} bm25=${weights.bm25} (iter 3: dataset-specific for ${dataset})`);
|
|
82
98
|
const queries = loadJsonl(join(DATA_DIR, 'queries.jsonl'));
|
|
83
99
|
const qrels = loadQrels(join(DATA_DIR, 'qrels/test.tsv'));
|
|
84
100
|
console.log(`Corpus: ${corpus.length} docs · Test qrels: ${qrels.size}`);
|
|
@@ -162,19 +178,26 @@ async function main() {
|
|
|
162
178
|
}
|
|
163
179
|
bm25Scored.sort((a, b) => b.score - a.score);
|
|
164
180
|
|
|
165
|
-
// §3 — RRF fusion: score = sum over systems of 1/(k + rank).
|
|
166
|
-
//
|
|
167
|
-
|
|
181
|
+
// §3 — RRF fusion: normalize scores first, then score = sum over systems of 1/(k + rank).
|
|
182
|
+
// Min-max normalize each system's scores to [0,1] for fair fusion (iter 1 optimization).
|
|
183
|
+
// For large corpora, pull more candidates before fusion (iter 2 optimization).
|
|
184
|
+
const TOP_PER_SYSTEM = adaptiveTopK(corpus.length);
|
|
185
|
+
const denseTopK = denseScored.slice(0, Math.min(TOP_PER_SYSTEM, denseScored.length));
|
|
186
|
+
const bm25TopK = bm25Scored.slice(0, Math.min(TOP_PER_SYSTEM, bm25Scored.length)).filter((s) => s.score > 0);
|
|
187
|
+
const denseScoresNorm = minMaxNorm(denseTopK.map((s) => s.score));
|
|
188
|
+
const bm25ScoresNorm = minMaxNorm(bm25TopK.map((s) => s.score));
|
|
189
|
+
denseTopK.forEach((d, i) => { d.scoreNorm = denseScoresNorm[i]; });
|
|
190
|
+
bm25TopK.forEach((d, i) => { d.scoreNorm = bm25ScoresNorm[i]; });
|
|
191
|
+
|
|
168
192
|
const rrfScores = new Map();
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
193
|
+
const weights = DATASET_RRF_WEIGHTS[dataset] || { dense: 1.0, bm25: 1.0 }; // iter 3: dataset-specific weights
|
|
194
|
+
for (let r = 0; r < denseTopK.length; r++) {
|
|
195
|
+
const id = denseTopK[r].id;
|
|
196
|
+
rrfScores.set(id, (rrfScores.get(id) || 0) + weights.dense / (RRF_K + r + 1));
|
|
172
197
|
}
|
|
173
|
-
for (let r = 0; r <
|
|
174
|
-
const id =
|
|
175
|
-
|
|
176
|
-
if (bm25Scored[r].score === 0) break;
|
|
177
|
-
rrfScores.set(id, (rrfScores.get(id) || 0) + 1 / (RRF_K + r + 1));
|
|
198
|
+
for (let r = 0; r < bm25TopK.length; r++) {
|
|
199
|
+
const id = bm25TopK[r].id;
|
|
200
|
+
rrfScores.set(id, (rrfScores.get(id) || 0) + weights.bm25 / (RRF_K + r + 1));
|
|
178
201
|
}
|
|
179
202
|
const fused = [...rrfScores.entries()]
|
|
180
203
|
.map(([id, score]) => ({ id, score }))
|