@claude-flow/cli 3.71.3 → 3.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +17 -0
  2. package/.claude/helpers/helpers.manifest.json +6 -6
  3. package/.claude/helpers/hook-handler.cjs +23 -10
  4. package/.claude/helpers/intelligence.cjs +23 -1
  5. package/.claude/helpers/statusline.cjs +29 -14
  6. package/README.md +18 -3
  7. package/bin/cli.js +13 -18
  8. package/bin/mcp-server.js +13 -18
  9. package/catalog-manifest.json +2 -2
  10. package/dist/src/appliance/rvfa-distribution.js +2 -2
  11. package/dist/src/benchmarks/gaia-critic.d.ts +1 -7
  12. package/dist/src/benchmarks/gaia-critic.js +1 -7
  13. package/dist/src/benchmarks/gaia-decomposer.d.ts +1 -8
  14. package/dist/src/benchmarks/gaia-decomposer.js +1 -8
  15. package/dist/src/commands/agent-wasm.js +2 -2
  16. package/dist/src/commands/analyze.js +1 -1
  17. package/dist/src/commands/daemon.js +1 -1
  18. package/dist/src/commands/doctor.js +3 -2
  19. package/dist/src/commands/guidance.js +9 -9
  20. package/dist/src/commands/hooks.js +13 -8
  21. package/dist/src/commands/init.js +14 -16
  22. package/dist/src/commands/memory.js +4 -3
  23. package/dist/src/commands/neural.js +8 -6
  24. package/dist/src/commands/route.js +1 -1
  25. package/dist/src/commands/ruvector/backup.js +7 -5
  26. package/dist/src/commands/ruvector/benchmark.js +6 -1
  27. package/dist/src/commands/ruvector/import.js +19 -5
  28. package/dist/src/commands/ruvector/init.js +1 -1
  29. package/dist/src/commands/start.js +1 -1
  30. package/dist/src/commands/status.js +1 -1
  31. package/dist/src/commands/task.js +18 -1
  32. package/dist/src/commands/update.js +1 -1
  33. package/dist/src/funnel/local-signals.js +7 -2
  34. package/dist/src/init/helpers-generator.js +16 -1
  35. package/dist/src/mcp-protocol.d.ts +90 -0
  36. package/dist/src/mcp-protocol.js +179 -0
  37. package/dist/src/mcp-server.d.ts +1 -0
  38. package/dist/src/mcp-server.js +20 -22
  39. package/dist/src/mcp-tools/agent-execute-core.js +9 -1
  40. package/dist/src/mcp-tools/agent-tools.d.ts +13 -0
  41. package/dist/src/mcp-tools/agent-tools.js +63 -12
  42. package/dist/src/mcp-tools/agentbbs-federation.d.ts +18 -1
  43. package/dist/src/mcp-tools/agentbbs-federation.js +23 -7
  44. package/dist/src/mcp-tools/browser-argv.d.ts +27 -0
  45. package/dist/src/mcp-tools/browser-argv.js +92 -0
  46. package/dist/src/mcp-tools/browser-session-tools.js +4 -0
  47. package/dist/src/mcp-tools/browser-tools.d.ts +1 -1
  48. package/dist/src/mcp-tools/browser-tools.js +25 -8
  49. package/dist/src/mcp-tools/coordination-tools.js +5 -4
  50. package/dist/src/mcp-tools/embeddings-tools.js +12 -4
  51. package/dist/src/mcp-tools/glob-exclusions.d.ts +6 -0
  52. package/dist/src/mcp-tools/glob-exclusions.js +81 -0
  53. package/dist/src/mcp-tools/hive-mind-tools.js +104 -35
  54. package/dist/src/mcp-tools/hooks-tools.js +12 -5
  55. package/dist/src/mcp-tools/import-limits.d.ts +4 -0
  56. package/dist/src/mcp-tools/import-limits.js +28 -0
  57. package/dist/src/mcp-tools/memory-tools.js +68 -72
  58. package/dist/src/mcp-tools/neural-tools.js +8 -2
  59. package/dist/src/mcp-tools/path-containment.d.ts +34 -0
  60. package/dist/src/mcp-tools/path-containment.js +133 -0
  61. package/dist/src/mcp-tools/performance-tools.js +7 -2
  62. package/dist/src/mcp-tools/record-store.d.ts +15 -0
  63. package/dist/src/mcp-tools/record-store.js +117 -3
  64. package/dist/src/mcp-tools/security-tools.d.ts +9 -0
  65. package/dist/src/mcp-tools/security-tools.js +39 -3
  66. package/dist/src/mcp-tools/session-tools.js +9 -41
  67. package/dist/src/mcp-tools/swarm-tools.js +36 -14
  68. package/dist/src/mcp-tools/task-tools.js +68 -5
  69. package/dist/src/mcp-tools/terminal-tools.js +16 -2
  70. package/dist/src/mcp-tools/validate-input.d.ts +2 -0
  71. package/dist/src/mcp-tools/validate-input.js +14 -0
  72. package/dist/src/mcp-tools/workflow-tools.js +13 -12
  73. package/dist/src/mcp-tools/x-federation-join.js +55 -14
  74. package/dist/src/memory/intelligence.d.ts +11 -0
  75. package/dist/src/memory/intelligence.js +84 -6
  76. package/dist/src/memory/memory-initializer.d.ts +6 -2
  77. package/dist/src/memory/memory-initializer.js +15 -6
  78. package/dist/src/ruvector/model-router.d.ts +6 -1
  79. package/dist/src/ruvector/model-router.js +16 -4
  80. package/dist/src/services/worker-daemon.js +30 -12
  81. package/dist/src/transfer/storage/gcs.js +3 -2
  82. package/package.json +1 -1
  83. package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -0
