Current templates: safe.directory, golangci-lint v2.14.0, fetch-depth 0, the policy's last stage (closes #50)
check / check (push) Failing after 2s
check / check (push) Failing after 2s
Brings keyfunc to the current sneak/prompts templates: REPO_POLICIES.md and .golangci.yml are the template copies, the lint phase runs golangci-lint v2.14.0 on the template digest (its one new finding fixed), and .dockerignore gains the template line for submodule configs. The stage that compiles keyfunc marks /src safe for git, so a context sent as a tar stream still stamps the tag or short commit. The last stage is now a development environment, as the policy asks of a non-server repo: run the tool as docker run IMAGE keyfunc .... The CI checkout fetches tags. Deviation: .gitea/workflows/check.yml differs from the template copy by fetch-depth: 0, which REPO_POLICIES.md requires. Model: opus-5-5
This commit was merged in pull request #56.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -6,4 +6,7 @@ jobs:
|
||||
steps:
|
||||
# actions/checkout v4.2.2, 2026-02-22
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
# All history and tags, which `git describe --tags` needs.
|
||||
fetch-depth: 0
|
||||
- run: script/cibuild
|
||||
|
||||
@@ -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
|
||||
|
||||
+27
-29
@@ -1,11 +1,11 @@
|
||||
# The lint phase, the test phase and the build. script/lint and
|
||||
# script/test each build one phase alone; a plain `docker build .` builds
|
||||
# both, because the build stage copies a file from each. Formatting is
|
||||
# checked on the host by script/fmt-check, not here.
|
||||
# The lint phase, the test phase and a development environment.
|
||||
# script/lint and script/test each build one phase alone; a plain
|
||||
# `docker build .` builds both, because the last stage copies a file from
|
||||
# each. Formatting is checked on the host by script/fmt-check, not here.
|
||||
|
||||
# Lint phase
|
||||
# golangci/golangci-lint:v2.12.2, 2026-09-07
|
||||
FROM golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS lint
|
||||
# golangci/golangci-lint:v2.14.0, 2026-10-04
|
||||
FROM golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
@@ -16,13 +16,12 @@ COPY . .
|
||||
|
||||
RUN golangci-lint run --config .golangci.yml ./...
|
||||
|
||||
# Test phase
|
||||
# golang:1.26-alpine, 2026-09-07. It carries Go 1.26.8, the version
|
||||
# script/bootstrap installs on the host; change both together.
|
||||
FROM golang@sha256:ce864e7223ac17b1775e6fd0b4c0db580c2eb50e7953a427916379e4b92a1628 AS test
|
||||
|
||||
# -race needs cgo, and cgo needs a C toolchain.
|
||||
RUN apk add --no-cache gcc musl-dev
|
||||
# Test phase. -race needs cgo and so a C compiler, which the Debian Go
|
||||
# image ships.
|
||||
# golang:1.26.8-trixie, 2026-10-04. It carries Go 1.26.8, the version
|
||||
# script/bootstrap installs on the host; change both together, and the
|
||||
# same image in the last stage.
|
||||
FROM golang@sha256:eae2aaa6add2936cbf350dd0d2628b363461542f0c4b3c0b558957e0f2997379 AS test
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
@@ -35,21 +34,27 @@ RUN go test -timeout 90s -race -cover ./... || \
|
||||
{ echo "--- Rerunning with -v for details ---"; \
|
||||
go test -timeout 90s -race -v ./...; exit 1; }
|
||||
|
||||
# Build stage. Nothing is wanted from either phase above; the copies
|
||||
# are what make BuildKit build them first, so this stage cannot run
|
||||
# unless lint and test passed.
|
||||
# golang:1.26-alpine, 2026-09-07
|
||||
FROM golang@sha256:ce864e7223ac17b1775e6fd0b4c0db580c2eb50e7953a427916379e4b92a1628 AS builder
|
||||
# Development environment, and the last stage: a plain `docker build .`
|
||||
# builds this one. It holds the source tree in /src, what
|
||||
# script/bootstrap installs, and keyfunc built from that tree on the
|
||||
# PATH. Nothing is wanted from either phase above; the copies are what
|
||||
# make BuildKit build them first, so this stage cannot run unless lint
|
||||
# and test passed.
|
||||
# golang:1.26.8-trixie, 2026-10-04
|
||||
FROM golang@sha256:eae2aaa6add2936cbf350dd0d2628b363461542f0c4b3c0b558957e0f2997379
|
||||
|
||||
COPY --from=lint /src/go.sum /dev/null
|
||||
COPY --from=test /src/go.sum /dev/null
|
||||
|
||||
RUN apk add --no-cache make 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
|
||||
# script/bootstrap needs only script/ and the dependency manifests.
|
||||
COPY script/ script/
|
||||
COPY go.mod go.sum package.json yarn.lock ./
|
||||
RUN script/bootstrap
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -64,11 +69,4 @@ RUN version="${VERSION:-$(git describe --tags --always || echo dev)}"; \
|
||||
echo "no version could be derived although the build context carries .git" >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
make build VERSION="$version"
|
||||
|
||||
# alpine:3.23, 2026-09-07
|
||||
FROM alpine@sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40
|
||||
|
||||
COPY --from=builder /src/keyfunc /usr/local/bin/keyfunc
|
||||
|
||||
ENTRYPOINT ["keyfunc"]
|
||||
make build VERSION="$version" && mv keyfunc /usr/local/bin/keyfunc
|
||||
|
||||
@@ -331,7 +331,10 @@ standard: most Makefile targets are thin shims over an executable in `script/`
|
||||
- `script/docker` builds the Docker image, uncached, tagged with the project
|
||||
name and stamped with the version `git describe` gives on the host. The image
|
||||
cannot be built unless the `lint` and `test` phases pass, so a plain
|
||||
`docker build .` runs them too.
|
||||
`docker build .` runs them too. The image is a development environment, not a
|
||||
runtime image: the Debian Go image with what `script/bootstrap` installs, the
|
||||
source tree in `/src`, and `keyfunc` built from it on the `PATH`.
|
||||
`docker run --rm -it keyfunc` opens a shell in it.
|
||||
- `script/cibuild` is the CI build the Gitea workflow calls: it runs
|
||||
`bootstrap`, then `check`, then builds the image as `script/docker` does.
|
||||
- `script/precommit` is what the git pre-commit hook runs: `go mod tidy` and
|
||||
|
||||
+55
-36
@@ -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
|
||||
@@ -592,10 +607,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 +641,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.
|
||||
|
||||
+1
-2
@@ -86,8 +86,7 @@ func Main() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
var passed ssh.StatusError
|
||||
if errors.As(err, &passed) {
|
||||
if passed, ok := errors.AsType[ssh.StatusError](err); ok {
|
||||
return passed.Status
|
||||
}
|
||||
|
||||
|
||||
@@ -88,8 +88,7 @@ func connect(ctx context.Context, argv []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ended *exec.ExitError
|
||||
if errors.As(err, &ended) {
|
||||
if ended, ok := errors.AsType[*exec.ExitError](err); ok {
|
||||
status := ended.ExitCode()
|
||||
if status < 0 {
|
||||
// A signal ended ssh, and a signal has no status of its
|
||||
|
||||
Reference in New Issue
Block a user