check / check (push) Waiting to run
A Python `test` phase that installs its packages from `Pipfile.lock` now does so above `ARG CACHEBUST`, through `script/bootstrap`, in a layer of its own. Check builds keep that layer as they keep Go's modules, and the tests still run after the line on every build. In that step `script/bootstrap` installs only system packages, pipenv and its dependencies by hash, and the lockfile's packages. The policy, both checklists and the README list the steps above the line the same way; the policy has a short example. This writes down sneak's ruling at sneak/rfscan#80 (comment). rfscan installs pipenv by version, not by hash, so its `script/bootstrap` does not meet this rule yet. Model: opus-5-5
260 lines
15 KiB
Markdown
260 lines
15 KiB
Markdown
---
|
|
title: Existing Repo Checklist
|
|
last_modified: 2026-10-26
|
|
---
|
|
|
|
Use this checklist when beginning work in a repo that may not yet conform to our
|
|
repository policies
|
|
(`https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md`).
|
|
|
|
Work on a feature branch. Check each item and fix any gaps before proceeding
|
|
with your task.
|
|
|
|
# Formatting (do this first)
|
|
|
|
- [ ] If the repo has never been formatted to our standards, run `make fmt` and
|
|
commit the result as a standalone branch/commit/PR before any other
|
|
changes. Formatting diffs can be large and should not be mixed with
|
|
functional changes.
|
|
|
|
# Required Files
|
|
|
|
- [ ] `README.md` exists with all required sections (Description, Getting
|
|
Started, Rationale, Design, TODO, License, Author)
|
|
- [ ] `LICENSE` file exists and matches the README
|
|
- [ ] `REPO_POLICIES.md` exists and version date is current — fetch from
|
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md`
|
|
- [ ] Guidance for coding agents, if the repo has any, is one `AGENTS.md` at the
|
|
root — never a file or directory named after one agent tool, such as
|
|
`CLAUDE.md` or `.claude/`, and never separate memory files. Move what any
|
|
such committed file says into `AGENTS.md` and delete it.
|
|
- [ ] `.gitignore` is comprehensive (OS, editor, agent scratch, secrets, the
|
|
repo's own build outputs) — fetch from
|
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` if missing.
|
|
An existing repo usually has a hand-written one that is never re-fetched,
|
|
so check the entries rather than the file's presence. The file is the
|
|
canonical content followed by the repo's own entries, such as its
|
|
binaries; a re-vendor replaces the canonical part and keeps those entries.
|
|
- [ ] `.editorconfig` exists — fetch from
|
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.editorconfig`. The
|
|
file is the canonical content followed by the repo's own sections, such as
|
|
one for another language it uses; a re-vendor replaces the canonical part
|
|
and keeps those sections.
|
|
- [ ] `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.
|
|
- [ ] In every stage, every step that lints, tests or compiles comes after
|
|
`ARG CACHEBUST`. `ARG CACHEBUST` comes right after these steps:
|
|
- the steps that install system packages
|
|
- the steps that download Go's modules
|
|
- the step that runs `script/bootstrap` to install a Python `test` phase's
|
|
packages from `Pipfile.lock`
|
|
- [ ] A stage with none of these steps declares `ARG CACHEBUST` right after
|
|
`FROM`. A step that installs anything else as well, such as a
|
|
`script/bootstrap` that also installs node packages, comes after it too.
|
|
The `script/` builds pass a new `CACHEBUST` value on every build, which
|
|
reruns every `RUN` step after that line and leaves the layers above it and
|
|
the cache mounts in place; `--no-cache` would build those layers again and
|
|
empty the mounts. A stage without the `ARG` line is served from cache on
|
|
an unchanged tree, so the scripts and the `ARG` lines are re-vendored in
|
|
one commit.
|
|
- [ ] Go: each Go stage downloads the modules (`COPY go.mod go.sum ./`,
|
|
`RUN go mod download`) above `ARG CACHEBUST`; the steps that compile
|
|
(`golangci-lint run`, `go test`, `go build`) mount Go's build cache
|
|
(`--mount=type=cache,target=/root/.cache/go-build`), `golangci-lint run`
|
|
also mounts golangci-lint's
|
|
(`--mount=type=cache,target=/root/.cache/golangci-lint`), and no step
|
|
mounts anything else; both `go test` runs carry `-count=1`, as in the
|
|
canonical Go `Dockerfile` in `REPO_POLICIES.md`
|
|
- [ ] Python: a `test` phase that installs its packages from `Pipfile.lock` does
|
|
it above `ARG CACHEBUST`, with `COPY script/ script/`,
|
|
`COPY Pipfile Pipfile.lock ./` and `RUN script/bootstrap`. When
|
|
`script/bootstrap` runs in that step, it installs nothing beyond system
|
|
packages, pipenv and its dependencies, and the packages from
|
|
`Pipfile.lock`. It installs pipenv and its dependencies by hash, with
|
|
`pip install --require-hashes`. It installs the packages from
|
|
`Pipfile.lock` with `pipenv install --deploy`, plus `--dev` when the tests
|
|
need the development packages. It installs no Python there, because the
|
|
tests use the base image's. The tests run after `ARG CACHEBUST`, and every
|
|
other stage installs its Python packages after it, as in the Python `test`
|
|
phase example in `REPO_POLICIES.md`
|
|
- [ ] 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 `.claude` are unprefixed, and
|
|
`.gitignore`'s patterns have not been transplanted unmodified — the
|
|
transplanted form leaves `config/.env` and `certs/server.key` in the build
|
|
context while reading as solved
|
|
- [ ] `.dockerignore` excludes the repo's own host-built artifacts (compiled
|
|
binaries, test binaries, coverage output), written root-anchored —
|
|
`/myapp`, never `**/myapp`. An existing repo is where such a binary is
|
|
likeliest to already be sitting in the build context, invisible to git.
|
|
- [ ] `.claude/` is in `.gitignore` (unanchored) and `.claude` in
|
|
`.dockerignore` (anchored, no `**/` prefix). Agent worktrees are entire
|
|
checkouts of the repo, so they inflate the context by a multiple of it and
|
|
can copy another session's unreviewed work into an image layer. If agents
|
|
here run anywhere other than the repo root, the anchored entry misses
|
|
`services/api/.claude/`: add anchored entries for those directories.
|
|
- [ ] If the repo embeds a version in a binary: `.dockerignore` lets `.git` into
|
|
the build context. It keeps out every git `config` at any depth
|
|
(`**/.git/config`, `**/.git/modules/**/config`): the repository's own,
|
|
each submodule's under `.git/modules/`, and that of a submodule keeping
|
|
its own `.git` directory. `git describe` does not need them, and each can
|
|
hold a credential: a password in a remote URL, or the token the CI
|
|
checkout step stores there. A submodule whose name has a `config` segment
|
|
(`config`, `deploy/config`, `config/lib`) loses its whole git directory to
|
|
`**/.git/modules/**/config`, and Go's version stamping then fails the
|
|
build: give it a name without that segment (`git submodule add --name`).
|
|
The stage that compiles has `git` (the Debian Go image has it; an alpine
|
|
one needs `apk add --no-cache git`) and takes the version from the
|
|
`VERSION` build argument when one is given, otherwise from
|
|
`git describe --tags --always`. That gives the tag on a tagged commit; on
|
|
a later commit, the tag, the number of commits since it and the short
|
|
commit (`v1.2.3-4-gabc1234`); and the short commit when no tag is
|
|
reachable. The stage that compiles also marks its working directory safe
|
|
for git (`git config --system --add safe.directory /src`): a context sent
|
|
as a tar stream keeps the sender's file owners, and git refuses a checkout
|
|
owned by another user, so the version would come out empty. `ARG VERSION`
|
|
has no default, and the build fails if the context carries `.git` and the
|
|
version still comes out empty, `dev` or `unknown`. A plain
|
|
`docker build .` with no build arguments must succeed; a Dockerfile that
|
|
refuses an empty build argument drops that refusal and keeps the argument.
|
|
A checkout whose `.git` is a file (a linked worktree, or a repository
|
|
checked out as a submodule) is the exception: that file points to a git
|
|
directory outside the build context, so the build cannot read the version
|
|
and a plain `docker build .` fails; pass the version with
|
|
`--build-arg VERSION=...`. `script/docker` and `script/cibuild` already
|
|
pass the version they compute on the host; it takes precedence. The
|
|
canonical `.gitea/workflows/check.yml` sets `fetch-depth: 0` on its
|
|
checkout step, which otherwise clones shallow and fetches no tags, so a CI
|
|
build finds the tag too.
|
|
- [ ] Gitea Actions workflow in `.gitea/workflows/` runs `script/cibuild` on
|
|
push, checks out with `persist-credentials: false` and with
|
|
`fetch-depth: 0` (which fetches the tags `git describe` needs), carries
|
|
the `concurrency` block that lets a new push cancel only the same branch's
|
|
older run, and sets `timeout-minutes: 20` on the `check` job so a hung
|
|
build frees the shared runner — reference
|
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml`
|
|
- [ ] Language-specific config:
|
|
- [ ] Go: `go.mod`, `go.sum`, `.golangci.yml` (fetch from
|
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml` and,
|
|
in the same commit, set the lint phase digest to the one named in the
|
|
`.golangci.yml` paragraph of `REPO_POLICIES.md`)
|
|
- [ ] JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
|
|
(fetch from
|
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrc` and
|
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierignore`)
|
|
- [ ] Python: `pyproject.toml`
|
|
- [ ] Docs/writing: `.prettierrc`, `.prettierignore` (same URLs as above)
|
|
|
|
# Makefile and script/ Entrypoints
|
|
|
|
- [ ] `Makefile` exists in root — reference
|
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile`
|
|
- [ ] Has targets: `test`, `lint`, `fmt`, `fmt-check`, `check`, `docker`,
|
|
`hooks`
|
|
- [ ] Target implementations live in `script/` (scripts-to-rule-them-all);
|
|
Makefile targets are thin shims calling them — model scripts at
|
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`
|
|
- [ ] `script/precommit` exists and the pre-commit hook (installed by
|
|
`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` each run
|
|
`docker build --build-arg CACHEBUST="$(date +%s)-$$" --target <phase> --output type=cacheonly .`,
|
|
which builds their phase by name and writes no image, 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.
|
|
- [ ] No `docker build` in `script/` leaves a dangling image behind:
|
|
`script/lint` and `script/test` write no image, and `script/docker` and
|
|
`script/cibuild` tag theirs. A build that writes an untagged image leaves
|
|
one behind on every run, on every host and CI runner.
|
|
- [ ] `script/cibuild` runs `script/bootstrap` before `script/check`, and builds
|
|
the image with a new `CACHEBUST` value on every build, like every
|
|
`docker build` in `script/`. 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` pick their `yarn` with the same test
|
|
as `script/bootstrap`: the `yarn` on `PATH` when the node on `PATH` has
|
|
the pinned major version, and otherwise yarn under the pinned node, with
|
|
nvm loaded through `$HOME/.nvm/nvm.sh`. `script/bootstrap` leaves the node
|
|
and yarn it installs under nvm off the `PATH` of the shell that called it,
|
|
so a bare `yarn` exits 127 on a runner carrying nothing but docker and
|
|
git, or runs under another node.
|
|
- [ ] `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
|
|
bug)
|
|
- [ ] `make test` runs real tests, not a no-op (at minimum, import/compile
|
|
check)
|
|
- [ ] `make check` passes on current branch
|
|
|
|
# Formatting
|
|
|
|
- [ ] Platform-standard formatter is configured (`black`, `prettier`, `go fmt`)
|
|
- [ ] Default formatter config, only exception: four-space indents (except Go)
|
|
- [ ] All files pass `make fmt-check`
|
|
|
|
# Git Hygiene
|
|
|
|
- [ ] Pre-commit hook is installed (`make hooks`)
|
|
- [ ] No secrets in the repo (`.env`, keys, credentials)
|
|
- [ ] No mutable references in Dockerfiles or scripts (tags, `@latest`) — all
|
|
pinned by cryptographic hash with version/date comment
|
|
- [ ] Using `yarn`, not `npm` (JS projects)
|
|
|
|
# Directory Structure
|
|
|
|
- [ ] No unnecessary files in repo root
|
|
- [ ] Files organized into canonical subdirectories (`bin/`, `cmd/`, `docs/`,
|
|
`internal/`, `static/`, etc.)
|
|
- [ ] Go migrations in `internal/db/migrations/` and embedded in binary
|
|
|
|
# HTTP Service Hardening (if targeting 1.0 and the repo is an HTTP/web service)
|
|
|
|
- [ ] Security headers set on all responses (HSTS, CSP, X-Frame-Options,
|
|
X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
|
|
- [ ] Request body size limits enforced on all endpoints
|
|
- [ ] Read/write/idle timeouts configured on the HTTP server (slowloris defense)
|
|
- [ ] Per-handler execution time limits in place
|
|
- [ ] Password-based auth endpoints are rate-limited
|
|
- [ ] CSRF tokens on all state-mutating HTML forms
|
|
- [ ] Passwords hashed with bcrypt, scrypt, or argon2
|
|
- [ ] Session cookies use HttpOnly, Secure, and SameSite attributes
|
|
- [ ] True client IP correctly detected behind reverse proxy (trusted proxy
|
|
allowlist configured)
|
|
- [ ] CORS restricted to explicit origin allowlist for authenticated endpoints
|
|
- [ ] Error responses do not leak stack traces, SQL queries, or internal paths
|
|
|
|
# Final
|
|
|
|
- [ ] `make check` passes
|
|
- [ ] `script/cibuild` succeeds in a fresh clone on a host carrying what CI has
|
|
(the runner image `docker.gitea.com/runner-images:ubuntu-latest`: docker,
|
|
git and node 24, with no yarn on `PATH`), and demonstrably executed the
|
|
checks — a sub-second build, or `CACHED` on a step that lints or tests,
|
|
one of the `RUN` steps below `ARG CACHEBUST`, means nothing ran. Above
|
|
that line, these steps are expected to show `CACHED`:
|
|
- the steps that install system packages
|
|
- the steps that download Go's modules
|
|
- the step that runs `script/bootstrap` to install a Python `test` phase's
|
|
packages from `Pipfile.lock`
|
|
- [ ] 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
|