claude-flow 3.14.4 → 3.16.0

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.
Files changed (23) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/package.json +1 -1
  3. package/v3/@claude-flow/cli/dist/src/business-pods/bbs-budget-tracker.d.ts +139 -0
  4. package/v3/@claude-flow/cli/dist/src/business-pods/bbs-budget-tracker.js +358 -0
  5. package/v3/@claude-flow/cli/dist/src/business-pods/domain-affinity-policy.d.ts +47 -0
  6. package/v3/@claude-flow/cli/dist/src/business-pods/domain-affinity-policy.js +65 -0
  7. package/v3/@claude-flow/cli/dist/src/business-pods/pod-schema.d.ts +96 -0
  8. package/v3/@claude-flow/cli/dist/src/business-pods/pod-schema.js +225 -0
  9. package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +20 -5
  10. package/v3/@claude-flow/cli/dist/src/mcp-client.js +21 -0
  11. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.d.ts +28 -0
  12. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.js +394 -0
  13. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.d.ts +36 -0
  14. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +253 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/business-pod-tools.d.ts +20 -0
  16. package/v3/@claude-flow/cli/dist/src/mcp-tools/business-pod-tools.js +169 -0
  17. package/v3/@claude-flow/cli/dist/src/mcp-tools/http-fetch-tools.d.ts +55 -0
  18. package/v3/@claude-flow/cli/dist/src/mcp-tools/http-fetch-tools.js +329 -0
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +4 -0
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +8 -0
  21. package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.d.ts +6 -0
  22. package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +83 -4
  23. package/v3/@claude-flow/cli/package.json +4 -1
