Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ohara

Ohara, with the Tree of Knowledge

ohara is a local-first context lineage engine that indexes a git repo’s commits, diffs, and source symbols and serves them to AI coding assistants through the Model Context Protocol (MCP).

Where Grep answers “where is this code?”, ohara answers two complementary questions about the history of the code:

  • find_pattern — “how was X done before?” Returns historical commits whose diffs resemble a natural-language query, ranked by a three-lane retrieval pipeline (vector + BM25 hunk-text + BM25 symbol-name) → Reciprocal Rank Fusion → cross-encoder rerank → recency tie-break.
  • explain_change — “why does THIS code look the way it does?” Given a file + line range, returns the commits that introduced and shaped those lines, ordered newest-first. Deterministic — backed by git blame, not embeddings.

Design principles

  • Local-first. All indexing, embedding, and retrieval happens on your machine. No cloud calls. The SQLite-based index lives under $OHARA_HOME/<repo-id>/index.sqlite.
  • Stays out of the way. A post-commit hook (installed by ohara init) keeps the index fresh; the --incremental fast path makes that essentially free.
  • Idempotent and abort-safe. Killed mid-index? Resume re-does at most ~100 commits.
  • Single static binary by default. By default ohara is local-first single-process; an opt-in ohara serve daemon is available for warm-cache workflows. Indexing is still always foreground. Distributed via cargo-dist for macOS and Linux (Windows users: WSL).

Where to go next

Status

Released versions: v0.1 (foundation + find_pattern) → v0.2 (auto-freshness) → v0.3 (retrieval pipeline upgrade) → v0.4 (Java + Kotlin support) → v0.5 (explain_change tool) → v0.5.1 (progress bar + abort-resume + self-update) → v0.6 (indexing throughput prep) → v0.7.0–v0.7.5 (evals, perf tracing, memory-efficient indexing, ohara serve daemon + multi-repo). Current: v0.7.5. See the Roadmap.

Install

ohara ships as two static binaries — ohara (the CLI) and ohara-mcp (the MCP stdio server) — built per-platform by cargo-dist and attached to every GitHub release.

Supported platforms

OSArchitectures
macOSApple silicon (aarch64-apple-darwin), Intel (x86_64-apple-darwin)
Linuxaarch64-unknown-linux-gnu, x86_64-unknown-linux-gnu
Windowsnot supported — use WSL

One-shot installer

The recommended path. Downloads the right binary for your platform, drops it on PATH, and writes an install receipt that ohara update later uses for self-update:

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/vss96/ohara/releases/latest/download/ohara-cli-installer.sh | sh

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/vss96/ohara/releases/latest/download/ohara-mcp-installer.sh | sh

Two installers because the CLI and the MCP server are independent artifacts — most users want both, but you can install just one.

Tarball download

If you’d rather not pipe a script:

  1. Open the releases page.
  2. Grab the ohara-cli-* and ohara-mcp-* tarball matching your platform.
  3. Unpack and move the binaries somewhere on PATH (e.g. /usr/local/bin or ~/.local/bin).

Build from source

You need Rust 1.85 or newer (see rust-toolchain.toml). From a clone of the repo:

cargo build --release --workspace

Both binaries land under target/release/.

Build with hardware acceleration

The cargo-dist installer for aarch64-apple-darwin (Apple Silicon) bundles the CoreML execution provider from v0.6.2 onwards — you no longer need to rebuild from source to get hardware acceleration on Apple Silicon. CUDA on Linux still requires a source rebuild:

# Linux x86_64 + NVIDIA — CUDA
cargo build --release --features cuda

# Apple silicon — CoreML (only needed if you want CoreML on a
# from-source build; the released binary already has it)
cargo build --release --features coreml

The features flow through ohara-embed to both ohara and ohara-mcp. Pair the resulting binary with ohara index --embed-provider coreml (or cuda) — or leave it on the default auto, which picks CoreML on Apple silicon, CUDA when CUDA_VISIBLE_DEVICES is set, and CPU otherwise. Default features stay CPU-only.

CoreML on long index runs (auto-downgrade still applies). On Apple Silicon, the CoreML execution path leaks ~4 MB per embed_batch call (MALLOC_LARGE heap, see docs/perf/v0.6.1-leak-diagnosis.md) — a 5,000+ commit first-time index would OOM the host before completing. The released v0.6.2 binary’s --embed-provider auto therefore resolves to CPU on Apple Silicon when the upcoming index pass would walk 1,000 commits or more; short passes (query, index --incremental, small repos) still pick CoreML. Pass --embed-provider coreml explicitly to bypass the downgrade and accept the OOM risk; CPU and CUDA paths are unaffected. Re-opened upstream investigation (fastembed / ort) is tracked for a future release.

Updating

The CLI can self-update in place:

ohara update              # install the latest release
ohara update --check      # report whether a newer version exists
ohara update --prerelease # opt into pre-release tags

ohara update only works when the binary was installed via the curl-pipe-sh installer above — it reads the install receipt that the installer dropped beside the binary. If you built from source or unpacked a tarball by hand, update by re-running the installer (or re-building). The cargo-dist installer also drops a standalone ohara-cli-update script alongside the binary; either entry point works. See ohara update for the full flag set.

Next

Now that the binaries are on PATH, head to the Quickstart to index your first repo, or jump straight to Wiring into MCP clients if you already know the drill.

Quickstart

Five minutes from curl | sh to “Claude can ask the past how I solved this before.” Assumes the install is done and both ohara and ohara-mcp are on PATH.

1. Index a repo

Pick a real repo with some history (the demo is more compelling on something with a few hundred commits than on a fresh git init):

cd ~/code/some-real-repo
ohara index

The first run downloads the BGE-small embedding model (~80 MB, one-time) and then walks every commit, embedding diffs and extracting HEAD-snapshot symbols. On a small repo this finishes in seconds; on a QuestDB-class polyglot codebase it currently takes a while — see the v0.6 throughput RFC.

The index lives at $OHARA_HOME/<repo-id>/index.sqlite (defaults to ~/.ohara/). Nothing leaves your machine.

2. Install the post-commit hook

So the index stays fresh as you commit:

ohara init

