@@ -136,28 +136,60 @@ function hasAnnotationQuery(query: string): boolean {
136136 * Detect query context for contextBoosts
137137 * Returns matching context keys from metadata
138138 */
139- function detectQueryContexts ( query : string ) : string [ ] {
140- const contexts : string [ ] = [ ] ;
141- const lower = query . toLowerCase ( ) ;
139+ export function detectQueryContexts ( query : string ) : string [ ] {
140+ const contexts = new Set < string > ( ) ;
141+
142+ // wdi5-related test automation queries. This has to run before generic
143+ // UI5/Fiori handling because wdi5 queries frequently mention UI5 controls.
144+ if ( / \b ( w d i 5 | w d i o | w e b d r i v e r | e 2 e | p a g e \s * o b j e c t | s e l e c t o r | l o c a t o r | a s c o n t r o l | a l l c o n t r o l s | f e - t e s t l i b ) \b / i. test ( query ) ) {
145+ contexts . add ( 'wdi5' ) ;
146+ }
147+
148+ // UI5 controls, API symbols, samples, and Demo Kit concepts. Include common
149+ // control names from user prompts that otherwise look too generic for SAP Help.
150+ if ( / \b ( u i 5 | s a p u i 5 | o p e n u i 5 | s a p \. m | s a p \. u i | s a p \. f | c o n t r o l | d e m o k i t | s a m p l e | w i z a r d | b u t t o n | t a b l e | d i a l o g | i n p u t | o p a 5 | r e c o r d r e p l a y ) \b / i. test ( query ) ) {
151+ contexts . add ( 'ui5' ) ;
152+ }
153+
154+ if ( / \b ( u i 5 \s + w e b \s + c o m p o n e n t s ? | w e b \s + c o m p o n e n t s ? ) \b / i. test ( query ) ) {
155+ contexts . add ( 'ui5 web components' ) ;
156+ }
157+
158+ if ( / \b ( u i 5 \s + t o o l i n g | u i 5 \s + c l i | u i 5 \. y a m l | b u i l d e r | m i d d l e w a r e ) \b / i. test ( query ) ) {
159+ contexts . add ( 'ui5 tooling' ) ;
160+ }
161+
162+ if ( / \b ( c a p | c a p i r e | c l o u d \s + a p p l i c a t i o n \s + p r o g r a m m i n g ) \b / i. test ( query ) ) {
163+ contexts . add ( 'cap' ) ;
164+ }
142165
143166 // RAP-related
144167 if ( / \b ( r a p | b e h a v i o r | b d e f | e m l | m a n a g e d | u n m a n a g e d ) \b / i. test ( query ) ) {
145- contexts . push ( 'rap' ) ;
168+ contexts . add ( 'rap' ) ;
146169 }
147170 // CDS-related
148171 if ( / \b ( c d s | a n n o t a t i o n | @ u i | v i e w | e n t i t y ) \b / i. test ( query ) ) {
149- contexts . push ( 'cds' ) ;
172+ contexts . add ( 'cds' ) ;
150173 }
151174 // Fiori-related
152175 if ( / \b ( f i o r i | l a u n c h p a d | f l p | t i l e | u i 5 ) \b / i. test ( query ) ) {
153- contexts . push ( 'fiori' ) ;
176+ contexts . add ( 'fiori' ) ;
177+ }
178+ if ( / \b f i o r i \s + e l e m e n t s ? \b / i. test ( query ) ) {
179+ contexts . add ( 'fiori elements' ) ;
154180 }
155181 // ABAP general
156182 if ( / \b a b a p \b / i. test ( query ) ) {
157- contexts . push ( 'abap' ) ;
183+ contexts . add ( 'abap' ) ;
184+ }
185+ if ( / \b ( a b a p \s + c l o u d | b t p | s t e a m p u n k ) \b / i. test ( query ) ) {
186+ contexts . add ( 'abap cloud' ) ;
187+ }
188+ if ( / \b ( s t a n d a r d \s + a b a p | o n - ? p r e m i s e | o n p r e m i s e ) \b / i. test ( query ) ) {
189+ contexts . add ( 'standard abap' ) ;
158190 }
159191
160- return contexts ;
192+ return [ ... contexts ] ;
161193}
162194
163195// Helper to extract source ID from library_id or document path
@@ -339,6 +371,7 @@ export async function search(
339371 const ftsFilters = sourceFilters
340372 ? { libraries : [ ...sourceFilters ] . map ( sourceId => `/${ sourceId } ` ) }
341373 : { } ;
374+ const minimumVariantCandidates = Math . max ( k * 2 , 50 ) ;
342375
343376 for ( const r of lookupExactDocs ( query , ftsFilters , Math . min ( k , 10 ) ) ) {
344377 seen . set ( r . id , r ) ;
@@ -357,7 +390,7 @@ export async function search(
357390 console . warn ( `FTS query failed for variant "${ variant } ":` , error ) ;
358391 continue ;
359392 }
360- if ( seen . size >= k * 2 ) break ; // enough candidates
393+ if ( seen . size >= minimumVariantCandidates ) break ; // enough candidates
361394 }
362395
363396 let rows = Array . from ( seen . values ( ) ) ;
@@ -467,6 +500,18 @@ export async function search(
467500 // Boost proportional to how many query terms match in title
468501 boost += 0.5 * titleMatchCount ;
469502 }
503+
504+ // Technical UI5/wdi5 prompts can contain words such as "selection" or
505+ // "table" that strongly match ABAP keyword docs, even when the query is
506+ // clearly about frontend test automation. Penalize ABAP keyword docs unless
507+ // ABAP was explicitly requested.
508+ const isUi5OrWdi5Query = queryContexts . includes ( 'ui5' ) || queryContexts . includes ( 'wdi5' ) ;
509+ if ( queryContexts . includes ( 'wdi5' ) && sourceId === 'wdi5' ) {
510+ boost += 5.0 ;
511+ }
512+ if ( isUi5OrWdi5Query && ! isExplicitAbapQuery && sourceId . startsWith ( 'abap-docs-' ) ) {
513+ boost -= 0.9 ;
514+ }
470515
471516 // Glossary down-ranking: slightly penalize glossary entries to prefer practical guides
472517 // Glossary entries are useful for definitions but often not what users want for "how to" queries
0 commit comments