Skip to content
Open
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
32 changes: 31 additions & 1 deletion cli/src/hooks/__tests__/use-suggestion-engine-mention.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { describe, test, expect } from 'bun:test'

import { isInsideStringDelimiters, parseAtInLine } from '../use-suggestion-engine'
import {
filterFileMatches,
isInsideStringDelimiters,
parseAtInLine,
} from '../use-suggestion-engine'

describe('@ mention edge cases - quote detection', () => {
test('isInsideStringDelimiters detects position inside double quotes', () => {
Expand Down Expand Up @@ -359,3 +363,29 @@ describe('single quote handling - apostrophes should NOT suppress @ menu', () =>
},
)
})

describe('filterFileMatches - bounding and ranking', () => {
test('caps results to top 100 matches when exceeding threshold', () => {
const manyFiles = Array.from({ length: 250 }, (_, i) => ({
path: `src/components/item-${i}.tsx`,
isDirectory: false,
}))

const results = filterFileMatches(manyFiles, 'item')
expect(results.length).toBe(100)
// Check that results are properly sorted (lowest score first)
for (let i = 1; i < results.length; i++) {
expect((results[i].matchScore ?? 0) >= (results[i - 1].matchScore ?? 0)).toBe(true)
}
})

test('returns all matches when count is within 100 limit', () => {
const files = Array.from({ length: 25 }, (_, i) => ({
path: `src/components/item-${i}.tsx`,
isDirectory: false,
}))

const results = filterFileMatches(files, 'item')
expect(results.length).toBe(25)
})
})
24 changes: 16 additions & 8 deletions cli/src/hooks/use-suggestion-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,13 @@ const getFileName = (filePath: string): string => {
return lastSlash === -1 ? filePath : filePath.slice(lastSlash + 1)
}

const createHighlightIndices = (start: number, end: number): number[] => [
...range(start, end),
]
const createHighlightIndices = (start: number, end: number): number[] => {
const result: number[] = []
for (let i = start; i < end; i++) {
result.push(i)
}
return result
}

const createPushUnique = <T, K>(
getKey: (item: T) => K,
Expand Down Expand Up @@ -351,9 +355,13 @@ const fuzzyMatch = (
// - Fewer gaps = better
// - Longer consecutive matches = better
// - Matches at word boundaries (after /) = better
const boundaryBonus = indices.filter(
(idx) => idx === 0 || text[idx - 1] === '/'
).length
let boundaryBonus = 0
for (let i = 0; i < indices.length; i++) {
const idx = indices[i]
if (idx === 0 || text[idx - 1] === '/') {
boundaryBonus++
}
}

const score =
gaps * 10 -
Expand All @@ -364,7 +372,7 @@ const fuzzyMatch = (
return { indices, score }
}

const filterFileMatches = (
export const filterFileMatches = (
pathInfos: PathInfo[],
query: string,
): MatchedFileInfo[] => {
Expand Down Expand Up @@ -489,7 +497,7 @@ const filterFileMatches = (
// Sort by score (lower is better)
matches.sort((a, b) => (a.matchScore ?? 0) - (b.matchScore ?? 0))

return matches
return matches.length > 100 ? matches.slice(0, 100) : matches
}

const filterAgentMatches = (
Expand Down
Loading