🌬️ Breeze docs

Repository conventions

The rules this repository follows, and why β€” an audit of the codebase once found seven kinds of drift that all traced back to the same cause: nobody had written the rule down, so each new feature picked a reasonable answer and the answers disagreed. Every rule below carries its reason, because a rule without one is a rule the next contributor is entitled to ignore.

Where code goes

Repo root β€” package breeze. The HTTP core and nothing else: router, Context, request/response, the Breeze server, worker pool, WebSocket engine, template engine, i18n, the error type, Auto-MCP route registration. A file belongs at root only if it is part of serving an HTTP request or is required to construct the server β€” everything else is a subpackage, even when that means a package with three files. The root package is what import "github.com/nelthaarion/breeze/v2" gets, and every symbol in it is in every user's namespace: adding to it costs everyone, adding a subpackage costs only its importers.

Subdirectories of root β€” public subsystems. One directory per subsystem, importable directly, and each can be left out of an application entirely: binding, client, dashboard, diag, events, fleet, mcp, middlewares, migrate, observability, rpc, scalar, video, workflow. Nesting one level deeper is allowed only when the nested packages are alternatives, not layers β€” fleet/transport/{httptransport,wstransport,eventtransport,gnettransport} are four implementations of one interface, chosen by configuration. Nesting to express "part of" is what files in a package are for instead.

internal/ β€” machinery with no public API. internal/generator (the CLI's implementation), internal/mcp (the MCP server's implementation), internal/mcpcmd (flag parsing shared by two binaries). Go enforces that nothing outside the module can import them β€” exactly the point, since these packages change shape whenever a tool grows a feature, and no user should be able to depend on that shape. The public faΓ§ade for anything in internal/ is a thin wrapper: mcp/ exports ServeInProcess over internal/mcp.

cmd/ β€” one directory per binary, never a loose .go file directly in cmd/:

PatternMeaningCurrent members
cmd/<tool>a shipped binary a user installsbreeze, breeze-mcp
cmd/<subsystem>-examplea runnable demonstration of one subsystemdashboard-example, workflow-example, video-example, fleet-example, templates-example, events-example, automcp-example
cmd/<name> (neither)a deployable that is not the CLIfleet-aggregator

The -example suffix is load-bearing: it's how a reader scanning cmd/ tells "this is how you use the framework" from "this is the framework".

Nested modules. A package may carry its own go.mod only to keep a heavy third-party dependency out of the framework's dependency graph. Currently one does β€” fleet/transport/eventtransport/backends/kafka, so kafka-go is opt-in. The cost: go build ./... and CI don't see it, so a nested module must be built explicitly by CI or it rots invisibly.

File naming within a package

FileHoldsRequired when
doc.gothe package doc comment, nothing elsepublic and more than a trivial API
config.gothe Config struct, its defaults, its validationthe package has a Config
types.gowire/DTO types with no behaviourthere are enough to make a home worthwhile
errors.gosentinel errors and error typesthe package exports either
diag.gothe diag probe and its registrationthe package registers a probe
<concern>.goone concernalways

Two file-name styles coexist and both stay: internal/generator and internal/mcp use snake_case with a shared prefix grouping related files (tools_*.go, generate_*.go) because both packages have 40+ files and the prefix is what makes ls readable; everything else uses single flat words (dispatch.go, tracer.go) because under 20 files a prefix is noise. Do not mix within one package.

Tests: <file>_test.go next to the code it tests; one bench_test.go per package, not zzperf_bench_test.go or a per-subsystem name, so go test -bench is predictable β€” though Benchmark**ZZ**… function names stay, since every recorded baseline in CHANGELOG.md is keyed to them, and renaming would make a benchstat comparison report "no benchmarks" rather than a regression. Fixtures live inline with t.TempDir(); testdata/ is only for assets a Go test cannot construct.

Known deviation, kept deliberately: middlewares/ declares package middleware (singular); every import site aliases it. Renaming either breaks every existing user's import line β€” it stays.

Naming inside code

  • Receivers: one name per type, everywhere β€” *Breeze is s, *Router is r, *Collector is c, *Tracer is t.
  • Config variables: cfg β€” not config (shadows the package name in internal/generator), not c (collides with the collector and the context receiver).
  • Abbreviations: ctx, cfg, req, res, err, buf, n, i. Spell out anything not on that list.
  • Sentinel errors: ErrFoo. One exception, events.Stop β€” exported API a listener returns, and its call sites read as English: return events.Stop.
  • Never shadow a package name with a local β€” config, template, url, path, time as variable names are all bugs waiting to happen.

