8701934e

By: Tom Sydney Kerckhove <syd@cs-syd.eu>

hopinion: static analysis that enforces the code review standards

A tool that reads a Haskell repository and reports the places it disagrees with
the standards in ~/.config/home-manager rules, one rule at a time, with a
suppression mechanism designed for adopting it into a repository that has never
been held to them.

Three checks, chosen to be one per level and one per combinator shape rather
than for their own sake:

- CommentBareTodo, module level. A TODO with nothing pointing at it.
- HsGenValidInGenPackage, package level. A generator compiled into a library.
- TestGenValidSpecPerGenValid, project level. A GenValid instance with no
  genValidSpec in the test suite of the package that declares it.

The point is not the three. It is that adding the fourth is a module and a line:
a rule owns its own metadata, its own table, and what it writes into that table
out of one module, and the envelope never learns what any of it means. See
plan/modularity.md for the shape and plan/rule-triage.md for the other 122.

What is here beyond the rules:

- A fact boundary. Each package is read once into a SQLite store, and the
  project layer answers from those stores alone, so a Nix build can be one
  derivation per package plus one for the whole repository. Changing one
  package's source rebuilds three derivations rather than all of them.
- Artifacts. The obligation rule reads .hie and .hi files, because a rule that
  cannot see generated code cannot answer for the 114 modules on the primary
  target that generate some. Given any artifact directory, every parsed module
  must have both, and a build that does not cover one fails by name rather than
  quietly narrowing the check.
- No third answer. A rule reports something to fix or reports nothing. What used
  to be an abstention is now either a demand on the build, which fails the run,
  or a question narrowed until it is answerable. See plan/generated-code.md.
- Annotations. `-- [allow:RuleId] reason`, matched by scope and span, with the
  suppression that has outlived its finding reported as an error in its own
  right, and the one that suppresses several at once reported as too broad.
  `hopinion annotate` writes them at every finding, which is how a repository
  adopts this in one reviewable diff.
- Nix builders that another flake consumes: `checksFor { src, haskellPackages }`
  gives one check per package plus a project check, and takes the package set so
  the artifacts exist.

Every finding says which rule, what the standards say, why the rule exists, and
the exact line to write the suppression above.

Held to itself from the first commit, and to two repositories that are not this
one. On nix-ci: 122 obligation findings, all of them genuine, every one now a
written genValidSpec; 33 bare TODOs, all genuine. plan/corpus.md has the
measurements, including what the check costs a repository that runs it.

plan/ is planning material and is deletable in one rm. README.md and CLAUDE.md
are the entry points and survive it.