Thanks for your interest. This is a personal research project — drive-by PRs are welcome, but please open an issue first for anything beyond a typo or single-line fix so we can align on scope.
Prerequisites: Python 3.12, uv, Docker + Docker Compose, and a CUDA-capable GPU if you want to run the visual or rerank stack locally.
git clone https://github.com/NorthernLightx/spectrarag.git
cd spectrarag
uv sync --extra dev
cp .env.example .env
docker compose up -d qdrant postgres langfuse ollama
docker exec rag-ollama ollama pull bge-m3uv run ruff check .
uv run ruff format --check .
uv run mypy src tests scripts
uv run pytest -v --cov=src --cov-report=term-missing
uv run python -m scripts.eval_retrieval_ci --output data/eval/runs/retrieval-ci.json
uv run python -m scripts.check_regression \
--baseline data/eval/baseline_retrieval.json \
--candidate data/eval/runs/retrieval-ci.json \
--metrics ndcg_at_5 recall_at_10 mrr --threshold 0.05scripts/ is three tiers by stability:
- top level — maintained entrypoints + the data-reproduction pipeline
(
eval_run,check_regression,ingest,render_pages,bootstrap_corpus,eval_visual,fetch_*, …). CI, the root README, anddocs/evals.mdinvoke these aspython -m scripts.<name>; their import paths are an API contract — relocating one is a cross-repo change (CI + docs + ADRs in the same commit), not a move. scripts/experiments/— the ADR-linked DoE / study / probe drivers from the retrieval investigation (ADRs 0012–0016).scripts/legacy/— superseded one-off and earlier-phase scripts kept for the historical record (referenced by older ADRs /docs/results.md).
experiments/ and legacy/ are intentionally exempt from the CI
ruff / format / mypy-strict gates (pyproject.toml extend-exclude + mypy
exclude): they are audit and reproducibility artifacts, not library code.
New exploratory scripts start in experiments/; nothing graduates out of
legacy/. A script stays top-tier (gated) if it is a documented
entrypoint, has a maintained test, or is referenced by maintained
code/CLI — even if its approach was superseded. legacy/ is for
untested, unreferenced, frozen spikes.
We use Conventional Commits. The
pattern is type(scope): subject, lowercase, imperative mood. Common types:
feat(<scope>):— new behaviorfix(<scope>):— bug fixdocs(<scope>):— README / ADR / docstring changestest(<scope>):— test-only changesrefactor(<scope>):— no behavior changechore:— repo housekeepingci:/build:— workflow / build configperf(<scope>):— perf-only changes
Examples from this repo:
feat(eval): --refusal-score-threshold CLI flag wires gate into eval_run
fix(ci): drop unused pull-requests:write permission from deploy workflow
docs(adr): 0006 — OOC refusal gate (accepted opt-in; judge artifact noted)
chore(deploy): tighten az containerapp create args; default min-replicas=0
Keep commits atomic — each commit should pass pytest -m "not integration"
on its own. If a refactor and a feature ride together, split them.
Any non-obvious choice gets an ADR in docs/decisions/NNNN-<slug>.md.
Mirror the structure of the existing ones (Status / Date / Phase / Context /
Implementation / Decision / Caveats / References). PRs that change behavior
in an area covered by an ADR should reference or supersede it.
This repo is open-source by design. Be defensive about leakage:
- ❌ Secrets — API keys, tokens, DSNs, private keys, OAuth secrets.
.envis gitignored;.env.exampleis the only sanctioned secrets-ish file (with placeholder values only). Pre-commitgitleakshook is the safety net, not a substitute for care. - ❌ Personal info — your home directory paths (
/c/Users/<you>/...,/home/<you>/...,C:\Users\<you>\...), your real-name email if you prefer pseudonymity, internal hostnames, customer data, anything you wouldn't paste into a public Gist. Use~or env vars in pasted shell commands. - ❌ Large binary blobs —
data/papers/,data/pages/, model weights,.parquet, anything > 1 MB unless it's a versioned artifact likedata/eval/baseline.json. Use the fetch scripts to reproduce locally. - ❌ Ephemeral local state —
.venv/,logs/,.coverage*,__pycache__/,qdrant_storage/,postgres_data/, IDE configs. These are covered by.gitignore; if you introduce a new tool whose state isn't already gitignored, add it to.gitignorein the same PR. - ❌ Work-in-progress notes —
STATUS.md-style running logs, scratchpad plans, one-off task lists, anything intended as a personal thinking aid. What belongs in the repo is the ADR after the decision is made, not the deliberation that led to it.
If you accidentally commit something sensitive, do not just delete it in a
follow-up commit — the data stays in history. Use git filter-repo to
purge the file from every commit, then force-push and rotate the secret.
See GitHub's removing sensitive data guide.
Recommended (mirrors CI + adds secret scanning):
uv pip install pre-commit
pre-commit installAfter install, git commit runs ruff, gitleaks, basic file-shape checks
locally. CI runs the same set on push.
See SECURITY.md. Please do not file public issues for security-impacting bugs — use GitHub's private vulnerability reporting.