ohara
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 bygit 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--incrementalfast 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 servedaemon is available for warm-cache workflows. Indexing is still always foreground. Distributed viacargo-distfor macOS and Linux (Windows users: WSL).
Where to go next
- Install —
curl | shinstall or build from source. - Quickstart — index a repo and run your first query.
- Wiring into MCP clients — point Claude Code, Cursor, Codex, OpenCode, or any MCP-aware client at the server.
- Architecture overview — for contributors and the curious.
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
| OS | Architectures |
|---|---|
| macOS | Apple silicon (aarch64-apple-darwin), Intel (x86_64-apple-darwin) |
| Linux | aarch64-unknown-linux-gnu, x86_64-unknown-linux-gnu |
| Windows | not 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:
- Open the releases page.
- Grab the
ohara-cli-*andohara-mcp-*tarball matching your platform. - Unpack and move the binaries somewhere on
PATH(e.g./usr/local/binor~/.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_batchcall (MALLOC_LARGEheap, seedocs/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 autotherefore 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 coremlexplicitly 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?
- Learn what the MCP tools do:
find_pattern,explain_change. - Check on an index:
ohara status. - Understand what the index actually contains: Architecture overview.
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
| Component | Purpose |
|---|---|
ohara MCP server | Registers find_pattern and explain_change as MCP tools. |
ohara:lineage skill | Tells 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 skill | Index 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.
-
tarwith 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
oharaCLI 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.jsonor.claude/mcp.jsonin 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.jsonin 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).
Per-repo wiring (recommended for teams)
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:
- The repo is indexed. Run
ohara indexonce. Both tools degrade gracefully on an empty index (zero hits + a_meta.hintexplaining why) but they’re not interesting until there’s history to look at. - The index stays fresh. Run
ohara initto install the post-commit hook. After that, every commit triggers an--incrementalre-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_patternto 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-mcpand recheck. - Binary not executable —
chmod +xif 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.
| Field | Type | Default | Description |
|---|---|---|---|
query | string | required | Natural-language description of the pattern to find. |
k | integer | 5 | Number of results to return; clamped to 1..=20. |
language | string | null | Optional language filter (e.g. "rust", "python", "java", "kotlin"). |
since | string | null | Optional lower bound on commit age. Accepts ISO date ("2024-01-01"), RFC-3339 datetime, or relative days ("30d"). |
no_rerank | boolean | false | Skip 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:
- “Which commits introduced these lines?” Backed by
git blame, exact attribution. Each blamehits[i]carriesprovenance = "EXACT". - “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_commitswithprovenance = "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_patterninstead - 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.
| Field | Type | Default | Description |
|---|---|---|---|
file | string | required | Repo-relative file path (e.g. src/auth.rs). |
line_start | integer | 1 | 1-based start line, inclusive. |
line_end | integer | 0 | 1-based end line, inclusive. 0 is a sentinel meaning “end of file” — the server resolves it by reading the workdir checkout. |
k | integer | 5 | Number of commits to return; clamped to 1..=20. |
include_diff | boolean | true | Include diff_excerpt in each hit. Set to false for a tighter response when only the blame attribution matters. |
include_related | boolean | CLI: true / MCP: false | Plan 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_queriedreflects the clamped range (the server clampsline_endto the file’s actual length).blame_coverageis the fraction of queried lines that resolved to a commit the local index knows about. Less than1.0means at least one line landed on a SHA older than the current watermark (re-runohara indexto backfill).limitationis 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 labelledprovenance = "INFERRED"and capped at 2 commits before + 2 commits after each blame anchor, deduped across anchors. The list is omitted entirely wheninclude_related = falseor no neighbouring commits exist.enrichment_limitationis 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
| Field | Provenance | Meaning |
|---|---|---|
hits[i] | EXACT | This commit introduced these specific lines (git blame). |
_meta.explain.related_commits[i] | INFERRED | This 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]
| Flag | Default | Description |
|---|---|---|
PATH (positional) | . | Path to the repo (or any path inside it — git2::Repository::discover resolves the actual .git dir). |
--write-claude-md | off | Also append/update an “ohara” stanza in CLAUDE.md at the repo root, fenced by <!-- ohara:start --> … <!-- ohara:end -->. |
--force | off | Overwrite 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}]
| Flag | Default | Description |
|---|---|---|
PATH (positional) | . | Path to the repo. |
--incremental | off | Skip 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. |
--force | off | Clear 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. |
--rebuild | off | Destructive. 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. |
--yes | off | Confirm a destructive operation. Currently only valid alongside --rebuild. |
--commit-batch | from --resources | Commits 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. |
--threads | from --resources | Cap 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-progress | off | Disable the progress bar even when stderr is a TTY. Structured tracing::info! events still fire every 100 commits. |
--profile | off | Emit 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-provider | from --resources | ONNX 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. |
--resources | auto | Resource 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]
| Flag | Default | Description |
|---|---|---|
PATH (positional) | . | Path to the repo. |
-q, --query | required | Natural-language query string. |
-k, --k | 5 | Number of results to return. |
--language | null | Filter results to a single language (rust, python, java, kotlin). |
--no-rerank | off | Skip 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
--sincefilter exposed by the MCP tool (since: "30d",since: "2024-01-01") is not currently surfaced on the CLI. - The CLI prints the
hitsarray directly (no_metaenvelope) 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]
| Flag | Default | Description |
|---|---|---|
FILE (positional) | required | Repo-relative path to the file to explain. |
PATH (positional) | . | Path to the repo. |
--lines | full file | Line 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, --k | 5 | Number of commits to return; clamped to 1..=20. |
--no-diff | off | Suppress 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 = 0to the file’s actual last line by reading the workdir checkout. - Every result has
provenance = "EXACT"—explain_changeis backed bygit blame, not embeddings. - The
_meta.explain.blame_coveragefield reports the fraction of queried lines attributed to a known commit. Less than1.0means some lines landed on a SHA older than the local watermark — re-runohara indexto 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]
| Flag | Default | Description |
|---|---|---|
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:
| Value | Meaning | Suggested action |
|---|---|---|
compatible | Every 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]
| Flag | Default | Description |
|---|---|---|
--check | off | Report whether a newer version exists without installing it. Exits 2 if an update is available, 0 if up-to-date. |
--force | off | Allow downgrades / re-installs of the same version. Off by default — running ohara update on the latest version is a no-op. |
--prerelease | off | Include 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:
- Indexing & abort-resume — what
ohara indexdoes end-to-end. - Storage schema — what lands in SQLite.
- Retrieval pipeline — how a query becomes a ranked list of hits.
- 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-veck-NN search against thevec_hunktable. 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.
Related
- Storage schema — what
vec_hunk,fts_hunk_text, andfts_symbol_namelook like on disk. - Language support — what feeds
fts_symbol_name. find_patterntool reference — the user-visible entry point.
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-vecfor thevec_*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
| Table | Kind | Purpose |
|---|---|---|
repo | normal | One row per indexed repo: id, path, first commit, watermark, indexed_at, schema version. |
commit_record | normal | One row per commit: SHA, parent SHA, merge flag, unix ts, author, message. Indexed on ts. |
file_path | normal | Interned file path strings + detected language + active flag. |
symbol | normal | HEAD-snapshot symbols: file, kind, name, qualified_name, line span, blob SHA, source_text. |
hunk | normal | One 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_cache | normal | Tracks which blob SHAs have been embedded by which model — keeps re-indexes idempotent. |
vec_hunk | vec0 | sqlite-vec table: hunk_id → diff_emb FLOAT[384] (BGE-small). |
vec_commit | vec0 | commit_sha → message_emb FLOAT[384]. |
vec_symbol | vec0 | symbol_id → source_emb FLOAT[384]. |
fts_commit | FTS5 | BM25 over commit messages. |
fts_symbol | FTS5 | BM25 over (qualified_name, source_text). |
V2 — three-lane retrieval (v0.3)
| Table / column | Kind | Purpose |
|---|---|---|
symbol.sibling_names | new column | JSON array of sibling-symbol names from the AST sibling-merge chunker. Defaults to '[]' for v0.2-era rows. |
fts_hunk_text(hunk_id, content) | FTS5 | BM25 lane over raw hunk body — feeds the second retrieval lane in find_pattern. |
fts_symbol_name(symbol_id, kind, name, sibling_names) | FTS5 | BM25 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 / column | Kind | Purpose |
|---|---|---|
index_metadata(repo_id, component, version, value_json, recorded_at) | new table | Per-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_commit—INSERT OR REPLACEon the commit record + thevec_commitandfts_commitrows.put_hunks— DELETE-then-INSERT scoped bycommit_sha. Any previously-written hunks (and theirvec_hunk/fts_hunk_textrows) 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 entiresymbol/vec_symbol/fts_symbol/fts_symbol_namecontent 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
| Language | Since | Notes |
|---|---|---|
| Rust | v0.1 | Functions, methods, structs, enums, traits, impls. |
| Python | v0.1 | Classes, methods, top-level functions. |
| Java 17+ | v0.4 | Classes (incl. sealed), interfaces, records, enums, methods. |
| Kotlin 1.9 / 2.0 | v0.4 | Classes (incl. sealed), data classes, objects + companion objects, interfaces, top-level + member functions. |
| TypeScript | v0.8 | .ts, .tsx. Functions, classes, methods, arrow-function consts, interfaces, type aliases, enums. |
| JavaScript | v0.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/@Serializableare preserved verbatim in the symbol’ssource_textfield. 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@RestControllerdirectly.
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
.tsand.tsx; the latter is parsed with the TSX dialect of the grammar so JSX expressions inside.tsxcomponents don’t derail symbol extraction. - JavaScript dispatches on
.js,.jsx,.mjs, and.cjs. JSX inside.jsxparses cleanly via the same grammar. - Symbols extracted (both languages):
functiondeclarations,classdeclarations, methods (includingconstructor,get/set,static, andasync), and arrow-functionconsts (export const Foo = (…) => {…}) — the last one matters because the modern React/Next idiom expresses components and hooks as arrow-bound consts rather thanfunctiondeclarations. - Symbols extracted (TypeScript only):
interfacedeclarations,typealiases, andenumdeclarations. - Intentionally not extracted yet: decorators (preserved inside
source_textlike Java/Kotlin annotations, but not surfaced as separate symbols), ambient declarations in.d.tsfiles (the file extension dispatches normally butdeclareblocks aren’t symbolized), andnamespace/moduledeclarations (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:
- Resolve repo id. Hash the canonical repo path + the SHA of the
first commit on
HEAD. Yields a stablerepo_idused as the directory name under$OHARA_HOME/. - Open / migrate the index.
SqliteStorage::openruns any pending refinery migrations (migrations/V*.sql) before returning. - List new commits.
CommitSource::list_commits(after = watermark)walksgit logfrom the storage watermark toHEAD. On a fresh index the watermark isNoneand the walker returns every commit. - For each commit (batched by
--commit-batch, default 512):- Extract hunks via
CommitSource::hunks_for_commit. - Embed
[commit_message, hunk_1.diff_text, …]in a singleembed_batchcall. put_commit(commit row +vec_commit+fts_commit).put_hunks(hunk rows +vec_hunk+fts_hunk_text, DELETE-then-INSERT scoped bycommit_sha).- Every 100 commits, advance
repo.last_indexed_commitand emit atracing::info!progress event.
- Extract hunks via
- Extract HEAD symbols.
SymbolSource::extract_head_symbolswalks the working tree, parses each supported file with tree-sitter, runs the AST sibling-merge chunker, and produces oneSymbolper chunk. put_head_symbols. Replaces the entiresymbol/vec_symbol/fts_symbol/fts_symbol_namecontent for the repo (HEAD is a snapshot, not history).- Final watermark advance. Set
last_indexed_committo 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 bysha256(commit_msg + diff_text); embedder input unchanged. Hit rate is driven by exact(message, diff)repeats — cherry-picks, reverts. Conservative.diff— cache keyed bysha256(diff_text); embedder input changes todiff_textonly (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 existingfts_hunk_semanticBM25 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:
- Built-in defaults (compiled into
ohara-core) — lockfiles,node_modules/,target/,vendor/,dist/, etc. .gitattributes— paths flaggedlinguist-generated=trueorlinguist-vendored=true..oharaignoreat 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:
| Verdict | Meaning | What it gates |
|---|---|---|
compatible | Every recorded component matches the binary. | Nothing — proceed. |
query-compatible, refresh recommended | A 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 rebuild | A 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. |
unknown | Pre-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:
--forcerefreshes derived symbol/chunker outputs without touching the commit/hunk/vector history. Cheap; safe to re-run.--rebuilddeletes the entire index and rebuilds from scratch. Slow and destructive; requires--yesto confirm. Use only when the verdict isneeds 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, andohara_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 indexafter an abort re-does those ≤ 100 commits. The DELETE step input_hunksclears 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 whenCUDA_VISIBLE_DEVICESis 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-providerdefaults from the host’s logical core count. Explicit flags always override the picked plan, so--resources aggressive --commit-batch 256is meaningful.--profile. Already covered above — emits the per-phasePhaseTimingsJSON used by the throughput baseline.- Pinned progress bar. The CLI now wires
tracing-indicatifinto itstracingsubscriber, so the indexer’s progress bar stays pinned to the bottom of the terminal whiletracing::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:
- Red. Write the test that pins the contract. Commit before implementing — the test should fail.
- 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)] modblocks. 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 realStorageor 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 tomain.perf.yml— runs thetests/perfharness 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 --allandcargo clippy --workspace --all-targetslocally before pushing.
Roadmap
Released
| Version | Theme | Headline |
|---|---|---|
| v0.1 | Foundation | Plan 1 — workspace + SQLite/sqlite-vec/FTS5 schema + find_pattern MCP tool. |
| v0.2 | Auto-freshness | Plan 2 — ohara init post-commit hook + ohara index --incremental fast path. |
| v0.3 | Retrieval quality | Plan 3 — three-lane retrieval (vector + BM25 hunk-text + BM25 symbol-name) → RRF → cross-encoder rerank → recency tie-break. AST sibling-merge chunking. |
| v0.4 | Java + Kotlin | Plan 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.5 | explain_change | Plan 5 — second MCP tool, deterministic git-blame-backed lookup of “why does THIS code look the way it does?”. |
| v0.5.1 | Polish | Progress bar, abort-resume hardening, ohara update self-update via axoupdater. |
| v0.6.0 | Throughput prep | --profile PhaseTimings JSON, --embed-provider auto-detect, --resources policy, CoreML/CUDA feature wiring, resume-crash fix, pinned progress bar. |
| v0.7.0 | Evals + attribution | Plan 10/11/13 — eval harness, historical symbol attribution, index metadata + rebuild safety. |
| v0.7.2 | Perf tracing | Plan 14 — phase tracing, per-method storage metrics, perf harness binaries. |
| v0.7.3 | Memory-efficient indexing | Plan 15 — embed_batch chunking + source-text cap + peak-RSS sampler. |
| v0.7.4 | Submodule fix | Gitlink-skip in file_at_commit for uninitialized submodules. |
| v0.7.5 | Daemon + multi-repo | Plan 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_hunkembedding 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_patternreturns 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 walksHEAD. Walking a feature branch’s range againstmainis 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_symboltable records which symbols inside a file each hunk actually touched.bm25_hunks_by_historical_symbolis 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 newextract_atomic_symbolsparser entry-point) andHunkHeader(git’s@@line includes an enclosing function/class). Solves the file-level lane’s wrong-hunk problem: a query forretry_policyno longer returns every hunk in a file containing it, only the hunk that actually touchedretry_policy.PatternHit.related_head_symbolsnow carries the touched-symbol names instead of staying empty. - Semantic hunk text. A new
hunk.semantic_textcolumn stores a normalized representation (commit / file / language / symbols / change / added_lines sections) that the embedder + a newbm25_hunks_by_semantic_textlane (fts_hunk_semantic) see in place of rawdiff_text. The raw diff stays put for display and provenance. Backfill: existing hunks getsemantic_text = diff_textso 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 ofImplementationPattern,BugFixPrecedent,ApiUsage,Configuration, orUnknown, and extracts explicit filters (language hints, path tokens, quoted symbol names, simplelast N days/weeks/monthstimeframes).find_pattern_with_profilereturns the pickedRetrievalProfilealongside 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 withfts5: 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 newStorage::get_neighboring_file_commits. CLI defaultsinclude_related = true; MCP defaultsfalseto keep the response payload predictable.
Index compatibility (plan 13)
index_metadatatable. 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 ofCompatible,QueryCompatibleNeedsRefresh,NeedsRebuild, orUnknown.- Surfaces.
ohara statusprints acompatibility:line with an actionable next-step command (no embedder load — status stays cheap). MCP_meta.compatibilitycarries the structured verdict;find_patternrefuses to run onNeedsRebuild(returns a structuredinvalid_paramserror naming the rebuild command) because KNN against a stale-vector index would silently produce wrong results.explain_changecontinues under every verdict because blame doesn’t depend on embedder/chunker/parser state. ohara index --rebuild --yes. Destructive recovery flag forNeedsRebuildverdicts. Refuses without--yes; verifies the DB path is underOHARA_HOMEbefore deletion; removes the main DB plus its-wal/-shmsidecars, then runs the normal index pipeline against the fresh DB.
Eval harness (plan 10)
#[ignore]’d retrieval-quality runner undertests/perf/context_engine_eval.rs. Indexes a deterministic synthetic fixture and runs every case intests/perf/fixtures/context_engine_eval/golden.jsonlthrough the sameRetriever::find_patternpath 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 freshohara 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_existsPK lookup. Sub-millisecond per commit viaSELECT 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::runnow consultscommit_existsbefore any hunk extraction or embedder work andcontinues on a hit, with atracing::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.shaprimary-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:queryand short--incrementalcalls now hit CoreML by default, while 1,000+ commit cold-index passes still fall back to CPU to dodge theembed_batchleak. - 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’scargo buildand letohara-embed’s target-conditionalortdependency strip the CoreML wiring on non-macOS triples. No extra ort weight on Linux artifacts; no link changes; no asset-name churn. - A
-cpuopt-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.rstightens theEmbedProvider::CoreMlcfg gate fromfeature = "coreml"toall(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 autonow downgrades to CPU on Apple Silicon for long index passes (1,000 commits or more to walk). Short-livedqueryandindex --incrementalcalls keep the CoreML auto-pick. Threshold lives incrates/ohara-cli/src/commands/provider.rs.- Explicit
--embed-provider coremlis honoured unchanged, with a one-timetracing::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/coremlon 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 whenCUDA_VISIBLE_DEVICESis set, else CPU).--resources {auto,conservative,aggressive}resource policy picks--commit-batch/--threads/--embed-providerdefaults from host core count; explicit flags still override.--profiledumps per-phase wall-time JSON for benchmarking; feeds the v0.6 throughput baseline atdocs/perf/v0.6-baseline.md.--no-progresssuppresses the progress bar in CI (structuredtracing::info!events still fire every 100 commits).- tracing-indicatif: progress bar pinned to the bottom of the
terminal,
tracinglog lines stream above without scrolling the bar away. - Cargo features
coremlandcudawire ONNX execution providers throughohara-embed→ohara-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::putis now DELETE-then-INSERT forvec_commitandfts_commit. Closes a “UNIQUE constraint failed” crash on resume after a kill mid-walk. ohara --versionnow reportsohara 0.6.0 (<sha>)so local builds are distinguishable from released ones.- Internal:
ohara-storage/src/split intotables/+codec/;ohara-parse/src/extractors consolidated underlanguages/. No public API change.
Known issues
- CoreML memory leak on long cold-index runs (Apple silicon). On a
5,000+ commit first-time
ohara indexwith--embed-provider coreml, memory grows unbounded (observed 32 GB+ before macOS jetsam kills the process). The leak appears specific to repeated small-batch inference throughort’s CoreML provider; the CPU and CUDA paths are unaffected. Documented workaround in v0.6.1:--embed-provider autodowngrades to CPU for long passes on Apple Silicon — see the v0.6.1 entry above anddocs/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 structuredtracing::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_changeMCP tool. Given a file + line range, returns the commits that introduced and shaped those lines, newest-first. Backed bygit blame, not embeddings — every result hasprovenance = "EXACT".ohara explainCLI. Same JSON envelope as the MCP tool, for debugging andjqpiping.
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_patternnow dispatches three queries in parallel: vector KNN overvec_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-parsenow 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 ifoharaisn’t onPATH(never blocks a commit).--write-claude-mdadds an “ohara” stanza toCLAUDE.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_patternMCP tool. First version: linear-blend ranking overvec_hunksimilarity + commit recency + commit-message similarity. Replaced by the three-lane pipeline in v0.3.ohara index,ohara query,ohara statusCLI. 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).