AGENTS.md · git:20260815.9c171c9 · 2026-08-15 · sha256 1e135b3ecaec6e97

AGENTS.md git:20260815.9c171c9A

Immutable. This exact content is served forever at /api/v1/blob/1e135b3ecaec6e97.

# AGENTS.md — working in this repo

Written for AI coding agents, and equally readable by a human contributor. If you are an agent
sent here to make a change, read this first; it is short on purpose.

## What this repo is

A two-stage auditor for quote-style RAG citations. Stage 1 (`gate.py`) is a deterministic,
zero-token, zero-dependency substring check over a normalized form. Stage 2 (`judge.py`) is a
burden-of-proof prompt wired to any model through an `llm_call` callable.

The value of this package is the *order* of the two stages: the cheap deterministic question is
asked before the expensive fallible one. A change that lets stage 2 run on a quote stage 1 has
not cleared is a change to the thesis, not to the implementation — say so in the PR.

## Stack and layout

- **Python ≥ 3.9, stdlib only.** `dependencies = []` in `pyproject.toml` is a feature, not an
  oversight. `pytest` is the only extra, and only for tests.
- `src/verbatim_citation_gate/gate.py` — `normalize()`, `quote_gate()`, `GateResult`.
- `src/verbatim_citation_gate/judge.py` — `JUDGE_SYSTEM`, `judge_support()`, `audit_citation()`.
- `tests/test_gate.py` — offline; the judge is exercised through a fake `llm_call`, so the whole
  control flow is covered with no API key and no network.
- `examples/quickstart.py` — the smallest end-to-end use.

## How to verify a change

```bash
pip install -e ".[test]"
pytest -q
```

Paste that output in the PR. If you touched `normalize()` or `quote_gate()`, also state what your
change does to each of the four verdicts the gate can return (`found`, `not_found`,
`misattributed`, and the frankenquote path) — a normalization change is never local, it moves the
boundary of every match in the corpus.

If you touched `JUDGE_SYSTEM`, show a before/after on at least one prompt where the verdict
changes, and one where it must not.

## Conventions

- **Fail closed.** Unparseable judge output must never become `supports`. Any new branch inherits
  this; if you add a code path that can return a verdict, prove what it does on garbage input.
- **No dependencies in the gate.** Ever. If you believe stage 1 needs a library, open an issue
  first — it is a design change.
- Verdicts are strings from a fixed set. Adding one is a breaking change for callers; it needs an
  issue and a version bump, not a quiet commit.
- Type hints on public functions; docstrings that say what the function *refuses* to do, not only
  what it does.

## Boundaries — what needs a human

- **Changing what counts as a match.** Widening `normalize()` makes fabrications easier to pass;
  narrowing it breaks real quotes. Either direction is a judgement call with an issue attached.
  (See the open issue about non-Latin scripts — that one is a bug, and it is `accepted`.)
- **Publishing to PyPI**, version bumps, and anything touching `[project]` metadata.
- **Loosening the fail-closed rule** — not a matter of taste; open an issue and expect pushback.

## The deal

Your copyright stays yours, there is no CLA, and issues labelled `accepted` are free to take —
comment "claiming this". Full terms:
[CONTRIBUTING.md](https://github.com/tonydzi/.github/blob/main/CONTRIBUTING.md).

If an AI wrote your change, say so in the PR and confirm you ran it. That is welcome here — we do
it daily. Unread generated code is the one thing that gets closed on sight.