claude-flow 3.10.0 → 3.10.1
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-plugin/hooks/hooks.json +2 -0
- package/.claude-plugin/scripts/ruflo-hook.cjs +166 -0
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +22 -1
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +71 -0
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "#1921 — hook commands invoke scripts/ruflo-hook.sh (resilient shim): prefers a locally-installed `ruflo`/`claude-flow` binary, falls back to `npx --prefer-offline`, and always exits 0 so a CLI/install failure (e.g. arborist `Invalid Version` on npm 10.8.x) never surfaces an error in Claude Code or blocks a turn. The trailing `|| true` guards the case where $CLAUDE_PLUGIN_ROOT is unset (older Claude Code) — the hook then no-ops silently. DO NOT revert to a bare `npx <pkg>@alpha hooks …` per fire.",
|
|
3
|
+
"_platform": "posix",
|
|
4
|
+
"_platform_note": "#2132 — This hooks.json uses /bin/bash, POSIX pipelines (jq, xargs, tr), and .sh scripts. It is intentionally POSIX-only (Mac/Linux). On Windows, ruflo init writes a .claude/settings.json that overrides these entries with node-based equivalents via plugins/ruflo-core/scripts/ruflo-hook.cjs. The audit exempts files with _platform:posix from the cross-platform check.",
|
|
3
5
|
"hooks": {
|
|
4
6
|
"PreToolUse": [
|
|
5
7
|
{
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ruflo-hook.cjs — cross-platform Node.js port of ruflo-hook.sh (#2132)
|
|
4
|
+
*
|
|
5
|
+
* The bash shim (ruflo-hook.sh) works on Mac/Linux but fails on native
|
|
6
|
+
* Windows (exit 126 — "cannot execute binary file"). This .cjs shim
|
|
7
|
+
* provides identical behaviour via Node.js child_process so Windows users
|
|
8
|
+
* get working hooks without WSL or Git Bash.
|
|
9
|
+
*
|
|
10
|
+
* Mac/Linux continue to use ruflo-hook.sh via the plugin hooks.json files
|
|
11
|
+
* (unchanged). On Windows, ruflo init writes a .claude/settings.json that
|
|
12
|
+
* overrides those entries with node-based equivalents pointing here.
|
|
13
|
+
*
|
|
14
|
+
* Behaviour mirrors ruflo-hook.sh:
|
|
15
|
+
* 1. Reads hook JSON payload from stdin.
|
|
16
|
+
* 2. Prefers a locally installed `ruflo` or `claude-flow` binary.
|
|
17
|
+
* 3. Falls back to `npx --prefer-offline ruflo@latest`.
|
|
18
|
+
* 4. Always exits 0 — hook subcommands are best-effort telemetry.
|
|
19
|
+
* 5. Swallows all stderr — nothing should surface to Claude Code.
|
|
20
|
+
*
|
|
21
|
+
* Usage: node ruflo-hook.cjs <hook-subcommand> [args...]
|
|
22
|
+
* e.g. node ruflo-hook.cjs post-edit --file "x.ts" --train-patterns
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
const { spawnSync, execSync } = require('child_process');
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
|
|
31
|
+
/** Exit 0 unconditionally — hooks must never block a turn */
|
|
32
|
+
function done() {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Resolve stdin to a JSON object, or null if not parseable */
|
|
37
|
+
function readStdinJson() {
|
|
38
|
+
try {
|
|
39
|
+
let buf = '';
|
|
40
|
+
// Read synchronously — hooks fire synchronously in Claude Code
|
|
41
|
+
const fd = fs.openSync('/dev/stdin', 'r');
|
|
42
|
+
const chunk = Buffer.alloc(64 * 1024);
|
|
43
|
+
let bytesRead;
|
|
44
|
+
while ((bytesRead = fs.readSync(fd, chunk, 0, chunk.length, null)) > 0) {
|
|
45
|
+
buf += chunk.slice(0, bytesRead).toString('utf8');
|
|
46
|
+
}
|
|
47
|
+
fs.closeSync(fd);
|
|
48
|
+
return buf.trim() ? JSON.parse(buf) : null;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Read stdin via process.stdin in sync mode (Windows-safe alternative) */
|
|
55
|
+
function readStdinSync() {
|
|
56
|
+
try {
|
|
57
|
+
// On Windows /dev/stdin doesn't exist; use fd 0 directly
|
|
58
|
+
const chunk = Buffer.alloc(64 * 1024);
|
|
59
|
+
let buf = '';
|
|
60
|
+
let bytesRead;
|
|
61
|
+
while (true) {
|
|
62
|
+
try {
|
|
63
|
+
bytesRead = fs.readSync(0 /* STDIN_FILENO */, chunk, 0, chunk.length, null);
|
|
64
|
+
if (bytesRead === 0) break;
|
|
65
|
+
buf += chunk.slice(0, bytesRead).toString('utf8');
|
|
66
|
+
} catch {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return buf.trim() ? JSON.parse(buf) : null;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Check if a binary is available on PATH */
|
|
77
|
+
function commandExists(cmd) {
|
|
78
|
+
try {
|
|
79
|
+
const result = execSync(
|
|
80
|
+
process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`,
|
|
81
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
82
|
+
);
|
|
83
|
+
return result.trim().length > 0;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Build the argv for the ruflo/claude-flow/npx invocation */
|
|
90
|
+
function buildArgs(subcommand, extraArgs) {
|
|
91
|
+
// The `hooks` word is prepended here, matching ruflo-hook.sh convention.
|
|
92
|
+
return ['hooks', subcommand, ...extraArgs];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Spawn the CLI with the hook subcommand.
|
|
97
|
+
* Passes the raw stdin payload as the child's stdin so the CLI can read
|
|
98
|
+
* the hook event JSON if needed (same as the bash pipe).
|
|
99
|
+
*
|
|
100
|
+
* Returns true on success (exit 0), false otherwise.
|
|
101
|
+
*/
|
|
102
|
+
function invokeHook(bin, binArgs, hookArgs, stdinData) {
|
|
103
|
+
const args = [...binArgs, ...hookArgs];
|
|
104
|
+
|
|
105
|
+
// On Windows, shell: true is needed to resolve .cmd shims in node_modules
|
|
106
|
+
const useShell = process.platform === 'win32';
|
|
107
|
+
|
|
108
|
+
const result = spawnSync(bin, args, {
|
|
109
|
+
shell: useShell,
|
|
110
|
+
input: stdinData || '',
|
|
111
|
+
encoding: 'utf8',
|
|
112
|
+
stdio: ['pipe', 'ignore', 'ignore'], // swallow all output
|
|
113
|
+
timeout: 30_000,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return result.status === 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function main() {
|
|
120
|
+
const args = process.argv.slice(2);
|
|
121
|
+
if (args.length === 0) {
|
|
122
|
+
// No subcommand — no-op, same as bash version
|
|
123
|
+
done();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const [subcommand, ...rest] = args;
|
|
127
|
+
|
|
128
|
+
// Read stdin (the hook event payload) — best effort
|
|
129
|
+
let stdinData = '';
|
|
130
|
+
try {
|
|
131
|
+
stdinData = fs.readFileSync(0 /* fd 0 = stdin */, 'utf8');
|
|
132
|
+
} catch {
|
|
133
|
+
// stdin may not be available when invoked directly for testing
|
|
134
|
+
stdinData = '';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const hookArgs = buildArgs(subcommand, rest);
|
|
138
|
+
|
|
139
|
+
// Priority 1: locally installed ruflo binary
|
|
140
|
+
if (commandExists('ruflo')) {
|
|
141
|
+
invokeHook('ruflo', [], hookArgs, stdinData);
|
|
142
|
+
done();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Priority 2: locally installed claude-flow binary
|
|
146
|
+
if (commandExists('claude-flow')) {
|
|
147
|
+
invokeHook('claude-flow', [], hookArgs, stdinData);
|
|
148
|
+
done();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Priority 3: npx --prefer-offline fallback (avoids cold registry resolve).
|
|
152
|
+
//
|
|
153
|
+
// SKIP this when RUFLO_HOOK_SKIP_NPX=1 — used by CI smokes that test
|
|
154
|
+
// the shim's *control flow* without exercising npm install network paths.
|
|
155
|
+
// Without the skip, npx can take 30+s on a cold runner (no warm cache,
|
|
156
|
+
// no offline tarball), exceeding the smoke's 15s timeout and producing
|
|
157
|
+
// a spurious failure even though the shim itself works correctly.
|
|
158
|
+
// The bash version doesn't hit this because it backgrounded the work.
|
|
159
|
+
if (process.env.RUFLO_HOOK_SKIP_NPX !== '1') {
|
|
160
|
+
invokeHook('npx', ['--prefer-offline', '--yes', 'ruflo@latest'], hookArgs, stdinData);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
done();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.1",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,7 @@ import { detectPlatform, DEFAULT_INIT_OPTIONS } from './types.js';
|
|
|
14
14
|
import { generateSettingsJson, generateSettings } from './settings-generator.js';
|
|
15
15
|
import { generateMCPJson } from './mcp-generator.js';
|
|
16
16
|
import { generateStatuslineScript } from './statusline-generator.js';
|
|
17
|
-
import { generatePreCommitHook, generatePostCommitHook, generateSessionManager, generateAgentRouter, generateMemoryHelper, generateHookHandler, generateIntelligenceStub, generateAutoMemoryHook, } from './helpers-generator.js';
|
|
17
|
+
import { generatePreCommitHook, generatePostCommitHook, generateSessionManager, generateAgentRouter, generateMemoryHelper, generateHookHandler, generateIntelligenceStub, generateAutoMemoryHook, generateRufloHookCjs, } from './helpers-generator.js';
|
|
18
18
|
import { generateClaudeMd } from './claudemd-generator.js';
|
|
19
19
|
/**
|
|
20
20
|
* Skills to copy based on configuration
|
|
@@ -1057,6 +1057,11 @@ async function writeHelpers(targetDir, options, result) {
|
|
|
1057
1057
|
const helpersDir = path.join(targetDir, '.claude', 'helpers');
|
|
1058
1058
|
// Find source helpers directory (works for npm package and local dev)
|
|
1059
1059
|
const sourceHelpersDir = findSourceHelpersDir(options.sourceBaseDir);
|
|
1060
|
+
// On Windows: emit a notice before writing helpers — the settings.json
|
|
1061
|
+
// hooks will use node-based commands instead of bash shims (#2132).
|
|
1062
|
+
if (process.platform === 'win32') {
|
|
1063
|
+
console.log('Detected Windows — adding cross-platform hook overrides to .claude/settings.json (#2132)');
|
|
1064
|
+
}
|
|
1060
1065
|
// Try to copy existing helpers from source first
|
|
1061
1066
|
if (sourceHelpersDir && fs.existsSync(sourceHelpersDir)) {
|
|
1062
1067
|
const helperFiles = fs.readdirSync(sourceHelpersDir);
|
|
@@ -1080,6 +1085,18 @@ async function writeHelpers(targetDir, options, result) {
|
|
|
1080
1085
|
result.skipped.push(`.claude/helpers/${file}`);
|
|
1081
1086
|
}
|
|
1082
1087
|
}
|
|
1088
|
+
// #2132: Always generate ruflo-hook.cjs regardless of source copy path.
|
|
1089
|
+
// The source helpers dir may not contain this file (it lives in
|
|
1090
|
+
// plugins/ruflo-core/scripts/, not .claude/helpers/), but it must
|
|
1091
|
+
// always be present so Windows users can use the node-based shim.
|
|
1092
|
+
const rufloHookDest = path.join(helpersDir, 'ruflo-hook.cjs');
|
|
1093
|
+
if (!fs.existsSync(rufloHookDest) || options.force) {
|
|
1094
|
+
fs.writeFileSync(rufloHookDest, generateRufloHookCjs(), 'utf-8');
|
|
1095
|
+
result.created.files.push('.claude/helpers/ruflo-hook.cjs');
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
result.skipped.push('.claude/helpers/ruflo-hook.cjs');
|
|
1099
|
+
}
|
|
1083
1100
|
if (copiedCount > 0) {
|
|
1084
1101
|
return; // Skip generating if we copied from source
|
|
1085
1102
|
}
|
|
@@ -1094,6 +1111,10 @@ async function writeHelpers(targetDir, options, result) {
|
|
|
1094
1111
|
'hook-handler.cjs': generateHookHandler(),
|
|
1095
1112
|
'intelligence.cjs': generateIntelligenceStub(),
|
|
1096
1113
|
'auto-memory-hook.mjs': generateAutoMemoryHook(),
|
|
1114
|
+
// #2132: cross-platform Node.js port of ruflo-hook.sh — always deployed so
|
|
1115
|
+
// Windows users have a working shim even if the plugin's hooks.json bash
|
|
1116
|
+
// commands are overridden via settings.json.
|
|
1117
|
+
'ruflo-hook.cjs': generateRufloHookCjs(),
|
|
1097
1118
|
};
|
|
1098
1119
|
for (const [name, content] of Object.entries(helpers)) {
|
|
1099
1120
|
const filePath = path.join(helpersDir, name);
|
|
@@ -58,4 +58,14 @@ export declare function generateCrossPlatformSessionManager(): string;
|
|
|
58
58
|
* Generate all helper files
|
|
59
59
|
*/
|
|
60
60
|
export declare function generateHelpers(options: InitOptions): Record<string, string>;
|
|
61
|
+
/**
|
|
62
|
+
* Generate cross-platform Node.js port of ruflo-hook.sh (#2132).
|
|
63
|
+
*
|
|
64
|
+
* The bash shim works on Mac/Linux but fails on native Windows (exit 126).
|
|
65
|
+
* This .cjs version is always deployed to .claude/helpers/ so:
|
|
66
|
+
* - Windows: settings.json overrides plugin bash hooks with node-based cmds
|
|
67
|
+
* - Mac/Linux: plugin hooks.json still uses .sh (faster, battle-tested)
|
|
68
|
+
* - Both: .claude/helpers/ruflo-hook.cjs available as a canonical cross-platform shim
|
|
69
|
+
*/
|
|
70
|
+
export declare function generateRufloHookCjs(): string;
|
|
61
71
|
//# sourceMappingURL=helpers-generator.d.ts.map
|
|
@@ -1195,4 +1195,75 @@ export function generateHelpers(options) {
|
|
|
1195
1195
|
}
|
|
1196
1196
|
return helpers;
|
|
1197
1197
|
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Generate cross-platform Node.js port of ruflo-hook.sh (#2132).
|
|
1200
|
+
*
|
|
1201
|
+
* The bash shim works on Mac/Linux but fails on native Windows (exit 126).
|
|
1202
|
+
* This .cjs version is always deployed to .claude/helpers/ so:
|
|
1203
|
+
* - Windows: settings.json overrides plugin bash hooks with node-based cmds
|
|
1204
|
+
* - Mac/Linux: plugin hooks.json still uses .sh (faster, battle-tested)
|
|
1205
|
+
* - Both: .claude/helpers/ruflo-hook.cjs available as a canonical cross-platform shim
|
|
1206
|
+
*/
|
|
1207
|
+
export function generateRufloHookCjs() {
|
|
1208
|
+
return `#!/usr/bin/env node
|
|
1209
|
+
/**
|
|
1210
|
+
* ruflo-hook.cjs — cross-platform Node.js port of ruflo-hook.sh (#2132)
|
|
1211
|
+
*
|
|
1212
|
+
* Deployed to .claude/helpers/ during ruflo init. On Windows, the
|
|
1213
|
+
* generated .claude/settings.json hooks point here instead of the
|
|
1214
|
+
* plugin's bash-only ruflo-hook.sh.
|
|
1215
|
+
*
|
|
1216
|
+
* Always exits 0 — hook subcommands are best-effort telemetry and must
|
|
1217
|
+
* never block a Claude Code turn.
|
|
1218
|
+
*/
|
|
1219
|
+
|
|
1220
|
+
'use strict';
|
|
1221
|
+
|
|
1222
|
+
const { spawnSync, execSync } = require('child_process');
|
|
1223
|
+
const fs = require('fs');
|
|
1224
|
+
|
|
1225
|
+
function done() { process.exit(0); }
|
|
1226
|
+
|
|
1227
|
+
function commandExists(cmd) {
|
|
1228
|
+
try {
|
|
1229
|
+
const r = execSync(
|
|
1230
|
+
process.platform === 'win32' ? 'where ' + cmd : 'command -v ' + cmd,
|
|
1231
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
1232
|
+
);
|
|
1233
|
+
return r.trim().length > 0;
|
|
1234
|
+
} catch { return false; }
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function invokeHook(bin, binArgs, hookArgs, stdinData) {
|
|
1238
|
+
const args = [...binArgs, ...hookArgs];
|
|
1239
|
+
const result = spawnSync(bin, args, {
|
|
1240
|
+
shell: process.platform === 'win32',
|
|
1241
|
+
input: stdinData || '',
|
|
1242
|
+
encoding: 'utf8',
|
|
1243
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
1244
|
+
timeout: 30_000,
|
|
1245
|
+
});
|
|
1246
|
+
return result.status === 0;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function main() {
|
|
1250
|
+
const args = process.argv.slice(2);
|
|
1251
|
+
if (args.length === 0) done();
|
|
1252
|
+
|
|
1253
|
+
const [subcommand, ...rest] = args;
|
|
1254
|
+
|
|
1255
|
+
let stdinData = '';
|
|
1256
|
+
try { stdinData = fs.readFileSync(0, 'utf8'); } catch { stdinData = ''; }
|
|
1257
|
+
|
|
1258
|
+
const hookArgs = ['hooks', subcommand, ...rest];
|
|
1259
|
+
|
|
1260
|
+
if (commandExists('ruflo')) { invokeHook('ruflo', [], hookArgs, stdinData); done(); }
|
|
1261
|
+
if (commandExists('claude-flow')) { invokeHook('claude-flow', [], hookArgs, stdinData); done(); }
|
|
1262
|
+
invokeHook('npx', ['--prefer-offline', '--yes', 'ruflo@latest'], hookArgs, stdinData);
|
|
1263
|
+
done();
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
main();
|
|
1267
|
+
`;
|
|
1268
|
+
}
|
|
1198
1269
|
//# sourceMappingURL=helpers-generator.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.1",
|
|
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",
|