Gate the build on Docker lint and test phases (closes #40, closes #30)
check / check (push) Successful in 5m12s

Per the owner ruling on issue 40, linting and testing are phases of the
main Dockerfile rather than a separate lint file. script/lint and
script/test build one phase each by name with caching disabled, and the
final stage copies a harmless file from each so the image cannot be built
unless both passed. A stage that is not the last is built only when
something depends on it or --target names it, so the gates are invoked by
name and the edges kept. script/check runs the gates and builds no image
of its own; script/cibuild bootstraps first, because CI runs it alone and
fmt-check is native. fmt and fmt-check source nvm for the pinned node
before calling yarn, which bootstrap installs but leaves off its caller's
PATH. Every build in script/ is tagged and uncached. Issue 30 closes too:
a container has its own lint cache and lock.

Model: opus-5
This commit is contained in:
2026-09-09 11:44:59 +00:00
parent 7b55c444ae
commit c4d5546e86
14 changed files with 381 additions and 136 deletions
+40 -5
View File
@@ -31,10 +31,18 @@ with your task.
so check the entries rather than the file's presence.
- [ ] `.editorconfig` exists — fetch from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.editorconfig`
- [ ] `Dockerfile` and `.dockerignore` exist; Dockerfile runs `make check` as a
build step, and `script/cibuild` and `script/docker` build it with
`--no-cache` — fetch `.dockerignore` from
- [ ] `Dockerfile` and `.dockerignore` exist; the Dockerfile carries a `lint`
phase and a `test` phase, and the final stage carries a `COPY --from=` of
a harmless file from each — fetch `.dockerignore` from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore`
- [ ] Nothing has been appended after the final stage, and the gate phases are
reachable from it. A stage nothing depends on is built only when
`--target` names it, so a lost `COPY --from=` edge leaves `docker build .`
passing while the gate never runs. Confirm by planting a violation, not by
reading the file.
- [ ] The gate phases invoke their tools directly, never through `make lint` or
`script/test` — those are themselves a `docker build` and would recurse
inside a build step
- [ ] Every depth-independent pattern in `.dockerignore` carries a `**/` prefix,
only genuinely root-anchored entries such as `.git` are unprefixed, and
`.gitignore`'s patterns have not been transplanted unmodified — the
@@ -81,6 +89,29 @@ with your task.
`script/install-precommit`, shimmed by `make hooks`) runs it
- [ ] README has an **Entrypoints** section documenting the `script/`
entrypoints and linking the standard
- [ ] `script/lint` and `script/test` build their phase by name
(`docker build --no-cache --target <phase> -t <name>-<phase> .`), and no
host invocation anywhere in the repo can produce a lint verdict — grep for
the linter's own name across `script/`, the `Makefile` and CI config, not
just `script/lint`. A second path is likeliest here: a `make lint-fast`,
an older host-versus-container branch, or a CI step calling the binary
directly. `script/fmt` and `script/fmt-check` are expected hits and stay
on the host.
- [ ] Every `docker build` in `script/` is tagged — an untagged one leaves a
dangling image behind on every run, on every host and CI runner
- [ ] `script/cibuild` runs `script/bootstrap` before `script/check`, and builds
the image with `--no-cache`. Without the bootstrap the CI run dies in
`script/fmt-check`, which runs the formatter on the host and finds nothing
installed.
- [ ] `script/fmt` and `script/fmt-check` source nvm for the pinned node version
before invoking `yarn`, as `script/bootstrap`'s own install step does.
`script/bootstrap` leaves the node and yarn it installs off the `PATH` of
the shell that called it, so a bare `yarn` exits 127 on a runner carrying
nothing but docker and git.
- [ ] `script/bootstrap` installs no linter of its own — delete the block, its
version variables and its call site. A JS repo's `yarn install` stays; it
brings a linter along with every other dependency, and no verdict is taken
from it.
- [ ] `make check` does not modify any files in the repo
- [ ] `make test` has a 90-second timeout and completes within the 60-second
hard cap (over 20 seconds is green but must be filed as an improvement
@@ -129,6 +160,10 @@ with your task.
# Final
- [ ] `make check` passes
- [ ] `script/cibuild` succeeds and demonstrably executed the checks — a
sub-second build, or `CACHED` on a check layer, means nothing ran
- [ ] `script/cibuild` succeeds in a fresh clone on a host carrying nothing but
docker and git, with no node or yarn on `PATH`, which is what CI has, and
demonstrably executed the checks — a sub-second build, or `CACHED` on a
gate layer, means nothing ran
- [ ] A planted lint violation fails both `make lint` and a plain
`docker build .`; revert it afterwards
- [ ] Commit and merge fixes before starting your actual task