Browse Source

Make build network host-optional and enable hybrid code indexing with local ollama embeddings

master
chodak166 2 days ago
parent
commit
99578fe985
  1. 11
      AGENTS.md
  2. 75
      README.md
  3. 3
      codebase-index.config.json
  4. 40
      docker-compose.yml

11
AGENTS.md

@ -13,6 +13,7 @@ cp .env.example .env # once; add BRAVE_API_KEY
./add-project.sh <repo> # register a project for code indexing (idempotent; starts stack) ./add-project.sh <repo> # register a project for code indexing (idempotent; starts stack)
./add-project.sh --remove <repo-or-slug> ./add-project.sh --remove <repo-or-slug>
docker compose up -d --build docker compose up -d --build
docker compose --profile embeddings up -d # same + ollama embeddings container
docker compose ps / logs -f mcp / logs -f mcp-codebase-index docker compose ps / logs -f mcp / logs -f mcp-codebase-index
opencode reload && sleep 3 && opencode mcp list # acceptance check opencode reload && sleep 3 && opencode mcp list # acceptance check
``` ```
@ -31,8 +32,9 @@ index ships NAPI binaries — do not switch the base to alpine):
One endpoint per registered project: `/<slug>/mcp`. Mounts `PROJECTS_ROOT` (default One endpoint per registered project: `/<slug>/mcp`. Mounts `PROJECTS_ROOT` (default
`~/src`, kept in `.env`) at the identical container path so index results return valid `~/src`, kept in `.env`) at the identical container path so index results return valid
host paths. Runs as `user: node` (uid 1000) so index data is host-user owned. Index host paths. Runs as `user: node` (uid 1000) so index data is host-user owned. Index
data lives in each project at `.opencode/index/` (structural mode: BM25 + call graph, data lives in each project at `.opencode/index/` (hybrid mode: BM25 + call graph +
no embedding provider; ollama upgrade documented in README). ollama `nomic-embed-text` embeddings; the ollama container ships dormant behind the
`embeddings` profile and indexing degrades to keyword-only while it is off).
Per-project client configs (written into each project by add-project.sh): `opencode.json`, Per-project client configs (written into each project by add-project.sh): `opencode.json`,
`.crush.json`, `.roo/mcp.json`, each pointing at `http://localhost:8001/<slug>/mcp`. `.crush.json`, `.roo/mcp.json`, each pointing at `http://localhost:8001/<slug>/mcp`.
@ -45,6 +47,11 @@ Global client configs only carry the shared tools (fetch/context7/brave-search).
working bridge leave it unset — newer compose delegates builds to buildx bake, which working bridge leave it unset — newer compose delegates builds to buildx bake, which
rejects `network: host` ("Pass --allow=network.host...") and compose cannot grant rejects `network: host` ("Pass --allow=network.host...") and compose cannot grant
the entitlement. the entitlement.
- The `ollama` container is profile-gated: plain `docker compose up -d` / `down` ignore
it. `INDEXING WARNING: N chunks failed to embed` in index output means ollama was down
or a batch exceeded the 30s embedding timeout — search falls back to keyword ranking
until a re-index succeeds. Batches are pre-tuned small (`OLLAMA_MAX_BATCH_TOKENS` /
`OLLAMA_MAX_BATCH_ITEMS` env on `mcp-codebase-index`) for CPU-only ollama.
- `opencode mcp list` can print "No MCP servers configured" for a few seconds after - `opencode mcp list` can print "No MCP servers configured" for a few seconds after
`opencode reload` (daemon reconnects asynchronously). Retry before assuming breakage. `opencode reload` (daemon reconnects asynchronously). Retry before assuming breakage.
- After changing client configs, crush/opencode sessions must be restarted/reloaded; - After changing client configs, crush/opencode sessions must be restarted/reloaded;

75
README.md

