|
| 1 | +# AGENTS.md - OpenFeature Go SDK |
| 2 | + |
| 3 | +This document provides guidelines for agents working on this codebase. |
| 4 | + |
| 5 | +## Build Commands |
| 6 | + |
| 7 | +```bash |
| 8 | +# Run unit tests (short mode, skips slow e2e tests) |
| 9 | +make test |
| 10 | +# Equivalent: go test --short -tags testtools -cover -timeout 1m ./... |
| 11 | + |
| 12 | +# Run end-to-end tests (includes e2e tests with race detection) |
| 13 | +make e2e-test |
| 14 | +# Equivalent: git submodule update --init --recursive && go test -tags testtools -race -cover -timeout 1m ./e2e/... |
| 15 | + |
| 16 | +# Run linter |
| 17 | +make lint |
| 18 | +# Equivalent: golangci-lint run ./... |
| 19 | + |
| 20 | +# Auto-fix linting issues |
| 21 | +make fix |
| 22 | +# Equivalent: golangci-lint run ./... --fix |
| 23 | + |
| 24 | +# Generate mocks (requires mockgen) |
| 25 | +make mockgen |
| 26 | + |
| 27 | +# Generate API documentation |
| 28 | +make docs |
| 29 | +``` |
| 30 | + |
| 31 | +### Running a Single Test |
| 32 | + |
| 33 | +```bash |
| 34 | +# Run specific test in current package |
| 35 | +go test -tags testtools -run TestMyFunction ./... |
| 36 | + |
| 37 | +# Run test in specific file/package |
| 38 | +go test -tags testtools -run TestMyFunction ./path/to/package/... |
| 39 | + |
| 40 | +# Run with verbose output |
| 41 | +go test -tags testtools -v -run TestMyFunction ./... |
| 42 | +``` |
| 43 | + |
| 44 | +## Developer Certificate of Origin (DCO) |
| 45 | + |
| 46 | +This project requires all commits to include a Signed-off-by line to certify adherence to the [Developer Certificate of Origin](https://developercertificate.org/). |
| 47 | + |
| 48 | +### Signing Off Commits |
| 49 | + |
| 50 | +Add the `-s` flag to your commit command: |
| 51 | + |
| 52 | +```bash |
| 53 | +git commit -s -m "your commit message" |
| 54 | +``` |
| 55 | + |
| 56 | +This adds a line like `Signed-off-by: Name <email@example.com>` to your commit message. |
| 57 | + |
| 58 | +### Amending to Add DCO |
| 59 | + |
| 60 | +If you forgot to sign off a commit: |
| 61 | + |
| 62 | +```bash |
| 63 | +git commit --amend -s |
| 64 | +``` |
| 65 | + |
| 66 | +### Verifying DCO Status |
| 67 | + |
| 68 | +Check if your commits are signed: |
| 69 | + |
| 70 | +```bash |
| 71 | +git log --pretty=format:%H,%s | head -5 |
| 72 | +``` |
| 73 | + |
| 74 | +Or use the DCO bot to check PRs (the bot will flag unsigned commits). |
| 75 | + |
| 76 | +## Conventional Commits |
| 77 | + |
| 78 | +This project uses [Conventional Commits](https://www.conventionalcommits.org/) for commit messages. |
| 79 | + |
| 80 | +### Format |
| 81 | + |
| 82 | +``` |
| 83 | +<type>[optional scope]: <description> |
| 84 | +
|
| 85 | +[optional body] |
| 86 | +
|
| 87 | +[optional footer(s)] |
| 88 | +``` |
| 89 | + |
| 90 | +### Types |
| 91 | + |
| 92 | +- **feat**: A new feature |
| 93 | +- **fix**: A bug fix |
| 94 | +- **docs**: Documentation only changes |
| 95 | +- **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc) |
| 96 | +- **refactor**: A code change that neither fixes a bug nor adds a feature |
| 97 | +- **perf**: A code change that improves performance |
| 98 | +- **test**: Adding missing tests or correcting existing tests |
| 99 | +- **chore**: Changes to the build process or auxiliary tools |
| 100 | + |
| 101 | +### Examples |
| 102 | + |
| 103 | +``` |
| 104 | +feat(provider): add new flag evaluation method |
| 105 | +
|
| 106 | +fix(api): resolve nil pointer in evaluation context |
| 107 | +
|
| 108 | +docs: update installation instructions |
| 109 | +``` |
| 110 | + |
| 111 | +## Code Style Guidelines |
| 112 | + |
| 113 | +### Formatting & Imports |
| 114 | + |
| 115 | +- Use `gofumpt` for formatting (stricter than gofmt) |
| 116 | +- Use `gci` for import sorting (stdlib first, then external) |
| 117 | +- Run `make fix` before committing to auto-format |
| 118 | + |
| 119 | +### Linting |
| 120 | + |
| 121 | +This project uses golangci-lint with the following linters: |
| 122 | + |
| 123 | +- **staticcheck**: All checks enabled (SAxxxx rules) |
| 124 | +- **errcheck**: Error checking enabled |
| 125 | +- **govet**: All checks except fieldalignment and shadow |
| 126 | +- **usetesting**: Enforces testing best practices |
| 127 | +- **nolintlint**: Requires specific linter annotations (no unused nolint directives) |
| 128 | +- **modernize**: Suggests modern Go idioms and language features |
| 129 | +- **copyloopvar**: Detects loop variable copying bugs |
| 130 | +- **intrange**: Suggests using `for range` for integer loops |
| 131 | + |
| 132 | +Excluded rules: |
| 133 | + |
| 134 | +- G101 (hardcoded credentials detection - too noisy) |
| 135 | +- G404 (weak random number generation - used in tests) |
| 136 | + |
| 137 | +### Naming Conventions |
| 138 | + |
| 139 | +- **Variables**: camelCase (e.g., `clientMetadata`, `evalCtx`) |
| 140 | +- **Exported Types/Functions**: PascalCase (e.g., `Client`, `NewClient`) |
| 141 | +- **Interfaces**: PascalCase, typically I-prefixed (e.g., `IClient`, `IFeatureProvider`) or role-based (e.g., `FeatureProvider`) |
| 142 | +- **Constants**: PascalCase |
| 143 | + |
| 144 | +### Error Handling |
| 145 | + |
| 146 | +- Use `fmt.Errorf` with `%w` for error wrapping |
| 147 | +- Return errors with descriptive messages (lowercase, no punctuation) |
| 148 | +- Use sentinel errors from this package when applicable |
| 149 | +- Check errors explicitly: `if err != nil { ... }` |
| 150 | + |
| 151 | +### Context Usage |
| 152 | + |
| 153 | +- Context is the first parameter in functions that need it |
| 154 | +- Use `context.Background()` for top-level entry points |
| 155 | +- Pass context through call chains (don't store in structs) |
| 156 | +- Support cancellation where operations may be slow |
| 157 | + |
| 158 | +### Mutex Patterns |
| 159 | + |
| 160 | +- Use `sync.RWMutex` for read-heavy workloads |
| 161 | +- `RLock/RUnlock` for reads, `Lock/Unlock` for writes |
| 162 | +- Always defer unlock after locking |
| 163 | +- Never hold locks across function calls or goroutines |
| 164 | + |
| 165 | +### Documentation |
| 166 | + |
| 167 | +- Document all exported types and functions with // comments |
| 168 | +- Use proper Go doc comments starting with type/function name |
| 169 | +- Example: `// Client implements the behaviour required of an openfeature client` |
| 170 | +- Include usage examples in example test files (\*\_example_test.go) |
| 171 | + |
| 172 | +### Go Examples |
| 173 | + |
| 174 | +- Go examples are a critical part of documentation and testing |
| 175 | +- Place in `*_example_test.go` files next to the code they document |
| 176 | +- Naming convention: `Example<Type>_<Method>` for method examples, `Example<Type>` for type examples |
| 177 | +- Example function signature: `func ExampleClient_Boolean()` |
| 178 | +- Use `// Output:` comment to specify expected output (verified by `go test`) |
| 179 | +- Use `fmt.Println` for output to verify example behavior |
| 180 | +- External examples use package `openfeature_test` to test public API usage |
| 181 | +- Examples serve dual purpose: test correctness and generate godoc examples |
| 182 | +- Run examples with: `go test -tags testtools -run Example ./...` |
| 183 | + |
| 184 | +### Testing |
| 185 | + |
| 186 | +- Use standard `testing` package with `testtools` build tag |
| 187 | +- Use `t.Cleanup()` for cleanup operations |
| 188 | +- Use table-driven tests where appropriate |
| 189 | +- Place tests in `*_test.go` files next to implementation |
| 190 | +- E2E tests are in `/e2e` directory with `testtools` tag |
| 191 | + |
| 192 | +### Modern Go Guidelines |
| 193 | + |
| 194 | +#### Standard Library Packages |
| 195 | + |
| 196 | +- **`slices` package**: Use modern slice operations instead of manual implementations |
| 197 | +- **`maps` package**: Use modern map operations |
| 198 | + |
| 199 | +#### Modern Language Features |
| 200 | + |
| 201 | +- **For loop variable scoping**: Variables declared in `for` loops are created anew for each iteration |
| 202 | +- **Ranging over integers**: Use `for i := range n` instead of `for i := 0; i < n; i++` |
| 203 | +- **Generics**: Use type parameters for reusable, type-safe functions and types |
| 204 | +- **Type inference**: Leverage automatic type inference with `:=` when possible |
| 205 | + |
| 206 | +#### Modernization Tools |
| 207 | + |
| 208 | +- **`go fix`**: Automatically modernize code to use current Go features |
| 209 | + |
| 210 | + ```bash |
| 211 | + go fix ./... |
| 212 | + ``` |
| 213 | + |
| 214 | +- **`modernize` analyzer**: Integrated in golangci-lint (enabled by default) |
| 215 | + - Run `make fix` to apply modernize suggestions automatically |
| 216 | + |
| 217 | +#### Best Practices |
| 218 | + |
| 219 | +- **Slice initialization**: Prefer `var s []T` over `s := []T{}` or `make([]T, 0)` for nil slices |
| 220 | + |
| 221 | + ```go |
| 222 | + // Good |
| 223 | + var results []string |
| 224 | + results = append(results, "item") |
| 225 | + |
| 226 | + // Avoid |
| 227 | + results := []string{} |
| 228 | + ``` |
| 229 | + |
| 230 | +- **Preallocation**: Preallocate slice capacity when size is known |
| 231 | + |
| 232 | + ```go |
| 233 | + // Good |
| 234 | + items := make([]Item, 0, len(source)) |
| 235 | + for _, s := range source { |
| 236 | + items = append(items, Item(s)) |
| 237 | + } |
| 238 | + ``` |
| 239 | + |
| 240 | +- **Error handling**: Use `fmt.Errorf` with `%w` for wrapping errors |
| 241 | +- **Context**: Pass context as first parameter, support cancellation |
| 242 | +- **Concurrency**: Use `sync/errgroup` for managing goroutines with error handling |
| 243 | +- **Testing**: Use table-driven tests with `t.Cleanup()` for resource cleanup |
| 244 | + |
| 245 | +### General |
| 246 | + |
| 247 | +- Go version: 1.25 |
| 248 | +- Module: `go.openfeature.dev/openfeature/v2` |
| 249 | +- Mock generation: `make mockgen` |
| 250 | +- Run `make test && make lint` before committing |
0 commit comments