Skip to content

Commit abba0ab

Browse files
authored
Merge branch 'main' into chore/consumer-skills
2 parents 241adaf + 29aae10 commit abba0ab

43 files changed

Lines changed: 1544 additions & 521 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/cosmos-go/SKILL.md

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
---
2+
name: cosmos-go
3+
description: >
4+
Project-specific Go skill for the Cosmos monorepo. Must use when writing,
5+
editing, planning, or reviewing any Go (.go) files in this workspace.
6+
Covers code style, naming, documentation, error handling, immutability
7+
patterns, testing, and module architecture. Use this skill instead of the
8+
global Go skill whenever working inside the Cosmos codebase.
9+
---
10+
11+
# Cosmos Go
12+
13+
Go conventions and patterns for the Cosmos HTTP framework monorepo.
14+
Go 1.25+ workspace with four modules: contract, router, problem, framework.
15+
16+
## Project Structure
17+
18+
```
19+
cosmos/
20+
├── go.work # Workspace: use + replace directives
21+
├── contract/ # Interfaces (zero deps) - the foundation
22+
├── router/ # Generic HTTP router (zero deps)
23+
├── problem/ # RFC 9457 problem details (zero deps)
24+
│ └── internal/ # Accept header parsing (not exported)
25+
└── framework/ # Full framework (depends on all above)
26+
├── cache/ # contract.Cache implementations
27+
├── crypto/ # contract.Encrypter implementations
28+
├── database/ # contract.Database implementation
29+
├── event/ # contract.EventBus implementations
30+
├── hash/ # contract.Hasher implementations
31+
├── middleware/ # Ready-made middleware
32+
└── session/ # Session management
33+
```
34+
35+
**Dependency direction:** contract (zero deps) -> router, problem (standalone) -> framework (uses all).
36+
37+
Always run tests from workspace root: `go test ./...`
38+
Test a single module: `go test ./router/...`
39+
40+
## Formatting
41+
42+
Standard `gofmt`. No exceptions.
43+
44+
### Import Groups
45+
46+
Separate groups with a blank line, alphabetical within each group:
47+
48+
```go
49+
import (
50+
"encoding/json"
51+
"fmt"
52+
"net/http"
53+
54+
"github.com/studiolambda/cosmos/problem/internal"
55+
56+
"github.com/stretchr/testify/assert"
57+
)
58+
```
59+
60+
- Group 1: Standard library
61+
- Group 2: Internal cosmos modules
62+
- Group 3: External dependencies
63+
64+
When only stdlib imports exist, use a single group.
65+
66+
## Naming
67+
68+
### Receiver Names
69+
70+
Use **full-word receiver names** matching the type name in lowercase. This is a firm convention.
71+
72+
```go
73+
// Correct
74+
func (problem Problem) With(key string, value any) Problem { ... }
75+
func (router *Router[H]) Group(pattern string, fn func(*Router[H])) { ... }
76+
func (accept Accept) Quality(media string) float64 { ... }
77+
78+
// Wrong
79+
func (p Problem) With(key string, value any) Problem { ... }
80+
func (r *Router[H]) Group(pattern string, fn func(*Router[H])) { ... }
81+
```
82+
83+
Rationale: Short names create ambiguity in methods >10 lines and make grep/search
84+
less useful. `r` could be a Router, Request, Reader, or Redis client. Full words
85+
eliminate that class of confusion entirely.
86+
87+
### General Naming
88+
89+
- Descriptive names over abbreviations: `subrouter` not `sr`, `handler` not `h` (exception: named return values like `h H, ok bool` are fine).
90+
- PascalCase for exported, camelCase for unexported.
91+
- Constructor: `New` for single-type packages, `NewTypeName` when multiple types exist.
92+
- Symmetric method pairs: `With`/`Without`, `WithError`/`WithoutError`.
93+
- Past participle for methods returning a modified copy: `Defaulted`, `Grouped`.
94+
95+
## Functions
96+
97+
- **Small and focused.** Target <30 lines body, <5 cyclomatic complexity.
98+
- **Single purpose.** If you need a comment saying "now do the second thing," extract a function.
99+
- **Early returns over nesting.** Use guard clauses for preconditions, errors, and edge cases.
100+
- **Zero `else` after early returns.** The happy path flows downward without indentation.
101+
102+
```go
103+
// Correct: early return, no else
104+
func (problem Problem) With(key string, value any) Problem {
105+
if problem.additional == nil {
106+
problem.additional = map[string]any{key: value}
107+
108+
return problem
109+
}
110+
111+
problem.additional = maps.Clone(problem.additional)
112+
problem.additional[key] = value
113+
114+
return problem
115+
}
116+
117+
// Wrong: unnecessary else
118+
func (problem Problem) With(key string, value any) Problem {
119+
if problem.additional == nil {
120+
problem.additional = map[string]any{key: value}
121+
} else {
122+
problem.additional = maps.Clone(problem.additional)
123+
problem.additional[key] = value
124+
}
125+
126+
return problem
127+
}
128+
```
129+
130+
## Blank Lines
131+
132+
Use blank lines to separate logical blocks within functions:
133+
134+
- After variable declarations before logic.
135+
- Between guard clauses / early-return blocks.
136+
- Between distinct logical steps.
137+
- After `struct {` opening when fields have doc-comment blocks.
138+
139+
Do not use blank lines between tightly coupled one-liners (e.g. sequential `delete()` calls).
140+
141+
## Documentation
142+
143+
### Doc Comments
144+
145+
All exported symbols get doc comments. Start with the symbol name:
146+
147+
```go
148+
// ParseAccept creates a new [Accept] based on a
149+
// [http.Request] by parsing its headers.
150+
func ParseAccept(request *http.Request) Accept {
151+
```
152+
153+
Unexported functions with non-obvious logic also get doc comments:
154+
155+
```go
156+
// stackTrace creates a stack trace of all the errors found
157+
// that have been either Joined or Wrapped using [errors.Join]
158+
// or [fmt.Errorf] with `%w` directive.
159+
func stackTrace(err error) []error {
160+
```
161+
162+
### Godoc Links
163+
164+
Use bracket syntax to cross-reference types and functions:
165+
166+
```go
167+
// With adds a new additional value to the given key.
168+
// Use [Problem.Without] to remove values.
169+
// See [NewProblem] for creating problems from errors.
170+
```
171+
172+
### Struct Field Comments
173+
174+
For structs with >3 fields, document each field with a comment block above it.
175+
For structs with 1-3 fields, inline comments or a single doc comment on the type suffice.
176+
177+
### Sentence Completeness
178+
179+
Doc comments must be complete sentences. End with a period. Do not leave
180+
truncated sentences.
181+
182+
## Error Handling
183+
184+
- Early-return on error: `if err != nil { return err }`.
185+
- Use `errors.Is()` and `errors.As()` for error inspection, never type assertions.
186+
- When intentionally discarding an error, use explicit `_ =` and comment the reason:
187+
188+
```go
189+
// Error is intentionally discarded: headers are already written,
190+
// nothing useful can be done if encoding fails.
191+
_ = json.NewEncoder(w).Encode(problem)
192+
```
193+
194+
- Never silently swallow errors without a documented reason.
195+
- Error strings are lowercase, no punctuation at the end.
196+
197+
## Design Patterns
198+
199+
### Immutability (Copy-on-Write)
200+
201+
For types holding mutable internal state (maps, slices), prefer value receivers
202+
and methods that return modified copies rather than mutating in place.
203+
204+
Clone mutable fields before modification to prevent aliasing:
205+
206+
```go
207+
func (problem Problem) With(key string, value any) Problem {
208+
problem.additional = maps.Clone(problem.additional)
209+
problem.additional[key] = value
210+
211+
return problem
212+
}
213+
```
214+
215+
This enables safe derivation from package-level variables: `ErrNotFound.With("id", 42)`.
216+
217+
Use `maps.Clone`, `slices.Clone` from the standard library. Never hand-roll copy loops.
218+
219+
When mutation is the natural API (like `Router.Use()`), document the contrast
220+
with the immutable alternative clearly.
221+
222+
### Encapsulation
223+
224+
Unexported struct fields, exported methods. Implementation details go in
225+
`internal/` packages when they serve a single parent package.
226+
227+
### Interface Satisfaction
228+
229+
Satisfy interfaces implicitly (duck typing). Do not add explicit compile-time checks
230+
like `var _ Interface = Type{}` unless the satisfaction is non-obvious or critical.
231+
232+
Common interfaces to implement: `error`, `http.Handler`, `json.Marshaler`/`json.Unmarshaler`.
233+
234+
### Generics
235+
236+
Use type constraints meaningfully. Prefer named constraints over inline unions:
237+
238+
```go
239+
type Middleware[H http.Handler] = func(H) H // type alias preserves assignability
240+
type Router[H http.Handler] struct { ... } // constrained to http.Handler
241+
```
242+
243+
### Modern Standard Library
244+
245+
Prefer modern Go stdlib functions:
246+
247+
- `strings.CutPrefix`, `strings.CutSuffix` over manual `HasPrefix` + `TrimPrefix` combinations.
248+
- `strings.SplitSeq` (iterator-based) over `strings.Split` when not materializing the full slice.
249+
- `slices.SortFunc`, `slices.Clone` over hand-rolled sort/copy.
250+
- `maps.Clone`, `maps.Copy` over manual map iteration.
251+
- `path.Join` for URL path construction.
252+
253+
## Testing
254+
255+
See [references/testing-guide.md](references/testing-guide.md) for complete patterns and examples.
256+
257+
Summary:
258+
259+
- **Atomic test functions.** One concern per test function. No table-driven tests.
260+
- **Parallel by default.** Call `t.Parallel()` in every test unless shared state prevents it.
261+
- **Black-box testing.** Use `package foo_test` (external test package).
262+
- **Testify assertions.** Use `require` for fatal checks, `assert` for non-fatal.
263+
- **Descriptive names.** `TestProblemWithAddsAdditionalValue`, `TestRouterHasMatchesTrailingSlash`.
264+
- **httptest for HTTP.** `httptest.NewRequest` + `httptest.NewRecorder` or `Router.Record()`.
265+
266+
## When to Load References
267+
268+
- **Writing or reviewing Go source code:** See [references/code-patterns.md](references/code-patterns.md) for annotated examples of all major patterns (immutability, middleware, constructors, content negotiation, recursive resolution).
269+
- **Writing or reviewing tests:** See [references/testing-guide.md](references/testing-guide.md) for test structure, assertion patterns, HTTP testing, and anti-patterns.

0 commit comments

Comments
 (0)