diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,24 @@
 The format is based on [Keep a Changelog 1.1](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/).
 
+## [0.4.0.1] - 2026-07-12
+
+### Added
+
+- Support for GHC 9.2 & GHC 9.4
+- `lint` flag
+- Support for doctest-0.25 (only needed by tests)
+
+## [0.4.0.0] – 2026-01-15
+
+### Added
+
+- Support for GHC 9.14
+
+### Removed
+
+- Support for Cabal <3.10.3 (due to haskell/cabal#9375)
+
 ## [0.3.0.0] – 2025-11-05
 
 ### Added
@@ -77,10 +95,12 @@
 
 - Initial release of this package.
 
+[0.4.0.1]: https://github.com/sellout/no-recursion/compare/v0.4.0.0...v0.4.1
+[0.4.0.0]: https://github.com/sellout/no-recursion/compare/v0.3.0.0...v0.4.0.0
 [0.3.0.0]: https://github.com/sellout/no-recursion/compare/v0.2.0.0...v0.3.0.0
 [0.2.0.0]: https://github.com/sellout/no-recursion/compare/v0.1.2.3...v0.2.0.0
 [0.1.2.3]: https://github.com/sellout/no-recursion/compare/v0.1.2.2...v0.1.2.3
 [0.1.2.2]: https://github.com/sellout/no-recursion/compare/v0.1.2.0...v0.1.2.2
 [0.1.2.0]: https://github.com/sellout/no-recursion/compare/v0.1.1.0...v0.1.2.0
 [0.1.1.0]: https://github.com/sellout/no-recursion/compare/v0.1.0.0...v0.1.1.0
-[0.1.0.0]: https://github.com/sellout/no-recursion/releases/tag/v0..1.0.0
+[0.1.0.0]: https://github.com/sellout/no-recursion/releases/tag/v0.1.0.0
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,5 @@
 LicenseListVersion: 3.27.0
-SPDX-License-Identifier: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-commercial
+SPDX-License-Identifier: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
 
 The individual licenses are included in files named “LICENSE.” with a suffix
 that matches each license’s identifier.
diff --git a/LICENSE.commercial b/LICENSE.commercial
deleted file mode 100644
--- a/LICENSE.commercial
+++ /dev/null
@@ -1,3 +0,0 @@
-This software is available for commercial licensing with flexible terms.
-
-Contact licensing@technomadic.org for more information.
diff --git a/LICENSE.proprietary b/LICENSE.proprietary
new file mode 100644
--- /dev/null
+++ b/LICENSE.proprietary
@@ -0,0 +1,3 @@
+This software is available for proprietary licensing with flexible terms.
+
+Contact licensing@technomadic.org for more information.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
 # NoRecursion plugin
 
+[![Hackage Version](https://img.shields.io/hackage/v/no-recursion)](https://hackage.haskell.org/package/no-recursion)
 [![Packaging status](https://repology.org/badge/tiny-repos/haskell:no-recursion.svg)](https://repology.org/project/haskell:no-recursion/versions)
 [![latest packaged versions](https://repology.org/badge/latest-versions/haskell:no-recursion.svg)](https://repology.org/project/haskell:no-recursion/versions)
 
@@ -103,6 +104,78 @@
 
 Unfortunately, because `sconcat` (and `mconcat`) require lazy lists (`[]`), it’s not possible to write a total definition for these.
 
+### mitigating [dependency hell](https://en.wikipedia.org/wiki/Dependency_hell)
+
+As NoRecursion is effectively a linter, you don’t have to depend on it in every case (although, be careful, because different GHC versions may catch (or induce) different occurrences of recursion).
+
+It’s easy to conditionalize the use of NoRecursion by adding the following (with a suitable replacement for `_`) to the stanzas in your Cabal file:
+
+```cabal
+  if _
+    build-depends:
+      no-recursion ^>= {x.y.z},
+    ghc-options:
+      -fplugin=NoRecursion
+```
+
+If the plugin isn’t enabled, any `-fplugin-opt=NoRecursion:…` elsewhere will simply be ignored.
+
+Here are a couple concrete situations where this is useful.
+
+#### you support a some environment that NoRecursion doesn’t
+
+```cabal
+  if impl(ghc >= 9.6.1) && impl(ghc < 9.14.1) && !arch(i386)
+```
+
+With the above condition, NoRecursion will only be used with GHC 9.6.1–9.12 and on architectures that aren’t i386 (32-bit).
+
+Of course, we would love to have NoRecursion work in all your environments, so please [open an issue](https://github.com/sellout/no-recursion/issues/new?title=Add+support+for+&labels=dependencies,enhancement) if you find yourself using this approach. Since it’s a compiler plugin, it’s more sensitive to GHC changes than most code, so just ignoring dependency bounds is less likely to work.
+
+#### you want to get out of consumers’ way
+
+A common situation is depending on a version that isn’t widely available (this can happen with Stackage or maybe it’s an unpublished revision that you’ve added as a `source-repository-package`).
+
+In this case, you can define a flag in your Cabal file
+
+```cabal
+flag verify-no-recursion
+  description:
+    Compile with "NoRecursion" enabled. This is intended for developers of this
+    package.
+  default: False
+  manual: True
+```
+
+And then conditionalize on that
+
+```cabal
+  if flag(verify-no-recursion)
+```
+
+In cabal.project, you should also add
+
+```cabal
+flags:
+  +verify-no-recursion
+```
+
+which will ensure that the flag doesn’t get automatically disabled when doing local development. You don’t want to discover that you had the `no-recursion` bounds set incorrectly only after a user complains that they can’t compile your library because of recursion errors.
+
+If you’re using Stack, you can achieve the same thing with
+
+```yaml
+flags:
+  local-package:
+    verify-no-recursion: true
+  another-local-package:
+    verify-no-recursion: true
+```
+
+Note that with Stack you need to set the flag separately for each package in your project.
+
+You can see an example of this (with Cabal) in the [duoids](https://github.com/sellout/duoids/blob/6de6468d173fdb8b95db3789d65984289b7b42d5/core/duoids.cabal#L64-L70) project.
+
 ## versioning
 
 This project largely follows the [Haskell Package Versioning Policy](https://pvp.haskell.org/) (PVP), but is more strict in some ways.
@@ -185,7 +258,7 @@
 
 ## licensing
 
-This package is licensed under [The GNU AGPL 3.0 or later](./LICENSE). If you need a license for usage that isn’t covered under the AGPL, please contact [Greg Pfeil](mailto:greg@technomadic.org?subject=licensing%20no-recursion).
+This package is licensed under [The GNU AGPL 3.0 only](./LICENSE). If you need a license for usage that isn’t covered under the AGPL, please contact [Greg Pfeil](mailto:greg@technomadic.org?subject=licensing%20no-recursion).
 
 You should review the [license report](docs/license-report.md) for details about dependency licenses.
 
diff --git a/docs/license-report.md b/docs/license-report.md
--- a/docs/license-report.md
+++ b/docs/license-report.md
@@ -9,37 +9,65 @@
 | Name | Version | [SPDX](https://spdx.org/licenses/) License Id | Description | Also depended upon by |
 | --- | --- | --- | --- | --- |
 | **`base`** | [`4.20.0.0`](http://hackage.haskell.org/package/base-4.20.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/base-4.20.0.0/src/LICENSE) | Core data structures and operations | *(core library)* |
-| **`ghc`** | [`9.10.1`](http://hackage.haskell.org/package/ghc-9.10.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-9.10.1/src/LICENSE) | The GHC API |  |
+| **`ghc`** | [`9.10.1`](http://hackage.haskell.org/package/ghc-9.10.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-9.10.1/src/LICENSE) | The GHC API | `ghc-compat-plugin`, `henforcer`, `pollock` |
+| `ghc-compat-plugin` | [`0.1.0.1`](http://hackage.haskell.org/package/ghc-compat-plugin-0.1.0.1) | [`AGPL-3.0-only`](http://hackage.haskell.org/package/ghc-compat-plugin-0.1.0.1/src/LICENSE.AGPL-3.0-only) | Eases support for multiple GHC versions |  |
+| `henforcer` | [`1.0.0.1`](http://hackage.haskell.org/package/henforcer-1.0.0.1) | [`MIT`](http://hackage.haskell.org/package/henforcer-1.0.0.1/src/LICENSE) | GHC plugin to enforce user specified rules on code. |  |
 
 ## Indirect transitive dependencies
 
 | Name | Version | [SPDX](https://spdx.org/licenses/) License Id | Description | Depended upon by |
 | --- | --- | --- | --- | --- |
-| **`array`** | [`0.5.7.0`](http://hackage.haskell.org/package/array-0.5.7.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/array-0.5.7.0/src/LICENSE) | Mutable and immutable arrays | `binary`, `containers`, `deepseq`, `ghc`, `ghci`, `stm` |
-| **`binary`** | [`0.8.9.2`](http://hackage.haskell.org/package/binary-0.8.9.2) | [`BSD-3-Clause`](http://hackage.haskell.org/package/binary-0.8.9.2/src/LICENSE) | Binary serialisation for Haskell values using lazy ByteStrings | `ghc`, `ghc-boot`, `ghci` |
-| **`bytestring`** | [`0.12.1.0`](http://hackage.haskell.org/package/bytestring-0.12.1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/bytestring-0.12.1.0/src/LICENSE) | Fast, compact, strict and lazy byte strings with a list interface | `binary`, `filepath`, `ghc`, `ghc-boot`, `ghci`, `os-string`, `unix` |
-| **`containers`** | [`0.7`](http://hackage.haskell.org/package/containers-0.7) | [`BSD-3-Clause`](http://hackage.haskell.org/package/containers-0.7/src/LICENSE) | Assorted concrete container types | `binary`, `ghc`, `ghc-boot`, `ghc-heap`, `ghci`, `hpc` |
-| **`deepseq`** | [`1.5.0.0`](http://hackage.haskell.org/package/deepseq-1.5.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/deepseq-1.5.0.0/src/LICENSE) | Deep evaluation of data structures | `bytestring`, `containers`, `filepath`, `ghc`, `ghc-boot`, `ghci`, `hpc`, `os-string`, `pretty`, `process`, `time` |
-| **`directory`** | [`1.3.8.3`](http://hackage.haskell.org/package/directory-1.3.8.3) | [`BSD-3-Clause`](http://hackage.haskell.org/package/directory-1.3.8.3/src/LICENSE) | Platform-agnostic library for filesystem operations | `ghc`, `ghc-boot`, `hpc`, `process` |
+| `ansi-terminal` | [`1.1.5`](http://hackage.haskell.org/package/ansi-terminal-1.1.5) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ansi-terminal-1.1.5/src/LICENSE) | Simple ANSI terminal support | `prettyprinter-ansi-terminal` |
+| `ansi-terminal-types` | [`1.1.3`](http://hackage.haskell.org/package/ansi-terminal-types-1.1.3) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ansi-terminal-types-1.1.3/src/LICENSE) | Types and functions used to represent SGR aspects | `ansi-terminal` |
+| **`array`** | [`0.5.7.0`](http://hackage.haskell.org/package/array-0.5.7.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/array-0.5.7.0/src/LICENSE) | Mutable and immutable arrays | `attoparsec`, `binary`, `containers`, `deepseq`, `ghc`, `ghci`, `integer-logarithms`, `megaparsec`, `stm`, `text` |
+| `attoparsec` | [`0.14.4`](http://hackage.haskell.org/package/attoparsec-0.14.4) | [`BSD-3-Clause`](http://hackage.haskell.org/package/attoparsec-0.14.4/src/LICENSE) | Fast combinator parsing for bytestrings and text | `pollock` |
+| `attoparsec` | [`0.14.4`](http://hackage.haskell.org/package/attoparsec-0.14.4) | [`BSD-3-Clause`](http://hackage.haskell.org/package/attoparsec-0.14.4/src/LICENSE) | Fast combinator parsing for bytestrings and text | `attoparsec` |
+| **`binary`** | [`0.8.9.2`](http://hackage.haskell.org/package/binary-0.8.9.2) | [`BSD-3-Clause`](http://hackage.haskell.org/package/binary-0.8.9.2/src/LICENSE) | Binary serialisation for Haskell values using lazy ByteStrings | `ghc`, `ghc-boot`, `ghci`, `scientific`, `text` |
+| **`bytestring`** | [`0.12.1.0`](http://hackage.haskell.org/package/bytestring-0.12.1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/bytestring-0.12.1.0/src/LICENSE) | Fast, compact, strict and lazy byte strings with a list interface | `attoparsec`, `binary`, `case-insensitive`, `filepath`, `ghc`, `ghc-boot`, `ghci`, `hashable`, `megaparsec`, `os-string`, `scientific`, `text`, `tomland`, `unix` |
+| `case-insensitive` | [`1.2.1.0`](http://hackage.haskell.org/package/case-insensitive-1.2.1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/case-insensitive-1.2.1.0/src/LICENSE) | Case insensitive string comparison | `megaparsec` |
+| `clock` | [`0.8.4`](http://hackage.haskell.org/package/clock-0.8.4) | [`BSD-3-Clause`](http://hackage.haskell.org/package/clock-0.8.4/src/LICENSE) | High-resolution clock functions: monotonic, realtime, cputime. | `extra` |
+| `colour` | [`2.3.7`](http://hackage.haskell.org/package/colour-2.3.7) | [`MIT`](http://hackage.haskell.org/package/colour-2.3.7/src/LICENSE) | A model for human colour/color perception | `ansi-terminal`, `ansi-terminal-types` |
+| **`containers`** | [`0.7`](http://hackage.haskell.org/package/containers-0.7) | [`BSD-3-Clause`](http://hackage.haskell.org/package/containers-0.7/src/LICENSE) | Assorted concrete container types | `attoparsec`, `binary`, `ghc`, `ghc-boot`, `ghc-heap`, `ghci`, `hashable`, `henforcer`, `hpc`, `megaparsec`, `pollock`, `scientific`, `tomland` |
+| **`deepseq`** | [`1.5.0.0`](http://hackage.haskell.org/package/deepseq-1.5.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/deepseq-1.5.0.0/src/LICENSE) | Deep evaluation of data structures | `attoparsec`, `bytestring`, `case-insensitive`, `containers`, `dlist`, `filepath`, `ghc`, `ghc-boot`, `ghci`, `hashable`, `hpc`, `megaparsec`, `os-string`, `pretty`, `primitive`, `process`, `scientific`, `text`, `time`, `tomland`, `unordered-containers`, `validation-selective` |
+| **`directory`** | [`1.3.8.3`](http://hackage.haskell.org/package/directory-1.3.8.3) | [`BSD-3-Clause`](http://hackage.haskell.org/package/directory-1.3.8.3/src/LICENSE) | Platform-agnostic library for filesystem operations | `extra`, `filepattern`, `ghc`, `ghc-boot`, `hpc`, `process` |
+| `dlist` | [`1.0`](http://hackage.haskell.org/package/dlist-1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/dlist-1.0/src/license.md) | Difference lists | `henforcer` |
 | **`exceptions`** | [`0.10.7`](http://hackage.haskell.org/package/exceptions-0.10.7) | [`BSD-3-Clause`](http://hackage.haskell.org/package/exceptions-0.10.7/src/LICENSE) | Extensible optionally-pure exceptions | `filepath`, `ghc`, `os-string`, `semaphore-compat` |
-| **`filepath`** | [`1.5.2.0`](http://hackage.haskell.org/package/filepath-1.5.2.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/filepath-1.5.2.0/src/LICENSE) | Library for manipulating FilePaths in a cross platform way. | `directory`, `ghc`, `ghc-boot`, `ghci`, `hpc`, `process`, `unix` |
+| `extra` | [`1.8.1`](http://hackage.haskell.org/package/extra-1.8.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/extra-1.8.1/src/LICENSE) | Extra functions I use. | `filepattern` |
+| **`filepath`** | [`1.5.2.0`](http://hackage.haskell.org/package/filepath-1.5.2.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/filepath-1.5.2.0/src/LICENSE) | Library for manipulating FilePaths in a cross platform way. | `directory`, `extra`, `filepattern`, `ghc`, `ghc-boot`, `ghci`, `hashable`, `hpc`, `process`, `unix` |
+| `filepattern` | [`0.1.3`](http://hackage.haskell.org/package/filepattern-0.1.3) | [`BSD-3-Clause`](http://hackage.haskell.org/package/filepattern-0.1.3/src/LICENSE) | File path glob-like matching | `henforcer` |
 | **`ghc-bignum`** | [`1.3`](http://hackage.haskell.org/package/ghc-bignum-1.3) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-bignum-1.3/src/LICENSE) | GHC BigNum library | `ghc-internal` |
 | **`ghc-boot`** | [`9.10.1`](http://hackage.haskell.org/package/ghc-boot-9.10.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-boot-9.10.1/src/LICENSE) | Shared functionality between GHC and its boot libraries | `ghc`, `ghci` |
-| **`ghc-boot-th`** | [`9.10.1`](http://hackage.haskell.org/package/ghc-boot-th-9.10.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-boot-th-9.10.1/src/LICENSE) | Shared functionality between GHC and the @template-haskell@ library | `ghc-boot`, `template-haskell` |
+| **`ghc-boot-th`** | [`9.10.1`](http://hackage.haskell.org/package/ghc-boot-th-9.10.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-boot-th-9.10.1/src/LICENSE) | Shared functionality between GHC and the @template-haskell@ library | `ghc-boot`, `ghc-compat-plugin`, `template-haskell` |
 | **`ghc-heap`** | [`9.10.1`](http://hackage.haskell.org/package/ghc-heap-9.10.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-heap-9.10.1/src/LICENSE) | Functions for walking GHC's heap | `ghc`, `ghci` |
 | **`ghc-internal`** | [`9.1001.0`](http://hackage.haskell.org/package/ghc-internal-9.1001.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-internal-9.1001.0/src/LICENSE) | Basic libraries | `base`, `ghc-heap` |
 | **`ghc-platform`** | [`0.1.0.0`](http://hackage.haskell.org/package/ghc-platform-0.1.0.0) |  *MISSING* | *MISSING* | `ghc-boot` |
 | **`ghc-prim`** | [`0.11.0`](http://hackage.haskell.org/package/ghc-prim-0.11.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-prim-0.11.0/src/LICENSE) | GHC primitives | *(core library)* |
 | **`ghci`** | [`9.10.1`](http://hackage.haskell.org/package/ghci-9.10.1) |  *MISSING* | *MISSING* | `ghc` |
+| `hashable` | [`1.5.1.0`](http://hackage.haskell.org/package/hashable-1.5.1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/hashable-1.5.1.0/src/LICENSE) | A class for types that can be converted to a hash value | `case-insensitive`, `scientific`, `tomland`, `unordered-containers` |
+| `henforcer` | [`1.0.0.1`](http://hackage.haskell.org/package/henforcer-1.0.0.1) | [`MIT`](http://hackage.haskell.org/package/henforcer-1.0.0.1/src/LICENSE) | GHC plugin to enforce user specified rules on code. | `henforcer` |
 | **`hpc`** | [`0.7.0.1`](http://hackage.haskell.org/package/hpc-0.7.0.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/hpc-0.7.0.1/src/LICENSE) | Code Coverage Library for Haskell | `ghc` |
-| **`mtl`** | [`2.3.1`](http://hackage.haskell.org/package/mtl-2.3.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/mtl-2.3.1/src/LICENSE) | Monad classes for transformers, using functional dependencies | `exceptions` |
-| **`os-string`** | [`2.0.2`](http://hackage.haskell.org/package/os-string-2.0.2) | [`BSD-3-Clause`](http://hackage.haskell.org/package/os-string-2.0.2/src/LICENSE) | Library for manipulating Operating system strings. | `directory`, `filepath`, `unix` |
+| `integer-logarithms` | [`1.0.5`](http://hackage.haskell.org/package/integer-logarithms-1.0.5) | [`MIT`](http://hackage.haskell.org/package/integer-logarithms-1.0.5/src/LICENSE) | Integer logarithms. | `scientific` |
+| `megaparsec` | [`9.8.1`](http://hackage.haskell.org/package/megaparsec-9.8.1) | [`BSD-2-Clause`](http://hackage.haskell.org/package/megaparsec-9.8.1/src/LICENSE.md) | Monadic parser combinators | `tomland` |
+| **`mtl`** | [`2.3.1`](http://hackage.haskell.org/package/mtl-2.3.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/mtl-2.3.1/src/LICENSE) | Monad classes for transformers, using functional dependencies | `exceptions`, `megaparsec`, `tomland` |
+| `optparse-applicative` | [`0.19.0.0`](http://hackage.haskell.org/package/optparse-applicative-0.19.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/optparse-applicative-0.19.0.0/src/LICENSE) | Utilities and combinators for parsing command line options | `henforcer` |
+| **`os-string`** | [`2.0.2`](http://hackage.haskell.org/package/os-string-2.0.2) | [`BSD-3-Clause`](http://hackage.haskell.org/package/os-string-2.0.2/src/LICENSE) | Library for manipulating Operating system strings. | `directory`, `filepath`, `hashable`, `unix` |
+| `parser-combinators` | [`1.3.1`](http://hackage.haskell.org/package/parser-combinators-1.3.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/parser-combinators-1.3.1/src/LICENSE.md) | Lightweight package providing commonly useful parser combinators | `megaparsec`, `tomland` |
+| `pollock` | [`0.1.0.4`](http://hackage.haskell.org/package/pollock-0.1.0.4) | [`MIT`](http://hackage.haskell.org/package/pollock-0.1.0.4/src/LICENSE) | Functionality to help examine Haddock information of a module. | `henforcer` |
 | **`pretty`** | [`1.1.3.6`](http://hackage.haskell.org/package/pretty-1.1.3.6) | [`BSD-3-Clause`](http://hackage.haskell.org/package/pretty-1.1.3.6/src/LICENSE) | Pretty-printing library | `template-haskell` |
-| **`process`** | [`1.6.19.0`](http://hackage.haskell.org/package/process-1.6.19.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/process-1.6.19.0/src/LICENSE) | Process libraries | `ghc` |
+| `prettyprinter` | [`1.7.2`](http://hackage.haskell.org/package/prettyprinter-1.7.2) | [`BSD-2-Clause`](http://hackage.haskell.org/package/prettyprinter-1.7.2/src/LICENSE.md) | A modern, easy to use, well-documented, extensible pretty-printer. | `optparse-applicative`, `prettyprinter-ansi-terminal` |
+| `prettyprinter-ansi-terminal` | [`1.1.4`](http://hackage.haskell.org/package/prettyprinter-ansi-terminal-1.1.4) | [`BSD-2-Clause`](http://hackage.haskell.org/package/prettyprinter-ansi-terminal-1.1.4/src/LICENSE.md) | ANSI terminal backend for the »prettyprinter« package. | `optparse-applicative` |
+| `primitive` | [`0.9.1.0`](http://hackage.haskell.org/package/primitive-0.9.1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/primitive-0.9.1.0/src/LICENSE) | Primitive memory-related operations | `scientific` |
+| **`process`** | [`1.6.19.0`](http://hackage.haskell.org/package/process-1.6.19.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/process-1.6.19.0/src/LICENSE) | Process libraries | `extra`, `ghc`, `optparse-applicative` |
+| `scientific` | [`0.3.8.1`](http://hackage.haskell.org/package/scientific-0.3.8.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/scientific-0.3.8.1/src/LICENSE) | Numbers represented using scientific notation | `attoparsec`, `megaparsec` |
+| `selective` | [`0.7.0.1`](http://hackage.haskell.org/package/selective-0.7.0.1) | [`MIT`](http://hackage.haskell.org/package/selective-0.7.0.1/src/LICENSE) | Selective applicative functors | `validation-selective` |
 | **`semaphore-compat`** | [`1.0.0`](http://hackage.haskell.org/package/semaphore-compat-1.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/semaphore-compat-1.0.0) | Cross-platform abstraction for system semaphores | `ghc` |
 | **`stm`** | [`2.5.3.1`](http://hackage.haskell.org/package/stm-2.5.3.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/stm-2.5.3.1/src/LICENSE) | Software Transactional Memory | `exceptions`, `ghc` |
-| **`template-haskell`** | [`2.22.0.0`](http://hackage.haskell.org/package/template-haskell-2.22.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/template-haskell-2.22.0.0/src/LICENSE) | Support library for Template Haskell | `bytestring`, `containers`, `exceptions`, `filepath`, `ghc`, `ghci`, `os-string` |
-| **`time`** | [`1.12.2`](http://hackage.haskell.org/package/time-1.12.2) | [`BSD-2-Clause`](http://hackage.haskell.org/package/time-1.12.2/src/LICENSE) | A time library | `directory`, `ghc`, `hpc`, `unix` |
-| **`transformers`** | [`0.6.1.1`](http://hackage.haskell.org/package/transformers-0.6.1.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/transformers-0.6.1.1/src/LICENSE) | Concrete functor and monad transformers | `exceptions`, `ghc`, `ghci`, `mtl` |
-| **`unix`** | [`2.8.5.1`](http://hackage.haskell.org/package/unix-2.8.5.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/unix-2.8.5.1/src/LICENSE) | POSIX functionality | `directory`, `ghc`, `ghc-boot`, `ghci`, `process`, `semaphore-compat` |
+| **`template-haskell`** | [`2.22.0.0`](http://hackage.haskell.org/package/template-haskell-2.22.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/template-haskell-2.22.0.0/src/LICENSE) | Support library for Template Haskell | `bytestring`, `containers`, `exceptions`, `filepath`, `ghc`, `ghci`, `os-string`, `primitive`, `scientific`, `text`, `unordered-containers` |
+| **`text`** | [`2.1.1`](http://hackage.haskell.org/package/text-2.1.1) | [`BSD-2-Clause`](http://hackage.haskell.org/package/text-2.1.1/src/LICENSE) | An efficient packed Unicode text type. | `attoparsec`, `case-insensitive`, `hashable`, `henforcer`, `megaparsec`, `optparse-applicative`, `pollock`, `prettyprinter`, `prettyprinter-ansi-terminal`, `scientific`, `tomland` |
+| **`time`** | [`1.12.2`](http://hackage.haskell.org/package/time-1.12.2) | [`BSD-2-Clause`](http://hackage.haskell.org/package/time-1.12.2/src/LICENSE) | A time library | `directory`, `extra`, `ghc`, `hpc`, `tomland`, `unix` |
+| `tomland` | [`1.3.3.3`](http://hackage.haskell.org/package/tomland-1.3.3.3) | [`MPL-2.0`](http://hackage.haskell.org/package/tomland-1.3.3.3/src/LICENSE) | Bidirectional TOML serialization | `henforcer` |
+| **`transformers`** | [`0.6.1.1`](http://hackage.haskell.org/package/transformers-0.6.1.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/transformers-0.6.1.1/src/LICENSE) | Concrete functor and monad transformers | `attoparsec`, `exceptions`, `ghc`, `ghci`, `megaparsec`, `mtl`, `optparse-applicative`, `primitive`, `selective` |
+| **`unix`** | [`2.8.5.1`](http://hackage.haskell.org/package/unix-2.8.5.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/unix-2.8.5.1/src/LICENSE) | POSIX functionality | `directory`, `extra`, `ghc`, `ghc-boot`, `ghci`, `process`, `semaphore-compat` |
+| `unordered-containers` | [`0.2.21`](http://hackage.haskell.org/package/unordered-containers-0.2.21) | [`BSD-3-Clause`](http://hackage.haskell.org/package/unordered-containers-0.2.21/src/LICENSE) | Efficient hashing-based container types | `tomland` |
+| `validation-selective` | [`0.2.0.0`](http://hackage.haskell.org/package/validation-selective-0.2.0.0) | [`MPL-2.0`](http://hackage.haskell.org/package/validation-selective-0.2.0.0/src/LICENSE) | Lighweight pure data validation based on Applicative and Selective functors | `tomland` |
 
diff --git a/no-recursion.cabal b/no-recursion.cabal
--- a/no-recursion.cabal
+++ b/no-recursion.cabal
@@ -1,66 +1,94 @@
 cabal-version: 3.0
+-- ^ Cabal 3.0 is the first with the `Universal-FOSS-exception-1.0` SPDX license
+--   exception.
 
 name: no-recursion
-version: 0.3.0.0
+version: 0.4.0.1
 synopsis: A GHC plugin to remove support for recursion
-description: General recursion can be the cause of a lot of problems. This
-             removes recursion from GHC, allowing you to guarantee you’re using
-             other mechanisms, like recursion schemes.
-author: Greg Pfeil <greg@technomadic.org>
+description:
+  General recursion can be the cause of a lot of problems. This removes
+  recursion from GHC, allowing you to guarantee you’re using other mechanisms,
+  like recursion schemes.
 maintainer: Greg Pfeil <greg@technomadic.org>
+author: Greg Pfeil <greg@technomadic.org>
 copyright: 2024 Greg Pfeil
-homepage: https://github.com/sellout/no-recursion#readme
-bug-reports: https://github.com/sellout/no-recursion/issues
-category: Recursion
-build-type: Custom
--- TODO: Remove the redundant `OR AGPL-3.0-only` once
---       haskell/hackage-server#1440 is fixed.
-license: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR AGPL-3.0-only OR LicenseRef-commercial
+license: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
 license-files:
   LICENSE
   LICENSE.AGPL-3.0-only
   LICENSE.Universal-FOSS-exception-1.0
-  LICENSE.commercial
+  LICENSE.proprietary
+category: Recursion
+build-type: Custom
 extra-doc-files:
   CHANGELOG.md
   README.md
   docs/*.md
 tested-with:
   GHC == {
-    9.6.1, 9.6.3,
+    9.2.1,
+    9.4.1, 9.4.8,
+    9.6.1, 9.6.7,
     9.8.1, 9.8.4,
-    9.10.1,
-    9.12.1
+    9.10.1, 9.10.2, 9.10.3,
+    9.12.1, 9.12.2,
+    9.14.1
   }
 
+homepage: https://github.com/sellout/no-recursion#readme
+bug-reports: https://github.com/sellout/no-recursion/issues
+
 source-repository head
   type: git
   location: https://github.com/sellout/no-recursion.git
   subdir: core
 
+source-repository this
+  type: git
+  location: https://github.com/sellout/no-recursion.git
+  subdir: core
+  -- NB: This is the repo version, which is distinct from the package version.
+  tag: v0.4.1
+
+custom-setup
+  setup-depends:
+    -- TODO: Due to haskell/cabal#3751, `Cabal` has to be specified even though
+    --       there’s no direct dependency. Its lower bound needs to be at least
+    --       as high as `cabal-version` at the top of this file, and Hackage
+    --       requires it to have _some_ upper bound. (Since there’s no direct
+    --       dependency, it doesn’t use PVP bounds.)
+    --
+    -- TODO: Due to haskell/cabal#9375, 3.10.3 is required in order to load
+    --       plugins from a `ghc-options` field. Unfortunately, GHCs prior to
+    --       9.8.3 include an older Cabal, so a newer one may need to be
+    --       manually installed.
+    Cabal >= 3.10.3 && < 99,
+    base ^>= {4.16.0, 4.17.0, 4.18.0, 4.19.0, 4.20.0, 4.21.0, 4.22.0},
+    cabal-doctest ^>= {1.0.0},
+
 flag noisy-deprecations
   description:
-    Prior to GHC 9.10, the `DEPRECATED` pragma can’t distinguish between terms
+    Prior to GHC 9.10, the @DEPRECATED@ pragma can’t distinguish between terms
     and types. Consenquently, you can get spurious warnings when there’s a name
     collision and the name in the other namespace is deprecated. Or you can
     choose to not get those warnings, at the risk of not being warned when
     there’s a name collision and the namespace you’re referencing is the one
     that’s deprecated.
+  default: True
+  -- Because disabling this flag won’t help the solver.
+  manual: True
 
-custom-setup
-  setup-depends:
-    -- TODO: Due to haskell/cabal#3751, `Cabal` has to be specified even though
-    --       there’s no direct dependency. Its lower bound needs to match
-    --      `cabal-version` at the top of this file, and Hackage requires it to
-    --       have _some_ upper bound. (Since there’s no direct dependency, it
-    --       doesn’t use PVP bounds.)
-    Cabal >= 3.0 && < 99,
-    base ^>= {4.18.0, 4.19.0, 4.20.0, 4.21.0},
-    cabal-doctest ^>= {1.0.0},
+flag lint
+  description:
+    Build with linting plugins enabled. This is intended for developers of this
+    package.
+  default: False
+  -- Because disabling this flag won’t help the solver.
+  manual: True
 
 -- This mimics the GHC2024 extension
 -- (https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/control.html?highlight=doandifthenelse#extension-GHC2024),
--- but supporting compilers back to GHC 8.0. If the oldest supported compiler
+-- but supporting compilers back to GHC 7.10. If the oldest supported compiler
 -- is GHC 9.10, then this stanza can be removed and `import: GHC2024` can be
 -- replaced by `default-language: GHC2024`. If the oldest supported compiler is
 -- GHC 9.2, then this can be simplified by setting `default-language: GHC2021`
@@ -80,7 +108,8 @@
 common defaults
   import: GHC2024
   build-depends:
-    base ^>= {4.18.0, 4.19.0, 4.20.0, 4.21.0},
+    base ^>= {4.16.0, 4.17.0, 4.18.0, 4.19.0, 4.20.0, 4.21.0, 4.22.0},
+    ghc-compat-plugin ^>= {0.1.0},
   ghc-options:
     -Weverything
     -- This one just reports unfixable things, AFAICT.
@@ -91,6 +120,9 @@
     -- Warns even when `Unsafe` is explicit, not inferred. See
     -- https://gitlab.haskell.org/ghc/ghc/-/issues/16689
     -Wno-unsafe
+    -fplugin GhcCompat
+    -fplugin-opt GhcCompat:minVersion=9.2.1
+    -fplugin-opt GhcCompat:reportIncompatibleExtensions=error
   if impl(ghc >= 9.8.1)
     ghc-options:
       -- Inference good.
@@ -118,12 +150,18 @@
     NoTypeApplications
   if flag(noisy-deprecations)
     cpp-options: -DSELLOUT_NOISY_DEPRECATIONS
+  if flag(lint) && impl(ghc >= 9.4.2)
+    build-depends:
+      henforcer ^>= {1.0.0},
+    ghc-options:
+      -fplugin Henforcer
 
 library
   import: defaults
-  hs-source-dirs: src
+  hs-source-dirs:
+    src
   build-depends:
-    ghc ^>= {9.6.1, 9.8.1, 9.10.1, 9.12.1},
+    ghc ^>= {9.2.1, 9.4.1, 9.6.1, 9.8.1, 9.10.1, 9.12.1, 9.14.1},
   exposed-modules:
     NoRecursion
   other-modules:
@@ -132,10 +170,11 @@
 test-suite doctests
   import: defaults
   type: exitcode-stdio-1.0
-  hs-source-dirs: tests
+  hs-source-dirs:
+    tests
   main-is: doctests.hs
   build-depends:
-    doctest ^>= {0.21.1, 0.22.2, 0.24.0},
+    doctest ^>= {0.18.2, 0.20.1, 0.21.1, 0.22.2, 0.24.0, 0.25.0},
     no-recursion,
   -- TODO: The sections below here are necessary because we don’t have control
   --       over the generated `Build_doctests.hs` file. So we have to silence
diff --git a/src/NoRecursion.hs b/src/NoRecursion.hs
--- a/src/NoRecursion.hs
+++ b/src/NoRecursion.hs
@@ -1,8 +1,11 @@
 {-# LANGUAGE Unsafe #-}
 
--- | A plugin that identifies and reports on uses of recursion. The name evokes
---   a language pragma – implying a @Recursion@ pragma that is enabled by
---   default.
+-- |
+-- Copyright: 2024 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
+--
+-- A plugin that identifies and reports on uses of recursion. The name evokes a
+-- language pragma – implying a @Recursion@ pragma that is enabled by default.
 module NoRecursion (plugin) where
 
 import safe "base" Control.Applicative (liftA2, pure)
@@ -40,6 +43,8 @@
   )
 
 -- | The entrypoint for the "NoRecursion" plugin.
+--
+-- @since 0.1.0
 plugin :: Plugins.Plugin
 plugin =
   defaultPurePlugin
diff --git a/src/PluginUtils.hs b/src/PluginUtils.hs
--- a/src/PluginUtils.hs
+++ b/src/PluginUtils.hs
@@ -2,8 +2,10 @@
 {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
 
 -- |
+-- Copyright: 2025 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
 --
---  __TODO__: Make a separate package with general plugin utilities.
+-- __TODO__: Make a separate package with general plugin utilities.
 module PluginUtils
   ( defaultPurePlugin,
     Annotations,
@@ -24,6 +26,10 @@
 import safe "base" Data.String (String)
 import "ghc" GHC.Plugins qualified as Plugins
 
+-- | The same as `Plugins.defaultPlugin`, but defaults to a pure plugin, rather
+--   than an impure one.
+--
+-- @since 0.2.0
 defaultPurePlugin :: Plugins.Plugin
 defaultPurePlugin =
   Plugins.defaultPlugin {Plugins.pluginRecompile = Plugins.purePlugin}
@@ -31,9 +37,15 @@
 -- | Annotations of type @a@ for a module – `fst` is the module-level
 --   annotations and `Data.Tuple.snd` is a map of annotations for each name in
 --   the module.
+--
+-- @since 0.2.0
 type Annotations :: Type -> Type
 type Annotations a = (a, Plugins.NameEnv a)
 
+-- | Similar to `Plugins.getAnnotations`, but only returns the annotations for
+--   the current module.
+--
+-- @since 0.2.0
 getAnnotations :: (Data a) => Plugins.ModGuts -> Plugins.CoreM (Annotations [a])
 getAnnotations guts =
   first
@@ -55,5 +67,7 @@
     elemIndex ':' opt
 
 -- | This splits each option on `:`, returning a separate “value” if it exists.
+--
+-- @since 0.2.0
 processOptions :: [Plugins.CommandLineOption] -> [(String, Maybe String)]
 processOptions = fmap processOption . correctOptionOrder
diff --git a/tests/doctests.hs b/tests/doctests.hs
--- a/tests/doctests.hs
+++ b/tests/doctests.hs
@@ -1,12 +1,28 @@
 {-# LANGUAGE Unsafe #-}
 
+-- |
+-- Copyright: 2017-2020 Oleg Grenrus, 2020- Max Ulidtko
+-- License: BSD-3-Clause
+--
+-- Test-suite driver, adapted from
+-- [cabal-doctest](https://hackage.haskell.org/package/cabal-doctest).
 module Main (main) where
 
+import safe "base" Control.Category ((.))
+import safe "base" Data.Foldable (fold)
 import safe "base" Data.Function (($))
+import safe "base" Data.Functor (fmap)
 import safe "base" Data.Semigroup ((<>))
-import safe "base" System.IO (IO)
+import safe "base" Data.Traversable (for)
+import safe "base" System.IO (IO, print, putStrLn)
 import "doctest" Test.DocTest (doctest)
-import "this" Build_doctests (flags, module_sources, pkgs)
+import "this" Build_doctests (Component (Component), components)
 
+-- | The doctest entry point.
+--
+-- @since 0.0.1
 main :: IO ()
-main = doctest $ flags <> pkgs <> module_sources
+main = fmap fold . for components $ \(Component name flags pkgs sources) -> do
+  print name
+  putStrLn "----------------------------------------"
+  doctest $ flags <> pkgs <> sources
diff --git a/tests/ignore/Test/AllowRecursion.hs b/tests/ignore/Test/AllowRecursion.hs
--- a/tests/ignore/Test/AllowRecursion.hs
+++ b/tests/ignore/Test/AllowRecursion.hs
@@ -2,6 +2,9 @@
 -- Removing this line should cause this module to not compile.
 {-# OPTIONS_GHC -fplugin-opt=NoRecursion:allow-recursion:true #-}
 
+-- |
+-- Copyright: 2024 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
 module Test.AllowRecursion
   ( recDef,
     nonRecDef,
@@ -10,8 +13,14 @@
 
 import "base" Control.Category (id)
 
+-- | A simple self-recursive definition.
+--
+-- @since 0.2.0
 recDef :: a -> b
 recDef = recDef
 
+-- | A trivial non-recursive definition.
+--
+-- @since 0.2.0
 nonRecDef :: a -> a
 nonRecDef = id
diff --git a/tests/ignore/Test/AnnModule.hs b/tests/ignore/Test/AnnModule.hs
--- a/tests/ignore/Test/AnnModule.hs
+++ b/tests/ignore/Test/AnnModule.hs
@@ -1,5 +1,8 @@
 {-# LANGUAGE Unsafe #-}
 
+-- |
+-- Copyright: 2024 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
 module Test.AnnModule
   ( recDef,
     nonRecDef,
@@ -11,9 +14,15 @@
 -- Removing this line should cause this module to not compile.
 {-# ANN module "Recursion" #-}
 
+-- | A simple self-recursive definition.
+--
+-- @since 0.1.1
 recDef :: a -> b
 recDef = recDef
 
+-- | A trivial non-recursive definition.
+--
+-- @since 0.1.1
 nonRecDef :: a -> a
 nonRecDef = id
 {-# ANN nonRecDef "NoRecursion" #-}
diff --git a/tests/ignore/Test/AnnName.hs b/tests/ignore/Test/AnnName.hs
--- a/tests/ignore/Test/AnnName.hs
+++ b/tests/ignore/Test/AnnName.hs
@@ -1,5 +1,8 @@
 {-# LANGUAGE Unsafe #-}
 
+-- |
+-- Copyright: 2024 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
 module Test.AnnName
   ( recDef,
     nonRecDef,
@@ -8,10 +11,16 @@
 
 import safe "base" Control.Category (id)
 
+-- | A simple self-recursive definition.
+--
+-- @since 0.1.1
 recDef :: a -> b
 recDef = recDef
 -- Removing this line should cause this module to not compile.
 {-# ANN recDef "Recursion" #-}
 
+-- | A trivial non-recursive definition.
+--
+-- @since 0.1.1
 nonRecDef :: a -> a
 nonRecDef = id
diff --git a/tests/ignore/Test/IgnoreDefaultImpls.hs b/tests/ignore/Test/IgnoreDefaultImpls.hs
--- a/tests/ignore/Test/IgnoreDefaultImpls.hs
+++ b/tests/ignore/Test/IgnoreDefaultImpls.hs
@@ -4,8 +4,12 @@
 -- Removing this line should cause this module to not compile.
 {-# OPTIONS_GHC -fplugin-opt=NoRecursion:ignore-methods:sconcat,stimes #-}
 
--- | Without @-fplugin-opt=NoRecursion:no-ignore-default-impls@ specified during
---   compilation, default definitions won’t trigger an error.
+-- |
+-- Copyright: 2025 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
+--
+-- Without @-fplugin-opt=NoRecursion:no-ignore-default-impls@ specified during
+-- compilation, default definitions won’t trigger an error.
 module Test.IgnoreDefaultImpls
   ( Example (Empty, NotEmpty),
   )
@@ -17,6 +21,9 @@
 import "base" Data.Semigroup (Semigroup, (<>))
 import "base" Data.Tuple (curry)
 
+-- | A trivial data type for testing instances.
+--
+-- @since 0.2.0
 type Example :: Type
 data Example
   = Empty
diff --git a/tests/ignore/Test/UnannName.hs b/tests/ignore/Test/UnannName.hs
--- a/tests/ignore/Test/UnannName.hs
+++ b/tests/ignore/Test/UnannName.hs
@@ -2,6 +2,9 @@
 -- Removing this line should cause this module to not compile.
 {-# OPTIONS_GHC -fplugin-opt=NoRecursion:ignore-decls:recDef #-}
 
+-- |
+-- Copyright: 2025 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
 module Test.UnannName
   ( recDef,
     nonRecDef,
@@ -10,8 +13,14 @@
 
 import "base" Control.Category (id)
 
+-- | A simple self-recursive definition.
+--
+-- @since 0.2.0
 recDef :: a -> b
 recDef = recDef
 
+-- | A trivial non-recursive definition.
+--
+-- @since 0.2.0
 nonRecDef :: a -> a
 nonRecDef = id
diff --git a/tests/ignore/test.hs b/tests/ignore/test.hs
--- a/tests/ignore/test.hs
+++ b/tests/ignore/test.hs
@@ -1,8 +1,17 @@
 {-# LANGUAGE Trustworthy #-}
 
+-- |
+-- Copyright: 2024 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
+module Main
+  ( main,
+  )
+where
+
 import safe "base" Control.Applicative (pure)
 import safe "base" Control.Category ((.))
-import safe "base" Data.Function (const, ($))
+import safe "base" Data.Function (($))
+import safe "base" Data.Functor (void)
 import safe "base" System.IO (IO)
 import safe "this" Test.AllowRecursion qualified as AllowRecursion
 import "this" Test.AnnModule qualified as AnnModule
@@ -10,11 +19,14 @@
 import safe "this" Test.IgnoreDefaultImpls qualified as IgnoreDefaultImpls
 import safe "this" Test.UnannName qualified as UnannName
 
+-- | The test-suite entry point.
+--
+-- @since 0.1.1
 main :: IO ()
 main =
-  pure
+  void
+    . pure
     . AllowRecursion.nonRecDef
     . AnnModule.nonRecDef
     . AnnName.nonRecDef
-    . UnannName.nonRecDef
-    $ const () IgnoreDefaultImpls.Empty
+    $ UnannName.nonRecDef IgnoreDefaultImpls.Empty
diff --git a/tests/no-ignore/Test/IgnoreDefaultImpls.hs b/tests/no-ignore/Test/IgnoreDefaultImpls.hs
--- a/tests/no-ignore/Test/IgnoreDefaultImpls.hs
+++ b/tests/no-ignore/Test/IgnoreDefaultImpls.hs
@@ -3,6 +3,9 @@
 {-# OPTIONS_GHC -fplugin-opt=NoRecursion:ignore-method-cycles:true #-}
 {-# OPTIONS_GHC -fplugin-opt=NoRecursion:ignore-methods:sconcat,stimes #-}
 
+-- |
+-- Copyright: 2025 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
 module Test.IgnoreDefaultImpls
   ( Example (Empty, NotEmpty),
   )
@@ -14,6 +17,9 @@
 import "base" Data.Semigroup (Semigroup, (<>))
 import "base" Data.Tuple (curry)
 
+-- | A trivial data type for testing instances.
+--
+-- @since 0.2.0
 type Example :: Type
 data Example
   = Empty
diff --git a/tests/no-ignore/test.hs b/tests/no-ignore/test.hs
--- a/tests/no-ignore/test.hs
+++ b/tests/no-ignore/test.hs
@@ -1,9 +1,21 @@
 {-# LANGUAGE Safe #-}
 
+-- |
+-- Copyright: 2025 Greg Pfeil
+-- License: AGPL-3.0-only WITH Universal-FOSS-exception-1.0 OR LicenseRef-proprietary
+module Main
+  ( main,
+  )
+where
+
 import "base" Control.Applicative (pure)
-import "base" Data.Function (const, ($))
+import "base" Data.Function (($))
+import "base" Data.Functor (void)
 import "base" System.IO (IO)
 import "this" Test.IgnoreDefaultImpls qualified as IgnoreDefaultImpls
 
+-- | The test-suite entry point.
+--
+-- @since 0.2.0
 main :: IO ()
-main = pure $ const () IgnoreDefaultImpls.Empty
+main = void $ pure IgnoreDefaultImpls.Empty