@@ -0,0 +1,329 @@
1
+ /**
2
+ * http_fetch MCP tool — ADR-164 §5.1.8.
3
+ *
4
+ * The Operations business pod (templates/ops.json) references `http_fetch` as
5
+ * an allowed MCP tool for the synthetic-endpoint availability bench (probe
6
+ * 200/500 from a configured URL, escalate to #ops on 500-rate spikes).
7
+ * Phase 3 left this as a TODO; Phase 4 ships the tool with secure-by-default
8
+ * gating per the §5.1.8 contract: URL allowlist (no file://, ftp://, no
9
+ * RFC-1918 / loopback unless explicitly enabled), header sanitization (no
10
+ * auth pass-through unless explicitly enabled), hard timeout via
11
+ * AbortController, response truncation, default User-Agent.
12
+ *
13
+ * Architectural constraints (load-bearing):
14
+ * - DEFAULT-REFUSES private addresses + loopback (CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 opt-in)
15
+ * - DEFAULT-REFUSES Authorization / Cookie / X-Auth-* (CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1 opt-in)
16
+ * - hard timeout 30s default, 60s ceiling
17
+ * - response truncated to 256KB default, 1MB ceiling
18
+ * - no redirects auto-followed beyond fetch's default; status reported as-is
19
+ *
20
+ * @module @claude-flow/cli/mcp-tools/http-fetch
21
+ */
22
+ const DEFAULT_TIMEOUT_MS = 30_000;
23
+ const MAX_TIMEOUT_MS = 60_000;
24
+ const DEFAULT_MAX_RESPONSE_BYTES = 256 * 1024; // 256 KB
25
+ const MAX_RESPONSE_BYTES = 1024 * 1024; // 1 MB
26
+ const DEFAULT_USER_AGENT = 'ruflo-http-fetch/1.0';
27
+ const FORBIDDEN_HEADER_PREFIXES = ['x-auth-', 'x-api-key'];
28
+ const FORBIDDEN_HEADERS_EXACT = ['authorization', 'cookie', 'set-cookie', 'proxy-authorization'];
29
+ export class HttpFetchValidationError extends Error {
30
+ code;
31
+ constructor(message, code) {
32
+ super(message);
33
+ this.code = code;
34
+ this.name = 'HttpFetchValidationError';
35
+ }
36
+ }
37
+ /**
38
+ * Decide whether the URL is permitted under the default secure-by-default
39
+ * allowlist. Block file://, ftp://, RFC-1918 private addresses, loopback,
40
+ * link-local — unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 is set.
41
+ */
42
+ export function validateUrl(rawUrl) {
43
+ let parsed;
44
+ try {
45
+ parsed = new URL(rawUrl);
46
+ }
47
+ catch {
48
+ throw new HttpFetchValidationError(`invalid URL: ${rawUrl}`, 'INVALID_URL');
49
+ }
50
+ const proto = parsed.protocol.toLowerCase();
51
+ if (proto !== 'http:' && proto !== 'https:') {
52
+ throw new HttpFetchValidationError(`protocol ${parsed.protocol} not allowed (only http: and https:)`, 'FORBIDDEN_PROTOCOL');
53
+ }
54
+ const host = parsed.hostname.toLowerCase();
55
+ const allowPrivate = process.env.CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE === '1';
56
+ if (!allowPrivate && isPrivateOrLoopback(host)) {
57
+ throw new HttpFetchValidationError(`host ${host} is loopback/private/link-local; set CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 to override`, 'PRIVATE_ADDRESS');
58
+ }
59
+ return parsed;
60
+ }
61
+ function isPrivateOrLoopback(host) {
62
+ if (host === 'localhost' || host === 'localhost.localdomain')
63
+ return true;
64
+ // IPv6 loopback
65
+ if (host === '::1' || host === '[::1]')
66
+ return true;
67
+ // IPv4 numeric checks
68
+ const m = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
69
+ if (m) {
70
+ const a = Number(m[1]);
71
+ const b = Number(m[2]);
72
+ if (a === 0)
73
+ return true; // 0.0.0.0/8
74
+ if (a === 127)
75
+ return true; // loopback
76
+ if (a === 10)
77
+ return true; // RFC1918
78
+ if (a === 172 && b >= 16 && b <= 31)
79
+ return true; // RFC1918
80
+ if (a === 192 && b === 168)
81
+ return true; // RFC1918
82
+ if (a === 169 && b === 254)
83
+ return true; // link-local
84
+ if (a === 100 && b >= 64 && b <= 127)
85
+ return true; // CGNAT
86
+ return false;
87
+ }
88
+ // IPv6 bracketed addresses and other special forms — be conservative and
89
+ // accept only public-looking literals. Reject obvious private/local forms.
90
+ if (host.startsWith('fc') || host.startsWith('fd'))
91
+ return true; // fc00::/7 ULA
92
+ if (host.startsWith('fe80:'))
93
+ return true; // link-local
94
+ return false;
95
+ }
96
+ export function validateHeaders(headers) {
97
+ const allowAuth = process.env.CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH === '1';
98
+ const out = {};
99
+ for (const [key, value] of Object.entries(headers)) {
100
+ const lower = key.toLowerCase();
101
+ if (!allowAuth) {
102
+ if (FORBIDDEN_HEADERS_EXACT.includes(lower)) {
103
+ throw new HttpFetchValidationError(`header "${key}" is not allowed without CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1`, 'FORBIDDEN_HEADER');
104
+ }
105
+ if (FORBIDDEN_HEADER_PREFIXES.some((p) => lower.startsWith(p))) {
106
+ throw new HttpFetchValidationError(`header "${key}" is not allowed without CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1`, 'FORBIDDEN_HEADER');
107
+ }
108
+ }
109
+ if (typeof value !== 'string') {
110
+ throw new HttpFetchValidationError(`header "${key}" must be a string`, 'INVALID_HEADER_VALUE');
111
+ }
112
+ out[key] = value;
113
+ }
114
+ return out;
115
+ }
116
+ function clampNumber(raw, defaultValue, max) {
117
+ if (raw === undefined || raw === null)
118
+ return defaultValue;
119
+ const n = Number(raw);
120
+ if (!Number.isFinite(n) || n <= 0)
121
+ return defaultValue;
122
+ return Math.min(Math.floor(n), max);
123
+ }
124
+ /**
125
+ * Pure execution path so tests can call it without going through the MCP
126
+ * dispatcher. Returns a result object (does not throw on validation failure
127
+ * — it returns success: false with an errorCode).
128
+ */
129
+ export async function httpFetchExecute(input) {
130
+ const startedAt = Date.now();
131
+ const url = String(input.url ?? '');
132
+ const method = String(input.method ?? 'GET').toUpperCase();
133
+ if (!['GET', 'POST', 'HEAD'].includes(method)) {
134
+ return {
135
+ success: false,
136
+ status: 0,
137
+ statusText: '',
138
+ headers: {},
139
+ body: '',
140
+ bodyTruncated: false,
141
+ bytesRead: 0,
142
+ durationMs: Date.now() - startedAt,
143
+ url,
144
+ method,
145
+ error: `method ${method} not allowed (GET, POST, HEAD only)`,
146
+ errorCode: 'INVALID_METHOD',
147
+ };
148
+ }
149
+ let parsed;
150
+ try {
151
+ parsed = validateUrl(url);
152
+ }
153
+ catch (e) {
154
+ const err = e;
155
+ return {
156
+ success: false,
157
+ status: 0,
158
+ statusText: '',
159
+ headers: {},
160
+ body: '',
161
+ bodyTruncated: false,
162
+ bytesRead: 0,
163
+ durationMs: Date.now() - startedAt,
164
+ url,
165
+ method,
166
+ error: err.message,
167
+ errorCode: err.code ?? 'VALIDATION_ERROR',
168
+ };
169
+ }
170
+ let headers;
171
+ try {
172
+ const raw = (input.headers ?? {});
173
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
174
+ throw new HttpFetchValidationError('headers must be an object', 'INVALID_HEADERS');
175
+ }
176
+ headers = validateHeaders(raw);
177
+ }
178
+ catch (e) {
179
+ const err = e;
180
+ return {
181
+ success: false,
182
+ status: 0,
183
+ statusText: '',
184
+ headers: {},
185
+ body: '',
186
+ bodyTruncated: false,
187
+ bytesRead: 0,
188
+ durationMs: Date.now() - startedAt,
189
+ url,
190
+ method,
191
+ error: err.message,
192
+ errorCode: err.code ?? 'VALIDATION_ERROR',
193
+ };
194
+ }
195
+ // Default UA if not set
196
+ const hasUA = Object.keys(headers).some((k) => k.toLowerCase() === 'user-agent');
197
+ if (!hasUA)
198
+ headers['User-Agent'] = DEFAULT_USER_AGENT;
199
+ const timeoutMs = clampNumber(input.timeoutMs, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
200
+ const maxResponseBytes = clampNumber(input.maxResponseBytes, DEFAULT_MAX_RESPONSE_BYTES, MAX_RESPONSE_BYTES);
201
+ const body = input.body !== undefined ? String(input.body) : undefined;
202
+ const controller = new AbortController();
203
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
204
+ try {
205
+ const response = await fetch(parsed.toString(), {
206
+ method,
207
+ headers,
208
+ body: method === 'GET' || method === 'HEAD' ? undefined : body,
209
+ signal: controller.signal,
210
+ // Keep redirect default ('follow') — caller sees final status/url.
211
+ });
212
+ // Drain the body with byte budget. Use array-buffer first to bound size
213
+ // deterministically before decoding to UTF-8.
214
+ const reader = response.body?.getReader();
215
+ let bytesRead = 0;
216
+ let bodyTruncated = false;
217
+ const chunks = [];
218
+ if (reader) {
219
+ // eslint-disable-next-line no-constant-condition
220
+ while (true) {
221
+ const { value, done } = await reader.read();
222
+ if (done)
223
+ break;
224
+ if (value) {
225
+ if (bytesRead + value.byteLength > maxResponseBytes) {
226
+ const remaining = Math.max(0, maxResponseBytes - bytesRead);
227
+ if (remaining > 0)
228
+ chunks.push(value.slice(0, remaining));
229
+ bytesRead += remaining;
230
+ bodyTruncated = true;
231
+ try {
232
+ await reader.cancel('body-truncated');
233
+ }
234
+ catch { /* ignore */ }
235
+ break;
236
+ }
237
+ chunks.push(value);
238
+ bytesRead += value.byteLength;
239
+ }
240
+ }
241
+ }
242
+ const combined = new Uint8Array(bytesRead);
243
+ let offset = 0;
244
+ for (const c of chunks) {
245
+ combined.set(c, offset);
246
+ offset += c.byteLength;
247
+ }
248
+ const decoder = new TextDecoder('utf-8', { fatal: false });
249
+ const bodyText = decoder.decode(combined);
250
+ const outHeaders = {};
251
+ response.headers.forEach((v, k) => { outHeaders[k] = v; });
252
+ return {
253
+ success: true,
254
+ status: response.status,
255
+ statusText: response.statusText,
256
+ headers: outHeaders,
257
+ body: bodyText,
258
+ bodyTruncated,
259
+ bytesRead,
260
+ durationMs: Date.now() - startedAt,
261
+ url: parsed.toString(),
262
+ method,
263
+ };
264
+ }
265
+ catch (e) {
266
+ const msg = e instanceof Error ? e.message : String(e);
267
+ const aborted = msg.includes('aborted') || e?.name === 'AbortError';
268
+ return {
269
+ success: false,
270
+ status: 0,
271
+ statusText: '',
272
+ headers: {},
273
+ body: '',
274
+ bodyTruncated: false,
275
+ bytesRead: 0,
276
+ durationMs: Date.now() - startedAt,
277
+ url: parsed.toString(),
278
+ method,
279
+ error: aborted ? `request aborted after ${timeoutMs}ms timeout` : msg,
280
+ errorCode: aborted ? 'TIMEOUT' : 'FETCH_ERROR',
281
+ };
282
+ }
283
+ finally {
284
+ clearTimeout(timer);
285
+ }
286
+ }
287
+ export const httpFetchTools = [
288
+ {
289
+ name: 'http_fetch',
290
+ description: 'ADR-164 §5.1.8 — HTTP probe primitive for business-pod ops benches (synthetic 200/500 endpoint checks, third-party status pages). Default-secure: blocks file://, ftp://, RFC-1918 / loopback / link-local hosts unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1, and rejects Authorization / Cookie / X-Auth-* headers unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1. Hard 30s timeout (60s ceiling), response truncated to 256 KB (1 MB ceiling), default User-Agent ruflo-http-fetch/1.0. Use when a pod or smoke contract needs a guarded HTTP probe — calling Node fetch() directly is wrong because it skips the URL allowlist and header sanitization that ADR-164 mandates for autopilot mode. Pair with the ops-pod bench in plugins/ruflo-business-pods/templates/ops.json (the §4.4 synthetic-endpoint test).',
291
+ category: 'business-pods',
292
+ tags: ['business-pods', 'http', 'fetch', 'adr-164', 'security', 'ops-pod'],
293
+ inputSchema: {
294
+ type: 'object',
295
+ properties: {
296
+ url: {
297
+ type: 'string',
298
+ description: 'Absolute http:// or https:// URL. file://, ftp://, RFC-1918 / loopback / link-local blocked by default.',
299
+ },
300
+ method: {
301
+ type: 'string',
302
+ enum: ['GET', 'POST', 'HEAD'],
303
+ description: 'HTTP method. Defaults to GET.',
304
+ },
305
+ timeoutMs: {
306
+ type: 'number',
307
+ description: 'Hard timeout in milliseconds. Default 30000, max 60000.',
308
+ },
309
+ maxResponseBytes: {
310
+ type: 'number',
311
+ description: 'Max body bytes to read before truncation. Default 262144 (256 KB), max 1048576 (1 MB).',
312
+ },
313
+ headers: {
314
+ type: 'object',
315
+ description: 'Extra request headers. Authorization / Cookie / X-Auth-* blocked unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1.',
316
+ },
317
+ body: {
318
+ type: 'string',
319
+ description: 'Request body for POST. Ignored for GET / HEAD.',
320
+ },
321
+ },
322
+ required: ['url'],
323
+ },
324
+ handler: async (input) => {
325
+ return await httpFetchExecute(input);
326
+ },
327
+ },
328
+ ];
329
+ //# sourceMappingURL=http-fetch-tools.js.map
@@ -26,4 +26,8 @@ export { guidanceTools } from './guidance-tools.js';
26
26
  export { autopilotTools } from './autopilot-tools.js';
