mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-28 14:59:47 +00:00
* 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>
74 lines
2.6 KiB
TypeScript
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([]);
|
|
});
|
|
});
|