check / check (push) Canceled after 0s
The ARG CACHEBUST rule and the CACHED expectation now list their steps as bullets, the same way in the policy, the README and both checklists. The checklists' Python item and the policy's Python point use shorter sentences with the verb early. The TODO entry no longer counts "no Python" as an install or says "except" where only a difference is meant. Model: opus-5-5
14 KiB
14 KiB
title, last_modified
| title | last_modified |
|---|---|
| New Repo Checklist | 2026-10-21 |
Use this checklist when creating a new repository from scratch. Follow the steps
in order. Full policies are at
https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md.
Template files can be fetched from:
https://git.eeqj.de/sneak/prompts/raw/branch/main/<path>
1. Initialize
git init- Ask the user for the license (MIT, GPL, or WTFPL)
2. First Commit (README only)
- Create
README.mdwith all required sections:- Description: name, purpose, category, license, author
- Getting Started: copy-pasteable code block
- Rationale: why does this exist?
- Design: how is it structured?
- TODO: initial task list
- License: matches chosen license
- Author: @sneak
git add README.md && git commit
3. Scaffolding (feature branch)
git checkout -b initial-scaffolding
Fetch Template Files
.gitignore— fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore, then add the repo's own build outputs, such as its binaries, at the end of the file, where a re-vendor keeps them. Extensions are written to.gitignore's own semantics, where an unanchored pattern already matches at every depth: never add a**/prefix here, which is a.dockerignoreform. The canonical file already carries.claude/so agent worktrees cannot be committed by accident..editorconfig— fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.editorconfig, then add the repo's own sections, such as one for another language it uses, at the end of the file, where a re-vendor keeps them.Makefile— fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile, adapt targets for the project's language and tools- For JS/docs repos:
.prettierrcand.prettierignore— fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrcandhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierignore
Create Project Files
LICENSEfile matching the chosen licenseREPO_POLICIES.md— fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md- Guidance for coding agents, if the repo has any, is one
AGENTS.mdat the root — never a file or directory named after one agent tool, such asCLAUDE.mdor.claude/, and never separate memory files Dockerfileand.dockerignore— fetch.dockerignorefromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore- Extend
.dockerignorewith the repo's own host-built artifacts, giving every depth-independent pattern a**/prefix — but write a repo-root binary anchored,/myappand never**/myapp, which would also matchcmd/myapp/and delete the package directory. Do not transplant.gitignore's patterns:.dockerignoreanchors an unprefixed pattern at the context root, so the copied form leavesconfig/.envin the build context while reading as solved. The canonical file's.claudeentry is anchored for the same reason as a repo-root binary; leave it that way, but note that it only covers agents running at the repo root — if this repo will run them in subdirectories,services/api/.claude/needs its own anchored entry. - If the image embeds a version in a binary:
.dockerignorelets.gitinto the build context. It keeps out every gitconfigat 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.gitdirectory.git describedoes 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 aconfigsegment (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 hasgit(the Debian Go image has it; an alpine one needsapk add --no-cache git) and takes the version from theVERSIONbuild argument when one is given, otherwise fromgit 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 VERSIONhas no default, and the build fails if the context carries.gitand the version still comes out empty,devorunknown. A plaindocker 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.gitis 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 plaindocker build .fails; pass the version with--build-arg VERSION=..., asscript/dockerandscript/cibuildalready do. - The Dockerfile carries a
lintphase and atestphase, each invoking its tool directly rather than throughmakeorscript/, and the final stage carries aCOPY --from=of a harmless file from each so the image cannot be built unless both passed. Keep the final stage last: a stage nothing depends on is built only when--targetnames it. - In every stage, every step that lints, tests or compiles comes after
ARG CACHEBUST.ARG CACHEBUSTcomes right after these steps:- the steps that install system packages
- the steps that download Go's modules
- the step that runs
script/bootstrapto install a Pythontestphase's packages fromPipfile.lock
- A stage with none of these steps declares
ARG CACHEBUSTright afterFROM. A step that installs anything else as well, such as ascript/bootstrapthat also installs node packages, comes after it too. Thescript/builds pass a newCACHEBUSTvalue on every build, which reruns everyRUNstep after that line and leaves the layers above it and the cache mounts in place;--no-cachewould build those layers again and empty the mounts. A stage without theARGline is served from cache on an unchanged tree, so the scripts and every stage'sARG CACHEBUSTline are added in the same commit. - Go: each Go stage downloads the modules (
COPY go.mod go.sum ./,RUN go mod download) aboveARG 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 runalso mounts golangci-lint's (--mount=type=cache,target=/root/.cache/golangci-lint), and no step mounts anything else; bothgo testruns carry-count=1, as in the canonical GoDockerfileinREPO_POLICIES.md - Python: a
testphase that installs its packages fromPipfile.lockdoes it aboveARG CACHEBUST, withCOPY script/ script/,COPY Pipfile Pipfile.lock ./andRUN script/bootstrap. Whenscript/bootstrapruns in that step, it installs nothing beyond system packages, pipenv and its dependencies, and the packages fromPipfile.lock. It installs pipenv and its dependencies by hash, withpip install --require-hashes. It installs the packages fromPipfile.lockwithpipenv install --deploy, plus--devwhen the tests need the development packages. It installs no Python there, because the tests use the base image's. The tests run afterARG CACHEBUST, and every other stage installs its Python packages after it, as in the Pythontestphase example inREPO_POLICIES.md - Server: the final stage builds and runs the application
- Non-server: the final stage brings up the dev environment
- Image pinned by sha256 hash with version/date comment
- Extend
- Gitea Actions workflow at
.gitea/workflows/check.ymlthat runsscript/cibuildon push, checks out withpersist-credentials: falseand withfetch-depth: 0(which fetches the tagsgit describeneeds), carries theconcurrencyblock that lets a new push cancel only the same branch's older run, and setstimeout-minutes: 20on thecheckjob so a hung build frees the shared runner — referencehttps://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml - Language-specific:
- Go:
go mod init sneak.berlin/go/<name>,.golangci.yml(fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.ymland, in the same commit, set the lint phase digest to the one named in the.golangci.ymlparagraph ofREPO_POLICIES.md) - JS:
yarn init,yarn add --dev prettier - Python:
pyproject.toml
- Go:
Configure script/ Entrypoints and Makefile
Implementations live in script/ (scripts-to-rule-them-all); Makefile targets
are thin shims calling them. Model scripts:
https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>
- scripts are POSIX sh (
#!/bin/sh,set -eu, no bashisms) so they run on alpine images without bash script/bootstrap/make bootstrap— installs all dependencies, idempotently, assuming nothing (pkg manager detection nix/apt/brew/apk; node used if its major version is the pinned one, else pinned version via nvm from a hash-verified archive, with yarn and the packages installed under it; pinned yarn via corepack); a non-server repo's development environment stage runs it instead of inline installs; a gate phase or the build stage installs what its base image lacks either inline or by running itscript/setup/make setup— readies a fresh clone: runsbootstrap, theninstall-precommit, plus repo-specific initscript/test/make test—docker build --build-arg CACHEBUST="$(date +%s)-$$" --target test --output type=cacheonly ., which writes no image; the phase runs real tests, not a no-op (90-second timeout, 60-second hard cap on wall time)script/lint/make lint—docker build --build-arg CACHEBUST="$(date +%s)-$$" --target lint --output type=cacheonly ., which writes no image. No lint verdict may come from a host invocation of the linter.script/fmt/make fmt— formats code (writes; native, never in a container)script/fmt-check/make fmt-check— checks formatting (read-only; native)script/check/make check— runstest,lint,fmt-check; must not modify filesscript/projectname— outputs the project name (used byscript/dockerfor the image tag)script/docker/make docker— builds Docker image, tagged viascript/projectname(byte-identical across repos); a newCACHEBUSTvalue on every build, plus the version as a build argscript/cibuild— cd to repo root, runscript/bootstrap, runscript/check, thendocker build --build-arg CACHEBUST="$(date +%s)-$$" --build-arg VERSION="$version" -t "$tag" .(what CI runs), with$versionfromgit describe --tags --always --dirty(unknownif empty) and$tagfromscript/projectname, each assigned on its own line before the build. The bootstrap is required: CI checks out and runs this alone, andscript/fmt-checkruns the formatter on the host.script/fmtandscript/fmt-checkpick theiryarnwith the same test asscript/bootstrap: theyarnonPATHwhen the node onPATHhas the pinned major version, and otherwise yarn under the pinned node, with nvm loaded through$HOME/.nvm/nvm.sh.script/bootstrapleaves the node and yarn it installs under nvm off thePATHof the shell that called it, so a bareyarnexits 127 on a runner carrying nothing but docker and git, or runs under another node.- No
docker buildinscript/leaves a dangling image behind:script/lintandscript/testwrite no image, andscript/dockerandscript/cibuildtag theirs script/precommit— called by the pre-commit hook; runsscript/checkscript/install-precommit— installs the pre-commit hook that runsscript/precommitmake hooks— shims toscript/install-precommit- README Entrypoints section documents the scripts and links the standard
4. Verify
make checkpassesmake dockersucceedsscript/cibuildsucceeds in a fresh clone on a host carrying what CI has (the runner imagedocker.gitea.com/runner-images:ubuntu-latest: docker, git and node 24, with no yarn onPATH), and demonstrably executed the checks — a sub-second build, orCACHEDon a step that lints or tests, one of theRUNsteps belowARG CACHEBUST, means nothing ran. Above that line, these steps are expected to showCACHED:- the steps that install system packages
- the steps that download Go's modules
- the step that runs
script/bootstrapto install a Pythontestphase's packages fromPipfile.lock
- Plant a lint violation and confirm both
make lintand a plaindocker build .fail on it; revert. A plain build that passes proves the final stage is missing itsCOPY --from=edge to the gate phases. - No secrets in repo, and none in the build context: enumerate a probe image
rather than reading
.dockerignore - No mutable image/package references
- No unnecessary files in repo root
- All dates written as YYYY-MM-DD
5. Merge and Set Up
- Commit, merge to
main make hooksto install pre-commit hook- Add remote and push
- Verify
mainpassesmake check