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
62 changes: 58 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,69 @@
This is built on top of [CocoIndex v1](https://cocoindex.io/docs-v1/llms.txt).
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

This is an AST-based semantic code search tool built on top of [CocoIndex v1](https://cocoindex.io/docs-v1/llms.txt).


## Build and Test Commands

This project uses [uv](https://docs.astral.sh/uv/) for project management.

```bash
uv run mypy . # Type check Python code
uv run pytest tests/ # Run Python tests
uv sync # Install all dev dependencies
uv run pytest tests/ # Run all tests
uv run pytest tests/test_settings.py # Run a single test file
uv run pytest tests/ -k test_name # Run tests matching a name
uv run mypy . # Type check Python code
uv run ruff check . # Lint
uv run ruff format . # Format
uv run prek run --all-files # Full CI suite (lint, format, mypy, pytest)
```

Docker E2E tests are excluded by default (need `pytest -m docker_e2e` to run them).


## Architecture

### Process Model

The tool runs as a **long-lived background daemon** (`daemon.py`) that accepts connections over a Unix socket (Windows: named pipe). The `ccc` CLI and MCP server communicate with it via short-lived **per-request connections** (`client.py`): connect → handshake → send one request → receive response(s) → close.

The daemon is auto-started by `client.py` when not running. It is restarted automatically when the `global_settings.yml` mtime changes (version bump or settings edit), detected via the `HandshakeResponse.global_settings_mtime_us` field.

### Key Layers

**`protocol.py`** — All IPC message types as `msgspec.Struct` tagged unions, serialized as msgpack. Every request type, response type, and streaming wrapper lives here. The `Request` and `Response` type aliases are the union types used by the decoder.

**`daemon.py`** — The daemon entry point (`run_daemon()`). Holds a `ProjectRegistry` that lazily creates and caches `Project` instances keyed by project root. Each connection is handled by `handle_connection()` running as an asyncio task. Dispatches to project operations or daemon-level operations (`_dispatch()`).

**`project.py`** — `Project` wraps a CocoIndex `Environment` + `App`. Manages an `asyncio.Lock` so only one indexing run is active at a time, and an `asyncio.Event` so search can wait for the initial index pass.

**`indexer.py`** — Defines the CocoIndex app (`process_file`): reads files from LocalFS, detects language, applies custom chunkers or the default `RecursiveSplitter`, embeds chunks, and stores them in SQLite via `sqlite-vec`. This is the `@coco.fn(memo=True)` function that CocoIndex tracks for incremental re-indexing.

**`shared.py`** — CocoIndex `ContextKey` definitions (`EMBEDDER`, `SQLITE_DB`, `CODEBASE_DIR`, `INDEXING_EMBED_PARAMS`, `QUERY_EMBED_PARAMS`), the `CodeChunk` dataclass (SQLite schema), and the `create_embedder()` factory.

**`settings.py`** — YAML config loading for both global (`~/.cocoindex_code/global_settings.yml`) and per-project (`.cocoindex_code/settings.yml`) settings. Also provides all path helpers (`cocoindex_db_path`, `target_sqlite_db_path`, `resolve_db_dir`, etc.) and host-path-mapping logic for Docker.

**`server.py`** — FastMCP wrapper that delegates `search` calls to the daemon client. Also contains the legacy `main()` entry point (`cocoindex-code` command) for backward-compatible env-var-based configuration.

**`cli.py`** — Typer app with `ccc` subcommands (`init`, `index`, `search`, `grep`, `status`, `reset`, `doctor`, `mcp`, `daemon status/restart/stop`). CLI logic only — delegates to `client.py` or `grep.py`.

**`grep.py`** — Structural code search (`ccc grep`) using CocoIndex's `code_match`. Runs entirely locally with no daemon or index. Matches in parallel, streams results per file.

**`chunking.py`** — Public `Chunk` type and the `CHUNKER_REGISTRY` context key. Custom chunkers in `settings.yml` are resolved at daemon startup and injected via this context.

### Embedding Model Flow

The embedder is created once at daemon startup (`create_embedder()` in `shared.py`) and stored in the `ProjectRegistry`. Two param dicts (`indexing_params`, `query_params`) are threaded through CocoIndex context keys so asymmetric models (Cohere, Voyage, nomic-embed) can use different kwargs for document vs. query embedding.

Two install flavors: `[full]` bundles `sentence-transformers` (local inference); slim uses only LiteLLM (cloud APIs). The `PacedLiteLLMEmbedder` in `litellm_embedder.py` adds rate-limiting between requests.

### Settings and Project Discovery

`find_project_root()` walks up from CWD looking for `.cocoindex_code/settings.yml`. Global settings at `~/.cocoindex_code/global_settings.yml` (location overridable via `COCOINDEX_CODE_DIR`). The `COCOINDEX_CODE_DB_PATH_MAPPING` env var redirects database files to a different directory (used in Docker to avoid LMDB on bind mounts).


## Code Conventions

### Internal vs External Modules
Expand All @@ -32,7 +86,7 @@ We distinguish between **internal modules** (under packages with `_` prefix, e.g
* Standard library and internal imports don't need underscore prefix
* Only prefix symbols that are truly private to the module itself (e.g. `_context_var` for a module-private ContextVar)

### General principles (also covered by `/review-changes`)
### General principles

- **Top-level imports.** Defer to in-function only for a real circular dependency or a heavy import that isn't always needed.
- **Specific types over `Any`.** When a value enters as a weaker form (`str`, `Any`), convert to the strong type at the earliest point. Don't propagate the weak form.
Expand Down
43 changes: 25 additions & 18 deletions EMBEDDINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

### Speed & Latency

- **Local Sentence-Transformers**: Typically the **fastest** option for small-to-medium models. Because it runs directly inside the `cocoindex-code` process, it avoids the network latency of Cloud APIs and the communication overhead of Local Servers (Ollama).
- **Local Sentence-Transformers**: Typically the **fastest** option for small-to-medium **encoder** models. Because it runs directly inside the `cocoindex-code` process, it avoids the network latency of Cloud APIs and the communication overhead of Local Servers (Ollama). Decoder-based models (e.g. `harrier-oss-v1`) are also in-process, but process tokens sequentially — expect 3–10× slower indexing per chunk on CPU unless you have a GPU.
- **Local Servers (Ollama)**: Ideal for running **heavy models** (like `mxbai-embed-large`) on a GPU. While it has slight overhead compared to in-process execution, it is much faster than running large models on a CPU.
- **Cloud APIs**: Slower per-request due to network latency, but highly parallel. Best for the initial indexing of massive repositories.

Expand All @@ -45,10 +45,14 @@ In `cocoindex-code`, this matters less than you might expect due to our **Langua
- **Target Size**: While respecting boundaries, it targets a chunk size of **~1,000 characters** (~300 tokens).
- **Compatibility**: This hybrid approach ensures code snippets are contextually coherent while remaining small enough to fit perfectly within even the smallest 512-token context windows.

### CPU vs. GPU
### CPU vs. GPU and Architecture

- The default `xs` model is optimized for **CPUs**; you likely won't see a benefit from a GPU.
- For `medium` or `large` models, a GPU is highly recommended. If you have one, you can tell Sentence-Transformers to use it by adding `device: cuda` (or `mps` for Mac) to your `global_settings.yml`.
Indexing speed depends on **both parameter count and model architecture**:

- **Encoder models** (BERT/ModernBERT-based, e.g. Snowflake, LateOn-Code, CodeSearch-ModernBERT): Process all tokens in parallel — fast on any modern CPU. The default `xs` and all models marked **Fast** or **Very Fast** in the table below are encoder-based.
- **Decoder models** (LLM-based, e.g. `harrier-oss-v1`): Process tokens sequentially, making them **3–10× slower** than an encoder of comparable parameter count on CPU. A GPU is strongly recommended to achieve acceptable indexing throughput with these models.

For encoder models at medium or large sizes, a GPU will still accelerate indexing. Add `device: cuda` (or `mps` on Mac) to `global_settings.yml`.

---

Expand All @@ -70,13 +74,14 @@ This option runs embedding models directly on your machine using the library.

These are based on MTEB [datasets](https://huggingface.co/datasets/mteb/results) as of 15-Jun-2026. All listed models have been verified to work with the `sentence-transformers` provider in `cocoindex-code`.

| Tier | Model | Params | Code Score | Best For |
| :--- | :--- | :--- | :--- | :--- |
| **Default** | [`Snowflake/arctic-embed-xs`](https://huggingface.co/Snowflake/snowflake-arctic-embed-xs) | 22M | 0.67 | Default |
| **Micro** | [`lightonai/LateOn-Code-edge`](https://huggingface.co/lightonai/LateOn-Code-edge) | 17M | 0.82 | **Efficiency King.** Incredible code performance for its size. |
| **Small** | [`lightonai/LateOn-Code`](https://huggingface.co/lightonai/LateOn-Code) | 149M | 0.85 | Great balance of speed and accuracy on modern laptops. |
| **Medium** | [`microsoft/harrier-oss-v1-270m`](https://huggingface.co/microsoft/harrier-oss-v1-270m) | 270M | **0.90** | **Performance sweet spot.** High accuracy, runs well on CPUs. |
| **Multi** | [`ibm-granite/granite-embedding-97m-multilingual-r2`](https://huggingface.co/ibm-granite/granite-embedding-97m-multilingual-r2) | 97M | 0.80 | Multilingual codebases (e.g. Code + Docs in different languages). |
| Tier | Model | Params | Dims | Code Score | Arch | CPU Speed | Best For |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| **Default** | [`Snowflake/arctic-embed-xs`](https://huggingface.co/Snowflake/snowflake-arctic-embed-xs) | 22M | 384 | 0.67 | Enc | Very Fast | Smallest, most compatible default |
| **Micro** | [`lightonai/LateOn-Code-edge`](https://huggingface.co/lightonai/LateOn-Code-edge) | 17M | 256 | 0.82 | Enc | Crazy Fast (231 cps) | **Efficiency King.** Incredible code performance for its size. |
| **Small** | [`lightonai/LateOn-Code`](https://huggingface.co/lightonai/LateOn-Code) | 149M | 768 | 0.85 | Enc | Fast (7 cps) | Great balance of speed and accuracy on modern laptops. |
| **Medium** | [`Shuu12121/CodeSearch-ModernBERT-Crow-Plus`](https://huggingface.co/Shuu12121/CodeSearch-ModernBERT-Crow-Plus) | 152M | | 0.89 | Enc | Fast | High accuracy with encoder speed; **best for CPU-only indexing at this tier.** |
| **Larger** | [`microsoft/harrier-oss-v1-270m`](https://huggingface.co/microsoft/harrier-oss-v1-270m) | 270M | 640 | **0.90** | *Dec* | Slow | Highest local accuracy; **GPU** *strongly recommended* for acceptable speed. |
| **Multi-Lingual** | [`ibm-granite/granite-embedding-97m-multilingual-r2`](https://huggingface.co/ibm-granite/granite-embedding-97m-multilingual-r2) | 97M | 384 | 0.80 | Enc | Fast | Multilingual codebases (e.g. Code + Docs in different languages). |

#### Other Model Options

Expand All @@ -90,7 +95,7 @@ The default of `Snowflake/arctic-embed-xs` is a good choice in most situations,
4. Enable the **Sentence-Transformers Compatible** toggle.
5. Adjust **Model Size** to fit your hardware (e.g., `< 500M` for CPUs).
- **Compatibility**: Look for **Bi-encoders** with a fixed dimension size (e.g., 384, 768, 1024). Avoid "Late Interaction" (ColBERT) or "Cross-Encoders".
- **Architecture**: **Encoder** models (BERT-based) are much faster on CPUs than **Decoder** models (LLM-based).
- **Architecture**: **Encoder** models (BERT/ModernBERT-based) are much faster on CPUs than **Decoder** models (LLM-based). A decoder model of the same parameter count can be 3–10× slower per chunk due to sequential token processing. Prefer encoders for CPU-only workloads.

### Installation & Configuration

Expand All @@ -101,7 +106,7 @@ Example `global_settings.yml`:
```yaml
embedding:
provider: sentence-transformers
model: jinaai/jina-embeddings-v5-text-nano
model: lightonai/LateOn-Code-edge
device: cpu # Use 'cuda' or 'mps' if you have a GPU
```

Expand Down Expand Up @@ -191,7 +196,8 @@ envs:

## Choosing Based on Your Content

- **Heavy Source Code**: Use **LateOn-Code** (Micro/Small) or **Harrier 270m** (Medium). Both score >0.85 on code search benchmarks.
- **Heavy Source Code, CPU-only**: Use **LateOn-Code** (Micro/Small) or **CodeSearch-ModernBERT-Crow-Plus** (Medium Encoder). Both are encoder-based and score >0.85 on code search benchmarks with good CPU throughput.
- **Heavy Source Code, GPU available**: **Harrier 270m** (Medium Decoder, 0.90) gives the highest local accuracy and is much faster with a GPU.
- **Large Documentation / Files**: Models with large context windows like **Voyage 4 Large** (Cloud) or **OpenAI v3 Large** (8k).
- **Multilingual Projects**: **Granite 97m** (Small Local) or **Cohere Multilingual v3** (Cloud).

Expand All @@ -211,14 +217,15 @@ embedding:
input_type: query
```

**Example for Sentence-Transformers (Harrier):**
**Example for Sentence-Transformers (symmetric models — Harrier, CodeSearch-ModernBERT, etc.):**

Most local models don't require asymmetric prompts. If `ccc init` doesn't auto-detect defaults
for your model, an explicit `null` disables any inherited prompt:

```yaml
embedding:
provider: sentence-transformers
model: microsoft/harrier-oss-v1-270m
# Most encoder-only models don't require explicit prompts,
# but some (like Nomic or BGE) do:
model: microsoft/harrier-oss-v1-270m # or Shuu12121/CodeSearch-ModernBERT-Crow-Plus
indexing_params:
prompt_name: null
query_params:
Expand Down
Loading
Loading