@claude-flow/cli 3.38.1 → 3.38.3

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.38.1",
3
+ "version": "3.38.3",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
6
6
  "hook-handler.cjs": "dae295fb9ae2626b89899c19a20cc911541af82b52d2eeb9b214d618b96e9a86",
@@ -8,6 +8,6 @@
8
8
  "statusline.cjs": "0457fe53f8cd2c56458ff178392536a5868efd1a573665fa43bc01d2d95ca677"
9
9
  }
10
10
  },
11
- "signature": "TN6Fpt7mR59UgYj2KM7wxDSHnm16eeq2BlouGa8YAxI+ZbrePbCpIUfUMNhED5vSbEPJhRvbiMnfO5JsYI8CBA==",
11
+ "signature": "+LCCU+BDg+NJQxxOVYmhpNNzgWAi4NXNYtdiNueujCaLjkoKboJs5MKThbWd7PHp7D9b+RjgqO9VMvCUWA41CQ==",
12
12
  "algorithm": "ed25519"
13
13
  }
package/README.md CHANGED
@@ -56,7 +56,7 @@ There are **two different install paths** with very different surface areas. Pic
56
56
  |---|---|---|
57
57
  | What it gives you | Slash commands + a few skills + agent definitions per-plugin | Full Ruflo loop — 98 agents, 60+ commands, 30 skills, MCP server, hooks, daemon |
58
58
  | Files in your workspace | **Zero** | `.claude/`, `.claude-flow/`, `CLAUDE.md`, helpers, settings |
59
- | MCP server registered | **No** (`memory_store`, `swarm_init`, etc. unavailable to Claude) | Yes |
59
+ | MCP server registered | Only if `ruflo-core` is installed (it ships its own `.mcp.json`) — most other plugins don't | Yes |
60
60
  | Hooks installed | No | Yes |
61
61
  | Best for | Try a single plugin's commands without committing to the full install | Production use — everything works as documented |
62
62
 
@@ -73,7 +73,7 @@ There are **two different install paths** with very different surface areas. Pic
73
73
  /plugin install ruflo-neural-trader@ruflo
74
74
  ```
75
75
 
76
- This adds slash commands and agent definitions only. The Ruflo MCP server is NOT registered, so `memory_store`, `swarm_init`, `agent_spawn`, etc. won't be callable from Claude. For the full loop, use Path B below.
76
+ This adds slash commands and agent definitions. `ruflo-core` (installed above) does register its own MCP server on install its tools are callable as `mcp__plugin_ruflo-core_ruflo__*` (e.g. `mcp__plugin_ruflo-core_ruflo__memory_store`), not the bare `memory_store`/`swarm_init`/`agent_spawn` names the CLI-track scaffold uses. Other plugins generally don't ship their own MCP server. For the full loop with the CLI-track tool names, use Path B below.
77
77
 
78
78
  <details>
79
79
  <summary><strong>🔌 All 35 plugins</strong></summary>
@@ -187,8 +187,8 @@ npm install -g ruflo@latest
187
187
  ### MCP Server
188
188
 
189
189
  ```bash
