The bullet in `prompts/REPO_POLICIES.md` that requires a `Dockerfile` said every Dockerfile installs its prerequisites by running `script/bootstrap`, while the canonical Go `Dockerfile` never runs it: its gate phases use their pinned images as they are and its build stage installs `git` inline.
The bullet now says the gate phases and the build stage take their tools from their pinned base images and install only what those images lack, and that the development environment stage, the final stage of a non-server repo, runs `script/bootstrap`. The new repo checklist says the same.
Model: opus-5-5
The server lifecycle example in `prompts/GO_HTTP_SERVER_CONVENTIONS.md` dropped its exit code, installed its own SIGINT/SIGTERM handler beside the one fx's `Run()` installs, and exited from a goroutine when Sentry could not start, so no stop hook ran.
fx now owns signals and the exit code: `main` calls `Run()`; a listen error shuts fx down with `fx.ExitCode(1)` through `fx.Shutdowner`; `enableSentry()` returns its error from the start hook; the stop hook shuts the HTTP server down within 5 seconds, flushes Sentry, and fails when requests are still running. The start hook builds the HTTP server before the listen goroutine so the stop hook can reach it. A new paragraph says who owns signals and the exit code.
Model: opus-5-5
Writes sneak's 2026-09-09 ruling ("commit pinned installation, not pulled into deps") into the `prompts/REPO_POLICIES.md` bullet that says `script/bootstrap` installs a pinned tool by comparing versions: a Go tool a repo needs on the host is installed with `go install` pinned to a commit hash, naming the tool's main package, and is never tracked as a `go.mod` tool dependency or through a `tools.go` file, either of which pulls the tool's own dependencies into the repo's `go.mod` and `go.sum`.
golangci-lint is unchanged: no repo installs it on the host, and it stays pinned by its image digest.
Model: opus-5-5
`ssh-keygen` names the private key of a key backed by a hardware security key `id_ecdsa_sk` or `id_ed25519_sk`. The canonical `.gitignore` and `.dockerignore` listed only `id_rsa`, `id_dsa`, `id_ecdsa` and `id_ed25519`, so a repository could commit these files or copy them into an image.
Both names are added beside their plain counterparts in each file's own style: unanchored in `.gitignore`, `**/`-prefixed in `.dockerignore`, case-folded with character ranges in both. A pattern matches the whole file name, so the `.pub` halves stay trackable and still reach the build context.
Model: opus-5-5
`package.json` now carries `"license": "MIT"`, matching `LICENSE`. Without it yarn printed `warning package.json: No license field` and `warning No license field` each time `script/bootstrap` ran inside the Docker phases of `make check`. No other yarn warning appears in the bootstrap output.
Model: opus-5-5
Writes down sneak's 2026-08-22 ruling: the in-repo memory rule that older vendored copies of `REPO_POLICIES.md` still carry was retired, not lost.
`prompts/REPO_POLICIES.md` gains one bullet after the files a new repo must contain: guidance for coding agents lives in one `AGENTS.md` at the repository root, never under a file or directory named after one agent tool, and never split into separate memory files. `AGENTS.md` joins the files allowed in the repo root, which would otherwise forbid it. Both checklists get the matching item; the existing-repo one says to move such a file's content into `AGENTS.md` and delete it.
The Dockerfile finding at the end of the issue is tracked in #90.
Model: opus-5-5
The note under the canonical Go `make test` example in `prompts/REPO_POLICIES.md` named a cache-busting build argument that `--no-cache` replaced, and said Go's test result cache survived in earlier image layers. Neither is true of the current files.
The note now says where that cache can replay a pass: on a developer's machine, where the Makefile target runs, so both invocations there keep `-count=1`. The `test` phase of the `Dockerfile` has nothing to replay, since its base image holds no result for the repo's tests and no step before `go test` runs one, so it needs no `-count=1`. Go stores only passing results, so neither run can report a stored pass.
Model: opus-5-5
The Makefile examples in `prompts/CODE_STYLEGUIDE_GO.md` and `prompts/GO_HTTP_SERVER_CONVENTIONS.md` now read `VERSION ?= $(or $(shell git describe --tags --always 2>/dev/null),dev)`.
When `git describe` prints nothing (outside a git checkout, or where git is missing or refuses the checkout) the old line stamped an empty version without a word; it now falls back to `dev`, the same way in every repo. The comment above each line says so.
In a Docker build stage with `.git` present, a real version needs git installed and the checkout trusted, as the canonical `Dockerfile` does; the `Dockerfile` already fails when the version comes out empty, `dev` or `unknown`.
Model: opus-5-5
The canonical `.dockerignore` kept out `.git/config`, which can hold a credential, but not the `config` in each submodule's git directory under `.git/modules/`, nested again for a submodule's own submodules. It now also lists `.git/modules/**/config`, with one sentence in the comment above; `prompts/REPO_POLICIES.md` and both checklists say so in the same words.
The pattern stays under `.git/modules/` because `.git/**/config` would also drop a branch or tag named `config`, which `git describe` may need.
Known gap: a submodule whose name has a `config` path segment loses its whole git directory, so Go's version stamping fails the build loudly; tracked separately.
Model: opus-5-5
The canonical Go `Dockerfile` example in `prompts/REPO_POLICIES.md` had two defects.
The test phase ran `go test -race` on the alpine Go image, which has no C compiler, so `-race` failed before any test ran. The test phase now uses the Debian Go image, pinned by digest like the others.
A build context sent as a tar stream keeps the sender's file owners, so git refused the checkout and the version step failed the build. The builder stage now runs `git config --system --add safe.directory /src`; the policy and both checklists say why in the same words.
The builder's `apk add --no-cache git` line is unchanged: whether it must be pinned is the open owner question on #72.
Model: opus-5-5
The canonical golangci-lint moves from v2.12.2 to v2.14.0, built with go1.27: v2.12.2 refuses a module whose `go` directive names 1.27 or later. The policy now states the rule: the `go` directive must not name a newer Go minor version than the one golangci-lint was built with.
From v2.13.0, `default: all` turns on `exhaustruct_v5`, the successor of the deprecated `exhaustruct`. `.golangci.yml` disables it beside the old name, which stays listed or its deprecation warning returns.
v2.12.2 rejects the new `.golangci.yml`, so a repo changes the lint phase digest and re-vendors `.golangci.yml` in one commit; both checklists point to that rule.
Model: opus-5-5
The canonical .gitignore matched only .env, .env.*, *.pem and *.key, so prod.env, .envrc, *.p12 and *.pfx bundles, and the SSH private keys id_rsa, id_dsa, id_ecdsa and id_ed25519 could be committed. The secrets section now covers the same shapes as the canonical .dockerignore, written to gitignore's own rules: unanchored, no **/ prefix, character ranges for case. example.env and sample.env stay trackable through negations, and the comment tells a repository to add its own negation for any other committed template.
Judgement call: the existing entries were rewritten with character ranges, which only widens them, and the bare .env line is dropped because *.env covers it.
Model: opus-5-5
The canonical documents told every repo to exclude .git from the build
context, default ARG VERSION to dev and never run git describe in a
build stage, so an image built from a clone with no build argument
reported dev. .dockerignore now sends .git but keeps out .git/config,
which can hold a credential. The Dockerfile example installs git, takes
the VERSION build argument when one is given and otherwise
git describe --tags --always, and fails when .git exists but the version
is empty, dev or unknown. The policy and both checklists state the rule
in the same words, including that a plain docker build . with no build
arguments must succeed.
Model: opus-5-5
Option-2 answer to sneak's ruling of 2026-08-19 on
sneak/homoicon#4: migrate to the successor, with
settings. Closes #25.
## The change
`golangci-lint` v2.12.0 deprecated `gomodguard`, and this config sets
`linters.default: all`, so it is enabled everywhere and warns on every run.
- `gomodguard` joins `wsl` in `linters.disable` under a shared "deprecated"
comment. The warning is attached to the old name, so disabling it is what
silences it.
- `gomodguard_v2` is named in `linters.enable`, a no-op under `default: all`
that gives the settings block a visible owner.
- Blocked, each restating a decision already recorded in the Go package
defaults: `rs/zerolog` → `log/slog`; the pre-fork `go-redis/redis` →
`redis/go-redis/v9`; `sergi/go-diff` and `hexops/gotextdiff` → `go-udiff`.
Every entry matches the module path exactly, so the pre-fork go-redis takes
three: `go-redis/redis`, `/v7`, `/v8`. A prefix would also cover
`go-redis/redismock`, the test double for the successor recommended here.
Deliberately absent: recorded rejections that vendoring repos still require
(`mattn/go-sqlite3`, `gorm.io/gorm`, `pmezard/go-difflib`), plus `urfave/cli`
and unversioned `go-chi/chi`. Blocking those would redden repos mid-migration
on their next re-vendor.
## After merge
The file's sha256 moves from
`d10f47ef5e0d8620efd62275b016a7de8fd5abedf333922eec86fe4abc06176e` to
`a79b63a254602a5318db5d0e9a06bc71b84bf0c1d896305229d8bfed1d1b1776`, so every
vendoring repo mismatches. #60 is the
propagation brief; it and the record on
#25 carry this value.
## Disclosures
- Judgement call: `wsl` moved two lines down to share the "deprecated" comment.
No behaviour change, but it widens the diff.
- The `go.mod` survey and the settings-block probe are on
#25.
- Unverified: the linter was not run against each vendoring repo; the per-repo
claim rests on reading their `go.mod` files.
- `make check` exit 0.
Model: opus-5
Co-authored-by: Jeffrey Paul <sneak@noreply.example.org>
Reviewed-on: #55
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
Per the owner ruling on issue 40, linting and testing are phases of the
main Dockerfile rather than a separate lint file. script/lint and
script/test build one phase each by name with caching disabled, and the
final stage copies a harmless file from each so the image cannot be built
unless both passed. A stage that is not the last is built only when
something depends on it or --target names it, so the gates are invoked by
name and the edges kept. script/check runs the gates and builds no image
of its own; script/cibuild bootstraps first, because CI runs it alone and
fmt-check is native. fmt and fmt-check source nvm for the pinned node
before calling yarn, which bootstrap installs but leaves off its caller's
PATH. Every build in script/ is tagged and uncached. Issue 30 closes too:
a container has its own lint cache and lock.
Model: opus-5
The canonical .dockerignore and .gitignore both omitted the in-repo agent
scratch directory, which holds one worktree per in-flight agent, so under
`COPY . .` an entire extra checkout of the repo reached the image. The
two entries are deliberately different shapes: anchored in .dockerignore,
where the `**/` form would also delete a legitimately named nested
directory, and unanchored in .gitignore, where a pattern already matches
at every depth. Anchoring leaves a gap where agents run in
subdirectories, stated in the vendored file itself. The second half is
the consequence of excluding .git: `git describe` in a build stage yields
an empty version without erroring, so the version is now computed on the
host and passed in.
Model: opus-5
The canonical .dockerignore was three lines while the canonical
Dockerfile does `COPY . .`, so a local .env, *.pem or *.key shipped into
the build context and could land in an image layer, invisible to every
git-based check. Copying .gitignore's patterns across is not the repair:
.dockerignore anchors an unprefixed pattern at the context root, so that
form protects only the repository root while reading as solved. Every
depth-independent pattern here carries `**/`, and secret names are
character ranges because matching is case-sensitive and an ALL-CAPS twin
still misses `Server.Key`. Public certificates are deliberately left in
as a legitimate build input. Verified by enumerating a probe image.
Model: opus-5
The canonical `if missing <tool>; then install; fi` guard tests PATH
presence and never version, so on any already-provisioned machine a pin
is inert and a version bump is a no-op, while the Dockerfile installs the
pinned version into a clean image and CI then disagrees with local about
what the tool is. Comparing versions alone is not enough either: an
installer writes to its own directory while callers resolve through PATH,
so a shadowing binary lets the install succeed and change nothing anyone
sees. REPO_POLICIES.md now states the whole form — exact whole-token
comparison, mis-parse falling through to a reinstall, re-resolution
through PATH after installing, and a call site that prints the version.
Model: opus-5
script/cibuild was a plain `docker build .` and the Dockerfile does
`COPY . .` followed by `RUN make check`, so on an unchanged tree Docker
served the check layer from cache: the suite never ran and the build
still exited 0. Measured here before the change, a second run on a
byte-identical tree returned in 0.286s with `RUN make check` CACHED.
script/cibuild and script/docker now pass --no-cache. The canonical text
asserting that a bare `docker build .` proves the checks ran was wrong in
REPO_POLICIES.md, both checklists and the Go styleguide, and is corrected
in all of them.
Model: opus-5
Answers #44: the canonical Go `make test` target omitted `-count=1`, so Go replayed cached successful results and the target could exit 0 having executed no test. Every repository that copied it inherited that false green.
The change adds `-count=1` to both `go test` invocations in the example in `REPO_POLICIES.md`, with a short paragraph saying why, and records the step in `TODO.md`. It defeats only the test-result cache, not the build cache, so it costs the suite's runtime and no recompilation. It is independent of the Docker layer cache that #26 addresses.
Rebased onto current `main`; the check is green. Repositories pick it up the next time each vendors the canonical files.
Model: opus-5 (change); fable-5-1 (this description)
Co-authored-by: sneak <sneak@sneak.berlin>
Co-authored-by: clawbot <cai2025@acidhou.se>
Co-authored-by: Jeffrey Paul <sneak@noreply.example.org>
Reviewed-on: #45
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
Requested by sneak.
Sets the canonical `.golangci.yml` to the org-standard v2-schema config already deployed byte-identical across the org's Go repos (vaultik, sfdupes, attrsum, upaas, simplelog, mfer, secret, rgoue, bsfirehose). sha256: `021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb`.
Why: settings live under `linters.settings`, so the lll/funlen/cyclop/dupl thresholds actually apply under golangci-lint v2. The old canonical file kept them under top-level `linters-settings`, which v2 ignores.
Version note: golangci-lint v2.12.2 tag = commit `c0d3ddc9cf3faa61a4e378e879ece580256d76e5`, recorded for consuming repos. This repo itself has no version pins; the `v2.x.x` / `@sha256:...` strings in `prompts/REPO_POLICIES.md` are intentional placeholders and are unchanged.
Known informational note: this config does not disable the deprecated `gomodguard` linter, so golangci-lint 2.12.x prints a deprecation warning. Harmless, accepted.
Matches dnswatcher PR #96: sneak/dnswatcher#96
Verification: `make check` green (prettier fmt-check on all markdown) after `make fmt`; `.golangci.yml` sha256 verified as `021cc83f...` matching the org-standard file.
Co-authored-by: sneak <sneak@sneak.berlin>
Co-authored-by: clawbot <clawbot@eeqj.de>
Reviewed-on: #24
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>