Skip to main content

hitgate

eval-gate (advisory)

pip install hitgate installs the harness (dependency-free; measures any retriever via --retriever). pip install "hitgate[hybrid]" adds the bundled hybrid retriever used in the demo below.

A pytest-style regression gate for retrieval quality — plus the small hybrid retriever it was built to measure. Point it at your retriever and find out whether a change helped or hurt, when you have no labeled data and no users to A/B against.

Status: working · stable · single-author personal tooling, published for the methodology. The adoptable part is the harness: a label-free, regression-gated quality check for any retriever (--retriever module:callable). The bundled hybrid engine is just the thing it measures.


Why this exists

Building RAG is easy; knowing whether a change made it better or worse is the hard part. With a small corpus and a single user you have none of the production crutches — no click logs, no A/B traffic, no annotation budget. This repo is one answer: treat retrieval quality as a measurable, regression-gated property, like a test suite, and be ruthlessly honest about what the numbers do and don't prove.

Quickstart (reproducible in ~10 seconds)

pip install -e ".[hybrid]"   # harness core is dependency-free; [hybrid] adds the bundled retriever

# Index this repo into a local ./.rag-index/ (the tool indexes itself)
RAG_SOURCE_ROOTS="$PWD" python -m ragcore.build

# Ask it something
RAG_SOURCE_ROOTS="$PWD" python -m ragcore.query --scope code "how does the reranker fall back"

# Run the eval gate (bundled retriever)
RAG_RERANK_AUTO=off python -m hitgate.run --label demo

# ...or point the SAME gate at YOUR retriever — any callable (query, top, scope) -> [{"path": ...}]
python -m hitgate.run --retriever hitgate.example_external_retriever:retrieve --label mine

That eval indexes the repo's own source and scores 50 golden cases against it — so you can reproduce the number below yourself, no private data required.

Results

Self-indexed demo (reproducible)

Metric Value
Hit@5 (code scope, pure hybrid) — the regression-gated headline 1.0
Hit@1 0.663
MRR 0.800
Corpus this repo, self-indexed · 101 cases

67 of 101 cases hit at rank 1; the misses are left in on purpose. Inflating a benchmark by quietly dropping the cases it fails is the first thing this project refuses to do — see DECISIONS.md; measured before/after deltas are in CHANGELOG.md. An honest ablation — where BM25-only wins Hit@1 (0.522) while hybrid wins Hit@5 (1.0) — is walked through in docs/METHODOLOGY.md.

Because the demo indexes this repo itself, the corpus grows as the repo does, so Hit@1 and MRR drift over time — adding a file can demote a borderline case. That's why Hit@5 is the number under regression gate (hitgate/check.sh, ±5pp). The drift is the honest behavior of a self-indexing benchmark, not noise swept under a frozen number.

External corpus benchmarks

The same retriever — zero tuning, same hitgate/run.py pipeline — measured against 7 other codebases with no corpus-specific configuration:

Corpus Language n Hit@5 Hit@1 MRR
FastAPI v0.115 Python 25 1.0 0.64 0.79
forge-space / mcp-gateway TypeScript 20 1.0 0.70 0.821
portfolio / src React/TS 15 1.0 0.60 0.778
ai-dev-toolkit / packages/core Python + TS 20 1.0 0.85 0.925
homelab / homelab_manager Python 20 0.950 0.85 0.900
Lucky / packages/backend TypeScript 21 0.905 0.71 0.810
Criativaria / web-app Next.js/TS 27 0.741 0.59 0.660

Hit@5=1.0 on four of seven corpora. The two lowest-performing corpora have structural causes: Lucky has one Category B drift miss (Prometheus registry vs middleware, identical vocabulary); Criativaria is a homogeneous Next.js component library where sibling components are lexically indistinguishable — a genuine retrieval ceiling, not a tuning problem.

The finding that matters: corpus module clarity predicts Hit@1 better than language or size. Clean functional boundaries (homelab, ADT) → 0.85. Same-layer UI components (portfolio, Criativaria) → 0.59–0.60. Python vs TypeScript is not the variable.

Full methodology, miss taxonomy, and reproduce commands: docs/METHODOLOGY.md.

How it works

  • Hybrid retrieval — dense embeddings (intfloat/multilingual-e5-small) + lexical BM25, fused with Reciprocal Rank Fusion. A code-aware tokenizer splits identifiers into camelCase/snake_case subtokens so "get user profile" matches getUserProfile.
  • Selective reranking (optional) — a cross-encoder reranker that, when enabled, is scoped to code-scope queries only (it was measured to help code and regress prose), with graceful fallback to the fused ranking if the model isn't present.
  • Language-aware chunking — Python by AST symbol, TS/JS/Shell by regex, with a word-count fallback.
  • Config by env var — zero-setup defaults (RAG_*); see ragcore/config.py.
  • Eval (the point)hitgate/run.py reports Hit@K/MRR for any retriever via --retriever; hitgate/check.sh gates a run against a frozen baseline (±5pp).
  • Golden set generatorhitgate/generate.py bootstraps candidate cases from your corpus structure (docstrings, symbol names) with zero dependencies. LLM paraphrase generation is opt-in via --llm. Output feeds directly into hitgate/run.py --dataset.

Use it on your own retriever

The harness doesn't care whose retriever it's measuring. A retriever is any callable:

