check / check (push) Canceled after 0s
The canonical `.gitea/workflows/check.yml` lacked two settings `dnswatcher` had added, so a byte-identical re-vendor removed them. A `concurrency` block grouped by workflow and branch, with `cancel-in-progress: true`, makes a new push cancel the older run on the same branch and leaves every other branch's runs alone; on 2026-10-02 45 stale runs had queued on the one shared runner. `persist-credentials: false` on the checkout step keeps the job's token out of `.git/config`; `script/cibuild` needs no token. Each has a one-line comment, and the policy's workflow bullet and both checklists describe the file as it now is. Unverified: the two live checks, which wait on the shared runner. Model: opus-5-5
196 lines
10 KiB
Markdown
196 lines
10 KiB
Markdown
---
|
|
title: New Repo Checklist
|
|
last_modified: 2026-10-06
|
|
---
|
|
|
|
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.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`, 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 `.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`, 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 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 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.
|
|
- 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, checks out with `persist-credentials: false`,
|
|
and carries the `concurrency` block that lets a new push cancel only the
|
|
same branch's older run — reference
|
|
`https://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 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/<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 present, else pinned version via nvm from a hash-verified
|
|
archive; 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
|
|
it
|
|
- [ ] `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`
|