diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -43,14 +43,14 @@
 ```haskell
 newtype Tracked f a = Tracked (FilePather (ReaderT Trail (ExceptT Cycle f)) (Outputs, a))
 
-step  :: Applicative f => String -> (FilePath -> [FilePath]) -> FilePather f a -> Tracked f a
-step' :: Functor f => String -> FilePather f ([FilePath], a) -> Tracked f a
+track  :: Applicative f => String -> (FilePath -> [FilePath]) -> FilePather f a -> Tracked f a
+track' :: Functor f => String -> FilePather f ([FilePath], a) -> Tracked f a
 ```
 
 A `Tracked` value runs with the `Trail` of its input file: the steps that
-built it, back to its source file. `step` checks the trail before it runs: a
+built it, back to its source file. `track` checks the trail before it runs: a
 step that is already in the trail, or that would make a file already in the
-trail, is stopped with a `Cycle`, and does not run. `step'` checks after it
+trail, is stopped with a `Cycle`, and does not run. `track'` checks after it
 runs, for steps whose outputs are known only then. A `Cycle` describes the
 whole chain:
 
@@ -86,9 +86,9 @@
 | `.fodt`, `.fods`, `.fodp`, `.fodg` | `libreofficeZipped` | `.odt`, `.ods`, `.odp`, `.odg` |
 | LibreOffice, Word | `libreoffice`, `libreofficeToPdf`, ... | any format LibreOffice exports |
 | PDF | `pdfSplit`, `pdfBurst` | PDF files |
-| any file | `gitDetails`, `gitPlaceholders` | the last commit and authors, and placeholders for them |
+| any file | `readGitDetails`, `gitPlaceholders` | the last commit and authors, and placeholders for them |
 | text file | `replaceStrings`, `requireStrings` | a file with strings replaced |
-| image | `imageInfo` | format, width, height, resolution, ... |
+| image | `readImageInfo` | format, width, height, resolution, ... |
 | image | `imageResize`, `imageConvert` | images |
 | directory | `pdfJoin`, `pdfConcatenate`, `pdftk` | one PDF of the PDF files in it |
 | directory | `imageAppend` | one image of images in it, side by side |
@@ -115,14 +115,40 @@
     p <- requireInput
     o <- lift (process (CommandLine "wc" ["-w", p]))
     maybe