27
27
  export { metaharnessTools } from './metaharness-tools.js';
28
28
  export { testgenTools } from './testgen-tools.js';
29
+ export { agenticowTools } from './agenticow-tools.js';
30
+ export { agentbbsTools } from './agentbbs-tools.js';
31
+ export { businessPodTools } from './business-pod-tools.js';
32
+ export { httpFetchTools } from './http-fetch-tools.js';
29
33
  //# sourceMappingURL=index.d.ts.map
@@ -27,4 +27,12 @@ export { autopilotTools } from './autopilot-tools.js';
27
27
  export { metaharnessTools } from './metaharness-tools.js';
28
28
  // ADR-175-inspired — Test-Driven Repair via headless `claude -p`
29
29
  export { testgenTools } from './testgen-tools.js';
30
+ // agenticow@~0.2.3 — Copy-On-Write memory branching (162-byte branches)
31
+ export { agenticowTools } from './agenticow-tools.js';
32
+ // ADR-164 — AgentBBS federated business-domain BBS rooms (Phase 1)
33
+ export { agentbbsTools } from './agentbbs-tools.js';
34
+ // ADR-164 Phase 2 — Business-pod template validation
35
+ export { businessPodTools } from './business-pod-tools.js';
36
+ // ADR-164 Phase 4 §5.1.8 — http_fetch (secure-by-default HTTP probe)
37
+ export { httpFetchTools } from './http-fetch-tools.js';
30
38
  //# sourceMappingURL=index.js.map
