Seed from go-template-repo, renamed to simplexcalc

The template's files at a77fd30, without its history or LICENSE, after
script/rename simplexcalc.

Model: opus-5-5
This commit is contained in:
clawbot
2026-09-26 21:38:57 +00:00
parent e336f46e34
commit f8ce8cef83
79 changed files with 7131 additions and 0 deletions
+446
View File
@@ -0,0 +1,446 @@
---
title: Repository Policies
last_modified: 2026-08-07
---
This document covers repository structure, tooling, and workflow
standards. Code style conventions are in separate documents:
- [Code Styleguide](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE.md)
(general, bash, Docker)
- [Go](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE_GO.md)
- [JavaScript](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE_JS.md)
- [Python](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE_PYTHON.md)
- [Go HTTP Server Conventions](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/GO_HTTP_SERVER_CONVENTIONS.md)
---
- Cross-project documentation (such as this file) must include
`last_modified: YYYY-MM-DD` in the YAML front matter so it can be kept
in sync with the authoritative source as policies evolve.
- **ALL external references must be pinned by cryptographic hash.** This
includes Docker base images, Go modules, npm packages, GitHub Actions,
and anything else fetched from a remote source. Version tags (`@v4`,
`@latest`, `:3.21`, etc.) are server-mutable and therefore remote code
execution vulnerabilities. The ONLY acceptable way to reference an
external dependency is by its content hash (Docker `@sha256:...`, Go
module hash in `go.sum`, npm integrity hash in lockfile, GitHub
Actions `@<commit-sha>`). No exceptions. This also means never
`curl | bash` to install tools like pyenv, nvm, rustup, etc. Instead,
download a specific release archive from GitHub, verify its hash
(hardcoded in the Dockerfile or script), and only then install.
Unverified install scripts are arbitrary remote code execution. This
is the single most important rule in this document. Double-check every
external reference in every file before committing. There are zero
exceptions to this rule.
- Every repo with software must have a root `Makefile` with these
targets: `make bootstrap`, `make setup`, `make test`, `make lint`,
`make fmt` (writes), `make fmt-check` (read-only), `make check` (runs
`test`, `lint`, `fmt-check`), `make docker`, and `make hooks`
(installs pre-commit hook). A model Makefile is at
`https://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile`.
- Repos follow the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
pattern: the implementation of each Makefile target lives in an
executable script in `script/` (`script/bootstrap`, `script/setup`,
`script/test`, `script/lint`, `script/fmt`, `script/fmt-check`,
`script/check`, `script/docker`), and the Makefile targets are thin
shims that call them. The scripts must be POSIX sh (`#!/bin/sh`,
`set -eu`, no bashisms) so they run in minimal containers (e.g. alpine
images have no bash); locate the repo root with
`$(cd "$(dirname "$0")/.." && pwd -P)` and `cd` there before acting.
From the standard's canonical set we use `bootstrap`, `setup` (make
the repo ready for development after a fresh clone: runs `bootstrap`,
then `install-precommit`, plus any repo-specific initialization),
`test`, and `cibuild`. `script/bootstrap` installs all dependencies
idempotently and assumes nothing is present: base tools come from nix,
apt, brew, or apk (detected in that order; apt runs noninteractive).
For node it uses the installed node if present; otherwise it installs
a PINNED node version via nvm, first installing nvm itself if missing
— from a hash-verified GitHub release archive (never `curl | sh`),
with bash installed as an explicit prerequisite since nvm requires
bash. yarn is then pinned via
`corepack prepare yarn@<version> --activate`. Never install "latest"
or "lts"; always exact versions. `script/cibuild` runs the CI build:
it changes to the repo root and runs `docker build .`; the Gitea
workflow calls it. Four further scripts are our own extensions to the
standard: `script/check` runs `script/test`, `script/lint`, and
`script/fmt-check`; `script/precommit` is what the git pre-commit hook
runs, and it calls `script/check`; `script/install-precommit` installs
the git pre-commit hook (the `make hooks` target shims to it); and
`script/projectname` (literally that filename) simply outputs the
project's name. Scripts that need the name call `script/projectname` —
e.g. `script/docker` assembles its image tag from it — so those
scripts stay byte-identical across all repos. Repo-type-specific
pre-commit extras (e.g. `go mod tidy` verification in Go repos) belong
in `script/precommit`, not in the hook itself. Model scripts are at
`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`. The
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.
- The Makefile is authoritative documentation for how the repo is used.
Beyond the required targets above, it should have targets for every
common operation: running a local development server (`make run`,
`make dev`), re-initializing or migrating the database
(`make db-reset`, `make migrate`), building artifacts (`make build`),
generating code, seeding data, or anything else a developer would do
regularly. If someone checks out the repo and types `make<tab>`, they
should see every meaningful operation available. A new contributor
should be able to understand the entire development workflow by
reading the Makefile.
- Every repo should have a `Dockerfile`. All Dockerfiles must run
`make check` as a build step so the build fails if the branch is not
green. For non-server repos, the Dockerfile should bring up a
development environment and run `make check`. For server repos,
`make check` should run as an early build stage before the final image
is assembled. Dockerfiles install development prerequisites by running
`script/bootstrap` rather than duplicating installs inline; COPY
`script/` and the dependency manifests (`package.json` + `yarn.lock`,
`go.mod` + `go.sum`, etc.) before running it so the bootstrap layer
stays cached until dependencies change.
- **Dockerfiles must use a separate lint stage for fail-fast feedback.**
Go repos use a multistage build where linting runs in an independent
stage based on the `golangci/golangci-lint` image (pinned by hash).
This stage runs `make fmt-check` and `make lint` before the full build
begins. The build stage then declares an explicit dependency on the
lint stage via `COPY --from=lint /src/go.sum /dev/null`, which forces
BuildKit to complete linting before proceeding to compilation and
tests. This ensures lint failures surface in seconds rather than
minutes, without blocking on dependency download or compilation in the
build stage.
The standard pattern for a Go repo Dockerfile is:
```dockerfile
# Lint stage — fast feedback on formatting and lint issues
# golangci/golangci-lint:v2.x.x, YYYY-MM-DD
FROM golangci/golangci-lint@sha256:... AS lint
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN make fmt-check
RUN make lint
# Build stage
# golang:1.x-alpine, YYYY-MM-DD
FROM golang@sha256:... AS builder
WORKDIR /src
# Force BuildKit to run the lint stage before proceeding
COPY --from=lint /src/go.sum /dev/null
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN make test
ARG VERSION=dev
RUN CGO_ENABLED=0 go build -trimpath \
-ldflags="-s -w -X main.Version=${VERSION}" \
-o /app ./cmd/app/
# Runtime stage
FROM alpine@sha256:...
COPY --from=builder /app /usr/local/bin/app
ENTRYPOINT ["app"]
```
Key points:
- The lint stage uses the `golangci/golangci-lint` image directly
(it includes both Go and the linter), so there is no need to
install the linter separately.
- `COPY --from=lint /src/go.sum /dev/null` is a no-op file copy that
creates a stage dependency. BuildKit runs stages in parallel by
default; without this line, the build stage would not wait for
lint to finish and a lint failure might not fail the overall
build.
- If the project uses `//go:embed` directives that reference build
artifacts (e.g. a web frontend compiled in a separate stage), the
lint stage must create placeholder files so the embed directives
resolve. Example:
`RUN mkdir -p web/dist && touch web/dist/index.html web/dist/style.css`.
The lint stage should not depend on the actual build output — it
exists to fail fast.
- If the project requires CGO or system libraries for linting (e.g.
`vips-dev`), install them in the lint stage with `apk add`.
- The build stage runs `make test` after compilation setup. Tests
run in the build stage, not the lint stage, because they may
require compiled artifacts or heavier dependencies.
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`)
that runs `script/cibuild` (which runs `docker build .`) on push.
Since the Dockerfile already runs `make check`, a successful build
implies all checks pass.
- Use platform-standard formatters: `black` for Python, `prettier` for
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default
configuration with two exceptions: four-space indents (except Go), and
`proseWrap: always` for Markdown (hard-wrap at 80 columns).
Documentation and writing repos (Markdown, HTML, CSS) should also have
`.prettierrc` and `.prettierignore`.
- Pre-commit hook: runs `script/precommit`, which calls `script/check`.
If local testing is not possible in the repo, `script/precommit` may
skip `script/test` and run only `script/lint` and `script/fmt-check`.
The hook is installed by `script/install-precommit`; the Makefile must
provide a `make hooks` target that shims to it.
- All repos with software must have tests that run via the
platform-standard test framework (`go test`, `pytest`,
`jest`/`vitest`, etc.). If no meaningful tests exist yet, add the most
minimal test possible — e.g. importing the module under test to verify
it compiles/parses. There is no excuse for `make test` to be a no-op.
- `make test` must complete in under 60 seconds. That is the hard cap,
and a suite that exceeds it fails. Under 20 seconds is the target. A
suite between 20 and 60 seconds is still green, but the overage must
be filed as an improvement bug against that repo. Add a 90-second
timeout to the test invocation in the Makefile
(`go test -timeout 90s`). The backstop deliberately sits above the
hard cap so that it catches a genuinely hung test rather than a merely
slow one.
- **`make test` should use the conditional verbose rerun pattern.** Run
tests without `-v` (verbose) first. If tests fail, automatically rerun
with `-v` to show full output. This keeps CI logs and `docker build`
output clean on success (just package/suite summaries) while providing
full diagnostic detail on failure (every test case, every assertion).
The general shell pattern:
```makefile
test:
@<test-command> || \
{ echo "--- Rerunning with -v for details ---"; \
<test-command-with-v>; exit 1; }
```
Go example:
```makefile
test:
@go test -timeout 90s -race -cover ./... || \
{ echo "--- Rerunning with -v for details ---"; \
go test -timeout 90s -race -v ./...; exit 1; }
```
Python example:
```makefile
test:
@python -m pytest || \
{ echo "--- Rerunning with -v for details ---"; \
python -m pytest -v; exit 1; }
```
The `exit 1` ensures the target always fails after a rerun — the
first run already proved the tests are broken, so the build must not
pass even if a flaky test happens to succeed on the second attempt.
The rerun exists solely for diagnostic output.
- Docker builds must complete in under 5 minutes.
- `make check` must not modify any files in the repo. Tests may use
temporary directories.
- `main` must always pass `make check`, no exceptions.
- Never commit secrets. `.env` files, credentials, API keys, and private
keys must be in `.gitignore`. No exceptions.
- `.gitignore` should be comprehensive from the start: OS files
(`.DS_Store`), editor files (`.swp`, `*~`), language build artifacts,
and `node_modules/`. Fetch the standard `.gitignore` from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` when
setting up a new repo.
- **No build artifacts in version control.** Code-derived data (compiled
bundles, minified output, generated assets) must never be committed to
the repository if it can be avoided. 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.
- Never use `git add -A` or `git add .`. Always stage files explicitly
by name.
- Never force-push to `main`.
- Make all changes on a feature branch. You can do whatever you want on
a feature branch.
- `.golangci.yml` is standardized and must _NEVER_ be modified by an
agent, only manually by the user. Fetch from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml`. The
canonical golangci-lint version is v2.12.2 (released 2026-05-06),
installed commit-pinned via
`go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9cf3faa61a4e378e879ece580256d76e5`.
- When pinning images or packages by hash, add a comment above the
reference with the version and date (YYYY-MM-DD).
- Use `yarn`, not `npm`.
- Write all dates as YYYY-MM-DD (ISO 8601).
- Simple projects should be configured with environment variables.
- Dockerized web services listen on port 8080 by default, overridable
with `PORT`.
- **HTTP/web services must be hardened for production internet exposure
before tagging 1.0.** This means full compliance with security best
practices including, without limitation, all of the following:
- **Security headers** on every response:
- `Strict-Transport-Security` (HSTS) with `max-age` of at least
one year and `includeSubDomains`.
- `Content-Security-Policy` (CSP) with a restrictive default
policy (`default-src 'self'` as a baseline, tightened
per-resource as needed). Never use `unsafe-inline` or
`unsafe-eval` unless unavoidable, and document the reason.
- `X-Frame-Options: DENY` (or `SAMEORIGIN` if framing is
required). Prefer the `frame-ancestors` CSP directive as the
primary control.
- `X-Content-Type-Options: nosniff`.
- `Referrer-Policy: strict-origin-when-cross-origin` (or
stricter).
- `Permissions-Policy` restricting access to browser features
the application does not use (camera, microphone, geolocation,
etc.).
- **Request and response limits:**
- Maximum request body size enforced on all endpoints (e.g. Go
`http.MaxBytesReader`). Choose a sane default per-route; never
accept unbounded input.
- Maximum response body size where applicable (e.g. paginated
APIs).
- `ReadTimeout` and `ReadHeaderTimeout` on the `http.Server` to
defend against slowloris attacks.
- `WriteTimeout` on the `http.Server`.
- `IdleTimeout` on the `http.Server`.
- Per-handler execution time limits via `context.WithTimeout` or
chi/stdlib `middleware.Timeout`.
- **Authentication and session security:**
- Rate limiting on password-based authentication endpoints. API
keys are high-entropy and not susceptible to brute force, so
they are exempt.
- CSRF tokens on all state-mutating HTML forms. API endpoints
authenticated via `Authorization` header (Bearer token, API
key) are exempt because the browser does not attach these
automatically.
- Passwords stored using bcrypt, scrypt, or argon2 — never
plain-text, MD5, or SHA.
- Session cookies set with `HttpOnly`, `Secure`, and
`SameSite=Lax` (or `Strict`) attributes.
- **Reverse proxy awareness:**
- True client IP detection when behind a reverse proxy
(`X-Forwarded-For`, `X-Real-IP`). The application must accept
forwarded headers only from a configured set of trusted proxy
addresses — never trust `X-Forwarded-For` unconditionally.
- **CORS:**
- Authenticated endpoints must restrict
`Access-Control-Allow-Origin` to an explicit allowlist of
known origins. Wildcard (`*`) is acceptable only for public,
unauthenticated read-only APIs.
- **Error handling:**
- Internal errors must never leak stack traces, SQL queries,
file paths, or other implementation details to the client.
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 application so
that the browser enforces HTTPS end-to-end.
This list is non-exhaustive. Apply defense-in-depth: if a standard
security hardening measure exists for HTTP services and is not
listed here, it is still expected. When in doubt, harden.
- `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."
- **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)
standard (with that link), then documents each provided `script/`
entrypoint and its purpose.
- **Rationale**: Why does this exist?
- **Design**: How is the program structured?
- **TODO**: Update meticulously, even between commits. When
planning, put the todo list in the README so a new agent can pick
up where the last one left off.
- **License**: MIT, GPL, or WTFPL. Ask the user for new projects.
Include a `LICENSE` file in the repo root and a License section in
the README.
- **Author**: [@sneak](https://sneak.berlin).
- First commit of a new repo should contain only `README.md`.
- Go module root: `sneak.berlin/go/<name>`. Always run `go mod tidy`
before committing.
- Use SemVer.
- Database migrations live in `internal/db/migrations/` and must be
embedded in the binary.
- `000_migration.sql` — contains ONLY the creation of the migrations
tracking table itself. Nothing else.
- `001_schema.sql` — the full application schema.
- **Pre-1.0.0:** never add additional migration files (002, 003,
etc.). There is no installed base to migrate. Edit
`001_schema.sql` directly.
- **Post-1.0.0:** add new numbered migration files for each schema
change. Never edit existing migrations after release.
- All repos should have an `.editorconfig` enforcing the project's
indentation settings.
- Avoid putting files in the repo root unless necessary. Root should
contain only project-level config files (`Makefile`, `Dockerfile`,
`.gitignore`, `.editorconfig`, and language-specific config) plus
exactly three documentation files: `README.md`, `AGENTS.md` and
`LICENSE`. Every other markdown file — `REPO_POLICIES.md`, `TODO.md`,
design notes, runbooks — lives under `docs/`. Everything else goes in
a subdirectory. Canonical subdirectory names:
- `bin/` — executable scripts and tools
- `cmd/` — Go command entrypoints
- `configs/` — configuration templates and examples
- `deploy/` — deployment manifests (k8s, compose, terraform)
- `docs/` — all markdown except the three root files above
- `internal/` — Go internal packages
- `internal/db/migrations/` — database migrations
- `pkg/` — Go library packages
- `share/` — systemd units, data files
- `static/` — static assets (images, fonts, etc.)
- `web/` — web frontend source
- When setting up a new repo, files from the `prompts` repo may be used
as templates. Fetch them from
`https://git.eeqj.de/sneak/prompts/raw/branch/main/<path>`.
- New repos must contain at minimum:
- `README.md`, `AGENTS.md`, `.git`, `.gitignore`, `.editorconfig`
- `LICENSE`, `docs/REPO_POLICIES.md` (copy from the `prompts` repo)
- `Makefile`
- `script/` entrypoints (`bootstrap`, `setup`, `projectname`,
`test`, `lint`, `fmt`, `fmt-check`, `check`, `docker`, `cibuild`,
`precommit`, `install-precommit`)
- `Dockerfile`, `.dockerignore`
- `.gitea/workflows/check.yml`
- Go: `go.mod`, `go.sum`, `.golangci.yml`
- JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
- Python: `pyproject.toml`
+53
View File
@@ -0,0 +1,53 @@
# Workflow
- branch (from `main`)
- do the work in Next Step
- move Next Step to the top of Completed Steps
- move the top item of Future Steps into Next Step
- commit (`docs/TODO.md` changes in the same commit as the work)
- merge to `main` if the branch is not protected, otherwise open a PR
- push
# Status
pre-1.0. No git tags exist. `main` is a working HTTP service that
builds, tests and lints clean: cobra command tree, `fx` object graph,
viper configuration that aborts on an unparseable value, sqlite with an
embedded schema and a migration runner, embedded templates and static
assets, the full middleware chain (request id, logging, metrics, panic
recovery, request timeout, body cap, security headers, CSRF), Sentry,
and Prometheus `/metrics` behind optional basic auth.
This repository is a template. A project seeded from it should replace
this Status section and everything below it, keeping the Workflow
section above unchanged.
# Next Step
Replace `gomodguard` with `gomodguard_v2` in `.golangci.yml`.
golangci-lint v2.12.2 emits a deprecation warning for it on every run
(`the linter 'gomodguard' is deprecated (since v2.12.0) ... Replaced by gomodguard_v2`).
Neither is configured with rules here, so the change is to the `linters`
block only; done when `make lint` runs clean with no deprecation warning
in the output.
# Completed Steps
- 2026-08-22 Built the template out from an empty repository: STRTA
`script/` entrypoints with `Makefile` shims, `Dockerfile` and
`Dockerfile.lint` on digest-pinned bases, Gitea workflow running
`script/cibuild`, `.golangci.yml`, `.editorconfig`, `.prettierrc`,
`docs/REPO_POLICIES.md`, `AGENTS.md`, the HTTP service and its tests,
and `script/rename` for seeding
# Future Steps
- Add a `docker-compose.yml` showing the service behind a
TLS-terminating reverse proxy with `X-Forwarded-Proto` set, since that
is the deployment shape the CSRF middleware is written for and the one
an operator is most likely to get wrong
- Add a `script/release` that tags, builds with `VERSION` set, and
pushes the image, so `globals.Version` is something other than `dev`
in a real deployment
- Decide whether the template should ship a session/auth layer or stay
deliberately without one