claude-flow 3.63.4 → 3.63.5

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,5 @@
1
1
  {
2
- "adoptedAt": 1793043879777,
2
+ "adoptedAt": 1793126252524,
3
3
  "championId": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
4
4
  "manifest": {
5
5
  "schema": "ruflo.proven-config/v1",
@@ -57,10 +57,10 @@
57
57
  "source": "git-subdir",
58
58
  "url": "https://github.com/proffesor-for-testing/agentic-qe.git",
59
59
  "path": "plugins/agentic-qe-fleet",
60
- "ref": "v3.15.13",
61
- "sha": "e976a8e451b331fff86fc249f2ba40f19644c9fc"
60
+ "ref": "v3.15.15",
61
+ "sha": "1d0cc76632e8663ca2dffeaf3e000ef7baf3d481"
62
62
  },
63
- "description": "Agentic QE (external: maintained in proffesor-for-testing/agentic-qe, pinned to v3.15.13): a quality-engineering fleet with 11 QE agents, 9 skills, 9 commands, the agentic-qe MCP server pinned to the plugin version, and the aqe-mod guard that refuses destructive operations on AQE learning data"
63
+ "description": "Agentic QE (external: maintained in proffesor-for-testing/agentic-qe, pinned to v3.15.15): a quality-engineering fleet with 11 QE agents, 9 skills, 9 commands, the agentic-qe MCP server pinned to the plugin version, and the aqe-mod guard that refuses destructive operations on AQE learning data"
64
64
  },
