| name | opencode-rag |
|---|---|
| description | Local-first RAG plugin for semantic code search — tree-sitter chunking, LanceDB, hybrid retrieval |
ALWAYS use OpenCodeRAG tools before reading or editing:
- Search first —
search_semantic(query)instead of grep/glob. Optional args:pathHints,languageHints,fileExtensions(e.g.[".ts"]),topK - Skeleton before read —
get_file_skeleton(filePath)then read specific lines - Usages before edit —
find_usages(symbolName)before modifying any symbol - Images via describe —
describe_image(filePath, systemPrompt?)— never read raw bytes
If no results, run opencode-rag index.
Entry points: src/index.ts (library), src/plugin-entry.ts (OpenCode plugin), src/cli.ts (CLI), src/tui.ts (TUI), src/web/server.ts (Web UI).
Core modules: src/core/ (config, interfaces, manifest), src/chunker/ (AST chunking), src/embedder/ (Ollama/OpenAI/Cohere), src/describer/ (LLM descriptions), src/retriever/ (vector + keyword hybrid), src/vectorstore/ (LanceDB), src/opencode/ (plugin integration).
Full architecture: doc/architecture.md.
- npm install: use
--legacy-peer-deps(LanceDB peer dep conflicts) - LanceDB types: cast through
unknown—rows as unknown as Record<string, unknown>[] - LanceDB index metric: the IVF index on
embeddingmust usedistanceType: "cosine"to matchsearchInternal(default isl2, which makes every query log "Requested metric Cosine is incompatible" and fall back to brute-force).LanceDbStore.ensureCosineIndex()self-heals stale L2 indexes on first search. When replacing an index, use a singlecreateIndex(..., replace: true, waitTimeoutSeconds)— adropIndex+createIndexsequence races and fails with "Retryable commit conflict". - LanceDB "partition N is empty, skipping" warnings: benign once per index build (IVF KMeans on duplicate/degenerate vectors). Constant spam = retrain churn from a store whose index commits never register (one new
_indices/<uuid>dir per attempt).repairIndexMetricOnceguards: counts only index-version dirs WITH files (empty husks from version pruning never trip it), verifies post-createIndex registration viaindexStats, gives up after 3 failed attempts per process;optimize()sweeps empty husk dirs, and rebuilds passoptimize({ skipIndex: true })to temp-store mid-run optimizes so the index is built once at the end. Fix for a truly non-converging store: delete rag_db + reindex. Note: u64-near_versions/*.manifestnames are NORMAL (counter starts at u64::MAX-1 and decrements) — not corruption. - tree-sitter: WASM-only (no native).
Parseris a class,Languageis top-level, useNodenotSyntaxNode - Plugin types:
@opencode-ai/pluginlives in.opencode/node_modules/, declared locally insrc/types/opencode-plugin.d.ts - Config loading:
loadConfig()deep-merges per section (not recursive). CLI auto-detects./opencode-rag.jsonand./.opencode/rag.json - Ollama responses: may return
{ embedding: number[] }or{ embeddings: number[][] }— both accepted - Quirk test:
opencode-rag quirk test <text>checks if a quirk already exists in the store (semantic search). Returns match details or "not appended" - Auto-capture quirks: three
memory.*flags —passiveCapture(per-turn extraction),promptEnforcement(mandatory system prompt),sessionEndExtraction(full-transcript on session end). All off by default. Requiresdescription.enabled: true(reuses description LLM for extraction). - Auto-capture dedup: candidate quirks are deduped against existing quirks via lexical similarity (
autoCaptureDedupThreshold, default 0.85) before being added. - excludeDirs/excludeFiles matching (
src/core/exclude.ts): plain names (no/, no glob chars) match basename at any depth; patterns with a separator are anchored to workspace root. Matching is case-insensitive. Usesminimatch(bundled TS types).walkFilesno longer auto-skips dotdirs — rely onexcludeDirsconfig instead. noUncheckedIndexedAccessintsconfig.json: array indexing returnsstring | undefined. Usefor...ofloops instead of indexedforin new code to avoidObject is possibly 'undefined'errors.- watch.ts ignores both excludeDirs AND excludeFiles:
createWatchIgnoreuses both matchers — any excludeFiles pattern applies to file-watch ignore too. walkFilessignature changed:excludeDirs/excludeFilesparams changed fromSet<string>toExcludeMatcher;rootDirparam added. If you importwalkFilesdirectly, update the call site or usescanWorkspaceFilesinstead.- Watcher runs once per workspace:
createBackgroundIndexerclaims{storePath}/watcher.lock(atomic O_EXCL create + PID liveness viaprocess.kill(pid, 0)). Only ONE process runs the auto-index watcher per workspace; later claimants go dormant (no chokidar/scheduler/passes) and take over via a 60s unref'd re-check timer after the owner exits. CLIindex --watchshares the same lock — if a plugin watcher already owns the workspace it warns and exits 0. Only the owner'sclose()releases the lock; stale/corrupt lock files are auto-reclaimed.
Every new/create/open MUST have a matching close()/destroy()/cancel():
- Use
try/finallyfor cleanup (seesrc/api.tsfor the pattern) - Signal handlers:
process.once(), remove withremoveListener - Map/Set growth must be bounded (session maps: max 50, config caches: clean on workspace reload)
- AbortSignal parameters: always wire through, never prefix with
_ - ReadableStream readers:
reader.cancel()beforereleaseLock()
npm test— unit tests only (Node.js built-innode:test, ~5s)npm run test:integration— integration tests (30s+, spawns opencode)npm run typecheck—tsc --noEmitnpm run build—tsc -p tsconfig.build.json && vite build(backend + frontend)npm run dev:ui— Vite dev server with HMR for frontend development
When testing the web UI (opencode-rag ui), use the firefox-devtools skill.
After rebuilding the frontend (npm run build), the server must be restarted
to pick up new HTML/JS assets (cached in memory). See the skill for the
restart pattern and browser cache troubleshooting.
npm run release:patch — bumps version, builds, tests, tags, publishes (dry-run via --dry).
ALWAYS use OpenCodeRAG tools before reading or editing:
- Search first —
search_semantic(query)instead of grep/glob. Optional args:pathHints,languageHints,fileExtensions(e.g.[".ts"]),topK - Skeleton before read —
get_file_skeleton(filePath)then read specific lines - Usages before edit —
find_usages(symbolName)before modifying any symbol - Images via describe —
describe_image(filePath, systemPrompt?)— never read raw bytes - Recall quirks —
recall_quirks(query)when you hit a known pitfall - Add quirks —
add_quirk(content)when you discover a non-obvious fact - Fix quirks —
update_quirk(id, ...)/delete_quirk(id)when a stored quirk is outdated or wrong
If no results, run opencode-rag index.
- User mentions code behavior/architecture →
search_semantic(query) - User mentions a file path →
get_file_skeleton(filePath)THENreadon specific lines - User mentions a function/class/variable to edit →
find_usages(symbolName)THENsearch_semanticTHENedit - User asks a code question →
search_semanticto gather context before answering - User asks about an image or visual asset →
describe_image(filePath)(optionally passsystemPromptto focus on specific features) to retrieve its generated description, then optionallysearch_semanticfor related code - You encounter an error or need to recall a known pitfall →
recall_quirks(query) - You discover a non-obvious fact or workaround →
add_quirk(content)to persist it for future sessions - A recalled quirk is outdated or wrong →
update_quirk(id, ...)to fix it, ordelete_quirk(id)if it no longer applies
- User asks about code behavior, architecture, or implementation details
- User asks to edit, refactor, or fix code — call
find_usagesfirst - User references files or functions you haven't read yet
- User says "find", "search", "look up", "where is", "how does"
- User refers to an image, screenshot, diagram, or visual asset
- Before answering ANY code-related question, retrieve context first
- Before reading ANY file, call
get_file_skeletonto orient first
- Reading full files without calling
get_file_skeletonfirst (wastes tokens) - Editing a function without calling
find_usagesfirst (breaks call sites) - Answering code questions without calling
search_semanticfirst (you guess at behavior) - Using
grep/globwhensearch_semanticwould find the answer faster - Treating image files as text — use
describe_imageinstead of reading raw bytes - Using
npx opencode-rag quirkshell commands instead of the built-in quirk tools (add_quirk/recall_quirks/update_quirk/delete_quirk) (the tools are faster, already loaded in-process, and go through the trust monitor)
- A build, test, or type-check command fails and you resolve it
- You discover an undocumented library constraint, peer dep, or workaround
- You learn an environment-specific requirement (OS, tool version, etc.)
- You make a design decision that future sessions should remember
- You resolve a gotcha that cost more than one attempt
- A stored quirk is outdated, wrong, or has been fixed — update it or delete it instead of adding a contradicting duplicate
- NEVER finish a coding session without adding quirks for resolved errors.