mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 00:48:18 +00:00
Adversarial review: survived a hostile reviewer plus two independent refuters, each told to assume the PR was broken and to default to refuting when uncertain. 19 of 62 PRs cleared that bar. One-character regex fix with a real user-visible effect: "what do **I** know about X" was classified as a general query while the you/we phrasings were correctly classified as entity queries. The new alternation is a strict superset, so no previously-matching phrasing changes, and stubbing the old regex back fails at the exact assertion. Closes verified issue #3615. Verified before merge: the PR's own tests fail when the production change is reverted; typecheck clean; MERGEABLE/CLEAN on the current base after batch 1 landed, not a stale one.
166 lines
6.1 KiB
TypeScript
166 lines
6.1 KiB
TypeScript
/**
|
|
* v0.29.1 — merged query-intent classifier tests.
|
|
*
|
|
* Covers the new classifyQuery(query) returning {intent, suggestedDetail,
|
|
* suggestedSalience, suggestedRecency}. Legacy intent.ts behavior is
|
|
* preserved in test/query-intent-legacy.test.ts (which imports the
|
|
* classifyQueryIntent + autoDetectDetail compat shims).
|
|
*
|
|
* Pure regex; no DB.
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import { classifyQuery } from '../src/core/search/query-intent.ts';
|
|
|
|
describe('classifyQuery — entity / canonical queries → both axes off', () => {
|
|
test('"who is widget-ceo" → recency=off, salience=off', () => {
|
|
const r = classifyQuery('who is widget-ceo');
|
|
expect(r.intent).toBe('entity');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
expect(r.suggestedDetail).toBe('low');
|
|
});
|
|
|
|
test('"what is recursion" → both off', () => {
|
|
const r = classifyQuery('what is recursion');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
});
|
|
|
|
test('"tell me about widget-co" → both off', () => {
|
|
const r = classifyQuery('tell me about widget-co');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
});
|
|
|
|
test('"what do I know about widget-co" → entity, same as you/we phrasings', () => {
|
|
const r = classifyQuery('what do I know about widget-co');
|
|
expect(r.intent).toBe('entity');
|
|
expect(r.suggestedDetail).toBe('low');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
});
|
|
|
|
test('"history of X" → both off (canonical)', () => {
|
|
const r = classifyQuery('history of acme corp');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
});
|
|
|
|
test('code lookup syntax → both off', () => {
|
|
const r = classifyQuery('Foo::bar() returns null');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
});
|
|
|
|
test('graph traversal language → both off', () => {
|
|
const r = classifyQuery('show me backlinks to widget-co');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
});
|
|
});
|
|
|
|
describe('classifyQuery — current-state queries → both axes on', () => {
|
|
test('"what\'s going on with widget-co" → both on', () => {
|
|
const r = classifyQuery("what's going on with widget-co");
|
|
expect(r.suggestedRecency).toBe('on');
|
|
expect(r.suggestedSalience).toBe('on');
|
|
});
|
|
|
|
test('"catch me up on acme" → both on', () => {
|
|
const r = classifyQuery('catch me up on acme');
|
|
expect(r.suggestedRecency).toBe('on');
|
|
expect(r.suggestedSalience).toBe('on');
|
|
});
|
|
|
|
test('"prep me for the widget-ceo meeting" → both on', () => {
|
|
const r = classifyQuery('prep me for the widget-ceo meeting');
|
|
expect(r.suggestedRecency).toBe('on');
|
|
expect(r.suggestedSalience).toBe('on');
|
|
});
|
|
|
|
test('"before my meeting with X" → both on', () => {
|
|
const r = classifyQuery('before my meeting with widget-ceo');
|
|
expect(r.suggestedRecency).toBe('on');
|
|
expect(r.suggestedSalience).toBe('on');
|
|
});
|
|
|
|
test('"remind me about acme" → both on', () => {
|
|
const r = classifyQuery('remind me about acme');
|
|
expect(r.suggestedRecency).toBe('on');
|
|
expect(r.suggestedSalience).toBe('on');
|
|
});
|
|
});
|
|
|
|
describe('classifyQuery — recency-only patterns (no salience signal)', () => {
|
|
test('"latest news on AI" → recency=on, salience=off', () => {
|
|
const r = classifyQuery('latest news on AI');
|
|
expect(r.suggestedRecency).toBe('on');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
});
|
|
|
|
test('"this week\'s updates" → recency=on, salience=off', () => {
|
|
const r = classifyQuery("this week's updates");
|
|
expect(r.suggestedRecency).toBe('on');
|
|
// "updates" + "on/with/from" pattern needed for salience
|
|
expect(r.suggestedSalience).toBe('off');
|
|
});
|
|
});
|
|
|
|
describe('classifyQuery — strong recency ("today" / "right now")', () => {
|
|
test('"what happened today" → recency=strong', () => {
|
|
const r = classifyQuery('what happened today');
|
|
expect(r.suggestedRecency).toBe('strong');
|
|
});
|
|
|
|
test('"right now what is the status" → strong', () => {
|
|
const r = classifyQuery('right now what is the status of the deal');
|
|
// "what is" canonical fires; but "right now" is a temporal bound
|
|
expect(r.suggestedRecency).toBe('strong');
|
|
});
|
|
});
|
|
|
|
describe('classifyQuery — D6 narrow temporal-bound exception', () => {
|
|
test('"who is widget-ceo right now" → recency=strong (temporal bound wins)', () => {
|
|
const r = classifyQuery('who is widget-ceo right now');
|
|
expect(r.suggestedRecency).toBe('strong');
|
|
});
|
|
|
|
test('"who is widget-ceo today" → recency=strong', () => {
|
|
const r = classifyQuery('who is widget-ceo today');
|
|
expect(r.suggestedRecency).toBe('strong');
|
|
});
|
|
|
|
test('"who is widget-ceo" (no bound) → recency=off (canonical wins)', () => {
|
|
const r = classifyQuery('who is widget-ceo');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
});
|
|
|
|
test('"what is widget-co\'s status this week" → recency=on (temporal bound wins)', () => {
|
|
const r = classifyQuery("what is widget-co's status this week");
|
|
expect(r.suggestedRecency).toBe('on');
|
|
});
|
|
});
|
|
|
|
describe('classifyQuery — orthogonality of axes', () => {
|
|
test('default plain query → both off', () => {
|
|
const r = classifyQuery('the quick brown fox');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
expect(r.intent).toBe('general');
|
|
expect(r.suggestedDetail).toBeUndefined();
|
|
});
|
|
|
|
test('intent vs recency are independent axes', () => {
|
|
// "when did widget-co IPO": both 'when' (temporal) and 'IPO' (event)
|
|
// match v0.29.0 patterns. classifyQueryIntent's priority is
|
|
// temporal > event so .intent = 'temporal'. But recency depends on
|
|
// CANONICAL/RECENCY_ON patterns, not on .intent — neither set
|
|
// matches here, so suggestedRecency = 'off'.
|
|
const r = classifyQuery('when did widget-co IPO');
|
|
expect(r.intent).toBe('temporal');
|
|
expect(r.suggestedRecency).toBe('off');
|
|
expect(r.suggestedSalience).toBe('off');
|
|
});
|
|
});
|