Skip to content

Commit b406773

Browse files
committed
fix: graceful search fallbacks
1 parent a2441c4 commit b406773

2 files changed

Lines changed: 44 additions & 19 deletions

File tree

src/commands/search-interactive.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { SearchFilter, SearchSnippet } from '../retriv/index.ts'
22
import { createLogUpdate } from 'log-update'
33
import { formatCompactSnippet, highlightTerms, sanitizeMarkdown } from '../core/index.ts'
44
import { closePool, openPool, searchPooled } from '../retriv/index.ts'
5-
import { findPackageDbs, parseFilterPrefix } from './search.ts'
5+
import { findPackageDbs, listLockPackages, parseFilterPrefix } from './search.ts'
66

77
const FILTER_CYCLE = [undefined, 'docs', 'issues', 'releases'] as const
88
type FilterLabel = typeof FILTER_CYCLE[number]
@@ -30,9 +30,16 @@ const SPINNER_FRAMES = ['◐', '◓', '◑', '◒']
3030
export async function interactiveSearch(packageFilter?: string): Promise<void> {
3131
const dbs = findPackageDbs(packageFilter)
3232
if (dbs.length === 0) {
33-
const msg = packageFilter
34-
? `No docs indexed for "${packageFilter}". Run \`skilld add ${packageFilter}\` first.`
35-
: 'No docs indexed yet. Run `skilld add <package>` first.'
33+
let msg: string
34+
if (packageFilter) {
35+
const available = listLockPackages()
36+
msg = available.length > 0
37+
? `No docs indexed for "${packageFilter}". Available: ${available.join(', ')}`
38+
: `No docs indexed for "${packageFilter}". Run \`skilld add ${packageFilter}\` first.`
39+
}
40+
else {
41+
msg = 'No docs indexed yet. Run `skilld add <package>` first.'
42+
}
3643
process.stderr.write(`\x1B[33m${msg}\x1B[0m\n`)
3744
return
3845
}

src/commands/search.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,47 +14,59 @@ import { searchSnippets } from '../retriv/index.ts'
1414
/** Collect search.db paths for packages installed in the current project (from skilld-lock.yaml) */
1515
export function findPackageDbs(packageFilter?: string): string[] {
1616
const cwd = process.cwd()
17+
const lock = readProjectLock(cwd)
18+
if (!lock)
19+
return []
20+
return filterLockDbs(lock, packageFilter)
21+
}
1722

18-
// Try shared dir first
23+
/** Read the project's skilld-lock.yaml (shared dir or agent skills dir) */
24+
function readProjectLock(cwd: string): ReturnType<typeof readLock> {
1925
const shared = getSharedSkillsDir(cwd)
2026
if (shared) {
2127
const lock = readLock(shared)
2228
if (lock)
23-
return filterLockDbs(lock, packageFilter)
29+
return lock
2430
}
25-
2631
const agent = detectTargetAgent()
2732
if (!agent)
28-
return []
33+
return null
34+
return readLock(`${cwd}/${agents[agent].skillsDir}`)
35+
}
2936

30-
const skillsDir = `${cwd}/${agents[agent].skillsDir}`
31-
const lock = readLock(skillsDir)
37+
/** List installed package names from the project lockfile */
38+
export function listLockPackages(cwd: string = process.cwd()): string[] {
39+
const lock = readProjectLock(cwd)
3240
if (!lock)
3341
return []
34-
35-
return filterLockDbs(lock, packageFilter)
42+
return [...new Set(Object.values(lock.skills).map(s => s.packageName).filter(Boolean) as string[])]
3643
}
3744

3845
function filterLockDbs(lock: ReturnType<typeof readLock>, packageFilter?: string): string[] {
3946
if (!lock)
4047
return []
41-
const normalize = (s: string) => s.toLowerCase().replace(/[-_@/]/g, '')
48+
const tokenize = (s: string) => s.toLowerCase().replace(/@/g, '').split(/[-_/]+/).filter(Boolean)
4249

4350
return Object.values(lock.skills)
4451
.filter((info) => {
4552
if (!info.packageName || !info.version)
4653
return false
4754
if (!packageFilter)
4855
return true
49-
const f = normalize(packageFilter)
50-
return normalize(info.packageName).includes(f) || normalize(info.packageName) === f
56+
// All tokens from filter must appear in package name tokens
57+
const filterTokens = tokenize(packageFilter)
58+
const nameTokens = tokenize(info.packageName)
59+
return filterTokens.every(ft => nameTokens.some(nt => nt.includes(ft) || ft.includes(nt)))
5160
})
5261
.map((info) => {
5362
const exact = getPackageDbPath(info.packageName!, info.version!)
5463
if (existsSync(exact))
5564
return exact
5665
// Fallback: find any cached version's search.db for this package
57-
return findAnyPackageDb(info.packageName!)
66+
const fallback = findAnyPackageDb(info.packageName!)
67+
if (fallback)
68+
p.log.warn(`Using cached search index for ${info.packageName} (v${info.version} not indexed). Run \`skilld update ${info.packageName}\` to re-index.`)
69+
return fallback
5870
})
5971
.filter((db): db is string => !!db)
6072
}
@@ -112,10 +124,16 @@ export async function searchCommand(rawQuery: string, packageFilter?: string): P
112124
const dbs = findPackageDbs(packageFilter)
113125

114126
if (dbs.length === 0) {
115-
if (packageFilter)
116-
p.log.warn(`No docs indexed for "${packageFilter}". Run \`skilld add ${packageFilter}\` first.`)
117-
else
127+
if (packageFilter) {
128+
const available = listLockPackages()
129+
if (available.length > 0)
130+
p.log.warn(`No docs indexed for "${packageFilter}". Available: ${available.join(', ')}`)
131+
else
132+
p.log.warn(`No docs indexed for "${packageFilter}". Run \`skilld add ${packageFilter}\` first.`)
133+
}
134+
else {
118135
p.log.warn('No docs indexed yet. Run `skilld add <package>` first.')
136+
}
119137
return
120138
}
121139

0 commit comments

Comments
 (0)