@@ -1819,7 +1819,6 @@ describe("memory index", () => {
18191819
18201820 it ( "preserves keyword-only hybrid hits alongside strict vector results" , async ( ) => {
18211821 const cfg = createCfg ( {
1822- storePath : path . join ( workspaceDir , "index-hybrid-keyword-with-strict-vector.sqlite" ) ,
18231822 minScore : 0.35 ,
18241823 hybrid : { enabled : true , vectorWeight : 0.7 , textWeight : 0.3 } ,
18251824 } ) ;
@@ -1871,6 +1870,97 @@ describe("memory index", () => {
18711870 }
18721871 } ) ;
18731872
1873+ it ( "keeps overlapping keyword-backed hybrid hits when all scores are below minScore" , async ( ) => {
1874+ const cfg = createCfg ( {
1875+ minScore : 0.35 ,
1876+ hybrid : { enabled : true , vectorWeight : 0.7 , textWeight : 0.3 } ,
1877+ } ) ;
1878+ const manager = await getFreshManager ( cfg ) ;
1879+ try {
1880+ const overlappingResult = {
1881+ path : "memory/overlap.md" ,
1882+ startLine : 3 ,
1883+ endLine : 4 ,
1884+ source : "memory" as const ,
1885+ snippet : "overlap keyword vector" ,
1886+ score : 0.2 ,
1887+ vectorScore : 0.1 ,
1888+ textScore : 0.5 ,
1889+ } ;
1890+ const selectHybridSearchResults = Reflect . get ( manager , "selectHybridSearchResults" ) as (
1891+ merged : Array < typeof overlappingResult > ,
1892+ keywordResults : Array < typeof overlappingResult & { id : string } > ,
1893+ maxResults : number ,
1894+ minScore : number ,
1895+ ) => Array < typeof overlappingResult > ;
1896+
1897+ const results = selectHybridSearchResults . call (
1898+ manager ,
1899+ [ overlappingResult ] ,
1900+ [ { ...overlappingResult , id : "overlap" } ] ,
1901+ 10 ,
1902+ 0.35 ,
1903+ ) ;
1904+
1905+ expect ( results . map ( ( result ) => result . path ) ) . toStrictEqual ( [ "memory/overlap.md" ] ) ;
1906+ expect ( results [ 0 ] ?. vectorScore ) . toBeGreaterThan ( 0 ) ;
1907+ expect ( results [ 0 ] ?. textScore ) . toBeGreaterThan ( 0 ) ;
1908+ } finally {
1909+ await manager . close ?.( ) ;
1910+ }
1911+ } ) ;
1912+
1913+ it ( "preserves hybrid merger order when retaining keyword-only hits" , async ( ) => {
1914+ const cfg = createCfg ( {
1915+ minScore : 0.35 ,
1916+ hybrid : { enabled : true , vectorWeight : 0.7 , textWeight : 0.3 } ,
1917+ } ) ;
1918+ const manager = await getFreshManager ( cfg ) ;
1919+ try {
1920+ const keywordOnlyResult = {
1921+ path : "memory/keyword-first.md" ,
1922+ startLine : 2 ,
1923+ endLine : 2 ,
1924+ source : "memory" as const ,
1925+ snippet : "keyword first" ,
1926+ score : 0.3 ,
1927+ vectorScore : 0 ,
1928+ textScore : 1 ,
1929+ } ;
1930+ const strictVectorResult = {
1931+ path : "memory/vector-second.md" ,
1932+ startLine : 1 ,
1933+ endLine : 1 ,
1934+ source : "memory" as const ,
1935+ snippet : "strict vector second" ,
1936+ score : 0.63 ,
1937+ vectorScore : 0.9 ,
1938+ textScore : 0 ,
1939+ } ;
1940+ const selectHybridSearchResults = Reflect . get ( manager , "selectHybridSearchResults" ) as (
1941+ merged : Array < typeof keywordOnlyResult > ,
1942+ keywordResults : Array < typeof keywordOnlyResult & { id : string } > ,
1943+ maxResults : number ,
1944+ minScore : number ,
1945+ ) => Array < typeof keywordOnlyResult > ;
1946+
1947+ const results = selectHybridSearchResults . call (
1948+ manager ,
1949+ [ keywordOnlyResult , strictVectorResult ] ,
1950+ [ { ...keywordOnlyResult , id : "keyword-only" } ] ,
1951+ 10 ,
1952+ 0.35 ,
1953+ ) ;
1954+
1955+ expect ( results . map ( ( result ) => result . path ) ) . toStrictEqual ( [
1956+ "memory/keyword-first.md" ,
1957+ "memory/vector-second.md" ,
1958+ ] ) ;
1959+ } finally {
1960+ await manager . close ?.( ) ;
1961+ }
1962+ } ) ;
1963+
18741964 it ( "reports vector availability after probe" , async ( ) => {
18751965 const cfg = createCfg ( { vectorEnabled : true } ) ;
18761966 const manager = await getPersistentManager ( cfg ) ;
0 commit comments