Files
prompts/prompts/EXISTING_REPO_CHECKLIST.md
T
sneak 3dc8b419b8
check / check (push) Canceled after 0s
Install node, yarn and python from the distribution (closes #146)
Follows sneak's ruling at
#146 (comment). Runtimes
and the installer tools bundled with python come from the distribution,
with no version and no hash. Third-party packages stay pinned by hash.

script/bootstrap drops nvm and corepack. It installs node, yarn and the
node packages only when yarn.lock is in the tree, and stops there when
yarn's major version is not 1. This repository's Dockerfile starts from
Alpine 3.24. The Python example starts from debian:trixie-slim, keeps
pipenv at $HOME/.pipenv on a host and in an image, and sets
PIP_ONLY_BINARY=:all:, so pip downloads no build tools without a hash
check.

Model: opus-5-5
2026-11-04 07:26:19 +00:00

17 KiB

title, last_modified
title last_modified
Existing Repo Checklist 2026-11-04

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 also installs anything else comes after ARG CACHEBUST too. A script/bootstrap that also installs node packages is one. 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; a first run that timed out gets no rerun, 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 these:

    • system packages
    • pipenv and its dependencies
    • the packages from Pipfile.lock

    It creates a virtual environment at $HOME/.pipenv with python3 -m venv when it is missing, then uses that environment's pip and pipenv. On a host, it first installs the distribution's python and venv packages when they are missing. 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 sets PIP_ONLY_BINARY=:all: for both installs, so a package with no wheel for the image stops the install. In that step it installs no Python, because a step above it already did. Pipfile names no python_version, because --deploy stops when the distribution's Python differs from it. 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/
    • 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. Some submodule names have a config segment, as in config, deploy/config or config/lib. Such a submodule 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, and an alpine one needs apk add --no-cache git. That stage 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
    • the tag, the number of commits since it and the short commit (v1.2.3-4-gabc1234), on a later commit
    • 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. 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 is the exception: a linked worktree, or a repository checked out as a submodule. 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/bootstrap installs node, yarn and the node packages only when yarn.lock is in the tree. node and yarn come from the distribution when they are missing. With yarn.lock in the tree, script/bootstrap stops with a one-line message when yarn's major version is not 1.
  • A Dockerfile stage copies yarn.lock before it runs script/bootstrap only if that stage needs yarn. A stage that needs yarn starts from an image whose distribution packages yarn 1, such as Alpine.
  • script/bootstrap, script/fmt and script/fmt-check run yarn by whichever name is present: yarn, or yarnpkg on Debian and Ubuntu. With neither name on PATH, they print one line saying to run script/bootstrap, and exit 1.
  • 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. The one exception is a package from the distribution's own package index, which needs no version and no hash. Pulling that index is also allowed. In a Dockerfile, the distribution is the pinned base image's. On a host, it is the host's, and the host's package manager stands for it, nix and brew included. A package source that a Dockerfile adds itself does not get this exception
  • node, yarn, npm, python, pip and venv come from the distribution wherever possible, never from nvm, pyenv, corepack or npm install -g yarn. If the distribution's version is too old, use a newer distribution
  • 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. That host is the runner image docker.gitea.com/runner-images:ubuntu-latest, which has docker, git and node 24. Take any yarn off PATH first. The run must show that the checks executed. A sub-second build means nothing ran. So does CACHED on a step that lints or tests, one of the RUN steps below ARG CACHEBUST. 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