diff --git a/TODO.md b/TODO.md index acb2991..12caf64 100644 --- a/TODO.md +++ b/TODO.md @@ -22,7 +22,7 @@ fmt-check, and commit. # Completed Steps - 2026-11-06: Compacted `prompts/REPO_POLICIES.md` from 1,145 lines and 8,427 - words to 1,012 lines and 7,709 words + words to 1,062 lines and 7,794 words (https://git.eeqj.de/sneak/prompts/issues/158). Every rule keeps its meaning. The rules now sit under headings, the pinning rule first. Each rule that was stated in several places is stated once. The Makefile copies of the Go and diff --git a/prompts/REPO_POLICIES.md b/prompts/REPO_POLICIES.md index f2f5ad7..7a1a01e 100644 --- a/prompts/REPO_POLICIES.md +++ b/prompts/REPO_POLICIES.md @@ -73,11 +73,15 @@ possible: - Never force-push to `main`. - Never use `git add -A` or `git add .`. Always stage files explicitly by name. - **No build artifacts in version control.** Code-derived data must never be - committed if it can be avoided: compiled bundles, minified output, generated - assets. The build process (e.g. Dockerfile, Makefile) should generate these at - build time. Notable exception: Go protobuf generated files (`.pb.go`) ARE - committed, because repos need to work with `go get`, which downloads code but - does not execute code generation. + committed to the repository if it can be avoided. Code-derived data includes: + - compiled bundles + - minified output + - generated assets + + The build process (e.g. Dockerfile, Makefile) should generate these at build + time. Notable exception: Go protobuf generated files (`.pb.go`) ARE + committed, because repos need to work with `go get`, which downloads code + but does not execute code generation. # Entrypoints @@ -206,8 +210,9 @@ present. Use `yarn`, not `npm`. - `script/bootstrap` installs node, yarn and the node packages only when - `yarn.lock` is in the tree. A `Dockerfile` stage that needs no yarn therefore - copies no `yarn.lock`. The Go and Python stages below are such stages. + `yarn.lock` is in the tree. A `Dockerfile` stage that needs no yarn copies no + `yarn.lock` before it runs `script/bootstrap`, so `script/bootstrap` installs + no node and no yarn there. The Go and Python stages below are such stages. - It installs the node packages with `yarn install --immutable`, which fails when a package's `checksum` is wrong, or when `package.json` is out of step with `yarn.lock`. @@ -250,8 +255,14 @@ what the tool even is. The canonical form: - compares the installed version against the pin over the **whole** version token. A parser that stops at the first `-` reports `2.12.2` for a host running `2.12.2-rc1` and skips the install. -- treats `--version` output that is absent, non-zero, empty or unrecognised as a - mismatch. A failure then causes a redundant install, never a skipped one. +- treats `--version` output as a mismatch when it is any of these: + - absent + - non-zero + - empty + - unrecognised + + So a failure causes a redundant install, never a skipped one. + - after installing, re-resolves the binary the way callers do: `hash -r`, then `PATH`, not the directory the installer wrote to. A shadowing binary can hide an install that reported success. In that case it fails and names the resolved @@ -289,7 +300,8 @@ docker build --build-arg CACHEBUST="$(date +%s)-$$" \ two gates are always invoked by name here, and why the final stage carries a `COPY --from=` of a harmless file from each of them. Without those copies, a plain `docker build .` builds the last stage's chain alone and exits 0 having - linted and tested nothing. Keep the final stage last. If you add a stage after + linted and tested nothing. Keep the stage that ships last: the runtime image, + or the development environment in a non-server repo. If you add a stage after it, give it the same two copies. - **The gate builds write no image.** With `--output type=cacheonly` the phase runs and a failing step fails the build, but the result is not exported. @@ -352,8 +364,12 @@ step that also installs anything else comes after `ARG CACHEBUST` too. A `script/bootstrap` that also installs node packages is one. - A new value reruns every `RUN` step after that line; `COPY` steps are compared - by content and need nothing. Docker keeps the layers above the line until the - base image, their own lines or the files they copy change. + by content and need nothing. Docker keeps the layers above the line until one + of these changes: + - the base image + - their own lines + - the files they copy + - A stage without the `ARG` line is served from cache on an unchanged tree, so a repository re-vendors the scripts and adds the `ARG` lines in one commit. - The scripts do not pass `--no-cache`. That flag reruns every step too, but it @@ -529,17 +545,26 @@ Key points: above `ARG CACHEBUST`, so check builds keep them.** The steps are `COPY script/ script/`, `COPY Pipfile Pipfile.lock ./` and `RUN script/bootstrap`, with `ARG CACHEBUST` right after them. Docker keeps what -that `RUN` installed until the base image, `script/`, `Pipfile`, `Pipfile.lock` -or a line up to and including the `RUN` changes. +that `RUN` installed until one of these changes: + +- the base image +- `script/` +- `Pipfile` +- `Pipfile.lock` +- a line up to and including the `RUN` The tests run after `ARG CACHEBUST`, on every check build. In every other stage, a step that installs Python packages comes after `ARG CACHEBUST`. In that step, `script/bootstrap`: -- installs nothing beyond system packages, pipenv and its dependencies, and the - packages from `Pipfile.lock`. If it installs anything else there, the step - goes after `ARG CACHEBUST`. +- installs nothing beyond these: + - system packages + - pipenv and its dependencies + - the packages from `Pipfile.lock` + + If it installs anything else there, the step goes after `ARG CACHEBUST`. + - installs no Python: the tests use the distribution's Python, which a step above it installs. - puts pipenv in a virtual environment at `$HOME/.pipenv`, because Debian's @@ -553,9 +578,11 @@ In that step, `script/bootstrap`: that lists each of them with `==` and its `--hash`. pip refuses to install a dependency the file does not list. - then runs `pipenv install --deploy`, with `--dev` when the tests need the - development packages. `--deploy` installs only what `Pipfile.lock` names, - checks every package against the lockfile's hashes, and fails when the - lockfile is out of date with `Pipfile`. + development packages. `--deploy` does all of these: + - installs only what `Pipfile.lock` names + - checks every package against the lockfile's hashes + - fails when the lockfile is out of date with `Pipfile` + - sets `PIP_ONLY_BINARY=:all:` for both installs, and pipenv passes it on to pip. A package with no ready-built wheel for the image then stops the install. Without the setting, pip builds that package from source and downloads the @@ -658,8 +685,9 @@ canonical Go `Dockerfile` above shows: stream keeps the sender's file owners, and git refuses a checkout owned by another user, so the version would come out empty. - The build fails if the context carries `.git` and the version still comes out - empty, `dev` or `unknown`. Otherwise a missing `git` or an unreadable checkout - would leave `-X main.Version=` empty, and the build would still exit 0. + empty, `dev` or `unknown`. Without that check, a missing `git` or an + unreadable checkout would leave `-X main.Version=` empty, and the build would + still exit 0. - 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. @@ -677,11 +705,11 @@ canonical Go `Dockerfile` above shows: - 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`). +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`). `script/docker` and `script/cibuild` pass the version they compute on the host; it takes precedence. They do this byte-identically across repos: @@ -711,7 +739,7 @@ docker build --build-arg CACHEBUST="$(date +%s)-$$" \ Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that runs `script/cibuild` on push. The workflow's only other step checks out the repo. -The canonical `.gitea/workflows/check.yml` sets these: +The workflow sets these: - `persist-credentials: false` on the checkout step. `script/cibuild` needs no token, and without that setting the checkout leaves the job's token in @@ -910,8 +938,14 @@ including, without limitation, all of the following: to an explicit allowlist of known origins. Wildcard (`*`) is acceptable only for public, unauthenticated read-only APIs. - **Error handling:** Internal errors must never leak implementation details to - the client: stack traces, SQL queries, file paths. Return generic error - messages in production; detailed errors only when `DEBUG` is enabled. + the client. Those details include: + - stack traces + - SQL queries + - file paths + + Return generic error messages in production; detailed errors only when + `DEBUG` is enabled. + - **TLS:** Services never terminate TLS directly. They are always deployed behind a TLS-terminating reverse proxy. The service itself listens on plain HTTP. However, HSTS headers and `Secure` cookie flags must still be set by the @@ -937,10 +971,17 @@ the binary. `README.md` is the primary documentation. Required sections: -- **Description**: First line must include the project name, purpose, category - (web server, SPA, CLI tool, etc.), license and author. Example: "µPaaS is an - MIT-licensed Go web application by @sneak that receives git-frontend webhooks - and deploys applications via Docker in realtime." +- **Description**: First line must include: + - the project name + - purpose + - category (web server, SPA, CLI tool, etc.) + - license + - author + + Example: "µPaaS is an MIT-licensed Go web application by @sneak that + receives git-frontend webhooks and deploys applications via Docker in + realtime." + - **Getting Started**: Copy-pasteable install/usage code block. - **Entrypoints**: Opens by stating that the repo adheres to the [Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) @@ -958,10 +999,19 @@ the binary. # Repository layout Avoid putting files in the repo root unless necessary. Root should contain only -project-level config files: `README.md`, `AGENTS.md`, `Makefile`, `Dockerfile`, -`LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md` and -language-specific config. Everything else goes in a subdirectory. Canonical -subdirectory names: +project-level config files: + +- `README.md` +- `AGENTS.md` +- `Makefile` +- `Dockerfile` +- `LICENSE` +- `.gitignore` +- `.editorconfig` +- `REPO_POLICIES.md` +- language-specific config + +Everything else goes in a subdirectory. Canonical subdirectory names: - `bin/` — executable scripts and tools - `cmd/` — Go command entrypoints; thin only: one `main.go` per binary whose