@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.14.2",
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
  {
2
- "trajectoriesRecorded": 22,
3
- "patternsLearned": 22,
2
+ "trajectoriesRecorded": 51,
3
+ "patternsLearned": 51,
4
4
  "signalsProcessed": 0,
5
- "lastAdaptation": 1781650872839
5
+ "lastAdaptation": 1782427967363
6
6
  }
@@ -1,6 +1,6 @@
1
1
  # ruflo-metaharness
2
2
 
3
- MetaHarness integration plugin for ruflo. Surfaces the upstream `metaharness` / `harness` CLIs through five 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.
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
- grep -B100 "Drift-from-history dispatcher round-trip" "$W" 2>/dev/null | grep -q "metaharness-real-data:" \
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: 9 MCP tools (mint deliberately excluded — see iter 73)
741
- [[ "$COUNT" == "9" ]] || miss="$miss mcp-tool-count-stale:$COUNT-expected-9"
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 9 unique scripts (one per MCP tool; mint deliberately excluded)
817
- [[ "$COUNT" == "9" ]] || miss="$miss mcp-script-count-stale:$COUNT-expected-9"
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 tool expect 10.
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" == "10" ]] || miss="$miss footnote-count:$COUNT-expected-10"
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. Future iters that add tools should bump this.
1662
- [[ "$COUNT_NEW" == "9" ]] || miss="$miss new-pattern-count:$COUNT_NEW-expected-9"
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 9 tool names enumerated (similarity iter 36, drift_from_history iter 54)
2021
- 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; do
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 === 9" "$F" || miss="$miss tool-count-assertion-stale"
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 === 9, `9 tools registered (got ${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 RRF_K = Number(process.env.RRF_K) || 60;
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
- console.log(`# BEIR ${dataset} hybrid RRF${RERANK ? ' + cross-encoder rerank' : ''} (ADR-087)`);
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
- const corpus = loadJsonl(join(DATA_DIR, 'corpus.jsonl'));
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
- // Only need top ~200 from each for stable RRF; cap at top-500 per system.
167
- const TOP_PER_SYSTEM = 500;
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
- for (let r = 0; r < Math.min(TOP_PER_SYSTEM, denseScored.length); r++) {
170
- const id = denseScored[r].id;
171
- rrfScores.set(id, (rrfScores.get(id) || 0) + 1 / (RRF_K + r + 1));
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 < Math.min(TOP_PER_SYSTEM, bm25Scored.length); r++) {
174
- const id = bm25Scored[r].id;
175
- // Skip if this doc has 0 BM25 score (no token overlap) true random docs would otherwise leak in
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 }))