-      (lift (failBuild (ProcessOutputUnrecognised (CommandLine "wc" ["-w", p]) "a word count")))
+      (lift (failBuild (BuildErrorProcessOutputUnrecognised (ProcessOutputUnrecognised (CommandLine "wc" ["-w", p]) "a word count"))))
       pure
       (readMaybe (takeWhile (/= ' ') o))
 ```
 
-Operations fail with any error type `e` that has a `ReviewBuildError`
-instance, so a build file can have an error type of its own that includes
-`BuildError`.
+Operations fail with any error type `e` for which `ReviewBuildError e` holds:
+`e` has an instance of `BuildErrorLike`, the class of `BuildError`, that
+makes a `BuildError` into an `e`. So a build file can have an error type of
+its own that includes `BuildError`. `ReviewBuildError e` needs
+`FlexibleContexts`.
+
+### Optics
+
+Every data type has a name for each of its fields and constructors: a class
+whose method is an optic, of the kind that each data type gives it (the
+classy optics recipe of
+[claude-haskell](https://github.com/sirius-beta/claude-haskell)). Each name
+is in its own module under `Document.Build.Name` (or `System.FilePather.Name`),
+and each data type has its own name too, such as `buildError`, `directories`
+and `gitDetails`. A data type that holds another inherits its names, so
+`outputDirectory` reaches the output directory of `Directories` and of a
+`Context`, and `file` reaches the file that a `BuildError` is about, through
+whichever constructor holds one:
+
+```haskell
+view (cloneLens outputDirectory) context    -- a lens
+preview file err                            -- an affine traversal
+review fileNotUtf8 "notes.odt" :: BuildError
+```
+
+Read one value with `view (cloneLens n)`, at most one with `preview n`, and
+set with `set` and `over`. A constructor of `BuildError` with two or more
+fields holds a type of its own, such as
+`BuildErrorProcessFailed (ProcessFailed commandLine stdout stderr)`.
 
 ## 3. Rules and builds
 
diff --git a/bench/Main.hs b/bench/Main.hs
--- a/bench/Main.hs
+++ b/bench/Main.hs
@@ -6,14 +6,14 @@
 -}
 module Main (main) where
 
-import Control.Lens (view, _Wrapped)
+import Control.Lens (cloneLens, view)
 import Control.Monad.Reader (asks)
 import Criterion.Main (bench, bgroup, defaultMain, nf, whnf)
 import qualified Data.ByteString.Lazy.Char8 as Char8
 import Data.Functor.Identity (Identity (..))
 import qualified Data.Text as Text
 import Document.Build (parsePdfinfoPages, replaceText, sha256Hex)
-import System.FilePather (FilePather, Step (..), Tracked, Trail (..), step, trackedFunction, (~>))
+import System.FilePather (FilePather, Step (..), Tracked, Trail (..), pathFunction, track, trackedFunction, (~>))
 
 -- | A 'FilePather' mapped over many times, to measure the fusion of 'fmap'.
 mapped :: Int -> FilePather Identity Int
@@ -28,12 +28,12 @@
 -- | A 'Tracked' value mapped over many times, to measure the fusion of 'fmap'.
 mappedTracked :: Int -> Tracked Identity Int
 mappedTracked n =
-  iterate (fmap (+ 1)) (step "length" (const []) (asks length)) !! n
+  iterate (fmap (+ 1)) (track "length" (const []) (asks length)) !! n
 
 -- | Many steps combined with '<>', run with a trail of many steps.
 combinedTracked :: Int -> Tracked Identity ()
 combinedTracked n =
-  foldMap (\k -> step ("step " <> show k) (\p -> [p <> "." <> show k]) (pure ())) [1 .. n]
+  foldMap (\k -> track ("step " <> show k) (\p -> [p <> "." <> show k]) (pure ())) [1 .. n]
 
 -- | A trail of many steps.
 longTrail :: Trail
@@ -62,8 +62,8 @@
   defaultMain
     [ bgroup
         "FilePather"
-        [ bench "fmap x1000" $ whnf (\n -> runIdentity (view _Wrapped (mapped n) "src/report.fodt")) 1000
-        , bench "~> x1000" $ nf (\n -> runIdentity (view _Wrapped (piped n) "src/report.fodt")) 1000
+        [ bench "fmap x1000" $ whnf (\n -> runIdentity (view (cloneLens pathFunction) (mapped n) "src/report.fodt")) 1000
+        , bench "~> x1000" $ nf (\n -> runIdentity (view (cloneLens pathFunction) (piped n) "src/report.fodt")) 1000
         ]
     , bgroup
         "Tracked"
diff --git a/build.hs b/build.hs
--- a/build.hs
+++ b/build.hs
@@ -2,7 +2,7 @@
 {- cabal:
 build-depends:
     base           >= 4.18      && < 5
-  , document-build >= 0.1       && < 0.2
+  , document-build >= 0.2       && < 0.3
   , filepath       >= 1.4.301.0 && < 1.6
   , mtl            >= 2.3.1     && < 2.4
 ghc-options: -Wall
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,50 @@
 # Changelog
 
+## 0.2.0.0
+
+* Depend on filepather 0.8, and replace the optics of every data type with
+  the classy optics recipe of claude-haskell: each data type has a class,
+  `TLike`, whose method is its own name (`commandLine`, `buildError`,
+  `directories`, `target`, `context`, `gitDetails`, `imageInfo`,
+  `outputFile`), each field and constructor has a name, a class in its own
+  module under Document.Build.Name, and each data type has a manifest
+  (`lawNamesT`), a synonym and function of inheritance (`InheritT`,
+  `inheritT`) and, where it holds another data type, a check of inheritance
+  (`inheritsT`)
+* Remove the GetX, HasX, ReviewX and AsX classes, and the Field and Injection
+  instances of every data type; the field lenses keep their names
+  (`commandLineProgram`, `outputDirectory`, `gitCommitHash`, `imageWidth`,
+  `outputPath` and so on), and are read with `view (cloneLens n)`
+* `ReviewBuildError e` is a constraint synonym for
+  `BuildErrorLike Tagged Identity e e`, the class of BuildError at
+  `review`; it needs FlexibleContexts
+* The constructors of BuildError with two or more fields each hold a product
+  type of their own, in a module under Document.Build.Error: ProcessFailed,
+  ProcessOutputUnrecognised, OutputFileMissing, StringsMissing,
+  FileTypeUnsupported, InputFilesTooFew and OutputNotInDirectory, held by
+  BuildErrorProcessFailed and so on; the prisms `_ProcessFailed` and so on
+  are the names `processFailed`, `inputFileMissing`, `cycle` and so on, and
+  BuildError inherits the names of the types it holds, such as `file` and
+  `commandLine`, as affine traversals
+* Move CommandLine to Document.Build.CommandLine, Directories to
+  Document.Build.Directories, Target to Document.Build.Target, GitDetails to
+  Document.Build.GitDetails, ImageInfo to Document.Build.ImageInfo and
+  OutputFile to Document.Build.OutputFile; the prisms `_TargetFile`,
+  `_TargetDirectory` and `_TargetComplete` are the names `targetFile`,
+  `targetDirectory` and `targetComplete`, and the lenses
+  `contextDirectories` and `contextTarget` are `directories` and `target`
+* Rename the operations gitDetails and imageInfo to readGitDetails and
+  readImageInfo, because `gitDetails` and `imageInfo` are the names of
+  GitDetails and ImageInfo
+* Document.Build exports the name `cycle`, which has the same name as
+  Prelude.cycle
+* Remove the dependency on either-n, and depend on tagged
+* Build with -Werror=incomplete-patterns, and without
+  -Wredundant-constraints
+* Add hedgehog tests of the classy optics: generators in the shape of each
+  manifest, the coverage of each sum, the manifest laws, and the laws of the
+  kind of every instance of every name
+
 ## 0.1.0.0
 
 * Start again, replacing the earlier prototype, with the project layout,
diff --git a/document-build.cabal b/document-build.cabal
--- a/document-build.cabal
+++ b/document-build.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               document-build
-version:            0.1.0.0
+version:            0.2.0.0
 license:            BSD-3-Clause
 license-file:       LICENCE
 author:             Tony Morris <ʇǝu˙sıɹɹoɯʇ@ןןǝʞsɐɥ> <dibblego>
@@ -61,14 +61,14 @@
                     , cryptohash-sha256 >= 0.11.102.1 && < 0.12
                     , deepseq           >= 1.4.8.1   && < 2
                     , directory         >= 1.3.8.5   && < 2
-                    , either-n          >= 0.1       && < 0.2
                     , exitcode          >= 0.3.0.1   && < 0.4
                     , filepath          >= 1.4.301.0 && < 1.6
-                    , filepather        >= 0.7       && < 0.8
+                    , filepather        >= 0.8       && < 0.9
                     , lens              >= 5.2.1     && < 6
                     , mtl               >= 2.3.1     && < 2.4
                     , natural           >= 0.5.0.1   && < 0.6
                     , process           >= 1.6.19.0  && < 2
+                    , tagged            >= 0.8.7     && < 0.9
                     , temporary         >= 1.3       && < 2
                     , text              >= 2.0.2     && < 2.2
                     , time              >= 1.12.2    && < 2
@@ -77,9 +77,8 @@
   ghc-options:
                     -Wall
                     -Wcompat
-                    -Wincomplete-record-updates
                     -Wincomplete-uni-patterns
-                    -Wredundant-constraints
+                    -Werror=incomplete-patterns
 
   hs-source-dirs:
                     src
@@ -87,21 +86,79 @@
   exposed-modules:
                     Document.Build
                     Document.Build.Build
+                    Document.Build.CommandLine
                     Document.Build.Context
+                    Document.Build.Directories
                     Document.Build.Documents
                     Document.Build.Error
+                    Document.Build.Error.FileTypeUnsupported
+                    Document.Build.Error.InputFilesTooFew
+                    Document.Build.Error.OutputFileMissing
+                    Document.Build.Error.OutputNotInDirectory
+                    Document.Build.Error.ProcessFailed
+                    Document.Build.Error.ProcessOutputUnrecognised
+                    Document.Build.Error.StringsMissing
                     Document.Build.File
                     Document.Build.Git
+                    Document.Build.GitDetails
                     Document.Build.Hash
                     Document.Build.Image
+                    Document.Build.ImageInfo
                     Document.Build.LibreOffice
+                    Document.Build.Name.CommandLineArguments
+                    Document.Build.Name.CommandLineProgram
+                    Document.Build.Name.ContextProject
+                    Document.Build.Name.Directory
+                    Document.Build.Name.ExpectedOutput
+                    Document.Build.Name.FileNotInGit
+                    Document.Build.Name.FileNotUtf8
+                    Document.Build.Name.FontsDirectory
+                    Document.Build.Name.GitAuthorEmail
+                    Document.Build.Name.GitAuthorName
+                    Document.Build.Name.GitAuthorTime
+                    Document.Build.Name.GitAuthors
+                    Document.Build.Name.GitCommitHash
+                    Document.Build.Name.GitCommitHashShort
+                    Document.Build.Name.GitCommitTime
+                    Document.Build.Name.GitCommitterEmail
+                    Document.Build.Name.GitCommitterName
+                    Document.Build.Name.GitSubject
+                    Document.Build.Name.ImageColourspace
+                    Document.Build.Name.ImageDepth
+                    Document.Build.Name.ImageFormat
+                    Document.Build.Name.ImageFrames
+                    Document.Build.Name.ImageHeight
+                    Document.Build.Name.ImageResolutionUnits
+                    Document.Build.Name.ImageResolutionX
+                    Document.Build.Name.ImageResolutionY
+                    Document.Build.Name.ImageWidth
+                    Document.Build.Name.InputDirectories
+                    Document.Build.Name.InputFileCount
+                    Document.Build.Name.InputFileMissing
+                    Document.Build.Name.InputFilesNeeded
+                    Document.Build.Name.MissingStrings
+                    Document.Build.Name.OutputDirectory
+                    Document.Build.Name.OutputGitDetails
+                    Document.Build.Name.OutputImageInfo
+                    Document.Build.Name.OutputPages
+                    Document.Build.Name.OutputPath
+                    Document.Build.Name.OutputSha256
+                    Document.Build.Name.ShallowRepository
+                    Document.Build.Name.StandardError
+                    Document.Build.Name.StandardOutput
+                    Document.Build.Name.SupportedExtensions
+                    Document.Build.Name.TargetComplete
+                    Document.Build.Name.TargetDirectory
+                    Document.Build.Name.TargetFile
                     Document.Build.Operations
                     Document.Build.Output
+                    Document.Build.OutputFile
                     Document.Build.Pandoc
                     Document.Build.Pdf
                     Document.Build.Project
                     Document.Build.Replace
                     Document.Build.Rules
+                    Document.Build.Target
 
 -- The build file, build.hs, which is a cabal script that each document project
 -- runs. Building it here checks that it compiles.
@@ -121,9 +178,8 @@
   ghc-options:
                     -Wall
                     -Wcompat
-                    -Wincomplete-record-updates
                     -Wincomplete-uni-patterns
-                    -Wredundant-constraints
+                    -Werror=incomplete-patterns
 
   hs-source-dirs:
                     .
@@ -139,6 +195,7 @@
                     BuildTests
                     Gens
                     Laws
+                    OpticsTests
                     OutputTests
 
   default-language:
@@ -151,6 +208,7 @@
                     , containers        >= 0.6.7     && < 0.9
                     , document-build
                     , hedgehog          >= 1.2       && < 2
+                    , hedgehog-fn       >= 1.1       && < 2
                     , lens              >= 5.2.1     && < 6
                     , text              >= 2.0.2     && < 2.2
                     , time              >= 1.12.2    && < 2
diff --git a/src/Document/Build.hs b/src/Document/Build.hs
--- a/src/Document/Build.hs
+++ b/src/Document/Build.hs
@@ -30,25 +30,151 @@
         <> 'pageOneRules' (\p -> dropExtension p <> "-1.pdf") (\`replaceExtension\` "png")
     )
 @
+
+Every data type has a name for each of its fields and constructors: a class
+whose method is an optic (see the modules under "Document.Build.Name" and
+"System.FilePather.Name"). Read a field with
+@'Control.Lens.view' ('Control.Lens.cloneLens' n)@, and a constructor with
+'Control.Lens.preview' and 'Control.Lens.review'.
+
+This module also exports the class method 'cycle', the name of 'Cycle', which
+has the same name as 'Prelude.cycle'.
 -}
 module Document.Build (
   module Document.Build.Build,
+  module Document.Build.CommandLine,
   module Document.Build.Context,
+  module Document.Build.Directories,
   module Document.Build.Documents,
   module Document.Build.Error,
+  module Document.Build.Error.FileTypeUnsupported,
+  module Document.Build.Error.InputFilesTooFew,
+  module Document.Build.Error.OutputFileMissing,
+  module Document.Build.Error.OutputNotInDirectory,
+  module Document.Build.Error.ProcessFailed,
+  module Document.Build.Error.ProcessOutputUnrecognised,
+  module Document.Build.Error.StringsMissing,
+  module Document.Build.GitDetails,
+  module Document.Build.ImageInfo,
+  module Document.Build.Name.CommandLineArguments,
+  module Document.Build.Name.CommandLineProgram,
+  module Document.Build.Name.ContextProject,
+  module Document.Build.Name.Directory,
+  module Document.Build.Name.ExpectedOutput,
+  module Document.Build.Name.FileNotInGit,
+  module Document.Build.Name.FileNotUtf8,
+  module Document.Build.Name.FontsDirectory,
+  module Document.Build.Name.GitAuthorEmail,
+  module Document.Build.Name.GitAuthorName,
+  module Document.Build.Name.GitAuthorTime,
+  module Document.Build.Name.GitAuthors,
+  module Document.Build.Name.GitCommitHash,
+  module Document.Build.Name.GitCommitHashShort,
+  module Document.Build.Name.GitCommitTime,
+  module Document.Build.Name.GitCommitterEmail,
+  module Document.Build.Name.GitCommitterName,
+  module Document.Build.Name.GitSubject,
+  module Document.Build.Name.ImageColourspace,
+  module Document.Build.Name.ImageDepth,
+  module Document.Build.Name.ImageFormat,
+  module Document.Build.Name.ImageFrames,
+  module Document.Build.Name.ImageHeight,
+  module Document.Build.Name.ImageResolutionUnits,
+  module Document.Build.Name.ImageResolutionX,
+  module Document.Build.Name.ImageResolutionY,
+  module Document.Build.Name.ImageWidth,
+  module Document.Build.Name.InputDirectories,
+  module Document.Build.Name.InputFileCount,
+  module Document.Build.Name.InputFileMissing,
+  module Document.Build.Name.InputFilesNeeded,
+  module Document.Build.Name.MissingStrings,
+  module Document.Build.Name.OutputDirectory,
+  module Document.Build.Name.OutputGitDetails,
+  module Document.Build.Name.OutputImageInfo,
+  module Document.Build.Name.OutputPages,
+  module Document.Build.Name.OutputPath,
+  module Document.Build.Name.OutputSha256,
+  module Document.Build.Name.ShallowRepository,
+  module Document.Build.Name.StandardError,
+  module Document.Build.Name.StandardOutput,
+  module Document.Build.Name.SupportedExtensions,
+  module Document.Build.Name.TargetComplete,
+  module Document.Build.Name.TargetDirectory,
+  module Document.Build.Name.TargetFile,
   module Document.Build.Operations,
   module Document.Build.Output,
+  module Document.Build.OutputFile,
   module Document.Build.Project,
   module Document.Build.Rules,
+  module Document.Build.Target,
   module System.FilePather,
 ) where
 
 import Document.Build.Build
+import Document.Build.CommandLine
 import Document.Build.Context
+import Document.Build.Directories
 import Document.Build.Documents
 import Document.Build.Error
+import Document.Build.Error.FileTypeUnsupported
+import Document.Build.Error.InputFilesTooFew
+import Document.Build.Error.OutputFileMissing
+import Document.Build.Error.OutputNotInDirectory
+import Document.Build.Error.ProcessFailed
+import Document.Build.Error.ProcessOutputUnrecognised
+import Document.Build.Error.StringsMissing
+import Document.Build.GitDetails
+import Document.Build.ImageInfo
+import Document.Build.Name.CommandLineArguments
+import Document.Build.Name.CommandLineProgram
+import Document.Build.Name.ContextProject
+import Document.Build.Name.Directory
+import Document.Build.Name.ExpectedOutput
+import Document.Build.Name.FileNotInGit
+import Document.Build.Name.FileNotUtf8
+import Document.Build.Name.FontsDirectory
+import Document.Build.Name.GitAuthorEmail
+import Document.Build.Name.GitAuthorName
+import Document.Build.Name.GitAuthorTime
+import Document.Build.Name.GitAuthors
+import Document.Build.Name.GitCommitHash
+import Document.Build.Name.GitCommitHashShort
+import Document.Build.Name.GitCommitTime
+import Document.Build.Name.GitCommitterEmail
+import Document.Build.Name.GitCommitterName
+import Document.Build.Name.GitSubject
+import Document.Build.Name.ImageColourspace
+import Document.Build.Name.ImageDepth
+import Document.Build.Name.ImageFormat
+import Document.Build.Name.ImageFrames
+import Document.Build.Name.ImageHeight
+import Document.Build.Name.ImageResolutionUnits
+import Document.Build.Name.ImageResolutionX
+import Document.Build.Name.ImageResolutionY
+import Document.Build.Name.ImageWidth
+import Document.Build.Name.InputDirectories
+import Document.Build.Name.InputFileCount
+import Document.Build.Name.InputFileMissing
+import Document.Build.Name.InputFilesNeeded
+import Document.Build.Name.MissingStrings
+import Document.Build.Name.OutputDirectory
+import Document.Build.Name.OutputGitDetails
+import Document.Build.Name.OutputImageInfo
+import Document.Build.Name.OutputPages
+import Document.Build.Name.OutputPath
+import Document.Build.Name.OutputSha256
+import Document.Build.Name.ShallowRepository
+import Document.Build.Name.StandardError
+import Document.Build.Name.StandardOutput
+import Document.Build.Name.SupportedExtensions
+import Document.Build.Name.TargetComplete
+import Document.Build.Name.TargetDirectory
+import Document.Build.Name.TargetFile
 import Document.Build.Operations
 import Document.Build.Output
+import Document.Build.OutputFile
 import Document.Build.Project
 import Document.Build.Rules
+import Document.Build.Target
 import System.FilePather
+import Prelude hiding (cycle)
diff --git a/src/Document/Build/Build.hs b/src/Document/Build/Build.hs
--- a/src/Document/Build/Build.hs
+++ b/src/Document/Build/Build.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 {- | The effect that document builds run in, and the operations that the
@@ -19,7 +20,7 @@
     o <- 'lift' ('process' ('CommandLine' "wc" ["-w", p]))
     case words o of
       (n : _) | Just c <- readMaybe n -> pure c
-      _ -> 'lift' ('failBuild' ('ProcessOutputUnrecognised' ('CommandLine' "wc" ["-w", p]) "a word count"))
+      _ -> 'lift' ('failBuild' ('BuildErrorProcessOutputUnrecognised' ('ProcessOutputUnrecognised' ('CommandLine' "wc" ["-w", p]) "a word count")))
 @
 -}
 module Document.Build.Build (
@@ -60,13 +61,18 @@
 
 import Control.Exception (IOException)
 import Control.Exitcode (Exitcode, bimapExitcode, liftTryExitcode, mkExitFailure, runExitcode)
-import Control.Lens (review, view, _Wrapped)
+import Control.Lens (cloneLens, review, view)
 import Control.Monad.Reader (ask)
 import Control.Monad.Trans.Class (lift)
 import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)
 import Control.Process.Process (readCreateProcessWithExitCode)
 import Data.Bool (bool)
-import Document.Build.Error (BuildError (..), CommandLine (..), ReviewBuildError (..), buildErrorExitCode, displayBuildError, displayCommandLine)
+import Document.Build.CommandLine (CommandLine (..), displayCommandLine)
+import Document.Build.Error (BuildError (..), BuildErrorLike (..), ReviewBuildError, buildErrorExitCode, displayBuildError)
+import Document.Build.Error.OutputFileMissing (OutputFileMissing (..))
+import Document.Build.Error.ProcessFailed (ProcessFailed (..))
+import Document.Build.Name.CommandLineArguments (CommandLineArgumentsLike (..))
+import Document.Build.Name.CommandLineProgram (CommandLineProgramLike (..))
 import GHC.IO.Encoding (setLocaleEncoding, utf8)
 import Natural (NotZero, notZeroInteger)
 import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, doesPathExist, getTemporaryDirectory, removePathForcibly)
@@ -74,6 +80,7 @@
 import System.FilePath (takeDirectory)
 import System.FilePather.Combinators (Rule)
 import System.FilePather.FilePather (FilePather)
+import System.FilePather.Name.PathFunction (PathFunctionLike (..))
 import System.IO (hPrint, hPutStr, hPutStrLn, hSetEncoding, stderr, stdout)
 import System.IO.Temp (createTempDirectory)
 import System.Process (CreateProcess (cwd), proc)
@@ -149,7 +156,7 @@
 -}
 commandMain :: Command' a -> FilePath -> IO a
 commandMain c =
-  buildMain . view _Wrapped c
+  buildMain . view (cloneLens pathFunction) c
 {-# INLINE commandMain #-}
 
 {- | Run an @IO@ action, catching an 'IOException'.
@@ -168,7 +175,7 @@
 -}
 failBuild :: (ReviewBuildError e) => BuildError -> Build e a
 failBuild e =
-  mkExitFailure (pure (buildErrorExitCode e)) (pure (review reviewBuildError e))
+  mkExitFailure (pure (buildErrorExitCode e)) (pure (review buildError e))
 {-# INLINE failBuild #-}
 
 {- | Run a build, and return its result, or 'Nothing' if it fails, either
@@ -216,20 +223,20 @@
 The command line is printed to standard error before the program runs, and
 the standard error of the program is passed through to standard error. If the
 program exits with a non-zero exit code, the build fails with
-'ProcessFailed' and the same exit code. If the program cannot be run, for
+'BuildErrorProcessFailed' and the same exit code. If the program cannot be run, for
 example because it is not installed, the build fails with an 'IOException'.
 
 @since 0.1.0.0
 -}
 processWith :: (ReviewBuildError e) => (CreateProcess -> CreateProcess) -> String -> CommandLine -> Build e String
-processWith f i c@(CommandLine p as) =
+processWith f i c =
   do
     say ("+ " <> displayCommandLine c)
     (o, r) <-
       bimapExitcode
-        (\(o, r) -> review reviewBuildError (ProcessFailed c o r))
+        (\(o, r) -> review buildError (BuildErrorProcessFailed (ProcessFailed c o r)))
         id
-        (readCreateProcessWithExitCode (f (proc p as)) i)
+        (readCreateProcessWithExitCode (f (proc (view (cloneLens commandLineProgram) c) (view (cloneLens commandLineArguments) c))) i)
     io (hPutStr stderr r)
     pure o
 
@@ -271,13 +278,13 @@
     lift (io (exists p) >>= bool (failBuild (InputFileMissing p)) (pure p))
 
 {- | Return the path of a file that a command should have produced. Fails with
-'OutputFileMissing' if the file does not exist.
+'BuildErrorOutputFileMissing' if the file does not exist.
 
 @since 0.1.0.0
 -}
 expectOutput :: (ReviewBuildError e) => CommandLine -> FilePath -> Build e FilePath
 expectOutput c p =
-  io (doesFileExist p) >>= bool (failBuild (OutputFileMissing c p)) (pure p)
+  io (doesFileExist p) >>= bool (failBuild (BuildErrorOutputFileMissing (OutputFileMissing c p))) (pure p)
 
 {- | Create the directory that a file path is in, and its parents, if they do
 not exist.
diff --git a/src/Document/Build/CommandLine.hs b/src/Document/Build/CommandLine.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/CommandLine.hs
@@ -0,0 +1,156 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | A program and its arguments.
+
+A 'CommandLine' is a product: its fields are named 'commandLineProgram' and
+'commandLineArguments'.
+-}
+module Document.Build.CommandLine (
+  -- * Type
+  CommandLine (..),
+
+  -- * Display
+  displayCommandLine,
+  shellQuote,
+
+  -- * Optics
+  CommandLineLike (..),
+  lawNamesCommandLine,
+  InheritCommandLine,
+  inheritCommandLine,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Document.Build.Name.CommandLineArguments (CommandLineArgumentsLike (..))
+import Document.Build.Name.CommandLineProgram (CommandLineProgramLike (..))
+import GHC.Generics (Generic)
+
+{- $setup
+>>> import Control.Lens (cloneLens, view)
+-}
+
+{- | A program ('commandLineProgram') and its arguments
+('commandLineArguments').
+
+>>> view (cloneLens commandLineProgram) (CommandLine "pdfinfo" ["my file.pdf"])
+"pdfinfo"
+
+>>> view (cloneLens commandLineArguments) (CommandLine "pdfinfo" ["my file.pdf"])
+["my file.pdf"]
+
+@since 0.1.0.0
+-}
+data CommandLine
+  = CommandLine FilePath [String]
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | A command line as it would be written in a POSIX shell.
+
+>>> displayCommandLine (CommandLine "pdfinfo" ["my file.pdf", "-box"])
+"pdfinfo 'my file.pdf' -box"
+
+@since 0.1.0.0
+-}
+displayCommandLine :: CommandLine -> String
+displayCommandLine c =
+  unwords (fmap shellQuote (view (cloneLens commandLineProgram) c : view (cloneLens commandLineArguments) c))
+
+{- | Quote a word for a POSIX shell, if it needs quoting.
+
+>>> shellQuote "public/report.pdf"
+"public/report.pdf"
+
+>>> shellQuote "it's"
+"'it'\\''s'"
+
+>>> shellQuote ""
+"''"
+
+@since 0.1.0.0
+-}
+shellQuote :: String -> String
+shellQuote s =
+  let safe c = c `elem` ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./=:,+%@" :: String)
+      quote c = if c == '\'' then "'\\''" else [c]
+   in if not (null s) && all safe s
+        then s
+        else "'" <> concatMap quote s <> "'"
+
+{- | The class of 'CommandLine'.
+
+@since 0.2.0.0
+-}
+class CommandLineLike p f x x' where
+  commandLine ::
+    Optic p f x x' CommandLine CommandLine
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ CommandLine) => CommandLineLike p f CommandLine x' where
+  commandLine =
+    id
+  {-# INLINE commandLine #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ CommandLine) => CommandLineProgramLike p f CommandLine x' where
+  commandLineProgram f (CommandLine p as) =
+    fmap (`CommandLine` as) (f p)
+  {-# INLINE commandLineProgram #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ CommandLine) => CommandLineArgumentsLike p f CommandLine x' where
+  commandLineArguments f (CommandLine p as) =
+    fmap (CommandLine p) (f as)
+  {-# INLINE commandLineArguments #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesCommandLine == id
+
+@since 0.2.0.0
+-}
+lawNamesCommandLine :: CommandLine -> CommandLine
+lawNamesCommandLine s =
+  CommandLine (view (cloneLens commandLineProgram) s) (view (cloneLens commandLineArguments) s)
+
+{- | The classes that a data type holding 'CommandLine' inherits.
+
+@since 0.2.0.0
+-}
+type InheritCommandLine s =
+  ( CommandLineLike (->) (Const ()) s s
+  , CommandLineProgramLike (->) (Const ()) s s
+  , CommandLineArgumentsLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of
+'CommandLine'.
+
+@since 0.2.0.0
+-}
+inheritCommandLine :: (InheritCommandLine s) => proxy s -> ()
+inheritCommandLine _ =
+  ()
diff --git a/src/Document/Build/Context.hs b/src/Document/Build/Context.hs
--- a/src/Document/Build/Context.hs
+++ b/src/Document/Build/Context.hs
@@ -1,10 +1,13 @@
+{-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeOperators #-}
 {-# OPTIONS_GHC -Wall #-}
 
 {- | The context that rules run in, and the combinators that make rules and
@@ -21,38 +24,14 @@
 'Document.Build.Rules.pageCountRules' (\<> ".pages")
   <> 'onProject' "group/project" ('Document.Build.Rules.splitPdf' "report.pdf" [("1", "report_cover.pdf")])
 @
--}
-module Document.Build.Context (
-  -- * Directories
-  Directories (..),
-  outputDirectory,
-  inputDirectories,
-  fontsDirectory,
-  GetDirectories (..),
-  HasDirectories (..),
-  ReviewDirectories (..),
-  AsDirectories (..),
 
-  -- * Targets
-  KnownFiles,
-  Target (..),
-  _TargetFile,
-  _TargetDirectory,
-  _TargetComplete,
-  GetTarget (..),
-  HasTarget (..),
-  ReviewTarget (..),
-  AsTarget (..),
 
-  -- * Context
+A 'Context' is a product: its fields are named 'directories', 'contextProject' and 'target'.
+It holds 'Directories' and 'Target', so it inherits their names.
+-}
+module Document.Build.Context (
+  -- * Type
   Context (..),
-  contextDirectories,
-  contextProject,
-  contextTarget,
-  GetContext (..),
-  HasContext (..),
-  ReviewContext (..),
-  AsContext (..),
 
   -- * Rules
   Rules,
@@ -72,354 +51,210 @@
   onOutputs,
   afterStep,
   unlessAfterStep,
+
+  -- * Optics
+  ContextLike (..),
+  lawNamesContext,
+  InheritContext,
+  inheritContext,
+  inheritsContext,
 ) where
 
 import Control.DeepSeq (NFData)
-import Control.Lens (Field1, Field2, Field3, Getter, Lens', Prism', Review, lens, preview, prism', review, unto, view, (^.), _1, _2, _3, _Wrapped)
+import Control.Lens (Optic, cloneLens, preview, view)
 import Data.Bool (bool)
 import Data.Data (Data)
-import Data.Lens.Injection (Injection1 (..), Injection2 (..), Injection3 (..))
-import Data.Map (Map)
+import Data.Functor.Const (Const)
+import Data.Proxy (Proxy (..))
 import Document.Build.Build (Build', Command', io)
+import Document.Build.Directories (Directories, DirectoriesLike (..), inheritDirectories)
+import Document.Build.Name.ContextProject (ContextProjectLike (..))
+import Document.Build.Name.FontsDirectory (FontsDirectoryLike (..))
+import Document.Build.Name.InputDirectories (InputDirectoriesLike (..))
+import Document.Build.Name.OutputDirectory (OutputDirectoryLike (..))
+import Document.Build.Name.TargetComplete (TargetCompleteLike (..))
+import Document.Build.Name.TargetDirectory (TargetDirectoryLike (..))
+import Document.Build.Name.TargetFile (TargetFileLike (..))
+import Document.Build.Target (KnownFiles, Target, TargetLike (..), inheritTarget)
 import GHC.Generics (Generic)
 import System.Directory (doesFileExist)
 import System.FilePather.Combinators (isWithin)
 import System.FilePather.FilePather (FilePather (..))
-import System.FilePather.Tracked (Tracked, step, step', whenPath, whenTrail)
-import System.FilePather.Trail (Trail, lastStep, stepName)
+import System.FilePather.Name.PathFunction (PathFunctionLike (..))
+import System.FilePather.Name.StepName (stepName)
+import System.FilePather.Tracked (Tracked, track, track', whenPath, whenTrail)
+import System.FilePather.Trail (lastStep)
 
 {- $setup
->>> import Control.Lens ((^.), (&), (.~), (^?))
+>>> import Control.Lens (cloneLens, preview, view)
+>>> import Document.Build.Directories (Directories (..))
+>>> import Document.Build.Target (Target (..))
 -}
 
-{- | The directories of a build: the output directory, which the build removes
-and writes to; the input directories, whose files the rules run on, in
-order; and the fonts directory, whose fonts LibreOffice uses during the
-build, if there is one.
+{- | The context that rules are run in: the directories of the build
+('directories'), the path of the project ('contextProject', see
+'Document.Build.Project.projectPath'), and what the rules are running on
+('target').
 
->>> Directories "public" ["src"] Nothing ^. outputDirectory
+>>> view (cloneLens outputDirectory) (Context (Directories "public" ["src"] Nothing) "group/project" TargetFile)
 "public"
 
->>> (Directories "public" ["src"] Nothing & fontsDirectory .~ Just "fonts") ^. fontsDirectory
-Just "fonts"
+>>> preview targetFile (Context (Directories "public" ["src"] Nothing) "group/project" TargetFile)
+Just ()
 
 @since 0.1.0.0
 -}
-data Directories
-  = Directories FilePath [FilePath] (Maybe FilePath)
+data Context
+  = Context Directories String Target
   deriving stock (Eq, Ord, Show, Generic, Data)
   deriving anyclass (NFData)
 
--- | @since 0.1.0.0
-instance Field1 Directories Directories FilePath FilePath
-
--- | @since 0.1.0.0
-instance Field2 Directories Directories [FilePath] [FilePath]
-
--- | @since 0.1.0.0
-instance Field3 Directories Directories (Maybe FilePath) (Maybe FilePath)
-
-{- | The directory that the build writes to. It is removed at the start of
-the build, and every file the rules make must be in it.
-
-@since 0.1.0.0
--}
-outputDirectory :: Lens' Directories FilePath
-outputDirectory =
-  _1
-{-# INLINE outputDirectory #-}
-
-{- | The directories whose files the rules run on, in order: when two files
-make the same file, the first is kept.
+{- | The class of 'Context'.
 
-@since 0.1.0.0
+@since 0.2.0.0
 -}
-inputDirectories :: Lens' Directories [FilePath]
-inputDirectories =
-  _2
-{-# INLINE inputDirectories #-}
+class ContextLike p f x x' where
+  context ::
+    Optic p f x x' Context Context
 
-{- | The directory of fonts that LibreOffice uses during the build, if there
-is one (see 'Document.Build.Project.withFonts').
+{- | Identity
 
-@since 0.1.0.0
+@since 0.2.0.0
 -}
-fontsDirectory :: Lens' Directories (Maybe FilePath)
-fontsDirectory =
-  _3
-{-# INLINE fontsDirectory #-}
-
--- | @since 0.1.0.0
-class GetDirectories s where
-  getDirectories :: Getter s Directories
-
--- | @since 0.1.0.0
-instance GetDirectories Directories where
-  getDirectories =
+instance (x' ~ Context) => ContextLike p f Context x' where
+  context =
     id
-  {-# INLINE getDirectories #-}
-
--- | @since 0.1.0.0
-class (GetDirectories s) => HasDirectories s where
-  {-# MINIMAL setDirectories #-}
-  setDirectories :: Directories -> s -> s
-
-  directories :: Lens' s Directories
-  directories =
-    lens (view getDirectories) (flip setDirectories)
-  {-# INLINE directories #-}
-
--- | @since 0.1.0.0
-instance HasDirectories Directories where
-  setDirectories =
-    const
-  {-# INLINE setDirectories #-}
-
--- | @since 0.1.0.0
-class ReviewDirectories t where
-  reviewDirectories :: Review t Directories
-
--- | @since 0.1.0.0
-instance ReviewDirectories Directories where
-  reviewDirectories =
-    unto id
-  {-# INLINE reviewDirectories #-}
-
--- | @since 0.1.0.0
-class (ReviewDirectories t) => AsDirectories t where
-  {-# MINIMAL matchDirectories #-}
-  matchDirectories :: t -> Maybe Directories
-
-  _Directories :: Prism' t Directories
-  _Directories =
-    prism' (review reviewDirectories) matchDirectories
-  {-# INLINE _Directories #-}
-
--- | @since 0.1.0.0
-instance AsDirectories Directories where
-  matchDirectories =
-    Just
-  {-# INLINE matchDirectories #-}
+  {-# INLINE context #-}
 
-{- | The files that a build knows: the files of its input directories, with
-the empty trail, and the files that the rules made, with their trails, by
-normalised path.
+{- | Lens: 'directories' is the default name of the field of type 'Directories'.
 
-@since 0.1.0.0
+@since 0.2.0.0
 -}
-type KnownFiles = Map FilePath Trail
+instance (p ~ (->), Functor f, x' ~ Context) => DirectoriesLike p f Context x' where
+  directories f (Context x1 x2 x3) =
+    fmap (\x1' -> Context x1' x2 x3) (f x1)
+  {-# INLINE directories #-}
 
-{- | What a build is running the rules on.
+{- | Lens
 
-@since 0.1.0.0
+@since 0.2.0.0
 -}
-data Target
-  = -- | A file: a file of an input directory, or a file the rules made.
-    TargetFile
-  | -- | A directory of the output directory.
-    TargetDirectory
-  | -- | The output directory, once, when the rules make no more files, with
-    -- every file the build knows.
-    TargetComplete KnownFiles
-  deriving stock (Eq, Ord, Show, Generic, Data)
-  deriving anyclass (NFData)
-
--- | @since 0.1.0.0
-instance Injection1 Target Target () ()
-
--- | @since 0.1.0.0
-instance Injection2 Target Target () ()
-
--- | @since 0.1.0.0
-instance Injection3 Target Target KnownFiles KnownFiles
-
--- | @since 0.1.0.0
-_TargetFile :: Prism' Target ()
-_TargetFile =
-  _I1
-{-# INLINE _TargetFile #-}
-
--- | @since 0.1.0.0
-_TargetDirectory :: Prism' Target ()
-_TargetDirectory =
-  _I2
-{-# INLINE _TargetDirectory #-}
+instance (p ~ (->), Functor f, x' ~ Context) => ContextProjectLike p f Context x' where
+  contextProject f (Context x1 x2 x3) =
+    fmap (\x2' -> Context x1 x2' x3) (f x2)
+  {-# INLINE contextProject #-}
 
-{- | >>> TargetComplete mempty ^? _TargetComplete
-Just (fromList [])
+{- | Lens: 'target' is the default name of the field of type 'Target'.
 
-@since 0.1.0.0
+@since 0.2.0.0
 -}
-_TargetComplete :: Prism' Target KnownFiles
-_TargetComplete =
-  _I3
-{-# INLINE _TargetComplete #-}
-
--- | @since 0.1.0.0
-class GetTarget s where
-  getTarget :: Getter s Target
-
--- | @since 0.1.0.0
-instance GetTarget Target where
-  getTarget =
-    id
-  {-# INLINE getTarget #-}
-
--- | @since 0.1.0.0
-class (GetTarget s) => HasTarget s where
-  {-# MINIMAL setTarget #-}
-  setTarget :: Target -> s -> s
-
-  target :: Lens' s Target
-  target =
-    lens (view getTarget) (flip setTarget)
+instance (p ~ (->), Functor f, x' ~ Context) => TargetLike p f Context x' where
+  target f (Context x1 x2 x3) =
+    fmap (Context x1 x2) (f x3)
   {-# INLINE target #-}
 
--- | @since 0.1.0.0
-instance HasTarget Target where
-  setTarget =
-    const
-  {-# INLINE setTarget #-}
-
--- | @since 0.1.0.0
-class ReviewTarget t where
-  reviewTarget :: Review t Target
-
--- | @since 0.1.0.0
-instance ReviewTarget Target where
-  reviewTarget =
-    unto id
-  {-# INLINE reviewTarget #-}
-
--- | @since 0.1.0.0
-class (ReviewTarget t) => AsTarget t where
-  {-# MINIMAL matchTarget #-}
-  matchTarget :: t -> Maybe Target
-
-  _Target :: Prism' t Target
-  _Target =
-    prism' (review reviewTarget) matchTarget
-  {-# INLINE _Target #-}
-
--- | @since 0.1.0.0
-instance AsTarget Target where
-  matchTarget =
-    Just
-  {-# INLINE matchTarget #-}
-
-{- | The context that rules are run in: the directories of the build, the
-path of the project (see 'Document.Build.Project.projectPath'), and what the
-rules are running on.
+{- | Lens: through 'directories', a lens, then 'Directories''s 'outputDirectory', a lens.
 
-@since 0.1.0.0
+@since 0.2.0.0
 -}
-data Context
-  = Context Directories String Target
-  deriving stock (Eq, Ord, Show, Generic, Data)
-  deriving anyclass (NFData)
+instance (p ~ (->), Functor f, x' ~ Context) => OutputDirectoryLike p f Context x' where
+  outputDirectory =
+    directories . outputDirectory
+  {-# INLINE outputDirectory #-}
 
--- | @since 0.1.0.0
-instance Field1 Context Context Directories Directories
+{- | Lens: through 'directories', a lens, then 'Directories''s 'inputDirectories', a lens.
 
--- | @since 0.1.0.0
-instance Field2 Context Context String String
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ Context) => InputDirectoriesLike p f Context x' where
+  inputDirectories =
+    directories . inputDirectories
+  {-# INLINE inputDirectories #-}
 
--- | @since 0.1.0.0
-instance Field3 Context Context Target Target
+{- | Lens: through 'directories', a lens, then 'Directories''s 'fontsDirectory', a lens.
 
--- | @since 0.1.0.0
-contextDirectories :: Lens' Context Directories
-contextDirectories =
-  _1
-{-# INLINE contextDirectories #-}
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ Context) => FontsDirectoryLike p f Context x' where
+  fontsDirectory =
+    directories . fontsDirectory
+  {-# INLINE fontsDirectory #-}
 
--- | @since 0.1.0.0
-contextProject :: Lens' Context String
-contextProject =
-  _2
-{-# INLINE contextProject #-}
+{- | Affine traversal: through 'target', a lens, then 'Target''s 'targetFile', a prism.
 
--- | @since 0.1.0.0
-contextTarget :: Lens' Context Target
-contextTarget =
-  _3
-{-# INLINE contextTarget #-}
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ Context) => TargetFileLike p f Context x' where
+  targetFile =
+    target . targetFile
+  {-# INLINE targetFile #-}
 
--- | @since 0.1.0.0
-class GetContext s where
-  getContext :: Getter s Context
+{- | Affine traversal: through 'target', a lens, then 'Target''s 'targetDirectory', a prism.
 
--- | @since 0.1.0.0
-instance GetContext Context where
-  getContext =
-    id
-  {-# INLINE getContext #-}
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ Context) => TargetDirectoryLike p f Context x' where
+  targetDirectory =
+    target . targetDirectory
+  {-# INLINE targetDirectory #-}
 
--- | @since 0.1.0.0
-class (GetContext s) => HasContext s where
-  {-# MINIMAL setContext #-}
-  setContext :: Context -> s -> s
+{- | Affine traversal: through 'target', a lens, then 'Target''s 'targetComplete', a prism.
 
-  context :: Lens' s Context
-  context =
-    lens (view getContext) (flip setContext)
-  {-# INLINE context #-}
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ Context) => TargetCompleteLike p f Context x' where
+  targetComplete =
+    target . targetComplete
+  {-# INLINE targetComplete #-}
 
--- | @since 0.1.0.0
-instance HasContext Context where
-  setContext =
-    const
-  {-# INLINE setContext #-}
+{- | Rebuild a value using only the name of each field.
 
--- | @since 0.1.0.0
-instance GetDirectories Context where
-  getDirectories =
-    contextDirectories
-  {-# INLINE getDirectories #-}
+Laws:
 
--- | @since 0.1.0.0
-instance HasDirectories Context where
-  setDirectories d (Context _ p t) =
-    Context d p t
-  {-# INLINE setDirectories #-}
+> lawNamesContext == id
 
--- | @since 0.1.0.0
-instance GetTarget Context where
-  getTarget =
-    contextTarget
-  {-# INLINE getTarget #-}
+@since 0.2.0.0
+-}
+lawNamesContext :: Context -> Context
+lawNamesContext s =
+  Context
+    (view (cloneLens directories) s)
+    (view (cloneLens contextProject) s)
+    (view (cloneLens target) s)
 
--- | @since 0.1.0.0
-instance HasTarget Context where
-  setTarget t (Context d p _) =
-    Context d p t
-  {-# INLINE setTarget #-}
+{- | The classes that a data type holding 'Context' inherits.
 
--- | @since 0.1.0.0
-class ReviewContext t where
-  reviewContext :: Review t Context
+@since 0.2.0.0
+-}
+type InheritContext s =
+  ( ContextLike (->) (Const ()) s s
+  , DirectoriesLike (->) (Const ()) s s
+  , ContextProjectLike (->) (Const ()) s s
+  , TargetLike (->) (Const ()) s s
+  , OutputDirectoryLike (->) (Const ()) s s
+  , InputDirectoriesLike (->) (Const ()) s s
+  , FontsDirectoryLike (->) (Const ()) s s
+  , TargetFileLike (->) (Const ()) s s
+  , TargetDirectoryLike (->) (Const ()) s s
+  , TargetCompleteLike (->) (Const ()) s s
+  )
 
--- | @since 0.1.0.0
-instance ReviewContext Context where
-  reviewContext =
-    unto id
-  {-# INLINE reviewContext #-}
+{- | Check, where it is applied, that @s@ inherits every name of 'Context'.
 
--- | @since 0.1.0.0
-class (ReviewContext t) => AsContext t where
-  {-# MINIMAL matchContext #-}
-  matchContext :: t -> Maybe Context
+@since 0.2.0.0
+-}
+inheritContext :: (InheritContext s) => proxy s -> ()
+inheritContext _ =
+  ()
 
-  _Context :: Prism' t Context
-  _Context =
-    prism' (review reviewContext) matchContext
-  {-# INLINE _Context #-}
+{- | 'Context' inherits every name of 'Directories' and of 'Target'.
 
--- | @since 0.1.0.0
-instance AsContext Context where
-  matchContext =
-    Just
-  {-# INLINE matchContext #-}
+@since 0.2.0.0
+-}
+inheritsContext :: [()]
+inheritsContext =
+  let p = Proxy :: Proxy Context
+   in [inheritDirectories p, inheritTarget p]
 
 {- | The rules of a build: a function of its context, to the build steps to
 run on a file or directory. Rules combine with '<>', which runs both, and
@@ -439,7 +274,7 @@
 {-# INLINE fromTracked #-}
 
 {- | A named step that runs a command, which makes the files given by a
-function of its input file. See 'System.FilePather.Tracked.step'.
+function of its input file. See 'System.FilePather.Tracked.track'.
 
 The command is not run if one of the files already exists, because the build
 removes the output directory first, so another step made it in this build:
@@ -449,19 +284,19 @@
 -}
 buildStep :: (Monoid a) => String -> (FilePath -> [FilePath]) -> Command' a -> Tracked Build' a
 buildStep n outs c =
-  step n outs $
+  track n outs $
     FilePather $ \p ->
       io (or <$> traverse doesFileExist (outs p))
-        >>= bool (view _Wrapped c p) (pure mempty)
+        >>= bool (view (cloneLens pathFunction) c p) (pure mempty)
 
 {- | A named step that runs a command, which returns the files it made. See
-'System.FilePather.Tracked.step''.
+'System.FilePather.Tracked.track''.
 
 @since 0.1.0.0
 -}
 buildStep' :: String -> Command' [FilePath] -> Tracked Build' ()
 buildStep' n c =
-  step' n (fmap (,()) c)
+  track' n (fmap (,()) c)
 
 {- | Rules for one project, by its path, such as @"group/project"@.
 
@@ -478,7 +313,7 @@
 -}
 onProjects :: (String -> Bool) -> Rules -> Rules
 onProjects f r c =
-  bool mempty (r c) (f (c ^. contextProject))
+  bool mempty (r c) (f (view (cloneLens contextProject) c))
 {-# INLINE onProjects #-}
 
 {- | Rules that depend on the path of the project, such as rules with
@@ -488,7 +323,7 @@
 -}
 withProject :: (String -> Rules) -> Rules
 withProject f c =
-  f (c ^. contextProject) c
+  f (view (cloneLens contextProject) c) c
 {-# INLINE withProject #-}
 
 {- | Rules for the files and directories whose paths satisfy a predicate.
@@ -507,7 +342,7 @@
 -}
 onFiles :: Rules -> Rules
 onFiles r c =
-  maybe mempty (const (r c)) (preview (contextTarget . _TargetFile) c)
+  maybe mempty (const (r c)) (preview targetFile c)
 {-# INLINE onFiles #-}
 
 {- | Rules for the directories of the output directory, which run after the
@@ -517,7 +352,7 @@
 -}
 onDirectories :: Rules -> Rules
 onDirectories r c =
-  maybe mempty (const (r c)) (preview (contextTarget . _TargetDirectory) c)
+  maybe mempty (const (r c)) (preview targetDirectory c)
 {-# INLINE onDirectories #-}
 
 {- | Rules that run once, on the output directory, when the rules make no
@@ -528,7 +363,7 @@
 -}
 onComplete :: (KnownFiles -> Rules) -> Rules
 onComplete r c =
-  maybe mempty (`r` c) (preview (contextTarget . _TargetComplete) c)
+  maybe mempty (`r` c) (preview targetComplete c)
 {-# INLINE onComplete #-}
 
 {- | Rules for the files of the input directories only: files with the empty
@@ -550,7 +385,7 @@
 -}
 onOutputs :: Rules -> Rules
 onOutputs r c =
-  whenPath (isWithin (c ^. contextDirectories . outputDirectory)) (r c)
+  whenPath (isWithin (view (cloneLens outputDirectory) c)) (r c)
 {-# INLINE onOutputs #-}
 
 {- | Rules for files made by a step whose name satisfies a predicate.
diff --git a/src/Document/Build/Directories.hs b/src/Document/Build/Directories.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Directories.hs
@@ -0,0 +1,135 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The directories of a build.
+
+A 'Directories' is a product: its fields are named 'outputDirectory', 'inputDirectories' and 'fontsDirectory'.
+-}
+module Document.Build.Directories (
+  -- * Type
+  Directories (..),
+
+  -- * Optics
+  DirectoriesLike (..),
+  lawNamesDirectories,
+  InheritDirectories,
+  inheritDirectories,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Document.Build.Name.FontsDirectory (FontsDirectoryLike (..))
+import Document.Build.Name.InputDirectories (InputDirectoriesLike (..))
+import Document.Build.Name.OutputDirectory (OutputDirectoryLike (..))
+import GHC.Generics (Generic)
+
+{- $setup
+>>> import Control.Lens (cloneLens, set, view)
+-}
+
+{- | The directories of a build: the output directory ('outputDirectory'), which
+the build removes and writes to; the input directories ('inputDirectories'),
+whose files the rules run on, in order; and the fonts directory
+('fontsDirectory'), whose fonts LibreOffice uses during the build, if there is
+one.
+
+>>> view (cloneLens outputDirectory) (Directories "public" ["src"] Nothing)
+"public"
+
+>>> view (cloneLens fontsDirectory) (set fontsDirectory (Just "fonts") (Directories "public" ["src"] Nothing))
+Just "fonts"
+
+@since 0.1.0.0
+-}
+data Directories
+  = Directories FilePath [FilePath] (Maybe FilePath)
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The class of 'Directories'.
+
+@since 0.2.0.0
+-}
+class DirectoriesLike p f x x' where
+  directories ::
+    Optic p f x x' Directories Directories
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Directories) => DirectoriesLike p f Directories x' where
+  directories =
+    id
+  {-# INLINE directories #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ Directories) => OutputDirectoryLike p f Directories x' where
+  outputDirectory f (Directories x1 x2 x3) =
+    fmap (\x1' -> Directories x1' x2 x3) (f x1)
+  {-# INLINE outputDirectory #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ Directories) => InputDirectoriesLike p f Directories x' where
+  inputDirectories f (Directories x1 x2 x3) =
+    fmap (\x2' -> Directories x1 x2' x3) (f x2)
+  {-# INLINE inputDirectories #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ Directories) => FontsDirectoryLike p f Directories x' where
+  fontsDirectory f (Directories x1 x2 x3) =
+    fmap (Directories x1 x2) (f x3)
+  {-# INLINE fontsDirectory #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesDirectories == id
+
+@since 0.2.0.0
+-}
+lawNamesDirectories :: Directories -> Directories
+lawNamesDirectories s =
+  Directories
+    (view (cloneLens outputDirectory) s)
+    (view (cloneLens inputDirectories) s)
+    (view (cloneLens fontsDirectory) s)
+
+{- | The classes that a data type holding 'Directories' inherits.
+
+@since 0.2.0.0
+-}
+type InheritDirectories s =
+  ( DirectoriesLike (->) (Const ()) s s
+  , OutputDirectoryLike (->) (Const ()) s s
+  , InputDirectoriesLike (->) (Const ()) s s
+  , FontsDirectoryLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'Directories'.
+
+@since 0.2.0.0
+-}
+inheritDirectories :: (InheritDirectories s) => proxy s -> ()
+inheritDirectories _ =
+  ()
diff --git a/src/Document/Build/Documents.hs b/src/Document/Build/Documents.hs
--- a/src/Document/Build/Documents.hs
+++ b/src/Document/Build/Documents.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE TupleSections #-}
 {-# OPTIONS_GHC -Wall #-}
@@ -30,7 +31,7 @@
 chain, stops the build with 'Document.Build.Error.BuildCycle'. A file made by
 two different steps is kept as the first made it, with a warning. A step that
 makes a file outside the output directory stops the build with
-'Document.Build.Error.OutputNotInDirectory', so that a build never changes its
+'Document.Build.Error.BuildErrorOutputNotInDirectory', so that a build never changes its
 input directories, and builds the same files every time.
 -}
 module Document.Build.Documents (
@@ -38,24 +39,32 @@
   buildProject,
 ) where
 
-import Control.Lens (preview, review, view, (^.), _Cons)
+import Control.Lens (cloneLens, preview, review, view, _Cons)
 import Control.Monad (foldM, void)
 import Control.Monad.Trans.Class (lift)
 import Data.Bool (bool)
 import Data.List (sort)
 import qualified Data.Map as Map
 import Document.Build.Build (Build', Command', commandMain, failBuild, io, say)
-import Document.Build.Context (Context (..), Directories, KnownFiles, Rules, Target (..), fontsDirectory, inputDirectories, outputDirectory)
+import Document.Build.Context (Context (..), Rules)
+import Document.Build.Directories (Directories)
 import Document.Build.Error (BuildError (..))
+import Document.Build.Error.OutputNotInDirectory (OutputNotInDirectory (..))
 import Document.Build.File (filesIn)
+import Document.Build.Name.FontsDirectory (FontsDirectoryLike (..))
+import Document.Build.Name.InputDirectories (InputDirectoriesLike (..))
+import Document.Build.Name.OutputDirectory (OutputDirectoryLike (..))
 import Document.Build.Project (clean, projectPath, withFonts)
+import Document.Build.Target (KnownFiles, Target (..))
 import System.Directory (doesDirectoryExist, listDirectory, withCurrentDirectory)
 import System.Environment (getArgs)
 import System.Exit (exitFailure)
 import System.FilePath (equalFilePath, normalise, (</>))
 import System.FilePather.Combinators (isWithin)
+import System.FilePather.Name.StepInput (StepInputLike (..))
+import System.FilePather.Name.StepName (StepNameLike (..))
 import System.FilePather.Tracked (trackedFunction)
-import System.FilePather.Trail (Step (..), Trail (..), lastStep)
+import System.FilePather.Trail (Trail (..), lastStep)
 import System.IO (hPutStrLn, stderr)
 
 {- | Build the project in the directory given as the only argument, or the
@@ -83,12 +92,12 @@
 buildProject ds r =
   do
     project <- projectPath
-    lift . maybe id withFonts (ds ^. fontsDirectory) $
+    lift . maybe id withFonts (view (cloneLens fontsDirectory) ds) $
       do
-        let out = ds ^. outputDirectory
+        let out = view (cloneLens outputDirectory) ds
             context = Context ds project
         clean out
-        roots <- concat <$> traverse filesIn (ds ^. inputDirectories)
+        roots <- concat <$> traverse filesIn (view (cloneLens inputDirectories) ds)
         known <-
           settle
             (runRules out r (context TargetFile))
@@ -129,9 +138,11 @@
   runOne acc (x, t) =
     view trackedFunction (r c) t x
       >>= either (failBuild . BuildCycle) (foldM record acc . fst)
-  record (k, made) (o, s@(Step n i)) =
+  record (k, made) (o, s) =
     let o' = normalise o
-     in bool (failBuild (OutputNotInDirectory o out)) (pure ()) (isWithin out o) *> case Map.lookup o' k of
+        n = view (cloneLens stepName) s
+        i = view (cloneLens stepInput) s
+     in bool (failBuild (BuildErrorOutputNotInDirectory (OutputNotInDirectory o out))) (pure ()) (isWithin out o) *> case Map.lookup o' k of
           Just t ->
             (k, made)
               <$ bool
@@ -141,10 +152,10 @@
           Nothing ->
             let t = review _Cons (s, Map.findWithDefault (Trail []) (normalise i) k)
              in pure (Map.insert o' t k, (o, t) : made)
-  sameStep (Step n i) =
-    maybe False (\(Step n' i') -> n == n' && equalFilePath i i')
+  sameStep s =
+    maybe False (\s' -> view (cloneLens stepName) s == view (cloneLens stepName) s' && equalFilePath (view (cloneLens stepInput) s) (view (cloneLens stepInput) s'))
   describeMaker t =
-    maybe "as a source file" (\(Step n i) -> "by " <> n <> " from " <> i) (preview lastStep t)
+    maybe "as a source file" (\s -> "by " <> view (cloneLens stepName) s <> " from " <> view (cloneLens stepInput) s) (preview lastStep t)
 
 -- | A directory and its subdirectories, in sorted order, if it exists.
 directoriesOf :: FilePath -> IO [FilePath]
diff --git a/src/Document/Build/Error.hs b/src/Document/Build/Error.hs
--- a/src/Document/Build/Error.hs
+++ b/src/Document/Build/Error.hs
@@ -1,460 +1,838 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# OPTIONS_GHC -Wall #-}
-
--- | Errors that occur while building documents.
-module Document.Build.Error (
-  -- * Command lines
-  CommandLine (..),
-  commandLineProgram,
-  commandLineArguments,
-  displayCommandLine,
-  shellQuote,
-  GetCommandLine (..),
-  HasCommandLine (..),
-  ReviewCommandLine (..),
-  AsCommandLine (..),
-
-  -- * Build errors
-  BuildError (..),
-  _ProcessFailed,
-  _ProcessOutputUnrecognised,
-  _InputFileMissing,
-  _OutputFileMissing,
-  _StringsMissing,
-  _FileTypeUnsupported,
-  _FileNotInGit,
-  _FileNotUtf8,
-  _BuildCycle,
-  _InputFilesTooFew,
-  _OutputNotInDirectory,
-  _ShallowRepository,
-  buildErrorExitCode,
-  displayBuildError,
-  GetBuildError (..),
-  HasBuildError (..),
-  ReviewBuildError (..),
-  AsBuildError (..),
-) where
-
-import Control.DeepSeq (NFData)
-import Control.Lens (Field1, Field2, Getter, Lens', Prism', Review, lens, prism', review, unto, view, _1, _2)
-import Data.Data (Data)
-import Data.Foldable (toList)
-import Data.Lens.Injection (Injection1 (..), Injection10 (..), Injection11 (..), Injection12 (..), Injection2 (..), Injection3 (..), Injection4 (..), Injection5 (..), Injection6 (..), Injection7 (..), Injection8 (..), Injection9 (..))
-import Data.List.NonEmpty (NonEmpty)
-import GHC.Generics (Generic)
-import Natural (NotZero, notZeroOr)
-import System.FilePather.Trail (Cycle, displayCycle)
-
-{- $setup
->>> import Control.Lens ((^.), (^?), (#))
->>> import Data.List.NonEmpty (NonEmpty (..))
--}
-
-{- | A program and its arguments.
-
->>> CommandLine "pdfinfo" ["my file.pdf"] ^. commandLineProgram
-"pdfinfo"
-
-@since 0.1.0.0
--}
-data CommandLine
-  = CommandLine FilePath [String]
-  deriving stock (Eq, Ord, Show, Generic, Data)
-  deriving anyclass (NFData)
-
--- | @since 0.1.0.0
-instance Field1 CommandLine CommandLine FilePath FilePath
-
--- | @since 0.1.0.0
-instance Field2 CommandLine CommandLine [String] [String]
-
-{- | The program of a command line.
-
-@since 0.1.0.0
--}
-commandLineProgram :: Lens' CommandLine FilePath
-commandLineProgram =
-  _1
-{-# INLINE commandLineProgram #-}
-
-{- | The arguments of a command line.
-
->>> CommandLine "pdfinfo" ["my file.pdf"] ^. commandLineArguments
-["my file.pdf"]
-
-@since 0.1.0.0
--}
-commandLineArguments :: Lens' CommandLine [String]
-commandLineArguments =
-  _2
-{-# INLINE commandLineArguments #-}
-
-{- | A command line as it would be written in a POSIX shell.
-
->>> displayCommandLine (CommandLine "pdfinfo" ["my file.pdf", "-box"])
-"pdfinfo 'my file.pdf' -box"
-
-@since 0.1.0.0
--}
-displayCommandLine :: CommandLine -> String
-displayCommandLine (CommandLine p as) =
-  unwords (fmap shellQuote (p : as))
-
-{- | Quote a word for a POSIX shell, if it needs quoting.
-
->>> shellQuote "public/report.pdf"
-"public/report.pdf"
-
->>> shellQuote "it's"
-"'it'\\''s'"
-
->>> shellQuote ""
-"''"
-
-@since 0.1.0.0
--}
-shellQuote :: String -> String
-shellQuote s =
-  let safe c = c `elem` ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./=:,+%@" :: String)
-      quote c = if c == '\'' then "'\\''" else [c]
-   in if not (null s) && all safe s
-        then s
-        else "'" <> concatMap quote s <> "'"
-
--- | @since 0.1.0.0
-class GetCommandLine s where
-  getCommandLine :: Getter s CommandLine
-
--- | @since 0.1.0.0
-instance GetCommandLine CommandLine where
-  getCommandLine =
-    id
-  {-# INLINE getCommandLine #-}
-
--- | @since 0.1.0.0
-class (GetCommandLine s) => HasCommandLine s where
-  {-# MINIMAL setCommandLine #-}
-  setCommandLine :: CommandLine -> s -> s
-
-  commandLine :: Lens' s CommandLine
-  commandLine =
-    lens (view getCommandLine) (flip setCommandLine)
-  {-# INLINE commandLine #-}
-
--- | @since 0.1.0.0
-instance HasCommandLine CommandLine where
-  setCommandLine =
-    const
-  {-# INLINE setCommandLine #-}
-
--- | @since 0.1.0.0
-class ReviewCommandLine t where
-  reviewCommandLine :: Review t CommandLine
-
--- | @since 0.1.0.0
-instance ReviewCommandLine CommandLine where
-  reviewCommandLine =
-    unto id
-  {-# INLINE reviewCommandLine #-}
-
--- | @since 0.1.0.0
-class (ReviewCommandLine t) => AsCommandLine t where
-  {-# MINIMAL matchCommandLine #-}
-  matchCommandLine :: t -> Maybe CommandLine
-
-  _CommandLine :: Prism' t CommandLine
-  _CommandLine =
-    prism' (review reviewCommandLine) matchCommandLine
-  {-# INLINE _CommandLine #-}
-
--- | @since 0.1.0.0
-instance AsCommandLine CommandLine where
-  matchCommandLine =
-    Just
-  {-# INLINE matchCommandLine #-}
-
-{- | An error that occurs while building documents.
-
-@since 0.1.0.0
--}
-data BuildError
-  = -- | A process exited with a non-zero exit code, with its standard output
-    -- and standard error.
-    ProcessFailed CommandLine String String
-  | -- | The output of a process was not recognised, with a description of what
-    -- was expected.
-    ProcessOutputUnrecognised CommandLine String
-  | -- | An input file does not exist.
-    InputFileMissing FilePath
-  | -- | A process succeeded, but did not produce an output file it should
-    -- have.
-    OutputFileMissing CommandLine FilePath
-  | -- | A file does not contain strings it is required to contain.
-    StringsMissing FilePath (NonEmpty String)
-  | -- | A file does not have one of the supported extensions.
-    FileTypeUnsupported FilePath [String]
-  | -- | A file has no history in git.
-    FileNotInGit FilePath
-  | -- | A file is not valid UTF-8 text.
-    FileNotUtf8 FilePath
-  | -- | A chain of build steps was stopped, because it would go round in a
-    -- cycle.
-    BuildCycle Cycle
-  | -- | A directory has fewer input files than a command needs: the
-    -- directory, the number of files it has, and the number the command
-    -- needs.
-    InputFilesTooFew FilePath Int Int
-  | -- | A build step made a file outside the output directory: the file, and
-    -- the output directory.
-    OutputNotInDirectory FilePath FilePath
-  | -- | The git history of a directory is shallow, so the last commit to
-    -- change a file in it is not known.
-    ShallowRepository FilePath
-  deriving stock (Eq, Ord, Show, Generic, Data)
-  deriving anyclass (NFData)
-
--- | @since 0.1.0.0
-instance Injection1 BuildError BuildError (CommandLine, String, String) (CommandLine, String, String)
-
--- | @since 0.1.0.0
-instance Injection2 BuildError BuildError (CommandLine, String) (CommandLine, String)
-
--- | @since 0.1.0.0
-instance Injection3 BuildError BuildError FilePath FilePath
-
--- | @since 0.1.0.0
-instance Injection4 BuildError BuildError (CommandLine, FilePath) (CommandLine, FilePath)
-
--- | @since 0.1.0.0
-instance Injection5 BuildError BuildError (FilePath, NonEmpty String) (FilePath, NonEmpty String)
-
--- | @since 0.1.0.0
-instance Injection6 BuildError BuildError (FilePath, [String]) (FilePath, [String])
-
--- | @since 0.1.0.0
-instance Injection7 BuildError BuildError FilePath FilePath
-
--- | @since 0.1.0.0
-instance Injection8 BuildError BuildError FilePath FilePath
-
--- | @since 0.1.0.0
-instance Injection9 BuildError BuildError Cycle Cycle
-
--- | @since 0.1.0.0
-instance Injection10 BuildError BuildError (FilePath, Int, Int) (FilePath, Int, Int)
-
--- | @since 0.1.0.0
-instance Injection11 BuildError BuildError (FilePath, FilePath) (FilePath, FilePath)
-
--- | @since 0.1.0.0
-instance Injection12 BuildError BuildError FilePath FilePath
-
--- | @since 0.1.0.0
-_ProcessFailed :: Prism' BuildError (CommandLine, String, String)
-_ProcessFailed =
-  _I1
-{-# INLINE _ProcessFailed #-}
-
--- | @since 0.1.0.0
-_ProcessOutputUnrecognised :: Prism' BuildError (CommandLine, String)
-_ProcessOutputUnrecognised =
-  _I2
-{-# INLINE _ProcessOutputUnrecognised #-}
-
-{- | >>> InputFileMissing "report.fodt" ^? _InputFileMissing
-Just "report.fodt"
-
-@since 0.1.0.0
--}
-_InputFileMissing :: Prism' BuildError FilePath
-_InputFileMissing =
-  _I3
-{-# INLINE _InputFileMissing #-}
-
--- | @since 0.1.0.0
-_OutputFileMissing :: Prism' BuildError (CommandLine, FilePath)
-_OutputFileMissing =
-  _I4
-{-# INLINE _OutputFileMissing #-}
-
--- | @since 0.1.0.0
-_StringsMissing :: Prism' BuildError (FilePath, NonEmpty String)
-_StringsMissing =
-  _I5
-{-# INLINE _StringsMissing #-}
-
--- | @since 0.1.0.0
-_FileTypeUnsupported :: Prism' BuildError (FilePath, [String])
-_FileTypeUnsupported =
-  _I6
-{-# INLINE _FileTypeUnsupported #-}
-
--- | @since 0.1.0.0
-_FileNotInGit :: Prism' BuildError FilePath
-_FileNotInGit =
-  _I7
-{-# INLINE _FileNotInGit #-}
-
-{- | >>> _FileNotUtf8 # "report.odt"
-FileNotUtf8 "report.odt"
-
-@since 0.1.0.0
--}
-_FileNotUtf8 :: Prism' BuildError FilePath
-_FileNotUtf8 =
-  _I8
-{-# INLINE _FileNotUtf8 #-}
-
--- | @since 0.1.0.0
-_BuildCycle :: Prism' BuildError Cycle
-_BuildCycle =
-  _I9
-{-# INLINE _BuildCycle #-}
-
--- | @since 0.1.0.0
-_InputFilesTooFew :: Prism' BuildError (FilePath, Int, Int)
-_InputFilesTooFew =
-  _I10
-{-# INLINE _InputFilesTooFew #-}
-
--- | @since 0.1.0.0
-_OutputNotInDirectory :: Prism' BuildError (FilePath, FilePath)
-_OutputNotInDirectory =
-  _I11
-{-# INLINE _OutputNotInDirectory #-}
-
--- | @since 0.1.0.0
-_ShallowRepository :: Prism' BuildError FilePath
-_ShallowRepository =
-  _I12
-{-# INLINE _ShallowRepository #-}
-
-{- | The exit code for an error that is not the failure of a process. A
-failed process keeps its own exit code, and this function gives @1@ for it.
-
->>> buildErrorExitCode (InputFileMissing "report.fodt")
-NotZero True (Positive 22)
-
-@since 0.1.0.0
--}
-buildErrorExitCode :: BuildError -> NotZero
-buildErrorExitCode e =
-  notZeroOr $
-    case e of
-      ProcessFailed{} -> 1 :: Int
-      FileNotInGit{} -> 12
-      ProcessOutputUnrecognised{} -> 14
-      InputFileMissing{} -> 22
-      StringsMissing{} -> 26
-      OutputFileMissing{} -> 28
-      FileTypeUnsupported{} -> 30
-      FileNotUtf8{} -> 32
-      BuildCycle{} -> 34
-      InputFilesTooFew{} -> 36
-      OutputNotInDirectory{} -> 38
-      ShallowRepository{} -> 40
-
-{- | A description of an error, for people to read.
-
->>> putStr (displayBuildError (StringsMissing "report.fodt" ("${UPDATED}" :| [])))
-report.fodt does not contain:
-  ${UPDATED}
-
-@since 0.1.0.0
--}
-displayBuildError :: BuildError -> String
-displayBuildError e =
-  case e of
-    ProcessFailed c o r ->
-      unlines (("command failed: " <> displayCommandLine c) : section "stdout" o <> section "stderr" r)
-    ProcessOutputUnrecognised c x ->
-      unlines ["output not recognised: " <> displayCommandLine c, "  expected " <> x]
-    InputFileMissing p ->
-      unlines ["input file does not exist: " <> p]
-    OutputFileMissing c p ->
-      unlines ["command did not produce " <> p <> ": " <> displayCommandLine c]
-    StringsMissing p xs ->
-      unlines ((p <> " does not contain:") : fmap ("  " <>) (toList xs))
-    FileTypeUnsupported p xs ->
-      unlines [p <> " is not one of the supported file types: " <> unwords xs]
-    FileNotInGit p ->
-      unlines ["file has no git history: " <> p]
-    FileNotUtf8 p ->
-      unlines ["file is not UTF-8 text: " <> p]
-    BuildCycle c ->
-      unlines ["build step stopped, because it would go round in a cycle:", "  " <> displayCycle c]
-    InputFilesTooFew d n m ->
-      unlines ["directory has " <> show n <> " input files, and needs at least " <> show m <> ": " <> d]
-    OutputNotInDirectory f d ->
-      unlines ["build step made a file outside the output directory " <> d <> ": " <> f]
-    ShallowRepository d ->
-      unlines ["git history is shallow, so the last commit to change each file is not known: " <> d, "  fetch the whole history, such as with GIT_DEPTH: 0 in GitLab CI"]
-
-section :: String -> String -> [String]
-section name x =
-  if null x
-    then []
-    else ("  " <> name <> ":") : fmap ("    " <>) (lines x)
-
--- | @since 0.1.0.0
-class GetBuildError s where
-  getBuildError :: Getter s BuildError
-
--- | @since 0.1.0.0
-instance GetBuildError BuildError where
-  getBuildError =
-    id
-  {-# INLINE getBuildError #-}
-
--- | @since 0.1.0.0
-class (GetBuildError s) => HasBuildError s where
-  {-# MINIMAL setBuildError #-}
-  setBuildError :: BuildError -> s -> s
-
-  buildError :: Lens' s BuildError
-  buildError =
-    lens (view getBuildError) (flip setBuildError)
-  {-# INLINE buildError #-}
-
--- | @since 0.1.0.0
-instance HasBuildError BuildError where
-  setBuildError =
-    const
-  {-# INLINE setBuildError #-}
-
-{- | Error types that can be constructed from a 'BuildError'. The commands in
-this library fail with any such error type, so that a build script can define
-its own error type that includes 'BuildError'.
-
-@since 0.1.0.0
--}
-class ReviewBuildError t where
-  reviewBuildError :: Review t BuildError
-
--- | @since 0.1.0.0
-instance ReviewBuildError BuildError where
-  reviewBuildError =
-    unto id
-  {-# INLINE reviewBuildError #-}
-
--- | @since 0.1.0.0
-class (ReviewBuildError t) => AsBuildError t where
-  {-# MINIMAL matchBuildError #-}
-  matchBuildError :: t -> Maybe BuildError
-
-  _BuildError :: Prism' t BuildError
-  _BuildError =
-    prism' (review reviewBuildError) matchBuildError
-  {-# INLINE _BuildError #-}
-
--- | @since 0.1.0.0
-instance AsBuildError BuildError where
-  matchBuildError =
-    Just
-  {-# INLINE matchBuildError #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | Errors that occur while building documents.
+
+A 'BuildError' is a sum: each constructor has a name, a prism. A constructor
+with two or more fields holds a product type of its own, such as
+'ProcessFailed', whose own name is the constructor's name. 'BuildError'
+inherits the names of every type it holds: those it reaches through two or
+more constructors, such as 'commandLine' and 'file', are affine traversals.
+
+Code that fails with a 'BuildError', in an error type @e@ that it builds with
+@'review' 'buildError'@, has the constraint @'ReviewBuildError' e@.
+-}
+module Document.Build.Error (
+  -- * Type
+  BuildError (..),
+  ReviewBuildError,
+
+  -- * Errors
+  buildErrorExitCode,
+  displayBuildError,
+
+  -- * Optics
+  BuildErrorLike (..),
+  lawNamesBuildError,
+  InheritBuildError,
+  inheritBuildError,
+  inheritsBuildError,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Choice, Optic, prism', review)
+import Data.Data (Data)
+import Data.Foldable (toList)
+import Data.Functor.Const (Const)
+import Data.Functor.Identity (Identity)
+import Data.Proxy (Proxy (..))
+import Data.Tagged (Tagged)
+import Document.Build.CommandLine (CommandLineLike (..), displayCommandLine)
+import Document.Build.Error.FileTypeUnsupported (FileTypeUnsupported (..), FileTypeUnsupportedLike (..), inheritFileTypeUnsupported)
+import Document.Build.Error.InputFilesTooFew (InputFilesTooFew (..), InputFilesTooFewLike (..), inheritInputFilesTooFew)
+import Document.Build.Error.OutputFileMissing (OutputFileMissing (..), OutputFileMissingLike (..), inheritOutputFileMissing)
+import Document.Build.Error.OutputNotInDirectory (OutputNotInDirectory (..), OutputNotInDirectoryLike (..), inheritOutputNotInDirectory)
+import Document.Build.Error.ProcessFailed (ProcessFailed (..), ProcessFailedLike (..), inheritProcessFailed)
+import Document.Build.Error.ProcessOutputUnrecognised (ProcessOutputUnrecognised (..), ProcessOutputUnrecognisedLike (..), inheritProcessOutputUnrecognised)
+import Document.Build.Error.StringsMissing (StringsMissing (..), StringsMissingLike (..), inheritStringsMissing)
+import Document.Build.Name.CommandLineArguments (CommandLineArgumentsLike (..))
+import Document.Build.Name.CommandLineProgram (CommandLineProgramLike (..))
+import Document.Build.Name.Directory (DirectoryLike (..))
+import Document.Build.Name.ExpectedOutput (ExpectedOutputLike (..))
+import Document.Build.Name.FileNotInGit (FileNotInGitLike (..))
+import Document.Build.Name.FileNotUtf8 (FileNotUtf8Like (..))
+import Document.Build.Name.InputFileCount (InputFileCountLike (..))
+import Document.Build.Name.InputFileMissing (InputFileMissingLike (..))
+import Document.Build.Name.InputFilesNeeded (InputFilesNeededLike (..))
+import Document.Build.Name.MissingStrings (MissingStringsLike (..))
+import Document.Build.Name.OutputDirectory (OutputDirectoryLike (..))
+import Document.Build.Name.ShallowRepository (ShallowRepositoryLike (..))
+import Document.Build.Name.StandardError (StandardErrorLike (..))
+import Document.Build.Name.StandardOutput (StandardOutputLike (..))
+import Document.Build.Name.SupportedExtensions (SupportedExtensionsLike (..))
+import GHC.Generics (Generic)
+import Natural (NotZero, notZeroOr)
+import System.FilePather.Cycle (Cycle (..), CycleLike (..), displayCycle, inheritCycle)
+import System.FilePather.Name.File (FileLike (..))
+import System.FilePather.Name.StepInput (StepInputLike (..))
+import System.FilePather.Name.StepName (StepNameLike (..))
+import System.FilePather.Name.Steps (StepsLike (..))
+import System.FilePather.RepeatsStep (RepeatsStepLike (..))
+import System.FilePather.ReproducesInput (ReproducesInputLike (..))
+import System.FilePather.Step (StepLike (..))
+import System.FilePather.Trail (TrailLike (..))
+import Prelude hiding (cycle)
+
+{- $setup
+>>> import Control.Lens (preview, review)
+>>> import Data.List.NonEmpty (NonEmpty (..))
+-}
+
+{- | An error that occurs while building documents.
+
+>>> preview inputFileMissing (InputFileMissing "report.fodt")
+Just "report.fodt"
+
+>>> review fileNotUtf8 "report.odt" :: BuildError
+FileNotUtf8 "report.odt"
+
+>>> preview file (BuildErrorStringsMissing (StringsMissing "report.fodt" ("${UPDATED}" :| [])))
+Just "report.fodt"
+
+@since 0.1.0.0
+-}
+data BuildError
+  = -- | A process exited with a non-zero exit code, with its standard output
+    -- and standard error.
+    -- Its name is 'processFailed'.
+    BuildErrorProcessFailed ProcessFailed
+  | -- | The output of a process was not recognised, with a description of what
+    -- was expected.
+    -- Its name is 'processOutputUnrecognised'.
+    BuildErrorProcessOutputUnrecognised ProcessOutputUnrecognised
+  | -- | An input file does not exist.
+    -- Its name is 'inputFileMissing'.
+    InputFileMissing FilePath
+  | -- | A process succeeded, but did not produce an output file it should
+    -- have.
+    -- Its name is 'outputFileMissing'.
+    BuildErrorOutputFileMissing OutputFileMissing
+  | -- | A file does not contain strings it is required to contain.
+    -- Its name is 'stringsMissing'.
+    BuildErrorStringsMissing StringsMissing
+  | -- | A file does not have one of the supported extensions.
+    -- Its name is 'fileTypeUnsupported'.
+    BuildErrorFileTypeUnsupported FileTypeUnsupported
+  | -- | A file has no history in git.
+    -- Its name is 'fileNotInGit'.
+    FileNotInGit FilePath
+  | -- | A file is not valid UTF-8 text. ('fileNotUtf8')
+    FileNotUtf8 FilePath
+  | -- | A chain of build steps was stopped, because it would go round in a
+    -- cycle.
+    -- Its name is 'cycle'.
+    BuildCycle Cycle
+  | -- | A directory has fewer input files than a command needs.
+    -- Its name is 'inputFilesTooFew'.
+    BuildErrorInputFilesTooFew InputFilesTooFew
+  | -- | A build step made a file outside the output directory.
+    -- Its name is 'outputNotInDirectory'.
+    BuildErrorOutputNotInDirectory OutputNotInDirectory
+  | -- | The git history of a directory is shallow, so the last commit to
+    -- change a file in it is not known.
+    -- Its name is 'shallowRepository'.
+    ShallowRepository FilePath
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The constraint of code that fails with a 'BuildError' in the error type
+@e@: a 'BuildError' is made into an @e@ with @'review' 'buildError'@.
+
+@since 0.2.0.0
+-}
+type ReviewBuildError e =
+  BuildErrorLike Tagged Identity e e
+
+{- | The class of 'BuildError'.
+
+@since 0.2.0.0
+-}
+class BuildErrorLike p f x x' where
+  buildError ::
+    Optic p f x x' BuildError BuildError
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ BuildError) => BuildErrorLike p f BuildError x' where
+  buildError =
+    id
+  {-# INLINE buildError #-}
+
+{- | Prism: 'processFailed' is the default name of 'BuildErrorProcessFailed'.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => ProcessFailedLike p f BuildError x' where
+  processFailed =
+    prism'
+      BuildErrorProcessFailed
+      ( \case
+          BuildErrorProcessFailed x -> Just x
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE processFailed #-}
+
+{- | Prism: 'processOutputUnrecognised' is the default name of 'BuildErrorProcessOutputUnrecognised'.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => ProcessOutputUnrecognisedLike p f BuildError x' where
+  processOutputUnrecognised =
+    prism'
+      BuildErrorProcessOutputUnrecognised
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised x -> Just x
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE processOutputUnrecognised #-}
+
+{- | Prism
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => InputFileMissingLike p f BuildError x' where
+  inputFileMissing =
+    prism'
+      InputFileMissing
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing x -> Just x
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE inputFileMissing #-}
+
+{- | Prism: 'outputFileMissing' is the default name of 'BuildErrorOutputFileMissing'.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => OutputFileMissingLike p f BuildError x' where
+  outputFileMissing =
+    prism'
+      BuildErrorOutputFileMissing
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing x -> Just x
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE outputFileMissing #-}
+
+{- | Prism: 'stringsMissing' is the default name of 'BuildErrorStringsMissing'.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => StringsMissingLike p f BuildError x' where
+  stringsMissing =
+    prism'
+      BuildErrorStringsMissing
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing x -> Just x
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE stringsMissing #-}
+
+{- | Prism: 'fileTypeUnsupported' is the default name of 'BuildErrorFileTypeUnsupported'.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => FileTypeUnsupportedLike p f BuildError x' where
+  fileTypeUnsupported =
+    prism'
+      BuildErrorFileTypeUnsupported
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported x -> Just x
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE fileTypeUnsupported #-}
+
+{- | Prism
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => FileNotInGitLike p f BuildError x' where
+  fileNotInGit =
+    prism'
+      FileNotInGit
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit x -> Just x
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE fileNotInGit #-}
+
+{- | Prism
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => FileNotUtf8Like p f BuildError x' where
+  fileNotUtf8 =
+    prism'
+      FileNotUtf8
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 x -> Just x
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE fileNotUtf8 #-}
+
+{- | Prism: 'cycle' is the default name of 'BuildCycle'.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => CycleLike p f BuildError x' where
+  cycle =
+    prism'
+      BuildCycle
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle x -> Just x
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE cycle #-}
+
+{- | Prism: 'inputFilesTooFew' is the default name of 'BuildErrorInputFilesTooFew'.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => InputFilesTooFewLike p f BuildError x' where
+  inputFilesTooFew =
+    prism'
+      BuildErrorInputFilesTooFew
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew x -> Just x
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE inputFilesTooFew #-}
+
+{- | Prism: 'outputNotInDirectory' is the default name of 'BuildErrorOutputNotInDirectory'.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => OutputNotInDirectoryLike p f BuildError x' where
+  outputNotInDirectory =
+    prism'
+      BuildErrorOutputNotInDirectory
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory x -> Just x
+          ShallowRepository _ -> Nothing
+      )
+  {-# INLINE outputNotInDirectory #-}
+
+{- | Prism
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => ShallowRepositoryLike p f BuildError x' where
+  shallowRepository =
+    prism'
+      ShallowRepository
+      ( \case
+          BuildErrorProcessFailed _ -> Nothing
+          BuildErrorProcessOutputUnrecognised _ -> Nothing
+          InputFileMissing _ -> Nothing
+          BuildErrorOutputFileMissing _ -> Nothing
+          BuildErrorStringsMissing _ -> Nothing
+          BuildErrorFileTypeUnsupported _ -> Nothing
+          FileNotInGit _ -> Nothing
+          FileNotUtf8 _ -> Nothing
+          BuildCycle _ -> Nothing
+          BuildErrorInputFilesTooFew _ -> Nothing
+          BuildErrorOutputNotInDirectory _ -> Nothing
+          ShallowRepository x -> Just x
+      )
+  {-# INLINE shallowRepository #-}
+
+{- | Affine traversal: 'commandLine' is reached through 3 constructors, once each: 'BuildErrorProcessFailed', 'BuildErrorProcessOutputUnrecognised', 'BuildErrorOutputFileMissing'.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => CommandLineLike p f BuildError x' where
+  commandLine f = \case
+    BuildErrorProcessFailed x -> BuildErrorProcessFailed <$> commandLine f x
+    BuildErrorProcessOutputUnrecognised x -> BuildErrorProcessOutputUnrecognised <$> commandLine f x
+    InputFileMissing x -> pure (InputFileMissing x)
+    BuildErrorOutputFileMissing x -> BuildErrorOutputFileMissing <$> commandLine f x
+    BuildErrorStringsMissing x -> pure (BuildErrorStringsMissing x)
+    BuildErrorFileTypeUnsupported x -> pure (BuildErrorFileTypeUnsupported x)
+    FileNotInGit x -> pure (FileNotInGit x)
+    FileNotUtf8 x -> pure (FileNotUtf8 x)
+    BuildCycle x -> pure (BuildCycle x)
+    BuildErrorInputFilesTooFew x -> pure (BuildErrorInputFilesTooFew x)
+    BuildErrorOutputNotInDirectory x -> pure (BuildErrorOutputNotInDirectory x)
+    ShallowRepository x -> pure (ShallowRepository x)
+  {-# INLINE commandLine #-}
+
+{- | Affine traversal: through 'BuildErrorProcessFailed', a prism, then 'ProcessFailed''s 'standardOutput', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => StandardOutputLike p f BuildError x' where
+  standardOutput =
+    processFailed . standardOutput
+  {-# INLINE standardOutput #-}
+
+{- | Affine traversal: through 'BuildErrorProcessFailed', a prism, then 'ProcessFailed''s 'standardError', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => StandardErrorLike p f BuildError x' where
+  standardError =
+    processFailed . standardError
+  {-# INLINE standardError #-}
+
+{- | Affine traversal: 'commandLineProgram' is reached through 3 constructors, once each: 'BuildErrorProcessFailed', 'BuildErrorProcessOutputUnrecognised', 'BuildErrorOutputFileMissing'.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => CommandLineProgramLike p f BuildError x' where
+  commandLineProgram f = \case
+    BuildErrorProcessFailed x -> BuildErrorProcessFailed <$> commandLineProgram f x
+    BuildErrorProcessOutputUnrecognised x -> BuildErrorProcessOutputUnrecognised <$> commandLineProgram f x
+    InputFileMissing x -> pure (InputFileMissing x)
+    BuildErrorOutputFileMissing x -> BuildErrorOutputFileMissing <$> commandLineProgram f x
+    BuildErrorStringsMissing x -> pure (BuildErrorStringsMissing x)
+    BuildErrorFileTypeUnsupported x -> pure (BuildErrorFileTypeUnsupported x)
+    FileNotInGit x -> pure (FileNotInGit x)
+    FileNotUtf8 x -> pure (FileNotUtf8 x)
+    BuildCycle x -> pure (BuildCycle x)
+    BuildErrorInputFilesTooFew x -> pure (BuildErrorInputFilesTooFew x)
+    BuildErrorOutputNotInDirectory x -> pure (BuildErrorOutputNotInDirectory x)
+    ShallowRepository x -> pure (ShallowRepository x)
+  {-# INLINE commandLineProgram #-}
+
+{- | Affine traversal: 'commandLineArguments' is reached through 3 constructors, once each: 'BuildErrorProcessFailed', 'BuildErrorProcessOutputUnrecognised', 'BuildErrorOutputFileMissing'.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => CommandLineArgumentsLike p f BuildError x' where
+  commandLineArguments f = \case
+    BuildErrorProcessFailed x -> BuildErrorProcessFailed <$> commandLineArguments f x
+    BuildErrorProcessOutputUnrecognised x -> BuildErrorProcessOutputUnrecognised <$> commandLineArguments f x
+    InputFileMissing x -> pure (InputFileMissing x)
+    BuildErrorOutputFileMissing x -> BuildErrorOutputFileMissing <$> commandLineArguments f x
+    BuildErrorStringsMissing x -> pure (BuildErrorStringsMissing x)
+    BuildErrorFileTypeUnsupported x -> pure (BuildErrorFileTypeUnsupported x)
+    FileNotInGit x -> pure (FileNotInGit x)
+    FileNotUtf8 x -> pure (FileNotUtf8 x)
+    BuildCycle x -> pure (BuildCycle x)
+    BuildErrorInputFilesTooFew x -> pure (BuildErrorInputFilesTooFew x)
+    BuildErrorOutputNotInDirectory x -> pure (BuildErrorOutputNotInDirectory x)
+    ShallowRepository x -> pure (ShallowRepository x)
+  {-# INLINE commandLineArguments #-}
+
+{- | Affine traversal: through 'BuildErrorProcessOutputUnrecognised', a prism, then 'ProcessOutputUnrecognised''s 'expectedOutput', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => ExpectedOutputLike p f BuildError x' where
+  expectedOutput =
+    processOutputUnrecognised . expectedOutput
+  {-# INLINE expectedOutput #-}
+
+{- | Affine traversal: 'file' is reached through 5 constructors, once each: 'BuildErrorOutputFileMissing', 'BuildErrorStringsMissing', 'BuildErrorFileTypeUnsupported', 'BuildCycle', 'BuildErrorOutputNotInDirectory'.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => FileLike p f BuildError x' where
+  file f = \case
+    BuildErrorProcessFailed x -> pure (BuildErrorProcessFailed x)
+    BuildErrorProcessOutputUnrecognised x -> pure (BuildErrorProcessOutputUnrecognised x)
+    InputFileMissing x -> pure (InputFileMissing x)
+    BuildErrorOutputFileMissing x -> BuildErrorOutputFileMissing <$> file f x
+    BuildErrorStringsMissing x -> BuildErrorStringsMissing <$> file f x
+    BuildErrorFileTypeUnsupported x -> BuildErrorFileTypeUnsupported <$> file f x
+    FileNotInGit x -> pure (FileNotInGit x)
+    FileNotUtf8 x -> pure (FileNotUtf8 x)
+    BuildCycle x -> BuildCycle <$> file f x
+    BuildErrorInputFilesTooFew x -> pure (BuildErrorInputFilesTooFew x)
+    BuildErrorOutputNotInDirectory x -> BuildErrorOutputNotInDirectory <$> file f x
+    ShallowRepository x -> pure (ShallowRepository x)
+  {-# INLINE file #-}
+
+{- | Affine traversal: through 'BuildErrorStringsMissing', a prism, then 'StringsMissing''s 'missingStrings', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => MissingStringsLike p f BuildError x' where
+  missingStrings =
+    stringsMissing . missingStrings
+  {-# INLINE missingStrings #-}
+
+{- | Affine traversal: through 'BuildErrorFileTypeUnsupported', a prism, then 'FileTypeUnsupported''s 'supportedExtensions', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => SupportedExtensionsLike p f BuildError x' where
+  supportedExtensions =
+    fileTypeUnsupported . supportedExtensions
+  {-# INLINE supportedExtensions #-}
+
+{- | Prism: through 'BuildCycle', a prism, then 'Cycle''s 'reproducesInput', a prism.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => ReproducesInputLike p f BuildError x' where
+  reproducesInput =
+    cycle . reproducesInput
+  {-# INLINE reproducesInput #-}
+
+{- | Prism: through 'BuildCycle', a prism, then 'Cycle''s 'repeatsStep', a prism.
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ BuildError) => RepeatsStepLike p f BuildError x' where
+  repeatsStep =
+    cycle . repeatsStep
+  {-# INLINE repeatsStep #-}
+
+{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'step', an affine traversal.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => StepLike p f BuildError x' where
+  step =
+    cycle . step
+  {-# INLINE step #-}
+
+{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'trail', an affine traversal.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => TrailLike p f BuildError x' where
+  trail =
+    cycle . trail
+  {-# INLINE trail #-}
+
+{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'stepName', an affine traversal.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => StepNameLike p f BuildError x' where
+  stepName =
+    cycle . stepName
+  {-# INLINE stepName #-}
+
+{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'stepInput', an affine traversal.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => StepInputLike p f BuildError x' where
+  stepInput =
+    cycle . stepInput
+  {-# INLINE stepInput #-}
+
+{- | Affine traversal: through 'BuildCycle', a prism, then 'Cycle''s 'steps', an affine traversal.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => StepsLike p f BuildError x' where
+  steps =
+    cycle . steps
+  {-# INLINE steps #-}
+
+{- | Affine traversal: through 'BuildErrorInputFilesTooFew', a prism, then 'InputFilesTooFew''s 'directory', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => DirectoryLike p f BuildError x' where
+  directory =
+    inputFilesTooFew . directory
+  {-# INLINE directory #-}
+
+{- | Affine traversal: through 'BuildErrorInputFilesTooFew', a prism, then 'InputFilesTooFew''s 'inputFileCount', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => InputFileCountLike p f BuildError x' where
+  inputFileCount =
+    inputFilesTooFew . inputFileCount
+  {-# INLINE inputFileCount #-}
+
+{- | Affine traversal: through 'BuildErrorInputFilesTooFew', a prism, then 'InputFilesTooFew''s 'inputFilesNeeded', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => InputFilesNeededLike p f BuildError x' where
+  inputFilesNeeded =
+    inputFilesTooFew . inputFilesNeeded
+  {-# INLINE inputFilesNeeded #-}
+
+{- | Affine traversal: through 'BuildErrorOutputNotInDirectory', a prism, then 'OutputNotInDirectory''s 'outputDirectory', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Applicative f, x' ~ BuildError) => OutputDirectoryLike p f BuildError x' where
+  outputDirectory =
+    outputNotInDirectory . outputDirectory
+  {-# INLINE outputDirectory #-}
+
+{- | Rebuild a value using only the name of each constructor.
+
+Laws:
+
+> lawNamesBuildError == id
+
+@since 0.2.0.0
+-}
+lawNamesBuildError :: BuildError -> BuildError
+lawNamesBuildError = \case
+  BuildErrorProcessFailed x -> review processFailed x
+  BuildErrorProcessOutputUnrecognised x -> review processOutputUnrecognised x
+  InputFileMissing x -> review inputFileMissing x
+  BuildErrorOutputFileMissing x -> review outputFileMissing x
+  BuildErrorStringsMissing x -> review stringsMissing x
+  BuildErrorFileTypeUnsupported x -> review fileTypeUnsupported x
+  FileNotInGit x -> review fileNotInGit x
+  FileNotUtf8 x -> review fileNotUtf8 x
+  BuildCycle x -> review cycle x
+  BuildErrorInputFilesTooFew x -> review inputFilesTooFew x
+  BuildErrorOutputNotInDirectory x -> review outputNotInDirectory x
+  ShallowRepository x -> review shallowRepository x
+
+{- | The classes that a data type holding 'BuildError' inherits.
+
+@since 0.2.0.0
+-}
+type InheritBuildError s =
+  ( BuildErrorLike (->) (Const ()) s s
+  , ProcessFailedLike (->) (Const ()) s s
+  , ProcessOutputUnrecognisedLike (->) (Const ()) s s
+  , InputFileMissingLike (->) (Const ()) s s
+  , OutputFileMissingLike (->) (Const ()) s s
+  , StringsMissingLike (->) (Const ()) s s
+  , FileTypeUnsupportedLike (->) (Const ()) s s
+  , FileNotInGitLike (->) (Const ()) s s
+  , FileNotUtf8Like (->) (Const ()) s s
+  , CycleLike (->) (Const ()) s s
+  , InputFilesTooFewLike (->) (Const ()) s s
+  , OutputNotInDirectoryLike (->) (Const ()) s s
+  , ShallowRepositoryLike (->) (Const ()) s s
+  , CommandLineLike (->) (Const ()) s s
+  , StandardOutputLike (->) (Const ()) s s
+  , StandardErrorLike (->) (Const ()) s s
+  , CommandLineProgramLike (->) (Const ()) s s
+  , CommandLineArgumentsLike (->) (Const ()) s s
+  , ExpectedOutputLike (->) (Const ()) s s
+  , FileLike (->) (Const ()) s s
+  , MissingStringsLike (->) (Const ()) s s
+  , SupportedExtensionsLike (->) (Const ()) s s
+  , ReproducesInputLike (->) (Const ()) s s
+  , RepeatsStepLike (->) (Const ()) s s
+  , StepLike (->) (Const ()) s s
+  , TrailLike (->) (Const ()) s s
+  , StepNameLike (->) (Const ()) s s
+  , StepInputLike (->) (Const ()) s s
+  , StepsLike (->) (Const ()) s s
+  , DirectoryLike (->) (Const ()) s s
+  , InputFileCountLike (->) (Const ()) s s
+  , InputFilesNeededLike (->) (Const ()) s s
+  , OutputDirectoryLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'BuildError'.
+
+@since 0.2.0.0
+-}
+inheritBuildError :: (InheritBuildError s) => proxy s -> ()
+inheritBuildError _ =
+  ()
+
+{- | 'BuildError' inherits every name of each data type that it holds in a
+constructor.
+
+@since 0.2.0.0
+-}
+inheritsBuildError :: [()]
+inheritsBuildError =
+  let p = Proxy :: Proxy BuildError
+   in [ inheritProcessFailed p
+      , inheritProcessOutputUnrecognised p
+      , inheritOutputFileMissing p
+      , inheritStringsMissing p
+      , inheritFileTypeUnsupported p
+      , inheritCycle p
+      , inheritInputFilesTooFew p
+      , inheritOutputNotInDirectory p
+      ]
+
+{- | The exit code for an error that is not the failure of a process. A
+failed process keeps its own exit code, and this function gives @1@ for it.
+
+>>> buildErrorExitCode (InputFileMissing "report.fodt")
+NotZero True (Positive 22)
+
+@since 0.1.0.0
+-}
+buildErrorExitCode :: BuildError -> NotZero
+buildErrorExitCode e =
+  notZeroOr $
+    case e of
+      BuildErrorProcessFailed _ -> 1 :: Int
+      FileNotInGit _ -> 12
+      BuildErrorProcessOutputUnrecognised _ -> 14
+      InputFileMissing _ -> 22
+      BuildErrorStringsMissing _ -> 26
+      BuildErrorOutputFileMissing _ -> 28
+      BuildErrorFileTypeUnsupported _ -> 30
+      FileNotUtf8 _ -> 32
+      BuildCycle _ -> 34
+      BuildErrorInputFilesTooFew _ -> 36
+      BuildErrorOutputNotInDirectory _ -> 38
+      ShallowRepository _ -> 40
+
+{- | A description of an error, for people to read.
+
+>>> putStr (displayBuildError (BuildErrorStringsMissing (StringsMissing "report.fodt" ("${UPDATED}" :| []))))
+report.fodt does not contain:
+  ${UPDATED}
+
+@since 0.1.0.0
+-}
+displayBuildError :: BuildError -> String
+displayBuildError e =
+  case e of
+    BuildErrorProcessFailed (ProcessFailed c o r) ->
+      unlines (("command failed: " <> displayCommandLine c) : section "stdout" o <> section "stderr" r)
+    BuildErrorProcessOutputUnrecognised (ProcessOutputUnrecognised c x) ->
+      unlines ["output not recognised: " <> displayCommandLine c, "  expected " <> x]
+    InputFileMissing p ->
+      unlines ["input file does not exist: " <> p]
+    BuildErrorOutputFileMissing (OutputFileMissing c p) ->
+      unlines ["command did not produce " <> p <> ": " <> displayCommandLine c]
+    BuildErrorStringsMissing (StringsMissing p xs) ->
+      unlines ((p <> " does not contain:") : fmap ("  " <>) (toList xs))
+    BuildErrorFileTypeUnsupported (FileTypeUnsupported p xs) ->
+      unlines [p <> " is not one of the supported file types: " <> unwords xs]
+    FileNotInGit p ->
+      unlines ["file has no git history: " <> p]
+    FileNotUtf8 p ->
+      unlines ["file is not UTF-8 text: " <> p]
+    BuildCycle c ->
+      unlines ["build step stopped, because it would go round in a cycle:", "  " <> displayCycle c]
+    BuildErrorInputFilesTooFew (InputFilesTooFew d n m) ->
+      unlines ["directory has " <> show n <> " input files, and needs at least " <> show m <> ": " <> d]
+    BuildErrorOutputNotInDirectory (OutputNotInDirectory f d) ->
+      unlines ["build step made a file outside the output directory " <> d <> ": " <> f]
+    ShallowRepository d ->
+      unlines ["git history is shallow, so the last commit to change each file is not known: " <> d, "  fetch the whole history, such as with GIT_DEPTH: 0 in GitLab CI"]
+
+section :: String -> String -> [String]
+section name x =
+  if null x
+    then []
+    else ("  " <> name <> ":") : fmap ("    " <>) (lines x)
diff --git a/src/Document/Build/Error/FileTypeUnsupported.hs b/src/Document/Build/Error/FileTypeUnsupported.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Error/FileTypeUnsupported.hs
@@ -0,0 +1,109 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | A file that does not have one of the supported extensions.
+
+A 'FileTypeUnsupported' is a product: its fields are named 'file' and 'supportedExtensions'.
+-}
+module Document.Build.Error.FileTypeUnsupported (
+  -- * Type
+  FileTypeUnsupported (..),
+
+  -- * Optics
+  FileTypeUnsupportedLike (..),
+  lawNamesFileTypeUnsupported,
+  InheritFileTypeUnsupported,
+  inheritFileTypeUnsupported,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Document.Build.Name.SupportedExtensions (SupportedExtensionsLike (..))
+import GHC.Generics (Generic)
+import System.FilePather.Name.File (FileLike (..))
+
+{- | A file ('file') does not have one of the supported extensions ('supportedExtensions').
+
+@since 0.2.0.0
+-}
+data FileTypeUnsupported
+  = FileTypeUnsupported FilePath [String]
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The class of 'FileTypeUnsupported'.
+
+@since 0.2.0.0
+-}
+class FileTypeUnsupportedLike p f x x' where
+  fileTypeUnsupported ::
+    Optic p f x x' FileTypeUnsupported FileTypeUnsupported
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ FileTypeUnsupported) => FileTypeUnsupportedLike p f FileTypeUnsupported x' where
+  fileTypeUnsupported =
+    id
+  {-# INLINE fileTypeUnsupported #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ FileTypeUnsupported) => FileLike p f FileTypeUnsupported x' where
+  file f (FileTypeUnsupported x1 x2) =
+    fmap (`FileTypeUnsupported` x2) (f x1)
+  {-# INLINE file #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ FileTypeUnsupported) => SupportedExtensionsLike p f FileTypeUnsupported x' where
+  supportedExtensions f (FileTypeUnsupported x1 x2) =
+    fmap (FileTypeUnsupported x1) (f x2)
+  {-# INLINE supportedExtensions #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesFileTypeUnsupported == id
+
+@since 0.2.0.0
+-}
+lawNamesFileTypeUnsupported :: FileTypeUnsupported -> FileTypeUnsupported
+lawNamesFileTypeUnsupported s =
+  FileTypeUnsupported
+    (view (cloneLens file) s)
+    (view (cloneLens supportedExtensions) s)
+
+{- | The classes that a data type holding 'FileTypeUnsupported' inherits.
+
+@since 0.2.0.0
+-}
+type InheritFileTypeUnsupported s =
+  ( FileTypeUnsupportedLike (->) (Const ()) s s
+  , FileLike (->) (Const ()) s s
+  , SupportedExtensionsLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'FileTypeUnsupported'.
+
+@since 0.2.0.0
+-}
+inheritFileTypeUnsupported :: (InheritFileTypeUnsupported s) => proxy s -> ()
+inheritFileTypeUnsupported _ =
+  ()
diff --git a/src/Document/Build/Error/InputFilesTooFew.hs b/src/Document/Build/Error/InputFilesTooFew.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Error/InputFilesTooFew.hs
@@ -0,0 +1,122 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | A directory that has fewer input files than a command needs.
+
+A 'InputFilesTooFew' is a product: its fields are named 'directory', 'inputFileCount' and 'inputFilesNeeded'.
+-}
+module Document.Build.Error.InputFilesTooFew (
+  -- * Type
+  InputFilesTooFew (..),
+
+  -- * Optics
+  InputFilesTooFewLike (..),
+  lawNamesInputFilesTooFew,
+  InheritInputFilesTooFew,
+  inheritInputFilesTooFew,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Document.Build.Name.Directory (DirectoryLike (..))
+import Document.Build.Name.InputFileCount (InputFileCountLike (..))
+import Document.Build.Name.InputFilesNeeded (InputFilesNeededLike (..))
+import GHC.Generics (Generic)
+
+{- | A directory ('directory') has fewer input files ('inputFileCount') than a command
+needs ('inputFilesNeeded').
+
+@since 0.2.0.0
+-}
+data InputFilesTooFew
+  = InputFilesTooFew FilePath Int Int
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The class of 'InputFilesTooFew'.
+
+@since 0.2.0.0
+-}
+class InputFilesTooFewLike p f x x' where
+  inputFilesTooFew ::
+    Optic p f x x' InputFilesTooFew InputFilesTooFew
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ InputFilesTooFew) => InputFilesTooFewLike p f InputFilesTooFew x' where
+  inputFilesTooFew =
+    id
+  {-# INLINE inputFilesTooFew #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ InputFilesTooFew) => DirectoryLike p f InputFilesTooFew x' where
+  directory f (InputFilesTooFew x1 x2 x3) =
+    fmap (\x1' -> InputFilesTooFew x1' x2 x3) (f x1)
+  {-# INLINE directory #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ InputFilesTooFew) => InputFileCountLike p f InputFilesTooFew x' where
+  inputFileCount f (InputFilesTooFew x1 x2 x3) =
+    fmap (\x2' -> InputFilesTooFew x1 x2' x3) (f x2)
+  {-# INLINE inputFileCount #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ InputFilesTooFew) => InputFilesNeededLike p f InputFilesTooFew x' where
+  inputFilesNeeded f (InputFilesTooFew x1 x2 x3) =
+    fmap (InputFilesTooFew x1 x2) (f x3)
+  {-# INLINE inputFilesNeeded #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesInputFilesTooFew == id
+
+@since 0.2.0.0
+-}
+lawNamesInputFilesTooFew :: InputFilesTooFew -> InputFilesTooFew
+lawNamesInputFilesTooFew s =
+  InputFilesTooFew
+    (view (cloneLens directory) s)
+    (view (cloneLens inputFileCount) s)
+    (view (cloneLens inputFilesNeeded) s)
+
+{- | The classes that a data type holding 'InputFilesTooFew' inherits.
+
+@since 0.2.0.0
+-}
+type InheritInputFilesTooFew s =
+  ( InputFilesTooFewLike (->) (Const ()) s s
+  , DirectoryLike (->) (Const ()) s s
+  , InputFileCountLike (->) (Const ()) s s
+  , InputFilesNeededLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'InputFilesTooFew'.
+
+@since 0.2.0.0
+-}
+inheritInputFilesTooFew :: (InheritInputFilesTooFew s) => proxy s -> ()
+inheritInputFilesTooFew _ =
+  ()
diff --git a/src/Document/Build/Error/OutputFileMissing.hs b/src/Document/Build/Error/OutputFileMissing.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Error/OutputFileMissing.hs
@@ -0,0 +1,144 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | A process that did not produce an output file it should have.
+
+A 'OutputFileMissing' is a product: its fields are named 'commandLine' and 'file'.
+It holds 'CommandLine', so it inherits its names.
+-}
+module Document.Build.Error.OutputFileMissing (
+  -- * Type
+  OutputFileMissing (..),
+
+  -- * Optics
+  OutputFileMissingLike (..),
+  lawNamesOutputFileMissing,
+  InheritOutputFileMissing,
+  inheritOutputFileMissing,
+  inheritsOutputFileMissing,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Data.Proxy (Proxy (..))
+import Document.Build.CommandLine (CommandLine, CommandLineLike (..), inheritCommandLine)
+import Document.Build.Name.CommandLineArguments (CommandLineArgumentsLike (..))
+import Document.Build.Name.CommandLineProgram (CommandLineProgramLike (..))
+import GHC.Generics (Generic)
+import System.FilePather.Name.File (FileLike (..))
+
+{- | A process succeeded, but did not produce an output file it should have: its command
+line ('commandLine'), and the file ('file').
+
+@since 0.2.0.0
+-}
+data OutputFileMissing
+  = OutputFileMissing CommandLine FilePath
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The class of 'OutputFileMissing'.
+
+@since 0.2.0.0
+-}
+class OutputFileMissingLike p f x x' where
+  outputFileMissing ::
+    Optic p f x x' OutputFileMissing OutputFileMissing
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ OutputFileMissing) => OutputFileMissingLike p f OutputFileMissing x' where
+  outputFileMissing =
+    id
+  {-# INLINE outputFileMissing #-}
+
+{- | Lens: 'commandLine' is the default name of the field of type 'CommandLine'.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputFileMissing) => CommandLineLike p f OutputFileMissing x' where
+  commandLine f (OutputFileMissing x1 x2) =
+    fmap (`OutputFileMissing` x2) (f x1)
+  {-# INLINE commandLine #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputFileMissing) => FileLike p f OutputFileMissing x' where
+  file f (OutputFileMissing x1 x2) =
+    fmap (OutputFileMissing x1) (f x2)
+  {-# INLINE file #-}
+
+{- | Lens: through 'commandLine', a lens, then 'CommandLine''s 'commandLineProgram', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputFileMissing) => CommandLineProgramLike p f OutputFileMissing x' where
+  commandLineProgram =
+    commandLine . commandLineProgram
+  {-# INLINE commandLineProgram #-}
+
+{- | Lens: through 'commandLine', a lens, then 'CommandLine''s 'commandLineArguments', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputFileMissing) => CommandLineArgumentsLike p f OutputFileMissing x' where
+  commandLineArguments =
+    commandLine . commandLineArguments
+  {-# INLINE commandLineArguments #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesOutputFileMissing == id
+
+@since 0.2.0.0
+-}
+lawNamesOutputFileMissing :: OutputFileMissing -> OutputFileMissing
+lawNamesOutputFileMissing s =
+  OutputFileMissing
+    (view (cloneLens commandLine) s)
+    (view (cloneLens file) s)
+
+{- | The classes that a data type holding 'OutputFileMissing' inherits.
+
+@since 0.2.0.0
+-}
+type InheritOutputFileMissing s =
+  ( OutputFileMissingLike (->) (Const ()) s s
+  , CommandLineLike (->) (Const ()) s s
+  , FileLike (->) (Const ()) s s
+  , CommandLineProgramLike (->) (Const ()) s s
+  , CommandLineArgumentsLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'OutputFileMissing'.
+
+@since 0.2.0.0
+-}
+inheritOutputFileMissing :: (InheritOutputFileMissing s) => proxy s -> ()
+inheritOutputFileMissing _ =
+  ()
+
+{- | 'OutputFileMissing' inherits every name of 'CommandLine'.
+
+@since 0.2.0.0
+-}
+inheritsOutputFileMissing :: [()]
+inheritsOutputFileMissing =
+  let p = Proxy :: Proxy OutputFileMissing
+   in [inheritCommandLine p]
diff --git a/src/Document/Build/Error/OutputNotInDirectory.hs b/src/Document/Build/Error/OutputNotInDirectory.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Error/OutputNotInDirectory.hs
@@ -0,0 +1,109 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | A file that a build step made outside the output directory.
+
+A 'OutputNotInDirectory' is a product: its fields are named 'file' and 'outputDirectory'.
+-}
+module Document.Build.Error.OutputNotInDirectory (
+  -- * Type
+  OutputNotInDirectory (..),
+
+  -- * Optics
+  OutputNotInDirectoryLike (..),
+  lawNamesOutputNotInDirectory,
+  InheritOutputNotInDirectory,
+  inheritOutputNotInDirectory,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Document.Build.Name.OutputDirectory (OutputDirectoryLike (..))
+import GHC.Generics (Generic)
+import System.FilePather.Name.File (FileLike (..))
+
+{- | A build step made a file ('file') outside the output directory ('outputDirectory').
+
+@since 0.2.0.0
+-}
+data OutputNotInDirectory
+  = OutputNotInDirectory FilePath FilePath
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The class of 'OutputNotInDirectory'.
+
+@since 0.2.0.0
+-}
+class OutputNotInDirectoryLike p f x x' where
+  outputNotInDirectory ::
+    Optic p f x x' OutputNotInDirectory OutputNotInDirectory
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ OutputNotInDirectory) => OutputNotInDirectoryLike p f OutputNotInDirectory x' where
+  outputNotInDirectory =
+    id
+  {-# INLINE outputNotInDirectory #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputNotInDirectory) => FileLike p f OutputNotInDirectory x' where
+  file f (OutputNotInDirectory x1 x2) =
+    fmap (`OutputNotInDirectory` x2) (f x1)
+  {-# INLINE file #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputNotInDirectory) => OutputDirectoryLike p f OutputNotInDirectory x' where
+  outputDirectory f (OutputNotInDirectory x1 x2) =
+    fmap (OutputNotInDirectory x1) (f x2)
+  {-# INLINE outputDirectory #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesOutputNotInDirectory == id
+
+@since 0.2.0.0
+-}
+lawNamesOutputNotInDirectory :: OutputNotInDirectory -> OutputNotInDirectory
+lawNamesOutputNotInDirectory s =
+  OutputNotInDirectory
+    (view (cloneLens file) s)
+    (view (cloneLens outputDirectory) s)
+
+{- | The classes that a data type holding 'OutputNotInDirectory' inherits.
+
+@since 0.2.0.0
+-}
+type InheritOutputNotInDirectory s =
+  ( OutputNotInDirectoryLike (->) (Const ()) s s
+  , FileLike (->) (Const ()) s s
+  , OutputDirectoryLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'OutputNotInDirectory'.
+
+@since 0.2.0.0
+-}
+inheritOutputNotInDirectory :: (InheritOutputNotInDirectory s) => proxy s -> ()
+inheritOutputNotInDirectory _ =
+  ()
diff --git a/src/Document/Build/Error/ProcessFailed.hs b/src/Document/Build/Error/ProcessFailed.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Error/ProcessFailed.hs
@@ -0,0 +1,156 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | A process that exited with a non-zero exit code.
+
+A 'ProcessFailed' is a product: its fields are named 'commandLine', 'standardOutput' and 'standardError'.
+It holds 'CommandLine', so it inherits its names.
+-}
+module Document.Build.Error.ProcessFailed (
+  -- * Type
+  ProcessFailed (..),
+
+  -- * Optics
+  ProcessFailedLike (..),
+  lawNamesProcessFailed,
+  InheritProcessFailed,
+  inheritProcessFailed,
+  inheritsProcessFailed,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Data.Proxy (Proxy (..))
+import Document.Build.CommandLine (CommandLine, CommandLineLike (..), inheritCommandLine)
+import Document.Build.Name.CommandLineArguments (CommandLineArgumentsLike (..))
+import Document.Build.Name.CommandLineProgram (CommandLineProgramLike (..))
+import Document.Build.Name.StandardError (StandardErrorLike (..))
+import Document.Build.Name.StandardOutput (StandardOutputLike (..))
+import GHC.Generics (Generic)
+
+{- | A process exited with a non-zero exit code: its command line ('commandLine'), standard
+output ('standardOutput') and standard error ('standardError').
+
+@since 0.2.0.0
+-}
+data ProcessFailed
+  = ProcessFailed CommandLine String String
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The class of 'ProcessFailed'.
+
+@since 0.2.0.0
+-}
+class ProcessFailedLike p f x x' where
+  processFailed ::
+    Optic p f x x' ProcessFailed ProcessFailed
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ ProcessFailed) => ProcessFailedLike p f ProcessFailed x' where
+  processFailed =
+    id
+  {-# INLINE processFailed #-}
+
+{- | Lens: 'commandLine' is the default name of the field of type 'CommandLine'.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ProcessFailed) => CommandLineLike p f ProcessFailed x' where
+  commandLine f (ProcessFailed x1 x2 x3) =
+    fmap (\x1' -> ProcessFailed x1' x2 x3) (f x1)
+  {-# INLINE commandLine #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ProcessFailed) => StandardOutputLike p f ProcessFailed x' where
+  standardOutput f (ProcessFailed x1 x2 x3) =
+    fmap (\x2' -> ProcessFailed x1 x2' x3) (f x2)
+  {-# INLINE standardOutput #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ProcessFailed) => StandardErrorLike p f ProcessFailed x' where
+  standardError f (ProcessFailed x1 x2 x3) =
+    fmap (ProcessFailed x1 x2) (f x3)
+  {-# INLINE standardError #-}
+
+{- | Lens: through 'commandLine', a lens, then 'CommandLine''s 'commandLineProgram', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ProcessFailed) => CommandLineProgramLike p f ProcessFailed x' where
+  commandLineProgram =
+    commandLine . commandLineProgram
+  {-# INLINE commandLineProgram #-}
+
+{- | Lens: through 'commandLine', a lens, then 'CommandLine''s 'commandLineArguments', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ProcessFailed) => CommandLineArgumentsLike p f ProcessFailed x' where
+  commandLineArguments =
+    commandLine . commandLineArguments
+  {-# INLINE commandLineArguments #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesProcessFailed == id
+
+@since 0.2.0.0
+-}
+lawNamesProcessFailed :: ProcessFailed -> ProcessFailed
+lawNamesProcessFailed s =
+  ProcessFailed
+    (view (cloneLens commandLine) s)
+    (view (cloneLens standardOutput) s)
+    (view (cloneLens standardError) s)
+
+{- | The classes that a data type holding 'ProcessFailed' inherits.
+
+@since 0.2.0.0
+-}
+type InheritProcessFailed s =
+  ( ProcessFailedLike (->) (Const ()) s s
+  , CommandLineLike (->) (Const ()) s s
+  , StandardOutputLike (->) (Const ()) s s
+  , StandardErrorLike (->) (Const ()) s s
+  , CommandLineProgramLike (->) (Const ()) s s
+  , CommandLineArgumentsLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'ProcessFailed'.
+
+@since 0.2.0.0
+-}
+inheritProcessFailed :: (InheritProcessFailed s) => proxy s -> ()
+inheritProcessFailed _ =
+  ()
+
+{- | 'ProcessFailed' inherits every name of 'CommandLine'.
+
+@since 0.2.0.0
+-}
+inheritsProcessFailed :: [()]
+inheritsProcessFailed =
+  let p = Proxy :: Proxy ProcessFailed
+   in [inheritCommandLine p]
diff --git a/src/Document/Build/Error/ProcessOutputUnrecognised.hs b/src/Document/Build/Error/ProcessOutputUnrecognised.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Error/ProcessOutputUnrecognised.hs
@@ -0,0 +1,144 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | A process whose output was not recognised.
+
+A 'ProcessOutputUnrecognised' is a product: its fields are named 'commandLine' and 'expectedOutput'.
+It holds 'CommandLine', so it inherits its names.
+-}
+module Document.Build.Error.ProcessOutputUnrecognised (
+  -- * Type
+  ProcessOutputUnrecognised (..),
+
+  -- * Optics
+  ProcessOutputUnrecognisedLike (..),
+  lawNamesProcessOutputUnrecognised,
+  InheritProcessOutputUnrecognised,
+  inheritProcessOutputUnrecognised,
+  inheritsProcessOutputUnrecognised,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Data.Proxy (Proxy (..))
+import Document.Build.CommandLine (CommandLine, CommandLineLike (..), inheritCommandLine)
+import Document.Build.Name.CommandLineArguments (CommandLineArgumentsLike (..))
+import Document.Build.Name.CommandLineProgram (CommandLineProgramLike (..))
+import Document.Build.Name.ExpectedOutput (ExpectedOutputLike (..))
+import GHC.Generics (Generic)
+
+{- | The output of a process was not recognised: its command line ('commandLine'), and a
+description of what was expected ('expectedOutput').
+
+@since 0.2.0.0
+-}
+data ProcessOutputUnrecognised
+  = ProcessOutputUnrecognised CommandLine String
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The class of 'ProcessOutputUnrecognised'.
+
+@since 0.2.0.0
+-}
+class ProcessOutputUnrecognisedLike p f x x' where
+  processOutputUnrecognised ::
+    Optic p f x x' ProcessOutputUnrecognised ProcessOutputUnrecognised
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ ProcessOutputUnrecognised) => ProcessOutputUnrecognisedLike p f ProcessOutputUnrecognised x' where
+  processOutputUnrecognised =
+    id
+  {-# INLINE processOutputUnrecognised #-}
+
+{- | Lens: 'commandLine' is the default name of the field of type 'CommandLine'.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ProcessOutputUnrecognised) => CommandLineLike p f ProcessOutputUnrecognised x' where
+  commandLine f (ProcessOutputUnrecognised x1 x2) =
+    fmap (`ProcessOutputUnrecognised` x2) (f x1)
+  {-# INLINE commandLine #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ProcessOutputUnrecognised) => ExpectedOutputLike p f ProcessOutputUnrecognised x' where
+  expectedOutput f (ProcessOutputUnrecognised x1 x2) =
+    fmap (ProcessOutputUnrecognised x1) (f x2)
+  {-# INLINE expectedOutput #-}
+
+{- | Lens: through 'commandLine', a lens, then 'CommandLine''s 'commandLineProgram', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ProcessOutputUnrecognised) => CommandLineProgramLike p f ProcessOutputUnrecognised x' where
+  commandLineProgram =
+    commandLine . commandLineProgram
+  {-# INLINE commandLineProgram #-}
+
+{- | Lens: through 'commandLine', a lens, then 'CommandLine''s 'commandLineArguments', a lens.
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ProcessOutputUnrecognised) => CommandLineArgumentsLike p f ProcessOutputUnrecognised x' where
+  commandLineArguments =
+    commandLine . commandLineArguments
+  {-# INLINE commandLineArguments #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesProcessOutputUnrecognised == id
+
+@since 0.2.0.0
+-}
+lawNamesProcessOutputUnrecognised :: ProcessOutputUnrecognised -> ProcessOutputUnrecognised
+lawNamesProcessOutputUnrecognised s =
+  ProcessOutputUnrecognised
+    (view (cloneLens commandLine) s)
+    (view (cloneLens expectedOutput) s)
+
+{- | The classes that a data type holding 'ProcessOutputUnrecognised' inherits.
+
+@since 0.2.0.0
+-}
+type InheritProcessOutputUnrecognised s =
+  ( ProcessOutputUnrecognisedLike (->) (Const ()) s s
+  , CommandLineLike (->) (Const ()) s s
+  , ExpectedOutputLike (->) (Const ()) s s
+  , CommandLineProgramLike (->) (Const ()) s s
+  , CommandLineArgumentsLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'ProcessOutputUnrecognised'.
+
+@since 0.2.0.0
+-}
+inheritProcessOutputUnrecognised :: (InheritProcessOutputUnrecognised s) => proxy s -> ()
+inheritProcessOutputUnrecognised _ =
+  ()
+
+{- | 'ProcessOutputUnrecognised' inherits every name of 'CommandLine'.
+
+@since 0.2.0.0
+-}
+inheritsProcessOutputUnrecognised :: [()]
+inheritsProcessOutputUnrecognised =
+  let p = Proxy :: Proxy ProcessOutputUnrecognised
+   in [inheritCommandLine p]
diff --git a/src/Document/Build/Error/StringsMissing.hs b/src/Document/Build/Error/StringsMissing.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Error/StringsMissing.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | A file that does not contain strings it is required to contain.
+
+A 'StringsMissing' is a product: its fields are named 'file' and 'missingStrings'.
+-}
+module Document.Build.Error.StringsMissing (
+  -- * Type
+  StringsMissing (..),
+
+  -- * Optics
+  StringsMissingLike (..),
+  lawNamesStringsMissing,
+  InheritStringsMissing,
+  inheritStringsMissing,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Data.List.NonEmpty (NonEmpty)
+import Document.Build.Name.MissingStrings (MissingStringsLike (..))
+import GHC.Generics (Generic)
+import System.FilePather.Name.File (FileLike (..))
+
+{- | A file ('file') does not contain strings it is required to contain ('missingStrings').
+
+@since 0.2.0.0
+-}
+data StringsMissing
+  = StringsMissing FilePath (NonEmpty String)
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The class of 'StringsMissing'.
+
+@since 0.2.0.0
+-}
+class StringsMissingLike p f x x' where
+  stringsMissing ::
+    Optic p f x x' StringsMissing StringsMissing
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ StringsMissing) => StringsMissingLike p f StringsMissing x' where
+  stringsMissing =
+    id
+  {-# INLINE stringsMissing #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ StringsMissing) => FileLike p f StringsMissing x' where
+  file f (StringsMissing x1 x2) =
+    fmap (`StringsMissing` x2) (f x1)
+  {-# INLINE file #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ StringsMissing) => MissingStringsLike p f StringsMissing x' where
+  missingStrings f (StringsMissing x1 x2) =
+    fmap (StringsMissing x1) (f x2)
+  {-# INLINE missingStrings #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesStringsMissing == id
+
+@since 0.2.0.0
+-}
+lawNamesStringsMissing :: StringsMissing -> StringsMissing
+lawNamesStringsMissing s =
+  StringsMissing
+    (view (cloneLens file) s)
+    (view (cloneLens missingStrings) s)
+
+{- | The classes that a data type holding 'StringsMissing' inherits.
+
+@since 0.2.0.0
+-}
+type InheritStringsMissing s =
+  ( StringsMissingLike (->) (Const ()) s s
+  , FileLike (->) (Const ()) s s
+  , MissingStringsLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'StringsMissing'.
+
+@since 0.2.0.0
+-}
+inheritStringsMissing :: (InheritStringsMissing s) => proxy s -> ()
+inheritStringsMissing _ =
+  ()
diff --git a/src/Document/Build/File.hs b/src/Document/Build/File.hs
--- a/src/Document/Build/File.hs
+++ b/src/Document/Build/File.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 -- | Commands that find, copy, read, write and archive files.
@@ -19,7 +20,7 @@
   tarGz,
 ) where
 
-import Control.Lens (view, _Wrapped)
+import Control.Lens (cloneLens, view)
 import Control.Monad.Trans.Class (lift)
 import Data.Bool (bool)
 import qualified Data.ByteString as ByteString
@@ -27,11 +28,13 @@
 import Data.Text (Text)
 import Data.Text.Encoding (decodeUtf8', encodeUtf8)
 import Document.Build.Build (Build, Command, createParentDirectory, expectOutput, failBuild, io, processWith, requireDirectory, requireInput)
-import Document.Build.Error (BuildError (..), CommandLine (..), ReviewBuildError)
+import Document.Build.CommandLine (CommandLine (..))
+import Document.Build.Error (BuildError (..), ReviewBuildError)
 import System.Directory (copyFileWithMetadata, doesDirectoryExist, doesFileExist, makeAbsolute)
 import System.FilePath (makeRelative, (</>))
 import System.FilePather.Combinators (directoryFiles, hasExtension)
 import System.FilePather.FilePather (FilePather (..))
+import System.FilePather.Name.PathFunction (PathFunctionLike (..))
 import System.Process (cwd)
 
 {- | A value if a file exists, and 'mempty' otherwise, such as options for a
@@ -55,7 +58,7 @@
 filesIn :: FilePath -> Build e [FilePath]
 filesIn d =
   io (doesDirectoryExist d)
-    >>= bool (pure []) (view _Wrapped directoryFiles d)
+    >>= bool (pure []) (view (cloneLens pathFunction) directoryFiles d)
 
 {- | The files with one of the extensions in a directory and its
 subdirectories, in sorted order, or none if the directory does not exist.
@@ -101,7 +104,7 @@
 readUtf8 :: (ReviewBuildError e) => FilePath -> Build e Text
 readUtf8 p =
   do
-    _ <- view _Wrapped requireInput p
+    _ <- view (cloneLens pathFunction) requireInput p
     b <- io (ByteString.readFile p)
     either (const (failBuild (FileNotUtf8 p))) pure (decodeUtf8' b)
 
diff --git a/src/Document/Build/Git.hs b/src/Document/Build/Git.hs
--- a/src/Document/Build/Git.hs
+++ b/src/Document/Build/Git.hs
@@ -1,10 +1,4 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 {- | Commands that get details of files from git, ready to be inserted into
@@ -12,25 +6,10 @@
 -}
 module Document.Build.Git (
   -- * Details
-  GitDetails (..),
-  gitCommitHash,
-  gitCommitHashShort,
-  gitAuthorName,
-  gitAuthorEmail,
-  gitAuthorTime,
-  gitCommitterName,
-  gitCommitterEmail,
-  gitCommitTime,
-  gitSubject,
-  gitAuthors,
-  gitDetails,
+  readGitDetails,
   gitLastCommit,
   gitLogFormat,
   parseGitLog,
-  GetGitDetails (..),
-  HasGitDetails (..),
-  ReviewGitDetails (..),
-  AsGitDetails (..),
 
   -- * Placeholders
   gitPlaceholders,
@@ -40,163 +19,37 @@
   gitFiles,
 ) where
 
-import Control.DeepSeq (NFData)
-import Control.Lens (Field1, Field10, Field2, Field3, Field4, Field5, Field6, Field7, Field8, Field9, Getter, Lens', Prism', Review, lens, prism', review, unto, view, _1, _10, _2, _3, _4, _5, _6, _7, _8, _9)
+import Control.Lens (cloneLens, view)
 import Control.Monad.Reader (ask)
 import Control.Monad.Trans.Class (lift)
-import Data.Aeson (FromJSON (..), ToJSON (..), object, withObject, (.:), (.=))
 import Data.Bool (bool)
 import Data.Containers.ListUtils (nubOrd)
-import Data.Data (Data)
 import Data.List (intercalate)
 import Data.Time (UTCTime, defaultTimeLocale, formatTime)
 import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
 import Document.Build.Build (Command, failBuild, io, process)
-import Document.Build.Error (BuildError (..), CommandLine (..), ReviewBuildError)
-import GHC.Generics (Generic)
+import Document.Build.CommandLine (CommandLine (..))
+import Document.Build.Error (BuildError (..), ReviewBuildError)
+import Document.Build.Error.ProcessOutputUnrecognised (ProcessOutputUnrecognised (..))
+import Document.Build.GitDetails (GitDetails (..))
+import Document.Build.Name.GitAuthorEmail (GitAuthorEmailLike (..))
+import Document.Build.Name.GitAuthorName (GitAuthorNameLike (..))
+import Document.Build.Name.GitAuthorTime (GitAuthorTimeLike (..))
+import Document.Build.Name.GitAuthors (GitAuthorsLike (..))
+import Document.Build.Name.GitCommitHash (GitCommitHashLike (..))
+import Document.Build.Name.GitCommitHashShort (GitCommitHashShortLike (..))
+import Document.Build.Name.GitCommitTime (GitCommitTimeLike (..))
+import Document.Build.Name.GitCommitterEmail (GitCommitterEmailLike (..))
+import Document.Build.Name.GitCommitterName (GitCommitterNameLike (..))
+import Document.Build.Name.GitSubject (GitSubjectLike (..))
 import System.Directory (doesDirectoryExist)
 import System.FilePath (normalise, takeDirectory, takeFileName, (</>))
 import Text.Read (readMaybe)
 
 {- $setup
->>> import Control.Lens ((^.), (^?), _Just)
+>>> import Control.Lens (cloneLens, view, (^?), _Just)
 -}
 
-{- | Details of the last commit to change a file, and everyone who has
-changed it: the commit hash, abbreviated commit hash, author name, author
-email, author time, committer name, committer email, commit time, subject,
-and the authors of every commit to change the file as @Name \<email\>@, most
-recent first, without duplicates.
-
-@since 0.1.0.0
--}
-data GitDetails
-  = GitDetails String String String String UTCTime String String UTCTime String [String]
-  deriving stock (Eq, Ord, Show, Generic, Data)
-  deriving anyclass (NFData)
-
--- | @since 0.1.0.0
-instance ToJSON GitDetails where
-  toJSON (GitDetails h h' an ae at cn ce ct subject as) =
-    object
-      [ "hash" .= h
-      , "hashShort" .= h'
-      , "authorName" .= an
-      , "authorEmail" .= ae
-      , "authorTime" .= at
-      , "committerName" .= cn
-      , "committerEmail" .= ce
-      , "commitTime" .= ct
-      , "subject" .= subject
-      , "authors" .= as
-      ]
-
--- | @since 0.1.0.0
-instance FromJSON GitDetails where
-  parseJSON =
-    withObject "GitDetails" $ \o ->
-      GitDetails
-        <$> o .: "hash"
-        <*> o .: "hashShort"
-        <*> o .: "authorName"
-        <*> o .: "authorEmail"
-        <*> o .: "authorTime"
-        <*> o .: "committerName"
-        <*> o .: "committerEmail"
-        <*> o .: "commitTime"
-        <*> o .: "subject"
-        <*> o .: "authors"
-
--- | @since 0.1.0.0
-instance Field1 GitDetails GitDetails String String
-
--- | @since 0.1.0.0
-instance Field2 GitDetails GitDetails String String
-
--- | @since 0.1.0.0
-instance Field3 GitDetails GitDetails String String
-
--- | @since 0.1.0.0
-instance Field4 GitDetails GitDetails String String
-
--- | @since 0.1.0.0
-instance Field5 GitDetails GitDetails UTCTime UTCTime
-
--- | @since 0.1.0.0
-instance Field6 GitDetails GitDetails String String
-
--- | @since 0.1.0.0
-instance Field7 GitDetails GitDetails String String
-
--- | @since 0.1.0.0
-instance Field8 GitDetails GitDetails UTCTime UTCTime
-
--- | @since 0.1.0.0
-instance Field9 GitDetails GitDetails String String
-
--- | @since 0.1.0.0
-instance Field10 GitDetails GitDetails [String] [String]
-
--- | @since 0.1.0.0
-gitCommitHash :: Lens' GitDetails String
-gitCommitHash =
-  _1
-{-# INLINE gitCommitHash #-}
-
--- | @since 0.1.0.0
-gitCommitHashShort :: Lens' GitDetails String
-gitCommitHashShort =
-  _2
-{-# INLINE gitCommitHashShort #-}
-
--- | @since 0.1.0.0
-gitAuthorName :: Lens' GitDetails String
-gitAuthorName =
-  _3
-{-# INLINE gitAuthorName #-}
-
--- | @since 0.1.0.0
-gitAuthorEmail :: Lens' GitDetails String
-gitAuthorEmail =
-  _4
-{-# INLINE gitAuthorEmail #-}
-
--- | @since 0.1.0.0
-gitAuthorTime :: Lens' GitDetails UTCTime
-gitAuthorTime =
-  _5
-{-# INLINE gitAuthorTime #-}
-
--- | @since 0.1.0.0
-gitCommitterName :: Lens' GitDetails String
-gitCommitterName =
-  _6
-{-# INLINE gitCommitterName #-}
-
--- | @since 0.1.0.0
-gitCommitterEmail :: Lens' GitDetails String
-gitCommitterEmail =
-  _7
-{-# INLINE gitCommitterEmail #-}
-
--- | @since 0.1.0.0
-gitCommitTime :: Lens' GitDetails UTCTime
-gitCommitTime =
-  _8
-{-# INLINE gitCommitTime #-}
-
--- | @since 0.1.0.0
-gitSubject :: Lens' GitDetails String
-gitSubject =
-  _9
-{-# INLINE gitSubject #-}
-
--- | @since 0.1.0.0
-gitAuthors :: Lens' GitDetails [String]
-gitAuthors =
-  _10
-{-# INLINE gitAuthors #-}
-
 {- | The @git log@ format that 'parseGitLog' reads: fields separated by NUL.
 
 @since 0.1.0.0
@@ -211,7 +64,7 @@
 >>> d ^? _Just . gitSubject
 Just "fix typo"
 
->>> fmap (formatTimeUtc . (^. gitCommitTime)) d
+>>> fmap (formatTimeUtc . view (cloneLens gitCommitTime)) d
 Just "1970-01-02 00:00:00 UTC"
 
 >>> parseGitLog "" []
@@ -239,8 +92,8 @@
 
 @since 0.1.0.0
 -}
-gitDetails :: (ReviewBuildError e) => Command e GitDetails
-gitDetails =
+readGitDetails :: (ReviewBuildError e) => Command e GitDetails
+readGitDetails =
   do
     p <- ask
     gitLastCommit >>= maybe (lift (failBuild (FileNotInGit p))) pure
@@ -273,7 +126,7 @@
       then pure Nothing
       else do
         as <- lift (process (CommandLine "git" ["-C", d, "log", "--format=%aN <%aE>", "--", f]))
-        lift (maybe (failBuild (ProcessOutputUnrecognised c ("fields in the format " <> gitLogFormat))) (pure . Just) (parseGitLog o (nubOrd (lines as))))
+        lift (maybe (failBuild (BuildErrorProcessOutputUnrecognised (ProcessOutputUnrecognised c ("fields in the format " <> gitLogFormat)))) (pure . Just) (parseGitLog o (nubOrd (lines as))))
 
 {- | Placeholders and their values for the details of a commit, to use with
 'Document.Build.Replace.replaceStrings'.
@@ -284,17 +137,17 @@
 @since 0.1.0.0
 -}
 gitPlaceholders :: GitDetails -> [(String, String)]
-gitPlaceholders (GitDetails h h' an ae at cn ce ct subject as) =
-  [ ("${GIT_COMMIT_HASH}", h)
-  , ("${GIT_COMMIT_HASH_SHORT}", h')
-  , ("${GIT_AUTHOR_NAME}", an)
-  , ("${GIT_AUTHOR_EMAIL}", ae)
-  , ("${GIT_AUTHOR_TIME}", formatTimeUtc at)
-  , ("${GIT_COMMITTER_NAME}", cn)
-  , ("${GIT_COMMITTER_EMAIL}", ce)
-  , ("${GIT_COMMIT_TIME}", formatTimeUtc ct)
-  , ("${GIT_SUBJECT}", subject)
-  , ("${GIT_AUTHORS}", intercalate ", " as)
+gitPlaceholders d =
+  [ ("${GIT_COMMIT_HASH}", view (cloneLens gitCommitHash) d)
+  , ("${GIT_COMMIT_HASH_SHORT}", view (cloneLens gitCommitHashShort) d)
+  , ("${GIT_AUTHOR_NAME}", view (cloneLens gitAuthorName) d)
+  , ("${GIT_AUTHOR_EMAIL}", view (cloneLens gitAuthorEmail) d)
+  , ("${GIT_AUTHOR_TIME}", formatTimeUtc (view (cloneLens gitAuthorTime) d))
+  , ("${GIT_COMMITTER_NAME}", view (cloneLens gitCommitterName) d)
+  , ("${GIT_COMMITTER_EMAIL}", view (cloneLens gitCommitterEmail) d)
+  , ("${GIT_COMMIT_TIME}", formatTimeUtc (view (cloneLens gitCommitTime) d))
+  , ("${GIT_SUBJECT}", view (cloneLens gitSubject) d)
+  , ("${GIT_AUTHORS}", intercalate ", " (view (cloneLens gitAuthors) d))
   ]
 
 {- | Format a time in UTC, as @YYYY-MM-DD HH:MM:SS UTC@.
@@ -320,55 +173,3 @@
     d <- ask
     o <- lift (process (CommandLine "git" ["-C", d, "ls-files", "-z"]))
     pure (fmap (normalise . (d </>)) (filter (not . null) (splitOn '\NUL' o)))
-
--- | @since 0.1.0.0
-class GetGitDetails s where
-  getGitDetails :: Getter s GitDetails
-
--- | @since 0.1.0.0
-instance GetGitDetails GitDetails where
-  getGitDetails =
-    id
-  {-# INLINE getGitDetails #-}
-
--- | @since 0.1.0.0
-class (GetGitDetails s) => HasGitDetails s where
-  {-# MINIMAL setGitDetails #-}
-  setGitDetails :: GitDetails -> s -> s
-
-  gitDetailsLens :: Lens' s GitDetails
-  gitDetailsLens =
-    lens (view getGitDetails) (flip setGitDetails)
-  {-# INLINE gitDetailsLens #-}
-
--- | @since 0.1.0.0
-instance HasGitDetails GitDetails where
-  setGitDetails =
-    const
-  {-# INLINE setGitDetails #-}
-
--- | @since 0.1.0.0
-class ReviewGitDetails t where
-  reviewGitDetails :: Review t GitDetails
-
--- | @since 0.1.0.0
-instance ReviewGitDetails GitDetails where
-  reviewGitDetails =
-    unto id
-  {-# INLINE reviewGitDetails #-}
-
--- | @since 0.1.0.0
-class (ReviewGitDetails t) => AsGitDetails t where
-  {-# MINIMAL matchGitDetails #-}
-  matchGitDetails :: t -> Maybe GitDetails
-
-  _GitDetails :: Prism' t GitDetails
-  _GitDetails =
-    prism' (review reviewGitDetails) matchGitDetails
-  {-# INLINE _GitDetails #-}
-
--- | @since 0.1.0.0
-instance AsGitDetails GitDetails where
-  matchGitDetails =
-    Just
-  {-# INLINE matchGitDetails #-}
diff --git a/src/Document/Build/GitDetails.hs b/src/Document/Build/GitDetails.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/GitDetails.hs
@@ -0,0 +1,247 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | Details of the last commit to change a file.
+
+A 'GitDetails' is a product: its fields are named 'gitCommitHash', 'gitCommitHashShort', 'gitAuthorName', 'gitAuthorEmail', 'gitAuthorTime', 'gitCommitterName', 'gitCommitterEmail', 'gitCommitTime', 'gitSubject' and 'gitAuthors'.
+-}
+module Document.Build.GitDetails (
+  -- * Type
+  GitDetails (..),
+
+  -- * Optics
+  GitDetailsLike (..),
+  lawNamesGitDetails,
+  InheritGitDetails,
+  inheritGitDetails,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Aeson (FromJSON (..), ToJSON (..), object, withObject, (.:), (.=))
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Data.Time (UTCTime)
+import Document.Build.Name.GitAuthorEmail (GitAuthorEmailLike (..))
+import Document.Build.Name.GitAuthorName (GitAuthorNameLike (..))
+import Document.Build.Name.GitAuthorTime (GitAuthorTimeLike (..))
+import Document.Build.Name.GitAuthors (GitAuthorsLike (..))
+import Document.Build.Name.GitCommitHash (GitCommitHashLike (..))
+import Document.Build.Name.GitCommitHashShort (GitCommitHashShortLike (..))
+import Document.Build.Name.GitCommitTime (GitCommitTimeLike (..))
+import Document.Build.Name.GitCommitterEmail (GitCommitterEmailLike (..))
+import Document.Build.Name.GitCommitterName (GitCommitterNameLike (..))
+import Document.Build.Name.GitSubject (GitSubjectLike (..))
+import GHC.Generics (Generic)
+
+{- | Details of the last commit to change a file, and everyone who has
+changed it: the commit hash ('gitCommitHash'), abbreviated commit hash
+('gitCommitHashShort'), author name ('gitAuthorName'), author email
+('gitAuthorEmail'), author time ('gitAuthorTime'), committer name
+('gitCommitterName'), committer email ('gitCommitterEmail'), commit time
+('gitCommitTime'), subject ('gitSubject'), and the authors of every commit to
+change the file as @Name \<email\>@, most recent first, without duplicates
+('gitAuthors').
+
+@since 0.1.0.0
+-}
+data GitDetails
+  = GitDetails String String String String UTCTime String String UTCTime String [String]
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+-- | @since 0.1.0.0
+instance ToJSON GitDetails where
+  toJSON (GitDetails h h' an ae at cn ce ct subject as) =
+    object
+      [ "hash" .= h
+      , "hashShort" .= h'
+      , "authorName" .= an
+      , "authorEmail" .= ae
+      , "authorTime" .= at
+      , "committerName" .= cn
+      , "committerEmail" .= ce
+      , "commitTime" .= ct
+      , "subject" .= subject
+      , "authors" .= as
+      ]
+
+-- | @since 0.1.0.0
+instance FromJSON GitDetails where
+  parseJSON =
+    withObject "GitDetails" $ \o ->
+      GitDetails
+        <$> o .: "hash"
+        <*> o .: "hashShort"
+        <*> o .: "authorName"
+        <*> o .: "authorEmail"
+        <*> o .: "authorTime"
+        <*> o .: "committerName"
+        <*> o .: "committerEmail"
+        <*> o .: "commitTime"
+        <*> o .: "subject"
+        <*> o .: "authors"
+
+{- | The class of 'GitDetails'.
+
+@since 0.2.0.0
+-}
+class GitDetailsLike p f x x' where
+  gitDetails ::
+    Optic p f x x' GitDetails GitDetails
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ GitDetails) => GitDetailsLike p f GitDetails x' where
+  gitDetails =
+    id
+  {-# INLINE gitDetails #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitCommitHashLike p f GitDetails x' where
+  gitCommitHash f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (\x1' -> GitDetails x1' x2 x3 x4 x5 x6 x7 x8 x9 x10) (f x1)
+  {-# INLINE gitCommitHash #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitCommitHashShortLike p f GitDetails x' where
+  gitCommitHashShort f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (\x2' -> GitDetails x1 x2' x3 x4 x5 x6 x7 x8 x9 x10) (f x2)
+  {-# INLINE gitCommitHashShort #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitAuthorNameLike p f GitDetails x' where
+  gitAuthorName f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (\x3' -> GitDetails x1 x2 x3' x4 x5 x6 x7 x8 x9 x10) (f x3)
+  {-# INLINE gitAuthorName #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitAuthorEmailLike p f GitDetails x' where
+  gitAuthorEmail f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (\x4' -> GitDetails x1 x2 x3 x4' x5 x6 x7 x8 x9 x10) (f x4)
+  {-# INLINE gitAuthorEmail #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitAuthorTimeLike p f GitDetails x' where
+  gitAuthorTime f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (\x5' -> GitDetails x1 x2 x3 x4 x5' x6 x7 x8 x9 x10) (f x5)
+  {-# INLINE gitAuthorTime #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitCommitterNameLike p f GitDetails x' where
+  gitCommitterName f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (\x6' -> GitDetails x1 x2 x3 x4 x5 x6' x7 x8 x9 x10) (f x6)
+  {-# INLINE gitCommitterName #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitCommitterEmailLike p f GitDetails x' where
+  gitCommitterEmail f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (\x7' -> GitDetails x1 x2 x3 x4 x5 x6 x7' x8 x9 x10) (f x7)
+  {-# INLINE gitCommitterEmail #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitCommitTimeLike p f GitDetails x' where
+  gitCommitTime f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (\x8' -> GitDetails x1 x2 x3 x4 x5 x6 x7 x8' x9 x10) (f x8)
+  {-# INLINE gitCommitTime #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitSubjectLike p f GitDetails x' where
+  gitSubject f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (\x9' -> GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9' x10) (f x9)
+  {-# INLINE gitSubject #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ GitDetails) => GitAuthorsLike p f GitDetails x' where
+  gitAuthors f (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9 x10) =
+    fmap (GitDetails x1 x2 x3 x4 x5 x6 x7 x8 x9) (f x10)
+  {-# INLINE gitAuthors #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesGitDetails == id
+
+@since 0.2.0.0
+-}
+lawNamesGitDetails :: GitDetails -> GitDetails
+lawNamesGitDetails s =
+  GitDetails
+    (view (cloneLens gitCommitHash) s)
+    (view (cloneLens gitCommitHashShort) s)
+    (view (cloneLens gitAuthorName) s)
+    (view (cloneLens gitAuthorEmail) s)
+    (view (cloneLens gitAuthorTime) s)
+    (view (cloneLens gitCommitterName) s)
+    (view (cloneLens gitCommitterEmail) s)
+    (view (cloneLens gitCommitTime) s)
+    (view (cloneLens gitSubject) s)
+    (view (cloneLens gitAuthors) s)
+
+{- | The classes that a data type holding 'GitDetails' inherits.
+
+@since 0.2.0.0
+-}
+type InheritGitDetails s =
+  ( GitDetailsLike (->) (Const ()) s s
+  , GitCommitHashLike (->) (Const ()) s s
+  , GitCommitHashShortLike (->) (Const ()) s s
+  , GitAuthorNameLike (->) (Const ()) s s
+  , GitAuthorEmailLike (->) (Const ()) s s
+  , GitAuthorTimeLike (->) (Const ()) s s
+  , GitCommitterNameLike (->) (Const ()) s s
+  , GitCommitterEmailLike (->) (Const ()) s s
+  , GitCommitTimeLike (->) (Const ()) s s
+  , GitSubjectLike (->) (Const ()) s s
+  , GitAuthorsLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'GitDetails'.
+
+@since 0.2.0.0
+-}
+inheritGitDetails :: (InheritGitDetails s) => proxy s -> ()
+inheritGitDetails _ =
+  ()
diff --git a/src/Document/Build/Hash.hs b/src/Document/Build/Hash.hs
--- a/src/Document/Build/Hash.hs
+++ b/src/Document/Build/Hash.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 -- | Commands that compute hashes of files.
diff --git a/src/Document/Build/Image.hs b/src/Document/Build/Image.hs
--- a/src/Document/Build/Image.hs
+++ b/src/Document/Build/Image.hs
@@ -1,10 +1,4 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 {- | Commands that operate on images, with ImageMagick (@identify@ and
@@ -12,22 +6,8 @@
 -}
 module Document.Build.Image (
   -- * Information
-  ImageInfo (..),
-  imageFormat,
-  imageWidth,
-  imageHeight,
-  imageResolutionX,
-  imageResolutionY,
-  imageResolutionUnits,
-  imageColourspace,
-  imageDepth,
-  imageFrames,
-  imageInfo,
+  readImageInfo,
   parseImageInfo,
-  GetImageInfo (..),
-  HasImageInfo (..),
-  ReviewImageInfo (..),
-  AsImageInfo (..),
 
   -- * Conversion
   imageConvert,
@@ -37,144 +17,22 @@
   imageAppend,
 ) where
 
-import Control.DeepSeq (NFData)
-import Control.Lens (Field1, Field2, Field3, Field4, Field5, Field6, Field7, Field8, Field9, Getter, Lens', Prism', Review, lens, prism', review, unto, view, _1, _2, _3, _4, _5, _6, _7, _8, _9, _Wrapped)
+import Control.Lens (cloneLens, view)
 import Control.Monad.Trans.Class (lift)
-import Data.Aeson (FromJSON (..), ToJSON (..), object, withObject, (.:), (.=))
-import Data.Data (Data)
 import Document.Build.Build (Command, createParentDirectory, expectOutput, failBuild, process, requireDirectory, requireInput)
-import Document.Build.Error (BuildError (..), CommandLine (..), ReviewBuildError)
-import GHC.Generics (Generic)
+import Document.Build.CommandLine (CommandLine (..))
+import Document.Build.Error (BuildError (..), ReviewBuildError)
+import Document.Build.Error.ProcessOutputUnrecognised (ProcessOutputUnrecognised (..))
+import Document.Build.ImageInfo (ImageInfo (..))
 import System.FilePath ((</>))
+import System.FilePather.Name.PathFunction (PathFunctionLike (..))
 import Text.Read (readMaybe)
 
 {- $setup
->>> import Control.Lens ((^.))
+>>> import Control.Lens (cloneLens, view)
+>>> import Document.Build.Name.ImageWidth (imageWidth)
 -}
 
-{- | Information about an image: its format (such as @"PNG"@), width and
-height in pixels, horizontal and vertical resolution, the units of the
-resolution (such as @"PixelsPerInch"@ or @"Undefined"@), colourspace (such as
-@"sRGB"@), bit depth, and number of frames.
-
-@since 0.1.0.0
--}
-data ImageInfo
-  = ImageInfo String Int Int Double Double String String Int Int
-  deriving stock (Eq, Ord, Show, Generic, Data)
-  deriving anyclass (NFData)
-
--- | @since 0.1.0.0
-instance ToJSON ImageInfo where
-  toJSON (ImageInfo m w h x y u c z n) =
-    object
-      [ "format" .= m
-      , "width" .= w
-      , "height" .= h
-      , "resolutionX" .= x
-      , "resolutionY" .= y
-      , "resolutionUnits" .= u
-      , "colourspace" .= c
-      , "depth" .= z
-      , "frames" .= n
-      ]
-
--- | @since 0.1.0.0
-instance FromJSON ImageInfo where
-  parseJSON =
-    withObject "ImageInfo" $ \o ->
-      ImageInfo
-        <$> o .: "format"
-        <*> o .: "width"
-        <*> o .: "height"
-        <*> o .: "resolutionX"
-        <*> o .: "resolutionY"
-        <*> o .: "resolutionUnits"
-        <*> o .: "colourspace"
-        <*> o .: "depth"
-        <*> o .: "frames"
-
--- | @since 0.1.0.0
-instance Field1 ImageInfo ImageInfo String String
-
--- | @since 0.1.0.0
-instance Field2 ImageInfo ImageInfo Int Int
-
--- | @since 0.1.0.0
-instance Field3 ImageInfo ImageInfo Int Int
-
--- | @since 0.1.0.0
-instance Field4 ImageInfo ImageInfo Double Double
-
--- | @since 0.1.0.0
-instance Field5 ImageInfo ImageInfo Double Double
-
--- | @since 0.1.0.0
-instance Field6 ImageInfo ImageInfo String String
-
--- | @since 0.1.0.0
-instance Field7 ImageInfo ImageInfo String String
-
--- | @since 0.1.0.0
-instance Field8 ImageInfo ImageInfo Int Int
-
--- | @since 0.1.0.0
-instance Field9 ImageInfo ImageInfo Int Int
-
--- | @since 0.1.0.0
-imageFormat :: Lens' ImageInfo String
-imageFormat =
-  _1
-{-# INLINE imageFormat #-}
-
--- | @since 0.1.0.0
-imageWidth :: Lens' ImageInfo Int
-imageWidth =
-  _2
-{-# INLINE imageWidth #-}
-
--- | @since 0.1.0.0
-imageHeight :: Lens' ImageInfo Int
-imageHeight =
-  _3
-{-# INLINE imageHeight #-}
-
--- | @since 0.1.0.0
-imageResolutionX :: Lens' ImageInfo Double
-imageResolutionX =
-  _4
-{-# INLINE imageResolutionX #-}
-
--- | @since 0.1.0.0
-imageResolutionY :: Lens' ImageInfo Double
-imageResolutionY =
-  _5
-{-# INLINE imageResolutionY #-}
-
--- | @since 0.1.0.0
-imageResolutionUnits :: Lens' ImageInfo String
-imageResolutionUnits =
-  _6
-{-# INLINE imageResolutionUnits #-}
-
--- | @since 0.1.0.0
-imageColourspace :: Lens' ImageInfo String
-imageColourspace =
-  _7
-{-# INLINE imageColourspace #-}
-
--- | @since 0.1.0.0
-imageDepth :: Lens' ImageInfo Int
-imageDepth =
-  _8
-{-# INLINE imageDepth #-}
-
--- | @since 0.1.0.0
-imageFrames :: Lens' ImageInfo Int
-imageFrames =
-  _9
-{-# INLINE imageFrames #-}
-
 -- | The @identify@ format that 'parseImageInfo' reads.
 identifyFormat :: String
 identifyFormat =
@@ -185,14 +43,14 @@
 
 @since 0.1.0.0
 -}
-imageInfo :: (ReviewBuildError e) => Command e ImageInfo
-imageInfo =
+readImageInfo :: (ReviewBuildError e) => Command e ImageInfo
+readImageInfo =
   do
     i <- requireInput
     let c = CommandLine "identify" ["-format", identifyFormat, i <> "[0]"]
     o <- lift (process c)
     maybe
-      (lift (failBuild (ProcessOutputUnrecognised c "image format, width, height, resolution, units, colourspace, depth and frames")))
+      (lift (failBuild (BuildErrorProcessOutputUnrecognised (ProcessOutputUnrecognised c "image format, width, height, resolution, units, colourspace, depth and frames"))))
       pure
       (parseImageInfo o)
 
@@ -200,7 +58,7 @@
 @%m\\n%w\\n%h\\n%x\\n%y\\n%U\\n%[colorspace]\\n%z\\n%n\\n@. A resolution may
 be followed by its units, which are ignored.
 
->>> fmap (^. imageWidth) (parseImageInfo "PNG\n3384\n262\n72\n72\nUndefined\nsRGB\n8\n1\n")
+>>> fmap (view (cloneLens imageWidth)) (parseImageInfo "PNG\n3384\n262\n72\n72\nUndefined\nsRGB\n8\n1\n")
 Just 3384
 
 >>> parseImageInfo "PNG\n3384\n262\n72 PixelsPerInch\n72 PixelsPerInch\nPixelsPerInch\nsRGB\n8\n1\n"
@@ -223,58 +81,6 @@
           ImageInfo m <$> number w <*> number h <*> number x <*> number y <*> pure u <*> pure c <*> number z <*> number n
         _ -> Nothing
 
--- | @since 0.1.0.0
-class GetImageInfo s where
-  getImageInfo :: Getter s ImageInfo
-
--- | @since 0.1.0.0
-instance GetImageInfo ImageInfo where
-  getImageInfo =
-    id
-  {-# INLINE getImageInfo #-}
-
--- | @since 0.1.0.0
-class (GetImageInfo s) => HasImageInfo s where
-  {-# MINIMAL setImageInfo #-}
-  setImageInfo :: ImageInfo -> s -> s
-
-  imageInfoLens :: Lens' s ImageInfo
-  imageInfoLens =
-    lens (view getImageInfo) (flip setImageInfo)
-  {-# INLINE imageInfoLens #-}
-
--- | @since 0.1.0.0
-instance HasImageInfo ImageInfo where
-  setImageInfo =
-    const
-  {-# INLINE setImageInfo #-}
-
--- | @since 0.1.0.0
-class ReviewImageInfo t where
-  reviewImageInfo :: Review t ImageInfo
-
--- | @since 0.1.0.0
-instance ReviewImageInfo ImageInfo where
-  reviewImageInfo =
-    unto id
-  {-# INLINE reviewImageInfo #-}
-
--- | @since 0.1.0.0
-class (ReviewImageInfo t) => AsImageInfo t where
-  {-# MINIMAL matchImageInfo #-}
-  matchImageInfo :: t -> Maybe ImageInfo
-
-  _ImageInfo :: Prism' t ImageInfo
-  _ImageInfo =
-    prism' (review reviewImageInfo) matchImageInfo
-  {-# INLINE _ImageInfo #-}
-
--- | @since 0.1.0.0
-instance AsImageInfo ImageInfo where
-  matchImageInfo =
-    Just
-  {-# INLINE matchImageInfo #-}
-
 {- | Convert the image being operated on to a file path, with the format given
 by its extension, and return the file path.
 
@@ -334,5 +140,5 @@
     d <- requireDirectory
     let is = fmap (d </>) fs
         c = CommandLine "convert" (is <> ["+append", o])
-    _ <- lift (traverse (view _Wrapped requireInput) is)
+    _ <- lift (traverse (view (cloneLens pathFunction) requireInput) is)
     lift (createParentDirectory o *> process c *> expectOutput c o)
diff --git a/src/Document/Build/ImageInfo.hs b/src/Document/Build/ImageInfo.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/ImageInfo.hs
@@ -0,0 +1,230 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | Information about an image.
+
+A 'ImageInfo' is a product: its fields are named 'imageFormat', 'imageWidth', 'imageHeight', 'imageResolutionX', 'imageResolutionY', 'imageResolutionUnits', 'imageColourspace', 'imageDepth' and 'imageFrames'.
+-}
+module Document.Build.ImageInfo (
+  -- * Type
+  ImageInfo (..),
+
+  -- * Optics
+  ImageInfoLike (..),
+  lawNamesImageInfo,
+  InheritImageInfo,
+  inheritImageInfo,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Aeson (FromJSON (..), ToJSON (..), object, withObject, (.:), (.=))
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Document.Build.Name.ImageColourspace (ImageColourspaceLike (..))
+import Document.Build.Name.ImageDepth (ImageDepthLike (..))
+import Document.Build.Name.ImageFormat (ImageFormatLike (..))
+import Document.Build.Name.ImageFrames (ImageFramesLike (..))
+import Document.Build.Name.ImageHeight (ImageHeightLike (..))
+import Document.Build.Name.ImageResolutionUnits (ImageResolutionUnitsLike (..))
+import Document.Build.Name.ImageResolutionX (ImageResolutionXLike (..))
+import Document.Build.Name.ImageResolutionY (ImageResolutionYLike (..))
+import Document.Build.Name.ImageWidth (ImageWidthLike (..))
+import GHC.Generics (Generic)
+
+{- | Information about an image: its format ('imageFormat'), such as @"PNG"@,
+width ('imageWidth') and height ('imageHeight') in pixels, horizontal
+('imageResolutionX') and vertical ('imageResolutionY') resolution, the units
+of the resolution ('imageResolutionUnits'), such as @"PixelsPerInch"@ or
+@"Undefined"@, colourspace ('imageColourspace'), such as @"sRGB"@, bit depth
+('imageDepth'), and number of frames ('imageFrames').
+
+@since 0.1.0.0
+-}
+data ImageInfo
+  = ImageInfo String Int Int Double Double String String Int Int
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+-- | @since 0.1.0.0
+instance ToJSON ImageInfo where
+  toJSON (ImageInfo m w h x y u c z n) =
+    object
+      [ "format" .= m
+      , "width" .= w
+      , "height" .= h
+      , "resolutionX" .= x
+      , "resolutionY" .= y
+      , "resolutionUnits" .= u
+      , "colourspace" .= c
+      , "depth" .= z
+      , "frames" .= n
+      ]
+
+-- | @since 0.1.0.0
+instance FromJSON ImageInfo where
+  parseJSON =
+    withObject "ImageInfo" $ \o ->
+      ImageInfo
+        <$> o .: "format"
+        <*> o .: "width"
+        <*> o .: "height"
+        <*> o .: "resolutionX"
+        <*> o .: "resolutionY"
+        <*> o .: "resolutionUnits"
+        <*> o .: "colourspace"
+        <*> o .: "depth"
+        <*> o .: "frames"
+
+{- | The class of 'ImageInfo'.
+
+@since 0.2.0.0
+-}
+class ImageInfoLike p f x x' where
+  imageInfo ::
+    Optic p f x x' ImageInfo ImageInfo
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ ImageInfo) => ImageInfoLike p f ImageInfo x' where
+  imageInfo =
+    id
+  {-# INLINE imageInfo #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ImageInfo) => ImageFormatLike p f ImageInfo x' where
+  imageFormat f (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9) =
+    fmap (\x1' -> ImageInfo x1' x2 x3 x4 x5 x6 x7 x8 x9) (f x1)
+  {-# INLINE imageFormat #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ImageInfo) => ImageWidthLike p f ImageInfo x' where
+  imageWidth f (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9) =
+    fmap (\x2' -> ImageInfo x1 x2' x3 x4 x5 x6 x7 x8 x9) (f x2)
+  {-# INLINE imageWidth #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ImageInfo) => ImageHeightLike p f ImageInfo x' where
+  imageHeight f (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9) =
+    fmap (\x3' -> ImageInfo x1 x2 x3' x4 x5 x6 x7 x8 x9) (f x3)
+  {-# INLINE imageHeight #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ImageInfo) => ImageResolutionXLike p f ImageInfo x' where
+  imageResolutionX f (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9) =
+    fmap (\x4' -> ImageInfo x1 x2 x3 x4' x5 x6 x7 x8 x9) (f x4)
+  {-# INLINE imageResolutionX #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ImageInfo) => ImageResolutionYLike p f ImageInfo x' where
+  imageResolutionY f (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9) =
+    fmap (\x5' -> ImageInfo x1 x2 x3 x4 x5' x6 x7 x8 x9) (f x5)
+  {-# INLINE imageResolutionY #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ImageInfo) => ImageResolutionUnitsLike p f ImageInfo x' where
+  imageResolutionUnits f (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9) =
+    fmap (\x6' -> ImageInfo x1 x2 x3 x4 x5 x6' x7 x8 x9) (f x6)
+  {-# INLINE imageResolutionUnits #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ImageInfo) => ImageColourspaceLike p f ImageInfo x' where
+  imageColourspace f (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9) =
+    fmap (\x7' -> ImageInfo x1 x2 x3 x4 x5 x6 x7' x8 x9) (f x7)
+  {-# INLINE imageColourspace #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ImageInfo) => ImageDepthLike p f ImageInfo x' where
+  imageDepth f (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9) =
+    fmap (\x8' -> ImageInfo x1 x2 x3 x4 x5 x6 x7 x8' x9) (f x8)
+  {-# INLINE imageDepth #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ ImageInfo) => ImageFramesLike p f ImageInfo x' where
+  imageFrames f (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9) =
+    fmap (ImageInfo x1 x2 x3 x4 x5 x6 x7 x8) (f x9)
+  {-# INLINE imageFrames #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesImageInfo == id
+
+@since 0.2.0.0
+-}
+lawNamesImageInfo :: ImageInfo -> ImageInfo
+lawNamesImageInfo s =
+  ImageInfo
+    (view (cloneLens imageFormat) s)
+    (view (cloneLens imageWidth) s)
+    (view (cloneLens imageHeight) s)
+    (view (cloneLens imageResolutionX) s)
+    (view (cloneLens imageResolutionY) s)
+    (view (cloneLens imageResolutionUnits) s)
+    (view (cloneLens imageColourspace) s)
+    (view (cloneLens imageDepth) s)
+    (view (cloneLens imageFrames) s)
+
+{- | The classes that a data type holding 'ImageInfo' inherits.
+
+@since 0.2.0.0
+-}
+type InheritImageInfo s =
+  ( ImageInfoLike (->) (Const ()) s s
+  , ImageFormatLike (->) (Const ()) s s
+  , ImageWidthLike (->) (Const ()) s s
+  , ImageHeightLike (->) (Const ()) s s
+  , ImageResolutionXLike (->) (Const ()) s s
+  , ImageResolutionYLike (->) (Const ()) s s
+  , ImageResolutionUnitsLike (->) (Const ()) s s
+  , ImageColourspaceLike (->) (Const ()) s s
+  , ImageDepthLike (->) (Const ()) s s
+  , ImageFramesLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'ImageInfo'.
+
+@since 0.2.0.0
+-}
+inheritImageInfo :: (InheritImageInfo s) => proxy s -> ()
+inheritImageInfo _ =
+  ()
diff --git a/src/Document/Build/LibreOffice.hs b/src/Document/Build/LibreOffice.hs
--- a/src/Document/Build/LibreOffice.hs
+++ b/src/Document/Build/LibreOffice.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 {- | Commands that operate on LibreOffice files, and other files that
@@ -25,15 +26,18 @@
   docxPages,
 ) where
 
-import Control.Lens (view, _Wrapped)
+import Control.Lens (cloneLens, view)
 import Control.Monad.Trans.Class (lift)
 import Data.Bool (bool)
 import Document.Build.Build (Build, Command, expectOutput, failBuild, io, process, requireInput, withTempDirectory)
-import Document.Build.Error (BuildError (..), CommandLine (..), ReviewBuildError)
+import Document.Build.CommandLine (CommandLine (..))
+import Document.Build.Error (BuildError (..), ReviewBuildError)
+import Document.Build.Error.FileTypeUnsupported (FileTypeUnsupported (..))
 import Document.Build.Pdf (pdfPages)
 import System.Directory (createDirectoryIfMissing, getTemporaryDirectory, makeAbsolute, removePathForcibly)
 import System.FilePath (equalFilePath, takeBaseName, takeExtension, (<.>), (</>))
 import System.FilePather.Combinators ((~>))
+import System.FilePather.Name.PathFunction (PathFunctionLike (..))
 
 {- | Convert the file being operated on with @soffice --convert-to@, into a
 directory, and return the file path of the result.
@@ -42,7 +46,7 @@
 @"docx"@, @"png"@ or @"txt:Text"@. The output file is named after the input
 file, with the extension before any @:@ in the format. LibreOffice exits
 successfully even when a conversion fails, so the build fails with
-'OutputFileMissing' if the output file is not produced.
+'BuildErrorOutputFileMissing' if the output file is not produced.
 
 @since 0.1.0.0
 -}
@@ -149,13 +153,13 @@
   do
     i <- requireInput
     maybe
-      (lift (failBuild (FileTypeUnsupported i supported)))
+      (lift (failBuild (BuildErrorFileTypeUnsupported (FileTypeUnsupported i supported))))
       (`libreoffice` d)
       (f (drop 1 (takeExtension i)))
 
 {- | Convert the zipped OpenDocument file being operated on (@.odt@, @.ods@,
 @.odp@ or @.odg@) to its flat form (@.fodt@, @.fods@, @.fodp@ or @.fodg@),
-into a directory. Fails with 'FileTypeUnsupported' for other files.
+into a directory. Fails with 'BuildErrorFileTypeUnsupported' for other files.
 
 @since 0.1.0.0
 -}
@@ -166,7 +170,7 @@
 
 {- | Convert the flat OpenDocument file being operated on (@.fodt@, @.fods@,
 @.fodp@ or @.fodg@) to its zipped form (@.odt@, @.ods@, @.odp@ or @.odg@),
-into a directory. Fails with 'FileTypeUnsupported' for other files.
+into a directory. Fails with 'BuildErrorFileTypeUnsupported' for other files.
 
 @since 0.1.0.0
 -}
@@ -184,7 +188,7 @@
 libreofficePages =
   do
     i <- requireInput
-    lift (withTempDirectory "libreoffice-pages" (\d -> view _Wrapped (libreofficeToPdf d ~> pdfPages) i))
+    lift (withTempDirectory "libreoffice-pages" (\d -> view (cloneLens pathFunction) (libreofficeToPdf d ~> pdfPages) i))
 
 {- | The number of pages in the Word file being operated on. See
 'libreofficePages'.
diff --git a/src/Document/Build/Name/CommandLineArguments.hs b/src/Document/Build/Name/CommandLineArguments.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/CommandLineArguments.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @commandLineArguments@, targeting @['String']@: the arguments of a command line.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.CommandLineArguments (
+  CommandLineArgumentsLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @commandLineArguments@.
+
+@since 0.2.0.0
+-}
+class CommandLineArgumentsLike p f x x' where
+  commandLineArguments ::
+    Optic p f x x' [String] [String]
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ [String]) => CommandLineArgumentsLike p f [String] x' where
+  commandLineArguments =
+    id
+  {-# INLINE commandLineArguments #-}
diff --git a/src/Document/Build/Name/CommandLineProgram.hs b/src/Document/Build/Name/CommandLineProgram.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/CommandLineProgram.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @commandLineProgram@, targeting 'FilePath': the program of a command line.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.CommandLineProgram (
+  CommandLineProgramLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @commandLineProgram@.
+
+@since 0.2.0.0
+-}
+class CommandLineProgramLike p f x x' where
+  commandLineProgram ::
+    Optic p f x x' FilePath FilePath
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ FilePath) => CommandLineProgramLike p f FilePath x' where
+  commandLineProgram =
+    id
+  {-# INLINE commandLineProgram #-}
diff --git a/src/Document/Build/Name/ContextProject.hs b/src/Document/Build/Name/ContextProject.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ContextProject.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @contextProject@, targeting 'String': the path of the project of a build, such as @"group/project"@.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ContextProject (
+  ContextProjectLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @contextProject@.
+
+@since 0.2.0.0
+-}
+class ContextProjectLike p f x x' where
+  contextProject ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => ContextProjectLike p f String x' where
+  contextProject =
+    id
+  {-# INLINE contextProject #-}
diff --git a/src/Document/Build/Name/Directory.hs b/src/Document/Build/Name/Directory.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/Directory.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @directory@, targeting 'FilePath': a directory that something is about.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.Directory (
+  DirectoryLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @directory@.
+
+@since 0.2.0.0
+-}
+class DirectoryLike p f x x' where
+  directory ::
+    Optic p f x x' FilePath FilePath
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ FilePath) => DirectoryLike p f FilePath x' where
+  directory =
+    id
+  {-# INLINE directory #-}
diff --git a/src/Document/Build/Name/ExpectedOutput.hs b/src/Document/Build/Name/ExpectedOutput.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ExpectedOutput.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @expectedOutput@, targeting 'String': a description of the output that was expected of a process.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ExpectedOutput (
+  ExpectedOutputLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @expectedOutput@.
+
+@since 0.2.0.0
+-}
+class ExpectedOutputLike p f x x' where
+  expectedOutput ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => ExpectedOutputLike p f String x' where
+  expectedOutput =
+    id
+  {-# INLINE expectedOutput #-}
diff --git a/src/Document/Build/Name/FileNotInGit.hs b/src/Document/Build/Name/FileNotInGit.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/FileNotInGit.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @fileNotInGit@, targeting 'FilePath': the constructor of a file that has no history in git.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.FileNotInGit (
+  FileNotInGitLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @fileNotInGit@.
+
+@since 0.2.0.0
+-}
+class FileNotInGitLike p f x x' where
+  fileNotInGit ::
+    Optic p f x x' FilePath FilePath
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ FilePath) => FileNotInGitLike p f FilePath x' where
+  fileNotInGit =
+    id
+  {-# INLINE fileNotInGit #-}
diff --git a/src/Document/Build/Name/FileNotUtf8.hs b/src/Document/Build/Name/FileNotUtf8.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/FileNotUtf8.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @fileNotUtf8@, targeting 'FilePath': the constructor of a file that is not valid UTF-8 text.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.FileNotUtf8 (
+  FileNotUtf8Like (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @fileNotUtf8@.
+
+@since 0.2.0.0
+-}
+class FileNotUtf8Like p f x x' where
+  fileNotUtf8 ::
+    Optic p f x x' FilePath FilePath
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ FilePath) => FileNotUtf8Like p f FilePath x' where
+  fileNotUtf8 =
+    id
+  {-# INLINE fileNotUtf8 #-}
diff --git a/src/Document/Build/Name/FontsDirectory.hs b/src/Document/Build/Name/FontsDirectory.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/FontsDirectory.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @fontsDirectory@, targeting @'Maybe' 'FilePath'@: the directory of fonts that LibreOffice uses during a build, if there is one.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.FontsDirectory (
+  FontsDirectoryLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @fontsDirectory@.
+
+@since 0.2.0.0
+-}
+class FontsDirectoryLike p f x x' where
+  fontsDirectory ::
+    Optic p f x x' (Maybe FilePath) (Maybe FilePath)
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Maybe FilePath) => FontsDirectoryLike p f (Maybe FilePath) x' where
+  fontsDirectory =
+    id
+  {-# INLINE fontsDirectory #-}
diff --git a/src/Document/Build/Name/GitAuthorEmail.hs b/src/Document/Build/Name/GitAuthorEmail.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitAuthorEmail.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitAuthorEmail@, targeting 'String': the email address of the author of a commit.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitAuthorEmail (
+  GitAuthorEmailLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @gitAuthorEmail@.
+
+@since 0.2.0.0
+-}
+class GitAuthorEmailLike p f x x' where
+  gitAuthorEmail ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => GitAuthorEmailLike p f String x' where
+  gitAuthorEmail =
+    id
+  {-# INLINE gitAuthorEmail #-}
diff --git a/src/Document/Build/Name/GitAuthorName.hs b/src/Document/Build/Name/GitAuthorName.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitAuthorName.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitAuthorName@, targeting 'String': the name of the author of a commit.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitAuthorName (
+  GitAuthorNameLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @gitAuthorName@.
+
+@since 0.2.0.0
+-}
+class GitAuthorNameLike p f x x' where
+  gitAuthorName ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => GitAuthorNameLike p f String x' where
+  gitAuthorName =
+    id
+  {-# INLINE gitAuthorName #-}
diff --git a/src/Document/Build/Name/GitAuthorTime.hs b/src/Document/Build/Name/GitAuthorTime.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitAuthorTime.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitAuthorTime@, targeting 'UTCTime': the time a commit was authored.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitAuthorTime (
+  GitAuthorTimeLike (..),
+) where
+
+import Control.Lens (Optic)
+import Data.Time (UTCTime)
+
+{- | A field or a constructor named @gitAuthorTime@.
+
+@since 0.2.0.0
+-}
+class GitAuthorTimeLike p f x x' where
+  gitAuthorTime ::
+    Optic p f x x' UTCTime UTCTime
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ UTCTime) => GitAuthorTimeLike p f UTCTime x' where
+  gitAuthorTime =
+    id
+  {-# INLINE gitAuthorTime #-}
diff --git a/src/Document/Build/Name/GitAuthors.hs b/src/Document/Build/Name/GitAuthors.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitAuthors.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitAuthors@, targeting @['String']@: the authors of every commit to change a file, as @Name \<email\>@, most recent first.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitAuthors (
+  GitAuthorsLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @gitAuthors@.
+
+@since 0.2.0.0
+-}
+class GitAuthorsLike p f x x' where
+  gitAuthors ::
+    Optic p f x x' [String] [String]
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ [String]) => GitAuthorsLike p f [String] x' where
+  gitAuthors =
+    id
+  {-# INLINE gitAuthors #-}
diff --git a/src/Document/Build/Name/GitCommitHash.hs b/src/Document/Build/Name/GitCommitHash.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitCommitHash.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitCommitHash@, targeting 'String': the hash of a commit.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitCommitHash (
+  GitCommitHashLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @gitCommitHash@.
+
+@since 0.2.0.0
+-}
+class GitCommitHashLike p f x x' where
+  gitCommitHash ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => GitCommitHashLike p f String x' where
+  gitCommitHash =
+    id
+  {-# INLINE gitCommitHash #-}
diff --git a/src/Document/Build/Name/GitCommitHashShort.hs b/src/Document/Build/Name/GitCommitHashShort.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitCommitHashShort.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitCommitHashShort@, targeting 'String': the abbreviated hash of a commit.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitCommitHashShort (
+  GitCommitHashShortLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @gitCommitHashShort@.
+
+@since 0.2.0.0
+-}
+class GitCommitHashShortLike p f x x' where
+  gitCommitHashShort ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => GitCommitHashShortLike p f String x' where
+  gitCommitHashShort =
+    id
+  {-# INLINE gitCommitHashShort #-}
diff --git a/src/Document/Build/Name/GitCommitTime.hs b/src/Document/Build/Name/GitCommitTime.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitCommitTime.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitCommitTime@, targeting 'UTCTime': the time a commit was committed.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitCommitTime (
+  GitCommitTimeLike (..),
+) where
+
+import Control.Lens (Optic)
+import Data.Time (UTCTime)
+
+{- | A field or a constructor named @gitCommitTime@.
+
+@since 0.2.0.0
+-}
+class GitCommitTimeLike p f x x' where
+  gitCommitTime ::
+    Optic p f x x' UTCTime UTCTime
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ UTCTime) => GitCommitTimeLike p f UTCTime x' where
+  gitCommitTime =
+    id
+  {-# INLINE gitCommitTime #-}
diff --git a/src/Document/Build/Name/GitCommitterEmail.hs b/src/Document/Build/Name/GitCommitterEmail.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitCommitterEmail.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitCommitterEmail@, targeting 'String': the email address of the committer of a commit.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitCommitterEmail (
+  GitCommitterEmailLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @gitCommitterEmail@.
+
+@since 0.2.0.0
+-}
+class GitCommitterEmailLike p f x x' where
+  gitCommitterEmail ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => GitCommitterEmailLike p f String x' where
+  gitCommitterEmail =
+    id
+  {-# INLINE gitCommitterEmail #-}
diff --git a/src/Document/Build/Name/GitCommitterName.hs b/src/Document/Build/Name/GitCommitterName.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitCommitterName.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitCommitterName@, targeting 'String': the name of the committer of a commit.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitCommitterName (
+  GitCommitterNameLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @gitCommitterName@.
+
+@since 0.2.0.0
+-}
+class GitCommitterNameLike p f x x' where
+  gitCommitterName ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => GitCommitterNameLike p f String x' where
+  gitCommitterName =
+    id
+  {-# INLINE gitCommitterName #-}
diff --git a/src/Document/Build/Name/GitSubject.hs b/src/Document/Build/Name/GitSubject.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/GitSubject.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @gitSubject@, targeting 'String': the subject of a commit.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.GitSubject (
+  GitSubjectLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @gitSubject@.
+
+@since 0.2.0.0
+-}
+class GitSubjectLike p f x x' where
+  gitSubject ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => GitSubjectLike p f String x' where
+  gitSubject =
+    id
+  {-# INLINE gitSubject #-}
diff --git a/src/Document/Build/Name/ImageColourspace.hs b/src/Document/Build/Name/ImageColourspace.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ImageColourspace.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @imageColourspace@, targeting 'String': the colourspace of an image, such as @sRGB@.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ImageColourspace (
+  ImageColourspaceLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @imageColourspace@.
+
+@since 0.2.0.0
+-}
+class ImageColourspaceLike p f x x' where
+  imageColourspace ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => ImageColourspaceLike p f String x' where
+  imageColourspace =
+    id
+  {-# INLINE imageColourspace #-}
diff --git a/src/Document/Build/Name/ImageDepth.hs b/src/Document/Build/Name/ImageDepth.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ImageDepth.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @imageDepth@, targeting 'Int': the bit depth of an image.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ImageDepth (
+  ImageDepthLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @imageDepth@.
+
+@since 0.2.0.0
+-}
+class ImageDepthLike p f x x' where
+  imageDepth ::
+    Optic p f x x' Int Int
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Int) => ImageDepthLike p f Int x' where
+  imageDepth =
+    id
+  {-# INLINE imageDepth #-}
diff --git a/src/Document/Build/Name/ImageFormat.hs b/src/Document/Build/Name/ImageFormat.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ImageFormat.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @imageFormat@, targeting 'String': the format of an image, such as @PNG@.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ImageFormat (
+  ImageFormatLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @imageFormat@.
+
+@since 0.2.0.0
+-}
+class ImageFormatLike p f x x' where
+  imageFormat ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => ImageFormatLike p f String x' where
+  imageFormat =
+    id
+  {-# INLINE imageFormat #-}
diff --git a/src/Document/Build/Name/ImageFrames.hs b/src/Document/Build/Name/ImageFrames.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ImageFrames.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @imageFrames@, targeting 'Int': the number of frames of an image.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ImageFrames (
+  ImageFramesLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @imageFrames@.
+
+@since 0.2.0.0
+-}
+class ImageFramesLike p f x x' where
+  imageFrames ::
+    Optic p f x x' Int Int
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Int) => ImageFramesLike p f Int x' where
+  imageFrames =
+    id
+  {-# INLINE imageFrames #-}
diff --git a/src/Document/Build/Name/ImageHeight.hs b/src/Document/Build/Name/ImageHeight.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ImageHeight.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @imageHeight@, targeting 'Int': the height of an image, in pixels.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ImageHeight (
+  ImageHeightLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @imageHeight@.
+
+@since 0.2.0.0
+-}
+class ImageHeightLike p f x x' where
+  imageHeight ::
+    Optic p f x x' Int Int
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Int) => ImageHeightLike p f Int x' where
+  imageHeight =
+    id
+  {-# INLINE imageHeight #-}
diff --git a/src/Document/Build/Name/ImageResolutionUnits.hs b/src/Document/Build/Name/ImageResolutionUnits.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ImageResolutionUnits.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @imageResolutionUnits@, targeting 'String': the units of the resolution of an image.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ImageResolutionUnits (
+  ImageResolutionUnitsLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @imageResolutionUnits@.
+
+@since 0.2.0.0
+-}
+class ImageResolutionUnitsLike p f x x' where
+  imageResolutionUnits ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => ImageResolutionUnitsLike p f String x' where
+  imageResolutionUnits =
+    id
+  {-# INLINE imageResolutionUnits #-}
diff --git a/src/Document/Build/Name/ImageResolutionX.hs b/src/Document/Build/Name/ImageResolutionX.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ImageResolutionX.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @imageResolutionX@, targeting 'Double': the horizontal resolution of an image.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ImageResolutionX (
+  ImageResolutionXLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @imageResolutionX@.
+
+@since 0.2.0.0
+-}
+class ImageResolutionXLike p f x x' where
+  imageResolutionX ::
+    Optic p f x x' Double Double
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Double) => ImageResolutionXLike p f Double x' where
+  imageResolutionX =
+    id
+  {-# INLINE imageResolutionX #-}
diff --git a/src/Document/Build/Name/ImageResolutionY.hs b/src/Document/Build/Name/ImageResolutionY.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ImageResolutionY.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @imageResolutionY@, targeting 'Double': the vertical resolution of an image.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ImageResolutionY (
+  ImageResolutionYLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @imageResolutionY@.
+
+@since 0.2.0.0
+-}
+class ImageResolutionYLike p f x x' where
+  imageResolutionY ::
+    Optic p f x x' Double Double
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Double) => ImageResolutionYLike p f Double x' where
+  imageResolutionY =
+    id
+  {-# INLINE imageResolutionY #-}
diff --git a/src/Document/Build/Name/ImageWidth.hs b/src/Document/Build/Name/ImageWidth.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ImageWidth.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @imageWidth@, targeting 'Int': the width of an image, in pixels.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ImageWidth (
+  ImageWidthLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @imageWidth@.
+
+@since 0.2.0.0
+-}
+class ImageWidthLike p f x x' where
+  imageWidth ::
+    Optic p f x x' Int Int
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Int) => ImageWidthLike p f Int x' where
+  imageWidth =
+    id
+  {-# INLINE imageWidth #-}
diff --git a/src/Document/Build/Name/InputDirectories.hs b/src/Document/Build/Name/InputDirectories.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/InputDirectories.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @inputDirectories@, targeting @['FilePath']@: the input directories of a build, whose files the rules run on, in order.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.InputDirectories (
+  InputDirectoriesLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @inputDirectories@.
+
+@since 0.2.0.0
+-}
+class InputDirectoriesLike p f x x' where
+  inputDirectories ::
+    Optic p f x x' [FilePath] [FilePath]
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ [FilePath]) => InputDirectoriesLike p f [FilePath] x' where
+  inputDirectories =
+    id
+  {-# INLINE inputDirectories #-}
diff --git a/src/Document/Build/Name/InputFileCount.hs b/src/Document/Build/Name/InputFileCount.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/InputFileCount.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @inputFileCount@, targeting 'Int': the number of input files that a directory has.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.InputFileCount (
+  InputFileCountLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @inputFileCount@.
+
+@since 0.2.0.0
+-}
+class InputFileCountLike p f x x' where
+  inputFileCount ::
+    Optic p f x x' Int Int
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Int) => InputFileCountLike p f Int x' where
+  inputFileCount =
+    id
+  {-# INLINE inputFileCount #-}
diff --git a/src/Document/Build/Name/InputFileMissing.hs b/src/Document/Build/Name/InputFileMissing.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/InputFileMissing.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @inputFileMissing@, targeting 'FilePath': the constructor of an input file that does not exist.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.InputFileMissing (
+  InputFileMissingLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @inputFileMissing@.
+
+@since 0.2.0.0
+-}
+class InputFileMissingLike p f x x' where
+  inputFileMissing ::
+    Optic p f x x' FilePath FilePath
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ FilePath) => InputFileMissingLike p f FilePath x' where
+  inputFileMissing =
+    id
+  {-# INLINE inputFileMissing #-}
diff --git a/src/Document/Build/Name/InputFilesNeeded.hs b/src/Document/Build/Name/InputFilesNeeded.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/InputFilesNeeded.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @inputFilesNeeded@, targeting 'Int': the number of input files that a command needs.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.InputFilesNeeded (
+  InputFilesNeededLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @inputFilesNeeded@.
+
+@since 0.2.0.0
+-}
+class InputFilesNeededLike p f x x' where
+  inputFilesNeeded ::
+    Optic p f x x' Int Int
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Int) => InputFilesNeededLike p f Int x' where
+  inputFilesNeeded =
+    id
+  {-# INLINE inputFilesNeeded #-}
diff --git a/src/Document/Build/Name/MissingStrings.hs b/src/Document/Build/Name/MissingStrings.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/MissingStrings.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @missingStrings@, targeting @'NonEmpty' 'String'@: the strings that a file does not contain.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.MissingStrings (
+  MissingStringsLike (..),
+) where
+
+import Control.Lens (Optic)
+import Data.List.NonEmpty (NonEmpty)
+
+{- | A field or a constructor named @missingStrings@.
+
+@since 0.2.0.0
+-}
+class MissingStringsLike p f x x' where
+  missingStrings ::
+    Optic p f x x' (NonEmpty String) (NonEmpty String)
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ NonEmpty String) => MissingStringsLike p f (NonEmpty String) x' where
+  missingStrings =
+    id
+  {-# INLINE missingStrings #-}
diff --git a/src/Document/Build/Name/OutputDirectory.hs b/src/Document/Build/Name/OutputDirectory.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/OutputDirectory.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @outputDirectory@, targeting 'FilePath': the output directory of a build, which it removes and writes to.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.OutputDirectory (
+  OutputDirectoryLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @outputDirectory@.
+
+@since 0.2.0.0
+-}
+class OutputDirectoryLike p f x x' where
+  outputDirectory ::
+    Optic p f x x' FilePath FilePath
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ FilePath) => OutputDirectoryLike p f FilePath x' where
+  outputDirectory =
+    id
+  {-# INLINE outputDirectory #-}
diff --git a/src/Document/Build/Name/OutputGitDetails.hs b/src/Document/Build/Name/OutputGitDetails.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/OutputGitDetails.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @outputGitDetails@, targeting @'Maybe' 'Document.Build.GitDetails.GitDetails'@: the details of the last commit to change the source of an output file, if it is known.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.OutputGitDetails (
+  OutputGitDetailsLike (..),
+) where
+
+import Control.Lens (Optic)
+import Document.Build.GitDetails (GitDetails)
+
+{- | A field or a constructor named @outputGitDetails@.
+
+@since 0.2.0.0
+-}
+class OutputGitDetailsLike p f x x' where
+  outputGitDetails ::
+    Optic p f x x' (Maybe GitDetails) (Maybe GitDetails)
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Maybe GitDetails) => OutputGitDetailsLike p f (Maybe GitDetails) x' where
+  outputGitDetails =
+    id
+  {-# INLINE outputGitDetails #-}
diff --git a/src/Document/Build/Name/OutputImageInfo.hs b/src/Document/Build/Name/OutputImageInfo.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/OutputImageInfo.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @outputImageInfo@, targeting @'Maybe' 'Document.Build.ImageInfo.ImageInfo'@: information about an output file, if it is an image.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.OutputImageInfo (
+  OutputImageInfoLike (..),
+) where
+
+import Control.Lens (Optic)
+import Document.Build.ImageInfo (ImageInfo)
+
+{- | A field or a constructor named @outputImageInfo@.
+
+@since 0.2.0.0
+-}
+class OutputImageInfoLike p f x x' where
+  outputImageInfo ::
+    Optic p f x x' (Maybe ImageInfo) (Maybe ImageInfo)
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Maybe ImageInfo) => OutputImageInfoLike p f (Maybe ImageInfo) x' where
+  outputImageInfo =
+    id
+  {-# INLINE outputImageInfo #-}
diff --git a/src/Document/Build/Name/OutputPages.hs b/src/Document/Build/Name/OutputPages.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/OutputPages.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @outputPages@, targeting @'Maybe' 'Int'@: the number of pages of an output file, if it is a PDF file.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.OutputPages (
+  OutputPagesLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @outputPages@.
+
+@since 0.2.0.0
+-}
+class OutputPagesLike p f x x' where
+  outputPages ::
+    Optic p f x x' (Maybe Int) (Maybe Int)
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Maybe Int) => OutputPagesLike p f (Maybe Int) x' where
+  outputPages =
+    id
+  {-# INLINE outputPages #-}
diff --git a/src/Document/Build/Name/OutputPath.hs b/src/Document/Build/Name/OutputPath.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/OutputPath.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @outputPath@, targeting 'FilePath': the path of an output file.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.OutputPath (
+  OutputPathLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @outputPath@.
+
+@since 0.2.0.0
+-}
+class OutputPathLike p f x x' where
+  outputPath ::
+    Optic p f x x' FilePath FilePath
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ FilePath) => OutputPathLike p f FilePath x' where
+  outputPath =
+    id
+  {-# INLINE outputPath #-}
diff --git a/src/Document/Build/Name/OutputSha256.hs b/src/Document/Build/Name/OutputSha256.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/OutputSha256.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @outputSha256@, targeting 'String': the SHA-256 hash of an output file.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.OutputSha256 (
+  OutputSha256Like (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @outputSha256@.
+
+@since 0.2.0.0
+-}
+class OutputSha256Like p f x x' where
+  outputSha256 ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => OutputSha256Like p f String x' where
+  outputSha256 =
+    id
+  {-# INLINE outputSha256 #-}
diff --git a/src/Document/Build/Name/ShallowRepository.hs b/src/Document/Build/Name/ShallowRepository.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/ShallowRepository.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @shallowRepository@, targeting 'FilePath': the constructor of a directory whose git history is shallow.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.ShallowRepository (
+  ShallowRepositoryLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @shallowRepository@.
+
+@since 0.2.0.0
+-}
+class ShallowRepositoryLike p f x x' where
+  shallowRepository ::
+    Optic p f x x' FilePath FilePath
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ FilePath) => ShallowRepositoryLike p f FilePath x' where
+  shallowRepository =
+    id
+  {-# INLINE shallowRepository #-}
diff --git a/src/Document/Build/Name/StandardError.hs b/src/Document/Build/Name/StandardError.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/StandardError.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @standardError@, targeting 'String': the standard error of a process.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.StandardError (
+  StandardErrorLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @standardError@.
+
+@since 0.2.0.0
+-}
+class StandardErrorLike p f x x' where
+  standardError ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => StandardErrorLike p f String x' where
+  standardError =
+    id
+  {-# INLINE standardError #-}
diff --git a/src/Document/Build/Name/StandardOutput.hs b/src/Document/Build/Name/StandardOutput.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/StandardOutput.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @standardOutput@, targeting 'String': the standard output of a process.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.StandardOutput (
+  StandardOutputLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @standardOutput@.
+
+@since 0.2.0.0
+-}
+class StandardOutputLike p f x x' where
+  standardOutput ::
+    Optic p f x x' String String
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ String) => StandardOutputLike p f String x' where
+  standardOutput =
+    id
+  {-# INLINE standardOutput #-}
diff --git a/src/Document/Build/Name/SupportedExtensions.hs b/src/Document/Build/Name/SupportedExtensions.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/SupportedExtensions.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @supportedExtensions@, targeting @['String']@: the file extensions that are supported.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.SupportedExtensions (
+  SupportedExtensionsLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @supportedExtensions@.
+
+@since 0.2.0.0
+-}
+class SupportedExtensionsLike p f x x' where
+  supportedExtensions ::
+    Optic p f x x' [String] [String]
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ [String]) => SupportedExtensionsLike p f [String] x' where
+  supportedExtensions =
+    id
+  {-# INLINE supportedExtensions #-}
diff --git a/src/Document/Build/Name/TargetComplete.hs b/src/Document/Build/Name/TargetComplete.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/TargetComplete.hs
@@ -0,0 +1,35 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @targetComplete@, targeting @'Map' 'FilePath' 'System.FilePather.Trail.Trail'@: the constructor of the target of a build that is complete, with every file it knows.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.TargetComplete (
+  TargetCompleteLike (..),
+) where
+
+import Control.Lens (Optic)
+import Data.Map (Map)
+import System.FilePather.Trail (Trail)
+
+{- | A field or a constructor named @targetComplete@.
+
+@since 0.2.0.0
+-}
+class TargetCompleteLike p f x x' where
+  targetComplete ::
+    Optic p f x x' (Map FilePath Trail) (Map FilePath Trail)
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Map FilePath Trail) => TargetCompleteLike p f (Map FilePath Trail) x' where
+  targetComplete =
+    id
+  {-# INLINE targetComplete #-}
diff --git a/src/Document/Build/Name/TargetDirectory.hs b/src/Document/Build/Name/TargetDirectory.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/TargetDirectory.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @targetDirectory@, targeting @()@: the constructor of a target that is a directory of the output directory.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.TargetDirectory (
+  TargetDirectoryLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @targetDirectory@.
+
+@since 0.2.0.0
+-}
+class TargetDirectoryLike p f x x' where
+  targetDirectory ::
+    Optic p f x x' () ()
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ ()) => TargetDirectoryLike p f () x' where
+  targetDirectory =
+    id
+  {-# INLINE targetDirectory #-}
diff --git a/src/Document/Build/Name/TargetFile.hs b/src/Document/Build/Name/TargetFile.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Name/TargetFile.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The name @targetFile@, targeting @()@: the constructor of a target that is a file.
+
+Any value of the target type has the name, as the identity.
+
+@since 0.2.0.0
+-}
+module Document.Build.Name.TargetFile (
+  TargetFileLike (..),
+) where
+
+import Control.Lens (Optic)
+
+{- | A field or a constructor named @targetFile@.
+
+@since 0.2.0.0
+-}
+class TargetFileLike p f x x' where
+  targetFile ::
+    Optic p f x x' () ()
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ ()) => TargetFileLike p f () x' where
+  targetFile =
+    id
+  {-# INLINE targetFile #-}
diff --git a/src/Document/Build/Operations.hs b/src/Document/Build/Operations.hs
--- a/src/Document/Build/Operations.hs
+++ b/src/Document/Build/Operations.hs
@@ -18,10 +18,10 @@
   form (@.fodt@, @.fods@, @.fodp@, @.fodg@), and back: 'libreofficeFlat',
   'libreofficeZipped'
 * a PDF file split into several PDF files: 'pdfSplit', 'pdfBurst'
-* the git details of a file, ready to insert into a file: 'gitDetails',
+* the git details of a file, ready to insert into a file: 'readGitDetails',
   'gitPlaceholders'
 * each first string replaced with the second in a file: 'replaceStrings'
-* the format, width, height and other details of an image: 'imageInfo'
+* the format, width, height and other details of an image: 'readImageInfo'
 * the PDF files of a directory joined into one PDF file: 'pdfJoin',
   'pdfConcatenate', 'pdftk'
 * images of a directory joined side by side: 'imageAppend'
diff --git a/src/Document/Build/Output.hs b/src/Document/Build/Output.hs
--- a/src/Document/Build/Output.hs
+++ b/src/Document/Build/Output.hs
@@ -1,9 +1,4 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_GHC -Wall #-}
 
@@ -14,18 +9,6 @@
 concatenated from several PDF files, appears once, under the first of them.
 -}
 module Document.Build.Output (
-  -- * Output files
-  OutputFile (..),
-  outputPath,
-  outputSha256,
-  outputGitDetails,
-  outputPages,
-  outputImageInfo,
-  GetOutputFile (..),
-  HasOutputFile (..),
-  ReviewOutputFile (..),
-  AsOutputFile (..),
-
   -- * Trees
   Edges,
   edgeForest,
@@ -42,15 +25,13 @@
   outputForestHtml,
 ) where
 
-import Control.DeepSeq (NFData)
-import Control.Lens (Field1, Field2, Field3, Field4, Field5, Getter, Lens', Prism', Review, lens, prism', review, unto, view, _1, _2, _3, _4, _5, _Wrapped)
-import Data.Aeson (FromJSON (..), ToJSON (..), Value (..), eitherDecodeFileStrict, object, withObject, (.:), (.:?), (.=))
+import Control.Lens (cloneLens, view)
+import Data.Aeson (FromJSON (..), ToJSON (..), Value (..), eitherDecodeFileStrict, withObject, (.:))
 import Data.Aeson.Encode.Pretty (encodePretty)
 import qualified Data.Aeson.KeyMap as KeyMap
 import Data.Aeson.Types (Parser, parseEither)
 import qualified Data.ByteString.Lazy as LazyByteString
 import Data.Containers.ListUtils (nubOrd)
-import Data.Data (Data)
 import Data.Functor (($>))
 import Data.List (isPrefixOf, sort)
 import qualified Data.Map as Map
@@ -60,141 +41,31 @@
 import Data.Tree (Forest, Tree (..), flatten)
 import Document.Build.Build (Build, attempt, createParentDirectory, io)
 import Document.Build.Error (ReviewBuildError)
-import Document.Build.Git (GitDetails, formatTimeUtc, gitCommitHashShort, gitCommitTime, gitDetails)
+import Document.Build.Git (formatTimeUtc, readGitDetails)
+import Document.Build.GitDetails (GitDetails)
 import Document.Build.Hash (sha256)
-import Document.Build.Image (ImageInfo (..), imageInfo)
+import Document.Build.Image (readImageInfo)
+import Document.Build.Name.GitCommitHashShort (GitCommitHashShortLike (..))
+import Document.Build.Name.GitCommitTime (GitCommitTimeLike (..))
+import Document.Build.Name.ImageFormat (ImageFormatLike (..))
+import Document.Build.Name.ImageHeight (ImageHeightLike (..))
+import Document.Build.Name.ImageWidth (ImageWidthLike (..))
+import Document.Build.Name.OutputGitDetails (OutputGitDetailsLike (..))
+import Document.Build.Name.OutputImageInfo (OutputImageInfoLike (..))
+import Document.Build.Name.OutputPages (OutputPagesLike (..))
+import Document.Build.Name.OutputPath (OutputPathLike (..))
+import Document.Build.Name.OutputSha256 (OutputSha256Like (..))
+import Document.Build.OutputFile (OutputFile (..))
 import Document.Build.Pdf (pdfPages)
 import Document.Build.Replace (xmlEscape)
-import GHC.Generics (Generic)
 import System.FilePath (makeRelative, normalise, splitDirectories)
 import System.FilePather.Combinators (hasExtension)
+import System.FilePather.Name.PathFunction (PathFunctionLike (..))
 
 {- $setup
 >>> import Data.Tree (drawForest)
 -}
 
-{- | An output file: its path, its SHA-256 hash, the git details of the source
-file at the root of its tree, its number of pages if it is a PDF file, and
-information about it if it is an image.
-
-@since 0.1.0.0
--}
-data OutputFile
-  = OutputFile FilePath String (Maybe GitDetails) (Maybe Int) (Maybe ImageInfo)
-  deriving stock (Eq, Ord, Show, Generic, Data)
-  deriving anyclass (NFData)
-
--- | @since 0.1.0.0
-instance Field1 OutputFile OutputFile FilePath FilePath
-
--- | @since 0.1.0.0
-instance Field2 OutputFile OutputFile String String
-
--- | @since 0.1.0.0
-instance Field3 OutputFile OutputFile (Maybe GitDetails) (Maybe GitDetails)
-
--- | @since 0.1.0.0
-instance Field4 OutputFile OutputFile (Maybe Int) (Maybe Int)
-
--- | @since 0.1.0.0
-instance Field5 OutputFile OutputFile (Maybe ImageInfo) (Maybe ImageInfo)
-
--- | @since 0.1.0.0
-instance ToJSON OutputFile where
-  toJSON (OutputFile p h g n i) =
-    object ["path" .= p, "sha256" .= h, "git" .= g, "pages" .= n, "image" .= i]
-
--- | @since 0.1.0.0
-instance FromJSON OutputFile where
-  parseJSON =
-    withObject "OutputFile" $ \o ->
-      OutputFile <$> o .: "path" <*> o .: "sha256" <*> o .:? "git" <*> o .:? "pages" <*> o .:? "image"
-
--- | @since 0.1.0.0
-outputPath :: Lens' OutputFile FilePath
-outputPath =
-  _1
-{-# INLINE outputPath #-}
-
--- | @since 0.1.0.0
-outputSha256 :: Lens' OutputFile String
-outputSha256 =
-  _2
-{-# INLINE outputSha256 #-}
-
-{- | The git details of the source file at the root of the tree of the file.
-
-@since 0.1.0.0
--}
-outputGitDetails :: Lens' OutputFile (Maybe GitDetails)
-outputGitDetails =
-  _3
-{-# INLINE outputGitDetails #-}
-
--- | @since 0.1.0.0
-outputPages :: Lens' OutputFile (Maybe Int)
-outputPages =
-  _4
-{-# INLINE outputPages #-}
-
--- | @since 0.1.0.0
-outputImageInfo :: Lens' OutputFile (Maybe ImageInfo)
-outputImageInfo =
-  _5
-{-# INLINE outputImageInfo #-}
-
--- | @since 0.1.0.0
-class GetOutputFile s where
-  getOutputFile :: Getter s OutputFile
-
--- | @since 0.1.0.0
-instance GetOutputFile OutputFile where
-  getOutputFile =
-    id
-  {-# INLINE getOutputFile #-}
-
--- | @since 0.1.0.0
-class (GetOutputFile s) => HasOutputFile s where
-  {-# MINIMAL setOutputFile #-}
-  setOutputFile :: OutputFile -> s -> s
-
-  outputFile :: Lens' s OutputFile
-  outputFile =
-    lens (view getOutputFile) (flip setOutputFile)
-  {-# INLINE outputFile #-}
-
--- | @since 0.1.0.0
-instance HasOutputFile OutputFile where
-  setOutputFile =
-    const
-  {-# INLINE setOutputFile #-}
-
--- | @since 0.1.0.0
-class ReviewOutputFile t where
-  reviewOutputFile :: Review t OutputFile
-
--- | @since 0.1.0.0
-instance ReviewOutputFile OutputFile where
-  reviewOutputFile =
-    unto id
-  {-# INLINE reviewOutputFile #-}
-
--- | @since 0.1.0.0
-class (ReviewOutputFile t) => AsOutputFile t where
-  {-# MINIMAL matchOutputFile #-}
-  matchOutputFile :: t -> Maybe OutputFile
-
-  _OutputFile :: Prism' t OutputFile
-  _OutputFile =
-    prism' (review reviewOutputFile) matchOutputFile
-  {-# INLINE _OutputFile #-}
-
--- | @since 0.1.0.0
-instance AsOutputFile OutputFile where
-  matchOutputFile =
-    Just
-  {-# INLINE matchOutputFile #-}
-
 {- | The files a build made: each input file path, and the file paths that
 were made from it.
 
@@ -260,7 +131,7 @@
 describeForest =
   traverse
     ( \t@(Node r _) ->
-        attempt (view _Wrapped gitDetails r) >>= \g -> traverse (describeFile g) t
+        attempt (view (cloneLens pathFunction) readGitDetails r) >>= \g -> traverse (describeFile g) t
     )
 
 {- | Describe a file, with the given git details: its hash, and its page count
@@ -271,9 +142,9 @@
 describeFile :: (ReviewBuildError e) => Maybe GitDetails -> FilePath -> Build e OutputFile
 describeFile g p =
   do
-    h <- view _Wrapped sha256 p
-    n <- if hasExtension ["pdf"] p then attempt (view _Wrapped pdfPages p) else pure Nothing
-    i <- if hasExtension ["png", "jpg", "jpeg", "gif", "svg", "webp", "tif", "tiff", "bmp"] p then attempt (view _Wrapped imageInfo p) else pure Nothing
+    h <- view (cloneLens pathFunction) sha256 p
+    n <- if hasExtension ["pdf"] p then attempt (view (cloneLens pathFunction) pdfPages p) else pure Nothing
+    i <- if hasExtension ["png", "jpg", "jpeg", "gif", "svg", "webp", "tif", "tiff", "bmp"] p then attempt (view (cloneLens pathFunction) readImageInfo p) else pure Nothing
     pure (OutputFile p h g n i)
 
 {- | A forest of output files as JSON: an array of trees, where each tree is
@@ -334,15 +205,15 @@
         if within p
           then let r = makeRelative dir p in "<a href=\"" <> xmlEscape r <> "\">" <> xmlEscape r <> "</a>"
           else "<code>" <> xmlEscape p <> "</code>"
-      details (OutputFile _ h g n i) =
+      details o =
         let parts =
-              maybe [] (\pages -> [show pages <> if pages == 1 then " page" else " pages"]) n
-                <> maybe [] (\(ImageInfo fmt w ht _ _ _ _ _ _) -> [show w <> "×" <> show ht <> " " <> fmt]) i
-                <> ["sha256 " <> take 12 h]
-                <> maybe [] (\d -> ["commit " <> view gitCommitHashShort d <> " " <> formatTimeUtc (view gitCommitTime d)]) g
+              maybe [] (\pages -> [show pages <> if pages == 1 then " page" else " pages"]) (view (cloneLens outputPages) o)
+                <> maybe [] (\i -> [show (view (cloneLens imageWidth) i) <> "×" <> show (view (cloneLens imageHeight) i) <> " " <> view (cloneLens imageFormat) i]) (view (cloneLens outputImageInfo) o)
+                <> ["sha256 " <> take 12 (view (cloneLens outputSha256) o)]
+                <> maybe [] (\d -> ["commit " <> view (cloneLens gitCommitHashShort) d <> " " <> formatTimeUtc (view (cloneLens gitCommitTime) d)]) (view (cloneLens outputGitDetails) o)
          in " <small>(" <> xmlEscape (concatWith "; " parts) <> ")</small>"
-      tree (Node a@(OutputFile p _ _ _ _) cs) =
-        "<li>" <> name p <> details a <> list cs <> "</li>\n"
+      tree (Node a cs) =
+        "<li>" <> name (view (cloneLens outputPath) a) <> details a <> list cs <> "</li>\n"
       list cs =
         if null cs then "" else "\n<ul>\n" <> concatMap tree cs <> "</ul>\n"
       concatWith s xs =
diff --git a/src/Document/Build/OutputFile.hs b/src/Document/Build/OutputFile.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/OutputFile.hs
@@ -0,0 +1,164 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | A file of the output tree of a build.
+
+A 'OutputFile' is a product: its fields are named 'outputPath', 'outputSha256', 'outputGitDetails', 'outputPages' and 'outputImageInfo'.
+-}
+module Document.Build.OutputFile (
+  -- * Type
+  OutputFile (..),
+
+  -- * Optics
+  OutputFileLike (..),
+  lawNamesOutputFile,
+  InheritOutputFile,
+  inheritOutputFile,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Optic, cloneLens, view)
+import Data.Aeson (FromJSON (..), ToJSON (..), object, withObject, (.:), (.:?), (.=))
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Document.Build.GitDetails (GitDetails)
+import Document.Build.ImageInfo (ImageInfo)
+import Document.Build.Name.OutputGitDetails (OutputGitDetailsLike (..))
+import Document.Build.Name.OutputImageInfo (OutputImageInfoLike (..))
+import Document.Build.Name.OutputPages (OutputPagesLike (..))
+import Document.Build.Name.OutputPath (OutputPathLike (..))
+import Document.Build.Name.OutputSha256 (OutputSha256Like (..))
+import GHC.Generics (Generic)
+
+{- | An output file: its path ('outputPath'), its SHA-256 hash
+('outputSha256'), the git details of the source file at the root of its tree
+('outputGitDetails'), its number of pages if it is a PDF file
+('outputPages'), and information about it if it is an image
+('outputImageInfo').
+
+@since 0.1.0.0
+-}
+data OutputFile
+  = OutputFile FilePath String (Maybe GitDetails) (Maybe Int) (Maybe ImageInfo)
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+-- | @since 0.1.0.0
+instance ToJSON OutputFile where
+  toJSON (OutputFile p h g n i) =
+    object ["path" .= p, "sha256" .= h, "git" .= g, "pages" .= n, "image" .= i]
+
+-- | @since 0.1.0.0
+instance FromJSON OutputFile where
+  parseJSON =
+    withObject "OutputFile" $ \o ->
+      OutputFile <$> o .: "path" <*> o .: "sha256" <*> o .:? "git" <*> o .:? "pages" <*> o .:? "image"
+
+{- | The class of 'OutputFile'.
+
+@since 0.2.0.0
+-}
+class OutputFileLike p f x x' where
+  outputFile ::
+    Optic p f x x' OutputFile OutputFile
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ OutputFile) => OutputFileLike p f OutputFile x' where
+  outputFile =
+    id
+  {-# INLINE outputFile #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputFile) => OutputPathLike p f OutputFile x' where
+  outputPath f (OutputFile x1 x2 x3 x4 x5) =
+    fmap (\x1' -> OutputFile x1' x2 x3 x4 x5) (f x1)
+  {-# INLINE outputPath #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputFile) => OutputSha256Like p f OutputFile x' where
+  outputSha256 f (OutputFile x1 x2 x3 x4 x5) =
+    fmap (\x2' -> OutputFile x1 x2' x3 x4 x5) (f x2)
+  {-# INLINE outputSha256 #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputFile) => OutputGitDetailsLike p f OutputFile x' where
+  outputGitDetails f (OutputFile x1 x2 x3 x4 x5) =
+    fmap (\x3' -> OutputFile x1 x2 x3' x4 x5) (f x3)
+  {-# INLINE outputGitDetails #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputFile) => OutputPagesLike p f OutputFile x' where
+  outputPages f (OutputFile x1 x2 x3 x4 x5) =
+    fmap (\x4' -> OutputFile x1 x2 x3 x4' x5) (f x4)
+  {-# INLINE outputPages #-}
+
+{- | Lens
+
+@since 0.2.0.0
+-}
+instance (p ~ (->), Functor f, x' ~ OutputFile) => OutputImageInfoLike p f OutputFile x' where
+  outputImageInfo f (OutputFile x1 x2 x3 x4 x5) =
+    fmap (OutputFile x1 x2 x3 x4) (f x5)
+  {-# INLINE outputImageInfo #-}
+
+{- | Rebuild a value using only the name of each field.
+
+Laws:
+
+> lawNamesOutputFile == id
+
+@since 0.2.0.0
+-}
+lawNamesOutputFile :: OutputFile -> OutputFile
+lawNamesOutputFile s =
+  OutputFile
+    (view (cloneLens outputPath) s)
+    (view (cloneLens outputSha256) s)
+    (view (cloneLens outputGitDetails) s)
+    (view (cloneLens outputPages) s)
+    (view (cloneLens outputImageInfo) s)
+
+{- | The classes that a data type holding 'OutputFile' inherits.
+
+@since 0.2.0.0
+-}
+type InheritOutputFile s =
+  ( OutputFileLike (->) (Const ()) s s
+  , OutputPathLike (->) (Const ()) s s
+  , OutputSha256Like (->) (Const ()) s s
+  , OutputGitDetailsLike (->) (Const ()) s s
+  , OutputPagesLike (->) (Const ()) s s
+  , OutputImageInfoLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'OutputFile'.
+
+@since 0.2.0.0
+-}
+inheritOutputFile :: (InheritOutputFile s) => proxy s -> ()
+inheritOutputFile _ =
+  ()
diff --git a/src/Document/Build/Pandoc.hs b/src/Document/Build/Pandoc.hs
--- a/src/Document/Build/Pandoc.hs
+++ b/src/Document/Build/Pandoc.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 -- | Commands that convert documents with @pandoc@.
@@ -12,7 +13,8 @@
 
 import Control.Monad.Trans.Class (lift)
 import Document.Build.Build (Command, createParentDirectory, expectOutput, process, requireInput)
-import Document.Build.Error (CommandLine (..), ReviewBuildError)
+import Document.Build.CommandLine (CommandLine (..))
+import Document.Build.Error (ReviewBuildError)
 import System.FilePath (takeBaseName, (<.>), (</>))
 
 {- | Convert the file being operated on with @pandoc@ and its options, writing
diff --git a/src/Document/Build/Pdf.hs b/src/Document/Build/Pdf.hs
--- a/src/Document/Build/Pdf.hs
+++ b/src/Document/Build/Pdf.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 {- | Commands that operate on PDF files.
@@ -31,17 +32,21 @@
   pageImagePaths,
 ) where
 
-import Control.Lens (preview, view, _Wrapped, _head)
+import Control.Lens (cloneLens, preview, view, _head)
 import Control.Monad.Trans.Class (lift)
 import Data.List (sort)
 import Data.Maybe (mapMaybe)
 import qualified Data.Text as Text
 import Document.Build.Build (Command, createParentDirectory, expectOutput, failBuild, io, process, processIn, requireDirectory, requireInput)
-import Document.Build.Error (BuildError (..), CommandLine (..), ReviewBuildError)
+import Document.Build.CommandLine (CommandLine (..))
+import Document.Build.Error (BuildError (..), ReviewBuildError)
+import Document.Build.Error.InputFilesTooFew (InputFilesTooFew (..))
+import Document.Build.Error.ProcessOutputUnrecognised (ProcessOutputUnrecognised (..))
 import Document.Build.File (writeUtf8)
 import System.Directory (createDirectoryIfMissing, doesFileExist, listDirectory, makeAbsolute, removePathForcibly)
 import System.FilePath (equalFilePath, splitExtension, takeBaseName, takeDirectory, takeFileName, (<.>), (</>))
 import System.FilePather.Combinators (hasExtension, onFile)
+import System.FilePather.Name.PathFunction (PathFunctionLike (..))
 import Text.Read (readMaybe)
 
 {- | The number of pages in the PDF file being operated on, using @pdfinfo@.
@@ -55,7 +60,7 @@
     let c = CommandLine "pdfinfo" [i]
     o <- lift (process c)
     maybe
-      (lift (failBuild (ProcessOutputUnrecognised c "a line \"Pages: <number>\"")))
+      (lift (failBuild (BuildErrorProcessOutputUnrecognised (ProcessOutputUnrecognised c "a line \"Pages: <number>\""))))
       pure
       (parsePdfinfoPages o)
 
@@ -152,7 +157,7 @@
 {- | Join the PDF files in the directory being operated on, in sorted order of
 their names, into a file path, and return the file path. The directory must
 have at least two PDF files, other than the output file, or the build fails
-with 'InputFilesTooFew'. Files in subdirectories are not joined.
+with 'BuildErrorInputFilesTooFew'. Files in subdirectories are not joined.
 
 @since 0.1.0.0
 -}
@@ -167,7 +172,7 @@
           files <- traverse (\n -> (\x -> [n | x]) <$> doesFileExist (d </> n)) names
           pure (concatMap (filter (not . equalFilePath o . (d </>))) files)
     if length fs < 2
-      then lift (failBuild (InputFilesTooFew d (length fs) 2))
+      then lift (failBuild (BuildErrorInputFilesTooFew (InputFilesTooFew d (length fs) 2)))
       else pdfConcatenate fs o
 
 {- | Concatenate PDF files in the directory being operated on, given by their
@@ -179,7 +184,7 @@
 pdfConcatenate fs o =
   do
     d <- requireDirectory
-    _ <- lift (traverse (view _Wrapped requireInput . (d </>)) fs)
+    _ <- lift (traverse (view (cloneLens pathFunction) requireInput . (d </>)) fs)
     pdftk fs ["cat"] o
 
 {- | Run @pdftk@ in the directory being operated on, with input arguments and
diff --git a/src/Document/Build/Project.hs b/src/Document/Build/Project.hs
--- a/src/Document/Build/Project.hs
+++ b/src/Document/Build/Project.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 {- | Steps for building a whole project: identifying it, replacing
@@ -45,9 +46,11 @@
 import Data.Time (UTCTime)
 import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
 import Document.Build.Build (Build, Command, io, process, say, withTempDirectory)
-import Document.Build.Error (CommandLine (..), ReviewBuildError)
+import Document.Build.CommandLine (CommandLine (..))
+import Document.Build.Error (ReviewBuildError)
 import Document.Build.File (copyTo, writeUtf8)
-import Document.Build.Git (GitDetails (..), gitDetails, gitLastCommit, gitPlaceholders)
+import Document.Build.Git (gitLastCommit, gitPlaceholders, readGitDetails)
+import Document.Build.GitDetails (GitDetails (..))
 import Document.Build.Replace (replaceStrings, xmlEscape)
 import System.Directory (doesDirectoryExist, makeAbsolute, removePathForcibly)
 import System.Environment (lookupEnv, setEnv, unsetEnv)
@@ -165,7 +168,7 @@
     gitPlaceholders
       <$> ( gitLastCommit
               >>= maybe
-                (lift (say ("warning: " <> p <> " has no git history, so its placeholders are of the last commit of the repository")) *> onFile "." gitDetails)
+                (lift (say ("warning: " <> p <> " has no git history, so its placeholders are of the last commit of the repository")) *> onFile "." readGitDetails)
                 pure
           )
 
diff --git a/src/Document/Build/Replace.hs b/src/Document/Build/Replace.hs
--- a/src/Document/Build/Replace.hs
+++ b/src/Document/Build/Replace.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleContexts #-}
 {-# OPTIONS_GHC -Wall #-}
 
 {- | Commands that replace strings in text files, such as placeholders for
@@ -22,6 +23,7 @@
 import qualified Data.Text as Text
 import Document.Build.Build (Command, failBuild, requireInput)
 import Document.Build.Error (BuildError (..), ReviewBuildError)
+import Document.Build.Error.StringsMissing (StringsMissing (..))
 import Document.Build.File (readUtf8, writeUtf8)
 
 {- $setup
@@ -62,7 +64,7 @@
     i <- requireInput
     lift (readUtf8 i >>= writeUtf8 o . replaceText (fmap (bimap Text.pack Text.pack) rs))
 
-{- | Fail with 'StringsMissing' unless the file being operated on contains
+{- | Fail with 'BuildErrorStringsMissing' unless the file being operated on contains
 every one of the given strings.
 
 @since 0.1.0.0
@@ -74,7 +76,7 @@
     t <- lift (readUtf8 i)
     maybe
       (pure ())
-      (lift . failBuild . StringsMissing i)
+      (lift . failBuild . BuildErrorStringsMissing . StringsMissing i)
       (nonEmpty (filter (\x -> not (Text.pack x `Text.isInfixOf` t)) xs))
 
 {- | Escape a string for XML text or an attribute value. Use this for
diff --git a/src/Document/Build/Rules.hs b/src/Document/Build/Rules.hs
--- a/src/Document/Build/Rules.hs
+++ b/src/Document/Build/Rules.hs
@@ -60,7 +60,7 @@
   outputTreeRules,
 ) where
 
-import Control.Lens (preview, (^.))
+import Control.Lens (cloneLens, preview, view)
 import Control.Monad (void)
 import Control.Monad.Reader (ask)
 import Control.Monad.Trans.Class (lift)
@@ -74,6 +74,7 @@
 import Document.Build.File (copyTo, filesIn, tarGz, writeUtf8)
 import Document.Build.Image (imageAppend, imageConvert, imageConvertFirstFrame)
 import Document.Build.LibreOffice (flatExtension, libreoffice, libreofficeOutput, zippedExtension)
+import Document.Build.Name.OutputDirectory (OutputDirectoryLike (..))
 import Document.Build.Output (describeForest, edgeForest, outputForestHtml, writeOutputForest)
 import Document.Build.Pandoc (pandoc)
 import Document.Build.Pdf (pdfConcatenate, pdfFirstPage, pdfPageCountFile, pdfSplit, pdfToImages, pdftk)
@@ -82,8 +83,9 @@
 import System.Directory (doesFileExist, listDirectory)
 import System.FilePath (equalFilePath, makeRelative, normalise, takeDirectory, takeExtension, takeFileName, (</>))
 import System.FilePather.Combinators (hasExtension, isFile, isWithin, onFile)
+import System.FilePather.Step (Step (..))
 import System.FilePather.Tracked (Tracked, askTrail, onPath, whenPath)
-import System.FilePather.Trail (Step (..), lastStep)
+import System.FilePather.Trail (lastStep)
 
 {- | Prepare the files of a directory at the same path relative to the output
 directory (step @prepare@): replace placeholders in the files with one of the
@@ -110,7 +112,7 @@
 prepareWith :: String -> [String] -> Command' Placeholders -> FilePath -> Rules
 prepareWith n text placeholders d =
   onFiles . onSources $ \c ->
-    let out = c ^. contextDirectories . outputDirectory
+    let out = view (cloneLens outputDirectory) c
      in whenPath (isWithin d) . buildStep n (\p -> [out </> makeRelative d p]) $
           ask >>= void . prepareFile text placeholders . (out </>) . makeRelative d
 
@@ -123,7 +125,7 @@
 fontRules :: FilePath -> Rules
 fontRules d =
   onFiles . onSources $ \c ->
-    let out = c ^. contextDirectories . outputDirectory
+    let out = view (cloneLens outputDirectory) c
      in whenPath (isWithin d) . buildStep "copy font" (\p -> [out </> p]) $
           ask >>= void . copyTo . (out </>)
 
@@ -287,7 +289,7 @@
 mergePdf :: [(String, FilePath)] -> [String] -> FilePath -> Rules
 mergePdf inputs ranges o =
   onDirectories $ \c ->
-    let out = c ^. contextDirectories . outputDirectory
+    let out = view (cloneLens outputDirectory) c
      in case inputs of
           [] -> mempty
           (_, first) : _ ->
@@ -346,7 +348,7 @@
 splitPdf :: FilePath -> [(String, FilePath)] -> Rules
 splitPdf pdf sections =
   onFiles $ \c ->
-    let out = c ^. contextDirectories . outputDirectory
+    let out = view (cloneLens outputDirectory) c
         sections' = fmap (fmap (out </>)) sections
      in whenPath (isFile (out </> pdf)) (buildStep "pdf split" (const (fmap snd sections')) (void (pdfSplit sections')))
 
@@ -401,7 +403,7 @@
 archiveRules :: (FilePath -> Bool) -> FilePath -> Rules
 archiveRules select name =
   onComplete $ \known c ->
-    let out = c ^. contextDirectories . outputDirectory
+    let out = view (cloneLens outputDirectory) c
         files = ["." </> makeRelative out p | p <- Map.keys known, isWithin out p, select p]
      in buildStep "archive" (const [out </> name]) (void (tarGz files (out </> name)))
 
@@ -418,7 +420,7 @@
 outputTreeRules :: FilePath -> FilePath -> Command' String -> Rules
 outputTreeRules json html title =
   onComplete $ \known c ->
-    let out = c ^. contextDirectories . outputDirectory
+    let out = view (cloneLens outputDirectory) c
         json' = out </> json
         html' = out </> html
         edges =
diff --git a/src/Document/Build/Target.hs b/src/Document/Build/Target.hs
new file mode 100644
--- /dev/null
+++ b/src/Document/Build/Target.hs
@@ -0,0 +1,165 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | What a build is running the rules on.
+
+A 'Target' is a sum: its constructors are named 'targetFile',
+'targetDirectory' and 'targetComplete'.
+-}
+module Document.Build.Target (
+  -- * Type
+  KnownFiles,
+  Target (..),
+
+  -- * Optics
+  TargetLike (..),
+  lawNamesTarget,
+  InheritTarget,
+  inheritTarget,
+) where
+
+import Control.DeepSeq (NFData)
+import Control.Lens (Choice, Optic, prism', review)
+import Data.Data (Data)
+import Data.Functor.Const (Const)
+import Data.Map (Map)
+import Document.Build.Name.TargetComplete (TargetCompleteLike (..))
+import Document.Build.Name.TargetDirectory (TargetDirectoryLike (..))
+import Document.Build.Name.TargetFile (TargetFileLike (..))
+import GHC.Generics (Generic)
+import System.FilePather.Trail (Trail)
+
+{- $setup
+>>> import Control.Lens (preview)
+-}
+
+{- | The files that a build knows: the files of its input directories, with
+the empty trail, and the files that the rules made, with their trails, by
+normalised path.
+
+@since 0.1.0.0
+-}
+type KnownFiles = Map FilePath Trail
+
+{- | What a build is running the rules on.
+
+>>> preview targetComplete (TargetComplete mempty)
+Just (fromList [])
+
+@since 0.1.0.0
+-}
+data Target
+  = -- | A file: a file of an input directory, or a file the rules made
+    -- ('targetFile').
+    TargetFile
+  | -- | A directory of the output directory ('targetDirectory').
+    TargetDirectory
+  | -- | The output directory, once, when the rules make no more files, with
+    -- every file the build knows ('targetComplete').
+    TargetComplete KnownFiles
+  deriving stock (Eq, Ord, Show, Generic, Data)
+  deriving anyclass (NFData)
+
+{- | The class of 'Target'.
+
+@since 0.2.0.0
+-}
+class TargetLike p f x x' where
+  target ::
+    Optic p f x x' Target Target
+
+{- | Identity
+
+@since 0.2.0.0
+-}
+instance (x' ~ Target) => TargetLike p f Target x' where
+  target =
+    id
+  {-# INLINE target #-}
+
+{- | Prism
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ Target) => TargetFileLike p f Target x' where
+  targetFile =
+    prism'
+      (\() -> TargetFile)
+      ( \case
+          TargetFile -> Just ()
+          TargetDirectory -> Nothing
+          TargetComplete _ -> Nothing
+      )
+  {-# INLINE targetFile #-}
+
+{- | Prism
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ Target) => TargetDirectoryLike p f Target x' where
+  targetDirectory =
+    prism'
+      (\() -> TargetDirectory)
+      ( \case
+          TargetDirectory -> Just ()
+          TargetFile -> Nothing
+          TargetComplete _ -> Nothing
+      )
+  {-# INLINE targetDirectory #-}
+
+{- | Prism
+
+@since 0.2.0.0
+-}
+instance (Choice p, Applicative f, x' ~ Target) => TargetCompleteLike p f Target x' where
+  targetComplete =
+    prism'
+      TargetComplete
+      ( \case
+          TargetComplete k -> Just k
+          TargetFile -> Nothing
+          TargetDirectory -> Nothing
+      )
+  {-# INLINE targetComplete #-}
+
+{- | Rebuild a value using only the name of each constructor.
+
+Laws:
+
+> lawNamesTarget == id
+
+@since 0.2.0.0
+-}
+lawNamesTarget :: Target -> Target
+lawNamesTarget = \case
+  TargetFile -> review targetFile ()
+  TargetDirectory -> review targetDirectory ()
+  TargetComplete k -> review targetComplete k
+
+{- | The classes that a data type holding 'Target' inherits.
+
+@since 0.2.0.0
+-}
+type InheritTarget s =
+  ( TargetLike (->) (Const ()) s s
+  , TargetFileLike (->) (Const ()) s s
+  , TargetDirectoryLike (->) (Const ()) s s
+  , TargetCompleteLike (->) (Const ()) s s
+  )
+
+{- | Check, where it is applied, that @s@ inherits every name of 'Target'.
+
+@since 0.2.0.0
+-}
+inheritTarget :: (InheritTarget s) => proxy s -> ()
+inheritTarget _ =
+  ()
diff --git a/test/BuildTests.hs b/test/BuildTests.hs
--- a/test/BuildTests.hs
+++ b/test/BuildTests.hs
@@ -2,7 +2,7 @@
 {-# OPTIONS_GHC -Wall #-}
 
 {- | Properties of the pure parts of the document build commands: parsers,
-string replacement, hashing, and the optics of the data types.
+string replacement, hashing, exit codes and the display of errors.
 -}
 module BuildTests (
   buildTests,
@@ -13,119 +13,30 @@
 import Data.List (isPrefixOf, isSuffixOf, nub)
 import qualified Data.List.NonEmpty as NonEmpty
 import qualified Data.Text as Text
-import Data.Time (UTCTime)
-import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
 import Document.Build
-import Gens (genInt, genString)
+import Gens (genBuildError, genGitDetails, genImageInfo, genString, genWord)
 import Hedgehog
 import qualified Hedgehog.Gen as Gen
 import qualified Hedgehog.Range as Range
-import Laws (Laws, lensLaws, prismLaws)
-
-genCommandLine :: Gen CommandLine
-genCommandLine =
-  CommandLine <$> genString <*> Gen.list (Range.linear 0 4) genString
-
-genBuildError :: Gen BuildError
-genBuildError =
-  Gen.choice
-    [ ProcessFailed <$> genCommandLine <*> genString <*> genString
-    , ProcessOutputUnrecognised <$> genCommandLine <*> genString
-    , InputFileMissing <$> genString
-    , OutputFileMissing <$> genCommandLine <*> genString
-    , StringsMissing <$> genString <*> Gen.nonEmpty (Range.linear 1 3) genString
-    , FileTypeUnsupported <$> genString <*> Gen.list (Range.linear 0 3) genString
-    , FileNotInGit <$> genString
-    , FileNotUtf8 <$> genString
-    , BuildCycle <$> genCycle
-    , InputFilesTooFew <$> genString <*> Gen.int (Range.linear 0 10) <*> Gen.int (Range.linear 0 10)
-    , OutputNotInDirectory <$> genString <*> genString
-    , ShallowRepository <$> genString
-    ]
-
-genCycle :: Gen Cycle
-genCycle =
-  let genStep = Step <$> genString <*> genString
-      genTrail = Trail <$> Gen.list (Range.linear 0 3) genStep
-   in Gen.choice
-        [ ReproducesInput <$> genString <*> genStep <*> genTrail
-        , RepeatsStep <$> genStep <*> genTrail
-        ]
-
-genTime :: Gen UTCTime
-genTime =
-  posixSecondsToUTCTime . fromIntegral <$> Gen.int (Range.linear 0 2000000000)
-
-genImageInfo :: Gen ImageInfo
-genImageInfo =
-  ImageInfo
-    <$> Gen.element ["PNG", "JPEG", "GIF", "PDF"]
-    <*> Gen.int (Range.linear 1 10000)
-    <*> Gen.int (Range.linear 1 10000)
-    <*> (fromIntegral <$> Gen.int (Range.linear 1 1200))
-    <*> (fromIntegral <$> Gen.int (Range.linear 1 1200))
-    <*> Gen.element ["Undefined", "PixelsPerInch", "PixelsPerCentimeter"]
-    <*> Gen.element ["sRGB", "Gray", "CMYK"]
-    <*> Gen.element [1, 8, 16]
-    <*> Gen.int (Range.linear 1 100)
-
-genWord :: Gen String
-genWord =
-  Gen.string (Range.linear 1 20) Gen.alphaNum
-
-genGitDetails :: Gen GitDetails
-genGitDetails =
-  GitDetails
-    <$> genWord
-    <*> genWord
-    <*> genWord
-    <*> genWord
-    <*> genTime
-    <*> genWord
-    <*> genWord
-    <*> genTime
-    <*> Gen.string (Range.linear 0 40) (Gen.filter (`notElem` ("\NUL\n" :: String)) Gen.unicode)
-    <*> Gen.list (Range.linear 0 3) genWord
+import Laws (Laws)
 
 buildTests :: Laws
 buildTests =
-  concat
-    [ lensLaws "commandLineProgram" commandLineProgram genCommandLine genString
-    , lensLaws "commandLineArguments" commandLineArguments genCommandLine (Gen.list (Range.linear 0 3) genString)
-    , lensLaws "imageWidth" imageWidth genImageInfo genInt
-    , lensLaws "imageColourspace" imageColourspace genImageInfo genString
-    , lensLaws "gitCommitHash" gitCommitHash genGitDetails genString
-    , lensLaws "gitCommitTime" gitCommitTime genGitDetails genTime
-    , lensLaws "gitAuthors" gitAuthors genGitDetails (Gen.list (Range.linear 0 3) genString)
-    , prismLaws "_ProcessFailed" _ProcessFailed genBuildError ((,,) <$> genCommandLine <*> genString <*> genString)
-    , prismLaws "_ProcessOutputUnrecognised" _ProcessOutputUnrecognised genBuildError ((,) <$> genCommandLine <*> genString)
-    , prismLaws "_InputFileMissing" _InputFileMissing genBuildError genString
-    , prismLaws "_OutputFileMissing" _OutputFileMissing genBuildError ((,) <$> genCommandLine <*> genString)
-    , prismLaws "_StringsMissing" _StringsMissing genBuildError ((,) <$> genString <*> Gen.nonEmpty (Range.linear 1 3) genString)
-    , prismLaws "_FileTypeUnsupported" _FileTypeUnsupported genBuildError ((,) <$> genString <*> Gen.list (Range.linear 0 3) genString)
-    , prismLaws "_FileNotInGit" _FileNotInGit genBuildError genString
-    , prismLaws "_FileNotUtf8" _FileNotUtf8 genBuildError genString
-    , prismLaws "_BuildCycle" _BuildCycle genBuildError genCycle
-    , prismLaws "_InputFilesTooFew" _InputFilesTooFew genBuildError ((,,) <$> genString <*> Gen.int (Range.linear 0 10) <*> Gen.int (Range.linear 0 10))
-    , prismLaws "_OutputNotInDirectory" _OutputNotInDirectory genBuildError ((,) <$> genString <*> genString)
-    , prismLaws "_ShallowRepository" _ShallowRepository genBuildError genString
-    , prismLaws "_BuildError" _BuildError genBuildError genBuildError
-    ,
-      [ ("parsePdfinfoPages finds the page count", prop_parsePdfinfoPages)
-      , ("parseImageInfo round trip", prop_parseImageInfo)
-      , ("parseGitLog round trip", prop_parseGitLog)
-      , ("pageImagePaths has one path for each page", prop_pageImagePaths)
-      , ("replaceText with no pairs is the identity", prop_replaceTextEmpty)
-      , ("replaceText of a string with itself is the identity", prop_replaceTextSame)
-      , ("replaceText of an absent string is the identity", prop_replaceTextAbsent)
-      , ("sha256Hex is 64 hexadecimal digits", prop_sha256Hex)
-      , ("xmlEscape leaves no markup", prop_xmlEscape)
-      , ("shellQuote leaves no unquoted special characters", prop_shellQuote)
-      , ("flatExtension and zippedExtension are inverse", prop_flatZipped)
-      , ("buildErrorExitCode is distinct for each kind of error", prop_exitCodes)
-      , ("displayBuildError ends with a newline", prop_display)
-      ]
-    ]
+  [ ("parsePdfinfoPages finds the page count", prop_parsePdfinfoPages)
+  , ("parseImageInfo round trip", prop_parseImageInfo)
+  , ("parseGitLog round trip", prop_parseGitLog)
+  , ("pageImagePaths has one path for each page", prop_pageImagePaths)
+  , ("replaceText with no pairs is the identity", prop_replaceTextEmpty)
+  , ("replaceText of a string with itself is the identity", prop_replaceTextSame)
+  , ("replaceText of an absent string is the identity", prop_replaceTextAbsent)
+  , ("sha256Hex is 64 hexadecimal digits", prop_sha256Hex)
+  , ("xmlEscape leaves no markup", prop_xmlEscape)
+  , ("shellQuote leaves no unquoted special characters", prop_shellQuote)
+  , ("flatExtension and zippedExtension are inverse", prop_flatZipped)
+  , ("buildErrorExitCode is distinct for each kind of error", prop_exitCodes)
+  , ("displayBuildError ends with a newline", prop_display)
+  ]
 
 prop_parsePdfinfoPages :: Property
 prop_parsePdfinfoPages =
@@ -216,16 +127,16 @@
 prop_exitCodes =
   withTests 1 . property $
     let errors =
-          [ ProcessOutputUnrecognised (CommandLine "" []) ""
+          [ BuildErrorProcessOutputUnrecognised (ProcessOutputUnrecognised (CommandLine "" []) "")
           , InputFileMissing ""
-          , OutputFileMissing (CommandLine "" []) ""
-          , StringsMissing "" ("" NonEmpty.:| [])
-          , FileTypeUnsupported "" []
+          , BuildErrorOutputFileMissing (OutputFileMissing (CommandLine "" []) "")
+          , BuildErrorStringsMissing (StringsMissing "" ("" NonEmpty.:| []))
+          , BuildErrorFileTypeUnsupported (FileTypeUnsupported "" [])
           , FileNotInGit ""
           , FileNotUtf8 ""
-          , BuildCycle (RepeatsStep (Step "" "") (Trail []))
-          , InputFilesTooFew "" 0 0
-          , OutputNotInDirectory "" ""
+          , BuildCycle (CycleRepeatsStep (RepeatsStep (Step "" "") (Trail [])))
+          , BuildErrorInputFilesTooFew (InputFilesTooFew "" 0 0)
+          , BuildErrorOutputNotInDirectory (OutputNotInDirectory "" "")
           , ShallowRepository ""
           ]
         codes = fmap buildErrorExitCode errors
diff --git a/test/Gens.hs b/test/Gens.hs
--- a/test/Gens.hs
+++ b/test/Gens.hs
@@ -1,15 +1,51 @@
+{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_GHC -Wall #-}
 
--- | Generators shared by the property tests.
+{- | Generators shared by the property tests.
+
+The generator of each data type has the shape of its manifest, and is built
+from its public interface: a product is its constructor applied to a
+generator of each field, and a sum is one alternative for each constructor,
+through its name.
+-}
 module Gens (
   genInt,
   genString,
   genPath,
+  genWord,
+  genTime,
+
+  -- * Data types
+  genCommandLine,
+  genProcessFailed,
+  genProcessOutputUnrecognised,
+  genOutputFileMissing,
+  genStringsMissing,
+  genFileTypeUnsupported,
+  genInputFilesTooFew,
+  genOutputNotInDirectory,
+  genBuildError,
+  genStep,
+  genTrail,
+  genCycle,
+  genImageInfo,
+  genGitDetails,
+  genOutputFile,
+  genDirectories,
+  genKnownFiles,
+  genTarget,
+  genContext,
 ) where
 
+import Control.Lens (review)
+import qualified Data.Map as Map
+import Data.Time (UTCTime)
+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
+import Document.Build
 import Hedgehog (Gen)
 import qualified Hedgehog.Gen as Gen
 import qualified Hedgehog.Range as Range
+import Prelude hiding (cycle)
 
 genInt :: Gen Int
 genInt =
@@ -27,3 +63,126 @@
         <$> Gen.list (Range.linear 0 3) part
         <*> part
         <*> Gen.element ["fodt", "md", "pdf", "png", "txt"]
+
+genWord :: Gen String
+genWord =
+  Gen.string (Range.linear 1 20) Gen.alphaNum
+
+genTime :: Gen UTCTime
+genTime =
+  posixSecondsToUTCTime . fromIntegral <$> Gen.int (Range.linear 0 2000000000)
+
+genCommandLine :: Gen CommandLine
+genCommandLine =
+  CommandLine <$> genString <*> Gen.list (Range.linear 0 4) genString
+
+genProcessFailed :: Gen ProcessFailed
+genProcessFailed =
+  ProcessFailed <$> genCommandLine <*> genString <*> genString
+
+genProcessOutputUnrecognised :: Gen ProcessOutputUnrecognised
+genProcessOutputUnrecognised =
+  ProcessOutputUnrecognised <$> genCommandLine <*> genString
+
+genOutputFileMissing :: Gen OutputFileMissing
+genOutputFileMissing =
+  OutputFileMissing <$> genCommandLine <*> genString
+
+genStringsMissing :: Gen StringsMissing
+genStringsMissing =
+  StringsMissing <$> genString <*> Gen.nonEmpty (Range.linear 1 3) genString
+
+genFileTypeUnsupported :: Gen FileTypeUnsupported
+genFileTypeUnsupported =
+  FileTypeUnsupported <$> genString <*> Gen.list (Range.linear 0 3) genString
+
+genInputFilesTooFew :: Gen InputFilesTooFew
+genInputFilesTooFew =
+  InputFilesTooFew <$> genString <*> Gen.int (Range.linear 0 10) <*> Gen.int (Range.linear 0 10)
+
+genOutputNotInDirectory :: Gen OutputNotInDirectory
+genOutputNotInDirectory =
+  OutputNotInDirectory <$> genString <*> genString
+
+genBuildError :: Gen BuildError
+genBuildError =
+  Gen.choice
+    [ review processFailed <$> genProcessFailed
+    , review processOutputUnrecognised <$> genProcessOutputUnrecognised
+    , review inputFileMissing <$> genString
+    , review outputFileMissing <$> genOutputFileMissing
+    , review stringsMissing <$> genStringsMissing
+    , review fileTypeUnsupported <$> genFileTypeUnsupported
+    , review fileNotInGit <$> genString
+    , review fileNotUtf8 <$> genString
+    , review cycle <$> genCycle
+    , review inputFilesTooFew <$> genInputFilesTooFew
+    , review outputNotInDirectory <$> genOutputNotInDirectory
+    , review shallowRepository <$> genString
+    ]
+
+genStep :: Gen Step
+genStep =
+  Step <$> genWord <*> genPath
+
+genTrail :: Gen Trail
+genTrail =
+  Trail <$> Gen.list (Range.linear 0 3) genStep
+
+genCycle :: Gen Cycle
+genCycle =
+  Gen.choice
+    [ review reproducesInput <$> (ReproducesInput <$> genPath <*> genStep <*> genTrail)
+    , review repeatsStep <$> (RepeatsStep <$> genStep <*> genTrail)
+    ]
+
+genImageInfo :: Gen ImageInfo
+genImageInfo =
+  ImageInfo
+    <$> Gen.element ["PNG", "JPEG", "GIF", "PDF"]
+    <*> Gen.int (Range.linear 1 10000)
+    <*> Gen.int (Range.linear 1 10000)
+    <*> (fromIntegral <$> Gen.int (Range.linear 1 1200))
+    <*> (fromIntegral <$> Gen.int (Range.linear 1 1200))
+    <*> Gen.element ["Undefined", "PixelsPerInch", "PixelsPerCentimeter"]
+    <*> Gen.element ["sRGB", "Gray", "CMYK"]
+    <*> Gen.element [1, 8, 16]
+    <*> Gen.int (Range.linear 1 100)
+
+genGitDetails :: Gen GitDetails
+genGitDetails =
+  GitDetails
+    <$> genWord
+    <*> genWord
+    <*> genWord
+    <*> genWord
+    <*> genTime
+    <*> genWord
+    <*> genWord
+    <*> genTime
+    <*> Gen.string (Range.linear 0 40) (Gen.filter (`notElem` ("\NUL\n" :: String)) Gen.unicode)
+    <*> Gen.list (Range.linear 0 3) genWord
+
+genOutputFile :: Gen OutputFile
+genOutputFile =
+  OutputFile <$> genPath <*> genWord <*> Gen.maybe genGitDetails <*> Gen.maybe (Gen.int (Range.linear 1 500)) <*> Gen.maybe genImageInfo
+
+genDirectories :: Gen Directories
+genDirectories =
+  Directories <$> genPath <*> Gen.list (Range.linear 0 3) genPath <*> Gen.maybe genPath
+
+genKnownFiles :: Gen KnownFiles
+genKnownFiles =
+  Map.fromList <$> Gen.list (Range.linear 0 3) ((,) <$> genPath <*> genTrail)
+
+genTarget :: Gen Target
+genTarget =
+  Gen.choice
+    [ pure (review targetFile ())
+    , pure (review targetDirectory ())
+    , review targetComplete <$> genKnownFiles
+    ]
+
+genContext :: Gen Context
+genContext =
+  Context <$> genDirectories <*> genWord <*> genTarget
diff --git a/test/Laws.hs b/test/Laws.hs
--- a/test/Laws.hs
+++ b/test/Laws.hs
@@ -1,19 +1,60 @@
 {-# LANGUAGE RankNTypes #-}
 {-# OPTIONS_GHC -Wall #-}
 
--- | Laws of lenses and prisms.
+{- | Laws of the kinds of optics that the instances of names have: identity,
+lens, prism and affine traversal, and the law of a manifest.
+
+Each law reads the optic through 'cloneLens' for a lens, 'clonePrism' for a
+prism and 'cloneTraversal' for an affine traversal, so a test fails to
+compile where an instance is of a weaker kind than its comment says.
+-}
 module Laws (
   Laws,
+  manifestLaw,
+  identityLaws,
   lensLaws,
   prismLaws,
+  affineLaws,
 ) where
 
-import Control.Lens (ALens', APrism', cloneLens, clonePrism, preview, review, set, view)
+import Control.Lens (ALens', APrism', ATraversal', cloneLens, clonePrism, cloneTraversal, preview, review, set, toListOf, traverseOf, view)
+import Data.Char (ord)
+import Data.Functor.Compose (Compose (..))
+import Data.Functor.Identity (Identity (..))
 import Data.String (fromString)
 import Hedgehog
+import Hedgehog.Function (fn, forAllFn)
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
 
 type Laws = [(PropertyName, Property)]
 
+-- | The law of a manifest: it rebuilds every value as it was.
+manifestLaw :: (Eq s, Show s) => String -> (s -> s) -> Gen s -> Laws
+manifestLaw name m genS =
+  [
+    ( fromString (name <> " == id")
+    , property $
+        do
+          s <- forAll genS
+          m s === s
+    )
+  ]
+
+-- | The laws of an identity: viewing gives the value, and setting replaces it.
+identityLaws :: (Eq s, Show s) => String -> ALens' s s -> Gen s -> Laws
+identityLaws name o genS =
+  [
+    ( fromString (name <> " identity")
+    , property $
+        do
+          s <- forAll genS
+          t <- forAll genS
+          view (cloneLens o) s === s
+          set (cloneLens o) t s === t
+    )
+  ]
+
 -- | The lens laws: view-set, set-view and set-set.
 lensLaws :: (Eq s, Show s, Eq a, Show a) => String -> ALens' s a -> Gen s -> Gen a -> Laws
 lensLaws name l genS genA =
@@ -61,3 +102,41 @@
           maybe s (review (clonePrism p)) (preview (clonePrism p) s) === s
     )
   ]
+
+{- | The laws of an affine traversal: identity, composition, and at most one
+target. The functions of the composition law are generated with hedgehog-fn,
+over a key that each target is mapped to by its 'show', so that targets
+without an instance of 'Hedgehog.Function.Arg', such as 'String', are
+covered.
+-}
+affineLaws :: (Eq s, Show s, Eq a, Show a) => String -> ATraversal' s a -> Gen s -> Gen a -> Laws
+affineLaws name o genS genA =
+  [
+    ( fromString (name <> " traverse pure")
+    , property $
+        do
+          s <- forAll genS
+          traverseOf (cloneTraversal o) Identity s === Identity s
+    )
+  ,
+    ( fromString (name <> " traverse composition")
+    , property $
+        do
+          s <- forAll genS
+          f <- (. key) <$> forAllFn (fn (Gen.maybe genA))
+          g <- (. key) <$> forAllFn (fn (Gen.list (Range.linear 0 3) genA))
+          fmap (traverseOf (cloneTraversal o) f) (traverseOf (cloneTraversal o) g s)
+            === getCompose (traverseOf (cloneTraversal o) (Compose . fmap f . g) s)
+    )
+  ,
+    ( fromString (name <> " at most one target")
+    , property $
+        do
+          s <- forAll genS
+          assert (length (toListOf (cloneTraversal o) s) <= 1)
+    )
+  ]
+ where
+  key :: (Show a) => a -> Int
+  key =
+    foldr (\c k -> (k * 31 + ord c) `mod` 1000003) 7 . show
diff --git a/test/OpticsTests.hs b/test/OpticsTests.hs
new file mode 100644
--- /dev/null
+++ b/test/OpticsTests.hs
@@ -0,0 +1,269 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -Wall #-}
+
+{- | The laws of the classy optics of every data type: the coverage of each
+sum by its generator, the manifest of each data type, and the laws of the
+kind of every instance of every name, including the identity instances of
+names and the inherited instances.
+-}
+module OpticsTests (
+  opticsTests,
+) where
+
+import Control.Lens (has)
+import Document.Build
+import Gens
+import Hedgehog
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import Laws
+import Prelude hiding (cycle)
+
+genStrings :: Gen [String]
+genStrings =
+  Gen.list (Range.linear 0 3) genString
+
+genSteps :: Gen [Step]
+genSteps =
+  Gen.list (Range.linear 0 3) genStep
+
+genReproducesInput :: Gen ReproducesInput
+genReproducesInput =
+  ReproducesInput <$> genPath <*> genStep <*> genTrail
+
+genRepeatsStep :: Gen RepeatsStep
+genRepeatsStep =
+  RepeatsStep <$> genStep <*> genTrail
+
+opticsTests :: Laws
+opticsTests =
+  [ ("cover BuildError", prop_coverBuildError)
+  , ("cover Target", prop_coverTarget)
+  , ("inherits", prop_inherits)
+  ]
+    -- Identity instances of names, for their target types
+    <> identityLaws "commandLineProgram FilePath" commandLineProgram genString
+    <> identityLaws "commandLineArguments [String]" commandLineArguments genStrings
+    <> identityLaws "standardOutput String" standardOutput genString
+    <> identityLaws "standardError String" standardError genString
+    <> identityLaws "expectedOutput String" expectedOutput genString
+    <> identityLaws "missingStrings (NonEmpty String)" missingStrings (Gen.nonEmpty (Range.linear 1 3) genString)
+    <> identityLaws "supportedExtensions [String]" supportedExtensions genStrings
+    <> identityLaws "directory FilePath" directory genPath
+    <> identityLaws "inputFileCount Int" inputFileCount genInt
+    <> identityLaws "inputFilesNeeded Int" inputFilesNeeded genInt
+    <> identityLaws "inputFileMissing FilePath" inputFileMissing genPath
+    <> identityLaws "fileNotInGit FilePath" fileNotInGit genPath
+    <> identityLaws "fileNotUtf8 FilePath" fileNotUtf8 genPath
+    <> identityLaws "shallowRepository FilePath" shallowRepository genPath
+    <> identityLaws "outputDirectory FilePath" outputDirectory genPath
+    <> identityLaws "inputDirectories [FilePath]" inputDirectories (Gen.list (Range.linear 0 3) genPath)
+    <> identityLaws "fontsDirectory (Maybe FilePath)" fontsDirectory (Gen.maybe genPath)
+    <> identityLaws "targetFile ()" targetFile (pure ())
+    <> identityLaws "targetDirectory ()" targetDirectory (pure ())
+    <> identityLaws "targetComplete KnownFiles" targetComplete genKnownFiles
+    <> identityLaws "contextProject String" contextProject genWord
+    <> identityLaws "gitCommitHash String" gitCommitHash genWord
+    <> identityLaws "gitCommitHashShort String" gitCommitHashShort genWord
+    <> identityLaws "gitAuthorName String" gitAuthorName genWord
+    <> identityLaws "gitAuthorEmail String" gitAuthorEmail genWord
+    <> identityLaws "gitAuthorTime UTCTime" gitAuthorTime genTime
+    <> identityLaws "gitCommitterName String" gitCommitterName genWord
+    <> identityLaws "gitCommitterEmail String" gitCommitterEmail genWord
+    <> identityLaws "gitCommitTime UTCTime" gitCommitTime genTime
+    <> identityLaws "gitSubject String" gitSubject genWord
+    <> identityLaws "gitAuthors [String]" gitAuthors genStrings
+    <> identityLaws "imageFormat String" imageFormat genWord
+    <> identityLaws "imageWidth Int" imageWidth genInt
+    <> identityLaws "imageHeight Int" imageHeight genInt
+    <> identityLaws "imageResolutionX Double" imageResolutionX (fromIntegral <$> genInt)
+    <> identityLaws "imageResolutionY Double" imageResolutionY (fromIntegral <$> genInt)
+    <> identityLaws "imageResolutionUnits String" imageResolutionUnits genWord
+    <> identityLaws "imageColourspace String" imageColourspace genWord
+    <> identityLaws "imageDepth Int" imageDepth genInt
+    <> identityLaws "imageFrames Int" imageFrames genInt
+    <> identityLaws "outputPath FilePath" outputPath genPath
+    <> identityLaws "outputSha256 String" outputSha256 genWord
+    <> identityLaws "outputGitDetails (Maybe GitDetails)" outputGitDetails (Gen.maybe genGitDetails)
+    <> identityLaws "outputPages (Maybe Int)" outputPages (Gen.maybe genInt)
+    <> identityLaws "outputImageInfo (Maybe ImageInfo)" outputImageInfo (Gen.maybe genImageInfo)
+    -- CommandLine
+    <> manifestLaw "lawNamesCommandLine" lawNamesCommandLine genCommandLine
+    <> identityLaws "commandLine CommandLine" commandLine genCommandLine
+    <> lensLaws "commandLineProgram CommandLine" commandLineProgram genCommandLine genString
+    <> lensLaws "commandLineArguments CommandLine" commandLineArguments genCommandLine genStrings
+    -- ProcessFailed
+    <> manifestLaw "lawNamesProcessFailed" lawNamesProcessFailed genProcessFailed
+    <> identityLaws "processFailed ProcessFailed" processFailed genProcessFailed
+    <> lensLaws "commandLine ProcessFailed" commandLine genProcessFailed genCommandLine
+    <> lensLaws "standardOutput ProcessFailed" standardOutput genProcessFailed genString
+    <> lensLaws "standardError ProcessFailed" standardError genProcessFailed genString
+    <> lensLaws "commandLineProgram ProcessFailed" commandLineProgram genProcessFailed genString
+    <> lensLaws "commandLineArguments ProcessFailed" commandLineArguments genProcessFailed genStrings
+    -- ProcessOutputUnrecognised
+    <> manifestLaw "lawNamesProcessOutputUnrecognised" lawNamesProcessOutputUnrecognised genProcessOutputUnrecognised
+    <> identityLaws "processOutputUnrecognised ProcessOutputUnrecognised" processOutputUnrecognised genProcessOutputUnrecognised
+    <> lensLaws "commandLine ProcessOutputUnrecognised" commandLine genProcessOutputUnrecognised genCommandLine
+    <> lensLaws "expectedOutput ProcessOutputUnrecognised" expectedOutput genProcessOutputUnrecognised genString
+    <> lensLaws "commandLineProgram ProcessOutputUnrecognised" commandLineProgram genProcessOutputUnrecognised genString
+    <> lensLaws "commandLineArguments ProcessOutputUnrecognised" commandLineArguments genProcessOutputUnrecognised genStrings
+    -- OutputFileMissing
+    <> manifestLaw "lawNamesOutputFileMissing" lawNamesOutputFileMissing genOutputFileMissing
+    <> identityLaws "outputFileMissing OutputFileMissing" outputFileMissing genOutputFileMissing
+    <> lensLaws "commandLine OutputFileMissing" commandLine genOutputFileMissing genCommandLine
+    <> lensLaws "file OutputFileMissing" file genOutputFileMissing genPath
+    <> lensLaws "commandLineProgram OutputFileMissing" commandLineProgram genOutputFileMissing genString
+    <> lensLaws "commandLineArguments OutputFileMissing" commandLineArguments genOutputFileMissing genStrings
+    -- StringsMissing
+    <> manifestLaw "lawNamesStringsMissing" lawNamesStringsMissing genStringsMissing
+    <> identityLaws "stringsMissing StringsMissing" stringsMissing genStringsMissing
+    <> lensLaws "file StringsMissing" file genStringsMissing genPath
+    <> lensLaws "missingStrings StringsMissing" missingStrings genStringsMissing (Gen.nonEmpty (Range.linear 1 3) genString)
+    -- FileTypeUnsupported
+    <> manifestLaw "lawNamesFileTypeUnsupported" lawNamesFileTypeUnsupported genFileTypeUnsupported
+    <> identityLaws "fileTypeUnsupported FileTypeUnsupported" fileTypeUnsupported genFileTypeUnsupported
+    <> lensLaws "file FileTypeUnsupported" file genFileTypeUnsupported genPath
+    <> lensLaws "supportedExtensions FileTypeUnsupported" supportedExtensions genFileTypeUnsupported genStrings
+    -- InputFilesTooFew
+    <> manifestLaw "lawNamesInputFilesTooFew" lawNamesInputFilesTooFew genInputFilesTooFew
+    <> identityLaws "inputFilesTooFew InputFilesTooFew" inputFilesTooFew genInputFilesTooFew
+    <> lensLaws "directory InputFilesTooFew" directory genInputFilesTooFew genPath
+    <> lensLaws "inputFileCount InputFilesTooFew" inputFileCount genInputFilesTooFew genInt
+    <> lensLaws "inputFilesNeeded InputFilesTooFew" inputFilesNeeded genInputFilesTooFew genInt
+    -- OutputNotInDirectory
+    <> manifestLaw "lawNamesOutputNotInDirectory" lawNamesOutputNotInDirectory genOutputNotInDirectory
+    <> identityLaws "outputNotInDirectory OutputNotInDirectory" outputNotInDirectory genOutputNotInDirectory
+    <> lensLaws "file OutputNotInDirectory" file genOutputNotInDirectory genPath
+    <> lensLaws "outputDirectory OutputNotInDirectory" outputDirectory genOutputNotInDirectory genPath
+    -- BuildError
+    <> manifestLaw "lawNamesBuildError" lawNamesBuildError genBuildError
+    <> identityLaws "buildError BuildError" buildError genBuildError
+    <> prismLaws "processFailed BuildError" processFailed genBuildError genProcessFailed
+    <> prismLaws "processOutputUnrecognised BuildError" processOutputUnrecognised genBuildError genProcessOutputUnrecognised
+    <> prismLaws "inputFileMissing BuildError" inputFileMissing genBuildError genPath
+    <> prismLaws "outputFileMissing BuildError" outputFileMissing genBuildError genOutputFileMissing
+    <> prismLaws "stringsMissing BuildError" stringsMissing genBuildError genStringsMissing
+    <> prismLaws "fileTypeUnsupported BuildError" fileTypeUnsupported genBuildError genFileTypeUnsupported
+    <> prismLaws "fileNotInGit BuildError" fileNotInGit genBuildError genPath
+    <> prismLaws "fileNotUtf8 BuildError" fileNotUtf8 genBuildError genPath
+    <> prismLaws "cycle BuildError" cycle genBuildError genCycle
+    <> prismLaws "inputFilesTooFew BuildError" inputFilesTooFew genBuildError genInputFilesTooFew
+    <> prismLaws "outputNotInDirectory BuildError" outputNotInDirectory genBuildError genOutputNotInDirectory
+    <> prismLaws "shallowRepository BuildError" shallowRepository genBuildError genPath
+    <> prismLaws "reproducesInput BuildError" reproducesInput genBuildError genReproducesInput
+    <> prismLaws "repeatsStep BuildError" repeatsStep genBuildError genRepeatsStep
+    <> affineLaws "commandLine BuildError" commandLine genBuildError genCommandLine
+    <> affineLaws "commandLineProgram BuildError" commandLineProgram genBuildError genString
+    <> affineLaws "commandLineArguments BuildError" commandLineArguments genBuildError genStrings
+    <> affineLaws "standardOutput BuildError" standardOutput genBuildError genString
+    <> affineLaws "standardError BuildError" standardError genBuildError genString
+    <> affineLaws "expectedOutput BuildError" expectedOutput genBuildError genString
+    <> affineLaws "file BuildError" file genBuildError genPath
+    <> affineLaws "missingStrings BuildError" missingStrings genBuildError (Gen.nonEmpty (Range.linear 1 3) genString)
+    <> affineLaws "supportedExtensions BuildError" supportedExtensions genBuildError genStrings
+    <> affineLaws "directory BuildError" directory genBuildError genPath
+    <> affineLaws "inputFileCount BuildError" inputFileCount genBuildError genInt
+    <> affineLaws "inputFilesNeeded BuildError" inputFilesNeeded genBuildError genInt
+    <> affineLaws "outputDirectory BuildError" outputDirectory genBuildError genPath
+    <> affineLaws "step BuildError" step genBuildError genStep
+    <> affineLaws "trail BuildError" trail genBuildError genTrail
+    <> affineLaws "stepName BuildError" stepName genBuildError genWord
+    <> affineLaws "stepInput BuildError" stepInput genBuildError genPath
+    <> affineLaws "steps BuildError" steps genBuildError genSteps
+    -- Directories
+    <> manifestLaw "lawNamesDirectories" lawNamesDirectories genDirectories
+    <> identityLaws "directories Directories" directories genDirectories
+    <> lensLaws "outputDirectory Directories" outputDirectory genDirectories genPath
+    <> lensLaws "inputDirectories Directories" inputDirectories genDirectories (Gen.list (Range.linear 0 3) genPath)
+    <> lensLaws "fontsDirectory Directories" fontsDirectory genDirectories (Gen.maybe genPath)
+    -- Target
+    <> manifestLaw "lawNamesTarget" lawNamesTarget genTarget
+    <> identityLaws "target Target" target genTarget
+    <> prismLaws "targetFile Target" targetFile genTarget (pure ())
+    <> prismLaws "targetDirectory Target" targetDirectory genTarget (pure ())
+    <> prismLaws "targetComplete Target" targetComplete genTarget genKnownFiles
+    -- Context
+    <> manifestLaw "lawNamesContext" lawNamesContext genContext
+    <> identityLaws "context Context" context genContext
+    <> lensLaws "directories Context" directories genContext genDirectories
+    <> lensLaws "contextProject Context" contextProject genContext genWord
+    <> lensLaws "target Context" target genContext genTarget
+    <> lensLaws "outputDirectory Context" outputDirectory genContext genPath
+    <> lensLaws "inputDirectories Context" inputDirectories genContext (Gen.list (Range.linear 0 3) genPath)
+    <> lensLaws "fontsDirectory Context" fontsDirectory genContext (Gen.maybe genPath)
+    <> affineLaws "targetFile Context" targetFile genContext (pure ())
+    <> affineLaws "targetDirectory Context" targetDirectory genContext (pure ())
+    <> affineLaws "targetComplete Context" targetComplete genContext genKnownFiles
+    -- GitDetails
+    <> manifestLaw "lawNamesGitDetails" lawNamesGitDetails genGitDetails
+    <> identityLaws "gitDetails GitDetails" gitDetails genGitDetails
+    <> lensLaws "gitCommitHash GitDetails" gitCommitHash genGitDetails genWord
+    <> lensLaws "gitCommitHashShort GitDetails" gitCommitHashShort genGitDetails genWord
+    <> lensLaws "gitAuthorName GitDetails" gitAuthorName genGitDetails genWord
+    <> lensLaws "gitAuthorEmail GitDetails" gitAuthorEmail genGitDetails genWord
+    <> lensLaws "gitAuthorTime GitDetails" gitAuthorTime genGitDetails genTime
+    <> lensLaws "gitCommitterName GitDetails" gitCommitterName genGitDetails genWord
+    <> lensLaws "gitCommitterEmail GitDetails" gitCommitterEmail genGitDetails genWord
+    <> lensLaws "gitCommitTime GitDetails" gitCommitTime genGitDetails genTime
+    <> lensLaws "gitSubject GitDetails" gitSubject genGitDetails genWord
+    <> lensLaws "gitAuthors GitDetails" gitAuthors genGitDetails genStrings
+    -- ImageInfo
+    <> manifestLaw "lawNamesImageInfo" lawNamesImageInfo genImageInfo
+    <> identityLaws "imageInfo ImageInfo" imageInfo genImageInfo
+    <> lensLaws "imageFormat ImageInfo" imageFormat genImageInfo genWord
+    <> lensLaws "imageWidth ImageInfo" imageWidth genImageInfo genInt
+    <> lensLaws "imageHeight ImageInfo" imageHeight genImageInfo genInt
+    <> lensLaws "imageResolutionX ImageInfo" imageResolutionX genImageInfo (fromIntegral <$> genInt)
+    <> lensLaws "imageResolutionY ImageInfo" imageResolutionY genImageInfo (fromIntegral <$> genInt)
+    <> lensLaws "imageResolutionUnits ImageInfo" imageResolutionUnits genImageInfo genWord
+    <> lensLaws "imageColourspace ImageInfo" imageColourspace genImageInfo genWord
+    <> lensLaws "imageDepth ImageInfo" imageDepth genImageInfo genInt
+    <> lensLaws "imageFrames ImageInfo" imageFrames genImageInfo genInt
+    -- OutputFile
+    <> manifestLaw "lawNamesOutputFile" lawNamesOutputFile genOutputFile
+    <> identityLaws "outputFile OutputFile" outputFile genOutputFile
+    <> lensLaws "outputPath OutputFile" outputPath genOutputFile genPath
+    <> lensLaws "outputSha256 OutputFile" outputSha256 genOutputFile genWord
+    <> lensLaws "outputGitDetails OutputFile" outputGitDetails genOutputFile (Gen.maybe genGitDetails)
+    <> lensLaws "outputPages OutputFile" outputPages genOutputFile (Gen.maybe genInt)
+    <> lensLaws "outputImageInfo OutputFile" outputImageInfo genOutputFile (Gen.maybe genImageInfo)
+
+prop_coverBuildError :: Property
+prop_coverBuildError =
+  withTests 500 . property $
+    do
+      x <- forAll genBuildError
+      cover 5 "processFailed" (has processFailed x)
+      cover 5 "processOutputUnrecognised" (has processOutputUnrecognised x)
+      cover 5 "inputFileMissing" (has inputFileMissing x)
+      cover 5 "outputFileMissing" (has outputFileMissing x)
+      cover 5 "stringsMissing" (has stringsMissing x)
+      cover 5 "fileTypeUnsupported" (has fileTypeUnsupported x)
+      cover 5 "fileNotInGit" (has fileNotInGit x)
+      cover 5 "fileNotUtf8" (has fileNotUtf8 x)
+      cover 5 "cycle" (has cycle x)
+      cover 5 "inputFilesTooFew" (has inputFilesTooFew x)
+      cover 5 "outputNotInDirectory" (has outputNotInDirectory x)
+      cover 5 "shallowRepository" (has shallowRepository x)
+
+prop_coverTarget :: Property
+prop_coverTarget =
+  property $
+    do
+      x <- forAll genTarget
+      cover 10 "targetFile" (has targetFile x)
+      cover 10 "targetDirectory" (has targetDirectory x)
+      cover 10 "targetComplete" (has targetComplete x)
+
+-- | The checks of inheritance compile, so each is @()@.
+prop_inherits :: Property
+prop_inherits =
+  withTests 1 . property $
+    concat
+      [ inheritsProcessFailed
+      , inheritsProcessOutputUnrecognised
+      , inheritsOutputFileMissing
+      , inheritsBuildError
+      , inheritsContext
+      ]
+      === replicate 13 ()
diff --git a/test/OutputTests.hs b/test/OutputTests.hs
--- a/test/OutputTests.hs
+++ b/test/OutputTests.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_GHC -Wall #-}
 
--- | Properties of the output tree, its JSON, the optics of the context, and placeholders.
+-- | Properties of the output tree, its JSON, and placeholders.
 module OutputTests (
   outputTests,
 ) where
@@ -12,41 +12,13 @@
 import Data.Containers.ListUtils (nubOrd)
 import Data.Foldable (toList)
 import Data.List (sort)
-import qualified Data.Map as Map
-import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
 import Data.Tree (Forest, Tree (..), flatten)
 import Document.Build
-import Gens (genPath)
+import Gens (genGitDetails, genImageInfo, genOutputFile, genPath, genWord)
 import Hedgehog
 import qualified Hedgehog.Gen as Gen
 import qualified Hedgehog.Range as Range
-import Laws (Laws, lensLaws, prismLaws)
-
-genWord :: Gen String
-genWord =
-  Gen.string (Range.linear 1 12) Gen.alphaNum
-
-genGitDetails :: Gen GitDetails
-genGitDetails =
-  let time = posixSecondsToUTCTime . fromIntegral <$> Gen.int (Range.linear 0 2000000000)
-   in GitDetails <$> genWord <*> genWord <*> genWord <*> genWord <*> time <*> genWord <*> genWord <*> time <*> genWord <*> Gen.list (Range.linear 0 3) genWord
-
-genImageInfo :: Gen ImageInfo
-genImageInfo =
-  ImageInfo
-    <$> genWord
-    <*> Gen.int (Range.linear 1 5000)
-    <*> Gen.int (Range.linear 1 5000)
-    <*> (fromIntegral <$> Gen.int (Range.linear 1 600))
-    <*> (fromIntegral <$> Gen.int (Range.linear 1 600))
-    <*> genWord
-    <*> genWord
-    <*> Gen.int (Range.linear 1 16)
-    <*> Gen.int (Range.linear 1 10)
-
-genOutputFile :: Gen OutputFile
-genOutputFile =
-  OutputFile <$> genPath <*> genWord <*> Gen.maybe genGitDetails <*> Gen.maybe (Gen.int (Range.linear 1 500)) <*> Gen.maybe genImageInfo
+import Laws (Laws)
 
 genForest :: Gen (Forest OutputFile)
 genForest =
@@ -57,8 +29,8 @@
 -}
 genEdges :: Gen Edges
 genEdges =
-  let file = Gen.element (fmap (\n -> "public/f" <> show n <> ".pdf") [1 .. 12 :: Int])
-   in Gen.list (Range.linear 0 10) ((,) <$> file <*> Gen.list (Range.linear 0 3) file)
+  let genFile = Gen.element (fmap (\n -> "public/f" <> show n <> ".pdf") [1 .. 12 :: Int])
+   in Gen.list (Range.linear 0 10) ((,) <$> genFile <*> Gen.list (Range.linear 0 3) genFile)
 
 outputTests :: Laws
 outputTests =
@@ -71,33 +43,6 @@
   , ("withPlaceholder then placeholderValue gives the value", prop_withPlaceholder)
   , ("placeholderAliases gives the value of the other placeholder", prop_placeholderAliases)
   ]
-    <> concat
-      [ lensLaws "outputDirectory" outputDirectory genDirectories genPath
-      , lensLaws "inputDirectories" inputDirectories genDirectories (Gen.list (Range.linear 0 3) genPath)
-      , lensLaws "fontsDirectory" fontsDirectory genDirectories (Gen.maybe genPath)
-      , lensLaws "contextDirectories" contextDirectories genContext genDirectories
-      , lensLaws "contextProject" contextProject genContext genWord
-      , lensLaws "contextTarget" contextTarget genContext genTarget
-      , prismLaws "_TargetFile" _TargetFile genTarget (pure ())
-      , prismLaws "_TargetDirectory" _TargetDirectory genTarget (pure ())
-      , prismLaws "_TargetComplete" _TargetComplete genTarget genKnownFiles
-      ]
-
-genDirectories :: Gen Directories
-genDirectories =
-  Directories <$> genPath <*> Gen.list (Range.linear 0 3) genPath <*> Gen.maybe genPath
-
-genKnownFiles :: Gen KnownFiles
-genKnownFiles =
-  Map.fromList <$> Gen.list (Range.linear 0 3) ((,) <$> genPath <*> (Trail <$> Gen.list (Range.linear 0 3) (Step <$> genWord <*> genPath)))
-
-genTarget :: Gen Target
-genTarget =
-  Gen.choice [pure TargetFile, pure TargetDirectory, TargetComplete <$> genKnownFiles]
-
-genContext :: Gen Context
-genContext =
-  Context <$> genDirectories <*> genWord <*> genTarget
 
 genPlaceholders :: Gen Placeholders
 genPlaceholders =
diff --git a/test/doctest_tests.hs b/test/doctest_tests.hs
--- a/test/doctest_tests.hs
+++ b/test/doctest_tests.hs
@@ -17,22 +17,35 @@
             , f
             ]
       )
-      [ "src/Document/Build/Build.hs"
+      [ "src/Document/Build.hs"
+      , "src/Document/Build/Build.hs"
+      , "src/Document/Build/CommandLine.hs"
+      , "src/Document/Build/Context.hs"
+      , "src/Document/Build/Directories.hs"
       , "src/Document/Build/Documents.hs"
       , "src/Document/Build/Error.hs"
+      , "src/Document/Build/Error/FileTypeUnsupported.hs"
+      , "src/Document/Build/Error/InputFilesTooFew.hs"
+      , "src/Document/Build/Error/OutputFileMissing.hs"
+      , "src/Document/Build/Error/OutputNotInDirectory.hs"
+      , "src/Document/Build/Error/ProcessFailed.hs"
+      , "src/Document/Build/Error/ProcessOutputUnrecognised.hs"
+      , "src/Document/Build/Error/StringsMissing.hs"
       , "src/Document/Build/File.hs"
       , "src/Document/Build/Git.hs"
+      , "src/Document/Build/GitDetails.hs"
       , "src/Document/Build/Hash.hs"
-      , "src/Document/Build.hs"
       , "src/Document/Build/Image.hs"
+      , "src/Document/Build/ImageInfo.hs"
       , "src/Document/Build/LibreOffice.hs"
       , "src/Document/Build/Operations.hs"
       , "src/Document/Build/Output.hs"
+      , "src/Document/Build/OutputFile.hs"
       , "src/Document/Build/Pandoc.hs"
-      , "src/Document/Build/Context.hs"
       , "src/Document/Build/Pdf.hs"
       , "src/Document/Build/Project.hs"
       , "src/Document/Build/Replace.hs"
       , "src/Document/Build/Rules.hs"
+      , "src/Document/Build/Target.hs"
       ]
   unless (all (== ExitSuccess) results) exitFailure
diff --git a/test/hedgehog_tests.hs b/test/hedgehog_tests.hs
--- a/test/hedgehog_tests.hs
+++ b/test/hedgehog_tests.hs
@@ -3,6 +3,7 @@
 import BuildTests (buildTests)
 import Control.Monad (unless)
 import Hedgehog (Group (..), checkParallel)
+import OpticsTests (opticsTests)
 import OutputTests (outputTests)
 import System.Exit (exitFailure)
 import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
@@ -18,6 +19,7 @@
         checkParallel
         [ Group "Document.Build" buildTests
         , Group "Output" outputTests
+        , Group "Optics" opticsTests
         ]
 
   unless result exitFailure
