@javierkaiser/opencode-indexing 0.1.0 → 0.1.1
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.
- package/CHANGELOG.md +10 -1
- package/README.md +54 -5
- package/install.sh +0 -0
- package/package.json +1 -1
- package/src/config.ts +30 -4
- package/src/lancedb-store.ts +2 -3
- package/src/lancedb-support.ts +83 -0
- package/src/status.ts +15 -2
- package/src/types.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.1] - 2026-10-14
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- npm releases are now automated: pushing a `v*` tag triggers a GitHub Actions
|
|
15
|
+
workflow that publishes through npm trusted publishing (OIDC), with no
|
|
16
|
+
long-lived token.
|
|
17
|
+
|
|
10
18
|
## [0.1.0] - 2026-10-09
|
|
11
19
|
|
|
12
20
|
First public release.
|
|
@@ -29,5 +37,6 @@ First public release.
|
|
|
29
37
|
- Cross-platform installer (`install.mjs`, plus `install.sh` / `install.ps1`
|
|
30
38
|
wrappers).
|
|
31
39
|
|
|
32
|
-
[Unreleased]: https://github.com/Javierkaiser/opencode-indexing/compare/v0.1.
|
|
40
|
+
[Unreleased]: https://github.com/Javierkaiser/opencode-indexing/compare/v0.1.1...HEAD
|
|
41
|
+
[0.1.1]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.1.1
|
|
33
42
|
[0.1.0]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
OpenCode plugin that adds **semantic code search** by reusing the code index created by [Kilo Code](https://kilo.ai) and maintaining its **own independent index** (
|
|
7
|
+
OpenCode plugin that adds **semantic code search** by reusing the code index created by [Kilo Code](https://kilo.ai) and maintaining its **own independent index** (embedded LanceDB by default, or Qdrant if you prefer), so search keeps working even when Kilo isn't installed, is disabled, or its index is stale.
|
|
8
8
|
|
|
9
9
|
## TL;DR
|
|
10
10
|
|
|
@@ -69,7 +69,7 @@ If no package manager is available the copy still happens and the plugin runs wi
|
|
|
69
69
|
|
|
70
70
|
```
|
|
71
71
|
OpenCode ──► indexing_search ──┬──► Kilo's index (read-only) Qdrant ws-* · Kilo LanceDB
|
|
72
|
-
└──► own index (read/write)
|
|
72
|
+
└──► own index (read/write) own LanceDB (default) · Qdrant oc-*
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
- **Read-only reuse**: the plugin computes the same collection name Kilo uses (`ws-` + sha256 of the workspace path, first 16 hex chars), validates the embedding profile stored in the metadata point, and queries with an embedding from the *same* model (e.g. Mistral `codestral-embed-2505`, 1536d).
|
|
@@ -181,7 +181,9 @@ Configurable from all three:
|
|
|
181
181
|
- **Auto-refresh on search** toggle.
|
|
182
182
|
- Build / refresh / import actions and a status summary.
|
|
183
183
|
|
|
184
|
-
Settings are stored in `~/.config/opencode/indexing.json` (plugin options → `indexing.json` → environment →
|
|
184
|
+
Settings are stored in `~/.config/opencode/indexing.json` (plugin options → `indexing.json` → environment → defaults). Plugin options in `opencode.json` still work and take precedence. API keys are never written to `indexing.json` in plain text; set them through the environment or plugin options.
|
|
185
|
+
|
|
186
|
+
`~/.config/kilo/kilo.jsonc` is still read, and still supplies the embedding provider, model, dimensions and API keys — and it still says where *Kilo* keeps its index. It no longer decides where this plugin's own index is written; that stopped being coupled so that the default could actually take effect for people running Kilo. See [Where each index lives](#where-each-index-lives).
|
|
185
187
|
|
|
186
188
|
## Configuration (optional, `opencode.json`)
|
|
187
189
|
|
|
@@ -200,7 +202,7 @@ Settings are stored in `~/.config/opencode/indexing.json` (plugin options → `i
|
|
|
200
202
|
|
|
201
203
|
| Variable | Meaning |
|
|
202
204
|
| --- | --- |
|
|
203
|
-
| `KILO_INDEX_VECTOR_STORE` | `
|
|
205
|
+
| `KILO_INDEX_VECTOR_STORE` | `lancedb` (default) or `qdrant` for the own index |
|
|
204
206
|
| `KILO_INDEX_QDRANT_URL` / `KILO_INDEX_QDRANT_API_KEY` | Qdrant connection |
|
|
205
207
|
| `KILO_INDEX_LANCEDB_DIR` | Base directory for the own LanceDB stores |
|
|
206
208
|
| `KILO_INDEX_KILO_LANCEDB_DIR` | Override Kilo's LanceDB directory (import source) |
|
|
@@ -211,9 +213,56 @@ Word about data safety: Kilo's indexes (`ws-*` collections and its LanceDB direc
|
|
|
211
213
|
## Requirements
|
|
212
214
|
|
|
213
215
|
- OpenCode v2 (plugin API `@opencode/plugin@2.x`).
|
|
214
|
-
- For
|
|
216
|
+
- **For the own index: nothing to run.** The default store is embedded LanceDB
|
|
217
|
+
(`@lancedb/lancedb@0.26.2`), so there is no server to start, no port to free and
|
|
218
|
+
no container to keep alive.
|
|
219
|
+
- **A Qdrant server is only needed if you ask for it**, either by setting
|
|
220
|
+
`vectorStore=qdrant` or because a Kilo index you want to import lives in Qdrant.
|
|
221
|
+
In that case the default URL is `http://localhost:6333`.
|
|
215
222
|
- Embedding credentials compatible with the collection you search (the profile is read from the metadata point).
|
|
216
223
|
|
|
224
|
+
### Where each index lives
|
|
225
|
+
|
|
226
|
+
The own index and Kilo's index are independent, and each one is chosen on its
|
|
227
|
+
own:
|
|
228
|
+
|
|
229
|
+
| Index | Default | Configured by |
|
|
230
|
+
| --- | --- | --- |
|
|
231
|
+
| Own index | embedded LanceDB, under `~/.local/state/opencode-indexing/lancedb` | `vectorStore` |
|
|
232
|
+
| Kilo index (read-only) | whatever `kilo.jsonc` says | Kilo itself |
|
|
233
|
+
|
|
234
|
+
Kilo's `vectorStore` used to decide where this plugin's own index was written,
|
|
235
|
+
because both were read from the same setting chain. That is no longer the case:
|
|
236
|
+
Kilo decides where an import **reads from**, and your own index decides where it
|
|
237
|
+
**writes to**. If Kilo is on Qdrant and you have not set `vectorStore` yourself,
|
|
238
|
+
the own index still goes to LanceDB and `indexing_status` says so.
|
|
239
|
+
|
|
240
|
+
### Platform support for the embedded store
|
|
241
|
+
|
|
242
|
+
`@lancedb/lancedb` is a JavaScript wrapper around a native library, with one
|
|
243
|
+
prebuilt binary per platform and no WebAssembly fallback. The platforms it ships
|
|
244
|
+
a binary for are:
|
|
245
|
+
|
|
246
|
+
| Platform | `platform-arch` | Own index on LanceDB |
|
|
247
|
+
| --- | --- | --- |
|
|
248
|
+
| Windows x64 / arm64 | `win32-x64`, `win32-arm64` | yes |
|
|
249
|
+
| Linux x64 / arm64 (glibc and musl) | `linux-x64`, `linux-arm64` | yes |
|
|
250
|
+
| macOS Apple Silicon | `darwin-arm64` | yes |
|
|
251
|
+
| **macOS Intel** | `darwin-x64` | **no binary is published** |
|
|
252
|
+
|
|
253
|
+
On macOS Intel the default store cannot load, and installing it again will not
|
|
254
|
+
help. The plugin says so explicitly and asks you to pick Qdrant:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
LanceDB is the default store for the own index but has no native binary for
|
|
258
|
+
darwin-x64. It cannot run on this platform and reinstalling will not change
|
|
259
|
+
that. Set vectorStore=qdrant explicitly in
|
|
260
|
+
~/.config/opencode/indexing.json to use a Qdrant server instead.
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Set `vectorStore=qdrant` in `~/.config/opencode/indexing.json` (or run
|
|
264
|
+
`/indexing-config set vectorstore=qdrant`) to use a Qdrant server instead.
|
|
265
|
+
|
|
217
266
|
## Semantic search vs. code graphs (roadmap)
|
|
218
267
|
|
|
219
268
|
`opencode-indexing` implements **semantic (vector) search**. It is worth being
|
package/install.sh
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@javierkaiser/opencode-indexing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "OpenCode plugin: semantic code search that reuses Kilo Code indexes (Qdrant/LanceDB) and maintains its own independent index",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Carlos Javier Lust <javierkaiser@gmail.com>",
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as os from "node:os"
|
|
2
2
|
|
|
3
3
|
import type { EmbedderProvider, IndexingSettings, ProviderCredentials } from "./types.ts"
|
|
4
|
-
import { defaultLanceDbDirectory, readSettingsFile, type SettingsFile } from "./settings.ts"
|
|
4
|
+
import { defaultLanceDbDirectory, readSettingsFile, settingsFilePath, type SettingsFile } from "./settings.ts"
|
|
5
|
+
import { lanceDbSupport } from "./lancedb-support.ts"
|
|
5
6
|
import { stripJsonComments } from "./jsonc.ts"
|
|
6
7
|
import {
|
|
7
8
|
DEFAULT_EMBEDDING_BATCH_SIZE,
|
|
@@ -184,9 +185,33 @@ export function resolveSettings(options: PluginOptionsInput): IndexingSettings {
|
|
|
184
185
|
nonEmpty(kilo?.openrouter?.apiKey),
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
// Kilo decides where an import reads FROM; it does not decide where the own
|
|
189
|
+
// index writes TO. Those were coupled through this one chain, and that is
|
|
190
|
+
// what made "LanceDB by default" unreachable for exactly the people most
|
|
191
|
+
// likely to have Kilo installed: kilo.jsonc's vectorStore sat above the
|
|
192
|
+
// fallback, so a Kilo-on-Qdrant setup silently kept pinning the own index to
|
|
193
|
+
// Qdrant. The coupling is reported below rather than dropped in silence.
|
|
194
|
+
const kiloVectorStore = pickVectorStore(kilo?.vectorStore)
|
|
195
|
+
const ownVectorStore = pickVectorStore(
|
|
196
|
+
options.vectorStore,
|
|
197
|
+
file?.vectorStore,
|
|
198
|
+
process.env.KILO_INDEX_VECTOR_STORE,
|
|
199
|
+
)
|
|
200
|
+
if (kiloVectorStore && !ownVectorStore && kiloVectorStore !== "lancedb") {
|
|
201
|
+
warnings.push(
|
|
202
|
+
`Kilo indexes with ${kiloVectorStore}; your own index no longer inherits that. ` +
|
|
203
|
+
`Set vectorStore explicitly in ${settingsFilePath(options.homeDir)} to change it.`,
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
const vectorStore = ownVectorStore ?? "lancedb"
|
|
207
|
+
|
|
208
|
+
if (vectorStore === "lancedb" && !lanceDbSupport().supported) {
|
|
209
|
+
const { id } = lanceDbSupport()
|
|
210
|
+
warnings.push(
|
|
211
|
+
`LanceDB is the default store but has no native binary for ${id}. ` +
|
|
212
|
+
`Indexing will fail until you set vectorStore=qdrant in ${settingsFilePath(options.homeDir)}.`,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
190
215
|
|
|
191
216
|
const qdrantUrl =
|
|
192
217
|
nonEmpty(options.qdrantUrl) ??
|
|
@@ -264,6 +289,7 @@ export function resolveSettings(options: PluginOptionsInput): IndexingSettings {
|
|
|
264
289
|
modelId,
|
|
265
290
|
dimension: dimension ?? 0,
|
|
266
291
|
vectorStore,
|
|
292
|
+
kiloVectorStore: kiloVectorStore ?? null,
|
|
267
293
|
qdrantUrl,
|
|
268
294
|
qdrantApiKey,
|
|
269
295
|
lancedbDirectory,
|
package/src/lancedb-store.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from "node:path"
|
|
|
3
3
|
|
|
4
4
|
import { workspaceHash } from "./registry.ts"
|
|
5
5
|
import { defaultLanceDbDirectory } from "./settings.ts"
|
|
6
|
+
import { lanceDbUnavailableMessage } from "./lancedb-support.ts"
|
|
6
7
|
import type { VectorStoreAdapter, VectorStoreInfo, StoreSearchOptions, ExportBatch } from "./vector-store.ts"
|
|
7
8
|
import type { EmbeddingProfile, QdrantPoint, QueryHit } from "./types.ts"
|
|
8
9
|
import { describeError } from "./errors.ts"
|
|
@@ -71,9 +72,7 @@ let lancedbPromise: Promise<any> | undefined
|
|
|
71
72
|
function loadLanceDb(): Promise<any> {
|
|
72
73
|
lancedbPromise ??= import("@lancedb/lancedb").catch((error) => {
|
|
73
74
|
lancedbPromise = undefined
|
|
74
|
-
throw new Error(
|
|
75
|
-
`LanceDB no está disponible (instala @lancedb/lancedb@0.26.2 o usa vectorStore=qdrant): ${describeError(error)}`,
|
|
76
|
-
)
|
|
75
|
+
throw new Error(lanceDbUnavailableMessage(error), { cause: error })
|
|
77
76
|
})
|
|
78
77
|
return lancedbPromise
|
|
79
78
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { settingsFilePath } from "./settings.ts"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which platforms the LanceDB binding can actually run on.
|
|
5
|
+
*
|
|
6
|
+
* `@lancedb/lancedb` is a JavaScript wrapper that `require()`s one of seven
|
|
7
|
+
* per-platform native binaries, each published as its own optional dependency.
|
|
8
|
+
* There is no WASM build, so a platform outside that list cannot use LanceDB at
|
|
9
|
+
* all, and installing it again will not change that.
|
|
10
|
+
*
|
|
11
|
+
* macOS on Intel is the one that bites: `lancedb-darwin-x64` is not published,
|
|
12
|
+
* so LanceDB works on Apple Silicon only. The list is here so the plugin can say
|
|
13
|
+
* so plainly instead of surfacing a module-resolution stack trace; the import
|
|
14
|
+
* itself stays the authority on whether the binary loaded.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const LANCEDB_VERSION = "0.26.2"
|
|
18
|
+
|
|
19
|
+
/** `platform-arch`, the way a native package would be named. */
|
|
20
|
+
export function platformId(platform: string = process.platform, arch: string = process.arch): string {
|
|
21
|
+
return `${platform}-${arch}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SUPPORTED = new Set([
|
|
25
|
+
"win32-x64",
|
|
26
|
+
"win32-arm64",
|
|
27
|
+
"darwin-arm64",
|
|
28
|
+
"linux-x64",
|
|
29
|
+
"linux-arm64",
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
export interface LanceDbSupport {
|
|
33
|
+
/** `platform-arch` of the current process. */
|
|
34
|
+
id: string
|
|
35
|
+
/** Whether a native binary is published for it. */
|
|
36
|
+
supported: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function lanceDbSupport(
|
|
40
|
+
platform: string = process.platform,
|
|
41
|
+
arch: string = process.arch,
|
|
42
|
+
): LanceDbSupport {
|
|
43
|
+
const id = platformId(platform, arch)
|
|
44
|
+
return { id, supported: SUPPORTED.has(id) }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Node appends the resolution chain to a MODULE_NOT_FOUND message, which buries
|
|
49
|
+
* the one line that says what went wrong under seven lines of internal files.
|
|
50
|
+
* The cause is still there; it is just what a reader acts on that is kept.
|
|
51
|
+
*/
|
|
52
|
+
function causeSummary(cause: unknown): string {
|
|
53
|
+
const message = cause instanceof Error ? cause.message : String(cause)
|
|
54
|
+
const atRequireStack = message.indexOf("Require stack:")
|
|
55
|
+
return (atRequireStack === -1 ? message : message.slice(0, atRequireStack)).trim()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The message shown when LanceDB cannot be loaded.
|
|
60
|
+
*
|
|
61
|
+
* The two cases need different advice, so they get different words: a platform
|
|
62
|
+
* with no binary cannot be fixed by installing anything, while a supported
|
|
63
|
+
* platform that failed to load usually means the optional dependency was
|
|
64
|
+
* skipped.
|
|
65
|
+
*/
|
|
66
|
+
export function lanceDbUnavailableMessage(
|
|
67
|
+
cause: unknown,
|
|
68
|
+
support: LanceDbSupport = lanceDbSupport(),
|
|
69
|
+
): string {
|
|
70
|
+
const { id, supported } = support
|
|
71
|
+
if (!supported) {
|
|
72
|
+
return (
|
|
73
|
+
`LanceDB is the default store for the own index but has no native binary for ${id}. ` +
|
|
74
|
+
`It cannot run on this platform and reinstalling will not change that. ` +
|
|
75
|
+
`Set vectorStore=qdrant explicitly in ${settingsFilePath()} to use a Qdrant server instead.`
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
return (
|
|
79
|
+
`LanceDB could not be loaded for ${id}. It ships as an optional dependency: ` +
|
|
80
|
+
`install @lancedb/lancedb@${LANCEDB_VERSION}, or set vectorStore=qdrant. ` +
|
|
81
|
+
`Cause: ${causeSummary(cause)}`
|
|
82
|
+
)
|
|
83
|
+
}
|
package/src/status.ts
CHANGED
|
@@ -56,13 +56,26 @@ const KILO_CACHE_PREFIX = "kiloCollection/"
|
|
|
56
56
|
|
|
57
57
|
export async function getStatus(deps: StatusDeps, options?: { checkFreshness?: boolean }): Promise<IndexStatus> {
|
|
58
58
|
const { qdrant, kv, root, settings } = deps
|
|
59
|
-
|
|
59
|
+
// Configuration warnings are produced once per resolution and were previously
|
|
60
|
+
// never shown, so a store that cannot work on this platform looked simply
|
|
61
|
+
// absent rather than misconfigured.
|
|
62
|
+
const warnings: string[] = [...settings.warnings]
|
|
60
63
|
let reachable = true
|
|
61
64
|
try {
|
|
62
65
|
await qdrant.listCollections()
|
|
63
66
|
} catch (error) {
|
|
64
67
|
reachable = false
|
|
65
|
-
|
|
68
|
+
// Only worth reporting when Qdrant is actually part of this setup. With the
|
|
69
|
+
// own index on LanceDB and no Qdrant-backed Kilo index, a dead Qdrant is
|
|
70
|
+
// not a problem the user has, and saying so would train them to ignore it.
|
|
71
|
+
const qdrantIsOurs = settings.vectorStore === "qdrant" || settings.kiloVectorStore === "qdrant"
|
|
72
|
+
if (qdrantIsOurs) {
|
|
73
|
+
warnings.push(
|
|
74
|
+
settings.vectorStore === "qdrant"
|
|
75
|
+
? `Qdrant unreachable at ${settings.qdrantUrl}: ${describeError(error)}`
|
|
76
|
+
: `Qdrant unreachable at ${settings.qdrantUrl}, so a Kilo index stored there cannot be imported: ${describeError(error)}`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
const ownStore = createOwnStore(settings, root)
|
package/src/types.ts
CHANGED
|
@@ -39,6 +39,15 @@ export interface ProviderCredentials {
|
|
|
39
39
|
export interface IndexingSettings extends EmbeddingProfile {
|
|
40
40
|
/** Backend for the plugin's OWN index. */
|
|
41
41
|
vectorStore: "qdrant" | "lancedb"
|
|
42
|
+
/**
|
|
43
|
+
* Store Kilo keeps its own index in, when kilo.jsonc says so.
|
|
44
|
+
*
|
|
45
|
+
* Kept alongside `vectorStore` rather than folded into it: Kilo decides where
|
|
46
|
+
* an import reads from, the own index decides where it writes, and the status
|
|
47
|
+
* report needs both to know whether an unreachable Qdrant is a problem here or
|
|
48
|
+
* simply not this user's business.
|
|
49
|
+
*/
|
|
50
|
+
kiloVectorStore: "qdrant" | "lancedb" | null
|
|
42
51
|
qdrantUrl: string
|
|
43
52
|
qdrantApiKey?: string
|
|
44
53
|
/** Base directory for the plugin's own LanceDB stores. */
|