Add AI agent configuration files (AGENTS.md, .cursorrules, copilot-instructions)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 22:20:38 +03:00
parent 138d4b6c6d
commit 16ff427c93
3 changed files with 237 additions and 0 deletions

48
.cursorrules Normal file
View File

@@ -0,0 +1,48 @@
You are working on `git.codelab.vc/pkg/httpx`, a Go 1.24 HTTP client/server library with zero external dependencies.
## Architecture
- Client middleware: `func(http.RoundTripper) http.RoundTripper` — compose with `middleware.Chain`
- Server middleware: `func(http.Handler) http.Handler` — compose with `server.Chain`
- All configuration uses functional options pattern (`WithXxx` functions)
- Chain order for client: Logging → User MW → Retry → Circuit Breaker → Balancer → Transport
## Package structure
- `httpx` (root) — Client, request builders (NewJSONRequest, NewFormRequest), error types
- `middleware/` — client-side middleware (Logging, Recovery, Auth, Headers, RequestID)
- `retry/` — retry middleware with exponential backoff and Retry-After support
- `circuitbreaker/` — per-host circuit breaker (sync.Map of host → Breaker)
- `balancer/` — load balancing with health checking (RoundRobin, Weighted, Failover)
- `server/` — Server, Router, server middleware (RequestID, Recovery, Logging, CORS, RateLimit, MaxBodySize, Timeout), response helpers (WriteJSON, WriteError)
- `internal/requestid/` — shared context key (avoids circular import between server and middleware)
- `internal/clock/` — deterministic time for tests
## Code conventions
- Zero external dependencies — stdlib only, do not add imports outside the module
- Functional options: `type Option func(*options)` with `With<Name>` constructors
- Test with stdlib only: `testing`, `httptest`, `net/http`. No testify/gomock
- Client test helper: `mockTransport(fn)` wrapping `middleware.RoundTripperFunc`
- Server test helper: `httptest.NewRecorder`, `httptest.NewRequest`, `waitForAddr(t, srv)`
- Thread safety with `sync.Mutex`, `sync.Map`, or `atomic`
- Use `internal/clock` for time-dependent tests, not `time.Now()` directly
- Sentinel errors in sub-packages, re-exported as aliases in root package
## When writing new code
- Client middleware → file in `middleware/`, return `middleware.Middleware`
- Server middleware → file in `server/middleware_<name>.go`, return `server.Middleware`
- New option → add field to options struct, create `With<Name>` func, apply in constructor
- Do NOT import `server` from `middleware` or vice versa (use `internal/requestid` for shared context)
- Client.Close() must be called when using WithEndpoints() (stops health checker goroutine)
- Request bodies must have GetBody set for retry — use NewJSONRequest/NewFormRequest
## Commands
```bash
go build ./... # compile
go test ./... # test
go test -race ./... # test with race detector
go vet ./... # static analysis
```

50
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,50 @@
# Copilot instructions — httpx
## Project
`git.codelab.vc/pkg/httpx` is a Go 1.24 HTTP client and server library with zero external dependencies.
## Architecture
### Client (`httpx` root package)
- Middleware type: `func(http.RoundTripper) http.RoundTripper`
- Chain assembly (outermost → innermost): Logging → User MW → Retry → Circuit Breaker → Balancer → Transport
- Retry wraps CB+Balancer so each attempt can hit a different endpoint
- Circuit breaker is per-host (sync.Map of host → Breaker)
- Client.Close() required when using WithEndpoints() — stops health checker goroutine
### Server (`server/` package)
- Middleware type: `func(http.Handler) http.Handler`
- Router wraps http.ServeMux with groups, prefix routing, Mount for sub-handlers
- Defaults() preset: RequestID → Recovery → Logging + production timeouts
- Available middleware: RequestID, Recovery, Logging, CORS, RateLimit, MaxBodySize, Timeout
- WriteJSON/WriteError for JSON responses
### Sub-packages
- `middleware/` — client-side middleware (Logging, Recovery, Auth, Headers, RequestID)
- `retry/` — retry with exponential backoff and Retry-After
- `circuitbreaker/` — per-host circuit breaker
- `balancer/` — load balancing with health checking
- `internal/requestid/` — shared context key between server and middleware
- `internal/clock/` — deterministic time for tests
## Conventions
- All configuration uses functional options (`WithXxx` functions)
- Zero external dependencies — do not add requires to go.mod
- Tests use stdlib only (testing, httptest) — no testify or gomock
- Thread safety with sync.Mutex, sync.Map, or atomic
- Client test mock: `mockTransport(fn)` using `middleware.RoundTripperFunc`
- Server test helpers: `httptest.NewRecorder`, `httptest.NewRequest`
- Do NOT import server from middleware or vice versa — use internal/requestid for shared context
- Sentinel errors in sub-packages, re-exported in root package
- Use internal/clock for time-dependent tests
## Commands
```bash
go build ./... # compile
go test ./... # test
go test -race ./... # test with race detector
go vet ./... # static analysis
```

