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:
@@ -0,0 +1,178 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user