Compact REPO_POLICIES.md without losing rules (closes #158) #159
@@ -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
|
||||
|
||||
+88
-38
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user