9 Commits
Author SHA1 Message Date
clawbot c43c1f4bca Pin host Go tools by commit hash with go install (closes #37)
check / check (push) Waiting to run
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
2026-10-04 09:49:13 +02:00
clawbot 567944f8d8 Ignore hardware-backed SSH key files in the canonical ignore files (closes #81)
check / check (push) Waiting to run
`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
2026-10-04 09:14:52 +02:00
clawbot fa3202f214 Give package.json the MIT license field (closes #76)
check / check (push) Waiting to run
`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
2026-10-04 08:31:46 +02:00
clawbot 562b40bfe5 Keep agent guidance in one root AGENTS.md (closes #31)
check / check (push) Waiting to run
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
2026-10-04 08:02:24 +02:00
clawbot 5805909fb9 Rewrite the -count=1 note to match the current files (closes #77)
check / check (push) Waiting to run
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
2026-10-04 07:31:48 +02:00
clawbot 3c1b435990 Fall back to dev when git describe prints nothing (closes #74)
check / check (push) Waiting to run
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
2026-10-04 07:02:27 +02:00
clawbot 5de98c404e Keep each submodule's git config out of the build context (closes #75)
check / check (push) Waiting to run
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
2026-10-04 05:31:51 +02:00
clawbot dcc0ba0b66 Fix git ownership and -race in the canonical Go Dockerfile (closes #73)
check / check (push) Waiting to run
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
2026-10-04 04:31:51 +02:00
clawbot 343628fb3a Pin golangci-lint v2.14.0; disable exhaustruct_v5 (closes #65)
check / check (push) Successful in 37s
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
2026-10-04 03:32:08 +02:00
10 changed files with 182 additions and 75 deletions
+5
View File
@@ -17,7 +17,10 @@
# stage that compiles runs `git describe --tags --always` on .git, which
# does not need .git/config; that file can hold a credential, such as a
# password in a remote URL or the token the CI checkout step stores there.
# Each submodule keeps a config with the same exposure in its git directory
# under .git/modules/, nested again for a submodule's own submodules.
.git/config
.git/modules/**/config
# Agent scratch: one full checkout of the repo per in-flight agent.
# Anchored because it occurs once where agents run at the repo root.
@@ -41,7 +44,9 @@
**/[iI][dD]_[rR][sS][aA]
**/[iI][dD]_[dD][sS][aA]
**/[iI][dD]_[eE][cC][dD][sS][aA]
**/[iI][dD]_[eE][cC][dD][sS][aA]_[sS][kK]
**/[iI][dD]_[eE][dD]25519
**/[iI][dD]_[eE][dD]25519_[sS][kK]
# Dependencies: restored inside the image, never copied in.
**/node_modules
+2
View File
@@ -42,4 +42,6 @@ node_modules/
[iI][dD]_[rR][sS][aA]
[iI][dD]_[dD][sS][aA]
[iI][dD]_[eE][cC][dD][sS][aA]
[iI][dD]_[eE][cC][dD][sS][aA]_[sS][kK]
[iI][dD]_[eE][dD]25519
[iI][dD]_[eE][dD]25519_[sS][kK]
+1
View File
@@ -17,6 +17,7 @@ linters:
disable:
# Genuinely incompatible with project patterns
- exhaustruct # Requires all struct fields
- exhaustruct_v5 # Requires all struct fields (successor to exhaustruct)
- godot # Requires comments to end with periods
- wrapcheck # Too verbose for internal packages
- varnamelen # Short names like db, id are idiomatic Go
+48
View File
@@ -21,6 +21,54 @@ fmt-check, and commit.
# Completed Steps
- 2026-10-04: `REPO_POLICIES.md` now says how a Go tool a repo needs on the host
is pinned (issue 37): installed with `go install` pinned to a commit hash,
never tracked as a `go.mod` tool dependency or through a `tools.go` file.
golangci-lint is unaffected, since no repo installs it on the host.
- 2026-10-04: The canonical `.gitignore` and `.dockerignore` now also keep out
`id_ecdsa_sk` and `id_ed25519_sk`, the private key files `ssh-keygen` writes
for keys backed by a hardware security key (issue 81). Their `.pub` halves
stay trackable.
- 2026-10-04: `package.json` now has `"license": "MIT"`, matching `LICENSE`, so
yarn no longer prints "No license field" when `script/bootstrap` runs it
inside the Docker phases (issue 76). That was the only yarn warning there.
- 2026-10-04: `REPO_POLICIES.md` now states that 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 in separate memory files (issue
31). This retires the rule, still present in older vendored copies, that kept
agent memory as committed files under `.claude/memory/`. `AGENTS.md` joins the
list of files allowed in the root, and both checklists say so.
- 2026-10-04: Rewrote the note under the canonical Go `make test` example in
`REPO_POLICIES.md` (issue 77), which still named the cache-busting build
argument that `--no-cache` replaced. It now says where Go's test result cache
can replay a pass: on a developer's machine, where the Makefile target runs,
and not in the `test` phase of the `Dockerfile`, whose base image and earlier
steps hold no result for the repo's tests.
- 2026-10-04: The Makefile examples in the Go styleguide and the HTTP server
conventions now fall back to `dev` when `git describe` prints nothing (outside
a git checkout, or where git is missing or refuses the checkout), instead of
stamping an empty version (issue 74). The canonical `Dockerfile` already fails
on a `dev` version when `.git` is in the build context.
- 2026-10-04: The canonical `.dockerignore` now also keeps out each submodule's
`config` (issue 75). A submodule's git directory lives under `.git/modules/`,
nested again for its own submodules, and its `config` can hold a credential
just like `.git/config`. The pattern `.git/modules/**/config` covers every
depth and leaves the top-level `.git` that `git describe` reads untouched.
`REPO_POLICIES.md` and both checklists say so in the same words.
- 2026-10-03: Fixed two defects in the canonical Go `Dockerfile` example (issue
73). The test phase now uses the Debian Go image, since `-race` needs cgo and
the alpine image has no C compiler, so the phase failed before running a test.
The stage that compiles runs `git config --system --add safe.directory /src`,
because a context sent as a tar stream keeps the sender's file owners and git
refuses that checkout, leaving the version empty. Both checklists state that
step in the same words.
- 2026-10-03: Moved the canonical golangci-lint to v2.14.0, built with go1.27,
because v2.12.2 refuses to lint a module whose `go` directive is 1.27 (issue
65). Releases from v2.13.0 deprecate `exhaustruct` in favour of
`exhaustruct_v5`, which `default: all` switches on, so the canonical
`.golangci.yml` now disables `exhaustruct_v5` beside `exhaustruct`. v2.12.2
rejects that file, so `REPO_POLICIES.md` and both repo checklists now say a
repo sets the lint phase digest and re-vendors `.golangci.yml` in one commit.
- 2026-10-03: Brought the canonical `.gitignore` level with `.dockerignore` on
secrets (issue 38): it now also ignores `prod.env`-style `*.env` files,
`.envrc`, `*.p12`, `*.pfx` and the extensionless SSH private keys, written to
+1
View File
@@ -1,4 +1,5 @@
{
"license": "MIT",
"devDependencies": {
"prettier": "3.8.1"
}
+5 -3
View File
@@ -1,6 +1,6 @@
---
title: Code Styleguide — Go
last_modified: 2026-10-02
last_modified: 2026-10-04
---
1. Try to hard wrap long lines at 77 characters or less.
@@ -51,8 +51,10 @@ last_modified: 2026-10-02
# ?= rather than := so that a `VERSION` build argument takes precedence:
# where a build stage invokes make, `ARG VERSION` puts it in the
# environment and `?=` defers to it. Otherwise `git describe` runs, in a
# build stage on the `.git` the build context carries.
VERSION ?= $(shell git describe --tags --always)
# build stage on the `.git` the build context carries. When it prints
# nothing (outside a git checkout, or where git is missing or refuses the
# checkout), the version falls back to `dev`.
VERSION ?= $(or $(shell git describe --tags --always 2>/dev/null),dev)
GOLDFLAGS += -X main.Version=$(VERSION)
+30 -18
View File
@@ -1,6 +1,6 @@
---
title: Existing Repo Checklist
last_modified: 2026-10-02
last_modified: 2026-10-04
---
Use this checklist when beginning work in a repo that may not yet conform to our
@@ -24,6 +24,10 @@ with your task.
- [ ] `LICENSE` file exists and matches the README
- [ ] `REPO_POLICIES.md` exists and version date is current — fetch from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md`
- [ ] Guidance for coding agents, if the repo has any, is one `AGENTS.md` at the
root — never a file or directory named after one agent tool, such as
`CLAUDE.md` or `.claude/`, and never separate memory files. Move what any
such committed file says into `AGENTS.md` and delete it.
- [ ] `.gitignore` is comprehensive (OS, editor, agent scratch, language
artifacts, secrets) — fetch from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` if missing.
@@ -59,28 +63,36 @@ with your task.
here run anywhere other than the repo root, the anchored entry misses
`services/api/.claude/`: add anchored entries for those directories.
- [ ] If the repo embeds a version in a binary: `.dockerignore` lets `.git` into
the build context. It keeps out `.git/config`, which `git describe` does
not need and which can hold a credential: a password in a remote URL, or
the token the CI checkout step stores there. The stage that compiles has
`git` (the Debian Go image has it; an alpine one needs
`apk add --no-cache git`) and takes the version from the `VERSION` build
argument when one is given, otherwise from `git describe --tags --always`.
That gives the tag on a tagged commit; on a later commit, the tag, the
number of commits since it and the short commit (`v1.2.3-4-gabc1234`); and
the short commit when no tag is reachable. `ARG VERSION` has no default,
and the build fails if the context carries `.git` and the version still
comes out empty, `dev` or `unknown`. 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. `script/docker` and
`script/cibuild` pass the version they compute on the host; it takes
precedence. A tag-derived version additionally needs `fetch-depth: 0` on
the CI checkout step, which clones shallow and fetches no tags by default.
the build context. It keeps out `.git/config` and each submodule's
`config` under `.git/modules/` at any depth (`.git/modules/**/config`),
which `git describe` does not need and which can hold a credential: a
password in a remote URL, or the token the CI checkout step stores there.
The stage that compiles has `git` (the Debian Go image has it; an alpine
one needs `apk add --no-cache git`) and takes the version from the
`VERSION` build argument when one is given, otherwise from
`git describe --tags --always`. That gives the tag on a tagged commit; on
a later commit, the tag, the number of commits since it and the short
commit (`v1.2.3-4-gabc1234`); and the short commit when no tag is
reachable. The stage that compiles also marks its working directory safe
for git (`git config --system --add safe.directory /src`): a context sent
as a tar stream keeps the sender's file owners, and git refuses a checkout
owned by another user, so the version would come out empty. `ARG VERSION`
has no default, and the build fails if the context carries `.git` and the
version still comes out empty, `dev` or `unknown`. 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.
`script/docker` and `script/cibuild` pass the version they compute on the
host; it takes precedence. A tag-derived version additionally needs
`fetch-depth: 0` on the CI checkout step, which clones shallow and fetches
no tags by default.
- [ ] Gitea Actions workflow in `.gitea/workflows/` runs `script/cibuild` on
push — reference
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml`
- [ ] Language-specific config:
- [ ] Go: `go.mod`, `go.sum`, `.golangci.yml` (fetch from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml`)
`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: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
(fetch from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrc` and
+5 -3
View File
@@ -1,6 +1,6 @@
---
title: Go HTTP Server Conventions
last_modified: 2026-10-02
last_modified: 2026-10-04
---
This document defines the architectural patterns, design decisions, and
@@ -987,8 +987,10 @@ Use ldflags to inject version information at build time:
# ?= rather than := so that a `VERSION` build argument takes precedence:
# where a build stage invokes make, `ARG VERSION` puts it in the
# environment and `?=` defers to it. Otherwise `git describe` runs, in a
# build stage on the `.git` the build context carries.
VERSION ?= $(shell git describe --tags --always)
# build stage on the `.git` the build context carries. When it prints
# nothing (outside a git checkout, or where git is missing or refuses the
# checkout), the version falls back to `dev`.
VERSION ?= $(or $(shell git describe --tags --always 2>/dev/null),dev)
build:
go build -ldflags "-X main.Version=$(VERSION)" ./cmd/httpd
+25 -15
View File
@@ -1,6 +1,6 @@
---
title: New Repo Checklist
last_modified: 2026-10-02
last_modified: 2026-10-04
---
Use this checklist when creating a new repository from scratch. Follow the steps
@@ -54,6 +54,9 @@ Template files can be fetched from:
- [ ] `LICENSE` file matching the chosen license
- [ ] `REPO_POLICIES.md` — fetch from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md`
- [ ] Guidance for coding agents, if the repo has any, is one `AGENTS.md` at the
root — never a file or directory named after one agent tool, such as
`CLAUDE.md` or `.claude/`, and never separate memory files
- [ ] `Dockerfile` and `.dockerignore` — fetch `.dockerignore` from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore`
- Extend `.dockerignore` with the repo's own host-built artifacts, giving
@@ -68,19 +71,24 @@ Template files can be fetched from:
will run them in subdirectories, `services/api/.claude/` needs its own
anchored entry.
- If the image embeds a version in a binary: `.dockerignore` lets `.git`
into the build context. It keeps out `.git/config`, which `git describe`
does not need and which can hold a credential: a password in a remote URL,
or the token the CI checkout step stores there. The stage that compiles
has `git` (the Debian Go image has it; an alpine one needs
`apk add --no-cache git`) and takes the version from the `VERSION` build
argument when one is given, otherwise from `git describe --tags --always`.
That gives the tag on a tagged commit; on a later commit, the tag, the
number of commits since it and the short commit (`v1.2.3-4-gabc1234`); and
the short commit when no tag is reachable. `ARG VERSION` has no default,
and the build fails if the context carries `.git` and the version still
comes out empty, `dev` or `unknown`. 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.
into the build context. It keeps out `.git/config` and each submodule's
`config` under `.git/modules/` at any depth (`.git/modules/**/config`),
which `git describe` does not need and which can hold a credential: a
password in a remote URL, or the token the CI checkout step stores there.
The stage that compiles has `git` (the Debian Go image has it; an alpine
one needs `apk add --no-cache git`) and takes the version from the
`VERSION` build argument when one is given, otherwise from
`git describe --tags --always`. That gives the tag on a tagged commit; on
a later commit, the tag, the number of commits since it and the short
commit (`v1.2.3-4-gabc1234`); and the short commit when no tag is
reachable. The stage that compiles also marks its working directory safe
for git (`git config --system --add safe.directory /src`): a context sent
as a tar stream keeps the sender's file owners, and git refuses a checkout
owned by another user, so the version would come out empty. `ARG VERSION`
has no default, and the build fails if the context carries `.git` and the
version still comes out empty, `dev` or `unknown`. 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.
- The Dockerfile carries a `lint` phase and a `test` phase, each invoking
its tool directly rather than through `make` or `script/`, and the final
stage carries a `COPY --from=` of a harmless file from each so the image
@@ -94,7 +102,9 @@ Template files can be fetched from:
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml`
- [ ] Language-specific:
- [ ] Go: `go mod init sneak.berlin/go/<name>`, `.golangci.yml` (fetch from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml`)
`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: `yarn init`, `yarn add --dev prettier`
- [ ] Python: `pyproject.toml`
+60 -36
View File
@@ -1,6 +1,6 @@
---
title: Repository Policies
last_modified: 2026-10-02
last_modified: 2026-10-04
---
This document covers repository structure, tooling, and workflow standards. Code
@@ -160,7 +160,7 @@ style conventions are in separate documents:
- **The gate phases are separate stages, and the build stage depends on both.**
The lint phase is based on the `golangci/golangci-lint` image (pinned by
hash), so lint failures surface in seconds rather than after a full compile,
and the test phase is based on the Go image. The canonical Go repo
and the test phase is based on the Debian Go image. The canonical Go repo
`Dockerfile`:
```dockerfile
@@ -173,8 +173,9 @@ style conventions are in separate documents:
COPY . .
RUN golangci-lint run --config .golangci.yml ./...
# Test phase
# golang:1.x-alpine, YYYY-MM-DD
# Test phase. -race needs cgo and so a C compiler, which the Debian Go
# image ships and the alpine one does not.
# golang:1.x, YYYY-MM-DD
FROM golang@sha256:... AS test
WORKDIR /src
COPY go.mod go.sum ./
@@ -192,6 +193,8 @@ style conventions are in separate documents:
COPY --from=lint /src/go.sum /dev/null
COPY --from=test /src/go.sum /dev/null
RUN apk add --no-cache git
# A tar-stream context keeps the sender's file owners, which git refuses.
RUN git config --system --add safe.directory /src
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
@@ -236,19 +239,23 @@ style conventions are in separate documents:
- If the project requires CGO or system libraries for linting (e.g.
`vips-dev`), install them in the lint phase with `apk add`.
- `.dockerignore` lets `.git` into the build context. It keeps out
`.git/config`, which `git describe` does not need and which can hold a
credential: a password in a remote URL, or the token the CI checkout step
stores there. The stage that compiles has `git` (the Debian Go image has
it; an alpine one needs `apk add --no-cache git`) and takes the version
from the `VERSION` build argument when one is given, otherwise from
`git describe --tags --always`. That gives the tag on a tagged commit; on
a later commit, the tag, the number of commits since it and the short
commit (`v1.2.3-4-gabc1234`); and the short commit when no tag is
reachable. `ARG VERSION` has no default, and the build fails if the
context carries `.git` and the version still comes out empty, `dev` or
`unknown`. 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.
`.git/config` and each submodule's `config` under `.git/modules/` at any
depth (`.git/modules/**/config`), which `git describe` does not need and
which can hold a credential: a password in a remote URL, or the token the
CI checkout step stores there. The stage that compiles has `git` (the
Debian Go image has it; an alpine one needs `apk add --no-cache git`) and
takes the version from the `VERSION` build argument when one is given,
otherwise from `git describe --tags --always`. That gives the tag on a
tagged commit; on a later commit, the tag, the number of commits since it
and the short commit (`v1.2.3-4-gabc1234`); and the short commit when no
tag is reachable. The stage that compiles also marks its working directory
safe for git (`git config --system --add safe.directory /src`): a context
sent as a tar stream keeps the sender's file owners, and git refuses a
checkout owned by another user, so the version would come out empty.
`ARG VERSION` has no default, and the build fails if the context carries
`.git` and the version still comes out empty, `dev` or `unknown`. 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.
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
runs `script/cibuild` on push, and checks out the repo as its only other step.
@@ -310,17 +317,19 @@ style conventions are in separate documents:
```
`-count=1` is required on both invocations: it defeats Go's test _result_
cache, so the target cannot report a pass it did not earn, and the rerun
reproduces a failure instead of replaying it. It leaves the build cache
alone, so it costs the runtime of the suite and no recompilation.
cache, so neither run can report a stored pass in place of running the
tests. It leaves the build cache alone, so it costs the runtime of the suite
and no recompilation.
Note that this is a second, independent cache, stacked below the Docker
layer cache that [issue #26](https://git.eeqj.de/sneak/prompts/issues/26)
addresses. `CHECK_EPOCH` guarantees the `RUN make test` _step_ re-executes;
it does not guarantee `go test` inside that step does any work, because the
`GOCACHE` baked into earlier image layers survives into the re-executed
step. They are two separate defects requiring two separate fixes, and a fix
for one must not be recorded as covering the other.
That cache is Go's own, separate from Docker's layer cache. Go stores a
passing result in its cache directory (`GOCACHE`), and when the same tests
run again on unchanged code it prints that result, marked `(cached)`,
without running them. That matters on a developer's machine, where this
target runs and the directory lasts from one run to the next. The `test`
phase of the `Dockerfile` needs no `-count=1`: its base image holds no
result for this repo's tests and nothing before its `go test` step runs a
test, so there is nothing to replay. `--no-cache` (above) is what makes that
step run on an unchanged tree.
Python example:
@@ -451,12 +460,18 @@ style conventions are in separate documents:
`test-support` depguard rule, where a repo names its own test-support packages
by full import path. A repo adds entries there and changes nothing else, and a
re-vendor carries its entries forward. The canonical golangci-lint version is
v2.12.2 (released 2026-05-06), pinned as the digest of the lint phase's base
v2.14.0 (released 2026-09-24), pinned as the digest of the lint phase's base
image
(`golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240`,
which reports `2.12.2 built with go1.26.2 from c0d3ddc9`). That digest is the
only pin, since no repo installs golangci-lint on the host: bumping the
version means changing it and nothing else.
(`golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f`,
which reports `2.14.0 built with go1.27.0 from 114493f9`). A module's `go`
directive must not name a newer Go minor version than the one golangci-lint
was built with, or golangci-lint refuses to lint it: this release lints
`go 1.27.1` but not `go 1.28`. That digest is the only pin, since no repo
installs golangci-lint on the host. A repo sets the lint phase digest to the
one named here and re-vendors `.golangci.yml` in the same commit, whichever of
the two prompted the change: the canonical copy can name linters that an older
golangci-lint rejects, and a newer golangci-lint can add linters that
`default: all` switches on until the canonical copy disables them.
- **`script/bootstrap` installs a pinned tool by comparing versions, never by
testing presence.** An `if ! command -v <tool>; then install; fi` guard tests
@@ -480,6 +495,11 @@ style conventions are in separate documents:
Keep it POSIX sh: no arrays, no `[[`, no `grep -P`.
A Go tool a repo needs on the host is installed with `go install` pinned to
a commit hash (`go install <package>@<commit hash>`). It 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`.
- When pinning images or packages by hash, add a comment above the reference
with the version and date (YYYY-MM-DD).
@@ -592,10 +612,10 @@ style conventions are in separate documents:
settings.
- Avoid putting files in the repo root unless necessary. Root should contain
only project-level config files (`README.md`, `Makefile`, `Dockerfile`,
`LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md`, and
language-specific config). Everything else goes in a subdirectory. Canonical
subdirectory names:
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:
- `bin/` — executable scripts and tools
- `cmd/` — Go command entrypoints; thin only: one `main.go` per binary whose
body is a single call into `internal/` or `pkg/`, no project logic in
@@ -626,3 +646,7 @@ style conventions are in separate documents:
- Go: `go.mod`, `go.sum`, `.golangci.yml`
- JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
- Python: `pyproject.toml`
- Guidance for coding agents lives in one `AGENTS.md` at the repository root. It
is never committed under a file or directory named after one agent tool, such
as `CLAUDE.md` or `.claude/`, and never split into separate memory files.