Skip to content

Commit a4e9557

Browse files
committed
fix: add missing search-helpers module
1 parent d6538b5 commit a4e9557

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

src/commands/search-helpers.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import type { SearchFilter } from '../retriv/index.ts'
2+
import { existsSync, readdirSync } from 'node:fs'
3+
import * as p from '@clack/prompts'
4+
import { join } from 'pathe'
5+
import { agents, detectTargetAgent } from '../agent/index.ts'
6+
import { getPackageDbPath, REFERENCES_DIR } from '../cache/index.ts'
7+
import { readLock } from '../core/index.ts'
8+
import { getSharedSkillsDir } from '../core/shared.ts'
9+
10+
/** Collect search.db paths for packages installed in the current project (from skilld-lock.yaml) */
11+
export function findPackageDbs(packageFilter?: string): string[] {
12+
const cwd = process.cwd()
13+
const lock = readProjectLock(cwd)
14+
if (!lock)
15+
return []
16+
return filterLockDbs(lock, packageFilter)
17+
}
18+
19+
/** Build package name → version map from the project lockfile */
20+
export function getPackageVersions(cwd: string = process.cwd()): Map<string, string> {
21+
const lock = readProjectLock(cwd)
22+
const map = new Map<string, string>()
23+
if (!lock)
24+
return map
25+
for (const s of Object.values(lock.skills)) {
26+
if (s.packageName && s.version)
27+
map.set(s.packageName, s.version)
28+
}
29+
return map
30+
}
31+
32+
/** Read the project's skilld-lock.yaml (shared dir or agent skills dir) */
33+
function readProjectLock(cwd: string): ReturnType<typeof readLock> {
34+
const shared = getSharedSkillsDir(cwd)
35+
if (shared) {
36+
const lock = readLock(shared)
37+
if (lock)
38+
return lock
39+
}
40+
const agent = detectTargetAgent()
41+
if (!agent)
42+
return null
43+
return readLock(`${cwd}/${agents[agent].skillsDir}`)
44+
}
45+
46+
/** List installed packages with versions from the project lockfile */
47+
export function listLockPackages(cwd: string = process.cwd()): string[] {
48+
const lock = readProjectLock(cwd)
49+
if (!lock)
50+
return []
51+
const seen = new Map<string, string>()
52+
for (const s of Object.values(lock.skills)) {
53+
if (s.packageName && s.version)
54+
seen.set(s.packageName, s.version)
55+
}
56+
return Array.from(seen, ([name, version]) => `${name}@${version}`)
57+
}
58+
59+
function filterLockDbs(lock: ReturnType<typeof readLock>, packageFilter?: string): string[] {
60+
if (!lock)
61+
return []
62+
const tokenize = (s: string) => s.toLowerCase().replace(/@/g, '').split(/[-_/]+/).filter(Boolean)
63+
64+
return Object.values(lock.skills)
65+
.filter((info) => {
66+
if (!info.packageName || !info.version)
67+
return false
68+
if (!packageFilter)
69+
return true
70+
const filterTokens = tokenize(packageFilter)
71+
const nameTokens = tokenize(info.packageName)
72+
return filterTokens.every(ft => nameTokens.some(nt => nt.includes(ft) || ft.includes(nt)))
73+
})
74+
.map((info) => {
75+
const exact = getPackageDbPath(info.packageName!, info.version!)
76+
if (existsSync(exact))
77+
return exact
78+
const fallback = findAnyPackageDb(info.packageName!)
79+
if (fallback)
80+
p.log.warn(`Using cached search index for ${info.packageName} (v${info.version} not indexed). Run \`skilld update ${info.packageName}\` to re-index.`)
81+
return fallback
82+
})
83+
.filter((db): db is string => !!db)
84+
}
85+
86+
/** Find any search.db for a package when exact version cache is missing */
87+
function findAnyPackageDb(name: string): string | null {
88+
if (!existsSync(REFERENCES_DIR))
89+
return null
90+
91+
const prefix = `${name}@`
92+
93+
if (name.startsWith('@')) {
94+
const [scope, pkg] = name.split('/')
95+
const scopeDir = join(REFERENCES_DIR, scope!)
96+
if (!existsSync(scopeDir))
97+
return null
98+
const scopePrefix = `${pkg}@`
99+
for (const entry of readdirSync(scopeDir)) {
100+
if (entry.startsWith(scopePrefix)) {
101+
const db = join(scopeDir, entry, 'search.db')
102+
if (existsSync(db))
103+
return db
104+
}
105+
}
106+
return null
107+
}
108+
109+
for (const entry of readdirSync(REFERENCES_DIR)) {
110+
if (entry.startsWith(prefix)) {
111+
const db = join(REFERENCES_DIR, entry, 'search.db')
112+
if (existsSync(db))
113+
return db
114+
}
115+
}
116+
return null
117+
}
118+
119+
/** Parse filter prefix (e.g., "issues:bug" -> filter by type=issue, query="bug") */
120+
export function parseFilterPrefix(rawQuery: string): { query: string, filter?: SearchFilter } {
121+
const prefixMatch = rawQuery.match(/^(issues?|docs?|releases?):(.+)$/i)
122+
if (!prefixMatch)
123+
return { query: rawQuery }
124+
125+
const prefix = prefixMatch[1]!.toLowerCase()
126+
const query = prefixMatch[2]!
127+
if (prefix.startsWith('issue'))
128+
return { query, filter: { type: 'issue' } }
129+
if (prefix.startsWith('release'))
130+
return { query, filter: { type: 'release' } }
131+
return { query, filter: { type: { $in: ['doc', 'docs'] } } }
132+
}

0 commit comments

Comments
 (0)