-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
chore: add AGENTS.md
#7652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+217
−0
Merged
chore: add AGENTS.md
#7652
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.