check / check (push) Successful in 1m1s
The bot now serves an HTTP API on PORT (default 8080) beside the chat client. Every request needs the credential read at startup from the file named by API_TOKEN_FILE, sent as a bearer token; without one, every request is refused. Responses carry the security headers, bodies are capped at 64 KiB and each request's work at 10 seconds. GET /api/v1/chats lists the bot's contacts from the chat client's /_contacts command, ordered by id, and marks the contacts who deleted their chat with the bot, which the chat client keeps listing. bot.Run starts the API after set-up and stops it within 5 seconds; a listener failure ends the bot as a chat client failure does. Model: opus-5-5
155 lines
6.9 KiB
Markdown
155 lines
6.9 KiB
Markdown
---
|
|
title: Agent Guidance
|
|
last_modified: 2026-09-29
|
|
---
|
|
|
|
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.
|
|
|
|
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 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
|
|
|
|
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.
|
|
|
|
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`, 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.**
|
|
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.
|
|
- `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
|
|
treated as lost.
|
|
- Never force-push, and never rewrite published history.
|
|
|
|
## Layout
|
|
|
|
```
|
|
cmd/simplexcalc/ main(), a single call into internal/cli
|
|
internal/api/ the HTTP API: its credential, headers and endpoints
|
|
internal/bot/ startup, address setup, and the reply to a message
|
|
internal/calc/ the arithmetic: go/parser and go/constant
|
|
internal/cli/ cobra command tree: run and version
|
|
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
|
|
|
|
- **Logging is `log/slog` only.** Never `zerolog`, never `logrus`. JSON
|
|
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.
|
|
- **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.
|