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.
@@ -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.0",
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.0",
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",