Files
ruvnet__ruflo/v3/@claude-flow/cli/__tests__/scm-classifier-2760.test.ts
ruv 79d266507e feat(memory): SCM query-intent classifier + routing hints (#2760 dream)
Dream-cycle #2760 (SCM routed memory 86% LongMemEval): agents retrieve
dramatically better on long-memory workloads when queries are routed to
the RIGHT memory tier — episodic (session recall), semantic (learned
patterns), or procedural (skills/recipes). Ruflo had all three tiers
writing to different namespaces but `memory search` never discriminated.

v1 MVP: keyword-scored classifier. Ships two integration surfaces:

1. Standalone: `ruflo memory classify -q "…" [--intent auto|mixed|episodic|
   semantic|procedural]` — prints the resolved intent + confidence +
   suggested namespaces. `--format json` for pipelines.

2. Search hint: `ruflo memory search -q "…" --intent auto` — logs the
   SCM router's decision + suggests a specific --namespace value the
   user can rerun with. Intentionally does NOT mutate the search
   backend behavior in v1 — the routing is advisory so no baseline
   retrieval is disturbed. v2 will apply when the search backend
   adds a multi-namespace OR filter.

Namespace map (matches ruflo's actual write conventions):
  episodic   → sessions, session-checkpoints, trajectory, trajectories,
               routing-outcomes, commands, feedback
  semantic   → patterns, learned-patterns, adr-patterns, adr-edges,
               reasoning-patterns, concepts
  procedural → skills, agents, workflow-templates, playbooks, recipes

Classifier weighting: keyword length / 6 (longer phrases score higher
because they're less ambiguous — "how do i" beats "how"). Confidence
threshold 0.35; below → mixed fallback so a low-confidence guess
doesn't misroute.

E2E:
  "when did we last touch auth"          → episodic  (conf 100%)
  "how does JWT work"                    → semantic  (conf 100%)
  "how do I onboard a new coder subagent" → procedural (conf 100%)
  "auth"                                 → mixed     (conf 0, no keywords)

Regression tests: 7/7 pass (three canonical intents + mixed fallback
+ explicit override + auto delegates to classifier + mixed explicit).

Refs: dream-cycle #2760 (2026-07-22).

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-26 22:55:12 -04:00

58 lines
2.4 KiB
TypeScript

/**
* Regression guard for #2760 dream-cycle SCM (Selective Context Memory)
* query-intent classifier.
*
* v1 classifier is keyword-scored. Must correctly route the three canonical
* intent classes (episodic / semantic / procedural), degrade gracefully to
* `mixed` on ambiguous queries, and honor an explicit override.
*/
import { describe, it, expect } from 'vitest';
import { classifyQueryIntent, resolveIntent, INTENT_NAMESPACES } from '../src/memory/scm-classifier.js';
describe('#2760 SCM classifier', () => {
it('classifies episodic recall ("when did we last …")', () => {
const r = classifyQueryIntent('When did we last touch the auth module?');
expect(r.intent).toBe('episodic');
expect(r.namespaces).toEqual(expect.arrayContaining(['sessions', 'trajectory']));
expect(r.confidence).toBeGreaterThan(0.35);
});
it('classifies semantic explanation ("how does X work" / "what is Y")', () => {
const r = classifyQueryIntent('What is the design principle behind our AgentDB memory tiers?');
expect(r.intent).toBe('semantic');
expect(r.namespaces).toEqual(expect.arrayContaining(['patterns']));
});
it('classifies procedural how-to ("how do I …" / "steps to …")', () => {
const r = classifyQueryIntent('How do I set up a new subagent role with workspace-scoped permissions?');
expect(r.intent).toBe('procedural');
expect(r.namespaces).toEqual(expect.arrayContaining(['skills']));
});
it('falls back to mixed on an ambiguous query with no intent keywords', () => {
const r = classifyQueryIntent('auth');
expect(r.intent).toBe('mixed');
expect(r.namespaces).toEqual([]);
expect(r.confidence).toBeLessThan(0.35);
});
it('resolveIntent respects an explicit override', () => {
const r = resolveIntent('completely ambiguous three word query', 'procedural');
expect(r.intent).toBe('procedural');
expect(r.namespaces).toEqual([...INTENT_NAMESPACES.procedural]);
});
it('resolveIntent auto path delegates to the keyword classifier', () => {
const auto = resolveIntent('How do I onboard a new coder subagent?', 'auto');
const direct = classifyQueryIntent('How do I onboard a new coder subagent?');
expect(auto.intent).toBe(direct.intent);
});
it('resolveIntent mixed explicit returns full namespaces empty', () => {
const r = resolveIntent('anything', 'mixed');
expect(r.intent).toBe('mixed');
expect(r.namespaces).toEqual([]);
});
});