139
AGENTS.md Normal file
View File

@@ -0,0 +1,139 @@
# AGENTS.md — httpx
Universal guide for AI coding agents working with this codebase.
## Overview
`git.codelab.vc/pkg/httpx` is a Go HTTP toolkit with **zero external dependencies** (Go 1.24, stdlib only). It provides:
- A composable HTTP **client** with retry, circuit breaking, load balancing
- A production-ready HTTP **server** with routing, middleware, graceful shutdown
## Package map
```
httpx/ Root — Client, request builders, error types
├── middleware/ Client-side middleware (RoundTripper wrappers)
├── retry/ Retry middleware with backoff
├── circuitbreaker/ Per-host circuit breaker
├── balancer/ Client-side load balancing + health checking
├── server/ Server, Router, server-side middleware, response helpers
└── internal/
├── requestid/ Shared context key (avoids circular imports)
└── clock/ Deterministic time for testing
```
## Middleware chain architecture
### Client middleware: `func(http.RoundTripper) http.RoundTripper`
```
Request flow (outermost → innermost):
Logging
└→ User Middlewares
└→ Retry
└→ Circuit Breaker
└→ Balancer
└→ Base Transport (http.DefaultTransport)
```
Retry wraps CB+Balancer so each attempt can hit a different endpoint.
### Server middleware: `func(http.Handler) http.Handler`
```
Chain(A, B, C)(handler) == A(B(C(handler)))
A is outermost (sees request first, response last)
```
Defaults() preset: `RequestID → Recovery → Logging`
## Common tasks
### Add a client middleware
1. Create file in `middleware/` (or inline)
2. Return `middleware.Middleware` (`func(http.RoundTripper) http.RoundTripper`)
3. Use `middleware.RoundTripperFunc` for the inner adapter
4. Test with `middleware.RoundTripperFunc` as mock transport
```go
func MyMiddleware() middleware.Middleware {
return func(next http.RoundTripper) http.RoundTripper {
return middleware.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
// before
resp, err := next.RoundTrip(req)
// after
return resp, err
})
}
}
```
### Add a server middleware
1. Create file in `server/` named `middleware_<name>.go`
2. Return `server.Middleware` (`func(http.Handler) http.Handler`)
3. Use `server.statusWriter` if you need to capture the response status
4. Test with `httptest.NewRecorder` + `httptest.NewRequest`
```go
func MyMiddleware() Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// before
next.ServeHTTP(w, r)
// after
})
}
}
```
### Add a route
```go
r := server.NewRouter()
r.HandleFunc("GET /users/{id}", getUser)
r.HandleFunc("POST /users", createUser)
// Group with prefix + middleware
api := r.Group("/api/v1", authMiddleware)
api.HandleFunc("GET /items", listItems)
// Mount sub-handler
r.Mount("/health", server.HealthHandler())
```
### Add a functional option
1. Add field to the options struct (`clientOptions` or `serverOptions`)
2. Create `With<Name>` function returning `Option`
3. Apply the field in the constructor (`New`)
## Gotchas
- **Middleware order matters**: Retry wraps CB+Balancer intentionally — each retry attempt can hit a different endpoint and a different circuit breaker
- **Circular imports via `internal/`**: Both `server` and `middleware` packages need request ID context. The shared key lives in `internal/requestid` — do NOT import `server` from `middleware` or vice versa
- **Client.Close() is required** when using `WithEndpoints()` — the balancer starts a background health checker goroutine that must be stopped
- **GetBody for retries**: Request bodies must be replayable. Use `NewJSONRequest`/`NewFormRequest` (they set `GetBody`) or set it manually
- **statusWriter.Unwrap()**: Server middleware must not type-assert `http.ResponseWriter` directly — use `http.ResponseController` which calls `Unwrap()` to find `http.Flusher`, `http.Hijacker`, etc.
- **No external deps**: This is a zero-dependency library. Do not add any `require` to `go.mod`
## Commands
```bash
go build ./... # compile
go test ./... # all tests
go test -race ./... # tests with race detector
go test -v -run TestName ./package/ # single test
go vet ./... # static analysis
```
## Conventions
- **Functional options** for all configuration (client and server)
- **stdlib only** testing — no testify, no gomock
- **Thread safety** — use `sync.Mutex`, `sync.Map`, or `atomic` where needed
- **`internal/clock`** — use for deterministic time in tests (never `time.Now()` directly in testable code)
- **Test helpers**: `mockTransport(fn)` wrapping `middleware.RoundTripperFunc` (client), `httptest.NewRecorder`/`httptest.NewRequest` (server), `waitForAddr(t, srv)` for server integration tests
- **Sentinel errors** live in sub-packages, root package re-exports as aliases