The template's files at a77fd30, without its history or LICENSE, after script/rename simplexcalc. Model: opus-5-5
179 lines
8.2 KiB
Markdown
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.
|