4 Commits
Author SHA1 Message Date
clawbot 8c889fa622 REPO_POLICIES: script/ entrypoints are the single source of truth (#157)
check / check (push) Successful in 1m16s
2026-11-06 01:18:52 +01:00
clawbot 6e5fa4c607 REPO_POLICIES: pinning covers what we download ourselves; distribution packages are outside it (#156)
check / check (push) Canceled after 3s
2026-11-06 01:18:44 +01:00
clawbot 9389bf0172 Commit yarn 4 in the tree and install with --immutable (closes #149)
check / check (push) Successful in 16s
Follows sneak's ruling at
#149 (comment). This
repository now carries yarn 4.18.1 under .yarn/releases. The
distribution's yarn only starts that file, and that file does the work.
yarn.lock is converted once, and script/bootstrap installs with
--immutable instead of stopping on yarn's major version. Node is the
distribution's newest Node package: nodejs-current on Alpine and
nodejs_latest on nix. Every Dockerfile stage that needs yarn also copies
.yarnrc.yml and .yarn/releases/. The policies and both checklists say
so, with the steps for converting and for a new repository.

Model: opus-5-5
2026-11-04 21:32:55 +01:00
clawbot 3066aae835 Allow go test on one package while iterating (closes #51)
check / check (push) Canceled after 0s
Follows sneak's ruling at
#51 (comment). An agent
whose work is confined to one package may run `go test` directly on that
package's suite, or on individual tests in it, while iterating. `go build`,
`go vet` and the linter still go through make targets. The full `make check`
is still required before every push. The `Makefile` gains nothing.

The rule on Docker now says the lint and test gates run there, because a run
while iterating is not a gate.

Model: opus-5-5
2026-11-04 19:32:37 +01:00
8 changed files with 111 additions and 82 deletions
+7 -4
View File
@@ -35,6 +35,12 @@
# `services/api/.claude/` and must add its own anchored entry.
.claude
# yarn's install state and cache stay out, and its release file stays
# in. Docker obeys the last matching line, so these two lines sit above
# the secret patterns, which must win under .yarn/releases/.
.yarn/*
!.yarn/releases
# Environment files. `*.env` covers bare `.env` and the `prod.env`
# convention. Re-include a committed template with a negation if the
# build needs one: `!docs/example.env`.
@@ -55,11 +61,8 @@
**/[iI][dD]_[eE][dD]25519
**/[iI][dD]_[eE][dD]25519_[sS][kK]
# Dependencies: restored inside the image, never copied in. yarn's install
# state and cache stay out, and its release file stays in.
# Dependencies: restored inside the image, never copied in.
**/node_modules
.yarn/*
!.yarn/releases
# OS metadata.
**/.DS_Store
+3 -3
View File
@@ -22,9 +22,9 @@ node_modules/
.yarn/*
!.yarn/releases
# Secrets. Unanchored like every entry above, so each matches at every
# depth. Matching is case-sensitive on Linux, so names use character
# ranges rather than a lowercase form that misses `Server.Key`.
# Secrets. Unanchored, so each matches at every depth. Matching is
# case-sensitive on Linux, so names use character ranges rather than a
# lowercase form that misses `Server.Key`.
# Environment files. `*.env` covers bare `.env` and the `prod.env`
# convention. Only the templates `example.env` and `sample.env` are
+2 -2
View File
@@ -6,8 +6,8 @@
# system packages. script/ passes a new value on every build. That value
# reruns every RUN step below the ARG line, and the install step keeps
# its layer. node and yarn come from the distribution, which is trusted,
# so they need no version and no hash. That yarn hands the work to the
# release file in .yarn/releases/, so every stage copies that file and
# so they need no version and no hash. That yarn only starts the release
# file in .yarn/releases/, so every stage copies that file and
# .yarnrc.yml before it runs script/bootstrap.
#
# alpine 3.24.3, 2026-11-04
+12
View File
@@ -21,6 +21,11 @@ fmt-check, and commit.
# Completed Steps
- 2026-11-06: The pinning rule in `prompts/REPO_POLICIES.md` now opens with its
scope: what we download and install ourselves in scripts and Dockerfiles
(https://git.eeqj.de/sneak/prompts/issues/154). Distribution packages are
outside the rule, and updating and upgrading them is allowed. Both checklists
say the same.
- 2026-11-04: Each repository now commits yarn 4 and installs with
`yarn install --immutable` (https://git.eeqj.de/sneak/prompts/issues/149), on
sneak's ruling at
@@ -36,6 +41,13 @@ fmt-check, and commit.
and yarn.
- Both checklists give the steps, and the ignore files keep yarn's install
state out of git and the build context.
- 2026-11-04: An agent whose work is confined to one package may run `go test`
directly on that package's suite, or on individual tests in it, while
iterating (https://git.eeqj.de/sneak/prompts/issues/51), on sneak's ruling at
https://git.eeqj.de/sneak/prompts/issues/51#issuecomment-167600. The full
`make check` is still required before every push, and the `Makefile` gains
nothing. The rule on Docker now names the lint and test gates, because a run
while iterating is not one.
- 2026-11-04: node, yarn, npm, python, pip and venv now come from the
distribution with no version and no hash
(https://git.eeqj.de/sneak/prompts/issues/146), on sneak's ruling at
+28 -24
View File
@@ -1,6 +1,6 @@
---
title: Existing Repo Checklist
last_modified: 2026-11-04
last_modified: 2026-11-06
---
Use this checklist when beginning work in a repo that may not yet conform to our
@@ -165,15 +165,16 @@ with your task.
in the same commit, set the lint phase digest to the one named in the
`.golangci.yml` paragraph of `REPO_POLICIES.md`)
- [ ] JS: `package.json`, `yarn.lock`, `.yarnrc.yml`, `.yarn/releases/`,
`.prettierrc`, `.prettierignore` (fetch the last two from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrc` and
`.gitattributes`, `.prettierrc`, `.prettierignore` (fetch the last two
from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrc`
and
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierignore`).
- [ ] JS: `yarn.lock` is in yarn 4's format. If its second line says
`yarn lockfile v1`, convert it with the steps below. Do them in one
commit with the re-vendored scripts, because yarn 1 ignores
`--immutable` and exits 0 on a tree with no `.yarnrc.yml`. Run them
with any distribution's node and yarn, and use `yarnpkg` on Debian and
Ubuntu.
- [ ] Any repository with `yarn.lock`: that file is in yarn 4's format. If
its second line says `yarn lockfile v1`, convert it with the steps
below. Do them in one commit with the re-vendored scripts, because
yarn 1 ignores `--immutable` and exits 0 on a tree with no
`.yarnrc.yml`. Run them with any distribution's node and yarn, and use
`yarnpkg` on Debian and Ubuntu.
1. Read the version from `yarnPath` in
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.yarnrc.yml`.
2. Run `yarn set version <version>`. It writes
@@ -187,12 +188,14 @@ with your task.
`npmMinimalAgeGate` from `.yarnrc.yml`. The conversion wrote them.
They allow install scripts, any git repository and packages of any
age, which yarn otherwise refuses.
6. Name any package that must run its install script in `package.json`,
6. In `package.json`, name any package that must run its install script,
as `"dependenciesMeta": { "<name>": { "built": true } }`, and run
`yarn install` again.
7. Add `.yarn/*` and `!.yarn/releases` to `.gitignore` and
`.dockerignore`, `.yarn/` to `.prettierignore`, and
`.yarn/releases/** binary` to `.gitattributes`.
7. Add `.yarn/*` and `!.yarn/releases` above the secret patterns in
`.gitignore` and `.dockerignore`. Docker obeys the last matching
line, and the secret patterns must win. Add `.yarn/` to
`.prettierignore`, and `.yarn/releases/** binary` to
`.gitattributes`.
8. Run `yarn install --immutable`. It must pass and change nothing.
- [ ] Python: `pyproject.toml`
- [ ] Docs/writing: `.prettierrc`, `.prettierignore` (same URLs as above)
@@ -231,8 +234,9 @@ with your task.
- [ ] `script/bootstrap` installs node, yarn and the node packages only when
`yarn.lock` is in the tree. node and yarn come from the distribution when
they are missing. It installs the node packages with
`yarn install --immutable`. Whichever yarn the distribution packages hands
the work to the release file that `yarnPath` in `.yarnrc.yml` names.
`yarn install --immutable`. The distribution's yarn only starts the
release file that `yarnPath` in `.yarnrc.yml` names, and that file does
the work.
- [ ] A `Dockerfile` stage that needs yarn may start from any distribution that
packages node and yarn. It copies `package.json`, `yarn.lock`,
`.yarnrc.yml` and `.yarn/releases/` before it runs `script/bootstrap`. A
@@ -263,21 +267,21 @@ with your task.
- [ ] Pre-commit hook is installed (`make hooks`)
- [ ] No secrets in the repo (`.env`, keys, credentials)
- [ ] No mutable references in Dockerfiles or scripts (tags, `@latest`) — all
pinned by cryptographic hash with version/date comment. The one exception
is a package from the distribution's own package index, which needs no
version and no hash. Pulling that index is also allowed. In a
`Dockerfile`, the distribution is the pinned base image's. On a host, it
is the host's, and the host's package manager stands for it, nix and brew
included. A package source that a `Dockerfile` adds itself does not get
this exception
- [ ] No mutable references (tags, `@latest`) to what the repo downloads and
installs itself in scripts and Dockerfiles — all pinned by cryptographic
hash with version/date comment. Distribution packages are outside this
rule. They need no version and no hash, and updating and upgrading them is
fine too. In a `Dockerfile`, the distribution is the pinned base image's.
On a host, it is the host's, and the host's package manager stands for it,
nix and brew included. A package source that a `Dockerfile` adds itself is
not the distribution's own, so its packages are pinned
- [ ] node, yarn, npm, python, pip and venv come from the distribution wherever
possible, never from nvm, pyenv, corepack or `npm install -g yarn`. If the
distribution's version is too old, use a newer distribution. node means
the distribution's newest Node package, such as `nodejs-current` on Alpine
3.24
- [ ] yarn's release file is in git under `.yarn/releases/`, written once by
`yarn set version` with an exact version
`yarn set version` with an exact version (repositories with `yarn.lock`)
- [ ] Using `yarn`, not `npm` (JS projects)
# Directory Structure
+17 -15
View File
@@ -1,6 +1,6 @@
---
title: New Repo Checklist
last_modified: 2026-11-04
last_modified: 2026-11-06
---
Use this checklist when creating a new repository from scratch. Follow the steps
@@ -179,10 +179,11 @@ Template files can be fetched 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: make the first `yarn.lock` with the steps below.
`script/bootstrap` installs no yarn until `yarn.lock` exists, so run
them with any distribution's node and yarn, such as Alpine's
(`apk add nodejs-current yarn`). Use `yarnpkg` on Debian and Ubuntu.
- [ ] JS, or any repository that runs prettier through yarn: make the first
`yarn.lock` with the steps below. `script/bootstrap` installs no yarn
until `yarn.lock` exists, so run them with any distribution's node and
yarn, such as Alpine's (`apk add nodejs-current yarn`). Use `yarnpkg`
on Debian and Ubuntu.
1. Read the version from `yarnPath` in
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.yarnrc.yml`.
2. Run `yarn set version <version>`. It writes
@@ -210,9 +211,9 @@ are thin shims calling them. Model scripts:
- It installs node, yarn and the node packages only when `yarn.lock` is in
the tree. node and yarn come from that package manager when they are
missing.
- It installs the node packages with `yarn install --immutable`. Whichever
yarn the distribution packages hands the work to the release file that
`yarnPath` in `.yarnrc.yml` names.
- It installs the node packages with `yarn install --immutable`. The
distribution's yarn only starts the release file that `yarnPath` in
`.yarnrc.yml` names, and that file does the work.
- A non-server repo's development environment stage runs it, with only
system packages installed inline above it.
- A gate phase or the build stage installs what its base image lacks, either
@@ -284,19 +285,20 @@ are thin shims calling them. Model scripts:
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. The one exception is a package from
the distribution's own package index, which needs no version and no hash.
Pulling that index is also allowed. In a `Dockerfile`, the distribution is
the pinned base image's. On a host, it is the host's, and the host's
package manager stands for it, nix and brew included. A package source
that a `Dockerfile` adds itself does not get this exception
- [ ] No mutable references to what the repo downloads and installs itself in
scripts and Dockerfiles. Distribution packages are outside this rule. They
need no version and no hash, and updating and upgrading them is fine too.
In a `Dockerfile`, the distribution is the pinned base image's. On a host,
it is the host's, and the host's package manager stands for it, nix and
brew included. A package source that a `Dockerfile` adds itself is not the
distribution's own, so its packages are pinned
- [ ] node, yarn, npm, python, pip and venv come from the distribution wherever
possible, never from nvm, pyenv, corepack or `npm install -g yarn`. If the
distribution's version is too old, use a newer distribution. node means
the distribution's newest Node package, such as `nodejs-current` on Alpine
3.24
- [ ] yarn's release file is in git under `.yarn/releases/`, written once by
`yarn set version` with an exact version
`yarn set version` with an exact version (repositories with `yarn.lock`)
- [ ] No unnecessary files in repo root
- [ ] All dates written as YYYY-MM-DD
+37 -29
View File
@@ -1,6 +1,6 @@
---
title: Repository Policies
last_modified: 2026-11-04
last_modified: 2026-11-06
---
This document covers repository structure, tooling, and workflow standards. Code
@@ -21,13 +21,28 @@ style conventions are in separate documents:
with the authoritative source as policies evolve.
- **External references must be pinned by cryptographic hash.** This is the
single most important rule in this document. It covers everything fetched from
a remote source, including:
single most important rule in this document. It covers what we download and
install ourselves in scripts and Dockerfiles:
- Docker base images
- Go modules
- Python packages
- Python packages from PyPI
- npm packages
- GitHub Actions
- release archives fetched with `curl` or `wget`
Packages from the distribution's own package manager are outside this rule.
They need no version and no hash, because the distribution is trusted. In a
`Dockerfile`, it is the pinned base image's distribution. On a host, it is
the host's. The host's package manager stands for its distribution, and that
includes nix and brew. Updating the package index and upgrading installed
packages are fine too. This command, for example, is allowed:
```sh
apt update && apt upgrade -y && apt install -y build-essential
```
A package source that a `Dockerfile` adds itself is not the distribution's
own, so its packages are pinned like any other download.
A version tag such as `@v4`, `@latest` or `:3.21` is server-mutable, and
therefore a remote code execution vulnerability. Reference an external
@@ -44,17 +59,6 @@ style conventions are in separate documents:
Unverified install scripts are arbitrary remote code execution. Double-check
every external reference in every file before committing.
There is one exception: a package from the distribution's own package index
needs no version and no hash. Pulling that index is also allowed. The
distribution is trusted. In a `Dockerfile`, it is the pinned base image's
distribution. On a host, it is the host's. The host's package manager stands
for its distribution, and that includes nix and brew. These commands are
examples:
- `apt-get update`
- `apt-get install`
- `apk update`
- `apk add`
These come from the distribution wherever possible:
- node, meaning the distribution's newest Node package, such as
`nodejs-current` on Alpine 3.24
@@ -79,9 +83,6 @@ style conventions are in separate documents:
- the npm registry
- `go get`
A package source that a `Dockerfile` adds itself is not the distribution's
own, so the exception does not cover it.
- Every repo with software must have a root `Makefile` with these targets:
- `make bootstrap`
- `make setup`
@@ -132,8 +133,8 @@ style conventions are in separate documents:
is out of step with `yarn.lock`.
Each repository commits the yarn that runs. `yarnPath` in `.yarnrc.yml`
points at the release file in `.yarn/releases/`. Whichever yarn the
distribution packages hands the work to that file. Alpine packages yarn 1,
points at the release file in `.yarn/releases/`. The distribution's yarn
only starts that file, and that file does the work. Alpine packages yarn 1,
and Debian 13 packages yarn 4.1. The same committed yarn does the work on
both. Corepack never runs, and no yarn is downloaded.
@@ -143,8 +144,8 @@ style conventions are in separate documents:
- `enableTelemetry: false`, because yarn otherwise sends usage reports from
any host that is not CI
Under these settings yarn runs no package's install script. A package that
must run one is named in `package.json`, as
By default, yarn runs no dependency's install script. A dependency that must
run one is named in `package.json`, as
`"dependenciesMeta": { "<name>": { "built": true } }`.
`.gitattributes` carries `.yarn/releases/** binary`, so diffs and merges
@@ -199,9 +200,16 @@ style conventions are in separate documents:
README must document the provided scripts in an **Entrypoints** section (see
the README requirements below).
- Always use Makefile targets (`make fmt`, `make test`, `make lint`, etc.)
instead of invoking the underlying tools directly. The Makefile is the single
source of truth for how these operations are run.
- Run the repo's own entrypoints, never the underlying tools directly. The
`script/` scripts are the single source of truth for how each operation runs.
Calling them directly is fine, and so is calling the Makefile targets. The
Makefile targets must still exist, as the thin shims described above.
An agent whose work is confined to one package may run `go test` directly on
that package's suite, or on individual tests in it, while iterating. This is
the only exception to this rule. `go build`, `go vet` and the linter still
go through the entrypoints. A run while iterating is not a gate, so the full
`make check` is still required before every push.
- The Makefile is authoritative documentation for how the repo is used. Beyond
the required targets above, it should have targets for every common operation:
@@ -235,9 +243,9 @@ style conventions are in separate documents:
- `package.json`, `yarn.lock`, `.yarnrc.yml` and `.yarn/releases/`
- `go.mod` and `go.sum`
- **Linting and testing run in Docker, as phases of the `Dockerfile`.** There is
no separate lint file. `script/lint` and `script/test` each build one phase
and nothing else:
- **The lint and test gates run in Docker, as phases of the `Dockerfile`.**
There is no separate lint file. `script/lint` and `script/test` each build one
phase and nothing else:
```sh
docker build --build-arg CACHEBUST="$(date +%s)-$$" \
@@ -1129,7 +1137,7 @@ style conventions are in separate documents:
- `.gitea/workflows/check.yml`
- Go: `go.mod`, `go.sum`, `.golangci.yml`
- JS: `package.json`, `yarn.lock`, `.yarnrc.yml`, `.yarn/releases/`,
`.prettierrc`, `.prettierignore`
`.gitattributes`, `.prettierrc`, `.prettierignore`
- Python: `pyproject.toml`
- Guidance for coding agents lives in one `AGENTS.md` at the repository root. It
+5 -5
View File
@@ -4,10 +4,10 @@
# installed tools are skipped. Base tools, node and yarn come from nix,
# apt, brew, or apk (detected in that order), with no version and no
# hash, because the distribution is trusted. It installs node, yarn and
# the node packages only when yarn.lock is in the tree. Whichever yarn
# the distribution packages hands the work to the release file that
# yarnPath in .yarnrc.yml names, so git pins the yarn that runs. Assumes
# nothing is present.
# the node packages only when yarn.lock is in the tree. The
# distribution's yarn only starts the release file that yarnPath in
# .yarnrc.yml names, and that file does the work. So git pins the yarn
# that runs. Assumes nothing is present.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
@@ -80,7 +80,7 @@ main() {
# A Dockerfile stage that needs no yarn copies no yarn.lock.
if [ -f yarn.lock ]; then
if missing node; then pkg_install nodejs nodejs node nodejs-current; fi
if missing node; then pkg_install nodejs_latest nodejs node nodejs-current; fi
if missing yarn && missing yarnpkg; then
pkg_install yarn yarnpkg yarn yarn
fi