190
- # Add Ruflo as an MCP server in Claude Code (canonical form, matches USERGUIDE.md)
191
- claude mcp add ruflo -- npx ruflo@latest mcp start
190
+ # Add Ruflo as an MCP server in Claude Code
191
+ claude mcp add claude-flow -- npx ruflo@latest mcp start
192
192
  ```
193
193
 
194
194
  ---
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 4,
4
- "generatedAt": "2026-08-12T14:05:18.962Z",
5
- "gitSha": "b8c37da8",
4
+ "generatedAt": "2026-08-12T18:13:50.566Z",
5
+ "gitSha": "1e2178f2",
6
6
  "catalog": {
7
7
  "agents": 164,
8
8
  "tools": 397,
@@ -2194,7 +2194,12 @@ const intelligenceCommand = {
2194
2194
  tokenReduction: 'N/A',
2195
2195
  sweBenchScore: 'N/A',
2196
2196
  },
2197
- lastTrainingMs: lastAdaptation ? Date.now() - lastAdaptation : undefined,
2197
+ // #2940: a training cycle that actually ran this invocation is "now"
2198
+ // — reading the pre-call `lastAdaptation` here would still show the
2199
+ // stale age (the exact "aged instead of reset" symptom reported).
2200
+ lastTrainingMs: mcpResult?.training?.trained
2201
+ ? 0
2202
+ : (lastAdaptation ? Date.now() - lastAdaptation : undefined),
2198
2203
  persistence: {
2199
2204
  dataDir: persistence.dataDir,
2200
2205
  patternsFile: persistence.patternsFile,
@@ -2206,10 +2211,34 @@ const intelligenceCommand = {
2206
2211
  trajectoriesFromDisk,
2207
2212
  },
2208
2213
  };
2214
+ // #2940: this block used to be a cosmetic 500ms sleep followed by an
2215
+ // unconditional "Training cycle completed" — no training ever ran and
2216
+ // `lastAdaptation` never moved, so `--status` could never report a
2217
+ // training cycle as recent no matter how many times `--train` was
2218
+ // invoked. `hooks_intelligence` now actually distills when asked
2219
+ // (see mcp-tools/hooks-tools.ts); report what it actually did instead
2220
+ // of a canned success message.
2221
+ const trainingResult = mcpResult?.training;
2209
2222
  if (forceTraining) {
2210
- spinner.setText('Running training cycle...');
2211
- await new Promise(resolve => setTimeout(resolve, 500));
2212
- spinner.succeed('Training cycle completed');
2223
+ if (trainingResult?.trained && trainingResult.patternsDistilled) {
2224
+ spinner.succeed(`Training cycle completed distilled ${trainingResult.patternsDistilled} pattern${trainingResult.patternsDistilled === 1 ? '' : 's'}`);
2225
+ }
2226
+ else if (trainingResult?.trained) {
2227
+ // Ran (SONA/EWC++ distillation pass executed, lastAdaptation
2228
+ // moved) but found zero new patterns — deliberately NOT the same
2229
+ // message as a real distillation, or this is exactly the "success
2230
+ // report with no state change behind it" the issue describes.
2231
+ spinner.stop();
2232
+ output.printWarning(`Training cycle ran — 0 new patterns to distill (${trainingResult.reason || 'nothing new since the last cycle'})`);
2233
+ }
2234
+ else if (trainingResult?.attempted) {
2235
+ spinner.stop();
2236
+ output.printWarning(`Training cycle ran with nothing to do — ${trainingResult.reason || 'no new trajectories to distill'}`);
2237
+ }
2238
+ else {
2239
+ spinner.stop();
2240
+ output.printWarning('Training cycle could not run — intelligence MCP tool unavailable');
2241
+ }
2213
2242
  }
2214
2243
  else {
2215
2244
  spinner.succeed(hasLocalData ? 'Intelligence system active (local data loaded)' : 'Intelligence system active');
@@ -201,7 +201,43 @@ const startCommand = {
201
201
  if (daemon) {
202
202
  output.writeln(output.dim(' Running in background mode'));
203
203
  }
204
- return { success: true, data: status };
204
+ // #2984: this command is only reached via the "normal CLI mode" branch
205
+ // of bin/cli.js — i.e. every invocation NOT auto-detected as Claude
206
+ // Code's implicit piped-stdin MCP handshake (that path bypasses this
207
+ // action entirely and blocks on stdin forever, which is why it was
208
+ // unaffected). bin/cli.js unconditionally exits the process once this
209
+ // action's promise resolves (`cli.run().then(() => process.exit(0))`,
210
+ // #1552) — a design that assumed "mcp start never resolves" but this
211
+ // action DID resolve immediately after printing the table above, so
212
+ // the freshly-bound http/websocket listener (or the interactive-TTY
213
+ // stdio server) was torn down within milliseconds of the printed
214
+ // "Status: Running" claim. `daemonize` is not currently wired to
215
+ // actually fork/detach a background process (no branch reads it in
216
+ // MCPServerManager), so there is no real backgrounding path yet —
217
+ // every successful start here is a foreground server and must block
218
+ // until told to stop, matching `daemon start --foreground`'s
219
+ // established pattern in daemon.ts.
220
+ output.writeln();
221
+ output.writeln(output.dim('Press Ctrl+C to stop the server'));
222
+ let stopping = false;
223
+ const shutdown = async () => {
224
+ if (stopping)
225
+ return;
226
+ stopping = true;
227
+ try {
228
+ await manager.stop();
229
+ }
230
+ catch { /* best-effort */ }
231
+ process.exit(0);
232
+ };
233
+ process.on('SIGINT', () => { void shutdown(); });
234
+ process.on('SIGTERM', () => { void shutdown(); });
235
+ // Ref'd handle so Node's event loop can't drain to empty even if some
236
+ // transport internals unref their own timers — same belt-and-suspenders
237
+ // as daemon.ts's foreground path (#1478).
238
+ setInterval(() => { }, 60_000);
239
+ await new Promise(() => { }); // Never resolves — server runs until killed.
240
+ return { success: true, data: status }; // unreachable, keeps the return type honest
205
241
  }
206
242
  catch (error) {
207
243
  output.printError(`Failed to start MCP server: ${error.message}`);
@@ -105,7 +105,11 @@ export function detectRemovedAgentGaps(cwd, removedAgents, options = {}) {
105
105
  gaps.push({
106
106
  agent: basename,
107
107
  plugin,
108
- installCommand: `ruflo plugins install ${plugin}`,
108
+ // #2985: these are Claude Code *marketplace* plugins. `ruflo plugins
109
+ // install` targets a different system (the npm-package PluginManager —
110
+ // see the module header) and cannot install them; the marketplace
111
+ // command below is the one that satisfies the registry check above.
112
+ installCommand: `/plugin install ${plugin}@ruflo`,
109
113
  });
