Re-vendor the canonical files from sneak/prompts at dd4027b (closes #95)
check / check (push) Waiting to run

The shared files are the copies at sneak/prompts commit dd4027b, with this
repository's own entries kept after them. golangci-lint is v2.14.0 and
raises no findings. Lint and test are phases of the Dockerfile, built by
script/lint and script/test; the tests run under the race detector as
nobody, so Dockerfile.lint, script/verify-lint-image-pin and make
test-race are gone. Every docker build in script/ passes --no-cache in
place of the old cache-busting build argument, and script/cibuild runs
script/bootstrap and script/check before the image build.
.claude/settings.json is deleted.

Deviation: the workflow keeps fetch-depth: 0 for the tag-derived version.
Deviation: .gitignore keeps the scan database patterns.
Deviation: prettier still runs in Docker, not on the host.
Over the cap: make test takes about 60 seconds on this host.

Model: opus-5-5
This commit is contained in:
clawbot
2026-10-07 21:46:20 +00:00
parent 0064eba542
commit b5819282f3
19 changed files with 664 additions and 579 deletions
+38 -77
View File
@@ -741,44 +741,26 @@ entrypoints are:
presence-checked only. `golangci-lint` and prettier are deliberately **not**
installed: they run in Docker (see `script/lint` and `script/fmt`) and never
from a host install, so there is no host copy to drift from the pin. A missing
`docker` is warned about rather than installed or treated as fatal —
everything except linting, formatting and `make test-race` works without it.
Ends with `go mod download`.
`docker` is warned about rather than installed or treated as fatal:
`make build` works without it, and testing, linting, formatting and the image
build need it. Ends with `go mod download`.
- `script/setup` — make a fresh clone ready for development: runs
`script/bootstrap`, then `script/install-precommit`.
- `script/projectname` — print this project's name (`sfdupes`). Scripts that
need the name call it, so they stay identical across repositories.
- `script/test` — run the test suite with a 30-second timeout and coverage
enabled, rerunning verbosely on failure so the logs show which test failed.
- `script/test-race` — run the test suite under the race detector with a
60-second timeout. The detector needs cgo and a C compiler, which the build
never uses, so the tests run in a digest-pinned Debian `golang` image that has
`gcc`, with the checkout mounted read-only; the container is removed when it
exits. They run as the calling user, or as `nobody` when that is root, because
several tests make a file unreadable and root reads it anyway; only then must
the checkout be readable by other users. Not part of `script/check`. Every run
starts with empty caches, so it needs the network and takes minutes, and the
mount needs a local docker daemon.
- `script/lint` — run the linter. It builds `Dockerfile.lint`, which copies the
repository into the digest-pinned `golangci/golangci-lint` image and runs
`golangci-lint config verify` and `golangci-lint run` as build steps, so a
successful build is a clean lint. That exit status is all it produces, so it
runs with `--output=type=cacheonly` and writes no image; a run leaves only
build cache. The linter is never run on the host, which makes a working
`docker` the one prerequisite for linting — and therefore for `make check` and
the pre-commit hook. Offline machines: the gate steps themselves make no
network calls. `golangci-lint run` does not, and neither does
`golangci-lint config verify` — it validates against a schema the pinned
binary embeds, measured under `--network none` to both pass a valid config and
reject an invalid one. The build around them does. `Dockerfile.lint` runs
`go mod download` before the gates and this module has external dependencies,
so a first lint on a machine with a cold BuildKit cache reaches the network
there (as well as pulling the pinned image); under `--network none` it fails
at that step, before any gate. That layer sits above the gates and stays
cached, so once it is warm `script/lint` — and with it `make check` — runs
entirely offline, until `go.mod` or `go.sum` changes and the download layer
goes cold again. Because the daemon only ever sees a build context, this works
when the docker daemon is remote and bind mounts are impossible.
- `script/test` — run the test suite under the race detector, with a 90-second
timeout and coverage enabled, rerunning verbosely on failure so the logs show
which test failed. It builds the `Dockerfile`'s `test` phase alone, tagged
`sfdupes-test`. The race detector needs cgo and a C compiler, which the build
never uses, so the phase starts from a digest-pinned Debian `golang` image,
which has `gcc`. The tests run as `nobody`, because several of them make a
file unreadable and root reads it anyway.
- `script/lint` — run the linter. It builds the `Dockerfile`'s `lint` phase
alone, tagged `sfdupes-lint`: in the digest-pinned `golangci/golangci-lint`
image, the gofmt check, `golangci-lint config verify` and `golangci-lint run`
run as build steps, so a successful build is a clean lint. The linter is never
run on the host, which makes a working `docker` the one prerequisite for
linting.
- `script/fmt` — format in place: the Go sources with `gofmt -s -w`, and every
Markdown file with prettier, at the settings in `.prettierrc` (4-space
indents, prose wrapped at 80 columns). prettier is pinned by hash through
@@ -791,51 +773,32 @@ entrypoints are:
repository mounted read-only: prints any unformatted file and exits non-zero
instead of writing. gofmt and prettier both run every time, and each names
itself when it fails. The `Dockerfile` runs the same two checks as gates: the
gofmt check in its lint stage, prettier in its `markdown` stage.
gofmt check in its `lint` phase, prettier in its `markdown` stage.
- `script/check` — run `script/test`, `script/lint`, and `script/fmt-check`, in
that order. Modifies nothing. Needs `docker`, because `script/lint` and
`script/fmt-check` do.
that order. Modifies nothing. Needs `docker`, because all three do.
- `script/docker` — build the Docker image, tagged with the name from
`script/projectname`. The `Dockerfile` runs the gates as build steps, so this
is also the check a developer or reviewer runs by hand.
- `script/cibuild` — build the Docker image untagged. This is what the Gitea
workflow runs on push; because the gates run as build steps, a successful
build implies the repository is green.
`script/projectname`, passing the output of
`git describe --tags --always --dirty` (or `unknown` when that is empty) as
the `VERSION` build argument. The `Dockerfile`'s build stage depends on its
`lint`, `test` and `markdown` stages, so this is also the check a developer or
reviewer runs by hand.
- `script/cibuild` — run `script/bootstrap`, then `script/check`, then build the
image as `script/docker` does. This is what the Gitea workflow runs on push; a
successful run means every check passed. The tests and the linter run twice:
once in `script/check`, and again as stages of the image build.
- `script/precommit` — run by the git pre-commit hook: `go mod tidy` must be a
no-op (a resulting change to `go.mod` or `go.sum` fails the commit), then
`script/check`.
- `script/install-precommit` — install the git pre-commit hook that runs
`script/precommit`. The hook is written to the common git directory, so the
main checkout and every worktree share it.
- `script/verify-lint-image-pin` — fail unless the `golangci/golangci-lint`
reference in `Dockerfile.lint` and the one in the `Dockerfile` lint stage are
the same image at the same digest, naming both if not. The linter is pinned in
those two files and nothing else keeps them in sync, so a bump applied to one
alone would leave `make lint` and the `Dockerfile`'s fail-fast lint stage
checking the same tree against different rulesets, both green. The guard
restates neither pin — a third copy would be the same drift one file further
out — and runs as a gate in both files, so `make lint`, `make check` and
`make docker` all catch it.
`script/verify-linter-pin` used to live here. It compared a linter binary
against a version pin in `script/bootstrap`, and both of its subjects are gone:
no linter binary is copied between build stages any more, and bootstrap pins no
version because it installs no linter. The drift it existed to catch has moved
from binary-versus-pin to pin-versus-pin, which is what
`script/verify-lint-image-pin` above checks.
`script/lint`, `script/docker` and `script/cibuild` all pass a freshly computed
`CHECK_EPOCH` build argument, and the gate steps in `Dockerfile.lint` and
`Dockerfile` reference it. Without that, an unchanged tree lets Docker serve the
gate layers from cache and the build exits 0 having executed no tests and no
lint — a green it never earned, and one this repository has produced twice.
`CHECK_EPOCH` invalidates the gate layers on every run while leaving the pinned
base images and the dependency layers cached. Each script's value carries the
process id as well as the epoch, because two runs land inside the same second
easily and a bare epoch would cache the second one. Each `Dockerfile` stage with
gates fails when the value is empty, so a bare `docker build .` stops with
`CHECK_EPOCH is unset; build via script/cibuild or script/docker` instead of
serving the gates from cache.
Every `docker build` in `script/` passes `--no-cache`. On an unchanged tree
Docker would serve the gate steps from its cache, and the build would pass
having run no test and no linter. Every run therefore downloads the Go modules
again and needs the network. `script/test` and `script/lint` send the daemon
only a build context, so they work against a remote docker daemon; `script/fmt`
and `script/fmt-check` mount the repository and need a local one.
## Build
@@ -847,17 +810,15 @@ compile recipe:
the default target.
- `make bootstrap` — install the build and development dependencies.
- `make setup` — prepare a fresh clone: `bootstrap` plus the pre-commit hook.
- `make test` — run the test suite (30-second timeout; reruns with `-v` on
failure).
- `make test-race` — run the test suite under the race detector, in Docker (see
`script/test-race`); requires `docker`. Not part of `make check`.
- `make lint` — run `golangci-lint` with the repo config, in Docker (see
`script/lint`); requires `docker`.
- `make test` — run the test suite under the race detector, in Docker (90-second
timeout; reruns with `-v` on failure; see `script/test`); requires `docker`.
- `make lint` — run `golangci-lint` with the repo config and the gofmt check, in
Docker (see `script/lint`); requires `docker`.
- `make fmt` / `make fmt-check` — format the Go sources and the Markdown /
verify formatting without writing; requires `docker`, for prettier (see
`script/fmt`).
- `make check` — `test`, `lint`, and `fmt-check`; modifies nothing. Requires
`docker`, via `lint` and `fmt-check`.
`docker`.
- `make docker` — build the Docker image, which runs the gates as build stages.
- `make hooks` — install the pre-commit hook.
- `make clean` — remove the binary.