--- title: New Repo Checklist last_modified: 2026-10-04 --- 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/` # 1. Initialize - [ ] `git init` - [ ] Ask the user for the license (MIT, GPL, or WTFPL) # 2. First Commit (README only) - [ ] Create `README.md` with 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](https://sneak.berlin) - [ ] `git add README.md && git commit` # 3. Scaffolding (feature branch) - [ ] `git checkout -b initial-scaffolding` ## Fetch Template Files - [ ] `.gitignore` — fetch from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore`, extend for language-specific artifacts. 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 `.dockerignore` form. The canonical file already carries `.claude/` so agent worktrees cannot be committed by accident. - [ ] `.editorconfig` — fetch from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.editorconfig` - [ ] `Makefile` — fetch from `https://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile`, adapt targets for the project's language and tools - [ ] For JS/docs repos: `.prettierrc` and `.prettierignore` — fetch from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrc` and `https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierignore` ## Create Project Files - [ ] `LICENSE` file matching the chosen license - [ ] `REPO_POLICIES.md` — 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 - [ ] `Dockerfile` and `.dockerignore` — fetch `.dockerignore` from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore` - Extend `.dockerignore` with the repo's own host-built artifacts, giving every depth-independent pattern a `**/` prefix — but write a repo-root binary anchored, `/myapp` and never `**/myapp`, which would also match `cmd/myapp/` and delete the package directory. Do not transplant `.gitignore`'s patterns: `.dockerignore` anchors an unprefixed pattern at the context root, so the copied form leaves `config/.env` in the build context while reading as solved. The canonical file's `.claude` entry 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: `.dockerignore` lets `.git` into the build context. It keeps out `.git/config` and each submodule's `config` under `.git/modules/` at any depth (`.git/modules/**/config`), which `git describe` does not need and which can hold a credential: a password in a remote URL, or the token the CI checkout step stores there. 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. - The Dockerfile carries a `lint` phase and a `test` phase, each invoking its tool directly rather than through `make` or `script/`, and the final stage carries a `COPY --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 `--target` names it. - 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 - [ ] Gitea Actions workflow at `.gitea/workflows/check.yml` that runs `script/cibuild` on push — reference `https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml` - [ ] Language-specific: - [ ] Go: `go mod init sneak.berlin/go/`, `.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: `yarn init`, `yarn add --dev prettier` - [ ] Python: `pyproject.toml` ## 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/` - [ ] 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 present, else pinned version via nvm from a hash-verified archive; pinned yarn via corepack); Dockerfile runs it instead of inline installs - [ ] `script/setup` / `make setup` — readies a fresh clone: runs `bootstrap`, then `install-precommit`, plus repo-specific init - [ ] `script/test` / `make test` — `docker build --no-cache --target test .`, tagged; 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 --no-cache --target lint .`, tagged. 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` — runs `test`, `lint`, `fmt-check`; must not modify files - [ ] `script/projectname` — outputs the project name (used by `script/docker` for the image tag) - [ ] `script/docker` / `make docker` — builds Docker image, tagged via `script/projectname` (byte-identical across repos); `--no-cache`, plus the version as a build arg - [ ] `script/cibuild` — cd to repo root, run `script/bootstrap`, run `script/check`, then `docker build --no-cache --build-arg VERSION="$version" .` (what CI runs). The bootstrap is required: CI checks out and runs this alone, and `script/fmt-check` runs the formatter on the host. - [ ] `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. - [ ] Every `docker build` in `script/` is tagged, so no invocation leaves a dangling image behind - [ ] `script/precommit` — called by the pre-commit hook; runs `script/check` - [ ] `script/install-precommit` — installs the pre-commit hook that runs `script/precommit` - [ ] `make hooks` — shims to `script/install-precommit` - [ ] README **Entrypoints** section documents the scripts and links the standard # 4. Verify - [ ] `make check` passes - [ ] `make docker` succeeds - [ ] `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 - [ ] Plant a lint violation and confirm both `make lint` and a plain `docker build .` fail on it; revert. A plain build that passes proves the final stage is missing its `COPY --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 hooks` to install pre-commit hook - [ ] Add remote and push - [ ] Verify `main` passes `make check`