[PR #266] [CLOSED] fix: Overhaul search relevance — prioritize exact matches, eliminate false positives #303

Closed
opened 2026-02-15 17:16:45 -05:00 by yindo · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/openclaw/clawhub/pull/266
Author: @shellcorpnet
Created: 2/13/2026
Status: Closed

Base: mainHead: fix/search-relevance-overhaul


📝 Commits (1)

  • 025f665 fix: overhaul search relevance scoring to prioritize exact matches

📊 Changes

3 files changed (+92 additions, -15 deletions)

View changed files

📝 convex/lib/searchText.test.ts (+23 -6)
📝 convex/lib/searchText.ts (+57 -3)
📝 convex/search.ts (+12 -6)

📄 Description

Problem

Searching for a skill by its exact name returns it far down the results list. For example, searching "Remind Me" shows the actual "Remind Me" skill at position #71 — the #1 result doesn't even mention "Remind Me" anywhere in its name or description.

Reported in #15.

Root Cause

Three compounding issues in the search pipeline:

  1. matchesExactTokens only required ONE query token to match — so "Remind Me" matched any skill containing the word "me" (or any word starting with "me"). Since "me" is extremely common, nearly every skill passed the "exact match" filter, defeating its purpose entirely.

  2. Lexical boosts were too weak relative to vector similarity scores — even when a skill's name exactly matched the query, the boost was only +1.1 to +1.4. This was easily overwhelmed by high vector cosine similarity scores from semantically-adjacent but differently-named skills.

  3. Summary/description text wasn't factored into lexical scoring — only displayName and slug were checked for lexical boosts, missing cases where the query terms appear prominently in the skill's summary.


Changes

convex/lib/searchText.ts — Core matching logic

matchesExactTokens — now requires ALL tokens (was: any one)

- // Require at least one token to prefix-match
- return queryTokens.some((queryToken) =>
+ // Require ALL query tokens to prefix-match at least one text token
+ return queryTokens.every((queryToken) =>
    textTokens.some((textToken) => textToken.startsWith(queryToken)),
  )

Before: "Remind Me" → matches anything with "remind" OR "me"
After: "Remind Me" → only matches skills containing BOTH "remind" AND "me"

This single change eliminates the vast majority of false positives.

New: scoreTokenMatch — granular lexical scoring

A new scoring function that returns a numeric relevance score instead of a boolean:

  • Exact token match: +2 per token (e.g., query "remind" matches text token "remind")
  • Prefix match: +1 per token (e.g., query "remind" matches text token "reminder")
  • All-tokens bonus: +3 when every query token matches
  • Threshold: For 1-2 token queries, ALL tokens must match. For 3+ tokens, 60% must match.

This enables ranked results within the set of matching skills.

convex/search.ts — Search scoring weights

Lexical boost weights increased ~2x

Boost Before After
Slug exact match 1.4 3.0
Slug prefix match 0.8 1.5
Name exact match 1.1 2.5
Name prefix match 0.6 1.2

These higher weights ensure that a skill literally named "Remind Me" will rank above a skill that's merely semantically similar (e.g., a "Notifications" skill with high vector similarity but no lexical match).

New: Summary text matching

Added SUMMARY_MATCH_WEIGHT = 0.3 — the skill's summary is now scored using scoreTokenMatch and contributes to the final ranking. This helps surface skills where the query terms appear in the description even if the name/slug don't match exactly.

convex/lib/searchText.test.ts — Updated tests

  • Updated matchesExactTokens tests to verify ALL-token matching behavior
  • Added scoreTokenMatch tests verifying exact > prefix scoring and threshold behavior

convex/search.test.ts — All existing tests pass unchanged

The scoreSkillResult function's new summary parameter is optional, so all 10 existing search tests continue to pass without modification.


Impact

Before (searching "Remind Me")

  1. Unrelated skill (matched on "me" alone, high vector score)
  2. Another unrelated skill
  3. ...
  4. Actual "Remind Me" skill

After

  1. "Remind Me" skill (exact name match: +2.5 name boost + summary score)
  2. Skills with "remind" and "me" in their text
  3. Semantically similar skills via vector search

Test Results

Test Files  58 passed (58)
     Tests  393 passed (393)
  Duration  7.47s

All 393 tests pass, including 16 search-specific tests.


Fixes #15

Greptile Overview

Greptile Summary

This PR tightens lexical matching and rebalances scoring so exact-name queries rank as expected.

  • Updates matchesExactTokens to require all query tokens to prefix-match across a skill’s displayName/slug/summary, reducing false positives for common tokens.
  • Introduces scoreTokenMatch for graded lexical scoring and uses it in searchSkills as an additional (lightweight) summary-based boost.
  • Increases slug/name lexical boost weights so literal matches can outrank purely semantic (vector) similarity.
  • Updates unit tests in convex/lib/searchText.test.ts to reflect the stricter token matching and validate scoreTokenMatch behavior.

No functional regressions or runtime errors were found in the changed code paths; existing search tests remain compatible with the optional summary parameter in scoreSkillResult.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk.
  • Changes are localized to search tokenization/matching and scoring; updated tests cover the stricter matching behavior, and the search scoring update is additive (optional summary) without breaking existing call sites.
  • No files require special attention

Last reviewed commit: 025f665

(2/5) Greptile learns from your feedback when you react with thumbs up/down!


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/openclaw/clawhub/pull/266 **Author:** [@shellcorpnet](https://github.com/shellcorpnet) **Created:** 2/13/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/search-relevance-overhaul` --- ### 📝 Commits (1) - [`025f665`](https://github.com/openclaw/clawhub/commit/025f665802856894fe18b8ad17eaa19eebdba378) fix: overhaul search relevance scoring to prioritize exact matches ### 📊 Changes **3 files changed** (+92 additions, -15 deletions) <details> <summary>View changed files</summary> 📝 `convex/lib/searchText.test.ts` (+23 -6) 📝 `convex/lib/searchText.ts` (+57 -3) 📝 `convex/search.ts` (+12 -6) </details> ### 📄 Description ## Problem Searching for a skill by its exact name returns it far down the results list. For example, searching **"Remind Me"** shows the actual "Remind Me" skill at position **#71** — the #1 result doesn't even mention "Remind Me" anywhere in its name or description. Reported in #15. ### Root Cause Three compounding issues in the search pipeline: 1. **`matchesExactTokens` only required ONE query token to match** — so `"Remind Me"` matched *any* skill containing the word "me" (or any word starting with "me"). Since "me" is extremely common, nearly every skill passed the "exact match" filter, defeating its purpose entirely. 2. **Lexical boosts were too weak relative to vector similarity scores** — even when a skill's name exactly matched the query, the boost was only +1.1 to +1.4. This was easily overwhelmed by high vector cosine similarity scores from semantically-adjacent but differently-named skills. 3. **Summary/description text wasn't factored into lexical scoring** — only `displayName` and `slug` were checked for lexical boosts, missing cases where the query terms appear prominently in the skill's summary. --- ## Changes ### `convex/lib/searchText.ts` — Core matching logic #### `matchesExactTokens` — now requires ALL tokens (was: any one) ```diff - // Require at least one token to prefix-match - return queryTokens.some((queryToken) => + // Require ALL query tokens to prefix-match at least one text token + return queryTokens.every((queryToken) => textTokens.some((textToken) => textToken.startsWith(queryToken)), ) ``` **Before:** `"Remind Me"` → matches anything with "remind" OR "me" **After:** `"Remind Me"` → only matches skills containing BOTH "remind" AND "me" This single change eliminates the vast majority of false positives. #### New: `scoreTokenMatch` — granular lexical scoring A new scoring function that returns a numeric relevance score instead of a boolean: - **Exact token match:** +2 per token (e.g., query "remind" matches text token "remind") - **Prefix match:** +1 per token (e.g., query "remind" matches text token "reminder") - **All-tokens bonus:** +3 when every query token matches - **Threshold:** For 1-2 token queries, ALL tokens must match. For 3+ tokens, 60% must match. This enables ranked results within the set of matching skills. ### `convex/search.ts` — Search scoring weights #### Lexical boost weights increased ~2x | Boost | Before | After | |-------|--------|-------| | Slug exact match | 1.4 | **3.0** | | Slug prefix match | 0.8 | **1.5** | | Name exact match | 1.1 | **2.5** | | Name prefix match | 0.6 | **1.2** | These higher weights ensure that a skill literally named "Remind Me" will rank above a skill that's merely semantically similar (e.g., a "Notifications" skill with high vector similarity but no lexical match). #### New: Summary text matching Added `SUMMARY_MATCH_WEIGHT = 0.3` — the skill's summary is now scored using `scoreTokenMatch` and contributes to the final ranking. This helps surface skills where the query terms appear in the description even if the name/slug don't match exactly. ### `convex/lib/searchText.test.ts` — Updated tests - Updated `matchesExactTokens` tests to verify ALL-token matching behavior - Added `scoreTokenMatch` tests verifying exact > prefix scoring and threshold behavior ### `convex/search.test.ts` — All existing tests pass unchanged The `scoreSkillResult` function's new `summary` parameter is optional, so all 10 existing search tests continue to pass without modification. --- ## Impact ### Before (searching "Remind Me") 1. ❌ Unrelated skill (matched on "me" alone, high vector score) 2. ❌ Another unrelated skill 3. ... 71. ✅ Actual "Remind Me" skill ### After 1. ✅ "Remind Me" skill (exact name match: +2.5 name boost + summary score) 2. Skills with "remind" and "me" in their text 3. Semantically similar skills via vector search --- ## Test Results ``` Test Files 58 passed (58) Tests 393 passed (393) Duration 7.47s ``` All 393 tests pass, including 16 search-specific tests. --- Fixes #15 <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR tightens lexical matching and rebalances scoring so exact-name queries rank as expected. - Updates `matchesExactTokens` to require *all* query tokens to prefix-match across a skill’s `displayName`/`slug`/`summary`, reducing false positives for common tokens. - Introduces `scoreTokenMatch` for graded lexical scoring and uses it in `searchSkills` as an additional (lightweight) summary-based boost. - Increases slug/name lexical boost weights so literal matches can outrank purely semantic (vector) similarity. - Updates unit tests in `convex/lib/searchText.test.ts` to reflect the stricter token matching and validate `scoreTokenMatch` behavior. No functional regressions or runtime errors were found in the changed code paths; existing search tests remain compatible with the optional `summary` parameter in `scoreSkillResult`. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk. - Changes are localized to search tokenization/matching and scoring; updated tests cover the stricter matching behavior, and the search scoring update is additive (optional summary) without breaking existing call sites. - No files require special attention <sub>Last reviewed commit: 025f665</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
yindo added the pull-request label 2026-02-15 17:16:45 -05:00
yindo closed this issue 2026-02-15 17:16:45 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: openclaw/clawhub#303