Last active
November 8, 2025 20:14
-
-
Save jupblb/214a08efb128190d824abdafd934ce46 to your computer and use it in GitHub Desktop.
zoekt.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bun | |
| import { exec } from 'child_process' | |
| import { promisify } from 'util' | |
| import * as fs from 'fs' | |
| import * as path from 'path' | |
| const execAsync = promisify(exec) | |
| const action = process.env.TOOLBOX_ACTION | |
| // Get repository root | |
| async function getRepoRoot() { | |
| try { | |
| const { stdout } = await execAsync('git rev-parse --show-toplevel') | |
| return stdout.trim() | |
| } catch (error) { | |
| console.error('Error: Not in a git repository') | |
| process.exit(1) | |
| } | |
| } | |
| // Get current HEAD commit | |
| async function getHeadCommit() { | |
| try { | |
| const { stdout } = await execAsync('git rev-parse HEAD') | |
| return stdout.trim() | |
| } catch (error) { | |
| console.error('Error getting HEAD commit:', error.message) | |
| process.exit(1) | |
| } | |
| } | |
| // Check if index needs updating | |
| async function indexNeedsUpdate(indexDir) { | |
| const commitFile = path.join(indexDir, 'last_indexed_commit') | |
| if (!fs.existsSync(indexDir) || !fs.existsSync(commitFile)) { | |
| return true | |
| } | |
| try { | |
| const lastIndexedCommit = fs.readFileSync(commitFile, 'utf8').trim() | |
| const currentCommit = await getHeadCommit() | |
| return lastIndexedCommit !== currentCommit | |
| } catch { | |
| return true | |
| } | |
| } | |
| // Update the index | |
| async function updateIndex(repoRoot, indexDir) { | |
| console.error('Updating zoekt index...') | |
| // Create index directory if it doesn't exist | |
| if (!fs.existsSync(indexDir)) { | |
| fs.mkdirSync(indexDir, { recursive: true }) | |
| } | |
| try { | |
| // Run zoekt-git-index | |
| const { stdout, stderr } = await execAsync( | |
| `zoekt-git-index -index "${indexDir}" "${repoRoot}"`, | |
| { cwd: repoRoot } | |
| ) | |
| // Save current commit | |
| const currentCommit = await getHeadCommit() | |
| fs.writeFileSync(path.join(indexDir, 'last_indexed_commit'), currentCommit) | |
| console.error('Index updated successfully') | |
| } catch (error) { | |
| console.error('Error updating index:', error.message) | |
| process.exit(1) | |
| } | |
| } | |
| // Execute search with pagination | |
| async function executeSearch() { | |
| const repoRoot = await getRepoRoot() | |
| const indexDir = path.join(repoRoot, '.zoekt') | |
| // Check if index needs updating | |
| if (await indexNeedsUpdate(indexDir)) { | |
| await updateIndex(repoRoot, indexDir) | |
| } | |
| // Read input from stdin | |
| let input = '' | |
| for await (const chunk of process.stdin) { | |
| input += chunk | |
| } | |
| const params = JSON.parse(input) | |
| const query = params.query | |
| const limit = params.limit || 20 // Default to 20 results | |
| const offset = params.offset || 0 // Default to 0 offset | |
| if (!query) { | |
| console.error('Error: query parameter is required') | |
| process.exit(1) | |
| } | |
| try { | |
| // Run zoekt search to get all results | |
| const { stdout } = await execAsync( | |
| `zoekt -index_dir "${indexDir}" '${query.replace(/'/g, "'\\''")}'`, | |
| { cwd: repoRoot, maxBuffer: 10 * 1024 * 1024 } // Increase buffer for large results | |
| ) | |
| // Parse results into lines | |
| const lines = stdout.split('\n').filter(line => line.trim() !== '') | |
| const totalResults = lines.length | |
| // Apply pagination | |
| const startIdx = Math.min(offset, totalResults) | |
| const endIdx = Math.min(startIdx + limit, totalResults) | |
| const paginatedLines = lines.slice(startIdx, endIdx) | |
| // Output results with metadata header | |
| if (totalResults > 0) { | |
| // Add header showing pagination info | |
| if (endIdx < totalResults) { | |
| console.log(`##RESULTS showing ${paginatedLines.length} of ${totalResults} total (next: use {"offset":${endIdx},"limit":${limit}} to continue from result #${endIdx})`) | |
| } else { | |
| console.log(`##RESULTS showing ${paginatedLines.length} of ${totalResults} total (all results shown)`) | |
| } | |
| console.log(paginatedLines.join('\n')) | |
| } else { | |
| console.log('##RESULTS 0/0') | |
| } | |
| } catch (error) { | |
| if (error.code === 1 && error.stdout) { | |
| // zoekt returns exit code 1 when no results found | |
| console.log('##RESULTS 0/0') | |
| } else { | |
| console.error('Error executing search:', error.message) | |
| process.exit(1) | |
| } | |
| } | |
| } | |
| // Main switch | |
| switch (action) { | |
| case 'describe': | |
| const schema = { | |
| name: 'zoekt', | |
| description: `Fast indexed code search using Zoekt. USE THIS TOOL FOR ALL CODE SEARCHES in git repositories, EXCEPT when you need to search through uncommitted/unstaged changes. | |
| REQUIREMENT: Only works inside git repositories. Use Grep for non-git directories. | |
| Searches only committed files at HEAD. Index auto-updates when HEAD changes. | |
| Supports full Zoekt query language: | |
| • Symbol search: sym:<name> for function/class/variable definitions (use this for faster, more precise results) | |
| • Text or /regex/ search | |
| • file:<pattern> for path filtering, lang:<language> for language filtering | |
| • Boolean operators: WHITESPACE BETWEEN TERMS MEANS AND (implicit). Use OR explicitly if you want OR behavior. | |
| Examples: "error handler" = error AND handler, "error OR warning" for either term | |
| • Parentheses for grouping: (error OR warning) AND file:\.log$ | |
| • case:yes/no for case sensitivity, -<filter> for negation | |
| PAGINATION: Returns limited results to save tokens. Default limit=20. | |
| - First call: use {"query":"search term"} to get first 20 results | |
| - If you need more: use the exact offset value shown in the results header | |
| - For all results at once: use {"query":"search term","limit":500} | |
| - IMPORTANT: offset is the number of results to SKIP, not a page number. If you fetched 500 results (offset=0, limit=500), next call should use offset=500 to get results 500-999.`, | |
| inputSchema: { | |
| type: 'object', | |
| properties: { | |
| query: { | |
| type: 'string', | |
| description: 'Search query using Zoekt query language. Examples: "functionName", "sym:MyClass", "error AND file:\.test\.ts$", "lang:go fmt.Printf"' | |
| }, | |
| limit: { | |
| type: 'integer', | |
| description: 'Maximum number of result lines to return (default: 20, max: 500). Use 500 to get many results at once.', | |
| default: 20 | |
| }, | |
| offset: { | |
| type: 'integer', | |
| description: 'Number of result lines to SKIP from the beginning (default: 0). Example: offset=500 means skip first 500 results and start from result #501.', | |
| default: 0 | |
| } | |
| }, | |
| required: ['query'] | |
| } | |
| } | |
| console.log(JSON.stringify(schema, null, 2)) | |
| break | |
| case 'execute': | |
| executeSearch().catch(error => { | |
| console.error('Fatal error:', error) | |
| process.exit(1) | |
| }) | |
| break | |
| default: | |
| console.error(`Error: Unknown TOOLBOX_ACTION: ${action}`) | |
| process.exit(1) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment