claude-flow 3.10.4 → 3.10.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.
Files changed (58) hide show
  1. package/package.json +1 -1
  2. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent-planning.smoke.d.ts +18 -0
  3. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent-planning.smoke.js +253 -0
  4. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent.d.ts +198 -0
  5. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent.js +651 -0
  6. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.d.ts +133 -0
  7. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.js +281 -0
  8. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.smoke.d.ts +22 -0
  9. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.smoke.js +300 -0
  10. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.d.ts +138 -0
  11. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.js +260 -0
  12. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.smoke.d.ts +19 -0
  13. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.smoke.js +246 -0
  14. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.d.ts +123 -0
  15. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.js +312 -0
  16. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.smoke.d.ts +21 -0
  17. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.smoke.js +327 -0
  18. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.d.ts +125 -0
  19. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.js +350 -0
  20. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.smoke.d.ts +21 -0
  21. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.smoke.js +228 -0
  22. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-e2e-smoke.d.ts +27 -0
  23. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-e2e-smoke.js +136 -0
  24. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-extract.smoke.d.ts +45 -0
  25. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-extract.smoke.js +242 -0
  26. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/features.d.ts +46 -0
  27. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/features.js +170 -0
  28. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.d.ts +105 -0
  29. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.js +260 -0
  30. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.smoke.d.ts +20 -0
  31. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.smoke.js +235 -0
  32. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/train-data-loader.d.ts +51 -0
  33. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/train-data-loader.js +179 -0
  34. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-judge.d.ts +88 -0
  35. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-judge.js +437 -0
  36. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-loader.d.ts +87 -0
  37. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-loader.js +326 -0
  38. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/file_read.d.ts +35 -0
  39. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/file_read.js +403 -0
  40. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/grounded_query.d.ts +126 -0
  41. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/grounded_query.js +225 -0
  42. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/index.d.ts +32 -0
  43. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/index.js +36 -0
  44. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/types.d.ts +62 -0
  45. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/types.js +12 -0
  46. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/web_search.d.ts +30 -0
  47. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/web_search.js +210 -0
  48. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.d.ts +88 -0
  49. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.js +297 -0
  50. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.smoke.d.ts +20 -0
  51. package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.smoke.js +332 -0
  52. package/v3/@claude-flow/cli/dist/src/commands/gaia-bench.d.ts +40 -0
  53. package/v3/@claude-flow/cli/dist/src/commands/gaia-bench.js +597 -0
  54. package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
  55. package/v3/@claude-flow/cli/dist/src/commands/init.js +41 -4
  56. package/v3/@claude-flow/cli/dist/src/init/executor.js +25 -6
  57. package/v3/@claude-flow/cli/dist/src/init/mcp-generator.js +9 -4
  58. package/v3/@claude-flow/cli/package.json +1 -1
