@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
22
33import {
44 lexicalFields ,
5+ NO_LEXICAL_PARAMS ,
56 scoreLexical ,
67 tokenizeLexical ,
78} from "./lexical-rank.js" ;
@@ -11,6 +12,7 @@ describe("scoreLexical", () => {
1112 const fields = lexicalFields (
1213 "mcp__linear__list_release_notes" ,
1314 "List release notes in the workspace" ,
15+ NO_LEXICAL_PARAMS ,
1416 ) ;
1517 // Same raw query on both sides so only the token list differs; the raw
1618 // query feeds a separate whole-query substring bonus.
@@ -19,4 +21,81 @@ describe("scoreLexical", () => {
1921 scoreLexical ( fields , tokenizeLexical ( "release notes release" ) , rawQuery ) ,
2022 ) . toBe ( scoreLexical ( fields , tokenizeLexical ( "release notes" ) , rawQuery ) ) ;
2123 } ) ;
24+
25+ // Two-word raw query that no fixture contains whole, so the whole-query
26+ // substring bonus never applies and only the per-token tiers are compared.
27+ const rawQuery = "release filter" ;
28+ const queryTokens = tokenizeLexical ( rawQuery ) ;
29+
30+ test ( "ranks a name substring above a parameter name above a parameter description" , ( ) => {
31+ // Every fixture matches "filter" in its blob, so the parameter tiers have
32+ // a direct match to boost.
33+ const nameSubstring = lexicalFields (
34+ "mcp__acme__releaselog" ,
35+ "Filter logs" ,
36+ NO_LEXICAL_PARAMS ,
37+ ) ;
38+ const paramName = lexicalFields ( "mcp__acme__records" , "Filter records" , {
39+ names : [ "release" ] ,
40+ blob : "" ,
41+ } ) ;
42+ const paramDescription = lexicalFields (
43+ "mcp__acme__records" ,
44+ "Filter records" ,
45+ {
46+ names : [ "id" ] ,
47+ blob : "Release ID or slug" ,
48+ } ,
49+ ) ;
50+ const scores = [ nameSubstring , paramName , paramDescription ] . map ( ( fields ) =>
51+ scoreLexical ( fields , queryTokens , rawQuery ) ,
52+ ) ;
53+ expect ( scores [ 0 ] ) . toBeGreaterThan ( scores [ 1 ] ?? 0 ) ;
54+ expect ( scores [ 1 ] ) . toBeGreaterThan ( scores [ 2 ] ?? 0 ) ;
55+ expect ( scores [ 2 ] ) . toBeGreaterThan ( 0 ) ;
56+ } ) ;
57+
58+ test ( "ranks a parameter name above a description substring of the same word" , ( ) => {
59+ // "release" sits only inside "prereleases", so the description offers
60+ // just a raw-substring hit.
61+ const without = lexicalFields (
62+ "mcp__acme__records" ,
63+ "Read prereleases" ,
64+ NO_LEXICAL_PARAMS ,
65+ ) ;
66+ const withParam = lexicalFields ( "mcp__acme__records" , "Read prereleases" , {
67+ names : [ "release" ] ,
68+ blob : "" ,
69+ } ) ;
70+ expect ( scoreLexical ( withParam , queryTokens , rawQuery ) ) . toBeGreaterThan (
71+ scoreLexical ( without , queryTokens , rawQuery ) ,
72+ ) ;
73+ } ) ;
74+
75+ test ( "a parameter match alone scores nothing" , ( ) => {
76+ const fields = lexicalFields ( "mcp__acme__records" , "Read records" , {
77+ names : [ "release" ] ,
78+ blob : "Release ID or slug" ,
79+ } ) ;
80+ expect ( scoreLexical ( fields , queryTokens , rawQuery ) ) . toBe ( 0 ) ;
81+ } ) ;
82+
83+ test ( "a parameter match adds nothing to a word already matched by name or description" , ( ) => {
84+ const params = { names : [ "release" ] , blob : "Release ID or slug" } ;
85+ for ( const [ name , blob ] of [
86+ [ "mcp__acme__release" , "Read logs" ] ,
87+ [ "mcp__acme__records" , "Read a release" ] ,
88+ [ "mcp__acme__releaselog" , "Read logs" ] ,
89+ ] as const ) {
90+ expect (
91+ scoreLexical ( lexicalFields ( name , blob , params ) , queryTokens , rawQuery ) ,
92+ ) . toBe (
93+ scoreLexical (
94+ lexicalFields ( name , blob , NO_LEXICAL_PARAMS ) ,
95+ queryTokens ,
96+ rawQuery ,
97+ ) ,
98+ ) ;
99+ }
100+ } ) ;
22101} ) ;
0 commit comments