retrieve(query: str, top: int, scope: str | None) -> Sequence[Mapping]
# results ranked best-first; each a mapping with at least "path" (optionally "start_line")

Point the gate at yours with --retriever module.path:callable:

python -m hitgate.run --retriever mypkg.myretriever:retrieve --label mine

A runnable, dependency-free example — a deliberately dumb keyword matcher — is in hitgate/example_external_retriever.py. Ecosystem wrappers (LangChain / LlamaIndex) live under adapters/. Bring your own retriever and corpus; keep the measurement discipline.

Bring your own corpus — 4-step quickstart

0. Bootstrap candidate cases from your corpus (optional):

RAG_SOURCE_ROOTS="/path/to/your/corpus" python -m hitgate.generate \
    --output hitgate/candidates.jsonl \
    --min-confidence medium

# LLM-enhanced (identifier + paraphrase per chunk, no extra package needed):
OPENAI_API_KEY=sk-... RAG_SOURCE_ROOTS="/path/to/your/corpus" \
    python -m hitgate.generate --llm --output hitgate/candidates.jsonl

Review and curate hitgate/candidates.jsonl — delete cases where the query is too vague or the expected file is wrong — then use it as your golden set below.

1. Write golden cases — each is a JSON object with three fields:

{"query": "what handles pagination in the API", "expect_path_contains": "api/pagination.py", "expect_scope": "code"}
{"query": "where are rate limits configured",    "expect_path_contains": "config/limits.yaml", "expect_scope": "code"}

expect_path_contains is a substring of the expected result's path (file name is usually enough). Aim for 20–50 cases across a mix of identifier lookups and paraphrase queries. Save as any .jsonl.

2. Run your retriever against the cases:

python -m hitgate.run \
    --retriever mypkg.myretriever:retrieve \
    --dataset   my_golden.jsonl \
    --label     baseline-v1
# writes hitgate/baseline-v1.json with hit@1/hit@3/hit@5/mrr + per_case breakdown

3. Freeze the baseline:

cp hitgate/baseline-v1.json hitgate/baseline.my-project.json
# edit _note to record conditions: corpus, model, date

4. Gate future runs with check.sh:

# hitgate/check.sh already reads BASELINE_FILE env var
BASELINE_FILE=hitgate/baseline.my-project.json \
RAG_SOURCE_ROOTS="/path/to/your/corpus" \
python -m hitgate.run --retriever mypkg.myretriever:retrieve --dataset my_golden.jsonl --label ci
bash hitgate/check.sh hitgate/ci.json hitgate/baseline.my-project.json
# exits 1 if any metric regresses by more than 5pp

To diff two runs case-by-case: python -m hitgate.diff hitgate/baseline-v1.json hitgate/ci.json.

What to adopt (and what to skip)

Adopt the harness. The reusable thing here is hitgate/ — the label-free, regression-gated quality check and the --retriever interface. The bundled hybrid engine is a reference implementation, not the product. What this is not:

  • Not a framework or a hosted service — no plugin marketplace, no SaaS. Fork the harness; the retriever is swappable by design.
  • Not state-of-the-art retrieval research — a pragmatic single-user system that knows its own ceiling and stops there.
  • Not a maintained project — a solo operator's personal tool, shared for the methodology. Issues and PRs are welcome but may not be triaged; expect best-effort, no SLA. The eval workflow is an advisory gate (it proves the numbers reproduce), not a support promise.

Other conventional repo furniture — CONTRIBUTING, issue templates, a badge wall — is deliberately omitted, not unfinished. DECISIONS.md records what's left out on purpose and the trigger that would reopen each.

Extending

The core indexes code + docs + commits and nothing else, on purpose. Tool-specific sources (assistant transcripts, code-graphs, other memory stores) plug in as opt-in adapters — see adapters/README.md.

Where this could go

Candidate experiments — each gated on a measured win, none promised — are written up in ROADMAP.md. They're directions, not commitments.

License

MIT — see LICENSE. Use the methodology freely.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hitgate-0.1.0.tar.gz (56.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hitgate-0.1.0-py3-none-any.whl (47.4 kB view details)

Uploaded Python 3

File details

Details for the file hitgate-0.1.0.tar.gz.

File metadata

  • Download URL: hitgate-0.1.0.tar.gz
  • Upload date:
  • Size: 56.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for hitgate-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ba306354037aeaf1570d6728f0cad47b469f0c142a4ae9633ac425c0f121e454
MD5 2b88be5c8fe474ec507cb1a4ce1101f7
BLAKE2b-256 6a4be2827440ac7d8d907c39c2ab14a33f2e71ead5d24282c9af8c3e5bd2889c

See more details on using hashes here.

Provenance

The following attestation bundles were made for hitgate-0.1.0.tar.gz:

Publisher: publish.yml on LucasSantana-Dev/hitgate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file hitgate-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: hitgate-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 47.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for hitgate-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9daea25f1011117f4b96456fa159273e1bed6d364c50e034b8bcb2ee3b0b8195
MD5 b53f1d3eb0930fbbaba36017b7556c76
BLAKE2b-256 9aa170d2758fe990e926ddb21d2b48d02206400350e17e5909bf6650b27a4894

See more details on using hashes here.

Provenance

The following attestation bundles were made for hitgate-0.1.0-py3-none-any.whl:

Publisher: publish.yml on LucasSantana-Dev/hitgate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page