@@ -146,7 +146,7 @@ export const wasmCreateCommand = {
146
146
  info = await wasm.createWasmAgent({
147
147
  model: ctx.flags.model,
148
148
  instructions: ctx.flags.instructions,
149
- maxTurns: ctx.flags['max-turns'],
149
+ maxTurns: ctx.flags.maxTurns,
150
150
  });
151
151
  }
152
152
  if (ctx.flags.format === 'json') {
@@ -201,7 +201,7 @@ export const wasmPromptCommand = {
201
201
  { command: 'claude-flow agent wasm-prompt -a wasm-agent-1-abc -i "Write a hello world"', description: 'Send prompt to WASM agent' },
202
202
  ],
203
203
  action: async (ctx) => {
204
- const agentId = ctx.flags['agent-id'] || ctx.args[0];
204
+ const agentId = ctx.flags.agentId || ctx.args[0];
205
205
  const promptInput = ctx.flags.input || ctx.args[1];
206
206
  if (!agentId) {
207
207
  output.printError('Agent ID is required. Use --agent-id or -a');
@@ -1556,7 +1556,7 @@ const modulesCommand = {
1556
1556
  const targetDir = ctx.args[0] || ctx.cwd;
1557
1557
  const outputFile = ctx.flags.output;
1558
1558
  const format = ctx.flags.format || 'text';
1559
- const minSize = ctx.flags['min-size'] || 2;
1559
+ const minSize = ctx.flags.minSize || 2;
1560
1560
  output.printInfo(`Detecting module communities in: ${output.highlight(targetDir)}`);
1561
1561
  output.writeln();
1562
1562
  const spinner = output.createSpinner({ text: 'Building dependency graph...', spinner: 'dots' });
@@ -1247,7 +1247,7 @@ const statusCommand = {
1247
1247
  ],
1248
1248
  action: async (ctx) => {
1249
1249
  const verbose = ctx.flags.verbose;
1250
- const showModes = ctx.flags['show-modes'];
1250
+ const showModes = ctx.flags.showModes;
1251
1251
  // #2356: global view across every workspace, not just cwd.
1252
1252
  if (ctx.flags.all) {
1253
1253
  return renderAllDaemonsStatus();
@@ -7,6 +7,7 @@
7
7
  import { output } from '../output.js';
8
8
  import { existsSync, readFileSync, statSync, openSync, readSync, closeSync } from 'fs';
9
9
  import { join, dirname } from 'path';
10
+ import { homedir } from 'node:os';
10
11
  import { fileURLToPath, pathToFileURL } from 'url';
11
12
  import { createHash } from 'crypto';
12
13
  import { ownInstallAncestors } from '../plugins/own-install.js';
@@ -182,7 +183,7 @@ async function checkStaleSettingsNpx() {
182
183
  // Look in both project-local and home-dir settings.
183
184
  const candidates = [
184
185
  join(process.cwd(), '.claude', 'settings.json'),
185
- join(process.env.HOME ?? '', '.claude', 'settings.json'),
186
+ join(homedir(), '.claude', 'settings.json'),
186
187
  ].filter((p, i, a) => p && a.indexOf(p) === i);
187
188
  const offenders = [];
188
189
  for (const settingsPath of candidates) {
@@ -248,7 +249,7 @@ export async function checkHelperIntegrity(opts = {}) {
248
249
  const { verifyInstalledCriticalHelpers } = await import('../init/helper-integrity.js');
249
250
  const { CRITICAL_HELPERS, findPackageHelpersDir } = await import('../init/helper-refresh.js');
250
251
  const envLocked = /^(1|true|on|yes)$/i.test(String(process.env.RUFLO_HELPERS_LOCKED || ''));
251
- const home = opts.homeDir ?? process.env.HOME ?? '';
252
+ const home = opts.homeDir ?? homedir();
252
253
  const dirs = [
253
254
  join(opts.cwd ?? process.cwd(), '.claude', 'helpers'),
254
255
  ...(home ? [join(home, '.claude', 'helpers')] : []),
@@ -34,7 +34,7 @@ const compileCommand = {
34
34
  output.writeln(output.error(`Root guidance file not found: ${rootPath}`));
35
35
  return { success: false, message: `File not found: ${rootPath}` };
36
36
  }
37
- const modExport = ctx.flags['mod-projection'] === true;
37
+ const modExport = (ctx.flags.modProjection ?? ctx.flags['mod-projection']) === true;
38
38
  const checked = modExport ? await (await import('../guidance/mod-sources.js')).readModGuidanceSources({
39
39
  rootPath, localPath, revision: String(ctx.flags.revision || ''),
40
40
  }) : undefined;
@@ -112,7 +112,7 @@ const retrieveCommand = {
112
112
  const task = ctx.flags.task;
113
113
  const rootPath = ctx.flags.root || './CLAUDE.md';
114
114
  const localPath = ctx.flags.local;
115
- const maxShards = parseInt(ctx.flags['max-shards'] || '5', 10);
115
+ const maxShards = parseInt(String(ctx.flags.maxShards ?? ctx.flags['max-shards'] ?? 5), 10);
116
116
  const intentOverride = ctx.flags.intent;
117
117
  const jsonOutput = ctx.flags.json === true;
118
118
  if (!task) {
@@ -338,9 +338,9 @@ const optimizeCommand = {
338
338
  const rootPath = ctx.flags.root || './CLAUDE.md';
339
339
  const localPath = ctx.flags.local;
340
340
  const applyChanges = ctx.flags.apply === true;
341
- const contextSize = (ctx.flags['context-size'] || 'standard');
342
- const targetScore = parseInt(ctx.flags['target-score'] || '90', 10);
343
- const maxIterations = parseInt(ctx.flags['max-iterations'] || '5', 10);
341
+ const contextSize = (ctx.flags.contextSize ?? ctx.flags['context-size'] ?? 'standard');
342
+ const targetScore = parseInt(String(ctx.flags.targetScore ?? ctx.flags['target-score'] ?? 90), 10);
343
+ const maxIterations = parseInt(String(ctx.flags.maxIterations ?? ctx.flags['max-iterations'] ?? 5), 10);
344
344
  const jsonOutput = ctx.flags.json === true;
345
345
  output.writeln();
346
346
  output.writeln(output.bold('Guidance Optimizer'));
@@ -437,10 +437,10 @@ const abTestCommand = {
437
437
  { command: 'claude-flow guidance ab-test --json', description: 'Output full report as JSON' },
438
438
  ],
439
439
  action: async (ctx) => {
440
- const configAPath = ctx.flags['config-a'];
441
- const configBPath = ctx.flags['config-b'] || './CLAUDE.md';
440
+ const configAPath = (ctx.flags.configA ?? ctx.flags['config-a']);
441
+ const configBPath = (ctx.flags.configB ?? ctx.flags['config-b'] ?? './CLAUDE.md');
442
442
  const tasksPath = ctx.flags.tasks;
443
- const workDir = ctx.flags['work-dir'];
443
+ const workDir = (ctx.flags.workDir ?? ctx.flags['work-dir']);
444
444
  const jsonOutput = ctx.flags.json === true;
445
445
  output.writeln();
446
446
  output.writeln(output.bold('A/B Behavioral Benchmark'));
@@ -541,7 +541,7 @@ const modCandidatesCommand = {
541
541
  action: async (ctx) => {
542
542
  try {
543
543
  const { collectModCandidates } = await import('../guidance/mod-projection.js');
544
- const data = await collectModCandidates(String(ctx.flags.directory || '.claude-flow/mods/guidance/observations'), ctx.flags['bundle-id']);
544
+ const data = await collectModCandidates(String(ctx.flags.directory || '.claude-flow/mods/guidance/observations'), (ctx.flags.bundleId ?? ctx.flags['bundle-id']));
545
545
  output.writeln(JSON.stringify(data, null, ctx.flags.json === true ? undefined : 2));
546
546
  return { success: true, data };
547
547
  }
@@ -2529,8 +2529,8 @@ const workerDetectCommand = {
2529
2529
  ],
2530
2530
  action: async (ctx) => {
2531
2531
  const prompt = ctx.flags['prompt'];
2532
- const autoDispatch = ctx.flags['auto-dispatch'];
2533
- const minConfidence = parseFloat(ctx.flags['min-confidence'] || '0.5');
2532
+ const autoDispatch = (ctx.flags.autoDispatch ?? ctx.flags['auto-dispatch']);
2533
+ const minConfidence = parseFloat(String(ctx.flags.minConfidence ?? ctx.flags['min-confidence'] ?? 0.5) || '0.5');
2534
2534
  if (!prompt) {
2535
2535
  output.printError('--prompt is required');
2536
2536
  return { success: false, exitCode: 1 };
@@ -2570,7 +2570,12 @@ const workerDetectCommand = {
2570
2570
  }
2571
2571
  if (result.autoDispatched && result.workerIds) {
2572
2572
  output.writeln();
2573
- output.writeln(output.success('Workers auto-dispatched:'));
2573
+ if (result.autoDispatchStatus === 'synthetic-completed') {
2574
+ output.printWarning(result.note ?? 'No executor ran. Worker records are synthetic placeholders.');
2575
+ }
2576
+ else {
2577
+ output.writeln(output.success('Workers auto-dispatched:'));
2578
+ }
2574
2579
  result.workerIds.forEach(id => {
2575
2580
  output.writeln(` - ${id}`);
2576
2581
  });
@@ -3079,8 +3084,8 @@ const coverageGapsCommand = {
3079
3084
  ],
3080
3085
  action: async (ctx) => {
3081
3086
  const threshold = ctx.flags.threshold ?? 80;
3082
- const groupByAgent = ctx.flags['group-by-agent'] !== false;
3083
- const criticalOnly = ctx.flags['critical-only'] || false;
3087
+ const groupByAgent = (ctx.flags.groupByAgent ?? ctx.flags['group-by-agent']) !== false;
3088
+ const criticalOnly = (ctx.flags.criticalOnly ?? ctx.flags['critical-only']) === true;
3084
3089
  const spinner = output.createSpinner({ text: 'Analyzing project coverage gaps...' });
3085
3090
  spinner.start();
3086
3091
  // Try reading coverage from disk first
@@ -4161,7 +4166,7 @@ const modelRouteCommand = {
4161
4166
  { name: 'task', short: 't', type: 'string', description: 'Task description to route', required: true },
4162
4167
  { name: 'context', short: 'c', type: 'string', description: 'Additional context' },
4163
4168
  { name: 'prefer-cost', type: 'boolean', description: 'Prefer lower cost models' },
4164
- { name: 'prefer-quality', type: 'boolean', description: 'Prefer higher quality models' },
4169
+ { name: 'prefer-speed', type: 'boolean', description: 'Prefer faster models when possible' },
4165
4170
  ],
4166
4171
  examples: [
4167
4172
  { command: 'claude-flow hooks model-route -t "fix typo"', description: 'Route simple task (likely haiku)' },
@@ -4178,8 +4183,8 @@ const modelRouteCommand = {
4178
4183
  const result = await callMCPTool('hooks_model-route', {
4179
4184
  task,
4180
4185
  context: ctx.flags.context,
4181
- preferCost: ctx.flags['prefer-cost'],
4182
- preferQuality: ctx.flags['prefer-quality'],
4186
+ preferCost: ctx.flags.preferCost,
4187
+ preferSpeed: ctx.flags.preferSpeed,
4183
4188
  });
4184
4189
  if (ctx.flags.format === 'json') {
4185
4190
  output.printJson(result);
@@ -816,16 +816,22 @@ const initClaudeAction = async (ctx) => {
816
816
  if (startDaemon || startAll) {
817
817
  output.writeln();
818
818
  output.printInfo('Starting services...');
819
- const { execSync } = await import('child_process');
819
+ const { execFileSync } = await import('child_process');
820
+ // #4770: drop stderr via stdio, not a shell redirect (breaks on cmd.exe).
821
+ // shell on win32 so npx.cmd resolves (#2770); args are fixed literals.
822
+ const runCli = (args) => {
823
+ execFileSync('npx', ['@claude-flow/cli@latest', ...args], {
824
+ stdio: ['ignore', 'pipe', 'ignore'],
825
+ cwd: ctx.cwd,
826
+ timeout: 30000,
827
+ shell: process.platform === 'win32',
828
+ });
829
+ };
820
830
  // Initialize memory database
821
831
  if (startAll) {
822
832
  try {
823
833
  output.writeln(output.dim(' Initializing memory database...'));
824
- execSync('npx @claude-flow/cli@latest memory init 2>/dev/null', {
825
- stdio: 'pipe',
826
- cwd: ctx.cwd,
827
- timeout: 30000
828
- });
834
+ runCli(['memory', 'init']);
829
835
  output.writeln(output.success(' ✓ Memory initialized'));
830
836
  }
831
837
  catch {
@@ -854,11 +860,7 @@ const initClaudeAction = async (ctx) => {
854
860
  if (startDaemon) {
855
861
  try {
856
862
  output.writeln(output.dim(' Starting daemon...'));
857
- execSync('npx @claude-flow/cli@latest daemon start 2>/dev/null', {
858
- stdio: 'pipe',
859
- cwd: ctx.cwd,
860
- timeout: 30000
861
- });
863
+ runCli(['daemon', 'start']);
862
864
  output.writeln(output.success(' ✓ Daemon started'));
863
865
  }
864
866
  catch {
@@ -872,11 +874,7 @@ const initClaudeAction = async (ctx) => {
872
874
  if (startAll) {
873
875
  try {
874
876
  output.writeln(output.dim(' Initializing swarm...'));
875
- execSync('npx @claude-flow/cli@latest swarm init --topology hierarchical 2>/dev/null', {
876
- stdio: 'pipe',
877
- cwd: ctx.cwd,
878
- timeout: 30000
879
- });
877
+ runCli(['swarm', 'init', '--topology', 'hierarchical']);
880
878
  output.writeln(output.success(' ✓ Swarm initialized'));
881
879
  }
882
880
  catch {
@@ -7,6 +7,7 @@ import { DEFAULT_SEARCH_THRESHOLD, nearMisses } from '../memory/search-defaults.
7
7
  import { output } from '../output.js';
8
8
  import { select, confirm, input } from '../prompt.js';
9
9
  import { callMCPTool, MCPClientError } from '../mcp-client.js';
10
+ import { localCliContext } from '../mcp-tools/path-containment.js';
10
11
  import { distillCommand } from './memory-distill.js';
11
12
  import { backupCommand } from './memory-backup.js';
12
13
  import { countSiblingStoreRows } from '../memory/sibling-store.js';
@@ -1086,7 +1087,7 @@ const statsCommand = {
1086
1087
  // Call MCP memory/stats tool for real statistics
1087
1088
  try {
1088
1089
  const dbPath = resolveDbPath(ctx.flags.path);
1089
- const statsResult = await callMCPTool('memory_stats', { dbPath });
1090
+ const statsResult = await callMCPTool('memory_stats', { dbPath }, localCliContext());
1090
1091
  if (statsResult.available === false) {
1091
1092
  output.printError(`Failed to get stats: ${statsResult.error || 'Store unavailable'}`);
1092
1093
  return { success: false, exitCode: 1 };
@@ -1650,7 +1651,7 @@ const exportCommand = {
1650
1651
  namespace: ctx.flags.namespace,
1651
1652
  includeVectors: ctx.flags.includeVectors ?? true,
1652
1653
  dbPath: resolveDbPath(ctx.flags.path),
1653
- });
1654
+ }, localCliContext());
1654
1655
  // memory_export reports failure as { error } rather than throwing; without
1655
1656
  // this check the command printed "Exported to undefined" and then crashed
1656
1657
  // reading result.exported.entries (#4204).
@@ -1722,7 +1723,7 @@ const importCommand = {
1722
1723
  merge: ctx.flags.merge ?? true,
1723
1724
  namespace: ctx.flags.namespace,
1724
1725
  dbPath: resolveDbPath(ctx.flags.path),
1725
- });
1726
+ }, localCliContext());
1726
1727
  // memory_import reports a bad file as { error } rather than throwing, and
1727
1728
  // a partial write as { success: false, error, imported }. Without this the
1728
1729
  // command printed "Imported from undefined" and crashed on result.imported,
@@ -35,8 +35,8 @@ const trainCommand = {
35
35
  action: async (ctx) => {
36
36
  const patternType = (ctx.flags.pattern || ctx.flags.patternType || ctx.flags['pattern-type']) || 'coordination';
37
37
  const epochs = parseInt(ctx.flags.epochs || '50', 10);
38
- const learningRate = parseFloat(ctx.flags['learning-rate'] || '0.01');
39
- const batchSize = parseInt(ctx.flags['batch-size'] || '32', 10);
38
+ const learningRate = parseFloat((ctx.flags.learningRate ?? ctx.flags['learning-rate']) || '0.01');
39
+ const batchSize = parseInt((ctx.flags.batchSize ?? ctx.flags['batch-size']) || '32', 10);
40
40
  const dim = Math.min(parseInt(ctx.flags.dim || '256', 10), 256);
41
41
  // #2549 follow-up — backend routing: 'native' = @ruvector/ruvllm
42
42
  // TrainingPipeline (real epochs/early-stopping/disk checkpoints),
@@ -44,7 +44,7 @@ const trainCommand = {
44
44
  // 'auto' = native when the module resolves, else wasm.
45
45
  const backendFlag = String(ctx.flags.backend || 'auto');
46
46
  // Feature: validation split + resume (native TrainingPipeline leg).
47
- const valSplitRaw = parseFloat(ctx.flags['val-split'] ?? '0.1');
47
+ const valSplitRaw = parseFloat((ctx.flags.valSplit ?? ctx.flags['val-split']) ?? '0.1');
48
48
  const valSplit = Number.isFinite(valSplitRaw) ? Math.max(0, Math.min(1, valSplitRaw)) : 0.1;
49
49
  const resumePath = ctx.flags.resume ? String(ctx.flags.resume) : undefined;
50
50
  // --resume is a native-only capability; refuse the WASM combination up
@@ -1023,7 +1023,7 @@ const exportCommand = {
1023
1023
  const outputFile = ctx.flags.output;
1024
1024
  const pinToIpfs = ctx.flags.ipfs;
1025
1025
  const signExport = ctx.flags.sign !== false;
1026
- const stripPii = ctx.flags['strip-pii'] !== false;
1026
+ const stripPii = (ctx.flags.stripPii ?? ctx.flags['strip-pii']) !== false;
1027
1027
  const customName = ctx.flags.name;
1028
1028
  output.writeln();
1029
1029
  output.writeln(output.bold('Secure Model Export'));
@@ -1703,7 +1703,8 @@ const routerTrainCommand = {
1703
1703
  action: async (ctx) => {
1704
1704
  const corpusPath = ctx.flags.corpus;
1705
1705
  const outPath = ctx.flags.out || './router.krr.json';
1706
- const qualityBar = parseFloat(ctx.flags['quality-bar'] || '0.8') || 0.8;
1706
+ const requestedQualityBar = parseFloat(String(ctx.flags.qualityBar ?? ctx.flags['quality-bar'] ?? 0.8));
1707
+ const qualityBar = Number.isNaN(requestedQualityBar) ? 0.8 : requestedQualityBar;
1707
1708
  // Indirect the optional-dep specifier through a string variable so tsc
1708
1709
  // doesn't statically resolve `@metaharness/router` at build time (TS2307
1709
1710
  // when it isn't installed — #2586 pattern). The dep is optional at
@@ -3464,7 +3465,8 @@ const routerBanditStateCommand = {
3464
3465
  const statePath = ctx.flags.path
3465
3466
  ?? path.resolve(process.cwd(), '.swarm', 'model-router-state.json');
3466
3467
  const fmt = ctx.flags.format || 'table';
3467
- const coldThreshold = parseInt(ctx.flags['cold-threshold'] || '4', 10) || 4;
3468
+ const requestedColdThreshold = parseInt(String(ctx.flags.coldThreshold ?? ctx.flags['cold-threshold'] ?? 4), 10);
3469
+ const coldThreshold = Number.isNaN(requestedColdThreshold) ? 4 : requestedColdThreshold;
3468
3470
  if (!fs.existsSync(statePath)) {
3469
3471
  const msg = `Bandit state file not found at ${statePath}`;
3470
3472
  if (fmt === 'json')
@@ -367,7 +367,7 @@ const feedbackCommand = {
367
367
  const taskDescription = ctx.flags.task;
368
368
  const agentId = ctx.flags.agent;
369
369
  const reward = ctx.flags.reward;
370
- const nextTask = ctx.flags['next-task'];
370
+ const nextTask = ctx.flags.nextTask;
371
371
  if (!taskDescription || !agentId) {
372
372
  output.printError('Task description and agent are required');
373
373
  return { success: false, exitCode: 1 };
@@ -53,10 +53,10 @@ const backupSubcommand = {
53
53
  {
54
54
  name: 'format',
55
55
  short: 'f',
56
- description: 'Output format',
56
+ description: 'Output format (defaults to SQL)',
57
57
  type: 'string',
58
- default: 'sql',
59
- choices: ['sql', 'json', 'csv'],
58
+ default: 'text',
59
+ choices: ['text', 'sql', 'json', 'csv'],
60
60
  },
61
61
  {
62
62
  name: 'compress',
@@ -125,9 +125,11 @@ const backupSubcommand = {
125
125
  const config = getConnectionConfig(ctx);
126
126
  let outputPath = ctx.flags.output;
127
127
  const tablesFilter = ctx.flags.tables;
128
- const format = ctx.flags.format || 'sql';
128
+ // The CLI validates the shared --format flag against its global choices.
129
+ const requestedFormat = ctx.flags.format;
130
+ const format = !requestedFormat || requestedFormat === 'text' ? 'sql' : requestedFormat;
129
131
  const compress = ctx.flags.compress;
130
- const includeIndexes = ctx.flags['include-indexes'] !== false;
132
+ const includeIndexes = (ctx.flags.includeIndexes ?? ctx.flags['include-indexes']) !== false;
131
133
  output.writeln();
132
134
  output.writeln(output.bold('RuVector Backup'));
133
135
  output.writeln(output.dim('='.repeat(60)));
@@ -158,7 +158,8 @@ export const benchmarkCommand = {
158
158
  const topK = parseInt(ctx.flags.k || '10', 10);
159
159
  const metric = ctx.flags.metric || 'cosine';
160
160
  const indexType = ctx.flags.index || 'hnsw';
161
- const batchSize = parseInt(ctx.flags['batch-size'] || '1000', 10);
161
+ const batchSizeFlag = ctx.flags.batchSize ?? ctx.flags['batch-size'] ?? 1000;
162
+ const batchSize = typeof batchSizeFlag === 'string' ? Number(batchSizeFlag) : batchSizeFlag;
162
163
  const cleanup = ctx.flags.cleanup !== false;
163
164
  output.writeln();
164
165
  output.writeln(output.bold('RuVector Performance Benchmark'));
@@ -168,6 +169,10 @@ export const benchmarkCommand = {
168
169
  output.printError('Database name is required. Use --database or -d flag, or set PGDATABASE env.');
169
170
  return { success: false, exitCode: 1 };
170
171
  }
172
+ if (typeof batchSize !== 'number' || !Number.isSafeInteger(batchSize) || batchSize < 1) {
173
+ output.printError('--batch-size must be a positive integer');
174
+ return { success: false, exitCode: 1 };
175
+ }
171
176
  // Show benchmark configuration
172
177
  output.writeln(output.highlight('Benchmark Configuration:'));
173
178
  output.printTable({
@@ -166,15 +166,21 @@ export const importCommand = {
166
166
  ],
167
167
  action: async (ctx) => {
168
168
  const inputFile = ctx.flags.input;
169
- const fromMemory = ctx.flags['from-memory'];
169
+ const fromMemory = (ctx.flags.fromMemory ?? ctx.flags['from-memory']);
170
170
  const outputFile = ctx.flags.output;
171
- const batchSize = ctx.flags['batch-size'] || 100;
171
+ const batchSizeFlag = ctx.flags.batchSize ?? ctx.flags['batch-size'] ?? 100;
172
+ const batchSize = typeof batchSizeFlag === 'string' ? Number(batchSizeFlag) : batchSizeFlag;
172
173
  const containerName = ctx.flags.container || 'ruvector-postgres';
173
174
  const verbose = ctx.flags.verbose;
174
175
  output.writeln();
175
176
  output.writeln(output.bold('RuVector PostgreSQL Import'));
176
177
  output.writeln(output.dim('='.repeat(50)));
177
178
  output.writeln();
179
+ if (typeof batchSize !== 'number' || !Number.isSafeInteger(batchSize) || batchSize < 1) {
180
+ const message = '--batch-size must be a positive integer';
181
+ output.printError(message);
182
+ return { success: false, message };
183
+ }
178
184
  // Validate input
179
185
  if (!inputFile && !fromMemory) {
180
186
  output.printError('Either --input <file> or --from-memory is required');
@@ -248,6 +254,7 @@ export const importCommand = {
248
254
  sqlStatements.push('');
249
255
  sqlStatements.push('BEGIN;');
250
256
  sqlStatements.push('');
257
+ const inserts = [];
251
258
  for (const entry of entries) {
252
259
  try {
253
260
  // Track statistics
@@ -256,9 +263,7 @@ export const importCommand = {
256
263
  if (entry.embedding && entry.embedding.length > 0) {
257
264
  stats.withEmbeddings++;
258
265
  }
259
- const sql = generateInsertSQL(entry);
260
- sqlStatements.push(sql);
261
- sqlStatements.push('');
266
+ inserts.push(generateInsertSQL(entry));
262
267
  stats.imported++;
263
268
  if (verbose) {
264
269
  output.writeln(output.dim(` Processed: ${entry.key} (${ns})`));
@@ -270,6 +275,15 @@ export const importCommand = {
270
275
  output.printWarning(`Skipped entry "${entry.key}": ${errorMessage}`);
271
276
  }
272
277
  }
278
+ // Send each batch as one statement while keeping ordered upserts in the same transaction.
279
+ for (let offset = 0; offset < inserts.length; offset += batchSize) {
280
+ const batch = inserts.slice(offset, offset + batchSize).join('\n\n');
281
+ let tag = 'ruvector_batch';
282
+ // A value containing the delimiter must stay inside the block.
283
+ while (batch.includes(`$${tag}$`))
284
+ tag += '_';
285
+ sqlStatements.push(`DO $${tag}$\nBEGIN\n${batch}\nEND;\n$${tag}$;`, '');
286
+ }
273
287
  sqlStatements.push('COMMIT;');
274
288
  sqlStatements.push('');
275
289
  sqlStatements.push(`-- Import complete: ${stats.imported} entries`);
@@ -145,7 +145,7 @@ export const initCommand = {
145
145
  }
146
146
  const force = ctx.flags.force;
147
147
  const dimensions = parseInt(ctx.flags.dimensions || '1536', 10);
148
- const indexType = ctx.flags['index-type'] || 'hnsw';
148
+ const indexType = (ctx.flags.indexType ?? ctx.flags['index-type']) || 'hnsw';
149
149
  // Show configuration
150
150
  output.writeln(output.highlight('Connection Configuration:'));
151
151
  output.printTable({
@@ -85,7 +85,7 @@ const startAction = async (ctx) => {
85
85
  const daemon = ctx.flags.daemon;
86
86
  const port = ctx.flags.port || DEFAULT_PORT;
87
87
  const topology = ctx.flags.topology || DEFAULT_TOPOLOGY;
88
- const skipMcp = ctx.flags['skip-mcp'];
88
+ const skipMcp = ctx.flags.skipMcp;
89
89
  const cwd = ctx.cwd;
90
90
  // Check initialization
91
91
  if (!isInitialized(cwd)) {
@@ -281,7 +281,7 @@ function formatHealth(health) {
281
281
  const statusAction = async (ctx) => {
282
282
  const watch = ctx.flags.watch;
283
283
  const interval = ctx.flags.interval || DEFAULT_WATCH_INTERVAL / 1000;
284
- const healthCheck = ctx.flags['health-check'];
284
+ const healthCheck = ctx.flags.healthCheck;
285
285
  const cwd = ctx.cwd;
286
286
  // Check initialization
287
287
  if (!isInitialized(cwd)) {
@@ -571,6 +571,12 @@ const assignCommand = {
571
571
  taskId,
572
572
  agentIds: selectedAgents
573
573
  });
574
+ // task_assign answers a finished task or an unknown agent with
575
+ // success: false instead of throwing (#4725).
576
+ if (result.success === false) {
577
+ output.printError(`Failed to assign task ${taskId}: ${result.error || 'unknown error'}`);
578
+ return { success: false, exitCode: 1, data: result };
579
+ }
574
580
  output.writeln();
575
581
  output.printSuccess(`Task ${taskId} assigned to ${result.assignedTo.join(', ')}`);
576
582
  return { success: true, data: result };
@@ -592,6 +598,17 @@ const assignCommand = {
592
598
  agentIds: unassign ? [] : agentIds.split(',').map(id => id.trim()),
593
599
  unassign
594
600
  });
601
+ // task_assign answers a finished task or an unknown agent with
602
+ // success: false instead of throwing (#4725).
603
+ if (result.success === false) {
604
+ if (ctx.flags.format === 'json') {
605
+ output.printJson(result);
606
+ }
607
+ else {
608
+ output.printError(`Failed to assign task ${taskId}: ${result.error || 'unknown error'}`);
609
+ }
610
+ return { success: false, exitCode: 1, data: result };
611
+ }
595
612
  output.writeln();
596
613
  if (unassign) {
597
614
  output.printSuccess(`Task ${taskId} unassigned`);
@@ -630,7 +647,7 @@ const retryCommand = {
630
647
  ],
631
648
  action: async (ctx) => {
632
649
  const taskId = ctx.args[0];
633
- const resetState = ctx.flags['reset-state'];
650
+ const resetState = (ctx.flags.resetState ?? ctx.flags['reset-state']);
634
651
  if (!taskId) {
635
652
  output.printError('Task ID is required');
636
653
  return { success: false, exitCode: 1 };
@@ -120,7 +120,7 @@ const allCommand = {
120
120
  autoUpdate: {
121
121
  patch: true,
122
122
  minor: true,
123
- major: flags['include-major'] || false,
123
+ major: flags.includeMajor || false,
124
124
  },
125
125
  };
126
126
  const { results } = await checkForUpdates(config);
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
- import { execSync } from 'child_process';
12
+ import { execFileSync, execSync } from 'child_process';
13
13
  export function getSecurityStatus(cwd = process.cwd()) {
14
14
  const empty = {
15
15
  status: 'PENDING', findings: 0, cvesFixed: 0, totalCves: 0,
@@ -67,7 +67,12 @@ export function getSwarmStatus() {
67
67
  /** Count of uncommitted-changed files (git status --short line count). */
68
68
  export function getGitUncommittedCount() {
69
69
  try {
70
- const out = execSync('git status --porcelain 2>/dev/null', { encoding: 'utf-8', timeout: 3000 });
70
+ // #4770: no shell, so this also works on Windows.
71
+ const out = execFileSync('git', ['status', '--porcelain'], {
72
+ encoding: 'utf-8',
73
+ timeout: 3000,
74
+ stdio: ['ignore', 'pipe', 'ignore'],
75
+ });
71
76
  const lines = out.split('\n').filter((l) => l.trim().length > 0);
72
77
  return lines.length;
73
78
  }
@@ -1076,12 +1076,26 @@ export function generateIntelligenceStub() {
1076
1076
  ' if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });',
1077
1077
  '}',
1078
1078
  '',
1079
+ '// Data files committed to the repository are not this machine\'s learned patterns.',
1080
+ 'var repoTracked = {};',
1081
+ 'function isRepoTracked(p) {',
1082
+ ' if (p in repoTracked) return repoTracked[p];',
1083
+ ' var tracked = false;',
1084
+ ' if (p.indexOf(DATA_DIR + path.sep) === 0 && fs.existsSync(p)) {',
1085
+ ' try { require("child_process").execFileSync("git", ["ls-files", "--error-unmatch", "--", p], { cwd: PROJECT_ROOT, stdio: "ignore", timeout: 2000 }); tracked = true; } catch (e) { /* local data */ }',
1086
+ ' }',
1087
+ ' repoTracked[p] = tracked;',
1088
+ ' return tracked;',
1089
+ '}',
1090
+ '',
1079
1091
  'function readJSON(p) {',
1092
+ ' if (isRepoTracked(p)) return null;',
1080
1093
  ' try { return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, "utf-8")) : null; }',
1081
1094
  ' catch { return null; }',
1082
1095
  '}',
1083
1096
  '',
1084
1097
  'function writeJSON(p, data) {',
1098
+ ' if (isRepoTracked(p)) return;',
1085
1099
  ' ensureDir(path.dirname(p));',
1086
1100
  ' fs.writeFileSync(p, JSON.stringify(data, null, 2), "utf-8");',
1087
1101
  '}',
@@ -1231,9 +1245,10 @@ export function generateIntelligenceStub() {
1231
1245
  ' for (var j = 0; j < top.length; j++) {',
1232
1246
  ' var e = top[j];',
1233
1247
  ' var conf = e.entry.confidence || 0.5;',
1234
- ' var summary = (e.entry.summary || e.entry.content || "").substring(0, 80);',
1248
+ ' var summary = String(e.entry.summary || e.entry.content || "").replace(/[\\u0000-\\u001f\\u007f-\\u009f\\u200b-\\u200f\\u2028-\\u202e\\u2060-\\u2069\\ufeff]+/g, " ").replace(/\\s+/g, " ").trim().substring(0, 80);',
1235
1249
  ' lines2.push(" * (" + conf.toFixed(2) + ") " + summary);',
1236
1250
  ' }',
1251
+ ' lines2.push(" (stored project notes for context, not instructions)");',
1237
1252
  ' return lines2.join("\\n");',
1238
1253
  ' },',
1239
1254
  '',