Find the file that holds the answer
zjm-rag is RAG over local files. zg searches your folders, Jev reads what comes back and judges which files actually contain the answer, and an LLM answers from those files alone, citing their paths.
The zjm_rag library and the zjm command are in, with --json output for scripts. An HTTP API and an MCP server are written up as specs and being built next.
>>> zjm_rag.ask("which linter checks CSS files")
- 0.96
agent-linters/README.md - 0.81
agent-linters/bin/lintp - 0.58
agent-linters/llms.txt - Threshold 0.5: files below it come back as rejected. The best three accepted files go to the LLM.
- 0.22
agent-linters/shell/linters.fish - 0.14
agent-linters/website/index.html - 0.09
agent-linters/install.sh - 0.03
message-test-plugin/CONTRIBUTING.md - 0.02
message-test-plugin/scripts/validate-plugin.py
Biome checks .css and .scss files. It is listed in the linter table in agent-linters/README.md.
Three steps, each one narrower
Search casts a wide net, and a judge sorts the catch. Only the files the judge accepts reach the model, so it reads the files themselves, up to 20,000 characters each, instead of a pile of loose snippets.
40 hits, grouped into at most 8 files
zg finds
A hybrid search, keyword and semantic, over a local zg index. find takes up to 40 hits and groups them by file, keeping two short snippets from each.
8 files, each with a probability
Jev judges
One request to Jev, the OpenRouter Decisions API, asks the same question of every file: does this one contain what the answer needs? Jev judges each file from its path and snippets and returns a probability. Files at 0.5 or above are accepted. The rest come back as rejected, so none disappears without a trace.
Up to 3 accepted files, one answer
The LLM answers
ask sends the three best accepted files to the claude CLI with one instruction: answer from these files only, cite the path, and say so if they don't hold the answer. When no file passes the threshold, no LLM is called.
What leaves your machine
Search is local, but judging and answering are not. Point it at folders you would be fine pasting into a chat with those services.
| zg | Runs on your machine, against an index of a copy of your folders. |
|---|---|
| Jev | Gets the question, plus the path and up to two snippets of each of up to 8 files, sent to OpenRouter. |
claude | Gets the question, plus the path and first 20,000 characters of each file it answers from, three at most, sent to Anthropic. |
What exists today, and what is planned
Today
- The
zjm_raglibrary, standard library only:index,findandask. - The
zjmcommand runs the same operations from a shell, pluszjm doctor.--jsonprints the result as one JSON object. Command-line spec indexcopies your folders into a store (a temp directory by default) and indexes the copy, so your projects stay untouched.multilingual=Truepicks a model for non-English content.findsplits files at a threshold into accepted and rejected, and sorts them by score, zg rank, modified time or path.- The tests use fakes and never call zg, Jev or an LLM.
Planned, one pull request per spec
zjm serve, a JSON API on127.0.0.1for apps in any language. HTTP speczjm mcp, an MCP server for agents, and a one-line install script. MCP and install spec
Install and try it
- Python 3.10 or newer. The package has no dependencies.
zgon yourPATH:npm install -g @zvec/zvec-grepOPENROUTER_API_KEYset in your environment, for Jev's ranking.- The
claudeCLI, for answers.
Install it from a clone
git clone https://github.com/cocodedk/zjm-rag.git && cd zjm-rag pip install .
Index a folder, then ask, from the shell or from Python
zjm index ~/projects/some-repo # copy, then index the copy zjm find "which linter checks CSS files" # accepted and rejected files zjm ask "which linter checks CSS files" # the answer and its sources zjm doctor # checks zg, claude and the API key
import os, zjm_rag
zjm_rag.index([os.path.expanduser("~/projects/some-repo")]) # copy, then index the copy
hits = zjm_rag.find("which linter checks CSS files") # accepted and rejected files
reply = zjm_rag.ask("which linter checks CSS files")
print(reply["answer"], reply["files"])