There is no CI: the instance has no act_runner and none is planned, so releases are cut by hand. That makes `make check` the only thing standing between a mistake and the tracker, and it is one command: gofmt, vet, the suite with the cache defeated, `go mod verify`, a vendored build, and `kettle gen skills --check`. The last one is the invariant worth having — the plugin's SKILL.md command reference is generated from the binary's registry, so a flag that changed cannot ship with documentation that recommends the old one. `cli/cmd/release` publishes to Gitea using the same SDK the binary already vendors, which is a pleasing thing to be able to say: nothing third-party handles the artifacts. It is a second binary rather than a `kettle` subcommand on purpose — `kettle`'s command tree is what generates the plugin's skills, so a verb there ships to every operator, and publishing a release is build infrastructure. It is idempotent end to end: an existing release for the tag is reused, an asset of the same name is replaced rather than doubled, and a retried run converges instead of duplicating. `make release` refuses three things, each with its own message: a dirty working tree, a TAG that is not what `git describe` reports, and a tag the remote does not have. A release built from uncommitted code is unreproducible and nobody finds out until they need to reproduce it. `kettle version` reports the stamp, the toolchain and the VCS revision. The default is `dev`, and a hand build says so and means it — a binary out of somebody's working tree is not a release and must not claim to be one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1.9 KiB
AGENTS.md — cmd/kettle
The binary's entry point, and all of it:
func main() { os.Exit(cmd.Main(os.Args[1:])) }
One file, main.go, holding a package comment and that line. The sibling
cmd/release is the module's other binary — build
infrastructure, deliberately not a kettle verb.
Why it is empty
Everything a main usually accumulates — flag parsing, dispatch, usage text,
error formatting, exit codes — is in internal/cmd,
where it is testable. A main package cannot be imported, so anything written
here can only be exercised by running the binary; the command tree is instead a
library with one caller, and its tests run it as a subprocess and call into it
directly where that is cheaper.
The exit status is the only thing this layer owns, and it owns it because
os.Exit skips deferred functions: it has to happen after everything else is
finished, at the outermost frame, and nowhere else in the tree may call it.
The version a build reports is not stamped here either. -ldflags -X names
internal/cmd.Version, because that is where the version command reads it and
where a test can build with the flag and read the answer back — a -X whose symbol
path is one character wrong is silently ignored, and the binary goes on saying dev.
Adding a command
Nothing here changes. A new command is a register(&Command{…}) in an init()
over in internal/cmd — that is the whole point
of a registry.
Keeping this file true
- Scope:
main.go, and the reason it stays this short. - Update it when this package grows a second file or a line that does anything but delegate — which should be treated as a design change and argued for, not documented after the fact.
- Do not describe commands, flags or exit codes here.