opencode-rag-plugin 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/ReadMe.md +463 -423
  2. package/dist/__tests__/chunker/c.test.d.ts +1 -0
  3. package/dist/__tests__/chunker/c.test.js +82 -0
  4. package/dist/__tests__/chunker/c.test.js.map +1 -0
  5. package/dist/__tests__/chunker/cpp.test.d.ts +1 -0
  6. package/dist/__tests__/chunker/cpp.test.js +96 -0
  7. package/dist/__tests__/chunker/cpp.test.js.map +1 -0
  8. package/dist/__tests__/chunker/csharp.test.d.ts +1 -0
  9. package/dist/__tests__/chunker/csharp.test.js +85 -0
  10. package/dist/__tests__/chunker/csharp.test.js.map +1 -0
  11. package/dist/__tests__/chunker/css.test.d.ts +1 -0
  12. package/dist/__tests__/chunker/css.test.js +64 -0
  13. package/dist/__tests__/chunker/css.test.js.map +1 -0
  14. package/dist/__tests__/chunker/factory.test.d.ts +1 -0
  15. package/dist/__tests__/chunker/factory.test.js +202 -0
  16. package/dist/__tests__/chunker/factory.test.js.map +1 -0
  17. package/dist/__tests__/chunker/fallback.test.d.ts +1 -0
  18. package/dist/__tests__/chunker/fallback.test.js +67 -0
  19. package/dist/__tests__/chunker/fallback.test.js.map +1 -0
  20. package/dist/__tests__/chunker/go.test.d.ts +1 -0
  21. package/dist/__tests__/chunker/go.test.js +90 -0
  22. package/dist/__tests__/chunker/go.test.js.map +1 -0
  23. package/dist/__tests__/chunker/grammar.test.d.ts +1 -0
  24. package/dist/__tests__/chunker/grammar.test.js +108 -0
  25. package/dist/__tests__/chunker/grammar.test.js.map +1 -0
  26. package/dist/__tests__/chunker/html.test.d.ts +1 -0
  27. package/dist/__tests__/chunker/html.test.js +64 -0
  28. package/dist/__tests__/chunker/html.test.js.map +1 -0
  29. package/dist/__tests__/chunker/java.test.d.ts +1 -0
  30. package/dist/__tests__/chunker/java.test.js +83 -0
  31. package/dist/__tests__/chunker/java.test.js.map +1 -0
  32. package/dist/__tests__/chunker/javascript.test.d.ts +1 -0
  33. package/dist/__tests__/chunker/javascript.test.js +88 -0
  34. package/dist/__tests__/chunker/javascript.test.js.map +1 -0
  35. package/dist/__tests__/chunker/json.test.d.ts +1 -0
  36. package/dist/__tests__/chunker/json.test.js +54 -0
  37. package/dist/__tests__/chunker/json.test.js.map +1 -0
  38. package/dist/__tests__/chunker/kotlin.test.d.ts +1 -0
  39. package/dist/__tests__/chunker/kotlin.test.js +42 -0
  40. package/dist/__tests__/chunker/kotlin.test.js.map +1 -0
  41. package/dist/__tests__/chunker/markdown.test.d.ts +1 -0
  42. package/dist/__tests__/chunker/markdown.test.js +76 -0
  43. package/dist/__tests__/chunker/markdown.test.js.map +1 -0
  44. package/dist/__tests__/chunker/python.test.d.ts +1 -0
  45. package/dist/__tests__/chunker/python.test.js +81 -0
  46. package/dist/__tests__/chunker/python.test.js.map +1 -0
  47. package/dist/__tests__/chunker/razor.test.d.ts +1 -0
  48. package/dist/__tests__/chunker/razor.test.js +73 -0
  49. package/dist/__tests__/chunker/razor.test.js.map +1 -0
  50. package/dist/__tests__/chunker/register.test.d.ts +1 -0
  51. package/dist/__tests__/chunker/register.test.js +93 -0
  52. package/dist/__tests__/chunker/register.test.js.map +1 -0
  53. package/dist/__tests__/chunker/ruby.test.d.ts +1 -0
  54. package/dist/__tests__/chunker/ruby.test.js +40 -0
  55. package/dist/__tests__/chunker/ruby.test.js.map +1 -0
  56. package/dist/__tests__/chunker/rust.test.d.ts +1 -0
  57. package/dist/__tests__/chunker/rust.test.js +42 -0
  58. package/dist/__tests__/chunker/rust.test.js.map +1 -0
  59. package/dist/__tests__/chunker/sln.test.d.ts +1 -0
  60. package/dist/__tests__/chunker/sln.test.js +45 -0
  61. package/dist/__tests__/chunker/sln.test.js.map +1 -0
  62. package/dist/__tests__/chunker/swift.test.d.ts +1 -0
  63. package/dist/__tests__/chunker/swift.test.js +49 -0
  64. package/dist/__tests__/chunker/swift.test.js.map +1 -0
  65. package/dist/__tests__/chunker/typescript.test.d.ts +1 -0
  66. package/dist/__tests__/chunker/typescript.test.js +92 -0
  67. package/dist/__tests__/chunker/typescript.test.js.map +1 -0
  68. package/dist/__tests__/chunker/uuid.test.d.ts +1 -0
  69. package/dist/__tests__/chunker/uuid.test.js +19 -0
  70. package/dist/__tests__/chunker/uuid.test.js.map +1 -0
  71. package/dist/__tests__/chunker/xml.test.d.ts +1 -0
  72. package/dist/__tests__/chunker/xml.test.js +50 -0
  73. package/dist/__tests__/chunker/xml.test.js.map +1 -0
  74. package/dist/__tests__/core/config.test.d.ts +1 -0
  75. package/dist/__tests__/core/config.test.js +75 -0
  76. package/dist/__tests__/core/config.test.js.map +1 -0
  77. package/dist/__tests__/core/fileLogger.test.d.ts +1 -0
  78. package/dist/__tests__/core/fileLogger.test.js +34 -0
  79. package/dist/__tests__/core/fileLogger.test.js.map +1 -0
  80. package/dist/__tests__/core/manifest.test.d.ts +1 -0
  81. package/dist/__tests__/core/manifest.test.js +56 -0
  82. package/dist/__tests__/core/manifest.test.js.map +1 -0
  83. package/dist/__tests__/embedder/embedBatch.test.d.ts +1 -0
  84. package/dist/__tests__/embedder/embedBatch.test.js +88 -0
  85. package/dist/__tests__/embedder/embedBatch.test.js.map +1 -0
  86. package/dist/__tests__/embedder/factory.test.d.ts +1 -0
  87. package/dist/__tests__/embedder/factory.test.js +71 -0
  88. package/dist/__tests__/embedder/factory.test.js.map +1 -0
  89. package/dist/__tests__/embedder/ollama.test.d.ts +1 -0
  90. package/dist/__tests__/embedder/ollama.test.js +106 -0
  91. package/dist/__tests__/embedder/ollama.test.js.map +1 -0
  92. package/dist/__tests__/embedder/openai.test.d.ts +1 -0
  93. package/dist/__tests__/embedder/openai.test.js +94 -0
  94. package/dist/__tests__/embedder/openai.test.js.map +1 -0
  95. package/dist/__tests__/indexer/indexer.test.d.ts +1 -0
  96. package/dist/__tests__/indexer/indexer.test.js +176 -0
  97. package/dist/__tests__/indexer/indexer.test.js.map +1 -0
  98. package/dist/__tests__/plugin.test.d.ts +1 -0
  99. package/dist/__tests__/plugin.test.js +77 -0
  100. package/dist/__tests__/plugin.test.js.map +1 -0
  101. package/dist/__tests__/retriever/retriever.test.d.ts +1 -0
  102. package/dist/__tests__/retriever/retriever.test.js +97 -0
  103. package/dist/__tests__/retriever/retriever.test.js.map +1 -0
  104. package/dist/__tests__/vectorstore/lancedb.test.d.ts +1 -0
  105. package/dist/__tests__/vectorstore/lancedb.test.js +159 -0
  106. package/dist/__tests__/vectorstore/lancedb.test.js.map +1 -0
  107. package/dist/chunker/doc.d.ts +8 -0
  108. package/dist/chunker/doc.js +79 -0
  109. package/dist/chunker/doc.js.map +1 -0
  110. package/dist/chunker/docx.d.ts +8 -0
  111. package/dist/chunker/docx.js +78 -0
  112. package/dist/chunker/docx.js.map +1 -0
  113. package/dist/chunker/excel.d.ts +8 -0
  114. package/dist/chunker/excel.js +78 -0
  115. package/dist/chunker/excel.js.map +1 -0
  116. package/dist/chunker/factory.d.ts +1 -0
  117. package/dist/chunker/factory.js +9 -0
  118. package/dist/chunker/factory.js.map +1 -1
  119. package/dist/cli.js +272 -1
  120. package/dist/cli.js.map +1 -1
  121. package/dist/core/config.d.ts +4 -0
  122. package/dist/core/config.js +6 -1
  123. package/dist/core/config.js.map +1 -1
  124. package/dist/indexer.js +18 -0
  125. package/dist/indexer.js.map +1 -1
  126. package/dist/opencode/create-read-tool.d.ts +30 -0
  127. package/dist/opencode/create-read-tool.js +248 -0
  128. package/dist/opencode/create-read-tool.js.map +1 -0
  129. package/dist/opencode/read-fallback.d.ts +21 -0
  130. package/dist/opencode/read-fallback.js +90 -0
  131. package/dist/opencode/read-fallback.js.map +1 -0
  132. package/dist/opencode/read-format.d.ts +40 -0
  133. package/dist/opencode/read-format.js +86 -0
  134. package/dist/opencode/read-format.js.map +1 -0
  135. package/dist/opencode/read-query.d.ts +26 -0
  136. package/dist/opencode/read-query.js +39 -0
  137. package/dist/opencode/read-query.js.map +1 -0
  138. package/dist/opencode/tool-args.d.ts +51 -0
  139. package/dist/opencode/tool-args.js +70 -0
  140. package/dist/opencode/tool-args.js.map +1 -0
  141. package/dist/plugin.js +1 -0
  142. package/dist/plugin.js.map +1 -1
  143. package/package.json +89 -82