@@ -18,6 +18,12 @@
18
18
  * - metaharness_security_bench upstream's "Darwin Shield" (their own ADR-155)
19
19
  * - metaharness_bench create/verify bench suites used by evolve --bench
20
20
  *
21
+ * @metaharness/redblue integration adds one tool that targets the standalone
22
+ * `@metaharness/redblue` npm package — adversarial red/blue LLM testing
23
+ * for the agents and apps you own:
24
+ *
25
+ * - metaharness_redblue red-team → judge → blue-patch → retest → report
26
+ *
21
27
  * Every tool resolves the corresponding plugin script
22
28
  * (`plugins/ruflo-metaharness/scripts/<X>.mjs`) via the same locator
23
29
  * the commands/metaharness.ts dispatcher uses, then spawns it with
@@ -18,6 +18,12 @@
18
18
  * - metaharness_security_bench upstream's "Darwin Shield" (their own ADR-155)
19
19
  * - metaharness_bench create/verify bench suites used by evolve --bench
20
20
  *
21
+ * @metaharness/redblue integration adds one tool that targets the standalone
22
+ * `@metaharness/redblue` npm package — adversarial red/blue LLM testing
23
+ * for the agents and apps you own:
24
+ *
25
+ * - metaharness_redblue red-team → judge → blue-patch → retest → report
26
+ *
21
27
  * Every tool resolves the corresponding plugin script
