@claude-flow/cli 3.38.0 → 3.38.2
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/.claude/helpers/helpers.manifest.json +2 -2
- package/README.md +4 -4
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/mcp.js +37 -1
- package/dist/src/commands/memory.js +9 -1
- package/dist/src/commands/migrate-agent-detection.js +5 -1
- package/dist/src/commands/migrate-agent-restore.d.ts +30 -0
- package/dist/src/commands/migrate-agent-restore.js +201 -0
- package/dist/src/commands/migrate.js +71 -3
- package/dist/src/init/executor.js +3 -1
- package/dist/src/memory/memory-bridge.d.ts +7 -0
- package/dist/src/memory/memory-bridge.js +28 -1
- package/dist/src/memory/memory-initializer.d.ts +3 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.38.
|
|
3
|
+
"version": "3.38.2",
|
|
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": "
|
|
11
|
+
"signature": "aUyZD8VBc8slPjWIexWvx5vD6cF9ms5YKEDvLl+6WSX8Y0hrtXQgSs42Bmetyqj0LTRW5+W9GF3kUNB6zGiDBw==",
|
|
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 |
|
|
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
|
|
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
|
|
191
|
-
claude mcp add
|
|
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
|
---
|
package/catalog-manifest.json
CHANGED
package/dist/src/commands/mcp.js
CHANGED
|
@@ -201,7 +201,43 @@ const startCommand = {
|
|
|
201
201
|
if (daemon) {
|
|
202
202
|
output.writeln(output.dim(' Running in background mode'));
|
|
203
203
|
}
|
|
204
|
-
|
|
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}`);
|
|
@@ -214,7 +214,15 @@ const storeCommand = {
|
|
|
214
214
|
]
|
|
215
215
|
});
|
|
216
216
|
output.writeln();
|
|
217
|
-
|
|
217
|
+
if (result.persistWarning) {
|
|
218
|
+
// #2968: the write ran, but the checkpoint signal indicates it may
|
|
219
|
+
// not have reached disk (sql.js fallback driver) — do not print an
|
|
220
|
+
// unconditional green success for a write that might vanish.
|
|
221
|
+
output.printWarning(`Data stored, but persistence is not guaranteed: ${result.persistWarning}`);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
output.printSuccess('Data stored successfully');
|
|
225
|
+
}
|
|
218
226
|
return { success: true, data: { ...storeData, id: result.id, embedding: result.embedding, provenanceType: provenance || 'unknown' } };
|
|
219
227
|
}
|
|
220
228
|
catch (error) {
|
|
@@ -105,7 +105,11 @@ export function detectRemovedAgentGaps(cwd, removedAgents, options = {}) {
|
|
|
105
105
|
gaps.push({
|
|
106
106
|
agent: basename,
|
|
107
107
|
plugin,
|
|
108
|
-
|
|
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
|
-
//
|
|
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`)
|
|
@@ -67,6 +67,13 @@ export declare function bridgeStoreEntry(options: {
|
|
|
67
67
|
cached?: boolean;
|
|
68
68
|
attested?: boolean;
|
|
69
69
|
error?: string;
|
|
70
|
+
/** #2968: set when the post-write checkpoint failed in a way that
|
|
71
|
+
* indicates this connection may not durably persist writes at all
|
|
72
|
+
* (the sql.js fallback driver, engaged when better-sqlite3's native
|
|
73
|
+
* binding is missing). The write above still ran and `success` is
|
|
74
|
+
* still true — this is advisory, not a failure — but callers should
|
|
75
|
+
* surface it instead of only printing an unconditional success message. */
|
|
76
|
+
persistWarning?: string;
|
|
70
77
|
} | null>;
|
|
71
78
|
/**
|
|
72
79
|
* Search entries via AgentDB v3.
|
|
@@ -897,12 +897,27 @@ export async function bridgeStoreEntry(options) {
|
|
|
897
897
|
// `sqlite3` vector count) then see a stale/empty main DB file and report
|
|
898
898
|
// "0 vectors" / empty search. A PASSIVE checkpoint flushes committed pages
|
|
899
899
|
// into the main file without blocking writers. Best-effort, never fatal.
|
|
900
|
+
let persistWarning;
|
|
900
901
|
try {
|
|
901
902
|
if (typeof ctx.db.pragma === 'function') {
|
|
902
903
|
ctx.db.pragma('wal_checkpoint(PASSIVE)');
|
|
903
904
|
}
|
|
904
905
|
}
|
|
905
|
-
catch {
|
|
906
|
+
catch (checkpointErr) {
|
|
907
|
+
// #2968: on the sql.js fallback driver (engaged when better-sqlite3's
|
|
908
|
+
// native binding never got built — e.g. a skipped optionalDependency
|
|
909
|
+
// postinstall), this PRAGMA isn't implemented and throws "Invalid
|
|
910
|
+
// PRAGMA command". sql.js has no WAL journal to checkpoint at all, so
|
|
911
|
+
// this specific failure is the strongest signal available here that
|
|
912
|
+
// the insert above may exist only in this process's in-memory image
|
|
913
|
+
// and never reach disk — not the ordinary "non-WAL, busy, or
|
|
914
|
+
// unsupported" case the old blanket catch assumed. Surface it rather
|
|
915
|
+
// than swallow it silently; other pragma failures stay non-fatal.
|
|
916
|
+
const msg = checkpointErr instanceof Error ? checkpointErr.message : String(checkpointErr);
|
|
917
|
+
if (/Invalid PRAGMA/i.test(msg)) {
|
|
918
|
+
persistWarning = `sql.js fallback driver in use — this write may not be durably persisted to disk (${msg}). Install better-sqlite3's native binding to restore durable writes (see issue #2968: npm rebuild better-sqlite3, or reinstall with install scripts enabled).`;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
906
921
|
// Phase 2: Write-through to TieredCache
|
|
907
922
|
const safeNs = String(namespace).replace(/:/g, '_');
|
|
908
923
|
const safeKey = String(key).replace(/:/g, '_');
|
|
@@ -918,6 +933,7 @@ export async function bridgeStoreEntry(options) {
|
|
|
918
933
|
guarded: true,
|
|
919
934
|
cached: true,
|
|
920
935
|
attested: true,
|
|
936
|
+
...(persistWarning ? { persistWarning } : {}),
|
|
921
937
|
};
|
|
922
938
|
}
|
|
923
939
|
catch (err) {
|
|
@@ -995,10 +1011,17 @@ export async function bridgeSearchEntries(options) {
|
|
|
995
1011
|
const whereExtra = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
|
|
996
1012
|
let rows;
|
|
997
1013
|
try {
|
|
1014
|
+
// #2982/#2976: this pre-rank SELECT truncates the corpus to 1000 rows
|
|
1015
|
+
// before BM25/embedding scoring runs. Without ORDER BY, SQLite returns
|
|
1016
|
+
// rows in arbitrary storage order (effectively oldest-inserted-first at
|
|
1017
|
+
// scale), so on any corpus over 1000 entries the newest — and often
|
|
1018
|
+
// most relevant — rows never reach scoring at all. bridgeListEntries()
|
|
1019
|
+
// already orders by updated_at DESC for the same reason; mirror it here.
|
|
998
1020
|
const stmt = ctx.db.prepare(`
|
|
999
1021
|
SELECT id, key, namespace, content, embedding, provenance_type
|
|
1000
1022
|
FROM memory_entries
|
|
1001
1023
|
WHERE ${ACTIVE_MEMORY_ROW_SQL} ${whereExtra}
|
|
1024
|
+
ORDER BY updated_at DESC
|
|
1002
1025
|
LIMIT 1000
|
|
1003
1026
|
`);
|
|
1004
1027
|
rows = filterParams.length > 0 ? stmt.all(...filterParams) : stmt.all();
|
|
@@ -1506,10 +1529,14 @@ export async function bridgeSearchHNSW(queryEmbedding, options, dbPath) {
|
|
|
1506
1529
|
: '';
|
|
1507
1530
|
let rows;
|
|
1508
1531
|
try {
|
|
1532
|
+
// #2982/#2976: same unordered pre-rank truncation as bridgeSearchEntries
|
|
1533
|
+
// above — without ORDER BY, a corpus over 10000 rows silently drops
|
|
1534
|
+
// its newest entries before cosine scoring ever runs.
|
|
1509
1535
|
const stmt = ctx.db.prepare(`
|
|
1510
1536
|
SELECT id, key, namespace, content, embedding
|
|
1511
1537
|
FROM memory_entries
|
|
1512
1538
|
WHERE status = 'active' AND embedding IS NOT NULL ${nsFilter}
|
|
1539
|
+
ORDER BY updated_at DESC
|
|
1513
1540
|
LIMIT 10000
|
|
1514
1541
|
`);
|
|
1515
1542
|
rows = nsFilter
|
|
@@ -448,6 +448,9 @@ export declare function storeEntry(options: {
|
|
|
448
448
|
model: string;
|
|
449
449
|
};
|
|
450
450
|
error?: string;
|
|
451
|
+
/** #2968: set when the bridge's checkpoint failed in a way indicating
|
|
452
|
+
* this write may not be durably persisted (sql.js fallback driver). */
|
|
453
|
+
persistWarning?: string;
|
|
451
454
|
}>;
|
|
452
455
|
/**
|
|
453
456
|
* Search entries using sql.js with vector similarity
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.38.
|
|
3
|
+
"version": "3.38.2",
|
|
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",
|