package/ReadMe.md CHANGED
@@ -1,423 +1,463 @@
1
- # OpenCodeRAG
2
-
3
- Local-first RAG plugin for OpenCode — semantic code search powered by
4
- embeddings and vector similarity.
5
-
6
- **Note: This is an early pre-release and may not work correctly in all cases.
7
- If you find bugs, please create an issue.**
8
-
9
- ## Features
10
-
11
- - **AST-aware chunking** splits code into functions, classes, methods using
12
- tree-sitter for 16 languages, plus regex-based chunking for 4 markup/config/doc
13
- formats (Markdown, Razor, .sln, LaTeX). Falls back to line-based chunking for
14
- unrecognized formats.
15
- - **Incremental indexing** manifest-backed indexing skips unchanged files,
16
- removes deleted entries, and updates only changed files.
17
- - **Watch mode** — `index --watch` re-indexes on file changes with debounced,
18
- serialized passes.
19
- - **Pluggable chunkers** — add custom language chunkers via config or programmatic API.
20
- - **Configurable embeddings** Ollama (default) or OpenAI-compatible providers.
21
- Batch embedding with configurable batch size.
22
- - **Local vector store** — LanceDB with L2 distance scoring, memory mode for
23
- testing.
24
- - **CLI** — index, query, clear, status commands.
25
- - **OpenCode plugin** — exposes a chunk retrieval tool and suggests relevant files after each user message via the `chat.message` hook.
26
-
27
- ## Architecture
28
-
29
- ```
30
- Workspace Files
31
-
32
-
33
- ┌──────────────┐
34
- │ Chunker │ AST-based (tree-sitter) or line-based fallback
35
- └──────┬───────┘
36
- │ chunks
37
-
38
- ┌──────────────┐
39
- │ Embedder │ Ollama / OpenAI-compatible API
40
- └──────┬───────┘
41
- │ vectors
42
-
43
- ┌──────────────┐
44
- │ VectorStore │ LanceDB (local files or memory:// for tests)
45
- └──────┬───────┘
46
- │ + manifest.json
47
-
48
- ┌──────────────┐
49
- │ Indexer/Retr.│ incremental index or query/search
50
- └──────┬───────┘
51
- │ results
52
-
53
- LLM Context
54
- ```
55
-
56
- ## Tech Stack
57
-
58
- | Layer | Technology |
59
- | ----------- | --------------------------------------------------- |
60
- | Runtime | Node.js v22 + tsx (ESM) |
61
- | Language | TypeScript 5.8 |
62
- | Chunking | web-tree-sitter (WASM) + tree-sitter-wasm grammars |
63
- | Embeddings | Ollama / OpenAI-compatible (native fetch) |
64
- | Vector DB | LanceDB (`@lancedb/lancedb`) |
65
- | CLI | commander |
66
- | Tests | Node built-in test runner (`node --test`) |
67
- | Package mgr | npm (with `--legacy-peer-deps`) |
68
-
69
- ## Installation
70
-
71
- ```bash
72
- git clone <repo-url>
73
- cd OpenCodeRAG
74
- npm install --legacy-peer-deps
75
- ```
76
-
77
- ### Dependencies
78
-
79
- - **Node.js v22+** for native ESM and fetch support
80
- - **apache-arrow** — peer dependency for LanceDB (auto-installed)
81
- - **tree-sitter-wasm** — ships pre-built WASM grammars for all supported languages
82
-
83
- ## Configuration
84
-
85
- Create `opencode-rag.json` in the project root (auto-detected) or pass via
86
- `--config`. The repository's own [`opencode-rag.json`](./opencode-rag.json) serves
87
- as a complete example with all available options.
88
-
89
- Config files support partial overrides missing keys fall back to defaults.
90
- Deep merging is applied per section.
91
-
92
- ### Embedding Providers
93
-
94
- | Provider | `baseUrl` example | Notes |
95
- | -------- | --------------------------------- | ---------------------------- |
96
- | ollama | `http://localhost:11434/api` | Default. No apiKey required. Proxy is disabled when `embedding.proxy.url` is empty. |
97
- | openai | `https://api.openai.com/v1` | Requires apiKey. |
98
-
99
- `embedding.timeoutMs` defaults to 30000 ms. Increase it if your local model has a slow cold start.
100
-
101
- OpenAI provider sends all texts in a single request. Ollama sends one request
102
- per request to `/api/embed`. Set `embedding.proxy.url` to use the standard
103
- proxy-aware HTTP path instead of the direct socket path.
104
-
105
- ## Usage
106
-
107
- This extension consists of two main interfaces:
108
- 1. **CLI** — for manual indexing and querying from the terminal
109
- 2. **OpenCode plugin** — for automatic retrieval and file suggestions within the chat interface
110
-
111
- ### CLI
112
-
113
- ```bash
114
- # Index the workspace incrementally
115
- npx tsx src/cli.ts index
116
-
117
- # Force full re-index (clears existing data first)
118
- npx tsx src/cli.ts index --force
119
-
120
- # Watch workspace and incrementally re-index on changes
121
- npx tsx src/cli.ts index --watch
122
-
123
- # Semantic search
124
- npx tsx src/cli.ts query "How is authentication handled?"
125
-
126
- # Limit results
127
- npx tsx src/cli.ts query "error handling" --top-k 5
128
-
129
- # Show indexing stats
130
- npx tsx src/cli.ts status
131
-
132
- # Example output:
133
- # Indexed chunks: 1247
134
- # Store path: /home/user/project/.opencode/rag_db
135
- # Embedding provider: ollama
136
- # Embedding model: nomic-embed-text
137
- # Manifest status: ok
138
- # Manifest entries: 42
139
- # Last indexed: 2026-05-28 10:45:02
140
- # Up-to-date files: 42
141
- # Pending files: 0
142
- # Watch mode: off
143
-
144
- # Clear all indexed data
145
- npx tsx src/cli.ts clear
146
-
147
- # Use custom config
148
- npx tsx src/cli.ts index --config ./my-config.json
149
- ```
150
-
151
- `index` is incremental by default. A sidecar manifest is stored at
152
- `<vectorStore.path>/manifest.json` and tracks file hashes, chunk counts, and the
153
- last successful index timestamp. If the manifest is missing or corrupt while the
154
- vector store already contains data, the next index pass clears and rebuilds the
155
- store to avoid duplicates.
156
-
157
- ### Watch workflow
158
-
159
- Start a watch session:
160
-
161
- ```bash
162
- npx tsx src/cli.ts index --watch
163
- ```
164
-
165
- The initial pass indexes the workspace, then watches for file changes. On each
166
- `add`, `change`, `unlink`, or `unlinkDir` event, the watch debounces (300 ms)
167
- and triggers a new incremental pass. If a pass is already running, the re-index
168
- queues one follow-up pass and runs it as soon as the current pass finishes.
169
-
170
- The watcher ignores excluded directories, the vector store path, and the
171
- manifest file itself. Press `Ctrl+C` to stop.
172
-
173
- ### OpenCode Plugin
174
-
175
- The plugin registers:
176
-
177
- 1. **`opencode-rag-context`** — a custom retrieval tool for chunk-level evidence
178
- 2. **`chat.message`** after each user message, automatically retrieves relevant indexed files and appends a compact suggestion list to the message text
179
-
180
- #### Chat Message File Suggestions
181
-
182
- After you send a message, the plugin:
183
- 1. Extracts the user's message text
184
- 2. Runs semantic retrieval against the indexed workspace
185
- 3. Groups results by file, sorts by best chunk score, and formats a compact file list:
186
- ```
187
- src/plugin.ts (typescript, lines 10-42)
188
- src/core/config.ts (typescript, lines 66-145)
189
- ```
190
- 4. Appends the list (max 10 files) to your message text
191
-
192
- Only file paths, language, and line ranges are shown — no scores or code snippets. This gives the agent lightweight hints about which files are relevant without inflating the context window.
193
-
194
- **Config:**
195
-
196
- | Option | Default | Description |
197
- | ------ | ------- | ----------- |
198
- | `openCode.overrideRead` | `false` | Set to `true` to restore the legacy RAG-backed `read` tool (deprecated) |
199
- | `openCode.maxContextChunks` | `5` | Maximum chunks per retrieval (affects `opencode-rag-context` tool output) |
200
- | `retrieval.topK` | `10` | Number of chunks fetched per query (controls chat.message file suggestion breadth) |
201
-
202
- Errors during retrieval are silently caught a failed search won't break the
203
- chat.
204
-
205
- #### Install from source
206
-
207
- After cloning and installing dependencies:
208
-
209
- ```bash
210
- # Option 1: Use the project-local auto-loaded plugin
211
- # The repo already includes .opencode/plugins/rag-plugin.ts
212
-
213
- # Option 2: Build and install via npm pack
214
- npm run build
215
- npm pack
216
- opencode plugin .\opencode-rag-0.1.0.tgz
217
-
218
- # Option 3: Install from npm (once published)
219
- opencode plugin opencode-rag
220
- ```
221
-
222
- The plugin auto-detects configuration from `opencode-rag.json` or
223
- `.opencode/rag.json` in the project root.
224
-
225
- If you use the project-local plugin file, OpenCode auto-loads it from
226
- `.opencode/plugins/` at startup and no `plugin` entry is required in
227
- `.opencode/opencode.json`.
228
-
229
- Restart OpenCode after changing plugin files or plugin configuration.
230
-
231
- ### Logging
232
-
233
- Logging is configured under the `logging` key:
234
-
235
- ```json
236
- {
237
- "logging": {
238
- "level": "info",
239
- "logFilePath": "./.opencode/opencode-rag.log"
240
- }
241
- }
242
- ```
243
-
244
- | Option | Default | Description |
245
- | ------------ | ------------------------------ | -------------------------------------------- |
246
- | `level` | `"info"` | Log level: `"debug"`, `"info"`, or `"error"` |
247
- | `logFilePath` | `"./.opencode/opencode-rag.log"` | Path to the log file (relative paths are resolved against the workspace directory) |
248
-
249
- The resolved log file path also falls back to the `LOG_FILE_PATH` environment variable when the config value is not set. Config takes precedence over the env var when both are provided.
250
-
251
- #### AGENTS.md hints for using the plugin
252
-
253
- Add a section like this to the target workspace's `AGENTS.md` so the agent
254
- knows how to use the plugin correctly:
255
-
256
- ```markdown
257
- ## OpenCodeRAG Plugin
258
-
259
- This workspace has OpenCodeRAG installed for semantic code retrieval.
260
-
261
- ### `opencode-rag-context` tool
262
- Before planning, editing, or answering, use this tool to retrieve relevant code
263
- chunks with file paths, line ranges, and surrounding implementation.
264
- - `query` (required) narrow, specific search, e.g. `"authentication middleware setup"`
265
- - `pathHints` (optional) — up to 10 path filters, e.g. `["src/auth/"]`
266
- - `languageHints` (optional) up to 10 language filters, e.g. `["typescript"]`
267
- - `topK` (optional) — result count (1-25, default 10)
268
-
269
- ### File suggestions
270
- After each user message, a `chat.message` hook appends up to 10 relevant file
271
- suggestions to the message. Look for lines like
272
- `src/file.ts (typescript, lines 10-42)` at the bottom of user input.
273
-
274
- ### Indexing
275
- - The plugin auto-indexes changed files in the background (debounced 5s)
276
- - If no results come back, the workspace may not be indexed yet —
277
- run `opencode-rag index` from the terminal
278
- - Tiny files (under 1 KB), excluded extensions, and excluded directories
279
- (`node_modules`, `.git`, `.opencode`, `dist`, etc.) are silently skipped
280
- ```
281
-
282
- The plugin registers itself in the system prompt via the
283
- `experimental.chat.system.transform` hook, so compliant agents will see a
284
- reminder about the `opencode-rag-context` tool in their system instructions.
285
-
286
- ## Data Model
287
-
288
- ```typescript
289
- interface Chunk {
290
- id: string;
291
- content: string;
292
- embedding?: number[];
293
- metadata: {
294
- filePath: string;
295
- startLine: number;
296
- endLine: number;
297
- language: string;
298
- };
299
- }
300
-
301
- interface SearchResult {
302
- chunk: Chunk;
303
- score: number; // 1 / (1 + L2_distance), range [0, 1]
304
- }
305
- ```
306
-
307
- ## Chunking
308
-
309
- | Language | Strategy | Captures |
310
- | ---------- | ------------------------------ | ----------------------------------------- |
311
- | TypeScript | AST (tree-sitter) | functions, methods, classes, interfaces |
312
- | Python | AST (tree-sitter) | functions, classes, decorated definitions |
313
- | Java | AST (tree-sitter) | methods, classes, interfaces, enums |
314
- | Go | AST (tree-sitter) | functions, methods, type declarations |
315
- | C | AST (tree-sitter) | functions, structs, enums, unions, typedefs |
316
- | C++ | AST (tree-sitter) | functions, classes, structs, enums, namespaces, templates |
317
- | C# | AST (tree-sitter) | classes, interfaces, structs, enums, methods, namespaces, records |
318
- | JavaScript | AST (tree-sitter) | functions, classes, arrow functions, exports |
319
- | JSON | AST (tree-sitter) | key-value pairs |
320
- | XML | AST (tree-sitter) | elements (1 chunk per root element) |
321
- | HTML | AST (tree-sitter) | `<script>` / `<style>` blocks |
322
- | CSS | AST (tree-sitter) | rule sets, at-rules, media, keyframes |
323
- | Razor | Regex (brace matching) | `@code` / `@functions` blocks, template regions |
324
- | Markdown | Regex heading split | h1/h2 sections + trailing content |
325
- | Solution | Regex (section boundary) | project entries and global sections |
326
- | Rust | AST (tree-sitter) | functions, structs, enums, traits, impl blocks, modules, types |
327
- | Ruby | AST (tree-sitter) | methods, classes, modules, singleton methods |
328
- | Kotlin | AST (tree-sitter) | functions, classes, interfaces, objects, properties |
329
- | Swift | AST (tree-sitter) | functions, classes, structs, enums, protocols, extensions, variables |
330
- | LaTeX | Regex section split | chapter/section/subsection/subsubsection boundaries |
331
- | PDF | Paragraph-based (text extraction) | groups small paragraphs, splits oversized |
332
- | (other) | Line-based (100 lines/chunk) | raw text blocks |
333
-
334
- Custom chunkers can be added without modifying the project source code. Two
335
- registration paths are supported:
336
-
337
- ### Config file
338
-
339
- Add a `chunkers` array to `opencode-rag.json`:
340
-
341
- ```json
342
- {
343
- "chunkers": [
344
- { "module": "./path/to/rust-chunker.js", "extensions": [".rs"] }
345
- ]
346
- }
347
- ```
348
-
349
- The module path is resolved relative to the config file. The loaded module must
350
- export (as default or named) an object implementing the `Chunker` interface:
351
-
352
- ```typescript
353
- interface Chunker {
354
- readonly language: string;
355
- readonly fileExtensions?: string[];
356
- chunk(filePath: string, content: string): Promise<Chunk[]>;
357
- }
358
- ```
359
-
360
- ### Programmatic
361
-
362
- ```typescript
363
- import { registerChunker } from "opencode-rag/library";
364
- registerChunker(myChunker, [".rs"]);
365
- ```
366
-
367
- The optional second argument overrides the chunker's `fileExtensions`. If a
368
- built-in chunker already covers the requested extension, the new registration is
369
- skipped and a warning is emitted.
370
-
371
- ## Vector Store
372
-
373
- LanceDB stores chunks in a `chunks` table with columns: `id`, `content`,
374
- `embedding` (vector), `filePath`, `startLine`, `endLine`, `language`.
375
-
376
- - **Disk mode**: files in `vectorStore.path` (default `.opencode/rag_db`)
377
- - **Memory mode**: `memory://` URI — for tests only, data lost on close
378
- - **Manifest sidecar**: `manifest.json` in the store directory tracks indexed
379
- files for incremental updates
380
- - Schema is auto-inferred from a seed row on first table creation
381
- - L2 distance search, score = `1 / (1 + distance)`
382
- - Stored file paths are normalized to absolute forward-slash paths
383
-
384
- ## Development
385
-
386
- ```bash
387
- # TypeScript typecheck
388
- npm run typecheck
389
-
390
- # Run all tests
391
- npm test
392
-
393
- # Run specific test file
394
- node --import tsx --test src/__tests__/chunker/fallback.test.ts
395
- ```
396
-
397
- Project structure:
398
- ```
399
- src/
400
- core/ — interfaces.ts, config.ts
401
- chunker/ — grammar.ts, base.ts, language chunkers, fallback.ts, factory.ts, uuid.ts
402
- embedder/ — ollama.ts, openai.ts, factory.ts
403
- vectorstore/ — lancedb.ts
404
- retriever/ — retriever.ts
405
- types/ — opencode-plugin.d.ts
406
- indexer.ts — incremental indexing + watch scheduling
407
- watcher.ts — background indexer (chokidar + debounced scheduler + periodic timer)
408
- cli.ts, plugin.ts, plugin-entry.ts, index.ts
409
- __tests__/ — mirrors the module structure
410
- ```
411
-
412
- Test framework is Node's built-in runner (`node:test`) with `tsx` for TypeScript
413
- imports. No test library dependencies.
414
-
415
- ## Limitations
416
-
417
- - Embedding model dimension is auto-probed at startup; falls back to 384 if probing fails.
418
- - 21 built-in chunkers (AST for 16, regex for 4, PDF text for 1) + configurable fallback
419
-
420
- ## Privacy
421
-
422
- All processing is local. Embeddings are generated via local Ollama by default.
423
- No data leaves the machine unless configured to use a remote embedding API.
1
+ # OpenCodeRAG
2
+
3
+ Local-first RAG plugin for OpenCode — semantic code search powered by
4
+ embeddings and vector similarity.
5
+
6
+ **Published on npm as [`opencode-rag-plugin`](https://www.npmjs.com/package/opencode-rag-plugin).**
7
+
8
+ ## Features
9
+
10
+ - **AST-aware chunking** — splits code into functions, classes, methods using
11
+ tree-sitter for 16 languages, plus regex-based chunking for 4 markup/config/doc
12
+ formats (Markdown, Razor, .sln, LaTeX). Falls back to line-based chunking for
13
+ unrecognized formats.
14
+ - **Incremental indexing** — manifest-backed indexing skips unchanged files,
15
+ removes deleted entries, and updates only changed files.
16
+ - **Watch mode** `index --watch` re-indexes on file changes with debounced,
17
+ serialized passes.
18
+ - **Pluggable chunkers** — add custom language chunkers via config or programmatic API.
19
+ - **Configurable embeddings** — Ollama (default) or OpenAI-compatible providers.
20
+ Batch embedding with configurable batch size.
21
+ - **Local vector store** — LanceDB with L2 distance scoring, memory mode for
22
+ testing.
23
+ - **CLI** — index, query, clear, status commands.
24
+ - **OpenCode plugin** — exposes a chunk retrieval tool and suggests relevant files after each user message via the `chat.message` hook.
25
+
26
+ ## Architecture
27
+
28
+ ```
29
+ Workspace Files
30
+
31
+
32
+ ┌──────────────┐
33
+ │ Chunker │ AST-based (tree-sitter) or line-based fallback
34
+ └──────┬───────┘
35
+ │ chunks
36
+
37
+ ┌──────────────┐
38
+ │ Embedder │ Ollama / OpenAI-compatible API
39
+ └──────┬───────┘
40
+ │ vectors
41
+
42
+ ┌──────────────┐
43
+ │ VectorStore │ LanceDB (local files or memory:// for tests)
44
+ └──────┬───────┘
45
+ │ + manifest.json
46
+
47
+ ┌──────────────┐
48
+ │ Indexer/Retr.│ incremental index or query/search
49
+ └──────┬───────┘
50
+ │ results
51
+
52
+ LLM Context
53
+ ```
54
+
55
+ ## Tech Stack
56
+
57
+ | Layer | Technology |
58
+ | ----------- | --------------------------------------------------- |
59
+ | Runtime | Node.js v22 + tsx (ESM) |
60
+ | Language | TypeScript 5.8 |
61
+ | Chunking | web-tree-sitter (WASM) + tree-sitter-wasm grammars |
62
+ | Embeddings | Ollama / OpenAI-compatible (native fetch) |
63
+ | Vector DB | LanceDB (`@lancedb/lancedb`) |
64
+ | CLI | commander |
65
+ | Tests | Node built-in test runner (`node --test`) |
66
+ | Package mgr | npm (with `--legacy-peer-deps`) |
67
+
68
+ ## Installation
69
+
70
+ ### Global installation (recommended)
71
+
72
+ Clone the repository and run the install script:
73
+
74
+ **Windows:**
75
+ ```powershell
76
+ .\install.ps1
77
+ ```
78
+
79
+ **Linux/macOS:**
80
+ ```bash
81
+ ./install.sh
82
+ ```
83
+
84
+ This will:
85
+ 1. Build the plugin from source (`npm run build`)
86
+ 2. Install it into OpenCode's runtime (`~/.opencode/node_modules/`)
87
+ 3. Register it in the global OpenCode config (`~/.config/opencode/opencode.jsonc`)
88
+
89
+ After installation, restart OpenCode and the plugin is ready.
90
+
91
+ ### Per-workspace setup
92
+
93
+ ```bash
94
+ cd your-workspace
95
+ opencode-rag init
96
+ ```
97
+
98
+ `opencode-rag init` bootstraps the current workspace by creating:
99
+
100
+ - `opencode-rag.json`
101
+ - `.opencode/.gitignore`
102
+ - `.opencode/opencode.json`
103
+ - `.opencode/package.json`
104
+ - `.opencode/plugins/rag-plugin.js` (workspace-local plugin fallback)
105
+ - `.opencode/node_modules/` with the workspace-local plugin dependencies
106
+
107
+ Add `--skip-install` if you only want the files without installing dependencies.
108
+
109
+ ### Dependencies
110
+
111
+ ### Dependencies
112
+
113
+ - **Node.js v22+** for native ESM and fetch support
114
+ - **apache-arrow** peer dependency for LanceDB (auto-installed)
115
+ - **tree-sitter-wasm** ships pre-built WASM grammars for all supported languages
116
+
117
+ ## Configuration
118
+
119
+ Create `opencode-rag.json` in the project root (auto-detected) or pass via
120
+ `--config`. The repository's own [`opencode-rag.json`](./opencode-rag.json) serves
121
+ as a complete example with all available options.
122
+
123
+ Config files support partial overrides — missing keys fall back to defaults.
124
+ Deep merging is applied per section.
125
+
126
+ ### Embedding Providers
127
+
128
+ | Provider | `baseUrl` example | Notes |
129
+ | -------- | --------------------------------- | ---------------------------- |
130
+ | ollama | `http://localhost:11434/api` | Default. No apiKey required. Proxy is disabled when `embedding.proxy.url` is empty. |
131
+ | openai | `https://api.openai.com/v1` | Requires apiKey. |
132
+
133
+ `embedding.timeoutMs` defaults to 30000 ms. Increase it if your local model has a slow cold start.
134
+
135
+ OpenAI provider sends all texts in a single request. Ollama sends one request
136
+ per request to `/api/embed`. Set `embedding.proxy.url` to use the standard
137
+ proxy-aware HTTP path instead of the direct socket path.
138
+
139
+ ## Usage
140
+
141
+ This extension consists of two main interfaces:
142
+ 1. **CLI** — for manual indexing and querying from the terminal
143
+ 2. **OpenCode plugin** — for automatic retrieval and file suggestions within the chat interface
144
+
145
+ ### CLI
146
+
147
+ ```bash
148
+ # Index the workspace incrementally
149
+ opencode-rag index
150
+
151
+ # Force full re-index (clears existing data first)
152
+ opencode-rag index --force
153
+
154
+ # Watch workspace and incrementally re-index on changes
155
+ opencode-rag index --watch
156
+
157
+ # Semantic search
158
+ opencode-rag query "How is authentication handled?"
159
+
160
+ # Limit results
161
+ opencode-rag query "error handling" --top-k 5
162
+
163
+ # Show indexing stats
164
+ opencode-rag status
165
+
166
+ # Example output:
167
+ # Indexed chunks: 1247
168
+ # Store path: /home/user/project/.opencode/rag_db
169
+ # Embedding provider: ollama
170
+ # Embedding model: nomic-embed-text
171
+ # Manifest status: ok
172
+ # Manifest entries: 42
173
+ # Last indexed: 2026-05-28 10:45:02
174
+ # Up-to-date files: 42
175
+ # Pending files: 0
176
+ # Watch mode: off
177
+
178
+ # Clear all indexed data
179
+ opencode-rag clear
180
+
181
+ # Use custom config
182
+ opencode-rag index --config ./my-config.json
183
+ ```
184
+
185
+ `index` is incremental by default. A sidecar manifest is stored at
186
+ `<vectorStore.path>/manifest.json` and tracks file hashes, chunk counts, and the
187
+ last successful index timestamp. If the manifest is missing or corrupt while the
188
+ vector store already contains data, the next index pass clears and rebuilds the
189
+ store to avoid duplicates.
190
+
191
+ ### Watch workflow
192
+
193
+ Start a watch session:
194
+
195
+ ```bash
196
+ opencode-rag index --watch
197
+ ```
198
+
199
+ The initial pass indexes the workspace, then watches for file changes. On each
200
+ `add`, `change`, `unlink`, or `unlinkDir` event, the watch debounces (300 ms)
201
+ and triggers a new incremental pass. If a pass is already running, the re-index
202
+ queues one follow-up pass and runs it as soon as the current pass finishes.
203
+
204
+ The watcher ignores excluded directories, the vector store path, and the
205
+ manifest file itself. Press `Ctrl+C` to stop.
206
+
207
+ ### OpenCode Plugin
208
+
209
+ The plugin registers:
210
+
211
+ 1. **`opencode-rag-context`** a custom retrieval tool for chunk-level evidence
212
+ 2. **`chat.message`** — after each user message, automatically retrieves relevant indexed files and appends a compact suggestion list to the message text
213
+
214
+ #### Chat Message File Suggestions
215
+
216
+ After you send a message, the plugin:
217
+ 1. Extracts the user's message text
218
+ 2. Runs semantic retrieval against the indexed workspace
219
+ 3. Groups results by file, sorts by best chunk score, and formats a compact file list:
220
+ ```
221
+ src/plugin.ts (typescript, lines 10-42)
222
+ src/core/config.ts (typescript, lines 66-145)
223
+ ```
224
+ 4. Appends the list (max 10 files) to your message text
225
+
226
+ Only file paths, language, and line ranges are shown — no scores or code snippets. This gives the agent lightweight hints about which files are relevant without inflating the context window.
227
+
228
+ **Config:**
229
+
230
+ | Option | Default | Description |
231
+ | ------ | ------- | ----------- |
232
+ | `openCode.overrideRead` | `false` | Set to `true` to restore the legacy RAG-backed `read` tool (deprecated) |
233
+ | `openCode.maxContextChunks` | `5` | Maximum chunks per retrieval (affects `opencode-rag-context` tool output) |
234
+ | `retrieval.topK` | `10` | Number of chunks fetched per query (controls chat.message file suggestion breadth) |
235
+
236
+ Errors during retrieval are silently caught — a failed search won't break the
237
+ chat.
238
+
239
+ #### Install from source
240
+
241
+ After cloning and installing dependencies:
242
+
243
+ ```bash
244
+ # Option 1: Install globally via the install script (recommended)
245
+ ./install.sh # Linux/macOS
246
+ .\install.ps1 # Windows
247
+
248
+ # Option 2: Build and pack manually, then register globally
249
+ npm run build
250
+ npm pack
251
+ npm install --prefix ~/.opencode/ opencode-rag-plugin-1.2.0.tgz
252
+ npm install --prefix ~/.config/opencode/ opencode-rag-plugin-1.2.0.tgz
253
+ # Add "opencode-rag-plugin" to the plugin array in ~/.config/opencode/opencode.jsonc
254
+
255
+ # Option 3: Bootstrap workspace only (uses npm version)
256
+ opencode-rag init
257
+ ```
258
+
259
+ The plugin auto-detects configuration from `opencode-rag.json` or
260
+ `.opencode/opencode-rag.json` or `.opencode/rag.json` in the project root.
261
+
262
+ `opencode-rag init` creates a workspace-local plugin file at `.opencode/plugins/rag-plugin.js`
263
+ that re-exports from `node_modules/`, serving as a fallback when global loading fails.
264
+ No `plugin` entry is required in `.opencode/opencode.json`.
265
+
266
+ Restart OpenCode after changing plugin files or plugin configuration.
267
+
268
+ ### Logging
269
+
270
+ Logging is configured under the `logging` key:
271
+
272
+ ```json
273
+ {
274
+ "logging": {
275
+ "level": "info",
276
+ "logFilePath": "./.opencode/opencode-rag.log"
277
+ }
278
+ }
279
+ ```
280
+
281
+ | Option | Default | Description |
282
+ | ------------ | ------------------------------ | -------------------------------------------- |
283
+ | `level` | `"info"` | Log level: `"debug"`, `"info"`, or `"error"` |
284
+ | `logFilePath` | `"./.opencode/opencode-rag.log"` | Path to the log file (relative paths are resolved against the workspace directory) |
285
+
286
+ The resolved log file path also falls back to the `LOG_FILE_PATH` environment variable when the config value is not set. Config takes precedence over the env var when both are provided.
287
+
288
+ #### AGENTS.md hints for using the plugin
289
+
290
+ Add a section like this to the target workspace's `AGENTS.md` so the agent
291
+ knows how to use the plugin correctly:
292
+
293
+ ```markdown
294
+ ## OpenCodeRAG Plugin
295
+
296
+ This workspace has OpenCodeRAG installed for semantic code retrieval.
297
+
298
+ ### `opencode-rag-context` tool
299
+ Before planning, editing, or answering, use this tool to retrieve relevant code
300
+ chunks with file paths, line ranges, and surrounding implementation.
301
+ - `query` (required) — narrow, specific search, e.g. `"authentication middleware setup"`
302
+ - `pathHints` (optional) — up to 10 path filters, e.g. `["src/auth/"]`
303
+ - `languageHints` (optional) up to 10 language filters, e.g. `["typescript"]`
304
+ - `topK` (optional) — result count (1-25, default 10)
305
+
306
+ ### File suggestions
307
+ After each user message, a `chat.message` hook appends up to 10 relevant file
308
+ suggestions to the message. Look for lines like
309
+ `src/file.ts (typescript, lines 10-42)` at the bottom of user input.
310
+
311
+ ### Indexing
312
+ - The plugin auto-indexes changed files in the background (debounced 5s)
313
+ - If no results come back, the workspace may not be indexed yet —
314
+ run `opencode-rag index` from the terminal (or `npx opencode-rag-plugin`)
315
+ - Tiny files (under 1 KB), excluded extensions, and excluded directories
316
+ (`node_modules`, `.git`, `.opencode`, `dist`, etc.) are silently skipped
317
+ ```
318
+
319
+ The plugin registers itself in the system prompt via the
320
+ `experimental.chat.system.transform` hook, so compliant agents will see a
321
+ reminder about the `opencode-rag-context` tool in their system instructions.
322
+
323
+ ## Data Model
324
+
325
+ ```typescript
326
+ interface Chunk {
327
+ id: string;
328
+ content: string;
329
+ embedding?: number[];
330
+ metadata: {
331
+ filePath: string;
332
+ startLine: number;
333
+ endLine: number;
334
+ language: string;
335
+ };
336
+ }
337
+
338
+ interface SearchResult {
339
+ chunk: Chunk;
340
+ score: number; // 1 / (1 + L2_distance), range [0, 1]
341
+ }
342
+ ```
343
+
344
+ ## Chunking
345
+
346
+ | Language | Strategy | Captures |
347
+ | ---------- | ------------------------------ | ----------------------------------------- |
348
+ | TypeScript | AST (tree-sitter) | functions, methods, classes, interfaces |
349
+ | Python | AST (tree-sitter) | functions, classes, decorated definitions |
350
+ | Java | AST (tree-sitter) | methods, classes, interfaces, enums |
351
+ | Go | AST (tree-sitter) | functions, methods, type declarations |
352
+ | C | AST (tree-sitter) | functions, structs, enums, unions, typedefs |
353
+ | C++ | AST (tree-sitter) | functions, classes, structs, enums, namespaces, templates |
354
+ | C# | AST (tree-sitter) | classes, interfaces, structs, enums, methods, namespaces, records |
355
+ | JavaScript | AST (tree-sitter) | functions, classes, arrow functions, exports |
356
+ | JSON | AST (tree-sitter) | key-value pairs |
357
+ | XML | AST (tree-sitter) | elements (1 chunk per root element) |
358
+ | HTML | AST (tree-sitter) | `<script>` / `<style>` blocks |
359
+ | CSS | AST (tree-sitter) | rule sets, at-rules, media, keyframes |
360
+ | Razor | Regex (brace matching) | `@code` / `@functions` blocks, template regions |
361
+ | Markdown | Regex heading split | h1/h2 sections + trailing content |
362
+ | Solution | Regex (section boundary) | project entries and global sections |
363
+ | Rust | AST (tree-sitter) | functions, structs, enums, traits, impl blocks, modules, types |
364
+ | Ruby | AST (tree-sitter) | methods, classes, modules, singleton methods |
365
+ | Kotlin | AST (tree-sitter) | functions, classes, interfaces, objects, properties |
366
+ | Swift | AST (tree-sitter) | functions, classes, structs, enums, protocols, extensions, variables |
367
+ | LaTeX | Regex section split | chapter/section/subsection/subsubsection boundaries |
368
+ | PDF | Paragraph-based (text extraction) | groups small paragraphs, splits oversized |
369
+ | Word (docx) | Paragraph-based (text extraction) | extracts raw text via mammoth, groups small paragraphs, splits oversized |
370
+ | Word (doc) | Paragraph-based (text extraction) | extracts raw text via word-extractor, groups small paragraphs, splits oversized |
371
+ | Excel (xls/xlsx) | Row-batch (text extraction) | extracts CSV per sheet via @e965/xlsx, splits by sheet then by row batches |
372
+ | (other) | Line-based (100 lines/chunk) | raw text blocks |
373
+
374
+ Custom chunkers can be added without modifying the project source code. Two
375
+ registration paths are supported:
376
+
377
+ ### Config file
378
+
379
+ Add a `chunkers` array to `opencode-rag.json`:
380
+
381
+ ```json
382
+ {
383
+ "chunkers": [
384
+ { "module": "./path/to/rust-chunker.js", "extensions": [".rs"] }
385
+ ]
386
+ }
387
+ ```
388
+
389
+ The module path is resolved relative to the config file. The loaded module must
390
+ export (as default or named) an object implementing the `Chunker` interface:
391
+
392
+ ```typescript
393
+ interface Chunker {
394
+ readonly language: string;
395
+ readonly fileExtensions?: string[];
396
+ chunk(filePath: string, content: string): Promise<Chunk[]>;
397
+ }
398
+ ```
399
+
400
+ ### Programmatic
401
+
402
+ ```typescript
403
+ import { registerChunker } from "opencode-rag-plugin/library";
404
+ registerChunker(myChunker, [".rs"]);
405
+ ```
406
+
407
+ The optional second argument overrides the chunker's `fileExtensions`. If a
408
+ built-in chunker already covers the requested extension, the new registration is
409
+ skipped and a warning is emitted.
410
+
411
+ ## Vector Store
412
+
413
+ LanceDB stores chunks in a `chunks` table with columns: `id`, `content`,
414
+ `embedding` (vector), `filePath`, `startLine`, `endLine`, `language`.
415
+
416
+ - **Disk mode**: files in `vectorStore.path` (default `.opencode/rag_db`)
417
+ - **Memory mode**: `memory://` URI for tests only, data lost on close
418
+ - **Manifest sidecar**: `manifest.json` in the store directory tracks indexed
419
+ files for incremental updates
420
+ - Schema is auto-inferred from a seed row on first table creation
421
+ - L2 distance search, score = `1 / (1 + distance)`
422
+ - Stored file paths are normalized to absolute forward-slash paths
423
+
424
+ ## Development
425
+
426
+ ```bash
427
+ # TypeScript typecheck
428
+ npm run typecheck
429
+
430
+ # Run all tests
431
+ npm test
432
+
433
+ # Run specific test file
434
+ node --import tsx --test src/__tests__/chunker/fallback.test.ts
435
+ ```
436
+
437
+ Project structure:
438
+ ```
439
+ src/
440
+ core/ — interfaces.ts, config.ts
441
+ chunker/ — grammar.ts, base.ts, language chunkers, fallback.ts, factory.ts, uuid.ts
442
+ embedder/ — ollama.ts, openai.ts, factory.ts
443
+ vectorstore/ — lancedb.ts
444
+ retriever/ — retriever.ts
445
+ types/ — opencode-plugin.d.ts
446
+ indexer.ts — incremental indexing + watch scheduling
447
+ watcher.ts — background indexer (chokidar + debounced scheduler + periodic timer)
448
+ cli.ts, plugin.ts, plugin-entry.ts, index.ts
449
+ __tests__/ — mirrors the module structure
450
+ ```
451
+
452
+ Test framework is Node's built-in runner (`node:test`) with `tsx` for TypeScript
453
+ imports. No test library dependencies.
454
+
455
+ ## Limitations
456
+
457
+ - Embedding model dimension is auto-probed at startup; falls back to 384 if probing fails.
458
+ - 21 built-in chunkers (AST for 16, regex for 4, PDF text for 1) + configurable fallback
459
+
460
+ ## Privacy
461
+
462
+ All processing is local. Embeddings are generated via local Ollama by default.
463
+ No data leaves the machine unless configured to use a remote embedding API.