Files
gbrain/test/context/entity-salience.test.ts
8f45624e55 v0.42.39.0 feat(context): Retrieval Reflex — teach the agent when/what to retrieve (#1981) (#2019)
* fix(integrations): parameterize resolver-row fence by recipe id

The install fence was hardcoded gbrain:agent-voice:resolver-rows, so any
second copy-into-host-repo recipe wrote a mislabeled block (and refresh/
uninstall keyed on recipe id would miss it). Derive it from manifest.recipe.

* feat(context): Retrieval Reflex — teach the agent when/what to retrieve (#1981)

Deterministic per-turn pointer layer in the context engine: a zero-LLM,
precision-biased scan resolves salient entities (names, @handles) to existing
brain pages and injects compact pointers (name → slug → safe synopsis). Detect
+ point, never auto-dump. Fail-open, capped, suppression on prior context only.

Engine-aware resolver ladder (no second DB connection): host ctx.brainQuery →
PGLite serve resolve IPC (unix socket) → Postgres cached direct → disabled.
Synopsis runs through get_page's privacy strip. Plus the retrieval-reflex recipe
+ policy skill, the retrieval_reflex_health doctor check, config gate, and the
init next-step hint.

* chore: bump version and changelog (v0.42.38.0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(architecture): KEY_FILES entries for Retrieval Reflex surface (#1981)

Document the new src/core/context/ modules, the context-engine resolver
ladder, the serve resolve IPC, the retrieval_reflex_health doctor check,
and the recipe-id-keyed install fence. Current-state only.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:12:33 -07:00

74 lines
2.6 KiB
TypeScript

/**
* Unit tests for the Retrieval Reflex pure extractor (#1981, T1).
* No DB, no SDK — just the deterministic candidate extraction + precision filters.
*/
import { describe, test, expect } from 'bun:test';
import { extractCandidates, MAX_CANDIDATES } from '../../src/core/context/entity-salience.ts';
function queries(text: string): string[] {
return extractCandidates(text).map((c) => c.query);
}
describe('extractCandidates', () => {
test('multi-word capitalized run', () => {
expect(queries('what do you think about Garry Tan?')).toContain('Garry Tan');
});
test('@handles are captured without the @ in the query, with @ in display', () => {
const c = extractCandidates('ping @garry about it');
const handle = c.find((x) => x.display === '@garry');
expect(handle).toBeDefined();
expect(handle!.query).toBe('garry');
});
test('drops hard stopwords even capitalized', () => {
const q = queries('What should We do? The plan is set.');
expect(q).not.toContain('What');
expect(q).not.toContain('We');
expect(q).not.toContain('The');
});
test('drops weekday/common words seen only at sentence start', () => {
expect(queries('Monday we ship. Today is busy.')).toEqual([]);
});
test('keeps a real name even at sentence start', () => {
expect(queries('Sarah went home early.')).toContain('Sarah');
});
test('keeps a common-looking word if also seen capitalized mid-sentence', () => {
// "Apple" appears mid-sentence → strong entity signal, kept despite being common-ish.
expect(queries('I love Apple. Apple makes phones.')).toContain('Apple');
});
test('rejects single chars and pure numbers', () => {
const q = queries('A 2026 plan');
expect(q).not.toContain('A');
expect(q).not.toContain('2026');
});
test('strips possessive', () => {
expect(queries("Garry's idea")).toContain('Garry');
});
test('dedups on normalized form', () => {
const q = queries('Garry and Garry again');
expect(q.filter((x) => x.toLowerCase() === 'garry')).toHaveLength(1);
});
test('caps at MAX_CANDIDATES', () => {
const many = Array.from({ length: 30 }, (_, i) => `Person${String.fromCharCode(65 + (i % 26))}x${i}`).join(' ');
expect(extractCandidates(many).length).toBeLessThanOrEqual(MAX_CANDIDATES);
});
test('empty / non-string input → []', () => {
expect(extractCandidates('')).toEqual([]);
// @ts-expect-error intentional bad input
expect(extractCandidates(null)).toEqual([]);
});
test('documented v1 limit: lowercase names are NOT detected', () => {
expect(queries('what about garry tan')).toEqual([]);
});
});