diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,36 @@
+{
+  "name": "template.hs",
+  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
+  // Features to add to the dev container. More info: https://containers.dev/features.
+  "features": {
+    "ghcr.io/devcontainers-contrib/features/haskell:2": {
+      "globalPackages": "cabal-fmt ormolu"
+    },
+    "ghcr.io/devcontainers-contrib/features/pre-commit:2": {},
+    "ghcr.io/devcontainers/features/git:1": {},
+    "ghcr.io/devcontainers/features/github-cli:1": {}
+  },
+  // Use 'forwardPorts' to make a list of ports inside the container available locally.
+  // "forwardPorts": [],
+  // Use 'postCreateCommand' to run commands after the container is created.
+  // "postCreateCommand": "",
+  // Configure tool-specific properties.
+  "customizations": {
+    "vscode": {
+      "settings": {
+        "haskell.manageHLS": "GHCup"
+      },
+      "extensions": [
+        "berberman.vscode-cabal-fmt",
+        "DavidAnson.vscode-markdownlint",
+        "eamodio.gitlens",
+        "github.vscode-github-actions",
+        "GitHub.vscode-pull-request-github",
+        "haskell.haskell",
+        "ms-azuretools.vscode-docker"
+      ]
+    }
+  },
+  // Comment out to connect as root instead. To add a non-root user, see: https://aka.ms/vscode-remote/containers/non-root.
+  "remoteUser": "vscode"
+}
diff --git a/.github/workflows/bounds-bump.yml b/.github/workflows/bounds-bump.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/bounds-bump.yml
@@ -0,0 +1,17 @@
+name: Create Dependency Bump PR
+on:
+    workflow_dispatch:
+    schedule:
+        - cron: '0 8 * * 4'
+
+permissions:
+    contents: write
+    pull-requests: write
+
+jobs:
+    bump-bounds:
+        runs-on: ubuntu-latest
+        steps:
+            - uses: nomeata/haskell-bounds-bump-action@main
+              with:
+                test: true
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/dependency-review.yml
@@ -0,0 +1,20 @@
+# Dependency Review Action
+#
+# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
+#
+# Source repository: https://github.com/actions/dependency-review-action
+# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
+name: 'Dependency Review'
+on: [pull_request]
+
+permissions:
+  contents: read
+
+jobs:
+  dependency-review:
+    runs-on: ubuntu-latest
+    steps:
+      - name: 'Checkout Repository'
+        uses: actions/checkout@v3
+      - name: 'Dependency Review'
+        uses: actions/dependency-review-action@v2
diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/haskell-ci.yml
@@ -0,0 +1,199 @@
+# This GitHub workflow config has been generated by a script via
+#
+#   haskell-ci 'github' '--haddock' 'templatise.cabal'
+#
+# To regenerate the script (for example after adjusting tested-with) run
+#
+#   haskell-ci regenerate
+#
+# For more information, see https://github.com/haskell-CI/haskell-ci
+#
+# version: 0.16.3
+#
+# REGENDATA ("0.16.3",["github","--haddock","templatise.cabal"])
+#
+name: Haskell-CI
+on:
+  - push
+  - pull_request
+jobs:
+  linux:
+    name: Haskell-CI - Linux - ${{ matrix.compiler }}
+    runs-on: ubuntu-20.04
+    timeout-minutes:
+      60
+    container:
+      image: buildpack-deps:bionic
+    continue-on-error: ${{ matrix.allow-failure }}
+    strategy:
+      matrix:
+        include:
+          - compiler: ghc-9.6.2
+            compilerKind: ghc
+            compilerVersion: 9.6.2
+            setup-method: ghcup
+            allow-failure: false
+          - compiler: ghc-9.4.5
+            compilerKind: ghc
+            compilerVersion: 9.4.5
+            setup-method: ghcup
+            allow-failure: false
+          - compiler: ghc-9.2.4
+            compilerKind: ghc
+            compilerVersion: 9.2.4
+            setup-method: ghcup
+            allow-failure: false
+      fail-fast: false
+    steps:
+      - name: apt
+        run: |
+          apt-get update
+          apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
+          mkdir -p "$HOME/.ghcup/bin"
+          curl -sL https://downloads.haskell.org/ghcup/0.1.19.2/x86_64-linux-ghcup-0.1.19.2 > "$HOME/.ghcup/bin/ghcup"
+          chmod a+x "$HOME/.ghcup/bin/ghcup"
+          "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
+          "$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
+        env:
+          HCKIND: ${{ matrix.compilerKind }}
+          HCNAME: ${{ matrix.compiler }}
+          HCVER: ${{ matrix.compilerVersion }}
+      - name: Set PATH and environment variables
+        run: |
+          echo "$HOME/.cabal/bin" >> $GITHUB_PATH
+          echo "LANG=C.UTF-8" >> "$GITHUB_ENV"
+          echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
+          echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
+          HCDIR=/opt/$HCKIND/$HCVER
+          HC=$HOME/.ghcup/bin/$HCKIND-$HCVER
+          echo "HC=$HC" >> "$GITHUB_ENV"
+          echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
+          echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
+          echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.1.0 -vnormal+nowrap" >> "$GITHUB_ENV"
+          HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
+          echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
+          echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
+          echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
+          echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
+          echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
+          echo "GHCJSARITH=0" >> "$GITHUB_ENV"
+        env:
+          HCKIND: ${{ matrix.compilerKind }}
+          HCNAME: ${{ matrix.compiler }}
+          HCVER: ${{ matrix.compilerVersion }}
+      - name: env
+        run: |
+          env
+      - name: write cabal config
+        run: |
+          mkdir -p $CABAL_DIR
+          cat >> $CABAL_CONFIG <<EOF
+          remote-build-reporting: anonymous
+          write-ghc-environment-files: never
+          remote-repo-cache: $CABAL_DIR/packages
+          logs-dir:          $CABAL_DIR/logs
+          world-file:        $CABAL_DIR/world
+          extra-prog-path:   $CABAL_DIR/bin
+          symlink-bindir:    $CABAL_DIR/bin
+          installdir:        $CABAL_DIR/bin
+          build-summary:     $CABAL_DIR/logs/build.log
+          store-dir:         $CABAL_DIR/store
+          install-dirs user
+            prefix: $CABAL_DIR
+          repository hackage.haskell.org
+            url: http://hackage.haskell.org/
+          EOF
+          cat >> $CABAL_CONFIG <<EOF
+          program-default-options
+            ghc-options: $GHCJOBS +RTS -M3G -RTS
+          EOF
+          cat $CABAL_CONFIG
+      - name: versions
+        run: |
+          $HC --version || true
+          $HC --print-project-git-commit-id || true
+          $CABAL --version || true
+      - name: update cabal index
+        run: |
+          $CABAL v2-update -v
+      - name: install cabal-plan
+        run: |
+          mkdir -p $HOME/.cabal/bin
+          curl -sL https://github.com/haskell-hvr/cabal-plan/releases/download/v0.7.3.0/cabal-plan-0.7.3.0-x86_64-linux.xz > cabal-plan.xz
+          echo 'f62ccb2971567a5f638f2005ad3173dba14693a45154c1508645c52289714cb2  cabal-plan.xz' | sha256sum -c -
+          xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan
+          rm -f cabal-plan.xz
+          chmod a+x $HOME/.cabal/bin/cabal-plan
+          cabal-plan --version
+      - name: checkout
+        uses: actions/checkout@v3
+        with:
+          path: source
+      - name: initial cabal.project for sdist
+        run: |
+          touch cabal.project
+          echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project
+          cat cabal.project
+      - name: sdist
+        run: |
+          mkdir -p sdist
+          $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist
+      - name: unpack
+        run: |
+          mkdir -p unpacked
+          find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \;
+      - name: generate cabal.project
+        run: |
+          PKGDIR_templatise="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/templatise-[0-9.]*')"
+          echo "PKGDIR_templatise=${PKGDIR_templatise}" >> "$GITHUB_ENV"
+          rm -f cabal.project cabal.project.local
+          touch cabal.project
+          touch cabal.project.local
+          echo "packages: ${PKGDIR_templatise}" >> cabal.project
+          echo "package templatise" >> cabal.project
+          echo "    ghc-options: -Werror=missing-methods" >> cabal.project
+          cat >> cabal.project <<EOF
+          EOF
+          $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(templatise)$/; }' >> cabal.project.local
+          cat cabal.project
+          cat cabal.project.local
+      - name: dump install plan
+        run: |
+          $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all
+          cabal-plan
+      - name: restore cache
+        uses: actions/cache/restore@v3
+        with:
+          key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
+          path: ~/.cabal/store
+          restore-keys: ${{ runner.os }}-${{ matrix.compiler }}-
+      - name: install dependencies
+        run: |
+          $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all
+          $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all
+      - name: build w/o tests
+        run: |
+          $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
+      - name: build
+        run: |
+          $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always
+      - name: tests
+        run: |
+          $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
+      - name: cabal check
+        run: |
+          cd ${PKGDIR_templatise} || false
+          ${CABAL} -vnormal check
+      - name: haddock
+        run: |
+          $CABAL v2-haddock --disable-documentation --haddock-all $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
+      - name: unconstrained build
+        run: |
+          rm -f cabal.project.local
+          $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
+      - name: save cache
+        uses: actions/cache/save@v3
+        if: always()
+        with:
+          key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
+          path: ~/.cabal/store
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,17 @@
+name: pre-commit
+
+on:
+  pull_request:
+  push:
+    branches: [main]
+
+jobs:
+  pre-commit:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+    - uses: actions/setup-python@v3
+    - uses: haskell-actions/setup@v2
+      with:
+        ghc-version: '9.4.7'
+    - uses: pre-commit/action@v3.0.0
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,19 @@
+name: Publish to Hackage
+
+on:
+  release:
+    types: [published]
+
+permissions:
+  contents: read
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: haskell-actions/setup@v2
+      - name: cabal sdist
+        run: cabal sdist
+      - name: cabal upload
+        run: cabal upload --publish --username=${{ secrets.HACKAGE_USERNAME }} --password=${{ secrets.HACKAGE_PASSWORD }} dist-newstyle/sdist/*.tar.gz
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,27 @@
+# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
+#
+# You can adjust the behavior by modifying this file.
+# For more information, see:
+# https://github.com/actions/stale
+name: Mark stale issues and pull requests
+
+on:
+  schedule:
+  - cron: '18 10 * * *'
+
+jobs:
+  stale:
+
+    runs-on: ubuntu-latest
+    permissions:
+      issues: write
+      pull-requests: write
+
+    steps:
+    - uses: actions/stale@v5
+      with:
+        repo-token: ${{ secrets.GITHUB_TOKEN }}
+        stale-issue-message: 'Stale issue message'
+        stale-pr-message: 'Stale pull request message'
+        stale-issue-label: 'no-issue-activity'
+        stale-pr-label: 'no-pr-activity'
diff --git a/bin/initialise/Main.hs b/bin/initialise/Main.hs
--- a/bin/initialise/Main.hs
+++ b/bin/initialise/Main.hs
@@ -1,10 +1,10 @@
-module Main (main, main') where
+module Main (main) where
 
 import qualified Cabal (replace)
 import qualified Configuration (parser)
 import qualified Defaults (getDefaults)
 import qualified File (replace)
-import Initialise (Initialise, runInitialise)
+import Initialise (Initialise, defaultInitialiser, runInitialise)
 import qualified Licence (replace)
 import Options.Applicative (execParser, fullDesc, helper, info, progDesc, (<**>))
 import System.FilePath ((</>))
@@ -25,17 +25,4 @@
                 )
           )
 
-  execParser options >>= runInitialise main'
-
-main' :: Initialise ()
-main' = do
-  mapM_
-    File.replace
-    [ ".devcontainer" </> "devcontainer.json",
-      ".github" </> "workflows" </> "haskell-ci.yml",
-      "CHANGELOG.md"
-    ]
-  Licence.replace "LICENSE"
-  Cabal.replace "templatise.cabal"
-
--- TODO README
+  execParser options >>= runInitialise defaultInitialiser
diff --git a/lib/initialise/Cabal.hs b/lib/initialise/Cabal.hs
--- a/lib/initialise/Cabal.hs
+++ b/lib/initialise/Cabal.hs
@@ -28,7 +28,7 @@
 import Distribution.Fields.Field (fieldLineAnn)
 import Distribution.Parsec.Position (Position)
 import Distribution.SPDX (licenseId)
-import Initialise (Initialise)
+import Initialise.Types (Initialise)
 import System.Directory.Extra (createDirectoryIfMissing, removeDirectoryRecursive, removeFile)
 import System.FilePath (replaceBaseName, (</>))
 import Text.Parsec.Error (ParseError)
@@ -46,7 +46,7 @@
 replaceCabal :: FilePath -> Initialise ()
 replaceCabal path = do
   -- TODO handle in replaceWith
-  path' <- asks (flip replaceBaseName path . T.unpack . name)
+  path' <- asks (replaceBaseName path . T.unpack . name)
   -- TODO replaceWith convert
   contents <- liftIO $ readFile path
   contents' <- convert contents
diff --git a/lib/initialise/File.hs b/lib/initialise/File.hs
--- a/lib/initialise/File.hs
+++ b/lib/initialise/File.hs
@@ -8,7 +8,7 @@
 import Data.Text (Text)
 import qualified Data.Text as T (replace)
 import Data.Text.IO (readFile, writeFile)
-import Initialise (Initialise)
+import Initialise.Types (Initialise)
 import Prelude hiding (readFile, writeFile)
 
 replace :: FilePath -> Initialise ()
diff --git a/lib/initialise/Git.hs b/lib/initialise/Git.hs
--- a/lib/initialise/Git.hs
+++ b/lib/initialise/Git.hs
@@ -1,7 +1,10 @@
-module Git (config) where
+module Git (config, root) where
 
 import Data.Text (Text, pack, unpack)
 import System.Process (readProcess)
 
 config :: Text -> IO Text
 config key = pack . head . lines <$> readProcess "git" ["config", unpack key] ""
+
+root :: IO Text
+root = pack . head . lines <$> readProcess "git" ["rev-parse", "--show-toplevel"] ""
diff --git a/lib/initialise/Initialise.hs b/lib/initialise/Initialise.hs
--- a/lib/initialise/Initialise.hs
+++ b/lib/initialise/Initialise.hs
@@ -1,13 +1,4 @@
-module Initialise
-  ( Initialise,
-    runInitialise,
-  )
-where
-
-import Configuration (Configuration)
-import Control.Monad.Reader (ReaderT, runReaderT)
-
-type Initialise = ReaderT Configuration IO
+module Initialise (Initialise, runInitialise, defaultInitialiser) where
 
-runInitialise :: Initialise a -> Configuration -> IO a
-runInitialise = runReaderT
+import Initialise.Initialisers
+import Initialise.Types
diff --git a/lib/initialise/Initialise/Initialisers.hs b/lib/initialise/Initialise/Initialisers.hs
new file mode 100644
--- /dev/null
+++ b/lib/initialise/Initialise/Initialisers.hs
@@ -0,0 +1,23 @@
+module Initialise.Initialisers
+  ( defaultInitialiser,
+  )
+where
+
+import qualified Cabal (replace)
+import qualified File (replace)
+import Initialise.Types
+import qualified Licence (replace)
+import System.FilePath ((</>))
+
+defaultInitialiser :: Initialise ()
+defaultInitialiser = do
+  mapM_
+    File.replace
+    [ ".devcontainer" </> "devcontainer.json",
+      ".github" </> "workflows" </> "haskell-ci.yml",
+      "CHANGELOG.md"
+    ]
+  Licence.replace "LICENSE"
+  Cabal.replace "templatise.cabal"
+
+-- TODO README
diff --git a/lib/initialise/Initialise/Types.hs b/lib/initialise/Initialise/Types.hs
new file mode 100644
--- /dev/null
+++ b/lib/initialise/Initialise/Types.hs
@@ -0,0 +1,13 @@
+module Initialise.Types
+  ( Initialise,
+    runInitialise,
+  )
+where
+
+import Configuration (Configuration)
+import Control.Monad.Reader (ReaderT, runReaderT)
+
+type Initialise = ReaderT Configuration IO
+
+runInitialise :: Initialise a -> Configuration -> IO a
+runInitialise = runReaderT
diff --git a/lib/initialise/Licence.hs b/lib/initialise/Licence.hs
--- a/lib/initialise/Licence.hs
+++ b/lib/initialise/Licence.hs
@@ -7,7 +7,7 @@
 import Control.Monad.Reader (ask, liftIO)
 import Data.ByteString.Lazy (ByteString, writeFile)
 import Distribution.SPDX.LicenseId (LicenseId (Unlicense), licenseId)
-import Initialise (Initialise)
+import Initialise.Types (Initialise)
 import Network.HTTP.Client (responseBody)
 import Network.HTTP.Simple (httpLBS, parseRequest)
 import System.FilePath ((</>))
@@ -17,7 +17,7 @@
 replace p = do
   Configuration {..} <- ask
   unless (licence == Unlicense) $
-    liftIO (writeFile (p </> path) =<< contents licence)
+    liftIO (writeFile (path </> p) =<< contents licence)
 
 contents :: LicenseId -> IO ByteString
 contents l = do
diff --git a/templatise.cabal b/templatise.cabal
--- a/templatise.cabal
+++ b/templatise.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               templatise
-version:            0.1.1.0
+version:            0.1.2.0
 license:            Unlicense
 license-file:       LICENSE
 copyright:          (c) 2023 Alex Brandt
@@ -26,9 +26,12 @@
    || ==9.4.8
    || ==9.6.2
    || ==9.6.3
+   || ==9.6.4
    || ==9.8.1
 
 extra-source-files:
+  .devcontainer/devcontainer.json
+  .github/workflows/*.yml
   CHANGELOG.md
   README.md
 
@@ -53,6 +56,7 @@
     , optparse-applicative  ^>=0.18.1.0
     , process               ^>=1.6.13.2
     , text                  ^>=1.2.5.0  || ^>=2.0.2    || ^>=2.1
+    , time                  ^>=1.11.1.1 || ^>=1.12.2   || ^>=1.13
 
 library initialise-library
   import:           initialise-common
@@ -66,14 +70,16 @@
     Licence
 
   -- TODO Make Git private.
-  --other-modules: Git
+  other-modules:
+    Initialise.Initialisers
+    Initialise.Types
+
   build-depends:
     , exceptions    ^>=0.10.4
     , extra         ^>=1.7.14
     , http-client   ^>=0.7.14
     , http-conduit  ^>=2.3.8.3
     , parsec        ^>=3.1.15.0
-    , time          ^>=1.11.1.1 || ^>=1.12.2
 
   hs-source-dirs:   lib/initialise
 
@@ -104,6 +110,7 @@
     FileGolden
     GitSpec
     Hooks
+    InitialiseSpec
 
   hs-source-dirs:   test/initialise
 
diff --git a/test/initialise/ConfigurationSpec.hs b/test/initialise/ConfigurationSpec.hs
--- a/test/initialise/ConfigurationSpec.hs
+++ b/test/initialise/ConfigurationSpec.hs
@@ -6,7 +6,19 @@
 import Data.Maybe (fromJust)
 import Defaults (Defaults (..))
 import Network.URI (parseURI)
-import Options.Applicative (ParserResult (CompletionInvoked, Failure, Success), defaultPrefs, execParserPure, helper, info, renderFailure, (<**>))
+import Options.Applicative
+  ( ParserResult
+      ( CompletionInvoked,
+        Failure,
+        Success
+      ),
+    defaultPrefs,
+    execParserPure,
+    helper,
+    info,
+    renderFailure,
+    (<**>),
+  )
 import System.Exit (ExitCode (ExitFailure))
 import Test.Hspec (Expectation, Spec, describe, expectationFailure, it, shouldBe)
 
@@ -15,9 +27,15 @@
   describe "Configuration" $ do
     describe "parser" $ do
       it "should error if homepage isn't a URI" $
-        parse ["--homepage", "not-a-url"] `shouldFailWith` ("", ExitFailure 1)
+        parse ["--homepage", "not-a-url"]
+          `shouldFailWith` ( "option --homepage: cannot parse value `not-a-url'\n\nUsage:  [--name NAME] [--homepage URL] [--author AUTHOR] \n        [--maintainer MAINTAINER] [--licence LICENCE]",
+                             ExitFailure 1
+                           )
       it "should error if licence isn't an SPDX licence ID" $ do
-        parse ["--licence", "not-a-licence"] `shouldFailWith` ("", ExitFailure 1)
+        parse ["--licence", "not-a-licence"]
+          `shouldFailWith` ( "option --licence: cannot parse value `not-a-licence'\n\nUsage:  [--name NAME] [--homepage URL] [--author AUTHOR] \n        [--maintainer MAINTAINER] [--licence LICENCE]",
+                             ExitFailure 1
+                           )
 
 parse :: [String] -> ParserResult SUT.Configuration
 parse =
diff --git a/test/initialise/DefaultsSpec.hs b/test/initialise/DefaultsSpec.hs
--- a/test/initialise/DefaultsSpec.hs
+++ b/test/initialise/DefaultsSpec.hs
@@ -3,6 +3,8 @@
 module DefaultsSpec (spec) where
 
 import Data.Maybe (fromJust)
+import Data.Time (LocalTime (localDay), getCurrentTime, getCurrentTimeZone, utcToLocalTime)
+import Data.Time.Calendar.OrdinalDate (toOrdinalDate)
 import qualified Defaults as SUT
 import Hooks (withGitRepo)
 import Network.URI (parseURI)
@@ -19,13 +21,16 @@
   describe "getDefaults" $ around withGitRepo $ do
     it "inspects the current repository" $ \p -> do
       ds <- SUT.getDefaults
+      -- TODO resolve code duplication with SUT.
+      timezone <- getCurrentTimeZone
+      (dYear, _day) <- toOrdinalDate . localDay . utcToLocalTime timezone <$> getCurrentTime
       ds
         `shouldBe` SUT.Defaults
           { SUT.dOrigin = fromJust (parseURI "https://github.com/sentinel/sentinel.git"),
             SUT.dAuthor = "Sentinel",
             SUT.dMaintainer = "sentinel@example.com",
             SUT.dPath = p,
-            SUT.dYear = 2023
+            SUT.dYear = dYear
           }
 
 defaults :: SUT.Defaults
diff --git a/test/initialise/Hooks.hs b/test/initialise/Hooks.hs
--- a/test/initialise/Hooks.hs
+++ b/test/initialise/Hooks.hs
@@ -1,7 +1,11 @@
-module Hooks (withGitRepo) where
+module Hooks (withGitRepo, withProjectCopy) where
 
+import Control.Applicative ((<|>))
 import Control.Monad (void)
-import System.Directory (withCurrentDirectory)
+import Data.Text (unpack)
+import Git (root)
+import System.Directory (getCurrentDirectory, withCurrentDirectory)
+import System.FilePath (takeFileName, (</>))
 import System.IO.Temp (withSystemTempDirectory)
 import System.Process (readProcess)
 
@@ -14,3 +18,11 @@
       void $ readProcess "git" ["config", "user.email", "sentinel@example.com"] ""
       void $ readProcess "git" ["remote", "add", "origin", "https://github.com/sentinel/sentinel.git"] ""
       action p
+
+withProjectCopy :: (FilePath -> IO ()) -> IO ()
+withProjectCopy action = do
+  withSystemTempDirectory "initialise" $ \p -> do
+    r <- unpack <$> root <|> getCurrentDirectory
+    void $ readProcess "cp" ["-a", r, p] ""
+    let p' = p </> takeFileName r
+    withCurrentDirectory p' $ action p'
diff --git a/test/initialise/InitialiseSpec.hs b/test/initialise/InitialiseSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/initialise/InitialiseSpec.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module InitialiseSpec (spec) where
+
+import Configuration (Configuration (..))
+import Data.Maybe (fromJust)
+import Data.Text (Text, pack)
+import Distribution.SPDX (LicenseId (MIT))
+import Hooks (withProjectCopy)
+import qualified Initialise as SUT
+import Network.URI (parseURI)
+import System.Directory (doesFileExist)
+import System.FilePath ((</>))
+import System.Process (readProcess)
+import Test.Hspec (Spec, describe, runIO, shouldNotReturn, shouldReturn)
+
+spec :: Spec
+spec = describe "Initialisers" $ do
+  describe "defaultInitialiser" $ do
+    runIO $ withProjectCopy $ \p -> do
+      let configuration =
+            Configuration
+              { name = "sentinel",
+                homepage = fromJust (parseURI "https://github.com/sentinel/sentinel.git"),
+                author = "Sentinel",
+                maintainer = "sentinel@example.com",
+                licence = MIT,
+                path = p,
+                year = 1970
+              }
+
+      SUT.runInitialise SUT.defaultInitialiser configuration
+
+      doesFileExist (p </> "templatise.cabal") `shouldReturn` False
+      doesFileExist (p </> "sentinel.cabal") `shouldReturn` True
+
+      grep ["MIT"] [p </> "LICENSE"] `shouldNotReturn` []
+
+      let ps =
+            [ "initialise",
+              "template-hs",
+              "template.hs",
+              "templatise"
+            ]
+
+      let fs =
+            map
+              (p </>)
+              [ ".devcontainer" </> "devcontainer.json",
+                ".github" </> "workflows" </> "haskell-ci.yml",
+                "CHANGELOG.md",
+                "sentinel.cabal"
+              ]
+
+      grep ps fs `shouldReturn` []
+
+grep :: [String] -> [FilePath] -> IO [Text]
+grep ps fs = map pack . lines <$> readProcess "grep" ("-o" : unlines ps : fs) ""
diff --git a/test/initialise/Main.hs b/test/initialise/Main.hs
--- a/test/initialise/Main.hs
+++ b/test/initialise/Main.hs
@@ -1,9 +1,11 @@
 module Main (main) where
 
 import qualified CabalGolden (golden)
+import qualified ConfigurationSpec (spec)
 import qualified DefaultsSpec (spec)
 import qualified FileGolden (golden)
 import qualified GitSpec (spec)
+import qualified InitialiseSpec (spec)
 import Test.Tasty (defaultMain, testGroup)
 import Test.Tasty.Hspec (testSpecs)
 
@@ -13,8 +15,10 @@
     concat
       <$> mapM
         testSpecs
-        [ DefaultsSpec.spec,
-          GitSpec.spec
+        [ ConfigurationSpec.spec,
+          DefaultsSpec.spec,
+          GitSpec.spec,
+          InitialiseSpec.spec
         ]
   goldens <- sequence [CabalGolden.golden, FileGolden.golden]
   defaultMain $
