A SimpleX Chat bot that answers arithmetic (closes #1)
check / check (push) Successful in 54s

Remove the template's HTTP service, database and fx wiring. Add exact
arithmetic on go/parser and go/constant, a client that runs simplex-chat
as a child process and drives its WebSocket API, and the bot, which keeps
an auto-accepting address and replies to each message. The image adds the
checksum-pinned simplex-chat v7.0.2 on Ubuntu 22.04.

Model: opus-5-5
This commit is contained in:
clawbot
2026-09-26 22:01:51 +00:00
parent f8ce8cef83
commit 16649e0f2f
66 changed files with 2119 additions and 5059 deletions
+41 -67
View File
@@ -1,6 +1,6 @@
---
title: Agent Guidance
last_modified: 2026-08-22
last_modified: 2026-09-26
---
This file is the single source of guidance for any automated agent
@@ -15,18 +15,20 @@ 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.
prose.
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.
A SimpleX Chat bot that answers arithmetic. The Go program starts the
SimpleX Chat command-line client (`simplex-chat`) as a child process,
drives it over its local WebSocket API, gives the bot a long-term
contact address that accepts every contact request, and replies to each
text message with the value of the arithmetic in it. Both ship in one
container image; the SimpleX database lives on a volume. It was seeded
from `go-template-repo` and keeps that template's gates and conventions.
## Iron rules
@@ -56,17 +58,15 @@ regardless of what else it achieves.
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.
file and edit it, even when there are many similar edits.
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.
`@sha256:`, Actions by commit SHA, Go modules by `go.sum`, the
`simplex-chat` download by the checksum on its `ADD`. 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.**
@@ -104,9 +104,11 @@ 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.
- `next` integrates work toward the next release: branch from `next`,
merge back to `next`. Before 1.0.0, green work may also land directly
on `main`.
- Merge directly when the target 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
@@ -116,63 +118,35 @@ Do not weaken them.
## 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
cmd/simplexcalc/ cobra command tree; main(), run and version
internal/bot/ startup, address setup, and the reply to a message
internal/calc/ the arithmetic: go/parser and go/constant
internal/config/ viper-backed configuration; the abort-on-garbage rule
internal/logger/ log/slog, JSON always
internal/simplex/ the simplex-chat child process and its WebSocket API
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.
output in every environment. The chat client's own output is logged
line by line 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.
errors are package-level `var`s.
- **A reply never shows an error's text to a contact.** The contact gets
a chosen sentence; anything unexpected goes to the log.
- **Unknown chat client records are ignored, never fatal.** The SimpleX
API adds fields and record types between releases, and its
documentation requires clients to skip what they do not know. Decode
only the fields that are read.
- **A failure of the chat client or the connection to it ends the
process with an error**, so the container's restart policy restarts
both together. There is no reconnect loop.
- **Tests exercise behaviour, not implementation.** The client tests
speak the real WebSocket protocol to a stand-in chat client. 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.