Stop grepping for concepts. Start searching for meaning.
opencode-codebase-index brings semantic understanding to your OpenCode workflow โ and now to any MCP-compatible client like Cursor, Claude Code, and Windsurf. Instead of guessing function names or grepping for keywords, ask your codebase questions in plain English.
- โก Quick Start
- ๐ MCP Server (Cursor, Claude Code, Windsurf, etc.)
- ๐ฏ When to Use What
- ๐งฐ Tools Available
- ๐ฎ Slash Commands
- ๐ Knowledge Base
- ๐ Reranking
- โ๏ธ Configuration
- ๐ค Contributing
- I want to try it now โ go to Quick Start
- I use Cursor/Claude Code/Windsurf โ go to MCP Server setup
- Iโm comparing tools and workflows โ go to When to Use What
- Iโm tuning behavior/cost/performance โ go to Configuration
- I want to contribute โ go to Contributing
- ๐ง Semantic Search: Finds "user authentication" logic even if the function is named
check_creds. - โก Blazing Fast Indexing: Powered by a Rust native module using
tree-sitterandusearch. Incremental updates take milliseconds. - ๐ฟ Branch-Aware: Seamlessly handles git branch switches โ reuses embeddings, filters stale results.
- ๐ Privacy Focused: Your vector index is stored locally in your project.
- ๐ Model Agnostic: Works out-of-the-box with GitHub Copilot, OpenAI, Gemini, or local Ollama models.
- ๐ MCP Server: Use with Cursor, Claude Code, Windsurf, or any MCP-compatible client โ index once, search from anywhere.
-
Install the plugin
npm install opencode-codebase-index
-
Add to
opencode.json{ "plugin": ["opencode-codebase-index"] } -
Index your codebase Run
/indexor ask the agent to index your codebase. This only needs to be done once โ subsequent updates are incremental. -
Start Searching Ask:
"Find the function that handles credit card validation errors"
Use the same semantic search from any MCP-compatible client. Index once, search from anywhere.
-
Install dependencies
npm install opencode-codebase-index @modelcontextprotocol/sdk zod
-
Configure your MCP client
Cursor (
.cursor/mcp.json):{ "mcpServers": { "codebase-index": { "command": "npx", "args": ["opencode-codebase-index-mcp", "--project", "/path/to/your/project"] } } }Claude Code (
claude_desktop_config.json):{ "mcpServers": { "codebase-index": { "command": "npx", "args": ["opencode-codebase-index-mcp", "--project", "/path/to/your/project"] } } } -
CLI options
npx opencode-codebase-index-mcp --project /path/to/repo # specify project root npx opencode-codebase-index-mcp --config /path/to/config # custom config file npx opencode-codebase-index-mcp # uses current directory
The MCP server exposes all 9 tools (codebase_search, codebase_peek, find_similar, call_graph, index_codebase, index_status, index_health_check, index_metrics, index_logs) and 4 prompts (search, find, index, status).
The MCP dependencies (@modelcontextprotocol/sdk, zod) are optional peer dependencies โ they're only needed if you use the MCP server.
Scenario: You're new to a codebase and need to fix a bug in the payment flow.
Without Plugin (grep):
grep "payment" .โ 500 results (too many)grep "card" .โ 200 results (mostly UI)grep "stripe" .โ 50 results (maybe?)
With opencode-codebase-index:
You ask: "Where is the payment validation logic?"
Plugin returns:
src/services/billing.ts:45 (Class PaymentValidator)
src/utils/stripe.ts:12 (Function validateCardToken)
src/api/checkout.ts:89 (Route handler for /pay)
| Scenario | Tool | Why |
|---|---|---|
| Don't know the function name | codebase_search |
Semantic search finds by meaning |
| Exploring unfamiliar codebase | codebase_search |
Discovers related code across files |
| Just need to find locations | codebase_peek |
Returns metadata only, saves ~90% tokens |
| Need the authoritative definition site | implementation_lookup |
Prioritizes real implementation definitions over docs/tests |
| Understand code flow | call_graph |
Find callers/callees of any function |
| Know exact identifier | grep |
Faster, finds all occurrences |
| Need ALL matches | grep |
Semantic returns top N only |
| Mixed discovery + precision | /find (hybrid) |
Best of both worlds |
Rule of thumb: codebase_peek to find locations โ Read to examine โ grep for precision. For symbol-definition questions, use implementation_lookup first.
In our testing across open-source codebases (axios, express), we observed up to 90% reduction in token usage for conceptual queries like "find the error handling middleware".
- Without plugin: Agent explores files, reads code, backtracks, explores more
- With plugin: Semantic search returns relevant code immediately โ less exploration
- Significant savings possible: Up to 90% reduction in the best cases
- Results vary: Savings depend on query type, codebase structure, and agent behavior
- Best for discovery: Conceptual queries benefit most; exact identifier lookups should use grep
- Complements existing tools: Provides a faster initial signal, doesn't replace grep/explore
- Conceptual queries: "Where is the authentication logic?" (no keywords to grep for)
- Unfamiliar codebases: You don't know what to search for yet
- Large codebases: Semantic search scales better than exhaustive exploration
graph TD
subgraph Indexing
A[Source Code] -->|Tree-sitter| B[Semantic Chunks]
B -->|Embedding Model| C[Vectors]
C -->|uSearch| D[(Vector Store)]
C -->|SQLite| G[(Embeddings DB)]
B -->|BM25| E[(Inverted Index)]
B -->|Branch Catalog| G
end
subgraph Searching
Q[User Query] -->|Embedding Model| V[Query Vector]
V -->|Cosine Similarity| D
Q -->|BM25| E
D --> F[Hybrid Fusion RRF/Weighted]
E --> F
F --> X[Deterministic Rerank]
G -->|Branch + Metadata Filters| X
X --> R[Ranked Results]
end
- Parsing: We use
tree-sitterto intelligently parse your code into meaningful blocks (functions, classes, interfaces). JSDoc comments and docstrings are automatically included with their associated code.
Supported Languages (Tree-sitter semantic parsing): TypeScript, JavaScript, Python, Rust, Go, Java, C#, Ruby, PHP, Bash, C, C++, JSON, TOML, YAML
Additional Supported Formats (line-based chunking): TXT, HTML, HTM, Markdown, Shell scripts
Default File Patterns:
**/*.{ts,tsx,js,jsx,mjs,cjs} **/*.{py,pyi}
**/*.{go,rs,java,kt,scala} **/*.{c,cpp,cc,h,hpp}
**/*.{rb,php,inc,swift} **/*.{vue,svelte,astro}
**/*.{sql,graphql,proto} **/*.{yaml,yml,toml}
**/*.{md,mdx} **/*.{sh,bash,zsh}
**/*.{txt,html,htm}
Use include to replace defaults, or additionalInclude to extend (e.g. "**/*.pdf", "**/*.csv").
Max File Size: Default 1MB (1048576 bytes). Configure via indexing.maxFileSize (bytes).
2. Chunking: Large blocks are split with overlapping windows to preserve context across chunk boundaries.
3. Embedding: These blocks are converted into vector representations using your configured AI provider.
4. Storage: Embeddings are stored in SQLite (deduplicated by content hash) and vectors in usearch with F16 quantization for 50% memory savings. A branch catalog tracks which chunks exist on each branch.
5. Hybrid Search: Combines semantic similarity (vectors) with BM25 keyword matching, fuses (rrf default, weighted fallback), applies deterministic rerank, then filters by current branch/metadata.
Performance characteristics:
- Incremental indexing: ~50ms check time โ only re-embeds changed files
- Smart chunking: Understands code structure to keep functions whole, with overlap for context
- Native speed: Core logic written in Rust for maximum performance
- Memory efficient: F16 vector quantization reduces index size by 50%
- Branch-aware: Automatically tracks which chunks exist on each git branch
- Provider validation: Detects embedding provider/model changes and requires rebuild to prevent garbage results
The plugin automatically detects git branches and optimizes indexing across branch switches.
When you switch branches, code changes but embeddings for unchanged content remain the same. The plugin:
- Stores embeddings by content hash: Embeddings are deduplicated across branches
- Tracks branch membership: A lightweight catalog tracks which chunks exist on each branch
- Filters search results: Queries only return results relevant to the current branch
| Scenario | Without Branch Awareness | With Branch Awareness |
|---|---|---|
| Switch to feature branch | Re-index everything | Instant โ reuse existing embeddings |
| Return to main | Re-index everything | Instant โ catalog already exists |
| Search on branch | May return stale results | Only returns current branch's code |
- Branch detection: Automatically reads from
.git/HEAD - Re-indexing on switch: Triggers when you switch branches (via file watcher)
- Legacy migration: Automatically migrates old indexes on first run
- Garbage collection: Health check removes orphaned embeddings and chunks
.opencode/index/
โโโ codebase.db # SQLite: embeddings, chunks, branch catalog, symbols, call edges
โโโ vectors.usearch # Vector index (uSearch)
โโโ inverted-index.json # BM25 keyword index
โโโ file-hashes.json # File change detection
The following files/folders are excluded from indexing by default:
- Hidden files/folders: Files starting with
.(e.g.,.github,.vscode,.env) - Build folders: Folders containing "build" in their name (e.g.,
build,mingwBuildDebug,cmake-build-debug) - Default excludes:
node_modules,dist,vendor,__pycache__,target,coverage, etc.
The plugin exposes these tools to the OpenCode agent:
The primary tool. Searches code by describing behavior.
- Use for: Discovery, understanding flows, finding logic when you don't know the names.
- Example:
"find the middleware that sanitizes input" - Ranking path: hybrid retrieval โ fusion (
search.fusionStrategy) โ deterministic rerank (search.rerankTopN) โ filters
Writing good queries:
| โ Good queries (describe behavior) | โ Bad queries (too vague) |
|---|---|
| "function that validates email format" | "email" |
| "error handling for failed API calls" | "error" |
| "middleware that checks authentication" | "auth middleware" |
| "code that calculates shipping costs" | "shipping" |
| "where user permissions are checked" | "permissions" |
Token-efficient discovery. Returns only metadata (file, line, name, type) without code content.
- Use for: Finding WHERE code is before deciding what to read. Saves ~90% tokens vs
codebase_search. - Ranking path: same hybrid ranking path as
codebase_search(metadata-only output) - Example output:
[1] function "validatePayment" at src/billing.ts:45-67 (score: 0.92) [2] class "PaymentProcessor" at src/processor.ts:12-89 (score: 0.87) Use Read tool to examine specific files. - Workflow:
codebase_peekโ find locations โReadspecific files
Definition-first lookup. Jumps to the authoritative definition site for a symbol or natural-language definition query.
- Use for: "Where is X defined?", symbol-definition requests, and cases where you want the implementation site rather than all usages.
- Behavior: Prefers real implementation files over tests, docs, examples, and fixtures.
- Fallback: If nothing authoritative is found, use
codebase_searchfor broader discovery.
Find code similar to a provided snippet.
- Use for: Duplicate detection, refactor prep, pattern mining.
- Ranking path: semantic retrieval only + deterministic rerank (no BM25, no RRF).
Manually trigger indexing.
- Use for: Forcing a re-index or checking stats.
- Parameters:
force(rebuild all),estimateOnly(check costs),verbose(show skipped files and parse failures).
Checks if the index is ready and healthy.
Maintenance tool to remove stale entries from deleted files and orphaned embeddings/chunks from the database.
Returns collected metrics about indexing and search performance. Requires debug.enabled and debug.metrics to be true.
- Metrics include: Files indexed, chunks created, cache hit rate, search timing breakdown, GC stats, embedding API call stats.
Returns recent debug logs with optional filtering.
- Parameters:
category(optional:search,embedding,cache,gc,branch),level(optional:error,warn,info,debug),limit(default: 50).
Query the call graph to find callers or callees of a function/method. Automatically built during indexing for TypeScript, JavaScript, Python, Go, and Rust.
- Use for: Understanding code flow, tracing dependencies, impact analysis.
- Parameters:
name(function name),direction(callersorcallees),symbolId(required forcallees, returned by previous queries). - Example: Find who calls
validateTokenโcall_graph(name="validateToken", direction="callers")
Add a folder as a knowledge base to be indexed alongside project code.
- Use for: Indexing external documentation, API references, example programs.
- Parameters:
path(folder path, absolute or relative),reindex(optional, defaulttrue). - Example:
add_knowledge_base(path="/path/to/docs")
List all configured knowledge base folders and their status.
Remove a knowledge base folder from the index.
- Parameters:
path(folder path to remove),reindex(optional, defaultfalse). - Example:
remove_knowledge_base(path="/path/to/docs")
The plugin automatically registers these slash commands:
| Command | Description |
|---|---|
/search <query> |
Pure Semantic Search. Best for "How does X work?" |
/find <query> |
Hybrid Search. Combines semantic search + grep. Best for "Find usage of X". |
/call-graph <query> |
Call Graph Trace. Find callers/callees to understand execution flow. |
/index |
Update Index. Forces a refresh of the codebase index. |
/status |
Check Status. Shows if indexed, chunk count, and provider info. |
The plugin can index external documentation alongside your project code. The indexed codebase includes:
- Project Source Code โ all code files in the current workspace
- API References โ hardware API docs, library documentation
- Usage Guides โ tutorials, how-to guides
- Example Programs โ code samples, demo projects
Use the built-in tools to add documentation folders:
add_knowledge_base(path="/path/to/api-docs")
add_knowledge_base(path="/path/to/examples")
The folder will be indexed into the same database as your project code. All searches automatically include both sources.
list_knowledge_bases # Show configured knowledge bases
remove_knowledge_base(path="/path/to/api-docs") # Remove a knowledge base
Project-level config (.opencode/codebase-index.json):
{
"knowledgeBases": [
"/home/user/docs/esp-idf",
"/home/user/docs/arduino"
]
}Global-level config (~/.config/opencode/codebase-index.json):
{
"embeddingProvider": "custom",
"customProvider": {
"baseUrl": "https://api.siliconflow.cn/v1",
"model": "BAAI/bge-m3",
"dimensions": 1024,
"apiKey": "{env:SILICONFLOW_API_KEY}"
}
}Config merging: Global config is the base, project config overrides. Knowledge bases from both levels are merged.
- Project code: Auto-synced via file watcher (real-time)
- Knowledge base folders: Manual sync โ run
/index forceafter changes
The plugin supports API-based reranking for improved search result quality. Reranking uses a cross-encoder model to rescore the top search results.
Add to your config (.opencode/codebase-index.json or global config):
{
"reranker": {
"enabled": true,
"baseUrl": "https://api.siliconflow.cn/v1",
"model": "BAAI/bge-reranker-v2-m3",
"apiKey": "{env:SILICONFLOW_API_KEY}",
"topN": 20
}
}| Option | Default | Description |
|---|---|---|
enabled |
false |
Enable reranking |
baseUrl |
- | Rerank API endpoint |
model |
- | Reranking model name |
apiKey |
- | API key (use {env:VAR} for security) |
topN |
20 |
Number of top results to rerank |
timeoutMs |
30000 |
Request timeout |
Query โ Embedding Search โ BM25 Search โ Fusion โ Reranking โ Results
- Embedding Search: Semantic similarity via vector search
- BM25 Search: Keyword matching via inverted index
- Fusion: Combine semantic + keyword results (RRF or weighted)
- Reranking: Cross-encoder rescores top N results via API
- Results: Final ranked results
Any OpenAI-compatible reranking endpoint. Examples:
- SiliconFlow:
BAAI/bge-reranker-v2-m3 - Cohere:
rerank-english-v3.0 - Local models: Any server implementing
/v1/rerankformat
Zero-config by default (uses auto mode). Customize in .opencode/codebase-index.json:
{
// === Embedding Provider ===
"embeddingProvider": "custom", // auto | github-copilot | openai | google | ollama | custom
"scope": "project", // project (per-repo) | global (shared)
// === Custom Embedding API (when embeddingProvider is "custom") ===
"customProvider": {
"baseUrl": "https://api.siliconflow.cn/v1",
"model": "BAAI/bge-m3",
"dimensions": 1024,
"apiKey": "{env:SILICONFLOW_API_KEY}",
"maxTokens": 8192, // Max tokens per input text
"timeoutMs": 30000, // Request timeout (ms)
"concurrency": 3, // Max concurrent requests
"requestIntervalMs": 1000, // Min delay between requests (ms)
"maxBatchSize": 64 // Max inputs per /embeddings request
},
// === File Patterns ===
"include": [ // Override default include patterns
"**/*.{ts,js,py,go,rs}"
],
"exclude": [ // Override default exclude patterns
"**/node_modules/**"
],
"additionalInclude": [ // Extend defaults (not replace)
"**/*.{txt,html,htm}",
"**/*.pdf"
],
// === Knowledge Bases ===
"knowledgeBases": [ // External docs to index alongside code
"/home/user/docs/esp-idf",
"/home/user/docs/arduino"
],
// === Indexing ===
"indexing": {
"autoIndex": false, // Auto-index on plugin load
"watchFiles": true, // Re-index on file changes
"maxFileSize": 1048576, // Max file size in bytes (default: 1MB)
"maxChunksPerFile": 100, // Max chunks per file
"semanticOnly": false, // Only index functions/classes (skip blocks)
"retries": 3, // Embedding API retry attempts
"retryDelayMs": 1000, // Delay between retries (ms)
"autoGc": true, // Auto garbage collection
"gcIntervalDays": 7, // GC interval (days)
"gcOrphanThreshold": 100, // GC trigger threshold
"requireProjectMarker": true, // Require .git/package.json to index
"maxDepth": 5, // Max directory depth (-1=unlimited, 0=root only)
"maxFilesPerDirectory": 100, // Max files per directory (smallest first)
"fallbackToTextOnMaxChunks": true // Fallback to text chunking on maxChunksPerFile
},
// === Search ===
"search": {
"maxResults": 20, // Max results to return
"minScore": 0.1, // Min similarity score (0-1)
"hybridWeight": 0.5, // Keyword (1.0) vs semantic (0.0)
"fusionStrategy": "rrf", // rrf | weighted
"rrfK": 60, // RRF smoothing constant
"rerankTopN": 20, // Deterministic rerank depth
"contextLines": 0, // Extra lines before/after match
"routingHints": true // Runtime nudges for local discovery/definition queries
},
"reranker": {
"enabled": false,
"provider": "cohere",
"model": "rerank-v3.5",
"apiKey": "{env:RERANK_API_KEY}",
"topN": 15,
"timeoutMs": 10000
},
"debug": {
"enabled": false, // Enable debug logging
"logLevel": "info", // error | warn | info | debug
"logSearch": true, // Log search operations
"logEmbedding": true, // Log embedding API calls
"logCache": true, // Log cache hits/misses
"logGc": true, // Log garbage collection
"logBranch": true, // Log branch detection
"metrics": false // Enable metrics collection
}
}String values in codebase-index.json can reference environment variables with {env:VAR_NAME} when the placeholder is the entire string value. Variable names must match [A-Z_][A-Z0-9_]*. This is useful for secrets such as custom provider API keys so they do not need to be committed to the config file.
{
"embeddingProvider": "custom",
"customProvider": {
"baseUrl": "{env:EMBED_BASE_URL}",
"model": "nomic-embed-text",
"dimensions": 768,
"apiKey": "{env:EMBED_API_KEY}"
}
}| Option | Default | Description |
|---|---|---|
embeddingProvider |
"auto" |
Which AI to use: auto, github-copilot, openai, google, ollama, custom |
scope |
"project" |
project = index per repo, global = shared index across repos |
include |
(defaults) | Override the default include patterns (replaces defaults) |
exclude |
(defaults) | Override the default exclude patterns (replaces defaults) |
additionalInclude |
[] |
Additional file patterns to include (extends defaults, e.g. "**/*.txt", "**/*.html") |
knowledgeBases |
[] |
External directories to index as knowledge bases (absolute or relative paths) |
| indexing | ||
autoIndex |
false |
Automatically index on plugin load |
watchFiles |
true |
Re-index when files change |
maxFileSize |
1048576 |
Skip files larger than this (bytes). Default: 1MB |
maxChunksPerFile |
100 |
Maximum chunks to index per file (controls token costs for large files) |
semanticOnly |
false |
When true, only index semantic nodes (functions, classes) and skip generic blocks |
retries |
3 |
Number of retry attempts for failed embedding API calls |
retryDelayMs |
1000 |
Delay between retries in milliseconds |
autoGc |
true |
Automatically run garbage collection to remove orphaned embeddings/chunks |
gcIntervalDays |
7 |
Run GC on initialization if last GC was more than N days ago |
gcOrphanThreshold |
100 |
Run GC after indexing if orphan count exceeds this threshold |
requireProjectMarker |
true |
Require a project marker (.git, package.json, etc.) to enable file watching and auto-indexing. Prevents accidentally indexing large directories like home. Set to false to index any directory. |
maxDepth |
5 |
Max directory traversal depth. -1 = unlimited, 0 = only files in root dir, 1 = one level of subdirectories, etc. |
maxFilesPerDirectory |
100 |
Max files to index per directory. Always picks the smallest files first. |
fallbackToTextOnMaxChunks |
true |
When a file exceeds maxChunksPerFile, fallback to text-based (line-by-line) chunking instead of skipping the rest of the file. |
| search | ||
maxResults |
20 |
Maximum results to return |
minScore |
0.1 |
Minimum similarity score (0-1). Lower = more results |
hybridWeight |
0.5 |
Balance between keyword (1.0) and semantic (0.0) search |
fusionStrategy |
"rrf" |
Hybrid fusion mode: "rrf" (rank-based reciprocal rank fusion) or "weighted" (legacy score blending fallback) |
rrfK |
60 |
RRF smoothing constant. Higher values flatten rank impact, lower values prioritize top-ranked candidates more strongly |
rerankTopN |
20 |
Deterministic rerank depth cap. Applies lightweight name/path/chunk-type rerank to top-N only |
contextLines |
0 |
Extra lines to include before/after each match |
routingHints |
true |
Inject lightweight runtime hints for local conceptual discovery and definition lookups. Set to false to disable plugin-side routing nudges. |
| reranker | Optional second-stage model reranker for the top candidate pool | |
enabled |
false |
Turn external reranking on/off |
provider |
"custom" |
Hosted shortcuts: cohere, jina, or custom |
model |
โ | Reranker model name required when enabled |
baseUrl |
provider default | Override reranker endpoint base URL. cohere โ https://api.cohere.ai/v1, jina โ https://api.jina.ai/v1 |
apiKey |
โ | API key for hosted reranker providers |
topN |
15 |
Number of top candidates to send to the external reranker |
timeoutMs |
10000 |
Timeout for external rerank requests |
| debug | ||
enabled |
false |
Enable debug logging and metrics collection |
logLevel |
"info" |
Log level: error, warn, info, debug |
logSearch |
true |
Log search operations with timing breakdown |
logEmbedding |
true |
Log embedding API calls (success, error, rate-limit) |
logCache |
true |
Log cache hits and misses |
logGc |
true |
Log garbage collection operations |
logBranch |
true |
Log branch detection and switches |
metrics |
false |
Enable metrics collection (indexing stats, search timing, cache performance) |
codebase_searchandcodebase_peekuse the hybrid path: semantic + keyword retrieval โ fusion (fusionStrategy) โ deterministic rerank (rerankTopN) โ optional external reranker (reranker) โ filtering.- When
search.routingHintsis enabled (default), the plugin adds tiny per-turn runtime hints for local conceptual discovery and definition queries. Conceptual discovery is nudged towardcodebase_peek/codebase_search, while definition questions are nudged towardimplementation_lookup. Exact identifier and unrelated operational tasks are left alone. find_similarstays semantic-only: semantic retrieval + deterministic rerank only (no keyword retrieval, no RRF).- For compatibility rollbacks, set
search.fusionStrategyto"weighted"to use the legacy weighted fusion path. - When enabled, the external reranker sees path metadata plus a bounded on-disk code snippet for each candidate so it can distinguish real implementations from docs/tests more reliably.
- Retrieval benchmark artifacts are separated by role:
- baseline (versioned):
benchmarks/baselines/retrieval-baseline.json - latest candidate run (generated):
benchmark-results/retrieval-candidate.json
- baseline (versioned):
This repository includes a first-class eval system for retrieval quality with versioned golden sets, compare mode, parameter sweeps, CI budgets, and run artifacts.
npm run eval
npm run eval:ci
npm run eval:ci:ollama
npm run eval:compare -- --against benchmarks/baselines/eval-baseline-summary.jsonCI usage split:
npm run eval:smoke: harness smoke check with local mock embeddings (used in main CI)npm run eval:ci: real quality gate against baseline/budget (for scheduled/manual quality workflow)
For eval-quality.yml, the default CI path uses GitHub Models with the workflow GITHUB_TOKEN plus models: read, so you do not need a separate OpenAI API key just to run the scheduled gate.
That default GitHub Models path uses benchmarks/budgets/github-models.json, which applies stable absolute thresholds instead of the stricter baseline-regression budget used for explicit external providers.
Optional override secrets for another OpenAI-compatible endpoint:
EVAL_EMBED_BASE_URLEVAL_EMBED_API_KEYEVAL_EMBED_MODEL(optional, defaulttext-embedding-3-small)EVAL_EMBED_DIMENSIONS(optional, default1536)
If you override the provider, set both EVAL_EMBED_BASE_URL and EVAL_EMBED_API_KEY. Otherwise the workflow falls back to GitHub Models automatically. Override providers continue to use the baseline-driven budget in benchmarks/budgets/default.json.
No OpenAI API access? Use Ollama quality gate locally:
- Config:
.github/eval-ollama-config.json - Script:
npm run eval:ci:ollama
Prerequisites: Ollama installed, ollama serve running on 127.0.0.1:11434, and nomic-embed-text pulled.
Examples:
# Run against small golden set
npm run eval -- --dataset benchmarks/golden/small.json
# Compare against baseline
npm run eval:compare -- --against benchmarks/baselines/eval-baseline-summary.json --dataset benchmarks/golden/medium.json
# Sweep retrieval parameters
npm run eval -- --dataset benchmarks/golden/small.json --sweepFusionStrategy rrf,weighted --sweepHybridWeight 0.3,0.5,0.7 --sweepRrfK 30,60 --sweepRerankTopN 10,20- Hit@1, Hit@3, Hit@5, Hit@10
- MRR@10, nDCG@10
- Latency p50/p95/p99
- Token estimates, embedding call counts, estimated embedding cost
- Failure buckets (
wrong-file,wrong-symbol,docs-tests-outranking-source,no-relevant-hit-top-k)
Each run writes:
benchmarks/results/<timestamp>/
summary.jsonsummary.mdper-query.jsoncompare.json(when baseline/sweep used)
- Golden datasets:
benchmarks/golden/small.jsonbenchmarks/golden/medium.jsonbenchmarks/golden/large.json
- CI budgets:
benchmarks/budgets/github-models.jsonfor the default GitHub Models workflow pathbenchmarks/budgets/default.jsonfor explicit external provider overrides with baseline comparison
Full docs: docs/evaluation.md
Recent representative runs (plugin vs ripgrep vs ast-grep) on two medium repos:
Methodology for the snapshot below:
- Dataset: auto-generated cross-repo golden sets for
axios+express - Repeats: 20 per mode
- Aggregation: median metric per tool (then averaged across repos)
- Reindex behavior: when enabled, index reset applies on repeat #1 only; subsequent repeats measure warm-index query behavior
- Sampling note: repository parsing can be capped; benchmark reports include truncation metadata
- ast-grep scope note: sg metrics are computed on its compatible query subset (
definition,keyword-heavy) with scoped denominators shown in run reports
| Metric | Plugin | ripgrep | ast-grep (5/10 queries) |
|---|---|---|---|
| Hit@5 | 50% | 5% | 100% |
| MRR@10 | 0.48 | 0.04 | 0.90 |
| nDCG@10 | 0.48 | 0.08 | 0.93 |
| Latency p50 (ms) | 17.5 | 36.9 | 66.6 |
| Latency p95 (ms) | 30.9 | 44.1 | 70.7 |
| Metric | Plugin | ripgrep | ast-grep (5/10 queries) |
|---|---|---|---|
| Hit@5 | 50% | 5% | 100% |
| MRR@10 | 0.48 | 0.04 | 0.98 |
| nDCG@10 | 0.48 | 0.07 | 0.98 |
| Latency p50 (ms) | 17.1 | 35.9 | 69.1 |
| Latency p95 (ms) | 30.4 | 43.7 | 75.1 |
ast-grep metrics are computed on its compatible query subset only (definition + keyword-heavy, 5/10 queries per repo). Plugin and ripgrep are scored on all 10 queries.
Interpretation:
- ast-grep dominates on its scoped subset (structural definition queries), but only handles 50% of query types. Plugin handles all query types including natural language.
- Plugin leads on rank-sensitive quality (MRR/nDCG) vs ripgrep across all query types.
- ripgrep remains a useful speed-oriented lexical baseline but has significantly lower retrieval relevance for intent-style queries.
- Plugin is the fastest tool at p50 (~17ms), ahead of ripgrep (~36ms) and ast-grep (~67ms).
- Reported numbers are rounded to avoid false precision; use report artifacts for full per-repeat audit trails.
For reproducible setup and commands (including with/without reindex), see:
docs/benchmarking-cross-repo.md
The plugin automatically detects available credentials in this order:
- GitHub Copilot (Free if you have it)
- OpenAI (Standard Embeddings)
- Google (Gemini Embeddings)
- Ollama (Local/Private - requires
nomic-embed-text)
You can also use Custom to connect any OpenAI-compatible embedding endpoint (llama.cpp, vLLM, text-embeddings-inference, LiteLLM, etc.).
Each provider has different rate limits. The plugin automatically adjusts concurrency and delays:
| Provider | Concurrency | Delay | Best For |
|---|---|---|---|
| GitHub Copilot | 1 | 4s | Small codebases (<1k files) |
| OpenAI | 3 | 500ms | Medium codebases |
| 5 | 200ms | Medium-large codebases | |
| Ollama | 5 | None | Large codebases (10k+ files) |
| Custom | 3 | 1s | Any OpenAI-compatible endpoint |
For large codebases, use Ollama locally to avoid rate limits:
# Install the embedding model
ollama pull nomic-embed-text// .opencode/codebase-index.json
{
"embeddingProvider": "ollama"
}The built-in ollama provider uses Ollama's native /api/embeddings endpoint and is the simplest setup when you want to use nomic-embed-text.
If you want to use a different Ollama embedding model through its OpenAI-compatible API, use the custom provider instead and set customProvider.baseUrl to http://127.0.0.1:11434/v1 so the plugin calls .../v1/embeddings.
The plugin is built for speed with a Rust native module (tree-sitter, usearch, SQLite). In practice, indexing and retrieval remain fast enough for interactive use on medium/large repositories.
- Typical query latency: ~800-1000ms (mostly embedding API time)
- Incremental indexing: only changed files are re-embedded
- Batch DB operations: significant write-speed improvements for large indexes
For reproducible measurements on your machine, run: npx tsx benchmarks/run.ts.
Quick recommendation:
- Want local + private + fast indexing โ use Ollama
- Already have Copilot and a smaller repo โ use GitHub Copilot
- General cloud setup โ use OpenAI or Google
- Custom/OpenAI-compatible endpoint โ use custom provider
| Provider | Speed | Cost | Privacy | Best For |
|---|---|---|---|---|
| Ollama | Fastest | Free | Full | Large codebases, privacy-sensitive |
| GitHub Copilot | Slow (rate limited) | Free* | Cloud | Small codebases, existing subscribers |
| OpenAI | Medium | ~$0.0001/1K tokens | Cloud | General use |
| Fast | Free tier available | Cloud | Medium-large codebases | |
| Custom | Varies | Varies | Varies | Self-hosted or third-party endpoints |
*Requires active Copilot subscription
Set the provider in .opencode/codebase-index.json:
{ "embeddingProvider": "ollama" }Credentials (if required) are read from environment variables (for example OPENAI_API_KEY or GOOGLE_API_KEY).
Custom (OpenAI-compatible)
Works with any server that implements the OpenAI /v1/embeddings API format (llama.cpp, vLLM, text-embeddings-inference, LiteLLM, etc.).
{
"embeddingProvider": "custom",
"customProvider": {
"baseUrl": "{env:EMBED_BASE_URL}",
"model": "nomic-embed-text",
"dimensions": 768,
"apiKey": "{env:EMBED_API_KEY}",
"maxTokens": 8192,
"timeoutMs": 30000,
"maxBatchSize": 64
}
}Required fields: baseUrl, model, dimensions (positive integer). Optional: apiKey, maxTokens, timeoutMs (default: 30000), maxBatchSize (or max_batch_size) to cap inputs per /embeddings request for servers like text-embeddings-inference. {env:VAR_NAME} placeholders are resolved before config validation for fields that are actually used and throw if the referenced environment variable is missing or malformed.
Custom Ollama models via OpenAI-compatible API
If you are running Ollama locally and want to use an embedding model other than the built-in ollama setup, point the custom provider at Ollama's OpenAI-compatible base URL with the /v1 suffix:
{
"embeddingProvider": "custom",
"customProvider": {
"baseUrl": "http://127.0.0.1:11434/v1",
"model": "qwen3-embedding:0.6b",
"dimensions": 1024,
"apiKey": "ollama"
}
}Notes:
- The plugin appends
/embeddings, sobaseUrlshould behttp://127.0.0.1:11434/v1, not justhttp://127.0.0.1:11434. - Ollama ignores the API key, but some OpenAI-compatible clients expect one, so a placeholder like
"ollama"is fine. - Make sure
dimensionsmatches the actual output size of the model you pulled locally.
Be aware of these characteristics:
| Aspect | Reality |
|---|---|
| Search latency | ~800-1000ms per query (embedding API call) |
| First index | Takes time depending on codebase size (e.g., ~30s for 500 chunks) |
| Requires API | Needs an embedding provider (Copilot, OpenAI, Google, or local Ollama) |
| Token costs | Uses embedding tokens (free with Copilot, minimal with others) |
| Best for | Discovery and exploration, not exhaustive matching |
-
Build:
npm run build
-
Register in Test Project (use
file://URL inopencode.json):{ "plugin": [ "file:///path/to/opencode-codebase-index" ] }This loads directly from your source directory, so changes take effect after rebuilding.
For contribution workflow, standards, and release-label requirements, see CONTRIBUTING.md.
If you want to add support for a new language, see docs/adding-language-support.md for the full Rust + TypeScript checklist.
Quick path:
- Fork + branch
- Implement + tests
- Run checks:
npm run build && npm run typecheck && npm run lint && npm run test:run - Open PR with a release category label
To ensure release notes reflect all merged work, this repo uses a draft-release workflow.
- Label every PR with at least one semantic label:
feature,bug,performance,documentation,dependencies,refactor,test,chore- and (when relevant)
semver:major,semver:minor, orsemver:patch - PRs are validated by CI (
Release Label Check) and fail if no release category label is present
- Let Release Drafter build the draft notes automatically from merged PRs on
main. - Before publishing:
- copy/finalize relevant highlights into
CHANGELOG.md - bump
package.jsonversion - run:
npm run build && npm run typecheck && npm run lint && npm run test:run
- copy/finalize relevant highlights into
- Publish release from the draft (or via
gh release createafter reviewing draft content).
PRs labeled skip-changelog are intentionally excluded from release notes.
โโโ src/
โ โโโ index.ts # Plugin entry point
โ โโโ mcp-server.ts # MCP server (Cursor, Claude Code, Windsurf)
โ โโโ cli.ts # CLI entry for MCP stdio transport
โ โโโ config/ # Configuration schema
โ โโโ embeddings/ # Provider detection and API calls
โ โโโ indexer/ # Core indexing logic + inverted index
โ โโโ git/ # Git utilities (branch detection)
โ โโโ tools/ # OpenCode tool definitions
โ โโโ utils/ # File collection, cost estimation
โ โโโ native/ # Rust native module wrapper
โ โโโ watcher/ # File/git change watcher
โโโ native/
โ โโโ src/ # Rust: tree-sitter, usearch, xxhash, SQLite
โโโ tests/ # Unit tests (vitest)
โโโ commands/ # Slash command definitions
โโโ skill/ # Agent skill guidance
โโโ .github/workflows/ # CI/CD (test, build, publish)
The Rust native module handles performance-critical operations:
- tree-sitter: Language-aware code parsing with JSDoc/docstring extraction
- usearch: High-performance vector similarity search with F16 quantization
- SQLite: Persistent storage for embeddings, chunks, branch catalog, symbols, and call edges
- BM25 inverted index: Fast keyword search for hybrid retrieval
- Call graph extraction: Tree-sitter query-based extraction of function calls, method calls, constructors, and imports (TypeScript/JavaScript, Python, Go, Rust)
- xxhash: Fast content hashing for change detection
Rebuild with: npm run build:native (requires Rust toolchain)
Pre-built native binaries are published for:
| Platform | Architecture | SIMD Acceleration |
|---|---|---|
| macOS | x86_64 | โ simsimd |
| macOS | ARM64 (Apple Silicon) | โ simsimd |
| Linux | x86_64 (GNU) | โ simsimd |
| Linux | ARM64 (GNU) | โ simsimd |
| Windows | x86_64 (MSVC) | โ scalar fallback |
Windows builds use scalar distance functions instead of SIMD โ functionally identical, marginally slower for very large indexes. This is due to MSVC lacking support for certain AVX-512 intrinsics used by simsimd.
MIT