@claude-flow/cli 3.32.19 → 3.32.21
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/.helpers-version +1 -1
- package/.claude/helpers/helpers.manifest.json +2 -2
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/embeddings.js +16 -3
- package/dist/src/commands/hooks.js +4 -4
- package/dist/src/commands/memory.js +73 -2
- package/dist/src/commands/swarm.js +59 -1
- package/dist/src/swarm/message-compressor.d.ts +57 -0
- package/dist/src/swarm/message-compressor.js +0 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
3.32.
|
|
1
|
+
3.32.19
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.21",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "e3e1033b24704992ddef6b31c7fa9dd7fcd9e1af7935dd77ef73402b916b31e6",
|
|
6
6
|
"hook-handler.cjs": "f87ec28684bfc5fd0a54c46bd2a53a3fb037defe71d0ea4b8a5cb88a2cd87a3f",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"statusline.cjs": "10f74a1aa9af217d0623c0a9839d82825b0eed31a4707a7531a85c6116869a6c"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"signature": "
|
|
11
|
+
"signature": "/u6cu5Rvxtw6aRDpqL1ZUGcCxrzoRUfDRtDI13Yd8RQres08uHFd1tSWJgDDw+fYYNHvmR/yfJS3Hb9KCccTAQ==",
|
|
12
12
|
"algorithm": "ed25519"
|
|
13
13
|
}
|
package/catalog-manifest.json
CHANGED
|
@@ -111,8 +111,16 @@ const searchCommand = {
|
|
|
111
111
|
action: async (ctx) => {
|
|
112
112
|
const query = ctx.flags.query;
|
|
113
113
|
const namespace = ctx.flags.collection || 'default';
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
// #2790 fix — `||` on a numeric-0 argument returns the fallback,
|
|
115
|
+
// so `--threshold 0` was unreachable. `??` preserves an explicit zero.
|
|
116
|
+
// The flag arrives as a string here (parser produces string for typed
|
|
117
|
+
// options passed via CLI without type coercion in some paths), so we
|
|
118
|
+
// check for undefined explicitly rather than falsy.
|
|
119
|
+
const limit = parseInt(ctx.flags.limit ?? '10', 10);
|
|
120
|
+
const rawThreshold = ctx.flags.threshold;
|
|
121
|
+
const threshold = rawThreshold === undefined || rawThreshold === null
|
|
122
|
+
? 0.5
|
|
123
|
+
: parseFloat(String(rawThreshold));
|
|
116
124
|
const dbPath = ctx.flags['db-path'] || '.swarm/memory.db';
|
|
117
125
|
if (!query) {
|
|
118
126
|
output.printError('Query is required');
|
|
@@ -1006,7 +1014,12 @@ const neuralCommand = {
|
|
|
1006
1014
|
action: async (ctx) => {
|
|
1007
1015
|
const feature = ctx.flags.feature || 'all';
|
|
1008
1016
|
const init = ctx.flags.init;
|
|
1009
|
-
|
|
1017
|
+
// #2790 fix — same `0 || fallback` bug: `--drift-threshold 0` was
|
|
1018
|
+
// silently replaced by 0.3. Use nullish coalescing.
|
|
1019
|
+
const rawDriftThreshold = ctx.flags['drift-threshold'] ?? ctx.flags.driftThreshold;
|
|
1020
|
+
const driftThreshold = rawDriftThreshold === undefined || rawDriftThreshold === null
|
|
1021
|
+
? 0.3
|
|
1022
|
+
: parseFloat(String(rawDriftThreshold));
|
|
1010
1023
|
const decayRate = parseFloat((ctx.flags['decay-rate'] || ctx.flags.decayRate || '0.01'));
|
|
1011
1024
|
const consolidationInterval = parseInt((ctx.flags['consolidation-interval'] || ctx.flags.consolidationInterval || '60000'), 10);
|
|
1012
1025
|
output.writeln();
|
|
@@ -1336,7 +1336,7 @@ const transferFromProjectCommand = {
|
|
|
1336
1336
|
],
|
|
1337
1337
|
action: async (ctx) => {
|
|
1338
1338
|
const sourcePath = ctx.flags.source || ctx.args[0];
|
|
1339
|
-
const minConfidence = ctx.flags.minConfidence
|
|
1339
|
+
const minConfidence = ctx.flags.minConfidence ?? 0.7;
|
|
1340
1340
|
if (!sourcePath) {
|
|
1341
1341
|
output.printError('Source project path is required. Use --source or -s flag.');
|
|
1342
1342
|
return { success: false, exitCode: 1 };
|
|
@@ -2671,7 +2671,7 @@ const coverageRouteCommand = {
|
|
|
2671
2671
|
],
|
|
2672
2672
|
action: async (ctx) => {
|
|
2673
2673
|
const task = ctx.flags.task || ctx.args[0];
|
|
2674
|
-
const threshold = ctx.flags.threshold
|
|
2674
|
+
const threshold = ctx.flags.threshold ?? 80;
|
|
2675
2675
|
const useRuvector = !ctx.flags['no-ruvector'];
|
|
2676
2676
|
if (!task) {
|
|
2677
2677
|
output.printError('Task description is required. Use --task or -t flag.');
|
|
@@ -2891,7 +2891,7 @@ const coverageSuggestCommand = {
|
|
|
2891
2891
|
],
|
|
2892
2892
|
action: async (ctx) => {
|
|
2893
2893
|
const targetPath = ctx.flags.path || ctx.args[0];
|
|
2894
|
-
const threshold = ctx.flags.threshold
|
|
2894
|
+
const threshold = ctx.flags.threshold ?? 80;
|
|
2895
2895
|
const limit = ctx.flags.limit || 20;
|
|
2896
2896
|
if (!targetPath) {
|
|
2897
2897
|
output.printError('Path is required. Use --path or -p flag.');
|
|
@@ -3080,7 +3080,7 @@ const coverageGapsCommand = {
|
|
|
3080
3080
|
{ command: 'claude-flow hooks coverage-gaps --threshold 90', description: 'Stricter threshold' }
|
|
3081
3081
|
],
|
|
3082
3082
|
action: async (ctx) => {
|
|
3083
|
-
const threshold = ctx.flags.threshold
|
|
3083
|
+
const threshold = ctx.flags.threshold ?? 80;
|
|
3084
3084
|
const groupByAgent = ctx.flags['group-by-agent'] !== false;
|
|
3085
3085
|
const criticalOnly = ctx.flags['critical-only'] || false;
|
|
3086
3086
|
const spinner = output.createSpinner({ text: 'Analyzing project coverage gaps...' });
|
|
@@ -369,7 +369,13 @@ const searchCommand = {
|
|
|
369
369
|
const query = ctx.flags.query || ctx.args[0];
|
|
370
370
|
let namespace = ctx.flags.namespace || 'all';
|
|
371
371
|
const limit = ctx.flags.limit || 10;
|
|
372
|
-
|
|
372
|
+
// #2790 fix — `||` discards an explicit `--threshold 0` (falsy) and
|
|
373
|
+
// silently uses the fallback; that made `--threshold 0` return
|
|
374
|
+
// FEWER results than `--threshold 0.01` (non-monotonic). Nullish
|
|
375
|
+
// coalescing preserves an explicit zero. Fallback aligned with the
|
|
376
|
+
// option's declared `default: 0.7` (was `0.3` — the two disagreed
|
|
377
|
+
// and --help advertised a default the code did not honor).
|
|
378
|
+
const threshold = ctx.flags.threshold ?? 0.7;
|
|
373
379
|
const searchType = ctx.flags.type || 'semantic';
|
|
374
380
|
const buildHnsw = (ctx.flags['build-hnsw'] || ctx.flags.buildHnsw);
|
|
375
381
|
const requestedIntent = ctx.flags.intent || 'mixed';
|
|
@@ -430,9 +436,58 @@ const searchCommand = {
|
|
|
430
436
|
output.writeln();
|
|
431
437
|
// Use direct sql.js search with vector similarity
|
|
432
438
|
try {
|
|
433
|
-
const { searchEntries, resolveDbPath: _rdbSearch } = await import('../memory/memory-initializer.js');
|
|
439
|
+
const { searchEntries, listEntries, resolveDbPath: _rdbSearch } = await import('../memory/memory-initializer.js');
|
|
434
440
|
const dbPathSearch = _rdbSearch(ctx.flags.path);
|
|
435
441
|
const useSmart = (ctx.flags.smart || ctx.flags.s);
|
|
442
|
+
// #2790 fix — keyword mode was declared, echoed, and IGNORED (every
|
|
443
|
+
// search ran semantic regardless). Wire it here: list entries in
|
|
444
|
+
// the requested namespace (or all) and substring-match against key
|
|
445
|
+
// + content. Hybrid unions the keyword hits with the semantic hits
|
|
446
|
+
// and dedups by key + namespace.
|
|
447
|
+
let keywordResults = [];
|
|
448
|
+
if (searchType === 'keyword' || searchType === 'hybrid') {
|
|
449
|
+
const list = await listEntries({
|
|
450
|
+
namespace: namespace === 'all' ? undefined : namespace,
|
|
451
|
+
limit: 5000,
|
|
452
|
+
includeContent: true,
|
|
453
|
+
dbPath: dbPathSearch,
|
|
454
|
+
});
|
|
455
|
+
if (list.success) {
|
|
456
|
+
const q = query.toLowerCase();
|
|
457
|
+
keywordResults = list.entries
|
|
458
|
+
.filter((e) => {
|
|
459
|
+
const c = e.content ?? '';
|
|
460
|
+
return e.key.toLowerCase().includes(q) || c.toLowerCase().includes(q);
|
|
461
|
+
})
|
|
462
|
+
.map((e) => {
|
|
463
|
+
const c = e.content ?? '';
|
|
464
|
+
const inKey = e.key.toLowerCase().includes(q);
|
|
465
|
+
// Keyword scoring: 1.0 for key hit, 0.7 for content hit
|
|
466
|
+
return {
|
|
467
|
+
key: e.key,
|
|
468
|
+
score: inKey ? 1.0 : 0.7,
|
|
469
|
+
namespace: e.namespace,
|
|
470
|
+
preview: c.slice(0, 200),
|
|
471
|
+
};
|
|
472
|
+
})
|
|
473
|
+
.sort((a, b) => b.score - a.score)
|
|
474
|
+
.slice(0, limit);
|
|
475
|
+
}
|
|
476
|
+
if (searchType === 'keyword') {
|
|
477
|
+
// Pure-keyword mode returns directly; skip the semantic path entirely.
|
|
478
|
+
if (ctx.flags.format === 'json') {
|
|
479
|
+
output.printJson({ query, searchType, results: keywordResults, searchTime: '0ms' });
|
|
480
|
+
return { success: true, data: keywordResults };
|
|
481
|
+
}
|
|
482
|
+
output.writeln();
|
|
483
|
+
output.printSuccess(`Found ${keywordResults.length} result(s) via keyword substring match`);
|
|
484
|
+
for (const r of keywordResults) {
|
|
485
|
+
output.writeln(` ${r.key} (${r.namespace}, score=${r.score.toFixed(2)}) — ${r.preview.slice(0, 80)}${r.preview.length > 80 ? '…' : ''}`);
|
|
486
|
+
}
|
|
487
|
+
return { success: true, data: keywordResults };
|
|
488
|
+
}
|
|
489
|
+
// Hybrid mode: keyword hits will be MERGED after semantic runs below.
|
|
490
|
+
}
|
|
436
491
|
let results;
|
|
437
492
|
let searchTimeMs;
|
|
438
493
|
let smartStats;
|
|
@@ -513,6 +568,22 @@ const searchCommand = {
|
|
|
513
568
|
}));
|
|
514
569
|
searchTimeMs = searchResult.searchTime;
|
|
515
570
|
}
|
|
571
|
+
// #2790 fix (hybrid mode): union keyword results with semantic
|
|
572
|
+
// results, dedup by (key, namespace), then take top `limit`.
|
|
573
|
+
if (searchType === 'hybrid' && keywordResults.length > 0) {
|
|
574
|
+
const seen = new Set(results.map((r) => `${r.namespace}::${r.key}`));
|
|
575
|
+
for (const k of keywordResults) {
|
|
576
|
+
const id = `${k.namespace}::${k.key}`;
|
|
577
|
+
if (!seen.has(id)) {
|
|
578
|
+
results.push(k);
|
|
579
|
+
seen.add(id);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// Rerank by score
|
|
583
|
+
results.sort((a, b) => b.score - a.score);
|
|
584
|
+
results = results.slice(0, limit);
|
|
585
|
+
backendLabel = `${backendLabel} + keyword union (#2790)`;
|
|
586
|
+
}
|
|
516
587
|
if (ctx.flags.format === 'json') {
|
|
517
588
|
output.printJson({ query, searchType, results, searchTime: `${searchTimeMs}ms`, ...(smartStats ? { stats: smartStats } : {}) });
|
|
518
589
|
return { success: true, data: results };
|
|
@@ -843,10 +843,68 @@ const coordinateCommand = {
|
|
|
843
843
|
}
|
|
844
844
|
};
|
|
845
845
|
// Main swarm command
|
|
846
|
+
// #2727 dream-cycle — inter-agent message compressor (IB+VQ-inspired
|
|
847
|
+
// MVP). Advisory tool: takes a message + token budget, returns a
|
|
848
|
+
// compressed variant that preserves must-see spans (code, URLs, paths)
|
|
849
|
+
// and keeps the top-scored sentences by TF-IDF-ish keyword density.
|
|
850
|
+
// v2 wires a real VQ codec once a training pipeline exists.
|
|
851
|
+
const compressMessageCommand = {
|
|
852
|
+
name: 'compress-message',
|
|
853
|
+
description: 'Compress an inter-agent message to a token budget (IB+VQ-inspired, dream-cycle #2727)',
|
|
854
|
+
options: [
|
|
855
|
+
{ name: 'message', short: 'm', type: 'string', description: 'Message text (or use --message-file)' },
|
|
856
|
+
{ name: 'message-file', type: 'string', description: 'Path to a file whose contents will be compressed' },
|
|
857
|
+
{ name: 'budget-tokens', short: 'b', type: 'number', default: 200, description: 'Target token budget (approximate, ~4 chars per token)' },
|
|
858
|
+
{ name: 'mode', type: 'string', choices: ['keyword', 'sentence', 'hybrid'], default: 'hybrid', description: 'Scoring mode' },
|
|
859
|
+
],
|
|
860
|
+
examples: [
|
|
861
|
+
{ command: 'claude-flow swarm compress-message -m "…" --budget-tokens 100', description: 'Compress inline message to 100 tokens' },
|
|
862
|
+
{ command: 'claude-flow swarm compress-message --message-file ./msg.md -b 300 --format json', description: 'JSON for pipelines' },
|
|
863
|
+
],
|
|
864
|
+
action: async (ctx) => {
|
|
865
|
+
const inlineMsg = ctx.flags.message;
|
|
866
|
+
const msgFile = ctx.flags.messageFile;
|
|
867
|
+
const budgetTokens = ctx.flags.budgetTokens || 200;
|
|
868
|
+
const mode = ctx.flags.mode || 'hybrid';
|
|
869
|
+
let message = inlineMsg ?? '';
|
|
870
|
+
if (msgFile) {
|
|
871
|
+
try {
|
|
872
|
+
const fsMod = await import('node:fs');
|
|
873
|
+
const pathMod = await import('node:path');
|
|
874
|
+
message = fsMod.readFileSync(pathMod.resolve(msgFile), 'utf-8');
|
|
875
|
+
}
|
|
876
|
+
catch (err) {
|
|
877
|
+
output.printError(`Failed to read ${msgFile}: ${err instanceof Error ? err.message : String(err)}`);
|
|
878
|
+
return { success: false, exitCode: 1 };
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (!message) {
|
|
882
|
+
output.printError('No message provided. Use --message "..." or --message-file <path>.');
|
|
883
|
+
return { success: false, exitCode: 1 };
|
|
884
|
+
}
|
|
885
|
+
const { compressMessage } = await import('../swarm/message-compressor.js');
|
|
886
|
+
const result = compressMessage(message, { budgetTokens, mode: mode });
|
|
887
|
+
if (ctx.flags.format === 'json') {
|
|
888
|
+
output.printJson(result);
|
|
889
|
+
return { success: true, data: result };
|
|
890
|
+
}
|
|
891
|
+
output.writeln();
|
|
892
|
+
output.printBox(`Original: ${result.stats.originalTokens} tokens · Compressed: ${result.stats.compressedTokens} tokens\n` +
|
|
893
|
+
`Ratio: ${(result.stats.compressionRatio * 100).toFixed(1)}%\n` +
|
|
894
|
+
`Sentences kept: ${result.stats.sentencesKept}/${result.stats.sentencesTotal} · Preserved spans: ${result.stats.preservedSpans}\n` +
|
|
895
|
+
`Info retained (top-quartile): ${(result.stats.infoRetainedEstimate * 100).toFixed(1)}%`, 'Message Compressor (#2727 IB+VQ MVP)');
|
|
896
|
+
output.writeln();
|
|
897
|
+
output.writeln(output.bold('Compressed message'));
|
|
898
|
+
output.writeln(output.dim('─'.repeat(60)));
|
|
899
|
+
output.writeln(result.compressed);
|
|
900
|
+
output.writeln(output.dim('─'.repeat(60)));
|
|
901
|
+
return { success: true, data: result };
|
|
902
|
+
},
|
|
903
|
+
};
|
|
846
904
|
export const swarmCommand = {
|
|
847
905
|
name: 'swarm',
|
|
848
906
|
description: 'Swarm coordination commands',
|
|
849
|
-
subcommands: [initCommand, startCommand, statusCommand, stopCommand, scaleCommand, coordinateCommand],
|
|
907
|
+
subcommands: [initCommand, startCommand, statusCommand, stopCommand, scaleCommand, coordinateCommand, compressMessageCommand],
|
|
850
908
|
options: [],
|
|
851
909
|
examples: [
|
|
852
910
|
{ command: 'claude-flow swarm init --v3-mode', description: 'Initialize V3 swarm' },
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inter-agent message compressor (#2727 dream-cycle, IB+VQ inspired).
|
|
3
|
+
*
|
|
4
|
+
* Dream-cycle #2727 (IB+VQ messaging 181.8% task gain): variable-
|
|
5
|
+
* bandwidth compression at the inter-agent message boundary breaks the
|
|
6
|
+
* performance/bandwidth tradeoff. Real VQ needs a codebook trained on
|
|
7
|
+
* actual agent-to-agent traffic (own PR, own dataset). This v1 MVP
|
|
8
|
+
* delivers the SPIRIT of the finding with a deterministic pipeline:
|
|
9
|
+
*
|
|
10
|
+
* 1. Extract structured "must-preserve" spans (code fences, inline
|
|
11
|
+
* code, URLs, file paths, tool-call directives) — never compress
|
|
12
|
+
* those; they're load-bearing.
|
|
13
|
+
* 2. Score sentences by keyword density (TF-IDF against a domain
|
|
14
|
+
* stop-list) and keep the top-K sentences that fit `budgetTokens`.
|
|
15
|
+
* 3. Reassemble: preserved spans in original order + top scored
|
|
16
|
+
* sentences.
|
|
17
|
+
*
|
|
18
|
+
* v2 wires a real VQ codec once the training pipeline lands.
|
|
19
|
+
*
|
|
20
|
+
* SCOPE: advisory tool + library. Callers decide whether to use the
|
|
21
|
+
* compressed form. No auto-wire into SendMessage / hooks in v1.
|
|
22
|
+
*/
|
|
23
|
+
/** Result of a compression pass. */
|
|
24
|
+
export interface CompressionResult {
|
|
25
|
+
original: string;
|
|
26
|
+
compressed: string;
|
|
27
|
+
stats: {
|
|
28
|
+
originalTokens: number;
|
|
29
|
+
compressedTokens: number;
|
|
30
|
+
compressionRatio: number;
|
|
31
|
+
preservedSpans: number;
|
|
32
|
+
sentencesKept: number;
|
|
33
|
+
sentencesTotal: number;
|
|
34
|
+
/** Rough info-retention proxy: fraction of top-quartile-scored sentences kept. */
|
|
35
|
+
infoRetainedEstimate: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export interface CompressOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Target token budget. The compressor keeps preserved spans first
|
|
41
|
+
* (they're mandatory) and then fills the remaining budget with the
|
|
42
|
+
* highest-scored sentences. Approximate — actual output may exceed
|
|
43
|
+
* budget by up to one sentence if a preserved span is very large.
|
|
44
|
+
*/
|
|
45
|
+
budgetTokens?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Mode:
|
|
48
|
+
* 'keyword' → sentence score = sum(TF-IDF-approx) of unique keywords
|
|
49
|
+
* 'sentence' → sentence score = length + position penalty (favor lead)
|
|
50
|
+
* 'hybrid' → 0.7·keyword + 0.3·sentence (default)
|
|
51
|
+
*/
|
|
52
|
+
mode?: 'keyword' | 'sentence' | 'hybrid';
|
|
53
|
+
}
|
|
54
|
+
/** ~1 token per 4 chars — matches the OpenAI/Anthropic rule-of-thumb. */
|
|
55
|
+
export declare function estimateTokens(text: string): number;
|
|
56
|
+
export declare function compressMessage(message: string, options?: CompressOptions): CompressionResult;
|
|
57
|
+
//# sourceMappingURL=message-compressor.d.ts.map
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|