@claude-flow/cli 3.10.17 → 3.10.19
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/neural-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/neural-tools.js +92 -15
- package/dist/src/mcp-tools/neural-tools.js.map +1 -1
- package/dist/src/memory/hybrid-retrieval.d.ts +77 -0
- package/dist/src/memory/hybrid-retrieval.d.ts.map +1 -0
- package/dist/src/memory/hybrid-retrieval.js +192 -0
- package/dist/src/memory/hybrid-retrieval.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/scripts/benchmark-pretrained-retrieval.mjs +85 -16
- package/scripts/pretrain-from-github.mjs +111 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.19",
|
|
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",
|
|
@@ -21,19 +21,20 @@ const REPO_ROOT = resolve(SCRIPT_DIR, '../../../..');
|
|
|
21
21
|
const RUNS_DIR = join(REPO_ROOT, 'docs', 'benchmarks', 'runs');
|
|
22
22
|
|
|
23
23
|
// Real ruflo-history-shaped queries. Each one targets a concept that should
|
|
24
|
-
// have been seen during pretrain
|
|
25
|
-
//
|
|
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).
|
|
26
27
|
const QUERIES = [
|
|
27
|
-
'how was the Opus model alias fixed',
|
|
28
|
-
'self-learning wiring task-completed pretrain',
|
|
29
|
-
'deterministic codemod engine var-to-const',
|
|
30
|
-
'MCP server orphan leak parent-death',
|
|
31
|
-
'unified learning stats aggregator',
|
|
32
|
-
'structured distillation 4-field schema',
|
|
33
|
-
'SQL injection migrate.ts table identifier',
|
|
34
|
-
'recall@k HNSW benchmark harness',
|
|
35
|
-
'Q-learning encoder keyword block',
|
|
36
|
-
'security hardening crypto random IDs',
|
|
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 },
|
|
37
38
|
];
|
|
38
39
|
|
|
39
40
|
const TOP_K = 5;
|
|
@@ -51,35 +52,97 @@ async function main() {
|
|
|
51
52
|
process.exit(2);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
// §2 —
|
|
55
|
-
//
|
|
55
|
+
// §2 — A/B hybrid vs cosine-only (HYBRID=0 forces pre-3.10.18 behaviour).
|
|
56
|
+
// Default runs hybrid (cosine + BM25 + MMR per ADR-078).
|
|
56
57
|
const listTool = neural.neuralTools.find((t) => t.name === 'neural_patterns');
|
|
58
|
+
const mode = process.env.HYBRID === '0' ? 'cosine' : 'hybrid';
|
|
59
|
+
|
|
60
|
+
// top-1-uniqueness — fraction of queries whose top-1 result is NOT the
|
|
61
|
+
// same pattern ID as another query's top-1. Catches the "everyone gets
|
|
62
|
+
// the same generic top-1" failure mode.
|
|
63
|
+
const top1Ids = new Map();
|
|
64
|
+
|
|
57
65
|
const tQuery0 = performance.now();
|
|
58
66
|
const results = [];
|
|
59
|
-
for (const q of QUERIES) {
|
|
60
|
-
const r = await listTool.handler({ action: 'search', query: q });
|
|
67
|
+
for (const { q, expect } of QUERIES) {
|
|
68
|
+
const r = await listTool.handler({ action: 'search', query: q, mode, limit: TOP_K });
|
|
61
69
|
const matches = (r.patterns || r.results || r.matches || []).slice(0, TOP_K);
|
|
70
|
+
if (matches.length > 0) {
|
|
71
|
+
const top1 = matches[0].id;
|
|
72
|
+
top1Ids.set(top1, (top1Ids.get(top1) ?? 0) + 1);
|
|
73
|
+
}
|
|
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
|
+
}
|
|
85
|
+
}
|
|
62
86
|
results.push({
|
|
63
87
|
query: q,
|
|
64
88
|
matched: matches.length > 0,
|
|
89
|
+
top1Relevant,
|
|
90
|
+
top3Relevant,
|
|
91
|
+
firstRelevantRank,
|
|
65
92
|
topK: matches.map((m) => ({
|
|
66
93
|
id: m.id,
|
|
67
94
|
name: m.name?.slice(0, 100),
|
|
68
95
|
type: m.type,
|
|
69
96
|
score: m.score ?? m.similarity,
|
|
97
|
+
cosineScore: m.cosineScore,
|
|
98
|
+
bm25Score: m.bm25Score,
|
|
99
|
+
mmrScore: m.mmrScore,
|
|
70
100
|
})),
|
|
71
101
|
});
|
|
72
102
|
}
|
|
73
103
|
const queryMs = performance.now() - tQuery0;
|
|
74
104
|
|
|
75
105
|
const matchedQueries = results.filter((r) => r.matched).length;
|
|
106
|
+
const top1Hits = results.filter((r) => r.top1Relevant).length;
|
|
107
|
+
const top3Hits = results.filter((r) => r.top3Relevant).length;
|
|
108
|
+
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
|
+
const mrr3 = QUERIES.length > 0
|
|
112
|
+
? Number((ranks.reduce((s, r) => s + 1 / r, 0) / QUERIES.length).toFixed(4))
|
|
113
|
+
: 0;
|
|
114
|
+
// top-1 collision: number of distinct top-1 IDs over the query count.
|
|
115
|
+
const uniqueTop1 = top1Ids.size;
|
|
116
|
+
const top1Diversity = Number((uniqueTop1 / QUERIES.length).toFixed(4));
|
|
117
|
+
|
|
118
|
+
// top-3 redundancy: average fraction of top-3 results that are duplicates
|
|
119
|
+
// of the same pattern within a single query (should be 0 — but if the same
|
|
120
|
+
// ID appears twice in top-3 we surface it).
|
|
121
|
+
let dupCount = 0, top3Slots = 0;
|
|
122
|
+
for (const r of results) {
|
|
123
|
+
const ids = r.topK.slice(0, 3).map((m) => m.id);
|
|
124
|
+
top3Slots += ids.length;
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
for (const id of ids) {
|
|
127
|
+
if (seen.has(id)) dupCount++;
|
|
128
|
+
seen.add(id);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const top3DupRate = top3Slots > 0 ? Number((dupCount / top3Slots).toFixed(4)) : 0;
|
|
132
|
+
|
|
76
133
|
const summary = {
|
|
77
134
|
runAt: new Date().toISOString(),
|
|
78
135
|
benchmark: 'pretrained-retrieval',
|
|
136
|
+
mode, // ADR-078: which retrieval path was used
|
|
79
137
|
storeSize: total,
|
|
80
138
|
queries: QUERIES.length,
|
|
81
139
|
matchedQueries,
|
|
82
140
|
matchRate: Number((matchedQueries / QUERIES.length).toFixed(4)),
|
|
141
|
+
top1HitRate: Number((top1Hits / QUERIES.length).toFixed(4)), // ADR-078: relevance, not just any-match
|
|
142
|
+
top3HitRate: Number((top3Hits / QUERIES.length).toFixed(4)),
|
|
143
|
+
mrr3, // mean reciprocal rank over top-3
|
|
144
|
+
top1Diversity, // 1.0 = every query gets a distinct top-1
|
|
145
|
+
top3DupRate, // 0.0 = no duplicate IDs inside any top-3
|
|
83
146
|
avgQueryLatencyMs: Number((queryMs / QUERIES.length).toFixed(2)),
|
|
84
147
|
totalQueryMs: Number(queryMs.toFixed(2)),
|
|
85
148
|
results,
|
|
@@ -90,9 +153,15 @@ async function main() {
|
|
|
90
153
|
console.log(JSON.stringify(summary, null, 2));
|
|
91
154
|
} else {
|
|
92
155
|
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)'}`);
|
|
93
157
|
console.log(`Store size: ${total} patterns`);
|
|
94
158
|
console.log(`Queries: ${QUERIES.length}`);
|
|
95
159
|
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}`);
|
|
163
|
+
console.log(`Top-1 diversity: ${(summary.top1Diversity * 100).toFixed(0)}% (${uniqueTop1} distinct top-1 IDs across ${QUERIES.length} queries)`);
|
|
164
|
+
console.log(`Top-3 dup rate: ${(summary.top3DupRate * 100).toFixed(0)}%`);
|
|
96
165
|
console.log(`Avg query latency: ${summary.avgQueryLatencyMs} ms`);
|
|
97
166
|
console.log('');
|
|
98
167
|
for (const r of results) {
|
|
@@ -43,29 +43,110 @@ const SOURCE = process.env.SOURCE || 'all'; // 'all' | 'git' | 'issues'
|
|
|
43
43
|
// Harvesters
|
|
44
44
|
// ---------------------------------------------------------------------------
|
|
45
45
|
|
|
46
|
+
// ADR-078 outcome signal — classify each commit by whether it was reverted,
|
|
47
|
+
// hotfix-followed, or stuck. Operates on the harvested-commits window plus a
|
|
48
|
+
// wider lookahead window for revert/hotfix detection.
|
|
49
|
+
//
|
|
50
|
+
// Verdicts emitted:
|
|
51
|
+
// success — landed cleanly, no later commit reverted or fixed it
|
|
52
|
+
// reverted — a later commit subject starts with `Revert "<this subject>"`
|
|
53
|
+
// hotfixed — a later commit (within HOTFIX_WINDOW_COMMITS) shares >=50%
|
|
54
|
+
// of the same touched files AND subject contains fix|hotfix|patch
|
|
55
|
+
//
|
|
56
|
+
// The "later" direction is git-log order (newest first → we look at older
|
|
57
|
+
// indices in the lookahead, which are NEWER commits chronologically).
|
|
58
|
+
const HOTFIX_WINDOW_COMMITS = Number(process.env.HOTFIX_WINDOW_COMMITS) || 20;
|
|
59
|
+
const HOTFIX_FILE_OVERLAP = Number(process.env.HOTFIX_FILE_OVERLAP) || 0.5;
|
|
60
|
+
const HOTFIX_KEYWORDS = /\b(fix|hotfix|patch|revert|bugfix|fixup)\b/i;
|
|
61
|
+
|
|
46
62
|
function harvestCommits(n) {
|
|
47
63
|
if (SOURCE === 'issues') return [];
|
|
48
64
|
const fmt = '%H%x00%s%x00%b%x01';
|
|
65
|
+
// Pull a wider window so we have lookahead for revert/hotfix detection
|
|
66
|
+
// without changing the trained set size n.
|
|
67
|
+
const lookahead = HOTFIX_WINDOW_COMMITS;
|
|
49
68
|
const raw = execSync(
|
|
50
|
-
`git log --pretty=format:'${fmt}' -n ${n} 2>/dev/null`,
|
|
69
|
+
`git log --pretty=format:'${fmt}' -n ${n + lookahead} 2>/dev/null`,
|
|
51
70
|
{ cwd: REPO_ROOT, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 },
|
|
52
71
|
);
|
|
53
|
-
|
|
72
|
+
|
|
73
|
+
// Parse all blocks (window + lookahead).
|
|
74
|
+
const all = [];
|
|
54
75
|
for (const block of raw.split('\x01')) {
|
|
55
76
|
if (!block.trim()) continue;
|
|
56
77
|
const [sha, subject, body] = block.split('\x00');
|
|
57
78
|
if (!sha || !subject) continue;
|
|
79
|
+
all.push({ sha, subject: subject.trim(), body: (body || '').trim() });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// For each commit in the trained slice, get its touched files.
|
|
83
|
+
const filesCache = new Map();
|
|
84
|
+
const filesOf = (sha) => {
|
|
85
|
+
if (filesCache.has(sha)) return filesCache.get(sha);
|
|
86
|
+
try {
|
|
87
|
+
const out = execSync(
|
|
88
|
+
`git show --pretty=format: --name-only ${sha} 2>/dev/null`,
|
|
89
|
+
{ cwd: REPO_ROOT, encoding: 'utf-8', maxBuffer: 8 * 1024 * 1024 },
|
|
90
|
+
).trim().split('\n').filter(Boolean);
|
|
91
|
+
filesCache.set(sha, new Set(out));
|
|
92
|
+
return filesCache.get(sha);
|
|
93
|
+
} catch {
|
|
94
|
+
filesCache.set(sha, new Set());
|
|
95
|
+
return filesCache.get(sha);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Build a revert-target map: any commit whose subject starts with
|
|
100
|
+
// `Revert "<X>"` flags X as reverted.
|
|
101
|
+
const revertedSubjects = new Set();
|
|
102
|
+
for (const c of all) {
|
|
103
|
+
const m = c.subject.match(/^Revert\s+"(.+?)"/);
|
|
104
|
+
if (m) revertedSubjects.add(m[1].trim());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const trained = all.slice(0, n);
|
|
108
|
+
const entries = [];
|
|
109
|
+
for (let i = 0; i < trained.length; i++) {
|
|
110
|
+
const c = trained[i];
|
|
111
|
+
const myFiles = filesOf(c.sha);
|
|
112
|
+
|
|
113
|
+
let verdict = 'success';
|
|
114
|
+
let outcomeNote = null;
|
|
115
|
+
|
|
116
|
+
// Reverted? Check if any later (lower-index = newer) commit reverted us.
|
|
117
|
+
if (revertedSubjects.has(c.subject)) {
|
|
118
|
+
verdict = 'reverted';
|
|
119
|
+
outcomeNote = 'subject reverted by a later commit';
|
|
120
|
+
} else {
|
|
121
|
+
// Hotfixed? A later commit within HOTFIX_WINDOW_COMMITS shares files
|
|
122
|
+
// AND has fix/hotfix in its subject.
|
|
123
|
+
const start = Math.max(0, i - HOTFIX_WINDOW_COMMITS);
|
|
124
|
+
for (let j = start; j < i; j++) {
|
|
125
|
+
const later = trained[j];
|
|
126
|
+
if (!HOTFIX_KEYWORDS.test(later.subject)) continue;
|
|
127
|
+
const laterFiles = filesOf(later.sha);
|
|
128
|
+
if (laterFiles.size === 0 || myFiles.size === 0) continue;
|
|
129
|
+
let overlap = 0;
|
|
130
|
+
for (const f of laterFiles) if (myFiles.has(f)) overlap++;
|
|
131
|
+
// Use min() so a small targeted fix on a big change still triggers
|
|
132
|
+
// (semantic: "≥half of the smaller commit's files overlap").
|
|
133
|
+
const overlapFrac = overlap / Math.min(laterFiles.size, myFiles.size);
|
|
134
|
+
if (overlapFrac >= HOTFIX_FILE_OVERLAP) {
|
|
135
|
+
verdict = 'hotfixed';
|
|
136
|
+
outcomeNote = `${(overlapFrac * 100).toFixed(0)}% file overlap with later fix ${later.sha.slice(0, 8)}`;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
58
142
|
entries.push({
|
|
59
143
|
source: 'commit',
|
|
60
|
-
id: `commit-${sha.slice(0, 12)}`,
|
|
61
|
-
subject: subject
|
|
62
|
-
body:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// success; open issues are partial).
|
|
67
|
-
verdict: 'success',
|
|
68
|
-
content: `${subject.trim()}\n\n${(body || '').trim()}`.slice(0, 8192),
|
|
144
|
+
id: `commit-${c.sha.slice(0, 12)}`,
|
|
145
|
+
subject: c.subject,
|
|
146
|
+
body: c.body,
|
|
147
|
+
verdict,
|
|
148
|
+
outcomeNote,
|
|
149
|
+
content: `${c.subject}\n\n${c.body}`.slice(0, 8192),
|
|
69
150
|
});
|
|
70
151
|
}
|
|
71
152
|
return entries;
|
|
@@ -126,7 +207,19 @@ async function main() {
|
|
|
126
207
|
console.log(`Harvested: ${commits.length} commits + ${issues.length} issues = ${items.length} trajectories (${harvestMs.toFixed(0)} ms)`);
|
|
127
208
|
}
|
|
128
209
|
|
|
129
|
-
// §3 — feed each item through the trajectory pipeline.
|
|
210
|
+
// §3 — feed each item through the trajectory pipeline. The harvester
|
|
211
|
+
// emits one of: success | partial | reverted | hotfixed. We map this to
|
|
212
|
+
// the trajectory pipeline's binary verdict {success, partial} since the
|
|
213
|
+
// pipeline doesn't accept arbitrary strings — but we preserve the original
|
|
214
|
+
// outcome in metadata + the summary's verdictMix so the signal isn't lost.
|
|
215
|
+
const verdictToPipeline = (v) => {
|
|
216
|
+
if (v === 'success') return 'success';
|
|
217
|
+
if (v === 'partial') return 'partial';
|
|
218
|
+
if (v === 'reverted') return 'partial'; // a revert is the strongest "this was wrong" signal we have
|
|
219
|
+
if (v === 'hotfixed') return 'partial'; // a same-files fix-followup is "needs adjustment"
|
|
220
|
+
return 'success';
|
|
221
|
+
};
|
|
222
|
+
const verdictMix = { success: 0, partial: 0, reverted: 0, hotfixed: 0 };
|
|
130
223
|
const tFeed0 = performance.now();
|
|
131
224
|
let trained = 0;
|
|
132
225
|
let failed = 0;
|
|
@@ -134,6 +227,7 @@ async function main() {
|
|
|
134
227
|
for (const item of items) {
|
|
135
228
|
try {
|
|
136
229
|
const distilled = distillAndSerialise(item.content);
|
|
230
|
+
verdictMix[item.verdict] = (verdictMix[item.verdict] ?? 0) + 1;
|
|
137
231
|
await intel.recordTrajectory(
|
|
138
232
|
[{
|
|
139
233
|
type: 'result',
|
|
@@ -142,10 +236,12 @@ async function main() {
|
|
|
142
236
|
source: item.source,
|
|
143
237
|
id: item.id,
|
|
144
238
|
subject: item.subject.slice(0, 200),
|
|
239
|
+
outcomeVerdict: item.verdict,
|
|
240
|
+
outcomeNote: item.outcomeNote ?? null,
|
|
145
241
|
},
|
|
146
242
|
timestamp: Date.now(),
|
|
147
243
|
}],
|
|
148
|
-
item.verdict,
|
|
244
|
+
verdictToPipeline(item.verdict),
|
|
149
245
|
);
|
|
150
246
|
trained++;
|
|
151
247
|
} catch (err) {
|
|
@@ -201,6 +297,7 @@ async function main() {
|
|
|
201
297
|
avgLatencyMs: items.length > 0 ? Number((feedMs / items.length).toFixed(2)) : 0,
|
|
202
298
|
totalMs: Number(feedMs.toFixed(2)),
|
|
203
299
|
sampleFailures: failures,
|
|
300
|
+
verdictMix, // ADR-078 outcome signal — counts per harvested verdict
|
|
204
301
|
},
|
|
205
302
|
seedNeuralStore: {
|
|
206
303
|
stored: seedResult.stored,
|
|
@@ -231,6 +328,7 @@ async function main() {
|
|
|
231
328
|
console.log(`Failed: ${failed}`);
|
|
232
329
|
console.log(`Avg latency per trajectory: ${summary.feed.avgLatencyMs} ms`);
|
|
233
330
|
console.log(`Neural store seeded: ${seedResult.stored}/${seedResult.total}`);
|
|
331
|
+
console.log(`Verdict mix: success=${verdictMix.success} partial=${verdictMix.partial} reverted=${verdictMix.reverted} hotfixed=${verdictMix.hotfixed}`);
|
|
234
332
|
console.log(`Overall: ${summary.passed ? '✅ PASSED' : '⚠️ partial'}`);
|
|
235
333
|
if (unified1.consistency.notes.length > 0) {
|
|
236
334
|
console.log(`\nConsistency notes:`);
|