Files
ruvnet__ruflo/v3/@claude-flow/cli/__tests__/structured-distill-2241.test.ts
ruv 2b9e2de89f feat(intelligence): Round B (post-edit/-command + trajectory-end → globalStats) + Round C structured distillation (ADR-076)
Round B — closes the wiring-side gap from ADR-074 / ADR-075:
- hooks_post-edit now synthesises a one-step trajectory from {filePath,
  success, agent} and calls recordTrajectory(), so globalStats reflects
  edit outcomes.
- hooks_post-command does the same for command outcomes.
- hooks_intelligence_trajectory-end also bumps globalStats (was only
  feeding sonaCoordinator); the response includes
  learning.globalStatsTrajectoriesDelta.
- Each handler returns learningPath ('trajectory-pipeline'|'recorded-only')
  + a note naming exactly what fired.

Round C — Structured Distillation (#2241 §SOTA, arXiv:2603.13017):
- src/memory/structured-distill.ts: 4-field schema (summary/detail/labels/
  paths), rule-based extractor, embedding-ready serialiser that places
  high-signal tokens (labels, paths) at the front.
- bench/trajectory-mrr-corpus.json: 30 paired (raw, query) trajectories.
- scripts/benchmark-trajectory-mrr.mjs: raw vs distilled retrieval MRR
  harness; uses bridge ONNX embedder, falls back to hash-deterministic
  with an explicit "degraded" warning.

Measured proof (bridge ONNX, Xenova/all-MiniLM-L6-v2, N=30):
  MRR raw 0.0964 → distilled 0.1367 (Δ +0.0403, +41.8% relative).
  Direction matches arXiv:2603.13017 (+0.014 absolute on a 214K paper
  corpus); relative delta is larger here because the small curated corpus
  benefits more from the labels-and-paths-first serialisation order.

Tests: 9 Round-C schema tests + 3 Round-B wiring tests = 12 new.
Full affected suite (self-learning + unified-stats + round-b + structured-
distill + mcp-tools-deep): 135/135.

Honest: a rule-based distiller cannot deliver the paper's 11× byte
compression (current ratio: 0.74× — distilled is 35% bigger). The schema,
corpus, harness, and serialiser are in place so a future round can plug
in a learned distiller as a drop-in extractor swap and pick up that
compression number while keeping this MRR direction.

Bump 3.10.15 → 3.10.16.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-30 12:30:58 -04:00

104 lines
4.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Structured Distillation regression tests (#2241 §SOTA, arXiv:2603.13017).
//
// Asserts the 4-field schema extractor behaves deterministically and the
// serialised form lands high-signal tokens (labels, paths) at the front.
import { describe, it, expect } from 'vitest';
import {
distillTrajectoryContent,
serialiseDistilled,
distillAndSerialise,
compressionRatio,
} from '../src/memory/structured-distill.js';
describe('distillTrajectoryContent — 4-field schema', () => {
it('returns the four documented fields', () => {
const d = distillTrajectoryContent('Refactored src/auth/middleware.ts to use jwt-verify helper.');
expect(d.summary).toBeDefined();
expect(d.detail).toBeDefined();
expect(d.labels).toBeDefined();
expect(d.paths).toBeDefined();
expect(Array.isArray(d.labels)).toBe(true);
expect(Array.isArray(d.paths)).toBe(true);
});
it('extracts file paths and file:line refs', () => {
const d = distillTrajectoryContent(
'Fix at src/auth/middleware.ts:45 — also touched __tests__/auth.test.ts and docs/auth.md.',
);
expect(d.paths).toContain('src/auth/middleware.ts:45');
expect(d.paths).toContain('__tests__/auth.test.ts');
expect(d.paths).toContain('docs/auth.md');
});
it('extracts action labels from the verb vocabulary', () => {
const d = distillTrajectoryContent(
'Refactor the authentication module. Add tests. Remove the dead branch.',
);
expect(d.labels).toContain('refactor');
expect(d.labels).toContain('add');
expect(d.labels).toContain('test');
expect(d.labels).toContain('remove');
});
it('summary is the first sentence (capped)', () => {
const d = distillTrajectoryContent(
'Fixed the SQL injection bug. Wrapped the table identifier with validateSqlIdentifier. Added a regression test.',
);
expect(d.summary).toBe('Fixed the SQL injection bug.');
expect(d.detail).toMatch(/Wrapped the table identifier/);
});
it('is deterministic — same input produces same output', () => {
const text = 'Refactor src/foo.ts and add tests at __tests__/foo.test.ts:10';
const a = distillTrajectoryContent(text);
const b = distillTrajectoryContent(text);
expect(a).toEqual(b);
});
it('handles empty input gracefully', () => {
const d = distillTrajectoryContent('');
expect(d.summary).toBe('');
expect(d.detail).toBe('');
expect(d.labels).toEqual([]);
expect(d.paths).toEqual([]);
});
});
describe('serialiseDistilled — embedding-ready format', () => {
it('starts with the labels block (high-signal tokens lead)', () => {
const d = distillTrajectoryContent(
'Refactor src/middleware.ts to use jwt-verify. Updated docs/auth.md.',
);
const s = serialiseDistilled(d);
expect(s.startsWith('[')).toBe(true);
});
it('includes a paths block when source contains file refs', () => {
const d = distillTrajectoryContent(
'Fix in src/auth.ts:45 plus a docs/auth.md update.',
);
const s = serialiseDistilled(d);
expect(s.indexOf('paths:')).toBeGreaterThanOrEqual(0);
expect(s).toContain('src/auth.ts:45');
});
});
describe('compressionRatio — honest about the rule-based limit', () => {
// The arXiv paper's 11× compression comes from a LEARNED distiller. Our
// rule-based version preserves the full detail (paths + labels prepended),
// so it does NOT compress in bytes — the schema is about *structure for
// retrieval*, not size. A future round can plug in a learned compressor.
it('does not bloat raw content beyond ~2× — rule-based preservation', () => {
const raw = 'I refactored the authentication middleware in src/auth/middleware.ts. ' +
'Replaced the manual JWT parsing logic with the jwt-verify helper from @claude-flow/security. ' +
'This removes ~80 lines of duplicated code and centralises the token-validation contract. ' +
'Touched src/auth/middleware.ts:45-128 and added a regression test at __tests__/auth-middleware.test.ts. ' +
'Verified the change passes existing integration tests at __tests__/integration/auth.test.ts.';
const r = compressionRatio(raw);
// ratio > 1 means distilled is smaller; ratio < 1 means it grew.
// Acceptable band: not larger than 2× (i.e. ratio >= 0.5).
expect(r).toBeGreaterThanOrEqual(0.5);
});
});