Compare commits

...
Author SHA1 Message Date
Peter Steinberger a6957069c3 fix: relax search token prefix matching (#27) (thanks @afmire877) 2026-01-24 21:22:48 +00:00
Ahmed Fuad Mireandvercel[bot] <35613825+vercel[bot]@users.noreply.github.com> f1e5ad66a4 Update convex/lib/searchText.ts
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
2026-01-24 21:22:06 +00:00
Ahmed 1871f79c58 more inclusive token check 2026-01-24 21:22:06 +00:00
Ahmed 0868cb9ff2 fix: update matchesExactTokens to require prefix matching for query tokens 2026-01-24 21:22:06 +00:00
AhmedandClaude Opus 4.5 7f07ada7c2 fix: relax search token matching to require at least one match
The search was requiring ALL query tokens to exist in the skill's
displayName, slug, or summary. This was too strict and caused valid
results to be filtered out. For example, searching "HTTP API client"
would fail to match skills about "HTTP API" that didn't mention "client".

Changed from `.every()` to `.some()` so at least one token must match,
allowing the vector similarity to determine relevance for the rest.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 21:22:06 +00:00
3 changed files with 26 additions and 4 deletions
+1
View File
@@ -4,6 +4,7 @@
### Fixed
- Registry: drop missing skills during search hydration (thanks @aaronn, #28).
- Registry: relax search token matching to require at least one prefix match (thanks @afmire877, #27).
## 0.3.0 - 2026-01-19
+21 -2
View File
@@ -13,12 +13,31 @@ describe('searchText', () => {
])
})
it('matchesExactTokens requires all query tokens', () => {
it('matchesExactTokens requires at least one query token to prefix-match', () => {
const queryTokens = tokenize('Remind Me')
expect(matchesExactTokens(queryTokens, ['Remind Me', '/remind-me', 'Short summary'])).toBe(true)
// "Reminder" starts with "remind", so it matches with prefix matching
expect(matchesExactTokens(queryTokens, ['Reminder tool', '/reminder', 'Short summary'])).toBe(
false,
true,
)
// Matches because "remind" token is present
expect(matchesExactTokens(queryTokens, ['Remind tool', '/remind', 'Short summary'])).toBe(true)
// No matching tokens at all
expect(matchesExactTokens(queryTokens, ['Other tool', '/other', 'Short summary'])).toBe(false)
})
it('matchesExactTokens supports prefix matching for partial queries', () => {
// "go" should match "gohome" because "gohome" starts with "go"
expect(matchesExactTokens(['go'], ['GoHome', '/gohome', 'Navigate home'])).toBe(true)
// "pad" should match "padel"
expect(matchesExactTokens(['pad'], ['Padel', '/padel', 'Tennis-like sport'])).toBe(true)
// "xyz" should not match anything
expect(matchesExactTokens(['xyz'], ['GoHome', '/gohome', 'Navigate home'])).toBe(false)
})
it('matchesExactTokens does not match non-prefix substrings', () => {
expect(matchesExactTokens(['art'], ['Cartoon', '/cartoon', 'Animated'])).toBe(false)
expect(matchesExactTokens(['art'], ['Smart tool', '/smart', 'Overview'])).toBe(false)
})
it('matchesExactTokens ignores empty inputs', () => {
+4 -2
View File
@@ -18,8 +18,10 @@ export function matchesExactTokens(
if (!text) return false
const textTokens = tokenize(text)
if (textTokens.length === 0) return false
const textSet = new Set(textTokens)
return queryTokens.every((token) => textSet.has(token))
// Require at least one token to prefix-match, allowing vector similarity to determine relevance
return queryTokens.some((queryToken) =>
textTokens.some((textToken) => textToken.startsWith(queryToken)),
)
}
export const __test = { normalize, tokenize, matchesExactTokens }