Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/__tests__/search-domain-weighting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,41 @@ describe('domain-weighted search scoring', () => {
expect(overrideResult!.score).toBeGreaterThan(normalResult!.score);
});

it('support-domain query boosts support entries above neutral (query domain inferred from support tokens)', async () => {
// inferQueryDomain must recognise SUPPORT_TAGS (faq/onboarding/guide/…).
// A query made of support tokens should be classified as a 'support' query,
// so a support entry (DOMAIN_WEIGHT.support.support = 1.0) outranks a neutral
// entry (DOMAIN_WEIGHT.support.neutral = 0.85) even with an equal raw score.
// Both entries below are identical except for their frontmatter domain.
const learningsDir = path.join(tmpDir, 'learnings');
await fse.ensureDir(learningsDir);

await fse.writeFile(
path.join(learningsDir, 'onboarding-faq-support.md'),
'---\ntitle: "onboarding faq"\ndomain: support\n---\nDetails.\n',
);
await fse.writeFile(
path.join(learningsDir, 'onboarding-faq-neutral.md'),
'---\ntitle: "onboarding faq"\ndomain: neutral\n---\nDetails.\n',
);

await buildIndex({ learningsDir, indexPath });
const index = await loadIndex(indexPath);
expect(index).not.toBeNull();

// "onboarding" and "faq" are both SUPPORT_TAGS → query domain = support
const results = search('onboarding faq', index!);
expect(results.length).toBe(2);

const supportEntry = results.find((r) => r.entry.domain === 'support');
const neutralEntry = results.find((r) => r.entry.domain === 'neutral');
expect(supportEntry).toBeDefined();
expect(neutralEntry).toBeDefined();

expect(supportEntry!.score).toBeGreaterThan(neutralEntry!.score);
expect(results[0].entry.domain).toBe('support');
});

it('built index carries domain field on every entry (version 4)', async () => {
const learningsDir = path.join(tmpDir, 'learnings');
await fse.ensureDir(learningsDir);
Expand Down
12 changes: 8 additions & 4 deletions src/utils/search-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,18 @@ const DOMAIN_WEIGHT: Record<KnowledgeDomain, Record<KnowledgeDomain, number>> =
function inferQueryDomain(queryTokens: string[]): KnowledgeDomain {
let techScore = 0;
let opsScore = 0;
let supportScore = 0;
for (const t of queryTokens) {
if (TECHNICAL_TAGS.has(t)) techScore++;
if (OPS_TAGS.has(t)) opsScore++;
if (SUPPORT_TAGS.has(t)) supportScore++;
}
if (opsScore > techScore) return 'ops';
if (techScore > opsScore) return 'technical';
if (techScore > 0) return 'technical'; // tie \u2192 technical
return 'neutral';
const maxScore = Math.max(techScore, opsScore, supportScore);
if (maxScore === 0) return 'neutral';
// Tie-breaking mirrors inferDomain: technical > ops > support.
if (techScore === maxScore) return 'technical';
if (opsScore === maxScore) return 'ops';
return 'support';
}

// Type bonuses: skills/rules already represent curated, high-confidence knowledge.
Expand Down
Loading