Error handling

Wrap with %w when returning an error caused by another error:

if err := os.WriteFile(path, body, 0o644); err != nil {
	return fmt.Errorf("write %s: %w", path, err)
}

errors.Is and errors.As are the only way a caller can react to a specific failure, and both walk the %w chain β€” fmt.Errorf("...: %v", err) produces the same string and silently deletes that ability.

Use a bare sentinel when the error is the whole fact: var ErrBusClosed = errors.New("events: bus is closed").

Don't wrap when the error text is the product, not a diagnosis of a cause β€” binding's validation errors are shown to a client as an RFC 9457 body; there's no underlying error to unwrap. binding and middlewares are documented as such deliberately, so the next contributor doesn't "fix" them into inconsistency with themselves.

Prefix with the package name in errors that escape the package: "events: bus is closed", "video: malformed range header".

Never swallow. If an error genuinely cannot be handled, assign it to _ with a comment saying why:

_ = conn.Close() // response already written; a close error changes nothing

Example projects

Every example is cmd/<subsystem>-example/:

cmd/<subsystem>-example/
β”œβ”€β”€ main.go             # package main, with a package doc comment
β”œβ”€β”€ README.md           # required
β”œβ”€β”€ <name>_test.go      # if the example demonstrates a claim, assert it
β”œβ”€β”€ docker-compose.yml  # only if it needs more than one process
└── views/ etc.         # only if it needs assets

README.md has three sections, in order: what this demonstrates (subsystem features exercised, which framework APIs to look at); how to run it (the exact, copy-pasteable command); what to look for (the URLs, the curl calls, what the output should show β€” an example whose output nobody described is an example nobody can tell is broken). cmd/fleet-example/README.md is the reference.

An example that is not compiled is an example that rots β€” all of cmd/... is covered by go build ./... and go vet ./... in CI. An example that makes a claim should test it β€” compiling proves the API still exists, not that the behaviour the README describes still holds. cmd/automcp-example is the reference: four claims, four separately named tests, so a failure says which guarantee broke.

Documentation

Every public subsystem has a doc, reachable from docs/README.md β€” the index is the contract: a subsystem not in it is undocumented regardless of what files exist. docs/<topic>.md for anything spanning packages or describing an operational workflow; <package>/README.md for a single package's guide; <package>/doc.go for the package comment, always, in addition to the above. Minimum content: what it does and what problem it solves that the standard library doesn't; a quick-start example that compiles; a configuration reference β€” every field, its default, what it affects; how it's exposed through the CLI and MCP, or an explicit statement that it isn't.

Linting

.golangci.yml at the repo root, run in CI alongside go vet ./... and go test ./.... Enabled beyond the defaults: staticcheck and unused β€” the two that found every drift finding that motivated this document, all of which had sat in the tree for months while CI reported green.

Suppressions carry a reason and live at the site, not in the config: //lint:ignore <Check> <why>, or //nolint:staticcheck // <why>. A bare nolint is not acceptable β€” the point of a suppression is to record a decision, and a decision recorded in a config file is attached to a path rather than to the line that made it. .golangci.yml currently has no exclusions at all, which is the target state, not an oversight.

gofmt -l .
go vet ./...
golangci-lint run
staticcheck ./...   # the bare binary does not read .golangci.yml
go test ./...

When these rules and the code disagree

The code is wrong, unless the deviation is listed as deliberate β€” add to that list in the same change that introduces the deviation, with the reason. A deviation nobody wrote down is indistinguishable from a mistake, which is how this repository accumulated the drift that produced these rules in the first place.

DeviationReason
middlewares/ declares package middlewarerenaming either breaks every existing user's import line
events.Stop is not ErrStopexported API a listener returns; return events.Stop reads as English where ErrStop would claim something failed
the kafka transport is a nested modulepulls a heavyweight client that must not appear in the root go.sum for users who don't want it
breeze.go:OnTraffic and video/handler.go:serve stay longmeasured hot paths β€” splitting risks the inlining and allocation behaviour the benchmark numbers verify
Benchmark**ZZ**… function names keep the zz prefixevery recorded baseline is keyed to these identifiers
env/envInt duplicated across example main packagesthey're separate package main programs by design; sharing them would mean a library package existing only for example glue

Generated documentation for the Breeze framework Β· built with SvelteKit, fully static.