claude-flow 3.32.17 → 3.32.19
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/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +135 -3
- package/v3/@claude-flow/cli/dist/src/memory/oas-operator-selector.d.ts +74 -0
- package/v3/@claude-flow/cli/dist/src/memory/oas-operator-selector.js +117 -0
- package/v3/@claude-flow/cli/dist/src/memory/scm-classifier.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/memory/scm-classifier.js +148 -0
- package/v3/@claude-flow/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.19",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -345,25 +345,62 @@ const searchCommand = {
|
|
|
345
345
|
type: 'boolean',
|
|
346
346
|
default: false
|
|
347
347
|
},
|
|
348
|
+
{
|
|
349
|
+
// #2760 dream-cycle — SCM routed memory. Classify the query intent
|
|
350
|
+
// and route search to the mapped namespaces (episodic → session
|
|
351
|
+
// writes, semantic → patterns, procedural → skills). Default
|
|
352
|
+
// "mixed" preserves the pre-#2760 behavior (search everything).
|
|
353
|
+
name: 'intent',
|
|
354
|
+
description: 'Route query to intent-mapped namespaces (auto | mixed | episodic | semantic | procedural) — dream-cycle #2760',
|
|
355
|
+
type: 'string',
|
|
356
|
+
choices: ['auto', 'mixed', 'episodic', 'semantic', 'procedural'],
|
|
357
|
+
default: 'mixed',
|
|
358
|
+
},
|
|
348
359
|
DB_PATH_OPTION
|
|
349
360
|
],
|
|
350
361
|
examples: [
|
|
351
362
|
{ command: 'claude-flow memory search -q "authentication patterns"', description: 'Semantic search' },
|
|
352
363
|
{ command: 'claude-flow memory search -q "JWT" -t keyword', description: 'Keyword search' },
|
|
353
364
|
{ command: 'claude-flow memory search -q "test" --build-hnsw', description: 'Build HNSW index and search' },
|
|
354
|
-
{ command: 'claude-flow memory search -q "auth patterns" --smart', description: 'SmartRetrieval with RRF + MMR' }
|
|
365
|
+
{ command: 'claude-flow memory search -q "auth patterns" --smart', description: 'SmartRetrieval with RRF + MMR' },
|
|
366
|
+
{ command: 'claude-flow memory search -q "when did we last touch auth" --intent auto', description: 'SCM-routed retrieval (dream-cycle #2760)' },
|
|
355
367
|
],
|
|
356
368
|
action: async (ctx) => {
|
|
357
369
|
const query = ctx.flags.query || ctx.args[0];
|
|
358
|
-
|
|
370
|
+
let namespace = ctx.flags.namespace || 'all';
|
|
359
371
|
const limit = ctx.flags.limit || 10;
|
|
360
372
|
const threshold = ctx.flags.threshold || 0.3;
|
|
361
373
|
const searchType = ctx.flags.type || 'semantic';
|
|
362
374
|
const buildHnsw = (ctx.flags['build-hnsw'] || ctx.flags.buildHnsw);
|
|
375
|
+
const requestedIntent = ctx.flags.intent || 'mixed';
|
|
363
376
|
if (!query) {
|
|
364
377
|
output.printError('Query is required. Use --query or -q');
|
|
365
378
|
return { success: false, exitCode: 1 };
|
|
366
379
|
}
|
|
380
|
+
// #2760 SCM routed memory (v1 MVP) — classify query intent and
|
|
381
|
+
// print a routing hint. Only fires when --intent is NOT the default
|
|
382
|
+
// "mixed" AND --namespace was NOT explicitly passed. Intentionally
|
|
383
|
+
// does NOT mutate the search path — the routing is advisory in v1
|
|
384
|
+
// so the search backend's exact behavior is preserved. v2 will
|
|
385
|
+
// apply the routing when the search-backend interface adds a
|
|
386
|
+
// multi-namespace OR filter.
|
|
387
|
+
if (requestedIntent !== 'mixed' && (namespace === 'all' || !ctx.flags.namespace)) {
|
|
388
|
+
try {
|
|
389
|
+
const { resolveIntent } = await import('../memory/scm-classifier.js');
|
|
390
|
+
const resolved = resolveIntent(query, requestedIntent);
|
|
391
|
+
if (resolved.intent !== 'mixed' && resolved.namespaces.length > 0) {
|
|
392
|
+
output.printInfo(`SCM router → ${resolved.intent} (confidence ${(resolved.confidence * 100).toFixed(1)}%)`);
|
|
393
|
+
output.writeln(output.dim(` ${resolved.reason}`));
|
|
394
|
+
output.writeln(output.dim(` Suggested: rerun with --namespace ${resolved.namespaces[0]} to filter (or one of: ${resolved.namespaces.slice(1, 4).join(', ')}...)`));
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
output.writeln(output.dim(`SCM router → mixed (${resolved.reason})`));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
output.writeln(output.dim(`SCM routing skipped: ${err instanceof Error ? err.message : String(err)}`));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
367
404
|
// Build/rebuild HNSW index if requested
|
|
368
405
|
if (buildHnsw) {
|
|
369
406
|
output.printInfo('Building HNSW index...');
|
|
@@ -1595,11 +1632,106 @@ const initMemoryCommand = {
|
|
|
1595
1632
|
}
|
|
1596
1633
|
}
|
|
1597
1634
|
};
|
|
1635
|
+
// #2763 dream-cycle — OAS memory-operator selector. Standalone
|
|
1636
|
+
// subcommand that returns the highest-value consolidation operator
|
|
1637
|
+
// that fits the given budget for the given entry count. Advisory —
|
|
1638
|
+
// the caller decides whether to invoke that operator.
|
|
1639
|
+
const selectOperatorCommand = {
|
|
1640
|
+
name: 'select-operator',
|
|
1641
|
+
description: 'Pick the highest-value consolidation operator that fits a budget (OAS, dream-cycle #2763)',
|
|
1642
|
+
options: [
|
|
1643
|
+
{ name: 'budget', short: 'b', type: 'number', required: true, description: 'Budget in abstract operator points (1 point ≈ 1 Haiku call)' },
|
|
1644
|
+
{ name: 'entries', short: 'e', type: 'number', required: true, description: 'Number of entries to consolidate' },
|
|
1645
|
+
{ name: 'hint', type: 'string', choices: ['duplicates', 'verbose', 'patterns', 'general'], default: 'general', description: 'Entry-shape hint' },
|
|
1646
|
+
],
|
|
1647
|
+
examples: [
|
|
1648
|
+
{ command: 'claude-flow memory select-operator -b 50 -e 200', description: 'Pick operator for 200 entries within 50 points' },
|
|
1649
|
+
{ command: 'claude-flow memory select-operator -b 5 -e 100 --hint duplicates --format json', description: 'JSON for pipelines' },
|
|
1650
|
+
],
|
|
1651
|
+
action: async (ctx) => {
|
|
1652
|
+
const budget = ctx.flags.budget;
|
|
1653
|
+
const entries = ctx.flags.entries;
|
|
1654
|
+
const hint = ctx.flags.hint || 'general';
|
|
1655
|
+
if (budget === undefined || entries === undefined) {
|
|
1656
|
+
output.printError('Both --budget and --entries are required.');
|
|
1657
|
+
return { success: false, exitCode: 1 };
|
|
1658
|
+
}
|
|
1659
|
+
if (budget < 0 || entries < 0) {
|
|
1660
|
+
output.printError('Budget and entries must be non-negative.');
|
|
1661
|
+
return { success: false, exitCode: 1 };
|
|
1662
|
+
}
|
|
1663
|
+
const { selectOperator, OPERATORS } = await import('../memory/oas-operator-selector.js');
|
|
1664
|
+
const selection = selectOperator({ budget, entries, hint: hint });
|
|
1665
|
+
if (ctx.flags.format === 'json') {
|
|
1666
|
+
output.printJson(selection);
|
|
1667
|
+
return { success: true, data: selection };
|
|
1668
|
+
}
|
|
1669
|
+
const chosen = OPERATORS[selection.operator];
|
|
1670
|
+
output.writeln();
|
|
1671
|
+
output.printBox(`Budget: ${budget} points · Entries: ${entries}\n` +
|
|
1672
|
+
`Operator: ${selection.operator}\n` +
|
|
1673
|
+
`Description: ${chosen.description}\n` +
|
|
1674
|
+
`Estimated cost: ${selection.estimatedCost.toFixed(2)} points\n` +
|
|
1675
|
+
`Needs split: ${selection.needsSplit ? `YES — batch size ${selection.suggestedBatchSize}` : 'no'}`, 'OAS Operator Selection (#2763)');
|
|
1676
|
+
output.writeln();
|
|
1677
|
+
output.writeln(output.dim(selection.reason));
|
|
1678
|
+
if (selection.considered.length > 0) {
|
|
1679
|
+
output.writeln();
|
|
1680
|
+
output.writeln(output.bold('All operators considered'));
|
|
1681
|
+
output.printTable({
|
|
1682
|
+
columns: [
|
|
1683
|
+
{ key: 'id', header: 'Operator', width: 15 },
|
|
1684
|
+
{ key: 'cost', header: 'Est. Cost', width: 12, align: 'right', format: (v) => Number(v).toFixed(2) },
|
|
1685
|
+
{ key: 'fits', header: 'Fits Budget', width: 12, align: 'center', format: (v) => v ? '✓' : '—' },
|
|
1686
|
+
],
|
|
1687
|
+
data: selection.considered,
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
return { success: true, data: selection };
|
|
1691
|
+
},
|
|
1692
|
+
};
|
|
1693
|
+
// #2760 dream-cycle — SCM query classifier. Standalone subcommand so
|
|
1694
|
+
// users can inspect what intent a query maps to and pipe the mapped
|
|
1695
|
+
// namespaces into other tools (e.g. `memory search --namespace ...`).
|
|
1696
|
+
const classifyCommand = {
|
|
1697
|
+
name: 'classify',
|
|
1698
|
+
description: 'Classify a query into episodic/semantic/procedural intent (SCM router, dream-cycle #2760)',
|
|
1699
|
+
options: [
|
|
1700
|
+
{ name: 'query', short: 'q', type: 'string', description: 'Query text to classify', required: true },
|
|
1701
|
+
{ name: 'intent', type: 'string', choices: ['auto', 'mixed', 'episodic', 'semantic', 'procedural'], default: 'auto', description: 'Force a specific intent (default: auto)' },
|
|
1702
|
+
],
|
|
1703
|
+
examples: [
|
|
1704
|
+
{ command: 'claude-flow memory classify -q "when did we last touch auth"', description: 'Auto-classify (should return episodic)' },
|
|
1705
|
+
{ command: 'claude-flow memory classify -q "how does JWT work" --format json', description: 'JSON output for pipelines' },
|
|
1706
|
+
],
|
|
1707
|
+
action: async (ctx) => {
|
|
1708
|
+
const query = ctx.flags.query;
|
|
1709
|
+
const requested = ctx.flags.intent || 'auto';
|
|
1710
|
+
if (!query) {
|
|
1711
|
+
output.printError('Query is required. Use --query or -q');
|
|
1712
|
+
return { success: false, exitCode: 1 };
|
|
1713
|
+
}
|
|
1714
|
+
const { resolveIntent } = await import('../memory/scm-classifier.js');
|
|
1715
|
+
const resolved = resolveIntent(query, requested);
|
|
1716
|
+
if (ctx.flags.format === 'json') {
|
|
1717
|
+
output.printJson(resolved);
|
|
1718
|
+
return { success: true, data: resolved };
|
|
1719
|
+
}
|
|
1720
|
+
output.writeln();
|
|
1721
|
+
output.printBox(`Query: "${query}"\n` +
|
|
1722
|
+
`Intent: ${resolved.intent}\n` +
|
|
1723
|
+
`Confidence: ${(resolved.confidence * 100).toFixed(1)}%\n` +
|
|
1724
|
+
`Namespaces: ${resolved.namespaces.length > 0 ? resolved.namespaces.slice(0, 4).join(', ') + (resolved.namespaces.length > 4 ? '…' : '') : '(all — mixed retrieval)'}`, 'SCM Classifier (#2760)');
|
|
1725
|
+
output.writeln();
|
|
1726
|
+
output.writeln(output.dim(resolved.reason));
|
|
1727
|
+
return { success: true, data: resolved };
|
|
1728
|
+
},
|
|
1729
|
+
};
|
|
1598
1730
|
// Main memory command
|
|
1599
1731
|
export const memoryCommand = {
|
|
1600
1732
|
name: 'memory',
|
|
1601
1733
|
description: 'Memory management commands',
|
|
1602
|
-
subcommands: [initMemoryCommand, storeCommand, retrieveCommand, searchCommand, listCommand, deleteCommand, purgeCommand, statsCommand, configureCommand, cleanupCommand, compressCommand, exportCommand, importCommand, distillCommand, backupCommand],
|
|
1734
|
+
subcommands: [initMemoryCommand, storeCommand, retrieveCommand, searchCommand, listCommand, deleteCommand, purgeCommand, statsCommand, configureCommand, cleanupCommand, compressCommand, exportCommand, importCommand, distillCommand, backupCommand, classifyCommand, selectOperatorCommand],
|
|
1603
1735
|
options: [],
|
|
1604
1736
|
examples: [
|
|
1605
1737
|
{ command: 'claude-flow memory store -k "key" -v "value"', description: 'Store data' },
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAS (Operator-Aware Selection) memory-consolidation operator picker
|
|
3
|
+
* (#2763 dream-cycle).
|
|
4
|
+
*
|
|
5
|
+
* Findings from the OAS paper referenced in dream-cycle #2763: memory-
|
|
6
|
+
* consolidation performance improves by +48% when the caller picks the
|
|
7
|
+
* RIGHT operator for the current budget + workload, instead of always
|
|
8
|
+
* applying the most-expensive one. Ruflo already has multiple
|
|
9
|
+
* consolidation operators (merge / summarize / compress / distill),
|
|
10
|
+
* but has no cost-aware selector — every `memory consolidate` call
|
|
11
|
+
* uses the same path.
|
|
12
|
+
*
|
|
13
|
+
* v1 MVP: rule-based selector. Cost-per-entry estimates are informed
|
|
14
|
+
* by ruflo's own measured performance targets in v3/@claude-flow/cli/
|
|
15
|
+
* CLAUDE.md — no external benchmark call needed. v2 will refine costs
|
|
16
|
+
* from routing-outcomes trajectories.
|
|
17
|
+
*/
|
|
18
|
+
/** The four consolidation operators ruflo can apply. */
|
|
19
|
+
export type OperatorId = 'merge' | 'summarize' | 'compress' | 'distill';
|
|
20
|
+
export interface OperatorSpec {
|
|
21
|
+
id: OperatorId;
|
|
22
|
+
/** Estimated cost per entry, in abstract "operator points" (1 point ≈ 1 Haiku call). */
|
|
23
|
+
costPerEntry: number;
|
|
24
|
+
/** Maximum recommended entry count per invocation (higher counts split). */
|
|
25
|
+
maxEntries: number;
|
|
26
|
+
/** What the operator does — human-readable. */
|
|
27
|
+
description: string;
|
|
28
|
+
/** Best when the entry set has this shape. */
|
|
29
|
+
bestWhen: string;
|
|
30
|
+
}
|
|
31
|
+
export declare const OPERATORS: Record<OperatorId, OperatorSpec>;
|
|
32
|
+
export interface OperatorSelection {
|
|
33
|
+
operator: OperatorId;
|
|
34
|
+
reason: string;
|
|
35
|
+
/** Estimated cost = costPerEntry × min(entries, maxEntries). */
|
|
36
|
+
estimatedCost: number;
|
|
37
|
+
/** True if entries > operator.maxEntries and caller should split into multiple invocations. */
|
|
38
|
+
needsSplit: boolean;
|
|
39
|
+
suggestedBatchSize: number;
|
|
40
|
+
/** All operators considered, in ranked order (best first). */
|
|
41
|
+
considered: Array<{
|
|
42
|
+
id: OperatorId;
|
|
43
|
+
cost: number;
|
|
44
|
+
fits: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export interface SelectOptions {
|
|
48
|
+
/** Budget in abstract operator points. */
|
|
49
|
+
budget: number;
|
|
50
|
+
/** Number of entries to consolidate. */
|
|
51
|
+
entries: number;
|
|
52
|
+
/**
|
|
53
|
+
* Optional hint about entry shape:
|
|
54
|
+
* 'duplicates' → strongly prefer merge (cheap wins first)
|
|
55
|
+
* 'verbose' → prefer summarize
|
|
56
|
+
* 'patterns' → prefer distill (small, high-signal)
|
|
57
|
+
* 'general' → let cost-fit decide (default)
|
|
58
|
+
*/
|
|
59
|
+
hint?: 'duplicates' | 'verbose' | 'patterns' | 'general';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Select the highest-value operator that fits the budget.
|
|
63
|
+
*
|
|
64
|
+
* Selection rule (v1):
|
|
65
|
+
* 1. If a hint is provided AND the hinted operator fits the budget,
|
|
66
|
+
* pick it (hint is a strong signal about entry shape).
|
|
67
|
+
* 2. Else, rank operators by cost ascending; pick the most expensive
|
|
68
|
+
* operator that still fits budget × 1.0 (no over-spend). Idea:
|
|
69
|
+
* spend the whole budget on the most-fidelity operator we can afford.
|
|
70
|
+
* 3. If nothing fits, return merge (guaranteed cheapest) with
|
|
71
|
+
* needsSplit=true so caller batches.
|
|
72
|
+
*/
|
|
73
|
+
export declare function selectOperator(opts: SelectOptions): OperatorSelection;
|
|
74
|
+
//# sourceMappingURL=oas-operator-selector.d.ts.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAS (Operator-Aware Selection) memory-consolidation operator picker
|
|
3
|
+
* (#2763 dream-cycle).
|
|
4
|
+
*
|
|
5
|
+
* Findings from the OAS paper referenced in dream-cycle #2763: memory-
|
|
6
|
+
* consolidation performance improves by +48% when the caller picks the
|
|
7
|
+
* RIGHT operator for the current budget + workload, instead of always
|
|
8
|
+
* applying the most-expensive one. Ruflo already has multiple
|
|
9
|
+
* consolidation operators (merge / summarize / compress / distill),
|
|
10
|
+
* but has no cost-aware selector — every `memory consolidate` call
|
|
11
|
+
* uses the same path.
|
|
12
|
+
*
|
|
13
|
+
* v1 MVP: rule-based selector. Cost-per-entry estimates are informed
|
|
14
|
+
* by ruflo's own measured performance targets in v3/@claude-flow/cli/
|
|
15
|
+
* CLAUDE.md — no external benchmark call needed. v2 will refine costs
|
|
16
|
+
* from routing-outcomes trajectories.
|
|
17
|
+
*/
|
|
18
|
+
export const OPERATORS = {
|
|
19
|
+
merge: {
|
|
20
|
+
id: 'merge',
|
|
21
|
+
costPerEntry: 0.02, // near-free — deterministic near-duplicate merge
|
|
22
|
+
maxEntries: 5000,
|
|
23
|
+
description: 'Near-duplicate merge (deterministic, cosine ≥ 0.95)',
|
|
24
|
+
bestWhen: 'Many near-identical entries (e.g. duplicated command traces)',
|
|
25
|
+
},
|
|
26
|
+
summarize: {
|
|
27
|
+
id: 'summarize',
|
|
28
|
+
costPerEntry: 0.5, // one Haiku call per ~2 entries
|
|
29
|
+
maxEntries: 500,
|
|
30
|
+
description: 'Short-to-long consolidation via Haiku (1 call per ~2 entries)',
|
|
31
|
+
bestWhen: 'Verbose logs / trajectory steps that share a theme',
|
|
32
|
+
},
|
|
33
|
+
compress: {
|
|
34
|
+
id: 'compress',
|
|
35
|
+
costPerEntry: 1.5, // LoRA-style compression pass — heavier
|
|
36
|
+
maxEntries: 200,
|
|
37
|
+
description: 'LoRA/EWC-style compression preserving semantic embeddings',
|
|
38
|
+
bestWhen: 'High-fidelity pattern preservation across many entries',
|
|
39
|
+
},
|
|
40
|
+
distill: {
|
|
41
|
+
id: 'distill',
|
|
42
|
+
costPerEntry: 3, // full distillation — heaviest
|
|
43
|
+
maxEntries: 100,
|
|
44
|
+
description: 'Full pattern distillation (extracts high-value patterns for ReasoningBank)',
|
|
45
|
+
bestWhen: 'Extracting durable learnings from a small high-signal set',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Select the highest-value operator that fits the budget.
|
|
50
|
+
*
|
|
51
|
+
* Selection rule (v1):
|
|
52
|
+
* 1. If a hint is provided AND the hinted operator fits the budget,
|
|
53
|
+
* pick it (hint is a strong signal about entry shape).
|
|
54
|
+
* 2. Else, rank operators by cost ascending; pick the most expensive
|
|
55
|
+
* operator that still fits budget × 1.0 (no over-spend). Idea:
|
|
56
|
+
* spend the whole budget on the most-fidelity operator we can afford.
|
|
57
|
+
* 3. If nothing fits, return merge (guaranteed cheapest) with
|
|
58
|
+
* needsSplit=true so caller batches.
|
|
59
|
+
*/
|
|
60
|
+
export function selectOperator(opts) {
|
|
61
|
+
const { budget, entries, hint } = opts;
|
|
62
|
+
const considered = Object.keys(OPERATORS).map((id) => {
|
|
63
|
+
const spec = OPERATORS[id];
|
|
64
|
+
const effectiveEntries = Math.min(entries, spec.maxEntries);
|
|
65
|
+
const cost = spec.costPerEntry * effectiveEntries;
|
|
66
|
+
return { id, cost, fits: cost <= budget };
|
|
67
|
+
}).sort((a, b) => a.cost - b.cost);
|
|
68
|
+
// Rule 1: honor a shape hint if the hinted operator fits.
|
|
69
|
+
const hintMap = {
|
|
70
|
+
duplicates: 'merge',
|
|
71
|
+
verbose: 'summarize',
|
|
72
|
+
patterns: 'distill',
|
|
73
|
+
general: 'compress',
|
|
74
|
+
};
|
|
75
|
+
if (hint && hint !== 'general') {
|
|
76
|
+
const preferred = hintMap[hint];
|
|
77
|
+
const match = considered.find((c) => c.id === preferred);
|
|
78
|
+
if (match?.fits) {
|
|
79
|
+
const spec = OPERATORS[match.id];
|
|
80
|
+
return {
|
|
81
|
+
operator: match.id,
|
|
82
|
+
reason: `Hint "${hint}" → ${match.id} (cost ${match.cost.toFixed(2)} ≤ budget ${budget})`,
|
|
83
|
+
estimatedCost: match.cost,
|
|
84
|
+
needsSplit: entries > spec.maxEntries,
|
|
85
|
+
suggestedBatchSize: spec.maxEntries,
|
|
86
|
+
considered,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Rule 2: pick the most-expensive operator that still fits.
|
|
91
|
+
const fitting = considered.filter((c) => c.fits);
|
|
92
|
+
if (fitting.length > 0) {
|
|
93
|
+
const best = fitting[fitting.length - 1]; // most expensive fitting
|
|
94
|
+
const spec = OPERATORS[best.id];
|
|
95
|
+
return {
|
|
96
|
+
operator: best.id,
|
|
97
|
+
reason: `Best-fit within budget: ${best.id} at cost ${best.cost.toFixed(2)} (budget ${budget}, entries ${entries})`,
|
|
98
|
+
estimatedCost: best.cost,
|
|
99
|
+
needsSplit: entries > spec.maxEntries,
|
|
100
|
+
suggestedBatchSize: spec.maxEntries,
|
|
101
|
+
considered,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Rule 3: budget too small for any operator on the full entry set.
|
|
105
|
+
// Split via merge (cheapest) with the merge batch size.
|
|
106
|
+
const mergeSpec = OPERATORS.merge;
|
|
107
|
+
const batchSize = Math.max(1, Math.floor(budget / mergeSpec.costPerEntry));
|
|
108
|
+
return {
|
|
109
|
+
operator: 'merge',
|
|
110
|
+
reason: `Budget ${budget} too small for any operator across ${entries} entries — split via merge in batches of ${batchSize}`,
|
|
111
|
+
estimatedCost: budget,
|
|
112
|
+
needsSplit: true,
|
|
113
|
+
suggestedBatchSize: batchSize,
|
|
114
|
+
considered,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=oas-operator-selector.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selective Context Memory (SCM) query-intent classifier (#2760).
|
|
3
|
+
*
|
|
4
|
+
* Dream-cycle #2760 (SCM routed memory 86% LongMemEval): agents perform
|
|
5
|
+
* dramatically better on long-memory retrieval when the query is routed
|
|
6
|
+
* to the RIGHT memory tier — episodic (session recall) vs semantic
|
|
7
|
+
* (learned patterns) vs procedural (skills/recipes). Ruflo previously
|
|
8
|
+
* had all these tiers writing to different namespaces but the search
|
|
9
|
+
* command didn't discriminate — every query searched everything.
|
|
10
|
+
*
|
|
11
|
+
* v1 classifier: keyword-scored intent detection. Ships as a bounded
|
|
12
|
+
* MVP so users can opt in via `memory search --intent auto|episodic|
|
|
13
|
+
* semantic|procedural|mixed`. Default remains `mixed` (existing
|
|
14
|
+
* behavior) so no baseline retrieval is disturbed.
|
|
15
|
+
*
|
|
16
|
+
* v2 (future): embedding-based classifier trained on the trajectory
|
|
17
|
+
* store's actual routing outcomes.
|
|
18
|
+
*/
|
|
19
|
+
export type MemoryIntent = 'episodic' | 'semantic' | 'procedural' | 'mixed';
|
|
20
|
+
export interface ClassifiedIntent {
|
|
21
|
+
intent: MemoryIntent;
|
|
22
|
+
confidence: number;
|
|
23
|
+
/** Per-intent scores for transparency; sums roughly to 1. */
|
|
24
|
+
scores: Record<Exclude<MemoryIntent, 'mixed'>, number>;
|
|
25
|
+
/** Namespaces the caller should search for this intent (relative to memory backend). */
|
|
26
|
+
namespaces: string[];
|
|
27
|
+
/** Human-readable rationale. */
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Keyword catalog per intent — case-insensitive substring match.
|
|
32
|
+
* Order matters for ties: earlier categories win when scores tie.
|
|
33
|
+
*/
|
|
34
|
+
declare const KEYWORDS: Record<Exclude<MemoryIntent, 'mixed'>, readonly string[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Namespaces owned by each intent. These match ruflo's actual
|
|
37
|
+
* memory-write conventions:
|
|
38
|
+
* episodic → session/trajectory writes (hooks_post-task, session-checkpoints)
|
|
39
|
+
* semantic → pattern/learning writes (patterns/, learned-*, adr-patterns)
|
|
40
|
+
* procedural → skill/workflow writes (skills/, agents/, workflow-templates)
|
|
41
|
+
* A namespace listed under more than one intent means it's genuinely dual-nature.
|
|
42
|
+
*/
|
|
43
|
+
declare const NAMESPACES: Record<Exclude<MemoryIntent, 'mixed'>, readonly string[]>;
|
|
44
|
+
export declare function classifyQueryIntent(query: string): ClassifiedIntent;
|
|
45
|
+
/**
|
|
46
|
+
* Given a user-requested intent (which may be `auto`), resolve to a
|
|
47
|
+
* concrete ClassifiedIntent for the given query.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveIntent(query: string, requested?: MemoryIntent | 'auto'): ClassifiedIntent;
|
|
50
|
+
export { KEYWORDS as INTENT_KEYWORDS, NAMESPACES as INTENT_NAMESPACES };
|
|
51
|
+
//# sourceMappingURL=scm-classifier.d.ts.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selective Context Memory (SCM) query-intent classifier (#2760).
|
|
3
|
+
*
|
|
4
|
+
* Dream-cycle #2760 (SCM routed memory 86% LongMemEval): agents perform
|
|
5
|
+
* dramatically better on long-memory retrieval when the query is routed
|
|
6
|
+
* to the RIGHT memory tier — episodic (session recall) vs semantic
|
|
7
|
+
* (learned patterns) vs procedural (skills/recipes). Ruflo previously
|
|
8
|
+
* had all these tiers writing to different namespaces but the search
|
|
9
|
+
* command didn't discriminate — every query searched everything.
|
|
10
|
+
*
|
|
11
|
+
* v1 classifier: keyword-scored intent detection. Ships as a bounded
|
|
12
|
+
* MVP so users can opt in via `memory search --intent auto|episodic|
|
|
13
|
+
* semantic|procedural|mixed`. Default remains `mixed` (existing
|
|
14
|
+
* behavior) so no baseline retrieval is disturbed.
|
|
15
|
+
*
|
|
16
|
+
* v2 (future): embedding-based classifier trained on the trajectory
|
|
17
|
+
* store's actual routing outcomes.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Keyword catalog per intent — case-insensitive substring match.
|
|
21
|
+
* Order matters for ties: earlier categories win when scores tie.
|
|
22
|
+
*/
|
|
23
|
+
const KEYWORDS = {
|
|
24
|
+
episodic: [
|
|
25
|
+
'when', 'last time', 'yesterday', 'today', 'session',
|
|
26
|
+
'recent', 'recently', 'trajectory', 'did i', 'did we',
|
|
27
|
+
'what happened', 'the previous', 'the last', 'earlier',
|
|
28
|
+
'this morning', 'this afternoon', 'this evening',
|
|
29
|
+
],
|
|
30
|
+
semantic: [
|
|
31
|
+
'how does', 'what is', 'why', 'why does', 'concept',
|
|
32
|
+
'pattern', 'design', 'architecture', 'principle',
|
|
33
|
+
'means', 'meaning', 'definition', 'explanation',
|
|
34
|
+
'relationship between',
|
|
35
|
+
],
|
|
36
|
+
procedural: [
|
|
37
|
+
'how do i', 'how to', 'steps to', 'recipe', 'playbook',
|
|
38
|
+
'procedure', 'walk me through', 'guide', 'runbook',
|
|
39
|
+
'checklist', 'template', 'workflow', 'skill',
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Namespaces owned by each intent. These match ruflo's actual
|
|
44
|
+
* memory-write conventions:
|
|
45
|
+
* episodic → session/trajectory writes (hooks_post-task, session-checkpoints)
|
|
46
|
+
* semantic → pattern/learning writes (patterns/, learned-*, adr-patterns)
|
|
47
|
+
* procedural → skill/workflow writes (skills/, agents/, workflow-templates)
|
|
48
|
+
* A namespace listed under more than one intent means it's genuinely dual-nature.
|
|
49
|
+
*/
|
|
50
|
+
const NAMESPACES = {
|
|
51
|
+
episodic: [
|
|
52
|
+
'sessions', 'session-checkpoints', 'trajectory', 'trajectories',
|
|
53
|
+
'routing-outcomes', 'commands', 'feedback',
|
|
54
|
+
],
|
|
55
|
+
semantic: [
|
|
56
|
+
'patterns', 'learned-patterns', 'adr-patterns', 'adr-edges',
|
|
57
|
+
'reasoning-patterns', 'concepts',
|
|
58
|
+
],
|
|
59
|
+
procedural: [
|
|
60
|
+
'skills', 'agents', 'workflow-templates', 'playbooks', 'recipes',
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
const CONFIDENCE_THRESHOLD = 0.35;
|
|
64
|
+
export function classifyQueryIntent(query) {
|
|
65
|
+
const q = query.toLowerCase();
|
|
66
|
+
const scores = {
|
|
67
|
+
episodic: 0,
|
|
68
|
+
semantic: 0,
|
|
69
|
+
procedural: 0,
|
|
70
|
+
};
|
|
71
|
+
// Score by keyword hits (weighted by phrase length — longer phrases
|
|
72
|
+
// are less ambiguous, e.g. "how do i" beats "how").
|
|
73
|
+
for (const intent of Object.keys(KEYWORDS)) {
|
|
74
|
+
for (const kw of KEYWORDS[intent]) {
|
|
75
|
+
if (q.includes(kw)) {
|
|
76
|
+
// Weight: longer keyword → higher confidence
|
|
77
|
+
scores[intent] += Math.max(1, kw.length / 6);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const total = scores.episodic + scores.semantic + scores.procedural;
|
|
82
|
+
if (total === 0) {
|
|
83
|
+
return {
|
|
84
|
+
intent: 'mixed',
|
|
85
|
+
confidence: 0,
|
|
86
|
+
scores,
|
|
87
|
+
namespaces: [],
|
|
88
|
+
reason: 'No intent keywords matched — falling back to mixed retrieval (search all namespaces).',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Normalize
|
|
92
|
+
const normalized = {
|
|
93
|
+
episodic: scores.episodic / total,
|
|
94
|
+
semantic: scores.semantic / total,
|
|
95
|
+
procedural: scores.procedural / total,
|
|
96
|
+
};
|
|
97
|
+
// Pick the winner
|
|
98
|
+
const entries = [
|
|
99
|
+
['episodic', normalized.episodic],
|
|
100
|
+
['semantic', normalized.semantic],
|
|
101
|
+
['procedural', normalized.procedural],
|
|
102
|
+
];
|
|
103
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
104
|
+
const [winner, winnerScore] = entries[0];
|
|
105
|
+
// Low-confidence winner → mixed
|
|
106
|
+
if (winnerScore < CONFIDENCE_THRESHOLD) {
|
|
107
|
+
return {
|
|
108
|
+
intent: 'mixed',
|
|
109
|
+
confidence: winnerScore,
|
|
110
|
+
scores: normalized,
|
|
111
|
+
namespaces: [],
|
|
112
|
+
reason: `Top intent (${winner}) below confidence threshold ${CONFIDENCE_THRESHOLD} — falling back to mixed retrieval.`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
intent: winner,
|
|
117
|
+
confidence: winnerScore,
|
|
118
|
+
scores: normalized,
|
|
119
|
+
namespaces: [...NAMESPACES[winner]],
|
|
120
|
+
reason: `Classified as ${winner} (confidence ${(winnerScore * 100).toFixed(1)}%) — route to ${NAMESPACES[winner].length} namespaces.`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Given a user-requested intent (which may be `auto`), resolve to a
|
|
125
|
+
* concrete ClassifiedIntent for the given query.
|
|
126
|
+
*/
|
|
127
|
+
export function resolveIntent(query, requested = 'auto') {
|
|
128
|
+
if (requested === 'auto')
|
|
129
|
+
return classifyQueryIntent(query);
|
|
130
|
+
if (requested === 'mixed') {
|
|
131
|
+
return {
|
|
132
|
+
intent: 'mixed',
|
|
133
|
+
confidence: 1,
|
|
134
|
+
scores: { episodic: 0, semantic: 0, procedural: 0 },
|
|
135
|
+
namespaces: [],
|
|
136
|
+
reason: 'Explicitly requested mixed retrieval (search all namespaces).',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
intent: requested,
|
|
141
|
+
confidence: 1,
|
|
142
|
+
scores: { episodic: 0, semantic: 0, procedural: 0, [requested]: 1 },
|
|
143
|
+
namespaces: [...NAMESPACES[requested]],
|
|
144
|
+
reason: `Explicitly requested ${requested} retrieval — route to ${NAMESPACES[requested].length} namespaces.`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export { KEYWORDS as INTENT_KEYWORDS, NAMESPACES as INTENT_NAMESPACES };
|
|
148
|
+
//# sourceMappingURL=scm-classifier.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.19",
|
|
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",
|