This drops a managed block into .git/hooks/post-commit that runs ohara index --incremental after every commit. The hook is wrapped in fence comments (# >>> ohara managed >>># <<< ohara managed <<<), so re-running ohara init is idempotent and your existing hook content is preserved. The hook fails-closed if ohara isn’t on PATH — it never blocks a commit.

If you also want a CLAUDE.md stanza describing the tool:

ohara init --write-claude-md

See ohara init for the full flag set.

3. Run a query

Sanity-check the index from the CLI before wiring into Claude:

ohara query --query "retry with backoff" --k 3

The output is the same JSON envelope the MCP find_pattern tool returns. Each hit carries the commit SHA, message, file path, a truncated diff excerpt, similarity / recency / combined scores, and provenance.

4. Wire into Claude Code

Point Claude at ohara-mcp and you’re done — see Wiring into MCP clients for the claude_desktop_config.json snippet. Both find_pattern and explain_change become available to Claude in any session whose working directory matches an indexed repo.

What next?

Claude Code plugin

The recommended way to use ohara from Claude Code is the official plugin. It registers the ohara-mcp server, ships two skills that teach the model when to reach for lineage queries, and auto-downloads the binary on first use.

For other MCP clients (Cursor, Codex CLI, OpenCode, etc.) see Wiring into MCP clients.

Install

/plugin marketplace add vss96/ohara
/plugin install ohara@vss96
/reload-plugins

That’s it. The plugin’s wrapper (bin/ohara-mcp) downloads the matching ohara-mcp release tarball for your platform on first invocation and caches it at ~/.cache/ohara-plugin/v<version>/. Subsequent starts skip the download.

What the plugin ships

ComponentPurpose
ohara MCP serverRegisters find_pattern and explain_change as MCP tools.
ohara:lineage skillTells Claude when to use find_pattern / explain_change vs Grep — triggers on “how did we do X before?”, “why does this code look this way?”, and prior-art questions.
ohara:indexing skillIndex lifecycle guidance — --incremental vs --force vs --rebuild, and how to recover from each compatibility verdict.

Prerequisites

  • Node.js 18+ — the wrapper that downloads the binary is a small Node script. macOS and most Linux distros ship Node 18+ by default.

  • tar with xz support — default on macOS and Linux. Used to extract the release tarball.

  • An indexed repo. The plugin only ships the MCP server; you index with the ohara CLI separately:

    ohara index <repo-path>
    

Supported platforms

The plugin’s binary download matches the ohara release matrix:

  • macOS aarch64 (Apple Silicon)
  • macOS x86_64 (Intel)
  • Linux x86_64
  • Linux aarch64

Windows is not shipped as a release binary (ort_sys link issue; WSL users can use the Linux x86_64 binary). Other targets need to build ohara-mcp from source and put it on PATH — but at that point you don’t need the plugin’s downloader; manual MCP wiring works just as well.

Updating

When ohara cuts a new release, refresh the marketplace and reinstall:

/plugin marketplace update vss96
/plugin install ohara@vss96

The wrapper’s hard-coded version determines which release tarball gets fetched, so plugin updates and ohara releases stay in lock-step.

Uninstall

/plugin uninstall ohara@vss96
/plugin marketplace remove vss96

The cached binary at ~/.cache/ohara-plugin/ is not removed automatically — rm -rf ~/.cache/ohara-plugin to clean up.

Troubleshooting

“ohara MCP server didn’t start”

Most likely the binary download failed silently. Run the wrapper manually to see the download log:

~/.claude/plugins/marketplaces/vss96/plugins/ohara/bin/ohara-mcp <<< ''

Common causes:

  • No network access on the MCP host
  • Firewall blocking https://github.com/vss96/ohara/releases/...
  • Cached binary corrupted — rm -rf ~/.cache/ohara-plugin/

“find_pattern returns errors mentioning rebuild”

The index was built with an embedder that doesn’t match the binary the plugin just downloaded. Run:

ohara index --rebuild --yes <repo-path>

See index compatibility for the full verdict table — the ohara:indexing skill teaches Claude to surface this command when it happens.

“Plugin loaded but skills don’t trigger”

/reload-plugins after install. Skills are namespaced as /ohara:lineage and /ohara:indexing/help should list them.

What’s in the repo

The plugin lives inside the ohara repository at plugins/ohara/:

plugins/ohara/
├── .claude-plugin/plugin.json
├── .mcp.json                 # registers the ohara stdio MCP server
├── bin/ohara-mcp             # Node wrapper that fetches the binary
├── skills/
│   ├── lineage/SKILL.md
│   └── indexing/SKILL.md
├── package.json
└── README.md

The marketplace catalog at .claude-plugin/marketplace.json makes the ohara repo itself act as a single-plugin marketplace.

Wiring into MCP clients

ohara ships as a stdio MCP server (ohara-mcp). Any MCP-aware client can talk to it. The server exposes two tools — find_pattern and explain_change — backed entirely by the local SQLite index. No network calls, no shared state.

The shape every client uses

The Model Context Protocol standardizes server registration as a small JSON object. Most clients accept this exact shape (some re-spell it slightly):

{
  "mcpServers": {
    "ohara": {
      "command": "/absolute/path/to/ohara-mcp",
      "args": [],
      "env": {}
    }
  }
}

Replace the path with which ohara-mcp after install. The server reads the current working directory of the spawning client session as the repo to query — open the client in an indexed repo and ohara’s tools become available for that repo automatically.

Client-by-client config locations

The exact file path is the only thing that varies. Always check your client’s docs for the canonical answer; the entries below reflect what was current at the time of writing.

Claude Code / Claude Desktop

The recommended path is the Claude Code plugin — one command installs the MCP server, registers two skills, and auto-downloads the binary. Manual wiring (below) is the fallback for custom setups or older Claude Code builds without /plugin support.

  • Global: ~/.claude/claude_desktop_config.json
  • Per-repo: .mcp.json or .claude/mcp.json in the repo root
  • CLI shortcut: claude mcp add ohara /absolute/path/to/ohara-mcp
  • Restart Claude after editing the config.

Cursor

  • Global: ~/.cursor/mcp.json
  • Per-workspace: .cursor/mcp.json in the project root
  • Same JSON shape as above. Cursor discovers MCP servers on startup; reload the workspace after a config change.

OpenAI Codex CLI

Codex CLI uses TOML rather than JSON. Add to ~/.codex/config.toml:

[mcp_servers.ohara]
command = "/absolute/path/to/ohara-mcp"
args = []

The block name (ohara) is the server identifier the model sees in tool selection — keep it short and descriptive.

OpenCode

OpenCode reads either ~/.config/opencode/opencode.json (global) or an opencode.json in the workspace root (per-project). The MCP block nests under an mcp key:

{
  "mcp": {
    "ohara": {
      "type": "local",
      "command": ["/absolute/path/to/ohara-mcp"]
    }
  }
}

Note command is an array (the binary plus any args).

Other / generic MCP clients

Any client that follows the MCP spec accepts the standard JSON shape at the top of this page. If your client supports per-server env overrides, the only env var ohara reads is OHARA_HOME (the index location, defaults to ~/.ohara).

When teammates have ohara-mcp at different paths, commit a .mcp.json (or .cursor/mcp.json, etc.) to the repo with a relative path or a portable command. Per-repo configs override global ones in every client tested above.

What every client gets

Two tools, both deterministic-ish, both read-only against the index:

  • find_pattern — semantic search over git history. Use when the user asks “how have we done X before?”, “is there a pattern for Y?”, or is about to write code that has prior art in this repo.
  • explain_change — git-blame-backed archaeology. Use when the user asks “why does this code look this way?” or wants the commits that shaped a specific file + line range.

The MCP server’s instructions field tells the model when to reach for ohara vs. generic search. Most clients propagate it through tool selection automatically.

_meta.compatibility (v0.7+)

Both tools’ responses now include a _meta.compatibility object reporting whether the opened index was built with the same embedder / chunker / parser / semantic-text versions as the running binary (plan 13). The shape is one of:

{ "status": "compatible" }
{ "status": "query_compatible_needs_refresh", "reason": "<component> mismatch …" }
{ "status": "needs_rebuild",                "reason": "<component> mismatch …" }
{ "status": "unknown",                      "missing_components": ["…"] }

_meta.hint carries the same information as a human-readable sentence so clients that don’t consume the structured field still surface it to the user.

find_pattern refuses to run when the verdict is needs_rebuild (a vector-affecting component differs and KNN would return wrong results); the tool returns an invalid_params error naming the rebuild command. explain_change continues to run under every verdict because git blame doesn’t depend on the embedder / chunker / parser state.

Bootstrapping

Two prerequisites for the MCP server to return useful results:

  1. The repo is indexed. Run ohara index once. Both tools degrade gracefully on an empty index (zero hits + a _meta.hint explaining why) but they’re not interesting until there’s history to look at.
  2. The index stays fresh. Run ohara init to install the post-commit hook. After that, every commit triggers an --incremental re-index — typically sub-second.

Both steps are described in detail in the Quickstart.

Verifying the wiring

In a session inside an indexed repo, ask the model:

Use find_pattern to look up “retry with backoff” in this repo.

If the wiring is good, the model invokes the tool and shows the JSON hits. If it isn’t, the tool simply won’t appear in the tool list. Common causes:

  • Wrong absolute path — which ohara-mcp and recheck.
  • Binary not executable — chmod +x if you unpacked a tarball by hand.
  • Client wasn’t restarted / workspace wasn’t reloaded after the config edit.
  • Working directory on launch doesn’t match a repo that’s been indexed.

find_pattern

Semantic search over a repo’s git history. Answers “how was X done before?” by ranking historical commits whose diffs resemble a natural-language query.

Backed by the three-lane retrieval pipeline (vector KNN + FTS5 BM25 hunk-text + FTS5 BM25 symbol-name) → Reciprocal Rank Fusion → cross-encoder rerank → recency tie-break. See the retrieval pipeline page for the full architecture.

When to use

USE WHEN the user:

  • asks “how did we do X before?” / “is there a pattern for Y?”
  • requests adding a feature similar to existing functionality (“add retry like we did before”, “make this look like the auth flow”)
  • is about to write code that likely has prior art in this repo

DO NOT USE for searching current code — use Grep/Read for that. DO NOT USE for general programming questions.

Input parameters

Schema source: FindPatternInput in crates/ohara-mcp/src/tools/find_pattern.rs.

FieldTypeDefaultDescription
querystringrequiredNatural-language description of the pattern to find.
kinteger5Number of results to return; clamped to 1..=20.
languagestringnullOptional language filter (e.g. "rust", "python", "java", "kotlin").
sincestringnullOptional lower bound on commit age. Accepts ISO date ("2024-01-01"), RFC-3339 datetime, or relative days ("30d").
no_rerankbooleanfalseSkip the cross-encoder rerank stage. Faster, deterministic, slightly less precise on the top result.

Output shape

The tool returns a single JSON document with hits and _meta. Each hit follows the PatternHit shape from ohara-core::query:

{
  "hits": [
    {
      "commit_sha": "a1b2c3d4...",
      "commit_message": "Add exponential backoff to HTTP client",
      "commit_author": "Alex Doe",
      "commit_date": "2024-09-12T14:23:00Z",
      "file_path": "src/http/retry.rs",
      "change_kind": "modified",
      "diff_excerpt": "@@ -10,3 +10,12 @@\n+    let mut delay = base_delay;\n+    for attempt in 0..max_attempts { ...",
      "diff_truncated": false,
      "related_head_symbols": ["http::retry::with_backoff"],
      "similarity": 0.83,
      "recency_weight": 0.94,
      "combined_score": 0.79,
      "provenance": "INFERRED"
    }
  ],
  "_meta": {
    "index_status": {
      "last_indexed_commit": "a1b2c3d4...",
      "commits_behind_head": 0,
      "indexed_at": "2026-04-30T18:11:00Z"
    },
    "hint": null
  }
}

provenance is always "INFERRED"find_pattern is a semantic match, not a deterministic lookup. (For deterministic results see explain_change, where provenance is always "EXACT".)

_meta.hint is populated when the index is empty, stale, or otherwise unable to answer the query usefully — surface it to the user so they know to run ohara index.

Example

Invocation from an MCP client:

{
  "name": "find_pattern",
  "arguments": {
    "query": "retry an HTTP request with exponential backoff",
    "k": 3,
    "language": "rust",
    "since": "180d"
  }
}

The same call from the CLI for debugging:

ohara query --query "retry an HTTP request with exponential backoff" --k 3 --language rust

Add --no-rerank (CLI) or "no_rerank": true (MCP) to skip the cross-encoder stage when latency matters more than top-1 precision.

explain_change

Git-archaeology over a specific file and line range. Two questions in one tool:

  1. “Which commits introduced these lines?” Backed by git blame, exact attribution. Each blame hits[i] carries provenance = "EXACT".
  2. “What nearby changes shaped this area?” Plan 12 enrichment. Contextual commits that touched the same file around the blame anchors, returned under _meta.explain.related_commits with provenance = "INFERRED" so clients don’t confuse them with line-level proof.

Deterministic — backed by git blame + a cheap indexed get_neighboring_file_commits lookup, not embeddings. Companion to find_pattern, which is semantic.

When to use

USE WHEN the user:

  • asks “why does this code look this way?” / “how did this get here?”
  • wants “git archaeology” / “who wrote this?” / “blame this”
  • wants the history of a specific block, function, or line range

DO NOT USE for:

  • searching for similar past patterns — use find_pattern instead
  • inspecting current code — use Grep/Read for that
  • general programming questions

Input parameters

Schema source: ExplainChangeInput in crates/ohara-mcp/src/tools/explain_change.rs.

FieldTypeDefaultDescription
filestringrequiredRepo-relative file path (e.g. src/auth.rs).
line_startinteger11-based start line, inclusive.
line_endinteger01-based end line, inclusive. 0 is a sentinel meaning “end of file” — the server resolves it by reading the workdir checkout.
kinteger5Number of commits to return; clamped to 1..=20.
include_diffbooleantrueInclude diff_excerpt in each hit. Set to false for a tighter response when only the blame attribution matters.
include_relatedbooleanCLI: true / MCP: falsePlan 12 Task 3.2 — attach contextual commits under _meta.explain.related_commits. CLI defaults to on so ohara explain answers include nearby context; MCP defaults to off to keep the response payload predictable.

If only file is provided, the tool explains the whole file (line 1 through end-of-file) with the default k = 5.

Output shape

A JSON document with hits and _meta. Each hit follows the ExplainHit shape from ohara-core::explain:

{
  "hits": [
    {
      "commit_sha": "9f8e7d6c...",
      "commit_message": "Refactor auth: extract token validator",
      "commit_author": "Alex Doe",
      "commit_date": "2024-11-03T09:42:00Z",
      "blame_lines": [42, 43, 44, 45],
      "file_path": "src/auth.rs",
      "diff_excerpt": "@@ -38,6 +38,12 @@ ...",
      "diff_truncated": false,
      "provenance": "EXACT"
    }
  ],
  "_meta": {
    "index_status": { "last_indexed_commit": "9f8e7d6c...", "commits_behind_head": 0, "indexed_at": "2026-04-30T18:11:00Z" },
    "hint": null,
    "explain": {
      "lines_queried": [40, 60],
      "commits_unique": 1,
      "blame_coverage": 1.0,
      "limitation": null,
      "related_commits": [
        {
          "commit_sha": "5a4b3c2d...",
          "commit_message": "auth: add token refresh helper",
          "commit_author": "Bob",
          "commit_date": "2024-10-20T14:11:00Z",
          "touched_hunks": 2,
          "provenance": "INFERRED"
        }
      ]
    }
  }
}

Notes on the _meta.explain block:

  • lines_queried reflects the clamped range (the server clamps line_end to the file’s actual length).
  • blame_coverage is the fraction of queried lines that resolved to a commit the local index knows about. Less than 1.0 means at least one line landed on a SHA older than the current watermark (re-run ohara index to backfill).
  • limitation is a free-form note when the result set is constrained (e.g. “file does not exist in HEAD” or “file was renamed; pre-rename history not reached”).
  • related_commits (plan 12) is the file-scope context list — commits that touched the same file near each blame anchor. These are NOT proof of line-level ownership; they’re labelled provenance = "INFERRED" and capped at 2 commits before + 2 commits after each blame anchor, deduped across anchors. The list is omitted entirely when include_related = false or no neighbouring commits exist.
  • enrichment_limitation is a free-form note when enrichment was constrained (e.g. “no indexed blame anchors — no contextual neighbours available”).

How to read the two response sections

FieldProvenanceMeaning
hits[i]EXACTThis commit introduced these specific lines (git blame).
_meta.explain.related_commits[i]INFERREDThis commit touched the same file in the same time window, but doesn’t necessarily own these lines. Use as context, not proof.

Example

Invocation from an MCP client:

{
  "name": "explain_change",
  "arguments": {
    "file": "src/auth.rs",
    "line_start": 40,
    "line_end": 60,
    "k": 3,
    "include_diff": true
  }
}

The same call from the CLI for debugging:

ohara explain src/auth.rs --lines 40:60 --k 3

Open-ended ranges work too: --lines :42 (start to line 42), --lines 10: (line 10 to end-of-file), no --lines at all (the whole file).

ohara init

Install the post-commit hook (and optionally a CLAUDE.md stanza) so a repo stays auto-indexed after every commit.

The hook is wrapped in fence comments (# >>> ohara managed (do not edit) >>># <<< ohara managed <<<), so re-running ohara init is idempotent — your existing post-commit content is preserved. The hook fails-closed if ohara is not on PATH, so it never blocks a commit.

Usage

ohara init [PATH] [--write-claude-md] [--force]
FlagDefaultDescription
PATH (positional).Path to the repo (or any path inside it — git2::Repository::discover resolves the actual .git dir).
--write-claude-mdoffAlso append/update an “ohara” stanza in CLAUDE.md at the repo root, fenced by <!-- ohara:start --><!-- ohara:end -->.
--forceoffOverwrite an existing post-commit hook even if it lacks the ohara marker fences. Use with care — replaces the whole file.

Examples

Install the hook in the current repo:

ohara init

Install the hook and document the tool for Claude Code in CLAUDE.md:

ohara init --write-claude-md

Install the hook in a specific repo, replacing any existing post-commit content:

ohara init ~/code/some-repo --force

What gets written

The managed post-commit block runs an incremental re-index in the repo root and silently no-ops if ohara is missing:

# >>> ohara managed (do not edit) >>>
# Re-index this repo on every commit. Silently skipped if `ohara` is not on PATH.
if command -v ohara >/dev/null 2>&1; then
  ( cd "$(git rev-parse --show-toplevel)" && ohara index --incremental >/dev/null 2>&1 ) || true
fi
# <<< ohara managed <<<

--write-claude-md adds a short stanza pointing collaborators at find_pattern and explaining how the index stays fresh. Three cases: file missing → write a fresh CLAUDE.md; file present with markers → replace the stanza in place; file present without markers → append the stanza separated by a blank line.

ohara index

Walk a repo’s git history, embed every commit’s diff hunks, and extract HEAD-snapshot symbols into the local SQLite index. Idempotent and abort-safe — see Indexing & abort-resume for the full state machine.

Usage

ohara index [PATH] [--incremental] [--force] [--rebuild --yes] \
            [--commit-batch N] [--threads N] [--no-progress] \
            [--profile] [--embed-provider {auto,cpu,coreml,cuda}] \
            [--resources {auto,conservative,aggressive}]
FlagDefaultDescription
PATH (positional).Path to the repo.
--incrementaloffSkip the indexer (and embedder init) when the storage watermark already points at HEAD. Used by the post-commit hook to make no-op re-indexes nearly free.
--forceoffClear existing HEAD symbol rows and re-extract from scratch. Used after upgrades that change the AST chunker. Wins over --incremental if both are set; commit/hunk history is untouched.
--rebuildoffDestructive. Delete the entire index for this repo and rebuild from scratch. Stronger than --force (which only refreshes HEAD-symbol rows). Used when ohara status reports compatibility: needs rebuild (the binary’s embedder dimension or model differs from what the index was built with). Requires --yes to confirm; conflicts with --incremental and --force.
--yesoffConfirm a destructive operation. Currently only valid alongside --rebuild.
--commit-batchfrom --resourcesCommits per storage transaction. Smaller = less peak RAM and more frequent fsyncs; larger = faster but uses more memory. When unset, --resources picks a value from host core count.
--threadsfrom --resourcesCap the embedder’s ONNX runtime to this many threads (0 = let ort decide, typically CPU count). When unset, --resources picks a value from host core count.
--no-progressoffDisable the progress bar even when stderr is a TTY. Structured tracing::info! events still fire every 100 commits.
--profileoffEmit a single-line JSON PhaseTimings blob on stdout after the run finishes (per-phase wall time + hunk-text inflation). Used by the v0.6 throughput baseline.
--embed-providerfrom --resourcesONNX execution provider for the embedder: auto (default — CoreML on Apple silicon, CUDA when CUDA_VISIBLE_DEVICES is set, else CPU), cpu, coreml, or cuda. CoreML / CUDA require a feature-flagged build; see Install → hardware acceleration.
--resourcesautoResource intensity policy. auto picks --commit-batch / --threads / --embed-provider from host core count. conservative halves the picked batch + thread count; aggressive doubles them. Explicit flags always override the picked plan.

Examples

First-time index of the current repo:

ohara index

Hook-style re-index — fast no-op when HEAD is already indexed:

ohara index --incremental

Force a HEAD-symbol rebuild after upgrading to a new ohara that changed the chunker:

ohara index --force

Full rebuild after an embedder/dimension change (status says needs rebuild):

ohara index --rebuild --yes

Cap embedder threads on a shared box, larger batches for speed:

ohara index --threads 4 --commit-batch 1024

Capture per-phase timings for performance work:

ohara index --profile | tail -1 | jq .

Run the indexer with hardware acceleration on Apple silicon (requires a --features coreml build):

ohara index --embed-provider coreml

Trade off resource intensity against the rest of the box — conservative halves batch + threads, aggressive doubles them:

ohara index --resources conservative
ohara index --resources aggressive --commit-batch 1024   # explicit flag still wins

Output

A summary line on stdout:

indexed: 132 new commits, 487 hunks, 1204 HEAD symbols

Plus structured tracing events on stderr (drive verbosity with RUST_LOG, e.g. RUST_LOG=info). With --profile, a JSON line follows the summary:

{"commit_walk_ms":42,"diff_extract_ms":318,"tree_sitter_parse_ms":0,"embed_ms":1820,"storage_write_ms":210,"fts_insert_ms":0,"head_symbols_ms":540,"total_diff_bytes":482312,"total_added_lines":1842}

Resume safety

Killed mid-walk? The watermark advances every 100 commits inside the indexer. Worst case on resume is re-doing ~100 commits — put_hunks clears any previously-written hunks for those SHAs first, so duplicates never accumulate. See Indexing & abort-resume.

ohara query

Run a find_pattern query from the command line. Useful for sanity-checking an index without going through an MCP client, and for piping ranked hits into jq for ad-hoc analysis.

Returns the same JSON envelope as the MCP tool — see find_pattern for the response shape.

Usage

ohara query [PATH] --query <STRING> [--k N] [--language LANG] [--no-rerank]
FlagDefaultDescription
PATH (positional).Path to the repo.
-q, --queryrequiredNatural-language query string.
-k, --k5Number of results to return.
--languagenullFilter results to a single language (rust, python, java, kotlin).
--no-rerankoffSkip the cross-encoder rerank stage. Faster, deterministic, slightly less precise on the top result. Skips the rerank model download too.

Examples

Top-5 retry-with-backoff matches in the current repo:

ohara query --query "retry with backoff"

Top-3 Rust-only matches, piped through jq:

ohara query --query "exponential retry" --k 3 --language rust | jq '.[].commit_message'

Skip the cross-encoder for a faster, deterministic ranking:

ohara query --query "auth middleware" --no-rerank

Notes

  • The --since filter exposed by the MCP tool (since: "30d", since: "2024-01-01") is not currently surfaced on the CLI.
  • The CLI prints the hits array directly (no _meta envelope) so the output is a JSON array, not the { hits, _meta } document the MCP tool returns.

ohara explain

Run an explain_change query from the command line. Returns the same JSON envelope as the MCP tool (see explain_change) so the result is pipeable into jq.

Usage

ohara explain <FILE> [PATH] [--lines START:END] [--k N] [--no-diff]
FlagDefaultDescription
FILE (positional)requiredRepo-relative path to the file to explain.
PATH (positional).Path to the repo.
--linesfull fileLine range as START:END (1-based, inclusive). Either bound may be omitted — :42 starts at line 1, 10: runs to end-of-file. Omit --lines entirely to explain the whole file.
-k, --k5Number of commits to return; clamped to 1..=20.
--no-diffoffSuppress diff excerpts in the output (only blame attribution and metadata).

Examples

Explain lines 40–60 of src/auth.rs with the top-3 contributing commits:

ohara explain src/auth.rs --lines 40:60 --k 3

Explain the whole file, no diff excerpts:

ohara explain src/auth.rs --no-diff

Open-ended range — line 100 to end of file:

ohara explain src/auth.rs --lines 100:

Pipe the newest contributor SHA into another tool:

ohara explain src/auth.rs --lines 1:50 | jq -r '.hits[0].commit_sha'

Notes

  • Line numbers are 1-based and inclusive on both bounds. Open-ended ranges resolve END = 0 to the file’s actual last line by reading the workdir checkout.
  • Every result has provenance = "EXACT"explain_change is backed by git blame, not embeddings.
  • The _meta.explain.blame_coverage field reports the fraction of queried lines attributed to a known commit. Less than 1.0 means some lines landed on a SHA older than the local watermark — re-run ohara index to backfill.

ohara status

Print the current freshness of a repo’s index: where the watermark is, when it was last updated, and how many commits exist on HEAD that the index hasn’t seen yet.

Usage

ohara status [PATH]
FlagDefaultDescription
PATH (positional).Path to the repo.

Example

ohara status

Sample output:

repo: /Users/alex/code/my-service
id: 9f1a3b2c8d4e5f6a
last_indexed_commit: a1b2c3d4e5f6...
indexed_at: 2026-04-30T18:11:00Z
commits_behind_head: 0
compatibility: compatible

commits_behind_head is computed against the current HEAD — a non-zero value means ohara index --incremental has work to do. <none> for last_indexed_commit / indexed_at means the repo hasn’t been indexed yet; run ohara index.

Compatibility line (v0.7+)

The compatibility line reports whether the index was built with the same embedder / chunker / parser / semantic-text versions the current binary expects (plan 13). One of:

ValueMeaningSuggested action
compatibleEvery recorded component matches the binary.None.
query-compatible, refresh recommended (<reason>)A derived component (chunker, parser, semantic-text, reranker) bumped versions; KNN still works but the derived rows are stale.ohara index --force
needs rebuild (<reason>)A vector-affecting component (embedding model, dimension, schema) differs. KNN against this index would be wrong.ohara index --rebuild
unknown (no metadata for …)Pre-v0.7 index, or freshly-migrated repo before any v0.7+ pass wrote metadata.ohara index --force records current versions.

status does not load the embedder model, so the compatibility check is fast and works on machines without the model cache populated.

Use it from CI

ohara status is cheap (no embedder boot) and machine-readable enough to grep:

behind=$(ohara status | awk '/commits_behind_head/ { print $2 }')
[ "$behind" = "0" ] || ohara index --incremental

ohara update

Self-update the installed ohara binary by checking GitHub Releases for a newer version and replacing the on-disk binary in place.

Backed by axoupdater, the same library that powers cargo-dist’s standalone <app>-update helper. ohara update and ohara-cli-update always agree on what’s latest because they read the same release manifest.

Usage

ohara update [--check] [--force] [--prerelease]
FlagDefaultDescription
--checkoffReport whether a newer version exists without installing it. Exits 2 if an update is available, 0 if up-to-date.
--forceoffAllow downgrades / re-installs of the same version. Off by default — running ohara update on the latest version is a no-op.
--prereleaseoffInclude pre-release tags when looking for “latest”. Default is stable-only.

Examples

Install the latest stable release:

ohara update

Just check, don’t install:

ohara update --check

Re-install the same version (e.g. after a corrupted binary):

ohara update --force

Requirements

ohara update only works when the binary was installed via the curl-pipe-sh installer described on the Install page. It locates the install receipt the installer dropped beside the binary; without that receipt it fails with a clear error.

If you built from source or unpacked a tarball by hand, “update” by re-running the installer or rebuilding from the tagged release.

Workspace overview

ohara is a Cargo workspace of seven library/binary crates plus an out-of-band perf harness. Crate boundaries follow the rule keep the core git-free: the only crates that depend on git2 or tree-sitter are the adapters (ohara-git, ohara-parse); everything else talks to them through async_trait ports defined in ohara-core.

Dependency direction

            ┌────────────┐
            │  ohara-cli │  ohara binary (commands/*)
            └─────┬──────┘
                  │ uses
   ┌──────────────┼─────────────┐
   ▼              ▼             ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ohara-mcp │ │ohara-git │ │ohara-parse│
└────┬─────┘ └────┬─────┘ └────┬─────┘
     │            │ adapters   │
     │            ▼            ▼
     │      ┌────────────────────────┐
     ├────► │      ohara-core        │  traits + orchestration
     │      └─────────┬──────────────┘
     │                │ uses
     │                ▼
     │      ┌──────────────────┐  ┌────────────┐
     └────► │ ohara-storage    │  │ ohara-embed│
            │ (sqlite + vec +  │  │ (fastembed)│
            │  fts5)           │  │            │
            └──────────────────┘  └────────────┘

ohara-mcp is the second binary (ohara-mcp); it composes the same core + storage + embed + git + parse stack the CLI uses.

Crates

ohara-core

The orchestration layer. Defines Indexer, Retriever, the explain_change orchestrator, and the async_trait ports they talk through (Storage, EmbeddingProvider, RerankProvider, CommitSource, SymbolSource, BlameSource, CommitsBehind, ProgressSink). Knows nothing about git or tree-sitter — those are hidden behind the traits, which keeps the core unit-testable with in-memory fakes.

ohara-storage

SQLite + sqlite-vec + FTS5 backend. Owns the on-disk index format, the refinery migrations under migrations/V*.sql, and the implementations of every storage trait the core declares. See Storage schema.

ohara-embed

Local embedding + cross-encoder reranker via fastembed-rs. Wraps the BGE-small embedding model (~80 MB, 384-dim) and bge-reranker-base (~110 MB) behind the EmbeddingProvider and RerankProvider traits. First call downloads the model; subsequent calls hit the local cache.

ohara-git

git2-backed implementations of CommitSource, BlameSource, and CommitsBehind. The only crate (besides the CLI’s repo-discovery helper) that opens a real git repo. Walks history, extracts hunks, runs blame_file.

ohara-parse

tree-sitter extractors for Rust, Python, Java, and Kotlin. Implements SymbolSource for HEAD-snapshot symbol extraction and applies the AST sibling-merge chunker (Plan 3 / Track C) to keep chunks under a 500-token budget. See Language support.

ohara-cli

The ohara binary. One subcommand per file under src/commands/: init, index, query, explain, status, update. Each command builds the same core/storage/embed/git/parse stack.

ohara-mcp

The ohara-mcp binary. Hosts the OharaService with the find_pattern and explain_change tools (see MCP tool reference) and serves them over stdio via rmcp.

tests/perf

Out-of-band perf harness — a workspace member but not a published crate. Used to capture the v0.6 baseline numbers (see docs/perf/v0.6-baseline.md) and to A/B retrieval-quality tweaks without polluting the main crates’ [dev-dependencies].

Reading order

If you’re new to the codebase, follow the data flow:

  1. Indexing & abort-resume — what ohara index does end-to-end.
  2. Storage schema — what lands in SQLite.
  3. Retrieval pipeline — how a query becomes a ranked list of hits.
  4. Language support — how symbols get extracted.

Retrieval pipeline

find_pattern is implemented as a four-stage pipeline: three parallel retrieval lanes → Reciprocal Rank Fusion → cross-encoder rerank → recency tie-break. This is the v0.3 architecture (Plan 3); the v0.1–v0.2 linear-blend formula was deleted in the same release.

Architecture

find_pattern(query, k=5)
    │
    ├─► vector top-100         (sqlite-vec on vec_hunk, BGE-small)
    ├─► FTS5 hunk-text top-100 (BM25 on hunk body)
    └─► FTS5 symbol-name top-100 (BM25 on tree-sitter symbol names)
    │
    ▼
Reciprocal Rank Fusion (k = 60) → top-50 candidates
    │
    ▼
Cross-encoder rerank (bge-reranker-base, opt-out via --no-rerank)
    │
    ▼
Recency tie-break → top-K → response

Stage 1: three retrieval lanes

Dispatched in parallel via tokio::join! from the Retriever:

  • Vector KNN. Embed the query with BGE-small (384-dim), then run a sqlite-vec k-NN search against the vec_hunk table. Top-100.
  • BM25 hunk-text. FTS5 BM25 over fts_hunk_text(content) — raw diff body. Top-100.
  • BM25 symbol-name. FTS5 BM25 over fts_symbol_name(kind, name, sibling_names) — tree-sitter symbol identifiers from HEAD. Top-100.

Each lane returns a best-first list of HunkIds.

Stage 2: Reciprocal Rank Fusion

Combine the three rankings with classic RRF (Cormack et al. 2009):

score(h) = Σ over lanes of  1 / (k_rrf + rank_in_lane(h))
where rank_in_lane is 1-based, k_rrf = 60

Hunks absent from a lane contribute 0 from that lane. Ties are broken deterministically by first-appearance order across the input rankings. Implemented in ohara_core::query::reciprocal_rank_fusion — a pure function over Vec<HunkId> that’s straightforward to unit-test.

The top 50 fused candidates feed the next stage.

Stage 3: cross-encoder rerank

bge-reranker-base (~110 MB ONNX, CPU-only) scores each (query, hunk_text) pair pointwise. The model downloads on first use and is cached locally.

Opt-out: find_pattern accepts no_rerank: true (MCP) or --no-rerank (CLI). When opted out, the Retriever skips the model download entirely and returns the post-RRF order, with the recency multiplier still applied. Useful when latency matters more than top-1 precision.

Stage 4: recency tie-break

A small multiplicative nudge applied after the cross-encoder score:

final_score = rerank_score * (1.0 + recency_weight * recency_factor)
where recency_factor = exp(-age_days / 90.0)   // 1.0 today → ~0.37 at 90 days
      recency_weight = 0.05                    // RankingWeights default

Recency does not feed into RRF or the cross-encoder score directly — relevance dominates, recency only nudges within a tight relevance band.

Why this shape

All three of Augment, Cursor, and Cody converged on the same pattern: multi-stage retrieve → cross-encoder rerank, hybrid sparse + dense retrieval before rerank, and AST-aware chunking that merges siblings up to a token budget. v0.3 ports those three choices into ohara, scaled to a local-first single-binary deployment. The v0.3 retrieval design spec has the full research summary and the rationale for each parameter.

Storage schema

ohara persists everything in a single SQLite file at $OHARA_HOME/<repo-id>/index.sqlite (default ~/.ohara/). The file loads three SQLite extensions:

  • sqlite-vec for the vec_* virtual tables (k-NN over float32 embeddings).
  • FTS5 (built into SQLite) for the fts_* virtual tables.
  • Refinery for forward migrations under crates/ohara-storage/migrations/V*.sql.

Tables

V1 — initial schema

TableKindPurpose
reponormalOne row per indexed repo: id, path, first commit, watermark, indexed_at, schema version.
commit_recordnormalOne row per commit: SHA, parent SHA, merge flag, unix ts, author, message. Indexed on ts.
file_pathnormalInterned file path strings + detected language + active flag.
symbolnormalHEAD-snapshot symbols: file, kind, name, qualified_name, line span, blob SHA, source_text.
hunknormalOne row per (commit, file, hunk): commit SHA, file path id, change kind, raw diff_text. Indexed on (file_path_id, commit_sha) and commit_sha.
blob_cachenormalTracks which blob SHAs have been embedded by which model — keeps re-indexes idempotent.
vec_hunkvec0sqlite-vec table: hunk_id → diff_emb FLOAT[384] (BGE-small).
vec_commitvec0commit_sha → message_emb FLOAT[384].
vec_symbolvec0symbol_id → source_emb FLOAT[384].
fts_commitFTS5BM25 over commit messages.
fts_symbolFTS5BM25 over (qualified_name, source_text).

V2 — three-lane retrieval (v0.3)

Table / columnKindPurpose
symbol.sibling_namesnew columnJSON array of sibling-symbol names from the AST sibling-merge chunker. Defaults to '[]' for v0.2-era rows.
fts_hunk_text(hunk_id, content)FTS5BM25 lane over raw hunk body — feeds the second retrieval lane in find_pattern.
fts_symbol_name(symbol_id, kind, name, sibling_names)FTS5BM25 lane over tree-sitter symbol names — feeds the third retrieval lane.

V2 backfills both new FTS tables from existing hunk and symbol rows on first run, so a v0.3 binary opening a v0.2 index is searchable immediately without re-embedding.

V3 — index metadata (v0.7, plan 13)

Table / columnKindPurpose
index_metadata(repo_id, component, version, value_json, recorded_at)new tablePer-component record of how the index was built. Lets the runtime detect when an old index is incompatible with the current binary’s embedder / chunker / parser / semantic-text versions and decide between “fine”, “refresh recommended”, and “rebuild required”.

Initial component keys: schema, embedding_model, embedding_dimension, reranker_model, chunker_version, semantic_text_version, plus one parser key per language (parser_rust, parser_python, parser_java, parser_kotlin).

V3 backfills only (repo_id, 'schema', '3', …) for every existing repo. Other components stay absent — the runtime reports them as Unknown rather than guessing what version the prior pass used. A subsequent index pass writes the current binary’s values into the table at successful completion.

Idempotency

Indexing is structured so any pass can be repeated without duplicating data:

  • put_commitINSERT OR REPLACE on the commit record + the vec_commit and fts_commit rows.
  • put_hunks — DELETE-then-INSERT scoped by commit_sha. Any previously-written hunks (and their vec_hunk / fts_hunk_text rows) for that SHA are cleared first, so a re-index of the same commit produces exactly the same row count.
  • put_head_symbols — replaces the entire symbol / vec_symbol / fts_symbol / fts_symbol_name content for the repo. HEAD is a snapshot, not history; partial updates would leak stale symbols across commits.

Watermark + abort safety

repo.last_indexed_commit is the watermark. The Indexer advances it every 100 commits during the walk (and once at the end of the pass). Combined with put_hunks’s DELETE-then-INSERT semantics, the worst-case cost of a Ctrl-C / kill / crash mid-walk is re-doing ~100 commits — already-written hunks are cleared and re-inserted identically, so no duplicate rows accumulate.

Schema version (repo.schema_version) is bumped when migrations land. A v0.3 binary on a v0.2 index runs migration V2; a v0.2 binary on a v0.3 index aborts with a clean error rather than risking a downgrade.

Source layout

Storage code lives under crates/ohara-storage/src/. As of v0.6 the crate is split into two submodules: tables/ (one file per table — commit, hunk, symbol, repo, blob_cache, plus the FTS / vec mirror tables) and codec/ (row codecs like change_kind and file_path interning, factored out of the table modules so the mapping is shared and unit-tested in one place). Internal-only — no public API change.

Why SQLite

A single static binary that survives cp index.sqlite and rsync across machines was the constraint. SQLite + sqlite-vec + FTS5 hit that without a daemon, and the DB is small enough on real-world repos (~100–200 MB on QuestDB-class history) that backup is just a file copy.

Language support

ohara extracts HEAD-snapshot symbols (classes, methods, functions, records, sealed types, …) from source files using tree-sitter. The extracted symbols feed the vec_symbol, fts_symbol, and fts_symbol_name tables and contribute one of the three retrieval lanes in find_pattern.

Hunks (the diff bodies that drive the other two retrieval lanes) are language-agnostic — they’re extracted by git2 regardless of the file type, so even unsupported languages still appear in find_pattern results via the BM25 hunk-text and vector lanes. Symbol extraction is the language-specific piece.

Supported languages

LanguageSinceNotes
Rustv0.1Functions, methods, structs, enums, traits, impls.
Pythonv0.1Classes, methods, top-level functions.
Java 17+v0.4Classes (incl. sealed), interfaces, records, enums, methods.
Kotlin 1.9 / 2.0v0.4Classes (incl. sealed), data classes, objects + companion objects, interfaces, top-level + member functions.
TypeScriptv0.8.ts, .tsx. Functions, classes, methods, arrow-function consts, interfaces, type aliases, enums.
JavaScriptv0.8.js, .jsx, .mjs, .cjs. Functions, classes, methods, arrow-function consts.

Java + Kotlin specifics

The v0.4 release was specifically designed to make ohara useful on Spring-flavored codebases, which means:

  • Sealed types and records (Java 17+) and data classes / objects / companion objects (Kotlin) are first-class symbol kinds.
  • Annotations stay inside source_text. Decorators like @RestController, @Service, @Component, @SpringBootApplication, and Kotlin’s @Composable / @Serializable are preserved verbatim in the symbol’s source_text field. That means embeddings and BM25 both pick up Spring-style markers without any new query syntax — a query like “REST controller for user signup” matches symbols annotated with @RestController directly.

TypeScript + JavaScript specifics

The v0.8 release adds first-class symbol extraction for the JS/TS ecosystem via the tree-sitter-typescript 0.23 and tree-sitter-javascript 0.23 grammars. Both grammars are loaded through the tree-sitter-language ABI shim so they keep working as the host tree-sitter runtime advances.

  • TypeScript dispatches on .ts and .tsx; the latter is parsed with the TSX dialect of the grammar so JSX expressions inside .tsx components don’t derail symbol extraction.
  • JavaScript dispatches on .js, .jsx, .mjs, and .cjs. JSX inside .jsx parses cleanly via the same grammar.
  • Symbols extracted (both languages): function declarations, class declarations, methods (including constructor, get/set, static, and async), and arrow-function consts (export const Foo = (…) => {…}) — the last one matters because the modern React/Next idiom expresses components and hooks as arrow-bound consts rather than function declarations.
  • Symbols extracted (TypeScript only): interface declarations, type aliases, and enum declarations.
  • Intentionally not extracted yet: decorators (preserved inside source_text like Java/Kotlin annotations, but not surfaced as separate symbols), ambient declarations in .d.ts files (the file extension dispatches normally but declare blocks aren’t symbolized), and namespace / module declarations (TS-only legacy module syntax). These are tracked on the roadmap.

Chunking

All six languages share the v0.3 AST sibling-merge chunker (implemented in ohara-parse): depth-first traversal of the tree-sitter parse tree, accumulate sibling nodes into the current chunk while the running token total stays under a 500-token budget (token ≈ char_count / 4 for chunking decisions). When a single node exceeds the budget, emit it alone — preserves semantic locality at the cost of larger chunks for occasional huge functions.

The chunker also records sibling-symbol names in a JSON-encoded sibling_names column on each symbol row. That column feeds the fts_symbol_name BM25 lane, so a query for “X” matches not just symbols literally named X but symbols that share a parent with one.

As of v0.6 the per-language extractors live under crates/ohara-parse/src/languages/ (one module per language) — same behavior, tighter directory tree.

Grammar and parser versions

Plan-18 (v0.7) modernized the tree-sitter stack: the workspace tracks tree-sitter 0.25, the rust/python/java grammars were bumped to their current upstream releases (rust 0.21 → 0.24, python 0.21 → 0.25, java 0.21 → 0.23), and the kotlin grammar was swapped from the abandoned fwcd/tree-sitter-kotlin to the more-recently-maintained tree-sitter-grammars/tree-sitter-kotlin-ng fork.

All four parser_versions bumped from "1" to "2" in lockstep, so indexes built before v0.7 receive a query_compatible_needs_refresh verdict per plan-13 (docs/superpowers/plans/2026-05-02-ohara-plan-13-index-metadata-and-rebuild-safety.md): queries still work, but the symbol/hunk derived rows can be out-of-date. Recovery is ohara index --force, which re-walks HEAD symbols and rewrites derived rows without touching the embedding vectors (model + dimension are unchanged).

Future languages

Tracked on the Roadmap. New languages are mostly a matter of adding a tree-sitter grammar, a node-kind → symbol-kind mapping, and a fixture file to the per-language unit tests in crates/ohara-parse/. Annotations / decorators should follow the “keep them in source_text” rule so the retrieval-side query story stays consistent.

Indexing & abort-resume

ohara index is the only writer to the SQLite index. This page walks through what it does end-to-end, the two fast-paths (--incremental and --force), and the abort-resume contract.

End-to-end

A full pass, in order:

  1. Resolve repo id. Hash the canonical repo path + the SHA of the first commit on HEAD. Yields a stable repo_id used as the directory name under $OHARA_HOME/.
  2. Open / migrate the index. SqliteStorage::open runs any pending refinery migrations (migrations/V*.sql) before returning.
  3. List new commits. CommitSource::list_commits(after = watermark) walks git log from the storage watermark to HEAD. On a fresh index the watermark is None and the walker returns every commit.
  4. For each commit (batched by --commit-batch, default 512):
    1. Extract hunks via CommitSource::hunks_for_commit.
    2. Embed [commit_message, hunk_1.diff_text, …] in a single embed_batch call.
    3. put_commit (commit row + vec_commit + fts_commit).
    4. put_hunks (hunk rows + vec_hunk + fts_hunk_text, DELETE-then-INSERT scoped by commit_sha).
    5. Every 100 commits, advance repo.last_indexed_commit and emit a tracing::info! progress event.
  5. Extract HEAD symbols. SymbolSource::extract_head_symbols walks the working tree, parses each supported file with tree-sitter, runs the AST sibling-merge chunker, and produces one Symbol per chunk.
  6. put_head_symbols. Replaces the entire symbol / vec_symbol / fts_symbol / fts_symbol_name content for the repo (HEAD is a snapshot, not history).
  7. Final watermark advance. Set last_indexed_commit to the newest commit walked.

Chunk-level embed cache (--embed-cache)

ohara index can be told to cache embeddings keyed by the content the embedder consumes, so identical chunk content costs one embed call across the entire history rather than one per occurrence.

Three modes:

  • off (default) — no cache; today’s behavior.
  • semantic — cache keyed by sha256(commit_msg + diff_text); embedder input unchanged. Hit rate is driven by exact (message, diff) repeats — cherry-picks, reverts. Conservative.
  • diff — cache keyed by sha256(diff_text); embedder input changes to diff_text only (commit message dropped from the vector lane). Hit rate is much higher (vendor refreshes, mass renames). The vector lane specialises in diff-similarity; commit messages remain indexed via the existing fts_hunk_semantic BM25 lane.

off and semantic are vector-equivalent (both embed the same input). diff produces a different vector lane; switching into or out of it requires --rebuild.

The cache lives in the same SQLite DB as vec_hunk and is bounded by unique(content_hash, embed_model). No eviction in v1.

Usage:

ohara index --embed-cache semantic ~/code/big-repo
ohara status ~/code/big-repo   # shows embed_cache: semantic (… KB)

Path-aware indexing — .oharaignore

ohara consults a layered ignore filter at index time. Three sources are merged, with the user layer winning so !negate patterns work:

  1. Built-in defaults (compiled into ohara-core) — lockfiles, node_modules/, target/, vendor/, dist/, etc.
  2. .gitattributes — paths flagged linguist-generated=true or linguist-vendored=true.
  3. .oharaignore at repo root — gitignore-syntax, team-shared.

Run ohara plan to survey a repo’s commit-share hotmap and write a suggested .oharaignore. The planner runs a paths-only libgit2 walk (seconds-to-minutes even on giant repos), groups commits by top-level directory, and proposes ignoring high-share directories outside a small documentation allowlist.

When a commit’s changed paths are 100% ignored, the indexer skips it entirely (no rows written) but advances last_indexed_commit past it, so --incremental runs work normally.

--incremental fast path

Used by the ohara init post-commit hook and any caller that wants a no-op re-index to be cheap.

Before booting the embedder (which costs hundreds of milliseconds even when the model is cached), ohara index --incremental reads repo.last_indexed_commit and compares to HEAD. If they match, it prints index up-to-date at <sha> and returns immediately — no embedder init, no walker boot, no SQLite write transaction.

When they don’t match, the pass proceeds normally and walks just the new commits.

--force rebuild path

Clears existing HEAD symbol rows before the pass and re-extracts from scratch. Used after upgrading to a new ohara that changed the AST chunker (e.g. the v0.3 sibling-merge chunker would otherwise produce duplicate symbols when run over a v0.2-era index).

--force only touches HEAD symbols. Commit and hunk history are untouched — they’re append-only and embed-stable. --force wins over --incremental if both flags are set.

Index compatibility (v0.7)

A v0.7 index records per-component metadata in the index_metadata table at the end of every successful pass: embedding model, embedding dimension, reranker model, AST chunker version, semantic-text version, schema version, and one parser version per language. On every CLI / MCP invocation the runtime builds the same snapshot from constants and compares the two. The verdict is one of:

VerdictMeaningWhat it gates
compatibleEvery recorded component matches the binary.Nothing — proceed.
query-compatible, refresh recommendedA derived component bumped (chunker, parser, semantic-text, reranker). KNN still works because the vectors are unchanged; the derived rows are stale.ohara index --force to refresh derived rows.
needs rebuildA vector-affecting component differs (embedding model, dimension, schema). KNN against this index would return wrong results.ohara index --rebuild to drop and rebuild. find_pattern MCP refuses to run; explain_change continues because blame doesn’t use vectors.
unknownPre-v0.7 index, or freshly migrated before any v0.7+ pass wrote metadata.ohara index --force records current versions; future runs become compatible.

--force vs --rebuild:

  • --force refreshes derived symbol/chunker outputs without touching the commit/hunk/vector history. Cheap; safe to re-run.
  • --rebuild deletes the entire index and rebuilds from scratch. Slow and destructive; requires --yes to confirm. Use only when the verdict is needs rebuild.

Component-version constants live in their owning crates: ohara_parse::CHUNKER_VERSION

  • parser_versions(), ohara_embed::DEFAULT_MODEL_ID / DEFAULT_DIM / DEFAULT_RERANKER_ID, and ohara_core::index_metadata::{SCHEMA_VERSION, SEMANTIC_TEXT_VERSION}. Bump a constant when its owning code’s output semantics change in a way that would invalidate previously-indexed rows.

Abort-resume contract

The watermark advances every 100 commits during the commit walk. That, plus put_hunks’s DELETE-then-INSERT semantics, gives the contract:

  • A Ctrl-C / kill / crash mid-walk loses at most ~100 commits of progress.
  • Re-running ohara index after an abort re-does those ≤ 100 commits. The DELETE step in put_hunks clears any partially-written hunks for those SHAs first, so no duplicate rows accumulate.
  • Anything outside the commit walk (HEAD-symbol extraction, the final watermark advance) is small enough to redo cleanly.

The watermark is a single SHA, so on resume the indexer also short-circuits per-commit when commit_record already has a row for the SHA being walked (v0.6.3). This matters on merge-heavy histories: git2::Revwalk::hide(watermark) only excludes the watermark and its strict ancestor chain, so commits reachable via a different parent path — feature-branch merges, octopus merges, history rewrites — would otherwise be re-walked and re-embedded even though they’re already in the index. A sub-millisecond PK lookup avoids that wasted embedder cost. See docs/superpowers/specs/2026-05-02-ohara-v0.6.3-resume-skip-rfc.md for the design.

Profiling

Pass --profile to dump a single-line JSON PhaseTimings blob on stdout after the run. Captures per-phase wall-time (commit_walk_ms, diff_extract_ms, embed_ms, storage_write_ms, head_symbols_ms, …) and the hunk-text inflation diagnostic (total_diff_bytes / total_added_lines). The numbers feed the v0.6 throughput baseline; see docs/perf/v0.6-baseline.md for the template.

v0.6 indexer knobs

A few v0.6 additions worth singling out — the full flag reference is on ohara index:

  • --embed-provider {auto,cpu,coreml,cuda}. Picks the ONNX execution provider for the embedder. auto (default) chooses CoreML on Apple silicon, CUDA when CUDA_VISIBLE_DEVICES is set, and CPU otherwise. CoreML / CUDA require a feature-flagged build — see Install → hardware acceleration; the published cargo-dist binaries are CPU-only.
  • --resources {auto,conservative,aggressive}. A small lookup table that picks reasonable --commit-batch / --threads / --embed-provider defaults from the host’s logical core count. Explicit flags always override the picked plan, so --resources aggressive --commit-batch 256 is meaningful.
  • --profile. Already covered above — emits the per-phase PhaseTimings JSON used by the throughput baseline.
  • Pinned progress bar. The CLI now wires tracing-indicatif into its tracing subscriber, so the indexer’s progress bar stays pinned to the bottom of the terminal while tracing::info! events stream above it. No more “log line scrolled the bar away.”

Parallel commit pipeline (--workers)

ohara index runs a multi-worker pipeline by default:

  • A walker task enumerates HEAD-reachable commits.
  • N worker tasks each pull a commit and run the full pipeline (hunk_chunk → attribute → embed → persist) end-to-end.
  • A bounded mpsc channel (capacity = N) provides backpressure.

Each commit gets a deterministic ULID derived from (commit_time, commit_sha). Persistence is order-free; the read path recovers chronological order via ORDER BY ulid. Resume falls back to plan-9’s commit_exists skip — already-indexed commits are dropped from the walker output.

Default: --workers $(num_cpus). Use --workers 1 for the serial path (matches today’s behavior; useful for debugging). The big speedup shows up when the chunk-embed cache (--embed-cache=semantic|diff, plan-27) is warm — most chunks then become cache lookups and parse becomes the dominant per-commit cost, fanning out across workers.

Known limits

ohara index is currently single-process. On large polyglot codebases the embed phase saturates a few cores for a burst, then drops to single-core for the SQLite/FTS5 tail — making this fast without regressing retrieval quality is the v0.6 throughput RFC.

Contributing

ohara is a small, focused project. Contributions are welcome — bug reports, doc fixes, perf work, and new language support all land through the same flow.

Build from source

You need Rust 1.85 or newer (the toolchain is pinned in rust-toolchain.toml so rustup picks it up automatically). From a fresh clone:

cargo build --workspace
cargo test --workspace

The release binaries land at target/release/{ohara,ohara-mcp} after cargo build --release --workspace.

TDD style: red/green commits per task

The codebase uses a strict test-driven loop. Plans (under docs/superpowers/plans/) are broken into small tasks; each task lands as two commits:

  1. Red. Write the test that pins the contract. Commit before implementing — the test should fail.
  2. Green. Implement just enough for the test to pass. Commit the implementation separately.

This keeps the diff for any one change small and the intent unambiguous in git log. The commit-per-step rule is documented in memory/feedback_commit_granularity.md.

Where specs and plans live

  • docs/superpowers/specs/ — design specs (one per release). The spec is written first, gets reviewed, and then a matching plan is drafted.
  • docs/superpowers/plans/ — implementation plans. Each plan enumerates tasks; each task has a clear deliverable, test, and exit criteria. Plans are the authoritative source for “what should this PR contain?”.
  • docs/perf/ — perf baselines and A/B notes (e.g. the v0.6 throughput baseline).

When in doubt, find the relevant spec and plan and follow the commit cadence they imply.

Test layout

  • Unit tests live alongside the code they test, in #[cfg(test)] mod blocks. Used for pure-function contracts and trait edge cases.
  • Integration tests live under each crate’s tests/ directory. Used when the test needs to exercise a real Storage or git repo.
  • Cross-crate end-to-end tests live under the workspace root’s tests/ directory.
  • Perf harness lives at tests/perf/ (a workspace member, not a published crate). Reserved for benchmarks the main crates’ [dev-dependencies] shouldn’t depend on.

Always run cargo test --workspace before opening a PR.

CI workflows

Three GitHub Actions workflows under .github/workflows/:

  • release.yml — cargo-dist-driven release pipeline. Builds the per-platform binaries, generates the curl-pipe-sh installer, and attaches both to the GitHub Release on every tag.
  • docs.yml — builds this mdBook site and deploys to GitHub Pages on every push to main.
  • perf.yml — runs the tests/perf harness on a schedule + on PRs that touch retrieval-quality code paths.

main is branch-protected: PRs require green CI before merge, and direct pushes are rejected.

Pull request etiquette

  • Reference the spec / plan / issue your change implements in the PR description.
  • Keep PRs small. If a plan task takes more than a couple of red/green commits, split it.
  • Don’t bundle unrelated changes — even small refactors get their own PR so they’re easy to revert if regression hunts pin them.
  • Run cargo fmt --all and cargo clippy --workspace --all-targets locally before pushing.

Roadmap

Released

VersionThemeHeadline
v0.1FoundationPlan 1 — workspace + SQLite/sqlite-vec/FTS5 schema + find_pattern MCP tool.
v0.2Auto-freshnessPlan 2 — ohara init post-commit hook + ohara index --incremental fast path.
v0.3Retrieval qualityPlan 3 — three-lane retrieval (vector + BM25 hunk-text + BM25 symbol-name) → RRF → cross-encoder rerank → recency tie-break. AST sibling-merge chunking.
v0.4Java + KotlinPlan 4 — Java 17/21 (sealed types, records) and Kotlin 1.9/2.0 (data classes, objects). Annotations preserved in source_text for Spring-friendly retrieval.
v0.5explain_changePlan 5 — second MCP tool, deterministic git-blame-backed lookup of “why does THIS code look the way it does?”.
v0.5.1PolishProgress bar, abort-resume hardening, ohara update self-update via axoupdater.
v0.6.0Throughput prep--profile PhaseTimings JSON, --embed-provider auto-detect, --resources policy, CoreML/CUDA feature wiring, resume-crash fix, pinned progress bar.
v0.7.0Evals + attributionPlan 10/11/13 — eval harness, historical symbol attribution, index metadata + rebuild safety.
v0.7.2Perf tracingPlan 14 — phase tracing, per-method storage metrics, perf harness binaries.
v0.7.3Memory-efficient indexingPlan 15 — embed_batch chunking + source-text cap + peak-RSS sampler.
v0.7.4Submodule fixGitlink-skip in file_at_commit for uninitialized submodules.
v0.7.5Daemon + multi-repoPlan 16 — ohara serve daemon, RetrievalEngine, multi-repo support, ohara daemon subcommands.

The Changelog has a per-tag breakdown.

In flight

v0.7.x — TypeScript / JavaScript support (plan-17)

The next active plan adds a tree-sitter grammar for TypeScript and JavaScript so find_pattern and explain_change work on TS/JS repos without requiring a separate indexing step. See docs/superpowers/plans/2026-05-04-ohara-plan-17-typescript-javascript.md.

Considered for later

These come from the v0.3 spec’s “Out of scope” list and the v0.6 RFC’s deferred items. Not committed; revisited as evidence accrues.

  • LLM-distilled commit summaries — replace commit_msg + first_hunk embedding with a “primary goal / key files / technical terms” summary against a local model. Wait until A/B against the v0.3 baseline shows it matters.
  • Bit-quantized vec index — premature at our scale today; revisit once cold-index size matters more than retrieval latency.
  • Branch-reachability filter — useful for users who switch branches often. Currently find_pattern returns hits across all reachable history.
  • Merkle file hashing (Cursor-style) — only meaningful when ohara expands beyond commit history into current-state code retrieval.
  • HyDE query rewriting — generate a hypothetical answer with a local LLM, embed that, and retrieve against the hypothetical. Mixed evidence in benchmarks; A/B before committing.
  • Query understanding pre-pass — wait for evidence that raw-query embeddings underperform.
  • Task-specific retrieval profiles — only meaningful once ohara has more than two MCP tools.
  • More languages — Go, TypeScript, C# are the obvious next candidates. Mostly a tree-sitter grammar pin + a small node-kind → symbol-kind mapping.
  • Branch / PR-aware explain_change — currently blame walks HEAD. Walking a feature branch’s range against main is a natural extension.

Changelog

User-facing release notes. The full commit log lives on GitHub; this page is the highlights.

v0.7.1 — Release CI token scope

No user-facing binary changes. Fixes GitHub Actions GITHUB_TOKEN permissions so cargo-dist can create a GitHub Release when the tagged commit touches .github/workflows/** (workflows: write alongside contents: write; see cli/cli#9514).

v0.7.0 — Retrieval-quality eval, semantic-text + per-hunk symbol attribution, query intent, explain enrichment, index compatibility

Four plans land together: a regression-tripwire eval harness (plan 10), historical per-hunk symbol attribution + a normalized semantic-text representation (plan 11), rule-based query understanding + contextual explain_change enrichment (plan 12), and an index-metadata + rebuild-safety layer that keeps old indexes from silently producing wrong results (plan 13).

Retrieval

  • Per-hunk symbol attribution. A new hunk_symbol table records which symbols inside a file each hunk actually touched. bm25_hunks_by_historical_symbol is the new primary symbol retrieval lane; the v0.3 file-level lane is kept as fallback for pre-v0.7 indexes. Two confidence kinds: ExactSpan (a parsed symbol’s line span intersects the hunk’s @@ post-image range, derived from the post-image source via the new extract_atomic_symbols parser entry-point) and HunkHeader (git’s @@ line includes an enclosing function/class). Solves the file-level lane’s wrong-hunk problem: a query for retry_policy no longer returns every hunk in a file containing it, only the hunk that actually touched retry_policy. PatternHit.related_head_symbols now carries the touched-symbol names instead of staying empty.
  • Semantic hunk text. A new hunk.semantic_text column stores a normalized representation (commit / file / language / symbols / change / added_lines sections) that the embedder + a new bm25_hunks_by_semantic_text lane (fts_hunk_semantic) see in place of raw diff_text. The raw diff stays put for display and provenance. Backfill: existing hunks get semantic_text = diff_text so the new lane returns useful results immediately on a v0.6 index, and a fresh index pass populates real semantic text.
  • Query understanding. A new rule-based parser (ohara_core::query_understanding::parse_query) classifies free-form queries into one of ImplementationPattern, BugFixPrecedent, ApiUsage, Configuration, or Unknown, and extracts explicit filters (language hints, path tokens, quoted symbol names, simple last N days/weeks/months timeframes). find_pattern_with_profile returns the picked RetrievalProfile alongside hits so MCP responses surface _meta.query_profile (name + explanation). Profile behaviour is conservative: bug-fix bumps recency 1.5x, API-usage widens the rerank pool, configuration disables the symbol lane, unknown intent uses today’s defaults byte-identically.
  • FTS5 query sanitizer (bug fix). Any user query containing backticks, parens, *, ^, +, -, etc. previously crashed with fts5: syntax error. The BM25 lanes now strip non-word characters before passing the query to FTS5.

explain_change enrichment

  • Two questions in one tool. hits[] still carries blame-exact attribution (provenance = "EXACT") for “which commits introduced these lines”. A new _meta.explain.related_commits[] carries file-scope contextual neighbours (provenance = "INFERRED") for “what nearby changes shaped this area”. Capped at 2 commits before + 2 commits after each blame anchor, deduped across anchors. Backed by the new Storage::get_neighboring_file_commits. CLI defaults include_related = true; MCP defaults false to keep the response payload predictable.

Index compatibility (plan 13)

  • index_metadata table. Every successful index pass now records the embedder model + dimension, reranker model, AST chunker version, semantic-text version, schema version, and per-language parser versions it ran with. The runtime compares the recorded values against what the current binary expects and produces one of Compatible, QueryCompatibleNeedsRefresh, NeedsRebuild, or Unknown.
  • Surfaces. ohara status prints a compatibility: line with an actionable next-step command (no embedder load — status stays cheap). MCP _meta.compatibility carries the structured verdict; find_pattern refuses to run on NeedsRebuild (returns a structured invalid_params error naming the rebuild command) because KNN against a stale-vector index would silently produce wrong results. explain_change continues under every verdict because blame doesn’t depend on embedder/chunker/parser state.
  • ohara index --rebuild --yes. Destructive recovery flag for NeedsRebuild verdicts. Refuses without --yes; verifies the DB path is under OHARA_HOME before deletion; removes the main DB plus its -wal / -shm sidecars, then runs the normal index pipeline against the fresh DB.

Eval harness (plan 10)

  • #[ignore]’d retrieval-quality runner under tests/perf/context_engine_eval.rs. Indexes a deterministic synthetic fixture and runs every case in tests/perf/fixtures/context_engine_eval/golden.jsonl through the same Retriever::find_pattern path the CLI / MCP use. Emits a one-line JSON summary on stderr (cases, recall_at_1, recall_at_5, mrr, ndcg_lite, p50_ms, p95_ms, failed_ids) so PR descriptions can paste it verbatim. Hard contracts: recall_at_5 == 1.0, mrr >= 0.80, no individual query over 2 s. Profile-mismatch warnings are informational, not gating. CONTRIBUTING.md §14 codifies the PR rule: any change to retrieval ranking, chunking, hunk text, symbol attribution, or query parsing must run the harness and paste the JSON summary.
  • Initial pass on the in-repo fixture: 8 cases, recall_at_1=0.875, recall_at_5=1.0, mrr=0.9375, nDCG-lite=0.95, p95=1245ms.

Schema migrations

  • V3 (index_metadata). Per-component compatibility tracking.
  • V4 (hunk.semantic_text + hunk_symbol + fts_hunk_semantic). Backfill makes both new tables immediately queryable on a pre-v0.7 index; richer attribution and proper semantic text populate after a fresh ohara index --force (or any subsequent index pass).

Upgrading

A pre-v0.7 index still works against v0.7 binaries — ohara status will print compatibility: query-compatible, refresh recommended because the chunker version bumped from 1 to 2. Run ohara index --force to repopulate the derived rows. Indexes built before the v0.6.x embedder changes (different model or dimension) report compatibility: needs rebuild; use ohara index --rebuild --yes to drop and rebuild from scratch.

The full per-plan breakdown lives in docs/superpowers/plans/.

v0.6.3 — Skip already-indexed commits on resume

Resume on a merge-heavy history no longer re-embeds commits that are already in the index. Diagnosed on a live QuestDB resume: 273 commits with existing commit_record rows were re-walked and re-embedded because the single-SHA watermark only excludes the strict ancestor chain, missing anything reachable via a feature-branch merge or history rewrite. That cost ~14 minutes of CPU per resume cycle.

  • New Storage::commit_exists PK lookup. Sub-millisecond per commit via SELECT 1 FROM commit_record WHERE sha = ? LIMIT 1. On cold-index runs the added cost is ~0.1 s across thousands of commits — well under measurement noise.
  • Indexer short-circuit. The per-commit loop in Indexer::run now consults commit_exists before any hunk extraction or embedder work and continues on a hit, with a tracing::debug! line for observability.
  • Watermark advances through skip stretches. A Ctrl-C right after a long all-skip stretch leaves the watermark at the most recently walked sha, so the next resume doesn’t have to re-iterate them either.
  • No schema migration. The lookup hits the existing commit_record.sha primary-key index.

Design notes: docs/superpowers/specs/2026-05-02-ohara-v0.6.3-resume-skip-rfc.md.

v0.6.2 — Per-host distribution: CoreML in the Apple Silicon binary

The released binary on aarch64-apple-darwin now bundles the CoreML execution provider. ohara update from a v0.6.1 install pulls the new artifact in transparently — the aarch64-apple-darwin asset name and -update shim are unchanged, so axoupdater follows without intervention.

  • Apple Silicon users no longer need a source rebuild for CoreML. The auto-downgrade shipped in v0.6.1 (--embed-provider auto → CPU on long passes, CoreML on short ones) becomes load-bearing on the released binary: query and short --incremental calls now hit CoreML by default, while 1,000+ commit cold-index passes still fall back to CPU to dodge the embed_batch leak.
  • Linux x86_64 / Linux aarch64 / Intel macOS artifacts unchanged. cargo-dist 0.31 has no per-target features override, so we pass features = ["coreml"] to every target’s cargo build and let ohara-embed’s target-conditional ort dependency strip the CoreML wiring on non-macOS triples. No extra ort weight on Linux artifacts; no link changes; no asset-name churn.
  • A -cpu opt-out artifact is not shipped. cargo-dist 0.31 does not support multiple builds per target, so the CoreML build is the only Apple Silicon artifact. Users who want pure CPU inference can still pass --embed-provider cpu (or rely on the long-pass auto-downgrade); they just don’t get a smaller binary.
  • Internal: crates/ohara-embed/src/fastembed.rs tightens the EmbedProvider::CoreMl cfg gate from feature = "coreml" to all(feature = "coreml", target_os = "macos"). Source builds (cargo build --release) stay CPU-only by default; the CoreML feature flag only flips on for the cargo-dist released binary.

v0.6.1 — CoreML long-pass auto-downgrade

Workaround release for the CoreML embedder leak called out under v0.6.0’s “Known issues”. Diagnosis is in docs/perf/v0.6.1-leak-diagnosis.md: the leak is heap-attributable (~4 MB / embed_batch, MALLOC_LARGE), not an MLModel / ANE-side retention. Rebuild-cadence probes mitigate by ~2× but don’t bound the growth, so v0.6.1 ships a documented workaround rather than an in-tree fix; the upstream investigation in fastembed / ort is re-opened.

  • --embed-provider auto now downgrades to CPU on Apple Silicon for long index passes (1,000 commits or more to walk). Short-lived query and index --incremental calls keep the CoreML auto-pick. Threshold lives in crates/ohara-cli/src/commands/provider.rs.
  • Explicit --embed-provider coreml is honoured unchanged, with a one-time tracing::warn! on startup pointing at the diagnosis doc. Bypass the downgrade with this flag if you have headroom and want the speedup.
  • tests/perf/coreml_leak_repro.rs (#[ignore]’d) ships as the regression harness. Re-run with --features ohara-embed/coreml on Apple Silicon when an upstream candidate fix lands.

Migration

Anyone using v0.6.0 with the documented workaround (--embed-provider cpu) can drop the flag — --embed-provider auto now does the same thing for long passes. CUDA and CPU users see no change.

v0.6.0 — Throughput prep, hardware acceleration opt-in

  • --embed-provider {auto,cpu,coreml,cuda} CLI flag with auto-detect (CoreML on Apple silicon, CUDA when CUDA_VISIBLE_DEVICES is set, else CPU).
  • --resources {auto,conservative,aggressive} resource policy picks --commit-batch / --threads / --embed-provider defaults from host core count; explicit flags still override.
  • --profile dumps per-phase wall-time JSON for benchmarking; feeds the v0.6 throughput baseline at docs/perf/v0.6-baseline.md.
  • --no-progress suppresses the progress bar in CI (structured tracing::info! events still fire every 100 commits).
  • tracing-indicatif: progress bar pinned to the bottom of the terminal, tracing log lines stream above without scrolling the bar away.
  • Cargo features coreml and cuda wire ONNX execution providers through ohara-embedohara-cli / ohara-mcp. The cargo-dist release binaries stay CPU-only; build from source with --features coreml (Apple silicon) or --features cuda (Linux NVIDIA) to opt in.
  • Resume-crash fix: commit::put is now DELETE-then-INSERT for vec_commit and fts_commit. Closes a “UNIQUE constraint failed” crash on resume after a kill mid-walk.
  • ohara --version now reports ohara 0.6.0 (<sha>) so local builds are distinguishable from released ones.
  • Internal: ohara-storage/src/ split into tables/ + codec/; ohara-parse/src/ extractors consolidated under languages/. No public API change.

Known issues

  • CoreML memory leak on long cold-index runs (Apple silicon). On a 5,000+ commit first-time ohara index with --embed-provider coreml, memory grows unbounded (observed 32 GB+ before macOS jetsam kills the process). The leak appears specific to repeated small-batch inference through ort’s CoreML provider; the CPU and CUDA paths are unaffected. Documented workaround in v0.6.1: --embed-provider auto downgrades to CPU for long passes on Apple Silicon — see the v0.6.1 entry above and docs/perf/v0.6.1-leak-diagnosis.md.

v0.5.1

  • Self-update. ohara update (and --check / --prerelease) drives axoupdater to install the latest release in place — same source of truth as the cargo-dist installer.
  • Progress bar. Indexing now shows a live progress bar on TTY stderr (suppress with --no-progress), plus structured tracing::info! events every 100 commits for log aggregators.
  • Abort-resume hardening. Watermark advances every 100 commits inside the indexer; a Ctrl-C / kill / crash mid-walk loses ≤ 100 commits of work, never duplicates rows.

v0.5

  • explain_change MCP tool. Given a file + line range, returns the commits that introduced and shaped those lines, newest-first. Backed by git blame, not embeddings — every result has provenance = "EXACT".
  • ohara explain CLI. Same JSON envelope as the MCP tool, for debugging and jq piping.

v0.4

  • Java 17 / 21 support. Classes (incl. sealed), interfaces, records, enums, methods. Tree-sitter-java grammar.
  • Kotlin 1.9 / 2.0 support. Classes (incl. sealed), data classes, objects + companion objects, interfaces, top-level + member functions. Tree-sitter-kotlin grammar.
  • Annotations preserved in source_text. Spring-flavored markers (@RestController, @Service, @Component, @SpringBootApplication, …) and Kotlin annotations (@Composable, @Serializable) are retained verbatim, so embeddings and BM25 pick them up without new query syntax.

v0.3

  • Three-lane retrieval pipeline. find_pattern now dispatches three queries in parallel: vector KNN over vec_hunk, FTS5 BM25 over hunk-text, FTS5 BM25 over symbol-name. Reciprocal Rank Fusion (k=60) merges them.
  • Cross-encoder rerank. Top-50 RRF candidates re-scored by bge-reranker-base (~110 MB ONNX, CPU). Opt-out via --no-rerank / no_rerank: true.
  • Recency as tie-breaker only. The v0.1 `0.7·sim + 0.2·recency
    • 0.1·msg_sim` linear formula is gone; recency is now a small multiplicative nudge applied after the cross-encoder.
  • AST sibling-merge chunking. ohara-parse now merges sibling AST nodes up to a 500-token budget instead of one chunk per top-level symbol — better recall on small functions, fewer giant chunks.

v0.2

  • ohara init. Installs a managed post-commit hook so the index stays fresh after every commit. Idempotent (re-running is safe); fails-closed if ohara isn’t on PATH (never blocks a commit). --write-claude-md adds an “ohara” stanza to CLAUDE.md.
  • ohara index --incremental. Fast no-op when HEAD is already indexed — skips embedder boot entirely. The post-commit hook uses this; sub-second on no-op re-indexes.

v0.1

  • Foundation. Workspace of seven crates, SQLite + sqlite-vec + FTS5 schema, refinery migrations, BGE-small embeddings via fastembed-rs.
  • find_pattern MCP tool. First version: linear-blend ranking over vec_hunk similarity + commit recency + commit-message similarity. Replaced by the three-lane pipeline in v0.3.
  • ohara index, ohara query, ohara status CLI. Full index, ad-hoc query, freshness inspection.
  • Rust + Python language support. Tree-sitter symbol extraction for both.
  • Distribution. cargo-dist installer for macOS (Apple silicon + Intel) and Linux (aarch64 + x86_64).