From 512119d364b1e83a3ee31901d636dd5ecdf0cd0f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste THERY Date: Wed, 19 Aug 2026 21:42:26 +0700 Subject: [PATCH 1/2] fix(core): anchor compound lexical identifiers Release highlights: - Keep compound-identifier retrieval deterministic when broad lexical matches saturate the FTS pool. Release details: - Require an exact identifier anchor before contextual scoring and retain fuzzy and broad backfill. - Cover bounded exact candidates, typo recovery, and distractor-heavy retrieval. Verification: - Run the reproducible S local-hash quality benchmark with 100 of 100 cases passing. - Run pnpm validate. --- packages/ragmir-core/src/query.test.ts | 47 ++++++++++++++++++++++++ packages/ragmir-core/src/query.ts | 49 ++++++++++++++++++++------ 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/packages/ragmir-core/src/query.test.ts b/packages/ragmir-core/src/query.test.ts index bd2927c..0cb52e5 100644 --- a/packages/ragmir-core/src/query.test.ts +++ b/packages/ragmir-core/src/query.test.ts @@ -408,6 +408,53 @@ describe("search", () => { expect(results[0]?.relativePath).toBe(".ragmir/raw/zeta.md") }) + it("should anchor compound identifiers before broad lexical backfill", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "ragmir-query-identifier-anchor-")) + tempDirs.push(root) + await initProject(root) + await mkdir(path.join(root, ".ragmir", "raw"), { recursive: true }) + await writeFile( + path.join(root, ".ragmir", "config.json"), + JSON.stringify({ retrievalProfile: "fast", topK: 10 }), + ) + await Promise.all([ + ...Array.from({ length: 50 }, (_entry, index) => + writeFile( + path.join(root, ".ragmir", "raw", `target-${String(index).padStart(3, "0")}.md`), + `Find evidence for compound identifier BENCH-IDENTIFIER-14 target ${index}.\n`, + ), + ), + ...Array.from({ length: 120 }, (_entry, index) => + writeFile( + path.join(root, ".ragmir", "raw", `distractor-${String(index).padStart(3, "0")}.md`), + `Find broad evidence for an unrelated routine ${index}.\n`, + ), + ), + ]) + await ingest({ cwd: root }) + + const exact = await search("Find evidence for BENCH-IDENTIFIER-14", { + cwd: root, + topK: 10, + explain: true, + }) + const fuzzy = await search("Find evidence for BENCH-IDENTIFIXR-14", { + cwd: root, + topK: 1, + explain: true, + }) + + expect(exact).toHaveLength(10) + expect(exact.every((result) => result.text.includes("BENCH-IDENTIFIER-14"))).toBe(true) + expect(exact[0]?.score).toMatchObject({ + lexicalBackend: "fts", + lexicalCandidatesMaterialized: 50, + lexicalQueryVariants: 1, + }) + expect(fuzzy[0]?.text).toContain("BENCH-IDENTIFIER-14") + expect(fuzzy[0]?.score?.lexicalQueryVariants).toBeGreaterThan(1) + }, 15_000) + it("should scan a complete lexical fallback in bounded batches", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "ragmir-query-truncated-fallback-")) tempDirs.push(root) diff --git a/packages/ragmir-core/src/query.ts b/packages/ragmir-core/src/query.ts index 4493381..6a92c31 100644 --- a/packages/ragmir-core/src/query.ts +++ b/packages/ragmir-core/src/query.ts @@ -1,8 +1,10 @@ import { channel } from "node:diagnostics_channel" import { + BooleanQuery, type Connection, type FullTextQuery, MatchQuery, + Occur, Operator, PhraseQuery, } from "@lancedb/lancedb" @@ -977,20 +979,47 @@ function lexicalQuery( return null } const joined = tokens.join(" ") + const broadQuery = new MatchQuery(joined, "searchText", { operator: Operator.Or }) const supplemental: FullTextQuery[] = [] - if (tokens.length > 1) { - supplemental.push(new PhraseQuery(joined, "searchText")) - } const identifierTerms = [...query.matchAll(LEXICAL_IDENTIFIER_PATTERN)] .map((match) => match[0]) .filter(Boolean) - for (const identifier of [...new Set(identifierTerms)]) { - supplemental.push( - new MatchQuery(identifier, "searchText", { - boost: 2, - ...(isFuzzyLexicalTerm(identifier) ? { fuzziness: 1, prefixLength: 3 } : {}), - }), + const identifiers = [...new Set(identifierTerms)] + const firstIdentifier = identifiers[0] + if (firstIdentifier !== undefined) { + const firstExactIdentifierQuery = new PhraseQuery(firstIdentifier, "searchText") + const exactIdentifierQueries = [ + firstExactIdentifierQuery, + ...identifiers.slice(1).map((identifier) => new PhraseQuery(identifier, "searchText")), + ] + const exactIdentifierQuery: FullTextQuery = + exactIdentifierQueries.length === 1 + ? firstExactIdentifierQuery + : new BooleanQuery( + exactIdentifierQueries.map((item): [Occur, FullTextQuery] => [Occur.Should, item]), + ) + const fuzzyIdentifierQuery = new BooleanQuery( + identifiers.map((identifier): [Occur, FullTextQuery] => [ + Occur.Should, + new MatchQuery(identifier, "searchText", { + boost: 2, + fuzziness: 1, + operator: Operator.And, + prefixLength: 3, + }), + ]), ) + supplemental.push(fuzzyIdentifierQuery, broadQuery) + return { + primary: new BooleanQuery([ + [Occur.Must, exactIdentifierQuery], + [Occur.Should, broadQuery], + ]), + supplemental, + } + } + if (tokens.length > 1) { + supplemental.push(new PhraseQuery(joined, "searchText")) } const rareTerms = tokens .filter(isFuzzyLexicalTerm) @@ -1006,7 +1035,7 @@ function lexicalQuery( ) } return { - primary: new MatchQuery(joined, "searchText", { operator: Operator.Or }), + primary: broadQuery, supplemental, } } From 679998eba1da4228b0d3459b7cf1698b38b37d78 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste THERY Date: Wed, 19 Aug 2026 21:54:28 +0700 Subject: [PATCH 2/2] test(core): bound retrieval fixture load Release highlights: - Keep lexical retrieval coverage reliable on constrained CI runners. Release details: - Reduce identifier and path fallback fixtures while preserving candidate-bound assertions. Verification: - Run the full Core coverage suite with 546 passing tests. --- packages/ragmir-core/src/query.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/ragmir-core/src/query.test.ts b/packages/ragmir-core/src/query.test.ts index 0cb52e5..96c41a3 100644 --- a/packages/ragmir-core/src/query.test.ts +++ b/packages/ragmir-core/src/query.test.ts @@ -415,16 +415,16 @@ describe("search", () => { await mkdir(path.join(root, ".ragmir", "raw"), { recursive: true }) await writeFile( path.join(root, ".ragmir", "config.json"), - JSON.stringify({ retrievalProfile: "fast", topK: 10 }), + JSON.stringify({ retrievalProfile: "fast", topK: 2 }), ) await Promise.all([ - ...Array.from({ length: 50 }, (_entry, index) => + ...Array.from({ length: 8 }, (_entry, index) => writeFile( path.join(root, ".ragmir", "raw", `target-${String(index).padStart(3, "0")}.md`), `Find evidence for compound identifier BENCH-IDENTIFIER-14 target ${index}.\n`, ), ), - ...Array.from({ length: 120 }, (_entry, index) => + ...Array.from({ length: 12 }, (_entry, index) => writeFile( path.join(root, ".ragmir", "raw", `distractor-${String(index).padStart(3, "0")}.md`), `Find broad evidence for an unrelated routine ${index}.\n`, @@ -435,7 +435,7 @@ describe("search", () => { const exact = await search("Find evidence for BENCH-IDENTIFIER-14", { cwd: root, - topK: 10, + topK: 2, explain: true, }) const fuzzy = await search("Find evidence for BENCH-IDENTIFIXR-14", { @@ -444,11 +444,11 @@ describe("search", () => { explain: true, }) - expect(exact).toHaveLength(10) + expect(exact).toHaveLength(2) expect(exact.every((result) => result.text.includes("BENCH-IDENTIFIER-14"))).toBe(true) expect(exact[0]?.score).toMatchObject({ lexicalBackend: "fts", - lexicalCandidatesMaterialized: 50, + lexicalCandidatesMaterialized: 8, lexicalQueryVariants: 1, }) expect(fuzzy[0]?.text).toContain("BENCH-IDENTIFIER-14") @@ -492,7 +492,7 @@ describe("search", () => { "Routine evidence without query terms.\n", ) await Promise.all( - Array.from({ length: 120 }, (_entry, index) => + Array.from({ length: 90 }, (_entry, index) => writeFile( path.join(root, ".ragmir", "raw", `distractor-${String(index).padStart(3, "0")}.md`), `Ragmir raw policy md distractor evidence ${index}.\n`, @@ -514,7 +514,7 @@ describe("search", () => { lexicalBackend: "fallback", lexicalExactPathMatch: true, }) - expect(vectorCandidateLimit(1)).toBeLessThan(120) + expect(vectorCandidateLimit(1)).toBeLessThan(90) }, 10_000) it("should explain complete lexical fallback activation and coverage", async () => {