@ -38,7 +38,8 @@ Two containers do all the work:
- **`mcp-codebase-index`** (port 8001) — a private search index for each of your code - **`mcp-codebase-index`** (port 8001) — a private search index for each of your code
projects, built with [open-codebase-index](https://github.com/Helweg/open-codebase-index). projects, built with [open-codebase-index](https://github.com/Helweg/open-codebase-index).
It lets the AI find things in a large codebase quickly (keyword search, "where is this It lets the AI find things in a large codebase quickly (keyword search, "where is this
defined", "who calls this") instead of reading files one by one. Every project gets its defined", "who calls this", and — with the bundled ollama embeddings — "find this by
meaning") instead of reading files one by one. Every project gets its
own address: `http://localhost:8001/<project>/mcp`. own address: `http://localhost:8001/<project>/mcp`.
## Quick start (once per machine) ## Quick start (once per machine)
@ -164,44 +165,56 @@ tools everywhere, add them once to your plugin's global settings file:
## What the code index actually searches ## What the code index actually searches
By default the index runs in **structural mode**: keyword (BM25) search, symbol The index runs in **hybrid mode**: keyword (BM25) search, symbol definitions, call
definitions, call graphs, branch awareness — entirely **offline**, no AI provider needed. graphs, branch awareness — plus **semantic search** ("find code by meaning") via local
This is usually plenty and it works out of the box. [ollama](https://ollama.com) embeddings. The `ollama` container ships with this stack
but stays dormant (it lives behind the `embeddings` profile), so everything already
works fully offline without it: while ollama is off, semantic ranking just falls back
to keyword ranking.
To add **semantic search** ("find code by meaning"), plug in an embedding provider: Enable semantic search (once per machine):
1. Start local Ollama embeddings: `docker compose --profile embeddings up -d ollama`, then ```bash
`docker compose exec ollama ollama pull nomic-embed-text`. docker compose --profile embeddings up -d # start the ollama container
2. In `codebase-index.config.json` switch `"mode": "structural"` to `"hybrid"` and add: docker compose exec ollama ollama pull nomic-embed-text # one-time, ~274 MB, kept in a volume
# build vectors for your projects (safe to repeat):
```json docker compose exec mcp-codebase-index ocbi index --project ~/src/git/my-project \
{ --config /etc/codebase-index/config.json --force
"embeddingProvider": "custom", ```
"customProvider": {
"baseUrl": "http://ollama:11434/v1",
"model": "nomic-embed-text",
"dimensions": 768
}
}
```
3. Rebuild each project's index:
```bash
docker compose exec mcp-codebase-index ocbi index --project ~/src/git/my-project --config /etc/codebase-index/config.json --force
```
(Or ask the AI to run the `index_codebase` / `index_status` tools — rebuilding and Projects registered later with `./add-project.sh` pick embeddings up automatically.
refreshing indexes is something the assistants can do themselves through MCP.) Check a project's mode: `docker compose exec mcp-codebase-index ocbi status --project
~/src/git/my-project --config /etc/codebase-index/config.json` — semantic search is
live when it reports `Provider: ollama` and the index shows embedded chunks. (You can
also just ask the AI to run `index_status` / `codebase_search`; semantic hits carry
similarity scores near 1.0.)
Gotchas:
- Plain `docker compose up -d` / `down` / `ps` do **not** manage the ollama container.
Pass `--profile embeddings` whenever a command should include it;
`docker compose --profile embeddings down` stops everything.
- If indexing logs `INDEXING WARNING: N chunks failed to embed`, ollama was unreachable
or too slow (CPU). Start it, make sure the model is pulled, re-run the index — failed
chunks are simply rebuilt on the next run. Search keeps working keyword-only meanwhile.
- Batch sizes for the index container are pre-tuned small (`OLLAMA_MAX_BATCH_TOKENS` /
`OLLAMA_MAX_BATCH_ITEMS` env in `docker-compose.yml`) so CPU-only ollama stays under
the embedding API's 30s timeout. Raise them if you have a GPU.
- Embeddings don't have to be local: any OpenAI-compatible endpoint works. Switch
`embeddingProvider` to `"custom"` in `codebase-index.config.json` and set
`customProvider: { "baseUrl", "model", "apiKey" }`. The chat model provider (z.ai,
OpenRouter, cortecs, …) is configured in your AI tool and is completely independent
of this stack — the index never talks to it.
## Everyday commands ## Everyday commands
```bash ```bash
docker compose ps # status docker compose ps # status
docker compose --profile embeddings ps # status, including ollama
docker compose logs -f mcp # shared-tools gateway logs docker compose logs -f mcp # shared-tools gateway logs
docker compose logs -f mcp-codebase-index # index gateway logs (per-project lines prefixed) docker compose logs -f mcp-codebase-index # index gateway logs (per-project lines prefixed)
docker compose restart mcp mcp-codebase-index # after editing supergateway.config.jsonc / codebase-index.config.json docker compose restart mcp mcp-codebase-index # after editing supergateway.config.jsonc / codebase-index.config.json
docker compose down # stop everything (index data survives, it lives in the projects) docker compose --profile embeddings down # stop everything incl. ollama (index data survives, it lives in the projects)
``` ```
## Adding or removing shared tools (port 8000) ## Adding or removing shared tools (port 8000)
@ -262,6 +275,12 @@ docker compose ps # both services "Up"
which refuses `network: host` builds with "Pass `--allow=network.host`..." unless the which refuses `network: host` builds with "Pass `--allow=network.host`..." unless the
host really needs it — on machines with working bridge networking leave `BUILD_NETWORK` host really needs it — on machines with working bridge networking leave `BUILD_NETWORK`
unset (or set it to `default`). If it still fails, check your firewall. unset (or set it to `default`). If it still fails, check your firewall.
- **Indexing logs `INDEXING WARNING: N chunks failed to embed` (no semantic scores)** —
the ollama embeddings container isn't running or the model isn't pulled yet. Fix with
`docker compose --profile embeddings up -d` +
`docker compose exec ollama ollama pull nomic-embed-text`, then re-run the index.
Search still works meanwhile, just keyword-ranked (see "What the code index actually
searches").
- **`brave-search` tools fail** — missing/invalid `BRAVE_API_KEY` in `.env`. - **`brave-search` tools fail** — missing/invalid `BRAVE_API_KEY` in `.env`.
- **Index seems stale** — re-run the project's index: - **Index seems stale** — re-run the project's index:
`./add-project.sh ~/src/git/my-project` (safe to repeat), or ask the AI to run `./add-project.sh ~/src/git/my-project` (safe to repeat), or ask the AI to run

3
codebase-index.config.json

@ -1,10 +1,11 @@
{ {
"scope": "project", "scope": "project",
"indexing": { "indexing": {
"mode": "structural", "mode": "hybrid",
"autoIndex": false, "autoIndex": false,
"watchFiles": true "watchFiles": true
}, },
"embeddingProvider": "ollama",
"search": { "search": {
"maxResults": 20 "maxResults": 20
} }

40
docker-compose.yml

@ -57,6 +57,13 @@ services:
- ${PROJECTS_ROOT:?run ./add-project.sh first}:${PROJECTS_ROOT:?run ./add-project.sh first} - ${PROJECTS_ROOT:?run ./add-project.sh first}:${PROJECTS_ROOT:?run ./add-project.sh first}
- ./codebase-index.config.json:/etc/codebase-index/config.json:ro - ./codebase-index.config.json:/etc/codebase-index/config.json:ro
- ./supergateway.projects.jsonc:/etc/supergateway/projects.jsonc:ro - ./supergateway.projects.jsonc:/etc/supergateway/projects.jsonc:ro
environment:
# Where the ollama embeddings service lives (compose network name).
OLLAMA_HOST: http://ollama:11434
# Small batches so each embedding request finishes well under the 30s
# client timeout when ollama runs on CPU (defaults intermittently time out).
OLLAMA_MAX_BATCH_TOKENS: 4096
OLLAMA_MAX_BATCH_ITEMS: 4
command: command:
- --config - --config
- /etc/supergateway/projects.jsonc - /etc/supergateway/projects.jsonc
@ -64,20 +71,21 @@ services:
- /healthz - /healthz
restart: unless-stopped restart: unless-stopped
# Optional: local embeddings for semantic (hybrid) codebase indexing. # Local embeddings for semantic (hybrid) codebase indexing. Dormant by default —
# Start with: docker compose --profile embeddings up -d # plain `docker compose up -d` ignores it; it only starts with the embeddings
# Then switch indexing.mode to "hybrid" in codebase-index.config.json and set # profile. Always pass the profile on later `up`/`down` calls that should
# embeddingProvider "custom" with baseUrl http://ollama:11434/v1, # include it (see README "Semantic search"):
# model nomic-embed-text, dimensions 768, and run: # docker compose --profile embeddings up -d
# docker compose exec mcp-codebase-index ocbi index --project $PWD --config /etc/codebase-index/config.json --force # docker compose exec ollama ollama pull nomic-embed-text
# ollama: ollama:
# image: ollama/ollama:latest image: ollama/ollama:latest
# profiles: ["embeddings"] profiles: ["embeddings"]
# ports: container_name: mcp-ollama
# - "11434:11434" ports:
# volumes: - "11434:11434"
# - ollama:/root/.ollama volumes:
# restart: unless-stopped - ollama:/root/.ollama
restart: unless-stopped
# volumes: volumes:
# ollama: ollama:

Loading…
Cancel
Save