Skip to main content
Glama
README.md
# ProjectRecall

Local retrieval over your own project notes, served to Claude Code as an MCP server.
Ask "how did I fix that crash?" and get the paragraph you wrote months ago, with its file and line.

## How it works

1. **Index** — markdown files in allowlisted folders are split by heading, so every chunk keeps
   its breadcrumb (`Project > Bugs > Encoder drift`).
2. **Embed** — chunks are embedded on your machine with `nomic-embed-text` through Ollama.
3. **Store** — one SQLite file holds the text, an FTS5 keyword index and the vectors.
4. **Search** — BM25 and vector rankings are merged with weighted reciprocal rank fusion.
5. **Serve** — an MCP server over stdio exposes `search_notes` and `list_projects`.

New to search or RAG? Start with the [documentation](docs/README.md): concepts, usage,
architecture, retrieval, evaluation and privacy, written for readers without a background
in any of them.

## Privacy model

The repository contains code and an invented sample corpus only.

- The index and the real config live in `%LOCALAPPDATA%\ProjectRecall`, outside the repository.
- Indexing is allowlist-only, and files that look like secrets (`.env*`, `*.key`, ...) are always skipped.
- Embeddings are computed locally; no hosted embedding API or vector database is used.
- The server speaks stdio only and opens no network port.
- A pre-commit guard blocks database files and any staged line that matches a privately indexed note.

Retrieved snippets do become part of the conversation with whichever MCP client asks for them.

## Setup

```
py -3.11 -m venv .venv
.venv\Scripts\pip install -e ".[dev]"
ollama pull nomic-embed-text
git config core.hooksPath .githooks

.venv\Scripts\recall init      # creates the config; add your folders to it
.venv\Scripts\recall index
.venv\Scripts\recall search "why did training crash"
```

Register with Claude Code:

```
claude mcp add --scope user project-recall -- <repo>\.venv\Scripts\python.exe -m recall.server
```

## Evaluation

`python eval/run_eval.py` indexes `sample_corpus/` (16 invented projects with deliberately
overlapping topics, 148 chunks) and scores questions from `eval/golden.jsonl`. A question is
answered at rank *r* if the *r*-th chunk comes from the expected file and contains the
expected text.

Fusion settings were tuned on 99 dev questions. The table is the held-out test split: 59
questions (42 paraphrased, 17 exact-term), assigned by hash and scored once, after the
settings were fixed.

| Mode | hit@1 | hit@3 | hit@5 | MRR@5 |
| --- | --- | --- | --- | --- |
| BM25 | 67.8% | 78.0% | 84.7% | 0.735 |
| Vector | 88.1% | 96.6% | 98.3% | 0.921 |
| Hybrid (weighted RRF) | 91.5% | 96.6% | 100.0% | 0.943 |

Hybrid hit@1 has a 95% interval of 81.6% to 96.3%. It beats BM25 on 19 questions and loses
on none. Against vector search it is ahead on average but level question by question (2
better, 2 worse), so this eval does not show hybrid beating vector search; it does show
hybrid as the only mode that puts the answer in the top five every time.

Plain RRF (equal weights, k = 60) scored below vector search alone. The fix was a small k, a
vector weight of 0.8, and equal weights for short exact-term queries. See
[docs/retrieval.md](docs/retrieval.md) and [docs/evaluation.md](docs/evaluation.md).

## Tests

```
.venv\Scripts\python -m pytest
```

## License

MIT. See [LICENSE](LICENSE).

Maintenance

ActivityMaintained
ResponsivenessNo issues