Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,13 @@ src/api/checkout.ts:89 (Route handler for /pay)
| 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.
**Rule of thumb**: `codebase_peek` to find locations → `Read` to examine → `grep` for precision. For symbol-definition questions, use `implementation_lookup` first.

## 📊 Token Usage

Expand Down Expand Up @@ -296,6 +297,12 @@ The plugin exposes these tools to the OpenCode agent:
```
- **Workflow**: `codebase_peek` → find locations → `Read` specific files

### `implementation_lookup`
**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_search` for broader discovery.

### `find_similar`
Find code similar to a provided snippet.
- **Use for**: Duplicate detection, refactor prep, pattern mining.
Expand Down Expand Up @@ -530,7 +537,8 @@ Zero-config by default (uses `auto` mode). Customize in `.opencode/codebase-inde
"fusionStrategy": "rrf", // rrf | weighted
"rrfK": 60, // RRF smoothing constant
"rerankTopN": 20, // Deterministic rerank depth
"contextLines": 0 // Extra lines before/after match
"contextLines": 0, // Extra lines before/after match
"routingHints": true // Runtime nudges for local discovery/definition queries
},
"reranker": {
"enabled": false,
Expand Down Expand Up @@ -600,6 +608,7 @@ String values in `codebase-index.json` can reference environment variables with
| `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` |
Expand All @@ -621,6 +630,7 @@ String values in `codebase-index.json` can reference environment variables with
### Retrieval ranking behavior

- `codebase_search` and `codebase_peek` use the hybrid path: semantic + keyword retrieval → fusion (`fusionStrategy`) → deterministic rerank (`rerankTopN`) → optional external reranker (`reranker`) → filtering.
- When `search.routingHints` is enabled (default), the plugin adds tiny per-turn runtime hints for local conceptual discovery and definition queries. Conceptual discovery is nudged toward `codebase_peek` / `codebase_search`, while definition questions are nudged toward `implementation_lookup`. Exact identifier and unrelated operational tasks are left alone.
- `find_similar` stays semantic-only: semantic retrieval + deterministic rerank only (no keyword retrieval, no RRF).
- For compatibility rollbacks, set `search.fusionStrategy` to `"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.
Expand Down
3 changes: 3 additions & 0 deletions src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface SearchConfig {
rrfK: number;
rerankTopN: number;
contextLines: number;
routingHints: boolean;
}

export type RerankerProvider = "cohere" | "jina" | "custom";
Expand Down Expand Up @@ -161,6 +162,7 @@ function getDefaultSearchConfig(): SearchConfig {
rrfK: 60,
rerankTopN: 20,
contextLines: 0,
routingHints: true,
};
}

Expand Down Expand Up @@ -277,6 +279,7 @@ export function parseConfig(raw: unknown): ParsedCodebaseIndexConfig {
rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
};

const rawDebug = (input.debug && typeof input.debug === "object" ? input.debug : {}) as Record<string, unknown>;
Expand Down
17 changes: 17 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
initializeTools,
} from "./tools/index.js";
import { loadCommandsFromDirectory } from "./commands/loader.js";
import { RoutingHintController } from "./routing-hints.js";
import { hasProjectMarker } from "./utils/files.js";
import type { CombinedWatcher } from "./watcher/index.js";

Expand Down Expand Up @@ -52,6 +53,9 @@ const plugin: Plugin = async ({ directory }) => {
initializeTools(projectRoot, config);

const indexer = getSharedIndexer();
const routingHints = config.search.routingHints
? new RoutingHintController(() => indexer.getStatus())
: null;

const isValidProject = !config.indexing.requireProjectMarker || hasProjectMarker(projectRoot);

Expand Down Expand Up @@ -91,6 +95,19 @@ const plugin: Plugin = async ({ directory }) => {
remove_knowledge_base,
},

async "chat.message"(input, output) {
routingHints?.observeUserMessage(input.sessionID, output.parts);
},

async "experimental.chat.system.transform"(input, output) {
const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
output.system.push(...hints);
},

async "tool.execute.after"(input) {
routingHints?.markToolUsed(input.sessionID, input.tool);
},

async config(cfg) {
cfg.command = cfg.command ?? {};

Expand Down
Loading
Loading