claude-flow 3.32.35 → 3.32.37
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/.claude/helpers/hook-handler.cjs +134 -3
- package/.claude/helpers/intelligence.cjs +83 -5
- package/node_modules/@claude-flow/codex/.agents/skills/github-automation/SKILL.md +32 -0
- package/node_modules/@claude-flow/codex/.agents/skills/performance-analysis/SKILL.md +32 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js +20 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js +18 -2
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts +11 -5
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.js +84 -849
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts +5 -0
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.js +54 -20
- package/node_modules/@claude-flow/codex/dist/initializer.js.map +1 -1
- package/package.json +1 -1
- package/v3/@claude-flow/cli/bin/cli.js +25 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/agent.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +159 -11
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +39 -4
- package/v3/@claude-flow/cli/dist/src/commands/init.js +52 -8
- package/v3/@claude-flow/cli/dist/src/commands/mcp.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/memory-distill.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +28 -0
- package/v3/@claude-flow/cli/dist/src/init/executor.js +45 -10
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +15 -4
- package/v3/@claude-flow/cli/dist/src/init/statusline-generator.js +18 -5
- package/v3/@claude-flow/cli/dist/src/mcp-server.d.ts +25 -0
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +57 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +51 -10
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +34 -8
- package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +12 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +29 -6
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +11 -8
- package/v3/@claude-flow/cli/dist/src/services/memory-distillation.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-distillation.js +81 -5
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +1 -0
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -1218,8 +1218,20 @@ const metricsCommand = {
|
|
|
1218
1218
|
const totalPatterns = safeNum(rawMetrics.patterns?.total ?? rawMetrics.summary?.patternsLearned);
|
|
1219
1219
|
const successfulPatterns = safeNum(rawMetrics.patterns?.successful ?? Math.round(safeNum(rawMetrics.summary?.successRate) * totalPatterns));
|
|
1220
1220
|
const failedPatterns = Math.max(0, safeNum(rawMetrics.patterns?.failed ?? totalPatterns - successfulPatterns));
|
|
1221
|
+
const unknownPatterns = Math.max(0, safeNum(rawMetrics.patterns?.unknown));
|
|
1222
|
+
const describedPatterns = Math.max(0, safeNum(rawMetrics.patterns?.described));
|
|
1223
|
+
const descriptionCoverage = safeNum(rawMetrics.patterns?.descriptionCoverage ??
|
|
1224
|
+
(totalPatterns > 0 ? describedPatterns / totalPatterns : 0));
|
|
1221
1225
|
const avgConfidence = safeNum(rawMetrics.patterns?.avgConfidence ?? rawMetrics.summary?.avgQuality);
|
|
1222
|
-
const routingAccuracy =
|
|
1226
|
+
const routingAccuracy = rawMetrics.agents?.routingAccuracy == null
|
|
1227
|
+
? null
|
|
1228
|
+
: safeNum(rawMetrics.agents.routingAccuracy);
|
|
1229
|
+
const averageRoutingConfidence = safeNum(rawMetrics.agents?.averageConfidence ??
|
|
1230
|
+
rawMetrics.routing?.avgConfidence ??
|
|
1231
|
+
rawMetrics.patterns?.avgConfidence);
|
|
1232
|
+
const routingOutcomeSuccessRate = rawMetrics.agents?.outcomeSuccessRate == null
|
|
1233
|
+
? null
|
|
1234
|
+
: safeNum(rawMetrics.agents.outcomeSuccessRate);
|
|
1223
1235
|
const totalRoutes = safeNum(rawMetrics.agents?.totalRoutes ?? rawMetrics.routing?.totalRoutes);
|
|
1224
1236
|
const topAgent = rawMetrics.agents?.topAgent ?? rawMetrics.routing?.topAgents?.[0]?.agent ?? 'n/a';
|
|
1225
1237
|
const totalCommands = safeNum(rawMetrics.commands?.totalExecuted ?? rawMetrics.commands?.totalCommands);
|
|
@@ -1227,8 +1239,22 @@ const metricsCommand = {
|
|
|
1227
1239
|
const avgRiskScore = safeNum(rawMetrics.commands?.avgRiskScore ?? rawMetrics.commands?.avgExecutionTime);
|
|
1228
1240
|
const result = {
|
|
1229
1241
|
...rawMetrics,
|
|
1230
|
-
patterns: {
|
|
1231
|
-
|
|
1242
|
+
patterns: {
|
|
1243
|
+
total: totalPatterns,
|
|
1244
|
+
successful: successfulPatterns,
|
|
1245
|
+
failed: failedPatterns,
|
|
1246
|
+
unknown: unknownPatterns,
|
|
1247
|
+
described: describedPatterns,
|
|
1248
|
+
descriptionCoverage,
|
|
1249
|
+
avgConfidence,
|
|
1250
|
+
},
|
|
1251
|
+
agents: {
|
|
1252
|
+
routingAccuracy,
|
|
1253
|
+
averageConfidence: averageRoutingConfidence,
|
|
1254
|
+
outcomeSuccessRate: routingOutcomeSuccessRate,
|
|
1255
|
+
totalRoutes,
|
|
1256
|
+
topAgent,
|
|
1257
|
+
},
|
|
1232
1258
|
commands: { totalExecuted: totalCommands, successRate: commandSuccessRate, avgRiskScore },
|
|
1233
1259
|
};
|
|
1234
1260
|
if (ctx.flags.format === 'json') {
|
|
@@ -1246,6 +1272,8 @@ const metricsCommand = {
|
|
|
1246
1272
|
{ metric: 'Total Patterns', value: totalPatterns },
|
|
1247
1273
|
{ metric: 'Successful', value: output.success(String(successfulPatterns)) },
|
|
1248
1274
|
{ metric: 'Failed', value: output.error(String(failedPatterns)) },
|
|
1275
|
+
...(unknownPatterns > 0 ? [{ metric: 'Unclassified', value: String(unknownPatterns) }] : []),
|
|
1276
|
+
{ metric: 'With Task Context', value: `${(descriptionCoverage * 100).toFixed(1)}%` },
|
|
1249
1277
|
{ metric: 'Avg Confidence', value: `${(avgConfidence * 100).toFixed(1)}%` }
|
|
1250
1278
|
]
|
|
1251
1279
|
});
|
|
@@ -1258,7 +1286,14 @@ const metricsCommand = {
|
|
|
1258
1286
|
{ key: 'value', header: 'Value', width: 20, align: 'right' }
|
|
1259
1287
|
],
|
|
1260
1288
|
data: [
|
|
1261
|
-
{
|
|
1289
|
+
{
|
|
1290
|
+
metric: routingAccuracy === null ? 'Avg Confidence' : 'Routing Accuracy',
|
|
1291
|
+
value: `${((routingAccuracy ?? averageRoutingConfidence) * 100).toFixed(1)}%`,
|
|
1292
|
+
},
|
|
1293
|
+
...(routingOutcomeSuccessRate === null ? [] : [{
|
|
1294
|
+
metric: 'Outcome Success Rate',
|
|
1295
|
+
value: `${(routingOutcomeSuccessRate * 100).toFixed(1)}%`,
|
|
1296
|
+
}]),
|
|
1262
1297
|
{ metric: 'Total Routes', value: totalRoutes },
|
|
1263
1298
|
{ metric: 'Top Agent', value: output.highlight(topAgent) }
|
|
1264
1299
|
]
|
|
@@ -492,7 +492,7 @@ function isInitialized(cwd) {
|
|
|
492
492
|
};
|
|
493
493
|
}
|
|
494
494
|
// Init subcommand (default)
|
|
495
|
-
const
|
|
495
|
+
const initClaudeAction = async (ctx) => {
|
|
496
496
|
const force = ctx.flags.force;
|
|
497
497
|
const minimal = ctx.flags.minimal;
|
|
498
498
|
const full = ctx.flags.full;
|
|
@@ -507,13 +507,7 @@ const initAction = async (ctx) => {
|
|
|
507
507
|
const noGlobal = ctx.flags['no-global'] === true || ctx.flags['global'] === false;
|
|
508
508
|
const allAgents = ctx.flags['all-agents'];
|
|
509
509
|
const cloudMcp = ctx.flags['cloud-mcp'];
|
|
510
|
-
const codexMode = ctx.flags.codex;
|
|
511
|
-
const dualMode = ctx.flags.dual;
|
|
512
510
|
const cwd = ctx.cwd;
|
|
513
|
-
// If codex mode, use the Codex initializer
|
|
514
|
-
if (codexMode || dualMode) {
|
|
515
|
-
return initCodexAction(ctx, { codexMode, dualMode, force, minimal, full });
|
|
516
|
-
}
|
|
517
511
|
// Check if already initialized
|
|
518
512
|
const initialized = isInitialized(cwd);
|
|
519
513
|
const hasExisting = initialized.claude || initialized.claudeFlow;
|
|
@@ -786,6 +780,56 @@ const initAction = async (ctx) => {
|
|
|
786
780
|
return { success: false, exitCode: 1 };
|
|
787
781
|
}
|
|
788
782
|
};
|
|
783
|
+
/**
|
|
784
|
+
* Route platform initialization. Dual mode deliberately runs both native
|
|
785
|
+
* initializers: Claude Code first, then Codex without its compatibility
|
|
786
|
+
* CLAUDE.md stub so the full native Claude scaffold remains authoritative.
|
|
787
|
+
*/
|
|
788
|
+
const initAction = async (ctx) => {
|
|
789
|
+
const force = ctx.flags.force;
|
|
790
|
+
const minimal = ctx.flags.minimal;
|
|
791
|
+
const full = ctx.flags.full;
|
|
792
|
+
const codexMode = ctx.flags.codex;
|
|
793
|
+
const dualMode = ctx.flags.dual;
|
|
794
|
+
if (codexMode && !dualMode) {
|
|
795
|
+
return initCodexAction(ctx, { codexMode, dualMode: false, force, minimal, full });
|
|
796
|
+
}
|
|
797
|
+
if (!dualMode) {
|
|
798
|
+
return initClaudeAction(ctx);
|
|
799
|
+
}
|
|
800
|
+
const claudeContext = {
|
|
801
|
+
...ctx,
|
|
802
|
+
flags: {
|
|
803
|
+
...ctx.flags,
|
|
804
|
+
codex: false,
|
|
805
|
+
dual: false,
|
|
806
|
+
// The explicit Codex pass below owns Codex setup.
|
|
807
|
+
'no-codex-detect': true,
|
|
808
|
+
},
|
|
809
|
+
};
|
|
810
|
+
const claudeResult = await initClaudeAction(claudeContext);
|
|
811
|
+
if (!claudeResult.success)
|
|
812
|
+
return claudeResult;
|
|
813
|
+
const codexResult = await initCodexAction(ctx, {
|
|
814
|
+
codexMode: true,
|
|
815
|
+
// Preserve the full CLAUDE.md and .claude/ scaffold just generated.
|
|
816
|
+
dualMode: false,
|
|
817
|
+
force,
|
|
818
|
+
minimal,
|
|
819
|
+
full,
|
|
820
|
+
});
|
|
821
|
+
if (!codexResult.success) {
|
|
822
|
+
return {
|
|
823
|
+
...codexResult,
|
|
824
|
+
message: 'Claude Code initialized, but Codex initialization failed',
|
|
825
|
+
data: { claude: claudeResult.data, codex: codexResult.data },
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
success: true,
|
|
830
|
+
data: { claude: claudeResult.data, codex: codexResult.data },
|
|
831
|
+
};
|
|
832
|
+
};
|
|
789
833
|
// Wizard subcommand for interactive setup
|
|
790
834
|
const wizardCommand = {
|
|
791
835
|
name: 'wizard',
|
|
@@ -1453,7 +1497,7 @@ export const initCommand = {
|
|
|
1453
1497
|
{ command: 'claude-flow init upgrade --settings', description: 'Update helpers and merge new settings (Agent Teams)' },
|
|
1454
1498
|
{ command: 'claude-flow init upgrade --verbose', description: 'Show detailed upgrade info' },
|
|
1455
1499
|
{ command: 'claude-flow init --codex', description: 'Initialize for OpenAI Codex (AGENTS.md)' },
|
|
1456
|
-
{ command: 'claude-flow init --codex --full', description: 'Codex init with all
|
|
1500
|
+
{ command: 'claude-flow init --codex --full', description: 'Codex init with all canonical packaged skills' },
|
|
1457
1501
|
{ command: 'claude-flow init --dual', description: 'Initialize for both Claude Code and Codex' },
|
|
1458
1502
|
{ command: 'claude-flow init --no-codex-detect', description: 'Skip auto-configuring OpenAI Codex even if it is installed' },
|
|
1459
1503
|
{ command: 'claude-flow init --no-skills-sh', description: 'Skip the post-init skills.sh registration' },
|
|
@@ -182,6 +182,7 @@ const runCommand = {
|
|
|
182
182
|
data: [
|
|
183
183
|
{ metric: 'Processed', value: String(report.processed) },
|
|
184
184
|
{ metric: 'Episodes', value: String(report.episodes) },
|
|
185
|
+
{ metric: 'Episode Embeddings', value: String(report.episodeEmbeddings) },
|
|
185
186
|
{ metric: 'Patterns', value: String(report.patterns) },
|
|
186
187
|
{ metric: 'Pattern Embeddings', value: String(report.patternEmbeddings) },
|
|
187
188
|
{ metric: 'Causal Edges', value: String(report.causalEdges) },
|
|
@@ -50,6 +50,34 @@ function getSwarmStatus(swarmId) {
|
|
|
50
50
|
// Ignore
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
// The canonical agent registry is the same source used by `agent list`.
|
|
54
|
+
// Prefer it over the swarm-level coordination boolean so idle agents are
|
|
55
|
+
// not reported as active (#2808). Hive agents are merged additively.
|
|
56
|
+
if (totalAgents === 0) {
|
|
57
|
+
try {
|
|
58
|
+
const canonicalPath = path.join(process.cwd(), '.claude-flow', 'agents', 'store.json');
|
|
59
|
+
const hivePath = path.join(process.cwd(), '.claude-flow', 'agents.json');
|
|
60
|
+
const merged = {};
|
|
61
|
+
for (const storePath of [hivePath, canonicalPath]) {
|
|
62
|
+
if (!fs.existsSync(storePath))
|
|
63
|
+
continue;
|
|
64
|
+
const parsed = JSON.parse(fs.readFileSync(storePath, 'utf-8'));
|
|
65
|
+
if (parsed?.agents && typeof parsed.agents === 'object') {
|
|
66
|
+
Object.assign(merged, parsed.agents);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const agents = Object.values(merged).filter(agent => agent.status !== 'terminated');
|
|
70
|
+
if (agents.length > 0) {
|
|
71
|
+
totalAgents = agents.length;
|
|
72
|
+
activeAgents = agents.filter(agent => agent.status === 'active' ||
|
|
73
|
+
agent.status === 'running' ||
|
|
74
|
+
agent.status === 'busy').length;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Ignore — the count-only activity file remains the final fallback.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
53
81
|
// #2799 — `agent spawn` never writes `.swarm/agents/*.json`; it records
|
|
54
82
|
// the count in `.claude-flow/metrics/swarm-activity.json` (via
|
|
55
83
|
// updateSwarmActivityMetrics in commands/agent.ts). So when the agents
|
|
@@ -370,18 +370,19 @@ function mergeSettingsForUpgrade(existing) {
|
|
|
370
370
|
// js/redos): a crafted settings.json command string with dozens of
|
|
371
371
|
// dash-token repetitions can hang this check for minutes.
|
|
372
372
|
const BROKEN_STATUSLINE_RE = /(?:npx\s+(?:--?\S+\s+){0,10})?@?claude-flow(?:\/cli)?(?:@\S+)?\s+hooks\s+statusline/;
|
|
373
|
+
const BROKEN_NPX_LATEST_RE = /npx\s+(?:--?\S+\s+){0,10}@?claude-flow\/cli@latest\s+\S+/;
|
|
373
374
|
const existingStatusLine = existing.statusLine;
|
|
374
375
|
if (existingStatusLine) {
|
|
375
376
|
const existingCmd = typeof existingStatusLine.command === 'string' ? existingStatusLine.command : '';
|
|
376
|
-
const isBroken = BROKEN_STATUSLINE_RE.test(existingCmd);
|
|
377
|
+
const isBroken = BROKEN_STATUSLINE_RE.test(existingCmd) || BROKEN_NPX_LATEST_RE.test(existingCmd);
|
|
377
378
|
merged.statusLine = {
|
|
378
379
|
type: 'command',
|
|
379
380
|
command: isBroken || !existingCmd ? NEW_STATUSLINE_CMD : existingCmd,
|
|
380
381
|
// Remove invalid fields: refreshMs, enabled (not supported by Claude Code)
|
|
381
382
|
};
|
|
382
383
|
}
|
|
383
|
-
// #2448 — Detect and REGENERATE per-action hook commands that still
|
|
384
|
-
// the runaway `npx @claude-flow/cli@latest hooks <sub>` form. These fire
|
|
384
|
+
// #2448 / #2677 — Detect and REGENERATE per-action hook commands that still
|
|
385
|
+
// use the runaway `npx @claude-flow/cli@latest hooks <sub>` form. These fire
|
|
385
386
|
// on every PreToolUse/PostToolUse/UserPromptSubmit, each spawning ~130 MB
|
|
386
387
|
// of cold Node + npm registry resolution; the storm is what kernel-paniced
|
|
387
388
|
// the reporter's machine in #2448.
|
|
@@ -409,15 +410,20 @@ function mergeSettingsForUpgrade(existing) {
|
|
|
409
410
|
for (const group of groups) {
|
|
410
411
|
if (!Array.isArray(group.hooks))
|
|
411
412
|
continue;
|
|
412
|
-
|
|
413
|
+
group.hooks = group.hooks.filter((h) => {
|
|
413
414
|
if (typeof h?.command !== 'string')
|
|
414
|
-
|
|
415
|
+
return true;
|
|
415
416
|
const m = BROKEN_HOOK_RE.exec(h.command);
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
417
|
+
if (m) {
|
|
418
|
+
// Subcommand captured (e.g. "pre-bash", "post-edit", "route") — keep it.
|
|
419
|
+
h.command = localHookCmd(m[1]);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
// Sibling CLI invocations (memory/daemon/swarm/...) have no
|
|
423
|
+
// hook-handler equivalent. Drop the stale extra; the merge above
|
|
424
|
+
// has already installed current local-helper hooks for this event.
|
|
425
|
+
return !BROKEN_NPX_LATEST_RE.test(h.command);
|
|
426
|
+
});
|
|
421
427
|
}
|
|
422
428
|
}
|
|
423
429
|
}
|
|
@@ -1373,6 +1379,7 @@ async function writeStatusline(targetDir, options, result) {
|
|
|
1373
1379
|
* Write runtime configuration (.claude-flow/)
|
|
1374
1380
|
*/
|
|
1375
1381
|
async function writeRuntimeConfig(targetDir, options, result) {
|
|
1382
|
+
updateRootGitignore(targetDir, result);
|
|
1376
1383
|
const configPath = path.join(targetDir, '.claude-flow', 'config.yaml');
|
|
1377
1384
|
if (fs.existsSync(configPath) && !options.force) {
|
|
1378
1385
|
result.skipped.push('.claude-flow/config.yaml');
|
|
@@ -1441,6 +1448,34 @@ neural/
|
|
|
1441
1448
|
// Write CAPABILITIES.md with full system overview
|
|
1442
1449
|
await writeCapabilitiesDoc(targetDir, options, result);
|
|
1443
1450
|
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Protect project-local secrets and runtime state for Claude-only installs.
|
|
1453
|
+
* Append missing entries without replacing the project's existing rules.
|
|
1454
|
+
*/
|
|
1455
|
+
function updateRootGitignore(targetDir, result) {
|
|
1456
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
1457
|
+
const entries = [
|
|
1458
|
+
'# Ruflo local secrets and runtime data',
|
|
1459
|
+
'.env',
|
|
1460
|
+
'.env.local',
|
|
1461
|
+
'.env.*.local',
|
|
1462
|
+
'.claude-flow/data/',
|
|
1463
|
+
'.claude-flow/logs/',
|
|
1464
|
+
'.claude-flow/sessions/',
|
|
1465
|
+
];
|
|
1466
|
+
const existing = fs.existsSync(gitignorePath)
|
|
1467
|
+
? fs.readFileSync(gitignorePath, 'utf8')
|
|
1468
|
+
: '';
|
|
1469
|
+
const existingLines = new Set(existing.split(/\r?\n/));
|
|
1470
|
+
const missing = entries.filter((entry) => !existingLines.has(entry));
|
|
1471
|
+
if (missing.length === 0)
|
|
1472
|
+
return;
|
|
1473
|
+
const separator = existing.length === 0
|
|
1474
|
+
? ''
|
|
1475
|
+
: existing.endsWith('\n') ? '\n' : '\n\n';
|
|
1476
|
+
fs.writeFileSync(gitignorePath, `${existing}${separator}${missing.join('\n')}\n`, 'utf8');
|
|
1477
|
+
result.created.files.push('.gitignore (updated)');
|
|
1478
|
+
}
|
|
1444
1479
|
/**
|
|
1445
1480
|
* Write initial metrics files for statusline
|
|
1446
1481
|
* Creates baseline data so statusline shows meaningful state instead of all zeros
|
|
@@ -775,11 +775,22 @@ export function generateIntelligenceStub() {
|
|
|
775
775
|
"const path = require('path');",
|
|
776
776
|
"const os = require('os');",
|
|
777
777
|
'',
|
|
778
|
-
|
|
778
|
+
'function resolveProjectRoot(startDir) {',
|
|
779
|
+
' if (process.env.CLAUDE_PROJECT_DIR) return path.resolve(process.env.CLAUDE_PROJECT_DIR);',
|
|
780
|
+
' var dir = path.resolve(startDir || process.cwd());',
|
|
781
|
+
' while (true) {',
|
|
782
|
+
' if (fs.existsSync(path.join(dir, ".git")) || fs.existsSync(path.join(dir, ".claude-flow"))) return dir;',
|
|
783
|
+
' var parent = path.dirname(dir);',
|
|
784
|
+
' if (parent === dir) return path.resolve(startDir || process.cwd());',
|
|
785
|
+
' dir = parent;',
|
|
786
|
+
' }',
|
|
787
|
+
'}',
|
|
788
|
+
"const PROJECT_ROOT = resolveProjectRoot(process.cwd());",
|
|
789
|
+
"const DATA_DIR = path.join(PROJECT_ROOT, '.claude-flow', 'data');",
|
|
779
790
|
"const STORE_PATH = path.join(DATA_DIR, 'auto-memory-store.json');",
|
|
780
791
|
"const RANKED_PATH = path.join(DATA_DIR, 'ranked-context.json');",
|
|
781
792
|
"const PENDING_PATH = path.join(DATA_DIR, 'pending-insights.jsonl');",
|
|
782
|
-
"const SESSION_DIR = path.join(
|
|
793
|
+
"const SESSION_DIR = path.join(PROJECT_ROOT, '.claude-flow', 'sessions');",
|
|
783
794
|
"const SESSION_FILE = path.join(SESSION_DIR, 'current.json');",
|
|
784
795
|
'',
|
|
785
796
|
'function ensureDir(dir) {',
|
|
@@ -823,8 +834,8 @@ export function generateIntelligenceStub() {
|
|
|
823
834
|
' var entries = [];',
|
|
824
835
|
' var candidates = [',
|
|
825
836
|
' path.join(os.homedir(), ".claude", "projects"),',
|
|
826
|
-
' path.join(
|
|
827
|
-
' path.join(
|
|
837
|
+
' path.join(PROJECT_ROOT, ".claude-flow", "memory"),',
|
|
838
|
+
' path.join(PROJECT_ROOT, ".claude", "memory"),',
|
|
828
839
|
' ];',
|
|
829
840
|
' for (var i = 0; i < candidates.length; i++) {',
|
|
830
841
|
' try {',
|
|
@@ -53,6 +53,16 @@ function getInstalledCliVersionLocal() {
|
|
|
53
53
|
return '0.0.0';
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
function compareVersionsLocal(a, b) {
|
|
57
|
+
const pa = a.split(/[.-]/).map(part => Number.parseInt(part, 10) || 0);
|
|
58
|
+
const pb = b.split(/[.-]/).map(part => Number.parseInt(part, 10) || 0);
|
|
59
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
60
|
+
const delta = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
61
|
+
if (delta !== 0)
|
|
62
|
+
return delta;
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
56
66
|
/**
|
|
57
67
|
* Generate optimized statusline script
|
|
58
68
|
* Output format:
|
|
@@ -91,10 +101,12 @@ export function generateStatuslineScript(options) {
|
|
|
91
101
|
// getInstalledCliVersionLocal() above — reuse the pattern for the
|
|
92
102
|
// same reason it exists there.
|
|
93
103
|
let helperContent = null;
|
|
104
|
+
let helperPackageRoot = '';
|
|
94
105
|
try {
|
|
95
106
|
const esmRequire = createRequire(import.meta.url);
|
|
96
107
|
const pkgJsonPath = esmRequire.resolve('@claude-flow/cli/package.json');
|
|
97
|
-
|
|
108
|
+
helperPackageRoot = path.dirname(pkgJsonPath);
|
|
109
|
+
const helperPath = path.join(helperPackageRoot, '.claude', 'helpers', 'statusline.cjs');
|
|
98
110
|
helperContent = fs.readFileSync(helperPath, 'utf-8');
|
|
99
111
|
}
|
|
100
112
|
catch {
|
|
@@ -105,6 +117,7 @@ export function generateStatuslineScript(options) {
|
|
|
105
117
|
if (pkg && pkg.name === '@claude-flow/cli') {
|
|
106
118
|
const candidate = path.join(dir, '.claude', 'helpers', 'statusline.cjs');
|
|
107
119
|
if (fs.existsSync(candidate)) {
|
|
120
|
+
helperPackageRoot = dir;
|
|
108
121
|
helperContent = fs.readFileSync(candidate, 'utf-8');
|
|
109
122
|
break;
|
|
110
123
|
}
|
|
@@ -128,15 +141,15 @@ export function generateStatuslineScript(options) {
|
|
|
128
141
|
// whatever the helper hard-codes) ships. Add a paired test in
|
|
129
142
|
// statusline-cost-display.test.ts before changing either token.
|
|
130
143
|
helperContent = helperContent.replace(/maxAgents: \d+,/, `maxAgents: ${maxAgents},`);
|
|
144
|
+
helperContent = helperContent.replace(/const BAKED_INSTALL_ROOT = "[^"]*";/, `const BAKED_INSTALL_ROOT = ${JSON.stringify(helperPackageRoot)};`);
|
|
131
145
|
// Only overwrite the helper's baked version if OURS resolves higher.
|
|
132
146
|
// Otherwise the substitution could DOWNGRADE (test environments where
|
|
133
147
|
// esmRequire.resolve happens to hit an older node_modules install would
|
|
134
|
-
// clobber a fresh committed helper).
|
|
135
|
-
//
|
|
136
|
-
// reliable at this stage of the version space.
|
|
148
|
+
// clobber a fresh committed helper). Compare numeric components:
|
|
149
|
+
// lexicographic comparison freezes 3.32.24 below 3.32.8 (#2811).
|
|
137
150
|
const helperVerMatch = helperContent.match(/let ver = "([^"]+)";/);
|
|
138
151
|
const helperVer = helperVerMatch ? helperVerMatch[1] : '';
|
|
139
|
-
if (!helperVer || bakedVersion >
|
|
152
|
+
if (!helperVer || compareVersionsLocal(bakedVersion, helperVer) > 0) {
|
|
140
153
|
helperContent = helperContent.replace(/let ver = "[^"]+";/, `let ver = ${JSON.stringify(bakedVersion)};`);
|
|
141
154
|
}
|
|
142
155
|
return helperContent;
|
|
@@ -48,6 +48,31 @@ export interface MCPServerStatus {
|
|
|
48
48
|
metrics?: Record<string, number>;
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
export declare function parseMcpToolSelection(value: string | undefined): string[] | 'all';
|
|
52
|
+
/**
|
|
53
|
+
* Apply the existing `--tools` contract to advertised schemas. A selector can
|
|
54
|
+
* be an exact tool name, a category, or a namespace prefix (`memory` matches
|
|
55
|
+
* `memory_store`). Execution remains registered internally; only the fixed
|
|
56
|
+
* per-request schema catalogue is reduced.
|
|
57
|
+
*/
|
|
58
|
+
export declare function filterAdvertisedMcpTools<T extends {
|
|
59
|
+
name: string;
|
|
60
|
+
category?: string;
|
|
61
|
+
}>(tools: T[], selection: string[] | 'all'): T[];
|
|
62
|
+
export interface McpSchemaOverhead {
|
|
63
|
+
toolCount: number;
|
|
64
|
+
bytes: number;
|
|
65
|
+
estimatedTokens: number;
|
|
66
|
+
contextWindowTokens?: number;
|
|
67
|
+
ratio?: number;
|
|
68
|
+
risk: 'normal' | 'high';
|
|
69
|
+
}
|
|
70
|
+
/** Conservative JSON-size estimate for the fixed tools/list catalogue. */
|
|
71
|
+
export declare function assessMcpSchemaOverhead(tools: Array<{
|
|
72
|
+
name: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
inputSchema?: unknown;
|
|
75
|
+
}>, contextWindowTokens?: number): McpSchemaOverhead;
|
|
51
76
|
/**
|
|
52
77
|
* MCP Server Manager
|
|
53
78
|
*
|
|
@@ -42,6 +42,54 @@ const DEFAULT_OPTIONS = {
|
|
|
42
42
|
daemonize: false,
|
|
43
43
|
timeout: 30000,
|
|
44
44
|
};
|
|
45
|
+
export function parseMcpToolSelection(value) {
|
|
46
|
+
if (!value || value.trim().toLowerCase() === 'all')
|
|
47
|
+
return 'all';
|
|
48
|
+
const selectors = value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
49
|
+
return selectors.length > 0 ? selectors : 'all';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Apply the existing `--tools` contract to advertised schemas. A selector can
|
|
53
|
+
* be an exact tool name, a category, or a namespace prefix (`memory` matches
|
|
54
|
+
* `memory_store`). Execution remains registered internally; only the fixed
|
|
55
|
+
* per-request schema catalogue is reduced.
|
|
56
|
+
*/
|
|
57
|
+
export function filterAdvertisedMcpTools(tools, selection) {
|
|
58
|
+
if (selection === 'all')
|
|
59
|
+
return tools;
|
|
60
|
+
const selectors = new Set(selection.map((item) => item.toLowerCase()));
|
|
61
|
+
return tools.filter((tool) => {
|
|
62
|
+
const name = tool.name.toLowerCase();
|
|
63
|
+
const category = tool.category?.toLowerCase();
|
|
64
|
+
return selectors.has(name)
|
|
65
|
+
|| (category !== undefined && selectors.has(category))
|
|
66
|
+
|| Array.from(selectors).some((selector) => name.startsWith(`${selector}_`));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** Conservative JSON-size estimate for the fixed tools/list catalogue. */
|
|
70
|
+
export function assessMcpSchemaOverhead(tools, contextWindowTokens) {
|
|
71
|
+
const catalogue = tools.map((tool) => ({
|
|
72
|
+
name: tool.name,
|
|
73
|
+
description: tool.description,
|
|
74
|
+
inputSchema: tool.inputSchema,
|
|
75
|
+
}));
|
|
76
|
+
const bytes = Buffer.byteLength(JSON.stringify(catalogue), 'utf8');
|
|
77
|
+
const estimatedTokens = Math.ceil(bytes / 4);
|
|
78
|
+
const validWindow = Number.isFinite(contextWindowTokens) && Number(contextWindowTokens) > 0
|
|
79
|
+
? Number(contextWindowTokens)
|
|
80
|
+
: undefined;
|
|
81
|
+
const ratio = validWindow ? estimatedTokens / validWindow : undefined;
|
|
82
|
+
return {
|
|
83
|
+
toolCount: tools.length,
|
|
84
|
+
bytes,
|
|
85
|
+
estimatedTokens,
|
|
86
|
+
...(validWindow === undefined ? {} : { contextWindowTokens: validWindow }),
|
|
87
|
+
...(ratio === undefined ? {} : { ratio }),
|
|
88
|
+
risk: (ratio !== undefined ? ratio >= 0.2 : estimatedTokens >= 8_000)
|
|
89
|
+
? 'high'
|
|
90
|
+
: 'normal',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
45
93
|
/**
|
|
46
94
|
* MCP Server Manager
|
|
47
95
|
*
|
|
@@ -55,7 +103,14 @@ export class MCPServerManager extends EventEmitter {
|
|
|
55
103
|
healthCheckInterval;
|
|
56
104
|
constructor(options = {}) {
|
|
57
105
|
super();
|
|
58
|
-
|
|
106
|
+
// `options.tools`, populated by the `mcp start --tools` CLI flag, is
|
|
107
|
+
// spread last below and therefore takes precedence over this env fallback.
|
|
108
|
+
const environmentTools = parseMcpToolSelection(process.env.CLAUDE_FLOW_MCP_TOOLS);
|
|
109
|
+
this.options = {
|
|
110
|
+
...DEFAULT_OPTIONS,
|
|
111
|
+
...(environmentTools === 'all' ? {} : { tools: environmentTools }),
|
|
112
|
+
...options,
|
|
113
|
+
};
|
|
59
114
|
}
|
|
60
115
|
/**
|
|
61
116
|
* Start the MCP server
|
|
@@ -420,7 +475,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
420
475
|
},
|
|
421
476
|
};
|
|
422
477
|
case 'tools/list':
|
|
423
|
-
const tools = listMCPTools();
|
|
478
|
+
const tools = filterAdvertisedMcpTools(listMCPTools(), this.options.tools);
|
|
424
479
|
return {
|
|
425
480
|
jsonrpc: '2.0',
|
|
426
481
|
id: message.id,
|