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.
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent-planning.smoke.d.ts +18 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent-planning.smoke.js +253 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent.d.ts +198 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent.js +651 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.d.ts +133 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.js +281 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.smoke.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.smoke.js +300 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.d.ts +138 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.js +260 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.smoke.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.smoke.js +246 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.d.ts +123 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.js +312 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.smoke.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.smoke.js +327 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.d.ts +125 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.js +350 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.smoke.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.smoke.js +228 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-e2e-smoke.d.ts +27 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-e2e-smoke.js +136 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-extract.smoke.d.ts +45 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-extract.smoke.js +242 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/features.d.ts +46 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/features.js +170 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.d.ts +105 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.js +260 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.smoke.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.smoke.js +235 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/train-data-loader.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/train-data-loader.js +179 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-judge.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-judge.js +437 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-loader.d.ts +87 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-loader.js +326 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/file_read.d.ts +35 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/file_read.js +403 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/grounded_query.d.ts +126 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/grounded_query.js +225 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/index.d.ts +32 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/index.js +36 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/types.d.ts +62 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/types.js +12 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/web_search.d.ts +30 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/web_search.js +210 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.js +297 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.smoke.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.smoke.js +332 -0
- package/v3/@claude-flow/cli/dist/src/commands/gaia-bench.d.ts +40 -0
- package/v3/@claude-flow/cli/dist/src/commands/gaia-bench.js +597 -0
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +41 -4
- package/v3/@claude-flow/cli/dist/src/init/executor.js +25 -6
- package/v3/@claude-flow/cli/dist/src/init/mcp-generator.js +9 -4
- 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:
|
|
138
|
-
claudeFlow:
|
|
174
|
+
claude: hasRufloSettings || hasRufloMcp,
|
|
175
|
+
claudeFlow: hasClaudeFlow,
|
|
139
176
|
};
|
|
140
177
|
}
|
|
141
178
|
// Init subcommand (default)
|