@claude-flow/cli 3.10.16 → 3.10.18

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.16",
3
+ "version": "3.10.18",
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",
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ // benchmark-pretrained-retrieval.mjs — proof that pretrained patterns are
3
+ // retrievable, not just stored.
4
+ //
5
+ // Runs sample queries against the neural store (post-pretrain) and reports
6
+ // the top-k matches. Demonstrates that after `pretrain-from-github.mjs`
7
+ // runs, an agent can recall relevant past work by intent.
8
+ //
9
+ // Usage:
10
+ // 1. node scripts/pretrain-from-github.mjs # populate the store
11
+ // 2. node scripts/benchmark-pretrained-retrieval.mjs # query + report
12
+
13
+ import { writeFileSync, mkdirSync } from 'node:fs';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { dirname, join, resolve } from 'node:path';
16
+ import { performance } from 'node:perf_hooks';
17
+
18
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
19
+ const CLI_ROOT = resolve(SCRIPT_DIR, '..');
20
+ const REPO_ROOT = resolve(SCRIPT_DIR, '../../../..');
21
+ const RUNS_DIR = join(REPO_ROOT, 'docs', 'benchmarks', 'runs');
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).
27
+ 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 },
38
+ ];
39
+
40
+ const TOP_K = 5;
41
+
42
+ async function main() {
43
+ const intel = await import(join(CLI_ROOT, 'dist/src/memory/intelligence.js'));
44
+ const neural = await import(join(CLI_ROOT, 'dist/src/mcp-tools/neural-tools.js'));
45
+
46
+ // §1 — snapshot the neural store + globalStats so we know what's there.
47
+ const unified = await intel.getUnifiedLearningStats();
48
+ const total = unified.neuralPatterns.patternCount;
49
+
50
+ if (total === 0) {
51
+ console.error('No patterns in neural store. Run scripts/pretrain-from-github.mjs first.');
52
+ process.exit(2);
53
+ }
54
+
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).
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
+
65
+ const tQuery0 = performance.now();
66
+ const results = [];
67
+ for (const { q, expect } of QUERIES) {
68
+ const r = await listTool.handler({ action: 'search', query: q, mode, limit: TOP_K });
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
+ }
86
+ results.push({
87
+ query: q,
88
+ matched: matches.length > 0,
89
+ top1Relevant,
90
+ top3Relevant,
91
+ firstRelevantRank,
92
+ topK: matches.map((m) => ({
93
+ id: m.id,
94
+ name: m.name?.slice(0, 100),
95
+ type: m.type,
96
+ score: m.score ?? m.similarity,
97
+ cosineScore: m.cosineScore,
98
+ bm25Score: m.bm25Score,
99
+ mmrScore: m.mmrScore,
100
+ })),
101
+ });
102
+ }
103
+ const queryMs = performance.now() - tQuery0;
104
+
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
+
133
+ const summary = {
134
+ runAt: new Date().toISOString(),
135
+ benchmark: 'pretrained-retrieval',
136
+ mode, // ADR-078: which retrieval path was used
137
+ storeSize: total,
138
+ queries: QUERIES.length,
139
+ matchedQueries,
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
146
+ avgQueryLatencyMs: Number((queryMs / QUERIES.length).toFixed(2)),
147
+ totalQueryMs: Number(queryMs.toFixed(2)),
148
+ results,
149
+ passed: matchedQueries === QUERIES.length,
150
+ };
151
+
152
+ if (process.env.BENCH_JSON) {
153
+ console.log(JSON.stringify(summary, null, 2));
154
+ } else {
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)'}`);
157
+ console.log(`Store size: ${total} patterns`);
158
+ console.log(`Queries: ${QUERIES.length}`);
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)}%`);
165
+ console.log(`Avg query latency: ${summary.avgQueryLatencyMs} ms`);
166
+ console.log('');
167
+ for (const r of results) {
168
+ console.log(`Q: "${r.query}"`);
169
+ if (r.topK.length === 0) {
170
+ console.log(` → no matches`);
171
+ } else {
172
+ for (const m of r.topK.slice(0, 3)) {
173
+ console.log(` → ${m.score?.toFixed?.(3) ?? '—'} ${m.name}`);
174
+ }
175
+ }
176
+ }
177
+ console.log('');
178
+ console.log(`Overall: ${summary.passed ? '✅ PASSED' : '⚠️ partial'}`);
179
+ }
180
+
181
+ if (!process.env.BENCH_NO_WRITE) {
182
+ mkdirSync(RUNS_DIR, { recursive: true });
183
+ const stamp = summary.runAt.replace(/[:.]/g, '-');
184
+ writeFileSync(join(RUNS_DIR, `pretrained-retrieval-${stamp}.json`), JSON.stringify(summary, null, 2));
185
+ writeFileSync(join(RUNS_DIR, 'pretrained-retrieval-latest.json'), JSON.stringify(summary, null, 2));
186
+ if (!process.env.BENCH_JSON) console.log(`\nWrote ${join(RUNS_DIR, `pretrained-retrieval-${stamp}.json`)}`);
187
+ }
188
+
189
+ // ONNX runtime keeps a worker thread alive — force exit.
190
+ process.exit(summary.passed ? 0 : 1);
191
+ }
192
+
193
+ main().catch((err) => { console.error(err); process.exit(1); });
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+ // pretrain-from-github.mjs — pretrain ruflo's self-learning system from its
3
+ // own GitHub history (commits + issues). Each commit/issue becomes one
4
+ // trajectory through the SONA + EWC++ pipeline; Structured Distillation
5
+ // (ADR-076) compresses each into the 4-field schema before embedding.
6
+ //
7
+ // What this proves:
8
+ // - globalStats.{trajectoriesRecorded, patternsLearned, signalsProcessed}
9
+ // all move from a measured before to a measured after.
10
+ // - neural_patterns.patternCount grows.
11
+ // - memory-bridge entries grow.
12
+ // - The unified-stats aggregator's consistency block stays clean.
13
+ // - Every item gets a learningPath of 'trajectory-pipeline' (not 'recorded-only').
14
+ //
15
+ // Usage:
16
+ // node scripts/pretrain-from-github.mjs # 50 commits + 30 issues
17
+ // COMMITS=200 ISSUES=100 node scripts/pretrain-from-github.mjs
18
+ // SOURCE=git node scripts/pretrain-from-github.mjs # git only, skip gh
19
+ // BENCH_JSON=1 node scripts/pretrain-from-github.mjs # machine-readable
20
+ // BENCH_NO_WRITE=1 node scripts/pretrain-from-github.mjs # don't write a run JSON
21
+ //
22
+ // Repro from a fresh checkout:
23
+ // git clone https://github.com/ruvnet/ruflo && cd ruflo
24
+ // npm install && ( cd v3/@claude-flow/cli && npx tsc -b )
25
+ // node v3/@claude-flow/cli/scripts/pretrain-from-github.mjs
26
+
27
+ import { execSync } from 'node:child_process';
28
+ import { writeFileSync, mkdirSync } from 'node:fs';
29
+ import { fileURLToPath } from 'node:url';
30
+ import { dirname, join, resolve } from 'node:path';
31
+ import { performance } from 'node:perf_hooks';
32
+
33
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
34
+ const CLI_ROOT = resolve(SCRIPT_DIR, '..');
35
+ const REPO_ROOT = resolve(SCRIPT_DIR, '../../../..');
36
+ const RUNS_DIR = join(REPO_ROOT, 'docs', 'benchmarks', 'runs');
37
+
38
+ const COMMITS = Number(process.env.COMMITS) || 50;
39
+ const ISSUES = Number(process.env.ISSUES) || 30;
40
+ const SOURCE = process.env.SOURCE || 'all'; // 'all' | 'git' | 'issues'
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Harvesters
44
+ // ---------------------------------------------------------------------------
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
+
62
+ function harvestCommits(n) {
63
+ if (SOURCE === 'issues') return [];
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;
68
+ const raw = execSync(
69
+ `git log --pretty=format:'${fmt}' -n ${n + lookahead} 2>/dev/null`,
70
+ { cwd: REPO_ROOT, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 },
71
+ );
72
+
73
+ // Parse all blocks (window + lookahead).
74
+ const all = [];
75
+ for (const block of raw.split('\x01')) {
76
+ if (!block.trim()) continue;
77
+ const [sha, subject, body] = block.split('\x00');
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
+
142
+ entries.push({
143
+ source: 'commit',
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),
150
+ });
151
+ }
152
+ return entries;
153
+ }
154
+
155
+ function harvestIssues(n) {
156
+ if (SOURCE === 'git') return [];
157
+ try {
158
+ const raw = execSync(
159
+ `gh issue list --repo ruvnet/ruflo --state all --limit ${n} --json number,title,body,state,closedAt 2>/dev/null`,
160
+ { cwd: REPO_ROOT, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 },
161
+ );
162
+ const items = JSON.parse(raw);
163
+ return items.map((i) => ({
164
+ source: 'issue',
165
+ id: `issue-${i.number}`,
166
+ subject: i.title,
167
+ body: (i.body || '').slice(0, 8192),
168
+ // closed = success outcome; open = partial (in-progress).
169
+ verdict: i.state === 'CLOSED' ? 'success' : 'partial',
170
+ content: `${i.title}\n\n${(i.body || '').slice(0, 8192)}`,
171
+ }));
172
+ } catch (err) {
173
+ console.error(`gh issue harvest skipped (${err.message?.slice(0, 60)}). Set SOURCE=git to silence.`);
174
+ return [];
175
+ }
176
+ }
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Main
180
+ // ---------------------------------------------------------------------------
181
+
182
+ async function main() {
183
+ const intel = await import(join(CLI_ROOT, 'dist/src/memory/intelligence.js'));
184
+ const neural = await import(join(CLI_ROOT, 'dist/src/mcp-tools/neural-tools.js'));
185
+ const { distillAndSerialise } = await import(join(CLI_ROOT, 'dist/src/memory/structured-distill.js'));
186
+
187
+ // §1 — record the baseline (no clear; we want to learn ON TOP of whatever
188
+ // history the user already has).
189
+ const unified0 = await intel.getUnifiedLearningStats();
190
+ const before = {
191
+ trajectoriesRecorded: unified0.global.trajectoriesRecorded,
192
+ patternsLearned: unified0.global.patternsLearned,
193
+ signalsProcessed: unified0.global.signalsProcessed,
194
+ neuralPatternCount: unified0.neuralPatterns.patternCount,
195
+ memoryBridgeTotal: unified0.memoryBridge.totalEntries,
196
+ };
197
+
198
+ // §2 — harvest
199
+ const tHarvest0 = performance.now();
200
+ const commits = harvestCommits(COMMITS);
201
+ const issues = harvestIssues(ISSUES);
202
+ const items = [...commits, ...issues];
203
+ const harvestMs = performance.now() - tHarvest0;
204
+
205
+ if (!process.env.BENCH_JSON) {
206
+ console.log(`# Pretrain from ruflo GitHub history`);
207
+ console.log(`Harvested: ${commits.length} commits + ${issues.length} issues = ${items.length} trajectories (${harvestMs.toFixed(0)} ms)`);
208
+ }
209
+
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 };
223
+ const tFeed0 = performance.now();
224
+ let trained = 0;
225
+ let failed = 0;
226
+ const failures = [];
227
+ for (const item of items) {
228
+ try {
229
+ const distilled = distillAndSerialise(item.content);
230
+ verdictMix[item.verdict] = (verdictMix[item.verdict] ?? 0) + 1;
231
+ await intel.recordTrajectory(
232
+ [{
233
+ type: 'result',
234
+ content: distilled,
235
+ metadata: {
236
+ source: item.source,
237
+ id: item.id,
238
+ subject: item.subject.slice(0, 200),
239
+ outcomeVerdict: item.verdict,
240
+ outcomeNote: item.outcomeNote ?? null,
241
+ },
242
+ timestamp: Date.now(),
243
+ }],
244
+ verdictToPipeline(item.verdict),
245
+ );
246
+ trained++;
247
+ } catch (err) {
248
+ failed++;
249
+ if (failures.length < 5) failures.push({ id: item.id, error: String(err.message).slice(0, 120) });
250
+ }
251
+ }
252
+ const feedMs = performance.now() - tFeed0;
253
+ intel.flushIntelligenceStats();
254
+
255
+ // §4 — also seed the neural store directly from the same items so
256
+ // `neural_patterns list` reflects them (closes the "globalStats moved but
257
+ // neural_patterns didn't" consistency note from ADR-075).
258
+ const tSeed0 = performance.now();
259
+ const neuralItems = items.map((item) => ({
260
+ name: item.subject.slice(0, 200),
261
+ type: item.source === 'commit' ? 'history-commit' : 'history-issue',
262
+ content: distillAndSerialise(item.content),
263
+ metadata: { source: item.source, id: item.id, verdict: item.verdict },
264
+ }));
265
+ const seedResult = await neural.storeNeuralPatterns(neuralItems);
266
+ const seedMs = performance.now() - tSeed0;
267
+
268
+ // §5 — read the after-counters via the unified aggregator (this is what
269
+ // hooks_intelligence_unified-stats would return for a live caller).
270
+ const unified1 = await intel.getUnifiedLearningStats();
271
+ const after = {
272
+ trajectoriesRecorded: unified1.global.trajectoriesRecorded,
273
+ patternsLearned: unified1.global.patternsLearned,
274
+ signalsProcessed: unified1.global.signalsProcessed,
275
+ neuralPatternCount: unified1.neuralPatterns.patternCount,
276
+ memoryBridgeTotal: unified1.memoryBridge.totalEntries,
277
+ };
278
+
279
+ const deltas = Object.fromEntries(
280
+ Object.keys(after).map((k) => [k, after[k] - before[k]]),
281
+ );
282
+
283
+ const summary = {
284
+ runAt: new Date().toISOString(),
285
+ benchmark: 'pretrain-from-github',
286
+ source: SOURCE,
287
+ config: { COMMITS, ISSUES },
288
+ harvest: {
289
+ commits: commits.length,
290
+ issues: issues.length,
291
+ total: items.length,
292
+ harvestMs: Number(harvestMs.toFixed(2)),
293
+ },
294
+ feed: {
295
+ trained,
296
+ failed,
297
+ avgLatencyMs: items.length > 0 ? Number((feedMs / items.length).toFixed(2)) : 0,
298
+ totalMs: Number(feedMs.toFixed(2)),
299
+ sampleFailures: failures,
300
+ verdictMix, // ADR-078 outcome signal — counts per harvested verdict
301
+ },
302
+ seedNeuralStore: {
303
+ stored: seedResult.stored,
304
+ total: seedResult.total,
305
+ seedMs: Number(seedMs.toFixed(2)),
306
+ },
307
+ before,
308
+ after,
309
+ deltas,
310
+ consistency: unified1.consistency,
311
+ passed:
312
+ trained === items.length &&
313
+ deltas.trajectoriesRecorded >= items.length &&
314
+ deltas.neuralPatternCount >= items.length,
315
+ };
316
+
317
+ if (process.env.BENCH_JSON) {
318
+ console.log(JSON.stringify(summary, null, 2));
319
+ } else {
320
+ console.log('');
321
+ console.log('| Counter | Before | After | Δ |');
322
+ console.log('|---|---:|---:|---:|');
323
+ for (const k of Object.keys(after)) {
324
+ console.log(`| ${k} | ${before[k]} | ${after[k]} | +${deltas[k]} |`);
325
+ }
326
+ console.log('');
327
+ console.log(`Trained via trajectory pipeline: ${trained}/${items.length}`);
328
+ console.log(`Failed: ${failed}`);
329
+ console.log(`Avg latency per trajectory: ${summary.feed.avgLatencyMs} ms`);
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}`);
332
+ console.log(`Overall: ${summary.passed ? '✅ PASSED' : '⚠️ partial'}`);
333
+ if (unified1.consistency.notes.length > 0) {
334
+ console.log(`\nConsistency notes:`);
335
+ for (const n of unified1.consistency.notes) console.log(` • ${n}`);
336
+ }
337
+ }
338
+
339
+ if (!process.env.BENCH_NO_WRITE) {
340
+ mkdirSync(RUNS_DIR, { recursive: true });
341
+ const stamp = summary.runAt.replace(/[:.]/g, '-');
342
+ writeFileSync(join(RUNS_DIR, `pretrain-from-github-${stamp}.json`), JSON.stringify(summary, null, 2));
343
+ writeFileSync(join(RUNS_DIR, 'pretrain-from-github-latest.json'), JSON.stringify(summary, null, 2));
344
+ if (!process.env.BENCH_JSON) console.log(`\nWrote ${join(RUNS_DIR, `pretrain-from-github-${stamp}.json`)}`);
345
+ }
346
+
347
+ // ONNX runtime keeps a worker thread alive — force exit so this can be used
348
+ // as a CI step or chained with other scripts.
349
+ process.exit(summary.passed ? 0 : 1);
350
+ }
351
+
352
+ main().catch((err) => { console.error(err); process.exit(1); });