claude-flow 3.63.4 → 3.63.6

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.
@@ -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.6",
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
  });
@@ -812,14 +812,18 @@ export const claimsTools = [
812
812
  const overloaded = loads.filter(l => l.claims.length > maxClaims * targetUtilization * 1.5);
813
813
  const underloaded = loads.filter(l => l.claims.length < maxClaims * targetUtilization * 0.5);
814
814
  const suggestions = [];
815
+ // Claim counts including moves suggested so far, so one target is not
816
+ // handed more claims than maxClaims allows.
817
+ const plannedCount = new Map(loads.map(l => [l.agentId, l.claims.length]));
815
818
  for (const over of overloaded) {
816
819
  // Find low-progress claims to redistribute
817
820
  const movable = over.claims
818
821
  .filter(c => c.progress < 25 && c.status === 'active')
819
822
  .slice(0, over.claims.length - Math.ceil(maxClaims * targetUtilization));
820
823
  for (const claim of movable) {
821
- const target = underloaded.find(u => u.agentType === over.agentType && u.claims.length < maxClaims);
824
+ const target = underloaded.find(u => u.agentType === over.agentType && plannedCount.get(u.agentId) < maxClaims);
822
825
  if (target) {
826
+ plannedCount.set(target.agentId, plannedCount.get(target.agentId) + 1);
823
827
  suggestions.push({
824
828
  issueId: claim.issueId,
825
829
  from: `agent:${over.agentId}:${over.agentType}`,
@@ -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
  }