Skip to main content
Glama
open-context-engine

open-context-engine

README.md
# Open Context Engine

<p align="center">
  <img src="https://img.shields.io/badge/TypeScript-5.6+-3178C6?logo=typescript&logoColor=white" alt="TypeScript" />
  <img src="https://img.shields.io/badge/SQLite-vec0-003B57?logo=sqlite&logoColor=white" alt="SQLite" />
  <img src="https://img.shields.io/badge/MCP-Compatible-1a1a1a?logo=protocols&logoColor=white" alt="MCP" />
  <img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License" />
</p>

<p align="center">
  <b>Local. Semantic. Instant.</b><br/>
  Turn any codebase into a searchable knowledge base for AI.
</p>

---

Open Context Engine (OCE) is a **local-first, high-performance retrieval engine** that indexes your entire codebase, embeds it into vectors, and serves it up to LLMs via semantic search, keyword search, and the Model Context Protocol (MCP). No cloud required. No code leaves your machine.

Whether you're building a coding assistant, automating PR reviews, or just want to ask natural-language questions about your own codeβ€”OCE gives your AI **ground truth**.

> πŸ“˜ **Docs, benchmarks & guides:** [opencontextengine.com](https://opencontextengine.com) β€” MCP setup for Claude Code & Cursor, the agentic-workflow guide, and measured retrieval benchmarks.

## ✨ What Makes It Special

| Feature | Description |
|---------|-------------|
| 🧠 **AST-Aware Chunking** | Uses Tree-sitter to split code along semantic boundaries (functions, classes, methods) rather than naive line counts. Understands TypeScript, JavaScript, Python, Go, Rust, Java, C#, C, C++, Ruby, PHP, Kotlin, and Swift. |
| πŸ” **Hybrid Search** | Fuses **dense vector similarity** (cosine) with **BM25 keyword search** via Reciprocal Rank Fusion for results that are both semantically relevant and lexically precise. |
| ⚑ **Local SQLite + Vectors** | Stores everything in a local SQLite database with `sqlite-vec`β€”no external vector DB, no network latency, no subscription fees. |
| 🏠 **Fully-Local Embeddings** | The default: in-process ONNX embeddings β€” zero API keys, zero servers, models cached on disk. Ollama also supported; Voyage is the drop-in upgrade for the best code retrieval. |
| πŸ”— **Symbol Expansion** | Automatically resolves identifiers in search results, pulling in definitions of functions, classes, and types referenced in snippets. |
| 🎯 **Optional Re-ranking** | Plug in Voyage or Cohere re-rankers to boost result quality for complex queries. |
| πŸ•ΈοΈ **Code Graph** | A tree-sitter AST pass extracts import/call/definition edges into a queryable graph; top search results are graph-expanded so callers and callees ride along. |
| πŸ”­ **Trace** | The agent workspace in your browser, terminal, or desktop: an evidence rail showing what is in the model's context (ranked, pinnable, evictable), checkpoint rewind, approvals with a blast-radius score, watchable sub-agents, and parallel sessions in isolated git worktrees you can review and land. |
| πŸ€– **Agent Harness** | A full tool-use agent with codebase retrieval, file editing, shell execution, and web search β€” plus parallel read-only tool execution, pre/post tool-call hooks, token-usage accounting, session export/import, model routing, and cross-session memory. |
| πŸ”Œ **MCP Native** | Exposes retrieval, file, and symbol tools through the Model Context Protocol over stdio or **Streamable HTTP** (shared endpoint with bearer auth). Works with Claude Desktop, Cursor, and any MCP-compatible client. |
| 🧰 **VS Code Extension** | Sidebar chat grounded in the index, agent edits with per-file diff/undo/redo, live re-indexing on save, and an index-health panel. Ships per-platform; one Node-API SQLite binding runs on every VS Code from 1.103. |
| πŸ›‘οΈ **Policy Controls** | Pin what the engine may do per workspace or org: disable shell/edits/web-search, pin command allowlists, force local-only embeddings, exclude paths. Org-signed policy locks cannot be loosened locally. |
| 🧾 **Tamper-Evident Audit Log** | Hash-chained JSONL of every agent run, tool call, and MCP invocation. `oce audit --verify` detects any alteration, deletion, or reordering. |
| πŸ‘₯ **Team Index Sync** | Build the index once in CI, publish it as an artifact (S3/HTTP/shared drive), and teammates `oce pull-index` it β€” only their local diff re-embeds. A content-hash embedding cache means identical code never bills twice. |
| πŸš€ **Parallel Indexing** | Worker-thread parse/chunk pool kicks in automatically on large repos; `oce bench` measures throughput on yours. |
| πŸ›Ÿ **Degrades, Never Dies** | No sqlite-vec build for your platform? The engine runs keyword-only (BM25) instead of crashing β€” indexing and search keep working, and every surface tells you which mode you're in. |
| πŸ“ **Measured, Not Guessed** | Retrieval quality is scored against committed gold sets β€” this repo's own and an external codebase β€” in **both** search modes, with **adaptive fusion** that tilts BM25/semantic weights per query shape: **hybrid recall@10 0.943 Β· keyword-only 0.886** here, **parity (0.926) on Flask** where hybrid previously lost. Ranking changes ship with before/after deltas. The coding agent itself is scored too: `oce eval-agent` runs end-to-end tasks graded by verification scripts. |

## πŸš€ Quick Start

### 1. Install

```bash
npm install -g @dhaxor/open-context     # Node 22+
```

### 2. Pick a model β€” free options included

```bash
oce setup
```

`oce setup` checks what you already have (a running Ollama, keys in your
environment) and lists every way to get a working model, **free ones first**:

| `-p` | Cost | |
|---|---|---|
| `ollama` | free, local | Runs on your machine. No key, no quota, works offline. |
| `google` | free tier | Gemini β€” the most generous free tier; key at aistudio.google.com/apikey |
| `groq` | free tier | Very fast; key at console.groq.com/keys |
| `cerebras` | free tier | Key at cloud.cerebras.ai |
| `openrouter` | free models | Models ending `:free` cost nothing; key at openrouter.ai/keys |
| `openai`, `anthropic` | paid | |

`oce setup -p google` records your choice; after that `oce trace` just works.

**Hybrid search out of the box β€” no key, no account.** The first index embeds
with local MiniLM (in-process, on-device, nothing sent anywhere), so semantic +
keyword search works immediately. Want the strongest code retrieval? A Voyage
key upgrades the embedding model β€” `oce setup --embeddings voyage` β€” and the
index rebuilds with it. Local does the job fine if you'd rather not.

### 3. Open Trace

```bash
oce trace      # the workspace, in your browser
oce            # the same thing, in your terminal
```

Both index the current directory on start and keep one conversation going β€”
see [Trace](#-trace) below for what makes it different.

### 4. Or search from the command line

```bash
oce index                                        # full index (incremental afterwards: --incremental)
oce search "how does the auth middleware work?"
```

## πŸ”­ Trace

Trace is the agent workspace. Every coding agent shows you what it *did*; Trace
also shows you what it *knew* β€” because OCE owns the retrieval, not just the
chat loop.

- **Evidence rail.** What is in the model's context window right now: every
  retrieved chunk ranked by its fusion score, the graph edge that pulled it in
  ("called by search.ts"), and a budget meter split into retrieval / files /
  history / system. **Pin** a chunk and it survives compaction; **evict** one
  and it is genuinely removed from the next request.
- **Retrieval readout instead of a spinner** β€” `retrieved 5 Β· top 0.94 Β· graph +2 Β· 251ms`.
- **The spine.** A timeline of checkpoints, one per turn, on the same hash chain
  as the audit log. Click one to rewind: the conversation *and* the files the
  agent changed roll back β€” and a file you edited by hand since is left alone.
- **Approvals with a blast radius.** Every edit and shell command waits for
  `y` / `a`lways / `n`, showing the diff, a risk score, and how many indexed
  callers the file has.
- **Sub-agents you can watch.** Delegated research streams into its own
  collapsible block instead of going silent for minutes.
- **Parallel sessions.** In a git repository, each new session gets its own
  worktree and branch, so agents never edit the same checkout. Review a
  session's diff and **land** it (commit + `--no-ff` merge) when it's done β€”
  uncommitted work is never discarded when you close one.
- **The composer.** `@path` pins a file into context (fuzzy-matched against
  the index), `!command` runs a shell command through the same approval flow,
  and `/command` for everything else β€” the same in the browser and terminal.

`oce trace` binds to `127.0.0.1` only and mints a fresh access token each
launch. Useful flags: `--port`, `--no-open`, `--headless` (API only),
`--no-parallel`.

### In the terminal

Bare `oce` opens the same workspace full-screen in your terminal: the same
evidence rail (from 100 columns wide, or <kbd>Ctrl</kbd>+<kbd>R</kbd>), the same
checkpoint gutter, the same approvals. <kbd>Esc</kbd> or <kbd>Ctrl</kbd>+<kbd>C</kbd>
interrupts a run; <kbd>Ctrl</kbd>+<kbd>C</kbd> twice or `/exit` quits. Pipes,
`--print`, and dumb terminals fall back to the classic line-by-line REPL, which
you can also ask for with `oce --classic`.

### As a desktop app

`desktop/` wraps Trace in an Electron shell that adds desktop notifications
(only when an approval is waiting, a turn finishes in the background, or
something fails), an application menu, and `trace://` deep links. It is built
from a source checkout for now β€” see [`desktop/README.md`](desktop/README.md).

### The classic REPL

`oce --classic` streams styled output, shows each tool call with a live spinner and timing, and β€” before any file edit or shell command β€” prints a **diff/command preview and asks for approval** (`y` / `a`lways / `n`). Ctrl+C interrupts the current run without quitting the session.

**Approval modes** (as in the other leading coding CLIs):

- default (`suggest`) β€” every edit and shell command asks first
- `--auto-edit` β€” file edits apply automatically; shell still asks
- `--full-auto` β€” nothing asks (for containers / CI)

Switch mid-session with `/mode`. Interactive sessions have the edit and shell tools available behind approvals; the non-interactive `--print` path keeps them opt-in via `--allow-edits` / `--allow-shell`.

**Slash commands:** `/help` `/reset` `/compact` `/plan` `/diff` `/usage` `/tools` `/mode` `/sessions` `/resume` `/exit`.

**Sessions persist automatically** to `.open-context/sessions/`. `oce --continue` resumes the last conversation in the workspace; `oce --resume <id>` restores a specific one (list them with `/sessions`).

**Harness features:**

- **Plan tracking** β€” the agent maintains a live step checklist for multi-step work, rendered as you watch.
- **Sub-agent delegation** β€” broad explorations run in a scoped read-only child agent, so their large intermediate output never floods the main context.
- **Summarizing compaction** β€” when history gets long, older turns are condensed into a context note *by the model* rather than dropped, so decisions and file paths survive; force it with `/compact`.
- **Environment awareness** β€” platform, git branch/status, date, and index stats are injected into the system prompt.
- **Cost + parallelism** β€” every turn ends with a stats line (steps, tool calls, token usage, wall time); read-only tool calls in a turn run in parallel, while any turn with an edit or shell call stays strictly ordered.
- `--route` sends each query to a cost-appropriate model tier; `--memory` remembers codebase insights across sessions; `--audit` logs runs to the tamper-evident audit log.

Non-interactive use for scripts and CI:

```bash
oce --print "where is the auth middleware defined?"           # one answer, then exit
oce --print "list the exported symbols in src/core" --json     # {answer, stats, toolCalls}
```

The programmatic API mirrors all of this: `defaultAgentTools({ context })` stays read-only unless you pass `includeEdits`/`shell`/`plan`/`delegate`, and `ContextAgent` accepts `hooks` (a `PermissionManager` composes in here), `compaction`, and `environmentProvider`.

### 5. Connect to Claude / Cursor via MCP

```bash
oce mcp --workspace ./my-project

# Or serve a shared HTTP endpoint (e.g. one index for the whole team):
oce mcp --workspace ./my-project --http --port 8940 --auth-token "$TOKEN"
```

The MCP server indexes the workspace on startup and then **watches for changes**, so the index stays live without a manual re-index. Pass `--no-watch` to disable the watcher. (The MCP handshake is established before indexing begins, so the first index of a large repo won't block your client from connecting.)

In HTTP mode the server speaks MCP's Streamable HTTP transport, binds to loopback by default, answers `GET /health`, and β€” when `--auth-token` (or `OCE_MCP_AUTH_TOKEN`) is set β€” requires `Authorization: Bearer <token>` on every request.

Then add to your MCP config:

```json
{
  "mcpServers": {
    "open-context": {
      "command": "oce",
      "args": ["mcp", "--workspace", "/path/to/project"]
    }
  }
}
```

### 6. Or use the VS Code extension

The `extension/` folder ships a full VS Code experience on top of the same
engine: a sidebar **chat** grounded in your index (streaming, markdown,
code-block copy/insert/apply), **agent edits** with per-file diff /
undo / redo and a per-turn "undo all", **quick search**
(<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>K</kbd>), auto-indexing on save, and an
**index health panel** for debugging retrieval. Each platform package carries
one Node-API SQLite binding, which runs on every VS Code from 1.103 and on
remote hosts (SSH, WSL, Codespaces) β€” see
[`extension/PUBLISHING.md`](extension/PUBLISHING.md) for the support matrix,
or build your own VSIX:

```bash
cd extension && npm ci --ignore-scripts
npm run package -- linux-x64   # β†’ ../artifacts/open-context-linux-x64-<version>.vsix
```

## πŸ“– Usage Guide

### CLI Reference

```bash
oce <command> [options]

Commands:
  index <workspace>      Index a codebase
  search <query>         Search the indexed codebase
  watch                  Index, then keep the index live as files change
  mcp                    Start MCP server (stdio, or --http for a shared endpoint)
  agent                  Start interactive agent (auto-indexes on startup)
  eval                   Score retrieval quality against a labeled query set
  push-index <dest>      Publish the index as a team artifact (Team)
  pull-index <src>       Install a team index, re-embed only local changes (Team)
  bench                  Benchmark parse/chunk throughput on this workspace
  policy                 Show the effective policy (user + workspace + org lock)
  audit                  Inspect / verify the tamper-evident audit log
  activate <key>         Activate a Team/Enterprise license
  license / deactivate   Show or remove the active license

Options:
  -w, --workspace <path>   Project root (default: cwd)
  -p, --provider <name>    Embedding provider: voyage | openai | ollama | local
  -m, --model <model>      Embedding model name
  --api-key <key>          API key (falls back to env vars)
  --store-path <path>      Custom store directory (default: .open-context/)
  --max-file-size <bytes>  Skip files larger than this
  --chunk-size <lines>     Lines per chunk (default: 80)
  --chunk-overlap <lines>  Overlap between chunks (default: 15)
```

### Programmatic API

```typescript
import { OpenContext, ContextAgent, defaultAgentTools } from "@dhaxor/open-context";

// 1. Create and index
const ctx = await OpenContext.create({
  workspaceRoot: "./my-project",
  embedding: {
    provider: "voyage",
    model: "voyage-code-3",
    dimension: 1024,
    apiKey: process.env.VOYAGE_API_KEY,
    batchSize: 32,
  },
});

await ctx.indexWorkspace();

// 2. Search
const results = await ctx.search("authentication middleware flow");
console.log(results);

// 3. Agent with tools
const agent = new ContextAgent({
  provider: "openai",
  model: "gpt-4o",
  apiKey: process.env.OPENAI_API_KEY,
  tools: defaultAgentTools({ context: ctx, includeEdits: true }),
  // Optional harness features:
  maxParallelTools: 4,                       // read-only tool calls fan out concurrently
  hooks: {
    preToolCall: (tc) => tc.name === "remove-file"
      ? { behavior: "deny", reason: "deletions are reviewed manually" }
      : { behavior: "allow" },
    postToolCall: (_tc, result) => result.replaceAll(process.env.HOME!, "~"),
  },
});

const answer = await agent.run("How does the auth middleware work?");
console.log(answer);
console.log(agent.getLastRunStats());        // steps, tool calls, token usage, duration
const saved = agent.exportSession();          // persist; importSession(saved) restores

// 4. Cleanup
ctx.close();
```

### MCP Server Integration

```typescript
import { runMCPServer } from "@dhaxor/open-context";

await runMCPServer({
  workspaceRoot: "./my-project",
  embedding: { provider: "voyage", model: "voyage-code-3", dimension: 1024, batchSize: 32 },
});
```

Exposed MCP tools:
- `codebase-retrieval` β€” Natural language search over code
- `list-files` β€” Browse indexed files with filters
- `read-file` β€” Read file contents with optional line ranges
- `find-symbol-definition` β€” Exact-name lookup of where a function/class/type is defined
- `find-symbol-references` β€” Every indexed usage site of an identifier
- `index-status` β€” Chunk count, file count, search mode, embedding model

## πŸ—οΈ Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Open Context Engine                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  CLI / API / MCP / Agent                                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  HybridRetriever                                             β”‚
β”‚  β”œβ”€β”€ Vector Search (sqlite-vec)                              β”‚
β”‚  β”œβ”€β”€ BM25 Search (SQLite FTS5, porter-stemmed)               β”‚
β”‚  β”œβ”€β”€ Reciprocal Rank Fusion + symbol/editor/recency boosts   β”‚
β”‚  β”œβ”€β”€ Optional Re-ranking (Voyage / Cohere)                   β”‚
β”‚  └── Graph Expansion (callers/callees via code graph)        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  AstChunker + CodeChunker                                    β”‚
β”‚  β”œβ”€β”€ Tree-sitter parsers (TS, JS, Py, Go, Rust, Java, C#)   β”‚
β”‚  └── Boundary-aware fallback chunking                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Embedding Providers                                         β”‚
β”‚  β”œβ”€β”€ Voyage Code-3 (recommended)                             β”‚
β”‚  β”œβ”€β”€ OpenAI text-embedding-3-*                               β”‚
β”‚  └── Ollama (local, nomic-embed-text)                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  SqliteStore                                                 β”‚
β”‚  β”œβ”€β”€ chunks (metadata)                                       β”‚
β”‚  β”œβ”€β”€ chunks_vec (vector index β€” skipped in keyword-only)     β”‚
β”‚  β”œβ”€β”€ chunks_fts (full-text index)                            β”‚
β”‚  β”œβ”€β”€ graph_edges (import/call/definition graph)              β”‚
β”‚  └── files (index state)                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## βš™οΈ Configuration

### Embedding Models

| Provider | Model | Dimension | Best For |
|----------|-------|-----------|----------|
| **Voyage** | `voyage-code-3` | 1024 | ⭐ Code retrieval (recommended) |
| OpenAI | `text-embedding-3-large` | 3072 | General purpose |
| OpenAI | `text-embedding-3-small` | 1536 | Speed + cost |
| Ollama | `nomic-embed-text` | 768 | 100% local via an Ollama server |
| Local | `jina-embeddings-v2-base-code` | 768 | 100% local, in-process, code-tuned β€” no key, no server |
| Local | `all-MiniLM-L6-v2` | 384 | 100% local, in-process, smallest/fastest |

The `local` provider needs the optional `@huggingface/transformers` package; models download once into `~/.open-context/models` (override with `OCE_MODEL_DIR`) and run offline afterwards.

### Agent LLMs

`openai`, `anthropic`, `google` (Gemini, streaming + tools), `ollama` (fully local β€” `oce -p ollama --llm-model qwen2.5-coder:7b` needs no API key), and `custom` (any OpenAI-compatible endpoint via `--llm-base-url`). With `-p ollama` for the LLM, `--embedding-provider local` for embeddings, and `reranker: { provider: "local" }`, the **entire stack runs offline** β€” index, search, rerank, and agent.

### Config file

Persistent settings live in `~/.open-context/config.json` (user) and `<workspace>/.open-context/config.json` (project β€” commit it). Flags and env vars always win. API keys are deliberately rejected in config files; use env vars.

```jsonc
{
  "embedding": { "provider": "local", "model": "jina-embeddings-v2-base-code" },
  "llm": { "provider": "ollama", "model": "qwen2.5-coder:7b" },
  "search": { "topK": 20, "minScore": 0.15 },
  "chunkSize": 80
}
```

### Search Configuration

```typescript
{
  search: {
    topK: 15,              // Final results returned
    candidateK: 60,        // Candidates before fusion/rerank
    maxOutputLength: 80000,// Max chars in formatted output
    minScore: 0.0,         // Score floor
    bm25Weight: 1.0,       // Keyword search weight
    vectorWeight: 1.0,     // Semantic search weight
    rerank: true,          // Enable reranking if configured
    expandSymbols: true,   // Pull in referenced definitions
  }
}
```

### Re-ranking

```typescript
{
  reranker: {
    provider: "voyage",    // or "cohere"
    model: "rerank-2",     // optional
    apiKey: "your-key",
  }
}
```

## πŸ‘₯ Team Index Sync (Team)

Index once β€” in CI or on a lead's machine β€” and let the whole team pull the result instead of each paying to embed the same monorepo:

```bash
# CI / lead machine: refresh the index and publish it (any file path or
# HTTP(S) PUT endpoint β€” S3/GCS presigned URLs work as-is)
oce push-index https://artifacts.example.com/oce/main.db.gz --token "$TOKEN"

# Each teammate: install it, then only their local diff gets embedded
oce pull-index https://artifacts.example.com/oce/main.db.gz --token "$TOKEN"
# β†’ Installed team index: 12,481 chunks, 1,904 files, built ... at commit ab12cd34
# β†’ Reconciled: 3 file(s) re-embedded locally, 1,901 reused from the artifact
```

The artifact is the store itself (vectors + FTS + code graph + file hashes) gzipped, so reconciliation is a plain incremental index: only files whose hashes differ from the artifact re-embed. No OCE cloud service is involved β€” artifacts go to storage **you** control.

On top of that, a machine-wide **embedding cache** (`~/.open-context/embed-cache.db`, on by default in the CLI and extension) keys vectors by content hash: identical code never embeds twice, across repos, branches, and store rebuilds. Disable with `--no-embed-cache` or `openContext.embedding.cache.enabled`.

## πŸ›‘οΈ Policy Controls

Commit a policy file and every OCE surface (CLI, extension, library) enforces it β€” a `--allow-shell` flag can't override it, and the merge is always most-restrictive-wins:

```jsonc
// <workspace>/.open-context/policy.json (or ~/.open-context/policy.json for user-wide)
{
  "agent": {
    "shell": { "enabled": false },                  // or pin: { "allowlist": ["git", "npm"] }
    "edits": { "enabled": true },
    "webSearch": { "enabled": false }
  },
  "embedding": { "localOnly": true },               // only ollama/local providers may run
  "ignore": ["secrets/**", "*.pem"],                // never indexed, on top of .contextignore
  "audit": { "required": true }                     // force audit logging on
}
```

`oce policy` prints the effective merge and where each rule came from. Orgs on a Team+ plan can additionally ship an **Ed25519-signed `policy.lock`** (issued with `scripts/license-tool.mjs sign-policy`) that developers cannot loosen or delete their way out of.

## 🧾 Audit Log

`oce agent --audit` (or the `openContext.agent.audit.enabled` setting) appends every run, tool call, and MCP invocation to `.open-context/audit/audit.jsonl`. Each record is SHA-256 hash-chained to the previous one, so edits, deletions, and reordering are detectable:

```bash
oce audit                 # recent events
oce audit --type tool-call --since 2026-07-01
oce audit --verify        # βœ“ chain intact β€” or exactly where it was tampered
```

Audit logging is an Enterprise feature; a workspace/org policy with `"audit": { "required": true }` also switches it on.

## πŸ› οΈ Development

```bash
git clone https://github.com/Dhaxor/open-context-engine.git
cd open-context-engine
npm install
npm run build
npm test
```

### Project Structure

```
src/
β”œβ”€β”€ core/
β”‚   β”œβ”€β”€ context.ts          # Main OpenContext class
β”‚   β”œβ”€β”€ ast-chunker.ts      # Tree-sitter chunking
β”‚   β”œβ”€β”€ chunker.ts          # Fallback line chunking
β”‚   β”œβ”€β”€ embedder.ts         # Embedding providers
β”‚   β”œβ”€β”€ retriever.ts        # Hybrid search + RRF + boosts
β”‚   β”œβ”€β”€ reranker.ts         # Re-ranking providers
β”‚   β”œβ”€β”€ sqlite-store.ts     # SQLite persistence (vec + FTS5 + graph)
β”‚   β”œβ”€β”€ code-graph.ts       # Import/call/definition edge queries
β”‚   β”œβ”€β”€ ast-graph-extractor.ts # Single-DFS AST edge extraction
β”‚   β”œβ”€β”€ graph-expander.ts   # Graph-aware result expansion
β”‚   β”œβ”€β”€ chunk-pool.ts       # Worker-thread parse/chunk pool (auto on big repos)
β”‚   β”œβ”€β”€ policy.ts           # Workspace/org policy loading + enforcement
β”‚   β”œβ”€β”€ audit.ts            # Tamper-evident (hash-chained) audit log
β”‚   β”œβ”€β”€ license.ts          # Offline Ed25519 license gate
β”‚   β”œβ”€β”€ search.ts           # Output formatting
β”‚   β”œβ”€β”€ file-filter.ts      # Gitignore-aware file collection
β”‚   └── file-watcher.ts     # Watch for changes
β”œβ”€β”€ agent/
β”‚   β”œβ”€β”€ agent.ts            # ContextAgent + tools
β”‚   β”œβ”€β”€ providers.ts        # LLM callers (OpenAI, Anthropic)
β”‚   β”œβ”€β”€ model-router.ts     # Cost-appropriate tier per query
β”‚   β”œβ”€β”€ session-memory.ts   # Cross-session insight memory
β”‚   β”œβ”€β”€ edit-tools.ts       # File editing tools
β”‚   └── extra-tools.ts      # Shell + web search
β”œβ”€β”€ eval/
β”‚   └── runner.ts           # Retrieval-quality scoring (oce eval)
β”œβ”€β”€ mcp/
β”‚   └── server.ts           # MCP server implementation
└── cli/
    └── index.ts            # oce CLI entrypoint
extension/                  # VS Code extension (chat, edit review, health)
```

## πŸ§ͺ How Search Works

1. **Chunking**: Your code is parsed by Tree-sitter into semantic units (functions, classes, methods). Unsupported files fall back to boundary-aware line chunking.
2. **Embedding**: Each chunk is embedded using your chosen provider. Voyage Code-3 is optimized for code and gives the best retrieval quality.
3. **Indexing**: Vectors go into `sqlite-vec`, text into FTS5, metadata into SQLite tables. All local.
4. **Query**: Your natural language query is embedded and searched simultaneously via vector similarity and BM25.
5. **Fusion**: Results are fused with Reciprocal Rank Fusion, then boosted by symbol-name matches, your editor context (active file, open tabs), and git recency.
6. **Re-ranking** (optional): Top candidates are re-scored by a dedicated re-ranker for higher precision.
7. **Graph Expansion**: Top results are expanded along the code graph β€” callers, callees, and referenced definitions ride along so the LLM sees the full picture.

If the `sqlite-vec` extension can't load on your platform, steps 2 and 4's vector half are skipped automatically and the engine runs **keyword-only (BM25)** β€” slower to find conceptual matches, but everything keeps working and `getStatus()` / the CLI / the extension all surface the mode.

## πŸ“ Measuring Retrieval Quality

`oce eval` scores the engine against a labeled query set β€” so ranking changes are measured, not guessed:

```bash
# Score the engine against the bundled gold set for this repo
oce eval --cases eval/oce.eval.json

# Save a baseline, change a ranking knob, then compare
oce eval --cases eval/oce.eval.json --out baseline.json
# ...tweak weights / expansion / reranker...
oce eval --cases eval/oce.eval.json --baseline baseline.json

# Measure what symbol/graph expansion contributes
oce eval --cases eval/oce.eval.json --no-expand --baseline baseline.json
```

Reports **recall@k**, **MRR**, **nDCG@k**, **hit-rate**, and per-query latency, plus per-case deltas (improved / regressed) against a saved baseline. Metrics are file-granular: a case passes when the gold file ranks in the top-k unique files returned.

Current numbers, measured fresh on this repo's committed 44-case gold set ([`eval/oce.eval.json`](eval/oce.eval.json)) and the external 27-case Flask set ([`eval/external/flask.eval.json`](eval/external/flask.eval.json)) β€” baselines committed beside them:

| gold set | keyword-only (BM25) | hybrid (voyage-code-3) | hybrid (local MiniLM, adaptive fusion) |
|---|---|---|---|
| this repo β€” recall@10 / MRR / nDCG@10 | 0.886 / 0.676 / 0.716 | **0.943 / 0.759 / 0.792** | 0.932 / 0.691 / 0.743 |
| Flask 3.1.2 (external) β€” recall@10 / MRR / nDCG@10 | 0.926 / 0.605 / 0.685 | 0.889 / 0.604 / 0.676 | **0.926 / 0.641 / 0.711** |

Two honest readings, and both matter:

- **Hybrid earns its keep where queries paraphrase intent** β€” +6 points of recall on this repo's set, and it's the mode the agent actually runs in.
- **Adaptive fusion closed the definition-query gap.** Equal-weight RRF used to demote confident BM25 hits below vector-preferred docs/tests (MiniLM hybrid scored 0.852 on Flask, losing to keyword-only's 0.926). Queries are now classified by shape β€” identifier spellings, quoted code and file paths lean BM25; paraphrases lean semantic (`src/core/query-profile.ts`) β€” and the local-MiniLM hybrid reaches **parity with keyword-only (0.926) on the Flask set while keeping hybrid's edge on paraphrase queries**. The voyage columns predate adaptive fusion (query-time change only; the committed baselines remain the equal-weight reference). Re-run any of it yourself: `oce eval --no-adaptive-fusion` A/Bs the old behavior.

The June baseline this section used to quote (hybrid 0.977) no longer reproduces on today's engine (0.943) β€” drift the harness now catches on every run instead of silently shipping.

Reports record which search mode produced them (`hybrid` vs `keyword-only`), and baseline comparisons warn loudly on a mode mismatch.

**Cross-repo validation:** the bundled set only proves retrieval on this repository. [`eval/external/`](eval/external/) holds gold sets for codebases OCE has never seen β€” starting with Flask 3.1.2 (Python, 27 cases, recall@10 0.926 keyword-only) β€” with committed baselines and a workflow for adding more. A ranking change that wins on `eval/oce.eval.json` should be re-run there before it counts as progress.

A case file is a JSON array (or `{ cases: [...] }`):

```json
{ "id": "rrf-fusion", "query": "where is reciprocal rank fusion implemented", "expectedPaths": ["src/core/retriever.ts"] }
```

Keep your eval sets out of the index by listing their directory in `.contextignore` β€” a gold set maps queries to answers, so indexing it contaminates the rankings it measures.

## πŸ“¦ Requirements

- Node.js 22 or newer (22 and 24 LTS install without a compiler; Node 20 is end-of-life and the SQLite binding no longer ships prebuilt binaries for it)
- A chat model β€” `oce setup` lists free options (Ollama locally, or a free-tier key from Google, Groq, Cerebras, or OpenRouter)
- Embeddings are optional: without them, search runs keyword-only (BM25). For semantic ranking use [Ollama](https://ollama.com) (`nomic-embed-text`, free and local), the optional `@huggingface/transformers` package (`--provider local`), or a Voyage / OpenAI key
- Platforms without a [sqlite-vec](https://github.com/asg017/sqlite-vec) build (e.g. win32-arm64, Alpine) also run keyword-only

## πŸ“„ License

MIT Β© [Gain](https://github.com/dhaxor)

---

<p align="center">
  Built for developers who want their AI to <i>actually understand</i> their code.
</p>