[autoresearch] baseline: system prompt quality tests (5/5 passing)

This commit is contained in:
evandrodevbr committed 2026-04-11 05:27:36 -03:00
1 parent 05cc036f6d
commit 60b0101717
142 files changed
+18589 -9947

No files matched your search

+315
View File
@@ -0,0 +1,315 @@
---
trigger: model_decision
description: When writing Go code, reviewing Go idioms, or working on a Go backend project
---
## Go Idioms and Patterns
### Core Philosophy
Go favors simplicity, explicitness, and readability. The language is intentionally small — resist the urge to import patterns from other languages. If it looks boring and obvious, it's probably idiomatic Go.
> **Scope:** This file covers Go-specific *coding idioms*. For file layout, see `project-structure-go-backend.md`. For test naming conventions, see `testing-strategy.md`. For logging library choice, see `logging-and-observability-principles.md`.
---
### Error Handling
1. **Always return errors — never panic in library or business code**
- `panic` is reserved for truly unrecoverable states (programmer errors, nil dereference)
- Use `recover` only at top-level goroutine boundaries (middleware, server startup)
2. **Wrap errors with context using `%w`**
```go
// ✅ Preserves the error chain for errors.Is / errors.As
return fmt.Errorf("creating task for user %s: %w", userID, err)
// ❌ Loses the error chain
return fmt.Errorf("creating task: %v", err)
```
3. **Use sentinel errors for expected branch conditions**
```go
// Define in errors.go
var ErrNotFound = errors.New("not found")
var ErrUnauthorized = errors.New("unauthorized")
// Caller checks with errors.Is
if errors.Is(err, ErrNotFound) {
// handle
}
```
4. **Use typed errors for rich domain errors**
```go
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Caller unwraps with errors.As
var ve *ValidationError
if errors.As(err, &ve) {
// access ve.Field, ve.Message
}
```
5. **Handle errors at the right level** — propagate upward until you have enough context to act on them; don't swallow or re-wrap the same error twice.
---
### Interfaces
1. **Keep interfaces small — one or two methods is ideal**
```go
// ✅ Focused, composable
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
// ❌ Monolithic
type FileManager interface {
Read(...); Write(...); Delete(...); List(...); Stat(...)
}
```
2. **"Accept interfaces, return structs"**
- Function parameters: accept interfaces for flexibility and testability
- Return values: return concrete structs so callers can access all methods
3. **Define interfaces where they are *used*, not where they are *implemented***
```go
// ✅ Defined in the consumer package (task feature)
// task/storage.go
type Storage interface {
GetByID(ctx context.Context, id string) (*Task, error)
}
// postgres.go implements Storage — it does NOT define it
```
4. **Implicit satisfaction is a feature — don't use embedding to "implement" interfaces**
- Any type with the right method set satisfies an interface automatically
- No `implements` keyword needed or wanted
---
### Goroutines and Channels
> For general concurrency principles (race conditions, deadlocks, message passing), see `concurrency-and-threading-principles.md`. This section covers Go-specific mechanics.
1. **Always pass `context.Context` as the first parameter**
```go
// ✅
func (s *Service) GetTask(ctx context.Context, id string) (*Task, error)
// ❌ — no way to cancel or propagate deadlines
func (s *Service) GetTask(id string) (*Task, error)
```
2. **Never start a goroutine without knowing how it will stop**
```go
// ✅ Goroutine is bounded by context cancellation
go func() {
for {
select {
case <-ctx.Done():
return
case item := <-ch:
process(item)
}
}
}()
```
3. **Use `errgroup` for concurrent fan-out with error collection**
```go
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return fetchUsers(ctx) })
g.Go(func() error { return fetchOrders(ctx) })
if err := g.Wait(); err != nil { ... }
```
4. **Prefer channels for ownership transfer; mutexes for shared state**
- Channel: "I'm handing this data to you"
- Mutex: "We're both reading/writing this shared thing"
5. **Close channels from the sender, never the receiver**
---
### Naming Conventions
1. **Receiver names: short, consistent, and the first letter of the type**
```go
func (s *Service) Create(...) {} // ✅
func (svc *Service) Create(...) {} // ❌ — too verbose
func (self *Service) Create(...) {} // ❌ — not Go
```
2. **Package names: short, lowercase, no underscores, no plurals**
```go
package task // ✅
package tasks // ❌ plural
package task_service // ❌ underscore
```
3. **Acronyms follow Go conventions (all caps or all lowercase)**
```go
userID // ✅
userId // ❌
HTTPClient // ✅
HttpClient // ❌
```
4. **Unexported identifiers omit the type name** — if it's private, keep it terse
5. **Don't stutter** — `task.Task` is fine; `task.TaskService` is not
---
### Idiomatic Patterns
1. **Functional options for optional configuration**
```go
type Option func(*Service)
func WithTimeout(d time.Duration) Option {
return func(s *Service) { s.timeout = d }
}
func NewService(store Storage, opts ...Option) *Service {
s := &Service{store: store, timeout: 30 * time.Second}
for _, o := range opts { o(s) }
return s
}
```
2. **`defer` for cleanup — always use error-checked closures**
Every deferred cleanup call that returns an error MUST check and log the error.
Never use bare `defer X.Close()` — the discarded error hides resource leak failures.
```go
// ❌ NEVER: Error silently discarded
defer rows.Close()
// ✅ ALWAYS: Error-checked closure with structured logging
rows, err := db.QueryContext(ctx, query)
if err != nil { return fmt.Errorf("querying tasks: %w", err) }
defer func() {
if err := rows.Close(); err != nil {
slog.Warn("failed to close rows", "error", err, "operation", "ListTasks")
}
}()
```
**Transaction rollback:**
```go
// ❌ NEVER
defer tx.Rollback()
// ✅ ALWAYS: Guard against sql.ErrTxDone (already committed)
defer func() {
if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) {
slog.Error("failed to rollback transaction", "error", err, "operation", "CreateOrder")
}
}()
```
**HTTP response body:**
```go
// ❌ NEVER
defer resp.Body.Close()
// ✅ ALWAYS: Drain then close (prevents connection reuse issues)
defer func() {
if _, err := io.Copy(io.Discard, resp.Body); err != nil {
slog.Warn("failed to drain response body", "error", err)
}
if err := resp.Body.Close(); err != nil {
slog.Warn("failed to close response body", "error", err)
}
}()
```
3. **Avoid `init()` functions** — they run implicitly and make testing harder; prefer explicit initialization in `main` or constructors
4. **Prefer `struct` embedding over inheritance for code reuse**, but only when the embedded type truly represents an "is-a" relationship
5. **Use named return values only for documentation or `defer`-based cleanup** — never rely on naked returns in non-trivial functions
---
### Testing
> Test file naming and pyramid proportions are defined in `testing-strategy.md`. This section covers Go-specific tooling only.
1. **Table-driven tests are the default pattern**
```go
func TestCalculateDiscount(t *testing.T) {
tests := []struct {
name string
input float64
expected float64
wantErr bool
}{
{"zero items", 0, 0, false},
{"negative input", -1, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := calculateDiscount(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, got)
})
}
}
```
2. **Use `testify` for assertions** (`require` for fatal assertions, `assert` for non-fatal)
3. **Run with the race detector in CI** — `go test -race ./...`
4. **Use `httptest.NewRecorder()` for HTTP handler tests** — no live server needed
5. **Test behaviour, not implementation** — assert on outputs and side effects, not internal field values
---
### Formatting and Static Analysis
All of the following **must pass with zero warnings/errors** before any commit. See `code-completion-mandate.md` for the full checklist.
| Tool | Purpose | Command |
| ----------------------- | ------------------------ | -------------------- |
| `gofumpt` / `goimports` | Canonical formatting | `gofumpt -l -w .` |
| `go vet` | Correctness checks | `go vet ./...` |
| `staticcheck` | Advanced static analysis | `staticcheck ./...` |
| `gosec` | Security scanning | `gosec -quiet ./...` |
| `golangci-lint` | Aggregated linter (CI) | `golangci-lint run` |
| `govulncheck` | Dependency CVE scanning | `govulncheck ./...` |
- Never disable a linter without a comment explaining why
- **`//nolint:errcheck` is NEVER acceptable.** If a function returns an error, handle it — even in `defer`. Use an error-checked closure (see § Idiomatic Patterns above). This is the #1 source of audit findings.
- Other `//nolint:` directives require a `// NOLINT:` rationale comment AND must be approved during code review
- Fast iteration during development: `go vet ./...` type-checks and catches correctness issues without producing binaries (analogous to `cargo check`) — reserve `golangci-lint` for pre-commit
> **Logging:** Never use `fmt.Println` or `log.Printf` in production service code — these produce unstructured output. Use `log/slog` (stdlib, Go 1.21+) or the project's chosen adapter. See `logging-and-observability-principles.md` for the required library and patterns.
---
### Related Principles
- Code Idioms and Conventions @code-idioms-and-conventions.md
- Project Structure — Go Backend @project-structure-go-backend.md
- Testing Strategy @testing-strategy.md
- Error Handling Principles @error-handling-principles.md
- Concurrency and Threading Principles @concurrency-and-threading-principles.md
- Logging and Observability Principles @logging-and-observability-principles.md
- Dependency Management Principles @dependency-management-principles.md