@@ -0,0 +1,597 @@
1
+ /**
2
+ * V3 CLI gaia-bench Command — ADR-133-PR8 + ADR-135 Tracks A/B/D/E/Q + ADR-136 Track Q
3
+ *
4
+ * Runs GAIA benchmark questions through the claude-flow agent loop and
5
+ * reports pass-rate, cost, and per-question results.
6
+ *
7
+ * Contract (matches gaia-benchmark.yml workflow expectations):
8
+ * node bin/cli.js gaia-bench run \
9
+ * --level <1|2|3> \
10
+ * --limit <N> \
11
+ * --models <csv> \
12
+ * --output json
13
+ *
14
+ * JSON output shape:
15
+ * {
16
+ * level: number,
17
+ * model: string,
18
+ * summary: { total, passed, passRate, estCostUsd, hardnessDist? },
19
+ * results: [{ task_id, question, model, correct, answer, expected_output, error }]
20
+ * }
21
+ *
22
+ * Integration (iter 39 — ADR-135):
23
+ * Wires standalone track modules into the CLI so they are usable end-to-end.
24
+ * - Track A (--voting-attempts N) : multi-attempt self-consistency voting
25
+ * - Track B (--planning-interval N) : periodic planning checkpoints in gaia-agent
26
+ * - Track D (--enable-critic) : adversarial critic review after agent answer
27
+ * - Track E (--decompose) : question decomposition for multi-step Qs
28
+ * - Track Q (--hardness-routing) : hardness-based compute allocation
29
+ *
30
+ * Precedence when flags combine:
31
+ * --hardness-routing overrides --max-turns and --voting-attempts per question.
32
+ * --voting-attempts > 1 takes precedence over --enable-critic (cost containment).
33
+ * --decompose works independently; sub-question answers feed into voting/critic/plain.
34
+ *
35
+ * Refs: ADR-133, ADR-135, ADR-136, #2165, iter 28/34/36/37/39
36
+ */
37
+ import { output } from '../output.js';
38
+ // ---------------------------------------------------------------------------
39
+ // Pricing constants for cost estimation
40
+ // ---------------------------------------------------------------------------
41
+ const MODEL_PRICING = {
42
+ 'claude-haiku-4-5': { inputPerM: 0.25, outputPerM: 1.25 },
43
+ 'claude-haiku-3': { inputPerM: 0.25, outputPerM: 1.25 },
44
+ 'claude-sonnet-4-5': { inputPerM: 3.0, outputPerM: 15.0 },
45
+ 'claude-sonnet-4-6': { inputPerM: 3.0, outputPerM: 15.0 },
46
+ 'claude-opus-4-5': { inputPerM: 15.0, outputPerM: 75.0 },
47
+ };
48
+ function estimateCost(model, totalInputTokens, totalOutputTokens) {
49
+ const pricing = MODEL_PRICING[model] ?? { inputPerM: 3.0, outputPerM: 15.0 };
50
+ return ((totalInputTokens / 1_000_000) * pricing.inputPerM +
51
+ (totalOutputTokens / 1_000_000) * pricing.outputPerM);
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // run subcommand
55
+ // ---------------------------------------------------------------------------
56
+ const runCommand = {
57
+ name: 'run',
58
+ description: 'Run GAIA benchmark questions against one or more models',
59
+ options: [
60
+ {
61
+ name: 'level',
62
+ short: 'l',
63
+ type: 'number',
64
+ description: 'GAIA difficulty level: 1 (easiest), 2, or 3',
65
+ default: '1',
66
+ },
67
+ {
68
+ name: 'limit',
69
+ short: 'n',
70
+ type: 'number',
71
+ description: 'Maximum number of questions to run (default: all)',
72
+ },
73
+ {
74
+ name: 'models',
75
+ short: 'm',
76
+ type: 'string',
77
+ description: 'Comma-separated list of model IDs to test',
78
+ default: 'claude-haiku-4-5',
79
+ },
80
+ {
81
+ name: 'output',
82
+ short: 'o',
83
+ type: 'string',
84
+ description: 'Output format: text or json',
85
+ default: 'text',
86
+ },
87
+ {
88
+ name: 'concurrency',
89
+ short: 'c',
90
+ type: 'number',
91
+ description: 'Number of questions to run in parallel (default: 3)',
92
+ default: '3',
93
+ },
94
+ {
95
+ name: 'smoke-only',
96
+ type: 'boolean',
97
+ description: 'Use the 5-question smoke fixture instead of real HF dataset (no HF token required)',
98
+ default: 'false',
99
+ },
100
+ {
101
+ name: 'max-turns',
102
+ type: 'number',
103
+ description: 'Maximum agent turns per question (default: 12). Overridden per-question when --hardness-routing is enabled.',
104
+ default: '12',
105
+ },
106
+ {
107
+ name: 'judge-model',
108
+ type: 'string',
109
+ description: 'Model for LLM-as-judge scoring (default: claude-sonnet-4-6)',
110
+ default: 'claude-sonnet-4-6',
111
+ },
112
+ {
113
+ name: 'voting-attempts',
114
+ type: 'number',
115
+ description: 'Number of parallel attempts for majority-vote self-consistency (default: 1 = no voting). N>1 costs Nx per question. Recommended: 3 (+5-10pp L1 lift per ADR-135 Track A). Overridden per-question when --hardness-routing is enabled.',
116
+ default: '1',
117
+ },
118
+ {
119
+ name: 'hardness-routing',
120
+ type: 'boolean',
121
+ description: 'ADR-136 Track Q: enable hardness-based compute routing. Trains a linear classifier from historical result JSONs and allocates: easy=Haiku/4t/1-attempt, medium=Sonnet/8t/1-attempt, hard=Sonnet/12t/3-vote. Overrides --max-turns and --voting-attempts per question.',
122
+ default: 'false',
123
+ },
124
+ {
125
+ name: 'hardness-verbose',
126
+ type: 'boolean',
127
+ description: 'ADR-136 Track Q: log hardness prediction for each question (requires --hardness-routing).',
128
+ default: 'false',
129
+ },
130
+ {
131
+ name: 'enable-critic',
132
+ type: 'boolean',
133
+ description: 'ADR-135 Track D: enable adversarial critic review after agent answer (+3-5pp L1 lift expected). Skipped when --voting-attempts > 1 (cost containment — voting takes precedence).',
134
+ default: 'false',
135
+ },
136
+ {
137
+ name: 'decompose',
138
+ type: 'boolean',
139
+ description: 'ADR-135 Track E: decompose complex questions into 1-5 sub-questions before solving (+5-10pp on multi-step Qs, ~30-40% of L1 set). Each sub-question runs through voting/critic/plain independently; sub-answers are synthesized before judging.',
140
+ default: 'false',
141
+ },
142
+ {
143
+ name: 'planning-interval',
144
+ type: 'number',
145
+ description: 'ADR-135 Track B: inject a planning checkpoint every N tool_use turns (default: 4, set 0 to disable). Based on smolagents finding — prevents tunnel-vision on bad strategies.',
146
+ default: '4',
147
+ },
148
+ {
149
+ name: 'enable-convergence',
150
+ type: 'boolean',
151
+ description: 'iter 62: enable convergence layer — forces a final commit when max_turns, loop, or token_overflow is detected (default: true). Disabling is for ablation only.',
152
+ default: 'true',
153
+ },
154
+ ],
155
+ examples: [
156
+ {
157
+ command: 'claude-flow gaia-bench run --level 1 --limit 10 --models claude-haiku-4-5 --output json',
158
+ description: 'Run 10 Level-1 questions with Haiku, JSON output',
159
+ },
160
+ {
161
+ command: 'claude-flow gaia-bench run --level 1 --limit 10 --models claude-haiku-4-5,claude-sonnet-4-6',
162
+ description: 'Compare Haiku vs Sonnet on 10 Level-1 questions',
163
+ },
164
+ {
165
+ command: 'claude-flow gaia-bench run --smoke-only --output json',
166
+ description: 'Quick smoke test (5 fixture questions, no HF token needed)',
167
+ },
168
+ {
169
+ command: 'claude-flow gaia-bench run --level 1 --limit 20 --models claude-haiku-4-5 --voting-attempts 3 --output json',
170
+ description: 'Self-consistency voting: run each question 3x, majority-vote (ADR-135 Track A, +5-10pp expected)',
171
+ },
172
+ {
173
+ command: 'claude-flow gaia-bench run --level 1 --models claude-sonnet-4-6 --hardness-routing --output json',
174
+ description: 'ADR-136 Track Q: auto-route questions to Haiku/Sonnet based on predicted difficulty',
175
+ },
176
+ {
177
+ command: 'claude-flow gaia-bench run --level 1 --models claude-sonnet-4-6 --enable-critic --output json',
178
+ description: 'ADR-135 Track D: adversarial critic reviews each answer before submission (+3-5pp expected)',
179
+ },
180
+ {
181
+ command: 'claude-flow gaia-bench run --level 1 --models claude-sonnet-4-6 --decompose --output json',
182
+ description: 'ADR-135 Track E: decompose complex questions into sub-questions (+5-10pp on multi-step Qs)',
183
+ },
184
+ {
185
+ command: 'claude-flow gaia-bench run --level 1 --models claude-sonnet-4-6 --hardness-routing --enable-critic --planning-interval 4',
186
+ description: 'Recommended config: hardness routing + critic + planning checkpoints (~$2/run est.)',
187
+ },
188
+ ],
189
+ action: async (ctx) => {
190
+ const level = parseInt(String(ctx.flags.level ?? '1'), 10);
191
+ const limit = ctx.flags.limit ? parseInt(String(ctx.flags.limit), 10) : undefined;
192
+ const modelsRaw = String(ctx.flags.models ?? 'claude-haiku-4-5');
193
+ const models = modelsRaw.split(',').map((m) => m.trim()).filter(Boolean);
194
+ const outputFormat = String(ctx.flags.output ?? 'text');
195
+ const concurrency = parseInt(String(ctx.flags.concurrency ?? '3'), 10);
196
+ // Parser converts --smoke-only to camelCase "smokeOnly"
197
+ const smokeOnly = ctx.flags['smokeOnly'] === true || ctx.flags['smokeOnly'] === 'true' ||
198
+ ctx.flags['smoke-only'] === true || ctx.flags['smoke-only'] === 'true';
199
+ // Parser converts --max-turns to maxTurns, --judge-model to judgeModel, --voting-attempts to votingAttempts
200
+ // NOTE: default must match DEFAULT_MAX_TURNS in benchmarks/gaia-agent.ts
201
+ const maxTurns = parseInt(String(ctx.flags['maxTurns'] ?? ctx.flags['max-turns'] ?? '12'), 10);
202
+ const judgeModel = String(ctx.flags['judgeModel'] ?? ctx.flags['judge-model'] ?? 'claude-sonnet-4-6');
203
+ // votingAttempts=1 means no voting (backward-compat default). N>1 routes through runGaiaAgentWithVoting.
204
+ const votingAttempts = parseInt(String(ctx.flags['votingAttempts'] ?? ctx.flags['voting-attempts'] ?? '1'), 10);
205
+ const useVoting = votingAttempts > 1;
206
+ // ADR-136 Track Q: hardness-based routing.
207
+ const hardnessRouting = ctx.flags['hardnessRouting'] === true || ctx.flags['hardnessRouting'] === 'true' ||
208
+ ctx.flags['hardness-routing'] === true || ctx.flags['hardness-routing'] === 'true';
209
+ const hardnessVerbose = ctx.flags['hardnessVerbose'] === true || ctx.flags['hardnessVerbose'] === 'true' ||
210
+ ctx.flags['hardness-verbose'] === true || ctx.flags['hardness-verbose'] === 'true';
211
+ // ADR-135 Track D: adversarial critic.
212
+ // Voting takes precedence over critic when both are enabled (cost containment).
213
+ const enableCritic = !useVoting && (ctx.flags['enableCritic'] === true || ctx.flags['enableCritic'] === 'true' ||
214
+ ctx.flags['enable-critic'] === true || ctx.flags['enable-critic'] === 'true');
215
+ // ADR-135 Track E: question decomposition.
216
+ const enableDecompose = ctx.flags['decompose'] === true || ctx.flags['decompose'] === 'true';
217
+ // ADR-135 Track B: planning interval (passed through to runGaiaAgent via agentOpts).
218
+ const planningInterval = parseInt(String(ctx.flags['planningInterval'] ?? ctx.flags['planning-interval'] ?? '4'), 10);
219
+ // iter 62: convergence layer — default ON, disable with --no-enable-convergence.
220
+ // Note: boolean false is falsy, so we check for explicit false values only.
221
+ const enableConvergence = !(ctx.flags['enableConvergence'] === false || ctx.flags['enableConvergence'] === 'false' ||
222
+ ctx.flags['enable-convergence'] === false || ctx.flags['enable-convergence'] === 'false');
223
+ // Dynamic imports to avoid loading at startup.
224
+ // NOTE: gaia-*.ts sources are pre-compiled under dist/src/benchmarks/ only --
225
+ // they are NOT in the src/ include glob so TypeScript cannot resolve them
226
+ // statically. We resolve the absolute path from import.meta.url at runtime
227
+ // and cast to `any` to bypass the static-analysis check.
228
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
229
+ const benchmarksBase = new URL('../benchmarks/', import.meta.url).href;
230
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
231
+ const { loadGaia } = (await import(benchmarksBase + 'gaia-loader.js'));
232
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
+ const { runGaiaAgent } = (await import(benchmarksBase + 'gaia-agent.js'));
234
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
235
+ const { judgeAnswer } = (await import(benchmarksBase + 'gaia-judge.js'));
236
+ // ADR-135 Track A: voting wrapper (imported when --voting-attempts > 1 OR hardness routing triggers it).
237
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
238
+ const { runGaiaAgentWithVoting } = (useVoting || hardnessRouting)
239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
+ ? (await import(benchmarksBase + 'gaia-voting.js'))
241
+ : { runGaiaAgentWithVoting: null };
242
+ // ADR-135 Track D: critic wrapper (only imported when --enable-critic and no voting).
243
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
244
+ const { runGaiaAgentWithCritic } = enableCritic
245
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
246
+ ? (await import(benchmarksBase + 'gaia-critic.js'))
247
+ : { runGaiaAgentWithCritic: null };
248
+ // ADR-135 Track E: decomposer (only imported when --decompose).
249
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
250
+ let decomposeQuestion = null;
251
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
252
+ let synthesizeFromSubAnswers = null;
253
+ if (enableDecompose) {
254
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
255
+ const decomposerMod = (await import(benchmarksBase + 'gaia-decomposer.js'));
256
+ decomposeQuestion = decomposerMod.decomposeQuestion;
257
+ synthesizeFromSubAnswers = decomposerMod.synthesizeFromSubAnswers;
258
+ }
259
+ // ADR-136 Track Q: hardness predictor.
260
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
261
+ let hardnessPredictor = null;
262
+ if (hardnessRouting) {
263
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
264
+ const { HardnessPredictor } = (await import(benchmarksBase + 'gaia-hardness/predictor.js'));
265
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
266
+ const { loadTrainingData } = (await import(benchmarksBase + 'gaia-hardness/train-data-loader.js'));
267
+ hardnessPredictor = new HardnessPredictor({ conservativeMode: true });
268
+ const trainingData = loadTrainingData([], hardnessVerbose);
269
+ if (trainingData.length >= 10) {
270
+ hardnessPredictor.train(trainingData);
271
+ }
272
+ // If < 10 examples: cold-start (medium for all) -- documented fallback.
273
+ }
274
+ // Only print to stderr so stdout stays clean for JSON consumers
275
+ const log = (msg) => {
276
+ if (outputFormat !== 'json') {
277
+ output.writeln(msg);
278
+ }
279
+ else {
280
+ process.stderr.write(msg + '\n');
281
+ }
282
+ };
283
+ log('');
284
+ log(output.bold(`GAIA Benchmark -- Level ${level}${smokeOnly ? ' [SMOKE]' : ''}`));
285
+ log(output.dim('-'.repeat(60)));
286
+ log(`Models : ${models.join(', ')}`);
287
+ log(`Limit : ${limit ?? 'all'}`);
288
+ log(`Concurrency: ${concurrency}`);
289
+ if (useVoting && !hardnessRouting) {
290
+ log(`Voting : ${votingAttempts}x self-consistency (ADR-135 Track A) -- cost ~${votingAttempts}x per question`);
291
+ }
292
+ if (enableCritic) {
293
+ log(`Critic : ADR-135 Track D enabled -- adversarial review after each answer`);
294
+ }
295
+ if (enableDecompose) {
296
+ log(`Decompose: ADR-135 Track E enabled -- multi-step questions will be split into sub-questions`);
297
+ }
298
+ if (planningInterval > 0) {
299
+ log(`Planning: ADR-135 Track B -- checkpoint every ${planningInterval} turns`);
300
+ }
301
+ if (hardnessRouting) {
302
+ const trainedStatus = hardnessPredictor?.isTrained
303
+ ? 'trained (classifier active)'
304
+ : 'cold-start (no training data -> all medium)';
305
+ log(`Hardness: ADR-136 Track Q enabled -- ${trainedStatus}`);
306
+ log(' easy=Haiku/4t/1-attempt medium=Sonnet/8t/1-attempt hard=Sonnet/12t/3-vote');
307
+ }
308
+ log('');
309
+ // Load questions
310
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
311
+ let questions;
312
+ try {
313
+ questions = await loadGaia({ level, limit, smokeOnly });
314
+ }
315
+ catch (err) {
316
+ const msg = err instanceof Error ? err.message : String(err);
317
+ if (outputFormat === 'json') {
318
+ process.stdout.write(JSON.stringify({ error: `Failed to load GAIA dataset: ${msg}` }, null, 2) + '\n');
319
+ }
320
+ else {
321
+ output.writeln(output.error(`Failed to load GAIA dataset: ${msg}`));
322
+ }
323
+ return { success: false };
324
+ }
325
+ log(`Loaded : ${questions.length} questions`);
326
+ log('');
327
+ const allModelOutputs = [];
328
+ // Resolve the API key once (shared across all per-question agent calls).
329
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
330
+ const { resolveAnthropicApiKey } = (await import(benchmarksBase + 'gaia-agent.js'));
331
+ const apiKey = resolveAnthropicApiKey();
332
+ for (const model of models) {
333
+ log(output.bold(`Running model: ${model}`));
334
+ log(output.dim('-'.repeat(40)));
335
+ const results = [];
336
+ let totalInputTokens = 0;
337
+ let totalOutputTokens = 0;
338
+ let totalTurns = 0;
339
+ let totalWallMs = 0;
340
+ // ADR-136 Track Q: hardness distribution tracking.
341
+ const hardnessDist = { easy: 0, medium: 0, hard: 0 };
342
+ // Process questions in batches of `concurrency`
343
+ for (let i = 0; i < questions.length; i += concurrency) {
344
+ const batch = questions.slice(i, Math.min(i + concurrency, questions.length));
345
+ const batchResults = await Promise.all(batch.map(async (q) => {
346
+ const qIdx = i + batch.indexOf(q) + 1;
347
+ // ADR-136 Track Q: predict hardness and set per-question compute budget.
348
+ let effectiveModel = model;
349
+ let effectiveMaxTurns = maxTurns;
350
+ let effectiveVotingAttempts = votingAttempts;
351
+ let predictedDifficulty;
352
+ let predictedConfidence;
353
+ if (hardnessRouting && hardnessPredictor) {
354
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
355
+ const prediction = hardnessPredictor.predict(q);
356
+ predictedDifficulty = prediction.difficulty;
357
+ predictedConfidence = prediction.confidence;
358
+ hardnessDist[predictedDifficulty]++;
359
+ // Override compute budget from hardness policy.
360
+ const budget = prediction.budget;
361
+ effectiveModel = budget.model === 'haiku'
362
+ ? 'claude-haiku-4-5'
363
+ : (model.includes('sonnet') ? model : 'claude-sonnet-4-6');
364
+ effectiveMaxTurns = budget.maxTurns;
365
+ effectiveVotingAttempts = budget.votingAttempts;
366
+ if (hardnessVerbose) {
367
+ log(` [${qIdx}/${questions.length}] ${q.task_id} hardness=${predictedDifficulty}` +
368
+ ` conf=${((predictedConfidence ?? 0) * 100).toFixed(0)}%` +
369
+ ` -> ${effectiveModel} / ${effectiveMaxTurns}t / ${effectiveVotingAttempts}-attempt`);
370
+ }
371
+ else {
372
+ log(` [${qIdx}/${questions.length}] ${q.task_id} [${predictedDifficulty}] -- ${String(q.question).slice(0, 50)}...`);
373
+ }
374
+ }
375
+ else {
376
+ log(` [${qIdx}/${questions.length}] ${q.task_id} -- ${String(q.question).slice(0, 60)}...`);
377
+ }
378
+ const useThisVoting = effectiveVotingAttempts > 1;
379
+ // Critic is suppressed when voting is active (same precedence rule as the global flag).
380
+ const useThisCritic = enableCritic && !useThisVoting && runGaiaAgentWithCritic;
381
+ // Shared agent options (Track B planning interval + iter 62 convergence wired here).
382
+ const agentOpts = {
383
+ model: effectiveModel,
384
+ maxTurns: effectiveMaxTurns,
385
+ planningInterval,
386
+ apiKey,
387
+ enableConvergence,
388
+ };
389
+ // ADR-135 Track E: decompose the question if enabled.
390
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
391
+ let decomposedResult = null;
392
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
393
+ let questionsToSolve = [q];
394
+ if (enableDecompose && decomposeQuestion) {
395
+ try {
396
+ decomposedResult = await decomposeQuestion(q.question, { apiKey });
397
+ if (decomposedResult?.decomposed === true && Array.isArray(decomposedResult.subQuestions) && decomposedResult.subQuestions.length > 1) {
398
+ questionsToSolve = decomposedResult.subQuestions.map((sq) => ({ ...q, question: sq }));
399
+ log(` decomposed into ${questionsToSolve.length} sub-questions`);
400
+ }
401
+ }
402
+ catch {
403
+ // Graceful fallback: treat as atomic question.
404
+ decomposedResult = null;
405
+ }
406
+ }
407
+ // Solve each (sub-)question.
408
+ const subAnswers = [];
409
+ let lastAgentResult = null; // eslint-disable-line @typescript-eslint/no-explicit-any
410
+ let solveError;
411
+ for (const sq of questionsToSolve) {
412
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
413
+ let agentResult;
414
+ try {
415
+ if (useThisVoting && runGaiaAgentWithVoting) {
416
+ // ADR-135 Track A: multi-attempt majority voting.
417
+ agentResult = await runGaiaAgentWithVoting(sq, {
418
+ ...agentOpts,
419
+ attempts: effectiveVotingAttempts,
420
+ });
421
+ const vr = agentResult;
422
+ log(` vote-method=${vr.votingMethod ?? '?'} agreement=${vr.agreementCount ?? '?'}/${effectiveVotingAttempts}`);
423
+ }
424
+ else if (useThisCritic) {
425
+ // ADR-135 Track D: critic-wrapped agent.
426
+ agentResult = await runGaiaAgentWithCritic(sq, {
427
+ ...agentOpts,
428
+ enableCritic: true,
429
+ });
430
+ const cr = agentResult;
431
+ log(` critic-verdict=${cr.criticVerdict ?? '?'}`);
432
+ }
433
+ else {
434
+ agentResult = await runGaiaAgent(sq, agentOpts);
435
+ }
436
+ }
437
+ catch (err) {
438
+ solveError = err instanceof Error ? err.message : String(err);
439
+ log(` ERROR: ${solveError}`);
440
+ break;
441
+ }
442
+ subAnswers.push(agentResult.finalAnswer ?? '');
443
+ lastAgentResult = agentResult;
444
+ }
445
+ if (solveError || !lastAgentResult) {
446
+ return {
447
+ task_id: q.task_id,
448
+ question: q.question,
449
+ model: effectiveModel,
450
+ correct: false,
451
+ answer: null,
452
+ expected_output: q.final_answer,
453
+ error: solveError ?? 'no result',
454
+ hardnessDifficulty: predictedDifficulty,
455
+ hardnessConfidence: predictedConfidence,
456
+ decomposed: decomposedResult?.decomposed === true,
457
+ };
458
+ }
459
+ // ADR-135 Track E: synthesize sub-answers if decomposed.
460
+ let finalAnswer = subAnswers[0] ?? null;
461
+ if (decomposedResult?.decomposed === true && questionsToSolve.length > 1 && synthesizeFromSubAnswers) {
462
+ try {
463
+ const synth = await synthesizeFromSubAnswers(decomposedResult, subAnswers, { apiKey });
464
+ finalAnswer = synth.finalAnswer ?? finalAnswer;
465
+ }
466
+ catch {
467
+ // Graceful fallback: use first sub-answer.
468
+ }
469
+ }
470
+ // Judge the answer
471
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
472
+ let judgeResult;
473
+ try {
474
+ judgeResult = await judgeAnswer({ id: q.task_id, expected: q.final_answer, questionText: q.question }, finalAnswer, { judgeModel });
475
+ }
476
+ catch (err) {
477
+ const errorMsg = err instanceof Error ? err.message : String(err);
478
+ judgeResult = {
479
+ questionId: q.task_id,
480
+ passed: false,
481
+ scoringPath: 'exact-match',
482
+ candidateAnswer: finalAnswer ?? '',
483
+ groundTruth: q.final_answer,
484
+ judgeReason: `Judge error: ${errorMsg}`,
485
+ };
486
+ }
487
+ const verdict = judgeResult.passed ? output.success('PASS') : output.error('FAIL');
488
+ log(` ${verdict} answer="${finalAnswer ?? 'null'}" expected="${q.final_answer}"` +
489
+ ` turns=${lastAgentResult.turns} ${(lastAgentResult.wallMs / 1000).toFixed(1)}s`);
490
+ return {
491
+ task_id: q.task_id,
492
+ question: q.question,
493
+ model: effectiveModel,
494
+ correct: judgeResult.passed,
495
+ answer: finalAnswer,
496
+ expected_output: q.final_answer,
497
+ error: lastAgentResult.error,
498
+ turns: lastAgentResult.turns,
499
+ wallMs: lastAgentResult.wallMs,
500
+ inputTokens: lastAgentResult.totalInputTokens,
501
+ outputTokens: lastAgentResult.totalOutputTokens,
502
+ hardnessDifficulty: predictedDifficulty,
503
+ hardnessConfidence: predictedConfidence,
504
+ decomposed: decomposedResult?.decomposed === true,
505
+ };
506
+ }));
507
+ for (const r of batchResults) {
508
+ results.push(r);
509
+ totalInputTokens += r.inputTokens ?? 0;
510
+ totalOutputTokens += r.outputTokens ?? 0;
511
+ totalTurns += r.turns ?? 0;
512
+ totalWallMs += r.wallMs ?? 0;
513
+ }
514
+ }
515
+ const passed = results.filter((r) => r.correct).length;
516
+ const total = results.length;
517
+ const passRate = total > 0 ? passed / total : 0;
518
+ const estCostUsd = estimateCost(model, totalInputTokens, totalOutputTokens);
519
+ const modelOutput = {
520
+ level,
521
+ model,
522
+ summary: {
523
+ total,
524
+ passed,
525
+ passRate,
526
+ estCostUsd,
527
+ meanTurns: total > 0 ? totalTurns / total : 0,
528
+ meanWallMs: total > 0 ? totalWallMs / total : 0,
529
+ ...(hardnessRouting ? { hardnessDist } : {}),
530
+ },
531
+ results,
532
+ };
533
+ allModelOutputs.push(modelOutput);
534
+ log('');
535
+ log(output.bold(`Results for ${model}:`));
536
+ log(` Pass rate : ${passed}/${total} (${(passRate * 100).toFixed(1)}%)`);
537
+ log(` Est. cost : $${estCostUsd.toFixed(4)}`);
538
+ log(` Mean turns: ${modelOutput.summary.meanTurns.toFixed(1)}`);
539
+ log(` Mean time : ${(modelOutput.summary.meanWallMs / 1000).toFixed(1)}s per question`);
540
+ if (hardnessRouting) {
541
+ log(` Hardness : easy=${hardnessDist.easy} medium=${hardnessDist.medium} hard=${hardnessDist.hard}`);
542
+ }
543
+ log('');
544
+ }
545
+ // Output results
546
+ if (outputFormat === 'json') {
547
+ if (allModelOutputs.length === 1) {
548
+ // Single model: emit flat object (matches workflow contract)
549
+ process.stdout.write(JSON.stringify(allModelOutputs[0], null, 2) + '\n');
550
+ }
551
+ else {
552
+ // Multiple models: emit array
553
+ process.stdout.write(JSON.stringify(allModelOutputs, null, 2) + '\n');
554
+ }
555
+ }
556
+ else {
557
+ // Print summary table
558
+ output.writeln(output.bold('Summary'));
559
+ output.writeln(output.dim('-'.repeat(60)));
560
+ for (const m of allModelOutputs) {
561
+ const pct = (m.summary.passRate * 100).toFixed(1);
562
+ output.writeln(`${m.model.padEnd(28)} ${m.summary.passed}/${m.summary.total} (${pct}%)` +
563
+ ` cost=$${m.summary.estCostUsd.toFixed(4)}` +
564
+ ` turns=${m.summary.meanTurns.toFixed(1)}`);
565
+ }
566
+ }
567
+ return { success: true };
568
+ },
569
+ };
570
+ // ---------------------------------------------------------------------------
571
+ // Main gaia-bench command
572
+ // ---------------------------------------------------------------------------
573
+ export const gaiaBenchCommand = {
574
+ name: 'gaia-bench',
575
+ description: 'GAIA benchmark harness -- measure agent pass-rate on real GAIA questions',
576
+ subcommands: [runCommand],
577
+ examples: [
578
+ {
579
+ command: 'claude-flow gaia-bench run --level 1 --limit 10 --models claude-haiku-4-5 --output json',
580
+ description: 'Mini Level-1 run with Haiku, JSON output',
581
+ },
582
+ {
583
+ command: 'claude-flow gaia-bench run --smoke-only',
584
+ description: 'Quick smoke test with built-in fixture (no HF token)',
585
+ },
586
+ {
587
+ command: 'claude-flow gaia-bench run --level 1 --models claude-sonnet-4-6 --hardness-routing --output json',
588
+ description: 'ADR-136 Track Q: tiered compute routing by predicted question difficulty',
589
+ },
590
+ {
591
+ command: 'claude-flow gaia-bench run --level 1 --models claude-sonnet-4-6 --hardness-routing --enable-critic --planning-interval 4',
592
+ description: 'Recommended config: all tracks active, ~$2/run estimated',
593
+ },
594
+ ],
595
+ };
596
+ export default gaiaBenchCommand;
597
+ //# sourceMappingURL=gaia-bench.js.map
@@ -67,6 +67,8 @@ const commandLoaders = {
67
67
  'transfer-store': () => import('./transfer-store.js'),
68
68
  cleanup: () => import('./cleanup.js'),
69
69
  autopilot: () => import('./autopilot.js'),
70
+ // GAIA Benchmark Harness (ADR-133)
71
+ 'gaia-bench': () => import('./gaia-bench.js'),
70
72
  };
71
73
  // Cache for loaded commands
72
74
  const loadedCommands = new Map();
@@ -129,13 +129,50 @@ async function initCodexAction(ctx, options) {
129
129
  return { success: false, exitCode: 1 };
130
130
  }
131
131
  }
