@claude-flow/cli 3.10.19 → 3.10.21

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.10.19",
3
+ "version": "3.10.21",
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",
@@ -20,23 +20,86 @@ const CLI_ROOT = resolve(SCRIPT_DIR, '..');
20
20
  const REPO_ROOT = resolve(SCRIPT_DIR, '../../../..');
21
21
  const RUNS_DIR = join(REPO_ROOT, 'docs', 'benchmarks', 'runs');
22
22
 
23
- // Real ruflo-history-shaped queries. Each one targets a concept that should
24
- // have been seen during pretrain. `expect` is a regex matched against the
25
- // result's `name` field (commit subject or issue title) to score relevance.
26
- // A result is "relevant" if its name matches the expect regex (case-insensitive).
23
+ // ADR-081 labelled held-out corpus. Each query has hand-curated
24
+ // `expectedSubstrings` case-insensitive substring matches against the
25
+ // pattern's name. A result is "relevant" if its name contains ANY of the
26
+ // substrings. Tighter than the regex proxy used in ADRs 077-080 (which
27
+ // matched related-but-not-exact commits).
28
+ //
29
+ // `expect` (regex) is kept for backwards compatibility with the older
30
+ // numbers in ADRs 077-080. New metrics use expectedSubstrings.
27
31
  const QUERIES = [
28
- { q: 'how was the Opus model alias fixed', expect: /opus|2232|model.*alias|4\.8/i },
29
- { q: 'self-learning wiring task-completed pretrain', expect: /self.?learning|task.?completed|pretrain|2245|074/i },
30
- { q: 'deterministic codemod engine var-to-const', expect: /codemod|var.?to.?const|143|deterministic/i },
31
- { q: 'MCP server orphan leak parent-death', expect: /mcp.*orphan|orphan.*mcp|parent.?death|leak/i },
32
- { q: 'unified learning stats aggregator', expect: /unified|stats|aggregator|075/i },
33
- { q: 'structured distillation 4-field schema', expect: /distillation|structured|076|4.?field/i },
34
- { q: 'SQL injection migrate.ts table identifier', expect: /sql.?injection|migrate|table|identifier/i },
35
- { q: 'recall@k HNSW benchmark harness', expect: /recall|hnsw|benchmark|harness/i },
36
- { q: 'Q-learning encoder keyword block', expect: /q.?learning|encoder|keyword|2239/i },
37
- { q: 'security hardening crypto random IDs', expect: /security|hardening|crypto|random/i },
32
+ {
33
+ q: 'how was the Opus model alias fixed',
34
+ expect: /opus|2232|model.*alias|4\.8/i,
35
+ expectedSubstrings: ['opus 4.8', 'opus alias', 'opus model alias', '#2232'],
36
+ },
37
+ {
38
+ q: 'self-learning wiring task-completed pretrain',
39
+ expect: /self.?learning|task.?completed|pretrain|2245|074/i,
40
+ expectedSubstrings: ['self-learning', 'adr-074', 'self learning', '#2245', 'task-completed'],
41
+ },
42
+ {
43
+ q: 'deterministic codemod engine var-to-const',
44
+ expect: /codemod|var.?to.?const|143|deterministic/i,
45
+ expectedSubstrings: ['deterministic tier-1 codemod', 'adr-143', 'codemod', 'var-to-const'],
46
+ },
47
+ {
48
+ q: 'MCP server orphan leak parent-death',
49
+ expect: /mcp.*orphan|orphan.*mcp|parent.?death|leak/i,
50
+ expectedSubstrings: ['mcp orphan', 'mcp servers orphan', 'parent-death', '#2234', 'orphan on every claude'],
51
+ },
52
+ {
53
+ q: 'unified learning stats aggregator',
54
+ expect: /unified|stats|aggregator|075/i,
55
+ expectedSubstrings: ['unified learning-stats', 'adr-075', 'unified learning stats'],
56
+ },
57
+ {
58
+ q: 'structured distillation 4-field schema',
59
+ expect: /distillation|structured|076|4.?field/i,
60
+ expectedSubstrings: ['structured distillation', 'adr-076', '4-field schema'],
61
+ },
62
+ {
63
+ q: 'SQL injection migrate.ts table identifier',
64
+ expect: /sql.?injection|migrate|table|identifier/i,
65
+ expectedSubstrings: ['sql injection', 'shell injection', 'migrate.ts', 'agentdb', 'cve'],
66
+ },
67
+ {
68
+ q: 'recall@k HNSW benchmark harness',
69
+ expect: /recall|hnsw|benchmark|harness/i,
70
+ expectedSubstrings: ['hnsw', 'memory-recall', 'benchmark suite', 'recall@k', 'benchmark intelligence'],
71
+ },
72
+ {
73
+ q: 'Q-learning encoder keyword block',
74
+ expect: /q.?learning|encoder|keyword|2239/i,
75
+ expectedSubstrings: ['q-state encoder', 'route q-state', 'keyword block', '#2239', 'q-encoder'],
76
+ },
77
+ {
78
+ q: 'security hardening crypto random IDs',
79
+ expect: /security|hardening|crypto|random/i,
80
+ expectedSubstrings: ['cwe-347', 'crypto.randomuuid', 'security fix', 'random id', 'crypto random'],
81
+ },
38
82
  ];
