code_nav.find_file
Locate repo-relative files by query, using fff or git-visible fallback, with optional scope and limit constraints.
Instructions
Find repo-relative files with fff or git-visible fallback.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| scope | No | ||
| limit | No |
Implementation Reference
- src/tools/find-file.ts:13-26 (handler)The main handler function for 'code_nav.find_file'. Gets the repo root, loads config, lists candidate files, clamps the limit, and delegates to fffFindFiles which uses the fff-node library for fuzzy file finding with a fallback.
export async function findFile(input: FindFileInput, cwd = process.cwd()) { const repo = await getRepoRoot(cwd); const loaded = await loadConfig(repo.repoRoot); const listed = await listCandidateFiles(repo.repoRoot, repo.isGitRepo, loaded.config); const limit = clampLimit(input.limit, 20, 200); const result = await fffFindFiles( repo.repoRoot, listed.files, input.query, input.scope, limit, ); return result.results; } - src/core/fff-adapter.ts:20-53 (handler)Core fuzzy file finding logic using fff-node library. Attempts to use the FFF finder for fast fuzzy matching; falls back to a simple path-based fuzzy scoring algorithm if FFF is unavailable or fails.
export async function fffFindFiles( repoRoot: string, files: CandidateFile[], query: string, scope: string[] | undefined, limit: number, ): Promise<{ results: FindFileResult[]; mode: "fff" | "fallback"; warning?: string }> { const state = await ensureFinder(repoRoot); const allowed = new Set(applyScope(files, scope).map((file) => file.path)); if (state.finder) { const result = state.finder.fileSearch(query, { pageSize: Math.max(limit * 5, 50) }); if (result.ok) { const results: FindFileResult[] = []; result.value.items.forEach((item: any, index: number) => { if (!allowed.has(item.relativePath)) return; const score = result.value.scores[index]?.total ?? 0; results.push({ path: item.relativePath, score: normalizeScore(score), source: "fff", why_matched: [`path fuzzy matched ${query}`], }); }); return { results: results.slice(0, limit), mode: "fff" }; } state.warning = result.error; state.mode = "degraded"; } return { results: fallbackFindFiles(files, query, scope, limit), mode: "fallback", warning: state.warning ?? undefined, }; } - src/tools/find-file.ts:7-11 (schema)Input type definition for the findFile handler: query (string), optional scope (string array), optional limit (number).
export interface FindFileInput { query: string; scope?: string[]; limit?: number; } - src/mcp.ts:30-42 (registration)Registration of the 'code_nav.find_file' tool in the MCP server with its schema, description, and handler binding.
server.registerTool( "code_nav.find_file", { description: "Find repo-relative files with fff or git-visible fallback.", inputSchema: { query: z.string(), scope: z.array(z.string()).optional(), limit: z.number().int().positive().optional(), }, annotations: { readOnlyHint: true, openWorldHint: false }, }, async (input) => mcpJson(await findFile(input)), ); - src/core/fff-adapter.ts:103-184 (helper)Fallback file finding logic that tokenizes the query and scores each file path using fuzzy matching (substring, subsequence, etc.) when the FFF library is unavailable.
export function fallbackFindFiles( files: CandidateFile[], query: string, scope: string[] | undefined, limit: number, ): FindFileResult[] { const terms = tokenize(query); return applyScope(files, scope) .map((file) => ({ file, score: fuzzyPathScore(file.path, terms) })) .filter((item) => item.score > 0) .sort((a, b) => b.score - a.score || a.file.path.localeCompare(b.file.path)) .slice(0, limit) .map(({ file, score }) => ({ path: file.path, score, source: "fallback", why_matched: [`path fuzzy matched ${terms.join(" ") || query}`], })); } async function ensureFinder(repoRoot: string): Promise<FinderState> { const cached = finderCache.get(repoRoot); if (cached) return cached; try { const created = FileFinder.create({ basePath: repoRoot, aiMode: true, disableWatch: true, cacheBudgetMaxFileSize: 2 * 1024 * 1024, }); if (!created.ok) { const state = { finder: null, mode: "degraded" as const, warning: created.error }; finderCache.set(repoRoot, state); return state; } const waited = await created.value.waitForScan(5000); const warning = waited.ok && waited.value ? null : "fff scan did not complete in 5s"; const state = { finder: created.value, mode: "node" as const, warning }; finderCache.set(repoRoot, state); return state; } catch (error) { const message = error instanceof Error ? error.message : String(error); const state = { finder: null, mode: "degraded" as const, warning: message }; finderCache.set(repoRoot, state); return state; } } function tokenize(query: string): string[] { return query .toLowerCase() .split(/[^a-z0-9_.-]+/i) .filter(Boolean); } function fuzzyPathScore(path: string, terms: string[]): number { const lower = path.toLowerCase(); if (terms.length === 0) return 0; let score = 0; for (const term of terms) { if (lower.includes(term)) score += 0.3; if (lower.split("/").some((part) => part.includes(term))) score += 0.2; if (path.toLowerCase().endsWith(term)) score += 0.2; if (isSubsequence(term, lower)) score += 0.1; } return Math.min(1, score); } function isSubsequence(needle: string, haystack: string): boolean { let j = 0; for (const ch of haystack) { if (ch === needle[j]) j += 1; if (j === needle.length) return true; } return false; } function normalizeScore(score: number): number { if (!Number.isFinite(score)) return 0; if (score <= 1) return Math.max(0, score); return Math.min(1, score / 1000); }