Files
prompts/prompts/EXISTING_REPO_CHECKLIST.md
T
sneak 22b2f19a4c
check / check (push) Waiting to run
Install the pinned node when the installed one is another major version (closes #118)
script/bootstrap used whatever node was installed, so on the CI runner
image, which ships node 24, the pinned yarn 1.22.22 printed the
deprecation warning DEP0169 on every bootstrap. It now uses the
installed node only when its major version is the pinned one, and
otherwise installs the pinned node with nvm, as it already did when node
was missing, with yarn and the packages under it. It prints the node
version it uses. Only the major is compared because the Dockerfile
stages use a node 22 alpine image that nvm cannot replace. script/fmt
and script/fmt-check carry the same test to pick their yarn: the yarn on
PATH, or yarn under the pinned node loaded through nvm.

Model: opus-5-5
2026-10-08 01:34:57 +00:00

13 KiB

title, last_modified
title last_modified
Existing Repo Checklist 2026-10-08

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.
  • 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 --no-cache --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 --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 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 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