39
83
 
84
+ /** Returns true if name contains ANY of the labelled substrings (case-insensitive). */
85
+ function isRelevant(name, expectedSubstrings) {
86
+ if (!name || !expectedSubstrings?.length) return false;
87
+ const lower = String(name).toLowerCase();
88
+ return expectedSubstrings.some((s) => lower.includes(s.toLowerCase()));
89
+ }
90
+
91
+ /** nDCG@k with binary relevance (each relevant item contributes 1 / log2(i+1)). */
92
+ function ndcgAtK(rankedRelevance, k) {
93
+ const arr = rankedRelevance.slice(0, k);
94
+ const dcg = arr.reduce((acc, rel, i) => acc + (rel ? 1 / Math.log2(i + 2) : 0), 0);
95
+ const numRelevant = arr.filter(Boolean).length;
96
+ if (numRelevant === 0) return 0;
97
+ // Ideal: all relevant items packed at positions 1..numRelevant
98
+ let idcg = 0;
99
+ for (let i = 0; i < numRelevant; i++) idcg += 1 / Math.log2(i + 2);
100
+ return idcg > 0 ? dcg / idcg : 0;
101
+ }
102
+
40
103
  const TOP_K = 5;
41
104
 
42
105
  async function main() {
@@ -56,6 +119,8 @@ async function main() {
56
119
  // Default runs hybrid (cosine + BM25 + MMR per ADR-078).
57
120
  const listTool = neural.neuralTools.find((t) => t.name === 'neural_patterns');
58
121
  const mode = process.env.HYBRID === '0' ? 'cosine' : 'hybrid';
122
+ // ADR-080 opt-in cross-encoder rerank — set RERANK=1 to enable.
123
+ const useRerank = process.env.RERANK === '1';
59
124
 
60
125
  // top-1-uniqueness — fraction of queries whose top-1 result is NOT the
61
126
  // same pattern ID as another query's top-1. Catches the "everyone gets
@@ -64,32 +129,45 @@ async function main() {
64
129
 
65
130
  const tQuery0 = performance.now();
66
131
  const results = [];
67
- for (const { q, expect } of QUERIES) {
68
- const r = await listTool.handler({ action: 'search', query: q, mode, limit: TOP_K });
132
+ for (const { q, expect, expectedSubstrings } of QUERIES) {
133
+ const r = await listTool.handler({ action: 'search', query: q, mode, limit: TOP_K, rerank: useRerank });
69
134
  const matches = (r.patterns || r.results || r.matches || []).slice(0, TOP_K);
70
135
  if (matches.length > 0) {
71
136
  const top1 = matches[0].id;
72
137
  top1Ids.set(top1, (top1Ids.get(top1) ?? 0) + 1);
73
138
  }
74
- // ADR-078 relevance: does the top-1/top-3 name match the expect regex?
75
- const top1Name = matches[0]?.name ?? '';
76
- const top1Relevant = expect.test(top1Name);
77
- let top3Relevant = false;
78
- let firstRelevantRank = -1;
79
- for (let i = 0; i < Math.min(matches.length, 3); i++) {
80
- if (expect.test(matches[i]?.name ?? '')) {
81
- top3Relevant = true;
82
- if (firstRelevantRank === -1) firstRelevantRank = i + 1;
83
- break;
84
- }
139
+
140
+ // Two relevance signals per result:
141
+ // regexRel old proxy (kept for back-compat with ADR-077-080 numbers)
142
+ // labelRel ADR-081 hand-curated label match
143
+ const regexRel = matches.map((m) => expect.test(m?.name ?? ''));
144
+ const labelRel = matches.map((m) => isRelevant(m?.name, expectedSubstrings));
145
+
146
+ // First rank where a labelled-relevant doc appears.
147
+ let firstLabelRank = -1;
148
+ for (let i = 0; i < matches.length; i++) {
149
+ if (labelRel[i]) { firstLabelRank = i + 1; break; }
85
150
  }
151
+ let firstRegexRank = -1;
152
+ for (let i = 0; i < matches.length; i++) {
153
+ if (regexRel[i]) { firstRegexRank = i + 1; break; }
154
+ }
155
+
86
156
  results.push({
87
157
  query: q,
88
158
  matched: matches.length > 0,
89
- top1Relevant,
90
- top3Relevant,
91
- firstRelevantRank,
92
- topK: matches.map((m) => ({
159
+ // Regex-proxy metrics (back-compat with ADR-077-080)
160
+ top1Relevant: regexRel[0] || false,
161
+ top3Relevant: regexRel.slice(0, 3).some(Boolean),
162
+ firstRelevantRank: firstRegexRank,
163
+ // ADR-081 labelled metrics
164
+ label_top1: labelRel[0] || false,
165
+ label_top3_count: labelRel.slice(0, 3).filter(Boolean).length,
166
+ label_top5_count: labelRel.slice(0, 5).filter(Boolean).length,
167
+ label_firstRank: firstLabelRank,
168
+ label_ndcg3: ndcgAtK(labelRel, 3),
169
+ label_ndcg5: ndcgAtK(labelRel, 5),
170
+ topK: matches.map((m, i) => ({
93
171
  id: m.id,
94
172
  name: m.name?.slice(0, 100),
95
173
  type: m.type,
@@ -97,20 +175,38 @@ async function main() {
97
175
  cosineScore: m.cosineScore,
98
176
  bm25Score: m.bm25Score,
99
177
  mmrScore: m.mmrScore,
178
+ labelRelevant: labelRel[i],
100
179
  })),
101
180
  });
102
181
  }
103
182
  const queryMs = performance.now() - tQuery0;
104
183
 
105
184
  const matchedQueries = results.filter((r) => r.matched).length;
185
+ // Regex-proxy metrics (back-compat)
106
186
  const top1Hits = results.filter((r) => r.top1Relevant).length;
107
187
  const top3Hits = results.filter((r) => r.top3Relevant).length;
108
188
  const ranks = results.filter((r) => r.firstRelevantRank > 0).map((r) => r.firstRelevantRank);
109
- // MRR over the top-3 window (rank-of-first-relevant). Queries with no
110
- // relevant result in top-3 contribute 0 to the mean.
111
189
  const mrr3 = QUERIES.length > 0
112
190
  ? Number((ranks.reduce((s, r) => s + 1 / r, 0) / QUERIES.length).toFixed(4))
113
191
  : 0;
192
+
193
+ // ADR-081 labelled metrics — tighter ground truth than regex proxy.
194
+ const label_top1Hits = results.filter((r) => r.label_top1).length;
195
+ const label_top3HitsBinary = results.filter((r) => r.label_top3_count > 0).length;
196
+ const label_ranks = results.filter((r) => r.label_firstRank > 0).map((r) => r.label_firstRank);
197
+ const label_mrr3 = QUERIES.length > 0
198
+ ? Number((label_ranks.reduce((s, r) => s + 1 / r, 0) / QUERIES.length).toFixed(4))
199
+ : 0;
200
+ const label_ndcg3_mean = QUERIES.length > 0
201
+ ? Number((results.reduce((s, r) => s + r.label_ndcg3, 0) / QUERIES.length).toFixed(4))
202
+ : 0;
203
+ const label_ndcg5_mean = QUERIES.length > 0
204
+ ? Number((results.reduce((s, r) => s + r.label_ndcg5, 0) / QUERIES.length).toFixed(4))
205
+ : 0;
206
+ // Mean precision@3 — fraction of top-3 that's relevant per query, averaged.
207
+ const label_precision3 = QUERIES.length > 0
208
+ ? Number((results.reduce((s, r) => s + r.label_top3_count / 3, 0) / QUERIES.length).toFixed(4))
209
+ : 0;
114
210
  // top-1 collision: number of distinct top-1 IDs over the query count.
115
211
  const uniqueTop1 = top1Ids.size;
116
212
  const top1Diversity = Number((uniqueTop1 / QUERIES.length).toFixed(4));
@@ -134,13 +230,22 @@ async function main() {
134
230
  runAt: new Date().toISOString(),
135
231
  benchmark: 'pretrained-retrieval',
136
232
  mode, // ADR-078: which retrieval path was used
233
+ rerank: useRerank, // ADR-080: cross-encoder rerank on/off
137
234
  storeSize: total,
138
235
  queries: QUERIES.length,
139
236
  matchedQueries,
140
237
  matchRate: Number((matchedQueries / QUERIES.length).toFixed(4)),
141
- top1HitRate: Number((top1Hits / QUERIES.length).toFixed(4)), // ADR-078: relevance, not just any-match
238
+ // Regex-proxy metrics (ADR-077-080 back-compat)
239
+ top1HitRate: Number((top1Hits / QUERIES.length).toFixed(4)),
142
240
  top3HitRate: Number((top3Hits / QUERIES.length).toFixed(4)),
143
- mrr3, // mean reciprocal rank over top-3
241
+ mrr3,
242
+ // ADR-081 labelled metrics — tighter ground truth
243
+ label_top1HitRate: Number((label_top1Hits / QUERIES.length).toFixed(4)),
244
+ label_top3HitRate: Number((label_top3HitsBinary / QUERIES.length).toFixed(4)),
245
+ label_mrr3,
246
+ label_precision3,
247
+ label_ndcg3: label_ndcg3_mean,
248
+ label_ndcg5: label_ndcg5_mean,
144
249
  top1Diversity, // 1.0 = every query gets a distinct top-1
145
250
  top3DupRate, // 0.0 = no duplicate IDs inside any top-3
146
251
  avgQueryLatencyMs: Number((queryMs / QUERIES.length).toFixed(2)),
@@ -153,13 +258,20 @@ async function main() {
153
258
  console.log(JSON.stringify(summary, null, 2));
154
259
  } else {
155
260
  console.log(`# Pretrained-retrieval benchmark — proof of learning`);
156
- console.log(`Mode: ${mode}${mode === 'hybrid' ? ' (cosine + BM25 + MMR, ADR-078)' : ' (cosine-only, pre-3.10.18)'}`);
261
+ console.log(`Mode: ${mode}${mode === 'hybrid' ? ' (cosine + BM25 + MMR, ADR-078)' : ' (cosine-only, pre-3.10.18)'}${useRerank ? ' + cross-encoder rerank (ADR-080)' : ''}`);
157
262
  console.log(`Store size: ${total} patterns`);
158
263
  console.log(`Queries: ${QUERIES.length}`);
159
264
  console.log(`Match rate: ${(summary.matchRate * 100).toFixed(0)}% (${matchedQueries}/${QUERIES.length})`);
160
- console.log(`Top-1 hit rate (RELEVANCE): ${(summary.top1HitRate * 100).toFixed(0)}% (${top1Hits}/${QUERIES.length})`);
161
- console.log(`Top-3 hit rate (RELEVANCE): ${(summary.top3HitRate * 100).toFixed(0)}% (${top3Hits}/${QUERIES.length})`);
162
- console.log(`MRR@3: ${summary.mrr3}`);
265
+ console.log(`Top-1 hit rate (regex proxy): ${(summary.top1HitRate * 100).toFixed(0)}% (${top1Hits}/${QUERIES.length})`);
266
+ console.log(`Top-3 hit rate (regex proxy): ${(summary.top3HitRate * 100).toFixed(0)}% (${top3Hits}/${QUERIES.length})`);
267
+ console.log(`MRR@3 (regex proxy): ${summary.mrr3}`);
268
+ console.log('');
269
+ console.log(`Top-1 hit rate (ADR-081 labelled): ${(summary.label_top1HitRate * 100).toFixed(0)}% (${label_top1Hits}/${QUERIES.length})`);
270
+ console.log(`Top-3 hit rate (ADR-081 labelled): ${(summary.label_top3HitRate * 100).toFixed(0)}% (${label_top3HitsBinary}/${QUERIES.length})`);
271
+ console.log(`MRR@3 (labelled): ${summary.label_mrr3}`);
272
+ console.log(`Precision@3 (labelled, mean): ${summary.label_precision3}`);
273
+ console.log(`nDCG@3 (labelled, mean): ${summary.label_ndcg3}`);
274
+ console.log(`nDCG@5 (labelled, mean): ${summary.label_ndcg5}`);
163
275
  console.log(`Top-1 diversity: ${(summary.top1Diversity * 100).toFixed(0)}% (${uniqueTop1} distinct top-1 IDs across ${QUERIES.length} queries)`);
164
276
  console.log(`Top-3 dup rate: ${(summary.top3DupRate * 100).toFixed(0)}%`);
165
277
  console.log(`Avg query latency: ${summary.avgQueryLatencyMs} ms`);