110
114
  }
111
115
  return gaps;
@@ -0,0 +1,30 @@
1
+ import type { RemovedAgentGap } from './migrate-agent-detection.js';
2
+ export type RestoreSource = 'marketplace-clone' | 'repo-checkout' | 'github-tag' | 'github-main';
3
+ export interface RestoreResult {
4
+ agent: string;
5
+ plugin: string;
6
+ status: 'restored' | 'skipped' | 'failed';
7
+ /** Where the content came from (set when status === 'restored'). */
8
+ source?: RestoreSource;
9
+ /** Project-relative path written (set when status === 'restored' | 'skipped'). */
10
+ path?: string;
11
+ /** Human-readable failure reason (set when status === 'failed'). */
12
+ error?: string;
13
+ }
14
+ export interface RestoreRemovedAgentsOptions {
15
+ /** Test injection: overrides os.homedir() for the marketplace-clone probe. */
16
+ homeDir?: string;
17
+ /** Test injection: overrides global fetch for the GitHub fallback. */
18
+ fetchImpl?: typeof fetch;
19
+ /** CLI version used to pin the GitHub tag (e.g. "3.38.1" -> tag v3.38.1). */
20
+ version?: string;
21
+ /** Report what would be written without writing. */
22
+ dryRun?: boolean;
23
+ }
24
+ /**
25
+ * Restore each gap's agent file into <cwd>/.claude/agents/<category>/.
26
+ * Never overwrites an existing file (detection already excludes present
27
+ * basenames anywhere under .claude/agents/, but re-check defensively).
28
+ */
29
+ export declare function restoreRemovedAgents(cwd: string, gaps: RemovedAgentGap[], options?: RestoreRemovedAgentsOptions): Promise<RestoreResult[]>;
30
+ //# sourceMappingURL=migrate-agent-restore.d.ts.map
@@ -0,0 +1,201 @@
1
+ /**
2
+ * ADR-382 Part B follow-up (#2985) — `ruflo migrate fix --agents`: actually
3
+ * restore the agents ADR-128 Phase 2 removed from the init template, instead
4
+ * of only pointing at them.
5
+ *
6
+ * Content resolution order (first hit wins):
7
+ * 1. The Claude Code marketplace clone, when present —
8
+ * ~/.claude/plugins/marketplaces/ruflo/plugins/<plugin>/agents/<basename>
9
+ * 2. A repo checkout at the project root (dogfood / development) —
10
+ * <cwd>/plugins/<plugin>/agents/<basename>
11
+ * 3. GitHub raw, pinned to this CLI's own version tag (v<version>), falling
12
+ * back to `main` when the tag predates the file. Requires network; the
13
+ * failure message says so explicitly.
14
+ *
15
+ * npm-track consideration: plugin agent bodies reference the
16
+ * `mcp__plugin_ruflo-core_ruflo__*` tool namespace, which only resolves when
17
+ * ruflo-core is installed as a Claude Code marketplace plugin. A user
18
+ * reaching for this command is on the npm track (otherwise the owning plugin
19
+ * would cover the gap and no restore would be offered), so restored copies
20
+ * are rewritten to the canonical `claude-flow` server key (#2206). The
21
+ * rewrite is annotated in a provenance comment inside the restored file.
22
+ *
23
+ * Kept in a sibling module (not migrate.ts) for the same reason as
24
+ * migrate-agent-detection.ts — migrate.ts is over the 500-line budget.
25
+ */
26
+ import * as fs from 'fs';
27
+ import * as path from 'path';
28
+ import * as os from 'os';
29
+ import { fileURLToPath } from 'node:url';
30
+ // Same local-reader pattern as mcp-tools/system-tools.ts — avoids importing
31
+ // from ../index.js (cycle risk) just for a version string.
32
+ function getPackageVersion() {
33
+ try {
34
+ const dir = path.dirname(fileURLToPath(import.meta.url));
35
+ for (const depth of ['../..', '../../..']) {
36
+ const pkgPath = path.join(dir, depth, 'package.json');
37
+ if (fs.existsSync(pkgPath)) {
38
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
39
+ if (pkg.name?.includes('claude-flow') || pkg.name === 'ruflo') {
40
+ return pkg.version;
41
+ }
42
+ }
43
+ }
44
+ return undefined;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
50
+ /** Marketplace plugin namespace prefix — resolves only under a marketplace install. */
51
+ const PLUGIN_NAMESPACE_PREFIX = 'mcp__plugin_ruflo-core_ruflo__';
52
+ /** Canonical npm-track namespace for the same server (key `claude-flow`, #2206). */
53
+ const CANONICAL_NAMESPACE_PREFIX = 'mcp__claude-flow__';
54
+ /**
55
+ * Where each removed agent lived in the init template before ADR-128 Phase 2
56
+ * deleted it (see the ADR's Gap 2 table). Restoring to the original category
57
+ * keeps category-based tooling (e.g. `--agents=<cat>` style filters) working.
58
+ */
59
+ const RESTORE_CATEGORY = {
60
+ 'coder.md': 'core',
61
+ 'researcher.md': 'core',
62
+ 'reviewer.md': 'core',
63
+ 'tester.md': 'core',
64
+ 'memory-specialist.md': 'v3',
65
+ 'security-auditor.md': 'v3',
66
+ 'sparc-orchestrator.md': 'v3',
67
+ 'adr-architect.md': 'v3',
68
+ 'goal-planner.md': 'goal',
69
+ };
70
+ const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/ruvnet/ruflo';
71
+ function findFileRecursive(dir, basename, depth) {
72
+ if (depth > 4)
73
+ return null;
74
+ let entries;
75
+ try {
76
+ entries = fs.readdirSync(dir, { withFileTypes: true });
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ for (const entry of entries) {
82
+ const full = path.join(dir, entry.name);
83
+ if (entry.isDirectory()) {
84
+ const hit = findFileRecursive(full, basename, depth + 1);
85
+ if (hit)
86
+ return hit;
87
+ }
88
+ else if (entry.isFile() && entry.name === basename) {
89
+ return full;
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ function readLocalCandidate(root, plugin, basename) {
95
+ const agentsDir = path.join(root, 'plugins', plugin, 'agents');
96
+ const hit = findFileRecursive(agentsDir, basename, 0);
97
+ if (!hit)
98
+ return null;
99
+ try {
100
+ return fs.readFileSync(hit, 'utf-8');
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ }
106
+ async function fetchGithubCandidate(fetchImpl, ref, plugin, basename) {
107
+ const url = `${GITHUB_RAW_BASE}/${ref}/plugins/${plugin}/agents/${basename}`;
108
+ try {
109
+ const res = await fetchImpl(url);
110
+ if (!res.ok)
111
+ return null;
112
+ return await res.text();
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ /**
119
+ * Rewrite the marketplace-plugin tool namespace to the canonical npm-track
120
+ * one and record provenance right after the frontmatter block, so a reader
121
+ * (or a future dedup pass) can tell a restored copy from a hand-authored
122
+ * agent. Inserting after the closing `---` keeps frontmatter parseable.
123
+ */
124
+ function adaptForNpmTrack(content, plugin, source) {
125
+ const rewritten = content.split(PLUGIN_NAMESPACE_PREFIX).join(CANONICAL_NAMESPACE_PREFIX);
126
+ const provenance = `<!-- restored by \`ruflo migrate fix --agents\` from plugins/${plugin} ` +
127
+ `(canonical per ADR-128; source: ${source}); MCP namespace rewritten to the ` +
128
+ `canonical claude-flow server key (#2206, #2985) -->`;
129
+ const fmClose = rewritten.indexOf('\n---', rewritten.startsWith('---') ? 3 : 0);
130
+ if (rewritten.startsWith('---') && fmClose !== -1) {
131
+ const insertAt = rewritten.indexOf('\n', fmClose + 1);
132
+ if (insertAt !== -1) {
133
+ return rewritten.slice(0, insertAt + 1) + provenance + '\n' + rewritten.slice(insertAt + 1);
134
+ }
135
+ }
136
+ return provenance + '\n' + rewritten;
137
+ }
138
+ async function resolveContent(cwd, homeDir, fetchImpl, version, plugin, basename) {
139
+ const marketplaceRoot = path.join(homeDir, '.claude', 'plugins', 'marketplaces', 'ruflo');
140
+ const fromMarketplace = readLocalCandidate(marketplaceRoot, plugin, basename);
141
+ if (fromMarketplace !== null)
142
+ return { content: fromMarketplace, source: 'marketplace-clone' };
143
+ const fromCheckout = readLocalCandidate(cwd, plugin, basename);
144
+ if (fromCheckout !== null)
145
+ return { content: fromCheckout, source: 'repo-checkout' };
146
+ if (version) {
147
+ const fromTag = await fetchGithubCandidate(fetchImpl, `v${version}`, plugin, basename);
148
+ if (fromTag !== null)
149
+ return { content: fromTag, source: 'github-tag' };
150
+ }
151
+ const fromMain = await fetchGithubCandidate(fetchImpl, 'main', plugin, basename);
152
+ if (fromMain !== null)
153
+ return { content: fromMain, source: 'github-main' };
154
+ return null;
155
+ }
156
+ /**
157
+ * Restore each gap's agent file into <cwd>/.claude/agents/<category>/.
158
+ * Never overwrites an existing file (detection already excludes present
159
+ * basenames anywhere under .claude/agents/, but re-check defensively).
160
+ */
161
+ export async function restoreRemovedAgents(cwd, gaps, options = {}) {
162
+ const homeDir = options.homeDir ?? os.homedir();
163
+ const fetchImpl = options.fetchImpl ?? fetch;
164
+ const version = options.version ?? getPackageVersion();
165
+ const results = [];
166
+ for (const gap of gaps) {
167
+ const category = RESTORE_CATEGORY[gap.agent] ?? 'core';
168
+ const relPath = path.join('.claude', 'agents', category, gap.agent);
169
+ const destPath = path.join(cwd, relPath);
170
+ if (fs.existsSync(destPath)) {
171
+ results.push({ agent: gap.agent, plugin: gap.plugin, status: 'skipped', path: relPath });
172
+ continue;
173
+ }
174
+ const resolved = await resolveContent(cwd, homeDir, fetchImpl, version, gap.plugin, gap.agent);
175
+ if (!resolved) {
176
+ results.push({
177
+ agent: gap.agent,
178
+ plugin: gap.plugin,
179
+ status: 'failed',
180
+ error: 'no local marketplace clone or repo checkout, and GitHub fetch failed ' +
181
+ '(offline?) — install the owning plugin instead: ' +
182
+ `/plugin install ${gap.plugin}@ruflo`,
183
+ });
184
+ continue;
185
+ }
186
+ const adapted = adaptForNpmTrack(resolved.content, gap.plugin, resolved.source);
187
+ if (!options.dryRun) {
188
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
189
+ fs.writeFileSync(destPath, adapted, 'utf-8');
190
+ }
191
+ results.push({
192
+ agent: gap.agent,
193
+ plugin: gap.plugin,
194
+ status: 'restored',
195
+ source: resolved.source,
196
+ path: relPath,
197
+ });
198
+ }
199
+ return results;
200
+ }
201
+ //# sourceMappingURL=migrate-agent-restore.js.map
@@ -6,6 +6,7 @@ import { output } from '../output.js';
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import { detectRemovedAgentGaps } from './migrate-agent-detection.js';
9
+ import { restoreRemovedAgents } from './migrate-agent-restore.js';
9
10
  // Migration targets
10
11
  const MIGRATION_TARGETS = [
11
12
  { value: 'config', label: 'Configuration', hint: 'Migrate configuration files' },
@@ -167,6 +168,7 @@ const statusCommand = {
167
168
  });
168
169
  output.writeln();
169
170
  output.printInfo(`${removedAgentGaps.length} agent(s) removed by ADR-128 are missing from .claude/agents/ and not covered by an installed plugin.`);
171
+ output.printInfo('Run "ruflo migrate fix --agents" to restore them from their canonical plugin content, or install the owning plugin.');
170
172
  }
171
173
  return { success: true, data: { components, needsMigration, removedAgentGaps } };
172
174
  }
@@ -713,16 +715,81 @@ const breakingCommand = {
713
715
  return { success: true, data: changes };
714
716
  }
715
717
  };
718
+ // Fix command — apply remediations that `status` can only point at (#2985).
719
+ // Logic lives in migrate-agent-restore.ts (migrate.ts is over the 500-line
720
+ // budget; same split as migrate-agent-detection.ts).
721
+ const fixCommand = {
722
+ name: 'fix',
723
+ description: 'Apply fixes for gaps detected by "migrate status"',
724
+ options: [
725
+ {
726
+ name: 'agents',
727
+ description: 'Restore agents removed by ADR-128 from their canonical plugin content',
728
+ type: 'boolean',
729
+ default: false
730
+ },
731
+ {
732
+ name: 'dry-run',
733
+ short: 'd',
734
+ description: 'Show what would be restored without writing',
735
+ type: 'boolean',
736
+ default: false
737
+ }
738
+ ],
739
+ action: async (ctx) => {
740
+ const cwd = ctx.cwd || process.cwd();
741
+ if (!ctx.flags.agents) {
742
+ output.printInfo('Nothing selected. Pass --agents to restore ADR-128-removed agents.');
743
+ return { success: true };
744
+ }
745
+ const gaps = detectRemovedAgentGaps(cwd, REMOVED_AGENTS);
746
+ if (gaps.length === 0) {
747
+ output.printSuccess('No removed-agent gaps to fix — every agent is present or covered by an installed plugin.');
748
+ return { success: true, data: { results: [] } };
749
+ }
750
+ const dryRun = Boolean(ctx.flags['dry-run']);
751
+ const results = await restoreRemovedAgents(cwd, gaps, { dryRun });
752
+ if (ctx.flags.format === 'json') {
753
+ output.printJson({ dryRun, results });
754
+ return { success: results.every(r => r.status !== 'failed'), data: { dryRun, results } };
755
+ }
756
+ output.writeln();
757
+ output.writeln(output.bold(dryRun ? 'Restore Plan (dry run)' : 'Restored Agents'));
758
+ output.printTable({
759
+ columns: [
760
+ { key: 'agent', header: 'Agent', width: 24 },
761
+ { key: 'status', header: 'Status', width: 10 },
762
+ { key: 'detail', header: dryRun ? 'Would write' : 'Detail', width: 44 }
763
+ ],
764
+ data: results.map(r => ({
765
+ agent: r.agent,
766
+ status: r.status === 'failed' ? output.warning(r.status) : r.status,
767
+ detail: r.status === 'failed' ? output.dim(r.error ?? '') : output.dim(`${r.path ?? ''}${r.source ? ` (from ${r.source})` : ''}`)
768
+ })),
769
+ border: false
770
+ });
771
+ const failed = results.filter(r => r.status === 'failed');
772
+ output.writeln();
773
+ if (failed.length > 0) {
774
+ output.printWarning(`${failed.length} agent(s) could not be restored — install the owning plugin instead (see table).`);
775
+ }
776
+ else if (!dryRun) {
777
+ output.printSuccess(`${results.filter(r => r.status === 'restored').length} agent(s) restored. Re-run "ruflo migrate status" to verify.`);
778
+ }
779
+ return { success: failed.length === 0, data: { dryRun, results } };
780
+ }
781
+ };
716
782
  // Main migrate command
717
783
  export const migrateCommand = {
718
784
  name: 'migrate',
719
785
  description: 'V2 to V3 migration tools',
720
- subcommands: [statusCommand, runCommand, verifyCommand, rollbackCommand, breakingCommand],
786
+ subcommands: [statusCommand, runCommand, verifyCommand, rollbackCommand, breakingCommand, fixCommand],
721
787
  options: [],
722
788
  examples: [
723
789
  { command: 'claude-flow migrate status', description: 'Check migration status' },
724
790
  { command: 'claude-flow migrate run --dry-run', description: 'Preview migration' },
725
- { command: 'claude-flow migrate run -t all', description: 'Run full migration' }
791
+ { command: 'claude-flow migrate run -t all', description: 'Run full migration' },
792
+ { command: 'claude-flow migrate fix --agents', description: 'Restore ADR-128-removed agents' }
726
793
  ],
727
794
  action: async (ctx) => {
728
795
  output.writeln();
@@ -736,7 +803,8 @@ export const migrateCommand = {
736
803
  `${output.highlight('run')} - Run migration`,
737
804
  `${output.highlight('verify')} - Verify migration integrity`,
738
805
  `${output.highlight('rollback')} - Rollback to previous version`,
739
- `${output.highlight('breaking')} - Show breaking changes`
806
+ `${output.highlight('breaking')} - Show breaking changes`,
807
+ `${output.highlight('fix')} - Apply fixes for detected gaps (--agents)`
740
808
  ]);
741
809
  return { success: true };
742
810
  }
@@ -962,7 +962,9 @@ async function writeMCPConfig(targetDir, options, result) {
962
962
  // #1779/#2612 — Skip writing if the user already has this MCP server
963
963
  // registered elsewhere (parent .mcp.json, ~/.claude.json, etc). The
964
964
  // canonical key is `claude-flow` (per #2206 — matches mcp__claude-flow__*
965
- // plugin tool refs); a stray `ruflo`-keyed entry pointing at the same
965
+ // tool refs used throughout the CLI-track scaffold; NOT the marketplace
966
+ // plugin binding, which is mcp__plugin_<plugin>_<server>__* per plugin —
967
+ // see #2985); a stray `ruflo`-keyed entry pointing at the same
966
968
  // binary is the legacy-duplicate form that #2612 healed. Writing our
967
969
  // fresh `claude-flow` entry on top of either variant starts the same
968
970
  // binary twice under two tool namespaces. Force-mode (`--force`)
@@ -2360,7 +2360,39 @@ export const hooksIntelligence = {
2360
2360
  const flashAvailable = (await getFlashAttention()) !== null;
2361
2361
  const ewcAvailable = (await getEWCConsolidator()) !== null;
2362
2362
  const loraAvailable = (await getLoRAAdapter()) !== null;
2363
+ // #2940: `forceTraining` (CLI `hooks intelligence --train`) was declared
2364
+ // on this tool's input schema but never read — the handler always
2365
+ // returned the same read-only status dashboard, so `lastAdaptation`
2366
+ // (what `--status` reports as "Last Training") could never move no
2367
+ // matter how many times `--train` ran. Actually distill when asked,
2368
+ // and report honestly rather than always implying success:
2369
+ // `attempted: false` (flag not passed), `trained: true` (real work
2370
+ // happened, lastAdaptation now current), or `trained: false` with a
2371
+ // reason (nothing new to distill, or distillation unavailable).
2372
+ const training = { attempted: false, trained: false };
2373
+ if (params.forceTraining) {
2374
+ training.attempted = true;
2375
+ try {
2376
+ const { distillLearning } = await import('../memory/intelligence.js');
2377
+ const result = await distillLearning();
2378
+ if (result) {
2379
+ training.trained = true;
2380
+ training.patternsDistilled = result.patternsDistilled;
2381
+ training.ewcPenalty = result.ewcPenalty;
2382
+ if (result.patternsDistilled === 0) {
2383
+ training.reason = 'no new trajectories to distill since the last training cycle';
2384
+ }
2385
+ }
2386
+ else {
2387
+ training.reason = 'intelligence system unavailable — could not initialize SONA/ReasoningBank';
2388
+ }
2389
+ }
2390
+ catch (error) {
2391
+ training.reason = `distillation failed: ${error instanceof Error ? error.message : String(error)}`;
2392
+ }
2393
+ }
2363
2394
  return {
2395
+ training,
2364
2396
  mode,
2365
2397
  status: 'active',
2366
2398
  components: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.38.1",
3
+ "version": "3.38.3",
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",