65
65
  {
66
66
  "name": "ruflo-docs",
package/README.md CHANGED
@@ -244,7 +244,7 @@ This adds slash commands and agent definitions. `ruflo-core` (installed above) d
244
244
  | [**ruflo-browser**](plugins/ruflo-browser/README.md) | Automate browser testing with Playwright |
245
245
  | [**ruflo-jujutsu**](plugins/ruflo-jujutsu/README.md) | Analyze git diffs, score risk, suggest reviewers |
246
246
  | [**ruflo-docs**](plugins/ruflo-docs/README.md) | Generate and maintain documentation automatically |
247
- | [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.13/plugins/agentic-qe-fleet) | External, maintained by [Agentic QE](https://github.com/proffesor-for-testing/agentic-qe): 11 QE agents, test generation, coverage, chaos and TDD skills, the `agentic-qe` MCP server, and a guard for AQE learning data (`/plugin install agentic-qe-fleet@ruflo`). Install it from one marketplace only, `agentic-qe` or `ruflo`: enabled from both, its mod and commands load twice |
247
+ | [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.15/plugins/agentic-qe-fleet) | External, maintained by [Agentic QE](https://github.com/proffesor-for-testing/agentic-qe): 11 QE agents, test generation, coverage, chaos and TDD skills, the `agentic-qe` MCP server, and a guard for AQE learning data (`/plugin install agentic-qe-fleet@ruflo`). Install it from one marketplace only, `agentic-qe` or `ruflo`: enabled from both, its mod and commands load twice |
248
248
 
249
249
  #### Security & Compliance
250
250
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.63.4",
3
+ "version": "3.63.5",
4
4
  "workspaces": [
5
5
  "v3/@claude-flow/codex",
6
6
  "v3/@claude-flow/mcp",
@@ -72,7 +72,7 @@
72
72
  "@claude-flow/cli-core": "3.7.5",
73
73
  "@claude-flow/codex": "3.0.5",
74
74
  "@claude-flow/mcp": "3.1.4",
75
- "@claude-flow/neural": "3.0.0",
75
+ "@claude-flow/neural": "3.0.1",
76
76
  "@claude-flow/plugin-agent-federation": "1.0.3",
77
77
  "@claude-flow/security": "3.0.5",
78
78
  "@claude-flow/shared": "3.0.6",
@@ -2489,7 +2489,7 @@ const workerStatusCommand = {
2489
2489
  });
2490
2490
  if (result.summary) {
2491
2491
  output.writeln();
2492
- output.writeln(`Total: ${result.summary.total} | Running: ${output.highlight(String(result.summary.running))} | Completed: ${output.success(String(result.summary.completed))} | Failed: ${output.error(String(result.summary.failed))}`);
2492
+ output.writeln(`Total: ${result.summary.total} | Pending: ${String(result.summary.pending ?? 0)} | Running: ${output.highlight(String(result.summary.running))} | Completed: ${output.success(String(result.summary.completed))} | Failed: ${output.error(String(result.summary.failed))}`);
2493
2493
  }
2494
2494
  }
2495
2495
  else {
@@ -152,7 +152,7 @@ async function getSystemStatus() {
152
152
  backend: 'none',
153
153
  performance: { searchTime: 0, cacheHitRate: 0 }
154
154
  },
155
- tasks: { total: 0, pending: 0, running: 0, completed: 0, failed: 0 },
155
+ tasks: { total: 0, pending: 0, running: 0, completed: 0, failed: 0, cancelled: 0 },
156
156
  performance: {
157
157
  cpuUsage: 0,
158
158
  memoryUsage: 0,
@@ -217,6 +217,7 @@ function displayStatus(status) {
217
217
  { status: 'Running', count: status.tasks.running },
218
218
  { status: 'Completed', count: status.tasks.completed },
219
219
  { status: 'Failed', count: status.tasks.failed },
220
+ { status: 'Cancelled', count: status.tasks.cancelled },
220
221
  { status: output.bold('Total'), count: status.tasks.total }
221
222
  ]
222
223
  });
@@ -4817,6 +4817,7 @@ export const hooksWorkerStatus = {
4817
4817
  workers,
4818
4818
  summary: {
4819
4819
  total: workers.length,
4820
+ pending: workers.filter(w => w.status === 'pending').length,
4820
4821
  running: workers.filter(w => w.status === 'running').length,
4821
4822
  completed: workers.filter(w => w.status === 'completed').length,
4822
4823
  failed: workers.filter(w => w.status === 'failed').length,
@@ -15,7 +15,7 @@
15
15
  * - DEFAULT-REFUSES Authorization / Cookie / X-Auth-* (CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1 opt-in)
16
16
  * - hard timeout 30s default, 60s ceiling
17
17
  * - response truncated to 256KB default, 1MB ceiling
18
- * - no redirects auto-followed beyond fetch's default; status reported as-is
18
+ * - redirects followed manually, max 10, every hop re-validated by the same allowlist
19
19
  *
20
20
  * @module @claude-flow/cli/mcp-tools/http-fetch
21
21
  */
@@ -15,7 +15,7 @@
15
15
  * - DEFAULT-REFUSES Authorization / Cookie / X-Auth-* (CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1 opt-in)
16
16
  * - hard timeout 30s default, 60s ceiling
17
17
  * - response truncated to 256KB default, 1MB ceiling
18
- * - no redirects auto-followed beyond fetch's default; status reported as-is
18
+ * - redirects followed manually, max 10, every hop re-validated by the same allowlist
19
19
  *
20
20
  * @module @claude-flow/cli/mcp-tools/http-fetch
21
21
  */
@@ -23,6 +23,8 @@ const DEFAULT_TIMEOUT_MS = 30_000;
23
23
  const MAX_TIMEOUT_MS = 60_000;
24
24
  const DEFAULT_MAX_RESPONSE_BYTES = 256 * 1024; // 256 KB
25
25
  const MAX_RESPONSE_BYTES = 1024 * 1024; // 1 MB
26
+ const MAX_REDIRECTS = 10;
27
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
26
28
  const DEFAULT_USER_AGENT = 'ruflo-http-fetch/1.0';
27
29
  const FORBIDDEN_HEADER_PREFIXES = ['x-auth-', 'x-api-key'];
28
30
  const FORBIDDEN_HEADERS_EXACT = ['authorization', 'cookie', 'set-cookie', 'proxy-authorization'];
@@ -230,13 +232,97 @@ export async function httpFetchExecute(input) {
230
232
  const controller = new AbortController();
231
233
  const timer = setTimeout(() => controller.abort(), timeoutMs);
232
234
  try {
233
- const response = await fetch(parsed.toString(), {
234
- method,
235
- headers,
236
- body: method === 'GET' || method === 'HEAD' ? undefined : body,
237
- signal: controller.signal,
238
- // Keep redirect default ('follow') — caller sees final status/url.
239
- });
235
+ let currentUrl = parsed;
236
+ let currentMethod = method;
237
+ let currentBody = body;
238
+ let response;
239
+ let redirects = 0;
240
+ // Do not let fetch follow redirects on its own: only the initial URL was
241
+ // validated, so an automatic follow could reach loopback/RFC1918 or switch
242
+ // protocols after passing the allowlist. Every hop is validated with the
243
+ // same rules; sensitive headers are dropped when the origin changes.
244
+ while (true) {
245
+ response = await fetch(currentUrl.toString(), {
246
+ method: currentMethod,
247
+ headers,
248
+ body: currentMethod === 'GET' || currentMethod === 'HEAD' ? undefined : currentBody,
249
+ signal: controller.signal,
250
+ redirect: 'manual',
251
+ });
252
+ if (!REDIRECT_STATUSES.has(response.status))
253
+ break;
254
+ const location = response.headers.get('location');
255
+ if (!location)
256
+ break;
257
+ if (redirects++ >= MAX_REDIRECTS) {
258
+ try {
259
+ await response.body?.cancel();
260
+ }
261
+ catch { /* ignore */ }
262
+ return {
263
+ success: false,
264
+ status: response.status,
265
+ statusText: response.statusText,
266
+ headers: {},
267
+ body: '',
268
+ bodyTruncated: false,
269
+ bytesRead: 0,
270
+ durationMs: Date.now() - startedAt,
271
+ url: currentUrl.toString(),
272
+ method,
273
+ error: `too many redirects (max ${MAX_REDIRECTS})`,
274
+ errorCode: 'REDIRECT_LIMIT',
275
+ };
276
+ }
277
+ let nextUrl;
278
+ try {
279
+ nextUrl = validateUrl(new URL(location, currentUrl).toString());
280
+ }
281
+ catch (e) {
282
+ try {
283
+ await response.body?.cancel();
284
+ }
285
+ catch { /* ignore */ }
286
+ const err = e;
287
+ return {
288
+ success: false,
289
+ status: response.status,
290
+ statusText: response.statusText,
291
+ headers: {},
292
+ body: '',
293
+ bodyTruncated: false,
294
+ bytesRead: 0,
295
+ durationMs: Date.now() - startedAt,
296
+ url: currentUrl.toString(),
297
+ method,
298
+ error: `redirect blocked: ${err.message}`,
299
+ errorCode: err.code ?? 'REDIRECT_BLOCKED',
300
+ };
301
+ }
302
+ // The redirect response body is not returned; release the connection
303
+ // before issuing the next request. A body-less 3xx is also fine.
304
+ try {
305
+ await response.body?.cancel();
306
+ }
307
+ catch { /* ignore */ }
308
+ if (nextUrl.origin !== currentUrl.origin) {
309
+ for (const key of Object.keys(headers)) {
310
+ const lower = key.toLowerCase();
311
+ if (lower === 'authorization' || lower === 'cookie' || lower === 'proxy-authorization') {
312
+ delete headers[key];
313
+ }
314
+ }
315
+ }
316
+ // Match fetch's redirect method rules: 303 becomes GET except for
317
+ // GET/HEAD, while 301/302 only rewrite POST to GET. 307/308 preserve
318
+ // both method and body.
319
+ if ((response.status === 303 && currentMethod !== 'GET' && currentMethod !== 'HEAD') ||
320
+ ((response.status === 301 || response.status === 302) && currentMethod === 'POST')) {
321
+ currentMethod = 'GET';
322
+ currentBody = undefined;
323
+ }
324
+ currentUrl = nextUrl;
325
+ }
240
326
  // Drain the body with byte budget. Use array-buffer first to bound size
241
327
  // deterministically before decoding to UTF-8.
242
328
  const reader = response.body?.getReader();
@@ -286,7 +372,7 @@ export async function httpFetchExecute(input) {
286
372
  bodyTruncated,
287
373
  bytesRead,
288
374
  durationMs: Date.now() - startedAt,
289
- url: parsed.toString(),
375
+ url: currentUrl.toString(),
290
376
  method,
291
377
  };
292
378
  }
@@ -633,6 +633,7 @@ export const systemTools = [
633
633
  running: tasks.filter(t => t.status === 'in_progress').length,
634
634
  completed: tasks.filter(t => t.status === 'completed').length,
635
635
  failed: tasks.filter(t => t.status === 'failed').length,
636
+ cancelled: tasks.filter(t => t.status === 'cancelled').length,
636
637
  };
637
638
  },
638
639
  },
@@ -208,7 +208,9 @@ export const taskTools = [
208
208
  // Apply filters
209
209
  if (input.status) {
210
210
  // Support comma-separated status values. 'all' (sent by `status tasks`) means no status filter.
211
- const statuses = input.status.split(',').map(s => s.trim());
211
+ // Each value goes through statusOf, the reader task_update uses, so "done", "canceled",
212
+ // "running" or "Completed" match the stored status rather than matching nothing.
213
+ const statuses = input.status.split(',').map(s => s.trim()).map(s => statusOf(s) ?? s);
212
214
  if (!statuses.includes('all')) {
213
215
  tasks = tasks.filter(t => statuses.includes(t.status));
214
216
  }
@@ -20,7 +20,9 @@ function parseCondition(when) {
20
20
  if (expression === 'true' || expression === 'false') {
21
21
  return { constant: expression === 'true' };
22
22
  }
23
- const match = expression.match(/^([a-zA-Z_]\w*)\s*===?\s*(?:'([^']*)'|"([^"]*)"|(-?\d+(?:\.\d+)?)|(true|false))$/);
23
+ // Variable names follow the `{{…}}` interpolation syntax, so a condition can
24
+ // read a task step's output, stored as `<stepId>.output` (`step-1.output`).
25
+ const match = expression.match(/^([a-zA-Z_][\w.-]*)\s*===?\s*(?:'([^']*)'|"([^"]*)"|(-?\d+(?:\.\d+)?)|(true|false))$/);
24
26
  if (!match) {
25
27
  return new Error('condition config.when must compare a variable to a quoted string, number, or boolean using == or ===');
26
28
  }
@@ -7,8 +7,8 @@
7
7
  * (a 2.1.107 npm-global beside a current native one, say) silently decides.
8
8
  */
9
9
  import { execFileSync } from 'node:child_process';
10
- import { existsSync, realpathSync, statSync } from 'node:fs';
11
- import { delimiter, join } from 'node:path';
10
+ import { closeSync, existsSync, fstatSync, openSync, readSync, realpathSync, statSync } from 'node:fs';
11
+ import { delimiter, dirname, join, resolve } from 'node:path';
12
12
  export const MODS_DEFAULT_ON = '2.1.287';
13
13
  export const MODS_FIRST = '2.1.277';
14
14
  const parse = (v) => v.split('.').map((n) => Number(n));
@@ -52,6 +52,98 @@ export const defaultVersionOf = (path) => {
52
52
  return null;
53
53
  }
54
54
  };
55
+ /** Known npm/cmd-shim Node launcher forms (legacy basedir and basedir_win).
56
+ * Match the whole template, not comments or arbitrary shell code. Unknown npm
57
+ * versions/formats simply keep the independent probe result. Never execute it.
58
+ */
59
+ const npmCmdShim = (entry, modern) => [
60
+ '@ECHO off', 'GOTO start', ':find_dp0', 'SET dp0=%~dp0', 'EXIT /b', ':start',
61
+ 'SETLOCAL', 'CALL :find_dp0', '', 'IF EXIST "%dp0%\\node.exe" (',
62
+ ' SET "_prog=%dp0%\\node.exe"', ') ELSE (', ' SET "_prog=node"',
63
+ ...(!modern ? [' SET PATHEXT=%PATHEXT:;.JS;=;%'] : []), ')', '',
64
+ 'endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & '
65
+ + (modern ? 'set PATHEXT=%PATHEXT:;.JS;=;% & ' : '')
66
+ + `"%_prog%" "%dp0%\\${entry.replaceAll('/', '\\')}" %*`, '',
67
+ ].join('\r\n');
68
+ const npmPosixShim = (entry, modern) => [
69
+ '#!/bin/sh', 'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")',
70
+ ...(modern ? ['basedir_win="$basedir"'] : []), '',
71
+ modern ? 'case `uname -a` in' : 'case `uname` in',
72
+ ...(modern ? [
73
+ ' *CYGWIN*|*MINGW*|*MSYS*)', ' if command -v cygpath > /dev/null 2>&1; then',
74
+ ' basedir_win=`cygpath -w "$basedir"`', ' fi', ' ;;', ' *WSL2*)',
75
+ ' if command -v wslpath > /dev/null 2>&1; then',
76
+ ' basedir_win="$(wslpath -w "$basedir" 2> /dev/null)"',
77
+ ' if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then',
78
+ ' echo "Error: wslpath failed to convert path. WSL environment may be misconfigured." >&2',
79
+ ' exit 1', ' fi', ' fi', ' ;;',
80
+ ] : [
81
+ ' *CYGWIN*|*MINGW*|*MSYS*)', ' if command -v cygpath > /dev/null 2>&1; then',
82
+ ' basedir=`cygpath -w "$basedir"`', ' fi', ' ;;',
83
+ ]), 'esac', '',
84
+ ...(modern ? [
85
+ 'PROG_EXE="$basedir/node.exe"', 'if ! [ -x "$PROG_EXE" ]; then',
86
+ ' PROG_EXE="$basedir/node"', ' if ! [ -x "$PROG_EXE" ]; then',
87
+ ' PROG_EXE=node', ' if ! [ -x "$PROG_EXE" ]; then', ' PROG_EXE=node.exe',
88
+ ' fi', ' fi', 'fi', '', `exec "$PROG_EXE" "$basedir_win/${entry}" "$@"`,
89
+ ] : [
90
+ 'if [ -x "$basedir/node" ]; then', ` exec "$basedir/node" "$basedir/${entry}" "$@"`,
91
+ 'else ', ` exec node "$basedir/${entry}" "$@"`, 'fi',
92
+ ]), '',
93
+ ].join('\n');
94
+ // A no-shebang/native bin uses npm's direct-exec forms, not a Node wrapper.
95
+ const npmDirectCmdShim = (entry) => [
96
+ '@ECHO off', 'GOTO start', ':find_dp0', 'SET dp0=%~dp0', 'EXIT /b', ':start',
97
+ 'SETLOCAL', 'CALL :find_dp0', `"%dp0%\\${entry.replaceAll('/', '\\')}" %*`, '',
98
+ ].join('\r\n');
99
+ const npmDirectPosixShim = (entry, modern) => {
100
+ const nodeShim = npmPosixShim(entry, modern);
101
+ const preamble = nodeShim.slice(0, nodeShim.indexOf(modern ? 'PROG_EXE=' : 'if [ -x'));
102
+ return preamble + `exec "$basedir/${entry}" "$@"\n`;
103
+ };
104
+ function npmShimTarget(path, cmd) {
105
+ let fd;
106
+ try {
107
+ fd = openSync(path, 'r');
108
+ const stat = fstatSync(fd);
109
+ if (!stat.isFile() || stat.size > 64 * 1024)
110
+ return null;
111
+ // Also cap the actual read if a file grows after stat.
112
+ const bytes = Buffer.alloc(64 * 1024 + 1);
113
+ let size = 0;
114
+ while (size < bytes.length) {
115
+ const n = readSync(fd, bytes, size, bytes.length - size, null);
116
+ if (!n)
117
+ break;
118
+ size += n;
119
+ }
120
+ if (size > 64 * 1024)
121
+ return null;
122
+ const text = bytes.subarray(0, size).toString('utf8').replace(/\r\n/g, '\n').trimEnd();
123
+ const targets = cmd
124
+ ? [...text.matchAll(/"%dp0%\\([^"\r\n]+)" +%\*$/gm)].map(m => m[1].replaceAll('\\', '/'))
125
+ : [...text.matchAll(/^\s*exec (?:[^\r\n]* )?"\$basedir(?:_win)?\/([^"\r\n]+)" +"\$@"$/gm)].map(m => m[1]);
126
+ const entry = targets[0];
127
+ if (!entry || targets.some(t => t !== entry) || !/^[a-zA-Z0-9@._ /-]+$/.test(entry) || entry.startsWith('/'))
128
+ return null;
129
+ const template = cmd ? npmCmdShim : npmPosixShim;
130
+ const normalize = (value) => value.replace(/\r\n/g, '\n').trimEnd();
131
+ const node = [false, true].some(modern => normalize(template(entry, modern)) === text);
132
+ const direct = cmd ? normalize(npmDirectCmdShim(entry)) === text
133
+ : [false, true].some(modern => normalize(npmDirectPosixShim(entry, modern)) === text);
134
+ if (!node && !direct)
135
+ return null;
136
+ const target = realpathSync(resolve(dirname(path), entry));
137
+ return statSync(target).isFile() ? { path: target, mode: node ? 'node' : 'direct' } : null;
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ finally {
143
+ if (fd !== undefined)
144
+ closeSync(fd);
145
+ }
146
+ }
55
147
  /** Every `claude` on PATH, in PATH order (the first is what runs), plus ~/.local/bin. */
56
148
  export function findClaudeInstalls(env, home, versionOf = defaultVersionOf) {
57
149
  const names = process.platform === 'win32' ? ['claude.exe', 'claude.cmd', 'claude'] : ['claude'];
@@ -59,6 +151,7 @@ export function findClaudeInstalls(env, home, versionOf = defaultVersionOf) {
59
151
  const seen = new Set();
60
152
  const installs = [];
61
153
  for (const dir of dirs) {
154
+ let cmd;
62
155
  for (const name of names) {
63
156
  const path = join(dir, name);
64
157
  let real;
@@ -73,7 +166,19 @@ export function findClaudeInstalls(env, home, versionOf = defaultVersionOf) {
73
166
  if (seen.has(real))
74
167
  continue;
75
168
  seen.add(real);
76
- installs.push({ path, version: versionOf(path) });
169
+ let version = versionOf(path);
170
+ if (process.platform === 'win32' && name === 'claude' && version === null && cmd?.version) {
171
+ const target = npmShimTarget(path, false);
172
+ const cmdTarget = npmShimTarget(cmd.path, true);
173
+ if (target && cmdTarget && target.path === cmdTarget.path && target.mode === cmdTarget.mode)
174
+ version = cmd.version;
175
+ }
176
+ const install = { path, version };
177
+ installs.push(install);
178
+ // Only a .cmd actually probed in this directory can supply a version.
179
+ // A realpath-deduplicated alias in another directory proves nothing here.
180
+ if (name === 'claude.cmd')
181
+ cmd = install;
77
182
  }
78
183
  }
79
184
  return installs;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.63.4",
3
+ "version": "3.63.5",
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",
@@ -103,7 +103,7 @@
103
103
  "@claude-flow/cli-core": "3.7.5",
104
104
  "@claude-flow/codex": "3.0.5",
105
105
  "@claude-flow/mcp": "3.1.4",
106
- "@claude-flow/neural": "3.0.0",
106
+ "@claude-flow/neural": "3.0.1",
107
107
  "@claude-flow/plugin-agent-federation": "1.0.3",
108
108
  "@claude-flow/security": "3.0.5",
109
109
  "@claude-flow/shared": "3.0.6",