Files
simplexcalc/AGENTS.md
T
clawbot f8ce8cef83 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
2026-09-26 21:38:57 +00:00

179 lines
8.2 KiB
Markdown

---
title: Agent Guidance
last_modified: 2026-08-22
---
This file is the single source of guidance for any automated agent
working in this repository. There is no other agent-facing file, and
none is to be created: no vendor-named directory and no vendor-named
markdown file is ever committed.
Keeping such directories out of commits is what `.gitignore` is for, and
listing them there is correct — an ignore entry is plumbing, not
documentation. What must not appear is vendor-named _content_: committed
configuration, instructions or notes addressed to one particular tool.
Guidance goes here instead, written so that any agent can follow it.
Nothing in this repository names an assistant, model or vendor in its
prose. That rule propagates: every project seeded from this template
inherits it.
Read `docs/REPO_POLICIES.md` as well. Where this file and that one
overlap, they agree; where this file is silent, that one governs.
## What this repository is
A template. It is a working HTTP service — it builds, tests, lints and
runs — whose purpose is to be copied. Nothing in it is precious. The
`widgets` table, the index page and the debug panic route are there to
prove the machinery works end to end, and are meant to be deleted.
## Iron rules
These are not preferences. A change that breaks one of them is wrong
regardless of what else it achieves.
1. **Never mention any assistant, model, vendor, or the involvement of
automated assistance** — not in code, comments, documentation, commit
messages, PR bodies, or trailers. No `Co-Authored-By`, no session
links, no attribution of any kind.
2. **Linting runs in Docker, never on the host.** `script/lint` builds
`Dockerfile.lint` against a digest-pinned image. Do not install
`golangci-lint` locally and do not run it directly.
3. **Never run `go build`, `go test`, `go vet`, `gofmt` or
`golangci-lint` directly.** Use the `make` targets or the `script/`
entrypoints. They carry the flags and policies (`-count=1`, `-race`,
timeouts, docker-only linting) that a raw invocation silently
bypasses. This applies to mid-task checks, not just the final one.
4. **Configuration that is set but unparseable aborts startup.** A
default applies only to a value that is ABSENT. Silently substituting
a default for a value the operator got wrong turns their mistake into
a misconfiguration that surfaces much later, somewhere else. See
`internal/config`. Reject any change that weakens this.
5. **Never use scripted search-and-replace to edit files** — no
`sed -i`, `perl -pi`, `awk` rewrites, or scripted heredocs. Read the
file and edit it, even when there are many similar edits. The single
exception is `script/rename`, which is a tool an operator runs once,
against a known-shaped tree, at seed time — and which deletes itself
afterwards.
6. **Formatting only via `make fmt`.** Never hand-roll a reformat.
`make fmt-check` is the read-only form and must stay non-mutating.
7. **Pin every external reference by hash.** Docker base images by
`@sha256:`, Actions by commit SHA, Go modules by `go.sum`. A version
tag is server-mutable and therefore remote code execution.
8. **Clean up every container and image you create. Never run
`docker builder prune`, `docker image prune`, or any other prune.**
Shared infrastructure purges on its own schedule; a prune destroys
other people's work in progress.
9. **Write verbatim identifiers in backticks in markdown** — branch
names, filenames, commands, environment variables. This is what
distinguishes a branch literally named `next` from the word "none"
meaning no such branch exists.
## Entrypoints
Everything is a `script/` entrypoint with a thin `make` shim. The
scripts are the real interface; `make` exists because fingers know it.
See the Entrypoints section of `README.md` for what each one does.
The two that matter most:
- `make check` — `test` + `lint` + `fmt-check`. Never modifies files.
This is the gate for a commit.
- `script/cibuild` — the container build with the lint and test stages
cache-busted, plus assertions that they really ran and really saw the
whole repository. This is the gate for a push, and the Gitea workflow
runs it.
A green `docker build` on its own proves nothing: an unchanged tree
serves every layer from cache and exits 0 having executed nothing, and a
`.dockerignore` entry can hide a package from a linter that then
truthfully reports `0 issues.` over what is left.
`script/assert-step-ran` and `script/assert-context-complete` exist to
close both holes, and their comments explain what they still cannot see.
Do not weaken them.
## Branching model
- `main` is the default branch and is kept green.
- Work happens on a branch off `main`.
- Merge to `main` directly when the branch is not protected; otherwise
open a PR.
- `docs/TODO.md` changes go in the same commit as the work they
describe.
- Push finished work to the remote. Work that exists only locally is
treated as lost.
- Never force-push, and never rewrite published history.
## Layout
```
cmd/simplexcalc/ cobra command tree; main() and serve
internal/app/ the fx object graph — the only place that knows
which concrete type satisfies what
internal/config/ viper-backed configuration; the abort-on-garbage rule
internal/database/ sqlite handle, embedded schema, migration runner
internal/globals/ build-time metadata (ldflags)
internal/handlers/ HTTP handlers, one struct, fx-injected dependencies
internal/logger/ log/slog, JSON always
internal/middleware/ request id, logging, metrics, recovery, timeout,
body cap, security headers, CSRF
internal/render/ template compilation and execution
internal/server/ lifecycle, route table, static file serving
internal/telemetry/ Sentry and Prometheus
templates/ go:embed HTML: base document, pages, partials
static/ go:embed CSS and JS
script/ the entrypoints
```
## Conventions
- **Dependency injection through `fx`.** A constructor takes an
`fx.Lifecycle` and a `Params` struct; it never reaches for a
package-level singleton. Returning an error from a constructor aborts
startup, which is how configuration failures become refusals to start.
- **Logging is `log/slog` only.** Never `zerolog`, never `logrus`. JSON
output in every environment. `net/http`'s own error output is routed
through the same handler so that one process emits one format.
- **Errors wrap with `%w`** and are compared with `errors.Is`. Sentinel
errors are package-level `var`s named `errThing`.
- **A handler never shows an error's text to a client.** The client gets
a chosen message; the error goes to the log and to Sentry, joined by
the request id that is also in the response header.
- **Route labels in metrics are chi route PATTERNS, never paths.**
Labelling by path makes every distinct URL a new time series, and a
crawler then owns the process's memory.
- **Tests exercise behaviour, not implementation.** The server tests
start a real listener against a real database and speak HTTP to it.
Prefer that over asserting on internals.
- **Comments explain why, and traps.** Delete the history, the
reasoning-out-loud and the self-justification; keep what a reader
needs in order not to fall in.
## What to change when seeding a new project
1. Clone, then run `script/rename <newname> [module-path]` on a clean
tree. It rewrites the placeholder name and module path everywhere,
renames `cmd/simplexcalc`, and deletes itself. Review the diff.
2. Delete the example domain:
`internal/database/schema/001_widgets.sql`,
`internal/database/model_widget.go`, the widget parts of
`internal/handlers/index.go` and `templates/index.html`, and their
tests. Start your own schema at `001`.
3. Rewrite `README.md` for the new project. It currently describes the
template, which the new project is not.
4. Rewrite this file's "What this repository is" section, and delete
this list. Keep the iron rules, the entrypoints, the branching model
and the conventions — they are why the template exists.
5. Reset `docs/TODO.md`: keep the Workflow section, replace the rest.
6. Decide on `CSRF_KEY` and the metrics credentials before the first
deployment. See `README.md` for what each one does when unset.