132
- // Check if project is already initialized
132
+ // Check if project is already initialized with ruflo.
133
+ // #2207: .claude/settings.json alone is NOT a ruflo marker — it's created by
134
+ // Claude Code itself and exists in every Claude Code project. We require a
135
+ // ruflo-specific signal: either a claudeFlow section in settings.json, OR a
136
+ // .mcp.json with a 'claude-flow' or 'ruflo' server key, OR the ruflo-only
137
+ // .claude-flow/config.yaml. Using the bare file-existence check was causing
138
+ // false-positives for new users whose only existing file was Claude Code's own
139
+ // settings.json.
133
140
  function isInitialized(cwd) {
134
- const claudePath = path.join(cwd, '.claude', 'settings.json');
135
141
  const claudeFlowPath = path.join(cwd, '.claude-flow', 'config.yaml');
142
+ const mcpJsonPath = path.join(cwd, '.mcp.json');
143
+ const settingsPath = path.join(cwd, '.claude', 'settings.json');
144
+ // Check .claude-flow/config.yaml — ruflo-specific, always reliable
145
+ const hasClaudeFlow = fs.existsSync(claudeFlowPath);
146
+ // Check .claude/settings.json for ruflo-specific content (claudeFlow section)
147
+ let hasRufloSettings = false;
148
+ if (fs.existsSync(settingsPath)) {
149
+ try {
150
+ const parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
151
+ hasRufloSettings =
152
+ parsed != null &&
153
+ typeof parsed === 'object' &&
154
+ 'claudeFlow' in parsed;
155
+ }
156
+ catch { /* malformed — ignore */ }
157
+ }
158
+ // Check .mcp.json for ruflo/claude-flow server key
159
+ let hasRufloMcp = false;
160
+ if (fs.existsSync(mcpJsonPath)) {
161
+ try {
162
+ const parsed = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
163
+ hasRufloMcp =
164
+ parsed != null &&
165
+ typeof parsed === 'object' &&
166
+ parsed.mcpServers != null &&
167
+ typeof parsed.mcpServers === 'object' &&
168
+ ('claude-flow' in parsed.mcpServers ||
169
+ 'ruflo' in parsed.mcpServers);
170
+ }
171
+ catch { /* malformed — ignore */ }
172
+ }
136
173
  return {
137
- claude: fs.existsSync(claudePath),
138
- claudeFlow: fs.existsSync(claudeFlowPath),
174
+ claude: hasRufloSettings || hasRufloMcp,
175
+ claudeFlow: hasClaudeFlow,
139
176
  };
140
177
  }
141
178
  // Init subcommand (default)