I left this here originally thinking that black and ruff are compatible,
so either could be used as a local formatter. But it turns that while
code formatted with ruff will be unchanged by black, the reverse is not
always true. So let's just enforce using ruff.
We could use flake8 for linting, but ruff is compatible with black
formatting out-of-the-box. It also seems to catch more than flake8,
such as unnecessary f-strings.