dc40a684

Suppress a finding, and turn a rule off

Replaces #6, which GitHub will not let me reopen: I merged it by mistake and
then took it back off master.

Stacked on #9. Review that first; this diff is against it.

Adds `CommentBareTodo` from `comments.md`, and with it the two things that make
a checker adoptable at all: a way to say no to a finding, and a way to turn a
rule off.

109 files, 3,380 lines. 234 tests.

## The keystone

**A suppression that answers for nothing is an error.** That is what makes
on-by-default with unlimited local escapes safe, and it is why there is no
config file of exceptions that nobody reads. All of these fail the run in their
own right:

- one that has outlived the finding it was written for
- one that answers for more than one finding at once
- one naming a rule nothing answers to, or a rule this repository turned off
- one with no reason
- one written where it cannot attach to anything
- one in a file no cabal component claims, which is watched by nobody

**A suppression the report offers must be one the parser accepts.** The report
tells you what to write and where. If the next run then rejected it, the tool
would have lied.

## Which rules run

`hopinion.yaml` at the repository root, not a flag, and there is no flag
anywhere. The development loop and CI read the same file, so they cannot
disagree about which rules they ran. Every way of getting that file wrong is
refused rather than ignored, because each one reads as a rule turned off and
behaves as a rule still running: an unknown key, a name that is not a rule id,
a rule nothing answers to, a document that is not a mapping.

**The whole file goes through the codec.** Nothing here reads a key or a value
for itself. Even the list of keys the file may hold comes off
`jsonObjectSchemaViaCodec` rather than being written down beside the codec,
because a second list is a second answer: a field added to one and not the other
is a setting the codec accepts and the reader refuses. A property asserts that
whatever the codec writes, the reader reads back, and blanking that derived key
list fails it.

`ChoicesError` says which of the four things went wrong; `ChoicesFileError`
pairs it with the file it is about, and one renderer turns that into the sentence
a reader is shown. `parseChoices` is handed bytes and no path, so what a file
means cannot depend on what it is called.

## What this rests on

The comment attachment pass, computed here in an explicit pass over spans
rather than taken from where exact-print annotations put a comment. That
placement answers "where must this be reprinted", not "what is this about", and
the two differ exactly where it would matter. The most consequential decision
in it: **a blank line between a comment and the code below means the comment is
not attached to that code.**

## Still not here

One level, no fact store, nothing reads what the compiler wrote down. Neither
rule needs any of it. weeder removed `readChoicesFrom` on the way in, because
the flag it exists for belongs to a command that does not exist yet.

## Review loop

```
nix flake check
nix develop --command cabal test hopinion-test --test-options="--ai-executor"
```

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 0s 1s 1s 1s 0s
Eval 1m59s 9s 9s 2m09s 1m58s
Build 2m08s 0s 5m40s 7m49s 5m40s
Suite 0s 10s 7m48s 7m49s 7m38s

Timeline

0s2m