22
28
  * (`plugins/ruflo-metaharness/scripts/<X>.mjs`) via the same locator
23
29
  * the commands/metaharness.ts dispatcher uses, then spawns it with
@@ -42,8 +48,14 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
42
48
  /**
43
49
  * Walk up from this module to find plugins/ruflo-metaharness/scripts/.
44
50
  * Handles three install layouts (mirrors commands/metaharness.ts).
51
+ *
52
+ * `requiredScript`, if provided, narrows the match the same way the
53
+ * commands dispatcher does — guards against the publish-artifact mirror
54
+ * (`v3/@claude-flow/cli/plugins/ruflo-metaharness/scripts/`, regenerated
55
+ * by `prepublishOnly`) shadowing the source when it's stale on a new
56
+ * script.
45
57
  */
46
- function locatePluginScripts() {
58
+ function locatePluginScripts(requiredScript) {
47
59
  const candidates = [];
48
60
  let p = resolve(__dirname);
49
61
  for (let i = 0; i < 8; i++) {
@@ -55,8 +67,11 @@ function locatePluginScripts() {
55
67
  candidates.push(join(cwd, 'plugins', 'ruflo-metaharness', 'scripts'));
56
68
  candidates.push(join(cwd, 'node_modules', '@claude-flow', 'cli', 'plugins', 'ruflo-metaharness', 'scripts'));
57
69
  for (const c of candidates) {
58
- if (existsSync(join(c, '_harness.mjs')))
59
- return c;
70
+ if (!existsSync(join(c, '_harness.mjs')))
71
+ continue;
72
+ if (requiredScript && !existsSync(join(c, requiredScript)))
73
+ continue;
74
+ return c;
60
75
  }
61
76
  return null;
62
77
  }
@@ -90,7 +105,7 @@ function locatePluginScripts() {
90
105
  */
91
106
  function runScript(scriptName, args) {
92
107
  return new Promise((resolve) => {
93
- const dir = locatePluginScripts();
108
+ const dir = locatePluginScripts(scriptName);
94
109
  if (!dir) {
95
110
  resolve({
96
111
  exitCode: 0, stdout: '', json: { degraded: true, reason: 'plugin-not-found' },
@@ -512,5 +527,69 @@ export const metaharnessTools = [
512
527
  return { success: r.success, data: r.json, degraded: r.degraded, exitCode: r.exitCode };
513
528
  },
514
529
  },
530
+ // ───────────────────────────────────────────────────────────────────────
531
+ // @metaharness/redblue integration (1 tool).
532
+ // Backed by `@metaharness/redblue@~0.1.1`. Plugin script shells out via
533
+ // _redblue.mjs. Same {success, data, degraded, exitCode} contract.
534
+ //
535
+ // SAFETY: redblue itself enforces hard boundaries (no real creds, no live
536
+ // targets, no shell, no arbitrary network, no eval) in upstream's
537
+ // src/config/safety.ts at config-load time. The wrapper does NOT relax
538
+ // those — it only forwards argv with shell:false. `--mock-judge` is the
539
+ // $0 marker-fixture CI path; the real model judge requires
540
+ // $OPENROUTER_API_KEY which we never inject.
541
+ // ───────────────────────────────────────────────────────────────────────
542
+ {
543
+ name: 'metaharness_redblue',
544
+ description: 'Adversarial red/blue LLM testing via @metaharness/redblue — generates attacks across OWASP LLM Top-10 / NIST AI RMF families (prompt injection, tool misuse, data leakage, jailbreaks, denial-of-wallet), runs them against an LLM target YOU OWN, judges compromise, optionally applies declarative blue-team patches, retests, and emits a board-readable report with measured failure reduction. Use when shipping an LLM-powered product and you need a repeatable security gate before exposing it to users — eyeballing prompts is wrong because attack surface coverage requires the OWASP/NIST taxonomy and the judge has to be model-driven for jailbreak detection. SAFETY: upstream hard-enforces no-creds / no-live-targets / no-shell / no-network / no-eval at config-load time; cannot be relaxed via flags. For CI / offline use --mockJudge=true ($0 marker fixture). For real model judging set $OPENROUTER_API_KEY and accept the per-run cost capped by max_cost_usd (default $3). ' + MCP_SUCCESS_SEMANTIC,
545
+ category: 'metaharness',
546
+ inputSchema: {
547
+ type: 'object',
548
+ properties: {
549
+ subcommand: {
550
+ type: 'string',
551
+ enum: ['init', 'run', 'patch', 'attack', 'report'],
552
+ description: 'init = scaffold redblue.yaml; run = baseline (+ optional --patch); patch = baseline → patch → retest delta; attack = preview attacks; report = render existing report.json',
553
+ default: 'run',
554
+ },
555
+ config: { type: 'string', description: 'Path to redblue.yaml (default: ./redblue.yaml)' },
556
+ out: { type: 'string', description: 'Output report path for run/patch (default: temp file we read back inline)' },
557
+ in: { type: 'string', description: 'Input report path for `report` subcommand' },
558
+ tests: { type: 'number', description: 'How many test cases (run/patch only)' },
559
+ patch: { type: 'boolean', description: '`run` only — after baseline, apply blue-team patches and retest', default: false },
560
+ mockJudge: { type: 'boolean', description: '$0 TEST-ONLY marker fixture (no model calls). Use for CI / offline. Real judging requires OPENROUTER_API_KEY.', default: false },
561
+ family: { type: 'string', enum: ['prompt', 'tools', 'data', 'all'], description: '`attack` subcommand only — which attack family to preview' },
562
+ count: { type: 'number', description: '`attack` only — how many cases to preview' },
563
+ alertOnFail: { type: 'boolean', description: 'Exit 1 when post-patch verdict is FAIL (gate-style)', default: false },
564
+ timeoutMs: { type: 'number', description: 'Subprocess hard timeout (default 120000; mock-judge runs complete in seconds)' },
565
+ },
566
+ },
567
+ handler: async (input) => {
568
+ const args = [String(input.subcommand ?? 'run')];
569
+ // attack family is positional
570
+ if (input.subcommand === 'attack' && input.family)
571
+ args.push(String(input.family));
572
+ if (input.config)
573
+ args.push('--config', String(input.config));
574
+ if (input.out)
575
+ args.push('--out', String(input.out));
576
+ if (input.in)
577
+ args.push('--in', String(input.in));
578
+ if (input.tests !== undefined)
579
+ args.push('--tests', String(input.tests));
580
+ if (input.patch === true)
581
+ args.push('--patch');
582
+ if (input.mockJudge === true)
583
+ args.push('--mock-judge');
584
+ if (input.count !== undefined)
585
+ args.push('--count', String(input.count));
586
+ if (input.alertOnFail === true)
587
+ args.push('--alert-on-fail');
588
+ if (input.timeoutMs !== undefined)
589
+ args.push('--timeout-ms', String(input.timeoutMs));
590
+ const r = await runScript('redblue.mjs', args);
591
+ return { success: r.success, data: r.json, degraded: r.degraded, exitCode: r.exitCode };
592
+ },
593
+ },
515
594
  ];
516
595
  //# sourceMappingURL=metaharness-tools.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.14.4",
3
+ "version": "3.16.0",
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",
@@ -106,6 +106,8 @@
106
106
  "yaml": "^2.8.0"
107
107
  },
108
108
  "optionalDependencies": {
109
+ "agentbbs": "~0.1.0",
110
+ "agenticow": "~0.2.3",
109
111
  "@claude-flow/aidefence": "^3.0.2",
110
112
  "@claude-flow/codex": "^3.0.0-alpha.8",
111
113
  "@claude-flow/embeddings": "^3.0.0-alpha.18",
@@ -115,6 +117,7 @@
115
117
  "@claude-flow/security": "^3.0.0-alpha.10",
116
118
  "@metaharness/darwin": "~0.3.1",
117
119
  "@metaharness/kernel": "~0.1.0",
120
+ "@metaharness/redblue": "~0.1.1",
118
121
  "@metaharness/router": "~0.3.2",
119
122
  "metaharness": "~0.2.6",
120
123
  "@ruvector/attention": "^0.1.32",