AGENTS.md · git:20261020.72d5a31 · 2026-10-20 · sha256 8b7f53a718f6f9dd

AGENTS.md git:20261020.72d5a31A

Immutable. This exact content never changes and is served at /api/v1/blob/8b7f53a718f6f9dd.

# Instructions for coding agents

See `docs/development-contributing.md` for the development setup and the test helpers. Run the commands below, and
the ones failures print, inside the development environment (`.tox/dev` from `tox run -e dev`, or prefix them with
`uv run` in a uv environment).

## Tests

- Add e2e tests with the existing helpers (`run_main_and_assert`, `assert_output`, ...); no expected path is needed.
- Never add or change an expected path to create, split, share or rename stored output: pytest names, shares and
  splits stored files itself.
- Run `pytest '<test>'` and commit every file it writes (`tests/data/expected/**`, `tests/**/*.refs`).
- To accept changed output: `pytest '<test>' --inline-snapshot=fix`.
- Never edit `.refs` or expected files by hand (each `.refs` file starts with a `# Generated by pytest` header);
  failures print the exact command to run.
- A CI failure that ends with `dcg-expected-fix:` is fixed by the `python scripts/fix_expected.py ...` command it prints.

## Adding an option

- Follow "Adding a New CLI Option" in `docs/development-contributing.md`, including Step 4: register the option with
  the recommender, or `test_every_option_is_registered_with_the_recommender` fails.

## Changing the parser

[Model processing passes](docs/architecture.md#model-processing-passes) lists the passes in order, when each fact
becomes final and where a decision belongs.

- Before writing a fix, list the facts the decision reads. Put the decision after the pass where the last of them
  becomes final, or move the pass that sets the fact earlier. Never predict what a later pass will do.
- Decide scope on the schema object, for example a marker set where the document is rewritten, not from the parse
  path.
- Don't change nodes in place that other models share; replace them in each holder through the generation store's
  mutation APIs.
- `scripts/check_generation_store_usage.py` checks fact writes against `parser/_facts.py`; its allowlist
  `scripts/generation_store_allowlist.toml` only shrinks. Route a new write through the store instead of adding an
  entry, and remove the entries of writes you route.
- Every test also runs the runtime write guard (`DATAMODEL_CODE_GENERATOR_CHECK_STORE_WRITES`): a test that fails with
  "... is written outside store operations" made a write the allowlist does not list; route it the same way. Witness
  entries (`target = "<datamodel-codegen args>"`) only record runtime-only writes that existed before the guard; never
  add one for a new write.
- Every pass has a declaration in `parser/_pass_decls.py` and an entry in the pipeline tables of `Parser`, whose runner
  runs it inside `fact_pass(DECL)`; a pass that conflicts with an earlier one of its segment needs an `after` edge with
  its reason (`scripts/check_pass_pipeline.py` says which). Every test runs the finality check
  (`DATAMODEL_CODE_GENERATOR_CHECK_FACT_FINALITY`): "... undeclared: ..." means the pass writes a fact its declaration
  does not list; the other rules report writes after a fact is final.
- For a fix that spans passes, write the design (the facts it reads, where each becomes final, the outputs it changes,
  and since which release) and have it reviewed before implementing.