---
description: "Go conventions: layout, types, errors, concurrency, HTTP hardening, security, tests, tooling, linter traps. Read before Go edits."
globs:
  - "**/*.go"
---

# Go conventions

Universal, applied to every Go project unless the project's own `AGENTS.md` overrides a line.
Sibling `go-*.md` rules in this directory fire during edits with deeper examples for specific idioms.

## Layout and naming

- File-level `var`, `const`, `type` go at the top, after imports, grouped into one block. NEVER declare `const` or `var` inside a function or method body. The only exception: function/method-level vars like `err`.
- NEVER shadow stdlib package names (`path`, `url`, `json`, `sync`, `time`) with local vars. Use `fp`, `rawURL`, and so on.

## Types, receivers, and boundaries

- Helpers use method form: `func (c *Client) drain(resp *http.Response)`. This is about ownership and maintainability: use method form even when the pointer receiver is not needed.
- Structs are always passed and read as pointers: read the DB into a `*struct` and pass it as a pointer, never a value.
- Service boundaries: domain logic lives in its owning service; other services call a single fire-and-forget entry point. NEVER leak helper structs, types, or wrappers across a service boundary.
- Do NOT clone slices/maps returned by repositories unless you can cite the specific race you are preventing. Default: return references.

## Context and concurrency

- `ctx context.Context` is the first parameter of every service method.
- Select race: after a `select` racing `<-done` against `<-ctx.Done()`, detect the timeout from `ctx.Err() != nil` AFTER the select, whichever arm fired. Both arms can be ready and the pick is random, so a `done` carrying the worker's own `ctx.Err()` can win and silently skip the timeout arm's logging, paging, and cleanup.

## HTTP hardening follows deployment posture

- Server-side, in-network: MAY dial plain `http://` container addresses and omit `httpclient.WithDialGuard()` (service X reaches Y at `https://example.com`; service Z reaches Y at `http://<container>:8080`).
- Operator tool on a laptop: REQUIRES `WithDialGuard()` (a mistyped or poisoned URL otherwise dials loopback or cloud-metadata from a machine holding other credentials) and an https check before `SetBasicAuth`, or the password crosses the wire in cleartext.
- Establish the deployment posture before copying any client construction.

## Security and validation

- Email: `net/mail.ParseAddress`. NEVER a regex or string compare.

### Matrix bot notices are an injection sink

- `format.RenderMarkdown(msg, true, true)` runs with `html.WithUnsafe()` on, so any `b.Notice`/`b.Error` concatenating raw user-derived text (command, host, arg, stored `Message`/`Homeserver`, message body) is a live markdown/HTML-injection and `@room` mass-ping sink.
- One backtick in a user token breaks out of its code span and forges bot-authored text; an `@room` riding in an echoed token fires the room-notify push rule off the bot's own power level, turning any member into a confused deputy that pings every admin.
- Route every user echo through `format.SafeMarkdownCode` (extends the fence past any embedded backtick) and strip `@` first.
- Unsafe: hand-wrapping a token in literal backticks (the user's own backtick closes the span) and a fenced code block (stops the markdown breakout but still carries `@room` into the plaintext the push rule reads).

## Tests

- Test files mirror the source name: `<source>_test.go`. NEVER a generic `unit_test.go`.

## Limits

- Function ≤ 150 lines / 100 statements; complexity ≤ 20 (gocognit) / 40 (gocyclo); line ≤ 120 chars.
- Files: 300 lines soft limit, 500 lines hard limit. When file grows past limit, split into smaller files, named by domain (e.g.: stripe/stripe.go -> stripe/events.go & stripe/charges.go).

## Dependencies and vendoring

- Add a dependency in the same change as its first import: write the `import` line first, then `go mod tidy`. A `go get` before the import exists is silently wiped by tidy (the build passes, nothing needs the dep yet). After any add, grep `go.mod` to confirm the require survived.
- Projects vendor dependencies. After adding one, run `go mod vendor`; build and tests run in vendor mode, so a stale `vendor/modules.txt` fails the build until you re-vendor.

## Tooling and generated files

- `go doc` first for any library lookup: zero network, zero tokens (`go doc package.Symbol`, `go doc -all package`). Beyond that, whatever fits the situation.
- New HTTP endpoints need full swagger annotations.

## Linter gotchas

- `unparam` "result 0 is never used" that appears after adding a test is lying about a dead return. unparam sees the concrete method, but the production caller reaches it via an interface (counted separately), so a test's direct discarding call becomes its only concrete caller. The return is live: assert on it in one test. NEVER `//nolint`.
- `goconst` under diff-scoped lint (`--new-from-rev`/`make lint-from-stage`) flags a bare repeated literal (≥3 uses) only on a *changed* line, so adding one occurrence of an already-crossed literal trips it. Fix: introduce a const and reference it on the changed lines only; the const declaration itself is not flagged.
- gofmt alignment trap: a const longer than the longest key in a map/struct block you edit re-aligns the whole block, turning pre-existing bare literals into changed (flagged) lines, so one edit cascades into three. Name the const no longer than the block's current longest key so neighbor lines stay byte-identical.
