Skip to content

Commit 06b98c9

Browse files
committed
sync: mcp-sap-docs 0b0f0b2f25df030ec71c2f4b291a70a46052d9b8 -> abap variant [skip-sync]
1 parent 04d439c commit 06b98c9

2 files changed

Lines changed: 62 additions & 9 deletions

File tree

src/lib/search.ts

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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(wdi5|wdio|webdriver|e2e|page\s*object|selector|locator|ascontrol|allcontrols|fe-testlib)\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(ui5|sapui5|openui5|sap\.m|sap\.ui|sap\.f|control|demokit|sample|wizard|button|table|dialog|input|opa5|recordreplay)\b/i.test(query)) {
151+
contexts.add('ui5');
152+
}
153+
154+
if (/\b(ui5\s+web\s+components?|web\s+components?)\b/i.test(query)) {
155+
contexts.add('ui5 web components');
156+
}
157+
158+
if (/\b(ui5\s+tooling|ui5\s+cli|ui5\.yaml|builder|middleware)\b/i.test(query)) {
159+
contexts.add('ui5 tooling');
160+
}
161+
162+
if (/\b(cap|capire|cloud\s+application\s+programming)\b/i.test(query)) {
163+
contexts.add('cap');
164+
}
142165

143166
// RAP-related
144167
if (/\b(rap|behavior|bdef|eml|managed|unmanaged)\b/i.test(query)) {
145-
contexts.push('rap');
168+
contexts.add('rap');
146169
}
147170
// CDS-related
148171
if (/\b(cds|annotation|@ui|view|entity)\b/i.test(query)) {
149-
contexts.push('cds');
172+
contexts.add('cds');
150173
}
151174
// Fiori-related
152175
if (/\b(fiori|launchpad|flp|tile|ui5)\b/i.test(query)) {
153-
contexts.push('fiori');
176+
contexts.add('fiori');
177+
}
178+
if (/\bfiori\s+elements?\b/i.test(query)) {
179+
contexts.add('fiori elements');
154180
}
155181
// ABAP general
156182
if (/\babap\b/i.test(query)) {
157-
contexts.push('abap');
183+
contexts.add('abap');
184+
}
185+
if (/\b(abap\s+cloud|btp|steampunk)\b/i.test(query)) {
186+
contexts.add('abap cloud');
187+
}
188+
if (/\b(standard\s+abap|on-?premise|onpremise)\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

test/search-response-schema.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ function getHandler(server: Server, method: string): RequestHandler {
1414
}
1515

1616
describe('search response schema', () => {
17+
it('detects source-specific contexts for ambiguous UI5 and wdi5 prompts', async () => {
18+
const { detectQueryContexts } = await import('../src/lib/search.js');
19+
20+
expect(detectQueryContexts('Wizard')).toContain('ui5');
21+
expect(detectQueryContexts('Wizard CAP')).toEqual(expect.arrayContaining(['ui5', 'cap']));
22+
expect(detectQueryContexts('wdi5 table selection')).toEqual(expect.arrayContaining(['wdi5', 'ui5']));
23+
});
24+
1725
it('returns schema-compliant empty results instead of an MCP schema error', async () => {
1826
vi.resetModules();
1927
vi.doMock('../src/lib/search.js', () => ({

0 commit comments

Comments
 (0)