cocode.dk EN FA

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")
  1. 0.96agent-linters/README.md
  2. 0.81agent-linters/bin/lintp
  3. 0.58agent-linters/llms.txt
  4. Threshold 0.5: files below it come back as rejected. The best three accepted files go to the LLM.
  5. 0.22agent-linters/shell/linters.fish
  6. 0.14agent-linters/website/index.html
  7. 0.09agent-linters/install.sh
  8. 0.03message-test-plugin/CONTRIBUTING.md
  9. 0.02message-test-plugin/scripts/validate-plugin.py

Biome checks .css and .scss files. It is listed in the linter table in agent-linters/README.md.

An illustration, not a recorded run. The paths come from the repo's test fixture; the scores are made up to show the shape of the result.

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.

zgRuns on your machine, against an index of a copy of your folders.
JevGets the question, plus the path and up to two snippets of each of up to 8 files, sent to OpenRouter.
claudeGets 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_rag library, standard library only: index, find and ask.
  • The zjm command runs the same operations from a shell, plus zjm doctor. --json prints the result as one JSON object. Command-line spec
  • index copies your folders into a store (a temp directory by default) and indexes the copy, so your projects stay untouched. multilingual=True picks a model for non-English content.
  • find splits 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 on 127.0.0.1 for apps in any language. HTTP spec
  • zjm mcp, an MCP server for agents, and a one-line install script. MCP and install spec

Install and try it

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"])
cocode.dk