chore: add `AGENTS.md` (#7652)
* chore: add `AGENTS.md` Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * Apply suggestions from code review Co-authored-by: Francis Lavoie <lavofr@gmail.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com> * review feedback Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> --------- Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> Co-authored-by: Francis Lavoie <lavofr@gmail.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com>pull/7661/head
parent
24bebd0a07
commit
bd9f145321
|
|
@ -0,0 +1,217 @@
|
||||||
|
# Caddy Project Guidelines
|
||||||
|
|
||||||
|
## Mission
|
||||||
|
|
||||||
|
**Every site on HTTPS.** Caddy is a security-first, modular, extensible server platform.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
### Go Idioms
|
||||||
|
|
||||||
|
Follow [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments):
|
||||||
|
|
||||||
|
- **Error flow**: Early return, indent error handling—not else blocks
|
||||||
|
```go
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// normal code
|
||||||
|
```
|
||||||
|
- **Naming**: initialisms (`URL`, `HTTP`, `ID`—not `Url`, `Http`, `Id`)
|
||||||
|
- **Receiver names**: 1–2 letters reflecting type (`c` for `Client`, `h` for `Handler`)
|
||||||
|
- **Error strings**: Lowercase, no trailing punctuation (`"something failed"` not `"Something failed."`)
|
||||||
|
- **Doc comments**: Full sentences starting with the name being documented
|
||||||
|
```go
|
||||||
|
// Handler serves HTTP requests for the file server.
|
||||||
|
type Handler struct { ... }
|
||||||
|
```
|
||||||
|
- **Empty slices**: `var t []string` (nil slice), not `t := []string{}` (non-nil zero-length)
|
||||||
|
- **Don't panic**: Use error returns for normal error handling
|
||||||
|
|
||||||
|
### Caddy Patterns
|
||||||
|
|
||||||
|
**Module registration**:
|
||||||
|
```go
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(MyModule{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MyModule) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "namespace.category.name",
|
||||||
|
New: func() caddy.Module { return new(MyModule) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Module lifecycle**: `New()` → JSON unmarshal → `Provision()` → `Validate()` → use → `Cleanup()`
|
||||||
|
|
||||||
|
**Interface guards** — compile-time verification that modules implement required interfaces:
|
||||||
|
```go
|
||||||
|
var (
|
||||||
|
_ caddy.Provisioner = (*MyModule)(nil)
|
||||||
|
_ caddy.Validator = (*MyModule)(nil)
|
||||||
|
_ caddyfile.Unmarshaler = (*MyModule)(nil)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structured logging** — use the module-scoped logger from context:
|
||||||
|
```go
|
||||||
|
func (m *MyModule) Provision(ctx caddy.Context) error {
|
||||||
|
m.logger = ctx.Logger()
|
||||||
|
m.logger.Debug("provisioning", zap.String("field", m.Field))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caddyfile support** — implement `UnmarshalCaddyfile(*caddyfile.Dispenser)` using the `Dispenser` API:
|
||||||
|
```go
|
||||||
|
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
|
||||||
|
//
|
||||||
|
// directive [arg1] [arg2] {
|
||||||
|
// subdir value
|
||||||
|
// }
|
||||||
|
func (m *MyModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
d.Next() // consume directive name
|
||||||
|
for d.NextArg() {
|
||||||
|
// handle inline arguments
|
||||||
|
}
|
||||||
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
|
switch d.Val() {
|
||||||
|
case "subdir":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
m.Field = d.Val()
|
||||||
|
default:
|
||||||
|
return d.Errf("unrecognized subdirective: %s", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Admin API**: Implement `caddy.AdminRouter` for custom endpoints.
|
||||||
|
|
||||||
|
**Context**: Use `caddy.Context` for accessing other apps/modules and logging—don't store contexts in structs.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Caddy is built around a **module system** where everything is a module registered via `caddy.RegisterModule()`:
|
||||||
|
|
||||||
|
- **Apps** (`caddy.App`): Top-level modules like `http`, `tls`, `pki` that Caddy loads and runs
|
||||||
|
- **Modules** (`caddy.Module`): Extensible components with namespaced IDs (e.g., `http.handlers.file_server`)
|
||||||
|
- **Configuration**: Native JSON with adapters (Caddyfile → JSON via `caddyconfig/httpcaddyfile`)
|
||||||
|
|
||||||
|
| Directory | Purpose |
|
||||||
|
|-----------|---------|
|
||||||
|
| `modules/` | All standard modules (HTTP, TLS, PKI, etc.) |
|
||||||
|
| `modules/standard/imports.go` | Standard module registry |
|
||||||
|
| `caddyconfig/httpcaddyfile/` | Caddyfile → JSON adapter for HTTP |
|
||||||
|
| `caddytest/` | Test utilities and integration tests |
|
||||||
|
| `cmd/caddy/` | CLI entry point with module imports |
|
||||||
|
|
||||||
|
### Critical Packages
|
||||||
|
|
||||||
|
`caddyhttp` and `caddytls` require **extra scrutiny** in code review—these are security-critical.
|
||||||
|
|
||||||
|
## Quality Gates
|
||||||
|
|
||||||
|
|
||||||
|
**All required before PR is merge-ready:**
|
||||||
|
|
||||||
|
| Gate | Command | Notes |
|
||||||
|
|------|---------|-------|
|
||||||
|
| Tests pass | `go test -race -short ./...` | Race detection enabled |
|
||||||
|
| Lint clean | `golangci-lint run --timeout 10m` | No warnings in changed files |
|
||||||
|
| Builds | `go build ./...` | Must compile |
|
||||||
|
| Benchmarks | `go test -bench=. -benchmem` | Required for optimizations |
|
||||||
|
|
||||||
|
CI runs tests on **Linux, macOS, and Windows**—ensure cross-platform compatibility.
|
||||||
|
|
||||||
|
### Build & Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
cd cmd/caddy && go build
|
||||||
|
|
||||||
|
# Tests with race detection (matches CI)
|
||||||
|
go test -race -short ./...
|
||||||
|
|
||||||
|
# Integration tests
|
||||||
|
go test ./caddytest/integration/...
|
||||||
|
|
||||||
|
# Lint (matches CI)
|
||||||
|
golangci-lint run --timeout 10m
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Conventions
|
||||||
|
|
||||||
|
**Table-driven tests** (preferred pattern):
|
||||||
|
```go
|
||||||
|
func TestFeature(t *testing.T) {
|
||||||
|
for i, tc := range []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{input: "valid", expected: "result", wantErr: false},
|
||||||
|
{input: "invalid", expected: "", wantErr: true},
|
||||||
|
} {
|
||||||
|
actual, err := Function(tc.input)
|
||||||
|
if tc.wantErr && err == nil {
|
||||||
|
t.Errorf("Test %d: expected error but got none", i)
|
||||||
|
}
|
||||||
|
if !tc.wantErr && err != nil {
|
||||||
|
t.Errorf("Test %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
if actual != tc.expected {
|
||||||
|
t.Errorf("Test %d: expected %q, got %q", i, tc.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Integration tests** use `caddytest.Tester`:
|
||||||
|
```go
|
||||||
|
func TestHTTPFeature(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
admin localhost:2999
|
||||||
|
http_port 9080
|
||||||
|
}
|
||||||
|
localhost:9080 {
|
||||||
|
respond "hello"
|
||||||
|
}`, "caddyfile")
|
||||||
|
|
||||||
|
tester.AssertGetResponse("http://localhost:9080/", 200, "hello")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use non-standard ports (9080, 9443, 2999) to avoid conflicts with running servers.
|
||||||
|
|
||||||
|
## AI Contribution Policy
|
||||||
|
|
||||||
|
Per [CONTRIBUTING.md](.github/CONTRIBUTING.md), AI-assisted code **MUST** be:
|
||||||
|
|
||||||
|
1. **Disclosed** — Tell reviewers when code was AI-generated or AI-assisted, mentioning which agent/model is used
|
||||||
|
2. **Fully comprehended** — You must be able to explain every line
|
||||||
|
3. **Tested** — Automated tests when feasible, thorough manual tests otherwise
|
||||||
|
4. **Licensed** — Verify AI output doesn't include plagiarized or incompatibly-licensed code
|
||||||
|
5. **Contributor License Agreement (CLA)** — The CLA must be signed by the human user
|
||||||
|
|
||||||
|
**Do NOT submit code you cannot fully explain.** Contributors are responsible for their submissions.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **Avoid new dependencies** — Justify any additions; tiny deps can be inlined
|
||||||
|
- **No exported dependency types** — Caddy must not export types defined by external packages
|
||||||
|
- Use Go modules; check with `go mod tidy`
|
||||||
|
|
||||||
|
## Further Reading
|
||||||
|
|
||||||
|
- [CONTRIBUTING.md](.github/CONTRIBUTING.md) — Full PR process and expectations
|
||||||
|
- [Extending Caddy](https://caddyserver.com/docs/extending-caddy) — Module development guide
|
||||||
|
- [JSON Config](https://caddyserver.com/docs/json/) — Native configuration reference
|
||||||
|
- [Caddyfile](https://caddyserver.com/docs/caddyfile/concepts) — Caddyfile syntax guide
|
||||||
Loading…
Reference in New Issue