Compact REPO_POLICIES.md without losing rules (closes #158) #159

Open
clawbot wants to merge 3 commits from compact-repo-policies into next
2 changed files with 89 additions and 39 deletions
Showing only changes of commit 2e98be476a - Show all commits
+1 -1
View File
@@ -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
View File
@@ -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