]> git.giacomolli.uk - onehouse/blob - .agents/skills/go-code/SKILL.md
Build typed MQTT publications
[onehouse] / .agents / skills / go-code / SKILL.md
1 ---
2 name: go-code
3 description: Go style rules. Use before you write or review Go code.
4 ---
5
6 # Go code
7
8 ## Packages and files
9
10 - Put commands in `cmd/` and shared packages at the repository root
11 beside `cmd/`. Do not add an `internal` package.
12 - Give each package one clear responsibility and a small public API.
13 Keep implementation types and helpers private.
14 - Keep composition and dispatch in the main service type. Put distinct
15 work in focused private types or standalone functions with explicit
16 inputs. Do not spread the methods of the service type across files to
17 make a file shorter.
18 - Keep `main.go` limited to the entry point. Name other files for their
19 function. Do not create `main_test.go`.
20 - A file carries its own context. Keep each struct and all of its
21 methods in the file that declares the struct. Give a separate
22 responsibility its own type.
23 - Name a file that holds a struct after that struct, in snake_case. Use
24 the platform suffix for a platform-specific file. When platforms share
25 the struct, put the platform details in standalone helpers.
26 - Write all the letters of an initialism in one case. `revive` checks
27 common initialisms.
28 - Put entry points first, then constructors with a `new` or `New`
29 prefix, then each struct's methods as a group, then standalone utility
30 functions. Do not put a utility function between the methods of one
31 type.
32 - In each group, order functions by first use in the file.
33 - Group related constants in one `const (...)` block and related
34 variables in one `var (...)` block. Put an empty line between entries
35 that have comments.
36
37 ## Entry point and context
38
39 - `main` creates the SIGINT/SIGTERM signal context and defers its
40 cancellation. Pass that context through operations and let cleanup
41 finish before `main` exits. Do not use `log.Fatal`.
42 - Use `ctx` for a context and `cancel` for its cancellation function.
43 Call `defer cancel()` immediately after you create the context.
44 - Reuse `ctx` when a child context replaces its parent. Use a different
45 name only when both contexts stay in use. Return `ctx.Err()` directly.
46 - Derive each context from the context of the caller. When work must
47 finish even after the caller is cancelled, such as a final write, use
48 `context.WithoutCancel` of the caller's context. Use
49 `context.Background()` only where no caller context exists: in `main`,
50 and in methods that follow `io.Closer`.
51 - Name a duration that limits one operation after that operation, and
52 end the name with `Timeout`, so that the name says what it limits
53 without its comment. Define it in the file that applies it, not in the
54 file of another type.
55
56 ## Errors
57
58 - Handle every returned error. Do not discard an error with the blank
59 identifier. `errcheck` checks this rule.
60 - Reuse an existing `err` for later operations in the same scope. Use a
61 different variable only to keep or join errors.
62 - Use `errors.Is` and `errors.As` to examine errors. Do not compare an
63 error directly with a sentinel, because errors can be wrapped.
64 `errorlint` checks comparisons and type assertions on errors.
65 - Handle a documented sentinel error explicitly when it does not show a
66 failure.
67 - When a function has more than one error path, wrap each returned error
68 with the context of its operation. A function with one error path can
69 return the error directly. `wrapcheck` checks package and interface
70 calls, with the exceptions in `.golangci.yml`. Review local calls and
71 the number of error paths by hand.
72 - When deferred cleanup can fail, use a named `err` result and join the
73 cleanup error with `errors.Join`.
74
75 ## Logs and cancellation
76
77 - Log with `log/slog`. Write the message as a short event in plain
78 words, such as "receive failed". Put names, IDs, counts, and durations
79 in attributes with short keys. Put the cause in `err`. Do not format
80 values into the message.
81 - Use info for normal events, warn for a failure that is retried or
82 loses nothing, and error for a failure that loses data or stops a
83 part.
84 - Cancellation of a service or request context is an expected stop, not
85 a failure. Do not log it as an error, and do not hide all errors only
86 because the context is canceled. A joined error with an independent
87 failure stays a failure.
88 - Use the worker or request context for this check. A device request
89 that reaches its own timeout while its worker is active stays a
90 failure.
91 - A client that cancels an HTTP request does not get a server-error
92 response for that cancellation.
93 - Never log secrets or tokens.
94
95 ## Style
96
97 - Keep function signatures on one line. Write function bodies on more
98 than one line, also for anonymous functions, deferred cleanup, and
99 test callbacks.
100 - Do not use short `if` initializers.
101 - Use a one-letter receiver name for each method.
102 - Use field names in struct literals. Put each field on its own line
103 when you set more than one field. This includes the cases of a
104 table-driven test.
105 - Put an empty line between logical blocks, such as two independent `if`
106 statements. Put an empty line before and after loops, switches,
107 selects, standalone blocks, and multiline declarations, assignments,
108 literals, and calls, when there is an adjacent statement. These
109 boundaries also apply when a loop immediately uses the declaration
110 before it.
111 - Keep an operation with its immediate error or result check. Keep a
112 short declaration with its immediate use, except at the boundaries
113 above. `wsl_v5` and `nlreturn` check part of these rules. Review the
114 remaining logical blocks and immediate-use groups by hand.
115 - Group imports in this order: standard library, external dependencies,
116 project imports. `gci` checks the groups.
117 - Use GNU-style command flags: two dashes for long flags, such as
118 `--cwd`, and one dash only for one-letter flags, such as `-h`.
119 - Prefer the Go standard library. Prefer Go over cgo.
120 - Do not add compile-time interface assertions. Prove compatibility
121 through actual assignments and calls.
122 - Use `// nolint: name` for lint directives.
123
124 ## Dependencies
125
126 - After a change to `go.mod` or `go.sum`, update `vendorHash` in the Nix
127 package.
128
129 ## Tests
130
131 - Add tests for behavior changes. Do not add tests that only repeat
132 implementation details, and do not add a test file without behavior
133 worth a test.
134 - Put tests in a file that matches the tested source file.
135 - Use `testify/require` for assertions. `testifylint` checks the
136 assertion forms.
137 - Name a table-driven case slice `testCases` and its range variable
138 `tc`. A subtest name describes the behavior, not a generic outcome or
139 input.
140 - In a test file, put constants immediately after the imports, then the
141 tests, then supporting types, methods, and helpers. Keep each
142 supporting struct with its methods.
143 - Keep a helper that only one test file uses in that file. Put a helper
144 that more than one test file uses in a file without tests. Name that
145 file for what it holds, and end the name with `_helper_test.go`.
146 - Supply valid required dependencies in tests. Do not make a dependency
147 optional or add a nil fallback only to make a test simpler.