AGENTS.md · git:20261006.687c3d9 · 2026-10-06 · sha256 c32e2f6ddafda6c7
AGENTS.md git:20261006.687c3d9A
Immutable. This exact content never changes and is served at /api/v1/blob/c32e2f6ddafda6c7.
# AGENTS.md
`claude-code-gauntlet` is a Claude Code marketplace plugin — no server, database, or Docker.
"Running it" means running the test suites and the stdlib-only Python pipeline scripts.
Directory-scoped rules live in nested `AGENTS.md` files: `workflows/`, `scripts/`, `agents/`.
Read the one for the directory you are editing.
## Design
**Build the mechanism, not the instruction.** Whatever code, a schema, a data structure, or a
removed capability can enforce, it must. Prose in an instruction, prompt, or agent file is the
fallback for what cannot be made structural.
**"Add more text" is a design smell.** If that is the fix under consideration, the shape is
wrong — change the shape.
**Extending should cost one edit.** A new dimension, field, or agent belongs in one place. If it
takes coordinated edits across N files, fix the shape rather than documenting the ritual.
## Tooling boundary (no npm in the tree)
- No `package.json`, lockfile, or `node_modules` is tracked, except the vendored scorer's own
`bench/vendor/code-review-benchmark/uv.lock`.
- The shipped pipeline runtime (`workflows/src`, `workflows/pipeline.js`) has zero
dependencies — language globals plus the host-injected `agent`/`parallel`/
`pipeline`/`args` only. No import survives into the bundle: `build.js` strips
relative sibling imports and *fails* on any other specifier, which would ship
as an undefined reference. See `workflows/AGENTS.md` for the sandbox surface.
- Pinned static binaries in CI are permitted (e.g. Biome for the `js-lint` job).
Bumps are manual and deliberate; do not add a package manifest to get Dependabot
coverage for those pins.
- This does not forbid npm on the CI runner (`validate.yml` already installs the
Claude Code CLI globally).
## Python
- **stdlib only, Python 3.10.** Nothing under `scripts/` imports a non-stdlib module;
`[dependency-groups]` CI tooling is exempt.
- **Language-agnostic.** Never assume the reviewed codebase's language: `--exclude-dir` for
non-source directories, never `--include=*.py`. `scripts/gauntlet/fix_tasks.py` documents the one
exception, its build-system table.
- **Shape of new and touched code.** A JSON wire shape is a `TypedDict`; an internal record is a
frozen slotted dataclass; a closed vocabulary is a `Literal`. A function unless state or
polymorphism earns a class. One implementation per mechanism.
- **New and rewritten tests are pytest functions**: fixtures, `parametrize` tables, builders that
produce inputs only. Prove a behaviour once, at the boundary callers use.
- **Comments say why:** a constraint, a measurement and how it was taken, or a twin. No history
or issue numbers in code.
## Tests
```bash
python -m pytest tests/ -q # pipeline + boundary parity
python -m pytest bench/tests/ -q # benchmark harness self-tests
node --test workflows/test/*.test.js # needs Node 24; the bare directory is not a valid target
```
Coverage gates (CI; JS on Node 24.18.0, Python on 3.12). Each command is
self-contained and byte-identical to the matching `run:` body in
`.github/workflows/ci.yml`. Locally, coverage data files must stay out of the
repo tree (an in-tree data file trips the bench plugin-mutation guard):
```bash
COVERAGE_FILE="$(mktemp -d)/.coverage" python -m pytest tests/ -q \
--cov=scripts --cov=.github --cov-fail-under=94.4
COVERAGE_FILE="$(mktemp -d)/.coverage" python -m pytest bench/tests/ -q \
--cov=bench --cov-fail-under=88.6
LCOV="$(mktemp -d)/js-coverage.lcov" && node --test --experimental-test-coverage \
--test-coverage-include='workflows/src/*.js' \
--test-coverage-include='workflows/build.js' \
--test-coverage-lines=98.6 \
--test-coverage-branches=91.5 \
--test-coverage-functions=98.5 \
--test-reporter=spec --test-reporter-destination=stdout \
--test-reporter=lcov --test-reporter-destination="$LCOV" \
workflows/test/*.test.js \
&& node workflows/test/tools/check_coverage_presence.mjs "$LCOV"
```
Floors: Python 94.4 / 88.6, JS 98.6 / 91.5 / 98.5, each pinned from a PR's CI measurement;
the ratchet history is in git. Policy: a floor sits no more than 1.0 pp below the CI
measurement for that gate; lower a floor only in the PR that causes the drop, with
the reason in the body; raise when measured headroom exceeds 1.0 pp. A sudden
multi-point JS drop usually means a deleted fixture group or an unloaded
module (presence check); a sudden multi-point Python drop means broken
subprocess capture — fix capture, do not lower.
`workflows/test/tools/record_parity.py` is test infrastructure, outside Python
scopes. JS measures `workflows/build.js` + loaded `workflows/src/*.js` via the
include allowlist; `pipeline_entry.js` is exempt from presence only.
After editing `workflows/src/*.js`, rebuild and confirm the bundle is unchanged:
```bash
node workflows/build.js && git diff --exit-code workflows/pipeline.js
```
**A regression test must fail against the bug it names.** Verify that by mutating the
implementation and watching it go red — not by reading the test. Mutate the whole mechanism; a
partial mutation falls through to a neighbouring fallback and passes misleadingly.
## Lint
`pre-commit run --all-files` is the gate.
- `markdownlint-fix` rewrites files in place, then reports failure if it changed anything. A
"Failed / files were modified by this hook" result means the fixes are already applied — re-stage
and re-run.
- `CHANGELOG.md` is excluded from that hook deliberately; it is regenerated on every release. Do
not re-add it.
- pre-commit sees **git-tracked files only**. A new file is invisible to every hook until
`git add`, so it can pass locally and fail in CI. Stage new files before trusting a green run.
- **Never put the literal skip-ci token in a commit message, even when writing about it.** GitHub
Actions scans the whole message, so the workflows silently never run — and a squash merge carries
it onto `main`. Call it "the skip-ci token".
## Writing JSON for pipeline scripts
Use `python3 -c "import json; ..."`. Not the Write tool (it requires a prior Read), and not a
heredoc (zsh corrupts `!`).
## Output directory
`{output_dir}` defaults to `.code-gauntlet/` (repo-local, gitignored). Override with
`$CODE_GAUNTLET_OUTPUT_DIR`.
## Layout
```text
claude-code-gauntlet/ <- plugin root
├── agents/ <- subagent contracts
├── scripts/ <- retained Python (verify_findings.py, post_review.py, ...)
├── workflows/ <- JS pipeline: src/*.js, build.js, pipeline.js (generated), test/
├── bench/ <- benchmark harness (stdlib-exempt)
├── tests/ <- pytest suite
└── skills/code-gauntlet/ <- skill base directory
```
`{plugin_root}` is two levels above the skill base directory. Never locate it by searching the
filesystem — a `find` hit picks arbitrarily among every cached plugin version.
Session scratch — design memos, implementation plans, handoff notes — never lands in the tree; it
belongs in the PR description or issue thread. Tracked docs are allowlisted by
`tests/test_docs_registry.py`; a durable doc is added there in the same commit, with a reason.
## Contribution surface
`tests/test_contribution_surface.py` covers the issue-form schema, `.github/labels.json`, and the
CI commands quoted in `CONTRIBUTING.md`. Two things it cannot reach, both needing write access
after a merge:
- GitHub serves issue forms from the default branch only, and refuses to render an invalid form
without erroring. A plan that tests form submission before the merge is mis-ordered.
- Labels must exist before a form can apply one; GitHub drops unknown labels silently. Sync with
`python3 .github/labels_diff.py --commands --repo <owner>/<repo>`, confirm with `--live -`. See
`docs/maintainer-issues.md`.
## Session output style
Canonical rules and the regeneration mechanism are documented in `scripts/gauntlet/style.py`.